xfs: wire up Q_XGETNEXTQUOTA / get_nextdqblk

Add code to allow the Q_XGETNEXTQUOTA quotactl to quickly find
all active quotas by examining the quota inode, and skipping
over unallocated or uninitialized regions.

Userspace can then use this interface rather than i.e. a
getpwent() loop when asked to report all active quotas.

Signed-off-by: Eric Sandeen <sandeen@redhat.com>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
Signed-off-by: Dave Chinner <david@fromorbit.com>
This commit is contained in:
Eric Sandeen 2016-02-08 11:27:38 +11:00 committed by Dave Chinner
parent 8aa7d37ebf
commit 296c24e26e
5 changed files with 142 additions and 9 deletions

View file

@ -37,7 +37,7 @@ typedef __uint16_t xfs_qwarncnt_t;
#define XFS_DQ_PROJ 0x0002 /* project quota */
#define XFS_DQ_GROUP 0x0004 /* a group quota */
#define XFS_DQ_DIRTY 0x0008 /* dquot is dirty */
#define XFS_DQ_FREEING 0x0010 /* dquot is beeing torn down */
#define XFS_DQ_FREEING 0x0010 /* dquot is being torn down */
#define XFS_DQ_ALLTYPES (XFS_DQ_USER|XFS_DQ_PROJ|XFS_DQ_GROUP)
@ -116,6 +116,7 @@ typedef __uint16_t xfs_qwarncnt_t;
#define XFS_QMOPT_DQREPAIR 0x0001000 /* repair dquot if damaged */
#define XFS_QMOPT_GQUOTA 0x0002000 /* group dquot requested */
#define XFS_QMOPT_ENOSPC 0x0004000 /* enospc instead of edquot (prj) */
#define XFS_QMOPT_DQNEXT 0x0008000 /* return next dquot >= this ID */
/*
* flags to xfs_trans_mod_dquot to indicate which field needs to be

View file

@ -685,6 +685,56 @@ xfs_qm_dqread(
return error;
}
/*
* Advance to the next id in the current chunk, or if at the
* end of the chunk, skip ahead to first id in next allocated chunk
* using the SEEK_DATA interface.
*/
int
xfs_dq_get_next_id(
xfs_mount_t *mp,
uint type,
xfs_dqid_t *id,
loff_t eof)
{
struct xfs_inode *quotip;
xfs_fsblock_t start;
loff_t offset;
uint lock;
xfs_dqid_t next_id;
int error = 0;
/* Simple advance */
next_id = *id + 1;
/* If new ID is within the current chunk, advancing it sufficed */
if (next_id % mp->m_quotainfo->qi_dqperchunk) {
*id = next_id;
return 0;
}
/* Nope, next_id is now past the current chunk, so find the next one */
start = (xfs_fsblock_t)next_id / mp->m_quotainfo->qi_dqperchunk;
quotip = xfs_quota_inode(mp, type);
lock = xfs_ilock_data_map_shared(quotip);
offset = __xfs_seek_hole_data(VFS_I(quotip), XFS_FSB_TO_B(mp, start),
eof, SEEK_DATA);
if (offset < 0)
error = offset;
xfs_iunlock(quotip, lock);
/* -ENXIO is essentially "no more data" */
if (error)
return (error == -ENXIO ? -ENOENT: error);
/* Convert next data offset back to a quota id */
*id = XFS_B_TO_FSB(mp, offset) * mp->m_quotainfo->qi_dqperchunk;
return 0;
}
/*
* Given the file system, inode OR id, and type (UDQUOT/GDQUOT), return a
* a locked dquot, doing an allocation (if requested) as needed.
@ -705,6 +755,7 @@ xfs_qm_dqget(
struct xfs_quotainfo *qi = mp->m_quotainfo;
struct radix_tree_root *tree = xfs_dquot_tree(qi, type);
struct xfs_dquot *dqp;
loff_t eof = 0;
int error;
ASSERT(XFS_IS_QUOTA_RUNNING(mp));
@ -732,6 +783,21 @@ xfs_qm_dqget(
}
#endif
/* Get the end of the quota file if we need it */
if (flags & XFS_QMOPT_DQNEXT) {
struct xfs_inode *quotip;
xfs_fileoff_t last;
uint lock_mode;
quotip = xfs_quota_inode(mp, type);
lock_mode = xfs_ilock_data_map_shared(quotip);
error = xfs_bmap_last_offset(quotip, &last, XFS_DATA_FORK);
xfs_iunlock(quotip, lock_mode);
if (error)
return error;
eof = XFS_FSB_TO_B(mp, last);
}
restart:
mutex_lock(&qi->qi_tree_lock);
dqp = radix_tree_lookup(tree, id);
@ -745,6 +811,18 @@ xfs_qm_dqget(
goto restart;
}
/* uninit / unused quota found in radix tree, keep looking */
if (flags & XFS_QMOPT_DQNEXT) {
if (XFS_IS_DQUOT_UNINITIALIZED(dqp)) {
xfs_dqunlock(dqp);
mutex_unlock(&qi->qi_tree_lock);
error = xfs_dq_get_next_id(mp, type, &id, eof);
if (error)
return error;
goto restart;
}
}
dqp->q_nrefs++;
mutex_unlock(&qi->qi_tree_lock);
@ -771,6 +849,13 @@ xfs_qm_dqget(
if (ip)
xfs_ilock(ip, XFS_ILOCK_EXCL);
/* If we are asked to find next active id, keep looking */
if (error == -ENOENT && (flags & XFS_QMOPT_DQNEXT)) {
error = xfs_dq_get_next_id(mp, type, &id, eof);
if (!error)
goto restart;
}
if (error)
return error;
@ -821,6 +906,17 @@ xfs_qm_dqget(
qi->qi_dquots++;
mutex_unlock(&qi->qi_tree_lock);
/* If we are asked to find next active id, keep looking */
if (flags & XFS_QMOPT_DQNEXT) {
if (XFS_IS_DQUOT_UNINITIALIZED(dqp)) {
xfs_qm_dqput(dqp);
error = xfs_dq_get_next_id(mp, type, &id, eof);
if (error)
return error;
goto restart;
}
}
dqret:
ASSERT((ip == NULL) || xfs_isilocked(ip, XFS_ILOCK_EXCL));
trace_xfs_dqget_miss(dqp);

View file

@ -164,8 +164,8 @@ extern void xfs_qm_dqrele_all_inodes(struct xfs_mount *, uint);
/* quota ops */
extern int xfs_qm_scall_trunc_qfiles(struct xfs_mount *, uint);
extern int xfs_qm_scall_getquota(struct xfs_mount *, xfs_dqid_t,
uint, struct qc_dqblk *);
extern int xfs_qm_scall_getquota(struct xfs_mount *, xfs_dqid_t *,
uint, struct qc_dqblk *, uint);
extern int xfs_qm_scall_setqlim(struct xfs_mount *, xfs_dqid_t, uint,
struct qc_dqblk *);
extern int xfs_qm_scall_quotaon(struct xfs_mount *, uint);

View file

@ -635,9 +635,10 @@ xfs_qm_log_quotaoff(
int
xfs_qm_scall_getquota(
struct xfs_mount *mp,
xfs_dqid_t id,
xfs_dqid_t *id,
uint type,
struct qc_dqblk *dst)
struct qc_dqblk *dst,
uint dqget_flags)
{
struct xfs_dquot *dqp;
int error;
@ -647,7 +648,7 @@ xfs_qm_scall_getquota(
* we aren't passing the XFS_QMOPT_DOALLOC flag. If it doesn't
* exist, we'll get ENOENT back.
*/
error = xfs_qm_dqget(mp, NULL, id, type, 0, &dqp);
error = xfs_qm_dqget(mp, NULL, *id, type, dqget_flags, &dqp);
if (error)
return error;
@ -660,6 +661,9 @@ xfs_qm_scall_getquota(
goto out_put;
}
/* Fill in the ID we actually read from disk */
*id = be32_to_cpu(dqp->q_core.d_id);
memset(dst, 0, sizeof(*dst));
dst->d_spc_hardlimit =
XFS_FSB_TO_B(mp, be64_to_cpu(dqp->q_core.d_blk_hardlimit));
@ -701,7 +705,7 @@ xfs_qm_scall_getquota(
if (((XFS_IS_UQUOTA_ENFORCED(mp) && type == XFS_DQ_USER) ||
(XFS_IS_GQUOTA_ENFORCED(mp) && type == XFS_DQ_GROUP) ||
(XFS_IS_PQUOTA_ENFORCED(mp) && type == XFS_DQ_PROJ)) &&
id != 0) {
*id != 0) {
if ((dst->d_space > dst->d_spc_softlimit) &&
(dst->d_spc_softlimit > 0)) {
ASSERT(dst->d_spc_timer != 0);

View file

@ -231,14 +231,45 @@ xfs_fs_get_dqblk(
struct qc_dqblk *qdq)
{
struct xfs_mount *mp = XFS_M(sb);
xfs_dqid_t id;
if (!XFS_IS_QUOTA_RUNNING(mp))
return -ENOSYS;
if (!XFS_IS_QUOTA_ON(mp))
return -ESRCH;
return xfs_qm_scall_getquota(mp, from_kqid(&init_user_ns, qid),
xfs_quota_type(qid.type), qdq);
id = from_kqid(&init_user_ns, qid);
return xfs_qm_scall_getquota(mp, &id,
xfs_quota_type(qid.type), qdq, 0);
}
/* Return quota info for active quota >= this qid */
STATIC int
xfs_fs_get_nextdqblk(
struct super_block *sb,
struct kqid *qid,
struct qc_dqblk *qdq)
{
int ret;
struct xfs_mount *mp = XFS_M(sb);
xfs_dqid_t id;
if (!XFS_IS_QUOTA_RUNNING(mp))
return -ENOSYS;
if (!XFS_IS_QUOTA_ON(mp))
return -ESRCH;
id = from_kqid(&init_user_ns, *qid);
ret = xfs_qm_scall_getquota(mp, &id,
xfs_quota_type(qid->type), qdq,
XFS_QMOPT_DQNEXT);
if (ret)
return ret;
/* ID may be different, so convert back what we got */
*qid = make_kqid(current_user_ns(), qid->type, id);
return 0;
}
STATIC int
@ -267,5 +298,6 @@ const struct quotactl_ops xfs_quotactl_operations = {
.quota_disable = xfs_quota_disable,
.rm_xquota = xfs_fs_rm_xquota,
.get_dqblk = xfs_fs_get_dqblk,
.get_nextdqblk = xfs_fs_get_nextdqblk,
.set_dqblk = xfs_fs_set_dqblk,
};