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