diff mbox series

[v4,5/6] syscalls/quotactl09: Test error when quota info hidden in filesystem

Message ID 1639380414-24390-5-git-send-email-xuyang2018.jy@fujitsu.com
State Superseded
Headers show
Series [v4,1/6] syscalls/quotactl08: Test quoatctl01 but quota info hidden in filesystem | expand

Commit Message

Yang Xu \(Fujitsu\) Dec. 13, 2021, 7:26 a.m. UTC
This case is similar to quotactl06 but only two differences
1) use quotactl and quotactl_fd syscalls without visible quota file
2) remove some error for addr argument

Signed-off-by: Yang Xu <xuyang2018.jy@fujitsu.com>
---
 runtest/syscalls                              |   1 +
 testcases/kernel/syscalls/quotactl/.gitignore |   1 +
 .../kernel/syscalls/quotactl/quotactl09.c     | 180 ++++++++++++++++++
 3 files changed, 182 insertions(+)
 create mode 100644 testcases/kernel/syscalls/quotactl/quotactl09.c

Comments

Cyril Hrubis Jan. 7, 2022, 3:50 p.m. UTC | #1
Hi!
> diff --git a/testcases/kernel/syscalls/quotactl/quotactl09.c b/testcases/kernel/syscalls/quotactl/quotactl09.c
> new file mode 100644
> index 000000000..c7c485077
> --- /dev/null
> +++ b/testcases/kernel/syscalls/quotactl/quotactl09.c
> @@ -0,0 +1,180 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +/*
> + * Copyright (c) 2021 FUJITSU LIMITED. All rights reserved.
> + * Author: Yang Xu <xuyang2018.jy@fujitsu.com>
> + */
> +
> +/*\
> + * [Description]
> + *
> + * Tests basic error handling of the quotactl syscall without visible quota files
> + * (use quotactl and quotactl_fd syscall):
> + *
> + * - EFAULT when addr or special is invalid
> + * - EINVAL when cmd or type is invalid
> + * - ENOTBLK when special is not a block device
> + * - ERANGE when cmd is Q_SETQUOTA, but the specified limits are out of the range
> + *   allowed by the quota format
> + * - EPERM when the caller lacked the required privilege (CAP_SYS_ADMIN) for the
> + *   specified operation
> + *
> + * Minimum e2fsprogs version required is 1.43.
> + */
> +
> +#include <errno.h>
> +#include <sys/quota.h>
> +#include "tst_test.h"
> +#include "tst_capability.h"
> +#include "quotactl_syscall_var.h"
> +
> +#define OPTION_INVALID 999
> +
> +static int32_t fmt_id = QFMT_VFS_V1;
> +static int test_id, mount_flag;
> +static int getnextquota_nsup;
> +
> +static struct if_nextdqblk res_ndq;
> +
> +static struct dqblk set_dqmax = {
> +	.dqb_bsoftlimit = 0x7fffffffffffffffLL,  /* 2^63-1 */
> +	.dqb_valid = QIF_BLIMITS
> +};
> +
> +static struct tst_cap dropadmin = {
> +	.action = TST_CAP_DROP,
> +	.id = CAP_SYS_ADMIN,
> +	.name = "CAP_SYS_ADMIN",
> +};
> +
> +static struct tst_cap needadmin = {
> +	.action = TST_CAP_REQ,
> +	.id = CAP_SYS_ADMIN,
> +	.name = "CAP_SYS_ADMIN",
> +};
> +
> +static struct tcase {
> +	int cmd;
> +	int *id;
> +	void *addr;
> +	int exp_err;
> +	int on_flag;
> +} tcases[] = {
> +	{QCMD(Q_SETQUOTA, USRQUOTA), &fmt_id, NULL, EFAULT, 1},
> +	{QCMD(OPTION_INVALID, USRQUOTA), &fmt_id, NULL, EINVAL, 0},
> +	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, NULL, ENOTBLK, 0},
> +	{QCMD(Q_SETQUOTA, USRQUOTA), &test_id, &set_dqmax, ERANGE, 1},
> +	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, NULL, EPERM, 0},
> +};
> +
> +static void verify_quotactl(unsigned int n)
> +{
> +	struct tcase *tc = &tcases[n];
> +	int quota_on = 0;
> +	int drop_flag = 0;
> +
> +	if (tc->cmd == QCMD(Q_GETNEXTQUOTA, USRQUOTA) && getnextquota_nsup) {
> +		tst_res(TCONF, "current system doesn't support Q_GETNEXTQUOTA");
> +		return;
> +	}
> +
> +	if (tc->on_flag) {
> +		TEST(do_quotactl(fd, QCMD(Q_QUOTAON, USRQUOTA), tst_device->dev,
> +			fmt_id, NULL));
> +		if (TST_RET == -1)
> +			tst_brk(TBROK,
> +				"quotactl with Q_QUOTAON returned %ld", TST_RET);
> +		quota_on = 1;
> +	}
> +
> +	if (tc->exp_err == EPERM) {
> +		tst_cap_action(&dropadmin);
> +		drop_flag = 1;
> +	}
> +
> +	if (tst_variant) {
> +		if (tc->exp_err == ENOTBLK) {
> +			tst_res(TCONF, "quotactl_fd() doesn't have this error, skip");
> +			return;
> +		}
> +	}
> +	if (tc->exp_err == ENOTBLK)
> +		TEST(do_quotactl(fd, tc->cmd, "/dev/null", *tc->id, tc->addr));
> +	else
> +		TEST(do_quotactl(fd, tc->cmd, tst_device->dev, *tc->id, tc->addr));

How does this work for the tst_variant == 1? We still do pass the fd
pointing to device right?

Actually one way to simplify the whole patchset would be to open() and
close() the device in each do_quotactl() call in the case that
tst_variant == 1. We wouldn't have to pass the file descriptor to the
function and everything will just work fine.

> +	if (TST_RET == -1) {
> +		if (tc->exp_err == TST_ERR) {
> +			tst_res(TPASS | TTERRNO, "quotactl failed as expected");
> +		} else {
> +			tst_res(TFAIL | TTERRNO,
> +				"quotactl failed unexpectedly; expected %s, but got",
> +				tst_strerrno(tc->exp_err));
> +		}
> +	} else {
> +		tst_res(TFAIL, "quotactl returned wrong value: %ld", TST_RET);
> +	}

Use TST_EXP_FAIL() in new testcases please.

> +	if (quota_on) {
> +		TEST(do_quotactl(fd, QCMD(Q_QUOTAOFF, USRQUOTA), tst_device->dev,
> +			fmt_id, NULL));
> +		if (TST_RET == -1)
> +			tst_brk(TBROK,
> +				"quotactl with Q_QUOTAOFF returned %ld", TST_RET);
> +		quota_on = 0;
> +	}
> +
> +	if (drop_flag) {
> +		tst_cap_action(&needadmin);
> +		drop_flag = 0;
> +	}
> +}
> +
> +static void setup(void)
> +{
> +	unsigned int i;
> +	const char *const fs_opts[] = { "-O quota", NULL};
> +
> +	quotactl_info();
> +	SAFE_MKFS(tst_device->dev, tst_device->fs_type, fs_opts, NULL);
> +	SAFE_MOUNT(tst_device->dev, MNTPOINT, tst_device->fs_type, 0, NULL);
> +	mount_flag = 1;
> +
> +	fd = SAFE_OPEN(MNTPOINT, O_RDONLY);
> +	TEST(do_quotactl(fd, QCMD(Q_GETNEXTQUOTA, USRQUOTA), tst_device->dev,
> +		test_id, (void *) &res_ndq));
> +	if (TST_ERR == EINVAL || TST_ERR == ENOSYS)
> +		getnextquota_nsup = 1;
> +
> +	for (i = 0; i < ARRAY_SIZE(tcases); i++) {
> +		if (!tcases[i].addr)
> +			tcases[i].addr = tst_get_bad_addr(NULL);
> +	}
> +}
> +
> +static void cleanup(void)
> +{
> +	if (fd > -1)
> +		SAFE_CLOSE(fd);
> +	if (mount_flag && tst_umount(MNTPOINT))
> +		tst_res(TWARN | TERRNO, "umount(%s)", MNTPOINT);
> +}
> +
> +static struct tst_test test = {
> +	.setup = setup,
> +	.cleanup = cleanup,
> +	.needs_kconfigs = (const char *[]) {
> +		"CONFIG_QFMT_V2",
> +		NULL
> +	},
> +	.tcnt = ARRAY_SIZE(tcases),
> +	.test = verify_quotactl,
> +	.dev_fs_type = "ext4",
> +	.mntpoint = MNTPOINT,
> +	.needs_device = 1,
> +	.needs_root = 1,
> +	.test_variants = QUOTACTL_SYSCALL_VARIANTS,
> +	.needs_cmds = (const char *[]) {
> +		"mkfs.ext4 >= 1.43.0",
> +		NULL
> +	}
> +};
> -- 
> 2.23.0
> 
> 
> -- 
> Mailing list info: https://lists.linux.it/listinfo/ltp
Yang Xu \(Fujitsu\) Jan. 10, 2022, 6:48 a.m. UTC | #2
Hi Cyril
> Hi!
>> diff --git a/testcases/kernel/syscalls/quotactl/quotactl09.c b/testcases/kernel/syscalls/quotactl/quotactl09.c
>> new file mode 100644
>> index 000000000..c7c485077
>> --- /dev/null
>> +++ b/testcases/kernel/syscalls/quotactl/quotactl09.c
>> @@ -0,0 +1,180 @@
>> +// SPDX-License-Identifier: GPL-2.0-or-later
>> +/*
>> + * Copyright (c) 2021 FUJITSU LIMITED. All rights reserved.
>> + * Author: Yang Xu<xuyang2018.jy@fujitsu.com>
>> + */
>> +
>> +/*\
>> + * [Description]
>> + *
>> + * Tests basic error handling of the quotactl syscall without visible quota files
>> + * (use quotactl and quotactl_fd syscall):
>> + *
>> + * - EFAULT when addr or special is invalid
>> + * - EINVAL when cmd or type is invalid
>> + * - ENOTBLK when special is not a block device
>> + * - ERANGE when cmd is Q_SETQUOTA, but the specified limits are out of the range
>> + *   allowed by the quota format
>> + * - EPERM when the caller lacked the required privilege (CAP_SYS_ADMIN) for the
>> + *   specified operation
>> + *
>> + * Minimum e2fsprogs version required is 1.43.
>> + */
>> +
>> +#include<errno.h>
>> +#include<sys/quota.h>
>> +#include "tst_test.h"
>> +#include "tst_capability.h"
>> +#include "quotactl_syscall_var.h"
>> +
>> +#define OPTION_INVALID 999
>> +
>> +static int32_t fmt_id = QFMT_VFS_V1;
>> +static int test_id, mount_flag;
>> +static int getnextquota_nsup;
>> +
>> +static struct if_nextdqblk res_ndq;
>> +
>> +static struct dqblk set_dqmax = {
>> +	.dqb_bsoftlimit = 0x7fffffffffffffffLL,  /* 2^63-1 */
>> +	.dqb_valid = QIF_BLIMITS
>> +};
>> +
>> +static struct tst_cap dropadmin = {
>> +	.action = TST_CAP_DROP,
>> +	.id = CAP_SYS_ADMIN,
>> +	.name = "CAP_SYS_ADMIN",
>> +};
>> +
>> +static struct tst_cap needadmin = {
>> +	.action = TST_CAP_REQ,
>> +	.id = CAP_SYS_ADMIN,
>> +	.name = "CAP_SYS_ADMIN",
>> +};
>> +
>> +static struct tcase {
>> +	int cmd;
>> +	int *id;
>> +	void *addr;
>> +	int exp_err;
>> +	int on_flag;
>> +} tcases[] = {
>> +	{QCMD(Q_SETQUOTA, USRQUOTA),&fmt_id, NULL, EFAULT, 1},
>> +	{QCMD(OPTION_INVALID, USRQUOTA),&fmt_id, NULL, EINVAL, 0},
>> +	{QCMD(Q_QUOTAON, USRQUOTA),&fmt_id, NULL, ENOTBLK, 0},
>> +	{QCMD(Q_SETQUOTA, USRQUOTA),&test_id,&set_dqmax, ERANGE, 1},
>> +	{QCMD(Q_QUOTAON, USRQUOTA),&fmt_id, NULL, EPERM, 0},
>> +};
>> +
>> +static void verify_quotactl(unsigned int n)
>> +{
>> +	struct tcase *tc =&tcases[n];
>> +	int quota_on = 0;
>> +	int drop_flag = 0;
>> +
>> +	if (tc->cmd == QCMD(Q_GETNEXTQUOTA, USRQUOTA)&&  getnextquota_nsup) {
>> +		tst_res(TCONF, "current system doesn't support Q_GETNEXTQUOTA");
>> +		return;
>> +	}
>> +
>> +	if (tc->on_flag) {
>> +		TEST(do_quotactl(fd, QCMD(Q_QUOTAON, USRQUOTA), tst_device->dev,
>> +			fmt_id, NULL));
>> +		if (TST_RET == -1)
>> +			tst_brk(TBROK,
>> +				"quotactl with Q_QUOTAON returned %ld", TST_RET);
>> +		quota_on = 1;
>> +	}
>> +
>> +	if (tc->exp_err == EPERM) {
>> +		tst_cap_action(&dropadmin);
>> +		drop_flag = 1;
>> +	}
>> +
>> +	if (tst_variant) {
>> +		if (tc->exp_err == ENOTBLK) {
>> +			tst_res(TCONF, "quotactl_fd() doesn't have this error, skip");
>> +			return;
>> +		}
>> +	}
>> +	if (tc->exp_err == ENOTBLK)
>> +		TEST(do_quotactl(fd, tc->cmd, "/dev/null", *tc->id, tc->addr));
>> +	else
>> +		TEST(do_quotactl(fd, tc->cmd, tst_device->dev, *tc->id, tc->addr));
>
> How does this work for the tst_variant == 1? We still do pass the fd
> pointing to device right?
Yes, but for tst_variant ==1 ,we don't use dev.
>
> Actually one way to simplify the whole patchset would be to open() and
> close() the device in each do_quotactl() call in the case that
> tst_variant == 1. We wouldn't have to pass the file descriptor to the
> function and everything will just work fine.
I don't think so, it needs to open/close fd each time. IMO, the argument 
cost is less than open/close files.

Best Regards
Yang Xu
>
>> +	if (TST_RET == -1) {
>> +		if (tc->exp_err == TST_ERR) {
>> +			tst_res(TPASS | TTERRNO, "quotactl failed as expected");
>> +		} else {
>> +			tst_res(TFAIL | TTERRNO,
>> +				"quotactl failed unexpectedly; expected %s, but got",
>> +				tst_strerrno(tc->exp_err));
>> +		}
>> +	} else {
>> +		tst_res(TFAIL, "quotactl returned wrong value: %ld", TST_RET);
>> +	}
>
> Use TST_EXP_FAIL() in new testcases please.
>
>> +	if (quota_on) {
>> +		TEST(do_quotactl(fd, QCMD(Q_QUOTAOFF, USRQUOTA), tst_device->dev,
>> +			fmt_id, NULL));
>> +		if (TST_RET == -1)
>> +			tst_brk(TBROK,
>> +				"quotactl with Q_QUOTAOFF returned %ld", TST_RET);
>> +		quota_on = 0;
>> +	}
>> +
>> +	if (drop_flag) {
>> +		tst_cap_action(&needadmin);
>> +		drop_flag = 0;
>> +	}
>> +}
>> +
>> +static void setup(void)
>> +{
>> +	unsigned int i;
>> +	const char *const fs_opts[] = { "-O quota", NULL};
>> +
>> +	quotactl_info();
>> +	SAFE_MKFS(tst_device->dev, tst_device->fs_type, fs_opts, NULL);
>> +	SAFE_MOUNT(tst_device->dev, MNTPOINT, tst_device->fs_type, 0, NULL);
>> +	mount_flag = 1;
>> +
>> +	fd = SAFE_OPEN(MNTPOINT, O_RDONLY);
>> +	TEST(do_quotactl(fd, QCMD(Q_GETNEXTQUOTA, USRQUOTA), tst_device->dev,
>> +		test_id, (void *)&res_ndq));
>> +	if (TST_ERR == EINVAL || TST_ERR == ENOSYS)
>> +		getnextquota_nsup = 1;
>> +
>> +	for (i = 0; i<  ARRAY_SIZE(tcases); i++) {
>> +		if (!tcases[i].addr)
>> +			tcases[i].addr = tst_get_bad_addr(NULL);
>> +	}
>> +}
>> +
>> +static void cleanup(void)
>> +{
>> +	if (fd>  -1)
>> +		SAFE_CLOSE(fd);
>> +	if (mount_flag&&  tst_umount(MNTPOINT))
>> +		tst_res(TWARN | TERRNO, "umount(%s)", MNTPOINT);
>> +}
>> +
>> +static struct tst_test test = {
>> +	.setup = setup,
>> +	.cleanup = cleanup,
>> +	.needs_kconfigs = (const char *[]) {
>> +		"CONFIG_QFMT_V2",
>> +		NULL
>> +	},
>> +	.tcnt = ARRAY_SIZE(tcases),
>> +	.test = verify_quotactl,
>> +	.dev_fs_type = "ext4",
>> +	.mntpoint = MNTPOINT,
>> +	.needs_device = 1,
>> +	.needs_root = 1,
>> +	.test_variants = QUOTACTL_SYSCALL_VARIANTS,
>> +	.needs_cmds = (const char *[]) {
>> +		"mkfs.ext4>= 1.43.0",
>> +		NULL
>> +	}
>> +};
>> --
>> 2.23.0
>>
>>
>> --
>> Mailing list info: https://lists.linux.it/listinfo/ltp
>
Cyril Hrubis Jan. 10, 2022, 2:46 p.m. UTC | #3
Hi!
> >> +	if (tc->exp_err == ENOTBLK)
> >> +		TEST(do_quotactl(fd, tc->cmd, "/dev/null", *tc->id, tc->addr));
> >> +	else
> >> +		TEST(do_quotactl(fd, tc->cmd, tst_device->dev, *tc->id, tc->addr));
> >
> > How does this work for the tst_variant == 1? We still do pass the fd
> > pointing to device right?
> Yes, but for tst_variant ==1 ,we don't use dev.

