// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2017, Intel Corporation.
 */

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Author: Fan, Yong <fan.yong@intel.com>
 */

#define DEBUG_SUBSYSTEM S_MGS
#define D_MGS D_CONFIG

#include <uapi/linux/lustre/lustre_ioctl.h>
#include <lustre_swab.h>
#include <uapi/linux/lustre/lustre_barrier_user.h>

#include "mgs_internal.h"

/**
 * mgs_barrier_gl_interpret_reply() - Handle the barrier lock glimpse reply.
 * @env: pointer to the thread context
 * @req: pointer to the glimpse callback RPC request
 * @data: pointer the async glimpse callback data
 * @rc: the glimpse callback RPC return value
 *
 * The barrier lock glimpse reply contains the target MDT's index and
 * the barrier operation status on such MDT. With such infomation. If
 * the MDT given barrier status is the expected one, then set related
 * 'fsdb''s barrier bitmap; otherwise record the failure or status.
 *
 * Return:
 * * %0 for success
 * * %negative on failure
 */
static int mgs_barrier_gl_interpret_reply(const struct lu_env *env,
					  struct ptlrpc_request *req,
					  void *data, int rc)
{
	struct ldlm_cb_async_args *ca = data;
	struct fs_db *fsdb = ca->ca_set_arg->gl_interpret_data;
	struct barrier_lvb *lvb;
	ENTRY;

	if (rc) {
		if (rc == -ENODEV || rc == -EINVAL) {
			/* The lock is useless, cancel it. */
			ldlm_lock_cancel(ca->ca_lock);
			rc = 0;
		}

		GOTO(out, rc);
	}

	lvb = req_capsule_server_swab_get(&req->rq_pill, &RMF_DLM_LVB,
					  lustre_swab_barrier_lvb);
	if (!lvb)
		GOTO(out, rc = -EPROTO);

	if (lvb->lvb_status == fsdb->fsdb_barrier_expected) {
		if (unlikely(lvb->lvb_index > INDEX_MAP_SIZE))
			rc = -EINVAL;
		else
			set_bit(lvb->lvb_index, fsdb->fsdb_barrier_map);
	} else if (likely(!test_bit(lvb->lvb_index, fsdb->fsdb_barrier_map))) {
		fsdb->fsdb_barrier_result = lvb->lvb_status;
	}

	GOTO(out, rc);

out:
	if (rc)
		fsdb->fsdb_barrier_result = rc;

	return rc;
}

/**
 * mgs_barrier_glimpse_lock() - Send glimpse callback to barrier locks holders
 * @env: pointer to the thread context
 * @mgs: pointer to the MGS device
 * @fsdb: pointer the barrier 'fsdb'
 * @timeout: indicate when the barrier will be expired
 * @expected: the expected barrier status on remote servers (MDTs)
 *
 * The glimpse callback takes the current barrier status. The barrier locks
 * holders (on the MDTs) will take related barrier actions according to the
 * given barrier status, then return their local barrier status.
 *
 * Return:
 * * %>0 positive number for unexpected barrier status
 * * %0 for success
 * * %negative error number on failure
 */
