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