/* $NetBSD: fsu_exec.c,v 1.1 2009/03/23 21:03:57 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 <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <rump/rump_syscalls.h>

#include <prop_mount.h>

static void usage(void);

#define BUFSIZE (8192)

static int copy_file(const char *, const char *, bool);
static void usage(void);

int
main(int argc, char **argv)
{
	char tmpfname[PATH_MAX];
	int len, rv;
	struct stat sb;
	pid_t child;
	char *from;

	setprogname(argv[0]);

	FSU_MOUNT(argc, argv);

	if (argc < 3)
		usage();
	++argv;
	--argc;

	from = argv[argc - 1];
	argv[argc - 1] = tmpfname;

	rv = snprintf(tmpfname, PATH_MAX-1, "/tmp/fsutmp.%i", getpid());
	if (rv <= 0) {
		fprintf(stderr, "error in snprintf\n");
		return -1;
	}

	if (stat(tmpfname, &sb) == 0)
		unlink(tmpfname);

	copy_file(from, tmpfname, true);

	child = fork();
	switch (child) {
	case -1:
		warn("fork");
		rv = -1;
		goto out;
	case 0:
		execvp(argv[0], argv);
		exit(EXIT_FAILURE);
	default:
		waitpid(child, &len, 0);
	}

	copy_file(tmpfname, from, false);

out:
	unlink(tmpfname);
	if (WIFEXITED(len))
		return WEXITSTATUS(len);
	return -1;
}

struct op_s {
	int (*op_open)(const char *, int, mode_t);
	int (*op_close)(int);
	ssize_t (*op_read)(int, void *, size_t);
	ssize_t (*op_write)(int, const void *, size_t);
	int (*op_stat)(const char *, struct stat *);
};

/*
 * get == true -> Copying from the image to the real fs
 * get == false -> Copying from the real fs to the image
 */
static int
copy_file(const char *from, const char *to, bool get)
{
	uint8_t buf[BUFSIZE];
	ssize_t rd, wr;
	int fd, rv, fd2;
	struct stat from_stat;
	struct op_s op_from, op_to;

	if (get) {
		op_from.op_open = rump_sys_open;
		op_from.op_close = rump_sys_close;
		op_from.op_read = rump_sys_read;
		op_from.op_write = rump_sys_write;
		op_from.op_stat = rump_sys_stat;

		op_to.op_open = (int (*)(const char *, int, mode_t))open;
		op_to.op_close = close;
		op_to.op_read = read;
		op_to.op_write = write;
		op_to.op_stat = stat;
	} else {
		op_from.op_open = (int (*)(const char *, int, mode_t))open;
		op_from.op_close = close;
		op_from.op_read = read;
		op_from.op_write = write;
		op_from.op_stat = stat;

		op_to.op_open = rump_sys_open;
		op_to.op_close = rump_sys_close;
		op_to.op_read = rump_sys_read;
		op_to.op_write = rump_sys_write;
		op_to.op_stat = rump_sys_stat;	
	}

	rv = op_from.op_stat(from, &from_stat);
	if (rv == -1) {
		warn("%s", from);
		return -1;
	}

	fd = op_from.op_open(from, O_RDONLY, from_stat.st_mode);
	if (fd == -1) {
		warn("%s", from);
		return -1;
	}
	fd2 = op_to.op_open(to, O_WRONLY|O_CREAT|O_EXCL, 0777);
	if (fd2 == -1) {
		warn("%s", to);
		op_from.op_close(fd);
		return -1;
	}
	
	do {
		rd = op_from.op_read(fd, buf, BUFSIZE);
		if (rd == -1) {
			warn("%s", from);
			rv = -1;
			goto out;
		}
		wr = op_to.op_write(fd2, buf, rd);
		if (wr == -1 || wr != rd) {
			warn("%s", to);
			rv = -1;
			goto out;
		}
	} while (rd > 0);

	rv = 0;
out:

	op_from.op_close(fd);
	op_to.op_close(fd2);

	return rv;
}

static void
usage(void)
{

	fprintf(stderr, "usage: %s %s command file...\n",
		getprogname(), fsu_mount_usage());

	exit(EXIT_FAILURE);
}
