/* $NetBSD: commands.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/stat.h>
#include <sys/syslimits.h>

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <fsurs_utils.h>

#include <rump/rump_syscalls.h>

#include "extern.h"

#define BUFSIZE (8192)

static int fsu_get_cwd(char *);

/* current working directory and last working directory */
char cwd[PATH_MAX + 1], old_wd[PATH_MAX + 1];

/* head and tail for the alias linked list */
alias_t *al_head, *al_tail;

command_t commands[] = {
	{ "alias",   "Add an alias",		      fsu_alias_main },
	{ "cat",     "Concatenate and print files",   NULL },
	{ "cd",	     "Change working directory",      fsu_cd_main },
	{ "chflags", "Change file flags",	      NULL },
	{ "chmod",   "Change file modes",	      NULL },
	{ "chown",   "Change file owner and group",   NULL },
	{ "cp",	     "Copy files",		      NULL },
	{ "diff",    "Compare files",		      NULL },
	{ "du",	     "Display disk usage statistics", NULL },
	{ "exit",    "Exit this program",	      fsu_exit_main },
	{ "exec",    "Execute an external command",   NULL },
	{ "find",    "Look for a file",		      NULL },
	{ "get",     "Copy a file from the image",    NULL },
	{ "help",    "Show these messages",	      fsu_help_main },
	{ "ln",	     "Make links",		      NULL },
	{ "ls",	     "List directory contents",	      NULL },
	{ "mkdir",   "Make directories",	      NULL },
	{ "mkfifo",  "Make fifos",		      NULL },
	{ "mknod",   "Make device special file",      NULL },
	{ "mv",	     "Move files",		      NULL },
	{ "put",     "Copy a file to the image",      NULL },
	{ "pwd",     "Print working directory name",  fsu_pwd_main },
	{ "quit",    "Exit this program",	      fsu_exit_main },
	{ "rm",	     "Remove directory entries",      NULL },
	{ "rmdir",   "Remove directories",	      NULL },
	{ "touch",   "Change file times",	      NULL },
	{ "umask",   "Set file creation mode mask",   fsu_umask_main },
	{ "unalias", "Remove an alias",		      fsu_unalias_main },
	{ "write",   "Write stdin to a file",	      NULL },
	{ NULL,	     NULL,			      NULL }
};

int
fsu_alias_main(int argc, char *argv[])
{
	alias_t *cur;
	int i;
	char *name, *cmd;

	--argc;
	++argv;

	/*
	 * No arguments: printing alias list.
	 * One argument: if it contains a '=' it is a new alias
	 *               if this argument match an alias print it.
	 */
	if (argc == 1) {
		for (cur = al_head; cur != NULL; cur = cur->al_next) {
			printf("%s=\"", cur->al_name);
			for (i = 0; i < cur->al_argc; ++i)
				printf("%s ", cur->al_argv[i]);
			printf("\b\"\n");
		}
		return 0;
	} else if (argc == 2) {
		cmd = strchr(argv[1], '=');
		if (cmd != NULL) {
			*cmd = '\0';
			++cmd;
			name = argv[1];
		} else {
			for (cur = al_head; cur != NULL; cur = cur->al_next) {
				if (strcmp(cur->al_name, argv[1]) == 0) {
					printf("%s=\"", cur->al_name);
					for (i = 0; i < cur->al_argc; ++i)
						printf("%s ", cur->al_argv[i]);
					printf("\b\"\n");
					break;
				}
			}
			return 0;
		}
	} else {
		name = argv[1];
		cmd = argv[2];
	}
	
	/* check whether this alias exists */
	for (cur = al_head; cur != NULL; cur = cur->al_next) {
		if (strcmp(cur->al_name, name) == 0) {
			fprintf(stderr, "alias %s: already exists\n",
				cur->al_name);
			return -1;
		}
	}

	/* this alias does not exists, add it to the linked list */
	cur = malloc(sizeof(alias_t));
	if (cur == NULL) {
		warn("malloc");
		return -1;
	}

	cur->al_name = strdup(name);
	if (cur->al_name == NULL) {
		warn("strdup");
		goto mem3;
	}

	cur->al_args = strdup(cmd);
	if (cur->al_args == NULL) {
		warn("malloc");
		goto mem2;
	}

	cur->al_argc = fsu_str2argc(cmd);

	cur->al_argv = malloc((cur->al_argc + 1) * sizeof(char *));
	if (cur->al_argv == NULL) {
		warn("malloc");
		goto mem1;
	}

	fsu_str2arg(cmd, &cur->al_argc, cur->al_argv, cur->al_argc + 1);

	cur->al_next = NULL;

	if (al_tail != NULL)
		al_tail = al_tail->al_next = cur;
	else	
		al_tail = al_head = cur;

	return 0;	

mem1:
	free(cur->al_args);
mem2:
	free(cur->al_name);
mem3:
	free(cur);

	return -1;
}

