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