| // Copyright 2021 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/storage/volume_image/fvm/fvm_unpack.h" |
| |
| #include <fcntl.h> |
| #include <sys/types.h> |
| |
| #include <cstdint> |
| #include <iostream> |
| #include <string> |
| #include <unordered_map> |
| #include <vector> |
| |
| #include "lib/fpromise/result.h" |
| #include "src/storage/fvm/metadata.h" |
| #include "src/storage/volume_image/fvm/fvm_metadata.h" |
| #include "src/storage/volume_image/utils/fd_writer.h" |
| #include "src/storage/volume_image/utils/reader.h" |
| #include "src/storage/volume_image/utils/writer.h" |
| |
| namespace storage::volume_image { |
| |
| namespace { |
| // A simple wrapper to hold onto the copying buffer while copying slices from the fvm image |
| // to their new blk files. |
| class SliceDistributor { |
| public: |
| SliceDistributor(const Reader& reader, const fvm::Metadata& metadata) |
| : reader_(reader), buffer_(metadata.GetHeader().slice_size), metadata_(metadata) {} |
| |
| fpromise::result<void, std::string> WriteSlice(uint64_t pslice, Writer* writer, uint64_t vslice) { |
| if (auto result = reader_.Read(metadata_.GetHeader().GetSliceDataOffset(pslice), buffer_); |
| result.is_error()) { |
| return result.take_error_result(); |
| } |
| if (auto result = writer->Write(vslice * metadata_.GetHeader().slice_size, buffer_); |
| result.is_error()) { |
| return result.take_error_result(); |
| } |
| return fpromise::ok(); |
| } |
| |
| private: |
| const Reader& reader_; |
| std::vector<uint8_t> buffer_; |
| const fvm::Metadata& metadata_; |
| }; |
| } // namespace |
| |
| namespace internal { |
| |
| fpromise::result<void, std::string> UnpackRawFvmPartitions( |
| const Reader& image, const fvm::Metadata& metadata, |
| const std::vector<std::unique_ptr<Writer>>& out_files) { |
| SliceDistributor distributor(image, metadata); |
| for (uint64_t pslice = 0; pslice <= metadata.GetHeader().GetAllocationTableUsedEntryCount(); |
| ++pslice) { |
| const fvm::SliceEntry& slice = metadata.GetSliceEntry(pslice); |
| if (!slice.IsAllocated()) { |
| continue; |
| } |
| const uint64_t partition = slice.VPartition(); |
| const uint64_t vslice = slice.VSlice(); |
| // Skip partitions that we didn't ask to write out. |
| if (partition >= out_files.size() || !out_files[partition]) { |
| continue; |
| } |
| if (auto result = distributor.WriteSlice(pslice, out_files[partition].get(), vslice); |
| result.is_error()) { |
| return fpromise::error("Failed to copy slice " + std::to_string(pslice) + " to vslice " + |
| std::to_string(vslice) + " of partition id " + |
| std::to_string(partition) + ": " + result.take_error()); |
| } |
| } |
| |
| return fpromise::ok(); |
| } |
| |
| std::vector<std::optional<std::string>> DisambiguateNames( |
| const std::vector<std::optional<std::string>>& names) { |
| std::vector<std::optional<std::string>> deduped(names.size()); |
| std::unordered_map<std::string, size_t> counts; |
| for (size_t i = 0; i < names.size(); ++i) { |
| if (!names[i].has_value()) { |
| continue; |
| } |
| std::string current = names[i].value(); |
| std::replace(current.begin(), current.end(), '-', '_'); |
| size_t dupe_number = 0; |
| auto entry = counts.find(current); |
| if (entry == counts.end()) { |
| counts[current] = 0; |
| } else { |
| dupe_number = entry->second + 1; |
| counts[current] = dupe_number; |
| } |
| if (dupe_number > 0 || current.empty()) { |
| deduped[i] = std::move(current) + "-" + std::to_string(dupe_number); |
| } else { |
| deduped[i] = std::move(current); |
| } |
| } |
| return deduped; |
| } |
| |
| } // namespace internal |
| |
| fpromise::result<void, std::string> UnpackRawFvm(const Reader& image, |
| const std::string& out_path_prefix) { |
| auto metadata_or = FvmGetMetadata(image); |
| if (metadata_or.is_error()) { |
| return metadata_or.take_error_result(); |
| } |
| const auto& metadata = metadata_or.take_value(); |
| |
| // Find all used partitions |
| size_t num_partitions = metadata.GetHeader().GetPartitionTableEntryCount(); |
| std::vector<std::optional<std::string>> names(num_partitions + 1); |
| for (uint64_t i = 1; i <= num_partitions; ++i) { |
| const fvm::VPartitionEntry& partition = metadata.GetPartitionEntry(i); |
| if (partition.IsFree()) { |
| continue; |
| } |
| std::cout << "Partition \"" << partition.name() << "\" has reserved " << partition.slices |
| << " slices for " << partition.slices * metadata.GetHeader().slice_size << " bytes." |
| << std::endl; |
| names[i] = partition.name(); |
| } |
| |
| // Open an output file for each partition |
| std::vector<std::optional<std::string>> out_names = internal::DisambiguateNames(names); |
| std::vector<std::unique_ptr<Writer>> writers(num_partitions + 1); |
| for (size_t i = 0; i < out_names.size(); ++i) { |
| if (!out_names[i].has_value()) { |
| continue; |
| } |
| std::string path = out_path_prefix + out_names[i].value(); |
| fbl::unique_fd fd(open(path.c_str(), O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR)); |
| if (!fd.is_valid()) { |
| return fpromise::error("Failed to open '" + path + "' for writing"); |
| } |
| writers[i] = std::make_unique<FdWriter>(std::move(fd)); |
| } |
| |
| return internal::UnpackRawFvmPartitions(image, metadata, writers); |
| } |
| |
| } // namespace storage::volume_image |