Viewing: osp_dev.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) 2012, 2017, Intel Corporation.
 */

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Author: Alex Zhuravlev <alexey.zhuravlev@intel.com>
 * Author: Mikhail Pershin <mike.pershin@intel.com>
 * Author: Di Wang <di.wang@intel.com>
 */

/*
 * The Object Storage Proxy (OSP) module provides an implementation of
 * the DT API for remote MDTs and OSTs. Every local OSP device (or
 * object) is a proxy for a remote OSD device (or object). Thus OSP
 * converts DT operations into RPCs, which are sent to the OUT service
 * on a remote target, converted back to DT operations, and
 * executed. Of course there are many ways in which this description
 * is inaccurate but it's a good enough mental model. OSP is used by
 * the MDT stack in several ways:
 *
 * - OSP devices allocate FIDs for the stripe sub-objects of a striped
 *   file or directory.
 *
 * - OSP objects represent the remote MDT and OST objects that are
 *   the stripes of a striped object.
 *
 * - OSP devices log, send, and track synchronous operations (setattr
 *   and unlink) to remote targets.
 *
 * - OSP objects are the bottom slice of the compound LU object
 *   representing a remote MDT object: MDT/MDD/LOD/OSP.
 *
 * - OSP objects are used by LFSCK to represent remote OST objects
 *   during the verification of MDT-OST consistency.
 *
 * - OSP devices batch idempotent requests (declare_attr_get() and
 *   declare_xattr_get()) to the remote target and cache their results.
 *
 * In addition the OSP layer implements a subset of the OBD device API
 * to support being a client of a remote target, connecting to other
 * layers, and FID allocation.
 */

#define DEBUG_SUBSYSTEM S_MDS

#include <linux/kthread.h>

#include <uapi/linux/lustre/lustre_ioctl.h>
#include <lustre_ioctl_old.h>
#include <lustre_log.h>
#include <lustre_obdo.h>
#include <uapi/linux/lustre/lustre_param.h>
#include <obd_class.h>

#include "osp_internal.h"

/* Slab for OSP object allocation */
struct kmem_cache *osp_object_kmem;

static struct lu_kmem_descr osp_caches[] = {
	{
		.ckd_cache = &osp_object_kmem,
		.ckd_name  = "osp_obj",
		.ckd_size  = sizeof(struct osp_object)
	},
	{
		.ckd_cache = NULL
	}
};

/**
 * osp_object_alloc() - Implementation of lu_device_operations::ldo_object_alloc
 * @env: execution environment
 * @hdr: The header of the object stack. If it is NULL, it means the object is
 *	not built from top device, i.e. it is a sub-stripe object of striped
 *	directory or an OST object
 * @d: OSP device
 *
 * Allocates an OSP object in memory, whose FID is on the remote target.
 *
 * Return:
 * * %object if creation succeed (object being created)
 * * %NULL if the creation failed.
 */
static struct lu_object *osp_object_alloc(const struct lu_env *env,
					  const struct lu_object_header *hdr,
					  struct lu_device *d)
{
	struct osp_object *o;

	OBD_SLAB_ALLOC_PTR_GFP(o, osp_object_kmem, GFP_NOFS);
	if (o != NULL) {
		struct lu_object *l = &o->opo_obj.do_lu;

		/* If hdr is NULL, it means the object is not built
		 * from the top dev(MDT/OST), usually it happens when
		 * building striped object, like data object on MDT or
		 * striped object for directory
		 */
		if (hdr == NULL) {
			struct lu_object_header *h = &o->opo_header;

			lu_object_header_init(h);
			dt_object_init(&o->opo_obj, h, d);
			lu_object_add_top(h, l);
			set_bit(LU_OBJECT_DFREE, &h->loh_flags);
		} else {
			dt_object_init(&o->opo_obj, NULL, d);
		}


		l->lo_ops = &osp_lu_obj_ops;

		init_rwsem(&o->opo_sem);
		INIT_LIST_HEAD(&o->opo_xattr_list);
		INIT_LIST_HEAD(&o->opo_invalidate_cb_list);
		spin_lock_init(&o->opo_lock);
		init_rwsem(&o->opo_invalidate_sem);

		return l;
	}

	return NULL;
}

/**
 * osp_find_or_create_local_file() - Find or create the local object
 * @env: execution environment
 * @osp: OSP device
 * @attr: attributes of the object [out]
 * @reg_id: the local object ID of the file. It will be used to compose a local
 *	FID{FID_SEQ_LOCAL_FILE, reg_id, 0} to identify the object.
 *
 * Finds or creates the local file referenced by @reg_id and return the
 * attributes of the local file.
 *
 * Return:
 * * %object if found or created (object(dt_object))
 * * %ERR_PTR(errno) if not get the object.
 */
static struct dt_object
*osp_find_or_create_local_file(const struct lu_env *env, struct osp_device *osp,
			       struct lu_attr *attr, __u32 reg_id)
{
	struct osp_thread_info *osi = osp_env_info(env);
	struct dt_object_format dof = { 0 };
	struct dt_object       *dto;
	int		     rc;

	ENTRY;

	lu_local_obj_fid(&osi->osi_fid, reg_id);
	attr->la_valid = LA_MODE;
	attr->la_mode = S_IFREG | 0644;
	dof.dof_type = DFT_REGULAR;
	/* Find or create the local object by osi_fid. */
	dto = dt_find_or_create(env, osp->opd_storage, &osi->osi_fid,
				&dof, attr);
	if (IS_ERR(dto))
		RETURN(dto);

	/* Get attributes of the local object. */
	rc = dt_attr_get(env, dto, attr);
	if (rc) {
		CERROR("%s: can't be initialized: rc = %d\n",
		       osp->opd_obd->obd_name, rc);
		dt_object_put(env, dto);
		RETURN(ERR_PTR(rc));
	}
	RETURN(dto);
}

/**
 * osp_write_local_file() - Write data buffer to a local file object.
 * @env: execution environment
 * @osp: OSP device
 * @dt_obj: object written to
 * @buf: buffer containing byte array and length
 * @offset: write offset in the object in bytes
 *
 * Return:
 * * %0 if write succeed
 * * %-EFAULT if only part of buffer is written.
 * * %negative other negative errno if write failed.
 */
static int osp_write_local_file(const struct lu_env *env,
				struct osp_device *osp,
				struct dt_object *dt_obj,
				struct lu_buf *buf,
				loff_t offset)
{
	struct thandle *th;
	int rc;

	if (osp->opd_storage->dd_rdonly)
		RETURN(0);

	th = dt_trans_create(env, osp->opd_storage);
	if (IS_ERR(th))
		RETURN(PTR_ERR(th));

	rc = dt_declare_record_write(env, dt_obj, buf, offset, th);
	if (rc)
		GOTO(out, rc);
	rc = dt_trans_start_local(env, osp->opd_storage, th);
	if (rc)
		GOTO(out, rc);

	rc = dt_record_write(env, dt_obj, buf, &offset, th);
out:
	dt_trans_stop(env, osp->opd_storage, th);
	RETURN(rc);
}

/**
 * osp_init_last_objid() - Initialize last ID object.
 * @env: execution environment
 * @osp: OSP device
 *
 * This function initializes the LAST_ID file, which stores the current last
 * used id of data objects. The MDT will use the last used id and the last_seq
 * (see osp_init_last_seq()) to synchronize the precreate object cache with
 * OSTs.
 *
 * Return:
 * * %0 if initialization succeed
 * * %negative errno if initialization failed
 */
static int osp_init_last_objid(const struct lu_env *env, struct osp_device *osp)
{
	struct osp_thread_info	*osi = osp_env_info(env);
	struct lu_fid		*fid = &osp->opd_last_used_fid;
	struct dt_object	*dto;
	int			rc = -EFAULT;

	ENTRY;

	dto = osp_find_or_create_local_file(env, osp, &osi->osi_attr,
					    MDD_LOV_OBJ_OID);
	if (IS_ERR(dto))
		RETURN(PTR_ERR(dto));

	osp_objid_buf_prep(&osi->osi_lb, &osi->osi_off, &osp->opd_last_id,
			   osp->opd_index);

	/* object will be released in device cleanup path */
	if (osi->osi_attr.la_size >= (osi->osi_off + osi->osi_lb.lb_len)) {
		rc = dt_record_read(env, dto, &osi->osi_lb, &osi->osi_off);
		if (rc != 0 && rc != -EFAULT)
			GOTO(out, rc);
		/* In case of idif bits 32-48 go to f_seq
		 * (see osp_init_last_seq). So don't care
		 * about u64->u32 convertion. */
		fid->f_oid = osp->opd_last_id;
	}

	if (rc == -EFAULT) { /* fresh LAST_ID */
		osp->opd_last_id = 0;
		fid->f_oid = 0;
		rc = osp_write_local_file(env, osp, dto, &osi->osi_lb,
					  osi->osi_off);
		if (rc != 0)
			GOTO(out, rc);
	}
	osp->opd_last_used_oid_file = dto;
	RETURN(0);
out:
	/* object will be released in device cleanup path */
	CERROR("%s: can't initialize lov_objid: rc = %d\n",
	       osp->opd_obd->obd_name, rc);
	dt_object_put(env, dto);
	osp->opd_last_used_oid_file = NULL;
	RETURN(rc);
}

