// SPDX-License-Identifier: GPL-2.0

/*
 * Copyright (c) 2012, 2017, Intel Corporation.
 * Use is subject to license terms.
 */

/*
 * Author: Johann Lombardi <johann.lombardi@intel.com>
 * Author: Niu    Yawei    <yawei.niu@intel.com>
 */

#define DEBUG_SUBSYSTEM S_LQUOTA

#include <linux/delay.h>
#include <linux/kthread.h>
#include <linux/workqueue.h>

#include <lustre_dlm.h>
#include <lustre_swab.h>
#include <obd_class.h>

#include "qmt_internal.h"

/* intent policy function called from mdt_intent_opc() when the intent is of
 * quota type */
int qmt_intent_policy(const struct lu_env *env, struct lu_device *ld,
		      struct ptlrpc_request *req, struct ldlm_lock **lockp,
		      int flags)
{
	struct qmt_device	*qmt = lu2qmt_dev(ld);
	struct ldlm_intent	*it;
	struct quota_body	*reqbody;
	struct quota_body	*repbody;
	struct obd_uuid		*uuid;
	struct lquota_lvb	*lvb;
	struct ldlm_resource	*res = (*lockp)->l_resource;
	struct ldlm_reply	*ldlm_rep;
	int			 rc, lvb_len;
	ENTRY;

	req_capsule_extend(&req->rq_pill, &RQF_LDLM_INTENT_QUOTA);
	req_capsule_set_size(&req->rq_pill, &RMF_DLM_LVB, RCL_SERVER,
			     ldlm_lvbo_size(*lockp));

	/* extract quota body and intent opc */
	it = req_capsule_client_get(&req->rq_pill, &RMF_LDLM_INTENT);
	if (it == NULL)
		RETURN(err_serious(-EFAULT));

	reqbody = req_capsule_client_get(&req->rq_pill, &RMF_QUOTA_BODY);
	if (reqbody == NULL)
		RETURN(err_serious(-EFAULT));

	/* prepare reply */
	rc = req_capsule_server_pack(&req->rq_pill);
	if (rc != 0) {
		CERROR("Can't pack response, rc %d\n", rc);
		RETURN(err_serious(rc));
	}

	repbody = req_capsule_server_get(&req->rq_pill, &RMF_QUOTA_BODY);
	if (repbody == NULL)
		RETURN(err_serious(-EFAULT));

	ldlm_rep = req_capsule_server_get(&req->rq_pill, &RMF_DLM_REP);
	if (ldlm_rep == NULL)
		RETURN(err_serious(-EFAULT));

	uuid = &(*lockp)->l_export->exp_client_uuid;
	switch (it->opc) {

	case IT_QUOTA_DQACQ: {
		struct lquota_entry *lqe;
		struct ldlm_lock *lock;
		enum qmt_stype stype;
		int idx;

		if (res->lr_name.name[LUSTRE_RES_ID_QUOTA_SEQ_OFF] == 0)
			/* acquire on global lock? something is wrong ... */
			GOTO(out, rc = -EPROTO);

		/* verify global lock isn't stale */
		if (!lustre_handle_is_used(&reqbody->qb_glb_lockh))
			GOTO(out, rc = -ENOLCK);

		lock = ldlm_handle2lock(&reqbody->qb_glb_lockh);
		if (lock == NULL)
			GOTO(out, rc = -ENOLCK);
		ldlm_lock_put(lock);

		stype = qmt_uuid2idx(uuid, &idx);
		if (stype < 0)
			GOTO(out, rc = -EINVAL);

		/* TODO: it seems we don't need to get lqe from
		 * lq_lvb_data anymore ... And do extra get
		 * and put on it */
		lqe = res->lr_lvb_data;
		LASSERT(lqe != NULL);
		lqe_getref(lqe);

		rc = qmt_pool_lqes_lookup(env, qmt, lqe_rtype(lqe), stype,
					  lqe_qtype(lqe), &reqbody->qb_id,
					  NULL, idx);
		if (rc) {
			lqe_putref(lqe);
			GOTO(out, rc);
		}

		/* acquire quota space */
		rc = qmt_dqacq0(env, qmt, uuid,
				reqbody->qb_flags, reqbody->qb_count,
				reqbody->qb_usage, repbody,
				qmt_dom(lqe_rtype(lqe), stype) ? -1 : idx);
		lqe_putref(lqe);
		qti_lqes_fini(env);
		if (rc)
			GOTO(out, rc);
		break;
	}

	case IT_QUOTA_CONN:
		/* new connection from slave */

		if (res->lr_name.name[LUSTRE_RES_ID_QUOTA_SEQ_OFF] != 0)
			/* connection on per-ID lock? something is wrong ... */
			GOTO(out, rc = -EPROTO);

		rc = qmt_pool_new_conn(env, qmt, &reqbody->qb_fid,
				       &repbody->qb_slv_fid,
				       &repbody->qb_slv_ver, uuid);
		if (rc)
			GOTO(out, rc);
		break;

	default:
		CERROR("%s: invalid intent opcode: %llu\n", qmt->qmt_svname,
		       it->opc);
		GOTO(out, rc = -EINVAL);
	}

	/* on success, pack lvb in reply */
	lvb = req_capsule_server_get(&req->rq_pill, &RMF_DLM_LVB);
	lvb_len = ldlm_lvbo_size(*lockp);
	lvb_len = ldlm_lvbo_fill(*lockp, lvb, &lvb_len);
	if (lvb_len < 0)
		GOTO(out, rc = lvb_len);

	req_capsule_shrink(&req->rq_pill, &RMF_DLM_LVB, lvb_len, RCL_SERVER);
out:
	ldlm_rep->lock_policy_res2 = clear_serious(rc);
	EXIT;
	return ELDLM_OK;
}

