diff --git a/Base/usr/share/man/man1/find.md b/Base/usr/share/man/man1/find.md index 9e166b6608..9686139319 100644 --- a/Base/usr/share/man/man1/find.md +++ b/Base/usr/share/man/man1/find.md @@ -88,6 +88,11 @@ by the current user. substituting the file path for any arguments specified as `{}`. The list of arguments must be terminated by a semicolon. Checks if the command exits successfully. +* `-ok command... ;`: Behaves identically to the `-exec` command, but will + prompt the user for confirmation before executing the given command. An + affirmative response is any response that begins with the 'y' character. + Any non-affirmative response will cause the command to not be executed and + the value returned by `-ok` to be false. The commands can be combined to form complex expressions using the following operators: diff --git a/Userland/Utilities/find.cpp b/Userland/Utilities/find.cpp index a367d19623..89b5888e39 100644 --- a/Userland/Utilities/find.cpp +++ b/Userland/Utilities/find.cpp @@ -566,30 +566,65 @@ private: class ExecCommand final : public Command { public: - ExecCommand(Vector&& argv) + enum class AwaitConfirmation { + Yes, + No + }; + + ExecCommand(Vector&& argv, AwaitConfirmation await_confirmation = AwaitConfirmation::No) : m_argv(move(argv)) + , m_await_confirmation(await_confirmation) { } private: virtual bool evaluate(FileData& file_data) const override { + // Replace any occurrences of "{}" with the path. + // Since we know that the array isn't modified before we + // fork the process, let's just const_cast away the constness. + auto argv = const_cast&>(m_argv); + for (auto& arg : argv) { + if (StringView { arg, strlen(arg) } == "{}") + arg = const_cast(file_data.full_path.string().characters()); + } + + if (m_await_confirmation == AwaitConfirmation::Yes) { + out(stderr, "\""); + bool first = true; + for (auto& arg : argv) { + if (!first) + out(stderr, " "); + + out(stderr, "{}", arg); + first = false; + } + out(stderr, "\"? "); + fflush(stderr); + + Array buffer; + auto nread_or_error = Core::System::read(STDIN_FILENO, buffer); + if (nread_or_error.is_error()) { + warn("Failed to read from stdin: {}", strerror(nread_or_error.error().code())); + return false; + } + + auto nread = nread_or_error.release_value(); + if (nread <= 0 || (buffer[0] != 'y' && buffer[0] != 'Y')) + return false; + } + pid_t pid = fork(); if (pid < 0) { perror("fork"); g_there_was_an_error = true; return false; - } else if (pid == 0) { - // Replace any occurrences of "{}" with the path. Since we're in the - // child and going to exec real soon, let's just const_cast away the - // constness. - auto argv = const_cast&>(m_argv); - for (auto& arg : argv) { - if (StringView { arg, strlen(arg) } == "{}") - arg = const_cast(file_data.full_path.string().characters()); - } - argv.append(nullptr); + } + + argv.ensure_capacity(argv.size() + 1); + argv.append(nullptr); + if (pid == 0) { execvp(m_argv[0], argv.data()); perror("execvp"); exit(1); @@ -606,6 +641,7 @@ private: } Vector m_argv; + AwaitConfirmation m_await_confirmation { AwaitConfirmation::No }; }; class AndCommand final : public Command { @@ -767,9 +803,9 @@ static OwnPtr parse_simple_command(Vector& args) } else if (arg == "-print0") { g_have_seen_action_command = true; return make(0); - } else if (arg == "-exec") { + } else if (arg == "-exec" || arg == "-ok") { if (args.is_empty()) - fatal_error("-exec: requires additional arguments"); + fatal_error("{}: requires additional arguments", arg); g_have_seen_action_command = true; Vector command_argv; while (!args.is_empty()) { @@ -778,7 +814,8 @@ static OwnPtr parse_simple_command(Vector& args) break; command_argv.append(next); } - return make(move(command_argv)); + auto await_confirmation = (arg == "-ok") ? ExecCommand::AwaitConfirmation::Yes : ExecCommand::AwaitConfirmation::No; + return make(move(command_argv), await_confirmation); } else { fatal_error("Unsupported command \033[1m{}", arg); }