| // Copyright 2023 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include <sysexits.h> | |
| #include <algorithm> | |
| #include <iostream> | |
| #include <memory> | |
| #include <optional> | |
| #include <string> | |
| #include <base/json/json_reader.h> | |
| #include <base/json/json_writer.h> | |
| #include <base/logging.h> | |
| #include <base/files/file_path.h> | |
| #include <base/files/file_util.h> | |
| #include <base/values.h> | |
| #include <brillo/flag_helper.h> | |
| #include <chromeos/constants/imageloader.h> | |
| #include <dlcservice/metadata/metadata.h> | |
| #include <libimageloader/manifest.h> | |
| using dlcservice::metadata::Metadata; | |
| namespace { | |
| int CountExclusiveFlags(const std::vector<bool>& flags) { | |
| return std::count(std::begin(flags), std::end(flags), true); | |
| } | |
| } // namespace | |
| class DlcMetadataUtil { | |
| public: | |
| DlcMetadataUtil(int argc, const char** argv) : argc_(argc), argv_(argv) {} | |
| ~DlcMetadataUtil() = default; | |
| DlcMetadataUtil(const DlcMetadataUtil&) = delete; | |
| DlcMetadataUtil& operator=(const DlcMetadataUtil&) = delete; | |
| int Run(); | |
| private: | |
| enum Action { | |
| kGetAction, | |
| kSetAction, | |
| kListAction, | |
| }; | |
| bool ParseFlags(); | |
| int SetMetadata(); | |
| int GetMetadata(); | |
| int ListDlcIds(); | |
| std::optional<Metadata::Entry> ReadMetadataEntry(); | |
| int argc_; | |
| const char** argv_; | |
| Action action_; | |
| std::string id_; | |
| base::FilePath file_path_; | |
| Metadata::FilterKey filter_key_; | |
| base::FilePath metadata_dir_; | |
| std::unique_ptr<Metadata> metadata_; | |
| }; | |
| int DlcMetadataUtil::Run() { | |
| if (!ParseFlags()) | |
| return EX_USAGE; | |
| metadata_ = std::make_unique<Metadata>(metadata_dir_); | |
| if (!metadata_->Initialize()) { | |
| LOG(ERROR) << "Failed to initialize metadata."; | |
| return EX_SOFTWARE; | |
| } | |
| switch (action_) { | |
| case kGetAction: | |
| return GetMetadata(); | |
| case kSetAction: | |
| return SetMetadata(); | |
| case kListAction: | |
| return ListDlcIds(); | |
| } | |
| } | |
| bool DlcMetadataUtil::ParseFlags() { | |
| DEFINE_bool(get, false, "Get the metadata and print to stdout as JSON"); | |
| DEFINE_bool(set, false, "Set the metadata from input JSON"); | |
| DEFINE_bool(list, false, "List all DLC IDs, or a subset if filters is given"); | |
| DEFINE_string(id, "", "The ID of the DLC"); | |
| DEFINE_string(file, "", "Use the file instead of stdin/stdout"); | |
| DEFINE_string(metadata_dir, "", | |
| "The DLC metadata directory path. " | |
| "Manifest root path is used if not specified"); | |
| DEFINE_bool(factory_install, false, "Filter factory installed DLCs"); | |
| DEFINE_bool(powerwash_safe, false, "Filter powerwash safe DLCs"); | |
| DEFINE_bool(preload_allowed, false, "Filter preload allowed DLCs"); | |
| brillo::FlagHelper::Init(argc_, argv_, "dlc_metadata_util"); | |
| if (CountExclusiveFlags({FLAGS_get, FLAGS_set, FLAGS_list}) != 1) { | |
| LOG(ERROR) | |
| << "One of the 'get', 'set' or 'list' option should be specified."; | |
| return false; | |
| } | |
| if (FLAGS_get) { | |
| action_ = kGetAction; | |
| } else if (FLAGS_set) { | |
| action_ = kSetAction; | |
| } else if (FLAGS_list) { | |
| action_ = kListAction; | |
| } else { | |
| LOG(ERROR) << "Invalid option."; | |
| return false; | |
| } | |
| id_ = FLAGS_id; | |
| if ((FLAGS_get || FLAGS_set) && id_.empty()) { | |
| LOG(ERROR) << "DLC ID cannot be empty."; | |
| return false; | |
| } | |
| if (CountExclusiveFlags({FLAGS_factory_install, FLAGS_powerwash_safe, | |
| FLAGS_preload_allowed}) > 1) { | |
| LOG(ERROR) << "At most one filter is supported."; | |
| return false; | |
| } | |
| if (FLAGS_factory_install) { | |
| filter_key_ = Metadata::FilterKey::kFactoryInstall; | |
| } else if (FLAGS_powerwash_safe) { | |
| filter_key_ = Metadata::FilterKey::kPowerwashSafe; | |
| } else if (FLAGS_preload_allowed) { | |
| filter_key_ = Metadata::FilterKey::kPreloadAllowed; | |
| } else { | |
| filter_key_ = Metadata::FilterKey::kNone; | |
| } | |
| file_path_ = base::FilePath(FLAGS_file); | |
| if (FLAGS_metadata_dir.empty()) { | |
| metadata_dir_ = base::FilePath(imageloader::kDlcManifestRootpath); | |
| } else { | |
| metadata_dir_ = base::FilePath(FLAGS_metadata_dir); | |
| } | |
| if (!base::PathExists(metadata_dir_)) { | |
| LOG(ERROR) << "The metadata direcotry " << metadata_dir_ | |
| << " does not exists."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| int DlcMetadataUtil::GetMetadata() { | |
| auto entry = metadata_->Get(id_); | |
| if (!entry) { | |
| LOG(ERROR) << "Unable to get metadata for " << id_; | |
| return EX_SOFTWARE; | |
| } | |
| auto dict = base::Value::Dict() | |
| .Set("manifest", std::move(entry->manifest)) | |
| .Set("table", entry->table); | |
| auto json = | |
| base::WriteJsonWithOptions(dict, base::JSONWriter::OPTIONS_PRETTY_PRINT); | |
| if (!json) | |
| return EX_SOFTWARE; | |
| if (file_path_.empty()) { | |
| std::cout << *json; | |
| return EX_OK; | |
| } else { | |
| return base::WriteFile(file_path_, *json) ? EX_OK : EX_IOERR; | |
| } | |
| } | |
| int DlcMetadataUtil::SetMetadata() { | |
| auto entry = ReadMetadataEntry(); | |
| if (!entry) { | |
| LOG(ERROR) << "Failed to read or parse metadata entry."; | |
| return EX_DATAERR; | |
| } | |
| return metadata_->Set(id_, *entry) ? EX_OK : EX_SOFTWARE; | |
| } | |
| int DlcMetadataUtil::ListDlcIds() { | |
| const auto& ids = metadata_->ListDlcIds(filter_key_, base::Value(true)); | |
| auto id_list = base::Value::List::with_capacity(ids.size()); | |
| for (const auto& id : ids) | |
| id_list.Append(id); | |
| std::string json; | |
| base::JSONWriter::Write(id_list, &json); | |
| std::cout << json; | |
| return EX_OK; | |
| } | |
| std::optional<Metadata::Entry> DlcMetadataUtil::ReadMetadataEntry() { | |
| std::string metadata_str; | |
| if (file_path_.empty()) { | |
| // Read metadata entry from stdin. | |
| std::string buffer; | |
| while (std::getline(std::cin, buffer)) { | |
| metadata_str.append(buffer); | |
| } | |
| } else { | |
| if (!base::ReadFileToString(file_path_, &metadata_str)) { | |
| LOG(ERROR) << "Unable to read the metadata entry from the input file."; | |
| return std::nullopt; | |
| } | |
| } | |
| // Parse and construct metadata entry. | |
| auto metadata_val = base::JSONReader::ReadAndReturnValueWithError( | |
| metadata_str, base::JSON_PARSE_RFC); | |
| if (!metadata_val.has_value() || !metadata_val->is_dict()) { | |
| LOG(ERROR) << "Could not parse input metadata entry as JSON. Error: " | |
| << metadata_val.error().message; | |
| return std::nullopt; | |
| } | |
| Metadata::Entry entry; | |
| auto* manifest_val = metadata_val->GetDict().FindDict("manifest"); | |
| if (!manifest_val) { | |
| LOG(ERROR) << "Could not get manifest from the input"; | |
| return std::nullopt; | |
| } | |
| if (!imageloader::Manifest().ParseManifest(*manifest_val)) { | |
| LOG(ERROR) << "Could not parse manifest from the input"; | |
| return std::nullopt; | |
| } | |
| entry.manifest = std::move(*manifest_val); | |
| auto* table_val = metadata_val->GetDict().FindString("table"); | |
| if (!table_val) { | |
| LOG(ERROR) << "Could not get verity table from the input"; | |
| return std::nullopt; | |
| } | |
| entry.table = std::move(*table_val); | |
| return entry; | |
| } | |
| int main(int argc, const char** argv) { | |
| return DlcMetadataUtil(argc, argv).Run(); | |
| } |