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
70typedef 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
86static bool	Found_one;		/* did we find a match? */
87static bool	Find_files = FALSE;	/* just find a list of proper fortune files */
88static bool	Fortunes_only = FALSE;	/* check only "fortunes" files */
89static bool	Wait = FALSE;		/* wait desired after fortune */
90static bool	Short_only = FALSE;	/* short fortune desired */
91static bool	Long_only = FALSE;	/* long fortune desired */
92static bool	Offend = FALSE;		/* offensive fortunes only */
93static bool	All_forts = FALSE;	/* any fortune allowed */
94static bool	Equal_probs = FALSE;	/* scatter un-allocted prob equally */
95static bool	Match = FALSE;		/* dump fortunes matching a pattern */
96static bool	WriteToDisk = false;	/* use files on disk to save state */
97#ifdef DEBUG
98static int	Debug = 0;		/* print debug messages */
99#endif
100
101static char	*Fortbuf = NULL;	/* fortune buffer for -m */
102
103static int	Fort_len = 0;
104
105static off_t	Seekpts[2];		/* seek pointers to fortunes */
106
107static FILEDESC	*File_list = NULL,	/* Head of file list */
108		*File_tail = NULL;	/* Tail of file list */
109static FILEDESC	*Fortfile;		/* Fortune file to use */
110
111static STRFILE	Noprob_tbl;		/* sum of data for all no prob files */
112
113static const char *Fortune_path;
114static char	**Fortune_path_arr;
115
116static int	 add_dir(FILEDESC *);
117static int	 add_file(int, const char *, const char *, FILEDESC **,
118		     FILEDESC **, FILEDESC *);
119static void	 all_forts(FILEDESC *, char *);
120static char	*copy(const char *, u_int);
121static void	 display(FILEDESC *);
122static void	 do_free(void *);
123static void	*do_malloc(u_int);
124static int	 form_file_list(char **, int);
125static int	 fortlen(void);
126static void	 get_fort(void);
127static void	 get_pos(FILEDESC *);
128static void	 get_tbl(FILEDESC *);
129static void	 getargs(int, char *[]);
130static void	 getpath(void);
131static void	 init_prob(void);
132static int	 is_dir(const char *);
133static int	 is_fortfile(const char *, char **, char **, int);
134static int	 is_off_name(const char *);
135static int	 max(int, int);
136static FILEDESC *new_fp(void);
137static char	*off_name(const char *);
138static void	 open_dat(FILEDESC *);
139static void	 open_fp(FILEDESC *);
140static FILEDESC *pick_child(FILEDESC *);
141static void	 print_file_list(void);
142static void	 print_list(FILEDESC *, int);
143static void	 sum_noprobs(FILEDESC *);
144static void	 sum_tbl(STRFILE *, STRFILE *);
145static void	 usage(void);
146static void	 zero_tbl(STRFILE *);
147
148static char	*conv_pat(char *);
149static int	 find_matches(void);
150static void	 matches_in_list(FILEDESC *);
151static int	 maxlen_in_list(FILEDESC *);
152
153static regex_t Re_pat;
154
155int
156main(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
204static void
205display(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 */
237static int
238fortlen(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 */
261static void
262getargs(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 */
347static int
348form_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 */
442static int
443add_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));
485over:
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 */
583static FILEDESC *
584new_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 */
609static char *
610off_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 */
623static int
624is_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 */
638static void
639all_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 */
685static int
686add_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 */
724static int
725is_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 */
743static int
744is_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 */
821static char *
822copy(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 */
839static void *
840do_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 */
856static void
857do_free(void *ptr)
858{
859	if (ptr != NULL)
860		free(ptr);
861}
862
863/*
864 * init_prob:
865 *	Initialize the fortune probabilities.
866 */
867static void
868init_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 */
936static void
937get_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 */
1000static FILEDESC *
1001pick_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 */
1036static void
1037sum_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
1052static int
1053max(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 */
1062static void
1063open_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 */
1075static void
1076open_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 */
1089static void
1090get_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 */
1117static void
1118get_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 */
1156static void
1157zero_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 */
1168static void
1169sum_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 */
1184static void
1185print_file_list(void)
1186{
1187	print_list(File_list, 0);
1188}
1189
1190/*
1191 * print_list:
1192 *	Print out the actual list, recursively.
1193 */
1194static void
1195print_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 */
1217static char *
1218conv_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 */
1260static int
1261find_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 */
1278static int
1279maxlen_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 */
1304static void
1305matches_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
1355static void
1356usage(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
1374static void
1375getpath(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