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