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