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