/*
 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
 * Copyright (C) 2004-2016 Red Hat, Inc. All rights reserved.
 *
 * This file is part of LVM2.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License v.2.1.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "tools.h"

static int _lvresize_params(struct cmd_context *cmd, struct lvresize_params *lp)
{
	const char *type_str = arg_str_value(cmd, type_ARG, NULL);
	int only_linear = 0;
	int set_fsopt = 0;
	int set_extents_and_size = 0;

	memset(lp, 0, sizeof(struct lvresize_params));

	switch (cmd->command->command_enum) {
	case lvextend_policy_CMD:
		lp->resize = LV_EXTEND;
		lp->size = 0;
		lp->extents = 0;
		lp->percent = PERCENT_LV;
		lp->sign = SIGN_PLUS;
		lp->poolmetadata_size = 0;
		lp->use_policies = 1;
		break;

	case lvextend_pool_metadata_CMD:
	case lvresize_pool_metadata_CMD:
		lp->resize = LV_EXTEND;
		lp->size = 0;
		lp->extents = 0;
		lp->percent = PERCENT_NONE;
		lp->sign = SIGN_NONE;
		lp->poolmetadata_size = arg_uint64_value(cmd, poolmetadatasize_ARG, 0);
		lp->poolmetadata_sign = arg_sign_value(cmd, poolmetadatasize_ARG, SIGN_NONE);
		break;

	case lvextend_pv_CMD:
	case lvresize_pv_CMD:
		lp->resize = LV_EXTEND;
		lp->size = 0;
		lp->extents = 0;
		lp->percent_value = 100;
		lp->percent = PERCENT_PVS;
		lp->sign = SIGN_PLUS;
		lp->poolmetadata_size = 0;
		set_fsopt = 1;
		break;

	case lvextend_size_CMD:
		lp->resize = LV_EXTEND;
		if ((lp->poolmetadata_size = arg_uint64_value(cmd, poolmetadatasize_ARG, 0)))
			lp->poolmetadata_sign = arg_sign_value(cmd, poolmetadatasize_ARG, SIGN_NONE);
		set_extents_and_size = 1;
		set_fsopt = 1;
		break;

	case lvreduce_size_CMD:
		lp->resize = LV_REDUCE;
		lp->poolmetadata_size = 0;
		set_extents_and_size = 1;
		set_fsopt = 1;
		break;

	case lvresize_size_CMD:
		lp->resize = LV_ANY;
		lp->poolmetadata_size = arg_uint64_value(cmd, poolmetadatasize_ARG, 0);
		if ((lp->poolmetadata_size = arg_uint64_value(cmd, poolmetadatasize_ARG, 0)))
			lp->poolmetadata_sign = arg_sign_value(cmd, poolmetadatasize_ARG, SIGN_NONE);
		set_extents_and_size = 1;
		set_fsopt = 1;
		break;

	default:
		log_error(INTERNAL_ERROR "unknown lvresize type");
		return 0;
	};

	if (set_fsopt) {
		const char *str;

		if (arg_is_set(cmd, resizefs_ARG) && arg_is_set(cmd, fs_ARG)) {
			log_error("Options --fs and --resizefs cannot be used together.");
			log_error("--resizefs is equivalent to --fs resize.");
			return 0;
		}

#ifdef HAVE_BLKID_SUBLKS_FSINFO
		/*
		 * When the libblkid fs info feature is available, use the
		 * the newer fs resizing capability unless the older
		 * fsadm-based resizing is requested with --fs resize_fsadm.
		 */
		if ((str = arg_str_value(cmd, fs_ARG, NULL))) {
			if (!strcmp(str, "checksize") ||
			    !strcmp(str, "resize") ||
			    !strcmp(str, "resize_fsadm")) {
				strncpy(lp->fsopt, str, sizeof(lp->fsopt)-1);
			} else if (!strcmp(str, "ignore")) {
				lp->fsopt[0] = '\0';
			} else {
				log_error("Unknown --fs value.");
				return 0;
			}
			lp->user_set_fs = 1;
		} else if (arg_is_set(cmd, resizefs_ARG)) {
			/* --resizefs alone equates to --fs resize */
			strncpy(lp->fsopt, "resize", sizeof(lp->fsopt)-1);
			lp->user_set_fs = 1;
		} else {
			/*
			 * Use checksize when no fs option is specified.
			 * checksize with extend does nothing: the LV
			 * is extended and any fs is ignored.
			 * checksize with reduce checks for an fs that
			 * needs reducing: the LV is reduced only if the
			 * fs does not need to be reduced (or no fs.)
			 */
			strncpy(lp->fsopt, "checksize", sizeof(lp->fsopt)-1);
		}
