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