blob: 1ccf75355e0aab7c50d1a2f1a93522b457563a79 [file] [log] [blame]
// 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 <CoreServices/CoreServices.h> // NOLINT
#include <errno.h> // NOLINT
#include <fcntl.h> // NOLINT
#include <unistd.h> // 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),
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() {
// 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.
close(write_fd_);
CFRelease(path_ref_);
watcher_ = nullptr; // this is to catch access-after-free in Callback
}
void set_ref(FSEventStreamRef ref) { ref_ = ref; }
void Start() {
FSEventStreamContext context;
memset(&context, 0, sizeof(context));
context.info = reinterpret_cast<void*>(this);
context.release = [](const void* info) {
delete static_cast<const Node*>(info);
};
CFArrayRef array = CFArrayCreate(
NULL, reinterpret_cast<const void**>(&path_ref_), 1, NULL);
FSEventStreamRef ref = FSEventStreamCreate(
NULL, Callback, &context, array, kFSEventStreamEventIdSinceNow, 0.10,
kFSEventStreamCreateFlagFileEvents);
CFRelease(array);
set_ref(ref);
FSEventStreamScheduleWithRunLoop(ref_, watcher_->run_loop_,
kCFRunLoopDefaultMode);
FSEventStreamStart(ref_);
FSEventStreamFlushSync(ref_);
}
void Stop() {
FSEventStreamStop(ref_);
FSEventStreamInvalidate(ref_);
FSEventStreamRelease(ref_);
}
FSEventsWatcher* watcher() const { return watcher_; }
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_;
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<uword>(this));
monitor_.Enter();
while (run_loop_ == NULL) {
monitor_.Wait(Monitor::kNoTimeout);
}
monitor_.Exit();
}
static void Run(uword arg) {
FSEventsWatcher* watcher = reinterpret_cast<FSEventsWatcher*>(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<FSEventsWatcher*>(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[]) {
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<Node*>(client);
RELEASE_ASSERT(node->watcher() != nullptr);
ASSERT(Thread::Compare(node->watcher()->threadId_,
Thread::GetCurrentThreadId()));
for (size_t i = 0; i < num_events; i++) {
char* path = reinterpret_cast<char**>(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<intptr_t>(new FSEventsWatcher());
}
void FileSystemWatcher::Close(intptr_t id) {
delete reinterpret_cast<FSEventsWatcher*>(id);
}
intptr_t FileSystemWatcher::WatchPath(intptr_t id,
Namespace* namespc,
const char* path,
int events,
bool recursive) {
FSEventsWatcher* watcher = reinterpret_cast<FSEventsWatcher*>(id);
return reinterpret_cast<intptr_t>(watcher->AddPath(path, events, recursive));
}
void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) {
USE(id);
reinterpret_cast<FSEventsWatcher::Node*>(path_id)->Stop();
}
intptr_t FileSystemWatcher::GetSocketId(intptr_t id, intptr_t path_id) {
return reinterpret_cast<FSEventsWatcher::Node*>(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<uint8_t*>(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 // !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) {}
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 // !DART_HOST_OS_IOS
#endif // defined(DART_HOST_OS_MACOS)