-
-
Notifications
You must be signed in to change notification settings - Fork 3.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Utilities: Introduce the watchfs utility
This utility is meant mainly for debugging purposes, to watch regular files and directories being modified.
- Loading branch information
1 parent
60cda20
commit ccf3d29
Showing
3 changed files
with
226 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
## Name | ||
|
||
watchfs - watch a file or directory being changed | ||
|
||
## Synopsis | ||
|
||
```**sh | ||
$ watchfs [options...] [path...] | ||
``` | ||
|
||
## Description | ||
|
||
`watchfs` watches files and directories being changed. | ||
|
||
## Options | ||
|
||
* `--help`: Display this message | ||
* `-E`, `--exit-after-change`: Wait for first change and exit | ||
* `-a`, `--watch-all-events`: Watch all types of events | ||
* `-d`, `--watch-delete-events`: Watch file deletion events | ||
* `-m`, `--watch-file-modify-events`: Watch file content being modified | ||
* `-M`, `--watch-file-metadata-events`: Watch file metadata being modified | ||
* `-c`, `--watch-directory-child-creation-events`: Watch directory child creation events | ||
* `-D`, `--watch-directory-child-deletion-events`: Watch directory child deletion events | ||
|
||
## Arguments | ||
|
||
* `path`: Files and/or directories to watch | ||
|
||
## Examples | ||
|
||
```sh | ||
# watch /tmp with all events being handled (child creation and deletion) | ||
$ watchfs -a /tmp/ | ||
# watch /tmp with child creation events being handled | ||
$ watchfs -c /tmp/ | ||
# watch /tmp with child creation events being handled | ||
$ watchfs -D /tmp/ | ||
|
||
# watch /tmp with all events being handled (child creation and deletion) and exit after first change | ||
$ watchfs -E /tmp/ | ||
|
||
# watch /tmp/test_file with all events being handled (file being deleted, metadata being modified or content modified) | ||
$ watchfs -a /tmp/test_file | ||
# watch /tmp/test_file being deleted | ||
$ watchfs -d /tmp/test_file | ||
# watch /tmp/test_file being metadata-modified | ||
$ watchfs -M /tmp/test_file | ||
# watch /tmp/test_file being content-modified | ||
$ watchfs -m /tmp/test_file | ||
``` | ||
|
||
## See also | ||
* [`listdir`(1)](help://man/1/listdir) to list directory entries | ||
* [`ls`(1)](help://man/1/ls) to list directory contents |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
/* | ||
* Copyright (c) 2024, Liav A. <[email protected]> | ||
* | ||
* SPDX-License-Identifier: BSD-2-Clause | ||
*/ | ||
|
||
#include <AK/Function.h> | ||
#include <AK/StringView.h> | ||
#include <LibCore/ArgsParser.h> | ||
#include <LibCore/DirIterator.h> | ||
#include <LibCore/DirectoryEntry.h> | ||
#include <LibCore/EventLoop.h> | ||
#include <LibCore/FileWatcher.h> | ||
#include <LibCore/System.h> | ||
#include <LibMain/Main.h> | ||
|
||
static ErrorOr<NonnullRefPtr<Core::FileWatcher>> watch_directory_child_creation(StringView path, bool exit_after_first_change) | ||
{ | ||
auto watcher = TRY(Core::FileWatcher::create()); | ||
|
||
watcher->on_change = [path, exit_after_first_change](auto&) { | ||
outln("{} has new file", path); | ||
if (exit_after_first_change) | ||
exit(1); | ||
}; | ||
|
||
TRY(watcher->add_watch(path, Core::FileWatcherEvent::Type::ChildCreated)); | ||
return watcher; | ||
} | ||
|
||
static ErrorOr<NonnullRefPtr<Core::FileWatcher>> watch_directory_child_deletion(StringView path, bool exit_after_first_change) | ||
{ | ||
auto watcher = TRY(Core::FileWatcher::create()); | ||
|
||
watcher->on_change = [path, exit_after_first_change](auto&) { | ||
outln("{} has file being deleted", path); | ||
if (exit_after_first_change) | ||
exit(1); | ||
}; | ||
|
||
TRY(watcher->add_watch(path, Core::FileWatcherEvent::Type::ChildDeleted)); | ||
return watcher; | ||
} | ||
|
||
static ErrorOr<NonnullRefPtr<Core::FileWatcher>> watch_file_content_modified(StringView path, bool exit_after_first_change) | ||
{ | ||
auto watcher = TRY(Core::FileWatcher::create()); | ||
|
||
watcher->on_change = [path, exit_after_first_change](auto&) { | ||
outln("{} content is modified", path); | ||
if (exit_after_first_change) | ||
exit(1); | ||
}; | ||
|
||
TRY(watcher->add_watch(path, Core::FileWatcherEvent::Type::ContentModified)); | ||
return watcher; | ||
} | ||
|
||
static ErrorOr<NonnullRefPtr<Core::FileWatcher>> watch_file_metadata_modified(StringView path, bool exit_after_first_change) | ||
{ | ||
auto watcher = TRY(Core::FileWatcher::create()); | ||
|
||
watcher->on_change = [path, exit_after_first_change](auto&) { | ||
outln("{} metadata is modified", path); | ||
if (exit_after_first_change) | ||
exit(1); | ||
}; | ||
|
||
TRY(watcher->add_watch(path, Core::FileWatcherEvent::Type::MetadataModified)); | ||
return watcher; | ||
} | ||
|
||
static ErrorOr<NonnullRefPtr<Core::FileWatcher>> watch_file_being_deleted(StringView path, bool exit_after_first_change) | ||
{ | ||
auto watcher = TRY(Core::FileWatcher::create()); | ||
|
||
watcher->on_change = [path, exit_after_first_change](auto&) { | ||
outln("{} is deleted", path); | ||
if (exit_after_first_change) | ||
exit(1); | ||
}; | ||
|
||
TRY(watcher->add_watch(path, Core::FileWatcherEvent::Type::Deleted)); | ||
return watcher; | ||
} | ||
|
||
ErrorOr<int> serenity_main(Main::Arguments arguments) | ||
{ | ||
TRY(Core::System::pledge("stdio rpath")); | ||
|
||
Vector<StringView> paths; | ||
bool flag_exit_after_first_change = false; | ||
bool flag_watch_all_events = false; | ||
bool flag_watch_file_being_deleted = false; | ||
bool flag_watch_file_being_content_modified = false; | ||
bool flag_watch_file_being_metadata_modified = false; | ||
bool flag_watch_directory_child_creation = false; | ||
bool flag_watch_directory_child_deletion = false; | ||
|
||
Core::ArgsParser args_parser; | ||
args_parser.set_general_help("Watch for filesystem activity in a directory."); | ||
args_parser.add_option(flag_exit_after_first_change, "Wait for first change and exit", "exit-after-change", 'E'); | ||
args_parser.add_option(flag_watch_all_events, "Watch all types of events", "watch-all-events", 'a'); | ||
args_parser.add_option(flag_watch_file_being_deleted, "Watch file deletion events", "watch-delete-events", 'd'); | ||
args_parser.add_option(flag_watch_file_being_content_modified, "Watch file content being modified", "watch-file-modify-events", 'm'); | ||
args_parser.add_option(flag_watch_file_being_metadata_modified, "Watch file metadata being modified", "watch-file-metadata-events", 'M'); | ||
args_parser.add_option(flag_watch_directory_child_creation, "Watch directory child creation events", "watch-directory-child-creation-events", 'c'); | ||
args_parser.add_option(flag_watch_directory_child_deletion, "Watch directory child deletion events", "watch-directory-child-deletion-events", 'D'); | ||
args_parser.add_positional_argument(paths, "Path to watch", "path", Core::ArgsParser::Required::No); | ||
args_parser.parse(arguments); | ||
|
||
if (flag_watch_all_events) { | ||
flag_watch_file_being_deleted = true; | ||
flag_watch_file_being_content_modified = true; | ||
flag_watch_file_being_metadata_modified = true; | ||
flag_watch_directory_child_creation = true; | ||
flag_watch_directory_child_deletion = true; | ||
} | ||
|
||
if (paths.is_empty()) | ||
paths.append("."sv); | ||
|
||
Vector<NonnullRefPtr<Core::FileWatcher>> watchers; | ||
|
||
Core::EventLoop event_loop; | ||
|
||
for (auto& path : paths) { | ||
auto st = TRY(Core::System::stat(path)); | ||
auto directory_entry_type = Core::DirectoryEntry::directory_entry_type_from_stat(st.st_mode); | ||
switch (directory_entry_type) { | ||
case Core::DirectoryEntry::Type::Directory: { | ||
if (flag_watch_directory_child_creation) { | ||
auto watcher = TRY(watch_directory_child_creation(path, flag_exit_after_first_change)); | ||
TRY(watchers.try_append(watcher)); | ||
} | ||
|
||
if (flag_watch_directory_child_deletion) { | ||
auto watcher = TRY(watch_directory_child_deletion(path, flag_exit_after_first_change)); | ||
TRY(watchers.try_append(watcher)); | ||
} | ||
break; | ||
} | ||
case Core::DirectoryEntry::Type::File: { | ||
if (flag_watch_file_being_content_modified) { | ||
auto watcher = TRY(watch_file_content_modified(path, flag_exit_after_first_change)); | ||
TRY(watchers.try_append(watcher)); | ||
} | ||
if (flag_watch_file_being_metadata_modified) { | ||
auto watcher = TRY(watch_file_metadata_modified(path, flag_exit_after_first_change)); | ||
TRY(watchers.try_append(watcher)); | ||
} | ||
if (flag_watch_file_being_deleted) { | ||
auto watcher = TRY(watch_file_being_deleted(path, flag_exit_after_first_change)); | ||
TRY(watchers.try_append(watcher)); | ||
} | ||
break; | ||
} | ||
|
||
default: | ||
warnln("Trying to watch unsupported file type"); | ||
break; | ||
} | ||
} | ||
|
||
if (watchers.is_empty()) | ||
return Error::from_string_literal("Watchers list is empty"); | ||
|
||
event_loop.exec(); | ||
return 0; | ||
} |