/* $NetBSD: fsu_console.c,v 1.1 2009/03/23 21:03:56 stacktic Exp $ */

/*
 * Copyright (c) 2008 Arnaud Ysmal.  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 AUTHOR 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/syslimits.h>
#include <sys/wait.h>

#include <assert.h>
#include <ctype.h>
#include <dirent.h>
#include <err.h>
#include <fcntl.h>
#include <grp.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <termios.h>
#include <time.h>
#include <tzfile.h>
#include <unistd.h>

#include <prop_mount.h>
#include <fsurs_utils.h>

#include "extern.h"
#include "fsu_console.h"

static int		loop(FILE *);
static int		read_line(char *, int);
static int		read_script(char *, int, FILE *);
static char		*str2arg(char *, int *, char **);
static enum fsu_op_e	next_command(char *, char **, char **, FILE *);
int			fsu_write(int, const char *, int);

extern char cwd[], old_wd[];
extern alias_t *al_head, *al_tail;
extern command_t commands[];
char *fstype, *fsdevice;

/* Init global variables and run the loop */
int
fsu_console(char *fst, char *fsd, char *script)
{
	int rv;
	FILE *fd;

	fstype = fst;
	fsdevice = fsd;

	setenv("FSU_DEVICE", fsdevice, 1);
	setenv("FSU_TYPE", fstype, 1);
	setenv("FSU_CWD", "/", 1);

      	if (script != NULL) {
		fd = fopen(script, "r");
		if (fd == NULL) {
			warn("%s", script);
			return -1;
		}
	} else
		fd = NULL;

	cwd[0] = old_wd[0] = '/';
	cwd[1] = old_wd[1] = '\0';

 	rv = loop(fd);
	/* NOTREACHED */

	assert(0);
	exit(EXIT_FAILURE); /* make GCC happy */
}

static int
loop(FILE *script)
{
	command_t *com;
	pid_t child;
	int argc, rv;
	char *argv[MAX_OPTIONS+1], buffer[MAX_LINE_SIZE+1], *command;
	char *curcom, *nextcom, *fsucom;
	bool redir;
	enum fsu_op_e op;

	curcom = nextcom = NULL;
	redir = false;
	argc = 0;
	op = ALL;
	rv = 0;
	for (;;) {
		optind = optreset = 1;
	
		op = next_command(buffer, &curcom, &nextcom, script);

		if (strchr(curcom, '>') != NULL)
			printf("no redir\n");

		/* allow local commands */
		if (curcom[0] == '!') {
			rv = system(curcom + 1);
			if (rv == -1)
				warn("%s", curcom + 1);
			goto next;
		}

		command = str2arg(curcom, &argc, argv);
		if (command == NULL)
			goto next;

		/* look for the right command and execute it */
		for (com = commands; com->c_name != NULL; com++)
			if (strcmp(command, com->c_name) == 0) {
				if (com->c_fun != NULL) { /* built-in */
					rv = com->c_fun(argc, argv);
					break;
				}

				switch (child = fork()) {
				case -1:
					warn("fork");
					break;
				case 0:
					rv = strlen(argv[0]) + 5;
					fsucom = malloc(rv);
					snprintf(fsucom, rv, "fsu_%s", argv[0]);
					execvp(fsucom, argv);
					warn("execvp");
					exit(-1);
					/* NOTREACHED */
				default:
					if (waitpid(child, &rv, 0) == -1)
						warn("waitpid");
					rv = WIFEXITED(rv) ?
						WEXITSTATUS(rv) : -1;
				}
				break;
			}

		if (com->c_name == NULL) {
			fprintf(stderr, "%s: command not found\n", command);
			rv = -1;
		}

	next:
		/* evaluates conditions */
		if ((op == OR && rv == 0) || (op == AND && rv != 0)) {
			/*
			 * skip remaining commands but stop skiping if there
			 * is a ';'
			 */
			while (nextcom != NULL &&
			       (op = next_command(buffer, &curcom,
						  &nextcom, script)) != ALL)
				continue;
		}
	}
	/* NOTREACHED */
	assert(0);
}


/* read a line from a file */
static int
read_script(char *buf, int bufsize, FILE *script)
{
	int c, i, quote;
	bool inquote;
	
	inquote = false;
	quote = i = 0;
	while (i < bufsize) {
		c = fgetc(script);
		if ((c == '\n' || c == ' ') && i == 0)
			continue;
		if (c == '#' && !inquote && !(i != 0 && buf[i - 1] == '\\')) {
			while ((c = fgetc(script)) != '\n')
				continue;
			if (i == 0)
				continue;
			break;
		}
		if (c == '\n' || c == EOF)
			break;
		if ((c == '\'' || c == '"') && !inquote) {
			inquote = true;
			quote = c;
		} else if (inquote && c == quote)
			inquote = false;
		buf[i++] = c;
	}
	
	buf[i] = '\0';
	return i;
}