static int mgs_barrier_glimpse_lock(const struct lu_env *env,
				    struct mgs_device *mgs,
				    struct fs_db *fsdb,
				    __u32 timeout, __u32 expected)
{
	union ldlm_gl_desc *desc = &mgs_env_info(env)->mgi_gl_desc;
	struct ldlm_res_id res_id;
	struct ldlm_resource *res;
	struct ldlm_glimpse_work *work;
	struct ldlm_glimpse_work *tmp;
	LIST_HEAD(gl_list);
	struct list_head *pos;
	int i;
	int rc;
	ENTRY;

	LASSERT(fsdb->fsdb_mdt_count > 0);

	rc = mgc_logname2resid(fsdb->fsdb_name, &res_id, MGS_CFG_T_BARRIER);
	if (rc)
		RETURN(rc);

	res = ldlm_resource_get(mgs->mgs_obd->obd_namespace, &res_id,
				LDLM_PLAIN, 0);
	if (IS_ERR(res))
		RETURN(PTR_ERR(res));

	fsdb->fsdb_barrier_result = 0;
	fsdb->fsdb_barrier_expected = expected;
	desc->barrier_desc.lgbd_status = fsdb->fsdb_barrier_status;
	desc->barrier_desc.lgbd_timeout = timeout;

again:
	list_for_each_entry(work, &gl_list, gl_list) {
		if (!work->gl_lock)
			break;

		ldlm_lock_put(work->gl_lock);
		work->gl_lock = NULL;
	}

	/* It is not big issue to alloc more work item than needed. */
	for (i = 0; i < fsdb->fsdb_mdt_count; i++) {
		OBD_ALLOC_PTR(work);
		if (!work)
			GOTO(out, rc = -ENOMEM);

		list_add_tail(&work->gl_list, &gl_list);
	}

	work = list_first_entry(&gl_list, struct ldlm_glimpse_work, gl_list);

	lock_res(res);
	list_for_each(pos, &res->lr_granted) {
		struct ldlm_lock *lock = list_entry(pos, struct ldlm_lock,
						    l_res_link);

		work->gl_lock = ldlm_lock_get(lock);
		work->gl_flags = 0;
		work->gl_desc = desc;
		work->gl_interpret_reply = mgs_barrier_gl_interpret_reply;
		work->gl_interpret_data = fsdb;

		if (unlikely(work->gl_list.next == &gl_list)) {
			if (likely(pos->next == &res->lr_granted))
				break;

			unlock_res(res);
			/* The granted locks are more than the MDTs count. */
			goto again;
		}

		work = list_entry(work->gl_list.next, struct ldlm_glimpse_work,
				  gl_list);
	}
	unlock_res(res);

	/* The MDTs count may be more than the granted locks. */
	list_for_each_entry_safe_reverse(work, tmp, &gl_list, gl_list) {
		if (work->gl_lock)
			break;

		list_del(&work->gl_list);
		OBD_FREE_PTR(work);
	}

	if (!list_empty(&gl_list))
		rc = ldlm_glimpse_locks(res, &gl_list);
	else
		rc = -ENODEV;

	GOTO(out, rc);

out:
	list_for_each_entry_safe(work, tmp, &gl_list, gl_list) {
		list_del(&work->gl_list);
		if (work->gl_lock)
			ldlm_lock_put(work->gl_lock);
		OBD_FREE_PTR(work);
	}

	ldlm_resource_putref(res);
	if (!rc)
		rc = fsdb->fsdb_barrier_result;

	return rc;
}

static void mgs_barrier_bitmap_setup(struct mgs_device *mgs,
				     struct fs_db *b_fsdb,
				     const char *name)
{
	struct fs_db *c_fsdb;

	c_fsdb = mgs_find_fsdb(mgs, name);
	if (likely(c_fsdb)) {
		memcpy(b_fsdb->fsdb_mdt_index_map,
		       c_fsdb->fsdb_mdt_index_map, INDEX_MAP_SIZE);
		b_fsdb->fsdb_mdt_count = c_fsdb->fsdb_mdt_count;
		mgs_put_fsdb(mgs, c_fsdb);
	}
}

static bool mgs_barrier_done(struct fs_db *fsdb)
{
	int i;

	for (i = 0; i < INDEX_MAP_SIZE * 8; i++) {
		if (test_bit(i, fsdb->fsdb_mdt_index_map) &&
		    !test_bit(i, fsdb->fsdb_barrier_map))
			return false;
	}

	return true;
}

static bool mgs_barrier_expired(struct fs_db *fsdb, time64_t timeout)
{
	time64_t expired = fsdb->fsdb_barrier_latest_create_time + timeout;

	return expired > ktime_get_real_seconds();
}

static inline bool mgs_barrier_tests_disabled(struct barrier_ctl *bc,
					      struct obd_export *exp,
					      struct fs_db *fsdb)
{
	__u64 flags = exp_connect_flags(exp);

	if ((flags & OBD_CONNECT_MDS_MDS) && !(flags & OBD_CONNECT_BARRIER)) {
		fsdb->fsdb_barrier_disabled = 1;
		LCONSOLE_WARN("%s: Barrier incompatible connection %s\n",
			      bc->bc_name,
			      obd_uuid2str(&exp->exp_client_uuid));
		return true;
	}
	return false;
}