/*
 * Initialize quota LVB associated with quota indexes.
 * Called with res->lr_lvb_sem held
 */
int qmt_lvbo_init(struct lu_device *ld, struct ldlm_resource *res)
{
	struct lu_env *env;
	struct qmt_thread_info *qti;
	struct qmt_device *qmt = lu2qmt_dev(ld);
	enum lquota_res_type pool_type;
	enum lquota_type qtype;
	int rc;
	ENTRY;

	LASSERT(res != NULL);

	if (res->lr_type != LDLM_PLAIN)
		RETURN(-ENOTSUPP);

	if (res->lr_lvb_data ||
	    res->lr_name.name[LUSTRE_RES_ID_SEQ_OFF] != FID_SEQ_QUOTA_GLB)
		RETURN(0);

	env = lu_env_find();
	LASSERT(env);
	qti = qmt_info(env);

	/* extract global index FID and quota identifier */
	fid_extract_from_quota_res(&qti->qti_fid, &qti->qti_id, &res->lr_name);

	/* sanity check the global index FID */
	rc = lquota_extract_fid(&qti->qti_fid, &pool_type, &qtype);
	if (rc) {
		CERROR("can't extract glb index information from FID "DFID"\n",
		       PFID(&qti->qti_fid));
		GOTO(out, rc);
	}

	if (res->lr_name.name[LUSTRE_RES_ID_QUOTA_SEQ_OFF] != 0) {
		/* no ID quota lock associated with UID/GID 0 or with a seq 0,
		 * we are thus dealing with an ID lock. */
		struct qmt_pool_info	*pool;
		struct lquota_entry	*lqe;
		struct lqe_glbl_data	*lgd;
		int wait = 0;

		pool = qmt_pool_lookup_glb(env, qmt, pool_type);
		if (IS_ERR(pool))
			GOTO(out, rc = -ENOMEM);

		/* Find the quota entry associated with the quota id */
		lqe = qmt_pool_lqe_lookup(env, qmt, pool_type, qtype,
					  &qti->qti_id, NULL);
		if (IS_ERR(lqe)) {
			qpi_putref(env, pool);
			GOTO(out, rc = PTR_ERR(lqe));
		}

again:
		mutex_lock(&lqe->lqe_glbl_data_lock);
		/* Is the old lqe_glbl_data still waiting to be freed in
		 * qmt_lvbo_free_wq?
		 */
		if (lqe->lqe_glbl_data) {
			mutex_unlock(&lqe->lqe_glbl_data_lock);

			wait++;
			/* wait one second */
			if (wait < 1000) {
				msleep_interruptible(1);
				goto again;
			}

			LQUOTA_ERROR(lqe, "the lvb is held by qmt_wq: %p\n",
				     res->lr_lvb_data);
			lqe_putref(lqe);
			GOTO(out_put_qpi, rc = -EBUSY);
		}

		/* TODO: need something like qmt_extend_lqe_gd that has
		 * to be called each time when qpi_slv_nr is incremented
		 */
		lgd = qmt_alloc_lqe_gd(pool, qtype);
		if (!lgd) {
			mutex_unlock(&lqe->lqe_glbl_data_lock);
			lqe_putref(lqe);
			GOTO(out_put_qpi, rc = -ENOMEM);
		}

		qmt_setup_lqe_gd(env, qmt, lqe, lgd, pool_type);
		lqe->lqe_glbl_data = lgd;
		mutex_unlock(&lqe->lqe_glbl_data_lock);

		qmt_id_lock_notify(qmt, lqe);

		/* store reference to lqe in lr_lvb_data */
		res->lr_lvb_data = lqe;
		LQUOTA_DEBUG(lqe, "initialized res lvb");

out_put_qpi:
		qpi_putref(env, pool);
	} else {
		struct dt_object	*obj;

		/* lookup global index */
		obj = dt_locate(env, qmt->qmt_child, &qti->qti_fid);
		if (IS_ERR(obj))
			GOTO(out, rc = PTR_ERR(obj));
		if (!dt_object_exists(obj)) {
			dt_object_put(env, obj);
			GOTO(out, rc = -ENOENT);
		}

		/* store reference to global index object in lr_lvb_data */
		res->lr_lvb_data = obj;
		CDEBUG(D_QUOTA, DFID" initialized lvb\n", PFID(&qti->qti_fid));
	}

	res->lr_lvb_len = sizeof(struct lquota_lvb);
	EXIT;
out:
	return rc;
}

/* clear lge_qunit/edquot_nu flags -
 * slave received new qunit and edquot.
 *
 * \retval	true if revoke is needed - qunit
 *		for this slave reaches least_qunit
 */
static bool qmt_clear_lgeg_arr_nu(struct lquota_entry *lqe, int stype, int idx)
{
	unsigned long least = lqe2qpi(lqe)->qpi_least_qunit;
	bool revoke = false;

	/* There is no array to store lge for the case of DOM.
	 * Ignore it until MDT pools will be ready.
	 */
	if (!qmt_dom(lqe_rtype(lqe), stype)) {
		struct lqe_glbl_data *lgd;

		mutex_lock(&lqe->lqe_glbl_data_lock);
		lgd = lqe->lqe_glbl_data;
		if (lgd) {
			int lge_idx = qmt_map_lge_idx(lgd, idx);

			if (lge_idx < 0)
				return false;
			lgd->lqeg_arr[lge_idx].lge_qunit_nu = 0;
			lgd->lqeg_arr[lge_idx].lge_edquot_nu = 0;
			/* We shouldn't call revoke for DOM case, it will be
			 * updated at qmt_id_lock_glimpse.
			 */
			revoke = lgd->lqeg_arr[lge_idx].lge_qunit == least;
		}
		mutex_unlock(&lqe->lqe_glbl_data_lock);
	}

	return revoke;
}

