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