/*
 * Copyright (C) 2001-2004 Sistina Software, Inc. All rights reserved.
 * Copyright (C) 2004-2011 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 "lib.h"
#include "locking.h"
#include "locking_types.h"
#include "lvm-string.h"
#include "activate.h"
#include "toolcontext.h"
#include "memlock.h"
#include "defaults.h"
#include "lvmcache.h"
#include "lvm-signal.h"

#include <assert.h>
#include <sys/stat.h>
#include <limits.h>
#include <unistd.h>

static struct locking_type _locking;

static int _vg_lock_count = 0;		/* Number of locks held */
static int _vg_write_lock_held = 0;	/* VG write lock held? */
static int _blocking_supported = 0;

typedef enum {
        LV_NOOP,
        LV_SUSPEND,
        LV_RESUME
} lv_operation_t;

static void _lock_memory(struct cmd_context *cmd, lv_operation_t lv_op)
{
	if (!(_locking.flags & LCK_PRE_MEMLOCK))
		return;

	if (lv_op == LV_SUSPEND)
		critical_section_inc(cmd, "locking for suspend");
}

static void _unlock_memory(struct cmd_context *cmd, lv_operation_t lv_op)
{
	if (!(_locking.flags & LCK_PRE_MEMLOCK))
		return;

	if (lv_op == LV_RESUME)
		critical_section_dec(cmd, "unlocking on resume");
}

static void _unblock_signals(void)
{
	/* Don't unblock signals while any locks are held */
	if (!_vg_lock_count)
		unblock_signals();
}

void reset_locking(void)
{
	int was_locked = _vg_lock_count;

	_vg_lock_count = 0;
	_vg_write_lock_held = 0;

	if (_locking.reset_locking)
		_locking.reset_locking();

	if (was_locked)
		_unblock_signals();

	memlock_reset();
}

static void _update_vg_lock_count(const char *resource, uint32_t flags)
{
	/* Ignore locks not associated with updating VG metadata */
	if ((flags & LCK_SCOPE_MASK) != LCK_VG ||
	    (flags & LCK_CACHE) ||
	    !strcmp(resource, VG_GLOBAL))
		return;

	if ((flags & LCK_TYPE_MASK) == LCK_UNLOCK)
		_vg_lock_count--;
	else
		_vg_lock_count++;

	/* We don't bother to reset this until all VG locks are dropped */
	if ((flags & LCK_TYPE_MASK) == LCK_WRITE)
		_vg_write_lock_held = 1;
	else if (!_vg_lock_count)
		_vg_write_lock_held = 0;
}

/*
 * Select a locking type
 * type: locking type; if < 0, then read config tree value
 */