/**
 * osp_init_last_seq() - Initialize last sequence object.
 * @env: execution environment
 * @osp: OSP device
 *
 * This function initializes the LAST_SEQ file in the local OSD, which stores
 * the current last used sequence of data objects. The MDT will use the last
 * sequence and last id (see osp_init_last_objid()) to synchronize the
 * precreate object cache with OSTs.
 *
 * Return:
 * * %0 if initialization succeed
 * * %negative errno if initialization failed
 */
static int osp_init_last_seq(const struct lu_env *env, struct osp_device *osp)
{
	struct osp_thread_info	*osi = osp_env_info(env);
	struct lu_fid		*fid = &osp->opd_last_used_fid;
	struct dt_object	*dto;
	int			rc = -EFAULT;

	ENTRY;

	dto = osp_find_or_create_local_file(env, osp, &osi->osi_attr,
					    MDD_LOV_OBJ_OSEQ);
	if (IS_ERR(dto))
		RETURN(PTR_ERR(dto));

	osp_objseq_buf_prep(&osi->osi_lb, &osi->osi_off, &fid->f_seq,
			   osp->opd_index);

	/* object will be released in device cleanup path */
	if (osi->osi_attr.la_size >= (osi->osi_off + osi->osi_lb.lb_len)) {
		rc = dt_record_read(env, dto, &osi->osi_lb, &osi->osi_off);
		if (rc != 0 && rc != -EFAULT)
			GOTO(out, rc);
		if (fid_is_idif(fid))
			fid->f_seq = fid_idif_seq(osp->opd_last_id,
						  osp->opd_index);
	}

	if (rc == -EFAULT) { /* fresh OSP */
		fid->f_seq = 0;
		rc = osp_write_local_file(env, osp, dto, &osi->osi_lb,
					  osi->osi_off);
		if (rc != 0)
			GOTO(out, rc);
	}
	osp->opd_last_used_seq_file = dto;
	RETURN(0);
out:
	/* object will be released in device cleanup path */
	CERROR("%s: can't initialize lov_seq: rc = %d\n",
	       osp->opd_obd->obd_name, rc);
	dt_object_put(env, dto);
	osp->opd_last_used_seq_file = NULL;
	RETURN(rc);
}

/**
 * osp_last_used_init() - Initialize last OID and sequence object.
 * @env: execution environment
 * @osp: OSP device
 *
 * If the MDT is just upgraded to 2.4 from the lower version, where the
 * LAST_SEQ file does not exist, the file will be created and IDIF sequence
 * will be written into the file.
 *
 * Return:
 * * %0 if initialization succeed
 * * %negative if initialization failed
 */
static int osp_last_used_init(const struct lu_env *env, struct osp_device *osp)
{
	struct osp_thread_info *osi = osp_env_info(env);
	int		     rc;

	ENTRY;

	fid_zero(&osp->opd_last_used_fid);
	rc = osp_init_last_objid(env, osp);
	if (rc < 0) {
		CERROR("%s: Can not get ids %d from old objid!\n",
		       osp->opd_obd->obd_name, rc);
		RETURN(rc);
	}

	rc = osp_init_last_seq(env, osp);
	if (rc < 0) {
		CERROR("%s: Can not get sequence %d from old objseq!\n",
		       osp->opd_obd->obd_name, rc);
		GOTO(out, rc);
	}

	if (fid_oid(&osp->opd_last_used_fid) != 0 &&
	    fid_seq(&osp->opd_last_used_fid) == 0) {
		/* Just upgrade from the old version,
		 * set the seq to be IDIF */
		osp->opd_last_used_fid.f_seq =
		   fid_idif_seq(fid_oid(&osp->opd_last_used_fid),
				osp->opd_index);
		osp_objseq_buf_prep(&osi->osi_lb, &osi->osi_off,
				    &osp->opd_last_used_fid.f_seq,
				    osp->opd_index);
		rc = osp_write_local_file(env, osp, osp->opd_last_used_seq_file,
					  &osi->osi_lb, osi->osi_off);
		if (rc) {
			CERROR("%s : Can not write seq file: rc = %d\n",
			       osp->opd_obd->obd_name, rc);
			GOTO(out, rc);
		}
	}

	if (!fid_is_zero(&osp->opd_last_used_fid) &&
		 !fid_is_sane(&osp->opd_last_used_fid)) {
		CERROR("%s: Got invalid FID "DFID"\n", osp->opd_obd->obd_name,
			PFID(&osp->opd_last_used_fid));
		GOTO(out, rc = -EINVAL);
	}

	osp_fid_to_obdid(&osp->opd_last_used_fid, &osp->opd_last_id);
	CDEBUG(D_INFO, "%s: Init last used fid "DFID"\n",
	       osp->opd_obd->obd_name, PFID(&osp->opd_last_used_fid));
out:
	if (rc != 0) {
		if (osp->opd_last_used_oid_file != NULL) {
			dt_object_put(env, osp->opd_last_used_oid_file);
			osp->opd_last_used_oid_file = NULL;
		}
		if (osp->opd_last_used_seq_file != NULL) {
			dt_object_put(env, osp->opd_last_used_seq_file);
			osp->opd_last_used_seq_file = NULL;
		}
	}

	RETURN(rc);
}

/**
 * osp_last_used_fini() - Release last sequence & OID file objects in OSP device
 * @env: execution environment
 * @osp: OSP device
 */
static void osp_last_used_fini(const struct lu_env *env, struct osp_device *osp)
{
	/* release last_used file */
	if (osp->opd_last_used_oid_file != NULL) {
		dt_object_put(env, osp->opd_last_used_oid_file);
		osp->opd_last_used_oid_file = NULL;
	}

	if (osp->opd_last_used_seq_file != NULL) {
		dt_object_put(env, osp->opd_last_used_seq_file);
		osp->opd_last_used_seq_file = NULL;
	}
}

/**
 * osp_disconnect() - Disconnects the connection between OSP and its
 * correspondent MDT or OST, and the import will be marked as inactive. It will
 * only be called during OSP cleanup process.
 * @d: OSP device being disconnected
 *
 * Return:
 * * %0 if disconnection succeed
 * * %negative if disconnection failed
 */
static int osp_disconnect(struct osp_device *d)
{
	struct obd_device *obd = d->opd_obd;
	struct obd_import *imp;
	int rc = 0;

	ENTRY;

	imp = obd->u.cli.cl_import;

	CDEBUG(D_INFO, "%s: disconnecting import %px\n", obd->obd_name,
	       imp);
	/* Mark import deactivated now, so we don't try to reconnect if any
	 * of the cleanup RPCs fails (e.g. ldlm cancel, etc).  We don't
	 * fully deactivate the import, or that would drop all requests. */
	LASSERT(imp != NULL);

	set_bit(IMPF_DEACTIVE, imp->imp_flags);
	smp_mb__after_atomic();
	ptlrpc_deactivate_import(imp);

	ldlm_namespace_cleanup(obd->obd_namespace, LDLM_FL_LOCAL_ONLY);

	/* Some non-replayable imports (MDS's OSCs) are pinged, so just
	 * delete it regardless.  (It's safe to delete an import that was
	 * never added.) */
	(void)ptlrpc_pinger_del_import(imp);

	/* Send disconnect on healthy import, do force disconnect otherwise */
	spin_lock(&imp->imp_lock);
	if (imp->imp_state != LUSTRE_IMP_FULL)
		set_bit(OBDF_FORCE, imp->imp_obd->obd_flags);
	spin_unlock(&imp->imp_lock);

	init_completion(&d->opd_disconnect_cmplt);
	d->opd_disconnecting = 1;
	rc = ptlrpc_disconnect_import_async(imp, 0, &d->opd_disconnect_cmplt,
					    &d->opd_disconnect_res);
	RETURN(rc);
}

