Viewing: mdd_dir.c

// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2007, 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/
 *
 * Lustre Metadata Server (mdd) routines
 *
 * Author: Wang Di <wangdi@intel.com>
 */

#define DEBUG_SUBSYSTEM S_MDS

#include <obd_class.h>
#include <obd_support.h>
#include <lustre_mds.h>
#include <lustre_fid.h>
#include <lustre_lmv.h>
#include <lustre_idmap.h>
#include <lustre_crypto.h>
#include <uapi/linux/lustre/lgss.h>

#include "mdd_internal.h"

static const char dot[] = ".";
static const char dotdot[] = "..";

static struct lu_name lname_dotdot = {
	.ln_name	= (char *) dotdot,
	.ln_namelen	= sizeof(dotdot) - 1,
};

static inline int
mdd_name_check(const struct lu_env *env, struct mdd_device *m,
	       const struct lu_name *ln)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	bool enc = info->mdi_pattr.la_valid & LA_FLAGS &&
		info->mdi_pattr.la_flags & LUSTRE_ENCRYPT_FL;

	if (!lu_name_is_valid(ln))
		return -EINVAL;
	else if (!enc && ln->ln_namelen > m->mdd_dt_conf.ddp_max_name_len)
		return -ENAMETOOLONG;
	else
		return 0;
}

/* Get FID from name and parent */
static int
__mdd_lookup(const struct lu_env *env, struct md_object *pobj,
	     const struct lu_attr *pattr, const struct lu_name *lname,
	     struct lu_fid *fid, unsigned int may_mask)
{
	const char *name = lname->ln_name;
	const struct dt_key *key = (const struct dt_key *)name;
	struct mdd_object *mdd_obj = md2mdd_obj(pobj);
	struct dt_object *dir = mdd_object_child(mdd_obj);
	int rc;

	ENTRY;

	if (unlikely(mdd_is_dead_obj(mdd_obj)))
		RETURN(-ESTALE);

	if (!mdd_object_exists(mdd_obj))
		RETURN(-ESTALE);

	if (mdd_object_remote(mdd_obj)) {
		CDEBUG(D_INFO, "%s: Object "DFID" located on remote server\n",
		       mdd_obj_dev_name(mdd_obj),
		       PFID(mdd_object_fid(mdd_obj)));
	}

	rc = mdd_permission_internal_locked(env, mdd_obj, pattr, may_mask,
					    DT_TGT_PARENT);
	if (rc)
		RETURN(rc);

	if (likely(dt_try_as_dir(env, dir, true)))
		rc = dt_lookup(env, dir, (struct dt_rec *)fid, key);
	else
		rc = -ENOTDIR;

	RETURN(rc);
}

int mdd_lookup(const struct lu_env *env,
	       struct md_object *pobj, const struct lu_name *lname,
	       struct lu_fid *fid, struct md_op_spec *spec)
{
	struct lu_attr *pattr = MDD_ENV_VAR(env, pattr);
	int rc;

	ENTRY;

	rc = mdd_la_get(env, md2mdd_obj(pobj), pattr);
	if (rc != 0)
		RETURN(rc);

	rc = __mdd_lookup(env, pobj, pattr, lname, fid,
			  (spec != NULL && spec->sp_permitted) ? 0 : MAY_EXEC);
	RETURN(rc);
}

/** Read the link EA into a temp buffer.
 * Uses the mdd_thread_info::mdi_link_buf since it is generally large.
 * A pointer to the buffer is stored in \a ldata::ld_buf.
 *
 * \retval 0 or error
 */
static int __mdd_links_read(const struct lu_env *env,
			    struct mdd_object *mdd_obj,
			    struct linkea_data *ldata)
{
	int rc;

	if (!mdd_object_exists(mdd_obj))
		return -ENODATA;

	/* First try a small buf */
	LASSERT(env != NULL);
	ldata->ld_buf = lu_buf_check_and_alloc(&mdd_env_info(env)->mdi_link_buf,
					       PAGE_SIZE);
	if (ldata->ld_buf->lb_buf == NULL)
		return -ENOMEM;

	rc = mdo_xattr_get(env, mdd_obj, ldata->ld_buf, XATTR_NAME_LINK);
	if (rc == -ERANGE) {
		/* Buf was too small, figure out what we need. */
		lu_buf_free(ldata->ld_buf);
		rc = mdo_xattr_get(env, mdd_obj, ldata->ld_buf,
				   XATTR_NAME_LINK);
		if (rc < 0)
			return rc;
		ldata->ld_buf = lu_buf_check_and_alloc(ldata->ld_buf, rc);
		if (ldata->ld_buf->lb_buf == NULL)
			return -ENOMEM;
		rc = mdo_xattr_get(env, mdd_obj, ldata->ld_buf,
				  XATTR_NAME_LINK);
	}
	if (rc < 0) {
		lu_buf_free(ldata->ld_buf);
		ldata->ld_buf = NULL;
		return rc;
	}

	return linkea_init(ldata);
}

int mdd_links_read(const struct lu_env *env,
		   struct mdd_object *mdd_obj,
		   struct linkea_data *ldata)
{
	int rc;

	rc = __mdd_links_read(env, mdd_obj, ldata);
	if (!rc)
		rc = linkea_init(ldata);

	return rc;
}

static int mdd_links_read_with_rec(const struct lu_env *env,
				   struct mdd_object *mdd_obj,
				   struct linkea_data *ldata)
{
	int rc;

	rc = __mdd_links_read(env, mdd_obj, ldata);
	if (!rc)
		rc = linkea_init_with_rec(ldata);

	return rc;
}

/**
 * mdd_parent_fid() - Get parent FID of the directory
 * @env: execution environment
 * @obj: object from which to find the parent FID
 * @attr: attribute of the object
 * @fid: fid to get the parent FID [out]
 *
 * Read parent FID from linkEA, if that fails, then do lookup
 * dotdot to get the parent FID.
 *
 * Return:
 * * %0 if getting the parent FID succeeds.
 * * %negative errno if getting the parent FID fails.
 */
static inline int mdd_parent_fid(const struct lu_env *env,
				 struct mdd_object *obj,
				 const struct lu_attr *attr,
				 struct lu_fid *fid)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct linkea_data ldata = { NULL };
	struct lu_buf *buf = &info->mdi_link_buf;
	struct lu_name lname;
	int rc = 0;

	ENTRY;

	CDEBUG(D_INFO, "find parent for "DFID"\n", PFID(mdd_object_fid(obj)));
	LASSERTF(S_ISDIR(mdd_object_type(obj)),
		 "%s: FID "DFID" is not a directory type = %o\n",
		 mdd_obj_dev_name(obj), PFID(mdd_object_fid(obj)),
		 mdd_object_type(obj));

	buf = lu_buf_check_and_alloc(buf, PATH_MAX);
	if (buf->lb_buf == NULL)
		GOTO(lookup, rc = 0);

	ldata.ld_buf = buf;
	rc = mdd_links_read_with_rec(env, obj, &ldata);
	if (rc != 0)
		GOTO(lookup, rc);

	/* the obj is not locked, don't cache attributes */
	mdd_invalidate(env, &obj->mod_obj);

	LASSERT(ldata.ld_leh != NULL);
	/* Directory should only have 1 parent */
	if (ldata.ld_leh->leh_reccount > 1)
		GOTO(lookup, rc);

	ldata.ld_lee = (struct link_ea_entry *)(ldata.ld_leh + 1);

	linkea_entry_unpack(ldata.ld_lee, &ldata.ld_reclen, &lname, fid);
	if (likely(fid_is_sane(fid)))
		RETURN(0);
lookup:
	rc =  __mdd_lookup(env, &obj->mod_obj, attr, &lname_dotdot, fid, 0);
	RETURN(rc);
}

/*
 * For root fid use special function, which does not compare version component
 * of fid. Version component is different for root fids on all MDTs.
 */
int mdd_is_root(struct mdd_device *mdd, const struct lu_fid *fid)
{
	return fid_seq(&mdd->mdd_root_fid) == fid_seq(fid) &&
		fid_oid(&mdd->mdd_root_fid) == fid_oid(fid);
}

/*
 * return 1: if @tfid is the FID of the ancestor of @mo;
 * return 0: if they are the same FID or in a different subtree;
 * otherwise: values < 0, errors.
 */
static int mdd_is_parent(const struct lu_env *env,
			struct mdd_device *mdd,
			struct mdd_object *mo,
			const struct lu_attr *attr,
			const struct lu_fid *tfid)
{
	static bool dumped;
	const struct lu_fid *mofid;
	struct lu_fid *pfid;
	int count;
	int rc;

	mofid = mdd_object_fid(mo);
	CDEBUG(D_INFO, "%s: check if "DFID" is a child of "DFID"\n",
		mdd2obd_dev(mdd)->obd_name, PFID(mofid), PFID(tfid));
	if (lu_fid_eq(mofid, tfid))
		return 0;

	if (mdd_is_root(mdd, mofid))
		return 0;

	if (mdd_is_root(mdd, tfid))
		return 1;

	pfid = &mdd_env_info(env)->mdi_fid;
	rc = mdd_parent_fid(env, mo, attr, pfid);
	if (rc)
		return rc;

	/* PATH_MAX / 2 would be the maximum "normal" iteration limit for a
	 * a directory tree with single-character names "a/b/c/d/...", but
	 * let's be accepting of potential subdirectory mount trees that may
	 * exceed the normal pathname limits (this has been seen before).
	 * The most important thing is not the actual limit, but that the
	 * loop iteration is bounded (LU-12800, LU-11218, LU-10406).
	 */
	for (count = 0; count < PATH_MAX; count++) {
		struct mdd_object *mp;

		if (lu_fid_eq(pfid, tfid))
			return 1;

		if (mdd_is_root(mdd, pfid))
			return 0;

		mp = mdd_object_find(env, mdd, pfid);
		if (IS_ERR(mp))
			return PTR_ERR(mp);

		if (!mdd_object_exists(mp)) {
			mdd_object_put(env, mp);
			return -ENOENT;
		}

		rc = mdd_parent_fid(env, mp, attr, pfid);
		mdd_object_put(env, mp);
		if (rc)
			return rc;
	}

	rc = -ELOOP;
	CERROR("%s: walk from "DFID" to "DFID" stuck at "DFID": rc = %d\n",
	       mdd2obd_dev(mdd)->obd_name, PFID(mofid), PFID(tfid), PFID(pfid),
	       rc);
	if (!dumped) {
		dumped = true;
		libcfs_debug_dumplog();
	}
	return rc;
}

/*
 * No permission check is needed.
 *
 * returns 1: if fid is ancestor of @mo;
 * returns 0: if fid is not an ancestor of @mo;
 * returns < 0: if error
 */
static int mdd_is_subdir(const struct lu_env *env, struct md_object *mo,
			 const struct lu_fid *fid)
{
	struct mdd_device *mdd = mdo2mdd(mo);
	struct lu_attr *attr = MDD_ENV_VAR(env, cattr);
	int rc;

	ENTRY;

	if (!mdd_object_exists(md2mdd_obj(mo)))
		RETURN(-ENOENT);

	if (!S_ISDIR(mdd_object_type(md2mdd_obj(mo))))
		RETURN(-ENOTDIR);

	rc = mdd_la_get(env, md2mdd_obj(mo), attr);
	if (rc != 0)
		RETURN(rc);

	rc = mdd_is_parent(env, mdd, md2mdd_obj(mo), attr, fid);
	RETURN(rc);
}

/**
 * mdd_dir_is_empty() - Check that @dir contains no entries except (possibly)
 * dot and dotdot.
 * @env: execution environment
 * @dir: dir object to check for emptyness
 *
 * Returns:
 * * %0 on empty
 * * %-ENOTDIR not a directory object
 * * %-ENOTEMPTY not empty
 * * %negative other error
 */
int mdd_dir_is_empty(const struct lu_env *env, struct mdd_object *dir)
{
	struct dt_it     *it;
	struct dt_object *obj;
	const struct dt_it_ops *iops;
	int result;

	ENTRY;

	obj = mdd_object_child(dir);
	if (!dt_try_as_dir(env, obj, true))
		RETURN(-ENOTDIR);

	iops = &obj->do_index_ops->dio_it;
	it = iops->init(env, obj, LUDA_64BITHASH);
	if (!IS_ERR(it)) {
		result = iops->get(env, it, (const struct dt_key *)"");
		if (result > 0) {
			int i;

			for (result = 0, i = 0; result == 0 && i < 3; ++i)
				result = iops->next(env, it);
			if (result == 0)
				result = -ENOTEMPTY;
			else if (result == 1)
				result = 0;
		} else if (result == 0)
			/*
			 * Huh? Index contains no zero key?
			 */
			result = -EIO;

		iops->put(env, it);
		iops->fini(env, it);
	} else {
		result = PTR_ERR(it);
		/* -ENODEV means no valid stripe */
		if (result == -ENODEV)
			RETURN(0);
	}
	RETURN(result);
}

/**
 * __mdd_may_link() - Determine if the target object can be hard linked
 * @env: thread environment
 * @obj: object being linked to
 * @la: attributes of @obj
 *
 * Determine if the target object can be hard linked, and right now it only
 * checks if the link count reach the maximum limit. Note: for ldiskfs, the
 * directory nlink count might exceed the maximum link count(see
 * osd_object_ref_add), so it only check nlink for non-directories.
 *
 * Return:
 * * %0 if @obj can be hard linked
 * * %negative error if @obj is a directory or has too many links
 */
static int __mdd_may_link(const struct lu_env *env, struct mdd_object *obj,
			  const struct lu_attr *la)
{
	struct mdd_device *m = mdd_obj2mdd_dev(obj);

	ENTRY;

	LASSERT(la != NULL);

	/* Subdir count limitation can be broken through
	 * (see osd_object_ref_add), so only check non-directory here.
	 */
	if (!S_ISDIR(la->la_mode) &&
	    la->la_nlink >= m->mdd_dt_conf.ddp_max_nlink)
		RETURN(-EMLINK);

	RETURN(0);
}

/**
 * mdd_may_create() - Check whether it may create the cobj under the pobj.
 * @env: execution environment
 * @pobj: the parent directory
 * @pattr: the attribute of the parent directory
 * @cobj: the child to be created
 * @check_perm: if check WRITE|EXEC permission for parent
 *
 * Return:
 * * %0 create the child under this dir is allowed
 * * %negative errno create the child under this dir is not allowed
 */
int mdd_may_create(const struct lu_env *env, struct mdd_object *pobj,
		   const struct lu_attr *pattr, struct mdd_object *cobj,
		   bool check_perm)
{
	int rc = 0;

	ENTRY;

	if (cobj && mdd_object_exists(cobj))
		RETURN(-EEXIST);

	if (mdd_is_dead_obj(pobj))
		RETURN(-ENOENT);

	if (check_perm)
		rc = mdd_permission_internal_locked(env, pobj, pattr,
						    MAY_WRITE | MAY_EXEC,
						    DT_TGT_PARENT);
	RETURN(rc);
}

/* pobj == NULL is remote ops case, under such case, pobj's
 * VTX feature has been checked already, no need check again.
 */
static inline int mdd_is_sticky(const struct lu_env *env,
				struct mdd_object *pobj,
				const struct lu_attr *pattr,
				struct mdd_object *cobj,
				const struct lu_attr *cattr)
{
	struct lu_ucred *uc = lu_ucred_assert(env);

	if (pobj != NULL) {
		LASSERT(pattr != NULL);
		if (!(pattr->la_mode & S_ISVTX) ||
		    (pattr->la_uid == uc->uc_fsuid))
			return 0;
	}

	LASSERT(cattr != NULL);
	if (cattr->la_uid == uc->uc_fsuid)
		return 0;

	return !cap_raised(uc->uc_cap, CAP_FOWNER);
}

static int mdd_may_delete_entry(const struct lu_env *env,
				struct mdd_object *pobj,
				const struct lu_attr *pattr,
				int check_perm)
{
	ENTRY;

	LASSERT(pobj != NULL);
	if (!mdd_object_exists(pobj))
		RETURN(-ENOENT);

	if (mdd_is_dead_obj(pobj))
		RETURN(-ENOENT);

	if (check_perm) {
		int rc;

		rc = mdd_permission_internal_locked(env, pobj, pattr,
					    MAY_WRITE | MAY_EXEC,
					    DT_TGT_PARENT);
		if (rc)
			RETURN(rc);
	}

	if (pattr->la_flags & LUSTRE_APPEND_FL)
		RETURN(-EPERM);

	RETURN(0);
}

/*
 * Check whether it may delete the cobj from the pobj.
 * pobj maybe NULL
 */
int mdd_may_delete(const struct lu_env *env, struct mdd_object *tpobj,
		   const struct lu_attr *tpattr, struct mdd_object *tobj,
		   const struct lu_attr *tattr, const struct lu_attr *cattr,
		   int check_perm, int check_empty)
{
	int rc = 0;

	ENTRY;

	if (tpobj) {
		LASSERT(tpattr != NULL);
		rc = mdd_may_delete_entry(env, tpobj, tpattr, check_perm);
		if (rc != 0)
			RETURN(rc);
	}

	if (tobj == NULL)
		RETURN(0);

	if (!mdd_object_exists(tobj))
		RETURN(-ENOENT);

	if (mdd_is_dead_obj(tobj))
		RETURN(-ESTALE);

	if (mdd_is_sticky(env, tpobj, tpattr, tobj, tattr))
		RETURN(-EPERM);

	if (tattr->la_flags & (LUSTRE_APPEND_FL | LUSTRE_IMMUTABLE_FL))
		RETURN(-EPERM);

	/* additional check the rename case */
	if (cattr) {
		if (S_ISDIR(cattr->la_mode)) {
			if (!S_ISDIR(tattr->la_mode))
				RETURN(-ENOTDIR);

			if (mdd_is_root(mdo2mdd(&tobj->mod_obj),
					mdd_object_fid(tobj)))
				RETURN(-EBUSY);
		} else if (S_ISDIR(tattr->la_mode))
			RETURN(-EISDIR);
	}

	if (S_ISDIR(tattr->la_mode) && check_empty)
		rc = mdd_dir_is_empty(env, tobj);

	RETURN(rc);
}

/**
 * mdd_link_sanity_check() - Check whether it can create the link file
 * @env: execution environment
 * @tgt_obj: the target directory
 * @tattr: attributes of target directory
 * @lname: the link name
 * @src_obj: source object for link
 * @cattr: attributes for source object
 *
 * Check whether it can create the link file(linked to @src_obj) under
 * the target directory(@tgt_obj), and src_obj has been locked by
 * mdd_write_lock.
 *
 * Return:
 * * %0 it is allowed to create the link file under @tgt_obj
 * * %negative error not allowed to create the link file
 */
static int mdd_link_sanity_check(const struct lu_env *env,
				 struct mdd_object *tgt_obj,
				 const struct lu_attr *tattr,
				 const struct lu_name *lname,
				 struct mdd_object *src_obj,
				 const struct lu_attr *cattr)
{
	struct mdd_device *m = mdd_obj2mdd_dev(src_obj);
	int rc = 0;

	ENTRY;

	if (!mdd_object_exists(src_obj))
		RETURN(-ENOENT);

	if (mdd_is_dead_obj(src_obj))
		RETURN(-ESTALE);

	/* Local ops, no lookup before link, check filename length here. */
	rc = mdd_name_check(env, m, lname);
	if (rc < 0)
		RETURN(rc);

	if (cattr->la_flags & (LUSTRE_IMMUTABLE_FL | LUSTRE_APPEND_FL))
		RETURN(-EPERM);

	if (S_ISDIR(mdd_object_type(src_obj)))
		RETURN(-EPERM);

	LASSERT(src_obj != tgt_obj);
	rc = mdd_may_create(env, tgt_obj, tattr, NULL, true);
	if (rc != 0)
		RETURN(rc);

	rc = __mdd_may_link(env, src_obj, cattr);

	RETURN(rc);
}

static int __mdd_index_delete_only(const struct lu_env *env,
				   struct mdd_object *pobj,
				   const char *name, struct thandle *handle)
{
	struct dt_object *next = mdd_object_child(pobj);
	int rc;

	ENTRY;

	if (dt_try_as_dir(env, next, true))
		rc = dt_delete(env, next, (struct dt_key *)name, handle);
	else
		rc = -ENOTDIR;

	RETURN(rc);
}

static int __mdd_index_insert_only(const struct lu_env *env,
				   struct mdd_object *pobj,
				   const struct lu_fid *lf, __u32 type,
				   const char *name, struct thandle *handle)
{
	struct dt_object *next = mdd_object_child(pobj);
	int rc;

	ENTRY;

	if (dt_try_as_dir(env, next, true)) {
		struct dt_insert_rec *rec = &mdd_env_info(env)->mdi_dt_rec;

		rec->rec_fid = lf;
		rec->rec_type = type;
		rc = dt_insert(env, next, (const struct dt_rec *)rec,
			       (const struct dt_key *)name, handle);
	} else {
		rc = -ENOTDIR;
	}
	RETURN(rc);
}

/* insert named index, add reference if isdir */
static int __mdd_index_insert(const struct lu_env *env, struct mdd_object *pobj,
			      const struct lu_fid *lf, __u32 type,
			      const char *name, struct thandle *handle)
{
	int rc;

	ENTRY;

	rc = __mdd_index_insert_only(env, pobj, lf, type, name, handle);
	if (rc == 0 && S_ISDIR(type)) {
		mdd_write_lock(env, pobj, DT_TGT_PARENT);
		mdo_ref_add(env, pobj, handle);
		mdd_write_unlock(env, pobj);
	}

	RETURN(rc);
}

/* delete named index, drop reference if isdir */
static int __mdd_index_delete(const struct lu_env *env, struct mdd_object *pobj,
			      const char *name, int is_dir,
			      struct thandle *handle)
{
	int rc;

	ENTRY;

	rc = __mdd_index_delete_only(env, pobj, name, handle);
	if (rc == 0 && is_dir) {
		mdd_write_lock(env, pobj, DT_TGT_PARENT);
		mdo_ref_del(env, pobj, handle);
		mdd_write_unlock(env, pobj);
	}

	RETURN(rc);
}

static int mdd_llog_record_calc_size(const struct lu_env *env,
				     const struct lu_name *tname,
				     const struct lu_name *sname)
{
	const struct lu_ucred	*uc = lu_ucred(env);
	enum changelog_rec_flags clf_flags = CLF_EXTRA_FLAGS;
	enum changelog_rec_extra_flags crfe = CLFE_UIDGID | CLFE_NID;

	if (sname != NULL)
		clf_flags |= CLF_RENAME;

	if (uc != NULL && uc->uc_jobid[0] != '\0')
		clf_flags |= CLF_JOBID;

	return llog_data_len(LLOG_CHANGELOG_HDR_SZ +
			     changelog_rec_offset(clf_flags, crfe) +
			     (tname != NULL ? tname->ln_namelen : 0) +
			     (sname != NULL ? 1 + sname->ln_namelen : 0));
}

int mdd_declare_changelog_store(const struct lu_env *env,
				struct mdd_device *mdd,
				enum changelog_rec_type type,
				const struct lu_name *tname,
				const struct lu_name *sname,
				struct thandle *handle)
{
	struct obd_device *obd = mdd2obd_dev(mdd);
	struct llog_ctxt *ctxt;
	struct llog_rec_hdr rec_hdr;
	struct thandle *llog_th;
	int rc;

	if (!mdd_changelog_enabled(env, mdd, type))
		return 0;

	rec_hdr.lrh_len = mdd_llog_record_calc_size(env, tname, sname);
	rec_hdr.lrh_type = CHANGELOG_REC;

	ctxt = llog_get_context(obd, LLOG_CHANGELOG_ORIG_CTXT);
	if (ctxt == NULL)
		return -ENXIO;

	llog_th = thandle_get_sub(env, handle, ctxt->loc_handle->lgh_obj);
	if (IS_ERR(llog_th))
		GOTO(out_put, rc = PTR_ERR(llog_th));

	mdd_env_info(env)->mdi_chlog_declared = 1;
	rc = llog_declare_add(env, ctxt->loc_handle, &rec_hdr, llog_th);

out_put:
	llog_ctxt_put(ctxt);

	return rc;
}

/* The locking here is a bit tricky. For a CHANGELOG_REC the function
 * drops loghandle->lgh_lock for a performance reasons. All dt_write()
 * are used own offset, so it is safe.
 * For other records general function is called and it doesnot drop
 * a semaphore. The callers are changelog catalog records and initialisation
 * records. llog_cat_new_log->llog_write_rec->mdd_changelog_write_rec()
 *
 * Since dt_record_write() could be reordered, rec1|rec2|0x0|rec4 could be
 * at memory, reader should care about it. When the th is commited it is
 * impossible to have a hole, since reordered records have the same th.
 */
