FileWatcher.cpp 8.2 KB

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