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