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