int mdd_changelog_write_rec(const struct lu_env *env,
			    struct llog_handle *loghandle,
			    struct llog_rec_hdr *r,
			    struct llog_cookie *cookie,
			    int idx, struct thandle *th)
{
	int rc;
	static struct thandle *saved_th;

	CDEBUG(D_TRACE, "Adding rec %u type %u to "DFID" flags %x count %d\n",
	       idx, r->lrh_type, PLOGID(&loghandle->lgh_id),
	       loghandle->lgh_hdr->llh_flags, loghandle->lgh_hdr->llh_count);

	if (r->lrh_type == CHANGELOG_REC) {
		struct mdd_device *mdd;
		struct llog_changelog_rec *rec;
		size_t left;
		__u32 chunk_size = loghandle->lgh_hdr->llh_hdr.lrh_len;
		struct dt_object *o = loghandle->lgh_obj;
		loff_t offset;
		struct lu_buf lgi_buf;

		left = chunk_size - (loghandle->lgh_cur_offset &
				     (chunk_size - 1));

		mdd = lu2mdd_dev(loghandle->lgh_ctxt->loc_obd->obd_lu_dev);
		rec = container_of(r, struct llog_changelog_rec, cr_hdr);

		/* Don't use padding records because it require a slot at header
		 * so previous result of checking llog_is_full(loghandle)
		 * would be invalid, leave zeroes at the end of block.
		 * A reader would care about it.
		 */
		if (left != 0 && left < r->lrh_len)
			loghandle->lgh_cur_offset += left;

		offset = loghandle->lgh_cur_offset;
		loghandle->lgh_cur_offset += r->lrh_len;
		r->lrh_index = ++loghandle->lgh_last_idx;

		spin_lock(&mdd->mdd_cl.mc_lock);
		rec->cr.cr_index = ++mdd->mdd_cl.mc_index;
		spin_unlock(&mdd->mdd_cl.mc_lock);

		/* drop the loghandle semaphore for parallel writes */
		up_write(&loghandle->lgh_lock);

		REC_TAIL(r)->lrt_len = r->lrh_len;
		REC_TAIL(r)->lrt_index = r->lrh_index;

		lgi_buf.lb_len = rec->cr_hdr.lrh_len;
		lgi_buf.lb_buf = rec;

		if (CFS_FAIL_CHECK(OBD_FAIL_MDS_CHANGELOG_FAIL_WRITE) &&
		    (rec->cr.cr_index % (cfs_fail_val + 1)) == 0)
			rc = -EIO;
		else
			rc = dt_record_write(env, o, &lgi_buf, &offset, th);

		if (rc) {
			CERROR("%s: failed to write changelog record file "DFID" rec idx %u off %llu chnlg idx %llu: rc = %d\n",
			       loghandle->lgh_ctxt->loc_obd->obd_name,
			       PFID(lu_object_fid(&o->do_lu)), r->lrh_index,
			       offset, rec->cr.cr_index, rc);
			return rc;
		}

		/* mark index at bitmap after successful write, increment count,
		 * and lrt_index with a last index. Use a lgh_hdr_lock for
		 * a synchronization with llog_cancel.
		 */
		spin_lock(&loghandle->lgh_hdr_lock);
		rc = __test_and_set_bit_le(r->lrh_index,
					   LLOG_HDR_BITMAP(loghandle->lgh_hdr));
		LASSERTF(!rc,
			 "%s: index %u already set in llog bitmap "DFID"\n",
			 loghandle->lgh_ctxt->loc_obd->obd_name,
			 r->lrh_index, PLOGID(&loghandle->lgh_id));
		loghandle->lgh_hdr->llh_count++;
		if (LLOG_HDR_TAIL(loghandle->lgh_hdr)->lrt_index < r->lrh_index)
			LLOG_HDR_TAIL(loghandle->lgh_hdr)->lrt_index =
				r->lrh_index;
		spin_unlock(&loghandle->lgh_hdr_lock);

		if (unlikely(th != saved_th)) {
			CDEBUG(D_OTHER, "%s: wrote rec %u "DFID" count %d\n",
			       loghandle->lgh_ctxt->loc_obd->obd_name,
			       r->lrh_index, PLOGID(&loghandle->lgh_id),
			       loghandle->lgh_hdr->llh_count);
			saved_th = th;
		}
		lgi_buf.lb_len = loghandle->lgh_hdr_size;
		lgi_buf.lb_buf = loghandle->lgh_hdr;
		offset = 0;
		CDEBUG(D_TRACE, "%s: writing header "DFID"\n",
		       loghandle->lgh_ctxt->loc_obd->obd_name,
		       PLOGID(&loghandle->lgh_id));
		/* full header write, it is a local. For a mapped bh
		 * it is memcpy() only. Probably it could be delayed as work.
		 */
		rc = dt_record_write(env, o, &lgi_buf, &offset, th);
	} else {
		rc = llog_osd_ops.lop_write_rec(env, loghandle, r,
						cookie, idx, th);
	}
	if (rc < 0)
		CERROR("%s: failed to write changelog record file "DFID" count %d offset %llu: rc = %d\n",
		       loghandle->lgh_ctxt->loc_obd->obd_name,
		       PLOGID(&loghandle->lgh_id),
		       loghandle->lgh_hdr->llh_count, loghandle->lgh_cur_offset,
		       rc);

	return rc;
}

/**
 * mdd_changelog_is_space_safe() - Checks that changelog consumes safe amount of
 * space comparing with FS free space
 * @env: current lu_env
 * @mdd: current MDD device
 * @lgh: changelog catalog llog handle
 * @estimate: get exact llog size or estimate it.
 *
 * Return:
 * * %true on success
 * * %false on failure
 */
bool mdd_changelog_is_space_safe(const struct lu_env *env,
				 struct mdd_device *mdd,
				 struct llog_handle *lgh,
				 bool estimate)
{
	struct obd_statfs sfs;
	unsigned long long free_space_limit;
	unsigned long long llog_size;
	int rc;

	rc = dt_statfs(env, mdd->mdd_bottom, &sfs);
	if (rc)
		/* check is ignored if OSD is not healthy for any reason */
		return true;

	/* if changelog consumes more than 1/4 of available space then start
	 * emergency cleanup.
	 */
	if (CFS_FAIL_CHECK(OBD_FAIL_MDS_CHANGELOG_ENOSPC))
		free_space_limit = cfs_fail_val;
	else
		free_space_limit = (sfs.os_bfree * sfs.os_bsize) >> 2;

	/* if \estimate parameter is used then calculate llog size from
	 * number of used catalog entries and plain llog maximum size.
	 * Plain llog maximum size if set as 1/64 of FS free space limited
	 * by 128MB as maximum and 2MB as minimum, see llog_cat_new_log()
	 * Estimation helps to avoid full llog processing to get exact size
	 * by llog_cat_size().
	 */
	if (estimate) {
		/* use 1/64 of FS size but keep it between 2MB and 128MB */
		llog_size = clamp_t(unsigned long long,
				    (sfs.os_blocks * sfs.os_bsize) >> 6,
				    2 << 20, 128 << 20);
		/* amount of plain llogs in use plus catalog itself */
		llog_size *= lgh->lgh_hdr->llh_count + 1;
	} else {
		/* get exact llog size */
		llog_size = llog_cat_size(env, lgh);
	}

	if (llog_size <= free_space_limit)
		return true;

	CDEBUG_LIMIT(estimate ? D_HA : D_WARNING,
		     "%s:%s changelog size %lluMB with %lluMB space limit\n",
		     mdd2obd_dev(mdd)->obd_name, estimate ? " estimated" : "",
		     llog_size >> 20, free_space_limit >> 20);

	return false;
}

/**
 * mdd_changelog_emrg_cleanup() - Checks if there is enough space in changelog
 * @env: current lu_env
 * @mdd: current MDD device
 * @lgh: changelog catalog llog handle
 *
 * Checks if there is enough space in changelog itself and in FS and force
 * emergency changelog cleanup if needed. It will purge users one by one
 * from the oldest one while emergency conditions are true.
 *
 * Return %true if emergency cleanup is needed for changelog
 */
static bool mdd_changelog_emrg_cleanup(const struct lu_env *env,
				       struct mdd_device *mdd,
				       struct llog_handle *lgh)
{
	unsigned long free_entries = llog_cat_free_space(lgh);

	/* free space GC is disabled or is in progress already */
	if (!mdd->mdd_changelog_free_space_gc || mdd->mdd_changelog_emrg_gc)
		return false;

	if (free_entries <= mdd->mdd_changelog_min_free_cat_entries) {
		CWARN("%s: changelog has only %lu free catalog entries\n",
		      mdd2obd_dev(mdd)->obd_name, free_entries);
		mdd->mdd_changelog_emrg_gc = true;
		return true;
	}

	if (!mdd_changelog_is_space_safe(env, mdd, lgh, true)) {
		mdd->mdd_changelog_emrg_gc = true;
		return true;
	}

	return false;
}

static bool mdd_changelog_need_gc(const struct lu_env *env,
				  struct mdd_device *mdd,
				  struct llog_handle *lgh)
{
	struct mdd_changelog *mc = &mdd->mdd_cl;

	return mdd_changelog_emrg_cleanup(env, mdd, lgh) ||
	       mdd_changelog_is_too_idle(mdd, mc->mc_minrec, mc->mc_mintime) ||
	       CFS_FAIL_CHECK(OBD_FAIL_FORCE_GC_THREAD);
}

/** Add a changelog entry \a rec to the changelog llog
 * \param mdd
 * \param rec
 * \param handle - currently ignored since llogs start their own transaction;
 *		this will hopefully be fixed in llog rewrite
 * \retval 0 ok
 */
int mdd_changelog_store(const struct lu_env *env, struct mdd_device *mdd,
			struct llog_changelog_rec *rec, struct thandle *th)
{
	struct obd_device *obd = mdd2obd_dev(mdd);
	struct llog_ctxt *ctxt;
	struct thandle *llog_th;
	int rc;
	bool need_gc;

	rec->cr_hdr.lrh_len = llog_data_len(sizeof(*rec) +
					    changelog_rec_varsize(&rec->cr));

	/* llog_lvfs_write_rec sets the llog tail len */
	rec->cr_hdr.lrh_type = CHANGELOG_REC;
	rec->cr.cr_time = cl_time();

	ctxt = llog_get_context(obd, LLOG_CHANGELOG_ORIG_CTXT);
	if (ctxt == NULL)
		return -ENXIO;

	llog_th = thandle_get_sub(env, th, ctxt->loc_handle->lgh_obj);
	if (IS_ERR(llog_th))
		GOTO(out_put, rc = PTR_ERR(llog_th));

	CFS_FAIL_TIMEOUT(OBD_FAIL_MDS_CHANGELOG_REORDER, cfs_fail_val);
	/* nested journal transaction */
	rc = llog_add(env, ctxt->loc_handle, &rec->cr_hdr, NULL, llog_th);
	if (unlikely(!(mdd->mdd_cl.mc_flags & CLM_ON))) {
		/* tolerate errors if changelog was turned off */
		GOTO(out_put, rc = 0);
	}

	/* time to recover some space ?? */
	if (likely(!mdd->mdd_changelog_gc ||
		   mdd->mdd_cl.mc_gc_task != MDD_CHLG_GC_NONE ||
		   mdd->mdd_changelog_min_gc_interval >=
			ktime_get_real_seconds() - mdd->mdd_cl.mc_gc_time))
		/* save a spin_lock trip */
		goto out_put;

	if (CFS_FAIL_PRECHECK(OBD_FAIL_MDS_CHANGELOG_IDX_PUMP)) {
		spin_lock(&mdd->mdd_cl.mc_lock);
		mdd->mdd_cl.mc_index += cfs_fail_val;
		spin_unlock(&mdd->mdd_cl.mc_lock);
	}

	need_gc = mdd_changelog_need_gc(env, mdd, ctxt->loc_handle);
	spin_lock(&mdd->mdd_cl.mc_lock);
	if (likely(mdd->mdd_changelog_gc &&
		     mdd->mdd_cl.mc_gc_task == MDD_CHLG_GC_NONE &&
		     ktime_get_real_seconds() - mdd->mdd_cl.mc_gc_time >
			mdd->mdd_changelog_min_gc_interval)) {
		if (unlikely(need_gc)) {
			CWARN("%s: %s starting changelog garbage collection\n",
			      obd->obd_name,
			      CFS_FAIL_CHECK(OBD_FAIL_FORCE_GC_THREAD) ?
			      " simulate" : "");
			/* indicate further kthread run will occur outside
			 * right after current journal transaction filling has
			 * completed
			 */
			mdd->mdd_cl.mc_gc_task = MDD_CHLG_GC_NEED;
		}
		/* next check in mdd_changelog_min_gc_interval anyway */
		mdd->mdd_cl.mc_gc_time = ktime_get_real_seconds();
	}
	spin_unlock(&mdd->mdd_cl.mc_lock);
out_put:
	llog_ctxt_put(ctxt);
	if (rc > 0)
		rc = 0;
	return rc;
}

static void mdd_changelog_rec_ext_rename(struct changelog_rec *rec,
					 const struct lu_fid *sfid,
					 const struct lu_fid *spfid,
					 const struct lu_name *sname)
{
	struct changelog_ext_rename *rnm = changelog_rec_rename(rec);
	size_t extsize;

	LASSERT(sfid != NULL);
	LASSERT(spfid != NULL);
	LASSERT(sname != NULL);

	extsize = sname->ln_namelen + 1;

	rnm->cr_sfid = *sfid;
	rnm->cr_spfid = *spfid;

	changelog_rec_name(rec)[rec->cr_namelen] = '\0';
	strscpy(changelog_rec_sname(rec), sname->ln_name, extsize);
	rec->cr_namelen += extsize;
}

void mdd_changelog_rec_ext_jobid(struct changelog_rec *rec, const char *jobid)
{
	struct changelog_ext_jobid *jid = changelog_rec_jobid(rec);

	if (jobid == NULL || jobid[0] == '\0')
		return;

	strscpy(jid->cr_jobid, jobid, sizeof(jid->cr_jobid));
}

void mdd_changelog_rec_ext_extra_flags(struct changelog_rec *rec, __u64 eflags)
{
	struct changelog_ext_extra_flags *ef = changelog_rec_extra_flags(rec);

	ef->cr_extra_flags = eflags;
}

void mdd_changelog_rec_extra_uidgid(struct changelog_rec *rec,
				    __u64 uid, __u64 gid)
{
	struct changelog_ext_uidgid *uidgid = changelog_rec_uidgid(rec);

	uidgid->cr_uid = uid;
	uidgid->cr_gid = gid;
}

/* To support the new large NID structure we use all the space in
 * struct changelog_ext_nid to store struct lnet_nid.
 */
void mdd_changelog_rec_extra_nid(struct changelog_rec *rec,
				 const struct lnet_nid *nid)
{
	struct changelog_ext_nid *clnid = changelog_rec_nid(rec);

	BUILD_BUG_ON(sizeof(*clnid) < sizeof(*nid));
	memcpy(clnid, nid, sizeof(*nid));
}

void mdd_changelog_rec_extra_omode(struct changelog_rec *rec, u32 flags)
{
	struct changelog_ext_openmode *omd = changelog_rec_openmode(rec);

	omd->cr_openflags = flags;
}

void mdd_changelog_rec_extra_xattr(struct changelog_rec *rec,
				   const char *xattr_name)
{
	struct changelog_ext_xattr *xattr = changelog_rec_xattr(rec);

	strscpy(xattr->cr_xattr, xattr_name, sizeof(xattr->cr_xattr));
}

/**
 * mdd_changelog_ns_pfid_set() - Set the parent FID at @pfid for a namespace
 * change changelog record
 * @env: execution environment
 * @mdd: mdd device
 * @parent: parent object
 * @pattr: parent attribute
 * @pfid: parent fid
 *
 * Set the parent FID at @pfid for a namespace change changelog record, using
 * XATTR_NAME_LMV and linkEA from the remote object to obtain the correct
 * parent FID for striped directories
 *
 * Return:
 * * %0 success
 * % %-errno failure
 */
static int mdd_changelog_ns_pfid_set(const struct lu_env *env,
				     struct mdd_device *mdd,
				     struct mdd_object *parent,
				     const struct lu_attr *pattr,
				     struct lu_fid *pfid)
{
	int rc = 0;

	/* Certain userspace tools might rely on the previous behavior of
	 * displaying the shard's parent FID, on some changelog records related
	 * to striped directories, so use that for compatibility if needed
	 */
	if (mdd->mdd_cl.mc_enable_shard_pfid) {
		*pfid = *mdd_object_fid(parent);
		return 0;
	}

	if (!fid_is_zero(&parent->mod_striped_pfid)) {
		*pfid = parent->mod_striped_pfid;
		return 0;
	}

	/* is the parent dir striped? */
	rc = mdo_xattr_get(env, parent, &LU_BUF_NULL, XATTR_NAME_LMV);
	if (rc == -ENODATA) {
		*pfid = *mdd_object_fid(parent);
		parent->mod_striped_pfid = *pfid;
		return 0;
	}

	if (rc < 0)
		return rc;

	LASSERT(!mdd_is_root(mdo2mdd(&parent->mod_obj),
			     mdd_object_fid(parent)));

	/* hide shard FID */
	rc = mdd_parent_fid(env, parent, pattr, pfid);
	if (!rc)
		parent->mod_striped_pfid = *pfid;

	return rc;
}

/* The digested form is made of a FID (16 bytes) followed by the second-to-last
 * ciphertext block (16 bytes), so a total length of 32 bytes.
 */
/* Must be identical to ll_digest_filename in llite_internal.h */
struct changelog_digest_filename {
	struct lu_fid	cdf_fid;
	char		cdf_excerpt[LL_CRYPTO_BLOCK_SIZE];
};

/**
 * changelog_name2digest() - Utility function to process filename in changelog
 * @name: file name
 * @namelen: file name len
 * @fid: file FID
 * @enc: is object encrypted?
 * @ln: pointer to the struct lu_name to hold the real name
 *
 * If file is not encrypted, output name is just the file name.
 * If file is encrypted, file name needs to be decoded then digested if the name
 * is also encrypted. In this case a new buffer is allocated, and ln->ln_name
 * needs to be freed by the caller.
 *
 * Return:
 * * %0 on success
 * * %negative on error
 */
static int changelog_name2digest(const char *name, int namelen,
				 const struct lu_fid *fid,
				 bool enc, struct lu_name *ln)
{
	struct changelog_digest_filename *digest = NULL;
	char *buf = NULL, *bufout = NULL, *p, *q;
	int len, bufoutlen;
	int rc = 0;

	ENTRY;

	ln->ln_name = name;
	ln->ln_namelen = namelen;

	if (!enc)
		GOTO(out, rc);

	/* now we know file is encrypted */
	if (strnchr(name, namelen, '=')) {
		/* only proceed to critical decode if
		 * encrypted name contains espace char '='
		 */
		buf = kmalloc(namelen, GFP_NOFS);
		if (!buf)
			GOTO(out, rc = -ENOMEM);

		namelen = critical_decode(name, namelen, buf);
		ln->ln_name = buf;
		ln->ln_namelen = namelen;
	}

	p = (char *)ln->ln_name;
	len = namelen;
	while (len--) {
		if (!isprint(*p++))
			break;
	}

	/* len == -1 means we went through the whole decoded name without
	 * finding any non-printable character, so consider it is not encrypted
	 */
	if (len == -1)
		GOTO(out, rc);

	/* now we know the name has some non-printable characters */
	if (namelen > LL_CRYPTO_BLOCK_SIZE * 2) {
		if (!fid)
			GOTO(out, rc = -EPROTO);

		OBD_ALLOC_PTR(digest);
		if (!digest)
			GOTO(out, rc = -ENOMEM);

		digest->cdf_fid = *fid;
		memcpy(digest->cdf_excerpt,
		       LLCRYPT_EXTRACT_DIGEST(ln->ln_name, ln->ln_namelen),
		       LL_CRYPTO_BLOCK_SIZE);
		p = (char *)digest;
		len = sizeof(*digest);
	} else {
		p = (char *)ln->ln_name;
		len = ln->ln_namelen;
	}

	bufoutlen = BASE64URL_CHARS(len) + 2;
	bufout = kmalloc(digest ? bufoutlen + 1 : bufoutlen, GFP_NOFS);
	if (!bufout)
		GOTO(free_digest, rc = -ENOMEM);

	q = bufout;
	if (digest)
		*q++ = LLCRYPT_DIGESTED_CHAR;
	/* beware that gss_base64url_encode adds a trailing space */
	gss_base64url_encode(&q, &bufoutlen, (__u8 *)p, len);
	if (bufoutlen == -1) {
		kfree(bufout);
	} else {
		kfree(buf);
		ln->ln_name = bufout;
		ln->ln_namelen = q - bufout - 1;
	}

free_digest:
	OBD_FREE_PTR(digest);
out:
	RETURN(rc);
}

/**
 * mdd_changelog_ns_store() - Store a namespace change into changelog record
 * @env: execution environment
 * @mdd: metadata device
 * @type: changelog record types
 * @clf_flags: current flags
 * @target: mdd_object of change
 * @parent: target parent object
 * @pattr: target parent attribute
 * @sfid: source object fid
 * @sparent: source parent object
 * @spattr: source parent attribute
 * @tname: target name string
 * @sname: source name string
 * @handle: transaction handle
 *
 * Store namespace changes (Eg create, mkdir) that modify the filesystem.
 * If this fails, we must fail the whole transaction; we don't
 * want the change to commit without the log entry.
 *
 * Return:
 * * %0 on success
 * * %errno on failure
 */
int mdd_changelog_ns_store(const struct lu_env *env,
			   struct mdd_device *mdd,
			   enum changelog_rec_type type,
			   enum changelog_rec_flags clf_flags,
			   struct mdd_object *target,
			   struct mdd_object *parent,
			   const struct lu_attr *pattr,
			   const struct lu_fid *sfid,
			   struct mdd_object *sparent,
			   const struct lu_attr *spattr,
			   const struct lu_name *tname,
			   const struct lu_name *sname,
			   struct thandle *handle)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct lu_name *ltname = NULL, *lsname = NULL;
	const struct lu_ucred *uc = lu_ucred(env);
	struct llog_changelog_rec *rec;
	__u64 xflags = CLFE_INVALID;
	struct lu_fid *tfid = NULL;
	struct lu_buf *buf;
	int reclen;
	bool enc;
	int rc = 0;

	ENTRY;

	/*
	 * we can't use mdd_changelog_enabled() here as the changelog
	 * can get enabled between declaration and execution.
	 */
	if (mdd_env_info(env)->mdi_chlog_declared == 0)
		RETURN(0);

	LASSERT(S_ISDIR(mdd_object_type(parent)));
	LASSERT(tname != NULL);
	LASSERT(handle != NULL);

	if (tname) {
		OBD_ALLOC_PTR(ltname);
		if (!ltname)
			GOTO(out, rc = -ENOMEM);

		if (sname) {
			enc = info->mdi_tpattr.la_valid & LA_FLAGS &&
				info->mdi_tpattr.la_flags & LUSTRE_ENCRYPT_FL;
			tfid = (struct lu_fid *)sfid;
		} else {
			enc = info->mdi_pattr.la_valid & LA_FLAGS &&
				info->mdi_pattr.la_flags & LUSTRE_ENCRYPT_FL;
			if (!target) {
				/* this is lfs rm_entry with no target fid */
				tfid = &info->mdi_fid2;
				memset(tfid, 0, sizeof(*tfid));
			} else {
				tfid = (struct lu_fid *)mdd_object_fid(target);
			}
		}
		rc = changelog_name2digest(tname->ln_name, tname->ln_namelen,
					   tfid, enc, ltname);
		if (rc)
			GOTO(out_ltname, rc);
	}
	if (sname) {
		OBD_ALLOC_PTR(lsname);
		if (!lsname)
			GOTO(out_ltname, rc = -ENOMEM);

		enc = info->mdi_pattr.la_valid & LA_FLAGS &&
			info->mdi_pattr.la_flags & LUSTRE_ENCRYPT_FL;
		rc = changelog_name2digest(sname->ln_name, sname->ln_namelen,
					   tfid, enc, lsname);
		if (rc)
			GOTO(out_lsname, rc);
	}

	reclen = mdd_llog_record_calc_size(env, ltname, lsname);
	buf = lu_buf_check_and_alloc(&mdd_env_info(env)->mdi_chlg_buf, reclen);
	if (buf->lb_buf == NULL)
		GOTO(out_lsname, rc = -ENOMEM);
	rec = buf->lb_buf;

	clf_flags &= CLF_FLAGMASK;
	clf_flags |= CLF_EXTRA_FLAGS;

	if (uc) {
		if (uc->uc_jobid[0] != '\0')
			clf_flags |= CLF_JOBID;
		xflags |= CLFE_UIDGID;
		xflags |= CLFE_NID;
		xflags |= CLFE_NID_BE;
	}

	if (lsname != NULL)
		clf_flags |= CLF_RENAME;
	else
		clf_flags |= CLF_VERSION;

	rec->cr.cr_flags = clf_flags;

	if (clf_flags & CLF_EXTRA_FLAGS) {
		mdd_changelog_rec_ext_extra_flags(&rec->cr, xflags);
		if (xflags & CLFE_UIDGID)
			mdd_changelog_rec_extra_uidgid(&rec->cr,
						       uc->uc_uid, uc->uc_gid);
		if (xflags & CLFE_NID)
			mdd_changelog_rec_extra_nid(&rec->cr, &uc->uc_nid);
	}

	rec->cr.cr_type = (__u32)type;

	rc = mdd_changelog_ns_pfid_set(env, mdd, parent, pattr,
				       &rec->cr.cr_pfid);
	if (rc < 0)
		GOTO(out_lsname, rc);

	rec->cr.cr_namelen = ltname->ln_namelen;
	memcpy(changelog_rec_name(&rec->cr), ltname->ln_name,
	       ltname->ln_namelen);

	if (clf_flags & CLF_RENAME) {
		struct lu_fid spfid;

		rc = mdd_changelog_ns_pfid_set(env, mdd, sparent, spattr,
					       &spfid);
		if (rc < 0)
			GOTO(out_lsname, rc);

		mdd_changelog_rec_ext_rename(&rec->cr, sfid, &spfid, lsname);
	}

	if (clf_flags & CLF_JOBID)
		mdd_changelog_rec_ext_jobid(&rec->cr, uc->uc_jobid);

	if (likely(target != NULL)) {
		rec->cr.cr_tfid = *mdd_object_fid(target);
		target->mod_cltime = ktime_get();
	} else {
		fid_zero(&rec->cr.cr_tfid);
	}

	rc = mdd_changelog_store(env, mdd, rec, handle);
	if (rc < 0) {
		CERROR("%s: cannot store changelog record: type = %d, name = '%s', t = "
		       DFID", p = "DFID": rc = %d\n",
		       mdd2obd_dev(mdd)->obd_name, type, ltname->ln_name,
		       PFID(&rec->cr.cr_tfid), PFID(&rec->cr.cr_pfid), rc);
		GOTO(out_lsname, rc = -EFAULT);
	}

