blob: 6819f9eca483443727c4ee18c29c0e37678bc4ca [file] [log] [blame]
/*
* 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/frontline_parser.h"
#include <sstream>
#include <type_traits>
#include <vector>
#include <android-base/file.h>
#include <android-base/strings.h>
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/flag_parser.h"
#include "host/commands/cvd/selector/selector_constants.h"
namespace cuttlefish {
struct BoolFlags {
std::unordered_set<std::string> selector_flags;
std::unordered_set<std::string> cvd_driver_flags;
};
struct ValueFlags {
std::unordered_set<std::string> selector_flags;
std::unordered_set<std::string> cvd_driver_flags;
};
static const BoolFlags bool_flags{
.selector_flags = {selector::kDisableDefaultGroupOpt},
.cvd_driver_flags = {"clean", "help"}};
static const ValueFlags value_flags{
.selector_flags = {selector::kGroupNameOpt, selector::kInstanceNameOpt},
.cvd_driver_flags = {}};
static std::unordered_set<std::string> Merge(
const std::unordered_set<std::string>& s1,
const std::unordered_set<std::string>& s2) {
std::unordered_set<std::string> out{s1};
out.insert(s2.begin(), s2.end());
return out;
}
Result<std::unique_ptr<FrontlineParser>> FrontlineParser::Parse(
CvdClient& client, const cvd_common::Args& all_args_orig,
const cvd_common::Envs& envs) {
cvd_common::Args all_args{all_args_orig};
CF_EXPECT(!all_args.empty());
// TODO(kwstephenkim): implement these ad-hoc help checking in the
// parser.
if (android::base::Basename(all_args[0]) == "cvd") {
if (all_args.size() == 1) {
all_args.emplace_back("--help");
}
if (all_args.at(1) == "-h") {
all_args[1] = "--help";
}
}
FrontlineParser* frontline_parser =
new FrontlineParser(client, all_args, envs);
CF_EXPECT(frontline_parser != nullptr,
"Memory allocation for FrontlineParser failed.");
CF_EXPECT(frontline_parser->Separate());
return std::unique_ptr<FrontlineParser>(frontline_parser);
}
FrontlineParser::FrontlineParser(
CvdClient& client, const cvd_common::Args& all_args,
const std::unordered_map<std::string, std::string>& envs)
: client_(client), all_args_(all_args), envs_{envs} {
known_bool_flags_ =
Merge(bool_flags.selector_flags, bool_flags.cvd_driver_flags);
known_value_flags_ =
Merge(value_flags.selector_flags, value_flags.cvd_driver_flags);
selector_flags_ =
Merge(bool_flags.selector_flags, value_flags.selector_flags);
}
Result<void> FrontlineParser::Separate() {
valid_subcmds_ = CF_EXPECT(ValidSubcmdsList());
arguments_separator_ = CF_EXPECT(CallSeparator());
auto filtered_output = CF_EXPECT(FilterNonSelectorArgs());
clean_ = filtered_output.clean;
help_ = filtered_output.help;
selector_args_ = std::move(filtered_output.selector_args);
return {};
}
Result<cvd_common::Args> FrontlineParser::ValidSubcmdsList() {
auto valid_subcmd_json = CF_EXPECT(ListSubcommands());
CF_EXPECT(valid_subcmd_json.isMember("subcmd"),
"Server returned the list of subcommands in Json but it is missing "
<< " \"subcmd\" field");
std::string valid_subcmd_string = valid_subcmd_json["subcmd"].asString();
auto valid_subcmds = android::base::Tokenize(valid_subcmd_string, ",");
cvd_common::Args cvd_client_internal_commands{"kill-server", "server-kill"};
std::copy(cvd_client_internal_commands.begin(),
cvd_client_internal_commands.end(),
std::back_inserter(valid_subcmds));
return valid_subcmds;
}
Result<std::unique_ptr<selector::ArgumentsSeparator>>
FrontlineParser::CallSeparator() {
std::unordered_set<std::string> valid_subcmds{valid_subcmds_.begin(),
valid_subcmds_.end()};
ArgumentsSeparator::FlagsRegistration flag_registration{
.known_boolean_flags = known_bool_flags_,
.known_value_flags = known_value_flags_,
.valid_subcommands = valid_subcmds};
auto arguments_separator =
CF_EXPECT(ArgumentsSeparator::Parse(flag_registration, all_args_));
CF_EXPECT(arguments_separator != nullptr);
return arguments_separator;
}
const std::string& FrontlineParser::ProgPath() const {
return arguments_separator_->ProgPath();
}
std::optional<std::string> FrontlineParser::SubCmd() const {
return arguments_separator_->SubCmd();
}
const cvd_common::Args& FrontlineParser::SubCmdArgs() const {
return arguments_separator_->SubCmdArgs();
}
Result<FrontlineParser::FilterOutput> FrontlineParser::FilterNonSelectorArgs() {
auto cvd_args = arguments_separator_->CvdArgs();
bool clean = false;
bool help = false;
std::vector<Flag> gflags;
gflags.emplace_back(GflagsCompatFlag("clean", clean));
gflags.emplace_back(GflagsCompatFlag("help", help));
CF_EXPECT(ParseFlags(gflags, cvd_args));
FilterOutput output{
.clean = clean, .help = help, .selector_args = std::move(cvd_args)};
return output;
}
const cvd_common::Args& FrontlineParser::SelectorArgs() const {
return selector_args_;
}
Result<Json::Value> FrontlineParser::ListSubcommands() {
std::vector<std::string> args{"cvd", "cmd-list"};
SharedFD read_pipe, write_pipe;
CF_EXPECT(cuttlefish::SharedFD::Pipe(&read_pipe, &write_pipe),
"Unable to create shutdown pipe: " << strerror(errno));
OverrideFd new_control_fd{.stdout_override_fd = write_pipe};
CF_EXPECT(client_.HandleCommand(args, envs_, std::vector<std::string>{},
new_control_fd));
write_pipe->Close();
const int kChunkSize = 512;
char buf[kChunkSize + 1] = {0};
std::stringstream ss;
do {
auto n_read = ReadExact(read_pipe, buf, kChunkSize);
CF_EXPECT(n_read >= 0 && (n_read <= kChunkSize));
if (n_read == 0) {
break;
}
buf[n_read] = 0; // null-terminate the C-style string
ss << buf;
if (n_read < sizeof(buf) - 1) {
break;
}
} while (true);
auto json_output = CF_EXPECT(ParseJson(ss.str()));
return json_output;
}
} // namespace cuttlefish