Viewing: lmv_obd.c

// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 * Use is subject to license terms.
 *
 * Copyright (c) 2011, 2017, Intel Corporation.
 */

/*
 * This file is part of Lustre, http://www.lustre.org/
 */

#define DEBUG_SUBSYSTEM S_LMV

#include <linux/file.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/user_namespace.h>
#include <linux/uidgid.h>
#include <linux/slab.h>
#include <linux/pagemap.h>
#include <linux/mm.h>
#include <linux/math64.h>
#include <linux/seq_file.h>
#include <linux/namei.h>
#include <linux/glob.h>
#include <linux/stringhash.h>

#include <obd_support.h>
#include <lustre_lib.h>
#include <lustre_net.h>
#include <obd_class.h>
#include <lustre_lmv.h>
#include <lprocfs_status.h>
#include <cl_object.h>
#include <lustre_fid.h>
#include <uapi/linux/lustre/lustre_ioctl.h>
#include <lustre_ioctl_old.h>
#include <lustre_kernelcomm.h>

#include "lmv_internal.h"

static int lmv_check_connect(struct obd_device *obd);
static inline bool lmv_op_default_rr_mkdir(const struct md_op_data *op_data);

void lmv_activate_target(struct lmv_obd *lmv, struct lmv_tgt_desc *tgt,
			 int activate)
{
	if (tgt->ltd_active == activate)
		return;

	tgt->ltd_active = activate;
	lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count +=
		(activate ? 1 : -1);

	tgt->ltd_exp->exp_obd->obd_inactive = !activate;
}

/**
 * lmv_set_mdc_active() - set the active state of a Metadata Client (MDC)
 * @lmv: pointer to lmv_obd
 * @uuid: UUID of the target MDC
 * @activate: 1 to activate MDC or 0 to de-activate MDC
 *
 * Return:
 * * %0: Success (MDC was put into activate state)
 * * %-ERRNO: Failure
 *            Error codes:
 *            -EINVAL  : UUID can't be found in the LMV's target list
 *            -ENOTCONN: UUID is found, but the target connection is bad (!)
 *            -EBADF   : UUID is found, but the OBD of the wrong type (!)
 */
static int lmv_set_mdc_active(struct lmv_obd *lmv,
			      const struct obd_uuid *uuid,
			      int activate)
{
	struct lu_tgt_desc *tgt = NULL;
	struct obd_device *obd;
	int rc = 0;

	ENTRY;

	CDEBUG(D_INFO, "Searching in lmv %p for uuid %s (activate=%d)\n",
			lmv, uuid->uuid, activate);

	spin_lock(&lmv->lmv_lock);
	lmv_foreach_connected_tgt(lmv, tgt) {
		CDEBUG(D_INFO, "Target idx %d is %s conn %#llx\n",
		       tgt->ltd_index, tgt->ltd_uuid.uuid,
		       tgt->ltd_exp->exp_handle.h_cookie);

		if (obd_uuid_equals(uuid, &tgt->ltd_uuid))
			break;
	}

	if (!tgt)
		GOTO(out_lmv_lock, rc = -EINVAL);

	obd = class_exp2obd(tgt->ltd_exp);
	if (obd == NULL)
		GOTO(out_lmv_lock, rc = -ENOTCONN);

	CDEBUG(D_INFO, "Found OBD %s=%s device %d (%p) type %s at LMV idx %d\n",
	       obd->obd_name, obd->obd_uuid.uuid, obd->obd_minor, obd,
	       obd->obd_type->typ_name, tgt->ltd_index);
	LASSERT(strcmp(obd->obd_type->typ_name, LUSTRE_MDC_NAME) == 0);

	if (tgt->ltd_active == activate) {
		CDEBUG(D_INFO, "OBD %p already %sactive!\n", obd,
		       activate ? "" : "in");
		GOTO(out_lmv_lock, rc);
	}

	CDEBUG(D_INFO, "Marking OBD %p %sactive\n", obd,
	       activate ? "" : "in");
	lmv_activate_target(lmv, tgt, activate);
	EXIT;

 out_lmv_lock:
	spin_unlock(&lmv->lmv_lock);
	return rc;
}

struct lu_tgt_desc *lmv_tgt_retry(struct lmv_obd *lmv, __u32 index)
{
	struct obd_device *obd = lmv2obd_dev(lmv);
	struct lu_tgt_desc *tgt;
	static time64_t next_print;
	time64_t retry_limit = 0;
	time64_t now;
	unsigned int level;
	int rc;

	might_sleep();
retry:
	tgt = lmv_tgt(lmv, index);
	if (likely(tgt && tgt->ltd_exp))
		return tgt;

	now = ktime_get_seconds();
	if (retry_limit == 0) {
		level = now > next_print ? D_WARNING : D_INFO;
		retry_limit = now + RECONNECT_DELAY_MAX;
	} else if (now > retry_limit) {
		level = D_ERROR;
	} else {
		level = D_INFO;
	}
	CDEBUG_LIMIT(level, index < lmv->lmv_mdt_count ?
		     "%s: MDT index %u/%u not configured\n" :
		     "%s: MDT index %u more than MDT count %u\n",
		     obd->obd_name, index, lmv->lmv_mdt_count);

	if (index >= LOV_V1_INSANE_STRIPE_INDEX)
		return NULL;

	if (now > next_print) {
		LCONSOLE_INFO("%s: wait %ds while client connects to new MDT\n",
			      obd->obd_name, (int)(retry_limit - now));
		next_print = retry_limit + 600;
	}
	if (now < retry_limit) {
		rc = schedule_timeout_interruptible(cfs_time_seconds(1));
		if (rc == 0)
			goto retry;
	}

	return NULL;
}

static struct obd_uuid *lmv_get_uuid(struct obd_export *exp)
{
	struct lmv_obd *lmv = &exp->exp_obd->u.lmv;
	struct lmv_tgt_desc *tgt = lmv_tgt(lmv, 0);

	return tgt ? obd_get_uuid(tgt->ltd_exp) : NULL;
}

static int lmv_notify(struct obd_device *obd, struct obd_device *watched,
		      enum obd_notify_event ev)
{
	struct obd_connect_data *conn_data;
	struct lmv_obd          *lmv = &obd->u.lmv;
	struct obd_uuid         *uuid;
	int                      rc = 0;

	ENTRY;

	if (strcmp(watched->obd_type->typ_name, LUSTRE_MDC_NAME)) {
		CERROR("unexpected notification of %s %s!\n",
		       watched->obd_type->typ_name,
		       watched->obd_name);
		RETURN(-EINVAL);
	}

	uuid = &watched->u.cli.cl_target_uuid;
	if (ev == OBD_NOTIFY_ACTIVE || ev == OBD_NOTIFY_INACTIVE) {
		/*
		 * Set MDC as active before notifying the observer, so the
		 * observer can use the MDC normally.
		 */
		rc = lmv_set_mdc_active(lmv, uuid,
					ev == OBD_NOTIFY_ACTIVE);
		if (rc) {
			CERROR("%sactivation of %s failed: %d\n",
			       ev == OBD_NOTIFY_ACTIVE ? "" : "de",
			       uuid->uuid, rc);
			RETURN(rc);
		}
	} else if (ev == OBD_NOTIFY_OCD) {
		conn_data = &watched->u.cli.cl_import->imp_connect_data;
		/*
		 * XXX: Make sure that ocd_connect_flags from all targets are
		 * the same. Otherwise one of MDTs runs wrong version or
		 * something like this.  --umka
		 */
		obd->obd_self_export->exp_connect_data = *conn_data;
	}

	/* Pass the notification up the chain.  */
	rc = obd_notify_observer(obd, watched, ev);
	RETURN(rc);
}

static int lmv_connect(const struct lu_env *env,
		       struct obd_export **pexp, struct obd_device *obd,
		       struct obd_uuid *cluuid, struct obd_connect_data *data,
		       void *localdata)
{
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lustre_handle conn = { 0 };
	struct obd_export *exp;
	int rc;

	ENTRY;

	rc = class_connect(&conn, obd, cluuid);
	if (rc) {
		CERROR("class_connection() returned %d\n", rc);
		RETURN(rc);
	}

	exp = class_conn2export(&conn);

	lmv->connected = 0;
	lmv->conn_data = *data;
	lmv->lmv_cache = localdata;

	lmv->lmv_tgts_kobj = kobject_create_and_add("target_obds",
						    &obd->obd_kset.kobj);
	if (!lmv->lmv_tgts_kobj) {
		CERROR("%s: cannot create /sys/fs/lustre/%s/%s/target_obds\n",
		       obd->obd_name, obd->obd_type->typ_name, obd->obd_name);
	}

	rc = lmv_check_connect(obd);
	if (rc != 0)
		GOTO(out_sysfs, rc);

	*pexp = exp;

	RETURN(rc);

out_sysfs:
	if (lmv->lmv_tgts_kobj)
		kobject_put(lmv->lmv_tgts_kobj);

	class_disconnect(exp);

	return rc;
}

static int lmv_init_ea_size(struct obd_export *exp, __u32 easize,
			    __u32 def_easize)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int change = 0;
	int rc = 0;

	ENTRY;

	if (lmv->max_easize < easize) {
		lmv->max_easize = easize;
		change = 1;
	}
	if (lmv->max_def_easize < def_easize) {
		lmv->max_def_easize = def_easize;
		change = 1;
	}

	if (change == 0)
		RETURN(0);

	if (lmv->connected == 0)
		RETURN(0);

	lmv_foreach_connected_tgt(lmv, tgt) {
		if (!tgt->ltd_active)
			continue;

		rc = md_init_ea_size(tgt->ltd_exp, easize, def_easize);
		if (rc) {
			CERROR("%s: obd_init_ea_size() failed on MDT target %d: rc = %d\n",
			       obd->obd_name, tgt->ltd_index, rc);
			break;
		}
	}
	RETURN(rc);
}

#define MAX_STRING_SIZE 128

static int lmv_connect_mdc(struct obd_device *obd, struct lmv_tgt_desc *tgt)
{
	struct lmv_obd *lmv = &obd->u.lmv;
	struct obd_device *mdc_obd;
	struct obd_export *mdc_exp = NULL;
	struct lu_fld_target target;
	int  rc;

	ENTRY;

	mdc_obd = class_find_client_obd(&tgt->ltd_uuid, LUSTRE_MDC_NAME,
					&obd->obd_uuid);
	if (!mdc_obd) {
		CERROR("target %s not attached\n", tgt->ltd_uuid.uuid);
		RETURN(-EINVAL);
	}

	CDEBUG(D_CONFIG, "connect to %s(%s) - %s, %s\n",
	       mdc_obd->obd_name, mdc_obd->obd_uuid.uuid,
	       tgt->ltd_uuid.uuid, obd->obd_uuid.uuid);

	if (!test_bit(OBDF_SET_UP, mdc_obd->obd_flags)) {
		CERROR("target %s is not set up\n", tgt->ltd_uuid.uuid);
		RETURN(-EINVAL);
	}

	/* Propagate upcall to MDC so it can be triggered before connection */
	mdc_obd->obd_upcall = obd->obd_upcall;
	rc = obd_register_observer(mdc_obd, obd);
	if (rc) {
		CERROR("%s: target %s register_observer error: rc = %d\n",
		       obd->obd_name, tgt->ltd_uuid.uuid, rc);
		RETURN(rc);
	}

	rc = obd_connect(NULL, &mdc_exp, mdc_obd, &obd->obd_uuid,
			 &lmv->conn_data, lmv->lmv_cache);
	if (rc)
		GOTO(out_observer, rc);

	/* Init fid sequence client for this mdc and add new fld target.  */
	rc = client_fid_init(mdc_obd, mdc_exp, LUSTRE_SEQ_METADATA);
	if (rc)
		GOTO(out_disconnect, rc);

	target.ft_srv = NULL;
	target.ft_exp = mdc_exp;
	target.ft_idx = tgt->ltd_index;

	rc = fld_client_add_target(&lmv->lmv_fld, &target);
	if (rc)
		GOTO(out_fid, rc);

	if (obd->obd_observer) {
		/* Tell the observer about the new target.  */
		rc = obd_notify(obd->obd_observer, mdc_exp->exp_obd,
				OBD_NOTIFY_ACTIVE);
		if (rc)
			GOTO(out_fld, rc);
	}

	tgt->ltd_active = 1;
	tgt->ltd_exp = mdc_exp;
	lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count++;

	md_init_ea_size(tgt->ltd_exp, lmv->max_easize, lmv->max_def_easize);

	rc = lu_qos_add_tgt(&lmv->lmv_qos, tgt);
	if (rc)
		GOTO(out_tgt, rc);

	CDEBUG(D_CONFIG, "Connected to %s(%s) successfully (%d)\n",
	       mdc_obd->obd_name, mdc_obd->obd_uuid.uuid,
	       kref_read(&obd->obd_refcount));

	lmv_statfs_check_update(obd, tgt);

	if (lmv->lmv_tgts_kobj) {
		/* Even if we failed to create the link, that's fine */
		rc = sysfs_create_link(lmv->lmv_tgts_kobj,
				       &mdc_obd->obd_kset.kobj,
				       mdc_obd->obd_name);
		if (rc)
			CWARN("%s: create sysfs link failure: rc = %d\n",
			      obd->obd_name, rc);
	}

	RETURN(0);

out_tgt:
	tgt->ltd_active = 0;
	tgt->ltd_exp = NULL;
	lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count--;
out_fld:
	fld_client_del_target(&lmv->lmv_fld, tgt->ltd_index);
out_fid:
	client_fid_fini(mdc_obd);
out_disconnect:
	obd_disconnect(mdc_exp);
out_observer:
	obd_register_observer(mdc_obd, NULL);
	mdc_obd->obd_upcall.onu_upcall = NULL;
	return rc;
}

static void lmv_del_target(struct lmv_obd *lmv, struct lu_tgt_desc *tgt)
{
	LASSERT(tgt);
	ltd_del_tgt(&lmv->lmv_mdt_descs, tgt);
	OBD_FREE_PTR(tgt);
}

static int lmv_add_target(struct obd_device *obd, struct obd_uuid *uuidp,
			   __u32 index, int gen)
{
	struct obd_device *mdc_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	struct lu_tgt_descs *ltd = &lmv->lmv_mdt_descs;
	int rc = 0;

	ENTRY;

	CDEBUG(D_CONFIG, "Target uuid: %s. index %d\n", uuidp->uuid, index);
	mdc_obd = class_find_client_obd(uuidp, LUSTRE_MDC_NAME,
					&obd->obd_uuid);
	if (!mdc_obd) {
		CERROR("%s: Target %s not attached: rc = %d\n",
		       obd->obd_name, uuidp->uuid, -EINVAL);
		RETURN(-EINVAL);
	}

	OBD_ALLOC_PTR(tgt);
	if (!tgt)
		RETURN(-ENOMEM);

	mutex_init(&tgt->ltd_fid_mutex);
	tgt->ltd_index = index;
	tgt->ltd_uuid = *uuidp;
	tgt->ltd_active = 0;

	mutex_lock(&ltd->ltd_mutex);
	rc = ltd_add_tgt(ltd, tgt);
	mutex_unlock(&ltd->ltd_mutex);

	if (rc)
		GOTO(out_tgt, rc);

	if (!lmv->connected)
		/* lmv_check_connect() will connect this target. */
		RETURN(0);

	rc = lmv_connect_mdc(obd, tgt);
	if (!rc) {
		int easize = sizeof(struct lmv_stripe_md) +
			lmv->lmv_mdt_count * sizeof(struct lu_fid);

		lmv_init_ea_size(obd->obd_self_export, easize, 0);
	}

	RETURN(rc);

out_tgt:
	OBD_FREE_PTR(tgt);
	return rc;
}

static int lmv_check_connect(struct obd_device *obd)
{
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int easize;
	int rc;

	ENTRY;

	if (lmv->connected)
		RETURN(0);

	mutex_lock(&lmv->lmv_mdt_descs.ltd_mutex);
	if (lmv->connected)
		GOTO(unlock, rc = 0);

	if (!lmv->lmv_mdt_count) {
		CERROR("%s: no targets configured: rc = -EINVAL\n",
		       obd->obd_name);
		GOTO(unlock, rc = -EINVAL);
	}

	if (!lmv_mdt0_inited(lmv)) {
		CERROR("%s: no target configured for index 0: rc = -EINVAL.\n",
		       obd->obd_name);
		GOTO(unlock, rc = -EINVAL);
	}

	CDEBUG(D_CONFIG, "Time to connect %s to %s\n",
	       obd->obd_uuid.uuid, obd->obd_name);

	lmv_foreach_tgt(lmv, tgt) {
		rc = lmv_connect_mdc(obd, tgt);
		if (rc)
			GOTO(out_disc, rc);
	}

	lmv->connected = 1;
	easize = lmv_mds_md_size(lmv->lmv_mdt_count, LMV_MAGIC);
	lmv_init_ea_size(obd->obd_self_export, easize, 0);
	EXIT;
unlock:
	mutex_unlock(&lmv->lmv_mdt_descs.ltd_mutex);

	return rc;

out_disc:
	lmv_foreach_tgt(lmv, tgt) {
		tgt->ltd_active = 0;
		if (!tgt->ltd_exp)
			continue;

		--lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count;
		obd_register_observer(tgt->ltd_exp->exp_obd, NULL);
		obd_disconnect(tgt->ltd_exp);
		tgt->ltd_exp->exp_obd->obd_upcall.onu_owner = NULL;
		tgt->ltd_exp->exp_obd->obd_upcall.onu_upcall = NULL;
	}

	goto unlock;
}

static int lmv_disconnect_mdc(struct obd_device *obd, struct lmv_tgt_desc *tgt)
{
	struct lmv_obd *lmv = &obd->u.lmv;
	struct obd_device *mdc_obd;
	int rc;

	ENTRY;

	LASSERT(tgt != NULL);
	LASSERT(obd != NULL);

	mdc_obd = class_exp2obd(tgt->ltd_exp);

	if (mdc_obd) {
		if (test_bit(OBDF_FORCE, obd->obd_flags))
			set_bit(OBDF_FORCE, mdc_obd->obd_flags);
		else
			clear_bit(OBDF_FORCE, mdc_obd->obd_flags);
		if (test_bit(OBDF_FAIL, obd->obd_flags))
			set_bit(OBDF_FAIL, mdc_obd->obd_flags);
		else
			clear_bit(OBDF_FAIL, mdc_obd->obd_flags);
		if (test_bit(OBDF_NO_RECOV, obd->obd_flags))
			set_bit(OBDF_NO_RECOV, mdc_obd->obd_flags);
		else
			clear_bit(OBDF_NO_RECOV, mdc_obd->obd_flags);

		if (lmv->lmv_tgts_kobj)
			sysfs_remove_link(lmv->lmv_tgts_kobj,
					  mdc_obd->obd_name);
	}

	rc = lu_qos_del_tgt(&lmv->lmv_qos, tgt);
	if (rc)
		CERROR("%s: Can't del target from QoS table: rc = %d\n",
		       tgt->ltd_exp->exp_obd->obd_name, rc);

	rc = fld_client_del_target(&lmv->lmv_fld, tgt->ltd_index);
	if (rc)
		CERROR("%s: Can't del fld targets: rc = %d\n",
		       tgt->ltd_exp->exp_obd->obd_name, rc);

	rc = client_fid_fini(tgt->ltd_exp->exp_obd);
	if (rc)
		CERROR("%s: Can't finalize fids factory: rc = %d\n",
		       tgt->ltd_exp->exp_obd->obd_name, rc);

	CDEBUG(D_INFO, "Disconnected from %s(%s) successfully\n",
	       tgt->ltd_exp->exp_obd->obd_name,
	       tgt->ltd_exp->exp_obd->obd_uuid.uuid);

	lmv_activate_target(lmv, tgt, 0);
	obd_register_observer(tgt->ltd_exp->exp_obd, NULL);
	rc = obd_disconnect(tgt->ltd_exp);
	if (rc) {
		CERROR("%s: Target %s disconnect error: rc = %d\n",
		       tgt->ltd_exp->exp_obd->obd_name,
		       tgt->ltd_uuid.uuid, rc);
	}
	tgt->ltd_exp->exp_obd->obd_upcall.onu_owner = NULL;
	tgt->ltd_exp->exp_obd->obd_upcall.onu_upcall = NULL;
	tgt->ltd_exp = NULL;
	RETURN(0);
}

