/*
 *  qemubuilder: pbuilder with qemu
 *  Copyright (C) 2007-2009 Junichi Uekawa
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 *
 */

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <dirent.h>
#include <getopt.h>
#include <stdarg.h>
#include <assert.h>
#include <termios.h>
#include <time.h>
#include <locale.h>
#include "parameter.h"
#include "qemuipsanitize.h"
#include "qemuarch.h"
#include "file.h"

#define STR(x) #x
#define XSTR(x) STR(x)

#define PBUILDER_INIT_VERSION 1
#define BUILDDIR "/build"
#define CHROOT_HOOKDIR BUILDDIR"/hooks"

#define EXECUTE_HOOKS_INDENT(indent, prefix) \
indent"if [ -d \""CHROOT_HOOKDIR"\" ]; then\n" \
indent"    for fn in \""CHROOT_HOOKDIR"/"prefix"\"[0-9][0-9]* ; do\n" \
indent"        case \"$fn\" in\n" \
indent"            \""CHROOT_HOOKDIR"/"prefix"\"'[0-9][0-9]*')\n" \
indent"                log.d \"no hooks of type "prefix" found -- ignoring\"\n" \
indent"                ;;\n" \
indent"            *~)\n" \
indent"                log.w \"skipping an editor backup file $fn\"\n" \
indent"                ;;\n" \
indent"            *)\n" \
indent"                if [ -x \"$fn\" ]; then\n" \
indent"                    log.i \"user script $fn starting\"\n" \
indent"                    \""CHROOT_HOOKDIR"/$(basename \"$fn\")\"\n" \
indent"                    log.i \"user script $fn finished\"\n" \
indent"                else\n" \
indent"                    if [ -f \"$fn\" ]; then\n" \
indent"                        filetype=$(basename \"$fn\")\n" \
indent"                        log.w \"execute priv not set on file $filetype, not executing.\"\n" \
indent"                    else\n" \
indent"                        # Should it reach here ? This case should be caught in the above case.\n" \
indent"                        log.w \"no hooks of type ${prefix} found -- internal error in logic\"\n" \
indent"                    fi\n" \
indent"                fi\n" \
indent"                ;;\n" \
indent"        esac\n" \
indent"    done\n" \
indent"fi\n"

#define EXECUTE_HOOKS(prefix) EXECUTE_HOOKS_INDENT("", prefix)

/*
 * example exit codes:
 *
 * END OF WORK EXIT CODE=1
 * END OF WORK EXIT CODE=0
 * END OF WORK EXIT CODE=16
 */
const char *qemu_keyword = "END OF WORK EXIT CODE=";

/** create a sparse ext3 block device suitable for
    loop-mount.

    This code takes approx 7 seconds to run, should be cached?

   @returns -1 on error, 0 on success
 */
static int create_ext3_block_device(const char *filename,
									unsigned long int gigabyte) {
	int ret = 0;
	char *s = NULL;
	char *s2 = NULL;

	/* create 10GB sparse data */
	if (create_sparse_file(filename, gigabyte * 1UL << 30UL)) {
		ret = -1;
		goto out;
	}

	if ((ret = forkexeclp("mke2fs",
						  "mke2fs",
						  "-q",
						  "-F",
						  "-j",
						  "-m1",
						  "-O",
						  "sparse_super",
						  filename,
						  NULL))) {
		ret = -1;
		goto out;
	}

	if ((ret = forkexeclp(
			 "tune2fs", "tune2fs", "-c", "0", "-i", "0", filename, NULL))) {
		ret = -1;
	}

out:
	if (s) {
		free(s);
	}
	if (s2) {
		free(s2);
	}
	return ret;
}

/** loopback mount file system.
    @returns 0 on success
*/
static int loop_mount(const char *device, const char *mountpoint) {
	int ret =
		forkexeclp("mount", "mount", "-o", "loop", device, mountpoint, NULL);
	return ret;
}

/**
   loopback umount file system

   @returns 0 on success
 */
static int loop_umount(const char *device) {
	int ret = forkexeclp("umount", "umount", device, NULL);
	return ret;
}

/** create a script file.

@returns NULL on failure, FILE* on success
*/
static FILE *create_script(const char *mountpoint, const char *relative_path) {
	char *c;
	char *s = NULL;
	FILE *f = NULL;
	FILE *ret = NULL;

	asprintf(&s, "%s/%s", mountpoint, relative_path);

	/* Create directories in relative_path */
	for (c = s + strlen(mountpoint) + 1; *c; ++c) {
		if (*c != '/') {
			continue;
		}
		*c = 0;
		if (mkdir(s, 0777) && errno != EEXIST) {
			log_printf(log_error,
					   "Could not create directory '%s': %s",
					   s,
					   strerror(errno));
			goto fail;
		}
		*c = '/';
	}

	if (!(f = fopen(s, "w"))) {
		goto fail;
	}
	if (chmod(s, 0700)) {
		fclose(f);
		goto fail;
	}
	ret = f;
fail:
	free(s);
	return ret;
}

static struct termios saved_termios;

static void save_termios(void) {
	if (isatty(1)) {
		tcgetattr(1, &saved_termios);
	}
}

static void restore_termios(void) {
	if (isatty(1)) {
		tcsetattr(1, TCSANOW, &saved_termios);
	}
}

static int copy_file_contents_to_temp(const char *orig,
									  const char *tempdir,
									  const char *tempname) {
	char *temppath;
	int ret;
	if (tempname == NULL) {
		tempname = basename(orig);
	}

	asprintf(&temppath, "%s/%s", tempdir, tempname);
	ret = copy_file(orig, temppath);
	if (ret == -1) {
		log_printf(log_error, "Copy file error in %s to %s", orig, temppath);
		goto out;
	}
out:
	free(temppath);
	return ret;
}

