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