diff options
author | DexesTTP <dexes.ttp@gmail.com> | 2021-02-08 20:40:58 +0100 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2021-02-11 13:13:32 +0100 |
commit | 2acbb811b1751233833f9ea89f7e76774262a706 (patch) | |
tree | 6bd8f5fdcd73bb01258cd2e4feceb2300faea2fb | |
parent | 0304ab3e678151c2f73fd91050a99e8875410ac0 (diff) | |
download | serenity-2acbb811b1751233833f9ea89f7e76774262a706.zip |
LibCore: Added FileWatcher, a binding for the watch_file syscall
This wrapper abstracts the watch_file setup and file handling, and
allows using the watch_file events as part of the event loop via the
Core::Notifier class.
Also renames the existing DirectoryWatcher class to BlockingFileWatcher,
and adds support for the Modified mode in this class.
-rw-r--r-- | AK/Debug.h.in | 4 | ||||
-rw-r--r-- | Meta/CMake/all_the_debug_macros.cmake | 1 | ||||
-rw-r--r-- | Userland/Libraries/LibCore/CMakeLists.txt | 2 | ||||
-rw-r--r-- | Userland/Libraries/LibCore/DirectoryWatcher.cpp | 98 | ||||
-rw-r--r-- | Userland/Libraries/LibCore/FileWatcher.cpp | 171 | ||||
-rw-r--r-- | Userland/Libraries/LibCore/FileWatcher.h (renamed from Userland/Libraries/LibCore/DirectoryWatcher.h) | 53 | ||||
-rw-r--r-- | Userland/Services/CrashDaemon/main.cpp | 6 |
7 files changed, 216 insertions, 119 deletions
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. <itamar8910@gmail.com> - * 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 <AK/LexicalPath.h> -#include <AK/Optional.h> -#include <LibCore/DirIterator.h> -#include <fcntl.h> -#include <sys/stat.h> - -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::Event> 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. <itamar8910@gmail.com> + * 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 <AK/Debug.h> +#include <AK/Function.h> +#include <AK/LexicalPath.h> +#include <AK/Noncopyable.h> +#include <AK/NonnullRefPtr.h> +#include <AK/RefCounted.h> +#include <AK/Result.h> +#include <AK/String.h> +#include <Kernel/API/InodeWatcherEvent.h> +#include <LibCore/DirIterator.h> +#include <LibCore/Notifier.h> +#include <fcntl.h> +#include <string.h> +#include <sys/stat.h> + +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<FileWatcherEvent> 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<NonnullRefPtr<FileWatcher>, 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> 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 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. <itamar8910@gmail.com> + * 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 <AK/Function.h> #include <AK/Noncopyable.h> +#include <AK/NonnullRefPtr.h> +#include <AK/RefCounted.h> +#include <AK/Result.h> #include <AK/String.h> -#include <Kernel/API/InodeWatcherEvent.h> +#include <LibCore/Notifier.h> 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(); - - struct Event { - enum class Type { - ChildAdded, - ChildRemoved, - }; - Type type; - String child_path; - }; + explicit BlockingFileWatcher(const String& path); + ~BlockingFileWatcher(); - Optional<Event> wait_for_event(); + Optional<FileWatcherEvent> wait_for_event(); private: - String get_child_with_inode_index(unsigned) const; - String m_path; int m_watcher_fd { -1 }; }; +class FileWatcher : public RefCounted<FileWatcher> { + AK_MAKE_NONCOPYABLE(FileWatcher); + +public: + static Result<NonnullRefPtr<FileWatcher>, String> watch(const String& path); + ~FileWatcher(); + + Function<void(FileWatcherEvent)> on_change; + +private: + FileWatcher(NonnullRefPtr<Notifier>, const String& path); + + NonnullRefPtr<Notifier> 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 <AK/LexicalPath.h> -#include <LibCore/DirectoryWatcher.h> +#include <LibCore/FileWatcher.h> #include <LibCoreDump/Backtrace.h> #include <LibCoreDump/Reader.h> #include <serenity.h> @@ -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); |