static int copy_file_contents_in_temp(FILE *f,
									  const char *tempname,
									  const char *targetdir,
									  const char *targetname) {
	int trailing_slash = targetdir[strlen(targetdir) - 1] == '/';
	const char *sep = trailing_slash ? "" : "/";
	fprintf(f,
			"log.i \"copying %s%s%s from temporary location\"\n"
			"mkdir -p %s\n"
			"cp $BUILDDIR/%s %s%s%s || log.e \"Copy failed\"\n",
			targetdir,
			sep,
			targetname,
			targetdir,
			tempname,
			targetdir,
			sep,
			targetname);
	return 0;
}

static int copy_file_contents_through_temp(FILE *f,
										   const char *orig,
										   const char *tempdir,
										   const char *targetdir) {
	int ret;
	char *file_basename = basename(orig);
	char *tempname = NULL;
	char *tempinput = NULL;
	if (0 > asprintf(&tempinput, "%s/input", tempdir)) {
		log_printf(log_error,
				   "failed to allocate string for '%s/input': %s",
				   tempdir,
				   strerror(errno));
		ret = 1;
		goto out;
	}

	if (0 > asprintf(&tempname, "input/%s", file_basename)) {
		log_printf(log_error,
				   "failed to allocate string for 'input/%s': %s",
				   file_basename,
				   strerror(errno));
		ret = 1;
		goto out;
	}

	if (mkdir(tempinput, 0777) && errno != EEXIST) {
		log_printf(log_error,
				   "failed to create directory '%s': %s",
				   tempinput,
				   strerror(errno));
		ret = 1;
		goto out;
	}

	ret = copy_file_contents_to_temp(orig, tempinput, file_basename);
	if (ret != 0) {
		goto out;
	}

	ret = copy_file_contents_in_temp(f, tempname, targetdir, file_basename);

out:
	if (tempname != NULL) {
		free(tempname);
	}
	if (tempinput != NULL) {
		free(tempinput);
	}
	return ret;
}

static int copy_hookdir(const char *hookdir, const char *tmp) {
	int ret = 0;
	struct dirent *dirp;
	DIR *hd = opendir(hookdir);
	char *hookstmp = NULL;
	if (0 > asprintf(&hookstmp, "%s/hooks", tmp)) {
		log_printf(log_error,
				   "Error allocating string for '%s/hooks': %s",
				   tmp,
				   strerror(errno));
		ret = 1;
		goto out;
	}

	if (mkdir(hookstmp, 0777)) {
		log_printf(log_error,
				   "Error creating directory for hooks: %s",
				   strerror(errno));
		ret = 1;
		goto out;
	}

	if (hd == NULL) {
		log_printf(log_error,
				   "Error copying hooks from '%s': %s",
				   hookdir,
				   strerror(errno));
		ret = 1;
		goto out;
	}

	while ((dirp = readdir(hd)) != NULL) {
		struct stat st;
		char *src = NULL;

		if (0 > asprintf(&src, "%s/%s", hookdir, dirp->d_name)) {
			log_printf(log_error,
					   "Error allocating string for '%s/%s': %s",
					   hookdir,
					   dirp->d_name,
					   strerror(errno));
			ret = 1;
			goto out;
		}
		if (0 > stat(src, &st)) {
			log_printf(
				log_error, "Error calling stat '%s': %s", src, strerror(errno));
			free(src);
			ret = 1;
			goto out;
		}

		if ((st.st_mode & S_IFMT) != S_IFREG) {
			// Not a regular file
			free(src);
			continue;
		}

		copy_file_contents_to_temp(src, hookstmp, basename(src));
		free(src);
	}

out:
	if (hookstmp != NULL) {
		free(hookstmp);
	}
	if (hd != NULL) {
		closedir(hd);
	}
	return ret;
}

static const char *format_for_image(const char *file) {
	static const char *formats[][2] = {
		{".cowdev", "qcow2"},
		{".dev", "raw"},
		{".qemu", "raw"}
	};
	int file_len = strlen(file);
	int i;
	for (i = 0; i < sizeof(formats) / sizeof(formats[0]); ++i) {
		const char *ext = formats[i][0];
		const char *format = formats[i][1];
		int ext_len = strlen(ext);
		if (file_len >= ext_len && !strcmp(ext, file + file_len - ext_len)) {
			return format;
		}
	}
	return NULL;
}

