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