12#include <sys/inotify.h>
14#include <sys/syscall.h>
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");
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));
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))) {
38 if (event->wd == wd && (event->mask & required_mask) == required_mask) {
39 if (!name && !event->len) {
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;
51 int padding_zeroed = 1;
52 for (
size_t i = name_length + 1; i <
event->len; ++i) {
63 offset += record_size;
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]) {
80 for (
size_t i = name_length + 1; i < padded_length; ++i) {
88static int find_one_shot_pair(
int fd,
int wd) {
89 unsigned char buffer[2048];
90 ssize_t amount = read(fd, buffer,
sizeof(buffer));
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) {
104 if (event->wd == wd && event->mask == IN_MODIFY) {
106 }
else if (event->wd == wd && event->mask == IN_IGNORED) {
109 offset += record_size;
111 return modifies == 1 && ignored == 1;
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;
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;
132static int empty_nonblocking(
int fd) {
133 unsigned char buffer[
sizeof(
struct inotify_event)];
135 return read(fd, buffer,
sizeof(buffer)) == -1 && errno == EAGAIN;
141 int one_shot_fd = -1;
149 char subdirectory[160];
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);
157 if (inotify_init1(0x40000000) != -1 || errno != EINVAL) {
160 int public_fd = inotify_init();
161 if (public_fd < 0 || close(public_fd)) {
164 int legacy_fd = (int)syscall(SYS_inotify_init);
165 if (legacy_fd < 0 || close(legacy_fd)) {
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)) {
175 unsigned char short_buffer[
sizeof(
struct inotify_event) - 1];
176 if (read(notify_fd, short_buffer,
sizeof(short_buffer)) != -1 || errno != EAGAIN) {
180 void* bad_read_buffer = (
void*)(uintptr_t)1;
181 if (read(notify_fd, bad_read_buffer,
sizeof(
struct inotify_event)) != -1 || errno != EAGAIN) {
185 if (mkdir(directory, 0700)) {
189 if (inotify_add_watch(-1, (
const char*)1, 0) != -1 || errno != EINVAL) {
193 if (inotify_add_watch(-1, (
const char*)1, IN_CREATE | 0x00800000U) != -1 || errno != EINVAL) {
197 if (inotify_add_watch(-1, directory, IN_CREATE) != -1 || errno != EBADF) {
200 const int directory_wd = inotify_add_watch(notify_fd, directory, IN_CREATE | IN_DELETE);
201 if (directory_wd < 0) {
205 if (inotify_add_watch(notify_fd, directory, IN_CREATE | IN_MASK_CREATE) != -1 ||
210 if (inotify_add_watch(notify_fd, directory, IN_CREATE | IN_MASK_CREATE | IN_MASK_ADD) != -1 ||
215 if (inotify_add_watch(notify_fd, (
const char*)1, IN_CREATE | IN_MASK_CREATE | IN_MASK_ADD) !=
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) {
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) {
236 if (read(notify_fd, short_buffer,
sizeof(short_buffer)) != -1 || errno != EINVAL ||
237 !find_event(notify_fd, directory_wd, IN_CREATE,
"child")) {
241 if (inotify_add_watch(notify_fd, directory, IN_MODIFY | IN_ATTRIB | IN_MASK_ADD) !=
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")) {
247 if (chmod(directory, 0755) || write(child_fd,
"f", 1) != 1) {
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)) {
258 int reopened = open(child, O_RDONLY);
259 if (reopened < 0 || close(reopened) || !empty_nonblocking(notify_fd)) {
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) !=
269 other_fd = open(other, O_CREAT | O_RDWR, 0600);
270 if (other_fd < 0 || !find_event(notify_fd, directory_wd, IN_CREATE,
"other")) {
274 if (inotify_add_watch(notify_fd, child, IN_MODIFY | IN_ONLYDIR) != -1 || errno != ENOTDIR) {
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)) {
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)) {
291 if (inotify_rm_watch(one_shot_fd, one_shot_wd) != -1 || errno != EINVAL) {
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)) {
299 const int ignored_only_wd = inotify_add_watch(one_shot_fd, child, IN_MODIFY);
300 if (ignored_only_wd < 0) {
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)) {
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")) {
317 if (inotify_rm_watch(delete_fd, delete_wd) != -1 || errno != EINVAL) {
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")) {
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")) {
333 if (inotify_rm_watch(notify_fd, directory_wd) ||
334 !find_event(notify_fd, directory_wd, IN_IGNORED, NULL)) {
349 if (one_shot_fd >= 0)
359 printf(
"INOTIFY-TEST: FAIL errno=%d\n", errno);
362 printf(
"INOTIFY-TEST: PASS\n");