dtsr: Coverity fixes for string buffer issues
[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         snprintf(d21new.or_swordkey, 16, "%s", 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         snprintf(d22new.or_lwordkey, 40, "%s", 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             int ret = 0;
695
696             if (num_holes > MAX_REC_READ) {
697                 num_reads = MAX_REC_READ;
698                 num_holes -= MAX_REC_READ;
699             }
700             else {
701                 done = TRUE;
702                 num_reads = num_holes;
703             }
704             errno = 0;
705             ret = fread (word_addrs, sizeof(DB_ADDR), (size_t)num_reads, fp_d99_old);
706             if (errno || -1 == ret) {
707                 TERMINATE_LINE ();
708                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 657,
709                     "%s Read error on %s: %s.\n"),
710                     PROGNAME"657", fname_d99_old, strerror (errno));
711                 end_of_job (4, SHOW_PROGRESS + SHOW_EXITCODE);
712             }
713             /* Note BYTE_SWAP only needed for validation_mode.
714              * If not validating, we're just going to copy
715              * the network format dba's as is directly to
716              * the new d99 file.
717              */
718
719             /*
720              * Addrs on d99 are now 'record numbers' not dbas. A
721              * rec# is what the dba/slot# would be if records took
722              * up just one slot and there were no dbrec at start of
723              * file.  D99 rec#s start at #1, not #0. 
724              */
725
726             /*
727              * If user requested validation_mode, validate each
728              * 'good' rec# (not free slots) in word_addrs buffer. 
729              * If any d99 links are corrupt, skip them when copying
730              * to the new d99 file. Rewrite -1's to all free slots.
731              * ----> NOTE UNUSUAL FORMAT OF DBA HOLES IN D99! <----
732              * Record number is shifted to the high order 3 bytes.
733              * The statistical weight is in the low order byte. The
734              * vista file number is known from the #define constant
735              * OR_D00, and the vista dba/slot# is mapped from rec#
736              * by mult/div number of slots per rec, plus/minus
737              * dbrec offset. 
738              */
739             if (validation_mode) {
740 #ifdef BYTE_SWAP
741                 for (swapx = 0;  swapx < num_reads;  swapx++)
742                     NTOHL (word_addrs[swapx]);
743 #endif
744                 /* set x to number of good addrs in this block */
745                 if (good_addrs_left > num_reads) {
746                     x = num_reads;
747                     good_addrs_left -= num_reads;
748                 }
749                 else {
750                     x = good_addrs_left;
751                     good_addrs_left = 0;
752                 }
753
754                 /*
755                  * Validate the rec#'s in this block.  Note that
756                  * the loop is skipped if the entire block is free
757                  * slots. 
758                  */
759                 good_addrs_this_block = 0;
760                 for (a = 0; a < x; a++) {       /* a = index to curr dba */
761                     /*
762                      * Get rec#.  Save original rec# for err msgs,
763                      * then shift slot number to lower 3 bytes,
764                      * discarding weight. 
765                      */
766                     dbaorig = word_addrs[a];    /* rec#,rec#,rec#:wt */
767                     dba = dbaorig >> 8; /* 0,rec#,rec#,rec# */
768                     is_valid_dba = TRUE;        /* default */
769
770                     /*
771                      * If original rec# == -1 we've overrun the
772                      * good rec#'s into the expansion area, which
773                      * is filled with -1's.  This is real bad news
774                      * because if the counts in d02 are bad, the
775                      * online programs will quickly crash, and we
776                      * can't continue this program. Advance to next
777                      * rec# because we can't mark the bit vector. 
778                      */
779                     if (dbaorig == -1L) {
780                         TERMINATE_LINE ();
781                         fprintf (aa_stderr,
782                             catgets(dtsearch_catd, MS_dtsrclean, 111,
783                             "*** %s DBA in d99 = -1.  "
784                             "Probable overrun into expansion\n"
785                             "  area due to incorrect count values "
786                             "in d2x file.\n"),
787                             PROGNAME"111");
788                         validation_error (dbaorig);
789                         corruption_count++;
790                         if (max_corruption > 0L &&
791                             corruption_count >= max_corruption)
792                             end_of_job (91, SHOW_PROGRESS + SHOW_EXITCODE);
793                         continue;       /* skip the bit vector
794                                          * check */
795                     }
796
797                     /*
798                      * If slot number > max totrecs, we have a
799                      * corrupted d99-d00 link because we've already
800                      * validated the d00 file and we know that it
801                      * has no slots > max.  Also we have to advance
802                      * to next slot because we can't mark the bit
803                      * vector. 
804                      */
805                     /******if (dba >= max_totrecs)*******/
806                     if (dba >= total_num_addrs) {
807                         TERMINATE_LINE ();
808                         fprintf (aa_stderr,
809                             catgets(dtsearch_catd, MS_dtsrclean, 222,
810                             "*** %s DBA in d99 not in d00,"
811                             " slot > max num docs.\n"),
812                             PROGNAME"222");
813                         validation_error (dbaorig);
814                         corruption_count++;
815                         if (max_corruption > 0L &&
816                             corruption_count >= max_corruption)
817                             end_of_job (92, SHOW_PROGRESS + SHOW_EXITCODE);
818                         continue;       /* skip the bit vector check */
819                     }
820
821                     /*
822                      * Verify that dba exists in d00 file (test bit
823                      * #1). If not, mark bit #3 (3rd lowest) in
824                      * nibble and print error msg unless bit #3
825                      * previously marked. 
826                      */
827                     bvptr = bit_vector + (dba >> 1);
828                     is_odd_nibble = (dba & 1L);
829                     if (!(*bvptr & ((is_odd_nibble) ? 0x01 : 0x10))) {
830                                                                 /* bit #1 */
831                         if (!(*bvptr & ((is_odd_nibble) ? 0x04 : 0x40))) {
832                                                                 /* bit #3 */
833                             *bvptr |= (is_odd_nibble) ? 0x04 : 0x40;
834                             TERMINATE_LINE ();
835                             fprintf (aa_stderr,
836                                 catgets(dtsearch_catd, MS_dtsrclean, 333,
837                                 "*** %s DBA in d99 does not exist in d00.\n"),
838                                 PROGNAME"333");
839                             validation_error (dbaorig);
840                             corruption_count++;
841                             if (max_corruption > 0L &&
842                                 corruption_count >= max_corruption)
843                                 end_of_job (93, SHOW_PROGRESS + SHOW_EXITCODE);
844                         }       /* endif where corrupt link
845                                  * detected */
846                     }
847
848                     /*
849                      * Mark bit #2 in bit vector indicating a d99
850                      * reference. 
851                      */
852                     *bvptr |= (is_odd_nibble) ? 0x02 : 0x20;    /* bit #2 */
853
854                     /*
855                      * move good dba to curr output block, incr
856                      * counter 
857                      */
858                     if (is_valid_dba)
859                         word_addrs_out[good_addrs_this_block++] = dbaorig;
860
861                 }       /* end validation loop for each good dba in
862                          * the block */
863
864                 /*
865                  * Write out only validated addrs in current block.
866                  * If this was the last block, fill out all the
867                  * free slots, if any, with -1 values, and exit the
868                  * dba loop for this word. 
869                  */
870                 if (good_addrs_this_block > 0) {
871 #ifdef BYTE_SWAP
872                     for (swapx = 0;  swapx < good_addrs_this_block;  swapx++)
873                         NTOHL (word_addrs_out[swapx]);
874 #endif
875                     num_writes = fwrite (word_addrs_out, sizeof (DB_ADDR),
876                         (size_t)good_addrs_this_block, fp_d99_new);
877                     if (num_writes != good_addrs_this_block)
878                         goto WRITE_ERROR;
879                 }
880                 if (good_addrs_left <= 0) {
881                     /*
882                      * Write blocks of -1s until new d2x free slot
883                      * count is exhausted.  The last block may be <
884                      * MAX_REC_READ. 
885                      */
886                     slots_left = d23new.or_hwfree;
887                     while (slots_left > 0) {
888                         /*
889                          * set x to number of -1's to write for
890                          * this block 
891                          */
892                         if (slots_left > MAX_REC_READ) {
893                             x = MAX_REC_READ;
894                             slots_left -= MAX_REC_READ;
895                         }
896                         else {
897                             x = slots_left;
898                             slots_left = 0;
899                         }
900                         for (a = 0; a < x; a++)
901                             word_addrs_out[a] = (DtSrINT32) -1;
902                         /* BYTE_SWAP not required for foxes */
903                         num_writes = fwrite (word_addrs_out,
904                             sizeof(DB_ADDR), (size_t)x, fp_d99_new);
905                         if (num_writes != x)
906                             goto WRITE_ERROR;
907                     }   /* end while loop to write out all -1's */
908                     done = TRUE;
909                 }
910             }   /* endif for validation_mode for this block */
911
912             /*
913              * If NOT in validation mode, just write out the new
914              * d99 block as an exact copy of the input block. 
915              * BYTE_SWAP not required because word_addrs is
916              * still in its original network order from the fread.
917              */
918             else {
919                 num_writes = fwrite (word_addrs, sizeof(DB_ADDR),
920                     (size_t)num_reads, fp_d99_new);
921                 if (num_writes != num_reads) {
922             WRITE_ERROR:
923                     fprintf (aa_stderr,
924                         catgets(dtsearch_catd, MS_dtsrclean, 665,
925                         "%s Write error on %s: %s.\n"),
926                         PROGNAME"665", fname_d99_new, strerror(errno));
927                     end_of_job (4, SHOW_PROGRESS + SHOW_EXITCODE);
928                 }
929             }   /* endelse for NOT validation_mode for this block */
930
931         }       /* end loop for all blocks for this entire word
932                  * (done = TRUE) */
933
934         /* write the updated d2x record */
935         write_d2x (&d23new, keyfield);
936         reccount++;
937
938         /*
939          * Every now and then print a dot. Print complete progress
940          * msg after DOTS_PER_MSG dots. 
941          */
942         if (!(reccount % recs_per_dot)) {
943             if (++dot_count > DOTS_PER_MSG) {
944                 dot_count = 0;
945                 print_progress ("Progress");
946             }
947             else {
948                 fputc ('.', aa_stderr);
949                 need_linefeed = TRUE;
950                 if (!(dot_count % 10L))
951                     fputc (' ', aa_stderr);
952             }
953             fflush (aa_stderr);
954         }       /* end of print-a-dot */
955
956         if (shutdown_now)
957             end_of_job (shutdown_now, SHOW_PROGRESS + SHOW_EXITCODE);
958         KEYNEXT (PROGNAME "196", keyfield, 0);
959     }   /* end of main loop on each word in database */
960
961
962     return;
963 }  /* copy_new_d99() */
964
965
966 /************************************************/
967 /*                                              */
968 /*                    main()                    */
969 /*                                              */
970 /************************************************/
971 int             main (int argc, char *argv[])
972 {
973     FILE_HEADER     fl_hdr;
974     int             a, i, j;
975     unsigned char  *bvptr;
976     DB_ADDR         dba, dba1, dbaorig;
977     char            dbfpath[1024];
978     char            fname_d21_new[1024];
979     char            fname_d21_old[1024];
980     char            fname_d22_new[1024];
981     char            fname_d22_old[1024];
982     char            fname_d23_new[1024];
983     char            fname_d23_old[1024];
984     FILE           *fp_d21_new = NULL;
985     FILE           *fp_d21_old = NULL;
986     FILE           *fp_d22_new = NULL;
987     FILE           *fp_d22_old = NULL;
988     FILE           *fp_d23_new = NULL;
989     FILE           *fp_d23_old = NULL;
990     char            full_dbname_old[1024];
991     char            full_dbname_new[1024];
992     DtSrINT32       max_bitvec = 0L;
993     int             oops;
994     char           *ptr;
995     char            readbuf[1024 + 32];
996     unsigned long   reads_per_dot;
997     char            recidbuf[DtSrMAX_DB_KEYSIZE + 4];
998     time_t          starttime;
999     DtSrINT32       x;
1000     struct or_dbrec dbrec;
1001
1002     aa_argv0 = argv[0];
1003     setlocale (LC_ALL, "");
1004     dtsearch_catd = catopen (FNAME_DTSRCAT, 0);
1005
1006     time (&starttime);
1007     strftime (dbfpath, sizeof (dbfpath),        /* just use any ol' buffer */
1008         catgets (dtsearch_catd, MS_misc, 22, "%A, %b %d %Y, %I:%M %p"),
1009         localtime (&starttime));
1010     printf ( catgets(dtsearch_catd, MS_dtsrclean, 11,
1011         "%s Version %s.  Run %s.\n") ,
1012         aa_argv0, AUSAPI_VERSION, dbfpath);
1013
1014     signal (SIGHUP, signal_shutdown);
1015     signal (SIGINT, signal_shutdown);
1016     signal (SIGQUIT, signal_shutdown);
1017     signal (SIGTRAP, signal_shutdown);
1018     signal (SIGKILL, signal_shutdown);  /* this cannot be trapped */
1019     signal (SIGALRM, signal_shutdown);
1020     signal (SIGTERM, signal_shutdown);
1021 #ifdef SIGPWR
1022     signal (SIGPWR, signal_shutdown);
1023 #endif
1024 #ifdef _AIX
1025     signal (SIGXCPU, signal_shutdown);
1026     signal (SIGDANGER, signal_shutdown);
1027 #endif
1028
1029     user_args_processor (argc, argv);
1030
1031     /* In order to find old files, we have to check if
1032      * DBFPATH environment variable has been set.
1033      * Load the fully constructed DBFPATH-dbname into its own buffer.
1034      */
1035     full_dbname_old[0] = '\0';
1036     dbfpath[0] = 0;
1037     if ((ptr = getenv ("DBFPATH")) != NULL) {
1038         if (*ptr == 0)
1039             fprintf (aa_stderr,
1040                  catgets(dtsearch_catd, MS_dtsrclean, 12,
1041                 "%s: Ignoring empty DBFPATH environment variable.\n") ,
1042                 aa_argv0);
1043         else {
1044             fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 13,
1045                 "%s: Using DBFPATH = '%s'.\n") ,
1046                 aa_argv0, ptr);
1047             snprintf(full_dbname_old, sizeof(full_dbname_old), "%s", ptr);
1048
1049             /* Ensure that DBFPATH ends in a slash. */
1050             ptr = strchr (full_dbname_old, '\0');
1051             if (*(ptr - 1) != LOCAL_SLASH) {
1052                 *ptr++ = LOCAL_SLASH;
1053                 *ptr = '\0';
1054             }
1055             strcpy (dbfpath, full_dbname_old);
1056         }
1057     }
1058
1059     /* Currently full_dbname_old contains just the path.
1060      * Similarly, build just path name for the 2 new files
1061      * using full_dbname_new as a buffer.
1062      * Verify they don't both refer to the same directory.
1063      */
1064     strcpy (full_dbname_new, arg_newpath);
1065     ptr = strchr (full_dbname_new, '\0');
1066     if (*(ptr - 1) != LOCAL_SLASH) {
1067         *ptr++ = LOCAL_SLASH;
1068         *ptr = '\0';
1069     }
1070     if (strcmp (full_dbname_old, full_dbname_new) == 0) {
1071         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 393,
1072             "%s Old and new directories are identical: '%s'.\n"),
1073             PROGNAME"393", full_dbname_old);
1074         end_of_job (2, SHOW_USAGE);
1075     }
1076
1077     /* Complete full_dbname_old by appending dbname to the path prefix.
1078      * Then build full path/file names for all 4 files.
1079      */
1080     strcat (full_dbname_old, arg_dbname);
1081     strcat (full_dbname_new, arg_dbname);
1082     fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 14,
1083         "%s: Old files: '%s.d2x, .d99'.\n") ,
1084         aa_argv0, full_dbname_old);
1085     fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 15,
1086         "%s: New files: '%s.d2x, .d99'.\n") ,
1087         aa_argv0, full_dbname_new);
1088
1089     strcpy (fname_d99_old, full_dbname_old);
1090     strcat (fname_d99_old, ".d99");
1091     strcpy (fname_d21_old, full_dbname_old);
1092     strcat (fname_d21_old, ".d21");
1093     strcpy (fname_d22_old, full_dbname_old);
1094     strcat (fname_d22_old, ".d22");
1095     strcpy (fname_d23_old, full_dbname_old);
1096     strcat (fname_d23_old, ".d23");
1097     strcpy (fname_d99_new, full_dbname_new);
1098     strcat (fname_d99_new, ".d99");
1099     strcpy (fname_d21_new, full_dbname_new);
1100     strcat (fname_d21_new, ".d21");
1101     strcpy (fname_d22_new, full_dbname_new);
1102     strcat (fname_d22_new, ".d22");
1103     strcpy (fname_d23_new, full_dbname_new);
1104     strcat (fname_d23_new, ".d23");
1105
1106     /* If the user hasn't already authorized overwriting preexisting files,
1107      * check new directory and if new files already exist,
1108      * ask permission to overwrite.
1109      */
1110     if (!overlay_yes) {
1111         oops = FALSE;   /* TRUE forces a user prompt */
1112         if ((fp_d99_new = fopen (fname_d99_new, "r")) != NULL) {
1113             fclose (fp_d99_new);
1114             oops = TRUE;
1115         }
1116         if ((fp_d21_new = fopen (fname_d21_new, "r")) != NULL) {
1117             fclose (fp_d21_new);
1118             oops = TRUE;
1119         }
1120         if ((fp_d22_new = fopen (fname_d22_new, "r")) != NULL) {
1121             fclose (fp_d22_new);
1122             oops = TRUE;
1123         }
1124         if ((fp_d23_new = fopen (fname_d23_new, "r")) != NULL) {
1125             fclose (fp_d23_new);
1126             oops = TRUE;
1127         }
1128         if (oops) {
1129             fprintf (aa_stderr,  catgets(dtsearch_catd, MS_dtsrclean, 24,
1130                 "%s: One or more new files already exist.\n") ,
1131                 aa_argv0);
1132             if (overlay_no) {
1133                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 463,
1134                     "%s Command line argument disallows file overlay.\n"),
1135                     PROGNAME"463");
1136                 end_of_job (2, SHOW_EXITCODE);
1137             }
1138             fputs (catgets(dtsearch_catd, MS_dtsrclean, 45,
1139                 "    Is it ok to overlay files in new directory? [y/n] "),
1140                 aa_stderr);
1141
1142             *readbuf = '\0';
1143             if(NULL == fgets (readbuf, sizeof(readbuf), stdin)) {
1144                   fprintf (aa_stderr, "Failed to read from stdin\n");
1145                   end_of_job (2, SHOW_EXITCODE);
1146             }
1147             if (strlen(readbuf) && readbuf[strlen(readbuf)-1] == '\n')
1148               readbuf[strlen(readbuf)-1] = '\0';
1149
1150             if (tolower (*readbuf) != 'y')
1151                 end_of_job (2, SHOW_NOTHING);
1152         }
1153     }   /* end of check for overlaying new files */
1154
1155     /* Open all files.  The d2x's are opened so that the old ones
1156      * can be copied into the new directory before starting
1157      * the garbage collection process proper.
1158      * The d99's are opened now just to verify permissions.
1159      */
1160     oops = FALSE;  /* TRUE ends job, but only after trying all 4 files */
1161     open_all_files (&fp_d21_old, fname_d21_old, "rb", &size_d21_old, &oops);
1162     open_all_files (&fp_d22_old, fname_d22_old, "rb", &size_d22_old, &oops);
1163     open_all_files (&fp_d23_old, fname_d23_old, "rb", &size_d23_old, &oops);
1164     open_all_files (&fp_d99_old, fname_d99_old, "rb", &size_d99_old, &oops);
1165     open_all_files (&fp_d21_new, fname_d21_new, "wb", NULL, &oops);
1166     open_all_files (&fp_d22_new, fname_d22_new, "wb", NULL, &oops);
1167     open_all_files (&fp_d23_new, fname_d23_new, "wb", NULL, &oops);
1168     open_all_files (&fp_d99_new, fname_d99_new, "wb", NULL, &oops);
1169
1170     if (shutdown_now)
1171         end_of_job (shutdown_now, SHOW_EXITCODE);
1172     if (oops)
1173         end_of_job (2, SHOW_EXITCODE);
1174
1175     /* Copy old d2x files to new directory.
1176      * Database will open using new files so only they will be changed.
1177      */
1178     copy_old_d2x_to_new (fname_d21_old, fname_d21_new, fp_d21_old, fp_d21_new);
1179     copy_old_d2x_to_new (fname_d22_old, fname_d22_new, fp_d22_old, fp_d22_new);
1180     copy_old_d2x_to_new (fname_d23_old, fname_d23_new, fp_d23_old, fp_d23_new);
1181
1182     /* Open database, but use new d2x files for updates. */
1183     RENFILE (PROGNAME"1102", arg_dbname, OR_D21, fname_d21_new);
1184     RENFILE (PROGNAME"1104", arg_dbname, OR_D22, fname_d22_new);
1185     RENFILE (PROGNAME"1106", arg_dbname, OR_D23, fname_d23_new);
1186     if (!austext_dopen (arg_dbname, (dbfpath[0] == 0) ? NULL : dbfpath,
1187             NULL, 0, &dbrec)) {
1188         puts (DtSearchGetMessages ());
1189         end_of_job (3, SHOW_EXITCODE);
1190     }
1191
1192     /* This is where efim changed real dba to
1193      * record number (still called dba)
1194      */
1195     RECFRST (PROGNAME "1067", OR_OBJREC, 0);
1196     CRGET (PROGNAME "1068", &dba, 0);   /* dba of first real obj
1197                                          * record */
1198     recslots = dbrec.or_recslots;       /* vista slots per obj
1199                                          * record */
1200     dba_offset = recslots - (dba & 0xffffff);   /* accounts for dbrec */
1201
1202     /* total_num_addrs = what reccount would be if
1203      * all holes were filled with good records.
1204      */
1205     total_num_addrs = (dbrec.or_maxdba - (dba & 0xffffff) + 1) / recslots + 1;
1206     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 25,
1207         "%s: curr reccnt=%ld, mxdba=%ld, sl/rec=%ld, tot#adr=%ld.\n") ,
1208         aa_argv0, (long)dbrec.or_reccount, (long)dbrec.or_maxdba,
1209         (long)dbrec.or_recslots, (long)total_num_addrs);
1210
1211     /* Initialize validation_mode (checkd99) */
1212     if (validation_mode) {
1213         /*
1214          * Allocate and initialize a bit vector: 4 bits for every
1215          * possible d00 database address. 
1216          */
1217         max_bitvec = (total_num_addrs >> 1) + 2;
1218         if ((bit_vector = malloc ((size_t)max_bitvec + 64)) == NULL) {
1219             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 465,
1220                 "%s WARNING: Can't allocate memory for bit vector.\n"
1221                 "  'Validate' mode switched off.\n"),
1222                 PROGNAME"465");
1223             validation_mode = FALSE;
1224             normal_exitcode = 1;        /* warning */
1225             goto EXIT_INIT_VALIDATION;
1226         }
1227         memset (bit_vector, 0, (size_t)max_bitvec);
1228
1229         /*
1230          * Read every d00 rec sequentially.  1 in bit #1 (lowest
1231          * order) in bit vector means record (dba) exists in d00
1232          * file. While we're at it, count the total number of
1233          * records. 
1234          */
1235         x = dbrec.or_reccount / 50 + 1; /* x = recs per dot */
1236         fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 26,
1237             "%s: Reading d00 file.  Each dot appx %ld database documents...\n"),
1238             aa_argv0, (long)x);
1239         reccount = 0;
1240         dot_count = 0L;
1241         RECFRST (PROGNAME "534", OR_OBJREC, 0);
1242         while (db_status == S_OKAY) {
1243             CRREAD (PROGNAME "617", OR_OBJKEY, recidbuf, 0);
1244
1245             /* print periodic progress dots */
1246             if (!(++reccount % x)) {
1247                 fputc ('.', aa_stderr);
1248                 need_linefeed = TRUE;
1249                 if (!(++dot_count % 10L))
1250                     fputc (' ', aa_stderr);
1251                 fflush (aa_stderr);
1252             }
1253
1254             /*
1255              * Get dba and record number and confirm it will not
1256              * overflow bit vector. 
1257              */
1258             CRGET (PROGNAME "537", &dba, 0);
1259             dba &= 0x00ffffff;  /* mask out file number in high order byte */
1260             dba1 = (dba + dba_offset) / recslots;  /* ="rec number", base 1 */
1261             if (dba1 >= total_num_addrs) {
1262                 TERMINATE_LINE ();
1263                 fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 561,
1264                     "%s DBA '%d:%ld' (rec #%ld) in d00 exceeds "
1265                     "total num addrs %ld;\n"
1266                     "  Bit vector overflow because maxdba %ld"
1267                     " in dbrec is incorrect.\n"),
1268                     PROGNAME"561", OR_D00, (long)dba, (long)dba1,
1269                     (long)total_num_addrs, (long)dbrec.or_maxdba);
1270                 end_of_job (7, SHOW_EXITCODE);
1271             }
1272             if (shutdown_now)
1273                 end_of_job (shutdown_now, SHOW_EXITCODE);
1274
1275             /*
1276              * Set bit #1 of even or odd nibble to indicate that
1277              * this record *number* actually exists in d00 file. 
1278              */
1279             bit_vector[dba1 >> 1] |= (dba1 & 1L) ? 0x01 : 0x10;
1280
1281             RECNEXT (PROGNAME "541", 0);
1282         }       /* end of sequential read thru d00 file */
1283
1284         TERMINATE_LINE ();      /* end the dots... */
1285
1286         /* confirm that RECCOUNT record holds the correct number */
1287         if (dbrec.or_reccount == reccount) {
1288             fprintf (aa_stderr,
1289                  catgets(dtsearch_catd, MS_dtsrclean, 27,
1290                 "%s: Confirmed %ld DOCUMENTS in %s.d00.\n") ,
1291                 aa_argv0, (long)dbrec.or_reccount, arg_dbname);
1292         }
1293         else {
1294             fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 28,
1295                 "%s: %ld DOCUMENTS actually in %s.d00 not ="
1296                 " %ld count stored there.\n"
1297                 "  Count will be corrected in new d00 file.\n") ,
1298                 aa_argv0, (long)reccount, arg_dbname, (long)dbrec.or_reccount);
1299             dbrec.or_reccount = reccount;
1300             rewrite_reccount = TRUE;
1301         }
1302
1303 EXIT_INIT_VALIDATION:;
1304     }   /* end of validation_mode initialization */
1305
1306     /* initialize main loop */
1307     time (&timestart);
1308     reccount = 0;
1309     bytes_in = 0L;
1310     dot_count = DOTS_PER_MSG;   /* force initial msg after first
1311                                  * blk of recs */
1312     TERMINATE_LINE ();
1313     fprintf (aa_stderr, catgets(dtsearch_catd, MS_dtsrclean, 29,
1314         "%s: Compressing into %s.  Each dot appx %lu words...\n") ,
1315         aa_argv0, arg_newpath, (unsigned long)recs_per_dot);
1316
1317     /* write New Header Information to a new d99 file */
1318     init_header (fp_d99_new, &fl_hdr);
1319
1320     /* Sequentially read each word key file in big loop.
1321      * For each word, read the d99.
1322      * In validation mode check the dbas.
1323      * If not validating, just blindly rewrite the old d99 to the new one.
1324      * If validating only write good dba's and mark the bit vector.
1325      */
1326     copy_new_d99 (OR_SWORDKEY);
1327     copy_new_d99 (OR_LWORDKEY);
1328     copy_new_d99 (OR_HWORDKEY);
1329
1330
1331     if (reccount == 0)
1332         end_of_job (50, SHOW_PROGRESS + SHOW_EXITCODE);
1333     else
1334         print_progress ("Final");
1335
1336     /* If validation_mode requested, traverse bit vector and print out
1337      * table of each d00 record which cannot be accessed from any d99 word.
1338      * If a validation file name was provided, write out a line for each
1339      * bad reecord in alebeniz-compatible format.
1340      */
1341     if (validation_mode) {
1342         for (x = 0, bvptr = bit_vector;  x < max_bitvec;  x++, bvptr++) {
1343             for (j = 0; j < 8; j += 4) {        /* j = 0 or 4, amount of
1344                                                  * bit shift */
1345                 /* a = bits #1 and #2 of current nibble */
1346                 a = 0x30 & (*bvptr << j);
1347
1348                 /* if dba is in d00 but not in d99... */
1349                 if (a & 0x10 && !(a & 0x20)) {
1350                     /* ...construct valid vista dba */
1351                     dbaorig = x << 1;
1352                     if (j)
1353                         dbaorig++;      /* slot number */
1354                     /***        dba = dbaorig | (OR_D00 << 24); ***//* r
1355                      * eal dba */
1356
1357                     /* now efim retranslates back to real dba */
1358                     dba = ((dbaorig + 1) * recslots - dba_offset)
1359                         | (OR_D00 << 24);
1360
1361                     /* ...print out err msg */
1362                     CRSET (PROGNAME "734", &dba, 0);
1363                     CRREAD (PROGNAME "735", OR_OBJKEY, readbuf, 0);
1364                     fprintf (aa_stderr,
1365                         catgets(dtsearch_catd, MS_dtsrclean, 444,
1366                         "*** %s d00 record '%s' is not referenced in d99.\n"
1367                         "  DBA = %d:%ld (x%02x:%06lx).\n") ,
1368                         PROGNAME"444", readbuf, OR_D00,
1369                         (long)dba, OR_D00, (long)dba);
1370
1371                     /*...if albeniz compatible output requested, do it */
1372                     if (frecids) {
1373                         fprintf (frecids, DISCARD_FORMAT, arg_dbname,
1374                             readbuf, "MrClean", datestr);
1375                     }
1376
1377                     corruption_count++;
1378                     if (max_corruption > 0L  &&
1379                                 corruption_count >= max_corruption)
1380                         end_of_job (94, SHOW_EXITCODE);
1381                 }       /* endif where d00 is not referenced by d99 */
1382             }   /* end forloop: every 2 bits in a bitvector byte */
1383         }       /* end forloop: every byte in bitvector */
1384     }
1385
1386     /* Normal_exitcode currently will contain either a 0 or a 1.
1387      * If we were uncorrupting the d99 and found any corrupt links,
1388      * make sure it's 1 (warning).  If there were corrupt links and
1389      * we weren't trying to uncorrupt it, change it to a hard error.
1390      */
1391  /***by the way, corruption_count can be > 0 only if in validation_mode.**/
1392     if (corruption_count > 0L) {
1393         if (validation_mode)
1394             normal_exitcode = 1;
1395         else
1396             normal_exitcode = 90;
1397     }
1398     end_of_job (normal_exitcode, SHOW_EXITCODE);
1399 }  /* main() */
1400
1401 /*************************** DTSRCLEAN.C ****************************/