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