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