/* * Copyright (C) 2021 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/libs/config/config_flag.h" #include #include #include #include #include #include #include #include "common/libs/utils/files.h" #include "common/libs/utils/flag_parser.h" #include "host/libs/config/cuttlefish_config.h" // To support other files that use this from gflags. DEFINE_string(system_image_dir, "", ""); using gflags::FlagSettingMode::SET_FLAGS_DEFAULT; namespace cuttlefish { namespace { class SystemImageDirFlagImpl : public SystemImageDirFlag { public: INJECT(SystemImageDirFlagImpl()) { auto help = "Location of the system partition images."; flag_ = GflagsCompatFlag("system_image_dir", path_).Help(help); } const std::string& Path() override { return path_; } std::string Name() const override { return "SystemImageDirFlagImpl"; } std::unordered_set Dependencies() const override { return {}; } bool Process(std::vector& args) override { path_ = DefaultGuestImagePath(""); if (!flag_.Parse(args)) { return false; } // To support other files that use this from gflags. FLAGS_system_image_dir = path_; gflags::SetCommandLineOptionWithMode("system_image_dir", path_.c_str(), SET_FLAGS_DEFAULT); return true; } bool WriteGflagsCompatHelpXml(std::ostream&) const override { // TODO(schuffelen): Write something here when this is removed from gflags return true; } private: std::string path_; Flag flag_; }; class ConfigReader : public FlagFeature { public: INJECT(ConfigReader()) = default; bool HasConfig(const std::string& name) const { return allowed_config_presets_.count(name) > 0; } const std::set& AvailableConfigs() const { return allowed_config_presets_; } std::optional ReadConfig(const std::string& name) const { auto path = DefaultHostArtifactsPath("etc/cvd_config/cvd_config_" + name + ".json"); Json::Value config; Json::CharReaderBuilder builder; std::ifstream ifs(path); std::string errorMessage; if (!Json::parseFromStream(builder, ifs, &config, &errorMessage)) { LOG(ERROR) << "Could not read config file " << path << ": " << errorMessage; return {}; } return config; } // FlagFeature std::string Name() const override { return "ConfigReader"; } std::unordered_set Dependencies() const override { return {}; } bool Process(std::vector&) override { for (const std::string& file : DirectoryContents(DefaultHostArtifactsPath("etc/cvd_config"))) { std::string_view local_file(file); if (android::base::ConsumePrefix(&local_file, "cvd_config_") && android::base::ConsumeSuffix(&local_file, ".json")) { allowed_config_presets_.emplace(local_file); } } return true; } bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; } private: std::set allowed_config_presets_; }; class ConfigFlagImpl : public ConfigFlag { public: INJECT(ConfigFlagImpl(ConfigReader& cr, SystemImageDirFlag& s)) : config_reader_(cr), system_image_dir_flag_(s) { is_default_ = true; config_ = "phone"; // default value auto help = "Config preset name. Will automatically set flag fields using the " "values from this file of presets. See " "device/google/cuttlefish/shared/config/config_*.json for possible " "values."; auto getter = [this]() { return config_; }; auto setter = [this](const FlagMatch& m) { return ChooseConfig(m.value); }; flag_ = GflagsCompatFlag("config").Help(help).Getter(getter).Setter(setter); } std::string Name() const override { return "ConfigFlagImpl"; } std::unordered_set Dependencies() const override { return { static_cast(&config_reader_), static_cast(&system_image_dir_flag_), }; } bool Process(std::vector& args) override { if (!flag_.Parse(args)) { LOG(ERROR) << "Failed to parse `--config` flag"; return false; } if (auto info_cfg = FindAndroidInfoConfig(); is_default_ && info_cfg) { config_ = *info_cfg; } LOG(INFO) << "Launching CVD using --config='" << config_ << "'."; auto config_values = config_reader_.ReadConfig(config_); if (!config_values) { LOG(ERROR) << "Failed to read config for " << config_; return false; } for (const std::string& flag : config_values->getMemberNames()) { std::string value; if (flag == "custom_actions") { Json::StreamWriterBuilder factory; value = Json::writeString(factory, (*config_values)[flag]); } else { value = (*config_values)[flag].asString(); } args.insert(args.begin(), "--" + flag + "=" + value); // To avoid the flag forwarder from thinking this song is different from a // default. Should fail silently if the flag doesn't exist. gflags::SetCommandLineOptionWithMode(flag.c_str(), value.c_str(), SET_FLAGS_DEFAULT); } return true; } bool WriteGflagsCompatHelpXml(std::ostream& out) const override { return flag_.WriteGflagsCompatXml(out); } private: bool ChooseConfig(const std::string& name) { if (!config_reader_.HasConfig(name)) { LOG(ERROR) << "Invalid --config option '" << name << "'. Valid options: " << android::base::Join(config_reader_.AvailableConfigs(), ","); return false; } config_ = name; is_default_ = false; return true; } std::optional FindAndroidInfoConfig() const { auto info_path = system_image_dir_flag_.Path() + "/android-info.txt"; if (!FileExists(info_path)) { return {}; } std::ifstream ifs{info_path}; if (!ifs.is_open()) { return {}; } std::string android_info; ifs >> android_info; std::string_view local_android_info(android_info); if (!android::base::ConsumePrefix(&local_android_info, "config=")) { return {}; } if (!config_reader_.HasConfig(std::string{local_android_info})) { LOG(WARNING) << info_path << " contains invalid config preset: '" << local_android_info << "'."; return {}; } return std::string{local_android_info}; } ConfigReader& config_reader_; SystemImageDirFlag& system_image_dir_flag_; std::string config_; bool is_default_; Flag flag_; }; class ConfigFlagPlaceholderImpl : public ConfigFlag { public: INJECT(ConfigFlagPlaceholderImpl()) {} std::string Name() const override { return "ConfigFlagPlaceholderImpl"; } std::unordered_set Dependencies() const override { return {}; } bool Process(std::vector&) override { return true; } bool WriteGflagsCompatHelpXml(std::ostream&) const override { return true; } }; } // namespace fruit::Component ConfigFlagComponent() { return fruit::createComponent() .addMultibinding() .bind() .addMultibinding() .bind() .addMultibinding(); } fruit::Component ConfigFlagPlaceholder() { return fruit::createComponent() .addMultibinding() .bind(); } } // namespace cuttlefish