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