diff mbox

[3/7] quota: add new quotactl Q_GETNEXTQUOTA

Message ID 1453435644-32261-4-git-send-email-sandeen@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Eric Sandeen Jan. 22, 2016, 4:07 a.m. UTC
Q_GETNEXTQUOTA is exactly like Q_GETQUOTA, except that it
will return quota information for the id equal to or greater
than the id requested.  In other words, if the requested id has
no quota, the command will return quota information for the
next higher id which does have a quota set.  If no higher id
has an active quota, -ESRCH is returned.

This allows filesystems to do efficient iteration in kernelspace,
much like extN filesystems do in userspace when asked to report
all active quotas.

This does require a new data structure for userspace, as the
current structure does not include an ID for the returned quota
information.

Today, Ext4 with a hidden quota inode requires getpwent-style
iterations, and for systems which have i.e. LDAP backends,
this can be very slow, or even impossible if iteration is not
allowed in the configuration.

Signed-off-by: Eric Sandeen <sandeen@redhat.com>
---
 fs/quota/compat.c          |   27 +++++++++++++++++++++++++++
 fs/quota/quota.c           |   31 +++++++++++++++++++++++++++++++
 include/uapi/linux/quota.h |   14 ++++++++++++++
 3 files changed, 72 insertions(+), 0 deletions(-)

Comments

Jan Kara Jan. 22, 2016, 9:28 a.m. UTC | #1
On Thu 21-01-16 22:07:20, Eric Sandeen wrote:
> Q_GETNEXTQUOTA is exactly like Q_GETQUOTA, except that it
> will return quota information for the id equal to or greater
> than the id requested.  In other words, if the requested id has
> no quota, the command will return quota information for the
> next higher id which does have a quota set.  If no higher id
> has an active quota, -ESRCH is returned.
> 
> This allows filesystems to do efficient iteration in kernelspace,
> much like extN filesystems do in userspace when asked to report
> all active quotas.
> 
> This does require a new data structure for userspace, as the
> current structure does not include an ID for the returned quota
> information.
> 
> Today, Ext4 with a hidden quota inode requires getpwent-style
> iterations, and for systems which have i.e. LDAP backends,
> this can be very slow, or even impossible if iteration is not
> allowed in the configuration.
> 
> Signed-off-by: Eric Sandeen <sandeen@redhat.com>

Same comments as for XFS version apply here. Furthermore:

> diff --git a/fs/quota/compat.c b/fs/quota/compat.c
> index fb1892f..80773a4 100644
> --- a/fs/quota/compat.c
> +++ b/fs/quota/compat.c
> @@ -19,6 +19,19 @@ struct compat_if_dqblk {
>  	compat_uint_t dqb_valid;
>  };
>  
> +struct compat_if_nextdqblk {
> +	compat_u64 dqb_bhardlimit;
> +	compat_u64 dqb_bsoftlimit;
> +	compat_u64 dqb_curspace;
> +	compat_u64 dqb_ihardlimit;
> +	compat_u64 dqb_isoftlimit;
> +	compat_u64 dqb_curinodes;
> +	compat_u64 dqb_btime;
> +	compat_u64 dqb_itime;
> +	compat_uint_t dqb_valid;
> +	compat_uint_t dqb_id;
> +};
> +

Is there a need for compat version of this structure? Everything is
naturally aligned and the size is a multiple of 8 bytes. But these things
keep surprising me... Added CC to linux-api in a hope that there's someone
who definitely knows.

On a side note when I have API people attention: AFAIU we need
struct compat_if_dqblk (defined in fs/quota/compat.c) because its size is
not multiple of 8 bytes, right? But what is then then reason for get_user()
/ put_user() when the structure is copied into userspace (see below)?
copy_in_user() should have copied everything...

               if (copy_in_user(compat_dqblk, dqblk, sizeof(*compat_dqblk)) ||
                        get_user(data, &dqblk->dqb_valid) ||
                        put_user(data, &compat_dqblk->dqb_valid))
                        ret = -EFAULT;


								Honza
