// 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(HOST_OS_MACOS) #include "bin/file_system_watcher.h" #if !HOST_OS_IOS #include // NOLINT #include // NOLINT #include // NOLINT #include // NOLINT #include "bin/eventhandler.h" #include "bin/fdutils.h" #include "bin/file.h" #include "bin/namespace.h" #include "bin/socket.h" #include "bin/thread.h" #include "platform/signal_blocker.h" #ifndef MAC_OS_X_VERSION_10_7 enum { kFSEventStreamCreateFlagFileEvents = 0x00000010 }; enum { kFSEventStreamEventFlagItemCreated = 0x00000100, kFSEventStreamEventFlagItemRemoved = 0x00000200, kFSEventStreamEventFlagItemInodeMetaMod = 0x00000400, kFSEventStreamEventFlagItemRenamed = 0x00000800, kFSEventStreamEventFlagItemModified = 0x00001000, kFSEventStreamEventFlagItemFinderInfoMod = 0x00002000, kFSEventStreamEventFlagItemChangeOwner = 0x00004000, kFSEventStreamEventFlagItemXattrMod = 0x00008000, kFSEventStreamEventFlagItemIsFile = 0x00010000, kFSEventStreamEventFlagItemIsDir = 0x00020000, kFSEventStreamEventFlagItemIsSymlink = 0x00040000 }; #endif namespace dart { namespace bin { union FSEvent { struct { uint32_t exists; uint32_t flags; char path[PATH_MAX]; } data; uint8_t bytes[PATH_MAX + 8]; }; class FSEventsWatcher { public: class Node { public: Node(FSEventsWatcher* watcher, char* base_path, int read_fd, int write_fd, bool recursive) : watcher_(watcher), ready_(false), base_path_length_(strlen(base_path)), path_ref_(CFStringCreateWithCString(NULL, base_path, kCFStringEncodingUTF8)), read_fd_(read_fd), write_fd_(write_fd), recursive_(recursive), ref_(NULL) { Start(); } ~Node() { Stop(); close(write_fd_); CFRelease(path_ref_); } void set_ref(FSEventStreamRef ref) { ref_ = ref; } void Start() { // Schedule StartCallback to be executed in the RunLoop. CFRunLoopTimerContext context; memset(&context, 0, sizeof(context)); context.info = this; CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, 0, 0, 0, 0, Node::StartCallback, &context); CFRunLoopAddTimer(watcher_->run_loop_, timer, kCFRunLoopCommonModes); CFRelease(timer); watcher_->monitor_.Enter(); while (!ready_) { watcher_->monitor_.Wait(Monitor::kNoTimeout); } watcher_->monitor_.Exit(); } static void StartCallback(CFRunLoopTimerRef timer, void* info) { Node* node = reinterpret_cast(info); ASSERT(Thread::Compare(node->watcher_->threadId_, Thread::GetCurrentThreadId())); FSEventStreamContext context; memset(&context, 0, sizeof(context)); context.info = reinterpret_cast(node); CFArrayRef array = CFArrayCreate( NULL, reinterpret_cast(&node->path_ref_), 1, NULL); FSEventStreamRef ref = FSEventStreamCreate( NULL, Callback, &context, array, kFSEventStreamEventIdSinceNow, 0.10, kFSEventStreamCreateFlagFileEvents); CFRelease(array); node->set_ref(ref); FSEventStreamScheduleWithRunLoop(node->ref_, node->watcher_->run_loop_, kCFRunLoopDefaultMode); FSEventStreamStart(node->ref_); FSEventStreamFlushSync(node->ref_); node->watcher_->monitor_.Enter(); node->ready_ = true; node->watcher_->monitor_.Notify(); node->watcher_->monitor_.Exit(); } void Stop() { // Schedule StopCallback to be executed in the RunLoop. ASSERT(ready_); CFRunLoopTimerContext context; memset(&context, 0, sizeof(context)); context.info = this; CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, 0, 0, 0, 0, StopCallback, &context); CFRunLoopAddTimer(watcher_->run_loop_, timer, kCFRunLoopCommonModes); CFRelease(timer); watcher_->monitor_.Enter(); while (ready_) { watcher_->monitor_.Wait(Monitor::kNoTimeout); } watcher_->monitor_.Exit(); } static void StopCallback(CFRunLoopTimerRef timer, void* info) { Node* node = reinterpret_cast(info); ASSERT(Thread::Compare(node->watcher_->threadId_, Thread::GetCurrentThreadId())); FSEventStreamStop(node->ref_); FSEventStreamInvalidate(node->ref_); FSEventStreamRelease(node->ref_); node->watcher_->monitor_.Enter(); node->ready_ = false; node->watcher_->monitor_.Notify(); node->watcher_->monitor_.Exit(); } FSEventsWatcher* watcher() const { return watcher_; } bool ready() const { return ready_; } intptr_t base_path_length() const { return base_path_length_; } int read_fd() const { return read_fd_; } int write_fd() const { return write_fd_; } bool recursive() const { return recursive_; } private: FSEventsWatcher* watcher_; bool ready_; intptr_t base_path_length_; CFStringRef path_ref_; int read_fd_; int write_fd_; bool recursive_; FSEventStreamRef ref_; DISALLOW_COPY_AND_ASSIGN(Node); }; FSEventsWatcher() : run_loop_(0) { Start(); } void Start() { Thread::Start("dart:io FileWatcher", Run, reinterpret_cast(this)); monitor_.Enter(); while (run_loop_ == NULL) { monitor_.Wait(Monitor::kNoTimeout); } monitor_.Exit(); } static void Run(uword arg) { FSEventsWatcher* watcher = reinterpret_cast(arg); // Only checked in debug mode. watcher->threadId_ = Thread::GetCurrentThreadId(); watcher->run_loop_ = CFRunLoopGetCurrent(); CFRetain(watcher->run_loop_); // Notify, as the run-loop is set. watcher->monitor().Enter(); watcher->monitor().Notify(); watcher->monitor().Exit(); CFRunLoopTimerRef timer = CFRunLoopTimerCreate( NULL, CFAbsoluteTimeGetCurrent() + 1, 1, 0, 0, TimerCallback, NULL); CFRunLoopAddTimer(watcher->run_loop_, timer, kCFRunLoopCommonModes); CFRelease(timer); CFRunLoopRun(); CFRelease(watcher->run_loop_); watcher->monitor_.Enter(); watcher->run_loop_ = NULL; watcher->monitor_.Notify(); watcher->monitor_.Exit(); } void Stop() { // Schedule StopCallback to be executed in the RunLoop. CFRunLoopTimerContext context; memset(&context, 0, sizeof(context)); context.info = this; CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, 0, 0, 0, 0, StopCallback, &context); CFRunLoopAddTimer(run_loop_, timer, kCFRunLoopCommonModes); CFRelease(timer); monitor_.Enter(); while (run_loop_ != NULL) { monitor_.Wait(Monitor::kNoTimeout); } monitor_.Exit(); } static void StopCallback(CFRunLoopTimerRef timer, void* info) { FSEventsWatcher* watcher = reinterpret_cast(info); ASSERT(Thread::Compare(watcher->threadId_, Thread::GetCurrentThreadId())); CFRunLoopStop(watcher->run_loop_); } ~FSEventsWatcher() { Stop(); } Monitor& monitor() { return monitor_; } bool has_run_loop() const { return run_loop_ != NULL; } static void TimerCallback(CFRunLoopTimerRef timer, void* context) { // Dummy callback to keep RunLoop alive. } Node* AddPath(const char* path, int events, bool recursive) { int fds[2]; VOID_NO_RETRY_EXPECTED(pipe(fds)); FDUtils::SetNonBlocking(fds[0]); FDUtils::SetBlocking(fds[1]); char base_path[PATH_MAX]; realpath(path, base_path); return new Node(this, base_path, fds[0], fds[1], recursive); } private: static void Callback(ConstFSEventStreamRef ref, void* client, size_t num_events, void* event_paths, const FSEventStreamEventFlags event_flags[], const FSEventStreamEventId event_ids[]) { Node* node = reinterpret_cast(client); ASSERT(Thread::Compare(node->watcher()->threadId_, Thread::GetCurrentThreadId())); // `ready` is set on same thread as this callback is invoked, so we don't // need to lock here. if (!node->ready()) { return; } for (size_t i = 0; i < num_events; i++) { char* path = reinterpret_cast(event_paths)[i]; FSEvent event; event.data.exists = File::GetType(NULL, path, false) != File::kDoesNotExist; path += node->base_path_length(); // If path is longer the base, skip next character ('/'). if (path[0] != '\0') { path += 1; } if (!node->recursive() && (strstr(path, "/") != NULL)) { continue; } event.data.flags = event_flags[i]; memmove(event.data.path, path, strlen(path) + 1); write(node->write_fd(), event.bytes, sizeof(event)); } } Monitor monitor_; CFRunLoopRef run_loop_; ThreadId threadId_; DISALLOW_COPY_AND_ASSIGN(FSEventsWatcher); }; #define kCFCoreFoundationVersionNumber10_7 635.00 bool FileSystemWatcher::IsSupported() { return kCFCoreFoundationVersionNumber >= kCFCoreFoundationVersionNumber10_7; } intptr_t FileSystemWatcher::Init() { return reinterpret_cast(new FSEventsWatcher()); } void FileSystemWatcher::Close(intptr_t id) { delete reinterpret_cast(id); } intptr_t FileSystemWatcher::WatchPath(intptr_t id, Namespace* namespc, const char* path, int events, bool recursive) { FSEventsWatcher* watcher = reinterpret_cast(id); return reinterpret_cast(watcher->AddPath(path, events, recursive)); } void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) { USE(id); delete reinterpret_cast(path_id); } intptr_t FileSystemWatcher::GetSocketId(intptr_t id, intptr_t path_id) { return reinterpret_cast(path_id)->read_fd(); } Dart_Handle FileSystemWatcher::ReadEvents(intptr_t id, intptr_t path_id) { intptr_t fd = GetSocketId(id, path_id); intptr_t avail = FDUtils::AvailableBytes(fd); int count = avail / sizeof(FSEvent); if (count <= 0) { return Dart_NewList(0); } Dart_Handle events = Dart_NewList(count); FSEvent e; for (int i = 0; i < count; i++) { intptr_t bytes = TEMP_FAILURE_RETRY(read(fd, e.bytes, sizeof(e))); if (bytes < 0) { return DartUtils::NewDartOSError(); } size_t path_len = strlen(e.data.path); Dart_Handle event = Dart_NewList(5); int flags = e.data.flags; int mask = 0; if ((flags & kFSEventStreamEventFlagItemRenamed) != 0) { if (path_len == 0) { // The moved path is the path being watched. mask |= kDeleteSelf; } else { mask |= e.data.exists ? kCreate : kDelete; } } if ((flags & kFSEventStreamEventFlagItemModified) != 0) { mask |= kModifyContent; } if ((flags & kFSEventStreamEventFlagItemXattrMod) != 0) { mask |= kModifyAttribute; } if ((flags & kFSEventStreamEventFlagItemCreated) != 0) { mask |= kCreate; } if ((flags & kFSEventStreamEventFlagItemIsDir) != 0) { mask |= kIsDir; } if ((flags & kFSEventStreamEventFlagItemRemoved) != 0) { if (path_len == 0) { // The removed path is the path being watched. mask |= kDeleteSelf; } else { mask |= kDelete; } } Dart_ListSetAt(event, 0, Dart_NewInteger(mask)); Dart_ListSetAt(event, 1, Dart_NewInteger(1)); Dart_Handle name = Dart_NewStringFromUTF8( reinterpret_cast(e.data.path), path_len); if (Dart_IsError(name)) { return name; } Dart_ListSetAt(event, 2, name); Dart_ListSetAt(event, 3, Dart_NewBoolean(true)); Dart_ListSetAt(event, 4, Dart_NewInteger(path_id)); Dart_ListSetAt(events, i, event); } return events; } } // namespace bin } // namespace dart #else // !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) {} intptr_t FileSystemWatcher::Init() { return -1; } void FileSystemWatcher::Close(intptr_t id) {} intptr_t FileSystemWatcher::WatchPath(intptr_t id, Namespace* namespc, const char* path, int events, bool recursive) { return -1; } } // namespace bin } // namespace dart #endif // !HOST_OS_IOS #endif // defined(HOST_OS_MACOS)