/*
 * Copyright (c) 1988 Mark Nudleman
 * Copyright (c) 1988, 1993
 *	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.
 */

#ifndef lint
char copyright[] = \
"@(#) Copyright (c) 1988 Mark Nudleman.\n\
@(#) Copyright (c) 1988, 1993\
	Regents of the University of California.  All rights reserved.\n";
#endif /* not lint */

#ifndef lint
static char sccsid[] = "@(#)main.c	8.1 (Berkeley) 6/7/93";
#endif /* not lint */

#ifndef lint
static const char rcsid[] =
  "$FreeBSD: src/usr.bin/more/main.c,v 1.16 2000/05/14 03:30:58 hoek Exp $";
#endif /* not lint */

/*
 * Entry point, initialization, miscellaneous routines.
 */

#include <sys/file.h>
#include <sys/param.h>
#include <sys/types.h>

#include <errno.h>
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define DEFINEGLOBALS
#include "defrc.h"
#include "less.h"
#include "pathnames.h"

static int str_read(void *, char *, size_t);

int	ispipe;
char	*current_file, *previous_file, *current_name, *next_name;
int	any_display;
int	ac;
char	**av;
int	curr_ac;

extern int	file;
extern int	cbufs;
extern int	errmsgs;

extern char	*tagfile;
extern int	tagoption;

/*
 * Edit a new file.
 * Filename "-" means standard input.
 * No filename means the "current" file, from the command line.  If called
 * with the same filename in succession, filename will be closed and reopened.
 *
 * If called with FORCE_OPEN, the file will be re-opened even if it is
 * already open.
 */
edit(filename, force_open)
	register char *filename;
{
	register int f;
	register char *m;
	off_t initial_pos, position();
	static off_t prev_pos;
	static int didpipe;
	char message[MAXPATHLEN + 50], *p;
	char *rindex(), *strerror(), *save(), *bad_file();
	extern int horiz_off, wraplines;

	if (force_open == NO_FORCE_OPEN &&
	    current_file && filename && !strcmp(filename, current_file))
		return(1);

	/* Okay since later code in this fcn() always forces redraw() */
	horiz_off = 0;
	wraplines = 1;

	initial_pos = NULL_POSITION;
	if (filename == NULL || *filename == '\0') {
		if (curr_ac >= ac) {
			error("No current file");
			return(0);
		}
		filename = save(av[curr_ac]);
	}
	else if (strcmp(filename, "#") == 0) {
		if (!previous_file || *previous_file == '\0') {
			error("no previous file");
			return(0);
		}
		filename = save(previous_file);
		initial_pos = prev_pos;
	} else
		filename = save(filename);

	/* use standard input. */
	if (!strcmp(filename, "-")) {
		if (didpipe) {
			error("can view standard input only once");
			return(0);
		}
		f = 0;
	}
	else if ((m = bad_file(filename, message, sizeof(message))) != NULL) {
		error(m);
		free(filename);
		return(0);
	}
	else if ((f = open(filename, O_RDONLY, 0)) < 0) {
		(void)snprintf(message, sizeof(message),
		   "%s: %s", filename, strerror(errno));
		error(message);
		free(filename);
		return(0);
	}

	if (isatty(f)) {
		/*
		 * Not really necessary to call this an error,
		 * but if the control terminal (for commands)
		 * and the input file (for data) are the same,
		 * we get weird results at best.
		 */
		error("Can't take input from a terminal");
		if (f > 0)
			(void)close(f);
		(void)free(filename);
		return(0);
	}

	/*
	 * We are now committed to using the new file.
	 * Close the current input file and set up to use the new one.
	 */
	if (file > 0)
		(void)close(file);
	if (previous_file != NULL)
		free(previous_file);
	previous_file = current_file;
	current_file = filename;
	pos_clear();
	prev_pos = position(TOP);
	ispipe = (f == 0);
	if (ispipe) {
		didpipe = 1;
		current_name = "stdin";
	} else
		current_name = (p = rindex(filename, '/')) ? p + 1 : filename;
	if (curr_ac >= ac)
		next_name = NULL;
	else
		next_name = av[curr_ac + 1];
	file = f;
	ch_init(cbufs, 0);

	if (isatty(STDOUT_FILENO)) {
		int no_display = !any_display;
		any_display = 1;
		if (no_display && errmsgs > 0) {
			/*
			 * We displayed some messages on error output
			 * (file descriptor 2; see error() function).
			 * Before erasing the screen contents,
			 * display the file name and wait for a keystroke.
			 */
			error(filename);
		}
		/*
		 * Indicate there is nothing displayed yet.
		 */
		if (initial_pos != NULL_POSITION)
			jump_loc(initial_pos);
		clr_linenum();
	}
	return(1);
}

