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