Skip to content
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
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@
.Rhistory
.RData
.Ruserdata
*.Rcheck/
*.tar.gz
src/*.o
src/*.so
src/*.dll
/docs/
/revdep/
/.claude/
Expand Down
3 changes: 2 additions & 1 deletion NEWS.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# watcher (development version)

* Fixes installation failing on systems with a partial or broken system 'libfswatch', now falling back to the bundled build.
* Fixes installation failing on systems with a partial or broken system 'libfswatch', now falling back to the bundled build (#48).
* Updates bundled 'libfswatch' source to 1.22.0 release. On Linux, this fixes 'Renamed' events reporting only the watched directory path when a file is moved in from an unwatched location.

# watcher 0.2.0

Expand Down
59 changes: 49 additions & 10 deletions src/fswatch/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
<p align="center">
<a href="https://emcrisostomo.github.io/fswatch/">
<img src="https://emcrisostomo.github.io/fswatch/images/fswatch-logo.svg" alt="fswatch logo" width="128">
</a>
</p>

[![License](https://img.shields.io/badge/license-GPL--3.0-blue.svg?style=flat)](https://github.com/emcrisostomo/fswatch/blob/master/COPYING)
[![C/C++ CI](https://github.com/emcrisostomo/fswatch/actions/workflows/c-cpp.yml/badge.svg)](https://github.com/emcrisostomo/fswatch/actions/workflows/c-cpp.yml)
[![CMake](https://github.com/emcrisostomo/fswatch/actions/workflows/cmake.yml/badge.svg)](https://github.com/emcrisostomo/fswatch/actions/workflows/cmake.yml)
Expand All @@ -16,6 +22,8 @@ several monitors:
and its derivatives.
* A monitor based on _inotify_, a Linux kernel subsystem that reports file
system changes to applications.
* A monitor based on _fanotify_, a Linux kernel subsystem that reports file
system events to applications.
* A monitor based on _ReadDirectoryChangesW_, a Microsoft Windows API that
reports changes to a directory.
* A monitor which periodically stats the file system, saves file modification
Expand Down Expand Up @@ -57,7 +65,8 @@ Features

* Support for many OS-specific APIs such as kevent, inotify, and FSEvents.
* Recursive directory monitoring.
* Path filtering using including and excluding regular expressions.
* Path filtering using including and excluding regular expressions, with
legacy and conjunctive evaluation modes.
* Customizable record format.
* Support for periodic idle events.

Expand All @@ -80,11 +89,38 @@ The limitations of `fswatch` depend largely on the monitor being used:
every file that cannot be opened.

* The **inotify** monitor, available on Linux since kernel 2.6.13, may suffer
a queue overflow if events are generated faster than they are read from the
queue. In any case, the application is guaranteed to receive an overflow
notification which can be handled to gracefully recover. `fswatch`
currently throws an exception if a queue overflow occurs. Future versions
will handle the overflow by emitting proper notifications.
from several limitations. It provides no information about the user or
process that triggered an event, so it cannot easily distinguish events it
triggers itself from those triggered by other processes. It reports only
events triggered through the filesystem API, so it does not catch remote
events on network filesystems and cannot monitor pseudo-filesystems such as
`/proc`, `/sys`, or `/dev/pts`. It also does not report accesses or
modifications that occur through `mmap(2)`, `msync(2)`, or `munmap(2)`.
Events identify files by name, but the name may already have been deleted
or renamed by the time the application handles the event. Events are also
identified by watch descriptors, so applications must cache a mapping to
pathnames if they need one, and directory renames can affect several cached
pathnames at once. Inotify monitoring of directories is not recursive, so
additional watches must be created for subdirectories, and that can take a
significant amount of time on large trees. If a new subdirectory appears
or is renamed into a monitored tree, the application may need to rescan it
immediately after adding the watch so that it does not miss children that
already exist. The event queue can overflow, in which case events are lost
and robust applications may need to rebuild some or all of their cache.
Finally, if a filesystem is mounted on top of a monitored directory, no
event is generated until the filesystem is unmounted again.

* The **fanotify** monitor, available on Linux since kernel 2.6.37, reports
only events triggered through the filesystem API, so it does not catch
remote events on network filesystems. It also does not report accesses or
modifications that occur through `mmap(2)`, `msync(2)`, or `munmap(2)`.
Directory events are generated only when the directory itself is opened,
read, and closed, so adding, removing, or changing children does not create
events for the monitored directory itself. Fanotify monitoring of
directories is not recursive: subdirectories require additional marks, and
using `FAN_CREATE` to detect them is racy because events may be lost before
the new mark is added. Monitoring mounts or entire filesystems avoids this
race. The event queue can also overflow, in which case events are lost.

* The **Windows** monitor can only establish a watch _directories_, not files.
To watch a file, its parent directory must be watched in order to receive
Expand Down Expand Up @@ -215,20 +251,23 @@ Documentation
* Texinfo documentation, included with the distribution.
* HTML documentation.
* PDF documentation.
* A [wiki] page.
* The [GitHub website].
* A man page.

`fswatch` official documentation is provided in Texinfo format. This is the
most comprehensive source of information about `fswatch` and the only
authoritative one. The man page, in particular, is a stub that suggests the
user to use the info page instead.

The project website also provides maintained online documentation and release
resources.

If you are installing `fswatch` using a package manager and you would like the
PDF manual to be bundled into the package, please send a feature request to the
package maintainer.

[documentation]: http://emcrisostomo.github.io/fswatch/doc
[wiki]: https://github.com/emcrisostomo/fswatch/wiki
[documentation]: https://emcrisostomo.github.io/fswatch/documentation.html
[GitHub website]: https://emcrisostomo.github.io/fswatch/

Localization
------------
Expand Down Expand Up @@ -315,7 +354,7 @@ v. 2.0.

-----

Copyright (c) 2013-2025 Enrico M. Crisostomo
Copyright (c) 2013-2026 Enrico M. Crisostomo

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Expand Down
10 changes: 5 additions & 5 deletions src/fswatch/libfswatch/src/libfswatch/c++/fanotify_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,7 @@ namespace fsw
return true;
}

void fanotify_monitor::scan(const std::filesystem::path& path, const bool accept_non_dirs)
void fanotify_monitor::scan(const std::filesystem::path& path, const bool is_root_path)
{
try
{
Expand All @@ -419,14 +419,14 @@ namespace fsw

if (follow_symlinks && std::filesystem::is_symlink(status))
{
scan(std::filesystem::read_symlink(path), accept_non_dirs);
scan(std::filesystem::read_symlink(path), is_root_path);
return;
}

const bool is_dir = std::filesystem::is_directory(status);
if (!is_dir && !accept_non_dirs) return;
if (should_prune_path(path.string(), is_dir, is_root_path)) return;
if (!is_dir && !is_root_path) return;
if (!is_dir && directory_only) return;
if (!accept_path(path.string())) return;
if (is_watched(path.string())) return;
if (!add_mark(path)) return;
if (!recursive || !is_dir) return;
Expand All @@ -447,7 +447,7 @@ namespace fsw
{
for (const std::string& path : paths)
{
if (!is_watched(path)) scan(path);
if (!is_watched(path)) scan(path, true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ namespace fsw

void initialize();
void scan_root_paths();
void scan(const std::filesystem::path& path, bool accept_non_dirs = true);
void scan(const std::filesystem::path& path, bool is_root_path = false);
bool add_mark(const std::filesystem::path& path);
bool is_watched(const std::string& path) const;
void process_pending_paths();
Expand Down
3 changes: 2 additions & 1 deletion src/fswatch/libfswatch/src/libfswatch/c++/fen_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -275,9 +275,10 @@ namespace fsw
}

bool is_dir = S_ISDIR(fd_stat.st_mode);
if (should_prune_path(path.string(), is_dir, is_root_path)) return true;

if (!is_dir && !is_root_path && directory_only) return true;
if (!is_dir && !accept_path(path)) return true;
if (!is_dir && !is_root_path && !accept_path(path)) return true;
if (!is_dir) return add_watch(path, fd_stat);
if (!recursive) return true;

Expand Down
27 changes: 19 additions & 8 deletions src/fswatch/libfswatch/src/libfswatch/c++/inotify_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ namespace fsw
return (inotify_desc != -1);
}

void inotify_monitor::scan(const std::filesystem::path& path, const bool accept_non_dirs)
void inotify_monitor::scan(const std::filesystem::path& path, const bool is_root_path)
{
try
{
Expand All @@ -260,24 +260,25 @@ namespace fsw
if (follow_symlinks && std::filesystem::is_symlink(status))
{
auto link_path = std::filesystem::read_symlink(path);
scan(link_path, accept_non_dirs);
scan(link_path, is_root_path);
return;
}

const bool is_dir = std::filesystem::is_directory(status);
if (should_prune_path(path.string(), is_dir, is_root_path)) return;

/*
* When watching a directory the inotify API will return change events of
* first-level children. Therefore, we do not need to manually add a watch
* for a child unless it is a directory. By default, accept_non_dirs is
* true to allow watching a file when first invoked on a node.
* for a child unless it is a directory. Directories and root paths must
* remain watchable so output filters do not prevent discovery of matching
* children.
*
* For the same reason, the directory_only flag is ignored and treated as if
* it were always set to true.
*/
if (!is_dir && !accept_non_dirs) return;
if (!is_dir && !is_root_path) return;
if (!is_dir && directory_only) return;
if (!accept_path(path)) return;
if (!add_watch(path)) return;
if (!recursive || !is_dir) return;

