Remove no longer needed catman periodic via 'make upgrade'.
[dragonfly.git] / contrib / groff / src / utils / indxbib / indxbib.cpp
1 // -*- C++ -*-
2 /* Copyright (C) 1989-1992, 2000, 2001, 2002, 2003, 2004, 2009
3    Free Software Foundation, Inc.
4      Written by James Clark (jjc@jclark.com)
5
6 This file is part of groff.
7
8 groff is free software; you can redistribute it and/or modify it under
9 the terms of the GNU General Public License as published by the Free
10 Software Foundation, either version 3 of the License, or
11 (at your option) any later version.
12
13 groff is distributed in the hope that it will be useful, but WITHOUT ANY
14 WARRANTY; without even the implied warranty of MERCHANTABILITY or
15 FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
16 for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program. If not, see <http://www.gnu.org/licenses/>. */
20
21 #include "lib.h"
22
23 #include <stdlib.h>
24 #include <assert.h>
25 #include <errno.h>
26
27 #include "posix.h"
28 #include "errarg.h"
29 #include "error.h"
30 #include "stringclass.h"
31 #include "cset.h"
32 #include "cmap.h"
33
34 #include "defs.h"
35 #include "index.h"
36
37 #include "nonposix.h"
38
39 extern "C" const char *Version_string;
40
41 #define DEFAULT_HASH_TABLE_SIZE 997
42 #define TEMP_INDEX_TEMPLATE "indxbibXXXXXX"
43
44 // (2^n - MALLOC_OVERHEAD) should be a good argument for malloc().
45
46 #define MALLOC_OVERHEAD 16
47
48 #ifdef BLOCK_SIZE
49 #undef BLOCK_SIZE
50 #endif
51
52 const int BLOCK_SIZE = ((1024 - MALLOC_OVERHEAD - sizeof(struct block *)
53                          - sizeof(int)) / sizeof(int));
54 struct block {
55   block *next;
56   int used;
57   int v[BLOCK_SIZE];
58   
59   block(block *p = 0) : next(p), used(0) { }
60 };
61
62 struct block;
63
64 union table_entry {
65   block *ptr;
66   int count;
67 };
68
69 struct word_list {
70   word_list *next;
71   char *str;
72   int len;
73   word_list(const char *, int, word_list *);
74 };
75
76 table_entry *hash_table;
77 int hash_table_size = DEFAULT_HASH_TABLE_SIZE;
78 // We make this the same size as hash_table so we only have to do one
79 // mod per key.
80 static word_list **common_words_table = 0;
81 char *key_buffer;
82
83 FILE *indxfp;
84 int ntags = 0;
85 string filenames;
86 char *temp_index_file = 0;
87
88 const char *ignore_fields = "XYZ";
89 const char *common_words_file = COMMON_WORDS_FILE;
90 int n_ignore_words = 100;
91 int truncate_len = 6;
92 int shortest_len = 3;
93 int max_keys_per_item = 100;
94
95 static void usage(FILE *stream);
96 static void write_hash_table();
97 static void init_hash_table();
98 static void read_common_words_file();
99 static int store_key(char *s, int len);
100 static void possibly_store_key(char *s, int len);
101 static int do_whole_file(const char *filename);
102 static int do_file(const char *filename);
103 static void store_reference(int filename_index, int pos, int len);
104 static void check_integer_arg(char opt, const char *arg, int min, int *res);
105 static void store_filename(const char *);
106 static void fwrite_or_die(const void *ptr, int size, int nitems, FILE *fp);
107 static char *get_cwd();
108
109 extern "C" {
110   void cleanup();
111   void catch_fatal_signals();
112   void ignore_fatal_signals();
113 }
114
115 int main(int argc, char **argv)
116 {
117   program_name = argv[0];
118   static char stderr_buf[BUFSIZ];
119   setbuf(stderr, stderr_buf);
120   
121   const char *base_name = 0;
122   typedef int (*parser_t)(const char *);
123   parser_t parser = do_file;
124   const char *directory = 0;
125   const char *foption = 0;
126   int opt;
127   static const struct option long_options[] = {
128     { "help", no_argument, 0, CHAR_MAX + 1 },
129     { "version", no_argument, 0, 'v' },
130     { NULL, 0, 0, 0 }
131   };
132   while ((opt = getopt_long(argc, argv, "c:o:h:i:k:l:t:n:c:d:f:vw",
133                             long_options, NULL))
134          != EOF)
135     switch (opt) {
136     case 'c':
137       common_words_file = optarg;
138       break;
139     case 'd':
140       directory = optarg;
141       break;
142     case 'f':
143       foption = optarg;
144       break;
145     case 'h':
146       check_integer_arg('h', optarg, 1, &hash_table_size);
147       if (!is_prime(hash_table_size)) {
148         while (!is_prime(++hash_table_size))
149           ;
150         warning("%1 not prime: using %2 instead", optarg, hash_table_size);
151       }
152       break;
153     case 'i':
154       ignore_fields = optarg;
155       break;
156     case 'k':
157       check_integer_arg('k', optarg, 1, &max_keys_per_item);
158       break;
159     case 'l':
160       check_integer_arg('l', optarg, 0, &shortest_len);
161       break;
162     case 'n':
163       check_integer_arg('n', optarg, 0, &n_ignore_words);
164       break;
165     case 'o':
166       base_name = optarg;
167       break;
168     case 't':
169       check_integer_arg('t', optarg, 1, &truncate_len);
170       break;
171     case 'w':
172       parser = do_whole_file;
173       break;
174     case 'v':
175       printf("GNU indxbib (groff) version %s\n", Version_string);
176       exit(0);
177       break;
178     case CHAR_MAX + 1: // --help
179       usage(stdout);
180       exit(0);
181       break;
182     case '?':
183       usage(stderr);
184       exit(1);
185       break;
186     default:
187       assert(0);
188       break;
189     }
190   if (optind >= argc && foption == 0)
191     fatal("no files and no -f option");
192   if (!directory) {
193     char *path = get_cwd();
194     store_filename(path);
195     a_delete path;
196   }
197   else
198     store_filename(directory);
199   init_hash_table();
200   store_filename(common_words_file);
201   store_filename(ignore_fields);
202   key_buffer = new char[truncate_len];
203   read_common_words_file();
204   if (!base_name)
205     base_name = optind < argc ? argv[optind] : DEFAULT_INDEX_NAME;
206   const char *p = strrchr(base_name, DIR_SEPS[0]), *p1;
207   const char *sep = &DIR_SEPS[1];
208   while (*sep) {
209     p1 = strrchr(base_name, *sep);
210     if (p1 && (!p || p1 > p))
211       p = p1;
212     sep++;
213   }
214   size_t name_max;
215   if (p) {
216     char *dir = strsave(base_name);
217     dir[p - base_name] = '\0';
218     name_max = file_name_max(dir);
219     a_delete dir;
220   }
221   else
222     name_max = file_name_max(".");
223   const char *filename = p ? p + 1 : base_name;
224   if (strlen(filename) + sizeof(INDEX_SUFFIX) - 1 > name_max)
225     fatal("`%1.%2' is too long for a filename", filename, INDEX_SUFFIX);
226   if (p) {
227     p++;
228     temp_index_file = new char[p - base_name + sizeof(TEMP_INDEX_TEMPLATE)];
229     memcpy(temp_index_file, base_name, p - base_name);
230     strcpy(temp_index_file + (p - base_name), TEMP_INDEX_TEMPLATE);
231   }
232   else {
233     temp_index_file = strsave(TEMP_INDEX_TEMPLATE);
234   }
235   catch_fatal_signals();
236   int fd = mkstemp(temp_index_file);
237   if (fd < 0)
238     fatal("can't create temporary index file: %1", strerror(errno));
239   indxfp = fdopen(fd, FOPEN_WB);
240   if (indxfp == 0)
241     fatal("fdopen failed");
242   if (fseek(indxfp, sizeof(index_header), 0) < 0)
243     fatal("can't seek past index header: %1", strerror(errno));
244   int failed = 0;
245   if (foption) {
246     FILE *fp = stdin;
247     if (strcmp(foption, "-") != 0) {
248       errno = 0;
249       fp = fopen(foption, "r");
250       if (!fp)
251         fatal("can't open `%1': %2", foption, strerror(errno));
252     }
253     string path;
254     int lineno = 1;
255     for (;;) {
256       int c;
257       for (c = getc(fp); c != '\n' && c != EOF; c = getc(fp)) {
258         if (c == '\0')
259           error_with_file_and_line(foption, lineno,
260                                    "nul character in pathname ignored");
261         else
262           path += c;
263       }
264       if (path.length() > 0) {
265         path += '\0';
266         if (!(*parser)(path.contents()))
267           failed = 1;
268         path.clear();
269       }
270       if (c == EOF)
271         break;
272       lineno++;
273     }
274     if (fp != stdin)
275       fclose(fp);
276   }
277   for (int i = optind; i < argc; i++)
278     if (!(*parser)(argv[i]))
279       failed = 1;
280   write_hash_table();
281   if (fclose(indxfp) < 0)
282     fatal("error closing temporary index file: %1", strerror(errno));
283   char *index_file = new char[strlen(base_name) + sizeof(INDEX_SUFFIX)];    
284   strcpy(index_file, base_name);
285   strcat(index_file, INDEX_SUFFIX);
286 #ifdef HAVE_RENAME
287 #ifdef __EMX__
288   if (access(index_file, R_OK) == 0)
289     unlink(index_file);
290 #endif /* __EMX__ */
291   if (rename(temp_index_file, index_file) < 0) {
292 #ifdef __MSDOS__
293     // RENAME could fail on plain MSDOS filesystems because
294     // INDEX_FILE is an invalid filename, e.g. it has multiple dots.
295     char *fname = p ? index_file + (p - base_name) : 0;
296     char *dot = 0;
297
298     // Replace the dot with an underscore and try again.
299     if (fname
300         && (dot = strchr(fname, '.')) != 0
301         && strcmp(dot, INDEX_SUFFIX) != 0)
302       *dot = '_';
303     if (rename(temp_index_file, index_file) < 0)
304 #endif
305     fatal("can't rename temporary index file: %1", strerror(errno));
306   }
307 #else /* not HAVE_RENAME */
308   ignore_fatal_signals();
309   if (unlink(index_file) < 0) {
310     if (errno != ENOENT)
311       fatal("can't unlink `%1': %2", index_file, strerror(errno));
312   }
313   if (link(temp_index_file, index_file) < 0)
314     fatal("can't link temporary index file: %1", strerror(errno));
315   if (unlink(temp_index_file) < 0)
316     fatal("can't unlink temporary index file: %1", strerror(errno));
317 #endif /* not HAVE_RENAME */
318   temp_index_file = 0;
319   return failed;
320 }
321
322 static void usage(FILE *stream)
323 {
324   fprintf(stream,
325 "usage: %s [-vw] [-c file] [-d dir] [-f file] [-h n] [-i XYZ] [-k n]\n"
326 "       [-l n] [-n n] [-o base] [-t n] [files...]\n",
327           program_name);
328 }
329
330 static void check_integer_arg(char opt, const char *arg, int min, int *res)
331 {
332   char *ptr;
333   long n = strtol(arg, &ptr, 10);
334   if (n == 0 && ptr == arg)
335     error("argument to -%1 not an integer", opt);
336   else if (n < min)
337     error("argument to -%1 must not be less than %2", opt, min);
338   else {
339     if (n > INT_MAX)
340       error("argument to -%1 greater than maximum integer", opt);
341     else if (*ptr != '\0')
342       error("junk after integer argument to -%1", opt);
343     *res = int(n);
344   }
345 }
346
347 static char *get_cwd()
348 {
349   char *buf;
350   int size = 12;
351
352   for (;;) {
353     buf = new char[size];
354     if (getcwd(buf, size))
355       break;
356     if (errno != ERANGE)
357       fatal("cannot get current working directory: %1", strerror(errno));
358     a_delete buf;
359     if (size == INT_MAX)
360       fatal("current working directory longer than INT_MAX");
361     if (size > INT_MAX/2)
362       size = INT_MAX;
363     else
364       size *= 2;
365   }
366   return buf;
367 }
368
369 word_list::word_list(const char *s, int n, word_list *p)
370 : next(p), len(n)
371 {
372   str = new char[n];
373   memcpy(str, s, n);
374 }
375
376 static void read_common_words_file()
377 {
378   if (n_ignore_words <= 0)
379     return;
380   errno = 0;
381   FILE *fp = fopen(common_words_file, "r");
382   if (!fp)
383     fatal("can't open `%1': %2", common_words_file, strerror(errno));
384   common_words_table = new word_list * [hash_table_size];
385   for (int i = 0; i < hash_table_size; i++)
386     common_words_table[i] = 0;
387   int count = 0;
388   int key_len = 0;
389   for (;;) {
390     int c = getc(fp);
391     while (c != EOF && !csalnum(c))
392       c = getc(fp);
393     if (c == EOF)
394       break;
395     do {
396       if (key_len < truncate_len)
397         key_buffer[key_len++] = cmlower(c);
398       c = getc(fp);
399     } while (c != EOF && csalnum(c));
400     if (key_len >= shortest_len) {
401       int h = hash(key_buffer, key_len) % hash_table_size;
402       common_words_table[h] = new word_list(key_buffer, key_len,
403                                             common_words_table[h]);
404     }
405     if (++count >= n_ignore_words)
406       break;
407     key_len = 0;
408     if (c == EOF)
409       break;
410   }
411   n_ignore_words = count;
412   fclose(fp);
413 }
414
415 static int do_whole_file(const char *filename)
416 {
417   errno = 0;
418   FILE *fp = fopen(filename, "r");
419   if (!fp) {
420     error("can't open `%1': %2", filename, strerror(errno));
421     return 0;
422   }
423   int count = 0;
424   int key_len = 0;
425   int c;
426   while ((c = getc(fp)) != EOF) {
427     if (csalnum(c)) {
428       key_len = 1;
429       key_buffer[0] = c;
430       while ((c = getc(fp)) != EOF) {
431         if (!csalnum(c))
432           break;
433         if (key_len < truncate_len)
434           key_buffer[key_len++] = c;
435       }
436       if (store_key(key_buffer, key_len)) {
437         if (++count >= max_keys_per_item)
438           break;
439       }
440       if (c == EOF)
441         break;
442     }
443   }
444   store_reference(filenames.length(), 0, 0);
445   store_filename(filename);
446   fclose(fp);
447   return 1;
448 }
449
450 static int do_file(const char *filename)
451 {
452   errno = 0;
453   // Need binary I/O for MS-DOS/MS-Windows, because indxbib relies on
454   // byte counts to be consistent with fseek.
455   FILE *fp = fopen(filename, FOPEN_RB);
456   if (fp == 0) {
457     error("can't open `%1': %2", filename, strerror(errno));
458     return 0;
459   }
460   int filename_index = filenames.length();
461   store_filename(filename);
462
463   enum {
464     START,      // at the start of the file; also in between references
465     BOL,        // in the middle of a reference, at the beginning of the line
466     PERCENT,    // seen a percent at the beginning of the line
467     IGNORE,     // ignoring a field
468     IGNORE_BOL, // at the beginning of a line ignoring a field
469     KEY,        // in the middle of a key
470     DISCARD,    // after truncate_len bytes of a key
471     MIDDLE      // in between keys
472   } state = START;
473   
474   // In states START, BOL, IGNORE_BOL, space_count how many spaces at
475   // the beginning have been seen.  In states PERCENT, IGNORE, KEY,
476   // MIDDLE space_count must be 0.
477   int space_count = 0;
478   int byte_count = 0;           // bytes read
479   int key_len = 0;
480   int ref_start = -1;           // position of start of current reference
481   for (;;) {
482     int c = getc(fp);
483     if (c == EOF)
484       break;
485     // We opened the file in binary mode, so we need to skip
486     // every CR character before a Newline.
487     if (c == '\r') {
488       int peek = getc(fp);
489       if (peek == '\n') {
490         byte_count++;
491         c = peek;
492       }
493       else
494         ungetc(peek, fp);
495     }
496 #if defined(__MSDOS__) || defined(_MSC_VER) || defined(__EMX__)
497     else if (c == 0x1a) // ^Z means EOF in text files
498       break;
499 #endif
500     byte_count++;
501     switch (state) {
502     case START:
503       if (c == ' ' || c == '\t') {
504         space_count++;
505         break;
506       }
507       if (c == '\n') {
508         space_count = 0;
509         break;
510       }
511       ref_start = byte_count - space_count - 1;
512       space_count = 0;
513       if (c == '%')
514         state = PERCENT;
515       else if (csalnum(c)) {
516         state = KEY;
517         key_buffer[0] = c;
518         key_len = 1;
519       }
520       else
521         state = MIDDLE;
522       break;
523     case BOL:
524       switch (c) {
525       case '%':
526         if (space_count > 0) {
527           space_count = 0;
528           state = MIDDLE;
529         }
530         else
531           state = PERCENT;
532         break;
533       case ' ':
534       case '\t':
535         space_count++;
536         break;
537       case '\n':
538         store_reference(filename_index, ref_start,
539                         byte_count - 1 - space_count - ref_start);
540         state = START;
541         space_count = 0;
542         break;
543       default:
544         space_count = 0;
545         if (csalnum(c)) {
546           state = KEY;
547           key_buffer[0] = c;
548           key_len = 1;
549         }
550         else
551           state = MIDDLE;
552       }
553       break;
554     case PERCENT:
555       if (strchr(ignore_fields, c) != 0)
556         state = IGNORE;
557       else if (c == '\n')
558         state = BOL;
559       else
560         state = MIDDLE;
561       break;
562     case IGNORE:
563       if (c == '\n')
564         state = IGNORE_BOL;
565       break;
566     case IGNORE_BOL:
567       switch (c) {
568       case '%':
569         if (space_count > 0) {
570           state = IGNORE;
571           space_count = 0;
572         }
573         else
574           state = PERCENT;
575         break;
576       case ' ':
577       case '\t':
578         space_count++;
579         break;
580       case '\n':
581         store_reference(filename_index, ref_start,
582                         byte_count - 1 - space_count - ref_start);
583         state = START;
584         space_count = 0;
585         break;
586       default:
587         space_count = 0;
588         state = IGNORE;
589       }
590       break;
591     case KEY:
592       if (csalnum(c)) {
593         if (key_len < truncate_len)
594           key_buffer[key_len++] = c;
595         else
596           state = DISCARD;
597       }
598       else {
599         possibly_store_key(key_buffer, key_len);
600         key_len = 0;
601         if (c == '\n')
602           state = BOL;
603         else
604           state = MIDDLE;
605       }
606       break;
607     case DISCARD:
608       if (!csalnum(c)) {
609         possibly_store_key(key_buffer, key_len);
610         key_len = 0;
611         if (c == '\n')
612           state = BOL;
613         else
614           state = MIDDLE;
615       }
616       break;
617     case MIDDLE:
618       if (csalnum(c)) {
619         state = KEY;
620         key_buffer[0] = c;
621         key_len = 1;
622       }
623       else if (c == '\n')
624         state = BOL;
625       break;
626     default:
627       assert(0);
628     }
629   }
630   switch (state) {
631   case START:
632     break;
633   case DISCARD:
634   case KEY:
635     possibly_store_key(key_buffer, key_len);
636     // fall through
637   case BOL:
638   case PERCENT:
639   case IGNORE_BOL:
640   case IGNORE:
641   case MIDDLE:
642     store_reference(filename_index, ref_start,
643                     byte_count - ref_start - space_count);
644     break;
645   default:
646     assert(0);
647   }
648   fclose(fp);
649   return 1;
650 }
651
652 static void store_reference(int filename_index, int pos, int len)
653 {
654   tag t;
655   t.filename_index = filename_index;
656   t.start = pos;
657   t.length = len;
658   fwrite_or_die(&t, sizeof(t), 1, indxfp);
659   ntags++;
660 }
661
662 static void store_filename(const char *fn)
663 {
664   filenames += fn;
665   filenames += '\0';
666 }
667
668 static void init_hash_table()
669 {
670   hash_table = new table_entry[hash_table_size];
671   for (int i = 0; i < hash_table_size; i++)
672     hash_table[i].ptr = 0;
673 }
674
675 static void possibly_store_key(char *s, int len)
676 {
677   static int last_tagno = -1;
678   static int key_count;
679   if (last_tagno != ntags) {
680     last_tagno = ntags;
681     key_count = 0;
682   }
683   if (key_count < max_keys_per_item) {
684     if (store_key(s, len))
685       key_count++;
686   }
687 }
688
689 static int store_key(char *s, int len)
690 {
691   if (len < shortest_len)
692     return 0;
693   int is_number = 1;
694   for (int i = 0; i < len; i++)
695     if (!csdigit(s[i])) {
696       is_number = 0;
697       s[i] = cmlower(s[i]);
698     }
699   if (is_number && !(len == 4 && s[0] == '1' && s[1] == '9'))
700     return 0;
701   int h = hash(s, len) % hash_table_size;
702   if (common_words_table) {
703     for (word_list *ptr = common_words_table[h]; ptr; ptr = ptr->next)
704       if (len == ptr->len && memcmp(s, ptr->str, len) == 0)
705         return 0;
706   }
707   table_entry *pp =  hash_table + h;
708   if (!pp->ptr)
709     pp->ptr = new block;
710   else if (pp->ptr->v[pp->ptr->used - 1] == ntags)
711     return 1;
712   else if (pp->ptr->used >= BLOCK_SIZE)
713     pp->ptr = new block(pp->ptr);
714   pp->ptr->v[(pp->ptr->used)++] = ntags;
715   return 1;
716 }
717
718 static void write_hash_table()
719 {
720   const int minus_one = -1;
721   int li = 0;
722   for (int i = 0; i < hash_table_size; i++) {
723     block *ptr = hash_table[i].ptr;
724     if (!ptr)
725       hash_table[i].count = -1;
726     else {
727       hash_table[i].count = li;
728       block *rev = 0;
729       while (ptr) {
730         block *tem = ptr;
731         ptr = ptr->next;
732         tem->next = rev;
733         rev = tem;
734       }
735       while (rev) {
736         fwrite_or_die(rev->v, sizeof(int), rev->used, indxfp);
737         li += rev->used;
738         block *tem = rev;
739         rev = rev->next;
740         delete tem;
741       }
742       fwrite_or_die(&minus_one, sizeof(int), 1, indxfp);
743       li += 1;
744     }
745   }
746   if (sizeof(table_entry) == sizeof(int))
747     fwrite_or_die(hash_table, sizeof(int), hash_table_size, indxfp);
748   else {
749     // write it out word by word
750     for (int i = 0; i < hash_table_size; i++)
751       fwrite_or_die(&hash_table[i].count, sizeof(int), 1, indxfp);
752   }
753   fwrite_or_die(filenames.contents(), 1, filenames.length(), indxfp);
754   if (fseek(indxfp, 0, 0) < 0)
755     fatal("error seeking on index file: %1", strerror(errno));
756   index_header h;
757   h.magic = INDEX_MAGIC;
758   h.version = INDEX_VERSION;
759   h.tags_size = ntags;
760   h.lists_size = li;
761   h.table_size = hash_table_size;
762   h.strings_size = filenames.length();
763   h.truncate = truncate_len;
764   h.shortest = shortest_len;
765   h.common = n_ignore_words;
766   fwrite_or_die(&h, sizeof(h), 1, indxfp);
767 }
768
769 static void fwrite_or_die(const void *ptr, int size, int nitems, FILE *fp)
770 {
771   if (fwrite(ptr, size, nitems, fp) != (size_t)nitems)
772     fatal("fwrite failed: %1", strerror(errno));
773 }
774
775 void fatal_error_exit()
776 {
777   cleanup();
778   exit(3);
779 }
780
781 extern "C" {
782
783 void cleanup()
784 {
785   if (temp_index_file)
786     unlink(temp_index_file);
787 }
788
789 }