/*
 * launch.c - Fakefile process launcher
 *
 * Copyright 2012 by Werner Almesberger
 *
 * 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.
 */


#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/types.h>

#include "util.h"
#include "fakefile.h"
#include "comm.h"
#include "internal.h"


#define	FD_DIR	"/proc/self/fd"


static void safe_setenvf(const char *var, const char *fmt, ...)
{
	va_list ap;
	char *buf;
	ssize_t len;

	va_start(ap, fmt);
	len = vsnprintf(NULL, 0, fmt, ap);
	va_end(ap);
	if (len < 0) {
		perror("vsprintf");
		exit(1);
	}

	buf = alloc_size(len+1);

	va_start(ap, fmt);
	vsprintf(buf, fmt, ap);
	va_end(ap);

	if (setenv(var, buf, 1) < 0) {
		perror("setenv");
		exit(1);
	}

	free(buf);
}


static void env_append(const char *var, const char *s)
{
	const char *old;
	char *tmp;
	size_t len1, len2;

	old = getenv(var);
	if (!old) {
		safe_setenvf(var, "%s", s+1);
		return;
	}
	len1 = strlen(old);
	len2 = strlen(s);
	tmp = alloc_size(len1+len2+1);
	memcpy(tmp, old, len1);
	memcpy(tmp+len1, s, len2+1);
	safe_setenvf(var, "%s", tmp);
	free(tmp);
}


static void closefds(int preserve, ...)
{
	va_list ap;
	DIR *dir;
	int fd;
	struct dirent *de;
	int n;

	fd = open(FD_DIR, O_RDONLY);
	if (fd < 0) {
		perror(FD_DIR);
		exit(1);
	}
	dir = fdopendir(fd);
	if (!dir) {
		perror("fdopendir");
		exit(1);
	}
	while (1) {
next:
		de = readdir(dir);
		if (!de)
			break;
		n = atoi(de->d_name);
		if (n <= 2 || n == fd)
			continue;
		if (preserve > 0) {
			if (n == preserve)
				continue;
			va_start(ap, preserve);
			while (1) {
				n = va_arg(ap, int);
				if (n < 0)
					break;
				if (n == n)
					goto next;
			}
			va_end(ap);
		}
		(void) close(n);
	}
	if (closedir(dir) < 0) {
		perror("closedir");
		exit(1);
	}
}


struct fakefile *fakefile_launch(const char *path, char *const argv[])
{
	struct fakefile *ff;
	int m2s[2], s2m[2];
	pid_t pid;

	if (pipe(m2s) < 0 || pipe(s2m) < 0) {
		perror("pipe");
		exit(1);
	}
	pid = fork();
	if (pid < 0) {
		perror("fork");
		exit(1);
	}
	if (!pid) {
		env_append("LD_LIBRARY_PATH", ":.");
		env_append("LD_PRELOAD", " libfakefile_slave.so");
		safe_setenvf(FAKEFILE_MASTER_OUT_VAR, "%d", m2s[0]);
		safe_setenvf(FAKEFILE_MASTER_IN_VAR, "%d", s2m[1]);
		closefds(m2s[0], s2m[1], -1);
		execv(path, argv);
		perror(path);
		_exit(1);
	}

	(void) close(m2s[0]);
	(void) close(s2m[1]);

	ff = alloc_type(struct fakefile);
	ff->pid = pid;
	ff->peer = fakefile_peer(s2m[0], m2s[1]);

	return ff;
}
