1 /* $OpenBSD: main.c,v 1.40 2014/07/12 02:47:51 guenther Exp $ */ 2 /* $NetBSD: main.c,v 1.3 1995/03/21 09:04:44 cgd Exp $ */ 3 4 /* main.c: This file contains the main control and user-interface routines 5 for the ed line editor. */ 6 /*- 7 * Copyright (c) 1993 Andrew Moore, Talke Studio. 8 * All rights reserved. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 /* 33 * CREDITS 34 * 35 * This program is based on the editor algorithm described in 36 * Brian W. Kernighan and P. J. Plauger's book "Software Tools 37 * in Pascal," Addison-Wesley, 1981. 38 * 39 * The buffering algorithm is attributed to Rodney Ruddock of 40 * the University of Guelph, Guelph, Ontario. 41 * 42 */ 43 44 #include <sys/ioctl.h> 45 #include <sys/stat.h> 46 #include <sys/wait.h> 47 #include <ctype.h> 48 #include <setjmp.h> 49 #include <unistd.h> 50 #include <pwd.h> 51 52 #include "ed.h" 53 54 55 #ifdef _POSIX_SOURCE 56 sigjmp_buf env; 57 #else 58 jmp_buf env; 59 #endif 60 61 /* static buffers */ 62 char stdinbuf[1]; /* stdin buffer */ 63 char *shcmd; /* shell command buffer */ 64 int shcmdsz; /* shell command buffer size */ 65 int shcmdi; /* shell command buffer index */ 66 char *ibuf; /* ed command-line buffer */ 67 int ibufsz; /* ed command-line buffer size */ 68 char *ibufp; /* pointer to ed command-line buffer */ 69 70 /* global flags */ 71 int garrulous = 0; /* if set, print all error messages */ 72 int isbinary; /* if set, buffer contains ASCII NULs */ 73 int isglobal; /* if set, doing a global command */ 74 int modified; /* if set, buffer modified since last write */ 75 int mutex = 0; /* if set, signals set "sigflags" */ 76 int scripted = 0; /* if set, suppress diagnostics */ 77 int sigflags = 0; /* if set, signals received while mutex set */ 78 int interactive = 0; /* if set, we are in interactive mode */ 79 80 /* if set, signal handlers are enabled */ 81 volatile sig_atomic_t sigactive = 0; 82 83 char old_filename[MAXPATHLEN] = ""; /* default filename */ 84 int current_addr; /* current address in editor buffer */ 85 int addr_last; /* last address in editor buffer */ 86 int lineno; /* script line number */ 87 char *prompt; /* command-line prompt */ 88 char *dps = "*"; /* default command-line prompt */ 89 90 const char usage[] = "usage: %s [-] [-s] [-p string] [file]\n"; 91 92 char *home; /* home directory */ 93 94 void 95 seterrmsg(char *s) 96 { 97 strlcpy(errmsg, s, sizeof(errmsg)); 98 } 99 100 /* ed: line editor */ 101 int 102 main(volatile int argc, char ** volatile argv) 103 { 104 int c, n; 105 int status = 0; 106 107 home = getenv("HOME"); 108 109 top: 110 while ((c = getopt(argc, argv, "p:sx")) != -1) 111 switch (c) { 112 case 'p': /* set prompt */ 113 prompt = optarg; 114 break; 115 case 's': /* run script */ 116 scripted = 1; 117 break; 118 case 'x': /* use crypt */ 119 fprintf(stderr, "crypt unavailable\n?\n"); 120 break; 121 default: 122 fprintf(stderr, usage, argv[0]); 123 exit(1); 124 } 125 argv += optind; 126 argc -= optind; 127 if (argc && **argv == '-') { 128 scripted = 1; 129 if (argc > 1) { 130 optind = 1; 131 goto top; 132 } 133 argv++; 134 argc--; 135 } 136 137 if (!(interactive = isatty(0))) { 138 struct stat sb; 139 140 /* assert: pipes show up as fifo's when fstat'd */ 141 if (fstat(STDIN_FILENO, &sb) || !S_ISFIFO(sb.st_mode)) { 142 if (lseek(STDIN_FILENO, 0, SEEK_CUR)) { 143 interactive = 1; 144 setlinebuf(stdout); 145 } 146 } 147 } 148 149 /* assert: reliable signals! */ 150 #ifdef SIGWINCH 151 if (isatty(STDIN_FILENO)) { 152 handle_winch(SIGWINCH); 153 signal(SIGWINCH, handle_winch); 154 } 155 #endif 156 signal(SIGHUP, signal_hup); 157 signal(SIGQUIT, SIG_IGN); 158 signal(SIGINT, signal_int); 159 #ifdef _POSIX_SOURCE 160 if (status = sigsetjmp(env, 1)) 161 #else 162 if ((status = setjmp(env)) != 0) 163 #endif 164 { 165 fputs("\n?\n", stderr); 166 seterrmsg("interrupt"); 167 } else { 168 init_buffers(); 169 sigactive = 1; /* enable signal handlers */ 170 if (argc && **argv) { 171 if (read_file(*argv, 0) < 0 && !interactive) 172 quit(2); 173 else if (**argv != '!') 174 strlcpy(old_filename, *argv, 175 sizeof old_filename); 176 } else if (argc) { 177 fputs("?\n", stderr); 178 if (**argv == '\0') 179 seterrmsg("invalid filename"); 180 if (!interactive) 181 quit(2); 182 } 183 } 184 for (;;) { 185 if (status < 0 && garrulous) 186 fprintf(stderr, "%s\n", errmsg); 187 if (prompt) { 188 fputs(prompt, stdout); 189 fflush(stdout); 190 } 191 if ((n = get_tty_line()) < 0) { 192 status = ERR; 193 continue; 194 } else if (n == 0) { 195 if (modified && !scripted) { 196 fputs("?\n", stderr); 197 seterrmsg("warning: file modified"); 198 if (!interactive) { 199 fprintf(stderr, garrulous ? 200 "script, line %d: %s\n" : 201 "", lineno, errmsg); 202 quit(2); 203 } 204 clearerr(stdin); 205 modified = 0; 206 status = EMOD; 207 continue; 208 } else 209 quit(0); 210 } else if (ibuf[n - 1] != '\n') { 211 /* discard line */ 212 seterrmsg("unexpected end-of-file"); 213 clearerr(stdin); 214 status = ERR; 215 continue; 216 } 217 isglobal = 0; 218 if ((status = extract_addr_range()) >= 0 && 219 (status = exec_command()) >= 0) 220 if (!status || (status && 221 (status = display_lines(current_addr, current_addr, 222 status)) >= 0)) 223 continue; 224 switch (status) { 225 case EOF: 226 quit(0); 227 case EMOD: 228 modified = 0; 229 fputs("?\n", stderr); /* give warning */ 230 seterrmsg("warning: file modified"); 231 if (!interactive) { 232 fprintf(stderr, garrulous ? 233 "script, line %d: %s\n" : 234 "", lineno, errmsg); 235 quit(2); 236 } 237 break; 238 case FATAL: 239 if (!interactive) 240 fprintf(stderr, garrulous ? 241 "script, line %d: %s\n" : "", 242 lineno, errmsg); 243 else 244 fprintf(stderr, garrulous ? "%s\n" : "", 245 errmsg); 246 quit(3); 247 default: 248 fputs("?\n", stderr); 249 if (!interactive) { 250 fprintf(stderr, garrulous ? 251 "script, line %d: %s\n" : "", 252 lineno, errmsg); 253 quit(2); 254 } 255 break; 256 } 257 } 258 /*NOTREACHED*/ 259 } 260 261 int first_addr, second_addr, addr_cnt; 262 263 /* extract_addr_range: get line addresses from the command buffer until an 264 illegal address is seen; return status */ 265 int 266 extract_addr_range(void) 267 { 268 int addr; 269 270 addr_cnt = 0; 271 first_addr = second_addr = current_addr; 272 while ((addr = next_addr()) >= 0) { 273 addr_cnt++; 274 first_addr = second_addr; 275 second_addr = addr; 276 if (*ibufp != ',' && *ibufp != ';') 277 break; 278 else if (*ibufp++ == ';') 279 current_addr = addr; 280 } 281 if ((addr_cnt = min(addr_cnt, 2)) == 1 || second_addr != addr) 282 first_addr = second_addr; 283 return (addr == ERR) ? ERR : 0; 284 } 285 286 287 #define SKIP_BLANKS() \ 288 do { \ 289 while (isspace((unsigned char)*ibufp) && *ibufp != '\n') \ 290 ibufp++; \ 291 } while (0) 292 293 #define MUST_BE_FIRST() \ 294 do { \ 295 if (!first) { \ 296 seterrmsg("invalid address"); \ 297 return ERR; \ 298 } \ 299 } while (0) 300 301 302 /* next_addr: return the next line address in the command buffer */ 303 int 304 next_addr(void) 305 { 306 char *hd; 307 int addr = current_addr; 308 int n; 309 int first = 1; 310 int c; 311 312 SKIP_BLANKS(); 313 for (hd = ibufp;; first = 0) 314 switch ((c = (unsigned char)*ibufp)) { 315 case '+': 316 case '\t': 317 case ' ': 318 case '-': 319 case '^': 320 ibufp++; 321 SKIP_BLANKS(); 322 if (isdigit((unsigned char)*ibufp)) { 323 STRTOI(n, ibufp); 324 addr += (c == '-' || c == '^') ? -n : n; 325 } else if (!isspace(c)) 326 addr += (c == '-' || c == '^') ? -1 : 1; 327 break; 328 case '0': case '1': case '2': 329 case '3': case '4': case '5': 330 case '6': case '7': case '8': case '9': 331 MUST_BE_FIRST(); 332 STRTOI(addr, ibufp); 333 break; 334 case '.': 335 case '$': 336 MUST_BE_FIRST(); 337 ibufp++; 338 addr = (c == '.') ? current_addr : addr_last; 339 break; 340 case '/': 341 case '?': 342 MUST_BE_FIRST(); 343 if ((addr = get_matching_node_addr( 344 get_compiled_pattern(), c == '/')) < 0) 345 return ERR; 346 else if (c == *ibufp) 347 ibufp++; 348 break; 349 case '\'': 350 MUST_BE_FIRST(); 351 ibufp++; 352 if ((addr = get_marked_node_addr((unsigned char)*ibufp++)) < 0) 353 return ERR; 354 break; 355 case '%': 356 case ',': 357 case ';': 358 if (first) { 359 ibufp++; 360 addr_cnt++; 361 second_addr = (c == ';') ? current_addr : 1; 362 addr = addr_last; 363 break; 364 } 365 /* FALLTHROUGH */ 366 default: 367 if (ibufp == hd) 368 return EOF; 369 else if (addr < 0 || addr_last < addr) { 370 seterrmsg("invalid address"); 371 return ERR; 372 } else 373 return addr; 374 } 375 /* NOTREACHED */ 376 } 377 378 379 #ifdef BACKWARDS 380 /* GET_THIRD_ADDR: get a legal address from the command buffer */ 381 #define GET_THIRD_ADDR(addr) \ 382 do { \ 383 int ol1, ol2; \ 384 \ 385 ol1 = first_addr; \ 386 ol2 = second_addr; \ 387 if (extract_addr_range() < 0) \ 388 return ERR; \ 389 else if (addr_cnt == 0) { \ 390 seterrmsg("destination expected"); \ 391 return ERR; \ 392 } else if (second_addr < 0 || addr_last < second_addr) { \ 393 seterrmsg("invalid address"); \ 394 return ERR; \ 395 } \ 396 addr = second_addr; \ 397 first_addr = ol1; \ 398 second_addr = ol2; \ 399 } while (0) 400 401 #else /* BACKWARDS */ 402 /* GET_THIRD_ADDR: get a legal address from the command buffer */ 403 #define GET_THIRD_ADDR(addr) \ 404 do { \ 405 int ol1, ol2; \ 406 \ 407 ol1 = first_addr; \ 408 ol2 = second_addr; \ 409 if (extract_addr_range() < 0) \ 410 return ERR; \ 411 if (second_addr < 0 || addr_last < second_addr) { \ 412 seterrmsg("invalid address"); \ 413 return ERR; \ 414 } \ 415 addr = second_addr; \ 416 first_addr = ol1; \ 417 second_addr = ol2; \ 418 } while (0) 419 #endif 420 421 422 /* GET_COMMAND_SUFFIX: verify the command suffix in the command buffer */ 423 #define GET_COMMAND_SUFFIX() \ 424 do { \ 425 int done = 0; \ 426 do { \ 427 switch (*ibufp) { \ 428 case 'p': \ 429 gflag |= GPR; \ 430 ibufp++; \ 431 break; \ 432 case 'l': \ 433 gflag |= GLS; \ 434 ibufp++; \ 435 break; \ 436 case 'n': \ 437 gflag |= GNP; \ 438 ibufp++; \ 439 break; \ 440 default: \ 441 done++; \ 442 } \ 443 } while (!done); \ 444 if (*ibufp++ != '\n') { \ 445 seterrmsg("invalid command suffix"); \ 446 return ERR; \ 447 } \ 448 } while (0) 449 450 /* sflags */ 451 #define SGG 001 /* complement previous global substitute suffix */ 452 #define SGP 002 /* complement previous print suffix */ 453 #define SGR 004 /* use last regex instead of last pat */ 454 #define SGF 010 /* repeat last substitution */ 455 456 int patlock = 0; /* if set, pattern not freed by get_compiled_pattern() */ 457 458 volatile sig_atomic_t rows = 22; /* scroll length: ws_row - 2 */ 459 volatile sig_atomic_t cols = 72; /* wrap column */ 460 461 /* exec_command: execute the next command in command buffer; return print 462 request, if any */ 463 int 464 exec_command(void) 465 { 466 extern int u_current_addr; 467 extern int u_addr_last; 468 469 static pattern_t *pat = NULL; 470 static int sgflag = 0; 471 static int sgnum = 0; 472 473 pattern_t *tpat; 474 char *fnp; 475 int gflag = 0; 476 int sflags = 0; 477 int addr = 0; 478 int n = 0; 479 int c; 480 481 SKIP_BLANKS(); 482 switch ((c = (unsigned char)*ibufp++)) { 483 case 'a': 484 GET_COMMAND_SUFFIX(); 485 if (!isglobal) clear_undo_stack(); 486 if (append_lines(second_addr) < 0) 487 return ERR; 488 break; 489 case 'c': 490 if (check_addr_range(current_addr, current_addr) < 0) 491 return ERR; 492 GET_COMMAND_SUFFIX(); 493 if (!isglobal) clear_undo_stack(); 494 if (delete_lines(first_addr, second_addr) < 0 || 495 append_lines(current_addr) < 0) 496 return ERR; 497 break; 498 case 'd': 499 if (check_addr_range(current_addr, current_addr) < 0) 500 return ERR; 501 GET_COMMAND_SUFFIX(); 502 if (!isglobal) clear_undo_stack(); 503 if (delete_lines(first_addr, second_addr) < 0) 504 return ERR; 505 else if ((addr = INC_MOD(current_addr, addr_last)) != 0) 506 current_addr = addr; 507 break; 508 case 'e': 509 if (modified && !scripted) 510 return EMOD; 511 /* FALLTHROUGH */ 512 case 'E': 513 if (addr_cnt > 0) { 514 seterrmsg("unexpected address"); 515 return ERR; 516 } else if (!isspace((unsigned char)*ibufp)) { 517 seterrmsg("unexpected command suffix"); 518 return ERR; 519 } else if ((fnp = get_filename()) == NULL) 520 return ERR; 521 GET_COMMAND_SUFFIX(); 522 if (delete_lines(1, addr_last) < 0) 523 return ERR; 524 clear_undo_stack(); 525 if (close_sbuf() < 0) 526 return ERR; 527 else if (open_sbuf() < 0) 528 return FATAL; 529 if (*fnp && *fnp != '!') 530 strlcpy(old_filename, fnp, sizeof old_filename); 531 #ifdef BACKWARDS 532 if (*fnp == '\0' && *old_filename == '\0') { 533 seterrmsg("no current filename"); 534 return ERR; 535 } 536 #endif 537 if (read_file(*fnp ? fnp : old_filename, 0) < 0) 538 return ERR; 539 clear_undo_stack(); 540 modified = 0; 541 u_current_addr = u_addr_last = -1; 542 break; 543 case 'f': 544 if (addr_cnt > 0) { 545 seterrmsg("unexpected address"); 546 return ERR; 547 } else if (!isspace((unsigned char)*ibufp)) { 548 seterrmsg("unexpected command suffix"); 549 return ERR; 550 } else if ((fnp = get_filename()) == NULL) 551 return ERR; 552 else if (*fnp == '!') { 553 seterrmsg("invalid redirection"); 554 return ERR; 555 } 556 GET_COMMAND_SUFFIX(); 557 if (*fnp) 558 strlcpy(old_filename, fnp, sizeof old_filename); 559 puts(strip_escapes(old_filename)); 560 break; 561 case 'g': 562 case 'v': 563 case 'G': 564 case 'V': 565 if (isglobal) { 566 seterrmsg("cannot nest global commands"); 567 return ERR; 568 } else if (check_addr_range(1, addr_last) < 0) 569 return ERR; 570 else if (build_active_list(c == 'g' || c == 'G') < 0) 571 return ERR; 572 else if ((n = (c == 'G' || c == 'V'))) 573 GET_COMMAND_SUFFIX(); 574 isglobal++; 575 if (exec_global(n, gflag) < 0) 576 return ERR; 577 break; 578 case 'h': 579 if (addr_cnt > 0) { 580 seterrmsg("unexpected address"); 581 return ERR; 582 } 583 GET_COMMAND_SUFFIX(); 584 if (*errmsg) fprintf(stderr, "%s\n", errmsg); 585 break; 586 case 'H': 587 if (addr_cnt > 0) { 588 seterrmsg("unexpected address"); 589 return ERR; 590 } 591 GET_COMMAND_SUFFIX(); 592 if ((garrulous = 1 - garrulous) && *errmsg) 593 fprintf(stderr, "%s\n", errmsg); 594 break; 595 case 'i': 596 if (second_addr == 0) { 597 seterrmsg("invalid address"); 598 return ERR; 599 } 600 GET_COMMAND_SUFFIX(); 601 if (!isglobal) clear_undo_stack(); 602 if (append_lines(second_addr - 1) < 0) 603 return ERR; 604 break; 605 case 'j': 606 if (check_addr_range(current_addr, current_addr + 1) < 0) 607 return ERR; 608 GET_COMMAND_SUFFIX(); 609 if (!isglobal) clear_undo_stack(); 610 if (first_addr != second_addr && 611 join_lines(first_addr, second_addr) < 0) 612 return ERR; 613 break; 614 case 'k': 615 c = (unsigned char)*ibufp++; 616 if (second_addr == 0) { 617 seterrmsg("invalid address"); 618 return ERR; 619 } 620 GET_COMMAND_SUFFIX(); 621 if (mark_line_node(get_addressed_line_node(second_addr), c) < 0) 622 return ERR; 623 break; 624 case 'l': 625 if (check_addr_range(current_addr, current_addr) < 0) 626 return ERR; 627 GET_COMMAND_SUFFIX(); 628 if (display_lines(first_addr, second_addr, gflag | GLS) < 0) 629 return ERR; 630 gflag = 0; 631 break; 632 case 'm': 633 if (check_addr_range(current_addr, current_addr) < 0) 634 return ERR; 635 GET_THIRD_ADDR(addr); 636 if (first_addr <= addr && addr < second_addr) { 637 seterrmsg("invalid destination"); 638 return ERR; 639 } 640 GET_COMMAND_SUFFIX(); 641 if (!isglobal) clear_undo_stack(); 642 if (move_lines(addr) < 0) 643 return ERR; 644 break; 645 case 'n': 646 if (check_addr_range(current_addr, current_addr) < 0) 647 return ERR; 648 GET_COMMAND_SUFFIX(); 649 if (display_lines(first_addr, second_addr, gflag | GNP) < 0) 650 return ERR; 651 gflag = 0; 652 break; 653 case 'p': 654 if (check_addr_range(current_addr, current_addr) < 0) 655 return ERR; 656 GET_COMMAND_SUFFIX(); 657 if (display_lines(first_addr, second_addr, gflag | GPR) < 0) 658 return ERR; 659 gflag = 0; 660 break; 661 case 'P': 662 if (addr_cnt > 0) { 663 seterrmsg("unexpected address"); 664 return ERR; 665 } 666 GET_COMMAND_SUFFIX(); 667 prompt = prompt ? NULL : optarg ? optarg : dps; 668 break; 669 case 'q': 670 case 'Q': 671 if (addr_cnt > 0) { 672 seterrmsg("unexpected address"); 673 return ERR; 674 } 675 GET_COMMAND_SUFFIX(); 676 gflag = (modified && !scripted && c == 'q') ? EMOD : EOF; 677 break; 678 case 'r': 679 if (!isspace((unsigned char)*ibufp)) { 680 seterrmsg("unexpected command suffix"); 681 return ERR; 682 } else if (addr_cnt == 0) 683 second_addr = addr_last; 684 if ((fnp = get_filename()) == NULL) 685 return ERR; 686 GET_COMMAND_SUFFIX(); 687 if (!isglobal) clear_undo_stack(); 688 if (*old_filename == '\0' && *fnp != '!') 689 strlcpy(old_filename, fnp, sizeof old_filename); 690 #ifdef BACKWARDS 691 if (*fnp == '\0' && *old_filename == '\0') { 692 seterrmsg("no current filename"); 693 return ERR; 694 } 695 #endif 696 if ((addr = read_file(*fnp ? fnp : old_filename, 697 second_addr)) < 0) 698 return ERR; 699 else if (addr && addr != addr_last) 700 modified = 1; 701 break; 702 case 's': 703 do { 704 switch (*ibufp) { 705 case '\n': 706 sflags |=SGF; 707 break; 708 case 'g': 709 sflags |= SGG; 710 ibufp++; 711 break; 712 case 'p': 713 sflags |= SGP; 714 ibufp++; 715 break; 716 case 'r': 717 sflags |= SGR; 718 ibufp++; 719 break; 720 case '0': case '1': case '2': case '3': case '4': 721 case '5': case '6': case '7': case '8': case '9': 722 STRTOI(sgnum, ibufp); 723 sflags |= SGF; 724 sgflag &= ~GSG; /* override GSG */ 725 break; 726 default: 727 if (sflags) { 728 seterrmsg("invalid command suffix"); 729 return ERR; 730 } 731 } 732 } while (sflags && *ibufp != '\n'); 733 if (sflags && !pat) { 734 seterrmsg("no previous substitution"); 735 return ERR; 736 } else if (sflags & SGG) 737 sgnum = 0; /* override numeric arg */ 738 if (*ibufp != '\n' && *(ibufp + 1) == '\n') { 739 seterrmsg("invalid pattern delimiter"); 740 return ERR; 741 } 742 tpat = pat; 743 SPL1(); 744 if ((!sflags || (sflags & SGR)) && 745 (tpat = get_compiled_pattern()) == NULL) { 746 SPL0(); 747 return ERR; 748 } else if (tpat != pat) { 749 if (pat) { 750 regfree(pat); 751 free(pat); 752 } 753 pat = tpat; 754 patlock = 1; /* reserve pattern */ 755 } 756 SPL0(); 757 if (!sflags && extract_subst_tail(&sgflag, &sgnum) < 0) 758 return ERR; 759 else if (isglobal) 760 sgflag |= GLB; 761 else 762 sgflag &= ~GLB; 763 if (sflags & SGG) 764 sgflag ^= GSG; 765 if (sflags & SGP) { 766 sgflag ^= GPR; 767 sgflag &= ~(GLS | GNP); 768 } 769 do { 770 switch (*ibufp) { 771 case 'p': 772 sgflag |= GPR; 773 ibufp++; 774 break; 775 case 'l': 776 sgflag |= GLS; 777 ibufp++; 778 break; 779 case 'n': 780 sgflag |= GNP; 781 ibufp++; 782 break; 783 default: 784 n++; 785 } 786 } while (!n); 787 if (check_addr_range(current_addr, current_addr) < 0) 788 return ERR; 789 GET_COMMAND_SUFFIX(); 790 if (!isglobal) clear_undo_stack(); 791 if (search_and_replace(pat, sgflag, sgnum) < 0) 792 return ERR; 793 break; 794 case 't': 795 if (check_addr_range(current_addr, current_addr) < 0) 796 return ERR; 797 GET_THIRD_ADDR(addr); 798 GET_COMMAND_SUFFIX(); 799 if (!isglobal) clear_undo_stack(); 800 if (copy_lines(addr) < 0) 801 return ERR; 802 break; 803 case 'u': 804 if (addr_cnt > 0) { 805 seterrmsg("unexpected address"); 806 return ERR; 807 } 808 GET_COMMAND_SUFFIX(); 809 if (pop_undo_stack() < 0) 810 return ERR; 811 break; 812 case 'w': 813 case 'W': 814 if ((n = *ibufp) == 'q' || n == 'Q') { 815 gflag = EOF; 816 ibufp++; 817 } 818 if (!isspace((unsigned char)*ibufp)) { 819 seterrmsg("unexpected command suffix"); 820 return ERR; 821 } else if ((fnp = get_filename()) == NULL) 822 return ERR; 823 if (addr_cnt == 0 && !addr_last) 824 first_addr = second_addr = 0; 825 else if (check_addr_range(1, addr_last) < 0) 826 return ERR; 827 GET_COMMAND_SUFFIX(); 828 if (*old_filename == '\0' && *fnp != '!') 829 strlcpy(old_filename, fnp, sizeof old_filename); 830 #ifdef BACKWARDS 831 if (*fnp == '\0' && *old_filename == '\0') { 832 seterrmsg("no current filename"); 833 return ERR; 834 } 835 #endif 836 if ((addr = write_file(*fnp ? fnp : old_filename, 837 (c == 'W') ? "a" : "w", first_addr, second_addr)) < 0) 838 return ERR; 839 else if (addr == addr_last) 840 modified = 0; 841 else if (modified && !scripted && n == 'q') 842 gflag = EMOD; 843 break; 844 case 'x': 845 if (addr_cnt > 0) { 846 seterrmsg("unexpected address"); 847 return ERR; 848 } 849 GET_COMMAND_SUFFIX(); 850 seterrmsg("crypt unavailable"); 851 return ERR; 852 case 'z': 853 first_addr = 1; 854 #ifdef BACKWARDS 855 if (check_addr_range(first_addr, current_addr + 1) < 0) 856 #else 857 if (check_addr_range(first_addr, current_addr + !isglobal) < 0) 858 #endif 859 return ERR; 860 else if ('0' < *ibufp && *ibufp <= '9') 861 STRTOI(rows, ibufp); 862 GET_COMMAND_SUFFIX(); 863 if (display_lines(second_addr, min(addr_last, 864 second_addr + rows), gflag) < 0) 865 return ERR; 866 gflag = 0; 867 break; 868 case '=': 869 GET_COMMAND_SUFFIX(); 870 printf("%d\n", addr_cnt ? second_addr : addr_last); 871 break; 872 case '!': 873 if (addr_cnt > 0) { 874 seterrmsg("unexpected address"); 875 return ERR; 876 } else if ((sflags = get_shell_command()) < 0) 877 return ERR; 878 GET_COMMAND_SUFFIX(); 879 if (sflags) printf("%s\n", shcmd + 1); 880 system(shcmd + 1); 881 if (!scripted) printf("!\n"); 882 break; 883 case '\n': 884 first_addr = 1; 885 #ifdef BACKWARDS 886 if (check_addr_range(first_addr, current_addr + 1) < 0 887 #else 888 if (check_addr_range(first_addr, current_addr + !isglobal) < 0 889 #endif 890 || display_lines(second_addr, second_addr, 0) < 0) 891 return ERR; 892 break; 893 default: 894 seterrmsg("unknown command"); 895 return ERR; 896 } 897 return gflag; 898 } 899 900 901 /* check_addr_range: return status of address range check */ 902 int 903 check_addr_range(int n, int m) 904 { 905 if (addr_cnt == 0) { 906 first_addr = n; 907 second_addr = m; 908 } 909 if (first_addr > second_addr || 1 > first_addr || 910 second_addr > addr_last) { 911 seterrmsg("invalid address"); 912 return ERR; 913 } 914 return 0; 915 } 916 917 918 /* get_matching_node_addr: return the address of the next line matching a 919 pattern in a given direction. wrap around begin/end of editor buffer if 920 necessary */ 921 int 922 get_matching_node_addr(pattern_t *pat, int dir) 923 { 924 char *s; 925 int n = current_addr; 926 line_t *lp; 927 928 if (!pat) return ERR; 929 do { 930 if ((n = dir ? INC_MOD(n, addr_last) : DEC_MOD(n, addr_last))) { 931 lp = get_addressed_line_node(n); 932 if ((s = get_sbuf_line(lp)) == NULL) 933 return ERR; 934 if (isbinary) 935 NUL_TO_NEWLINE(s, lp->len); 936 if (!regexec(pat, s, 0, NULL, 0)) 937 return n; 938 } 939 } while (n != current_addr); 940 seterrmsg("no match"); 941 return ERR; 942 } 943 944 945 /* get_filename: return pointer to copy of filename in the command buffer */ 946 char * 947 get_filename(void) 948 { 949 static char *file = NULL; 950 static int filesz = 0; 951 int n; 952 953 if (*ibufp != '\n') { 954 SKIP_BLANKS(); 955 if (*ibufp == '\n') { 956 seterrmsg("invalid filename"); 957 return NULL; 958 } else if ((ibufp = get_extended_line(&n, 1)) == NULL) 959 return NULL; 960 else if (*ibufp == '!') { 961 ibufp++; 962 if ((n = get_shell_command()) < 0) 963 return NULL; 964 if (n) printf("%s\n", shcmd + 1); 965 return shcmd; 966 } else if (n >= MAXPATHLEN) { 967 seterrmsg("filename too long"); 968 return NULL; 969 } 970 } 971 #ifndef BACKWARDS 972 else if (*old_filename == '\0') { 973 seterrmsg("no current filename"); 974 return NULL; 975 } 976 #endif 977 REALLOC(file, filesz, MAXPATHLEN, NULL); 978 for (n = 0; *ibufp != '\n';) 979 file[n++] = *ibufp++; 980 file[n] = '\0'; 981 return file; 982 } 983 984 985 /* get_shell_command: read a shell command from stdin; return substitution 986 status */ 987 int 988 get_shell_command(void) 989 { 990 static char *buf = NULL; 991 static int n = 0; 992 993 char *s; /* substitution char pointer */ 994 int i = 0; 995 int j = 0; 996 997 if ((s = ibufp = get_extended_line(&j, 1)) == NULL) 998 return ERR; 999 REALLOC(buf, n, j + 1, ERR); 1000 buf[i++] = '!'; /* prefix command w/ bang */ 1001 while (*ibufp != '\n') 1002 switch (*ibufp) { 1003 default: 1004 REALLOC(buf, n, i + 2, ERR); 1005 buf[i++] = *ibufp; 1006 if (*ibufp++ == '\\') 1007 buf[i++] = *ibufp++; 1008 break; 1009 case '!': 1010 if (s != ibufp) { 1011 REALLOC(buf, n, i + 1, ERR); 1012 buf[i++] = *ibufp++; 1013 } 1014 #ifdef BACKWARDS 1015 else if (shcmd == NULL || *(shcmd + 1) == '\0') 1016 #else 1017 else if (shcmd == NULL) 1018 #endif 1019 { 1020 seterrmsg("no previous command"); 1021 return ERR; 1022 } else { 1023 REALLOC(buf, n, i + shcmdi, ERR); 1024 for (s = shcmd + 1; s < shcmd + shcmdi;) 1025 buf[i++] = *s++; 1026 s = ibufp++; 1027 } 1028 break; 1029 case '%': 1030 if (*old_filename == '\0') { 1031 seterrmsg("no current filename"); 1032 return ERR; 1033 } 1034 j = strlen(s = strip_escapes(old_filename)); 1035 REALLOC(buf, n, i + j, ERR); 1036 while (j--) 1037 buf[i++] = *s++; 1038 s = ibufp++; 1039 break; 1040 } 1041 REALLOC(shcmd, shcmdsz, i + 1, ERR); 1042 memcpy(shcmd, buf, i); 1043 shcmd[shcmdi = i] = '\0'; 1044 return *s == '!' || *s == '%'; 1045 } 1046 1047 1048 /* append_lines: insert text from stdin to after line n; stop when either a 1049 single period is read or EOF; return status */ 1050 int 1051 append_lines(int n) 1052 { 1053 int l; 1054 char *lp = ibuf; 1055 char *eot; 1056 undo_t *up = NULL; 1057 1058 for (current_addr = n;;) { 1059 if (!isglobal) { 1060 if ((l = get_tty_line()) < 0) 1061 return ERR; 1062 else if (l == 0 || ibuf[l - 1] != '\n') { 1063 clearerr(stdin); 1064 return l ? EOF : 0; 1065 } 1066 lp = ibuf; 1067 } else if (*(lp = ibufp) == '\0') 1068 return 0; 1069 else { 1070 while (*ibufp++ != '\n') 1071 ; 1072 l = ibufp - lp; 1073 } 1074 if (l == 2 && lp[0] == '.' && lp[1] == '\n') { 1075 return 0; 1076 } 1077 eot = lp + l; 1078 SPL1(); 1079 do { 1080 if ((lp = put_sbuf_line(lp)) == NULL) { 1081 SPL0(); 1082 return ERR; 1083 } else if (up) 1084 up->t = get_addressed_line_node(current_addr); 1085 else if ((up = push_undo_stack(UADD, current_addr, 1086 current_addr)) == NULL) { 1087 SPL0(); 1088 return ERR; 1089 } 1090 } while (lp != eot); 1091 modified = 1; 1092 SPL0(); 1093 } 1094 /* NOTREACHED */ 1095 } 1096 1097 1098 /* join_lines: replace a range of lines with the joined text of those lines */ 1099 int 1100 join_lines(int from, int to) 1101 { 1102 static char *buf = NULL; 1103 static int n; 1104 1105 char *s; 1106 int size = 0; 1107 line_t *bp, *ep; 1108 1109 ep = get_addressed_line_node(INC_MOD(to, addr_last)); 1110 bp = get_addressed_line_node(from); 1111 for (; bp != ep; bp = bp->q_forw) { 1112 if ((s = get_sbuf_line(bp)) == NULL) 1113 return ERR; 1114 REALLOC(buf, n, size + bp->len, ERR); 1115 memcpy(buf + size, s, bp->len); 1116 size += bp->len; 1117 } 1118 REALLOC(buf, n, size + 2, ERR); 1119 memcpy(buf + size, "\n", 2); 1120 if (delete_lines(from, to) < 0) 1121 return ERR; 1122 current_addr = from - 1; 1123 SPL1(); 1124 if (put_sbuf_line(buf) == NULL || 1125 push_undo_stack(UADD, current_addr, current_addr) == NULL) { 1126 SPL0(); 1127 return ERR; 1128 } 1129 modified = 1; 1130 SPL0(); 1131 return 0; 1132 } 1133 1134 1135 /* move_lines: move a range of lines */ 1136 int 1137 move_lines(int addr) 1138 { 1139 line_t *b1, *a1, *b2, *a2; 1140 int n = INC_MOD(second_addr, addr_last); 1141 int p = first_addr - 1; 1142 int done = (addr == first_addr - 1 || addr == second_addr); 1143 1144 SPL1(); 1145 if (done) { 1146 a2 = get_addressed_line_node(n); 1147 b2 = get_addressed_line_node(p); 1148 current_addr = second_addr; 1149 } else if (push_undo_stack(UMOV, p, n) == NULL || 1150 push_undo_stack(UMOV, addr, INC_MOD(addr, addr_last)) == NULL) { 1151 SPL0(); 1152 return ERR; 1153 } else { 1154 a1 = get_addressed_line_node(n); 1155 if (addr < first_addr) { 1156 b1 = get_addressed_line_node(p); 1157 b2 = get_addressed_line_node(addr); 1158 /* this get_addressed_line_node last! */ 1159 } else { 1160 b2 = get_addressed_line_node(addr); 1161 b1 = get_addressed_line_node(p); 1162 /* this get_addressed_line_node last! */ 1163 } 1164 a2 = b2->q_forw; 1165 REQUE(b2, b1->q_forw); 1166 REQUE(a1->q_back, a2); 1167 REQUE(b1, a1); 1168 current_addr = addr + ((addr < first_addr) ? 1169 second_addr - first_addr + 1 : 0); 1170 } 1171 if (isglobal) 1172 unset_active_nodes(b2->q_forw, a2); 1173 modified = 1; 1174 SPL0(); 1175 return 0; 1176 } 1177 1178 1179 /* copy_lines: copy a range of lines; return status */ 1180 int 1181 copy_lines(int addr) 1182 { 1183 line_t *lp, *np = get_addressed_line_node(first_addr); 1184 undo_t *up = NULL; 1185 int n = second_addr - first_addr + 1; 1186 int m = 0; 1187 1188 current_addr = addr; 1189 if (first_addr <= addr && addr < second_addr) { 1190 n = addr - first_addr + 1; 1191 m = second_addr - addr; 1192 } 1193 for (; n > 0; n=m, m=0, np = get_addressed_line_node(current_addr + 1)) 1194 for (; n-- > 0; np = np->q_forw) { 1195 SPL1(); 1196 if ((lp = dup_line_node(np)) == NULL) { 1197 SPL0(); 1198 return ERR; 1199 } 1200 add_line_node(lp); 1201 if (up) 1202 up->t = lp; 1203 else if ((up = push_undo_stack(UADD, current_addr, 1204 current_addr)) == NULL) { 1205 SPL0(); 1206 return ERR; 1207 } 1208 modified = 1; 1209 SPL0(); 1210 } 1211 return 0; 1212 } 1213 1214 1215 /* delete_lines: delete a range of lines */ 1216 int 1217 delete_lines(int from, int to) 1218 { 1219 line_t *n, *p; 1220 1221 SPL1(); 1222 if (push_undo_stack(UDEL, from, to) == NULL) { 1223 SPL0(); 1224 return ERR; 1225 } 1226 n = get_addressed_line_node(INC_MOD(to, addr_last)); 1227 p = get_addressed_line_node(from - 1); 1228 /* this get_addressed_line_node last! */ 1229 if (isglobal) 1230 unset_active_nodes(p->q_forw, n); 1231 REQUE(p, n); 1232 addr_last -= to - from + 1; 1233 current_addr = from - 1; 1234 modified = 1; 1235 SPL0(); 1236 return 0; 1237 } 1238 1239 1240 /* display_lines: print a range of lines to stdout */ 1241 int 1242 display_lines(int from, int to, int gflag) 1243 { 1244 line_t *bp; 1245 line_t *ep; 1246 char *s; 1247 1248 if (!from) { 1249 seterrmsg("invalid address"); 1250 return ERR; 1251 } 1252 ep = get_addressed_line_node(INC_MOD(to, addr_last)); 1253 bp = get_addressed_line_node(from); 1254 for (; bp != ep; bp = bp->q_forw) { 1255 if ((s = get_sbuf_line(bp)) == NULL) 1256 return ERR; 1257 if (put_tty_line(s, bp->len, current_addr = from++, gflag) < 0) 1258 return ERR; 1259 } 1260 return 0; 1261 } 1262 1263 1264 #define MAXMARK 26 /* max number of marks */ 1265 1266 line_t *mark[MAXMARK]; /* line markers */ 1267 int markno; /* line marker count */ 1268 1269 /* mark_line_node: set a line node mark */ 1270 int 1271 mark_line_node(line_t *lp, int n) 1272 { 1273 if (!islower(n)) { 1274 seterrmsg("invalid mark character"); 1275 return ERR; 1276 } else if (mark[n - 'a'] == NULL) 1277 markno++; 1278 mark[n - 'a'] = lp; 1279 return 0; 1280 } 1281 1282 1283 /* get_marked_node_addr: return address of a marked line */ 1284 int 1285 get_marked_node_addr(int n) 1286 { 1287 if (!islower(n)) { 1288 seterrmsg("invalid mark character"); 1289 return ERR; 1290 } 1291 return get_line_node_addr(mark[n - 'a']); 1292 } 1293 1294 1295 /* unmark_line_node: clear line node mark */ 1296 void 1297 unmark_line_node(line_t *lp) 1298 { 1299 int i; 1300 1301 for (i = 0; markno && i < MAXMARK; i++) 1302 if (mark[i] == lp) { 1303 mark[i] = NULL; 1304 markno--; 1305 } 1306 } 1307 1308 1309 /* dup_line_node: return a pointer to a copy of a line node */ 1310 line_t * 1311 dup_line_node(line_t *lp) 1312 { 1313 line_t *np; 1314 1315 if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) { 1316 perror(NULL); 1317 seterrmsg("out of memory"); 1318 return NULL; 1319 } 1320 np->seek = lp->seek; 1321 np->len = lp->len; 1322 return np; 1323 } 1324 1325 1326 /* has_trailing_escape: return the parity of escapes preceding a character 1327 in a string */ 1328 int 1329 has_trailing_escape(char *s, char *t) 1330 { 1331 return (s == t || *(t - 1) != '\\') ? 0 : !has_trailing_escape(s, t - 1); 1332 } 1333 1334 1335 /* strip_escapes: return copy of escaped string of at most length MAXPATHLEN */ 1336 char * 1337 strip_escapes(char *s) 1338 { 1339 static char *file = NULL; 1340 static int filesz = 0; 1341 1342 int i = 0; 1343 1344 REALLOC(file, filesz, MAXPATHLEN, NULL); 1345 /* assert: no trailing escape */ 1346 while ((file[i++] = (*s == '\\') ? *++s : *s) != '\0' && 1347 i < MAXPATHLEN-1) 1348 s++; 1349 file[MAXPATHLEN-1] = '\0'; 1350 return file; 1351 } 1352 1353 1354 void 1355 signal_hup(int signo) 1356 { 1357 int save_errno = errno; 1358 1359 if (mutex) 1360 sigflags |= (1 << (signo - 1)); 1361 else 1362 handle_hup(signo); 1363 errno = save_errno; 1364 } 1365 1366 1367 void 1368 signal_int(int signo) 1369 { 1370 int save_errno = errno; 1371 1372 if (mutex) 1373 sigflags |= (1 << (signo - 1)); 1374 else 1375 handle_int(signo); 1376 errno = save_errno; 1377 } 1378 1379 1380 void 1381 handle_hup(int signo) 1382 { 1383 char hup[MAXPATHLEN]; 1384 1385 if (!sigactive) 1386 quit(1); 1387 sigflags &= ~(1 << (signo - 1)); 1388 /* XXX signal race */ 1389 if (addr_last && write_file("ed.hup", "w", 1, addr_last) < 0 && 1390 home != NULL && home[0] == '/') { 1391 if (strlcpy(hup, home, sizeof(hup)) < sizeof(hup) && 1392 strlcat(hup, "/ed.hup", sizeof(hup)) < sizeof(hup)) 1393 write_file(hup, "w", 1, addr_last); 1394 } 1395 _exit(2); 1396 } 1397 1398 1399 void 1400 handle_int(int signo) 1401 { 1402 if (!sigactive) 1403 _exit(1); 1404 sigflags &= ~(1 << (signo - 1)); 1405 #ifdef _POSIX_SOURCE 1406 siglongjmp(env, -1); 1407 #else 1408 longjmp(env, -1); 1409 #endif 1410 } 1411 1412 1413 void 1414 handle_winch(int signo) 1415 { 1416 int save_errno = errno; 1417 struct winsize ws; /* window size structure */ 1418 1419 sigflags &= ~(1 << (signo - 1)); 1420 if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) >= 0) { 1421 if (ws.ws_row > 2) 1422 rows = ws.ws_row - 2; 1423 if (ws.ws_col > 8) 1424 cols = ws.ws_col - 8; 1425 } 1426 errno = save_errno; 1427 } 1428