out_lsname:
	if (lsname && lsname->ln_name != sname->ln_name)
		kfree(lsname->ln_name);
	OBD_FREE_PTR(lsname);
out_ltname:
	if (ltname && ltname->ln_name != tname->ln_name)
		kfree(ltname->ln_name);
	OBD_FREE_PTR(ltname);
out:
	RETURN(rc);
}

static int __mdd_links_add(const struct lu_env *env,
			   struct mdd_object *mdd_obj,
			   struct linkea_data *ldata,
			   const struct lu_name *lname,
			   const struct lu_fid *pfid,
			   int first, int check)
{
	/* cattr is set in mdd_link */
	struct lu_attr *cattr = MDD_ENV_VAR(env, cattr);
	int rc;

	if (ldata->ld_leh == NULL) {
		rc = first ? -ENODATA : mdd_links_read(env, mdd_obj, ldata);
		if (rc) {
			if (rc != -ENODATA)
				return rc;
			rc = linkea_data_new(ldata,
					     &mdd_env_info(env)->mdi_link_buf);
			if (rc)
				return rc;
		}
	}

	if (check) {
		rc = linkea_links_find(ldata, lname, pfid);
		if (rc && rc != -ENOENT)
			return rc;
		if (rc == 0)
			return -EEXIST;
	}

	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_LINKEA_MORE)) {
		struct lu_fid *tfid = &mdd_env_info(env)->mdi_fid2;

		*tfid = *pfid;
		tfid->f_ver = ~0;
		linkea_add_buf(ldata, lname, tfid, false);
	}

	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_LINKEA_MORE2))
		linkea_add_buf(ldata, lname, pfid, false);

	/* For encrypted file, we want to limit number of hard links to what
	 * linkEA can contain. So ask to return error in case of overflow.
	 * Currently linkEA stores 4KiB of links, that is 14 NAME_MAX links,
	 * or 119 16-byte names.
	 */
	return linkea_add_buf(ldata, lname, pfid,
			      cattr->la_valid & LA_FLAGS &&
			      cattr->la_flags & LUSTRE_ENCRYPT_FL);
}

static int __mdd_links_del(const struct lu_env *env,
			   struct mdd_object *mdd_obj,
			   struct linkea_data *ldata,
			   const struct lu_name *lname,
			   const struct lu_fid *pfid)
{
	/* cattr is set in mdd_link */
	struct lu_attr *cattr = MDD_ENV_VAR(env, cattr);
	int rc;

	if (ldata->ld_leh == NULL) {
		rc = mdd_links_read(env, mdd_obj, ldata);
		if (rc)
			return rc;
	}

	rc = linkea_links_find(ldata, lname, pfid);
	if (rc)
		return rc;

	linkea_del_buf(ldata, lname,
		       cattr->la_valid & LA_FLAGS &&
		       cattr->la_flags & LUSTRE_ENCRYPT_FL);
	return 0;
}

static int mdd_linkea_prepare(const struct lu_env *env,
			      struct mdd_object *mdd_obj,
			      const struct lu_fid *oldpfid,
			      const struct lu_name *oldlname,
			      const struct lu_fid *newpfid,
			      const struct lu_name *newlname,
			      int first, int check,
			      struct linkea_data *ldata)
{
	int rc = 0;

	ENTRY;

	if (CFS_FAIL_CHECK(OBD_FAIL_FID_IGIF))
		RETURN(0);

	LASSERT(oldpfid != NULL || newpfid != NULL);

	if (mdd_obj->mod_flags & DEAD_OBJ)
		/* Unnecessary to update linkEA for dead object.  */
		RETURN(0);

	if (oldpfid != NULL) {
		rc = __mdd_links_del(env, mdd_obj, ldata, oldlname, oldpfid);
		if (rc) {
			if ((check == 1) || (rc != -ENODATA && rc != -ENOENT))
				RETURN(rc);

			/* No changes done. */
			rc = 0;
		}
	}

	/* If renaming, add the new record */
	if (newpfid != NULL)
		rc = __mdd_links_add(env, mdd_obj, ldata, newlname, newpfid,
				     first, check);

	RETURN(rc);
}

int mdd_links_rename(const struct lu_env *env,
		     struct mdd_object *mdd_obj,
		     const struct lu_fid *oldpfid,
		     const struct lu_name *oldlname,
		     const struct lu_fid *newpfid,
		     const struct lu_name *newlname,
		     struct thandle *handle,
		     struct linkea_data *ldata,
		     int first, int check)
{
	int rc = 0;

	ENTRY;

	if (ldata == NULL) {
		ldata = &mdd_env_info(env)->mdi_link_data;
		memset(ldata, 0, sizeof(*ldata));
		rc = mdd_linkea_prepare(env, mdd_obj, oldpfid, oldlname,
					newpfid, newlname, first, check, ldata);
		if (rc)
			GOTO(out, rc);
	}

	if (!(mdd_obj->mod_flags & DEAD_OBJ))
		rc = mdd_links_write(env, mdd_obj, ldata, handle);

	GOTO(out, rc);

out:
	if (rc != 0) {
		if (newlname == NULL)
			CERROR("link_ea add failed "DFID": rc = %d\n",
			       PFID(mdd_object_fid(mdd_obj)), rc);
		else if (oldpfid == NULL)
			CERROR("link_ea add '%.*s' failed "DFID": rc = %d\n",
			       newlname->ln_namelen, newlname->ln_name,
			       PFID(mdd_object_fid(mdd_obj)), rc);
		else if (newpfid == NULL)
			CERROR("link_ea del '%.*s' failed "DFID": rc = %d\n",
			       oldlname->ln_namelen, oldlname->ln_name,
			       PFID(mdd_object_fid(mdd_obj)), rc);
		else
			CERROR("link_ea rename '%.*s'->'%.*s' failed "DFID": rc = %d\n",
			       oldlname->ln_namelen, oldlname->ln_name,
			       newlname->ln_namelen, newlname->ln_name,
			       PFID(mdd_object_fid(mdd_obj)), rc);
	}

	if (is_vmalloc_addr(ldata->ld_buf))
		/* if we vmalloced a large buffer drop it */
		lu_buf_free(ldata->ld_buf);

	return rc;
}

static inline int mdd_links_add(const struct lu_env *env,
				struct mdd_object *mdd_obj,
				const struct lu_fid *pfid,
				const struct lu_name *lname,
				struct thandle *handle,
				struct linkea_data *ldata, int first)
{
	return mdd_links_rename(env, mdd_obj, NULL, NULL,
				pfid, lname, handle, ldata, first, 0);
}

static inline int mdd_links_del(const struct lu_env *env,
				struct mdd_object *mdd_obj,
				const struct lu_fid *pfid,
				const struct lu_name *lname,
				struct thandle *handle)
{
	return mdd_links_rename(env, mdd_obj, pfid, lname,
				NULL, NULL, handle, NULL, 0, 0);
}

/** Read the link EA into a temp buffer.
 * Uses the name_buf since it is generally large.
 * \retval IS_ERR err
 * \retval ptr to \a lu_buf (always \a mdi_link_buf)
 */
struct lu_buf *mdd_links_get(const struct lu_env *env,
			     struct mdd_object *mdd_obj)
{
	struct linkea_data ldata = { NULL };
	int rc;

	rc = mdd_links_read(env, mdd_obj, &ldata);
	return rc ? ERR_PTR(rc) : ldata.ld_buf;
}

int mdd_links_write(const struct lu_env *env, struct mdd_object *mdd_obj,
		    struct linkea_data *ldata, struct thandle *handle)
{
	const struct lu_buf *buf;
	int		    rc;

	if (ldata == NULL || ldata->ld_buf == NULL ||
	    ldata->ld_leh == NULL)
		return 0;

	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_NO_LINKEA))
		return 0;

again:
	buf = mdd_buf_get_const(env, ldata->ld_buf->lb_buf,
				ldata->ld_leh->leh_len);
	rc = mdo_xattr_set(env, mdd_obj, buf, XATTR_NAME_LINK, 0, handle);
	if (unlikely(rc == -ENOSPC)) {
		rc = linkea_overflow_shrink(ldata);
		if (likely(rc > 0))
			goto again;
	}

	return rc;
}

static int mdd_declare_links_add(const struct lu_env *env,
				 struct mdd_object *mdd_obj,
				 struct thandle *handle,
				 struct linkea_data *ldata)
{
	int rc;
	int ea_len;
	void *linkea;

	if (ldata != NULL && ldata->ld_leh != NULL) {
		ea_len = ldata->ld_leh->leh_len;
		linkea = ldata->ld_buf->lb_buf;
	} else {
		ea_len = MAX_LINKEA_SIZE;
		linkea = NULL;
	}

	rc = mdo_declare_xattr_set(env, mdd_obj,
				   mdd_buf_get_const(env, linkea, ea_len),
				   XATTR_NAME_LINK, 0, handle);

	return rc;
}

static inline int mdd_declare_links_del(const struct lu_env *env,
					struct mdd_object *c,
					struct thandle *handle)
{
	int rc = 0;

	/* For directory, linkEA will be removed together with the object. */
	if (!S_ISDIR(mdd_object_type(c)))
		rc = mdd_declare_links_add(env, c, handle, NULL);

	return rc;
}

static int mdd_declare_link(const struct lu_env *env,
			    struct mdd_device *mdd,
			    struct mdd_object *p,
			    struct mdd_object *c,
			    const struct lu_name *name,
			    struct thandle *handle,
			    struct lu_attr *la,
			    struct linkea_data *data)
{
	struct lu_fid tfid = *mdd_object_fid(c);
	int rc;

	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_DANGLING3))
		tfid.f_oid = cfs_fail_val;

	rc = mdo_declare_index_insert(env, p, &tfid, mdd_object_type(c),
				      name->ln_name, handle);
	if (rc != 0)
		return rc;

	rc = mdo_declare_ref_add(env, c, handle);
	if (rc != 0)
		return rc;

	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdo_declare_attr_set(env, p, la, handle);
	if (rc != 0)
		return rc;

	la->la_valid = LA_CTIME;
	rc = mdo_declare_attr_set(env, c, la, handle);
	if (rc != 0)
		return rc;

	rc = mdd_declare_links_add(env, c, handle, data);
	if (rc != 0)
		return rc;

	rc = mdd_declare_changelog_store(env, mdd, CL_HARDLINK, name, NULL,
					 handle);

	return rc;
}

static int mdd_link(const struct lu_env *env, struct md_object *tgt_obj,
		    struct md_object *src_obj, const struct lu_name *lname,
		    struct md_attr *ma)
{
	const char *name = lname->ln_name;
	struct lu_attr *la = &mdd_env_info(env)->mdi_la_for_fix;
	struct mdd_object *mdd_tobj = md2mdd_obj(tgt_obj);
	struct mdd_object *mdd_sobj = md2mdd_obj(src_obj);
	struct lu_attr *cattr = MDD_ENV_VAR(env, cattr);
	struct lu_attr *tattr = MDD_ENV_VAR(env, tattr);
	struct mdd_device *mdd = mdo2mdd(src_obj);
	struct thandle *handle;
	struct lu_fid *tfid = &mdd_env_info(env)->mdi_fid2;
	struct linkea_data *ldata = &mdd_env_info(env)->mdi_link_data;
	int rc;

	ENTRY;

	rc = mdd_la_get(env, mdd_sobj, cattr);
	if (rc != 0)
		RETURN(rc);

	rc = mdd_la_get(env, mdd_tobj, tattr);
	if (rc != 0)
		RETURN(rc);

	/* If we are using project inheritance, we only allow hard link
	 * creation in our tree when the project IDs are the same;
	 * otherwise the tree quota mechanism could be circumvented.
	 */
	if ((tattr->la_flags & LUSTRE_PROJINHERIT_FL) &&
	    (tattr->la_projid != cattr->la_projid))
		RETURN(-EXDEV);

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		GOTO(out_pending, rc = PTR_ERR(handle));

	memset(ldata, 0, sizeof(*ldata));

	LASSERT(ma->ma_attr.la_valid & LA_CTIME);
	la->la_ctime = la->la_mtime = ma->ma_attr.la_ctime;

	/* Note: even this function will change ldata, but it comes from
	 * thread_info, which is completely temporary and only seen in
	 * this function, so we do not need reset ldata once it fails.
	 */
	rc = mdd_linkea_prepare(env, mdd_sobj, NULL, NULL,
				mdd_object_fid(mdd_tobj), lname, 0, 0, ldata);
	if (rc != 0)
		GOTO(stop, rc);

	rc = mdd_declare_link(env, mdd, mdd_tobj, mdd_sobj, lname, handle,
			      la, ldata);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop, rc);

	mdd_write_lock(env, mdd_sobj, DT_TGT_CHILD);
	rc = mdd_link_sanity_check(env, mdd_tobj, tattr, lname, mdd_sobj,
				   cattr);
	if (rc)
		GOTO(out_unlock, rc);

	if (!CFS_FAIL_CHECK(OBD_FAIL_LFSCK_LESS_NLINK)) {
		rc = mdo_ref_add(env, mdd_sobj, handle);
		if (rc != 0)
			GOTO(out_unlock, rc);
	}

	*tfid = *mdd_object_fid(mdd_sobj);
	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_DANGLING3))
		tfid->f_oid = cfs_fail_val;

	rc = __mdd_index_insert_only(env, mdd_tobj, tfid,
				     mdd_object_type(mdd_sobj), name, handle);
	if (rc != 0) {
		mdo_ref_del(env, mdd_sobj, handle);
		GOTO(out_unlock, rc);
	}

	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdd_update_time(env, mdd_tobj, tattr, la, handle);
	if (rc)
		GOTO(out_unlock, rc);

	la->la_valid = LA_CTIME;
	rc = mdd_update_time(env, mdd_sobj, cattr, la, handle);
	if (rc == 0)
		/* Note: The failure of links_add should not cause the
		 * link failure, so do not check return value.
		 */
		mdd_links_add(env, mdd_sobj, mdd_object_fid(mdd_tobj),
			      lname, handle, ldata, 0);

	EXIT;
out_unlock:
	mdd_write_unlock(env, mdd_sobj);
	if (rc == 0)
		rc = mdd_changelog_ns_store(env, mdd, CL_HARDLINK, 0, mdd_sobj,
					    mdd_tobj, tattr, NULL,
					    NULL, NULL, lname, NULL, handle);
stop:
	rc = mdd_trans_stop(env, mdd, rc, handle);
	if (is_vmalloc_addr(ldata->ld_buf))
		/* if we vmalloced a large buffer drop it */
		lu_buf_free(ldata->ld_buf);
out_pending:
	return rc;
}

static int mdd_mark_orphan_object(const struct lu_env *env,
				struct mdd_object *obj, struct thandle *handle,
				bool declare)
{
	struct lu_attr *attr = MDD_ENV_VAR(env, la_for_start);
	int rc;

	attr->la_valid = LA_FLAGS;
	attr->la_flags = LUSTRE_ORPHAN_FL;

	if (declare)
		rc = mdo_declare_attr_set(env, obj, attr, handle);
	else
		rc = mdo_attr_set(env, obj, attr, handle);

	return rc;
}

static int mdd_declare_finish_unlink(const struct lu_env *env,
				     struct mdd_object *obj,
				     struct thandle *handle)
{
	int rc;

	/* Sigh, we do not know if the unlink object will become orphan in
	 * declare phase, but fortunately the flags here does not matter
	 * in current declare implementation
	 */
	rc = mdd_mark_orphan_object(env, obj, handle, true);
	if (rc != 0)
		return rc;

	rc = mdo_declare_destroy(env, obj, handle);
	if (rc != 0)
		return rc;

	rc = mdd_orphan_declare_insert(env, obj, mdd_object_type(obj), handle);
	if (rc != 0)
		return rc;

	return mdd_declare_links_del(env, obj, handle);
}

/* caller should take a lock before calling */
int mdd_finish_unlink(const struct lu_env *env,
		      struct mdd_object *obj, struct md_attr *ma,
		      struct mdd_object *pobj,
		      const struct lu_name *lname,
		      struct thandle *th)
{
	int rc = 0;
	int is_dir = S_ISDIR(ma->ma_attr.la_mode);

	ENTRY;

	LASSERT(mdd_write_locked(env, obj) != 0);

	if (ma->ma_attr.la_nlink == 0 || is_dir) {
		/* add new orphan, object will be deleted during mdd_close() */
		obj->mod_flags |= DEAD_OBJ;
		if (obj->mod_count) {
			rc = mdd_orphan_insert(env, obj, th);
			if (rc == 0)
				CDEBUG(D_HA,
				       "Object "DFID" is inserted into orphan list, open count = %d\n",
				       PFID(mdd_object_fid(obj)),
				       obj->mod_count);
			else
				CERROR("Object "DFID" fail to be an orphan, open count = %d, maybe cause failed open replay\n",
					PFID(mdd_object_fid(obj)),
					obj->mod_count);

			/* mark object as an orphan here, not before
			 * mdd_orphan_insert() as racing mdd_la_get() may
			 * propagate ORPHAN_OBJ causing the asserition
			 */
			rc = mdd_mark_orphan_object(env, obj, th, false);
		} else {
			rc = mdo_destroy(env, obj, th);
		}
	} else if (!is_dir) {
		/* old files may not have link ea; ignore errors */
		mdd_links_del(env, obj, mdd_object_fid(pobj), lname, th);
	}

	RETURN(rc);
}

/*
 * pobj maybe NULL
 * has mdd_write_lock on cobj already, but not on pobj yet
 */
int mdd_unlink_sanity_check(const struct lu_env *env, struct mdd_object *pobj,
			    const struct lu_attr *pattr,
			    struct mdd_object *cobj,
			    const struct lu_attr *cattr)
{
	int rc;

	ENTRY;

	rc = mdd_may_delete(env, pobj, pattr, cobj, cattr, NULL, 1, 1);

	RETURN(rc);
}

static int mdd_declare_unlink(const struct lu_env *env, struct mdd_device *mdd,
			      struct mdd_object *p, struct mdd_object *c,
			      const struct lu_name *name, struct md_attr *ma,
			      struct thandle *handle, int no_name, int is_dir)
{
	struct lu_attr *la = &mdd_env_info(env)->mdi_la_for_fix;
	int rc;

	if (!CFS_FAIL_CHECK(OBD_FAIL_LFSCK_DANGLING2)) {
		if (likely(no_name == 0)) {
			rc = mdo_declare_index_delete(env, p, name->ln_name,
						      handle);
			if (rc != 0)
				return rc;
		}

		if (is_dir != 0) {
			rc = mdo_declare_ref_del(env, p, handle);
			if (rc != 0)
				return rc;
		}
	}

	LASSERT(ma->ma_attr.la_valid & LA_CTIME);
	la->la_ctime = la->la_mtime = ma->ma_attr.la_ctime;
	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdo_declare_attr_set(env, p, la, handle);
	if (rc)
		return rc;

	if (c != NULL) {
		rc = mdo_declare_ref_del(env, c, handle);
		if (rc)
			return rc;

		rc = mdo_declare_ref_del(env, c, handle);
		if (rc)
			return rc;

		la->la_valid = LA_CTIME;
		rc = mdo_declare_attr_set(env, c, la, handle);
		if (rc)
			return rc;

		rc = mdd_declare_finish_unlink(env, c, handle);
		if (rc)
			return rc;
	}

	rc = mdd_declare_changelog_store(env, mdd, CL_UNLINK, name,
					 NULL, handle);

	return rc;
}

/*
 * test if a file has an HSM archive
 * if HSM attributes are not found in ma update them from
 * HSM xattr
 */
static bool mdd_hsm_archive_exists(const struct lu_env *env,
				   struct mdd_object *obj,
				   struct md_attr *ma)
{
	ENTRY;

	if (!(ma->ma_valid & MA_HSM)) {
		/* no HSM MD provided, read xattr */
		struct lu_buf *hsm_buf;
		const size_t buflen = sizeof(struct hsm_attrs);
		int rc;

		hsm_buf = mdd_buf_get(env, NULL, 0);
		lu_buf_alloc(hsm_buf, buflen);
		rc = mdo_xattr_get(env, obj, hsm_buf, XATTR_NAME_HSM);
		rc = lustre_buf2hsm(hsm_buf->lb_buf, rc, &ma->ma_hsm);
		lu_buf_free(hsm_buf);
		if (rc < 0)
			RETURN(false);

		ma->ma_valid |= MA_HSM;
	}
	if (ma->ma_hsm.mh_flags & HS_EXISTS)
		RETURN(true);
	RETURN(false);
}

/**
 * mdd_unlink() - Delete name entry and the object.
 * @env: execution environment
 * @pobj: the directory(parent) to delete files
 * @cobj: file(child object) to be deleted
 * @lname: the name of the deleted file/dir
 * @ma: create specification
 * @no_name: flag for destroy (see notes below)
 *
 * Note: no_name == 1 means it only destory the object, i.e. name_entry
 * does not exist for this object, and it could only happen during resending
 * of remote unlink. see the comments in mdt_reint_unlink. Unfortunately, lname
 * is also needed in this case(needed by changelog), so we have to add another
 * parameter(no_name)here. XXX: this is only needed in DNE phase I, on Phase II,
 * the ENOENT failure should be able to be fixed by redo mechanism.
 */
static int mdd_unlink(const struct lu_env *env, struct md_object *pobj,
		      struct md_object *cobj, const struct lu_name *lname,
		      struct md_attr *ma, int no_name)
{
	char *name = (char *)lname->ln_name;
	struct lu_attr *pattr = MDD_ENV_VAR(env, pattr);
	struct lu_attr *cattr = MDD_ENV_VAR(env, cattr);
	struct lu_attr *la = &mdd_env_info(env)->mdi_la_for_fix;
	struct mdd_object *mdd_pobj = md2mdd_obj(pobj);
	struct mdd_object *mdd_cobj = NULL;
	struct mdd_device *mdd = mdo2mdd(pobj);
	struct thandle    *handle;
	int rc, is_dir = 0, cl_flags = 0;

	ENTRY;

	/* let shutdown to start */
	CFS_FAIL_TIMEOUT(OBD_FAIL_TGT_REPLY_DATA_RACE, 1);

	/* cobj == NULL means only delete name entry */
	if (likely(cobj != NULL)) {
		mdd_cobj = md2mdd_obj(cobj);
		if (mdd_object_exists(mdd_cobj) == 0)
			RETURN(-ENOENT);
	}

	rc = mdd_la_get(env, mdd_pobj, pattr);
	if (rc)
		RETURN(rc);

	if (likely(mdd_cobj != NULL)) {
		/* fetch cattr */
		rc = mdd_la_get(env, mdd_cobj, cattr);
		if (rc)
			RETURN(rc);

		is_dir = S_ISDIR(cattr->la_mode);
		/* search for an existing archive. We should check ahead as the
		 * object can be destroyed in this transaction
		 */
		if (mdd_hsm_archive_exists(env, mdd_cobj, ma))
			cl_flags |= CLF_UNLINK_HSM_EXISTS;
	}

	rc = mdd_unlink_sanity_check(env, mdd_pobj, pattr, mdd_cobj, cattr);
	if (rc)
		RETURN(rc);

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		RETURN(PTR_ERR(handle));

