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