/**
 * osp_update_init() - Initialize the osp_update structure in OSP device
 * @osp: OSP device
 *
 * Allocate osp update structure and start update thread.
 *
 * Return:
 * * %0 if initialization succeeds.
 * * %negative if initialization fails.
 */
static int osp_update_init(struct osp_device *osp)
{
	struct task_struct *task;
	int rc;

	ENTRY;

	LASSERT(osp->opd_connect_mdt);

	if (osp->opd_storage->dd_rdonly)
		RETURN(0);

	OBD_ALLOC_PTR(osp->opd_update);
	if (osp->opd_update == NULL)
		RETURN(-ENOMEM);

	init_waitqueue_head(&osp->opd_update->ou_waitq);
	spin_lock_init(&osp->opd_update->ou_lock);
	INIT_LIST_HEAD(&osp->opd_update->ou_list);
	osp->opd_update->ou_rpc_version = 1;
	osp->opd_update->ou_version = 1;
	osp->opd_update->ou_generation = 0;

	rc = lu_env_init(&osp->opd_update->ou_env,
			 osp->opd_dt_dev.dd_lu_dev.ld_type->ldt_ctx_tags);
	if (rc < 0) {
		CERROR("%s: init env error: rc = %d\n", osp->opd_obd->obd_name,
		       rc);
		OBD_FREE_PTR(osp->opd_update);
		osp->opd_update = NULL;
		RETURN(rc);
	}
	/* start thread handling sending updates to the remote MDT */
	task = kthread_create(osp_send_update_thread, osp,
			      "osp_up%u-%u", osp->opd_index, osp->opd_group);
	if (IS_ERR(task)) {
		int rc = PTR_ERR(task);

		lu_env_fini(&osp->opd_update->ou_env);
		OBD_FREE_PTR(osp->opd_update);
		osp->opd_update = NULL;
		CERROR("%s: can't start precreate thread: rc = %d\n",
		       osp->opd_obd->obd_name, rc);
		RETURN(rc);
	}

	osp->opd_update->ou_update_task = task;
	wake_up_process(task);

	RETURN(0);
}

/**
 * osp_update_fini() - Finialize osp_update structure in OSP device
 * @env: execution environment.
 * @osp: OSP device.
 *
 * Stop the OSP update sending thread, then delete the left
 * osp thandle in the sending list.
 */
static void osp_update_fini(const struct lu_env *env, struct osp_device *osp)
{
	struct obd_import *imp = osp->opd_obd->u.cli.cl_import;
	struct osp_update_request *our;
	struct osp_update_request *tmp;
	struct osp_updates *ou = osp->opd_update;

	if (ou == NULL)
		return;

	kthread_stop(ou->ou_update_task);
	lu_env_fini(&ou->ou_env);

	/*
	 * import invalidation can be running in a dedicated thread.
	 * we have to wait for the thread's completion as the thread
	 * invalidates this list as well.
	 */
	wait_event_idle(imp->imp_recovery_waitq,
			(atomic_read(&imp->imp_inval_count) == 0));

	/* Remove the left osp thandle from the list */
	list_for_each_entry_safe(our, tmp, &ou->ou_list, our_list) {
		list_del_init(&our->our_list);
		LASSERT(our->our_th != NULL);
		osp_trans_callback(env, our->our_th, -EIO);
		/* our will be destroyed in osp_thandle_put() */
		osp_thandle_put(env, our->our_th);
	}

	OBD_FREE_PTR(ou);
	osp->opd_update = NULL;
}

/**
 * osp_shutdown() - Cleanup OSP, which includes disconnect import, cleanup
 * unlink log, stop precreate threads etc.
 * @env: execution environment.
 * @d: OSP device being disconnected.
 *
 * Return:
 * * %0 if cleanup succeed
 * * %negative if cleanup failed
 */
static int osp_shutdown(const struct lu_env *env, struct osp_device *d)
{
	struct obd_device *obd = d->opd_obd;
	struct obd_import *imp = obd->u.cli.cl_import;
	int  rc = 0;

	ENTRY;

	LASSERT(env);

	/* Shutdown could be called during fail initialization, LCFG_CLEANUP
	 * without LCFG_PRE_CLEANUP phase, like
	 * lod_add_device()->obd_connect() failure.
	 */
	if (d->opd_disconnecting) {
		wait_for_completion(&d->opd_disconnect_cmplt);
		rc = d->opd_disconnect_res;

		if (rc != 0)
			CERROR("%s: can't disconnect: rc = %d\n",
			       obd->obd_name, rc);
	}
	ptlrpc_invalidate_import(imp);
	osp_statfs_fini(d);

	if (!d->opd_connect_mdt) {
		/* stop sync thread */
		osp_sync_fini(d);

		/* stop precreate thread */
		osp_precreate_fini(d);

		/* release last_used file */
		osp_last_used_fini(env, d);
	}

	client_fid_fini(d->opd_obd);

	RETURN(rc);
}

/**
 * osp_process_config() - Implementation of osp_lu_ops::ldo_process_config
 * @env: execution environment
 * @dev: lu_device of OSP
 * @lcfg: config log
 *
 * This function processes config log records in OSP layer. It is usually
 * called from the top layer of MDT stack, and goes through the stack by calling
 * ldo_process_config of next layer.
 *
 * Return:
 * * %0 if the config log record is executed correctly.
 * * %negative if the record execution fails.
 */
static int osp_process_config(const struct lu_env *env,
			      struct lu_device *dev, struct lustre_cfg *lcfg)
{
	struct osp_device *d = lu2osp_dev(dev);
	struct dt_device *dt = lu2dt_dev(dev);
	struct obd_device *obd = d->opd_obd;
	ssize_t count;
	int rc;

	ENTRY;

	switch (lcfg->lcfg_command) {
	case LCFG_PRE_CLEANUP:
		rc = osp_disconnect(d);
		osp_update_fini(env, d);
		break;
	case LCFG_CLEANUP:
		/*
		 * cleanup ldlm so that PRE_CLEANUP phase doesn't block
		 * awaiting for locks held by MDT threads awaiting for
		 * all OSPs to interrupt their in-flight RPCs
		 */
		if (obd->obd_namespace != NULL)
			ldlm_namespace_free_prior(obd->obd_namespace, NULL, 1);
		lu_dev_del_linkage(dev->ld_site, dev);
		rc = osp_shutdown(env, d);
		break;
	case LCFG_PARAM:
		count = class_modify_config(lcfg, d->opd_connect_mdt ?
						  PARAM_OSP : PARAM_OSC,
					    &dt->dd_kobj);
		if (count < 0) {
			/* class_modify_config() haven't found matching
			 * parameter and returned an error so that layer(s)
			 * below could use that. But OSP is the bottom, so
			 * just ignore it
			 */
			CERROR("%s: unknown param %s\n",
			       (char *)lustre_cfg_string(lcfg, 0),
			       (char *)lustre_cfg_string(lcfg, 1));
		}
		rc = 0;
		break;
	default:
		CERROR("%s: unknown command %u\n",
		       (char *)lustre_cfg_string(lcfg, 0), lcfg->lcfg_command);
		rc = 0;
		break;
	}

	RETURN(rc);
}

/**
 * osp_recovery_complete() - Implementation of osp_lu_ops::ldo_recovery_complete
 * @env: execution environment
 * @dev: lu_device of OSP
 *
 * This function is called after recovery is finished, and OSP layer
 * will wake up precreate thread here.
 *
 * Return 0 unconditionally
 */
static int osp_recovery_complete(const struct lu_env *env,
				 struct lu_device *dev)
{
	struct osp_device	*osp = lu2osp_dev(dev);

	ENTRY;
	osp->opd_recovery_completed = 1;

	if (!osp->opd_connect_mdt && osp->opd_pre != NULL)
		wake_up(&osp->opd_pre_waitq);

	RETURN(0);
}

/**
 * osp_fid_alloc() - Implementation of ldo_fid_alloc() for OSP
 * @env: execution environment.
 * @d: lu_device of OSP (remote MDT to get @fid from)
 * @fid: new FID allocated [out]
 * @parent: parent pointer to lu device (unused)
 * @name: Common name structure (unused)
 *
 * Allocate FID from remote MDT.
 * Implementation of lu_device_operations::ldo_fid_alloc() for OSP
 *
 * see include/lu_object.h for the details.
 */
