1 /* $OpenBSD: edit.c,v 1.2 2001/01/29 01:58:01 niklas Exp $ */ 2 3 /* 4 * Copyright (c) 1984,1985,1989,1994,1995 Mark Nudelman 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice in the documentation and/or other materials provided with 14 * the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY 17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 22 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 23 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 24 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 25 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 26 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 30 #include "less.h" 31 32 public int fd0 = 0; 33 34 extern int new_file; 35 extern int errmsgs; 36 extern int quit_at_eof; 37 extern int cbufs; 38 extern char *every_first_cmd; 39 extern int any_display; 40 extern int force_open; 41 extern int is_tty; 42 extern IFILE curr_ifile; 43 extern IFILE old_ifile; 44 extern struct scrpos initial_scrpos; 45 46 #if LOGFILE 47 extern int logfile; 48 extern int force_logfile; 49 extern char *namelogfile; 50 #endif 51 52 char *curr_altfilename = NULL; 53 static void *curr_altpipe; 54 55 56 /* 57 * Textlist functions deal with a list of words separated by spaces. 58 * init_textlist sets up a textlist structure. 59 * forw_textlist uses that structure to iterate thru the list of 60 * words, returning each one as a standard null-terminated string. 61 * back_textlist does the same, but runs thru the list backwards. 62 */ 63 public void 64 init_textlist(tlist, str) 65 struct textlist *tlist; 66 char *str; 67 { 68 char *s; 69 70 tlist->string = skipsp(str); 71 tlist->endstring = tlist->string + strlen(tlist->string); 72 for (s = str; s < tlist->endstring; s++) 73 { 74 if (*s == ' ') 75 *s = '\0'; 76 } 77 } 78 79 public char * 80 forw_textlist(tlist, prev) 81 struct textlist *tlist; 82 char *prev; 83 { 84 char *s; 85 86 /* 87 * prev == NULL means return the first word in the list. 88 * Otherwise, return the word after "prev". 89 */ 90 if (prev == NULL) 91 s = tlist->string; 92 else 93 s = prev + strlen(prev); 94 if (s >= tlist->endstring) 95 return (NULL); 96 while (*s == '\0') 97 s++; 98 if (s >= tlist->endstring) 99 return (NULL); 100 return (s); 101 } 102 103 public char * 104 back_textlist(tlist, prev) 105 struct textlist *tlist; 106 char *prev; 107 { 108 char *s; 109 110 /* 111 * prev == NULL means return the last word in the list. 112 * Otherwise, return the word before "prev". 113 */ 114 if (prev == NULL) 115 s = tlist->endstring; 116 else if (prev <= tlist->string) 117 return (NULL); 118 else 119 s = prev - 1; 120 while (*s == '\0') 121 s--; 122 if (s <= tlist->string) 123 return (NULL); 124 while (s[-1] != '\0' && s > tlist->string) 125 s--; 126 return (s); 127 } 128 129 /* 130 * Close the current input file. 131 */ 132 static void 133 close_file() 134 { 135 struct scrpos scrpos; 136 137 if (curr_ifile == NULL_IFILE) 138 return; 139 /* 140 * Save the current position so that we can return to 141 * the same position if we edit this file again. 142 */ 143 get_scrpos(&scrpos); 144 if (scrpos.pos != NULL_POSITION) 145 { 146 store_pos(curr_ifile, &scrpos); 147 lastmark(); 148 } 149 /* 150 * Close the file descriptor, unless it is a pipe. 151 */ 152 ch_close(); 153 /* 154 * If we opened a file using an alternate name, 155 * do special stuff to close it. 156 */ 157 if (curr_altfilename != NULL) 158 { 159 close_altfile(curr_altfilename, get_filename(curr_ifile), 160 curr_altpipe); 161 free(curr_altfilename); 162 curr_altfilename = NULL; 163 } 164 curr_ifile = NULL_IFILE; 165 } 166 167 /* 168 * Edit a new file (given its name). 169 * Filename == "-" means standard input. 170 * Filename == NULL means just close the current file. 171 */ 172 public int 173 edit(filename) 174 char *filename; 175 { 176 if (filename == NULL) 177 return (edit_ifile(NULL_IFILE)); 178 return (edit_ifile(get_ifile(filename, curr_ifile))); 179 } 180 181 /* 182 * Edit a new file (given its IFILE). 183 * ifile == NULL means just close the current file. 184 */ 185 public int 186 edit_ifile(ifile) 187 IFILE ifile; 188 { 189 int f; 190 int answer; 191 int no_display; 192 int chflags; 193 char *filename; 194 char *open_filename; 195 char *alt_filename; 196 void *alt_pipe; 197 IFILE was_curr_ifile; 198 PARG parg; 199 200 if (ifile == curr_ifile) 201 { 202 /* 203 * Already have the correct file open. 204 */ 205 return (0); 206 } 207 208 /* 209 * We must close the currently open file now. 210 * This is necessary to make the open_altfile/close_altfile pairs 211 * nest properly (or rather to avoid nesting at all). 212 * {{ Some stupid implementations of popen() mess up if you do: 213 * fA = popen("A"); fB = popen("B"); pclose(fA); pclose(fB); }} 214 */ 215 #if LOGFILE 216 end_logfile(); 217 #endif 218 was_curr_ifile = curr_ifile; 219 if (curr_ifile != NULL_IFILE) 220 { 221 close_file(); 222 } 223 224 if (ifile == NULL_IFILE) 225 { 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 return (0); 233 } 234 235 filename = get_filename(ifile); 236 /* 237 * See if LESSOPEN specifies an "alternate" file to open. 238 */ 239 alt_pipe = NULL; 240 alt_filename = open_altfile(filename, &f, &alt_pipe); 241 open_filename = (alt_filename != NULL) ? alt_filename : filename; 242 243 chflags = 0; 244 if (alt_pipe != NULL) 245 { 246 /* 247 * The alternate "file" is actually a pipe. 248 * f has already been set to the file descriptor of the pipe 249 * in the call to open_altfile above. 250 * Keep the file descriptor open because it was opened 251 * via popen(), and pclose() wants to close it. 252 */ 253 chflags |= CH_POPENED; 254 } else if (strcmp(open_filename, "-") == 0) 255 { 256 /* 257 * Use standard input. 258 * Keep the file descriptor open because we can't reopen it. 259 */ 260 f = fd0; 261 chflags |= CH_KEEPOPEN; 262 } else if ((parg.p_string = bad_file(open_filename)) != NULL) 263 { 264 /* 265 * It looks like a bad file. Don't try to open it. 266 */ 267 error("%s", &parg); 268 free(parg.p_string); 269 err1: 270 if (alt_filename != NULL) 271 { 272 close_altfile(alt_filename, filename, alt_pipe); 273 free(alt_filename); 274 } 275 del_ifile(ifile); 276 /* 277 * Re-open the current file. 278 */ 279 (void) edit_ifile(was_curr_ifile); 280 return (1); 281 } else if ((f = open(open_filename, OPEN_READ)) < 0) 282 { 283 /* 284 * Got an error trying to open it. 285 */ 286 parg.p_string = errno_message(filename); 287 error("%s", &parg); 288 free(parg.p_string); 289 goto err1; 290 } else if (!force_open && !opened(ifile) && bin_file(f)) 291 { 292 /* 293 * Looks like a binary file. Ask user if we should proceed. 294 */ 295 parg.p_string = filename; 296 answer = query("\"%s\" may be a binary file. See it anyway? ", 297 &parg); 298 if (answer != 'y' && answer != 'Y') 299 { 300 close(f); 301 goto err1; 302 } 303 } 304 305 /* 306 * Get the new ifile. 307 * Get the saved position for the file. 308 */ 309 if (was_curr_ifile != NULL_IFILE) 310 old_ifile = was_curr_ifile; 311 curr_ifile = ifile; 312 curr_altfilename = alt_filename; 313 curr_altpipe = alt_pipe; 314 set_open(curr_ifile); /* File has been opened */ 315 get_pos(curr_ifile, &initial_scrpos); 316 new_file = TRUE; 317 ch_init(f, chflags); 318 #if LOGFILE 319 if (namelogfile != NULL && is_tty) 320 use_logfile(namelogfile); 321 #endif 322 323 if (every_first_cmd != NULL) 324 ungetsc(every_first_cmd); 325 326 no_display = !any_display; 327 flush(); 328 any_display = TRUE; 329 330 if (is_tty) 331 { 332 /* 333 * Output is to a real tty. 334 */ 335 336 /* 337 * Indicate there is nothing displayed yet. 338 */ 339 pos_clear(); 340 clr_linenum(); 341 #if HILITE_SEARCH 342 clr_hilite(); 343 #endif 344 if (no_display && errmsgs > 0) 345 { 346 /* 347 * We displayed some messages on error output 348 * (file descriptor 2; see error() function). 349 * Before erasing the screen contents, 350 * display the file name and wait for a keystroke. 351 */ 352 parg.p_string = filename; 353 error("%s", &parg); 354 } 355 } 356 return (0); 357 } 358 359 /* 360 * Edit a space-separated list of files. 361 * For each filename in the list, enter it into the ifile list. 362 * Then edit the first one. 363 */ 364 public int 365 edit_list(filelist) 366 char *filelist; 367 { 368 IFILE save_curr_ifile; 369 char *good_filename; 370 char *filename; 371 char *gfilelist; 372 char *gfilename; 373 struct textlist tl_files; 374 struct textlist tl_gfiles; 375 376 save_curr_ifile = curr_ifile; 377 good_filename = NULL; 378 379 /* 380 * Run thru each filename in the list. 381 * Try to glob the filename. 382 * If it doesn't expand, just try to open the filename. 383 * If it does expand, try to open each name in that list. 384 */ 385 init_textlist(&tl_files, filelist); 386 filename = NULL; 387 while ((filename = forw_textlist(&tl_files, filename)) != NULL) 388 { 389 gfilelist = glob(filename); 390 init_textlist(&tl_gfiles, gfilelist); 391 gfilename = NULL; 392 while ((gfilename = forw_textlist(&tl_gfiles, gfilename)) != NULL) 393 { 394 if (edit(gfilename) == 0 && good_filename == NULL) 395 good_filename = get_filename(curr_ifile); 396 } 397 free(gfilelist); 398 } 399 /* 400 * Edit the first valid filename in the list. 401 */ 402 if (good_filename == NULL) 403 return (1); 404 if (get_ifile(good_filename, curr_ifile) == curr_ifile) 405 /* 406 * Trying to edit the current file; don't reopen it. 407 */ 408 return (0); 409 if (edit_ifile(save_curr_ifile)) 410 quit(QUIT_ERROR); 411 return (edit(good_filename)); 412 } 413 414 /* 415 * Edit the first file in the command line (ifile) list. 416 */ 417 public int 418 edit_first() 419 { 420 curr_ifile = NULL_IFILE; 421 return (edit_next(1)); 422 } 423 424 /* 425 * Edit the last file in the command line (ifile) list. 426 */ 427 public int 428 edit_last() 429 { 430 curr_ifile = NULL_IFILE; 431 return (edit_prev(1)); 432 } 433 434 435 /* 436 * Edit the next file in the command line (ifile) list. 437 */ 438 public int 439 edit_next(n) 440 int n; 441 { 442 IFILE h; 443 IFILE next; 444 445 h = curr_ifile; 446 /* 447 * Skip n filenames, then try to edit each filename. 448 */ 449 for (;;) 450 { 451 next = next_ifile(h); 452 if (--n < 0) 453 { 454 if (edit_ifile(h) == 0) 455 break; 456 } 457 if (next == NULL_IFILE) 458 { 459 /* 460 * Reached end of the ifile list. 461 */ 462 return (1); 463 } 464 h = next; 465 } 466 /* 467 * Found a file that we can edit. 468 */ 469 return (0); 470 } 471 472 /* 473 * Edit the previous file in the command line list. 474 */ 475 public int 476 edit_prev(n) 477 int n; 478 { 479 IFILE h; 480 IFILE next; 481 482 h = curr_ifile; 483 /* 484 * Skip n filenames, then try to edit each filename. 485 */ 486 for (;;) 487 { 488 next = prev_ifile(h); 489 if (--n < 0) 490 { 491 if (edit_ifile(h) == 0) 492 break; 493 } 494 if (next == NULL_IFILE) 495 { 496 /* 497 * Reached beginning of the ifile list. 498 */ 499 return (1); 500 } 501 h = next; 502 } 503 /* 504 * Found a file that we can edit. 505 */ 506 return (0); 507 } 508 509 /* 510 * Edit a specific file in the command line (ifile) list. 511 */ 512 public int 513 edit_index(n) 514 int n; 515 { 516 IFILE h; 517 518 h = NULL_IFILE; 519 do 520 { 521 if ((h = next_ifile(h)) == NULL_IFILE) 522 { 523 /* 524 * Reached end of the list without finding it. 525 */ 526 return (1); 527 } 528 } while (get_index(h) != n); 529 530 return (edit_ifile(h)); 531 } 532 533 /* 534 * Edit standard input. 535 */ 536 public int 537 edit_stdin() 538 { 539 if (isatty(fd0)) 540 { 541 #if MSOFTC || OS2 542 error("Missing filename (\"less -?\" for help)", NULL_PARG); 543 #else 544 error("Missing filename (\"less -\\?\" for help)", NULL_PARG); 545 #endif 546 quit(QUIT_OK); 547 } 548 return (edit("-")); 549 } 550 551 /* 552 * Copy a file directly to standard output. 553 * Used if standard output is not a tty. 554 */ 555 public void 556 cat_file() 557 { 558 register int c; 559 560 while ((c = ch_forw_get()) != EOI) 561 putchr(c); 562 flush(); 563 } 564 565 #if LOGFILE 566 567 /* 568 * If the user asked for a log file and our input file 569 * is standard input, create the log file. 570 * We take care not to blindly overwrite an existing file. 571 */ 572 public void 573 use_logfile(filename) 574 char *filename; 575 { 576 register int exists; 577 register int answer; 578 PARG parg; 579 580 if (ch_getflags() & CH_CANSEEK) 581 /* 582 * Can't currently use a log file on a file that can seek. 583 */ 584 return; 585 586 /* 587 * {{ We could use access() here. }} 588 */ 589 exists = open(filename, OPEN_READ); 590 close(exists); 591 exists = (exists >= 0); 592 593 /* 594 * Decide whether to overwrite the log file or append to it. 595 * If it doesn't exist we "overwrite" it. 596 */ 597 if (!exists || force_logfile) 598 { 599 /* 600 * Overwrite (or create) the log file. 601 */ 602 answer = 'O'; 603 } else 604 { 605 /* 606 * Ask user what to do. 607 */ 608 parg.p_string = filename; 609 answer = query("Warning: \"%s\" exists; Overwrite, Append or Don't log? ", &parg); 610 } 611 612 loop: 613 switch (answer) 614 { 615 case 'O': case 'o': 616 /* 617 * Overwrite: create the file. 618 */ 619 logfile = creat(filename, 0644); 620 break; 621 case 'A': case 'a': 622 /* 623 * Append: open the file and seek to the end. 624 */ 625 logfile = open(filename, OPEN_APPEND); 626 if (lseek(logfile, (off_t)0, 2) == BAD_LSEEK) 627 { 628 close(logfile); 629 logfile = -1; 630 } 631 break; 632 case 'D': case 'd': 633 /* 634 * Don't do anything. 635 */ 636 return; 637 case 'q': 638 quit(QUIT_OK); 639 /*NOTREACHED*/ 640 default: 641 /* 642 * Eh? 643 */ 644 answer = query("Overwrite, Append, or Don't log? (Type \"O\", \"A\", \"D\" or \"q\") ", NULL_PARG); 645 goto loop; 646 } 647 648 if (logfile < 0) 649 { 650 /* 651 * Error in opening logfile. 652 */ 653 parg.p_string = filename; 654 error("Cannot write to \"%s\"", &parg); 655 } 656 } 657 658 #endif 659