cdesktopenv/cde/programs/dtsr/dtsrclean.c

1402 lines
45 KiB
C

/*
* CDE - Common Desktop Environment
*
* Copyright (c) 1993-2012, The Open Group. All rights reserved.
*
* These libraries and programs are free software; you can
* redistribute them and/or modify them under the terms of the GNU
* Lesser General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* These libraries and programs are distributed in the hope that
* they will be useful, but WITHOUT ANY WARRANTY; without even the
* implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with these libraries and programs; if not, write
* to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
* Floor, Boston, MA 02110-1301 USA
*/
/*
* COMPONENT_NAME: austext
*
* FUNCTIONS: TERMINATE_LINE
* copy_new_d99
* copy_old_d2x_to_new
* end_of_job
* main
* open_all_files
* print_progress
* print_usage
* read_d2x
* signal_shutdown
* user_args_processor
* validation_error
* write_d2x
*
* ORIGINS: 27
*
*
* (C) COPYRIGHT International Business Machines Corp. 1993,1995
* All Rights Reserved
* Licensed Materials - Property of IBM
* US Government Users Restricted Rights - Use, duplication or
* disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
*/
/*************************** DTSRCLEAN.C ****************************
* $TOG: dtsrclean.c /main/9 1998/04/17 11:23:57 mgreess $
* Does garbage collection (ie compression) of .d99 file.
* Optionally verifies all database addresses in d99.
* Modification of clndtbs.c and checkd99.c.
* Does NOT use austext engine so this must be modified if schema changes.
*
* INPUT FORMAT:
* All command input is on command line. Reads existing d2x and d99 files.
*
* OUTPUT FORMAT:
* New .d2x and .d99 files are placed into the directory specified by user.
*
* EXIT CODE STANDARDS:
* 0 = normal.
* 1 = warnings, but output should be ok.
* 2 = failure in cmd line parse or other initialization; job never started.
* 3 - 49 = fatal error, but output may be acceptable.
* 50 - 99 = fatal error and output files are probably unusable.
* (In this program, even input may be corrupted).
* 100+ = aborting due to asynchronous interrupt signal.
* Output files may or may not be unusable.
*
* $Log$
* Revision 2.4 1996/05/08 16:20:50 miker
* Added RENFILEs for new d2x files; austext_dopen no longer does.
*
* Revision 2.3 1996/02/01 18:13:06 miker
* Deleted BETA definition.
*
* Revision 2.2 1995/10/26 14:51:08 miker
* Renamed from mrclean.c. Added prolog.
*
* Log: mrclean.c,v
* Revision 2.1 1995/09/22 21:18:52 miker
* Freeze DtSearch 0.1, AusText 2.1.8
*
* Revision 1.11 1995/09/05 18:16:46 miker
* Name, msg, and other minor changes for DtSearch..
* Print messages if austext_dopen() fails.
*
* Revision 1.10 1995/06/02 15:52:42 miker
* Cleaned up -m and bit vector overflow msgs.
*
* Revision 1.9 1995/05/30 19:15:58 miker
* Print beta char in startup banner msg.
* Remove -m option and max_totrecs; select bit vector
* size from maxdba, not reccount.
*/
#include "SearchP.h"
#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <locale.h>
#include "vista.h"
#include <sys/types.h>
#include <netinet/in.h>
#define MS_misc 1 /* msg catalog set number */
#define MS_dtsrclean 26 /* msg catalog set number */
#define DISCARD_FORMAT "%s\t\"%s\"\t%s\t%s\n" /* copied from oe.h */
#define RECS_PER_DOT 1000
#define DOTS_PER_MSG 50
#define DISK_BLKSIZE 512
#define MAX_CORRUPTION 100
#define MAX_REC_READ (DISK_BLKSIZE / sizeof(DB_ADDR))
/*
* Max number of addresses to be read from database addresses
* file, ie the size of one block read from hard disk.
*/
#define PROGNAME "DTSRCLEAN"
#define SHOW_NOTHING 0 /* bit arguments for end_of_job() */
#define SHOW_USAGE 1
#define SHOW_EXITCODE 2
#define SHOW_PROGRESS 4
#define TERMINATE_LINE() if(need_linefeed){fputc('\n',aa_stderr);need_linefeed=FALSE;}
/*-------------------------- GLOBALS ----------------------------*/
static char *arg_dbname = NULL;
static char *arg_newpath = NULL;
unsigned char *bit_vector = NULL;
static size_t bytes_in = 0L;
static size_t corruption_count = 0L;
static struct or_swordrec
d21new, d21old;
static struct or_lwordrec
d22new, d22old;
static struct or_hwordrec
d23new, d23old;
static char datestr[32] = ""; /* "1946/04/17 13:03" */
static int debug_mode = FALSE;
static size_t dot_count = 0L;
char fname_d99_new[1024];
char fname_d99_old[1024];
FILE *fp_d99_new = NULL;
FILE *fp_d99_old = NULL;
static FILE *frecids = NULL;
static int is_valid_dba;
static size_t max_corruption = MAX_CORRUPTION;
static int normal_exitcode = 0;
static int need_linefeed = FALSE;
static int overlay_no = FALSE;
static int overlay_yes = FALSE;
static DtSrINT32
reccount = 0;
static DtSrINT32
recslots; /* dbrec.or_recslots promoted to INT32 */
static DtSrINT32
dba_offset;
static DtSrINT32
recs_per_dot = RECS_PER_DOT;
static int rewrite_reccount = FALSE;
static int shutdown_now = 0; /* = FALSE */
static size_t size_d21_old = 0L;
static size_t size_d22_old = 0L;
static size_t size_d23_old = 0L;
static size_t size_d99_old = 0L;
static time_t timestart = 0L;
static DtSrINT32
total_num_addrs = 0;
static int validation_mode = FALSE;
/********************************************************/
/* */
/* signal_shutdown */
/* */
/********************************************************/
/* interrupt handler for SIGINT */
static void signal_shutdown (int sig)
{
shutdown_now = 100 + sig;
return;
} /* signal_shutdown() */
/************************************************/
/* */
/* print_usage */
/* */
/************************************************/
/* Prints usage statement to stderr. */
static void print_usage (void)
{
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 1,
"\nUSAGE: %s [options] <dbname> <newpath>\n"
" Compresses unused d99 space and validates d00-d99 links.\n"
" -p<N> Progress dots printed every <N> records (default %lu).\n"
" Complete progress message printed every %d dots.\n"
" -oy Authorizes overlaying preexisting d99/d2<N> files in newpath.\n"
" -on Forces exit if preexisting d99/d2<N> files in newpath.\n"
" -v Validates d99 and d00 links, uncorrupts d99 file, and ensures\n"
" accurate record count. Also use -c0 to uncorrupt entire database.\n"
" -v<fname> Same as -v but also writes all d00 recs unreferenced by d99\n"
" to <fname> in format suitable to extract into .fzk file format.\n"
" -c<N> Exits if more than <N> corrupted/incomplete links (default %d).\n"
" Corruption limit turned off by -c0.\n"
" <dbname> 1 - 8 char database name = the old d99/d2<N> files to be updated.\n"
" Files found in local directory or DBFPATH environment variable.\n"
" <newpath> Specifies where the new d99/d2<N> files will be placed.\n"
" If first char is not slash, path is relative to local directory.\n"
"EXIT CODES:\n"
" 0: Complete success. 1: Warning. 2: Job never started.\n"
" 3-49: Job ended prematurely, old files ok, new files unusable.\n"
" 50-99: Fatal Error, even old database may be corrupted.\n"
" 100+: Ctrl-C, kill, and all other signal interrupts cause premature\n"
" end, new files may be unusable. Signal = exit code - 100.\n")
,aa_argv0, RECS_PER_DOT, DOTS_PER_MSG, MAX_CORRUPTION);
return;
} /* print_usage() */
/************************************************/
/* */
/* print_progress */
/* */
/************************************************/
/* Prints progress msg after dots or at end of job.
* Label is "Final" or "Progress".
*/
static void print_progress (char *label)
{
long seconds;
int compression;
seconds = time (NULL) - timestart; /* total seconds elapsed */
if (seconds < 0L)
seconds = 0L;
if ((float) bytes_in / (float) size_d99_old >= 99.5)
compression = 100;
else {
compression = (int) (100.* (float) bytes_in / (float) size_d99_old);
if (compression < 0 || compression > 100)
compression = 0;
}
TERMINATE_LINE ();
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 2,
"%s: %s Compression %d%% (about %lu KB) in %ld:%02ld min:sec.\n") ,
aa_argv0, label, compression, bytes_in / 1000L,
seconds / 60UL, seconds % 60UL);
if (*label == 'F')
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 3,
"%s: Counted %ld WORDS in %s.d99.\n") ,
aa_argv0, (long)reccount, arg_dbname);
return;
} /* print_progress() */
/************************************************/
/* */
/* end_of_job */
/* */
/************************************************/
/* Exits program. Prints status messages before going down.
* Should be called on even record boundaries whenever possible,
* ie after record writes complete and shutdown_now > 0 (TRUE).
*/
static void end_of_job (int exitcode, int show_flags)
{
TERMINATE_LINE ();
if (exitcode >= 100) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 66,
"%s Aborting after interrupt signal %d.\n"),
PROGNAME"66", exitcode - 100);
}
if (validation_mode && corruption_count == 0L)
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 4,
"%s: No corrupted links detected.\n") ,
aa_argv0);
if (corruption_count > 0L) {
if (max_corruption > 0L && corruption_count >= max_corruption)
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 193,
"%s Aborting at %ld corrupted links.\n"),
PROGNAME"193", corruption_count);
else
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 194,
"%s Detected%s %ld corrupted/incomplete link(s).\n"),
PROGNAME"194",
(validation_mode) ? " and corrected" : "",
corruption_count);
}
if (show_flags & SHOW_PROGRESS) {
print_progress ("Final");
}
if (show_flags & SHOW_USAGE)
print_usage ();
if (show_flags & SHOW_EXITCODE)
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 5,
"%s: Exit code = %d.\n") , aa_argv0, exitcode);
DtSearchExit (exitcode);
} /* end_of_job() */
/************************************************/
/* */
/* user_args_processor() */
/* */
/************************************************/
/* Reads and verifies users command line arguments and
* converts them into internal switches and variables.
* Some attempt is made to read as many errors as possible
* before ending job for bad arguments.
*/
static void user_args_processor (int argc, char **argv)
{
char *argptr;
int oops = FALSE;
int i;
time_t stamp;
size_t tempsize;
if (argc < 3)
end_of_job (2, SHOW_USAGE);
/* parse all args that begin with a dash (-) */
while (--argc > 0) {
argv++;
argptr = argv[0];
if (argptr[0] != '-')
break;
switch (tolower (argptr[1])) {
case 'r':
if (strcmp (argptr, "-russell") == 0) /* backdoor debug */
debug_mode = TRUE;
else
goto UNKNOWN_ARG;
case 'm':
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 301,
"%s The -m argument is no longer necessary.\n"),
PROGNAME"301");
break;
case 'o':
i = tolower (argptr[2]);
if (i == 'n')
overlay_no = TRUE;
else if (i == 'y')
overlay_yes = TRUE;
else {
INVALID_ARG:
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 177,
"%s Invalid %.2s argument.\n"),
PROGNAME"177", argptr);
oops = TRUE;
}
break;
case 'v':
validation_mode = TRUE;
if (argptr[2] != '\0') {
if ((frecids = fopen (argptr + 2, "w")) == NULL) {
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 802,
"%s Unable to open '%s' to output"
" unreferenced d00 records:\n %s\n"),
PROGNAME"802", argptr, strerror(errno));
oops = TRUE;
}
time (&stamp);
strftime (datestr, sizeof (datestr),
"%Y/%m/%d %H:%M", localtime (&stamp));
}
break;
case 'p':
recs_per_dot = (DtSrINT32) atol (argptr + 2);
if (recs_per_dot <= 0)
goto INVALID_ARG;
break;
case 'c':
tempsize = atol (argptr + 2);
if (tempsize < 0L)
goto INVALID_ARG;
max_corruption = tempsize;
break;
UNKNOWN_ARG:
default:
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 159,
"%s Unknown argument: '%s'.\n"),
PROGNAME"159", argptr);
oops = TRUE;
break;
} /* end switch */
} /* end parse of cmd line args */
/* Test how we broke loop.
* There should still be 2 args past the ones
* beginning with a dash: dbname and newpath.
*/
if (argc != 2) {
if (argc <= 0)
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 210,
"%s Missing required dbname argument.\n"),
PROGNAME"210");
if (argc <= 1)
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 211,
"%s Missing required newpath argument.\n"),
PROGNAME"211");
if (argc > 2)
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 212,
"%s Too many arguments.\n"),
PROGNAME"212");
oops = TRUE;
}
if (oops)
end_of_job (2, SHOW_USAGE);
/* DBNAME */
arg_dbname = argv[0];
if (strlen (arg_dbname) > 8) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 229,
"%s Invalid database name '%s'.\n"),
PROGNAME"229", arg_dbname);
end_of_job (2, SHOW_USAGE);
}
/* NEWPATH:
* Oldpath and newpath are validated when the files
* are copied and the database is opened.
*/
arg_newpath = argv[1];
return;
} /* user_args_processor() */
/************************************************/
/* */
/* validation_error() */
/* */
/************************************************/
/* Subroutine of validation_mode in main().
* Prints d2x and d99 data at location of error.
* Adjusts d2x counts for number of good addrs and free slots.
*/
static void validation_error (DB_ADDR dbaorig)
{
DB_ADDR slot;
is_valid_dba = FALSE;
slot = dbaorig >> 8;
/* now efim retranslates back to real dba */
if (dbaorig != -1)
slot = ((slot + 1) * recslots - dba_offset)
| (OR_D00 << 24);
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 6,
" DBA = %d:%ld (x%02x:%06lx), orig addr val = x%08lx\n"
" Word='%c%s' offset=%ld addrs=%ld free=%d\n") ,
OR_D00, slot, OR_D00, slot, dbaorig,
(!isgraph (d23old.or_hwordkey[0])) ? '^' : d23old.or_hwordkey[0],
d23old.or_hwordkey + 1, d23old.or_hwoffset,
d23old.or_hwaddrs, d23old.or_hwfree);
if (--d23new.or_hwaddrs < 0L)
d23new.or_hwaddrs = 0L;
/* (should never occur) */
d23new.or_hwfree++;
return;
} /* validation_error() */
/************************************************/
/* */
/* open_all_files */
/* */
/************************************************/
static void open_all_files
(FILE ** fp, char *fname, char *mode, size_t * size, int *oops) {
struct stat fstatbuf;
if ((*fp = fopen (fname, mode)) == NULL) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 439,
"%s Can't open %s: %s\n"),
PROGNAME"439", fname, strerror (errno));
*oops = TRUE;
return;
}
if (fstat (fileno (*fp), &fstatbuf) == -1) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 440,
"%s Can't access status of %s: %s\n"),
PROGNAME"440", fname, strerror (errno));
*oops = TRUE;
return;
}
if (size)
if ((*size = fstatbuf.st_size) <= 0L) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 499,
"%s %s is empty.\n"),
PROGNAME"499", fname);
*oops = TRUE;
}
return;
} /* open_all_files() */
/************************************************/
/* */
/* copy_old_d2x_to_new */
/* */
/************************************************/
static void copy_old_d2x_to_new
(char *fname_old, char *fname_new, FILE * fp_old, FILE * fp_new) {
char readbuf[1024 + 32];
int i, j;
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 7,
"%s: Copying from old d2x files to %s...\n") ,
aa_argv0, fname_new);
for (;;) { /* loop ends when eof set on input stream */
errno = 0;
i = fread (readbuf, 1, sizeof (readbuf), fp_old);
/* byte swap not required on pure copy operation */
if (errno) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 517,
"%s Read error on %s: %s.\n"),
PROGNAME"517", fname_old, strerror (errno));
end_of_job (3, SHOW_EXITCODE);
}
j = fwrite (readbuf, 1, i, fp_new);
if (i != j) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 489,
"%s Write error on %s: %s.\n"),
PROGNAME"489", fname_new, strerror (errno));
end_of_job (3, SHOW_EXITCODE);
}
if (shutdown_now)
end_of_job (shutdown_now, SHOW_EXITCODE);
if (feof (fp_old))
break;
}
TERMINATE_LINE ();
fclose (fp_old);
fclose (fp_new);
return;
} /* copy_old_d2x_to_new() */
/********************************/
/* */
/* read_d2x */
/* */
/********************************/
/* Performs vista RECREAD on curr word record.
* CALLER SHOULD CHECK DB_STATUS.
*/
void read_d2x (struct or_hwordrec * glob_word, long field)
{
if (field == OR_SWORDKEY) {
RECREAD (PROGNAME "061", &d21old, 0);
if (db_status != S_OKAY)
return;
strncpy (glob_word->or_hwordkey, d21old.or_swordkey,
DtSrMAXWIDTH_HWORD);
glob_word->or_hwordkey[DtSrMAXWIDTH_HWORD - 1] = 0;
glob_word->or_hwoffset = ntohl (d21old.or_swoffset);
glob_word->or_hwfree = ntohl (d21old.or_swfree);
glob_word->or_hwaddrs = ntohl (d21old.or_swaddrs);
}
else if (field == OR_LWORDKEY) {
RECREAD (PROGNAME "069", &d22old, 0);
if (db_status != S_OKAY)
return;
strncpy (glob_word->or_hwordkey, d22old.or_lwordkey,
DtSrMAXWIDTH_HWORD);
glob_word->or_hwordkey[DtSrMAXWIDTH_HWORD - 1] = 0;
glob_word->or_hwoffset = ntohl (d22old.or_lwoffset);
glob_word->or_hwfree = ntohl (d22old.or_lwfree);
glob_word->or_hwaddrs = ntohl (d22old.or_lwaddrs);
}
else {
RECREAD (PROGNAME "078", glob_word, 0);
glob_word->or_hwordkey[DtSrMAXWIDTH_HWORD - 1] = 0;
NTOHL (glob_word->or_hwoffset);
NTOHL (glob_word->or_hwfree);
NTOHL (glob_word->or_hwaddrs);
}
return;
} /* read_d2x() */
/********************************/
/* */
/* write_d2x */
/* */
/********************************/
/* performs vista RECWRITE on curr word record.
* CALLER MUST CHECK DB_STATUS.
*/
static void write_d2x (struct or_hwordrec * glob_word, long field)
{
if (field == OR_SWORDKEY) {
snprintf(d21new.or_swordkey, 16, "%s", glob_word->or_hwordkey);
d21new.or_swoffset = htonl (glob_word->or_hwoffset);
d21new.or_swfree = htonl (glob_word->or_hwfree);
d21new.or_swaddrs = htonl (glob_word->or_hwaddrs);
RECWRITE (PROGNAME "102", &d21new, 0);
}
else if (field == OR_LWORDKEY) {
snprintf(d22new.or_lwordkey, 40, "%s", glob_word->or_hwordkey);
d22new.or_lwoffset = htonl (glob_word->or_hwoffset);
d22new.or_lwfree = htonl (glob_word->or_hwfree);
d22new.or_lwaddrs = htonl (glob_word->or_hwaddrs);
RECWRITE (PROGNAME"112", &d22new, 0);
}
else {
HTONL (glob_word->or_hwoffset);
HTONL (glob_word->or_hwfree);
HTONL (glob_word->or_hwaddrs);
RECWRITE (PROGNAME "115", glob_word, 0);
}
return;
} /* write_d2x() */
/************************************************/
/* */
/* copy_new_d99() */
/* */
/************************************************/
/* The garbage collection/compression process itself.
* For very large databases, there will be appx 3 million word records,
* so the loop should be coded for ***EFFICIENCY***.
*/
static void copy_new_d99 (long keyfield)
{
int is_odd_nibble;
DtSrINT32 num_holes;
DtSrINT32 slots_left;
unsigned char *bvptr;
int a;
DB_ADDR dba, dbaorig;
DtSrINT32 x;
DtSrINT32 swapx;
int done;
DtSrINT32 good_addrs_left;
DtSrINT32 good_addrs_this_block;
DtSrINT32 num_reads, num_writes;
DB_ADDR word_addrs[MAX_REC_READ + 64]; /* d99 read buf */
DB_ADDR word_addrs_out[MAX_REC_READ + 64]; /* d99 write buf */
KEYFRST (PROGNAME "179", keyfield, 0);
while (db_status == S_OKAY) {
read_d2x (&d23new, keyfield);
if (validation_mode) /* save for validation err msgs */
memcpy (&d23old, &d23new, sizeof (d23old));
/*
* Read old d99 file at specified offset to get total num
* "holes". In the first portion of record holes are filled
* with representations of valid database addresses +
* statistical weights. In the second portion the holes are
* "free slots" for future expansion which are
* conventionally initialized with a -1.
*/
/* force number of free slots to 0(ZERO) */
d23new.or_hwfree = 0;
fseek (fp_d99_old, d23new.or_hwoffset, SEEK_SET);
num_holes = d23new.or_hwaddrs + d23new.or_hwfree;
good_addrs_left = d23new.or_hwaddrs;
bytes_in += sizeof (DB_ADDR) * num_holes;
/* Update the offset in the d2x record buffer */
d23new.or_hwoffset = ftell (fp_d99_new);
/*
* Copy the array of holes in each disk block, reading the
* old and writing to the new. Loop ends when the number
* of holes left will fit into one last block.
*/
done = FALSE;
while (!done) { /* loop on each block in this word */
int ret = 0;
if (num_holes > MAX_REC_READ) {
num_reads = MAX_REC_READ;
num_holes -= MAX_REC_READ;
}
else {
done = TRUE;
num_reads = num_holes;
}
errno = 0;
ret = fread (word_addrs, sizeof(DB_ADDR), (size_t)num_reads, fp_d99_old);
if (errno || -1 == ret) {
TERMINATE_LINE ();
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 657,
"%s Read error on %s: %s.\n"),
PROGNAME"657", fname_d99_old, strerror (errno));
end_of_job (4, SHOW_PROGRESS + SHOW_EXITCODE);
}
/* Note BYTE_SWAP only needed for validation_mode.
* If not validating, we're just going to copy
* the network format dba's as is directly to
* the new d99 file.
*/
/*
* Addrs on d99 are now 'record numbers' not dbas. A
* rec# is what the dba/slot# would be if records took
* up just one slot and there were no dbrec at start of
* file. D99 rec#s start at #1, not #0.
*/
/*
* If user requested validation_mode, validate each
* 'good' rec# (not free slots) in word_addrs buffer.
* If any d99 links are corrupt, skip them when copying
* to the new d99 file. Rewrite -1's to all free slots.
* ----> NOTE UNUSUAL FORMAT OF DBA HOLES IN D99! <----
* Record number is shifted to the high order 3 bytes.
* The statistical weight is in the low order byte. The
* vista file number is known from the #define constant
* OR_D00, and the vista dba/slot# is mapped from rec#
* by mult/div number of slots per rec, plus/minus
* dbrec offset.
*/
if (validation_mode) {
#ifdef BYTE_SWAP
for (swapx = 0; swapx < num_reads; swapx++)
NTOHL (word_addrs[swapx]);
#endif
/* set x to number of good addrs in this block */
if (good_addrs_left > num_reads) {
x = num_reads;
good_addrs_left -= num_reads;
}
else {
x = good_addrs_left;
good_addrs_left = 0;
}
/*
* Validate the rec#'s in this block. Note that
* the loop is skipped if the entire block is free
* slots.
*/
good_addrs_this_block = 0;
for (a = 0; a < x; a++) { /* a = index to curr dba */
/*
* Get rec#. Save original rec# for err msgs,
* then shift slot number to lower 3 bytes,
* discarding weight.
*/
dbaorig = word_addrs[a]; /* rec#,rec#,rec#:wt */
dba = dbaorig >> 8; /* 0,rec#,rec#,rec# */
is_valid_dba = TRUE; /* default */
/*
* If original rec# == -1 we've overrun the
* good rec#'s into the expansion area, which
* is filled with -1's. This is real bad news
* because if the counts in d02 are bad, the
* online programs will quickly crash, and we
* can't continue this program. Advance to next
* rec# because we can't mark the bit vector.
*/
if (dbaorig == -1L) {
TERMINATE_LINE ();
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 111,
"*** %s DBA in d99 = -1. "
"Probable overrun into expansion\n"
" area due to incorrect count values "
"in d2x file.\n"),
PROGNAME"111");
validation_error (dbaorig);
corruption_count++;
if (max_corruption > 0L &&
corruption_count >= max_corruption)
end_of_job (91, SHOW_PROGRESS + SHOW_EXITCODE);
continue; /* skip the bit vector
* check */
}
/*
* If slot number > max totrecs, we have a
* corrupted d99-d00 link because we've already
* validated the d00 file and we know that it
* has no slots > max. Also we have to advance
* to next slot because we can't mark the bit
* vector.
*/
/******if (dba >= max_totrecs)*******/
if (dba >= total_num_addrs) {
TERMINATE_LINE ();
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 222,
"*** %s DBA in d99 not in d00,"
" slot > max num docs.\n"),
PROGNAME"222");
validation_error (dbaorig);
corruption_count++;
if (max_corruption > 0L &&
corruption_count >= max_corruption)
end_of_job (92, SHOW_PROGRESS + SHOW_EXITCODE);
continue; /* skip the bit vector check */
}
/*
* Verify that dba exists in d00 file (test bit
* #1). If not, mark bit #3 (3rd lowest) in
* nibble and print error msg unless bit #3
* previously marked.
*/
bvptr = bit_vector + (dba >> 1);
is_odd_nibble = (dba & 1L);
if (!(*bvptr & ((is_odd_nibble) ? 0x01 : 0x10))) {
/* bit #1 */
if (!(*bvptr & ((is_odd_nibble) ? 0x04 : 0x40))) {
/* bit #3 */
*bvptr |= (is_odd_nibble) ? 0x04 : 0x40;
TERMINATE_LINE ();
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 333,
"*** %s DBA in d99 does not exist in d00.\n"),
PROGNAME"333");
validation_error (dbaorig);
corruption_count++;
if (max_corruption > 0L &&
corruption_count >= max_corruption)
end_of_job (93, SHOW_PROGRESS + SHOW_EXITCODE);
} /* endif where corrupt link
* detected */
}
/*
* Mark bit #2 in bit vector indicating a d99
* reference.
*/
*bvptr |= (is_odd_nibble) ? 0x02 : 0x20; /* bit #2 */
/*
* move good dba to curr output block, incr
* counter
*/
if (is_valid_dba)
word_addrs_out[good_addrs_this_block++] = dbaorig;
} /* end validation loop for each good dba in
* the block */
/*
* Write out only validated addrs in current block.
* If this was the last block, fill out all the
* free slots, if any, with -1 values, and exit the
* dba loop for this word.
*/
if (good_addrs_this_block > 0) {
#ifdef BYTE_SWAP
for (swapx = 0; swapx < good_addrs_this_block; swapx++)
NTOHL (word_addrs_out[swapx]);
#endif
num_writes = fwrite (word_addrs_out, sizeof (DB_ADDR),
(size_t)good_addrs_this_block, fp_d99_new);
if (num_writes != good_addrs_this_block)
goto WRITE_ERROR;
}
if (good_addrs_left <= 0) {
/*
* Write blocks of -1s until new d2x free slot
* count is exhausted. The last block may be <
* MAX_REC_READ.
*/
slots_left = d23new.or_hwfree;
while (slots_left > 0) {
/*
* set x to number of -1's to write for
* this block
*/
if (slots_left > MAX_REC_READ) {
x = MAX_REC_READ;
slots_left -= MAX_REC_READ;
}
else {
x = slots_left;
slots_left = 0;
}
for (a = 0; a < x; a++)
word_addrs_out[a] = (DtSrINT32) -1;
/* BYTE_SWAP not required for foxes */
num_writes = fwrite (word_addrs_out,
sizeof(DB_ADDR), (size_t)x, fp_d99_new);
if (num_writes != x)
goto WRITE_ERROR;
} /* end while loop to write out all -1's */
done = TRUE;
}
} /* endif for validation_mode for this block */
/*
* If NOT in validation mode, just write out the new
* d99 block as an exact copy of the input block.
* BYTE_SWAP not required because word_addrs is
* still in its original network order from the fread.
*/
else {
num_writes = fwrite (word_addrs, sizeof(DB_ADDR),
(size_t)num_reads, fp_d99_new);
if (num_writes != num_reads) {
WRITE_ERROR:
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 665,
"%s Write error on %s: %s.\n"),
PROGNAME"665", fname_d99_new, strerror(errno));
end_of_job (4, SHOW_PROGRESS + SHOW_EXITCODE);
}
} /* endelse for NOT validation_mode for this block */
} /* end loop for all blocks for this entire word
* (done = TRUE) */
/* write the updated d2x record */
write_d2x (&d23new, keyfield);
reccount++;
/*
* Every now and then print a dot. Print complete progress
* msg after DOTS_PER_MSG dots.
*/
if (!(reccount % recs_per_dot)) {
if (++dot_count > DOTS_PER_MSG) {
dot_count = 0;
print_progress ("Progress");
}
else {
fputc ('.', aa_stderr);
need_linefeed = TRUE;
if (!(dot_count % 10L))
fputc (' ', aa_stderr);
}
fflush (aa_stderr);
} /* end of print-a-dot */
if (shutdown_now)
end_of_job (shutdown_now, SHOW_PROGRESS + SHOW_EXITCODE);
KEYNEXT (PROGNAME "196", keyfield, 0);
} /* end of main loop on each word in database */
return;
} /* copy_new_d99() */
/************************************************/
/* */
/* main() */
/* */
/************************************************/
int main (int argc, char *argv[])
{
FILE_HEADER fl_hdr;
int a, i, j;
unsigned char *bvptr;
DB_ADDR dba, dba1, dbaorig;
char dbfpath[1024];
char fname_d21_new[1024];
char fname_d21_old[1024];
char fname_d22_new[1024];
char fname_d22_old[1024];
char fname_d23_new[1024];
char fname_d23_old[1024];
FILE *fp_d21_new = NULL;
FILE *fp_d21_old = NULL;
FILE *fp_d22_new = NULL;
FILE *fp_d22_old = NULL;
FILE *fp_d23_new = NULL;
FILE *fp_d23_old = NULL;
char full_dbname_old[1024];
char full_dbname_new[1024];
DtSrINT32 max_bitvec = 0L;
int oops;
char *ptr;
char readbuf[1024 + 32];
unsigned long reads_per_dot;
char recidbuf[DtSrMAX_DB_KEYSIZE + 4];
time_t starttime;
DtSrINT32 x;
struct or_dbrec dbrec;
aa_argv0 = argv[0];
setlocale (LC_ALL, "");
dtsearch_catd = catopen (FNAME_DTSRCAT, 0);
time (&starttime);
strftime (dbfpath, sizeof (dbfpath), /* just use any ol' buffer */
catgets (dtsearch_catd, MS_misc, 22, "%A, %b %d %Y, %I:%M %p"),
localtime (&starttime));
printf ( catgets(dtsearch_catd, MS_dtsrclean, 11,
"%s Version %s. Run %s.\n") ,
aa_argv0, AUSAPI_VERSION, dbfpath);
signal (SIGHUP, signal_shutdown);
signal (SIGINT, signal_shutdown);
signal (SIGQUIT, signal_shutdown);
signal (SIGTRAP, signal_shutdown);
signal (SIGKILL, signal_shutdown); /* this cannot be trapped */
signal (SIGALRM, signal_shutdown);
signal (SIGTERM, signal_shutdown);
#ifdef SIGPWR
signal (SIGPWR, signal_shutdown);
#endif
#ifdef _AIX
signal (SIGXCPU, signal_shutdown);
signal (SIGDANGER, signal_shutdown);
#endif
user_args_processor (argc, argv);
/* In order to find old files, we have to check if
* DBFPATH environment variable has been set.
* Load the fully constructed DBFPATH-dbname into its own buffer.
*/
full_dbname_old[0] = '\0';
dbfpath[0] = 0;
if ((ptr = getenv ("DBFPATH")) != NULL) {
if (*ptr == 0)
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 12,
"%s: Ignoring empty DBFPATH environment variable.\n") ,
aa_argv0);
else {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 13,
"%s: Using DBFPATH = '%s'.\n") ,
aa_argv0, ptr);
snprintf(full_dbname_old, sizeof(full_dbname_old), "%s", ptr);
/* Ensure that DBFPATH ends in a slash. */
ptr = strchr (full_dbname_old, '\0');
if (*(ptr - 1) != LOCAL_SLASH) {
*ptr++ = LOCAL_SLASH;
*ptr = '\0';
}
strcpy (dbfpath, full_dbname_old);
}
}
/* Currently full_dbname_old contains just the path.
* Similarly, build just path name for the 2 new files
* using full_dbname_new as a buffer.
* Verify they don't both refer to the same directory.
*/
strcpy (full_dbname_new, arg_newpath);
ptr = strchr (full_dbname_new, '\0');
if (*(ptr - 1) != LOCAL_SLASH) {
*ptr++ = LOCAL_SLASH;
*ptr = '\0';
}
if (strcmp (full_dbname_old, full_dbname_new) == 0) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 393,
"%s Old and new directories are identical: '%s'.\n"),
PROGNAME"393", full_dbname_old);
end_of_job (2, SHOW_USAGE);
}
/* Complete full_dbname_old by appending dbname to the path prefix.
* Then build full path/file names for all 4 files.
*/
strcat (full_dbname_old, arg_dbname);
strcat (full_dbname_new, arg_dbname);
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 14,
"%s: Old files: '%s.d2x, .d99'.\n") ,
aa_argv0, full_dbname_old);
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 15,
"%s: New files: '%s.d2x, .d99'.\n") ,
aa_argv0, full_dbname_new);
strcpy (fname_d99_old, full_dbname_old);
strcat (fname_d99_old, ".d99");
strcpy (fname_d21_old, full_dbname_old);
strcat (fname_d21_old, ".d21");
strcpy (fname_d22_old, full_dbname_old);
strcat (fname_d22_old, ".d22");
strcpy (fname_d23_old, full_dbname_old);
strcat (fname_d23_old, ".d23");
strcpy (fname_d99_new, full_dbname_new);
strcat (fname_d99_new, ".d99");
strcpy (fname_d21_new, full_dbname_new);
strcat (fname_d21_new, ".d21");
strcpy (fname_d22_new, full_dbname_new);
strcat (fname_d22_new, ".d22");
strcpy (fname_d23_new, full_dbname_new);
strcat (fname_d23_new, ".d23");
/* If the user hasn't already authorized overwriting preexisting files,
* check new directory and if new files already exist,
* ask permission to overwrite.
*/
if (!overlay_yes) {
oops = FALSE; /* TRUE forces a user prompt */
if ((fp_d99_new = fopen (fname_d99_new, "r")) != NULL) {
fclose (fp_d99_new);
oops = TRUE;
}
if ((fp_d21_new = fopen (fname_d21_new, "r")) != NULL) {
fclose (fp_d21_new);
oops = TRUE;
}
if ((fp_d22_new = fopen (fname_d22_new, "r")) != NULL) {
fclose (fp_d22_new);
oops = TRUE;
}
if ((fp_d23_new = fopen (fname_d23_new, "r")) != NULL) {
fclose (fp_d23_new);
oops = TRUE;
}
if (oops) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 24,
"%s: One or more new files already exist.\n") ,
aa_argv0);
if (overlay_no) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 463,
"%s Command line argument disallows file overlay.\n"),
PROGNAME"463");
end_of_job (2, SHOW_EXITCODE);
}
fputs (catgets(dtsearch_catd, MS_dtsrclean, 45,
" Is it ok to overlay files in new directory? [y/n] "),
aa_stderr);
*readbuf = '\0';
if(NULL == fgets (readbuf, sizeof(readbuf), stdin)) {
fprintf (aa_stderr, "Failed to read from stdin\n");
end_of_job (2, SHOW_EXITCODE);
}
if (strlen(readbuf) && readbuf[strlen(readbuf)-1] == '\n')
readbuf[strlen(readbuf)-1] = '\0';
if (tolower (*readbuf) != 'y')
end_of_job (2, SHOW_NOTHING);
}
} /* end of check for overlaying new files */
/* Open all files. The d2x's are opened so that the old ones
* can be copied into the new directory before starting
* the garbage collection process proper.
* The d99's are opened now just to verify permissions.
*/
oops = FALSE; /* TRUE ends job, but only after trying all 4 files */
open_all_files (&fp_d21_old, fname_d21_old, "rb", &size_d21_old, &oops);
open_all_files (&fp_d22_old, fname_d22_old, "rb", &size_d22_old, &oops);
open_all_files (&fp_d23_old, fname_d23_old, "rb", &size_d23_old, &oops);
open_all_files (&fp_d99_old, fname_d99_old, "rb", &size_d99_old, &oops);
open_all_files (&fp_d21_new, fname_d21_new, "wb", NULL, &oops);
open_all_files (&fp_d22_new, fname_d22_new, "wb", NULL, &oops);
open_all_files (&fp_d23_new, fname_d23_new, "wb", NULL, &oops);
open_all_files (&fp_d99_new, fname_d99_new, "wb", NULL, &oops);
if (shutdown_now)
end_of_job (shutdown_now, SHOW_EXITCODE);
if (oops)
end_of_job (2, SHOW_EXITCODE);
/* Copy old d2x files to new directory.
* Database will open using new files so only they will be changed.
*/
copy_old_d2x_to_new (fname_d21_old, fname_d21_new, fp_d21_old, fp_d21_new);
copy_old_d2x_to_new (fname_d22_old, fname_d22_new, fp_d22_old, fp_d22_new);
copy_old_d2x_to_new (fname_d23_old, fname_d23_new, fp_d23_old, fp_d23_new);
/* Open database, but use new d2x files for updates. */
RENFILE (PROGNAME"1102", arg_dbname, OR_D21, fname_d21_new);
RENFILE (PROGNAME"1104", arg_dbname, OR_D22, fname_d22_new);
RENFILE (PROGNAME"1106", arg_dbname, OR_D23, fname_d23_new);
if (!austext_dopen (arg_dbname, (dbfpath[0] == 0) ? NULL : dbfpath,
NULL, 0, &dbrec)) {
puts (DtSearchGetMessages ());
end_of_job (3, SHOW_EXITCODE);
}
/* This is where efim changed real dba to
* record number (still called dba)
*/
RECFRST (PROGNAME "1067", OR_OBJREC, 0);
CRGET (PROGNAME "1068", &dba, 0); /* dba of first real obj
* record */
recslots = dbrec.or_recslots; /* vista slots per obj
* record */
dba_offset = recslots - (dba & 0xffffff); /* accounts for dbrec */
/* total_num_addrs = what reccount would be if
* all holes were filled with good records.
*/
total_num_addrs = (dbrec.or_maxdba - (dba & 0xffffff) + 1) / recslots + 1;
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 25,
"%s: curr reccnt=%ld, mxdba=%ld, sl/rec=%ld, tot#adr=%ld.\n") ,
aa_argv0, (long)dbrec.or_reccount, (long)dbrec.or_maxdba,
(long)dbrec.or_recslots, (long)total_num_addrs);
/* Initialize validation_mode (checkd99) */
if (validation_mode) {
/*
* Allocate and initialize a bit vector: 4 bits for every
* possible d00 database address.
*/
max_bitvec = (total_num_addrs >> 1) + 2;
if ((bit_vector = malloc ((size_t)max_bitvec + 64)) == NULL) {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 465,
"%s WARNING: Can't allocate memory for bit vector.\n"
" 'Validate' mode switched off.\n"),
PROGNAME"465");
validation_mode = FALSE;
normal_exitcode = 1; /* warning */
goto EXIT_INIT_VALIDATION;
}
memset (bit_vector, 0, (size_t)max_bitvec);
/*
* Read every d00 rec sequentially. 1 in bit #1 (lowest
* order) in bit vector means record (dba) exists in d00
* file. While we're at it, count the total number of
* records.
*/
x = dbrec.or_reccount / 50 + 1; /* x = recs per dot */
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 26,
"%s: Reading d00 file. Each dot appx %ld database documents...\n"),
aa_argv0, (long)x);
reccount = 0;
dot_count = 0L;
RECFRST (PROGNAME "534", OR_OBJREC, 0);
while (db_status == S_OKAY) {
CRREAD (PROGNAME "617", OR_OBJKEY, recidbuf, 0);
/* print periodic progress dots */
if (!(++reccount % x)) {
fputc ('.', aa_stderr);
need_linefeed = TRUE;
if (!(++dot_count % 10L))
fputc (' ', aa_stderr);
fflush (aa_stderr);
}
/*
* Get dba and record number and confirm it will not
* overflow bit vector.
*/
CRGET (PROGNAME "537", &dba, 0);
dba &= 0x00ffffff; /* mask out file number in high order byte */
dba1 = (dba + dba_offset) / recslots; /* ="rec number", base 1 */
if (dba1 >= total_num_addrs) {
TERMINATE_LINE ();
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 561,
"%s DBA '%d:%ld' (rec #%ld) in d00 exceeds "
"total num addrs %ld;\n"
" Bit vector overflow because maxdba %ld"
" in dbrec is incorrect.\n"),
PROGNAME"561", OR_D00, (long)dba, (long)dba1,
(long)total_num_addrs, (long)dbrec.or_maxdba);
end_of_job (7, SHOW_EXITCODE);
}
if (shutdown_now)
end_of_job (shutdown_now, SHOW_EXITCODE);
/*
* Set bit #1 of even or odd nibble to indicate that
* this record *number* actually exists in d00 file.
*/
bit_vector[dba1 >> 1] |= (dba1 & 1L) ? 0x01 : 0x10;
RECNEXT (PROGNAME "541", 0);
} /* end of sequential read thru d00 file */
TERMINATE_LINE (); /* end the dots... */
/* confirm that RECCOUNT record holds the correct number */
if (dbrec.or_reccount == reccount) {
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 27,
"%s: Confirmed %ld DOCUMENTS in %s.d00.\n") ,
aa_argv0, (long)dbrec.or_reccount, arg_dbname);
}
else {
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 28,
"%s: %ld DOCUMENTS actually in %s.d00 not ="
" %ld count stored there.\n"
" Count will be corrected in new d00 file.\n") ,
aa_argv0, (long)reccount, arg_dbname, (long)dbrec.or_reccount);
dbrec.or_reccount = reccount;
rewrite_reccount = TRUE;
}
EXIT_INIT_VALIDATION:;
} /* end of validation_mode initialization */
/* initialize main loop */
time (&timestart);
reccount = 0;
bytes_in = 0L;
dot_count = DOTS_PER_MSG; /* force initial msg after first
* blk of recs */
TERMINATE_LINE ();
fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 29,
"%s: Compressing into %s. Each dot appx %lu words...\n") ,
aa_argv0, arg_newpath, (unsigned long)recs_per_dot);
/* write New Header Information to a new d99 file */
init_header (fp_d99_new, &fl_hdr);
/* Sequentially read each word key file in big loop.
* For each word, read the d99.
* In validation mode check the dbas.
* If not validating, just blindly rewrite the old d99 to the new one.
* If validating only write good dba's and mark the bit vector.
*/
copy_new_d99 (OR_SWORDKEY);
copy_new_d99 (OR_LWORDKEY);
copy_new_d99 (OR_HWORDKEY);
if (reccount == 0)
end_of_job (50, SHOW_PROGRESS + SHOW_EXITCODE);
else
print_progress ("Final");
/* If validation_mode requested, traverse bit vector and print out
* table of each d00 record which cannot be accessed from any d99 word.
* If a validation file name was provided, write out a line for each
* bad reecord in alebeniz-compatible format.
*/
if (validation_mode) {
for (x = 0, bvptr = bit_vector; x < max_bitvec; x++, bvptr++) {
for (j = 0; j < 8; j += 4) { /* j = 0 or 4, amount of
* bit shift */
/* a = bits #1 and #2 of current nibble */
a = 0x30 & (*bvptr << j);
/* if dba is in d00 but not in d99... */
if (a & 0x10 && !(a & 0x20)) {
/* ...construct valid vista dba */
dbaorig = x << 1;
if (j)
dbaorig++; /* slot number */
/*** dba = dbaorig | (OR_D00 << 24); ***//* r
* eal dba */
/* now efim retranslates back to real dba */
dba = ((dbaorig + 1) * recslots - dba_offset)
| (OR_D00 << 24);
/* ...print out err msg */
CRSET (PROGNAME "734", &dba, 0);
CRREAD (PROGNAME "735", OR_OBJKEY, readbuf, 0);
fprintf (aa_stderr,
catgets(dtsearch_catd, MS_dtsrclean, 444,
"*** %s d00 record '%s' is not referenced in d99.\n"
" DBA = %d:%ld (x%02x:%06lx).\n") ,
PROGNAME"444", readbuf, OR_D00,
(long)dba, OR_D00, (long)dba);
/*...if albeniz compatible output requested, do it */
if (frecids) {
fprintf (frecids, DISCARD_FORMAT, arg_dbname,
readbuf, "MrClean", datestr);
}
corruption_count++;
if (max_corruption > 0L &&
corruption_count >= max_corruption)
end_of_job (94, SHOW_EXITCODE);
} /* endif where d00 is not referenced by d99 */
} /* end forloop: every 2 bits in a bitvector byte */
} /* end forloop: every byte in bitvector */
}
/* Normal_exitcode currently will contain either a 0 or a 1.
* If we were uncorrupting the d99 and found any corrupt links,
* make sure it's 1 (warning). If there were corrupt links and
* we weren't trying to uncorrupt it, change it to a hard error.
*/
/***by the way, corruption_count can be > 0 only if in validation_mode.**/
if (corruption_count > 0L) {
if (validation_mode)
normal_exitcode = 1;
else
normal_exitcode = 90;
}
end_of_job (normal_exitcode, SHOW_EXITCODE);
} /* main() */
/*************************** DTSRCLEAN.C ****************************/