Link with C++ linker
[oweals/cde.git] / cde / programs / dtsr / dtsrclean.c
1 /*
2  * CDE - Common Desktop Environment
3  *
4  * Copyright (c) 1993-2012, The Open Group. All rights reserved.
5  *
6  * These libraries and programs are free software; you can
7  * redistribute them and/or modify them under the terms of the GNU
8  * Lesser General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option)
10  * any later version.
11  *
12  * These libraries and programs are distributed in the hope that
13  * they will be useful, but WITHOUT ANY WARRANTY; without even the
14  * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15  * PURPOSE. See the GNU Lesser General Public License for more
16  * details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with these librararies and programs; if not, write
20  * to the Free Software Foundation, Inc., 51 Franklin Street, Fifth
21  * Floor, Boston, MA 02110-1301 USA
22  */
23 /*
24  *   COMPONENT_NAME: austext
25  *
26  *   FUNCTIONS: TERMINATE_LINE
27  *              copy_new_d99
28  *              copy_old_d2x_to_new
29  *              end_of_job
30  *              main
31  *              open_all_files
32  *              print_progress
33  *              print_usage
34  *              read_d2x
35  *              signal_shutdown
36  *              user_args_processor
37  *              validation_error
38  *              write_d2x
39  *
40  *   ORIGINS: 27
41  *
42  *
43  *   (C) COPYRIGHT International Business Machines Corp. 1993,1995
44  *   All Rights Reserved
45  *   Licensed Materials - Property of IBM
46  *   US Government Users Restricted Rights - Use, duplication or
47  *   disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
48  */
49 /*************************** DTSRCLEAN.C ****************************
50  * $TOG: dtsrclean.c /main/9 1998/04/17 11:23:57 mgreess $
51  * Does garbage collection (ie compression) of .d99 file.
52  * Optionally verifies all database addresses in d99.
53  * Modification of clndtbs.c and checkd99.c.
54  * Does NOT use austext engine so this must be modified if schema changes.
55  * 
56  * INPUT FORMAT:
57  * All command input is on command line.  Reads existing d2x and d99 files.
58  * 
59  * OUTPUT FORMAT:
60  * New .d2x and .d99 files are placed into the directory specified by user.
61  *
62  * EXIT CODE STANDARDS:
63  * 0 = normal.
64  * 1 = warnings, but output should be ok.
65  * 2 = failure in cmd line parse or other initialization; job never started.
66  * 3 - 49 = fatal error, but output may be acceptable.
67  * 50 - 99 = fatal error and output files are probably unusable.
68  *      (In this program, even input may be corrupted).
69  * 100+ = aborting due to asynchronous interrupt signal.
70  *     Output files may or may not be unusable.
71  *
72  * $Log$
73  * Revision 2.4  1996/05/08  16:20:50  miker
74  * Added RENFILEs for new d2x files; austext_dopen no longer does.
75  *
76  * Revision 2.3  1996/02/01  18:13:06  miker
77  * Deleted BETA definition.
78  *
79  * Revision 2.2  1995/10/26  14:51:08  miker
80  * Renamed from mrclean.c.  Added prolog.
81  *
82  * Log: mrclean.c,v
83  * Revision 2.1  1995/09/22  21:18:52  miker
84  * Freeze DtSearch 0.1, AusText 2.1.8
85  *
86  * Revision 1.11  1995/09/05  18:16:46  miker
87  * Name, msg, and other minor changes for DtSearch..
88  * Print messages if austext_dopen() fails.
89  *
90  * Revision 1.10  1995/06/02  15:52:42  miker
91  * Cleaned up -m and bit vector overflow msgs.
92  *
93  * Revision 1.9  1995/05/30  19:15:58  miker
94  * Print beta char in startup banner msg.
95  * Remove -m option and max_totrecs; select bit vector
96  * size from maxdba, not reccount.
97  */
98 #include "SearchP.h"
99 #include <stdlib.h>
100 #include <ctype.h>
101 #include <string.h>
102 #include <errno.h>
103 #include <fcntl.h>
104 #include <signal.h>
105 #include <sys/stat.h>
106 #include <locale.h>
107 #include "vista.h"
108 #include <sys/types.h>
109 #include <netinet/in.h>
110
111 #define MS_misc         1       /* msg catalog set number */
112 #define MS_dtsrclean    26      /* msg catalog set number */
113 #define DISCARD_FORMAT  "%s\t\"%s\"\t%s\t%s\n"  /* copied from oe.h */
114 #define RECS_PER_DOT    1000
115 #define DOTS_PER_MSG    50
116 #define DISK_BLKSIZE    512
117 #define MAX_CORRUPTION  100
118 #define MAX_REC_READ    (DISK_BLKSIZE / sizeof(DB_ADDR))
119  /*
120   * Max number of addresses to be read from database addresses
121   * file, ie the size of one block read from hard disk. 
122   */
123 #define PROGNAME        "DTSRCLEAN"
124
125 #define SHOW_NOTHING    0       /* bit arguments for end_of_job() */
126 #define SHOW_USAGE      1
127 #define SHOW_EXITCODE   2
128 #define SHOW_PROGRESS   4
129
130 #define TERMINATE_LINE()   if(need_linefeed){fputc('\n',aa_stderr);need_linefeed=FALSE;}
131
132 /*-------------------------- GLOBALS ----------------------------*/
133 static char    *arg_dbname = NULL;
134 static char    *arg_newpath = NULL;
135 unsigned char  *bit_vector = NULL;
136 static size_t   bytes_in = 0L;
137 static size_t   corruption_count = 0L;
138 static struct or_swordrec
139                 d21new, d21old;
140 static struct or_lwordrec
141                 d22new, d22old;
142 static struct or_hwordrec
143                 d23new, d23old;
144 static char     datestr[32] = "";       /* "1946/04/17 13:03" */
145 static int      debug_mode = FALSE;
146 static size_t   dot_count = 0L;
147 char            fname_d99_new[1024];
148 char            fname_d99_old[1024];
149 FILE           *fp_d99_new = NULL;
150 FILE           *fp_d99_old = NULL;
151 static FILE    *frecids = NULL;
152 static int      is_valid_dba;
153 static size_t   max_corruption = MAX_CORRUPTION;
154 static int      normal_exitcode = 0;
155 static int      need_linefeed = FALSE;
156 static int      overlay_no = FALSE;
157 static int      overlay_yes = FALSE;
158 static DtSrINT32
159                 reccount =              0;
160 static DtSrINT32
161                 recslots;       /* dbrec.or_recslots promoted to INT32 */
162 static DtSrINT32
163                 dba_offset;
164 static DtSrINT32
165                 recs_per_dot =          RECS_PER_DOT;
166 static int      rewrite_reccount = FALSE;
167 static int      shutdown_now = 0;       /* = FALSE */
168 static size_t   size_d21_old = 0L;
169 static size_t   size_d22_old = 0L;
170 static size_t   size_d23_old = 0L;
171 static size_t   size_d99_old = 0L;
172 static time_t   timestart = 0L;
173 static DtSrINT32
174                 total_num_addrs =       0;
175 static int      validation_mode = FALSE;
176
177
178 /********************************************************/
179 /*                                                      */
180 /*                  signal_shutdown                     */
181 /*                                                      */
182 /********************************************************/
183 /* interrupt handler for SIGINT  */
184 static void     signal_shutdown (int sig)
185 {
186     shutdown_now = 100 + sig;
187     return;
188 }  /* signal_shutdown() */
189
190
191 /************************************************/
192 /*                                              */
193 /*                 print_usage                  */
194 /*                                              */
195 /************************************************/
196 /* Prints usage statement to stderr. */
197 static void     print_usage (void)
198 {
199     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 1,
200 "\nUSAGE: %s [options] <dbname> <newpath>\n"
201 "       Compresses unused d99 space and validates d00-d99 links.\n"
202 "  -p<N>     Progress dots printed every <N> records (default %lu).\n"
203 "            Complete progress message printed every %d dots.\n"
204 "  -oy       Authorizes overlaying preexisting d99/d2<N> files in newpath.\n"
205 "  -on       Forces exit if preexisting d99/d2<N> files in newpath.\n"
206 "  -v        Validates d99 and d00 links, uncorrupts d99 file, and ensures\n"
207 "            accurate record count.  Also use -c0 to uncorrupt entire database.\n"
208 "  -v<fname> Same as -v but also writes all d00 recs unreferenced by d99\n"
209 "            to <fname> in format suitable to extract into .fzk file format.\n"
210 "  -c<N>     Exits if more than <N> corrupted/incomplete links (default %d).\n"
211 "            Corruption limit turned off by -c0.\n"
212 "  <dbname>  1 - 8 char database name = the old d99/d2<N> files to be updated.\n"
213 "            Files found in local directory or DBFPATH environment variable.\n"
214 "  <newpath> Specifies where the new d99/d2<N> files will be placed.\n"
215 "            If first char is not slash, path is relative to local directory.\n"
216 "EXIT CODES:\n"
217 "  0: Complete success.  1: Warning.  2: Job never started.\n"
218 "  3-49: Job ended prematurely, old files ok, new files unusable.\n"
219 "  50-99: Fatal Error, even old database may be corrupted.\n"
220 "  100+: Ctrl-C, kill, and all other signal interrupts cause premature\n"
221 "     end, new files may be unusable.  Signal = exit code - 100.\n")
222         ,aa_argv0, RECS_PER_DOT, DOTS_PER_MSG, MAX_CORRUPTION);
223     return;
224 }  /* print_usage() */
225
226
227 /************************************************/
228 /*                                              */
229 /*                print_progress                */
230 /*                                              */
231 /************************************************/
232 /* Prints progress msg after dots or at end of job.
233  * Label is "Final" or "Progress".
234  */
235 static void     print_progress (char *label)
236 {
237     long            seconds;
238     int             compression;
239
240     seconds = time (NULL) - timestart;  /* total seconds elapsed */
241     if (seconds < 0L)
242         seconds = 0L;
243
244     if ((float) bytes_in / (float) size_d99_old >= 99.5)
245         compression = 100;
246     else {
247         compression = (int) (100.* (float) bytes_in / (float) size_d99_old);
248         if (compression < 0 || compression > 100)
249             compression = 0;
250     }
251
252     TERMINATE_LINE ();
253     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 2,
254         "%s: %s Compression %d%% (about %lu KB) in %ld:%02ld min:sec.\n") ,
255         aa_argv0, label, compression, bytes_in / 1000L,
256         seconds / 60UL, seconds % 60UL);
257     if (*label == 'F')
258         fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 3,
259             "%s: Counted %ld WORDS in %s.d99.\n") ,
260             aa_argv0, (long)reccount, arg_dbname);
261     return;
262 }  /* print_progress() */
263
264
265 /************************************************/
266 /*                                              */
267 /*                  end_of_job                  */
268 /*                                              */
269 /************************************************/
270 /* Exits program.  Prints status messages before going down.
271  * Should be called on even record boundaries whenever possible,
272  * ie after record writes complete and shutdown_now > 0 (TRUE).
273  */
274 static void     end_of_job (int exitcode, int show_flags)
275 {
276     TERMINATE_LINE ();
277     if (exitcode >= 100) {
278         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 66,
279             "%s Aborting after interrupt signal %d.\n"),
280             PROGNAME"66", exitcode - 100);
281     }
282     if (validation_mode && corruption_count == 0L)
283         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 4,
284             "%s: No corrupted links detected.\n") ,
285             aa_argv0);
286     if (corruption_count > 0L) {
287         if (max_corruption > 0L && corruption_count >= max_corruption)
288             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 193,
289                 "%s Aborting at %ld corrupted links.\n"),
290                 PROGNAME"193", corruption_count);
291         else
292             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 194,
293                 "%s Detected%s %ld corrupted/incomplete link(s).\n"),
294                 PROGNAME"194",
295                 (validation_mode) ? " and corrected" : "",
296                 corruption_count);
297     }
298     if (show_flags & SHOW_PROGRESS) {
299         print_progress ("Final");
300     }
301     if (show_flags & SHOW_USAGE)
302         print_usage ();
303     if (show_flags & SHOW_EXITCODE)
304         fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 5,
305         "%s: Exit code = %d.\n") , aa_argv0, exitcode);
306     DtSearchExit (exitcode);
307 }  /* end_of_job() */
308
309
310 /************************************************/
311 /*                                              */
312 /*              user_args_processor()           */
313 /*                                              */
314 /************************************************/
315 /* Reads and verifies users command line arguments and
316  * converts them into internal switches and variables.
317  * Some attempt is made to read as many errors as possible
318  * before ending job for bad arguments.
319  */
320 static void     user_args_processor (int argc, char **argv)
321 {
322     char           *argptr;
323     int             oops = FALSE;
324     int             i;
325     time_t          stamp;
326     size_t          tempsize;
327
328     if (argc < 3)
329         end_of_job (2, SHOW_USAGE);
330
331     /* parse all args that begin with a dash (-) */
332     while (--argc > 0) {
333         argv++;
334         argptr = argv[0];
335         if (argptr[0] != '-')
336             break;
337         switch (tolower (argptr[1])) {
338             case 'r':
339                 if (strcmp (argptr, "-russell") == 0)   /* backdoor debug */
340                     debug_mode = TRUE;
341                 else
342                     goto UNKNOWN_ARG;
343
344             case 'm':
345                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 301,
346                     "%s The -m argument is no longer necessary.\n"),
347                     PROGNAME"301");
348                 break;
349
350             case 'o':
351                 i = tolower (argptr[2]);
352                 if (i == 'n')
353                     overlay_no = TRUE;
354                 else if (i == 'y')
355                     overlay_yes = TRUE;
356                 else {
357         INVALID_ARG:
358                     fprintf (aa_stderr,
359                         catgets(dtsearch_catd, MS_dtsrclean, 177,
360                         "%s Invalid %.2s argument.\n"),
361                         PROGNAME"177", argptr);
362                     oops = TRUE;
363                 }
364                 break;
365
366             case 'v':
367                 validation_mode = TRUE;
368                 if (argptr[2] != '\0') {
369                     if ((frecids = fopen (argptr + 2, "w")) == NULL) {
370                         fprintf (aa_stderr,
371                             catgets(dtsearch_catd, MS_dtsrclean, 802,
372                             "%s Unable to open '%s' to output"
373                             " unreferenced d00 records:\n  %s\n"),
374                             PROGNAME"802", argptr, strerror(errno));
375                         oops = TRUE;
376                     }
377                     time (&stamp);
378                     strftime (datestr, sizeof (datestr),
379                         "%Y/%m/%d %H:%M", localtime (&stamp));
380                 }
381                 break;
382
383             case 'p':
384                 recs_per_dot = (DtSrINT32) atol (argptr + 2);
385                 if (recs_per_dot <= 0)
386                     goto INVALID_ARG;
387                 break;
388
389             case 'c':
390                 tempsize = atol (argptr + 2);
391                 if (tempsize < 0L)
392                     goto INVALID_ARG;
393                 max_corruption = tempsize;
394                 break;
395
396         UNKNOWN_ARG:
397             default:
398                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 159,
399                     "%s Unknown argument: '%s'.\n"),
400                     PROGNAME"159", argptr);
401                 oops = TRUE;
402                 break;
403         }       /* end switch */
404     }   /* end parse of cmd line args */
405
406     /* Test how we broke loop.
407      * There should still be 2 args past the ones
408      * beginning with a dash: dbname and newpath.
409      */
410     if (argc != 2) {
411         if (argc <= 0)
412             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 210,
413                 "%s Missing required dbname argument.\n"),
414                 PROGNAME"210");
415         if (argc <= 1)
416             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 211,
417                 "%s Missing required newpath argument.\n"),
418                 PROGNAME"211");
419         if (argc > 2)
420             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 212,
421                 "%s Too many arguments.\n"),
422                 PROGNAME"212");
423         oops = TRUE;
424     }
425     if (oops)
426         end_of_job (2, SHOW_USAGE);
427
428     /* DBNAME */
429     arg_dbname = argv[0];
430     if (strlen (arg_dbname) > 8) {
431         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 229,
432             "%s Invalid database name '%s'.\n"),
433             PROGNAME"229", arg_dbname);
434         end_of_job (2, SHOW_USAGE);
435     }
436
437     /* NEWPATH:
438      * Oldpath and newpath are validated when the files
439      * are copied and the database is opened.
440      */
441     arg_newpath = argv[1];
442     return;
443 }  /* user_args_processor() */
444
445
446 /************************************************/
447 /*                                              */
448 /*               validation_error()             */
449 /*                                              */
450 /************************************************/
451 /* Subroutine of validation_mode in main().
452  * Prints d2x and d99 data at location of error.
453  * Adjusts d2x counts for number of good addrs and free slots.
454  */
455 static void     validation_error (DB_ADDR dbaorig)
456 {
457     DB_ADDR         slot;
458     is_valid_dba = FALSE;
459
460     slot = dbaorig >> 8;
461
462     /* now efim retranslates back to real dba */
463     if (dbaorig != -1)
464         slot = ((slot + 1) * recslots - dba_offset)
465             | (OR_D00 << 24);
466
467     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 6,
468         "  DBA = %d:%ld (x%02x:%06lx),  orig addr val = x%08lx\n"
469         "  Word='%c%s' offset=%ld addrs=%ld free=%d\n") ,
470         OR_D00, slot, OR_D00, slot, dbaorig,
471         (!isgraph (d23old.or_hwordkey[0])) ? '^' : d23old.or_hwordkey[0],
472         d23old.or_hwordkey + 1, d23old.or_hwoffset,
473         d23old.or_hwaddrs, d23old.or_hwfree);
474     if (--d23new.or_hwaddrs < 0L)
475         d23new.or_hwaddrs = 0L;
476     /* (should never occur) */
477     d23new.or_hwfree++;
478
479     return;
480 }  /* validation_error() */
481
482
483 /************************************************/
484 /*                                              */
485 /*                open_all_files                */
486 /*                                              */
487 /************************************************/
488 static void     open_all_files
489                 (FILE ** fp, char *fname, char *mode, size_t * size, int *oops) {
490     struct stat     fstatbuf;
491
492     if ((*fp = fopen (fname, mode)) == NULL) {
493         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 439,
494             "%s Can't open %s: %s\n"),
495             PROGNAME"439", fname, strerror (errno));
496         *oops = TRUE;
497         return;
498     }
499     if (fstat (fileno (*fp), &fstatbuf) == -1) {
500         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 440,
501             "%s Can't access status of %s: %s\n"),
502             PROGNAME"440", fname, strerror (errno));
503         *oops = TRUE;
504         return;
505     }
506     if (size)
507         if ((*size = fstatbuf.st_size) <= 0L) {
508             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 499,
509                 "%s %s is empty.\n"),
510                 PROGNAME"499", fname);
511             *oops = TRUE;
512         }
513     return;
514 }  /* open_all_files() */
515
516
517 /************************************************/
518 /*                                              */
519 /*            copy_old_d2x_to_new               */
520 /*                                              */
521 /************************************************/
522 static void     copy_old_d2x_to_new
523                 (char *fname_old, char *fname_new, FILE * fp_old, FILE * fp_new) {
524     char            readbuf[1024 + 32];
525     int             i, j;
526
527     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 7,
528         "%s: Copying from old d2x files to %s...\n") ,
529         aa_argv0, fname_new);
530     for (;;) {  /* loop ends when eof set on input stream */
531         errno = 0;
532         i = fread (readbuf, 1, sizeof (readbuf), fp_old);
533         /* byte swap not required on pure copy operation */
534         if (errno) {
535             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 517,
536                 "%s Read error on %s: %s.\n"),
537                 PROGNAME"517", fname_old, strerror (errno));
538             end_of_job (3, SHOW_EXITCODE);
539         }
540         j = fwrite (readbuf, 1, i, fp_new);
541         if (i != j) {
542             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 489,
543                 "%s Write error on %s: %s.\n"),
544                 PROGNAME"489", fname_new, strerror (errno));
545             end_of_job (3, SHOW_EXITCODE);
546         }
547         if (shutdown_now)
548             end_of_job (shutdown_now, SHOW_EXITCODE);
549         if (feof (fp_old))
550             break;
551     }
552     TERMINATE_LINE ();
553     fclose (fp_old);
554     fclose (fp_new);
555     return;
556 }  /* copy_old_d2x_to_new() */
557
558
559 /********************************/
560 /*                              */
561 /*         read_d2x             */
562 /*                              */
563 /********************************/
564 /* Performs vista RECREAD on curr word record.
565  * CALLER SHOULD CHECK DB_STATUS.
566  */
567 void            read_d2x (struct or_hwordrec * glob_word, long field)
568 {
569     if (field == OR_SWORDKEY) {
570         RECREAD (PROGNAME "061", &d21old, 0);
571         if (db_status != S_OKAY)
572             return;
573         strncpy (glob_word->or_hwordkey, d21old.or_swordkey,
574             DtSrMAXWIDTH_HWORD);
575         glob_word->or_hwordkey[DtSrMAXWIDTH_HWORD - 1] = 0;
576         glob_word->or_hwoffset =        ntohl (d21old.or_swoffset);
577         glob_word->or_hwfree =          ntohl (d21old.or_swfree);
578         glob_word->or_hwaddrs =         ntohl (d21old.or_swaddrs);
579     }
580     else if (field == OR_LWORDKEY) {
581         RECREAD (PROGNAME "069", &d22old, 0);
582         if (db_status != S_OKAY)
583             return;
584         strncpy (glob_word->or_hwordkey, d22old.or_lwordkey,
585             DtSrMAXWIDTH_HWORD);
586         glob_word->or_hwordkey[DtSrMAXWIDTH_HWORD - 1] = 0;
587         glob_word->or_hwoffset =        ntohl (d22old.or_lwoffset);
588         glob_word->or_hwfree =          ntohl (d22old.or_lwfree);
589         glob_word->or_hwaddrs =         ntohl (d22old.or_lwaddrs);
590     }
591     else {
592         RECREAD (PROGNAME "078", glob_word, 0);
593         glob_word->or_hwordkey[DtSrMAXWIDTH_HWORD - 1] = 0;
594         NTOHL (glob_word->or_hwoffset);
595         NTOHL (glob_word->or_hwfree);
596         NTOHL (glob_word->or_hwaddrs);
597     }
598     return;
599 }  /* read_d2x() */
600
601
602 /********************************/
603 /*                              */
604 /*         write_d2x            */
605 /*                              */
606 /********************************/
607 /* performs vista RECWRITE on curr word record.
608  * CALLER MUST CHECK DB_STATUS.
609  */
610 static void     write_d2x (struct or_hwordrec * glob_word, long field)
611 {
612     if (field == OR_SWORDKEY) {
613         strcpy (d21new.or_swordkey, glob_word->or_hwordkey);
614         d21new.or_swoffset =    htonl (glob_word->or_hwoffset);
615         d21new.or_swfree =      htonl (glob_word->or_hwfree);
616         d21new.or_swaddrs =     htonl (glob_word->or_hwaddrs);
617         RECWRITE (PROGNAME "102", &d21new, 0);
618     }
619     else if (field == OR_LWORDKEY) {
620         strcpy (d22new.or_lwordkey, glob_word->or_hwordkey);
621         d22new.or_lwoffset =    htonl (glob_word->or_hwoffset);
622         d22new.or_lwfree =      htonl (glob_word->or_hwfree);
623         d22new.or_lwaddrs =     htonl (glob_word->or_hwaddrs);
624         RECWRITE (PROGNAME"112", &d22new, 0);
625     }
626     else {
627         HTONL (glob_word->or_hwoffset);
628         HTONL (glob_word->or_hwfree);
629         HTONL (glob_word->or_hwaddrs);
630         RECWRITE (PROGNAME "115", glob_word, 0);
631     }
632     return;
633 }  /* write_d2x() */
634
635
636
637 /************************************************/
638 /*                                              */
639 /*                  copy_new_d99()              */
640 /*                                              */
641 /************************************************/
642 /* The garbage collection/compression process itself.
643  * For very large databases, there will be appx 3 million word records,
644  * so the loop should be coded for ***EFFICIENCY***.
645  */
646 static void     copy_new_d99 (long keyfield)
647 {
648     int             is_odd_nibble;
649     DtSrINT32       num_holes;
650     DtSrINT32       slots_left;
651     unsigned char  *bvptr;
652     int             a;
653     DB_ADDR         dba, dbaorig;
654     DtSrINT32       x;
655     DtSrINT32       swapx;
656     int             done;
657     DtSrINT32       good_addrs_left;
658     DtSrINT32       good_addrs_this_block;
659     DtSrINT32       num_reads, num_writes;
660     DB_ADDR         word_addrs[MAX_REC_READ + 64];      /* d99 read buf */
661     DB_ADDR         word_addrs_out[MAX_REC_READ + 64];  /* d99 write buf */
662
663     KEYFRST (PROGNAME "179", keyfield, 0);
664     while (db_status == S_OKAY) {
665         read_d2x (&d23new, keyfield);
666         if (validation_mode)    /* save for validation err msgs */
667             memcpy (&d23old, &d23new, sizeof (d23old));
668
669         /*
670          * Read old d99 file at specified offset to get total num
671          * "holes". In the first portion of record holes are filled
672          * with representations of valid database addresses +
673          * statistical weights. In the second portion the holes are
674          * "free slots" for future expansion which are
675          * conventionally initialized with a -1. 
676          */
677         /* force number of free slots to 0(ZERO) */
678         d23new.or_hwfree = 0;
679         fseek (fp_d99_old, d23new.or_hwoffset, SEEK_SET);
680         num_holes = d23new.or_hwaddrs + d23new.or_hwfree;
681         good_addrs_left = d23new.or_hwaddrs;
682         bytes_in += sizeof (DB_ADDR) * num_holes;
683
684         /* Update the offset in the d2x record buffer */
685         d23new.or_hwoffset = ftell (fp_d99_new);
686
687         /*
688          * Copy the array of holes in each disk block, reading the
689          * old and writing to the new.  Loop ends when the number
690          * of holes left will fit into one last block. 
691          */
692         done = FALSE;
693         while (!done) { /* loop on each block in this word */
694             if (num_holes > MAX_REC_READ) {
695                 num_reads = MAX_REC_READ;
696                 num_holes -= MAX_REC_READ;
697             }
698             else {
699                 done = TRUE;
700                 num_reads = num_holes;
701             }
702             errno = 0;
703             fread (word_addrs, sizeof(DB_ADDR), (size_t)num_reads, fp_d99_old);
704             if (errno) {
705                 TERMINATE_LINE ();
706                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 657,
707                     "%s Read error on %s: %s.\n"),
708                     PROGNAME"657", fname_d99_old, strerror (errno));
709                 end_of_job (4, SHOW_PROGRESS + SHOW_EXITCODE);
710             }
711             /* Note BYTE_SWAP only needed for validation_mode.
712              * If not validating, we're just going to copy
713              * the network format dba's as is directly to
714              * the new d99 file.
715              */
716
717             /*
718              * Addrs on d99 are now 'record numbers' not dbas. A
719              * rec# is what the dba/slot# would be if records took
720              * up just one slot and there were no dbrec at start of
721              * file.  D99 rec#s start at #1, not #0. 
722              */
723
724             /*
725              * If user requested validation_mode, validate each
726              * 'good' rec# (not free slots) in word_addrs buffer. 
727              * If any d99 links are corrupt, skip them when copying
728              * to the new d99 file. Rewrite -1's to all free slots.
729              * ----> NOTE UNUSUAL FORMAT OF DBA HOLES IN D99! <----
730              * Record number is shifted to the high order 3 bytes.
731              * The statistical weight is in the low order byte. The
732              * vista file number is known from the #define constant
733              * OR_D00, and the vista dba/slot# is mapped from rec#
734              * by mult/div number of slots per rec, plus/minus
735              * dbrec offset. 
736              */
737             if (validation_mode) {
738 #ifdef BYTE_SWAP
739                 for (swapx = 0;  swapx < num_reads;  swapx++)
740                     NTOHL (word_addrs[swapx]);
741 #endif
742                 /* set x to number of good addrs in this block */
743                 if (good_addrs_left > num_reads) {
744                     x = num_reads;
745                     good_addrs_left -= num_reads;
746                 }
747                 else {
748                     x = good_addrs_left;
749                     good_addrs_left = 0;
750                 }
751
752                 /*
753                  * Validate the rec#'s in this block.  Note that
754                  * the loop is skipped if the entire block is free
755                  * slots. 
756                  */
757                 good_addrs_this_block = 0;
758                 for (a = 0; a < x; a++) {       /* a = index to curr dba */
759                     /*
760                      * Get rec#.  Save original rec# for err msgs,
761                      * then shift slot number to lower 3 bytes,
762                      * discarding weight. 
763                      */
764                     dbaorig = word_addrs[a];    /* rec#,rec#,rec#:wt */
765                     dba = dbaorig >> 8; /* 0,rec#,rec#,rec# */
766                     is_valid_dba = TRUE;        /* default */
767
768                     /*
769                      * If original rec# == -1 we've overrun the
770                      * good rec#'s into the expansion area, which
771                      * is filled with -1's.  This is real bad news
772                      * because if the counts in d02 are bad, the
773                      * online programs will quickly crash, and we
774                      * can't continue this program. Advance to next
775                      * rec# because we can't mark the bit vector. 
776                      */
777                     if (dbaorig == -1L) {
778                         TERMINATE_LINE ();
779                         fprintf (aa_stderr,
780                             catgets(dtsearch_catd, MS_dtsrclean, 111,
781                             "*** %s DBA in d99 = -1.  "
782                             "Probable overrun into expansion\n"
783                             "  area due to incorrect count values "
784                             "in d2x file.\n"),
785                             PROGNAME"111");
786                         validation_error (dbaorig);
787                         corruption_count++;
788                         if (max_corruption > 0L &&
789                             corruption_count >= max_corruption)
790                             end_of_job (91, SHOW_PROGRESS + SHOW_EXITCODE);
791                         continue;       /* skip the bit vector
792                                          * check */
793                     }
794
795                     /*
796                      * If slot number > max totrecs, we have a
797                      * corrupted d99-d00 link because we've already
798                      * validated the d00 file and we know that it
799                      * has no slots > max.  Also we have to advance
800                      * to next slot because we can't mark the bit
801                      * vector. 
802                      */
803                     /******if (dba >= max_totrecs)*******/
804                     if (dba >= total_num_addrs) {
805                         TERMINATE_LINE ();
806                         fprintf (aa_stderr,
807                             catgets(dtsearch_catd, MS_dtsrclean, 222,
808                             "*** %s DBA in d99 not in d00,"
809                             " slot > max num docs.\n"),
810                             PROGNAME"222");
811                         validation_error (dbaorig);
812                         corruption_count++;
813                         if (max_corruption > 0L &&
814                             corruption_count >= max_corruption)
815                             end_of_job (92, SHOW_PROGRESS + SHOW_EXITCODE);
816                         continue;       /* skip the bit vector check */
817                     }
818
819                     /*
820                      * Verify that dba exists in d00 file (test bit
821                      * #1). If not, mark bit #3 (3rd lowest) in
822                      * nibble and print error msg unless bit #3
823                      * previously marked. 
824                      */
825                     bvptr = bit_vector + (dba >> 1);
826                     is_odd_nibble = (dba & 1L);
827                     if (!(*bvptr & ((is_odd_nibble) ? 0x01 : 0x10))) {
828                                                                 /* bit #1 */
829                         if (!(*bvptr & ((is_odd_nibble) ? 0x04 : 0x40))) {
830                                                                 /* bit #3 */
831                             *bvptr |= (is_odd_nibble) ? 0x04 : 0x40;
832                             TERMINATE_LINE ();
833                             fprintf (aa_stderr,
834                                 catgets(dtsearch_catd, MS_dtsrclean, 333,
835                                 "*** %s DBA in d99 does not exist in d00.\n"),
836                                 PROGNAME"333");
837                             validation_error (dbaorig);
838                             corruption_count++;
839                             if (max_corruption > 0L &&
840                                 corruption_count >= max_corruption)
841                                 end_of_job (93, SHOW_PROGRESS + SHOW_EXITCODE);
842                         }       /* endif where corrupt link
843                                  * detected */
844                     }
845
846                     /*
847                      * Mark bit #2 in bit vector indicating a d99
848                      * reference. 
849                      */
850                     *bvptr |= (is_odd_nibble) ? 0x02 : 0x20;    /* bit #2 */
851
852                     /*
853                      * move good dba to curr output block, incr
854                      * counter 
855                      */
856                     if (is_valid_dba)
857                         word_addrs_out[good_addrs_this_block++] = dbaorig;
858
859                 }       /* end validation loop for each good dba in
860                          * the block */
861
862                 /*
863                  * Write out only validated addrs in current block.
864                  * If this was the last block, fill out all the
865                  * free slots, if any, with -1 values, and exit the
866                  * dba loop for this word. 
867                  */
868                 if (good_addrs_this_block > 0) {
869 #ifdef BYTE_SWAP
870                     for (swapx = 0;  swapx < good_addrs_this_block;  swapx++)
871                         NTOHL (word_addrs_out[swapx]);
872 #endif
873                     num_writes = fwrite (word_addrs_out, sizeof (DB_ADDR),
874                         (size_t)good_addrs_this_block, fp_d99_new);
875                     if (num_writes != good_addrs_this_block)
876                         goto WRITE_ERROR;
877                 }
878                 if (good_addrs_left <= 0) {
879                     /*
880                      * Write blocks of -1s until new d2x free slot
881                      * count is exhausted.  The last block may be <
882                      * MAX_REC_READ. 
883                      */
884                     slots_left = d23new.or_hwfree;
885                     while (slots_left > 0) {
886                         /*
887                          * set x to number of -1's to write for
888                          * this block 
889                          */
890                         if (slots_left > MAX_REC_READ) {
891                             x = MAX_REC_READ;
892                             slots_left -= MAX_REC_READ;
893                         }
894                         else {
895                             x = slots_left;
896                             slots_left = 0;
897                         }
898                         for (a = 0; a < x; a++)
899                             word_addrs_out[a] = (DtSrINT32) -1;
900                         /* BYTE_SWAP not required for foxes */
901                         num_writes = fwrite (word_addrs_out,
902                             sizeof(DB_ADDR), (size_t)x, fp_d99_new);
903                         if (num_writes != x)
904                             goto WRITE_ERROR;
905                     }   /* end while loop to write out all -1's */
906                     done = TRUE;
907                 }
908             }   /* endif for validation_mode for this block */
909
910             /*
911              * If NOT in validation mode, just write out the new
912              * d99 block as an exact copy of the input block. 
913              * BYTE_SWAP not required because word_addrs is
914              * still in its original network order from the fread.
915              */
916             else {
917                 num_writes = fwrite (word_addrs, sizeof(DB_ADDR),
918                     (size_t)num_reads, fp_d99_new);
919                 if (num_writes != num_reads) {
920             WRITE_ERROR:
921                     fprintf (aa_stderr,
922                         catgets(dtsearch_catd, MS_dtsrclean, 665,
923                         "%s Write error on %s: %s.\n"),
924                         PROGNAME"665", fname_d99_new, strerror(errno));
925                     end_of_job (4, SHOW_PROGRESS + SHOW_EXITCODE);
926                 }
927             }   /* endelse for NOT validation_mode for this block */
928
929         }       /* end loop for all blocks for this entire word
930                  * (done = TRUE) */
931
932         /* write the updated d2x record */
933         write_d2x (&d23new, keyfield);
934         reccount++;
935
936         /*
937          * Every now and then print a dot. Print complete progress
938          * msg after DOTS_PER_MSG dots. 
939          */
940         if (!(reccount % recs_per_dot)) {
941             if (++dot_count > DOTS_PER_MSG) {
942                 dot_count = 0;
943                 print_progress ("Progress");
944             }
945             else {
946                 fputc ('.', aa_stderr);
947                 need_linefeed = TRUE;
948                 if (!(dot_count % 10L))
949                     fputc (' ', aa_stderr);
950             }
951             fflush (aa_stderr);
952         }       /* end of print-a-dot */
953
954         if (shutdown_now)
955             end_of_job (shutdown_now, SHOW_PROGRESS + SHOW_EXITCODE);
956         KEYNEXT (PROGNAME "196", keyfield, 0);
957     }   /* end of main loop on each word in database */
958
959
960     return;
961 }  /* copy_new_d99() */
962
963
964 /************************************************/
965 /*                                              */
966 /*                    main()                    */
967 /*                                              */
968 /************************************************/
969 int             main (int argc, char *argv[])
970 {
971     FILE_HEADER     fl_hdr;
972     int             a, i, j;
973     unsigned char  *bvptr;
974     DB_ADDR         dba, dba1, dbaorig;
975     char            dbfpath[1024];
976     char            fname_d21_new[1024];
977     char            fname_d21_old[1024];
978     char            fname_d22_new[1024];
979     char            fname_d22_old[1024];
980     char            fname_d23_new[1024];
981     char            fname_d23_old[1024];
982     FILE           *fp_d21_new = NULL;
983     FILE           *fp_d21_old = NULL;
984     FILE           *fp_d22_new = NULL;
985     FILE           *fp_d22_old = NULL;
986     FILE           *fp_d23_new = NULL;
987     FILE           *fp_d23_old = NULL;
988     char            full_dbname_old[1024];
989     char            full_dbname_new[1024];
990     DtSrINT32       max_bitvec = 0L;
991     int             oops;
992     char           *ptr;
993     char            readbuf[1024 + 32];
994     unsigned long   reads_per_dot;
995     char            recidbuf[DtSrMAX_DB_KEYSIZE + 4];
996     time_t          starttime;
997     DtSrINT32       x;
998     struct or_dbrec dbrec;
999
1000     aa_argv0 = argv[0];
1001     setlocale (LC_ALL, "");
1002     dtsearch_catd = catopen (FNAME_DTSRCAT, 0);
1003
1004     time (&starttime);
1005     strftime (dbfpath, sizeof (dbfpath),        /* just use any ol' buffer */
1006         catgets (dtsearch_catd, MS_misc, 22, "%A, %b %d %Y, %I:%M %p"),
1007         localtime (&starttime));
1008     printf ( catgets(dtsearch_catd, MS_dtsrclean, 11,
1009         "%s Version %s.  Run %s.\n") ,
1010         aa_argv0, AUSAPI_VERSION, dbfpath);
1011
1012     signal (SIGHUP, signal_shutdown);
1013     signal (SIGINT, signal_shutdown);
1014     signal (SIGQUIT, signal_shutdown);
1015     signal (SIGTRAP, signal_shutdown);
1016     signal (SIGKILL, signal_shutdown);  /* this cannot be trapped */
1017     signal (SIGALRM, signal_shutdown);
1018     signal (SIGTERM, signal_shutdown);
1019     signal (SIGPWR, signal_shutdown);
1020 #ifdef _AIX
1021     signal (SIGXCPU, signal_shutdown);
1022     signal (SIGDANGER, signal_shutdown);
1023 #endif
1024
1025     user_args_processor (argc, argv);
1026
1027     /* In order to find old files, we have to check if
1028      * DBFPATH environment variable has been set.
1029      * Load the fully constructed DBFPATH-dbname into its own buffer.
1030      */
1031     full_dbname_old[0] = '\0';
1032     dbfpath[0] = 0;
1033     if ((ptr = getenv ("DBFPATH")) != NULL) {
1034         if (*ptr == 0)
1035             fprintf (aa_stderr,
1036                  catgets(dtsearch_catd, MS_dtsrclean, 12,
1037                 "%s: Ignoring empty DBFPATH environment variable.\n") ,
1038                 aa_argv0);
1039         else {
1040             fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 13,
1041                 "%s: Using DBFPATH = '%s'.\n") ,
1042                 aa_argv0, ptr);
1043             strcpy (full_dbname_old, ptr);
1044
1045             /* Ensure that DBFPATH ends in a slash. */
1046             ptr = strchr (full_dbname_old, '\0');
1047             if (*(ptr - 1) != LOCAL_SLASH) {
1048                 *ptr++ = LOCAL_SLASH;
1049                 *ptr = '\0';
1050             }
1051             strcpy (dbfpath, full_dbname_old);
1052         }
1053     }
1054
1055     /* Currently full_dbname_old contains just the path.
1056      * Similarly, build just path name for the 2 new files
1057      * using full_dbname_new as a buffer.
1058      * Verify they don't both refer to the same directory.
1059      */
1060     strcpy (full_dbname_new, arg_newpath);
1061     ptr = strchr (full_dbname_new, '\0');
1062     if (*(ptr - 1) != LOCAL_SLASH) {
1063         *ptr++ = LOCAL_SLASH;
1064         *ptr = '\0';
1065     }
1066     if (strcmp (full_dbname_old, full_dbname_new) == 0) {
1067         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 393,
1068             "%s Old and new directories are identical: '%s'.\n"),
1069             PROGNAME"393", full_dbname_old);
1070         end_of_job (2, SHOW_USAGE);
1071     }
1072
1073     /* Complete full_dbname_old by appending dbname to the path prefix.
1074      * Then build full path/file names for all 4 files.
1075      */
1076     strcat (full_dbname_old, arg_dbname);
1077     strcat (full_dbname_new, arg_dbname);
1078     fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 14,
1079         "%s: Old files: '%s.d2x, .d99'.\n") ,
1080         aa_argv0, full_dbname_old);
1081     fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 15,
1082         "%s: New files: '%s.d2x, .d99'.\n") ,
1083         aa_argv0, full_dbname_new);
1084
1085     strcpy (fname_d99_old, full_dbname_old);
1086     strcat (fname_d99_old, ".d99");
1087     strcpy (fname_d21_old, full_dbname_old);
1088     strcat (fname_d21_old, ".d21");
1089     strcpy (fname_d22_old, full_dbname_old);
1090     strcat (fname_d22_old, ".d22");
1091     strcpy (fname_d23_old, full_dbname_old);
1092     strcat (fname_d23_old, ".d23");
1093     strcpy (fname_d99_new, full_dbname_new);
1094     strcat (fname_d99_new, ".d99");
1095     strcpy (fname_d21_new, full_dbname_new);
1096     strcat (fname_d21_new, ".d21");
1097     strcpy (fname_d22_new, full_dbname_new);
1098     strcat (fname_d22_new, ".d22");
1099     strcpy (fname_d23_new, full_dbname_new);
1100     strcat (fname_d23_new, ".d23");
1101
1102     /* If the user hasn't already authorized overwriting preexisting files,
1103      * check new directory and if new files already exist,
1104      * ask permission to overwrite.
1105      */
1106     if (!overlay_yes) {
1107         oops = FALSE;   /* TRUE forces a user prompt */
1108         if ((fp_d99_new = fopen (fname_d99_new, "r")) != NULL) {
1109             fclose (fp_d99_new);
1110             oops = TRUE;
1111         }
1112         if ((fp_d21_new = fopen (fname_d21_new, "r")) != NULL) {
1113             fclose (fp_d21_new);
1114             oops = TRUE;
1115         }
1116         if ((fp_d22_new = fopen (fname_d22_new, "r")) != NULL) {
1117             fclose (fp_d22_new);
1118             oops = TRUE;
1119         }
1120         if ((fp_d23_new = fopen (fname_d23_new, "r")) != NULL) {
1121             fclose (fp_d23_new);
1122             oops = TRUE;
1123         }
1124         if (oops) {
1125             fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 24,
1126                 "%s: One or more new files already exist.\n") ,
1127                 aa_argv0);
1128             if (overlay_no) {
1129                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 463,
1130                     "%s Command line argument disallows file overlay.\n"),
1131                     PROGNAME"463");
1132                 end_of_job (2, SHOW_EXITCODE);
1133             }
1134             fputs (catgets(dtsearch_catd, MS_dtsrclean, 45,
1135                 "    Is it ok to overlay files in new directory? [y/n] "),
1136                 aa_stderr);
1137
1138             *readbuf = '\0';
1139             fgets (readbuf, sizeof(readbuf), stdin);
1140             if (strlen(readbuf) && readbuf[strlen(readbuf)-1] == '\n')
1141               readbuf[strlen(readbuf)-1] = '\0';
1142
1143             if (tolower (*readbuf) != 'y')
1144                 end_of_job (2, SHOW_NOTHING);
1145         }
1146     }   /* end of check for overlaying new files */
1147
1148     /* Open all files.  The d2x's are opened so that the old ones
1149      * can be copied into the new directory before starting
1150      * the garbage collection process proper.
1151      * The d99's are opened now just to verify permissions.
1152      */
1153     oops = FALSE;  /* TRUE ends job, but only after trying all 4 files */
1154     open_all_files (&fp_d21_old, fname_d21_old, "rb", &size_d21_old, &oops);
1155     open_all_files (&fp_d22_old, fname_d22_old, "rb", &size_d22_old, &oops);
1156     open_all_files (&fp_d23_old, fname_d23_old, "rb", &size_d23_old, &oops);
1157     open_all_files (&fp_d99_old, fname_d99_old, "rb", &size_d99_old, &oops);
1158     open_all_files (&fp_d21_new, fname_d21_new, "wb", NULL, &oops);
1159     open_all_files (&fp_d22_new, fname_d22_new, "wb", NULL, &oops);
1160     open_all_files (&fp_d23_new, fname_d23_new, "wb", NULL, &oops);
1161     open_all_files (&fp_d99_new, fname_d99_new, "wb", NULL, &oops);
1162
1163     if (shutdown_now)
1164         end_of_job (shutdown_now, SHOW_EXITCODE);
1165     if (oops)
1166         end_of_job (2, SHOW_EXITCODE);
1167
1168     /* Copy old d2x files to new directory.
1169      * Database will open using new files so only they will be changed.
1170      */
1171     copy_old_d2x_to_new (fname_d21_old, fname_d21_new, fp_d21_old, fp_d21_new);
1172     copy_old_d2x_to_new (fname_d22_old, fname_d22_new, fp_d22_old, fp_d22_new);
1173     copy_old_d2x_to_new (fname_d23_old, fname_d23_new, fp_d23_old, fp_d23_new);
1174
1175     /* Open database, but use new d2x files for updates. */
1176     RENFILE (PROGNAME"1102", arg_dbname, OR_D21, fname_d21_new);
1177     RENFILE (PROGNAME"1104", arg_dbname, OR_D22, fname_d22_new);
1178     RENFILE (PROGNAME"1106", arg_dbname, OR_D23, fname_d23_new);
1179     if (!austext_dopen (arg_dbname, (dbfpath[0] == 0) ? NULL : dbfpath,
1180             NULL, 0, &dbrec)) {
1181         puts (DtSearchGetMessages ());
1182         end_of_job (3, SHOW_EXITCODE);
1183     }
1184
1185     /* This is where efim changed real dba to
1186      * record number (still called dba)
1187      */
1188     RECFRST (PROGNAME "1067", OR_OBJREC, 0);
1189     CRGET (PROGNAME "1068", &dba, 0);   /* dba of first real obj
1190                                          * record */
1191     recslots = dbrec.or_recslots;       /* vista slots per obj
1192                                          * record */
1193     dba_offset = recslots - (dba & 0xffffff);   /* accounts for dbrec */
1194
1195     /* total_num_addrs = what reccount would be if
1196      * all holes were filled with good records.
1197      */
1198     total_num_addrs = (dbrec.or_maxdba - (dba & 0xffffff) + 1) / recslots + 1;
1199     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 25,
1200         "%s: curr reccnt=%ld, mxdba=%ld, sl/rec=%ld, tot#adr=%ld.\n") ,
1201         aa_argv0, (long)dbrec.or_reccount, (long)dbrec.or_maxdba,
1202         (long)dbrec.or_recslots, (long)total_num_addrs);
1203
1204     /* Initialize validation_mode (checkd99) */
1205     if (validation_mode) {
1206         /*
1207          * Allocate and initialize a bit vector: 4 bits for every
1208          * possible d00 database address. 
1209          */
1210         max_bitvec = (total_num_addrs >> 1) + 2;
1211         if ((bit_vector = malloc ((size_t)max_bitvec + 64)) == NULL) {
1212             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 465,
1213                 "%s WARNING: Can't allocate memory for bit vector.\n"
1214                 "  'Validate' mode switched off.\n"),
1215                 PROGNAME"465");
1216             validation_mode = FALSE;
1217             normal_exitcode = 1;        /* warning */
1218             goto EXIT_INIT_VALIDATION;
1219         }
1220         memset (bit_vector, 0, (size_t)max_bitvec);
1221
1222         /*
1223          * Read every d00 rec sequentially.  1 in bit #1 (lowest
1224          * order) in bit vector means record (dba) exists in d00
1225          * file. While we're at it, count the total number of
1226          * records. 
1227          */
1228         x = dbrec.or_reccount / 50 + 1; /* x = recs per dot */
1229         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 26,
1230             "%s: Reading d00 file.  Each dot appx %ld database documents...\n"),
1231             aa_argv0, (long)x);
1232         reccount = 0;
1233         dot_count = 0L;
1234         RECFRST (PROGNAME "534", OR_OBJREC, 0);
1235         while (db_status == S_OKAY) {
1236             CRREAD (PROGNAME "617", OR_OBJKEY, recidbuf, 0);
1237
1238             /* print periodic progress dots */
1239             if (!(++reccount % x)) {
1240                 fputc ('.', aa_stderr);
1241                 need_linefeed = TRUE;
1242                 if (!(++dot_count % 10L))
1243                     fputc (' ', aa_stderr);
1244                 fflush (aa_stderr);
1245             }
1246
1247             /*
1248              * Get dba and record number and confirm it will not
1249              * overflow bit vector. 
1250              */
1251             CRGET (PROGNAME "537", &dba, 0);
1252             dba &= 0x00ffffff;  /* mask out file number in high order byte */
1253             dba1 = (dba + dba_offset) / recslots;  /* ="rec number", base 1 */
1254             if (dba1 >= total_num_addrs) {
1255                 TERMINATE_LINE ();
1256                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 561,
1257                     "%s DBA '%d:%ld' (rec #%ld) in d00 exceeds "
1258                     "total num addrs %ld;\n"
1259                     "  Bit vector overflow because maxdba %ld"
1260                     " in dbrec is incorrect.\n"),
1261                     PROGNAME"561", OR_D00, (long)dba, (long)dba1,
1262                     (long)total_num_addrs, (long)dbrec.or_maxdba);
1263                 end_of_job (7, SHOW_EXITCODE);
1264             }
1265             if (shutdown_now)
1266                 end_of_job (shutdown_now, SHOW_EXITCODE);
1267
1268             /*
1269              * Set bit #1 of even or odd nibble to indicate that
1270              * this record *number* actually exists in d00 file. 
1271              */
1272             bit_vector[dba1 >> 1] |= (dba1 & 1L) ? 0x01 : 0x10;
1273
1274             RECNEXT (PROGNAME "541", 0);
1275         }       /* end of sequential read thru d00 file */
1276
1277         TERMINATE_LINE ();      /* end the dots... */
1278
1279         /* confirm that RECCOUNT record holds the correct number */
1280         if (dbrec.or_reccount == reccount) {
1281             fprintf (aa_stderr,
1282                  catgets(dtsearch_catd, MS_dtsrclean, 27,
1283                 "%s: Confirmed %ld DOCUMENTS in %s.d00.\n") ,
1284                 aa_argv0, (long)dbrec.or_reccount, arg_dbname);
1285         }
1286         else {
1287             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 28,
1288                 "%s: %ld DOCUMENTS actually in %s.d00 not ="
1289                 " %ld count stored there.\n"
1290                 "  Count will be corrected in new d00 file.\n") ,
1291                 aa_argv0, (long)reccount, arg_dbname, (long)dbrec.or_reccount);
1292             dbrec.or_reccount = reccount;
1293             rewrite_reccount = TRUE;
1294         }
1295
1296 EXIT_INIT_VALIDATION:;
1297     }   /* end of validation_mode initialization */
1298
1299     /* initialize main loop */
1300     time (&timestart);
1301     reccount = 0;
1302     bytes_in = 0L;
1303     dot_count = DOTS_PER_MSG;   /* force initial msg after first
1304                                  * blk of recs */
1305     TERMINATE_LINE ();
1306     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 29,
1307         "%s: Compressing into %s.  Each dot appx %lu words...\n") ,
1308         aa_argv0, arg_newpath, (unsigned long)recs_per_dot);
1309
1310     /* write New Header Information to a new d99 file */
1311     init_header (fp_d99_new, &fl_hdr);
1312
1313     /* Sequentially read each word key file in big loop.
1314      * For each word, read the d99.
1315      * In validation mode check the dbas.
1316      * If not validating, just blindly rewrite the old d99 to the new one.
1317      * If validating only write good dba's and mark the bit vector.
1318      */
1319     copy_new_d99 (OR_SWORDKEY);
1320     copy_new_d99 (OR_LWORDKEY);
1321     copy_new_d99 (OR_HWORDKEY);
1322
1323
1324     if (reccount == 0)
1325         end_of_job (50, SHOW_PROGRESS + SHOW_EXITCODE);
1326     else
1327         print_progress ("Final");
1328
1329     /* If validation_mode requested, traverse bit vector and print out
1330      * table of each d00 record which cannot be accessed from any d99 word.
1331      * If a validation file name was provided, write out a line for each
1332      * bad reecord in alebeniz-compatible format.
1333      */
1334     if (validation_mode) {
1335         for (x = 0, bvptr = bit_vector;  x < max_bitvec;  x++, bvptr++) {
1336             for (j = 0; j < 8; j += 4) {        /* j = 0 or 4, amount of
1337                                                  * bit shift */
1338                 /* a = bits #1 and #2 of current nibble */
1339                 a = 0x30 & (*bvptr << j);
1340
1341                 /* if dba is in d00 but not in d99... */
1342                 if (a & 0x10 && !(a & 0x20)) {
1343                     /* ...construct valid vista dba */
1344                     dbaorig = x << 1;
1345                     if (j)
1346                         dbaorig++;      /* slot number */
1347                     /***        dba = dbaorig | (OR_D00 << 24); ***//* r
1348                      * eal dba */
1349
1350                     /* now efim retranslates back to real dba */
1351                     dba = ((dbaorig + 1) * recslots - dba_offset)
1352                         | (OR_D00 << 24);
1353
1354                     /* ...print out err msg */
1355                     CRSET (PROGNAME "734", &dba, 0);
1356                     CRREAD (PROGNAME "735", OR_OBJKEY, readbuf, 0);
1357                     fprintf (aa_stderr,
1358                         catgets(dtsearch_catd, MS_dtsrclean, 444,
1359                         "*** %s d00 record '%s' is not referenced in d99.\n"
1360                         "  DBA = %d:%ld (x%02x:%06lx).\n") ,
1361                         PROGNAME"444", readbuf, OR_D00,
1362                         (long)dba, OR_D00, (long)dba);
1363
1364                     /*...if albeniz compatible output requested, do it */
1365                     if (frecids) {
1366                         fprintf (frecids, DISCARD_FORMAT, arg_dbname,
1367                             readbuf, "MrClean", datestr);
1368                     }
1369
1370                     corruption_count++;
1371                     if (max_corruption > 0L  &&
1372                                 corruption_count >= max_corruption)
1373                         end_of_job (94, SHOW_EXITCODE);
1374                 }       /* endif where d00 is not referenced by d99 */
1375             }   /* end forloop: every 2 bits in a bitvector byte */
1376         }       /* end forloop: every byte in bitvector */
1377     }
1378
1379     /* Normal_exitcode currently will contain either a 0 or a 1.
1380      * If we were uncorrupting the d99 and found any corrupt links,
1381      * make sure it's 1 (warning).  If there were corrupt links and
1382      * we weren't trying to uncorrupt it, change it to a hard error.
1383      */
1384  /***by the way, corruption_count can be > 0 only if in validation_mode.**/
1385     if (corruption_count > 0L) {
1386         if (validation_mode)
1387             normal_exitcode = 1;
1388         else
1389             normal_exitcode = 90;
1390     }
1391     end_of_job (normal_exitcode, SHOW_EXITCODE);
1392 }  /* main() */
1393
1394 /*************************** DTSRCLEAN.C ****************************/