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