Viewing: nrs_tbf.c

// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (C) 2013 DataDirect Networks, Inc.
 *
 * Copyright (c) 2014, 2016, Intel Corporation.
 */

/*
 * Network Request Scheduler (NRS) Token Bucket Filter(TBF) policy
 */

#define DEBUG_SUBSYSTEM S_RPC
#include <linux/delay.h>

#include <obd_support.h>
#include <obd_class.h>
#include <lustre_req_layout.h>
#include <lustre_nodemap.h>
#include "ptlrpc_internal.h"

/*
 * name tbf
 *
 * Token Bucket Filter over client NIDs
 */

#define NRS_POL_NAME_TBF	"tbf"

static int tbf_jobid_cache_size = 8192;
module_param(tbf_jobid_cache_size, int, 0644);
MODULE_PARM_DESC(tbf_jobid_cache_size, "The size of jobid cache");

static int tbf_rate = 10000;
module_param(tbf_rate, int, 0644);
MODULE_PARM_DESC(tbf_rate, "Default rate limit in RPCs/s");

static int tbf_depth = 3;
module_param(tbf_depth, int, 0644);
MODULE_PARM_DESC(tbf_depth, "How many tokens that a client can save up");

static int nrs_tbf_jobid_str(const struct nrs_tbf_key *key, char *str, int len)
{
	return snprintf(str, len, "%.*s", LUSTRE_JOBID_SIZE - 1, key->tk_jobid);
}

static int nrs_tbf_nid_str(const struct nrs_tbf_key *key, char *str, int len)
{
	char nidstr[LNET_NIDSTR_SIZE];

	libcfs_nidstr_r(&key->tk_nid, nidstr, sizeof(nidstr));
	return snprintf(str, len, "%s", nidstr);
}

static int nrs_tbf_opc_str(const struct nrs_tbf_key *key, char *str, int len)
{
	return snprintf(str, len, "%s", ll_opcode2str(key->tk_opcode));
}

static int nrs_tbf_id_str(u32 id, char *str, int len)
{
	if (id == U32_MAX)
		return snprintf(str, len, "-1");

	return snprintf(str, len, "%u", id);
}

static int nrs_tbf_uid_str(const struct nrs_tbf_key *key, char *str, int len)
{
	return nrs_tbf_id_str(key->tk_id.ti_uid, str, len);
}

static int nrs_tbf_gid_str(const struct nrs_tbf_key *key, char *str, int len)
{
	return nrs_tbf_id_str(key->tk_id.ti_gid, str, len);
}

static int nrs_tbf_projid_str(const struct nrs_tbf_key *key, char *str, int len)
{
	return nrs_tbf_id_str(key->tk_id.ti_projid, str, len);
}

static int nrs_tbf_nodemap_str(const struct nrs_tbf_key *key, char *str,
			       int len)
{
	return nrs_tbf_id_str(key->tk_nmid, str, len);
}

static const struct nrs_tbf_type nrs_tbf_types[] = {
	[NRS_TBF_FIELD_JOBID] = {
		.ntt_name = NRS_TBF_TYPE_JOBID,
		.ntt_flag = NRS_TBF_FLAG_JOBID,
		.ntt_str = nrs_tbf_jobid_str,
	},
	[NRS_TBF_FIELD_NID] = {
		.ntt_name = NRS_TBF_TYPE_NID,
		.ntt_flag = NRS_TBF_FLAG_NID,
		.ntt_str = nrs_tbf_nid_str,
		},
	[NRS_TBF_FIELD_OPCODE] = {
		.ntt_name = NRS_TBF_TYPE_OPCODE,
		.ntt_flag = NRS_TBF_FLAG_OPCODE,
		.ntt_str = nrs_tbf_opc_str,
	},
	[NRS_TBF_FIELD_UID] = {
		.ntt_name = NRS_TBF_TYPE_UID,
		.ntt_flag = NRS_TBF_FLAG_UID,
		.ntt_str = nrs_tbf_uid_str,
	},
	[NRS_TBF_FIELD_GID] = {
		.ntt_name = NRS_TBF_TYPE_GID,
		.ntt_flag = NRS_TBF_FLAG_GID,
		.ntt_str = nrs_tbf_gid_str,
	},
	[NRS_TBF_FIELD_PROJID] = {
		.ntt_name = NRS_TBF_TYPE_PROJID,
		.ntt_flag = NRS_TBF_FLAG_PROJID,
		.ntt_str = nrs_tbf_projid_str,
	},
	[NRS_TBF_FIELD_NODEMAP] = {
		.ntt_name = NRS_TBF_TYPE_NODEMAP,
		.ntt_flag = NRS_TBF_FLAG_NODEMAP,
		.ntt_str = nrs_tbf_nodemap_str,
	},
};

/* This converts the class key to a string using the TBF rule format.
 * e.g: "jobid={kworker.0}&uid={0}&gid={0}"
 */
static int nrs_tbf_cli2str_r(struct nrs_tbf_client *cli, char *str, int len)
{
	const struct nrs_tbf_type *type = nrs_tbf_types;
	struct nrs_tbf_key *key = &cli->tc_key;
	int outl = 0;
	int i;

	for (i = 0; i < ARRAY_SIZE(nrs_tbf_types); i++, type++) {
		if (!(key->tk_flags & type->ntt_flag))
			continue;

		if (!type->ntt_str)
			continue;

		outl += snprintf(str + outl, len - outl,
				 "%s%s={", outl ? "&" : "", type->ntt_name);
		if (outl >= len - 1)
			break;

		outl += type->ntt_str(key, str + outl, len - outl);
		if (outl >= len - 2)
			break;

		str[outl++] = '}';
		str[outl] = '\0';
	}

	outl = min(outl, len - 1);
	str[outl] = '\0';

	return outl;
}

/* racy print (used for debug logs) */
#define TBF_PRINT_KEY_BUF_SIZE 256
static DEFINE_PER_CPU(char [TBF_PRINT_KEY_BUF_SIZE], nrs_tbf_print_bufs);
static char *nrs_tbf_cli2str(struct nrs_tbf_client *cli)
{
	char *buf = raw_cpu_ptr(nrs_tbf_print_bufs);

	nrs_tbf_cli2str_r(cli, buf, TBF_PRINT_KEY_BUF_SIZE);

	return buf;
}

__printf(3, 4) /* function attribute */
static void __nrs_tbf_cli_debug(struct nrs_tbf_client *cli,
				struct libcfs_debug_msg_data *data,
				const char *fmt, ...)
{
	struct ptlrpc_nrs_policy *pol;
	struct va_format vaf;
	char *srv_name;
	va_list args;
	int cpt;

	va_start(args, fmt);
	vaf.fmt = fmt;
	vaf.va = &args;
	if (unlikely(!cli)) {
		libcfs_debug_msg(data, "%pV: class@%p\n", &vaf, cli);
		goto out;
	}

	pol = cli->tc_res.res_policy;
	cpt = pol ? nrs_pol2cptid(pol) : 0;
	srv_name = pol ? nrs_pol2svc(pol)->srv_name : "unknown";
	libcfs_debug_msg(data, "%s.%d NRS: %pV: class@%p key %s rule@0x%p gen %llu rate %llu depth %llu token %llu deadline %llu time %llu state 0x%lx ref %u\n",
			 srv_name, cpt, &vaf, cli, nrs_tbf_cli2str(cli),
			 cli->tc_rule, cli->tc_rule_generation,
			 cli->tc_rpc_rate, cli->tc_depth, cli->tc_ntoken,
			 cli->tc_deadline, cli->tc_check_time,
			 cli->tc_state, refcount_read(&cli->tc_ref));

out:
	va_end(args);
}

#define TBF_CLI_DEBUG(cli, fmt, a...)	\
do {									\
	if (cfs_cdebug_show(D_RPCTRACE, DEBUG_SUBSYSTEM)) {		\
		LIBCFS_DEBUG_MSG_DATA_DECL(msgdata, D_RPCTRACE, NULL);	\
		__nrs_tbf_cli_debug(cli, &msgdata, fmt, ##a);		\
	}								\
} while (0)

static int nrs_tbf_id_cli_set(struct ptlrpc_request *req, struct tbf_id *id,
			      enum nrs_tbf_flag need);

#define NRS_TBF_JOBID_NULL ""

static void nrs_tbf_cli_gen_key(struct ptlrpc_request *req,
				struct nrs_tbf_key *key,
				enum nrs_tbf_flag valid)
{
	if (valid & NRS_TBF_FLAG_IDS)
		nrs_tbf_id_cli_set(req, &key->tk_id, valid & NRS_TBF_FLAG_IDS);

	if (valid & NRS_TBF_FLAG_NID)
		key->tk_nid = req->rq_peer.nid;
	if (valid & NRS_TBF_FLAG_OPCODE)
		key->tk_opcode = lustre_msg_get_opc(req->rq_reqmsg);
	if (valid & NRS_TBF_FLAG_JOBID) {
		const char *jobid;

		jobid = lustre_msg_get_jobid(req->rq_reqmsg);
		if (jobid == NULL)
			jobid = NRS_TBF_JOBID_NULL;
		strscpy(key->tk_jobid, jobid, sizeof(key->tk_jobid));
	}

	if (valid & NRS_TBF_FLAG_NODEMAP) {
		struct lu_nodemap *lnm;

		lnm = nodemap_get_from_exp(req->rq_export);
		if (IS_ERR_OR_NULL(lnm)) {
			key->tk_nmid = LUSTRE_NODEMAP_MAX_ID;
		} else {
			key->tk_nmid = lnm->nm_id;
			nodemap_putref(lnm);
		}
	}

	key->tk_flags = valid;
}

static enum hrtimer_restart nrs_tbf_timer_cb(struct hrtimer *timer)
{
	struct nrs_tbf_head *head = container_of(timer, struct nrs_tbf_head,
						 th_timer);
	struct ptlrpc_nrs   *nrs = head->th_res.res_policy->pol_nrs;
	struct ptlrpc_service_part *svcpt = nrs->nrs_svcpt;

	nrs->nrs_throttling = 0;
	wake_up(&svcpt->scp_waitq);

	return HRTIMER_NORESTART;
}

static void nrs_tbf_conds_free(struct list_head *cond_list);

#define NRS_TBF_DEFAULT_RULE "default"

/* rule's usage reference count is now dropped below one. There is no more
 * outstanding usage references left. Stops the rule in case it was already
 * stopping.
 */
static void nrs_tbf_rule_fini(struct kref *kref)
{
	struct nrs_tbf_rule *rule = container_of(kref, struct nrs_tbf_rule,
						 tr_ref);

	LASSERT(list_empty(&rule->tr_cli_list));
	LASSERT(list_empty(&rule->tr_linkage));

	if (!list_empty(&rule->tr_conds))
		nrs_tbf_conds_free(&rule->tr_conds);
	LASSERT(rule->tr_conds_str != NULL);
	OBD_FREE_STR(rule->tr_conds_str);
	OBD_FREE_PTR(rule);
}

static void
nrs_tbf_cli_rule_put(struct nrs_tbf_client *cli)
{
	LASSERT(!list_empty(&cli->tc_linkage));
	LASSERT(cli->tc_rule);
	spin_lock(&cli->tc_rule->tr_rule_lock);
	list_del_init(&cli->tc_linkage);
	spin_unlock(&cli->tc_rule->tr_rule_lock);
	kref_put(&cli->tc_rule->tr_ref, nrs_tbf_rule_fini);
	cli->tc_rule = NULL;
}

static void
nrs_tbf_cli_reset_value(struct nrs_tbf_head *head,
			struct nrs_tbf_client *cli)

{
	struct nrs_tbf_rule *rule = cli->tc_rule;

	cli->tc_rpc_rate = rule->tr_rpc_rate;
	cli->tc_nsecs = rule->tr_nsecs_per_rpc;
	cli->tc_nsecs_resid = 0;
	cli->tc_depth = rule->tr_depth;
	cli->tc_ntoken = rule->tr_depth;
	cli->tc_check_time = ktime_to_ns(ktime_get());
	cli->tc_rule_sequence = atomic_read(&head->th_rule_sequence);
	cli->tc_rule_generation = rule->tr_generation;

	if (test_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state))
		binheap_relocate(head->th_binheap,
				 &cli->tc_node);
}