Well yes, but the fd points to the device, right? So we pass a fd that
points to a device and we expect ENOTBLK if tst_variant == 1 and
exp_err == ENOTBLK? That does not sound fine, what do I miss?
Yang Xu \(Fujitsu\) Jan. 11, 2022, 6:51 a.m. UTC | #4
Hi Cyril
> Hi!
>>>> +	if (tc->exp_err == ENOTBLK)
>>>> +		TEST(do_quotactl(fd, tc->cmd, "/dev/null", *tc->id, tc->addr));
>>>> +	else
>>>> +		TEST(do_quotactl(fd, tc->cmd, tst_device->dev, *tc->id, tc->addr));
>>>
>>> How does this work for the tst_variant == 1? We still do pass the fd
>>> pointing to device right?
>> Yes, but for tst_variant ==1 ,we don't use dev.
>
> Well yes, but the fd points to the device, right?
Yes, we get super block quota info stored in dev by using the fd.
>So we pass a fd that
> points to a device and we expect ENOTBLK if tst_variant == 1 and
> exp_err == ENOTBLK? That does not sound fine, what do I miss?
We skip ENOTBLK error test when tst_variant ==1

   	if (tst_variant) {
   		if (tc->exp_err == ENOTBLK) {
   			tst_res(TCONF, "quotactl_fd() doesn't have this error, skip");
   			return;
   		}
   	}
   	if (tc->exp_err == ENOTBLK)
   		TEST(do_quotactl(fd, tc->cmd, "/dev/null", *tc->id, tc->addr));
  	else
   		TEST(do_quotactl(fd, tc->cmd, tst_device->dev, *tc->id, tc->addr));

