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