/*
 * This code contains changes by
 *      Gunnar Ritter, Freiburg i. Br., Germany, 2002. All rights reserved.
 *
 * Conditions 1, 2, and 4 and the no-warranty notice below apply
 * to these changes.
 *
 *
 * Copyright (c) 1980, 1993
 * 	The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 * 	This product includes software developed by the University of
 * 	California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 *
 * Copyright(C) Caldera International Inc. 2001-2002. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *   Redistributions of source code and documentation must retain the
 *    above copyright notice, this list of conditions and the following
 *    disclaimer.
 *   Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *   All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed or owned by Caldera
 *      International, Inc.
 *   Neither the name of Caldera International, Inc. nor the names of
 *    other contributors may be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * USE OF THE SOFTWARE PROVIDED FOR UNDER THIS LICENSE BY CALDERA
 * INTERNATIONAL, INC. AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL CALDERA INTERNATIONAL, INC. BE
 * LIABLE FOR ANY DIRECT, INDIRECT INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef	lint
#ifdef	DOSCCS
char *copyright =
"@(#) Copyright (c) 1980 Regents of the University of California.\n\
 All rights reserved.\n";

static char sccsid[] = "@(#)ex.c	1.26 (gritter) 4/3/02";
#endif	/* DOSCCS */
#endif	/* !lint */

/* from ex.c	7.5.1.1 (Berkeley) 8/12/86 */

#include "ex.h"
#include "ex_argv.h"
#include "ex_temp.h"
#include "ex_tty.h"

#ifdef TRACE
char	tttrace[]	= { '/','d','e','v','/','t','t','y','x','x',0 };
#endif

/*
 * The code for ex is divided as follows:
 *
 * ex.c			Entry point and routines handling interrupt, hangup
 *			signals; initialization code.
 *
 * ex_addr.c		Address parsing routines for command mode decoding.
 *			Routines to set and check address ranges on commands.
 *
 * ex_cmds.c		Command mode command decoding.
 *
 * ex_cmds2.c		Subroutines for command decoding and processing of
 *			file names in the argument list.  Routines to print
 *			messages and reset state when errors occur.
 *
 * ex_cmdsub.c		Subroutines which implement command mode functions
 *			such as append, delete, join.
 *
 * ex_data.c		Initialization of options.
 *
 * ex_get.c		Command mode input routines.
 *
 * ex_io.c		General input/output processing: file i/o, unix
 *			escapes, filtering, source commands, preserving
 *			and recovering.
 *
 * ex_put.c		Terminal driving and optimizing routines for low-level
 *			output (cursor-positioning); output line formatting
 *			routines.
 *
 * ex_re.c		Global commands, substitute, regular expression
 *			compilation and execution.
 *
 * ex_set.c		The set command.
 *
 * ex_subr.c		Loads of miscellaneous subroutines.
 *
 * ex_temp.c		Editor buffer routines for main buffer and also
 *			for named buffers (Q registers if you will.)
 *
 * ex_tty.c		Terminal dependent initializations from termcap
 *			data base, grabbing of tty modes (at beginning
 *			and after escapes).
 *
 * ex_unix.c		Routines for the ! command and its variations.
 *
 * ex_v*.c		Visual/open mode routines... see ex_v.c for a
 *			guide to the overall organization.
 */

#ifdef	POSIX_2
static char *progname;

erropen()
{
	close(1);
	dup(2);
}

usage()
{
	printf(catgets(catd, 1, 1,
"%s: Usage: %s [-c command | +command ] [-l] [-L] [-r [filename]] [-R]\n\
            [-s | -] [-t tagstring] [-V] [-v] [-w size] [filename ...]\n"),
		progname, progname);
	flush();
	exitex(1);
}

needarg(c)
{
	erropen();
	printf(catgets(catd, 1, 2,
		"%s: option requires an argument -- %c\n"), progname, c);
	usage();
}

invopt(c)
{
	erropen();
	printf(catgets(catd, 1, 3, "%s: illegal option -- %c\n"), progname, c);
	usage();
}
#endif	/* POSIX_2 */

/*
 * Return last component of unix path name p.
 */
char *
tailpath(p)
register char *p;
{
	register char *r;

	for (r=p; *p; p++)
		if (*p == '/')
			r = p+1;
	return(r);
}

/*
 * Check ownership of file.  Return nonzero if it exists and is owned by the
 * user or the option sourceany is used
 */