/**
   run qemu until exit signal is received from within QEMU via serial
   console.

   exit code:
   -1: error
   0..X: return code from inside qemu
*/
static int
fork_qemu(const char *hda, const char *hdb, const struct pbuilderconfig *pc) {
	pid_t child;
	int sp[2];
	fd_set readfds;
	int exit_code = -1;
	const int buffer_size = 4096;
	size_t count;

	if (-1 == socketpair(AF_UNIX, SOCK_STREAM, 0, sp)) {
		/* error handle? */
		return -1;
	}

	save_termios();

	fflush(NULL);
	if ((child = fork())) {
		/* this is parent process */
		char *buf = malloc(buffer_size);

		close(sp[1]);
		close(0);

		FD_ZERO(&readfds);
		while (1) {
			int status;
			if (0 < waitpid(child, &status, WNOHANG)) {
				/* child has exited */
				log_printf(log_error, "qemu exited unexpectedly: %d", status);
				break;
			}
			FD_SET(sp[0], &readfds);
			if (-1 != (select(sp[0] + 1, &readfds, NULL, NULL, NULL))) {
				if (FD_ISSET(sp[0], &readfds)) {
					void *matchptr;

					/* data available from qemu */

					/* sleep a bit to let it buffer-up a bit more. */
					usleep(100000);

					count = read(sp[0], buf, buffer_size);

					/* this won't work sometimes, but this is a good best-effort thing. */
					if ((matchptr = memmem(
							 buf, count, qemu_keyword, strlen(qemu_keyword))) !=
						0) {
						exit_code = atoi(matchptr + strlen(qemu_keyword));
						log_printf(
							log_info,
							"received termination message from inside qemu with exit-code %i, killing child process (qemu:%i)",
							exit_code,
							child);

						assert(child != 0);
						assert(child > 0);

						if (!kill(child, SIGTERM)) {
							log_printf(log_info, "successfully killed qemu");
						} else {
							log_perror("failed to kill qemu?");
						}
						if (-1 == waitpid(child, &status, 0)) {
							log_perror("qemubuilder: waitpid");
						}
						break;
					}
					write(1, buf, count);
				}
			} else {
				log_perror("select");
				break;
			}
		}

		free(buf);
	} else if (child == 0) {
		/* this is the child process */
		const char *qemu = qemu_arch_qemu(pc->arch);
		const char *machine = qemu_arch_qemumachine(pc->arch);
		const char *cpu = qemu_arch_cpu(pc->arch);
		const char *hda_format = format_for_image(hda);
		const char *hdb_format = format_for_image(hdb);
		char *hda_command;
		char *hdb_command;
		int virt;
		char *append_command;
		const char *kernel_image = pc->kernel_image;
		const char *initrd = pc->initrd;
		char *mem;
		int argc = 0;
		const int MAX_ARGS = 40;
		char *argv[MAX_ARGS];
		int i;
		int is_tty = isatty(1);
		const char *term;
		if (is_tty) {
			term = getenv("TERM");
			if (!term) {
				term = ""; /* Use whatever getty defaults to */
			}
		} else {
			term = "dumb";
		}

		if (qemu == NULL || machine == NULL) {
			log_printf(log_error,
					   "Your architecture %s does not seem to be supported",
					   pc->arch);
			exit(1);
		}

		if (kernel_image == NULL || !strcmp(kernel_image, "")) {
			log_printf(log_error, "No KERNEL_IMAGE defined in pbuilderrc");
			exit(1);
		}

		asprintf(&mem, "%i", pc->memory_megs);

		if (hda_format == NULL) {
			log_printf(log_error, "Unknown format for disk image %s", hda);
			exit(1);
		}

		if (hdb_format == NULL) {
			log_printf(log_error, "Unknown format for disk image %s", hdb);
			exit(1);
		}

		virt = !strcmp(machine, "virt");

		asprintf(
			&hda_command,
			"%sfile=%s,format=%s,index=0,media=disk,cache=writeback,id=hd0",
			virt ? "if=none," : "",
			hda,
			hda_format);

		asprintf(
			&hdb_command,
			"%sfile=%s,format=%s,index=1,media=disk,cache=writeback,id=hd1",
			virt ? "if=none," : "",
			hdb,
			hdb_format);

		/* panic < 0 means reboot immediately on panic; this will actually
		 * halt as -no-reboot is given to qemu */
		asprintf(
			&append_command,
			"root=/dev/%sa quiet init=/usr/bin/setsid console=%s panic=-1 -- -c -w /sbin/getty -n -l /pbuilder-run -8 -L - %s",
			qemu_arch_diskdevice(pc),
			qemu_arch_tty(pc->arch),
			term);

		dup2(sp[1], 1);
		dup2(sp[1], 2);
		close(sp[0]);

		argv[argc++] = strdupa(qemu);
		argv[argc++] = "-nodefaults";
		argv[argc++] = "-nographic";
		argv[argc++] = "-no-reboot"; /* halt instead of rebooting */
		argv[argc++] = "-machine";
		asprintf(
			&argv[argc++],
			"type=%s,accel=kvm:xen:tcg",
			strdupa(machine));

		if (cpu) {
			argv[argc++] = "-cpu";
			argv[argc++] = strdupa(cpu);
		}
		argv[argc++] = "-m";
		argv[argc++] = mem;
		if (pc->smp) {
			argv[argc++] = "-smp";
			argv[argc++] = strdupa(pc->smp);
		}
		argv[argc++] = "-kernel";
		argv[argc++] = strdupa(kernel_image);
		if (initrd && strcmp(initrd, "")) {
			argv[argc++] = "-initrd";
			argv[argc++] = strdupa(initrd);
		}
		argv[argc++] = "-drive";
		argv[argc++] = hda_command;
		argv[argc++] = "-drive";
		argv[argc++] = hdb_command;
		argv[argc++] = "-append";
		argv[argc++] = append_command;
		argv[argc++] = "-serial";
		if (is_tty) {
			argv[argc++] = "mon:stdio";
		} else {
			argv[argc++] = "stdio";
		}
		argv[argc++] = "-net";
		argv[argc++] = "user";
		if (virt) {
			argv[argc++] = "-device";
			argv[argc++] = "virtio-scsi-device,id=scsi";
			argv[argc++] = "-device";
			argv[argc++] = "virtio-net-device,netdev=net0";
			argv[argc++] = "-device";
			argv[argc++] = "scsi-hd,drive=hd0";
			argv[argc++] = "-device";
			argv[argc++] = "scsi-hd,drive=hd1";
			argv[argc++] = "-netdev";
			argv[argc++] = "user,id=net0";
		} else {
			argv[argc++] = "-net";
			argv[argc++] = "nic";
		}
		argv[argc] = NULL;
		assert(argc < MAX_ARGS);

		log_begin(log_info);
		log_middle(log_info, "forking qemu:");
		for (i = 0; i < argc; ++i) {
			if (strchr(argv[i], ' ')) {
				log_middle(log_info, " '%s'", argv[i]);
			} else {
				log_middle(log_info, " %s", argv[i]);
			}
		}
		log_end(log_info);

		execvp(argv[0], argv);
		log_perror("fork_qemu");
		exit(1);
	} else {
		log_perror("fork");
		return -1;
	}

	restore_termios();
	return exit_code;
}

