/*
 *  Copyright (C) 2000-2012, Parallels, Inc. All rights reserved.
 *
 *  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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <grp.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <dirent.h>
#include <sys/ioctl.h>
#include <netinet/in.h>
#include <linux/vzcalluser.h>
#include <linux/vzctl_veth.h>
#include <linux/vzctl_venet.h>
#include <linux/cpt_ioctl.h>

#include "env.h"
#include "exec.h"
#include "cpt.h"
#include "util.h"
#include "types.h"
#include "logger.h"
#include "vzerror.h"
#include "vzsyscalls.h"

#define ENVRETRY	3

static int vz_env_create_ioctl(vps_handler *h, envid_t veid, int flags)
{
	struct vzctl_env_create env_create;
	int errcode;
	int retry = 0;

	memset(&env_create, 0, sizeof(env_create));
	env_create.veid = veid;
	env_create.flags = flags;
	do {
		if (retry)
			sleep(1);
		errcode = ioctl(h->vzfd, VZCTL_ENV_CREATE, &env_create);
	} while (errcode < 0 && errno == EBUSY && retry++ < ENVRETRY);
	if (errcode >= 0 && (flags & VE_ENTER)) {
		/* Clear supplementary group IDs */
		setgroups(0, NULL);
		/* Set personality PER_LINUX32 for i386 based CTs */
		set_personality32();
	}
	return errcode;
}

static int vz_is_run(vps_handler *h, envid_t veid)
{
	int ret = vz_env_create_ioctl(h, veid, VE_TEST);

	if (ret < 0 && (errno == ESRCH || errno == ENOTTY))
		return 0;
	else if (ret < 0)
		logger(-1, errno, "Error on vz_env_create_ioctl(VE_TEST)");
	return 1;
}

static int vz_enter(vps_handler *h, envid_t veid, const char *root, int flags)
{
	int ret;

	if ((ret = vz_chroot(root)))
		return ret;

	ret = vz_env_create_ioctl(h, veid, VE_ENTER | flags);
	if (ret < 0) {
		if (errno == ESRCH)
			ret = VZ_VE_NOT_RUNNING;
		else
			ret = VZ_ENVCREATE_ERROR;
	}
	else
		ret = 0;

	close(h->vzfd);
	return ret;
}

static int vz_destroy(vps_handler *h, envid_t veid)
{
	/* Destroys automatically after reboot */
	return 0;
}

static int vz_env_configure(int fd, envid_t veid, const char *osrelease)
{
	int ret = 0;
	struct vzctl_ve_configure *cparam;
	int len;

	len = strlen(osrelease) + 1;
	cparam = calloc(1, sizeof(struct vzctl_ve_configure) + len);
	if (cparam == NULL)
		return VZ_RESOURCE_ERROR;

	cparam->veid = veid;
	cparam->key = VE_CONFIGURE_OS_RELEASE;
	cparam->size = len;
	strcpy(cparam->data, osrelease);

	if (ioctl(fd, VZCTL_VE_CONFIGURE, cparam) != 0)
		if (errno != ENOTTY)
			ret = VZ_SET_OSRELEASE;

	free(cparam);

	return ret;
}

static int vz_env_create_data_ioctl(vps_handler *h,
	struct vzctl_env_create_data *data)
{
	int errcode;
	int retry = 0;

	do {
		if (retry)
			sleep(1);
		errcode = ioctl(h->vzfd, VZCTL_ENV_CREATE_DATA, data);
	} while (errcode < 0 && errno == EBUSY && retry++ < ENVRETRY);

	return errcode;
}

