Viewing: osd_quota.c

// SPDX-License-Identifier: GPL-2.0

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

/*
 * This file is part of Lustre, http://www.lustre.org/
 *
 * Author: Johann Lombardi <johann@whamcloud.com>
 */

#include <dt_object.h>
#include <lustre_quota.h>
#include <obd.h>
#include "osd_internal.h"

/*
 * Helper function to estimate the number of inodes in use for the given
 * uid/gid/projid from the block usage
 */
static uint64_t osd_objset_user_iused(struct osd_device *osd, uint64_t uidbytes)
{
	uint64_t refdbytes, availbytes, usedobjs, availobjs;
	uint64_t uidobjs, bshift;

	/* get fresh statfs info */
	dmu_objset_space(osd->od_os, &refdbytes, &availbytes,
			 &usedobjs, &availobjs);

	/* estimate the number of objects based on the disk usage */
	bshift = fls64(osd->od_max_blksz) - 1;
	uidobjs = osd_objs_count_estimate(refdbytes, usedobjs,
					  uidbytes >> bshift, bshift);
	if (uidbytes > 0)
		/* if we have at least 1 byte, we have at least one dnode ... */
		uidobjs = max_t(uint64_t, uidobjs, 1);

	return uidobjs;
}

/* Space Accounting Management */

/**
 * osd_acct_index_lookup() - Get space usage consumed by a given uid/gid/projid.
 * @env: is the environment passed by the caller
 * @dtobj: is the accounting object
 * @dtrec: is the record to fill with space usage information
 * @dtkey: is the id the of the user or group for which we would like to access
 *         disk usage.
 *
 * Return space usage consumed by a given uid or gid or projid.
 * Block usage is accurrate since it is maintained by DMU itself.
 * However, DMU does not provide inode accounting, so the #inodes in use
 * is estimated from the block usage and statfs information.
 *
 * Return:
 * * %positive success (exact match)
 * * %negative failure
 */
static int osd_acct_index_lookup(const struct lu_env *env,
				struct dt_object *dtobj,
				struct dt_rec *dtrec,
				const struct dt_key *dtkey)
{
	struct osd_thread_info *info = osd_oti_get(env);
	char *buf = info->oti_buf;
	struct lquota_acct_rec *rec = (struct lquota_acct_rec *)dtrec;
	struct osd_object *obj = osd_dt_obj(dtobj);
	struct osd_device *osd = osd_obj2dev(obj);
	dnode_t *dn = obj->oo_dn;
	size_t buflen = sizeof(info->oti_buf);
	int rc;

	ENTRY;
	rec->bspace = rec->ispace = 0;

	/* convert the 64-bit uid/gid/projid into a string */
	snprintf(buf, buflen, "%llx", *((__u64 *)dtkey));
	if (unlikely(!dn)) {
		CDEBUG(D_QUOTA, "%s: miss accounting obj for %s\n",
		       osd->od_svname, buf);

		RETURN(-ENOENT);
	}

	/* disk usage (in bytes) is maintained by DMU.
	 * DMU_USERUSED_OBJECT/DMU_GROUPUSED_OBJECT are special objects which
	 * not associated with any dmu_but_t (see dnode_special_open()).
	 */
	rc = osd_zap_lookup(osd, dn->dn_object, dn, buf, sizeof(uint64_t), 1,
			    &rec->bspace);
	if (rc == -ENOENT) {
		/* user/group/project has not created anything yet */
		CDEBUG(D_QUOTA, "%s: id %s not found in DMU accounting ZAP\n",
		       osd->od_svname, buf);
		/* -ENOENT is normal case, convert it as 1. */
		rc = 1;
	} else if (rc) {
		RETURN(rc);
	}

	if (!osd_dmu_userobj_accounting_available(osd)) {
		if (rec->bspace != 0)
			/* estimate #inodes in use */
			rec->ispace = osd_objset_user_iused(osd, rec->bspace);
		rc = 1;
	} else {
		snprintf(buf, buflen, DMU_OBJACCT_PREFIX "%llx",
			 *((__u64 *)dtkey));
		rc = osd_zap_lookup(osd, dn->dn_object, dn, buf,
				    sizeof(uint64_t), 1, &rec->ispace);
		if (rc == -ENOENT) {
			CDEBUG(D_QUOTA,
			       "%s: id %s not found dnode accounting\n",
			       osd->od_svname, buf);
			/* -ENOENT is normal case, convert it as 1. */
			rc = 1;
		} else if (rc == 0) {
			rc = 1;
		}
	}

	RETURN(rc);
}

