Viewing: mdt_hsm_cdt_agent.c

// SPDX-License-Identifier: GPL-2.0

/*
 * (C) Copyright 2012 Commissariat a l'energie atomique et aux energies
 *
 * Copyright (c) 2016, 2017, Intel Corporation.
 *     alternatives
 */

/*
 * lustre/mdt/mdt_hsm_cdt_agent.c
 *
 * Author: Jacques-Charles Lafoucriere <jacques-charles.lafoucriere@cea.fr>
 * Author: Aurelien Degremont <aurelien.degremont@cea.fr>
 */

#define DEBUG_SUBSYSTEM S_MDS

#include <obd.h>
#include <obd_support.h>
#include <lustre_export.h>
#include <lprocfs_status.h>
#include <lustre_kernelcomm.h>
#include <lustre_log.h>
#include "mdt_internal.h"

/*
 * Agent external API
 */

/*
 * find a hsm_agent by uuid
 * lock cdt_agent_lock needs to be held by caller
 * \param cdt [IN] coordinator
 * \param uuid [IN] agent UUID
 * \retval hsm_agent pointer or NULL if not found
 */
static struct hsm_agent *mdt_hsm_agent_lookup(struct coordinator *cdt,
					      const struct obd_uuid *uuid)
{
	struct hsm_agent	*ha;

	list_for_each_entry(ha, &cdt->cdt_agents, ha_list) {
		if (obd_uuid_equals(&ha->ha_uuid, uuid))
			return ha;
	}
	return NULL;
}

/**
 * register a copy tool
 * \param mti [IN] MDT context
 * \param uuid [IN] client UUID to be registered
 * \param count [IN] number of archives agent serves
 * \param archive_id [IN] vector of archive number served by the copytool
 * \retval 0 success
 * \retval -ve failure
 */
int mdt_hsm_agent_register(struct mdt_thread_info *mti,
			   const struct obd_uuid *uuid,
			   int nr_archives, __u32 *archive_id)
{
	struct coordinator	*cdt = &mti->mti_mdt->mdt_coordinator;
	struct hsm_agent	*ha, *tmp;
	int			 rc;
	ENTRY;

	/* no coordinator started, so we cannot serve requests */
	if (!cdt_getref_try(cdt)) {
		LCONSOLE_WARN("HSM coordinator thread is not running - "
			      "denying agent registration.\n");
		/* The client will resend the request if starting */
		RETURN(cdt->cdt_state == CDT_RUNNING ? -EINPROGRESS : -ENXIO);
	}

	OBD_ALLOC_PTR(ha);
	if (ha == NULL)
		GOTO(out, rc = -ENOMEM);

	ha->ha_uuid = *uuid;
	ha->ha_archive_cnt = nr_archives;
	if (ha->ha_archive_cnt != 0) {
		int sz;

		sz = ha->ha_archive_cnt * sizeof(*ha->ha_archive_id);
		OBD_ALLOC(ha->ha_archive_id, sz);
		if (ha->ha_archive_id == NULL)
			GOTO(out_free, rc = -ENOMEM);
		memcpy(ha->ha_archive_id, archive_id, sz);
	}
	atomic_set(&ha->ha_requests, 0);
	atomic_set(&ha->ha_success, 0);
	atomic_set(&ha->ha_failure, 0);

	down_write(&cdt->cdt_agent_lock);
	tmp = mdt_hsm_agent_lookup(cdt, uuid);
	if (tmp != NULL) {
		LCONSOLE_WARN("HSM agent %s already registered\n",
			      obd_uuid2str(uuid));
		up_write(&cdt->cdt_agent_lock);
		GOTO(out_free, rc = -EEXIST);
	}

	list_add_tail(&ha->ha_list, &cdt->cdt_agents);

	if (ha->ha_archive_cnt == 0)
		CDEBUG(D_HSM, "agent %s registered for all archives\n",
		       obd_uuid2str(&ha->ha_uuid));
	else
		CDEBUG(D_HSM, "agent %s registered for %d archives\n",
		       obd_uuid2str(&ha->ha_uuid), ha->ha_archive_cnt);

	up_write(&cdt->cdt_agent_lock);
	GOTO(out, rc = 0);

out_free:

	if (ha != NULL && ha->ha_archive_id != NULL)
		OBD_FREE_PTR_ARRAY(ha->ha_archive_id, ha->ha_archive_cnt);
	OBD_FREE_PTR(ha);
out:
	/* wake the coordinator to potentially schedule requests */
	if (rc == -EEXIST || rc == 0)
		mdt_hsm_cdt_event(cdt);

	cdt_putref(cdt);
	return rc;
}