static int osp_fid_alloc(const struct lu_env *env, struct lu_device *d,
			 struct lu_fid *fid, struct lu_object *parent,
			 const struct lu_name *name)
{
	struct osp_device *osp = lu2osp_dev(d);
	struct client_obd *cli = &osp->opd_obd->u.cli;
	struct lu_client_seq *seq = cli->cl_seq;
	int rc;

	ENTRY;

	/* Sigh, fid client is not ready yet */
	if (!osp->opd_obd->u.cli.cl_seq)
		RETURN(-ENOTCONN);

	if (!osp->opd_obd->u.cli.cl_seq->lcs_exp)
		RETURN(-ENOTCONN);

	rc = seq_client_alloc_fid(env, seq, fid);

	RETURN(rc);
}

const struct lu_device_operations osp_lu_ops = {
	.ldo_object_alloc	= osp_object_alloc,
	.ldo_process_config	= osp_process_config,
	.ldo_recovery_complete	= osp_recovery_complete,
	.ldo_fid_alloc		= osp_fid_alloc,
};

/**
 * osp_statfs() - Implementation of dt_device_operations::dt_statfs
 * @env: execution environment.
 * @dev: dt_device of OSP.
 * @sfs: holds the retrieved statfs data. [out]
 * @info: if true then populate with object allocation space info [out]
 *
 * This function provides statfs status (for precreation) from
 * corresponding OST. Note: this function only retrieves the status
 * from the OSP device, and the real statfs RPC happens inside
 * precreate thread (see osp_statfs_update). Note: OSP for MDT does
 * not need to retrieve statfs data for now.
 *
 * Return:
 * * %0 if statfs data retrieved successfully or retrieval was not needed
 * * %negative if get statfs failed.
 */
static int osp_statfs(const struct lu_env *env, struct dt_device *dev,
		      struct obd_statfs *sfs, struct obd_statfs_info *info)
{
	enum lustre_imp_state state = LUSTRE_IMP_CLOSED;
	struct osp_device *d = dt2osp_dev(dev);
	struct obd_import *imp;
	int rc;

	ENTRY;

	with_imp_locked(d->opd_obd, imp, rc)
		state = imp->imp_state;
	if (rc || state == LUSTRE_IMP_CLOSED)
		RETURN(-ESHUTDOWN);

	if (unlikely(d->opd_imp_active == 0))
		RETURN(-ENOTCONN);

	/* return recently updated data */
	*sfs = d->opd_statfs;
	if (info) {
		info->os_reserved_mb_low = d->opd_reserved_mb_low;
		info->os_reserved_mb_high = d->opd_reserved_mb_high;
	}

	CDEBUG(D_OTHER,
	       "%s: blocks=%llu, bfree=%llu, bavail=%llu, bsize=%u, reserved_mb_low=%u, reserved_mb_high=%u, files=%llu, ffree=%llu, state=%x\n",
	       d->opd_obd->obd_name,
	       sfs->os_blocks, sfs->os_bfree, sfs->os_bavail, sfs->os_bsize,
	       d->opd_reserved_mb_low, d->opd_reserved_mb_high,
	       sfs->os_files, sfs->os_ffree, sfs->os_state);

	if (d->opd_pre == NULL || (info && !info->os_enable_pre))
		RETURN(0);

	/*
	 * The layer above osp (usually lod) can use f_precreated to
	 * estimate how many objects are available for immediate usage.
	 */
	spin_lock(&d->opd_pre_lock);
	sfs->os_fprecreated = osp_fid_diff(&d->opd_pre_last_created_fid,
					   &d->opd_pre_used_fid);
	sfs->os_fprecreated -= d->opd_pre_reserved;
	LASSERTF(sfs->os_fprecreated <= OST_MAX_PRECREATE * 2,
		 "last_created "DFID", next_fid "DFID", reserved %llu\n",
		 PFID(&d->opd_pre_last_created_fid), PFID(&d->opd_pre_used_fid),
		 d->opd_pre_reserved);
	spin_unlock(&d->opd_pre_lock);
	RETURN(0);
}

/**
 * osp_sync() - Implementation of dt_device_operations::dt_sync
 * @env: execution environment
 * @dev: dt_device of OSP
 *
 * This function synchronizes the OSP cache to the remote target. It wakes
 * up unlink log threads and sends out unlink records to the remote OST.
 *
 * Return:
 * * %0 if synchronization succeeds
 * * %negative if synchronization fails
 */
static int osp_sync(const struct lu_env *env, struct dt_device *dev)
{
	struct osp_device *d = dt2osp_dev(dev);
	time64_t start = ktime_get_seconds();
	int recs, rc = 0;
	u64 old;

	ENTRY;

	/* No Sync between MDTs yet. */
	if (d->opd_connect_mdt)
		RETURN(0);

	if (d->opd_storage->dd_rdonly)
		RETURN(0);

	recs = atomic_read(&d->opd_sync_changes);
	old = atomic64_read(&d->opd_sync_processed_recs);

	osp_sync_force(env, dt2osp_dev(dev));

	if (unlikely(d->opd_imp_active == 0))
		RETURN(-ENOTCONN);

	down_write(&d->opd_async_updates_rwsem);

	CDEBUG(D_OTHER, "%s: async updates %d\n", d->opd_obd->obd_name,
	       atomic_read(&d->opd_async_updates_count));

	/* make sure the connection is fine */
	rc = wait_event_idle_timeout(
		d->opd_sync_barrier_waitq,
		atomic_read(&d->opd_async_updates_count) == 0,
		cfs_time_seconds(obd_timeout));
	if (rc > 0)
		rc = 0;
	else if (rc == 0)
		rc = -ETIMEDOUT;

	up_write(&d->opd_async_updates_rwsem);
	if (rc != 0)
		GOTO(out, rc);

	CDEBUG(D_CACHE, "%s: processed %llu\n", d->opd_obd->obd_name,
	       (unsigned long long)atomic64_read(&d->opd_sync_processed_recs));

	while (atomic64_read(&d->opd_sync_processed_recs) < old + recs) {
		__u64 last = atomic64_read(&d->opd_sync_processed_recs);
		/* make sure the connection is fine */
		wait_event_idle_timeout(
			d->opd_sync_barrier_waitq,
			atomic64_read(&d->opd_sync_processed_recs)
			     >= old + recs,
			cfs_time_seconds(obd_timeout));

		if (atomic64_read(&d->opd_sync_processed_recs) >= old + recs)
			break;

		if (atomic64_read(&d->opd_sync_processed_recs) != last) {
			/* some progress have been made,
			 * keep trying... */
			continue;
		}

		/* no changes and expired, something is wrong */
		GOTO(out, rc = -ETIMEDOUT);
	}

	/* block new processing (barrier>0 - few callers are possible */
	atomic_inc(&d->opd_sync_barrier);

	CDEBUG(D_CACHE, "%s: %u in flight\n", d->opd_obd->obd_name,
	       atomic_read(&d->opd_sync_rpcs_in_flight));

	/* wait till all-in-flight are replied, so executed by the target */
	/* XXX: this is used by LFSCK at the moment, which doesn't require
	 *	all the changes to be committed, but in general it'd be
	 *	better to wait till commit */
	while (atomic_read(&d->opd_sync_rpcs_in_flight) > 0) {
		old = atomic_read(&d->opd_sync_rpcs_in_flight);

		wait_event_idle_timeout(
			d->opd_sync_barrier_waitq,
			atomic_read(&d->opd_sync_rpcs_in_flight) == 0,
			cfs_time_seconds(obd_timeout));

		if (atomic_read(&d->opd_sync_rpcs_in_flight) == 0)
			break;

		if (atomic_read(&d->opd_sync_rpcs_in_flight) != old) {
			/* some progress have been made */
			continue;
		}

		/* no changes and expired, something is wrong */
		GOTO(out, rc = -ETIMEDOUT);
	}

out:
	/* resume normal processing (barrier=0) */
	atomic_dec(&d->opd_sync_barrier);
	osp_sync_check_for_work(d);

	CDEBUG(D_CACHE, "%s: done in %lld: rc = %d\n", d->opd_obd->obd_name,
	       ktime_get_seconds() - start, rc);

	RETURN(rc);
}

static const struct dt_device_operations osp_dt_ops = {
	.dt_statfs	 = osp_statfs,
	.dt_sync	 = osp_sync,
	.dt_trans_create = osp_trans_create,
	.dt_trans_start  = osp_trans_start,
	.dt_trans_stop   = osp_trans_stop,
	.dt_trans_cb_add   = osp_trans_cb_add,
};

