/*	$OpenBSD: cmd.c,v 1.63 2014/07/20 19:33:54 tobias Exp $	*/

/*
 * Copyright (c) 1997-1999 Michael Shalayeff
 * 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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.
 */

#include <sys/param.h>
#include <sys/reboot.h>

#include <libsa.h>
#include <lib/libkern/funcs.h>

#include "cmd.h"

#define CTRL(c)	((c)&0x1f)

static int Xboot(void);
static int Xecho(void);
static int Xhelp(void);
static int Xls(void);
static int Xnop(void);
static int Xreboot(void);
static int Xstty(void);
static int Xtime(void);
#ifdef MACHINE_CMD
static int Xmachine(void);
extern const struct cmd_table MACHINE_CMD[];
#endif
extern int Xset(void);
extern int Xenv(void);

#ifdef CHECK_SKIP_CONF
extern int CHECK_SKIP_CONF(void);
#endif

extern const struct cmd_table cmd_set[];
const struct cmd_table cmd_table[] = {
	{"#",      CMDT_CMD, Xnop},  /* XXX must be first */
	{"boot",   CMDT_CMD, Xboot},
	{"echo",   CMDT_CMD, Xecho},
	{"env",    CMDT_CMD, Xenv},
	{"help",   CMDT_CMD, Xhelp},
	{"ls",     CMDT_CMD, Xls},
#ifdef MACHINE_CMD
	{"machine",CMDT_MDC, Xmachine},
#endif
	{"reboot", CMDT_CMD, Xreboot},
	{"set",    CMDT_SET, Xset},
	{"stty",   CMDT_CMD, Xstty},
	{"time",   CMDT_CMD, Xtime},
	{NULL, 0},
};

static void ls(char *, struct stat *);
static int readline(char *, size_t, int);
char *nextword(char *);
static char *whatcmd(const struct cmd_table **ct, char *);
static char *qualify(char *);

char cmd_buf[CMD_BUFF_SIZE];

int
getcmd(void)
{
	cmd.cmd = NULL;

	if (!readline(cmd_buf, sizeof(cmd_buf), cmd.timeout))
		cmd.cmd = cmd_table;

	return docmd();
}

int
read_conf(void)
{
#ifndef INSECURE
	struct stat sb;
#endif
	int fd, rc = 0;

#ifdef CHECK_SKIP_CONF
	if (CHECK_SKIP_CONF()) {
		printf("boot.conf processing skipped at operator request\n");
		cmd.timeout = 0;
		return -1;		/* Pretend file wasn't found */
	}
#endif

	if ((fd = open(qualify(cmd.conf), 0)) < 0) {
		if (errno != ENOENT && errno != ENXIO) {
			printf("open(%s): %s\n", cmd.path, strerror(errno));
			return 0;
		}
		return -1;
	}

#ifndef INSECURE
	(void) fstat(fd, &sb);
	if (sb.st_uid || (sb.st_mode & 2)) {
		printf("non-secure %s, will not proceed\n", cmd.path);
		close(fd);
		return -1;
	}
#endif

	do {
		char *p = cmd_buf;

		cmd.cmd = NULL;
		do {
			rc = read(fd, p, 1);
		} while (rc > 0 && *p++ != '\n' &&
		    (p-cmd_buf) < sizeof(cmd_buf));

		if (rc < 0) {			/* Error from read() */
			printf("%s: %s\n", cmd.path, strerror(errno));
			break;
		}

		if (rc == 0) {			/* eof from read() */
			if (p != cmd_buf) {	/* Line w/o trailing \n */
				*p = '\0';
				rc = docmd();
				break;
			}
		} else {			/* rc > 0, read a char */
			p--;			/* Get back to last character */

			if (*p != '\n') {	/* Line was too long */
				printf("%s: line too long\n", cmd.path);

				/* Don't want to run the truncated command */
				rc = -1;
			}
			*p = '\0';
		}
	} while (rc > 0 && !(rc = docmd()));

	close(fd);
	return rc;
}

