/*
 *  Copyright (C) 2011-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 <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mount.h>
#include <limits.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>
#include <dlfcn.h>

#include "image.h"
#include "logger.h"
#include "list.h"
#include "util.h"
#include "vzerror.h"
#include "vzctl.h"
#include "env.h"
#include "destroy.h"
#include "cleanup.h"

#define DEFAULT_FSTYPE		"ext4"
#define SNAPSHOT_MOUNT_ID	"snap"

#ifdef HAVE_PLOOP
struct ploop_functions ploop;
#endif

/* We want get_ploop_type() to work even if vzctl is compiled
 * w/o ploop includes, thus a copy-paste from libploop.h:
 */
#ifndef HAVE_PLOOP
enum ploop_image_mode {
	PLOOP_EXPANDED_MODE = 0,
	PLOOP_EXPANDED_PREALLOCATED_MODE = 1,
	PLOOP_RAW_MODE = 2,
};
#endif

int get_ploop_type(const char *type)
{
	if (type == NULL)
		return -1;
	if (!strcmp(type, "expanded"))
		return PLOOP_EXPANDED_MODE;
	else if (!strcmp(type, "plain"))
		return PLOOP_EXPANDED_PREALLOCATED_MODE;
	else if (!strcmp(type, "raw"))
		return PLOOP_RAW_MODE;
	return -1;
}

int ve_private_is_ploop(const char *private)
{
	char image[PATH_MAX];

	GET_DISK_DESCRIPTOR(image, private);

	return (stat_file(image) == 1);
}

#ifdef HAVE_PLOOP
/* This should only be called once */
static int load_ploop_lib(void)
{
	void *h;
	void (*resolve)(struct ploop_functions *);
	char *err;
	struct {
		struct ploop_functions ploop;
		/* Newer versions of ploop library might have struct ploop_functions
		 * bigger than the one we are compiling against. Reserve some space
		 * to prevent a buffer overflow.
		 */
		void *padding[32];
	} src = { {0}, {0} };

	h = dlopen("libploop.so", RTLD_LAZY);
	if (!h) {
		logger(-1, 0, "Can't load ploop library: %s", dlerror());
		logger(-1, 0, "Please install ploop packages!");
		return -1;
	}

	dlerror(); /* Clear any existing error */
	resolve = dlsym(h, "ploop_resolve_functions");
	if ((err = dlerror()) != NULL) {
		logger(-1, 0, "Can't init ploop library: %s", err);
		logger(-1, 0, "Please upgrade your ploop packages!");
		dlclose(h);
		return -1;
	}

	(*resolve)(&src.ploop);
	if (src.padding[0] != NULL)
		logger(1, 0, "Notice: ploop library is newer when expected");
	memcpy(&ploop, &src.ploop, sizeof(ploop));

	vzctl_init_ploop_log();
	logger(1, 0, "The ploop library has been loaded successfully");
	/* Don't dlclose(), we need lib for the rest of runtime */

	return 0;
}
#endif

int is_ploop_supported()
{
	static int ret = -1;

	if (ret >= 0)
		return ret;

#ifndef HAVE_PLOOP
	ret = 0;
	logger(-1, 0, "Warning: ploop support is not compiled in");
#else
	if (stat_file("/proc/vz/ploop_minor") != 1) {
		logger(-1, 0, "No ploop support in the kernel, or kernel is way too old.\n"
				"Make sure you have OpenVZ kernel 042stab058.7 or later running,\n"
				"and kernel ploop modules loaded.");
		ret = 0;
		return ret;
	}

	if (load_ploop_lib() != 0) {
		/* Error is printed by load_ploop_lib() */
		ret = 0;
		return ret;
	}

	ret = 1;
#endif
	return ret;
}

#ifdef HAVE_PLOOP

static void cancel_ploop_op(void *data)
{
	ploop.cancel_operation();
}

/* Note: caller should call is_ploop_supported() before.
 * Currently the only caller is vps_start_custom() which does.
 */
int is_image_mounted(const char *ve_private)
{
	int ret;
	char dev[64];
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;

	GET_DISK_DESCRIPTOR(fname, ve_private);
	ret = ploop.read_disk_descr(&di, fname);
	if (ret) {
		logger(-1, 0, "Failed to read %s", fname);
		return -1;
	}
	ret = ploop.get_dev(di, dev, sizeof(dev));
	ploop.free_diskdescriptor(di);

	return (ret == 0);
}