static int do_fsck(const char *devfile) {
	/* force-running this fsck isn't a good idea; let it fail.
	 * If it's mounted by someone else, I don't want to touch it,
	 * and chroots can be re-created any time, right?
	 */
	return forkexeclp("/sbin/fsck", "/sbin/fsck", devfile, NULL);
}

/*
   get the current time string which can be used in date command to
   set time inside the chroot.
 */
static char *get_current_time_string(void) {
	char *locsave, *timestring;
	time_t currenttime;

	/* save/set/restore locale settings to get current time in POSIX format */
	locsave = setlocale(LC_TIME, NULL);
	(void)setlocale(LC_TIME, "POSIX");
	currenttime = time(NULL);
	timestring = asctime(gmtime(&currenttime));
	(void)setlocale(LC_TIME, locsave);
	return timestring;
}

static void write_first_stage(FILE *f, const struct pbuilderconfig *pc) {
	fprintf(
		f,
		"#!/bin/bash\n"
		"echo \n"
		/* Can't use log.i or check LOGLEVEL in first stage, since this lives
		 * inside the base chroot, and has not yet mounted the input disk. */
		"echo 'I: qemu-pbuilder first-stage' \n"
		"export PBUILDER_INIT_VERSION=" XSTR(PBUILDER_INIT_VERSION) "\n"
		"export PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'\n"
		"stty sane\n"
		"[ -d /proc/1 ] || mount -n /proc /proc -t proc\n"
		"ln -s /dev/shm /run/shm\n"
		"mkdir /run/lock\n"
		"chmod 01777 /run/lock\n"
		"mount -t tmpfs tmpfs /run/lock\n"
		"mount -n -o rw,remount / || mount -v -n -o rw,remount /dev/root \n"
		"find /tmp -mindepth 1 -maxdepth 1 -print0 | xargs -0 rm -rf --\n"
		"mount -t tmpfs tmpfs /tmp\n"
		"mkdir /dev/shm\n"
		"chmod 01777 /dev/shm\n"
		"mount -t tmpfs tmpfs /dev/shm\n"
		"ln -fs /proc/mounts /etc/mtab\n"
		"export BUILDDIR=" BUILDDIR "\n"
		"mkdir -p $BUILDDIR\n"
		"ln -s $BUILDDIR /tmp/buildd\n"
		"mount -n -t ext3 /dev/%sb $BUILDDIR \n"
		"$BUILDDIR/input/pbuilder-run \n",
		qemu_arch_diskdevice(pc));
}

static void write_second_stage_header(FILE *f, int debug_shell) {
	const char *log_level;

	switch (log_get_filter_level()) {
		case log_debug:
			log_level = "D";
			break;
		case log_info:
			log_level = "I";
			break;
		case log_warn:
			log_level = "W";
			break;
		case log_error:
			log_level = "E";
			break;
		default:
			log_level = "I";
			break;
	}

	fprintf(
		f,
		"#!/bin/bash\n"

		/* Will not be auto (using log_get_use_colors, not _unresolved) */
		"export USECOLORS=\"%s\"\n"
		"export LOGLEVEL=\"%s\"\n"

		/* Bash logging taken from pbuilder */
		"# Log a message\n"
		"# message is of a format\n"
		"#  E: error message\n"
		"#  W: warning message\n"
		"#  I: informational message\n"
		"_log() {\n"
		"    set -u\n"
		"    local color=\"$1\" ; shift\n"
		"    local red='\\033[0;31m'\n"
		"    local yellow='\\033[1;33m'\n"
		"    local blue='\\033[0;34m'\n"
		"    local reset='\\033[0m'\n"
		"    case \"$USECOLORS\" in\n"
		"        yes)\n"
		"            printf \"${!color}${*}${reset}\\n\"\n"
		"            ;;\n"
		"        no)\n"
		"            printf \"${*}\\n\"\n"
		"            ;;\n"
		"        *)\n"
		"            printf \"malformed value of USECOLORS: [%%s]\\n\" \"$USECOLORS\" >&2\n"
		"            exit 1\n"
		"            ;;\n"
		"    esac\n"
		"    set +u\n"
		"}\n"
		"function log() {\n"
		"    case \"$*\" in\n"
		"        \"E: \"*)\n"
		"            _log 'red' \"$*\" >&2\n"
		"            ;;\n"
		"        \"W: \"*)\n"
		"            _log 'yellow' \"$*\" >&2\n"
		"            ;;\n"
		"        \"I: \"*)\n"
		"            _log 'reset' \"$*\"\n"
		"            ;;\n"
		"        \"D: \"*)\n"
		"            _log 'blue' \"$*\"\n"
		"            ;;\n"
		"        *)\n"
		"            echo \"malformed log message: $*\" >&2\n"
		"            exit 1\n"
		"            ;;\n"
		"    esac\n"
		"}\n"

		"log.e() {\n"
		"    case \"$LOGLEVEL\" in\n"
		"        D|I|W|E) log \"E: $*\" ;;\n"
		"    esac\n"
		"}\n"
		"log.w() {\n"
		"    case \"$LOGLEVEL\" in\n"
		"        D|I|W) log \"W: $*\" ;;\n"
		"    esac\n"
		"}\n"
		"log.i() {\n"
		"    case \"$LOGLEVEL\" in\n"
		"        D|I) log \"I: $*\" ;;\n"
		"    esac\n"
		"}\n"
		"log.d() {\n"
		"    case \"$LOGLEVEL\" in\n"
		"        D) log \"D: $*\" ;;\n"
		"    esac\n"
		"}\n"

		/* define function to terminate qemu */
		"function exit_from_qemu() {\n"
		"    %s\n"
		"    sync\n"
		"    sync\n"
		"    sleep 1s\n" /* sleep before sending dying message */
		"    log.i 'qemu-pbuilder %s'\"$1\"\n"
		"    sleep 1s\n"
		"    halt -f -p\n" /* just halt myself if possible */
		"}\n",
		log_get_use_colors() == log_use_colors_yes ? "yes" : "no",
		log_level,
		debug_shell ? "log.i \"Debug shell\"; /bin/bash" : "",
		qemu_keyword);
}

