diff --git a/Base/usr/share/man/man1/find.md b/Base/usr/share/man/man1/find.md index 56457f9fe6..572850552b 100644 --- a/Base/usr/share/man/man1/find.md +++ b/Base/usr/share/man/man1/find.md @@ -27,13 +27,17 @@ specified commands, a `-print` command is implicitly appended. * `-type t`: Checks if the file is of the specified type, which must be one of `b` (for block device), `c` (character device), `d` (directory), `l` (symbolic link), `p` (FIFO), `f` (regular file), and `s` (socket). -* `-links number`: Checks if the file has the given number of hard links. +* `-links [-|+]number`: Checks if the file has the given number of hard links. * `-user name`: Checks if the file is owned by the given user. Instead of a user name, a numerical UID may be specified. * `-group name`: Checks if the file is owned by the given group. Instead of a group name, a numerical GID may be specified. -* `-size number[bcwkMG]`: Checks if the file uses the specified `n` units of -space rounded up to the nearest whole unit. +* `-size [-|+]number[bcwkMG]`: Checks if the file uses the specified `n` units of + space rounded up to the nearest whole unit. + + The '+' and '-' prefixes denote greater than and less than, i.e an exact size + of `n` units doesn't match. Sizes are always rounded up to the nearest unit, + empty files, while the latter will match files from 0 to 1,048,575 bytes. The unit of space may be specified by any of these suffixes: @@ -83,6 +87,11 @@ operators: * `command1 -a command2`, `command1 command2`: Logical AND. * `( command )`: Groups commands together for operator priority purposes. +Commands which take a numeric argument `n` (`-links` and `-size` for example), +may be prefixed by a plus sign ('+') or a minus sign ('-'). A plus sign means +"grater than `n`", while a minus sign means "less than `n`". A numeric argument +with no prefix means "exactly equal". + ## Examples ```sh diff --git a/Userland/Utilities/find.cpp b/Userland/Utilities/find.cpp index 78cf0537c3..4897582839 100644 --- a/Userland/Utilities/find.cpp +++ b/Userland/Utilities/find.cpp @@ -94,6 +94,59 @@ struct FileData { } }; +template +class NumericRange { +public: + NumericRange() = default; + + static ErrorOr> parse(StringView arg) + { + if (arg.is_empty()) + return Error::from_errno(EINVAL); + + auto comparison_type = ComparisonType::Equal; + if (arg[0] == '-') { + comparison_type = ComparisonType::LessThan; + arg = arg.substring_view(1); + } else if (arg[0] == '+') { + comparison_type = ComparisonType::GreaterThan; + arg = arg.substring_view(1); + } + + auto maybe_value = arg.to_uint(); + if (!maybe_value.has_value()) + return Error::from_errno(EINVAL); + + return NumericRange(maybe_value.release_value(), comparison_type); + } + + bool contains(T other) const + { + if (m_comparison_type == ComparisonType::LessThan) + return other < m_value; + if (m_comparison_type == ComparisonType::GreaterThan) + return other > m_value; + + return other == m_value; + } + +private: + enum class ComparisonType { + Equal, + LessThan, + GreaterThan + }; + + NumericRange(T value, ComparisonType comparison_type) + : m_value(value) + , m_comparison_type(comparison_type) + { + } + + T m_value {}; + ComparisonType m_comparison_type { ComparisonType::Equal }; +}; + class Command { public: virtual ~Command() = default; @@ -163,19 +216,20 @@ class LinksCommand final : public StatCommand { public: LinksCommand(char const* arg) { - auto number = StringView { arg, strlen(arg) }.to_uint(); - if (!number.has_value()) + StringView arg_string { arg, strlen(arg) }; + auto links_or_error = NumericRange::parse(arg_string); + if (links_or_error.is_error()) fatal_error("Invalid number: \033[1m{}", arg); - m_links = number.value(); + m_links = links_or_error.release_value(); } private: virtual bool evaluate(const struct stat& stat) const override { - return stat.st_nlink == m_links; + return m_links.contains(stat.st_nlink); } - nlink_t m_links { 0 }; + NumericRange m_links {}; }; class UserCommand final : public StatCommand { @@ -258,24 +312,24 @@ public: view = view.substring_view(0, view.length() - 1); } - auto number = view.to_uint(); - if (!number.has_value() || number.value() > AK::NumericLimits::max()) + auto number_or_error = NumericRange::parse(view); + if (number_or_error.is_error()) fatal_error("Invalid size: \033[1m{}", arg); - m_number_of_units = number.value(); + m_number_of_units = number_or_error.release_value(); } private: virtual bool evaluate(const struct stat& stat) const override { if (m_unit_size == 1) - return stat.st_size == m_number_of_units; + return m_number_of_units.contains(stat.st_size); auto size_divided_by_unit_rounded_up = (stat.st_size + m_unit_size - 1) / m_unit_size; - return size_divided_by_unit_rounded_up == m_number_of_units; + return m_number_of_units.contains(size_divided_by_unit_rounded_up); } - off_t m_number_of_units { 0 }; - off_t m_unit_size { 512 }; + NumericRange m_number_of_units {}; + u64 m_unit_size { 512 }; }; class EmptyCommand final : public Command {