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