/**
 * Invoke qemu, and run the second-stage script within QEMU.
 *
 * hostcommand1 is used from build and login and exeute
 */
static int run_second_stage_script(
	/** save the result of this command*/
	int save_result,
	/** the command-line to invoke within QEMU */
	const char *commandline,
	const struct pbuilderconfig *pc,
	/** the commands to invoke in the host OS */
	const char *hostcommand1,
	/** the commands to invoke in the guest OS */
	const char *hostcommand2) {
	char *script = NULL;
	char *workblockdevicepath = NULL;
	char *cowdevpath = NULL;
	char *timestring;
	int ret = 1;
	FILE *f;
	int i;

	if (mkdir(pc->buildplace, 0777)) {
		/* could not create the buildplace here. */
		log_perror("mkdir");
		goto out;
	}

	do_fsck(pc->basepath);

	timestring = get_current_time_string();

	asprintf(&workblockdevicepath, "%s.dev", pc->buildplace);
	ret = create_ext3_block_device(workblockdevicepath, 1);
	loop_mount(workblockdevicepath, pc->buildplace);

	f = create_script(pc->buildplace, "input/pbuilder-run");
	write_second_stage_header(f, 0);
	fprintf(
		f,
		/* main code */
		"echo \n"
		"log.i 'qemu-pbuilder second-stage' \n"
		// Remove compatibility symlink
		"rm \"$BUILDDIR\"/pbuilder-run\n"
		//"mount -n /proc /proc -t proc\n" // this is done in first stage.
		"if [ \"${PBUILDER_INIT_VERSION:-0}\" -lt " XSTR(PBUILDER_INIT_VERSION) " ]; then\n"
		"    log.e \"qemubuilder init script is out of date (${PBUILDER_INIT_VERSION:-0} < " XSTR(PBUILDER_INIT_VERSION) ")\"\n"
		"    log.e \"Please run qemubuilder --update\"\n"
		"    exit_from_qemu 1\n"
		"elif [ \"${PBUILDER_INIT_VERSION:-0}\" -gt " XSTR(PBUILDER_INIT_VERSION) " ]; then\n"
		"    log.e \"qemubuilder init script is newer than expected (${PBUILDER_INIT_VERSION:-0} < " XSTR(PBUILDER_INIT_VERSION) ")\"\n"
		"    exit_from_qemu 1\n"
		"fi\n"
		"log.i 'setting time to %s' \n"
		"date --set=\"%s\"\n"
		"log.i 'configuring network' \n"
		"ifconfig -a\n"
		"export IFNAME=`/sbin/ifconfig -a | grep eth | head -n1 | awk '{print $1}'`\n"
		"dhclient $IFNAME\n"
		"mkdir -p \"" BUILDDIR
		"\"\n"
		"%s\n"
		"$BUILDDIR/input/run-copyfiles\n"
		"hostname pbuilder-$(cat /etc/hostname)\n"
		"%s\n"
		//TODO: I can mount /var/cache/apt/archives from some scratch space to not need this:
		"apt-get clean || true\n"
		"exit_from_qemu 0\n",
		timestring,
		timestring,
		pc->hookdir && pc->hookdir[0] ? "mkdir -p \"" CHROOT_HOOKDIR "\"" : "",
		commandline);
	fclose(f);
	{
		char *compat_symlink;
		if (0 > asprintf(&compat_symlink, "%s/pbuilder-run", pc->buildplace)) {
			log_printf(
				log_error,
				"Failed to allocate string for compatibility symlink path '%s/pbuilder-run': %s",
				pc->buildplace,
				strerror(errno));
			goto out;
		}
		if (symlink("input/pbuilder-run", compat_symlink)) {
			log_printf(log_error,
					   "Failed to create compatibility symlink: %s",
					   strerror(errno));
			free(compat_symlink);
			goto out;
		}
		free(compat_symlink);
	}

	/* copy files script */
	f = create_script(pc->buildplace, "input/run-copyfiles");
	copy_file_contents_through_temp(f, "/etc/hosts", pc->buildplace, "/etc");
	copy_file_contents_through_temp(f, "/etc/hostname", pc->buildplace, "/etc");
	/* copy inputfile */
	for (i = 0; pc->inputfile[i]; ++i) {
		copy_file_contents_to_temp(pc->inputfile[i], pc->buildplace, NULL);
	}
	if (pc->hookdir != NULL && pc->hookdir[0]) {
		copy_hookdir(pc->hookdir, pc->buildplace);
	}
	fclose(f);

	/* do I not need to copy /etc/pbuilderrc, and ~/.pbuilderrc to inside chroot? */
	/* TODO: recover aptcache */

	if (hostcommand1) {
		log_printf(log_info, "running host command: %s", hostcommand1);
		system(hostcommand1);
	}

	loop_umount(pc->buildplace);

	asprintf(&cowdevpath, "%s.cowdev", pc->buildplace);
	ret = forkexeclp("qemu-img",
					 "qemu-img",
					 "create",
					 "-f",
					 "qcow2",
					 "-b",
					 pc->basepath,
					 cowdevpath,
					 NULL);
	if (ret) {
		goto out;
	}

	fork_qemu(cowdevpath, workblockdevicepath, pc);
	/* this will always return 0. */

	/* commit the change here */
	if (save_result) {
		log_printf(log_info, "committing changes to qemu image");
		ret = forkexeclp("qemu-img", "qemu-img", "commit", cowdevpath, NULL);
		if (ret) {
			goto out;
		}
	}

	/* after-run */
	loop_mount(workblockdevicepath, pc->buildplace);
	log_printf(log_info, "running post-run process");
	if (hostcommand2) {
		log_printf(log_info, "running host command: %s", hostcommand2);
		system(hostcommand2);
	}
	loop_umount(pc->buildplace);
	rmdir(pc->buildplace);
	log_printf(log_info, "clean up COW device files");
	unlink(workblockdevicepath);
	unlink(cowdevpath);
	ret = 0;

out:
	if (workblockdevicepath) {
		free(workblockdevicepath);
	}
	if (cowdevpath) {
		free(cowdevpath);
	}
	if (script) {
		free(script);
	}
	return ret;
}

