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