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