	rc = mdd_declare_unlink(env, mdd, mdd_pobj, mdd_cobj,
				lname, ma, handle, no_name, is_dir);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop, rc);

	if (likely(mdd_cobj != NULL))
		mdd_write_lock(env, mdd_cobj, DT_TGT_CHILD);

	if (lname->ln_name[lname->ln_namelen] != '\0') {
		/* lname->ln_name is not necessarily NUL terminated */
		name = kmalloc(lname->ln_namelen + 1, GFP_NOFS);
		if (!name)
			GOTO(cleanup, rc = -ENOMEM);

		memcpy(name, lname->ln_name, lname->ln_namelen);
		name[lname->ln_namelen] = '\0';
	}

	if (likely(no_name == 0) && !CFS_FAIL_CHECK(OBD_FAIL_LFSCK_DANGLING2)) {
		rc = __mdd_index_delete(env, mdd_pobj, name, is_dir, handle);
		if (rc)
			GOTO(cleanup, rc);
	}

	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_MUL_REF) ||
	    CFS_FAIL_CHECK(OBD_FAIL_LFSCK_NO_NAMEENTRY))
		GOTO(cleanup, rc = 0);

	if (likely(mdd_cobj != NULL)) {
		rc = mdo_ref_del(env, mdd_cobj, handle);
		if (rc != 0) {
			__mdd_index_insert_only(env, mdd_pobj,
						mdd_object_fid(mdd_cobj),
						mdd_object_type(mdd_cobj),
						name, handle);
			GOTO(cleanup, rc);
		}

		if (is_dir)
			/* unlink dot */
			mdo_ref_del(env, mdd_cobj, handle);

		/* fetch updated nlink */
		rc = mdd_la_get(env, mdd_cobj, cattr);
		if (rc)
			GOTO(cleanup, rc);
	}

	LASSERT(ma->ma_attr.la_valid & LA_CTIME);
	la->la_ctime = la->la_mtime = ma->ma_attr.la_ctime;

	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdd_update_time(env, mdd_pobj, pattr, la, handle);
	if (rc)
		GOTO(cleanup, rc);

	/* Enough for only unlink the entry */
	if (unlikely(mdd_cobj == NULL))
		GOTO(cleanup, rc);

	if (cattr->la_nlink > 0 || mdd_cobj->mod_count > 0) {
		/* update ctime of an unlinked file only if it is still opened
		 * or a link still exists
		 */
		la->la_valid = LA_CTIME;
		rc = mdd_update_time(env, mdd_cobj, cattr, la, handle);
		if (rc)
			GOTO(cleanup, rc);
	}

	/* XXX: this transfer to ma will be removed with LOD/OSP */
	ma->ma_attr = *cattr;
	ma->ma_valid |= MA_INODE;
	rc = mdd_finish_unlink(env, mdd_cobj, ma, mdd_pobj, lname, handle);
	if (rc != 0)
		GOTO(cleanup, rc);

	/* fetch updated nlink */
	rc = mdd_la_get(env, mdd_cobj, cattr);
	/* if object is removed then we can't get its attrs, use last get */
	if (rc == -ENOENT) {
		cattr->la_nlink = 0;
		rc = 0;
	}

	if (cattr->la_nlink == 0) {
		ma->ma_attr = *cattr;
		ma->ma_valid |= MA_INODE;
	}

	EXIT;
cleanup:
	if (name != lname->ln_name)
		kfree(name);

	if (likely(mdd_cobj != NULL))
		mdd_write_unlock(env, mdd_cobj);

	if (rc == 0) {
		if (mdd_is_dead_obj(mdd_cobj))
			cl_flags |= CLF_UNLINK_LAST;
		else
			cl_flags &= ~CLF_UNLINK_HSM_EXISTS;

		rc = mdd_changelog_ns_store(env, mdd,
			is_dir ? CL_RMDIR : CL_UNLINK, cl_flags,
			mdd_cobj, mdd_pobj, pattr, NULL,
			NULL, NULL, lname, NULL, handle);
	}

stop:
	rc = mdd_trans_stop(env, mdd, rc, handle);

	return rc;
}

/*
 * The permission has been checked when obj created, no need check again.
 */
static int mdd_cd_sanity_check(const struct lu_env *env,
			       struct mdd_object *obj)
{
	ENTRY;

	/* EEXIST check */
	if (!obj || mdd_is_dead_obj(obj))
		RETURN(-ENOENT);

	RETURN(0);
}

static int mdd_create_data(const struct lu_env *env, struct md_object *pobj,
			   struct md_object *cobj,
			   const struct md_op_spec *spec, struct md_attr *ma)
{
	struct mdd_device *mdd = mdo2mdd(cobj);
	struct mdd_object *mdd_pobj = md2mdd_obj(pobj);
	struct mdd_object *son = md2mdd_obj(cobj);
	struct thandle *handle;
	const struct lu_buf *buf;
	struct lu_attr *attr = MDD_ENV_VAR(env, cattr);
	struct dt_allocation_hint *hint = &mdd_env_info(env)->mdi_hint;
	int rc;

	ENTRY;

	rc = mdd_cd_sanity_check(env, son);
	if (rc)
		RETURN(rc);

	if (!md_should_create(spec->sp_cr_flags))
		RETURN(0);

	/*
	 * there are following use cases for this function:
	 * 1) late striping - file was created with MDS_OPEN_DELAY_CREATE
	 *    striping can be specified or not
	 * 2) CMD?
	 */
	rc = mdd_la_get(env, son, attr);
	if (rc)
		RETURN(rc);

	/* calling ->ah_make_hint(), used to transfer information from parent */
	mdd_object_make_hint(env, mdd_pobj, son, attr, spec, hint);

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		GOTO(out_free, rc = PTR_ERR(handle));

	/*
	 * XXX: Setting the lov ea is not locked but setting the attr is locked?
	 * Should this be fixed?
	 */
	CDEBUG(D_OTHER, "ea %p/%u, cr_flags %#lo, no_create %u\n",
	       spec->u.sp_ea.eadata, spec->u.sp_ea.eadatalen,
	       spec->sp_cr_flags, spec->no_create);

	if (spec->no_create || (spec->sp_cr_flags & MDS_OPEN_HAS_EA)) {
		buf = mdd_buf_get_const(env, spec->u.sp_ea.eadata,
					spec->u.sp_ea.eadatalen);
	} else {
		buf = &LU_BUF_NULL;
	}

	rc = dt_declare_xattr_set(env, mdd_object_child(son), NULL, buf,
				  XATTR_NAME_LOV, 0, handle);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_declare_changelog_store(env, mdd, CL_LAYOUT, NULL, NULL,
					 handle);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop, rc);

	rc = dt_xattr_set(env, mdd_object_child(son), buf, XATTR_NAME_LOV,
			  0, handle);

	if (rc)
		GOTO(stop, rc);

	rc = mdd_changelog_data_store(env, mdd, CL_LAYOUT, 0, son, handle,
				      NULL);

stop:
	rc = mdd_trans_stop(env, mdd, rc, handle);

out_free:
	RETURN(rc);
}

static int mdd_declare_object_initialize(const struct lu_env *env,
					 struct mdd_object *parent,
					 struct mdd_object *child,
					 const struct lu_attr *attr,
					 struct thandle *handle)
{
	int rc;

	ENTRY;

	LASSERT(attr->la_valid & (LA_MODE | LA_TYPE));
	if (!S_ISDIR(attr->la_mode))
		RETURN(0);

	rc = mdo_declare_index_insert(env, child, mdd_object_fid(child),
				      S_IFDIR, dot, handle);
	if (rc != 0)
		RETURN(rc);

	rc = mdo_declare_ref_add(env, child, handle);
	if (rc != 0)
		RETURN(rc);

	rc = mdo_declare_index_insert(env, child, mdd_object_fid(parent),
				      S_IFDIR, dotdot, handle);

	RETURN(rc);
}

static int mdd_object_initialize(const struct lu_env *env,
				 const struct lu_fid *pfid,
				 struct mdd_object *child,
				 struct lu_attr *attr,
				 struct thandle *handle)
{
	int rc = 0;

	ENTRY;

	if (S_ISDIR(attr->la_mode)) {
		/* Add "." and ".." for newly created dir */
		mdo_ref_add(env, child, handle);
		rc = __mdd_index_insert_only(env, child, mdd_object_fid(child),
					     S_IFDIR, dot, handle);
		if (rc == 0)
			rc = __mdd_index_insert_only(env, child, pfid, S_IFDIR,
						     dotdot, handle);
		if (rc != 0)
			mdo_ref_del(env, child, handle);
	}

	RETURN(rc);
}

/**
 * mdd_create_sanity_check() - sanity check while creating file/dir
 * @env: execution environment
 * @pobj: the directory to create files
 * @pattr: the attributes of the directory
 * @lname: the name of the created file/dir
 * @cattr: the attributes of the file/dir
 * @spec: create specification
 *
 * This function checks whether it can create a file/dir under the
 * directory(@pobj). The directory(@pobj) is not being locked by
 * mdd lock.
 *
 * Return:
 * * %0 it is allowed to create file/dir under the directory
 * * %negative error not allowed to create file/dir under the directory
 */
static int mdd_create_sanity_check(const struct lu_env *env,
				   struct md_object *pobj,
				   const struct lu_attr *pattr,
				   const struct lu_name *lname,
				   struct lu_attr *cattr,
				   struct md_op_spec *spec)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct lu_fid *fid = &info->mdi_fid;
	struct mdd_object *obj = md2mdd_obj(pobj);
	struct mdd_device *m = mdo2mdd(pobj);
	bool check_perm = true;
	int rc;

	ENTRY;

	/* EEXIST check */
	if (mdd_is_dead_obj(obj))
		RETURN(-ENOENT);

	/*
	 * In some cases this lookup is not needed - we know before if name
	 * exists or not because MDT performs lookup for it.
	 * name length check is done in lookup.
	 */
	if (spec->sp_cr_lookup) {
		/*
		 * Check if the name already exist, though it will be checked in
		 * _index_insert also, for avoiding rolling back if exists
		 * _index_insert.
		 */
		rc = __mdd_lookup(env, pobj, pattr, lname, fid,
				  MAY_WRITE | MAY_EXEC);
		if (rc != -ENOENT)
			RETURN(rc ? : -EEXIST);

		/* Permission is already being checked in mdd_lookup */
		check_perm = false;
	}

	if (S_ISDIR(cattr->la_mode) &&
	    unlikely(spec != NULL && spec->sp_cr_flags & MDS_OPEN_HAS_EA) &&
	    spec->u.sp_ea.eadata != NULL && spec->u.sp_ea.eadatalen > 0) {
		const struct lmv_user_md *lum = spec->u.sp_ea.eadata;
		s32 stripe_count;

		if (!lmv_user_magic_supported(le32_to_cpu(lum->lum_magic)) &&
		    !(spec->sp_replay &&
		      lum->lum_magic == cpu_to_le32(LMV_MAGIC_V1))) {
			rc = -EINVAL;
out_err:
			CERROR("%s: invalid lmv_user_md: magic=%x hash=%x stripe_offset=%d stripe_count=%d: rc = %d\n",
			       mdd2obd_dev(m)->obd_name,
			       le32_to_cpu(lum->lum_magic),
			       le32_to_cpu(lum->lum_hash_type),
			       (int)le32_to_cpu(lum->lum_stripe_offset),
			       le32_to_cpu(lum->lum_stripe_count), rc);
			RETURN(rc);
		}
		stripe_count = le32_to_cpu(lum->lum_stripe_count);
		if (stripe_count > LMV_MAX_STRIPE_COUNT ||
		    stripe_count < LMV_OVERSTRIPE_COUNT_MAX)
			GOTO(out_err, rc = -EOVERFLOW);
	}

	rc = mdd_may_create(env, obj, pattr, NULL, check_perm);
	if (rc != 0)
		RETURN(rc);

	/* sgid check */
	if (pattr->la_mode & S_ISGID) {
		struct lu_ucred *uc = lu_ucred(env);

		cattr->la_gid = pattr->la_gid;

		/* Directories are special, and always inherit S_ISGID */
		if (S_ISDIR(cattr->la_mode)) {
			cattr->la_mode |= S_ISGID;
			cattr->la_valid |= LA_MODE;
		} else if ((cattr->la_mode & (S_ISGID | 0010))
				== (S_ISGID | 0010) &&
			   !lustre_in_group_p(uc,
					      (cattr->la_valid & LA_GID) ?
					      cattr->la_gid : pattr->la_gid) &&
			   !cap_raised(uc->uc_cap, CAP_FSETID)) {
			cattr->la_mode &= ~S_ISGID;
			cattr->la_valid |= LA_MODE;
		}
	}

	/* Inherit project ID from parent directory */
	if (pattr->la_flags & LUSTRE_PROJINHERIT_FL) {
		cattr->la_projid = pattr->la_projid;
		if (S_ISDIR(cattr->la_mode)) {
			cattr->la_flags |= LUSTRE_PROJINHERIT_FL;
			cattr->la_valid |= LA_FLAGS;
		}
		cattr->la_valid |= LA_PROJID;
	}

	rc = mdd_name_check(env, m, lname);
	if (rc < 0)
		RETURN(rc);

	switch (cattr->la_mode & S_IFMT) {
	case S_IFLNK: {
		unsigned int symlen = spec->u.sp_symname.ln_namelen + 1;

		if (symlen > m->mdd_dt_conf.ddp_symlink_max)
			RETURN(-ENAMETOOLONG);
		else
			RETURN(0);
	}
	case S_IFDIR:
	case S_IFREG:
	case S_IFCHR:
	case S_IFBLK:
	case S_IFIFO:
	case S_IFSOCK:
		rc = 0;
		break;
	default:
		rc = -EINVAL;
		break;
	}
	RETURN(rc);
}

static int mdd_declare_create_object(const struct lu_env *env,
				     struct mdd_device *mdd,
				     struct mdd_object *p, struct mdd_object *c,
				     struct lu_attr *attr,
				     struct thandle *handle,
				     const struct md_op_spec *spec,
				     struct lu_buf *def_acl_buf,
				     struct lu_buf *acl_buf,
				     struct lu_buf *hsm_buf,
				     struct lu_buf *pin_buf,
				     struct dt_allocation_hint *hint)
{
	const struct lu_buf *buf;
	int rc;

#ifdef CONFIG_LUSTRE_FS_POSIX_ACL
	/* ldiskfs OSD needs this information for credit allocation */
	if (def_acl_buf)
		hint->dah_acl_len = def_acl_buf->lb_len;
#endif
	rc = mdd_declare_create_object_internal(env, p, c, attr, handle, spec,
						hint);
	if (rc)
		GOTO(out, rc);

#ifdef CONFIG_LUSTRE_FS_POSIX_ACL
	if (def_acl_buf && def_acl_buf->lb_len > 0 && S_ISDIR(attr->la_mode)) {
		/* if dir, then can inherit default ACl */
		rc = mdo_declare_xattr_set(env, c, def_acl_buf,
					   XATTR_NAME_ACL_DEFAULT,
					   0, handle);
		if (rc)
			GOTO(out, rc);
	}

	if (acl_buf && acl_buf->lb_len > 0) {
		rc = mdo_declare_attr_set(env, c, attr, handle);
		if (rc)
			GOTO(out, rc);

		rc = mdo_declare_xattr_set(env, c, acl_buf,
					   XATTR_NAME_ACL_ACCESS, 0, handle);
		if (rc)
			GOTO(out, rc);
	}
#endif
	rc = mdd_declare_object_initialize(env, p, c, attr, handle);
	if (rc)
		GOTO(out, rc);

	/* replay case, create LOV EA from client data */
	if ((!(spec->sp_cr_flags & MDS_OPEN_DELAY_CREATE) && spec->no_create) ||
	    (spec->sp_cr_flags & MDS_OPEN_HAS_EA && S_ISREG(attr->la_mode))) {
		buf = mdd_buf_get_const(env, spec->u.sp_ea.eadata,
					spec->u.sp_ea.eadatalen);
		rc = mdo_declare_xattr_set(env, c, buf,
					   S_ISDIR(attr->la_mode) ?
						XATTR_NAME_LMV : XATTR_NAME_LOV,
					   LU_XATTR_CREATE, handle);
		if (rc)
			GOTO(out, rc);

		if (spec->sp_cr_flags & MDS_OPEN_PCC) {
			rc = mdo_declare_xattr_set(env, c, hsm_buf,
						   XATTR_NAME_HSM,
						   0, handle);
			if (rc)
				GOTO(out, rc);
		}
	}

	if (S_ISLNK(attr->la_mode)) {
		const char *target_name = spec->u.sp_symname.ln_name;
		int sym_len = spec->u.sp_symname.ln_namelen;
		const struct lu_buf *buf;

		buf = mdd_buf_get_const(env, target_name, sym_len);
		rc = dt_declare_record_write(env, mdd_object_child(c),
					     buf, 0, handle);
		if (rc)
			GOTO(out, rc);
	}

	if (spec->sp_cr_file_secctx_name != NULL) {
		buf = mdd_buf_get_const(env, spec->sp_cr_file_secctx,
					spec->sp_cr_file_secctx_size);
		rc = mdo_declare_xattr_set(env, c, buf,
					   spec->sp_cr_file_secctx_name, 0,
					   handle);
		if (rc < 0)
			GOTO(out, rc);
	}

	if (spec->sp_cr_file_encctx != NULL) {
		buf = mdd_buf_get_const(env, spec->sp_cr_file_encctx,
					spec->sp_cr_file_encctx_size);
		rc = mdo_declare_xattr_set(env, c, buf,
					   LL_XATTR_NAME_ENCRYPTION_CONTEXT, 0,
					   handle);
		if (rc < 0)
			GOTO(out, rc);
	}

	/* Declare inheritance of parent's lustre.pin, if any. */
	if (pin_buf && pin_buf->lb_len > 0 &&
	    (S_ISREG(attr->la_mode) || S_ISDIR(attr->la_mode))) {
		rc = mdo_declare_xattr_set(env, c, pin_buf,
					   XATTR_NAME_PIN, 0, handle);
		if (rc)
			GOTO(out, rc);
	}
out:
	return rc;
}

static int mdd_declare_create(const struct lu_env *env, struct mdd_device *mdd,
			      struct mdd_object *p, struct mdd_object *c,
			      const struct lu_name *name,
			      struct lu_attr *attr,
			      struct thandle *handle,
			      const struct md_op_spec *spec,
			      struct linkea_data *ldata,
			      struct lu_buf *def_acl_buf,
			      struct lu_buf *acl_buf,
			      struct lu_buf *hsm_buf,
			      struct lu_buf *pin_buf,
			      struct dt_allocation_hint *hint)
{
	int rc;

	rc = mdd_declare_create_object(env, mdd, p, c, attr, handle, spec,
				       def_acl_buf, acl_buf, hsm_buf,
				       pin_buf, hint);
	if (rc)
		GOTO(out, rc);

	if (S_ISDIR(attr->la_mode)) {
		rc = mdo_declare_ref_add(env, p, handle);
		if (rc)
			GOTO(out, rc);
	}

	if (unlikely(spec->sp_cr_flags & MDS_OPEN_VOLATILE)) {
		rc = mdd_orphan_declare_insert(env, c, attr->la_mode, handle);
		if (rc)
			GOTO(out, rc);
	} else {
		struct lu_attr *la = &mdd_env_info(env)->mdi_la_for_fix;
		enum changelog_rec_type type;

		rc = mdo_declare_index_insert(env, p, mdd_object_fid(c),
					      attr->la_mode, name->ln_name,
					      handle);
		if (rc != 0)
			return rc;

		rc = mdd_declare_links_add(env, c, handle, ldata);
		if (rc)
			return rc;

		*la = *attr;
		la->la_valid = LA_CTIME | LA_MTIME;
		rc = mdo_declare_attr_set(env, p, la, handle);
		if (rc)
			return rc;

		type = S_ISDIR(attr->la_mode) ? CL_MKDIR :
		       S_ISREG(attr->la_mode) ? CL_CREATE :
		       S_ISLNK(attr->la_mode) ? CL_SOFTLINK : CL_MKNOD;

		rc = mdd_declare_changelog_store(env, mdd, type, name, NULL,
						 handle);
		if (rc)
			return rc;
	}
out:
	return rc;
}

static int mdd_acl_init(const struct lu_env *env, struct mdd_object *pobj,
			struct lu_attr *la, struct lu_buf *def_acl_buf,
			struct lu_buf *acl_buf)
{
	int rc;

	ENTRY;

	if (S_ISLNK(la->la_mode)) {
		acl_buf->lb_len = 0;
		def_acl_buf->lb_len = 0;
		RETURN(0);
	}

	mdd_read_lock(env, pobj, DT_TGT_PARENT);
	rc = mdo_xattr_get(env, pobj, def_acl_buf,
			   XATTR_NAME_ACL_DEFAULT);
	mdd_read_unlock(env, pobj);
	if (rc > 0) {
		/* ACL buffer size is not enough, need realloc */
		if (rc > acl_buf->lb_len)
			RETURN(-ERANGE);

		/* If there are default ACL, fix mode/ACL by default ACL */
		def_acl_buf->lb_len = rc;
		memcpy(acl_buf->lb_buf, def_acl_buf->lb_buf, rc);
		acl_buf->lb_len = rc;
		rc = __mdd_fix_mode_acl(env, acl_buf, &la->la_mode);
		if (rc < 0)
			RETURN(rc);
	} else if (rc == -ENODATA || rc == -EOPNOTSUPP) {
		/* If there are no default ACL, fix mode by mask */
		struct lu_ucred *uc = lu_ucred(env);

		/* The create triggered by MDT internal events, such as
		 * LFSCK reset, will not contain valid "uc".
		 */
		if (unlikely(uc != NULL))
			la->la_mode &= ~uc->uc_umask;
		rc = 0;
		acl_buf->lb_len = 0;
		def_acl_buf->lb_len = 0;
	}

	RETURN(rc);
}

#define XATTR_SIZE_MIN 256U

/*
 * Fetch parent directory lustre.pin (trusted.pin) value so it can be
 * inherited by newly created children.
 *
 * If the parent has no pin xattr or the backend does not support it,
 * this returns 0 and leaves @pin_buf empty. On other errors a negative
 * errno is returned.
 *
 * When a pin xattr is present, this helper allocates memory for @pin_buf
 * (via lu_buf_alloc()); it is the caller's responsibility to free that
 * buffer with lu_buf_free().
 */
static int mdd_pin_init(const struct lu_env *env, struct mdd_object *pobj,
			struct lu_buf *pin_buf)
{
	struct mdd_device *mdd = mdo2mdd(&pobj->mod_obj);
	unsigned int buf_size;
	int rc;

	ENTRY;

	LASSERT(pin_buf != NULL);

	/* Only directories can carry a default pin for their children. */
	if (!S_ISDIR(mdd_object_type(pobj)))
		RETURN(0);

	/*
	 * Start with a fixed buffer size; if that is too small (-ERANGE),
	 * grow to the maximum allowed EA size for this backend (capped by
	 * XATTR_SIZE_MAX) and retry once.
	 */
	buf_size = XATTR_SIZE_MIN;

retry:
	lu_buf_alloc(pin_buf, buf_size);
	if (pin_buf->lb_buf == NULL)
		RETURN(-ENOMEM);

	rc = mdo_xattr_get(env, pobj, pin_buf, XATTR_NAME_PIN);
	if (rc == -ERANGE && buf_size < XATTR_SIZE_MAX) {
		/* Buffer too small: enlarge it and retry. */
		lu_buf_free(pin_buf);
		pin_buf->lb_buf = NULL;
		pin_buf->lb_len = 0;
		buf_size =  min_t(unsigned int,
				  mdd->mdd_dt_conf.ddp_max_ea_size,
				  XATTR_SIZE_MAX);
		goto retry;
	}
	if (rc < 0) {
		/* Parent pin disappeared or backend stopped supporting it. */
		lu_buf_free(pin_buf);
		pin_buf->lb_buf = NULL;
		pin_buf->lb_len = 0;
		if (rc == -ENODATA || rc == -EOPNOTSUPP)
			/* No parent pin to inherit. */
			RETURN(0);
		RETURN(rc);
	}
	lu_buf_check_and_shrink(pin_buf, rc);

	RETURN(0);
}

/*
 * Create a metadata object and initialize it, set acl, xattr.
 */
static int mdd_create_object(const struct lu_env *env, struct mdd_object *pobj,
			     struct mdd_object *son, struct lu_attr *attr,
			     struct md_op_spec *spec, struct lu_buf *acl_buf,
			     struct lu_buf *def_acl_buf,
			     struct lu_buf *hsm_buf,
			     struct lu_buf *pin_buf,
			     struct dt_allocation_hint *hint,
			     struct thandle *handle, bool initial_create)
{
	const struct lu_fid *son_fid = mdd_object_fid(son);
	const struct lu_ucred *uc = lu_ucred(env);
	const char *jobid = uc->uc_jobid;
	const struct lu_buf *buf;
	size_t jobid_len;
	int rc;

	mdd_write_lock(env, son, DT_TGT_CHILD);
	rc = mdd_create_object_internal(env, NULL, son, attr, handle, spec,
					hint);
	if (rc)
		GOTO(unlock, rc);

