| // 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 "dlcservice/utils/utils.h" | |
| #include <fcntl.h> | |
| #include <sys/types.h> | |
| #include <unistd.h> | |
| #include <algorithm> | |
| #include <memory> | |
| #include <string> | |
| #include <vector> | |
| #include <base/logging.h> | |
| #include <base/files/file_path.h> | |
| #include <base/files/file_util.h> | |
| #include <base/values.h> | |
| #include <crypto/secure_hash.h> | |
| #include <crypto/sha2.h> | |
| #include "dlcservice/metadata/metadata.h" | |
| using base::FilePath; | |
| using crypto::SecureHash; | |
| using std::unique_ptr; | |
| using std::vector; | |
| namespace dlcservice { | |
| const char kDlcLogicalVolumePrefix[] = "dlc_"; | |
| const char kDlcLogicalVolumeSlotA[] = "_a"; | |
| const char kDlcLogicalVolumeSlotB[] = "_b"; | |
| const char kDlcPowerwashSafeFile[] = "/opt/google/dlc/_powerwash_safe_"; | |
| const char kPackage[] = "package"; | |
| const char kManifestName[] = "imageloader.json"; | |
| constexpr char kDlcDaemonStorePath[] = "/run/daemon-store-cache/dlcservice"; | |
| constexpr char kDlcRedactedPackage[] = "<REDACTED_PKG>"; | |
| std::string Utils::LogicalVolumeName(const std::string& id, | |
| PartitionSlot slot) { | |
| static const std::string& kPrefix(kDlcLogicalVolumePrefix); | |
| switch (slot) { | |
| case PartitionSlot::A: | |
| return kPrefix + id + kDlcLogicalVolumeSlotA; | |
| case PartitionSlot::B: | |
| return kPrefix + id + kDlcLogicalVolumeSlotB; | |
| } | |
| } | |
| std::string LogicalVolumeName(const std::string& id, | |
| PartitionSlot slot, | |
| std::unique_ptr<UtilsInterface> utils) { | |
| return utils->LogicalVolumeName(id, slot); | |
| } | |
| std::string Utils::LogicalVolumeNameToId(const std::string& lv_name) { | |
| if (!lv_name.starts_with(kDlcLogicalVolumePrefix)) { | |
| return ""; | |
| } | |
| std::string id; | |
| id = lv_name.substr(strlen(kDlcLogicalVolumePrefix)); | |
| if (!id.ends_with(kDlcLogicalVolumeSlotA) && | |
| !id.ends_with(kDlcLogicalVolumeSlotB)) { | |
| return ""; | |
| } | |
| return id.substr(0, id.size() - strlen(kDlcLogicalVolumeSlotA)); | |
| } | |
| bool Utils::HashFile(const base::FilePath& path, | |
| int64_t size, | |
| vector<uint8_t>* sha256, | |
| bool skip_size_check) { | |
| base::File f(path, base::File::FLAG_OPEN | base::File::FLAG_READ); | |
| if (!f.IsValid()) { | |
| PLOG(ERROR) << "Failed to read file at " << path.value() | |
| << ", reason: " << base::File::ErrorToString(f.error_details()); | |
| return false; | |
| } | |
| if (!skip_size_check) { | |
| auto length = f.GetLength(); | |
| if (length < 0) { | |
| LOG(ERROR) << "Failed to get length for file at " << path.value(); | |
| return false; | |
| } | |
| if (length < size) { | |
| LOG(ERROR) << "File size " << length | |
| << " is smaller than intended file size " << size; | |
| return false; | |
| } | |
| } | |
| constexpr int64_t kMaxBufSize = 4096; | |
| unique_ptr<SecureHash> hash(SecureHash::Create(SecureHash::SHA256)); | |
| vector<char> buf(kMaxBufSize); | |
| for (; size > 0; size -= kMaxBufSize) { | |
| int bytes = std::min(kMaxBufSize, size); | |
| if (f.ReadAtCurrentPos(buf.data(), bytes) != bytes) { | |
| PLOG(ERROR) << "Failed to read from file at " << path.value(); | |
| return false; | |
| } | |
| hash->Update(buf.data(), bytes); | |
| } | |
| sha256->resize(crypto::kSHA256Length); | |
| hash->Finish(sha256->data(), sha256->size()); | |
| return true; | |
| } | |
| bool HashFile(const base::FilePath& path, | |
| int64_t size, | |
| vector<uint8_t>* sha256, | |
| bool skip_size_check, | |
| std::unique_ptr<UtilsInterface> utils) { | |
| return utils->HashFile(path, size, sha256, skip_size_check); | |
| } | |
| std::shared_ptr<imageloader::Manifest> Utils::GetDlcManifestFallback( | |
| const FilePath& dlc_manifest_path, | |
| const std::string& id, | |
| const std::string& package) { | |
| std::string dlc_json_str; | |
| const auto& dlc_manifest_file = | |
| dlc_manifest_path.Append(id).Append(package).Append(kManifestName); | |
| if (!base::ReadFileToString(dlc_manifest_file, &dlc_json_str)) { | |
| LOG(ERROR) << "Failed to read DLC manifest file '" | |
| << dlc_manifest_file.value() << "'."; | |
| return nullptr; | |
| } | |
| auto manifest = std::make_shared<imageloader::Manifest>(); | |
| if (!manifest->ParseManifest(dlc_json_str)) { | |
| LOG(ERROR) << "Failed to parse DLC manifest for DLC:" << id << "."; | |
| return nullptr; | |
| } | |
| return manifest; | |
| } | |
| std::shared_ptr<imageloader::Manifest> Utils::GetDlcManifest( | |
| const std::string& id, const base::FilePath& dlc_manifest_path) { | |
| InitializeDlcMetadata(dlc_manifest_path); | |
| if (auto manifest = GetDlcManifestInternal(id)) { | |
| return manifest; | |
| } else { | |
| return GetDlcManifestFallback(dlc_manifest_path, id, kPackage); | |
| } | |
| } | |
| std::shared_ptr<imageloader::Manifest> Utils::GetDlcManifestInternal( | |
| const std::string& id) { | |
| if (!metadata_) | |
| return nullptr; | |
| auto entry = metadata_->Get(id); | |
| if (!entry) { | |
| LOG(ERROR) << "Failed to get metadata for DLC=" << id; | |
| return nullptr; | |
| } | |
| auto manifest = std::make_shared<imageloader::Manifest>(); | |
| if (!manifest->ParseManifest(entry->manifest)) { | |
| LOG(ERROR) << "Failed to parse manifest for DLC=" << id; | |
| return nullptr; | |
| } | |
| return manifest; | |
| } | |
| std::shared_ptr<imageloader::Manifest> GetDlcManifest( | |
| const std::string& id, | |
| const FilePath& dlc_manifest_path, | |
| std::unique_ptr<UtilsInterface> utils) { | |
| return utils->GetDlcManifest(id, dlc_manifest_path); | |
| } | |
| DlcIdList Utils::GetSupportedDlcIds(const base::FilePath& metadata_path) { | |
| InitializeDlcMetadata(metadata_path); | |
| if (metadata_) { | |
| return metadata_->ListDlcIds(metadata::Metadata::FilterKey::kNone, | |
| base::Value()); | |
| } | |
| return {}; | |
| } | |
| bool Utils::InitializeDlcMetadata(const base::FilePath& path) { | |
| if (metadata_) | |
| return true; | |
| metadata_ = std::make_unique<metadata::Metadata>(path); | |
| if (!metadata_->Initialize()) { | |
| metadata_.reset(); | |
| LOG(ERROR) << "Failed to initialize the DLC metadata."; | |
| return false; | |
| } | |
| return true; | |
| } | |
| base::FilePath Utils::MakeAbsoluteFilePath(const base::FilePath& path) { | |
| return base::MakeAbsoluteFilePath(path); | |
| } | |
| bool Utils::WaitForGid(const base::FilePath& target_path, int target_gid) { | |
| base::stat_wrapper_t st = {0}; | |
| for (int loop_cnt = 0; st.st_gid != target_gid; ++loop_cnt) { | |
| // 3s (10ms * 300) limit. | |
| if (loop_cnt >= 300) { | |
| LOG(ERROR) << "Failed check within threshold for path=" << target_path; | |
| return false; | |
| } | |
| if (base::File::Stat(target_path, &st) < 0) { | |
| PLOG(ERROR) << "Failed to stat path=" << target_path; | |
| return false; | |
| } | |
| // 10ms delay. | |
| usleep(10 * 1000); | |
| } | |
| return true; | |
| } | |
| } // namespace dlcservice |