12490Sjkh/*-
22490Sjkh * Copyright (c) 1986, 1993
32490Sjkh *	The Regents of the University of California.  All rights reserved.
42490Sjkh *
52490Sjkh * This code is derived from software contributed to Berkeley by
62490Sjkh * Ken Arnold.
72490Sjkh *
82490Sjkh * Redistribution and use in source and binary forms, with or without
92490Sjkh * modification, are permitted provided that the following conditions
102490Sjkh * are met:
112490Sjkh * 1. Redistributions of source code must retain the above copyright
122490Sjkh *    notice, this list of conditions and the following disclaimer.
132490Sjkh * 2. Redistributions in binary form must reproduce the above copyright
142490Sjkh *    notice, this list of conditions and the following disclaimer in the
152490Sjkh *    documentation and/or other materials provided with the distribution.
16203926Suqs * 3. Neither the name of the University nor the names of its contributors
172490Sjkh *    may be used to endorse or promote products derived from this software
182490Sjkh *    without specific prior written permission.
192490Sjkh *
202490Sjkh * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
212490Sjkh * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
222490Sjkh * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
232490Sjkh * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
242490Sjkh * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
252490Sjkh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
262490Sjkh * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
272490Sjkh * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
282490Sjkh * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
292490Sjkh * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
302490Sjkh * SUCH DAMAGE.
312490Sjkh */
322490Sjkh
33114725Sobrien#if 0
342490Sjkh#ifndef lint
3515944Sachestatic const char copyright[] =
362490Sjkh"@(#) Copyright (c) 1986, 1993\n\
372490Sjkh	The Regents of the University of California.  All rights reserved.\n";
382490Sjkh#endif /* not lint */
392490Sjkh
402490Sjkh#ifndef lint
4115944Sachestatic const char sccsid[] = "@(#)fortune.c   8.1 (Berkeley) 5/31/93";
42114725Sobrien#endif /* not lint */
4353920Sbillf#endif
44114725Sobrien#include <sys/cdefs.h>
45114725Sobrien__FBSDID("$FreeBSD$");
462490Sjkh
47203926Suqs#include <sys/stat.h>
48203926Suqs#include <sys/endian.h>
492490Sjkh
50203926Suqs#include <assert.h>
51203926Suqs#include <ctype.h>
52203926Suqs#include <dirent.h>
53203926Suqs#include <fcntl.h>
54203926Suqs#include <locale.h>
55203926Suqs#include <regex.h>
56242576Seadler#include <stdbool.h>
57203926Suqs#include <stdio.h>
58203926Suqs#include <stdlib.h>
59203926Suqs#include <string.h>
60203926Suqs#include <time.h>
61203926Suqs#include <unistd.h>
622490Sjkh
63203926Suqs#include "strfile.h"
64203926Suqs#include "pathnames.h"
652490Sjkh
66242576Seadler#define	TRUE	true
67242576Seadler#define	FALSE	false
682490Sjkh
69203926Suqs#define	MINW	6		/* minimum wait if desired */
70203926Suqs#define	CPERS	20		/* # of chars for each sec */
71203926Suqs#define	SLEN	160		/* # of chars in short fortune */
722490Sjkh
73203926Suqs#define	POS_UNKNOWN	((uint32_t) -1)	/* pos for file unknown */
74203926Suqs#define	NO_PROB		(-1)		/* no prob specified for file */
752490Sjkh
76203926Suqs#ifdef	DEBUG
77203926Suqs#define	DPRINTF(l,x)	{ if (Debug >= l) fprintf x; }
78203926Suqs#undef	NDEBUG
79203926Suqs#else
80203926Suqs#define	DPRINTF(l,x)
81203926Suqs#define	NDEBUG	1
82203926Suqs#endif
83203926Suqs
842490Sjkhtypedef struct fd {
852490Sjkh	int		percent;
862490Sjkh	int		fd, datfd;
87142022Sru	uint32_t	pos;
882490Sjkh	FILE		*inf;
89203922Suqs	const char	*name;
90203922Suqs	const char	*path;
912490Sjkh	char		*datfile, *posfile;
922490Sjkh	bool		read_tbl;
932490Sjkh	bool		was_pos_file;
942490Sjkh	STRFILE		tbl;
952490Sjkh	int		num_children;
962490Sjkh	struct fd	*child, *parent;
972490Sjkh	struct fd	*next, *prev;
982490Sjkh} FILEDESC;
992490Sjkh
100227101Sedstatic bool	Found_one;		/* did we find a match? */
101227101Sedstatic bool	Find_files = FALSE;	/* just find a list of proper fortune files */
102227101Sedstatic bool	Fortunes_only = FALSE;	/* check only "fortunes" files */
103227101Sedstatic bool	Wait = FALSE;		/* wait desired after fortune */
104227101Sedstatic bool	Short_only = FALSE;	/* short fortune desired */
105227101Sedstatic bool	Long_only = FALSE;	/* long fortune desired */
106227101Sedstatic bool	Offend = FALSE;		/* offensive fortunes only */
107227101Sedstatic bool	All_forts = FALSE;	/* any fortune allowed */
108227101Sedstatic bool	Equal_probs = FALSE;	/* scatter un-allocted prob equally */
109227101Sedstatic bool	Match = FALSE;		/* dump fortunes matching a pattern */
110242577Seadlerstatic bool	WriteToDisk = false;	/* use files on disk to save state */
1112490Sjkh#ifdef DEBUG
112243036Sdimstatic int	Debug = 0;		/* print debug messages */
1132490Sjkh#endif
1142490Sjkh
115227101Sedstatic char	*Fortbuf = NULL;	/* fortune buffer for -m */
1162490Sjkh
117227101Sedstatic int	Fort_len = 0;
1182490Sjkh
119227101Sedstatic off_t	Seekpts[2];		/* seek pointers to fortunes */
1202490Sjkh
121227101Sedstatic FILEDESC	*File_list = NULL,	/* Head of file list */
1222490Sjkh		*File_tail = NULL;	/* Tail of file list */
123227101Sedstatic FILEDESC	*Fortfile;		/* Fortune file to use */
1242490Sjkh
125227101Sedstatic STRFILE	Noprob_tbl;		/* sum of data for all no prob files */
1262490Sjkh
127227101Sedstatic const char *Fortune_path;
128227101Sedstatic char	**Fortune_path_arr;
129173396Sedwin
130227101Sedstatic int	 add_dir(FILEDESC *);
131227101Sedstatic int	 add_file(int, const char *, const char *, FILEDESC **,
132227101Sed		     FILEDESC **, FILEDESC *);
133227101Sedstatic void	 all_forts(FILEDESC *, char *);
134227101Sedstatic char	*copy(const char *, u_int);
135227101Sedstatic void	 display(FILEDESC *);
136227101Sedstatic void	 do_free(void *);
137227101Sedstatic void	*do_malloc(u_int);
138227101Sedstatic int	 form_file_list(char **, int);
139227101Sedstatic int	 fortlen(void);
140227101Sedstatic void	 get_fort(void);
141227101Sedstatic void	 get_pos(FILEDESC *);
142227101Sedstatic void	 get_tbl(FILEDESC *);
143227101Sedstatic void	 getargs(int, char *[]);
144227101Sedstatic void	 getpath(void);
145227101Sedstatic void	 init_prob(void);
146227101Sedstatic int	 is_dir(const char *);
147227101Sedstatic int	 is_fortfile(const char *, char **, char **, int);
148227101Sedstatic int	 is_off_name(const char *);
149227101Sedstatic int	 max(int, int);
150227101Sedstatic FILEDESC *new_fp(void);
151227101Sedstatic char	*off_name(const char *);
152227101Sedstatic void	 open_dat(FILEDESC *);
153227101Sedstatic void	 open_fp(FILEDESC *);
154227101Sedstatic FILEDESC *pick_child(FILEDESC *);
155227101Sedstatic void	 print_file_list(void);
156227101Sedstatic void	 print_list(FILEDESC *, int);
157227101Sedstatic void	 sum_noprobs(FILEDESC *);
158227101Sedstatic void	 sum_tbl(STRFILE *, STRFILE *);
159227101Sedstatic void	 usage(void);
160227101Sedstatic void	 zero_tbl(STRFILE *);
1612490Sjkh
162227101Sedstatic char	*conv_pat(char *);
163227101Sedstatic int	 find_matches(void);
164227101Sedstatic void	 matches_in_list(FILEDESC *);
165227101Sedstatic int	 maxlen_in_list(FILEDESC *);
1662490Sjkh
167130851Sphkstatic regex_t Re_pat;
1682490Sjkh
1692490Sjkhint
170203922Suqsmain(int argc, char *argv[])
1712490Sjkh{
1722490Sjkh	int	fd;
1732490Sjkh
174242577Seadler	if (getenv("FORTUNE_SAVESTATE") != NULL)
175242577Seadler		WriteToDisk = true;
176242577Seadler
17717537Sache	(void) setlocale(LC_ALL, "");
17815944Sache
179173396Sedwin	getpath();
180203922Suqs	getargs(argc, argv);
1812490Sjkh
1822490Sjkh	if (Match)
1832490Sjkh		exit(find_matches() != 0);
1842490Sjkh
1852490Sjkh	init_prob();
1862490Sjkh	do {
1872490Sjkh		get_fort();
1882490Sjkh	} while ((Short_only && fortlen() > SLEN) ||
1892490Sjkh		 (Long_only && fortlen() <= SLEN));
1902490Sjkh
1912490Sjkh	display(Fortfile);
1922490Sjkh
193242577Seadler	if (WriteToDisk) {
194242577Seadler		if ((fd = creat(Fortfile->posfile, 0666)) < 0) {
195242577Seadler			perror(Fortfile->posfile);
196242577Seadler			exit(1);
197242577Seadler		}
198242577Seadler		/*
199242577Seadler		 * if we can, we exclusive lock, but since it isn't very
200242577Seadler		 * important, we just punt if we don't have easy locking
201242577Seadler		 * available.
202242577Seadler		 */
203242577Seadler		flock(fd, LOCK_EX);
204242577Seadler		write(fd, (char *) &Fortfile->pos, sizeof Fortfile->pos);
205242577Seadler		if (!Fortfile->was_pos_file)
206242577Seadler		chmod(Fortfile->path, 0666);
207242577Seadler		flock(fd, LOCK_UN);
2082490Sjkh	}
2092490Sjkh	if (Wait) {
2102490Sjkh		if (Fort_len == 0)
2112490Sjkh			(void) fortlen();
2122490Sjkh		sleep((unsigned int) max(Fort_len / CPERS, MINW));
2132490Sjkh	}
214203926Suqs
215204178Suqs	exit(0);
2162490Sjkh}
2172490Sjkh
218227101Sedstatic void
219203922Suqsdisplay(FILEDESC *fp)
2202490Sjkh{
22153210Sbillf	char   *p;
22253210Sbillf	unsigned char ch;
2232490Sjkh	char	line[BUFSIZ];
2242490Sjkh
2252490Sjkh	open_fp(fp);
226203926Suqs	fseeko(fp->inf, Seekpts[0], SEEK_SET);
2272490Sjkh	for (Fort_len = 0; fgets(line, sizeof line, fp->inf) != NULL &&
2282490Sjkh	    !STR_ENDSTRING(line, fp->tbl); Fort_len++) {
2292490Sjkh		if (fp->tbl.str_flags & STR_ROTATED)
23015944Sache			for (p = line; (ch = *p) != '\0'; ++p) {
23115944Sache				if (isascii(ch)) {
23215944Sache					if (isupper(ch))
23315944Sache						*p = 'A' + (ch - 'A' + 13) % 26;
23415944Sache					else if (islower(ch))
23515944Sache						*p = 'a' + (ch - 'a' + 13) % 26;
23615944Sache				}
23715944Sache			}
23851863Sdcs		if (fp->tbl.str_flags & STR_COMMENTS
23951863Sdcs		    && line[0] == fp->tbl.str_delim
24051863Sdcs		    && line[1] == fp->tbl.str_delim)
24151863Sdcs			continue;
2422490Sjkh		fputs(line, stdout);
2432490Sjkh	}
2442490Sjkh	(void) fflush(stdout);
2452490Sjkh}
2462490Sjkh
2472490Sjkh/*
2482490Sjkh * fortlen:
2492490Sjkh *	Return the length of the fortune.
2502490Sjkh */
251227101Sedstatic int
252203922Suqsfortlen(void)
2532490Sjkh{
25453210Sbillf	int	nchar;
255173401Sedwin	char	line[BUFSIZ];
2562490Sjkh
2572490Sjkh	if (!(Fortfile->tbl.str_flags & (STR_RANDOM | STR_ORDERED)))
258123905Sceri		nchar = (int)(Seekpts[1] - Seekpts[0]);
2592490Sjkh	else {
2602490Sjkh		open_fp(Fortfile);
261203926Suqs		fseeko(Fortfile->inf, Seekpts[0], SEEK_SET);
2622490Sjkh		nchar = 0;
2632490Sjkh		while (fgets(line, sizeof line, Fortfile->inf) != NULL &&
2642490Sjkh		       !STR_ENDSTRING(line, Fortfile->tbl))
2652490Sjkh			nchar += strlen(line);
2662490Sjkh	}
2672490Sjkh	Fort_len = nchar;
268203926Suqs
269203926Suqs	return (nchar);
2702490Sjkh}
2712490Sjkh
2722490Sjkh/*
2732490Sjkh *	This routine evaluates the arguments on the command line
2742490Sjkh */
275227101Sedstatic void
276203922Suqsgetargs(int argc, char *argv[])
2772490Sjkh{
27853210Sbillf	int	ignore_case;
27953210Sbillf	char	*pat;
2802490Sjkh	int ch;
2812490Sjkh
2822490Sjkh	ignore_case = FALSE;
2832490Sjkh	pat = NULL;
2842490Sjkh
285203926Suqs#ifdef DEBUG
28647440Simp	while ((ch = getopt(argc, argv, "aDefilm:osw")) != -1)
2872490Sjkh#else
28847440Simp	while ((ch = getopt(argc, argv, "aefilm:osw")) != -1)
2892490Sjkh#endif /* DEBUG */
2902490Sjkh		switch(ch) {
2912490Sjkh		case 'a':		/* any fortune */
2922490Sjkh			All_forts++;
2932490Sjkh			break;
294203926Suqs#ifdef DEBUG
2952490Sjkh		case 'D':
2962490Sjkh			Debug++;
2972490Sjkh			break;
298203926Suqs#endif /* DEBUG */
2992490Sjkh		case 'e':
3002490Sjkh			Equal_probs++;	/* scatter un-allocted prob equally */
3012490Sjkh			break;
3022490Sjkh		case 'f':		/* find fortune files */
3032490Sjkh			Find_files++;
3042490Sjkh			break;
3052490Sjkh		case 'l':		/* long ones only */
3062490Sjkh			Long_only++;
3072490Sjkh			Short_only = FALSE;
3082490Sjkh			break;
3092490Sjkh		case 'o':		/* offensive ones only */
3102490Sjkh			Offend++;
3112490Sjkh			break;
3122490Sjkh		case 's':		/* short ones only */
3132490Sjkh			Short_only++;
3142490Sjkh			Long_only = FALSE;
3152490Sjkh			break;
3162490Sjkh		case 'w':		/* give time to read */
3172490Sjkh			Wait++;
3182490Sjkh			break;
3192490Sjkh		case 'm':			/* dump out the fortunes */
3202490Sjkh			Match++;
3212490Sjkh			pat = optarg;
3222490Sjkh			break;
3232490Sjkh		case 'i':			/* case-insensitive match */
3242490Sjkh			ignore_case++;
3252490Sjkh			break;
3262490Sjkh		case '?':
3272490Sjkh		default:
3282490Sjkh			usage();
3292490Sjkh		}
3302490Sjkh	argc -= optind;
3312490Sjkh	argv += optind;
3322490Sjkh
3332490Sjkh	if (!form_file_list(argv, argc))
3342490Sjkh		exit(1);	/* errors printed through form_file_list() */
3352490Sjkh	if (Find_files) {
3362490Sjkh		print_file_list();
3372490Sjkh		exit(0);
3382490Sjkh	}
33915957Sache#ifdef DEBUG
34015957Sache	else if (Debug >= 1)
34115957Sache		print_file_list();
34215957Sache#endif /* DEBUG */
3432490Sjkh
3442490Sjkh	if (pat != NULL) {
345130851Sphk		int error;
346130851Sphk
3472490Sjkh		if (ignore_case)
3482490Sjkh			pat = conv_pat(pat);
349130851Sphk		error = regcomp(&Re_pat, pat, REG_BASIC);
350130851Sphk		if (error) {
351130851Sphk			fprintf(stderr, "regcomp(%s) fails\n", pat);
352203926Suqs			exit(1);
3532490Sjkh		}
3542490Sjkh	}
3552490Sjkh}
3562490Sjkh
3572490Sjkh/*
3582490Sjkh * form_file_list:
3592490Sjkh *	Form the file list from the file specifications.
3602490Sjkh */
361227101Sedstatic int
362203922Suqsform_file_list(char **files, int file_cnt)
3632490Sjkh{
36453210Sbillf	int	i, percent;
36553210Sbillf	char	*sp;
366173396Sedwin	char	**pstr;
3672490Sjkh
36849040Sbillf	if (file_cnt == 0) {
36915957Sache		if (Find_files) {
37015957Sache			Fortunes_only = TRUE;
371173396Sedwin			pstr = Fortune_path_arr;
372173396Sedwin			i = 0;
373173396Sedwin			while (*pstr) {
374203926Suqs				i += add_file(NO_PROB, *pstr++, NULL,
375173396Sedwin					      &File_list, &File_tail, NULL);
376173396Sedwin			}
37715957Sache			Fortunes_only = FALSE;
378173396Sedwin			if (!i) {
379173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
380173401Sedwin				    Fortune_path);
381173396Sedwin			}
382203926Suqs			return (i != 0);
383173396Sedwin		} else {
384173396Sedwin			pstr = Fortune_path_arr;
385173396Sedwin			i = 0;
386173396Sedwin			while (*pstr) {
387173396Sedwin				i += add_file(NO_PROB, "fortunes", *pstr++,
388173396Sedwin					      &File_list, &File_tail, NULL);
389173396Sedwin			}
390173396Sedwin			if (!i) {
391173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
392173401Sedwin				    Fortune_path);
393173396Sedwin			}
394203926Suqs			return (i != 0);
395173396Sedwin		}
39649040Sbillf	}
3972490Sjkh	for (i = 0; i < file_cnt; i++) {
3982490Sjkh		percent = NO_PROB;
39915944Sache		if (!isdigit((unsigned char)files[i][0]))
4002490Sjkh			sp = files[i];
4012490Sjkh		else {
4022490Sjkh			percent = 0;
40315944Sache			for (sp = files[i]; isdigit((unsigned char)*sp); sp++)
4042490Sjkh				percent = percent * 10 + *sp - '0';
4052490Sjkh			if (percent > 100) {
4062490Sjkh				fprintf(stderr, "percentages must be <= 100\n");
407203926Suqs				return (FALSE);
4082490Sjkh			}
4092490Sjkh			if (*sp == '.') {
4102490Sjkh				fprintf(stderr, "percentages must be integers\n");
411203926Suqs				return (FALSE);
4122490Sjkh			}
4132490Sjkh			/*
4142490Sjkh			 * If the number isn't followed by a '%', then
4152490Sjkh			 * it was not a percentage, just the first part
4162490Sjkh			 * of a file name which starts with digits.
4172490Sjkh			 */
4182490Sjkh			if (*sp != '%') {
4192490Sjkh				percent = NO_PROB;
4202490Sjkh				sp = files[i];
4212490Sjkh			}
4222490Sjkh			else if (*++sp == '\0') {
4232490Sjkh				if (++i >= file_cnt) {
4242490Sjkh					fprintf(stderr, "percentages must precede files\n");
425203926Suqs					return (FALSE);
4262490Sjkh				}
4272490Sjkh				sp = files[i];
4282490Sjkh			}
4292490Sjkh		}
430173396Sedwin		if (strcmp(sp, "all") == 0) {
431173396Sedwin			pstr = Fortune_path_arr;
432173396Sedwin			i = 0;
433173396Sedwin			while (*pstr) {
434203926Suqs				i += add_file(NO_PROB, *pstr++, NULL,
435173396Sedwin					      &File_list, &File_tail, NULL);
436173396Sedwin			}
437173396Sedwin			if (!i) {
438173401Sedwin				fprintf(stderr, "No fortunes found in %s.\n",
439173401Sedwin				    Fortune_path);
440203926Suqs				return (FALSE);
441173396Sedwin			}
442203926Suqs		} else if (!add_file(percent, sp, NULL, &File_list,
443173396Sedwin				     &File_tail, NULL)) {
444203926Suqs 			return (FALSE);
445173396Sedwin		}
4462490Sjkh	}
447203926Suqs
448203926Suqs	return (TRUE);
4492490Sjkh}
4502490Sjkh
4512490Sjkh/*
4522490Sjkh * add_file:
4532490Sjkh *	Add a file to the file list.
4542490Sjkh */
455227101Sedstatic int
456203922Suqsadd_file(int percent, const char *file, const char *dir, FILEDESC **head,
457203922Suqs    FILEDESC **tail, FILEDESC *parent)
4582490Sjkh{
45953210Sbillf	FILEDESC	*fp;
46053210Sbillf	int		fd;
461203922Suqs	const char 	*path;
462203922Suqs	char		*tpath, *offensive;
46353210Sbillf	bool		was_malloc;
46453210Sbillf	bool		isdir;
4652490Sjkh
4662490Sjkh	if (dir == NULL) {
4672490Sjkh		path = file;
468203922Suqs		tpath = NULL;
4692490Sjkh		was_malloc = FALSE;
4702490Sjkh	}
4712490Sjkh	else {
472203922Suqs		tpath = do_malloc((unsigned int)(strlen(dir) + strlen(file) + 2));
473203922Suqs		strcat(strcat(strcpy(tpath, dir), "/"), file);
474203922Suqs		path = tpath;
4752490Sjkh		was_malloc = TRUE;
4762490Sjkh	}
4772490Sjkh	if ((isdir = is_dir(path)) && parent != NULL) {
4782490Sjkh		if (was_malloc)
479203922Suqs			free(tpath);
480203926Suqs		return (FALSE);	/* don't recurse */
4812490Sjkh	}
4822490Sjkh	offensive = NULL;
4832490Sjkh	if (!isdir && parent == NULL && (All_forts || Offend) &&
4842490Sjkh	    !is_off_name(path)) {
4852490Sjkh		offensive = off_name(path);
4862490Sjkh		if (Offend) {
4872490Sjkh			if (was_malloc)
488203922Suqs				free(tpath);
489261994Smarcel			path = tpath = offensive;
49015957Sache			offensive = NULL;
49115957Sache			was_malloc = TRUE;
49215957Sache			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
4932490Sjkh			file = off_name(file);
4942490Sjkh		}
4952490Sjkh	}
4962490Sjkh
4972490Sjkh	DPRINTF(1, (stderr, "adding file \"%s\"\n", path));
4982490Sjkhover:
499203926Suqs	if ((fd = open(path, O_RDONLY)) < 0) {
5002490Sjkh		/*
5012490Sjkh		 * This is a sneak.  If the user said -a, and if the
5022490Sjkh		 * file we're given isn't a file, we check to see if
5032490Sjkh		 * there is a -o version.  If there is, we treat it as
5042490Sjkh		 * if *that* were the file given.  We only do this for
5052490Sjkh		 * individual files -- if we're scanning a directory,
5062490Sjkh		 * we'll pick up the -o file anyway.
5072490Sjkh		 */
5082490Sjkh		if (All_forts && offensive != NULL) {
5092490Sjkh			if (was_malloc)
510203922Suqs				free(tpath);
511261994Smarcel			path = tpath = offensive;
5122490Sjkh			offensive = NULL;
5132490Sjkh			was_malloc = TRUE;
5142490Sjkh			DPRINTF(1, (stderr, "\ttrying \"%s\"\n", path));
5152490Sjkh			file = off_name(file);
5162490Sjkh			goto over;
5172490Sjkh		}
518173396Sedwin		if (dir == NULL && file[0] != '/') {
519173396Sedwin			int i = 0;
520173396Sedwin			char **pstr = Fortune_path_arr;
521173396Sedwin
522173396Sedwin			while (*pstr) {
523203926Suqs				i += add_file(percent, file, *pstr++,
524173396Sedwin					      head, tail, parent);
525173396Sedwin			}
526173396Sedwin			if (!i) {
527173401Sedwin				fprintf(stderr, "No '%s' found in %s.\n",
528173401Sedwin				    file, Fortune_path);
529173396Sedwin			}
530203926Suqs			return (i != 0);
531173396Sedwin		}
532173396Sedwin		/*
5332490Sjkh		if (parent == NULL)
5342490Sjkh			perror(path);
535173396Sedwin		*/
5362490Sjkh		if (was_malloc)
537203922Suqs			free(tpath);
538203926Suqs		return (FALSE);
5392490Sjkh	}
5402490Sjkh
5412490Sjkh	DPRINTF(2, (stderr, "path = \"%s\"\n", path));
5422490Sjkh
5432490Sjkh	fp = new_fp();
5442490Sjkh	fp->fd = fd;
5452490Sjkh	fp->percent = percent;
5462490Sjkh	fp->name = file;
5472490Sjkh	fp->path = path;
5482490Sjkh	fp->parent = parent;
5492490Sjkh
5502490Sjkh	if ((isdir && !add_dir(fp)) ||
5512490Sjkh	    (!isdir &&
5522490Sjkh	     !is_fortfile(path, &fp->datfile, &fp->posfile, (parent != NULL))))
5532490Sjkh	{
5542490Sjkh		if (parent == NULL)
5552490Sjkh			fprintf(stderr,
5562490Sjkh				"fortune:%s not a fortune file or directory\n",
5572490Sjkh				path);
5582490Sjkh		if (was_malloc)
559203922Suqs			free(tpath);
5602490Sjkh		do_free(fp->datfile);
5612490Sjkh		do_free(fp->posfile);
562203926Suqs		free(fp);
5632490Sjkh		do_free(offensive);
564203926Suqs		return (FALSE);
5652490Sjkh	}
5662490Sjkh	/*
5672490Sjkh	 * If the user said -a, we need to make this node a pointer to
5682490Sjkh	 * both files, if there are two.  We don't need to do this if
5692490Sjkh	 * we are scanning a directory, since the scan will pick up the
5702490Sjkh	 * -o file anyway.
5712490Sjkh	 */
5722490Sjkh	if (All_forts && parent == NULL && !is_off_name(path))
5732490Sjkh		all_forts(fp, offensive);
5742490Sjkh	if (*head == NULL)
5752490Sjkh		*head = *tail = fp;
5762490Sjkh	else if (fp->percent == NO_PROB) {
5772490Sjkh		(*tail)->next = fp;
5782490Sjkh		fp->prev = *tail;
5792490Sjkh		*tail = fp;
5802490Sjkh	}
5812490Sjkh	else {
5822490Sjkh		(*head)->prev = fp;
5832490Sjkh		fp->next = *head;
5842490Sjkh		*head = fp;
5852490Sjkh	}
586242577Seadler	if (WriteToDisk)
587242577Seadler		fp->was_pos_file = (access(fp->posfile, W_OK) >= 0);
5882490Sjkh
589203926Suqs	return (TRUE);
5902490Sjkh}
5912490Sjkh
5922490Sjkh/*
5932490Sjkh * new_fp:
5942490Sjkh *	Return a pointer to an initialized new FILEDESC.
5952490Sjkh */
596227101Sedstatic FILEDESC *
597203922Suqsnew_fp(void)
5982490Sjkh{
59953210Sbillf	FILEDESC	*fp;
6002490Sjkh
601203926Suqs	fp = do_malloc(sizeof(*fp));
6022490Sjkh	fp->datfd = -1;
6032490Sjkh	fp->pos = POS_UNKNOWN;
6042490Sjkh	fp->inf = NULL;
6052490Sjkh	fp->fd = -1;
6062490Sjkh	fp->percent = NO_PROB;
6072490Sjkh	fp->read_tbl = FALSE;
6082490Sjkh	fp->next = NULL;
6092490Sjkh	fp->prev = NULL;
6102490Sjkh	fp->child = NULL;
6112490Sjkh	fp->parent = NULL;
6122490Sjkh	fp->datfile = NULL;
6132490Sjkh	fp->posfile = NULL;
614203926Suqs
615203926Suqs	return (fp);
6162490Sjkh}
6172490Sjkh
6182490Sjkh/*
6192490Sjkh * off_name:
6202490Sjkh *	Return a pointer to the offensive version of a file of this name.
6212490Sjkh */
622227101Sedstatic char *
623203922Suqsoff_name(const char *file)
6242490Sjkh{
6252490Sjkh	char	*new;
6262490Sjkh
6272490Sjkh	new = copy(file, (unsigned int) (strlen(file) + 2));
628203926Suqs
629203926Suqs	return (strcat(new, "-o"));
6302490Sjkh}
6312490Sjkh
6322490Sjkh/*
6332490Sjkh * is_off_name:
6342490Sjkh *	Is the file an offensive-style name?
6352490Sjkh */
636227101Sedstatic int
637203922Suqsis_off_name(const char *file)
6382490Sjkh{
6392490Sjkh	int	len;
6402490Sjkh
6412490Sjkh	len = strlen(file);
642203926Suqs
6432490Sjkh	return (len >= 3 && file[len - 2] == '-' && file[len - 1] == 'o');
6442490Sjkh}
6452490Sjkh
6462490Sjkh/*
6472490Sjkh * all_forts:
6482490Sjkh *	Modify a FILEDESC element to be the parent of two children if
6492490Sjkh *	there are two children to be a parent of.
6502490Sjkh */
651227101Sedstatic void
652203922Suqsall_forts(FILEDESC *fp, char *offensive)
6532490Sjkh{
65453210Sbillf	char		*sp;
65553210Sbillf	FILEDESC	*scene, *obscene;
65653210Sbillf	int		fd;
657203926Suqs	char		*datfile, *posfile;
6582490Sjkh
6592490Sjkh	if (fp->child != NULL)	/* this is a directory, not a file */
6602490Sjkh		return;
6612490Sjkh	if (!is_fortfile(offensive, &datfile, &posfile, FALSE))
6622490Sjkh		return;
663203926Suqs	if ((fd = open(offensive, O_RDONLY)) < 0)
6642490Sjkh		return;
6652490Sjkh	DPRINTF(1, (stderr, "adding \"%s\" because of -a\n", offensive));
6662490Sjkh	scene = new_fp();
6672490Sjkh	obscene = new_fp();
6682490Sjkh	*scene = *fp;
6692490Sjkh
6702490Sjkh	fp->num_children = 2;
6712490Sjkh	fp->child = scene;
6722490Sjkh	scene->next = obscene;
6732490Sjkh	obscene->next = NULL;
6742490Sjkh	scene->child = obscene->child = NULL;
6752490Sjkh	scene->parent = obscene->parent = fp;
6762490Sjkh
6772490Sjkh	fp->fd = -1;
6782490Sjkh	scene->percent = obscene->percent = NO_PROB;
6792490Sjkh
6802490Sjkh	obscene->fd = fd;
6812490Sjkh	obscene->inf = NULL;
6822490Sjkh	obscene->path = offensive;
683229403Sed	if ((sp = strrchr(offensive, '/')) == NULL)
6842490Sjkh		obscene->name = offensive;
6852490Sjkh	else
6862490Sjkh		obscene->name = ++sp;
6872490Sjkh	obscene->datfile = datfile;
6882490Sjkh	obscene->posfile = posfile;
689242577Seadler	obscene->read_tbl = false;
690242577Seadler	if (WriteToDisk)
691242577Seadler		obscene->was_pos_file = (access(obscene->posfile, W_OK) >= 0);
6922490Sjkh}
6932490Sjkh
6942490Sjkh/*
6952490Sjkh * add_dir:
6962490Sjkh *	Add the contents of an entire directory.
6972490Sjkh */
698227101Sedstatic int
699203922Suqsadd_dir(FILEDESC *fp)
7002490Sjkh{
70153210Sbillf	DIR		*dir;
70253210Sbillf	struct dirent	*dirent;
703203926Suqs	FILEDESC	*tailp;
704203926Suqs	char		*name;
7052490Sjkh
7062490Sjkh	(void) close(fp->fd);
7072490Sjkh	fp->fd = -1;
7082490Sjkh	if ((dir = opendir(fp->path)) == NULL) {
7092490Sjkh		perror(fp->path);
710203926Suqs		return (FALSE);
7112490Sjkh	}
7122490Sjkh	tailp = NULL;
7132490Sjkh	DPRINTF(1, (stderr, "adding dir \"%s\"\n", fp->path));
7142490Sjkh	fp->num_children = 0;
7152490Sjkh	while ((dirent = readdir(dir)) != NULL) {
7162490Sjkh		if (dirent->d_namlen == 0)
7172490Sjkh			continue;
7182490Sjkh		name = copy(dirent->d_name, dirent->d_namlen);
7192490Sjkh		if (add_file(NO_PROB, name, fp->path, &fp->child, &tailp, fp))
7202490Sjkh			fp->num_children++;
7212490Sjkh		else
7222490Sjkh			free(name);
7232490Sjkh	}
7242490Sjkh	if (fp->num_children == 0) {
7252490Sjkh		(void) fprintf(stderr,
7262490Sjkh		    "fortune: %s: No fortune files in directory.\n", fp->path);
727203926Suqs		return (FALSE);
7282490Sjkh	}
729203926Suqs
730203926Suqs	return (TRUE);
7312490Sjkh}
7322490Sjkh
7332490Sjkh/*
7342490Sjkh * is_dir:
7352490Sjkh *	Return TRUE if the file is a directory, FALSE otherwise.
7362490Sjkh */
737227101Sedstatic int
738203922Suqsis_dir(const char *file)
7392490Sjkh{
740203926Suqs	struct stat	sbuf;
7412490Sjkh
7422490Sjkh	if (stat(file, &sbuf) < 0)
743203926Suqs		return (FALSE);
744203926Suqs
7452490Sjkh	return (sbuf.st_mode & S_IFDIR);
7462490Sjkh}
7472490Sjkh
7482490Sjkh/*
7492490Sjkh * is_fortfile:
7502490Sjkh *	Return TRUE if the file is a fortune database file.  We try and
7512490Sjkh *	exclude files without reading them if possible to avoid
7522490Sjkh *	overhead.  Files which start with ".", or which have "illegal"
7532490Sjkh *	suffixes, as contained in suflist[], are ruled out.
7542490Sjkh */
7552490Sjkh/* ARGSUSED */
756227101Sedstatic int
757203922Suqsis_fortfile(const char *file, char **datp, char **posp, int check_for_offend)
7582490Sjkh{
75953210Sbillf	int	i;
760203922Suqs	const char	*sp;
76153210Sbillf	char	*datfile;
762203922Suqs	static const char *suflist[] = {
763203922Suqs		/* list of "illegal" suffixes" */
764203922Suqs		"dat", "pos", "c", "h", "p", "i", "f",
765203922Suqs		"pas", "ftn", "ins.c", "ins,pas",
766203922Suqs		"ins.ftn", "sml",
767203922Suqs		NULL
768203922Suqs	};
7692490Sjkh
7702490Sjkh	DPRINTF(2, (stderr, "is_fortfile(%s) returns ", file));
7712490Sjkh
7722490Sjkh	/*
7732490Sjkh	 * Preclude any -o files for offendable people, and any non -o
7742490Sjkh	 * files for completely offensive people.
7752490Sjkh	 */
7762490Sjkh	if (check_for_offend && !All_forts) {
7772490Sjkh		i = strlen(file);
77815957Sache		if (Offend ^ (file[i - 2] == '-' && file[i - 1] == 'o')) {
77915957Sache			DPRINTF(2, (stderr, "FALSE (offending file)\n"));
780203926Suqs			return (FALSE);
78115957Sache		}
7822490Sjkh	}
7832490Sjkh
784229403Sed	if ((sp = strrchr(file, '/')) == NULL)
7852490Sjkh		sp = file;
7862490Sjkh	else
7872490Sjkh		sp++;
7882490Sjkh	if (*sp == '.') {
7892490Sjkh		DPRINTF(2, (stderr, "FALSE (file starts with '.')\n"));
790203926Suqs		return (FALSE);
7912490Sjkh	}
79215957Sache	if (Fortunes_only && strncmp(sp, "fortunes", 8) != 0) {
79315957Sache		DPRINTF(2, (stderr, "FALSE (check fortunes only)\n"));
794203926Suqs		return (FALSE);
79515957Sache	}
796229403Sed	if ((sp = strrchr(sp, '.')) != NULL) {
7972490Sjkh		sp++;
7982490Sjkh		for (i = 0; suflist[i] != NULL; i++)
7992490Sjkh			if (strcmp(sp, suflist[i]) == 0) {
8002490Sjkh				DPRINTF(2, (stderr, "FALSE (file has suffix \".%s\")\n", sp));
801203926Suqs				return (FALSE);
8022490Sjkh			}
8032490Sjkh	}
8042490Sjkh
8052490Sjkh	datfile = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
8062490Sjkh	strcat(datfile, ".dat");
8072490Sjkh	if (access(datfile, R_OK) < 0) {
80844599Sdcs		DPRINTF(2, (stderr, "FALSE (no readable \".dat\" file)\n"));
8092490Sjkh		free(datfile);
810203926Suqs		return (FALSE);
8112490Sjkh	}
8122490Sjkh	if (datp != NULL)
8132490Sjkh		*datp = datfile;
8142490Sjkh	else
8152490Sjkh		free(datfile);
81615944Sache	if (posp != NULL) {
817242577Seadler		if (WriteToDisk) {
818242577Seadler			*posp = copy(file, (unsigned int) (strlen(file) + 4)); /* +4 for ".dat" */
819242577Seadler			strcat(*posp, ".pos");
820242577Seadler		}
821242577Seadler		else {
822242577Seadler			*posp = NULL;
823242577Seadler		}
8242490Sjkh	}
8252490Sjkh	DPRINTF(2, (stderr, "TRUE\n"));
826203926Suqs
827203926Suqs	return (TRUE);
8282490Sjkh}
8292490Sjkh
8302490Sjkh/*
8312490Sjkh * copy:
8322490Sjkh *	Return a malloc()'ed copy of the string
8332490Sjkh */
834227101Sedstatic char *
835203922Suqscopy(const char *str, unsigned int len)
8362490Sjkh{
837203926Suqs	char *new, *sp;
8382490Sjkh
8392490Sjkh	new = do_malloc(len + 1);
8402490Sjkh	sp = new;
8412490Sjkh	do {
8422490Sjkh		*sp++ = *str;
8432490Sjkh	} while (*str++);
844203926Suqs
845203926Suqs	return (new);
8462490Sjkh}
8472490Sjkh
8482490Sjkh/*
8492490Sjkh * do_malloc:
8502490Sjkh *	Do a malloc, checking for NULL return.
8512490Sjkh */
852227101Sedstatic void *
853203922Suqsdo_malloc(unsigned int size)
8542490Sjkh{
855203926Suqs	void *new;
8562490Sjkh
8572490Sjkh	if ((new = malloc(size)) == NULL) {
8582490Sjkh		(void) fprintf(stderr, "fortune: out of memory.\n");
8592490Sjkh		exit(1);
8602490Sjkh	}
861203926Suqs
862203926Suqs	return (new);
8632490Sjkh}
8642490Sjkh
8652490Sjkh/*
8662490Sjkh * do_free:
8672490Sjkh *	Free malloc'ed space, if any.
8682490Sjkh */
869227101Sedstatic void
870203922Suqsdo_free(void *ptr)
8712490Sjkh{
8722490Sjkh	if (ptr != NULL)
8732490Sjkh		free(ptr);
8742490Sjkh}
8752490Sjkh
8762490Sjkh/*
8772490Sjkh * init_prob:
8782490Sjkh *	Initialize the fortune probabilities.
8792490Sjkh */
880227101Sedstatic void
881203922Suqsinit_prob(void)
8822490Sjkh{
88353210Sbillf	FILEDESC       *fp, *last = NULL;
88453210Sbillf	int		percent, num_noprob, frac;
8852490Sjkh
8862490Sjkh	/*
8872490Sjkh	 * Distribute the residual probability (if any) across all
8882490Sjkh	 * files with unspecified probability (i.e., probability of 0)
8892490Sjkh	 * (if any).
8902490Sjkh	 */
8912490Sjkh
8922490Sjkh	percent = 0;
8932490Sjkh	num_noprob = 0;
8942490Sjkh	for (fp = File_tail; fp != NULL; fp = fp->prev)
8952490Sjkh		if (fp->percent == NO_PROB) {
8962490Sjkh			num_noprob++;
8972490Sjkh			if (Equal_probs)
8982490Sjkh				last = fp;
899203926Suqs		} else
9002490Sjkh			percent += fp->percent;
9012490Sjkh	DPRINTF(1, (stderr, "summing probabilities:%d%% with %d NO_PROB's",
9022490Sjkh		    percent, num_noprob));
9032490Sjkh	if (percent > 100) {
9042490Sjkh		(void) fprintf(stderr,
90515957Sache		    "fortune: probabilities sum to %d%% > 100%%!\n", percent);
9062490Sjkh		exit(1);
907203926Suqs	} else if (percent < 100 && num_noprob == 0) {
9082490Sjkh		(void) fprintf(stderr,
90915957Sache		    "fortune: no place to put residual probability (%d%% < 100%%)\n",
9102490Sjkh		    percent);
9112490Sjkh		exit(1);
912203926Suqs	} else if (percent == 100 && num_noprob != 0) {
9132490Sjkh		(void) fprintf(stderr,
91415957Sache		    "fortune: no probability left to put in residual files (100%%)\n");
9152490Sjkh		exit(1);
9162490Sjkh	}
9172490Sjkh	percent = 100 - percent;
91849040Sbillf	if (Equal_probs) {
9192490Sjkh		if (num_noprob != 0) {
9202490Sjkh			if (num_noprob > 1) {
9212490Sjkh				frac = percent / num_noprob;
9222490Sjkh				DPRINTF(1, (stderr, ", frac = %d%%", frac));
923173396Sedwin				for (fp = File_tail; fp != last; fp = fp->prev)
9242490Sjkh					if (fp->percent == NO_PROB) {
9252490Sjkh						fp->percent = frac;
9262490Sjkh						percent -= frac;
9272490Sjkh					}
9282490Sjkh			}
9292490Sjkh			last->percent = percent;
9302490Sjkh			DPRINTF(1, (stderr, ", residual = %d%%", percent));
9312490Sjkh		}
932203922Suqs		else
933203926Suqs		DPRINTF(1, (stderr,
9342490Sjkh			    ", %d%% distributed over remaining fortunes\n",
9352490Sjkh			    percent));
9362490Sjkh	}
9372490Sjkh	DPRINTF(1, (stderr, "\n"));
9382490Sjkh
9392490Sjkh#ifdef DEBUG
9402490Sjkh	if (Debug >= 1)
9412490Sjkh		print_file_list();
9422490Sjkh#endif
9432490Sjkh}
9442490Sjkh
9452490Sjkh/*
9462490Sjkh * get_fort:
9472490Sjkh *	Get the fortune data file's seek pointer for the next fortune.
9482490Sjkh */
949227101Sedstatic void
950203922Suqsget_fort(void)
9512490Sjkh{
95253210Sbillf	FILEDESC	*fp;
95353210Sbillf	int		choice;
9542490Sjkh
9552490Sjkh	if (File_list->next == NULL || File_list->percent == NO_PROB)
9562490Sjkh		fp = File_list;
9572490Sjkh	else {
958181385Sache		choice = arc4random_uniform(100);
9592490Sjkh		DPRINTF(1, (stderr, "choice = %d\n", choice));
9602490Sjkh		for (fp = File_list; fp->percent != NO_PROB; fp = fp->next)
9612490Sjkh			if (choice < fp->percent)
9622490Sjkh				break;
9632490Sjkh			else {
9642490Sjkh				choice -= fp->percent;
9652490Sjkh				DPRINTF(1, (stderr,
9662490Sjkh					    "    skip \"%s\", %d%% (choice = %d)\n",
9672490Sjkh					    fp->name, fp->percent, choice));
9682490Sjkh			}
9692490Sjkh			DPRINTF(1, (stderr,
9702490Sjkh				    "using \"%s\", %d%% (choice = %d)\n",
9712490Sjkh				    fp->name, fp->percent, choice));
9722490Sjkh	}
9732490Sjkh	if (fp->percent != NO_PROB)
9742490Sjkh		get_tbl(fp);
9752490Sjkh	else {
9762490Sjkh		if (fp->next != NULL) {
9772490Sjkh			sum_noprobs(fp);
978181385Sache			choice = arc4random_uniform(Noprob_tbl.str_numstr);
979142022Sru			DPRINTF(1, (stderr, "choice = %d (of %u) \n", choice,
9802490Sjkh				    Noprob_tbl.str_numstr));
981203922Suqs			while ((unsigned int)choice >= fp->tbl.str_numstr) {
9822490Sjkh				choice -= fp->tbl.str_numstr;
9832490Sjkh				fp = fp->next;
9842490Sjkh				DPRINTF(1, (stderr,
985142022Sru					    "    skip \"%s\", %u (choice = %d)\n",
9862490Sjkh					    fp->name, fp->tbl.str_numstr,
9872490Sjkh					    choice));
9882490Sjkh			}
989142022Sru			DPRINTF(1, (stderr, "using \"%s\", %u\n", fp->name,
9902490Sjkh				    fp->tbl.str_numstr));
9912490Sjkh		}
9922490Sjkh		get_tbl(fp);
9932490Sjkh	}
9942490Sjkh	if (fp->child != NULL) {
9952490Sjkh		DPRINTF(1, (stderr, "picking child\n"));
9962490Sjkh		fp = pick_child(fp);
9972490Sjkh	}
9982490Sjkh	Fortfile = fp;
9992490Sjkh	get_pos(fp);
10002490Sjkh	open_dat(fp);
1001203926Suqs	lseek(fp->datfd,
1002203926Suqs	    (off_t) (sizeof fp->tbl + fp->pos * sizeof Seekpts[0]), SEEK_SET);
10032490Sjkh	read(fp->datfd, Seekpts, sizeof Seekpts);
1004142022Sru	Seekpts[0] = be64toh(Seekpts[0]);
1005142022Sru	Seekpts[1] = be64toh(Seekpts[1]);
10062490Sjkh}
10072490Sjkh
10082490Sjkh/*
10092490Sjkh * pick_child
10102490Sjkh *	Pick a child from a chosen parent.
10112490Sjkh */
1012227101Sedstatic FILEDESC *
1013203922Suqspick_child(FILEDESC *parent)
10142490Sjkh{
101553210Sbillf	FILEDESC	*fp;
101653210Sbillf	int		choice;
10172490Sjkh
10182490Sjkh	if (Equal_probs) {
1019181385Sache		choice = arc4random_uniform(parent->num_children);
10202490Sjkh		DPRINTF(1, (stderr, "    choice = %d (of %d)\n",
10212490Sjkh			    choice, parent->num_children));
10222490Sjkh		for (fp = parent->child; choice--; fp = fp->next)
10232490Sjkh			continue;
10242490Sjkh		DPRINTF(1, (stderr, "    using %s\n", fp->name));
1025203926Suqs		return (fp);
10262490Sjkh	}
10272490Sjkh	else {
10282490Sjkh		get_tbl(parent);
1029181385Sache		choice = arc4random_uniform(parent->tbl.str_numstr);
1030142022Sru		DPRINTF(1, (stderr, "    choice = %d (of %u)\n",
10312490Sjkh			    choice, parent->tbl.str_numstr));
1032203922Suqs		for (fp = parent->child; (unsigned)choice >= fp->tbl.str_numstr;
10332490Sjkh		     fp = fp->next) {
10342490Sjkh			choice -= fp->tbl.str_numstr;
1035142022Sru			DPRINTF(1, (stderr, "\tskip %s, %u (choice = %d)\n",
10362490Sjkh				    fp->name, fp->tbl.str_numstr, choice));
10372490Sjkh		}
1038142022Sru		DPRINTF(1, (stderr, "    using %s, %u\n", fp->name,
10392490Sjkh			    fp->tbl.str_numstr));
1040203926Suqs		return (fp);
10412490Sjkh	}
10422490Sjkh}
10432490Sjkh
10442490Sjkh/*
10452490Sjkh * sum_noprobs:
10462490Sjkh *	Sum up all the noprob probabilities, starting with fp.
10472490Sjkh */
1048227101Sedstatic void
1049203922Suqssum_noprobs(FILEDESC *fp)
10502490Sjkh{
10512490Sjkh	static bool	did_noprobs = FALSE;
10522490Sjkh
10532490Sjkh	if (did_noprobs)
10542490Sjkh		return;
10552490Sjkh	zero_tbl(&Noprob_tbl);
10562490Sjkh	while (fp != NULL) {
10572490Sjkh		get_tbl(fp);
10582490Sjkh		sum_tbl(&Noprob_tbl, &fp->tbl);
10592490Sjkh		fp = fp->next;
10602490Sjkh	}
10612490Sjkh	did_noprobs = TRUE;
10622490Sjkh}
10632490Sjkh
1064227101Sedstatic int
1065203922Suqsmax(int i, int j)
10662490Sjkh{
10672490Sjkh	return (i >= j ? i : j);
10682490Sjkh}
10692490Sjkh
10702490Sjkh/*
10712490Sjkh * open_fp:
10722490Sjkh *	Assocatiate a FILE * with the given FILEDESC.
10732490Sjkh */
1074227101Sedstatic void
1075203922Suqsopen_fp(FILEDESC *fp)
10762490Sjkh{
10772490Sjkh	if (fp->inf == NULL && (fp->inf = fdopen(fp->fd, "r")) == NULL) {
10782490Sjkh		perror(fp->path);
10792490Sjkh		exit(1);
10802490Sjkh	}
10812490Sjkh}
10822490Sjkh
10832490Sjkh/*
10842490Sjkh * open_dat:
10852490Sjkh *	Open up the dat file if we need to.
10862490Sjkh */
1087227101Sedstatic void
1088203922Suqsopen_dat(FILEDESC *fp)
10892490Sjkh{
1090203926Suqs	if (fp->datfd < 0 && (fp->datfd = open(fp->datfile, O_RDONLY)) < 0) {
10912490Sjkh		perror(fp->datfile);
10922490Sjkh		exit(1);
10932490Sjkh	}
10942490Sjkh}
10952490Sjkh
10962490Sjkh/*
10972490Sjkh * get_pos:
10982490Sjkh *	Get the position from the pos file, if there is one.  If not,
10992490Sjkh *	return a random number.
11002490Sjkh */
1101227101Sedstatic void
1102203922Suqsget_pos(FILEDESC *fp)
11032490Sjkh{
11042490Sjkh	int	fd;
11052490Sjkh
11062490Sjkh	assert(fp->read_tbl);
11072490Sjkh	if (fp->pos == POS_UNKNOWN) {
1108242577Seadler		if (WriteToDisk) {
1109242577Seadler			if ((fd = open(fp->posfile, O_RDONLY)) < 0 ||
1110242577Seadler			    read(fd, &fp->pos, sizeof fp->pos) != sizeof fp->pos)
1111242577Seadler				fp->pos = arc4random_uniform(fp->tbl.str_numstr);
1112242577Seadler			else if (fp->pos >= fp->tbl.str_numstr)
1113242577Seadler				fp->pos %= fp->tbl.str_numstr;
1114242577Seadler			if (fd >= 0)
1115242577Seadler				close(fd);
1116242577Seadler		}
1117242577Seadler		else
1118181385Sache			fp->pos = arc4random_uniform(fp->tbl.str_numstr);
11192490Sjkh	}
11202490Sjkh	if (++(fp->pos) >= fp->tbl.str_numstr)
11212490Sjkh		fp->pos -= fp->tbl.str_numstr;
1122142022Sru	DPRINTF(1, (stderr, "pos for %s is %ld\n", fp->name, (long)fp->pos));
11232490Sjkh}
11242490Sjkh
11252490Sjkh/*
11262490Sjkh * get_tbl:
11272490Sjkh *	Get the tbl data file the datfile.
11282490Sjkh */
1129227101Sedstatic void
1130203922Suqsget_tbl(FILEDESC *fp)
11312490Sjkh{
1132203926Suqs	int		fd;
113353210Sbillf	FILEDESC	*child;
11342490Sjkh
11352490Sjkh	if (fp->read_tbl)
11362490Sjkh		return;
11372490Sjkh	if (fp->child == NULL) {
1138203926Suqs		if ((fd = open(fp->datfile, O_RDONLY)) < 0) {
11392490Sjkh			perror(fp->datfile);
11402490Sjkh			exit(1);
11412490Sjkh		}
11422490Sjkh		if (read(fd, (char *) &fp->tbl, sizeof fp->tbl) != sizeof fp->tbl) {
11432490Sjkh			(void)fprintf(stderr,
11442490Sjkh			    "fortune: %s corrupted\n", fp->path);
11452490Sjkh			exit(1);
11462490Sjkh		}
1147142022Sru		/* fp->tbl.str_version = be32toh(fp->tbl.str_version); */
1148142022Sru		fp->tbl.str_numstr = be32toh(fp->tbl.str_numstr);
1149142022Sru		fp->tbl.str_longlen = be32toh(fp->tbl.str_longlen);
1150142022Sru		fp->tbl.str_shortlen = be32toh(fp->tbl.str_shortlen);
1151142022Sru		fp->tbl.str_flags = be32toh(fp->tbl.str_flags);
11522490Sjkh		(void) close(fd);
11532490Sjkh	}
11542490Sjkh	else {
11552490Sjkh		zero_tbl(&fp->tbl);
11562490Sjkh		for (child = fp->child; child != NULL; child = child->next) {
11572490Sjkh			get_tbl(child);
11582490Sjkh			sum_tbl(&fp->tbl, &child->tbl);
11592490Sjkh		}
11602490Sjkh	}
11612490Sjkh	fp->read_tbl = TRUE;
11622490Sjkh}
11632490Sjkh
11642490Sjkh/*
11652490Sjkh * zero_tbl:
11662490Sjkh *	Zero out the fields we care about in a tbl structure.
11672490Sjkh */
1168227101Sedstatic void
1169203922Suqszero_tbl(STRFILE *tp)
11702490Sjkh{
11712490Sjkh	tp->str_numstr = 0;
11722490Sjkh	tp->str_longlen = 0;
1173142022Sru	tp->str_shortlen = ~0;
11742490Sjkh}
11752490Sjkh
11762490Sjkh/*
11772490Sjkh * sum_tbl:
11782490Sjkh *	Merge the tbl data of t2 into t1.
11792490Sjkh */
1180227101Sedstatic void
1181203922Suqssum_tbl(STRFILE *t1, STRFILE *t2)
11822490Sjkh{
11832490Sjkh	t1->str_numstr += t2->str_numstr;
11842490Sjkh	if (t1->str_longlen < t2->str_longlen)
11852490Sjkh		t1->str_longlen = t2->str_longlen;
11862490Sjkh	if (t1->str_shortlen > t2->str_shortlen)
11872490Sjkh		t1->str_shortlen = t2->str_shortlen;
11882490Sjkh}
11892490Sjkh
11902490Sjkh#define	STR(str)	((str) == NULL ? "NULL" : (str))
11912490Sjkh
11922490Sjkh/*
11932490Sjkh * print_file_list:
11942490Sjkh *	Print out the file list
11952490Sjkh */
1196227101Sedstatic void
1197203922Suqsprint_file_list(void)
11982490Sjkh{
11992490Sjkh	print_list(File_list, 0);
12002490Sjkh}
12012490Sjkh
12022490Sjkh/*
12032490Sjkh * print_list:
12042490Sjkh *	Print out the actual list, recursively.
12052490Sjkh */
1206227101Sedstatic void
1207203922Suqsprint_list(FILEDESC *list, int lev)
12082490Sjkh{
12092490Sjkh	while (list != NULL) {
12102490Sjkh		fprintf(stderr, "%*s", lev * 4, "");
12112490Sjkh		if (list->percent == NO_PROB)
12122490Sjkh			fprintf(stderr, "___%%");
12132490Sjkh		else
12142490Sjkh			fprintf(stderr, "%3d%%", list->percent);
12152490Sjkh		fprintf(stderr, " %s", STR(list->name));
121615957Sache		DPRINTF(1, (stderr, " (%s, %s, %s)", STR(list->path),
12172490Sjkh			    STR(list->datfile), STR(list->posfile)));
121815957Sache		fprintf(stderr, "\n");
12192490Sjkh		if (list->child != NULL)
12202490Sjkh			print_list(list->child, lev + 1);
12212490Sjkh		list = list->next;
12222490Sjkh	}
12232490Sjkh}
12242490Sjkh
12252490Sjkh/*
12262490Sjkh * conv_pat:
12272490Sjkh *	Convert the pattern to an ignore-case equivalent.
12282490Sjkh */
1229227101Sedstatic char *
1230203922Suqsconv_pat(char *orig)
12312490Sjkh{
123253210Sbillf	char		*sp;
123353210Sbillf	unsigned int	cnt;
123453210Sbillf	char		*new;
12352490Sjkh
12362490Sjkh	cnt = 1;	/* allow for '\0' */
12372490Sjkh	for (sp = orig; *sp != '\0'; sp++)
123815944Sache		if (isalpha((unsigned char)*sp))
12392490Sjkh			cnt += 4;
12402490Sjkh		else
12412490Sjkh			cnt++;
12422490Sjkh	if ((new = malloc(cnt)) == NULL) {
12432490Sjkh		fprintf(stderr, "pattern too long for ignoring case\n");
12442490Sjkh		exit(1);
12452490Sjkh	}
12462490Sjkh
12472490Sjkh	for (sp = new; *orig != '\0'; orig++) {
124815944Sache		if (islower((unsigned char)*orig)) {
12492490Sjkh			*sp++ = '[';
12502490Sjkh			*sp++ = *orig;
125115944Sache			*sp++ = toupper((unsigned char)*orig);
12522490Sjkh			*sp++ = ']';
12532490Sjkh		}
125415944Sache		else if (isupper((unsigned char)*orig)) {
12552490Sjkh			*sp++ = '[';
12562490Sjkh			*sp++ = *orig;
125715944Sache			*sp++ = tolower((unsigned char)*orig);
12582490Sjkh			*sp++ = ']';
12592490Sjkh		}
12602490Sjkh		else
12612490Sjkh			*sp++ = *orig;
12622490Sjkh	}
12632490Sjkh	*sp = '\0';
1264203926Suqs
1265203926Suqs	return (new);
12662490Sjkh}
12672490Sjkh
12682490Sjkh/*
12692490Sjkh * find_matches:
12702490Sjkh *	Find all the fortunes which match the pattern we've been given.
12712490Sjkh */
1272227101Sedstatic int
1273203922Suqsfind_matches(void)
12742490Sjkh{
12752490Sjkh	Fort_len = maxlen_in_list(File_list);
12762490Sjkh	DPRINTF(2, (stderr, "Maximum length is %d\n", Fort_len));
12772490Sjkh	/* extra length, "%\n" is appended */
12782490Sjkh	Fortbuf = do_malloc((unsigned int) Fort_len + 10);
12792490Sjkh
12802490Sjkh	Found_one = FALSE;
12812490Sjkh	matches_in_list(File_list);
1282203926Suqs
1283203926Suqs	return (Found_one);
12842490Sjkh}
12852490Sjkh
12862490Sjkh/*
12872490Sjkh * maxlen_in_list
12882490Sjkh *	Return the maximum fortune len in the file list.
12892490Sjkh */
1290227101Sedstatic int
1291203922Suqsmaxlen_in_list(FILEDESC *list)
12922490Sjkh{
129353210Sbillf	FILEDESC	*fp;
129453210Sbillf	int		len, maxlen;
12952490Sjkh
12962490Sjkh	maxlen = 0;
12972490Sjkh	for (fp = list; fp != NULL; fp = fp->next) {
12982490Sjkh		if (fp->child != NULL) {
12992490Sjkh			if ((len = maxlen_in_list(fp->child)) > maxlen)
13002490Sjkh				maxlen = len;
13012490Sjkh		}
13022490Sjkh		else {
13032490Sjkh			get_tbl(fp);
1304203926Suqs			if (fp->tbl.str_longlen > (unsigned int)maxlen)
13052490Sjkh				maxlen = fp->tbl.str_longlen;
13062490Sjkh		}
13072490Sjkh	}
1308203926Suqs
1309203926Suqs	return (maxlen);
13102490Sjkh}
13112490Sjkh
13122490Sjkh/*
13132490Sjkh * matches_in_list
13142490Sjkh *	Print out the matches from the files in the list.
13152490Sjkh */
1316227101Sedstatic void
1317203922Suqsmatches_in_list(FILEDESC *list)
13182490Sjkh{
131953210Sbillf	char           *sp, *p;
132053210Sbillf	FILEDESC	*fp;
1321203926Suqs	int		in_file;
1322203926Suqs	unsigned char	ch;
13232490Sjkh
13242490Sjkh	for (fp = list; fp != NULL; fp = fp->next) {
13252490Sjkh		if (fp->child != NULL) {
13262490Sjkh			matches_in_list(fp->child);
13272490Sjkh			continue;
13282490Sjkh		}
13292490Sjkh		DPRINTF(1, (stderr, "searching in %s\n", fp->path));
13302490Sjkh		open_fp(fp);
13312490Sjkh		sp = Fortbuf;
13322490Sjkh		in_file = FALSE;
13332490Sjkh		while (fgets(sp, Fort_len, fp->inf) != NULL)
133451863Sdcs			if (fp->tbl.str_flags & STR_COMMENTS
133551863Sdcs			    && sp[0] == fp->tbl.str_delim
133651863Sdcs			    && sp[1] == fp->tbl.str_delim)
133751863Sdcs				continue;
133851863Sdcs			else if (!STR_ENDSTRING(sp, fp->tbl))
13392490Sjkh				sp += strlen(sp);
13402490Sjkh			else {
13412490Sjkh				*sp = '\0';
134215944Sache				if (fp->tbl.str_flags & STR_ROTATED)
134315944Sache					for (p = Fortbuf; (ch = *p) != '\0'; ++p) {
134415944Sache						if (isascii(ch)) {
134515944Sache							if (isupper(ch))
134615944Sache								*p = 'A' + (ch - 'A' + 13) % 26;
134715944Sache							else if (islower(ch))
134815944Sache								*p = 'a' + (ch - 'a' + 13) % 26;
134915944Sache						}
135015944Sache					}
1351130851Sphk				if (regexec(&Re_pat, Fortbuf, 0, NULL, 0) != REG_NOMATCH) {
13522490Sjkh					printf("%c%c", fp->tbl.str_delim,
13532490Sjkh					    fp->tbl.str_delim);
13542490Sjkh					if (!in_file) {
13552490Sjkh						printf(" (%s)", fp->name);
13562490Sjkh						Found_one = TRUE;
13572490Sjkh						in_file = TRUE;
13582490Sjkh					}
13592490Sjkh					putchar('\n');
13602490Sjkh					(void) fwrite(Fortbuf, 1, (sp - Fortbuf), stdout);
13612490Sjkh				}
13622490Sjkh				sp = Fortbuf;
13632490Sjkh			}
13642490Sjkh	}
13652490Sjkh}
13662490Sjkh
1367227101Sedstatic void
1368203922Suqsusage(void)
13692490Sjkh{
13702490Sjkh	(void) fprintf(stderr, "fortune [-a");
13712490Sjkh#ifdef	DEBUG
13722490Sjkh	(void) fprintf(stderr, "D");
13732490Sjkh#endif	/* DEBUG */
1374141581Sru	(void) fprintf(stderr, "efilosw]");
13752490Sjkh	(void) fprintf(stderr, " [-m pattern]");
1376141581Sru	(void) fprintf(stderr, " [[N%%] file/directory/all]\n");
13772490Sjkh	exit(1);
13782490Sjkh}
1379173396Sedwin
1380173396Sedwin/*
1381173396Sedwin * getpath
1382173396Sedwin * 	Set up file search patch from environment var FORTUNE_PATH;
1383173396Sedwin *	if not set, use the compiled in FORTDIR.
1384173396Sedwin */
1385173396Sedwin
1386227101Sedstatic void
1387173396Sedwingetpath(void)
1388173396Sedwin{
1389173401Sedwin	int	nstr, foundenv;
1390173401Sedwin	char	*pch, **ppch, *str, *path;
1391173396Sedwin
1392173401Sedwin	foundenv = 1;
1393173396Sedwin	Fortune_path = getenv("FORTUNE_PATH");
1394173401Sedwin	if (Fortune_path == NULL) {
1395173401Sedwin		Fortune_path = FORTDIR;
1396173401Sedwin		foundenv = 0;
1397173401Sedwin	}
1398173396Sedwin	path = strdup(Fortune_path);
1399173396Sedwin
1400173396Sedwin	for (nstr = 2, pch = path; *pch != '\0'; pch++) {
1401173396Sedwin		if (*pch == ':')
1402173396Sedwin			nstr++;
1403173396Sedwin	}
1404173396Sedwin
1405173396Sedwin	ppch = Fortune_path_arr = (char **)calloc(nstr, sizeof(char *));
1406173396Sedwin
1407173396Sedwin	nstr = 0;
1408173396Sedwin	str = strtok(path, ":");
1409173396Sedwin	while (str) {
1410173396Sedwin		if (is_dir(str)) {
1411173396Sedwin			nstr++;
1412173396Sedwin			*ppch++ = str;
1413173396Sedwin		}
1414173396Sedwin		str = strtok(NULL, ":");
1415173396Sedwin	}
1416173401Sedwin
1417173396Sedwin	if (nstr == 0) {
1418173401Sedwin		if (foundenv == 1) {
1419173401Sedwin			fprintf(stderr,
1420173401Sedwin			    "fortune: FORTUNE_PATH: None of the specified "
1421173401Sedwin			    "directories found.\n");
1422173401Sedwin			exit(1);
1423173401Sedwin		}
1424173396Sedwin		free(path);
1425203922Suqs		Fortune_path_arr[0] = strdup(FORTDIR);
1426173396Sedwin	}
1427173396Sedwin}
1428