Expand All @@ -304,7 +305,7 @@ namespace fsw
{
for (const std::string& path : paths)
{
if (!is_watched(path)) scan(path);
if (!is_watched(path)) scan(path, true);
}
}

Expand All @@ -328,7 +329,17 @@ namespace fsw

if (!flags.empty())
{
impl->events.emplace_back(impl->wd_to_path[event->wd], impl->curr_time, flags, event->cookie);
std::ostringstream filename_stream;
filename_stream << impl->wd_to_path[event->wd];

if ((event->mask & (IN_MOVED_FROM | IN_MOVED_TO)) &&
event->len > 0 && event->name[0] != '\0')
{
filename_stream << "/";
filename_stream << event->name;
}

impl->events.emplace_back(filename_stream.str(), impl->curr_time, flags, event->cookie);
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ namespace fsw
void preprocess_dir_event(const struct inotify_event *event);
void preprocess_event(const struct inotify_event *event);
void preprocess_node_event(const struct inotify_event *event);
void scan(const std::filesystem::path& path, const bool accept_non_dirs = true);
void scan(const std::filesystem::path& path, bool is_root_path = false);
bool add_watch(const std::string& path);
void process_pending_events();
void process_synthetic_events();
Expand Down
11 changes: 6 additions & 5 deletions src/fswatch/libfswatch/src/libfswatch/c++/kqueue_monitor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ namespace fsw
return true;
}

