| // 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 "minios/log_store_manifest.h" | |
| #include <algorithm> | |
| #include <array> | |
| #include <cstdint> | |
| #include <optional> | |
| #include <string> | |
| #include <base/logging.h> | |
| #include <base/strings/stringprintf.h> | |
| namespace minios { | |
| const uint64_t kLogStoreMagic = 0x13577531; | |
| const uint64_t kBlockSize = 512; | |
| // Offset from end of partition. Location for storing manifest. | |
| const uint64_t kDefaultManifestStoreOffset = 2; | |
| LogStoreManifest::LogStoreManifest(base::FilePath disk_path, | |
| uint64_t kernel_size, | |
| uint64_t partition_size) | |
| : disk_path_(disk_path), | |
| kernel_size_(kernel_size), | |
| partition_size_(partition_size), | |
| manifest_store_start_(partition_size_ - | |
| (kDefaultManifestStoreOffset * kBlockSize)) { | |
| // Verify sanity of kernel size. Kernel MUST end before manifest blocks to | |
| // avoid corruption. In case of invalid offset, set valid flag to false to | |
| // disable reading and writing later on. | |
| if (kernel_size_ > manifest_store_start_) { | |
| LOG(ERROR) << base::StringPrintf( | |
| "Invalid kernel size, disabling manifest storage. kernel_size: %ld, " | |
| "partition_size: %ld, " | |
| "manifest_store_location: %ld", | |
| kernel_size_, partition_size_, manifest_store_start_); | |
| SetValid(false); | |
| } | |
| if (partition_size % kBlockSize != 0) { | |
| LOG(ERROR) << "Partition is not block aligned, disabling storage. " | |
| "Partition size: " | |
| << partition_size_; | |
| SetValid(false); | |
| } | |
| if (disk_path_.empty()) { | |
| LOG(ERROR) << "Disabling manifest storage due to empty disk path"; | |
| SetValid(false); | |
| } | |
| } | |
| bool LogStoreManifest::Generate(const LogManifest::Entry& entry) { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Ignoring manifest generate due to bad params."; | |
| return false; | |
| } | |
| // Fill out manifest and store it for a future write. | |
| manifest_.emplace(); | |
| auto* log_entry = manifest_->mutable_entry(); | |
| log_entry->CopyFrom(entry); | |
| return true; | |
| } | |
| std::optional<LogManifest> LogStoreManifest::Retrieve() { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Ignoring manifest retrieve due to bad params."; | |
| return std::nullopt; | |
| } | |
| base::File disk(disk_path_, base::File::FLAG_OPEN | base::File::FLAG_READ); | |
| if (!disk.IsValid()) { | |
| PLOG(ERROR) << "Failed to open disk to retrieve from: " << disk_path_; | |
| return std::nullopt; | |
| } | |
| disk_manifest_location_ = FindManifestMagic(disk); | |
| if (disk_manifest_location_) { | |
| // Skip ahead size of Magic to reach the serialized manifest. | |
| disk.Seek(base::File::FROM_BEGIN, | |
| disk_manifest_location_.value() + sizeof(kLogStoreMagic)); | |
| LogManifest manifest; | |
| std::string serialized_manifest; | |
| auto max_manifest_size = | |
| partition_size_ - | |
| (disk_manifest_location_.value() + sizeof(kLogStoreMagic)); | |
| serialized_manifest.resize(max_manifest_size); | |
| disk.ReadAtCurrentPos(serialized_manifest.data(), max_manifest_size); | |
| manifest.ParseFromString(serialized_manifest); | |
| return manifest; | |
| } | |
| LOG(INFO) << "No manifest found on disk."; | |
| return std::nullopt; | |
| } | |
| bool LogStoreManifest::Write() { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Ignoring manifest write due to bad params."; | |
| return false; | |
| } | |
| if (!manifest_) { | |
| LOG(ERROR) << "Log store manifest has not been generated!"; | |
| return false; | |
| } | |
| base::File disk(disk_path_, base::File::FLAG_OPEN | base::File::FLAG_WRITE); | |
| if (!disk.IsValid()) { | |
| PLOG(ERROR) << "Failed to open disk to write to: " << disk_path_; | |
| return false; | |
| } | |
| // If manifest had been generated, write it to disk. | |
| disk.Seek(base::File::FROM_BEGIN, manifest_store_start_); | |
| // Write magic block header. | |
| disk.WriteAtCurrentPos(reinterpret_cast<const char*>(&kLogStoreMagic), | |
| sizeof(kLogStoreMagic)); | |
| auto serialized_manifest = manifest_->SerializeAsString(); | |
| disk.WriteAtCurrentPos(serialized_manifest.c_str(), | |
| serialized_manifest.size()); | |
| // Flush and close disk stream. | |
| if (!disk.Flush()) { | |
| LOG(ERROR) << "Failed to flush manifest to device: " << disk_path_ | |
| << " error: " << disk.GetLastFileError(); | |
| return false; | |
| } | |
| return true; | |
| } | |
| void LogStoreManifest::Clear() { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Ignoring manifest clear due to bad params."; | |
| return; | |
| } | |
| base::File disk(disk_path_, base::File::FLAG_OPEN | base::File::FLAG_WRITE | | |
| base::File::FLAG_READ); | |
| if (!disk.IsValid()) { | |
| PLOG(ERROR) << "Failed to open disk to clear: " << disk_path_; | |
| return; | |
| } | |
| if (!disk_manifest_location_) { | |
| // If a manifest location isn't set, search the partition for a manifest. | |
| disk_manifest_location_ = FindManifestMagic(disk); | |
| } | |
| if (disk_manifest_location_) { | |
| if (disk_manifest_location_.value() < kernel_size_) { | |
| LOG(ERROR) << "Manifest found in kernel data, skipping erase"; | |
| return; | |
| } | |
| disk.Seek(base::File::FROM_BEGIN, disk_manifest_location_.value()); | |
| } else { | |
| // No manifest on disk, return without doing anything. | |
| return; | |
| } | |
| // Clear out all data until the end of the partition. | |
| std::array<char, kBlockSize> zeros{0}; | |
| auto bytes_to_write = partition_size_ - disk_manifest_location_.value(); | |
| while (bytes_to_write > 0) { | |
| disk.WriteAtCurrentPos(zeros.data(), std::min(kBlockSize, bytes_to_write)); | |
| if (bytes_to_write <= kBlockSize) | |
| break; | |
| bytes_to_write -= kBlockSize; | |
| } | |
| // Flush disk stream. | |
| if (!disk.Flush()) { | |
| LOG(ERROR) << "Failed to clear manifest on device: " << disk_path_ | |
| << " error: " << disk.GetLastFileError(); | |
| } | |
| // Clear manifest location since now there's nothing on disk. | |
| disk_manifest_location_.reset(); | |
| } | |
| std::optional<uint64_t> LogStoreManifest::FindManifestMagic(base::File& disk) { | |
| if (!IsValid()) { | |
| LOG(ERROR) << "Invalid disk to find manifest: " << disk_path_; | |
| } | |
| if (!disk.IsValid()) { | |
| PLOG(ERROR) << "Invalid disk to find manifest: " << disk_path_; | |
| return std::nullopt; | |
| } | |
| // Seek to the end of the partition and step backwards the blocks until we | |
| // find the expected header magic. | |
| const auto num_blocks = partition_size_ / kBlockSize; | |
| const auto last_kernel_block = | |
| ((kernel_size_ + kBlockSize - 1) / kBlockSize) + 1; | |
| for (uint64_t block = num_blocks - 1; block > last_kernel_block; --block) { | |
| disk.Seek(base::File::FROM_BEGIN, kBlockSize * block); | |
| uint64_t block_magic = 0; | |
| disk.ReadAtCurrentPos(reinterpret_cast<char*>(&block_magic), | |
| sizeof(block_magic)); | |
| if (block_magic == kLogStoreMagic) { | |
| return kBlockSize * block; | |
| } | |
| } | |
| // Return `nullopt` if no magic is found. | |
| return std::nullopt; | |
| } | |
| } // namespace minios |