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