main.c revision 77407
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 77407 2001-05-29 18:03:14Z imp $";
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[PATH_MAX] = "";	/* 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
106const char 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)
952				printf("%s\n", shcmd + 1);
953			return shcmd;
954		} else if (n > PATH_MAX - 1) {
955			sprintf(errmsg, "filename too long");
956			return  NULL;
957		}
958	}
959#ifndef BACKWARDS
960	else if (*old_filename == '\0') {
961		sprintf(errmsg, "no current filename");
962		return  NULL;
963	}
964#endif
965	REALLOC(file, filesz, PATH_MAX, NULL);
966	for (n = 0; *ibufp != '\n';)
967		file[n++] = *ibufp++;
968	file[n] = '\0';
969	return is_legal_filename(file) ? file : NULL;
970}
971
972
973/* get_shell_command: read a shell command from stdin; return substitution
974   status */
975int
976get_shell_command()
977{
978	static char *buf = NULL;
979	static int n = 0;
980
981	char *s;			/* substitution char pointer */
982	int i = 0;
983	int j = 0;
984
985	if (red) {
986		sprintf(errmsg, "shell access restricted");
987		return ERR;
988	} else if ((s = ibufp = get_extended_line(&j, 1)) == NULL)
989		return ERR;
990	REALLOC(buf, n, j + 1, ERR);
991	buf[i++] = '!';			/* prefix command w/ bang */
992	while (*ibufp != '\n')
993		switch (*ibufp) {
994		default:
995			REALLOC(buf, n, i + 2, ERR);
996			buf[i++] = *ibufp;
997			if (*ibufp++ == '\\')
998				buf[i++] = *ibufp++;
999			break;
1000		case '!':
1001			if (s != ibufp) {
1002				REALLOC(buf, n, i + 1, ERR);
1003				buf[i++] = *ibufp++;
1004			}
1005#ifdef BACKWARDS
1006			else if (shcmd == NULL || *(shcmd + 1) == '\0')
1007#else
1008			else if (shcmd == NULL)
1009#endif
1010			{
1011				sprintf(errmsg, "no previous command");
1012				return ERR;
1013			} else {
1014				REALLOC(buf, n, i + shcmdi, ERR);
1015				for (s = shcmd + 1; s < shcmd + shcmdi;)
1016					buf[i++] = *s++;
1017				s = ibufp++;
1018			}
1019			break;
1020		case '%':
1021			if (*old_filename  == '\0') {
1022				sprintf(errmsg, "no current filename");
1023				return ERR;
1024			}
1025			j = strlen(s = strip_escapes(old_filename));
1026			REALLOC(buf, n, i + j, ERR);
1027			while (j--)
1028				buf[i++] = *s++;
1029			s = ibufp++;
1030			break;
1031		}
1032	REALLOC(shcmd, shcmdsz, i + 1, ERR);
1033	memcpy(shcmd, buf, i);
1034	shcmd[shcmdi = i] = '\0';
1035	return *s == '!' || *s == '%';
1036}
1037
1038
1039/* append_lines: insert text from stdin to after line n; stop when either a
1040   single period is read or EOF; return status */
1041int
1042append_lines(n)
1043	long n;
1044{
1045	int l;
1046	char *lp = ibuf;
1047	char *eot;
1048	undo_t *up = NULL;
1049
1050	for (current_addr = n;;) {
1051		if (!isglobal) {
1052			if ((l = get_tty_line()) < 0)
1053				return ERR;
1054			else if (l == 0 || ibuf[l - 1] != '\n') {
1055				clearerr(stdin);
1056				return  l ? EOF : 0;
1057			}
1058			lp = ibuf;
1059		} else if (*(lp = ibufp) == '\0')
1060			return 0;
1061		else {
1062			while (*ibufp++ != '\n')
1063				;
1064			l = ibufp - lp;
1065		}
1066		if (l == 2 && lp[0] == '.' && lp[1] == '\n') {
1067			return 0;
1068		}
1069		eot = lp + l;
1070		SPL1();
1071		do {
1072			if ((lp = put_sbuf_line(lp)) == NULL) {
1073				SPL0();
1074				return ERR;
1075			} else if (up)
1076				up->t = get_addressed_line_node(current_addr);
1077			else if ((up = push_undo_stack(UADD, current_addr,
1078			    current_addr)) == NULL) {
1079				SPL0();
1080				return ERR;
1081			}
1082		} while (lp != eot);
1083		modified = 1;
1084		SPL0();
1085	}
1086	/* NOTREACHED */
1087}
1088
1089
1090/* join_lines: replace a range of lines with the joined text of those lines */
1091int
1092join_lines(from, to)
1093	long from;
1094	long to;
1095{
1096	static char *buf = NULL;
1097	static int n;
1098
1099	char *s;
1100	int size = 0;
1101	line_t *bp, *ep;
1102
1103	ep = get_addressed_line_node(INC_MOD(to, addr_last));
1104	bp = get_addressed_line_node(from);
1105	for (; bp != ep; bp = bp->q_forw) {
1106		if ((s = get_sbuf_line(bp)) == NULL)
1107			return ERR;
1108		REALLOC(buf, n, size + bp->len, ERR);
1109		memcpy(buf + size, s, bp->len);
1110		size += bp->len;
1111	}
1112	REALLOC(buf, n, size + 2, ERR);
1113	memcpy(buf + size, "\n", 2);
1114	if (delete_lines(from, to) < 0)
1115		return ERR;
1116	current_addr = from - 1;
1117	SPL1();
1118	if (put_sbuf_line(buf) == NULL ||
1119	    push_undo_stack(UADD, current_addr, current_addr) == NULL) {
1120		SPL0();
1121		return ERR;
1122	}
1123	modified = 1;
1124	SPL0();
1125	return 0;
1126}
1127
1128
1129/* move_lines: move a range of lines */
1130int
1131move_lines(addr)
1132	long addr;
1133{
1134	line_t *b1, *a1, *b2, *a2;
1135	long n = INC_MOD(second_addr, addr_last);
1136	long p = first_addr - 1;
1137	int done = (addr == first_addr - 1 || addr == second_addr);
1138
1139	SPL1();
1140	if (done) {
1141		a2 = get_addressed_line_node(n);
1142		b2 = get_addressed_line_node(p);
1143		current_addr = second_addr;
1144	} else if (push_undo_stack(UMOV, p, n) == NULL ||
1145	    push_undo_stack(UMOV, addr, INC_MOD(addr, addr_last)) == NULL) {
1146		SPL0();
1147		return ERR;
1148	} else {
1149		a1 = get_addressed_line_node(n);
1150		if (addr < first_addr) {
1151			b1 = get_addressed_line_node(p);
1152			b2 = get_addressed_line_node(addr);
1153					/* this get_addressed_line_node last! */
1154		} else {
1155			b2 = get_addressed_line_node(addr);
1156			b1 = get_addressed_line_node(p);
1157					/* this get_addressed_line_node last! */
1158		}
1159		a2 = b2->q_forw;
1160		REQUE(b2, b1->q_forw);
1161		REQUE(a1->q_back, a2);
1162		REQUE(b1, a1);
1163		current_addr = addr + ((addr < first_addr) ?
1164		    second_addr - first_addr + 1 : 0);
1165	}
1166	if (isglobal)
1167		unset_active_nodes(b2->q_forw, a2);
1168	modified = 1;
1169	SPL0();
1170	return 0;
1171}
1172
1173
1174/* copy_lines: copy a range of lines; return status */
1175int
1176copy_lines(addr)
1177	long addr;
1178{
1179	line_t *lp, *np = get_addressed_line_node(first_addr);
1180	undo_t *up = NULL;
1181	long n = second_addr - first_addr + 1;
1182	long m = 0;
1183
1184	current_addr = addr;
1185	if (first_addr <= addr && addr < second_addr) {
1186		n =  addr - first_addr + 1;
1187		m = second_addr - addr;
1188	}
1189	for (; n > 0; n=m, m=0, np = get_addressed_line_node(current_addr + 1))
1190		for (; n-- > 0; np = np->q_forw) {
1191			SPL1();
1192			if ((lp = dup_line_node(np)) == NULL) {
1193				SPL0();
1194				return ERR;
1195			}
1196			add_line_node(lp);
1197			if (up)
1198				up->t = lp;
1199			else if ((up = push_undo_stack(UADD, current_addr,
1200			    current_addr)) == NULL) {
1201				SPL0();
1202				return ERR;
1203			}
1204			modified = 1;
1205			SPL0();
1206		}
1207	return 0;
1208}
1209
1210
1211/* delete_lines: delete a range of lines */
1212int
1213delete_lines(from, to)
1214	long from, to;
1215{
1216	line_t *n, *p;
1217
1218	SPL1();
1219	if (push_undo_stack(UDEL, from, to) == NULL) {
1220		SPL0();
1221		return ERR;
1222	}
1223	n = get_addressed_line_node(INC_MOD(to, addr_last));
1224	p = get_addressed_line_node(from - 1);
1225					/* this get_addressed_line_node last! */
1226	if (isglobal)
1227		unset_active_nodes(p->q_forw, n);
1228	REQUE(p, n);
1229	addr_last -= to - from + 1;
1230	current_addr = from - 1;
1231	modified = 1;
1232	SPL0();
1233	return 0;
1234}
1235
1236
1237/* display_lines: print a range of lines to stdout */
1238int
1239display_lines(from, to, gflag)
1240	long from;
1241	long to;
1242	int gflag;
1243{
1244	line_t *bp;
1245	line_t *ep;
1246	char *s;
1247
1248	if (!from) {
1249		sprintf(errmsg, "invalid address");
1250		return ERR;
1251	}
1252	ep = get_addressed_line_node(INC_MOD(to, addr_last));
1253	bp = get_addressed_line_node(from);
1254	for (; bp != ep; bp = bp->q_forw) {
1255		if ((s = get_sbuf_line(bp)) == NULL)
1256			return ERR;
1257		if (put_tty_line(s, bp->len, current_addr = from++, gflag) < 0)
1258			return ERR;
1259	}
1260	return 0;
1261}
1262
1263
1264#define MAXMARK 26			/* max number of marks */
1265
1266line_t	*mark[MAXMARK];			/* line markers */
1267int markno;				/* line marker count */
1268
1269/* mark_line_node: set a line node mark */
1270int
1271mark_line_node(lp, n)
1272	line_t *lp;
1273	int n;
1274{
1275	if (!islower((unsigned char)n)) {
1276		sprintf(errmsg, "invalid mark character");
1277		return ERR;
1278	} else if (mark[n - 'a'] == NULL)
1279		markno++;
1280	mark[n - 'a'] = lp;
1281	return 0;
1282}
1283
1284
1285/* get_marked_node_addr: return address of a marked line */
1286long
1287get_marked_node_addr(n)
1288	int n;
1289{
1290	if (!islower((unsigned char)n)) {
1291		sprintf(errmsg, "invalid mark character");
1292		return ERR;
1293	}
1294	return get_line_node_addr(mark[n - 'a']);
1295}
1296
1297
1298/* unmark_line_node: clear line node mark */
1299void
1300unmark_line_node(lp)
1301	line_t *lp;
1302{
1303	int i;
1304
1305	for (i = 0; markno && i < MAXMARK; i++)
1306		if (mark[i] == lp) {
1307			mark[i] = NULL;
1308			markno--;
1309		}
1310}
1311
1312
1313/* dup_line_node: return a pointer to a copy of a line node */
1314line_t *
1315dup_line_node(lp)
1316	line_t *lp;
1317{
1318	line_t *np;
1319
1320	if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) {
1321		fprintf(stderr, "%s\n", strerror(errno));
1322		sprintf(errmsg, "out of memory");
1323		return NULL;
1324	}
1325	np->seek = lp->seek;
1326	np->len = lp->len;
1327	return np;
1328}
1329
1330
1331/* has_trailing_escape:  return the parity of escapes preceding a character
1332   in a string */
1333int
1334has_trailing_escape(s, t)
1335	char *s;
1336	char *t;
1337{
1338    return (s == t || *(t - 1) != '\\') ? 0 : !has_trailing_escape(s, t - 1);
1339}
1340
1341
1342/* strip_escapes: return copy of escaped string of at most length PATH_MAX */
1343char *
1344strip_escapes(s)
1345	char *s;
1346{
1347	static char *file = NULL;
1348	static int filesz = 0;
1349
1350	int i = 0;
1351
1352	REALLOC(file, filesz, PATH_MAX, NULL);
1353	while (i < filesz - 1	/* Worry about a possible trailing escape */
1354	       && (file[i++] = (*s == '\\') ? *++s : *s))
1355		s++;
1356	return file;
1357}
1358
1359
1360void
1361signal_hup(signo)
1362	int signo;
1363{
1364	if (mutex)
1365		sigflags |= (1 << (signo - 1));
1366	else
1367		handle_hup(signo);
1368}
1369
1370
1371void
1372signal_int(signo)
1373	int signo;
1374{
1375	if (mutex)
1376		sigflags |= (1 << (signo - 1));
1377	else
1378		handle_int(signo);
1379}
1380
1381
1382void
1383handle_hup(signo)
1384	int signo;
1385{
1386	char *hup = NULL;		/* hup filename */
1387	char *s;
1388	int n;
1389
1390	if (!sigactive)
1391		quit(1);
1392	sigflags &= ~(1 << (signo - 1));
1393	if (addr_last && write_file("ed.hup", "w", 1, addr_last) < 0 &&
1394	    (s = getenv("HOME")) != NULL &&
1395	    (n = strlen(s)) + 8 <= PATH_MAX &&	/* "ed.hup" + '/' */
1396	    (hup = (char *) malloc(n + 10)) != NULL) {
1397		strcpy(hup, s);
1398		if (hup[n - 1] != '/')
1399			hup[n] = '/', hup[n+1] = '\0';
1400		strcat(hup, "ed.hup");
1401		write_file(hup, "w", 1, addr_last);
1402	}
1403	quit(2);
1404}
1405
1406
1407void
1408handle_int(signo)
1409	int signo;
1410{
1411	if (!sigactive)
1412		quit(1);
1413	sigflags &= ~(1 << (signo - 1));
1414#ifdef _POSIX_SOURCE
1415	siglongjmp(env, -1);
1416#else
1417	longjmp(env, -1);
1418#endif
1419}
1420
1421
1422int cols = 72;				/* wrap column */
1423
1424void
1425handle_winch(signo)
1426	int signo;
1427{
1428	int save_errno = errno;
1429
1430	struct winsize ws;		/* window size structure */
1431
1432	sigflags &= ~(1 << (signo - 1));
1433	if (ioctl(0, TIOCGWINSZ, (char *) &ws) >= 0) {
1434		if (ws.ws_row > 2) rows = ws.ws_row - 2;
1435		if (ws.ws_col > 8) cols = ws.ws_col - 8;
1436	}
1437	errno = save_errno;
1438}
1439
1440
1441/* is_legal_filename: return a legal filename */
1442int
1443is_legal_filename(s)
1444	char *s;
1445{
1446	if (red && (*s == '!' || !strcmp(s, "..") || strchr(s, '/'))) {
1447		sprintf(errmsg, "shell access restricted");
1448		return 0;
1449	}
1450	return 1;
1451}
1452