/**
 * osd_it_acct_init() - Initialize osd Iterator for given osd index object.
 * @env: Lustre environment
 * @dt: osd index object
 * @attr: not used
 *
 * Return struct di_it on success or %negative on failure
 */
static struct dt_it *osd_it_acct_init(const struct lu_env *env,
				      struct dt_object *dt,
				      __u32 attr)
{
	struct osd_thread_info *info = osd_oti_get(env);
	struct osd_it_quota *it;
	struct osd_object *obj = osd_dt_obj(dt);
	struct osd_device *osd = osd_obj2dev(obj);
	dnode_t *dn = obj->oo_dn;
	int rc;

	ENTRY;
	if (unlikely(!dn)) {
		CDEBUG(D_QUOTA, "%s: Not found in DMU accounting ZAP\n",
		       osd->od_svname);

		RETURN(ERR_PTR(-ENOENT));
	}

	if (info == NULL)
		RETURN(ERR_PTR(-ENOMEM));

	OBD_ALLOC_PTR(it);
	if (it == NULL)
		RETURN(ERR_PTR(-ENOMEM));

	memset(it, 0, sizeof(*it));
	it->oiq_oid = dn->dn_object;

	/* initialize zap cursor */
	rc = osd_zap_cursor_init(&it->oiq_zc, osd->od_os, it->oiq_oid, 0);
	if (rc != 0) {
		OBD_FREE_PTR(it);
		RETURN(ERR_PTR(rc));
	}

	/* take object reference */
	lu_object_get(&dt->do_lu);
	it->oiq_obj   = osd_dt_obj(dt);
	it->oiq_reset = 1;

	RETURN((struct dt_it *)it);
}

/*
 * osd_it_acct_fini() - Free given iterator.
 * @env: Lustre environment
 * @di: osd iterator
 */
static void osd_it_acct_fini(const struct lu_env *env, struct dt_it *di)
{
	struct osd_it_quota *it = (struct osd_it_quota *)di;

	ENTRY;
	osd_zap_cursor_fini(it->oiq_zc);
	osd_object_put(env, it->oiq_obj);
	OBD_FREE_PTR(it);

	EXIT;
}

/**
 * osd_zap_locate() - Locate the first entry that is for space accounting.
 * @it: Iterator for quota
 * @za: ZFS attributes located [out]
 *
 * Return:
 * * %0 on success (ZAP entry found)
 * * %-ENOENT (ZAP entry not found)
 * * %negative on other error
 */
static int osd_zap_locate(struct osd_it_quota *it, zap_attribute_t *za)
{
	int rc;

	ENTRY;
	while (1) {
		rc = -zap_cursor_retrieve(it->oiq_zc, za);
		if (rc)
			break;

		if (strncmp(za->za_name, DMU_OBJACCT_PREFIX,
			    DMU_OBJACCT_PREFIX_LEN))
			break;

		zap_cursor_advance(it->oiq_zc);
	}

	RETURN(rc);
}

/**
 * osd_it_acct_next() - Move on to the next valid entry.
 * @env: Lustre environment
 * @di: osd iterator
 *
 * Return:
 * * %positive iterator reached the end
 * * %0 iterator has not reached the end yet
 * * %negitive unexpected failure
 */
static int osd_it_acct_next(const struct lu_env *env, struct dt_it *di)
{
	struct osd_it_quota *it = (struct osd_it_quota *)di;
	zap_attribute_t *za = &osd_oti_get(env)->oti_za;
	int rc;

	ENTRY;
	if (it->oiq_reset == 0)
		zap_cursor_advance(it->oiq_zc);
	it->oiq_reset = 0;

	rc = osd_zap_locate(it, za);
	RETURN(rc == -ENOENT ? 1 : rc);
}

/**
 * osd_it_acct_key() - Get pointer to the key under iterator.
 * @env: Lustre environment
 * @di: osd iterator
 *
 * Return pointer to the key under iterator.
 */