static bool qmt_set_revoke(struct lu_env *env, struct lquota_entry *lqe_gl,
			  int stype, int idx)
{
	unsigned long least_qunit = lqe2qpi(lqe_gl)->qpi_least_qunit;
	bool notify = false;

	if (qmt_dom(lqe_rtype(lqe_gl), stype))
		return false;

	qti_lqes_write_lock(env);
	mutex_lock(&lqe_gl->lqe_glbl_data_lock);
	if (lqe_gl->lqe_glbl_data) {
		struct lqe_glbl_data *lgd = lqe_gl->lqe_glbl_data;
		int lge_idx;

		lge_idx = qmt_map_lge_idx(lgd, idx);
		LASSERT(lge_idx >= 0);
		if (lgd->lqeg_arr[lge_idx].lge_qunit == least_qunit) {
			struct lquota_entry *lqe;
			int i;

			for (i = 0; i < qti_lqes_cnt(env); i++) {
				lqe = qti_lqes(env)[i];
				LQUOTA_DEBUG(lqe,
					     "lge_qunit %llu least_qunit %lu idx %d\n",
					     lgd->lqeg_arr[lge_idx].lge_qunit,
					     least_qunit, idx);
				if (lqe->lqe_qunit == least_qunit) {
					lqe->lqe_revoke_time =
							ktime_get_seconds();
					notify |= qmt_adjust_edquot(lqe,
						  ktime_get_real_seconds());
				}
			}
		}
	}
	mutex_unlock(&lqe_gl->lqe_glbl_data_lock);
	qti_lqes_write_unlock(env);

	return notify;
}

/*
 * Update LVB associated with the global quota index.
 * This function is called from the DLM itself after a glimpse callback, in this
 * case valid ptlrpc request is passed.
 */
int qmt_lvbo_update(struct lu_device *ld, struct ldlm_resource *res,
		    struct ptlrpc_request *req, int increase_only)
{
	struct lu_env *env;
	struct qmt_thread_info *qti;
	struct qmt_device *qmt = lu2qmt_dev(ld);
	struct lquota_entry *lqe;
	struct lquota_lvb *lvb;
	struct ldlm_lock *lock;
	struct obd_export *exp;
	bool need_revoke;
	enum qmt_stype stype;
	int rc = 0, idx;
	ENTRY;

	LASSERT(res != NULL);

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

	if (res->lr_name.name[LUSTRE_RES_ID_QUOTA_SEQ_OFF] == 0)
		/* no need to update lvb for global quota locks */
		RETURN(0);

	lvb = req_capsule_server_swab_get(&req->rq_pill, &RMF_DLM_LVB,
					  lustre_swab_lquota_lvb);
	if (lvb == NULL) {
		CERROR("%s: failed to extract lvb from request\n",
		       qmt->qmt_svname);
		RETURN(-EFAULT);
	}

	lqe = res->lr_lvb_data;
	LASSERT(lqe != NULL);
	lqe_getref(lqe);

	/* allocate environement */
	env = lu_env_find();
	LASSERT(env);
	qti = qmt_info(env);

	/* The request is a glimpse callback which was sent via the
	 * reverse import to the slave. What we care about here is the
	 * export associated with the slave and req->rq_export is
	 * definitely not what we are looking for (it is actually set to
	 * NULL here).
	 * Therefore we extract the lock from the request argument
	 * and use lock->l_export. */
	lock = ldlm_request_lock(req);
	if (IS_ERR(lock)) {
		CERROR("%s: failed to get lock from request!\n",
		       qmt->qmt_svname);
		GOTO(out, rc = PTR_ERR(lock));
	}

	exp = class_export_get(lock->l_export);
	if (exp == NULL) {
		CERROR("%s: failed to get export from lock!\n",
		       qmt->qmt_svname);
		GOTO(out, rc = -EFAULT);
	}

	stype = qmt_uuid2idx(&exp->exp_client_uuid, &idx);
	if (stype < 0)
		GOTO(out_exp, rc = stype);

	need_revoke = qmt_clear_lgeg_arr_nu(lqe, stype, idx);
	if (lvb->lvb_id_rel == 0) {
		/* nothing to release */
		if (lvb->lvb_id_may_rel != 0) {
			/* but might still release later ... */
			lqe_write_lock(lqe);
			lqe->lqe_may_rel += lvb->lvb_id_may_rel;
			lqe_write_unlock(lqe);
		}
	}

	if (!need_revoke && lvb->lvb_id_rel == 0)
		GOTO(out_exp, rc = 0);

	rc = qmt_pool_lqes_lookup(env, qmt, lqe_rtype(lqe), stype,
				  lqe_qtype(lqe), &lqe->lqe_id, NULL, idx);
	if (rc)
		GOTO(out_exp, rc);

	if (need_revoke && qmt_set_revoke(env, lqe, stype, idx)) {
		int notify = false;

		mutex_lock(&lqe->lqe_glbl_data_lock);
		if (lqe->lqe_glbl_data) {
			qmt_seed_glbe_edquot(env, lqe->lqe_glbl_data);
			notify = true;
		}
		mutex_unlock(&lqe->lqe_glbl_data_lock);
		if (notify)
			qmt_id_lock_notify(qmt, lqe);
	}

