main.c

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

Generated by GNU Enscript 1.6.6.