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