2 * COMPONENT_NAME: austext
4 * FUNCTIONS: descend_tree
11 * put_addrs_2_dtbs_addr_file
15 * write_2_dtbs_addr_file
16 * write_new_word_2_dtbs
22 * (C) COPYRIGHT International Business Machines Corp. 1992,1996
24 * Licensed Materials - Property of IBM
25 * US Government Users Restricted Rights - Use, duplication or
26 * disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
28 /************************ DTSRINDEX.C *******************************
29 * $XConsortium: dtsrindex.c /main/10 1996/09/23 21:02:54 cde-ibm $
30 * CDE version of borodin.c
31 * Formerly dtsrindex.c was cborodin.c.
34 * Text file in FZK format.
35 * Each record contains 4 formatted 'lines' (text strings ending in \n):
36 * 1. fzkey (not used in this program).
37 * 2. abstract (not used in this program).
38 * 3. unique database key for the record. Used to find the database
39 * address of the record which is the reference for the inverted index.
40 * 4. The record's date (not used in this program).
42 * The rest of the record is unformatted text (not necessarily organized
43 * into 'lines'). It is read a character at a time and parsed into
44 * individual words by the parser function for the database's language.
45 * Each record ends with a delimiter string specified by command line arg.
48 * Revision 2.8 1996/04/10 19:50:38 miker
49 * Deleted dangerous and unnecessary -a option.
51 * Revision 2.7 1996/03/25 18:54:15 miker
52 * Changed FILENAME_MAX to _POSIX_PATH_MAX.
54 * Revision 2.6 1996/02/01 18:25:44 miker
55 * AusText 2.1.11, DtSearch 0.3. Pass 1 changed to accommodate
56 * new single-character reading parser/stemmers.
58 * Revision 2.5 1995/12/29 17:16:04 miker
59 * Bug fix: Opened wrong msg catalog.
61 * Revision 2.4 1995/12/27 21:18:40 miker
62 * Msg bug: 'percent done' was negative number.
64 * Revision 2.3 1995/12/01 16:15:44 miker
65 * Deleted unnecessary log2 var, conflict with Solaris function.
66 * Added -r command line arg.
68 * Revision 2.2 1995/10/26 15:26:53 miker
71 * Revision 2.1 1995/09/22 19:29:53 miker
72 * Freeze DtSearch 0.1, AusText 2.1.8
74 * Revision 1.3 1995/09/05 21:08:54 miker
75 * Fixed bug: appeared as if 1 and 2 char 'words' were being indexed.
76 * Added DEBUG_P switch.
78 * Revision 1.2 1995/09/01 22:17:02 miker
79 * Fixed solaris segfault: too many args to printf in print_usage().
81 * Revision 1.1 1995/08/31 20:51:08 miker
82 * Initial revision of dtsrindex.c, copied from cborodin.c.
85 * Revision 1.18 1995/05/30 18:58:54 miker
86 * Correct bug introduced by previous fix (2.1.5c).
88 * Revision 1.17 1995/05/18 22:54:08 miker
89 * 2.1.5b cborodin bug. Segfault due to overflowing bitvector
90 * after many deletions and no mrclean.
101 #include <sys/stat.h>
105 extern void find_keyword (char *cur_word, int vista_num);
106 extern void read_wordstr (struct or_hwordrec * glob_word, int vista_num);
107 extern void write_wordstr (struct or_hwordrec * glob_word, int vista_num);
108 extern void fill_data1 (char *ch);
110 #define PROGNAME "DTSRINDEX"
112 #define BATCH_SIZE 10000L
113 #define WORDS_PER_DOT 500
114 #define RECS_PER_DOT 20
115 #define INBUFSZ 1024 /* default input text header line size */
117 #define MS_cborodin 14
119 /******************* BIT VECTORS *****************/
120 DB_ADDR *word_addrs_ii; /* fread buf for d99 (= tot # dbas) */
121 DtSrINT32 *dbas_word_count;
122 char *dbas_bits_batch;
123 DB_ADDR *record_addr_word;
124 DtSrINT32 num_addrs_for_word;
125 DtSrINT32 or_reccount;
126 DtSrINT32 bit_vector_size;
128 /*-------------------------- GLOBALS ----------------------------*/
129 /* batch_size also used by fileman.c for allocating unused holes
130 * in order to no go past end of 'record_addr_word' array.
132 extern DtSrINT32 batch_size;
135 static int cache_size = CACHE_SIZE;
136 static int check_existing_addrs = TRUE;
137 long count_word_ii = 0L;
138 long dbkey_seqno = 0L;
141 static int debugging = 0;
142 #define DEBUG_I 0x01 /* P1 tree insertions */
143 #define DEBUG_P 0x10 /* P1 parser/stemmer */
144 #define DEBUG_T 0x02 /* P2 tree dump (words) */
145 #define DEBUG_N 0x04 /* P2 NEW words, vista */
146 #define DEBUG_O 0x08 /* P2 OLD words, vista) */
147 #define DEBUG_t 0x20 /* P2 tree dump (dbas) */
148 #define DEBUG_n 0x40 /* P2 NEW d99 for new words */
149 #define DEBUG_o 0x80 /* P2 OLD d99 updates for old words */
153 char dicpath [_POSIX_PATH_MAX];
154 static int dotcount = 0;
155 char dtbs_addr_file [_POSIX_PATH_MAX];
157 long dtbs_size_records = 0L;
158 static long duplicate_recids = 0L;
159 struct stat fstat_input;
161 static char fname_input [_POSIX_PATH_MAX];
164 static FILE *instream;
166 int inbuf_overflowed = FALSE;
167 size_t inbufsz = INBUFSZ;
171 static char msg_374[] = "\n%s Out of Memory!\n"
172 " Split the incoming file into several "
173 "smaller files and try again.\n";
174 static char msg_776[] = "\n%s Write Failure d99 file: %s\n";
175 char new_dtbs_file = FALSE;
176 long num_of_diff_words = 0L;
177 int normal_retncode = 0;
179 int parsep_char = END_RETAIN_PAGE;
181 unsigned long record_count = 0UL;
183 static int recs_per_dot = RECS_PER_DOT;
186 extern int shutdown_now;
189 char *sprintbuffer = NULL;
191 extern int debugging_teskey;
192 time_t timestart = 0;
193 time_t totalstart = 0;
194 static int words_per_dot = WORDS_PER_DOT;
196 /************************************************/
200 /************************************************/
201 typedef struct dba_str {
204 struct dba_str *next_dba;
207 /************************************************/
211 /************************************************/
212 typedef struct _treen_ {
213 char *word; /* ptr to word in stop list */
214 struct _treen_ *llink; /* left link in binary tree */
215 struct _treen_ *rlink; /* ptr to right link in binary tree */
219 static TREENODE *root_node = NULL;
220 static TREENODE *top_of_stack;
221 static TREENODE *stack;
222 static TREENODE *pres;
223 static TREENODE *prev;
224 static TREENODE *next;
225 static TREENODE *avail_node;
229 /************************************************/
233 /************************************************/
234 /* Returns static string same as passed string except nonprintable
235 * and nonascii chars replaced by '^' for display.
237 static char *displayable (char *passed_string)
239 static char *buf = NULL;
240 static size_t buflen = 0;
241 size_t passed_len = strlen (passed_string);
243 if (buflen < passed_len) {
247 buf = austext_malloc (buflen + 4, PROGNAME"158", NULL);
250 for (src = passed_string; *src != 0; src++) {
251 if (*src >= 32 && *src < 127)
258 } /* displayable() */
261 /************************************************/
263 /* print_exit_code */
265 /************************************************/
266 /* Called from inside DtSearchExit() at (*austext_exit_last)() */
267 static void print_exit_code (int exit_code)
273 /* Put total seconds into totalstart */
275 totalstart = time (NULL) - totalstart;
276 printf (catgets (dtsearch_catd, MS_cborodin, 206,
277 "%s: Exit Code = %d, Total elapsed time %ldm %lds.\n"),
278 aa_argv0, exit_code, totalstart / 60L, totalstart % 60L);
280 } /* print_exit_code() */
283 /****************************************/
285 /* write_to_file() */
287 /****************************************/
288 /* This is the 'visit node' point for the tree traversal
289 * functions of Pass 2 (traverse_tree() and descend_tree()).
291 * Each tree node = word or stem + linked list of dbas.
292 * When called, each dba list member just contains the number
293 * of times the token appears in that document. This function
294 * chains through the list, builds a statistical 'weight'
295 * for each doc/word pair, and stores it as a reformatted 'dba'
296 * in array 'record_addr_word[]', in 'host' byte swap order.
297 * The count of the current number of addrs
298 * in the array is stored in 'num_addrs_for_word'.
299 * Fill_data1() is then called to update or write a new
300 * vista record and d99 data for the token.
302 * The weight stored for each doc-word instance is 1 byte.
303 * It's the ratio of log of number of times given word occurs in doc,
304 * divided by log of total count of all words in doc,
305 * scaled to range 0 to 255.
306 * Fundamentally it's a word count of that word in the doc,
307 * but adjusted as follows:
308 * 1) Large occurrances in small documents weigh more than
309 * the same number of occurrances in large documents.
310 * 2) Taking the log skews the ratio to be more linear,
311 * ie take advantage of higher ranges of the 'weight'.
312 * For example a word that occurs in 10% of the document,
313 * will have a weight of .5 (50%).
314 * 3) The scaling changes the ratio, a float between 0. and .9999,
315 * to an integer between 0 and 255.
317 void write_to_file (TREENODE * output_node)
322 /* 'record_addr_word[]' was permanently allocated
323 * with a size = max batch size so it can hold
324 * all the addrs for a single word node in the tree.
325 * In effect it will replace the dba linked list.
326 * Note: word_addrs_ii (io buffer for d99 file) != record_addr_word[].
329 if (debugging & (DEBUG_T | DEBUG_t)) { /* Print out tree node */
330 printf (" node '%s' %c%c%c",
331 displayable(output_node->word),
332 (output_node->llink)? 'L' : '.',
333 (output_node->rlink)? 'R' : '.',
334 (debugging & DEBUG_t)? '\n' : ' ');
337 num_addrs_for_word = 0; /* DtSrINT32 */
338 print_dba = output_node->dba_list;
339 while (print_dba != NULL) {
341 mydba = print_dba->dba;
342 if (debugging & DEBUG_t)
343 printf (" dba #%ld: node adr=%ld cnt=%ld",
344 (long)num_addrs_for_word, (long)mydba, (long)print_dba->w_c);
346 record_addr_word [num_addrs_for_word] =
347 mydba << 8; /* rec# in hi 3 bytes */
348 record_addr_word [num_addrs_for_word] +=
349 (log ((double) (print_dba->w_c) + 0.5) /
350 log ((double) (dbas_word_count[mydba] + 1))) * 256;
352 if (debugging & DEBUG_t)
353 printf (" -> x%lx (%ld:%ld)\n",
354 (long)record_addr_word [num_addrs_for_word],
355 (long)record_addr_word [num_addrs_for_word] >> 8,
356 (long)record_addr_word [num_addrs_for_word] & 0xffL);
358 print_dba = print_dba->next_dba;
359 num_addrs_for_word++;
360 if (num_addrs_for_word >= batch_size) {
361 printf (catgets (dtsearch_catd, MS_cborodin, 280,
362 "\n%s num_addrs_for_word (%ld) >= batchsz (%ld).\n"),
363 PROGNAME"280", (long)num_addrs_for_word, (long)batch_size);
367 if ((debugging & DEBUG_T) && !(debugging & DEBUG_t))
368 printf (" dbacnt=%ld\n", (long)num_addrs_for_word);
370 fill_data1 (output_node->word);
373 } /* write_to_file() */
376 /****************************************/
380 /****************************************/
381 /* Coroutine of traverse_tree(), Pass 2 Robson tree traversal.
382 * The write_to_file() function is the 'preorder visit' point.
384 void descend_tree (void)
389 if ((pres->llink == NULL) && (pres->rlink == NULL)) {
390 write_to_file (pres);
394 if (pres->llink != NULL) {
401 write_to_file (pres);
409 } /* descend_tree() */
412 /********************************/
416 /********************************/
417 /* This is the actual Pass 2 function, a tree traversal
418 * of Pass 1's word-dba binary tree.
419 * The algorithm is based on the J. M. ROBSON link inversion traversal
420 * algorithm for binary trees. Ref. Thomas A. STANDISH pp. 77-78.
421 * The write_to_file() function is the 'preorder visit' point.
423 void traverse_tree (void)
428 /* Dheck for the empty tree */
429 if (root_node == NULL) {
430 printf (catgets (dtsearch_catd, MS_cborodin, 288,
431 "%s Abort. There are no words in the input file %s.\n"),
432 PROGNAME"288", fname_input);
435 /* Initialize the variables */
445 if (pres == root_node) {
448 if (prev->rlink == NULL) {
449 write_to_file (prev);
457 if (prev->llink == NULL) {
465 if (prev == top_of_stack) {
467 top_of_stack = stack->rlink;
468 stack = stack->llink;
472 prev->llink = prev->rlink;
479 write_to_file (prev);
480 avail_node->llink = stack;
481 avail_node->rlink = top_of_stack;
492 } /* traverse_tree() */
496 /********************************************************/
498 /* print_usage_msg */
500 /********************************************************/
501 static void print_usage_msg (void)
503 printf (catgets (dtsearch_catd, MS_cborodin, 17,
505 "USAGE: %s -d<dbname> [options] <infile>\n"
506 " Listed default file name extensions can be overridden.\n"
507 " -d<dbname> 1 - 8 character database name, include optional path prefix.\n"
508 " -t<etxstr> End of text document delimiter string. Default '\\f\\n'.\n"
509 " -r<N> Change Pass 1 records-per-dot from %d to <N>.\n"
510 " -b<N> Change max batch size from %ld to <N>.\n"
511 " -c<N> Change database paging cache from %ld 1K pages to <N> 1K pages.\n"
512 " <N> >= 16 by powers of 2. Initially try only small changes.\n"
513 " -i<N> Change (i)nput buffer size from default %d to <N>.\n"
514 " -h<N> Change duplicate record id hash table size from %ld to <N>.\n"
515 " -h0 means there are no duplicates, do not check for them.\n"
516 " <infile> Input [path]file name. Default extension %s.\n"),
519 (long) BATCH_SIZE, (long) CACHE_SIZE,
520 (int) INBUFSZ, default_hashsize, EXT_FZKEY);
522 } /* print_usage_msg() */
525 /********************************************************/
527 /* segregate_dicname */
529 /********************************************************/
530 /* Separates dictionary name from pathname and loads
531 * them into the globals 'dicname' and 'dicpath'.
532 * Returns TRUE if dicname is valid, else returns FALSE.
534 static int segregate_dicname (char *string)
536 char mybuf[_POSIX_PATH_MAX];
540 strncpy (mybuf, string, sizeof (mybuf));
541 mybuf[sizeof (mybuf) - 1] = 0;
544 * Set 'ptr' to just the 8 char dictionary name by moving
545 * it backwards until first non-alphanumeric character
546 * (such as a ":" in the dos drive id or a slash between directories),
547 * or to the beginning of string.
549 for (ptr = mybuf + strlen (mybuf) - 1; ptr >= mybuf; ptr--)
550 if (!isalnum (*ptr)) {
557 /* test for valid dictionary name */
562 strcpy (dicname, ptr);
564 strncpy (dicpath, mybuf, sizeof (dicpath));
565 dicpath[sizeof (dicpath) - 1] = 0;
567 } /* segregate_dicname() */
570 /********************************************************/
572 /* USER_ARGS_PROCESSOR */
574 /********************************************************/
575 /* handles command line arguments for 'main' */
576 void user_args_processor (int argc, char **argv)
586 /* Initialize some variables prior to parsing command line */
590 /* Each pass grabs new parm of "-xxx" format */
591 while (--argc > 0 && (*++argv)[0] == '-') {
595 case 't': /* ETX delimiter string */
596 /* Replace any "\n" string with real linefeed */
597 targ = parg.etxdelim = malloc (strlen (argptr + 2) + 4);
600 if (src[0] == '\\' && src[1] == 'n') {
611 if ((recs_per_dot = atoi (argptr + 2)) <= 0) {
612 printf (catgets (dtsearch_catd, MS_cborodin, 577,
613 "%s Invalid arg '%s'. Using default -r%d.\n"),
614 PROGNAME"577", argptr, RECS_PER_DOT);
615 recs_per_dot = RECS_PER_DOT;
620 duprec_hashsize = atol (argptr + 2);
621 if (duprec_hashsize == 0UL)
622 printf (catgets (dtsearch_catd, MS_cborodin, 539,
623 "%s Duplicate record id checking disabled.\n"),
628 batch_size = atol (argptr + 2);
629 if (batch_size <= 0L) {
630 printf (catgets (dtsearch_catd, MS_cborodin, 595,
631 "%s Invalid batch size argument '%s'.\n"),
632 PROGNAME"595", argptr);
638 cache_size = atoi (argptr + 2);
639 if (cache_size < 16) {
640 /* minimum size is 16 */
643 /* on error reset size to default */
645 cache_size = CACHE_SIZE;
647 printf (catgets (dtsearch_catd, MS_cborodin, 600,
648 "%sCache size readjusted to %d.\n"),
649 PROGNAME "600 ", cache_size);
652 /* If necessary, round up to nearest power of 2 */
653 for (i = 4; i < 12; i++)
654 if (1 << i >= cache_size)
657 if (i != cache_size) {
663 case 'D': /* unadvertised debugging feature */
664 for (i = 2; argptr[i] != 0; i++) {
666 case 'I': debugging |= DEBUG_I; break;
667 case 'P': debugging |= DEBUG_P;
668 /******* debugging_teskey = TRUE; ******/
670 case 'N': debugging |= DEBUG_N; break;
671 case 'n': debugging |= DEBUG_n; break;
672 case 'O': debugging |= DEBUG_O; break;
673 case 'o': debugging |= DEBUG_o; break;
674 case 'T': debugging |= DEBUG_T; break;
675 case 't': debugging |= DEBUG_t; break;
676 default: goto BADPARM;
682 /* May include both dicname and dicpath */
683 if (!segregate_dicname (argptr + 2)) {
684 printf (catgets (dtsearch_catd, MS_cborodin, 550,
685 "%s '%s' is invalid path/database name.\n"),
686 PROGNAME"550", argptr);
691 case 'i': /* (I)nput buffer size */
692 if ((inbufsz = atol (argptr + 2)) <= 0) {
693 printf (catgets (dtsearch_catd, MS_cborodin, 558,
694 "%s Invalid input buffer size '%s'.\n"),
695 PROGNAME"558", argptr);
701 printf (catgets (dtsearch_catd, MS_cborodin, 567,
702 "%s Unknown command line argument '%s'.\n"),
703 PROGNAME"567", argptr);
706 DtSearchExit (2); /* abort */
709 } /* endwhile for cmd line '-'processing */
711 /* Validate input file name */
713 printf (catgets (dtsearch_catd, MS_cborodin, 580,
714 "%s Missing required input file name.\n"),
718 /* Don't incr argv yet--save input file name */
720 append_ext (fname_input, _POSIX_PATH_MAX, argv[0], EXT_FZKEY);
722 /* Check for missing database name */
723 if (dicname[0] == 0) {
724 printf (catgets (dtsearch_catd, MS_cborodin, 589,
725 "%s No database name specified (-d argument).\a\n"),
729 strcpy (dblk.name, dicname);
732 } /* user_args_processor() */
735 /****************************************/
737 /* put_addrs_2_dtbs_addr_file */
739 /****************************************/
740 /* Suboutine of write_2_dtbs_addr_file() from Pass 2.
741 * That function has used a bit vector to determine
742 * the total change in old d99 addrs for preexisting words,
743 * and prepared for writing an array of old dbas that
744 * are not in the current words tree node (globally named
745 * word_addrs_ii [num_addrs]).
746 * The addrs that ARE in the Pass 1 node fzk file were previously
747 * prepared in a similar array of dbas, globally named
748 * record_addr_word [num_addrs_for_word] but passed here as
749 * 'addrs_array' and 'nitems'.
750 * Both arrays will be byte swapped from 'host' to
751 * 'network' order in this function.
752 * This function does the actual fwrite of both arrays to the d99.
753 * If the number of new addrs can fit in the available free slots,
754 * it rewrites to original offset, otherwise appends to end of d99.
756 static void put_addrs_2_dtbs_addr_file (
757 DB_ADDR *addrs_array,
760 FREE_SPACE_STR *free_slot;
761 FREE_SPACE_STR del_rec;
763 DtSrINT32 num_writes;
766 if (nitems >= batch_size) {
767 printf ( catgets(dtsearch_catd, MS_cborodin, 6,
768 "put_addrs_2_dtbs_addr_file() nitems=%d, batchsz=%ld\n") ,
769 (int)nitems, (long)batch_size);
773 num_addrs = got_word.or_hwaddrs;
774 got_word.or_hwaddrs += nitems; /** somehow, this can exceed total
775 **** num addrs in database by 1 (!?) ******/
776 /* (...only if prev 'overlay/compression' didn't delete all) */
779 /* Put both arrays in 'network' byte order */
780 for (int32 = 0; int32 < nitems; int32++)
781 HTONL (addrs_array[int32]);
782 for (int32 = 0; int32 < num_addrs; int32++)
783 HTONL (word_addrs_ii[int32]);
787 * If number of new addresses greater than number of free holes,
788 * find new free slot that is big enough to hold the data .
790 if (nitems > got_word.or_hwfree) {
791 /* Discard old slot, find new one. */
792 del_rec.hole_size = num_addrs + got_word.or_hwfree;
793 del_rec.offset = got_word.or_hwoffset;
794 free_slot = find_free_space (got_word.or_hwaddrs, &fl_hdr);
795 add_free_space (&del_rec, &fl_hdr);
796 if (free_slot == NULL) {
797 fseek (dtbs_addr_fp, 0L, SEEK_END);
798 got_word.or_hwoffset = ftell (dtbs_addr_fp);
799 got_word.or_hwfree = 0;
802 fseek (dtbs_addr_fp, free_slot->offset, SEEK_SET);
803 got_word.or_hwoffset = free_slot->offset;
804 got_word.or_hwfree = free_slot->hole_size -
807 /*----- Write new database addresses to a file -----*/
808 num_writes = fwrite (addrs_array, sizeof(DB_ADDR),
809 (size_t)nitems, dtbs_addr_fp);
810 if (num_writes != nitems) {
814 /* Copy the old addresses immediately after the new ones */
815 num_writes = fwrite (word_addrs_ii, sizeof(DB_ADDR), (size_t)num_addrs,
817 if (num_writes != num_addrs) {
818 printf (catgets (dtsearch_catd, MS_cborodin, 776, msg_776),
819 PROGNAME"776", strerror(errno));
823 /* Write foxes to the free holes, if any, no byte swap */
824 for (int32 = 0; int32 < got_word.or_hwfree; int32++)
825 addrs_array [int32] = 0xFFFFFFFF;
826 num_writes = fwrite (addrs_array, sizeof(DB_ADDR),
827 (size_t)got_word.or_hwfree, dtbs_addr_fp);
828 if (num_writes != got_word.or_hwfree) {
829 printf (catgets (dtsearch_catd, MS_cborodin, 776, msg_776),
830 PROGNAME"786", strerror(errno));
833 } /* end if (nitems > got_word.or_hwfree), had to get bigger slot */
835 /* Else can reuse existing slot.
836 * Write the new addresses into free holes.
837 * The remaining free holes should already have foxes. (?)
840 fseek (dtbs_addr_fp, got_word.or_hwoffset, SEEK_SET);
841 num_writes = fwrite (addrs_array, sizeof(DB_ADDR),
842 (size_t)nitems, dtbs_addr_fp);
843 if (num_writes != nitems) {
844 printf (catgets (dtsearch_catd, MS_cborodin, 776, msg_776),
845 PROGNAME"798", strerror(errno));
848 /* Copy the old addresses immediately after the new ones */
849 num_writes = fwrite (word_addrs_ii, sizeof(DB_ADDR),
850 (size_t)num_addrs, dtbs_addr_fp);
851 if (num_writes != num_addrs) {
852 printf (catgets (dtsearch_catd, MS_cborodin, 776, msg_776),
853 PROGNAME"889", strerror(errno));
856 got_word.or_hwfree -= nitems;
858 } /* put_addrs_2_dtbs_addr_file() */
861 /****************************************/
863 /* write_2_dtbs_addr_file */
865 /****************************************/
866 /* Subroutine of fill_data1() from Pass 2.
867 * Updates OLD (preexisting) word's d99 file.
869 * The vista word rec has already been read into global 'got_word'.
870 * record_addr_word [num_addrs_for_word] is the array of dba's
871 * for docs from this batch that contain the current word (built by
872 * fill_data1 from the dba_list for the word's Pass 1 binary tree node,
873 * and still in 'host' byte swap order).
874 * This function freads all the old addresses for that word from
875 * the d99 file. It then deletes(!) d99 addrs that
876 * are in the word's Pass 1 tree node. It then calls
877 * put_addrs_2_dtbs_addr_file() to fwrite out the
878 * dba's in the tree, which are either brand new,
879 * or are 'updating' the deleted addrs.
880 * Then it writes the modified old addrs.
881 * Then rewrites vista word rec with new data.
883 * The bit vector dbas_bits_batch contains a 1 bit
884 * for every dba for every doc in the fzk file.
885 * got_word structure:
886 * .or_hwordkey - the word. (always in a 'huge' word buffer).
887 * .or_hwoffset - offset in a d99 inverted index file for
888 * a given word. the first address starts
890 * .or_hwaddrs - total number of addresses for a given word.
891 * .or_hwfree - number of free slots in a database
892 * addresses file for a given word.
894 void write_2_dtbs_addr_file (void)
896 DtSrINT32 num_addrs_ii;
898 DtSrINT32 i_start, k, cur_ind;
899 DtSrINT32 num_delete_addrs = 0;
900 char addrs_removed = FALSE;
901 register DtSrINT32 i;
902 register DtSrINT32 cur_byte;
903 register char bit_addrs;
904 register DB_ADDR temp1;
906 if (debugging & DEBUG_O)
907 printf (" old vis '%s' ofs=%ld adr=%ld fre=%ld\n",
908 displayable(got_word.or_hwordkey),
909 (long) got_word.or_hwoffset,
910 (long) got_word.or_hwaddrs,
911 (long) got_word.or_hwfree);
913 num_addrs_ii = got_word.or_hwaddrs;
914 if (num_addrs_ii > or_reccount) {
915 printf (catgets (dtsearch_catd, MS_cborodin, 713,
916 "\n%s Word '%s' occurs in %ld records,\n"
917 " but there are only %ld records in database!\n"
918 " (This may be a good candidate for the stoplist).\n"),
920 (long) got_word.or_hwordkey,
926 if (fseek (dtbs_addr_fp, (long) got_word.or_hwoffset, SEEK_SET) != 0)
928 printf (catgets (dtsearch_catd, MS_cborodin, 875,
929 "\n%s Could not fseek d99 file to offset %ld.\n"),
930 PROGNAME"875", got_word.or_hwoffset);
933 num_reads = fread (word_addrs_ii, sizeof(DB_ADDR),
934 (size_t)num_addrs_ii, dtbs_addr_fp);
935 if (num_reads != num_addrs_ii) {
936 printf (catgets (dtsearch_catd, MS_cborodin, 848,
937 "\n%s Could not fread %ld bytes (%ld dba's) of d99 file\n"
938 " at offset %ld. Number of dba's read (return code) = %ld.\n"),
939 PROGNAME"848", sizeof(DB_ADDR) * num_addrs_ii, (long)num_addrs_ii,
940 (long)got_word.or_hwoffset, (long)num_reads);
944 for (i = 0; i < num_addrs_ii; i++)
945 NTOHL (word_addrs_ii[i]);
946 /* Now both addr arrays are in 'host' byte swap order */
949 /* If there are only new docs,
950 * this switch will prevent the checking for updates.
952 if (check_existing_addrs) {
955 /* Loop on every preexisting dba for word as read from d99 */
956 for (i = 0; i < num_addrs_ii; i++) {
957 if (debugging & DEBUG_o)
958 printf (" old d99 %ld: x%lx(%ld:%ld)",
960 (long) word_addrs_ii[i],
961 (long) word_addrs_ii[i] >> 8,
962 (long) word_addrs_ii[i] & 0xffL);
964 /* Get 'record number' by shifting hi 3 bytes 1 byte (8 bits)
965 * to right over stat wt byte. D99 rec#'s start at 1,
966 * so subtract 1 to start at 0 for bit vector.
968 temp1 = (*(word_addrs_ii + i) >> 8) - 1; /* = rec#, base 0 */
969 cur_byte = temp1 >> 3; /* get matching byte# in bit vector */
970 if (cur_byte >= bit_vector_size) {
971 printf ( catgets(dtsearch_catd, MS_cborodin, 9,
972 "\n%s Corrupted d99 file for word '%s',\n"
973 " database address %ld @ file position %ld => bitvector[%ld],"
974 " but max bitvector allocation = %ld.\n") ,
975 PROGNAME"727", displayable(got_word.or_hwordkey),
976 (long)temp1, (long)i,
977 (long)cur_byte, (long)bit_vector_size);
981 bit_addrs |= 1 << (temp1 % 8); /* bit mask */
983 * If this dba, which is on the current word's old d99
984 * addrs list, is also a doc in the fzk file (dbas_bits_batch),
985 * delete it from the d99 list by writing subsequent dba's
986 * over it. Boy this recursive nested loop has gotta be slow.
987 * Faster algorithm? Add 'good' addrs to the end of
988 * record_addr_word[]. No nested overlay loop, only one write!
990 if (bit_addrs & (*(dbas_bits_batch + cur_byte))) {
991 addrs_removed = TRUE;
999 /* compress: move good addrs over
1000 * space of deleted ones */
1001 for (k = i_start; k < i; k++) {
1002 word_addrs_ii[cur_ind] = word_addrs_ii[k];
1008 } /* end if where dba is on both fzk list and curr d99 */
1009 } /* end loop on every d99 addr for this word */
1011 if (addrs_removed) { /* final overlay compression */
1013 /* compress: move good addrs over
1014 * space of deleted ones */
1015 for (k = i_start; k < i; k++) {
1016 word_addrs_ii[cur_ind] = word_addrs_ii[k];
1021 } /* end if (check_existing_addrs) */
1023 got_word.or_hwaddrs -= num_delete_addrs;
1024 got_word.or_hwfree += num_delete_addrs;
1026 /* The old dba array word_addrs_ii[] is now 'compressed',
1027 * it contains only addrs not in fzk file.
1028 * And the vista rec 'got_word' now matches it.
1029 * And record_addr_word[] still contains
1030 * the new/updated addrs from the fzk file.
1031 * Now Efim calls a func to write them both back out to d99 file.
1033 put_addrs_2_dtbs_addr_file (record_addr_word, num_addrs_for_word);
1034 write_wordstr (&got_word, 0); /* update vista WORD rec */
1037 } /* write_2_dtbs_addr_file() */
1040 /********************************/
1042 /* write_new_word_2_dtbs */
1044 /********************************/
1045 /* Subroutine of fill_data1() in Pass 2 for a NEW word.
1046 * Writes d99 data, and updates (empty) got_word vista record.
1047 * record_addr_word [num_addrs_for_word] is the array of addrs
1048 * for docs from this batch that contain the current word (built by
1049 * fill_data1 from the dba_list for the word's Pass 1 binary tree node).
1050 * It will be byte swapped from 'host' to 'network' order in this function.
1052 void write_new_word_2_dtbs (void)
1054 FREE_SPACE_STR *free_slot;
1055 DtSrINT32 num_writes;
1059 if (debugging & (DEBUG_n | DEBUG_N))
1060 printf (" new word '%s', adrs=%ld,",
1061 got_word.or_hwordkey, (long)num_addrs_for_word);
1063 free_slot = find_free_space (num_addrs_for_word, &fl_hdr);
1064 if (free_slot == NULL) {
1065 /* append addrs to end of d99 file */
1066 ret_fseek = fseek (dtbs_addr_fp, 0L, SEEK_END);
1067 got_word.or_hwoffset = ftell (dtbs_addr_fp);
1068 got_word.or_hwfree = 0;
1069 if (debugging & (DEBUG_n | DEBUG_N))
1070 printf ("APPEND ofs=%ld, fre=0\n", got_word.or_hwoffset);
1073 ret_fseek = fseek (dtbs_addr_fp,
1074 (long)free_slot->offset, SEEK_SET);
1075 got_word.or_hwoffset = free_slot->offset;
1076 got_word.or_hwfree = free_slot->hole_size -
1078 if (debugging & (DEBUG_n | DEBUG_N))
1079 printf (" REUSE slot ofs=%ld, fre=%ld\n",
1080 got_word.or_hwoffset, got_word.or_hwfree);
1083 /***** Write new database addresses to d99 file *********/
1084 if (debugging & DEBUG_n) {
1085 for (int32 = 0; int32 < num_addrs_for_word; int32++) {
1086 printf (" dba #%ld: x%lx(%ld:%ld)\n",
1088 (long)record_addr_word[int32],
1089 (long)record_addr_word[int32] >> 8,
1090 (long)record_addr_word[int32] & 0xffL);
1094 /* Put addr array in 'network' byte order */
1095 for (int32 = 0; int32 < num_addrs_for_word; int32++)
1096 HTONL (record_addr_word[int32]);
1098 num_writes = fwrite (record_addr_word, sizeof(DB_ADDR),
1099 (size_t)num_addrs_for_word, dtbs_addr_fp);
1100 if (num_writes != num_addrs_for_word)
1103 got_word.or_hwaddrs = num_addrs_for_word;
1105 if (got_word.or_hwfree != 0) {
1106 /* Fill unused free holes with foxes for debugging.
1107 * Note that byte swap is unnecessary for foxes.
1108 * Note that record_addr_word is now available for this action.
1110 for (int32 = 0; int32 < got_word.or_hwfree; int32++)
1111 *(record_addr_word + int32) = 0xFFFFFFFF;
1112 num_writes = fwrite (record_addr_word, sizeof(DB_ADDR),
1113 (size_t)got_word.or_hwfree, dtbs_addr_fp);
1114 if (num_writes != got_word.or_hwfree) {
1115 printf (catgets (dtsearch_catd, MS_cborodin, 776, msg_776),
1116 PROGNAME"960", strerror(errno));
1121 /* Save changed word_info structure back to the vista database! */
1122 write_wordstr (&got_word, 0);
1124 } /* write_new_word_2_dtbs() */
1127 /************************/
1131 /************************/
1132 /* Called from write_to_file() in Pass 2.
1133 * Write_to_file() is 'visit node' function of tree traversal.
1134 * It has converted dbalist in each word node in tree to
1135 * array of dbas (record_addr_word [num_addrs_for_word])
1136 * with correct statistical weighting, still in 'host' byte swap order.
1137 * This function seeks word key in database. If word is new,
1138 * it calls functions to write new vista rec and d99 data.
1139 * If word is old it calls functions to read word rec and update d99.
1141 void fill_data1 (char *node_word)
1144 strcpy (miker, node_word);
1148 printf (catgets (dtsearch_catd, MS_cborodin, 164,
1149 "\n%s Abort due to signal %d. Database %s\n"
1150 " probably corrupted. Restore backup database.\n"),
1151 PROGNAME"164", shutdown_now, dicname);
1155 /* print occasional progress dots and msgs */
1156 if (!(count_word_ii % words_per_dot)) {
1159 if (!(dotcount % 10))
1161 if (dotcount >= 50) {
1163 seconds_left = (unsigned long)
1164 (((float) num_of_diff_words /
1165 (float) count_word_ii - 1.) *
1166 (float) (time (NULL) - timestart));
1167 printf (catgets (dtsearch_catd, MS_cborodin, 849,
1168 "\n%s: Word #%ld, %.0f%% done. Est %lum %02lus "
1169 "to completion.\n"),
1170 aa_argv0, count_word_ii,
1171 (float) count_word_ii / (float) num_of_diff_words * 100.0,
1172 /***(count_word_ii * 100L) / num_of_diff_words,***/
1173 seconds_left / 60L, seconds_left % 60L);
1177 } /* endif for progress dots and msgs */
1179 strncpy (got_word.or_hwordkey, node_word, DtSrMAXWIDTH_HWORD);
1180 got_word.or_hwordkey[DtSrMAXWIDTH_HWORD - 1] = 0;
1181 find_keyword (miker, 0); /* vista KEYFIND for word rec */
1182 if (db_status == S_NOTFOUND) { /* this is a NEW word */
1183 got_word.or_hwoffset = 0;
1184 got_word.or_hwfree = 0;
1185 got_word.or_hwaddrs = 0;
1186 fillnew_wordrec (&got_word, 0); /* write (empty) vista word rec */
1187 if (db_status != S_OKAY)
1188 vista_abort (PROGNAME"981");
1189 write_new_word_2_dtbs(); /* write NEW word's d99 entries
1190 * and update vista word rec */
1194 /* update previously existing word */
1195 read_wordstr (&got_word, 0); /* read OLD word rec into got_word */
1196 if (db_status == S_OKAY)
1197 write_2_dtbs_addr_file(); /* update OLD word's d99 entries
1198 * and update vista word rec */
1200 } /* fill_data1() */
1203 /************************************************/
1205 /* load_into_bintree */
1207 /************************************************/
1209 * Loads parsed word token or stem token into
1210 * inverted index binary tree along with passed dba.
1211 * Token is allowed to be empty, ie first byte is \0.
1212 * Derived from Efim's original 'teskey_parse()'
1213 * and bin_tree() functions.
1214 * Variables static for speeeeeeed.
1216 static void load_into_bintree (
1221 static DtSrINT16 or_maxwordsz;
1224 static TREENODE **this_link;
1225 static TREENODE *newnode;
1226 static DBALIST *newdba;
1227 static char *tokbuf = NULL;
1229 if (*parser_token == 0) {
1230 if (debugging & DEBUG_I)
1231 printf (" bintr=<empty> dba=%ld\n", (long)dba);
1235 /* Copy token to a buffer.
1236 * Stems have a special prefix character
1237 * to distinguish them from words.
1238 * Also increment total dba word count.
1240 if (tokbuf == NULL) {
1241 or_maxwordsz = dblk.dbrec.or_maxwordsz;
1242 tokbuf = austext_malloc ((size_t) or_maxwordsz + 4,
1243 PROGNAME"1152", NULL);
1245 if (token_is_stem) {
1246 tokbuf[0] = STEM_CH;
1247 strncpy (tokbuf + 1, parser_token, (size_t)or_maxwordsz);
1248 dbas_word_count[dba]++;
1251 strncpy (tokbuf, parser_token, (size_t)or_maxwordsz);
1252 tokbuf [or_maxwordsz] = 0;
1253 if (debugging & DEBUG_I)
1254 printf (" bintr='%s' dba=%ld ", displayable(tokbuf), (long)dba);
1256 /* TREE TRAVERSAL. Search binary tree to find either
1257 * insertion point or identical preexisting token.
1259 for (this_link = &root_node; *this_link != NULL; ) {
1260 i = strcmp (tokbuf, (*this_link)->word);
1262 /* If identical word/stem token already exists... */
1264 /* If token appears more than once in current
1265 * document (dba already exists at top of dba list),
1266 * just increment the word count in the list.
1268 if ((*this_link)->dba_list->dba == dba)
1269 (*this_link)->dba_list->w_c++;
1271 /* If this is first appearance of token for this doc
1272 * (dba is not at start of token's dba list),
1273 * insert dba at start of token's dba list.
1276 if ((newdba = malloc (sizeof(DBALIST))) == NULL) {
1277 printf (catgets (dtsearch_catd, MS_cborodin, 374,
1278 msg_374), PROGNAME"1150");
1283 newdba->next_dba = (*this_link)->dba_list;
1284 (*this_link)->dba_list = newdba;
1286 if (debugging & DEBUG_I)
1287 printf (" Old %ld=%ld\n",
1288 (long)((*this_link)->dba_list->dba),
1289 (long)((*this_link)->dba_list->w_c));
1290 return; /* done with token */
1292 } /* endif where token was found in binary tree */
1294 /* Increment link ptr by descending to correct subtree */
1296 this_link = &(*this_link)->llink;
1297 if (debugging & DEBUG_I)
1301 this_link = &(*this_link)->rlink;
1302 if (debugging & DEBUG_I)
1305 } /* end tree traversal */
1307 /* Tree traversal never found a preexisting token node.
1308 * Create a new node and insert it at the point
1309 * indicated by link ptr.
1311 newnode = austext_malloc (sizeof(TREENODE) + strlen(tokbuf) + 4,
1312 PROGNAME"1234", NULL);
1313 newnode->llink = NULL;
1314 newnode->rlink = NULL;
1315 newnode->word = (char *) (newnode + 1); /* use mem at end of node */
1316 strcpy (newnode->word, tokbuf);
1318 newdba = austext_malloc (sizeof(DBALIST), PROGNAME"1235", NULL);
1319 newnode->dba_list = newdba;
1322 newdba->next_dba = NULL;
1324 *this_link = newnode;
1325 num_of_diff_words++;
1327 if (debugging & DEBUG_I)
1328 printf (" New %ld=%ld\n",
1329 (long)((*this_link)->dba_list->dba),
1330 (long)((*this_link)->dba_list->w_c));
1332 } /* load_into_bintree() */
1335 /**********************************************/
1339 /**********************************************/
1340 main (int argc, char **argv)
1343 long word_offset; /* <-- PARG.offsetp */
1344 long bytes_in; /* ftell() */
1345 DtSrINT32 dba_offset;
1349 char db_key [DtSrMAX_DB_KEYSIZE + 2];
1351 register DtSrINT32 cur_byte;
1353 DB_ADDR dba, temp_dba;
1356 char *parsebufp, *stembufp;
1358 /******************* INITIALIZE ******************/
1359 setlocale (LC_ALL, "");
1360 dtsearch_catd = catopen (FNAME_DTSRCAT, 0);
1362 aa_argv0 = strdup (argv[0]);
1364 tmptr = localtime (&elapsed);
1365 strftime (buf, sizeof(buf),
1366 catgets (dtsearch_catd, MS_misc, 22, "%A, %b %d %Y, %I:%M %p"),
1368 printf (catgets (dtsearch_catd, MS_cborodin, 1, "%s. Run %s.\n"),
1370 austext_exit_last = print_exit_code;
1371 batch_size = BATCH_SIZE;
1372 init_user_interrupt ();
1373 default_hashsize = duprec_hashsize;
1375 memset (&dblk, 0, sizeof(DBLK));
1377 memset (&parg, 0, sizeof(PARG));
1379 parg.etxdelim = ETXDELIM; /* default, can be changed */
1380 parg.offsetp = &word_offset;
1381 parg.flags |= PA_INDEXING; /* do compounding, if parser can */
1383 /* Read user specified command line arguments */
1384 user_args_processor (argc, argv);
1386 /* Finish init now that we know final values */
1387 inbuf = austext_malloc (inbufsz + 16, PROGNAME"1349", NULL);
1388 temp = austext_malloc (inbufsz + 16, PROGNAME"1285", NULL);
1389 sprintbuffer = austext_malloc (inbufsz + _POSIX_PATH_MAX + 16,
1390 PROGNAME"1286", NULL);
1391 record_addr_word = austext_malloc ((sizeof(DB_ADDR) * batch_size) + 16,
1392 PROGNAME "1133", NULL);
1394 /* Save dicname and path in dblk. Save full name of d99 file. */
1395 strcpy (dblk.name, dicname);
1396 dblk.path = dicpath;
1397 strcpy (dtbs_addr_file, dicpath);
1398 strcat (dtbs_addr_file, dicname);
1399 strcat (dtbs_addr_file, EXT_DTBS);
1401 /* Open the database */
1402 if (!austext_dopen (dicname, dicpath, NULL, cache_size, &dbrec)) {
1403 fprintf (aa_stderr, "%s\n", DtSearchGetMessages());
1406 memcpy (&dblk.dbrec, &dbrec, sizeof(DBREC));
1408 /* Load database's parser, stemmer, and linguistic files into dblk. */
1409 if (!load_language (&dblk, NULL)) {
1410 puts (DtSearchGetMessages());
1411 printf (catgets (dtsearch_catd, MS_cborodin, 1097,
1412 "%s Aborting due to errors in loading language files.\n"),
1417 RECFRST (PROGNAME "1067", OR_OBJREC, 0);
1418 CRGET (PROGNAME "1069", &dba, 0); /* byte swap already done in vista */
1420 or_reccount = dbrec.or_reccount; /* DtSrINT32 */
1421 or_recslots = dbrec.or_recslots; /* promoted to DtSrINT32 */
1422 or_maxdba = dbrec.or_maxdba; /* DtSrINT32 lim of dbas_word_count */
1423 bit_vector_size = ((or_maxdba / or_recslots + 1) >> 3) + 1; /* DtSrINT32 */
1424 dba_offset = or_recslots - (dba & 0x00FFFFFF); /* DtSrINT32 */
1427 printf (PROGNAME"1286 "
1428 "realnumrec=%ld recslots=%ld bitvecsz=%ld"
1429 " dbaoffset=%d maxdba=%ld\n",
1430 (long)or_reccount, (long)or_recslots, (long)bit_vector_size,
1431 (int)dba_offset, (long)or_maxdba);
1433 /* Allocate memory space for the arrays.
1434 * dbas_bits_batch = 'bit vector', one bit for every possible rec#.
1435 * the 1 bits = only the dba's that are in this fzk batch.
1436 * word_addrs_ii = fread buffer for d99 file.
1437 * dbas_word_count = summing bkts for word count statistics.
1439 dbas_bits_batch = (char *) austext_malloc ((size_t)bit_vector_size + 48,
1440 PROGNAME "1150", NULL);
1441 word_addrs_ii = (DB_ADDR *) austext_malloc (
1442 sizeof (DB_ADDR) * (or_reccount + 1) + 48,
1443 PROGNAME "1152", NULL);
1444 mallocsz = sizeof(DtSrINT32) * (or_maxdba + 1) + 48;
1445 dbas_word_count = (DtSrINT32 *) austext_malloc (mallocsz,
1446 PROGNAME "1154", NULL);
1447 memset (dbas_bits_batch, 0, (size_t)bit_vector_size + 48);
1448 memset (dbas_word_count, 0, mallocsz);
1452 /* Open the d99 file that contains database addresses.
1453 * If the file doesn't exist, it means the database
1454 * for keyword search is empty - open a new file.
1456 if ((dtbs_addr_fp = fopen (dtbs_addr_file, "r+b")) == NULL) {
1457 dtbs_addr_fp = fopen (dtbs_addr_file, "w+b");
1458 check_existing_addrs = FALSE;
1459 new_dtbs_file = TRUE;
1460 if (dtbs_addr_fp == NULL) {
1461 /* msg 1068 used multiple places */
1462 printf (catgets (dtsearch_catd, MS_cborodin, 1068,
1463 "%s Can't open new inverted index file '%s': %s\n"),
1464 PROGNAME"1068", dtbs_addr_file, strerror(errno));
1467 /* write New Header Information to a file */
1468 init_header (dtbs_addr_fp, &fl_hdr);
1471 /* read Header Information from d99 file */
1472 if (!fread_d99_header (&fl_hdr, dtbs_addr_fp)) {
1473 /* msg 1068 used multiple places */
1474 printf (catgets (dtsearch_catd, MS_cborodin, 1068,
1475 "%s Can't read header data for '%s': %s\n"),
1476 PROGNAME"1422", dtbs_addr_file, strerror(errno));
1481 /* open input .fzk file */
1482 src = getcwd (sprintbuffer, _POSIX_PATH_MAX);
1483 if (!src && debugging)
1484 printf (PROGNAME"1336 Can't getcwd: %s.\n", strerror(errno));
1486 src = getenv ("PWD");
1487 printf (catgets (dtsearch_catd, MS_misc, 24,
1488 "%s: current working directory = '%s', .fzk file = '%s'\n"),
1490 (src) ? src : catgets (dtsearch_catd, MS_misc, 6, "<unknown>"),
1492 if ((instream = fopen (fname_input, "rt")) == NULL) {
1494 printf (catgets (dtsearch_catd, MS_cborodin, 1083,
1495 "%s Can't read input file '%s': %s\n"),
1496 PROGNAME"1083", fname_input, strerror(errno));
1499 if (fstat (fileno (instream), &fstat_input) == -1)
1500 goto BAD_INPUT_FILE;
1501 parg.ftext = instream; /* for readchar_ftext(), discard_to_ETX() */
1503 time (&totalstart); /* for total elapsed time */
1504 timestart = totalstart; /* for Pass 1 elapsed time */
1506 /*------------ PASS 1: ------------
1507 * Main Read Loop. For each text record in input file,
1508 * parse and stem words, store them into binary tree
1509 * inverted index in memory.
1510 * The first few lines are database administrative values.
1511 * They are presumed ascii and read with fgets() as
1512 * 'lines' terminated with \n. The text of the document
1513 * itself is presumed to be in the appropriate database
1514 * 'language', so it is *not* presumed to be lines
1515 * terminated with \n. The document text is read by
1516 * the language's parser() a 'word' at a time, which
1517 * ultimately means a byte at a time.
1519 printf (catgets (dtsearch_catd, MS_cborodin, 1108,
1520 "%s: Beginning Pass 1, reading records from '%s'.\n"
1521 " Each dot = %d records.\n"),
1522 aa_argv0, fname_input, recs_per_dot);
1525 while (!feof(instream)) {
1527 /* 1. Read and discard the FZKEY line.
1528 * 2. Read and discard the ABSTRACT line.
1529 * 3. Read the UNIQUE KEY for the record.
1530 * Do some record initialization steps here.
1531 * 4. Read and discard the DATE line.
1532 * 5. Let the parser read and parse rest of record, ie doc text...
1535 /*----- READ LINE #1, fzkey -----*/
1536 if (fgets (inbuf, inbufsz, instream) == NULL)
1538 inbuf [inbufsz] = 0; /* just to be sure */
1541 printf (catgets (dtsearch_catd, MS_cborodin, 164,
1542 "\n%s: %s Abort due to signal %d. Database %s\n"
1543 " possibly corrupted. Restore backup database.\n"),
1544 aa_argv0, PROGNAME"1299", shutdown_now, dicname);
1548 /* Silently skip null records just like dtsrload */
1549 if (strcmp (inbuf, parg.etxdelim) == 0)
1554 /*----- READ LINE #2, abstract -----*/
1555 if (fgets (inbuf, inbufsz, instream) == NULL) {
1557 printf (catgets (dtsearch_catd, MS_cborodin, 1129,
1558 "%s: %s Invalid .fzk file format.\n"),
1559 fname_input, PROGNAME"1129");
1562 inbuf[inbufsz] = 0; /* just to be sure */
1564 /*--- READ LINE #3, unique database key ---*/
1565 if (fgets (inbuf, inbufsz, instream) == NULL)
1566 goto INVALID_FZK_FORMAT;
1567 inbuf[inbufsz] = 0; /* just to be sure */
1569 if ((cptr = strtok (inbuf, " \t\n")) == NULL)
1570 goto INVALID_FZK_FORMAT;
1572 /* If necessary, discard long keys exactly like cravel */
1573 if (strlen (cptr) >= DtSrMAX_DB_KEYSIZE) {
1574 printf (catgets (dtsearch_catd, MS_cborodin, 659,
1575 "\n%s: %s Discarding record, key too long:\n '%s'.\n"),
1576 aa_argv0, PROGNAME"659", cptr);
1577 discard_to_ETX (&parg);
1580 strcpy (db_key, cptr);
1582 /* Skip duplicate record ids in same order as dtsrload */
1583 i = is_duprec (db_key);
1584 if (i == 2) { /* out of memory */
1585 printf (catgets (dtsearch_catd, MS_cborodin, 374, msg_374),
1589 else if (i == 1) { /* duplicate record id */
1593 printf (catgets (dtsearch_catd, MS_cborodin, 1402,
1594 "%s: Discarded duplicate rec #%lu '%s'.\n"),
1595 aa_argv0, record_count, db_key);
1596 discard_to_ETX (&parg);
1600 /****** FFFFFFFFFFFFFFFFFFFFF **********/
1601 /* Convert database address (slot #) to 'record number',
1602 * what dba would have been if all records took up
1603 * only one slot and there were no dbrec at top of file.
1604 * Record numbers on d99, like dba's, start at #1,
1605 * but rec numbers here (in bit vector) start at #0.
1607 KEYFIND (PROGNAME "222", OR_OBJKEY, (char *) db_key, 0);
1608 if (db_status != S_OKAY) {
1609 normal_retncode = 1; /* = 'warning' */
1612 printf (catgets (dtsearch_catd, MS_cborodin, 1168,
1613 "%s: %s Discarded '%s', key not in database.\n"),
1614 aa_argv0, PROGNAME"1168", displayable(db_key));
1615 discard_to_ETX (&parg);
1619 CRGET (PROGNAME "224", &temp_dba, 0); /* vista already byte swapped */
1620 temp_dba &= 0x00FFFFFF; /* = slot# */
1621 dba = (temp_dba + dba_offset) / or_recslots; /* = rec#, base 1 */
1623 * Don't change this 'dba'!--eventually it goes
1624 * into d99 in this exact format! It will also
1625 * be used as an index into dbas_word_count[] in
1626 * load_into_bintree() so do a sanity check
1627 * to make sure that it hasn't exceeded the size
1628 * of that array. (The count increments have been
1629 * reported as as 'uninitialized memory reads'
1630 * by a debugger). This might happen for example
1631 * if user failed to run dtsrload before dtsrindex?
1633 if (dba < 1 || dba > or_maxdba) {
1634 printf ( catgets(dtsearch_catd, MS_cborodin, 21,
1635 "\n%s '%s' record overflows word counter array.\n"
1636 "Record number %ld > maxdba %ld, dba=%ld, "
1637 "recslots=%ld, offs=%d.\n") ,
1638 PROGNAME"1526", displayable(db_key),
1639 (long)dba, (long)or_maxdba, (long)temp_dba,
1640 (long)or_recslots, (int)dba_offset);
1643 temp_dba = dba - 1; /* = rec# starting at 0 */
1644 cur_byte = temp_dba >> 3; /* bits to bytes: div by 8 */
1645 if (cur_byte >= bit_vector_size) {
1646 printf ( catgets(dtsearch_catd, MS_cborodin, 22,
1647 "\n%s '%s' record in database (dba=%ld)\n"
1648 " overflows bitvector allocation (%ld >= %ld).\n") ,
1649 PROGNAME"1475", displayable(db_key), (long)dba,
1650 (long)cur_byte, (long)bit_vector_size);
1653 dbas_bits_batch[cur_byte] |= 1 << (temp_dba % 8);
1655 /* Print occasional progress dots and msgs */
1656 if (!(record_count % recs_per_dot)) {
1659 if (!(dotcount % 10))
1661 if (dotcount >= 50) {
1663 bytes_in = ftell (instream);
1664 seconds_left = (unsigned long)
1665 (((float) fstat_input.st_size /
1666 (float) bytes_in - 1.) *
1667 (float) (time (NULL) - timestart));
1668 printf (catgets (dtsearch_catd, MS_cborodin, 1190,
1669 "\n%s: Rec #%lu, %.0f%% done. "
1670 "Est %lum %02lus to end Pass 1.\n"),
1673 (float) bytes_in / (float) fstat_input.st_size * 100.0,
1674 seconds_left / 60UL,
1675 seconds_left % 60UL);
1680 /*----- READ LINE #4, date -----*/
1681 if (fgets (inbuf, inbufsz, instream) == NULL)
1682 goto INVALID_FZK_FORMAT;
1683 inbuf[inbufsz] = 0; /* just to be sure */
1685 /* PARSE LOOP FOR CURRENT TEXT BLOCK.
1686 * We must be in the middle of a record ('lines' #5 and beyond).
1687 * From here to ETX, which is either the record delimiter string
1688 * or the end of file, read the file a 'word' at a time
1689 * using the parse() function for the language specified
1691 * Load_into_bintree() stores each token into
1692 * inverted index binary tree.
1693 * Note: dba here MUST still be rec#, base 1.
1694 * It's stored as is by load_into_bintree(),
1695 * and will be moved as is into d99 file in Pass 2.
1697 if (debugging & DEBUG_P)
1698 printf ("\nRecord #%lu '%s'\n"
1699 "Offset Word---- Stem----\n",
1700 record_count, db_key);
1701 for ( cptr = dblk.parser (&parg);
1703 cptr = dblk.parser (NULL)) {
1705 if (debugging & DEBUG_P) {
1706 printf ("%6ld %s %n", word_offset, cptr, &i);
1707 if (!(debugging & DEBUG_I))
1711 load_into_bintree (cptr, FALSE, dba);
1712 cptr = dblk.stemmer (cptr, &dblk);
1713 if (debugging & DEBUG_P) {
1714 printf ("%s\n", cptr);
1717 load_into_bintree (cptr, TRUE, dba);
1720 } /* end of PASS 1 Main read loop */
1722 elapsed = time(NULL) - timestart;
1727 if (duplicate_recids > 0L) {
1728 normal_retncode = 1; /* 'warning' */
1729 sprintf (buf, catgets (dtsearch_catd, MS_cborodin, 40,
1730 "Ignored %ld duplicate records"),
1734 strcpy (buf, catgets (dtsearch_catd, MS_cborodin, 41,
1735 "No duplicate records found"));
1736 printf (catgets (dtsearch_catd, MS_cborodin, 1225,
1737 "%s: Pass 1 completed in %lum %lus, read %lu records.\n"
1738 " %s, parsed %lu words.\n"),
1739 aa_argv0, elapsed / 60L, elapsed % 60L, record_count,
1740 buf, num_of_diff_words);
1741 if (record_count > batch_size) {
1742 printf (catgets (dtsearch_catd, MS_cborodin, 33,
1743 "\n%s Number of incoming records exceeded %d.\n"
1744 " This will usually result in 'Out of Paging Space' "
1746 " and corruption of database. Either split the incoming file to\n"
1747 " reduce record count or use the -b option, and rerun.\n"),
1748 PROGNAME"33", (int)batch_size);
1752 /*----------------- PASS 2: -----------------
1753 * Traverse completed binary tree and write it to d99 file.
1755 printf (catgets (dtsearch_catd, MS_cborodin, 1233,
1756 "%s: Beginning Pass 2: batch index traversal and database update.\n"
1757 " Each dot = %d words.\n"),
1758 aa_argv0, words_per_dot);
1761 traverse_tree (); /* actual Pass 2 */
1767 /* Write header information to the d99 file */
1768 if (!fwrite_d99_header (&fl_hdr, dtbs_addr_fp)) {
1769 printf (catgets (dtsearch_catd, MS_cborodin, 776, msg_776),
1770 PROGNAME"1723", strerror(errno));
1774 fclose (dtbs_addr_fp);
1776 elapsed = time (NULL) - timestart;
1777 printf (catgets (dtsearch_catd, MS_cborodin, 1246,
1778 "%s: Pass 2 completed in %lum %lus, updated %lu words.\n"),
1779 aa_argv0, elapsed / 60L, elapsed % 60L, count_word_ii);
1780 if (normal_retncode == 1)
1781 printf (catgets (dtsearch_catd, MS_cborodin, 2,
1782 "%s: Warnings were detected.\n"), aa_argv0);
1783 DtSearchExit (normal_retncode);
1787 /*************************** DTSRINDEX.C ****************************/