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