FileWatcher.cpp 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  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. result.event_path = LexicalPath::join(path, child_name).string();
  70. } else {
  71. result.event_path = path;
  72. }
  73. dbgln_if(FILE_WATCHER_DEBUG, "get_event_from_fd: got event from wd {} on '{}' type {}", fd, result.event_path, result.type);
  74. return result;
  75. }
  76. static String canonicalize_path(String path)
  77. {
  78. if (!path.is_empty() && path[0] == '/')
  79. return LexicalPath::canonicalized_path(move(path));
  80. char* cwd = getcwd(nullptr, 0);
  81. VERIFY(cwd);
  82. return LexicalPath::join(cwd, move(path)).string();
  83. }
  84. Result<bool, String> FileWatcherBase::add_watch(String path, FileWatcherEvent::Type event_mask)
  85. {
  86. String canonical_path = canonicalize_path(move(path));
  87. if (m_path_to_wd.find(canonical_path) != m_path_to_wd.end()) {
  88. dbgln_if(FILE_WATCHER_DEBUG, "add_watch: path '{}' is already being watched", canonical_path);
  89. return false;
  90. }
  91. auto kernel_mask = InodeWatcherEvent::Type::Invalid;
  92. if (has_flag(event_mask, FileWatcherEvent::Type::ChildCreated))
  93. kernel_mask |= InodeWatcherEvent::Type::ChildCreated;
  94. if (has_flag(event_mask, FileWatcherEvent::Type::ChildDeleted))
  95. kernel_mask |= InodeWatcherEvent::Type::ChildDeleted;
  96. if (has_flag(event_mask, FileWatcherEvent::Type::Deleted))
  97. kernel_mask |= InodeWatcherEvent::Type::Deleted;
  98. if (has_flag(event_mask, FileWatcherEvent::Type::ContentModified))
  99. kernel_mask |= InodeWatcherEvent::Type::ContentModified;
  100. if (has_flag(event_mask, FileWatcherEvent::Type::MetadataModified))
  101. kernel_mask |= InodeWatcherEvent::Type::MetadataModified;
  102. int wd = inode_watcher_add_watch(m_watcher_fd, canonical_path.characters(), canonical_path.length(), static_cast<unsigned>(kernel_mask));
  103. if (wd < 0)
  104. return String::formatted("Could not watch file '{}' : {}", canonical_path, strerror(errno));
  105. m_path_to_wd.set(canonical_path, wd);
  106. m_wd_to_path.set(wd, canonical_path);
  107. dbgln_if(FILE_WATCHER_DEBUG, "add_watch: watching path '{}' on InodeWatcher {} wd {}", canonical_path, m_watcher_fd, wd);
  108. return true;
  109. }
  110. Result<bool, String> FileWatcherBase::remove_watch(String path)
  111. {
  112. String canonical_path = canonicalize_path(move(path));
  113. auto it = m_path_to_wd.find(canonical_path);
  114. if (it == m_path_to_wd.end()) {
  115. dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: path '{}' is not being watched", canonical_path);
  116. return false;
  117. }
  118. int rc = inode_watcher_remove_watch(m_watcher_fd, it->value);
  119. if (rc < 0) {
  120. return String::formatted("Could not stop watching file '{}' : {}", path, strerror(errno));
  121. }
  122. m_path_to_wd.remove(it);
  123. m_wd_to_path.remove(it->value);
  124. dbgln_if(FILE_WATCHER_DEBUG, "remove_watch: stopped watching path '{}' on InodeWatcher {}", canonical_path, m_watcher_fd);
  125. return true;
  126. }
  127. BlockingFileWatcher::BlockingFileWatcher(InodeWatcherFlags flags)
  128. : FileWatcherBase(create_inode_watcher(static_cast<unsigned>(flags)))
  129. {
  130. VERIFY(m_watcher_fd != -1);
  131. dbgln_if(FILE_WATCHER_DEBUG, "BlockingFileWatcher created with InodeWatcher {}", m_watcher_fd);
  132. }
  133. BlockingFileWatcher::~BlockingFileWatcher()
  134. {
  135. close(m_watcher_fd);
  136. }
  137. Optional<FileWatcherEvent> BlockingFileWatcher::wait_for_event()
  138. {
  139. dbgln_if(FILE_WATCHER_DEBUG, "BlockingFileWatcher::wait_for_event()");
  140. auto maybe_event = get_event_from_fd(m_watcher_fd, m_wd_to_path);
  141. if (!maybe_event.has_value())
  142. return maybe_event;
  143. auto event = maybe_event.release_value();
  144. if (event.type == FileWatcherEvent::Type::Deleted) {
  145. auto result = remove_watch(event.event_path);
  146. if (result.is_error()) {
  147. dbgln_if(FILE_WATCHER_DEBUG, "wait_for_event: {}", result.error());
  148. }
  149. }
  150. return event;
  151. }
  152. Result<NonnullRefPtr<FileWatcher>, String> FileWatcher::create(InodeWatcherFlags flags)
  153. {
  154. auto watcher_fd = create_inode_watcher(static_cast<unsigned>(flags | InodeWatcherFlags::CloseOnExec));
  155. if (watcher_fd < 0) {
  156. return String::formatted("FileWatcher: Could not create InodeWatcher: {}", strerror(errno));
  157. }
  158. auto notifier = Notifier::construct(watcher_fd, Notifier::Event::Read);
  159. return adopt_ref(*new FileWatcher(watcher_fd, move(notifier)));
  160. }
  161. FileWatcher::FileWatcher(int watcher_fd, NonnullRefPtr<Notifier> notifier)
  162. : FileWatcherBase(watcher_fd)
  163. , m_notifier(move(notifier))
  164. {
  165. m_notifier->on_ready_to_read = [this] {
  166. auto maybe_event = get_event_from_fd(m_notifier->fd(), m_wd_to_path);
  167. if (maybe_event.has_value()) {
  168. auto event = maybe_event.value();
  169. on_change(event);
  170. if (event.type == FileWatcherEvent::Type::Deleted) {
  171. auto result = remove_watch(event.event_path);
  172. if (result.is_error()) {
  173. dbgln_if(FILE_WATCHER_DEBUG, "on_ready_to_read: {}", result.error());
  174. }
  175. }
  176. }
  177. };
  178. }
  179. FileWatcher::~FileWatcher()
  180. {
  181. m_notifier->on_ready_to_read = nullptr;
  182. close(m_notifier->fd());
  183. dbgln_if(FILE_WATCHER_DEBUG, "Stopped watcher at fd {}", m_notifier->fd());
  184. }
  185. #endif
  186. }