/* read a line from stdin */
static int
read_line(char *buf, int bufsize)
{
	struct termios tm;
	int i, rv;
	tcflag_t old_lflag;
	cc_t old_verase;

	fflush(stdout);
	
	rv = tcgetattr(STDIN_FILENO, &tm);
	if (rv != 0) {
		warn("tcgetattr");
		return -1;
	}
	
	old_lflag = tm.c_lflag;
	old_verase = tm.c_cc[VERASE];

	tm.c_cc[VERASE] = '\b';
	tm.c_lflag |= ICANON | ECHOE;

	rv = tcsetattr(STDIN_FILENO, TCSANOW, &tm);
	if (rv != 0) {
		warn("tcsetattr");
		return -1;
	}
	
	i = read(STDIN_FILENO, buf, bufsize) - 1;
	if (i == -1) {
		warn("read");
		i = -1;
	} else
		buf[i] = '\0';

	tm.c_cc[VERASE] = old_verase;
	tm.c_lflag = old_lflag;	

	rv = tcsetattr(STDIN_FILENO, TCSANOW, &tm);
	if (rv != 0) {
		warn("tcsetattr");
		return -1;
	}

	return i;
}

/*
 * get argc and argv from a string
 */
static char
*str2arg(char *str, int *argc, char **argv)
{
	alias_t *cur;
	int i;
	char *p, *lp, delim;
	bool inquote;

	inquote = false;
	delim = '\0';
	lp = NULL;

	*argc = 2;
	argv[0] = str;
	argv[1] = strdup("--");
	
	/* replace the command name with the alias */
	for (cur = al_head; cur != NULL; cur = cur->al_next)
		if (strcmp(cur->al_name, argv[0]) == 0) {
			argv[0] = cur->al_argv[0];
			for (i = 1; i < cur->al_argc; ++i)
				argv[i + 1] = cur->al_argv[i];
			*argc = cur->al_argc + 1;
			break;
		}

	p = strchr(str, ' ');
	if (p != NULL) {
		*p++ = '\0';
		lp = p;
	} else {
		argv[*argc] = NULL;
		return argv[0];
	}

	while (isspace((int)lp[0]))
		++lp;

	if (lp[0] != '\0')
		argv[(*argc)++] = p;

	for (; lp[0] != '\0' && *argc < MAX_OPTIONS; ++lp) {
		if (lp[0] == '\\')
			*p++ = *++lp;
		else if (inquote && lp[0] == delim)
			inquote = false;
		else if (lp[0] == '"' || lp[0] == '\'') {
			delim = lp[0];
			inquote = true;
		} else if (isspace((int)lp[0]) && !inquote) {
			*p++ ='\0';

			while (isspace((int)lp[1]))
				++lp;

			if (lp[1] != '\0')
				argv[(*argc)++] = p;
		} else
			*p++ = *lp;
	}
	argv[*argc] = NULL;
	*p = '\0';

	if (inquote)
		fprintf(stderr, "Unbalanced quote, processing as if there was "
			"one at the end of the command line\n");
	return argv[0];
}

#ifdef REDIR
/* This function is a thread function. */
static int
write_to_file(struct thr_args_s *arg)
{
	int rv;

	rv = fsu_write(arg->fd, arg->fname, arg->append);

	close(arg->fd);
	free(arg->fname);

	return rv;
}
#endif

/*
 * get next command to execute
 */
enum fsu_op_e
next_command(char *buffer, char **curcom, char **nextcom, FILE *script)
{
	char *next, *tmp;
	enum fsu_op_e op;
	ssize_t len;

	*curcom = *nextcom;

	if (*curcom == NULL) {
		if (script == NULL) {
			len = 0;
			while (len <= 0) {
				if (fsdevice == NULL)
					printf("%s # ", cwd);
				else
					printf("%s(%s):%s # ",
					       fsdevice, fstype, cwd);
				len = read_line(buffer, MAX_LINE_SIZE);
			}
		} else {
			len = read_script(buffer, MAX_LINE_SIZE, script);
			if (len <= 0) {
				fclose(script);
				fsu_exit_main(0, NULL);
			}
		}
		op = ALL;
		*curcom = buffer;
		while (*curcom[0] == ' ')
			(*curcom)++;
	}

	next = strchr(*curcom, ';');
	if (next > buffer)
		while (next != NULL && next[-1] == '\\')
			next = strchr(next + 1, ';');
	op = ALL;

	tmp = strstr(*curcom, "&&");
	if (next == NULL || (tmp != NULL && tmp < next)) {
		next = tmp;
		op = AND;
	}

	tmp = strstr(*curcom, "||");
	if (next == NULL || (tmp != NULL && tmp < next)) {
		next = tmp;
		op = OR;
	}

	if (next != NULL) {
		*next++ = '\0';
		if (next[0] == '&' || next[0] == '|')
			*next++ = '\0';
		while (next[0] == ' ')
			*next++ = '\0';
		if (next[0] == '\0')
			next = NULL;
	}
	*nextcom = next;

	return op;
}
