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