/**
 * mgs_barrier_freeze() - Create the barrier for the given instance.
 * @env: pointer to the thread context
 * @mgs: pointer to the MGS device
 * @bc: pointer the barrier control structure
 *
 * We use two-phases barrier to guarantee that after the barrier setup:
 * 1) All the server side pending async modification RPCs have been flushed.
 * 2) Any subsequent modification will be blocked.
 * 3) All async transactions on the MDTs have been committed.
 *
 * For phase1, we do the following:
 *
 * Firstly, it sets barrier flag on the instance that will block subsequent
 * modifications from clients. (Note: server sponsored modification will be
 * allowed for flush pending modifications)
 *
 * Secondly, it will flush all pending modification via dt_sync(), such as
 * async OST-object destroy, async OST-object owner changes, and so on.
 *
 * If there are some on-handling clients sponsored modifications during the
 * barrier creating, then related modifications may cause pending requests
 * after the first dt_sync(), so call dt_sync() again after all on-handling
 * modifications done.
 *
 * With the phase1 barrier set, all pending cross-servers modification RPCs
 * have been flushed to remote servers, and any new modification will be
 * blocked. But it does not guarantees that all the updates have been
 * committed to storage on remote servers. So when all the instances have
 * done phase1 barrier successfully, the MGS will notify all instances to
 * do the phase2 barrier as following:
 *
 * Every barrier instance will call dt_sync() to make all async transactions
 * to be committed locally.
 *
 * Return:
 * * %0 on success
 * * %negative on failure
 */
static int mgs_barrier_freeze(const struct lu_env *env,
			      struct mgs_device *mgs,
			      struct barrier_ctl *bc)
{
	char *name = mgs_env_info(env)->mgi_fsname;
	struct fs_db *fsdb;
	int rc = 0;
	time64_t left;
	bool phase1 = true;
	bool dirty = false;
	ENTRY;

	snprintf(name, sizeof(mgs_env_info(env)->mgi_fsname) - 1, "%s-%s",
		 bc->bc_name, BARRIER_FILENAME);

	down_write(&mgs->mgs_barrier_rwsem);
	mutex_lock(&mgs->mgs_mutex);

	rc = mgs_find_or_make_fsdb_nolock(env, mgs, name, &fsdb);
	if (rc) {
		mutex_unlock(&mgs->mgs_mutex);
		up_write(&mgs->mgs_barrier_rwsem);
		RETURN(rc);
	}

	if (unlikely(fsdb->fsdb_mdt_count == 0)) {
		mgs_barrier_bitmap_setup(mgs, fsdb, bc->bc_name);

		/* fsdb was just created, ensure that fsdb_barrier_disabled is
		 * set correctly */
		if (fsdb->fsdb_mdt_count > 0) {
			struct obd_export *exp;
			struct obd_device *mgs_obd = mgs->mgs_obd;

			spin_lock(&mgs_obd->obd_dev_lock);
			list_for_each_entry(exp, &mgs_obd->obd_exports,
					    exp_obd_chain) {
				if (mgs_barrier_tests_disabled(bc, exp, fsdb))
					break;
			}
			spin_unlock(&mgs_obd->obd_dev_lock);
		}
	}

	mutex_lock(&fsdb->fsdb_mutex);
	mutex_unlock(&mgs->mgs_mutex);