/*
 * Edit the next file in the command line list.
 */
next_file(n)
	int n;
{
	extern int quit_at_eof;
	off_t position();

	if (curr_ac + n >= ac) {
		if (quit_at_eof || position(TOP) == NULL_POSITION)
			return;
		error("No (N-th) next file");
	}
	else
		(void)edit(av[curr_ac += n], FORCE_OPEN);
}

/*
 * Edit the previous file in the command line list.
 */
prev_file(n)
	int n;
{
	if (curr_ac - n < 0)
		error("No (N-th) previous file");
	else
		(void)edit(av[curr_ac -= n], FORCE_OPEN);
}

/*
 * copy a file directly to standard output; used if stdout is not a tty.
 * the only processing is to squeeze multiple blank input lines.
 */
static
cat_file()
{
	extern int squeeze;
	register int c, empty;

	if (squeeze) {
		empty = 0;
		while ((c = ch_forw_get()) != EOI)
			if (c != '\n') {
				putchr(c);
				empty = 0;
			}
			else if (empty < 2) {
				putchr(c);
				++empty;
			}
	}
	else while ((c = ch_forw_get()) != EOI)
		putchr(c);
	flush();
}

main(argc, argv)
	int argc;
	char **argv;
{
	int envargc, argcnt;
	char *envargv[3];

	(void) setlocale(LC_ALL, "");

	/*
	 * Process command line arguments and MORE environment arguments.
	 * Command line arguments override environment arguments.
	 */
	if (envargv[1] = getenv("MORE")) {
		envargc = 2;
		envargv[0] = "more";
		envargv[2] = NULL;
		(void)option(envargc, envargv);
	}
	argcnt = option(argc, argv);
	argv += argcnt;
	argc -= argcnt;

	/*
	 * Set up list of files to be examined.
	 */
	ac = argc;
	av = argv;
	curr_ac = 0;

	marks_initmarks();

	/*
	 * Set up terminal, etc.
	 */
	if (!isatty(STDOUT_FILENO)) {
		/*
		 * Output is not a tty.
		 * Just copy the input file(s) to output.
		 */
		if (ac < 1) {
			(void)edit("-", NOFLAGS);
			if (file >= 0)
				cat_file();
		} else {
			do {
				(void)edit((char *)NULL, FORCE_OPEN);
				if (file >= 0)
					cat_file();
			} while (++curr_ac < ac);
		}
		exit(0);
	}

	raw_mode(1);
	get_term();
	open_getchr();
	init();
	init_signals(1);

	/* select the first file to examine. */
	if (tagoption) {
		/*
		 * A -t option was given; edit the file selected by the
		 * "tags" search, and search for the proper line in the file.
		 */
		if (!tagfile || !edit(tagfile, NOFLAGS) || tagsearch())
			quit();
	}
	else if (ac < 1)
		(void)edit("-", NOFLAGS);  /* Standard input */
	else {
		/*
		 * Try all the files named as command arguments.
		 * We are simply looking for one which can be
		 * opened without error.
		 */
		do {
			(void)edit((char *)NULL, NOFLAGS);
		} while (file < 0 && ++curr_ac < ac);
	}

	if (file >= 0) {
		/*
		 * Don't call rcfiles() until now so that people who put
		 * wierd things (like "forw_scroll") in their rc file don't
		 * cause us to SEGV.
		 */
		rcfiles();
		commands();
	}
	quit();
	/*NOTREACHED*/
}

/*
 * Copy a string to a "safe" place
 * (that is, to a buffer allocated by malloc).
 */
char *
save(s)
	char *s;
{
	char *p, *strcpy();

	p = malloc((u_int)strlen(s)+1);
	if (p == NULL)
	{
		error("cannot allocate memory");
		quit();
	}
	return(strcpy(p, s));
}

/*
 * Exit the program.
 */
quit()
{
	/*
	 * Put cursor at bottom left corner, clear the line,
	 * reset the terminal modes, and exit.
	 */
	lower_left();
	clear_eol();
	deinit();
	flush();
	raw_mode(0);
	exit(0);
}

