main.c revision 256281
120253Sjoerg/* main.c: This file contains the main control and user-interface routines
220302Sjoerg   for the ed line editor. */
320302Sjoerg/*-
420253Sjoerg * Copyright (c) 1993 Andrew Moore, Talke Studio.
520253Sjoerg * All rights reserved.
620253Sjoerg *
720253Sjoerg * Redistribution and use in source and binary forms, with or without
820253Sjoerg * modification, are permitted provided that the following conditions
920302Sjoerg * are met:
1020253Sjoerg * 1. Redistributions of source code must retain the above copyright
1120253Sjoerg *    notice, this list of conditions and the following disclaimer.
1220253Sjoerg * 2. Redistributions in binary form must reproduce the above copyright
1320253Sjoerg *    notice, this list of conditions and the following disclaimer in the
1420302Sjoerg *    documentation and/or other materials provided with the distribution.
1520253Sjoerg *
1620253Sjoerg * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
1720302Sjoerg * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1820253Sjoerg * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1920253Sjoerg * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
2020253Sjoerg * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2120253Sjoerg * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2220253Sjoerg * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2320253Sjoerg * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
2420253Sjoerg * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
2544229Sdavidn * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2620253Sjoerg * SUCH DAMAGE.
2720253Sjoerg */
2830259Scharnier
2930259Scharnier#ifndef lint
3050479Speter#if 0
3130259Scharnierstatic const char copyright[] =
3230259Scharnier"@(#) Copyright (c) 1993 Andrew Moore, Talke Studio. \n\
33286201Sbapt All rights reserved.\n";
34286201Sbapt#endif
35286201Sbapt#endif /* not lint */
3630259Scharnier
37286201Sbapt#include <sys/cdefs.h>
3830259Scharnier__FBSDID("$FreeBSD: stable/10/bin/ed/main.c 241720 2012-10-19 05:43:38Z ed $");
39286982Sbapt
4020253Sjoerg/*
41286201Sbapt * CREDITS
42286201Sbapt *
43286201Sbapt *	This program is based on the editor algorithm described in
44286201Sbapt *	Brian W. Kernighan and P. J. Plauger's book "Software Tools
4530259Scharnier *	in Pascal," Addison-Wesley, 1981.
46286201Sbapt *
47286201Sbapt *	The buffering algorithm is attributed to Rodney Ruddock of
4820253Sjoerg *	the University of Guelph, Guelph, Ontario.
49286201Sbapt *
50286201Sbapt *	The cbc.c encryption code is adapted from
5120253Sjoerg *	the bdes program by Matt Bishop of Dartmouth College,
5220253Sjoerg *	Hanover, NH.
53286201Sbapt *
5420253Sjoerg */
5523318Sache
5622394Sdavidn#include <sys/types.h>
5752512Sdavidn
5824214Sache#include <sys/ioctl.h>
59286196Sbapt#include <sys/wait.h>
60286196Sbapt#include <ctype.h>
61286196Sbapt#include <locale.h>
62286196Sbapt#include <pwd.h>
63286196Sbapt#include <setjmp.h>
64286196Sbapt
65286196Sbapt#include "ed.h"
66286196Sbapt
67286196Sbapt
68286196Sbapt#ifdef _POSIX_SOURCE
69286196Sbaptstatic sigjmp_buf env;
70286196Sbapt#else
71286196Sbaptstatic jmp_buf env;
7220253Sjoerg#endif
73286196Sbapt
74286196Sbapt/* static buffers */
75286196Sbaptchar stdinbuf[1];		/* stdin buffer */
76286196Sbaptstatic char *shcmd;		/* shell command buffer */
77286196Sbaptstatic int shcmdsz;		/* shell command buffer size */
78286196Sbaptstatic int shcmdi;		/* shell command buffer index */
79286196Sbaptchar *ibuf;			/* ed command-line buffer */
80286196Sbaptint ibufsz;			/* ed command-line buffer size */
81286196Sbaptchar *ibufp;			/* pointer to ed command-line buffer */
82286196Sbapt
83286196Sbapt/* global flags */
84286196Sbaptint des = 0;			/* if set, use crypt(3) for i/o */
85286196Sbaptstatic int garrulous = 0;	/* if set, print all error messages */
86283961Sbaptint isbinary;			/* if set, buffer contains ASCII NULs */
87286982Sbaptint isglobal;			/* if set, doing a global command */
88286982Sbaptint modified;			/* if set, buffer modified since last write */
89286982Sbaptint mutex = 0;			/* if set, signals set "sigflags" */
90286982Sbaptstatic int red = 0;		/* if set, restrict shell/directory access */
91286982Sbaptint scripted = 0;		/* if set, suppress diagnostics */
92286982Sbaptint sigflags = 0;		/* if set, signals received while mutex set */
93286982Sbaptstatic int sigactive = 0;	/* if set, signal handlers are enabled */
94286982Sbapt
95286982Sbaptstatic char old_filename[PATH_MAX] = ""; /* default filename */
96286982Sbaptlong current_addr;		/* current address in editor buffer */
97286982Sbaptlong addr_last;			/* last address in editor buffer */
98286982Sbaptint lineno;			/* script line number */
99286982Sbaptstatic const char *prompt;	/* command-line prompt */
100286982Sbaptstatic const char *dps = "*";	/* default command-line prompt */
101286982Sbapt
102286982Sbaptstatic const char *usage = "usage: %s [-] [-sx] [-p string] [file]\n";
103286982Sbapt
104286982Sbapt/* ed: line editor */
105286982Sbaptint
106286982Sbaptmain(volatile int argc, char ** volatile argv)
107286982Sbapt{
108290153Sbdrewery	int c, n;
109290153Sbdrewery	long status = 0;
110286982Sbapt
111290153Sbdrewery	(void)setlocale(LC_ALL, "");
112286982Sbapt
113286982Sbapt	red = (n = strlen(argv[0])) > 2 && argv[0][n - 3] == 'r';
114286982Sbapttop:
115286982Sbapt	while ((c = getopt(argc, argv, "p:sx")) != -1)
116286982Sbapt		switch(c) {
117286982Sbapt		case 'p':				/* set prompt */
118286982Sbapt			prompt = optarg;
119286982Sbapt			break;
120286982Sbapt		case 's':				/* run script */
121286982Sbapt			scripted = 1;
122286982Sbapt			break;
123286982Sbapt		case 'x':				/* use crypt */
124286982Sbapt#ifdef DES
125286982Sbapt			des = get_keyword();
126286986Sbapt#else
127286982Sbapt			fprintf(stderr, "crypt unavailable\n?\n");
128286982Sbapt#endif
129286982Sbapt			break;
130286982Sbapt
131286982Sbapt		default:
132286982Sbapt			fprintf(stderr, usage, red ? "red" : "ed");
133286982Sbapt			exit(1);
134286982Sbapt		}
135286982Sbapt	argv += optind;
136286982Sbapt	argc -= optind;
137286982Sbapt	if (argc && **argv == '-') {
138286982Sbapt		scripted = 1;
139286982Sbapt		if (argc > 1) {
140286982Sbapt			optind = 1;
141286982Sbapt			goto top;
142286982Sbapt		}
143286982Sbapt		argv++;
144286982Sbapt		argc--;
145286982Sbapt	}
146286982Sbapt	/* assert: reliable signals! */
147286982Sbapt#ifdef SIGWINCH
148286982Sbapt	handle_winch(SIGWINCH);
149286982Sbapt	if (isatty(0)) signal(SIGWINCH, handle_winch);
150286982Sbapt#endif
151286196Sbapt	signal(SIGHUP, signal_hup);
152286196Sbapt	signal(SIGQUIT, SIG_IGN);
153283961Sbapt	signal(SIGINT, signal_int);
154285430Sbapt#ifdef _POSIX_SOURCE
155283961Sbapt	if ((status = sigsetjmp(env, 1)))
156286982Sbapt#else
157286982Sbapt	if ((status = setjmp(env)))
158286982Sbapt#endif
159285430Sbapt	{
160285434Sbapt		fputs("\n?\n", stderr);
161285434Sbapt		errmsg = "interrupt";
162285434Sbapt	} else {
163283961Sbapt		init_buffers();
164283961Sbapt		sigactive = 1;			/* enable signal handlers */
165286196Sbapt		if (argc && **argv && is_legal_filename(*argv)) {
166285430Sbapt			if (read_file(*argv, 0) < 0 && !isatty(0))
167286196Sbapt				quit(2);
168286196Sbapt			else if (**argv != '!')
169283961Sbapt				if (strlcpy(old_filename, *argv, sizeof(old_filename))
170283961Sbapt				    >= sizeof(old_filename))
171285133Sbapt					quit(2);
172286196Sbapt		} else if (argc) {
173285133Sbapt			fputs("?\n", stderr);
174285133Sbapt			if (**argv == '\0')
175285133Sbapt				errmsg = "invalid filename";
176285133Sbapt			if (!isatty(0))
177285133Sbapt				quit(2);
178285133Sbapt		}
179285133Sbapt	}
180286196Sbapt	for (;;) {
181285133Sbapt		if (status < 0 && garrulous)
182285133Sbapt			fprintf(stderr, "%s\n", errmsg);
183285133Sbapt		if (prompt) {
184285133Sbapt			printf("%s", prompt);
185285133Sbapt			fflush(stdout);
186285133Sbapt		}
187285133Sbapt		if ((n = get_tty_line()) < 0) {
188286196Sbapt			status = ERR;
189286196Sbapt			continue;
190285133Sbapt		} else if (n == 0) {
191285133Sbapt			if (modified && !scripted) {
192285137Sbapt				fputs("?\n", stderr);
193285133Sbapt				errmsg = "warning: file modified";
194286196Sbapt				if (!isatty(0)) {
195285133Sbapt					if (garrulous)
196285133Sbapt						fprintf(stderr,
197286196Sbapt						    "script, line %d: %s\n",
198285133Sbapt						    lineno, errmsg);
199285133Sbapt					quit(2);
200285133Sbapt				}
201285133Sbapt				clearerr(stdin);
202286196Sbapt				modified = 0;
203285133Sbapt				status = EMOD;
204286196Sbapt				continue;
205285133Sbapt			} else
206285133Sbapt				quit(0);
207285133Sbapt		} else if (ibuf[n - 1] != '\n') {
208285133Sbapt			/* discard line */
209285133Sbapt			errmsg = "unexpected end-of-file";
210285133Sbapt			clearerr(stdin);
211286196Sbapt			status = ERR;
212285133Sbapt			continue;
213285133Sbapt		}
214285133Sbapt		isglobal = 0;
215285133Sbapt		if ((status = extract_addr_range()) >= 0 &&
216285133Sbapt		    (status = exec_command()) >= 0)
217286196Sbapt			if (!status ||
218286196Sbapt			    (status = display_lines(current_addr, current_addr,
219285133Sbapt			        status)) >= 0)
220285137Sbapt				continue;
221286196Sbapt		switch (status) {
222285133Sbapt		case EOF:
223285133Sbapt			quit(0);
224285133Sbapt		case EMOD:
225285133Sbapt			modified = 0;
226285133Sbapt			fputs("?\n", stderr);		/* give warning */
227285133Sbapt			errmsg = "warning: file modified";
228285133Sbapt			if (!isatty(0)) {
229285133Sbapt				if (garrulous)
230285133Sbapt					fprintf(stderr, "script, line %d: %s\n",
231285133Sbapt					    lineno, errmsg);
232285133Sbapt				quit(2);
233285405Sbapt			}
234286196Sbapt			break;
235285405Sbapt		case FATAL:
236285405Sbapt			if (!isatty(0)) {
237286196Sbapt				if (garrulous)
238285405Sbapt					fprintf(stderr, "script, line %d: %s\n",
239286196Sbapt					    lineno, errmsg);
240286196Sbapt			} else if (garrulous)
241286196Sbapt				fprintf(stderr, "%s\n", errmsg);
242286196Sbapt			quit(3);
243285405Sbapt		default:
244285405Sbapt			fputs("?\n", stderr);
245285405Sbapt			if (!isatty(0)) {
246285405Sbapt				if (garrulous)
247285405Sbapt					fprintf(stderr, "script, line %d: %s\n",
248285405Sbapt					    lineno, errmsg);
249286196Sbapt				quit(2);
250286196Sbapt			}
251285405Sbapt			break;
252285405Sbapt		}
253285984Sbapt	}
254285405Sbapt	/*NOTREACHED*/
255285405Sbapt}
256285405Sbapt
257285405Sbaptlong first_addr, second_addr;
258285405Sbaptstatic long addr_cnt;
259285405Sbapt
260285405Sbapt/* extract_addr_range: get line addresses from the command buffer until an
261285405Sbapt   illegal address is seen; return status */
262285405Sbaptint
263285405Sbaptextract_addr_range(void)
264285405Sbapt{
265285405Sbapt	long addr;
266285405Sbapt
267285405Sbapt	addr_cnt = 0;
268285405Sbapt	first_addr = second_addr = current_addr;
269286196Sbapt	while ((addr = next_addr()) >= 0) {
270285405Sbapt		addr_cnt++;
271285405Sbapt		first_addr = second_addr;
272285405Sbapt		second_addr = addr;
273286196Sbapt		if (*ibufp != ',' && *ibufp != ';')
274285405Sbapt			break;
275291657Sbapt		else if (*ibufp++ == ';')
276285405Sbapt			current_addr = addr;
277286196Sbapt	}
278286196Sbapt	if ((addr_cnt = min(addr_cnt, 2)) == 1 || second_addr != addr)
279286196Sbapt		first_addr = second_addr;
280286196Sbapt	return (addr == ERR) ? ERR : 0;
281285405Sbapt}
282285405Sbapt
283291657Sbapt
284291657Sbapt#define SKIP_BLANKS() while (isspace((unsigned char)*ibufp) && *ibufp != '\n') ibufp++
285291657Sbapt
286286196Sbapt#define MUST_BE_FIRST() do {					\
287291657Sbapt	if (!first) {						\
288291657Sbapt		errmsg = "invalid address";			\
289291657Sbapt		return ERR;					\
290291657Sbapt	}							\
291291657Sbapt} while (0)
292291657Sbapt
293285405Sbapt/*  next_addr: return the next line address in the command buffer */
294291657Sbaptlong
295291657Sbaptnext_addr(void)
296285405Sbapt{
297285405Sbapt	const char *hd;
298285405Sbapt	long addr = current_addr;
299285405Sbapt	long n;
300285405Sbapt	int first = 1;
301285405Sbapt	int c;
302285405Sbapt
303285405Sbapt	SKIP_BLANKS();
304285405Sbapt	for (hd = ibufp;; first = 0)
305285405Sbapt		switch (c = *ibufp) {
306285405Sbapt		case '+':
307285405Sbapt		case '\t':
308285405Sbapt		case ' ':
309285405Sbapt		case '-':
310285405Sbapt		case '^':
311285405Sbapt			ibufp++;
312285405Sbapt			SKIP_BLANKS();
313285405Sbapt			if (isdigit((unsigned char)*ibufp)) {
314285405Sbapt				STRTOL(n, ibufp);
315285405Sbapt				addr += (c == '-' || c == '^') ? -n : n;
316285405Sbapt			} else if (!isspace((unsigned char)c))
317285405Sbapt				addr += (c == '-' || c == '^') ? -1 : 1;
318286196Sbapt			break;
319285405Sbapt		case '0': case '1': case '2':
320285405Sbapt		case '3': case '4': case '5':
321285405Sbapt		case '6': case '7': case '8': case '9':
322285405Sbapt			MUST_BE_FIRST();
323285405Sbapt			STRTOL(addr, ibufp);
324286196Sbapt			break;
325286196Sbapt		case '.':
32620253Sjoerg		case '$':
327286196Sbapt			MUST_BE_FIRST();
328286196Sbapt			ibufp++;
329286196Sbapt			addr = (c == '.') ? current_addr : addr_last;
33020253Sjoerg			break;
33120267Sjoerg		case '/':
332286196Sbapt		case '?':
33320253Sjoerg			MUST_BE_FIRST();
334286196Sbapt			if ((addr = get_matching_node_addr(
335286196Sbapt			    get_compiled_pattern(), c == '/')) < 0)
33620253Sjoerg				return ERR;
337286196Sbapt			else if (c == *ibufp)
338286196Sbapt				ibufp++;
339286196Sbapt			break;
340286196Sbapt		case '\'':
34120253Sjoerg			MUST_BE_FIRST();
34221052Sdavidn			ibufp++;
343286196Sbapt			if ((addr = get_marked_node_addr(*ibufp++)) < 0)
344286196Sbapt				return ERR;
345286196Sbapt			break;
34621052Sdavidn		case '%':
347286196Sbapt		case ',':
348286196Sbapt		case ';':
349286196Sbapt			if (first) {
350286196Sbapt				ibufp++;
35121052Sdavidn				addr_cnt++;
352286196Sbapt				second_addr = (c == ';') ? current_addr : 1;
35321052Sdavidn				addr = addr_last;
35420253Sjoerg				break;
355286196Sbapt			}
35620253Sjoerg			/* FALLTHROUGH */
357286196Sbapt		default:
358286196Sbapt			if (ibufp == hd)
359286196Sbapt				return EOF;
360286196Sbapt			else if (addr < 0 || addr_last < addr) {
361286196Sbapt				errmsg = "invalid address";
36252527Sdavidn				return ERR;
36320253Sjoerg			} else
364286196Sbapt				return addr;
36520253Sjoerg		}
366286196Sbapt	/* NOTREACHED */
367286196Sbapt}
36820253Sjoerg
36920267Sjoerg
370286196Sbapt#ifdef BACKWARDS
37120267Sjoerg/* GET_THIRD_ADDR: get a legal address from the command buffer */
372286196Sbapt#define GET_THIRD_ADDR(addr) \
373286196Sbapt{ \
374286196Sbapt	long ol1, ol2; \
375286196Sbapt\
37620253Sjoerg	ol1 = first_addr, ol2 = second_addr; \
37720253Sjoerg	if (extract_addr_range() < 0) \
378286196Sbapt		return ERR; \
379286196Sbapt	else if (addr_cnt == 0) { \
38020253Sjoerg		errmsg = "destination expected"; \
38120253Sjoerg		return ERR; \
38220253Sjoerg	} else if (second_addr < 0 || addr_last < second_addr) { \
38320253Sjoerg		errmsg = "invalid address"; \
38420253Sjoerg		return ERR; \
38520253Sjoerg	} \
38620253Sjoerg	addr = second_addr; \
38744229Sdavidn	first_addr = ol1, second_addr = ol2; \
388286196Sbapt}
389286196Sbapt#else	/* BACKWARDS */
390286196Sbapt/* GET_THIRD_ADDR: get a legal address from the command buffer */
391286196Sbapt#define GET_THIRD_ADDR(addr) \
39220253Sjoerg{ \
39320253Sjoerg	long ol1, ol2; \
394262865Sjulian\
395262865Sjulian	ol1 = first_addr, ol2 = second_addr; \
39620267Sjoerg	if (extract_addr_range() < 0) \
39720253Sjoerg		return ERR; \
398286196Sbapt	if (second_addr < 0 || addr_last < second_addr) { \
39920253Sjoerg		errmsg = "invalid address"; \
40020253Sjoerg		return ERR; \
40120253Sjoerg	} \
40220253Sjoerg	addr = second_addr; \
40320253Sjoerg	first_addr = ol1, second_addr = ol2; \
40420253Sjoerg}
40520253Sjoerg#endif
40620253Sjoerg
40720253Sjoerg
40820253Sjoerg/* GET_COMMAND_SUFFIX: verify the command suffix in the command buffer */
409285414Sbapt#define GET_COMMAND_SUFFIX() { \
410285414Sbapt	int done = 0; \
411286196Sbapt	do { \
412285395Sbapt		switch(*ibufp) { \
413285395Sbapt		case 'p': \
414286196Sbapt			gflag |= GPR, ibufp++; \
415286196Sbapt			break; \
416286196Sbapt		case 'l': \
41744229Sdavidn			gflag |= GLS, ibufp++; \
41820267Sjoerg			break; \
41920267Sjoerg		case 'n': \
42020253Sjoerg			gflag |= GNP, ibufp++; \
42144229Sdavidn			break; \
422286196Sbapt		default: \
42320253Sjoerg			done++; \
42420253Sjoerg		} \
425286196Sbapt	} while (!done); \
426286196Sbapt	if (*ibufp++ != '\n') { \
42720253Sjoerg		errmsg = "invalid command suffix"; \
428282699Sbapt		return ERR; \
42920253Sjoerg	} \
430286196Sbapt}
431286196Sbapt
43220253Sjoerg
433282699Sbapt/* sflags */
434282699Sbapt#define SGG 001		/* complement previous global substitute suffix */
435282699Sbapt#define SGP 002		/* complement previous print suffix */
436282699Sbapt#define SGR 004		/* use last regex instead of last pat */
437282699Sbapt#define SGF 010		/* repeat last substitution */
43820253Sjoerg
43920253Sjoergint patlock = 0;	/* if set, pattern not freed by get_compiled_pattern() */
440286196Sbapt
44120253Sjoerglong rows = 22;		/* scroll length: ws_row - 2 */
44220253Sjoerg
44320253Sjoerg/* exec_command: execute the next command in command buffer; return print
44420253Sjoerg   request, if any */
44520253Sjoergint
44620253Sjoergexec_command(void)
44720253Sjoerg{
44820253Sjoerg	static pattern_t *pat = NULL;
44920253Sjoerg	static int sgflag = 0;
45020253Sjoerg	static long sgnum = 0;
45120253Sjoerg
452130633Srobert	pattern_t *tpat;
45320253Sjoerg	char *fnp;
45420253Sjoerg	int gflag = 0;
45520253Sjoerg	int sflags = 0;
45620253Sjoerg	long addr = 0;
45720253Sjoerg	int n = 0;
458282700Sbapt	int c;
45920253Sjoerg
46020253Sjoerg	SKIP_BLANKS();
46120253Sjoerg	switch(c = *ibufp++) {
46220253Sjoerg	case 'a':
463282700Sbapt		GET_COMMAND_SUFFIX();
46420253Sjoerg		if (!isglobal) clear_undo_stack();
46520253Sjoerg		if (append_lines(second_addr) < 0)
46620253Sjoerg			return ERR;
46720253Sjoerg		break;
46820253Sjoerg	case 'c':
46930259Scharnier		if (check_addr_range(current_addr, current_addr) < 0)
47030259Scharnier			return ERR;
47120253Sjoerg		GET_COMMAND_SUFFIX();
47220253Sjoerg		if (!isglobal) clear_undo_stack();
47320253Sjoerg		if (delete_lines(first_addr, second_addr) < 0 ||
47420253Sjoerg		    append_lines(current_addr) < 0)
475286196Sbapt			return ERR;
476286196Sbapt		break;
47720253Sjoerg	case 'd':
47820253Sjoerg		if (check_addr_range(current_addr, current_addr) < 0)
479286196Sbapt			return ERR;
48020253Sjoerg		GET_COMMAND_SUFFIX();
48120253Sjoerg		if (!isglobal) clear_undo_stack();
482179365Santoine		if (delete_lines(first_addr, second_addr) < 0)
48320253Sjoerg			return ERR;
484179365Santoine		else if ((addr = INC_MOD(current_addr, addr_last)) != 0)
485179365Santoine			current_addr = addr;
486286196Sbapt		break;
48720253Sjoerg	case 'e':
48820253Sjoerg		if (modified && !scripted)
48920253Sjoerg			return EMOD;
490179365Santoine		/* FALLTHROUGH */
491231994Skevlo	case 'E':
49220253Sjoerg		if (addr_cnt > 0) {
49320253Sjoerg			errmsg = "unexpected address";
49420253Sjoerg			return ERR;
49520253Sjoerg		} else if (!isspace((unsigned char)*ibufp)) {
49620253Sjoerg			errmsg = "unexpected command suffix";
497179365Santoine			return ERR;
498181785Sache		} else if ((fnp = get_filename()) == NULL)
499179365Santoine			return ERR;
50020253Sjoerg		GET_COMMAND_SUFFIX();
501231994Skevlo		if (delete_lines(1, addr_last) < 0)
502231994Skevlo			return ERR;
503231994Skevlo		clear_undo_stack();
504231994Skevlo		if (close_sbuf() < 0)
50520253Sjoerg			return ERR;
50620253Sjoerg		else if (open_sbuf() < 0)
507286196Sbapt			return FATAL;
508286196Sbapt		if (*fnp && *fnp != '!') strcpy(old_filename, fnp);
50920253Sjoerg#ifdef BACKWARDS
51020253Sjoerg		if (*fnp == '\0' && *old_filename == '\0') {
51120253Sjoerg			errmsg = "no current filename";
51220253Sjoerg			return ERR;
51320253Sjoerg		}
51420253Sjoerg#endif
51573563Skris		if (read_file(*fnp ? fnp : old_filename, 0) < 0)
51620253Sjoerg			return ERR;
517181785Sache		clear_undo_stack();
51820253Sjoerg		modified = 0;
51920253Sjoerg		u_current_addr = u_addr_last = -1;
52020253Sjoerg		break;
52120253Sjoerg	case 'f':
52220253Sjoerg		if (addr_cnt > 0) {
523286196Sbapt			errmsg = "unexpected address";
52461957Sache			return ERR;
52520712Sdavidn		} else if (!isspace((unsigned char)*ibufp)) {
52620253Sjoerg			errmsg = "unexpected command suffix";
52720253Sjoerg			return ERR;
52820253Sjoerg		} else if ((fnp = get_filename()) == NULL)
52920253Sjoerg			return ERR;
53020253Sjoerg		else if (*fnp == '!') {
53120253Sjoerg			errmsg = "invalid redirection";
53220253Sjoerg			return ERR;
53320253Sjoerg		}
53420253Sjoerg		GET_COMMAND_SUFFIX();
53520253Sjoerg		if (*fnp) strcpy(old_filename, fnp);
53620253Sjoerg		printf("%s\n", strip_escapes(old_filename));
53720253Sjoerg		break;
53820253Sjoerg	case 'g':
539130633Srobert	case 'v':
54020253Sjoerg	case 'G':
54120253Sjoerg	case 'V':
54220253Sjoerg		if (isglobal) {
54320253Sjoerg			errmsg = "cannot nest global commands";
54420253Sjoerg			return ERR;
545284111Sbapt		} else if (check_addr_range(1, addr_last) < 0)
546286196Sbapt			return ERR;
547284111Sbapt		else if (build_active_list(c == 'g' || c == 'G') < 0)
548286196Sbapt			return ERR;
549286196Sbapt		else if ((n = (c == 'G' || c == 'V')))
550286196Sbapt			GET_COMMAND_SUFFIX();
551286196Sbapt		isglobal++;
552286196Sbapt		if (exec_global(n, gflag) < 0)
553286196Sbapt			return ERR;
554286196Sbapt		break;
555286196Sbapt	case 'h':
556286196Sbapt		if (addr_cnt > 0) {
557286196Sbapt			errmsg = "unexpected address";
558286196Sbapt			return ERR;
559286196Sbapt		}
560286196Sbapt		GET_COMMAND_SUFFIX();
561286196Sbapt		if (*errmsg) fprintf(stderr, "%s\n", errmsg);
562286196Sbapt		break;
563286196Sbapt	case 'H':
564286196Sbapt		if (addr_cnt > 0) {
565286196Sbapt			errmsg = "unexpected address";
566286196Sbapt			return ERR;
567286196Sbapt		}
568286196Sbapt		GET_COMMAND_SUFFIX();
569286196Sbapt		if ((garrulous = 1 - garrulous) && *errmsg)
570286196Sbapt			fprintf(stderr, "%s\n", errmsg);
571286196Sbapt		break;
572286196Sbapt	case 'i':
573286196Sbapt		if (second_addr == 0) {
574286196Sbapt			errmsg = "invalid address";
575286196Sbapt			return ERR;
576286196Sbapt		}
577286196Sbapt		GET_COMMAND_SUFFIX();
578286196Sbapt		if (!isglobal) clear_undo_stack();
579286196Sbapt		if (append_lines(second_addr - 1) < 0)
580286196Sbapt			return ERR;
581286196Sbapt		break;
582286196Sbapt	case 'j':
583286196Sbapt		if (check_addr_range(current_addr, current_addr + 1) < 0)
584286196Sbapt			return ERR;
585286196Sbapt		GET_COMMAND_SUFFIX();
586286196Sbapt		if (!isglobal) clear_undo_stack();
587286196Sbapt		if (first_addr != second_addr &&
588286196Sbapt		    join_lines(first_addr, second_addr) < 0)
589286196Sbapt			return ERR;
590286196Sbapt		break;
591286196Sbapt	case 'k':
592286196Sbapt		c = *ibufp++;
593286196Sbapt		if (second_addr == 0) {
594286196Sbapt			errmsg = "invalid address";
595286196Sbapt			return ERR;
596286196Sbapt		}
597286196Sbapt		GET_COMMAND_SUFFIX();
598286196Sbapt		if (mark_line_node(get_addressed_line_node(second_addr), c) < 0)
599286196Sbapt			return ERR;
600286196Sbapt		break;
601286196Sbapt	case 'l':
602286196Sbapt		if (check_addr_range(current_addr, current_addr) < 0)
603286196Sbapt			return ERR;
604286196Sbapt		GET_COMMAND_SUFFIX();
605286196Sbapt		if (display_lines(first_addr, second_addr, gflag | GLS) < 0)
606286196Sbapt			return ERR;
607286196Sbapt		gflag = 0;
608286196Sbapt		break;
609286196Sbapt	case 'm':
610286196Sbapt		if (check_addr_range(current_addr, current_addr) < 0)
611286196Sbapt			return ERR;
612286196Sbapt		GET_THIRD_ADDR(addr);
613286196Sbapt		if (first_addr <= addr && addr < second_addr) {
614286196Sbapt			errmsg = "invalid destination";
615286196Sbapt			return ERR;
616286196Sbapt		}
617286196Sbapt		GET_COMMAND_SUFFIX();
618286196Sbapt		if (!isglobal) clear_undo_stack();
619286196Sbapt		if (move_lines(addr) < 0)
620286196Sbapt			return ERR;
621286196Sbapt		break;
622286196Sbapt	case 'n':
623286196Sbapt		if (check_addr_range(current_addr, current_addr) < 0)
624286196Sbapt			return ERR;
625286196Sbapt		GET_COMMAND_SUFFIX();
626286196Sbapt		if (display_lines(first_addr, second_addr, gflag | GNP) < 0)
627286196Sbapt			return ERR;
628286196Sbapt		gflag = 0;
629286196Sbapt		break;
630286196Sbapt	case 'p':
631286196Sbapt		if (check_addr_range(current_addr, current_addr) < 0)
632286196Sbapt			return ERR;
633286196Sbapt		GET_COMMAND_SUFFIX();
634286196Sbapt		if (display_lines(first_addr, second_addr, gflag | GPR) < 0)
635286196Sbapt			return ERR;
636286196Sbapt		gflag = 0;
637286196Sbapt		break;
638286196Sbapt	case 'P':
639286196Sbapt		if (addr_cnt > 0) {
640286196Sbapt			errmsg = "unexpected address";
641286196Sbapt			return ERR;
642286196Sbapt		}
643291658Sbapt		GET_COMMAND_SUFFIX();
644291658Sbapt		prompt = prompt ? NULL : optarg ? optarg : dps;
645286196Sbapt		break;
646286196Sbapt	case 'q':
647286196Sbapt	case 'Q':
648286196Sbapt		if (addr_cnt > 0) {
649286196Sbapt			errmsg = "unexpected address";
650286196Sbapt			return ERR;
651286196Sbapt		}
652286196Sbapt		GET_COMMAND_SUFFIX();
653286196Sbapt		gflag =  (modified && !scripted && c == 'q') ? EMOD : EOF;
654286196Sbapt		break;
655286196Sbapt	case 'r':
656286196Sbapt		if (!isspace((unsigned char)*ibufp)) {
657286196Sbapt			errmsg = "unexpected command suffix";
658286196Sbapt			return ERR;
659286196Sbapt		} else if (addr_cnt == 0)
660286196Sbapt			second_addr = addr_last;
661286196Sbapt		if ((fnp = get_filename()) == NULL)
662286196Sbapt			return ERR;
663286196Sbapt		GET_COMMAND_SUFFIX();
664286196Sbapt		if (!isglobal) clear_undo_stack();
665286196Sbapt		if (*old_filename == '\0' && *fnp != '!')
666286196Sbapt			strcpy(old_filename, fnp);
667286196Sbapt#ifdef BACKWARDS
668286196Sbapt		if (*fnp == '\0' && *old_filename == '\0') {
669286196Sbapt			errmsg = "no current filename";
670286196Sbapt			return ERR;
671286196Sbapt		}
672286196Sbapt#endif
673286196Sbapt		if ((addr = read_file(*fnp ? fnp : old_filename, second_addr)) < 0)
674286196Sbapt			return ERR;
675286196Sbapt		else if (addr && addr != addr_last)
676286196Sbapt			modified = 1;
677286196Sbapt		break;
678286196Sbapt	case 's':
679286196Sbapt		do {
680286196Sbapt			switch(*ibufp) {
681286196Sbapt			case '\n':
682286196Sbapt				sflags |=SGF;
683286196Sbapt				break;
684286196Sbapt			case 'g':
685286196Sbapt				sflags |= SGG;
686286196Sbapt				ibufp++;
687286196Sbapt				break;
688286196Sbapt			case 'p':
689286196Sbapt				sflags |= SGP;
690286196Sbapt				ibufp++;
691286196Sbapt				break;
692286196Sbapt			case 'r':
693286196Sbapt				sflags |= SGR;
694286196Sbapt				ibufp++;
695286196Sbapt				break;
696286196Sbapt			case '0': case '1': case '2': case '3': case '4':
697286202Sbapt			case '5': case '6': case '7': case '8': case '9':
698286202Sbapt				STRTOL(sgnum, ibufp);
699286196Sbapt				sflags |= SGF;
700286196Sbapt				sgflag &= ~GSG;		/* override GSG */
701286196Sbapt				break;
702286196Sbapt			default:
703286196Sbapt				if (sflags) {
704286196Sbapt					errmsg = "invalid command suffix";
705286196Sbapt					return ERR;
706286196Sbapt				}
707286196Sbapt			}
708286196Sbapt		} while (sflags && *ibufp != '\n');
709286196Sbapt		if (sflags && !pat) {
710286196Sbapt			errmsg = "no previous substitution";
711286196Sbapt			return ERR;
712286196Sbapt		} else if (sflags & SGG)
713286196Sbapt			sgnum = 0;		/* override numeric arg */
714286196Sbapt		if (*ibufp != '\n' && *(ibufp + 1) == '\n') {
715286196Sbapt			errmsg = "invalid pattern delimiter";
716286196Sbapt			return ERR;
717286196Sbapt		}
718286196Sbapt		tpat = pat;
719286196Sbapt		SPL1();
720286196Sbapt		if ((!sflags || (sflags & SGR)) &&
721286196Sbapt		    (tpat = get_compiled_pattern()) == NULL) {
722286196Sbapt		 	SPL0();
723286196Sbapt			return ERR;
724286196Sbapt		} else if (tpat != pat) {
725286196Sbapt			if (pat) {
726286196Sbapt				regfree(pat);
727286196Sbapt				free(pat);
728286196Sbapt			}
729286196Sbapt			pat = tpat;
730286196Sbapt			patlock = 1;		/* reserve pattern */
731286196Sbapt		}
732286196Sbapt		SPL0();
733286196Sbapt		if (!sflags && extract_subst_tail(&sgflag, &sgnum) < 0)
734286196Sbapt			return ERR;
735286196Sbapt		else if (isglobal)
736286196Sbapt			sgflag |= GLB;
737286196Sbapt		else
738286196Sbapt			sgflag &= ~GLB;
739286196Sbapt		if (sflags & SGG)
740286196Sbapt			sgflag ^= GSG;
741286196Sbapt		if (sflags & SGP)
742286196Sbapt			sgflag ^= GPR, sgflag &= ~(GLS | GNP);
743286196Sbapt		do {
744286196Sbapt			switch(*ibufp) {
745301367Sbapt			case 'p':
746286196Sbapt				sgflag |= GPR, ibufp++;
747286196Sbapt				break;
748286196Sbapt			case 'l':
749286196Sbapt				sgflag |= GLS, ibufp++;
750286196Sbapt				break;
751286217Sadrian			case 'n':
752286196Sbapt				sgflag |= GNP, ibufp++;
753286196Sbapt				break;
754286196Sbapt			default:
755286196Sbapt				n++;
756286196Sbapt			}
757286196Sbapt		} while (!n);
758286196Sbapt		if (check_addr_range(current_addr, current_addr) < 0)
759286196Sbapt			return ERR;
760286196Sbapt		GET_COMMAND_SUFFIX();
761286196Sbapt		if (!isglobal) clear_undo_stack();
762286196Sbapt		if (search_and_replace(pat, sgflag, sgnum) < 0)
763286196Sbapt			return ERR;
764286196Sbapt		break;
765286196Sbapt	case 't':
766286196Sbapt		if (check_addr_range(current_addr, current_addr) < 0)
767286196Sbapt			return ERR;
768286196Sbapt		GET_THIRD_ADDR(addr);
769286196Sbapt		GET_COMMAND_SUFFIX();
770286196Sbapt		if (!isglobal) clear_undo_stack();
771286196Sbapt		if (copy_lines(addr) < 0)
772285401Sbapt			return ERR;
773286196Sbapt		break;
774286218Sbapt	case 'u':
775286196Sbapt		if (addr_cnt > 0) {
776286196Sbapt			errmsg = "unexpected address";
777286196Sbapt			return ERR;
778286196Sbapt		}
779286196Sbapt		GET_COMMAND_SUFFIX();
780286196Sbapt		if (pop_undo_stack() < 0)
78120253Sjoerg			return ERR;
782286196Sbapt		break;
783286259Sed	case 'w':
784286196Sbapt	case 'W':
785286196Sbapt		if ((n = *ibufp) == 'q' || n == 'Q') {
786286196Sbapt			gflag = EOF;
787286196Sbapt			ibufp++;
788286196Sbapt		}
789286196Sbapt		if (!isspace((unsigned char)*ibufp)) {
790286196Sbapt			errmsg = "unexpected command suffix";
791286196Sbapt			return ERR;
792286196Sbapt		} else if ((fnp = get_filename()) == NULL)
793286196Sbapt			return ERR;
794286196Sbapt		if (addr_cnt == 0 && !addr_last)
795286196Sbapt			first_addr = second_addr = 0;
796286196Sbapt		else if (check_addr_range(1, addr_last) < 0)
797286196Sbapt			return ERR;
798286196Sbapt		GET_COMMAND_SUFFIX();
799286196Sbapt		if (*old_filename == '\0' && *fnp != '!')
800286196Sbapt			strcpy(old_filename, fnp);
801286196Sbapt#ifdef BACKWARDS
802286196Sbapt		if (*fnp == '\0' && *old_filename == '\0') {
803286196Sbapt			errmsg = "no current filename";
804286196Sbapt			return ERR;
805286196Sbapt		}
806286196Sbapt#endif
807286196Sbapt		if ((addr = write_file(*fnp ? fnp : old_filename,
808286196Sbapt		    (c == 'W') ? "a" : "w", first_addr, second_addr)) < 0)
809286196Sbapt			return ERR;
810286196Sbapt		else if (addr == addr_last)
811286196Sbapt			modified = 0;
812287799Sbapt		else if (modified && !scripted && n == 'q')
813286196Sbapt			gflag = EMOD;
814286196Sbapt		break;
815286196Sbapt	case 'x':
816286196Sbapt		if (addr_cnt > 0) {
817286196Sbapt			errmsg = "unexpected address";
818286196Sbapt			return ERR;
819286196Sbapt		}
820286196Sbapt		GET_COMMAND_SUFFIX();
821286196Sbapt#ifdef DES
822286196Sbapt		des = get_keyword();
823286196Sbapt		break;
824286196Sbapt#else
825286196Sbapt		errmsg = "crypt unavailable";
826286196Sbapt		return ERR;
827286196Sbapt#endif
828286196Sbapt	case 'z':
829285401Sbapt#ifdef BACKWARDS
830285401Sbapt		if (check_addr_range(first_addr = 1, current_addr + 1) < 0)
831285401Sbapt#else
832285401Sbapt		if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0)
833285401Sbapt#endif
834286196Sbapt			return ERR;
835286196Sbapt		else if ('0' < *ibufp && *ibufp <= '9')
836286196Sbapt			STRTOL(rows, ibufp);
837286196Sbapt		GET_COMMAND_SUFFIX();
838286196Sbapt		if (display_lines(second_addr, min(addr_last,
839286196Sbapt		    second_addr + rows), gflag) < 0)
840286196Sbapt			return ERR;
841286196Sbapt		gflag = 0;
842286196Sbapt		break;
843286196Sbapt	case '=':
844286196Sbapt		GET_COMMAND_SUFFIX();
845286196Sbapt		printf("%ld\n", addr_cnt ? second_addr : addr_last);
846286196Sbapt		break;
847286196Sbapt	case '!':
848286196Sbapt		if (addr_cnt > 0) {
849286196Sbapt			errmsg = "unexpected address";
850286196Sbapt			return ERR;
851286196Sbapt		} else if ((sflags = get_shell_command()) < 0)
852286196Sbapt			return ERR;
853286196Sbapt		GET_COMMAND_SUFFIX();
854286196Sbapt		if (sflags) printf("%s\n", shcmd + 1);
855286196Sbapt		system(shcmd + 1);
856286196Sbapt		if (!scripted) printf("!\n");
857286196Sbapt		break;
858286196Sbapt	case '\n':
859286196Sbapt#ifdef BACKWARDS
860286218Sbapt		if (check_addr_range(first_addr = 1, current_addr + 1) < 0
861286196Sbapt#else
862286196Sbapt		if (check_addr_range(first_addr = 1, current_addr + !isglobal) < 0
863286196Sbapt#endif
864286196Sbapt		 || display_lines(second_addr, second_addr, 0) < 0)
865286196Sbapt			return ERR;
866286196Sbapt		break;
867286259Sed	default:
868286196Sbapt		errmsg = "unknown command";
869286196Sbapt		return ERR;
870286196Sbapt	}
871286196Sbapt	return gflag;
872286196Sbapt}
873286196Sbapt
874286196Sbapt
875286196Sbapt/* check_addr_range: return status of address range check */
876286196Sbaptint
877286196Sbaptcheck_addr_range(long n, long m)
878286196Sbapt{
879286196Sbapt	if (addr_cnt == 0) {
880286196Sbapt		first_addr = n;
881286196Sbapt		second_addr = m;
882286196Sbapt	}
883286196Sbapt	if (first_addr > second_addr || 1 > first_addr ||
884286196Sbapt	    second_addr > addr_last) {
885286196Sbapt		errmsg = "invalid address";
886286196Sbapt		return ERR;
887286196Sbapt	}
888286196Sbapt	return 0;
889286196Sbapt}
890286196Sbapt
891286196Sbapt
892286196Sbapt/* get_matching_node_addr: return the address of the next line matching a
893286196Sbapt   pattern in a given direction.  wrap around begin/end of editor buffer if
894286196Sbapt   necessary */
895286196Sbaptlong
896286196Sbaptget_matching_node_addr(pattern_t *pat, int dir)
897286196Sbapt{
898286196Sbapt	char *s;
899286196Sbapt	long n = current_addr;
900286196Sbapt	line_t *lp;
901286196Sbapt
902286196Sbapt	if (!pat) return ERR;
903286196Sbapt	do {
904286196Sbapt	       if ((n = dir ? INC_MOD(n, addr_last) : DEC_MOD(n, addr_last))) {
905286196Sbapt			lp = get_addressed_line_node(n);
906286196Sbapt			if ((s = get_sbuf_line(lp)) == NULL)
907286196Sbapt				return ERR;
908286196Sbapt			if (isbinary)
909286196Sbapt				NUL_TO_NEWLINE(s, lp->len);
910286196Sbapt			if (!regexec(pat, s, 0, NULL, 0))
911286196Sbapt				return n;
912285401Sbapt	       }
913286196Sbapt	} while (n != current_addr);
914285401Sbapt	errmsg = "no match";
915285401Sbapt	return  ERR;
916285989Sbapt}
917285989Sbapt
918286196Sbapt
919286196Sbapt/* get_filename: return pointer to copy of filename in the command buffer */
920286196Sbaptchar *
921286196Sbaptget_filename(void)
922286196Sbapt{
923286196Sbapt	static char *file = NULL;
924286196Sbapt	static int filesz = 0;
925286196Sbapt
926286196Sbapt	int n;
927286196Sbapt
928285989Sbapt	if (*ibufp != '\n') {
929286196Sbapt		SKIP_BLANKS();
930285401Sbapt		if (*ibufp == '\n') {
931285401Sbapt			errmsg = "invalid filename";
932285401Sbapt			return NULL;
933284111Sbapt		} else if ((ibufp = get_extended_line(&n, 1)) == NULL)
934284111Sbapt			return NULL;
935284111Sbapt		else if (*ibufp == '!') {
936286196Sbapt			ibufp++;
937285433Sbapt			if ((n = get_shell_command()) < 0)
938284111Sbapt				return NULL;
939284111Sbapt			if (n)
940285433Sbapt				printf("%s\n", shcmd + 1);
941285433Sbapt			return shcmd;
942284111Sbapt		} else if (n > PATH_MAX - 1) {
943284111Sbapt			errmsg = "filename too long";
944286202Sbapt			return  NULL;
945286202Sbapt		}
946284111Sbapt	}
947284111Sbapt#ifndef BACKWARDS
948284111Sbapt	else if (*old_filename == '\0') {
949286196Sbapt		errmsg = "no current filename";
950284111Sbapt		return  NULL;
951284111Sbapt	}
952284111Sbapt#endif
953284111Sbapt	REALLOC(file, filesz, PATH_MAX, NULL);
954284111Sbapt	for (n = 0; *ibufp != '\n';)
955284111Sbapt		file[n++] = *ibufp++;
956284111Sbapt	file[n] = '\0';
957284111Sbapt	return is_legal_filename(file) ? file : NULL;
958284111Sbapt}
959284111Sbapt
960284111Sbapt
961284111Sbapt/* get_shell_command: read a shell command from stdin; return substitution
962284111Sbapt   status */
963284111Sbaptint
964284111Sbaptget_shell_command(void)
965284111Sbapt{
966284111Sbapt	static char *buf = NULL;
967284111Sbapt	static int n = 0;
968286196Sbapt
969286196Sbapt	char *s;			/* substitution char pointer */
970284111Sbapt	int i = 0;
971285401Sbapt	int j = 0;
972285401Sbapt
973284111Sbapt	if (red) {
974284111Sbapt		errmsg = "shell access restricted";
975284111Sbapt		return ERR;
976284111Sbapt	} else if ((s = ibufp = get_extended_line(&j, 1)) == NULL)
977284128Sbapt		return ERR;
978284111Sbapt	REALLOC(buf, n, j + 1, ERR);
979284111Sbapt	buf[i++] = '!';			/* prefix command w/ bang */
980284128Sbapt	while (*ibufp != '\n')
981284128Sbapt		switch (*ibufp) {
982284111Sbapt		default:
983284111Sbapt			REALLOC(buf, n, i + 2, ERR);
984284111Sbapt			buf[i++] = *ibufp;
985284111Sbapt			if (*ibufp++ == '\\')
986284113Sbapt				buf[i++] = *ibufp++;
987284113Sbapt			break;
988284113Sbapt		case '!':
989284113Sbapt			if (s != ibufp) {
990284128Sbapt				REALLOC(buf, n, i + 1, ERR);
991284113Sbapt				buf[i++] = *ibufp++;
992284113Sbapt			}
993284113Sbapt#ifdef BACKWARDS
994284113Sbapt			else if (shcmd == NULL || *(shcmd + 1) == '\0')
995284113Sbapt#else
996284113Sbapt			else if (shcmd == NULL)
997284111Sbapt#endif
998284111Sbapt			{
999284111Sbapt				errmsg = "no previous command";
1000284111Sbapt				return ERR;
1001286196Sbapt			} else {
1002286196Sbapt				REALLOC(buf, n, i + shcmdi, ERR);
1003284111Sbapt				for (s = shcmd + 1; s < shcmd + shcmdi;)
1004285433Sbapt					buf[i++] = *s++;
1005285433Sbapt				s = ibufp++;
1006285433Sbapt			}
1007284111Sbapt			break;
1008286196Sbapt		case '%':
1009286196Sbapt			if (*old_filename  == '\0') {
1010286196Sbapt				errmsg = "no current filename";
1011284111Sbapt				return ERR;
1012285433Sbapt			}
1013286196Sbapt			j = strlen(s = strip_escapes(old_filename));
1014286196Sbapt			REALLOC(buf, n, i + j, ERR);
1015285433Sbapt			while (j--)
1016286196Sbapt				buf[i++] = *s++;
1017286196Sbapt			s = ibufp++;
1018286196Sbapt			break;
1019285433Sbapt		}
1020285433Sbapt	REALLOC(shcmd, shcmdsz, i + 1, ERR);
1021284111Sbapt	memcpy(shcmd, buf, i);
1022284111Sbapt	shcmd[shcmdi = i] = '\0';
1023284111Sbapt	return *s == '!' || *s == '%';
1024284111Sbapt}
1025284111Sbapt
1026286196Sbapt
1027286196Sbapt/* append_lines: insert text from stdin to after line n; stop when either a
102820253Sjoerg   single period is read or EOF; return status */
1029286196Sbaptint
103020253Sjoergappend_lines(long n)
1031286196Sbapt{
1032286196Sbapt	int l;
1033286196Sbapt	const char *lp = ibuf;
1034286196Sbapt	const char *eot;
1035286196Sbapt	undo_t *up = NULL;
1036286196Sbapt
103720253Sjoerg	for (current_addr = n;;) {
1038286196Sbapt		if (!isglobal) {
103920253Sjoerg			if ((l = get_tty_line()) < 0)
1040286196Sbapt				return ERR;
1041286196Sbapt			else if (l == 0 || ibuf[l - 1] != '\n') {
1042286196Sbapt				clearerr(stdin);
1043286196Sbapt				return  l ? EOF : 0;
1044286196Sbapt			}
1045286196Sbapt			lp = ibuf;
1046286196Sbapt		} else if (*(lp = ibufp) == '\0')
1047286196Sbapt			return 0;
1048286196Sbapt		else {
1049286196Sbapt			while (*ibufp++ != '\n')
1050286196Sbapt				;
1051286196Sbapt			l = ibufp - lp;
1052286196Sbapt		}
1053286196Sbapt		if (l == 2 && lp[0] == '.' && lp[1] == '\n') {
105420253Sjoerg			return 0;
105520253Sjoerg		}
1056286196Sbapt		eot = lp + l;
1057286196Sbapt		SPL1();
105820253Sjoerg		do {
105920253Sjoerg			if ((lp = put_sbuf_line(lp)) == NULL) {
1060286196Sbapt				SPL0();
1061286196Sbapt				return ERR;
106220253Sjoerg			} else if (up)
1063286196Sbapt				up->t = get_addressed_line_node(current_addr);
1064286196Sbapt			else if ((up = push_undo_stack(UADD, current_addr,
1065286196Sbapt			    current_addr)) == NULL) {
106620253Sjoerg				SPL0();
1067286196Sbapt				return ERR;
1068286196Sbapt			}
1069286196Sbapt		} while (lp != eot);
1070286196Sbapt		modified = 1;
1071286196Sbapt		SPL0();
1072286196Sbapt	}
1073286196Sbapt	/* NOTREACHED */
107420253Sjoerg}
1075286196Sbapt
1076286196Sbapt
1077286196Sbapt/* join_lines: replace a range of lines with the joined text of those lines */
1078286196Sbaptint
1079286196Sbaptjoin_lines(long from, long to)
1080286196Sbapt{
1081286196Sbapt	static char *buf = NULL;
1082286196Sbapt	static int n;
1083286196Sbapt
1084286196Sbapt	char *s;
1085286196Sbapt	int size = 0;
1086286196Sbapt	line_t *bp, *ep;
1087286196Sbapt
1088286196Sbapt	ep = get_addressed_line_node(INC_MOD(to, addr_last));
1089286196Sbapt	bp = get_addressed_line_node(from);
1090286196Sbapt	for (; bp != ep; bp = bp->q_forw) {
1091286196Sbapt		if ((s = get_sbuf_line(bp)) == NULL)
1092286196Sbapt			return ERR;
1093286196Sbapt		REALLOC(buf, n, size + bp->len, ERR);
1094286196Sbapt		memcpy(buf + size, s, bp->len);
1095286196Sbapt		size += bp->len;
1096286196Sbapt	}
1097286196Sbapt	REALLOC(buf, n, size + 2, ERR);
1098286196Sbapt	memcpy(buf + size, "\n", 2);
1099286196Sbapt	if (delete_lines(from, to) < 0)
1100286196Sbapt		return ERR;
1101286196Sbapt	current_addr = from - 1;
1102286196Sbapt	SPL1();
1103286196Sbapt	if (put_sbuf_line(buf) == NULL ||
1104286196Sbapt	    push_undo_stack(UADD, current_addr, current_addr) == NULL) {
1105286196Sbapt		SPL0();
1106286196Sbapt		return ERR;
1107286196Sbapt	}
1108286196Sbapt	modified = 1;
1109286196Sbapt	SPL0();
1110286196Sbapt	return 0;
1111286196Sbapt}
1112286196Sbapt
1113286196Sbapt
1114286196Sbapt/* move_lines: move a range of lines */
1115286196Sbaptint
1116286196Sbaptmove_lines(long addr)
1117286196Sbapt{
1118286196Sbapt	line_t *b1, *a1, *b2, *a2;
1119286196Sbapt	long n = INC_MOD(second_addr, addr_last);
1120286196Sbapt	long p = first_addr - 1;
1121286196Sbapt	int done = (addr == first_addr - 1 || addr == second_addr);
1122286196Sbapt
1123286196Sbapt	SPL1();
1124286196Sbapt	if (done) {
1125286196Sbapt		a2 = get_addressed_line_node(n);
1126286196Sbapt		b2 = get_addressed_line_node(p);
1127286196Sbapt		current_addr = second_addr;
1128286196Sbapt	} else if (push_undo_stack(UMOV, p, n) == NULL ||
1129286196Sbapt	    push_undo_stack(UMOV, addr, INC_MOD(addr, addr_last)) == NULL) {
1130286196Sbapt		SPL0();
1131286196Sbapt		return ERR;
1132286196Sbapt	} else {
1133286196Sbapt		a1 = get_addressed_line_node(n);
1134286196Sbapt		if (addr < first_addr) {
1135286196Sbapt			b1 = get_addressed_line_node(p);
1136286196Sbapt			b2 = get_addressed_line_node(addr);
1137286196Sbapt					/* this get_addressed_line_node last! */
1138286196Sbapt		} else {
1139286196Sbapt			b2 = get_addressed_line_node(addr);
1140286196Sbapt			b1 = get_addressed_line_node(p);
1141286196Sbapt					/* this get_addressed_line_node last! */
1142286196Sbapt		}
1143286196Sbapt		a2 = b2->q_forw;
1144286196Sbapt		REQUE(b2, b1->q_forw);
1145286196Sbapt		REQUE(a1->q_back, a2);
1146286196Sbapt		REQUE(b1, a1);
1147286196Sbapt		current_addr = addr + ((addr < first_addr) ?
1148286196Sbapt		    second_addr - first_addr + 1 : 0);
1149286196Sbapt	}
1150286196Sbapt	if (isglobal)
1151286196Sbapt		unset_active_nodes(b2->q_forw, a2);
1152286196Sbapt	modified = 1;
1153286196Sbapt	SPL0();
1154286196Sbapt	return 0;
1155286196Sbapt}
1156286196Sbapt
1157286196Sbapt
1158286196Sbapt/* copy_lines: copy a range of lines; return status */
1159286196Sbaptint
1160286196Sbaptcopy_lines(long addr)
1161286196Sbapt{
1162286196Sbapt	line_t *lp, *np = get_addressed_line_node(first_addr);
1163286196Sbapt	undo_t *up = NULL;
1164286196Sbapt	long n = second_addr - first_addr + 1;
1165286196Sbapt	long m = 0;
1166286196Sbapt
1167286196Sbapt	current_addr = addr;
1168286196Sbapt	if (first_addr <= addr && addr < second_addr) {
1169286196Sbapt		n =  addr - first_addr + 1;
1170286196Sbapt		m = second_addr - addr;
1171286196Sbapt	}
1172286196Sbapt	for (; n > 0; n=m, m=0, np = get_addressed_line_node(current_addr + 1))
1173286196Sbapt		for (; n-- > 0; np = np->q_forw) {
1174286196Sbapt			SPL1();
1175286196Sbapt			if ((lp = dup_line_node(np)) == NULL) {
1176286196Sbapt				SPL0();
1177286196Sbapt				return ERR;
1178286196Sbapt			}
1179286196Sbapt			add_line_node(lp);
1180286196Sbapt			if (up)
1181286196Sbapt				up->t = lp;
1182286196Sbapt			else if ((up = push_undo_stack(UADD, current_addr,
1183286196Sbapt			    current_addr)) == NULL) {
1184286196Sbapt				SPL0();
1185286196Sbapt				return ERR;
1186286196Sbapt			}
1187286196Sbapt			modified = 1;
1188286196Sbapt			SPL0();
1189286196Sbapt		}
1190286196Sbapt	return 0;
1191286196Sbapt}
1192286196Sbapt
1193286196Sbapt
1194286196Sbapt/* delete_lines: delete a range of lines */
1195286196Sbaptint
1196286196Sbaptdelete_lines(long from, long to)
1197286196Sbapt{
1198286259Sed	line_t *n, *p;
1199286196Sbapt
1200286196Sbapt	SPL1();
1201286196Sbapt	if (push_undo_stack(UDEL, from, to) == NULL) {
1202286196Sbapt		SPL0();
1203286196Sbapt		return ERR;
1204286196Sbapt	}
1205286196Sbapt	n = get_addressed_line_node(INC_MOD(to, addr_last));
1206286196Sbapt	p = get_addressed_line_node(from - 1);
1207286196Sbapt					/* this get_addressed_line_node last! */
1208286196Sbapt	if (isglobal)
1209286196Sbapt		unset_active_nodes(p->q_forw, n);
1210286196Sbapt	REQUE(p, n);
1211286196Sbapt	addr_last -= to - from + 1;
1212286196Sbapt	current_addr = from - 1;
1213286196Sbapt	modified = 1;
1214286196Sbapt	SPL0();
1215286196Sbapt	return 0;
1216286196Sbapt}
1217286196Sbapt
1218286196Sbapt
1219286196Sbapt/* display_lines: print a range of lines to stdout */
1220286196Sbaptint
1221286196Sbaptdisplay_lines(long from, long to, int gflag)
1222286196Sbapt{
1223286196Sbapt	line_t *bp;
1224286196Sbapt	line_t *ep;
1225286196Sbapt	char *s;
1226286196Sbapt
1227286196Sbapt	if (!from) {
1228286196Sbapt		errmsg = "invalid address";
1229286196Sbapt		return ERR;
1230286196Sbapt	}
1231286196Sbapt	ep = get_addressed_line_node(INC_MOD(to, addr_last));
1232286196Sbapt	bp = get_addressed_line_node(from);
1233286196Sbapt	for (; bp != ep; bp = bp->q_forw) {
1234286196Sbapt		if ((s = get_sbuf_line(bp)) == NULL)
1235286196Sbapt			return ERR;
1236286196Sbapt		if (put_tty_line(s, bp->len, current_addr = from++, gflag) < 0)
1237286196Sbapt			return ERR;
1238286196Sbapt	}
1239286196Sbapt	return 0;
1240286196Sbapt}
1241286196Sbapt
1242286196Sbapt
1243286196Sbapt#define MAXMARK 26			/* max number of marks */
1244286196Sbapt
1245286196Sbaptstatic line_t *mark[MAXMARK];		/* line markers */
1246286196Sbaptstatic int markno;			/* line marker count */
1247286196Sbapt
1248286196Sbapt/* mark_line_node: set a line node mark */
1249286196Sbaptint
1250286196Sbaptmark_line_node(line_t *lp, int n)
1251286196Sbapt{
1252286196Sbapt	if (!islower((unsigned char)n)) {
1253286196Sbapt		errmsg = "invalid mark character";
1254286196Sbapt		return ERR;
1255286196Sbapt	} else if (mark[n - 'a'] == NULL)
1256286196Sbapt		markno++;
1257286196Sbapt	mark[n - 'a'] = lp;
1258286196Sbapt	return 0;
1259286196Sbapt}
1260286196Sbapt
1261286196Sbapt
1262286196Sbapt/* get_marked_node_addr: return address of a marked line */
1263286196Sbaptlong
1264286196Sbaptget_marked_node_addr(int n)
1265286196Sbapt{
1266286196Sbapt	if (!islower((unsigned char)n)) {
1267286196Sbapt		errmsg = "invalid mark character";
1268286196Sbapt		return ERR;
1269286196Sbapt	}
1270286196Sbapt	return get_line_node_addr(mark[n - 'a']);
1271286196Sbapt}
1272286196Sbapt
1273286196Sbapt
1274286196Sbapt/* unmark_line_node: clear line node mark */
1275286196Sbaptvoid
1276286196Sbaptunmark_line_node(line_t *lp)
1277286196Sbapt{
1278286196Sbapt	int i;
1279286196Sbapt
1280286196Sbapt	for (i = 0; markno && i < MAXMARK; i++)
1281286196Sbapt		if (mark[i] == lp) {
1282286196Sbapt			mark[i] = NULL;
1283286196Sbapt			markno--;
1284286196Sbapt		}
1285286196Sbapt}
1286286196Sbapt
1287286196Sbapt
1288286196Sbapt/* dup_line_node: return a pointer to a copy of a line node */
1289286196Sbaptline_t *
1290286196Sbaptdup_line_node(line_t *lp)
1291286196Sbapt{
1292286196Sbapt	line_t *np;
1293286196Sbapt
1294286196Sbapt	if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) {
1295286196Sbapt		fprintf(stderr, "%s\n", strerror(errno));
1296286196Sbapt		errmsg = "out of memory";
1297286196Sbapt		return NULL;
1298286196Sbapt	}
1299286196Sbapt	np->seek = lp->seek;
1300286196Sbapt	np->len = lp->len;
1301286196Sbapt	return np;
1302286196Sbapt}
1303286196Sbapt
1304286196Sbapt
1305109961Sgad/* has_trailing_escape:  return the parity of escapes preceding a character
1306109961Sgad   in a string */
1307286196Sbaptint
1308286196Sbapthas_trailing_escape(char *s, char *t)
1309286196Sbapt{
1310286196Sbapt    return (s == t || *(t - 1) != '\\') ? 0 : !has_trailing_escape(s, t - 1);
1311286196Sbapt}
1312286196Sbapt
1313286196Sbapt
1314286196Sbapt/* strip_escapes: return copy of escaped string of at most length PATH_MAX */
1315286196Sbaptchar *
1316286196Sbaptstrip_escapes(char *s)
1317286196Sbapt{
1318305741Sasomers	static char *file = NULL;
1319286196Sbapt	static int filesz = 0;
1320286196Sbapt
1321286196Sbapt	int i = 0;
1322286196Sbapt
1323286196Sbapt	REALLOC(file, filesz, PATH_MAX, NULL);
1324286196Sbapt	while (i < filesz - 1	/* Worry about a possible trailing escape */
1325286196Sbapt	       && (file[i++] = (*s == '\\') ? *++s : *s))
1326286196Sbapt		s++;
1327286196Sbapt	return file;
1328286196Sbapt}
1329286196Sbapt
1330286196Sbapt
1331286196Sbaptvoid
1332109961Sgadsignal_hup(int signo)
1333286196Sbapt{
1334286196Sbapt	if (mutex)
1335286196Sbapt		sigflags |= (1 << (signo - 1));
1336286196Sbapt	else
1337286196Sbapt		handle_hup(signo);
1338286196Sbapt}
1339286196Sbapt
1340286196Sbapt
1341286196Sbaptvoid
1342286196Sbaptsignal_int(int signo)
1343286196Sbapt{
1344286196Sbapt	if (mutex)
1345286196Sbapt		sigflags |= (1 << (signo - 1));
1346109961Sgad	else
1347286196Sbapt		handle_int(signo);
1348286196Sbapt}
1349286196Sbapt
1350286196Sbapt
1351286196Sbaptvoid
1352286196Sbapthandle_hup(int signo)
1353286196Sbapt{
1354286196Sbapt	char *hup = NULL;		/* hup filename */
1355286196Sbapt	char *s;
1356286198Sbapt	char ed_hup[] = "ed.hup";
1357286198Sbapt	int n;
1358286198Sbapt
1359286196Sbapt	if (!sigactive)
1360286196Sbapt		quit(1);
1361286196Sbapt	sigflags &= ~(1 << (signo - 1));
1362286196Sbapt	if (addr_last && write_file(ed_hup, "w", 1, addr_last) < 0 &&
1363286196Sbapt	    (s = getenv("HOME")) != NULL &&
1364286196Sbapt	    (n = strlen(s)) + 8 <= PATH_MAX &&	/* "ed.hup" + '/' */
1365286196Sbapt	    (hup = (char *) malloc(n + 10)) != NULL) {
1366286196Sbapt		strcpy(hup, s);
1367286196Sbapt		if (hup[n - 1] != '/')
1368286196Sbapt			hup[n] = '/', hup[n+1] = '\0';
1369286196Sbapt		strcat(hup, "ed.hup");
1370286196Sbapt		write_file(hup, "w", 1, addr_last);
1371286196Sbapt	}
1372286196Sbapt	quit(2);
1373286196Sbapt}
1374286196Sbapt
1375286196Sbapt
1376286196Sbaptvoid
1377286196Sbapthandle_int(int signo)
1378286196Sbapt{
1379286196Sbapt	if (!sigactive)
1380286196Sbapt		quit(1);
1381286196Sbapt	sigflags &= ~(1 << (signo - 1));
1382286196Sbapt#ifdef _POSIX_SOURCE
1383286196Sbapt	siglongjmp(env, -1);
1384286196Sbapt#else
1385286196Sbapt	longjmp(env, -1);
1386286196Sbapt#endif
1387286196Sbapt}
1388286196Sbapt
1389286196Sbapt
1390286196Sbaptint cols = 72;				/* wrap column */
1391286196Sbapt
1392109961Sgadvoid
1393286196Sbapthandle_winch(int signo)
1394286196Sbapt{
1395286196Sbapt	int save_errno = errno;
1396286196Sbapt
1397286202Sbapt	struct winsize ws;		/* window size structure */
1398286202Sbapt
1399286196Sbapt	sigflags &= ~(1 << (signo - 1));
1400286196Sbapt	if (ioctl(0, TIOCGWINSZ, (char *) &ws) >= 0) {
1401286196Sbapt		if (ws.ws_row > 2) rows = ws.ws_row - 2;
1402286196Sbapt		if (ws.ws_col > 8) cols = ws.ws_col - 8;
1403284110Sbapt	}
1404286196Sbapt	errno = save_errno;
1405286196Sbapt}
1406286196Sbapt
1407286196Sbapt
1408286196Sbapt/* is_legal_filename: return a legal filename */
1409286196Sbaptint
1410286196Sbaptis_legal_filename(char *s)
1411286196Sbapt{
1412286196Sbapt	if (red && (*s == '!' || !strcmp(s, "..") || strchr(s, '/'))) {
1413286196Sbapt		errmsg = "shell access restricted";
1414286196Sbapt		return 0;
1415286196Sbapt	}
1416286196Sbapt	return 1;
1417286196Sbapt}
1418286196Sbapt