	switch (fsdb->fsdb_barrier_status) {
	case BS_THAWING:
	case BS_RESCAN:
		rc = -EBUSY;
		break;
	case BS_FREEZING_P1:
	case BS_FREEZING_P2:
		rc = -EINPROGRESS;
		break;
	case BS_FROZEN:
		if (mgs_barrier_expired(fsdb, fsdb->fsdb_barrier_timeout)) {
			rc = -EALREADY;
			break;
		}
		fallthrough;
	case BS_INIT:
	case BS_THAWED:
	case BS_EXPIRED:
	case BS_FAILED:
		if (fsdb->fsdb_barrier_disabled) {
			rc = -EOPNOTSUPP;
		} else if (unlikely(fsdb->fsdb_mdt_count == 0)) {
			rc = -ENODEV;
		} else {
			fsdb->fsdb_barrier_latest_create_time =
				ktime_get_real_seconds();
			fsdb->fsdb_barrier_status = BS_FREEZING_P1;
			if (bc->bc_timeout != 0)
				fsdb->fsdb_barrier_timeout = bc->bc_timeout;
			else
				fsdb->fsdb_barrier_timeout =
						BARRIER_TIMEOUT_DEFAULT;
			memset(fsdb->fsdb_barrier_map, 0, INDEX_MAP_SIZE);
		}
		break;
	default:
		LCONSOLE_WARN("%s: found unexpected barrier status %u\n",
			      bc->bc_name, fsdb->fsdb_barrier_status);
		rc = -EINVAL;
		LBUG();
	}

	if (rc)
		GOTO(out, rc);

	left = fsdb->fsdb_barrier_timeout;

again:
	mutex_unlock(&fsdb->fsdb_mutex);
	up_write(&mgs->mgs_barrier_rwsem);

	CFS_FAIL_TIMEOUT(OBD_FAIL_BARRIER_DELAY, cfs_fail_val);

	rc = mgs_barrier_glimpse_lock(env, mgs, fsdb, left,
				      phase1 ? BS_FREEZING_P1 : BS_FROZEN);
	down_write(&mgs->mgs_barrier_rwsem);
	mutex_lock(&fsdb->fsdb_mutex);

	dirty = true;
	left = fsdb->fsdb_barrier_latest_create_time +
	       fsdb->fsdb_barrier_timeout - ktime_get_real_seconds();
	if (left <= 0) {
		fsdb->fsdb_barrier_status = BS_EXPIRED;

		GOTO(out, rc = -ETIME);
	}

	LASSERTF(fsdb->fsdb_barrier_status ==
		 (phase1 ? BS_FREEZING_P1 : BS_FREEZING_P2),
		 "unexpected barrier status %u\n",
		 fsdb->fsdb_barrier_status);

	if (rc == -ETIMEDOUT) {
		fsdb->fsdb_barrier_status = BS_EXPIRED;
		rc = -ETIME;
	} else if (rc > 0) {
		fsdb->fsdb_barrier_status = rc;
		rc = -EREMOTE;
	} else if (rc < 0) {
		fsdb->fsdb_barrier_status = BS_FAILED;
	} else if (mgs_barrier_done(fsdb)) {
		if (phase1) {
			fsdb->fsdb_barrier_status = BS_FREEZING_P2;
			memset(fsdb->fsdb_barrier_map, 0,
			       INDEX_MAP_SIZE);
			phase1 = false;

			goto again;
		} else {
			fsdb->fsdb_barrier_status = BS_FROZEN;
		}
	} else {
		fsdb->fsdb_barrier_status = BS_FAILED;
		rc = -EREMOTE;
	}

	GOTO(out, rc);

out:
	mutex_unlock(&fsdb->fsdb_mutex);
	up_write(&mgs->mgs_barrier_rwsem);
	if (rc && dirty) {
		memset(fsdb->fsdb_barrier_map, 0, INDEX_MAP_SIZE);
		mgs_barrier_glimpse_lock(env, mgs, fsdb, 0, BS_THAWED);
	}

	mgs_put_fsdb(mgs, fsdb);

	return rc;
}

static int mgs_barrier_thaw(const struct lu_env *env,
			    struct mgs_device *mgs,
			    struct barrier_ctl *bc)
{
	char *name = mgs_env_info(env)->mgi_fsname;
	struct fs_db *fsdb;
	int rc = 0;
	ENTRY;

	snprintf(name, sizeof(mgs_env_info(env)->mgi_fsname) - 1, "%s-%s",
		 bc->bc_name, BARRIER_FILENAME);

	down_write(&mgs->mgs_barrier_rwsem);
	mutex_lock(&mgs->mgs_mutex);

