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