static int _env_create(vps_handler *h, void *data)
{
	struct vzctl_env_create_data env_create_data;
	struct env_create_param3 create_param;
	int ret;
	struct arg_start *arg = data;
	envid_t veid = arg->veid;
	int wait_p = arg->wait_p;
	int err_p = arg->err_p;

	fill_container_param(arg, &create_param);

	env_create_data.veid = veid;
	env_create_data.class_id = 0;
	env_create_data.flags = VE_CREATE | VE_EXCLUSIVE;
	env_create_data.data = &create_param;
	env_create_data.datalen = sizeof(create_param);

	/* Close all fds except stdin. stdin is status pipe */
	close(STDERR_FILENO); close(STDOUT_FILENO);
	close_fds(0, wait_p, err_p, h->vzfd, -1);

try:
	ret = vz_env_create_data_ioctl(h, &env_create_data);
	if (ret < 0) {
		switch(errno) {
			case EINVAL:
				ret = VZ_ENVCREATE_ERROR;
				/* Run-time kernel did not understand the
				 * latest create_param -- so retry with
				 * the old env_create_param structs.
				 */
				switch (env_create_data.datalen) {
				case sizeof(struct env_create_param3):
					env_create_data.datalen =
						sizeof(struct env_create_param2);
					goto try;
				case sizeof(struct env_create_param2):
					env_create_data.datalen =
						sizeof(struct env_create_param);
					goto try;
				}
				break;
			case EACCES:
			/* License is not loaded */
				ret = VZ_NO_ACCES;
				break;
			case ENOTTY:
			/* Some vz modules are not present */
				ret = VZ_BAD_KERNEL;
				break;
			default:
				logger(-1, errno, "env_create error");
				ret = VZ_ENVCREATE_ERROR;
				break;
		}
		return ret;
	}

	if (arg->res->env.osrelease != NULL) {
		ret = vz_env_configure(h->vzfd, veid,
				arg->res->env.osrelease);
		if (ret != 0)
			return ret;
	}

	close(h->vzfd);
	return exec_container_init(arg, &create_param);
}

static inline int setluid(uid_t uid)
{
	return syscall(__NR_setluid, uid);
}

static int vz_setluid(envid_t veid)
{
	if (setluid(veid) == -1) {
		if (errno == ENOSYS)
			logger(-1, 0, "Error: kernel does not support"
				" user resources. Please, rebuild with"
				" CONFIG_USER_RESOURCE=y");
		return VZ_SETLUID_ERROR;
	}
	return 0;
}

static int vz_do_env_create(struct arg_start *arg)
{
	int ret, pid;
	int wait_p = arg->wait_p;
	int old_wait_p = arg->old_wait_p;
	int err_p = arg->err_p;
	env_create_FN fn = arg->fn;
	void *data = arg->data;
	struct vps_res *res = arg->res;
	vps_handler *h = arg->h;
	envid_t veid = arg->veid;

	if ((ret = vz_chroot(res->fs.root)))
		return ret;
	if ((ret = vz_setluid(veid)))
		return ret;
	if ((ret = set_ublimit(h, veid, &res->ub)))
		return ret;
	/* Create another process for proper resource accounting */
	if ((pid = fork()) < 0) {
		logger(-1, errno, "Unable to fork");
		return VZ_RESOURCE_ERROR;
	} else if (pid == 0) {
		if ((ret = vps_set_cap(veid, &res->env, &res->cap, 0)))
			goto env_err;
		if (fn == NULL) {
			ret = _env_create(h, (void *)arg);
		} else {
			ret = fn(h, veid, &res->fs, wait_p, old_wait_p, err_p, data);
		}
env_err:
		if (ret)
			write(STDIN_FILENO, &ret, sizeof(ret));
		exit(ret);
	}
	return 0;
}

static int vz_setcpu(vps_handler *h, envid_t veid, cpu_param *cpu)
{
	int ret = 0;

	if (cpu->limit != NULL)
		ret = set_cpulimit(veid, *cpu->limit);

	if (cpu->units != NULL)
		ret = set_cpuunits(veid, *cpu->units);
	else if (cpu->weight != NULL)
		ret = set_cpuweight(veid, *cpu->weight);

	if (cpu->vcpus != NULL)
		ret = env_set_vcpus(veid, *cpu->vcpus);
	if (cpu->mask != NULL)
		ret = set_cpumask(veid, cpu->mask);
	return ret;
}

static int vz_set_devperm(vps_handler *h, envid_t veid, dev_res *dev)
{
	struct vzctl_setdevperms devperms;

	devperms.veid = veid;
	devperms.dev = dev->dev;
	devperms.mask = dev->mask;
	devperms.type = dev->type;

	if (ioctl(h->vzfd, VZCTL_SETDEVPERMS, &devperms)) {
		logger(-1, errno, "Error setting device permissions");
		return VZ_SET_DEVICES;
	}

	return 0;
}