static struct dt_key *osd_it_acct_key(const struct lu_env *env,
				      const struct dt_it *di)
{
	struct osd_it_quota *it = (struct osd_it_quota *)di;
	zap_attribute_t *za = &osd_oti_get(env)->oti_za;
	int rc;

	ENTRY;
	it->oiq_reset = 0;
	rc = osd_zap_locate(it, za);
	if (rc)
		RETURN(ERR_PTR(rc));

	rc = kstrtoull(za->za_name, 16, &it->oiq_id);
	if (rc)
		CERROR("couldn't parse name %s: rc = %d\n", za->za_name, rc);

	RETURN((struct dt_key *) &it->oiq_id);
}

/**
 * osd_it_acct_key_size() - Get size of key under iterator (in bytes)
 * @env: Lustre environment
 * @di: osd iterator
 *
 * Returns size of key
 */
static int osd_it_acct_key_size(const struct lu_env *env,
				const struct dt_it *di)
{
	ENTRY;
	RETURN((int)sizeof(uint64_t));
}

/*
 * zap_cursor_retrieve read from current record.
 * to read bytes we need to call zap_lookup explicitly.
 */
static int osd_zap_cursor_retrieve_value(const struct lu_env *env,
					 struct osd_it_quota *it,
					 char *buf, int buf_size,
					 int *bytes_read)
{
	const struct lu_fid *fid = lu_object_fid(&it->oiq_obj->oo_dt.do_lu);
	zap_attribute_t *za = &osd_oti_get(env)->oti_za;
	zap_cursor_t *zc = it->oiq_zc;
	struct osd_device *osd = osd_obj2dev(it->oiq_obj);
	uint64_t acct_obj;
	int rc, actual_size;

	rc = -zap_cursor_retrieve(zc, za);
	if (unlikely(rc != 0))
		return rc;

	if (unlikely(za->za_integer_length <= 0))
		return -ERANGE;

	actual_size = za->za_integer_length * za->za_num_integers;

	if (actual_size > buf_size) {
		actual_size = buf_size;
		buf_size = actual_size / za->za_integer_length;
	} else {
		buf_size = za->za_num_integers;
	}

	/* use correct special ID to request bytes used */
	if (fid_oid(fid) == ACCT_USER_OID)
		acct_obj = DMU_USERUSED_OBJECT;
	else if (fid_oid(fid) == ACCT_GROUP_OID)
		acct_obj = DMU_GROUPUSED_OBJECT;
	else
		acct_obj = DMU_PROJECTUSED_OBJECT;

	rc = osd_zap_lookup(osd, acct_obj, NULL, za->za_name,
			    za->za_integer_length, buf_size, buf);
	if (likely(rc == 0))
		*bytes_read = actual_size;

	return rc;
}

/**
 * osd_it_acct_rec() - Return pointer to the record under iterator.
 * @env: Lustre environment
 * @di: osd iterator
 * @dtrec: record fill with accounting information [out]
 * @attr: Unused
 *
 * * Return:
 * * %0 on success
 * * %negative on failure
 */
static int osd_it_acct_rec(const struct lu_env *env,
			   const struct dt_it *di,
			   struct dt_rec *dtrec, __u32 attr)
{
	struct osd_thread_info *info = osd_oti_get(env);
	zap_attribute_t *za = &info->oti_za;
	struct osd_it_quota *it = (struct osd_it_quota *)di;
	struct lquota_acct_rec *rec = (struct lquota_acct_rec *)dtrec;
	struct osd_object *obj = it->oiq_obj;
	struct osd_device *osd = osd_obj2dev(obj);
	int bytes_read;
	int rc;

	ENTRY;
	it->oiq_reset = 0;
	rec->ispace = rec->bspace = 0;

	/* retrieve block usage from the DMU accounting object */
	rc = osd_zap_cursor_retrieve_value(env, it, (char *)&rec->bspace,
					   sizeof(uint64_t), &bytes_read);
	if (rc)
		RETURN(rc);

	if (!osd_dmu_userobj_accounting_available(osd)) {
		if (rec->bspace != 0)
			/* estimate #inodes in use */
			rec->ispace = osd_objset_user_iused(osd, rec->bspace);
		RETURN(0);
	}

	/* retrieve key associated with the current cursor */
	rc = -zap_cursor_retrieve(it->oiq_zc, za);
	if (unlikely(rc != 0))
		RETURN(rc);