	/* Note: In DNE phase I, for striped dir, though sub-stripes will be
	 * created in declare phase, they also needs to be added to master
	 * object as sub-directory entry. So it has to initialize the master
	 * object, then set dir striped EA.(in mdo_xattr_set)
	 */
	rc = mdd_object_initialize(env, mdd_object_fid(pobj), son, attr,
				   handle);
	if (rc != 0)
		GOTO(err_destroy, rc);

	/*
	 * in case of replay we just set LOVEA provided by the client
	 * XXX: I think it would be interesting to try "old" way where
	 *      MDT calls this xattr_set(LOV) in a different transaction.
	 *      probably this way we code can be made better.
	 */

	/* During creation, there are only a few cases we need do xattr_set to
	 * create stripes.
	 * 1. regular file: see comments above.
	 * 2. dir: inherit default striping or pool settings from parent.
	 * 3. create striped directory with provided stripeEA.
	 * 4. create striped directory because inherit default layout from the
	 * parent.
	 */
	if (spec->no_create ||
	    (S_ISREG(attr->la_mode) && spec->sp_cr_flags & MDS_OPEN_HAS_EA) ||
	    S_ISDIR(attr->la_mode)) {
		buf = mdd_buf_get_const(env, spec->u.sp_ea.eadata,
					spec->u.sp_ea.eadatalen);
		rc = mdo_xattr_set(env, son, buf,
				   S_ISDIR(attr->la_mode) ? XATTR_NAME_LMV :
							    XATTR_NAME_LOV,
				   LU_XATTR_CREATE, handle);
		if (rc != 0)
			GOTO(err_destroy, rc);
	}

	if (S_ISREG(attr->la_mode) && spec->sp_cr_flags & MDS_OPEN_PCC) {
		struct md_hsm mh;

		memset(&mh, 0, sizeof(mh));
		mh.mh_flags = HS_EXISTS | HS_ARCHIVED | HS_RELEASED;
		mh.mh_arch_id = spec->sp_archive_id;
		lustre_hsm2buf(hsm_buf->lb_buf, &mh);
		rc = mdo_xattr_set(env, son, hsm_buf, XATTR_NAME_HSM,
				   0, handle);
		if (rc != 0)
			GOTO(err_destroy, rc);
	}

#ifdef CONFIG_LUSTRE_FS_POSIX_ACL
	if (def_acl_buf != NULL && def_acl_buf->lb_len > 0 &&
	    S_ISDIR(attr->la_mode)) {
		/* set default acl */
		rc = mdo_xattr_set(env, son, def_acl_buf,
				   XATTR_NAME_ACL_DEFAULT, 0,
				   handle);
		if (rc)
			GOTO(err_destroy, rc);
	}
	/* set its own acl */
	if (acl_buf != NULL && acl_buf->lb_len > 0) {
		rc = mdo_xattr_set(env, son, acl_buf,
				   XATTR_NAME_ACL_ACCESS,
				   0, handle);
		if (rc)
			GOTO(err_destroy, rc);
	}
#endif

	/* Inherit parent's lustre.pin (trusted.pin) if present. */
	if (pin_buf && pin_buf->lb_len > 0 &&
	    (S_ISREG(attr->la_mode) || S_ISDIR(attr->la_mode))) {
		rc = mdo_xattr_set(env, son, pin_buf,
				   XATTR_NAME_PIN, 0, handle);
		if (rc)
			GOTO(err_destroy, rc);
	}

	if (S_ISLNK(attr->la_mode)) {
		struct dt_object *dt = mdd_object_child(son);
		const char *target_name = spec->u.sp_symname.ln_name;
		int sym_len = spec->u.sp_symname.ln_namelen;
		loff_t pos = 0;

		buf = mdd_buf_get_const(env, target_name, sym_len);
		rc = dt->do_body_ops->dbo_write(env, dt, buf, &pos, handle);
		if (rc == sym_len)
			rc = 0;
		else
			GOTO(err_initlized, rc = -EFAULT);
	}

	if (initial_create && spec->sp_cr_file_secctx_name != NULL) {
		buf = mdd_buf_get_const(env, spec->sp_cr_file_secctx,
					spec->sp_cr_file_secctx_size);
		rc = mdo_xattr_set(env, son, buf,
				   spec->sp_cr_file_secctx_name, 0,
				   handle);
		if (rc < 0)
			GOTO(err_initlized, rc);
	}

	if (spec->sp_cr_file_encctx != NULL) {
		buf = mdd_buf_get_const(env, spec->sp_cr_file_encctx,
					spec->sp_cr_file_encctx_size);
		rc = mdo_xattr_set(env, son, buf,
				   LL_XATTR_NAME_ENCRYPTION_CONTEXT, 0,
				   handle);
		if (rc < 0)
			GOTO(err_initlized, rc);
	}

	/* removes enclosing quotes from JobID before storing in xattr */
	if (initial_create && spec->sp_cr_job_xattr[0] != '\0' &&
	    jobid[0] != '\0' &&
	    (S_ISREG(attr->la_mode) || S_ISDIR(attr->la_mode))) {
		jobid_len = strnlen(jobid, LUSTRE_JOBID_SIZE);
		if (jobid[0] == '"' && jobid[jobid_len - 1] == '"' &&
		    jobid_len >= 2) {
			jobid++;
			jobid_len -= 2;
		}
		if (jobid_len > 0) {
			buf = mdd_buf_get_const(env, jobid, jobid_len);
			rc = mdo_xattr_set(env, son, buf,
					   spec->sp_cr_job_xattr, 0,
					   handle);
			/* this xattr is nonessential, ignore errors. */
			if (rc != 0) {
				CDEBUG(D_INODE, DFID
				       " failed to set '%s': rc = %d\n",
				       PFID(son_fid),
				       spec->sp_cr_job_xattr, rc);
				rc = 0;
			}
		}
	}

err_initlized:
	if (unlikely(rc != 0)) {
		int rc2;

		if (S_ISDIR(attr->la_mode)) {
			/* Drop reference, no need to delete "."/"..",
			 * because the object to be destroyed directly.
			 */
			rc2 = mdo_ref_del(env, son, handle);
			if (rc2 != 0)
				GOTO(unlock, rc);
		}
		rc2 = mdo_ref_del(env, son, handle);
		if (rc2 != 0)
			GOTO(unlock, rc);
err_destroy:
		mdo_destroy(env, son, handle);
	}
unlock:
	mdd_write_unlock(env, son);
	RETURN(rc);
}

static int mdd_index_delete(const struct lu_env *env,
			    struct mdd_object *mdd_pobj,
			    struct lu_attr *cattr,
			    const struct lu_name *lname)
{
	struct mdd_device *mdd = mdo2mdd(&mdd_pobj->mod_obj);
	struct thandle *handle;
	int rc;

	ENTRY;

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		RETURN(PTR_ERR(handle));

	rc = mdo_declare_index_delete(env, mdd_pobj, lname->ln_name,
				      handle);
	if (rc != 0)
		GOTO(stop, rc);

	if (S_ISDIR(cattr->la_mode)) {
		rc = mdo_declare_ref_del(env, mdd_pobj, handle);
		if (rc != 0)
			GOTO(stop, rc);
	}

	/* Since this will only be used in the error handler path,
	 * Let's set the thandle to be local and not mess the transno
	 */
	handle->th_local = 1;
	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop, rc);

	rc = __mdd_index_delete(env, mdd_pobj, lname->ln_name,
				S_ISDIR(cattr->la_mode), handle);
	if (rc)
		GOTO(stop, rc);
stop:
	rc = mdd_trans_stop(env, mdd, rc, handle);

	RETURN(rc);
}

/**
 * mdd_create() - Create object and insert it into namespace.
 * @env: execution environment
 * @pobj: parent object
 * @lname: name of child being created
 * @child: child object being created [in, out]
 * @spec: additional create parameters
 * @ma: attributes for new child object
 *
 * Two operations have to be performed:
 *
 *  - an allocation of a new object (->do_create()), and
 *  - an insertion into a parent index (->dio_insert()).
 *
 * Due to locking, operation order is not important, when both are
 * successful, *but* error handling cases are quite different:
 *
 *  - if insertion is done first, and following object creation fails,
 *  insertion has to be rolled back, but this operation might fail
 *  also leaving us with dangling index entry.
 *
 *  - if creation is done first, is has to be undone if insertion fails,
 *  leaving us with leaked space, which is not good but not fatal.
 *
 * It seems that creation-first is simplest solution, but it is sub-optimal
 * in the frequent
 *
 * $ mkdir foo
 * $ mkdir foo
 *
 * case, because second mkdir is bound to create object, only to
 * destroy it immediately.
 *
 * To avoid this follow local file systems that do double lookup:
 *
 * 0. lookup -> -EEXIST (mdd_create_sanity_check())
 * 1. create            (mdd_create_object_internal())
 * 2. insert            (__mdd_index_insert(), lookup again)
 *
 * Return:
 * * %0 on success
 * * %negative errno on failure
 */
int mdd_create(const struct lu_env *env, struct md_object *pobj,
		      const struct lu_name *lname, struct md_object *child,
		      struct md_op_spec *spec, struct md_attr *ma)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct lu_attr *la = &info->mdi_la_for_fix;
	struct mdd_object *mdd_pobj = md2mdd_obj(pobj);
	struct mdd_object *son = md2mdd_obj(child);
	struct mdd_device *mdd = mdo2mdd(pobj);
	struct lu_attr *attr = &ma->ma_attr;
	struct thandle *handle;
	struct lu_attr *pattr = &info->mdi_pattr;
	struct lu_buf acl_buf;
	struct lu_buf def_acl_buf;
	struct lu_buf hsm_buf;
	struct lu_buf pin_buf = { NULL, 0 };
	struct linkea_data *ldata = &info->mdi_link_data;
	const char *name = lname->ln_name;
	struct dt_allocation_hint *hint = &mdd_env_info(env)->mdi_hint;
	int acl_size = LUSTRE_POSIX_ACL_MAX_SIZE_OLD;
	bool name_inserted = false;
	int rc, rc2;

	ENTRY;

	rc = mdd_la_get(env, mdd_pobj, pattr);
	if (rc != 0)
		RETURN(rc);

	/* Sanity checks before big job. */
	rc = mdd_create_sanity_check(env, pobj, pattr, lname, attr, spec);
	if (unlikely(rc == -EEXIST && S_ISDIR(attr->la_mode) &&
		     spec->sp_replay && mdd_object_remote(mdd_pobj)))
		/* if it's replay by client request, and name is found in
		 * parent directory on remote MDT, it means mkdir was partially
		 * executed: name was successfully added, but target not.
		 */
		name_inserted = true;
	else if (rc)
		RETURN(rc);

	if (CFS_FAIL_CHECK(OBD_FAIL_MDS_DQACQ_NET))
		GOTO(out_free, rc = -EINPROGRESS);

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		GOTO(out_free, rc = PTR_ERR(handle));

use_bigger_buffer:
	acl_buf = *lu_buf_check_and_alloc(&info->mdi_xattr_buf, acl_size);
	if (!acl_buf.lb_buf)
		GOTO(out_stop, rc = -ENOMEM);

	def_acl_buf = *lu_buf_check_and_alloc(&info->mdi_big_buf, acl_size);
	if (!def_acl_buf.lb_buf)
		GOTO(out_stop, rc = -ENOMEM);

	rc = mdd_acl_init(env, mdd_pobj, attr, &def_acl_buf, &acl_buf);
	if (unlikely(rc == -ERANGE &&
		     acl_size == LUSTRE_POSIX_ACL_MAX_SIZE_OLD)) {
		/* use maximum-sized xattr buffer for too-big default ACL */
		acl_size = min_t(unsigned int, mdd->mdd_dt_conf.ddp_max_ea_size,
				 XATTR_SIZE_MAX);
		goto use_bigger_buffer;
	}
	if (rc < 0)
		GOTO(out_stop, rc);

	/* Prepare inheritance of parent's lustre.pin for new files/dirs. */
	if (S_ISREG(attr->la_mode) || S_ISDIR(attr->la_mode)) {
		rc = mdd_pin_init(env, mdd_pobj, &pin_buf);
		if (rc < 0)
			GOTO(out_stop, rc);
	}

	/* adjust stripe count to 0 for 'lfs mkdir -c 1 ...' to avoid creating
	 * 1-stripe directory, MDS_OPEN_DEFAULT_LMV means ea is default LMV.
	 */
	if (unlikely(S_ISDIR(attr->la_mode) && spec->u.sp_ea.eadata &&
		     !(spec->sp_cr_flags & MDS_OPEN_DEFAULT_LMV))) {
		struct lmv_user_md *lmu = spec->u.sp_ea.eadata;

		/* migrate may create 1-stripe directory, adjust stripe count
		 * before lod_ah_init().
		 */
		if (lmu && lmu->lum_magic == cpu_to_le32(LMV_USER_MAGIC) &&
		    lmu->lum_stripe_count == cpu_to_le32(1))
			lmu->lum_stripe_count = 0;
	}

	mdd_object_make_hint(env, mdd_pobj, son, attr, spec, hint);

	memset(ldata, 0, sizeof(*ldata));
	if (CFS_FAIL_CHECK(OBD_FAIL_LFSCK_BAD_PARENT)) {
		struct lu_fid tfid = *mdd_object_fid(mdd_pobj);

		tfid.f_oid--;
		rc = mdd_linkea_prepare(env, son, NULL, NULL,
					&tfid, lname, 1, 0, ldata);
	} else {
		rc = mdd_linkea_prepare(env, son, NULL, NULL,
					mdd_object_fid(mdd_pobj),
					lname, 1, 0, ldata);
	}

	if (spec->sp_cr_flags & MDS_OPEN_PCC) {
		LASSERT(spec->sp_cr_flags & MDS_OPEN_HAS_EA);

		memset(&hsm_buf, 0, sizeof(hsm_buf));
		lu_buf_alloc(&hsm_buf, sizeof(struct hsm_attrs));
		if (hsm_buf.lb_buf == NULL)
			GOTO(out_stop, rc = -ENOMEM);
	}

	rc = mdd_declare_create(env, mdd, mdd_pobj, son, lname, attr,
				handle, spec, ldata, &def_acl_buf, &acl_buf,
				&hsm_buf, &pin_buf, hint);
	if (rc)
		GOTO(out_stop, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(out_stop, rc);

	rc = mdd_create_object(env, mdd_pobj, son, attr, spec, &acl_buf,
			       &def_acl_buf, &hsm_buf, &pin_buf,
			       hint, handle, true);
	if (rc != 0)
		GOTO(out_stop, rc);

	if (unlikely(spec->sp_cr_flags & MDS_OPEN_VOLATILE)) {
		mdd_write_lock(env, son, DT_TGT_CHILD);
		son->mod_flags |= VOLATILE_OBJ;
		rc = mdd_orphan_insert(env, son, handle);
		GOTO(out_volatile, rc);
	} else {
		if (likely(!name_inserted)) {
			rc = __mdd_index_insert(env, mdd_pobj,
						mdd_object_fid(son),
						attr->la_mode, name, handle);
			if (rc != 0)
				GOTO(err_created, rc);
		}

		mdd_links_add(env, son, mdd_object_fid(mdd_pobj), lname,
			      handle, ldata, 1);

		/* update parent directory mtime/ctime */
		*la = *attr;
		la->la_valid = LA_CTIME | LA_MTIME;
		rc = mdd_update_time(env, mdd_pobj, pattr, la, handle);
		if (rc)
			GOTO(err_insert, rc);
	}

	EXIT;
err_insert:
	if (rc != 0) {
		if (spec->sp_cr_flags & MDS_OPEN_VOLATILE)
			rc2 = mdd_orphan_delete(env, son, handle);
		else
			rc2 = __mdd_index_delete(env, mdd_pobj, name,
						 S_ISDIR(attr->la_mode),
						 handle);
		if (rc2 != 0)
			goto out_stop;

err_created:
		mdd_write_lock(env, son, DT_TGT_CHILD);
		if (S_ISDIR(attr->la_mode)) {
			/* Drop the reference, no need to delete "."/"..",
			 * because the object is to be destroyed directly.
			 */
			rc2 = mdo_ref_del(env, son, handle);
			if (rc2 != 0) {
				mdd_write_unlock(env, son);
				goto out_stop;
			}
		}
out_volatile:
		/* For volatile files drop one link immediately, since there is
		 * no filename in the namespace, and save any error returned.
		 */
		rc2 = mdo_ref_del(env, son, handle);
		if (rc2 != 0) {
			mdd_write_unlock(env, son);
			if (unlikely(rc == 0))
				rc = rc2;
			goto out_stop;
		}

		/* Don't destroy the volatile object on success */
		if (likely(rc != 0))
			mdo_destroy(env, son, handle);
		mdd_write_unlock(env, son);
	}

	if (rc == 0 && fid_is_namespace_visible(mdd_object_fid(son)) &&
	    likely((spec->sp_cr_flags & MDS_OPEN_VOLATILE) == 0))
		rc = mdd_changelog_ns_store(env, mdd,
				S_ISDIR(attr->la_mode) ? CL_MKDIR :
				S_ISREG(attr->la_mode) ? CL_CREATE :
				S_ISLNK(attr->la_mode) ? CL_SOFTLINK : CL_MKNOD,
				0, son, mdd_pobj, pattr, NULL, NULL, NULL,
				lname, NULL, handle);
out_stop:
	rc2 = mdd_trans_stop(env, mdd, rc, handle);
	if (rc == 0) {
		/* If creation fails, it is most likely due to the remote update
		 * failure, because local transaction will mostly succeed at
		 * this stage. There is no easy way to rollback all of previous
		 * updates, so let's remove the object from namespace, and
		 * LFSCK should handle the orphan object.
		 */
		if (rc2 < 0 && !mdd_object_remote(mdd_pobj))
			mdd_index_delete(env, mdd_pobj, attr, lname);
		rc = rc2;
	}
out_free:
	if (is_vmalloc_addr(ldata->ld_buf))
		/* if we vmalloced a large buffer drop it */
		lu_buf_free(ldata->ld_buf);

	if (spec->sp_cr_flags & MDS_OPEN_PCC)
		lu_buf_free(&hsm_buf);
	if (pin_buf.lb_buf)
		lu_buf_free(&pin_buf);

	/* The child object shouldn't be cached anymore */
	if (rc)
		set_bit(LU_OBJECT_HEARD_BANSHEE,
			&child->mo_lu.lo_header->loh_flags);
	return rc;
}

/* has not mdd_write{read}_lock on any obj yet. */
static int mdd_rename_sanity_check(const struct lu_env *env,
				   struct mdd_object *src_pobj,
				   const struct lu_attr *spattr,
				   struct mdd_object *tgt_pobj,
				   const struct lu_attr *tpattr,
				   struct mdd_object *sobj,
				   const struct lu_attr *sattr,
				   struct mdd_object *tobj,
				   const struct lu_attr *tattr)
{
	int rc = 0;

	ENTRY;

	/* XXX: when get here, sobj must NOT be NULL,
	 * the other case has been processed in cld_rename
	 * before mdd_rename and enable MDS_PERM_BYPASS.
	 */
	LASSERT(sobj);

	/*
	 * If we are using project inheritance, we only allow renames
	 * into our tree when the project IDs are the same; otherwise
	 * tree quota mechanism would be circumvented.
	 */
	if ((tpattr->la_flags & LUSTRE_PROJINHERIT_FL) &&
	     tpattr->la_projid != sattr->la_projid && S_ISDIR(sattr->la_mode))
		RETURN(-EXDEV);

	/* we prevent an encrypted file from being renamed
	 * into an unencrypted dir
	 */
	if ((spattr->la_valid & LA_FLAGS &&
	     spattr->la_flags & LUSTRE_ENCRYPT_FL) &&
	    !(tpattr->la_valid & LA_FLAGS &&
	      tpattr->la_flags & LUSTRE_ENCRYPT_FL))
		RETURN(-EXDEV);

	rc = mdd_may_delete(env, src_pobj, spattr, sobj, sattr, NULL, 1, 0);
	if (rc)
		RETURN(rc);

	/* XXX: when get here, "tobj == NULL" means tobj must
	 * NOT exist (neither on remote MDS, such case has been
	 * processed in cld_rename before mdd_rename and enable
	 * MDS_PERM_BYPASS).
	 * So check may_create, but not check may_unlink.
	 */
	if (tobj == NULL)
		rc = mdd_may_create(env, tgt_pobj, tpattr, NULL,
				    (src_pobj != tgt_pobj));
	else
		rc = mdd_may_delete(env, tgt_pobj, tpattr, tobj, tattr, sattr,
				    (src_pobj != tgt_pobj), 1);

	if (!rc && !tobj && (src_pobj != tgt_pobj) && S_ISDIR(sattr->la_mode))
		rc = __mdd_may_link(env, tgt_pobj, tpattr);

	RETURN(rc);
}

static
int mdd_declare_rename(const struct lu_env *env, struct mdd_device *mdd,
		     struct mdd_object *mdd_spobj, struct mdd_object *mdd_tpobj,
		     struct mdd_object *mdd_sobj, struct mdd_object *mdd_tobj,
		     const struct lu_name *sname, const struct lu_name *tname,
		     struct md_attr *ma, struct linkea_data *ldata,
		     bool change_projid, struct thandle *handle)
{
	struct lu_attr *la = &mdd_env_info(env)->mdi_la_for_fix;
	int rc;

	LASSERT(ma->ma_attr.la_valid & LA_CTIME);
	la->la_ctime = la->la_mtime = ma->ma_attr.la_ctime;

	LASSERT(mdd_spobj);
	LASSERT(mdd_tpobj);
	LASSERT(mdd_sobj);

	/* name from source dir */
	rc = mdo_declare_index_delete(env, mdd_spobj, sname->ln_name, handle);
	if (rc)
		return rc;

	/* .. from source child */
	if (S_ISDIR(mdd_object_type(mdd_sobj))) {
		/* source child can be directory, count by source dir's nlink */
		rc = mdo_declare_ref_del(env, mdd_spobj, handle);
		if (rc)
			return rc;
		if (mdd_spobj != mdd_tpobj) {
			rc = mdo_declare_index_delete(env, mdd_sobj, dotdot,
						      handle);
			if (rc != 0)
				return rc;

			rc = mdo_declare_index_insert(env, mdd_sobj,
						      mdd_object_fid(mdd_tpobj),
						      S_IFDIR, dotdot, handle);
			if (rc != 0)
				return rc;
		}

		/* new target child can be dir, counted by target dir's nlink */
		rc = mdo_declare_ref_add(env, mdd_tpobj, handle);
		if (rc != 0)
			return rc;
	}

	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdo_declare_attr_set(env, mdd_spobj, la, handle);
	if (rc != 0)
		return rc;

	rc = mdo_declare_attr_set(env, mdd_tpobj, la, handle);
	if (rc != 0)
		return rc;

	la->la_valid = LA_CTIME;
	if (change_projid)
		la->la_valid |= LA_PROJID;
	rc = mdo_declare_attr_set(env, mdd_sobj, la, handle);
	if (rc)
		return rc;

	rc = mdd_declare_links_add(env, mdd_sobj, handle, ldata);
	if (rc)
		return rc;

	/* new name */
	rc = mdo_declare_index_insert(env, mdd_tpobj, mdd_object_fid(mdd_sobj),
				      mdd_object_type(mdd_sobj),
				      tname->ln_name, handle);
	if (rc != 0)
		return rc;

	if (mdd_tobj && mdd_object_exists(mdd_tobj)) {
		/* delete target child in target parent directory */
		rc = mdo_declare_index_delete(env, mdd_tpobj, tname->ln_name,
					      handle);
		if (rc)
			return rc;

		rc = mdo_declare_ref_del(env, mdd_tobj, handle);
		if (rc)
			return rc;

		if (S_ISDIR(mdd_object_type(mdd_tobj))) {
			/* target child can be directory,
			 * delete "." reference in target child directory
			 */
			rc = mdo_declare_ref_del(env, mdd_tobj, handle);
			if (rc)
				return rc;

			/* delete ".." reference in target parent directory */
			rc = mdo_declare_ref_del(env, mdd_tpobj, handle);
			if (rc)
				return rc;
		}

		la->la_valid = LA_CTIME;
		rc = mdo_declare_attr_set(env, mdd_tobj, la, handle);
		if (rc)
			return rc;

		rc = mdd_declare_finish_unlink(env, mdd_tobj, handle);
		if (rc)
			return rc;
	}

	rc = mdd_declare_changelog_store(env, mdd, CL_RENAME, tname, sname,
					 handle);
	if (rc)
		return rc;

	return rc;
}

static
int mdd_migrate_object(const struct lu_env *env, struct mdd_object *spobj,
		       struct mdd_object *tpobj, struct mdd_object *sobj,
		       struct mdd_object *tobj, const struct lu_name *sname,
		       const struct lu_name *tname, struct md_op_spec *spec,
		       struct md_attr *ma);

