FileWatcher.cpp 7.3 KB

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