/* Removes an alias from the linked list */
int
fsu_unalias_main(int argc, char *argv[])
{
	alias_t *cur, *tmp;

	--argc;
	++argv;

	if (argc < 2) {
		fprintf(stderr, "Usage: unalias alias...\n");
		return -1;
	}
	if (al_head == NULL) {
		fprintf(stderr, "unalias: alias list is empty\n");
		return -1;
	}
	
	for (++argv; *argv != NULL; ++argv) {
		tmp = NULL;
		if (strcmp(*argv, al_head->al_name) == 0) {
			tmp = al_head;
			al_head = al_head->al_next;
			if (al_head == NULL)
				al_tail = NULL;
		} else
			for (cur = al_head;
			     cur->al_next != NULL;
			     cur = cur->al_next)
				if (strcmp(cur->al_next->al_name, *argv) == 0) {
					tmp = cur->al_next;
					cur->al_next = cur->al_next->al_next;
					break;
				}
		if (tmp == NULL) {
			fprintf(stderr, "%s: no such alias\n", *argv);
			continue;
		}
		
		/* freeing */
		free(tmp->al_name);
		free(tmp->al_args);
		free(tmp->al_argv);
		free(tmp);
	}
	return 0;
}

/*
 * function for the cd command, supports "cd", "cd -" and "cd pathname"
 */
int
fsu_cd_main(int argc, char *argv[])
{
	int rv;
	char tmp[PATH_MAX + 1];

	--argc;
	++argv;

	if (argc == 1) {
		/* "cd" go to root directory */
		rv = strlcpy(old_wd, cwd, PATH_MAX + 1);
		if (rv == -1) {
			warn("%s: %s", argv[0], cwd);
			return -1;
		}
		cwd[0] = '/';
		cwd[1] = '\0';
	} else if (argc == 2 && strcmp(argv[1], "-") == 0) {
		/* "cd -" go to previous directory (swap cwd and old_cwd) */
		rv = strlcpy(tmp, old_wd, PATH_MAX + 1);
		if (rv == -1) {
			warn("%s: %s", argv[0], old_wd);
			return -1;
		}

		strlcpy(old_wd, cwd, PATH_MAX + 1);
		if (rv == -1) {
			warn("%s: %s", argv[0], cwd);
			strlcpy(old_wd, tmp, PATH_MAX + 1);
			return -1;
		}

		rv = strlcpy(cwd, tmp, PATH_MAX + 1);
		if (rv == -1) {
			warn("%s: %s", argv[0], tmp);
			strlcpy(cwd, old_wd, PATH_MAX + 1);
			strlcpy(old_wd, tmp, PATH_MAX + 1);
			return -1;
		}
		printf("%s\n", cwd);
	} else if (argc == 2) {
		/* check whether the given directory exists. */
		rv = rump_sys_chdir(argv[1]);
		if (rv == -1) {
			rump_sys_chdir(cwd);
			warn("%s: %s", argv[0], argv[1]);
			return -1;
		}
		strlcpy(old_wd, cwd, PATH_MAX + 1);

		fsu_get_cwd(cwd);
		setenv("FSU_CWD", cwd, 1);

		return 0;
	} else {
		fprintf(stderr, "usage: cd path\n");
		return -1;
	}

	rv = rump_sys_chdir(cwd);
	if (rv == -1) {
		warn("%s: %s", argv[0], cwd);
		return -1;
	}
	setenv("FSU_CWD", cwd, 1);
	return 0;
}

int
fsu_help_main(int argc, char *argv[])
{
	command_t *com;
	
	for (com = commands; com->c_name != NULL; com++)
		printf("%-10s: %s\n", com->c_name, com->c_help);
	return 0;
}

int
fsu_exit_main(int argc, char *argv[])
{

	exit(EXIT_SUCCESS);
}

int
fsu_umask_main(int argc, char **argv)
{
	mode_t mode, *set;

	--argc;
	++argv;

	umask((mode = umask(0)));
	switch (argc) {
	case 1:
		printf("%04o\n", mode);
		break;
	case 2:
		set = setmode(argv[1]);
		mode = getmode(set, ~mode);
		free(set);
		umask(~mode);
		break;
	default:
		fprintf(stderr, "usage: %s [mode]\n", argv[0]);
		return -1;
	}
	return 0;
}

int
fsu_pwd_main(int argc, char *argv[])
{

	printf("%s\n", cwd);
	return 0;
}

static int
fsu_get_cwd(char *path)
{
	char *npath;

	npath = fsu_getcwd();
	strlcpy(path, npath, PATH_MAX + 1);
	free(npath);
	return 0;
}