/**
 * register a copy tool
 * \param mti [IN] MDT context
 * \param uuid [IN] uuid to be registered
 * \param archive_mask [IN] bitmask of archive number served by the copytool
 * \retval 0 success
 * \retval -ve failure
 */
int mdt_hsm_agent_register_mask(struct mdt_thread_info *mti,
				const struct obd_uuid *uuid, __u32 archive_mask)
{
	int		 rc, i, nr_archives = 0;
	__u32		*archive_id = NULL;
	ENTRY;

	nr_archives = hweight32(archive_mask);

	if (nr_archives != 0) {
		OBD_ALLOC_PTR_ARRAY(archive_id, nr_archives);
		if (!archive_id)
			RETURN(-ENOMEM);

		nr_archives = 0;
		for (i = 0; i < sizeof(archive_mask) * 8; i++) {
			if (BIT(i) & archive_mask) {
				archive_id[nr_archives] = i + 1;
				nr_archives++;
			}
		}
	}

	rc = mdt_hsm_agent_register(mti, uuid, nr_archives, archive_id);

	if (archive_id != NULL)
		OBD_FREE_PTR_ARRAY(archive_id, nr_archives);

	RETURN(rc);
}

/**
 * unregister a copy tool
 * \param mti [IN] MDT context
 * \param uuid [IN] uuid to be unregistered
 * \retval 0 success
 * \retval -ve failure
 */
int mdt_hsm_agent_unregister(struct mdt_thread_info *mti,
			     const struct obd_uuid *uuid)
{
	struct coordinator	*cdt = &mti->mti_mdt->mdt_coordinator;
	struct hsm_agent	*ha;
	int			 rc;
	ENTRY;

	/* no coordinator started, so we cannot serve requests */
	if (!cdt_getref_try(cdt))
		RETURN(-ENXIO);

	down_write(&cdt->cdt_agent_lock);

	ha = mdt_hsm_agent_lookup(cdt, uuid);
	if (ha != NULL)
		list_del_init(&ha->ha_list);

	up_write(&cdt->cdt_agent_lock);

	if (ha == NULL)
		GOTO(out, rc = -ENOENT);

	if (ha->ha_archive_cnt != 0)
		OBD_FREE_PTR_ARRAY(ha->ha_archive_id, ha->ha_archive_cnt);
	OBD_FREE_PTR(ha);

	GOTO(out, rc = 0);
out:
	CDEBUG(D_HSM, "agent %s unregistration: %d\n", obd_uuid2str(uuid), rc);

	cdt_putref(cdt);
	return rc;
}

/**
 * update agent statistics
 * \param mdt [IN] MDT device
 * \param succ_rq [IN] number of success
 * \param fail_rq [IN] number of failure
 * \param new_rq [IN] number of new requests
 * \param uuid [IN] agent uuid
 * if all counters == 0, clear counters
 * \retval 0 success
 * \retval -ve failure
 */
int mdt_hsm_agent_update_statistics(struct coordinator *cdt,
				    int succ_rq, int fail_rq, int new_rq,
				    const struct obd_uuid *uuid)
{
	struct hsm_agent	*ha;
	int			 rc;
	ENTRY;

	down_read(&cdt->cdt_agent_lock);
	list_for_each_entry(ha, &cdt->cdt_agents, ha_list) {
		if (obd_uuid_equals(&ha->ha_uuid, uuid)) {
			if (succ_rq == 0 && fail_rq == 0 && new_rq == 0) {
				atomic_set(&ha->ha_success, 0);
				atomic_set(&ha->ha_failure, 0);
				atomic_set(&ha->ha_requests, 0);
			} else {
				atomic_add(succ_rq, &ha->ha_success);
				atomic_add(fail_rq, &ha->ha_failure);
				atomic_add(new_rq, &ha->ha_requests);
				atomic_sub(succ_rq, &ha->ha_requests);
				atomic_sub(fail_rq, &ha->ha_requests);
			}
			GOTO(out, rc = 0);
		}

	}
	rc = -ENOENT;
out:
	up_read(&cdt->cdt_agent_lock);
	RETURN(rc);
}

/**
 * find the best agent
 * \param cdt [IN] coordinator
 * \param archive [IN] archive number
 * \param uuid [OUT] agent who can serve archive
 * \retval 0 success
 * \retval -ve failure
 */