static int vz_netdev_ctl(vps_handler *h, envid_t veid, int op, char *name)
{
	struct vzctl_ve_netdev ve_netdev;

	ve_netdev.veid = veid;
	ve_netdev.op = op;
	ve_netdev.dev_name = name;
	if (ioctl(h->vzfd, VZCTL_VE_NETDEV, &ve_netdev) < 0)
		return VZ_NETDEV_ERROR;
	return 0;
}

static int vz_ip_ctl(vps_handler *h, envid_t veid, int op, const char *ipstr)
{
	struct vzctl_ve_ip_map ip_map;
	int family;
	unsigned int ip[4];
	int ret;

	union {
		struct sockaddr_in  a4;
		struct sockaddr_in6 a6;
	} addr;

	if ((family = get_netaddr(ipstr, ip)) < 0)
		return 0;


	if (family == AF_INET) {
		addr.a4.sin_family = AF_INET;
		addr.a4.sin_addr.s_addr = ip[0];
		addr.a4.sin_port = 0;
		ip_map.addrlen = sizeof(addr.a4);
	} else if (family == AF_INET6) {
		addr.a6.sin6_family = AF_INET6;
		memcpy(&addr.a6.sin6_addr, ip, 16);
		addr.a6.sin6_port = 0;
		ip_map.addrlen = sizeof(addr.a6);
	} else {
		return -EAFNOSUPPORT;
	}

	ip_map.veid = veid;
	ip_map.op = op;
	ip_map.addr = (struct sockaddr*) &addr;

	ret = ioctl(h->vzfd, VENETCTL_VE_IP_MAP, &ip_map);
	if (ret) {
		switch (errno) {
			case EADDRINUSE:
				ret = VZ_IP_INUSE;
				break;
			case ESRCH:
				ret = VZ_VE_NOT_RUNNING;
				break;
			case EADDRNOTAVAIL:
				if (op == VE_IP_DEL)
					return 0;
				ret = VZ_IP_NA;
				break;
			default:
				ret = VZ_CANT_ADDIP;
				break;
		}
		logger(-1, errno, "Unable to %s IP %s",
			op == VE_IP_ADD ? "add" : "del", ipstr);
	}
	return ret;

}

static int veth_dev_mac_filter(vps_handler *h, envid_t veid, veth_dev *dev)
{
	struct vzctl_ve_hwaddr veth;
	int ret;

	veth.op = dev->mac_filter == YES ? VE_ETH_DENY_MAC_CHANGE :
							VE_ETH_ALLOW_MAC_CHANGE;
	veth.veid = veid;
	memcpy(veth.dev_name, dev->dev_name, IFNAMSIZE);
	memcpy(veth.dev_name_ve, dev->dev_name_ve, IFNAMSIZE);
	ret = ioctl(h->vzfd, VETHCTL_VE_HWADDR, &veth);
	if (ret) {
		if (errno != ENODEV) {
			logger(-1, errno, "Unable to set mac filter");
			ret = VZ_VETH_ERROR;
		} else
			ret = 0;
	}
	return ret;
}

static int veth_dev_create(vps_handler *h, envid_t veid, veth_dev *dev)
{
	struct vzctl_ve_hwaddr veth;

	if (!dev->dev_name[0] || dev->addrlen != ETH_ALEN)
		return VZ_VETH_ERROR;
	if (dev->addrlen_ve != 0 && dev->addrlen_ve != ETH_ALEN)
		return VZ_VETH_ERROR;
	veth.op = VE_ETH_ADD;
	veth.veid = veid;
	veth.addrlen = dev->addrlen;
	veth.addrlen_ve = dev->addrlen_ve;
	memcpy(veth.dev_addr, dev->dev_addr, ETH_ALEN);
	memcpy(veth.dev_addr_ve, dev->dev_addr_ve, ETH_ALEN);
	memcpy(veth.dev_name, dev->dev_name, IFNAMSIZE);
	memcpy(veth.dev_name_ve, dev->dev_name_ve, IFNAMSIZE);
	if (ioctl(h->vzfd, VETHCTL_VE_HWADDR, &veth) != 0) {
		if (errno == ENOTTY) {
			logger(-1, 0, "Error: veth feature is"
				" not supported by kernel");
			logger(-1, 0, "Please check that vzethdev"
				" kernel module is loaded");
		} else {
			logger(-1, errno, "Unable to create veth");
		}
		return VZ_VETH_ERROR;
	}

	return 0;
}