	rc = mgs_find_or_make_fsdb_nolock(env, mgs, name, &fsdb);
	if (rc) {
		mutex_unlock(&mgs->mgs_mutex);
		up_write(&mgs->mgs_barrier_rwsem);
		RETURN(rc);
	}

	if (unlikely(fsdb->fsdb_mdt_count == 0)) {
		mgs_barrier_bitmap_setup(mgs, fsdb, bc->bc_name);

		/* fsdb was just created, ensure that fsdb_barrier_disabled is
		 * set correctly */
		if (fsdb->fsdb_mdt_count > 0) {
			struct obd_export *exp;
			struct obd_device *mgs_obd = mgs->mgs_obd;

			spin_lock(&mgs_obd->obd_dev_lock);
			list_for_each_entry(exp, &mgs_obd->obd_exports,
					    exp_obd_chain) {
				if (mgs_barrier_tests_disabled(bc, exp, fsdb))
					break;
			}
			spin_unlock(&mgs_obd->obd_dev_lock);
		}
	}

	mutex_lock(&fsdb->fsdb_mutex);
	mutex_unlock(&mgs->mgs_mutex);

	switch (fsdb->fsdb_barrier_status) {
	case BS_FREEZING_P1:
	case BS_FREEZING_P2:
	case BS_RESCAN:
		rc = -EBUSY;
		break;
	case BS_INIT:
	case BS_THAWED:
		rc = -EALREADY;
		break;
	case BS_THAWING:
		rc = -EINPROGRESS;
		break;
	case BS_FROZEN:
	case BS_EXPIRED: /* The barrier on some MDT(s) may be expired,
			  * but may be not on others. Destory anyway. */
	case BS_FAILED:
		if (unlikely(fsdb->fsdb_mdt_count == 0)) {
			rc = -ENODEV;
		} else {
			fsdb->fsdb_barrier_status = BS_THAWING;
			memset(fsdb->fsdb_barrier_map, 0, INDEX_MAP_SIZE);
		}
		break;
	default:
		LCONSOLE_WARN("%s: found unexpected barrier status %u\n",
			      bc->bc_name, fsdb->fsdb_barrier_status);
		rc = -EINVAL;
		LBUG();
	}

	if (rc)
		GOTO(out, rc);

	mutex_unlock(&fsdb->fsdb_mutex);
	up_write(&mgs->mgs_barrier_rwsem);

	CFS_FAIL_TIMEOUT(OBD_FAIL_BARRIER_DELAY, cfs_fail_val);

	rc = mgs_barrier_glimpse_lock(env, mgs, fsdb, 0, BS_THAWED);
	down_write(&mgs->mgs_barrier_rwsem);
	mutex_lock(&fsdb->fsdb_mutex);

	LASSERTF(fsdb->fsdb_barrier_status == BS_THAWING,
		 "unexpected barrier status %u\n",
		 fsdb->fsdb_barrier_status);

	if (rc > 0) {
		fsdb->fsdb_barrier_status = rc;
		rc = -EREMOTE;
	} else if (rc < 0) {
		fsdb->fsdb_barrier_status = BS_FAILED;
	} else if (mgs_barrier_done(fsdb)) {
		fsdb->fsdb_barrier_status = BS_THAWED;
	} else {
		fsdb->fsdb_barrier_status = BS_FAILED;
		rc = -EREMOTE;
	}

	GOTO(out, rc);

out:
	mutex_unlock(&fsdb->fsdb_mutex);
	up_write(&mgs->mgs_barrier_rwsem);
	mgs_put_fsdb(mgs, fsdb);

	return rc;
}

static int mgs_barrier_stat(const struct lu_env *env,
			    struct mgs_device *mgs,
			    struct barrier_ctl *bc)
{
	char *name = mgs_env_info(env)->mgi_fsname;
	struct fs_db *fsdb;
	ENTRY;

	snprintf(name, sizeof(mgs_env_info(env)->mgi_fsname) - 1, "%s-%s",
		 bc->bc_name, BARRIER_FILENAME);

	mutex_lock(&mgs->mgs_mutex);

