1 /* $NetBSD: fortune.c,v 1.8 1995/03/23 08:28:40 cgd Exp $ */
4 * Copyright (c) 1986, 1993
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
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.
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
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,
52 * 6: Calculated probabilities for all files, so that -f will print them.
55 /* Changes Copyright (c) 1997 Dennis L. Clark. All rights reserved.
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.
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
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
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
77 * Added to debian by Alastair McKinstry, <mckinstry@computer.org>, 2002-07-31
80 #define PROGRAM_NAME "fortune-mod"
83 #include "fortune-mod-common.h"
110 #define MINW 6 /* minimum wait if desired */
111 #define CPERS 20 /* # of chars for each sec */
113 #define POS_UNKNOWN ((int32_t)-1) /* pos for file unknown */
114 #define NO_PROB (-1) /* no prob specified for file */
117 #define DPRINTF(l, x) \
123 #define DPRINTF(l, x)
134 char *datfile, *posfile;
140 struct fd *child, *parent;
141 struct fd *next, *prev;
144 static const char *env_lang = NULL;
146 static bool Find_files = false; /* just find a list of proper fortune files */
147 static bool Wait = false; /* wait desired after fortune */
148 static bool Short_only = false; /* short fortune desired */
149 static bool Long_only = false; /* long fortune desired */
150 static bool Offend = false; /* offensive fortunes only */
151 static bool All_forts = false; /* any fortune allowed */
152 static bool Equal_probs = false; /* scatter un-allocated prob equally */
153 static bool Show_filename = false;
154 static bool No_recode = false; /* Do we want to stop recoding from occuring */
156 static bool ErrorMessage =
157 false; /* Set to true if an error message has been displayed */
161 #define RE_COMP(p) regcomp(&Re_pat, (p), REG_NOSUB)
162 #define BAD_COMP(f) ((f) != 0)
163 #define RE_EXEC(p) (regexec(&Re_pat, (p), 0, NULL, 0) == 0)
165 static regex_t Re_pat;
168 #endif /* POSIX_REGEX */
171 static bool Match = false; /* dump fortunes matching a pattern */
175 static bool Debug = false; /* print debug messages */
178 static unsigned char *Fortbuf = NULL; /* fortune buffer for -m */
180 static int Fort_len = 0, Spec_prob = 0, /* total prob specified on cmd line */
181 Num_files, Num_kids, /* totals of files and children. */
182 SLEN = 160; /* max. characters in a "short" fortune */
184 static int32_t Seekpts[2]; /* seek pointers to fortunes */
186 static FILEDESC *File_list = NULL, /* Head of file list */
187 *File_tail = NULL; /* Tail of file list */
188 static FILEDESC *Fortfile; /* Fortune file to use */
190 static STRFILE Noprob_tbl; /* sum of data for all no prob files */
193 static RECODE_REQUEST request;
194 static RECODE_OUTER outer;
195 static inline char *my_recode_string(const char *s)
197 return recode_string(request, (const char *)s);
200 static inline char *my_recode_string(const char *s) { return strdup(s); }
203 static int add_dir(FILEDESC *);
205 static unsigned long my_random(const unsigned long base)
207 unsigned long long l = 0;
208 char *hard_coded_val = getenv("FORTUNE_MOD_RAND_HARD_CODED_VALS");
211 return ((unsigned long)atol(hard_coded_val) % base);
213 if (getenv("FORTUNE_MOD_USE_SRAND"))
217 FILE *const fp = fopen("/dev/urandom", "rb");
222 if (fread(&l, sizeof(l), 1, fp) != 1)
230 return (((unsigned long)random()) % base);
233 static char *program_version(void)
235 static char buf[BUFSIZ];
236 (void)snprintf(buf, sizeof(buf), "%s version %s", PROGRAM_NAME, VERSION);
240 static void __attribute__((noreturn)) usage(void)
242 (void)fprintf(stderr, "%s\n", program_version());
243 (void)fprintf(stderr, "%s", "fortune [-a");
245 (void)fprintf(stderr, "%s", "D");
247 (void)fprintf(stderr, "%s", "f");
249 (void)fprintf(stderr, "%s", "i");
251 (void)fprintf(stderr, "%s", "l");
253 (void)fprintf(stderr, "%s", "o");
255 (void)fprintf(stderr, "%s", "sw]");
257 (void)fprintf(stderr, "%s", " [-m pattern]");
259 (void)fprintf(stderr, "%s", " [-n number] [ [#%] file/directory/all]\n");
263 #define STR(str) ((!str) ? "NULL" : (str))
267 * Set the global values for number of files/children, to be used
268 * in printing probabilities when listing files
270 static void calc_equal_probs(void)
272 Num_files = Num_kids = 0;
273 FILEDESC *fiddlylist = File_list;
277 Num_kids += fiddlylist->num_children;
278 fiddlylist = fiddlylist->next;
284 * Print out the actual list, recursively.
286 static void print_list(FILEDESC *list, int lev)
290 fprintf(stderr, "%*s", lev * 4, "");
291 if (list->percent == NO_PROB)
295 /* This, with some changes elsewhere, gives proper percentages
296 * for every case fprintf(stderr, "___%%"); */
297 fprintf(stderr, "%5.2f%%",
298 (100.0 - Spec_prob) * list->tbl.str_numstr /
299 Noprob_tbl.str_numstr);
303 fprintf(stderr, "%5.2f%%", 100.0 / Num_files);
307 fprintf(stderr, "%5.2f%%", 100.0 / Num_kids);
312 fprintf(stderr, "%5.2f%%", 1.0 * list->percent);
314 fprintf(stderr, " %s", STR(list->name));
315 DPRINTF(1, (stderr, " (%s, %s, %s)\n", STR(list->path),
316 STR(list->datfile), STR(list->posfile)));
320 print_list(list->child, lev + 1);
329 * Convert the pattern to an ignore-case equivalent.
331 static char *conv_pat(const char *const orig_str)
336 size_t cnt = 1; /* allow for '\0' */
337 for (sp = orig_str; *sp != '\0'; sp++)
339 const size_t prev_cnt = cnt;
350 fprintf(stderr, "%s",
351 "pattern too long for ignoring case; overflow!\n");
355 if (!(new_buf = malloc(cnt)))
357 fprintf(stderr, "%s", "pattern too long for ignoring case\n");
362 const char *orig = orig_str;
363 for (dest_ptr = new_buf; *orig != '\0'; ++orig)
369 *dest_ptr++ = (char)toupper(*orig);
372 else if (isupper(*orig))
376 *dest_ptr++ = (char)tolower(*orig);
391 * Do a malloc, checking for NULL return.
393 static void *do_malloc(const size_t size)
395 void *new_buf = malloc(size);
399 (void)fprintf(stderr, "%s", "fortune: out of memory.\n");
407 * Return a pointer to an initialized new FILEDESC.
409 static FILEDESC *new_fp(void)
413 fp = do_malloc(sizeof *fp);
415 fp->pos = POS_UNKNOWN;
418 fp->percent = NO_PROB;
419 fp->read_tbl = false;
420 fp->tbl.str_version = 0;
421 fp->tbl.str_numstr = 0;
422 fp->tbl.str_longlen = 0;
423 fp->tbl.str_shortlen = 0;
424 fp->tbl.str_flags = 0;
425 fp->tbl.stuff[0] = 0;
426 fp->tbl.stuff[1] = 0;
427 fp->tbl.stuff[2] = 0;
428 fp->tbl.stuff[3] = 0;
439 static inline void debugprint(const char *msg, ...)
444 vfprintf(stderr, msg, ap);
449 #define debugprint(format, ...) \
455 * Return true if the file is a directory, false otherwise.
457 static int is_dir(const char *const file)
461 if (stat(file, &sbuf) < 0)
463 debugprint("is_dir failed for file=<%s>\n", file);
466 const bool ret = (S_ISDIR(sbuf.st_mode) ? true : false);
467 debugprint("is_dir for file=<%s> gave ret=<%d>\n", file, ret);
473 * Return true if the file exists, false otherwise.
475 static int is_existant(char *file)
479 if (stat(file, &staat) == 0)
489 perror("fortune: bad juju in is_existant");
496 * Return true if the file is a fortune database file. We try and
497 * exclude files without reading them if possible to avoid
498 * overhead. Files which start with ".", or which have "illegal"
499 * suffixes, as contained in suflist[], are ruled out.
501 static int is_fortfile(const char *const file, char **datp)
503 const char *sp = strrchr(file, '/');
504 static const char *suflist[] = {/* list of "illegal" suffixes" */
505 "dat", "pos", "c", "h", "p", "i", "f", "pas", "ftn", "ins.c", "ins,pas",
506 "ins.ftn", "sml", NULL};
508 DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
520 DPRINTF(2, (stderr, "%s", "false (file starts with '.')\n"));
523 if ((sp = strrchr(sp, '.')))
526 for (int i = 0; suflist[i]; ++i)
528 if (strcmp(sp, suflist[i]) == 0)
530 DPRINTF(2, (stderr, "false (file has suffix \".%s\")\n", sp));
536 const size_t do_len = (strlen(file) + 6);
537 char *const datfile = do_malloc(do_len + 1);
538 snprintf(datfile, do_len, "%s.dat", file);
539 if (access(datfile, R_OK) < 0)
542 DPRINTF(2, (stderr, "%s", "false (no \".dat\" file)\n"));
553 DPRINTF(2, (stderr, "%s", "true\n"));
557 static bool path_is_absolute(const char *const path)
564 if (isalpha(path[0]) && path[1] == ':' && path[2] == '/')
572 static int open4read(const char *const path)
574 return open(path, O_RDONLY | O_BINARY);
579 * Add a file to the file list.
581 #define GCC_SNPRINTF_MARGIN 10
582 static int add_file(int percent, const char *file, const char *dir,
583 FILEDESC **head, FILEDESC **tail, FILEDESC *parent)
597 const size_t do_len =
598 (strlen(dir) + strlen(file) + (2 + GCC_SNPRINTF_MARGIN));
599 path = do_malloc(do_len + 1);
600 snprintf(path, do_len, "%s/%s", dir, file);
603 !is_existant(path)) /* If doesn't exist, don't do anything. */
608 const int isdir = is_dir(path);
609 if ((isdir > 0 && parent) || (isdir < 0))
612 return false; /* don't recurse */
615 DPRINTF(1, (stderr, "trying to add file \"%s\"\n", path));
620 ((fd = open4read(path)) < 0)) ||
621 !path_is_absolute(path))
623 debugprint("check file fd=%d path=<%s> dir=<%s> file=<%s> percent=%d\n",
624 fd, path, dir, file, percent);
626 if (!found && !parent && !dir)
627 { /* don't display an error when trying language specific files */
634 strncpy(llang, env_lang, sizeof(llang));
635 llang[sizeof(llang) - 1] = '\0';
638 /* the language string can be like "es:fr_BE:ga" */
639 while (!ret && lang && (*lang))
641 char *p = strchr(lang, ':');
646 snprintf(langdir, sizeof(langdir), "%s/%s", FORTDIR, lang);
648 if (strncmp(path, lang, 2) == 0)
652 else if (strncmp(path, langdir, strlen(FORTDIR) + 3) == 0)
674 DPRINTF(2, (stderr, "path = \"%s\"\n", path));
678 fp->percent = percent;
680 fp->name = strdup(file);
681 fp->path = strdup(path);
684 fp->utf8_charset = false;
685 const size_t do_len = (strlen(path) + 5);
686 char *testpath = do_malloc(do_len + 1);
687 snprintf(testpath, do_len, "%s.u8", path);
688 // fprintf(stderr, "State mal: %s\n", testpath);
689 if (stat(testpath, &statbuf) == 0)
691 fp->utf8_charset = true;
696 // fprintf(stderr, "Is utf8?: %i\n", fp->utf8_charset );
700 if ((isdir && !add_dir(fp)) || (!isdir && !is_fortfile(path, &fp->datfile)))
705 stderr, "fortune:%s not a fortune file or directory\n", path);
721 /* This is a hack to come around another hack - add_dir returns success
722 * if the directory is allowed to be empty, but we can not handle an
723 * empty directory... */
724 if (isdir && fp->num_children == 0)
745 else if (fp->percent == NO_PROB)
764 static int names_compare(const void *a, const void *b)
766 return strcmp(*(const char *const *)a, *(const char *const *)b);
770 * Add the contents of an entire directory.
772 static int add_dir(FILEDESC *const fp)
775 struct dirent *dirent;
777 size_t i, count_names, max_count_names;
781 if (!(dir = opendir(fp->path)))
786 FILEDESC *tailp = NULL;
787 DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
788 fp->num_children = 0;
789 max_count_names = 200;
791 names = malloc(sizeof(names[0]) * max_count_names);
794 perror("Out of RAM!");
797 while ((dirent = readdir(dir)))
799 if (dirent->d_name[0] == 0)
803 char *name = strdup(dirent->d_name);
804 if (count_names == max_count_names)
806 max_count_names += 200;
807 names = realloc(names, sizeof(names[0]) * max_count_names);
810 perror("Out of RAM!");
814 names[count_names++] = name;
817 qsort(names, count_names, sizeof(names[0]), names_compare);
819 for (i = 0; i < count_names; ++i)
821 if (add_file(NO_PROB, names[i], fp->path, &fp->child, &tailp, fp))
829 if (fp->num_children == 0)
832 * Only the local fortune dir and the local offensive dir are
833 * allowed to be empty.
834 * - Brian Bassett (brianb@debian.org) 1999/07/31
836 if (strcmp(LOCFORTDIR, fp->path) == 0 ||
837 strcmp(LOCOFFDIR, fp->path) == 0)
842 stderr, "fortune: %s: No fortune files in directory.\n", fp->path);
850 * Form the file list from the file specifications.
853 static int top_level__add_file(const char *dirpath)
855 return add_file(NO_PROB, dirpath, NULL, &File_list, &File_tail, NULL);
858 static int cond_top_level__add_file(
859 const char *dirpath, const char *possible_dup)
861 if (!strcmp(dirpath, possible_dup))
865 return top_level__add_file(dirpath);
868 static int cond_top_level__LOCFORTDIR(void)
870 return cond_top_level__add_file(FORTDIR, LOCFORTDIR);
873 static int cond_top_level__OFFDIR(void)
875 return cond_top_level__add_file(OFFDIR, LOCOFFDIR);
878 static int top_level_LOCFORTDIR(void)
880 return (top_level__add_file(LOCFORTDIR) | cond_top_level__LOCFORTDIR());
883 static int form_file_list(char **files, int file_cnt)
888 char fullpathname[512], locpathname[512];
894 return (top_level__add_file(LOCFORTDIR) |
895 top_level__add_file(LOCOFFDIR) |
896 cond_top_level__LOCFORTDIR() | cond_top_level__OFFDIR());
900 return (top_level__add_file(LOCOFFDIR) | cond_top_level__OFFDIR());
911 strncpy(llang, env_lang, sizeof(llang));
912 llang[sizeof(llang) - 1] = '\0';
915 /* the language string can be like "es:fr_BE:ga" */
916 while (lang && (*lang))
918 p = strchr(lang, ':');
924 /* first try full locale */
926 NO_PROB, lang, NULL, &File_list, &File_tail, NULL);
928 /* if not try language name only (two first chars) */
933 strncpy(ll, lang, 2);
936 NO_PROB, ll, NULL, &File_list, &File_tail, NULL);
939 /* if we have found one we have finished */
947 return top_level_LOCFORTDIR();
951 /* no locales available, use default */
952 return top_level_LOCFORTDIR();
957 for (i = 0; i < file_cnt; i++)
960 if (!isdigit(files[i][0]))
966 const int MAX_PERCENT = 100;
967 bool percent_has_overflowed = false;
969 for (sp = files[i]; isdigit(*sp); sp++)
971 percent = percent * 10 + *sp - '0';
972 percent_has_overflowed = (percent > MAX_PERCENT);
973 if (percent_has_overflowed)
978 if (percent_has_overflowed || (percent > 100))
980 fprintf(stderr, "percentages must be <= 100\n");
982 "Overflow percentage detected at argument \"%s\"!\n",
990 "Overflow percentage detected at argument \"%s\"!\n",
997 fprintf(stderr, "%s", "percentages must be integers\n");
1002 * If the number isn't followed by a '%', then
1003 * it was not a percentage, just the first part
1004 * of a file name which starts with digits.
1011 else if (*++sp == '\0')
1013 if (++i >= file_cnt)
1015 fprintf(stderr, "%s", "percentages must precede files\n");
1016 ErrorMessage = true;
1023 /* BSD-style '-o' offensive file suffix */
1024 bool offensive = false;
1025 const size_t sp_len = strlen(sp);
1026 if (sp_len >= 3 && sp[sp_len - 2] == '-' && sp[sp_len - 1] == 'o')
1028 sp[sp_len - 2] = '\0';
1032 const char* fulldir = offensive ? OFFDIR : FORTDIR;
1033 const char* locdir = offensive ? LOCOFFDIR : LOCFORTDIR;
1035 if (strcmp(sp, "all") == 0)
1037 snprintf(fullpathname, sizeof(fullpathname), "%s", fulldir);
1038 snprintf(locpathname, sizeof(locpathname), "%s", locdir);
1040 /* if it isn't an absolute path or relative to . or ..
1041 make it an absolute path relative to FORTDIR */
1044 if (strncmp(sp, "/", 1) != 0 && strncmp(sp, "./", 2) != 0 &&
1045 strncmp(sp, "../", 3) != 0)
1048 fullpathname, sizeof(fullpathname), "%s/%s", fulldir, sp);
1050 locpathname, sizeof(locpathname), "%s/%s", locdir, sp);
1054 snprintf(fullpathname, sizeof(fullpathname), "%s", sp);
1055 snprintf(locpathname, sizeof(locpathname), "%s", sp);
1064 strncpy(llang, env_lang, sizeof(llang));
1065 llang[sizeof(llang) - 1] = '\0';
1068 /* the language string can be like "es:fr_BE:ga" */
1069 while (!ret && lang && (*lang))
1071 char *p = strchr(lang, ':');
1077 /* first try full locale */
1079 langdir, sizeof(langdir), "%s/%s/%s", fulldir, lang, sp);
1081 percent, langdir, NULL, &File_list, &File_tail, NULL);
1083 /* if not try language name only (two first chars) */
1088 strncpy(ll, lang, 2);
1091 langdir, sizeof(langdir), "%s/%s/%s", fulldir, ll, sp);
1093 percent, langdir, NULL, &File_list, &File_tail, NULL);
1102 percent, fullpathname, NULL, &File_list, &File_tail, NULL);
1105 strncmp(fullpathname, locpathname, sizeof(fullpathname)))
1108 percent, locpathname, NULL, &File_list, &File_tail, NULL);
1110 if (strncmp(fullpathname, locpathname, sizeof(fullpathname)) &&
1111 strcmp(sp, "all") == 0)
1114 percent, locpathname, NULL, &File_list, &File_tail, NULL);
1120 // restore -o suffix
1121 sp[sp_len - 2] = '-';
1123 snprintf(locpathname, sizeof(locpathname), "%s/%s",
1127 percent, locpathname, NULL, &File_list, &File_tail, NULL);
1135 percent, fullpathname, NULL, &File_list, &File_tail, NULL))
1144 * This routine evaluates the arguments on the command line
1146 static void getargs(int argc, char **argv)
1149 bool ignore_case = false;
1156 #define DEBUG_GETOPT "D"
1158 #define DEBUG_GETOPT
1162 #define OFFENSIVE_GETOPT
1164 #define OFFENSIVE_GETOPT "o"
1167 while ((ch = getopt(argc, argv,
1168 "ac" DEBUG_GETOPT "efhilm:n:" OFFENSIVE_GETOPT "suvw")) != EOF)
1172 case 'a': /* any fortune */
1181 Equal_probs = true; /* scatter un-allocted prob equally */
1183 case 'f': /* find fortune files */
1186 case 'l': /* long ones only */
1191 SLEN = atoi(optarg);
1193 #ifndef NO_OFFENSIVE
1194 case 'o': /* offensive ones only */
1198 case 's': /* short ones only */
1202 case 'w': /* give time to read */
1206 case 'i': /* case-insensitive match */
1207 case 'm': /* dump out the fortunes */
1208 (void)fprintf(stderr, "%s",
1209 "fortune: can't match fortunes on this system "
1212 #else /* NO_REGEX */
1213 case 'm': /* dump out the fortunes */
1217 case 'i': /* case-insensitive match */
1220 #endif /* NO_REGEX */
1221 case 'u': /* Don't recode the fortune */
1225 (void)printf("%s\n", program_version());
1228 Show_filename = true;
1239 if (!form_file_list(argv, argc))
1243 fprintf(stderr, "%s", "No fortunes found\n");
1245 exit(1); /* errors printed through form_file_list() */
1250 print_list(File_list, 0); /* Causes crash with new %% code */
1255 /* If (Find_files) print_list() moved to main */
1261 pat = conv_pat(pat);
1263 if (BAD_COMP(RE_COMP(pat)))
1265 fprintf(stderr, "bad pattern: %s\n", pat);
1278 * Initialize the fortune probabilities.
1280 static void init_prob(void)
1283 int percent = 0, num_noprob = 0, frac;
1286 * Distribute the residual probability (if any) across all
1287 * files with unspecified probability (i.e., probability of 0)
1290 FILEDESC *last = NULL;
1291 for (fp = File_tail; fp; fp = fp->prev)
1293 if (fp->percent == NO_PROB)
1303 percent += fp->percent;
1306 DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's\n",
1307 percent, num_noprob));
1310 fprintf(stderr, "fortune: probabilities sum to %d%%!\n", percent);
1313 else if (percent < 100 && num_noprob == 0)
1316 "fortune: no place to put residual probability "
1321 else if (percent == 100 && num_noprob != 0)
1323 fprintf(stderr, "fortune: no probability left to put in "
1324 "residual files\n");
1327 Spec_prob = percent; /* this is for -f when % is specified on
1329 percent = 100 - percent;
1332 if (num_noprob != 0)
1336 frac = percent / num_noprob;
1337 DPRINTF(1, (stderr, ", frac = %d%%", frac));
1338 for (fp = File_tail; fp != last; fp = fp->prev)
1340 if (fp->percent == NO_PROB)
1347 last->percent = percent;
1348 DPRINTF(1, (stderr, ", residual = %d%%", percent));
1352 DPRINTF(1, (stderr, ", %d%% distributed over remaining fortunes\n",
1356 DPRINTF(1, (stderr, "%s", "\n"));
1362 print_list(File_list, 0); /* Causes crash with new %% code */
1371 * Zero out the fields we care about in a tbl structure.
1373 static void zero_tbl(STRFILE *tp)
1376 tp->str_longlen = 0;
1377 tp->str_shortlen = (uint32_t)(-1);
1382 * Merge the tbl data of t2 into t1.
1384 static void sum_tbl(STRFILE *t1, STRFILE *t2)
1386 t1->str_numstr += t2->str_numstr;
1387 if (t1->str_longlen < t2->str_longlen)
1389 t1->str_longlen = t2->str_longlen;
1391 if (t1->str_shortlen > t2->str_shortlen)
1393 t1->str_shortlen = t2->str_shortlen;
1399 * Get the tbl data file the datfile.
1401 static void get_tbl(FILEDESC *fp)
1412 if ((fd = open4read(fp->datfile)) < 0)
1414 perror(fp->datfile);
1417 if (read(fd, &fp->tbl.str_version, sizeof fp->tbl.str_version) !=
1418 sizeof fp->tbl.str_version)
1420 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1423 if (read(fd, &fp->tbl.str_numstr, sizeof fp->tbl.str_numstr) !=
1424 sizeof fp->tbl.str_numstr)
1426 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1429 if (read(fd, &fp->tbl.str_longlen, sizeof fp->tbl.str_longlen) !=
1430 sizeof fp->tbl.str_longlen)
1432 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1435 if (read(fd, &fp->tbl.str_shortlen, sizeof fp->tbl.str_shortlen) !=
1436 sizeof fp->tbl.str_shortlen)
1438 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1441 if (read(fd, &fp->tbl.str_flags, sizeof fp->tbl.str_flags) !=
1442 sizeof fp->tbl.str_flags)
1444 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1447 if (read(fd, &fp->tbl.stuff, sizeof fp->tbl.stuff) !=
1448 sizeof fp->tbl.stuff)
1450 fprintf(stderr, "fortune: %s corrupted\n", fp->path);
1453 fp->tbl.str_version = ntohl(fp->tbl.str_version);
1454 fp->tbl.str_numstr = ntohl(fp->tbl.str_numstr);
1455 fp->tbl.str_longlen = ntohl(fp->tbl.str_longlen);
1456 fp->tbl.str_shortlen = ntohl(fp->tbl.str_shortlen);
1457 fp->tbl.str_flags = ntohl(fp->tbl.str_flags);
1463 for (child = fp->child; child; child = child->next)
1466 sum_tbl(&fp->tbl, &child->tbl);
1469 fp->read_tbl = true;
1474 * Sum up all the noprob probabilities, starting with fp.
1476 static void sum_noprobs(FILEDESC *fp)
1478 static bool did_noprobs = false;
1484 zero_tbl(&Noprob_tbl);
1488 /* This conditional should help us return correct values for
1489 * -f when a percentage is specified */
1490 if (fp->percent == NO_PROB)
1492 sum_tbl(&Noprob_tbl, &fp->tbl);
1501 * Pick a child from a chosen parent.
1503 static FILEDESC *pick_child(FILEDESC *parent)
1510 choice = (int)my_random((unsigned long)parent->num_children);
1511 DPRINTF(1, (stderr, " choice = %d (of %d)\n", choice,
1512 parent->num_children));
1513 for (fp = parent->child; choice--; fp = fp->next)
1517 DPRINTF(1, (stderr, " using %s\n", fp->name));
1523 choice = (int)(my_random(parent->tbl.str_numstr));
1524 DPRINTF(1, (stderr, " choice = %d (of %ld)\n", choice,
1525 parent->tbl.str_numstr));
1526 for (fp = parent->child; choice >= (int)fp->tbl.str_numstr;
1529 choice -= fp->tbl.str_numstr;
1530 DPRINTF(1, (stderr, "\tskip %s, %ld (choice = %d)\n", fp->name,
1531 fp->tbl.str_numstr, choice));
1534 1, (stderr, " using %s, %ld\n", fp->name, fp->tbl.str_numstr));
1541 * Open up the dat file if we need to.
1543 static void open_dat(FILEDESC *fp)
1545 if (fp->datfd < 0 && (fp->datfd = open4read(fp->datfile)) < 0)
1553 * Get the position from the pos file, if there is one. If
1554 * not, return a random number.
1556 static void get_pos(FILEDESC *fp)
1558 assert(fp->read_tbl);
1559 if (fp->pos == POS_UNKNOWN)
1561 fp->pos = (int32_t)(my_random(fp->tbl.str_numstr));
1563 if (++(fp->pos) >= (int32_t)fp->tbl.str_numstr)
1565 fp->pos -= fp->tbl.str_numstr;
1567 DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, fp->pos));
1572 * Get the fortune data file's seek pointer for the next
1575 static void get_fort(void)
1580 if (!File_list->next || File_list->percent == NO_PROB)
1586 choice = (int)my_random(100);
1587 DPRINTF(1, (stderr, "choice = %d\n", choice));
1588 for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
1590 if (choice < fp->percent)
1596 choice -= fp->percent;
1597 DPRINTF(1, (stderr, " skip \"%s\", %d%% (choice = %d)\n",
1598 fp->name, fp->percent, choice));
1601 DPRINTF(1, (stderr, "using \"%s\", %d%% (choice = %d)\n", fp->name,
1602 fp->percent, choice));
1604 if (fp->percent != NO_PROB)
1613 choice = (int)(my_random(Noprob_tbl.str_numstr));
1614 DPRINTF(1, (stderr, "choice = %d (of %ld) \n", choice,
1615 Noprob_tbl.str_numstr));
1616 while (choice >= (int)fp->tbl.str_numstr)
1618 choice -= (int)fp->tbl.str_numstr;
1620 DPRINTF(1, (stderr, " skip \"%s\", %ld (choice = %d)\n",
1621 fp->name, fp->tbl.str_numstr, choice));
1624 (stderr, "using \"%s\", %ld\n", fp->name, fp->tbl.str_numstr));
1628 if (fp->tbl.str_numstr == 0)
1630 fprintf(stderr, "%s", "fortune: no fortune found\n");
1635 DPRINTF(1, (stderr, "%s", "picking child\n"));
1636 fp = pick_child(fp);
1642 (off_t)(sizeof fp->tbl + (size_t)fp->pos * sizeof Seekpts[0]), 0);
1643 if ((read(fp->datfd, &Seekpts[0], sizeof Seekpts[0]) < 0) ||
1644 (read(fp->datfd, &Seekpts[1], sizeof Seekpts[1]) < 0))
1648 Seekpts[0] = (int32_t)ntohl((uint32_t)Seekpts[0]);
1649 Seekpts[1] = (int32_t)ntohl((uint32_t)Seekpts[1]);
1654 * Assocatiate a FILE * with the given FILEDESC.
1656 static void open_fp(FILEDESC *fp)
1658 if (!fp->inf && !(fp->inf = fdopen(fp->fd, "r")))
1668 * Return the maximum fortune len in the file list.
1670 static int maxlen_in_list(FILEDESC *list)
1673 int len, maxlen = 0;
1675 for (fp = list; fp; fp = fp->next)
1679 if ((len = maxlen_in_list(fp->child)) > maxlen)
1687 if ((int)fp->tbl.str_longlen > maxlen)
1689 maxlen = (int)fp->tbl.str_longlen;
1698 * Print out the matches from the files in the list.
1700 static void matches_in_list(FILEDESC *list, bool *const Found_one_ptr)
1703 unsigned char *p; /* -allover */
1704 unsigned char ch; /* -allover */
1709 for (fp = list; fp; fp = fp->next)
1713 matches_in_list(fp->child, Found_one_ptr);
1716 DPRINTF(1, (stderr, "searching in %s\n", fp->path));
1720 while (fgets((char *)sp, Fort_len, fp->inf))
1722 if (!STR_ENDSTRING(sp, fp->tbl))
1724 sp += strlen((const char *)sp);
1729 nchar = (int)(sp - Fortbuf);
1731 if (fp->utf8_charset && (!No_recode))
1733 output = my_recode_string((const char *)Fortbuf);
1737 output = (char *)Fortbuf;
1739 /* Should maybe rot13 Fortbuf -allover */
1741 if (fp->tbl.str_flags & STR_ROTATED)
1743 for (p = (unsigned char *)output; (ch = *p); ++p)
1745 if (isupper(ch) && isascii(ch))
1747 *p = 'A' + (ch - 'A' + 13) % 26;
1749 else if (islower(ch) && isascii(ch))
1751 *p = 'a' + (ch - 'a' + 13) % 26;
1756 DPRINTF(1, (stdout, "nchar = %d\n", nchar));
1757 if ((nchar < SLEN || !Short_only) &&
1758 (nchar > SLEN || !Long_only) && RE_EXEC(output))
1763 stderr, "(%s)\n%c\n", fp->name, fp->tbl.str_delim);
1764 (*Found_one_ptr) = true;
1767 fputs(output, stdout);
1768 printf("%c\n", fp->tbl.str_delim);
1771 if (fp->utf8_charset && (!No_recode))
1785 * Find all the fortunes which match the pattern we've been
1788 static bool find_matches(void)
1790 Fort_len = maxlen_in_list(File_list);
1791 DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
1792 /* extra length, "%\n" is appended */
1793 Fortbuf = do_malloc((unsigned int)Fort_len + 10);
1795 bool Found_one = false;
1796 matches_in_list(File_list, &Found_one);
1802 static void display(FILEDESC *fp)
1805 unsigned char line[BUFSIZ];
1808 fseek(fp->inf, (long)Seekpts[0], SEEK_SET);
1811 printf("(%s)\n%%\n", fp->name);
1813 for (Fort_len = 0; fgets((char *)line, sizeof line, fp->inf) &&
1814 !STR_ENDSTRING(line, fp->tbl);
1817 if (fp->tbl.str_flags & STR_ROTATED)
1819 for (p = (char *)line; (ch = *p); ++p)
1821 if (isupper(ch) && isascii(ch))
1823 *p = 'A' + (ch - 'A' + 13) % 26;
1825 else if (islower(ch) && isascii(ch))
1827 *p = 'a' + (ch - 'a' + 13) % 26;
1831 if (fp->utf8_charset && (!No_recode))
1833 char *output = my_recode_string((const char *)line);
1834 fputs(output, stdout);
1838 fputs((char *)line, stdout);
1845 * Return the length of the fortune.
1847 static int fortlen(void)
1852 if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
1854 nchar = (Seekpts[1] - Seekpts[0]) - 2; /* for %^J delimiter */
1859 fseek(Fortfile->inf, (long)Seekpts[0], SEEK_SET);
1861 while (fgets(line, sizeof line, Fortfile->inf) &&
1862 !STR_ENDSTRING(line, Fortfile->tbl))
1864 nchar += strlen(line);
1871 static int mymax(int i, int j) { return (i >= j ? i : j); }
1873 static void free_desc(FILEDESC *ptr)
1877 free_desc(ptr->child);
1887 FILEDESC *next = ptr->next;
1893 int main(int argc, char *argv[])
1900 env_lang = getenv("LC_ALL");
1903 env_lang = getenv("LC_MESSAGES");
1907 env_lang = getenv("LANGUAGE");
1911 env_lang = getenv("LANG");
1920 getargs(argc, argv);
1922 outer = recode_new_outer(true);
1923 request = recode_new_request(outer);
1926 setlocale(LC_ALL, "");
1932 ctype = nl_langinfo(CODESET);
1933 if (!ctype || !*ctype)
1937 else if (strcmp(ctype, "ANSI_X3.4-1968") == 0)
1939 ctype = "ISO-8859-1";
1942 const size_t do_len = strlen(ctype) + (7 + 1 + GCC_SNPRINTF_MARGIN);
1943 char *crequest = do_malloc(do_len + 1);
1944 snprintf(crequest, do_len, "UTF-8..%s", ctype);
1945 recode_scan_request(request, crequest);
1952 exit_code = find_matches();
1961 sum_noprobs(File_list);
1966 print_list(File_list, 0);
1974 } while ((Short_only && fortlen() > SLEN) ||
1975 (Long_only && fortlen() <= SLEN));
1982 sleep((unsigned int)mymax(Fort_len / CPERS, MINW));
1990 recode_delete_request(request);
1991 recode_delete_outer(outer);
1994 /* Free the File_list */
1995 free_desc(File_list);