Eric Sandeen Jan. 22, 2016, 1:58 p.m. UTC | #2
On 1/22/16 3:28 AM, Jan Kara wrote:
> On Thu 21-01-16 22:07:20, Eric Sandeen wrote:

...

> Same comments as for XFS version apply here. 

*nod*

> Furthermore:
> 
>> diff --git a/fs/quota/compat.c b/fs/quota/compat.c
>> index fb1892f..80773a4 100644
>> --- a/fs/quota/compat.c
>> +++ b/fs/quota/compat.c
>> @@ -19,6 +19,19 @@ struct compat_if_dqblk {
>>  	compat_uint_t dqb_valid;
>>  };
>>  
>> +struct compat_if_nextdqblk {
>> +	compat_u64 dqb_bhardlimit;
>> +	compat_u64 dqb_bsoftlimit;
>> +	compat_u64 dqb_curspace;
>> +	compat_u64 dqb_ihardlimit;
>> +	compat_u64 dqb_isoftlimit;
>> +	compat_u64 dqb_curinodes;
>> +	compat_u64 dqb_btime;
>> +	compat_u64 dqb_itime;
>> +	compat_uint_t dqb_valid;
>> +	compat_uint_t dqb_id;
>> +};
>> +
> 
> Is there a need for compat version of this structure? Everything is
> naturally aligned and the size is a multiple of 8 bytes. But these things
> keep surprising me... Added CC to linux-api in a hope that there's someone
> who definitely knows.

Ok, yeah, I wasn't sure.  I'll take all the compat stuff out of this one,
and can do a separate patch if needed, but I bet you're right, it's probably
not.  I did set up to test 32-compat calls, so I'll just test w/o this.

I'd be perfectly happy to drop it.  :)

Thanks for the review, Jan.

-Eric
--
To unsubscribe from this list: send the line "unsubscribe linux-fsdevel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
diff mbox

Patch

diff --git a/fs/quota/compat.c b/fs/quota/compat.c
index fb1892f..80773a4 100644
--- a/fs/quota/compat.c
+++ b/fs/quota/compat.c
@@ -19,6 +19,19 @@  struct compat_if_dqblk {
 	compat_uint_t dqb_valid;
 };
 
