This commit was manufactured by cvs2svn to create tag 'FreeBSD-release/1.0'.
[unix-history] / bin / ed / ed.c
CommitLineData
54a7a3ed
AM
1/* ed.c: This file contains the main control and user-interface routines
2 for the ed line editor. */
3/*-
4 * Copyright (c) 1993 The Regents of the University of California.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to Berkeley
8 * by Andrew Moore, Talke Studio.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by the University of
21 * California, Berkeley and its contributors.
22 * 4. Neither the name of the University nor the names of its contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * SUCH DAMAGE.
37 */
38/*-
39 * Kernighan/Plauger, "Software Tools in Pascal," (c) 1981 by
40 * Addison-Wesley Publishing Company, Inc. Reprinted with permission of
41 * the publisher.
42 */
43
44#ifndef lint
45char copyright1[] =
46"@(#) Copyright (c) 1993 The Regents of the University of California.\n\
47 All rights reserved.\n";
48char copyright2[] =
49"@(#) Kernighan/Plauger, Software Tools in Pascal, (c) 1981 by\n\
50 Addison-Wesley Publishing Company, Inc. Reprinted with permission of\n\
51 the publisher.\n";
52#endif /* not lint */
53
54#ifndef lint
55static char sccsid[] = "@(#)ed.c 5.5 (Berkeley) 3/28/93";
56#endif /* not lint */
57
58/*
59 * CREDITS
60 * The buf.c algorithm is attributed to Rodney Ruddock of
61 * the University of Guelph, Guelph, Ontario.
62 *
63 * The cbc.c encryption code is adapted from
64 * the bdes program by Matt Bishop of Dartmouth College,
65 * Hanover, NH.
66 *
67 * Addison-Wesley Publishing Company generously granted
68 * permission to distribute this program over Internet.
69 *
70 */
71
72#include <unistd.h>
73#include <stdio.h>
74#include <stdlib.h>
75#include <string.h>
76#include <ctype.h>
77#include <setjmp.h>
78#include <pwd.h>
79#include <sys/ioctl.h>
80
81#include "ed.h"
82
83#ifdef _POSIX_SOURCE
84sigjmp_buf env;
85#else
86jmp_buf env;
87#endif
88
89/* static buffers */
90char *shcmd; /* shell command buffer */
91int shcmdsz; /* shell command buffer size */
92int shcmdi; /* shell command buffer index */
93char *cvbuf; /* global command buffer */
94int cvbufsz; /* global command buffer size */
95char *lhbuf; /* lhs buffer */
96int lhbufsz; /* lhs buffer size */
97char *rhbuf; /* rhs buffer */
98int rhbufsz; /* rhs buffer size */
99int rhbufi; /* rhs buffer index */
100char *rbuf; /* regsub buffer */
101int rbufsz; /* regsub buffer size */
102char *sbuf; /* file i/o buffer */
103int sbufsz; /* file i/o buffer size */
104char *ibuf; /* ed command-line buffer */
105int ibufsz; /* ed command-line buffer size */
106char *ibufp; /* pointer to ed command-line buffer */
107
108/* global flags */
109int isbinary; /* if set, buffer contains ASCII NULs */
110int modified; /* if set, buffer modified since last write */
111int garrulous = 0; /* if set, print all error messages */
112int scripted = 0; /* if set, suppress diagnostics */
113int des = 0; /* if set, use crypt(3) for i/o */
114int mutex = 0; /* if set, signals set "sigflags" */
115int sigflags = 0; /* if set, signals received while mutex set */
116int sigactive = 0; /* if set, signal handlers are enabled */
117int red = 0; /* if set, restrict shell/directory access */
118
119char dfn[MAXFNAME + 1] = ""; /* default filename */
120long curln; /* current address */
121long lastln; /* last address */
122int lineno; /* script line number */
123char *prompt; /* command-line prompt */
124char *dps = "*"; /* default command-line prompt */
125
126char *usage = "usage: %s [-] [-sx] [-p string] [name]\n";
127
128extern char errmsg[];
129extern int optind;
130extern char *optarg;
131
132/* ed: line editor */
133main(argc, argv)
134 int argc;
135 char **argv;
136{
137 int c, n;
138 long status = 0;
139
140 red = (n = strlen(argv[0])) > 2 && argv[0][n - 3] == 'r';
141top:
142 while ((c = getopt(argc, argv, "p:sx")) != EOF)
143 switch(c) {
144 case 'p': /* set prompt */
145 prompt = optarg;
146 break;
147 case 's': /* run script */
148 scripted = 1;
149 break;
150 case 'x': /* use crypt */
151#ifdef DES
152 des = getkey();
153#else
154 fprintf(stderr, "crypt unavailable\n?\n");
155#endif
156 break;
157
158 default:
159 fprintf(stderr, usage, argv[0]);
160 exit(1);
161 }
162 argv += optind;
163 argc -= optind;
164 if (argc && **argv == '-') {
165 scripted = 1;
166 if (argc > 1) {
167 optind = 1;
168 goto top;
169 }
170 argv++;
171 argc--;
172 }
173 /* assert: reliable signals! */
174#ifdef SIGWINCH
175 dowinch(SIGWINCH);
176 if (isatty(0)) signal(SIGWINCH, dowinch);
177#endif
178 signal(SIGHUP, onhup);
179 signal(SIGQUIT, SIG_IGN);
180 signal(SIGINT, onintr);
181#ifdef _POSIX_SOURCE
182 if (status = sigsetjmp(env, 1))
183#else
184 if (status = setjmp(env))
185#endif
186 {
187 fputs("\n?\n", stderr);
188 sprintf(errmsg, "interrupt");
189 } else {
190 init_buf();
191 sigactive = 1; /* enable signal handlers */
192 if (argc && **argv && ckfn(*argv)) {
193 if (doread(0, *argv) < 0 && !isatty(0))
194 quit(2);
195 else if (**argv != '!')
196 strcpy(dfn, *argv);
197 } else if (argc) {
198 fputs("?\n", stderr);
199 if (**argv == '\0')
200 sprintf(errmsg, "invalid filename");
201 if (!isatty(0))
202 quit(2);
203 }
204 }
205 for (;;) {
206 if (status < 0 && garrulous)
207 fprintf(stderr, "%s\n", errmsg);
208 if (prompt) {
209 printf("%s", prompt);
210 fflush(stdout);
211 }
212 if ((n = getline()) < 0) {
213 status = ERR;
214 continue;
215 } else if (n == 0) {
216 if (modified && !scripted) {
217 fputs("?\n", stderr);
218 sprintf(errmsg, "warning: file modified");
219 if (!isatty(0)) {
220 fprintf(stderr, garrulous ? "script, line %d: %s\n"
221 : "", lineno, errmsg);
222 quit(2);
223 }
224 clearerr(stdin);
225 modified = 0;
226 status = EMOD;
227 continue;
228 } else
229 quit(0);
230 } else if (ibuf[n - 1] != '\n') {
231 /* discard line */
232 sprintf(errmsg, "unexpected end-of-file");
233 clearerr(stdin);
234 status = ERR;
235 continue;
236 }
237 if ((n = getlist()) >= 0 && (status = ckglob()) != 0) {
238 if (status > 0 && (status = doglob(status)) >= 0) {
239 curln = status;
240 continue;
241 }
242 } else if ((status = n) >= 0 && (status = docmd(0)) >= 0) {
243 if (!status || status
244 && (status = doprint(curln, curln, status)) >= 0)
245 continue;
246 }
247 switch (status) {
248 case EOF:
249 quit(0);
250 case EMOD:
251 modified = 0;
252 fputs("?\n", stderr); /* give warning */
253 sprintf(errmsg, "warning: file modified");
254 if (!isatty(0)) {
255 fprintf(stderr, garrulous ? "script, line %d: %s\n" : "", lineno, errmsg);
256 quit(2);
257 }
258 break;
259 case FATAL:
260 if (!isatty(0))
261 fprintf(stderr, garrulous ? "script, line %d: %s\n" : "", lineno, errmsg);
262 else
263 fprintf(stderr, garrulous ? "%s\n" : "", errmsg);
264 quit(3);
265 default:
266 fputs("?\n", stderr);
267 if (!isatty(0)) {
268 fprintf(stderr, garrulous ? "script, line %d: %s\n" : "", lineno, errmsg);
269 quit(2);
270 }
271 break;
272 }
273 }
274 /*NOTREACHED*/
275}
276
277
278long line1, line2, nlines;
279
280/* getlist: get line numbers from the command buffer until an illegal
281 address is seen. return range status */
282getlist()
283{
284 long num;
285
286 nlines = line2 = 0;
287 while ((num = getone()) >= 0) {
288 line1 = line2;
289 line2 = num;
290 nlines++;
291 if (*ibufp != ',' && *ibufp != ';')
292 break;
293 else if (*ibufp++ == ';')
294 curln = num;
295 }
296 nlines = min(nlines, 2);
297 if (nlines == 0)
298 line2 = curln;
299 if (nlines <= 1)
300 line1 = line2;
301 return (num == ERR) ? ERR : nlines;
302}
303
304
305/* getone: return the next line number in the command buffer */
306long
307getone()
308{
309 int c;
310 long i, num;
311
312 if ((num = getnum(1)) < 0)
313 return num;
314 for (;;) {
315 c = isspace(*ibufp);
316 skipblanks();
317 c = c && isdigit(*ibufp);
318 if (!c && *ibufp != '+' && *ibufp != '-' && *ibufp != '^')
319 break;
320 c = c ? '+' : *ibufp++;
321 if ((i = getnum(0)) < 0) {
322 sprintf(errmsg, "invalid address");
323 return i;
324 }
325 if (c == '+')
326 num += i;
327 else num -= i;
328 }
329 if (num > lastln || num < 0) {
330 sprintf(errmsg, "invalid address");
331 return ERR;
332 }
333 return num;
334}
335
336
54a7a3ed
AM
337/* getnum: return a relative line number from the command buffer */
338long
339getnum(first)
340 int first;
341{
342 pattern_t *pat;
343 char c;
344
345 skipblanks();
346 if (isdigit(*ibufp))
347 return strtol(ibufp, &ibufp, 10);
348 switch(c = *ibufp) {
349 case '.':
350 ibufp++;
351 return first ? curln : ERR;
352 case '$':
353 ibufp++;
354 return first ? lastln : ERR;
355 case '/':
356 case '?':
357 if ((pat = optpat()) == NULL)
358 return ERR;
359 else if (*ibufp == c)
360 ibufp++;
361 return first ? patscan(pat, (c == '/') ? 1 : 0) : ERR;
362 case '^':
363 case '-':
364 case '+':
365 return first ? curln : 1;
366 case '\'':
367 ibufp++;
78ed81a3 368 return first ? getmark(*ibufp++) : ERR;
54a7a3ed
AM
369 case '%':
370 case ',':
371 case ';':
372 if (first) {
373 ibufp++;
374 line2 = (c == ';') ? curln : 1;
375 nlines++;
376 return lastln;
377 }
378 return 1;
379 default:
380 return first ? EOF : 1;
381 }
382}
383
384
385/* gflags */
386#define GLB 001 /* global command */
387#define GPR 002 /* print after command */
388#define GLS 004 /* list after command */
389#define GNP 010 /* enumerate after command */
390#define GSG 020 /* global substitute */
391
392
393/* VRFYCMD: verify the command suffix in the command buffer */
394#define VRFYCMD() { \
395 int done = 0; \
396 do { \
397 switch(*ibufp) { \
398 case 'p': \
399 gflag |= GPR, ibufp++; \
400 break; \
401 case 'l': \
402 gflag |= GLS, ibufp++; \
403 break; \
404 case 'n': \
405 gflag |= GNP, ibufp++; \
406 break; \
407 default: \
408 done++; \
409 } \
410 } while (!done); \
411 if (*ibufp++ != '\n') { \
412 sprintf(errmsg, "invalid command suffix"); \
413 return ERR; \
414 } \
415}
416
417
418/* ckglob: set lines matching a pattern in the command buffer; return
419 global status */
420ckglob()
421{
422 pattern_t *pat;
423 char c, delim;
424 char *s;
425 int nomatch;
426 long n;
427 line_t *lp;
428 int gflag = 0; /* print suffix of interactive cmd */
429
430 if ((c = *ibufp) == 'V' || c == 'G')
431 gflag = GLB;
432 else if (c != 'g' && c != 'v')
433 return 0;
434 if (ckrange(1, lastln) < 0)
435 return ERR;
436 else if ((delim = *++ibufp) == ' ' || delim == '\n') {
437 sprintf(errmsg, "invalid pattern delimiter");
438 return ERR;
439 } else if ((pat = optpat()) == NULL)
440 return ERR;
441 else if (*ibufp == delim)
442 ibufp++;
443 if (gflag)
444 VRFYCMD(); /* get print suffix */
445 for (lp = getlp(n = 1); n <= lastln; n++, lp = lp->next) {
446 if ((s = gettxt(lp)) == NULL)
447 return ERR;
448 lp->len &= ~ACTV; /* zero ACTV bit */
449 if (isbinary)
450 s = nultonl(s, lp->len & ~ACTV);
451 if (line1 <= n && n <= line2
452 && (!(nomatch = regexec(pat, s, 0, NULL, 0))
453 && (c == 'g' || c == 'G')
454 || nomatch && (c == 'v' || c == 'V')))
455 lp->len |= ACTV;
456 }
457 return gflag | GSG;
458}
459
460
461/* doglob: apply command list in the command buffer to the active
462 lines in a range; return command status */
463long
464doglob(gflag)
465 int gflag;
466{
467 static char *ocmd = NULL;
468 static int ocmdsz = 0;
469
470 line_t *lp = NULL;
471 long lc;
472 int status;
473 int n;
474 int interact = gflag & ~GSG; /* GLB & gflag ? */
475 char *cmd = NULL;
476
477#ifdef BACKWARDS
478 if (!interact)
479 if (!strcmp(ibufp, "\n"))
480 cmd = "p\n"; /* null cmd-list == `p' */
481 else if ((cmd = getcmdv(&n, 0)) == NULL)
482 return ERR;
483#else
484 if (!interact && (cmd = getcmdv(&n, 0)) == NULL)
485 return ERR;
486#endif
487 ureset();
488 for (;;) {
489 for (lp = getlp(lc = 1); lc <= lastln; lc++, lp = lp->next)
490 if (lp->len & ACTV) /* active line */
491 break;
492 if (lc > lastln)
493 break;
494 lp->len ^= ACTV; /* zero ACTV bit */
495 curln = lc;
496 if (interact) {
497 /* print curln and get a command in global syntax */
498 if (doprint(curln, curln, 0) < 0)
499 return ERR;
500 while ((n = getline()) > 0
501 && ibuf[n - 1] != '\n')
502 clearerr(stdin);
503 if (n < 0)
504 return ERR;
505 else if (n == 0) {
506 sprintf(errmsg, "unexpected end-of-file");
507 return ERR;
508 } else if (n == 1 && !strcmp(ibuf, "\n"))
509 continue;
510 else if (n == 2 && !strcmp(ibuf, "&\n")) {
511 if (cmd == NULL) {
512 sprintf(errmsg, "no previous command");
513 return ERR;
514 } else cmd = ocmd;
515 } else if ((cmd = getcmdv(&n, 0)) == NULL)
516 return ERR;
517 else {
518 CKBUF(ocmd, ocmdsz, n + 1, ERR);
519 memcpy(ocmd, cmd, n + 1);
520 cmd = ocmd;
521 }
522
523 }
524 ibufp = cmd;
525 for (; *ibufp;)
526 if ((status = getlist()) < 0
527 || (status = docmd(1)) < 0
528 || (status > 0
529 && (status = doprint(curln, curln, status)) < 0))
530 return status;
531 }
532 return ((interact & ~GLB ) && doprint(curln, curln, interact) < 0) ? ERR : curln;
533}
534
535
536#ifdef BACKWARDS
537/* GETLINE3: get a legal address from the command buffer */
538#define GETLINE3(num) \
539{ \
540 long ol1, ol2; \
541\
542 ol1 = line1, ol2 = line2; \
543 if (getlist() < 0) \
544 return ERR; \
545 else if (nlines == 0) { \
546 sprintf(errmsg, "destination expected"); \
547 return ERR; \
548 } else if (line2 < 0 || lastln < line2) { \
549 sprintf(errmsg, "invalid address"); \
550 return ERR; \
551 } \
552 num = line2; \
553 line1 = ol1, line2 = ol2; \
554}
555#else /* BACKWARDS */
556/* GETLINE3: get a legal address from the command buffer */
557#define GETLINE3(num) \
558{ \
559 long ol1, ol2; \
560\
561 ol1 = line1, ol2 = line2; \
562 if (getlist() < 0) \
563 return ERR; \
564 if (line2 < 0 || lastln < line2) { \
565 sprintf(errmsg, "invalid address"); \
566 return ERR; \
567 } \
568 num = line2; \
569 line1 = ol1, line2 = ol2; \
570}
571#endif
572
573/* sgflags */
574#define SGG 001 /* complement previous global substitute suffix */
575#define SGP 002 /* complement previous print suffix */
576#define SGR 004 /* use last regex instead of last pat */
577#define SGF 010 /* newline found */
578
579long ucurln = -1; /* if >= 0, undo enabled */
580long ulastln = -1; /* if >= 0, undo enabled */
54a7a3ed
AM
581int patlock = 0; /* if set, pattern not released by optpat() */
582
583long rows = 22; /* scroll length: ws_row - 2 */
584
585/* docmd: execute the next command in command buffer; return print
586 request, if any */
587docmd(glob)
588 int glob;
589{
590 static pattern_t *pat = NULL;
591 static int sgflag = 0;
592
593 pattern_t *tpat;
594 char *fnp;
595 int gflag = 0;
596 int sflags = 0;
597 long num = 0;
598 int n = 0;
599 int c;
600
601 skipblanks();
602 switch(c = *ibufp++) {
603 case 'a':
604 VRFYCMD();
605 if (!glob) ureset();
606 if (append(line2, glob) < 0)
607 return ERR;
608 break;
609 case 'c':
610 if (ckrange(curln, curln) < 0)
611 return ERR;
612 VRFYCMD();
613 if (!glob) ureset();
614 if (lndelete(line1, line2) < 0 || append(curln, glob) < 0)
615 return ERR;
616 break;
617 case 'd':
618 if (ckrange(curln, curln) < 0)
619 return ERR;
620 VRFYCMD();
621 if (!glob) ureset();
622 if (lndelete(line1, line2) < 0)
623 return ERR;
624 else if (nextln(curln, lastln) != 0)
625 curln = nextln(curln, lastln);
626 modified = 1;
627 break;
628 case 'e':
629 if (modified && !scripted)
630 return EMOD;
631 /* fall through */
632 case 'E':
633 if (nlines > 0) {
634 sprintf(errmsg, "unexpected address");
635 return ERR;
636 } else if (!isspace(*ibufp)) {
637 sprintf(errmsg, "unexpected command suffix");
638 return ERR;
639 } else if ((fnp = getfn()) == NULL)
640 return ERR;
641 VRFYCMD();
78ed81a3 642 if (lndelete(1, lastln) < 0)
643 return ERR;
54a7a3ed
AM
644 ureset();
645 if (sbclose() < 0)
646 return ERR;
647 else if (sbopen() < 0)
648 return FATAL;
649 if (*fnp && *fnp != '!') strcpy(dfn, fnp);
650#ifdef BACKWARDS
651 if (*fnp == '\0' && *dfn == '\0') {
652 sprintf(errmsg, "no current filename");
653 return ERR;
654 }
655#endif
656 if (doread(0, *fnp ? fnp : dfn) < 0)
657 return ERR;
658 ureset();
659 modified = 0;
660 ucurln = ulastln = -1;
661 break;
662 case 'f':
663 if (nlines > 0) {
664 sprintf(errmsg, "unexpected address");
665 return ERR;
666 } else if (!isspace(*ibufp)) {
667 sprintf(errmsg, "unexpected command suffix");
668 return ERR;
669 } else if ((fnp = getfn()) == NULL)
670 return ERR;
671 else if (*fnp == '!') {
672 sprintf(errmsg, "invalid redirection");
673 return ERR;
674 }
675 VRFYCMD();
676 if (*fnp) strcpy(dfn, fnp);
677 printf("%s\n", esctos(dfn));
678 break;
679 case 'g':
680 case 'G':
681 sprintf(errmsg, "cannot nest global commands");
682 return ERR;
683 case 'h':
684 if (nlines > 0) {
685 sprintf(errmsg, "unexpected address");
686 return ERR;
687 }
688 VRFYCMD();
689 if (*errmsg) fprintf(stderr, "%s\n", errmsg);
690 break;
691 case 'H':
692 if (nlines > 0) {
693 sprintf(errmsg, "unexpected address");
694 return ERR;
695 }
696 VRFYCMD();
697 if ((garrulous = 1 - garrulous) && *errmsg)
698 fprintf(stderr, "%s\n", errmsg);
699 break;
700 case 'i':
701 if (line2 == 0) {
702 sprintf(errmsg, "invalid address");
703 return ERR;
704 }
705 VRFYCMD();
706 if (!glob) ureset();
707 if (append(prevln(line2, lastln), glob) < 0)
708 return ERR;
709 break;
710 case 'j':
711 if (ckrange(curln, curln + 1) < 0)
712 return ERR;
713 VRFYCMD();
714 if (!glob) ureset();
715 if (line1 != line2 && join(line1, line2) < 0)
716 return ERR;
717 break;
718 case 'k':
719 c = *ibufp++;
720 if (line2 == 0) {
721 sprintf(errmsg, "invalid address");
722 return ERR;
78ed81a3 723 }
54a7a3ed 724 VRFYCMD();
78ed81a3 725 if (putmark(c, getlp(line2)) < 0)
726 return ERR;
54a7a3ed
AM
727 break;
728 case 'l':
729 if (ckrange(curln, curln) < 0)
730 return ERR;
731 VRFYCMD();
732 if (doprint(line1, line2, gflag | GLS) < 0)
733 return ERR;
734 gflag = 0;
735 break;
736 case 'm':
737 if (ckrange(curln, curln) < 0)
738 return ERR;
739 GETLINE3(num);
740 if (line1 <= num && num < line2) {
741 sprintf(errmsg, "invalid destination");
742 return ERR;
743 }
744 VRFYCMD();
745 if (!glob) ureset();
78ed81a3 746 if (move(num, glob) < 0)
54a7a3ed
AM
747 return ERR;
748 else
749 modified = 1;
750 break;
751 case 'n':
752 if (ckrange(curln, curln) < 0)
753 return ERR;
754 VRFYCMD();
755 if (doprint(line1, line2, gflag | GNP) < 0)
756 return ERR;
757 gflag = 0;
758 break;
759 case 'p':
760 if (ckrange(curln, curln) < 0)
761 return ERR;
762 VRFYCMD();
763 if (doprint(line1, line2, gflag | GPR) < 0)
764 return ERR;
765 gflag = 0;
766 break;
767 case 'P':
768 if (nlines > 0) {
769 sprintf(errmsg, "unexpected address");
770 return ERR;
771 }
772 VRFYCMD();
773 prompt = prompt ? NULL : optarg ? optarg : dps;
774 break;
775 case 'q':
776 case 'Q':
777 if (nlines > 0) {
778 sprintf(errmsg, "unexpected address");
779 return ERR;
780 }
781 VRFYCMD();
782 gflag = (modified && !scripted && c == 'q') ? EMOD : EOF;
783 break;
784 case 'r':
785 if (!isspace(*ibufp)) {
786 sprintf(errmsg, "unexpected command suffix");
787 return ERR;
788 } else if (nlines == 0)
789 line2 = lastln;
790 if ((fnp = getfn()) == NULL)
791 return ERR;
792 VRFYCMD();
793 if (!glob) ureset();
794 if (*dfn == '\0' && *fnp != '!') strcpy(dfn, fnp);
795#ifdef BACKWARDS
796 if (*fnp == '\0' && *dfn == '\0') {
797 sprintf(errmsg, "no current filename");
798 return ERR;
799 }
800#endif
801 if ((num = doread(line2, *fnp ? fnp : dfn)) < 0)
802 return ERR;
803 else if (num && num != lastln)
804 modified = 1;
805 break;
806 case 's':
807 do {
808 switch(*ibufp) {
809 case '\n':
810 sflags |=SGF;
811 break;
812 case 'g':
813 sflags |= SGG;
814 ibufp++;
815 break;
816 case 'p':
817 sflags |= SGP;
818 ibufp++;
819 break;
820 case 'r':
821 sflags |= SGR;
822 ibufp++;
823 break;
824 default:
825 if (sflags) {
826 sprintf(errmsg, "invalid command suffix");
827 return ERR;
828 }
829 }
830 } while (sflags && *ibufp != '\n');
831 if (sflags && !pat) {
832 sprintf(errmsg, "no previous substitution");
833 return ERR;
834 } else if (!(sflags & SGF))
835 sgflag &= 0xff;
78ed81a3 836 if (*ibufp != '\n' && *(ibufp + 1) == '\n') {
837 sprintf(errmsg, "invalid pattern delimiter");
838 return ERR;
839 }
54a7a3ed
AM
840 tpat = pat;
841 spl1();
842 if ((!sflags || (sflags & SGR))
843 && (tpat = optpat()) == NULL)
844 return ERR;
845 else if (tpat != pat) {
846 if (pat) {
847 regfree(pat);
848 free(pat);
849 }
850 pat = tpat;
851 patlock = 1; /* reserve pattern */
852 } else if (pat == NULL) {
853 /* NOTREACHED */
854 sprintf(errmsg, "no previous substitution");
855 return ERR;
856 }
857 spl0();
858 if (!sflags && (sgflag = getrhs(glob)) < 0)
859 return ERR;
860 else if (glob)
861 sgflag |= GLB;
862 else
863 sgflag &= ~GLB;
864 if (sflags & SGG)
865 sgflag ^= GSG;
866 if (sflags & SGP)
867 sgflag ^= GPR, sgflag &= ~(GLS | GNP);
868 do {
869 switch(*ibufp) {
870 case 'p':
871 sgflag |= GPR, ibufp++;
872 break;
873 case 'l':
874 sgflag |= GLS, ibufp++;
875 break;
876 case 'n':
877 sgflag |= GNP, ibufp++;
878 break;
879 default:
880 n++;
881 }
882 } while (!n);
883 if (ckrange(curln, curln) < 0)
884 return ERR;
885 VRFYCMD();
886 if (!glob) ureset();
887 if ((n = subst(pat, sgflag)) < 0)
888 return ERR;
889 else if (n)
890 modified = 1;
891 break;
892 case 't':
893 if (ckrange(curln, curln) < 0)
894 return ERR;
895 GETLINE3(num);
896 VRFYCMD();
897 if (!glob) ureset();
898 if (transfer(num) < 0)
899 return ERR;
900 modified = 1;
901 break;
902 case 'u':
903 if (nlines > 0) {
904 sprintf(errmsg, "unexpected address");
905 return ERR;
906 }
907 VRFYCMD();
78ed81a3 908 if (undo(glob) < 0)
54a7a3ed
AM
909 return ERR;
910 break;
911 case 'v':
912 case 'V':
913 sprintf(errmsg, "cannot nest global commands");
914 return ERR;
915 case 'w':
916 case 'W':
917 if ((n = *ibufp) == 'q' || n == 'Q') {
918 gflag = EOF;
919 ibufp++;
920 }
921 if (!isspace(*ibufp)) {
922 sprintf(errmsg, "unexpected command suffix");
923 return ERR;
924 } else if ((fnp = getfn()) == NULL)
925 return ERR;
926 if (nlines == 0 && !lastln)
927 line1 = line2 = 0;
928 else if (ckrange(1, lastln) < 0)
929 return ERR;
930 VRFYCMD();
931 if (*dfn == '\0' && *fnp != '!') strcpy(dfn, fnp);
932#ifdef BACKWARDS
933 if (*fnp == '\0' && *dfn == '\0') {
934 sprintf(errmsg, "no current filename");
935 return ERR;
936 }
937#endif
938 if ((num = dowrite(line1, line2, *fnp ? fnp : dfn, (c == 'W') ? "a" : "w")) < 0)
939 return ERR;
940 else if (num == lastln)
941 modified = 0;
942 else if (modified && !scripted && n == 'q')
943 gflag = EMOD;
944 break;
945 case 'x':
946 if (nlines > 0) {
947 sprintf(errmsg, "unexpected address");
948 return ERR;
949 }
950 VRFYCMD();
951#ifdef DES
952 des = getkey();
953#else
954 sprintf(errmsg, "crypt unavailable");
955 return ERR;
956#endif
957 break;
958 case 'z':
959#ifdef BACKWARDS
960 if (ckrange(line1 = 1, curln + 1) < 0)
961#else
962 if (ckrange(line1 = 1, curln + !glob) < 0)
963#endif
964 return ERR;
965 else if ('0' < *ibufp && *ibufp <= '9')
966 rows = strtol(ibufp, &ibufp, 10);
967 VRFYCMD();
968 if (doprint(line2, min(lastln, line2 + rows - 1), gflag) < 0)
969 return ERR;
970 gflag = 0;
971 break;
972 case '=':
973 VRFYCMD();
974 printf("%d\n", nlines ? line2 : lastln);
975 break;
976 case '!':
977#ifndef VI_BANG
978 if (nlines > 0) {
979 sprintf(errmsg, "unexpected address");
980 return ERR;
981 }
982#endif
983 if ((sflags = getshcmd()) < 0)
984 return ERR;
985 VRFYCMD();
986 if (sflags) printf("%s\n", shcmd + 1);
987#ifdef VI_BANG
988 if (nlines == 0) {
989#endif
990 system(shcmd + 1);
991 if (!scripted) printf("!\n");
992 break;
993#ifdef VI_BANG
994 }
995 if (!lastln && !line1 && !line2) {
996 if (!glob) ureset();
997 } else if (ckrange(curln, curln) < 0)
998 return ERR;
999 else {
1000 if (!glob) ureset();
1001 if (lndelete(line1, line2) < 0)
1002 return ERR;
1003 line2 = curln;
1004 modified = 1;
1005 }
1006 if ((num = doread(line2, shcmd)) < 0)
1007 return ERR;
1008 else if (num && num != lastln)
1009 modified = 1;
1010 break;
1011#endif
1012 case '\n':
1013#ifdef BACKWARDS
1014 if (ckrange(line1 = 1, curln + 1) < 0
1015#else
1016 if (ckrange(line1 = 1, curln + !glob) < 0
1017#endif
1018 || doprint(line2, line2, 0) < 0)
1019 return ERR;
1020 break;
1021 default:
1022 sprintf(errmsg, "unknown command");
1023 return ERR;
1024 }
1025 return gflag;
1026}
1027
1028
1029/* ckrange: return status of line number range check */
1030ckrange(def1, def2)
1031 long def1, def2;
1032{
1033 if (nlines == 0) {
1034 line1 = def1;
1035 line2 = def2;
1036 }
1037 if (line1 > line2 || 1 > line1 || line2 > lastln) {
1038 sprintf(errmsg, "invalid address");
1039 return ERR;
1040 }
1041 return 0;
1042}
1043
1044
1045/* patscan: return the number of the next line matching a pattern in a
1046 given direction. wrap around begin/end of line queue if necessary */
1047long
1048patscan(pat, dir)
1049 pattern_t *pat;
1050 int dir;
1051{
1052 char *s;
1053 long n = curln;
1054 line_t *lp;
1055
1056 do {
1057 if (n = dir ? nextln(n, lastln) : prevln(n, lastln)) {
1058 if ((s = gettxt(lp = getlp(n))) == NULL)
1059 return ERR;
1060 if (isbinary)
1061 s = nultonl(s, lp->len & ~ACTV);
1062 if (!regexec(pat, s, 0, NULL, 0))
1063 return n;
1064 }
1065 } while (n != curln);
1066 sprintf(errmsg, "no match");
1067 return ERR;
1068}
1069
1070
1071/* getfn: return pointer to copy of filename in the command buffer */
1072char *
1073getfn()
1074{
1075 static char *file = NULL;
1076 static int filesz = 0;
1077
1078 int n;
1079
1080 if (*ibufp != '\n') {
1081 skipblanks();
1082 if (*ibufp == '\n') {
1083 sprintf(errmsg, "invalid filename");
1084 return NULL;
1085 } else if ((ibufp = getcmdv(&n, 1)) == NULL)
1086 return NULL;
1087#ifdef VI_BANG
1088 else if (*ibufp == '!') {
1089 ibufp++;
1090 if ((n = getshcmd()) < 0)
1091 return NULL;
1092 if (n) printf("%s\n", shcmd + 1);
1093 return shcmd;
1094 }
1095#endif
1096 else if (n - 1 > MAXFNAME) {
1097 sprintf(errmsg, "filename too long");
1098 return NULL;
1099 }
1100 }
1101#ifndef BACKWARDS
1102 else if (*dfn == '\0') {
1103 sprintf(errmsg, "no current filename");
1104 return NULL;
1105 }
1106#endif
1107 CKBUF(file, filesz, MAXFNAME + 1, NULL);
1108 for (n = 0; *ibufp != '\n';)
1109 file[n++] = *ibufp++;
1110 file[n] = '\0';
1111 return ckfn(file);
1112}
1113
1114
1115/* getrhs: extract substitution template from the command buffer */
1116getrhs(glob)
1117 int glob;
1118{
1119 char delim;
1120
1121 if ((delim = *ibufp) == '\n') {
1122 rhbufi = 0;
1123 return GPR;
1124 } else if (makesub(glob) == NULL)
1125 return ERR;
1126 else if (*ibufp == '\n')
1127 return GPR;
1128 else if (*ibufp == delim)
1129 ibufp++;
1130 if ('1' <= *ibufp && *ibufp <= '9')
1131 return (int) strtol(ibufp, &ibufp, 10) << 8;
1132 else if (*ibufp == 'g') {
1133 ibufp++;
1134 return GSG;
1135 }
1136 return 0;
1137}
1138
1139
1140/* makesub: return pointer to copy of substitution template in the command
1141 buffer */
1142char *
1143makesub(glob)
1144 int glob;
1145{
1146 int n = 0;
1147 int i = 0;
1148 char delim = *ibufp++;
1149 char c;
1150
1151 if (*ibufp == '%' && *(ibufp + 1) == delim) {
1152 ibufp++;
1153 if (!rhbuf) sprintf(errmsg, "no previous substitution");
1154 return rhbuf;
1155 }
1156 while (*ibufp != delim) {
1157 CKBUF(rhbuf, rhbufsz, i + 2, NULL);
1158 if ((c = rhbuf[i++] = *ibufp++) == '\n' && *ibufp == '\0') {
1159 i--, ibufp--;
1160 break;
1161 } else if (c != '\\')
1162 ;
1163 else if ((rhbuf[i++] = *ibufp++) != '\n')
1164 ;
1165 else if (!glob) {
1166 while ((n = getline()) == 0
1167 || n > 0 && ibuf[n - 1] != '\n')
1168 clearerr(stdin);
1169 if (n < 0)
1170 return NULL;
1171 } else
1172 /*NOTREACHED*/
1173 ;
1174 }
1175 CKBUF(rhbuf, rhbufsz, i + 1, NULL);
1176 rhbuf[rhbufi = i] = '\0';
1177 return rhbuf;
1178}
1179
1180
1181/* getshcmd: read a shell command up a maximum size from stdin; return
1182 substitution status */
1183int
1184getshcmd()
1185{
1186 static char *buf = NULL;
1187 static int n = 0;
1188
1189 char *s; /* substitution char pointer */
1190 int i = 0;
1191 int j = 0;
1192
1193 if (red) {
1194 sprintf(errmsg, "shell access restricted");
1195 return ERR;
1196 } else if ((s = ibufp = getcmdv(&j, 1)) == NULL)
1197 return ERR;
1198 CKBUF(buf, n, j + 1, ERR);
1199 buf[i++] = '!'; /* prefix command w/ bang */
1200 while (*ibufp != '\n')
1201 switch (*ibufp) {
1202 default:
1203 CKBUF(buf, n, i + 2, ERR);
1204 buf[i++] = *ibufp;
1205 if (*ibufp++ == '\\')
1206 buf[i++] = *ibufp++;
1207 break;
1208 case '!':
1209 if (s != ibufp) {
1210 CKBUF(buf, n, i + 1, ERR);
1211 buf[i++] = *ibufp++;
1212 }
1213#ifdef BACKWARDS
1214 else if (shcmd == NULL || *(shcmd + 1) == '\0')
1215#else
1216 else if (shcmd == NULL)
1217#endif
1218 {
1219 sprintf(errmsg, "no previous command");
1220 return ERR;
1221 } else {
1222 CKBUF(buf, n, i + shcmdi, ERR);
1223 for (s = shcmd + 1; s < shcmd + shcmdi;)
1224 buf[i++] = *s++;
1225 s = ibufp++;
1226 }
1227 break;
1228 case '%':
1229 if (*dfn == '\0') {
1230 sprintf(errmsg, "no current filename");
1231 return ERR;
1232 }
1233 j = strlen(s = esctos(dfn));
1234 CKBUF(buf, n, i + j, ERR);
1235 while (j--)
1236 buf[i++] = *s++;
1237 s = ibufp++;
1238 break;
1239 }
1240 CKBUF(shcmd, shcmdsz, i + 1, ERR);
1241 memcpy(shcmd, buf, i);
1242 shcmd[shcmdi = i] = '\0';
1243 return *s == '!' || *s == '%';
1244}
1245
1246
1247/* append: insert text from stdin to after line n; stop when either a
1248 single period is read or EOF; return status */
1249append(n, glob)
1250 long n;
1251 int glob;
1252{
1253 int l;
1254 char *lp = ibuf;
1255 char *eot;
1256 undo_t *up = NULL;
1257
1258 for (curln = n;;) {
1259 if (!glob) {
1260 if ((l = getline()) < 0)
1261 return ERR;
1262 else if (l == 0 || ibuf[l - 1] != '\n') {
1263 clearerr(stdin);
1264 return l ? EOF : 0;
1265 }
1266 lp = ibuf;
1267 } else if (*(lp = ibufp) == '\0')
1268 return 0;
1269 else {
1270 while (*ibufp++ != '\n')
1271 ;
1272 l = ibufp - lp;
1273 }
1274 if (l == 2 && lp[0] == '.' && lp[1] == '\n') {
1275 return 0;
1276 }
1277 eot = lp + l;
1278 spl1();
1279 do {
1280 if ((lp = puttxt(lp)) == NULL) {
1281 spl0();
1282 return ERR;
1283 } else if (up)
1284 up->t = getlp(curln);
1285 else if ((up = upush(UADD, curln, curln)) == NULL) {
1286 spl0();
1287 return ERR;
1288 }
1289 } while (lp != eot);
1290 spl0();
1291 modified = 1;
1292 }
1293}
1294
1295
54a7a3ed
AM
1296/* subst: change all text matching a pattern in a range of lines according to
1297 a substitution template; return status */
1298subst(pat, gflag)
1299 pattern_t *pat;
1300 int gflag;
1301{
1302 undo_t *up;
1303 char *txt;
1304 char *eot;
1305 long lc;
1306 int nsubs = 0;
1307 line_t *lp;
1308 int len;
1309
1310 curln = prevln(line1, lastln);
1311 for (lc = 0; lc <= line2 - line1; lc++) {
1312 lp = getlp(curln = nextln(curln, lastln));
1313 if ((len = regsub(pat, lp, gflag)) < 0)
1314 return ERR;
1315 else if (len) {
1316 up = NULL;
78ed81a3 1317 if (lndelete(curln, curln) < 0)
1318 return ERR;
54a7a3ed
AM
1319 txt = rbuf;
1320 eot = rbuf + len;
1321 spl1();
1322 do {
1323 if ((txt = puttxt(txt)) == NULL) {
1324 spl0();
1325 return ERR;
1326 } else if (up)
1327 up->t = getlp(curln);
1328 else if ((up = upush(UADD, curln, curln)) == NULL) {
1329 spl0();
1330 return ERR;
1331 }
1332 } while (txt != eot);
1333 spl0();
1334 nsubs++;
1335 }
1336 }
1337 if (nsubs == 0 && !(gflag & GLB)) {
1338 sprintf(errmsg, "no match");
1339 return ERR;
1340 } else if ((gflag & (GPR | GLS | GNP))
1341 && doprint(curln, curln, gflag) < 0)
1342 return ERR;
1343 return 1;
1344}
54a7a3ed
AM
1345
1346
1347/* regsub: replace text matched by a pattern according to a substitution
1348 template; return pointer to the modified text */
1349regsub(pat, lp, gflag)
1350 pattern_t *pat;
1351 line_t *lp;
1352 int gflag;
1353{
1354 int off = 0;
1355 int kth = gflag >> 8; /* substitute kth match only */
1356 int chngd = 0;
1357 int matchno = 0;
1358 int len;
1359 int i = 0;
1360 regmatch_t rm[SE_MAX];
1361 char *txt;
1362 char *eot;
1363
1364 if ((txt = gettxt(lp)) == NULL)
1365 return ERR;
1366 len = lp->len & ~ACTV;
1367 eot = txt + len;
1368 if (isbinary) txt = nultonl(txt, len);
1369 if (!regexec(pat, txt, SE_MAX, rm, 0)) {
1370 do {
1371 if (!kth || kth == ++matchno) {
1372 chngd++;
1373 i = rm[0].rm_so;
1374 CKBUF(rbuf, rbufsz, off + i, ERR);
1375 if (isbinary) txt = nltonul(txt, rm[0].rm_eo);
1376 memcpy(rbuf + off, txt, i);
1377 if ((off = catsub(txt, rm, off += i)) < 0)
1378 return ERR;
1379 } else {
1380 i = rm[0].rm_eo;
1381 CKBUF(rbuf, rbufsz, off + i, ERR);
1382 if (isbinary) txt = nltonul(txt, i);
1383 memcpy(rbuf + off, txt, i);
1384 off += i;
1385 }
1386 txt += rm[0].rm_eo;
1387 } while (*txt && (!chngd || (gflag & GSG) && rm[0].rm_eo)
1388 && !regexec(pat, txt, SE_MAX, rm, REG_NOTBOL));
1389 i = eot - txt;
1390 CKBUF(rbuf, rbufsz, off + i + 2, ERR);
1391 if (i > 0 && !rm[0].rm_eo && (gflag & GSG)) {
1392 sprintf(errmsg, "infinite substitution loop");
1393 return ERR;
1394 }
1395 if (isbinary) txt = nltonul(txt, i);
1396 memcpy(rbuf + off, txt, i);
1397 memcpy(rbuf + off + i, "\n", 2);
1398 }
1399 return chngd ? off + i + 1 : 0;
1400}
1401
1402
1403/* join: replace a range of lines with the joined text of those lines */
1404join(from, to)
1405 long from;
1406 long to;
1407{
1408 static char *buf = NULL;
1409 static int n;
1410
1411 char *s;
1412 int len = 0;
1413 int size = 0;
1414 line_t *bp, *ep;
1415
1416 ep = getlp(nextln(to, lastln));
1417 for (bp = getlp(from); bp != ep; bp = bp->next, size += len) {
1418 if ((s = gettxt(bp)) == NULL)
1419 return ERR;
1420 len = bp->len & ~ACTV;
1421 CKBUF(buf, n, size + len, ERR);
1422 memcpy(buf + size, s, len);
1423 }
1424 CKBUF(buf, n, size + 2, ERR);
1425 memcpy(buf + size, "\n", 2);
78ed81a3 1426 if (lndelete(from, to) < 0)
1427 return ERR;
54a7a3ed
AM
1428 curln = from - 1;
1429 spl1();
1430 if (puttxt(buf) == NULL
1431 || upush(UADD, curln, curln) == NULL) {
1432 spl0();
1433 return ERR;
1434 }
1435 spl0();
1436 modified = 1;
1437 return 0;
1438}
1439
1440
1441/* move: move a range of lines */
78ed81a3 1442move(num, glob)
54a7a3ed 1443 long num;
78ed81a3 1444 int glob;
54a7a3ed 1445{
78ed81a3 1446 line_t *b1, *a1, *b2, *a2, *lp;
54a7a3ed
AM
1447 long n = nextln(line2, lastln);
1448 long p = prevln(line1, lastln);
78ed81a3 1449 int done = (num == line1 - 1 || num == line2);
54a7a3ed
AM
1450
1451 spl1();
78ed81a3 1452 if (done) {
1453 a2 = getlp(n);
1454 b2 = getlp(p);
1455 curln = line2;
1456 } else if (upush(UMOV, p, n) == NULL
54a7a3ed 1457 || upush(UMOV, num, nextln(num, lastln)) == NULL) {
78ed81a3 1458 spl0();
1459 return ERR;
1460 } else {
1461 a1 = getlp(n);
1462 if (num < line1)
1463 b1 = getlp(p), b2 = getlp(num); /* this getlp last! */
1464 else b2 = getlp(num), b1 = getlp(p); /* this getlp last! */
1465 a2 = b2->next;
1466 requeue(b2, b1->next);
1467 requeue(a1->prev, a2);
1468 requeue(b1, a1);
1469 curln = num + ((num < line1) ? line2 - line1 + 1 : 0);
54a7a3ed 1470 }
78ed81a3 1471 if (glob)
1472 for (lp = b2->next; lp != a2; lp = lp->next)
1473 lp->len &= ~ACTV; /* zero ACTV bit */
54a7a3ed
AM
1474 spl0();
1475 return 0;
1476}
1477
1478
1479/* transfer: copy a range of lines; return status */
1480transfer(num)
1481 long num;
1482{
1483 line_t *lp;
1484 long nl, nt, lc;
1485 long mid = (num < line2) ? num : line2;
1486 undo_t *up = NULL;
1487
1488 curln = num;
1489 for (nt = 0, nl = line1; nl <= mid; nl++, nt++) {
1490 spl1();
1491 if ((lp = lpdup(getlp(nl))) == NULL) {
1492 spl0();
1493 return ERR;
1494 }
1495 lpqueue(lp);
1496 if (up)
1497 up->t = lp;
1498 else if ((up = upush(UADD, curln, curln)) == NULL) {
1499 spl0();
1500 return ERR;
1501 }
1502 spl0();
1503 }
1504 for (nl += nt, lc = line2 + nt; nl <= lc; nl += 2, lc++) {
1505 spl1();
1506 if ((lp = lpdup(getlp(nl))) == NULL) {
1507 spl0();
1508 return ERR;
1509 }
1510 lpqueue(lp);
1511 if (up)
1512 up->t = lp;
1513 else if ((up = upush(UADD, curln, curln)) == NULL) {
1514 spl0();
1515 return ERR;
1516 }
1517 spl0();
1518 }
1519 return 0;
1520}
1521
1522
1523/* lndelete: delete a range of lines */
1524lndelete(from, to)
1525 long from, to;
1526{
1527 line_t *before, *after;
1528
1529 spl1();
1530 if (upush(UDEL, from, to) == NULL) {
1531 spl0();
1532 return ERR;
1533 }
1534 after = getlp(nextln(to, lastln));
1535 before = getlp(prevln(from, lastln)); /* this getlp last! */
1536 requeue(before, after);
1537 lastln -= to - from + 1;
1538 curln = prevln(from, lastln);
1539 spl0();
1540 return 0;
1541}
1542
1543
1544/* catsub: modify text according to a substitution template;
1545 return offset to end of modified text */
1546catsub(boln, rm, off)
1547 char *boln;
1548 regmatch_t *rm;
1549 int off;
1550{
1551 int j = 0;
1552 int k = 0;
1553 char *sub = rhbuf;
1554
1555 for (; sub - rhbuf < rhbufi; sub++)
1556 if (*sub == '&') {
1557 j = rm[0].rm_so;
1558 k = rm[0].rm_eo;
1559 CKBUF(rbuf, rbufsz, off + k - j, ERR);
1560 while (j < k)
1561 rbuf[off++] = boln[j++];
1562 } else if (*sub == '\\' && '1' <= *++sub && *sub <= '9'
1563 && rm[*sub - '0'].rm_so >= 0
1564 && rm[*sub - '0'].rm_eo >= 0) {
1565 j = rm[*sub - '0'].rm_so;
1566 k = rm[*sub - '0'].rm_eo;
1567 CKBUF(rbuf, rbufsz, off + k - j, ERR);
1568 while (j < k)
1569 rbuf[off++] = boln[j++];
1570 } else {
1571 CKBUF(rbuf, rbufsz, off + 1, ERR);
1572 rbuf[off++] = *sub;
1573 }
1574 CKBUF(rbuf, rbufsz, off + 1, ERR);
1575 rbuf[off] = '\0';
1576 return off;
1577}
1578
1579/* doprint: print a range of lines to stdout */
1580doprint(from, to, gflag)
1581 long from;
1582 long to;
1583 int gflag;
1584{
1585 line_t *bp;
1586 line_t *ep;
1587 char *s;
1588
1589 if (!from) {
1590 sprintf(errmsg, "invalid address");
1591 return ERR;
1592 }
1593 ep = getlp(nextln(to, lastln));
1594 for (bp = getlp(from); bp != ep; bp = bp->next) {
1595 if ((s = gettxt(bp)) == NULL)
1596 return ERR;
1597 putstr(s, bp->len & ~ACTV, curln = from++, gflag);
1598 }
1599 return 0;
1600}
1601
1602
1603int cols = 72; /* wrap column: ws_col - 8 */
1604
1605/* putstr: print text to stdout */
1606void
1607putstr(s, l, n, gflag)
1608 char *s;
1609 int l;
1610 long n;
1611 int gflag;
1612{
1613 int col = 0;
1614
1615 if (gflag & GNP) {
1616 printf("%ld\t", n);
1617 col = 8;
1618 }
1619 for (; l--; s++) {
1620 if ((gflag & GLS) && ++col > cols) {
1621 fputs("\\\n", stdout);
1622 col = 1;
1623 }
1624 if (gflag & GLS) {
1625 switch (*s) {
1626 case '\b':
1627 fputs("\\b", stdout);
1628 break;
1629 case '\f':
1630 fputs("\\f", stdout);
1631 break;
1632 case '\n':
1633 fputs("\\n", stdout);
1634 break;
1635 case '\r':
1636 fputs("\\r", stdout);
1637 break;
1638 case '\t':
1639 fputs("\\t", stdout);
1640 break;
1641 case '\v':
1642 fputs("\\v", stdout);
1643 break;
1644 default:
1645 if (*s < 32 || 126 < *s) {
1646 putchar('\\');
1647 putchar((((unsigned char) *s & 0300) >> 6) + '0');
1648 putchar((((unsigned char) *s & 070) >> 3) + '0');
1649 putchar(((unsigned char) *s & 07) + '0');
1650 col += 2;
1651 } else if (*s == '\\')
1652 fputs("\\\\", stdout);
1653 else {
1654 putchar(*s);
1655 col--;
1656 }
1657 }
1658 col++;
1659 } else
1660 putchar(*s);
1661 }
1662#ifndef BACKWARDS
1663 if (gflag & GLS)
1664 putchar('$');
1665#endif
1666 putchar('\n');
1667}
1668
1669
1670int newline_added; /* set if newline appended to input file */
1671
1672/* doread: read a text file into the editor buffer; return line count */
1673long
1674doread(n, fn)
1675 long n;
1676 char *fn;
1677{
1678 FILE *fp;
1679 line_t *lp = getlp(n);
1680 unsigned long size = 0;
1681 undo_t *up = NULL;
1682 int len;
1683
1684 isbinary = newline_added = 0;
1685 if ((fp = (*fn == '!') ? popen(fn + 1, "r") : fopen(esctos(fn), "r")) == NULL) {
1686 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
1687 sprintf(errmsg, "cannot open input file");
1688 return ERR;
1689 } else if (des)
1690 desinit();
1691 for (curln = n; (len = sgetline(fp)) > 0; size += len) {
1692 spl1();
1693 if (puttxt(sbuf) == NULL) {
1694 spl0();
1695 return ERR;
1696 }
1697 lp = lp->next;
1698 if (up)
1699 up->t = lp;
1700 else if ((up = upush(UADD, curln, curln)) == NULL) {
1701 spl0();
1702 return ERR;
1703 }
1704 spl0();
1705 }
1706 if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) {
1707 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
1708 sprintf(errmsg, "cannot close input file");
1709 return ERR;
1710 }
1711 if (newline_added && !isbinary)
1712 fputs("newline appended\n", stderr);
1713 if (des) size += 8 - size % 8;
1714 fprintf(stderr, !scripted ? "%lu\n" : "", size);
1715 return (len < 0) ? ERR : curln - n;
1716}
1717
1718
1719/* dowrite: write the text of a range of lines to a file; return line count */
1720long
1721dowrite(n, m, fn, mode)
1722 long n;
1723 long m;
1724 char *fn;
1725 char *mode;
1726{
1727 FILE *fp;
1728 line_t *lp;
1729 unsigned long size = 0;
1730 long lc = n ? m - n + 1 : 0;
1731 char *s = NULL;
1732 int len;
1733 int ct;
1734
1735 if ((fp = ((*fn == '!') ? popen(fn + 1, "w") : fopen(esctos(fn), mode))) == NULL) {
1736 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
1737 sprintf(errmsg, "cannot open output file");
1738 return ERR;
1739 } else if (des)
1740 desinit();
1741 if (n && !des)
1742 for (lp = getlp(n); n <= m; n++, lp = lp->next) {
1743 if ((s = gettxt(lp)) == NULL)
1744 return ERR;
1745 len = lp->len & ~ACTV;
1746 if (n != lastln || !isbinary || !newline_added)
1747 s[len++] = '\n';
1748 if ((ct = fwrite(s, sizeof(char), len, fp)) < 0 || ct != len) {
1749 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
1750 sprintf(errmsg, "cannot write file");
1751 return ERR;
1752 }
1753 size += len;
1754 }
1755 else if (n)
1756 for (lp = getlp(n); n <= m; n++, lp = lp->next) {
1757 if ((s = gettxt(lp)) == NULL)
1758 return ERR;
1759 len = lp->len & ~ACTV;
1760 while (len--) {
1761 if (desputc(*s++, fp) == EOF && ferror(fp)) {
1762 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
1763 sprintf(errmsg, "cannot write file");
1764 return ERR;
1765 }
1766 }
1767 if (n != lastln || !isbinary || !newline_added) {
1768 if (desputc('\n', fp) < 0) {
1769 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
1770 sprintf(errmsg, "cannot write file");
1771 return ERR;
1772 }
1773 size++; /* for '\n' */
1774 }
1775 size += (lp->len & ~ACTV);
1776 }
1777 if (des) {
1778 desflush(fp); /* flush buffer */
1779 size += 8 - size % 8; /* adjust DES size */
1780 }
1781 if (((*fn == '!') ? pclose(fp) : fclose(fp)) < 0) {
1782 fprintf(stderr, "%s: %s\n", fn, strerror(errno));
1783 sprintf(errmsg, "cannot close output file");
1784 return ERR;
1785 }
1786 fprintf(stderr, !scripted ? "%lu\n" : "", size);
1787 return lc;
1788}
1789
1790
1791#define USIZE 100 /* undo stack size */
1792undo_t *ustack = NULL; /* undo stack */
1793long usize = 0; /* stack size variable */
1794long u_p = 0; /* undo stack pointer */
1795
54a7a3ed
AM
1796/* upush: return pointer to intialized undo node */
1797undo_t *
1798upush(type, from, to)
1799 int type;
1800 long from;
1801 long to;
1802{
1803 undo_t *t;
1804
1805#if defined(sun) || defined(NO_REALLOC_NULL)
1806 if (ustack == NULL
1807 && (ustack = (undo_t *) malloc((usize = USIZE) * sizeof(undo_t))) == NULL) {
1808 fprintf(stderr, "%s\n", strerror(errno));
1809 sprintf(errmsg, "out of memory");
1810 return NULL;
1811 }
1812#endif
1813 t = ustack;
1814 if (u_p < usize
1815 || (t = (undo_t *) realloc(ustack, (usize += USIZE) * sizeof(undo_t))) != NULL) {
1816 ustack = t;
1817 ustack[u_p].type = type;
1818 ustack[u_p].t = getlp(to);
1819 ustack[u_p].h = getlp(from);
1820 return ustack + u_p++;
1821 }
1822 /* out of memory - release undo stack */
1823 fprintf(stderr, "%s\n", strerror(errno));
1824 sprintf(errmsg, "out of memory");
1825 ureset();
1826 free(ustack);
1827 ustack = NULL;
1828 usize = 0;
1829 return NULL;
1830}
1831
78ed81a3 1832
1833/* USWAP: swap undo nodes */
1834#define USWAP(x,y) { \
1835 undo_t utmp; \
1836 utmp = x, x = y, y = utmp; \
1837}
1838
1839
54a7a3ed 1840/* undo: undo last change to the editor buffer */
78ed81a3 1841undo(glob)
1842 int glob;
54a7a3ed 1843{
78ed81a3 1844 long n;
54a7a3ed
AM
1845 long ocurln = curln;
1846 long olastln = lastln;
78ed81a3 1847 line_t *lp, *np;
54a7a3ed
AM
1848
1849 if (ucurln == -1 || ulastln == -1) {
1850 sprintf(errmsg, "nothing to undo");
1851 return ERR;
1852 } else if (u_p)
1853 modified = 1;
1854 getlp(0); /* this getlp last! */
1855 spl1();
78ed81a3 1856 for (n = u_p; n-- > 0;) {
1857 switch(ustack[n].type) {
54a7a3ed
AM
1858 case UADD:
1859 requeue(ustack[n].h->prev, ustack[n].t->next);
1860 break;
1861 case UDEL:
1862 requeue(ustack[n].h->prev, ustack[n].h);
1863 requeue(ustack[n].t, ustack[n].t->next);
1864 break;
1865 case UMOV:
1866 case VMOV:
78ed81a3 1867 requeue(ustack[n - 1].h, ustack[n].h->next);
1868 requeue(ustack[n].t->prev, ustack[n - 1].t);
54a7a3ed 1869 requeue(ustack[n].h, ustack[n].t);
78ed81a3 1870 n--;
54a7a3ed
AM
1871 break;
1872 default:
1873 /*NOTREACHED*/
1874 ;
1875 }
78ed81a3 1876 ustack[n].type ^= 1;
1877 }
1878 /* reverse undo order */
1879 for (n = u_p; n-- > (u_p + 1)/ 2;)
1880 USWAP(ustack[n], ustack[u_p - 1 - n]);
1881 if (glob)
1882 for (lp = np = getlp(0); (lp = lp->next) != np;)
1883 lp->len &= ~ACTV; /* zero ACTV bit */
54a7a3ed
AM
1884 curln = ucurln, ucurln = ocurln;
1885 lastln = ulastln, ulastln = olastln;
1886 spl0();
1887 return 0;
1888}
1889
1890
1891/* ureset: clear the undo stack */
1892void
1893ureset()
1894{
1895 line_t *lp, *ep, *tl;
54a7a3ed
AM
1896
1897 while (u_p--)
78ed81a3 1898 if (ustack[u_p].type == UDEL) {
54a7a3ed
AM
1899 ep = ustack[u_p].t->next;
1900 for (lp = ustack[u_p].h; lp != ep; lp = tl) {
78ed81a3 1901 clrmark(lp);
54a7a3ed
AM
1902 tl = lp->next;
1903 free(lp);
1904 }
1905 }
78ed81a3 1906 u_p = 0;
54a7a3ed
AM
1907 ucurln = curln;
1908 ulastln = lastln;
1909}
1910
1911
78ed81a3 1912#define MAXMARK 26 /* max number of marks */
1913
1914line_t *mark[MAXMARK]; /* line markers */
1915int markno; /* line marker count */
1916
1917/* getmark: return address of a marked line */
1918long
1919getmark(n)
1920 int n;
1921{
1922 if (!islower(n)) {
1923 sprintf(errmsg, "invalid mark character");
1924 return ERR;
1925 }
1926 return getaddr(mark[n - 'a']);
1927}
1928
1929
1930/* putmark: set a line node mark */
1931int
1932putmark(n, lp)
1933 int n;
1934 line_t *lp;
1935{
1936 if (!islower(n)) {
1937 sprintf(errmsg, "invalid mark character");
1938 return ERR;
1939 } else if (mark[n - 'a'] == NULL)
1940 markno++;
1941 mark[n - 'a'] = lp;
1942 return 0;
1943}
1944
1945
1946/* clrmark: clear line node marks */
1947void
1948clrmark(lp)
1949 line_t *lp;
1950{
1951 int i;
1952
1953 if (markno)
1954 for (i = 0; i < MAXMARK; i++)
1955 if (mark[i] == lp) {
1956 mark[i] = NULL;
1957 markno--;
1958 }
1959}
1960
54a7a3ed
AM
1961
1962/* sgetline: read a line of text up a maximum size from a file; return
1963 line length */
1964sgetline(fp)
1965 FILE *fp;
1966{
1967 register int c;
1968 register int i = 0;
1969
1970 while (((c = des ? desgetc(fp) : getc(fp)) != EOF || !feof(fp) && !ferror(fp)) && c != '\n') {
1971 CKBUF(sbuf, sbufsz, i + 1, ERR);
1972 if (!(sbuf[i++] = c)) isbinary = 1;
1973 }
1974 CKBUF(sbuf, sbufsz, i + 2, ERR);
1975 if (c == '\n')
1976 sbuf[i++] = c;
1977 else if (feof(fp) && i) {
1978 sbuf[i++] = '\n';
1979 newline_added = 1;
1980 } else if (ferror(fp)) {
1981 fprintf(stderr, "%s\n", strerror(errno));
1982 sprintf(errmsg, "cannot read input file");
1983 return ERR;
1984 }
1985 sbuf[i] = '\0';
1986 return (isbinary && newline_added && i) ? --i : i;
1987}
1988
1989
1990/* getline: read a line of text up a maximum size from stdin; return
1991 line length */
1992getline()
1993{
1994 register int i = 0;
1995 register int oi = 0;
1996 char c;
1997
1998 /* Read one character at a time to avoid i/o contention with shell
1999 escapes invoked by nonterminal input, e.g.,
2000 ed - <<EOF
2001 !cat
2002 hello, world
2003 EOF */
2004 for (;;)
2005 switch (read(0, &c, 1)) {
2006 default:
2007 oi = 0;
2008 CKBUF(ibuf, ibufsz, i + 2, ERR);
2009 if (!(ibuf[i++] = c)) isbinary = 1;
2010 if (c != '\n')
2011 continue;
2012 lineno++; /* script line no. */
2013 ibuf[i] = '\0';
2014 ibufp = ibuf;
2015 return i;
2016 case 0:
2017 if (i != oi) {
2018 oi = i;
2019 continue;
2020 } else if (i)
2021 ibuf[i] = '\0';
2022 ibufp = ibuf;
2023 return i;
2024 case -1:
2025 fprintf(stderr, "%s\n", strerror(errno));
2026 sprintf(errmsg, "cannot read standard input");
2027 clearerr(stdin);
2028 ibufp = NULL;
2029 return ERR;
2030 }
2031}
2032
2033
2034/* getcmdv: get a command vector */
2035char *
2036getcmdv(sizep, nonl)
2037 int *sizep;
2038 int nonl;
2039{
2040 int l, n;
2041 char *t = ibufp;
2042
2043 while (*t++ != '\n')
2044 ;
2045 if ((l = t - ibufp) < 2 || !oddesc(ibufp, ibufp + l - 1)) {
2046 *sizep = l;
2047 return ibufp;
2048 }
2049 *sizep = -1;
2050 CKBUF(cvbuf, cvbufsz, l, NULL);
2051 memcpy(cvbuf, ibufp, l);
2052 *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */
2053 if (nonl) l--; /* strip newline */
2054 for (;;) {
2055 if ((n = getline()) < 0)
2056 return NULL;
2057 else if (n == 0 || ibuf[n - 1] != '\n') {
2058 sprintf(errmsg, "unexpected end-of-file");
2059 return NULL;
2060 }
2061 CKBUF(cvbuf, cvbufsz, l + n, NULL);
2062 memcpy(cvbuf + l, ibuf, n);
2063 l += n;
2064 if (n < 2 || !oddesc(cvbuf, cvbuf + l - 1))
2065 break;
2066 *(cvbuf + --l - 1) = '\n'; /* strip trailing esc */
2067 if (nonl) l--; /* strip newline */
2068 }
2069 CKBUF(cvbuf, cvbufsz, l + 1, NULL);
2070 cvbuf[l] = '\0';
2071 *sizep = l;
2072 return cvbuf;
2073}
2074
2075
2076/* lpdup: return a pointer to a copy of a line node */
2077line_t *
2078lpdup(lp)
2079 line_t *lp;
2080{
2081 line_t *np;
2082
2083 if ((np = (line_t *) malloc(sizeof(line_t))) == NULL) {
2084 fprintf(stderr, "%s\n", strerror(errno));
2085 sprintf(errmsg, "out of memory");
2086 return NULL;
2087 }
2088 np->seek = lp->seek;
2089 np->len = (lp->len & ~ACTV); /* zero ACTV bit */
2090 return np;
2091}
2092
2093
2094/* oddesc: return the parity of escapes preceding a character in a
2095 string */
2096oddesc(s, t)
2097 char *s;
2098 char *t;
2099{
2100 return (s == t || *(t - 1) != '\\') ? 0 : !oddesc(s, t - 1);
2101}
2102
2103
2104/* esctos: return copy of escaped string */
2105char *
2106esctos(s)
2107 char *s;
2108{
2109 static char *file = NULL;
2110 static int filesz = 0;
2111
2112 int i = 0;
2113
2114 CKBUF(file, filesz, MAXFNAME + 1, NULL);
2115 /* assert: no trailing escape */
2116 while (file[i++] = (*s == '\\') ? *++s : *s)
2117 s++;
2118 return file;
2119}
2120
2121
2122void
2123onhup(signo)
2124 int signo;
2125{
2126 if (mutex)
2127 sigflags |= (1 << signo);
2128 else dohup(signo);
2129}
2130
2131
2132void
2133onintr(signo)
2134 int signo;
2135{
2136 if (mutex)
2137 sigflags |= (1 << signo);
2138 else dointr(signo);
2139}
2140
2141
54a7a3ed
AM
2142void
2143dohup(signo)
2144 int signo;
2145{
2146 char *hup = NULL; /* hup filename */
2147 char *s;
2148 int n;
2149
2150 if (!sigactive)
2151 quit(1);
2152 sigflags &= ~(1 << signo);
2153 if (lastln && dowrite(1, lastln, "ed.hup", "w") < 0
2154 && (s = getenv("HOME")) != NULL
2155 && (n = strlen(s)) + 8 <= MAXFNAME /* "ed.hup" + '/' */
2156 && (hup = (char *) malloc(n + 10)) != NULL) {
2157 strcpy(hup, s);
2158 if (hup[n - 1] != '/')
2159 hup[n] = '/', hup[n+1] = '\0';
2160 strcat(hup, "ed.hup");
2161 dowrite(1, lastln, hup, "w");
2162 }
2163 quit(2);
2164}
2165
2166
2167void
2168dointr(signo)
2169 int signo;
2170{
2171 if (!sigactive)
2172 quit(1);
2173 sigflags &= ~(1 << signo);
2174#ifdef _POSIX_SOURCE
2175 siglongjmp(env, -1);
2176#else
2177 longjmp(env, -1);
2178#endif
2179}
2180
2181
2182struct winsize ws; /* window size structure */
2183
2184void
2185dowinch(signo)
2186 int signo;
2187{
2188 sigflags &= ~(1 << signo);
2189 if (ioctl(0, TIOCGWINSZ, (char *) &ws) >= 0) {
2190 if (ws.ws_row > 2) rows = ws.ws_row - 2;
2191 if (ws.ws_col > 8) cols = ws.ws_col - 8;
2192 }
2193}
2194
2195
54a7a3ed
AM
2196/* ckfn: return a legal filename */
2197char *
2198ckfn(s)
2199 char *s;
2200{
2201 if (red && (*s == '!' || !strcmp(s, "..") || strchr(s, '/'))) {
2202 sprintf(errmsg, "shell access restricted");
2203 return NULL;
2204 }
2205 return s;
2206}