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