summaryrefslogtreecommitdiffhomepage
path: root/test/syscalls/linux/inotify.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/syscalls/linux/inotify.cc')
-rw-r--r--test/syscalls/linux/inotify.cc1947
1 files changed, 0 insertions, 1947 deletions
diff --git a/test/syscalls/linux/inotify.cc b/test/syscalls/linux/inotify.cc
deleted file mode 100644
index 2306d9cab..000000000
--- a/test/syscalls/linux/inotify.cc
+++ /dev/null
@@ -1,1947 +0,0 @@
-// Copyright 2018 The gVisor Authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-#include <fcntl.h>
-#include <libgen.h>
-#include <sched.h>
-#include <sys/epoll.h>
-#include <sys/inotify.h>
-#include <sys/ioctl.h>
-#include <sys/time.h>
-
-#include <atomic>
-#include <list>
-#include <string>
-#include <vector>
-
-#include "absl/strings/str_cat.h"
-#include "absl/strings/str_format.h"
-#include "absl/strings/str_join.h"
-#include "absl/time/clock.h"
-#include "absl/time/time.h"
-#include "test/util/epoll_util.h"
-#include "test/util/file_descriptor.h"
-#include "test/util/fs_util.h"
-#include "test/util/posix_error.h"
-#include "test/util/temp_path.h"
-#include "test/util/test_util.h"
-#include "test/util/thread_util.h"
-
-namespace gvisor {
-namespace testing {
-namespace {
-
-using ::absl::StreamFormat;
-using ::absl::StrFormat;
-
-constexpr int kBufSize = 1024;
-
-// C++-friendly version of struct inotify_event.
-struct Event {
- int32_t wd;
- uint32_t mask;
- uint32_t cookie;
- uint32_t len;
- std::string name;
-
- Event(uint32_t mask, int32_t wd, absl::string_view name, uint32_t cookie)
- : wd(wd),
- mask(mask),
- cookie(cookie),
- len(name.size()),
- name(std::string(name)) {}
- Event(uint32_t mask, int32_t wd, absl::string_view name)
- : Event(mask, wd, name, 0) {}
- Event(uint32_t mask, int32_t wd) : Event(mask, wd, "", 0) {}
- Event() : Event(0, 0, "", 0) {}
-};
-
-// Prints the symbolic name for a struct inotify_event's 'mask' field.
-std::string FlagString(uint32_t flags) {
- std::vector<std::string> names;
-
-#define EMIT(target) \
- if (flags & target) { \
- names.push_back(#target); \
- flags &= ~target; \
- }
-
- EMIT(IN_ACCESS);
- EMIT(IN_ATTRIB);
- EMIT(IN_CLOSE_WRITE);
- EMIT(IN_CLOSE_NOWRITE);
- EMIT(IN_CREATE);
- EMIT(IN_DELETE);
- EMIT(IN_DELETE_SELF);
- EMIT(IN_MODIFY);
- EMIT(IN_MOVE_SELF);
- EMIT(IN_MOVED_FROM);
- EMIT(IN_MOVED_TO);
- EMIT(IN_OPEN);
-
- EMIT(IN_DONT_FOLLOW);
- EMIT(IN_EXCL_UNLINK);
- EMIT(IN_ONESHOT);
- EMIT(IN_ONLYDIR);
-
- EMIT(IN_IGNORED);
- EMIT(IN_ISDIR);
- EMIT(IN_Q_OVERFLOW);
- EMIT(IN_UNMOUNT);
-
-#undef EMIT
-
- // If we have anything left over at the end, print it as a hex value.
- if (flags) {
- names.push_back(absl::StrCat("0x", absl::Hex(flags)));
- }
-
- return absl::StrJoin(names, "|");
-}
-
-std::string DumpEvent(const Event& event) {
- return StrFormat(
- "%s, wd=%d%s%s", FlagString(event.mask), event.wd,
- (event.len > 0) ? StrFormat(", name=%s", event.name) : "",
- (event.cookie > 0) ? StrFormat(", cookie=%ud", event.cookie) : "");
-}
-
-std::string DumpEvents(const std::vector<Event>& events, int indent_level) {
- std::stringstream ss;
- ss << StreamFormat("%d event%s:\n", events.size(),
- (events.size() > 1) ? "s" : "");
- int i = 0;
- for (const Event& ev : events) {
- ss << StreamFormat("%sevents[%d]: %s\n", std::string(indent_level, '\t'),
- i++, DumpEvent(ev));
- }
- return ss.str();
-}
-
-// A matcher which takes an expected list of events to match against another
-// list of inotify events, in order. This is similar to the ElementsAre matcher,
-// but displays more informative messages on mismatch.
-class EventsAreMatcher
- : public ::testing::MatcherInterface<std::vector<Event>> {
- public:
- explicit EventsAreMatcher(std::vector<Event> references)
- : references_(std::move(references)) {}
-
- bool MatchAndExplain(
- std::vector<Event> events,
- ::testing::MatchResultListener* const listener) const override {
- if (references_.size() != events.size()) {
- *listener << StreamFormat("\n\tCount mismatch, got %s",
- DumpEvents(events, 2));
- return false;
- }
-
- bool success = true;
- for (unsigned int i = 0; i < references_.size(); ++i) {
- const Event& reference = references_[i];
- const Event& target = events[i];
-
- if (target.mask != reference.mask || target.wd != reference.wd ||
- target.name != reference.name || target.cookie != reference.cookie) {
- *listener << StreamFormat("\n\tMismatch at index %d, want %s, got %s,",
- i, DumpEvent(reference), DumpEvent(target));
- success = false;
- }
- }
-
- if (!success) {
- *listener << StreamFormat("\n\tIn total of %s", DumpEvents(events, 2));
- }
- return success;
- }
-
- void DescribeTo(::std::ostream* const os) const override {
- *os << StreamFormat("%s", DumpEvents(references_, 1));
- }
-
- void DescribeNegationTo(::std::ostream* const os) const override {
- *os << StreamFormat("mismatch from %s", DumpEvents(references_, 1));
- }
-
- private:
- std::vector<Event> references_;
-};
-
-::testing::Matcher<std::vector<Event>> Are(std::vector<Event> events) {
- return MakeMatcher(new EventsAreMatcher(std::move(events)));
-}
-
-// Similar to the EventsAre matcher, but the order of events are ignored.
-class UnorderedEventsAreMatcher
- : public ::testing::MatcherInterface<std::vector<Event>> {
- public:
- explicit UnorderedEventsAreMatcher(std::vector<Event> references)
- : references_(std::move(references)) {}
-
- bool MatchAndExplain(
- std::vector<Event> events,
- ::testing::MatchResultListener* const listener) const override {
- if (references_.size() != events.size()) {
- *listener << StreamFormat("\n\tCount mismatch, got %s",
- DumpEvents(events, 2));
- return false;
- }
-
- std::vector<Event> unmatched(references_);
-
- for (const Event& candidate : events) {
- for (auto it = unmatched.begin(); it != unmatched.end();) {
- const Event& reference = *it;
- if (candidate.mask == reference.mask && candidate.wd == reference.wd &&
- candidate.name == reference.name &&
- candidate.cookie == reference.cookie) {
- it = unmatched.erase(it);
- break;
- } else {
- ++it;
- }
- }
- }
-
- // Anything left unmatched? If so, the matcher fails.
- if (!unmatched.empty()) {
- *listener << StreamFormat("\n\tFailed to match %s",
- DumpEvents(unmatched, 2));
- *listener << StreamFormat("\n\tIn total of %s", DumpEvents(events, 2));
- return false;
- }
-
- return true;
- }
-
- void DescribeTo(::std::ostream* const os) const override {
- *os << StreamFormat("unordered %s", DumpEvents(references_, 1));
- }
-
- void DescribeNegationTo(::std::ostream* const os) const override {
- *os << StreamFormat("mismatch from unordered %s",
- DumpEvents(references_, 1));
- }
-
- private:
- std::vector<Event> references_;
-};
-
-::testing::Matcher<std::vector<Event>> AreUnordered(std::vector<Event> events) {
- return MakeMatcher(new UnorderedEventsAreMatcher(std::move(events)));
-}
-
-// Reads events from an inotify fd until either EOF, or read returns EAGAIN.
-PosixErrorOr<std::vector<Event>> DrainEvents(int fd) {
- std::vector<Event> events;
- while (true) {
- int events_size = 0;
- if (ioctl(fd, FIONREAD, &events_size) < 0) {
- return PosixError(errno, "ioctl(FIONREAD) failed on inotify fd");
- }
- // Deliberately use a buffer that is larger than necessary, expecting to
- // only read events_size bytes.
- std::vector<char> buf(events_size + kBufSize, 0);
- const ssize_t readlen = read(fd, buf.data(), buf.size());
- MaybeSave();
- // Read error?
- if (readlen < 0) {
- if (errno == EAGAIN) {
- // If EAGAIN, no more events at the moment. Return what we have so far.
- return events;
- }
- // Some other read error. Return an error. Right now if we encounter this
- // after already reading some events, they get lost. However, we don't
- // expect to see any error, and the calling test will fail immediately if
- // we signal an error anyways, so this is acceptable.
- return PosixError(errno, "read() failed on inotify fd");
- }
- if (readlen < static_cast<int>(sizeof(struct inotify_event))) {
- // Impossibly short read.
- return PosixError(
- EIO,
- "read() didn't return enough data represent even a single event");
- }
- if (readlen != events_size) {
- return PosixError(EINVAL, absl::StrCat("read ", readlen,
- " bytes, expected ", events_size));
- }
- if (readlen == 0) {
- // EOF.
- return events;
- }
-
- // Normal read.
- const char* cursor = buf.data();
- while (cursor < (buf.data() + readlen)) {
- struct inotify_event event = {};
- memcpy(&event, cursor, sizeof(struct inotify_event));
-
- Event ev;
- ev.wd = event.wd;
- ev.mask = event.mask;
- ev.cookie = event.cookie;
- ev.len = event.len;
- if (event.len > 0) {
- TEST_CHECK(static_cast<int>(sizeof(struct inotify_event) + event.len) <=
- readlen);
- ev.name = std::string(cursor +
- offsetof(struct inotify_event, name)); // NOLINT
- // Name field should always be smaller than event.len, otherwise we have
- // a buffer overflow. The two sizes aren't equal because the string
- // constructor will stop at the first null byte, while event.name may be
- // padded up to event.len using multiple null bytes.
- TEST_CHECK(ev.name.size() <= event.len);
- }
-
- events.push_back(ev);
- cursor += sizeof(struct inotify_event) + event.len;
- }
- }
-}
-
-PosixErrorOr<FileDescriptor> InotifyInit1(int flags) {
- int fd;
- EXPECT_THAT(fd = inotify_init1(flags), SyscallSucceeds());
- if (fd < 0) {
- return PosixError(errno, "inotify_init1() failed");
- }
- return FileDescriptor(fd);
-}
-
-PosixErrorOr<int> InotifyAddWatch(int fd, const std::string& path,
- uint32_t mask) {
- int wd;
- EXPECT_THAT(wd = inotify_add_watch(fd, path.c_str(), mask),
- SyscallSucceeds());
- if (wd < 0) {
- return PosixError(errno, "inotify_add_watch() failed");
- }
- return wd;
-}
-
-TEST(Inotify, InotifyFdNotWritable) {
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
- EXPECT_THAT(write(fd.get(), "x", 1), SyscallFailsWithErrno(EBADF));
-}
-
-TEST(Inotify, InitFlags) {
- EXPECT_THAT(inotify_init1(IN_NONBLOCK | IN_CLOEXEC), SyscallSucceeds());
- EXPECT_THAT(inotify_init1(12345), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, NonBlockingReadReturnsEagain) {
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- std::vector<char> buf(kBufSize, 0);
-
- // The read below should return fail with EAGAIN because there is no data to
- // read and we've specified IN_NONBLOCK. We're guaranteed that there is no
- // data to read because we haven't registered any watches yet.
- EXPECT_THAT(read(fd.get(), buf.data(), buf.size()),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(Inotify, AddWatchOnInvalidFdFails) {
- // Garbage fd.
- EXPECT_THAT(inotify_add_watch(-1, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EBADF));
- EXPECT_THAT(inotify_add_watch(1337, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EBADF));
-
- // Non-inotify fds.
- EXPECT_THAT(inotify_add_watch(0, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(inotify_add_watch(1, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(inotify_add_watch(2, "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(Open("/tmp", O_RDONLY));
- EXPECT_THAT(inotify_add_watch(fd.get(), "/tmp", IN_ALL_EVENTS),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, RemovingWatchGeneratesEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
-
- // Read events, ensure the first event is IN_IGNORED.
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_IGNORED, wd)}));
-}
-
-TEST(Inotify, CanDeleteFileAfterRemovingWatch) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
- file1.reset();
-}
-
-TEST(Inotify, RemoveWatchAfterDeletingFileFails) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- file1.reset();
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd), Event(IN_DELETE_SELF, wd),
- Event(IN_IGNORED, wd)}));
-
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, DuplicateWatchRemovalFails) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, ConcurrentFileDeletionAndWatchRemoval) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const std::string filename = NewTempAbsPathInDir(root.path());
-
- auto file_create_delete = [filename]() {
- const DisableSave ds; // Too expensive.
- for (int i = 0; i < 100; ++i) {
- FileDescriptor file_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(filename, O_CREAT, S_IRUSR | S_IWUSR));
- file_fd.reset(); // Close before unlinking (although save is disabled).
- EXPECT_THAT(unlink(filename.c_str()), SyscallSucceeds());
- }
- };
-
- const int shared_fd = fd.get(); // We need to pass it to the thread.
- auto add_remove_watch = [shared_fd, filename]() {
- for (int i = 0; i < 100; ++i) {
- int wd = inotify_add_watch(shared_fd, filename.c_str(), IN_ALL_EVENTS);
- MaybeSave();
- if (wd != -1) {
- // Watch added successfully, try removal.
- if (inotify_rm_watch(shared_fd, wd)) {
- // If removal fails, the only acceptable reason is if the wd
- // is invalid, which will be the case if we try to remove
- // the watch after the file has been deleted.
- EXPECT_EQ(errno, EINVAL);
- }
- } else {
- // Add watch failed, this should only fail if the target file doesn't
- // exist.
- EXPECT_EQ(errno, ENOENT);
- }
- }
- };
-
- ScopedThread t1(file_create_delete);
- ScopedThread t2(add_remove_watch);
-}
-
-TEST(Inotify, DeletingChildGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- const std::string file1_path = file1.reset();
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- AreUnordered({Event(IN_ATTRIB, file1_wd), Event(IN_DELETE_SELF, file1_wd),
- Event(IN_IGNORED, file1_wd),
- Event(IN_DELETE, root_wd, Basename(file1_path))}));
-}
-
-// Creating a file in "parent/child" should generate events for child, but not
-// parent.
-TEST(Inotify, CreatingFileGeneratesEvents) {
- const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath child =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS));
-
- // Create a new file in the directory.
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(child.path()));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- // The library function we use to create the new file opens it for writing to
- // create it and sets permissions on it, so we expect the three extra events.
- ASSERT_THAT(events, Are({Event(IN_CREATE, wd, Basename(file1.path())),
- Event(IN_OPEN, wd, Basename(file1.path())),
- Event(IN_CLOSE_WRITE, wd, Basename(file1.path())),
- Event(IN_ATTRIB, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, ReadingFileGeneratesAccessEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- char buf;
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ACCESS, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, WritingFileGeneratesModifyEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- const std::string data = "some content";
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_MODIFY, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, SizeZeroReadWriteGeneratesNothing) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- // Read from the empty file.
- int val;
- ASSERT_THAT(read(file1_fd.get(), &val, sizeof(val)),
- SyscallSucceedsWithValue(0));
-
- // Write zero bytes.
- ASSERT_THAT(write(file1_fd.get(), "", 0), SyscallSucceedsWithValue(0));
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({}));
-}
-
-TEST(Inotify, FailedFileCreationGeneratesNoEvents) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir.path(), IN_ALL_EVENTS));
-
- const char* p = dir.path().c_str();
- ASSERT_THAT(mkdir(p, 0777), SyscallFails());
- ASSERT_THAT(mknod(p, S_IFIFO, 0777), SyscallFails());
- ASSERT_THAT(symlink(p, p), SyscallFails());
- ASSERT_THAT(link(p, p), SyscallFails());
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({}));
-}
-
-TEST(Inotify, WatchSetAfterOpenReportsCloseFdEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- FileDescriptor file1_fd_writable =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- FileDescriptor file1_fd_not_writable =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- file1_fd_writable.reset(); // Close file1_fd_writable.
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_CLOSE_WRITE, wd, Basename(file1.path()))}));
-
- file1_fd_not_writable.reset(); // Close file1_fd_not_writable.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events,
- Are({Event(IN_CLOSE_NOWRITE, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, ChildrenDeletionInWatchedDirGeneratesEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- const std::string file1_path = file1.reset();
- const std::string dir1_path = dir1.release();
- EXPECT_THAT(rmdir(dir1_path.c_str()), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- ASSERT_THAT(events,
- Are({Event(IN_DELETE, wd, Basename(file1_path)),
- Event(IN_DELETE | IN_ISDIR, wd, Basename(dir1_path))}));
-}
-
-TEST(Inotify, RmdirOnWatchedTargetGeneratesEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- EXPECT_THAT(rmdir(root.path().c_str()), SyscallSucceeds());
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_DELETE_SELF, wd), Event(IN_IGNORED, wd)}));
-}
-
-TEST(Inotify, MoveGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const TempPath dir1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
- const TempPath dir2 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int dir1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir1.path(), IN_ALL_EVENTS));
- const int dir2_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir2.path(), IN_ALL_EVENTS));
- // Test move from root -> root.
- std::string newpath = NewTempAbsPathInDir(root.path());
- std::string oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, root_wd, Basename(newpath), events[1].cookie)}));
- EXPECT_NE(events[0].cookie, 0);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
- uint32_t last_cookie = events[0].cookie;
-
- // Test move from root -> root/dir1.
- newpath = NewTempAbsPathInDir(dir1.path());
- oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, dir1_wd, Basename(newpath), events[1].cookie)}));
- // Cookies should be distinct between distinct rename events.
- EXPECT_NE(events[0].cookie, last_cookie);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
- last_cookie = events[0].cookie;
-
- // Test move from root/dir1 -> root/dir2.
- newpath = NewTempAbsPathInDir(dir2.path());
- oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, dir1_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, dir2_wd, Basename(newpath), events[1].cookie)}));
- EXPECT_NE(events[0].cookie, last_cookie);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
- last_cookie = events[0].cookie;
-}
-
-TEST(Inotify, MoveWatchedTargetGeneratesEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- const std::string newpath = NewTempAbsPathInDir(root.path());
- const std::string oldpath = file1.release();
- EXPECT_THAT(rename(oldpath.c_str(), newpath.c_str()), SyscallSucceeds());
- file1.reset(newpath);
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_MOVED_FROM, root_wd, Basename(oldpath), events[0].cookie),
- Event(IN_MOVED_TO, root_wd, Basename(newpath), events[1].cookie),
- // Self move events do not have a cookie.
- Event(IN_MOVE_SELF, file1_wd)}));
- EXPECT_NE(events[0].cookie, 0);
- EXPECT_EQ(events[0].cookie, events[1].cookie);
-}
-
-TEST(Inotify, CoalesceEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- // Read the file a few times. This will would generate multiple IN_ACCESS
- // events but they should get coalesced to a single event.
- char buf;
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- // Use the close event verify that we haven't simply left the additional
- // IN_ACCESS events unread.
- file1_fd.reset(); // Close file1_fd.
-
- const std::string file1_name = std::string(Basename(file1.path()));
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ACCESS, wd, file1_name),
- Event(IN_CLOSE_NOWRITE, wd, file1_name)}));
-
- // Now let's try interleaving other events into a stream of repeated events.
- file1_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
-
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds());
- EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds());
- EXPECT_THAT(write(file1_fd.get(), "x", 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- file1_fd.reset(); // Close the file.
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_OPEN, wd, file1_name), Event(IN_ACCESS, wd, file1_name),
- Event(IN_MODIFY, wd, file1_name), Event(IN_ACCESS, wd, file1_name),
- Event(IN_CLOSE_WRITE, wd, file1_name)}));
-
- // Ensure events aren't coalesced if they are from different files.
- const TempPath file2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
- // Discard events resulting from creation of file2.
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- file1_fd = ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- FileDescriptor file2_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file2.path(), O_RDONLY));
-
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file2_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- // Close both files.
- file1_fd.reset();
- file2_fd.reset();
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- const std::string file2_name = std::string(Basename(file2.path()));
- ASSERT_THAT(
- events,
- Are({Event(IN_OPEN, wd, file1_name), Event(IN_OPEN, wd, file2_name),
- Event(IN_ACCESS, wd, file1_name), Event(IN_ACCESS, wd, file2_name),
- Event(IN_ACCESS, wd, file1_name),
- Event(IN_CLOSE_NOWRITE, wd, file1_name),
- Event(IN_CLOSE_NOWRITE, wd, file2_name)}));
-}
-
-TEST(Inotify, ClosingInotifyFdWithoutRemovingWatchesWorks) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- // Note: The check on close will happen in FileDescriptor::~FileDescriptor().
-}
-
-TEST(Inotify, NestedWatches) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- // Read from file1. This should generate an event for both watches.
- char buf;
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ACCESS, root_wd, Basename(file1.path())),
- Event(IN_ACCESS, file1_wd)}));
-}
-
-TEST(Inotify, ConcurrentThreadsGeneratingEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- std::vector<TempPath> files;
- files.reserve(10);
- for (int i = 0; i < 10; i++) {
- files.emplace_back(ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode)));
- }
-
- auto test_thread = [&files]() {
- uint32_t seed = time(nullptr);
- for (int i = 0; i < 20; i++) {
- const TempPath& file = files[rand_r(&seed) % files.size()];
- const FileDescriptor file_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_WRONLY));
- TEST_PCHECK(write(file_fd.get(), "x", 1) == 1);
- }
- };
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- std::list<ScopedThread> threads;
- for (int i = 0; i < 3; i++) {
- threads.emplace_back(test_thread);
- }
- for (auto& t : threads) {
- t.Join();
- }
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- // 3 threads doing 20 iterations, 3 events per iteration (open, write,
- // close). However, some events may be coalesced, and we can't reliably
- // predict how they'll be coalesced since the test threads aren't
- // synchronized. We can only check that we aren't getting unexpected events.
- for (const Event& ev : events) {
- EXPECT_NE(ev.mask & (IN_OPEN | IN_MODIFY | IN_CLOSE_WRITE), 0);
- }
-}
-
-TEST(Inotify, ReadWithTooSmallBufferFails) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- // Open the file to queue an event. This event will not have a filename, so
- // reading from the inotify fd should return sizeof(struct inotify_event)
- // bytes of data.
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- std::vector<char> buf(kBufSize, 0);
- ssize_t readlen;
-
- // Try a buffer too small to hold any potential event. This is rejected
- // outright without the event being dequeued.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event) - 1),
- SyscallFailsWithErrno(EINVAL));
- // Try a buffer just large enough. This should succeeed.
- EXPECT_THAT(
- readlen = read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallSucceeds());
- EXPECT_EQ(readlen, sizeof(struct inotify_event));
- // Event queue is now empty, the next read should return EAGAIN.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallFailsWithErrno(EAGAIN));
-
- // Now put a watch on the directory, so that generated events contain a name.
- EXPECT_THAT(inotify_rm_watch(fd.get(), wd), SyscallSucceeds());
-
- // Drain the event generated from the watch removal.
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- file1_fd.reset(); // Close file to generate an event.
-
- // Try a buffer too small to hold any event and one too small to hold an event
- // with a name. These should both fail without consuming the event.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event) - 1),
- SyscallFailsWithErrno(EINVAL));
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallFailsWithErrno(EINVAL));
- // Now try with a large enough buffer. This should return the one event.
- EXPECT_THAT(readlen = read(fd.get(), buf.data(), buf.size()),
- SyscallSucceeds());
- EXPECT_GE(readlen,
- sizeof(struct inotify_event) + Basename(file1.path()).size());
- // With the single event read, the queue should once again be empty.
- EXPECT_THAT(read(fd.get(), buf.data(), sizeof(struct inotify_event)),
- SyscallFailsWithErrno(EAGAIN));
-}
-
-TEST(Inotify, BlockingReadOnInotifyFd) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(0));
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- // Spawn a thread performing a blocking read for new events on the inotify fd.
- std::vector<char> buf(kBufSize, 0);
- const int shared_fd = fd.get(); // The thread needs it.
- ScopedThread t([shared_fd, &buf]() {
- ssize_t readlen;
- EXPECT_THAT(readlen = read(shared_fd, buf.data(), buf.size()),
- SyscallSucceeds());
- });
-
- // Perform a read on the watched file, which should generate an IN_ACCESS
- // event, unblocking the event_reader thread.
- char c;
- EXPECT_THAT(read(file1_fd.get(), &c, 1), SyscallSucceeds());
-
- // Wait for the thread to read the event and exit.
- t.Join();
-
- // Make sure the event we got back is sane.
- uint32_t event_mask;
- memcpy(&event_mask, buf.data() + offsetof(struct inotify_event, mask),
- sizeof(event_mask));
- EXPECT_EQ(event_mask, IN_ACCESS);
-}
-
-TEST(Inotify, WatchOnRelativePath) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
-
- // Change working directory to root.
- const FileDescriptor cwd = ASSERT_NO_ERRNO_AND_VALUE(Open(".", O_PATH));
- EXPECT_THAT(chdir(root.path().c_str()), SyscallSucceeds());
-
- // Add a watch on file1 with a relative path.
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
- fd.get(), std::string(Basename(file1.path())), IN_ALL_EVENTS));
-
- // Perform a read on file1, this should generate an IN_ACCESS event.
- char c;
- EXPECT_THAT(read(file1_fd.get(), &c, 1), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ACCESS, wd)}));
-
- // Explicitly reset the working directory so that we don't continue to
- // reference "root". Once the test ends, "root" will get unlinked. If we
- // continue to hold a reference, random save/restore tests can fail if a save
- // is triggered after "root" is unlinked; we can't save deleted fs objects
- // with active references.
- EXPECT_THAT(fchdir(cwd.get()), SyscallSucceeds());
-}
-
-TEST(Inotify, ZeroLengthReadWriteDoesNotGenerateEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const char kContent[] = "some content";
- TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), kContent, TempPath::kDefaultFileMode));
- const int kContentSize = sizeof(kContent) - 1;
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- std::vector<char> buf(kContentSize, 0);
- // Read all available data.
- ssize_t readlen;
- EXPECT_THAT(readlen = read(file1_fd.get(), buf.data(), kContentSize),
- SyscallSucceeds());
- EXPECT_EQ(readlen, kContentSize);
- // Drain all events and make sure we got the IN_ACCESS for the read.
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ACCESS, wd, Basename(file1.path()))}));
-
- // Now try read again. This should be a 0-length read, since we're at EOF.
- char c;
- EXPECT_THAT(readlen = read(file1_fd.get(), &c, 1), SyscallSucceeds());
- EXPECT_EQ(readlen, 0);
- // We should have no new events.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_TRUE(events.empty());
-
- // Try issuing a zero-length read.
- EXPECT_THAT(readlen = read(file1_fd.get(), &c, 0), SyscallSucceeds());
- EXPECT_EQ(readlen, 0);
- // We should have no new events.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_TRUE(events.empty());
-
- // Try issuing a zero-length write.
- ssize_t writelen;
- EXPECT_THAT(writelen = write(file1_fd.get(), &c, 0), SyscallSucceeds());
- EXPECT_EQ(writelen, 0);
- // We should have no new events.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_TRUE(events.empty());
-}
-
-TEST(Inotify, ChmodGeneratesAttribEvent_NoRandomSave) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- FileDescriptor root_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(root.path(), O_RDONLY));
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
- FileDescriptor fd = ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- auto verify_chmod_events = [&]() {
- std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, root_wd, Basename(file1.path())),
- Event(IN_ATTRIB, file1_wd)}));
- };
-
- // Don't do cooperative S/R tests for any of the {f}chmod* syscalls below, the
- // test will always fail because nodes cannot be saved when they have stricter
- // permissions than the original host node.
- const DisableSave ds;
-
- // Chmod.
- ASSERT_THAT(chmod(file1.path().c_str(), S_IWGRP), SyscallSucceeds());
- verify_chmod_events();
-
- // Fchmod.
- ASSERT_THAT(fchmod(file1_fd.get(), S_IRGRP | S_IWGRP), SyscallSucceeds());
- verify_chmod_events();
-
- // Fchmodat.
- const std::string file1_basename = std::string(Basename(file1.path()));
- ASSERT_THAT(fchmodat(root_fd.get(), file1_basename.c_str(), S_IWGRP, 0),
- SyscallSucceeds());
- verify_chmod_events();
-
- // Make sure the chmod'ed file descriptors are destroyed before DisableSave
- // is destructed.
- root_fd.reset();
- file1_fd.reset();
-}
-
-TEST(Inotify, TruncateGeneratesModifyEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- auto verify_truncate_events = [&]() {
- std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_MODIFY, root_wd, Basename(file1.path())),
- Event(IN_MODIFY, file1_wd)}));
- };
-
- // Truncate.
- EXPECT_THAT(truncate(file1.path().c_str(), 4096), SyscallSucceeds());
- verify_truncate_events();
-
- // Ftruncate.
- EXPECT_THAT(ftruncate(file1_fd.get(), 8192), SyscallSucceeds());
- verify_truncate_events();
-
- // No events if truncate fails.
- EXPECT_THAT(ftruncate(file1_fd.get(), -1), SyscallFailsWithErrno(EINVAL));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({}));
-}
-
-TEST(Inotify, GetdentsGeneratesAccessEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- // This internally calls getdents(2). We also expect to see an open/close
- // event for the dirfd.
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(root.path(), false));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- // Linux only seems to generate access events on getdents() on some
- // calls. Allow the test to pass even if it isn't generated. gVisor will
- // always generate the IN_ACCESS event so the test will at least ensure gVisor
- // behaves reasonably.
- int i = 0;
- EXPECT_EQ(events[i].mask, IN_OPEN | IN_ISDIR);
- ++i;
- if (IsRunningOnGvisor()) {
- EXPECT_EQ(events[i].mask, IN_ACCESS | IN_ISDIR);
- ++i;
- } else {
- if (events[i].mask == (IN_ACCESS | IN_ISDIR)) {
- // Skip over the IN_ACCESS event on Linux, it only shows up some of the
- // time so we can't assert its existence.
- ++i;
- }
- }
- EXPECT_EQ(events[i].mask, IN_CLOSE_NOWRITE | IN_ISDIR);
-}
-
-TEST(Inotify, MknodGeneratesCreateEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- const TempPath file1(root.path() + "/file1");
- const int rc = mknod(file1.path().c_str(), S_IFREG, 0);
- // mknod(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0);
- ASSERT_THAT(rc, SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_CREATE, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, SymlinkGeneratesCreateEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const TempPath link1(NewTempAbsPathInDir(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- ASSERT_THAT(symlink(file1.path().c_str(), link1.path().c_str()),
- SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
-
- ASSERT_THAT(events, Are({Event(IN_CREATE, root_wd, Basename(link1.path()))}));
-}
-
-TEST(Inotify, LinkGeneratesAttribAndCreateEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const TempPath link1(root.path() + "/link1");
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- const int rc = link(file1.path().c_str(), link1.path().c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, file1_wd),
- Event(IN_CREATE, root_wd, Basename(link1.path()))}));
-}
-
-TEST(Inotify, UtimesGeneratesAttribEvent) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDWR));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
-
- struct timeval times[2] = {{1, 0}, {2, 0}};
- EXPECT_THAT(futimes(file1_fd.get(), times), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, wd, Basename(file1.path()))}));
-}
-
-TEST(Inotify, HardlinksReuseSameWatch) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- TempPath link1(root.path() + "/link1");
- const int rc = link(file1.path().c_str(), link1.path().c_str());
- // link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int root_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int file1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- const int link1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), link1.path(), IN_ALL_EVENTS));
-
- // The watch descriptors for watches on different links to the same file
- // should be identical.
- EXPECT_NE(root_wd, file1_wd);
- EXPECT_EQ(file1_wd, link1_wd);
-
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
-
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events,
- AreUnordered({Event(IN_OPEN, root_wd, Basename(file1.path())),
- Event(IN_OPEN, file1_wd)}));
-
- // For the next step, we want to ensure all fds to the file are closed. Do
- // that now and drain the resulting events.
- file1_fd.reset();
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events,
- Are({Event(IN_CLOSE_WRITE, root_wd, Basename(file1.path())),
- Event(IN_CLOSE_WRITE, file1_wd)}));
-
- // Try removing the link and let's see what events show up. Note that after
- // this, we still have a link to the file so the watch shouldn't be
- // automatically removed.
- const std::string link1_path = link1.reset();
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ATTRIB, link1_wd),
- Event(IN_DELETE, root_wd, Basename(link1_path))}));
-
- // Now remove the other link. Since this is the last link to the file, the
- // watch should be automatically removed.
- const std::string file1_path = file1.reset();
-
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- AreUnordered({Event(IN_ATTRIB, file1_wd), Event(IN_DELETE_SELF, file1_wd),
- Event(IN_IGNORED, file1_wd),
- Event(IN_DELETE, root_wd, Basename(file1_path))}));
-}
-
-// Calling mkdir within "parent/child" should generate an event for child, but
-// not parent.
-TEST(Inotify, MkdirGeneratesCreateEventWithDirFlag) {
- const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath child =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), parent.path(), IN_ALL_EVENTS));
- const int child_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), child.path(), IN_ALL_EVENTS));
-
- const TempPath dir1(NewTempAbsPathInDir(child.path()));
- ASSERT_THAT(mkdir(dir1.path().c_str(), 0777), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(
- events,
- Are({Event(IN_CREATE | IN_ISDIR, child_wd, Basename(dir1.path()))}));
-}
-
-TEST(Inotify, MultipleInotifyInstancesAndWatchesAllGetEvents) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- constexpr int kNumFds = 30;
- std::vector<FileDescriptor> inotify_fds;
-
- for (int i = 0; i < kNumFds; ++i) {
- const DisableSave ds; // Too expensive.
- inotify_fds.emplace_back(
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK)));
- const FileDescriptor& fd = inotify_fds[inotify_fds.size() - 1]; // Back.
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- }
-
- const std::string data = "some content";
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- for (const FileDescriptor& fd : inotify_fds) {
- const DisableSave ds; // Too expensive.
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- if (events.size() >= 2) {
- EXPECT_EQ(events[0].mask, IN_MODIFY);
- EXPECT_EQ(events[0].wd, 1);
- EXPECT_EQ(events[0].name, Basename(file1.path()));
- EXPECT_EQ(events[1].mask, IN_MODIFY);
- EXPECT_EQ(events[1].wd, 2);
- EXPECT_EQ(events[1].name, "");
- }
- }
-}
-
-TEST(Inotify, EventsGoUpAtMostOneLevel) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath dir1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
- TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), root.path(), IN_ALL_EVENTS));
- const int dir1_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir1.path(), IN_ALL_EVENTS));
-
- const std::string file1_path = file1.reset();
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_DELETE, dir1_wd, Basename(file1_path))}));
-}
-
-TEST(Inotify, DuplicateWatchReturnsSameWatchDescriptor) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd1 = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
- const int wd2 = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- EXPECT_EQ(wd1, wd2);
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- // The watch shouldn't be duplicated, we only expect one event.
- ASSERT_THAT(events, Are({Event(IN_OPEN, wd1)}));
-}
-
-TEST(Inotify, UnmatchedEventsAreDiscarded) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(fd.get(), file1.path(), IN_ACCESS));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- // We only asked for access events, the open event should be discarded.
- ASSERT_THAT(events, Are({}));
-}
-
-TEST(Inotify, AddWatchWithInvalidEventMaskFails) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- EXPECT_THAT(inotify_add_watch(fd.get(), root.path().c_str(), 0),
- SyscallFailsWithErrno(EINVAL));
-}
-
-TEST(Inotify, AddWatchOnInvalidPathFails) {
- const TempPath nonexistent(NewTempAbsPath());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- // Non-existent path.
- EXPECT_THAT(
- inotify_add_watch(fd.get(), nonexistent.path().c_str(), IN_CREATE),
- SyscallFailsWithErrno(ENOENT));
-
- // Garbage path pointer.
- EXPECT_THAT(inotify_add_watch(fd.get(), nullptr, IN_CREATE),
- SyscallFailsWithErrno(EFAULT));
-}
-
-TEST(Inotify, InOnlyDirFlagRespected) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- EXPECT_THAT(
- inotify_add_watch(fd.get(), root.path().c_str(), IN_ACCESS | IN_ONLYDIR),
- SyscallSucceeds());
-
- EXPECT_THAT(
- inotify_add_watch(fd.get(), file1.path().c_str(), IN_ACCESS | IN_ONLYDIR),
- SyscallFailsWithErrno(ENOTDIR));
-}
-
-TEST(Inotify, MaskAddMergesWithExistingEventMask) {
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file1 =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(root.path()));
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_WRONLY));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_OPEN | IN_CLOSE_WRITE));
-
- const std::string data = "some content";
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- // We shouldn't get any events, since IN_MODIFY wasn't in the event mask.
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({}));
-
- // Add IN_MODIFY to event mask.
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_MODIFY | IN_MASK_ADD));
-
- EXPECT_THAT(write(file1_fd.get(), data.c_str(), data.length()),
- SyscallSucceeds());
-
- // This time we should get the modify event.
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_MODIFY, wd)}));
-
- // Now close the fd. If the modify event was added to the event mask rather
- // than replacing the event mask we won't get the close event.
- file1_fd.reset();
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events, Are({Event(IN_CLOSE_WRITE, wd)}));
-}
-
-// Test that control events bits are not considered when checking event mask.
-TEST(Inotify, ControlEvents) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), dir.path(), IN_ACCESS));
-
- // Check that events in the mask are dispatched and that control bits are
- // part of the event mask.
- std::vector<std::string> files =
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(dir.path(), false));
- ASSERT_EQ(files.size(), 2);
-
- const std::vector<Event> events1 =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events1, Are({Event(IN_ACCESS | IN_ISDIR, wd)}));
-
- // Check that events not in the mask are discarded.
- const FileDescriptor dir_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dir.path(), O_RDONLY | O_DIRECTORY));
-
- const std::vector<Event> events2 =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- ASSERT_THAT(events2, Are({}));
-}
-
-// Regression test to ensure epoll and directory access doesn't deadlock.
-TEST(Inotify, EpollNoDeadlock) {
- const DisableSave ds; // Too many syscalls.
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- // Create lots of directories and watch all of them.
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- std::vector<TempPath> children;
- for (size_t i = 0; i < 1000; ++i) {
- auto child = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(root.path()));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), child.path(), IN_ACCESS));
- children.emplace_back(std::move(child));
- }
-
- // Run epoll_wait constantly in a separate thread.
- std::atomic<bool> done(false);
- ScopedThread th([&fd, &done] {
- for (auto start = absl::Now(); absl::Now() - start < absl::Seconds(5);) {
- FileDescriptor epoll_fd = ASSERT_NO_ERRNO_AND_VALUE(NewEpollFD());
- ASSERT_NO_ERRNO(RegisterEpollFD(epoll_fd.get(), fd.get(),
- EPOLLIN | EPOLLOUT | EPOLLET, 0));
- struct epoll_event result[1];
- EXPECT_THAT(RetryEINTR(epoll_wait)(epoll_fd.get(), result, 1, -1),
- SyscallSucceedsWithValue(1));
-
- sched_yield();
- }
- done = true;
- });
-
- // While epoll thread is running, constantly access all directories to
- // generate inotify events.
- while (!done) {
- std::vector<std::string> files =
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(root.path(), false));
- ASSERT_EQ(files.size(), 1002);
- for (const auto& child : files) {
- if (child == "." || child == "..") {
- continue;
- }
- ASSERT_NO_ERRNO_AND_VALUE(ListDir(JoinPath(root.path(), child), false));
- }
- sched_yield();
- }
-}
-
-TEST(Inotify, SpliceEvent) {
- // TODO(gvisor.dev/issue/138): Implement splice in VFS2.
- SKIP_IF(IsRunningOnGvisor() && !IsRunningWithVFS1());
- int pipes[2];
- ASSERT_THAT(pipe2(pipes, O_NONBLOCK), SyscallSucceeds());
-
- const TempPath root = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const TempPath file1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileWith(
- root.path(), "some content", TempPath::kDefaultFileMode));
-
- const FileDescriptor file1_fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file1.path(), O_RDONLY));
- const int watcher = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), file1.path(), IN_ALL_EVENTS));
-
- char buf;
- EXPECT_THAT(read(file1_fd.get(), &buf, 1), SyscallSucceeds());
-
- EXPECT_THAT(splice(fd.get(), nullptr, pipes[1], nullptr,
- sizeof(struct inotify_event) + 1, SPLICE_F_NONBLOCK),
- SyscallSucceedsWithValue(sizeof(struct inotify_event)));
-
- const FileDescriptor read_fd(pipes[0]);
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(read_fd.get()));
- ASSERT_THAT(events, Are({Event(IN_ACCESS, watcher)}));
-}
-
-// Watches on a parent should not be triggered by actions on a hard link to one
-// of its children that has a different parent.
-TEST(Inotify, LinkOnOtherParent) {
- const TempPath dir1 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath dir2 = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir1.path()));
- std::string link_path = NewTempAbsPathInDir(dir2.path());
-
- const int rc = link(file.path().c_str(), link_path.c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
-
- const FileDescriptor inotify_fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(inotify_fd.get(), dir1.path(), IN_ALL_EVENTS));
-
- // Perform various actions on the link outside of dir1, which should trigger
- // no inotify events.
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(link_path.c_str(), O_RDWR));
- int val = 0;
- ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
- ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
- ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds());
- ASSERT_THAT(unlink(link_path.c_str()), SyscallSucceeds());
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- EXPECT_THAT(events, Are({}));
-}
-
-TEST(Inotify, Exec) {
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath bin = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateSymlinkTo(dir.path(), "/bin/true"));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(fd.get(), bin.path(), IN_ALL_EVENTS));
-
- // Perform exec.
- ScopedThread t([&bin]() {
- ASSERT_THAT(execl(bin.path().c_str(), bin.path().c_str(), (char*)nullptr),
- SyscallSucceeds());
- });
- t.Join();
-
- std::vector<Event> events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(fd.get()));
- EXPECT_THAT(events, Are({Event(IN_OPEN, wd), Event(IN_ACCESS, wd)}));
-}
-
-// Watches without IN_EXCL_UNLINK, should continue to emit events for file
-// descriptors after their corresponding files have been unlinked.
-//
-// We need to disable S/R because there are filesystems where we cannot re-open
-// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
-TEST(Inotify, IncludeUnlinkedFile_NoRandomSave) {
- const DisableSave ds;
-
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file = ASSERT_NO_ERRNO_AND_VALUE(
- TempPath::CreateFileWith(dir.path(), "123", TempPath::kDefaultFileMode));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- const FileDescriptor inotify_fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int dir_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(inotify_fd.get(), dir.path(), IN_ALL_EVENTS));
- const int file_wd = ASSERT_NO_ERRNO_AND_VALUE(
- InotifyAddWatch(inotify_fd.get(), file.path(), IN_ALL_EVENTS));
-
- ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
- int val = 0;
- ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
- ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- EXPECT_THAT(events, Are({
- Event(IN_ATTRIB, file_wd),
- Event(IN_DELETE, dir_wd, Basename(file.path())),
- Event(IN_ACCESS, dir_wd, Basename(file.path())),
- Event(IN_ACCESS, file_wd),
- Event(IN_MODIFY, dir_wd, Basename(file.path())),
- Event(IN_MODIFY, file_wd),
- }));
-}
-
-// Watches created with IN_EXCL_UNLINK will stop emitting events on fds for
-// children that have already been unlinked.
-//
-// We need to disable S/R because there are filesystems where we cannot re-open
-// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
-TEST(Inotify, ExcludeUnlink_NoRandomSave) {
- const DisableSave ds;
- // TODO(gvisor.dev/issue/1624): This test fails on VFS1.
- SKIP_IF(IsRunningWithVFS1());
-
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path(), O_RDWR));
-
- const FileDescriptor inotify_fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
- inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
-
- // Unlink the child, which should cause further operations on the open file
- // descriptor to be ignored.
- ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
- int val = 0;
- ASSERT_THAT(write(fd.get(), &val, sizeof(val)), SyscallSucceeds());
- ASSERT_THAT(read(fd.get(), &val, sizeof(val)), SyscallSucceeds());
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- EXPECT_THAT(events, Are({Event(IN_DELETE, wd, Basename(file.path()))}));
-}
-
-// We need to disable S/R because there are filesystems where we cannot re-open
-// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
-TEST(Inotify, ExcludeUnlinkDirectory_NoRandomSave) {
- const DisableSave ds;
-
- const TempPath parent = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- TempPath dir =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDirIn(parent.path()));
- std::string dirPath = dir.path();
- const FileDescriptor inotify_fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(dirPath.c_str(), O_RDONLY | O_DIRECTORY));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
- inotify_fd.get(), parent.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
-
- // Unlink the dir, and then close the open fd.
- ASSERT_THAT(rmdir(dirPath.c_str()), SyscallSucceeds());
- dir.reset();
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- // No close event should appear.
- ASSERT_THAT(events,
- Are({Event(IN_DELETE | IN_ISDIR, wd, Basename(dirPath))}));
-}
-
-// If "dir/child" and "dir/child2" are links to the same file, and "dir/child"
-// is unlinked, a watch on "dir" with IN_EXCL_UNLINK will exclude future events
-// for fds on "dir/child" but not "dir/child2".
-//
-// We need to disable S/R because there are filesystems where we cannot re-open
-// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
-TEST(Inotify, ExcludeUnlinkMultipleChildren_NoRandomSave) {
- const DisableSave ds;
- // TODO(gvisor.dev/issue/1624): This test fails on VFS1.
- SKIP_IF(IsRunningWithVFS1());
-
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
- std::string path1 = file.path();
- std::string path2 = NewTempAbsPathInDir(dir.path());
-
- const int rc = link(path1.c_str(), path2.c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(IsRunningOnGvisor() && rc != 0 &&
- (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
- const FileDescriptor fd1 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path1.c_str(), O_RDWR));
- const FileDescriptor fd2 =
- ASSERT_NO_ERRNO_AND_VALUE(Open(path2.c_str(), O_RDWR));
-
- const FileDescriptor inotify_fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
- inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
-
- // After unlinking path1, only events on the fd for path2 should be generated.
- ASSERT_THAT(unlink(path1.c_str()), SyscallSucceeds());
- ASSERT_THAT(write(fd1.get(), "x", 1), SyscallSucceeds());
- ASSERT_THAT(write(fd2.get(), "x", 1), SyscallSucceeds());
-
- const std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- EXPECT_THAT(events, Are({
- Event(IN_DELETE, wd, Basename(path1)),
- Event(IN_MODIFY, wd, Basename(path2)),
- }));
-}
-
-// On native Linux, actions of data type FSNOTIFY_EVENT_INODE are not affected
-// by IN_EXCL_UNLINK (see
-// fs/notify/inotify/inotify_fsnotify.c:inotify_handle_event). Inode-level
-// events include changes to metadata and extended attributes.
-//
-// We need to disable S/R because there are filesystems where we cannot re-open
-// fds to an unlinked file across S/R, e.g. gofer-backed filesytems.
-TEST(Inotify, ExcludeUnlinkInodeEvents_NoRandomSave) {
- const DisableSave ds;
-
- const TempPath dir = ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateDir());
- const TempPath file =
- ASSERT_NO_ERRNO_AND_VALUE(TempPath::CreateFileIn(dir.path()));
-
- const FileDescriptor fd =
- ASSERT_NO_ERRNO_AND_VALUE(Open(file.path().c_str(), O_RDWR));
- const FileDescriptor inotify_fd =
- ASSERT_NO_ERRNO_AND_VALUE(InotifyInit1(IN_NONBLOCK));
- const int wd = ASSERT_NO_ERRNO_AND_VALUE(InotifyAddWatch(
- inotify_fd.get(), dir.path(), IN_ALL_EVENTS | IN_EXCL_UNLINK));
-
- // NOTE(b/157163751): Create another link before unlinking. This is needed for
- // the gofer filesystem in gVisor, where open fds will not work once the link
- // count hits zero. In VFS2, we end up skipping the gofer test anyway, because
- // hard links are not supported for gofer fs.
- if (IsRunningOnGvisor()) {
- std::string link_path = NewTempAbsPath();
- const int rc = link(file.path().c_str(), link_path.c_str());
- // NOTE(b/34861058): link(2) is only supported on tmpfs in the sandbox.
- SKIP_IF(rc != 0 && (errno == EPERM || errno == ENOENT));
- ASSERT_THAT(rc, SyscallSucceeds());
- }
-
- // Even after unlinking, inode-level operations will trigger events regardless
- // of IN_EXCL_UNLINK.
- ASSERT_THAT(unlink(file.path().c_str()), SyscallSucceeds());
-
- // Perform various actions on fd.
- ASSERT_THAT(ftruncate(fd.get(), 12345), SyscallSucceeds());
- std::vector<Event> events =
- ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- EXPECT_THAT(events, Are({
- Event(IN_DELETE, wd, Basename(file.path())),
- Event(IN_MODIFY, wd, Basename(file.path())),
- }));
-
- struct timeval times[2] = {{1, 0}, {2, 0}};
- ASSERT_THAT(futimes(fd.get(), times), SyscallSucceeds());
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd, Basename(file.path()))}));
-
- // S/R is disabled on this entire test due to behavior with unlink; it must
- // also be disabled after this point because of fchmod.
- ASSERT_THAT(fchmod(fd.get(), 0777), SyscallSucceeds());
- events = ASSERT_NO_ERRNO_AND_VALUE(DrainEvents(inotify_fd.get()));
- EXPECT_THAT(events, Are({Event(IN_ATTRIB, wd, Basename(file.path()))}));
-}
-
-} // namespace
-} // namespace testing
-} // namespace gvisor