int mdt_hsm_find_best_agent(struct coordinator *cdt, __u32 archive,
			    struct obd_uuid *uuid)
{
	int			 rc = -EAGAIN, i, load = -1;
	struct hsm_agent	*ha;
	ENTRY;

	/* Choose an export to send a copytool req to */
	down_read(&cdt->cdt_agent_lock);
	list_for_each_entry(ha, &cdt->cdt_agents, ha_list) {
		for (i = 0; (i < ha->ha_archive_cnt) &&
			      (ha->ha_archive_id[i] != archive); i++) {
			/* nothing to do, just skip unmatching records */
		}

		/* archive count == 0 means copy tool serves any backend */
		if (ha->ha_archive_cnt != 0 && i == ha->ha_archive_cnt)
			continue;

		if (load == -1 || load > atomic_read(&ha->ha_requests)) {
			load = atomic_read(&ha->ha_requests);
			*uuid = ha->ha_uuid;
			rc = 0;
		}
		if (atomic_read(&ha->ha_requests) == 0)
			break;
	}
	up_read(&cdt->cdt_agent_lock);

	RETURN(rc);
}

static int mdt_hsm_send_action_to_each_archive(struct mdt_thread_info *mti,
					       struct hsm_action_item *hai)
{
	struct hsm_agent *ha;
	__u32 archive_mask = 0;
	struct coordinator *cdt = &mti->mti_mdt->mdt_coordinator;
	int i;
	/* return error by default in case all archive_ids have unregistered */
	int rc = -EAGAIN;
	ENTRY;

	/* send action to all registered archive_ids */
	down_read(&cdt->cdt_agent_lock);
	list_for_each_entry(ha, &cdt->cdt_agents, ha_list) {
		for (i = 0; (i < ha->ha_archive_cnt); i++) {
			/* only send once for each archive_id */
			if (BIT(ha->ha_archive_id[i]) & archive_mask)
				continue;
			archive_mask |= BIT(ha->ha_archive_id[i]);

			/* XXX: it could make sense to gather all
			 * actions for the same archive_id like in
			 * mdt_hsm_add_actions() ?? */
			rc = mdt_agent_record_add(mti->mti_env, mti->mti_mdt,
						  ha->ha_archive_id[i], 0,
						  hai);
			if (rc) {
				CERROR("%s: unable to add HSM remove request "
				       "for "DFID": rc=%d\n",
				       mdt_obd_name(mti->mti_mdt),
				       PFID(&hai->hai_fid), rc);
				break;
			} else {
				CDEBUG(D_HSM, "%s: added HSM remove request "
				       "for "DFID", archive_id=%d\n",
				       mdt_obd_name(mti->mti_mdt),
				       PFID(&hai->hai_fid),
				       ha->ha_archive_id[i]);
			}
		}
		/* early exit from loop due to error? */
		if (i != ha->ha_archive_cnt)
			break;
	}
	up_read(&cdt->cdt_agent_lock);

	RETURN(rc);
}

int mdt_hsm_agent_modify_record(const struct lu_env *env,
				struct mdt_device *mdt,
				struct hsm_mem_req_rec *hmm)
{
	struct obd_device *obd = mdt2obd_dev(mdt);
	struct llog_ctxt *lctxt;
	struct llog_cookie cookie;
	int rc;

	lctxt = llog_get_context(obd, LLOG_AGENT_ORIG_CTXT);
	if (lctxt == NULL || lctxt->loc_handle == NULL)
		RETURN(-ENOENT);

	cookie.lgc_offset = hmm->mr_offset;
	cookie.lgc_index = hmm->mr_rec.arr_hdr.lrh_index;
	hmm->mr_rec.arr_req_change = ktime_get_real_seconds();
	rc = llog_cat_modify_rec(env, lctxt->loc_handle, &hmm->mr_lid,
				 (struct llog_rec_hdr *)&hmm->mr_rec,
				 &cookie);

	llog_ctxt_put(lctxt);
	return rc;
}

static size_t hsr_hal_size(struct hsm_scan_request *rq)
{
	struct cdt_agent_req *car;
	struct hsm_action_item *hai;
	size_t sz;

	sz = sizeof(struct hsm_action_list) +
	     __ALIGN_KERNEL(strlen(rq->hsr_fsname) + 1, 8);
	list_for_each_entry(car, &rq->hsr_cars, car_scan_list) {
		hai = &car->car_hai;
		sz += __ALIGN_KERNEL(hai->hai_len, 8);
	}
	return sz;
}