Best Regards
Yang Xu
>
Cyril Hrubis Jan. 11, 2022, 10:55 a.m. UTC | #5
Hi!
> >So we pass a fd that
> > points to a device and we expect ENOTBLK if tst_variant == 1 and
> > exp_err == ENOTBLK? That does not sound fine, what do I miss?
> We skip ENOTBLK error test when tst_variant ==1
> 
>    	if (tst_variant) {
>    		if (tc->exp_err == ENOTBLK) {
>    			tst_res(TCONF, "quotactl_fd() doesn't have this error, skip");
>    			return;
>    		}
>    	}

Ah, that's what I have missed. But still, what happen if we pass fd to a
regular file to the quotactl_fd()? Shouldn't we test that case too?
Yang Xu \(Fujitsu\) Jan. 12, 2022, 1:52 a.m. UTC | #6
Hi Cyril
> Hi!
>>> So we pass a fd that
>>> points to a device and we expect ENOTBLK if tst_variant == 1 and
>>> exp_err == ENOTBLK? That does not sound fine, what do I miss?
>> We skip ENOTBLK error test when tst_variant ==1
>>
>>     	if (tst_variant) {
>>     		if (tc->exp_err == ENOTBLK) {
>>     			tst_res(TCONF, "quotactl_fd() doesn't have this error, skip");
>>     			return;
>>     		}
>>     	}
>
> Ah, that's what I have missed. But still, what happen if we pass fd to a
> regular file to the quotactl_fd()? Shouldn't we test that case too?
Good suggestion.
Yes, AFAIK, the fd point to superblock, every subcmd will check quota 
feature bit or whether enable but return different errno.