static void
nrs_tbf_cli_reset(struct nrs_tbf_head *head,
		  struct nrs_tbf_rule *rule,
		  struct nrs_tbf_client *cli)
{
	spin_lock(&cli->tc_rule_lock);
	if (cli->tc_rule != NULL && !list_empty(&cli->tc_linkage)) {
		LASSERT(rule != cli->tc_rule);
		nrs_tbf_cli_rule_put(cli);
	}
	LASSERT(cli->tc_rule == NULL);
	LASSERT(list_empty(&cli->tc_linkage));
	/* Rule's ref is added before called */
	cli->tc_rule = rule;
	spin_lock(&rule->tr_rule_lock);
	list_add_tail(&cli->tc_linkage, &rule->tr_cli_list);
	spin_unlock(&rule->tr_rule_lock);
	spin_unlock(&cli->tc_rule_lock);
	nrs_tbf_cli_reset_value(head, cli);
}

static int
nrs_tbf_rule_dump(struct nrs_tbf_rule *rule, struct seq_file *m)
{
	seq_printf(m, "%s %s %llu, ref %d\n", rule->tr_name,
		   rule->tr_conds_str, rule->tr_rpc_rate,
		   kref_read(&rule->tr_ref) - 1);
	return 0;
}

static int
nrs_tbf_rule_dump_all(struct nrs_tbf_head *head, struct seq_file *m)
{
	struct nrs_tbf_rule *rule;
	int rc = 0;

	LASSERT(head != NULL);
	spin_lock(&head->th_rule_lock);
	/* List the rules from newest to oldest */
	list_for_each_entry(rule, &head->th_list, tr_linkage) {
		LASSERT((rule->tr_flags & NTRS_STOPPING) == 0);
		rc = nrs_tbf_rule_dump(rule, m);
		if (rc) {
			rc = -ENOSPC;
			break;
		}
	}
	spin_unlock(&head->th_rule_lock);

	return rc;
}

static struct nrs_tbf_rule *
nrs_tbf_rule_find_nolock(struct nrs_tbf_head *head,
			 const char *name)
{
	struct nrs_tbf_rule *rule;

	LASSERT(head != NULL);
	list_for_each_entry(rule, &head->th_list, tr_linkage) {
		LASSERT((rule->tr_flags & NTRS_STOPPING) == 0);
		if (strcmp(rule->tr_name, name) == 0) {
			kref_get(&rule->tr_ref);
			return rule;
		}
	}
	return NULL;
}

static struct nrs_tbf_rule *
nrs_tbf_rule_find(struct nrs_tbf_head *head,
		  const char *name)
{
	struct nrs_tbf_rule *rule;

	LASSERT(head != NULL);
	spin_lock(&head->th_rule_lock);
	rule = nrs_tbf_rule_find_nolock(head, name);
	spin_unlock(&head->th_rule_lock);
	return rule;
}

static int nrs_tbf_cond_match(struct nrs_tbf_rule *rule,
			      struct nrs_tbf_client *cli);

static struct nrs_tbf_rule *
nrs_tbf_rule_match(struct nrs_tbf_head *head,
		   struct nrs_tbf_client *cli)
{
	struct nrs_tbf_rule *rule = NULL;
	struct nrs_tbf_rule *tmp_rule;

	spin_lock(&head->th_rule_lock);
	/* Match the newest rule in the list */
	list_for_each_entry(tmp_rule, &head->th_list, tr_linkage) {
		LASSERT((tmp_rule->tr_flags & NTRS_STOPPING) == 0);
		if (nrs_tbf_cond_match(tmp_rule, cli)) {
			rule = tmp_rule;
			break;
		}
	}

	if (rule == NULL)
		rule = head->th_rule;

	kref_get(&rule->tr_ref);
	spin_unlock(&head->th_rule_lock);
	return rule;
}

static
struct nrs_tbf_client *nrs_tbf_cli_alloc(struct nrs_tbf_head *head,
					 struct ptlrpc_request *req,
					 gfp_t gfp)
{
	struct ptlrpc_nrs_policy *pol = head->th_res.res_policy;
	enum nrs_tbf_flag valid = head->th_type_flag;
	struct nrs_tbf_client *cli;
	struct nrs_tbf_rule *rule;

	OBD_CPT_ALLOC_GFP(cli, nrs_pol2cptab(pol), nrs_pol2cptid(pol),
			  sizeof(*cli), gfp);
	if (!cli)
		return NULL;

	nrs_tbf_cli_gen_key(req, &cli->tc_key, valid);

	INIT_LIST_HEAD(&cli->tc_lru);
	INIT_LIST_HEAD(&cli->tc_list);
	INIT_LIST_HEAD(&cli->tc_linkage);
	spin_lock_init(&cli->tc_rule_lock);
	refcount_set(&cli->tc_ref, 1);
	rule = nrs_tbf_rule_match(head, cli);
	nrs_tbf_cli_reset(head, rule, cli);

	return cli;
}

static void nrs_tbf_cli_free(struct rcu_head *head)
{
	struct nrs_tbf_client *cli = container_of(head, struct nrs_tbf_client,
						  tc_rcu_head);

	OBD_FREE_PTR(cli);
}

static void
nrs_tbf_cli_fini(struct nrs_tbf_client *cli)
{
	LASSERT(list_empty(&cli->tc_list));
	LASSERT(!test_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state));
	LASSERT(!test_and_set_bit(NRS_TBF_CLI_DEL_BIT, &cli->tc_state));

	TBF_CLI_DEBUG(cli, "TBF class fini");
	spin_lock(&cli->tc_rule_lock);
	nrs_tbf_cli_rule_put(cli);
	spin_unlock(&cli->tc_rule_lock);

	call_rcu(&cli->tc_rcu_head, nrs_tbf_cli_free);
}

static
struct nrs_tbf_rule *nrs_tbf_rule_alloc(struct ptlrpc_nrs_policy *policy,
					struct nrs_tbf_head *head,
					struct nrs_tbf_cmd *start);

static int
nrs_tbf_rule_start(struct ptlrpc_nrs_policy *policy,
		   struct nrs_tbf_head *head,
		   struct nrs_tbf_cmd *start)
{
	struct nrs_tbf_rule *rule;
	struct nrs_tbf_rule *tmp_rule;
	struct nrs_tbf_rule *next_rule;
	char *next_name = start->u.tc_start.ts_next_name;

	rule = nrs_tbf_rule_find(head, start->tc_name);
	if (rule) {
		kref_put(&rule->tr_ref, nrs_tbf_rule_fini);
		return -EEXIST;
	}

	rule = nrs_tbf_rule_alloc(policy, head, start);
	if (IS_ERR(rule))
		return PTR_ERR(rule);

	/* Add as the newest rule */
	spin_lock(&head->th_rule_lock);
	tmp_rule = nrs_tbf_rule_find_nolock(head, start->tc_name);
	if (tmp_rule) {
		spin_unlock(&head->th_rule_lock);
		kref_put(&tmp_rule->tr_ref, nrs_tbf_rule_fini);
		kref_put(&rule->tr_ref, nrs_tbf_rule_fini);
		return -EEXIST;
	}

	if (next_name) {
		next_rule = nrs_tbf_rule_find_nolock(head, next_name);
		if (!next_rule) {
			spin_unlock(&head->th_rule_lock);
			kref_put(&rule->tr_ref, nrs_tbf_rule_fini);
			return -ENOENT;
		}

		list_add(&rule->tr_linkage, next_rule->tr_linkage.prev);
		kref_put(&next_rule->tr_ref, nrs_tbf_rule_fini);
	} else {
		/* Add on the top of the rule list */
		list_add(&rule->tr_linkage, &head->th_list);
	}
	spin_unlock(&head->th_rule_lock);
	atomic_inc(&head->th_rule_sequence);
	if (start->u.tc_start.ts_rule_flags & NTRS_DEFAULT) {
		rule->tr_flags |= NTRS_DEFAULT;
		LASSERT(head->th_rule == NULL);
		head->th_rule = rule;
	}

	CDEBUG(D_RPCTRACE, "TBF starts rule@%p rate %llu gen %llu\n",
	       rule, rule->tr_rpc_rate, rule->tr_generation);

	return 0;
}

/**
 * nrs_tbf_rule_change_rank() - Change the rank of a rule in the rule list
 * @policy: the policy instance
 * @head: the TBF policy instance
 * @name: the rule name to be moved
 * @next_name: the rule name before which the matched rule will be moved
 *
 * The matched rule will be moved to the position right before another
 * given rule.
 *
 * Return
 * * %0 on success
 * * %negative on error
 */
static int
nrs_tbf_rule_change_rank(struct ptlrpc_nrs_policy *policy,
			 struct nrs_tbf_head *head,
			 char *name,
			 char *next_name)
{
	struct nrs_tbf_rule	*rule = NULL;
	struct nrs_tbf_rule	*next_rule = NULL;
	int			 rc = 0;

	LASSERT(head != NULL);

	spin_lock(&head->th_rule_lock);
	rule = nrs_tbf_rule_find_nolock(head, name);
	if (!rule)
		GOTO(out, rc = -ENOENT);

	if (strcmp(name, next_name) == 0)
		GOTO(out_put, rc);

	next_rule = nrs_tbf_rule_find_nolock(head, next_name);
	if (!next_rule)
		GOTO(out_put, rc = -ENOENT);

	/* rules may be adjacent in same list, so list_move() isn't safe here */
	list_move_tail(&rule->tr_linkage, &next_rule->tr_linkage);
	kref_put(&next_rule->tr_ref, nrs_tbf_rule_fini);
out_put:
	kref_put(&rule->tr_ref, nrs_tbf_rule_fini);
out:
	spin_unlock(&head->th_rule_lock);
	return rc;
}

static int
nrs_tbf_rule_change_rate(struct ptlrpc_nrs_policy *policy,
			 struct nrs_tbf_head *head,
			 char *name,
			 __u64 rate)
{
	struct nrs_tbf_rule *rule;

	assert_spin_locked(&policy->pol_nrs->nrs_lock);

	rule = nrs_tbf_rule_find(head, name);
	if (rule == NULL)
		return -ENOENT;

	rule->tr_rpc_rate = rate;
	rule->tr_nsecs_per_rpc = NSEC_PER_SEC / rule->tr_rpc_rate;
	rule->tr_generation++;
	kref_put(&rule->tr_ref, nrs_tbf_rule_fini);

	return 0;
}

static int
nrs_tbf_rule_change(struct ptlrpc_nrs_policy *policy,
		    struct nrs_tbf_head *head,
		    struct nrs_tbf_cmd *change)
{
	__u64	 rate = change->u.tc_change.tc_rpc_rate;
	char	*next_name = change->u.tc_change.tc_next_name;
	int	 rc;

	if (rate != 0) {
		rc = nrs_tbf_rule_change_rate(policy, head, change->tc_name,
					      rate);
		if (rc)
			return rc;
	}

	if (next_name) {
		rc = nrs_tbf_rule_change_rank(policy, head, change->tc_name,
					      next_name);
		if (rc)
			return rc;
	}

	return 0;
}

static int
nrs_tbf_rule_stop(struct ptlrpc_nrs_policy *policy,
		  struct nrs_tbf_head *head,
		  struct nrs_tbf_cmd *stop)
{
	struct nrs_tbf_rule *rule;

	assert_spin_locked(&policy->pol_nrs->nrs_lock);

	if (strcmp(stop->tc_name, NRS_TBF_DEFAULT_RULE) == 0)
		return -EPERM;

	rule = nrs_tbf_rule_find(head, stop->tc_name);
	if (rule == NULL)
		return -ENOENT;

	list_del_init(&rule->tr_linkage);
	rule->tr_flags |= NTRS_STOPPING;
	kref_put(&rule->tr_ref, nrs_tbf_rule_fini);
	kref_put(&rule->tr_ref, nrs_tbf_rule_fini);

	return 0;
}

static int
nrs_tbf_command(struct ptlrpc_nrs_policy *policy,
		struct nrs_tbf_head *head,
		struct nrs_tbf_cmd *cmd)
{
	int rc;

	assert_spin_locked(&policy->pol_nrs->nrs_lock);

