Viewing: qmt_lqa.c
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2025, DataDirect Networks Inc, all rights reserved.
*/
/*
* Lustre quota aggregation(LQA) API
*
* Author: Sergey Cheremencev <scherementsev@ddn.com>
*/
#define DEBUG_SUBSYSTEM S_LQUOTA
#include <uapi/linux/lustre/lustre_user.h>
#include "qmt_internal.h"
/**
* LQA range record format uses struct lqa_id_range from lustre_user.h
* @lir_start: Range start ID (inclusive)
* @lir_end: Range end ID (inclusive)
*
* This structure defines the record format used to store LQA ranges
* in indexed storage. Each range is stored as a separate IAM record with
* the range start ID as the key and this structure as the record value.
*
* The key for IAM operations is the range start ID (__u32).
* The record contains both start and end IDs for completeness and validation.
*/
/* LQA range index features */
static const struct dt_index_features dt_lqa_range_features = {
.dif_flags = DT_IND_UPDATE,
.dif_keysize_min = sizeof(((struct lqa_id_range *)
{0})->lir_start),
.dif_keysize_max = sizeof(((struct lqa_id_range *)
{0})->lir_start),
.dif_recsize_min = sizeof(struct lqa_id_range), /* 8 bytes */
.dif_recsize_max = sizeof(struct lqa_id_range), /* 8 bytes */
.dif_ptrsize = 4
};
static int qmt_lqa_save_range_to_disk(struct lu_env *env,
struct qmt_pool_info *qpi,
__u32 start, __u32 end);
static int qmt_lqa_remove_range_from_disk(struct lu_env *env,
struct qmt_pool_info *qpi,
__u32 start, __u32 end);
static int qmt_lqa_load_ranges_from_disk(struct lu_env *env,
struct qmt_pool_info *qpi);
int qmt_lqa_create(struct obd_device *obd, struct qmt_device *qmt, char *name)
{
int rc;
ENTRY;
rc = qmt_pool_create(obd, LQUOTA_RES_DT, name, true);
if (rc)
RETURN(rc);
rc = qmt_pool_create(obd, LQUOTA_RES_MD, name, true);
if (rc)
GOTO(out, rc);
atomic_inc(&qmt->qmt_lqa_num);
RETURN(0);
out:
qmt_pool_destroy(obd, LQUOTA_RES_DT, name, true);
RETURN(rc);
}
int qmt_lqa_destroy(struct obd_device *obd, struct qmt_device *qmt, char *name)
{
int rc, rc2;
ENTRY;
rc = qmt_pool_destroy(obd, LQUOTA_RES_DT, name, true);
if (rc)
CERROR("%s: cannot destroy lqa-dt-%s: rc = %d\n", obd->obd_name,
name, rc);
rc2 = qmt_pool_destroy(obd, LQUOTA_RES_MD, name, true);
if (rc2)
CERROR("%s: cannot destroy lqa-md-%s: rc = %d\n",
obd->obd_name, name, rc2);
if (!rc && !rc2)
atomic_dec(&qmt->qmt_lqa_num);
RETURN(rc ? rc : rc2);
}
/* Disk persistence functions */
#define QMT_LQA_RANGE_NAME "ranges"
/**
* qmt_lqa_get_range_obj() - Get or create LQA range index for a pool
* @env: Lustre environment
* @qpi: QMT pool info structure
*
* Range indexes are stored as "ranges" IAM index in the pool directory:
* quota_master/lqa-dt-<pool>/ranges or quota_master/lqa-md-<pool>/ranges
*
* Return: dt_object pointer on success, ERR_PTR on failure
*/
static struct dt_object *qmt_lqa_get_range_obj(const struct lu_env *env,
struct qmt_pool_info *qpi)
{
struct qmt_thread_info *qti = qmt_info(env);
struct dt_object *range_obj, *root;
struct local_oid_storage *los = NULL;
char name[LQUOTA_NAME_MAX];
int rc;
ENTRY;
/* Create filename: ranges */
snprintf(name, sizeof(name), QMT_LQA_RANGE_NAME);
CDEBUG(D_QUOTA, "%s: Looking for LQA range file %s in pool %s\n",
qpi->qpi_qmt->qmt_svname, name, qpi->qpi_name);
/* Use the pool's root directory for storing range files */
if (!qpi->qpi_root) {
CERROR("%s: Pool root directory not initialized for pool %s: rc = %d\n",
qpi->qpi_qmt->qmt_svname, qpi->qpi_name, -EINVAL);
RETURN(ERR_PTR(-EINVAL));
}
/* Get reference to pool root directory */
root = qpi->qpi_root;
lu_object_get(&root->do_lu);
/* Look up the file first */
rc = dt_lookup_dir(env, root, name, &qti->qti_fid);
if (rc == 0) {
/* File exists, get the object */
range_obj = dt_locate(env, qpi->qpi_qmt->qmt_child,
&qti->qti_fid);
if (IS_ERR(range_obj)) {
CERROR("%s: Failed to locate LQA range file %s: rc = %ld\n",
qpi->qpi_qmt->qmt_svname, name,
PTR_ERR(range_obj));
}
CDEBUG(D_QUOTA, "%s: Found existing LQA range file %s\n",
qpi->qpi_qmt->qmt_svname, name);
GOTO(success, range_obj);
} else if (rc != -ENOENT) {
CERROR("%s: Failed to lookup LQA range file %s: rc = %d\n",
qpi->qpi_qmt->qmt_svname, name, rc);
dt_object_put(env, root);
RETURN(ERR_PTR(rc));
}
/* File doesn't exist, create it */
/* Set up local storage to create the LQA range file */
lu_local_name_obj_fid(&qti->qti_fid, 1);
rc = local_oid_storage_init(env, qpi->qpi_qmt->qmt_child, &qti->qti_fid,
&los);
if (rc) {
CERROR("%s: Failed to initialize LQA local OID storage: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rc);
dt_object_put(env, root);
RETURN(ERR_PTR(rc));
}
CDEBUG(D_QUOTA, "%s: Creating LQA range IAM index %s using local storage\n",
qpi->qpi_qmt->qmt_svname, name);
/* Create IAM index using local storage mechanism with root parent */
range_obj = local_index_find_or_create(env, los, root, name,
S_IFREG | 0644,
&dt_lqa_range_features);
if (IS_ERR(range_obj)) {
rc = PTR_ERR(range_obj);
CERROR("%s: Failed to create LQA range IAM index %s in pool %s: rc = %d\n",
qpi->qpi_qmt->qmt_svname, name, qpi->qpi_name, rc);
} else {
CDEBUG(D_QUOTA, "%s: LQA range IAM index %s created successfully in pool %s\n",
qpi->qpi_qmt->qmt_svname, name, qpi->qpi_name);
}
local_oid_storage_fini(env, los);
success:
/* Set up index operations */
if (!IS_ERR(range_obj) && range_obj->do_index_ops == NULL) {
rc = range_obj->do_ops->do_index_try(env, range_obj,
&dt_lqa_range_features);
if (rc) {
CERROR("%s: Failed to set up operations: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rc);
range_obj = ERR_PTR(rc);
}
}
dt_object_put(env, root);
RETURN(range_obj);
}
/**
* qmt_lqa_insert_range() - Insert a range into the red-black tree
* @qpi: QMT pool info structure
* @start: Start of range
* @end: End of range
*
* Return: 0 on success, negative error code on failure
*/
static int qmt_lqa_insert_range(struct qmt_pool_info *qpi,
__u32 start, __u32 end)
{
struct rb_node **node = &qpi->qpi_lqa_rbroot.rb_node;
struct rb_node *parent = NULL;
struct qmt_lqa_range *cur;
struct qmt_lqa_range *new_range;
/* Allocate and insert range */
OBD_ALLOC_PTR(new_range);
if (!new_range) {
CERROR("%s: Failed to allocate LQA range: rc = %d\n",
qpi->qpi_qmt->qmt_svname, -ENOMEM);
return -ENOMEM;
}
new_range->qlr_start = start;
new_range->qlr_end = end;
RB_CLEAR_NODE(&new_range->qlr_rbnode);
write_lock(&qpi->qpi_lqa_lock);
while (*node) {
parent = *node;
cur = rb_entry(*node, struct qmt_lqa_range, qlr_rbnode);
/* New range is equal or a subset of existed */
if (start >= cur->qlr_start && end <= cur->qlr_end) {
CERROR("%s: LQA range %u-%u is subset of existing range %u-%u: rc = %d\n",
qpi->qpi_qmt->qmt_svname, new_range->qlr_start,
new_range->qlr_end, cur->qlr_start, cur->qlr_end,
-EEXIST);
goto error;
}
/* Check for partial overlaps */
if ((end >= cur->qlr_start && start <= cur->qlr_start)
|| (start <= cur->qlr_end && end >= cur->qlr_end)) {
CERROR("%s: LQA range %u-%u partially overlaps with existing range %u-%u: rc = %d\n",
qpi->qpi_qmt->qmt_svname, start, end,
cur->qlr_start, cur->qlr_end,
-ERANGE);
goto error;
}
/* Navigate the tree */
if (end < cur->qlr_start)
node = &((*node)->rb_left);
else if (start > cur->qlr_end)
node = &((*node)->rb_right);
}
rb_link_node(&new_range->qlr_rbnode, parent, node);
rb_insert_color(&new_range->qlr_rbnode, &qpi->qpi_lqa_rbroot);
write_unlock(&qpi->qpi_lqa_lock);
return 0;
error:
write_unlock(&qpi->qpi_lqa_lock);
OBD_FREE_PTR(new_range);
return -EEXIST;
}
/**
* qmt_lqa_save_range_to_disk() - Save a single LQA range to disk
* @env: Lustre environment
* @qpi: QMT pool info structure
* @start: Range start ID
* @end: Range end ID
*
* Simple disk storage logic:
* 1. Check if range already exists on disk
* 2. If exists and matches, return -EEXIST
* 3. If exists but different, return error
* 4. If doesn't exist, insert new range
*
* Return: 0 on success, negative error code on failure
*/
static int qmt_lqa_save_range_to_disk(struct lu_env *env,
struct qmt_pool_info *qpi, __u32 start,
__u32 end)
{
struct dt_object *range_obj = NULL;
struct thandle *th = NULL;
struct lqa_id_range rec;
__u32 range_key = start;
int rc;
ENTRY;
/* Get the range IAM index */
range_obj = qmt_lqa_get_range_obj(env, qpi);
if (IS_ERR(range_obj))
RETURN(rc = PTR_ERR(range_obj));
/* Check if range already exists on disk */
rc = dt_lookup(env, range_obj, (struct dt_rec *)&rec,
(struct dt_key *)&range_key);
if (rc == 0) {
/* Range exists - check if it matches */
if (rec.lir_start == start && rec.lir_end == end) {
CDEBUG(D_QUOTA, "%s: Range [%u-%u] already exists on disk: rc = %d\n",
qpi->qpi_qmt->qmt_svname, start, end, -EEXIST);
GOTO(out_obj, rc = -EEXIST);
} else {
CERROR("%s: Disk range mismatch for key %u: disk=[%u-%u], new=[%u-%u]: rc = %d\n",
qpi->qpi_qmt->qmt_svname, start,
rec.lir_start, rec.lir_end, start, end, -EINVAL);
GOTO(out_obj, rc = -EINVAL);
}
} else if (rc != -ENOENT) {
CERROR("%s: Failed to lookup range key %u: rc = %d\n",
qpi->qpi_qmt->qmt_svname, start, rc);
GOTO(out_obj, rc);
}
/* Range doesn't exist on disk - insert it */
rec.lir_start = start;
rec.lir_end = end;
/* Start transaction */
th = dt_trans_create(env, qpi->qpi_qmt->qmt_child);
if (IS_ERR(th))
GOTO(out_obj, rc = PTR_ERR(th));
/* Declare insert operation */
rc = dt_declare_insert(env, range_obj, (struct dt_rec *)&rec,
(struct dt_key *)&range_key, th);
if (rc)
GOTO(out_trans, rc);
rc = dt_trans_start_local(env, qpi->qpi_qmt->qmt_child, th);
if (rc)
GOTO(out_trans, rc);
/* Insert the new range */
rc = dt_insert(env, range_obj, (struct dt_rec *)&rec,
(struct dt_key *)&range_key, th);
if (rc) {
CERROR("%s: Failed to insert LQA range [%u-%u]: rc = %d\n",
qpi->qpi_qmt->qmt_svname, start, end, rc);
GOTO(out_trans, rc);
}
CDEBUG(D_QUOTA, "%s: Successfully inserted LQA range [%u-%u] to disk\n",
qpi->qpi_qmt->qmt_svname, start, end);
/* Success */
rc = 0;
out_trans:
dt_trans_stop(env, qpi->qpi_qmt->qmt_child, th);
out_obj:
dt_object_put(env, range_obj);
RETURN(rc);
}
/**
* qmt_lqa_remove_range_from_disk() - Remove a single LQA range from disk
* @env: Lustre environment
* @qpi: QMT pool info structure
* @start: Range start ID
* @end: Range end ID
*
* Simple disk removal logic:
* 1. Check if range exists on disk
* 2. If doesn't exist, return -ENOENT
* 3. If exists but different, return error
* 4. If exists and matches, delete it
*
* Return: 0 on success, negative error code on failure
*/
static int qmt_lqa_remove_range_from_disk(struct lu_env *env,
struct qmt_pool_info *qpi,
__u32 start, __u32 end)
{
struct dt_object *range_obj = NULL;
struct thandle *th = NULL;
struct lqa_id_range rec;
__u32 range_key = start;
int rc;
ENTRY;
/* Get the range IAM index */
range_obj = qmt_lqa_get_range_obj(env, qpi);
if (IS_ERR(range_obj))
RETURN(rc = PTR_ERR(range_obj));
/* Check if range exists on disk */
rc = dt_lookup(env, range_obj, (struct dt_rec *)&rec,
(struct dt_key *)&range_key);
if (rc == -ENOENT) {
CDEBUG(D_QUOTA, "%s: Range [%u-%u] doesn't exist on disk\n",
qpi->qpi_qmt->qmt_svname, start, end);
GOTO(out_obj, rc);
} else if (rc != 0) {
CERROR("%s: Failed to lookup range key %u: rc = %d\n",
qpi->qpi_qmt->qmt_svname, start, rc);
GOTO(out_obj, rc);
}
/* Range exists - check if it matches */
if (rec.lir_start != start || rec.lir_end != end) {
rc = -EINVAL;
CERROR("%s: Disk range mismatch for key %u: disk=[%u-%u], expected=[%u-%u]: rc =%d\n",
qpi->qpi_qmt->qmt_svname, start, rec.lir_start,
rec.lir_end, start, end, rc);
GOTO(out_obj, rc);
}
/* Range matches - delete it */
th = dt_trans_create(env, qpi->qpi_qmt->qmt_child);
if (IS_ERR(th))
GOTO(out_obj, rc = PTR_ERR(th));
/* Declare delete operation */
rc = dt_declare_delete(env, range_obj, (struct dt_key *)&range_key, th);
if (rc)
GOTO(out_trans, rc);
rc = dt_trans_start_local(env, qpi->qpi_qmt->qmt_child, th);
if (rc)
GOTO(out_trans, rc);
/* Delete the range */
rc = dt_delete(env, range_obj, (struct dt_key *)&range_key, th);
if (rc) {
CERROR("%s: Failed to delete LQA range [%u-%u]: rc = %d\n",
qpi->qpi_qmt->qmt_svname, start, end, rc);
GOTO(out_trans, rc);
}
CDEBUG(D_QUOTA, "%s: Successfully deleted LQA range [%u-%u] from disk\n",
qpi->qpi_qmt->qmt_svname, start, end);
/* Success */
rc = 0;
out_trans:
dt_trans_stop(env, qpi->qpi_qmt->qmt_child, th);
out_obj:
dt_object_put(env, range_obj);
RETURN(rc);
}
/**
* qmt_lqa_load_ranges_from_disk() - Load LQA ranges from IAM index to pool
* @env: Lustre environment
* @qpi: QMT pool info structure
*
* Return: 0 on success, negative error code on failure
*/
static int qmt_lqa_load_ranges_from_disk(struct lu_env *env,
struct qmt_pool_info *qpi)
{
struct dt_object *range_obj = NULL;
struct lqa_id_range rec;
const struct dt_it_ops *iops;
struct dt_it *it = NULL;
struct dt_key *key;
__u32 range_key;
int rc, loaded = 0;
ENTRY;
/* Get the range IAM index */
range_obj = qmt_lqa_get_range_obj(env, qpi);
if (IS_ERR(range_obj)) {
CERROR("%s: Get failed for %s (res_type %d): rc = %ld\n",
qpi->qpi_qmt->qmt_svname, qpi->qpi_name, qpi->qpi_rtype,
PTR_ERR(range_obj));
RETURN(rc = PTR_ERR(range_obj));
}
/* Initialize iterator for the IAM index */
iops = &range_obj->do_index_ops->dio_it;
it = iops->init(env, range_obj, 0);
if (IS_ERR(it)) {
CERROR("%s: Failed to initialize IAM iterator: rc = %ld\n",
qpi->qpi_qmt->qmt_svname, PTR_ERR(it));
GOTO(out_obj, rc = PTR_ERR(it));
}
rc = iops->load(env, it, 0);
if (rc < 0) {
CERROR("%s: Failed to load IAM iterator: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rc);
GOTO(out_it, rc);
} else if (rc == 0) {
rc = iops->next(env, it);
if (rc != 0)
GOTO(out_it, rc = (rc < 0) ? rc : 0);
}
/* Iterate through all records in the IAM index */
do {
/* Get the key (range start ID) */
key = iops->key(env, it);
if (IS_ERR(key)) {
rc = PTR_ERR(key);
CERROR("%s: Failed to get key from IAM iterator: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rc);
break;
}
range_key = *(__u32 *)key;
/* Get the record (range data) */
rc = iops->rec(env, it, (struct dt_rec *)&rec, 0);
if (rc) {
CERROR("%s: Failed to get record from IAM iterator: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rc);
break;
}
/*
* Newly created ldiskfs IAM indexes may include a
* zeroed-out key and record. Ignore it here.
*/
if (range_key == 0 && rec.lir_start == 0 && rec.lir_end == 0) {
CDEBUG(D_QUOTA, "%s: Zeroed-out key and record: key=%u, rec_start=%u, rec_end=%u\n",
qpi->qpi_qmt->qmt_svname, range_key,
rec.lir_start, rec.lir_end);
goto next;
}
/* Validate the record */
if (rec.lir_start != range_key) {
CERROR("%s: mismatch: key=%u, rec_start=%u: rc = %d\n",
qpi->qpi_qmt->qmt_svname, range_key,
rec.lir_start, -EINVAL);
rc = -EINVAL;
break;
}
if (rec.lir_start > rec.lir_end) {
CERROR("%s: Invalid LQA range %u-%u: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rec.lir_start,
rec.lir_end, -EINVAL);
rc = -EINVAL;
break;
}
/* Insert into red-black tree */
rc = qmt_lqa_insert_range(qpi, rec.lir_start, rec.lir_end);
if (rc) {
CERROR("%s: Failed to insert range %u-%u: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rec.lir_start,
rec.lir_end, rc);
break;
}
loaded++;
CDEBUG(D_QUOTA, "%s: Loaded LQA range %u-%u for pool %s\n",
qpi->qpi_qmt->qmt_svname, rec.lir_start,
rec.lir_end, qpi->qpi_name);
next:
rc = iops->next(env, it);
if (rc < 0) {
CERROR("%s: Failed to iterate IAM iterator: rc = %d\n",
qpi->qpi_qmt->qmt_svname, rc);
}
} while (rc == 0);
if (rc >= 0) {
CDEBUG(D_QUOTA, "%s: Loaded %d ranges for pool %s (res_type %d)\n",
qpi->qpi_qmt->qmt_svname, loaded, qpi->qpi_name,
qpi->qpi_rtype);
rc = 0;
}
out_it:
iops->put(env, it);
iops->fini(env, it);
out_obj:
dt_object_put(env, range_obj);
RETURN(rc);
}
bool qmt_lqa_contain_id(struct qmt_pool_info *qpi, __u64 id)
{
struct qmt_lqa_range *cur;
struct rb_node *node;
bool found = false;
LASSERT(qpi->qpi_lqa);
if (id > UINT_MAX) {
CERROR("%s: lqa:%s id:%llu is greater UNIT_MAX: rc = %d\n",
qpi->qpi_qmt->qmt_svname, qpi->qpi_name, id, -ERANGE);
return false;
}
read_lock(&qpi->qpi_lqa_lock);
node = qpi->qpi_lqa_rbroot.rb_node;
while (node) {
cur = rb_entry(node, struct qmt_lqa_range, qlr_rbnode);
if (id >= cur->qlr_start && id <= cur->qlr_end) {
found = true;
break;
} else if (id < cur->qlr_start) {
node = node->rb_left;
} else { /* id > cur->qlr_end) */
node = node->rb_right;
}
}
read_unlock(&qpi->qpi_lqa_lock);
return found;
}
int qmt_lqa_add(struct qmt_device *qmt, char *name, __u32 start, __u32 end)
{
struct qmt_pool_info *qpi;
struct lu_env env;
int res, rc = 0;
ENTRY;
rc = lu_env_init(&env, LCT_MD_THREAD);
if (rc) {
CERROR("%s: can't init env: rc = %d\n", qmt->qmt_svname, rc);
RETURN(rc);
}
for (res = LQUOTA_FIRST_RES; res < LQUOTA_LAST_RES; res++) {
qpi = qmt_pool_lookup_name_lqa(&env, qmt, res, name, true);
if (IS_ERR(qpi)) {
rc = PTR_ERR(qpi);
break;
}
rc = qmt_lqa_insert_range(qpi, start, end);
if (rc) {
qpi_putref(&env, qpi);
break;
}
CDEBUG(D_QUOTA, "Insert a new range: %u:%u for %s\n",
start, end, name);
qmt_start_pool_recalc(&env, qpi);
qpi_putref(&env, qpi);
}
/* Save range to disk */
if (rc == 0) {
int save_rc = qmt_lqa_save_range_to_disk(&env, qpi, start, end);
if (save_rc && save_rc != -EEXIST) {
CWARN("%s: Failed to save LQA range [%u-%u] to disk: rc = %d\n",
qmt->qmt_svname, start, end, save_rc);
/* Don't fail the operation for disk save errors */
}
}
lu_env_fini(&env);
if (rc)
CERROR("%s: lqa:%s can't add range %u:%u: rc = %d\n",
qmt->qmt_svname, name, start, end, rc);
RETURN(rc);
}
int qmt_lqa_list(struct qmt_device *qmt, char *name,
struct obd_ioctl_data *data)
{
struct qmt_lqa_range *range;
struct qmt_pool_info *qpi;
struct rb_node *node;
struct lu_env env;
char *buf = NULL;
__u32 *p;
int buf_size, max;
int rc = 0;
int i = 0;
ENTRY;
if (!name) {
int lqa_num = atomic_read(&qmt->qmt_lqa_num);
int max_names = data->ioc_plen2 / LQA_NAME_MAX;
if (!max_names)
RETURN(-EINVAL);
if (!lqa_num) {
data->ioc_plen2 = 0;
RETURN(0);
}
lqa_num = min(max_names, lqa_num);
buf_size = LQA_NAME_MAX * lqa_num;
OBD_ALLOC(buf, buf_size);
if (!buf) {
data->ioc_plen2 = 0;
RETURN(-ENOMEM);
}
down_read(&qmt->qmt_pool_lock);
/* Metadata LQAs duplicate Data LQAs. It is enough to go only
* through the pool data list.
*/
list_for_each_entry(qpi, &qmt->qmt_pool_list, qpi_linkage) {
if (!qpi->qpi_lqa || qpi->qpi_rtype != LQUOTA_RES_DT)
continue;
memcpy(buf + i * LQA_NAME_MAX, qpi->qpi_name,
LQA_NAME_MAX);
if (lqa_num == ++i)
break;
}
up_read(&qmt->qmt_pool_lock);
data->ioc_plen2 = buf_size;
if (copy_to_user(data->ioc_pbuf2, buf, buf_size))
rc = -EFAULT;
GOTO(out_buf, rc);
}
buf_size = data->ioc_plen2;
if (buf_size < LQA_RANGE_SIZE)
RETURN(-EINVAL);
rc = lu_env_init(&env, LCT_MD_THREAD);
if (rc) {
CERROR("%s: can't init env: rc = %d\n", qmt->qmt_svname, rc);
RETURN(rc);
}
qpi = qmt_pool_lookup_name_lqa(&env, qmt, LQUOTA_RES_DT, name, true);
if (IS_ERR(qpi))
GOTO(out_env, rc = PTR_ERR(qpi));
OBD_ALLOC(buf, buf_size);
if (!buf)
GOTO(out_qpi, rc = -ENOMEM);
max = buf_size / LQA_RANGE_SIZE;
p = (__u32 *)buf;
read_lock(&qpi->qpi_lqa_lock);
for (node = rb_first(&qpi->qpi_lqa_rbroot); node && i < max;
node = rb_next(node), i++, p += 2) {
range = rb_entry(node, struct qmt_lqa_range, qlr_rbnode);
p[0] = range->qlr_start;
p[1] = range->qlr_end;
}
read_unlock(&qpi->qpi_lqa_lock);
data->ioc_plen2 = LQA_RANGE_SIZE * i;
if (copy_to_user(data->ioc_pbuf2, buf, data->ioc_plen2))
rc = -EFAULT;
GOTO(out_qpi, rc);
out_qpi:
qpi_putref(&env, qpi);
out_env:
lu_env_fini(&env);
out_buf:
OBD_FREE(buf, buf_size);
return rc;
}
int qmt_lqa_remove(struct qmt_device *qmt, char *name, __u32 start, __u32 end)
{
struct qmt_pool_info *qpi;
struct qmt_lqa_range *range;
struct rb_node **node;
struct lu_env env;
char *qmt_name;
bool found = false;
int res, rc;
ENTRY;
rc = lu_env_init(&env, LCT_MD_THREAD);
if (rc) {
CERROR("%s: can't init env: rc = %d\n", qmt->qmt_svname, rc);
RETURN(rc);
}
qmt_name = qmt->qmt_svname;
for (res = LQUOTA_FIRST_RES; res < LQUOTA_LAST_RES; res++) {
qpi = qmt_pool_lookup_name_lqa(&env, qmt, res, name, true);
if (IS_ERR(qpi)) {
CERROR("%s: cannot find lqa-%s-%s to remove range %u:%u: rc = %d\n",
qmt_name, RES_NAME(res), name, start, end, rc);
rc = PTR_ERR(qpi);
break;
}
found = false;
range = NULL;
write_lock(&qpi->qpi_lqa_lock);
node = &qpi->qpi_lqa_rbroot.rb_node;
while (*node) {
range = rb_entry(*node, struct qmt_lqa_range, qlr_rbnode);
if (start < range->qlr_start) {
node = &((*node)->rb_left);
} else if (start > range->qlr_start) {
node = &((*node)->rb_right);
} else if (end == range->qlr_end) {
found = true;
rb_erase(*node, &qpi->qpi_lqa_rbroot);
break;
} else {
break;
}
}
write_unlock(&qpi->qpi_lqa_lock);
if (found) {
OBD_FREE_PTR(range);
} else {
rc = -ENOENT;
CERROR("%s: lqa-%s-%s cannot remove range %u:%u: rc = %d\n",
qmt_name, RES_NAME(res), name, start, end, rc);
}
qpi_putref(&env, qpi);
}
if (found) {
int remove_rc;
/* Remove range from disk */
remove_rc = qmt_lqa_remove_range_from_disk(&env, qpi, start,
end);
if (remove_rc && remove_rc != -ENOENT) {
CWARN("%s: Failed to remove LQA range [%u-%u] from disk: rc = %d\n",
qmt->qmt_svname, start, end, remove_rc);
/* Don't fail the operation for disk remove errors */
}
}
lu_env_fini(&env);
RETURN(rc);
}
/**
* qmt_lqa_recreate_pools_from_disk() - Recreate LQA pools
* @qmt: QMT device
*
* This function scans the quota_master directory for LQA pool directories
* and recreates LQA pools that have persistent range data. It discovers
* LQA pools by looking for directories with names matching:
* - "lqa-dt-<lqa_name>" (data target pools)
* - "lqa-md-<lqa_name>" (metadata target pools)
*
* Return: 0 on success, negative error code on failure
*/
static int qmt_lqa_recreate_pools_from_disk(struct qmt_device *qmt)
{
struct lu_env env;
struct dt_it *it = NULL;
const struct dt_it_ops *iops;
char lqa_name[LQUOTA_NAME_MAX];
char entry_name[LQUOTA_NAME_MAX];
int rc = 0, pools_created = 0;
ENTRY;
CDEBUG(D_QUOTA, "%s: Scanning quota_master for LQA pools to recreate\n",
qmt->qmt_svname);
rc = lu_env_init(&env, LCT_MD_THREAD);
if (rc)
RETURN(rc);
/* Get iterator for quota_master directory */
iops = &qmt->qmt_root->do_index_ops->dio_it;
it = iops->init(&env, qmt->qmt_root, LUDA_64BITHASH);
if (IS_ERR(it))
GOTO(out_env, rc = PTR_ERR(it));
rc = iops->load(&env, it, 0);
if (rc <= 0)
GOTO(out_it, rc = rc < 0 ? rc : 0);
/* Iterate through all entries in quota_master */
do {
struct dt_key *key;
int keylen;
key = iops->key(&env, it);
if (IS_ERR(key)) {
rc = PTR_ERR(key);
break;
}
keylen = iops->key_size(&env, it);
if (keylen >= sizeof(entry_name)) {
CWARN("%s: LQA pool name too long (size %d)\n",
qmt->qmt_svname, keylen);
goto next;
}
memcpy(entry_name, key, keylen);
entry_name[keylen] = '\0';
/* Check if this is an LQA pool directory */
if (strncmp(entry_name, "lqa-dt-", 7) == 0 ||
strncmp(entry_name, "lqa-md-", 7) == 0) {
/* "lqa-dt-<lqa_name>" or "lqa-md-<lqa_name>" */
strscpy(lqa_name, entry_name + 7,
sizeof(lqa_name));
} else {
/* Not an LQA directory, skip */
goto next;
}
CDEBUG(D_QUOTA, "%s: Found ranges for LQA pool %s, recreating\n",
qmt->qmt_svname, lqa_name);
/* Recreate the LQA pool */
rc = qmt_lqa_create(qmt2lu_dev(qmt)->ld_obd, qmt,
lqa_name);
if (rc == 0) {
pools_created++;
CDEBUG(D_QUOTA, "%s: Successfully recreated LQA pool %s from disk\n",
qmt->qmt_svname, lqa_name);
} else if (rc == -EEXIST) {
/* LQA pool already exists, that's fine */
pools_created++;
CDEBUG(D_QUOTA, "%s: LQA pool %s already exists\n",
qmt->qmt_svname, lqa_name);
rc = 0;
} else {
CERROR("%s: Failed to recreate LQA pool %s: rc = %d\n",
qmt->qmt_svname, lqa_name, rc);
/* Return error and expect that cleanup code will clean
* created LQA
*/
goto out_it;
}
next:
rc = iops->next(&env, it);
} while (rc == 0);
/* Successfully processed last entry */
rc = 0;
CDEBUG(D_QUOTA, "%s: Recreated %d LQA pools from disk\n",
qmt->qmt_svname, pools_created);
out_it:
iops->fini(&env, it);
out_env:
lu_env_fini(&env);
RETURN(rc);
}
/**
* qmt_lqa_init_from_disk() - Initialize LQA ranges from disk at startup
* @qmt: QMT device
*
* On-disk LQA state format:
* ========================
*
* Directory Structure:
* quota_master/
* ├── lqa-dt-<lqa_name>/ # Data target pool for <lqa_name>
* │ └── ranges # Binary file containing DT ranges
* └── lqa-md-<lqa_name>/ # Metadata target pool for <lqa_name>
* └── ranges # Binary file containing MD ranges
*
* Return: 0 on success, negative error code on failure
*/
int qmt_lqa_init_from_disk(struct qmt_device *qmt)
{
struct lu_env env;
struct qmt_pool_info *qpi;
int rc = 0;
ENTRY;
rc = lu_env_init(&env, LCT_MD_THREAD);
if (rc) {
CERROR("%s: Failed to init env: rc = %d\n",
qmt->qmt_svname, rc);
RETURN(rc);
}
CDEBUG(D_QUOTA, "%s: Initializing LQA ranges from disk\n",
qmt->qmt_svname);
/* Recreate LQA pools from range files on disk */
rc = qmt_lqa_recreate_pools_from_disk(qmt);
if (rc) {
CDEBUG(D_QUOTA, "%s: Failed to recreate LQA pools from disk: %d\n",
qmt->qmt_svname, rc);
GOTO(out_env, rc);
}
/* Load ranges for any existing pools */
down_read(&qmt->qmt_pool_lock);
list_for_each_entry(qpi, &qmt->qmt_pool_list, qpi_linkage) {
/* Only process LQA pools */
if (!qpi->qpi_lqa)
continue;
CDEBUG(D_QUOTA,
"%s: Loading LQA ranges for pool %s (res_type %d)\n",
qmt->qmt_svname, qpi->qpi_name, qpi->qpi_rtype);
rc = qmt_lqa_load_ranges_from_disk(&env, qpi);
if (rc) {
CDEBUG(D_QUOTA,
"%s: No ranges for pool %s (res_type %d): %d\n",
qmt->qmt_svname, qpi->qpi_name, qpi->qpi_rtype,
rc);
/* Continue with other pools, don't fail startup */
}
}
up_read(&qmt->qmt_pool_lock);
out_env:
lu_env_fini(&env);
RETURN(rc);
}