/*
   @return shell command to copy the dsc file.
 */
static char *copy_dscfile(const char *dscfile_, const char *destdir) {
	int ret = 1;
	size_t bufsiz = 0;
	char *buf = NULL;
	char *filename = NULL;
	char *origdir = NULL;
	char *dscfile = canonicalize_file_name(dscfile_);
	FILE *f = fopen(dscfile, "r");

	char *memstr = 0;
	size_t len = 0;
	FILE *fmem = open_memstream(&memstr, &len);
	int filelist = 0;

	origdir = strdup(dscfile);

	assert(strrchr(origdir, '/') != 0);
	(*(strrchr(origdir, '/'))) = 0;

	fprintf(fmem, "cp %s %s/\n", dscfile, destdir);

	while (getline(&buf, &bufsiz, f) > 0) {
		if (strrchr(buf, '\n')) {
			*(strrchr(buf, '\n')) = 0;
		}
		if (filelist) {
			if (sscanf(buf, " %*s %*s %ms", &filename) != 1) {
				filelist = 0;
			} else {
				fprintf(fmem, "cp %s/%s %s/\n", origdir, filename, destdir);
				assert(filename);
				free(filename);
			}
		}
		if (!(buf[0] == ' ') && !strncmp(buf, "Files:", 6)) {
			filelist = 1;
		}
	}

	ret = 0;
	assert(fmem);
	assert(f);
	fclose(fmem);
	fclose(f);

	if (buf) {
		free(buf);
	}
	if (origdir) {
		free(origdir);
	}
	if (dscfile) {
		free(dscfile);
	}
	return ret ? NULL : memstr;
}

int cpbuilder_check_config(const struct pbuilderconfig *pc) {
	if (!pc->arch || !*pc->arch) {
		log_printf(log_error, "No architecture specified");
		return 1;
	}
	return 0;
}

/*
   return 0 on success, nonzero on failure.

   variable ret holds the state.
 */
