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