226 lines
7.0 KiB
C++
226 lines
7.0 KiB
C++
/*
|
|
* Copyright (C) 2022 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include <stdlib.h>
|
|
#include <sys/capability.h>
|
|
#include <sys/resource.h>
|
|
#include <unistd.h>
|
|
|
|
#include <filesystem>
|
|
#include <iostream>
|
|
#include <iterator>
|
|
#include <optional>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <system_error>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
#include "android-base/logging.h"
|
|
#include "android-base/parseint.h"
|
|
#include "android-base/result.h"
|
|
#include "android-base/strings.h"
|
|
#include "base/macros.h"
|
|
#include "base/scoped_cap.h"
|
|
#include "fmt/format.h"
|
|
#include "palette/palette.h"
|
|
#include "system/thread_defs.h"
|
|
|
|
namespace {
|
|
|
|
using ::android::base::ConsumePrefix;
|
|
using ::android::base::Join;
|
|
using ::android::base::ParseInt;
|
|
using ::android::base::Result;
|
|
using ::android::base::Split;
|
|
|
|
using ::fmt::literals::operator""_format; // NOLINT
|
|
|
|
constexpr const char* kUsage =
|
|
R"(A wrapper binary that configures the process and executes a command.
|
|
|
|
By default, it closes all open file descriptors except stdin, stdout, and stderr. `--keep-fds` can
|
|
be passed to keep some more file descriptors open.
|
|
|
|
Usage: art_exec [OPTIONS]... -- [COMMAND]...
|
|
|
|
Supported options:
|
|
--help: Print this text.
|
|
--set-task-profile=PROFILES: Apply a set of task profiles (see
|
|
https://source.android.com/devices/tech/perf/cgroups). Requires root access. PROFILES can be a
|
|
comma-separated list of task profile names.
|
|
--set-priority=PRIORITY: Apply the process priority. Currently, the only supported value of
|
|
PRIORITY is "background".
|
|
--drop-capabilities: Drop all root capabilities. Note that this has effect only if `art_exec` runs
|
|
with some root capabilities but not as the root user.
|
|
--keep-fds=FILE_DESCRIPTORS: A semicolon-separated list of file descriptors to keep open.
|
|
--env=KEY=VALUE: Set an environment variable. This flag can be passed multiple times to set
|
|
multiple environment variables.
|
|
)";
|
|
|
|
constexpr int kErrorUsage = 100;
|
|
constexpr int kErrorOther = 101;
|
|
|
|
struct Options {
|
|
int command_pos = -1;
|
|
std::vector<std::string> task_profiles;
|
|
std::optional<int> priority = std::nullopt;
|
|
bool drop_capabilities = false;
|
|
std::unordered_set<int> keep_fds{fileno(stdin), fileno(stdout), fileno(stderr)};
|
|
std::unordered_map<std::string, std::string> envs;
|
|
};
|
|
|
|
[[noreturn]] void Usage(const std::string& error_msg) {
|
|
LOG(ERROR) << error_msg;
|
|
std::cerr << error_msg << "\n" << kUsage << "\n";
|
|
exit(kErrorUsage);
|
|
}
|
|
|
|
Options ParseOptions(int argc, char** argv) {
|
|
Options options;
|
|
for (int i = 1; i < argc; i++) {
|
|
std::string_view arg = argv[i];
|
|
if (arg == "--help") {
|
|
std::cerr << kUsage << "\n";
|
|
exit(0);
|
|
} else if (ConsumePrefix(&arg, "--set-task-profile=")) {
|
|
options.task_profiles = Split(std::string(arg), ",");
|
|
if (options.task_profiles.empty()) {
|
|
Usage("Empty task profile list");
|
|
}
|
|
} else if (ConsumePrefix(&arg, "--set-priority=")) {
|
|
if (arg == "background") {
|
|
options.priority = ANDROID_PRIORITY_BACKGROUND;
|
|
} else {
|
|
Usage("Unknown priority " + std::string(arg));
|
|
}
|
|
} else if (arg == "--drop-capabilities") {
|
|
options.drop_capabilities = true;
|
|
} else if (ConsumePrefix(&arg, "--keep-fds=")) {
|
|
for (const std::string& fd_str : Split(std::string(arg), ":")) {
|
|
int fd;
|
|
if (!ParseInt(fd_str, &fd)) {
|
|
Usage("Invalid fd " + fd_str);
|
|
}
|
|
options.keep_fds.insert(fd);
|
|
}
|
|
} else if (ConsumePrefix(&arg, "--env=")) {
|
|
size_t pos = arg.find('=');
|
|
if (pos == std::string_view::npos) {
|
|
Usage("Malformed environment variable. Must contain '='");
|
|
}
|
|
options.envs[std::string(arg.substr(/*pos=*/0, /*n=*/pos))] =
|
|
std::string(arg.substr(pos + 1));
|
|
} else if (arg == "--") {
|
|
if (i + 1 >= argc) {
|
|
Usage("Missing command after '--'");
|
|
}
|
|
options.command_pos = i + 1;
|
|
return options;
|
|
} else {
|
|
Usage("Unknown option " + std::string(arg));
|
|
}
|
|
}
|
|
Usage("Missing '--'");
|
|
}
|
|
|
|
Result<void> DropInheritableCaps() {
|
|
art::ScopedCap cap(cap_get_proc());
|
|
if (cap.Get() == nullptr) {
|
|
return ErrnoErrorf("Failed to call cap_get_proc");
|
|
}
|
|
if (cap_clear_flag(cap.Get(), CAP_INHERITABLE) != 0) {
|
|
return ErrnoErrorf("Failed to call cap_clear_flag");
|
|
}
|
|
if (cap_set_proc(cap.Get()) != 0) {
|
|
return ErrnoErrorf("Failed to call cap_set_proc");
|
|
}
|
|
return {};
|
|
}
|
|
|
|
Result<void> CloseFds(const std::unordered_set<int>& keep_fds) {
|
|
std::vector<int> open_fds;
|
|
std::error_code ec;
|
|
for (const std::filesystem::directory_entry& dir_entry :
|
|
std::filesystem::directory_iterator("/proc/self/fd", ec)) {
|
|
int fd;
|
|
if (!ParseInt(dir_entry.path().filename(), &fd)) {
|
|
return Errorf("Invalid entry in /proc/self/fd {}", dir_entry.path().filename());
|
|
}
|
|
open_fds.push_back(fd);
|
|
}
|
|
if (ec) {
|
|
return Errorf("Failed to list open FDs: {}", ec.message());
|
|
}
|
|
for (int fd : open_fds) {
|
|
if (keep_fds.find(fd) == keep_fds.end()) {
|
|
if (close(fd) != 0) {
|
|
Result<void> error = ErrnoErrorf("Failed to close FD {}", fd);
|
|
if (std::filesystem::exists("/proc/self/fd/{}"_format(fd))) {
|
|
return error;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return {};
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char** argv) {
|
|
android::base::InitLogging(argv);
|
|
|
|
Options options = ParseOptions(argc, argv);
|
|
|
|
if (auto result = CloseFds(options.keep_fds); !result.ok()) {
|
|
LOG(ERROR) << "Failed to close open FDs: " << result.error();
|
|
return kErrorOther;
|
|
}
|
|
|
|
if (!options.task_profiles.empty()) {
|
|
if (int ret = PaletteSetTaskProfiles(/*tid=*/0, options.task_profiles);
|
|
ret != PALETTE_STATUS_OK) {
|
|
LOG(ERROR) << "Failed to set task profile: " << ret;
|
|
return kErrorOther;
|
|
}
|
|
}
|
|
|
|
if (options.priority.has_value()) {
|
|
if (setpriority(PRIO_PROCESS, /*who=*/0, options.priority.value()) != 0) {
|
|
PLOG(ERROR) << "Failed to setpriority";
|
|
return kErrorOther;
|
|
}
|
|
}
|
|
|
|
if (options.drop_capabilities) {
|
|
if (auto result = DropInheritableCaps(); !result.ok()) {
|
|
LOG(ERROR) << "Failed to drop inheritable capabilities: " << result.error();
|
|
return kErrorOther;
|
|
}
|
|
}
|
|
|
|
for (const auto& [key, value] : options.envs) {
|
|
setenv(key.c_str(), value.c_str(), /*overwrite=*/1);
|
|
}
|
|
|
|
execv(argv[options.command_pos], argv + options.command_pos);
|
|
|
|
std::vector<const char*> command_args(argv + options.command_pos, argv + argc);
|
|
PLOG(FATAL) << "Failed to execute (" << Join(command_args, ' ') << ")";
|
|
UNREACHABLE();
|
|
}
|