1 /* $NetBSD */ 2 3 /* 4 * Copyright (C) 1984-2011 Mark Nudelman 5 * 6 * You may distribute under the terms of either the GNU General Public 7 * License or the Less License, as specified in the README file. 8 * 9 * For more information about less, or for information on how to 10 * contact the author, see the README file. 11 */ 12 13 14 #include "less.h" 15 #if HAVE_STAT 16 #include <sys/stat.h> 17 #endif 18 19 public int fd0 = 0; 20 21 extern int new_file; 22 extern int errmsgs; 23 extern int cbufs; 24 extern char *every_first_cmd; 25 extern int any_display; 26 extern int force_open; 27 extern int is_tty; 28 extern int sigs; 29 extern IFILE curr_ifile; 30 extern IFILE old_ifile; 31 extern struct scrpos initial_scrpos; 32 extern void constant *ml_examine; 33 #if SPACES_IN_FILENAMES 34 extern char openquote; 35 extern char closequote; 36 #endif 37 38 #if LOGFILE 39 extern int logfile; 40 extern int force_logfile; 41 extern char *namelogfile; 42 #endif 43 44 #if HAVE_STAT_INO 45 public dev_t curr_dev; 46 public ino_t curr_ino; 47 #endif 48 49 char *curr_altfilename = NULL; 50 static void *curr_altpipe; 51 52 53 /* 54 * Textlist functions deal with a list of words separated by spaces. 55 * init_textlist sets up a textlist structure. 56 * forw_textlist uses that structure to iterate thru the list of 57 * words, returning each one as a standard null-terminated string. 58 * back_textlist does the same, but runs thru the list backwards. 59 */ 60 public void 61 init_textlist(tlist, str) 62 struct textlist *tlist; 63 char *str; 64 { 65 char *s; 66 #if SPACES_IN_FILENAMES 67 int meta_quoted = 0; 68 int delim_quoted = 0; 69 char *esc = get_meta_escape(); 70 int esclen = strlen(esc); 71 #endif 72 73 tlist->string = skipsp(str); 74 tlist->endstring = tlist->string + strlen(tlist->string); 75 for (s = str; s < tlist->endstring; s++) 76 { 77 #if SPACES_IN_FILENAMES 78 if (meta_quoted) 79 { 80 meta_quoted = 0; 81 } else if (esclen > 0 && s + esclen < tlist->endstring && 82 strncmp(s, esc, esclen) == 0) 83 { 84 meta_quoted = 1; 85 s += esclen - 1; 86 } else if (delim_quoted) 87 { 88 if (*s == closequote) 89 delim_quoted = 0; 90 } else /* (!delim_quoted) */ 91 { 92 if (*s == openquote) 93 delim_quoted = 1; 94 else if (*s == ' ') 95 *s = '\0'; 96 } 97 #else 98 if (*s == ' ') 99 *s = '\0'; 100 #endif 101 } 102 } 103 104 public char * 105 forw_textlist(tlist, prev) 106 struct textlist *tlist; 107 char *prev; 108 { 109 char *s; 110 111 /* 112 * prev == NULL means return the first word in the list. 113 * Otherwise, return the word after "prev". 114 */ 115 if (prev == NULL) 116 s = tlist->string; 117 else 118 s = prev + strlen(prev); 119 if (s >= tlist->endstring) 120 return (NULL); 121 while (*s == '\0') 122 s++; 123 if (s >= tlist->endstring) 124 return (NULL); 125 return (s); 126 } 127 128 public char * 129 back_textlist(tlist, prev) 130 struct textlist *tlist; 131 char *prev; 132 { 133 char *s; 134 135 /* 136 * prev == NULL means return the last word in the list. 137 * Otherwise, return the word before "prev". 138 */ 139 if (prev == NULL) 140 s = tlist->endstring; 141 else if (prev <= tlist->string) 142 return (NULL); 143 else 144 s = prev - 1; 145 while (*s == '\0') 146 s--; 147 if (s <= tlist->string) 148 return (NULL); 149 while (s[-1] != '\0' && s > tlist->string) 150 s--; 151 return (s); 152 } 153 154 /* 155 * Close the current input file. 156 */ 157 static void 158 close_file() 159 { 160 struct scrpos scrpos; 161 162 if (curr_ifile == NULL_IFILE) 163 return; 164 165 /* 166 * Save the current position so that we can return to 167 * the same position if we edit this file again. 168 */ 169 get_scrpos(&scrpos); 170 if (scrpos.pos != NULL_POSITION) 171 { 172 store_pos(curr_ifile, &scrpos); 173 lastmark(); 174 } 175 /* 176 * Close the file descriptor, unless it is a pipe. 177 */ 178 ch_close(); 179 /* 180 * If we opened a file using an alternate name, 181 * do special stuff to close it. 182 */ 183 if (curr_altfilename != NULL) 184 { 185 close_altfile(curr_altfilename, get_filename(curr_ifile), 186 curr_altpipe); 187 free(curr_altfilename); 188 curr_altfilename = NULL; 189 } 190 curr_ifile = NULL_IFILE; 191 #if HAVE_STAT_INO 192 curr_ino = curr_dev = 0; 193 #endif 194 } 195 196 /* 197 * Edit a new file (given its name). 198 * Filename == "-" means standard input. 199 * Filename == NULL means just close the current file. 200 */ 201 public int 202 edit(filename) 203 char *filename; 204 { 205 if (filename == NULL) 206 return (edit_ifile(NULL_IFILE)); 207 return (edit_ifile(get_ifile(filename, curr_ifile))); 208 } 209 210 /* 211 * Edit a new file (given its IFILE). 212 * ifile == NULL means just close the current file. 213 */ 214 public int 215 edit_ifile(ifile) 216 IFILE ifile; 217 { 218 int f; 219 int answer; 220 int no_display; 221 int chflags; 222 char *filename; 223 char *open_filename; 224 char *qopen_filename; 225 char *alt_filename; 226 void *alt_pipe; 227 IFILE was_curr_ifile; 228 PARG parg; 229 230 if (ifile == curr_ifile) 231 { 232 /* 233 * Already have the correct file open. 234 */ 235 return (0); 236 } 237 238 /* 239 * We must close the currently open file now. 240 * This is necessary to make the open_altfile/close_altfile pairs 241 * nest properly (or rather to avoid nesting at all). 242 * {{ Some stupid implementations of popen() mess up if you do: 243 * fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }} 244 */ 245 #if LOGFILE 246 end_logfile(); 247 #endif 248 was_curr_ifile = save_curr_ifile(); 249 if (curr_ifile != NULL_IFILE) 250 { 251 chflags = ch_getflags(); 252 close_file(); 253 if ((chflags & CH_HELPFILE) && held_ifile(was_curr_ifile) <= 1) 254 { 255 /* 256 * Don't keep the help file in the ifile list. 257 */ 258 del_ifile(was_curr_ifile); 259 was_curr_ifile = old_ifile; 260 } 261 } 262 263 if (ifile == NULL_IFILE) 264 { 265 /* 266 * No new file to open. 267 * (Don't set old_ifile, because if you call edit_ifile(NULL), 268 * you're supposed to have saved curr_ifile yourself, 269 * and you'll restore it if necessary.) 270 */ 271 unsave_ifile(was_curr_ifile); 272 return (0); 273 } 274 275 filename = save(get_filename(ifile)); 276 /* 277 * See if LESSOPEN specifies an "alternate" file to open. 278 */ 279 alt_pipe = NULL; 280 alt_filename = open_altfile(filename, &f, &alt_pipe); 281 open_filename = (alt_filename != NULL) ? alt_filename : filename; 282 qopen_filename = shell_unquote(open_filename); 283 284 chflags = 0; 285 if (alt_pipe != NULL) 286 { 287 /* 288 * The alternate "file" is actually a pipe. 289 * f has already been set to the file descriptor of the pipe 290 * in the call to open_altfile above. 291 * Keep the file descriptor open because it was opened 292 * via popen(), and pclose() wants to close it. 293 */ 294 chflags |= CH_POPENED; 295 } else if (strcmp(open_filename, "-") == 0) 296 { 297 /* 298 * Use standard input. 299 * Keep the file descriptor open because we can't reopen it. 300 */ 301 f = fd0; 302 chflags |= CH_KEEPOPEN; 303 /* 304 * Must switch stdin to BINARY mode. 305 */ 306 SET_BINARY(f); 307 #if MSDOS_COMPILER==DJGPPC 308 /* 309 * Setting stdin to binary by default causes 310 * Ctrl-C to not raise SIGINT. We must undo 311 * that side-effect. 312 */ 313 __djgpp_set_ctrl_c(1); 314 #endif 315 } else if (strcmp(open_filename, FAKE_HELPFILE) == 0) 316 { 317 f = -1; 318 chflags |= CH_HELPFILE; 319 } else if ((parg.p_string = bad_file(open_filename)) != NULL) 320 { 321 /* 322 * It looks like a bad file. Don't try to open it. 323 */ 324 error("%s", &parg); 325 free(parg.p_string); 326 err1: 327 if (alt_filename != NULL) 328 { 329 close_altfile(alt_filename, filename, alt_pipe); 330 free(alt_filename); 331 } 332 del_ifile(ifile); 333 free(qopen_filename); 334 free(filename); 335 /* 336 * Re-open the current file. 337 */ 338 if (was_curr_ifile == ifile) 339 { 340 /* 341 * Whoops. The "current" ifile is the one we just deleted. 342 * Just give up. 343 */ 344 quit(QUIT_ERROR); 345 } 346 reedit_ifile(was_curr_ifile); 347 return (1); 348 } else if ((f = open(qopen_filename, OPEN_READ)) < 0) 349 { 350 /* 351 * Got an error trying to open it. 352 */ 353 parg.p_string = errno_message(filename); 354 error("%s", &parg); 355 free(parg.p_string); 356 goto err1; 357 } else 358 { 359 chflags |= CH_CANSEEK; 360 if (!force_open && !opened(ifile) && bin_file(f)) 361 { 362 /* 363 * Looks like a binary file. 364 * Ask user if we should proceed. 365 */ 366 parg.p_string = filename; 367 answer = query("\"%s\" may be a binary file. See it anyway? ", 368 &parg); 369 if (answer != 'y' && answer != 'Y') 370 { 371 close(f); 372 goto err1; 373 } 374 } 375 } 376 377 /* 378 * Get the new ifile. 379 * Get the saved position for the file. 380 */ 381 if (was_curr_ifile != NULL_IFILE) 382 { 383 old_ifile = was_curr_ifile; 384 unsave_ifile(was_curr_ifile); 385 } 386 curr_ifile = ifile; 387 curr_altfilename = alt_filename; 388 curr_altpipe = alt_pipe; 389 set_open(curr_ifile); /* File has been opened */ 390 get_pos(curr_ifile, &initial_scrpos); 391 new_file = TRUE; 392 ch_init(f, chflags); 393 394 if (!(chflags & CH_HELPFILE)) 395 { 396 #if LOGFILE 397 if (namelogfile != NULL && is_tty) 398 use_logfile(namelogfile); 399 #endif 400 #if HAVE_STAT_INO 401 /* Remember the i-number and device of the opened file. */ 402 { 403 struct stat statbuf; 404 int r = stat(qopen_filename, &statbuf); 405 if (r == 0) 406 { 407 curr_ino = statbuf.st_ino; 408 curr_dev = statbuf.st_dev; 409 } 410 } 411 #endif 412 if (every_first_cmd != NULL) 413 ungetsc(every_first_cmd); 414 } 415 416 free(qopen_filename); 417 no_display = !any_display; 418 flush(); 419 any_display = TRUE; 420 421 if (is_tty) 422 { 423 /* 424 * Output is to a real tty. 425 */ 426 427 /* 428 * Indicate there is nothing displayed yet. 429 */ 430 pos_clear(); 431 clr_linenum(); 432 #if HILITE_SEARCH 433 clr_hilite(); 434 #endif 435 cmd_addhist(ml_examine, filename); 436 if (no_display && errmsgs > 0) 437 { 438 /* 439 * We displayed some messages on error output 440 * (file descriptor 2; see error() function). 441 * Before erasing the screen contents, 442 * display the file name and wait for a keystroke. 443 */ 444 parg.p_string = filename; 445 error("%s", &parg); 446 } 447 } 448 free(filename); 449 return (0); 450 } 451 452 /* 453 * Edit a space-separated list of files. 454 * For each filename in the list, enter it into the ifile list. 455 * Then edit the first one. 456 */ 457 public int 458 edit_list(filelist) 459 char *filelist; 460 { 461 IFILE save_ifile; 462 char *good_filename; 463 char *filename; 464 char *gfilelist; 465 char *gfilename; 466 struct textlist tl_files; 467 struct textlist tl_gfiles; 468 469 save_ifile = save_curr_ifile(); 470 good_filename = NULL; 471 472 /* 473 * Run thru each filename in the list. 474 * Try to glob the filename. 475 * If it doesn't expand, just try to open the filename. 476 * If it does expand, try to open each name in that list. 477 */ 478 init_textlist(&tl_files, filelist); 479 filename = NULL; 480 while ((filename = forw_textlist(&tl_files, filename)) != NULL) 481 { 482 gfilelist = lglob(filename); 483 init_textlist(&tl_gfiles, gfilelist); 484 gfilename = NULL; 485 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL) 486 { 487 if (edit(gfilename) == 0 && good_filename == NULL) 488 good_filename = get_filename(curr_ifile); 489 } 490 free(gfilelist); 491 } 492 /* 493 * Edit the first valid filename in the list. 494 */ 495 if (good_filename == NULL) 496 { 497 unsave_ifile(save_ifile); 498 return (1); 499 } 500 if (get_ifile(good_filename, curr_ifile) == curr_ifile) 501 { 502 /* 503 * Trying to edit the current file; don't reopen it. 504 */ 505 unsave_ifile(save_ifile); 506 return (0); 507 } 508 reedit_ifile(save_ifile); 509 return (edit(good_filename)); 510 } 511 512 /* 513 * Edit the first file in the command line (ifile) list. 514 */ 515 public int 516 edit_first() 517 { 518 curr_ifile = NULL_IFILE; 519 return (edit_next(1)); 520 } 521 522 /* 523 * Edit the last file in the command line (ifile) list. 524 */ 525 public int 526 edit_last() 527 { 528 curr_ifile = NULL_IFILE; 529 return (edit_prev(1)); 530 } 531 532 533 /* 534 * Edit the n-th next or previous file in the command line (ifile) list. 535 */ 536 static int 537 edit_istep(h, n, dir) 538 IFILE h; 539 int n; 540 int dir; 541 { 542 IFILE next; 543 544 /* 545 * Skip n filenames, then try to edit each filename. 546 */ 547 for (;;) 548 { 549 next = (dir > 0) ? next_ifile(h) : prev_ifile(h); 550 if (--n < 0) 551 { 552 if (edit_ifile(h) == 0) 553 break; 554 } 555 if (next == NULL_IFILE) 556 { 557 /* 558 * Reached end of the ifile list. 559 */ 560 return (1); 561 } 562 if (ABORT_SIGS()) 563 { 564 /* 565 * Interrupt breaks out, if we're in a long 566 * list of files that can't be opened. 567 */ 568 return (1); 569 } 570 h = next; 571 } 572 /* 573 * Found a file that we can edit. 574 */ 575 return (0); 576 } 577 578 static int 579 edit_inext(h, n) 580 IFILE h; 581 int n; 582 { 583 return (edit_istep(h, n, +1)); 584 } 585 586 public int 587 edit_next(n) 588 int n; 589 { 590 return edit_istep(curr_ifile, n, +1); 591 } 592 593 static int 594 edit_iprev(h, n) 595 IFILE h; 596 int n; 597 { 598 return (edit_istep(h, n, -1)); 599 } 600 601 public int 602 edit_prev(n) 603 int n; 604 { 605 return edit_istep(curr_ifile, n, -1); 606 } 607 608 /* 609 * Edit a specific file in the command line (ifile) list. 610 */ 611 public int 612 edit_index(n) 613 int n; 614 { 615 IFILE h; 616 617 h = NULL_IFILE; 618 do 619 { 620 if ((h = next_ifile(h)) == NULL_IFILE) 621 { 622 /* 623 * Reached end of the list without finding it. 624 */ 625 return (1); 626 } 627 } while (get_index(h) != n); 628 629 return (edit_ifile(h)); 630 } 631 632 public IFILE 633 save_curr_ifile() 634 { 635 if (curr_ifile != NULL_IFILE) 636 hold_ifile(curr_ifile, 1); 637 return (curr_ifile); 638 } 639 640 public void 641 unsave_ifile(save_ifile) 642 IFILE save_ifile; 643 { 644 if (save_ifile != NULL_IFILE) 645 hold_ifile(save_ifile, -1); 646 } 647 648 /* 649 * Reedit the ifile which was previously open. 650 */ 651 public void 652 reedit_ifile(save_ifile) 653 IFILE save_ifile; 654 { 655 IFILE next; 656 IFILE prev; 657 658 /* 659 * Try to reopen the ifile. 660 * Note that opening it may fail (maybe the file was removed), 661 * in which case the ifile will be deleted from the list. 662 * So save the next and prev ifiles first. 663 */ 664 unsave_ifile(save_ifile); 665 next = next_ifile(save_ifile); 666 prev = prev_ifile(save_ifile); 667 if (edit_ifile(save_ifile) == 0) 668 return; 669 /* 670 * If can't reopen it, open the next input file in the list. 671 */ 672 if (next != NULL_IFILE && edit_inext(next, 0) == 0) 673 return; 674 /* 675 * If can't open THAT one, open the previous input file in the list. 676 */ 677 if (prev != NULL_IFILE && edit_iprev(prev, 0) == 0) 678 return; 679 /* 680 * If can't even open that, we're stuck. Just quit. 681 */ 682 quit(QUIT_ERROR); 683 } 684 685 public void 686 reopen_curr_ifile() 687 { 688 IFILE save_ifile = save_curr_ifile(); 689 close_file(); 690 reedit_ifile(save_ifile); 691 } 692 693 /* 694 * Edit standard input. 695 */ 696 public int 697 edit_stdin() 698 { 699 if (isatty(fd0)) 700 { 701 error("Missing filename (\"less --help\" for help)", NULL_PARG); 702 quit(QUIT_OK); 703 } 704 return (edit("-")); 705 } 706 707 /* 708 * Copy a file directly to standard output. 709 * Used if standard output is not a tty. 710 */ 711 public void 712 cat_file() 713 { 714 register int c; 715 716 while ((c = ch_forw_get()) != EOI) 717 putchr(c); 718 flush(); 719 } 720 721 #if LOGFILE 722 723 /* 724 * If the user asked for a log file and our input file 725 * is standard input, create the log file. 726 * We take care not to blindly overwrite an existing file. 727 */ 728 public void 729 use_logfile(filename) 730 char *filename; 731 { 732 register int exists; 733 register int answer; 734 PARG parg; 735 736 if (ch_getflags() & CH_CANSEEK) 737 /* 738 * Can't currently use a log file on a file that can seek. 739 */ 740 return; 741 742 /* 743 * {{ We could use access() here. }} 744 */ 745 filename = shell_unquote(filename); 746 exists = open(filename, OPEN_READ); 747 close(exists); 748 exists = (exists >= 0); 749 750 /* 751 * Decide whether to overwrite the log file or append to it. 752 * If it doesn't exist we "overwrite" it. 753 */ 754 if (!exists || force_logfile) 755 { 756 /* 757 * Overwrite (or create) the log file. 758 */ 759 answer = 'O'; 760 } else 761 { 762 /* 763 * Ask user what to do. 764 */ 765 parg.p_string = filename; 766 answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg); 767 } 768 769 loop: 770 switch (answer) 771 { 772 case 'O': case 'o': 773 /* 774 * Overwrite: create the file. 775 */ 776 logfile = creat(filename, 0644); 777 break; 778 case 'A': case 'a': 779 /* 780 * Append: open the file and seek to the end. 781 */ 782 logfile = open(filename, OPEN_APPEND); 783 if (lseek(logfile, (off_t)0, SEEK_END) == BAD_LSEEK) 784 { 785 close(logfile); 786 logfile = -1; 787 } 788 break; 789 case 'D': case 'd': 790 /* 791 * Don't do anything. 792 */ 793 free(filename); 794 return; 795 case 'q': 796 quit(QUIT_OK); 797 /*NOTREACHED*/ 798 default: 799 /* 800 * Eh? 801 */ 802 answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG); 803 goto loop; 804 } 805 806 if (logfile < 0) 807 { 808 /* 809 * Error in opening logfile. 810 */ 811 parg.p_string = filename; 812 error("Cannot write to \"%s\"", &parg); 813 free(filename); 814 return; 815 } 816 free(filename); 817 SET_BINARY(logfile); 818 } 819 820 #endif 821