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