int
docmd(void)
{
	char *p = NULL;
	const struct cmd_table *ct = cmd_table, *cs;

	cmd.argc = 1;
	if (cmd.cmd == NULL) {

		/* command */
		for (p = cmd_buf; *p == ' ' || *p == '\t'; p++)
			;
		if (*p == '#' || *p == '\0') { /* comment or empty string */
#ifdef DEBUG
			printf("rem\n");
#endif
			return 0;
		}
		ct = cmd_table;
		cs = NULL;
		cmd.argv[cmd.argc] = p; /* in case it's shortcut boot */
		p = whatcmd(&ct, p);
		if (ct == NULL) {
			cmd.argc++;
			ct = cmd_table;
		} else if (ct->cmd_type == CMDT_SET && p != NULL) {
			cs = cmd_set;
#ifdef MACHINE_CMD
		} else if (ct->cmd_type == CMDT_MDC && p != NULL) {
			cs = MACHINE_CMD;
#endif
		}

		if (cs != NULL) {
			p = whatcmd(&cs, p);
			if (cs == NULL) {
				printf("%s: syntax error\n", ct->cmd_name);
				return 0;
			}
			ct = cs;
		}
		cmd.cmd = ct;
	}

	cmd.argv[0] = ct->cmd_name;
	while (p && cmd.argc+1 < sizeof(cmd.argv) / sizeof(cmd.argv[0])) {
		cmd.argv[cmd.argc++] = p;
		p = nextword(p);
	}
	cmd.argv[cmd.argc] = NULL;

	return (*cmd.cmd->cmd_exec)();
}

static char *
whatcmd(const struct cmd_table **ct, char *p)
{
	char *q;
	int l;

	q = nextword(p);

	for (l = 0; p[l]; l++)
		;

	while ((*ct)->cmd_name != NULL && strncmp(p, (*ct)->cmd_name, l))
		(*ct)++;

	if ((*ct)->cmd_name == NULL)
		*ct = NULL;

	return q;
}

static int
readline(char *buf, size_t n, int to)
{
#ifdef DEBUG
	extern int debug;
#endif
	char *p = buf, ch;

	/* Only do timeout if greater than 0 */
	if (to > 0) {
		u_long i = 0;
		time_t tt = getsecs() + to;
#ifdef DEBUG
		if (debug > 2)
			printf ("readline: timeout(%d) at %u\n", to, tt);
#endif
		/* check for timeout expiration less often
		   (for some very constrained archs) */
		while (!cnischar())
			if (!(i++ % 1000) && (getsecs() >= tt))
				break;

		if (!cnischar()) {
			strlcpy(buf, "boot", 5);
			putchar('\n');
			return strlen(buf);
		}
	} else
		while (!cnischar())
			;

	/* User has typed something.  Turn off timeouts. */
	cmd.timeout = 0;

	while (1) {
		switch ((ch = getchar())) {
		case CTRL('u'):
			while (p > buf) {
				putchar('\177');
				p--;
			}
			continue;
		case '\n':
		case '\r':
			*p = '\0';
			break;
		case '\b':
		case '\177':
			if (p > buf) {
				putchar('\177');
				p--;
			}
			continue;
		default:
			if (ch >= ' ' && ch < '\177') {
				if (p - buf < n-1)
					*p++ = ch;
				else {
					putchar('\007');
					putchar('\177');
				}
			}
			continue;
		}
		break;
	}

	return p - buf;
}

/*
 * Search for spaces/tabs after the current word. If found, \0 the
 * first one.  Then pass a pointer to the first character of the
 * next word, or NULL if there is no next word.
 */
char *
nextword(char *p)
{
	/* skip blanks */
	while (*p && *p != '\t' && *p != ' ')
		p++;
	if (*p) {
		*p++ = '\0';
		while (*p == '\t' || *p == ' ')
			p++;
	}
	if (*p == '\0')
		p = NULL;
	return p;
}

static void
print_help(const struct cmd_table *ct)
{
	for (; ct->cmd_name != NULL; ct++)
		printf(" %s", ct->cmd_name);
	putchar('\n');
}

static int
Xhelp(void)
{
	printf("commands:");
	print_help(cmd_table);
#ifdef MACHINE_CMD
	return Xmachine();
#else
	return 0;
#endif
}

#ifdef MACHINE_CMD
static int
Xmachine(void)
{
	printf("machine:");
	print_help(MACHINE_CMD);
	return 0;
}
#endif