static int hsr_hal_copy(struct hsm_scan_request *rq, void *buf, size_t buf_size)
{
	struct hsm_action_list *hal = buf;
	struct cdt_agent_req *car;
	struct hsm_action_item *hai;
	struct hsm_action_item *shai;

	hal->hal_version = rq->hsr_version;
	strscpy(hal->hal_fsname, rq->hsr_fsname, MTI_NAME_MAXLEN + 1);
	hal->hal_archive_id = hsr_get_archive_id(rq);
	hal->hal_count = 0;

	hai = hai_first(hal);
	/* Copy only valid hai base on a record status */
	list_for_each_entry(car, &rq->hsr_cars, car_scan_list) {
		shai = &car->car_hai;
		hal->hal_flags = car->car_flags;
		if (car->car_hmm->mr_rec.arr_status == ARS_FAILED)
			continue;
		if ((buf_size - ((char *)hai - (char *)buf)) < shai->hai_len) {
			CDEBUG(D_HA, "buffer overflow for hsm_action_item\n");
			return -EOVERFLOW;
		}
		memcpy(hai, shai, shai->hai_len);
		hal->hal_count++;
		hai = hai_next(hai);
	}

	return 0;
}

/**
 * Checks agent records, creates a hal and sends it to the agent. Updates llog
 * records at the end.
 * \param mti [IN] context
 * \param rq [IN] request
 * \param purge [IN] purge mode (not register a record)
 * \retval 0 success
 * \retval -ve failure
 * This function supposes:
 *  - all actions are for the same archive number
 *  - in case of cancel, all cancel are for the same agent
 * This implies that request split has to be done
 *  before when building the rq
 */