/*
 * Read in from each of the three rc files - default, system, user.
 * Calls handle_error() directly to report errors.
 */
rcfiles()
{
	FILE *fd;
	char fbuf[MAXPATHLEN + 1];
	char *c;
	int readrc();
	int savederrno;

	/* The default builtin rc file */
	if ((c = getenv("HOME")) &&
	    strlen(c) + strlen(_PATH_DEFRC) + 1 < MAXPATHLEN) {
		sprintf(fbuf, "%s/%s", c, _PATH_DEFRC);
		fd = fopen(fbuf, "r");
		savederrno = errno;
		if (!fd) {
			if (!access(_PATH_SYSMORERC, F_OK)) {
				SETERRSTR(E_EXTERN, "unable to read %s: %s",
				    _PATH_SYSMORERC, strerror(savederrno));
				handle_error();
			}
			/* We'd better have some type of default keys!! */
			goto use_builtin_defrc;
		} else {
			readrc(fd);
			fclose(fd);
		}
	} else {
use_builtin_defrc:
		fd = fropen(DEFRC, str_read);
		readrc(fd);
		fclose(fd);
	}

	/* The system rc file */
	fd = fopen(_PATH_SYSMORERC, "r");
	savederrno = errno;
	if (!fd) {
		if (!access(_PATH_SYSMORERC, F_OK)) {
			SETERRSTR(E_EXTERN, "unable to read %s: %s",
			    _PATH_SYSMORERC, strerror(savederrno));
			handle_error();
		} else
			; /* non-existant => non-error */
	} else {
		readrc(fd);
		fclose(fd);
	}

	/* The user rc file */
	if ((c = getenv("HOME")) &&
	    strlen(c) + strlen(_PATH_RC) + 1 < MAXPATHLEN) {
		sprintf(fbuf, "%s/%s", c, _PATH_RC);
		fd = fopen(fbuf, "r");
		savederrno = errno;
		if (!fd) {
			if (!access(fbuf, F_OK)) {
				SETERRSTR(E_EXTERN,
				    "unable to read %s: %s", fbuf,
				    strerror(savederrno));
				handle_error();
			} else
				; /* non-existant => non-error */
		} else {
			readrc(fd);
			fclose(fd);
		}
	}
}

/*
 * Read-in an rc file from a fd.  Calls handle_error() directly to handle
 * errors.
 *
 * This really belongs in ncommand.c, but that file is already 33292 bytes
 * long.
 */
readrc(fd)
	FILE *fd;
{
	char *bufptr, *buf;
	size_t len;
	int strlenbuf;

	buf = NULL;
	strlenbuf = 0;
	while (bufptr = fgetln(fd, &len)) {
		if (!len)
			continue;  /* ??? */
		if (*bufptr == '#')
			continue;  /* skip comments */
		if (!(buf = reallocf(buf, strlenbuf + len + 1))) {
			SETERR(E_MALLOC);
			handle_error();
			if (strlenbuf + len < 1024)
				return;  /* major memory shortage... */
			continue;
		}
		memcpy (buf + strlenbuf, bufptr, len);
		buf[len + strlenbuf] = '\0';
		if (len > 1 && buf[strlenbuf + len - 2] == '\\') {
			/* line continuation */
			buf[strlenbuf + len - 2] = '\0';
			strlenbuf = strlen(buf);
			continue;
		}
		if (buf[len + strlenbuf - 1] == '\n')
			buf[len + strlenbuf - 1] = '\0';
		if (command(buf))
			handle_error();
		free(buf);
		buf = NULL;
		strlenbuf = 0;
	}
}

/*
 * Read from the NUL-terminated cookie.  Non-reentrant: keeps a static pointer
 * to the current position in the cookie.  Used for funopen().
 */
static int
str_read(cookie, buf, len)
	void *cookie;
	char *buf;
	size_t len;
{
	static char *curpos;
	static int cooklen;
	static char *lastcook;

	if (lastcook != cookie) {
		/* begin working on a new cookie */
		curpos = cookie;
		lastcook = cookie;
		cooklen = strlen(cookie);
	}

	if (curpos + len > lastcook + cooklen) {
		ssize_t r;
		memcpy(buf, curpos, r = (cooklen - (curpos - lastcook)));
		curpos = cookie + cooklen;
		return (int) r;
	} else {
		memcpy(buf, curpos, len);
		curpos += len;
		return (int) len;
	}
}


syntax highlighted by Code2HTML, v. 0.9.1