PageRenderTime 86ms CodeModel.GetById 10ms app.highlight 66ms RepoModel.GetById 2ms app.codeStats 0ms

/bin/ed/main.c

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