#else
		/*
		 * When the libblkid fs info feature is not available we can only
		 * use fsadm, so --resizefs, --fs resize, --fs resize_fsadm
		 * all translate to resize_fsadm.
		 */
		if ((str = arg_str_value(cmd, fs_ARG, NULL))) {
			if (!strcmp(str, "resize")) {
				log_warn("Using fsadm for file system handling (resize_fsadm).");
				strcpy(lp->fsopt, "resize_fsadm");
			} else if (!strcmp(str, "resize_fsadm")) {
				strcpy(lp->fsopt, "resize_fsadm");
			} else if (!strcmp(str, "ignore")) {
				log_warn("Ignoring unsupported --fs ignore with fsadm resizing.");
			} else {
				log_error("Unknown --fs value.");
				return 0;
			}
		} else if (arg_is_set(cmd, resizefs_ARG)) {
			/* --resizefs alone equates to --fs resize_fsadm */
			strcpy(lp->fsopt, "resize_fsadm");
		}
#endif
		if (lp->fsopt[0])
			lp->nofsck = arg_is_set(cmd, nofsck_ARG);

		if (!strcmp(lp->fsopt, "resize_fsadm") && arg_is_set(cmd, fsmode_ARG)) {
			log_error("The --fsmode option does not apply to resize_fsadm.");
			return 0;
		}

		if ((str = arg_str_value(cmd, fsmode_ARG, NULL))) {
			if (!strcmp(str, "nochange") ||
			    !strcmp(str, "offline") ||
			    !strcmp(str, "manage")) {
				strncpy(lp->fsmode, str, sizeof(lp->fsmode)-1);
				lp->user_set_fsmode = 1;
			} else {
				log_error("Unknown --fsmode value.");
				return 0;
			}
		} else {
			/* Use manage when no fsmode option is specified. */
			strncpy(lp->fsmode, "manage", sizeof(lp->fsmode)-1);
		}
	}

	if (set_extents_and_size) {
		if ((lp->extents = arg_uint_value(cmd, extents_ARG, 0))) {
			lp->sign = arg_sign_value(cmd, extents_ARG, SIGN_NONE);
			lp->percent = arg_percent_value(cmd, extents_ARG, PERCENT_NONE);
		}
		if ((lp->size = arg_uint64_value(cmd, size_ARG, 0))) {
			lp->sign = arg_sign_value(cmd, size_ARG, SIGN_NONE);
			lp->percent = PERCENT_NONE;
		}
		if (lp->size && lp->extents) {
			log_error("Please specify either size or extents but not both.");
			return 0;
		}
	}

	lp->alloc = (alloc_policy_t) arg_uint_value(cmd, alloc_ARG, 0);
	lp->yes = arg_is_set(cmd, yes_ARG);
	lp->force = arg_is_set(cmd, force_ARG),
	lp->nosync = arg_is_set(cmd, nosync_ARG);

	if (type_str) {
		if (!strcmp(type_str, "linear")) {
			type_str = "striped";
			only_linear = 1; /* User requested linear only target */
		}

		if (!(lp->segtype = get_segtype_from_string(cmd, type_str)))
			return_0;
	}

	if ((lp->resize == LV_REDUCE) &&
	    (type_str ||
	     arg_is_set(cmd, mirrors_ARG) ||
	     arg_is_set(cmd, stripes_ARG) ||
	     arg_is_set(cmd, stripesize_ARG))) {
		/* should be obvious since reduce doesn't alloc space. */
		log_print_unless_silent("Ignoring type, stripes, stripesize and mirrors "
					"arguments when reducing.");
		goto out;
	}

	if (arg_is_set(cmd, mirrors_ARG)) {
		if (arg_sign_value(cmd, mirrors_ARG, SIGN_NONE) != SIGN_NONE) {
			log_error("Mirrors argument may not be signed.");
			return 0;
		}
		if ((lp->mirrors = arg_uint_value(cmd, mirrors_ARG, 0)))
			lp->mirrors++;
	}

	if ((lp->stripes = arg_uint_value(cmd, stripes_ARG, 0)) &&
	    (arg_sign_value(cmd, stripes_ARG, SIGN_NONE) == SIGN_MINUS)) {
		log_error("Stripes argument may not be negative.");
		return 0;
	}

	if (only_linear && lp->stripes > 1) {
		log_error("Cannot use stripes with linear type.");
		return 0;
	}

	if ((lp->stripe_size = arg_uint64_value(cmd, stripesize_ARG, 0)) &&
	    (arg_sign_value(cmd, stripesize_ARG, SIGN_NONE) == SIGN_MINUS)) {
		log_error("Stripesize may not be negative.");
		return 0;
	}
out:
	return 1;
}

/*
 * lvextend --use-policies is usually called by dmeventd, as a method of
 * "auto extending" an LV as it's used.  It checks how full a snapshot cow or
 * thin pool is, and extends it if it's too full, based on threshold settings
 * in lvm.conf for when to auto extend it.
 *
 * The extension of a thin pool LV can involve extending either the data sub
 * LV, the metadata sub LV, or both, so there may be two LVs extended here.
 */
static int _lv_extend_policy(struct cmd_context *cmd, struct logical_volume *lv,
			     struct lvresize_params *lp, int *skipped)
{
	uint32_t percent_main = 0;
	uint32_t percent_meta = 0;
	int is_active;

