find: Add the -maxdepth and -mindepth options

The `-maxdepth` option limits the number of levels `find` will descend
into the file system for each given starting point.

The `-mindepth` option causes commands not to be evaluated until the
specified depth is reached.
This commit is contained in:
Tim Ledbetter 2023-09-10 00:08:34 +01:00 committed by Andrew Kaster
parent 05d8e2f6f8
commit a95c2ed978
2 changed files with 69 additions and 3 deletions

View file

@ -24,6 +24,12 @@ specified commands, a `-print` command is implicitly appended.
## Commands
* `-maxdepth n`: Do not descend more than `n` levels below each path given on
the command line. Specifying `-maxdepth 0` has the effect of only evaluating
each command line argument.
* `-mindepth n`: Descend `n` levels below each path given on the command line
before executing any commands. Specifying `-mindepth 1` has the effect of
processing all files except the command line arguments.
* `-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).

View file

@ -33,6 +33,8 @@ bool g_follow_symlinks = false;
bool g_there_was_an_error = false;
bool g_have_seen_action_command = false;
bool g_print_hyperlinks = false;
Optional<u32> g_max_depth = {};
Optional<u32> g_min_depth = {};
template<typename... Parameters>
[[noreturn]] static void fatal_error(CheckedFormatString<Parameters...>&& fmtstr, Parameters const&... parameters)
@ -167,6 +169,42 @@ private:
}
};
class MaxDepthCommand : public Command {
public:
MaxDepthCommand(char const* arg)
{
auto max_depth_string = StringView { arg, strlen(arg) };
auto maybe_max_depth = max_depth_string.to_uint<u32>();
if (!maybe_max_depth.has_value())
fatal_error("-maxdepth: '{}' is not a valid non-negative integer", arg);
g_max_depth = maybe_max_depth.value();
}
virtual bool evaluate(FileData&) const override
{
return true;
}
};
class MinDepthCommand : public Command {
public:
MinDepthCommand(char const* arg)
{
auto min_depth_string = StringView { arg, strlen(arg) };
auto maybe_min_depth = min_depth_string.to_uint<u32>();
if (!maybe_min_depth.has_value())
fatal_error("-mindepth: '{}' is not a valid non-negative integer", arg);
g_min_depth = maybe_min_depth.value();
}
virtual bool evaluate(FileData&) const override
{
return true;
}
};
class TypeCommand final : public Command {
public:
TypeCommand(char const* arg)
@ -605,6 +643,14 @@ static OwnPtr<Command> parse_simple_command(Vector<char*>& args)
auto command = parse_simple_command(args).release_nonnull();
return make<NotCommand>(move(command));
} else if (arg == "-maxdepth"sv) {
if (args.is_empty())
fatal_error("-maxdepth: requires additional arguments");
return make<MaxDepthCommand>(args.take_first());
} else if (arg == "-mindepth"sv) {
if (args.is_empty())
fatal_error("-mindepth: requires additional arguments");
return make<MinDepthCommand>(args.take_first());
} else if (arg == "-type") {
if (args.is_empty())
fatal_error("-type: requires additional arguments");
@ -746,9 +792,10 @@ static NonnullOwnPtr<Command> parse_all_commands(Vector<char*>& args)
return make<AndCommand>(command.release_nonnull(), make<PrintCommand>());
}
static void walk_tree(FileData& root_data, Command& command)
static void walk_tree(FileData& root_data, Command& command, u32 depth = 0)
{
command.evaluate(root_data);
if (!g_min_depth.has_value() || g_min_depth.value() <= depth)
command.evaluate(root_data);
// We should try to read directory entries if either:
// * This is a directory.
@ -798,7 +845,20 @@ static void walk_tree(FileData& root_data, Command& command)
false,
dirent->d_type,
};
walk_tree(file_data, command);
bool should_increase_depth = false;
if (g_max_depth.has_value() || g_min_depth.has_value()) {
if (g_max_depth.has_value() && depth >= g_max_depth.value())
return;
if (file_data.d_type == DT_UNKNOWN)
file_data.ensure_stat();
if (file_data.d_type == DT_DIR)
should_increase_depth = true;
}
walk_tree(file_data, command, should_increase_depth ? depth + 1 : depth);
}
if (errno != 0) {