	if (lvb->lvb_id_rel) {
		LQUOTA_DEBUG(lqe, "releasing:%llu may release:%llu",
			     lvb->lvb_id_rel, lvb->lvb_id_may_rel);

		/* release quota space */
		rc = qmt_dqacq0(env, qmt, &exp->exp_client_uuid,
				QUOTA_DQACQ_FL_REL, lvb->lvb_id_rel,
				0, &qti->qti_body,
				qmt_dom(lqe_rtype(lqe), stype) ? -1 : idx);
		if (rc || qti->qti_body.qb_count != lvb->lvb_id_rel)
			LQUOTA_ERROR(lqe,
				     "failed to release quota space on glimpse %llu!=%llu : rc = %d\n",
				     qti->qti_body.qb_count,
				     lvb->lvb_id_rel, rc);
	}
	qti_lqes_fini(env);
	if (rc)
		GOTO(out_exp, rc);
	EXIT;
out_exp:
	class_export_put(exp);
out:
	lqe_putref(lqe);
	return rc;
}

/*
 * Report size of lvb to ldlm layer in order to allocate lvb buffer
 * As far as quota locks are concerned, the size is static and is the same
 * for both global and per-ID locks which shares the same lvb format.
 */
int qmt_lvbo_size(struct lu_device *ld, struct ldlm_lock *lock)
{
	return sizeof(struct lquota_lvb);
}

/*
 * Fill request buffer with quota lvb
 */
int qmt_lvbo_fill(struct lu_device *ld, struct ldlm_lock *lock, void *lvb,
		  int lvblen)
{
	struct ldlm_resource *res = lock->l_resource;
	struct lquota_lvb *qlvb = lvb;
	struct lu_env *env;
	int rc;
	ENTRY;

	LASSERT(res != NULL);
	rc = 0;

	if (res->lr_type != LDLM_PLAIN || res->lr_lvb_data == NULL ||
	    res->lr_name.name[LUSTRE_RES_ID_SEQ_OFF] != FID_SEQ_QUOTA_GLB)
		RETURN(-EINVAL);

	env = lu_env_find();
	LASSERT(env);

	if (res->lr_name.name[LUSTRE_RES_ID_QUOTA_SEQ_OFF] != 0) {
		/* no ID quota lock associated with UID/GID 0 or with a seq 0,
		 * we are thus dealing with an ID lock. */
		struct lquota_entry *lqe = res->lr_lvb_data;
		struct qmt_device *qmt;
		struct obd_uuid *uuid;
		enum qmt_stype stype;
		int idx;

		uuid = &(lock)->l_export->exp_client_uuid;
		stype = qmt_uuid2idx(uuid, &idx);
		if (stype < 0)
			RETURN(stype);
		qmt = lu2qmt_dev(ld);
		/* return current qunit value & edquot flags in lvb */
		lqe_getref(lqe);
		rc = qmt_pool_lqes_lookup(env, qmt, lqe_rtype(lqe), stype,
					  lqe_qtype(lqe), &lqe->lqe_id,
					  NULL, idx);
		if (!rc) {
			qlvb->lvb_id_qunit = qti_lqes_min_qunit(env);
			qlvb->lvb_flags = 0;
			if (qti_lqes_edquot(env))
				qlvb->lvb_flags = LQUOTA_FL_EDQUOT;
			qti_lqes_fini(env);
		}
		CDEBUG(D_QUOTA, "uuid %s lqe_id %lu, edquot %llu qunit %llu\n",
		       (char *)uuid, (unsigned long)lqe->lqe_id.qid_uid,
		       qlvb->lvb_flags, qlvb->lvb_id_qunit);
		lqe_putref(lqe);
	} else {
		/* global quota lock */
		struct dt_object *obj = res->lr_lvb_data;

		/* return current version of global index */
		qlvb->lvb_glb_ver = dt_version_get(env, obj);
	}

	RETURN(rc = rc ?: sizeof(struct lquota_lvb));
}

/*
 * Free lvb associated with a given ldlm resource
 * we don't really allocate a lvb, lr_lvb_data just points to
 * the appropriate backend structures.
 */
int qmt_lvbo_free(struct lu_device *ld, struct ldlm_resource *res)
{
	struct qmt_device *qmt = lu2qmt_dev(ld);

	ENTRY;

	if (res->lr_lvb_data == NULL)
		RETURN(0);

	if (res->lr_name.name[LUSTRE_RES_ID_QUOTA_SEQ_OFF] != 0) {
		struct lquota_entry *lqe = res->lr_lvb_data;

		queue_work(qmt->qmt_lvbo_free_wq, &lqe->lqe_work);
	} else {
		struct dt_object *obj = res->lr_lvb_data;
		/* release object reference */
		dt_object_put(lu_env_find(), obj);
	}

	res->lr_lvb_data = NULL;
	res->lr_lvb_len  = 0;

	RETURN(0);
}

typedef int (*qmt_glimpse_cb_t)(struct ldlm_lock *, struct lquota_entry *);

struct qmt_gl_lock_array {
	unsigned long		  q_max;
	unsigned long		  q_cnt;
	struct ldlm_lock	**q_locks;
};

