From 8abbca48b237c9edc3ffb387e9074ddcff8b645d Mon Sep 17 00:00:00 2001 From: thorpej Date: Wed, 20 Oct 2021 03:08:16 +0000 Subject: Overhaul of the EVFILT_VNODE kevent(2) filter: - Centralize vnode kevent handling in the VOP_*() wrappers, rather than forcing each individual file system to deal with it (except VOP_RENAME(), because VOP_RENAME() is a mess and we currently have 2 different ways of handling it; at least it's reasonably well-centralized in the "new" way). - Add support for NOTE_OPEN, NOTE_CLOSE, NOTE_CLOSE_WRITE, and NOTE_READ, compatible with the same events in FreeBSD. - Track which kevent notifications clients are interested in receiving to avoid doing work for events no one cares about (avoiding, e.g. taking locks and traversing the klist to send a NOTE_WRITE when someone is merely watching for a file to be deleted, for example). In support of the above: - Add support in vnode_if.sh for specifying PRE- and POST-op handlers, to be invoked before and after vop_pre() and vop_post(), respectively. Basic idea from FreeBSD, but implemented differently. - Add support in vnode_if.sh for specifying CONTEXT fields in the vop_*_args structures. These context fields are used to convey information between the file system VOP function and the VOP wrapper, but do not occupy an argument slot in the VOP_*() call itself. These context fields are initialized and subsequently interpreted by PRE- and POST-op handlers. - Version VOP_REMOVE(), uses the a context field for the file system to report back the resulting link count of the target vnode. Return this in tmpfs, udf, nfs, chfs, ext2fs, lfs, and ufs. NetBSD 9.99.92. --- tests/kernel/kqueue/t_vnode.c | 173 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) (limited to 'tests/kernel/kqueue') diff --git a/tests/kernel/kqueue/t_vnode.c b/tests/kernel/kqueue/t_vnode.c index e87c2b2c2d6..b3a95cc8e93 100644 --- a/tests/kernel/kqueue/t_vnode.c +++ b/tests/kernel/kqueue/t_vnode.c @@ -509,25 +509,198 @@ ATF_TC_CLEANUP(dir_note_write_mv_file_within, tc) cleanup(); } +static const char testfile[] = "testfile"; + +ATF_TC_WITH_CLEANUP(open_write_read_close); +ATF_TC_HEAD(open_write_read_close, tc) +{ + atf_tc_set_md_var(tc, "descr", "This test case exercises " + "that kevent(2) returns NOTE_OPEN, NOTE_READ, NOTE_WRITE, " + "NOTE_CLOSE, and NOTE_CLOSE_WRITE."); +} +ATF_TC_BODY(open_write_read_close, tc) +{ + struct kevent event[1]; + char buf[sizeof(testfile)]; + int fd; + + ATF_REQUIRE((kq = kqueue()) != -1); + + /* + * Create the test file and register an event on it. We need + * to keep the fd open to keep receiving events, so we'll just + * leak it and re-use the fd variable. + */ + ATF_REQUIRE((fd = open(testfile, + O_RDWR | O_CREAT | O_TRUNC, 0600)) != -1); + EV_SET(&event[0], fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, + NOTE_OPEN | NOTE_READ | NOTE_WRITE | + NOTE_CLOSE | NOTE_CLOSE_WRITE, 0, NULL); + ATF_REQUIRE(kevent(kq, event, 1, NULL, 0, NULL) == 0); + + /* + * Open the file for writing, check for NOTE_OPEN. + * Write to the file, check for NOTE_WRITE | NOTE_EXTEND. + * Re-write the file, check for NOTE_WRITE and !NOTE_EXTEND. + * Write one additional byte, check for NOTE_WRITE | NOTE_EXTEND. + * Close the file, check for NOTE_CLOSE_WRITE. + */ + ATF_REQUIRE((fd = open(testfile, O_RDWR)) != -1); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_OPEN); + + ATF_REQUIRE((pwrite(fd, testfile, + sizeof(testfile), 0)) == sizeof(testfile)); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_WRITE); + ATF_REQUIRE(event[0].fflags & NOTE_EXTEND); + + ATF_REQUIRE((pwrite(fd, testfile, + sizeof(testfile), 0)) == sizeof(testfile)); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_WRITE); + ATF_REQUIRE((event[0].fflags & NOTE_EXTEND) == 0); + + ATF_REQUIRE((pwrite(fd, testfile, + 1, sizeof(testfile))) == 1); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_WRITE); + ATF_REQUIRE(event[0].fflags & NOTE_EXTEND); + + (void)close(fd); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_CLOSE_WRITE); + ATF_REQUIRE((event[0].fflags & NOTE_CLOSE) == 0); + + /* + * Open the file for reading, check for NOTE_OPEN. + * Read from the file, check for NOTE_READ. + * Close the file., check for NOTE_CLOSE. + */ + ATF_REQUIRE((fd = open(testfile, O_RDONLY)) != -1); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_OPEN); + + ATF_REQUIRE((read(fd, buf, sizeof(buf))) == sizeof(buf)); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_READ); + + (void)close(fd); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_CLOSE); + ATF_REQUIRE((event[0].fflags & NOTE_CLOSE_WRITE) == 0); +} +ATF_TC_CLEANUP(open_write_read_close, tc) +{ + (void)unlink(testfile); +} + +ATF_TC_WITH_CLEANUP(interest); +ATF_TC_HEAD(interest, tc) +{ + atf_tc_set_md_var(tc, "descr", "This test case exercises " + "the kernel code that computes vnode kevent interest"); +} +ATF_TC_BODY(interest, tc) +{ + struct kevent event[3]; + int open_ev_fd, write_ev_fd, close_ev_fd; + int fd; + + /* + * This test cases exercises some implementation details + * regarding how "kevent interest" is computed for a vnode. + * + * We are going to add events, one at a time, in a specific + * order, and then remove one of them, with the knowledge that + * a specific code path in vfs_vnops.c:vn_knote_detach() will + * be taken. There are several KASSERT()s in this code path + * that will be validated. + * + * In order to ensure distinct knotes are attached to the vnodes, + * we must use a different file descriptor to register interest + * in each kind of event. + */ + + ATF_REQUIRE((kq = kqueue()) != -1); + + /* + * Create the test file and register an event on it. We need + * to keep the fd open to keep receiving events, so we'll just + * leak it and re-use the fd variable. + */ + ATF_REQUIRE((open_ev_fd = open(testfile, + O_RDWR | O_CREAT | O_TRUNC, 0600)) != -1); + ATF_REQUIRE((write_ev_fd = dup(open_ev_fd)) != -1); + ATF_REQUIRE((close_ev_fd = dup(open_ev_fd)) != -1); + + EV_SET(&event[0], open_ev_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, + NOTE_OPEN, 0, NULL); + EV_SET(&event[1], write_ev_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, + NOTE_WRITE, 0, NULL); + EV_SET(&event[2], close_ev_fd, EVFILT_VNODE, EV_ADD | EV_CLEAR, + NOTE_CLOSE | NOTE_CLOSE_WRITE, 0, NULL); + ATF_REQUIRE(kevent(kq, event, 3, NULL, 0, NULL) == 0); + + /* + * The testfile vnode now has 3 knotes attached, in "LIFO" + * order: + * + * NOTE_CLOSE -> NOTE_WRITE -> NOTE_OPEN + * + * We will now remove the NOTE_WRITE knote. + */ + (void)close(write_ev_fd); + + ATF_REQUIRE((fd = open(testfile, O_RDWR)) != -1); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_OPEN); + + ATF_REQUIRE((pwrite(fd, testfile, + sizeof(testfile), 0)) == sizeof(testfile)); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 0); + + (void)close(fd); + ATF_REQUIRE(kevent(kq, NULL, 0, event, 1, &ts) == 1); + ATF_REQUIRE(event[0].fflags & NOTE_CLOSE_WRITE); + ATF_REQUIRE((event[0].fflags & NOTE_CLOSE) == 0); + +} +ATF_TC_CLEANUP(interest, tc) +{ + (void)unlink(testfile); +} + + ATF_TP_ADD_TCS(tp) { ATF_TP_ADD_TC(tp, dir_no_note_link_create_file_in); ATF_TP_ADD_TC(tp, dir_no_note_link_delete_file_in); + ATF_TP_ADD_TC(tp, dir_no_note_link_mv_dir_within); ATF_TP_ADD_TC(tp, dir_no_note_link_mv_file_within); + ATF_TP_ADD_TC(tp, dir_note_link_create_dir_in); ATF_TP_ADD_TC(tp, dir_note_link_delete_dir_in); + ATF_TP_ADD_TC(tp, dir_note_link_mv_dir_in); ATF_TP_ADD_TC(tp, dir_note_link_mv_dir_out); + ATF_TP_ADD_TC(tp, dir_note_write_create_dir_in); ATF_TP_ADD_TC(tp, dir_note_write_create_file_in); + ATF_TP_ADD_TC(tp, dir_note_write_delete_dir_in); ATF_TP_ADD_TC(tp, dir_note_write_delete_file_in); + ATF_TP_ADD_TC(tp, dir_note_write_mv_dir_in); ATF_TP_ADD_TC(tp, dir_note_write_mv_dir_out); ATF_TP_ADD_TC(tp, dir_note_write_mv_dir_within); ATF_TP_ADD_TC(tp, dir_note_write_mv_file_in); ATF_TP_ADD_TC(tp, dir_note_write_mv_file_out); ATF_TP_ADD_TC(tp, dir_note_write_mv_file_within); + + ATF_TP_ADD_TC(tp, open_write_read_close); + ATF_TP_ADD_TC(tp, interest); + return atf_no_error(); } -- cgit