iownit(file)
char *file;
{
	struct stat sb;

#ifndef	POSIX_2
	if (stat(file, &sb) == 0 && (value(SOURCEANY) || sb.st_uid == getuid()))
		return(1);
	else
		return(0);
#else	/* POSIX_2 */
	if (*file == '.' && value(EXRC) == 0)
		return 0;
	if (stat(file, &sb))
		return 0;
	if (value(SOURCEANY))
		return 1;
	if (sb.st_uid != getuid())
		return 0;
	if (sb.st_mode & (S_IWOTH | S_IWGRP))
		return 0;
	return 1;
#endif	/* POSIX_2 */
}

#ifdef	POSIX_1
shand
setsig(signum, handler)
shand handler;
{
	struct sigaction nact, oact;

	nact.sa_handler = handler;
	sigemptyset(&nact.sa_mask);
	nact.sa_flags = 0;
	if (signum == SIGALRM) {
#ifdef	SA_INTERRUPT
		nact.sa_flags |= SA_INTERRUPT;
#endif
	/*EMPTY*/ ;
	} else {
#ifdef	SA_RESTART
		nact.sa_flags |= SA_RESTART;
#endif
	/*EMPTY*/ ;
	}
	if (sigaction(signum, &nact, &oact) != 0)
		return SIG_ERR;
	return oact.sa_handler;
}
#endif	/* POSIX_1 */

/*
 * Initialization, before editing a new file.
 * Main thing here is to get a new buffer (in fileinit),
 * rest is peripheral state resetting.
 */
init()
{
	register int i;

	fileinit();
	dot = zero = truedol = unddol = dol = fendcore;
	one = zero+1;
	undkind = UNDNONE;
	chng = 0;
	edited = 0;
	for (i = 0; i <= 'z'-'a'+1; i++)
		names[i] = 1;
	anymarks = 0;
}

/*
 * Main procedure.  Process arguments and then
 * transfer control to the main command processing loop
 * in the routine commands.  We are entered as either "ex", "edit", "vi"
 * or "view" and the distinction is made here.  Actually, we are "vi" if
 * there is a 'v' in our name, "view" is there is a 'w', and "edit" if
 * there is a 'd' in our name.  For edit we just diddle options;
 * for vi we actually force an early visual command.
 */