static void qmt_free_lock_array(struct qmt_gl_lock_array *array)
{
	int i;

	if (array->q_max == 0) {
		LASSERT(array->q_locks == NULL);
		return;
	}

	for (i = 0; i < array->q_cnt; i++) {
		LASSERT(array->q_locks[i]);
		ldlm_lock_put(array->q_locks[i]);
		array->q_locks[i] = NULL;
	}
	array->q_cnt = 0;
	OBD_FREE_PTR_ARRAY(array->q_locks, array->q_max);
	array->q_locks = NULL;
	array->q_max = 0;
}

static int qmt_alloc_lock_array(struct ldlm_resource *res,
				struct qmt_gl_lock_array *array,
				qmt_glimpse_cb_t cb, void *arg)
{
	struct lquota_entry *lqe = arg;
	struct list_head *pos;
	unsigned long count = 0;
	int fail_cnt = 0;
	ENTRY;

	LASSERT(!array->q_max && !array->q_cnt && !array->q_locks);
again:
	if (cb)
		mutex_lock(&lqe->lqe_glbl_data_lock);
	lock_res(res);
	/* scan list of granted locks */
	list_for_each(pos, &res->lr_granted) {
		struct ldlm_lock *lock;
		int rc;

		lock = list_entry(pos, struct ldlm_lock, l_res_link);
		LASSERT(lock->l_export);

		if (cb != NULL) {
			rc = cb(lock, arg);
			/* slave should not be notified */
			if (rc == 0)
				continue;
		}

		count++;
		if (array->q_max != 0 && array->q_cnt < array->q_max) {
			array->q_locks[array->q_cnt] = ldlm_lock_get(lock);
			array->q_cnt++;
		}
	}
	unlock_res(res);
	if (cb)
		mutex_unlock(&lqe->lqe_glbl_data_lock);

	if (count > array->q_max) {
		qmt_free_lock_array(array);
		if (++fail_cnt > 5)
			RETURN(-EAGAIN);
		/*
		 * allocate more slots in case of more qualified locks are
		 * found during next loop
		 */
		array->q_max = count + count / 2 + 10;
		count = 0;
		LASSERT(array->q_locks == NULL && array->q_cnt == 0);
		OBD_ALLOC_PTR_ARRAY(array->q_locks, array->q_max);
		if (array->q_locks == NULL) {
			array->q_max = 0;
			RETURN(-ENOMEM);
		}

		goto again;
	}
	if (unlikely(count == 0))
		qmt_free_lock_array(array);
	RETURN(0);
}

static void qmt_setup_id_desc(struct ldlm_lock *lock, union ldlm_gl_desc *desc,
			      struct lquota_entry *lqe)
{
	struct obd_uuid *uuid = &(lock)->l_export->exp_client_uuid;
	enum qmt_stype stype;
	int idx;
	__u64 qunit;
	bool edquot;

	stype = qmt_uuid2idx(uuid, &idx);
	LASSERT(stype >= 0);

	/* DOM case - set global lqe settings */
	if (qmt_dom(lqe_rtype(lqe), stype)) {
		edquot = lqe->lqe_edquot;
		qunit = lqe->lqe_qunit;
	} else {
		struct lqe_glbl_data *lgd;
		int lge_idx;

		mutex_lock(&lqe->lqe_glbl_data_lock);
		lgd = lqe->lqe_glbl_data;
		if (lgd) {
			lge_idx = qmt_map_lge_idx(lgd, idx);
			LASSERT(lge_idx >= 0);
			edquot = lgd->lqeg_arr[lge_idx].lge_edquot;
			qunit = lgd->lqeg_arr[lge_idx].lge_qunit;
		} else {
			edquot = lqe->lqe_edquot;
			qunit = lqe->lqe_qunit;
		}
		mutex_unlock(&lqe->lqe_glbl_data_lock);
	}

	/* fill glimpse descriptor with lqe settings */
	desc->lquota_desc.gl_flags = edquot ? LQUOTA_FL_EDQUOT : 0;
	desc->lquota_desc.gl_qunit = qunit;
	CDEBUG(D_QUOTA, "setup desc: stype %d idx %d, edquot %llu qunit %llu\n",
			 stype, idx, desc->lquota_desc.gl_flags,
			 desc->lquota_desc.gl_qunit);
}

/*
 * Send glimpse callback to slaves holding a lock on resource \res.
 * This is used to notify slaves of new quota settings or to claim quota space
 * back.
 *
 * \param env  - is the environment passed by the caller
 * \param qmt  - is the quota master target
 * \param res  - is the dlm resource associated with the quota object
 * \param desc - is the glimpse descriptor to pack in glimpse callback
 * \param cb   - is the callback function called on every lock and determine
 *               whether a glimpse should be issued
 * \param arg  - is an opaq parameter passed to the callback function
 */