	fsdb = mgs_find_fsdb(mgs, name);
	if (fsdb) {
		mutex_lock(&fsdb->fsdb_mutex);
		mutex_unlock(&mgs->mgs_mutex);

		bc->bc_status = fsdb->fsdb_barrier_status;
		if (bc->bc_status == BS_FREEZING_P1 ||
		    bc->bc_status == BS_FREEZING_P2 ||
		    bc->bc_status == BS_FROZEN) {
			if (mgs_barrier_expired(fsdb, fsdb->fsdb_barrier_timeout))
				bc->bc_timeout =
					fsdb->fsdb_barrier_latest_create_time +
					fsdb->fsdb_barrier_timeout -
					ktime_get_real_seconds();
			else
				bc->bc_status = fsdb->fsdb_barrier_status =
					BS_EXPIRED;
		}

		mutex_unlock(&fsdb->fsdb_mutex);
		mgs_put_fsdb(mgs, fsdb);
	} else {
		mutex_unlock(&mgs->mgs_mutex);

		bc->bc_status = BS_INIT;
	}

	RETURN(0);
}

static int mgs_barrier_rescan(const struct lu_env *env,
			      struct mgs_device *mgs,
			      struct barrier_ctl *bc)
{
	char *name = mgs_env_info(env)->mgi_fsname;
	struct fs_db *b_fsdb;
	struct fs_db *c_fsdb;
	int rc = 0;
	ENTRY;

	down_write(&mgs->mgs_barrier_rwsem);
	mutex_lock(&mgs->mgs_mutex);

	c_fsdb = mgs_find_fsdb(mgs, bc->bc_name);
	if (!c_fsdb || unlikely(c_fsdb->fsdb_mdt_count == 0)) {
		mutex_unlock(&mgs->mgs_mutex);
		up_write(&mgs->mgs_barrier_rwsem);

		RETURN(-ENODEV);
	}

	snprintf(name, sizeof(mgs_env_info(env)->mgi_fsname) - 1, "%s-%s",
		 bc->bc_name, BARRIER_FILENAME);
	rc = mgs_find_or_make_fsdb_nolock(env, mgs, name, &b_fsdb);
	if (rc) {
		mutex_unlock(&mgs->mgs_mutex);
		up_write(&mgs->mgs_barrier_rwsem);
		mgs_put_fsdb(mgs, c_fsdb);
		RETURN(rc);
	}

	if (unlikely(b_fsdb->fsdb_mdt_count == 0 &&
		     c_fsdb->fsdb_mdt_count > 0)) {
		/* fsdb was just created, ensure that fsdb_barrier_disabled is
		 * set correctly */
		struct obd_export *exp;
		struct obd_device *mgs_obd = mgs->mgs_obd;

		spin_lock(&mgs_obd->obd_dev_lock);
		list_for_each_entry(exp, &mgs_obd->obd_exports,
				    exp_obd_chain) {
			if (mgs_barrier_tests_disabled(bc, exp, b_fsdb))
				break;
		}
		spin_unlock(&mgs_obd->obd_dev_lock);
	}

	mutex_lock(&b_fsdb->fsdb_mutex);
	mutex_lock(&c_fsdb->fsdb_mutex);
	mutex_unlock(&mgs->mgs_mutex);

	switch (b_fsdb->fsdb_barrier_status) {
	case BS_RESCAN:
		rc = -EINPROGRESS;
		break;
	case BS_THAWING:
	case BS_FREEZING_P1:
	case BS_FREEZING_P2:
		rc = -EBUSY;
		break;
	case BS_FROZEN:
		if (mgs_barrier_expired(b_fsdb, b_fsdb->fsdb_barrier_timeout)) {
			rc = -EBUSY;
			break;
		}
		fallthrough;
	case BS_INIT:
	case BS_THAWED:
	case BS_EXPIRED:
	case BS_FAILED:
		b_fsdb->fsdb_barrier_latest_create_time = ktime_get_real_seconds();
		b_fsdb->fsdb_barrier_status = BS_RESCAN;
		memcpy(b_fsdb->fsdb_mdt_index_map, c_fsdb->fsdb_mdt_index_map,
		       INDEX_MAP_SIZE);
		memset(b_fsdb->fsdb_barrier_map, 0, INDEX_MAP_SIZE);
		b_fsdb->fsdb_mdt_count = c_fsdb->fsdb_mdt_count;
		break;
	default:
		LCONSOLE_WARN("%s: found unexpected barrier status %u\n",
			      bc->bc_name, b_fsdb->fsdb_barrier_status);
		rc = -EINVAL;
		LBUG();
	}

