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