void kqueue_monitor::scan(const std::filesystem::path& path)
void kqueue_monitor::scan(const std::filesystem::path& path, const bool is_root_path)
{
try
{
Expand All @@ -192,17 +192,18 @@ namespace fsw
if (follow_symlinks && std::filesystem::is_symlink(status))
{
const auto link_path = std::filesystem::read_symlink(path);
scan(link_path);
scan(link_path, is_root_path);
return;
}

const bool is_dir = std::filesystem::is_directory(status);
if (should_prune_path(path.string(), is_dir, is_root_path)) return;

// Do not fall through if the monitor is set to watch directories only,
// except for the case of root paths, where the user explicitly asked to
// watch the file itself.
if (!is_dir && directory_only) return;
if (!accept_path(path)) return;
if (!is_root_path && !is_dir && !accept_path(path)) return;

// TODO: C++17 doesn't provide a single, comparable, type to represent st_mode
struct stat fd_stat;
Expand All @@ -217,7 +218,7 @@ namespace fsw

for (const auto& entry : entries)
{
scan(entry);
scan(entry, false);
}
}
catch (const std::filesystem::filesystem_error& e)
Expand Down Expand Up @@ -270,7 +271,7 @@ namespace fsw
{
if (is_path_watched(path)) continue;

scan(path);
scan(path, true);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ namespace fsw

void initialize_kqueue();
void terminate_kqueue();
void scan(const std::filesystem::path& path);
void scan(const std::filesystem::path& path, bool is_root_path = false);
bool add_watch(const std::string& path, const struct stat& fd_stat);
bool is_path_watched(const std::string& path) const;
void remove_deleted();
Expand Down
Loading
Loading