static int qmt_glimpse_lock(const struct lu_env *env, struct qmt_device *qmt,
			    struct ldlm_resource *res, union ldlm_gl_desc *desc,
			    qmt_glimpse_cb_t cb, struct lquota_entry *lqe)
{
	union ldlm_gl_desc *descs = NULL;
	struct list_head *tmp, *pos;
	LIST_HEAD(gl_list);
	struct qmt_gl_lock_array locks;
	unsigned long i, locks_count;
	int rc = 0;
	ENTRY;

	memset(&locks, 0, sizeof(locks));
	rc = qmt_alloc_lock_array(res, &locks, cb, lqe);
	if (rc) {
		CERROR("%s: failed to allocate glimpse lock array (%d)\n",
		       qmt->qmt_svname, rc);
		RETURN(rc);
	}
	if (!locks.q_cnt) {
		CDEBUG(D_QUOTA, "%s: no granted locks to send glimpse\n",
		       qmt->qmt_svname);
		RETURN(0);
	}
	CDEBUG(D_QUOTA, "found granted locks %lu\n", locks.q_cnt);
	locks_count = locks.q_cnt;

	/* Use one desc for all works, when called from qmt_glb_lock_notify */
	if (cb && locks.q_cnt > 1) {
		/* TODO: think about to store this preallocated descs
		 * in lqe_global in lqeg_arr as a part of lqe_glbl_entry.
		 * The benefit is that we don't need to allocate/free
		 * and setup this descs each time. But the drawback is
		 * memory use (sizeof ldlm_gl_desc * OST_COUNT * user_number).
		 * for examfple it could be 88 * 256 * 10 000 about 225 MB. */
		OBD_ALLOC(descs,
			  sizeof(struct ldlm_gl_lquota_desc) * locks.q_cnt);
		if (!descs) {
			CERROR("%s: alloc glimpse lock array failed: rc = %d\n",
			       qmt->qmt_svname, rc);
			qmt_free_lock_array(&locks);
			RETURN(-ENOMEM);
		}
	}

	for (i = locks.q_cnt; i > 0; i--) {
		struct ldlm_glimpse_work *work;

		OBD_ALLOC_PTR(work);
		if (work == NULL) {
			CERROR("%s: failed to notify a lock.\n",
			       qmt->qmt_svname);
			continue;
		}

		if (cb) {
			if (descs)
				desc = &descs[i - 1];
			qmt_setup_id_desc(locks.q_locks[i - 1], desc, lqe);
			work->gl_interpret_data = lqe;
		}

		list_add_tail(&work->gl_list, &gl_list);
		work->gl_lock  = locks.q_locks[i - 1];
		work->gl_flags = 0;
		work->gl_desc  = desc;

		locks.q_locks[i - 1] = NULL;
		locks.q_cnt--;
	}

	qmt_free_lock_array(&locks);

	if (list_empty(&gl_list)) {
		CDEBUG(D_QUOTA, "%s: nobody to notify\n", qmt->qmt_svname);
		GOTO(out, rc = 0);
	}

	/* issue glimpse callbacks to all connected slaves */
	rc = ldlm_glimpse_locks(res, &gl_list);

	list_for_each_safe(pos, tmp, &gl_list) {
		struct ldlm_glimpse_work *work;

		work = list_entry(pos, struct ldlm_glimpse_work, gl_list);

		list_del(&work->gl_list);
		CERROR("%s: failed to notify %s of new quota settings\n",
		       qmt->qmt_svname,
		       obd_uuid2str(&work->gl_lock->l_export->exp_client_uuid));
		ldlm_lock_put(work->gl_lock);
		OBD_FREE_PTR(work);
	}
out:
	OBD_FREE(descs,
		 sizeof(struct ldlm_gl_lquota_desc) * locks_count);

	RETURN(rc);
}

/*
 * Send glimpse request to all global quota locks to push new quota setting to
 * slaves.
 *
 * \param env - is the environment passed by the caller
 * \param lqe - is the lquota entry which has new settings
 * \param ver - is the version associated with the setting change
 */
void qmt_glb_lock_notify(const struct lu_env *env, struct lquota_entry *lqe,
			 __u64 ver)
{
	struct qmt_thread_info	*qti = qmt_info(env);
	struct qmt_pool_info	*pool = lqe2qpi(lqe);
	struct ldlm_resource	*res = NULL;
	ENTRY;

	lquota_generate_fid(&qti->qti_fid, pool->qpi_rtype, lqe_qtype(lqe));

	/* send glimpse callback to notify slaves of new quota settings */
	qti->qti_gl_desc.lquota_desc.gl_id        = lqe->lqe_id;
	qti->qti_gl_desc.lquota_desc.gl_flags     = 0;
	if (lqe->lqe_is_default) {
		qti->qti_gl_desc.lquota_desc.gl_hardlimit = 0;
		qti->qti_gl_desc.lquota_desc.gl_softlimit = 0;
		qti->qti_gl_desc.lquota_desc.gl_time = LQUOTA_GRACE_FLAG(0,
							LQUOTA_FLAG_DEFAULT);

	} else if (lqe->lqe_is_deleted) {
		qti->qti_gl_desc.lquota_desc.gl_hardlimit = 0;
		qti->qti_gl_desc.lquota_desc.gl_softlimit = 0;
		qti->qti_gl_desc.lquota_desc.gl_time = LQUOTA_GRACE_FLAG(0,
							LQUOTA_FLAG_DELETED);
	} else if (lqe->lqe_is_reset) {
		qti->qti_gl_desc.lquota_desc.gl_hardlimit = lqe->lqe_hardlimit;
		qti->qti_gl_desc.lquota_desc.gl_softlimit = lqe->lqe_softlimit;
		qti->qti_gl_desc.lquota_desc.gl_time = LQUOTA_GRACE_FLAG(0,
							LQUOTA_FLAG_RESET);
	} else if (lqe->lqe_granted > lqe->lqe_hardlimit) {
		qti->qti_gl_desc.lquota_desc.gl_hardlimit = lqe->lqe_hardlimit;
		qti->qti_gl_desc.lquota_desc.gl_softlimit = lqe->lqe_softlimit;
		qti->qti_gl_desc.lquota_desc.gl_time = LQUOTA_GRACE_FLAG(0,
							LQUOTA_FLAG_REVOKE);
	} else {
		qti->qti_gl_desc.lquota_desc.gl_hardlimit = lqe->lqe_hardlimit;
		qti->qti_gl_desc.lquota_desc.gl_softlimit = lqe->lqe_softlimit;
		qti->qti_gl_desc.lquota_desc.gl_time = lqe->lqe_gracetime;
	}
	qti->qti_gl_desc.lquota_desc.gl_ver       = ver;

	/* look up ldlm resource associated with global index */
	fid_build_reg_res_name(&qti->qti_fid, &qti->qti_resid);
	res = ldlm_resource_get(pool->qpi_qmt->qmt_ns, &qti->qti_resid,
				LDLM_PLAIN, 0);
	if (IS_ERR(res)) {
		/* this might happen if no slaves have enqueued global quota
		 * locks yet */
		LQUOTA_DEBUG(lqe, "failed to lookup ldlm resource associated "
			     "with "DFID, PFID(&qti->qti_fid));
		RETURN_EXIT;
	}

	qmt_glimpse_lock(env, pool->qpi_qmt, res, &qti->qti_gl_desc,
			 NULL, NULL);
	ldlm_resource_putref(res);
	EXIT;
}