I will test Q_QUOTAON with regular file fd because it is the most 
meaningful.

Best Regards
Yang Xu
>
Cyril Hrubis Jan. 12, 2022, 8:31 a.m. UTC | #7
Hi!
> > Ah, that's what I have missed. But still, what happen if we pass fd to a
> > regular file to the quotactl_fd()? Shouldn't we test that case too?
> Good suggestion.
> Yes, AFAIK, the fd point to superblock, every subcmd will check quota 
> feature bit or whether enable but return different errno.
> 
> I will test Q_QUOTAON with regular file fd because it is the most 
> meaningful.

And we can always try to pass a fd pointing to a socket, that shouldn't
be associated with any block device at all.
Yang Xu \(Fujitsu\) Jan. 12, 2022, 9:24 a.m. UTC | #8
Hi Cyril
> Hi!
>>> Ah, that's what I have missed. But still, what happen if we pass fd to a
>>> regular file to the quotactl_fd()? Shouldn't we test that case too?
>> Good suggestion.
>> Yes, AFAIK, the fd point to superblock, every subcmd will check quota
>> feature bit or whether enable but return different errno.
>>
>> I will test Q_QUOTAON with regular file fd because it is the most
>> meaningful.
>
> And we can always try to pass a fd pointing to a socket, that shouldn't
> be associated with any block device at all.
Yes, my old way use external fd deponds on tmp environmet(ie use 
xfs,ext4 with quota or with quota feature) and it make test unstable.

