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