+struct compat_if_nextdqblk {
+	compat_u64 dqb_bhardlimit;
+	compat_u64 dqb_bsoftlimit;
+	compat_u64 dqb_curspace;
+	compat_u64 dqb_ihardlimit;
+	compat_u64 dqb_isoftlimit;
+	compat_u64 dqb_curinodes;
+	compat_u64 dqb_btime;
+	compat_u64 dqb_itime;
+	compat_uint_t dqb_valid;
+	compat_uint_t dqb_id;
+};
+
 /* XFS structures */
 struct compat_fs_qfilestat {
 	compat_u64 dqb_bhardlimit;
@@ -46,6 +59,8 @@  asmlinkage long sys32_quotactl(unsigned int cmd, const char __user *special,
 	unsigned int cmds;
 	struct if_dqblk __user *dqblk;
 	struct compat_if_dqblk __user *compat_dqblk;
+	struct if_nextdqblk __user *nxtdqblk;
+	struct compat_if_nextdqblk __user *compat_nextdqblk;
 	struct fs_quota_stat __user *fsqstat;
 	struct compat_fs_quota_stat __user *compat_fsqstat;
 	compat_uint_t data;
@@ -66,6 +81,18 @@  asmlinkage long sys32_quotactl(unsigned int cmd, const char __user *special,
 			put_user(data, &compat_dqblk->dqb_valid))
 			ret = -EFAULT;
 		break;
+	case Q_GETNEXTQUOTA:
+		nxtdqblk = compat_alloc_user_space(sizeof(struct if_nextdqblk));
+		compat_nextdqblk = addr;
+		ret = sys_quotactl(cmd, special, id, nxtdqblk);
+		if (ret)
+			break;
+		if (copy_in_user(compat_nextdqblk, nxtdqblk,
+				 sizeof(*compat_nextdqblk)) ||
+			get_user(data, &nxtdqblk->dqb_valid) ||
+			put_user(data, &compat_nextdqblk->dqb_valid))
+			ret = -EFAULT;
+		break;
 	case Q_SETQUOTA:
 		dqblk = compat_alloc_user_space(sizeof(struct if_dqblk));
 		compat_dqblk = addr;
diff --git a/fs/quota/quota.c b/fs/quota/quota.c
index 4bf8d40..85d8f7b 100644
--- a/fs/quota/quota.c
+++ b/fs/quota/quota.c
@@ -32,6 +32,7 @@  static int check_quotactl_permission(struct super_block *sb, int type, int cmd,
 		break;
 	/* allow to query information for dquots we "own" */
 	case Q_GETQUOTA:
+	case Q_GETNEXTQUOTA:
 	case Q_XGETQUOTA:
 	case Q_XGETNEXTQUOTA:
 		if ((type == USRQUOTA && uid_eq(current_euid(), make_kuid(current_user_ns(), id))) ||
@@ -218,6 +219,34 @@  static int quota_getquota(struct super_block *sb, int type, qid_t id,
 	if (ret)
 		return ret;
 	copy_to_if_dqblk(&idq, &fdq);
+	if (copy_to_user(addr, &idq, sizeof(struct if_dqblk)))
+		return -EFAULT;
+	return 0;
+}
+
+/*
+ * Return quota for next active quota >= this id, if any exists,
+ * otherwise return -ESRCH via ->get_nextdqblk
+ */
+static int quota_getnextquota(struct super_block *sb, int type, qid_t id,
+			  void __user *addr)
+{
+	struct kqid qid;
+	struct qc_dqblk fdq;
+	struct if_nextdqblk idq;
+	int ret;
+
+	if (!sb->s_qcop->get_nextdqblk)
+		return -ENOSYS;
+	qid = make_kqid(current_user_ns(), type, id);
+	if (!qid_valid(qid))
+		return -EINVAL;
+	ret = sb->s_qcop->get_nextdqblk(sb, qid, &fdq);
+	if (ret)
+		return ret;
+	/* struct if_nextdqblk is a superset of struct if_dqblk */
+	copy_to_if_dqblk((struct if_dqblk *)&idq, &fdq);
+	idq.dqb_id = fdq.d_id;
 	if (copy_to_user(addr, &idq, sizeof(idq)))
 		return -EFAULT;
 	return 0;
@@ -697,6 +726,8 @@  static int do_quotactl(struct super_block *sb, int type, int cmd, qid_t id,
 		return quota_setinfo(sb, type, addr);
 	case Q_GETQUOTA:
 		return quota_getquota(sb, type, id, addr);
+	case Q_GETNEXTQUOTA:
+		return quota_getnextquota(sb, type, id, addr);
 	case Q_SETQUOTA:
 		return quota_setquota(sb, type, id, addr);
 	case Q_SYNC:
diff --git a/include/uapi/linux/quota.h b/include/uapi/linux/quota.h
index 9c95b2c..38baddb 100644
--- a/include/uapi/linux/quota.h
+++ b/include/uapi/linux/quota.h
@@ -71,6 +71,7 @@ 
 #define Q_SETINFO  0x800006	/* set information about quota files */
 #define Q_GETQUOTA 0x800007	/* get user quota structure */
 #define Q_SETQUOTA 0x800008	/* set user quota structure */
+#define Q_GETNEXTQUOTA 0x800009	/* get disk limits and usage >= ID */
 
 /* Quota format type IDs */
 #define	QFMT_VFS_OLD 1
@@ -119,6 +120,19 @@  struct if_dqblk {
 	__u32 dqb_valid;
 };
 
+struct if_nextdqblk {
+	__u64 dqb_bhardlimit;
+	__u64 dqb_bsoftlimit;
+	__u64 dqb_curspace;
+	__u64 dqb_ihardlimit;
+	__u64 dqb_isoftlimit;
+	__u64 dqb_curinodes;
+	__u64 dqb_btime;
+	__u64 dqb_itime;
+	__u32 dqb_valid;
+	__u32 dqb_id;
+};
+
 /*
  * Structure used for setting quota information about file via quotactl
  * Following flags are used to specify which fields are valid