/**
 * osp_connect_to_osd() - Connect OSP to local OSD.
 * @env: execution environment
 * @osp: OSP device
 * @nextdev: the name of local OSD
 *
 * Locate the local OSD referenced by @nextdev and connect to it. Sometimes,
 * OSP needs to access the local OSD to store some information. For example,
 * during precreate, it needs to update last used OID and sequence file
 * (LAST_SEQ) in local OSD.
 *
 * Return:
 * * %0 connection succeeded
 * * %negative connection failed
 */
static int osp_connect_to_osd(const struct lu_env *env, struct osp_device *osp,
			      const char *nextdev)
{
	struct obd_connect_data	*data = NULL;
	struct obd_device	*obd;
	int			 rc;

	ENTRY;

	LASSERT(osp->opd_storage_exp == NULL);

	OBD_ALLOC_PTR(data);
	if (data == NULL)
		RETURN(-ENOMEM);

	obd = class_name2obd(nextdev);
	if (obd == NULL) {
		CERROR("%s: can't locate next device: %s\n",
		       osp->opd_obd->obd_name, nextdev);
		GOTO(out, rc = -ENOTCONN);
	}

	rc = obd_connect(env, &osp->opd_storage_exp, obd, &obd->obd_uuid, data,
			 NULL);
	if (rc) {
		CERROR("%s: cannot connect to next dev %s: rc = %d\n",
		       osp->opd_obd->obd_name, nextdev, rc);
		GOTO(out, rc);
	}

	osp->opd_dt_dev.dd_lu_dev.ld_site =
		osp->opd_storage_exp->exp_obd->obd_lu_dev->ld_site;
	LASSERT(osp->opd_dt_dev.dd_lu_dev.ld_site);
	osp->opd_storage = lu2dt_dev(osp->opd_storage_exp->exp_obd->obd_lu_dev);

out:
	OBD_FREE_PTR(data);
	RETURN(rc);
}

/**
 * osp_cancel_weight() - Determine if the lock needs to be cancelled
 * @lock: lock to be checked.
 *
 * Determine if the unused lock should be cancelled before replay, see
 * (ldlm_cancel_no_wait_policy()). Currently, only inode bits lock exists
 * between MDTs.
 *
 * Return:
 * * %1 if the lock needs to be cancelled before replay.
 * * %0 if the lock does not need to be cancelled before replay.
 */
static int osp_cancel_weight(struct ldlm_lock *lock)
{
	if (lock->l_resource->lr_type != LDLM_IBITS)
		RETURN(0);

	RETURN(1);
}

/**
 * osp_init0() - Initialize OSP device according to parameters in configuration
 * log @cfg.
 * @env: execution environment
 * @osp: OSP device
 * @ldt: lu device type of OSP
 * @cfg: configuration log
 *
 * Reconstruct the local device name from the configuration profile, and
 * initialize necessary threads and structures according to the OSP type
 * (MDT or OST).
 *
 * Since there is no record in the MDT configuration for the local disk
 * device, we have to extract this from elsewhere in the profile.
 * The only information we get at setup is from the OSC records:
 * setup 0:{fsname}-OSTxxxx-osc[-MDTxxxx] 1:lustre-OST0000_UUID 2:NID
 *
 * Note: configs generated by Lustre 1.8 are missing the -MDTxxxx part,
 * so, we need to reconstruct the name of the underlying OSD from this:
 * {fsname}-{svname}-osd, for example "lustre-MDT0000-osd".
 *
 * Return:
 * * %0 if OSP initialization succeeded.
 * * %negative if OSP initialization failed.
 */
static int osp_init0(const struct lu_env *env, struct osp_device *osp,
		     struct lu_device_type *ldt, struct lustre_cfg *cfg)
{
	struct obd_device	*obd;
	struct obd_import	*imp;
	char *src, *tgt, *osdname = NULL;
	const char *mdt;
	int			rc;
	u32 idx;

	ENTRY;

	mutex_init(&osp->opd_async_requests_mutex);
	INIT_LIST_HEAD(&osp->opd_async_updates);
	init_rwsem(&osp->opd_async_updates_rwsem);
	atomic_set(&osp->opd_async_updates_count, 0);

	obd = class_name2obd(lustre_cfg_string(cfg, 0));
	if (obd == NULL) {
		CERROR("Cannot find obd with name %s\n",
		       lustre_cfg_string(cfg, 0));
		RETURN(-ENODEV);
	}
	osp->opd_obd = obd;
	osp->opd_dt_dev.dd_lu_dev.ld_obd = obd;

	src = lustre_cfg_string(cfg, 0);
	if (src == NULL)
		RETURN(-EINVAL);

	tgt = strrchr(src, '-');
	if (tgt == NULL) {
		CERROR("%s: invalid target name %s: rc = %d\n",
		       osp->opd_obd->obd_name, lustre_cfg_string(cfg, 0),
		       -EINVAL);
		RETURN(-EINVAL);
	}

	if (strncmp(tgt, "-osc", 4) == 0) {
		/* Old OSC name fsname-OSTXXXX-osc */
		for (tgt--; tgt > src && *tgt != '-'; tgt--)
			;
		if (tgt == src) {
			CERROR("%s: invalid target name %s: rc = %d\n",
			       osp->opd_obd->obd_name,
			       lustre_cfg_string(cfg, 0), -EINVAL);
			RETURN(-EINVAL);
		}

		if (strncmp(tgt, "-OST", 4) != 0) {
			CERROR("%s: invalid target name %s: rc = %d\n",
			       osp->opd_obd->obd_name,
			       lustre_cfg_string(cfg, 0), -EINVAL);
			RETURN(-EINVAL);
		}

		rc = target_name2index(tgt + 1, &idx, &mdt);
		if (rc < 0 || rc & LDD_F_SV_ALL || mdt[0] != '-') {
			CERROR("%s: invalid OST index in '%s': rc = %d\n",
			       osp->opd_obd->obd_name, src, -EINVAL);
			RETURN(-EINVAL);
		}
		osp->opd_index = idx;
		osp->opd_group = 0;
		idx = tgt - src;
	} else {
		/* New OSC name fsname-OSTXXXX-osc-MDTXXXX */
		if (strncmp(tgt, "-MDT", 4) != 0 &&
		    strncmp(tgt, "-OST", 4) != 0) {
			CERROR("%s: invalid target name %s: rc = %d\n",
			       osp->opd_obd->obd_name,
			       lustre_cfg_string(cfg, 0), -EINVAL);
			RETURN(-EINVAL);
		}

		rc = target_name2index(tgt + 1, &idx, &mdt);
		if (rc < 0 || rc & LDD_F_SV_ALL || *mdt != '\0') {
			CERROR("%s: invalid OST index in '%s': rc = %d\n",
			       osp->opd_obd->obd_name, src, -EINVAL);
			RETURN(-EINVAL);
		}

		/* Get MDT index from the name and set it to opd_group,
		 * which will be used by OSP to connect with OST */
		osp->opd_group = idx;
		if (tgt - src <= 12) {
			CERROR("%s: invalid mdt index from %s: rc =%d\n",
			       osp->opd_obd->obd_name,
			       lustre_cfg_string(cfg, 0), -EINVAL);
			RETURN(-EINVAL);
		}

		if (strncmp(tgt - 12, "-MDT", 4) == 0)
			osp->opd_connect_mdt = 1;

		rc = target_name2index(tgt - 11, &idx, &mdt);
		if (rc < 0 || rc & LDD_F_SV_ALL || mdt[0] != '-') {
			CERROR("%s: invalid OST index in '%s': rc =%d\n",
			       osp->opd_obd->obd_name, src, -EINVAL);
			RETURN(-EINVAL);
		}

		osp->opd_index = idx;
		idx = tgt - src - 12;
	}
	/* check the fsname length, and after this everything else will fit */
	if (idx > MTI_NAME_MAXLEN) {
		CERROR("%s: fsname too long in '%s': rc = %d\n",
		       osp->opd_obd->obd_name, src, -EINVAL);
		RETURN(-EINVAL);
	}

	OBD_ALLOC(osdname, MAX_OBD_NAME);
	if (osdname == NULL)
		RETURN(-ENOMEM);

	memcpy(osdname, src, idx); /* copy just the fsname part */
	osdname[idx] = '\0';