/* Callback function used to select locks that should be glimpsed when
 * broadcasting the new qunit value */
static int qmt_id_lock_cb(struct ldlm_lock *lock, struct lquota_entry *lqe)
{
	struct obd_uuid *uuid = &(lock)->l_export->exp_client_uuid;
	struct lqe_glbl_data *lgd = lqe->lqe_glbl_data;
	enum qmt_stype stype;
	int idx;

	stype = qmt_uuid2idx(uuid, &idx);
	LASSERT(stype == QMT_STYPE_OST || stype == QMT_STYPE_MDT);

	CDEBUG(D_QUOTA, "stype %d rtype %d idx %d uuid %s\n",
	       stype, lqe_rtype(lqe), idx, uuid->uuid);
	/* Quota pools support only OSTs, despite MDTs also could be registered
	 * as LQUOTA_RES_DT devices(DOM). */
	if (qmt_dom(lqe_rtype(lqe), stype))
		return 1;

	if (lgd) {
		int lge_idx = qmt_map_lge_idx(lgd, idx);

		LASSERT(lge_idx >= 0);
		CDEBUG(D_QUOTA,
		       "tgt idx:%d lge_idx:%d edquot_nu:%d qunit_nu:%d\n",
		       idx, lge_idx, lgd->lqeg_arr[lge_idx].lge_edquot_nu,
		       lgd->lqeg_arr[lge_idx].lge_qunit_nu);
		return lgd->lqeg_arr[lge_idx].lge_edquot_nu ||
		       lgd->lqeg_arr[lge_idx].lge_qunit_nu;
	}

	return 0;
}


/*
 * Send glimpse request on per-ID lock to push new qunit value to slave.
 *
 * \param env  - is the environment passed by the caller
 * \param qmt  - is the quota master target device
 * \param lqe  - is the lquota entry with the new qunit value
 * \param uuid - is the uuid of the slave acquiring space, if any
 */
static void qmt_id_lock_glimpse(const struct lu_env *env,
				struct qmt_device *qmt,
				struct lquota_entry *lqe, struct obd_uuid *uuid)
{
	struct qmt_thread_info	*qti = qmt_info(env);
	struct qmt_pool_info	*pool = lqe2qpi(lqe);
	struct ldlm_resource	*res = NULL;
	ENTRY;

	if (!lqe->lqe_enforced)
		RETURN_EXIT;

	lquota_generate_fid(&qti->qti_fid, pool->qpi_rtype, lqe_qtype(lqe));
	fid_build_quota_res_name(&qti->qti_fid, &lqe->lqe_id, &qti->qti_resid);
	res = ldlm_resource_get(qmt->qmt_ns, &qti->qti_resid, LDLM_PLAIN, 0);
	if (IS_ERR(res)) {
		/* this might legitimately happens if slaves haven't had the
		 * opportunity to enqueue quota lock yet. */
		LQUOTA_DEBUG(lqe, "failed to lookup ldlm resource for per-ID "
			     "lock "DFID, PFID(&qti->qti_fid));
		lqe_write_lock(lqe);
		if (lqe->lqe_revoke_time == 0 &&
		    lqe->lqe_qunit == pool->qpi_least_qunit)
			lqe->lqe_revoke_time = ktime_get_seconds();
		lqe_write_unlock(lqe);
		RETURN_EXIT;
	}

	lqe_write_lock(lqe);
	/* The purpose of glimpse callback on per-ID lock is twofold:
	 * - notify slaves of new qunit value and hope they will release some
	 *   spare quota space in return
	 * - notify slaves that master ran out of quota space and there is no
	 *   need to send acquire request any more until further notice */

	/* TODO: it is not clear how to implement below case for all lqes
	 * from where slaves will be notified in qmt_glimpse_lock. Because
	 * here we have just global lqe with an array of OSTs that should
	 * be notified. Theoretically we can find all lqes that includes
	 * these OSTs, but it is not trivial. So I would propose to move
	 * this case to another place ... */
	if (lqe->lqe_revoke_time == 0 &&
	    lqe->lqe_qunit == pool->qpi_least_qunit)
		/* reset lqe_may_rel, it will be updated on glimpse callback
		 * replies if needed */
		lqe->lqe_may_rel = 0;

	/* The rebalance thread is the only thread which can issue glimpses */
	LASSERT(!lqe->lqe_gl);
	lqe->lqe_gl = true;
	lqe_write_unlock(lqe);

	/* issue glimpse callback to slaves */
	if (lqe->lqe_glbl_data)
		qmt_glimpse_lock(env, qmt, res, &qti->qti_gl_desc,
				 qmt_id_lock_cb, lqe);

