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