static int lmv_disconnect(struct obd_export *exp)
{
	struct obd_device *obd = class_exp2obd(exp);
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	lmv_foreach_connected_tgt(lmv, tgt)
		lmv_disconnect_mdc(obd, tgt);

	if (lmv->lmv_tgts_kobj)
		kobject_put(lmv->lmv_tgts_kobj);

	lmv->connected = 0;
	rc = class_disconnect(exp);

	RETURN(rc);
}

static void lmv_statfs_update(struct lmv_obd *lmv, struct lmv_tgt_desc *tgt,
			      struct obd_statfs *osfs)
{
	spin_lock(&lmv->lmv_lock);
	tgt->ltd_statfs = *osfs;
	tgt->ltd_statfs_age = ktime_get_seconds();
	spin_unlock(&lmv->lmv_lock);
	set_bit(LQ_DIRTY, &lmv->lmv_qos.lq_flags);
}

static int lmv_fid2path(struct obd_export *exp, int len, void *karg,
			void __user *uarg)
{
	struct obd_device *obd = class_exp2obd(exp);
	struct lmv_obd *lmv = &obd->u.lmv;
	struct getinfo_fid2path *gf;
	struct lmv_tgt_desc *tgt;
	struct getinfo_fid2path *remote_gf = NULL;
	struct lu_fid root_fid;
	int remote_gf_size = 0;
	int currentisenc = 0;
	int globalisenc = 0;
	int excess;
	int rc;

	gf = karg;
	tgt = lmv_fid2tgt(lmv, &gf->gf_fid);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	root_fid = *gf->gf_u.gf_root_fid;
	LASSERT(fid_is_sane(&root_fid));

repeat_fid2path:
	rc = obd_iocontrol(OBD_IOC_FID2PATH, tgt->ltd_exp, len, gf, uarg);
	if (rc != 0 && rc != -EREMOTE)
		GOTO(out_fid2path, rc);

	if (gf->gf_u.gf_path[0] == '/') {
		/* by convention, server side (mdt_path_current()) puts
		 * a leading '/' to tell client that we are dealing with
		 * an encrypted file
		 */
		currentisenc = 1;
		globalisenc = 1;
	} else {
		currentisenc = 0;
	}

	/* If remote_gf != NULL, it means just building the
	 * path on the remote MDT, copy this path segment to gf.
	 */
	if (remote_gf != NULL) {
		struct getinfo_fid2path *ori_gf;
		int oldisenc = 0;
		char *ptr;
		int len;

		ori_gf = (struct getinfo_fid2path *)karg;
		if (strlen(ori_gf->gf_u.gf_path) + 1 +
		    strlen(gf->gf_u.gf_path) + 1 > ori_gf->gf_pathlen)
			GOTO(out_fid2path, rc = -ENAMETOOLONG);

		ptr = ori_gf->gf_u.gf_path;
		oldisenc = ptr[0] == '/';

		len = strlen(gf->gf_u.gf_path);
		if (len) {
			/* move the current path to the right to release space
			 * for closer-to-root part
			 */
			memmove(ptr + len - currentisenc + 1 + globalisenc,
				ptr + oldisenc,
				strlen(ori_gf->gf_u.gf_path) - oldisenc + 1);
			if (globalisenc)
				*(ptr++) = '/';
			memcpy(ptr, gf->gf_u.gf_path + currentisenc,
			       len - currentisenc);
			ptr[len - currentisenc] = '/';
		}
	}

	excess = gf->gf_pathlen > 3072 ? gf->gf_pathlen - 3072 : 0;
	CDEBUG(D_INFO, "%s: get path %.*s "DFID" rec: %llu ln: %u\n",
	       tgt->ltd_exp->exp_obd->obd_name,
	       gf->gf_pathlen - excess, gf->gf_u.gf_path + excess,
	       PFID(&gf->gf_fid), gf->gf_recno, gf->gf_linkno);

	if (rc == 0)
		GOTO(out_fid2path, rc);

	/* sigh, has to go to another MDT to do path building further */
	if (remote_gf == NULL) {
		remote_gf_size = sizeof(*remote_gf) + len - sizeof(*gf);
		OBD_ALLOC(remote_gf, remote_gf_size);
		if (remote_gf == NULL)
			GOTO(out_fid2path, rc = -ENOMEM);
		remote_gf->gf_pathlen = len - sizeof(*gf);
	}

	if (!fid_is_sane(&gf->gf_fid)) {
		CERROR("%s: invalid FID "DFID": rc = %d\n",
		       tgt->ltd_exp->exp_obd->obd_name,
		       PFID(&gf->gf_fid), -EINVAL);
		GOTO(out_fid2path, rc = -EINVAL);
	}

	tgt = lmv_fid2tgt(lmv, &gf->gf_fid);
	if (IS_ERR(tgt))
		GOTO(out_fid2path, rc = -EINVAL);

	remote_gf->gf_fid = gf->gf_fid;
	remote_gf->gf_recno = -1;
	remote_gf->gf_linkno = -1;
	memset(remote_gf->gf_u.gf_path, 0, remote_gf->gf_pathlen);
	*remote_gf->gf_u.gf_root_fid = root_fid;
	gf = remote_gf;
	goto repeat_fid2path;

out_fid2path:
	OBD_FREE(remote_gf, remote_gf_size);
	RETURN(rc);
}

static int lmv_hsm_req_count(struct lmv_obd *lmv,
			     const struct hsm_user_request *hur,
			     const struct lmv_tgt_desc *tgt_mds)
{
	struct lmv_tgt_desc *curr_tgt;
	__u32 i;
	int nr = 0;

	/* count how many requests must be sent to the given target */
	for (i = 0; i < hur->hur_request.hr_itemcount; i++) {
		curr_tgt = lmv_fid2tgt(lmv, &hur->hur_user_item[i].hui_fid);
		if (IS_ERR(curr_tgt))
			RETURN(PTR_ERR(curr_tgt));
		if (obd_uuid_equals(&curr_tgt->ltd_uuid, &tgt_mds->ltd_uuid))
			nr++;
	}
	return nr;
}

static int lmv_hsm_req_build(struct lmv_obd *lmv,
			      struct hsm_user_request *hur_in,
			      const struct lmv_tgt_desc *tgt_mds,
			      struct hsm_user_request *hur_out)
{
	__u32 i, nr_out;
	struct lmv_tgt_desc *curr_tgt;

	/* build the hsm_user_request for the given target */
	hur_out->hur_request = hur_in->hur_request;
	nr_out = 0;
	for (i = 0; i < hur_in->hur_request.hr_itemcount; i++) {
		curr_tgt = lmv_fid2tgt(lmv, &hur_in->hur_user_item[i].hui_fid);
		if (IS_ERR(curr_tgt))
			RETURN(PTR_ERR(curr_tgt));
		if (obd_uuid_equals(&curr_tgt->ltd_uuid, &tgt_mds->ltd_uuid)) {
			hur_out->hur_user_item[nr_out] =
						hur_in->hur_user_item[i];
			nr_out++;
		}
	}
	hur_out->hur_request.hr_itemcount = nr_out;
	memcpy(hur_data(hur_out), hur_data(hur_in),
	       hur_in->hur_request.hr_data_len);

	RETURN(0);
}

static int lmv_hsm_ct_unregister(struct obd_device *obd, unsigned int cmd,
				 int len, struct lustre_kernelcomm *lk,
				 void __user *uarg)
{
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lu_tgt_desc *tgt;
	int rc;

	ENTRY;

	/* unregister request (call from llapi_hsm_copytool_fini) */
	lmv_foreach_connected_tgt(lmv, tgt)
		/* try to clean as much as possible (continue on error) */
		obd_iocontrol(cmd, tgt->ltd_exp, len, lk, uarg);

	/* Whatever the result, remove copytool from kuc groups.
	 * Unreached coordinators will get EPIPE on next requests
	 * and will unregister automatically.
	 */
	rc = libcfs_kkuc_group_rem(&obd->obd_uuid, lk->lk_uid, lk->lk_group);

	RETURN(rc);
}

static int lmv_hsm_ct_register(struct obd_device *obd, unsigned int cmd,
			       int len, struct lustre_kernelcomm *lk,
			       void __user *uarg)
{
	struct lmv_obd *lmv = &obd->u.lmv;
	struct file *filp;
	bool any_set = false;
	struct kkuc_ct_data *kcd;
	size_t kcd_size;
	struct lu_tgt_desc *tgt;
	__u32 i;
	int err;
	int rc = 0;

	ENTRY;

	filp = fget(lk->lk_wfd);
	if (!filp)
		RETURN(-EBADF);

	if (lk->lk_flags & LK_FLG_DATANR)
		kcd_size = offsetof(struct kkuc_ct_data,
				    kcd_archives[lk->lk_data_count]);
	else
		kcd_size = sizeof(*kcd);

	OBD_ALLOC(kcd, kcd_size);
	if (kcd == NULL)
		GOTO(err_fput, rc = -ENOMEM);

	kcd->kcd_nr_archives = lk->lk_data_count;
	if (lk->lk_flags & LK_FLG_DATANR) {
		kcd->kcd_magic = KKUC_CT_DATA_ARRAY_MAGIC;
		if (lk->lk_data_count > 0)
			memcpy(kcd->kcd_archives, lk->lk_data,
			       sizeof(*kcd->kcd_archives) * lk->lk_data_count);
	} else {
		kcd->kcd_magic = KKUC_CT_DATA_BITMAP_MAGIC;
	}

	rc = libcfs_kkuc_group_add(filp, &obd->obd_uuid, lk->lk_uid,
				   lk->lk_group, kcd, kcd_size);
	OBD_FREE(kcd, kcd_size);
	if (rc)
		GOTO(err_fput, rc);

	/* All or nothing: try to register to all MDS.
	 * In case of failure, unregister from previous MDS,
	 * except if it because of inactive target.
	 */
	lmv_foreach_connected_tgt(lmv, tgt) {
		err = obd_iocontrol(cmd, tgt->ltd_exp, len, lk, uarg);
		if (err) {
			if (tgt->ltd_active) {
				/* permanent error */
				CERROR("%s: iocontrol MDC %s on MDT idx %d cmd %x: err = %d\n",
				       lmv2obd_dev(lmv)->obd_name,
				       tgt->ltd_uuid.uuid, tgt->ltd_index, cmd,
				       err);
				rc = err;
				lk->lk_flags |= LK_FLG_STOP;
				i = tgt->ltd_index;
				/* unregister from previous MDS */
				lmv_foreach_connected_tgt(lmv, tgt) {
					if (tgt->ltd_index >= i)
						break;

					obd_iocontrol(cmd, tgt->ltd_exp, len,
						      lk, uarg);
				}
				GOTO(err_kkuc_rem, rc);
			}
			/* else: transient error.
			 * kuc will register to the missing MDT when it is back
			 */
		} else {
			any_set = true;
		}
	}

	if (!any_set)
		/* no registration done: return error */
		GOTO(err_kkuc_rem, rc = -ENOTCONN);

err_kkuc_rem:
	if (rc)
		libcfs_kkuc_group_rem(&obd->obd_uuid, lk->lk_uid, lk->lk_group);

err_fput:
	fput(filp);
	return rc;
}

static int lmv_iocontrol(unsigned int cmd, struct obd_export *exp,
			 int len, void *karg, void __user *uarg)
{
	struct obd_device *obd = class_exp2obd(exp);
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lu_tgt_desc *tgt = NULL;
	int set = 0;
	__u32 count = lmv->lmv_mdt_count;
	int rc = 0;

	ENTRY;
	CDEBUG(D_IOCTL, "%s: cmd=%x len=%u karg=%pK uarg=%pK\n",
	       exp->exp_obd->obd_name, cmd, len, karg, uarg);
	if (count == 0)
		RETURN(-ENOTTY);

	/* exit early for unknown ioctl types */
	if (unlikely(_IOC_TYPE(cmd) != 'f' && !IOC_OSC_SET_ACTIVE_ALLOW(cmd)))
		RETURN(OBD_IOC_ERROR(obd->obd_name, cmd, "unknown", -ENOTTY));

	/* handle commands that don't use @karg first */
	switch (cmd) {
	case LL_IOC_GET_CONNECT_FLAGS:
		tgt = lmv_tgt(lmv, 0);
		rc = -ENODATA;
		if (tgt && tgt->ltd_exp)
			rc = obd_iocontrol(cmd, tgt->ltd_exp, len, NULL, uarg);
		RETURN(rc);
	}

	if (unlikely(karg == NULL))
		RETURN(OBD_IOC_ERROR(obd->obd_name, cmd, "karg=NULL", -EINVAL));

	switch (cmd) {
	case IOC_OBD_STATFS: {
		struct obd_ioctl_data *data = karg;
		struct obd_device *mdc_obd;
		struct obd_statfs stat_buf = {0};
		__u32 index;

		memcpy(&index, data->ioc_inlbuf2, sizeof(__u32));

		if (index >= lmv->lmv_mdt_descs.ltd_tgts_size)
			RETURN(-ENODEV);

		tgt = lmv_tgt(lmv, index);
		if (!tgt)
			RETURN(-EAGAIN);

		if (!tgt->ltd_active)
			RETURN(-ENODATA);

		mdc_obd = class_exp2obd(tgt->ltd_exp);
		if (!mdc_obd)
			RETURN(-EINVAL);

		/* copy UUID */
		if (copy_to_user(data->ioc_pbuf2, obd2cli_tgt(mdc_obd),
				 min((int) data->ioc_plen2,
				     (int) sizeof(struct obd_uuid))))
			RETURN(-EFAULT);

		rc = obd_statfs(NULL, tgt->ltd_exp, &stat_buf,
				ktime_get_seconds() - OBD_STATFS_CACHE_SECONDS,
				0);
		if (rc)
			RETURN(rc);
		lmv_statfs_update(lmv, tgt, &stat_buf);
		if (copy_to_user(data->ioc_pbuf1, &stat_buf,
				 min_t(int, data->ioc_plen1, sizeof(stat_buf))))
			RETURN(-EFAULT);
		break;
	}
	case OBD_IOC_QUOTACTL: {
		struct if_quotactl *qctl = karg;
		struct obd_quotactl *oqctl;
		struct obd_import *imp;

		if (qctl->qc_valid == QC_MDTIDX) {
			tgt = lmv_tgt(lmv, qctl->qc_idx);
		} else if (qctl->qc_valid == QC_UUID) {
			lmv_foreach_tgt(lmv, tgt) {
				if (!obd_uuid_equals(&tgt->ltd_uuid,
						     &qctl->obd_uuid))
					continue;

				if (!tgt->ltd_exp)
					RETURN(-EINVAL);

				break;
			}
		} else {
			RETURN(-EINVAL);
		}

		if (!tgt)
			RETURN(-ENODEV);

		if (!tgt->ltd_exp)
			RETURN(-EINVAL);

		imp = class_exp2cliimp(tgt->ltd_exp);
		if (!tgt->ltd_active && imp->imp_state != LUSTRE_IMP_IDLE) {
			qctl->qc_valid = QC_MDTIDX;
			qctl->obd_uuid = tgt->ltd_uuid;
			RETURN(-ENODATA);
		}

		OBD_ALLOC_PTR(oqctl);
		if (!oqctl)
			RETURN(-ENOMEM);

		QCTL_COPY(oqctl, qctl);
		rc = obd_quotactl(tgt->ltd_exp, oqctl);
		if (rc == 0) {
			QCTL_COPY_NO_PNAME(qctl, oqctl);
			qctl->qc_valid = QC_MDTIDX;
			qctl->obd_uuid = tgt->ltd_uuid;
		}
		OBD_FREE_PTR(oqctl);
		break;
	}
	case LL_IOC_FID2MDTIDX: {
		struct lu_fid *fid = karg;
		int		mdt_index;

		rc = lmv_fld_lookup(lmv, fid, &mdt_index);
		if (rc != 0)
			RETURN(rc);

		/* Note: this is from llite(see ll_dir_ioctl()), @uarg does not
		 * point to user space memory for FID2MDTIDX.
		 */
		*(__u32 *)uarg = mdt_index;
		break;
	}
	case OBD_IOC_FID2PATH: {
		rc = lmv_fid2path(exp, len, karg, uarg);
		break;
	}
	case LL_IOC_HSM_STATE_GET:
	case LL_IOC_HSM_STATE_SET:
	case LL_IOC_HSM_DATA_VERSION:
	case LL_IOC_HSM_ACTION: {
		struct md_op_data *op_data = karg;

		tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
		if (IS_ERR(tgt))
			RETURN(PTR_ERR(tgt));

		if (tgt->ltd_exp == NULL)
			RETURN(-EINVAL);

		rc = obd_iocontrol(cmd, tgt->ltd_exp, len, karg, uarg);
		break;
	}
	case LL_IOC_HSM_PROGRESS: {
		const struct hsm_progress_kernel *hpk = karg;

		tgt = lmv_fid2tgt(lmv, &hpk->hpk_fid);
		if (IS_ERR(tgt))
			RETURN(PTR_ERR(tgt));
		rc = obd_iocontrol(cmd, tgt->ltd_exp, len, karg, uarg);
		break;
	}
	case LL_IOC_HSM_REQUEST: {
		struct hsm_user_request *hur = karg;
		unsigned int reqcount = hur->hur_request.hr_itemcount;

		if (reqcount == 0)
			RETURN(0);

		/* if the request is about a single fid or if there is a single
		 * MDS, no need to split the request.
		 */
		if (reqcount == 1 || count == 1) {
			tgt = lmv_fid2tgt(lmv, &hur->hur_user_item[0].hui_fid);
			if (IS_ERR(tgt))
				RETURN(PTR_ERR(tgt));
			rc = obd_iocontrol(cmd, tgt->ltd_exp, len, karg, uarg);
		} else {
			/* split fid list to their respective MDS */
			lmv_foreach_connected_tgt(lmv, tgt) {
				int nr, rc1;
				size_t reqlen;
				struct hsm_user_request *req;

				nr = lmv_hsm_req_count(lmv, hur, tgt);
				if (nr < 0)
					RETURN(nr);
				if (nr == 0) /* nothing for this MDS */
					continue;

				/* build a request with fids for this MDS */
				reqlen = offsetof(typeof(*hur),
						  hur_user_item[nr])
						+ hur->hur_request.hr_data_len;
				OBD_ALLOC_LARGE(req, reqlen);
				if (req == NULL)
					RETURN(-ENOMEM);
				rc1 = lmv_hsm_req_build(lmv, hur, tgt, req);
				if (rc1 < 0)
					GOTO(hsm_req_err, rc1);
				rc1 = obd_iocontrol(cmd, tgt->ltd_exp, reqlen,
						    req, uarg);
hsm_req_err:
				if (rc1 != 0 && rc == 0)
					rc = rc1;
				OBD_FREE_LARGE(req, reqlen);
			}
		}
		break;
	}
	case LL_IOC_LOV_SWAP_LAYOUTS: {
		struct md_op_data *op_data = karg;
		struct lmv_tgt_desc *tgt1, *tgt2;

		tgt1 = lmv_fid2tgt(lmv, &op_data->op_fid1);
		if (IS_ERR(tgt1))
			RETURN(PTR_ERR(tgt1));

		tgt2 = lmv_fid2tgt(lmv, &op_data->op_fid2);
		if (IS_ERR(tgt2))
			RETURN(PTR_ERR(tgt2));

		if ((tgt1->ltd_exp == NULL) || (tgt2->ltd_exp == NULL))
			RETURN(-EINVAL);

		/* only files on same MDT can have their layouts swapped */
		if (tgt1->ltd_index != tgt2->ltd_index)
			RETURN(-EPERM);

		rc = obd_iocontrol(cmd, tgt1->ltd_exp, len, karg, uarg);
		break;
	}
	case LL_IOC_HSM_CT_START: {
		struct lustre_kernelcomm *lk = karg;

		if (lk->lk_flags & LK_FLG_STOP)
			rc = lmv_hsm_ct_unregister(obd, cmd, len, lk, uarg);
		else
			rc = lmv_hsm_ct_register(obd, cmd, len, lk, uarg);
		break;
	}
	default:
		lmv_foreach_connected_tgt(lmv, tgt) {
			struct obd_device *mdc_obd;
			int err;

			/* ll_umount_begin() sets force flag but for lmv, not
			 * mdc. Let's pass it through
			 */
			mdc_obd = class_exp2obd(tgt->ltd_exp);
			if (test_bit(OBDF_FORCE, obd->obd_flags))
				set_bit(OBDF_FORCE, mdc_obd->obd_flags);
			else
				clear_bit(OBDF_FORCE, mdc_obd->obd_flags);
			err = obd_iocontrol(cmd, tgt->ltd_exp, len, karg, uarg);
			if (err) {
				if (tgt->ltd_active) {
					OBD_IOC_ERROR(obd->obd_name, cmd,
						      tgt->ltd_uuid.uuid, err);
					if (!rc)
						rc = err;
					if (unlikely(err == -ENOTTY))
						break;
				}
			} else {
				set = 1;
			}
		}
		if (!set && !rc)
			rc = -EIO;
		break;
	}
	RETURN(rc);
}

