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