int vzctl_mount_image(const char *ve_private, struct vzctl_mount_param *param)
{
	int ret;
	struct ploop_mount_param mount_param = {};
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	GET_DISK_DESCRIPTOR(fname, ve_private);
	ret = ploop.read_disk_descr(&di, fname);
	if (ret) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_MOUNT_IMAGE;
	}

	mount_param.fstype = DEFAULT_FSTYPE;
	mount_param.target = param->target;
	mount_param.quota = param->quota;
	mount_param.mount_data = param->mount_data;
/* FIXME: enable after ploop release
	mount_param.fsck = param->fsck;
 */

	PLOOP_CLEANUP(ret = ploop.mount_image(di, &mount_param));
	if (ret) {
		logger(-1, 0, "Failed to mount image: %s [%d]",
				ploop.get_last_error(), ret);
		ret = VZCTL_E_MOUNT_IMAGE;
	}
	ploop.free_diskdescriptor(di);
	return ret;
}

int vzctl_umount_image(const char *ve_private)
{
	int ret;
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	GET_DISK_DESCRIPTOR(fname, ve_private);
	ret = ploop.read_disk_descr(&di, fname);
	if (ret) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_UMOUNT_IMAGE;
	}

	PLOOP_CLEANUP(ret = ploop.umount_image(di));
	if (ret) {
		logger(-1, 0, "Failed to umount image: %s [%d]",
				ploop.get_last_error(), ret);
		ret = VZCTL_E_UMOUNT_IMAGE;
	}
	ploop.free_diskdescriptor(di);
	return ret;
}

int vzctl_create_image(const char *ve_private,
		struct vzctl_create_image_param *param)
{
	int ret = 0;
	struct ploop_create_param create_param = {};
	char dir[PATH_MAX];
	char image[PATH_MAX];

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	snprintf(dir, sizeof(dir), "%s/" VZCTL_VE_ROOTHDD_DIR, ve_private);
	ret = make_dir_mode(dir, 1, 0700);
	if (ret)
		return ret;

	snprintf(image, sizeof(image), "%s/root.hdd", dir);

	logger(0, 0, "Creating image: %s size=%luK", image, param->size);
	create_param.mode = param->mode;
	create_param.fstype = DEFAULT_FSTYPE;
	create_param.size = param->size * 2; /* Kb to 512b sectors */
	create_param.image = image;
	PLOOP_CLEANUP(ret = ploop.create_image(&create_param));
	if (ret) {
		rmdir(dir);
		logger(-1, 0, "Failed to create image: %s [%d]",
				ploop.get_last_error(), ret);
		return VZCTL_E_CREATE_IMAGE;
	}
	return 0;
}

int vzctl_convert_image(const char *ve_private, int mode)
{
	int ret;
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	GET_DISK_DESCRIPTOR(fname, ve_private);
	ret = ploop.read_disk_descr(&di, fname);
	if (ret) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_CONVERT_IMAGE;
	}
	PLOOP_CLEANUP(ret = ploop.convert_image(di, mode, 0));
	if (ret) {
		logger(-1, 0, "Failed to convert image: %s [%d]",
				ploop.get_last_error(), ret);
		ret = VZCTL_E_CONVERT_IMAGE;
	}
	ploop.free_diskdescriptor(di);
	return ret;
}

int vzctl_resize_image(const char *ve_private, unsigned long long newsize)
{
	int ret;
	struct ploop_disk_images_data *di;
	struct ploop_resize_param param = {};
	char fname[PATH_MAX];

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	if (ve_private == NULL) {
		logger(-1, 0, "Failed to resize image: "
				"CT private is not specified");
		return VZ_VE_PRIVATE_NOTSET;
	}

	GET_DISK_DESCRIPTOR(fname, ve_private);
	ret = ploop.read_disk_descr(&di, fname);
	if (ret) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_RESIZE_IMAGE;
	}
	param.size = newsize * 2; /* Kb to 512b sectors */
	PLOOP_CLEANUP(ret = ploop.resize_image(di, &param));
	if (ret) {
		logger(-1, 0, "Failed to resize image: %s [%d]",
				ploop.get_last_error(), ret);
		ret = VZCTL_E_RESIZE_IMAGE;
	}
	ploop.free_diskdescriptor(di);
	return ret;
}