int lmv_fid_alloc(const struct lu_env *env, struct obd_export *exp,
		  struct lu_fid *fid, struct md_op_data *op_data)
{
	struct obd_device *obd = class_exp2obd(exp);
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	LASSERT(op_data);
	LASSERT(fid);

	tgt = lmv_tgt(lmv, op_data->op_mds);
	if (!tgt)
		RETURN(-ENODEV);

	if (!tgt->ltd_active || !tgt->ltd_exp)
		RETURN(-ENODEV);

	/*
	 * New seq alloc and FLD setup should be atomic. Otherwise we may find
	 * on server that seq in new allocated fid is not yet known.
	 */
	mutex_lock(&tgt->ltd_fid_mutex);
	rc = obd_fid_alloc(NULL, tgt->ltd_exp, fid, NULL);
	mutex_unlock(&tgt->ltd_fid_mutex);
	if (rc > 0) {
		LASSERT(fid_is_sane(fid));
		rc = 0;
	}

	RETURN(rc);
}

static const struct lu_device_operations lmv_lu_ops;

static struct lu_device *lmv_device_alloc(const struct lu_env *env,
					  struct lu_device_type *ldt,
					  struct lustre_cfg *lcfg)
{
	struct qos_exclude_pattern *pat;
	struct lnet_processid lnet_id;
	struct obd_device *obd;
	struct lmv_desc *desc;
	struct lu_device *lu;
	struct lmv_obd *lmv;
	int i = 0;
	int rc;

	ENTRY;

	OBD_ALLOC_PTR(lu);
	if (!lu)
		RETURN(ERR_PTR(-ENOMEM));

	lu->ld_ops = &lmv_lu_ops;
	obd = class_name2obd(lustre_cfg_string(lcfg, 0));
	LASSERT(obd);
	lmv = &obd->u.lmv;
	obd->obd_lu_dev = lu;

	if (LUSTRE_CFG_BUFLEN(lcfg, 1) < 1) {
		CERROR("LMV setup requires a descriptor\n");
		GOTO(out_free, rc = -EINVAL);
	}

	desc = (struct lmv_desc *)lustre_cfg_buf(lcfg, 1);
	if (sizeof(*desc) > LUSTRE_CFG_BUFLEN(lcfg, 1)) {
		CERROR("Lmv descriptor size wrong: %d > %d\n",
		       (int)sizeof(*desc), LUSTRE_CFG_BUFLEN(lcfg, 1));
		GOTO(out_free, rc = -EINVAL);
	}

	obd_str2uuid(&lmv->lmv_mdt_descs.ltd_lmv_desc.ld_uuid,
		     desc->ld_uuid.uuid);
	lmv->lmv_mdt_descs.ltd_lmv_desc.ld_tgt_count = 0;
	lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count = 0;
	lmv->lmv_mdt_descs.ltd_lmv_desc.ld_qos_maxage =
		LMV_DESC_QOS_MAXAGE_DEFAULT;
	lmv->max_def_easize = 0;
	lmv->max_easize = 0;

	spin_lock_init(&lmv->lmv_lock);
	INIT_LIST_HEAD(&lmv->lmv_qos_exclude_list);

	/*
	 * initialize rr_index to lower 32bit of netid, so that client
	 * can distribute new subdir creation in round-robin directories
	 * relatively evenly across MDTs from the beginning.
	 */
	while (LNetGetId(i++, &lnet_id, true) != -ENOENT) {
		if (!nid_is_lo0(&lnet_id.nid)) {
			lmv->lmv_qos_rr_index = nidhash(&lnet_id.nid);
			break;
		}
	}

	rc = fld_client_init(&lmv->lmv_fld, obd->obd_name,
			     LUSTRE_CLI_FLD_HASH_DHT);
	if (rc)
		CERROR("Can't init FLD, err %d\n", rc);

	rc = lu_tgt_descs_init(&lmv->lmv_mdt_descs, true);
	if (rc)
		CWARN("%s: error initialize target table: rc = %d\n",
		      obd->obd_name, rc);

	OBD_ALLOC_PTR(pat);
	if (!pat)
		GOTO(out_free, rc = -ENOMEM);

	/* Apache Spark creates a _temporary directory for staging files */
	strcpy(pat->qep_name, "_temporary");

	list_add_tail(&pat->qep_list, &lmv->lmv_qos_exclude_list);

	OBD_ALLOC_PTR(pat);
	if (!pat)
		GOTO(out_free, rc = -ENOMEM);

	strcpy(pat->qep_name, "_temporary.*");

	list_add_tail(&pat->qep_list, &lmv->lmv_qos_exclude_list);

	rc = lmv_tunables_init(obd);
	if (rc)
		CWARN("%s: error adding LMV sysfs/debugfs files: rc = %d\n",
		      obd->obd_name, rc);

	RETURN(lu);

out_free:
	OBD_FREE_PTR(lu);
	RETURN(ERR_PTR(rc));
}

static struct lu_device *lmv_device_free(const struct lu_env *env,
					 struct lu_device *lu)
{
	struct obd_device *obd = lu->ld_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lu_tgt_desc *tgt;
	struct lu_tgt_desc *tmp;
	struct qos_exclude_pattern *pat;
	struct qos_exclude_pattern *ptmp;

	ENTRY;

	lprocfs_obd_cleanup(obd);
	fld_client_debugfs_fini(&lmv->lmv_fld);

	spin_lock(&lmv->lmv_lock);
	list_for_each_entry_safe(pat, ptmp,
		&lmv->lmv_qos_exclude_list, qep_list) {
		list_del(&pat->qep_list);
		OBD_FREE_PTR(pat);
	}
	spin_unlock(&lmv->lmv_lock);
	fld_client_fini(&lmv->lmv_fld);

	lprocfs_free_md_stats(obd);

	lmv_foreach_tgt_safe(lmv, tgt, tmp)
		lmv_del_target(lmv, tgt);
	lu_tgt_descs_fini(&lmv->lmv_mdt_descs);
	OBD_FREE_PTR(lu);

	RETURN(NULL);
}

static int lmv_process_config(const struct lu_env *env, struct lu_device *lu,
			      struct lustre_cfg *lcfg)
{
	struct obd_device *obd = lu->ld_obd;
	struct obd_uuid	obd_uuid;
	__u32 index;
	int gen;
	int rc;

	ENTRY;

	switch (lcfg->lcfg_command) {
	case LCFG_ADD_MDC:
		/* modify_mdc_tgts add 0:lustre-clilmv  1:lustre-MDT0000_UUID
		 * 2:0  3:1  4:lustre-MDT0000-mdc_UUID
		 */
		if (LUSTRE_CFG_BUFLEN(lcfg, 1) > sizeof(obd_uuid.uuid))
			GOTO(out, rc = -EINVAL);

		obd_str2uuid(&obd_uuid,  lustre_cfg_buf(lcfg, 1));

		if (sscanf(lustre_cfg_buf(lcfg, 2), "%u", &index) != 1)
			GOTO(out, rc = -EINVAL);
		if (sscanf(lustre_cfg_buf(lcfg, 3), "%d", &gen) != 1)
			GOTO(out, rc = -EINVAL);
		rc = lmv_add_target(obd, &obd_uuid, index, gen);
		GOTO(out, rc);
	default:
		CERROR("Unknown command: %d\n", lcfg->lcfg_command);
		GOTO(out, rc = -EINVAL);
	}
out:
	RETURN(rc);
}

static int lmv_select_statfs_mdt(struct obd_export *exp, struct lmv_obd *lmv,
				 u32 flags)
{
	bool large_nid = exp_connect_flags2(exp) & OBD_CONNECT2_LARGE_NID;
	int i;

	if (flags & OBD_STATFS_FOR_MDT0)
		return 0;

	if (lmv->lmv_statfs_start || lmv->lmv_mdt_count == 1)
		return lmv->lmv_statfs_start;

	/* choose initial MDT for this client */
	for (i = 0;; i++) {
		struct lnet_processid lnet_id;

		if (LNetGetId(i, &lnet_id, large_nid) == -ENOENT)
			break;

		if (!nid_is_lo0(&lnet_id.nid)) {
			/* We dont need a full 64-bit modulus, just enough
			 * to distribute the requests across MDTs evenly.
			 */
			lmv->lmv_statfs_start = nidhash(&lnet_id.nid) %
						lmv->lmv_mdt_count;
			break;
		}
	}

	return lmv->lmv_statfs_start;
}

static int lmv_statfs(const struct lu_env *env, struct obd_export *exp,
		      struct obd_statfs *osfs, time64_t max_age, __u32 flags)
{
	struct obd_device *obd = class_exp2obd(exp);
	struct lmv_obd *lmv = &obd->u.lmv;
	struct obd_statfs *temp;
	struct lu_tgt_desc *tgt;
	__u32 i;
	__u32 idx;
	int rc = 0;
	int err = 0;

	ENTRY;

	if (lmv->lmv_mdt_count == 0)
		return -ENODATA;
	OBD_ALLOC(temp, sizeof(*temp));
	if (temp == NULL)
		RETURN(-ENOMEM);

	/* distribute statfs among MDTs */
	idx = lmv_select_statfs_mdt(exp, lmv, flags);

	for (i = 0; i < lmv->lmv_mdt_descs.ltd_tgts_size; i++, idx++) {
		idx = idx % lmv->lmv_mdt_descs.ltd_tgts_size;
		tgt = lmv_tgt(lmv, idx);
		if (!tgt || !tgt->ltd_exp)
			continue;

		rc = obd_statfs(env, tgt->ltd_exp, temp, max_age,
				flags | OBD_STATFS_NESTED);
		if (rc) {
			CERROR("%s: can't stat MDS #%d: rc = %d\n",
			       tgt->ltd_exp->exp_obd->obd_name, i, rc);
			err = rc;
			/* Try another MDT */
			if (flags & OBD_STATFS_SUM)
				continue;
			GOTO(out_free_temp, rc);
		}

		if (temp->os_state & OS_STATFS_SUM ||
		    flags == OBD_STATFS_FOR_MDT0) {
			/* reset to the last aggregated values and don't sum
			 * with non-aggrated data
			 *
			 * If the statfs is from mount, it needs to retrieve
			 * necessary info from MDT0. i.e. mount does not need
			 * the merged osfs from all of MDT. Also clients can be
			 * mounted as long as MDT0 is in service
			 */
			*osfs = *temp;
			GOTO(out_free_temp, rc);
		}

		if (i == 0) {
			*osfs = *temp;
		} else {
			osfs->os_bavail += temp->os_bavail;
			osfs->os_blocks += temp->os_blocks;
			osfs->os_ffree += temp->os_ffree;
			osfs->os_files += temp->os_files;
			osfs->os_granted += temp->os_granted;
			osfs->os_namelen = min(osfs->os_namelen,
					       temp->os_namelen);
			osfs->os_maxbytes = min(osfs->os_maxbytes,
						temp->os_maxbytes);
			/* OR failure states, AND performance states */
			osfs->os_state |= temp->os_state & ~OS_STATFS_DOWNGRADE;
			osfs->os_state &= temp->os_state & OS_STATFS_UPGRADE;
		}
	}
	/* There is no stats from some MDTs, data incomplete */
	if (err)
		rc = err;
out_free_temp:
	OBD_FREE(temp, sizeof(*temp));
	RETURN(rc);
}

static int lmv_statfs_cb(void *cookie, int rc)
{
	struct obd_info *oinfo = cookie;
	struct obd_device *obd = oinfo->oi_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt = oinfo->oi_tgt;
	struct obd_statfs *osfs = oinfo->oi_osfs;

	/*
	 * NB: don't deactivate TGT upon error, because we may not trigger async
	 * statfs any longer, then there is no chance to activate TGT.
	 */
	if (!rc)
		lmv_statfs_update(lmv, tgt, osfs);

	return rc;
}

/* update tgt statfs async if it's ld_qos_maxage old */
int lmv_statfs_check_update(struct obd_device *obd, struct lmv_tgt_desc *tgt)
{
	struct obd_info oinfo = {
		.oi_obd	= obd,
		.oi_tgt = tgt,
		.oi_cb_up = lmv_statfs_cb,
	};
	int rc;

	if (ktime_get_seconds() - tgt->ltd_statfs_age <
	    obd->u.lmv.lmv_mdt_descs.ltd_lmv_desc.ld_qos_maxage)
		return 0;

	rc = obd_statfs_async(tgt->ltd_exp, &oinfo, 0, NULL);

	return rc;
}

static int lmv_get_root(struct obd_export *exp, const char *fileset,
			struct lu_fid *fid)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lu_tgt_desc *tgt = lmv_tgt(lmv, 0);
	int rc;

	ENTRY;

	if (!tgt)
		RETURN(-ENODEV);

	rc = md_get_root(tgt->ltd_exp, fileset, fid);
	if (!rc)
		lmv->lmv_setup_time = ktime_get_seconds();
	RETURN(rc);
}

static int lmv_getxattr(struct obd_export *exp, const struct lu_fid *fid,
			u64 obd_md_valid, const char *name, size_t buf_size,
			u32 projid, struct ptlrpc_request **req)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, fid);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	rc = md_getxattr(tgt->ltd_exp, fid, obd_md_valid, name, buf_size,
			 projid, req);

	RETURN(rc);
}

static int lmv_setxattr(struct obd_export *exp, const struct lu_fid *fid,
			u64 obd_md_valid, const char *name, const void *value,
			size_t value_size, unsigned int xattr_flags,
			u32 suppgid, u32 projid, struct ptlrpc_request **req)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, fid);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	rc = md_setxattr(tgt->ltd_exp, fid, obd_md_valid, name, value,
			 value_size, xattr_flags, suppgid, projid, req);

	RETURN(rc);
}

static int lmv_getattr(struct obd_export *exp, struct md_op_data *op_data,
		       struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	if (op_data->op_flags & MF_GET_MDT_IDX) {
		op_data->op_mds = tgt->ltd_index;
		RETURN(0);
	}

	rc = md_getattr(tgt->ltd_exp, op_data, request);

	RETURN(rc);
}

static int lmv_null_inode(struct obd_export *exp, const struct lu_fid *fid)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lu_tgt_desc *tgt;

	ENTRY;

	CDEBUG(D_INODE, "CBDATA for "DFID"\n", PFID(fid));

	/*
	 * With DNE every object can have two locks in different namespaces:
	 * lookup lock in space of MDT storing direntry and update/open lock in
	 * space of MDT storing inode.
	 */
	lmv_foreach_connected_tgt(lmv, tgt)
		md_null_inode(tgt->ltd_exp, fid);

	RETURN(0);
}

static int lmv_close(struct obd_export *exp, struct md_op_data *op_data,
		     struct md_open_data *mod, struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	CDEBUG(D_INODE, "CLOSE "DFID"\n", PFID(&op_data->op_fid1));
	rc = md_close(tgt->ltd_exp, op_data, mod, request);
	RETURN(rc);
}

static inline bool tgt_qos_is_usable(struct lmv_obd *lmv,
				     struct lu_tgt_desc *tgt, time64_t now)
{
	struct obd_import *imp = class_exp2cliimp(tgt->ltd_exp);
	u32 maxage = lmv->lmv_mdt_descs.ltd_lmv_desc.ld_qos_maxage;

	return tgt->ltd_exp && tgt->ltd_active &&
	       !(tgt->ltd_statfs.os_state & OS_STATFS_NOCREATE) &&
	       (now - imp->imp_setup_time > (maxage >> 1) ||
		now - lmv->lmv_setup_time < (maxage << 1));
}

static struct lu_tgt_desc *lmv_locate_tgt_qos(struct lmv_obd *lmv,
					      struct md_op_data *op_data)
{
	struct lu_tgt_desc *tgt, *cur = NULL;
	time64_t now = ktime_get_seconds();
	__u64 total_avail = 0;
	__u64 total_weight = 0;
	__u64 cur_weight = 0;
	int total_usable = 0;
	__u64 rand;
	int rc;

	ENTRY;

	if (!ltd_qos_is_usable(&lmv->lmv_mdt_descs))
		RETURN(ERR_PTR(-EAGAIN));

	down_write(&lmv->lmv_qos.lq_rw_sem);

	if (!ltd_qos_is_usable(&lmv->lmv_mdt_descs))
		GOTO(unlock, tgt = ERR_PTR(-EAGAIN));

	rc = ltd_qos_penalties_calc(&lmv->lmv_mdt_descs);
	if (rc)
		GOTO(unlock, tgt = ERR_PTR(rc));

	lmv_foreach_tgt(lmv, tgt) {
		if (!tgt_qos_is_usable(lmv, tgt, now)) {
			tgt->ltd_qos.ltq_usable = 0;
			continue;
		}
		/* update one hour overdue statfs */
		if (now - tgt->ltd_statfs_age >
		    60 * lmv->lmv_mdt_descs.ltd_lmv_desc.ld_qos_maxage)
			lmv_statfs_check_update(lmv2obd_dev(lmv), tgt);
		tgt->ltd_qos.ltq_usable = 1;
		lu_tgt_qos_weight_calc(tgt, true);
		if (tgt->ltd_index == op_data->op_mds)
			cur = tgt;
		total_avail += tgt->ltd_qos.ltq_avail;
		total_weight += tgt->ltd_qos.ltq_weight;
		total_usable++;
	}

	/* If current MDT has above-average space and dir is not already using
	 * round-robin to spread across more MDTs, stay on the parent MDT
	 * to avoid creating needless remote MDT directories.  Remote dirs
	 * close to the root balance space more effectively than bottom dirs,
	 * so prefer to create remote dirs at top level of directory tree.
	 * "16 / (dir_depth + 10)" is the factor to make it less likely
	 * for top-level directories to stay local unless they have more than
	 * average free space, while deep dirs prefer local until more full.
	 *    depth=0 -> 160%, depth=3 -> 123%, depth=6 -> 100%,
	 *    depth=9 -> 84%, depth=12 -> 73%, depth=15 -> 64%
	 */
	if (!lmv_op_default_rr_mkdir(op_data)) {
		rand = total_avail * 16 /
			(total_usable * (op_data->op_dir_depth + 10));
		if (cur && cur->ltd_qos.ltq_avail >= rand) {
			tgt = cur;
			GOTO(unlock, tgt);
		}
	}

	rand = lu_prandom_u64_max(total_weight);

	lmv_foreach_connected_tgt(lmv, tgt) {
		if (!tgt->ltd_qos.ltq_usable)
			continue;

		cur_weight += tgt->ltd_qos.ltq_weight;
		if (cur_weight < rand)
			continue;

		ltd_qos_update(&lmv->lmv_mdt_descs, tgt, &total_weight);
		GOTO(unlock, tgt);
	}

	/* no proper target found */
	GOTO(unlock, tgt = ERR_PTR(-EAGAIN));
unlock:
	up_write(&lmv->lmv_qos.lq_rw_sem);

	return tgt;
}

static struct lu_tgt_desc *lmv_locate_tgt_rr(struct lmv_obd *lmv)
{
	time64_t now = ktime_get_seconds();
	struct lu_tgt_desc *tgt;
	int i;
	int index;

	ENTRY;

	spin_lock(&lmv->lmv_lock);
	for (i = 0; i < lmv->lmv_mdt_descs.ltd_tgts_size; i++) {
		index = (i + lmv->lmv_qos_rr_index) %
			lmv->lmv_mdt_descs.ltd_tgts_size;
		tgt = lmv_tgt(lmv, index);
		if (!tgt || !tgt_qos_is_usable(lmv, tgt, now))
			continue;

		lmv->lmv_qos_rr_index = (tgt->ltd_index + 1) %
					lmv->lmv_mdt_descs.ltd_tgts_size;
		spin_unlock(&lmv->lmv_lock);

		RETURN(tgt);
	}
	spin_unlock(&lmv->lmv_lock);

	RETURN(ERR_PTR(-ENODEV));
}

/* locate MDT which is less full (avoid the most full MDT) */
static struct lu_tgt_desc *lmv_locate_tgt_lf(struct lmv_obd *lmv)
{
	struct lu_tgt_desc *min = NULL;
	struct lu_tgt_desc *tgt;
	__u64 avail = 0;
	__u64 rand;

