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