	switch (cmd->tc_cmd) {
	case NRS_CTL_TBF_START_RULE:
		if (cmd->u.tc_start.ts_valid_type != head->th_type_flag)
			return -EINVAL;

		spin_unlock(&policy->pol_nrs->nrs_lock);
		rc = nrs_tbf_rule_start(policy, head, cmd);
		spin_lock(&policy->pol_nrs->nrs_lock);
		return rc;
	case NRS_CTL_TBF_CHANGE_RULE:
		rc = nrs_tbf_rule_change(policy, head, cmd);
		return rc;
	case NRS_CTL_TBF_STOP_RULE:
		rc = nrs_tbf_rule_stop(policy, head, cmd);
		/* Take it as a success, if not exists at all */
		return rc == -ENOENT ? 0 : rc;
	default:
		return -EFAULT;
	}
}

/**
 * tbf_cli_compare() - Binary heap predicate.
 * @e1: the first binheap node to compare
 * @e2: the second binheap node to compare
 *
 * Return
 * * %0 if e1 > e2
 * * %1 if e1 < e2
 */
static int
tbf_cli_compare(struct binheap_node *e1, struct binheap_node *e2)
{
	struct nrs_tbf_client *cli1;
	struct nrs_tbf_client *cli2;

	cli1 = container_of(e1, struct nrs_tbf_client, tc_node);
	cli2 = container_of(e2, struct nrs_tbf_client, tc_node);

	if (cli1->tc_deadline < cli2->tc_deadline)
		return 1;
	else if (cli1->tc_deadline > cli2->tc_deadline)
		return 0;

	if (cli1->tc_check_time < cli2->tc_check_time)
		return 1;
	else if (cli1->tc_check_time > cli2->tc_check_time)
		return 0;

	/* Maybe need more comparasion, e.g. request number in the rules */
	return 1;
}

/*
 * TBF binary heap operations
 */
static struct binheap_ops nrs_tbf_heap_ops = {
	.hop_enter	= NULL,
	.hop_exit	= NULL,
	.hop_compare	= tbf_cli_compare,
};

/*
 * Frees jobid of @jobid_list.
 *
 */
static void
nrs_tbf_jobid_list_free(struct list_head *jobid_list)
{
	struct nrs_tbf_jobid *jobid, *n;

	list_for_each_entry_safe(jobid, n, jobid_list, tj_linkage) {
		OBD_FREE_STR(jobid->tj_id);
		list_del(&jobid->tj_linkage);
		OBD_FREE_PTR(jobid);
	}
}

static int
nrs_tbf_jobid_list_add(char *id, struct list_head *jobid_list)
{
	struct nrs_tbf_jobid *jobid;
	char *ptr;

	OBD_ALLOC_PTR(jobid);
	if (jobid == NULL)
		return -ENOMEM;

	OBD_STRNDUP(jobid->tj_id, id, strlen(id));
	if (jobid->tj_id == NULL) {
		OBD_FREE_PTR(jobid);
		return -ENOMEM;
	}

	ptr = strchr(id, '*');
	if (ptr == NULL)
		jobid->tj_match_flag = NRS_TBF_MATCH_FULL;
	else
		jobid->tj_match_flag = NRS_TBF_MATCH_WILDCARD;

	list_add_tail(&jobid->tj_linkage, jobid_list);
	return 0;
}

static bool
cfs_match_wildcard(const char *pattern, const char *content)
{
	if (*pattern == '\0' && *content == '\0')
		return true;

	if (*pattern == '*' && *(pattern + 1) != '\0' && *content == '\0')
		return false;

	while (*pattern == *content) {
		pattern++;
		content++;
		if (*pattern == '\0' && *content == '\0')
			return true;

		if (*pattern == '*' && *(pattern + 1) != '\0' &&
		    *content == '\0')
			return false;
	}

	if (*pattern == '*')
		return (cfs_match_wildcard(pattern + 1, content) ||
			cfs_match_wildcard(pattern, content + 1));

	return false;
}

static inline bool
nrs_tbf_jobid_match(const struct nrs_tbf_jobid *jobid, const char *id)
{
	if (jobid->tj_match_flag == NRS_TBF_MATCH_FULL)
		return strcmp(jobid->tj_id, id) == 0;

	if (jobid->tj_match_flag == NRS_TBF_MATCH_WILDCARD)
		return cfs_match_wildcard(jobid->tj_id, id);

	return false;
}

static int
nrs_tbf_jobid_list_match(struct list_head *jobid_list, char *id)
{
	struct nrs_tbf_jobid *jobid;

	list_for_each_entry(jobid, jobid_list, tj_linkage) {
		if (nrs_tbf_jobid_match(jobid, id))
			return 1;
	}
	return 0;
}

static int
nrs_tbf_jobid_list_parse(char *orig, struct list_head *jobid_list)
{
	char *str, *copy;
	int rc = 0;
	ENTRY;

	copy = kstrdup(orig, GFP_KERNEL);
	if (!copy)
		RETURN(-ENOMEM);
	str = copy;
	INIT_LIST_HEAD(jobid_list);
	while (str && rc == 0) {
		char *tok = strsep(&str, " ");

		if (*tok)
			rc = nrs_tbf_jobid_list_add(tok, jobid_list);
	}
	if (list_empty(jobid_list))
		rc = -EINVAL;
	if (rc)
		nrs_tbf_jobid_list_free(jobid_list);
	kfree(copy);
	RETURN(rc);
}

static inline bool nrs_tbf_flags_valid(u32 flags)
{
	return (flags & NRS_TBF_FLAG_ALL) &&
	       (flags & ~NRS_TBF_FLAG_ALL) == 0;
}

static void nrs_tbf_cli_exit(void *vcli, void *data)
{
	struct nrs_tbf_client *cli = vcli;

	if (test_bit(NRS_TBF_CLI_LRU_BIT, &cli->tc_state))
		list_del(&cli->tc_lru);
	else
		TBF_CLI_DEBUG(cli, "Busy TBF class");

	nrs_tbf_cli_fini(cli);
}

static int
nrs_tbf_startup(struct ptlrpc_nrs_policy *policy, struct nrs_tbf_head *head)
{
	struct rhashtable_params *params = &head->th_rhash_params;
	struct nrs_tbf_cmd start;
	int rc;

	memset(params, 0, sizeof(*params));
	params->key_len = sizeof(struct nrs_tbf_key);
	params->key_offset = offsetof(struct nrs_tbf_client, tc_key);
	params->head_offset = offsetof(struct nrs_tbf_client, tc_rhash);
	params->min_size = 256;
	params->automatic_shrinking = true;

	rc = rhashtable_init(&head->th_cli_rhash, params);
	if (rc)
		return rc;

	memset(&start, 0, sizeof(start));
	start.u.tc_start.ts_conds_str = "*";

	start.u.tc_start.ts_rpc_rate = tbf_rate;
	start.u.tc_start.ts_rule_flags = NTRS_DEFAULT;
	start.tc_name = NRS_TBF_DEFAULT_RULE;
	INIT_LIST_HEAD(&start.u.tc_start.ts_conds);
	rc = nrs_tbf_rule_start(policy, head, &start);
	if (rc)
		rhashtable_free_and_destroy(&head->th_cli_rhash,
					    nrs_tbf_cli_exit, NULL);

	return rc;
}

/*
 * ONLY opcode presented in this function will be checked in
 * nrs_tbf_id_cli_set(). That means, we can add or remove an
 * opcode to enable or disable requests handled in nrs_tbf
 */
static struct req_format *req_fmt(__u32 opcode)
{
	switch (opcode) {
	case OST_GETATTR:
		return &RQF_OST_GETATTR;
	case OST_SETATTR:
		return &RQF_OST_SETATTR;
	case OST_READ:
		return &RQF_OST_BRW_READ;
	case OST_WRITE:
		return &RQF_OST_BRW_WRITE;
	/* FIXME: OST_CREATE and OST_DESTROY comes from MDS
	 * in most case. Should they be removed? */
	case OST_CREATE:
		return &RQF_OST_CREATE;
	case OST_DESTROY:
		return &RQF_OST_DESTROY;
	case OST_PUNCH:
		return &RQF_OST_PUNCH;
	case OST_SYNC:
		return &RQF_OST_SYNC;
	case OST_LADVISE:
		return &RQF_OST_LADVISE;
	case MDS_GETATTR:
		return &RQF_MDS_GETATTR;
	case MDS_GETATTR_NAME:
		return &RQF_MDS_GETATTR_NAME;
	/* close is skipped to avoid LDLM cancel slowness */
#if 0
	case MDS_CLOSE:
		return &RQF_MDS_CLOSE;
#endif
	case MDS_REINT:
		return &RQF_MDS_REINT;
	case MDS_READPAGE:
		return &RQF_MDS_READPAGE;
	case MDS_GET_ROOT:
		return &RQF_MDS_GET_ROOT;
	case MDS_STATFS:
		return &RQF_MDS_STATFS;
	case MDS_SYNC:
		return &RQF_MDS_SYNC;
	case MDS_QUOTACTL:
		return &RQF_MDS_QUOTACTL;
	case MDS_GETXATTR:
		return &RQF_MDS_GETXATTR;
	case MDS_GET_INFO:
		return &RQF_MDS_GET_INFO;
	/* HSM op is skipped */
#if 0 
	case MDS_HSM_STATE_GET:
		return &RQF_MDS_HSM_STATE_GET;
	case MDS_HSM_STATE_SET:
		return &RQF_MDS_HSM_STATE_SET;
	case MDS_HSM_ACTION:
		return &RQF_MDS_HSM_ACTION;
	case MDS_HSM_CT_REGISTER:
		return &RQF_MDS_HSM_CT_REGISTER;
	case MDS_HSM_CT_UNREGISTER:
		return &RQF_MDS_HSM_CT_UNREGISTER;
#endif
	case MDS_SWAP_LAYOUTS:
		return &RQF_MDS_SWAP_LAYOUTS;
	case LDLM_ENQUEUE:
		return &RQF_LDLM_ENQUEUE;
	default:
		return NULL;
	}
}

static struct req_format *intent_req_fmt(__u32 it_opc)
{
	if (it_opc & (IT_OPEN | IT_CREAT))
		return &RQF_LDLM_INTENT_OPEN;
	else if (it_opc & (IT_GETATTR | IT_LOOKUP))
		return &RQF_LDLM_INTENT_GETATTR;
	else if (it_opc & IT_GETXATTR)
		return &RQF_LDLM_INTENT_GETXATTR;
	else if (it_opc & (IT_GLIMPSE | IT_BRW))
		return &RQF_LDLM_INTENT;
	else
		return NULL;
}

static int ost_tbf_id_cli_set(struct ptlrpc_request *req,
			      struct tbf_id *id)
{
	struct ost_body *body;

	body = req_capsule_client_get(&req->rq_pill, &RMF_OST_BODY);
	if (body != NULL) {
		id->ti_uid = body->oa.o_uid;
		id->ti_gid = body->oa.o_gid;
		id->ti_projid = body->oa.o_projid;
		return 0;
	}

	return -EINVAL;
}

static void unpack_ugpid_from_mdt_body(struct ptlrpc_request *req,
				       struct tbf_id *id)
{
	struct mdt_body *b = req_capsule_client_get(&req->rq_pill,
						    &RMF_MDT_BODY);
	LASSERT(b != NULL);

	/*
	 * TODO: nodemaping feature converts {ug}id from individual
	 * clients to the actual ones of the file system. Some work
	 * may be needed to fix this.
	 * TODO: Set ProjID in @mdt_body->mbo_projid.
	 */
	id->ti_uid = b->mbo_uid;
	id->ti_gid = b->mbo_gid;
	id->ti_projid = b->mbo_projid;
}

static void unpack_ugid_from_mdt_rec_reint(struct ptlrpc_request *req,
					   struct tbf_id *id)
{
	struct mdt_rec_reint *rec;

	rec = req_capsule_client_get(&req->rq_pill, &RMF_REC_REINT);
	LASSERT(rec != NULL);

	/* use the fs{ug}id as {ug}id of the process */
	id->ti_uid = rec->rr_fsuid;
	id->ti_gid = rec->rr_fsgid;
	/* TODO: extend @mdt_rec_reint to store ProjID. */
}

static int mdt_tbf_id_cli_set(struct ptlrpc_request *req,
			      struct tbf_id *id)
{
	u32 opc = lustre_msg_get_opc(req->rq_reqmsg);
	int rc = 0;

	switch (opc) {
	case MDS_GETATTR:
	case MDS_GETATTR_NAME:
	case MDS_GET_ROOT:
	case MDS_READPAGE:
	case MDS_SYNC:
	case MDS_GETXATTR:
	case MDS_HSM_STATE_GET ... MDS_SWAP_LAYOUTS:
		unpack_ugpid_from_mdt_body(req, id);
		break;
	case MDS_CLOSE:
	case MDS_REINT:
		unpack_ugid_from_mdt_rec_reint(req, id);
		break;
	default:
		rc = -EINVAL;
		break;
	}
	return rc;
}