	ENTRY;

	if (!ltd_qos_is_usable(&lmv->lmv_mdt_descs))
		RETURN(ERR_PTR(-EAGAIN));

	down_write(&lmv->lmv_qos.lq_rw_sem);

	if (!ltd_qos_is_usable(&lmv->lmv_mdt_descs))
		GOTO(unlock, tgt = ERR_PTR(-EAGAIN));

	lmv_foreach_tgt(lmv, tgt) {
		if (!tgt->ltd_exp || !tgt->ltd_active ||
		    (tgt->ltd_statfs.os_state & OS_STATFS_NOCREATE)) {
			tgt->ltd_qos.ltq_usable = 0;
			continue;
		}

		tgt->ltd_qos.ltq_usable = 1;
		lu_tgt_qos_weight_calc(tgt, true);
		avail += tgt->ltd_qos.ltq_avail;
		if (!min || min->ltd_qos.ltq_avail > tgt->ltd_qos.ltq_avail)
			min = tgt;
	}

	/* avoid the most full MDT */
	if (min)
		avail -= min->ltd_qos.ltq_avail;

	rand = lu_prandom_u64_max(avail);
	avail = 0;
	lmv_foreach_connected_tgt(lmv, tgt) {
		if (!tgt->ltd_qos.ltq_usable)
			continue;

		if (tgt == min)
			continue;

		avail += tgt->ltd_qos.ltq_avail;
		if (avail < rand)
			continue;

		GOTO(unlock, tgt);
	}

	/* no proper target found */
	GOTO(unlock, tgt = ERR_PTR(-EAGAIN));
unlock:
	up_write(&lmv->lmv_qos.lq_rw_sem);

	RETURN(tgt);
}

/* locate MDT by file name, for striped directory, the file name hash decides
 * which stripe its dirent is stored.
 */
static struct lmv_tgt_desc *
lmv_locate_tgt_by_name(struct lmv_obd *lmv, struct lmv_stripe_object *lso,
		       const char *name, int namelen, struct lu_fid *fid,
		       __u32 *mds, bool new_layout)
{
	struct lmv_tgt_desc *tgt;
	const struct lmv_oinfo *oinfo;

	if (!lmv_dir_striped(lso) || !namelen) {
		tgt = lmv_fid2tgt(lmv, fid);
		if (IS_ERR(tgt))
			return tgt;

		*mds = tgt->ltd_index;
		return tgt;
	}

	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_BAD_NAME_HASH)) {
		if (cfs_fail_val >= lso->lso_lsm.lsm_md_stripe_count)
			return ERR_PTR(-EBADF);
		oinfo = &lso->lso_lsm.lsm_md_oinfo[cfs_fail_val];
	} else {
		oinfo = lsm_name_to_stripe_info(lso, name, namelen, new_layout);
		if (IS_ERR(oinfo))
			return ERR_CAST(oinfo);
	}

	/* check stripe FID is sane */
	if (!fid_is_sane(&oinfo->lmo_fid))
		return ERR_PTR(-ENODEV);

	*fid = oinfo->lmo_fid;
	*mds = oinfo->lmo_mds;
	tgt = lmv_tgt_retry(lmv, oinfo->lmo_mds);

	CDEBUG(D_INODE, "locate MDT %u parent "DFID"\n", *mds, PFID(fid));

	return tgt ? tgt : ERR_PTR(-ENODEV);
}

/**
 * lmv_locate_tgt() - Locate MDT of op_data->op_fid1
 * @lmv: LMV device
 * @op_data: client MD stack parameters, name, namelen etc,
 * op_mds and op_fid1 will be updated if op_lso1 indicates
 * fid1 represents a striped directory.
 *
 * For striped directory, it will locate the stripe by name hash, if hash_type
 * is unknown, it will return the stripe specified by 'op_data->op_stripe_index'
 * which is set outside, and if dir is migrating, 'op_data->op_new_layout'
 * indicates whether old or new layout is used to locate.
 *
 * For plain directory, it just locate the MDT of op_data->op_fid1.
 *
 * Return:
 * * pointer to the lmv_tgt_desc if succeed. ERR_PTR(errno) if failed.
 */
struct lmv_tgt_desc *
lmv_locate_tgt(struct lmv_obd *lmv, struct md_op_data *op_data)
{
	struct lmv_stripe_md *lsm;
	struct lmv_oinfo *oinfo;
	struct lmv_tgt_desc *tgt;

	if (lmv_dir_foreign(op_data->op_lso1))
		return ERR_PTR(-ENODATA);

	/* During creating VOLATILE file, it should honor the mdt
	 * index if the file under striped dir is being restored, see
	 * ct_restore().
	 */
	if (op_data->op_bias & MDS_CREATE_VOLATILE &&
	    op_data->op_mds != LMV_OFFSET_DEFAULT) {
		tgt = lmv_tgt(lmv, op_data->op_mds);
		if (!tgt)
			return ERR_PTR(-ENODEV);

		if (lmv_dir_striped(op_data->op_lso1)) {
			int i;

			/* refill the right parent fid */
			lsm = &op_data->op_lso1->lso_lsm;
			for (i = 0; i < lsm->lsm_md_stripe_count; i++) {
				oinfo = &lsm->lsm_md_oinfo[i];
				if (oinfo->lmo_mds == op_data->op_mds) {
					op_data->op_fid1 = oinfo->lmo_fid;
					break;
				}
			}

			if (i == lsm->lsm_md_stripe_count)
				op_data->op_fid1 = lsm->lsm_md_oinfo[0].lmo_fid;
		}
	} else if (lmv_dir_bad_hash(op_data->op_lso1)) {
		lsm = &op_data->op_lso1->lso_lsm;

		LASSERT(op_data->op_stripe_index < lsm->lsm_md_stripe_count);
		oinfo = &lsm->lsm_md_oinfo[op_data->op_stripe_index];

		op_data->op_fid1 = oinfo->lmo_fid;
		op_data->op_mds = oinfo->lmo_mds;
		tgt = lmv_tgt(lmv, oinfo->lmo_mds);
		if (!tgt)
			return ERR_PTR(-ENODEV);
	} else {
		tgt = lmv_locate_tgt_by_name(lmv, op_data->op_lso1,
				op_data->op_name, op_data->op_namelen,
				&op_data->op_fid1, &op_data->op_mds,
				op_data->op_new_layout);
	}

	return tgt;
}

/* Locate MDT of op_data->op_fid2 for link/rename */
static struct lmv_tgt_desc *
lmv_locate_tgt2(struct lmv_obd *lmv, struct md_op_data *op_data)
{
	struct lmv_tgt_desc *tgt;
	int rc;

	LASSERT(op_data->op_name);
	if (lmv_dir_layout_changing(op_data->op_lso2)) {
		struct lu_fid fid1 = op_data->op_fid1;
		struct lmv_stripe_object *lso1 = op_data->op_lso1;
		struct ptlrpc_request *request = NULL;

		/*
		 * avoid creating new file under old layout of migrating
		 * directory, check it here.
		 */
		tgt = lmv_locate_tgt_by_name(lmv, op_data->op_lso2,
				op_data->op_name, op_data->op_namelen,
				&op_data->op_fid2, &op_data->op_mds, false);
		if (IS_ERR(tgt))
			RETURN(tgt);

		op_data->op_fid1 = op_data->op_fid2;
		op_data->op_lso1 = op_data->op_lso2;
		rc = md_getattr_name(tgt->ltd_exp, op_data, &request);
		op_data->op_fid1 = fid1;
		op_data->op_lso1 = lso1;
		if (!rc) {
			ptlrpc_req_put(request);
			RETURN(ERR_PTR(-EEXIST));
		}

		if (rc != -ENOENT)
			RETURN(ERR_PTR(rc));
	}

	return lmv_locate_tgt_by_name(lmv, op_data->op_lso2,
				      op_data->op_name, op_data->op_namelen,
				      &op_data->op_fid2, &op_data->op_mds,
				      true);
}

int lmv_old_layout_lookup(struct lmv_obd *lmv, struct md_op_data *op_data)
{
	struct lu_tgt_desc *tgt;
	struct ptlrpc_request *request;
	int rc;

	LASSERT(lmv_dir_layout_changing(op_data->op_lso1));
	LASSERT(!op_data->op_new_layout);

	tgt = lmv_locate_tgt(lmv, op_data);
	if (IS_ERR(tgt))
		return PTR_ERR(tgt);

	rc = md_getattr_name(tgt->ltd_exp, op_data, &request);
	if (!rc) {
		ptlrpc_req_put(request);
		return -EEXIST;
	}

	return rc;
}

/* mkdir by QoS upon 'lfs mkdir -i -1'.
 *
 * NB, mkdir by QoS only if parent is not striped, this is to avoid remote
 * directories under striped directory.
 */
static inline bool lmv_op_user_qos_mkdir(const struct md_op_data *op_data)
{
	const struct lmv_user_md *lum = op_data->op_data;

	if (op_data->op_code != LUSTRE_OPC_MKDIR)
		return false;

	if (lmv_dir_striped(op_data->op_lso1))
		return false;

	return (op_data->op_cli_flags & CLI_SET_MEA) && lum &&
	       le32_to_cpu(lum->lum_magic) == LMV_USER_MAGIC &&
	       le32_to_cpu(lum->lum_stripe_offset) == LMV_OFFSET_DEFAULT;
}

/* mkdir by QoS if either ROOT or parent default LMV is space balanced. */
static inline bool lmv_op_default_qos_mkdir(const struct md_op_data *op_data)
{
	const struct lmv_stripe_object *lso = op_data->op_default_lso1;

	if (op_data->op_code != LUSTRE_OPC_MKDIR)
		return false;

	if (lmv_dir_striped(op_data->op_lso1))
		return false;

	return (op_data->op_flags & MF_QOS_MKDIR) ||
	       (lso && lso->lso_lsm.lsm_md_master_mdt_index ==
		LMV_OFFSET_DEFAULT);
}

/* if parent default LMV is space balanced, and
 * 1. max_inherit_rr is set
 * 2. or parent is ROOT
 * mkdir roundrobin. Or if parent doesn't have default LMV, while ROOT default
 * LMV requests roundrobin mkdir, do the same.
 * NB, this needs to check server is balanced, which is done by caller.
 */
static inline bool lmv_op_default_rr_mkdir(const struct md_op_data *op_data)
{
	const struct lmv_stripe_object *lso = op_data->op_default_lso1;

	return (op_data->op_flags & MF_RR_MKDIR) ||
	       (lso && lso->lso_lsm.lsm_md_max_inherit_rr !=
		LMV_INHERIT_RR_NONE) || fid_is_root(&op_data->op_fid1);
}

/* 'lfs mkdir -i <specific_MDT>' */
static inline bool lmv_op_user_specific_mkdir(const struct md_op_data *op_data)
{
	const struct lmv_user_md *lum = op_data->op_data;

	return op_data->op_code == LUSTRE_OPC_MKDIR &&
	       op_data->op_cli_flags & CLI_SET_MEA && lum &&
	       (le32_to_cpu(lum->lum_magic) == LMV_USER_MAGIC ||
		le32_to_cpu(lum->lum_magic) == LMV_USER_MAGIC_SPECIFIC) &&
	       le32_to_cpu(lum->lum_stripe_offset) != LMV_OFFSET_DEFAULT;
}

/* parent default LMV master_mdt_index is not -1. */
static inline bool
lmv_op_default_specific_mkdir(const struct md_op_data *op_data)
{
	return op_data->op_code == LUSTRE_OPC_MKDIR &&
	       op_data->op_default_lso1 &&
	       op_data->op_default_lso1->lso_lsm.lsm_md_master_mdt_index !=
			LMV_OFFSET_DEFAULT;
}

/* locate MDT by space usage */
static struct lu_tgt_desc *lmv_locate_tgt_by_space(struct lmv_obd *lmv,
						   struct md_op_data *op_data,
						   struct lmv_tgt_desc *tgt)
{
	struct lmv_tgt_desc *tmp = tgt;

	tgt = lmv_locate_tgt_qos(lmv, op_data);
	if (tgt == ERR_PTR(-EAGAIN)) {
		if (ltd_qos_is_balanced(&lmv->lmv_mdt_descs) &&
		    !lmv_op_default_rr_mkdir(op_data) &&
		    !lmv_op_user_qos_mkdir(op_data) &&
		    !(tmp->ltd_statfs.os_state & OS_STATFS_NOCREATE)) {
			/* if not necessary, don't create remote directory. */
			tgt = tmp;
		} else {
			tgt = lmv_locate_tgt_rr(lmv);
			/* if no MDT chosen, use parent MDT */
			if (IS_ERR(tgt))
				tgt = tmp;
		}
		if (!IS_ERR(tgt))
			lmv_statfs_check_update(lmv2obd_dev(lmv), tgt);
	}

	if (!IS_ERR(tgt))
		op_data->op_mds = tgt->ltd_index;

	/* If space balance was called because the original target was marked
	 * NOCREATE, periodically check whether the state has changed.
	 */
	if (tmp != tgt && tmp->ltd_statfs.os_state & OS_STATFS_NOCREATE)
		lmv_statfs_check_update(lmv2obd_dev(lmv), tmp);

	return tgt;
}

/**
 * lmv_locate_tgt_next_avail() - Select next available MDT
 * @lmv: LMV device
 * @op_data: operation data containing user/group IDs
 * @cur: current MDT
 * @exclude_bitmap: bitmap of MDT indices to exclude from selection (already
 * tried)
 *
 *
 * Return:
 * Pointer to lu_tgt_desc of selected MDT, or error pointer.
 */
static
struct lu_tgt_desc *lmv_locate_tgt_next_avail(struct lmv_obd *lmv,
					      struct md_op_data *op_data,
					      struct lmv_tgt_desc *cur,
					      unsigned long *exclude_bitmap)
{
	__u32 count = lmv->lmv_mdt_count;
	__u32 start;
	__u32 i;

	if (count <= 1)
		return ERR_PTR(-EDQUOT);

	/* Make start random to spread creates across MDTs */
	start = (cur->ltd_index + 1 + get_random_u32_below(count - 1)) % count;

	for (i = 0; i < count ; i++) {
		__u32 idx = (start+i) % count;

		struct lu_tgt_desc *tgt;

		if (test_bit(idx, exclude_bitmap))
			continue;

		tgt = lmv_tgt_retry(lmv, idx);
		if (!tgt || !tgt->ltd_active)
			continue;
		if (tgt->ltd_statfs.os_state & OS_STATFS_NOCREATE)
			continue;

		op_data->op_mds = tgt->ltd_index;
		return tgt;
	}

	return ERR_PTR(-EDQUOT);
}

static bool lmv_tgt_nocreate(struct lmv_obd *lmv, struct lmv_tgt_desc *tgt)
{
	if (likely(!(tgt->ltd_statfs.os_state & OS_STATFS_NOCREATE)))
		return false;

	obd_statfs(NULL, tgt->ltd_exp, &tgt->ltd_statfs,
		   ktime_get_seconds() -
			lmv->lmv_mdt_descs.ltd_lmv_desc.ld_qos_maxage, 0);
	return tgt->ltd_statfs.os_state & OS_STATFS_NOCREATE;
}

static bool lmv_qos_exclude(struct lmv_obd *lmv, struct md_op_data *op_data)
{
	const char *name = op_data->op_name;
	struct qos_exclude_pattern *pat;

	/* skip encrypted files */
	if (op_data->op_file_encctx)
		return false;

	list_for_each_entry(pat, &lmv->lmv_qos_exclude_list, qep_list) {
		if (glob_match(pat->qep_name, name))
			return true;
	}

	return false;
}

struct lmv_tgt_desc *lmv_locate_tgt_create(struct obd_device *obd,
					   struct lmv_obd *lmv,
					   struct md_op_data *op_data)
{
	struct lmv_tgt_desc *tgt;

	ENTRY;

	tgt = lmv_locate_tgt(lmv, op_data);
	if (IS_ERR(tgt))
		RETURN(tgt);

	/* the order to apply policy in mkdir:
	 * 1. is "lfs mkdir -i N"? mkdir on MDT N.
	 * 2. is "lfs mkdir -i -1"? mkdir by space usage.
	 * 3. is starting MDT specified in default LMV? mkdir on MDT N.
	 * 4. is default LMV space balanced? mkdir by space usage.
	 *
	 * If the existing parent or specific MDT selected is deactivated
	 * with OS_STATFS_NOCREATE then select a different MDT by QOS.
	 */
	if (lmv_op_user_specific_mkdir(op_data)) {
		struct lmv_user_md *lum = op_data->op_data;

		op_data->op_mds = le32_to_cpu(lum->lum_stripe_offset);
		tgt = lmv_tgt(lmv, op_data->op_mds);
		if (!tgt)
			RETURN(ERR_PTR(-ENODEV));
		if (unlikely(lmv_tgt_nocreate(lmv, tgt)))
			GOTO(new_tgt, -EAGAIN);
	} else if (lmv_op_user_qos_mkdir(op_data)) {
		tgt = lmv_locate_tgt_by_space(lmv, op_data, tgt);
		if (IS_ERR(tgt))
			RETURN(tgt);
	} else if (lmv_op_default_specific_mkdir(op_data)) {
		struct lmv_stripe_md *lsm = &op_data->op_default_lso1->lso_lsm;

		op_data->op_mds = lsm->lsm_md_master_mdt_index;
		tgt = lmv_tgt(lmv, op_data->op_mds);
		if (!tgt)
			RETURN(ERR_PTR(-ENODEV));
		if (unlikely(lmv_tgt_nocreate(lmv, tgt)))
			GOTO(new_tgt, -EAGAIN);
	} else if ((lmv_op_default_qos_mkdir(op_data) &&
		    !lmv_qos_exclude(lmv, op_data)) ||
		   unlikely(lmv_tgt_nocreate(lmv, tgt))) {
new_tgt:
		tgt = lmv_locate_tgt_by_space(lmv, op_data, tgt);
		if (IS_ERR(tgt))
			RETURN(tgt);
	}

	RETURN(tgt);
}