If using socket fd, it should report ENOSYS.

Best Regards
Yang Xu
>
diff mbox series

Patch

diff --git a/runtest/syscalls b/runtest/syscalls
index c795b9101..13a62e4a1 100644
--- a/runtest/syscalls
+++ b/runtest/syscalls
@@ -1072,6 +1072,7 @@  quotactl05 quotactl05
 quotactl06 quotactl06
 quotactl07 quotactl07
 quotactl08 quotactl08
+quotactl09 quotactl09
 
 read01 read01
 read02 read02
diff --git a/testcases/kernel/syscalls/quotactl/.gitignore b/testcases/kernel/syscalls/quotactl/.gitignore
index dab9b3420..94de2c8f2 100644
--- a/testcases/kernel/syscalls/quotactl/.gitignore
+++ b/testcases/kernel/syscalls/quotactl/.gitignore
@@ -6,3 +6,4 @@ 
 /quotactl06
 /quotactl07
 /quotactl08
+/quotactl09
diff --git a/testcases/kernel/syscalls/quotactl/quotactl09.c b/testcases/kernel/syscalls/quotactl/quotactl09.c
new file mode 100644
index 000000000..c7c485077
--- /dev/null
+++ b/testcases/kernel/syscalls/quotactl/quotactl09.c
@@ -0,0 +1,180 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (c) 2021 FUJITSU LIMITED. All rights reserved.
+ * Author: Yang Xu <xuyang2018.jy@fujitsu.com>
+ */
+
+/*\
+ * [Description]
+ *
+ * Tests basic error handling of the quotactl syscall without visible quota files
+ * (use quotactl and quotactl_fd syscall):
+ *
+ * - EFAULT when addr or special is invalid
+ * - EINVAL when cmd or type is invalid
+ * - ENOTBLK when special is not a block device
+ * - ERANGE when cmd is Q_SETQUOTA, but the specified limits are out of the range
+ *   allowed by the quota format
+ * - EPERM when the caller lacked the required privilege (CAP_SYS_ADMIN) for the
+ *   specified operation
+ *
+ * Minimum e2fsprogs version required is 1.43.
+ */
+
+#include <errno.h>
+#include <sys/quota.h>
+#include "tst_test.h"
+#include "tst_capability.h"
+#include "quotactl_syscall_var.h"
+
+#define OPTION_INVALID 999
+
+static int32_t fmt_id = QFMT_VFS_V1;
+static int test_id, mount_flag;
+static int getnextquota_nsup;
+
+static struct if_nextdqblk res_ndq;
+
+static struct dqblk set_dqmax = {
+	.dqb_bsoftlimit = 0x7fffffffffffffffLL,  /* 2^63-1 */
+	.dqb_valid = QIF_BLIMITS
+};
+
+static struct tst_cap dropadmin = {
+	.action = TST_CAP_DROP,
+	.id = CAP_SYS_ADMIN,
+	.name = "CAP_SYS_ADMIN",
+};
+
+static struct tst_cap needadmin = {
+	.action = TST_CAP_REQ,
+	.id = CAP_SYS_ADMIN,
+	.name = "CAP_SYS_ADMIN",
+};
+
+static struct tcase {
+	int cmd;
+	int *id;
+	void *addr;
+	int exp_err;
+	int on_flag;
+} tcases[] = {
+	{QCMD(Q_SETQUOTA, USRQUOTA), &fmt_id, NULL, EFAULT, 1},
+	{QCMD(OPTION_INVALID, USRQUOTA), &fmt_id, NULL, EINVAL, 0},
+	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, NULL, ENOTBLK, 0},
+	{QCMD(Q_SETQUOTA, USRQUOTA), &test_id, &set_dqmax, ERANGE, 1},
+	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, NULL, EPERM, 0},
+};
+
+static void verify_quotactl(unsigned int n)
+{
+	struct tcase *tc = &tcases[n];
+	int quota_on = 0;
+	int drop_flag = 0;
+
+	if (tc->cmd == QCMD(Q_GETNEXTQUOTA, USRQUOTA) && getnextquota_nsup) {
+		tst_res(TCONF, "current system doesn't support Q_GETNEXTQUOTA");
+		return;
+	}
+
+	if (tc->on_flag) {
+		TEST(do_quotactl(fd, QCMD(Q_QUOTAON, USRQUOTA), tst_device->dev,
+			fmt_id, NULL));
+		if (TST_RET == -1)
+			tst_brk(TBROK,
+				"quotactl with Q_QUOTAON returned %ld", TST_RET);
+		quota_on = 1;
+	}
+
+	if (tc->exp_err == EPERM) {
+		tst_cap_action(&dropadmin);
+		drop_flag = 1;
+	}
+
+	if (tst_variant) {
+		if (tc->exp_err == ENOTBLK) {
+			tst_res(TCONF, "quotactl_fd() doesn't have this error, skip");
+			return;
+		}
+	}
+	if (tc->exp_err == ENOTBLK)
+		TEST(do_quotactl(fd, tc->cmd, "/dev/null", *tc->id, tc->addr));
+	else
+		TEST(do_quotactl(fd, tc->cmd, tst_device->dev, *tc->id, tc->addr));
+
+	if (TST_RET == -1) {
+		if (tc->exp_err == TST_ERR) {
+			tst_res(TPASS | TTERRNO, "quotactl failed as expected");
+		} else {
+			tst_res(TFAIL | TTERRNO,
+				"quotactl failed unexpectedly; expected %s, but got",
+				tst_strerrno(tc->exp_err));
+		}
+	} else {
+		tst_res(TFAIL, "quotactl returned wrong value: %ld", TST_RET);
+	}
+
+	if (quota_on) {
+		TEST(do_quotactl(fd, QCMD(Q_QUOTAOFF, USRQUOTA), tst_device->dev,
+			fmt_id, NULL));
+		if (TST_RET == -1)
+			tst_brk(TBROK,
+				"quotactl with Q_QUOTAOFF returned %ld", TST_RET);
+		quota_on = 0;
+	}
+
+	if (drop_flag) {
+		tst_cap_action(&needadmin);
+		drop_flag = 0;
+	}
+}
+
+static void setup(void)
+{
+	unsigned int i;
+	const char *const fs_opts[] = { "-O quota", NULL};
+
+	quotactl_info();
+	SAFE_MKFS(tst_device->dev, tst_device->fs_type, fs_opts, NULL);
+	SAFE_MOUNT(tst_device->dev, MNTPOINT, tst_device->fs_type, 0, NULL);
+	mount_flag = 1;
+
+	fd = SAFE_OPEN(MNTPOINT, O_RDONLY);
+	TEST(do_quotactl(fd, QCMD(Q_GETNEXTQUOTA, USRQUOTA), tst_device->dev,
+		test_id, (void *) &res_ndq));
+	if (TST_ERR == EINVAL || TST_ERR == ENOSYS)
+		getnextquota_nsup = 1;
+
+	for (i = 0; i < ARRAY_SIZE(tcases); i++) {
+		if (!tcases[i].addr)
+			tcases[i].addr = tst_get_bad_addr(NULL);
+	}
+}
+
+static void cleanup(void)
+{
+	if (fd > -1)
+		SAFE_CLOSE(fd);
+	if (mount_flag && tst_umount(MNTPOINT))
+		tst_res(TWARN | TERRNO, "umount(%s)", MNTPOINT);
+}
+
+static struct tst_test test = {
+	.setup = setup,
+	.cleanup = cleanup,
+	.needs_kconfigs = (const char *[]) {
+		"CONFIG_QFMT_V2",
+		NULL
+	},
+	.tcnt = ARRAY_SIZE(tcases),
+	.test = verify_quotactl,
+	.dev_fs_type = "ext4",
+	.mntpoint = MNTPOINT,
+	.needs_device = 1,
+	.needs_root = 1,
+	.test_variants = QUOTACTL_SYSCALL_VARIANTS,
+	.needs_cmds = (const char *[]) {
+		"mkfs.ext4 >= 1.43.0",
+		NULL
+	}
+};