	mdt = strstr(mdt, "-MDT");
	if (mdt == NULL) /* 1.8 configs don't have "-MDT0000" at the end */
		strcat(osdname, "-MDT0000");
	else
		strcat(osdname, mdt);
	strcat(osdname, "-osd");
	CDEBUG(D_HA, "%s: connect to %s (%s)\n", obd->obd_name, osdname, src);

	osp->opd_dt_dev.dd_lu_dev.ld_ops = &osp_lu_ops;
	osp->opd_dt_dev.dd_ops = &osp_dt_ops;

	obd->obd_lu_dev = &osp->opd_dt_dev.dd_lu_dev;

	rc = osp_connect_to_osd(env, osp, osdname);
	if (rc)
		GOTO(out_fini, rc);

	rc = ptlrpcd_addref();
	if (rc)
		GOTO(out_disconnect, rc);

	rc = client_obd_setup(obd, cfg);
	if (rc) {
		CERROR("%s: can't setup obd: rc = %d\n", osp->opd_obd->obd_name,
		       rc);
		GOTO(out_ref, rc);
	}

	rc = client_fid_init(osp->opd_obd, NULL, osp->opd_connect_mdt ?
			     LUSTRE_SEQ_METADATA : LUSTRE_SEQ_DATA);
	if (rc) {
		CERROR("%s: fid init error: rc = %d\n",
		       osp->opd_obd->obd_name, rc);
		GOTO(out_obd, rc);
	}

	if (!osp->opd_connect_mdt) {
		/* Initialize last id from the storage - will be
		 * used in orphan cleanup. */
		if (!osp->opd_storage->dd_rdonly) {
			rc = osp_last_used_init(env, osp);
			if (rc)
				GOTO(out_fid, rc);
		}

		/* Initialize precreation thread, it handles new
		 * connections as well. */
		rc = osp_init_precreate(osp);
		if (rc)
			GOTO(out_last_used, rc);

		/*
		 * Initialize synhronization mechanism taking
		 * care of propogating changes to OST in near
		 * transactional manner.
		 */
		rc = osp_sync_init(env, osp);
		if (rc < 0)
			GOTO(out_precreat, rc);
	} else {
		osp->opd_got_disconnected = 1;
		rc = osp_update_init(osp);
		if (rc != 0)
			GOTO(out_fid, rc);
	}

	rc = osp_init_statfs(osp);
	if (rc)
		GOTO(out_precreat, rc);

	ns_register_cancel(obd->obd_namespace, osp_cancel_weight);

	/*
	 * Initiate connect to OST
	 */
	imp = obd->u.cli.cl_import;

	rc = ptlrpc_init_import(imp);
	if (rc)
		GOTO(out, rc);

	osp_tunables_init(osp);

	OBD_FREE(osdname, MAX_OBD_NAME);
	init_waitqueue_head(&osp->opd_out_waitq);
	RETURN(0);

out:
	if (!osp->opd_connect_mdt)
		/* stop sync thread */
		osp_sync_fini(osp);
out_precreat:
	/* stop precreate thread */
	if (!osp->opd_connect_mdt)
		osp_precreate_fini(osp);
	else
		osp_update_fini(env, osp);
out_last_used:
	if (!osp->opd_connect_mdt)
		osp_last_used_fini(env, osp);
out_fid:
	client_fid_fini(osp->opd_obd);
out_obd:
	client_obd_cleanup(obd);
out_ref:
	ptlrpcd_decref();
out_disconnect:
	obd_disconnect(osp->opd_storage_exp);
out_fini:
	OBD_FREE(osdname, MAX_OBD_NAME);
	RETURN(rc);
}

/**
 * osp_device_free() - Implementation of ldto_device_free
 * @env: execution environment
 * @lu: lu_device of OSP
 *
 * Implementation of lu_device_type_operations::ldto_device_free
 * Free the OSP device in memory.  No return value is needed for now,
 * so always return NULL to comply with the interface.
 *
 * Return:
 * * %NULL unconditionally
 */
static struct lu_device *osp_device_free(const struct lu_env *env,
					 struct lu_device *lu)
{
	struct osp_device *osp = lu2osp_dev(lu);

	lu_site_print(env, lu->ld_site, &lu->ld_ref, D_ERROR,
		      lu_cdebug_printer);
	dt_device_fini(&osp->opd_dt_dev);
	OBD_FREE_PTR(osp);

	return NULL;
}

/**
 * osp_device_alloc() - Implementation of ldto_device_alloc
 * @env: execution environment
 * @type: device type of OSP
 * @lcfg: config log
 *
 * Implementation of lu_device_type_operations::ldto_device_alloc
 * This function allocates and initializes OSP device in memory according to
 * the config log.
 *
 *
 * Return:
 * * %pointer of allocated OSP if succeed.
 * * %ERR_PTR(errno) if failed.
 */
static struct lu_device *osp_device_alloc(const struct lu_env *env,
					  struct lu_device_type *type,
					  struct lustre_cfg *lcfg)
{
	struct osp_device *osp;
	struct lu_device  *ld;

	OBD_ALLOC_PTR(osp);
	if (osp == NULL) {
		ld = ERR_PTR(-ENOMEM);
	} else {
		int rc;

		ld = osp2lu_dev(osp);
		dt_device_init(&osp->opd_dt_dev, type);
		rc = osp_init0(env, osp, type, lcfg);
		if (rc != 0) {
			osp_device_free(env, ld);
			ld = ERR_PTR(rc);
		}
	}
	return ld;
}

/**
 * osp_device_fini() - Implementation of ldto_device_fini
 * @env: execution environment
 * @ld: lu_device of OSP
 *
 * Implementation of lu_device_type_operations::ldto_device_fini
 * This function cleans up the OSP device, i.e. release and free those
 * attached items in osp_device.
 *
 * Return:
 * * %NULL if cleanup succeeded.
 * * %ERR_PTR(errno) if cleanup failed.
 */
static struct lu_device *osp_device_fini(const struct lu_env *env,
					 struct lu_device *ld)
{
	struct osp_device *osp = lu2osp_dev(ld);

	ENTRY;

	if (osp->opd_async_requests != NULL) {
		osp_update_request_destroy(env, osp->opd_async_requests);
		osp->opd_async_requests = NULL;
	}

	if (osp->opd_storage_exp) {
		/* wait for the commit callbacks to complete */
		io_wait_event(osp->opd_sync_waitq,
			      atomic_read(&osp->opd_commits_registered) == 0);
		obd_disconnect(osp->opd_storage_exp);
	}

	LASSERT(osp->opd_obd);

	/* We must remove the tunables first so nobody accessed them when
	 * the obd devices are already gone.
	 */
	osp_tunables_fini(osp);
	client_obd_cleanup(osp->opd_obd);
	ptlrpcd_decref();
	RETURN(NULL);
}

/**
 * osp_reconnect() - Implementation of obd_ops::o_reconnect
 * @env: unused
 * @exp: unused
 * @obd: unused
 * @cluuid: unused
 * @data: unused
 * @localdata: unused
 *
 * This function is empty and does not need to do anything for now.
 *
 * Return 0 always
 */
static int osp_reconnect(const struct lu_env *env,
			 struct obd_export *exp, struct obd_device *obd,
			 struct obd_uuid *cluuid,
			 struct obd_connect_data *data,
			 void *localdata)
{
	return 0;
}

/*
 * osp_obd_connect() - Implementation of obd_ops::o_connect
 * @env: execution environment
 * @exp: export connected to OSP [out]
 * @obd: OSP device
 * @cluuid: OSP device client uuid
 * @data: connect_data to be used to connect to the remote target
 * @localdata: necessary for the API interface, but not used in this function
 *
 * Connect OSP to the remote target (MDT or OST). Allocate the
 * export and return it to the LOD, which calls this function
 * for each OSP to connect it to the remote target. This function
 * is currently only called once per OSP.
 *
 * Return:
 * * %0 if the connection succeeded.
 * * %negative if the connection failed.
 */
static int osp_obd_connect(const struct lu_env *env, struct obd_export **exp,
			   struct obd_device *obd, struct obd_uuid *cluuid,
			   struct obd_connect_data *data, void *localdata)
{
	struct osp_device *osp = lu2osp_dev(obd->obd_lu_dev);
	int rc;

	ENTRY;

	LASSERT(data != NULL);
	LASSERT(data->ocd_connect_flags & OBD_CONNECT_INDEX);

	rc = client_connect_import(env, &osp->opd_exp, obd, cluuid, data,
				   localdata);
	if (rc)
		RETURN(rc);

	*exp = osp->opd_exp;