static int lmv_create(struct obd_export *exp, struct md_op_data *op_data,
		      const void *data, size_t datalen, umode_t mode, uid_t uid,
		      gid_t gid, kernel_cap_t cap_effective, __u64 rdev,
		      struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	struct mdt_body *repbody;
	DECLARE_BITMAP(exclude_bitmap, LMV_MAX_TGT_COUNT);
	int quota_retry = 0;
	int rc;

	ENTRY;

	bitmap_zero(exclude_bitmap, LMV_MAX_TGT_COUNT);

	if (!lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count)
		RETURN(-EIO);

	if (lmv_dir_bad_hash(op_data->op_lso1))
		RETURN(-EBADF);

	if (lmv_dir_layout_changing(op_data->op_lso1)) {
		/*
		 * if parent is migrating, create() needs to lookup existing
		 * name in both old and new layout, check old layout on client.
		 */
		rc = lmv_old_layout_lookup(lmv, op_data);
		if (rc != -ENOENT)
			RETURN(rc);

		op_data->op_new_layout = true;
	}

	tgt = lmv_locate_tgt_create(obd, lmv, op_data);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

retry:
	rc = lmv_fid_alloc(NULL, exp, &op_data->op_fid2, op_data);
	if (rc)
		RETURN(rc);

	if (lmv_dir_striped(op_data->op_lso1))
		op_data->op_layout_version =
			op_data->op_lso1->lso_lsm.lsm_md_layout_version;
	else
		op_data->op_layout_version = 1;

	CDEBUG(D_INODE, "CREATE name '"DNAME"' "DFID" on "DFID
	       " -> mds #%x, layout version %u\n",
	       encode_fn_opdata(op_data), PFID(&op_data->op_fid2),
	       PFID(&op_data->op_fid1), op_data->op_mds,
	       op_data->op_layout_version);

	op_data->op_flags |= MF_MDC_CANCEL_FID1;
	rc = md_create(tgt->ltd_exp, op_data, data, datalen, mode, uid, gid,
		       cap_effective, rdev, request);
	if (rc == 0) {
		if (*request == NULL)
			RETURN(rc);
		CDEBUG(D_INODE, "Created - "DFID"\n", PFID(&op_data->op_fid2));
	}

	/* If we hit quota limit and this is a directory creation, try to
	 * find an alternative MDT with available quota space. Retry up to
	 * the number of active MDTs.
	 */
	if (rc == -EDQUOT && S_ISDIR(mode) &&
	    quota_retry < lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count &&
	    lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count > 1) {
		struct lmv_tgt_desc *new_tgt;

		CDEBUG(D_QUOTA,
		       "%s: mkdir hit EDQUOT on MDT%04x (retry %d/%u), searching for alternative MDT: rc = %d\n",
		       lmv2obd_dev(lmv)->obd_name, tgt->ltd_index, quota_retry,
		       lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count, rc);

		set_bit(tgt->ltd_index, exclude_bitmap);

		new_tgt = lmv_locate_tgt_next_avail(lmv, op_data, tgt,
						    exclude_bitmap);
		if (!IS_ERR(new_tgt) && new_tgt != tgt) {
			CDEBUG(D_INFO,
			       "\nretrying mkdir on MDT%04x (was MDT%04x)\n",
			       new_tgt->ltd_index, tgt->ltd_index);

			if (*request != NULL) {
				ptlrpc_req_put(*request);
				*request = NULL;
			}

			tgt = new_tgt;
			quota_retry++;
			/* Reset FID2 for retry, and allocate a new FID */
			fid_zero(&op_data->op_fid2);
			rc = lmv_fid_alloc(NULL, exp, &op_data->op_fid2, op_data);
			if (rc)
				RETURN(rc);
			goto retry;
		}

		CDEBUG(D_QUOTA,
		       "Failed to find alternative MDT: new_tgt=%p, tgt=%p, is_err=%d\n",
		       new_tgt, tgt, IS_ERR(new_tgt));
	} else {
		if (rc == -EDQUOT) {
			CDEBUG(D_QUOTA,
			       "EDQUOT but conditions not met: S_ISDIR=%d, retry_count=%d/%u, mdt_count=%u\n",
			       S_ISDIR(mode), quota_retry,
			       lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count,
			       lmv->lmv_mdt_descs.ltd_lmv_desc.ld_active_tgt_count);
		}
	}

	/* dir restripe needs to send to MDT where dir is located */
	if (rc != -EREMOTE ||
	    !(exp_connect_flags2(exp) & OBD_CONNECT2_CRUSH))
		RETURN(rc);

	repbody = req_capsule_server_get(&(*request)->rq_pill, &RMF_MDT_BODY);
	if (repbody == NULL)
		RETURN(-EPROTO);

	/* Not cross-ref case, just get out of here. */
	if (likely(!(repbody->mbo_valid & OBD_MD_MDS)))
		RETURN(rc);

	op_data->op_fid2 = repbody->mbo_fid1;
	ptlrpc_req_put(*request);
	*request = NULL;

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid2);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	op_data->op_mds = tgt->ltd_index;
	goto retry;
}

static int
lmv_enqueue(struct obd_export *exp, struct ldlm_enqueue_info *einfo,
	    const union ldlm_policy_data *policy, struct md_op_data *op_data,
	    struct lustre_handle *lockh, __u64 extra_lock_flags)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	CDEBUG(D_INODE, "ENQUEUE on "DFID"\n", PFID(&op_data->op_fid1));

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	CDEBUG(D_INODE, "ENQUEUE on "DFID" -> mds #%u\n",
	       PFID(&op_data->op_fid1), tgt->ltd_index);

	rc = md_enqueue(tgt->ltd_exp, einfo, policy, op_data, lockh,
			extra_lock_flags);

	RETURN(rc);
}

static int
lmv_enqueue_async(struct obd_export *exp, struct ldlm_enqueue_info *einfo,
		  obd_enqueue_update_f upcall, struct md_op_data *op_data,
		  const union ldlm_policy_data *policy, __u64 flags)
{
	struct obd_device        *obd = exp->exp_obd;
	struct lmv_obd           *lmv = &obd->u.lmv;
	struct lmv_tgt_desc      *tgt;
	int                       rc;

	ENTRY;

	CDEBUG(D_INODE, "ENQUEUE ASYNC on "DFID"\n",
			PFID(&op_data->op_fid1));

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	CDEBUG(D_INODE, "ENQUEUE ASYNC on "DFID" -> mds #%d\n",
	       PFID(&op_data->op_fid1), tgt->ltd_index);

	rc = md_enqueue_async(tgt->ltd_exp, einfo, upcall, op_data, policy,
			      flags);

	RETURN(rc);
}

int
lmv_getattr_name(struct obd_export *exp, struct md_op_data *op_data,
		 struct ptlrpc_request **preq)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	struct mdt_body *body;
	int rc;

	ENTRY;

retry:
	if (op_data->op_namelen == 2 &&
	    op_data->op_name[0] == '.' && op_data->op_name[1] == '.')
		tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	else
		tgt = lmv_locate_tgt(lmv, op_data);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	CDEBUG(D_INODE, "GETATTR_NAME for %*s on "DFID" -> mds #%d\n",
		(int)op_data->op_namelen, op_data->op_name,
		PFID(&op_data->op_fid1), tgt->ltd_index);

	rc = md_getattr_name(tgt->ltd_exp, op_data, preq);
	if (rc == -ENOENT && lmv_dir_retry_check_update(op_data)) {
		ptlrpc_req_put(*preq);
		*preq = NULL;
		goto retry;
	}

	if (rc)
		RETURN(rc);

	body = req_capsule_server_get(&(*preq)->rq_pill, &RMF_MDT_BODY);
	LASSERT(body != NULL);

	if (body->mbo_valid & OBD_MD_MDS) {
		op_data->op_fid1 = body->mbo_fid1;
		op_data->op_valid |= OBD_MD_FLCROSSREF;
		op_data->op_namelen = 0;
		op_data->op_name = NULL;

		ptlrpc_req_put(*preq);
		*preq = NULL;

		goto retry;
	}

	RETURN(rc);
}

#define md_op_data_fid(op_data, fl)                     \
	(fl == MF_MDC_CANCEL_FID1 ? &op_data->op_fid1 : \
	 fl == MF_MDC_CANCEL_FID2 ? &op_data->op_fid2 : \
	 fl == MF_MDC_CANCEL_FID3 ? &op_data->op_fid3 : \
	 fl == MF_MDC_CANCEL_FID4 ? &op_data->op_fid4 : \
	 NULL)

static int lmv_early_cancel(struct obd_export *exp, struct lmv_tgt_desc *tgt,
			    struct md_op_data *op_data, __u32 op_tgt,
			    enum ldlm_mode mode, enum mds_ibits_locks bits,
			    int flag)
{
	struct lu_fid *fid = md_op_data_fid(op_data, flag);
	struct lmv_obd *lmv = &exp->exp_obd->u.lmv;
	union ldlm_policy_data policy = { { 0 } };
	int rc = 0;

	ENTRY;

	if (!fid_is_sane(fid))
		RETURN(0);

	if (tgt == NULL) {
		tgt = lmv_fid2tgt(lmv, fid);
		if (IS_ERR(tgt))
			RETURN(PTR_ERR(tgt));
	}

	if (tgt->ltd_index != op_tgt) {
		CDEBUG(D_INODE, "EARLY_CANCEL on "DFID"\n", PFID(fid));
		policy.l_inodebits.bits = bits;
		rc = md_cancel_unused(tgt->ltd_exp, fid, &policy,
				      mode, LCF_ASYNC, NULL);
	} else {
		CDEBUG(D_INODE,
		       "EARLY_CANCEL skip operation target %d on "DFID"\n",
		       op_tgt, PFID(fid));
		op_data->op_flags |= flag;
		rc = 0;
	}

	RETURN(rc);
}

/*
 * llite passes fid of an target inode in op_data->op_fid1 and id of directory
 * in op_data->op_fid2
 */
static int lmv_link(struct obd_export *exp, struct md_op_data *op_data,
		    struct ptlrpc_request **request)
{
	struct obd_device       *obd = exp->exp_obd;
	struct lmv_obd          *lmv = &obd->u.lmv;
	struct lmv_tgt_desc     *tgt;
	int                      rc;

	ENTRY;

	LASSERT(op_data->op_namelen != 0);

	CDEBUG(D_INODE, "LINK "DFID":%*s to "DFID"\n",
	       PFID(&op_data->op_fid2), (int)op_data->op_namelen,
	       op_data->op_name, PFID(&op_data->op_fid1));

	op_data->op_fsuid = from_kuid(&init_user_ns, current_fsuid());
	op_data->op_fsgid = from_kgid(&init_user_ns, current_fsgid());
	op_data->op_cap = current_cap();

	tgt = lmv_locate_tgt2(lmv, op_data);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	/* Cancel UPDATE lock on child (fid1).  */
	op_data->op_flags |= MF_MDC_CANCEL_FID2;
	rc = lmv_early_cancel(exp, NULL, op_data, tgt->ltd_index, LCK_EX,
			      MDS_INODELOCK_UPDATE, MF_MDC_CANCEL_FID1);
	if (rc != 0)
		RETURN(rc);

	rc = md_link(tgt->ltd_exp, op_data, request);

	RETURN(rc);
}

/* migrate the top directory */
static inline bool lmv_op_topdir_migrate(const struct md_op_data *op_data)
{
	if (!S_ISDIR(op_data->op_mode))
		return false;

	if (lmv_dir_layout_changing(op_data->op_lso1))
		return false;

	return true;
}

/* migrate top dir to specific MDTs */
static inline bool lmv_topdir_specific_migrate(const struct md_op_data *op_data)
{
	const struct lmv_user_md *lum = op_data->op_data;

	if (!lmv_op_topdir_migrate(op_data))
		return false;

	return le32_to_cpu(lum->lum_stripe_offset) != LMV_OFFSET_DEFAULT;
}

/* migrate top dir in QoS mode if user issued "lfs migrate -m -1..." */
static inline bool lmv_topdir_qos_migrate(const struct md_op_data *op_data)
{
	const struct lmv_user_md *lum = op_data->op_data;

	if (!lmv_op_topdir_migrate(op_data))
		return false;

	return le32_to_cpu(lum->lum_stripe_offset) == LMV_OFFSET_DEFAULT;
}

static inline bool lmv_subdir_specific_migrate(const struct md_op_data *op_data)
{
	const struct lmv_user_md *lum = op_data->op_data;

	if (!S_ISDIR(op_data->op_mode))
		return false;

	if (!lmv_dir_layout_changing(op_data->op_lso1))
		return false;

	return le32_to_cpu(lum->lum_stripe_offset) != LMV_OFFSET_DEFAULT;
}

static int lmv_migrate(struct obd_export *exp, struct md_op_data *op_data,
			const char *name, size_t namelen,
			struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_stripe_object *lso = op_data->op_lso1;
	struct lmv_tgt_desc *parent_tgt;
	struct lmv_tgt_desc *sp_tgt;
	struct lmv_tgt_desc *tp_tgt = NULL;
	struct lmv_tgt_desc *child_tgt;
	struct lmv_tgt_desc *tgt;
	struct lu_fid target_fid = { 0 };
	int rc;

	ENTRY;

	LASSERT(op_data->op_cli_flags & CLI_MIGRATE);

	CDEBUG(D_INODE, "MIGRATE "DFID"/"DNAME"\n",
	       PFID(&op_data->op_fid1), encode_fn_dname(namelen, name));

	op_data->op_fsuid = from_kuid(&init_user_ns, current_fsuid());
	op_data->op_fsgid = from_kgid(&init_user_ns, current_fsgid());
	op_data->op_cap = current_cap();

	parent_tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	if (IS_ERR(parent_tgt))
		RETURN(PTR_ERR(parent_tgt));

	if (lmv_dir_striped(lso)) {
		const struct lmv_oinfo *oinfo;

		oinfo = lsm_name_to_stripe_info(lso, name, namelen, false);
		if (IS_ERR(oinfo))
			RETURN(PTR_ERR(oinfo));

		/* save source stripe FID in fid4 temporarily for ELC */
		op_data->op_fid4 = oinfo->lmo_fid;
		sp_tgt = lmv_tgt_retry(lmv, oinfo->lmo_mds);
		if (!sp_tgt)
			RETURN(-ENODEV);

		/*
		 * if parent is being migrated too, fill op_fid2 with target
		 * stripe fid, otherwise the target stripe is not created yet.
		 */
		if (lmv_dir_layout_changing(lso)) {
			oinfo = lsm_name_to_stripe_info(lso, name, namelen,
							true);
			if (IS_ERR(oinfo))
				RETURN(PTR_ERR(oinfo));

			op_data->op_fid2 = oinfo->lmo_fid;
			tp_tgt = lmv_tgt_retry(lmv, oinfo->lmo_mds);
			if (!tp_tgt)
				RETURN(-ENODEV);

			/* parent unchanged and update namespace only */
			if (lu_fid_eq(&op_data->op_fid4, &op_data->op_fid2) &&
			    op_data->op_bias & MDS_MIGRATE_NSONLY)
				RETURN(-EALREADY);
		}
	} else {
		sp_tgt = parent_tgt;
	}

	child_tgt = lmv_fid2tgt(lmv, &op_data->op_fid3);
	if (IS_ERR(child_tgt))
		RETURN(PTR_ERR(child_tgt));

	if (lmv_topdir_specific_migrate(op_data)) {
		struct lmv_user_md *lum = op_data->op_data;

		op_data->op_mds = le32_to_cpu(lum->lum_stripe_offset);
	} else if (lmv_topdir_qos_migrate(op_data)) {
		tgt = lmv_locate_tgt_lf(lmv);
		if (tgt == ERR_PTR(-EAGAIN))
			tgt = lmv_locate_tgt_rr(lmv);
		if (IS_ERR(tgt))
			RETURN(PTR_ERR(tgt));

		op_data->op_mds = tgt->ltd_index;
	} else if (lmv_subdir_specific_migrate(op_data)) {
		struct lmv_user_md *lum = op_data->op_data;
		__u32 i;

		LASSERT(tp_tgt);
		if (le32_to_cpu(lum->lum_magic) == LMV_USER_MAGIC_SPECIFIC) {
			/* adjust MDTs in lum, since subdir is located on where
			 * its parent stripe is, not the first specified MDT.
			 */
			for (i = 0; i < le32_to_cpu(lum->lum_stripe_count);
			     i++) {
				if (le32_to_cpu(lum->lum_objects[i].lum_mds) ==
				    tp_tgt->ltd_index)
					break;
			}

			if (i == le32_to_cpu(lum->lum_stripe_count))
				RETURN(-ENODEV);

			lum->lum_objects[i].lum_mds =
				lum->lum_objects[0].lum_mds;
			lum->lum_objects[0].lum_mds =
				cpu_to_le32(tp_tgt->ltd_index);
		}
		/* NB, the above adjusts subdir migration for command like
		 * "lfs migrate -m 0,1,2 ...", but for migration like
		 * "lfs migrate -m 0 -c 2 ...", the top dir is migrated to MDT0
		 * and MDT1, however its subdir may be migrated to MDT1 and MDT2
		 */

		lum->lum_stripe_offset = cpu_to_le32(tp_tgt->ltd_index);
		op_data->op_mds = tp_tgt->ltd_index;
	} else if (tp_tgt) {
		op_data->op_mds = tp_tgt->ltd_index;
	} else {
		op_data->op_mds = sp_tgt->ltd_index;
	}

	rc = lmv_fid_alloc(NULL, exp, &target_fid, op_data);
	if (rc)
		RETURN(rc);

	/*
	 * for directory, send migrate request to the MDT where the object will
	 * be migrated to, because we can't create a striped directory remotely.
	 *
	 * otherwise, send to the MDT where source is located because regular
	 * file may open lease.
	 *
	 * NB. if MDT doesn't support DIR_MIGRATE, send to source MDT too for
	 * backward compatibility.
	 */
	if (S_ISDIR(op_data->op_mode) &&
	    (exp_connect_flags2(exp) & OBD_CONNECT2_DIR_MIGRATE)) {
		tgt = lmv_fid2tgt(lmv, &target_fid);
		if (IS_ERR(tgt))
			RETURN(PTR_ERR(tgt));
	} else {
		tgt = child_tgt;
	}

	/* cancel UPDATE lock of parent master object */
	rc = lmv_early_cancel(exp, parent_tgt, op_data, tgt->ltd_index, LCK_EX,
			      MDS_INODELOCK_UPDATE, MF_MDC_CANCEL_FID1);
	if (rc)
		RETURN(rc);

	/* cancel UPDATE lock of source parent */
	if (sp_tgt != parent_tgt) {
		/*
		 * migrate RPC packs master object FID, because we can only pack
		 * two FIDs in reint RPC, but MDS needs to know both source
		 * parent and target parent, and it will obtain them from master
		 * FID and LMV, the other FID in RPC is kept for target.
		 *
		 * since this FID is not passed to MDC, cancel it anyway.
		 */
		rc = lmv_early_cancel(exp, sp_tgt, op_data, -1, LCK_EX,
				      MDS_INODELOCK_UPDATE, MF_MDC_CANCEL_FID4);
		if (rc)
			RETURN(rc);

		op_data->op_flags &= ~MF_MDC_CANCEL_FID4;
	}
	op_data->op_fid4 = target_fid;

	/* cancel UPDATE locks of target parent */
	rc = lmv_early_cancel(exp, tp_tgt, op_data, tgt->ltd_index, LCK_EX,
			      MDS_INODELOCK_UPDATE, MF_MDC_CANCEL_FID2);
	if (rc)
		RETURN(rc);

	/* cancel LOOKUP lock of source if source is remote object */
	if (child_tgt != sp_tgt) {
		rc = lmv_early_cancel(exp, sp_tgt, op_data, tgt->ltd_index,
				      LCK_EX, MDS_INODELOCK_LOOKUP,
				      MF_MDC_CANCEL_FID3);
		if (rc)
			RETURN(rc);
	}

	/* cancel ELC locks of source */
	rc = lmv_early_cancel(exp, child_tgt, op_data, tgt->ltd_index, LCK_EX,
			      MDS_INODELOCK_ELC, MF_MDC_CANCEL_FID3);
	if (rc)
		RETURN(rc);

	rc = md_rename(tgt->ltd_exp, op_data, name, namelen, NULL, 0, request);

	RETURN(rc);
}