static int ldlm_tbf_id_cli_set(struct ptlrpc_request *req,
			      struct tbf_id *id)
{
	struct ldlm_intent *lit;
	struct req_format *fmt;

	if (req->rq_reqmsg->lm_bufcount <= DLM_INTENT_IT_OFF)
		return -EINVAL;

	req_capsule_extend(&req->rq_pill, &RQF_LDLM_INTENT_BASIC);
	lit = req_capsule_client_get(&req->rq_pill, &RMF_LDLM_INTENT);
	if (lit == NULL)
		return -EINVAL;

	fmt = intent_req_fmt(lit->opc);
	if (fmt == NULL)
		return -EINVAL;

	req_capsule_extend(&req->rq_pill, fmt);

	if (lit->opc & (IT_GETXATTR | IT_GETATTR | IT_LOOKUP))
		unpack_ugpid_from_mdt_body(req, id);
	else if (lit->opc & (IT_OPEN | IT_OPEN | IT_GLIMPSE | IT_BRW))
		unpack_ugid_from_mdt_rec_reint(req, id);
	else
		return -EINVAL;
	return 0;
}

static int nrs_tbf_id_cli_set(struct ptlrpc_request *req, struct tbf_id *id,
			      enum nrs_tbf_flag need)
{
	struct req_format *fmt;
	const struct req_format *old_fmt;
	int rc = 0;
	u32 opc;

	id->ti_uid = (__u32) -1;
	id->ti_gid = (__u32) -1;
	id->ti_projid = (__u32) -1;

	if (need & (NRS_TBF_FLAG_UID | NRS_TBF_FLAG_GID)) {
		rc = lustre_msg_get_uid_gid(req->rq_reqmsg, &id->ti_uid,
					    &id->ti_gid);
		if (!rc) {
			if (id->ti_uid != (u32) -1)
				need &= ~NRS_TBF_FLAG_UID;
			if (id->ti_gid != (u32) -1)
				need &= ~NRS_TBF_FLAG_GID;
		}
	}

	if (need & NRS_TBF_FLAG_PROJID) {
		rc = lustre_msg_get_projid(req->rq_reqmsg, &id->ti_projid);
		if (!rc && id->ti_projid != (u32) -1)
			need &= ~NRS_TBF_FLAG_PROJID;
	}

	/* Obtain all ID(s) in need. */
	if (need == 0)
		return rc;

	/* The client req doesn't have uid/gid/projid pack in ptlrpc_body
	 * --> fallback to the old method.
	 */
	opc = lustre_msg_get_opc(req->rq_reqmsg);
	fmt = req_fmt(opc);
	if (fmt == NULL)
		return -EINVAL;

	req_capsule_init(&req->rq_pill, req, RCL_SERVER);
	old_fmt = req->rq_pill.rc_fmt;
	if (old_fmt == NULL)
		req_capsule_set(&req->rq_pill, fmt);

	if (opc < OST_LAST_OPC)
		rc = ost_tbf_id_cli_set(req, id);
	else if (opc >= MDS_FIRST_OPC && opc < MDS_LAST_OPC)
		rc = mdt_tbf_id_cli_set(req, id);
	else if (opc == LDLM_ENQUEUE)
		rc = ldlm_tbf_id_cli_set(req, id);
	else
		rc = -EINVAL;

	/* restore it to the original state */
	if (req->rq_pill.rc_fmt != old_fmt)
		req->rq_pill.rc_fmt = old_fmt;
	return rc;
}

/* The following nrs_tbf_lru_* function manipulate class object with a 0
 * refcount. These need to be accessed with a rcu lock to prevent deletions.
 * A TBF class with a 0 refcount means the following things:
 *  - class is in LRU list (NRS_TBF_CLI_LRU_BIT is set)
 *  - class is being added in LRU (refs 1->0 then NRS_TBF_CLI_LRU_BIT 0->1)
 *  - class is being deleted (NRS_TBF_CLI_LRU_BIT 1->0)
 *  - class is being removed from LRU (NRS_TBF_CLI_LRU_BIT 1->0 then refs 0->1)
 */

static void nrs_tbf_lru_add(struct nrs_tbf_head *head,
			    struct nrs_tbf_client *cli)
{
	LASSERTF(!test_bit(NRS_TBF_CLI_LRU_BIT, &cli->tc_state),
		 "class@%p already in LRU", cli);
	LASSERTF(!refcount_read(&cli->tc_ref), "class@%p ref != 0: %d",
		 cli, refcount_read(&cli->tc_ref));

	spin_lock(&head->th_lru_lock);
	list_add_tail_rcu(&cli->tc_lru, &head->th_lru_list);
	atomic_inc(&head->th_lru_cnt);
	spin_unlock(&head->th_lru_lock);

	set_bit(NRS_TBF_CLI_LRU_BIT, &cli->tc_state);
	TBF_CLI_DEBUG(cli, "TBF LRU add");

}

static
struct nrs_tbf_client *nrs_tbf_lru_tryhit(struct nrs_tbf_head *head,
					  struct nrs_tbf_client *cli)
{
	if (!test_and_clear_bit(NRS_TBF_CLI_LRU_BIT, &cli->tc_state))
		return NULL;

	LASSERTF(!refcount_read(&cli->tc_ref), "class@%p ref != 0: %d",
		 cli, refcount_read(&cli->tc_ref));

	spin_lock(&head->th_lru_lock);
	atomic_dec(&head->th_lru_cnt);
	list_del_rcu(&cli->tc_lru);
	spin_unlock(&head->th_lru_lock);

	refcount_set(&cli->tc_ref, 1);
	TBF_CLI_DEBUG(cli, "TBF LRU hit");

	return cli;
}

static int nrs_tbf_lru_shrink(struct nrs_tbf_head *head)
{
	struct nrs_tbf_client *cli;
	int high = tbf_jobid_cache_size;
	int low = 3 * high / 4;
	int freed = 0;

	if (atomic_read(&head->th_lru_cnt) <= high)
		return 0;

	/* already running ? */
	if (test_and_set_bit(NRS_TBF_SHRINKING_BIT, &head->th_state))
		return 0;

	rcu_read_lock();
	list_for_each_entry_rcu(cli, &head->th_lru_list, tc_lru) {
		if (atomic_read(&head->th_lru_cnt) <= low)
			break;

		/* race with nrs_tbf_lru_tryhit()/nrs_tbf_lru_add ? */
		if (!test_and_clear_bit(NRS_TBF_CLI_LRU_BIT, &cli->tc_state))
			continue;

		LASSERTF(!refcount_read(&cli->tc_ref), "class@%p ref != 0: %d",
			 cli, refcount_read(&cli->tc_ref));

		rhashtable_remove_fast(&head->th_cli_rhash, &cli->tc_rhash,
				       head->th_rhash_params);

		spin_lock(&head->th_lru_lock);
		atomic_dec(&head->th_lru_cnt);
		list_del_rcu(&cli->tc_lru);
		spin_unlock(&head->th_lru_lock);

		nrs_tbf_cli_fini(cli);
		freed++;
	}
	rcu_read_unlock();

	clear_bit(NRS_TBF_SHRINKING_BIT, &head->th_state);
	CDEBUG(D_RPCTRACE,
	       "%d objects freed from LRU (cur: %u, high: %d, low: %d)\n",
	       freed, atomic_read(&head->th_lru_cnt), high, low);

	return freed;
}

static struct nrs_tbf_client *
nrs_tbf_cli_find(struct nrs_tbf_head *head, struct ptlrpc_request *req)
{
	struct nrs_tbf_client *cli;
	struct nrs_tbf_key key;

	memset(&key, 0, sizeof(key));
	nrs_tbf_cli_gen_key(req, &key, head->th_type_flag);

	rcu_read_lock();
	cli = rhashtable_lookup(&head->th_cli_rhash, &key,
				head->th_rhash_params);
	if (cli && !refcount_inc_not_zero(&cli->tc_ref))
		cli = nrs_tbf_lru_tryhit(head, cli);
	rcu_read_unlock();

	return cli;
}

static struct nrs_tbf_client *
nrs_tbf_cli_findadd(struct nrs_tbf_head *head,
		    struct nrs_tbf_client *cli)
{
	struct nrs_tbf_client *cli2 = NULL;

try_again:
	rcu_read_lock();
	cli2 = rhashtable_lookup_get_insert_fast(&head->th_cli_rhash,
						 &cli->tc_rhash,
						 head->th_rhash_params);
	if (IS_ERR(cli2))
		goto out;

	if (cli2 && !refcount_inc_not_zero(&cli2->tc_ref)) {
		cli2 = nrs_tbf_lru_tryhit(head, cli2);
		if (!cli2) {
			/* lost race -> retry */
			rcu_read_unlock();
			schedule();
			goto try_again;
		}
	}

out:
	rcu_read_unlock();
	if (!cli2)
		TBF_CLI_DEBUG(cli, "TBF class insert");

	return cli2 ?: cli;
}

static void
nrs_tbf_cli_put(struct nrs_tbf_head *head, struct nrs_tbf_client *cli)
{

	if (!refcount_dec_and_test(&cli->tc_ref))
		return;

	nrs_tbf_lru_add(head, cli);
	nrs_tbf_lru_shrink(head);
}

static void
nrs_tbf_id_list_free(struct list_head *id_list)
{
	struct nrs_tbf_id *nti_id, *n;

	list_for_each_entry_safe(nti_id, n, id_list, nti_linkage) {
		list_del_init(&nti_id->nti_linkage);
		OBD_FREE_PTR(nti_id);
	}
}

static void
nrs_tbf_nodemap_list_free(struct list_head *nm_list)
{
	struct nrs_tbf_nodemap *nodemap, *n;

	list_for_each_entry_safe(nodemap, n, nm_list, ntn_linkage) {
		list_del_init(&nodemap->ntn_linkage);
		OBD_FREE_PTR(nodemap);
	}
}

static void
nrs_tbf_expression_free(struct nrs_tbf_expression *expr)
{
	LASSERT(expr->te_field >= 0 && expr->te_field < NRS_TBF_FIELD_MAX);
	switch (expr->te_field) {
	case NRS_TBF_FIELD_NID:
		cfs_free_nidlist(&expr->te_cond);
		break;
	case NRS_TBF_FIELD_JOBID:
		nrs_tbf_jobid_list_free(&expr->te_cond);
		break;
	case NRS_TBF_FIELD_OPCODE:
		bitmap_free(expr->te_opcodes);
		break;
	case NRS_TBF_FIELD_UID:
	case NRS_TBF_FIELD_GID:
	case NRS_TBF_FIELD_PROJID:
		nrs_tbf_id_list_free(&expr->te_cond);
		break;
	case NRS_TBF_FIELD_NODEMAP:
		nrs_tbf_nodemap_list_free(&expr->te_cond);
		break;
	default:
		LBUG();
	}
	OBD_FREE_PTR(expr);
}

static void
nrs_tbf_conjunction_free(struct nrs_tbf_conjunction *conjunction)
{
	struct nrs_tbf_expression *expression;
	struct nrs_tbf_expression *n;

	LASSERT(list_empty(&conjunction->tc_linkage));
	list_for_each_entry_safe(expression, n,
				 &conjunction->tc_expressions,
				 te_linkage) {
		list_del_init(&expression->te_linkage);
		nrs_tbf_expression_free(expression);
	}
	OBD_FREE_PTR(conjunction);
}

static void
nrs_tbf_conds_free(struct list_head *cond_list)
{
	struct nrs_tbf_conjunction *conjunction;
	struct nrs_tbf_conjunction *n;

	list_for_each_entry_safe(conjunction, n, cond_list, tc_linkage) {
		list_del_init(&conjunction->tc_linkage);
		nrs_tbf_conjunction_free(conjunction);
	}
}

#define NRS_TBF_DISJUNCTION_DELIM	(",")
#define NRS_TBF_CONJUNCTION_DELIM	("&")
#define NRS_TBF_EXPRESSION_DELIM	("=")

static int
nrs_tbf_opcode_list_parse(char *str, unsigned long **bitmaptr);
static int
nrs_tbf_id_list_parse(char *str, struct list_head *id_list,
		      enum nrs_tbf_flag tif);

