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