int cpbuilder_create(const struct pbuilderconfig *pc) {
	int ret = 0;
	char *s = NULL; /* generic asprintf buffer */
	char *workblockdevicepath = NULL;
	FILE *f; /* generic FILE pointer which is reused. */
	char *t;
	char *timestring;

	/* remove existing file; it can be old qemu image, or a directory if
	 * it didn't exist before. */
	unlink(pc->basepath);
	rmdir(pc->basepath);

	/* 3GB should be enough to install any Debian system; hopefully */
	ret = create_ext3_block_device(pc->basepath, 3);

	if (ret) {
		goto out;
	}

	if (mkdir(pc->buildplace, 0777)) {
		/* could not create the buildplace here. */
		ret = 1;
		log_perror("mkdir");
		goto out;
	}

	ret = loop_mount(pc->basepath, pc->buildplace);
	if (ret) {
		goto out;
	}

	debootstrap_command_line[1] = "--arch";
	debootstrap_command_line[2] = pc->arch;
	debootstrap_command_line[3] = "--foreign";
	DEBOOTSTRAP_ADD_PARAM(pc->distribution);
	DEBOOTSTRAP_ADD_PARAM(pc->buildplace);
	DEBOOTSTRAP_ADD_PARAM(pc->mirror);
	DEBOOTSTRAP_ADD_PARAM(NULL);

	log_printf(log_info, "Invoking debootstrap");
	ret = forkexecvp(debootstrap_command_line);
	if (ret) {
		log_printf(log_error, "debootstrap failed with %i", ret);
		goto umount_out;
	}

	/* arch-dependent code here.
	 * create required device files.

	 * ttyAMA0 is probably ARM-specific
	 * others are probably linux-portable as documented in
	 * linux/Documentation/devices.txt
	 * other OSes will require different, but hey, they probably don't
	 * even boot from ext3,
	 * we'll need think of other ways to work with them.
	 */
	log_printf(log_info, "Doing architecture-specific /dev population");

	qemu_create_arch_devices(pc->buildplace, pc->arch);

	f = create_script(pc->buildplace, "pbuilder-run");
	write_first_stage(f, pc);
	fclose(f);
	f = NULL;

	if (pc->http_proxy != NULL) {
		f = create_script(pc->buildplace,
						  "etc/apt/apt.conf.d/20pbuilder-proxy");
		fprintf(f,
				"Acquire\n"
				"{\n"
				"http \n"
				"{\n"
				"Proxy \"%s\";\n"
				"};\n"
				"};\n",
				pc->http_proxy);
		fclose(f);
		f = NULL;
	}

	free(s);
	s = 0;

	ret = loop_umount(pc->buildplace);
	if (ret) {
		goto out;
	}

	/* create the temporary device for command-execution */
	asprintf(&workblockdevicepath, "%s.dev", pc->buildplace);
	ret = create_ext3_block_device(workblockdevicepath, 1);
	if (ret) {
		goto out;
	}

	loop_mount(workblockdevicepath, pc->buildplace);

	timestring = get_current_time_string();

	f = create_script(pc->buildplace, "input/pbuilder-run");
	write_second_stage_header(f, pc->debug);
	fprintf(
		f,
		/* start of main code */
		"export RET=0\n"
		"echo \n"
		"log.i 'qemu-pbuilder second-stage' \n"
		"log.i 'setting time to %s' \n"
		"date --set=\"%s\"\n"
		"log.i 'Running debootstrap second-stage script' \n"
		"touch /etc/udev/disabled\n" // work-around for #520742
		"/debootstrap/debootstrap --second-stage || ( "
		"  log.i \"dumping debootstrap log\"\n"
		"  cat /debootstrap/debootstrap.log\n"
		"  exit_from_qemu\n"
		"\n )\n"
		"rm /etc/udev/disabled\n" // work-around for #520742
		"echo deb %s %s %s > /etc/apt/sources.list \n"
		"echo 'APT::Install-Recommends \"false\"; ' > /etc/apt/apt.conf.d/15pbuilder\n"
		"mount -n proc /proc -t proc\n"
		"mount -n sysfs /sys -t sysfs\n"
		"mkdir /dev/pts\n"
		"mount -n devpts /dev/pts -t devpts\n"
		"dhclient eth0\n"
		"%s\n"
		"$BUILDDIR/input/run-copyfiles\n"
		"hostname pbuilder-$(cat /etc/hostname)\n"
		//TODO: installaptlines
		"echo '%s' > /etc/apt/sources.list.d/other.list\n"
		EXECUTE_HOOKS("G")
		"apt-get update || exit_from_qemu 1\n"
		//TODO: "dpkg --purge $REMOVEPACKAGES\n"
		//recover aptcache
		"apt-get -y %s -o DPkg::Options::=--force-confnew dist-upgrade || exit_from_qemu 1\n"
		"apt-get install %s -y build-essential dpkg-dev apt aptitude pbuilder %s || exit_from_qemu 1\n"
		//TODO: save aptcache
		//optionally autoclean aptcache
		EXECUTE_HOOKS("E")
		//TODO: I can mount /var/cache/apt/archives from some scratch space to not need this:
		"apt-get clean || true\n"
		"exit_from_qemu $RET\n"
		"bash\n",
		timestring,
		timestring,
		t = sanitize_mirror(pc->mirror),
		pc->distribution,
		pc->components,
		pc->hookdir && pc->hookdir[0] ? "mkdir -p \"" CHROOT_HOOKDIR "\"" : "",
		pc->othermirror ? pc->othermirror : "",
		pc->allow_untrusted ? "--force-yes" : "",
		pc->allow_untrusted ? "--force-yes" : "",
		pc->extrapackages ? pc->extrapackages : "");
	fclose(f);
	f = NULL;

	free(t);

	/* TODO: can I do 'date --set' from output of 'LC_ALL=C date' */

	/* copy files script */
	f = create_script(pc->buildplace, "input/run-copyfiles");
	copy_file_contents_through_temp(f, "/etc/hosts", pc->buildplace, "/etc");
	copy_file_contents_through_temp(f, "/etc/hostname", pc->buildplace, "/etc");
	if (pc->hookdir != NULL && pc->hookdir[0]) {
		copy_hookdir(pc->hookdir, pc->buildplace);
	}
	fclose(f);

	/* do I not need to copy /etc/pbuilderrc, and ~/.pbuilderrc to inside chroot? */
	/* TODO: recover aptcache */

	loop_umount(pc->buildplace);
	rmdir(pc->buildplace);

	// this will have wrong time. how to workaround?
	ret = fork_qemu(pc->basepath, workblockdevicepath, pc);

	unlink(workblockdevicepath);

out:
	if (workblockdevicepath) {
		free(workblockdevicepath);
	}
	if (s) {
		free(s);
	}
	return ret;

umount_out:
	loop_umount(pc->buildplace);
	if (s) {
		free(s);
	}
	return ret;
}

/*
 * @return: return code of pbuilder, or <0 on failure
 */
int cpbuilder_build(const struct pbuilderconfig *pc, const char *dscfile) {
	int ret;
	char *hoststr = NULL;
	char *hoststr2 = NULL;
	char *commandline = NULL;
	const char *buildopt = NULL;
	const char *debbuildopts = NULL;
	char *debbuildopts_work = NULL;

	if (pc->binary_arch) {
		asprintf(&debbuildopts_work, "%s -B", pc->debbuildopts);
		debbuildopts = debbuildopts_work;
		buildopt = "--binary-arch";
	} else if (pc->binary_indep) {
		asprintf(&debbuildopts_work, "%s -A", pc->debbuildopts);
		debbuildopts = debbuildopts_work;
		buildopt = "--binary-indep";
	} else {
		debbuildopts = pc->debbuildopts;
		buildopt = "--binary-all";
	}

	hoststr = copy_dscfile(dscfile, pc->buildplace);

	asprintf(
		&commandline,
		EXECUTE_HOOKS("D")
		"ALLOWUNTRUSTED=%s /usr/lib/pbuilder/pbuilder-satisfydepends --control $BUILDDIR/*.dsc --internal-chrootexec 'chroot . ' %s \n"
		"apt-get install %s -y %s\n"
		"cd $BUILDDIR; /usr/bin/dpkg-source -x $(basename %s) \n"
		"log.i 'Building the package'\n"
		EXECUTE_HOOKS("A")
		"if ! (\n"
		"    cd $BUILDDIR/*-*/\n"
		"    dpkg-buildpackage -us -uc %s\n"
		"); then\n"
		EXECUTE_HOOKS_INDENT("    ", "C")
		"    exit_from_qemu 1\n"
		"fi\n"
		EXECUTE_HOOKS("B"),
		pc->allow_untrusted ? "yes" : "no",
		buildopt,
		pc->allow_untrusted ? "--force-yes" : "",
		pc->extrapackages ? pc->extrapackages : "",
		dscfile,
		debbuildopts);

	/* Obscure assumption!: assume _ is significant for package name
	 * and no other file will have _. */

	asprintf(
		&hoststr2,
		"BUILDPLACE='%s'\n"
		"BUILDRESULT='%s'\n"
		"BUILDRESULTUID=%d\n"
		"BUILDRESULTGID=%d\n"
		"if [ -d \"${BUILDRESULT}\" ]; then\n"
		"    chown \"${BUILDRESULTUID}:${BUILDRESULTGID}\" \"${BUILDPLACE}$BUILDDIR/\"*\n"
		"    chgrp \"${BUILDRESULTGID}\" \"${BUILDPLACE}$BUILDDIR/\"*\n"
		"    for FILE in \"${BUILDPLACE}$BUILDDIR\"/*; do\n"
		"        if [ -f \"${FILE}\" ]; then\n"
		"            cp -p \"${FILE}\" \"${BUILDRESULT}\" || true\n"
		"        fi\n"
		"    done\n"
		"else\n"
		"    log.e \"BUILDRESULT=[$BUILDRESULT] is not a directory.\"\n"
		"fi\n",
		pc->buildplace,
		pc->buildresult,
		pc->buildresultuid,
		pc->buildresultgid);

	ret = run_second_stage_script(0, commandline, pc, hoststr, hoststr2);

	if (debbuildopts_work) {
		free(debbuildopts_work);
	}
	if (hoststr2) {
		free(hoststr2);
	}
	if (hoststr) {
		free(hoststr);
	}
	if (commandline) {
		free(commandline);
	}
	return ret;
}

