blob: cf6566e129beb6059ed03728c657d3409c6a45e4 [file] [log] [blame] [edit]
/*
* 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 "host/commands/cvd/server_command/utils.h"
#include <fmt/core.h>
#include "common/libs/fs/shared_buf.h"
#include "common/libs/utils/contains.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/users.h"
#include "host/commands/cvd/instance_manager.h"
#include "host/commands/cvd/server.h"
#include "host/libs/config/config_constants.h"
namespace cuttlefish {
CommandInvocation ParseInvocation(const cvd::Request& request) {
CommandInvocation invocation;
if (request.contents_case() != cvd::Request::ContentsCase::kCommandRequest) {
return invocation;
}
if (request.command_request().args_size() == 0) {
return invocation;
}
for (const std::string& arg : request.command_request().args()) {
invocation.arguments.push_back(arg);
}
invocation.arguments[0] = cpp_basename(invocation.arguments[0]);
if (invocation.arguments[0] == "cvd") {
invocation.command = invocation.arguments[1];
invocation.arguments.erase(invocation.arguments.begin());
invocation.arguments.erase(invocation.arguments.begin());
} else {
invocation.command = invocation.arguments[0];
invocation.arguments.erase(invocation.arguments.begin());
}
return invocation;
}
Result<void> VerifyPrecondition(const RequestWithStdio& request) {
CF_EXPECT(
Contains(request.Message().command_request().env(), kAndroidHostOut),
"ANDROID_HOST_OUT in client environment is invalid.");
return {};
}
cuttlefish::cvd::Response ResponseFromSiginfo(siginfo_t infop) {
cvd::Response response;
response.mutable_command_response(); // set oneof field
auto& status = *response.mutable_status();
if (infop.si_code == CLD_EXITED && infop.si_status == 0) {
status.set_code(cvd::Status::OK);
return response;
}
status.set_code(cvd::Status::INTERNAL);
std::string status_code_str = std::to_string(infop.si_status);
if (infop.si_code == CLD_EXITED) {
status.set_message("Exited with code " + status_code_str);
} else if (infop.si_code == CLD_KILLED) {
status.set_message("Exited with signal " + status_code_str);
} else {
status.set_message("Quit with code " + status_code_str);
}
return response;
}
Result<Command> ConstructCommand(const ConstructCommandParam& param) {
Command command(param.command_name);
command.SetExecutable(param.bin_path);
for (const std::string& arg : param.args) {
command.AddParameter(arg);
}
// Set CuttlefishConfig path based on assembly dir,
// used by subcommands when locating the CuttlefishConfig.
if (param.envs.count(cuttlefish::kCuttlefishConfigEnvVarName) == 0) {
auto config_path = InstanceManager::GetCuttlefishConfigPath(param.home);
if (config_path.ok()) {
command.AddEnvironmentVariable(cuttlefish::kCuttlefishConfigEnvVarName,
*config_path);
}
}
for (auto& it : param.envs) {
command.UnsetFromEnvironment(it.first);
command.AddEnvironmentVariable(it.first, it.second);
}
// Redirect stdin, stdout, stderr back to the cvd client
command.RedirectStdIO(Subprocess::StdIOChannel::kStdIn, param.in);
command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, param.out);
command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, param.err);
if (!param.working_dir.empty()) {
auto fd =
SharedFD::Open(param.working_dir, O_RDONLY | O_PATH | O_DIRECTORY);
CF_EXPECT(fd->IsOpen(), "Couldn't open \"" << param.working_dir
<< "\": " << fd->StrError());
command.SetWorkingDirectory(fd);
}
return {std::move(command)};
}
Result<Command> ConstructCvdHelpCommand(
const std::string& bin_file, cvd_common::Envs envs,
const std::vector<std::string>& subcmd_args,
const RequestWithStdio& request) {
const auto host_artifacts_path = envs.at("ANDROID_HOST_OUT");
const auto bin_path = host_artifacts_path + "/bin/" + bin_file;
auto client_pwd = request.Message().command_request().working_directory();
const auto home = (Contains(envs, "HOME") ? envs.at("HOME") : client_pwd);
cvd_common::Envs envs_copy{envs};
envs_copy["HOME"] = AbsolutePath(home);
envs[kAndroidSoongHostOut] = envs.at(kAndroidHostOut);
ConstructCommandParam construct_cmd_param{.bin_path = bin_path,
.home = home,
.args = subcmd_args,
.envs = std::move(envs_copy),
.working_dir = client_pwd,
.command_name = bin_file,
.in = request.In(),
.out = request.Out(),
.err = request.Err()};
Command help_command = CF_EXPECT(ConstructCommand(construct_cmd_param));
return help_command;
}
Result<Command> ConstructCvdGenericNonHelpCommand(
const ConstructNonHelpForm& request_form, const RequestWithStdio& request) {
cvd_common::Envs envs{request_form.envs};
envs["HOME"] = request_form.home;
envs[kAndroidHostOut] = request_form.android_host_out;
envs[kAndroidSoongHostOut] = request_form.android_host_out;
const auto bin_path = ConcatToString(request_form.android_host_out, "/bin/",
request_form.bin_file);
if (request_form.verbose) {
std::stringstream verbose_stream;
verbose_stream << "HOME=" << request_form.home << " ";
verbose_stream << kAndroidHostOut << "=" << envs.at(kAndroidHostOut) << " "
<< kAndroidSoongHostOut << "="
<< envs.at(kAndroidSoongHostOut) << " ";
verbose_stream << bin_path << "\\" << std::endl;
for (const auto& cmd_arg : request_form.cmd_args) {
verbose_stream << cmd_arg << " ";
}
if (!request_form.cmd_args.empty()) {
// remove trailing " ", and add a new line
verbose_stream.seekp(-1, std::ios_base::end);
verbose_stream << std::endl;
}
WriteAll(request.Err(), verbose_stream.str());
}
ConstructCommandParam construct_cmd_param{
.bin_path = bin_path,
.home = request_form.home,
.args = request_form.cmd_args,
.envs = envs,
.working_dir = request.Message().command_request().working_directory(),
.command_name = request_form.bin_file,
.in = request.In(),
.out = request.Out(),
.err = request.Err()};
return CF_EXPECT(ConstructCommand(construct_cmd_param));
}
/*
* From external/gflags/src, commit:
* 061f68cd158fa658ec0b9b2b989ed55764870047
*
*/
constexpr static std::array help_bool_opts{
"help", "helpfull", "helpshort", "helppackage", "helpxml", "version"};
constexpr static std::array help_str_opts{
"helpon",
"helpmatch",
};
Result<bool> IsHelpSubcmd(const std::vector<std::string>& args) {
std::vector<std::string> copied_args(args);
std::vector<Flag> flags;
flags.reserve(help_bool_opts.size() + help_str_opts.size());
bool bool_value_placeholder = false;
std::string str_value_placeholder;
for (const auto bool_opt : help_bool_opts) {
flags.emplace_back(GflagsCompatFlag(bool_opt, bool_value_placeholder));
}
for (const auto str_opt : help_str_opts) {
flags.emplace_back(GflagsCompatFlag(str_opt, str_value_placeholder));
}
CF_EXPECT(ParseFlags(flags, copied_args));
// if there was any match, some in copied_args were consumed.
return (args.size() != copied_args.size());
}
static constexpr char kTerminalBoldRed[] = "\033[0;1;31m";
static constexpr char kTerminalCyan[] = "\033[0;36m";
static constexpr char kTerminalRed[] = "\033[0;31m";
static constexpr char kTerminalReset[] = "\033[0m";
Result<cvd::Response> NoGroupResponse(const RequestWithStdio& request) {
cvd::Response response;
response.mutable_command_response();
response.mutable_status()->set_code(cvd::Status::OK);
const uid_t uid = CF_EXPECT(request.Credentials()).uid;
auto notice = fmt::format(
"Command `{}{}{}` is not applicable: {}{}{} (uid: '{}{}{}')",
kTerminalRed, fmt::join(request.Message().command_request().args(), " "),
kTerminalReset, kTerminalBoldRed, "no device", kTerminalReset,
kTerminalCyan, uid, kTerminalReset);
CF_EXPECT_EQ(WriteAll(request.Out(), notice + "\n"), notice.size() + 1);
response.mutable_status()->set_message(notice);
return response;
}
} // namespace cuttlefish