static int
nrs_tbf_nodemap_list_add(char *name, struct list_head *nm_list)
{
	struct nrs_tbf_nodemap *nodemap;
	struct lu_nodemap *lnm;

	ENTRY;

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

	/*
	 * If the setting for nodemap and TBF are using Lustre config log,
	 * it must ensure that nodemap is configured before the TBF.
	 * Otherwise, the nodemap lookup will return an error and adding
	 * TBF rule for the nodemap will fail.
	 */
	lnm = nodemap_lookup_unlocked(name);
	if (IS_ERR(lnm)) {
		OBD_FREE_PTR(nodemap);
		RETURN(PTR_ERR(lnm));
	}

	nodemap->ntn_nmid = lnm->nm_id;
	nodemap_putref(lnm);
	list_add_tail(&nodemap->ntn_linkage, nm_list);
	RETURN(0);
}

static int
nrs_tbf_nodemap_list_parse(char *orig, struct list_head *nm_list)
{
	char *str, *copy;
	int rc = 0;

	ENTRY;

	copy = kstrdup(orig, GFP_KERNEL);
	if (!copy)
		RETURN(-ENOMEM);

	str = copy;
	INIT_LIST_HEAD(nm_list);
	while (str && rc == 0) {
		char *tok = strsep(&str, " ");

		if (*tok)
			rc = nrs_tbf_nodemap_list_add(tok, nm_list);
	}
	if (list_empty(nm_list))
		rc = -EINVAL;
	if (rc)
		nrs_tbf_nodemap_list_free(nm_list);
	kfree(copy);
	RETURN(rc);
}

static int
nrs_tbf_expression_parse(enum nrs_tbf_flag ntf, char *str,
			 struct list_head *cond_list)
{
	struct nrs_tbf_expression *expr;
	char *field;
	int rc = 0;
	int len;

	OBD_ALLOC_PTR(expr);
	if (expr == NULL)
		return -ENOMEM;

	field = strim(strsep(&str, NRS_TBF_EXPRESSION_DELIM));
	if (!*field || !str)
		/* No LHS or no '=' sign */
		GOTO(out, rc = -EINVAL);
	str = strim(str);
	len = strlen(str);
	if (len < 2 || str[0] != '{' || str[len-1] != '}')
		/* No {} around RHS */
		GOTO(out, rc = -EINVAL);

	/* Skip '{' and '}' */
	str[len-1] = '\0';
	str += 1;
	len -= 2;

	if (strcmp(field, NRS_TBF_TYPE_NID) == 0) {
		if (!(ntf & NRS_TBF_FLAG_NID))
			GOTO(out, rc = -EINVAL);
		if (cfs_parse_nidlist(str, len, &expr->te_cond) < 0)
			GOTO(out, rc = -EINVAL);
		expr->te_field = NRS_TBF_FIELD_NID;
	} else if (strcmp(field, NRS_TBF_TYPE_JOBID) == 0) {
		if (!(ntf & NRS_TBF_FLAG_JOBID))
			GOTO(out, rc = -EINVAL);
		if (nrs_tbf_jobid_list_parse(str, &expr->te_cond) < 0)
			GOTO(out, rc = -EINVAL);
		expr->te_field = NRS_TBF_FIELD_JOBID;
	} else if (strcmp(field, NRS_TBF_TYPE_OPCODE) == 0) {
		if (!(ntf & NRS_TBF_FLAG_OPCODE))
			GOTO(out, rc = -EINVAL);
		if (nrs_tbf_opcode_list_parse(str, &expr->te_opcodes) < 0)
			GOTO(out, rc = -EINVAL);
		expr->te_field = NRS_TBF_FIELD_OPCODE;
	} else if (strcmp(field, NRS_TBF_TYPE_UID) == 0) {
		if (!(ntf & NRS_TBF_FLAG_UID))
			GOTO(out, rc = -EINVAL);
		if (nrs_tbf_id_list_parse(str, &expr->te_cond,
					  NRS_TBF_FLAG_UID) < 0)
			GOTO(out, rc = -EINVAL);
		expr->te_field = NRS_TBF_FIELD_UID;
	} else if (strcmp(field, NRS_TBF_TYPE_GID) == 0) {
		if (!(ntf & NRS_TBF_FLAG_GID))
			GOTO(out, rc = -EINVAL);
		if (nrs_tbf_id_list_parse(str, &expr->te_cond,
					  NRS_TBF_FLAG_GID) < 0)
			GOTO(out, rc = -EINVAL);
		expr->te_field = NRS_TBF_FIELD_GID;
	} else if (strcmp(field, NRS_TBF_TYPE_PROJID) == 0) {
		if (!(ntf & NRS_TBF_FLAG_PROJID))
			GOTO(out, rc = -EINVAL);
		if (nrs_tbf_id_list_parse(str, &expr->te_cond,
					  NRS_TBF_FLAG_PROJID) < 0)
			GOTO(out, rc = -EINVAL);
		expr->te_field = NRS_TBF_FIELD_PROJID;
	} else if (strcmp(field, NRS_TBF_TYPE_NODEMAP) == 0) {
		if (!(ntf & NRS_TBF_FLAG_NODEMAP))
			GOTO(out, rc = -EINVAL);
		if (nrs_tbf_nodemap_list_parse(str, &expr->te_cond) < 0)
			GOTO(out, rc = -EINVAL);
		expr->te_field = NRS_TBF_FIELD_NODEMAP;
	} else {
		GOTO(out, rc = -EINVAL);
	}

	list_add_tail(&expr->te_linkage, cond_list);
	return 0;
out:
	OBD_FREE_PTR(expr);
	return rc;
}

static int
nrs_tbf_conjunction_parse(enum nrs_tbf_flag ntf, char *str,
			  struct list_head *cond_list)
{
	struct nrs_tbf_conjunction *conjunction;
	int rc = 0;

	OBD_ALLOC_PTR(conjunction);
	if (conjunction == NULL)
		return -ENOMEM;

	INIT_LIST_HEAD(&conjunction->tc_expressions);
	list_add_tail(&conjunction->tc_linkage, cond_list);

	while (str && !rc) {
		char *expr = strsep(&str, NRS_TBF_CONJUNCTION_DELIM);

		rc = nrs_tbf_expression_parse(ntf, expr,
					      &conjunction->tc_expressions);
	}
	return rc;
}

static int
nrs_tbf_conds_parse(enum nrs_tbf_flag ntf, char *orig,
		    struct list_head *cond_list)
{
	char *str;
	int rc = 0;

	orig = kstrdup(orig, GFP_KERNEL);
	if (!orig)
		return -ENOMEM;
	str = orig;

	INIT_LIST_HEAD(cond_list);
	while (str && !rc) {
		char *term = strsep(&str, NRS_TBF_DISJUNCTION_DELIM);

		rc = nrs_tbf_conjunction_parse(ntf, term, cond_list);
	}
	kfree(orig);

	if (rc)
		nrs_tbf_conds_free(cond_list);

	return rc;
}

static int
nrs_tbf_id_list_match(struct list_head *id_list, u32 id,
		      enum nrs_tbf_flag flag);

static int
nrs_tbf_nodemap_list_match(struct list_head *nm_list, unsigned int nmid)
{
	struct nrs_tbf_nodemap *nodemap;

	list_for_each_entry(nodemap, nm_list, ntn_linkage) {
		if (nodemap->ntn_nmid == nmid)
			return 1;
	}
	return 0;
}

static int
nrs_tbf_expression_match(struct nrs_tbf_expression *expr,
			 struct nrs_tbf_rule *rule,
			 struct nrs_tbf_client *cli)
{
	switch (expr->te_field) {
	case NRS_TBF_FIELD_NID:
		return cfs_match_nid(&cli->tc_nid, &expr->te_cond);
	case NRS_TBF_FIELD_JOBID:
		return nrs_tbf_jobid_list_match(&expr->te_cond, cli->tc_jobid);
	case NRS_TBF_FIELD_OPCODE:
		return test_bit(cli->tc_opcode, expr->te_opcodes);
	case NRS_TBF_FIELD_UID:
		return nrs_tbf_id_list_match(&expr->te_cond, cli->tc_id.ti_uid,
					     NRS_TBF_FLAG_UID);
	case NRS_TBF_FIELD_GID:
		return nrs_tbf_id_list_match(&expr->te_cond, cli->tc_id.ti_gid,
					     NRS_TBF_FLAG_GID);
	case NRS_TBF_FIELD_PROJID:
		return nrs_tbf_id_list_match(&expr->te_cond,
					     cli->tc_id.ti_projid,
					     NRS_TBF_FLAG_PROJID);
	case NRS_TBF_FIELD_NODEMAP:
		return nrs_tbf_nodemap_list_match(&expr->te_cond,
						  cli->tc_nmid);
	default:
		return 0;
	}
}

static int
nrs_tbf_conjunction_match(struct nrs_tbf_conjunction *conjunction,
			  struct nrs_tbf_rule *rule,
			  struct nrs_tbf_client *cli)
{
	struct nrs_tbf_expression *expr;
	int matched;

	list_for_each_entry(expr, &conjunction->tc_expressions, te_linkage) {
		matched = nrs_tbf_expression_match(expr, rule, cli);
		if (!matched)
			return 0;
	}

	return 1;
}

static int
nrs_tbf_cond_match(struct nrs_tbf_rule *rule, struct nrs_tbf_client *cli)
{
	struct nrs_tbf_conjunction *conjunction;
	int matched;

	list_for_each_entry(conjunction, &rule->tr_conds, tc_linkage) {
		matched = nrs_tbf_conjunction_match(conjunction, rule, cli);
		if (matched)
			return 1;
	}

	return 0;
}

static
struct nrs_tbf_rule *nrs_tbf_rule_alloc(struct ptlrpc_nrs_policy *policy,
					struct nrs_tbf_head *head,
					struct nrs_tbf_cmd *start)
{
	struct nrs_tbf_rule *rule;
	int rc = 0;

	OBD_CPT_ALLOC_PTR(rule, nrs_pol2cptab(policy), nrs_pol2cptid(policy));
	if (!rule)
		return ERR_PTR(-ENOMEM);

	strscpy(rule->tr_name, start->tc_name, sizeof(rule->tr_name));
	rule->tr_rpc_rate = start->u.tc_start.ts_rpc_rate;
	rule->tr_flags = start->u.tc_start.ts_rule_flags;
	rule->tr_nsecs_per_rpc = NSEC_PER_SEC / rule->tr_rpc_rate;
	rule->tr_depth = tbf_depth;
	kref_init(&rule->tr_ref);
	INIT_LIST_HEAD(&rule->tr_cli_list);
	INIT_LIST_HEAD(&rule->tr_nids);
	INIT_LIST_HEAD(&rule->tr_linkage);
	spin_lock_init(&rule->tr_rule_lock);
	rule->tr_head = head;

	LASSERT(start->u.tc_start.ts_conds_str);
	OBD_STRNDUP(rule->tr_conds_str,
		    start->u.tc_start.ts_conds_str,
		    strlen(start->u.tc_start.ts_conds_str));
	if (!rule->tr_conds_str)
		GOTO(err, rc = -ENOMEM);

	INIT_LIST_HEAD(&rule->tr_conds);
	if (!list_empty(&start->u.tc_start.ts_conds)) {
		rc = nrs_tbf_conds_parse(start->u.tc_start.ts_valid_type,
					 rule->tr_conds_str,
					 &rule->tr_conds);
	}
	if (rc)
		GOTO(err, rc);

	return rule;

err:
	kref_put(&rule->tr_ref, nrs_tbf_rule_fini);

	return ERR_PTR(rc);
}

#define MAX_OPCODE_LEN	32
static int
nrs_tbf_opcode_set_bit(char *id, unsigned long *opcodes)
{
	int op;

	op = ll_str2opcode(id);
	if (op < 0)
		return -EINVAL;

	set_bit(op, opcodes);
	return 0;
}

static int
nrs_tbf_opcode_list_parse(char *orig, unsigned long **bitmaptr)
{
	unsigned long *opcodes;
	char *str;
	int cnt = 0;
	int rc = 0;

	ENTRY;
	orig = kstrdup(orig, GFP_KERNEL);
	if (!orig)
		return -ENOMEM;
	opcodes = bitmap_zalloc(LUSTRE_MAX_OPCODES, GFP_KERNEL);
	if (!opcodes) {
		kfree(orig);
		return -ENOMEM;
	}
	str = orig;
	while (str && rc == 0) {
		char *tok = strsep(&str, " ");

		if (*tok) {
			rc = nrs_tbf_opcode_set_bit(tok, opcodes);
			cnt += 1;
		}
	}
	if (cnt == 0)
		rc = -EINVAL;

	kfree(orig);
	if (rc == 0 && bitmaptr)
		*bitmaptr = opcodes;
	else
		bitmap_free(opcodes);

	RETURN(rc);
}