	if (lv_is_cow(lv))
		is_active = lv_is_active(lv);
	else if (lv_is_thin_pool(lv) || lv_is_vdo_pool(lv))
		/* check for -layer active LV */
		is_active = lv_info(lv->vg->cmd, lv, 1, NULL, 0, 0);
	else {
		log_error("lvextend policy is supported only for snapshot, thin pool and vdo pool volumes.");
		return 0;
	}

	if (!is_active) {
		log_error("lvextend using policy requires the volume to be active.");
		return 0;
	}

	/*
	 * Calculate the percent of extents to extend the LV based on current
	 * usage info from the kernel and policy settings from lvm.conf, e.g.
	 * autoextend_threshold, autoextend_percent.  For thin pools, both the
	 * thin pool data LV and thin pool metadata LV may need to be extended.
	 * In this case, percent_main is the amount to extend the data LV, and
	 * percent_meta is the amount to extend the metadata LV.
	 */
	if (!lv_extend_policy_calculate_percent(lv, &percent_main, &percent_meta))
		return_0;

	if (!percent_main && !percent_meta) {
		log_debug("lvextend policy not needed.");
		*skipped = 1;
		return 1;
	}

	*skipped = 0;
	lp->policy_percent_main = percent_main;
	lp->policy_percent_meta = percent_meta;

	return lv_resize(cmd, lv, lp);
}

static int _lvextend_policy_single(struct cmd_context *cmd, struct logical_volume *lv,
				   struct processing_handle *handle)
{
	struct lvresize_params *lp = (struct lvresize_params *) handle->custom_handle;
	int skipped = 0;

	if (cmd->position_argc > 1) {
		/* First pos arg is required LV, remaining are optional PVs. */
		if (!(lp->pvh = create_pv_list(cmd->mem, lv->vg, cmd->position_argc - 1, cmd->position_argv + 1, 0)))
			return_ECMD_FAILED;
	} else
		lp->pvh = &lv->vg->pvs;

	if (!_lv_extend_policy(cmd, lv, lp, &skipped))
		return ECMD_FAILED;

	if (!skipped)
		log_print_unless_silent("Logical volume %s successfully resized.", display_lvname(lv));
	return ECMD_PROCESSED;
}

static int _lvresize_single(struct cmd_context *cmd, struct logical_volume *lv,
			    struct processing_handle *handle)
{
	struct lvresize_params *lp = (struct lvresize_params *) handle->custom_handle;
	int ret;

	if (cmd->position_argc > 1) {
		/* First pos arg is required LV, remaining are optional PVs. */
		if (!(lp->pvh = create_pv_list(cmd->mem, lv->vg, cmd->position_argc - 1, cmd->position_argv + 1, 0)))
			return_ECMD_FAILED;
	} else
		lp->pvh = &lv->vg->pvs;

	ret = lv_resize(cmd, lv, lp);

	if (ret || lp->extend_fs_error)
		log_print_unless_silent("Logical volume %s successfully resized.",
					display_lvname(lv));
	if (!ret)
		return ECMD_FAILED;
	return ECMD_PROCESSED;
}

int lvextend_policy_cmd(struct cmd_context *cmd, int argc, char **argv)
{
	struct processing_handle *handle;
	struct lvresize_params lp;
	int ret;

	if (!_lvresize_params(cmd, &lp))
		return EINVALID_CMD_LINE;

	if (!(handle = init_processing_handle(cmd, NULL)))
		return ECMD_FAILED;

	handle->custom_handle = &lp;

	ret = process_each_lv(cmd, 1, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE,
			      handle, NULL, &_lvextend_policy_single);

	destroy_processing_handle(cmd, handle);

	return ret;
}

int lvresize_cmd(struct cmd_context *cmd, int argc, char **argv)
{
	struct processing_handle *handle;
	struct lvresize_params lp;
	int retries = 0;
	int ret;

	if (!_lvresize_params(cmd, &lp))
		return EINVALID_CMD_LINE;

	if (!(handle = init_processing_handle(cmd, NULL)))
		return ECMD_FAILED;

	handle->custom_handle = &lp;

	do  {
		ret = process_each_lv(cmd, 1, cmd->position_argv, NULL, NULL, READ_FOR_UPDATE,
				      handle, NULL, &_lvresize_single);

		if (!lp.vg_changed_error)
			break;

		if (retries) {
			log_error("VG changed during file system resize, LV not resized.");
			ret = ECMD_FAILED;
			break;
		}

		/*
		 * The VG can be changed by another command while it is unlocked
		 * during fs resize.  The fs steps likely succeeded, and this
		 * retry will likely find that no more fs steps are needed, and
		 * will resize the LV directly.
		 */

		lp.vg_changed_error = 0;

	} while (++retries < 2);

	destroy_processing_handle(cmd, handle);

	if (lp.lockd_lv_refresh_path && !lockd_lv_refresh(cmd, &lp))
		ret = ECMD_FAILED;

	return ret;
}

/*
 * All lvresize command defs have their own function,
 * so the generic function name is unused.
 */

int lvresize(struct cmd_context *cmd, int argc, char **argv)
{
	log_error(INTERNAL_ERROR "Missing function for command definition %d:%s.",
		  cmd->command->command_index, command_enum(cmd->command->command_enum));
	return ECMD_FAILED;
}