int cpbuilder_login(const struct pbuilderconfig *pc) {
	return run_second_stage_script(pc->save_after_login,
								   EXECUTE_HOOKS("H")
								   EXECUTE_HOOKS("F")
								   "bash",
								   pc,
								   NULL,
								   NULL);
}

/*

Mostly a copy of pbuilder login, executes a script.

 */
int cpbuilder_execute(const struct pbuilderconfig *pc, char **av) {
	char *hostcommand;
	char *runcommandline;
	int ret;

	asprintf(
		&hostcommand,
		"[ -d %s/input ] || mkdir %s/input\n || log.e \"Failed to create directory '%s/input'\"\n"
		"cp %s %s/input/runscript\n",
		pc->buildplace,
		pc->buildplace,
		pc->buildplace,
		av[0],
		pc->buildplace);
	/* TODO: add options too */
	asprintf(&runcommandline,
			 EXECUTE_HOOKS("H")
			 EXECUTE_HOOKS("F")
			 "sh $BUILDDIR/input/runscript");
	ret = run_second_stage_script(
		pc->save_after_login, runcommandline, pc, hostcommand, NULL);
	free(hostcommand);
	free(runcommandline);
	return ret;
}

/**
   implement pbuilder update

   @return 0 on success, other values on failure.
 */
int cpbuilder_update(const struct pbuilderconfig *pc) {
	int ret;

	ret = do_fsck(pc->basepath);
	if (ret) {
		log_printf(log_error, "Failed fsck '%s'", pc->basepath);
		return ret;
	}

	if (mkdir(pc->buildplace, 0777)) {
		log_printf(log_error,
				   "Could not create directory '%s': %s",
				   pc->buildplace,
				   strerror(errno));
		return 1;
	}

	ret = loop_mount(pc->basepath, pc->buildplace);
	if (ret) {
		log_printf(log_error,
				   "Could not mount '%s' on '%s'",
				   pc->basepath,
				   pc->buildplace);
		return ret;
	}
	FILE *g = create_script(pc->buildplace, "pbuilder-run");
	write_first_stage(g, pc);
	fclose(g);
	ret = loop_umount(pc->buildplace);
	if (ret) {
		log_printf(log_error, "Could not unmount '%s'", pc->buildplace);
		return ret;
	}
	ret = rmdir(pc->buildplace);
	if (ret) {
		log_printf(log_error, "Could not rmdir '%s'", pc->buildplace);
		return ret;
	}

	/* TODO: --override-config support, --othermirror support etc.
	 * There is no way to change distribution in this code-path...
	 */
	char *script;
	if (0 >
		asprintf(
			&script,
			EXECUTE_HOOKS("H")
			//TODO: installaptlines if required.
			//TODO: "dpkg --purge $REMOVEPACKAGES\n"
			//TODO: add error code handling.
			"apt-get update -o Acquire::PDiffs=false\n"
			"apt-get -y %s -o DPkg::Options::=--force-confnew dist-upgrade\n"
			"apt-get install %s -y build-essential dpkg-dev apt aptitude pbuilder %s\n"
			//TODO: optionally autoclean aptcache
			EXECUTE_HOOKS("E"),
			pc->allow_untrusted ? "--force-yes" : "",
			pc->allow_untrusted ? "--force-yes" : "",
			pc->extrapackages ? pc->extrapackages : "")) {
		log_printf(log_error, "qemubuilder: out of memory.");
		return 1;
	}

	ret = run_second_stage_script(1, script, pc, NULL, NULL);
	free(script);
	return ret;
}

int cpbuilder_help(void) {
	printf("qemubuilder [operation] [options]\n"
		   "operation:\n"
		   " --build\n"
		   " --create\n"
		   " --update\n"
		   " --login\n"
		   " --execute\n"
		   " --help\n"
		   " --dumpconfig\n"
		   "options:\n"
		   " --basepath:\n"
		   " --buildplace:\n"
		   " --distribution:\n"
		   " ... and other pbuilder options \n");
	return 0;
}

int app_main(int ac, char **av) {
	return parse_parameter(ac, av, "qemu");
}