/* src object can be remote that is why we use only fid and type of object */
static int mdd_rename(const struct lu_env *env,  struct md_object *src_pobj,
		      struct md_object *tgt_pobj, const struct lu_fid *lf,
		      const struct lu_name *lsname, struct md_object *tobj,
		      const struct lu_name *ltname, struct md_attr *ma)
{
	const char *sname = lsname->ln_name;
	const char *tname = ltname->ln_name;
	struct lu_attr *la = &mdd_env_info(env)->mdi_la_for_fix;
	struct mdd_object *mdd_spobj = md2mdd_obj(src_pobj); /* source parent */
	struct mdd_object *mdd_tpobj = md2mdd_obj(tgt_pobj); /* target parent */
	struct mdd_object *mdd_sobj = NULL;		     /* source object */
	struct mdd_object *mdd_tobj = NULL;	  /* (possible) target object */
	struct lu_attr *sattr = MDD_ENV_VAR(env, cattr);
	struct lu_attr *spattr = MDD_ENV_VAR(env, pattr);
	struct lu_attr *tattr = MDD_ENV_VAR(env, tattr);
	struct lu_attr *tpattr = MDD_ENV_VAR(env, tpattr);
	struct linkea_data *ldata = &mdd_env_info(env)->mdi_link_data;
	const struct lu_fid *tpobj_fid = mdd_object_fid(mdd_tpobj);
	const struct lu_fid *spobj_fid = mdd_object_fid(mdd_spobj);
	struct mdd_device *mdd = mdo2mdd(src_pobj);
	struct thandle *handle;
	bool is_dir;
	bool tobj_ref = 0;
	bool tobj_locked = 0;
	bool change_projid = false;
	unsigned int cl_flags = 0;
	int rc, rc2;

	ENTRY;

	/* let unlink to complete and commit */
	CFS_FAIL_TIMEOUT(OBD_FAIL_TGT_REPLY_DATA_RACE, 2 + cfs_fail_val);

	if (tobj)
		mdd_tobj = md2mdd_obj(tobj);

	mdd_sobj = mdd_object_find(env, mdd, lf);
	if (IS_ERR(mdd_sobj))
		RETURN(PTR_ERR(mdd_sobj));

	rc = mdd_la_get(env, mdd_sobj, sattr);
	if (rc)
		GOTO(out_pending, rc);

	/* if rename is cross MDTs, migrate symlink if it doesn't have other
	 * hard links, and target doesn't exist.
	 */
	if (mdd_object_remote(mdd_sobj) && S_ISLNK(sattr->la_mode) &&
	    sattr->la_nlink == 1 && !tobj) {
		struct md_op_spec *spec = &mdd_env_info(env)->mdi_spec;
		struct lu_device *ld = &mdd->mdd_md_dev.md_lu_dev;
		struct lu_fid tfid;

		rc = ld->ld_ops->ldo_fid_alloc(env, ld, &tfid, &tgt_pobj->mo_lu,
					       NULL);
		if (rc < 0)
			GOTO(out_pending, rc);

		mdd_tobj = mdd_object_find(env, mdd, &tfid);
		if (IS_ERR(mdd_tobj))
			GOTO(out_pending, rc = PTR_ERR(mdd_tobj));

		memset(spec, 0, sizeof(*spec));
		rc = mdd_migrate_object(env, mdd_spobj, mdd_tpobj, mdd_sobj,
					mdd_tobj, lsname, ltname, spec, ma);
		mdd_object_put(env, mdd_tobj);
		GOTO(out_pending, rc);
	}

	rc = mdd_la_get(env, mdd_spobj, spattr);
	if (rc)
		GOTO(out_pending, rc);

	if (mdd_tobj) {
		rc = mdd_la_get(env, mdd_tobj, tattr);
		if (rc)
			GOTO(out_pending, rc);
		/* search for an existing archive.  we should check ahead as the
		 * object can be destroyed in this transaction
		 */
		if (mdd_hsm_archive_exists(env, mdd_tobj, ma))
			cl_flags |= CLF_RENAME_LAST_EXISTS;
	}

	rc = mdd_la_get(env, mdd_tpobj, tpattr);
	if (rc)
		GOTO(out_pending, rc);

	rc = mdd_rename_sanity_check(env, mdd_spobj, spattr, mdd_tpobj, tpattr,
				     mdd_sobj, sattr, mdd_tobj, tattr);
	if (rc)
		GOTO(out_pending, rc);

	rc = mdd_name_check(env, mdd, ltname);
	if (rc < 0)
		GOTO(out_pending, rc);

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		GOTO(out_pending, rc = PTR_ERR(handle));

	memset(ldata, 0, sizeof(*ldata));
	rc = mdd_linkea_prepare(env, mdd_sobj, spobj_fid, lsname, tpobj_fid,
				ltname, 1, 0, ldata);
	if (rc)
		GOTO(stop, rc);

	if (tpattr->la_projid != sattr->la_projid &&
	    tpattr->la_flags & LUSTRE_PROJINHERIT_FL)
		change_projid = true;

	rc = mdd_declare_rename(env, mdd, mdd_spobj, mdd_tpobj, mdd_sobj,
				mdd_tobj, lsname, ltname, ma, ldata,
				change_projid, handle);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop, rc);

	is_dir = S_ISDIR(sattr->la_mode);

	/* Remove source name from source directory */
	rc = __mdd_index_delete(env, mdd_spobj, sname, is_dir, handle);
	if (rc)
		GOTO(stop, rc);

	/* "mv dir1 dir2" needs "dir1/.." link update */
	if (is_dir && !lu_fid_eq(spobj_fid, tpobj_fid)) {
		rc = __mdd_index_delete_only(env, mdd_sobj, dotdot, handle);
		if (rc)
			GOTO(fixup_spobj2, rc);

		rc = __mdd_index_insert_only(env, mdd_sobj, tpobj_fid, S_IFDIR,
					     dotdot, handle);
		if (rc)
			GOTO(fixup_spobj, rc);
	}

	if (mdd_tobj != NULL && mdd_object_exists(mdd_tobj)) {
		rc = __mdd_index_delete(env, mdd_tpobj, tname, is_dir, handle);
		if (rc)
			/* tname might been renamed to something else */
			GOTO(fixup_spobj, rc);
	}

	/* Insert new fid with target name into target dir */
	rc = __mdd_index_insert(env, mdd_tpobj, lf, sattr->la_mode,
				tname, handle);
	if (rc)
		GOTO(fixup_tpobj, rc);

	LASSERT(ma->ma_attr.la_valid & LA_CTIME);
	la->la_ctime = la->la_mtime = ma->ma_attr.la_ctime;

	/* XXX: mdd_sobj must be local one if it is NOT NULL. */
	la->la_valid = LA_CTIME;
	if (change_projid) {
		/* mdd_update_time honors other valid flags except TIME ones */
		la->la_valid |= LA_PROJID;
		la->la_projid = tpattr->la_projid;
	}
	rc = mdd_update_time(env, mdd_sobj, sattr, la, handle);
	if (rc)
		GOTO(fixup_tpobj, rc);

	/* Update the linkEA for the source object */
	mdd_write_lock(env, mdd_sobj, DT_SRC_CHILD);
	rc = mdd_links_rename(env, mdd_sobj, mdd_object_fid(mdd_spobj),
			      lsname, mdd_object_fid(mdd_tpobj), ltname,
			      handle, ldata, 0, 0);
	if (rc == -ENOENT)
		/* Old files might not have EA entry */
		mdd_links_add(env, mdd_sobj, mdd_object_fid(mdd_spobj),
			      lsname, handle, NULL, 0);
	mdd_write_unlock(env, mdd_sobj);
	/* We don't fail the transaction if the link ea can't be
	 * updated -- fid2path will use alternate lookup method.
	 */
	rc = 0;

	/* Remove old target object
	 * For tobj is remote case cmm layer has processed
	 * and set tobj to NULL then. So when tobj is NOT NULL,
	 * it must be local one.
	 */
	if (tobj && mdd_object_exists(mdd_tobj)) {
		mdd_write_lock(env, mdd_tobj, DT_TGT_CHILD);
		tobj_locked = 1;
		if (mdd_is_dead_obj(mdd_tobj)) {
			/* should not be dead, something is wrong */
			rc = -EINVAL;
			CERROR("%s: something bad, dead tobj "DFID": rc = %d\n",
			       mdd2obd_dev(mdd)->obd_name, PFID(tpobj_fid), rc);
			goto cleanup;
		}
		mdo_ref_del(env, mdd_tobj, handle);

		/* Remove dot reference. */
		if (S_ISDIR(tattr->la_mode))
			mdo_ref_del(env, mdd_tobj, handle);
		tobj_ref = 1;

		/* fetch updated nlink */
		rc = mdd_la_get(env, mdd_tobj, tattr);
		if (rc) {
			CERROR("%s: failed get nlink of tobj "DFID": rc = %d\n",
			       mdd2obd_dev(mdd)->obd_name, PFID(tpobj_fid), rc);
			GOTO(fixup_tpobj, rc);
		}

		la->la_valid = LA_CTIME;
		rc = mdd_update_time(env, mdd_tobj, tattr, la, handle);
		if (rc) {
			CERROR("%s: failed set ctime of tobj "DFID": rc = %d\n",
			       mdd2obd_dev(mdd)->obd_name, PFID(tpobj_fid), rc);
			GOTO(fixup_tpobj, rc);
		}

		/* XXX: this transfer to ma will be removed with LOD/OSP */
		ma->ma_attr = *tattr;
		ma->ma_valid |= MA_INODE;
		rc = mdd_finish_unlink(env, mdd_tobj, ma, mdd_tpobj, ltname,
				       handle);
		if (rc) {
			CERROR("%s: failed to unlink tobj "DFID": rc = %d\n",
			       mdd2obd_dev(mdd)->obd_name, PFID(tpobj_fid), rc);
			GOTO(fixup_tpobj, rc);
		}

		/* fetch updated nlink */
		rc = mdd_la_get(env, mdd_tobj, tattr);
		if (rc == -ENOENT) {
			/* object removed? return the latest known attributes */
			tattr->la_nlink = 0;
			rc = 0;
		} else if (rc) {
			CERROR("%s: failed get nlink of tobj "DFID": rc = %d\n",
			       mdd2obd_dev(mdd)->obd_name, PFID(tpobj_fid), rc);
			GOTO(fixup_tpobj, rc);
		}
		/* XXX: this transfer to ma will be removed with LOD/OSP */
		ma->ma_attr = *tattr;
		ma->ma_valid |= MA_INODE;

		if (tattr->la_nlink == 0)
			cl_flags |= CLF_RENAME_LAST;
		else
			cl_flags &= ~CLF_RENAME_LAST_EXISTS;
	}

	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdd_update_time(env, mdd_spobj, spattr, la, handle);
	if (rc)
		GOTO(fixup_tpobj, rc);

	if (mdd_spobj != mdd_tpobj) {
		la->la_valid = LA_CTIME | LA_MTIME;
		rc = mdd_update_time(env, mdd_tpobj, tpattr, la, handle);
		if (rc != 0)
			GOTO(fixup_tpobj, rc);
	}

	EXIT;

fixup_tpobj:
	if (rc) {
		rc2 = __mdd_index_delete(env, mdd_tpobj, tname, is_dir, handle);
		if (rc2)
			CWARN("%s: tpobj "DFID" fix error: rc = %d\n",
			      mdd2obd_dev(mdd)->obd_name, PFID(tpobj_fid), rc2);

		if (mdd_tobj && mdd_object_exists(mdd_tobj) &&
		    !mdd_is_dead_obj(mdd_tobj)) {
			if (tobj_ref) {
				mdo_ref_add(env, mdd_tobj, handle);
				if (is_dir)
					mdo_ref_add(env, mdd_tobj, handle);
			}

			rc2 = __mdd_index_insert(env, mdd_tpobj,
						 mdd_object_fid(mdd_tobj),
						 mdd_object_type(mdd_tobj),
						 tname, handle);
			if (rc2)
				CWARN("%s: tpobj "DFID" fix error: rc = %d\n",
				      mdd2obd_dev(mdd)->obd_name,
				      PFID(tpobj_fid), rc2);
		}
	}

fixup_spobj:
	if (rc && is_dir && mdd_sobj && mdd_spobj != mdd_tpobj) {
		rc2 = __mdd_index_delete_only(env, mdd_sobj, dotdot, handle);
		if (rc2)
			CWARN("%s: spobj "DFID" dotdot delete error: rc = %d\n",
			      mdd2obd_dev(mdd)->obd_name, PFID(spobj_fid), rc2);


		rc2 = __mdd_index_insert_only(env, mdd_sobj, spobj_fid, S_IFDIR,
					      dotdot, handle);
		if (rc2)
			CWARN("%s: spobj "DFID" dotdot insert error: rc = %d\n",
			      mdd2obd_dev(mdd)->obd_name, PFID(spobj_fid), rc2);
	}

fixup_spobj2:
	if (rc) {
		rc2 = __mdd_index_insert(env, mdd_spobj, lf,
					 mdd_object_type(mdd_sobj), sname,
					 handle);
		if (rc2)
			CWARN("%s: spobj "DFID" fix error: rc = %d\n",
			      mdd2obd_dev(mdd)->obd_name, PFID(spobj_fid), rc2);
	}

cleanup:
	if (tobj_locked)
		mdd_write_unlock(env, mdd_tobj);

	if (rc == 0)
		rc = mdd_changelog_ns_store(env, mdd, CL_RENAME, cl_flags,
					    mdd_tobj, mdd_tpobj, tpattr, lf,
					    mdd_spobj, spattr, ltname, lsname,
					    handle);

stop:
	rc = mdd_trans_stop(env, mdd, rc, handle);

out_pending:
	mdd_object_put(env, mdd_sobj);

	return rc;
}

/*
 * Check whether we should migrate the file/dir
 *
 * Return:
 * * %negative permission check failed or other error.
 * * %0 the file can be migrated.
 */
static int mdd_migrate_sanity_check(const struct lu_env *env,
				    struct mdd_device *mdd,
				    struct mdd_object *spobj,
				    struct mdd_object *tpobj,
				    struct mdd_object *sobj,
				    struct mdd_object *tobj,
				    const struct lu_attr *spattr,
				    const struct lu_attr *tpattr,
				    const struct lu_attr *attr,
				    bool nsonly)
{
	int rc;

	ENTRY;

	if (!nsonly && !mdd_object_remote(sobj)) {
		mdd_read_lock(env, sobj, DT_SRC_CHILD);
		if (sobj->mod_count > 0) {
			CDEBUG(D_INFO, "%s: "DFID" is opened, count %d\n",
			       mdd_obj_dev_name(sobj),
			       PFID(mdd_object_fid(sobj)),
			       sobj->mod_count);
			mdd_read_unlock(env, sobj);
			RETURN(-EBUSY);
		}
		mdd_read_unlock(env, sobj);
	}

	if (mdd_object_exists(tobj))
		RETURN(-EEXIST);

	rc = mdd_may_delete(env, spobj, spattr, sobj, attr, NULL, 1, 0);
	if (rc)
		RETURN(rc);

	rc = mdd_may_create(env, tpobj, tpattr, NULL, true);

	RETURN(rc);
}

struct mdd_xattr_entry {
	struct list_head	mxe_linkage;
	char		       *mxe_name;
	struct lu_buf		mxe_buf;
};

struct mdd_xattrs {
	struct lu_buf		mx_namebuf;
	struct list_head	mx_list;
};

static inline void mdd_xattrs_init(struct mdd_xattrs *xattrs)
{
	INIT_LIST_HEAD(&xattrs->mx_list);
	xattrs->mx_namebuf.lb_buf = NULL;
	xattrs->mx_namebuf.lb_len = 0;
}

static inline void mdd_xattrs_fini(struct mdd_xattrs *xattrs)
{
	struct mdd_xattr_entry *entry;
	struct mdd_xattr_entry *tmp;

	list_for_each_entry_safe(entry, tmp, &xattrs->mx_list, mxe_linkage) {
		lu_buf_free(&entry->mxe_buf);
		list_del(&entry->mxe_linkage);
		OBD_FREE_PTR(entry);
	}

	lu_buf_free(&xattrs->mx_namebuf);
}

/* read xattrs into buf, but skip LMA, LMV, LINKEA if 'skip_linkea' is
 * set, and DMV if 'skip_dmv" is set.
 */
static int mdd_xattrs_migrate_prep(const struct lu_env *env,
				   struct mdd_xattrs *xattrs,
				   struct mdd_object *sobj,
				   struct mdd_object *tobj,
				   bool skip_linkea,
				   bool skip_dmv)
{
	struct lu_attr *attr = MDD_ENV_VAR(env, cattr);
	struct mdd_xattr_entry *entry;
	struct ost_id oi = {0, };
	bool needencxattr = false;
	bool encxattrfound = false;
	char *xname;
	int list_xsize;
	int xlen;
	int rem;
	int xsize;
	int rc;

	ENTRY;

	if (S_ISREG(mdd_object_type(sobj))) {
		LASSERT(tobj != NULL);

		fid_to_lmm_oi(mdd_object_fid(tobj), &oi);
		lmm_oi_cpu_to_le(&oi, &oi);
	}

	list_xsize = mdo_xattr_list(env, sobj, &LU_BUF_NULL);
	if (list_xsize == -ENODATA)
		RETURN(0);

	if (list_xsize < 0)
		RETURN(list_xsize);

	if (attr->la_valid & LA_FLAGS &&
	    attr->la_flags & LUSTRE_ENCRYPT_FL) {
		needencxattr = true;
		list_xsize +=
			strlen(LL_XATTR_NAME_ENCRYPTION_CONTEXT) + 1;
	}

	lu_buf_alloc(&xattrs->mx_namebuf, list_xsize);
	if (xattrs->mx_namebuf.lb_buf == NULL)
		RETURN(-ENOMEM);

	rc = mdo_xattr_list(env, sobj, &xattrs->mx_namebuf);
	if (rc < 0)
		GOTO(fini, rc);

	rem = rc;
	rc = 0;
	xname = xattrs->mx_namebuf.lb_buf;
reloop:
	for (; rem > 0; xname += xlen, rem -= xlen) {
		if (needencxattr &&
		    strcmp(xname, LL_XATTR_NAME_ENCRYPTION_CONTEXT) == 0)
			encxattrfound = true;
		xlen = strnlen(xname, rem - 1) + 1;
		if (strcmp(XATTR_NAME_LMA, xname) == 0 ||
		    strcmp(XATTR_NAME_LMV, xname) == 0)
			continue;

		if (skip_linkea &&
		    strcmp(XATTR_NAME_LINK, xname) == 0)
			continue;

		if (skip_dmv &&
		    strcmp(XATTR_NAME_DEFAULT_LMV, xname) == 0)
			continue;

		xsize = mdo_xattr_get(env, sobj, &LU_BUF_NULL, xname);
		if (xsize == -ENODATA)
			continue;
		if (xsize < 0)
			GOTO(fini, rc = xsize);

		OBD_ALLOC_PTR(entry);
		if (!entry)
			GOTO(fini, rc = -ENOMEM);

		lu_buf_alloc(&entry->mxe_buf, xsize);
		if (!entry->mxe_buf.lb_buf) {
			OBD_FREE_PTR(entry);
			GOTO(fini, rc = -ENOMEM);
		}

		rc = mdo_xattr_get(env, sobj, &entry->mxe_buf, xname);
		if (rc < 0) {
			lu_buf_free(&entry->mxe_buf);
			OBD_FREE_PTR(entry);
			if (rc == -ENODATA)
				continue;
			GOTO(fini, rc);
		}

		if (S_ISREG(attr->la_mode) &&
		    strcmp(XATTR_NAME_LOV, xname) == 0) {
			struct lov_mds_md *lmm = entry->mxe_buf.lb_buf;

			mdd_set_lmm_oi(lmm, &oi);
		}

		entry->mxe_name = xname;
		list_add_tail(&entry->mxe_linkage, &xattrs->mx_list);
	}

	if (needencxattr && !encxattrfound) {
		xlen = strlen(LL_XATTR_NAME_ENCRYPTION_CONTEXT) + 1;
		strncpy(xname, LL_XATTR_NAME_ENCRYPTION_CONTEXT, xlen);
		rem = xlen;
		GOTO(reloop, 0);
	}

	RETURN(0);
fini:
	mdd_xattrs_fini(xattrs);
	RETURN(rc);
}

typedef int (*mdd_xattr_cb)(const struct lu_env *env,
			    struct mdd_object *obj,
			    const struct lu_buf *buf,
			    const char *name,
			    int fl, struct thandle *handle);

static int mdd_foreach_xattr(const struct lu_env *env,
			     struct mdd_object *tobj,
			     struct mdd_xattrs *xattrs,
			     struct thandle *handle,
			     mdd_xattr_cb cb)
{
	struct mdd_xattr_entry *entry;
	int rc;

	list_for_each_entry(entry, &xattrs->mx_list, mxe_linkage) {
		rc = cb(env, tobj, &entry->mxe_buf, entry->mxe_name, 0, handle);
		if (rc)
			return rc;
	}

	return 0;
}

typedef int (*mdd_linkea_cb)(const struct lu_env *env,
			     struct mdd_object *sobj,
			     struct mdd_object *tobj,
			     const struct lu_name *sname,
			     const struct lu_fid *sfid,
			     const struct lu_name *lname,
			     const struct lu_fid *fid,
			     void *opaque,
			     struct thandle *handle);

static int mdd_declare_update_link(const struct lu_env *env,
				   struct mdd_object *sobj,
				   struct mdd_object *tobj,
				   const struct lu_name *tname,
				   const struct lu_fid *tpfid,
				   const struct lu_name *lname,
				   const struct lu_fid *fid,
				   void *unused,
				   struct thandle *handle)
{
	struct mdd_device *mdd = mdo2mdd(&sobj->mod_obj);
	struct mdd_object *pobj;
	int rc;

	/* ignore tobj */
	if (lu_fid_eq(tpfid, fid) && tname->ln_namelen == lname->ln_namelen &&
	    !strcmp(tname->ln_name, lname->ln_name))
		return 0;

	pobj = mdd_object_find(env, mdd, fid);
	if (IS_ERR(pobj))
		return PTR_ERR(pobj);


	rc = mdo_declare_index_delete(env, pobj, lname->ln_name, handle);
	if (!rc)
		rc = mdo_declare_index_insert(env, pobj, mdd_object_fid(tobj),
					      mdd_object_type(sobj),
					      lname->ln_name, handle);
	mdd_object_put(env, pobj);
	if (rc)
		return rc;

	rc = mdo_declare_ref_add(env, tobj, handle);
	if (rc)
		return rc;

	rc = mdo_declare_ref_del(env, sobj, handle);
	return rc;
}

static int mdd_update_link(const struct lu_env *env,
			   struct mdd_object *sobj,
			   struct mdd_object *tobj,
			   const struct lu_name *tname,
			   const struct lu_fid *tpfid,
			   const struct lu_name *lname,
			   const struct lu_fid *fid,
			   void *unused,
			   struct thandle *handle)
{
	struct mdd_device *mdd = mdo2mdd(&sobj->mod_obj);
	struct mdd_object *pobj;
	int rc;

	ENTRY;

	/* ignore tobj */
	if (lu_fid_eq(tpfid, fid) && tname->ln_namelen == lname->ln_namelen &&
	    !memcmp(tname->ln_name, lname->ln_name, lname->ln_namelen))
		RETURN(0);

	CDEBUG(D_INFO, "update "DFID"/"DNAME":"DFID"\n",
	       PFID(fid), encode_fn_luname(lname), PFID(mdd_object_fid(tobj)));

	pobj = mdd_object_find(env, mdd, fid);
	if (IS_ERR(pobj)) {
		CWARN("%s: cannot find obj "DFID": %ld\n",
		      mdd2obd_dev(mdd)->obd_name, PFID(fid), PTR_ERR(pobj));
		RETURN(PTR_ERR(pobj));
	}

	if (!mdd_object_exists(pobj)) {
		CDEBUG(D_INFO, DFID" doesn't exist\n", PFID(fid));
		mdd_object_put(env, pobj);
		RETURN(-ENOENT);
	}

	mdd_write_lock(env, pobj, DT_TGT_PARENT);
	rc = __mdd_index_delete_only(env, pobj, lname->ln_name, handle);
	if (!rc)
		rc = __mdd_index_insert_only(env, pobj, mdd_object_fid(tobj),
					     mdd_object_type(sobj),
					     lname->ln_name, handle);
	mdd_write_unlock(env, pobj);
	mdd_object_put(env, pobj);
	if (rc)
		RETURN(rc);

	mdd_write_lock(env, tobj, DT_TGT_CHILD);
	rc = mdo_ref_add(env, tobj, handle);
	mdd_write_unlock(env, tobj);
	if (rc)
		RETURN(rc);

	mdd_write_lock(env, sobj, DT_SRC_CHILD);
	rc = mdo_ref_del(env, sobj, handle);
	mdd_write_unlock(env, sobj);

	RETURN(rc);
}