static int veth_dev_remove(vps_handler *h, envid_t veid, veth_dev *dev)
{
	struct vzctl_ve_hwaddr veth;
	int ret;

	if (!dev->dev_name[0])
		return EINVAL;
	veth.op = VE_ETH_DEL;
	veth.veid = veid;
	memcpy(veth.dev_name, dev->dev_name, IFNAMSIZE);
	ret = ioctl(h->vzfd, VETHCTL_VE_HWADDR, &veth);
	if (ret) {
		if (errno != ENODEV) {
			logger(-1, errno, "Unable to remove veth");
			ret = VZ_VETH_ERROR;
		} else
			ret = 0;
	}
	return ret;
}

static int vz_veth_ctl(vps_handler *h, envid_t veid, int op, veth_dev *dev)
{
	int ret = 0;
	if (op == ADD) {
		if (!dev->active) {
			if ((ret = veth_dev_create(h, veid, dev)))
				return ret;
		}
		dev->flags = 1;
		if (dev->mac_filter) {
			if ((ret = veth_dev_mac_filter(h, veid, dev)))
				return ret;
		}
	} else {
		if (!dev->active)
			return ret;
		ret = veth_dev_remove(h, veid, dev);
	}
	return ret;
}

/* with mix of md5sum: try generate unique name */
#define CPT_HARDLINK_DIR ".cpt_hardlink_dir_a920e4ddc233afddc9fb53d26c392319"

void clean_hardlink_dir(const char *mntdir)
{
	char buf[STR_SIZE];
	struct dirent *ep;
	DIR *dp;

	snprintf(buf, sizeof(buf), "%s/%s", mntdir, CPT_HARDLINK_DIR);

	unlink(buf);		/* if file was created by someone */
	if (!(dp = opendir(buf)))
		return;
	while ((ep = readdir(dp))) {
		if (!strcmp(ep->d_name, ".") || !strcmp(ep->d_name, ".."))
			continue;

		snprintf(buf, sizeof(buf), "%s/%s/%s",
				mntdir, CPT_HARDLINK_DIR, ep->d_name);
		unlink(buf);
	}
	closedir(dp);

	snprintf(buf, sizeof(buf), "%s/%s", mntdir, CPT_HARDLINK_DIR);
	rmdir(buf);
}

static int setup_hardlink_dir(const char *mntdir, int cpt_fd)
{
	char buf[STR_SIZE];
	int fd, res = 0;

	snprintf(buf, sizeof(buf), "%s/%s", mntdir, CPT_HARDLINK_DIR);
	mkdir(buf, 0711);

	fd = open(buf, O_RDONLY | O_NOFOLLOW | O_DIRECTORY);
	if (fd < 0) {
		logger(-1, errno, "Error: Unable to open "
				"hardlink directory %s", buf);
		return 1;
	}

	if (ioctl(cpt_fd, CPT_LINKDIR_ADD, fd) < 0) {
		if (errno != EINVAL) {
			res = 1;
			logger(-1, errno, "Cannot set linkdir in kernel");
		}
		rmdir(buf);
	}

	close(fd);
	return res;
}

