Flutter Engine
The Flutter Engine
file_system_watcher_macos.cc
Go to the documentation of this file.
1// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2// for details. All rights reserved. Use of this source code is governed by a
3// BSD-style license that can be found in the LICENSE file.
4
5#include "platform/globals.h"
6#if defined(DART_HOST_OS_MACOS)
7
9
10#if !DART_HOST_OS_IOS
11
12#include <CoreServices/CoreServices.h> // NOLINT
13#include <errno.h> // NOLINT
14#include <fcntl.h> // NOLINT
15#include <unistd.h> // NOLINT
16
17#include "bin/eventhandler.h"
18#include "bin/fdutils.h"
19#include "bin/file.h"
20#include "bin/namespace.h"
21#include "bin/socket.h"
22#include "bin/thread.h"
24
25#ifndef MAC_OS_X_VERSION_10_7
26enum { kFSEventStreamCreateFlagFileEvents = 0x00000010 };
27enum {
28 kFSEventStreamEventFlagItemCreated = 0x00000100,
29 kFSEventStreamEventFlagItemRemoved = 0x00000200,
30 kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400,
31 kFSEventStreamEventFlagItemRenamed = 0x00000800,
32 kFSEventStreamEventFlagItemModified = 0x00001000,
33 kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000,
34 kFSEventStreamEventFlagItemChangeOwner = 0x00004000,
35 kFSEventStreamEventFlagItemXattrMod = 0x00008000,
36 kFSEventStreamEventFlagItemIsFile = 0x00010000,
37 kFSEventStreamEventFlagItemIsDir = 0x00020000,
38 kFSEventStreamEventFlagItemIsSymlink = 0x00040000
39};
40#endif
41
42namespace dart {
43namespace bin {
44
45union FSEvent {
46 struct {
47 uint32_t exists;
48 uint32_t flags;
49 char path[PATH_MAX];
50 } data;
51 uint8_t bytes[PATH_MAX + 8];
52};
53
54class FSEventsWatcher {
55 public:
56 class Node {
57 public:
58 Node(FSEventsWatcher* watcher,
59 char* base_path,
60 int read_fd,
61 int write_fd,
62 bool recursive)
63 : watcher_(watcher),
64 base_path_length_(strlen(base_path)),
65 path_ref_(CFStringCreateWithCString(nullptr,
66 base_path,
67 kCFStringEncodingUTF8)),
68 read_fd_(read_fd),
69 write_fd_(write_fd),
70 recursive_(recursive),
71 ref_(nullptr) {
72 Start();
73 }
74
75 ~Node() {
76 // This is invoked outside of [Callback] execution because
77 // [context.release] callback is invoked when [FSEventStream] is
78 // deallocated, the same [FSEventStream] that [Callback] gets a reference
79 // to during its execution. [Callback] holding a reference prevents stream
80 // from deallocation.
81 close(write_fd_);
82 CFRelease(path_ref_);
83 watcher_ = nullptr; // this is to catch access-after-free in Callback
84 }
85
86 void set_ref(FSEventStreamRef ref) { ref_ = ref; }
87
88 void Start() {
89 FSEventStreamContext context;
90 memset(&context, 0, sizeof(context));
91 context.info = reinterpret_cast<void*>(this);
92 context.release = [](const void* info) {
93 delete static_cast<const Node*>(info);
94 };
95 CFArrayRef array = CFArrayCreate(
96 nullptr, reinterpret_cast<const void**>(&path_ref_), 1, nullptr);
97 FSEventStreamRef ref = FSEventStreamCreate(
98 nullptr, Callback, &context, array, kFSEventStreamEventIdSinceNow,
99 0.10, kFSEventStreamCreateFlagFileEvents);
100 CFRelease(array);
101
102 set_ref(ref);
103
104 FSEventStreamScheduleWithRunLoop(ref_, watcher_->run_loop_,
105 kCFRunLoopDefaultMode);
106
107 FSEventStreamStart(ref_);
108 FSEventStreamFlushSync(ref_);
109 }
110
111 void Stop() {
112 FSEventStreamStop(ref_);
113 FSEventStreamInvalidate(ref_);
114 FSEventStreamRelease(ref_);
115 }
116
117 FSEventsWatcher* watcher() const { return watcher_; }
118 intptr_t base_path_length() const { return base_path_length_; }
119 int read_fd() const { return read_fd_; }
120 int write_fd() const { return write_fd_; }
121 bool recursive() const { return recursive_; }
122
123 private:
124 FSEventsWatcher* watcher_;
125 intptr_t base_path_length_;
126 CFStringRef path_ref_;
127 int read_fd_;
128 int write_fd_;
129 bool recursive_;
130 FSEventStreamRef ref_;
131
133 };
134
135 FSEventsWatcher() : run_loop_(0) { Start(); }
136
137 void Start() {
138 Thread::Start("dart:io FileWatcher", Run, reinterpret_cast<uword>(this));
139 monitor_.Enter();
140 while (run_loop_ == nullptr) {
141 monitor_.Wait(Monitor::kNoTimeout);
142 }
143 monitor_.Exit();
144 }
145
146 static void Run(uword arg) {
147 FSEventsWatcher* watcher = reinterpret_cast<FSEventsWatcher*>(arg);
148 // Only checked in debug mode.
149 watcher->threadId_ = Thread::GetCurrentThreadId();
150 watcher->run_loop_ = CFRunLoopGetCurrent();
151 CFRetain(watcher->run_loop_);
152
153 // Notify, as the run-loop is set.
154 watcher->monitor().Enter();
155 watcher->monitor().Notify();
156 watcher->monitor().Exit();
157
158 CFRunLoopTimerRef timer =
159 CFRunLoopTimerCreate(nullptr, CFAbsoluteTimeGetCurrent() + 1, 1, 0, 0,
160 TimerCallback, nullptr);
161 CFRunLoopAddTimer(watcher->run_loop_, timer, kCFRunLoopCommonModes);
162 CFRelease(timer);
163
164 CFRunLoopRun();
165
166 CFRelease(watcher->run_loop_);
167 watcher->monitor_.Enter();
168 watcher->run_loop_ = nullptr;
169 watcher->monitor_.Notify();
170 watcher->monitor_.Exit();
171 }
172
173 void Stop() {
174 // Schedule StopCallback to be executed in the RunLoop.
175 CFRunLoopTimerContext context;
176 memset(&context, 0, sizeof(context));
177 context.info = this;
178 CFRunLoopTimerRef timer =
179 CFRunLoopTimerCreate(nullptr, 0, 0, 0, 0, StopCallback, &context);
180 CFRunLoopAddTimer(run_loop_, timer, kCFRunLoopCommonModes);
181 CFRelease(timer);
182 monitor_.Enter();
183 while (run_loop_ != nullptr) {
184 monitor_.Wait(Monitor::kNoTimeout);
185 }
186 monitor_.Exit();
187 }
188
189 static void StopCallback(CFRunLoopTimerRef timer, void* info) {
190 FSEventsWatcher* watcher = reinterpret_cast<FSEventsWatcher*>(info);
191 ASSERT(Thread::Compare(watcher->threadId_, Thread::GetCurrentThreadId()));
192 CFRunLoopStop(watcher->run_loop_);
193 }
194
195 ~FSEventsWatcher() { Stop(); }
196
197 Monitor& monitor() { return monitor_; }
198
199 bool has_run_loop() const { return run_loop_ != nullptr; }
200
201 static void TimerCallback(CFRunLoopTimerRef timer, void* context) {
202 // Dummy callback to keep RunLoop alive.
203 }
204
205 Node* AddPath(const char* path, int events, bool recursive) {
206 int fds[2];
207 VOID_NO_RETRY_EXPECTED(pipe(fds));
209 FDUtils::SetBlocking(fds[1]);
210
211 char base_path[PATH_MAX];
212 realpath(path, base_path);
213
214 return new Node(this, base_path, fds[0], fds[1], recursive);
215 }
216
217 private:
218 static void Callback(ConstFSEventStreamRef ref,
219 void* client,
220 size_t num_events,
221 void* event_paths,
222 const FSEventStreamEventFlags event_flags[],
223 const FSEventStreamEventId event_ids[]) {
225 // Used in tests to highlight race between callback invocation
226 // and unwatching the file path, Node destruction
227 TimerUtils::Sleep(1000 /* ms */);
228 }
229 Node* node = static_cast<Node*>(client);
230 RELEASE_ASSERT(node->watcher() != nullptr);
231 ASSERT(Thread::Compare(node->watcher()->threadId_,
233 for (size_t i = 0; i < num_events; i++) {
234 char* path = reinterpret_cast<char**>(event_paths)[i];
235 FSEvent event;
236 event.data.exists =
237 File::GetType(nullptr, path, false) != File::kDoesNotExist;
238 path += node->base_path_length();
239 // If path is longer the base, skip next character ('/').
240 if (path[0] != '\0') {
241 path += 1;
242 }
243 if (!node->recursive() && (strstr(path, "/") != nullptr)) {
244 continue;
245 }
246 event.data.flags = event_flags[i];
247 memmove(event.data.path, path, strlen(path) + 1);
248 write(node->write_fd(), event.bytes, sizeof(event));
249 }
250 }
251
252 Monitor monitor_;
253 CFRunLoopRef run_loop_;
254 ThreadId threadId_;
255
256 DISALLOW_COPY_AND_ASSIGN(FSEventsWatcher);
257};
258
259#define kCFCoreFoundationVersionNumber10_7 635.00
261 return kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber10_7;
262}
263
264intptr_t FileSystemWatcher::Init() {
265 return reinterpret_cast<intptr_t>(new FSEventsWatcher());
266}
267
268void FileSystemWatcher::Close(intptr_t id) {
269 delete reinterpret_cast<FSEventsWatcher*>(id);
270}
271
272intptr_t FileSystemWatcher::WatchPath(intptr_t id,
273 Namespace* namespc,
274 const char* path,
275 int events,
276 bool recursive) {
277 FSEventsWatcher* watcher = reinterpret_cast<FSEventsWatcher*>(id);
278 return reinterpret_cast<intptr_t>(watcher->AddPath(path, events, recursive));
279}
280
281void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) {
282 USE(id);
283 reinterpret_cast<FSEventsWatcher::Node*>(path_id)->Stop();
284}
285
286intptr_t FileSystemWatcher::GetSocketId(intptr_t id, intptr_t path_id) {
287 return reinterpret_cast<FSEventsWatcher::Node*>(path_id)->read_fd();
288}
289
290Dart_Handle FileSystemWatcher::ReadEvents(intptr_t id, intptr_t path_id) {
291 intptr_t fd = GetSocketId(id, path_id);
292 intptr_t avail = FDUtils::AvailableBytes(fd);
293 int count = avail / sizeof(FSEvent);
294 if (count <= 0) {
295 return Dart_NewList(0);
296 }
298 FSEvent e;
299 for (int i = 0; i < count; i++) {
300 intptr_t bytes = TEMP_FAILURE_RETRY(read(fd, e.bytes, sizeof(e)));
301 if (bytes < 0) {
303 }
304 size_t path_len = strlen(e.data.path);
305 Dart_Handle event = Dart_NewList(5);
306 int flags = e.data.flags;
307 int mask = 0;
308 if ((flags & kFSEventStreamEventFlagItemRenamed) != 0) {
309 if (path_len == 0) {
310 // The moved path is the path being watched.
311 mask |= kDeleteSelf;
312 } else {
313 mask |= e.data.exists ? kCreate : kDelete;
314 }
315 }
316 if ((flags & kFSEventStreamEventFlagItemModified) != 0) {
317 mask |= kModifyContent;
318 }
319 if ((flags & kFSEventStreamEventFlagItemXattrMod) != 0) {
320 mask |= kModifyAttribute;
321 }
322 if ((flags & kFSEventStreamEventFlagItemCreated) != 0) {
323 mask |= kCreate;
324 }
325 if ((flags & kFSEventStreamEventFlagItemIsDir) != 0) {
326 mask |= kIsDir;
327 }
328 if ((flags & kFSEventStreamEventFlagItemRemoved) != 0) {
329 if (path_len == 0) {
330 // The removed path is the path being watched.
331 mask |= kDeleteSelf;
332 } else {
333 mask |= kDelete;
334 }
335 }
339 reinterpret_cast<uint8_t*>(e.data.path), path_len);
340 if (Dart_IsError(name)) {
341 return name;
342 }
346 Dart_ListSetAt(events, i, event);
347 }
348 return events;
349}
350
351} // namespace bin
352} // namespace dart
353
354#else // !DART_HOST_OS_IOS
355
356namespace dart {
357namespace bin {
358
359// FSEvents are unavailable on iOS. Stub out related methods
360Dart_Handle FileSystemWatcher::ReadEvents(intptr_t id, intptr_t path_id) {
362}
363
364intptr_t FileSystemWatcher::GetSocketId(intptr_t id, intptr_t path_id) {
365 return -1;
366}
367
369 return false;
370}
371
372void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) {}
373
374intptr_t FileSystemWatcher::Init() {
375 return -1;
376}
377
378void FileSystemWatcher::Close(intptr_t id) {}
379
380intptr_t FileSystemWatcher::WatchPath(intptr_t id,
381 Namespace* namespc,
382 const char* path,
383 int events,
384 bool recursive) {
385 return -1;
386}
387
388} // namespace bin
389} // namespace dart
390
391#endif // !DART_HOST_OS_IOS
392#endif // defined(DART_HOST_OS_MACOS)
static void info(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: DM.cpp:213
int count
Definition: FontMgrTest.cpp:50
static bool read(SkStream *stream, void *buffer, size_t amount)
#define RELEASE_ASSERT(cond)
Definition: assert.h:327
static Dart_Handle NewDartOSError()
Definition: dartutils.cc:702
static intptr_t AvailableBytes(intptr_t fd)
static bool SetBlocking(intptr_t fd)
static bool SetNonBlocking(intptr_t fd)
static intptr_t GetSocketId(intptr_t id, intptr_t path_id)
static void Close(intptr_t id)
static void UnwatchPath(intptr_t id, intptr_t path_id)
static Dart_Handle ReadEvents(intptr_t id, intptr_t path_id)
static intptr_t WatchPath(intptr_t id, Namespace *namespc, const char *path, int events, bool recursive)
@ kDoesNotExist
Definition: file.h:81
static Type GetType(Namespace *namespc, const char *path, bool follow_links)
static constexpr int64_t kNoTimeout
Definition: thread.h:79
static int Start(const char *name, ThreadStartFunction function, uword parameters)
static bool Compare(ThreadId a, ThreadId b)
static ThreadId GetCurrentThreadId()
static void Sleep(int64_t millis)
struct _Dart_Handle * Dart_Handle
Definition: dart_api.h:258
#define ASSERT(E)
FlutterSemanticsFlag flags
FlKeyEvent * event
Definition: dart.idl:29
pthread_t ThreadId
Definition: thread_absl.h:21
Definition: dart_vm.cc:33
const char *const name
DART_EXPORT Dart_Handle Dart_NewInteger(int64_t value)
DART_EXPORT Dart_Handle Dart_NewStringFromUTF8(const uint8_t *utf8_array, intptr_t length)
uintptr_t uword
Definition: globals.h:501
DART_EXPORT Dart_Handle Dart_ListSetAt(Dart_Handle list, intptr_t index, Dart_Handle value)
DART_EXPORT Dart_Handle Dart_NewBoolean(bool value)
DART_EXPORT bool Dart_IsError(Dart_Handle handle)
static void USE(T &&)
Definition: globals.h:618
DART_EXPORT Dart_Handle Dart_NewList(intptr_t length)
static int8_t data[kExtLength]
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
std::function< void(MTLRenderPipelineDescriptor *)> Callback
#define PATH_MAX
Definition: globals.h:708
#define DISALLOW_COPY_AND_ASSIGN(TypeName)
Definition: globals.h:581
#define VOID_NO_RETRY_EXPECTED(expression)
#define TEMP_FAILURE_RETRY(expression)
void write(SkWStream *wStream, const T &text)
Definition: skqp.cpp:188
const uintptr_t id