static int
nrs_tbf_id_list_match(struct list_head *id_list, u32 id,
		      enum nrs_tbf_flag idtype)
{
	struct nrs_tbf_id *nti_id;

	list_for_each_entry(nti_id, id_list, nti_linkage) {
		u32 curid;

		switch (idtype) {
		case NRS_TBF_FLAG_UID:
			curid = nti_id->nti_id.ti_uid;
			break;
		case NRS_TBF_FLAG_GID:
			curid = nti_id->nti_id.ti_gid;
			break;
		case NRS_TBF_FLAG_PROJID:
			curid = nti_id->nti_id.ti_projid;
			break;
		default:
			return 0;
		}

		if (id != curid)
			continue;

		return 1;
	}
	return 0;
}

static int
nrs_tbf_id_list_parse(char *orig, struct list_head *id_list,
		      enum nrs_tbf_flag ntf)
{
	int rc = 0;
	unsigned long val;
	char *str;
	struct tbf_id id = { 0 };

	ENTRY;

	if (ntf != NRS_TBF_FLAG_UID && ntf != NRS_TBF_FLAG_GID &&
	    ntf != NRS_TBF_FLAG_PROJID)
		RETURN(-EINVAL);

	orig = kstrdup(orig, GFP_KERNEL);
	if (!orig)
		return -ENOMEM;

	INIT_LIST_HEAD(id_list);
	for (str = orig; str ; ) {
		struct nrs_tbf_id *nti_id;
		char *tok;

		tok = strsep(&str, " ");
		if (!*tok)
			/* Empty token - leading, trailing, or
			 * multiple spaces in list
			 */
			continue;

		rc = kstrtoul(tok, 0, &val);
		if (rc < 0)
			GOTO(out, rc = -EINVAL);
		if (ntf == NRS_TBF_FLAG_UID)
			id.ti_uid = val;
		else if (ntf == NRS_TBF_FLAG_GID)
			id.ti_gid = val;
		else
			id.ti_projid = val;

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

		nti_id->nti_id = id;
		list_add_tail(&nti_id->nti_linkage, id_list);
	}
	if (list_empty(id_list))
		/* Only white space in the list */
		GOTO(out, rc = -EINVAL);
out:
	kfree(orig);
	if (rc)
		nrs_tbf_id_list_free(id_list);
	RETURN(rc);
}


#define NRS_TBF_TYPE_COMBINATOR	"+"

static int nrs_tbf_type_parse(const char *orig, enum nrs_tbf_flag *fl)
{
	enum nrs_tbf_flag flags = NRS_TBF_FLAG_INVALID;
	char *copy, *str;
	int rc = 0;

	ENTRY;

	copy = kstrdup(orig, GFP_KERNEL);
	if (!copy)
		RETURN(-ENOMEM);

	str = copy;
	while (str && rc == 0) {
		char *name;
		bool found = false;
		int i;

		name = strsep(&str, NRS_TBF_TYPE_COMBINATOR);
		if (!*name)
			GOTO(out, rc);

		for (i = 0; i < ARRAY_SIZE(nrs_tbf_types); i++) {
			if (strcmp(name, nrs_tbf_types[i].ntt_name) == 0) {
				flags |= nrs_tbf_types[i].ntt_flag;
				found = true;
				break;
			}
		}

		if (found == false)
			GOTO(out, rc = -EINVAL);
	}
out:
	kfree(copy);
	if (rc == 0) {
		if (flags == NRS_TBF_FLAG_INVALID)
			RETURN(-EINVAL);

		*fl = flags;
	}

	RETURN(rc);
}

/**
 * nrs_tbf_start() - policy to start
 * @policy: The policy to start
 * @arg: tbf flags
 *
 * Is called before the policy transitions into
 * ptlrpc_nrs_pol_state::NRS_POL_STATE_STARTED; allocates and initializes a
 * policy-specific private data structure.
 *
 * see nrs_policy_register()
 * see nrs_policy_ctl()
 *
 * Return
 * * %0 on success
 * * %errno on error (-ENOMEM (OOM) error)
 *
 */
static int nrs_tbf_start(struct ptlrpc_nrs_policy *policy, char *arg)
{
	struct nrs_tbf_head *head;
	enum nrs_tbf_flag flags;
	char *name;
	int rc = 0;

	if (arg == NULL) {
		flags = NRS_TBF_FLAG_ALL;
		name = NRS_TBF_TYPE_GENERIC;
	} else if (strlen(arg) < NRS_TBF_TYPE_MAX_LEN) {
		name = arg;
		rc = nrs_tbf_type_parse(arg, &flags);
		if (rc)
			GOTO(out, rc);
	} else {
		GOTO(out, rc = -EINVAL);
	}

	LASSERT((flags & NRS_TBF_FLAG_ALL) != 0);
	LASSERT((flags & ~NRS_TBF_FLAG_ALL) == 0);

	if (!nrs_tbf_flags_valid(flags))
		GOTO(out, rc = -EINVAL);

	OBD_CPT_ALLOC_PTR(head, nrs_pol2cptab(policy), nrs_pol2cptid(policy));
	if (head == NULL)
		GOTO(out, rc = -ENOMEM);

	memcpy(head->th_type, name, strlen(name));
	head->th_type[strlen(name)] = '\0';
	head->th_type_flag = flags;

	head->th_binheap = binheap_create(&nrs_tbf_heap_ops,
					  CBH_FLAG_ATOMIC_GROW, 4096, NULL,
					  nrs_pol2cptab(policy),
					  nrs_pol2cptid(policy));
	if (head->th_binheap == NULL)
		GOTO(out_free_head, rc = -ENOMEM);

	atomic_set(&head->th_rule_sequence, 0);
	atomic_set(&head->th_lru_cnt, 0);
	spin_lock_init(&head->th_rule_lock);
	spin_lock_init(&head->th_lru_lock);
	INIT_LIST_HEAD(&head->th_list);
	INIT_LIST_HEAD(&head->th_lru_list);
	hrtimer_setup(&head->th_timer, nrs_tbf_timer_cb, CLOCK_MONOTONIC,
		      HRTIMER_MODE_ABS);
	rc = nrs_tbf_startup(policy, head);
	if (rc)
		GOTO(out_free_heap, rc);

	policy->pol_private = head;
	return 0;
out_free_heap:
	binheap_destroy(head->th_binheap);
out_free_head:
	OBD_FREE_PTR(head);
out:
	return rc;
}

/**
 * nrs_tbf_stop() - Stop policy transction
 * @policy: The policy to stop
 *
 * Is called before the policy transitions into
 * ptlrpc_nrs_pol_state::NRS_POL_STATE_STOPPED; deallocates the policy-specific
 * private data structure.
 *
 * see __nrs_policy_stop()
 */
static void nrs_tbf_stop(struct ptlrpc_nrs_policy *policy)
{
	struct nrs_tbf_head *head = policy->pol_private;
	struct ptlrpc_nrs *nrs = policy->pol_nrs;
	struct nrs_tbf_rule *rule, *n;

	LASSERT(head != NULL);
	hrtimer_cancel(&head->th_timer);
	/* Should cleanup hash first before free rules */
	atomic_set(&head->th_lru_cnt, 0);
	rhashtable_free_and_destroy(&head->th_cli_rhash,
				    nrs_tbf_cli_exit, NULL);
	list_for_each_entry_safe(rule, n, &head->th_list, tr_linkage) {
		list_del_init(&rule->tr_linkage);
		kref_put(&rule->tr_ref, nrs_tbf_rule_fini);
	}

	/* wait for all the class object to be freed */
	rcu_barrier();

	LASSERT(list_empty(&head->th_lru_list));
	LASSERT(list_empty(&head->th_list));
	LASSERT(head->th_binheap != NULL);
	LASSERT(binheap_is_empty(head->th_binheap));
	binheap_destroy(head->th_binheap);
	OBD_FREE_PTR(head);
	nrs->nrs_throttling = 0;
	wake_up(&policy->pol_nrs->nrs_svcpt->scp_waitq);
}

/**
 * nrs_tbf_ctl() - Performs a policy-specific ctl function on TBF policy
 * instances; similar to ioctl.
 * @policy: the policy instance
 * @opc: the opcode
 * @arg: used for passing parameters and information[in,out]
 *
 * pre: assert_spin_locked(&policy->pol_nrs->->nrs_lock)
 * post: assert_spin_locked(&policy->pol_nrs->->nrs_lock)
 *
 * Return
 * * %0 on success (operation carried out successfully)
 * * %negative on error
 */
static int nrs_tbf_ctl(struct ptlrpc_nrs_policy *policy,
		       enum ptlrpc_nrs_ctl opc,
		       void *arg)
{
	int rc = 0;
	ENTRY;

	assert_spin_locked(&policy->pol_nrs->nrs_lock);

	switch (opc) {
	default:
		RETURN(-EINVAL);

	/**
	 * Read RPC rate size of a policy instance.
	 */
	case NRS_CTL_TBF_RD_RULE: {
		struct nrs_tbf_head *head = policy->pol_private;
		struct seq_file *m = arg;
		struct ptlrpc_service_part *svcpt;

		svcpt = policy->pol_nrs->nrs_svcpt;
		seq_printf(m, "CPT %d:\n", svcpt->scp_cpt);

		rc = nrs_tbf_rule_dump_all(head, m);
		}
		break;

	/**
	 * Write RPC rate of a policy instance.
	 */
	case NRS_CTL_TBF_WR_RULE: {
		struct nrs_tbf_head *head = policy->pol_private;
		struct nrs_tbf_cmd *cmd;

		cmd = (struct nrs_tbf_cmd *)arg;
		rc = nrs_tbf_command(policy,
				     head,
				     cmd);
		}
		break;
	/**
	 * Read the TBF policy type of a policy instance.
	 */
	case NRS_CTL_TBF_RD_TYPE_FLAG: {
		struct nrs_tbf_head *head = policy->pol_private;

		*(__u32 *)arg = head->th_type_flag;
		}
		break;
	}

	RETURN(rc);
}

/**
 * nrs_tbf_res_get() - Is called for obtaining a TBF policy resource.
 * @policy: The policy on which the request is being asked for
 * @nrq: The request for which resources are being taken
 * @parent: Parent resource, unused in this policy
 * @resp: Resources references are placed in this array[out]
 * @moving_req: Signifies limited caller context; unused in this policy
 *
 * see nrs_resource_get_safe()
 *
 * Return
 * * %0 on success
 * * %negative on error
 */
static int nrs_tbf_res_get(struct ptlrpc_nrs_policy *policy,
			   struct ptlrpc_nrs_request *nrq,
			   const struct ptlrpc_nrs_resource *parent,
			   struct ptlrpc_nrs_resource **resp,
			   bool moving_req)
{
	struct nrs_tbf_head   *head;
	struct nrs_tbf_client *cli;
	struct nrs_tbf_client *tmp;
	struct ptlrpc_request *req;

	if (parent == NULL) {
		*resp = &((struct nrs_tbf_head *)policy->pol_private)->th_res;
		return 0;
	}

	head = container_of(parent, struct nrs_tbf_head, th_res);
	req = container_of(nrq, struct ptlrpc_request, rq_nrq);
	cli = nrs_tbf_cli_find(head, req);
	if (cli != NULL) {
		spin_lock(&policy->pol_nrs->nrs_svcpt->scp_req_lock);
		LASSERT(cli->tc_rule);
		if (cli->tc_rule_sequence !=
		    atomic_read(&head->th_rule_sequence) ||
		    cli->tc_rule->tr_flags & NTRS_STOPPING) {
			struct nrs_tbf_rule *rule;

			TBF_CLI_DEBUG(cli, "TBF rule update, rule flags 0x%x, head sequence %d",
				      cli->tc_rule->tr_flags,
				      atomic_read(&head->th_rule_sequence));
			rule = nrs_tbf_rule_match(head, cli);
			if (rule != cli->tc_rule) {
				nrs_tbf_cli_reset(head, rule, cli);
			} else {
				if (cli->tc_rule_generation != rule->tr_generation)
					nrs_tbf_cli_reset_value(head, cli);
				kref_put(&rule->tr_ref, nrs_tbf_rule_fini);
			}
		} else if (cli->tc_rule_generation !=
			   cli->tc_rule->tr_generation) {
			nrs_tbf_cli_reset_value(head, cli);
		}
		spin_unlock(&policy->pol_nrs->nrs_svcpt->scp_req_lock);
		goto out;
	}


	cli = nrs_tbf_cli_alloc(head, req, moving_req ? GFP_ATOMIC : __GFP_IO);
	if (cli == NULL)
		return -ENOMEM;

	tmp = nrs_tbf_cli_findadd(head, cli);
	if (tmp != cli) {
		WARN_ON(!refcount_dec_if_one(&cli->tc_ref));
		nrs_tbf_cli_fini(cli);
		cli = tmp;
		if (IS_ERR(cli))
			return PTR_ERR(cli);
	}
out:
	*resp = &cli->tc_res;

	return 1;
}