int mdt_hsm_agent_send(struct mdt_thread_info *mti, struct hsm_scan_request *rq,
		       bool purge)
{
	struct obd_export *exp;
	struct mdt_device *mdt = mti->mti_mdt;
	struct coordinator *cdt = &mti->mti_mdt->mdt_coordinator;
	struct hsm_action_list *buf = NULL;
	struct hsm_action_item *hai;
	struct cdt_agent_req *car;
	struct obd_uuid uuid;
	int len, rc = 0;
	int fail_request = 0;
	u32 archive_id = hsr_get_archive_id(rq);

	ENTRY;

	rc = mdt_hsm_find_best_agent(cdt, archive_id, &uuid);
	if (rc && archive_id == 0) {
		uint notrmcount = 0;
		int rc2 = 0;

		/* special case of remove requests with no archive_id specified,
		 * and no agent registered to serve all archives, then create a
		 * set of new requests, each to be sent to each registered
		 * archives.
		 * Todo so, find all HSMA_REMOVE entries, and then :
		 *     _ set completed status as SUCCESS (or FAIL?)
		 *     _ create a new LLOG record for each archive_id
		 *       presently being served by any CT
		 */
		list_for_each_entry(car, &rq->hsr_cars, car_scan_list) {
			hai = &car->car_hai;
			/* only removes are concerned */
			if (hai->hai_action != HSMA_REMOVE) {
				/* count if other actions than HSMA_REMOVE,
				 * to return original error/rc */
				notrmcount++;
				continue;
			}

			/* send remove request to all registered archive_ids */
			rc2 = mdt_hsm_send_action_to_each_archive(mti, hai);
			if (rc2)
				break;

			/* only update original request as SUCCEED if it has
			 * been successfully broadcasted to all available
			 * archive_ids
			 * XXX: this should only cause duplicates to be sent,
			 * unless a method to record already successfully
			 * reached archive_ids is implemented */

			car->car_hmm->mr_rec.arr_status = ARS_SUCCEED;
		}
		/* only remove requests with archive_id=0 */
		if (notrmcount == 0)
			GOTO(update_records, rc = rc2);

	}

	if (rc) {
		CERROR("%s: Cannot find agent for archive %d: rc = %d\n",
		       mdt_obd_name(mdt), archive_id, rc);
		GOTO(update_records, rc);
	}

	CDEBUG(D_HSM, "Agent %s selected for archive %d request %px items %d\n",
	       obd_uuid2str(&uuid), archive_id, rq, rq->hsr_count);

	/* Check if request is still valid (cf file hsm flags) */
	list_for_each_entry(car, &rq->hsr_cars, car_scan_list) {
		struct mdt_object *obj;
		struct md_hsm hsm;

		hai = &car->car_hai;
		if (hai->hai_action == HSMA_CANCEL)
			continue;

		obj = mdt_hsm_get_md_hsm(mti, &hai->hai_fid, &hsm);
		if (!IS_ERR(obj)) {
			mdt_object_put(mti->mti_env, obj);
		} else {
			if (PTR_ERR(obj) == -ENOENT &&
			    hai->hai_action == HSMA_REMOVE)
				continue;

			fail_request++;
			car->car_hmm->mr_rec.arr_status = ARS_FAILED;
			continue;
		}

		if (!mdt_hsm_is_action_compat(hai, archive_id, car->car_flags,
					      &hsm)) {

			/* incompatible request, we abort the request */
			fail_request++;
			car->car_hmm->mr_rec.arr_status = ARS_FAILED;

			/* if restore and record status updated, give
			 * back granted layout lock */
			if (hai->hai_action == HSMA_RESTORE)
				cdt_restore_handle_del(mti, cdt, &hai->hai_fid);
		}
	}

	/* we found incompatible requests, so the HAL will be built only
	 * with a vaild one. Bad records have been invalidated in llog.
	 */
	if (fail_request)
		CDEBUG(D_HSM, "Some HSM actions are invalid, skipping it\n");

	/* nothing to send to agent */
	if (fail_request == rq->hsr_count)
		GOTO(update_records, rc = 0);

	len = hsr_hal_size(rq);
	buf = kuc_alloc(len, KUC_TRANSPORT_HSM, HMT_ACTION_LIST);
	if (IS_ERR(buf))
		GOTO(update_records, rc = PTR_ERR(buf));

	rc = hsr_hal_copy(rq, buf, len);
	if (rc)
		GOTO(update_records, rc);

	/* Cancel memory registration is useless for purge
	 * non registration avoid a deadlock :
	 * in case of failure we have to take the write lock
	 * to remove entry which conflict with the read loack needed
	 * by purge
	 */
	if (!purge) {
		rc = mdt_hsm_add_hsr(mti, rq, &uuid);
		if (rc)
			GOTO(update_records, rc);
	}

	/* Uses the ldlm reverse import; this rpc will be seen by
	 *  the ldlm_callback_handler. Note this sends a request RPC
	 * from a server (MDT) to a client (MDC), backwards of normal comms.
	 */
	exp = obd_uuid_lookup(mdt2obd_dev(mdt), &uuid);
	if (exp == NULL || exp->exp_disconnected) {
		if (exp != NULL)
			class_export_put(exp);
		/* This should clean up agents on evicted exports */
		rc = -ENOENT;
		CERROR("%s: agent uuid (%s) not found, unregistering:"
		       " rc = %d\n",
		       mdt_obd_name(mdt), obd_uuid2str(&uuid), rc);
		mdt_hsm_agent_unregister(mti, &uuid);
		GOTO(update_records, rc);
	}

	/* send request to agent */
	rc = do_set_info_async(exp->exp_imp_reverse, LDLM_SET_INFO,
			       LUSTRE_OBD_VERSION,
			       sizeof(KEY_HSM_COPYTOOL_SEND),
			       KEY_HSM_COPYTOOL_SEND,
			       kuc_len(len), kuc_ptr(buf), NULL);

	if (rc)
		CERROR("%s: cannot send request to agent '%s': rc = %d\n",
		       mdt_obd_name(mdt), obd_uuid2str(&uuid), rc);

	class_export_put(exp);

	if (rc == -EPIPE) {
		CDEBUG(D_HSM, "Lost connection to agent '%s', unregistering\n",
		       obd_uuid2str(&uuid));
		mdt_hsm_agent_unregister(mti, &uuid);
	}

update_records:
	/* for purge record updates do hsm_cancel_all_actions() */
	if (purge)
		GOTO(out_free, rc);

	/* in case of error, we have to unregister requests
	 * also update request status here
	 */
	list_for_each_entry(car, &rq->hsr_cars, car_scan_list) {
		int rc2;

		hai = &car->car_hai;
		if (rc != 0 && hai->hai_action != HSMA_CANCEL)
			mdt_cdt_remove_request(cdt, hai->hai_cookie);

		if (car->car_hmm->mr_rec.arr_status == ARS_WAITING && !rc)
			car->car_hmm->mr_rec.arr_status = ARS_STARTED;

		/* update llog record with ARS_ status */
		rc2 = mdt_hsm_agent_modify_record(mti->mti_env, mdt,
						  car->car_hmm);
		if (!rc2)
			continue;

		CERROR("%s: modify record failed, cannot update status to %s for cookie %#llx: rc = %d\n",
		       mdt_obd_name(mdt),
		       agent_req_status2name(car->car_hmm->mr_rec.arr_status),
		       hai->hai_cookie, rc2);
	}
out_free:
	if (!IS_ERR_OR_NULL(buf))
		kuc_free(buf, len);

	RETURN(rc);
}