static int lmv_rename(struct obd_export *exp, struct md_op_data *op_data,
		      const char *old, size_t oldlen,
		      const char *new, size_t newlen,
		      struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *sp_tgt;
	struct lmv_tgt_desc *tp_tgt = NULL;
	struct lmv_tgt_desc *src_tgt = NULL;
	struct lmv_tgt_desc *tgt;
	struct mdt_body *body;
	int rc;

	ENTRY;

	LASSERT(oldlen != 0);

	if (op_data->op_cli_flags & CLI_MIGRATE) {
		rc = lmv_migrate(exp, op_data, old, oldlen, request);
		RETURN(rc);
	}

	op_data->op_fsuid = from_kuid(&init_user_ns, current_fsuid());
	op_data->op_fsgid = from_kgid(&init_user_ns, current_fsgid());
	op_data->op_cap = current_cap();

	op_data->op_name = new;
	op_data->op_namelen = newlen;

	tp_tgt = lmv_locate_tgt2(lmv, op_data);
	if (IS_ERR(tp_tgt))
		RETURN(PTR_ERR(tp_tgt));

	/* Since the target child might be destroyed, and it might become
	 * orphan, and we can only check orphan on the local MDT right now, so
	 * we send rename request to the MDT where target child is located. If
	 * target child does not exist, then it will send the request to the
	 * target parent
	 */
	if (fid_is_sane(&op_data->op_fid4)) {
		tgt = lmv_fid2tgt(lmv, &op_data->op_fid4);
		if (IS_ERR(tgt))
			RETURN(PTR_ERR(tgt));
	} else {
		tgt = tp_tgt;
	}

	op_data->op_flags |= MF_MDC_CANCEL_FID4;

	/* cancel UPDATE locks of target parent */
	rc = lmv_early_cancel(exp, tp_tgt, op_data, tgt->ltd_index, LCK_EX,
			      MDS_INODELOCK_UPDATE, MF_MDC_CANCEL_FID2);
	if (rc != 0)
		RETURN(rc);

	if (fid_is_sane(&op_data->op_fid4)) {
		/* cancel LOOKUP lock of target on target parent */
		if (tgt != tp_tgt) {
			rc = lmv_early_cancel(exp, tp_tgt, op_data,
					      tgt->ltd_index, LCK_EX,
					      MDS_INODELOCK_LOOKUP,
					      MF_MDC_CANCEL_FID4);
			if (rc != 0)
				RETURN(rc);
		}
	}

	if (fid_is_sane(&op_data->op_fid3)) {
		src_tgt = lmv_fid2tgt(lmv, &op_data->op_fid3);
		if (IS_ERR(src_tgt))
			RETURN(PTR_ERR(src_tgt));

		/* cancel ELC locks of source */
		rc = lmv_early_cancel(exp, src_tgt, op_data, tgt->ltd_index,
				      LCK_EX, MDS_INODELOCK_ELC,
				      MF_MDC_CANCEL_FID3);
		if (rc != 0)
			RETURN(rc);
	}

	op_data->op_name = old;
	op_data->op_namelen = oldlen;
retry:
	sp_tgt = lmv_locate_tgt(lmv, op_data);
	if (IS_ERR(sp_tgt))
		RETURN(PTR_ERR(sp_tgt));

	/* cancel UPDATE locks of source parent */
	rc = lmv_early_cancel(exp, sp_tgt, op_data, tgt->ltd_index, LCK_EX,
			      MDS_INODELOCK_UPDATE, MF_MDC_CANCEL_FID1);
	if (rc != 0)
		RETURN(rc);

	if (fid_is_sane(&op_data->op_fid3)) {
		/* cancel LOOKUP lock of source on source parent */
		if (src_tgt != sp_tgt) {
			rc = lmv_early_cancel(exp, sp_tgt, op_data,
					      tgt->ltd_index, LCK_EX,
					      MDS_INODELOCK_LOOKUP,
					      MF_MDC_CANCEL_FID3);
			if (rc != 0)
				RETURN(rc);
		}
	}

rename:
	CDEBUG(D_INODE, "RENAME "DFID"/"DNAME" to "DFID"/"DNAME"\n",
		PFID(&op_data->op_fid1), encode_fn_dname(oldlen, old),
		PFID(&op_data->op_fid2), encode_fn_dname(newlen, new));

	rc = md_rename(tgt->ltd_exp, op_data, old, oldlen, new, newlen,
			request);
	if (rc == -ENOENT && lmv_dir_retry_check_update(op_data)) {
		ptlrpc_req_put(*request);
		*request = NULL;
		goto retry;
	}

	if (rc && rc != -EXDEV)
		RETURN(rc);

	body = req_capsule_server_get(&(*request)->rq_pill, &RMF_MDT_BODY);
	if (body == NULL)
		RETURN(-EPROTO);

	/* Not cross-ref case, just get out of here. */
	if (likely(!(body->mbo_valid & OBD_MD_MDS)))
		RETURN(rc);

	op_data->op_fid4 = body->mbo_fid1;

	ptlrpc_req_put(*request);
	*request = NULL;

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid4);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	if (fid_is_sane(&op_data->op_fid4)) {
		/* cancel LOOKUP lock of target on target parent */
		if (tgt != tp_tgt) {
			rc = lmv_early_cancel(exp, tp_tgt, op_data,
					      tgt->ltd_index, LCK_EX,
					      MDS_INODELOCK_LOOKUP,
					      MF_MDC_CANCEL_FID4);
			if (rc != 0)
				RETURN(rc);
		}
	}

	goto rename;
}

static int lmv_setattr(struct obd_export *exp, struct md_op_data *op_data,
		       void *ea, size_t ealen, struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc = 0;

	ENTRY;

	CDEBUG(D_INODE, "SETATTR for "DFID", valid 0x%x/0x%x\n",
	       PFID(&op_data->op_fid1), op_data->op_attr.ia_valid,
	       op_data->op_xvalid);

	op_data->op_flags |= MF_MDC_CANCEL_FID1;
	tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	rc = md_setattr(tgt->ltd_exp, op_data, ea, ealen, request);

	RETURN(rc);
}

static int lmv_fsync(struct obd_export *exp, const struct lu_fid *fid,
		     struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, fid);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	rc = md_fsync(tgt->ltd_exp, fid, request);
	RETURN(rc);
}

struct stripe_dirent {
	struct folio		*sd_folio;
	struct lu_dirpage	*sd_dp;
	struct lu_dirent	*sd_ent;
	bool			 sd_eof;
};

struct lmv_dir_ctxt {
	struct lmv_obd		*ldc_lmv;
	struct md_op_data	*ldc_op_data;
	struct md_readdir_info  *ldc_mrinfo;
	__u64			 ldc_hash;
	int			 ldc_count;
	struct stripe_dirent	 ldc_stripes[];
};

static inline void stripe_dirent_unload(struct stripe_dirent *stripe)
{
	if (stripe->sd_folio) {
		if (stripe->sd_dp) {
			ll_kunmap_local(stripe->sd_dp);
			stripe->sd_dp = NULL;
		}
		folio_put(stripe->sd_folio);
		stripe->sd_folio = NULL;
		stripe->sd_ent = NULL;
	}
}

static inline void put_lmv_dir_ctxt(struct lmv_dir_ctxt *ctxt)
{
	int i;

	for (i = 0; i < ctxt->ldc_count; i++)
		stripe_dirent_unload(&ctxt->ldc_stripes[i]);
}

/* if @ent is dummy, or . .., get next */
static struct lu_dirent *stripe_dirent_get(struct lmv_dir_ctxt *ctxt,
					   struct lu_dirent *ent,
					   int stripe_index)
{
	for (; ent; ent = lu_dirent_next(ent)) {
		/* Skip dummy entry */
		if (le16_to_cpu(ent->lde_namelen) == 0)
			continue;

		/* skip . and .. for other stripes */
		if (stripe_index &&
		    (strncmp(ent->lde_name, ".",
			     le16_to_cpu(ent->lde_namelen)) == 0 ||
		     strncmp(ent->lde_name, "..",
			     le16_to_cpu(ent->lde_namelen)) == 0))
			continue;

		if (le64_to_cpu(ent->lde_hash) >= ctxt->ldc_hash)
			break;
	}

	return ent;
}

static struct lu_dirent *stripe_dirent_load(struct lmv_dir_ctxt *ctxt,
					    struct stripe_dirent *stripe,
					    int stripe_index)
{
	struct md_op_data *op_data = ctxt->ldc_op_data;
	struct lmv_oinfo *oinfo;
	struct lu_fid fid = op_data->op_fid1;
	struct inode *inode = op_data->op_data;
	struct lmv_tgt_desc *tgt;
	struct lu_dirent *ent = stripe->sd_ent;
	__u64 hash = ctxt->ldc_hash;
	int rc = 0;

	ENTRY;

	LASSERT(stripe == &ctxt->ldc_stripes[stripe_index]);
	LASSERT(!ent);

	do {
		if (stripe->sd_folio && stripe->sd_dp) {
			__u64 end = le64_to_cpu(stripe->sd_dp->ldp_hash_end);

			/* @hash should be the last dirent hash */
			LASSERTF(hash <= end,
				 "ctxt@%px stripe@%px hash %llx end %llx\n",
				 ctxt, stripe, hash, end);
			/* unload last page */
			stripe_dirent_unload(stripe);
			/* eof */
			if (end == MDS_DIR_END_OFF) {
				stripe->sd_eof = true;
				break;
			}
			hash = end;
		}

		oinfo = &op_data->op_lso1->lso_lsm.lsm_md_oinfo[stripe_index];
		if (!oinfo->lmo_root) {
			rc = -ENOENT;
			break;
		}

		tgt = lmv_tgt_retry(ctxt->ldc_lmv, oinfo->lmo_mds);
		if (!tgt) {
			rc = -ENODEV;
			break;
		}

		/* op_data is shared by stripes, reset after use */
		op_data->op_fid1 = oinfo->lmo_fid;
		op_data->op_fid2 = oinfo->lmo_fid;
		op_data->op_data = oinfo->lmo_root;

		stripe->sd_dp = NULL;
		rc = md_read_page(tgt->ltd_exp, op_data, ctxt->ldc_mrinfo, hash,
				  &stripe->sd_folio);

		op_data->op_fid1 = fid;
		op_data->op_fid2 = fid;
		op_data->op_data = inode;

		if (rc)
			break;

		stripe->sd_dp = ll_kmap_local_folio(stripe->sd_folio, 0);
		ent = stripe_dirent_get(ctxt, lu_dirent_start(stripe->sd_dp),
					stripe_index);
		/* in case a page filled with ., .. and dummy, read next */
	} while (!ent);

	stripe->sd_ent = ent;
	if (rc) {
		LASSERT(!ent);
		/* treat error as eof, so dir can be partially accessed */
		stripe->sd_eof = true;
		ctxt->ldc_mrinfo->mr_partial_readdir_rc = rc;
		LCONSOLE_WARN("dir "DFID" stripe %d readdir failed: %d, directory is partially accessed!\n",
			      PFID(&ctxt->ldc_op_data->op_fid1), stripe_index,
			      rc);
	}

	RETURN(ent);
}

static int lmv_file_resync(struct obd_export *exp, struct md_op_data *data)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	rc = lmv_check_connect(obd);
	if (rc != 0)
		RETURN(rc);

	tgt = lmv_fid2tgt(lmv, &data->op_fid1);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	data->op_flags |= MF_MDC_CANCEL_FID1;
	rc = md_file_resync(tgt->ltd_exp, data);
	RETURN(rc);
}

/**
 * lmv_dirent_next() - Get dirent with the closest hash for striped directory
 * @ctxt: dir read context
 *
 * This function will search the dir entry, whose hash value is the
 * closest(>=) to hash from all of sub-stripes, and it is only being called
 * for striped directory.
 *
 *
 * * Returns dirent if retrieval of the entry is successfully. NULL does not
 * get the entry, normally it means it reaches the end of the directory,
 * while read stripe dirent error is ignored to allow partial access.
 */
static struct lu_dirent *lmv_dirent_next(struct lmv_dir_ctxt *ctxt)
{
	struct stripe_dirent *stripe;
	struct lu_dirent *ent = NULL;
	int i;
	int min = -1;

	/* TODO: optimize with k-way merge sort */
	for (i = 0; i < ctxt->ldc_count; i++) {
		stripe = &ctxt->ldc_stripes[i];
		if (stripe->sd_eof)
			continue;

		if (!stripe->sd_ent) {
			stripe_dirent_load(ctxt, stripe, i);
			if (!stripe->sd_ent) {
				LASSERT(stripe->sd_eof);
				continue;
			}
		}

		if (min == -1 ||
		    le64_to_cpu(ctxt->ldc_stripes[min].sd_ent->lde_hash) >
		    le64_to_cpu(stripe->sd_ent->lde_hash)) {
			min = i;
			if (le64_to_cpu(stripe->sd_ent->lde_hash) ==
			    ctxt->ldc_hash)
				break;
		}
	}

	if (min != -1) {
		stripe = &ctxt->ldc_stripes[min];
		ent = stripe->sd_ent;
		/* pop found dirent */
		stripe->sd_ent = stripe_dirent_get(ctxt, lu_dirent_next(ent),
						   min);
	}

	return ent;
}

/**
 * lmv_striped_read_page() - Build dir entry page for striped directory
 * @exp: obd export refer to LMV
 * @op_data: hold those MD parameters of read_entry
 * @mrinfo: ldlm callback being used in enqueue in mdc_read_entry, and partial
 * readdir result will be stored in it.
 * @offset: starting hash offset
 * @ppage: the page holding the entry. Note: because the entry will be
 * accessed in upper layer, so we need hold the page until the usages of entry
 * is finished, see ll_dir_entry_next.
 *
 * This function gets one entry by @offset from a striped directory. It will
 * read entries from all of stripes, and choose one closest to the required
 * offset(&offset). A few notes
 * 1. skip . and .. for non-zero stripes, because there can only have one .
 * and .. in a directory.
 * 2. op_data will be shared by all of stripes, instead of allocating new
 * one, so need to restore before reusing.
 *
 * * Return:
 * * %>=0: get entry successfully
 * * %<0: Cannot get entry
 */
static int lmv_striped_read_page(struct obd_export *exp,
				 struct md_op_data *op_data,
				 struct md_readdir_info *mrinfo, __u64 offset,
				 struct folio **pfolio)
{
	struct folio *folio = NULL;
	struct lu_dirpage *dp;
	void *start;
	struct lu_dirent *ent;
	struct lu_dirent *last_ent;
	int stripe_count;
	struct lmv_dir_ctxt *ctxt;
	struct lu_dirent *next = NULL;
	__u16 ent_size;
	size_t left_bytes;
	int rc = 0;

	ENTRY;

	/* Allocate a page and read entries from all of stripes and fill
	 * the page by hash order
	 */
	folio = folio_alloc(GFP_KERNEL, 0);
	if (IS_ERR_OR_NULL(folio))
		RETURN(-ENOMEM);

	/* Initialize the entry page */
	dp = ll_kmap_local_folio(folio, 0);
	memset(dp, 0, sizeof(*dp));
	dp->ldp_hash_start = cpu_to_le64(offset);

	start = dp + 1;
	left_bytes = PAGE_SIZE - sizeof(*dp);
	ent = start;
	last_ent = ent;

	/* initalize dir read context */
	stripe_count = op_data->op_lso1->lso_lsm.lsm_md_stripe_count;
	OBD_ALLOC(ctxt, offsetof(typeof(*ctxt), ldc_stripes[stripe_count]));
	if (!ctxt)
		GOTO(free_page, rc = -ENOMEM);
	ctxt->ldc_lmv = &exp->exp_obd->u.lmv;
	ctxt->ldc_op_data = op_data;
	ctxt->ldc_mrinfo = mrinfo;
	ctxt->ldc_hash = offset;
	ctxt->ldc_count = stripe_count;

	while (1) {
		next = lmv_dirent_next(ctxt);

		/* end of directory */
		if (!next) {
			ctxt->ldc_hash = MDS_DIR_END_OFF;
			break;
		}
		ctxt->ldc_hash = le64_to_cpu(next->lde_hash);

		ent_size = le16_to_cpu(next->lde_reclen);

		/* the last entry lde_reclen is 0, but it might not be the last
		 * one of this temporay dir page
		 */
		if (!ent_size)
			ent_size = lu_dirent_calc_size(
					le16_to_cpu(next->lde_namelen),
					le32_to_cpu(next->lde_attrs));
		/* page full */
		if (ent_size > left_bytes)
			break;

		memcpy(ent, next, ent_size);

		/* Replace . with master FID and Replace .. with the parent FID
		 * of master object
		 */
		if (strncmp(ent->lde_name, ".",
			    le16_to_cpu(ent->lde_namelen)) == 0 &&
		    le16_to_cpu(ent->lde_namelen) == 1)
			fid_cpu_to_le(&ent->lde_fid, &op_data->op_fid1);
		else if (strncmp(ent->lde_name, "..",
				   le16_to_cpu(ent->lde_namelen)) == 0 &&
			   le16_to_cpu(ent->lde_namelen) == 2)
			fid_cpu_to_le(&ent->lde_fid, &op_data->op_fid3);

		CDEBUG(D_INODE, "entry %.*s hash %#llx\n",
		       le16_to_cpu(ent->lde_namelen), ent->lde_name,
		       le64_to_cpu(ent->lde_hash));

		left_bytes -= ent_size;
		ent->lde_reclen = cpu_to_le16(ent_size);
		last_ent = ent;
		ent = (void *)ent + ent_size;
	};

	last_ent->lde_reclen = 0;

	if (ent == start)
		dp->ldp_flags |= LDF_EMPTY;
	else if (ctxt->ldc_hash == le64_to_cpu(last_ent->lde_hash))
		dp->ldp_flags |= LDF_COLLIDE;
	dp->ldp_flags = cpu_to_le32(dp->ldp_flags);
	dp->ldp_hash_end = cpu_to_le64(ctxt->ldc_hash);

	ll_kunmap_local(dp);
	put_lmv_dir_ctxt(ctxt);
	OBD_FREE(ctxt, offsetof(typeof(*ctxt), ldc_stripes[stripe_count]));

	*pfolio = folio;

	RETURN(0);

free_page:
	ll_kunmap_local(dp);
	folio_put(folio);

	return rc;
}

static int lmv_read_page(struct obd_export *exp, struct md_op_data *op_data,
			 struct md_readdir_info *mrinfo, __u64 offset,
			 struct folio **pfolio)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	if (unlikely(lmv_dir_foreign(op_data->op_lso1)))
		RETURN(-ENODATA);

	if (unlikely(lmv_dir_striped(op_data->op_lso1))) {
		rc = lmv_striped_read_page(exp, op_data, mrinfo, offset,
					   pfolio);
		RETURN(rc);
	}

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid1);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	rc = md_read_page(tgt->ltd_exp, op_data, mrinfo, offset, pfolio);

	RETURN(rc);
}

/**
 * lmv_unlink() - Unlink a file/directory
 * @exp: export refer to LMV
 * @op_data: different parameters transferred between
 * client MD stacks, name, namelen, FIDs etc. op_fid1 is the parent FID,
 * op_fid2 is the child FID.
 * @request: point to the request of unlink.
 *
 * Unlink a file or directory under the parent dir. The unlink request
 * usually will be sent to the MDT where the child is located, but if
 * the client does not have the child FID then request will be sent to the
 * MDT where the parent is located.
 *
 * If the parent is a striped directory then it also needs to locate which
 * stripe the name of the child is located, and replace the parent FID
 * (@op->op_fid1) with the stripe FID. Note: if the stripe is unknown,
 * it will walk through all of sub-stripes until the child is being
 * unlinked finally.
 *
 * * Return:
 * * %0: Success
 * * %-ERRNO: Failure
 */
static int lmv_unlink(struct obd_export *exp, struct md_op_data *op_data,
		      struct ptlrpc_request **request)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	struct lmv_tgt_desc *parent_tgt;
	struct mdt_body *body;
	int rc;

	ENTRY;

	op_data->op_fsuid = from_kuid(&init_user_ns, current_fsuid());
	op_data->op_fsgid = from_kgid(&init_user_ns, current_fsgid());
	op_data->op_cap = current_cap();

retry:
	parent_tgt = lmv_locate_tgt(lmv, op_data);
	if (IS_ERR(parent_tgt))
		RETURN(PTR_ERR(parent_tgt));

	if (likely(!fid_is_zero(&op_data->op_fid2))) {
		tgt = lmv_fid2tgt(lmv, &op_data->op_fid2);
		if (IS_ERR(tgt))
			RETURN(PTR_ERR(tgt));
	} else {
		tgt = parent_tgt;
	}

	/*
	 * If child's fid is given, cancel unused locks for it if it is from
	 * another export than parent.
	 *
	 * LOOKUP lock for child (fid3) should also be cancelled on parent
	 * tgt_tgt in mdc_unlink().
	 */
	op_data->op_flags |= MF_MDC_CANCEL_FID1 | MF_MDC_CANCEL_FID3;

	if (parent_tgt != tgt)
		rc = lmv_early_cancel(exp, parent_tgt, op_data, tgt->ltd_index,
				      LCK_EX, MDS_INODELOCK_LOOKUP,
				      MF_MDC_CANCEL_FID3);

	rc = lmv_early_cancel(exp, NULL, op_data, tgt->ltd_index, LCK_EX,
			      MDS_INODELOCK_ELC, MF_MDC_CANCEL_FID3);
	if (rc)
		RETURN(rc);

	CDEBUG(D_INODE, "unlink with fid="DFID"/"DFID" -> mds #%u\n",
	       PFID(&op_data->op_fid1), PFID(&op_data->op_fid2),
	       tgt->ltd_index);

	rc = md_unlink(tgt->ltd_exp, op_data, request);
	if (rc == -ENOENT && lmv_dir_retry_check_update(op_data)) {
		ptlrpc_req_put(*request);
		*request = NULL;
		goto retry;
	}

	if (rc != -EREMOTE)
		RETURN(rc);

	body = req_capsule_server_get(&(*request)->rq_pill, &RMF_MDT_BODY);
	if (body == NULL)
		RETURN(-EPROTO);

	/* Not cross-ref case, just get out of here. */
	if (likely(!(body->mbo_valid & OBD_MD_MDS)))
		RETURN(rc);

	/* This is a remote object, try remote MDT. */
	op_data->op_fid2 = body->mbo_fid1;
	ptlrpc_req_put(*request);
	*request = NULL;

	tgt = lmv_fid2tgt(lmv, &op_data->op_fid2);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	goto retry;
}

static struct lu_device *lmv_device_fini(const struct lu_env *env,
					 struct lu_device *lu)
{
	struct obd_device *obd = lu->ld_obd;

	libcfs_kkuc_group_rem(&obd->obd_uuid, 0, KUC_GRP_HSM);

	return NULL;
}