/**
 * nrs_tbf_res_put() - Called when releasing references to the resource hierachy
 * obtained for a
 * request for scheduling using the TBF policy.
 * @policy: the policy the resource belongs to
 * @res: the resource to be released
 */
static void nrs_tbf_res_put(struct ptlrpc_nrs_policy *policy,
			    const struct ptlrpc_nrs_resource *res)
{
	struct nrs_tbf_head   *head;
	struct nrs_tbf_client *cli;

	/**
	 * Do nothing for freeing parent, nrs_tbf_net resources
	 */
	if (res->res_parent == NULL)
		return;

	cli = container_of(res, struct nrs_tbf_client, tc_res);
	head = container_of(res->res_parent, struct nrs_tbf_head, th_res);

	nrs_tbf_cli_put(head, cli);
}

/**
 * nrs_tbf_req_get() - Called when getting a request from the TBF policy for
 * handling, or just peeking; removes the request from the policy when it is
 * to be handled.
 * @policy: The policy
 * @peek: When set, signifies that we just want to examine the request,
 * and not handle it, so the request is not removed from the policy.
 * @force: Force the policy to return a request
 *
 * See: ptlrpc_nrs_req_get_nolock()
 * See: nrs_request_get()
 *
 * Return The request to be handled; this is the next request in the TBF
 *	rule
 *
 */
static
struct ptlrpc_nrs_request *nrs_tbf_req_get(struct ptlrpc_nrs_policy *policy,
					   bool peek, bool force)
{
	struct nrs_tbf_head	  *head = policy->pol_private;
	struct ptlrpc_nrs_request *nrq = NULL;
	struct nrs_tbf_client     *cli;
	struct binheap_node	  *node;

	assert_spin_locked(&policy->pol_nrs->nrs_svcpt->scp_req_lock);

	if (likely(!peek && !force) && policy->pol_nrs->nrs_throttling)
		return NULL;

	node = binheap_root(head->th_binheap);
	if (unlikely(node == NULL))
		return NULL;

	cli = container_of(node, struct nrs_tbf_client, tc_node);
	LASSERT(test_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state));
	if (unlikely(peek)) {
		nrq = list_first_entry(&cli->tc_list,
				       struct ptlrpc_nrs_request,
				       nr_u.tbf.tr_list);
	} else {
		struct nrs_tbf_rule *rule = cli->tc_rule;
		__u64 now = ktime_to_ns(ktime_get());
		__u64 passed;
		__u64 ntoken;
		__u64 deadline;
		__u64 old_resid = 0;

		deadline = cli->tc_check_time +
			  cli->tc_nsecs;
		LASSERT(now >= cli->tc_check_time);
		passed = now - cli->tc_check_time;
		ntoken = passed * cli->tc_rpc_rate;
		do_div(ntoken, NSEC_PER_SEC);

		ntoken += cli->tc_ntoken;
		if (rule->tr_flags & NTRS_REALTIME) {
			LASSERT(cli->tc_nsecs_resid < cli->tc_nsecs);
			old_resid = cli->tc_nsecs_resid;
			cli->tc_nsecs_resid += passed % cli->tc_nsecs;
			if (cli->tc_nsecs_resid > cli->tc_nsecs) {
				ntoken++;
				cli->tc_nsecs_resid -= cli->tc_nsecs;
			}
		} else if (ntoken > cli->tc_depth)
			ntoken = cli->tc_depth;

		/* give an extra token with force mode */
		if (unlikely(force) && ntoken == 0)
			ntoken = 1;

		if (ntoken > 0) {
			nrq = list_first_entry(&cli->tc_list,
					 struct ptlrpc_nrs_request,
					 nr_u.tbf.tr_list);
			ntoken--;
			cli->tc_ntoken = ntoken;
			cli->tc_check_time = now;
			list_del_init(&nrq->nr_u.tbf.tr_list);
			if (list_empty(&cli->tc_list)) {
				binheap_remove(head->th_binheap,
					       &cli->tc_node);
				clear_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state);
			} else {
				if (!(rule->tr_flags & NTRS_REALTIME))
					cli->tc_deadline = now + cli->tc_nsecs;
				binheap_relocate(head->th_binheap,
						 &cli->tc_node);
			}
			TBF_CLI_DEBUG(cli, "TBF dequeues");
		} else {
			ktime_t time;

			if (rule->tr_flags & NTRS_REALTIME) {
				cli->tc_deadline = deadline;
				cli->tc_nsecs_resid = old_resid;
				binheap_relocate(head->th_binheap,
						 &cli->tc_node);
				if (node != binheap_root(head->th_binheap))
					return nrs_tbf_req_get(policy,
							       peek, force);
			}
			policy->pol_nrs->nrs_throttling = 1;
			head->th_deadline = deadline;
			time = ktime_set(0, 0);
			time = ktime_add_ns(time, deadline);
			hrtimer_start(&head->th_timer, time, HRTIMER_MODE_ABS);
		}
	}

	return nrq;
}

/**
 * nrs_tbf_req_add() - Adds request @nrq to @policy's list of queued requests
 * @policy: The policy
 * @nrq: The request to add
 *
 * Return 0 on success; nrs_request_enqueue() assumes this function will always
 * succeed
 */
static int nrs_tbf_req_add(struct ptlrpc_nrs_policy *policy,
			   struct ptlrpc_nrs_request *nrq)
{
	struct nrs_tbf_head   *head;
	struct nrs_tbf_client *cli;
	int		       rc = 0;

	assert_spin_locked(&policy->pol_nrs->nrs_svcpt->scp_req_lock);

	cli = container_of(nrs_request_resource(nrq),
			   struct nrs_tbf_client, tc_res);
	head = container_of(nrs_request_resource(nrq)->res_parent,
			    struct nrs_tbf_head, th_res);
	if (list_empty(&cli->tc_list)) {
		LASSERT(!test_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state));
		cli->tc_deadline = cli->tc_check_time + cli->tc_nsecs;
		rc = binheap_insert(head->th_binheap, &cli->tc_node);
		if (rc == 0) {
			set_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state);
			nrq->nr_u.tbf.tr_sequence = head->th_sequence++;
			list_add_tail(&nrq->nr_u.tbf.tr_list,
					  &cli->tc_list);
			if (policy->pol_nrs->nrs_throttling) {
				__u64 deadline = cli->tc_deadline;
				if ((head->th_deadline > deadline) &&
				    (hrtimer_try_to_cancel(&head->th_timer)
				     >= 0)) {
					ktime_t time;
					head->th_deadline = deadline;
					time = ktime_set(0, 0);
					time = ktime_add_ns(time, deadline);
					hrtimer_start(&head->th_timer, time,
						      HRTIMER_MODE_ABS);
				}
			}
		}
	} else {
		LASSERT(test_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state));
		nrq->nr_u.tbf.tr_sequence = head->th_sequence++;
		list_add_tail(&nrq->nr_u.tbf.tr_list,
				  &cli->tc_list);
	}

	if (rc == 0)
		TBF_CLI_DEBUG(cli, "TBF enqueues");

	return rc;
}

/**
 * nrs_tbf_req_del() - Removes request @nrq from @policy's list of queued
 * requests.
 * @policy: The policy
 * @nrq: The request to remove
 */
static void nrs_tbf_req_del(struct ptlrpc_nrs_policy *policy,
			     struct ptlrpc_nrs_request *nrq)
{
	struct nrs_tbf_head   *head;
	struct nrs_tbf_client *cli;

	assert_spin_locked(&policy->pol_nrs->nrs_svcpt->scp_req_lock);

	cli = container_of(nrs_request_resource(nrq),
			   struct nrs_tbf_client, tc_res);
	head = container_of(nrs_request_resource(nrq)->res_parent,
			    struct nrs_tbf_head, th_res);

	LASSERT(!list_empty(&nrq->nr_u.tbf.tr_list));
	list_del_init(&nrq->nr_u.tbf.tr_list);
	if (list_empty(&cli->tc_list)) {
		binheap_remove(head->th_binheap,
			       &cli->tc_node);
		clear_bit(NRS_TBF_CLI_HEAP_BIT, &cli->tc_state);
	} else {
		binheap_relocate(head->th_binheap,
				 &cli->tc_node);
	}
}

/**
 * nrs_tbf_req_stop() - Prints a debug statement right before the request
 * @nrq stops being handled.
 * @policy: The policy handling the request
 * @nrq: The request being handled
 *
 * ptlrpc_server_finish_request()
 * ptlrpc_nrs_req_stop_nolock()
 */
static void nrs_tbf_req_stop(struct ptlrpc_nrs_policy *policy,
			      struct ptlrpc_nrs_request *nrq)
{
	struct ptlrpc_request *req = container_of(nrq, struct ptlrpc_request,
						  rq_nrq);

	assert_spin_locked(&policy->pol_nrs->nrs_svcpt->scp_req_lock);

	CDEBUG(D_RPCTRACE, "NRS stop %s request from %s, seq: %llu\n",
	       policy->pol_desc->pd_name, libcfs_idstr(&req->rq_peer),
	       nrq->nr_u.tbf.tr_sequence);
}

/*
 * debugfs interface
 */

/*
 * The maximum RPC rate.
 */
#define LPROCFS_NRS_RATE_MAX		1000000ULL	/* 1rpc/us */

static int
ptlrpc_lprocfs_nrs_tbf_rule_seq_show(struct seq_file *m, void *data)
{
	struct ptlrpc_service	    *svc = m->private;
	int			     rc;

	seq_printf(m, "regular_requests:\n");
	/**
	 * Perform two separate calls to this as only one of the NRS heads'
	 * policies may be in the ptlrpc_nrs_pol_state::NRS_POL_STATE_STARTED or
	 * ptlrpc_nrs_pol_state::NRS_POL_STATE_STOPPING state.
	 */
	rc = ptlrpc_nrs_policy_control(svc, PTLRPC_NRS_QUEUE_REG,
				       NRS_POL_NAME_TBF,
				       NRS_CTL_TBF_RD_RULE,
				       false, m);
	if (rc == 0) {
		/**
		 * -ENOSPC means buf in the parameter m is overflow, return 0
		 * here to let upper layer function seq_read alloc a larger
		 * memory area and do this process again.
		 */
	} else if (rc == -ENOSPC) {
		return 0;

		/**
		 * Ignore -ENODEV as the regular NRS head's policy may be in the
		 * ptlrpc_nrs_pol_state::NRS_POL_STATE_STOPPED state.
		 */
	} else if (rc != -ENODEV) {
		return rc;
	}

	if (!nrs_svc_has_hp(svc))
		goto no_hp;

	seq_printf(m, "high_priority_requests:\n");
	rc = ptlrpc_nrs_policy_control(svc, PTLRPC_NRS_QUEUE_HP,
				       NRS_POL_NAME_TBF,
				       NRS_CTL_TBF_RD_RULE,
				       false, m);
	if (rc == 0) {
		/**
		 * -ENOSPC means buf in the parameter m is overflow, return 0
		 * here to let upper layer function seq_read alloc a larger
		 * memory area and do this process again.
		 */
	} else if (rc == -ENOSPC) {
		return 0;
	}

no_hp:

	return rc;
}