static int real_chkpnt(int cpt_fd, envid_t veid, const char *root,
		cpt_param *param, int cmd)
{
	int ret, len, len1;
	char buf[PIPE_BUF];
	int err_p[2];

	if ((ret = vz_chroot(root)))
		return VZ_CHKPNT_ERROR;
	if (pipe(err_p) < 0) {
		logger(-1, errno, "Can't create pipe");
		return VZ_CHKPNT_ERROR;
	}
	fcntl(err_p[0], F_SETFL, O_NONBLOCK);
	fcntl(err_p[1], F_SETFL, O_NONBLOCK);
	if (ioctl(cpt_fd, CPT_SET_ERRORFD, err_p[1]) < 0) {
		logger(-1, errno, "Can't set errorfd");
		return VZ_CHKPNT_ERROR;
	}
	close(err_p[1]);
	if (cmd == CMD_CHKPNT || cmd == CMD_SUSPEND) {
		logger(0, 0, "\tsuspend...");
		if (ioctl(cpt_fd, CPT_SUSPEND, 0) < 0) {
			logger(-1, errno, "Can not suspend container");
			goto err_out;
		}
	}
	if (cmd == CMD_CHKPNT || cmd == CMD_DUMP) {
		logger(0, 0, "\tdump...");
		clean_hardlink_dir("/");
		if (setup_hardlink_dir("/", cpt_fd))
			goto err_out;
		if (ioctl(cpt_fd, CPT_DUMP, 0) < 0) {
			logger(-1, errno, "Can not dump container");
			if (cmd == CMD_CHKPNT) {
				clean_hardlink_dir("/");
				if (ioctl(cpt_fd, CPT_RESUME, 0) < 0)
					logger(-1, errno, "Can not "
							"resume container");
			}
			goto err_out;
		}
	}
	if (cmd == CMD_CHKPNT) {
		logger(0, 0, "\tkill...");
		if (ioctl(cpt_fd, CPT_KILL, 0) < 0) {
			logger(-1, errno, "Can not kill container");
			goto err_out;
		}
	}
	if (cmd == CMD_SUSPEND && !param->ctx) {
		logger(0, 0, "\tget context...");
		if (ioctl(cpt_fd, CPT_GET_CONTEXT, veid) < 0) {
			logger(-1, errno, "Can not get context");
			goto err_out;
		}
	}
	close(err_p[0]);
	return 0;
err_out:
	while ((len = read(err_p[0], buf, PIPE_BUF)) > 0) {
		do {
			len1 = write(STDERR_FILENO, buf, len);
			len -= len1;
		} while (len > 0 && len1 > 0);
		if (cmd == CMD_SUSPEND && param->ctx) {
			/* destroy context */
			if (ioctl(cpt_fd, CPT_PUT_CONTEXT, veid) < 0)
				logger(-1, errno, "Can't put context");
		}
	}
	fflush(stderr);
	close(err_p[0]);
	return VZ_CHKPNT_ERROR;
}

static int vz_chkpnt(vps_handler *h, envid_t veid,
		     const fs_param *fs, int cmd, cpt_param *param)
{
	int dump_fd = -1;
	char buf[PATH_LEN];
	const char *dumpfile = NULL;
	int cpt_fd, pid, ret;
	const char *root = fs->root;

	ret = VZ_CHKPNT_ERROR;

	logger(0, 0, "Setting up checkpoint...");
	if ((cpt_fd = open(PROC_CPT, O_RDWR)) < 0) {
		if (errno == ENOENT)
			logger(-1, errno, "Error: No checkpointing"
				" support, unable to open " PROC_CPT);
		else
			logger(-1, errno, "Unable to open " PROC_CPT);
		return VZ_CHKPNT_ERROR;
	}
	if ((cmd == CMD_CHKPNT || cmd == CMD_DUMP)) {
		GET_DUMP_FILE(CMD_DUMP);
		make_dir(dumpfile, 0);
		dump_fd = open(dumpfile, O_CREAT|O_TRUNC|O_RDWR, 0600);
		if (dump_fd < 0) {
			logger(-1, errno, "Can not create dump file %s",
					dumpfile);
			goto err;
		}
	}
	if (param->ctx || cmd > CMD_SUSPEND) {
		logger(0, 0, "\tjoin context..");
		if (ioctl(cpt_fd, CPT_JOIN_CONTEXT, param->ctx ? : veid) < 0) {
			logger(-1, errno, "Can not join cpt context");
			goto err;
		}
	} else {
		if (ioctl(cpt_fd, CPT_SET_VEID, veid) < 0) {
			logger(-1, errno, "Can not set CT ID");
			goto err;
		}
	}
	if (dump_fd != -1) {
		if (ioctl(cpt_fd, CPT_SET_DUMPFD, dump_fd) < 0) {
			logger(-1, errno, "Can not set dump file");
			goto err;
		}
	}
	if (param->cpu_flags) {
		logger(0, 0, "\tset CPU flags..");
		if (ioctl(cpt_fd, CPT_SET_CPU_FLAGS, param->cpu_flags) < 0) {
			logger(-1, errno, "Can not set CPU flags");
			goto err;
		}
	}
	if ((pid = fork()) < 0) {
		logger(-1, errno, "Can't fork");
		ret = VZ_RESOURCE_ERROR;
		goto err;
	} else if (pid == 0) {
		if ((ret = h->setcontext(veid)))
			exit(ret);
		if ((pid = fork()) < 0) {
			logger(-1, errno, "Can't fork");
			exit(VZ_RESOURCE_ERROR);
		} else if (pid == 0) {
			ret = real_chkpnt(cpt_fd, veid, root, param, cmd);
			exit(ret);
		}
		ret = env_wait(pid);
		exit(ret);
	}
	close(cpt_fd);
	cpt_fd = -1;
	ret = env_wait(pid);
	if (ret)
		goto err;
	ret = 0;
	logger(0, 0, "Checkpointing completed successfully");
err:
	if (ret) {
		ret = VZ_CHKPNT_ERROR;
		logger(-1, 0, "Checkpointing failed");
		if (cmd == CMD_CHKPNT || cmd == CMD_DUMP)
			if (dumpfile)
				unlink(dumpfile);
	}
	if (dump_fd != -1) {
		if (ret == 0)
			fsync(dump_fd);
		close(dump_fd);
	}
	if (cpt_fd != -1)
		close(cpt_fd);

	return ret;
}

