1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /*
3 * Copyright (c) 2019 FUJITSU LIMITED. All rights reserved.
4 * Author: Yang Xu <xuyang2018.jy@cn.fujitsu.com>
5 *
6 * Tests basic error handling of the quotactl syscall.
7 * 1) quotactl fails with EACCES when cmd is Q_QUOTAON and addr
8 * existed but not a regular file.
9 * 2) quotaclt fails with ENOENT when the file specified by special
10 * or addr does not exist.
11 * 3) quotactl fails with EBUSTY when cmd is Q_QUOTAON and another
12 * Q_QUOTAON had already been performed.
13 * 4) quotactl fails with EFAULT when addr or special is invalid.
14 * 5) quotactl fails with EINVAL when cmd or type is invalid.
15 * 6) quotactl fails with ENOTBLK when special is not a block device.
16 * 7) quotactl fails with ESRCH when no disk quota is found for the
17 * indicated user and quotas have not been turned on for this fs.
18 * 8) quotactl fails with ESRCH when cmd is Q_QUOTAON, but the quota
19 * format was not found.
20 * 9) quotactl fails with ESRCH when cmd is Q_GETNEXTQUOTA, but there
21 * is no ID greater than or equal to id that has an active quota.
22 * 10) quotactl fails with ERANGE when cmd is Q_SETQUOTA, but the
23 * specified limits are out of the range allowed by the quota format.
24 * 11) quotactl fails with EPERM when the caller lacked the required
25 * privilege (CAP_SYS_ADMIN) for the specified operation.
26 */
27
28 #include <errno.h>
29 #include <sys/quota.h>
30 #include "tst_test.h"
31 #include "lapi/quotactl.h"
32 #include "tst_capability.h"
33
34 #define OPTION_INVALID 999
35 #define QFMT_VFS_V0 2
36 #define USRPATH MNTPOINT "/aquota.user"
37 #define FMTID QFMT_VFS_V0
38
39 #define MNTPOINT "mntpoint"
40 #define TESTDIR1 MNTPOINT "/testdir1"
41 #define TESTDIR2 MNTPOINT "/testdir2"
42
43 static int32_t fmt_id = FMTID;
44 static int32_t fmt_invalid = 999;
45 static int test_invalid;
46 static int test_id;
47 static int getnextquota_nsup;
48
49 static struct if_nextdqblk res_ndq;
50 static struct dqblk set_dq = {
51 .dqb_bsoftlimit = 100,
52 .dqb_valid = QIF_BLIMITS
53 };
54
55 static struct dqblk set_dqmax = {
56 .dqb_bsoftlimit = 0x7fffffffffffffffLL, /* 2^63-1 */
57 .dqb_valid = QIF_BLIMITS
58 };
59
60 struct tst_cap dropadmin = {
61 .action = TST_CAP_DROP,
62 .id = CAP_SYS_ADMIN,
63 .name = "CAP_SYS_ADMIN",
64 };
65
66 struct tst_cap needadmin = {
67 .action = TST_CAP_REQ,
68 .id = CAP_SYS_ADMIN,
69 .name = "CAP_SYS_ADMIN",
70 };
71
72 static struct tcase {
73 int cmd;
74 int *id;
75 void *addr;
76 int exp_err;
77 int on_flag;
78 } tcases[] = {
79 {QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, TESTDIR1, EACCES, 0},
80 {QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, TESTDIR2, ENOENT, 0},
81 {QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, USRPATH, EBUSY, 1},
82 {QCMD(Q_SETQUOTA, USRQUOTA), &fmt_id, NULL, EFAULT, 1},
83 {QCMD(OPTION_INVALID, USRQUOTA), &fmt_id, USRPATH, EINVAL, 0},
84 {QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, USRPATH, ENOTBLK, 0},
85 {QCMD(Q_SETQUOTA, USRQUOTA), &test_id, &set_dq, ESRCH, 0},
86 {QCMD(Q_QUOTAON, USRQUOTA), &fmt_invalid, USRPATH, ESRCH, 0},
87 {QCMD(Q_GETNEXTQUOTA, USRQUOTA), &test_invalid, USRPATH, ESRCH, 0},
88 {QCMD(Q_SETQUOTA, USRQUOTA), &test_id, &set_dqmax, ERANGE, 1},
89 {QCMD(Q_QUOTAON, USRQUOTA), &fmt_id, USRPATH, EPERM, 0},
90 };
91
verify_quotactl(unsigned int n)92 static void verify_quotactl(unsigned int n)
93 {
94 struct tcase *tc = &tcases[n];
95 int quota_on = 0;
96 int drop_flag = 0;
97
98 if (tc->cmd == QCMD(Q_GETNEXTQUOTA, USRQUOTA) && getnextquota_nsup) {
99 tst_res(TCONF, "current system doesn't support Q_GETNEXTQUOTA");
100 return;
101 }
102
103 if (tc->on_flag) {
104 TEST(quotactl(QCMD(Q_QUOTAON, USRQUOTA), tst_device->dev, FMTID, USRPATH));
105 if (TST_RET == -1)
106 tst_brk(TBROK,
107 "quotactl with Q_QUOTAON returned %ld", TST_RET);
108 quota_on = 1;
109 }
110
111 if (tc->exp_err == EPERM) {
112 tst_cap_action(&dropadmin);
113 drop_flag = 1;
114 }
115
116 if (tc->exp_err == ENOTBLK)
117 TEST(quotactl(tc->cmd, "/dev/null", *tc->id, tc->addr));
118 else
119 TEST(quotactl(tc->cmd, tst_device->dev, *tc->id, tc->addr));
120 if (TST_RET == -1) {
121 if (tc->exp_err == TST_ERR) {
122 tst_res(TPASS | TTERRNO, "quotactl failed as expected");
123 } else {
124 tst_res(TFAIL | TTERRNO,
125 "quotactl failed unexpectedly; expected %s, but got",
126 tst_strerrno(tc->exp_err));
127 }
128 } else {
129 tst_res(TFAIL, "quotactl returned wrong value: %ld", TST_RET);
130 }
131
132 if (quota_on) {
133 TEST(quotactl(QCMD(Q_QUOTAOFF, USRQUOTA), tst_device->dev, FMTID, USRPATH));
134 if (TST_RET == -1)
135 tst_brk(TBROK,
136 "quotactl with Q_QUOTAOFF returned %ld", TST_RET);
137 quota_on = 0;
138 }
139
140 if (drop_flag) {
141 tst_cap_action(&needadmin);
142 drop_flag = 0;
143 }
144 }
145
setup(void)146 static void setup(void)
147 {
148 const char *const cmd[] = {"quotacheck", "-uF", "vfsv0", MNTPOINT, NULL};
149 int ret;
150 unsigned int i;
151
152 ret = tst_run_cmd(cmd, NULL, NULL, 1);
153 switch (ret) {
154 case 0:
155 break;
156 case 255:
157 tst_brk(TCONF, "quotacheck binary not installed");
158 break;
159 default:
160 tst_brk(TBROK, "quotacheck exited with %i", ret);
161 }
162
163 if (access(USRPATH, F_OK) == -1)
164 tst_brk(TFAIL | TERRNO, "user quotafile didn't exist");
165
166 SAFE_MKDIR(TESTDIR1, 0666);
167 test_id = geteuid();
168 test_invalid = test_id + 1;
169
170 TEST(quotactl(QCMD(Q_GETNEXTQUOTA, USRQUOTA), tst_device->dev,
171 test_id, (void *) &res_ndq));
172 if (TST_ERR == EINVAL || TST_ERR == ENOSYS)
173 getnextquota_nsup = 1;
174
175 for (i = 0; i < ARRAY_SIZE(tcases); i++) {
176 if (!tcases[i].addr)
177 tcases[i].addr = tst_get_bad_addr(NULL);
178 }
179 }
180
181 static const char *kconfigs[] = {
182 "CONFIG_QFMT_V2",
183 NULL
184 };
185
186 static struct tst_test test = {
187 .setup = setup,
188 .needs_kconfigs = kconfigs,
189 .tcnt = ARRAY_SIZE(tcases),
190 .test = verify_quotactl,
191 .dev_fs_type = "ext4",
192 .mntpoint = MNTPOINT,
193 .mount_device = 1,
194 .mnt_data = "usrquota",
195 .needs_root = 1,
196 };
197