GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: bin/ed/main.c Lines: 0 719 0.0 %
Date: 2016-12-06 Branches: 0 1149 0.0 %

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