/**
 * lmv_get_info() - Get by key a value associated with a LMV device.
 * @env: execution environment for this thread
 * @exp: export for the LMV device
 * @keylen: length of key identifier
 * @key: identifier of key to get value for
 * @vallen: size of \a val
 * @val: pointer to storage location for value
 *
 * Dispatch request to lower-layer devices as needed.
 * lsm: optional striping metadata of object
 *
 * Return:
 * * %0 on success
 * * %negative negated errno on failure
 */
static int lmv_get_info(const struct lu_env *env, struct obd_export *exp,
			__u32 keylen, void *key, __u32 *vallen, void *val)
{
	struct obd_device *obd;
	struct lmv_obd *lmv;
	struct lu_tgt_desc *tgt;
	int rc = 0;

	ENTRY;

	obd = class_exp2obd(exp);
	if (obd == NULL) {
		CDEBUG(D_IOCTL, "Invalid client cookie %#llx\n",
		       exp->exp_handle.h_cookie);
		RETURN(-EINVAL);
	}

	lmv = &obd->u.lmv;
	if (keylen >= strlen("remote_flag") && !strcmp(key, "remote_flag")) {
		LASSERT(*vallen == sizeof(__u32));
		lmv_foreach_connected_tgt(lmv, tgt) {
			if (!obd_get_info(env, tgt->ltd_exp, keylen, key,
					  vallen, val))
				RETURN(0);
		}
		RETURN(-EINVAL);
	} else if (KEY_IS(KEY_MAX_EASIZE) ||
		   KEY_IS(KEY_DEFAULT_EASIZE) ||
		   KEY_IS(KEY_CONN_DATA)) {
		/*
		 * Forwarding this request to first MDS, it should know LOV
		 * desc.
		 */
		tgt = lmv_tgt(lmv, 0);
		if (!tgt)
			RETURN(-ENODEV);

		rc = obd_get_info(env, tgt->ltd_exp, keylen, key, vallen, val);
		if (!rc && KEY_IS(KEY_CONN_DATA))
			exp->exp_connect_data = *(struct obd_connect_data *)val;
		RETURN(rc);
	} else if (KEY_IS(KEY_TGT_COUNT)) {
		*((int *)val) = lmv->lmv_mdt_descs.ltd_tgts_size;
		RETURN(0);
	}

	CDEBUG(D_IOCTL, "Invalid key\n");
	RETURN(-EINVAL);
}

static int lmv_rmfid(struct obd_export *exp, struct fid_array *fa,
		     int *__rcs, struct ptlrpc_request_set *_set)
{
	struct obd_device *obd = class_exp2obd(exp);
	struct ptlrpc_request_set *set = _set;
	struct lmv_obd *lmv = &obd->u.lmv;
	int tgt_count = lmv->lmv_mdt_count;
	struct lu_tgt_desc *tgt;
	struct fid_array *fat, **fas = NULL;
	int i, rc, **rcs = NULL;

	if (!set) {
		set = ptlrpc_prep_set();
		if (!set)
			RETURN(-ENOMEM);
	}

	/* split FIDs by targets */
	OBD_ALLOC_PTR_ARRAY_LARGE(fas, tgt_count);
	if (fas == NULL)
		GOTO(out, rc = -ENOMEM);

	OBD_ALLOC_PTR_ARRAY_LARGE(rcs, tgt_count);
	if (rcs == NULL)
		GOTO(out_fas, rc = -ENOMEM);

	for (i = 0; i < fa->fa_nr; i++) {
		unsigned int idx;

		rc = lmv_fld_lookup(lmv, &fa->fa_fids[i], &idx);
		if (rc) {
			CDEBUG(D_OTHER, "can't lookup "DFID": rc = %d\n",
			       PFID(&fa->fa_fids[i]), rc);
			continue;
		}
		LASSERT(idx < tgt_count);
		if (!fas[idx]) {
			OBD_ALLOC_LARGE(fas[idx], offsetof(struct fid_array,
							   fa_fids[fa->fa_nr]));
			if (!fas[idx])
				GOTO(out_rcs, rc = -ENOMEM);
		}
		if (!rcs[idx]) {
			OBD_ALLOC_PTR_ARRAY_LARGE(rcs[idx], fa->fa_nr);
			if (!rcs[idx])
				GOTO(out_rcs, rc = -ENOMEM);
		}

		fat = fas[idx];
		fat->fa_fids[fat->fa_nr++] = fa->fa_fids[i];
	}

	lmv_foreach_connected_tgt(lmv, tgt) {
		fat = fas[tgt->ltd_index];
		if (!fat || fat->fa_nr == 0)
			continue;
		rc = md_rmfid(tgt->ltd_exp, fat, rcs[tgt->ltd_index], set);
	}

	rc = ptlrpc_set_wait(NULL, set);
	if (rc == 0) {
		int j = 0;

		for (i = 0; i < tgt_count; i++) {
			fat = fas[i];
			if (!fat || fat->fa_nr == 0)
				continue;
			/* copy FIDs back */
			memcpy(fa->fa_fids + j, fat->fa_fids,
			       fat->fa_nr * sizeof(struct lu_fid));
			/* copy rcs back */
			memcpy(__rcs + j, rcs[i], fat->fa_nr * sizeof(**rcs));
			j += fat->fa_nr;
		}
	}
out_rcs:
	for (i = 0; i < tgt_count; i++) {
		if (fas[i])
			OBD_FREE_LARGE(fas[i], offsetof(struct fid_array,
							fa_fids[fa->fa_nr]));
		if (rcs[i])
			OBD_FREE_PTR_ARRAY_LARGE(rcs[i], fa->fa_nr);
	}
	OBD_FREE_PTR_ARRAY_LARGE(rcs, tgt_count);
out_fas:
	OBD_FREE_PTR_ARRAY_LARGE(fas, tgt_count);
out:
	if (set != _set)
		ptlrpc_set_destroy(set);

	RETURN(rc);
}

/**
 * lmv_set_info_async() - Asynchronously set by key a value associated with a
 * LMV device.
 * @env: execution environment for this thread
 * @exp: export for the LMV device
 * @keylen: length of key identifier
 * @key: identifier of key to store value for
 * @vallen: size of value to store
 * @val: pointer to data to be stored
 * @set: optional list of related ptlrpc requests
 *
 * Dispatch request to lower-layer devices as needed.
 *
 * Return:
 * * %0 on success
 * * %negative negated errno on failure
 */
static int lmv_set_info_async(const struct lu_env *env, struct obd_export *exp,
			      __u32 keylen, void *key, __u32 vallen, void *val,
			      struct ptlrpc_request_set *set)
{
	struct lmv_tgt_desc *tgt;
	struct obd_device *obd;
	struct lmv_obd *lmv;
	int rc = 0;

	ENTRY;

	obd = class_exp2obd(exp);
	if (obd == NULL) {
		CDEBUG(D_IOCTL, "Invalid client cookie %#llx\n",
		       exp->exp_handle.h_cookie);
		RETURN(-EINVAL);
	}
	lmv = &obd->u.lmv;

	if (KEY_IS(KEY_READ_ONLY) || KEY_IS(KEY_FLUSH_CTX) ||
	    KEY_IS(KEY_DEFAULT_EASIZE)) {
		int err = 0;

		lmv_foreach_connected_tgt(lmv, tgt) {
			err = obd_set_info_async(env, tgt->ltd_exp,
						 keylen, key, vallen, val, set);
			if (err && rc == 0)
				rc = err;
		}

		RETURN(rc);
	}

	RETURN(-EINVAL);
}

static int lmv_unpack_md_v1(struct obd_export *exp, struct lmv_stripe_md *lsm,
			    const struct lmv_mds_md_v1 *lmm1)
{
	struct lmv_obd	*lmv = &exp->exp_obd->u.lmv;
	int		stripe_count;
	int		cplen;
	int		i;
	int		rc = 0;

	ENTRY;

	lsm->lsm_md_magic = le32_to_cpu(lmm1->lmv_magic);
	lsm->lsm_md_stripe_count = le32_to_cpu(lmm1->lmv_stripe_count);
	lsm->lsm_md_master_mdt_index = le32_to_cpu(lmm1->lmv_master_mdt_index);
	if (CFS_FAIL_CHECK(OBD_FAIL_LMV_UNKNOWN_STRIPE))
		lsm->lsm_md_hash_type = cfs_fail_val ?: LMV_HASH_TYPE_UNKNOWN;
	else
		lsm->lsm_md_hash_type = le32_to_cpu(lmm1->lmv_hash_type);
	lsm->lsm_md_layout_version = le32_to_cpu(lmm1->lmv_layout_version);
	lsm->lsm_md_migrate_offset = le32_to_cpu(lmm1->lmv_migrate_offset);
	lsm->lsm_md_migrate_hash = le32_to_cpu(lmm1->lmv_migrate_hash);
	cplen = strscpy(lsm->lsm_md_pool_name, lmm1->lmv_pool_name,
			sizeof(lsm->lsm_md_pool_name));

	if (cplen < 0)
		RETURN(cplen);

	CDEBUG(D_INFO, "unpack lsm count %d/%d, master %d hash_type %#x/%#x layout_version %d\n",
	       lsm->lsm_md_stripe_count,
	       lsm->lsm_md_migrate_offset, lsm->lsm_md_master_mdt_index,
	       lsm->lsm_md_hash_type, lsm->lsm_md_migrate_hash,
	       lsm->lsm_md_layout_version);

	stripe_count = le32_to_cpu(lmm1->lmv_stripe_count);
	for (i = 0; i < stripe_count; i++) {
		fid_le_to_cpu(&lsm->lsm_md_oinfo[i].lmo_fid,
			      &lmm1->lmv_stripe_fids[i]);
		/*
		 * set default value -1, so lmv_locate_tgt() knows this stripe
		 * target is not initialized.
		 */
		lsm->lsm_md_oinfo[i].lmo_mds = LMV_OFFSET_DEFAULT;
		if (!fid_is_sane(&lsm->lsm_md_oinfo[i].lmo_fid))
			continue;

		rc = lmv_fld_lookup(lmv, &lsm->lsm_md_oinfo[i].lmo_fid,
				    &lsm->lsm_md_oinfo[i].lmo_mds);
		if (rc == -ENOENT)
			continue;

		if (rc)
			RETURN(rc);

		CDEBUG(D_INFO, "unpack fid #%d "DFID"\n", i,
		       PFID(&lsm->lsm_md_oinfo[i].lmo_fid));
	}

	RETURN(rc);
}

static inline int lmv_unpack_user_md(struct obd_export *exp,
				     struct lmv_stripe_md *lsm,
				     const struct lmv_user_md *lmu)
{
	lsm->lsm_md_magic = le32_to_cpu(lmu->lum_magic);
	lsm->lsm_md_stripe_count = le32_to_cpu(lmu->lum_stripe_count);
	lsm->lsm_md_master_mdt_index = le32_to_cpu(lmu->lum_stripe_offset);
	lsm->lsm_md_hash_type = le32_to_cpu(lmu->lum_hash_type);
	lsm->lsm_md_max_inherit = lmu->lum_max_inherit;
	lsm->lsm_md_max_inherit_rr = lmu->lum_max_inherit_rr;
	lsm->lsm_md_pool_name[LOV_MAXPOOLNAME] = 0;

	return 0;
}

struct lmv_stripe_object *lmv_stripe_object_alloc(__u32 magic,
						  const union lmv_mds_md *lmm,
						  size_t lmm_size)
{
	struct lmv_stripe_object *lsm_obj;
	int size;

	if (magic == LMV_MAGIC_FOREIGN) {
		struct lmv_foreign_md *lfm;

		size = offsetof(typeof(*lfm), lfm_value[0]);
		if (lmm_size < size)
			RETURN(ERR_PTR(-EPROTO));

		size += le32_to_cpu(lmm->lmv_foreign_md.lfm_length);
		if (lmm_size < size)
			RETURN(ERR_PTR(-EPROTO));

		OBD_ALLOC_LARGE(lsm_obj, lmm_size +
				offsetof(typeof(*lsm_obj), lso_lfm));
	} else {
		if (magic == LMV_MAGIC_V1) {
			int count;

			size = offsetof(struct lmv_mds_md_v1,
					lmv_stripe_fids[0]);
			if (lmm_size < size)
				RETURN(ERR_PTR(-EPROTO));

			count = lmv_mds_md_stripe_count_get(lmm);
			size += count * sizeof(struct lu_fid);
			if (lmm_size < size)
				RETURN(ERR_PTR(-EPROTO));

			size = lmv_stripe_md_size(count);
		} else {
			if (lmm && lmm_size < sizeof(struct lmv_user_md))
				RETURN(ERR_PTR(-EPROTO));

			/**
			 * Unpack default dirstripe(lmv_user_md) to
			 * lmv_stripe_md, stripecount should be 0 then.
			 */
			size = lmv_stripe_md_size(0);
		}
		size += offsetof(typeof(*lsm_obj), lso_lsm);
		OBD_ALLOC(lsm_obj, size);
	}

	if (lsm_obj) {
		kref_init(&lsm_obj->lso_refs);
		RETURN(lsm_obj);
	}

	RETURN(ERR_PTR(-ENOMEM));
}
EXPORT_SYMBOL(lmv_stripe_object_alloc);

static int lmv_stripe_object_create(struct obd_export *exp,
				    struct lmv_stripe_object **lsop,
				    const union lmv_mds_md *lmm,
				    size_t lmm_size)
{
	struct lmv_stripe_object *lsm_obj;
	__u32 magic;
	int rc;

	ENTRY;

	LASSERT(lsop != NULL && *lsop == NULL);

	if (lmm_size == 0)
		RETURN(-EPROTO);

	magic = le32_to_cpu(lmm->lmv_magic);
	if (magic == LMV_MAGIC_STRIPE)
		RETURN(-EPERM);

	if (magic != LMV_MAGIC_V1 && magic != LMV_USER_MAGIC &&
	    magic != LMV_MAGIC_FOREIGN) {
		CERROR("%s: invalid lmv magic %x: rc = %d\n",
		       exp->exp_obd->obd_name, magic, -EIO);
		RETURN(-EIO);
	}

	/* foreign lmv case */
	if (magic == LMV_MAGIC_FOREIGN) {
		struct lmv_foreign_md *lfm;

		lsm_obj = lmv_stripe_object_alloc(magic, lmm, lmm_size);
		if (IS_ERR(lsm_obj))
			RETURN(PTR_ERR(lsm_obj));

		*lsop = lsm_obj;
		lfm = &lsm_obj->lso_lfm;
		lfm->lfm_magic = magic;
		lfm->lfm_length = le32_to_cpu(lmm->lmv_foreign_md.lfm_length);
		lfm->lfm_type = le32_to_cpu(lmm->lmv_foreign_md.lfm_type);
		lfm->lfm_flags = le32_to_cpu(lmm->lmv_foreign_md.lfm_flags);
		memcpy(&lfm->lfm_value, &lmm->lmv_foreign_md.lfm_value,
		       lfm->lfm_length);
		RETURN(0);
	}

	/* Unpack memmd */
	lsm_obj = lmv_stripe_object_alloc(magic, lmm, lmm_size);
	if (IS_ERR(lsm_obj))
		RETURN(PTR_ERR(lsm_obj));

	switch (magic) {
	case LMV_MAGIC_V1:
		rc = lmv_unpack_md_v1(exp, &lsm_obj->lso_lsm, &lmm->lmv_md_v1);
		break;
	case LMV_USER_MAGIC:
		rc = lmv_unpack_user_md(exp, &lsm_obj->lso_lsm,
					&lmm->lmv_user_md);
		break;
	default:
		CERROR("%s: unrecognized magic %x\n", exp->exp_obd->obd_name,
		       magic);
		rc = -EINVAL;
		break;
	}

	if (rc != 0)
		lmv_stripe_object_put(&lsm_obj);

	*lsop = lsm_obj;
	RETURN(rc);
}

struct lmv_stripe_object *
lmv_stripe_object_get(struct lmv_stripe_object *lsm_obj)
{
	if (lsm_obj == NULL)
		return NULL;

	kref_get(&lsm_obj->lso_refs);
	CDEBUG(D_INODE, "get %p %u\n", lsm_obj,
	       kref_read(&lsm_obj->lso_refs));
	return lsm_obj;
}
EXPORT_SYMBOL(lmv_stripe_object_get);

void lmv_stripe_object_free(struct kref *kref)
{
	struct lmv_stripe_object *lsm_obj;
	size_t size;
	int i;

	lsm_obj = container_of(kref, struct lmv_stripe_object, lso_refs);

	if (lmv_dir_foreign(lsm_obj)) {
		size = lsm_obj->lso_lfm.lfm_length +
			offsetof(typeof(lsm_obj->lso_lfm), lfm_value[0]) +
			offsetof(typeof(*lsm_obj), lso_lsm);
		OBD_FREE_LARGE(lsm_obj, size);
		return;
	}

	if (lmv_dir_striped(lsm_obj)) {
		struct lmv_stripe_md *lsm = &lsm_obj->lso_lsm;

		for (i = 0; i < lsm->lsm_md_stripe_count; i++)
			iput(lsm->lsm_md_oinfo[i].lmo_root);
		size = lmv_stripe_md_size(lsm->lsm_md_stripe_count);
	} else {
		size = lmv_stripe_md_size(0);
	}
	OBD_FREE(lsm_obj, size + offsetof(typeof(*lsm_obj), lso_lsm));
}


void lmv_stripe_object_put(struct lmv_stripe_object **lsop)
{
	struct lmv_stripe_object *lsm_obj;

	LASSERT(lsop != NULL);

	lsm_obj = *lsop;
	if (lsm_obj == NULL)
		return;

	*lsop = NULL;
	CDEBUG(D_INODE, "put %p %u\n", lsm_obj, kref_read(&lsm_obj->lso_refs));

	kref_put(&lsm_obj->lso_refs, lmv_stripe_object_free);
}
EXPORT_SYMBOL(lmv_stripe_object_put);

static int lmv_cancel_unused(struct obd_export *exp, const struct lu_fid *fid,
			     union ldlm_policy_data *policy,
			     enum ldlm_mode mode, enum ldlm_cancel_flags flags,
			     void *opaque)
{
	struct lmv_obd *lmv = &exp->exp_obd->u.lmv;
	struct lu_tgt_desc *tgt;
	int err;
	int rc = 0;

	ENTRY;

	LASSERT(fid != NULL);

	lmv_foreach_connected_tgt(lmv, tgt) {
		if (!tgt->ltd_active)
			continue;

		err = md_cancel_unused(tgt->ltd_exp, fid, policy, mode, flags,
				       opaque);
		if (!rc)
			rc = err;
	}
	RETURN(rc);
}

static int lmv_set_lock_data(struct obd_export *exp,
			     const struct lustre_handle *lockh,
			     void *data, enum mds_ibits_locks *bits)
{
	struct lmv_obd *lmv = &exp->exp_obd->u.lmv;
	struct lmv_tgt_desc *tgt = lmv_tgt(lmv, 0);
	int rc;

	ENTRY;

	if (tgt == NULL || tgt->ltd_exp == NULL)
		RETURN(-EINVAL);
	rc =  md_set_lock_data(tgt->ltd_exp, lockh, data, bits);
	RETURN(rc);
}

static enum ldlm_mode
lmv_lock_match(struct obd_export *exp, __u64 flags,
	       const struct lu_fid *fid, enum ldlm_type type,
	       union ldlm_policy_data *policy,
	       enum ldlm_mode mode, enum ldlm_match_flags match_flags,
	       struct lustre_handle *lockh)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lu_tgt_desc *tgt;
	__u64 bits = policy->l_inodebits.bits;
	enum ldlm_mode rc = LCK_MODE_MIN;
	int index;
	int i;

	/* only one bit is set */
	LASSERT(bits && !(bits & (bits - 1)));
	/* With DNE every object can have two locks in different namespaces:
	 * lookup lock in space of MDT storing direntry and update/open lock in
	 * space of MDT storing inode.  Try the MDT that the FID maps to first,
	 * since this can be easily found, and only try others if that fails.
	 */
	if (bits == MDS_INODELOCK_LOOKUP) {
		for (i = 0, index = lmv_fid2tgt_index(lmv, fid);
		     i < lmv->lmv_mdt_descs.ltd_tgts_size; i++,
		     index = (index + 1) % lmv->lmv_mdt_descs.ltd_tgts_size) {
			if (index < 0) {
				CDEBUG(D_HA,
				       "%s: "DFID" is inaccessible: rc = %d\n",
				       obd->obd_name, PFID(fid), index);
				index = 0;
			}
			tgt = lmv_tgt(lmv, index);
			if (!tgt || !tgt->ltd_exp || !tgt->ltd_active)
				continue;
			rc = md_lock_match(tgt->ltd_exp, flags, fid, type,
					   policy, mode, match_flags, lockh);
			if (rc)
				break;
		}
	} else {
		tgt = lmv_fid2tgt(lmv, fid);
		if (!IS_ERR(tgt) && tgt->ltd_exp && tgt->ltd_active)
			rc = md_lock_match(tgt->ltd_exp, flags, fid, type,
					   policy, mode, match_flags, lockh);
	}

	CDEBUG(D_INODE, "Lock match for "DFID": %d\n", PFID(fid), rc);

	return rc;
}

