blob: ee8c84c5766aff802250eb56e5c81269d51e7a80 [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 <dispatch/dispatch.h>
#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/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 <typename T>
struct SetCObjectValue;
template <>
struct SetCObjectValue<const char*> {
static void Assign(Dart_CObject* o, const char* str) {
o->type = Dart_CObject_kString;
o->value.as_string = str;
}
};
template <>
struct SetCObjectValue<char*> {
static void Assign(Dart_CObject* o, const char* str) {
o->type = Dart_CObject_kString;
o->value.as_string = str;
}
};
template <>
struct SetCObjectValue<int64_t> {
static void Assign(Dart_CObject* o, int64_t v) {
o->type = Dart_CObject_kInt64;
o->value.as_int64 = v;
}
};
template <typename... Ts>
Dart_CObject* CreateCObjectArray(Ts... elements) {
const auto length = sizeof...(elements);
auto array = static_cast<Dart_CObject*>(
malloc(sizeof(Dart_CObject) +
(sizeof(Dart_CObject*) + sizeof(Dart_CObject)) * length));
array->type = Dart_CObject_kArray;
array->value.as_array.values = reinterpret_cast<Dart_CObject**>(array + 1);
array->value.as_array.length = length;
for (uintptr_t i = 0; i < length; i++) {
array->value.as_array.values[i] =
reinterpret_cast<Dart_CObject*>(array->value.as_array.values + length) +
i;
}
int index = 0;
(
[&] {
SetCObjectValue<Ts>::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<void*>(this);
context.release = [](const void* info) {
reinterpret_cast<Node*>(const_cast<void*>(info))->NotifyStopped();
};
CFArrayRef array = CFArrayCreate(
nullptr, reinterpret_cast<const void**>(&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<Node*>(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<Dart_CObject**>(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<char**>(event_paths)[i];
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, "/") != nullptr)) {
continue;
}
const bool is_path_empty = path[0] == '\0';
const bool path_exists =
!is_path_empty &&
File::GetType(nullptr, path, false) != File::kDoesNotExist;
events.value.as_array.values[events.value.as_array.length++] =
CreateCObjectArray(
/*flags=*/ConvertEventFlags(event_flags[i], is_path_empty,
path_exists),
/*cookie=*/static_cast<int64_t>(0), path,
/*path_id=*/reinterpret_cast<int64_t>(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_path_empty,
bool path_exists) {
int64_t mask = 0;
if ((flags & kFSEventStreamEventFlagItemRenamed) != 0) {
if (is_path_empty) {
// 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_path_empty) {
// 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<intptr_t>(
Node::Watch(static_cast<Dart_Port>(id), path, events, recursive));
}
void FileSystemWatcher::UnwatchPath(intptr_t id, intptr_t path_id) {
USE(id);
Node::Unwatch(reinterpret_cast<Node*>(path_id));
}
void FileSystemWatcher::DestroyWatch(intptr_t path_id) {
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(intptr_t path_id) {}
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)