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