static inline int mdd_fld_lookup(const struct lu_env *env,
				 struct mdd_device *mdd,
				 const struct lu_fid *fid,
				 __u32 *mdt_index)
{
	struct lu_seq_range *range = &mdd_env_info(env)->mdi_range;
	struct seq_server_site *ss;
	int rc;

	ss = mdd->mdd_md_dev.md_lu_dev.ld_site->ld_seq_site;

	range->lsr_flags = LU_SEQ_RANGE_MDT;
	rc = fld_server_lookup(env, ss->ss_server_fld, fid->f_seq, range);
	if (rc)
		return rc;

	*mdt_index = range->lsr_index;

	return 0;
}

static int mdd_is_link_on_source_mdt(const struct lu_env *env,
				     struct mdd_object *sobj,
				     struct mdd_object *tobj,
				     const struct lu_name *tname,
				     const struct lu_fid *tpfid,
				     const struct lu_name *lname,
				     const struct lu_fid *fid,
				     void *opaque,
				     struct thandle *handle)
{
	struct mdd_device *mdd = mdo2mdd(&sobj->mod_obj);
	__u32 source_mdt_index = *(__u32 *)opaque;
	__u32 link_mdt_index;
	int rc;

	ENTRY;

	/* ignore tobj */
	if (lu_fid_eq(tpfid, fid) && tname->ln_namelen == lname->ln_namelen &&
	    !memcmp(tname->ln_name, lname->ln_name, lname->ln_namelen))
		return 0;

	rc = mdd_fld_lookup(env, mdd, fid, &link_mdt_index);
	if (rc)
		RETURN(rc);

	RETURN(link_mdt_index == source_mdt_index);
}

static int mdd_iterate_linkea(const struct lu_env *env,
			      struct mdd_object *sobj,
			      struct mdd_object *tobj,
			      const struct lu_name *tname,
			      const struct lu_fid *tpfid,
			      struct linkea_data *ldata,
			      void *opaque,
			      struct thandle *handle,
			      mdd_linkea_cb cb)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	char *filename = info->mdi_name;
	struct lu_name lname;
	struct lu_fid fid;
	int rc = 0;

	if (!ldata->ld_buf)
		return 0;

	for (linkea_first_entry(ldata); ldata->ld_lee && !rc;
	     linkea_next_entry(ldata)) {
		linkea_entry_unpack(ldata->ld_lee, &ldata->ld_reclen, &lname,
				    &fid);

		/* Note: lname might miss \0 at the end */
		snprintf(filename, sizeof(info->mdi_name), DNAME,
			 lname.ln_namelen, lname.ln_name);
		lname.ln_name = filename;

		CDEBUG(D_INFO, DFID"/"DNAME"\n",
		       PFID(&fid), encode_fn_luname(&lname));

		rc = cb(env, sobj, tobj, tname, tpfid, &lname, &fid, opaque,
			handle);
	}

	return rc;
}

/**
 * mdd_migrate_linkea_prepare() - Prepare linkea
 * @env: execution environment
 * @mdd: MDD metadata device
 * @spobj: source parent object
 * @tpobj: target parent object
 * @sobj: source object
 * @sname: source name
 * @tname: target name
 * @attr: source attributes
 * @ldata: pointer to linkea_data
 *
 * Prepare linkea, and check whether file needs migrate: if source still has
 * link on source MDT, no need to migrate, just update namespace on source and
 * target parents.
 *
 * Return:
 * * %0 do migrate
 * * %1 don't migrate
 * * %-errno on failure
 */
static int mdd_migrate_linkea_prepare(const struct lu_env *env,
				      struct mdd_device *mdd,
				      struct mdd_object *spobj,
				      struct mdd_object *tpobj,
				      struct mdd_object *sobj,
				      const struct lu_name *sname,
				      const struct lu_name *tname,
				      const struct lu_attr *attr,
				      struct linkea_data *ldata)
{
	__u32 source_mdt_index;
	int rc;

	ENTRY;

	memset(ldata, 0, sizeof(*ldata));
	rc = mdd_linkea_prepare(env, sobj, mdd_object_fid(spobj), sname,
				mdd_object_fid(tpobj), tname, 1, 0, ldata);
	if (rc)
		RETURN(rc);

	/*
	 * Then it will check if the file should be migrated. If the file has
	 * mulitple links, we only need migrate the file if all of its entries
	 * has been migrated to the remote MDT.
	 */
	if (S_ISDIR(attr->la_mode) || attr->la_nlink < 2)
		RETURN(0);

	/* If there are still links locally, don't migrate this file */
	LASSERT(ldata->ld_leh != NULL);

	/* If linkEA is overflow, switch to ns-only migrate */
	if (unlikely(ldata->ld_leh->leh_overflow_time))
		RETURN(+EOVERFLOW);

	rc = mdd_fld_lookup(env, mdd, mdd_object_fid(sobj), &source_mdt_index);
	if (rc)
		RETURN(rc);

	rc = mdd_iterate_linkea(env, sobj, NULL, tname, mdd_object_fid(tpobj),
				ldata, &source_mdt_index, NULL,
				mdd_is_link_on_source_mdt);
	RETURN(rc);
}

static int mdd_declare_migrate_update(const struct lu_env *env,
				      struct mdd_object *spobj,
				      struct mdd_object *tpobj,
				      struct mdd_object *obj,
				      const struct lu_name *sname,
				      const struct lu_name *tname,
				      struct lu_attr *attr,
				      struct lu_attr *spattr,
				      struct lu_attr *tpattr,
				      struct linkea_data *ldata,
				      struct md_attr *ma,
				      struct thandle *handle)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct lu_attr *la = &info->mdi_la_for_fix;
	int rc;

	rc = mdo_declare_index_delete(env, spobj, sname->ln_name, handle);
	if (rc)
		return rc;

	if (S_ISDIR(attr->la_mode)) {
		rc = mdo_declare_ref_del(env, spobj, handle);
		if (rc)
			return rc;
	}

	rc = mdo_declare_index_insert(env, tpobj, mdd_object_fid(obj),
				      attr->la_mode & S_IFMT,
				      tname->ln_name, handle);
	if (rc)
		return rc;

	rc = mdd_declare_links_add(env, obj, handle, ldata);
	if (rc)
		return rc;

	if (S_ISDIR(attr->la_mode)) {
		rc = mdo_declare_ref_add(env, tpobj, handle);
		if (rc)
			return rc;
	}

	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdo_declare_attr_set(env, spobj, la, handle);
	if (rc)
		return rc;

	if (tpobj != spobj) {
		rc = mdo_declare_attr_set(env, tpobj, la, handle);
		if (rc)
			return rc;
	}

	return rc;
}

static int mdd_declare_migrate_create(const struct lu_env *env,
				      struct mdd_object *spobj,
				      struct mdd_object *tpobj,
				      struct mdd_object *sobj,
				      struct mdd_object *tobj,
				      const struct lu_name *sname,
				      const struct lu_name *tname,
				      struct lu_attr *spattr,
				      struct lu_attr *tpattr,
				      struct lu_attr *attr,
				      struct lu_buf *sbuf,
				      struct linkea_data *ldata,
				      struct mdd_xattrs *xattrs,
				      struct md_attr *ma,
				      struct md_op_spec *spec,
				      struct dt_allocation_hint *hint,
				      struct thandle *handle)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct md_layout_change *mlc = &info->mdi_mlc;
	struct lmv_mds_md_v1 *lmv = sbuf->lb_buf;
	int rc;

	ENTRY;

	if (S_ISDIR(attr->la_mode)) {
		struct lmv_user_md *lum = spec->u.sp_ea.eadata;

		mlc->mlc_opc = MD_LAYOUT_DETACH;
		rc = mdo_declare_layout_change(env, sobj, mlc, handle);
		if (rc)
			return rc;

		lum->lum_hash_type |= cpu_to_le32(LMV_HASH_FLAG_MIGRATION);
	} else if (S_ISLNK(attr->la_mode)) {
		spec->u.sp_symname.ln_name = sbuf->lb_buf;
		/* don't count NUL */
		spec->u.sp_symname.ln_namelen = sbuf->lb_len - 1;
	} else if (S_ISREG(attr->la_mode)) {
		spec->sp_cr_flags |= MDS_OPEN_DELAY_CREATE;
		spec->sp_cr_flags &= ~MDS_OPEN_HAS_EA;
	}

	mdd_object_make_hint(env, tpobj, tobj, attr, spec, hint);

	rc = mdd_declare_create(env, mdo2mdd(&tpobj->mod_obj), tpobj, tobj,
				tname, attr, handle, spec, ldata, NULL, NULL,
				NULL, NULL, hint);
	if (rc)
		return rc;

	/*
	 * tobj mode will be used in mdo_declare_layout_change(), but it's not
	 * createb yet, copy from sobj.
	 */
	tobj->mod_obj.mo_lu.lo_header->loh_attr &= ~S_IFMT;
	tobj->mod_obj.mo_lu.lo_header->loh_attr |=
		sobj->mod_obj.mo_lu.lo_header->loh_attr & S_IFMT;

	if (S_ISDIR(attr->la_mode)) {
		if (!lmv) {
			/* if sobj is not striped, fake a 1-stripe LMV */
			LASSERT(sizeof(info->mdi_key) >
				lmv_mds_md_size(1, LMV_MAGIC_V1));
			lmv = (typeof(lmv))info->mdi_key;
			memset(lmv, 0, sizeof(*lmv));
			lmv->lmv_magic = cpu_to_le32(LMV_MAGIC_V1);
			lmv->lmv_stripe_count = cpu_to_le32(1);
			lmv->lmv_hash_type = cpu_to_le32(LMV_HASH_TYPE_DEFAULT);
			fid_le_to_cpu(&lmv->lmv_stripe_fids[0],
				      mdd_object_fid(sobj));
			mlc->mlc_buf.lb_buf = lmv;
			mlc->mlc_buf.lb_len = lmv_mds_md_size(1, LMV_MAGIC_V1);
		} else {
			mlc->mlc_buf = *sbuf;
		}
		mlc->mlc_opc = MD_LAYOUT_ATTACH;
		rc = mdo_declare_layout_change(env, tobj, mlc, handle);
		if (rc)
			return rc;
	}

	rc = mdd_foreach_xattr(env, tobj, xattrs, handle,
			       mdo_declare_xattr_set);
	if (rc)
		return rc;

	if (S_ISREG(attr->la_mode)) {
		struct lu_buf fid_buf;

		handle->th_complex = 1;

		/* target may be remote, update PFID via sobj. */
		fid_buf.lb_buf = (void *)mdd_object_fid(tobj);
		fid_buf.lb_len = sizeof(struct lu_fid);
		rc = mdo_declare_xattr_set(env, sobj, &fid_buf, XATTR_NAME_FID,
					   0, handle);
		if (rc)
			return rc;

		rc = mdo_declare_xattr_del(env, sobj, XATTR_NAME_LOV, handle);
		if (rc)
			return rc;
	}

	if (!S_ISDIR(attr->la_mode)) {
		rc = mdd_iterate_linkea(env, sobj, tobj, tname,
					mdd_object_fid(tpobj), ldata, NULL,
					handle, mdd_declare_update_link);
		if (rc)
			return rc;
	}

	if (!S_ISDIR(attr->la_mode) || lmv) {
		rc = mdo_declare_ref_del(env, sobj, handle);
		if (rc)
			return rc;

		if (S_ISDIR(attr->la_mode)) {
			rc = mdo_declare_ref_del(env, sobj, handle);
			if (rc)
				return rc;
		}

		rc = mdo_declare_destroy(env, sobj, handle);
		if (rc)
			return rc;
	}

	rc = mdd_declare_migrate_update(env, spobj, tpobj, tobj, sname, tname,
					attr, spattr, tpattr, ldata, ma,
					handle);
	return rc;
}

/*
 * migrate dirent from @spobj to @tpobj.
 */
static int mdd_migrate_update(const struct lu_env *env,
			      struct mdd_object *spobj,
			      struct mdd_object *tpobj,
			      struct mdd_object *obj,
			      const struct lu_name *sname,
			      const struct lu_name *tname,
			      struct lu_attr *attr,
			      struct lu_attr *spattr,
			      struct lu_attr *tpattr,
			      struct linkea_data *ldata,
			      struct md_attr *ma,
			      struct thandle *handle)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct lu_attr *la = &info->mdi_la_for_fix;
	int rc;

	ENTRY;

	CDEBUG(D_INFO, "update "DFID" from "DFID"/"DNAME" to "DFID"/"DNAME"\n",
	       PFID(mdd_object_fid(obj)), PFID(mdd_object_fid(spobj)),
	       encode_fn_luname(sname), PFID(mdd_object_fid(tpobj)),
	       encode_fn_luname(tname));

	rc = __mdd_index_delete(env, spobj, sname->ln_name,
				S_ISDIR(attr->la_mode), handle);
	if (rc)
		RETURN(rc);

	rc = __mdd_index_insert(env, tpobj, mdd_object_fid(obj),
				attr->la_mode & S_IFMT,
				tname->ln_name, handle);
	if (rc)
		RETURN(rc);

	rc = mdd_links_write(env, obj, ldata, handle);
	if (rc)
		RETURN(rc);

	la->la_ctime = la->la_mtime = ma->ma_attr.la_ctime;
	la->la_valid = LA_CTIME | LA_MTIME;
	mdd_write_lock(env, spobj, DT_SRC_PARENT);
	rc = mdd_update_time(env, spobj, spattr, la, handle);
	mdd_write_unlock(env, spobj);
	if (rc)
		RETURN(rc);

	if (tpobj != spobj) {
		la->la_valid = LA_CTIME | LA_MTIME;
		mdd_write_lock(env, tpobj, DT_TGT_PARENT);
		rc = mdd_update_time(env, tpobj, tpattr, la, handle);
		mdd_write_unlock(env, tpobj);
		if (rc)
			RETURN(rc);
	}

	RETURN(rc);
}

/**
 * mdd_migrate_create() - Migrate file/dir to target MDT.
 * @env: execution environment
 * @spobj: source parent object
 * @tpobj: target parent object
 * @sobj: source object
 * @tobj: target object
 * @sname: source name
 * @tname: target name
 * @spattr: source parent attributes
 * @tpattr: target parent attributes
 * @attr: source attributes
 * @sbuf: source LMV buf
 * @ldata: pointer to linkea_data
 * @xattrs: extended attributes
 * @ma: pointer to md_attr struct
 * @spec: migrate create spec
 * @hint: target creation hint
 * @handle: tranasction handle
 *
 * Create target according to @spec, and then migrate xattrs, if it's
 * directory, migrate source stripes to target.
 *
 * Return:
 * * %0 on success
 * * %-errno on failure
 **/
static int mdd_migrate_create(const struct lu_env *env,
			      struct mdd_object *spobj,
			      struct mdd_object *tpobj,
			      struct mdd_object *sobj,
			      struct mdd_object *tobj,
			      const struct lu_name *sname,
			      const struct lu_name *tname,
			      struct lu_attr *spattr,
			      struct lu_attr *tpattr,
			      struct lu_attr *attr,
			      const struct lu_buf *sbuf,
			      struct linkea_data *ldata,
			      struct mdd_xattrs *xattrs,
			      struct md_attr *ma,
			      struct md_op_spec *spec,
			      struct dt_allocation_hint *hint,
			      struct thandle *handle)
{
	int rc;

	ENTRY;

	/*
	 * migrate sobj stripes to tobj if it's directory:
	 * 1. detach stripes from sobj.
	 * 2. attach stripes to tobj, see mdd_declare_migrate_mdt().
	 * 3. create stripes for tobj, see lod_xattr_set_lmv().
	 */
	if (S_ISDIR(attr->la_mode)) {
		struct mdd_thread_info *info = mdd_env_info(env);
		struct md_layout_change *mlc = &info->mdi_mlc;

		mlc->mlc_opc = MD_LAYOUT_DETACH;

		mdd_write_lock(env, sobj, DT_SRC_PARENT);
		rc = mdo_layout_change(env, sobj, mlc, handle);
		mdd_write_unlock(env, sobj);
		if (rc)
			RETURN(rc);
	}

	/* don't set nlink from sobj */
	attr->la_valid &= ~LA_NLINK;

	rc = mdd_create_object(env, tpobj, tobj, attr, spec, NULL, NULL, NULL,
			       NULL, hint, handle, false);
	if (rc)
		RETURN(rc);

	mdd_write_lock(env, tobj, DT_TGT_CHILD);
	rc = mdd_foreach_xattr(env, tobj, xattrs, handle, mdo_xattr_set);
	mdd_write_unlock(env, tobj);
	if (rc)
		RETURN(rc);

	/* for regular file, update OST objects XATTR_NAME_FID */
	if (S_ISREG(attr->la_mode)) {
		struct lu_buf fid_buf;

		/* target may be remote, update PFID via sobj. */
		fid_buf.lb_buf = (void *)mdd_object_fid(tobj);
		fid_buf.lb_len = sizeof(struct lu_fid);
		rc = mdo_xattr_set(env, sobj, &fid_buf, XATTR_NAME_FID, 0,
				   handle);
		if (rc)
			RETURN(rc);

		/* delete LOV to avoid deleting OST objs when destroying sobj */
		mdd_write_lock(env, sobj, DT_SRC_CHILD);
		rc = mdo_xattr_del(env, sobj, XATTR_NAME_LOV, handle);
		mdd_write_unlock(env, sobj);
		/* O_DELAY_CREATE file may not have LOV, ignore -ENODATA */
		if (rc && rc != -ENODATA)
			RETURN(rc);
		rc = 0;
	}

	/* update links FID */
	if (!S_ISDIR(attr->la_mode)) {
		rc = mdd_iterate_linkea(env, sobj, tobj, tname,
					mdd_object_fid(tpobj), ldata,
					NULL, handle, mdd_update_link);
		if (rc)
			RETURN(rc);
	}

	/* don't destroy sobj if it's plain directory */
	if (!S_ISDIR(attr->la_mode) || sbuf->lb_buf) {
		mdd_write_lock(env, sobj, DT_SRC_CHILD);
		rc = mdo_ref_del(env, sobj, handle);
		if (!rc) {
			if (S_ISDIR(attr->la_mode))
				rc = mdo_ref_del(env, sobj, handle);
			if (!rc)
				rc = mdo_destroy(env, sobj, handle);
		}
		mdd_write_unlock(env, sobj);
		if (rc)
			RETURN(rc);
	}

	rc = mdd_migrate_update(env, spobj, tpobj, tobj, sname, tname, attr,
				spattr, tpattr, ldata, ma, handle);

	RETURN(rc);
}

/* NB: if user issued different migrate command, we can't adjust it silently
 * here, because this command will decide target MDT in subdir migration in
 * LMV.
 */
static int mdd_migrate_cmd_check(const struct lu_env *env, struct mdd_device *mdd,
				 struct mdd_object *sobj,
				 const struct lmv_mds_md_v1 *lmv,
				 const struct lmv_user_md_v1 *lum,
				 size_t lum_len, const struct lu_name *lname)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	__s32 lum_stripe_count = lum->lum_stripe_count;
	__u32 lum_hash_type = lum->lum_hash_type &
			      cpu_to_le32(LMV_HASH_TYPE_MASK);
	struct md_layout_change *mlc = &info->mdi_mlc;
	__u32 lmv_hash_type;
	int rc = 0;

	ENTRY;

	if (lmv && !lmv_is_sane(lmv))
		RETURN(-EBADF);

	if (lum_stripe_count > LMV_MAX_STRIPE_COUNT)
		RETURN(-EOVERFLOW);

	/* If stripe_count unspecified, set to 1 */
	if (!lum_stripe_count)
		lum_stripe_count = cpu_to_le32(1);

	/* Easy check for plain and single-striped dirs
	 * if the object is on the target MDT already
	 */
	if (!lmv || lmv->lmv_stripe_count == cpu_to_le32(1)) {
		struct seq_server_site  *ss = mdd_seq_site(mdd);
		struct lu_seq_range range = { 0 };

		fld_range_set_type(&range, LU_SEQ_RANGE_MDT);
		rc = fld_server_lookup(env, ss->ss_server_fld,
				fid_seq(mdd_object_fid(sobj)), &range);
		if (rc)
			RETURN(rc);

		if (lum_stripe_count == cpu_to_le32(1) &&
		    le32_to_cpu(lum->lum_stripe_offset) == range.lsr_index)
			RETURN(-EALREADY);
		RETURN(0);
	}

	lmv_hash_type = lmv->lmv_hash_type & cpu_to_le32(LMV_HASH_TYPE_MASK);

	if (lmv_is_migrating(lmv)) {
		if (lum_stripe_count != lmv->lmv_migrate_offset ||
		    lum->lum_stripe_offset != lmv->lmv_master_mdt_index ||
		    (lum_hash_type && lum_hash_type != lmv_hash_type)) {
			rc = -EPERM;
		}
	} else {
		/* check at top level if the target layout already applied */
		if ((lum_hash_type && lum_hash_type != lmv_hash_type) ||
		    lum->lum_stripe_offset != lmv->lmv_master_mdt_index ||
		    lum_stripe_count != lmv->lmv_stripe_count)
			RETURN(0);
	}

	if (rc == 0) {
		mlc->mlc_buf.lb_buf = (void *)lum;
		mlc->mlc_buf.lb_len = lum_len;
		rc = mo_layout_check(env, &sobj->mod_obj, mlc);
	}

	if (rc == -EPERM) {
		CERROR("%s: '"DNAME"' migration was interrupted, run 'lfs migrate -m %d -c %d -H %s "
		       DNAME"' to finish migration: rc = %d\n",
		       mdd2obd_dev(mdd)->obd_name, encode_fn_luname(lname),
		       le32_to_cpu(lmv->lmv_master_mdt_index),
		       le32_to_cpu(lmv->lmv_migrate_offset),
		       mdt_hash_name[le32_to_cpu(lmv_hash_type)],
		       encode_fn_luname(lname), rc);
	}

	RETURN(rc);
}

/**
 * mdd_migrate_object() - Internal function to migrate directory or file between
 * MDTs.
 * @env: execution environment
 * @spobj: source parent object
 * @tpobj: target parent object
 * @sobj: source object
 * @tobj: target object
 * @sname: source name
 * @tname: target name
 * @spec: target creation spec
 * @ma: used to update @tpobj mtime and ctime [out]
 *
 * migrate source to target in following steps:
 *   1. create target, append source stripes after target's if it's directory,
 *      migrate xattrs and update fid of source links.
 *   2. update namespace: migrate dirent from source parent to target parent,
 *      update file linkea, and destroy source if it's not needed any more.
 *
 * Return:
 * * %0 on success
 * * %-errno on failure
 */
static int mdd_migrate_object(const struct lu_env *env,
			      struct mdd_object *spobj,
			      struct mdd_object *tpobj,
			      struct mdd_object *sobj,
			      struct mdd_object *tobj,
			      const struct lu_name *sname,
			      const struct lu_name *tname,
			      struct md_op_spec *spec,
			      struct md_attr *ma)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct mdd_device *mdd = mdo2mdd(&spobj->mod_obj);
	struct lu_attr *spattr = &info->mdi_pattr;
	struct lu_attr *tpattr = &info->mdi_tpattr;
	struct lu_attr *attr = &info->mdi_cattr;
	struct linkea_data *ldata = &info->mdi_link_data;
	struct dt_allocation_hint *hint = &info->mdi_hint;
	struct lu_buf sbuf = { NULL };
	struct mdd_xattrs xattrs;
	struct lmv_mds_md_v1 *lmv;
	struct thandle *handle;
	int retried = 0;
	int rc;

	ENTRY;

	CDEBUG(D_INFO, "migrate "DNAME" from "DFID"/"DFID" to "DFID"/"DFID"\n",
	       encode_fn_luname(sname), PFID(mdd_object_fid(spobj)),
	       PFID(mdd_object_fid(sobj)), PFID(mdd_object_fid(tpobj)),
	       PFID(mdd_object_fid(tobj)));

