From fca5a34ad30cdcdf53669b4923e56d324f45aaed Mon Sep 17 00:00:00 2001 From: Ali Mohammad Pur Date: Wed, 19 Apr 2023 12:19:14 +0330 Subject: [PATCH] Shell: Allow assignment-prefixed commands to run builtins `env` is not capable of running shell builtins, so make a simple builtin in its place. --- Userland/Shell/Builtin.cpp | 56 +++++++++++++++++++++++++++ Userland/Shell/PosixParser.cpp | 58 +++++++++++++++++++--------- Userland/Shell/Shell.h | 69 +++++++++++++++++----------------- 3 files changed, 131 insertions(+), 52 deletions(-) diff --git a/Userland/Shell/Builtin.cpp b/Userland/Shell/Builtin.cpp index 9fb6c67d34..c6a5d0dbbb 100644 --- a/Userland/Shell/Builtin.cpp +++ b/Userland/Shell/Builtin.cpp @@ -1876,6 +1876,62 @@ ErrorOr Shell::builtin_read(Main::Arguments arguments) return 0; } +ErrorOr Shell::builtin_run_with_env(Main::Arguments arguments) +{ + Vector environment_variables; + Vector command_and_arguments; + + Core::ArgsParser parser; + parser.add_option(environment_variables, "Environment variables to set", "env", 'e', "NAME=VALUE"); + parser.add_positional_argument(command_and_arguments, "Command and arguments to run", "command", Core::ArgsParser::Required::Yes); + parser.set_stop_on_first_non_option(true); + + if (!parser.parse(arguments, Core::ArgsParser::FailureBehavior::Ignore)) + return 1; + + if (command_and_arguments.is_empty()) { + warnln("run_with_env: No command to run"); + return 1; + } + + AST::Command command; + TRY(command.argv.try_ensure_capacity(command_and_arguments.size())); + for (auto& arg : command_and_arguments) + command.argv.append(TRY(String::from_utf8(arg))); + + auto commands = TRY(expand_aliases({ move(command) })); + + HashMap> old_environment_entries; + for (auto& variable : environment_variables) { + auto parts = variable.split_limit('=', 2, SplitBehavior::KeepEmpty); + if (parts.size() != 2) { + warnln("run_with_env: Invalid environment variable: '{}'", variable); + return 1; + } + + DeprecatedString name = parts[0]; + old_environment_entries.set(name, getenv(name.characters()) ?: Optional {}); + + DeprecatedString value = parts[1]; + setenv(name.characters(), value.characters(), 1); + } + + int exit_code = 0; + for (auto& job : run_commands(commands)) { + block_on_job(job); + exit_code = job->exit_code(); + } + + for (auto& entry : old_environment_entries) { + if (entry.value.has_value()) + setenv(entry.key.characters(), entry.value->characters(), 1); + else + unsetenv(entry.key.characters()); + } + + return exit_code; +} + bool Shell::has_builtin(StringView name) const { if (name == ":"sv) diff --git a/Userland/Shell/PosixParser.cpp b/Userland/Shell/PosixParser.cpp index 748816fc88..67b07e2670 100644 --- a/Userland/Shell/PosixParser.cpp +++ b/Userland/Shell/PosixParser.cpp @@ -1849,22 +1849,33 @@ ErrorOr> Parser::parse_simple_command() while (peek().type == Token::Type::AssignmentWord) { definitions.append(peek().value); - if (!nodes.is_empty()) { - nodes.append( - make_ref_counted( - peek().position.value_or(empty_position()), - consume().value)); - } else { - // env (assignments) (command) + if (nodes.is_empty()) { + // run_with_env -e*(assignments) -- (command) nodes.append(make_ref_counted( empty_position(), - "env"_short_string)); - - nodes.append( - make_ref_counted( - peek().position.value_or(empty_position()), - consume().value)); + TRY("run_with_env"_string))); } + + auto position = peek().position.value_or(empty_position()); + nodes.append(make_ref_counted( + position, + AST::NameWithPosition { + TRY("reexpand"_string), + position, + }, + Vector> { + make_ref_counted( + position, + TRY(String::formatted("-e{}", consume().value)), + AST::StringLiteral::EnclosureType::DoubleQuotes), + }, + Optional {})); + } + + if (!definitions.is_empty()) { + nodes.append(make_ref_counted( + empty_position(), + "--"_short_string)); } // WORD or io_redirect: IO_NUMBER or io_file @@ -1879,13 +1890,24 @@ ErrorOr> Parser::parse_simple_command() auto split_offset = equal_offset.value_or(definition.bytes().size()); auto name = make_ref_counted( empty_position(), - definition.substring_from_byte_offset_with_shared_superstring(0, split_offset).release_value_but_fixme_should_propagate_errors()); + TRY(definition.substring_from_byte_offset_with_shared_superstring(0, split_offset))); - auto value = make_ref_counted( - empty_position(), - definition.substring_from_byte_offset_with_shared_superstring(equal_offset.map([](auto x) { return x + 1; }).value_or(definition.bytes().size())).release_value_but_fixme_should_propagate_errors()); + auto position = peek().position.value_or(empty_position()); + auto expanded_value = make_ref_counted( + position, + AST::NameWithPosition { + TRY("reexpand"_string), + position, + }, + Vector> { + make_ref_counted( + position, + TRY(definition.substring_from_byte_offset_with_shared_superstring(split_offset + 1)), + AST::StringLiteral::EnclosureType::DoubleQuotes), + }, + Optional {}); - variables.append({ move(name), move(value) }); + variables.append({ move(name), move(expanded_value) }); } return make_ref_counted(empty_position(), move(variables)); diff --git a/Userland/Shell/Shell.h b/Userland/Shell/Shell.h index b4e7501c88..ff1615e949 100644 --- a/Userland/Shell/Shell.h +++ b/Userland/Shell/Shell.h @@ -23,40 +23,41 @@ #include #include -#define ENUMERATE_SHELL_BUILTINS() \ - __ENUMERATE_SHELL_BUILTIN(alias) \ - __ENUMERATE_SHELL_BUILTIN(where) \ - __ENUMERATE_SHELL_BUILTIN(cd) \ - __ENUMERATE_SHELL_BUILTIN(cdh) \ - __ENUMERATE_SHELL_BUILTIN(pwd) \ - __ENUMERATE_SHELL_BUILTIN(type) \ - __ENUMERATE_SHELL_BUILTIN(exec) \ - __ENUMERATE_SHELL_BUILTIN(exit) \ - __ENUMERATE_SHELL_BUILTIN(export) \ - __ENUMERATE_SHELL_BUILTIN(glob) \ - __ENUMERATE_SHELL_BUILTIN(unalias) \ - __ENUMERATE_SHELL_BUILTIN(unset) \ - __ENUMERATE_SHELL_BUILTIN(history) \ - __ENUMERATE_SHELL_BUILTIN(umask) \ - __ENUMERATE_SHELL_BUILTIN(not ) \ - __ENUMERATE_SHELL_BUILTIN(dirs) \ - __ENUMERATE_SHELL_BUILTIN(pushd) \ - __ENUMERATE_SHELL_BUILTIN(popd) \ - __ENUMERATE_SHELL_BUILTIN(setopt) \ - __ENUMERATE_SHELL_BUILTIN(shift) \ - __ENUMERATE_SHELL_BUILTIN(source) \ - __ENUMERATE_SHELL_BUILTIN(time) \ - __ENUMERATE_SHELL_BUILTIN(jobs) \ - __ENUMERATE_SHELL_BUILTIN(disown) \ - __ENUMERATE_SHELL_BUILTIN(fg) \ - __ENUMERATE_SHELL_BUILTIN(bg) \ - __ENUMERATE_SHELL_BUILTIN(wait) \ - __ENUMERATE_SHELL_BUILTIN(dump) \ - __ENUMERATE_SHELL_BUILTIN(kill) \ - __ENUMERATE_SHELL_BUILTIN(noop) \ - __ENUMERATE_SHELL_BUILTIN(break) \ - __ENUMERATE_SHELL_BUILTIN(continue) \ - __ENUMERATE_SHELL_BUILTIN(read) \ +#define ENUMERATE_SHELL_BUILTINS() \ + __ENUMERATE_SHELL_BUILTIN(alias) \ + __ENUMERATE_SHELL_BUILTIN(where) \ + __ENUMERATE_SHELL_BUILTIN(cd) \ + __ENUMERATE_SHELL_BUILTIN(cdh) \ + __ENUMERATE_SHELL_BUILTIN(pwd) \ + __ENUMERATE_SHELL_BUILTIN(type) \ + __ENUMERATE_SHELL_BUILTIN(exec) \ + __ENUMERATE_SHELL_BUILTIN(exit) \ + __ENUMERATE_SHELL_BUILTIN(export) \ + __ENUMERATE_SHELL_BUILTIN(glob) \ + __ENUMERATE_SHELL_BUILTIN(unalias) \ + __ENUMERATE_SHELL_BUILTIN(unset) \ + __ENUMERATE_SHELL_BUILTIN(history) \ + __ENUMERATE_SHELL_BUILTIN(umask) \ + __ENUMERATE_SHELL_BUILTIN(not ) \ + __ENUMERATE_SHELL_BUILTIN(dirs) \ + __ENUMERATE_SHELL_BUILTIN(pushd) \ + __ENUMERATE_SHELL_BUILTIN(popd) \ + __ENUMERATE_SHELL_BUILTIN(setopt) \ + __ENUMERATE_SHELL_BUILTIN(shift) \ + __ENUMERATE_SHELL_BUILTIN(source) \ + __ENUMERATE_SHELL_BUILTIN(time) \ + __ENUMERATE_SHELL_BUILTIN(jobs) \ + __ENUMERATE_SHELL_BUILTIN(disown) \ + __ENUMERATE_SHELL_BUILTIN(fg) \ + __ENUMERATE_SHELL_BUILTIN(bg) \ + __ENUMERATE_SHELL_BUILTIN(wait) \ + __ENUMERATE_SHELL_BUILTIN(dump) \ + __ENUMERATE_SHELL_BUILTIN(kill) \ + __ENUMERATE_SHELL_BUILTIN(noop) \ + __ENUMERATE_SHELL_BUILTIN(break) \ + __ENUMERATE_SHELL_BUILTIN(continue) \ + __ENUMERATE_SHELL_BUILTIN(read) \ + __ENUMERATE_SHELL_BUILTIN(run_with_env) \ __ENUMERATE_SHELL_BUILTIN(argsparser_parse) #define ENUMERATE_SHELL_OPTIONS() \