FileWatcher.cpp 8.2 KB

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