static int restore_fn(vps_handler *h, envid_t veid, const fs_param *fs,
			int wait_p, int old_wait_p, int err_p, void *data)
{
	int status, len, len1, ret;
	cpt_param *param = (cpt_param *) data;
	char buf[PIPE_BUF];
	int error_pipe[2];

	status = VZ_RESTORE_ERROR;
	if (param == NULL)
		goto err_pipe;
	/* Close all fds */
	close_fds(0, wait_p, old_wait_p, err_p, param->rst_fd, h->vzfd, -1);
	if (ioctl(param->rst_fd, CPT_SET_VEID, veid) < 0) {
		logger(-1, errno, "Can't set CT ID %d", param->rst_fd);
		goto err_pipe;
	}
	if (pipe(error_pipe) < 0 ) {
		logger(-1, errno, "Can't create pipe");
		goto err_pipe;
	}
	fcntl(error_pipe[0], F_SETFL, O_NONBLOCK);
	fcntl(error_pipe[1], F_SETFL, O_NONBLOCK);
	if (ioctl(param->rst_fd, CPT_SET_ERRORFD, error_pipe[1]) < 0) {
		logger(-1, errno, "Can't set errorfd");
		goto err;
	}
	close(error_pipe[1]);

	ret = ioctl(param->rst_fd, CPT_SET_LOCKFD2, wait_p);
	if (ret < 0 && errno == EINVAL)
		ret = ioctl(param->rst_fd, CPT_SET_LOCKFD, old_wait_p);
	if (ret < 0) {
		logger(-1, errno, "Can't set lockfd");
		goto err;
	}

	if (ioctl(param->rst_fd, CPT_SET_STATUSFD, STDIN_FILENO) < 0) {
		logger(-1, errno, "Can't set statusfd");
		goto err;
	}
	/* Close status descriptor to report that
	* environment is created.
	*/
	close(STDIN_FILENO);

	ioctl(param->rst_fd, CPT_HARDLNK_ON);

	logger(0, 0, "\tundump...");
	if (ioctl(param->rst_fd, CPT_UNDUMP, 0) != 0) {
		logger(-1, errno, "Error: undump failed");
		goto err_undump;
	}
	/* Now we wait until CT setup will be done */
	read(wait_p, &len, sizeof(len));
	if (param->cmd == CMD_RESTORE) {
		clean_hardlink_dir("/");
		logger(0, 0, "\tresume...");
		if (ioctl(param->rst_fd, CPT_RESUME, 0)) {
			logger(-1, errno, "Error: resume failed");
			goto err_undump;
		}
	} else if (param->cmd == CMD_UNDUMP && !param->ctx) {
		logger(0, 0, "\tget context...");
		if (ioctl(param->rst_fd, CPT_GET_CONTEXT, veid) < 0) {
			logger(-1, errno, "Can not get context");
			goto err_undump;
		}
	}
	status = 0;

err:
	close(error_pipe[0]);
err_pipe:
	if (status)
		write(err_p, &status, sizeof(status));
	return status;

err_undump:
	logger(-1, 0, "Restoring failed:");
	while ((len = read(error_pipe[0], buf, PIPE_BUF)) > 0) {
		do {
			len1 = write(STDERR_FILENO, buf, len);
			len -= len1;
		} while (len > 0 && len1 > 0);
	}
	fflush(stderr);
	close(error_pipe[0]);
	write(err_p, &status, sizeof(status));
	return status;
}

