FileWatcherSerenity.cpp 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. /*
  2. * Copyright (c) 2020, Itamar S. <itamar8910@gmail.com>
  3. * Copyright (c) 2021, the SerenityOS developers.
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include "FileWatcher.h"
  8. #include <AK/ByteString.h>
  9. #include <AK/Debug.h>
  10. #include <AK/LexicalPath.h>
  11. #include <AK/NonnullRefPtr.h>
  12. #include <Kernel/API/InodeWatcherEvent.h>
  13. #include <Kernel/API/InodeWatcherFlags.h>
  14. #include <LibCore/Notifier.h>
  15. #include <errno.h>
  16. #include <fcntl.h>
  17. #include <string.h>
  18. #include <unistd.h>
  19. #if !defined(AK_OS_SERENITY)
  20. static_assert(false, "This file must only be used for SerenityOS");
  21. #endif
  22. namespace Core {
  23. static constexpr unsigned file_watcher_flags_to_inode_watcher_flags(FileWatcherFlags flags)
  24. {
  25. auto result = InodeWatcherFlags::None;
  26. if (has_flag(flags, FileWatcherFlags::Nonblock))
  27. result |= InodeWatcherFlags::Nonblock;
  28. if (has_flag(flags, FileWatcherFlags::CloseOnExec))
  29. result |= InodeWatcherFlags::CloseOnExec;
  30. return static_cast<unsigned>(result);
  31. }
  32. static Optional<FileWatcherEvent> get_event_from_fd(int fd, HashMap<unsigned, ByteString> const& wd_to_path)
  33. {
  34. u8 buffer[MAXIMUM_EVENT_SIZE];
  35. int rc = read(fd, &buffer, MAXIMUM_EVENT_SIZE);
  36. if (rc == 0) {
  37. return {};
  38. } else if (rc < 0) {
  39. dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: Reading from wd {} failed: {}", fd, strerror(errno));
  40. return {};
  41. }
  42. InodeWatcherEvent* event = reinterpret_cast<InodeWatcherEvent*>(buffer);
  43. FileWatcherEvent result;
  44. auto it = wd_to_path.find(event->watch_descriptor);
  45. if (it == wd_to_path.end()) {
  46. dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: Got an event for a non-existent wd {}?!", event->watch_descriptor);
  47. return {};
  48. }
  49. ByteString const& path = it->value;
  50. switch (event->type) {
  51. case InodeWatcherEvent::Type::ChildCreated:
  52. result.type = FileWatcherEvent::Type::ChildCreated;
  53. break;
  54. case InodeWatcherEvent::Type::ChildDeleted:
  55. result.type = FileWatcherEvent::Type::ChildDeleted;
  56. break;
  57. case InodeWatcherEvent::Type::Deleted:
  58. result.type = FileWatcherEvent::Type::Deleted;
  59. break;
  60. case InodeWatcherEvent::Type::ContentModified:
  61. result.type = FileWatcherEvent::Type::ContentModified;
  62. break;
  63. case InodeWatcherEvent::Type::MetadataModified:
  64. result.type = FileWatcherEvent::Type::MetadataModified;
  65. break;
  66. default:
  67. warnln("Unknown event type {} returned by the watch_file descriptor for {}", static_cast<unsigned>(event->type), path);
  68. return {};
  69. }
  70. // We trust that the kernel only sends the name when appropriate.
  71. if (event->name_length > 0) {
  72. ByteString child_name { event->name, event->name_length - 1 };
  73. result.event_path = LexicalPath::join(path, child_name).string();
  74. } else {
  75. result.event_path = path;
  76. }
  77. dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: got event from wd {} on '{}' type {}", fd, result.event_path, result.type);
  78. return result;
  79. }
  80. static ByteString canonicalize_path(ByteString path)
  81. {
  82. if (!path.is_empty() && path[0] == '/')
  83. return LexicalPath::canonicalized_path(move(path));
  84. char* cwd = getcwd(nullptr, 0);
  85. VERIFY(cwd);
  86. return LexicalPath::join({ cwd, strlen(cwd) }, move(path)).string();
  87. }
  88. ErrorOr<bool> FileWatcherBase::add_watch(ByteString path, FileWatcherEvent::Type event_mask)
  89. {
  90. ByteString canonical_path = canonicalize_path(move(path));
  91. if (m_path_to_wd.find(canonical_path) != m_path_to_wd.end()) {
  92. dbgln_if(FILE_WATCHER_DEBUG, "add_watch: path '{}' is already being watched", canonical_path);
  93. return false;
  94. }
  95. auto kernel_mask = InodeWatcherEvent::Type::Invalid;
  96. if (has_flag(event_mask, FileWatcherEvent::Type::ChildCreated))
  97. kernel_mask |= InodeWatcherEvent::Type::ChildCreated;
  98. if (has_flag(event_mask, FileWatcherEvent::Type::ChildDeleted))
  99. kernel_mask |= InodeWatcherEvent::Type::ChildDeleted;
  100. if (has_flag(event_mask, FileWatcherEvent::Type::Deleted))
  101. kernel_mask |= InodeWatcherEvent::Type::Deleted;
  102. if (has_flag(event_mask, FileWatcherEvent::Type::ContentModified))
  103. kernel_mask |= InodeWatcherEvent::Type::ContentModified;
  104. if (has_flag(event_mask, FileWatcherEvent::Type::MetadataModified))
  105. kernel_mask |= InodeWatcherEvent::Type::MetadataModified;
  106. int wd = inode_watcher_add_watch(m_watcher_fd, canonical_path.characters(), canonical_path.length(), static_cast<unsigned>(kernel_mask));
  107. if (wd < 0)
  108. return Error::from_errno(errno);
  109. m_path_to_wd.set(canonical_path, wd);
  110. m_wd_to_path.set(wd, canonical_path);
  111. dbgln_if(FILE_WATCHER_DEBUG, "add_watch: watching path '{}' on InodeWatcher {} wd {}", canonical_path, m_watcher_fd, wd);
  112. return true;
  113. }
  114. ErrorOr<bool> FileWatcherBase::remove_watch(ByteString path)
  115. {
  116. ByteString canonical_path = canonicalize_path(move(path));
  117. auto it = m_path_to_wd.find(canonical_path);
  118. if (it == m_path_to_wd.end()) {
  119. dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: path '{}' is not being watched", canonical_path);
  120. return false;
  121. }
  122. if (inode_watcher_remove_watch(m_watcher_fd, it->value) < 0)
  123. return Error::from_errno(errno);
  124. m_path_to_wd.remove(it);
  125. m_wd_to_path.remove(it->value);
  126. dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: stopped watching path '{}' on InodeWatcher {}", canonical_path, m_watcher_fd);
  127. return true;
  128. }
  129. BlockingFileWatcher::BlockingFileWatcher(FileWatcherFlags flags)
  130. : FileWatcherBase(create_inode_watcher(file_watcher_flags_to_inode_watcher_flags(flags)))
  131. {
  132. VERIFY(m_watcher_fd != -1);
  133. dbgln_if(FILE_WATCHER_DEBUG, "BlockingFileWatcher created with InodeWatcher {}", m_watcher_fd);
  134. }
  135. BlockingFileWatcher::~BlockingFileWatcher()
  136. {
  137. close(m_watcher_fd);
  138. }
  139. Optional<FileWatcherEvent> BlockingFileWatcher::wait_for_event()
  140. {
  141. dbgln_if(FILE_WATCHER_DEBUG, "BlockingFileWatcher::wait_for_event()");
  142. auto maybe_event = get_event_from_fd(m_watcher_fd, m_wd_to_path);
  143. if (!maybe_event.has_value())
  144. return maybe_event;
  145. auto event = maybe_event.release_value();
  146. if (event.type == FileWatcherEvent::Type::Deleted) {
  147. auto result = remove_watch(event.event_path);
  148. if (result.is_error()) {
  149. dbgln_if(FILE_WATCHER_DEBUG, "wait_for_event: {}", result.error());
  150. }
  151. }
  152. return event;
  153. }
  154. ErrorOr<NonnullRefPtr<FileWatcher>> FileWatcher::create(FileWatcherFlags flags)
  155. {
  156. auto watcher_fd = create_inode_watcher(file_watcher_flags_to_inode_watcher_flags(flags | FileWatcherFlags::CloseOnExec));
  157. if (watcher_fd < 0)
  158. return Error::from_errno(errno);
  159. auto notifier = Notifier::construct(watcher_fd, Notifier::Type::Read);
  160. return adopt_ref(*new FileWatcher(watcher_fd, move(notifier)));
  161. }
  162. FileWatcher::FileWatcher(int watcher_fd, NonnullRefPtr<Notifier> notifier)
  163. : FileWatcherBase(watcher_fd)
  164. , m_notifier(move(notifier))
  165. {
  166. m_notifier->on_activation = [this] {
  167. auto maybe_event = get_event_from_fd(m_notifier->fd(), m_wd_to_path);
  168. if (maybe_event.has_value()) {
  169. auto event = maybe_event.value();
  170. on_change(event);
  171. if (event.type == FileWatcherEvent::Type::Deleted) {
  172. auto result = remove_watch(event.event_path);
  173. if (result.is_error()) {
  174. dbgln_if(FILE_WATCHER_DEBUG, "on_ready_to_read: {}", result.error());
  175. }
  176. }
  177. }
  178. };
  179. }
  180. FileWatcher::~FileWatcher()
  181. {
  182. m_notifier->on_activation = nullptr;
  183. close(m_notifier->fd());
  184. dbgln_if(FILE_WATCHER_DEBUG, "Stopped watcher at fd {}", m_notifier->fd());
  185. }
  186. }