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