/*
 * Copyright (c) 2007 Hypertriton, Inc. <http://www.hypertriton.com/>
 * 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 COPYRIGHT HOLDERS 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 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 <agar/compat/strlcpy.h>
#include <agar/compat/strlcat.h>

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <ctype.h>

#include "machctld.h"
#include "prog.h"
#include "pathnames.h"
#include "fgetln.h"
#include "mkpath.h"

static char *
parse_path(NS_Command *cmd, const char *key)
{
	char *path = NS_CommandString(cmd, key);

	if (path[0] == '/' || strstr(path, "..") != NULL) {
		AG_SetError("%s: bad program path: `%s'", key, path);
		return (NULL);
	}
	return (path);
}

static char *
parse_name(NS_Command *cmd, const char *key)
{
	char *name = NS_CommandString(cmd, key);
	const char *p;

	for (p = &key[0]; *p != '\0'; p++) {
		if (*p == '/' || *p == '.' || *p == ' ') {
			AG_SetError("%s: bad name: %s", key, name);
			return (NULL);
		}
	}
	if ((p - &key[0]) > RCSINFO_NAME_MAX) {
		AG_SetError("%s: name too large", key);
		return (NULL);
	}
	return (name);
}

static char *
parse_msg(NS_Command *cmd, const char *key)
{
	char *name = NS_CommandString(cmd, key);
	const char *p;

	for (p = &key[0]; *p != '\0'; p++) {
		if (!isprint(*p)) {
			AG_SetError("%s: non-printable character", key);
			return (NULL);
		}
	}
	if ((p - &key[0]) > RCSINFO_MSG_MAX) {
		AG_SetError("%s: name too large", key);
		return (NULL);
	}
	return (name);
}

static size_t
parse_size(NS_Command *cmd, const char *key)
{
	long lval;

	if (NS_CommandLong(cmd, key, &lval) == -1)
		return (0);

	/* Agar signatures are at least 8 bytes. */
	if (lval < 8) {
		AG_SetError("%s: data file is too small (%lu)", key, lval);
		return (0);
	}
	return ((size_t)lval);
}

int
prog_commit(NS_Command *cmd, void *p)
{
	char buf[BUFSIZ];
	char filepath[MAXPATHLEN];
	char *progname, *objname, *objtype;
	size_t nread = 0, size;
	int fd;

	if ((progname = parse_path(cmd, "prog-name")) == NULL ||
	    (size = parse_size(cmd, "prog-size")) == 0) {
		return (-1);
	}
	if (mkpath(progname, 0755) == -1) {
		return (-1);
	}
	strlcpy(filepath, progname, sizeof(filepath));
	strlcat(filepath, "/", sizeof(filepath));
	strlcat(filepath, progname, sizeof(filepath));
	strlcat(filepath, ".Prog", sizeof(filepath));

	if ((fd = open(filepath, O_CREAT|O_WRONLY|O_EXCL, 0644)) == -1) {
		AG_SetError("%s: %s", filepath, strerror(errno));
		NS_Log(NS_ERR, "%s", AG_GetError());
		return (-1);
	}
	
	NS_Log(NS_INFO, "Importing %s (%lu bytes)", filepath,
	    (u_long)size);

	printf("0 Go ahead\n");

	while (nread < size) {
		size_t csize, rv;

		if ((csize = (size - nread)) > sizeof(buf))
			csize = sizeof(buf);

		if ((rv = fread(buf, 1, csize, stdin)) < csize) {
			AG_SetError("incomplete chunk");
			goto fail;
		}
		if (write(fd, buf, rv) < rv) {
			AG_SetError("write error");
			goto fail;
		}
		nread += rv;
	}

	close(fd);
	printf("0 Program OK\n");
	return (0);
fail:
	close(fd);
	printf("! Import failed (%s)\n", AG_GetError());
	NS_Log(NS_ERR, "Import failed: %s: %s", filepath, AG_GetError());
	return (-1);
}

int
prog_fetch(NS_Command *cmd, void *p)
{
	char buf[BUFSIZ];
	char filepath[MAXPATHLEN];
	char *dirpath, *progname;
	off_t len;
	ssize_t rv;
	int fd;
	
	if ((progname = parse_name(cmd, "prog-name")) == NULL) {
		return (-1);
	}
	strlcpy(filepath, progname, sizeof(filepath));
	strlcat(filepath, "/", sizeof(filepath));
	strlcat(filepath, progname, sizeof(filepath));
	strlcat(filepath, ".Prog", sizeof(filepath));

	NS_Log(NS_INFO, "Sending program %s", filepath);

	if ((fd = open(filepath, O_RDONLY|O_EXCL)) == -1) {
		AG_SetError("%s: %s", filepath, strerror(errno));
		NS_Log(NS_ERR, "%s", AG_GetError());
		return (-1);
	}
	if ((len = lseek(fd, 0, SEEK_END)) == -1) {
		AG_SetError("%s: cannot seek end", filepath);
		goto fail_com;
	}
	if (lseek(fd, 0, SEEK_SET) == -1) {
		AG_SetError("%s: cannot seek set", filepath);
		goto fail_com;
	}

	NS_BinaryMode((size_t)len);

	while ((rv = read(fd, buf, sizeof(buf))) > 0) {
		if (fwrite(buf, 1, rv, stdout) < rv) {
			AG_SetError("%s: error writing to socket", filepath);
			goto fail_bin;
		}
	}
	if (rv < 0) {
		AG_SetError("%s: read error", filepath);
		goto fail_bin;
	}
	close(fd);
	fflush(stdout);
	NS_CommandMode();
	return (0);
fail_bin:
	NS_Log(NS_ERR, "%s", AG_GetError());
	NS_CommandMode();
fail_com:
	close(fd);
	return (-1);
}

int
prog_list(NS_Command *cmd, void *p)
{
	struct stat sb;
	struct dirent *dent;
	DIR *dir;

	if ((dir = opendir(_PATH_DATA)) == NULL) {
		AG_SetError("%s: %s", _PATH_DATA, strerror(errno));
		NS_Log(NS_CRIT, "%s", AG_GetError());
		return (-1);
	}
	NS_BeginList();
	while ((dent = readdir(dir)) != NULL) {
		if (dent->d_name[0] == '.' ||
		    stat(dent->d_name, &sb) == -1) {
			continue;
		}
		if ((sb.st_mode & S_IFDIR) == S_IFDIR) {
			NS_ListString("%s:%lu", dent->d_name,
			    (unsigned long)sb.st_size);
		}
	}
	closedir(dir);
	NS_EndList();
	return (0);
}