retry:
	rc = mdd_la_get(env, sobj, attr);
	if (rc)
		RETURN(rc);

	rc = mdd_la_get(env, spobj, spattr);
	if (rc)
		RETURN(rc);

	rc = mdd_la_get(env, tpobj, tpattr);
	if (rc)
		RETURN(rc);

	rc = mdd_migrate_sanity_check(env, mdd, spobj, tpobj, sobj, tobj,
				      spattr, tpattr, attr,
				      spec->sp_migrate_nsonly);
	if (rc == -EBUSY && !spec->sp_migrate_nsonly) {
		spec->sp_migrate_nsonly = 1;
		CWARN("%s: "DFID"/%s is open, migrate only dentry\n",
		      mdd2obd_dev(mdd)->obd_name, PFID(mdd_object_fid(spobj)),
		      sname->ln_name);
		rc = mdd_migrate_sanity_check(env, mdd, spobj, tpobj, sobj,
					      tobj, spattr, tpattr, attr,
					      spec->sp_migrate_nsonly);
	}
	if (rc)
		RETURN(rc);

	mdd_xattrs_init(&xattrs);

	if (S_ISDIR(attr->la_mode) && !spec->sp_migrate_nsonly) {
		struct lmv_user_md_v1 *lum = spec->u.sp_ea.eadata;
		size_t lum_len = spec->u.sp_ea.eadatalen;

		LASSERT(lum);

		/* if user use default value '0' for stripe_count, we need to
		 * adjust it to '1' to create a 1-stripe directory.
		 */
		if (lum->lum_stripe_count == 0)
			lum->lum_stripe_count = cpu_to_le32(1);

		rc = mdd_stripe_get(env, sobj, &sbuf, XATTR_NAME_LMV);
		if (rc && rc != -ENODATA)
			GOTO(out, rc);

		lmv = sbuf.lb_buf;
		rc = mdd_migrate_cmd_check(env, mdd, sobj, lmv, lum,
					   lum_len, sname);
		if (rc)
			GOTO(out, rc);
	} else if (!S_ISDIR(attr->la_mode)) {
		/* update namespace only if @sobj is on MDT where @tpobj is. */
		if (!mdd_object_remote(tpobj) && !mdd_object_remote(sobj))
			spec->sp_migrate_nsonly = true;

		if (S_ISLNK(attr->la_mode)) {
			lu_buf_check_and_alloc(&sbuf, attr->la_size + 1);
			if (!sbuf.lb_buf)
				GOTO(out, rc = -ENOMEM);

			rc = mdd_readlink(env, &sobj->mod_obj, &sbuf);
			if (rc <= 0) {
				rc = rc ?: -EFAULT;
				CERROR("%s: "DFID" readlink failed: rc = %d\n",
				       mdd2obd_dev(mdd)->obd_name,
				       PFID(mdd_object_fid(sobj)), rc);
				GOTO(out, rc);
			}
		}
	}

	/* linkea needs update upon FID or parent stripe change */
	rc = mdd_migrate_linkea_prepare(env, mdd, spobj, tpobj, sobj, sname,
					tname, attr, ldata);
	if (rc > 0)
		/* update namespace only if @sobj has link on its MDT. */
		spec->sp_migrate_nsonly = true;
	else if (rc < 0)
		GOTO(out, rc);

	/* migrate inode will migrate xattrs, prepare xattrs early to avoid
	 * RPCs inside transaction.
	 */
	if (!spec->sp_migrate_nsonly) {
		rc = mdd_xattrs_migrate_prep(env, &xattrs, sobj, tobj,
					     true, true);
		if (rc)
			GOTO(out, rc);
	}

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		GOTO(out, rc = PTR_ERR(handle));

	/* bypass quota during migration */
	handle->th_ignore_quota = true;

	if (spec->sp_migrate_nsonly)
		rc = mdd_declare_migrate_update(env, spobj, tpobj, sobj, sname,
						tname, attr, spattr, tpattr,
						ldata, ma, handle);
	else
		rc = mdd_declare_migrate_create(env, spobj, tpobj, sobj, tobj,
						sname, tname, spattr, tpattr,
						attr, &sbuf, ldata, &xattrs, ma,
						spec, hint, handle);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_declare_changelog_store(env, mdd, CL_MIGRATE, tname, sname,
					 handle);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop, rc);

	if (spec->sp_migrate_nsonly)
		rc = mdd_migrate_update(env, spobj, tpobj, sobj, sname, tname,
					attr, spattr, tpattr, ldata, ma,
					handle);
	else
		rc = mdd_migrate_create(env, spobj, tpobj, sobj, tobj, sname,
					tname, spattr, tpattr, attr, &sbuf,
					ldata, &xattrs, ma, spec, hint, handle);
	if (rc)
		GOTO(stop, rc);

	rc = mdd_changelog_ns_store(env, mdd, CL_MIGRATE, 0,
				    spec->sp_migrate_nsonly ? sobj : tobj,
				    spobj, spattr, mdd_object_fid(sobj),
				    tpobj, tpattr, tname, sname,
				    handle);

stop:
	rc = mdd_trans_stop(env, mdd, rc, handle);
out:
	mdd_xattrs_fini(&xattrs);
	lu_buf_free(&sbuf);

	/**
	 * -EAGAIN means transaction execution phase detect the layout
	 * has been changed by others.
	 */
	if (rc == -EAGAIN && retried++ < MAX_TRANS_RETRIED)
		GOTO(retry, retried);

	RETURN(rc);
}

/**
 * mdd_migrate() - Migrate directory or file between MDTs.
 * @env: execution environment
 * @md_spobj: source parent object
 * @md_tpobj: target parent object
 * @md_sobj: source object
 * @lname: file name
 * @md_tobj: target object
 * @spec: target creation spec
 * @ma: used to update @md_tpobj mtime and ctime [out]
 *
 * Return:
 * * %0 on success
 * * %-errno on failure
 */
static int mdd_migrate(const struct lu_env *env, struct md_object *md_spobj,
		       struct md_object *md_tpobj, struct md_object *md_sobj,
		       struct md_object *md_tobj, const struct lu_name *lname,
		       struct md_op_spec *spec, struct md_attr *ma)
{
	return mdd_migrate_object(env, md2mdd_obj(md_spobj),
				  md2mdd_obj(md_tpobj), md2mdd_obj(md_sobj),
				  md2mdd_obj(md_tobj), lname, lname, spec, ma);
}

static int mdd_declare_1sd_collapse(const struct lu_env *env,
				    struct mdd_object *pobj,
				    struct mdd_object *obj,
				    struct mdd_object *stripe,
				    struct lu_attr *attr,
				    struct mdd_xattrs *xattrs,
				    struct md_layout_change *mlc,
				    struct lu_name *lname,
				    struct thandle *handle)
{
	int rc;

	mlc->mlc_opc = MD_LAYOUT_DETACH;
	rc = mdo_declare_layout_change(env, obj, mlc, handle);
	if (rc)
		return rc;

	rc = mdo_declare_index_insert(env, stripe, mdd_object_fid(pobj),
				      S_IFDIR, dotdot, handle);
	if (rc)
		return rc;

	rc = mdd_foreach_xattr(env, stripe, xattrs, handle,
			       mdo_declare_xattr_set);
	if (rc)
		return rc;

	rc = mdo_declare_xattr_del(env, stripe, XATTR_NAME_LMV, handle);
	if (rc)
		return rc;

	rc = mdo_declare_attr_set(env, stripe, attr, handle);
	if (rc)
		return rc;

	rc = mdo_declare_index_delete(env, pobj, lname->ln_name, handle);
	if (rc)
		return rc;

	rc = mdo_declare_index_insert(env, pobj, mdd_object_fid(stripe),
				      attr->la_mode, lname->ln_name, handle);
	if (rc)
		return rc;

	rc = mdo_declare_ref_del(env, obj, handle);
	if (rc)
		return rc;

	rc = mdo_declare_ref_del(env, obj, handle);
	if (rc)
		return rc;

	rc = mdo_declare_destroy(env, obj, handle);
	if (rc)
		return rc;

	return rc;
}

/* transform one-stripe directory to a plain directory */
static int mdd_1sd_collapse(const struct lu_env *env,
			    struct mdd_object *pobj,
			    struct mdd_object *obj,
			    struct mdd_object *stripe,
			    struct lu_attr *attr,
			    struct mdd_xattrs *xattrs,
			    struct md_layout_change *mlc,
			    struct lu_name *lname,
			    struct thandle *handle)
{
	int rc;

	ENTRY;

	/* replace 1-stripe directory with its stripe */
	mlc->mlc_opc = MD_LAYOUT_DETACH;

	mdd_write_lock(env, obj, DT_SRC_PARENT);
	rc = mdo_layout_change(env, obj, mlc, handle);
	mdd_write_unlock(env, obj);
	if (rc)
		RETURN(rc);

	mdd_write_lock(env, pobj, DT_SRC_PARENT);
	mdd_write_lock(env, obj, DT_SRC_CHILD);

	/* insert dotdot to stripe which points to parent */
	rc = __mdd_index_insert_only(env, stripe, mdd_object_fid(pobj),
				     S_IFDIR, dotdot, handle);
	if (rc)
		GOTO(out, rc);

	rc = mdd_foreach_xattr(env, stripe, xattrs, handle, mdo_xattr_set);
	if (rc)
		GOTO(out, rc);

	/* delete LMV */
	rc = mdo_xattr_del(env, stripe, XATTR_NAME_LMV, handle);
	if (rc)
		GOTO(out, rc);

	/* don't set nlink from parent */
	attr->la_valid &= ~LA_NLINK;

	rc = mdo_attr_set(env, stripe, attr, handle);
	if (rc)
		GOTO(out, rc);

	/* delete dir name from parent */
	rc = __mdd_index_delete_only(env, pobj, lname->ln_name, handle);
	if (rc)
		GOTO(out, rc);

	/* insert stripe to parent with dir name */
	rc = __mdd_index_insert_only(env, pobj, mdd_object_fid(stripe),
				     attr->la_mode, lname->ln_name, handle);
	if (rc)
		GOTO(out, rc);

	/* destroy dir obj */
	rc = mdo_ref_del(env, obj, handle);
	if (rc)
		GOTO(out, rc);

	rc = mdo_ref_del(env, obj, handle);
	if (rc)
		GOTO(out, rc);

	rc = mdo_destroy(env, obj, handle);
	if (rc)
		GOTO(out, rc);

	EXIT;
out:
	mdd_write_unlock(env, obj);
	mdd_write_unlock(env, pobj);

	return rc;
}

/*
 * shrink directory stripes after migration/merge
 */
int mdd_dir_layout_shrink(const struct lu_env *env,
			  struct md_object *md_obj,
			  struct md_layout_change *mlc)
{
	struct mdd_device *mdd = mdo2mdd(md_obj);
	struct mdd_thread_info *info = mdd_env_info(env);
	struct mdd_object *obj = md2mdd_obj(md_obj);
	struct mdd_object *pobj = NULL;
	struct mdd_object *stripe = NULL;
	struct lu_attr *attr = &info->mdi_pattr;
	struct lu_fid *fid = &info->mdi_fid2;
	struct lu_name lname = { NULL };
	struct lu_buf lmv_buf = { NULL };
	struct mdd_xattrs xattrs;
	struct lmv_mds_md_v1 *lmv;
	struct lmv_user_md *lmu;
	struct thandle *handle;
	int rc;

	ENTRY;

	rc = mdd_la_get(env, obj, attr);
	if (rc)
		RETURN(rc);

	if (!S_ISDIR(attr->la_mode))
		RETURN(-ENOTDIR);

	rc = mdd_stripe_get(env, obj, &lmv_buf, XATTR_NAME_LMV);
	if (rc < 0)
		RETURN(rc);

	lmv = lmv_buf.lb_buf;
	if (!lmv_is_sane(lmv))
		GOTO(out_lmv, rc = -EBADF);

	lmu = mlc->mlc_buf.lb_buf;

	/* adjust the default value '0' to '1' */
	if (lmu->lum_stripe_count == 0)
		lmu->lum_stripe_count = cpu_to_le32(1);

	/* these were checked in MDT */
	LASSERT(le32_to_cpu(lmu->lum_stripe_count) <
		le32_to_cpu(lmv->lmv_stripe_count));
	LASSERT(!lmv_is_splitting(lmv));
	LASSERT(lmv_is_migrating(lmv) || lmv_is_merging(lmv));

	mdd_xattrs_init(&xattrs);

	/* if dir stripe count will be shrunk to 1, it needs to be transformed
	 * to a plain dir, which will cause FID change and namespace update.
	 */
	if (le32_to_cpu(lmu->lum_stripe_count) == 1) {
		struct linkea_data *ldata = &info->mdi_link_data;
		char *filename = info->mdi_name;

		rc = mdd_links_read(env, obj, ldata);
		if (rc)
			GOTO(out, rc);

		if (ldata->ld_leh->leh_reccount > 1)
			GOTO(out, rc = -EINVAL);

		linkea_first_entry(ldata);
		if (!ldata->ld_lee)
			GOTO(out, rc = -ENODATA);

		linkea_entry_unpack(ldata->ld_lee, &ldata->ld_reclen, &lname,
				    fid);

		/* Note: lname might miss \0 at the end */
		snprintf(filename, sizeof(info->mdi_name), DNAME,
			 lname.ln_namelen, lname.ln_name);
		lname.ln_name = filename;

		pobj = mdd_object_find(env, mdd, fid);
		if (IS_ERR(pobj)) {
			rc = PTR_ERR(pobj);
			pobj = NULL;
			GOTO(out, rc);
		}

		fid_le_to_cpu(fid, &lmv->lmv_stripe_fids[0]);

		stripe = mdd_object_find(env, mdd, fid);
		if (IS_ERR(stripe)) {
			mdd_object_put(env, pobj);
			pobj = NULL;
			GOTO(out, rc = PTR_ERR(stripe));
		}

		if (!lmv_is_fixed(lmv))
			rc = mdd_xattrs_migrate_prep(env, &xattrs, obj, NULL,
						     false, false);
	}

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		GOTO(out, rc = PTR_ERR(handle));

	mlc->mlc_opc = MD_LAYOUT_SHRINK;
	rc = mdo_declare_layout_change(env, obj, mlc, handle);
	if (rc)
		GOTO(stop_trans, rc);

	if (le32_to_cpu(lmu->lum_stripe_count) == 1 && !lmv_is_fixed(lmv)) {
		rc = mdd_declare_1sd_collapse(env, pobj, obj, stripe, attr,
					      &xattrs, mlc, &lname, handle);
		if (rc)
			GOTO(stop_trans, rc);
	}

	rc = mdd_declare_changelog_store(env, mdd, CL_LAYOUT, NULL, NULL,
					 handle);
	if (rc)
		GOTO(stop_trans, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop_trans, rc);

	mdd_write_lock(env, obj, DT_SRC_PARENT);
	mlc->mlc_opc = MD_LAYOUT_SHRINK;
	rc = mdo_layout_change(env, obj, mlc, handle);
	mdd_write_unlock(env, obj);
	if (rc)
		GOTO(stop_trans, rc);

	if (le32_to_cpu(lmu->lum_stripe_count) == 1 && !lmv_is_fixed(lmv)) {
		rc = mdd_1sd_collapse(env, pobj, obj, stripe, attr, &xattrs,
				      mlc, &lname, handle);
		if (rc)
			GOTO(stop_trans, rc);
	}

	rc = mdd_changelog_data_store_xattr(env, mdd, CL_LAYOUT, 0, obj,
					    XATTR_NAME_LMV, handle);
	GOTO(stop_trans, rc);

stop_trans:
	rc = mdd_trans_stop(env, mdd, rc, handle);
out:
	mdd_xattrs_fini(&xattrs);
	if (pobj) {
		mdd_object_put(env, stripe);
		mdd_object_put(env, pobj);
	}
out_lmv:
	lu_buf_free(&lmv_buf);
	return rc;
}

static int mdd_dir_declare_split_plain(const struct lu_env *env,
					struct mdd_device *mdd,
					struct mdd_object *pobj,
					struct mdd_object *obj,
					struct mdd_object *tobj,
					struct mdd_xattrs *xattrs,
					struct md_layout_change *mlc,
					struct dt_allocation_hint *hint,
					struct thandle *handle)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	const struct lu_name *lname = mlc->mlc_name;
	struct lu_attr *la = &info->mdi_la_for_fix;
	struct lmv_user_md_v1 *lum = mlc->mlc_spec->u.sp_ea.eadata;
	struct linkea_data *ldata = &info->mdi_link_data;
	struct lmv_mds_md_v1 *lmv;
	__u32 count;
	int rc;

	mlc->mlc_opc = MD_LAYOUT_DETACH;
	rc = mdo_declare_layout_change(env, obj, mlc, handle);
	if (rc)
		return rc;

	memset(ldata, 0, sizeof(*ldata));
	rc = mdd_linkea_prepare(env, obj, NULL, NULL, mdd_object_fid(pobj),
				lname, 1, 0, ldata);
	if (rc)
		return rc;

	count = lum->lum_stripe_count;
	lum->lum_stripe_count = 0;
	/* don't set default LMV since it will become a striped dir  */
	lum->lum_max_inherit = LMV_INHERIT_NONE;
	mdd_object_make_hint(env, pobj, tobj, mlc->mlc_attr, mlc->mlc_spec,
			     hint);
	rc = mdd_declare_create(env, mdo2mdd(&pobj->mod_obj), pobj, tobj,
				lname, mlc->mlc_attr, handle, mlc->mlc_spec,
				ldata, NULL, NULL, NULL, NULL, hint);
	if (rc)
		return rc;

	/* tobj mode will be used in lod_declare_xattr_set(), but it's not
	 * created yet.
	 */
	tobj->mod_obj.mo_lu.lo_header->loh_attr |= S_IFDIR;

	lmv = (typeof(lmv))info->mdi_key;
	memset(lmv, 0, sizeof(*lmv));
	lmv->lmv_magic = cpu_to_le32(LMV_MAGIC_V1);
	lmv->lmv_stripe_count = cpu_to_le32(1);
	lmv->lmv_hash_type = cpu_to_le32(LMV_HASH_TYPE_DEFAULT);
	fid_le_to_cpu(&lmv->lmv_stripe_fids[0], mdd_object_fid(obj));

	mlc->mlc_opc = MD_LAYOUT_ATTACH;
	mlc->mlc_buf.lb_buf = lmv;
	mlc->mlc_buf.lb_len = lmv_mds_md_size(1, LMV_MAGIC_V1);
	rc = mdo_declare_layout_change(env, tobj, mlc, handle);
	if (rc)
		return rc;

	rc = mdd_foreach_xattr(env, tobj, xattrs, handle,
			       mdo_declare_xattr_set);
	if (rc)
		return rc;

	lum->lum_stripe_count = count;
	mlc->mlc_opc = MD_LAYOUT_SPLIT;
	rc = mdo_declare_layout_change(env, tobj, mlc, handle);
	if (rc)
		return rc;

	rc = mdo_declare_index_delete(env, pobj, lname->ln_name, handle);
	if (rc)
		return rc;

	rc = mdo_declare_index_insert(env, pobj, mdd_object_fid(tobj),
				      S_IFDIR, lname->ln_name, handle);
	if (rc)
		return rc;

	la->la_valid = LA_CTIME | LA_MTIME;
	rc = mdo_declare_attr_set(env, obj, la, handle);
	if (rc)
		return rc;

	rc = mdo_declare_attr_set(env, pobj, la, handle);
	if (rc)
		return rc;

	rc = mdd_declare_changelog_store(env, mdd, CL_MIGRATE, lname, NULL,
					 handle);
	return rc;
}

/**
 * mdd_dir_split_plain() - plain directory split
 * @env: execution environment
 * @mdd: MDD (metadata device)
 * @pobj: Parent dir being split
 * @obj: first stripe (shard 0) of @tobj
 * @tobj: Newly created plain dir (shard 1)
 * @xattrs: extended attribute from @obj to @tobj
 * @mlc: pointer to object's layout
 * @hint: performance hint
 * @handle: operations callback
 *
 * Return:
 * * %0 on success
 * * %negative on errno
 */
static int mdd_dir_split_plain(const struct lu_env *env,
				struct mdd_device *mdd,
				struct mdd_object *pobj,
				struct mdd_object *obj,
				struct mdd_object *tobj,
				struct mdd_xattrs *xattrs,
				struct md_layout_change *mlc,
				struct dt_allocation_hint *hint,
				struct thandle *handle)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct lu_attr *pattr = &info->mdi_pattr;
	struct lu_attr *la = &info->mdi_la_for_fix;
	const struct lu_name *lname = mlc->mlc_name;
	struct linkea_data *ldata = &info->mdi_link_data;
	int rc;

	ENTRY;

	/* copy linkea out and set on target later */
	rc = mdd_links_read(env, obj, ldata);
	if (rc)
		RETURN(rc);

	mlc->mlc_opc = MD_LAYOUT_DETACH;
	rc = mdo_layout_change(env, obj, mlc, handle);
	if (rc)
		RETURN(rc);

	/* don't set nlink from obj */
	mlc->mlc_attr->la_valid &= ~LA_NLINK;

	rc = mdd_create_object(env, pobj, tobj, mlc->mlc_attr, mlc->mlc_spec,
			       NULL, NULL, NULL, NULL, hint, handle, false);
	if (rc)
		RETURN(rc);

	rc = mdd_foreach_xattr(env, tobj, xattrs, handle, mdo_xattr_set);
	if (rc)
		RETURN(rc);

	rc = mdd_links_write(env, tobj, ldata, handle);
	if (rc)
		RETURN(rc);

	rc = __mdd_index_delete(env, pobj, lname->ln_name, true, handle);
	if (rc)
		RETURN(rc);

	rc = __mdd_index_insert(env, pobj, mdd_object_fid(tobj), S_IFDIR,
				lname->ln_name, handle);
	if (rc)
		RETURN(rc);

	la->la_ctime = la->la_mtime = mlc->mlc_attr->la_mtime;
	la->la_valid = LA_CTIME | LA_MTIME;

	mdd_write_lock(env, obj, DT_SRC_CHILD);
	rc = mdd_update_time(env, tobj, mlc->mlc_attr, la, handle);
	mdd_write_unlock(env, obj);
	if (rc)
		RETURN(rc);

	rc = mdd_la_get(env, pobj, pattr);
	if (rc)
		RETURN(rc);

	la->la_valid = LA_CTIME | LA_MTIME;

	mdd_write_lock(env, pobj, DT_SRC_PARENT);
	rc = mdd_update_time(env, pobj, pattr, la, handle);
	mdd_write_unlock(env, pobj);
	if (rc)
		RETURN(rc);

	/* FID changes, record it as CL_MIGRATE */
	rc = mdd_changelog_ns_store(env, mdd, CL_MIGRATE, 0, tobj,
				    pobj, pattr, mdd_object_fid(obj),
				    pobj, pattr, lname, lname, handle);
	RETURN(rc);
}

int mdd_dir_layout_split(const struct lu_env *env, struct md_object *o,
			 struct md_layout_change *mlc)
{
	struct mdd_thread_info *info = mdd_env_info(env);
	struct mdd_device *mdd = mdo2mdd(o);
	struct mdd_object *obj = md2mdd_obj(o);
	struct mdd_object *pobj = md2mdd_obj(mlc->mlc_parent);
	struct mdd_object *tobj = md2mdd_obj(mlc->mlc_target);
	struct dt_allocation_hint *hint = &info->mdi_hint;
	bool is_plain = false;
	struct mdd_xattrs xattrs;
	struct thandle *handle;
	int rc;

	ENTRY;

	LASSERT(S_ISDIR(mdd_object_type(obj)));

	rc = mdo_xattr_get(env, obj, &LU_BUF_NULL, XATTR_NAME_LMV);
	if (rc == -ENODATA)
		is_plain = true;
	else if (rc < 0)
		RETURN(rc);

	mdd_xattrs_init(&xattrs);
	if (is_plain)
		rc = mdd_xattrs_migrate_prep(env, &xattrs, obj, tobj,
					     true, true);

	handle = mdd_trans_create(env, mdd);
	if (IS_ERR(handle))
		GOTO(out, rc = PTR_ERR(handle));

	if (is_plain) {
		rc = mdd_dir_declare_split_plain(env, mdd, pobj, obj, tobj,
						 &xattrs, mlc, hint, handle);
	} else {
		mlc->mlc_opc = MD_LAYOUT_SPLIT;
		rc = mdo_declare_layout_change(env, obj, mlc, handle);
		if (rc)
			GOTO(stop_trans, rc);

		rc = mdd_declare_changelog_store(env, mdd, CL_LAYOUT, NULL,
						 NULL, handle);
	}
	if (rc)
		GOTO(stop_trans, rc);

	rc = mdd_trans_start(env, mdd, handle);
	if (rc)
		GOTO(stop_trans, rc);

	if (is_plain) {
		rc = mdd_dir_split_plain(env, mdd, pobj, obj, tobj, &xattrs,
					 mlc, hint, handle);
	} else {
		struct lu_buf *buf = &info->mdi_buf[0];

		buf->lb_buf = mlc->mlc_spec->u.sp_ea.eadata;
		buf->lb_len = mlc->mlc_spec->u.sp_ea.eadatalen;

		mdd_write_lock(env, obj, DT_TGT_CHILD);
		rc = mdo_xattr_set(env, obj, buf, XATTR_NAME_LMV,
				   LU_XATTR_CREATE, handle);
		mdd_write_unlock(env, obj);
		if (rc)
			GOTO(stop_trans, rc);

		rc = mdd_changelog_data_store_xattr(env, mdd, CL_LAYOUT, 0, obj,
						    XATTR_NAME_LMV, handle);
	}
	if (rc)
		GOTO(stop_trans, rc);

	EXIT;

stop_trans:
	rc = mdd_trans_stop(env, mdd, rc, handle);
out:
	mdd_xattrs_fini(&xattrs);

	return rc;
}

const struct md_dir_operations mdd_dir_ops = {
	.mdo_is_subdir     = mdd_is_subdir,
	.mdo_lookup        = mdd_lookup,
	.mdo_create        = mdd_create,
	.mdo_rename        = mdd_rename,
	.mdo_link          = mdd_link,
	.mdo_unlink        = mdd_unlink,
	.mdo_create_data   = mdd_create_data,
	.mdo_migrate	   = mdd_migrate,
};