Viewing: lquota_entry.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2012, 2015, 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/module.h>
#include <linux/slab.h>
#include <obd_class.h>
#include "lquota_internal.h"
static int hash_lqs_cur_bits = HASH_LQE_CUR_BITS;
module_param(hash_lqs_cur_bits, int, 0444);
MODULE_PARM_DESC(hash_lqs_cur_bits, "the current bits of lqe hash");
static unsigned
lqe64_hash_hash(struct cfs_hash *hs, const void *key, const unsigned int bits)
{
return hash_64(*((__u64 *)key), bits);
}
static void *lqe64_hash_key(struct hlist_node *hnode)
{
struct lquota_entry *lqe;
lqe = hlist_entry(hnode, struct lquota_entry, lqe_hash);
return &lqe->lqe_id.qid_uid;
}
static int lqe64_hash_keycmp(const void *key, struct hlist_node *hnode)
{
struct lquota_entry *lqe;
lqe = hlist_entry(hnode, struct lquota_entry, lqe_hash);
return (lqe->lqe_id.qid_uid == *((__u64 *)key));
}
static void *lqe_hash_object(struct hlist_node *hnode)
{
return hlist_entry(hnode, struct lquota_entry, lqe_hash);
}
static void lqe_hash_get(struct cfs_hash *hs, struct hlist_node *hnode)
{
struct lquota_entry *lqe;
lqe = hlist_entry(hnode, struct lquota_entry, lqe_hash);
lqe_getref(lqe);
}
static void lqe_hash_put_locked(struct cfs_hash *hs, struct hlist_node *hnode)
{
struct lquota_entry *lqe;
lqe = hlist_entry(hnode, struct lquota_entry, lqe_hash);
lqe_putref(lqe);
}
static void lqe_hash_exit(struct cfs_hash *hs, struct hlist_node *hnode)
{
CERROR("Should not have any item left!\n");
}
/* lqe hash methods for 64-bit uid/gid, new hash functions would have to be
* defined for per-directory quota relying on a 128-bit FID
*/
static struct cfs_hash_ops lqe64_hash_ops = {
.hs_hash = lqe64_hash_hash,
.hs_key = lqe64_hash_key,
.hs_keycmp = lqe64_hash_keycmp,
.hs_object = lqe_hash_object,
.hs_get = lqe_hash_get,
.hs_put_locked = lqe_hash_put_locked,
.hs_exit = lqe_hash_exit
};
/* Logging helper function */
void lquota_lqe_debug0(struct lquota_entry *lqe,
struct libcfs_debug_msg_data *msgdata,
const char *fmt, ...)
{
struct lquota_site *site = lqe->lqe_site;
struct va_format vaf;
va_list args;
LASSERT(site->lqs_ops->lqe_debug != NULL);
va_start(args, fmt);
vaf.fmt = fmt;
vaf.va = &args;
site->lqs_ops->lqe_debug(lqe, site->lqs_parent, msgdata, &vaf);
va_end(args);
}
struct lqe_iter_data {
unsigned long lid_inuse;
unsigned long lid_freed;
bool lid_free_all;
};
static int lqe_iter_cb(struct cfs_hash *hs, struct cfs_hash_bd *bd,
struct hlist_node *hnode, void *data)
{
struct lqe_iter_data *d = (struct lqe_iter_data *)data;
struct lquota_entry *lqe;
lqe = hlist_entry(hnode, struct lquota_entry, lqe_hash);
LASSERT(kref_read(&lqe->lqe_ref) > 0);
/* Only one reference held by hash table, and nobody else can
* grab the entry at this moment, it's safe to remove it from
* the hash and free it. */
if (kref_read(&lqe->lqe_ref) == 1) {
if (!lqe_is_master(lqe)) {
LASSERT(lqe->lqe_pending_write == 0);
LASSERT(lqe->lqe_pending_req == 0);
}
if (d->lid_free_all || lqe->lqe_enforced) {
d->lid_freed++;
cfs_hash_bd_del_locked(hs, bd, hnode);
return 0;
}
}
d->lid_inuse++;
if (d->lid_free_all)
LQUOTA_ERROR(lqe, "Inuse quota entry");
return 0;
}
/**
* lqe_cleanup() - Cleanup the entries in the hashtable
* @hash: hash table which stores quota entries
* @free_all: free all entries or only free the entries without quota enforce ?
*/
static void lqe_cleanup(struct cfs_hash *hash, bool free_all)
{
struct lqe_iter_data d;
int repeat = 0;
ENTRY;
retry:
memset(&d, 0, sizeof(d));
d.lid_free_all = free_all;
cfs_hash_for_each_safe(hash, lqe_iter_cb, &d);
/* In most case, when this function is called on master or
* slave finalization, there should be no inuse quota entry.
*
* If the per-fs quota updating thread is still holding
* some entries, we just wait for it's finished. */
if (free_all && d.lid_inuse) {
CDEBUG(D_QUOTA, "Hash:%p has entries inuse: inuse:%lu, freed:%lu, repeat:%u\n",
hash,
d.lid_inuse, d.lid_freed, repeat);
repeat++;
schedule_timeout_interruptible(cfs_time_seconds(1));
goto retry;
}
EXIT;
}
/**
* lquota_site_alloc() - Allocate a new lquota site.
* @env: the environment passed by the caller
* @parent: is a pointer to the parent structure, either a qmt_pool_info
* structure on the master or a qsd_qtype_info structure on the slave.
* @is_master: is set when the site belongs to a QMT.
* @qtype: is the quota type managed by this site
* @ops: is the quota entry operation vector to be used for quota entry
* belonging to this site.
*
* Return:
* * %0 success
* * %negative failure
*/
struct lquota_site *lquota_site_alloc(const struct lu_env *env, void *parent,
bool is_master, short qtype,
const struct lquota_entry_operations *ops)
{
struct lquota_site *site;
char hashname[15];
ENTRY;
if (qtype >= LL_MAXQUOTAS)
RETURN(ERR_PTR(-ENOTSUPP));
OBD_ALLOC_PTR(site);
if (site == NULL)
RETURN(ERR_PTR(-ENOMEM));
/* assign parameters */
site->lqs_qtype = qtype;
site->lqs_parent = parent;
site->lqs_is_mst = is_master;
site->lqs_ops = ops;
/* allocate hash table */
memset(hashname, 0, sizeof(hashname));
snprintf(hashname, sizeof(hashname), "LQUOTA_HASH%hu", qtype);
site->lqs_hash = cfs_hash_create(hashname, hash_lqs_cur_bits,
HASH_LQE_MAX_BITS,
min(hash_lqs_cur_bits,
HASH_LQE_BKT_BITS),
0, CFS_HASH_MIN_THETA,
CFS_HASH_MAX_THETA, &lqe64_hash_ops,
CFS_HASH_RW_SEM_BKTLOCK |
CFS_HASH_COUNTER |
CFS_HASH_REHASH |
CFS_HASH_BIGNAME);
if (site->lqs_hash == NULL) {
OBD_FREE_PTR(site);
RETURN(ERR_PTR(-ENOMEM));
}
RETURN(site);
}
/**
* lquota_site_free() - Destroy a lquota site.
* @env: - the environment passed by the caller
* @site: - lquota site to be destroyed
*
* Return:
* * %0 success
* * %negative failure
*/
void lquota_site_free(const struct lu_env *env, struct lquota_site *site)
{
/* cleanup hash table */
lqe_cleanup(site->lqs_hash, true);
cfs_hash_putref(site->lqs_hash);
site->lqs_parent = NULL;
OBD_FREE_PTR(site);
}
/*
* Initialize qsd/qmt-specific fields of quota entry.
* @lqe: - is the quota entry to initialize
*/
static void lqe_init(struct lquota_entry *lqe)
{
struct lquota_site *site;
ENTRY;
LASSERT(lqe != NULL);
site = lqe->lqe_site;
LASSERT(site != NULL);
LASSERT(site->lqs_ops->lqe_init != NULL);
site->lqs_ops->lqe_init(lqe, site->lqs_parent);
}
/*
* lqe_read() - Update a lquota entry.
* @env: the environment passed by the caller
* @lqe: is the quota entry to refresh
* @find: don't create entry on disk if true
*
* Update a lquota entry. This is done by reading quota settings from the
* on-disk index. The lquota entry must be write locked.
*
* Return:
* * %0 success
* * %negative failure
*/
static int lqe_read(const struct lu_env *env,
struct lquota_entry *lqe, bool find)
{
struct lquota_site *site;
int rc;
ENTRY;
LASSERT(lqe != NULL);
site = lqe->lqe_site;
LASSERT(site != NULL);
LASSERT(site->lqs_ops->lqe_read != NULL);
LQUOTA_DEBUG(lqe, "read");
rc = site->lqs_ops->lqe_read(env, lqe, site->lqs_parent, find);
if (rc == 0)
/* mark the entry as up-to-date */
lqe->lqe_uptodate = true;
RETURN(rc);
}
/*
* lqe_locate_find() - Find or create a quota entry.
* @env: the environment passed by the caller
* @site: lquota site which stores quota entries in a hash table
* @qid: is the quota ID to be found/created
* @find: don't create lqe on disk in case of ENOENT if true
*
* Return:
* * %0 success
* * %negative failure
*/
struct lquota_entry *lqe_locate_find(const struct lu_env *env,
struct lquota_site *site,
const union lquota_id *qid, bool find)
{
struct lquota_entry *lqe, *new = NULL;
int rc = 0;
ENTRY;
lqe = cfs_hash_lookup(site->lqs_hash, (void *)&qid->qid_uid);
if (lqe != NULL) {
LASSERT(lqe->lqe_uptodate);
RETURN(lqe);
}
OBD_SLAB_ALLOC_PTR_GFP(new, lqe_kmem, GFP_NOFS);
if (new == NULL) {
CERROR("Fail to allocate lqe for id:%llu, hash:%s\n",
qid->qid_uid, site->lqs_hash->hs_name);
RETURN(ERR_PTR(-ENOMEM));
}
kref_init(&new->lqe_ref); /* hold 1 for caller */
new->lqe_id = *qid;
new->lqe_site = site;
INIT_LIST_HEAD(&new->lqe_link);
/* quota settings need to be updated from disk, that's why
* lqe->lqe_uptodate isn't set yet */
new->lqe_uptodate = false;
/* perform qmt/qsd specific initialization */
lqe_init(new);
/* read quota settings from disk and mark lqe as up-to-date */
rc = lqe_read(env, new, find);
if (rc)
GOTO(out, lqe = ERR_PTR(rc));
/* add new entry to hash */
lqe = cfs_hash_findadd_unique(site->lqs_hash, &new->lqe_id.qid_uid,
&new->lqe_hash);
if (lqe == new)
new = NULL;
out:
if (new)
lqe_putref(new);
RETURN(lqe);
}