17#define CHECK(condition) \
20 fprintf(stderr, "QUOTA-CONTRACT: line=%d errno=%d\n", __LINE__, errno); \
25enum { TestUser = 4100, TestGroup = 4200 };
26static char device[512];
28static int control(
unsigned operation,
int type,
int id,
void* address) {
29 return quotactl((
int)((operation << 8) | (
unsigned)type), device,
id, address);
32static int get(
int type,
int id,
struct dqblk* record) {
33 memset(record, 0,
sizeof(*record));
34 return control(Q_GETQUOTA, type,
id, record);
37static int limits(
int type,
int id, uint64_t blocks, uint64_t inodes) {
38 struct dqblk record = {0};
39 record.dqb_valid = QIF_LIMITS;
40 record.dqb_bhardlimit = blocks;
41 record.dqb_ihardlimit = inodes;
42 return control(Q_SETQUOTA, type,
id, &record);
45static int find_device(dev_t mounted) {
46 DIR* directory = opendir(
"/dev/block");
51 while ((entry = readdir(directory))) {
52 struct stat attributes;
53 if (snprintf(device,
sizeof(device),
"/dev/block/%s", entry->d_name) >=
sizeof(device))
55 if (!stat(device, &attributes) && S_ISBLK(attributes.st_mode) &&
56 attributes.st_rdev == mounted) {
65static int create_quota(
const char* path) {
66 const uint32_t old_record[8] = {0};
67 int fd = open(path, O_CREAT | O_EXCL | O_RDWR, 0600);
70 const int result = write(fd, old_record,
sizeof(old_record)) ==
sizeof(old_record) ? 0 : -1;
75static int child_permissions(
const char* creation) {
77 if (setgid(TestGroup) || setuid(TestUser))
79 if (get(USRQUOTA, TestUser, &record) || get(GRPQUOTA, TestGroup, &record))
82 if (get(USRQUOTA, TestUser + 1, &record) != -1 || errno != EPERM)
85 if (limits(USRQUOTA, TestUser, 0, 0) != -1 || errno != EPERM)
88 int fd = open(creation, O_CREAT | O_EXCL | O_WRONLY, 0600);
93 return errno == EDQUOT ? 0 : 6;
96int main(
int argc,
char** argv) {
98 fprintf(stderr,
"usage: quota-contract-test EXT2-SCRATCH-DIRECTORY\n");
101 char directory[512], users[544], groups[544], first[544], second[544], creation[544];
102 int fd = -1, other = -1, quota_fd = -1, result = 1;
103 int user_enabled = 0, group_enabled = 0;
104 struct stat attributes;
105 struct dqblk user_base, group_base, record, destination;
107 const long page = sysconf(_SC_PAGESIZE);
108 void* denied = MAP_FAILED;
109 snprintf(directory,
sizeof(directory),
"%s/quota-%ld", argv[1], (
long)getpid());
110 snprintf(users,
sizeof(users),
"%s/quota.user", directory);
111 snprintf(groups,
sizeof(groups),
"%s/quota.group", directory);
112 snprintf(first,
sizeof(first),
"%s/first", directory);
113 snprintf(second,
sizeof(second),
"%s/second", directory);
114 snprintf(creation,
sizeof(creation),
"%s/child", directory);
115 CHECK(geteuid() == 0 && page > 0);
116 CHECK(mkdir(directory, 0777) == 0 && chmod(directory, 0777) == 0);
117 CHECK(stat(directory, &attributes) == 0 && find_device(attributes.st_dev) == 0);
119 CHECK(control(Q_GETFMT, USRQUOTA, 0, &format) == -1 && errno == ESRCH);
121 CHECK(control(Q_GETFMT, GRPQUOTA, 0, &format) == -1 && errno == ESRCH);
122 CHECK(create_quota(users) == 0 && create_quota(groups) == 0);
123 fd = open(first, O_CREAT | O_EXCL | O_RDWR, 0600);
124 other = open(second, O_CREAT | O_EXCL | O_RDWR, 0600);
125 CHECK(fd >= 0 && other >= 0);
126 CHECK(fchown(fd, TestUser, TestGroup) == 0 && fchown(other, TestUser, TestGroup) == 0);
127 CHECK(fstat(fd, &attributes) == 0 && attributes.st_blksize >= 1024);
128 const uint64_t block = attributes.st_blksize;
129 CHECK(control(Q_QUOTAON, USRQUOTA, QFMT_VFS_OLD, users) == 0);
131 CHECK(control(Q_QUOTAON, GRPQUOTA, QFMT_VFS_OLD, groups) == 0);
133 CHECK(control(Q_GETFMT, USRQUOTA, 0, &format) == 0 && format == QFMT_VFS_OLD);
134 CHECK(get(USRQUOTA, TestUser, &user_base) == 0 && get(GRPQUOTA, TestGroup, &group_base) == 0);
135 CHECK(user_base.dqb_curinodes >= 2 && group_base.dqb_curinodes >= 2);
136 CHECK(limits(USRQUOTA, TestUser, user_base.dqb_curspace / 1024 + 14 * block / 1024,
137 user_base.dqb_curinodes) == 0);
138 CHECK(limits(GRPQUOTA, TestGroup, group_base.dqb_curspace / 1024 + 14 * block / 1024,
139 group_base.dqb_curinodes) == 0);
140 CHECK(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 13 * block) == 0);
141 CHECK(fstat(fd, &attributes) == 0 && attributes.st_size == 0 &&
142 attributes.st_blocks == 14 * block / 512);
143 CHECK(get(USRQUOTA, TestUser, &record) == 0 &&
144 record.dqb_curspace == user_base.dqb_curspace + 14 * block);
146 CHECK(fallocate(other, FALLOC_FL_KEEP_SIZE, 0, block) == -1 && errno == EDQUOT);
148 CHECK(fsetxattr(other,
"user.quota",
"x", 1, 0) == -1 && errno == EDQUOT);
149 CHECK(ftruncate(fd, 0) == 0);
150 CHECK(get(USRQUOTA, TestUser, &record) == 0 && record.dqb_curspace == user_base.dqb_curspace);
151 CHECK(fsetxattr(other,
"user.quota",
"x", 1, 0) == 0);
152 CHECK(get(GRPQUOTA, TestGroup, &record) == 0 &&
153 record.dqb_curspace == group_base.dqb_curspace + block);
154 CHECK(fremovexattr(other,
"user.quota") == 0);
156 pid_t child = fork();
159 _exit(child_permissions(creation));
161 CHECK(waitpid(child, &status, 0) == child && WIFEXITED(status) && WEXITSTATUS(status) == 0);
163 quota_fd = open(users, O_RDWR);
164 CHECK(quota_fd >= 0);
166 CHECK(pwrite(quota_fd,
"x", 1, 0) == -1 && errno == EPERM);
168 CHECK(ftruncate(quota_fd, 0) == -1 && errno == EPERM);
170 CHECK(unlink(users) == -1 && errno == EPERM);
173 denied = mmap(NULL, page, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
174 CHECK(denied != MAP_FAILED);
176 CHECK(control(Q_SETQUOTA, USRQUOTA, TestUser, denied) == -1 && errno == EFAULT);
178 CHECK(control(Q_GETQUOTA, USRQUOTA, TestUser, denied) == -1 && errno == EFAULT);
179 struct dqblk unsupported = {0};
180 unsupported.dqb_valid = QIF_BLIMITS;
181 unsupported.dqb_bsoftlimit = 1;
183 CHECK(control(Q_SETQUOTA, USRQUOTA, TestUser, &unsupported) == -1 && errno == EOPNOTSUPP);
185 CHECK(quotactl((
int)((
unsigned)Q_SYNC << 8), first, 0, NULL) == -1 && errno == ENOTBLK);
187 CHECK(fallocate(fd, FALLOC_FL_KEEP_SIZE, 0, 2 * block) == 0);
188 CHECK(get(USRQUOTA, TestUser + 1, &destination) == 0);
189 CHECK(limits(USRQUOTA, TestUser + 1, destination.dqb_curspace / 1024 + block / 1024,
190 destination.dqb_curinodes + 1) == 0);
192 CHECK(fchown(fd, TestUser + 1, -1) == -1 && errno == EDQUOT);
193 CHECK(fstat(fd, &attributes) == 0 && attributes.st_uid == TestUser);
194 CHECK(unlink(first) == 0);
195 CHECK(get(USRQUOTA, TestUser, &record) == 0 &&
196 record.dqb_curspace == user_base.dqb_curspace + 2 * block);
197 CHECK(close(fd) == 0);
199 CHECK(get(USRQUOTA, TestUser, &record) == 0 && record.dqb_curspace == user_base.dqb_curspace &&
200 record.dqb_curinodes == user_base.dqb_curinodes - 1);
201 CHECK(control(Q_SYNC, USRQUOTA, 0, NULL) == 0);
202 CHECK(control(Q_QUOTAOFF, GRPQUOTA, 0, NULL) == 0);
204 CHECK(control(Q_QUOTAOFF, USRQUOTA, 0, NULL) == 0);
206 CHECK(control(Q_QUOTAON, USRQUOTA, QFMT_VFS_OLD, users) == 0);
208 CHECK(get(USRQUOTA, TestUser, &record) == 0 &&
209 record.dqb_bhardlimit == user_base.dqb_curspace / 1024 + 14 * block / 1024 &&
210 record.dqb_curinodes == user_base.dqb_curinodes - 1);
211 CHECK(control(Q_QUOTAOFF, USRQUOTA, 0, NULL) == 0);
215 if (denied != MAP_FAILED)
216 munmap(denied, page);
217 if (group_enabled && control(Q_QUOTAOFF, GRPQUOTA, 0, NULL))
219 if (user_enabled && control(Q_QUOTAOFF, USRQUOTA, 0, NULL))
233 puts(result ?
"QUOTA-CONTRACT: FAIL" :
"QUOTA-CONTRACT: PASS");