Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const w = await watcher.watchPath('/var/log', {}, events => {
// Absolute path to the filesystem entry that was touched
console.log(`Event path: ${event.path}`)

// "file", "directory", or "unknown"
// "file", "directory", "symlink", or "unknown"
console.log(`Event entry kind: ${event.kind}`)

if (event.action === 'renamed') {
Expand Down Expand Up @@ -111,7 +111,7 @@ The _options_ argument configures the nature of the watch. Pass `{}` to accept t
The _callback_ argument will be called repeatedly with each batch of filesystem events that are delivered until the [`.dispose() method`](#pathwatcherdispose) is called. Event batches are `Arrays` containing objects with the following keys:

* `action`: a `String` describing the filesystem action that occurred. One of `"created"`, `"modified"`, `"deleted"`, or `"renamed"`.
* `kind`: a `String` distinguishing the type of filesystem entry that was acted upon, if known. One of `"file"`, `"directory"`, or `"unknown"`.
* `kind`: a `String` distinguishing the type of filesystem entry that was acted upon, if known. One of `"file"`, `"directory"`, `"symlink"`, or `"unknown"`.
* `path`: a `String` containing the absolute path to the filesystem entry that was acted upon. In the event of a rename, this is the _new_ path of the entry.
* `oldPath`: a `String` containing the former absolute path of a renamed filesystem entry. Omitted when action is not `"renamed"`.

Expand Down
3 changes: 2 additions & 1 deletion lib/native-watcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ const ACTIONS = new Map([
const ENTRIES = new Map([
[0, 'file'],
[1, 'directory'],
[2, 'unknown']
[2, 'symlink'],
[3, 'unknown']
])

// Private: Possible states of a {NativeWatcher}.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"format:cpp": "script/c++-format",
"format:js": "standard --fix",
"build:debug": "node --harmony script/helper/gen-compilation-db.js rebuild --debug",
"test": "mocha --require test/global.js --require mocha-stress --recursive --harmony",
"test": "mocha",
"test:lldb": "lldb -- node --harmony ./node_modules/.bin/_mocha --require test/global.js --require mocha-stress --recursive",
"test:gdb": "gdb --args node --harmony ./node_modules/.bin/_mocha --require test/global.js --require mocha-stress --recursive",
"ci:appveyor": "npm run test -- --fgrep ^windows --invert --reporter mocha-appveyor-reporter --reporter-options appveyorBatchSize=5 --timeout 30000",
Expand Down
1 change: 1 addition & 0 deletions src/helper/libuv.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ inline bool ts_not_equal(const uv_timespec_t &left, const uv_timespec_t &right)

inline EntryKind kind_from_stat(const uv_stat_t &st)
{
if ((st.st_mode & S_IFLNK) == S_IFLNK) return KIND_SYMLINK;
if ((st.st_mode & S_IFDIR) == S_IFDIR) return KIND_DIRECTORY;
if ((st.st_mode & S_IFREG) == S_IFREG) return KIND_FILE;
return KIND_UNKNOWN;
Expand Down
1 change: 1 addition & 0 deletions src/message.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ ostream &operator<<(ostream &out, EntryKind kind)
switch (kind) {
case KIND_FILE: out << "file"; break;
case KIND_DIRECTORY: out << "directory"; break;
case KIND_SYMLINK: out << "symlink"; break;
case KIND_UNKNOWN: out << "unknown"; break;
default: out << "!! EntryKind=" << static_cast<int>(kind);
}
Expand Down
3 changes: 2 additions & 1 deletion src/message.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ enum EntryKind
{
KIND_FILE = 0,
KIND_DIRECTORY = 1,
KIND_UNKNOWN = 2,
KIND_SYMLINK = 2,
KIND_UNKNOWN = 3,
KIND_MIN = KIND_FILE,
KIND_MAX = KIND_UNKNOWN
};
Expand Down
8 changes: 6 additions & 2 deletions src/worker/linux/linux_worker_platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "../../log.h"
#include "../../message.h"
#include "../../result.h"
#include "../recent_file_cache.h"
#include "../worker_platform.h"
#include "../worker_thread.h"
#include "cookie_jar.h"
Expand All @@ -21,11 +22,13 @@ using std::string;
using std::unique_ptr;
using std::vector;

const size_t DEFAULT_CACHE_SIZE = 4096;

// Platform-specific worker implementation for Linux systems.
class LinuxWorkerPlatform : public WorkerPlatform
{
public:
LinuxWorkerPlatform(WorkerThread *thread) : WorkerPlatform(thread)
LinuxWorkerPlatform(WorkerThread *thread) : WorkerPlatform(thread), cache{DEFAULT_CACHE_SIZE}
{
report_errable(pipe);
report_errable(registry);
Expand Down Expand Up @@ -67,7 +70,7 @@ class LinuxWorkerPlatform : public WorkerPlatform
if ((to_poll[1].revents & (POLLIN | POLLERR)) != 0u) {
MessageBuffer messages;

Result<> cr = registry.consume(messages, jar);
Result<> cr = registry.consume(messages, jar, cache);
if (cr.is_error()) LOGGER << cr << endl;

if (!messages.empty()) {
Expand Down Expand Up @@ -128,6 +131,7 @@ class LinuxWorkerPlatform : public WorkerPlatform
Pipe pipe;
WatchRegistry registry;
CookieJar jar;
RecentFileCache cache;
};

unique_ptr<WorkerPlatform> WorkerPlatform::for_worker(WorkerThread *thread)
Expand Down
5 changes: 3 additions & 2 deletions src/worker/linux/watch_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "../../message.h"
#include "../../message_buffer.h"
#include "../../result.h"
#include "../recent_file_cache.h"
#include "cookie_jar.h"
#include "side_effect.h"
#include "watch_registry.h"
Expand Down Expand Up @@ -219,7 +220,7 @@ Result<> WatchRegistry::remove(ChannelID channel_id)
return ok_result();
}

Result<> WatchRegistry::consume(MessageBuffer &messages, CookieJar &jar)
Result<> WatchRegistry::consume(MessageBuffer &messages, CookieJar &jar, RecentFileCache &cache)
{
Timer t;
const size_t BUFSIZE = 2048 * sizeof(inotify_event);
Expand Down Expand Up @@ -285,7 +286,7 @@ Result<> WatchRegistry::consume(MessageBuffer &messages, CookieJar &jar)

for (shared_ptr<WatchedDirectory> &watched_directory : watched_directories) {
SideEffect side;
Result<> r = watched_directory->accept_event(messages, jar, side, *event);
Result<> r = watched_directory->accept_event(messages, jar, side, cache, *event);
if (r.is_error()) LOGGER << "Unable to process event: " << r << "." << endl;
side.enact_in(watched_directory, this, messages);
}
Expand Down
6 changes: 4 additions & 2 deletions src/worker/linux/watch_registry.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "../../errable.h"
#include "../../message_buffer.h"
#include "../../result.h"
#include "../recent_file_cache.h"
#include "cookie_jar.h"
#include "side_effect.h"
#include "watched_directory.h"
Expand Down Expand Up @@ -50,8 +51,9 @@ class WatchRegistry : public Errable

// Interpret all inotify events created since the previous call to consume(), until the
// read() call would block. Buffer messages corresponding to each inotify event. Use the
// CookieJar to match pairs of rename events across event batches.
Result<> consume(MessageBuffer &messages, CookieJar &jar);
// CookieJar to match pairs of rename events across event batches and the RecentFileCache to
// identify symlinks without doing a stat for every event.
Result<> consume(MessageBuffer &messages, CookieJar &jar, RecentFileCache &cache);

// Return the file descriptor that should be polled to wake up when inotify events are
// available.
Expand Down
17 changes: 16 additions & 1 deletion src/worker/linux/watched_directory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "../../message.h"
#include "../../message_buffer.h"
#include "../../result.h"
#include "../recent_file_cache.h"
#include "cookie_jar.h"
#include "side_effect.h"
#include "watched_directory.h"
Expand All @@ -33,12 +34,22 @@ WatchedDirectory::WatchedDirectory(int wd,
Result<> WatchedDirectory::accept_event(MessageBuffer &buffer,
CookieJar &jar,
SideEffect &side,
RecentFileCache &cache,
const inotify_event &event)
{
EntryKind kind = (event.mask & IN_ISDIR) == IN_ISDIR ? KIND_DIRECTORY : KIND_FILE;
string basename{event.name};
string path = absolute_event_path(event);

bool dir_hint = (event.mask & IN_ISDIR) == IN_ISDIR;

// Read or refresh the cached lstat() entry primarily to determine if this entry is a symlink or not.
shared_ptr<StatResult> stat = cache.former_at_path(path, !dir_hint, dir_hint, false);
if (stat->is_absent()) {
stat = cache.current_at_path(path, !dir_hint, dir_hint, false);
cache.apply();
}
EntryKind kind = stat->get_entry_kind();

if ((event.mask & IN_CREATE) == IN_CREATE) {
// create entry inside directory
if (kind == KIND_DIRECTORY && recursive) {
Expand All @@ -50,6 +61,7 @@ Result<> WatchedDirectory::accept_event(MessageBuffer &buffer,

if ((event.mask & IN_DELETE) == IN_DELETE) {
// delete entry inside directory
cache.evict(path);
buffer.deleted(channel_id, move(path), kind);
return ok_result();
}
Expand All @@ -63,6 +75,7 @@ Result<> WatchedDirectory::accept_event(MessageBuffer &buffer,
if ((event.mask & (IN_DELETE_SELF | IN_UNMOUNT)) != 0u) {
if (is_root()) {
side.remove_channel(channel_id);
cache.evict(get_absolute_path());
buffer.deleted(channel_id, get_absolute_path(), KIND_DIRECTORY);
}
return ok_result();
Expand All @@ -72,13 +85,15 @@ Result<> WatchedDirectory::accept_event(MessageBuffer &buffer,
// directory itself was renamed
if (is_root()) {
side.remove_channel(channel_id);
cache.evict(get_absolute_path());
buffer.deleted(channel_id, get_absolute_path(), KIND_DIRECTORY);
}
return ok_result();
}

if ((event.mask & IN_MOVED_FROM) == IN_MOVED_FROM) {
// rename source for directory or entry inside directory
cache.evict(path);
jar.moved_from(buffer, channel_id, event.cookie, move(path), kind);
return ok_result();
}
Expand Down
7 changes: 6 additions & 1 deletion src/worker/linux/watched_directory.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include "../../message_buffer.h"
#include "../../result.h"
#include "../recent_file_cache.h"
#include "cookie_jar.h"
#include "side_effect.h"

Expand All @@ -26,7 +27,11 @@ class WatchedDirectory

// Interpret a single inotify event. Buffer messages, store or resolve rename Cookies from the CookieJar, and
// enqueue SideEffects based on the event's mask.
Result<> accept_event(MessageBuffer &buffer, CookieJar &jar, SideEffect &side, const inotify_event &event);
Result<> accept_event(MessageBuffer &buffer,
CookieJar &jar,
SideEffect &side,
RecentFileCache &cache,
const inotify_event &event);

// A parent WatchedDirectory reported that this directory was renamed. Update our internal state immediately so
// that events on child paths will be reported with the correct path.
Expand Down
4 changes: 2 additions & 2 deletions src/worker/macos/batch_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ bool Event::skip_recursive_event()
void Event::collect_info()
{
if (!former) {
former = cache().former_at_path(event_path, flag_file(), flag_directory());
former = cache().former_at_path(event_path, flag_file(), flag_directory(), flag_symlink());
}
current = cache().current_at_path(get_stat_path(), flag_file(), flag_directory());
current = cache().current_at_path(get_stat_path(), flag_file(), flag_directory(), flag_symlink());
}

bool Event::should_defer()
Expand Down
2 changes: 2 additions & 0 deletions src/worker/macos/batch_handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ class Event

bool flag_directory() { return (flags & IS_DIRECTORY) != 0; }

bool flag_symlink() { return (flags & IS_SYMLINK) != 0; }

bool is_recursive();

const std::string &root_path();
Expand Down
2 changes: 2 additions & 0 deletions src/worker/macos/flags.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const FSEventStreamEventFlags IS_FILE = kFSEventStreamEventFlagItemIsFile;

const FSEventStreamEventFlags IS_DIRECTORY = kFSEventStreamEventFlagItemIsDir;

const FSEventStreamEventFlags IS_SYMLINK = kFSEventStreamEventFlagItemIsSymlink;

const CFTimeInterval RENAME_TIMEOUT = 0.05;

#endif
27 changes: 18 additions & 9 deletions src/worker/recent_file_cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ using std::static_pointer_cast;
using std::string;
using std::vector;

shared_ptr<StatResult> StatResult::at(string &&path, bool file_hint, bool directory_hint)
shared_ptr<StatResult> StatResult::at(string &&path, bool file_hint, bool directory_hint, bool symlink_hint)
{
FSReq lstat_req;

Expand All @@ -49,8 +49,9 @@ shared_ptr<StatResult> StatResult::at(string &&path, bool file_hint, bool direct
}

EntryKind guessed_kind = KIND_UNKNOWN;
if (file_hint && !directory_hint) guessed_kind = KIND_FILE;
if (!file_hint && directory_hint) guessed_kind = KIND_DIRECTORY;
if (symlink_hint) guessed_kind = KIND_SYMLINK;
if (file_hint && !directory_hint && !symlink_hint) guessed_kind = KIND_FILE;
if (!file_hint && directory_hint && !symlink_hint) guessed_kind = KIND_DIRECTORY;
return shared_ptr<StatResult>(new AbsentEntry(move(path), guessed_kind));
}

Expand Down Expand Up @@ -187,27 +188,34 @@ RecentFileCache::RecentFileCache(size_t maximum_size) : maximum_size{maximum_siz
//
}

shared_ptr<StatResult> RecentFileCache::current_at_path(const string &path, bool file_hint, bool directory_hint)
shared_ptr<StatResult> RecentFileCache::current_at_path(const string &path,
bool file_hint,
bool directory_hint,
bool symlink_hint)
{
auto maybe_pending = pending.find(path);
if (maybe_pending != pending.end()) {
return maybe_pending->second;
}

shared_ptr<StatResult> stat_result = StatResult::at(string(path), file_hint, directory_hint);
shared_ptr<StatResult> stat_result = StatResult::at(string(path), file_hint, directory_hint, symlink_hint);
if (stat_result->is_present()) {
pending.emplace(path, static_pointer_cast<PresentEntry>(stat_result));
}
return stat_result;
}

shared_ptr<StatResult> RecentFileCache::former_at_path(const string &path, bool file_hint, bool directory_hint)
shared_ptr<StatResult> RecentFileCache::former_at_path(const string &path,
bool file_hint,
bool directory_hint,
bool symlink_hint)
{
auto maybe = by_path.find(path);
if (maybe == by_path.end()) {
EntryKind kind = KIND_UNKNOWN;
if (file_hint && !directory_hint) kind = KIND_FILE;
if (!file_hint && directory_hint) kind = KIND_DIRECTORY;
if (symlink_hint) kind = KIND_SYMLINK;
if (file_hint && !directory_hint && !symlink_hint) kind = KIND_FILE;
if (!file_hint && directory_hint && !symlink_hint) kind = KIND_DIRECTORY;

return shared_ptr<StatResult>(new AbsentEntry(string(path), kind));
}
Expand Down Expand Up @@ -338,10 +346,11 @@ size_t RecentFileCache::prepopulate_helper(const string &root, size_t max, bool
string entry_name(dirent.name);
string entry_path(path_join(current_root, entry_name));

bool symlink_hint = dirent.type == UV_DIRENT_LINK;
bool file_hint = dirent.type == UV_DIRENT_FILE;
bool dir_hint = dirent.type == UV_DIRENT_DIR;

shared_ptr<StatResult> r = current_at_path(entry_path, file_hint, dir_hint);
shared_ptr<StatResult> r = current_at_path(entry_path, file_hint, dir_hint, symlink_hint);
if (r->is_present()) {
entries++;
if (recursive && r->get_entry_kind() == KIND_DIRECTORY) next_roots.push(entry_path);
Expand Down
12 changes: 9 additions & 3 deletions src/worker/recent_file_cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class StatResult
{
public:
static std::shared_ptr<StatResult> at(std::string &&path, bool file_hint, bool directory_hint);
static std::shared_ptr<StatResult> at(std::string &&path, bool file_hint, bool directory_hint, bool symlink_hint);

virtual ~StatResult() = default;

Expand Down Expand Up @@ -111,9 +111,15 @@ class RecentFileCache

~RecentFileCache() = default;

std::shared_ptr<StatResult> current_at_path(const std::string &path, bool file_hint, bool directory_hint);
std::shared_ptr<StatResult> current_at_path(const std::string &path,
bool file_hint,
bool directory_hint,
bool symlink_hint);

std::shared_ptr<StatResult> former_at_path(const std::string &path, bool file_hint, bool directory_hint);
std::shared_ptr<StatResult> former_at_path(const std::string &path,
bool file_hint,
bool directory_hint,
bool symlink_hint);

void evict(const std::string &path);

Expand Down
6 changes: 3 additions & 3 deletions src/worker/windows/windows_worker_platform.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,11 +328,11 @@ class WindowsWorkerPlatform : public WorkerPlatform

shared_ptr<StatResult> stat;
if (info->Action == FILE_ACTION_REMOVED || info->Action == FILE_ACTION_RENAMED_OLD_NAME) {
stat = cache.former_at_path(path, false, false);
stat = cache.former_at_path(path, false, false, false);
} else {
stat = cache.current_at_path(path, false, false);
stat = cache.current_at_path(path, false, false, false);
if (stat->is_absent()) {
stat = cache.former_at_path(path, false, false);
stat = cache.former_at_path(path, false, false, false);
}
}
EntryKind kind = stat->get_entry_kind();
Expand Down
Loading