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