diff --git a/AK/Debug.h.in b/AK/Debug.h.in index 6a23a9935c..4b6ca779de 100644 --- a/AK/Debug.h.in +++ b/AK/Debug.h.in @@ -138,6 +138,10 @@ #cmakedefine01 FILE_CONTENT_DEBUG #endif +#ifndef FILE_WATCHER_DEBUG +#cmakedefine01 FILE_WATCHER_DEBUG +#endif + #ifndef FILL_PATH_DEBUG #cmakedefine01 FILL_PATH_DEBUG #endif diff --git a/Meta/CMake/all_the_debug_macros.cmake b/Meta/CMake/all_the_debug_macros.cmake index 097ae7a555..d2fa65a8e2 100644 --- a/Meta/CMake/all_the_debug_macros.cmake +++ b/Meta/CMake/all_the_debug_macros.cmake @@ -165,6 +165,7 @@ set(CPP_DEBUG ON) set(DEBUG_SPAM ON) set(DEBUG_CPP_LANGUAGE_SERVER ON) set(DEBUG_AUTOCOMPLETE ON) +set(FILE_WATCHER_DEBUG ON) # False positive: DEBUG is a flag but it works differently. # set(DEBUG ON) diff --git a/Userland/Libraries/LibCore/CMakeLists.txt b/Userland/Libraries/LibCore/CMakeLists.txt index ee6c046bb9..2705753b34 100644 --- a/Userland/Libraries/LibCore/CMakeLists.txt +++ b/Userland/Libraries/LibCore/CMakeLists.txt @@ -5,11 +5,11 @@ set(SOURCES ConfigFile.cpp Command.cpp DateTime.cpp - DirectoryWatcher.cpp DirIterator.cpp ElapsedTimer.cpp Event.cpp EventLoop.cpp + FileWatcher.cpp File.cpp GetPassword.cpp Gzip.cpp diff --git a/Userland/Libraries/LibCore/DirectoryWatcher.cpp b/Userland/Libraries/LibCore/DirectoryWatcher.cpp deleted file mode 100644 index a363c2461d..0000000000 --- a/Userland/Libraries/LibCore/DirectoryWatcher.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2020, Itamar S. - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this - * list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, - * this list of conditions and the following disclaimer in the documentation - * and/or other materials provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE - * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL - * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR - * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER - * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "DirectoryWatcher.h" -#include -#include -#include -#include -#include - -namespace Core { - -// Only supported in serenity mode because we use `watch_file` -#ifdef __serenity__ - -DirectoryWatcher::DirectoryWatcher(const String& path) - : m_path(path) -{ - m_watcher_fd = watch_file(path.characters(), path.length()); - ASSERT(m_watcher_fd != -1); -} - -DirectoryWatcher::~DirectoryWatcher() -{ - close(m_watcher_fd); -} - -Optional DirectoryWatcher::wait_for_event() -{ - InodeWatcherEvent event {}; - int rc = read(m_watcher_fd, &event, sizeof(event)); - if (rc <= 0) - return {}; - - Event result; - if (event.type == InodeWatcherEvent::Type::ChildAdded) - result.type = Event::Type::ChildAdded; - else if (event.type == InodeWatcherEvent::Type::ChildRemoved) - result.type = Event::Type::ChildRemoved; - else - return {}; - - auto child_path = get_child_with_inode_index(event.inode_index); - if (!LexicalPath(child_path).is_valid()) - return {}; - - result.child_path = child_path; - return result; -} - -String DirectoryWatcher::get_child_with_inode_index(unsigned child_inode_index) const -{ - DirIterator iterator(m_path, Core::DirIterator::SkipDots); - if (iterator.has_error()) { - return {}; - } - - while (iterator.has_next()) { - auto child_full_path = String::formatted("{}/{}", m_path, iterator.next_path()); - struct stat st; - - if (lstat(child_full_path.characters(), &st)) { - return {}; - } - - if (st.st_ino == child_inode_index) { - return child_full_path; - } - } - return {}; -} - -#endif - -} diff --git a/Userland/Libraries/LibCore/FileWatcher.cpp b/Userland/Libraries/LibCore/FileWatcher.cpp new file mode 100644 index 0000000000..19bb73eb8a --- /dev/null +++ b/Userland/Libraries/LibCore/FileWatcher.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2020, Itamar S. + * Copyright (c) 2021, the SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "FileWatcher.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Core { + +// Only supported in serenity mode because we use `watch_file` +#ifdef __serenity__ + +static String get_child_path_from_inode_index(const String& path, unsigned child_inode_index) +{ + DirIterator iterator(path, Core::DirIterator::SkipDots); + if (iterator.has_error()) { + return {}; + } + + while (iterator.has_next()) { + auto child_full_path = String::formatted("{}/{}", path, iterator.next_path()); + struct stat st; + + if (lstat(child_full_path.characters(), &st)) { + return {}; + } + + if (st.st_ino == child_inode_index) { + return child_full_path; + } + } + return {}; +} + +BlockingFileWatcher::BlockingFileWatcher(const String& path) + : m_path(path) +{ + m_watcher_fd = watch_file(path.characters(), path.length()); + ASSERT(m_watcher_fd != -1); +} + +BlockingFileWatcher::~BlockingFileWatcher() +{ + close(m_watcher_fd); +} + +Optional BlockingFileWatcher::wait_for_event() +{ + InodeWatcherEvent event {}; + int rc = read(m_watcher_fd, &event, sizeof(event)); + if (rc <= 0) + return {}; + + FileWatcherEvent result; + if (event.type == InodeWatcherEvent::Type::ChildAdded) + result.type = FileWatcherEvent::Type::ChildAdded; + else if (event.type == InodeWatcherEvent::Type::ChildRemoved) + result.type = FileWatcherEvent::Type::ChildRemoved; + else if (event.type == InodeWatcherEvent::Type::Modified) + result.type = FileWatcherEvent::Type::Modified; + else + return {}; + + if (result.type == FileWatcherEvent::Type::ChildAdded || result.type == FileWatcherEvent::Type::ChildRemoved) { + auto child_path = get_child_path_from_inode_index(m_path, event.inode_index); + if (!LexicalPath(child_path).is_valid()) + return {}; + + result.child_path = child_path; + } + + return result; +} + +Result, String> FileWatcher::watch(const String& path) +{ + auto watch_fd = watch_file(path.characters(), path.length()); + if (watch_fd < 0) { + return String::formatted("Could not watch file '{}' : {}", path.characters(), strerror(errno)); + } + + fcntl(watch_fd, F_SETFD, FD_CLOEXEC); + if (watch_fd < 0) { + return String::formatted("Could not watch file '{}' : {}", path.characters(), strerror(errno)); + } + + dbgln_if(FILE_WATCHER_DEBUG, "Started watcher for file '{}'", path.characters()); + auto notifier = Notifier::construct(watch_fd, Notifier::Event::Read); + return adopt(*new FileWatcher(move(notifier), move(path))); +} + +FileWatcher::FileWatcher(NonnullRefPtr notifier, const String& path) + : m_notifier(move(notifier)) + , m_path(path) +{ + m_notifier->on_ready_to_read = [this] { + InodeWatcherEvent event {}; + int rc = read(m_notifier->fd(), &event, sizeof(event)); + if (rc <= 0) + return; + + FileWatcherEvent result; + if (event.type == InodeWatcherEvent::Type::ChildAdded) { + result.type = FileWatcherEvent::Type::ChildAdded; + } else if (event.type == InodeWatcherEvent::Type::ChildRemoved) { + result.type = FileWatcherEvent::Type::ChildRemoved; + } else if (event.type == InodeWatcherEvent::Type::Modified) { + result.type = FileWatcherEvent::Type::Modified; + } else { + warnln("Unknown event type {} returned by the watch_file descriptor for {}", (unsigned)event.type, m_path.characters()); + return; + } + + if (result.type == FileWatcherEvent::Type::ChildAdded || result.type == FileWatcherEvent::Type::ChildRemoved) { + auto child_path = get_child_path_from_inode_index(m_path, event.inode_index); + if (!LexicalPath(child_path).is_valid()) + return; + + result.child_path = child_path; + } + + on_change(result); + }; +} + +FileWatcher::~FileWatcher() +{ + m_notifier->on_ready_to_read = nullptr; + close(m_notifier->fd()); + dbgln_if(FILE_WATCHER_DEBUG, "Ended watcher for file '{}'", m_path.characters()); +} + +#endif + +} diff --git a/Userland/Libraries/LibCore/DirectoryWatcher.h b/Userland/Libraries/LibCore/FileWatcher.h similarity index 63% rename from Userland/Libraries/LibCore/DirectoryWatcher.h rename to Userland/Libraries/LibCore/FileWatcher.h index 63be2c1aa7..8a7df3acc9 100644 --- a/Userland/Libraries/LibCore/DirectoryWatcher.h +++ b/Userland/Libraries/LibCore/FileWatcher.h @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, Itamar S. + * Copyright (c) 2021, the SerenityOS developers. * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -28,34 +29,52 @@ #include #include +#include +#include +#include #include -#include +#include namespace Core { -class DirectoryWatcher { - AK_MAKE_NONCOPYABLE(DirectoryWatcher); +struct FileWatcherEvent { + enum class Type { + Modified, + ChildAdded, + ChildRemoved, + }; + Type type; + String child_path; +}; + +class BlockingFileWatcher { + AK_MAKE_NONCOPYABLE(BlockingFileWatcher); public: - explicit DirectoryWatcher(const String& path); - ~DirectoryWatcher(); + explicit BlockingFileWatcher(const String& path); + ~BlockingFileWatcher(); - struct Event { - enum class Type { - ChildAdded, - ChildRemoved, - }; - Type type; - String child_path; - }; - - Optional wait_for_event(); + Optional wait_for_event(); private: - String get_child_with_inode_index(unsigned) const; - String m_path; int m_watcher_fd { -1 }; }; +class FileWatcher : public RefCounted { + AK_MAKE_NONCOPYABLE(FileWatcher); + +public: + static Result, String> watch(const String& path); + ~FileWatcher(); + + Function on_change; + +private: + FileWatcher(NonnullRefPtr, const String& path); + + NonnullRefPtr m_notifier; + String m_path; +}; + } diff --git a/Userland/Services/CrashDaemon/main.cpp b/Userland/Services/CrashDaemon/main.cpp index 8dcd318566..b651675864 100644 --- a/Userland/Services/CrashDaemon/main.cpp +++ b/Userland/Services/CrashDaemon/main.cpp @@ -25,7 +25,7 @@ */ #include -#include +#include #include #include #include @@ -89,11 +89,11 @@ int main() return 1; } - Core::DirectoryWatcher watcher { "/tmp/coredump" }; + Core::BlockingFileWatcher watcher { "/tmp/coredump" }; while (true) { auto event = watcher.wait_for_event(); ASSERT(event.has_value()); - if (event.value().type != Core::DirectoryWatcher::Event::Type::ChildAdded) + if (event.value().type != Core::FileWatcherEvent::Type::ChildAdded) continue; auto coredump_path = event.value().child_path; dbgln("New coredump file: {}", coredump_path);