	/* inode accounting is maintained by DMU since 0.7.0 */
	strncpy(info->oti_buf, DMU_OBJACCT_PREFIX,
		DMU_OBJACCT_PREFIX_LEN);
	strscpy(info->oti_buf + DMU_OBJACCT_PREFIX_LEN, za->za_name,
		sizeof(info->oti_buf) - DMU_OBJACCT_PREFIX_LEN);
	rc = osd_zap_lookup(osd, it->oiq_obj->oo_dn->dn_object,
			    it->oiq_obj->oo_dn, info->oti_buf, sizeof(uint64_t),
			    1, &rec->ispace);
	if (rc == -ENOENT)
		/* user/group has not created any file yet */
		CDEBUG(D_QUOTA, "%s: id %s not found in accounting ZAP\n",
		       osd->od_svname, info->oti_buf);
	else if (rc)
		RETURN(rc);

	RETURN(0);
}

/**
 * osd_it_acct_store() - Returns cookie for current Iterator position.
 * @env: Lustre environment
 * @di: osd iterator
 *
 * Returns cookie/hash representing iterator state
 */
static __u64 osd_it_acct_store(const struct lu_env *env,
			       const struct dt_it *di)
{
	struct osd_it_quota *it = (struct osd_it_quota *)di;

	ENTRY;
	it->oiq_reset = 0;
	RETURN(osd_zap_cursor_serialize(it->oiq_zc));
}

/**
 * osd_it_acct_load() - Restore iterator from cookie
 * @env: Lustre environment
 * @di: osd iterator
 * @hash: iterator location cookie
 *
 * Restore iterator from cookie. if the @hash isn't found,
 * restore the first valid record.
 *
 * Return:
 * * %positive @di points to exact matched key
 * * %0 @di points to the first valid record
 * * %negative on failure
 */
static int osd_it_acct_load(const struct lu_env *env,
			    const struct dt_it *di, __u64 hash)
{
	struct osd_it_quota *it = (struct osd_it_quota *)di;
	struct osd_device *osd = osd_obj2dev(it->oiq_obj);
	zap_attribute_t *za = &osd_oti_get(env)->oti_za;
	zap_cursor_t *zc;
	int rc;

	ENTRY;
	/* create new cursor pointing to the new hash */
	rc = osd_zap_cursor_init(&zc, osd->od_os, it->oiq_oid, hash);
	if (rc)
		RETURN(rc);
	osd_zap_cursor_fini(it->oiq_zc);
	it->oiq_zc = zc;
	it->oiq_reset = 0;

	rc = osd_zap_locate(it, za);
	if (rc == 0)
		rc = 1;
	else if (rc == -ENOENT)
		rc = 0;
	RETURN(rc);
}

/**
 * osd_it_acct_get() - Move Iterator to record specified by @key, if the @key
 *                     isn't found, move to the first valid record.
 * @env: Lustre environment
 * @di: osd iterator
 * @key: uid or gid or projid
 *
 * Return:
 * * %positive @di points to exact matched key
 * * %0 @di points to the first valid record
 * * %negative on failure
 */
static int osd_it_acct_get(const struct lu_env *env, struct dt_it *di,
		const struct dt_key *key)
{
	ENTRY;

	/* XXX: like osd_zap_it_get(), API is currently broken */
	LASSERT(*((__u64 *)key) == 0);

	RETURN(osd_it_acct_load(env, di, 0));
}

/*
 * osd_id_acct_put() - Release Iterator
 * @env: Lustre environment
 * @di: osd iterator
 */
static void osd_it_acct_put(const struct lu_env *env, struct dt_it *di)
{
}

/* Index and Iterator operations for accounting objects */
const struct dt_index_operations osd_acct_index_ops = {
	.dio_lookup = osd_acct_index_lookup,
	.dio_it     = {
		.init		= osd_it_acct_init,
		.fini		= osd_it_acct_fini,
		.get		= osd_it_acct_get,
		.put		= osd_it_acct_put,
		.next		= osd_it_acct_next,
		.key		= osd_it_acct_key,
		.key_size	= osd_it_acct_key_size,
		.rec		= osd_it_acct_rec,
		.store		= osd_it_acct_store,
		.load		= osd_it_acct_load
	}
};

/* Quota Enforcement Management */

