mirror of
https://github.com/dart-lang/sdk
synced 2024-09-05 16:41:07 +00:00
![Ryan Macnak](/assets/img/avatar_default.png)
TEST=build Change-Id: Ie3be570c274b0275a995a0f54b5e6ccdfc77ccd3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/292287 Commit-Queue: Ryan Macnak <rmacnak@google.com> Reviewed-by: Brian Quinlan <bquinlan@google.com>
393 lines
12 KiB
C++
393 lines
12 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 <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(nullptr,
|
|
base_path,
|
|
kCFStringEncodingUTF8)),
|
|
read_fd_(read_fd),
|
|
write_fd_(write_fd),
|
|
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.
|
|
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(
|
|
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);
|
|
|
|
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_ == nullptr) {
|
|
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(nullptr, CFAbsoluteTimeGetCurrent() + 1, 1, 0, 0,
|
|
TimerCallback, nullptr);
|
|
CFRunLoopAddTimer(watcher->run_loop_, timer, kCFRunLoopCommonModes);
|
|
CFRelease(timer);
|
|
|
|
CFRunLoopRun();
|
|
|
|
CFRelease(watcher->run_loop_);
|
|
watcher->monitor_.Enter();
|
|
watcher->run_loop_ = nullptr;
|
|
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(nullptr, 0, 0, 0, 0, StopCallback, &context);
|
|
CFRunLoopAddTimer(run_loop_, timer, kCFRunLoopCommonModes);
|
|
CFRelease(timer);
|
|
monitor_.Enter();
|
|
while (run_loop_ != nullptr) {
|
|
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_ != nullptr; }
|
|
|
|
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(nullptr, 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, "/") != nullptr)) {
|
|
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)
|