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 * This testcase checks that quotactl(2) on ext4 filesystem succeeds to:
11 *
12 * - turn on quota with Q_QUOTAON flag for project
13 * - set disk quota limits with Q_SETQUOTA flag for project
14 * - get disk quota limits with Q_GETQUOTA flag for project
15 * - set information about quotafile with Q_SETINFO flag for project
16 * - get information about quotafile with Q_GETINFO flag for project
17 * - get quota format with Q_GETFMT flag for project
18 * - get disk quota limit greater than or equal to ID with Q_GETNEXTQUOTA flag for project
19 * - turn off quota with Q_QUOTAOFF flag for project
20 *
21 * Minimum e2fsprogs version required is 1.43.
22 */
23
24 #include <errno.h>
25 #include <string.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <sys/mount.h>
29 #include "tst_test.h"
30 #include "quotactl_syscall_var.h"
31
32 #define FMTID QFMT_VFS_V1
33
34 static int32_t fmt_id = FMTID;
35 static int test_id, mount_flag;
36 static struct dqblk set_dq = {
37 .dqb_bsoftlimit = 100,
38 .dqb_valid = QIF_BLIMITS
39 };
40 static struct dqblk res_dq;
41 static struct dqinfo set_qf = {
42 .dqi_bgrace = 80,
43 .dqi_valid = IIF_BGRACE
44 };
45
46 static struct dqinfo res_qf;
47 static int32_t fmt_buf;
48
49 static struct if_nextdqblk res_ndq;
50
51 static struct tcase {
52 int cmd;
53 int *id;
54 void *addr;
55 void *set_data;
56 void *res_data;
57 int sz;
58 char *des;
59 char *tname;
60 } tcases[] = {
61 {QCMD(Q_QUOTAON, PRJQUOTA), &fmt_id, NULL,
62 NULL, NULL, 0, "turn on quota for project",
63 "QCMD(Q_QUOTAON, PRJQUOTA)"},
64
65 {QCMD(Q_SETQUOTA, PRJQUOTA), &test_id, &set_dq,
66 NULL, NULL, 0, "set disk quota limit for project",
67 "QCMD(Q_SETQUOTA, PRJQUOTA)"},
68
69 {QCMD(Q_GETQUOTA, PRJQUOTA), &test_id, &res_dq,
70 &set_dq.dqb_bsoftlimit, &res_dq.dqb_bsoftlimit,
71 sizeof(res_dq.dqb_bsoftlimit), "get disk quota limit for project",
72 "QCMD(Q_GETQUOTA, PRJQUOTA)"},
73
74 {QCMD(Q_SETINFO, PRJQUOTA), &test_id, &set_qf,
75 NULL, NULL, 0, "set information about quotafile for project",
76 "QCMD(Q_SETINFO, PRJQUOTA"},
77
78 {QCMD(Q_GETINFO, PRJQUOTA), &test_id, &res_qf,
79 &set_qf.dqi_bgrace, &res_qf.dqi_bgrace, sizeof(res_qf.dqi_bgrace),
80 "get information about quotafile for project",
81 "QCMD(Q_GETINFO, PRJQUOTA"},
82
83 {QCMD(Q_GETFMT, PRJQUOTA), &test_id, &fmt_buf,
84 &fmt_id, &fmt_buf, sizeof(fmt_buf),
85 "get quota format for project", "QCMD(Q_GETFMT, PRJQUOTA)"},
86
87 {QCMD(Q_GETNEXTQUOTA, PRJQUOTA), &test_id, &res_ndq,
88 &test_id, &res_ndq.dqb_id, sizeof(res_ndq.dqb_id),
89 "get next disk quota limit for project",
90 "QCMD(Q_GETNEXTQUOTA, PRJQUOTA)"},
91
92 {QCMD(Q_QUOTAOFF, PRJQUOTA), &test_id, NULL,
93 NULL, NULL, 0, "turn off quota for project",
94 "QCMD(Q_QUOTAOFF, PRJQUOTA)"},
95
96 };
97
do_mount(const char * source,const char * target,const char * filesystemtype,unsigned long mountflags,const void * data)98 static void do_mount(const char *source, const char *target,
99 const char *filesystemtype, unsigned long mountflags,
100 const void *data)
101 {
102 TEST(mount(source, target, filesystemtype, mountflags, data));
103
104 if (TST_RET == -1 && TST_ERR == ESRCH)
105 tst_brk(TCONF, "Kernel or device does not support FS quotas");
106
107 if (TST_RET == -1) {
108 tst_brk(TBROK | TTERRNO, "mount(%s, %s, %s, %lu, %p) failed",
109 source, target, filesystemtype, mountflags, data);
110 }
111
112 if (TST_RET) {
113 tst_brk(TBROK | TTERRNO, "mount(%s, %s, %s, %lu, %p) failed",
114 source, target, filesystemtype, mountflags, data);
115 }
116
117 mount_flag = 1;
118 }
119
setup(void)120 static void setup(void)
121 {
122 const char *const fs_opts[] = {"-I 256", "-O quota,project", NULL};
123
124 quotactl_info();
125 SAFE_MKFS(tst_device->dev, tst_device->fs_type, fs_opts, NULL);
126 do_mount(tst_device->dev, MNTPOINT, tst_device->fs_type, 0, NULL);
127 fd = SAFE_OPEN(MNTPOINT, O_RDONLY);
128 }
129
cleanup(void)130 static void cleanup(void)
131 {
132 if (fd > -1)
133 SAFE_CLOSE(fd);
134 if (mount_flag && tst_umount(MNTPOINT))
135 tst_res(TWARN | TERRNO, "umount(%s)", MNTPOINT);
136 }
137
verify_quota(unsigned int n)138 static void verify_quota(unsigned int n)
139 {
140 struct tcase *tc = &tcases[n];
141
142 res_dq.dqb_bsoftlimit = 0;
143 res_qf.dqi_igrace = 0;
144 fmt_buf = 0;
145
146 tst_res(TINFO, "Test #%d: %s", n, tc->tname);
147
148 TST_EXP_PASS_SILENT(do_quotactl(fd, tc->cmd, tst_device->dev, *tc->id, tc->addr),
149 "do_quotactl to %s", tc->des);
150 if (!TST_PASS)
151 return;
152
153 if (memcmp(tc->res_data, tc->set_data, tc->sz)) {
154 tst_res(TFAIL, "quotactl failed to %s", tc->des);
155 tst_res_hexd(TINFO, tc->res_data, tc->sz, "retval: ");
156 tst_res_hexd(TINFO, tc->set_data, tc->sz, "expected: ");
157 return;
158 }
159
160 tst_res(TPASS, "quotactl succeeded to %s", tc->des);
161 }
162
163 static struct tst_test test = {
164 .needs_root = 1,
165 .needs_kconfigs = (const char *[]) {
166 "CONFIG_QFMT_V2",
167 NULL
168 },
169 .min_kver = "4.10", /* commit 689c958cbe6b (ext4: add project quota support) */
170 .test = verify_quota,
171 .tcnt = ARRAY_SIZE(tcases),
172 .setup = setup,
173 .cleanup = cleanup,
174 .needs_device = 1,
175 .dev_fs_type = "ext4",
176 .mntpoint = MNTPOINT,
177 .test_variants = QUOTACTL_SYSCALL_VARIANTS,
178 .needs_cmds = (const char *[]) {
179 "mkfs.ext4 >= 1.43.0",
180 NULL
181 }
182 };
183