]> granicus.if.org Git - fortune-mod/blob - fortune-mod/fortune/fortune.c
fix some C compiler warnings on windows
[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 const char *env_lang = NULL;
143
144 static bool Found_one = false;   /* 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     char *new_buf;
316
317     size_t cnt = 1; /* allow for '\0' */
318     for (sp = orig; *sp != '\0'; sp++)
319     {
320         const size_t prev_cnt = cnt;
321         if (isalpha(*sp))
322             cnt += 4;
323         else
324             cnt++;
325         if (prev_cnt >= cnt)
326         {
327             fprintf(stderr, "%s",
328                 "pattern too long for ignoring case; overflow!\n");
329             exit(1);
330         }
331     }
332     if (!(new_buf = malloc(cnt)))
333     {
334         fprintf(stderr, "%s", "pattern too long for ignoring case\n");
335         exit(1);
336     }
337
338     for (sp = new_buf; *orig != '\0'; orig++)
339     {
340         if (islower(*orig))
341         {
342             *sp++ = '[';
343             *sp++ = *orig;
344             *sp++ = (char)toupper(*orig);
345             *sp++ = ']';
346         }
347         else if (isupper(*orig))
348         {
349             *sp++ = '[';
350             *sp++ = *orig;
351             *sp++ = (char)tolower(*orig);
352             *sp++ = ']';
353         }
354         else
355             *sp++ = *orig;
356     }
357     *sp = '\0';
358     return new_buf;
359 }
360 #endif /* NO_REGEX */
361
362 /*
363  * do_malloc:
364  *      Do a malloc, checking for NULL return.
365  */
366 static void *do_malloc(size_t size)
367 {
368     void *new_buf = malloc(size);
369
370     if (!new_buf)
371     {
372         (void)fprintf(stderr, "%s", "fortune: out of memory.\n");
373         exit(1);
374     }
375     return new_buf;
376 }
377
378 /*
379  * new_fp:
380  *      Return a pointer to an initialized new FILEDESC.
381  */
382 static FILEDESC *new_fp(void)
383 {
384     FILEDESC *fp;
385
386     fp = (FILEDESC *)do_malloc(sizeof *fp);
387     fp->datfd = -1;
388     fp->pos = POS_UNKNOWN;
389     fp->inf = NULL;
390     fp->fd = -1;
391     fp->percent = NO_PROB;
392     fp->read_tbl = false;
393     fp->tbl.str_version = 0;
394     fp->tbl.str_numstr = 0;
395     fp->tbl.str_longlen = 0;
396     fp->tbl.str_shortlen = 0;
397     fp->tbl.str_flags = 0;
398     fp->tbl.stuff[0] = 0;
399     fp->tbl.stuff[1] = 0;
400     fp->tbl.stuff[2] = 0;
401     fp->tbl.stuff[3] = 0;
402     fp->next = NULL;
403     fp->prev = NULL;
404     fp->child = NULL;
405     fp->parent = NULL;
406     fp->datfile = NULL;
407     fp->posfile = NULL;
408     return fp;
409 }
410
411 #ifdef MYDEBUG
412 static inline void debugprint(const char *msg, ...)
413 {
414     va_list ap;
415
416     va_start(ap, msg);
417     vfprintf(stderr, msg, ap);
418     fflush(stderr);
419     va_end(ap);
420 }
421 #else
422 #define debugprint(format, ...)                                                \
423     {                                                                          \
424     }
425 #endif
426 /*
427  * is_dir:
428  *      Return true if the file is a directory, false otherwise.
429  */
430 static int is_dir(const char *const file)
431 {
432     struct stat sbuf;
433
434     if (stat(file, &sbuf) < 0)
435     {
436         debugprint("is_dir failed for file=<%s>\n", file);
437         return -1;
438     }
439     const bool ret = (S_ISDIR(sbuf.st_mode) ? true : false);
440     debugprint("is_dir for file=<%s> gave ret=<%d>\n", file, ret);
441     return ret;
442 }
443
444 /*
445  * is_existant:
446  *      Return true if the file exists, false otherwise.
447  */
448 static int is_existant(char *file)
449 {
450     struct stat staat;
451
452     if (stat(file, &staat) == 0)
453         return true;
454     switch (errno)
455     {
456     case ENOENT:
457     case ENOTDIR:
458         return false;
459     default:
460         perror("fortune: bad juju in is_existant");
461         exit(1);
462     }
463 }
464
465 /*
466  * is_fortfile:
467  *      Return true if the file is a fortune database file.  We try and
468  *      exclude files without reading them if possible to avoid
469  *      overhead.  Files which start with ".", or which have "illegal"
470  *      suffixes, as contained in suflist[], are ruled out.
471  */
472 static int is_fortfile(const char *const file, char **datp)
473 {
474     const char *sp = strrchr(file, '/');
475     static const char *suflist[] = {/* list of "illegal" suffixes" */
476         "dat", "pos", "c", "h", "p", "i", "f", "pas", "ftn", "ins.c", "ins,pas",
477         "ins.ftn", "sml", NULL};
478
479     DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
480
481     if (!sp)
482         sp = file;
483     else
484         sp++;
485     if (*sp == '.')
486     {
487         DPRINTF(2, (stderr, "%s", "false (file starts with '.')\n"));
488         return false;
489     }
490     if ((sp = strrchr(sp, '.')))
491     {
492         sp++;
493         for (int i = 0; suflist[i]; ++i)
494             if (strcmp(sp, suflist[i]) == 0)
495             {
496                 DPRINTF(2, (stderr, "false (file has suffix \".%s\")\n", sp));
497                 return false;
498             }
499     }
500
501     const size_t do_len = (strlen(file) + 6);
502     char *const datfile = do_malloc(do_len + 1);
503     snprintf(datfile, do_len, "%s.dat", file);
504     if (access(datfile, R_OK) < 0)
505     {
506         free(datfile);
507         DPRINTF(2, (stderr, "%s", "false (no \".dat\" file)\n"));
508         return false;
509     }
510     if (datp)
511         *datp = datfile;
512     else
513         free(datfile);
514     DPRINTF(2, (stderr, "%s", "true\n"));
515     return true;
516 }
517
518 static bool path_is_absolute(const char *const path)
519 {
520     if (path[0] == '/')
521     {
522         return true;
523     }
524 #ifdef _WIN32
525     if (isalpha(path[0]) && path[1] == ':' && path[2] == '/')
526     {
527         return true;
528     }
529 #endif
530     return false;
531 }
532 /*
533  * add_file:
534  *      Add a file to the file list.
535  */
536 static int add_file(int percent, const char *file, const char *dir,
537     FILEDESC **head, FILEDESC **tail, FILEDESC *parent)
538 {
539     FILEDESC *fp;
540     int fd = -1;
541     char *path;
542     char *sp;
543     struct stat statbuf;
544
545     if (!dir)
546     {
547         path = strdup(file);
548     }
549     else
550     {
551         const size_t do_len = (strlen(dir) + strlen(file) + 2);
552         path = do_malloc(do_len + 1);
553         snprintf(path, do_len, "%s/%s", dir, file);
554     }
555     if (*path == '/' &&
556         !is_existant(path)) /* If doesn't exist, don't do anything. */
557     {
558         free(path);
559         return false;
560     }
561     const int isdir = is_dir(path);
562     if ((isdir > 0 && parent) || (isdir < 0))
563     {
564         free(path);
565         return false; /* don't recurse */
566     }
567
568     DPRINTF(1, (stderr, "trying to add file \"%s\"\n", path));
569     if ((
570 #ifdef _WIN32
571             (!isdir) &&
572 #endif
573             ((fd = open(path, O_RDONLY | O_BINARY)) < 0)) ||
574         !path_is_absolute(path))
575     {
576         debugprint("sarahhhhh fd=%d path=<%s> dir=<%s> file=<%s> percent=%d\n",
577             fd, path, dir, file, percent);
578         bool found = false;
579         if (!dir && (!strchr(file, '/')))
580         {
581             if (((sp = strrchr(file, '-')) != NULL) && (strcmp(sp, "-o") == 0))
582             {
583 #define CALL__add_file(dir) add_file(percent, file, dir, head, tail, parent)
584 #define COND_CALL__add_file(loc_dir, dir)                                      \
585     ((!strcmp((loc_dir), (dir))) ? 0 : CALL__add_file(dir))
586                 /* BSD-style '-o' offensive file suffix */
587                 *sp = '\0';
588                 found = CALL__add_file(LOCOFFDIR) ||
589                         COND_CALL__add_file(LOCOFFDIR, OFFDIR);
590                 /* put the suffix back in for better identification later */
591                 *sp = '-';
592             }
593             else if (All_forts)
594                 found =
595                     (CALL__add_file(LOCFORTDIR) || CALL__add_file(LOCOFFDIR) ||
596                         COND_CALL__add_file(LOCFORTDIR, FORTDIR) ||
597                         COND_CALL__add_file(LOCOFFDIR, OFFDIR));
598             else if (Offend)
599                 found = (CALL__add_file(LOCOFFDIR) ||
600                          COND_CALL__add_file(LOCOFFDIR, OFFDIR));
601             else
602                 found = (CALL__add_file(LOCFORTDIR) ||
603                          COND_CALL__add_file(LOCFORTDIR, FORTDIR));
604 #undef COND_CALL__add_file
605 #undef CALL__add_file
606         }
607         if (!found && !parent && !dir)
608         { /* don't display an error when trying language specific files */
609             if (env_lang)
610             {
611                 char llang[512];
612                 char langdir[1024];
613                 int ret = 0;
614
615                 strncpy(llang, env_lang, sizeof(llang));
616                 llang[sizeof(llang) - 1] = '\0';
617                 char *lang = llang;
618
619                 /* the language string can be like "es:fr_BE:ga" */
620                 while (!ret && lang && (*lang))
621                 {
622                     char *p = strchr(lang, ':');
623                     if (p)
624                         *p++ = '\0';
625                     snprintf(langdir, sizeof(langdir), "%s/%s", FORTDIR, lang);
626
627                     if (strncmp(path, lang, 2) == 0)
628                         ret = 1;
629                     else if (strncmp(path, langdir, strlen(FORTDIR) + 3) == 0)
630                         ret = 1;
631                     lang = p;
632                 }
633                 if (!ret)
634                 {
635                     debugprint("moshe\n");
636                     perror(path);
637                 }
638             }
639             else
640             {
641                 debugprint("abe\n");
642                 perror(path);
643             }
644         }
645
646         free(path);
647         path = NULL;
648         return found;
649     }
650
651     DPRINTF(2, (stderr, "path = \"%s\"\n", path));
652
653     fp = new_fp();
654     fp->fd = fd;
655     fp->percent = percent;
656
657     fp->name = strdup(file);
658     fp->path = strdup(path);
659
660     // FIXME
661     fp->utf8_charset = false;
662     const size_t do_len = (strlen(path) + 5);
663     char *testpath = do_malloc(do_len + 1);
664     snprintf(testpath, do_len, "%s.u8", path);
665     //    fprintf(stderr, "State mal: %s\n", testpath);
666     if (stat(testpath, &statbuf) == 0)
667         fp->utf8_charset = true;
668
669     free(testpath);
670     testpath = NULL;
671     //    fprintf(stderr, "Is utf8?: %i\n", fp->utf8_charset );
672
673     fp->parent = parent;
674
675     if ((isdir && !add_dir(fp)) || (!isdir && !is_fortfile(path, &fp->datfile)))
676     {
677         if (!parent)
678             fprintf(
679                 stderr, "fortune:%s not a fortune file or directory\n", path);
680         free(path);
681         path = NULL;
682         free(fp->datfile);
683         free(fp->posfile);
684         free(fp->name);
685         free(fp->path);
686         if (fp->fd >= 0)
687             close(fp->fd);
688         free(fp);
689         return false;
690     }
691
692     /* This is a hack to come around another hack - add_dir returns success
693      * if the directory is allowed to be empty, but we can not handle an
694      * empty directory... */
695     if (isdir && fp->num_children == 0)
696     {
697         free(path);
698         path = NULL;
699         free(fp->datfile);
700         free(fp->posfile);
701         free(fp->name);
702         free(fp->path);
703         if (fp->fd >= 0)
704             close(fp->fd);
705         free(fp);
706         return true;
707     }
708     /* End hack. */
709
710     if (!(*head))
711         *head = *tail = fp;
712     else if (fp->percent == NO_PROB)
713     {
714         (*tail)->next = fp;
715         fp->prev = *tail;
716         *tail = fp;
717     }
718     else
719     {
720         (*head)->prev = fp;
721         fp->next = *head;
722         *head = fp;
723     }
724
725     free(path);
726     path = NULL;
727
728     return true;
729 }
730
731 static int names_compare(const void *a, const void *b)
732 {
733     return strcmp(*(const char *const *)a, *(const char *const *)b);
734 }
735 /*
736  * add_dir:
737  *      Add the contents of an entire directory.
738  */
739 int add_dir(FILEDESC *fp)
740 {
741     DIR *dir;
742     struct dirent *dirent;
743     char **names;
744     size_t i, count_names, max_count_names;
745
746     close(fp->fd);
747     fp->fd = -1;
748     if (!(dir = opendir(fp->path)))
749     {
750         debugprint("yonah\n");
751         perror(fp->path);
752         return false;
753     }
754     FILEDESC *tailp = NULL;
755     DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
756     fp->num_children = 0;
757     max_count_names = 200;
758     count_names = 0;
759     names = malloc(sizeof(names[0]) * max_count_names);
760     if (!names)
761     {
762         debugprint("zach\n");
763         perror("Out of RAM!");
764         exit(-1);
765     }
766     while ((dirent = readdir(dir)))
767     {
768         if (dirent->d_name[0] == 0)
769             continue;
770         char *name = strdup(dirent->d_name);
771         if (count_names == max_count_names)
772         {
773             max_count_names += 200;
774             names = realloc(names, sizeof(names[0]) * max_count_names);
775             if (!names)
776             {
777                 debugprint("rebecca\n");
778                 perror("Out of RAM!");
779                 exit(-1);
780             }
781         }
782         names[count_names++] = name;
783     }
784     closedir(dir);
785     qsort(names, count_names, sizeof(names[0]), names_compare);
786
787     for (i = 0; i < count_names; ++i)
788     {
789         if (add_file(NO_PROB, names[i], fp->path, &fp->child, &tailp, fp))
790         {
791             fp->num_children++;
792         }
793         free(names[i]);
794     }
795     free(names);
796     dir = NULL;
797     if (fp->num_children == 0)
798     {
799         /*
800          * Only the local fortune dir and the local offensive dir are
801          * allowed to be empty.
802          *  - Brian Bassett (brianb@debian.org) 1999/07/31
803          */
804         if (strcmp(LOCFORTDIR, fp->path) == 0 ||
805             strcmp(LOCOFFDIR, fp->path) == 0)
806         {
807             return true;
808         }
809         fprintf(
810             stderr, "fortune: %s: No fortune files in directory.\n", fp->path);
811         return false;
812     }
813     return true;
814 }
815
816 /*
817  * form_file_list:
818  *      Form the file list from the file specifications.
819  */
820
821 static int top_level__add_file(const char *dirpath)
822 {
823     return add_file(NO_PROB, dirpath, NULL, &File_list, &File_tail, NULL);
824 }
825
826 static int cond_top_level__add_file(
827     const char *dirpath, const char *possible_dup)
828 {
829     if (!strcmp(dirpath, possible_dup))
830     {
831         return 0;
832     }
833     return top_level__add_file(dirpath);
834 }
835
836 static int cond_top_level__LOCFORTDIR(void)
837 {
838     return cond_top_level__add_file(FORTDIR, LOCFORTDIR);
839 }
840
841 static int cond_top_level__OFFDIR(void)
842 {
843     return cond_top_level__add_file(OFFDIR, LOCOFFDIR);
844 }
845
846 static int top_level_LOCFORTDIR(void)
847 {
848     return (top_level__add_file(LOCFORTDIR) | cond_top_level__LOCFORTDIR());
849 }
850
851 static int form_file_list(char **files, int file_cnt)
852 {
853     int i, percent;
854     char *sp;
855     char langdir[1024];
856     char fullpathname[512], locpathname[512];
857
858     if (file_cnt == 0)
859     {
860         if (All_forts)
861         {
862             return (top_level__add_file(LOCFORTDIR) |
863                     top_level__add_file(LOCOFFDIR) |
864                     cond_top_level__LOCFORTDIR() | cond_top_level__OFFDIR());
865         }
866         else if (Offend)
867         {
868             return (top_level__add_file(LOCOFFDIR) | cond_top_level__OFFDIR());
869         }
870         else
871         {
872             if (env_lang)
873             {
874                 char *lang;
875                 char llang[512];
876                 int ret = 0;
877                 char *p;
878
879                 strncpy(llang, env_lang, sizeof(llang));
880                 llang[sizeof(llang) - 1] = '\0';
881                 lang = llang;
882
883                 /* the language string can be like "es:fr_BE:ga" */
884                 while (lang && (*lang))
885                 {
886                     p = strchr(lang, ':');
887                     if (p)
888                         *p++ = '\0';
889
890                     /* first try full locale */
891                     ret = add_file(
892                         NO_PROB, lang, NULL, &File_list, &File_tail, NULL);
893
894                     /* if not try language name only (two first chars) */
895                     if (!ret)
896                     {
897                         char ll[3];
898
899                         strncpy(ll, lang, 2);
900                         ll[2] = '\0';
901                         ret = add_file(
902                             NO_PROB, ll, NULL, &File_list, &File_tail, NULL);
903                     }
904
905                     /* if we have found one we have finished */
906                     if (ret)
907                         return ret;
908                     lang = p;
909                 }
910                 /* default */
911                 return top_level_LOCFORTDIR();
912             }
913             else
914             {
915                 /* no locales available, use default */
916                 return top_level_LOCFORTDIR();
917             }
918         }
919     }
920
921     for (i = 0; i < file_cnt; i++)
922     {
923         percent = NO_PROB;
924         if (!isdigit(files[i][0]))
925             sp = files[i];
926         else
927         {
928             const int MAX_PERCENT = 100;
929             bool percent_has_overflowed = false;
930             percent = 0;
931             for (sp = files[i]; isdigit(*sp); sp++)
932             {
933                 percent = percent * 10 + *sp - '0';
934                 percent_has_overflowed = (percent > MAX_PERCENT);
935                 if (percent_has_overflowed)
936                 {
937                     break;
938                 }
939             }
940             if (percent_has_overflowed || (percent > 100))
941             {
942                 fprintf(stderr, "percentages must be <= 100\n");
943                 fprintf(stderr,
944                     "Overflow percentage detected at argument \"%s\"!\n",
945                     files[i]);
946                 ErrorMessage = true;
947                 return false;
948             }
949             if (percent < 0)
950             {
951                 fprintf(stderr,
952                     "Overflow percentage detected at argument \"%s\"!\n",
953                     files[i]);
954                 ErrorMessage = true;
955                 return false;
956             }
957             if (*sp == '.')
958             {
959                 fprintf(stderr, "%s", "percentages must be integers\n");
960                 ErrorMessage = true;
961                 return false;
962             }
963             /*
964              * If the number isn't followed by a '%', then
965              * it was not a percentage, just the first part
966              * of a file name which starts with digits.
967              */
968             if (*sp != '%')
969             {
970                 percent = NO_PROB;
971                 sp = files[i];
972             }
973             else if (*++sp == '\0')
974             {
975                 if (++i >= file_cnt)
976                 {
977                     fprintf(stderr, "%s", "percentages must precede files\n");
978                     ErrorMessage = true;
979                     return false;
980                 }
981                 sp = files[i];
982             }
983         }
984         if (strcmp(sp, "all") == 0)
985         {
986             snprintf(fullpathname, sizeof(fullpathname), "%s", FORTDIR);
987             snprintf(locpathname, sizeof(locpathname), "%s", LOCFORTDIR);
988         }
989         /* if it isn't an absolute path or relative to . or ..
990            make it an absolute path relative to FORTDIR */
991         else
992         {
993             if (strncmp(sp, "/", 1) != 0 && strncmp(sp, "./", 2) != 0 &&
994                 strncmp(sp, "../", 3) != 0)
995             {
996                 snprintf(
997                     fullpathname, sizeof(fullpathname), "%s/%s", FORTDIR, sp);
998                 snprintf(
999                     locpathname, sizeof(locpathname), "%s/%s", LOCFORTDIR, sp);
1000             }
1001             else
1002             {
1003                 snprintf(fullpathname, sizeof(fullpathname), "%s", sp);
1004                 snprintf(locpathname, sizeof(locpathname), "%s", sp);
1005             }
1006         }
1007
1008         if (env_lang)
1009         {
1010             char llang[512];
1011             int ret = 0;
1012
1013             strncpy(llang, env_lang, sizeof(llang));
1014             llang[sizeof(llang) - 1] = '\0';
1015             char *lang = llang;
1016
1017             /* the language string can be like "es:fr_BE:ga" */
1018             while (!ret && lang && (*lang))
1019             {
1020                 char *p = strchr(lang, ':');
1021                 if (p)
1022                     *p++ = '\0';
1023
1024                 /* first try full locale */
1025                 snprintf(
1026                     langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, lang, sp);
1027                 ret = add_file(
1028                     percent, langdir, NULL, &File_list, &File_tail, NULL);
1029
1030                 /* if not try language name only (two first chars) */
1031                 if (!ret)
1032                 {
1033                     char ll[3];
1034
1035                     strncpy(ll, lang, 2);
1036                     ll[2] = '\0';
1037                     snprintf(
1038                         langdir, sizeof(langdir), "%s/%s/%s", FORTDIR, ll, sp);
1039                     ret = add_file(
1040                         percent, langdir, NULL, &File_list, &File_tail, NULL);
1041                 }
1042
1043                 lang = p;
1044             }
1045             /* default */
1046             if (!ret)
1047                 ret = add_file(
1048                     percent, fullpathname, NULL, &File_list, &File_tail, NULL);
1049             if (!ret &&
1050                 strncmp(fullpathname, locpathname, sizeof(fullpathname)))
1051                 ret = add_file(
1052                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1053
1054             if (!ret)
1055             {
1056                 snprintf(locpathname, sizeof(locpathname), "%s/%s",
1057                     getenv("PWD"), sp);
1058
1059                 ret = add_file(
1060                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1061             }
1062             if (!ret)
1063             {
1064                 return false;
1065             }
1066             if (strncmp(fullpathname, locpathname, sizeof(fullpathname)) &&
1067                 strcmp(sp, "all") == 0)
1068             {
1069                 add_file(
1070                     percent, locpathname, NULL, &File_list, &File_tail, NULL);
1071             }
1072         }
1073         else if (!add_file(
1074                      percent, fullpathname, NULL, &File_list, &File_tail, NULL))
1075             return false;
1076     }
1077     return true;
1078 }
1079
1080 /*
1081  *    This routine evaluates the arguments on the command line
1082  */
1083 static void getargs(int argc, char **argv)
1084 {
1085     bool ignore_case = false;
1086
1087 #ifndef NO_REGEX
1088     char *pat = NULL;
1089
1090 #endif /* NO_REGEX */
1091     int ch;
1092
1093 #ifdef DEBUG
1094 #define DEBUG_GETOPT "D"
1095 #else
1096 #define DEBUG_GETOPT
1097 #endif
1098
1099 #ifdef NO_OFFENSIVE
1100 #define OFFENSIVE_GETOPT
1101 #else
1102 #define OFFENSIVE_GETOPT "o"
1103 #endif
1104
1105     while ((ch = getopt(argc, argv,
1106                 "ac" DEBUG_GETOPT "efilm:n:" OFFENSIVE_GETOPT "suvw")) != EOF)
1107         switch (ch)
1108         {
1109         case 'a': /* any fortune */
1110             All_forts = true;
1111             break;
1112 #ifdef DEBUG
1113         case 'D':
1114             Debug++;
1115             break;
1116 #endif /* DEBUG */
1117         case 'e':
1118             Equal_probs = true; /* scatter un-allocted prob equally */
1119             break;
1120         case 'f': /* find fortune files */
1121             Find_files = true;
1122             break;
1123         case 'l': /* long ones only */
1124             Long_only = true;
1125             Short_only = false;
1126             break;
1127         case 'n':
1128             SLEN = atoi(optarg);
1129             break;
1130 #ifndef NO_OFFENSIVE
1131         case 'o': /* offensive ones only */
1132             Offend = true;
1133             break;
1134 #endif
1135         case 's': /* short ones only */
1136             Short_only = true;
1137             Long_only = false;
1138             break;
1139         case 'w': /* give time to read */
1140             Wait = true;
1141             break;
1142 #ifdef NO_REGEX
1143         case 'i': /* case-insensitive match */
1144         case 'm': /* dump out the fortunes */
1145             (void)fprintf(stderr, "%s",
1146                 "fortune: can't match fortunes on this system (Sorry)\n");
1147             exit(0);
1148 #else             /* NO_REGEX */
1149         case 'm': /* dump out the fortunes */
1150             Match = true;
1151             pat = optarg;
1152             break;
1153         case 'i': /* case-insensitive match */
1154             ignore_case = true;
1155             break;
1156 #endif            /* NO_REGEX */
1157         case 'u': /* Don't recode the fortune */
1158             No_recode = true;
1159             break;
1160         case 'v':
1161             (void)printf("%s\n", program_version());
1162             exit(0);
1163         case 'c':
1164             Show_filename = true;
1165             break;
1166         case '?':
1167         default:
1168             usage();
1169         }
1170     argc -= optind;
1171     argv += optind;
1172
1173     if (!form_file_list(argv, argc))
1174     {
1175         if (!ErrorMessage)
1176         {
1177             fprintf(stderr, "%s", "No fortunes found\n");
1178         }
1179         exit(1); /* errors printed through form_file_list() */
1180     }
1181 #ifdef DEBUG
1182 /*      if (Debug >= 1)
1183  * print_list(File_list, 0); */
1184 #endif /* DEBUG */
1185 /* If (Find_files) print_list() moved to main */
1186 #ifndef NO_REGEX
1187     if (pat)
1188     {
1189         if (ignore_case)
1190             pat = conv_pat(pat);
1191         if (BAD_COMP(RE_COMP(pat)))
1192         {
1193             fprintf(stderr, "bad pattern: %s\n", pat);
1194             exit(1);
1195         }
1196         if (ignore_case)
1197         {
1198             free(pat);
1199         }
1200     }
1201 #endif /* NO_REGEX */
1202 }
1203
1204 /*
1205  * init_prob:
1206  *      Initialize the fortune probabilities.
1207  */
1208 static void init_prob(void)
1209 {
1210     FILEDESC *fp;
1211     int percent = 0, num_noprob = 0, frac;
1212
1213     /*
1214      * Distribute the residual probability (if any) across all
1215      * files with unspecified probability (i.e., probability of 0)
1216      * (if any).
1217      */
1218     FILEDESC *last = NULL;
1219     for (fp = File_tail; fp; fp = fp->prev)
1220         if (fp->percent == NO_PROB)
1221         {
1222             num_noprob++;
1223             if (Equal_probs)
1224                 last = fp;
1225         }
1226         else
1227             percent += fp->percent;
1228     DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's\n",
1229                    percent, num_noprob));
1230     if (percent > 100)
1231     {
1232         fprintf(stderr, "fortune: probabilities sum to %d%%!\n", percent);
1233         exit(1);
1234     }
1235     else if (percent < 100 && num_noprob == 0)
1236     {
1237         fprintf(stderr,
1238             "fortune: no place to put residual probability (%d%%)\n", percent);
1239         exit(1);
1240     }
1241     else if (percent == 100 && num_noprob != 0)
1242     {
1243         fprintf(
1244             stderr, "fortune: no probability left to put in residual files\n");
1245         exit(1);
1246     }
1247     Spec_prob = percent; /* this is for -f when % is specified on cmd line */
1248     percent = 100 - percent;
1249     if (Equal_probs)
1250     {
1251         if (num_noprob != 0)
1252         {
1253             if (num_noprob > 1)
1254             {
1255                 frac = percent / num_noprob;
1256                 DPRINTF(1, (stderr, ", frac = %d%%", frac));
1257                 for (fp = File_tail; fp != last; fp = fp->prev)
1258                     if (fp->percent == NO_PROB)
1259                     {
1260                         fp->percent = frac;
1261                         percent -= frac;
1262                     }
1263             }
1264             last->percent = percent;
1265             DPRINTF(1, (stderr, ", residual = %d%%", percent));
1266         }
1267         else
1268         {
1269             DPRINTF(1, (stderr, ", %d%% distributed over remaining fortunes\n",
1270                            percent));
1271         }
1272     }
1273     DPRINTF(1, (stderr, "%s", "\n"));
1274
1275 #ifdef DEBUG
1276 /*      if (Debug >= 1)
1277  * print_list(File_list, 0); *//* Causes crash with new %% code */
1278 #endif
1279 }
1280
1281 /*
1282  * zero_tbl:
1283  *      Zero out the fields we care about in a tbl structure.
1284  */
1285 static void zero_tbl(STRFILE *tp)
1286 {
1287     tp->str_numstr = 0;
1288     tp->str_longlen = 0;
1289     tp->str_shortlen = (uint32_t)(-1);
1290 }
1291
1292 /*
1293  * sum_tbl:
1294  *      Merge the tbl data of t2 into t1.
1295  */
1296 static void sum_tbl(STRFILE *t1, STRFILE *t2)
1297 {
1298     t1->str_numstr += t2->str_numstr;
1299     if (t1->str_longlen < t2->str_longlen)
1300         t1->str_longlen = t2->str_longlen;
1301     if (t1->str_shortlen > t2->str_shortlen)
1302         t1->str_shortlen = t2->str_shortlen;
1303 }
1304
1305 /*
1306  * get_tbl:
1307  *      Get the tbl data file the datfile.
1308  */
1309 static void get_tbl(FILEDESC *fp)
1310 {
1311     int fd;
1312     FILEDESC *child;
1313
1314     if (fp->read_tbl)
1315         return;
1316     if (!(fp->child))
1317     {
1318         if ((fd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1319         {
1320             perror(fp->datfile);
1321             exit(1);
1322         }
1323         if (read(fd, &fp->tbl.str_version, sizeof fp->tbl.str_version) !=
1324             sizeof fp->tbl.str_version)
1325         {
1326             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1327             exit(1);
1328         }
1329         if (read(fd, &fp->tbl.str_numstr, sizeof fp->tbl.str_numstr) !=
1330             sizeof fp->tbl.str_numstr)
1331         {
1332             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1333             exit(1);
1334         }
1335         if (read(fd, &fp->tbl.str_longlen, sizeof fp->tbl.str_longlen) !=
1336             sizeof fp->tbl.str_longlen)
1337         {
1338             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1339             exit(1);
1340         }
1341         if (read(fd, &fp->tbl.str_shortlen, sizeof fp->tbl.str_shortlen) !=
1342             sizeof fp->tbl.str_shortlen)
1343         {
1344             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1345             exit(1);
1346         }
1347         if (read(fd, &fp->tbl.str_flags, sizeof fp->tbl.str_flags) !=
1348             sizeof fp->tbl.str_flags)
1349         {
1350             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1351             exit(1);
1352         }
1353         if (read(fd, &fp->tbl.stuff, sizeof fp->tbl.stuff) !=
1354             sizeof fp->tbl.stuff)
1355         {
1356             fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1357             exit(1);
1358         }
1359         fp->tbl.str_version = ntohl(fp->tbl.str_version);
1360         fp->tbl.str_numstr = ntohl(fp->tbl.str_numstr);
1361         fp->tbl.str_longlen = ntohl(fp->tbl.str_longlen);
1362         fp->tbl.str_shortlen = ntohl(fp->tbl.str_shortlen);
1363         fp->tbl.str_flags = ntohl(fp->tbl.str_flags);
1364         close(fd);
1365     }
1366     else
1367     {
1368         zero_tbl(&fp->tbl);
1369         for (child = fp->child; child; child = child->next)
1370         {
1371             get_tbl(child);
1372             sum_tbl(&fp->tbl, &child->tbl);
1373         }
1374     }
1375     fp->read_tbl = true;
1376 }
1377
1378 /*
1379  * sum_noprobs:
1380  *      Sum up all the noprob probabilities, starting with fp.
1381  */
1382 static void sum_noprobs(FILEDESC *fp)
1383 {
1384     static bool did_noprobs = false;
1385
1386     if (did_noprobs)
1387         return;
1388     zero_tbl(&Noprob_tbl);
1389     while (fp)
1390     {
1391         get_tbl(fp);
1392         /* This conditional should help us return correct values for -f
1393          * when a percentage is specified */
1394         if (fp->percent == NO_PROB)
1395             sum_tbl(&Noprob_tbl, &fp->tbl);
1396         fp = fp->next;
1397     }
1398     did_noprobs = true;
1399 }
1400
1401 /*
1402  * pick_child
1403  *      Pick a child from a chosen parent.
1404  */
1405 static FILEDESC *pick_child(FILEDESC *parent)
1406 {
1407     FILEDESC *fp;
1408     int choice;
1409
1410     if (Equal_probs)
1411     {
1412         choice = my_random(parent->num_children);
1413         DPRINTF(1, (stderr, "    choice = %d (of %d)\n", choice,
1414                        parent->num_children));
1415         for (fp = parent->child; choice--; fp = fp->next)
1416             continue;
1417         DPRINTF(1, (stderr, "    using %s\n", fp->name));
1418         return fp;
1419     }
1420     else
1421     {
1422         get_tbl(parent);
1423         choice = (int)(my_random(parent->tbl.str_numstr));
1424         DPRINTF(1, (stderr, "    choice = %d (of %ld)\n", choice,
1425                        parent->tbl.str_numstr));
1426         for (fp = parent->child; choice >= (int)fp->tbl.str_numstr;
1427              fp = fp->next)
1428         {
1429             choice -= fp->tbl.str_numstr;
1430             DPRINTF(1, (stderr, "\tskip %s, %ld (choice = %d)\n", fp->name,
1431                            fp->tbl.str_numstr, choice));
1432         }
1433         DPRINTF(
1434             1, (stderr, "    using %s, %ld\n", fp->name, fp->tbl.str_numstr));
1435         return fp;
1436     }
1437 }
1438
1439 /*
1440  * open_dat:
1441  *      Open up the dat file if we need to.
1442  */
1443 static void open_dat(FILEDESC *fp)
1444 {
1445     if (fp->datfd < 0 &&
1446         (fp->datfd = open(fp->datfile, O_RDONLY | O_BINARY)) < 0)
1447     {
1448         exit(1);
1449     }
1450 }
1451
1452 /*
1453  * get_pos:
1454  *      Get the position from the pos file, if there is one.  If not,
1455  *      return a random number.
1456  */
1457 static void get_pos(FILEDESC *fp)
1458 {
1459     assert(fp->read_tbl);
1460     if (fp->pos == POS_UNKNOWN)
1461     {
1462         fp->pos = (int32_t)(my_random(fp->tbl.str_numstr));
1463     }
1464     if (++(fp->pos) >= (int32_t)fp->tbl.str_numstr)
1465         fp->pos -= fp->tbl.str_numstr;
1466     DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, fp->pos));
1467 }
1468
1469 /*
1470  * get_fort:
1471  *      Get the fortune data file's seek pointer for the next fortune.
1472  */
1473 static void get_fort(void)
1474 {
1475     FILEDESC *fp;
1476     int choice;
1477
1478     if (!File_list->next || File_list->percent == NO_PROB)
1479         fp = File_list;
1480     else
1481     {
1482         choice = my_random(100);
1483         DPRINTF(1, (stderr, "choice = %d\n", choice));
1484         for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
1485             if (choice < fp->percent)
1486                 break;
1487             else
1488             {
1489                 choice -= fp->percent;
1490                 DPRINTF(1, (stderr, "    skip \"%s\", %d%% (choice = %d)\n",
1491                                fp->name, fp->percent, choice));
1492             }
1493         DPRINTF(1, (stderr, "using \"%s\", %d%% (choice = %d)\n", fp->name,
1494                        fp->percent, choice));
1495     }
1496     if (fp->percent != NO_PROB)
1497         get_tbl(fp);
1498     else
1499     {
1500         if (fp->next)
1501         {
1502             sum_noprobs(fp);
1503             choice = (int)(my_random(Noprob_tbl.str_numstr));
1504             DPRINTF(1, (stderr, "choice = %d (of %ld) \n", choice,
1505                            Noprob_tbl.str_numstr));
1506             while (choice >= (int)fp->tbl.str_numstr)
1507             {
1508                 choice -= (int)fp->tbl.str_numstr;
1509                 fp = fp->next;
1510                 DPRINTF(1, (stderr, "    skip \"%s\", %ld (choice = %d)\n",
1511                                fp->name, fp->tbl.str_numstr, choice));
1512             }
1513             DPRINTF(1,
1514                 (stderr, "using \"%s\", %ld\n", fp->name, fp->tbl.str_numstr));
1515         }
1516         get_tbl(fp);
1517     }
1518     if (fp->tbl.str_numstr == 0)
1519     {
1520         fprintf(stderr, "%s", "fortune: no fortune found\n");
1521         exit(1);
1522     }
1523     if (fp->child)
1524     {
1525         DPRINTF(1, (stderr, "%s", "picking child\n"));
1526         fp = pick_child(fp);
1527     }
1528     Fortfile = fp;
1529     get_pos(fp);
1530     open_dat(fp);
1531     lseek(fp->datfd,
1532         (off_t)(sizeof fp->tbl + (size_t)fp->pos * sizeof Seekpts[0]), 0);
1533     if ((read(fp->datfd, &Seekpts[0], sizeof Seekpts[0]) < 0) ||
1534         (read(fp->datfd, &Seekpts[1], sizeof Seekpts[1]) < 0))
1535     {
1536         exit(1);
1537     }
1538     Seekpts[0] = (int32_t)ntohl((uint32_t)Seekpts[0]);
1539     Seekpts[1] = (int32_t)ntohl((uint32_t)Seekpts[1]);
1540 }
1541
1542 /*
1543  * open_fp:
1544  *      Assocatiate a FILE * with the given FILEDESC.
1545  */
1546 static void open_fp(FILEDESC *fp)
1547 {
1548     if (!fp->inf && !(fp->inf = fdopen(fp->fd, "r")))
1549     {
1550         perror(fp->path);
1551         exit(1);
1552     }
1553 }
1554
1555 #ifndef NO_REGEX
1556 /*
1557  * maxlen_in_list
1558  *      Return the maximum fortune len in the file list.
1559  */
1560 static int maxlen_in_list(FILEDESC *list)
1561 {
1562     FILEDESC *fp;
1563     int len, maxlen = 0;
1564
1565     for (fp = list; fp; fp = fp->next)
1566     {
1567         if (fp->child)
1568         {
1569             if ((len = maxlen_in_list(fp->child)) > maxlen)
1570                 maxlen = len;
1571         }
1572         else
1573         {
1574             get_tbl(fp);
1575             if ((int)fp->tbl.str_longlen > maxlen)
1576             {
1577                 maxlen = (int)fp->tbl.str_longlen;
1578             }
1579         }
1580     }
1581     return maxlen;
1582 }
1583
1584 /*
1585  * matches_in_list
1586  *      Print out the matches from the files in the list.
1587  */
1588 static void matches_in_list(FILEDESC *list)
1589 {
1590     unsigned char *sp;
1591     unsigned char *p; /* -allover */
1592     unsigned char ch; /* -allover */
1593     FILEDESC *fp;
1594     int in_file, nchar;
1595     char *output;
1596
1597     for (fp = list; fp; fp = fp->next)
1598     {
1599         if (fp->child)
1600         {
1601             matches_in_list(fp->child);
1602             continue;
1603         }
1604         DPRINTF(1, (stderr, "searching in %s\n", fp->path));
1605         open_fp(fp);
1606         sp = Fortbuf;
1607         in_file = false;
1608         while (fgets((char *)sp, Fort_len, fp->inf))
1609         {
1610             if (!STR_ENDSTRING(sp, fp->tbl))
1611             {
1612                 sp += strlen((const char *)sp);
1613             }
1614             else
1615             {
1616                 *sp = '\0';
1617                 nchar = (int)(sp - Fortbuf);
1618
1619                 if (fp->utf8_charset && (!No_recode))
1620                 {
1621 #ifdef WITH_RECODE
1622                     output = recode_string(request, (const char *)Fortbuf);
1623 #else
1624                     output = strdup((const char *)Fortbuf);
1625 #endif
1626                 }
1627                 else
1628                 {
1629                     output = (char *)Fortbuf;
1630                 }
1631                 /* Should maybe rot13 Fortbuf -allover */
1632
1633                 if (fp->tbl.str_flags & STR_ROTATED)
1634                 {
1635                     for (p = (unsigned char *)output; (ch = *p); ++p)
1636                     {
1637                         if (isupper(ch) && isascii(ch))
1638                             *p = 'A' + (ch - 'A' + 13) % 26;
1639                         else if (islower(ch) && isascii(ch))
1640                             *p = 'a' + (ch - 'a' + 13) % 26;
1641                     }
1642                 }
1643
1644                 DPRINTF(1, (stdout, "nchar = %d\n", nchar));
1645                 if ((nchar < SLEN || !Short_only) &&
1646                     (nchar > SLEN || !Long_only) && RE_EXEC(output))
1647                 {
1648                     if (!in_file)
1649                     {
1650                         fprintf(
1651                             stderr, "(%s)\n%c\n", fp->name, fp->tbl.str_delim);
1652                         Found_one = true;
1653                         in_file = true;
1654                     }
1655                     fputs(output, stdout);
1656                     printf("%c\n", fp->tbl.str_delim);
1657                 }
1658
1659                 if (fp->utf8_charset && (!No_recode))
1660                 {
1661                     free(output);
1662                     output = NULL;
1663                 }
1664
1665                 sp = Fortbuf;
1666             }
1667         }
1668     }
1669 }
1670
1671 /*
1672  * find_matches:
1673  *      Find all the fortunes which match the pattern we've been given.
1674  */
1675 static int find_matches(void)
1676 {
1677     Fort_len = maxlen_in_list(File_list);
1678     DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
1679     /* extra length, "%\n" is appended */
1680     Fortbuf = do_malloc((unsigned int)Fort_len + 10);
1681
1682     Found_one = false;
1683     matches_in_list(File_list);
1684     return Found_one;
1685     /* NOTREACHED */
1686 }
1687 #endif /* NO_REGEX */
1688
1689 static void display(FILEDESC *fp)
1690 {
1691     char *p, ch;
1692     unsigned char line[BUFSIZ];
1693
1694     open_fp(fp);
1695     fseek(fp->inf, (long)Seekpts[0], SEEK_SET);
1696     if (Show_filename)
1697         printf("(%s)\n%%\n", fp->name);
1698     for (Fort_len = 0; fgets((char *)line, sizeof line, fp->inf) &&
1699                        !STR_ENDSTRING(line, fp->tbl);
1700          Fort_len++)
1701     {
1702         if (fp->tbl.str_flags & STR_ROTATED)
1703         {
1704             for (p = (char *)line; (ch = *p); ++p)
1705             {
1706                 if (isupper(ch) && isascii(ch))
1707                     *p = 'A' + (ch - 'A' + 13) % 26;
1708                 else if (islower(ch) && isascii(ch))
1709                     *p = 'a' + (ch - 'a' + 13) % 26;
1710             }
1711         }
1712         if (fp->utf8_charset && (!No_recode))
1713         {
1714             char *output;
1715 #ifdef WITH_RECODE
1716             output = recode_string(request, (const char *)line);
1717 #else
1718             output = strdup((const char *)line);
1719 #endif
1720             fputs(output, stdout);
1721             free(output);
1722         }
1723         else
1724             fputs((char *)line, stdout);
1725     }
1726     fflush(stdout);
1727 }
1728
1729 /*
1730  * fortlen:
1731  *      Return the length of the fortune.
1732  */
1733 static int fortlen(void)
1734 {
1735     int nchar;
1736     char line[BUFSIZ];
1737
1738     if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
1739         nchar = (Seekpts[1] - Seekpts[0]) - 2; /* for %^J delimiter */
1740     else
1741     {
1742         open_fp(Fortfile);
1743         fseek(Fortfile->inf, (long)Seekpts[0], SEEK_SET);
1744         nchar = 0;
1745         while (fgets(line, sizeof line, Fortfile->inf) &&
1746                !STR_ENDSTRING(line, Fortfile->tbl))
1747             nchar += strlen(line);
1748     }
1749     Fort_len = nchar;
1750     return nchar;
1751 }
1752
1753 static int mymax(int i, int j) { return (i >= j ? i : j); }
1754
1755 static void free_desc(FILEDESC *ptr)
1756 {
1757     while (ptr)
1758     {
1759         free_desc(ptr->child);
1760         free(ptr->datfile);
1761         free(ptr->posfile);
1762         free(ptr->name);
1763         free(ptr->path);
1764         if (ptr->inf)
1765         {
1766             fclose(ptr->inf);
1767             ptr->inf = NULL;
1768         }
1769         FILEDESC *next = ptr->next;
1770         free(ptr);
1771         ptr = next;
1772     }
1773 }
1774
1775 int main(int ac, char *av[])
1776 {
1777     const char *ctype;
1778     int exit_code = 0;
1779     env_lang = getenv("LC_ALL");
1780     if (!env_lang)
1781         env_lang = getenv("LC_MESSAGES");
1782     if (!env_lang)
1783         env_lang = getenv("LANGUAGE");
1784     if (!env_lang)
1785         env_lang = getenv("LANG");
1786 #ifdef _WIN32
1787     if (!env_lang)
1788     {
1789         env_lang = "en";
1790     }
1791 #endif
1792
1793 #ifndef DONT_CALL_GETARGS
1794     getargs(ac, av);
1795 #endif
1796
1797 #ifdef WITH_RECODE
1798     outer = recode_new_outer(true);
1799     request = recode_new_request(outer);
1800 #endif
1801
1802     setlocale(LC_ALL, "");
1803 #ifdef _WIN32
1804     ctype = "C";
1805 #else
1806     ctype = nl_langinfo(CODESET);
1807     if (!ctype || !*ctype)
1808     {
1809         ctype = "C";
1810     }
1811     else if (strcmp(ctype, "ANSI_X3.4-1968") == 0)
1812     {
1813         ctype = "ISO-8859-1";
1814     }
1815 #endif
1816
1817 #ifdef WITH_RECODE
1818     const size_t do_len = strlen(ctype) + 7 + 1;
1819     char *crequest = do_malloc(do_len + 1);
1820     snprintf(crequest, do_len, "UTF-8..%s", ctype);
1821     recode_scan_request(request, crequest);
1822     free(crequest);
1823 #endif
1824
1825 #ifndef NO_REGEX
1826     if (Match)
1827     {
1828         exit_code = (find_matches() != 0);
1829         regfree(&Re_pat);
1830         goto cleanup;
1831     }
1832 #endif
1833     init_prob();
1834     if (Find_files)
1835     {
1836         sum_noprobs(File_list);
1837         if (Equal_probs)
1838             calc_equal_probs();
1839         print_list(File_list, 0);
1840     }
1841     else
1842     {
1843         srandom((unsigned int)(time((time_t *)NULL) + getpid()));
1844         do
1845         {
1846             get_fort();
1847         } while ((Short_only && fortlen() > SLEN) ||
1848                  (Long_only && fortlen() <= SLEN));
1849
1850         display(Fortfile);
1851
1852         if (Wait)
1853         {
1854             fortlen();
1855             sleep((unsigned int)mymax(Fort_len / CPERS, MINW));
1856         }
1857     }
1858 cleanup:
1859 #ifdef WITH_RECODE
1860     recode_delete_request(request);
1861     recode_delete_outer(outer);
1862 #endif
1863
1864     /* Free the File_list */
1865     free_desc(File_list);
1866     free(Fortbuf);
1867     exit(exit_code);
1868 }