	osp->opd_obd->u.cli.cl_seq->lcs_exp = class_export_get(osp->opd_exp);
	/* precreate thread can be waiting for us to initialize fld client */
	wake_up(&osp->opd_pre_waitq);

	RETURN(rc);
}

/**
 * osp_obd_disconnect() - Implementation of obd_ops::o_disconnect
 * @exp: export to be disconnected.
 *
 * Disconnect the export for the OSP.  This is called by LOD to release the
 * OSP during cleanup (see lod_del_device()). The OSP will be released after
 * the export is released.
 *
 * Return:
 * * %0 if disconnection succeed
 * * %negative if disconnection failed
 */
static int osp_obd_disconnect(struct obd_export *exp)
{
	struct obd_device *obd = exp->exp_obd;
	int                rc;

	ENTRY;

	rc = class_disconnect(exp);
	if (rc) {
		CERROR("%s: class disconnect error: rc = %d\n",
		       obd->obd_name, rc);
		RETURN(rc);
	}

	/* destroy the device */
	class_manual_cleanup(obd);

	RETURN(rc);
}

/**
 * osp_obd_statfs() - Implementation of obd_ops::o_statfs
 * @env: execution environment
 * @exp: connection state from this OSP to the parent (LOD) device
 * @osfs: hold the statfs result [out]
 * @unused: Not used in this function for now
 * @flags: flags to indicate how OSP will issue the RPC
 *
 * Send a RPC to the remote target to get statfs status. This is only used
 * in lprocfs helpers by obd_statfs.
 *
 * Return:
 * * %0 if statfs succeeded.
 * * %negative if statfs failed.
 */
static int osp_obd_statfs(const struct lu_env *env, struct obd_export *exp,
			  struct obd_statfs *osfs, time64_t unused, __u32 flags)
{
	struct obd_statfs	*msfs;
	struct ptlrpc_request	*req;
	struct obd_import	*imp = NULL, *imp0;
	int			 rc;

	ENTRY;

	/* Since the request might also come from lprocfs, so we need
	 * sync this with client_disconnect_export Bug15684
	 */
	with_imp_locked(exp->exp_obd, imp0, rc)
		imp = class_import_get(imp0);
	if (rc)
		RETURN(rc);

	req = ptlrpc_request_alloc(imp, &RQF_OST_STATFS);

	class_import_put(imp);

	if (req == NULL)
		RETURN(-ENOMEM);

	rc = ptlrpc_request_pack(req, LUSTRE_OST_VERSION, OST_STATFS);
	if (rc) {
		ptlrpc_request_free(req);
		RETURN(rc);
	}
	ptlrpc_request_set_replen(req);
	req->rq_request_portal = OST_CREATE_PORTAL;
	ptlrpc_at_set_req_timeout(req);

	if (flags & OBD_STATFS_NODELAY) {
		/* procfs requests not want stat in wait for avoid deadlock */
		req->rq_no_resend = 1;
		req->rq_no_delay = 1;
	}

	rc = ptlrpc_queue_wait(req);
	if (rc)
		GOTO(out, rc);

	msfs = req_capsule_server_get(&req->rq_pill, &RMF_OBD_STATFS);
	if (msfs == NULL)
		GOTO(out, rc = -EPROTO);

	*osfs = *msfs;

	EXIT;
out:
	ptlrpc_req_put(req);
	return rc;
}

/**
 * osp_import_event() - Implementation of obd_ops::o_import_event
 * @obd: OSP OBD device
 * @imp: import attached from OSP to remote (OST/MDT) service
 * @event: event related to remote service (IMP_EVENT_*)
 *
 * This function is called when some related import event happens. It will
 * mark the necessary flags according to the event and notify the necessary
 * threads (mainly precreate thread).
 *
 * Return:
 * * %0 if the event handling succeeded.
 * * %negative if the event handling failed.
 */
static int osp_import_event(struct obd_device *obd, struct obd_import *imp,
			    enum obd_import_event event)
{
	struct osp_device *d = lu2osp_dev(obd->obd_lu_dev);
	int rc;

	switch (event) {
	case IMP_EVENT_DISCON:
		d->opd_got_disconnected = 1;
		d->opd_imp_connected = 0;
		if (d->opd_connect_mdt)
			break;

		if (d->opd_pre != NULL) {
			osp_pre_update_status(d, -ENODEV);
			wake_up(&d->opd_pre_waitq);
		}

		CDEBUG(D_HA, "got disconnected\n");
		break;
	case IMP_EVENT_INACTIVE:
		d->opd_imp_active = 0;
		d->opd_imp_connected = 0;
		d->opd_obd->obd_inactive = 1;
		if (d->opd_connect_mdt)
			break;
		if (d->opd_pre != NULL) {
			/* Import is invalid, we can`t get stripes so
			 * wakeup waiters */
			rc = test_bit(IMPF_DEACTIVE, imp->imp_flags) ? -ESHUTDOWN : -ENODEV;
			osp_pre_update_status(d, rc);
			wake_up(&d->opd_pre_waitq);
		}

		CDEBUG(D_HA, "got inactive\n");
		break;
	case IMP_EVENT_ACTIVE:
		d->opd_imp_active = 1;

		d->opd_new_connection = 1;
		d->opd_imp_connected = 1;
		d->opd_imp_seen_connected = 1;
		d->opd_obd->obd_inactive = 0;
		wake_up(&d->opd_pre_waitq);
		if (d->opd_connect_mdt)
			break;

		osp_sync_check_for_work(d);
		CDEBUG(D_HA, "got connected\n");
		break;
	case IMP_EVENT_INVALIDATE:
		if (d->opd_connect_mdt)
			osp_invalidate_request(d);

		if (obd->obd_namespace == NULL)
			break;
		ldlm_namespace_cleanup(obd->obd_namespace, LDLM_FL_LOCAL_ONLY);
		break;
	case IMP_EVENT_OCD:
	case IMP_EVENT_DEACTIVATE:
	case IMP_EVENT_ACTIVATE:
		break;
	default:
		CERROR("%s: unsupported import event: %#x\n",
		       obd->obd_name, event);
	}
	return 0;
}

/**
 * osp_iocontrol() - Implementation of obd_ops: o_iocontrol
 * @cmd: ioctl command.
 * @exp: export of this OSP.
 * @len: data length of @karg.
 * @karg: input argument which is packed as obd_ioctl_data
 * @uarg: pointer to userspace buffer (must access by copy_to_user()) [out]
 *
 * This function is the ioctl handler for OSP. Note: lctl will access the OSP
 * directly by ioctl, instead of through the MDS stack.
 *
 * Return:
 * * %0 if the ioctl handling succeeded.
 * * %negative if the ioctl handling failed.
 */
static int osp_iocontrol(unsigned int cmd, struct obd_export *exp, int len,
			 void *karg, void __user *uarg)
{
	struct obd_device *obd = exp->exp_obd;
	struct osp_device *d;
	struct obd_ioctl_data *data;
	int rc = -EINVAL;

	ENTRY;
	CDEBUG(D_IOCTL, "%s: cmd=%x len=%u karg=%pK uarg=%pK\n",
	       exp->exp_obd->obd_name, cmd, len, karg, uarg);
	if (unlikely(karg == NULL)) {
		CERROR("%s: iocontrol from '%s' cmd=%x karg=NULL: rc = %d\n",
		       obd->obd_name, current->comm, cmd, rc);
		RETURN(rc);
	}
	data = karg;

	LASSERT(obd->obd_lu_dev);
	d = lu2osp_dev(obd->obd_lu_dev);
	LASSERT(d->opd_dt_dev.dd_ops == &osp_dt_ops);

	if (!try_module_get(THIS_MODULE)) {
		CERROR("%s: cannot get module '%s'\n", obd->obd_name,
		       module_name(THIS_MODULE));
		return -EINVAL;
	}

	switch (cmd) {
	case OBD_IOC_CLIENT_RECOVER:
		rc = ptlrpc_recover_import(obd->u.cli.cl_import,
					   data->ioc_inlbuf1, 0);
		if (rc > 0)
			rc = 0;
		break;
#ifdef IOC_OSC_SET_ACTIVE
	case_OBD_IOC_DEPRECATED_FT(IOC_OSC_SET_ACTIVE, obd->obd_name, 2, 17);
#endif
	case OBD_IOC_SET_ACTIVE:
		rc = ptlrpc_set_import_active(obd->u.cli.cl_import,
					      data->ioc_offset);
		break;
	default:
		rc = OBD_IOC_ERROR(obd->obd_name, cmd, "unrecognized", -ENOTTY);
		break;
	}
	module_put(THIS_MODULE);
	return rc;
}

