xref: /openbsd/bin/ed/main.c (revision 1ca96170)
1 /*	$OpenBSD: main.c,v 1.70 2024/10/12 07:58:40 jsg 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 	siginterrupt(SIGHUP, 1);
184 	signal(SIGQUIT, SIG_IGN);
185 	signal(SIGINT, signal_int);
186 	if (sigsetjmp(env, 1)) {
187 		status = -1;
188 		fputs("\n?\n", stdout);
189 		seterrmsg("interrupt");
190 	} else {
191 		init_buffers();
192 		sigactive = 1;			/* enable signal handlers */
193 		if (argc && **argv) {
194 			if (read_file(*argv, 0) < 0 && !interactive)
195 				quit(2);
196 			else if (**argv != '!')
197 				strlcpy(old_filename, *argv,
198 				    sizeof old_filename);
199 		} else if (argc) {
200 			fputs("?\n", stdout);
201 			if (**argv == '\0')
202 				seterrmsg("invalid filename");
203 			if (!interactive)
204 				quit(2);
205 		}
206 	}
207 	for (;;) {
208 		if (status < 0 && garrulous)
209 			fprintf(stderr, "%s\n", errmsg);
210 		if (prompt) {
211 			fputs(prompt, stdout);
212 			fflush(stdout);
213 		}
214 		if ((n = get_tty_line()) < 0) {
215 			status = ERR;
216 			continue;
217 		} else if (n == 0) {
218 			if (modified && !scripted) {
219 				fputs("?\n", stdout);
220 				seterrmsg("warning: file modified");
221 				if (!interactive) {
222 					if (garrulous)
223 						fprintf(stderr,
224 						    "script, line %d: %s\n",
225 						    lineno, errmsg);
226 					quit(2);
227 				}
228 				clearerr(stdin);
229 				modified = 0;
230 				status = EMOD;
231 				continue;
232 			} else
233 				quit(0);
234 		} else if (ibuf[n - 1] != '\n') {
235 			/* discard line */
236 			seterrmsg("unexpected end-of-file");
237 			clearerr(stdin);
238 			status = ERR;
239 			continue;
240 		}
241 		isglobal = 0;
242 		if ((status = extract_addr_range()) >= 0 &&
243 		    (status = exec_command()) >= 0)
244 			if (!status || (status &&
245 			    (status = display_lines(current_addr, current_addr,
246 				status)) >= 0))
247 				continue;
248 		switch (status) {
249 		case EOF:
250 			quit(0);
251 			break;
252 		case EMOD:
253 			modified = 0;
254 			fputs("?\n", stdout);		/* give warning */
255 			seterrmsg("warning: file modified");
256 			if (!interactive) {
257 				if (garrulous)
258 					fprintf(stderr,
259 					    "script, line %d: %s\n",
260 					    lineno, errmsg);
261 				quit(2);
262 			}
263 			break;
264 		case FATAL:
265 			if (!interactive) {
266 				if (garrulous)
267 					fprintf(stderr,
268 					    "script, line %d: %s\n",
269 					    lineno, errmsg);
270 			} else if (garrulous)
271 				fprintf(stderr, "%s\n", errmsg);
272 			quit(3);
273 			break;
274 		default:
275 			fputs("?\n", stdout);
276 			if (!interactive) {
277 				if (garrulous)
278 					fprintf(stderr,
279 					    "script, line %d: %s\n",
280 					    lineno, errmsg);
281 				quit(2);
282 			}
283 			break;
284 		}
285 	}
286 	/*NOTREACHED*/
287 }
288 
289 int first_addr, second_addr, addr_cnt;
290 
291 /* extract_addr_range: get line addresses from the command buffer until an
292    illegal address is seen; return status */
293 int
extract_addr_range(void)294 extract_addr_range(void)
295 {
296 	int addr;
297 
298 	addr_cnt = 0;
299 	first_addr = second_addr = current_addr;
300 	while ((addr = next_addr()) >= 0) {
301 		addr_cnt++;
302 		first_addr = second_addr;
303 		second_addr = addr;
304 		if (*ibufp != ',' && *ibufp != ';')
305 			break;
306 		else if (*ibufp++ == ';')
307 			current_addr = addr;
308 	}
309 	if ((addr_cnt = min(addr_cnt, 2)) == 1 || second_addr != addr)
310 		first_addr = second_addr;
311 	return (addr == ERR) ? ERR : 0;
312 }
313 
314 
315 #define	SKIP_BLANKS() \
316 	do { \
317 		while (isspace((unsigned char)*ibufp) && *ibufp != '\n') \
318 			ibufp++; \
319 	} while (0)
320 
321 #define MUST_BE_FIRST() \
322 	do { \
323 		if (!first) { \
324 			seterrmsg("invalid address"); \
325 			return ERR; \
326 		} \
327 	} while (0)
328 
329 
330 /*  next_addr: return the next line address in the command buffer */
331 static int
next_addr(void)332 next_addr(void)
333 {
334 	char *hd;
335 	int addr = current_addr;
336 	int n;
337 	int first = 1;
338 	int c;
339 
340 	SKIP_BLANKS();
341 	for (hd = ibufp;; first = 0)
342 		switch ((c = (unsigned char)*ibufp)) {
343 		case '+':
344 		case '\t':
345 		case ' ':
346 		case '-':
347 		case '^':
348 			ibufp++;
349 			SKIP_BLANKS();
350 			if (isdigit((unsigned char)*ibufp)) {
351 				STRTOI(n, ibufp);
352 				addr += (c == '-' || c == '^') ? -n : n;
353 			} else if (!isspace(c))
354 				addr += (c == '-' || c == '^') ? -1 : 1;
355 			break;
356 		case '0': case '1': case '2':
357 		case '3': case '4': case '5':
358 		case '6': case '7': case '8': case '9':
359 			MUST_BE_FIRST();
360 			STRTOI(addr, ibufp);
361 			break;
362 		case '.':
363 		case '$':
364 			MUST_BE_FIRST();
365 			ibufp++;
366 			addr = (c == '.') ? current_addr : addr_last;
367 			break;
368 		case '/':
369 		case '?':
370 			MUST_BE_FIRST();
371 			if ((addr = get_matching_node_addr(
372 			    get_compiled_pattern(), c == '/')) < 0)
373 				return ERR;
374 			else if (c == *ibufp)
375 				ibufp++;
376 			break;
377 		case '\'':
378 			MUST_BE_FIRST();
379 			ibufp++;
380 			if ((addr = get_marked_node_addr((unsigned char)*ibufp++)) < 0)
381 				return ERR;
382 			break;
383 		case '%':
384 		case ',':
385 		case ';':
386 			if (first) {
387 				ibufp++;
388 				addr_cnt++;
389 				second_addr = (c == ';') ? current_addr : 1;
390 				if ((addr = next_addr()) < 0)
391 					addr = addr_last;
392 				break;
393 			}
394 			/* FALLTHROUGH */
395 		default:
396 			if (ibufp == hd)
397 				return EOF;
398 			else if (addr < 0 || addr_last < addr) {
399 				seterrmsg("invalid address");
400 				return ERR;
401 			} else
402 				return addr;
403 		}
404 	/* NOTREACHED */
405 }
406 
407 
408 /* GET_THIRD_ADDR: get a legal address from the command buffer */
409 #define GET_THIRD_ADDR(addr) \
410 	do { \
411 		int ol1, ol2; \
412 		\
413 		ol1 = first_addr; \
414 		ol2 = second_addr; \
415 		if (extract_addr_range() < 0) \
416 			return ERR; \
417 		else if (addr_cnt == 0) { \
418 			seterrmsg("destination expected"); \
419 			return ERR; \
420 		} else if (second_addr < 0 || addr_last < second_addr) { \
421 			seterrmsg("invalid address"); \
422 			return ERR; \
423 		} \
424 		addr = second_addr; \
425 		first_addr = ol1; \
426 		second_addr = ol2; \
427 	} while (0)
428 
429 
430 /* GET_COMMAND_SUFFIX: verify the command suffix in the command buffer */
431 #define GET_COMMAND_SUFFIX() \
432 	do { \
433 		int done = 0; \
434 		do { \
435 			switch (*ibufp) { \
436 			case 'p': \
437 				gflag |= GPR; \
438 				ibufp++; \
439 				break; \
440 			case 'l': \
441 				gflag |= GLS; \
442 				ibufp++; \
443 				break; \
444 			case 'n': \
445 				gflag |= GNP; \
446 				ibufp++; \
447 				break; \
448 			default: \
449 				done++; \
450 			} \
451 		} while (!done); \
452 		if (*ibufp++ != '\n') { \
453 			seterrmsg("invalid command suffix"); \
454 			return ERR; \
455 		} \
456 	} while (0)
457 
458 /* sflags */
459 #define SGG 001		/* complement previous global substitute suffix */
460 #define SGP 002		/* complement previous print suffix */
461 #define SGR 004		/* use last regex instead of last pat */
462 #define SGF 010		/* repeat last substitution */
463 
464 int patlock = 0;	/* if set, pattern not freed by get_compiled_pattern() */
465 
466 volatile sig_atomic_t rows = 22;	/* scroll length: ws_row - 2 */
467 volatile sig_atomic_t cols = 72;	/* wrap column */
468 
469 /* exec_command: execute the next command in command buffer; return print
470    request, if any */
471 int
exec_command(void)472 exec_command(void)
473 {
474 	extern int u_current_addr;
475 	extern int u_addr_last;
476 
477 	static regex_t *pat = NULL;
478 	static int sgflag = 0;
479 	static int sgnum = 0;
480 
481 	regex_t *tpat;
482 	char *fnp;
483 	int gflag = 0;
484 	int sflags = 0;
485 	int addr = 0;
486 	int n = 0;
487 	int c;
488 
489 	SKIP_BLANKS();
490 	switch ((c = (unsigned char)*ibufp++)) {
491 	case 'a':
492 		GET_COMMAND_SUFFIX();
493 		if (!isglobal) clear_undo_stack();
494 		if (append_lines(second_addr) < 0)
495 			return ERR;
496 		break;
497 	case 'c':
498 		if (check_addr_range(current_addr, current_addr) < 0)
499 			return ERR;
500 		GET_COMMAND_SUFFIX();
501 		if (!isglobal) clear_undo_stack();
502 		if (delete_lines(first_addr, second_addr) < 0 ||
503 		    append_lines(current_addr) < 0)
504 			return ERR;
505 		break;
506 	case 'd':
507 		if (check_addr_range(current_addr, current_addr) < 0)
508 			return ERR;
509 		GET_COMMAND_SUFFIX();
510 		if (!isglobal) clear_undo_stack();
511 		if (delete_lines(first_addr, second_addr) < 0)
512 			return ERR;
513 		else if ((addr = INC_MOD(current_addr, addr_last)) != 0)
514 			current_addr = addr;
515 		break;
516 	case 'e':
517 		if (modified && !scripted)
518 			return EMOD;
519 		/* FALLTHROUGH */
520 	case 'E':
521 		if (addr_cnt > 0) {
522 			seterrmsg("unexpected address");
523 			return ERR;
524 		} else if (!isspace((unsigned char)*ibufp)) {
525 			seterrmsg("unexpected command suffix");
526 			return ERR;
527 		} else if ((fnp = get_filename(1)) == NULL)
528 			return ERR;
529 		GET_COMMAND_SUFFIX();
530 		if (delete_lines(1, addr_last) < 0)
531 			return ERR;
532 		clear_undo_stack();
533 		if (close_sbuf() < 0)
534 			return ERR;
535 		else if (open_sbuf() < 0)
536 			return FATAL;
537 		if (read_file(fnp, 0) < 0)
538 			return ERR;
539 		clear_undo_stack();
540 		modified = 0;
541 		u_current_addr = u_addr_last = -1;
542 		break;
543 	case 'f':
544 		if (addr_cnt > 0) {
545 			seterrmsg("unexpected address");
546 			return ERR;
547 		} else if (!isspace((unsigned char)*ibufp)) {
548 			seterrmsg("unexpected command suffix");
549 			return ERR;
550 		} else if ((fnp = get_filename(1)) == NULL)
551 			return ERR;
552 		else if (*fnp == '!') {
553 			seterrmsg("invalid redirection");
554 			return ERR;
555 		}
556 		GET_COMMAND_SUFFIX();
557 		puts(strip_escapes(fnp));
558 		break;
559 	case 'g':
560 	case 'v':
561 	case 'G':
562 	case 'V':
563 		if (isglobal) {
564 			seterrmsg("cannot nest global commands");
565 			return ERR;
566 		} else if (check_addr_range(1, addr_last) < 0)
567 			return ERR;
568 		else if (build_active_list(c == 'g' || c == 'G') < 0)
569 			return ERR;
570 		else if ((n = (c == 'G' || c == 'V')))
571 			GET_COMMAND_SUFFIX();
572 		isglobal++;
573 		if (exec_global(n, gflag) < 0)
574 			return ERR;
575 		break;
576 	case 'h':
577 		if (addr_cnt > 0) {
578 			seterrmsg("unexpected address");
579 			return ERR;
580 		}
581 		GET_COMMAND_SUFFIX();
582 		if (*errmsg) fprintf(stderr, "%s\n", errmsg);
583 		break;
584 	case 'H':
585 		if (addr_cnt > 0) {
586 			seterrmsg("unexpected address");
587 			return ERR;
588 		}
589 		GET_COMMAND_SUFFIX();
590 		if ((garrulous = 1 - garrulous) && *errmsg)
591 			fprintf(stderr, "%s\n", errmsg);
592 		break;
593 	case 'i':
594 		if (second_addr == 0) {
595 			second_addr = 1;
596 		}
597 		GET_COMMAND_SUFFIX();
598 		if (!isglobal) clear_undo_stack();
599 		if (append_lines(second_addr - 1) < 0)
600 			return ERR;
601 		break;
602 	case 'j':
603 		if (check_addr_range(current_addr, current_addr + 1) < 0)
604 			return ERR;
605 		GET_COMMAND_SUFFIX();
606 		if (!isglobal) clear_undo_stack();
607 		if (first_addr != second_addr &&
608 		    join_lines(first_addr, second_addr) < 0)
609 			return ERR;
610 		break;
611 	case 'k':
612 		c = (unsigned char)*ibufp++;
613 		if (second_addr == 0) {
614 			seterrmsg("invalid address");
615 			return ERR;
616 		}
617 		GET_COMMAND_SUFFIX();
618 		if (mark_line_node(get_addressed_line_node(second_addr), c) < 0)
619 			return ERR;
620 		break;
621 	case 'l':
622 		if (check_addr_range(current_addr, current_addr) < 0)
623 			return ERR;
624 		GET_COMMAND_SUFFIX();
625 		if (display_lines(first_addr, second_addr, gflag | GLS) < 0)
626 			return ERR;
627 		gflag = 0;
628 		break;
629 	case 'm':
630 		if (check_addr_range(current_addr, current_addr) < 0)
631 			return ERR;
632 		GET_THIRD_ADDR(addr);
633 		if (first_addr <= addr && addr < second_addr) {
634 			seterrmsg("invalid destination");
635 			return ERR;
636 		}
637 		GET_COMMAND_SUFFIX();
638 		if (!isglobal) clear_undo_stack();
639 		if (move_lines(addr) < 0)
640 			return ERR;
641 		break;
642 	case 'n':
643 		if (check_addr_range(current_addr, current_addr) < 0)
644 			return ERR;
645 		GET_COMMAND_SUFFIX();
646 		if (display_lines(first_addr, second_addr, gflag | GNP) < 0)
647 			return ERR;
648 		gflag = 0;
649 		break;
650 	case 'p':
651 		if (check_addr_range(current_addr, current_addr) < 0)
652 			return ERR;
653 		GET_COMMAND_SUFFIX();
654 		if (display_lines(first_addr, second_addr, gflag | GPR) < 0)
655 			return ERR;
656 		gflag = 0;
657 		break;
658 	case 'P':
659 		if (addr_cnt > 0) {
660 			seterrmsg("unexpected address");
661 			return ERR;
662 		}
663 		GET_COMMAND_SUFFIX();
664 		prompt = prompt ? NULL : optarg ? optarg : dps;
665 		break;
666 	case 'q':
667 	case 'Q':
668 		if (addr_cnt > 0) {
669 			seterrmsg("unexpected address");
670 			return ERR;
671 		}
672 		GET_COMMAND_SUFFIX();
673 		gflag =  (modified && !scripted && c == 'q') ? EMOD : EOF;
674 		break;
675 	case 'r':
676 		if (!isspace((unsigned char)*ibufp)) {
677 			seterrmsg("unexpected command suffix");
678 			return ERR;
679 		} else if (addr_cnt == 0)
680 			second_addr = addr_last;
681 		if ((fnp = get_filename(0)) == NULL)
682 			return ERR;
683 		GET_COMMAND_SUFFIX();
684 		if (!isglobal) clear_undo_stack();
685 		if ((addr = read_file(fnp, second_addr)) < 0)
686 			return ERR;
687 		else if (addr)
688 			modified = 1;
689 		break;
690 	case 's':
691 		do {
692 			switch (*ibufp) {
693 			case '\n':
694 				sflags |=SGF;
695 				break;
696 			case 'g':
697 				sflags |= SGG;
698 				ibufp++;
699 				break;
700 			case 'p':
701 				sflags |= SGP;
702 				ibufp++;
703 				break;
704 			case 'r':
705 				sflags |= SGR;
706 				ibufp++;
707 				break;
708 			case '0': case '1': case '2': case '3': case '4':
709 			case '5': case '6': case '7': case '8': case '9':
710 				STRTOI(sgnum, ibufp);
711 				sflags |= SGF;
712 				sgflag &= ~GSG;		/* override GSG */
713 				break;
714 			default:
715 				if (sflags) {
716 					seterrmsg("invalid command suffix");
717 					return ERR;
718 				}
719 			}
720 		} while (sflags && *ibufp != '\n');
721 		if (sflags && !pat) {
722 			seterrmsg("no previous substitution");
723 			return ERR;
724 		} else if (sflags & SGG)
725 			sgnum = 0;		/* override numeric arg */
726 		if (*ibufp != '\n' && *(ibufp + 1) == '\n') {
727 			seterrmsg("invalid pattern delimiter");
728 			return ERR;
729 		}
730 		tpat = pat;
731 		SPL1();
732 		if ((!sflags || (sflags & SGR)) &&
733 		    (tpat = get_compiled_pattern()) == NULL) {
734 		 	SPL0();
735 			return ERR;
736 		} else if (tpat != pat) {
737 			if (pat) {
738 				regfree(pat);
739 				free(pat);
740 			}
741 			pat = tpat;
742 			patlock = 1;		/* reserve pattern */
743 		}
744 		SPL0();
745 		if (!sflags && extract_subst_tail(&sgflag, &sgnum) < 0)
746 			return ERR;
747 		else if (isglobal)
748 			sgflag |= GLB;
749 		else
750 			sgflag &= ~GLB;
751 		if (sflags & SGG)
752 			sgflag ^= GSG;
753 		if (sflags & SGP) {
754 			sgflag ^= GPR;
755 			sgflag &= ~(GLS | GNP);
756 		}
757 		do {
758 			switch (*ibufp) {
759 			case 'p':
760 				sgflag |= GPR;
761 				ibufp++;
762 				break;
763 			case 'l':
764 				sgflag |= GLS;
765 				ibufp++;
766 				break;
767 			case 'n':
768 				sgflag |= GNP;
769 				ibufp++;
770 				break;
771 			default:
772 				n++;
773 			}
774 		} while (!n);
775 		if (check_addr_range(current_addr, current_addr) < 0)
776 			return ERR;
777 		GET_COMMAND_SUFFIX();
778 		if (!isglobal) clear_undo_stack();
779 		if (search_and_replace(pat, sgflag, sgnum) < 0)
780 			return ERR;
781 		break;
782 	case 't':
783 		if (check_addr_range(current_addr, current_addr) < 0)
784 			return ERR;
785 		GET_THIRD_ADDR(addr);
786 		GET_COMMAND_SUFFIX();
787 		if (!isglobal) clear_undo_stack();
788 		if (copy_lines(addr) < 0)
789 			return ERR;
790 		break;
791 	case 'u':
792 		if (addr_cnt > 0) {
793 			seterrmsg("unexpected address");
794 			return ERR;
795 		}
796 		GET_COMMAND_SUFFIX();
797 		if (pop_undo_stack() < 0)
798 			return ERR;
799 		break;
800 	case 'w':
801 	case 'W':
802 		if ((n = *ibufp) == 'q' || n == 'Q') {
803 			gflag = EOF;
804 			ibufp++;
805 		}
806 		if (!isspace((unsigned char)*ibufp)) {
807 			seterrmsg("unexpected command suffix");
808 			return ERR;
809 		} else if ((fnp = get_filename(0)) == NULL)
810 			return ERR;
811 		if (addr_cnt == 0 && !addr_last)
812 			first_addr = second_addr = 0;
813 		else if (check_addr_range(1, addr_last) < 0)
814 			return ERR;
815 		GET_COMMAND_SUFFIX();
816 		if ((addr = write_file(fnp, (c == 'W') ? "a" : "w",
817 		    first_addr, second_addr)) < 0)
818 			return ERR;
819 		else if (addr == addr_last && *fnp != '!')
820 			modified = 0;
821 		else if (modified && !scripted && n == 'q')
822 			gflag = EMOD;
823 		break;
824 	case 'x':
825 		if (addr_cnt > 0) {
826 			seterrmsg("unexpected address");
827 			return ERR;
828 		}
829 		GET_COMMAND_SUFFIX();
830 		seterrmsg("crypt unavailable");
831 		return ERR;
832 	case 'z':
833 		first_addr = 1;
834 		if (check_addr_range(first_addr, current_addr + 1) < 0)
835 			return ERR;
836 		else if ('0' < *ibufp && *ibufp <= '9')
837 			STRTOI(rows, ibufp);
838 		GET_COMMAND_SUFFIX();
839 		if (display_lines(second_addr, min(addr_last,
840 		    second_addr + rows), gflag) < 0)
841 			return ERR;
842 		gflag = 0;
843 		break;
844 	case '=':
845 		GET_COMMAND_SUFFIX();
846 		printf("%d\n", addr_cnt ? second_addr : addr_last);
847 		break;
848 	case '!':
849 		if (addr_cnt > 0) {
850 			seterrmsg("unexpected address");
851 			return ERR;
852 		} else if ((sflags = get_shell_command()) < 0)
853 			return ERR;
854 		GET_COMMAND_SUFFIX();
855 		if (sflags) printf("%s\n", shcmd + 1);
856 		fflush(NULL); /* flush any buffered I/O */
857 		system(shcmd + 1);
858 		if (!scripted) printf("!\n");
859 		break;
860 	case '\n':
861 		first_addr = 1;
862 		if (check_addr_range(first_addr, current_addr + 1) < 0
863 		 || display_lines(second_addr, second_addr, 0) < 0)
864 			return ERR;
865 		break;
866 	default:
867 		seterrmsg("unknown command");
868 		return ERR;
869 	}
870 	return gflag;
871 }
872 
873 
874 /* check_addr_range: return status of address range check */
875 static int
check_addr_range(int n,int m)876 check_addr_range(int n, int m)
877 {
878 	if (addr_cnt == 0) {
879 		first_addr = n;
880 		second_addr = m;
881 	}
882 	if (first_addr > second_addr || 1 > first_addr ||
883 	    second_addr > addr_last) {
884 		seterrmsg("invalid address");
885 		return ERR;
886 	}
887 	return 0;
888 }
889 
890 
891 /* get_matching_node_addr: return the address of the next line matching a
892    pattern in a given direction.  wrap around begin/end of editor buffer if
893    necessary */
894 static int
get_matching_node_addr(regex_t * pat,int dir)895 get_matching_node_addr(regex_t *pat, int dir)
896 {
897 	char *s;
898 	int 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 	seterrmsg("no match");
914 	return  ERR;
915 }
916 
917 
918 /* get_filename: return pointer to copy of filename in the command buffer */
919 static char *
get_filename(int save)920 get_filename(int save)
921 {
922 	static char filename[PATH_MAX];
923 	char *p;
924 	int n;
925 
926 	if (*ibufp != '\n') {
927 		SKIP_BLANKS();
928 		if (*ibufp == '\n') {
929 			seterrmsg("invalid filename");
930 			return NULL;
931 		} else if ((ibufp = get_extended_line(&n, 1)) == NULL)
932 			return NULL;
933 		else if (*ibufp == '!') {
934 			ibufp++;
935 			if ((n = get_shell_command()) < 0)
936 				return NULL;
937 			if (n) printf("%s\n", shcmd + 1);
938 			return shcmd;
939 		} else if (n >= PATH_MAX - 1) {
940 			seterrmsg("filename too long");
941 			return NULL;
942 		}
943 	} else {
944 		if (*old_filename == '\0') {
945 			seterrmsg("no current filename");
946 			return  NULL;
947 		}
948 		return old_filename;
949 	}
950 
951 	p = save ? old_filename : *old_filename ? filename : old_filename;
952 	for (n = 0; *ibufp != '\n';)
953 		p[n++] = *ibufp++;
954 	p[n] = '\0';
955 	return p;
956 }
957 
958 
959 /* get_shell_command: read a shell command from stdin; return substitution
960    status */
961 static int
get_shell_command(void)962 get_shell_command(void)
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 ((s = ibufp = get_extended_line(&j, 1)) == NULL)
972 		return ERR;
973 	REALLOC(buf, n, j + 1, ERR);
974 	buf[i++] = '!';			/* prefix command w/ bang */
975 	while (*ibufp != '\n')
976 		switch (*ibufp) {
977 		default:
978 			REALLOC(buf, n, i + 2, ERR);
979 			buf[i++] = *ibufp;
980 			if (*ibufp++ == '\\')
981 				buf[i++] = *ibufp++;
982 			break;
983 		case '!':
984 			if (s != ibufp) {
985 				REALLOC(buf, n, i + 1, ERR);
986 				buf[i++] = *ibufp++;
987 			}
988 			else if (shcmd == NULL)
989 			{
990 				seterrmsg("no previous command");
991 				return ERR;
992 			} else {
993 				REALLOC(buf, n, i + shcmdi, ERR);
994 				for (s = shcmd + 1; s < shcmd + shcmdi;)
995 					buf[i++] = *s++;
996 				s = ibufp++;
997 			}
998 			break;
999 		case '%':
1000 			if (*old_filename  == '\0') {
1001 				seterrmsg("no current filename");
1002 				return ERR;
1003 			}
1004 			j = strlen(s = strip_escapes(old_filename));
1005 			REALLOC(buf, n, i + j, ERR);
1006 			while (j--)
1007 				buf[i++] = *s++;
1008 			s = ibufp++;
1009 			break;
1010 		}
1011 	if (i == 1) {
1012 		seterrmsg("no command");
1013 		return ERR;
1014 	}
1015 	REALLOC(shcmd, shcmdsz, i + 1, ERR);
1016 	memcpy(shcmd, buf, i);
1017 	shcmd[shcmdi = i] = '\0';
1018 	return *s == '!' || *s == '%';
1019 }
1020 
1021 
1022 /* append_lines: insert text from stdin to after line n; stop when either a
1023    single period is read or EOF; return status */
1024 static int
append_lines(int n)1025 append_lines(int n)
1026 {
1027 	int l;
1028 	char *lp = ibuf;
1029 	char *eot;
1030 	undo_t *up = NULL;
1031 
1032 	for (current_addr = n;;) {
1033 		if (!isglobal) {
1034 			if ((l = get_tty_line()) < 0)
1035 				return ERR;
1036 			else if (l == 0 || ibuf[l - 1] != '\n') {
1037 				clearerr(stdin);
1038 				return  l ? EOF : 0;
1039 			}
1040 			lp = ibuf;
1041 		} else if (*(lp = ibufp) == '\0')
1042 			return 0;
1043 		else {
1044 			while (*ibufp++ != '\n')
1045 				;
1046 			l = ibufp - lp;
1047 		}
1048 		if (l == 2 && lp[0] == '.' && lp[1] == '\n') {
1049 			return 0;
1050 		}
1051 		eot = lp + l;
1052 		SPL1();
1053 		do {
1054 			if ((lp = put_sbuf_line(lp)) == NULL) {
1055 				SPL0();
1056 				return ERR;
1057 			} else if (up)
1058 				up->t = get_addressed_line_node(current_addr);
1059 			else if ((up = push_undo_stack(UADD, current_addr,
1060 			    current_addr)) == NULL) {
1061 				SPL0();
1062 				return ERR;
1063 			}
1064 		} while (lp != eot);
1065 		modified = 1;
1066 		SPL0();
1067 	}
1068 	/* NOTREACHED */
1069 }
1070 
1071 
1072 /* join_lines: replace a range of lines with the joined text of those lines */
1073 static int
join_lines(int from,int to)1074 join_lines(int from, int to)
1075 {
1076 	static char *buf = NULL;
1077 	static int n;
1078 
1079 	char *s;
1080 	int size = 0;
1081 	line_t *bp, *ep;
1082 
1083 	ep = get_addressed_line_node(INC_MOD(to, addr_last));
1084 	bp = get_addressed_line_node(from);
1085 	for (; bp != ep; bp = bp->q_forw) {
1086 		if ((s = get_sbuf_line(bp)) == NULL)
1087 			return ERR;
1088 		REALLOC(buf, n, size + bp->len, ERR);
1089 		memcpy(buf + size, s, bp->len);
1090 		size += bp->len;
1091 	}
1092 	REALLOC(buf, n, size + 2, ERR);
1093 	memcpy(buf + size, "\n", 2);
1094 	if (delete_lines(from, to) < 0)
1095 		return ERR;
1096 	current_addr = from - 1;
1097 	SPL1();
1098 	if (put_sbuf_line(buf) == NULL ||
1099 	    push_undo_stack(UADD, current_addr, current_addr) == NULL) {
1100 		SPL0();
1101 		return ERR;
1102 	}
1103 	modified = 1;
1104 	SPL0();
1105 	return 0;
1106 }
1107 
1108 
1109 /* move_lines: move a range of lines */
1110 static int
move_lines(int addr)1111 move_lines(int addr)
1112 {
1113 	line_t *b1, *a1, *b2, *a2;
1114 	int n = INC_MOD(second_addr, addr_last);
1115 	int p = first_addr - 1;
1116 	int done = (addr == first_addr - 1 || addr == second_addr);
1117 
1118 	SPL1();
1119 	if (done) {
1120 		a2 = get_addressed_line_node(n);
1121 		b2 = get_addressed_line_node(p);
1122 		current_addr = second_addr;
1123 	} else if (push_undo_stack(UMOV, p, n) == NULL ||
1124 	    push_undo_stack(UMOV, addr, INC_MOD(addr, addr_last)) == NULL) {
1125 		SPL0();
1126 		return ERR;
1127 	} else {
1128 		a1 = get_addressed_line_node(n);
1129 		if (addr < first_addr) {
1130 			b1 = get_addressed_line_node(p);
1131 			b2 = get_addressed_line_node(addr);
1132 					/* this get_addressed_line_node last! */
1133 		} else {
1134 			b2 = get_addressed_line_node(addr);
1135 			b1 = get_addressed_line_node(p);
1136 					/* this get_addressed_line_node last! */
1137 		}
1138 		a2 = b2->q_forw;
1139 		REQUE(b2, b1->q_forw);
1140 		REQUE(a1->q_back, a2);
1141 		REQUE(b1, a1);
1142 		current_addr = addr + ((addr < first_addr) ?
1143 		    second_addr - first_addr + 1 : 0);
1144 	}
1145 	if (isglobal)
1146 		unset_active_nodes(b2->q_forw, a2);
1147 	modified = 1;
1148 	SPL0();
1149 	return 0;
1150 }
1151 
1152 
1153 /* copy_lines: copy a range of lines; return status */
1154 static int
copy_lines(int addr)1155 copy_lines(int addr)
1156 {
1157 	line_t *lp, *np = get_addressed_line_node(first_addr);
1158 	undo_t *up = NULL;
1159 	int n = second_addr - first_addr + 1;
1160 	int m = 0;
1161 
1162 	current_addr = addr;
1163 	if (first_addr <= addr && addr < second_addr) {
1164 		n =  addr - first_addr + 1;
1165 		m = second_addr - addr;
1166 	}
1167 	for (; n > 0; n=m, m=0, np = get_addressed_line_node(current_addr + 1))
1168 		for (; n-- > 0; np = np->q_forw) {
1169 			SPL1();
1170 			if ((lp = dup_line_node(np)) == NULL) {
1171 				SPL0();
1172 				return ERR;
1173 			}
1174 			add_line_node(lp);
1175 			if (up)
1176 				up->t = lp;
1177 			else if ((up = push_undo_stack(UADD, current_addr,
1178 			    current_addr)) == NULL) {
1179 				SPL0();
1180 				return ERR;
1181 			}
1182 			modified = 1;
1183 			SPL0();
1184 		}
1185 	return 0;
1186 }
1187 
1188 
1189 /* delete_lines: delete a range of lines */
1190 int
delete_lines(int from,int to)1191 delete_lines(int from, int to)
1192 {
1193 	line_t *n, *p;
1194 
1195 	SPL1();
1196 	if (push_undo_stack(UDEL, from, to) == NULL) {
1197 		SPL0();
1198 		return ERR;
1199 	}
1200 	n = get_addressed_line_node(INC_MOD(to, addr_last));
1201 	p = get_addressed_line_node(from - 1);
1202 					/* this get_addressed_line_node last! */
1203 	if (isglobal)
1204 		unset_active_nodes(p->q_forw, n);
1205 	REQUE(p, n);
1206 	addr_last -= to - from + 1;
1207 	current_addr = from - 1;
1208 	modified = 1;
1209 	SPL0();
1210 	return 0;
1211 }
1212 
1213 
1214 /* display_lines: print a range of lines to stdout */
1215 int
display_lines(int from,int to,int gflag)1216 display_lines(int from, int to, int gflag)
1217 {
1218 	line_t *bp;
1219 	line_t *ep;
1220 	char *s;
1221 
1222 	if (!from) {
1223 		seterrmsg("invalid address");
1224 		return ERR;
1225 	}
1226 	ep = get_addressed_line_node(INC_MOD(to, addr_last));
1227 	bp = get_addressed_line_node(from);
1228 	for (; bp != ep; bp = bp->q_forw) {
1229 		if ((s = get_sbuf_line(bp)) == NULL)
1230 			return ERR;
1231 		if (put_tty_line(s, bp->len, current_addr = from++, gflag) < 0)
1232 			return ERR;
1233 	}
1234 	return 0;
1235 }
1236 
1237 
1238 #define MAXMARK 26			/* max number of marks */
1239 
1240 static line_t *mark[MAXMARK];		/* line markers */
1241 static int markno;			/* line marker count */
1242 
1243 /* mark_line_node: set a line node mark */
1244 static int
mark_line_node(line_t * lp,int n)1245 mark_line_node(line_t *lp, int n)
1246 {
1247 	if (!islower(n)) {
1248 		seterrmsg("invalid mark character");
1249 		return ERR;
1250 	} else if (mark[n - 'a'] == NULL)
1251 		markno++;
1252 	mark[n - 'a'] = lp;
1253 	return 0;
1254 }
1255 
1256 
1257 /* get_marked_node_addr: return address of a marked line */
1258 static int
get_marked_node_addr(int n)1259 get_marked_node_addr(int n)
1260 {
1261 	if (!islower(n)) {
1262 		seterrmsg("invalid mark character");
1263 		return ERR;
1264 	}
1265 	return get_line_node_addr(mark[n - 'a']);
1266 }
1267 
1268 
1269 /* unmark_line_node: clear line node mark */
1270 void
unmark_line_node(line_t * lp)1271 unmark_line_node(line_t *lp)
1272 {
1273 	int i;
1274 
1275 	for (i = 0; markno && i < MAXMARK; i++)
1276 		if (mark[i] == lp) {
1277 			mark[i] = NULL;
1278 			markno--;
1279 		}
1280 }
1281 
1282 
1283 /* dup_line_node: return a pointer to a copy of a line node */
1284 static line_t *
dup_line_node(line_t * lp)1285 dup_line_node(line_t *lp)
1286 {
1287 	line_t *np;
1288 
1289 	if ((np = malloc(sizeof(line_t))) == NULL) {
1290 		perror(NULL);
1291 		seterrmsg("out of memory");
1292 		return NULL;
1293 	}
1294 	np->seek = lp->seek;
1295 	np->len = lp->len;
1296 	return np;
1297 }
1298 
1299 
1300 /* has_trailing_escape:  return the parity of escapes preceding a character
1301    in a string */
1302 int
has_trailing_escape(char * s,char * t)1303 has_trailing_escape(char *s, char *t)
1304 {
1305     return (s == t || *(t - 1) != '\\') ? 0 : !has_trailing_escape(s, t - 1);
1306 }
1307 
1308 
1309 /* strip_escapes: return copy of escaped string of at most length PATH_MAX */
1310 char *
strip_escapes(char * s)1311 strip_escapes(char *s)
1312 {
1313 	static char *file = NULL;
1314 	static int filesz = 0;
1315 
1316 	int i = 0;
1317 
1318 	REALLOC(file, filesz, PATH_MAX, NULL);
1319 	/* assert: no trailing escape */
1320 	while ((file[i++] = (*s == '\\') ? *++s : *s) != '\0' &&
1321 	       i < PATH_MAX-1)
1322 		s++;
1323 	file[PATH_MAX-1] = '\0';
1324 	return file;
1325 }
1326 
1327 
1328 void
signal_hup(int signo)1329 signal_hup(int signo)
1330 {
1331 	sighup = 1;
1332 }
1333 
1334 
1335 void
signal_int(int signo)1336 signal_int(int signo)
1337 {
1338 	if (mutex)
1339 		sigint = 1;
1340 	else
1341 		handle_int(signo);	/* XXX quite unsafe */
1342 }
1343 
1344 
1345 void
handle_hup(void)1346 handle_hup(void)
1347 {
1348 	char hup[PATH_MAX];
1349 
1350 	signal(SIGHUP, SIG_IGN);
1351 	sighup = 0;
1352 	if (addr_last && write_file("ed.hup", "w", 1, addr_last) < 0 &&
1353 	    home != NULL && home[0] == '/') {
1354 		if (strlcpy(hup, home, sizeof(hup)) < sizeof(hup) &&
1355 		    strlcat(hup, "/ed.hup", sizeof(hup)) < sizeof(hup))
1356 			write_file(hup, "w", 1, addr_last);
1357 	}
1358 	exit(2);
1359 }
1360 
1361 
1362 void
handle_int(int signo)1363 handle_int(int signo)
1364 {
1365 	if (!sigactive)
1366 		_exit(1);
1367 	sigint = 0;
1368 	siglongjmp(env, -1);
1369 }
1370 
1371 
1372 void
handle_winch(int signo)1373 handle_winch(int signo)
1374 {
1375 	int save_errno = errno;
1376 	struct winsize ws;		/* window size structure */
1377 
1378 	if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == 0) {
1379 		if (ws.ws_row > 2)
1380 			rows = ws.ws_row - 2;
1381 		if (ws.ws_col > 8)
1382 			cols = ws.ws_col - 8;
1383 	}
1384 	errno = save_errno;
1385 }
1386