Home | History | Annotate | Line # | Download | only in fortune
      1 /*	$NetBSD: fortune.c,v 1.65 2020/07/21 03:05:40 nia 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. Neither the name of the University nor the names of its contributors
     19  *    may be used to endorse or promote products derived from this software
     20  *    without specific prior written permission.
     21  *
     22  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
     23  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     24  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
     25  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
     26  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
     27  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
     28  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
     29  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
     30  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
     31  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
     32  * SUCH DAMAGE.
     33  */
     34 
     35 #include <sys/cdefs.h>
     36 #ifndef lint
     37 __COPYRIGHT("@(#) Copyright (c) 1986, 1993\
     38  The Regents of the University of California.  All rights reserved.");
     39 #endif /* not lint */
     40 
     41 #ifndef lint
     42 #if 0
     43 static char sccsid[] = "@(#)fortune.c	8.1 (Berkeley) 5/31/93";
     44 #else
     45 __RCSID("$NetBSD: fortune.c,v 1.65 2020/07/21 03:05:40 nia Exp $");
     46 #endif
     47 #endif /* not lint */
     48 
     49 #include <sys/param.h>
     50 #include <sys/stat.h>
     51 #include <sys/time.h>
     52 #include <sys/endian.h>
     53 
     54 #include <dirent.h>
     55 #include <fcntl.h>
     56 #include <assert.h>
     57 #include <unistd.h>
     58 #include <stdio.h>
     59 #include <ctype.h>
     60 #include <stdlib.h>
     61 #include <string.h>
     62 #include <err.h>
     63 #include <time.h>
     64 
     65 #ifndef NO_REGEX
     66 #include <regex.h>
     67 #endif
     68 
     69 #include "strfile.h"
     70 #include "pathnames.h"
     71 
     72 #define TRUE	1
     73 #define FALSE	0
     74 #define bool	short
     75 
     76 #define MINW	6		/* minimum wait if desired */
     77 #define CPERS	20		/* # of chars for each sec */
     78 #define SLEN	160		/* # of chars in short fortune */
     79 
     80 #define POS_UNKNOWN	((off_t) -1)	/* pos for file unknown */
     81 #define NO_PROB		(-1)		/* no prob specified for file */
     82 
     83 #ifdef DEBUG
     84 #define DPRINTF(l,x)	if (Debug >= l) fprintf x; else
     85 #undef NDEBUG
     86 #else
     87 #define DPRINTF(l,x)
     88 #define NDEBUG		1
     89 #endif
     90 
     91 typedef struct fd {
     92 	int		percent;
     93 	int		fd, datfd;
     94 	off_t		pos;
     95 	FILE		*inf;
     96 	const char	*name;
     97 	const char	*path;
     98 	char		*datfile, *posfile;
     99 	bool		read_tbl;
    100 	bool		was_pos_file;
    101 	STRFILE		tbl;
    102 	int		num_children;
    103 	struct fd	*child, *parent;
    104 	struct fd	*next, *prev;
    105 } FILEDESC;
    106 
    107 static bool Found_one;			/* did we find a match? */
    108 static bool Find_files	= FALSE;	/* just find a list of proper fortune files */
    109 static bool Wait	= FALSE;	/* wait desired after fortune */
    110 static bool Short_only	= FALSE;	/* short fortune desired */
    111 static bool Long_only	= FALSE;	/* long fortune desired */
    112 static bool Offend	= FALSE;	/* offensive fortunes only */
    113 static bool All_forts	= FALSE;	/* any fortune allowed */
    114 static bool Equal_probs	= FALSE;	/* scatter un-allocted prob equally */
    115 
    116 #ifndef NO_REGEX
    117 struct re {
    118 	bool valid;
    119 	regex_t regex;
    120 };
    121 
    122 static bool Match	= FALSE;	/* dump fortunes matching a pattern */
    123 struct re Re_pat;
    124 struct re Re_pat13;
    125 static struct re *Re_use = NULL;
    126 #endif
    127 
    128 #ifdef DEBUG
    129 static bool Debug = FALSE;		/* print debug messages */
    130 #endif
    131 
    132 static char *Fortbuf = NULL;		/* fortune buffer for -m */
    133 
    134 static size_t Fort_len = 0;
    135 
    136 static off_t Seekpts[2];		/* seek pointers to fortunes */
    137 
    138 static FILEDESC *File_list = NULL,	/* Head of file list */
    139 		*File_tail = NULL;	/* Tail of file list */
    140 static FILEDESC *Fortfile;		/* Fortune file to use */
    141 
    142 static STRFILE Noprob_tbl;		/* sum of data for all no prob files */
    143 
    144 static int add_dir(FILEDESC *);
    145 static int add_file(int,
    146 	    const char *, const char *, FILEDESC **, FILEDESC **, FILEDESC *);
    147 static void all_forts(FILEDESC *, const char *);
    148 static char *copy(const char *, size_t);
    149 static void rot13(char *line, int len);
    150 static void display(FILEDESC *);
    151 static void do_free(void *);
    152 static void *do_malloc(size_t);
    153 static int form_file_list(char **, int);
    154 static size_t fortlen(void);
    155 static void get_fort(void);
    156 static void get_pos(FILEDESC *);
    157 static void get_tbl(FILEDESC *);
    158 static void getargs(int, char *[]);
    159 static void init_prob(void);
    160 static int is_dir(const char *);
    161 static int is_fortfile(const char *, char **, char **, int);
    162 static int is_off_name(const char *);
    163 static int max(int, int);
    164 static FILEDESC *new_fp(void);
    165 static char *off_name(const char *);
    166 static void open_dat(FILEDESC *);
    167 static void open_fp(FILEDESC *);
    168 static FILEDESC *pick_child(FILEDESC *);
    169 static void print_file_list(void);
    170 static void print_list(FILEDESC *, int);
    171 static void sum_noprobs(FILEDESC *);
    172 static void sum_tbl(STRFILE *, STRFILE *);
    173 static void usage(void) __dead;
    174 static void zero_tbl(STRFILE *);
    175 
    176 int main(int, char *[]);
    177 
    178 #ifndef NO_REGEX
    179 static void re_setup(struct re *rx, const char *pattern, bool ignore_case);
    180 static void re_cleanup(struct re *rx);
    181 static int find_matches(void);
    182 static void matches_in_list(FILEDESC *);
    183 static size_t maxlen_in_list(FILEDESC *);
    184 #endif
    185 
    186 #ifndef NAMLEN
    187 #define		NAMLEN(d)	((d)->d_namlen)
    188 #endif
    189 
    190 int
    191 main(int ac, char *av[])
    192 {
    193 #ifdef OK_TO_WRITE_DISK
    194 	int fd;
    195 #endif /* OK_TO_WRITE_DISK */
    196 
    197 	getargs(ac, av);
    198 
    199 #ifndef NO_REGEX
    200 	if (Match)
    201 		exit(find_matches() != 0);
    202 #endif
    203 
    204 	init_prob();
    205 	do {
    206 		get_fort();
    207 	} while ((Short_only && fortlen() > SLEN) ||
    208 		 (Long_only && fortlen() <= SLEN));
    209 
    210 	display(Fortfile);
    211 
    212 #ifdef OK_TO_WRITE_DISK
    213 	if ((fd = creat(Fortfile->posfile, 0666)) < 0)
    214 		err(1, "Can't create `%s'", Fortfile->posfile);
    215 #ifdef LOCK_EX
    216 	/*
    217 	 * if we can, we exclusive lock, but since it isn't very
    218 	 * important, we just punt if we don't have easy locking
    219 	 * available.
    220 	 */
    221 	(void) flock(fd, LOCK_EX);
    222 #endif /* LOCK_EX */
    223 	write(fd, (char *) &Fortfile->pos, sizeof Fortfile->pos);
    224 	if (!Fortfile->was_pos_file)
    225 		(void) chmod(Fortfile->path, 0666);
    226 #ifdef LOCK_EX
    227 	(void) flock(fd, LOCK_UN);
    228 #endif /* LOCK_EX */
    229 	close(fd);
    230 #endif /* OK_TO_WRITE_DISK */
    231 	if (Wait) {
    232 		if (Fort_len == 0)
    233 			(void) fortlen();
    234 		sleep(max(Fort_len / CPERS, MINW));
    235 	}
    236 	return(0);
    237 }
    238 
    239 static void
    240 rot13(char *line, int len)
    241 {
    242 	char *p, ch;
    243 
    244 	if (len == 0)
    245 		len = strlen(line);
    246 
    247 	for (p = line; (ch = *p) != 0; ++p)
    248 		if (isupper((unsigned char)ch))
    249 			*p = 'A' + (ch - 'A' + 13) % 26;
    250 		else if (islower((unsigned char)ch))
    251 			*p = 'a' + (ch - 'a' + 13) % 26;
    252 }
    253 
    254 static void
    255 display(FILEDESC *fp)
    256 {
    257 	char line[BUFSIZ];
    258 
    259 	open_fp(fp);
    260 	(void) fseek(fp->inf, (long)Seekpts[0], SEEK_SET);
    261 	for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL &&
    262 	    !STR_ENDSTRING(line, fp->tbl); Fort_len++) {
    263 		if (fp->tbl.str_flags & STR_ROTATED)
    264 			rot13(line, 0);
    265 		fputs(line, stdout);
    266 	}
    267 	(void) fflush(stdout);
    268 }
    269 
    270 /*
    271  * fortlen:
    272  *	Return the length of the fortune.
    273  */
    274 static size_t
    275 fortlen(void)
    276 {
    277 	size_t nchar;
    278 	char line[BUFSIZ];
    279 
    280 	if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
    281 		nchar = Seekpts[1] - Seekpts[0];
    282 	else {
    283 		open_fp(Fortfile);
    284 		(void) fseek(Fortfile->inf, (long)Seekpts[0], SEEK_SET);
    285 		nchar = 0;
    286 		while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
    287 		       !STR_ENDSTRING(line, Fortfile->tbl))
    288 			nchar += strlen(line);
    289 	}
    290 	Fort_len = nchar;
    291 	return nchar;
    292 }
    293 
    294 /*
    295  *	This routine evaluates the arguments on the command line
    296  */
    297 static void
    298 getargs(int argc, char **argv)
    299 {
    300 	int ignore_case;
    301 #ifndef NO_REGEX
    302 	char *pat = NULL;
    303 #endif /* NO_REGEX */
    304 	int ch;
    305 
    306 	ignore_case = FALSE;
    307 
    308 #ifdef DEBUG
    309 	while ((ch = getopt(argc, argv, "aDefilm:osw")) != -1)
    310 #else
    311 	while ((ch = getopt(argc, argv, "aefilm:osw")) != -1)
    312 #endif /* DEBUG */
    313 		switch(ch) {
    314 		case 'a':		/* any fortune */
    315 			All_forts++;
    316 			break;
    317 #ifdef DEBUG
    318 		case 'D':
    319 			Debug++;
    320 			break;
    321 #endif /* DEBUG */
    322 		case 'e':
    323 			Equal_probs++;	/* scatter un-allocted prob equally */
    324 			break;
    325 		case 'f':		/* find fortune files */
    326 			Find_files++;
    327 			break;
    328 		case 'l':		/* long ones only */
    329 			Long_only++;
    330 			Short_only = FALSE;
    331 			break;
    332 		case 'o':		/* offensive ones only */
    333 			Offend++;
    334 			break;
    335 		case 's':		/* short ones only */
    336 			Short_only++;
    337 			Long_only = FALSE;
    338 			break;
    339 		case 'w':		/* give time to read */
    340 			Wait++;
    341 			break;
    342 #ifdef NO_REGEX
    343 		case 'i':			/* case-insensitive match */
    344 		case 'm':			/* dump out the fortunes */
    345 			errx(1, "Can't match fortunes on this system (Sorry)");
    346 #else /* NO_REGEX */
    347 		case 'm':			/* dump out the fortunes */
    348 			Match++;
    349 			pat = optarg;
    350 			break;
    351 		case 'i':			/* case-insensitive match */
    352 			ignore_case++;
    353 			break;
    354 #endif /* NO_REGEX */
    355 		case '?':
    356 		default:
    357 			usage();
    358 		}
    359 	argc -= optind;
    360 	argv += optind;
    361 
    362 	if (!form_file_list(argv, argc))
    363 		exit(1);	/* errors printed through form_file_list() */
    364 #ifdef DEBUG
    365 	if (Debug >= 1)
    366 		print_file_list();
    367 #endif /* DEBUG */
    368 	if (Find_files) {
    369 		print_file_list();
    370 		exit(0);
    371 	}
    372 
    373 #ifndef NO_REGEX
    374 	if (pat != NULL) {
    375 		re_setup(&Re_pat, pat, ignore_case);
    376 		rot13(pat, 0);
    377 		re_setup(&Re_pat13, pat, ignore_case);
    378 	}
    379 #endif /* NO_REGEX */
    380 }
    381 
    382 /*
    383  * form_file_list:
    384  *	Form the file list from the file specifications.
    385  */
    386 static int
    387 form_file_list(char **files, int file_cnt)
    388 {
    389 	int i, percent;
    390 	const char *sp;
    391 
    392 	if (file_cnt == 0) {
    393 		if (All_forts)
    394 			return add_file(NO_PROB, FORTDIR, NULL, &File_list,
    395 					&File_tail, NULL);
    396 		else
    397 			return add_file(NO_PROB, "fortunes", FORTDIR,
    398 					&File_list, &File_tail, NULL);
    399 	}
    400 	for (i = 0; i < file_cnt; i++) {
    401 		percent = NO_PROB;
    402 		if (!isdigit((unsigned char)files[i][0]))
    403 			sp = files[i];
    404 		else {
    405 			percent = 0;
    406 			for (sp = files[i]; isdigit((unsigned char)*sp); sp++)
    407 				percent = percent * 10 + *sp - '0';
    408 			if (percent > 100) {
    409 				warnx("Percentages must be <= 100");
    410 				return FALSE;
    411 			}
    412 			if (*sp == '.') {
    413 				warnx("Percentages must be integers");
    414 				return FALSE;
    415 			}
    416 			/*
    417 			 * If the number isn't followed by a '%', then
    418 			 * it was not a percentage, just the first part
    419 			 * of a file name which starts with digits.
    420 			 */
    421 			if (*sp != '%') {
    422 				percent = NO_PROB;
    423 				sp = files[i];
    424 			}
    425 			else if (*++sp == '\0') {
    426 				if (++i >= file_cnt) {
    427 					warnx("Percentages must precede files");
    428 					return FALSE;
    429 				}
    430 				sp = files[i];
    431 			}
    432 		}
    433 		if (strcmp(sp, "all") == 0)
    434 			sp = FORTDIR;
    435 		if (!add_file(percent, sp, NULL, &File_list, &File_tail, NULL))
    436 			return FALSE;
    437 	}
    438 	return TRUE;
    439 }
    440 
    441 /*
    442  * add_file:
    443  *	Add a file to the file list.
    444  */
    445 static int
    446 add_file(int percent, const char *file, const char *dir,
    447 	 FILEDESC **head, FILEDESC **tail, FILEDESC *parent)
    448 {
    449 	FILEDESC *fp;
    450 	int fd;
    451 	const char *path;
    452 	char *tpath, *offensive, *tfile = strdup(file), *tf;
    453 	bool isdir;
    454 
    455 	if (dir == NULL) {
    456 		path = tfile;
    457 		tpath = NULL;
    458 	}
    459 	else {
    460 		tpath = do_malloc(strlen(dir) + strlen(file) + 2);
    461 		(void) strcat(strcat(strcpy(tpath, dir), "/"), file);
    462 		path = tpath;
    463 	}
    464 	if ((isdir = is_dir(path)) && parent != NULL) {
    465 		if (tpath)
    466 			free(tpath);
    467 		free(tfile);
    468 		return FALSE;	/* don't recurse */
    469 	}
    470 	offensive = NULL;
    471 	if (!isdir && parent == NULL && (All_forts || Offend) &&
    472 	    !is_off_name(path)) {
    473 		offensive = off_name(path);
    474 		if (Offend) {
    475 			if (tpath) {
    476 				free(tpath);
    477 				tpath = NULL;
    478 			}
    479 			path = offensive;
    480 			tf = off_name(tfile);
    481 			free(tfile);
    482 			tfile = tf;
    483 		}
    484 	}
    485 
    486 	DPRINTF(1, (stderr, "adding file \"%s\"\n", path));
    487 over:
    488 	if ((fd = open(path, O_RDONLY)) < 0) {
    489 		/*
    490 		 * This is a sneak.  If the user said -a, and if the
    491 		 * file we're given isn't a file, we check to see if
    492 		 * there is a -o version.  If there is, we treat it as
    493 		 * if *that* were the file given.  We only do this for
    494 		 * individual files -- if we're scanning a directory,
    495 		 * we'll pick up the -o file anyway.
    496 		 */
    497 		if (All_forts && offensive != NULL && path != offensive) {
    498 			path = offensive;
    499 			if (tpath) {
    500 				free(tpath);
    501 				tpath = NULL;
    502 			}
    503 			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", tfile));
    504 			tf = off_name(tfile);
    505 			free(tfile);
    506 			tfile = tf;
    507 			goto over;
    508 		}
    509 		if (dir == NULL && tfile[0] != '/') {
    510 			int n = add_file(percent, tfile, FORTDIR, head, tail,
    511 					parent);
    512 			free(tfile);
    513 			if (offensive)
    514 				free(offensive);
    515 			return n;
    516 		}
    517 		if (parent == NULL)
    518 			warn("Cannot open `%s'", path);
    519 		if (tpath) {
    520 			free(tpath);
    521 			tpath = NULL;
    522 		}
    523 		free(tfile);
    524 		if (offensive)
    525 			free(offensive);
    526 		return FALSE;
    527 	}
    528 
    529 	DPRINTF(2, (stderr, "path = \"%s\"\n", path));
    530 
    531 	fp = new_fp();
    532 	fp->fd = fd;
    533 	fp->percent = percent;
    534 	fp->name = tfile;
    535 	fp->path = path;
    536 	fp->parent = parent;
    537 
    538 	if ((isdir && !add_dir(fp)) ||
    539 	    (!isdir &&
    540 	     !is_fortfile(path, &fp->datfile, &fp->posfile, (parent != NULL))))
    541 	{
    542 		if (parent == NULL)
    543 			warnx("`%s' not a fortune file or directory", path);
    544 		if (tpath) {
    545 			free(tpath);
    546 			tpath = NULL;
    547 		}
    548 		do_free(fp->datfile);
    549 		do_free(fp->posfile);
    550 		free(fp);
    551 		do_free(offensive);
    552 		return FALSE;
    553 	}
    554 	/*
    555 	 * If the user said -a, we need to make this node a pointer to
    556 	 * both files, if there are two.  We don't need to do this if
    557 	 * we are scanning a directory, since the scan will pick up the
    558 	 * -o file anyway.
    559 	 */
    560 	if (All_forts && parent == NULL && !is_off_name(path) && offensive)
    561 		all_forts(fp, offensive);
    562 	if (*head == NULL)
    563 		*head = *tail = fp;
    564 	else if (fp->percent == NO_PROB) {
    565 		(*tail)->next = fp;
    566 		fp->prev = *tail;
    567 		*tail = fp;
    568 	}
    569 	else {
    570 		(*head)->prev = fp;
    571 		fp->next = *head;
    572 		*head = fp;
    573 	}
    574 #ifdef OK_TO_WRITE_DISK
    575 	fp->was_pos_file = (access(fp->posfile, W_OK) >= 0);
    576 #endif /* OK_TO_WRITE_DISK */
    577 
    578 	return TRUE;
    579 }
    580 
    581 /*
    582  * new_fp:
    583  *	Return a pointer to an initialized new FILEDESC.
    584  */
    585 static FILEDESC *
    586 new_fp(void)
    587 {
    588 	FILEDESC *fp;
    589 
    590 	fp = do_malloc(sizeof *fp);
    591 	fp->datfd = -1;
    592 	fp->pos = POS_UNKNOWN;
    593 	fp->inf = NULL;
    594 	fp->fd = -1;
    595 	fp->percent = NO_PROB;
    596 	fp->read_tbl = FALSE;
    597 	fp->next = NULL;
    598 	fp->prev = NULL;
    599 	fp->child = NULL;
    600 	fp->parent = NULL;
    601 	fp->datfile = NULL;
    602 	fp->posfile = NULL;
    603 	return fp;
    604 }
    605 
    606 /*
    607  * off_name:
    608  *	Return a pointer to the offensive version of a file of this name.
    609  */
    610 static char *
    611 off_name(const char *file)
    612 {
    613 	char *new;
    614 
    615 	new = copy(file, strlen(file) + 2);
    616 	return strcat(new, "-o");
    617 }
    618 
    619 /*
    620  * is_off_name:
    621  *	Is the file an offensive-style name?
    622  */
    623 static int
    624 is_off_name(const char *file)
    625 {
    626 	int len;
    627 
    628 	len = strlen(file);
    629 	return (len >= 3 && file[len - 2] == '-' && file[len - 1] == 'o');
    630 }
    631 
    632 /*
    633  * all_forts:
    634  *	Modify a FILEDESC element to be the parent of two children if
    635  *	there are two children to be a parent of.
    636  */
    637 static void
    638 all_forts(FILEDESC *fp, const char *offensive)
    639 {
    640 	char *sp;
    641 	FILEDESC *scene, *obscene;
    642 	int fd;
    643 	char *datfile, *posfile;
    644 
    645 	posfile = NULL;
    646 
    647 	if (fp->child != NULL)	/* this is a directory, not a file */
    648 		return;
    649 	if (!is_fortfile(offensive, &datfile, &posfile, FALSE))
    650 		return;
    651 	if ((fd = open(offensive, O_RDONLY)) < 0)
    652 		return;
    653 	DPRINTF(1, (stderr, "adding \"%s\" because of -a\n", offensive));
    654 	scene = new_fp();
    655 	obscene = new_fp();
    656 	*scene = *fp;
    657 
    658 	fp->num_children = 2;
    659 	fp->child = scene;
    660 	scene->next = obscene;
    661 	obscene->next = NULL;
    662 	scene->child = obscene->child = NULL;
    663 	scene->parent = obscene->parent = fp;
    664 
    665 	fp->fd = -1;
    666 	scene->percent = obscene->percent = NO_PROB;
    667 
    668 	obscene->fd = fd;
    669 	obscene->inf = NULL;
    670 	obscene->path = offensive;
    671 	if ((sp = rindex(offensive, '/')) == NULL)
    672 		obscene->name = offensive;
    673 	else
    674 		obscene->name = ++sp;
    675 	obscene->datfile = datfile;
    676 	obscene->posfile = posfile;
    677 	obscene->read_tbl = FALSE;
    678 #ifdef OK_TO_WRITE_DISK
    679 	obscene->was_pos_file = (access(obscene->posfile, W_OK) >= 0);
    680 #endif /* OK_TO_WRITE_DISK */
    681 }
    682 
    683 /*
    684  * add_dir:
    685  *	Add the contents of an entire directory.
    686  */
    687 static int
    688 add_dir(FILEDESC *fp)
    689 {
    690 	DIR *dir;
    691 	struct dirent *dirent;
    692 	FILEDESC *tailp;
    693 	char *name;
    694 
    695 	(void) close(fp->fd);
    696 	fp->fd = -1;
    697 	if ((dir = opendir(fp->path)) == NULL) {
    698 		warn("Cannot open `%s'", fp->path);
    699 		return FALSE;
    700 	}
    701 	tailp = NULL;
    702 	DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
    703 	fp->num_children = 0;
    704 	while ((dirent = readdir(dir)) != NULL) {
    705 		if (NAMLEN(dirent) == 0)
    706 			continue;
    707 		name = copy(dirent->d_name, NAMLEN(dirent));
    708 		if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp))
    709 			fp->num_children++;
    710 		else
    711 			free(name);
    712 	}
    713 	(void) closedir(dir);
    714 	if (fp->num_children == 0) {
    715 		warnx("`%s': No fortune files in directory.", fp->path);
    716 		return FALSE;
    717 	}
    718 	return TRUE;
    719 }
    720 
    721 /*
    722  * is_dir:
    723  *	Return TRUE if the file is a directory, FALSE otherwise.
    724  */
    725 static int
    726 is_dir(const char *file)
    727 {
    728 	struct stat sbuf;
    729 
    730 	if (stat(file, &sbuf) < 0)
    731 		return FALSE;
    732 	return (S_ISDIR(sbuf.st_mode));
    733 }
    734 
    735 /*
    736  * is_fortfile:
    737  *	Return TRUE if the file is a fortune database file.  We try and
    738  *	exclude files without reading them if possible to avoid
    739  *	overhead.  Files which start with ".", or which have "illegal"
    740  *	suffixes, as contained in suflist[], are ruled out.
    741  */
    742 /* ARGSUSED */
    743 static int
    744 is_fortfile(const char *file, char **datp, char **posp, int check_for_offend)
    745 {
    746 	int i;
    747 	const char *sp;
    748 	char *datfile;
    749 	static const char *const suflist[] = {
    750 		/* list of "illegal" suffixes" */
    751 		"dat", "pos", "c", "h", "p", "i", "f",
    752 		"pas", "ftn", "ins.c", "ins,pas",
    753 		"ins.ftn", "sml",
    754 		NULL
    755 	};
    756 
    757 	DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
    758 
    759 	/*
    760 	 * Preclude any -o files for offendable people, and any non -o
    761 	 * files for completely offensive people.
    762 	 */
    763 	if (check_for_offend && !All_forts) {
    764 		i = strlen(file);
    765 		if (Offend ^ (file[i - 2] == '-' && file[i - 1] == 'o'))
    766 			return FALSE;
    767 	}
    768 
    769 	if ((sp = rindex(file, '/')) == NULL)
    770 		sp = file;
    771 	else
    772 		sp++;
    773 	if (*sp == '.') {
    774 		DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
    775 		return FALSE;
    776 	}
    777 	if ((sp = rindex(sp, '.')) != NULL) {
    778 		sp++;
    779 		for (i = 0; suflist[i] != NULL; i++)
    780 			if (strcmp(sp, suflist[i]) == 0) {
    781 				DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
    782 				return FALSE;
    783 			}
    784 	}
    785 
    786 	datfile = copy(file, strlen(file) + 4); /* +4 for ".dat" */
    787 	strcat(datfile, ".dat");
    788 	if (access(datfile, R_OK) < 0) {
    789 		free(datfile);
    790 		DPRINTF(2, (stderr, "FALSE (no \".dat\" file)\n"));
    791 		return FALSE;
    792 	}
    793 	if (datp != NULL)
    794 		*datp = datfile;
    795 	else
    796 		free(datfile);
    797 #ifdef OK_TO_WRITE_DISK
    798 	if (posp != NULL) {
    799 		*posp = copy(file, strlen(file) + 4); /* +4 for ".dat" */
    800 		(void) strcat(*posp, ".pos");
    801 	}
    802 #else
    803 	if (posp != NULL) {
    804 		*posp = NULL;
    805 	}
    806 #endif /* OK_TO_WRITE_DISK */
    807 	DPRINTF(2, (stderr, "TRUE\n"));
    808 	return TRUE;
    809 }
    810 
    811 /*
    812  * copy:
    813  *	Return a malloc()'ed copy of the string
    814  */
    815 static char *
    816 copy(const char *str, size_t len)
    817 {
    818 	char *new, *sp;
    819 
    820 	new = do_malloc(len + 1);
    821 	sp = new;
    822 	do {
    823 		*sp++ = *str;
    824 	} while (*str++);
    825 	return new;
    826 }
    827 
    828 /*
    829  * do_malloc:
    830  *	Do a malloc, checking for NULL return.
    831  */
    832 static void *
    833 do_malloc(size_t size)
    834 {
    835 	void *new;
    836 
    837 	if ((new = malloc(size)) == NULL)
    838 		err(1, NULL);
    839 	return new;
    840 }
    841 
    842 /*
    843  * do_free:
    844  *	Free malloc'ed space, if any.
    845  */
    846 static void
    847 do_free(void *ptr)
    848 {
    849 	if (ptr != NULL)
    850 		free(ptr);
    851 }
    852 
    853 /*
    854  * init_prob:
    855  *	Initialize the fortune probabilities.
    856  */
    857 static void
    858 init_prob(void)
    859 {
    860 	FILEDESC *fp, *last;
    861 	int percent, num_noprob, frac;
    862 
    863 	last = NULL;
    864 	/*
    865 	 * Distribute the residual probability (if any) across all
    866 	 * files with unspecified probability (i.e., probability of 0)
    867 	 * (if any).
    868 	 */
    869 
    870 	percent = 0;
    871 	num_noprob = 0;
    872 	for (fp = File_tail; fp != NULL; fp = fp->prev)
    873 		if (fp->percent == NO_PROB) {
    874 			num_noprob++;
    875 			if (Equal_probs)
    876 				last = fp;
    877 		} else
    878 			percent += fp->percent;
    879 	DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's",
    880 		    percent, num_noprob));
    881 	if (percent > 100)
    882 		errx(1, "Probabilities sum to %d%%!", percent);
    883 	else if (percent < 100 && num_noprob == 0)
    884 		errx(1, "No place to put residual probability (%d%%)",
    885 		    100 - percent);
    886 	else if (percent == 100 && num_noprob != 0)
    887 		errx(1, "No probability left to put in residual files");
    888 	percent = 100 - percent;
    889 	if (Equal_probs) {
    890 		if (num_noprob != 0) {
    891 			if (num_noprob > 1) {
    892 				frac = percent / num_noprob;
    893 				DPRINTF(1, (stderr, ", frac = %d%%", frac));
    894 				for (fp = File_tail; fp != last; fp = fp->prev)
    895 					if (fp->percent == NO_PROB) {
    896 						fp->percent = frac;
    897 						percent -= frac;
    898 					}
    899 			}
    900 			last->percent = percent;
    901 			DPRINTF(1, (stderr, ", residual = %d%%", percent));
    902 		}
    903 	} else {
    904 		DPRINTF(1, (stderr,
    905 			    ", %d%% distributed over remaining fortunes\n",
    906 			    percent));
    907 	}
    908 	DPRINTF(1, (stderr, "\n"));
    909 
    910 #ifdef DEBUG
    911 	if (Debug >= 1)
    912 		print_file_list();
    913 #endif
    914 }
    915 
    916 /*
    917  * get_fort:
    918  *	Get the fortune data file's seek pointer for the next fortune.
    919  */
    920 static void
    921 get_fort(void)
    922 {
    923 	FILEDESC *fp;
    924 	int choice;
    925 
    926 	if (File_list->next == NULL || File_list->percent == NO_PROB)
    927 		fp = File_list;
    928 	else {
    929 		choice = arc4random_uniform(100);
    930 		DPRINTF(1, (stderr, "choice = %d\n", choice));
    931 		for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
    932 			if (choice < fp->percent)
    933 				break;
    934 			else {
    935 				choice -= fp->percent;
    936 				DPRINTF(1, (stderr,
    937 					    "    skip \"%s\", %d%% (choice = %d)\n",
    938 					    fp->name, fp->percent, choice));
    939 			}
    940 			DPRINTF(1, (stderr,
    941 				    "using \"%s\", %d%% (choice = %d)\n",
    942 				    fp->name, fp->percent, choice));
    943 	}
    944 	if (fp->percent != NO_PROB)
    945 		get_tbl(fp);
    946 	else {
    947 		if (fp->next != NULL) {
    948 			sum_noprobs(fp);
    949 			choice = arc4random_uniform(Noprob_tbl.str_numstr);
    950 			DPRINTF(1, (stderr, "choice = %d (of %d) \n", choice,
    951 				    Noprob_tbl.str_numstr));
    952 			while ((u_int32_t)choice >= fp->tbl.str_numstr) {
    953 				choice -= fp->tbl.str_numstr;
    954 				fp = fp->next;
    955 				DPRINTF(1, (stderr,
    956 					    "    skip \"%s\", %d (choice = %d)\n",
    957 					    fp->name, fp->tbl.str_numstr,
    958 					    choice));
    959 			}
    960 			DPRINTF(1, (stderr, "using \"%s\", %d\n", fp->name,
    961 				    fp->tbl.str_numstr));
    962 		}
    963 		get_tbl(fp);
    964 	}
    965 	if (fp->child != NULL) {
    966 		DPRINTF(1, (stderr, "picking child\n"));
    967 		fp = pick_child(fp);
    968 	}
    969 	Fortfile = fp;
    970 	get_pos(fp);
    971 	open_dat(fp);
    972 	(void) lseek(fp->datfd,
    973 		     (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), SEEK_SET);
    974 	read(fp->datfd, Seekpts, sizeof Seekpts);
    975 	BE64TOH(Seekpts[0]);
    976 	BE64TOH(Seekpts[1]);
    977 }
    978 
    979 /*
    980  * pick_child
    981  *	Pick a child from a chosen parent.
    982  */
    983 static FILEDESC *
    984 pick_child(FILEDESC *parent)
    985 {
    986 	FILEDESC *fp;
    987 	int  choice;
    988 
    989 	if (Equal_probs) {
    990 		choice = arc4random_uniform(parent->num_children);
    991 		DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
    992 			    choice, parent->num_children));
    993 		for (fp = parent->child; choice--; fp = fp->next)
    994 			continue;
    995 		DPRINTF(1, (stderr, "    using %s\n", fp->name));
    996 		return fp;
    997 	}
    998 	else {
    999 		get_tbl(parent);
   1000 		choice = arc4random_uniform(parent->tbl.str_numstr);
   1001 		DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
   1002 			    choice, parent->tbl.str_numstr));
   1003 		for (fp = parent->child; (u_int32_t)choice >= fp->tbl.str_numstr;
   1004 		     fp = fp->next) {
   1005 			choice -= fp->tbl.str_numstr;
   1006 			DPRINTF(1, (stderr, "\tskip %s, %d (choice = %d)\n",
   1007 				    fp->name, fp->tbl.str_numstr, choice));
   1008 		}
   1009 		DPRINTF(1, (stderr, "    using %s, %d\n", fp->name,
   1010 			    fp->tbl.str_numstr));
   1011 		return fp;
   1012 	}
   1013 }
   1014 
   1015 /*
   1016  * sum_noprobs:
   1017  *	Sum up all the noprob probabilities, starting with fp.
   1018  */
   1019 static void
   1020 sum_noprobs(FILEDESC *fp)
   1021 {
   1022 	static bool did_noprobs = FALSE;
   1023 
   1024 	if (did_noprobs)
   1025 		return;
   1026 	zero_tbl(&Noprob_tbl);
   1027 	while (fp != NULL) {
   1028 		get_tbl(fp);
   1029 		sum_tbl(&Noprob_tbl, &fp->tbl);
   1030 		fp = fp->next;
   1031 	}
   1032 	did_noprobs = TRUE;
   1033 }
   1034 
   1035 static int
   1036 max(int i, int j)
   1037 {
   1038 	return (i >= j ? i : j);
   1039 }
   1040 
   1041 /*
   1042  * open_fp:
   1043  *	Assocatiate a FILE * with the given FILEDESC.
   1044  */
   1045 static void
   1046 open_fp(FILEDESC *fp)
   1047 {
   1048 	if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL)
   1049 		err(1, "Cannot open `%s'", fp->path);
   1050 }
   1051 
   1052 /*
   1053  * open_dat:
   1054  *	Open up the dat file if we need to.
   1055  */
   1056 static void
   1057 open_dat(FILEDESC *fp)
   1058 {
   1059 	if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, O_RDONLY)) < 0)
   1060 		err(1, "Cannot open `%s'", fp->datfile);
   1061 }
   1062 
   1063 /*
   1064  * get_pos:
   1065  *	Get the position from the pos file, if there is one.  If not,
   1066  *	return a random number.
   1067  */
   1068 static void
   1069 get_pos(FILEDESC *fp)
   1070 {
   1071 #ifdef OK_TO_WRITE_DISK
   1072 	int fd;
   1073 #endif /* OK_TO_WRITE_DISK */
   1074 
   1075 	assert(fp->read_tbl);
   1076 	if (fp->pos == POS_UNKNOWN) {
   1077 #ifdef OK_TO_WRITE_DISK
   1078 		if ((fd = open(fp->posfile, O_RDONLY)) < 0 ||
   1079 		    read(fd, &fp->pos, sizeof fp->pos) != sizeof fp->pos)
   1080 			fp->pos = arc4random_uniform(fp->tbl.str_numstr);
   1081 		else if (fp->pos >= fp->tbl.str_numstr)
   1082 			fp->pos %= fp->tbl.str_numstr;
   1083 		if (fd >= 0)
   1084 			(void) close(fd);
   1085 #else
   1086 		fp->pos = arc4random_uniform(fp->tbl.str_numstr);
   1087 #endif /* OK_TO_WRITE_DISK */
   1088 	}
   1089 	if ((u_int64_t)++(fp->pos) >= fp->tbl.str_numstr)
   1090 		fp->pos -= fp->tbl.str_numstr;
   1091 	DPRINTF(1, (stderr, "pos for %s is %lld\n", fp->name,
   1092 	    (long long)fp->pos));
   1093 }
   1094 
   1095 /*
   1096  * get_tbl:
   1097  *	Get the tbl data file the datfile.
   1098  */
   1099 static void
   1100 get_tbl(FILEDESC *fp)
   1101 {
   1102 	int fd;
   1103 	FILEDESC *child;
   1104 
   1105 	if (fp->read_tbl)
   1106 		return;
   1107 	if (fp->child == NULL) {
   1108 		if ((fd = open(fp->datfile, O_RDONLY)) < 0)
   1109 			err(1, "Cannot open `%s'", fp->datfile);
   1110 		if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl) {
   1111 			errx(1, "Database `%s' corrupted", fp->path);
   1112 		}
   1113 		/* BE32TOH(fp->tbl.str_version); */
   1114 		BE32TOH(fp->tbl.str_numstr);
   1115 		BE32TOH(fp->tbl.str_longlen);
   1116 		BE32TOH(fp->tbl.str_shortlen);
   1117 		BE32TOH(fp->tbl.str_flags);
   1118 		(void) close(fd);
   1119 	}
   1120 	else {
   1121 		zero_tbl(&fp->tbl);
   1122 		for (child = fp->child; child != NULL; child = child->next) {
   1123 			get_tbl(child);
   1124 			sum_tbl(&fp->tbl, &child->tbl);
   1125 		}
   1126 	}
   1127 	fp->read_tbl = TRUE;
   1128 }
   1129 
   1130 /*
   1131  * zero_tbl:
   1132  *	Zero out the fields we care about in a tbl structure.
   1133  */
   1134 static void
   1135 zero_tbl(STRFILE *tp)
   1136 {
   1137 	tp->str_numstr = 0;
   1138 	tp->str_longlen = 0;
   1139 	tp->str_shortlen = -1;
   1140 }
   1141 
   1142 /*
   1143  * sum_tbl:
   1144  *	Merge the tbl data of t2 into t1.
   1145  */
   1146 static void
   1147 sum_tbl(STRFILE *t1, STRFILE *t2)
   1148 {
   1149 	t1->str_numstr += t2->str_numstr;
   1150 	if (t1->str_longlen < t2->str_longlen)
   1151 		t1->str_longlen = t2->str_longlen;
   1152 	if (t1->str_shortlen > t2->str_shortlen)
   1153 		t1->str_shortlen = t2->str_shortlen;
   1154 }
   1155 
   1156 #define	STR(str)	((str) == NULL ? "NULL" : (str))
   1157 
   1158 /*
   1159  * print_file_list:
   1160  *	Print out the file list
   1161  */
   1162 static void
   1163 print_file_list(void)
   1164 {
   1165 	print_list(File_list, 0);
   1166 }
   1167 
   1168 /*
   1169  * print_list:
   1170  *	Print out the actual list, recursively.
   1171  */
   1172 static void
   1173 print_list(FILEDESC *list, int lev)
   1174 {
   1175 	while (list != NULL) {
   1176 		fprintf(stderr, "%*s", lev * 4, "");
   1177 		if (list->percent == NO_PROB)
   1178 			fprintf(stderr, "___%%");
   1179 		else
   1180 			fprintf(stderr, "%3d%%", list->percent);
   1181 		fprintf(stderr, " %s", STR(list->name));
   1182 		DPRINTF(1, (stderr, " (%s, %s, %s)\n", STR(list->path),
   1183 			    STR(list->datfile), STR(list->posfile)));
   1184 		putc('\n', stderr);
   1185 		if (list->child != NULL)
   1186 			print_list(list->child, lev + 1);
   1187 		list = list->next;
   1188 	}
   1189 }
   1190 
   1191 #ifndef NO_REGEX
   1192 
   1193 /*
   1194  * re_setup:
   1195  *	Initialize regular expression pattern.
   1196  */
   1197 static void
   1198 re_setup(struct re *rx, const char *pattern, bool ignore_case)
   1199 {
   1200 	int code, flags;
   1201 	char errbuf[1024];
   1202 
   1203 	assert(!rx->valid);
   1204 
   1205 	flags = REG_EXTENDED | REG_NOSUB;
   1206 	if (ignore_case) {
   1207 		flags |= REG_ICASE;
   1208 	}
   1209 	code = regcomp(&rx->regex, pattern, flags);
   1210 
   1211 	if (code != 0) {
   1212 		regerror(code, &rx->regex, errbuf, sizeof(errbuf));
   1213 		warnx("%s: `%s'", errbuf, pattern);
   1214 		regfree(&rx->regex);
   1215 		rx->valid = FALSE;
   1216 	}
   1217 	rx->valid = TRUE;
   1218 }
   1219 
   1220 /*
   1221  * re_cleanup:
   1222  *	Undo re_setup.
   1223  */
   1224 static void
   1225 re_cleanup(struct re *rx)
   1226 {
   1227 	if (rx->valid) {
   1228 		regfree(&rx->regex);
   1229 		rx->valid = FALSE;
   1230 	}
   1231 }
   1232 
   1233 static bool
   1234 re_match(struct re *rx, const char *string)
   1235 {
   1236 	return regexec(&rx->regex, string, 0, NULL, 0) == 0;
   1237 }
   1238 
   1239 /*
   1240  * find_matches:
   1241  *	Find all the fortunes which match the pattern we've been given.
   1242  */
   1243 static int
   1244 find_matches(void)
   1245 {
   1246 	Fort_len = maxlen_in_list(File_list);
   1247 	DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
   1248 	/* extra length, "%\n" is appended */
   1249 	Fortbuf = do_malloc(Fort_len + 10);
   1250 
   1251 	Found_one = FALSE;
   1252 	matches_in_list(File_list);
   1253 	return Found_one;
   1254 	/* NOTREACHED */
   1255 }
   1256 
   1257 /*
   1258  * maxlen_in_list
   1259  *	Return the maximum fortune len in the file list.
   1260  */
   1261 static size_t
   1262 maxlen_in_list(FILEDESC *list)
   1263 {
   1264 	FILEDESC *fp;
   1265 	size_t len, maxlen;
   1266 
   1267 	maxlen = 0;
   1268 	for (fp = list; fp != NULL; fp = fp->next) {
   1269 		if (fp->child != NULL) {
   1270 			if ((len = maxlen_in_list(fp->child)) > maxlen)
   1271 				maxlen = len;
   1272 		}
   1273 		else {
   1274 			get_tbl(fp);
   1275 			if (fp->tbl.str_longlen > maxlen)
   1276 				maxlen = fp->tbl.str_longlen;
   1277 		}
   1278 	}
   1279 	return maxlen;
   1280 }
   1281 
   1282 /*
   1283  * matches_in_list
   1284  *	Print out the matches from the files in the list.
   1285  */
   1286 static void
   1287 matches_in_list(FILEDESC *list)
   1288 {
   1289 	char *sp;
   1290 	FILEDESC *fp;
   1291 	int in_file;
   1292 
   1293 	if (!Re_pat.valid || !Re_pat13.valid)
   1294 		return;
   1295 
   1296 	for (fp = list; fp != NULL; fp = fp->next) {
   1297 		if (fp->child != NULL) {
   1298 			matches_in_list(fp->child);
   1299 			continue;
   1300 		}
   1301 		DPRINTF(1, (stderr, "searching in %s\n", fp->path));
   1302 		open_fp(fp);
   1303 		sp = Fortbuf;
   1304 		in_file = FALSE;
   1305 		while (fgets(sp, Fort_len, fp->inf) != NULL)
   1306 			if (!STR_ENDSTRING(sp, fp->tbl))
   1307 				sp += strlen(sp);
   1308 			else {
   1309 				*sp = '\0';
   1310 				if (fp->tbl.str_flags & STR_ROTATED)
   1311 					Re_use = &Re_pat13;
   1312 				else
   1313 					Re_use = &Re_pat;
   1314 				if (re_match(Re_use, Fortbuf)) {
   1315 					printf("%c%c", fp->tbl.str_delim,
   1316 					    fp->tbl.str_delim);
   1317 					if (!in_file) {
   1318 						printf(" (%s)", fp->name);
   1319 						Found_one = TRUE;
   1320 						in_file = TRUE;
   1321 					}
   1322 					putchar('\n');
   1323 					if (fp->tbl.str_flags & STR_ROTATED)
   1324 						rot13(Fortbuf, (sp - Fortbuf));
   1325 					(void) fwrite(Fortbuf, 1, (sp - Fortbuf), stdout);
   1326 				}
   1327 				sp = Fortbuf;
   1328 			}
   1329 	}
   1330 	re_cleanup(&Re_pat);
   1331 	re_cleanup(&Re_pat13);
   1332 }
   1333 #endif /* NO_REGEX */
   1334 
   1335 static void
   1336 usage(void)
   1337 {
   1338 
   1339 	(void) fprintf(stderr, "Usage: %s [-ae", getprogname());
   1340 #ifdef DEBUG
   1341 	(void) fprintf(stderr, "D");
   1342 #endif /* DEBUG */
   1343 	(void) fprintf(stderr, "f");
   1344 #ifndef NO_REGEX
   1345 	(void) fprintf(stderr, "i");
   1346 #endif /* NO_REGEX */
   1347 	(void) fprintf(stderr, "losw]");
   1348 #ifndef NO_REGEX
   1349 	(void) fprintf(stderr, " [-m pattern]");
   1350 #endif /* NO_REGEX */
   1351 	(void) fprintf(stderr, "[ [#%%] file/directory/all]\n");
   1352 	exit(1);
   1353 }
   1354