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