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