From 32ab09a9300c0756b155b5975088117764773c5a Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Sat, 12 Mar 2022 20:23:43 +0000 Subject: [PATCH] LibCore: Add a wrapper for execvpe() and friends This is a single function, which behaves like the various LibC exec() functions depending on the passed parameters. No direct equivalent is made for execl() - you have to wrap your arguments in a Span of some kind. On Serenity, this calls the syscall directly, whereas Lagom forwards to the appropriate LibC function. --- Userland/Libraries/LibCore/System.cpp | 126 +++++++++++++++++++++++++- Userland/Libraries/LibCore/System.h | 5 + 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/Userland/Libraries/LibCore/System.cpp b/Userland/Libraries/LibCore/System.cpp index 7f91c311f7..8ebc0c0962 100644 --- a/Userland/Libraries/LibCore/System.cpp +++ b/Userland/Libraries/LibCore/System.cpp @@ -1,12 +1,14 @@ /* * Copyright (c) 2021-2022, Andreas Kling * Copyright (c) 2021-2022, Kenneth Myhra - * Copyright (c) 2021, Sam Atkins + * Copyright (c) 2021-2022, Sam Atkins * Copyright (c) 2022, Matthias Zimmerman * * SPDX-License-Identifier: BSD-2-Clause */ +#include +#include #include #include #include @@ -906,6 +908,128 @@ ErrorOr adjtime(const struct timeval* delta, struct timeval* old_delta) #endif } +ErrorOr exec(StringView filename, Span arguments, SearchInPath search_in_path, Optional> environment) +{ +#ifdef __serenity__ + Syscall::SC_execve_params params; + + auto argument_strings = TRY(FixedArray::try_create(arguments.size())); + for (size_t i = 0; i < arguments.size(); ++i) { + argument_strings[i] = { arguments[i].characters_without_null_termination(), arguments[i].length() }; + } + params.arguments.strings = argument_strings.data(); + params.arguments.length = argument_strings.size(); + + size_t env_count = 0; + if (environment.has_value()) { + env_count = environment->size(); + } else { + for (size_t i = 0; environ[i]; ++i) + ++env_count; + } + + auto environment_strings = TRY(FixedArray::try_create(env_count)); + if (environment.has_value()) { + for (size_t i = 0; i < env_count; ++i) { + environment_strings[i] = { environment->at(i).characters_without_null_termination(), environment->at(i).length() }; + } + } else { + for (size_t i = 0; i < env_count; ++i) { + environment_strings[i] = { environ[i], strlen(environ[i]) }; + } + } + params.environment.strings = environment_strings.data(); + params.environment.length = environment_strings.size(); + + auto run_exec = [](Syscall::SC_execve_params& params) -> ErrorOr { + int rc = syscall(Syscall::SC_execve, ¶ms); + if (rc < 0) + return Error::from_syscall("exec"sv, rc); + return {}; + }; + + if (search_in_path == SearchInPath::Yes && !filename.contains('/')) { + StringView path = getenv("PATH"); + if (path.is_empty()) + path = "/bin:/usr/bin"; + auto parts = path.split_view(':'); + for (auto& part : parts) { + auto candidate = String::formatted("{}/{}", part, filename); + params.path = { candidate.characters(), candidate.length() }; + auto result = run_exec(params); + if (result.is_error()) { + if (result.error().code() != ENOENT) + return result.error(); + } else { + VERIFY_NOT_REACHED(); + } + } + return Error::from_syscall("exec"sv, -ENOENT); + } else { + params.path = { filename.characters_without_null_termination(), filename.length() }; + } + + TRY(run_exec(params)); + VERIFY_NOT_REACHED(); +#else + String filename_string { filename }; + + auto argument_strings = TRY(FixedArray::try_create(arguments.size())); + auto argv = TRY(FixedArray::try_create(arguments.size() + 1)); + for (size_t i = 0; i < arguments.size(); ++i) { + argument_strings[i] = arguments[i].to_string(); + argv[i] = const_cast(argument_strings[i].characters()); + } + argv[arguments.size()] = nullptr; + + int rc = 0; + if (environment.has_value()) { + auto environment_strings = TRY(FixedArray::try_create(environment->size())); + auto envp = TRY(FixedArray::try_create(environment->size() + 1)); + for (size_t i = 0; i < environment->size(); ++i) { + environment_strings[i] = environment->at(i).to_string(); + envp[i] = const_cast(environment_strings[i].characters()); + } + envp[environment->size()] = nullptr; + + if (search_in_path == SearchInPath::Yes && !filename.contains('/')) { +# if defined(__APPLE__) || defined(__FreeBSD__) + // These BSDs don't support execvpe(), so we'll have to manually search the PATH. + // This is copy-pasted from LibC's execvpe() with minor changes. + ScopedValueRollback errno_rollback(errno); + String path = getenv("PATH"); + if (path.is_empty()) + path = "/bin:/usr/bin"; + auto parts = path.split(':'); + for (auto& part : parts) { + auto candidate = String::formatted("{}/{}", part, filename); + rc = ::execve(candidate.characters(), argv.data(), envp.data()); + if (rc < 0 && errno != ENOENT) { + errno_rollback.set_override_rollback_value(errno); + return Error::from_syscall("exec"sv, rc); + } + } + errno_rollback.set_override_rollback_value(ENOENT); +# else + rc = ::execvpe(filename_string.characters(), argv.data(), envp.data()); +# endif + } else { + rc = ::execve(filename_string.characters(), argv.data(), envp.data()); + } + + } else { + if (search_in_path == SearchInPath::Yes) + rc = ::execvp(filename_string.characters(), argv.data()); + else + rc = ::execv(filename_string.characters(), argv.data()); + } + + if (rc < 0) + return Error::from_syscall("exec"sv, rc); + VERIFY_NOT_REACHED(); +#endif +} + ErrorOr socket(int domain, int type, int protocol) { auto fd = ::socket(domain, type, protocol); diff --git a/Userland/Libraries/LibCore/System.h b/Userland/Libraries/LibCore/System.h index 71dcfc6b4c..49a0a2d288 100644 --- a/Userland/Libraries/LibCore/System.h +++ b/Userland/Libraries/LibCore/System.h @@ -127,6 +127,11 @@ ErrorOr utime(StringView path, Optional); ErrorOr uname(); ErrorOr> pipe2(int flags); ErrorOr adjtime(const struct timeval* delta, struct timeval* old_delta); +enum class SearchInPath { + No, + Yes, +}; +ErrorOr exec(StringView filename, Span arguments, SearchInPath, Optional> environment = {}); ErrorOr socket(int domain, int type, int protocol); ErrorOr bind(int sockfd, struct sockaddr const*, socklen_t);