main(ac, av)
	register int ac;
	register char *av[];
{
#ifndef VMUNIX
	char *erpath = EXSTRINGS;
#endif
#ifndef	__STDC__
	register
#endif
		char *cp = NULL;
	register int c;
#ifndef	POSIX_2
	bool recov = 0;
#endif
	bool ivis;
	bool fast = 0;
#ifdef TRACE
	register char *tracef;
#endif

#ifndef	POSIX_2
	CLOBBGRD(recov);
#endif
	CLOBBGRD(ivis);
	CLOBBGRD(fast);
	CLOBBGRD(cp);

	/*
	 * Initialize the built-in memory allocator.
	 */
#ifdef	VMUNIX
	poolsbrk(0);
#endif

	/*
	 * Immediately grab the tty modes so that we wont
	 * get messed up if an interrupt comes in quickly.
	 */
	gTTY(1);
#ifdef	TERMIO_S
	normf = tty;
#else
	normf = tty.sg_flags;
#endif
	ppid = getpid();
	/*
	 * Defend against d's, v's, w's, and a's in directories of
	 * path leading to our true name.
	 */
	av[0] = tailpath(av[0]);

	/*
	 * Figure out how we were invoked: ex, edit, vi, view.
	 */
	ivis = any('v', av[0]);	/* "vi" */
	if (any('w', av[0]))	/* "view" */
		value(READONLY) = 1;
	if (any('d', av[0])) {	/* "edit" */
#ifdef	POSIX_2
		value(SHOWMODE) = 1;
		/*
		 * I do not understand why novices should not
		 * switch to visual mode. So they can if
		 * POSIX_2 is defined. GR
		 */
#else	/* !POSIX_2 */
		value(OPEN) = 0;
#endif	/* !POSIX_2 */
		value(REPORT) = 1;
		value(MAGIC) = 0;
	}

#ifndef VMUNIX
	/*
	 * For debugging take files out of . if name is a.out.
	 */
	if (av[0][0] == 'a')
		erpath = tailpath(erpath);
#endif	/* !VMUNIX */

#ifdef	POSIX_2
	progname = av[0];
#endif	/* POSIX_2 */
	/*
	 * Open the error message file.
	 */
	draino();
#ifndef VMUNIX
	erfile = open(erpath+4, O_RDONLY);
	if (erfile < 0) {
		erfile = open(erpath, O_RDONLY);
	}
#endif	/* !VMUNIX */
	pstop();

	/*
	 * Initialize interrupt handling.
	 */
	oldhup = signal(SIGHUP, SIG_IGN);
	if (oldhup == SIG_DFL)
		signal(SIGHUP, onhup);
	oldquit = signal(SIGQUIT, SIG_IGN);
#ifdef	SIGXFSZ
	oldxfsz = signal(SIGXFSZ, SIG_IGN);
#endif
	ruptible = signal(SIGINT, SIG_IGN) == SIG_DFL;
	if (signal(SIGTERM, SIG_IGN) == SIG_DFL)
		signal(SIGTERM, onhup);
#ifdef	SIGEMT
	if (signal(SIGEMT, SIG_IGN) == SIG_DFL)
		signal(SIGEMT, onemt);
#endif

#ifdef	BIT8
#ifndef	ISO8859_1
	setlocale(LC_CTYPE, "");
#endif
#endif

#ifdef	LANGMSG
	setlocale(LC_MESSAGES, "");
	catd = catopen(CATNAME, NL_CAT_LOCALE);
#endif

	/*
	 * Process flag arguments.
	 */
	ac--, av++;
#ifndef	POSIX_2
	while (ac && av[0][0] == '-') {
#else	/* POSIX_2 */
	while (ac) {
		if (av[0][0] == '+') {
			firstpat = &av[0][1];
			if (*firstpat == '\0')
				needarg('+');
		} else if (av[0][0] == '-') {
arggroup:
#endif	/* POSIX_2 */
		c = av[0][1];
		if (c == 0
#ifdef	POSIX_2
			|| c == 's'
#endif	/* POSIX_2 */
				) {
			hush = 1;
			value(AUTOPRINT) = 0;
			fast++;
		} else switch (c) {

#ifdef	POSIX_2
		case '-':
			if (av[0][2])
				invopt('-');
			ac--, av++;
			goto argend;
#endif	/* POSIX_2 */

		case 'R':
			value(READONLY) = 1;
			break;

#ifdef TRACE
		case 'T':
			if (av[0][2] == 0)
				tracef = "trace";
			else {
				tracef = tttrace;
				tracef[8] = av[0][2];
				if (tracef[8])
					tracef[9] = av[0][3];
				else
					tracef[9] = 0;
			}
			trace = fopen(tracef, "w");
#define tracbuf NULL
			if (trace == NULL)
				printf(catgets(catd, 1, 4,
						"Trace create error\n"));
			else
				setbuf(trace, tracbuf);
			break;

#endif	/* TRACE */

#ifdef	POSIX_2
		case 'c':
			if (av[0][2] == '\0' && (av[1] == NULL
					|| *av[1] == '-' || *av[1] == '+'))
				needarg('c');
			if (av[0][2]) {
				firstpat = &av[0][2];
			} else {
				firstpat = av[1];
				ac--, av++;
			}
			break;

		case 'e':
			ivis = 0;
			break;
#endif	/* POSIX_2 */

#ifdef LISPCODE
		case 'l':
			value(LISP) = 1;
			value(SHOWMATCH) = 1;
			break;
#endif

		case 'L':
		case 'r':
			recov++;
			break;

		case 't':
#ifndef	POSIX_2
			if (ac > 1 && av[1][0] != '-') {
#else	/* POSIX_2 */
			if (ac > 1 && av[1][0] != '-' && av[1][0] != '+') {
#endif	/* POSIX_2 */
				ac--, av++;
				tflag = 1;
				/* BUG: should check for too long tag. */
				CP(lasttag, av[0]);
			}
#ifdef	POSIX_2
			else if (av[0][2]) {
				tflag = 1;
				CP(lasttag, &av[0][2]);
			} else
				needarg('t');
#endif	/* POSIX_2 */
			break;

		case 'v':
			ivis = 1;
			break;

		case 'V':
			verbose = 1;
			break;

		case 'w':
#ifndef	POSIX_2
			defwind = 0;
			if (av[0][2] == 0) defwind = 3;
			else for (cp = &av[0][2]; isdigit(*cp); cp++)
				defwind = 10*defwind + *cp - '0';
#else	/* POSIX_2 */
			if (ac > 1 && av[1][0] != '-' && av[1][0] != '+')
				cp = av[1];
			else if (av[0][2])
				cp = &av[0][2];
			else
				needarg('w');
			defwind = atoi(cp);
#endif	/* POSIX_2 */
			break;

		default:
#ifndef	POSIX_2
			printf(catgets(catd, 1, 5,
					"Unknown option %s\n"), av[0]);
			break;
#else	/* POSIX_2 */
			invopt(c);
#endif	/* POSIX_2 */
		}
#ifdef	POSIX_2
		if (c && c != 'c' && c != 't' && c != 'w' && av[0][2]) {
			av[0]++;
			goto arggroup;
		}
	    } else
		    break;
#endif	/* POSIX_2 */
		ac--, av++;
	}
#ifdef	POSIX_2
argend:
#endif	/* POSIX_2 */

	cntrlhm = catgets(catd, 1, 70, "^H discarded\n");
	/*
	 * Initialize end of core pointers.
	 * Normally we avoid breaking back to fendcore after each
	 * file since this can be expensive (much core-core copying).
	 * If your system can scatter load processes you could do
	 * this as ed does, saving a little core, but it will probably
	 * not often make much difference.
	 */
	fendcore = (line *) sbrk(0);
	endcore = fendcore - 2;

#ifndef	USG3TTY
#ifdef SIGTSTP
	if (!hush && signal(SIGTSTP, SIG_IGN) == SIG_DFL)
		signal(SIGTSTP, onsusp), dosusp++;
#endif	/* SIGTSTP */
#endif	/* !USG3TTY */

#ifndef	POSIX_2
	if (ac && av[0][0] == '+') {
		firstpat = &av[0][1];
		ac--, av++;
	}
#endif	/* !POSIX_2 */

	/*
	 * If we are doing a recover and no filename
	 * was given, then execute an exrecover command with
	 * the -r option to type out the list of saved file names.
	 * Otherwise set the remembered file name to the first argument
	 * file name so the "recover" initial command will find it.
	 */
	if (recov) {
		if (ac == 0) {
			ppid = 0;
			setrupt();
			execl(EXRECOVER, "exrecover", "-r", (char *)0);
			filioerr(EXRECOVER);
			exitex(1);
		}
		strcpy(savedfile, *av++), ac--;
	}

	/*
	 * Initialize the argument list.
	 */
	argv0 = av;
	argc0 = ac;
	args0 = av[0];
	erewind();

	/*
	 * Initialize a temporary file (buffer) and
	 * set up terminal environment.  Read user startup commands.
	 */
	if (setexit() == 0) {
		setrupt();
		intty = isatty(0);
		value(PROMPT) = intty;
		if (cp = getenv("SHELL"))
			strcpy(shell, cp);
		if (fast || !intty)
			setterm("dumb");
		else {
			gettmode();
			if ((cp = getenv("TERM")) != 0 && *cp) {
				setterm(cp);
			}
		}
	}
	if (setexit() == 0 && !fast
#ifndef	POSIX_2
			&& intty
#endif
			) {
		if ((globp = getenv("EXINIT")) && *globp)
			commands(1,1);
		else {
			globp = 0;
			if ((cp = getenv("HOME")) != 0 && *cp) {
				strcat(strcpy(genbuf, cp), "/.exrc");
				if (iownit(genbuf))
					source(genbuf, 1);
			}
		}
		/*
		 * Allow local .exrc too.  This loses if . is $HOME,
		 * but nobody should notice unless they do stupid things
		 * like putting a version command in .exrc.  Besides,
		 * they should be using EXINIT, not .exrc, right?
		 *
		 * This may not be done in POSIX.2 anymore. GR
		 */
#ifdef	POSIX_2
		/*
		 * The getcwd() function is not present on very
		 * old Unix systems. So if this fails, either
		 * #undef POSIX_2, comment out the following three lines
		 * or supply code e.g. from the `pwd' utility.
		 */
		if (cp == NULL || *cp == '\0'
				|| getcwd(genbuf, MAXBSIZE) == NULL
				|| strcmp(cp, genbuf) != 0)

#endif	/* POSIX_2 */
		if (iownit(".exrc"))
			source(".exrc", 1);
	}
	init();	/* moved after prev 2 chunks to fix directory option */

	/*
	 * Initial processing.  Handle tag, recover, and file argument
	 * implied next commands.  If going in as 'vi', then don't do
	 * anything, just set initev so we will do it later (from within
	 * visual).
	 */
	if (setexit() == 0) {
		if (recov)
			globp = "recover";
		else if (tflag)
			globp = ivis ? "tag" : "tag|p";
		else if (argc)
			globp = "next";
		if (ivis)
			initev = globp;
		else if (globp) {
			inglobal = 1;
			commands(1, 1);
			inglobal = 0;
		}
	}

	/*
	 * Vi command... go into visual.
	 * Strange... everything in vi usually happens
	 * before we ever "start".
	 */
	if (ivis) {
		/*
		 * Don't have to be upward compatible with stupidity
		 * of starting editing at line $.
		 */
		if (dol > zero)
			dot = one;
		globp = "visual";
		if (setexit() == 0)
			commands(1, 1);
	}

	/*
	 * Clear out trash in state accumulated by startup,
	 * and then do the main command loop for a normal edit.
	 * If you quit out of a 'vi' command by doing Q or ^\,
	 * you also fall through to here.
	 */
	seenprompt = 1;
	ungetchar(0);
	globp = 0;
	initev = 0;
	setlastchar('\n');
	setexit();
	commands(0, 0);
	cleanup(1);
	exitex(0);
}
