xref: /dragonfly/bin/ed/main.c (revision 92e10cbe8bcf053cad0b647bd29a30c52d3ed4f2)
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
main(volatile int argc,char ** volatile argv)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
extract_addr_range(void)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
next_addr(void)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
exec_command(void)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
check_addr_range(long n,long m)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
get_matching_node_addr(pattern_t * pat,int dir)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 *
get_filename(void)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
get_shell_command(void)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
append_lines(long n)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
join_lines(long from,long to)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
move_lines(long addr)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
copy_lines(long addr)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
delete_lines(long from,long to)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
display_lines(long from,long to,int gflag)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
mark_line_node(line_t * lp,int n)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
get_marked_node_addr(int n)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
unmark_line_node(line_t * lp)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 *
dup_line_node(line_t * lp)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
has_trailing_escape(char * s,char * t)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 *
strip_escapes(char * s)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
signal_hup(int signo)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
signal_int(int signo)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
handle_hup(int signo)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
handle_int(int signo)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
handle_winch(int signo)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
is_legal_filename(char * s)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 }
1400