• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3  * Copyright (c) 2019-2021 FUJITSU LIMITED. All rights reserved.
4  * Author: Yang Xu <xuyang2018.jy@fujitsu.com>
5  */
6 
7 /*\
8  * [Description]
9  *
10  * Tests basic error handling of the quotactl syscall with visible quota files
11  * (cover two formats, vfsv0 and vfsv1):
12  *
13  * - EACCES when cmd is Q_QUOTAON and addr existed but not a regular file
14  * - ENOENT when the file specified by special or addr does not exist
15  * - EBUSY when cmd is Q_QUOTAON and another Q_QUOTAON had already been performed
16  * - EFAULT when addr or special is invalid
17  * - EINVAL when cmd or type is invalid
18  * - ENOTBLK when special is not a block device
19  * - ESRCH when no disk quota is found for the indicated user and quotas have not been
20  *   turned on for this fs
21  * - ESRCH when cmd is Q_QUOTAON, but the quota format was not found
22  * - ESRCH when cmd is Q_GETNEXTQUOTA, but there is no ID greater than or equal to id that
23  *   has an active quota
24  * - ERANGE when cmd is Q_SETQUOTA, but the specified limits are out of the range allowed
25  *   by the quota format
26  * - EPERM when the caller lacked the required privilege (CAP_SYS_ADMIN) for the specified
27  *   operation
28  *
29  * For ERANGE error, the vfsv0 and vfsv1 format's maximum quota limit setting have been
30  * fixed since the following kernel patch:
31  *
32  *  commit 7e08da50cf706151f324349f9235ebd311226997
33  *  Author: Jan Kara <jack@suse.cz>
34  *  Date:   Wed Mar 4 14:42:02 2015 +0100
35  *
36  *  quota: Fix maximum quota limit settings
37  */
38 
39 #include <errno.h>
40 #include <sys/quota.h>
41 #include "tst_test.h"
42 #include "quotactl_fmt_var.h"
43 #include "tst_capability.h"
44 
45 #define OPTION_INVALID 999
46 #define USRPATH MNTPOINT "/aquota.user"
47 #define MNTPOINT "mntpoint"
48 #define TESTDIR1 MNTPOINT "/testdir1"
49 #define TESTDIR2 MNTPOINT "/testdir2"
50 
51 static char usrpath[] = USRPATH;
52 static char testdir1[] = TESTDIR1;
53 static char testdir2[] = TESTDIR2;
54 static int32_t fmt_id;
55 static int32_t fmt_invalid = 999;
56 static int test_invalid = 1;
57 static int test_id;
58 static int getnextquota_nsup;
59 
60 static struct if_nextdqblk res_ndq;
61 static struct dqblk set_dq = {
62 	.dqb_bsoftlimit = 100,
63 	.dqb_valid = QIF_BLIMITS
64 };
65 
66 static struct dqblk set_dqmax = {
67 	.dqb_bsoftlimit = 0x7fffffffffffffffLL,  /* 2^63-1 */
68 	.dqb_valid = QIF_BLIMITS
69 };
70 
71 static struct tst_cap dropadmin = {
72 	.action = TST_CAP_DROP,
73 	.id = CAP_SYS_ADMIN,
74 	.name = "CAP_SYS_ADMIN",
75 };
76 
77 static struct tst_cap needadmin = {
78 	.action = TST_CAP_REQ,
79 	.id = CAP_SYS_ADMIN,
80 	.name = "CAP_SYS_ADMIN",
81 };
82 
83 static struct tcase {
84 	int cmd;
85 	int *id;
86 	void *addr;
87 	int exp_err;
88 	int on_flag;
89 	char *des;
90 } tcases[] = {
91 	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, testdir1, EACCES, 0,
92 	"EACCES when cmd is Q_QUOTAON and addr existed but not a regular file"},
93 
94 	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, testdir2, ENOENT, 0,
95 	"ENOENT when the file specified by special or addr does not exist"},
96 
97 	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, usrpath, EBUSY, 1,
98 	"EBUSY when cmd is Q_QUOTAON and another Q_QUOTAON had already been performed"},
99 
100 	{QCMD(Q_SETQUOTA, USRQUOTA), &fmt_id, NULL, EFAULT, 1,
101 	"EFAULT when addr or special is invalid"},
102 
103 	{QCMD(OPTION_INVALID, USRQUOTA), &fmt_id, usrpath, EINVAL, 0,
104 	"EINVAL when cmd or type is invalid"},
105 
106 	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, usrpath, ENOTBLK, 0,
107 	"ENOTBLK when special is not a block device"},
108 
109 	{QCMD(Q_SETQUOTA, USRQUOTA), &test_id, &set_dq, ESRCH, 0,
110 	"ESRCH is for Q_SETQUOTA but no quota found for the user or quotas are off"},
111 
112 	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_invalid, usrpath, ESRCH, 0,
113 	"ESRCH when cmd is Q_QUOTAON, but the quota format was not found"},
114 
115 	{QCMD(Q_GETNEXTQUOTA, USRQUOTA), &test_invalid, usrpath, ESRCH, 0,
116 	"ESRCH for Q_GETNEXTQUOTA, but the id was last one"},
117 
118 	{QCMD(Q_SETQUOTA, USRQUOTA), &test_id, &set_dqmax, ERANGE, 1,
119 	"ERANGE for Q_SETQUOTA, but the specified limits are out of range"},
120 
121 	{QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, usrpath, EPERM, 0,
122 	"EPERM when the caller lacks the required privilege (CAP_SYS_ADMIN)"},
123 };
124 
verify_quotactl(unsigned int n)125 static void verify_quotactl(unsigned int n)
126 {
127 	struct tcase *tc = &tcases[n];
128 	int quota_on = 0;
129 	int drop_flag = 0;
130 
131 	tst_res(TINFO, "Testing %s", tc->des);
132 	if (tc->cmd == QCMD(Q_GETNEXTQUOTA, USRQUOTA) && getnextquota_nsup) {
133 		tst_res(TCONF, "current system doesn't support Q_GETNEXTQUOTA");
134 		return;
135 	}
136 
137 	if (tc->on_flag) {
138 		TST_EXP_PASS_SILENT(quotactl(QCMD(Q_QUOTAON, USRQUOTA), tst_device->dev,
139 					fmt_id, usrpath), "quotactl with Q_QUOTAON");
140 		if (!TST_PASS)
141 			return;
142 		quota_on = 1;
143 	}
144 
145 	if (tc->exp_err == EPERM) {
146 		tst_cap_action(&dropadmin);
147 		drop_flag = 1;
148 	}
149 
150 	if (tc->exp_err == ENOTBLK)
151 		TST_EXP_FAIL(quotactl(tc->cmd, "/dev/null", *tc->id, tc->addr),
152 			ENOTBLK, "quotactl()");
153 	else
154 		TST_EXP_FAIL(quotactl(tc->cmd, tst_device->dev, *tc->id, tc->addr),
155 			tc->exp_err, "quotactl()");
156 
157 	if (quota_on) {
158 		TST_EXP_PASS_SILENT(quotactl(QCMD(Q_QUOTAOFF, USRQUOTA), tst_device->dev,
159 					fmt_id, usrpath), "quotactl with Q_QUOTAOFF");
160 		if (!TST_PASS)
161 			return;
162 	}
163 
164 	if (drop_flag)
165 		tst_cap_action(&needadmin);
166 }
167 
setup(void)168 static void setup(void)
169 {
170 	unsigned int i;
171 	const struct quotactl_fmt_variant *var = &fmt_variants[tst_variant];
172 	const char *const cmd[] = {"quotacheck", "-ugF", var->fmt_name, MNTPOINT, NULL};
173 
174 	tst_res(TINFO, "quotactl() with %s format", var->fmt_name);
175 	SAFE_CMD(cmd, NULL, NULL);
176 	fmt_id = var->fmt_id;
177 	/* vfsv0 block limit 2^42, vfsv1 block limit 2^63 - 1 */
178 	set_dqmax.dqb_bsoftlimit = tst_variant ? 0x20000000000000 : 0x100000000;
179 
180 	if (access(USRPATH, F_OK) == -1)
181 		tst_brk(TFAIL | TERRNO, "user quotafile didn't exist");
182 
183 	tst_require_quota_support(tst_device->dev, fmt_id, usrpath);
184 
185 	SAFE_MKDIR(TESTDIR1, 0666);
186 
187 	TEST(quotactl(QCMD(Q_GETNEXTQUOTA, USRQUOTA), tst_device->dev,
188 		test_id, (void *) &res_ndq));
189 	if (TST_ERR == EINVAL || TST_ERR == ENOSYS)
190 		getnextquota_nsup = 1;
191 
192 	for (i = 0; i < ARRAY_SIZE(tcases); i++) {
193 		if (!tcases[i].addr)
194 			tcases[i].addr = tst_get_bad_addr(NULL);
195 	}
196 }
197 
cleanup(void)198 static void cleanup(void)
199 {
200 	SAFE_UNLINK(USRPATH);
201 	SAFE_RMDIR(TESTDIR1);
202 }
203 
204 static struct tst_test test = {
205 	.setup = setup,
206 	.cleanup = cleanup,
207 	.needs_kconfigs = (const char *[]) {
208 		"CONFIG_QFMT_V2",
209 		NULL
210 	},
211 	.tcnt = ARRAY_SIZE(tcases),
212 	.test = verify_quotactl,
213 	.dev_fs_type = "ext4",
214 	.mntpoint = MNTPOINT,
215 	.mount_device = 1,
216 	.mnt_data = "usrquota",
217 	.needs_cmds = (const char *const []) {
218 		"quotacheck",
219 		NULL
220 	},
221 	.needs_root = 1,
222 	.test_variants = QUOTACTL_FMT_VARIANTS,
223 	.tags = (const struct tst_tag[]) {
224 		{"linux-git", "7e08da50cf70"},
225 		{}
226 	}
227 };
228