/**
 * osd_declare_quota() - Wrapper for qsd_op_begin().
 * @env: the environment passed by the caller
 * @osd: is the osd_device
 * @uid: user id of the inode
 * @gid: group id of the inode
 * @projid: project id of the inode
 * @space: how many blocks/inodes will be consumed/released
 * @oh: osd transaction handle
 * @local_flags: if the operation is write, return no user quota, no group
 *               quota, or sync commit flags to the caller
 * @osd_qid_declare_flags: indicate this is a inode/block accounting and whether
 *                         changes are performed by root user
 *
 * Return:
 * * %0 on success
 * * %negative on failure
 */
int osd_declare_quota(const struct lu_env *env, struct osd_device *osd,
		      qid_t uid, qid_t gid, qid_t projid, long long space,
		      struct osd_thandle *oh,
		      enum osd_quota_local_flags *local_flags,
		      enum osd_qid_declare_flags osd_qid_declare_flags)
{
	struct osd_thread_info *info = osd_oti_get(env);
	struct lquota_id_info *qi = &info->oti_qi;
	struct qsd_instance *qsd = NULL;
	int rcu, rcg, rcp = 0; /* user & group & project rc */
	struct thandle *th = &oh->ot_super;
	enum osd_quota_local_flags tmp_flags;
	bool force = !!(osd_qid_declare_flags & OSD_QID_FORCE) ||
			th->th_ignore_quota;

	ENTRY;
	/* very fast path for special files like llog */
	if (uid == 0 && gid == 0 && projid == 0)
		return 0;

	if (osd_qid_declare_flags & OSD_QID_INODE)
		qsd = osd->od_quota_slave_md;
	else if (osd_qid_declare_flags & OSD_QID_BLK)
		qsd = osd->od_quota_slave_dt;
	else
		RETURN(0);

	if (unlikely(qsd == NULL))
		/* quota slave instance hasn't been allocated yet */
		RETURN(0);

	/* let's start with user quota */
	qi->lqi_id.qid_uid = uid;
	qi->lqi_type       = USRQUOTA;
	qi->lqi_space      = space;
	qi->lqi_is_blk     = !!(osd_qid_declare_flags & OSD_QID_BLK);
	rcu = qsd_op_begin(env, qsd, &oh->ot_quota_trans, qi, local_flags);
	if (force && (rcu == -EDQUOT || rcu == -EINPROGRESS))
		/* ignore EDQUOT & EINPROGRESS when changes are done by root */
		rcu = 0;

	/* For non-fatal error, we want to continue to get the noquota flags
	 * for group id. This is only for commit write, which has @flags passed
	 * in. See osd_declare_write_commit().
	 * When force is set to true, we also want to proceed with the gid
	 */
	if (rcu && (rcu != -EDQUOT || local_flags == NULL))
		RETURN(rcu);

	/* and now group quota */
	qi->lqi_id.qid_gid = gid;
	qi->lqi_type       = GRPQUOTA;
	rcg = qsd_op_begin(env, qsd, &oh->ot_quota_trans, qi, local_flags);
	if (force && (rcg == -EDQUOT || rcg == -EINPROGRESS))
		/* as before, ignore EDQUOT & EINPROGRESS for root */
		rcg = 0;

#ifdef ZFS_PROJINHERIT
	if (rcg && (rcg != -EDQUOT || local_flags == NULL))
		RETURN(rcg);

	/* for project quota */
	if (osd->od_projectused_dn) {
		qi->lqi_id.qid_projid = projid;
		qi->lqi_ignore_root_proj_quota = th->th_ignore_root_proj_quota;
		qi->lqi_type = PRJQUOTA;

		tmp_flags = 0;
		if (local_flags)
			tmp_flags = *local_flags;
		rcp = qsd_op_begin(env, qsd, &oh->ot_quota_trans, qi,
				   &tmp_flags);
		if (tmp_flags & QUOTA_FL_ROOT_PRJQUOTA &&
		    !(osd_qid_declare_flags & OSD_QID_IGNORE_ROOT_PRJ))
			/* Currently, th_ignore_quota is only set for inode
			 * quota in mdd_trans_create if the user has
			 * CAP_SYS_RESOURCE, then it should be ignored if
			 * root_prj_enable is set.
			 */
			force = 0;
		if (local_flags)
			*local_flags = tmp_flags;
		if (force && (rcp == -EDQUOT || rcp == -EINPROGRESS))
			rcp = 0;
	}
#endif

	RETURN(rcu ? rcu : (rcg ? rcg : rcp));
}