From eec3dd0f3419cadd6f5d2391f880ec648b159ceb Mon Sep 17 00:00:00 2001 From: Zhenchen Wang Date: Mon, 17 Nov 2025 23:27:59 +0800 Subject: [PATCH] Add inotify regression tests and gVisor tests Signed-off-by: Zhenchen Wang --- kernel/src/fs/notify/inotify.rs | 2 +- test/src/apps/Makefile | 1 + test/src/apps/inotify/Makefile | 5 + test/src/apps/inotify/inotify_align.c | 238 ++++++++++++++++++ test/src/apps/inotify/inotify_poll.c | 140 +++++++++++ test/src/apps/scripts/process.sh | 2 + test/src/syscall/gvisor/Makefile | 1 + .../gvisor/blocklists.exfat/inotify_test | 11 + .../syscall/gvisor/blocklists/inotify_test | 12 + 9 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 test/src/apps/inotify/Makefile create mode 100644 test/src/apps/inotify/inotify_align.c create mode 100644 test/src/apps/inotify/inotify_poll.c create mode 100644 test/src/syscall/gvisor/blocklists.exfat/inotify_test create mode 100644 test/src/syscall/gvisor/blocklists/inotify_test diff --git a/kernel/src/fs/notify/inotify.rs b/kernel/src/fs/notify/inotify.rs index 11e9f226a..37caf541c 100644 --- a/kernel/src/fs/notify/inotify.rs +++ b/kernel/src/fs/notify/inotify.rs @@ -568,7 +568,7 @@ impl InotifyEvent { let actual_name_len = name.as_ref().map_or(0, |name| name.len() + 1); // Calculate padded name length aligned to sizeof(struct inotify_event) let pad_name_len = Self::round_event_name_len(actual_name_len); - + Self { header: InotifyEventHeader { wd, diff --git a/test/src/apps/Makefile b/test/src/apps/Makefile index 25a5e66ad..86a37cbe4 100644 --- a/test/src/apps/Makefile +++ b/test/src/apps/Makefile @@ -28,6 +28,7 @@ TEST_APPS := \ getcpu \ getpid \ hello_pie \ + inotify \ itimer \ mmap \ mongoose \ diff --git a/test/src/apps/inotify/Makefile b/test/src/apps/inotify/Makefile new file mode 100644 index 000000000..ce42e33b0 --- /dev/null +++ b/test/src/apps/inotify/Makefile @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: MPL-2.0 + +include ../test_common.mk + +EXTRA_C_FLAGS := \ No newline at end of file diff --git a/test/src/apps/inotify/inotify_align.c b/test/src/apps/inotify/inotify_align.c new file mode 100644 index 000000000..63d65ca57 --- /dev/null +++ b/test/src/apps/inotify/inotify_align.c @@ -0,0 +1,238 @@ +// SPDX-License-Identifier: MPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define INOTIFY_EVENT_SIZE 16 // sizeof(struct inotify_event) + +static void die(const char *msg) +{ + perror(msg); + exit(1); +} + +static void ensure_dir(const char *path) +{ + if (mkdir(path, 0700) < 0 && errno != EEXIST) + die("mkdir"); +} + +static void create_file(const char *path) +{ + int fd = open(path, O_CREAT | O_WRONLY, 0600); + if (fd < 0) + die("open"); + close(fd); +} + +// Round up to the nearest multiple of 16 +static size_t round_to_16(size_t n) +{ + return (n + 15) & ~15; +} + +// Calculate expected event size based on name length +static size_t expected_event_size(const char *name) +{ + size_t name_len = name ? strlen(name) + 1 : 0; // +1 for null terminator + size_t header_size = INOTIFY_EVENT_SIZE; + size_t padded_name_len = round_to_16(name_len); + return header_size + padded_name_len; +} + +int main(void) +{ + const char *dir = "inotify_align_tmp"; + + // Test files with different name lengths to test alignment + const char *test_files[] = { + "inotify_align_tmp/a", // 2 bytes name (1 char + null) + "inotify_align_tmp/ab", // 3 bytes name + "inotify_align_tmp/abc", // 4 bytes name + "inotify_align_tmp/abcd", // 5 bytes name + "inotify_align_tmp/abcdefgh", // 9 bytes name + "inotify_align_tmp/abcdefghijklmnop", // 17 bytes name + NULL + }; + + ensure_dir(dir); + + int ifd = inotify_init1(0); + if (ifd < 0) + die("inotify_init1"); + + int wd = inotify_add_watch(ifd, dir, IN_CREATE); + if (wd < 0) + die("inotify_add_watch"); + + // Create test files to generate events + for (int i = 0; test_files[i] != NULL; i++) { + create_file(test_files[i]); + } + + // Wait a bit for events to be queued + struct timespec ts = { .tv_sec = 0, .tv_nsec = 100 * 1000 * 1000 }; + nanosleep(&ts, NULL); + + // Check FIONREAD before reading + int pending = 0; + if (ioctl(ifd, FIONREAD, &pending) < 0) + die("ioctl(FIONREAD)"); + + printf("FIONREAD reports %d bytes pending\n", pending); + + // Read all events + char buf[4096] + __attribute__((aligned(__alignof__(struct inotify_event)))); + ssize_t total_read = read(ifd, buf, sizeof(buf)); + if (total_read < 0) + die("read"); + + printf("Read %zd bytes total\n", total_read); + + // Verify FIONREAD matches actual read size + if (total_read != pending) { + fprintf(stderr, + "ERROR: FIONREAD (%d) != actual read size (%zd)\n", + pending, total_read); + return 1; + } + + // Parse and verify events + size_t offset = 0; + int event_count = 0; + const char *expected_names[] = { "a", "ab", "abc", + "abcd", "abcdefgh", "abcdefghijklmnop", + NULL }; + + while (offset < (size_t)total_read) { + if (offset + INOTIFY_EVENT_SIZE > (size_t)total_read) { + fprintf(stderr, + "ERROR: Incomplete event at offset %zu\n", + offset); + return 1; + } + + struct inotify_event *event = + (struct inotify_event *)(buf + offset); + + // Calculate expected size for this event + const char *expected_name = expected_names[event_count]; + + // Verify event is aligned + if ((uintptr_t)event % __alignof__(struct inotify_event) != 0) { + fprintf(stderr, + "ERROR: Event at offset %zu is not aligned\n", + offset); + return 1; + } + + // Verify len field matches expected padded length + size_t actual_name_len = + expected_name ? strlen(expected_name) + 1 : 0; + size_t expected_padded_len = round_to_16(actual_name_len); + + if (event->len != expected_padded_len) { + fprintf(stderr, + "ERROR: Event %d: len field is %u, expected %zu (name_len=%zu)\n", + event_count, event->len, expected_padded_len, + actual_name_len); + return 1; + } + + // Verify event size is aligned to 16 bytes + size_t event_size = INOTIFY_EVENT_SIZE + event->len; + size_t expected_size = expected_event_size(expected_name); + if (event_size != expected_size) { + fprintf(stderr, + "ERROR: Event %d: total size %zu doesn't match expected %zu\n", + event_count, event_size, expected_size); + return 1; + } + if (event_size % INOTIFY_EVENT_SIZE != 0) { + fprintf(stderr, + "ERROR: Event %d: total size %zu is not aligned to %d bytes\n", + event_count, event_size, INOTIFY_EVENT_SIZE); + return 1; + } + + // Verify name if present + if (expected_name && event->len > 0) { + if (strcmp(event->name, expected_name) != 0) { + fprintf(stderr, + "ERROR: Event %d: name mismatch: got '%s', expected '%s'\n", + event_count, event->name, + expected_name); + return 1; + } + + // Verify padding bytes are zero + size_t actual_name_len_with_null = + strlen(expected_name) + 1; + if (event->len > actual_name_len_with_null) { + size_t padding_start = + actual_name_len_with_null; + for (size_t i = padding_start; i < event->len; + i++) { + if (event->name[i] != '\0') { + fprintf(stderr, + "ERROR: Event %d: padding byte at offset %zu is not zero (0x%02x)\n", + event_count, i, + (unsigned char) + event->name[i]); + return 1; + } + } + } + } + + printf("Event %d: wd=%d, mask=0x%x, len=%u, name='%s', size=%zu (aligned: %s)\n", + event_count, event->wd, event->mask, event->len, + event->len > 0 ? event->name : "(none)", event_size, + (event_size % INOTIFY_EVENT_SIZE == 0) ? "yes" : "no"); + + // Move to next event + offset += event_size; + event_count++; + + // Verify next event offset is aligned + if (offset < (size_t)total_read && + offset % INOTIFY_EVENT_SIZE != 0) { + fprintf(stderr, + "ERROR: Next event offset %zu is not aligned to %d bytes\n", + offset, INOTIFY_EVENT_SIZE); + return 1; + } + } + + // Cleanup + close(ifd); + for (int i = 0; test_files[i] != NULL; i++) { + unlink(test_files[i]); + } + rmdir(dir); + + printf("\nAll alignment tests passed! Processed %d events.\n", + event_count); + printf("Summary:\n"); + printf(" - FIONREAD matched actual read size: ✓\n"); + printf(" - All events aligned to 16 bytes: ✓\n"); + printf(" - All len fields correct (padded): ✓\n"); + printf(" - All padding bytes are zero: ✓\n"); + printf(" - Event offsets are aligned: ✓\n"); + + return 0; +} diff --git a/test/src/apps/inotify/inotify_poll.c b/test/src/apps/inotify/inotify_poll.c new file mode 100644 index 000000000..69488d298 --- /dev/null +++ b/test/src/apps/inotify/inotify_poll.c @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: MPL-2.0 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void die(const char *msg) +{ + perror(msg); + exit(1); +} + +static void ensure_dir(const char *path) +{ + if (mkdir(path, 0700) < 0 && errno != EEXIST) + die("mkdir"); +} + +static void touch_after_delay(const char *path) +{ + struct timespec ts = { .tv_sec = 0, .tv_nsec = 200 * 1000 * 1000 }; + nanosleep(&ts, NULL); + int fd = open(path, O_CREAT | O_WRONLY, 0600); + if (fd < 0) + die("open"); + ssize_t wr = write(fd, "x", 1); + if (wr < 0) + die("write"); + if (wr != 1) { + fprintf(stderr, "short write\n"); + exit(1); + } + close(fd); +} + +int main(void) +{ + const char *dir = "inotify_tmp"; + const char *file = "inotify_tmp/testfile"; + + ensure_dir(dir); + + int ifd = inotify_init1(0); + if (ifd < 0) + die("inotify_init1"); + + int wd = inotify_add_watch(ifd, dir, + IN_CREATE | IN_MODIFY | IN_MOVED_TO); + if (wd < 0) + die("inotify_add_watch"); + + /* Subtest 0: without events, poll(0) should timeout */ + { + struct pollfd p0 = { .fd = ifd, + .events = POLLIN, + .revents = 0 }; + int r0 = poll(&p0, 1, 0); + if (r0 < 0) + die("poll"); + if (r0 != 0) { + fprintf(stderr, + "unexpected ready without events: revents=0x%x\n", + p0.revents); + return 4; + } + } + + pid_t pid = fork(); + if (pid < 0) + die("fork"); + if (pid == 0) { + touch_after_delay(file); + _exit(0); + } + + struct pollfd pfd = { .fd = ifd, .events = POLLIN, .revents = 0 }; + int pret = poll(&pfd, 1, 5000); + if (pret < 0) + die("poll"); + if (pret == 0) { + fprintf(stderr, "poll timeout without events\n"); + return 2; + } + if (!(pfd.revents & POLLIN)) { + fprintf(stderr, "unexpected revents: 0x%x\n", pfd.revents); + return 3; + } + + char buf[4096] + __attribute__((aligned(__alignof__(struct inotify_event)))); + /* FIONREAD should indicate pending bytes before read */ + int pending = 0; + if (ioctl(ifd, FIONREAD, &pending) < 0) + die("ioctl(FIONREAD)"); + if (pending <= 0) { + fprintf(stderr, "FIONREAD should be > 0 when POLLIN set\n"); + return 5; + } + ssize_t len = read(ifd, buf, sizeof(buf)); + if (len < 0) + die("read"); + + /* After drain, poll(0) should not fire */ + { + struct pollfd p1 = { .fd = ifd, + .events = POLLIN, + .revents = 0 }; + int r1 = poll(&p1, 1, 0); + if (r1 < 0) + die("poll"); + if (r1 != 0) { + fprintf(stderr, + "still ready after drain: revents=0x%x\n", + p1.revents); + return 6; + } + } + + int status = 0; + waitpid(pid, &status, 0); + close(ifd); + unlink(file); + rmdir(dir); + + printf("inotify poll basic test: OK (bytes=%zd)\n", len); + return 0; +} diff --git a/test/src/apps/scripts/process.sh b/test/src/apps/scripts/process.sh index 7fd920366..3e43dca53 100755 --- a/test/src/apps/scripts/process.sh +++ b/test/src/apps/scripts/process.sh @@ -28,6 +28,8 @@ getcpu/getcpu getpid/getpid hello_pie/hello hello_world/hello_world +inotify/inotify_align +inotify/inotify_poll itimer/setitimer itimer/timer_create mmap/mmap_and_fork diff --git a/test/src/syscall/gvisor/Makefile b/test/src/syscall/gvisor/Makefile index ce3396013..b619df15a 100644 --- a/test/src/syscall/gvisor/Makefile +++ b/test/src/syscall/gvisor/Makefile @@ -19,6 +19,7 @@ TESTS ?= \ fsync_test \ futex_test \ getdents_test \ + inotify_test \ ioctl_test \ link_test \ lseek_test \ diff --git a/test/src/syscall/gvisor/blocklists.exfat/inotify_test b/test/src/syscall/gvisor/blocklists.exfat/inotify_test new file mode 100644 index 000000000..61fd3c733 --- /dev/null +++ b/test/src/syscall/gvisor/blocklists.exfat/inotify_test @@ -0,0 +1,11 @@ +Inotify.SymlinkGeneratesCreateEvent +Inotify.LinkGeneratesAttribAndCreateEvents +Inotify.HardlinksReuseSameWatch +Inotify.Fallocate +Inotify.LinkOnOtherParent +Inotify.Xattr +InotifyTest.NotifyNoDeadlock_NoRandomSave +Inotify.RemoveWatchAfterDeletingFileFails +Inotify.DeletingChildGeneratesEvents +Inotify.RmdirOnWatchedTargetGeneratesEvent +Inotify.UnmatchedEventsAreDiscarded \ No newline at end of file diff --git a/test/src/syscall/gvisor/blocklists/inotify_test b/test/src/syscall/gvisor/blocklists/inotify_test new file mode 100644 index 000000000..1d27c2ed4 --- /dev/null +++ b/test/src/syscall/gvisor/blocklists/inotify_test @@ -0,0 +1,12 @@ +Inotify.MoveGeneratesEvents +Inotify.MoveWatchedTargetGeneratesEvents +Inotify.SpliceOnInotifyFD +Inotify.SpliceOnWatchTarget +Inotify.Exec +Inotify.IncludeUnlinkedFile_NoRandomSave +Inotify.ExcludeUnlink_NoRandomSave +Inotify.ExcludeUnlinkDirectory_NoRandomSave +Inotify.ExcludeUnlinkMultipleChildren_NoRandomSave +Inotify.ExcludeUnlinkInodeEvents_NoRandomSave +Inotify.OneShot +InotifyTest.InotifyAndTargetDestructionDoNotDeadlock_NoRandomSave \ No newline at end of file