static int vz_restore(vps_handler *h, envid_t veid, vps_param *vps_p,
			int cmd, cpt_param *param, skipFlags skip)
{
	int ret, rst_fd;
	int dump_fd = -1;
	char buf[PATH_LEN];
	const char *dumpfile = NULL;

	logger(0, 0, "Restoring container ...");
	ret = VZ_RESTORE_ERROR;
	if ((rst_fd = open(PROC_RST, O_RDWR)) < 0) {
		if (errno == ENOENT)
			logger(-1, errno, "Error: No checkpointing"
				" support, unable to open " PROC_RST);
		else
			logger(-1, errno, "Unable to open " PROC_RST);
		return VZ_RESTORE_ERROR;
	}
	if (param->ctx) {
		if (ioctl(rst_fd, CPT_JOIN_CONTEXT, param->ctx) < 0) {
			logger(-1, errno, "Can not join cpt context");
			goto err;
		}
	}
	GET_DUMP_FILE(CMD_UNDUMP);
	if (cmd == CMD_RESTORE || cmd == CMD_UNDUMP) {
		dump_fd = open(dumpfile, O_RDONLY);
		if (dump_fd < 0) {
			logger(-1, errno, "Unable to open %s", dumpfile);
			goto err;
		}
	}
	if (dump_fd != -1) {
		if (ioctl(rst_fd, CPT_SET_DUMPFD, dump_fd)) {
			logger(-1, errno, "Can't set dumpfile");
			goto err;
		}
	}
	param->rst_fd = rst_fd;
	param->cmd = cmd;
	ret = vps_start_custom(h, veid, vps_p,
			SKIP_CONFIGURE | SKIP_ACTION_SCRIPT | skip,
			NULL, restore_fn, param);
	if (ret)
		goto err;
err:
	close(rst_fd);
	if (dump_fd != -1)
		close(dump_fd);
	if (!ret) {
		logger(0, 0, "Restoring completed successfully");
		if (cmd == CMD_RESTORE)
			if (dumpfile)
				unlink(dumpfile);
	}
	return ret;
}

int vz_do_open(vps_handler *h, vps_param *param)
{
	if ((h->vzfd = open(VZCTLDEV, O_RDWR)) < 0) {
		logger(-1, errno, "Unable to open %s", VZCTLDEV);
		logger(-1, 0, "Please check that vzdev kernel module is loaded"
			" and you have sufficient permissions"
			" to access the file.");
		return -1;
	}

	if (vz_env_create_ioctl(h, 0, 0) < 0 &&
		(errno == ENOSYS || errno == EPERM))
	{
		logger(-1, 0, "Your kernel lacks support for virtual"
			" environments or modules not loaded");
		goto err;
	}

	h->is_run = vz_is_run;
	h->enter = vz_enter;
	h->destroy = vz_destroy;
	h->env_create = vz_do_env_create;
	h->env_chkpnt = vz_chkpnt;
	h->env_restore = vz_restore;
	h->setlimits = set_ublimit;
	h->setcpus = vz_setcpu;
	h->setcontext = vz_setluid;
	h->setdevperm = vz_set_devperm;
	h->netdev_ctl = vz_netdev_ctl;
	h->ip_ctl = vz_ip_ctl;
	h->veth_ctl = vz_veth_ctl;

	return 0;
err:
	close(h->vzfd);
	return -1;
}