/* Note: caller should call is_ploop_supported() before.
 * Currently the only caller is fill_2quota_param() which does.
 */
int vzctl_get_ploop_dev(const char *mnt, char *out, int len)
{
	return ploop.get_partition_by_mnt(mnt, out, len);
}

int vzctl_create_snapshot(const char *ve_private, const char *guid)
{
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;
	int ret;
	struct ploop_snapshot_param param = {};

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	if (ve_private == NULL) {
		logger(-1, 0, "Failed to create snapshot: "
				"CT private is not specified");
		return VZ_VE_PRIVATE_NOTSET;
	}
	GET_DISK_DESCRIPTOR(fname, ve_private);
	if (ploop.read_disk_descr(&di, fname)) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_CREATE_SNAPSHOT;
	}
	param.guid = strdup(guid);
	PLOOP_CLEANUP(ret = ploop.create_snapshot(di, &param));
	free(param.guid);
	if (ret) {
		logger(-1, 0, "Failed to create snapshot: %s [%d]",
				ploop.get_last_error(), ret);
		ret = VZCTL_E_CREATE_SNAPSHOT;
	}
	ploop.free_diskdescriptor(di);

	return ret;
}

int vzctl_delete_snapshot(const char *ve_private, const char *guid)
{
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;
	int ret;

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	if (ve_private == NULL) {
		logger(-1, 0, "Failed to delete snapshot: "
				"CT private is not specified");
		return VZ_VE_PRIVATE_NOTSET;
	}
	GET_DISK_DESCRIPTOR(fname, ve_private);
	if (ploop.read_disk_descr(&di, fname)) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_DELETE_SNAPSHOT;
	}
	PLOOP_CLEANUP(ret = ploop.delete_snapshot(di, guid));
	if (ret) {
		logger(-1, 0, "Failed to delete snapshot: %s [%d]",
				ploop.get_last_error(), ret);
		ret = VZCTL_E_DELETE_SNAPSHOT;
	}
	ploop.free_diskdescriptor(di);

	return ret;
}

int vzctl_merge_snapshot(const char *ve_private, const char *guid)
{
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;
	int ret;
	struct ploop_merge_param param = {};

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	if (guid == NULL)
		return VZCTL_E_MERGE_SNAPSHOT;
	if (ve_private == NULL) {
		logger(-1, 0, "Failed to merge snapshot: "
				"CT private is not specified");
		return VZ_VE_PRIVATE_NOTSET;
	}
	GET_DISK_DESCRIPTOR(fname, ve_private);
	if (ploop.read_disk_descr(&di, fname)) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_MERGE_SNAPSHOT;
	}
	param.guid = guid;
	PLOOP_CLEANUP(ret = ploop.merge_snapshot(di, &param));
	if (ret) {
		logger(-1, 0, "Failed to merge snapshot %s: %s [%d]",
				guid, ploop.get_last_error(), ret);
		ret = VZCTL_E_MERGE_SNAPSHOT;
	}

	ploop.free_diskdescriptor(di);

	return ret;
}


const char *generate_snapshot_component_name(unsigned int envid,
		const char *data, char *buf, int len)
{
	snprintf(buf, len, "%d-%s-%s", envid, SNAPSHOT_MOUNT_ID, data);
	return buf;
}

int vzctl_mount_snapshot(unsigned envid, const char *ve_private,
		struct vzctl_mount_param *param)
{
	int ret = VZCTL_E_MOUNT_SNAPSHOT;
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;
	struct ploop_mount_param mount_param = {};

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	GET_DISK_DESCRIPTOR(fname, ve_private);
	if (ploop.read_disk_descr(&di, fname)) {
		logger(-1, 0, "Failed to read %s", fname);
		return ret;
	}

	mount_param.ro = param->ro;
	if (param->mount_by_parent_guid) {
		mount_param.guid = ploop.find_parent_by_guid(di, param->guid);
		if (mount_param.guid == NULL) {
			logger(-1, 0, "Unable to find parent guid by %s",
					param->guid);
			goto err;
		}
	} else {
		mount_param.guid = (char *)param->guid;
	}
	mount_param.target = param->target;
	ploop.set_component_name(di,
			generate_snapshot_component_name(envid, param->guid,
				fname, sizeof(fname)));

