// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. #include "platform/globals.h" #if defined(DART_HOST_OS_MACOS) #include "bin/file_system_watcher.h" #if !DART_HOST_OS_IOS #include // NOLINT #include #include // NOLINT #include // NOLINT #include // NOLINT #include "bin/eventhandler.h" #include "bin/fdutils.h" #include "bin/file.h" #include "bin/lockers.h" #include "bin/namespace.h" #include "bin/socket.h" #include "bin/thread.h" #include "platform/signal_blocker.h" namespace dart { namespace bin { union FSEvent { struct { uint32_t exists; uint32_t flags; char path[PATH_MAX]; } data; uint8_t bytes[PATH_MAX + 8]; }; // A helper for creating Dart_CObject array using a single allocation. // // We can't use CObject helpers because those rely on Dart_ScopeAllocate. namespace { template struct SetCObjectValue; template <> struct SetCObjectValue { static void Assign(Dart_CObject* o, const char* str) { o->type = Dart_CObject_kString; o->value.as_string = str; } }; template <> struct SetCObjectValue { static void Assign(Dart_CObject* o, const char* str) { o->type = Dart_CObject_kString; o->value.as_string = str; } }; template <> struct SetCObjectValue { static void Assign(Dart_CObject* o, int64_t v) { o->type = Dart_CObject_kInt64; o->value.as_int64 = v; } }; template Dart_CObject* CreateCObjectArray(Ts... elements) { const auto length = sizeof...(elements); auto array = static_cast( malloc(sizeof(Dart_CObject) + (sizeof(Dart_CObject*) + sizeof(Dart_CObject)) * length)); array->type = Dart_CObject_kArray; array->value.as_array.values = reinterpret_cast(array + 1); array->value.as_array.length = length; for (uintptr_t i = 0; i < length; i++) { array->value.as_array.values[i] = reinterpret_cast(array->value.as_array.values + length) + i; } int index = 0; ( [&] { SetCObjectValue::Assign(array->value.as_array.values[index], elements); ++index; }(), ...); return array; } } // namespace class Node { public: Node(Dart_Port port, char* base_path, bool recursive) : port_(port), base_path_length_(strlen(base_path)), path_ref_(CFStringCreateWithCString(nullptr, base_path, kCFStringEncodingUTF8)), recursive_(recursive), ref_(nullptr) { Start(); } ~Node() { // This is invoked outside of [Callback] execution because // [context.release] callback is invoked when [FSEventStream] is // deallocated, the same [FSEventStream] that [Callback] gets a reference // to during its execution. [Callback] holding a reference prevents stream // from deallocation. CFRelease(path_ref_); } void set_ref(FSEventStreamRef ref) { ref_ = ref; } void Start() { FSEventStreamContext context; memset(&context, 0, sizeof(context)); context.info = reinterpret_cast(this); context.release = [](const void* info) { reinterpret_cast(const_cast(info))->NotifyStopped(); }; CFArrayRef array = CFArrayCreate( nullptr, reinterpret_cast(&path_ref_), 1, nullptr); FSEventStreamRef ref = FSEventStreamCreate( nullptr, Callback, &context, array, kFSEventStreamEventIdSinceNow, 0.10, kFSEventStreamCreateFlagFileEvents); CFRelease(array); set_ref(ref); FSEventStreamSetDispatchQueue(ref_, notification_queue_); FSEventStreamStart(ref_); FSEventStreamFlushSync(ref_); } void Stop() { FSEventStreamStop(ref_); FSEventStreamInvalidate(ref_); FSEventStreamRelease(ref_); { MonitorLocker lock(&monitor_); while (running_) { lock.Wait(); } } } intptr_t base_path_length() const { return base_path_length_; } bool recursive() const { return recursive_; } static Node* Watch(Dart_Port port, const char* path, int events, bool recursive) { char base_path[PATH_MAX]; realpath(path, base_path); return new Node(port, base_path, recursive); } static void Unwatch(Node* node) { node->Stop(); delete node; } static void InitOnce() { notification_queue_ = dispatch_queue_create("dev.dart.fsevents", DISPATCH_QUEUE_SERIAL); } static void Cleanup() { // We want to make sure that no Node is active, these should have all been // destroyed. dispatch_release(notification_queue_); } private: void NotifyStopped() { MonitorLocker lock(&monitor_); running_ = false; lock.Notify(); } static void Callback(ConstFSEventStreamRef ref, void* client, size_t num_events, void* event_paths, const FSEventStreamEventFlags event_flags[], const FSEventStreamEventId event_ids[]) { if (FileSystemWatcher::delayed_filewatch_callback()) { // Used in tests to highlight race between callback invocation // and unwatching the file path, Node destruction TimerUtils::Sleep(1000 /* ms */); } Node* node = static_cast(client); // Can't use CObject helpers because they expect Dart_ScopeAllocate to work // and this thread is not attached to any isolate or native message handler. Dart_CObject events; events.type = Dart_CObject_kArray; events.value.as_array.values = static_cast(malloc(sizeof(Dart_CObject*) * num_events)); events.value.as_array.length = 0; for (size_t i = 0; i < num_events; i++) { char* path = reinterpret_cast(event_paths)[i]; char* relative_path = path; relative_path += node->base_path_length(); // If path is longer than the base, skip next character ('/'). if (relative_path[0] != '\0') { relative_path += 1; } if (!node->recursive() && (strstr(relative_path, "/") != nullptr)) { continue; } const bool is_self = relative_path[0] == '\0'; const bool path_exists = File::GetType(nullptr, path, false) != File::kDoesNotExist; events.value.as_array.values[events.value.as_array.length++] = CreateCObjectArray( /*flags=*/ConvertEventFlags(event_flags[i], is_self, path_exists), /*cookie=*/static_cast(0), relative_path, /*path_id=*/reinterpret_cast(node)); } if (events.value.as_array.length != 0) { Dart_PostCObject(node->port_, &events); } for (int i = 0; i < events.value.as_array.length; i++) { free(events.value.as_array.values[i]); } free(events.value.as_array.values); } static int64_t ConvertEventFlags(FSEventStreamEventFlags flags, bool is_self, bool path_exists) { int64_t mask = 0; if ((flags & kFSEventStreamEventFlagItemRenamed) != 0) { if (is_self) { // The moved path is the path being watched. mask |= FileSystemWatcher::kDeleteSelf; } else if (path_exists) { mask |= FileSystemWatcher::kCreate; } else { mask |= FileSystemWatcher::kDelete; } } if ((flags & kFSEventStreamEventFlagItemModified) != 0) { mask |= FileSystemWatcher::kModifyContent; } if ((flags & kFSEventStreamEventFlagItemXattrMod) != 0) { mask |= FileSystemWatcher::kModifyAttribute; } if ((flags & kFSEventStreamEventFlagItemCreated) != 0) { mask |= FileSystemWatcher::kCreate; } if ((flags & kFSEventStreamEventFlagItemIsDir) != 0) { mask |= FileSystemWatcher::kIsDir; } if ((flags & kFSEventStreamEventFlagItemRemoved) != 0) { if (is_self) { // The removed path is the path being watched. mask |= FileSystemWatcher::kDeleteSelf; } else { mask |= FileSystemWatcher::kDelete; } } return mask; } static dispatch_queue_t notification_queue_; Dart_Port port_; intptr_t base_path_length_; CFStringRef path_ref_; bool recursive_; FSEventStreamRef ref_; Monitor monitor_; bool running_ = true; DISALLOW_COPY_AND_ASSIGN(Node); }; dispatch_queue_t Node::notification_queue_; bool FileSystemWatcher::IsSupported() { return true; } void FileSystemWatcher::InitOnce() { Node::InitOnce(); } void FileSystemWatcher::Cleanup() { Node::Cleanup(); } intptr_t FileSystemWatcher::Init() { return 0; } intptr_t FileSystemWatcher::WatchPath(intptr_t id, Namespace* namespc, const char* path, int events, bool recursive) { return reinterpret_cast( Node::Watch(static_cast(id), path, events, recursive)); } void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) { USE(id); Node::Unwatch(reinterpret_cast(path_id)); } void FileSystemWatcher::DestroyWatch(void* ptr) { intptr_t path_id = reinterpret_cast(ptr); FileSystemWatcher::UnwatchPath(0, path_id); } intptr_t FileSystemWatcher::GetSocketId(intptr_t id, intptr_t path_id) { // This API should not be called. We are communicating over ports instead. return -1; } Dart_Handle FileSystemWatcher::ReadEvents(intptr_t id, intptr_t path_id) { // This API should not be called. We are communicating over ports instead. return DartUtils::NewDartOSError(); } } // namespace bin } // namespace dart #else // !DART_HOST_OS_IOS namespace dart { namespace bin { // FSEvents are unavailable on iOS. Stub out related methods Dart_Handle FileSystemWatcher::ReadEvents(intptr_t id, intptr_t path_id) { return DartUtils::NewDartOSError(); } intptr_t FileSystemWatcher::GetSocketId(intptr_t id, intptr_t path_id) { return -1; } bool FileSystemWatcher::IsSupported() { return false; } void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) {} void FileSystemWatcher::DestroyWatch(void* ptr) {} void FileSystemWatcher::InitOnce() {} void FileSystemWatcher::Cleanup() {} intptr_t FileSystemWatcher::Init() { return -1; } intptr_t FileSystemWatcher::WatchPath(intptr_t id, Namespace* namespc, const char* path, int events, bool recursive) { return -1; } } // namespace bin } // namespace dart #endif // !DART_HOST_OS_IOS #endif // defined(DART_HOST_OS_MACOS)