	lqe_write_lock(lqe);
	if (lqe->lqe_revoke_time == 0 &&
	    lqe->lqe_qunit == pool->qpi_least_qunit) {
		lqe->lqe_revoke_time = ktime_get_seconds();
		qmt_adjust_edquot(lqe, ktime_get_real_seconds());
	}
	LASSERT(lqe->lqe_gl);
	lqe->lqe_gl = false;
	lqe_write_unlock(lqe);
	ldlm_resource_putref(res);
	EXIT;
}

/*
 * Schedule a glimpse request on per-ID locks to push new qunit value or
 * edquot flag to quota slaves.
 *
 * \param qmt  - is the quota master target device
 * \param lqe  - is the lquota entry with the new qunit value
 */
void qmt_id_lock_notify(struct qmt_device *qmt, struct lquota_entry *lqe)
{
	bool	added = false;
	ENTRY;

	LASSERT(lqe->lqe_is_global);
	lqe_getref(lqe);
	spin_lock(&qmt->qmt_reba_lock);
	if (!qmt->qmt_stopping && list_empty(&lqe->lqe_link)) {
		list_add_tail(&lqe->lqe_link, &qmt->qmt_reba_list);
		added = true;
		if (qmt->qmt_reba_task)
			wake_up_process(qmt->qmt_reba_task);
	}
	spin_unlock(&qmt->qmt_reba_lock);

	if (!added)
		lqe_putref(lqe);
	EXIT;
}

struct qmt_reba_args {
	struct qmt_device	*qra_dev;
	struct lu_env		 qra_env;
	struct completion	*qra_started;
};

#ifndef TASK_IDLE
#define TASK_IDLE TASK_INTERRUPTIBLE
#endif

/*
 * The rebalance thread is in charge of sending glimpse callbacks on per-ID
 * quota locks owned by slaves in order to notify them of:
 * - a qunit shrink in which case slaves might release quota space back in
 *   glimpse reply.
 * - set/clear edquot flag used to cache the "quota exhausted" state of the
 *   master. When the flag is set, slaves know that there is no need to
 *   try to acquire quota from the master since this latter has already
 *   distributed all the space.
 */
static int qmt_reba_thread(void *_args)
{
	struct qmt_reba_args	*args = _args;
	struct qmt_device	*qmt = args->qra_dev;
	struct lu_env		*env = &args->qra_env;
	struct lquota_entry	*lqe, *tmp;
	ENTRY;

	complete(args->qra_started);
	while (({set_current_state(TASK_IDLE);
		 !kthread_should_stop(); })) {

		spin_lock(&qmt->qmt_reba_lock);
		list_for_each_entry_safe(lqe, tmp, &qmt->qmt_reba_list,
					 lqe_link) {
			__set_current_state(TASK_RUNNING);
			list_del_init(&lqe->lqe_link);
			spin_unlock(&qmt->qmt_reba_lock);

			/* lqe_ref == 1 means we hold the last ref,
			 * so no need to send glimpse callbacks.
			 */
			if (!kthread_should_stop() &&
			    kref_read(&lqe->lqe_ref) > 1)
				qmt_id_lock_glimpse(env, qmt, lqe, NULL);

			lqe_putref(lqe);
			spin_lock(&qmt->qmt_reba_lock);
		}
		spin_unlock(&qmt->qmt_reba_lock);
		schedule();
	}
	__set_current_state(TASK_RUNNING);

	lu_env_remove(env);
	lu_env_fini(env);
	OBD_FREE_PTR(args);
	RETURN(0);
}

/*
 * Start rebalance thread. Called when the QMT is being setup
 */
int qmt_start_reba_thread(struct qmt_device *qmt)
{
	struct task_struct *task;
	struct qmt_reba_args *args;
	DECLARE_COMPLETION_ONSTACK(started);
	int rc;
	ENTRY;

	OBD_ALLOC_PTR(args);
	if (args == NULL)
		RETURN(-ENOMEM);
	args->qra_dev = qmt;
	args->qra_started = &started;

	rc = lu_env_init(&args->qra_env, LCT_MD_THREAD);
	if (rc) {
		CERROR("%s: failed to init env.\n", qmt->qmt_svname);
		GOTO(out_env, rc);
	}

	task = kthread_create(qmt_reba_thread, args,
			      "qmt_reba_%s", qmt->qmt_svname);
	if (IS_ERR(task)) {
		CERROR("%s: failed to start rebalance thread (%ld)\n",
		       qmt->qmt_svname, PTR_ERR(task));
		GOTO(out_env_fini, rc = PTR_ERR(task));
	}

	rc = lu_env_add_task(&args->qra_env, task);
	if (rc) {
		kthread_stop(task);
		GOTO(out_env_fini, rc);
	}
	qmt->qmt_reba_task = task;
	wake_up_process(task);
	wait_for_completion(&started);

	RETURN(0);
out_env_fini:
	lu_env_fini(&args->qra_env);
out_env:
	OBD_FREE_PTR(args);
	RETURN(rc);
}

/*
 * Stop rebalance thread. Called when the QMT is about to shutdown.
 */
void qmt_stop_reba_thread(struct qmt_device *qmt)
{
	struct task_struct *task;

	spin_lock(&qmt->qmt_reba_lock);
	task = qmt->qmt_reba_task;
	qmt->qmt_reba_task = NULL;
	spin_unlock(&qmt->qmt_reba_lock);

	if (task)
		kthread_stop(task);

	LASSERT(list_empty(&qmt->qmt_reba_list));
}
