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