GCC Code Coverage Report
Directory: ./ Exec Total Coverage
File: bin/ed/main.c Lines: 0 730 0.0 %
Date: 2017-11-07 Branches: 0 1212 0.0 %

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