int init_locking(int type, struct cmd_context *cmd, int suppress_messages)
{
	if (getenv("LVM_SUPPRESS_LOCKING_FAILURE_MESSAGES"))
		suppress_messages = 1;

	if (type < 0)
		type = find_config_tree_int(cmd, global_locking_type_CFG, NULL);

	_blocking_supported = find_config_tree_bool(cmd, global_wait_for_locks_CFG, NULL);

	switch (type) {
	case 0:
		init_no_locking(&_locking, cmd, suppress_messages);
		log_warn_suppress(suppress_messages,
			"WARNING: Locking disabled. Be careful! "
			"This could corrupt your metadata.");
		return 1;

	case 1:
		log_very_verbose("%sFile-based locking selected.",
				 _blocking_supported ? "" : "Non-blocking ");

		if (!init_file_locking(&_locking, cmd, suppress_messages)) {
			log_error_suppress(suppress_messages,
					   "File-based locking initialisation failed.");
			break;
		}
		return 1;

#ifdef HAVE_LIBDL
	case 2:
		if (!is_static()) {
			log_very_verbose("External locking selected.");
			if (init_external_locking(&_locking, cmd, suppress_messages))
				return 1;
		}
		if (!find_config_tree_bool(cmd, global_fallback_to_clustered_locking_CFG, NULL)) {
			log_error_suppress(suppress_messages, "External locking initialisation failed.");
			break;
		}
#endif

#ifdef CLUSTER_LOCKING_INTERNAL
		log_very_verbose("Falling back to internal clustered locking.");
		/* Fall through */

	case 3:
		log_very_verbose("Cluster locking selected.");
		if (!init_cluster_locking(&_locking, cmd, suppress_messages)) {
			log_error_suppress(suppress_messages,
					   "Internal cluster locking initialisation failed.");
			break;
		}
		return 1;
#endif

	case 4:
		log_verbose("Read-only locking selected. "
			    "Only read operations permitted.");
		if (!init_readonly_locking(&_locking, cmd, suppress_messages))
			break;
		return 1;

	case 5:
		init_dummy_locking(&_locking, cmd, suppress_messages);
		log_verbose("Locking disabled for read-only access.");
		return 1;

	default:
		log_error("Unknown locking type requested.");
		return 0;
	}

	if ((type == 2 || type == 3) &&
	    find_config_tree_bool(cmd, global_fallback_to_local_locking_CFG, NULL)) {
		log_warn_suppress(suppress_messages, "WARNING: Falling back to local file-based locking.");
		log_warn_suppress(suppress_messages,
				  "Volume Groups with the clustered attribute will "
				  "be inaccessible.");
		if (init_file_locking(&_locking, cmd, suppress_messages))
			return 1;
		else
			log_error_suppress(suppress_messages,
					   "File-based locking initialisation failed.");
	}

	if (!ignorelockingfailure())
		return 0;

	log_verbose("Locking disabled - only read operations permitted.");
	init_readonly_locking(&_locking, cmd, suppress_messages);

	return 1;
}

void fin_locking(void)
{
	_locking.fin_locking();
}

/*
 * Does the LVM1 driver know of this VG name?
 */
int check_lvm1_vg_inactive(struct cmd_context *cmd, const char *vgname)
{
	struct stat info;
	char path[PATH_MAX];

	/* We'll allow operations on orphans */
	if (!is_real_vg(vgname))
		return 1;

	/* LVM1 is only present in 2.4 kernels. */
	if (strncmp(cmd->kernel_vsn, "2.4.", 4))
		return 1;

	if (dm_snprintf(path, sizeof(path), "%s/lvm/VGs/%s", cmd->proc_dir,
			 vgname) < 0) {
		log_error("LVM1 proc VG pathname too long for %s", vgname);
		return 0;
	}

	if (stat(path, &info) == 0) {
		log_error("%s exists: Is the original LVM driver using "
			  "this volume group?", path);
		return 0;
	} else if (errno != ENOENT && errno != ENOTDIR) {
		log_sys_error("stat", path);
		return 0;
	}

	return 1;
}

/*
 * VG locking is by VG name.
 * FIXME This should become VG uuid.
 */
static int _lock_vol(struct cmd_context *cmd, const char *resource,
		     uint32_t flags, lv_operation_t lv_op, const struct logical_volume *lv)
{
	uint32_t lck_type = flags & LCK_TYPE_MASK;
	uint32_t lck_scope = flags & LCK_SCOPE_MASK;
	int ret = 0;

	block_signals(flags);
	_lock_memory(cmd, lv_op);

	assert(resource);

	if (!*resource) {
		log_error(INTERNAL_ERROR "Use of P_orphans is deprecated.");
		goto out;
	}

	if ((is_orphan_vg(resource) || is_global_vg(resource)) && (flags & LCK_CACHE)) {
		log_error(INTERNAL_ERROR "P_%s referenced", resource);
		goto out;
	}

	if (cmd->metadata_read_only && lck_type == LCK_WRITE &&
	    strcmp(resource, VG_GLOBAL)) {
		log_error("Operation prohibited while global/metadata_read_only is set.");
		goto out;
	}

	if ((ret = _locking.lock_resource(cmd, resource, flags, lv))) {
		if (lck_scope == LCK_VG && !(flags & LCK_CACHE)) {
			if (lck_type != LCK_UNLOCK)
				lvmcache_lock_vgname(resource, lck_type == LCK_READ);
			dev_reset_error_count(cmd);
		}

		_update_vg_lock_count(resource, flags);
	} else
		stack;

	/* If unlocking, always remove lock from lvmcache even if operation failed. */
	if (lck_scope == LCK_VG && !(flags & LCK_CACHE) && lck_type == LCK_UNLOCK) {
		lvmcache_unlock_vgname(resource);
		if (!ret)
			_update_vg_lock_count(resource, flags);
	}
out:
	_unlock_memory(cmd, lv_op);
	_unblock_signals();

	return ret;
}

