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