The Pedigree Project 0.1
inotify-test/main.c
1#define _GNU_SOURCE
2
3#include <errno.h>
4#include <fcntl.h>
5#include <poll.h>
6#include <stdint.h>
7#include <stdio.h>
8#include <string.h>
9#include <unistd.h>
10
11#include <sys/epoll.h>
12#include <sys/inotify.h>
13#include <sys/stat.h>
14#include <sys/syscall.h>
15
16_Static_assert(sizeof(struct inotify_event) == 16,
17 "musl's Linux amd64 inotify_event header must be 16 bytes");
18_Static_assert(SYS_inotify_init == 253, "unexpected musl inotify_init syscall number");
19_Static_assert(SYS_inotify_add_watch == 254, "unexpected musl inotify_add_watch syscall number");
20_Static_assert(SYS_inotify_rm_watch == 255, "unexpected musl inotify_rm_watch syscall number");
21_Static_assert(SYS_inotify_init1 == 294, "unexpected musl inotify_init1 syscall number");
22
23static int find_event(int fd, int wd, uint32_t required_mask, const char* name) {
24 unsigned char buffer[2048];
25 ssize_t amount = read(fd, buffer, sizeof(buffer));
26 if (amount < 0) {
27 return 0;
28 }
29
30 size_t offset = 0;
31 while (offset + sizeof(struct inotify_event) <= (size_t)amount) {
32 const struct inotify_event* event = (const struct inotify_event*)(buffer + offset);
33 const size_t record_size = sizeof(*event) + event->len;
34 if (record_size > (size_t)amount - offset ||
35 (event->len && event->len % sizeof(struct inotify_event))) {
36 return 0;
37 }
38 if (event->wd == wd && (event->mask & required_mask) == required_mask) {
39 if (!name && !event->len) {
40 return 1;
41 }
42 if (name) {
43 const size_t name_length = strlen(name);
44 const size_t expected_length = (name_length + 1 + sizeof(struct inotify_event) - 1) &
45 ~(sizeof(struct inotify_event) - 1);
46 if (event->len != expected_length || memcmp(event->name, name, name_length) ||
47 event->name[name_length]) {
48 offset += record_size;
49 continue;
50 }
51 int padding_zeroed = 1;
52 for (size_t i = name_length + 1; i < event->len; ++i) {
53 if (event->name[i]) {
54 padding_zeroed = 0;
55 break;
56 }
57 }
58 if (padding_zeroed) {
59 return 1;
60 }
61 }
62 }
63 offset += record_size;
64 }
65 return 0;
66}
67
68static int find_single_named_event(int fd, int wd, uint32_t mask, const char* name) {
69 unsigned char buffer[256];
70 const size_t name_length = strlen(name);
71 const size_t padded_length =
72 (name_length + 1 + sizeof(struct inotify_event) - 1) & ~(sizeof(struct inotify_event) - 1);
73 const ssize_t amount = read(fd, buffer, sizeof(buffer));
74 const struct inotify_event* event = (const struct inotify_event*)buffer;
75 if (amount != (ssize_t)(sizeof(*event) + padded_length) || event->wd != wd ||
76 event->mask != mask || event->cookie != 0 || event->len != padded_length ||
77 memcmp(event->name, name, name_length) || event->name[name_length]) {
78 return 0;
79 }
80 for (size_t i = name_length + 1; i < padded_length; ++i) {
81 if (event->name[i]) {
82 return 0;
83 }
84 }
85 return 1;
86}
87
88static int find_one_shot_pair(int fd, int wd) {
89 unsigned char buffer[2048];
90 ssize_t amount = read(fd, buffer, sizeof(buffer));
91 if (amount < 0) {
92 return 0;
93 }
94
95 int modifies = 0;
96 int ignored = 0;
97 size_t offset = 0;
98 while (offset + sizeof(struct inotify_event) <= (size_t)amount) {
99 const struct inotify_event* event = (const struct inotify_event*)(buffer + offset);
100 const size_t record_size = sizeof(*event) + event->len;
101 if (record_size > (size_t)amount - offset) {
102 return 0;
103 }
104 if (event->wd == wd && event->mask == IN_MODIFY) {
105 ++modifies;
106 } else if (event->wd == wd && event->mask == IN_IGNORED) {
107 ++ignored;
108 }
109 offset += record_size;
110 }
111 return modifies == 1 && ignored == 1;
112}
113
114static int find_exact_pair(int fd, int wd, uint32_t first_mask, uint32_t second_mask) {
115 uint64_t storage[4] = {0};
116 ssize_t amount = read(fd, storage, sizeof(storage));
117 const struct inotify_event* first = (const struct inotify_event*)storage;
118 const struct inotify_event* second =
119 (const struct inotify_event*)((const unsigned char*)storage + sizeof(*first));
120 return amount == (ssize_t)sizeof(storage) && first->wd == wd && first->mask == first_mask &&
121 first->cookie == 0 && first->len == 0 && second->wd == wd && second->mask == second_mask &&
122 second->cookie == 0 && second->len == 0;
123}
124
125static int find_single_event(int fd, int wd, uint32_t mask) {
126 struct inotify_event event;
127 ssize_t amount = read(fd, &event, sizeof(event));
128 return amount == (ssize_t)sizeof(event) && event.wd == wd && event.mask == mask &&
129 event.cookie == 0 && event.len == 0;
130}
131
132static int empty_nonblocking(int fd) {
133 unsigned char buffer[sizeof(struct inotify_event)];
134 errno = 0;
135 return read(fd, buffer, sizeof(buffer)) == -1 && errno == EAGAIN;
136}
137
138int main(void) {
139 int result = 1;
140 int notify_fd = -1;
141 int one_shot_fd = -1;
142 int delete_fd = -1;
143 int epoll_fd = -1;
144 int child_fd = -1;
145 int other_fd = -1;
146 char directory[128];
147 char child[160];
148 char other[160];
149 char subdirectory[160];
150
151 snprintf(directory, sizeof(directory), "/tmp/inotify-test-%ld", (long)getpid());
152 snprintf(child, sizeof(child), "%s/child", directory);
153 snprintf(other, sizeof(other), "%s/other", directory);
154 snprintf(subdirectory, sizeof(subdirectory), "%s/subdir", directory);
155
156 errno = 0;
157 if (inotify_init1(0x40000000) != -1 || errno != EINVAL) {
158 goto cleanup;
159 }
160 int public_fd = inotify_init();
161 if (public_fd < 0 || close(public_fd)) {
162 goto cleanup;
163 }
164 int legacy_fd = (int)syscall(SYS_inotify_init);
165 if (legacy_fd < 0 || close(legacy_fd)) {
166 goto cleanup;
167 }
168
169 notify_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
170 if (notify_fd < 0 || !(fcntl(notify_fd, F_GETFL) & O_NONBLOCK) ||
171 !(fcntl(notify_fd, F_GETFD) & FD_CLOEXEC)) {
172 goto cleanup;
173 }
174 errno = 0;
175 unsigned char short_buffer[sizeof(struct inotify_event) - 1];
176 if (read(notify_fd, short_buffer, sizeof(short_buffer)) != -1 || errno != EAGAIN) {
177 goto cleanup;
178 }
179 errno = 0;
180 void* bad_read_buffer = (void*)(uintptr_t)1;
181 if (read(notify_fd, bad_read_buffer, sizeof(struct inotify_event)) != -1 || errno != EAGAIN) {
182 goto cleanup;
183 }
184
185 if (mkdir(directory, 0700)) {
186 goto cleanup;
187 }
188 errno = 0;
189 if (inotify_add_watch(-1, (const char*)1, 0) != -1 || errno != EINVAL) {
190 goto cleanup;
191 }
192 errno = 0;
193 if (inotify_add_watch(-1, (const char*)1, IN_CREATE | 0x00800000U) != -1 || errno != EINVAL) {
194 goto cleanup;
195 }
196 errno = 0;
197 if (inotify_add_watch(-1, directory, IN_CREATE) != -1 || errno != EBADF) {
198 goto cleanup;
199 }
200 const int directory_wd = inotify_add_watch(notify_fd, directory, IN_CREATE | IN_DELETE);
201 if (directory_wd < 0) {
202 goto cleanup;
203 }
204 errno = 0;
205 if (inotify_add_watch(notify_fd, directory, IN_CREATE | IN_MASK_CREATE) != -1 ||
206 errno != EEXIST) {
207 goto cleanup;
208 }
209 errno = 0;
210 if (inotify_add_watch(notify_fd, directory, IN_CREATE | IN_MASK_CREATE | IN_MASK_ADD) != -1 ||
211 errno != EINVAL) {
212 goto cleanup;
213 }
214 errno = 0;
215 if (inotify_add_watch(notify_fd, (const char*)1, IN_CREATE | IN_MASK_CREATE | IN_MASK_ADD) !=
216 -1 ||
217 errno != EINVAL) {
218 goto cleanup;
219 }
220
221 epoll_fd = epoll_create1(EPOLL_CLOEXEC);
222 struct epoll_event interest = {.events = EPOLLIN, .data.u64 = 0x494e4f5449465901ULL};
223 struct pollfd poll_state = {.fd = notify_fd, .events = POLLIN, .revents = 0};
224 if (epoll_fd < 0 || epoll_ctl(epoll_fd, EPOLL_CTL_ADD, notify_fd, &interest) ||
225 poll(&poll_state, 1, 0) != 0) {
226 goto cleanup;
227 }
228 child_fd = open(child, O_CREAT | O_RDWR, 0600);
229 struct epoll_event ready = {0};
230 if (child_fd < 0 || poll(&poll_state, 1, 0) != 1 || !(poll_state.revents & POLLIN) ||
231 epoll_wait(epoll_fd, &ready, 1, 1000) != 1 || !(ready.events & EPOLLIN) ||
232 ready.data.u64 != interest.data.u64) {
233 goto cleanup;
234 }
235 errno = 0;
236 if (read(notify_fd, short_buffer, sizeof(short_buffer)) != -1 || errno != EINVAL ||
237 !find_event(notify_fd, directory_wd, IN_CREATE, "child")) {
238 goto cleanup;
239 }
240
241 if (inotify_add_watch(notify_fd, directory, IN_MODIFY | IN_ATTRIB | IN_MASK_ADD) !=
242 directory_wd ||
243 write(child_fd, "a", 1) != 1 || write(child_fd, "a", 1) != 1 ||
244 !find_single_named_event(notify_fd, directory_wd, IN_MODIFY, "child")) {
245 goto cleanup;
246 }
247 if (chmod(directory, 0755) || write(child_fd, "f", 1) != 1) {
248 goto cleanup;
249 }
250 errno = 0;
251 if (read(notify_fd, bad_read_buffer, sizeof(struct inotify_event)) != -1 || errno != EFAULT ||
252 !find_single_named_event(notify_fd, directory_wd, IN_MODIFY, "child") ||
253 !empty_nonblocking(notify_fd) || chmod(directory, 0700) ||
254 !find_event(notify_fd, directory_wd, IN_ATTRIB | IN_ISDIR, NULL)) {
255 goto cleanup;
256 }
257
258 int reopened = open(child, O_RDONLY);
259 if (reopened < 0 || close(reopened) || !empty_nonblocking(notify_fd)) {
260 goto cleanup;
261 }
262 if (inotify_add_watch(notify_fd, directory, IN_ATTRIB) != directory_wd ||
263 write(child_fd, "r", 1) != 1 || !empty_nonblocking(notify_fd) || chmod(directory, 0755) ||
264 !find_event(notify_fd, directory_wd, IN_ATTRIB | IN_ISDIR, NULL) ||
265 inotify_add_watch(notify_fd, directory, IN_CREATE | IN_DELETE | IN_MODIFY | IN_MASK_ADD) !=
266 directory_wd) {
267 goto cleanup;
268 }
269 other_fd = open(other, O_CREAT | O_RDWR, 0600);
270 if (other_fd < 0 || !find_event(notify_fd, directory_wd, IN_CREATE, "other")) {
271 goto cleanup;
272 }
273 errno = 0;
274 if (inotify_add_watch(notify_fd, child, IN_MODIFY | IN_ONLYDIR) != -1 || errno != ENOTDIR) {
275 goto cleanup;
276 }
277
278 one_shot_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
279 const int output_only_wd = inotify_add_watch(one_shot_fd, child, IN_Q_OVERFLOW);
280 if (one_shot_fd < 0 || output_only_wd < 0 || inotify_rm_watch(one_shot_fd, output_only_wd) ||
281 !find_event(one_shot_fd, output_only_wd, IN_IGNORED, NULL)) {
282 goto cleanup;
283 }
284 const int one_shot_wd = inotify_add_watch(one_shot_fd, child, IN_MODIFY | IN_ONESHOT);
285 if (one_shot_fd < 0 || one_shot_wd < 0 || write(child_fd, "b", 1) != 1 ||
286 write(child_fd, "c", 1) != 1 || !find_one_shot_pair(one_shot_fd, one_shot_wd) ||
287 !empty_nonblocking(one_shot_fd)) {
288 goto cleanup;
289 }
290 errno = 0;
291 if (inotify_rm_watch(one_shot_fd, one_shot_wd) != -1 || errno != EINVAL) {
292 goto cleanup;
293 }
294 const int replacement_wd = inotify_add_watch(one_shot_fd, child, IN_ATTRIB);
295 if (replacement_wd < 0 || inotify_rm_watch(one_shot_fd, replacement_wd) ||
296 !find_event(one_shot_fd, replacement_wd, IN_IGNORED, NULL)) {
297 goto cleanup;
298 }
299 const int ignored_only_wd = inotify_add_watch(one_shot_fd, child, IN_MODIFY);
300 if (ignored_only_wd < 0) {
301 goto cleanup;
302 }
303
304 delete_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
305 const int delete_wd = inotify_add_watch(delete_fd, child, IN_DELETE_SELF);
306 if (delete_fd < 0 || delete_wd < 0 || close(child_fd)) {
307 child_fd = -1;
308 goto cleanup;
309 }
310 child_fd = -1;
311 if (unlink(child) || !find_exact_pair(delete_fd, delete_wd, IN_DELETE_SELF, IN_IGNORED) ||
312 !find_single_event(one_shot_fd, ignored_only_wd, IN_IGNORED) ||
313 !find_event(notify_fd, directory_wd, IN_DELETE, "child")) {
314 goto cleanup;
315 }
316 errno = 0;
317 if (inotify_rm_watch(delete_fd, delete_wd) != -1 || errno != EINVAL) {
318 goto cleanup;
319 }
320 close(delete_fd);
321 delete_fd = inotify_init1(IN_NONBLOCK | IN_CLOEXEC);
322 if (mkdir(subdirectory, 0700) ||
323 !find_event(notify_fd, directory_wd, IN_CREATE | IN_ISDIR, "subdir")) {
324 goto cleanup;
325 }
326 const int directory_delete_wd = inotify_add_watch(delete_fd, subdirectory, IN_DELETE_SELF);
327 if (delete_fd < 0 || directory_delete_wd < 0 || rmdir(subdirectory) ||
328 !find_exact_pair(delete_fd, directory_delete_wd, IN_DELETE_SELF, IN_IGNORED) ||
329 !find_event(notify_fd, directory_wd, IN_DELETE | IN_ISDIR, "subdir")) {
330 goto cleanup;
331 }
332
333 if (inotify_rm_watch(notify_fd, directory_wd) ||
334 !find_event(notify_fd, directory_wd, IN_IGNORED, NULL)) {
335 goto cleanup;
336 }
337
338 result = 0;
339
340cleanup:
341 if (child_fd >= 0)
342 close(child_fd);
343 if (other_fd >= 0)
344 close(other_fd);
345 if (epoll_fd >= 0)
346 close(epoll_fd);
347 if (delete_fd >= 0)
348 close(delete_fd);
349 if (one_shot_fd >= 0)
350 close(one_shot_fd);
351 if (notify_fd >= 0)
352 close(notify_fd);
353 unlink(child);
354 unlink(other);
355 rmdir(subdirectory);
356 rmdir(directory);
357
358 if (result) {
359 printf("INOTIFY-TEST: FAIL errno=%d\n", errno);
360 return 1;
361 }
362 printf("INOTIFY-TEST: PASS\n");
363 return 0;
364}