From 64bb5652a1c6a149a5c5c836ffd2ed8ec2b30aee Mon Sep 17 00:00:00 2001 From: Tim Ledbetter Date: Tue, 29 Aug 2023 14:31:34 +0100 Subject: [PATCH] find: Add the `-newer`, `-anewer` and `-cnewer` options These return true if the last modification time, last access time or creation time of a file is greater than the given reference file. If the `-L` option is in use and the given reference file is a symbolic link then the timestamp of the file pointed to by the symbolic link will be used. --- Base/usr/share/man/man1/find.md | 12 +++++++ Userland/Utilities/find.cpp | 59 +++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/Base/usr/share/man/man1/find.md b/Base/usr/share/man/man1/find.md index 127e476f18..721d5b7c60 100644 --- a/Base/usr/share/man/man1/find.md +++ b/Base/usr/share/man/man1/find.md @@ -52,6 +52,18 @@ space rounded up to the nearest whole unit. * `-writable`: Checks if the file is writable by the current user. * `-executable`: Checks if the file is executable, or directory is searchable, by the current user. +* `-newer file`: Checks if the file last modification time is greater than that + of the specified reference file. If `file` is a symbolic link and the `-L` + option is in use, then the last modification time of the file pointed to by + the symbolic link is used. +* `-anewer file`: Checks if the file last access time is greater than that of + the specified reference file. If `file` is a symbolic link and the `-L` + option is in use, then the last access time of the file pointed to by the + symbolic link is used. +* `-cnewer file`: Checks if the file creation time is greater than that of + the specified reference file. If `file` is a symbolic link and the `-L` + option is in use, then the creation time of the file pointed to by the + symbolic link is used. * `-print`: Outputs the file path, followed by a newline. Always evaluates to true. * `-print0`: Outputs the file path, followed by a zero byte. Always evaluates to diff --git a/Userland/Utilities/find.cpp b/Userland/Utilities/find.cpp index 8f1646eeeb..b0683952bd 100644 --- a/Userland/Utilities/find.cpp +++ b/Userland/Utilities/find.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -326,6 +327,52 @@ private: mode_t m_mode { 0 }; }; +class NewerCommand final : public StatCommand { +public: + enum class TimestampType { + LastAccess, + Creation, + LastModification + }; + + NewerCommand(char const* arg, TimestampType timestamp_type) + { + auto stat_function = g_follow_symlinks ? Core::System::stat : Core::System::lstat; + auto stat_or_error = stat_function({ arg, strlen(arg) }); + if (stat_or_error.is_error()) + fatal_error("find: '{}': {}", arg, strerror(stat_or_error.error().code())); + + m_reference_file_stat = stat_or_error.release_value(); + m_timestamp_type = timestamp_type; + } + +private: + virtual bool evaluate(struct stat const& stat) const override + { + struct timespec current_file_timestamp; + struct timespec reference_file_timestamp; + switch (m_timestamp_type) { + case TimestampType::LastAccess: + current_file_timestamp = stat.st_atim; + reference_file_timestamp = m_reference_file_stat.st_atim; + break; + case TimestampType::Creation: + current_file_timestamp = stat.st_ctim; + reference_file_timestamp = m_reference_file_stat.st_ctim; + break; + case TimestampType::LastModification: + current_file_timestamp = stat.st_mtim; + reference_file_timestamp = m_reference_file_stat.st_mtim; + break; + } + + return Duration::from_timespec(current_file_timestamp) > Duration::from_timespec(reference_file_timestamp); + } + + struct stat m_reference_file_stat; + TimestampType m_timestamp_type { TimestampType::LastModification }; +}; + class PrintCommand final : public Command { public: PrintCommand(char terminator = '\n') @@ -488,6 +535,18 @@ static OwnPtr parse_simple_command(Vector& args) return make(W_OK); } else if (arg == "-executable") { return make(X_OK); + } else if (arg == "-newer") { + if (args.is_empty()) + fatal_error("-newer: requires additional arguments"); + return make(args.take_first(), NewerCommand::TimestampType::LastModification); + } else if (arg == "-anewer") { + if (args.is_empty()) + fatal_error("-anewer: requires additional arguments"); + return make(args.take_first(), NewerCommand::TimestampType::LastAccess); + } else if (arg == "-cnewer") { + if (args.is_empty()) + fatal_error("-cnewer: requires additional arguments"); + return make(args.take_first(), NewerCommand::TimestampType::Creation); } else if (arg == "-print") { g_have_seen_action_command = true; return make();