b4c47fd691
Fixes https://github.com/dart-lang/sdk/issues/62365 TEST=ci Change-Id: I70232a592c3382b1833eeffa499e713ef2cadbc2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/471342 Reviewed-by: Ryan Macnak <rmacnak@google.com> Commit-Queue: Alexander Aprelev <aam@google.com>
390 lines
11 KiB
C++
390 lines
11 KiB
C++
// 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];
|
|
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<int64_t>(0), relative_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_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<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(void* ptr) {
|
|
intptr_t path_id = reinterpret_cast<intptr_t>(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)
|