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