]> granicus.if.org Git - fortune-mod/blob - fortune-mod/fortune/fortune.c
convert to bool
[fortune-mod] / fortune-mod / fortune / fortune.c
1 /*      $NetBSD: fortune.c,v 1.8 1995/03/23 08:28:40 cgd Exp $  */
2
3 /*-
4  * Copyright (c) 1986, 1993
5  *      The Regents of the University of California.  All rights reserved.
6  *
7  * This code is derived from software contributed to Berkeley by
8  * Ken Arnold.
9  *
10  * Redistribution and use in source and binary forms, with or without
11  * modification, are permitted provided that the following conditions
12  * are met:
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in the
17  *    documentation and/or other materials provided with the distribution.
18  * 3. All advertising materials mentioning features or use of this software
19  *    must display the following acknowledgement:
20  *      This product includes software developed by the University of
21  *      California, Berkeley and its contributors.
22  * 4. Neither the name of the University nor the names of its contributors
23  *    may be used to endorse or promote products derived from this software
24  *    without specific prior written permission.
25  *
26  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36  * SUCH DAMAGE.
37  */
38
39 /* Modified September, 1995, Amy A. Lewis
40  * 1: removed all file-locking dreck.  Unnecessary
41  * 2: Fixed bug that made fortune -f report a different list than
42  *    fortune with any other parameters, or none, and which forced
43  *    the program to read only one file (named 'fortunes')
44  * 3: removed the unnecessary print_file_list()
45  * 4: Added "OFFDIR" to pathnames.h as the directory in which offensive
46  *    fortunes are kept.  This considerably simplifies our life by
47  *    permitting us to dispense with a lot of silly tests for the string
48  *    "-o" at the end of a filename.
49  * 5: I think the problems with trying to find filenames were fixed by
50  *    the change in the way that offensive files are defined.  Two birds,
51  *    one stone!
52  * 6: Calculated probabilities for all files, so that -f will print them.
53  */
54
55 /* Changes Copyright (c) 1997 Dennis L. Clark.  All rights reserved.
56  *
57  *    The changes in this file may be freely redistributed, modified or
58  *    included in other software, as long as both the above copyright
59  *    notice and these conditions appear intact.
60  */
61
62 /* Modified May 1997, Dennis L. Clark (dbugger@progsoc.uts.edu.au)
63  *  + Various portability fixes
64  *  + Percent selection of files with -a now works on datafiles which
65  *    appear in both unoffensive and offensive directories (see man page
66  *    for details)
67  *  + The -s and -l options are now more consistent in their
68  *    interpretation of fortune length
69  *  + The -s and -l options can now be combined wit the -m option
70  */
71
72 /* Modified Jul 1999, Pablo Saratxaga <srtxg@chanae.alphanet.ch>
73  * - added use of the LANG variables; now if called without argument
74  * it will choose (if they exist) fortunes in the users' language.
75  * (that is, under a directory $LANG/ under the main fortunes directory
76  *
77  * Added to debian by Alastair McKinstry, <mckinstry@computer.org>, 2002-07-31
78  */
79
80 #define PROGRAM_NAME "fortune-mod"
81
82 #include "fortune-mod-common.h"
83
84 #include <dirent.h>
85 #include <fcntl.h>
86 #include <assert.h>
87 #include <errno.h>
88 #include <locale.h>
89 #ifndef _WIN32
90 #include <langinfo.h>
91 #define O_BINARY 0
92 #define WITH_RECODE
93 #endif
94 #ifdef WITH_RECODE
95 #include <recode.h>
96 #endif
97
98 #ifdef HAVE_REGEX_H
99 #include <regex.h>
100 #endif
101 #ifdef HAVE_REGEXP_H
102 #include <regexp.h>
103 #endif
104 #ifdef HAVE_RX_H
105 #include <rx.h>
106 #endif
107
108 #include "config.h"
109
110 #define MINW 6   /* minimum wait if desired */
111 #define CPERS 20 /* # of chars for each sec */
112
113 #define POS_UNKNOWN ((int32_t)-1) /* pos for file unknown */
114 #define NO_PROB (-1)              /* no prob specified for file */
115
116 #ifdef DEBUG
117 #define DPRINTF(l, x)                                                          \
118     if (Debug >= l)                                                            \
119         fprintf x;
120 #else
121 #define DPRINTF(l, x)
122 #endif
123
124 typedef struct fd
125 {
126     int percent;
127     int fd, datfd;
128     int32_t pos;
129     FILE *inf;
130     char *name;
131     char *path;
132     char *datfile, *posfile;
133     bool read_tbl;
134     bool was_pos_file;
135     bool utf8_charset;
136     STRFILE tbl;
137     int num_children;
138     struct fd *child, *parent;
139     struct fd *next, *prev;
140 } FILEDESC;
141
142 static char *env_lang;
143
144 static bool Found_one;           /* did we find a match? */
145 static bool Find_files = false;  /* just find a list of proper fortune files */
146 static bool Wait = false;        /* wait desired after fortune */
147 static bool Short_only = false;  /* short fortune desired */
148 static bool Long_only = false;   /* long fortune desired */
149 static bool Offend = false;      /* offensive fortunes only */
150 static bool All_forts = false;   /* any fortune allowed */
151 static bool Equal_probs = false; /* scatter un-allocated prob equally */
152 static bool Show_filename = false;
153 static bool No_recode = false; /* Do we want to stop recoding from occuring */
154
155 static bool ErrorMessage =
156     false; /* Set to true if an error message has been displayed */
157
158 #ifndef NO_REGEX
159 static bool Match = false; /* dump fortunes matching a pattern */
160
161 #endif
162 #ifdef DEBUG
163 static bool Debug = false; /* print debug messages */
164
165 #endif
166
167 static unsigned char *Fortbuf = NULL; /* fortune buffer for -m */
168
169 static int Fort_len = 0, Spec_prob = 0, /* total prob specified on cmd line */
170     Num_files, Num_kids,                /* totals of files and children. */
171     SLEN = 160; /* max. characters in a "short" fortune */
172
173 static int32_t Seekpts[2]; /* seek pointers to fortunes */
174
175 static FILEDESC *File_list = NULL, /* Head of file list */
176     *File_tail = NULL;             /* Tail of file list */
177 static FILEDESC *Fortfile;         /* Fortune file to use */
178
179 static STRFILE Noprob_tbl; /* sum of data for all no prob files */
180
181 #ifdef POSIX_REGEX
182 #define RE_COMP(p) regcomp(&Re_pat, (p), REG_NOSUB)
183 #define BAD_COMP(f) ((f) != 0)
184 #define RE_EXEC(p) (regexec(&Re_pat, (p), 0, NULL, 0) == 0)
185
186 static regex_t Re_pat;
187 #else
188 #define NO_REGEX
189 #endif /* POSIX_REGEX */
190
191 #ifdef WITH_RECODE
192 static RECODE_REQUEST request;
193 static RECODE_OUTER outer;
194 #endif
195
196 int add_dir(FILEDESC *);
197
198 static unsigned long my_random(unsigned long base)
199 {
200     unsigned long long l = 0;
201     char *hard_coded_val = getenv("FORTUNE_MOD_RAND_HARD_CODED_VALS");
202     if (hard_coded_val)
203     {
204         return ((unsigned long)atol(hard_coded_val) % base);
205     }
206     if (getenv("FORTUNE_MOD_USE_SRAND"))
207     {
208         goto fallback;
209     }
210     FILE *const fp = fopen("/dev/urandom", "rb");
211     if (!fp)
212     {
213         goto fallback;
214     }
215     if (fread(&l, sizeof(l), 1, fp) != 1)
216     {
217         fclose(fp);
218         goto fallback;
219     }
220     fclose(fp);
221     return l % base;
222 fallback:
223     return random() % base;
224 }
225
226 static char *program_version(void)
227 {
228     static char buf[BUFSIZ];
229     (void)snprintf(buf, sizeof(buf), "%s version %s", PROGRAM_NAME, VERSION);
230     return buf;
231 }
232
233 static void __attribute__((noreturn)) usage(void)
234 {
235     (void)fprintf(stderr, "%s\n", program_version());
236     (void)fprintf(stderr, "%s", "fortune [-a");
237 #ifdef DEBUG
238     (void)fprintf(stderr, "%s", "D");
239 #endif /* DEBUG */
240     (void)fprintf(stderr, "%s", "f");
241 #ifndef NO_REGEX
242     (void)fprintf(stderr, "%s", "i");
243 #endif /* NO_REGEX */
244     (void)fprintf(stderr, "%s", "l");
245 #ifndef NO_OFFENSIVE
246     (void)fprintf(stderr, "%s", "o");
247 #endif
248     (void)fprintf(stderr, "%s", "sw]");
249 #ifndef NO_REGEX
250     (void)fprintf(stderr, "%s", " [-m pattern]");
251 #endif /* NO_REGEX */
252     (void)fprintf(stderr, "%s", " [-n number] [ [#%] file/directory/all]\n");
253     exit(1);
254 }
255
256 #define STR(str) ((!str) ? "NULL" : (str))
257
258 /*
259  * calc_equal_probs:
260  *      Set the global values for number of files/children, to be used
261  * in printing probabilities when listing files
262  */
263 static void calc_equal_probs(void)
264 {
265     Num_files = Num_kids = 0;
266     FILEDESC *fiddlylist = File_list;
267     while (fiddlylist)
268     {
269         Num_files++;
270         Num_kids += fiddlylist->num_children;
271         fiddlylist = fiddlylist->next;
272     }
273 }
274
275 /*
276  * print_list:
277  *      Print out the actual list, recursively.
278  */
279 static void print_list(FILEDESC *list, int lev)
280 {
281     while (list)
282     {
283         fprintf(stderr, "%*s", lev * 4, "");
284         if (list->percent == NO_PROB)
285             if (!Equal_probs)
286                 /* This, with some changes elsewhere, gives proper percentages
287                  * for every case fprintf(stderr, "___%%"); */
288                 fprintf(stderr, "%5.2f%%",
289                     (100.0 - Spec_prob) * list->tbl.str_numstr /
290                         Noprob_tbl.str_numstr);
291             else if (lev == 0)
292                 fprintf(stderr, "%5.2f%%", 100.0 / Num_files);
293             else
294                 fprintf(stderr, "%5.2f%%", 100.0 / Num_kids);
295         else
296             fprintf(stderr, "%5.2f%%", 1.0 * list->percent);
297         fprintf(stderr, " %s", STR(list->name));
298         DPRINTF(1, (stderr, " (%s, %s, %s)\n", STR(list->path),
299                        STR(list->datfile), STR(list->posfile)));
300         putc('\n', stderr);
301         if (list->child)
302             print_list(list->child, lev + 1);
303         list = list->next;
304     }
305 }
306
307 #ifndef NO_REGEX
308 /*
309  * conv_pat:
310  *      Convert the pattern to an ignore-case equivalent.
311  */
312 static char *conv_pat(char *orig)
313 {
314     char *sp;
315     unsigned int cnt;
316     char *new_buf;
317
318     cnt = 1; /* allow for '\0' */
319     for (sp = orig; *sp != '\0'; sp++)
320         if (isalpha(*sp))
321             cnt += 4;
322         else
323             cnt++;
324     if (!(new_buf = malloc(cnt)))
325     {
326         fprintf(stderr, "%s", "pattern too long for ignoring case\n");
327         exit(1);
328     }
329
330     for (sp = new_buf; *orig != '\0'; orig++)
331     {
332         if (islower(*orig))
333         {
334             *sp++ = '[';
335             *sp++ = *orig;
336             *sp++ = (char)toupper(*orig);
337             *sp++ = ']';
338         }
339         else if (isupper(*orig))
340         {
341             *sp++ = '[';
342             *sp++ = *orig;
343             *sp++ = (char)tolower(*orig);
344             *sp++ = ']';
345         }
346         else
347             *sp++ = *orig;
348     }
349     *sp = '\0';
350     return new_buf;
351 }
352 #endif /* NO_REGEX */
353
354 /*
355  * do_malloc:
356  *      Do a malloc, checking for NULL return.
357  */
358 static void *do_malloc(size_t size)
359 {
360     void *new_buf = malloc(size);
361
362     if (!new_buf)
363     {
364         (void)fprintf(stderr, "%s", "fortune: out of memory.\n");
365         exit(1);
366     }
367     return new_buf;
368 }
369
370 /*
371  * new_fp:
372  *      Return a pointer to an initialized new FILEDESC.
373  */
374 static FILEDESC *new_fp(void)
375 {
376     FILEDESC *fp;
377
378     fp = (FILEDESC *)do_malloc(sizeof *fp);
379     fp->datfd = -1;
380     fp->pos = POS_UNKNOWN;
381     fp->inf = NULL;
382     fp->fd = -1;
383     fp->percent = NO_PROB;
384     fp->read_tbl = false;
385     fp->tbl.str_version = 0;
386     fp->tbl.str_numstr = 0;
387     fp->tbl.str_longlen = 0;
388     fp->tbl.str_shortlen = 0;
389     fp->tbl.str_flags = 0;
390     fp->tbl.stuff[0] = 0;
391     fp->tbl.stuff[1] = 0;
392     fp->tbl.stuff[2] = 0;
393     fp->tbl.stuff[3] = 0;
394     fp->next = NULL;
395     fp->prev = NULL;
396     fp->child = NULL;
397     fp->parent = NULL;
398     fp->datfile = NULL;
399     fp->posfile = NULL;
400     return fp;
401 }
402
403 #ifdef MYDEBUG
404 static inline void debugprint(const char *msg, ...)
405 {
406     va_list ap;
407
408     va_start(ap, msg);
409     vfprintf(stderr, msg, ap);
410     fflush(stderr);
411     va_end(ap);
412 }
413 #else
414 #define debugprint(format, ...)                                                \
415     {                                                                          \
416     }
417 #endif
418 /*
419  * is_dir:
420  *      Return true if the file is a directory, false otherwise.
421  */
422 static int is_dir(const char *const file)
423 {
424     struct stat sbuf;
425
426     if (stat(file, &sbuf) < 0)
427     {
428         debugprint("is_dir failed for file=<%s>\n", file);
429         return -1;
430     }
431     const bool ret = (S_ISDIR(sbuf.st_mode) ? true : false);
432     debugprint("is_dir for file=<%s> gave ret=<%d>\n", file, ret);
433     return ret;
434 }
435
436 /*
437  * is_existant:
438  *      Return true if the file exists, false otherwise.
439  */
440 static int is_existant(char *file)
441 {
442     struct stat staat;
443
444     if (stat(file, &staat) == 0)
445         return true;
446     switch (errno)
447     {
448     case ENOENT:
449     case ENOTDIR:
450         return false;
451     default:
452         perror("fortune: bad juju in is_existant");
453         exit(1);
454     }
455 }
456
457 /*
458  * is_fortfile:
459  *      Return true if the file is a fortune database file.  We try and
460  *      exclude files without reading them if possible to avoid
461  *      overhead.  Files which start with ".", or which have "illegal"
462  *      suffixes, as contained in suflist[], are ruled out.
463  */
464 static int is_fortfile(const char *const file, char **datp)
465 {
466     const char *sp = strrchr(file, '/');
467     static const char *suflist[] = {/* list of "illegal" suffixes" */
468         "dat", "pos", "c", "h", "p", "i", "f", "pas", "ftn", "ins.c", "ins,pas",
469         "ins.ftn", "sml", NULL};
470
471     DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
472
473     if (!sp)
474         sp = file;
475     else
476         sp++;
477     if (*sp == '.')
478     {
479         DPRINTF(2, (stderr, "%s", "false (file starts with '.')\n"));
480         return false;
481     }
482     if ((sp = strrchr(sp, '.')))
483     {
484         sp++;
485         for (int i = 0; suflist[i]; ++i)
486             if (strcmp(sp, suflist[i]) == 0)
487             {
488                 DPRINTF(2, (stderr, "false (file has suffix \".%s\")\n", sp));
489                 return false;
490             }
491     }
492
493     const size_t do_len = (strlen(file) + 6);
494     char *const datfile = do_malloc(do_len + 1);
495     snprintf(datfile, do_len, "%s.dat", file);
496     if (access(datfile, R_OK) < 0)
497     {
498         free(datfile);
499         DPRINTF(2, (stderr, "%s", "false (no \".dat\" file)\n"));
500         return false;
501     }
502     if (datp)
503         *datp = datfile;
504     else
505         free(datfile);
506     DPRINTF(2, (stderr, "%s", "true\n"));
507     return true;
508 }
509
510 static bool path_is_absolute(const char *const path)
511 {
512     if (path[0] == '/')
513     {
514         return true;
515     }
516 #ifdef _WIN32
517     if (isalpha(path[0]) && path[1] == ':' && path[2] == '/')
518     {
519         return true;
520     }
521 #endif
522     return false;
523 }
524 /*
525  * add_file:
526  *      Add a file to the file list.
527  */
528 static int add_file(int percent, const char *file, const char *dir,
529     FILEDESC **head, FILEDESC **tail, FILEDESC *parent)
530 {
531     FILEDESC *fp;
532     int fd = -1;
533     char *path;
534     char *sp;
535     struct stat statbuf;
536
537     if (!dir)
538     {
539         path = strdup(file);
540     }
541     else
542     {
543         const size_t do_len = (strlen(dir) + strlen(file) + 2);
544         path = do_malloc(do_len + 1);
545         snprintf(path, do_len, "%s/%s", dir, file);
546     }
547     if (*path == '/' &&
548         !is_existant(path)) /* If doesn't exist, don't do anything. */
549     {
550         free(path);
551         return false;
552     }
553     const int isdir = is_dir(path);
554     if ((isdir > 0 && parent) || (isdir < 0))
555     {
556         free(path);
557         return false; /* don't recurse */
558     }
559
560     DPRINTF(1, (stderr, "trying to add file \"%s\"\n", path));
561     if ((
562 #ifdef _WIN32
563             (!isdir) &&
564 #endif
565             ((fd = open(path, O_RDONLY | O_BINARY)) < 0)) ||
566         !path_is_absolute(path))
567     {
568         debugprint("sarahhhhh fd=%d path=<%s> dir=<%s> file=<%s> percent=%d\n",
569             fd, path, dir, file, percent);
570         bool found = false;
571         if (!dir && (!strchr(file, '/')))
572         {
573             if (((sp = strrchr(file, '-')) != NULL) && (strcmp(sp, "-o") == 0))
574             {
575 #define CALL__add_file(dir) add_file(percent, file, dir, head, tail, parent)
576 #define COND_CALL__add_file(loc_dir, dir)                                      \
577     ((!strcmp((loc_dir), (dir))) ? 0 : CALL__add_file(dir))
578                 /* BSD-style '-o' offensive file suffix */
579                 *sp = '\0';
580                 found = CALL__add_file(LOCOFFDIR) ||
581                         COND_CALL__add_file(LOCOFFDIR, OFFDIR);
582                 /* put the suffix back in for better identification later */
583                 *sp = '-';
584             }
585             else if (All_forts)
586                 found =
587                     (CALL__add_file(LOCFORTDIR) || CALL__add_file(LOCOFFDIR) ||
588                         COND_CALL__add_file(LOCFORTDIR, FORTDIR) ||
589                         COND_CALL__add_file(LOCOFFDIR, OFFDIR));
590             else if (Offend)
591                 found = (CALL__add_file(LOCOFFDIR) ||
592                          COND_CALL__add_file(LOCOFFDIR, OFFDIR));
593             else
594                 found = (CALL__add_file(LOCFORTDIR) ||
595                          COND_CALL__add_file(LOCFORTDIR, FORTDIR));
596 #undef COND_CALL__add_file
597 #undef CALL__add_file
598         }
599         if (!found && !parent && !dir)
600         { /* don't display an error when trying language specific files */
601             if (env_lang)
602             {
603                 char llang[512];
604                 char langdir[1024];
605                 int ret = 0;
606
607                 strncpy(llang, env_lang, sizeof(llang));
608                 llang[sizeof(llang) - 1] = '\0';
609                 char *lang = llang;
610
611                 /* the language string can be like "es:fr_BE:ga" */
612                 while (!ret && lang && (*lang))
613                 {
614                     char *p = strchr(lang, ':');
615                     if (p)
616                         *p++ = '\0';
617                     snprintf(langdir, sizeof(langdir), "%s/%s", FORTDIR, lang);
618
619                     if (strncmp(path, lang, 2) == 0)
620                         ret = 1;
621                     else if (strncmp(path, langdir, strlen(FORTDIR) + 3) == 0)
622                         ret = 1;
623                     lang = p;
624                 }
625                 if (!ret)
626                 {
627                     debugprint("moshe\n");
628                     perror(path);
629                 }
630             }
631             else
632             {
633                 debugprint("abe\n");
634                 perror(path);
635             }
636         }
637
638         free(path);
639         path = NULL;
640         return found;
641     }
642
643     DPRINTF(2, (stderr, "path = \"%s\"\n", path));
644
645     fp = new_fp();
646     fp->fd = fd;
647     fp->percent = percent;
648
649     fp->name = strdup(file);
650     fp->path = strdup(path);
651
652     // FIXME
653     fp->utf8_charset = false;
654     const size_t do_len = (strlen(path) + 5);
655     char *testpath = do_malloc(do_len + 1);
656     snprintf(testpath, do_len, "%s.u8", path);
657     //    fprintf(stderr, "State mal: %s\n", testpath);
658     if (stat(testpath, &statbuf) == 0)
659         fp->utf8_charset = true;
660
661     free(testpath);
662     testpath = NULL;
663     //    fprintf(stderr, "Is utf8?: %i\n", fp->utf8_charset );
664
665     fp->parent = parent;
666
667     if ((isdir && !add_dir(fp)) || (!isdir && !is_fortfile(path, &fp->datfile)))
668     {
669         if (!parent)
670             fprintf(
671                 stderr, "fortune:%s not a fortune file or directory\n", path);
672         free(path);
673         path = NULL;
674         free(fp->datfile);
675         free(fp->posfile);
676         free(fp->name);
677         free(fp->path);
678         if (fp->fd >= 0)
679             close(fp->fd);
680         free(fp);
681         return false;
682     }
683
684     /* This is a hack to come around another hack - add_dir returns success
685      * if the directory is allowed to be empty, but we can not handle an
686      * empty directory... */
687     if (isdir && fp->num_children == 0)
688     {
689         free(path);
690         path = NULL;
691         free(fp->datfile);
692         free(fp->posfile);
693         free(fp->name);
694         free(fp->path);
695         if (fp->fd >= 0)
696             close(fp->fd);
697         free(fp);
698         return true;
699     }
700     /* End hack. */
701
702     if (!(*head))
703         *head = *tail = fp;
704     else if (fp->percent == NO_PROB)
705     {
706         (*tail)->next = fp;
707         fp->prev = *tail;
708         *tail = fp;
709     }
710     else
711     {
712         (*head)->prev = fp;
713         fp->next = *head;
714         *head = fp;
715     }
716
717     free(path);
718     path = NULL;
719
720     return true;
721 }
722
723 static int names_compare(const void *a, const void *b)
724 {
725     return strcmp(*(const char *const *)a, *(const char *const *)b);
726 }
727 /*
728  * add_dir:
729  *      Add the contents of an entire directory.
730  */
731 int add_dir(FILEDESC *fp)
732 {
733     DIR *dir;
734     struct dirent *dirent;
735     char **names;
736     size_t i, count_names, max_count_names;
737
738     close(fp->fd);
739     fp->fd = -1;
740     if (!(dir = opendir(fp->path)))
741     {
742         debugprint("yonah\n");
743         perror(fp->path);
744         return false;
745     }
746     FILEDESC *tailp = NULL;
747     DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
748     fp->num_children = 0;
749     max_count_names = 200;
750     count_names = 0;
751     names = malloc(sizeof(names[0]) * max_count_names);
752     if (!names)
753     {
754         debugprint("zach\n");
755         perror("Out of RAM!");
756         exit(-1);
757     }
758     while ((dirent = readdir(dir)))
759     {
760         if (dirent->d_name[0] == 0)
761             continue;
762         char *name = strdup(dirent->d_name);
763         if (count_names == max_count_names)
764         {
765             max_count_names += 200;
766             names = realloc(names, sizeof(names[0]) * max_count_names);
767             if (!names)
768             {
769                 debugprint("rebecca\n");
770                 perror("Out of RAM!");
771                 exit(-1);
772             }
773         }
774         names[count_names++] = name;
775     }
776     closedir(dir);
777     qsort(names, count_names, sizeof(names[0]), names_compare);
778
779     for (i = 0; i < count_names; ++i)
780     {
781         if (add_file(NO_PROB, names[i], fp->path, &fp->child, &tailp, fp))
782         {
783             fp->num_children++;
784         }
785         free(names[i]);
786     }
787     free(names);
788     dir = NULL;
789     if (fp->num_children == 0)
790     {
791         /*
792          * Only the local fortune dir and the local offensive dir are
793          * allowed to be empty.
794          *  - Brian Bassett (brianb@debian.org) 1999/07/31
795          */
796         if (strcmp(LOCFORTDIR, fp->path) == 0 ||
797             strcmp(LOCOFFDIR, fp->path) == 0)
798         {
799             return true;
800         }
801         fprintf(
802             stderr, "fortune: %s: No fortune files in directory.\n", fp->path);
803         return false;
804     }
805     return true;
806 }
807
808 /*
809  * form_file_list:
810  *      Form the file list from the file specifications.
811  */
812
813 static int top_level__add_file(const char *dirpath)
814 {
815     return add_file(NO_PROB, dirpath, NULL, &File_list, &File_tail, NULL);
816 }
817
818 static int cond_top_level__add_file(
819     const char *dirpath, const char *possible_dup)
820 {
821     if (!strcmp(dirpath, possible_dup))
822     {
823         return 0;
824     }
825     return top_level__add_file(dirpath);
826 }
827
828 static int cond_top_level__LOCFORTDIR(void)
829 {
830     return cond_top_level__add_file(FORTDIR, LOCFORTDIR);
831 }
832
833 static int cond_top_level__OFFDIR(void)
834 {
835     return cond_top_level__add_file(OFFDIR, LOCOFFDIR);
836 }
837
838 static int top_level_LOCFORTDIR(void)
839 {
840     return (top_level__add_file(LOCFORTDIR) | cond_top_level__LOCFORTDIR());
841 }
842
843 static int form_file_list(char **files, int file_cnt)
844 {
845     int i, percent;
846     char *sp;
847     char langdir[1024];
848     char fullpathname[512], locpathname[512];
849
850     if (file_cnt == 0)
851     {
852         if (All_forts)
853         {
854             return (top_level__add_file(LOCFORTDIR) |
855                     top_level__add_file(LOCOFFDIR) |
856                     cond_top_level__LOCFORTDIR() | cond_top_level__OFFDIR());
857         }
858         else if (Offend)
859         {
860             return (top_level__add_file(LOCOFFDIR) | cond_top_level__OFFDIR());
861         }
862         else
863         {
864             if (env_lang)
865             {
866                 char *lang;
867                 char llang[512];
868                 int ret = 0;
869                 char *p;
870
871                 strncpy(llang, env_lang, sizeof(llang));
872                 llang[sizeof(llang) - 1] = '\0';
873                 lang = llang;
874
875                 /* the language string can be like "es:fr_BE:ga" */
876                 while (lang && (*lang))
877                 {
878                     p = strchr(lang, ':');
879                     if (p)
880                         *p++ = '\0';
881
882                     /* first try full locale */
883                     ret = add_file(
884                         NO_PROB, lang, NULL, &File_list, &File_tail, NULL);
885
886                     /* if not try language name only (two first chars) */
887                     if (!ret)
888                     {
889                         char ll[3];
890
891                         strncpy(ll, lang, 2);
892                         ll[2] = '\0';
893                         ret = add_file(
894                             NO_PROB, ll, NULL, &File_list, &File_tail, NULL);
895                     }
896
897                     /* if we have found one we have finished */
898                     if (ret)
899                         return ret;
900                     lang = p;
901                 }
902                 /* default */
903                 return top_level_LOCFORTDIR();
904             }
905             else
906             {
907                 /* no locales available, use default */
908                 return top_level_LOCFORTDIR();
909             }
910         }
911     }
912
913     for (i = 0; i < file_cnt; i++)
914     {
915         percent = NO_PROB;
916         if (!isdigit(files[i][0]))
917             sp = files[i];
918         else
919         {
920             const int MAX_PERCENT = 100;
921             bool percent_has_overflowed = false;
922             percent = 0;
923             for (sp = files[i]; isdigit(*sp); sp++)
924             {
925                 percent = percent * 10 + *sp - '0';
926                 percent_has_overflowed = (percent > MAX_PERCENT);
927                 if (percent_has_overflowed)
928                 {
929                     break;
930                 }
931             }
932             if (percent_has_overflowed || (percent > 100))
933             {
934                 fprintf(stderr, "percentages must be <= 100\n");
935                 fprintf(stderr,
936                     "Overflow percentage detected at argument \"%s\"!\n",
937                     files[i]);
938                 ErrorMessage = true;
939                 return false;
940             }
941             if (percent < 0)
942             {
943                 fprintf(stderr,
944                     "Overflow percentage detected at argument \"%s\"!\n",
945                     files[i]);
946                 ErrorMessage = true;
947                 return false;
948             }
949             if (*sp == '.')
950             {
951                 fprintf(stderr, "%s", "percentages must be integers\n");
952                 ErrorMessage = true;
953                 return false;
954             }
955             /*
956              * If the number isn't followed by a '%', then
957              * it was not a percentage, just the first part
958              * of a file name which starts with digits.
959              */
960             if (*sp != '%')
961             {
962                 percent = NO_PROB;
963                 sp = files[i];
964             }
965             else if (*++sp == '\0')
966             {
967                 if (++i >= file_cnt)
968                 {
969                     fprintf(stderr, "%s", "percentages must precede files\n");
970                     ErrorMessage = true;
971                     return false;
972                 }
973                 sp = files[i];
974             }
975         }
976         if (strcmp(sp, "all") == 0)
977         {
978             snprintf(fullpathname, sizeof(fullpathname), "%s", FORTDIR);
979             snprintf(locpathname, sizeof(locpathname), "%s", LOCFORTDIR);
980         }
981         /* if it isn't an absolute path or relative to . or ..
982            make it an absolute path relative to FORTDIR */
983         else
984         {
985             if (strncmp(sp, "/", 1) != 0 && strncmp(sp, "./", 2) != 0 &&
986                 strncmp(sp, "../", 3) != 0)
987             {
988                 snprintf(
989                     fullpathname, sizeof(fullpathname), "%s/%s", FORTDIR, sp);
990                 snprintf(
991                     locpathname, sizeof(locpathname), "%s/%s", LOCFORTDIR, sp);
992             }
993             else
994             {
995                 snprintf(fullpathname, sizeof(fullpathname), "%s", sp);
996                 snprintf(locpathname, sizeof(locpathname), "%s", sp);
997             }
998         }
999
1000         if (env_lang)
1001         {
1002             char llang[512];
1003             int ret = 0;
1004
1005             strncpy(llang, env_lang, sizeof(llang));
1006             llang[sizeof(llang) - 1] = '\0';
1007             char *lang = llang;
1008
1009             /* the language string can be like "es:fr_BE:ga" */
1010             while (!ret && lang && (*lang))
1011             {
1012                 char *p = strchr(lang, ':');
1013                 if (p)
1014                     *p++ = '\0';
1015
1016                 /* first try full locale */
1017                 snprintf(
1018                     langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, lang, sp);
1019                 ret = add_file(
1020                     percent, langdir, NULL, &File_list, &File_tail, NULL);
1021
1022                 /* if not try language name only (two first chars) */
1023                 if (!ret)
1024                 {
1025                     char ll[3];
1026
1027                     strncpy(ll, lang, 2);
1028                     ll[2] = '\0';
1029                     snprintf(
1030                         langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, ll, sp);
1031                     ret = add_file(
1032                         percent, langdir, NULL, &File_list, &File_tail, NULL);
1033                 }
1034
1035                 lang = p;
1036             }
1037             /* default */
1038             if (!ret)
1039                 ret = add_file(
1040                     percent, fullpathname, NULL, &File_list, &File_tail, NULL);
1041             if (!ret &&
1042                 strncmp(fullpathname, locpathname, sizeof(fullpathname)))
1043                 ret = add_file(
1044                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1045
1046             if (!ret)
1047             {
1048                 snprintf(locpathname, sizeof(locpathname), "%s/%s",
1049                     getenv("PWD"), sp);
1050
1051                 ret = add_file(
1052                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1053             }
1054             if (!ret)
1055             {
1056                 return false;
1057             }
1058             if (strncmp(fullpathname, locpathname, sizeof(fullpathname)) &&
1059                 strcmp(sp, "all") == 0)
1060             {
1061                 add_file(
1062                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1063             }
1064         }
1065         else if (!add_file(
1066                      percent, fullpathname, NULL, &File_list, &File_tail, NULL))
1067             return false;
1068     }
1069     return true;
1070 }
1071
1072 /*
1073  *    This routine evaluates the arguments on the command line
1074  */
1075 static void getargs(int argc, char **argv)
1076 {
1077     bool ignore_case = false;
1078
1079 #ifndef NO_REGEX
1080     char *pat = NULL;
1081
1082 #endif /* NO_REGEX */
1083     int ch;
1084
1085 #ifdef DEBUG
1086 #define DEBUG_GETOPT "D"
1087 #else
1088 #define DEBUG_GETOPT
1089 #endif
1090
1091 #ifdef NO_OFFENSIVE
1092 #define OFFENSIVE_GETOPT
1093 #else
1094 #define OFFENSIVE_GETOPT "o"
1095 #endif
1096
1097     while ((ch = getopt(argc, argv,
1098                 "ac" DEBUG_GETOPT "efilm:n:" OFFENSIVE_GETOPT "suvw")) != EOF)
1099         switch (ch)
1100         {
1101         case 'a': /* any fortune */
1102             All_forts = true;
1103             break;
1104 #ifdef DEBUG
1105         case 'D':
1106             Debug++;
1107             break;
1108 #endif /* DEBUG */
1109         case 'e':
1110             Equal_probs = true; /* scatter un-allocted prob equally */
1111             break;
1112         case 'f': /* find fortune files */
1113             Find_files = true;
1114             break;
1115         case 'l': /* long ones only */
1116             Long_only = true;
1117             Short_only = false;
1118             break;
1119         case 'n':
1120             SLEN = atoi(optarg);
1121             break;
1122 #ifndef NO_OFFENSIVE
1123         case 'o': /* offensive ones only */
1124             Offend = true;
1125             break;
1126 #endif
1127         case 's': /* short ones only */
1128             Short_only = true;
1129             Long_only = false;
1130             break;
1131         case 'w': /* give time to read */
1132             Wait = true;
1133             break;
1134 #ifdef NO_REGEX
1135         case 'i': /* case-insensitive match */
1136         case 'm': /* dump out the fortunes */
1137             (void)fprintf(stderr, "%s",
1138                 "fortune: can't match fortunes on this system (Sorry)\n");
1139             exit(0);
1140 #else             /* NO_REGEX */
1141         case 'm': /* dump out the fortunes */
1142             Match = true;
1143             pat = optarg;
1144             break;
1145         case 'i': /* case-insensitive match */
1146             ignore_case = true;
1147             break;
1148 #endif            /* NO_REGEX */
1149         case 'u': /* Don't recode the fortune */
1150             No_recode = true;
1151             break;
1152         case 'v':
1153             (void)printf("%s\n", program_version());
1154             exit(0);
1155         case 'c':
1156             Show_filename = true;
1157             break;
1158         case '?':
1159         default:
1160             usage();
1161         }
1162     argc -= optind;
1163     argv += optind;
1164
1165     if (!form_file_list(argv, argc))
1166     {
1167         if (!ErrorMessage)
1168         {
1169             fprintf(stderr, "%s", "No fortunes found\n");
1170         }
1171         exit(1); /* errors printed through form_file_list() */
1172     }
1173 #ifdef DEBUG
1174 /*      if (Debug >= 1)
1175  * print_list(File_list, 0); */
1176 #endif /* DEBUG */
1177 /* If (Find_files) print_list() moved to main */
1178 #ifndef NO_REGEX
1179     if (pat)
1180     {
1181         if (ignore_case)
1182             pat = conv_pat(pat);
1183         if (BAD_COMP(RE_COMP(pat)))
1184         {
1185             fprintf(stderr, "bad pattern: %s\n", pat);
1186             exit(1);
1187         }
1188         if (ignore_case)
1189         {
1190             free(pat);
1191         }
1192     }
1193 #endif /* NO_REGEX */
1194 }
1195
1196 /*
1197  * init_prob:
1198  *      Initialize the fortune probabilities.
1199  */
1200 static void init_prob(void)
1201 {
1202     FILEDESC *fp;
1203     int percent = 0, num_noprob = 0, frac;
1204
1205     /*
1206      * Distribute the residual probability (if any) across all
1207      * files with unspecified probability (i.e., probability of 0)
1208      * (if any).
1209      */
1210     FILEDESC *last = NULL;
1211     for (fp = File_tail; fp; fp = fp->prev)
1212         if (fp->percent == NO_PROB)
1213         {
1214             num_noprob++;
1215             if (Equal_probs)
1216                 last = fp;
1217         }
1218         else
1219             percent += fp->percent;
1220     DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's\n",
1221                    percent, num_noprob));
1222     if (percent > 100)
1223     {
1224         fprintf(stderr, "fortune: probabilities sum to %d%%!\n", percent);
1225         exit(1);
1226     }
1227     else if (percent < 100 && num_noprob == 0)
1228     {
1229         fprintf(stderr,
1230             "fortune: no place to put residual probability (%d%%)\n", percent);
1231         exit(1);
1232     }
1233     else if (percent == 100 && num_noprob != 0)
1234     {
1235         fprintf(
1236             stderr, "fortune: no probability left to put in residual files\n");
1237         exit(1);
1238     }
1239     Spec_prob = percent; /* this is for -f when % is specified on cmd line */
1240     percent = 100 - percent;
1241     if (Equal_probs)
1242     {
1243         if (num_noprob != 0)
1244         {
1245             if (num_noprob > 1)
1246             {
1247                 frac = percent / num_noprob;
1248                 DPRINTF(1, (stderr, ", frac = %d%%", frac));
1249                 for (fp = File_tail; fp != last; fp = fp->prev)
1250                     if (fp->percent == NO_PROB)
1251                     {
1252                         fp->percent = frac;
1253                         percent -= frac;
1254                     }
1255             }
1256             last->percent = percent;
1257             DPRINTF(1, (stderr, ", residual = %d%%", percent));
1258         }
1259         else
1260         {
1261             DPRINTF(1, (stderr, ", %d%% distributed over remaining fortunes\n",
1262                            percent));
1263         }
1264     }
1265     DPRINTF(1, (stderr, "%s", "\n"));
1266
1267 #ifdef DEBUG
1268 /*      if (Debug >= 1)
1269  * print_list(File_list, 0); *//* Causes crash with new %% code */
1270 #endif
1271 }
1272
1273 /*
1274  * zero_tbl:
1275  *      Zero out the fields we care about in a tbl structure.
1276  */
1277 static void zero_tbl(STRFILE *tp)
1278 {
1279     tp->str_numstr = 0;
1280     tp->str_longlen = 0;
1281     tp->str_shortlen = (uint32_t)(-1);
1282 }
1283
1284 /*
1285  * sum_tbl:
1286  *      Merge the tbl data of t2 into t1.
1287  */
1288 static void sum_tbl(STRFILE *t1, STRFILE *t2)
1289 {
1290     t1->str_numstr += t2->str_numstr;
1291     if (t1->str_longlen < t2->str_longlen)
1292         t1->str_longlen = t2->str_longlen;
1293     if (t1->str_shortlen > t2->str_shortlen)
1294         t1->str_shortlen = t2->str_shortlen;
1295 }
1296
1297 /*
1298  * get_tbl:
1299  *      Get the tbl data file the datfile.
1300  */
1301 static void get_tbl(FILEDESC *fp)
1302 {
1303     int fd;
1304     FILEDESC *child;
1305
1306     if (fp->read_tbl)
1307         return;
1308     if (!(fp->child))
1309     {
1310         if ((fd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1311         {
1312             perror(fp->datfile);
1313             exit(1);
1314         }
1315         if (read(fd, &fp->tbl.str_version, sizeof fp->tbl.str_version) !=
1316             sizeof fp->tbl.str_version)
1317         {
1318             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1319             exit(1);
1320         }
1321         if (read(fd, &fp->tbl.str_numstr, sizeof fp->tbl.str_numstr) !=
1322             sizeof fp->tbl.str_numstr)
1323         {
1324             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1325             exit(1);
1326         }
1327         if (read(fd, &fp->tbl.str_longlen, sizeof fp->tbl.str_longlen) !=
1328             sizeof fp->tbl.str_longlen)
1329         {
1330             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1331             exit(1);
1332         }
1333         if (read(fd, &fp->tbl.str_shortlen, sizeof fp->tbl.str_shortlen) !=
1334             sizeof fp->tbl.str_shortlen)
1335         {
1336             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1337             exit(1);
1338         }
1339         if (read(fd, &fp->tbl.str_flags, sizeof fp->tbl.str_flags) !=
1340             sizeof fp->tbl.str_flags)
1341         {
1342             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1343             exit(1);
1344         }
1345         if (read(fd, &fp->tbl.stuff, sizeof fp->tbl.stuff) !=
1346             sizeof fp->tbl.stuff)
1347         {
1348             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1349             exit(1);
1350         }
1351         fp->tbl.str_version = ntohl(fp->tbl.str_version);
1352         fp->tbl.str_numstr = ntohl(fp->tbl.str_numstr);
1353         fp->tbl.str_longlen = ntohl(fp->tbl.str_longlen);
1354         fp->tbl.str_shortlen = ntohl(fp->tbl.str_shortlen);
1355         fp->tbl.str_flags = ntohl(fp->tbl.str_flags);
1356         close(fd);
1357     }
1358     else
1359     {
1360         zero_tbl(&fp->tbl);
1361         for (child = fp->child; child; child = child->next)
1362         {
1363             get_tbl(child);
1364             sum_tbl(&fp->tbl, &child->tbl);
1365         }
1366     }
1367     fp->read_tbl = true;
1368 }
1369
1370 /*
1371  * sum_noprobs:
1372  *      Sum up all the noprob probabilities, starting with fp.
1373  */
1374 static void sum_noprobs(FILEDESC *fp)
1375 {
1376     static bool did_noprobs = false;
1377
1378     if (did_noprobs)
1379         return;
1380     zero_tbl(&Noprob_tbl);
1381     while (fp)
1382     {
1383         get_tbl(fp);
1384         /* This conditional should help us return correct values for -f
1385          * when a percentage is specified */
1386         if (fp->percent == NO_PROB)
1387             sum_tbl(&Noprob_tbl, &fp->tbl);
1388         fp = fp->next;
1389     }
1390     did_noprobs = true;
1391 }
1392
1393 /*
1394  * pick_child
1395  *      Pick a child from a chosen parent.
1396  */
1397 static FILEDESC *pick_child(FILEDESC *parent)
1398 {
1399     FILEDESC *fp;
1400     int choice;
1401
1402     if (Equal_probs)
1403     {
1404         choice = my_random(parent->num_children);
1405         DPRINTF(1, (stderr, "    choice = %d (of %d)\n", choice,
1406                        parent->num_children));
1407         for (fp = parent->child; choice--; fp = fp->next)
1408             continue;
1409         DPRINTF(1, (stderr, "    using %s\n", fp->name));
1410         return fp;
1411     }
1412     else
1413     {
1414         get_tbl(parent);
1415         choice = (int)(my_random(parent->tbl.str_numstr));
1416         DPRINTF(1, (stderr, "    choice = %d (of %ld)\n", choice,
1417                        parent->tbl.str_numstr));
1418         for (fp = parent->child; choice >= (int)fp->tbl.str_numstr;
1419              fp = fp->next)
1420         {
1421             choice -= fp->tbl.str_numstr;
1422             DPRINTF(1, (stderr, "\tskip %s, %ld (choice = %d)\n", fp->name,
1423                            fp->tbl.str_numstr, choice));
1424         }
1425         DPRINTF(
1426             1, (stderr, "    using %s, %ld\n", fp->name, fp->tbl.str_numstr));
1427         return fp;
1428     }
1429 }
1430
1431 /*
1432  * open_dat:
1433  *      Open up the dat file if we need to.
1434  */
1435 static void open_dat(FILEDESC *fp)
1436 {
1437     if (fp->datfd < 0 &&
1438         (fp->datfd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1439     {
1440         exit(1);
1441     }
1442 }
1443
1444 /*
1445  * get_pos:
1446  *      Get the position from the pos file, if there is one.  If not,
1447  *      return a random number.
1448  */
1449 static void get_pos(FILEDESC *fp)
1450 {
1451     assert(fp->read_tbl);
1452     if (fp->pos == POS_UNKNOWN)
1453     {
1454         fp->pos = (int32_t)(my_random(fp->tbl.str_numstr));
1455     }
1456     if (++(fp->pos) >= (int32_t)fp->tbl.str_numstr)
1457         fp->pos -= fp->tbl.str_numstr;
1458     DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, fp->pos));
1459 }
1460
1461 /*
1462  * get_fort:
1463  *      Get the fortune data file's seek pointer for the next fortune.
1464  */
1465 static void get_fort(void)
1466 {
1467     FILEDESC *fp;
1468     int choice;
1469
1470     if (!File_list->next || File_list->percent == NO_PROB)
1471         fp = File_list;
1472     else
1473     {
1474         choice = my_random(100);
1475         DPRINTF(1, (stderr, "choice = %d\n", choice));
1476         for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
1477             if (choice < fp->percent)
1478                 break;
1479             else
1480             {
1481                 choice -= fp->percent;
1482                 DPRINTF(1, (stderr, "    skip \"%s\", %d%% (choice = %d)\n",
1483                                fp->name, fp->percent, choice));
1484             }
1485         DPRINTF(1, (stderr, "using \"%s\", %d%% (choice = %d)\n", fp->name,
1486                        fp->percent, choice));
1487     }
1488     if (fp->percent != NO_PROB)
1489         get_tbl(fp);
1490     else
1491     {
1492         if (fp->next)
1493         {
1494             sum_noprobs(fp);
1495             choice = (int)(my_random(Noprob_tbl.str_numstr));
1496             DPRINTF(1, (stderr, "choice = %d (of %ld) \n", choice,
1497                            Noprob_tbl.str_numstr));
1498             while (choice >= (int)fp->tbl.str_numstr)
1499             {
1500                 choice -= (int)fp->tbl.str_numstr;
1501                 fp = fp->next;
1502                 DPRINTF(1, (stderr, "    skip \"%s\", %ld (choice = %d)\n",
1503                                fp->name, fp->tbl.str_numstr, choice));
1504             }
1505             DPRINTF(1,
1506                 (stderr, "using \"%s\", %ld\n", fp->name, fp->tbl.str_numstr));
1507         }
1508         get_tbl(fp);
1509     }
1510     if (fp->tbl.str_numstr == 0)
1511     {
1512         fprintf(stderr, "%s", "fortune: no fortune found\n");
1513         exit(1);
1514     }
1515     if (fp->child)
1516     {
1517         DPRINTF(1, (stderr, "%s", "picking child\n"));
1518         fp = pick_child(fp);
1519     }
1520     Fortfile = fp;
1521     get_pos(fp);
1522     open_dat(fp);
1523     lseek(fp->datfd,
1524         (off_t)(sizeof fp->tbl + (size_t)fp->pos * sizeof Seekpts[0]), 0);
1525     if ((read(fp->datfd, &Seekpts[0], sizeof Seekpts[0]) < 0) ||
1526         (read(fp->datfd, &Seekpts[1], sizeof Seekpts[1]) < 0))
1527     {
1528         exit(1);
1529     }
1530     Seekpts[0] = (int32_t)ntohl((uint32_t)Seekpts[0]);
1531     Seekpts[1] = (int32_t)ntohl((uint32_t)Seekpts[1]);
1532 }
1533
1534 /*
1535  * open_fp:
1536  *      Assocatiate a FILE * with the given FILEDESC.
1537  */
1538 static void open_fp(FILEDESC *fp)
1539 {
1540     if (!fp->inf && !(fp->inf = fdopen(fp->fd, "r")))
1541     {
1542         perror(fp->path);
1543         exit(1);
1544     }
1545 }
1546
1547 #ifndef NO_REGEX
1548 /*
1549  * maxlen_in_list
1550  *      Return the maximum fortune len in the file list.
1551  */
1552 static int maxlen_in_list(FILEDESC *list)
1553 {
1554     FILEDESC *fp;
1555     int len, maxlen = 0;
1556
1557     for (fp = list; fp; fp = fp->next)
1558     {
1559         if (fp->child)
1560         {
1561             if ((len = maxlen_in_list(fp->child)) > maxlen)
1562                 maxlen = len;
1563         }
1564         else
1565         {
1566             get_tbl(fp);
1567             if ((int)fp->tbl.str_longlen > maxlen)
1568             {
1569                 maxlen = (int)fp->tbl.str_longlen;
1570             }
1571         }
1572     }
1573     return maxlen;
1574 }
1575
1576 /*
1577  * matches_in_list
1578  *      Print out the matches from the files in the list.
1579  */
1580 static void matches_in_list(FILEDESC *list)
1581 {
1582     unsigned char *sp;
1583     unsigned char *p; /* -allover */
1584     unsigned char ch; /* -allover */
1585     FILEDESC *fp;
1586     int in_file, nchar;
1587     char *output;
1588
1589     for (fp = list; fp; fp = fp->next)
1590     {
1591         if (fp->child)
1592         {
1593             matches_in_list(fp->child);
1594             continue;
1595         }
1596         DPRINTF(1, (stderr, "searching in %s\n", fp->path));
1597         open_fp(fp);
1598         sp = Fortbuf;
1599         in_file = false;
1600         while (fgets((char *)sp, Fort_len, fp->inf))
1601         {
1602             if (!STR_ENDSTRING(sp, fp->tbl))
1603             {
1604                 sp += strlen((const char *)sp);
1605             }
1606             else
1607             {
1608                 *sp = '\0';
1609                 nchar = (int)(sp - Fortbuf);
1610
1611                 if (fp->utf8_charset && (!No_recode))
1612                 {
1613 #ifdef WITH_RECODE
1614                     output = recode_string(request, (const char *)Fortbuf);
1615 #else
1616                     output = strdup((const char *)Fortbuf);
1617 #endif
1618                 }
1619                 else
1620                 {
1621                     output = (char *)Fortbuf;
1622                 }
1623                 /* Should maybe rot13 Fortbuf -allover */
1624
1625                 if (fp->tbl.str_flags & STR_ROTATED)
1626                 {
1627                     for (p = (unsigned char *)output; (ch = *p); ++p)
1628                     {
1629                         if (isupper(ch) && isascii(ch))
1630                             *p = 'A' + (ch - 'A' + 13) % 26;
1631                         else if (islower(ch) && isascii(ch))
1632                             *p = 'a' + (ch - 'a' + 13) % 26;
1633                     }
1634                 }
1635
1636                 DPRINTF(1, (stdout, "nchar = %d\n", nchar));
1637                 if ((nchar < SLEN || !Short_only) &&
1638                     (nchar > SLEN || !Long_only) && RE_EXEC(output))
1639                 {
1640                     if (!in_file)
1641                     {
1642                         fprintf(
1643                             stderr, "(%s)\n%c\n", fp->name, fp->tbl.str_delim);
1644                         Found_one = true;
1645                         in_file = true;
1646                     }
1647                     fputs(output, stdout);
1648                     printf("%c\n", fp->tbl.str_delim);
1649                 }
1650
1651                 if (fp->utf8_charset && (!No_recode))
1652                 {
1653                     free(output);
1654                     output = NULL;
1655                 }
1656
1657                 sp = Fortbuf;
1658             }
1659         }
1660     }
1661 }
1662
1663 /*
1664  * find_matches:
1665  *      Find all the fortunes which match the pattern we've been given.
1666  */
1667 static int find_matches(void)
1668 {
1669     Fort_len = maxlen_in_list(File_list);
1670     DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
1671     /* extra length, "%\n" is appended */
1672     Fortbuf = do_malloc((unsigned int)Fort_len + 10);
1673
1674     Found_one = false;
1675     matches_in_list(File_list);
1676     return Found_one;
1677     /* NOTREACHED */
1678 }
1679 #endif /* NO_REGEX */
1680
1681 static void display(FILEDESC *fp)
1682 {
1683     char *p, ch;
1684     unsigned char line[BUFSIZ];
1685
1686     open_fp(fp);
1687     fseek(fp->inf, (long)Seekpts[0], SEEK_SET);
1688     if (Show_filename)
1689         printf("(%s)\n%%\n", fp->name);
1690     for (Fort_len = 0; fgets((char *)line, sizeof line, fp->inf) &&
1691                        !STR_ENDSTRING(line, fp->tbl);
1692          Fort_len++)
1693     {
1694         if (fp->tbl.str_flags & STR_ROTATED)
1695         {
1696             for (p = (char *)line; (ch = *p); ++p)
1697             {
1698                 if (isupper(ch) && isascii(ch))
1699                     *p = 'A' + (ch - 'A' + 13) % 26;
1700                 else if (islower(ch) && isascii(ch))
1701                     *p = 'a' + (ch - 'a' + 13) % 26;
1702             }
1703         }
1704         if (fp->utf8_charset && (!No_recode))
1705         {
1706             char *output;
1707 #ifdef WITH_RECODE
1708             output = recode_string(request, (const char *)line);
1709 #else
1710             output = strdup((const char *)line);
1711 #endif
1712             fputs(output, stdout);
1713             free(output);
1714         }
1715         else
1716             fputs((char *)line, stdout);
1717     }
1718     fflush(stdout);
1719 }
1720
1721 /*
1722  * fortlen:
1723  *      Return the length of the fortune.
1724  */
1725 static int fortlen(void)
1726 {
1727     int nchar;
1728     char line[BUFSIZ];
1729
1730     if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
1731         nchar = (Seekpts[1] - Seekpts[0]) - 2; /* for %^J delimiter */
1732     else
1733     {
1734         open_fp(Fortfile);
1735         fseek(Fortfile->inf, (long)Seekpts[0], SEEK_SET);
1736         nchar = 0;
1737         while (fgets(line, sizeof line, Fortfile->inf) &&
1738                !STR_ENDSTRING(line, Fortfile->tbl))
1739             nchar += strlen(line);
1740     }
1741     Fort_len = nchar;
1742     return nchar;
1743 }
1744
1745 static int mymax(int i, int j) { return (i >= j ? i : j); }
1746
1747 static void free_desc(FILEDESC *ptr)
1748 {
1749     while (ptr)
1750     {
1751         free_desc(ptr->child);
1752         free(ptr->datfile);
1753         free(ptr->posfile);
1754         free(ptr->name);
1755         free(ptr->path);
1756         if (ptr->inf)
1757         {
1758             fclose(ptr->inf);
1759             ptr->inf = NULL;
1760         }
1761         FILEDESC *next = ptr->next;
1762         free(ptr);
1763         ptr = next;
1764     }
1765 }
1766
1767 int main(int ac, char *av[])
1768 {
1769     const char *ctype;
1770     int exit_code = 0;
1771     env_lang = getenv("LC_ALL");
1772     if (!env_lang)
1773         env_lang = getenv("LC_MESSAGES");
1774     if (!env_lang)
1775         env_lang = getenv("LANGUAGE");
1776     if (!env_lang)
1777         env_lang = getenv("LANG");
1778 #ifdef _WIN32
1779     if (!env_lang)
1780     {
1781         env_lang = "en";
1782     }
1783 #endif
1784
1785 #ifndef DONT_CALL_GETARGS
1786     getargs(ac, av);
1787 #endif
1788
1789 #ifdef WITH_RECODE
1790     outer = recode_new_outer(true);
1791     request = recode_new_request(outer);
1792 #endif
1793
1794     setlocale(LC_ALL, "");
1795 #ifdef _WIN32
1796     ctype = "C";
1797 #else
1798     ctype = nl_langinfo(CODESET);
1799     if (!ctype || !*ctype)
1800     {
1801         ctype = "C";
1802     }
1803     else if (strcmp(ctype, "ANSI_X3.4-1968") == 0)
1804     {
1805         ctype = "ISO-8859-1";
1806     }
1807 #endif
1808
1809 #ifdef WITH_RECODE
1810     const size_t do_len = strlen(ctype) + 7 + 1;
1811     char *crequest = do_malloc(do_len + 1);
1812     snprintf(crequest, do_len, "UTF-8..%s", ctype);
1813     recode_scan_request(request, crequest);
1814     free(crequest);
1815 #endif
1816
1817 #ifndef NO_REGEX
1818     if (Match)
1819     {
1820         exit_code = (find_matches() != 0);
1821         regfree(&Re_pat);
1822         goto cleanup;
1823     }
1824 #endif
1825     init_prob();
1826     if (Find_files)
1827     {
1828         sum_noprobs(File_list);
1829         if (Equal_probs)
1830             calc_equal_probs();
1831         print_list(File_list, 0);
1832     }
1833     else
1834     {
1835         srandom((unsigned int)(time((time_t *)NULL) + getpid()));
1836         do
1837         {
1838             get_fort();
1839         } while ((Short_only && fortlen() > SLEN) ||
1840                  (Long_only && fortlen() <= SLEN));
1841
1842         display(Fortfile);
1843
1844         if (Wait)
1845         {
1846             fortlen();
1847             sleep((unsigned int)mymax(Fort_len / CPERS, MINW));
1848         }
1849     }
1850 cleanup:
1851 #ifdef WITH_RECODE
1852     recode_delete_request(request);
1853     recode_delete_outer(outer);
1854 #endif
1855
1856     /* Free the File_list */
1857     free_desc(File_list);
1858     free(Fortbuf);
1859     exit(exit_code);
1860 }