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