	mutex_unlock(&c_fsdb->fsdb_mutex);
	mgs_put_fsdb(mgs, c_fsdb);

	if (rc)
		GOTO(out, rc);

again:
	mutex_unlock(&b_fsdb->fsdb_mutex);
	up_write(&mgs->mgs_barrier_rwsem);
	rc = mgs_barrier_glimpse_lock(env, mgs, b_fsdb, 0, BS_INIT);
	down_write(&mgs->mgs_barrier_rwsem);
	mutex_lock(&b_fsdb->fsdb_mutex);

	LASSERTF(b_fsdb->fsdb_barrier_status == BS_RESCAN,
		 "unexpected barrier status %u\n",
		 b_fsdb->fsdb_barrier_status);

	if (rc > 0) {
		b_fsdb->fsdb_barrier_status = rc;
		rc = -EREMOTE;
	} else if (rc == -ETIMEDOUT &&
		   mgs_barrier_expired(b_fsdb, bc->bc_timeout)) {
		memset(b_fsdb->fsdb_barrier_map, 0, INDEX_MAP_SIZE);

		goto again;
	} else if (rc < 0 && rc != -ETIMEDOUT && rc != -ENODEV) {
		b_fsdb->fsdb_barrier_status = BS_FAILED;
	} else {
		int i;

		b_fsdb->fsdb_mdt_count = 0;
		bc->bc_total = 0;
		bc->bc_absence = 0;
		rc = 0;
		for (i = 0; i < INDEX_MAP_SIZE * 8; i++) {
			if (test_bit(i, b_fsdb->fsdb_barrier_map)) {
				b_fsdb->fsdb_mdt_count++;
			} else if (test_bit(i, b_fsdb->fsdb_mdt_index_map)) {
				b_fsdb->fsdb_mdt_count++;
				bc->bc_absence++;
			}
		}

		bc->bc_total = b_fsdb->fsdb_mdt_count;
		memcpy(b_fsdb->fsdb_mdt_index_map,
		       b_fsdb->fsdb_barrier_map, INDEX_MAP_SIZE);
		b_fsdb->fsdb_barrier_status = BS_INIT;
	}

	GOTO(out, rc);

out:
	mutex_unlock(&b_fsdb->fsdb_mutex);
	up_write(&mgs->mgs_barrier_rwsem);
	mgs_put_fsdb(mgs, b_fsdb);

	return rc;
}

int mgs_iocontrol_barrier(const struct lu_env *env,
			  struct mgs_device *mgs,
			  struct obd_ioctl_data *data)
{
	struct barrier_ctl *bc = (struct barrier_ctl *)(data->ioc_inlbuf1);
	int rc;
	ENTRY;

	if (unlikely(bc->bc_version != BARRIER_VERSION_V1))
		RETURN(-EOPNOTSUPP);

	if (unlikely(bc->bc_name[0] == '\0' ||
		     strnlen(bc->bc_name, sizeof(bc->bc_name)) > 8))
		RETURN(-EINVAL);

	/* NOT allow barrier operations during recovery. */
	if (unlikely(test_bit(OBDF_RECOVERING, mgs->mgs_obd->obd_flags)))
		RETURN(-EBUSY);

	switch (bc->bc_cmd) {
	case BC_FREEZE:
		rc = mgs_barrier_freeze(env, mgs, bc);
		break;
	case BC_THAW:
		rc = mgs_barrier_thaw(env, mgs, bc);
		break;
	case BC_STAT:
		rc = mgs_barrier_stat(env, mgs, bc);
		break;
	case BC_RESCAN:
		rc = mgs_barrier_rescan(env, mgs, bc);
		break;
	default:
		rc = -EINVAL;
		break;
	}

	RETURN(rc);
}