int lock_vol(struct cmd_context *cmd, const char *vol, uint32_t flags, const struct logical_volume *lv)
{
	char resource[258] __attribute__((aligned(8)));
	lv_operation_t lv_op;
	int lck_type = flags & LCK_TYPE_MASK;

	switch (flags & (LCK_SCOPE_MASK | LCK_TYPE_MASK)) {
		case LCK_LV_SUSPEND:
				lv_op = LV_SUSPEND;
				break;
		case LCK_LV_RESUME:
				lv_op = LV_RESUME;
				break;
		default:	lv_op = LV_NOOP;
	}


	if (flags == LCK_NONE) {
		log_debug_locking(INTERNAL_ERROR "%s: LCK_NONE lock requested", vol);
		return 1;
	}

	switch (flags & LCK_SCOPE_MASK) {
	case LCK_ACTIVATION:
		break;
	case LCK_VG:
		if (!_blocking_supported)
			flags |= LCK_NONBLOCK;

		/* Global VG_ORPHANS lock covers all orphan formats. */
		if (is_orphan_vg(vol))
			vol = VG_ORPHANS;
		/* VG locks alphabetical, ORPHAN lock last */
		if ((lck_type != LCK_UNLOCK) &&
		    !(flags & LCK_CACHE) &&
		    !lvmcache_verify_lock_order(vol))
			return_0;

		/* Lock VG to change on-disk metadata. */
		/* If LVM1 driver knows about the VG, it can't be accessed. */
		if (!check_lvm1_vg_inactive(cmd, vol))
			return_0;
		break;
	case LCK_LV:
		/* All LV locks are non-blocking. */
		flags |= LCK_NONBLOCK;
		break;
	default:
		log_error("Unrecognised lock scope: %d",
			  flags & LCK_SCOPE_MASK);
		return 0;
	}

	strncpy(resource, vol, sizeof(resource) - 1);
	resource[sizeof(resource) - 1] = '\0';

	if (!_lock_vol(cmd, resource, flags, lv_op, lv))
		return_0;

	/*
	 * If a real lock was acquired (i.e. not LCK_CACHE),
	 * perform an immediate unlock unless LCK_HOLD was requested.
	 */
	if ((lck_type == LCK_NULL) || (lck_type == LCK_UNLOCK) ||
	    (flags & (LCK_CACHE | LCK_HOLD)))
		return 1;

	if (!_lock_vol(cmd, resource, (flags & ~LCK_TYPE_MASK) | LCK_UNLOCK, lv_op, lv))
		return_0;

	return 1;
}

/* Unlock list of LVs */
int resume_lvs(struct cmd_context *cmd, struct dm_list *lvs)
{
	struct lv_list *lvl;
	int r = 1;

	dm_list_iterate_items(lvl, lvs)
		if (!resume_lv(cmd, lvl->lv)) {
			r = 0;
			stack;
		}

	return r;
}

/* Unlock and revert list of LVs */
int revert_lvs(struct cmd_context *cmd, struct dm_list *lvs)
{
	struct lv_list *lvl;
	int r = 1;

	dm_list_iterate_items(lvl, lvs)
		if (!revert_lv(cmd, lvl->lv)) {
			r = 0;
			stack;
		}

	return r;
}
/*
 * Lock a list of LVs.
 * On failure to lock any LV, calls vg_revert() if vg_to_revert is set and 
 * then unlocks any LVs on the list already successfully locked.
 */