static int
Xecho(void)
{
	int i;

	for (i = 1; i < cmd.argc; i++)
		printf("%s ", cmd.argv[i]);
	putchar('\n');
	return 0;
}

static int
Xstty(void)
{
	int sp;
	char *cp;
	dev_t dev;

	if (cmd.argc == 1) {
		printf("%s speed is %d\n", ttyname(0), cnspeed(0, -1));
		return 0;
	}
	dev = ttydev(cmd.argv[1]);
	if (dev == NODEV) {
		printf("%s not a console device\n", cmd.argv[1]);
		return 0;
	}

	if (cmd.argc == 2)
		printf("%s speed is %d\n", cmd.argv[1],
		    cnspeed(dev, -1));
	else {
		sp = 0;
		for (cp = cmd.argv[2]; isdigit(*cp); cp++)
			sp = sp * 10 + (*cp - '0');
		cnspeed(dev, sp);
	}
	return 0;
}

static int
Xtime(void)
{
	time_t tt = getsecs();

	if (cmd.argc == 1)
		printf(ctime(&tt));

	return 0;
}

static int
Xls(void)
{
	struct stat sb;
	char *p;
	int fd;

	if (stat(qualify((cmd.argv[1]? cmd.argv[1]: "/.")), &sb) < 0) {
		printf("stat(%s): %s\n", cmd.path, strerror(errno));
		return 0;
	}

	if ((sb.st_mode & S_IFMT) != S_IFDIR)
		ls(cmd.path, &sb);
	else {
		if ((fd = opendir(cmd.path)) < 0) {
			printf("opendir(%s): %s\n", cmd.path,
			    strerror(errno));
			return 0;
		}

		/* no strlen in lib !!! */
		for (p = cmd.path; *p; p++)
			;
		*p++ = '/';
		*p = '\0';

		while (readdir(fd, p) >= 0) {
			if (stat(cmd.path, &sb) < 0)
				printf("stat(%s): %s\n", cmd.path,
				    strerror(errno));
			else
				ls(p, &sb);
		}
		closedir (fd);
	}
	return 0;
}

#define lsrwx(mode,s) \
	putchar ((mode) & S_IROTH? 'r' : '-'); \
	putchar ((mode) & S_IWOTH? 'w' : '-'); \
	putchar ((mode) & S_IXOTH? *(s): (s)[1]);

static void
ls(char *name, struct stat *sb)
{
	putchar("-fc-d-b---l-s-w-"[(sb->st_mode & S_IFMT) >> 12]);
	lsrwx(sb->st_mode >> 6, (sb->st_mode & S_ISUID? "sS" : "x-"));
	lsrwx(sb->st_mode >> 3, (sb->st_mode & S_ISGID? "sS" : "x-"));
	lsrwx(sb->st_mode     , (sb->st_mode & S_ISTXT? "tT" : "x-"));

	printf (" %u,%u\t%lu\t%s\n", sb->st_uid, sb->st_gid,
	    (u_long)sb->st_size, name);
}
#undef lsrwx

int doboot = 1;

static int
Xnop(void)
{
	if (doboot) {
		doboot = 0;
		return (Xboot());
	}

	return 0;
}

static int
Xboot(void)
{
	if (cmd.argc > 1 && cmd.argv[1][0] != '-') {
		qualify((cmd.argv[1]? cmd.argv[1]: cmd.image));
		if (bootparse(2))
			return 0;
	} else {
		if (bootparse(1))
			return 0;
		snprintf(cmd.path, sizeof cmd.path, "%s:%s",
		    cmd.bootdev, cmd.image);
	}

	return 1;
}

/*
 * Qualifies the path adding necessary dev
 */

static char *
qualify(char *name)
{
	char *p;

	for (p = name; *p; p++)
		if (*p == ':')
			break;
	if (*p == ':')
		strlcpy(cmd.path, name, sizeof(cmd.path));
	else
		snprintf(cmd.path, sizeof cmd.path, "%s:%s",
		    cmd.bootdev, name);
	return cmd.path;
}

static int
Xreboot(void)
{
	printf("Rebooting...\n");
	exit();
	return 0; /* just in case */
}