/**
 * osp_obd_get_info() - Implementation of obd_ops::o_get_info
 * @env: execution environment
 * @exp: export of this OSP
 * @keylen: length of @key
 * @key: the key
 * @vallen: length of @val [out]
 * @val: holds the value returned by the key [out]
 *
 * Retrieve information by key. Retrieval starts from the top layer
 * (MDT) of the MDS stack and traverses the stack by calling the
 * obd_get_info() method of the next sub-layer.
 *
 * Return:
 * * %0 if getting information succeeded.
 * * %negative if getting information failed.
 */
static int osp_obd_get_info(const struct lu_env *env, struct obd_export *exp,
			    __u32 keylen, void *key, __u32 *vallen, void *val)
{
	int rc = -EINVAL;

	if (KEY_IS(KEY_OSP_CONNECTED)) {
		struct obd_device	*obd = exp->exp_obd;
		struct osp_device	*osp;

		if (!test_bit(OBDF_SET_UP, obd->obd_flags) ||
		    test_bit(OBDF_STOPPING, obd->obd_flags))
			RETURN(-EAGAIN);

		osp = lu2osp_dev(obd->obd_lu_dev);
		LASSERT(osp);
		/*
		 * 1.8/2.0 behaviour is that OST being connected once at least
		 * is considered "healthy". and one "healthy" OST is enough to
		 * allow lustre clients to connect to MDS
		 */
		RETURN(!osp->opd_imp_seen_connected);
	}

	RETURN(rc);
}

static int osp_obd_set_info_async(const struct lu_env *env,
				  struct obd_export *exp,
				  u32 keylen, void *key,
				  u32 vallen, void *val,
				  struct ptlrpc_request_set *set)
{
	struct obd_device	*obd = exp->exp_obd;
	struct obd_import	*imp = obd->u.cli.cl_import;
	struct osp_device	*osp;
	struct ptlrpc_request	*req;
	char			*tmp;
	int			 rc;

	if (KEY_IS(KEY_SPTLRPC_CONF)) {
		sptlrpc_conf_client_adapt(exp->exp_obd);
		RETURN(0);
	}

	LASSERT(set != NULL);
	if (!test_bit(OBDF_SET_UP, obd->obd_flags) ||
	    test_bit(OBDF_STOPPING, obd->obd_flags))
		RETURN(-EAGAIN);
	osp = lu2osp_dev(obd->obd_lu_dev);

	req = ptlrpc_request_alloc(imp, &RQF_OBD_SET_INFO);
	if (req == NULL)
		RETURN(-ENOMEM);

	req_capsule_set_size(&req->rq_pill, &RMF_SETINFO_KEY,
			     RCL_CLIENT, keylen);
	req_capsule_set_size(&req->rq_pill, &RMF_SETINFO_VAL,
			     RCL_CLIENT, vallen);
	if (osp->opd_connect_mdt)
		rc = ptlrpc_request_pack(req, LUSTRE_MDS_VERSION, MDS_SET_INFO);
	else
		rc = ptlrpc_request_pack(req, LUSTRE_OST_VERSION, OST_SET_INFO);
	if (rc) {
		ptlrpc_request_free(req);
		RETURN(rc);
	}

	tmp = req_capsule_client_get(&req->rq_pill, &RMF_SETINFO_KEY);
	memcpy(tmp, key, keylen);
	tmp = req_capsule_client_get(&req->rq_pill, &RMF_SETINFO_VAL);
	memcpy(tmp, val, vallen);

	ptlrpc_request_set_replen(req);
	ptlrpc_set_add_req(set, req);
	ptlrpc_check_set(NULL, set);

	RETURN(0);
}

/* context key constructor/destructor: mdt_key_init, mdt_key_fini */
LU_KEY_INIT_FINI(osp, struct osp_thread_info);

static void osp_key_exit(const struct lu_context *ctx,
			 struct lu_context_key *key, void *data)
{
	struct osp_thread_info *info = data;

	info->osi_attr.la_valid = 0;
}

struct lu_context_key osp_thread_key = {
	.lct_tags = LCT_MD_THREAD,
	.lct_init = osp_key_init,
	.lct_fini = osp_key_fini,
	.lct_exit = osp_key_exit
};

LU_TYPE_INIT_FINI(osp, &osp_thread_key);

static const struct lu_device_type_operations osp_device_type_ops = {
	.ldto_init           = osp_type_init,
	.ldto_fini           = osp_type_fini,

	.ldto_start          = osp_type_start,
	.ldto_stop           = osp_type_stop,

	.ldto_device_alloc   = osp_device_alloc,
	.ldto_device_free    = osp_device_free,

	.ldto_device_fini    = osp_device_fini
};

static struct lu_device_type osp_device_type = {
	.ldt_tags     = LU_DEVICE_DT,
	.ldt_name     = LUSTRE_OSP_NAME,
	.ldt_ops      = &osp_device_type_ops,
	.ldt_ctx_tags = LCT_MD_THREAD | LCT_DT_THREAD,
};

static const struct obd_ops osp_obd_device_ops = {
	.o_owner	= THIS_MODULE,
	.o_add_conn	= client_import_add_conn,
	.o_del_conn	= client_import_del_conn,
	.o_reconnect	= osp_reconnect,
	.o_connect	= osp_obd_connect,
	.o_disconnect	= osp_obd_disconnect,
	.o_get_info     = osp_obd_get_info,
	.o_set_info_async = osp_obd_set_info_async,
	.o_import_event	= osp_import_event,
	.o_iocontrol	= osp_iocontrol,
	.o_statfs	= osp_obd_statfs,
};

/**
 * osp_init() - Initialize OSP module.
 *
 * Register device types OSP and Light Weight Proxy (LWP) (see lwp_dev.c)
 * in obd_types (see class_obd.c).  Initialize procfs for the
 * OSP device.  Note: OSP was called OSC before Lustre 2.4,
 * so for compatibility it still uses the name "osc" in procfs.
 * This is called at module load time.
 *
 * Return:
 * * %0 if initialization succeeds.
 * * %negative if initialization failed.
 */
static int __init osp_init(void)
{
	struct obd_type *sym;
	int rc;

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

	rc = lu_kmem_init(osp_caches);
	if (rc)
		return rc;

	rc = class_register_type(&osp_obd_device_ops, NULL, false,
				 LUSTRE_OSP_NAME, &osp_device_type);
	if (rc != 0) {
		lu_kmem_fini(osp_caches);
		return rc;
	}

	rc = class_register_type(&lwp_obd_device_ops, NULL, false,
				 LUSTRE_LWP_NAME, &lwp_device_type);
	if (rc != 0) {
		class_unregister_type(LUSTRE_OSP_NAME);
		lu_kmem_fini(osp_caches);
		return rc;
	}

	/* create "osc" entry for compatibility purposes */
	sym = class_add_symlinks(LUSTRE_OSC_NAME, true);
	if (IS_ERR(sym)) {
		rc = PTR_ERR(sym);
		/* does real "osc" already exist ? */
		if (rc == -EEXIST)
			rc = 0;
	}

	return rc;
}

/**
 * osp_exit() - Finalize OSP module.
 *
 * This callback is called when kernel unloads OSP module from memory, and
 * it will deregister OSP and LWP device type from obd_types (see class_obd.c).
 */
static void __exit osp_exit(void)
{
	struct obd_type *sym = class_search_type(LUSTRE_OSC_NAME);

	/* if this was never fully initialized by the osc layer
	 * then we are responsible for freeing this obd_type
	 */
	if (sym) {
		/* final put if we manage this obd type */
		if (sym->typ_sym_filter)
			kobject_put(&sym->typ_kobj);
		/* put reference taken by class_search_type */
		kobject_put(&sym->typ_kobj);
	}

	class_unregister_type(LUSTRE_LWP_NAME);
	class_unregister_type(LUSTRE_OSP_NAME);
	lu_kmem_fini(osp_caches);
}

MODULE_AUTHOR("OpenSFS, Inc. <http://www.lustre.org/>");
MODULE_DESCRIPTION("Lustre OSD Storage Proxy ("LUSTRE_OSP_NAME")");
MODULE_VERSION(LUSTRE_VERSION_STRING);
MODULE_LICENSE("GPL");

late_initcall_sync(osp_init);
module_exit(osp_exit);