/*
 *  Copyright (C) 2000-2011, 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 <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <mntent.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/param.h>

#include "fs.h"
#include "util.h"
#include "logger.h"
#include "vzerror.h"
#include "script.h"
#include "quota.h"

int vps_is_run(vps_handler *h, envid_t veid);

/** Get CT mount status.
 *
 * @param root		CT root.
 * @return		 1 - CT mounted
 *			 0 - CT unmounted.
 *			-1 - error
 */
int vps_is_mounted(const char *root)
{
	return vz_fs_is_mounted(root);
}

/** Mount CT.
 *
 * @param veid		CT ID.
 * @param fs		file system parameters.
 * @param dq		disk quota parameters.
 * @return		0 on success.
 */
int fsmount(envid_t veid, fs_param *fs, dq_param *dq)
{
	int ret;

	/* Create VE_ROOT mount point if not exist */
	if (make_dir(fs->root, 1)) {
		logger(-1, 0, "Can't create mount point %s", fs->root);
		return VZ_FS_MPOINTCREATE;
	}
	if ((ret = vps_quotaon(veid, fs->private, dq)))
		return ret;
	if ((ret = vz_mount(fs, 0)))
		vps_quotaoff(veid, dq);
	return ret;
}

/** Unmount CT.
 *
 * @param veid		CT ID.
 * @param root		CT root.
 * @return		0 on success.
 */
int fsumount(envid_t veid, const char *root)
{
	if (umount(root) != 0) {
		logger(-1, errno, "Can't umount %s", root);
		return VZ_FS_CANTUMOUNT;
	}

	if (!quota_ctl(veid, QUOTA_STAT))
		return quota_off(veid, 0);

	return 0;
}

/** Mount CT and run mount action script if exists.
 *
 * @param h		CT handler.
 * @param veid		CT ID.
 * @param fs		file system parameters.
 * @param dq		disk quota parameters.
 * @param skip		skip mount action scrips
 * @return		0 on success.
 */
int vps_mount(vps_handler *h, envid_t veid, fs_param *fs, dq_param *dq,
	skipFlags skip)
{
	char buf[PATH_LEN];
	int ret, i;

	if (check_var(fs->root, "VE_ROOT is not set"))
		return VZ_VE_ROOT_NOTSET;
	if (check_var(fs->private, "VE_PRIVATE is not set"))
		return VZ_VE_PRIVATE_NOTSET;
	if (!stat_file(fs->private)) {
		logger(-1, 0, "Container private area %s does not exist",
				fs->private);
		return VZ_FS_NOPRVT;
	}
	if (vps_is_mounted(fs->root)) {
		logger(-1, 0, "Container is already mounted");
		return 0;
	}
	/* Execute pre mount scripts */
	if (!(skip & SKIP_ACTION_SCRIPT)) {
		snprintf(buf, sizeof(buf), "%svps.%s", VPS_CONF_DIR,
			PRE_MOUNT_PREFIX);
		for (i = 0; i < 2; i++) {
			if (run_pre_script(veid, buf)) {
				logger(-1, 0, "Error executing mount script %s",
					buf);
				fsumount(veid, fs->root);
				return VZ_ACTIONSCRIPT_ERROR;
			}
			snprintf(buf, sizeof(buf), "%s%d.%s", VPS_CONF_DIR,
				veid, PRE_MOUNT_PREFIX);
		}
	}
	if ((ret = fsmount(veid, fs, dq)))
		return ret;
	/* Execute per-CT & global mount scripts */
	if (!(skip & SKIP_ACTION_SCRIPT)) {
		snprintf(buf, sizeof(buf), "%svps.%s", VPS_CONF_DIR,
			MOUNT_PREFIX);
		for (i = 0; i < 2; i++) {
			if (run_pre_script(veid, buf)) {
				logger(-1, 0, "Error executing mount script %s",
					buf);
				fsumount(veid, fs->root);
				return VZ_ACTIONSCRIPT_ERROR;
			}
			snprintf(buf, sizeof(buf), "%s%d.%s", VPS_CONF_DIR,
				veid, MOUNT_PREFIX);
		}
	}
	logger(0, 0, "Container is mounted");

	return 0;
}

static int umount_submounts(const char *root)
{
	FILE *fp;
	struct mntent *mnt;
	int len;
	char path[MAXPATHLEN + 1];

	if (realpath(root, path) == NULL) {
		logger(-1, errno, "realpath(%s) failed", root);
		return -1;
	}
	if ((fp = setmntent("/proc/mounts", "r")) == NULL) {
		logger(-1, errno, "Unable to open /proc/mounts");
		return -1;
	}
	strcat(path, "/"); /* skip base mountpoint */
	len = strlen(path);
	while ((mnt = getmntent(fp)) != NULL) {
		if (strncmp(path, mnt->mnt_dir, len) == 0) {
			if (umount(mnt->mnt_dir))
				logger(-1, errno, "Cannot umount %s",
						mnt->mnt_dir);
		}
	}
	endmntent(fp);

	return 0;
}

/** Unmount CT and run unmount action script if exists.
 *
 * @param h		CT handler.
 * @param veid		CT ID.
 * @param root		CT root.
 * @param skip		skip unmount action scrips
 * @return		0 on success.
 */
int vps_umount(vps_handler *h, envid_t veid, const char *root, skipFlags skip)
{
	char buf[PATH_LEN];
	int ret, i;

	if (!vps_is_mounted(root)) {
		logger(-1, 0, "CT is not mounted");
		return VZ_FS_NOT_MOUNTED;
	}
	if (vps_is_run(h, veid)) {
		logger(-1, 0, "Container is running -- stop it first");
		return 0;
	}
	if (!(skip & SKIP_ACTION_SCRIPT)) {
		snprintf(buf, sizeof(buf), "%s%d.%s", VPS_CONF_DIR,
			veid, UMOUNT_PREFIX);
		for (i = 0; i < 2; i++) {
			if (run_pre_script(veid, buf)) {
				logger(-1, 0, "Error executing umount script %s",
					buf);
				return VZ_ACTIONSCRIPT_ERROR;
			}
			snprintf(buf, sizeof(buf), "%svps.%s", VPS_CONF_DIR,
				UMOUNT_PREFIX);
		}
	}
	umount_submounts(root);
	if (!(ret = fsumount(veid, root)))
		logger(0, 0, "Container is unmounted");
	if (!(skip & SKIP_ACTION_SCRIPT)) {
		snprintf(buf, sizeof(buf), "%s%d.%s", VPS_CONF_DIR,
			veid, POST_UMOUNT_PREFIX);
		for (i = 0; i < 2; i++) {
			if (run_pre_script(veid, buf)) {
				logger(-1, 0, "Error executing umount script %s",
					buf);
				return VZ_ACTIONSCRIPT_ERROR;
			}
			snprintf(buf, sizeof(buf), "%svps.%s", VPS_CONF_DIR,
				POST_UMOUNT_PREFIX);
		}
	}

	return ret;
}

int vps_set_fs(fs_param *g_fs, fs_param *fs)
{
	if (fs->noatime != YES)
		return 0;
	if (check_var(g_fs->root, "VE_ROOT is not set"))
		return VZ_VE_ROOT_NOTSET;
	if (check_var(g_fs->private, "VE_PRIVATE is not set"))
		return VZ_VE_PRIVATE_NOTSET;
	if (!vps_is_mounted(g_fs->root)) {
		logger(-1, 0, "Container is not mounted");
		return VZ_FS_NOT_MOUNTED;
	}
	g_fs->noatime = fs->noatime;
	return vz_mount(g_fs, 1);
}