	PLOOP_CLEANUP(ret = ploop.mount_image(di, &mount_param));
	if (ret) {
		logger(-1, 0, "Failed to mount snapshot %s: %s [%d]",
				param->guid, ploop.get_last_error(), ret);
		ret = VZCTL_E_MOUNT_SNAPSHOT;
		goto err;
	}

	/* provide device to caller */
	strncpy(param->device, mount_param.device, sizeof(param->device)-1);

err:
	ploop.free_diskdescriptor(di);

	return ret;
}

int vzctl_umount_snapshot(unsigned envid, const char *ve_private, char *guid)
{
	int ret;
	char fname[PATH_MAX];
	struct ploop_disk_images_data *di;

	if (!is_ploop_supported())
		return VZ_PLOOP_UNSUP;

	GET_DISK_DESCRIPTOR(fname, ve_private);
	if (ploop.read_disk_descr(&di, fname)) {
		logger(-1, 0, "Failed to read %s", fname);
		return VZCTL_E_UMOUNT_SNAPSHOT;
	}

	ploop.set_component_name(di,
			generate_snapshot_component_name(envid, guid,
				fname, sizeof(fname)));

	PLOOP_CLEANUP(ret = ploop.umount_image(di));
	ploop.free_diskdescriptor(di);
	if (ret)
		return vzctl_err(VZCTL_E_UMOUNT_SNAPSHOT, 0,
				"Failed to umount snapshot %s: %s [%d]",
				guid, ploop.get_last_error(), ret);

	return 0;
}

/* Convert a CT to ploop layout
 * 1) mount CT
 * 2) create & mount image
 * 3) cp VE_ROOT -> image
 * 4) update ve_layout
 */
int vzctl_env_convert_ploop(vps_handler *h, envid_t veid,
		fs_param *fs, dq_param *dq, int mode)
{
	struct vzctl_create_image_param param = {};
	struct vzctl_mount_param mount_param = {};
	int ret, ret2;
	char cmd[STR_SIZE];
	char new_private[STR_SIZE];

	if (ve_private_is_ploop(fs->private)) {
		logger(0, 0, "CT is already on ploop");
		return 0;
	}
	if (!is_ploop_supported()) {
		return VZ_PLOOP_UNSUP;
	}
	if (vps_is_run(h, veid)) {
		logger(-1, 0, "CT is running (stop it first)");
		return VZ_VE_RUNNING;
	}
	if (vps_is_mounted(fs->root, fs->private) == 1) {
		logger(-1, 0, "CT is mounted (umount it first)");
		return VZ_FS_MOUNTED;
	}
	if (!dq->diskspace || dq->diskspace[1] <= 0) {
		logger(-1, 0, "Error: diskspace not set");
		return VZ_DISKSPACE_NOT_SET;
	}

	snprintf(new_private, sizeof(new_private), "%s.ploop", fs->private);
	if (make_dir_mode(new_private, 1, 0600) != 0)
		return VZ_CANT_CREATE_DIR;

	param.mode = mode;
	param.size = dq->diskspace[1]; // limit
	ret = vzctl_create_image(new_private, &param);
	if (ret)
		goto err;

	mount_param.target = fs->root;

	ret = vzctl_mount_image(new_private, &mount_param);
	if (ret)
		goto err;

	logger(0, 0, "Copying content to ploop...");
	snprintf(cmd, sizeof(cmd), "/bin/cp -ax %s/. %s",
			fs->private, fs->root);
	logger(1, 0, "Executing %s", cmd);
	ret = system(cmd);
	ret2 = vzctl_umount_image(new_private);
	if (ret) {
		ret = VZ_SYSTEM_ERROR;
		goto err;
	}
	if (ret2) {
		/* Error message already printed by vzctl_umount_image() */
		ret = ret2;
		goto err;
	}
	/* Finally, del the old private and replace it with the new one */
	del_dir(fs->private);
	rename(new_private, fs->private);
	logger(0, 0, "Container was successfully converted "
			"to the ploop layout");
err:
	if (ret != 0)
		del_dir(new_private);
	return ret;
}

#endif /* HAVE_PLOOP */