int suspend_lvs(struct cmd_context *cmd, struct dm_list *lvs,
		struct volume_group *vg_to_revert)
{
	struct lv_list *lvl;

	dm_list_iterate_items(lvl, lvs) {
		if (!suspend_lv(cmd, lvl->lv)) {
			log_error("Failed to suspend %s", lvl->lv->name);
			if (vg_to_revert)
				vg_revert(vg_to_revert);
			/*
			 * FIXME Should be
			 * 	dm_list_uniterate(lvh, lvs, &lvl->list) {
			 *	lvl = dm_list_item(lvh, struct lv_list);
			 * but revert would need fixing to use identical tree deps first.
			 */
			dm_list_iterate_items(lvl, lvs)
				if (!revert_lv(cmd, lvl->lv))
					stack;

			return 0;
		}
	}

	return 1;
}

/*
 * First try to activate exclusively locally.
 * Then if the VG is clustered and the LV is not yet active (e.g. due to 
 * an activation filter) try activating on remote nodes.
 */
int activate_lv_excl(struct cmd_context *cmd, struct logical_volume *lv) 
{
	/* Non-clustered VGs are only activated locally. */
	if (!vg_is_clustered(lv->vg))
		return activate_lv_excl_local(cmd, lv);

	if (lv_is_active_exclusive_locally(lv))
		return 1;

	if (!activate_lv_excl_local(cmd, lv))
		return_0;

	if (lv_is_active_exclusive(lv))
		return 1;

	/* FIXME Deal with error return codes. */
	if (!activate_lv_excl_remote(cmd, lv))
		return_0;

	return 1;
}

/* Lock a list of LVs */
int activate_lvs(struct cmd_context *cmd, struct dm_list *lvs, unsigned exclusive)
{
	struct dm_list *lvh;
	struct lv_list *lvl;

	dm_list_iterate_items(lvl, lvs) {
		if (!exclusive && !lv_is_active_exclusive(lvl->lv)) {
			if (!activate_lv(cmd, lvl->lv)) {
				log_error("Failed to activate %s", lvl->lv->name);
				return 0;
			}
		} else if (!activate_lv_excl(cmd, lvl->lv)) {
			log_error("Failed to activate %s", lvl->lv->name);
			dm_list_uniterate(lvh, lvs, &lvl->list) {
				lvl = dm_list_item(lvh, struct lv_list);
				if (!activate_lv(cmd, lvl->lv))
					stack;
			}
			return 0;
		}
	}

	return 1;
}

int vg_write_lock_held(void)
{
	return _vg_write_lock_held;
}

int locking_is_clustered(void)
{
	return (_locking.flags & LCK_CLUSTERED) ? 1 : 0;
}

int locking_supports_remote_queries(void)
{
	return (_locking.flags & LCK_SUPPORTS_REMOTE_QUERIES) ? 1 : 0;
}

int cluster_lock_held(const char *vol, const char *node, int *exclusive)
{
	int mode = LCK_NULL;

	if (!locking_is_clustered())
		return 0;

	if (!_locking.query_resource)
		return -1;

	/*
	 * If an error occured, expect that volume is active
	 */
	if (!_locking.query_resource(vol, node, &mode)) {
		stack;
		return 1;
	}

	if (exclusive)
		*exclusive = (mode == LCK_EXCL);

	return mode == LCK_NULL ? 0 : 1;
}

int sync_local_dev_names(struct cmd_context* cmd)
{
	memlock_unlock(cmd);

	return lock_vol(cmd, VG_SYNC_NAMES, LCK_VG_SYNC_LOCAL, NULL);
}

int sync_dev_names(struct cmd_context* cmd)
{
	memlock_unlock(cmd);

	return lock_vol(cmd, VG_SYNC_NAMES, LCK_VG_SYNC, NULL);
}