/**
 * seq_file method called to start access to debugfs file
 */
static void *mdt_hsm_agent_debugfs_start(struct seq_file *s, loff_t *off)
{
	struct mdt_device	*mdt = s->private;
	struct coordinator	*cdt = &mdt->mdt_coordinator;
	struct list_head	*pos;
	loff_t			 i;
	ENTRY;

	down_read(&cdt->cdt_agent_lock);

	if (list_empty(&cdt->cdt_agents))
		RETURN(NULL);

	if (*off == 0)
		RETURN(SEQ_START_TOKEN);

	i = 0;
	list_for_each(pos, &cdt->cdt_agents) {
		i++;
		if (i >= *off)
			RETURN(pos);
	}

	RETURN(NULL);
}

/**
 * seq_file method called to get next item
 * just returns NULL at eof
 */
static void *mdt_hsm_agent_debugfs_next(struct seq_file *s, void *v, loff_t *p)
{
	struct mdt_device	*mdt = s->private;
	struct coordinator	*cdt = &mdt->mdt_coordinator;
	struct list_head	*pos = v;
	ENTRY;

	if (pos == SEQ_START_TOKEN)
		pos = cdt->cdt_agents.next;
	else
		pos = pos->next;

	(*p)++;
	if (pos != &cdt->cdt_agents)
		RETURN(pos);

	RETURN(NULL);
}

/**
 */
static int mdt_hsm_agent_debugfs_show(struct seq_file *s, void *v)
{
	struct list_head	*pos = v;
	struct hsm_agent	*ha;
	int			 i;
	ENTRY;

	if (pos == SEQ_START_TOKEN)
		RETURN(0);

	ha = list_entry(pos, struct hsm_agent, ha_list);
	seq_printf(s, "uuid=%s archive_id=", ha->ha_uuid.uuid);
	if (ha->ha_archive_cnt == 0) {
		seq_printf(s, "ANY");
	} else {
		seq_printf(s, "%d", ha->ha_archive_id[0]);
		for (i = 1; i < ha->ha_archive_cnt; i++)
			seq_printf(s, ",%d", ha->ha_archive_id[i]);
	}

	seq_printf(s, " requests=[current:%d ok:%d errors:%d]\n",
		   atomic_read(&ha->ha_requests),
		   atomic_read(&ha->ha_success),
		   atomic_read(&ha->ha_failure));
	RETURN(0);
}

/**
 * seq_file method called to stop access to debugfs file
 */
static void mdt_hsm_agent_debugfs_stop(struct seq_file *s, void *v)
{
	struct mdt_device	*mdt = s->private;
	struct coordinator	*cdt = &mdt->mdt_coordinator;

	up_read(&cdt->cdt_agent_lock);
}

/* hsm agent list debugfs functions */
static const struct seq_operations mdt_hsm_agent_debugfs_ops = {
	.start	= mdt_hsm_agent_debugfs_start,
	.next	= mdt_hsm_agent_debugfs_next,
	.show	= mdt_hsm_agent_debugfs_show,
	.stop	= mdt_hsm_agent_debugfs_stop,
};

/**
 * public function called at open of debugfs file to get
 * list of agents
 */
static int ldebugfs_open_hsm_agent(struct inode *inode, struct file *file)
{
	struct seq_file	*s;
	int		 rc;
	ENTRY;

	rc = seq_open(file, &mdt_hsm_agent_debugfs_ops);
	if (rc)
		RETURN(rc);

	s = file->private_data;
	s->private = inode->i_private;

	RETURN(rc);
}

/* methods to access hsm agent list */
const struct file_operations mdt_hsm_agent_fops = {
	.owner		= THIS_MODULE,
	.open		= ldebugfs_open_hsm_agent,
	.read		= seq_read,
	.llseek		= seq_lseek,
	.release	= seq_release,
};