static int nrs_tbf_id_parse(struct nrs_tbf_cmd *cmd, char *token)
{
	int rc;
	ENTRY;

	if (!nrs_tbf_flags_valid(cmd->u.tc_start.ts_valid_type))
		RETURN(-EINVAL);

	OBD_STRNDUP(cmd->u.tc_start.ts_conds_str, token, strlen(token));
	if (cmd->u.tc_start.ts_conds_str == NULL)
		return -ENOMEM;

	/* Parse hybird NID and JOBID conditions */
	rc = nrs_tbf_conds_parse(cmd->u.tc_start.ts_valid_type,
				 cmd->u.tc_start.ts_conds_str,
				 &cmd->u.tc_start.ts_conds);

	if (rc)
		OBD_FREE_STR(cmd->u.tc_start.ts_conds_str);

	RETURN(rc);
}

static void nrs_tbf_cmd_fini(struct nrs_tbf_cmd *cmd)
{
	if (cmd->tc_cmd != NRS_CTL_TBF_START_RULE)
		return;

	if (!nrs_tbf_flags_valid(cmd->u.tc_start.ts_valid_type)) {
		CWARN("unknown NRS_TBF_FLAGS:0x%x\n",
		      cmd->u.tc_start.ts_valid_type);
		return;
	}

	if (!list_empty(&cmd->u.tc_start.ts_conds))
		nrs_tbf_conds_free(&cmd->u.tc_start.ts_conds);
	OBD_FREE_STR(cmd->u.tc_start.ts_conds_str);
}

static int check_rule_name(const char *name)
{
	int i;

	if (name[0] == '\0')
		return -EINVAL;

	for (i = 0; name[i] != '\0' && i < MAX_TBF_NAME; i++) {
		if (!isalnum(name[i]) && name[i] != '_')
			return -EINVAL;
	}

	if (i == MAX_TBF_NAME)
		return -ENAMETOOLONG;

	return 0;
}

static int
nrs_tbf_parse_value_pair(struct nrs_tbf_cmd *cmd, char *buffer)
{
	char	*key;
	char	*val;
	int	 rc;
	__u64	 rate;

	val = buffer;
	key = strsep(&val, "=");
	if (val == NULL || strlen(val) == 0)
		return -EINVAL;

	/* Key of the value pair */
	if (strcmp(key, "rate") == 0) {
		rc = kstrtoull(val, 10, &rate);
		if (rc)
			return rc;

		if (rate <= 0 || rate >= LPROCFS_NRS_RATE_MAX)
			return -EINVAL;

		if (cmd->tc_cmd == NRS_CTL_TBF_START_RULE)
			cmd->u.tc_start.ts_rpc_rate = rate;
		else if (cmd->tc_cmd == NRS_CTL_TBF_CHANGE_RULE)
			cmd->u.tc_change.tc_rpc_rate = rate;
		else
			return -EINVAL;
	}  else if (strcmp(key, "rank") == 0) {
		rc = check_rule_name(val);
		if (rc)
			return rc;

		if (cmd->tc_cmd == NRS_CTL_TBF_START_RULE)
			cmd->u.tc_start.ts_next_name = val;
		else if (cmd->tc_cmd == NRS_CTL_TBF_CHANGE_RULE)
			cmd->u.tc_change.tc_next_name = val;
		else
			return -EINVAL;
	} else if (strcmp(key, "realtime") == 0) {
		unsigned long realtime;

		rc = kstrtoul(val, 10, &realtime);
		if (rc)
			return rc;

		if (realtime > 0)
			cmd->u.tc_start.ts_rule_flags |= NTRS_REALTIME;
	} else {
		return -EINVAL;
	}
	return 0;
}

static int
nrs_tbf_parse_value_pairs(struct nrs_tbf_cmd *cmd, char *buffer)
{
	char	*val;
	char	*token;
	int	 rc;

	val = buffer;
	while (val != NULL && strlen(val) != 0) {
		token = strsep(&val, " ");
		rc = nrs_tbf_parse_value_pair(cmd, token);
		if (rc)
			return rc;
	}

	switch (cmd->tc_cmd) {
	case NRS_CTL_TBF_START_RULE:
		if (cmd->u.tc_start.ts_rpc_rate == 0)
			cmd->u.tc_start.ts_rpc_rate = tbf_rate;
		break;
	case NRS_CTL_TBF_CHANGE_RULE:
		if (cmd->u.tc_change.tc_rpc_rate == 0 &&
		    cmd->u.tc_change.tc_next_name == NULL)
			return -EINVAL;
		break;
	case NRS_CTL_TBF_STOP_RULE:
		break;
	default:
		return -EINVAL;
	}
	return 0;
}

static struct nrs_tbf_cmd *
nrs_tbf_parse_cmd(char *buffer, unsigned long count, __u32 type_flag)
{
	struct nrs_tbf_cmd *cmd;
	char *token;
	char *val;
	int rc = 0;

	OBD_ALLOC_PTR(cmd);
	if (cmd == NULL)
		GOTO(out, rc = -ENOMEM);
	memset(cmd, 0, sizeof(*cmd));

	val = buffer;
	token = strsep(&val, " ");
	if (val == NULL || strlen(val) == 0)
		GOTO(out_free_cmd, rc = -EINVAL);

	/* Type of the command */
	if (strcmp(token, "start") == 0) {
		cmd->tc_cmd = NRS_CTL_TBF_START_RULE;
		cmd->u.tc_start.ts_valid_type = type_flag;
	} else if (strcmp(token, "stop") == 0)
		cmd->tc_cmd = NRS_CTL_TBF_STOP_RULE;
	else if (strcmp(token, "change") == 0)
		cmd->tc_cmd = NRS_CTL_TBF_CHANGE_RULE;
	else
		GOTO(out_free_cmd, rc = -EINVAL);

	/* Name of the rule */
	token = strsep(&val, " ");
	if ((val == NULL && cmd->tc_cmd != NRS_CTL_TBF_STOP_RULE))
		GOTO(out_free_cmd, rc = -EINVAL);

	rc = check_rule_name(token);
	if (rc)
		GOTO(out_free_cmd, rc);

	cmd->tc_name = token;

	if (cmd->tc_cmd == NRS_CTL_TBF_START_RULE) {
		/* List of ID */
		LASSERT(val);
		token = val;
		val = strrchr(token, '}');
		if (!val)
			GOTO(out_free_cmd, rc = -EINVAL);

		/* Skip '}' */
		val++;
		if (*val == '\0') {
			val = NULL;
		} else if (*val == ' ') {
			*val = '\0';
			val++;
		} else
			GOTO(out_free_cmd, rc = -EINVAL);

		rc = nrs_tbf_id_parse(cmd, token);
		if (rc)
			GOTO(out_free_cmd, rc);
	}

	rc = nrs_tbf_parse_value_pairs(cmd, val);
	if (rc)
		GOTO(out_cmd_fini, rc = -EINVAL);
	goto out;
out_cmd_fini:
	nrs_tbf_cmd_fini(cmd);
out_free_cmd:
	OBD_FREE_PTR(cmd);
out:
	if (rc)
		cmd = ERR_PTR(rc);
	return cmd;
}

/**
 * nrs_tbf_type_flag() - Get the TBF policy type (nid, jobid, etc) preset by
 * proc entry 'nrs_policies' for command buffer parsing.
 * @svc: the PTLRPC service
 * @queue: the NRS queue type
 *
 * Return the preset TBF policy type flag
 */
static __u32
nrs_tbf_type_flag(struct ptlrpc_service *svc, enum ptlrpc_nrs_queue_type queue)
{
	__u32	type;
	int	rc;

	rc = ptlrpc_nrs_policy_control(svc, queue,
				       NRS_POL_NAME_TBF,
				       NRS_CTL_TBF_RD_TYPE_FLAG,
				       true, &type);
	if (rc != 0)
		type = NRS_TBF_FLAG_INVALID;

	return type;
}

#define LPROCFS_WR_NRS_TBF_MAX_CMD (4096)
static ssize_t
ptlrpc_lprocfs_nrs_tbf_rule_seq_write(struct file *file,
				      const char __user *buffer,
				      size_t count, loff_t *off)
{
	struct seq_file *m = file->private_data;
	struct ptlrpc_service *svc = m->private;
	char *kernbuf;
	char *val;
	int rc;
	struct nrs_tbf_cmd *cmd;
	enum ptlrpc_nrs_queue_type queue = PTLRPC_NRS_QUEUE_BOTH;
	unsigned long length;
	char *token;

	OBD_ALLOC(kernbuf, LPROCFS_WR_NRS_TBF_MAX_CMD);
	if (kernbuf == NULL)
		GOTO(out, rc = -ENOMEM);

	if (count > LPROCFS_WR_NRS_TBF_MAX_CMD - 1)
		GOTO(out_free_kernbuff, rc = -EINVAL);

	if (copy_from_user(kernbuf, buffer, count))
		GOTO(out_free_kernbuff, rc = -EFAULT);

	val = kernbuf;
	token = strsep(&val, " ");
	if (val == NULL)
		GOTO(out_free_kernbuff, rc = -EINVAL);

	if (strcmp(token, "reg") == 0) {
		queue = PTLRPC_NRS_QUEUE_REG;
	} else if (strcmp(token, "hp") == 0) {
		queue = PTLRPC_NRS_QUEUE_HP;
	} else {
		kernbuf[strlen(token)] = ' ';
		val = kernbuf;
	}
	length = strlen(val);

	if (length == 0)
		GOTO(out_free_kernbuff, rc = -EINVAL);

	if (queue == PTLRPC_NRS_QUEUE_HP && !nrs_svc_has_hp(svc))
		GOTO(out_free_kernbuff, rc = -ENODEV);
	else if (queue == PTLRPC_NRS_QUEUE_BOTH && !nrs_svc_has_hp(svc))
		queue = PTLRPC_NRS_QUEUE_REG;

	cmd = nrs_tbf_parse_cmd(val, length, nrs_tbf_type_flag(svc, queue));
	if (IS_ERR(cmd))
		GOTO(out_free_kernbuff, rc = PTR_ERR(cmd));

	/**
	 * Serialize NRS core lprocfs operations with policy registration/
	 * unregistration.
	 */
	mutex_lock(&nrs_core.nrs_mutex);
	rc = ptlrpc_nrs_policy_control(svc, queue,
				       NRS_POL_NAME_TBF,
				       NRS_CTL_TBF_WR_RULE,
				       false, cmd);
	mutex_unlock(&nrs_core.nrs_mutex);

	nrs_tbf_cmd_fini(cmd);
	OBD_FREE_PTR(cmd);
out_free_kernbuff:
	OBD_FREE(kernbuf, LPROCFS_WR_NRS_TBF_MAX_CMD);
out:
	return rc ? rc : count;
}

LDEBUGFS_SEQ_FOPS(ptlrpc_lprocfs_nrs_tbf_rule);

/**
 * nrs_tbf_lprocfs_init() - Initializes a TBF policy's lprocfs interface for
 * service @svc
 * @svc: the service (Portal RPC)
 *
 * Return
 * * %0 on success
 * * %!=0 on error
 */
static int nrs_tbf_lprocfs_init(struct ptlrpc_service *svc)
{
	struct ldebugfs_vars nrs_tbf_lprocfs_vars[] = {
		{ .name		= "nrs_tbf_rule",
		  .fops		= &ptlrpc_lprocfs_nrs_tbf_rule_fops,
		  .data = svc },
		{ NULL }
	};

	if (!svc->srv_debugfs_entry)
		return 0;

	ldebugfs_add_vars(svc->srv_debugfs_entry, nrs_tbf_lprocfs_vars, NULL);

	return 0;
}

/*
 * TBF policy operations
 */
static const struct ptlrpc_nrs_pol_ops nrs_tbf_ops = {
	.op_policy_start	= nrs_tbf_start,
	.op_policy_stop		= nrs_tbf_stop,
	.op_policy_ctl		= nrs_tbf_ctl,
	.op_res_get		= nrs_tbf_res_get,
	.op_res_put		= nrs_tbf_res_put,
	.op_req_get		= nrs_tbf_req_get,
	.op_req_enqueue		= nrs_tbf_req_add,
	.op_req_dequeue		= nrs_tbf_req_del,
	.op_req_stop		= nrs_tbf_req_stop,
	.op_lprocfs_init	= nrs_tbf_lprocfs_init,
};

/*
 * TBF policy configuration
 */
struct ptlrpc_nrs_pol_conf nrs_conf_tbf = {
	.nc_name		= NRS_POL_NAME_TBF,
	.nc_ops			= &nrs_tbf_ops,
	.nc_compat		= nrs_policy_compat_all,
};