| /* |
| * 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/parser/load_configs_parser.h" |
| |
| #include <unistd.h> |
| |
| #include <chrono> |
| #include <cstdio> |
| #include <fstream> |
| #include <string> |
| #include <vector> |
| |
| #include <android-base/file.h> |
| #include <android-base/parseint.h> |
| #include <android-base/strings.h> |
| #include <fmt/format.h> |
| #include <json/json.h> |
| |
| #include "common/libs/utils/files.h" |
| #include "common/libs/utils/flag_parser.h" |
| #include "common/libs/utils/json.h" |
| #include "common/libs/utils/result.h" |
| #include "host/commands/cvd/parser/cf_configs_common.h" |
| #include "host/commands/cvd/parser/cf_flags_validator.h" |
| #include "host/commands/cvd/parser/fetch_cvd_parser.h" |
| #include "host/commands/cvd/parser/launch_cvd_parser.h" |
| #include "host/commands/cvd/parser/selector_parser.h" |
| |
| namespace cuttlefish { |
| namespace { |
| |
| constexpr std::string_view kOverrideSeparator = ":"; |
| constexpr std::string_view kCredentialSourceOverride = |
| "fetch.credential_source"; |
| |
| Flag GflagsCompatFlagOverride(const std::string& name, |
| std::vector<Override>& values) { |
| return GflagsCompatFlag(name) |
| .Getter([&values]() { return android::base::Join(values, ','); }) |
| .Setter([&values](const FlagMatch& match) -> Result<void> { |
| std::size_t separator_index = match.value.find(kOverrideSeparator); |
| CF_EXPECTF(separator_index != std::string::npos, |
| "Unable to find separator \"{}\" in input \"{}\"", |
| kOverrideSeparator, match.value); |
| auto result = |
| Override{.config_path = match.value.substr(0, separator_index), |
| .new_value = match.value.substr(separator_index + 1)}; |
| CF_EXPECTF(!result.config_path.empty(), |
| "Config path before the separator \"{}\" cannot be empty in " |
| "input \"{}\"", |
| kOverrideSeparator, match.value); |
| CF_EXPECTF(!result.new_value.empty(), |
| "New value after the separator \"{}\" cannot be empty in " |
| "input \"{}\"", |
| kOverrideSeparator, match.value); |
| CF_EXPECTF(result.config_path.front() != '.' && |
| result.config_path.back() != '.', |
| "Config path \"{}\" must not start or end with dot", |
| result.config_path); |
| CF_EXPECTF(result.config_path.find("..") == std::string::npos, |
| "Config path \"{}\" cannot contain two consecutive dots", |
| result.config_path); |
| values.emplace_back(result); |
| return {}; |
| }); |
| } |
| |
| // TODO(moelsherif): expand this enum in the future to support more types ( |
| // double , float , etc) if neeeded |
| enum ArgValueType { UINTEGER, BOOLEAN, TEXT }; |
| |
| bool IsUnsignedInteger(const std::string& str) { |
| return !str.empty() && std::all_of(str.begin(), str.end(), |
| [](char c) { return std::isdigit(c); }); |
| } |
| |
| ArgValueType GetArgValueType(const std::string& str) { |
| if (IsUnsignedInteger(str)) { |
| return UINTEGER; |
| } |
| |
| if (str == "true" || str == "false") { |
| return BOOLEAN; |
| } |
| |
| // Otherwise, treat the string as text |
| return TEXT; |
| } |
| |
| Json::Value OverrideToJson(const std::string& key, |
| const std::string& leafValue) { |
| std::stack<std::string> levels; |
| std::stringstream ks(key); |
| std::string token; |
| while (std::getline(ks, token, '.')) { |
| levels.push(token); |
| } |
| |
| // assign the leaf value based on the type of input value |
| Json::Value leaf; |
| if (GetArgValueType(leafValue) == UINTEGER) { |
| std::uint32_t leaf_val{}; |
| if (!android::base::ParseUint(leafValue, &leaf_val)) { |
| LOG(ERROR) << "Failed to parse unsigned integer " << leafValue; |
| return Json::Value::null; |
| }; |
| leaf = leaf_val; |
| } else if (GetArgValueType(leafValue) == BOOLEAN) { |
| leaf = (leafValue == "true"); |
| } else { |
| leaf = leafValue; |
| } |
| |
| while (!levels.empty()) { |
| Json::Value curr; |
| std::string index = levels.top(); |
| |
| if (GetArgValueType(index) == UINTEGER) { |
| std::uint32_t index_val{}; |
| if (!android::base::ParseUint(index, &index_val)) { |
| LOG(ERROR) << "Failed to parse unsigned integer " << index; |
| return Json::Value::null; |
| } |
| curr[index_val] = leaf; |
| } else { |
| curr[index] = leaf; |
| } |
| |
| leaf = curr; |
| levels.pop(); |
| } |
| |
| return leaf; |
| } |
| |
| std::vector<Flag> GetFlagsVector(LoadFlags& load_flags) { |
| std::vector<Flag> flags; |
| flags.emplace_back(GflagsCompatFlag("help", load_flags.help)); |
| flags.emplace_back( |
| GflagsCompatFlag("credential_source", load_flags.credential_source)); |
| flags.emplace_back( |
| GflagsCompatFlag("base_directory", load_flags.base_dir) |
| .Help("Parent directory for artifacts and runtime files. Defaults to " |
| "/tmp/cvd/<uid>/<timestamp>.")); |
| flags.emplace_back(GflagsCompatFlagOverride("override", load_flags.overrides) |
| .Help("Use --override=<config_identifier>:<new_value> " |
| "to override config values")); |
| return flags; |
| } |
| |
| std::string DefaultBaseDir() { |
| auto time = std::chrono::system_clock::now().time_since_epoch().count(); |
| std::stringstream ss; |
| ss << "/tmp/cvd/" << getuid() << "/" << time; |
| return ss.str(); |
| } |
| |
| void MakeAbsolute(std::string& path, const std::string& working_dir) { |
| if (path.size() > 0 && path[0] == '/') { |
| return; |
| } |
| path.insert(0, working_dir + "/"); |
| } |
| |
| } // namespace |
| |
| Result<LoadFlags> GetFlags(std::vector<std::string>& args, |
| const std::string& working_directory) { |
| LoadFlags load_flags; |
| auto flags = GetFlagsVector(load_flags); |
| CF_EXPECT(ParseFlags(flags, args)); |
| CF_EXPECT(load_flags.help || args.size() > 0, |
| "No arguments provided to cvd load command, please provide at " |
| "least one argument (help or path to json file)"); |
| |
| if (load_flags.base_dir.empty()) { |
| load_flags.base_dir = DefaultBaseDir(); |
| } |
| MakeAbsolute(load_flags.base_dir, working_directory); |
| |
| load_flags.config_path = args.front(); |
| MakeAbsolute(load_flags.config_path, working_directory); |
| |
| if (!load_flags.credential_source.empty()) { |
| for (const auto& flag : load_flags.overrides) { |
| CF_EXPECT(!android::base::StartsWith(flag.config_path, |
| kCredentialSourceOverride), |
| "Specifying both --override=fetch.credential_source and the " |
| "--credential_source flag is not allowed."); |
| } |
| load_flags.overrides.emplace_back( |
| Override{.config_path = std::string(kCredentialSourceOverride), |
| .new_value = load_flags.credential_source}); |
| } |
| return load_flags; |
| } |
| |
| Result<Json::Value> ParseJsonFile(const std::string& file_path) { |
| CF_EXPECTF(FileExists(file_path), |
| "Provided file \"{}\" to cvd load does not exist", file_path); |
| |
| std::string file_content; |
| using android::base::ReadFileToString; |
| CF_EXPECTF(ReadFileToString(file_path.c_str(), &file_content, |
| /* follow_symlinks */ true), |
| "Failed to read file \"{}\"", file_path); |
| auto root = CF_EXPECTF(ParseJson(file_content), |
| "Failed parsing file \"{}\" as JSON", file_path); |
| return root; |
| } |
| |
| Result<Json::Value> GetOverriddenConfig( |
| const std::string& config_path, |
| const std::vector<Override>& override_flags) { |
| Json::Value result = CF_EXPECT(ParseJsonFile(config_path)); |
| |
| if (override_flags.size() > 0) { |
| for (const auto& flag : override_flags) { |
| MergeTwoJsonObjs(result, |
| OverrideToJson(flag.config_path, flag.new_value)); |
| } |
| } |
| |
| return result; |
| } |
| |
| std::ostream& operator<<(std::ostream& out, const Override& override) { |
| fmt::print(out, "(config_path=\"{}\", new_value=\"{}\")", |
| override.config_path, override.new_value); |
| return out; |
| } |
| |
| Result<LoadDirectories> GenerateLoadDirectories(const std::string& parent_directory, |
| const int num_instances) { |
| CF_EXPECT_GT(num_instances, 0, "No instances in config to load"); |
| auto result = LoadDirectories{ |
| .target_directory = parent_directory + "/artifacts", |
| .launch_home_directory = parent_directory + "/home", |
| }; |
| |
| std::vector<std::string> system_image_directories; |
| for (int i = 0; i < num_instances; i++) { |
| LOG(INFO) << "Instance " << i << " directory is " << result.target_directory |
| << "/" << std::to_string(i); |
| auto target_subdirectory = std::to_string(i); |
| result.target_subdirectories.emplace_back(target_subdirectory); |
| system_image_directories.emplace_back(result.target_directory + "/" + |
| target_subdirectory); |
| } |
| result.host_package_directory = |
| result.target_directory + "/" + result.target_subdirectories[0]; |
| result.system_image_directory_flag = |
| "--system_image_dir=" + |
| android::base::Join(system_image_directories, ','); |
| return result; |
| } |
| |
| Result<CvdFlags> ParseCvdConfigs(Json::Value& root, |
| const LoadDirectories& load_directories) { |
| CF_EXPECT(ValidateCfConfigs(root), "Loaded Json validation failed"); |
| return CvdFlags{.launch_cvd_flags = CF_EXPECT(ParseLaunchCvdConfigs(root)), |
| .selector_flags = CF_EXPECT(ParseSelectorConfigs(root)), |
| .fetch_cvd_flags = CF_EXPECT(ParseFetchCvdConfigs( |
| root, load_directories.target_directory, |
| load_directories.target_subdirectories))}; |
| } |
| |
| } // namespace cuttlefish |