static int
lmv_get_lustre_md(struct obd_export *exp, struct req_capsule *pill,
		  struct obd_export *dt_exp, struct obd_export *md_exp,
		  struct lustre_md *md)
{
	struct lmv_obd *lmv = &exp->exp_obd->u.lmv;
	struct lmv_tgt_desc *tgt = lmv_tgt(lmv, 0);

	if (!tgt || !tgt->ltd_exp)
		return -EINVAL;

	return md_get_lustre_md(tgt->ltd_exp, pill, dt_exp, md_exp, md);
}

static int lmv_put_lustre_md(struct obd_export *exp, struct lustre_md *md)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt = lmv_tgt(lmv, 0);

	ENTRY;

	lmv_stripe_object_put(&md->def_lsm_obj);
	lmv_stripe_object_put(&md->lsm_obj);

	if (!tgt || !tgt->ltd_exp)
		RETURN(-EINVAL);
	RETURN(0);
}

static int lmv_set_open_replay_data(struct obd_export *exp,
				    struct obd_client_handle *och,
				    struct lookup_intent *it)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, &och->och_fid);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	RETURN(md_set_open_replay_data(tgt->ltd_exp, och, it));
}

static int lmv_clear_open_replay_data(struct obd_export *exp,
				      struct obd_client_handle *och)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, &och->och_fid);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	RETURN(md_clear_open_replay_data(tgt->ltd_exp, och));
}

static int lmv_intent_getattr_async(struct obd_export *exp,
				    struct md_op_item *item)
{
	struct md_op_data *op_data = &item->mop_data;
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *ptgt;
	struct lmv_tgt_desc *ctgt;
	int rc;

	ENTRY;

	if (!(fid_is_sane(&op_data->op_fid2) ||
	      fid_is_zero(&op_data->op_fid2)))
		RETURN(-EINVAL);

	ptgt = lmv_locate_tgt(lmv, op_data);
	if (IS_ERR(ptgt))
		RETURN(PTR_ERR(ptgt));

	/*
	 * Zeroed FID @op_fid2 means that the intent getattr() comes from
	 * statahead by regularized file names. Currently only do statahead
	 * for the children files located same as the parent directory.
	 */
	if (!fid_is_zero(&op_data->op_fid2)) {
		ctgt = lmv_fid2tgt(lmv, &op_data->op_fid2);
		if (IS_ERR(ctgt))
			RETURN(PTR_ERR(ctgt));

		/*
		 * remote object needs two RPCs to lookup and getattr,
		 * considering the complexity don't support statahead for now.
		 */
		if (ctgt != ptgt)
			RETURN(-EREMOTE);
	}

	rc = md_intent_getattr_async(ptgt->ltd_exp, item);

	RETURN(rc);
}

static int lmv_revalidate_lock(struct obd_export *exp, struct lookup_intent *it,
			       struct lu_fid *fid, enum mds_ibits_locks *bits)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	int rc;

	ENTRY;

	tgt = lmv_fid2tgt(lmv, fid);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	rc = md_revalidate_lock(tgt->ltd_exp, it, fid, bits);
	RETURN(rc);
}

static int lmv_get_fid_from_lsm(struct obd_export *exp,
				const struct lmv_stripe_object *lso,
				const char *name, int namelen,
				struct lu_fid *fid)
{
	const struct lmv_oinfo *oinfo;

	LASSERT(lmv_dir_striped(lso));
	oinfo = lsm_name_to_stripe_info(lso, name, namelen, false);
	if (IS_ERR(oinfo))
		return PTR_ERR(oinfo);

	*fid = oinfo->lmo_fid;

	RETURN(0);
}

/*
 * For lmv, only need to send request to master MDT, and the master MDT will
 * process with other slave MDTs. The only exception is Q_GETOQUOTA for which
 * we directly fetch data from the slave MDTs.
 */
static int lmv_quotactl(struct obd_device *unused, struct obd_export *exp,
			struct obd_quotactl *oqctl)
{
	struct obd_device *obd = class_exp2obd(exp);
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt = lmv_tgt(lmv, 0);
	__u64 curspace, curinodes;
	int rc = 0;

	ENTRY;

	if (!tgt || !tgt->ltd_exp || !tgt->ltd_active) {
		CERROR("master lmv inactive\n");
		RETURN(-EIO);
	}

	if (oqctl->qc_cmd == LUSTRE_Q_ITERQUOTA ||
	    oqctl->qc_cmd == LUSTRE_Q_ITEROQUOTA) {
		struct list_head *lst =
			(struct list_head *)(uintptr_t)(oqctl->qc_iter_list);
		int err;

		if (oqctl->qc_cmd == LUSTRE_Q_ITERQUOTA)
			RETURN(obd_quota_iter(tgt->ltd_exp, oqctl, lst));

		lmv_foreach_connected_tgt(lmv, tgt) {
			if (!tgt->ltd_active)
				continue;

			err = obd_quota_iter(tgt->ltd_exp, oqctl, lst);
			if (err) {
				CERROR("%s: getquota failed mdt %d: rc = %d\n",
				       obd->obd_name, tgt->ltd_index, err);
				if (!rc)
					rc = err;
			}
		}

		RETURN(rc);
	}

	if (oqctl->qc_cmd != Q_GETOQUOTA) {
		rc = obd_quotactl(tgt->ltd_exp, oqctl);
		RETURN(rc);
	}

	curspace = curinodes = 0;
	lmv_foreach_connected_tgt(lmv, tgt) {
		int err;

		if (!tgt->ltd_active)
			continue;

		err = obd_quotactl(tgt->ltd_exp, oqctl);
		if (err) {
			CERROR("getquota on mdt %d failed. %d\n",
			       tgt->ltd_index, err);
			if (!rc)
				rc = err;
		} else {
			curspace += oqctl->qc_dqblk.dqb_curspace;
			curinodes += oqctl->qc_dqblk.dqb_curinodes;
		}
	}
	oqctl->qc_dqblk.dqb_curspace = curspace;
	oqctl->qc_dqblk.dqb_curinodes = curinodes;

	RETURN(rc);
}

static int lmv_merge_attr(struct obd_export *exp,
			  const struct lmv_stripe_object *lso,
			  struct cl_attr *attr,
			  ldlm_blocking_callback cb_blocking)
{
	const struct lmv_stripe_md *lsm = &lso->lso_lsm;
	int rc;
	int i;
	int nlink_overflow = 0;

	if (!lmv_dir_striped(lso))
		return 0;

	rc = lmv_revalidate_slaves(exp, lsm, cb_blocking, 0, NULL);
	if (rc < 0)
		return rc;

	for (i = 0; i < lsm->lsm_md_stripe_count; i++) {
		struct inode *inode = lsm->lsm_md_oinfo[i].lmo_root;

		if (!inode)
			continue;

		CDEBUG(D_INFO,
		       "" DFID " size %llu, blocks %llu nlink %u, atime %lld ctime %lld, mtime %lld.\n",
		       PFID(&lsm->lsm_md_oinfo[i].lmo_fid),
		       i_size_read(inode), (unsigned long long)inode->i_blocks,
		       inode->i_nlink, (s64)inode_get_atime_sec(inode),
		       (s64)inode_get_ctime_sec(inode),
		       (s64)inode_get_mtime_sec(inode));

		/* nlink==1 is a special value meaning nlink overflow
		 * for directories on Ldiskfs.
		 */
		nlink_overflow |= (inode->i_nlink == 1);
		/* not counting . and .. for each stripe */
		attr->cat_nlink += inode->i_nlink - 2;

		attr->cat_size += i_size_read(inode);
		attr->cat_blocks += inode->i_blocks;

		if (attr->cat_atime < inode_get_atime_sec(inode))
			attr->cat_atime = inode_get_atime_sec(inode);

		if (attr->cat_ctime < inode_get_ctime_sec(inode))
			attr->cat_ctime = inode_get_ctime_sec(inode);

		if (attr->cat_mtime < inode_get_mtime_sec(inode))
			attr->cat_mtime = inode_get_mtime_sec(inode);
	}
	if (nlink_overflow)
		/* Indicate that nlink is not correct for a striped dir the
		 * same way it is done in Ldiskfs by setting nlink = 1.
		 */
		attr->cat_nlink = 1;
	else
		/* add 2 for . and .. */
		attr->cat_nlink += 2;
	return 0;
}

static struct lu_batch *lmv_batch_create(struct obd_export *exp,
					 enum lu_batch_flags flags,
					 __u32 max_count)
{
	struct lu_batch *bh;
	struct lmv_batch *lbh;

	ENTRY;
	OBD_ALLOC_PTR(lbh);
	if (!lbh)
		RETURN(ERR_PTR(-ENOMEM));

	bh = &lbh->lbh_super;
	bh->lbt_flags = flags;
	bh->lbt_max_count = max_count;

	if (flags & BATCH_FL_RQSET) {
		bh->lbt_rqset = ptlrpc_prep_set();
		if (bh->lbt_rqset == NULL) {
			OBD_FREE_PTR(lbh);
			RETURN(ERR_PTR(-ENOMEM));
		}
	}

	INIT_LIST_HEAD(&lbh->lbh_sub_batch_list);
	RETURN(bh);
}

static int lmv_batch_stop(struct obd_export *exp, struct lu_batch *bh)
{
	struct lmv_batch *lbh;
	struct lmvsub_batch *sub;
	struct lmvsub_batch *tmp;
	int rc = 0;

	ENTRY;

	lbh = container_of(bh, struct lmv_batch, lbh_super);
	list_for_each_entry_safe(sub, tmp, &lbh->lbh_sub_batch_list,
				 sbh_sub_item) {
		list_del(&sub->sbh_sub_item);
		rc = md_batch_stop(sub->sbh_tgt->ltd_exp, sub->sbh_sub);
		if (rc < 0) {
			CERROR("%s: stop batch processing failed: rc = %d\n",
			       exp->exp_obd->obd_name, rc);
			if (bh->lbt_result == 0)
				bh->lbt_result = rc;
		}
		OBD_FREE_PTR(sub);
	}

	if (bh->lbt_flags & BATCH_FL_RQSET) {
		rc = ptlrpc_set_wait(NULL, bh->lbt_rqset);
		ptlrpc_set_destroy(bh->lbt_rqset);
	}

	OBD_FREE_PTR(lbh);
	RETURN(rc);
}

static int lmv_batch_flush(struct obd_export *exp, struct lu_batch *bh,
			   bool wait)
{
	struct lmv_batch *lbh;
	struct lmvsub_batch *sub;
	int rc = 0;
	int rc1;

	ENTRY;

	lbh = container_of(bh, struct lmv_batch, lbh_super);
	list_for_each_entry(sub, &lbh->lbh_sub_batch_list, sbh_sub_item) {
		rc1 = md_batch_flush(sub->sbh_tgt->ltd_exp, sub->sbh_sub, wait);
		if (rc1 < 0) {
			CERROR("%s: stop batch processing failed: rc = %d\n",
			       exp->exp_obd->obd_name, rc);
			if (bh->lbt_result == 0)
				bh->lbt_result = rc;

			if (rc == 0)
				rc = rc1;
		}
	}

	if (wait && bh->lbt_flags & BATCH_FL_RQSET) {
		rc1 = ptlrpc_set_wait(NULL, bh->lbt_rqset);
		if (rc == 0)
			rc = rc1;
	}

	RETURN(rc);
}

static inline struct lmv_tgt_desc *
lmv_batch_locate_tgt(struct lmv_obd *lmv, struct md_op_item *item)
{
	struct md_op_data *op_data = &item->mop_data;
	struct lmv_tgt_desc *tgt;

	switch (item->mop_opc) {
	case MD_OP_GETATTR: {
		struct lmv_tgt_desc *ptgt;

		if (!(fid_is_sane(&op_data->op_fid2) ||
		      fid_is_zero(&op_data->op_fid2)))
			RETURN(ERR_PTR(-EINVAL));

		ptgt = lmv_locate_tgt(lmv, op_data);
		if (IS_ERR(ptgt))
			RETURN(ptgt);

		/*
		 * Zeroed @op_fid2 means that it is a statahead populating call
		 * in the file name pattern which is using file name format to
		 * prefetch the attributes. Thus it has no idea about the FID of
		 * the children file. The children file is considered to be
		 * located on the same storage target with the parent directory
		 * or the stripped directory.
		 */
		if (fid_is_zero(&op_data->op_fid2)) {
			tgt = ptgt;
			break;
		}

		tgt = lmv_fid2tgt(lmv, &op_data->op_fid2);
		if (IS_ERR(tgt))
			RETURN(tgt);

		/*
		 * Remote object needs two RPCs to lookup and getattr,
		 * considering the complexity don't support statahead for now.
		 */
		if (tgt != ptgt)
			RETURN(ERR_PTR(-EREMOTE));

		break;
	}
	default:
		tgt = ERR_PTR(-ENOTSUPP);
	}

	return tgt;
}

static struct lu_batch *lmv_batch_lookup_sub(struct lmv_batch *lbh,
					     struct lmv_tgt_desc *tgt)
{
	struct lmvsub_batch *sub;

	list_for_each_entry(sub, &lbh->lbh_sub_batch_list, sbh_sub_item) {
		if (sub->sbh_tgt == tgt)
			return sub->sbh_sub;
	}

	return NULL;
}

static struct lu_batch *lmv_batch_get_sub(struct lmv_batch *lbh,
					  struct lmv_tgt_desc *tgt)
{
	struct lmvsub_batch *sbh;
	struct lu_batch *child_bh;
	struct lu_batch *bh;

	ENTRY;

	child_bh = lmv_batch_lookup_sub(lbh, tgt);
	if (child_bh != NULL)
		RETURN(child_bh);

	OBD_ALLOC_PTR(sbh);
	if (sbh == NULL)
		RETURN(ERR_PTR(-ENOMEM));

	INIT_LIST_HEAD(&sbh->sbh_sub_item);
	sbh->sbh_tgt = tgt;

	bh = &lbh->lbh_super;
	child_bh = md_batch_create(tgt->ltd_exp, bh->lbt_flags,
				   bh->lbt_max_count);
	if (IS_ERR(child_bh)) {
		OBD_FREE_PTR(sbh);
		RETURN(child_bh);
	}

	child_bh->lbt_rqset = bh->lbt_rqset;
	sbh->sbh_sub = child_bh;
	list_add(&sbh->sbh_sub_item, &lbh->lbh_sub_batch_list);
	RETURN(child_bh);
}

static int lmv_batch_add(struct obd_export *exp, struct lu_batch *bh,
			 struct md_op_item *item)
{
	struct obd_device *obd = exp->exp_obd;
	struct lmv_obd *lmv = &obd->u.lmv;
	struct lmv_tgt_desc *tgt;
	struct lmv_batch *lbh;
	struct lu_batch *child_bh;
	int rc;

	ENTRY;

	tgt = lmv_batch_locate_tgt(lmv, item);
	if (IS_ERR(tgt))
		RETURN(PTR_ERR(tgt));

	lbh = container_of(bh, struct lmv_batch, lbh_super);
	child_bh = lmv_batch_get_sub(lbh, tgt);
	if (IS_ERR(child_bh))
		RETURN(PTR_ERR(child_bh));

	rc = md_batch_add(tgt->ltd_exp, child_bh, item);
	RETURN(rc);
}

static int lmv_dirpage_add(struct obd_export *exp,
			   struct inode *inode,
			   struct folio **pool,
			   unsigned int cfs_pgs,
			   unsigned int lu_pgs, int is_hash64)
{
	struct lmv_obd *lmv = &exp->exp_obd->u.lmv;
	struct lmv_tgt_desc *tgt = lmv_tgt(lmv, 0);
	int rc;

	ENTRY;

	if (tgt == NULL || tgt->ltd_exp == NULL)
		RETURN(-EINVAL);
	rc =  md_dirpage_add(tgt->ltd_exp,
			     inode, pool, cfs_pgs, lu_pgs, is_hash64);
	RETURN(rc);
}

static const struct obd_ops lmv_obd_ops = {
	.o_owner                = THIS_MODULE,
	.o_connect              = lmv_connect,
	.o_disconnect           = lmv_disconnect,
	.o_statfs               = lmv_statfs,
	.o_get_info             = lmv_get_info,
	.o_set_info_async       = lmv_set_info_async,
	.o_notify               = lmv_notify,
	.o_get_uuid             = lmv_get_uuid,
	.o_fid_alloc		= lmv_fid_alloc,
	.o_iocontrol            = lmv_iocontrol,
	.o_quotactl             = lmv_quotactl
};

static const struct md_ops lmv_md_ops = {
	.m_get_root		= lmv_get_root,
	.m_null_inode		= lmv_null_inode,
	.m_close                = lmv_close,
	.m_create               = lmv_create,
	.m_enqueue              = lmv_enqueue,
	.m_enqueue_async        = lmv_enqueue_async,
	.m_getattr              = lmv_getattr,
	.m_getxattr             = lmv_getxattr,
	.m_getattr_name         = lmv_getattr_name,
	.m_intent_lock          = lmv_intent_lock,
	.m_link                 = lmv_link,
	.m_rename               = lmv_rename,
	.m_setattr              = lmv_setattr,
	.m_setxattr             = lmv_setxattr,
	.m_fsync		= lmv_fsync,
	.m_file_resync		= lmv_file_resync,
	.m_read_page		= lmv_read_page,
	.m_unlink               = lmv_unlink,
	.m_init_ea_size         = lmv_init_ea_size,
	.m_cancel_unused        = lmv_cancel_unused,
	.m_set_lock_data        = lmv_set_lock_data,
	.m_lock_match           = lmv_lock_match,
	.m_get_lustre_md        = lmv_get_lustre_md,
	.m_put_lustre_md        = lmv_put_lustre_md,
	.m_merge_attr		= lmv_merge_attr,
	.m_set_open_replay_data = lmv_set_open_replay_data,
	.m_clear_open_replay_data = lmv_clear_open_replay_data,
	.m_intent_getattr_async = lmv_intent_getattr_async,
	.m_revalidate_lock      = lmv_revalidate_lock,
	.m_get_fid_from_lsm	= lmv_get_fid_from_lsm,
	.m_stripe_object_create	= lmv_stripe_object_create,
	.m_rmfid		= lmv_rmfid,
	.m_batch_create		= lmv_batch_create,
	.m_batch_add		= lmv_batch_add,
	.m_batch_stop		= lmv_batch_stop,
	.m_batch_flush		= lmv_batch_flush,
	.m_dirpage_add		= lmv_dirpage_add,
};

static const struct lu_device_operations lmv_lu_ops = {
	.ldo_process_config    = lmv_process_config,
};

static const struct lu_device_type_operations lmv_type_ops = {
	.ldto_device_alloc	= lmv_device_alloc,
	.ldto_device_free	= lmv_device_free,
	.ldto_device_fini	= lmv_device_fini,
};

static struct lu_device_type lmv_device_type = {
	.ldt_tags     = LU_DEVICE_MISC,
	.ldt_name     = LUSTRE_LMV_NAME,
	.ldt_ops      = &lmv_type_ops,
	.ldt_ctx_tags = LCT_LOCAL
};

static int __init lmv_init(void)
{
	int rc;

	rc  = libcfs_setup();
	if (rc)
		return rc;

	return class_register_type(&lmv_obd_ops, &lmv_md_ops, true,
				   LUSTRE_LMV_NAME, &lmv_device_type);
}

static void __exit lmv_exit(void)
{
	class_unregister_type(LUSTRE_LMV_NAME);
}

MODULE_AUTHOR("OpenSFS, Inc. <http://www.lustre.org/>");
MODULE_DESCRIPTION("Lustre Logical Metadata Volume");
MODULE_VERSION(LUSTRE_VERSION_STRING);
MODULE_LICENSE("GPL");

late_initcall_sync(lmv_init);
module_exit(lmv_exit);