| // 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 <lib/fdio/fd.h> |
| #include <lib/zx/status.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| #include <zircon/assert.h> |
| #include <zircon/errors.h> |
| |
| #include <cstdint> |
| #include <iostream> |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <utility> |
| |
| #include <fbl/unique_fd.h> |
| #include <safemath/safe_math.h> |
| |
| #include "src/storage/extractor/c/extractor.h" |
| #include "src/storage/extractor/cpp/extractor.h" |
| #include "src/storage/fvm/format.h" |
| |
| namespace extractor { |
| namespace { |
| |
| // Walks the fvm and collects interesting metadata. |
| class FvmWalker { |
| public: |
| static zx::status<std::unique_ptr<FvmWalker>> Create(fbl::unique_fd input_fd, |
| Extractor& extractor); |
| |
| zx::status<> Walk(); |
| |
| private: |
| // Returns maximum addressable byte in the fvm. |
| uint64_t ByteLimit() const { return info_.fvm_partition_size; } |
| |
| // Walks the fvm partition and marks all bytes as reported by ByteLimit() as unmapped |
| zx::status<> WalkPartition() const; |
| |
| // Walks different segments, like partition table and allocation table, of the fvm partition. |
| // Marks them as data unmodified. |
| zx::status<> WalkSegments() const; |
| |
| const fvm::Header& Info() const { return info_; } |
| |
| FvmWalker(fbl::unique_fd input_fd, Extractor& extractor); |
| |
| // Not copyable or movable |
| FvmWalker(const FvmWalker&) = delete; |
| FvmWalker& operator=(const FvmWalker&) = delete; |
| FvmWalker(FvmWalker&&) = delete; |
| FvmWalker& operator=(FvmWalker&&) = delete; |
| |
| // Loads superblock located at start_offset. If the copy of superblock has valid |
| // magic values, the function returns zx::ok(). |
| zx::status<> TryLoadSuperblock(uint64_t start_offset); |
| |
| // Tries to load the primary superblock. If primary superblock is corrupt, |
| // dumps the first copy of metadata based on max sizes for the |
| // partition and allocation tables |
| zx::status<> LoadSuperblock(); |
| |
| // The primary superblock, if valid |
| fvm::Header info_; |
| |
| // Pointer to extractor. |
| Extractor& extractor_; |
| |
| // File from where the fvm is parsed/loaded. |
| fbl::unique_fd input_fd_; |
| }; |
| |
| FvmWalker::FvmWalker(fbl::unique_fd input_fd, Extractor& extractor) |
| : extractor_(extractor), input_fd_(std::move(input_fd)) {} |
| |
| zx::status<std::unique_ptr<FvmWalker>> FvmWalker::Create(fbl::unique_fd input_fd, |
| Extractor& extractor) { |
| auto walker = std::unique_ptr<FvmWalker>(new FvmWalker(std::move(input_fd), extractor)); |
| |
| if (auto status = walker->LoadSuperblock(); status.is_error()) { |
| std::cerr << "Loading primary superblock failed" << std::endl; |
| return zx::error(status.error_value()); |
| } |
| return zx::ok(std::move(walker)); |
| } |
| |
| zx::status<> FvmWalker::Walk() { |
| if (auto status = WalkPartition(); status.is_error()) { |
| std::cerr << "Walking partition failed" << std::endl; |
| return status; |
| } |
| |
| if (auto status = WalkSegments(); status.is_error()) { |
| std::cerr << "Walking segments failed" << std::endl; |
| return status; |
| } |
| return zx::ok(); |
| } |
| |
| zx::status<> FvmWalker::WalkPartition() const { |
| auto max_offset = ByteLimit(); |
| ExtentProperties properties{.extent_kind = ExtentKind::Unmmapped, .data_kind = DataKind::Skipped}; |
| return extractor_.Add(0, max_offset, properties); |
| } |
| |
| zx::status<> FvmWalker::WalkSegments() const { |
| ExtentProperties properties{.extent_kind = ExtentKind::Data, .data_kind = DataKind::Unmodified}; |
| if (auto status = extractor_.AddBlocks(info_.GetSuperblockOffset(fvm::SuperblockType::kPrimary), |
| 1, properties); |
| status.is_error()) { |
| std::cerr << "FAIL: Add first superblock copy" << std::endl; |
| return status; |
| } |
| if (auto status = |
| extractor_.AddBlocks(info_.GetPartitionTableOffset() / fvm::kBlockSize, |
| info_.GetPartitionTableByteSize() / fvm::kBlockSize, properties); |
| status.is_error()) { |
| std::cerr << "FAIL: Add first partition table copy" << std::endl; |
| return status; |
| } |
| if (auto status = extractor_.AddBlocks(info_.GetAllocationTableOffset() / fvm::kBlockSize, |
| info_.GetAllocationTableUsedByteSize() / fvm::kBlockSize, |
| properties); |
| status.is_error()) { |
| std::cerr << "FAIL: Add first allocation table copy" << std::endl; |
| return status; |
| } |
| size_t secondarySuperblockOffset = |
| info_.GetSuperblockOffset(fvm::SuperblockType::kSecondary) / fvm::kBlockSize; |
| if (auto status = extractor_.AddBlocks(secondarySuperblockOffset, 1, properties); |
| status.is_error()) { |
| std::cerr << "FAIL: Add second superblock copy" << std::endl; |
| return status; |
| } |
| if (auto status = extractor_.AddBlocks( |
| secondarySuperblockOffset + info_.GetPartitionTableOffset() / fvm::kBlockSize, |
| info_.GetPartitionTableByteSize() / fvm::kBlockSize, properties); |
| status.is_error()) { |
| std::cerr << "FAIL: Add second partition table copy" << std::endl; |
| return status; |
| } |
| if (auto status = extractor_.AddBlocks( |
| secondarySuperblockOffset + info_.GetAllocationTableOffset() / fvm::kBlockSize, |
| info_.GetAllocationTableUsedByteSize() / fvm::kBlockSize, properties); |
| status.is_error()) { |
| std::cerr << "FAIL: Add second allocation table copy" << std::endl; |
| return status; |
| } |
| return zx::ok(); |
| } |
| |
| zx::status<> FvmWalker::TryLoadSuperblock(uint64_t start_offset) { |
| off_t pread_offset; |
| if (!safemath::MakeCheckedNum(start_offset).Cast<off_t>().AssignIfValid(&pread_offset)) { |
| return zx::error(ZX_ERR_OUT_OF_RANGE); |
| } |
| |
| char buffer[fvm::kBlockSize]; |
| pread(input_fd_.get(), buffer, fvm::kBlockSize, pread_offset); |
| memcpy(&info_, buffer, sizeof(info_)); |
| if (info_.magic == fvm::kMagic) { |
| return zx::ok(); |
| } |
| std::cerr << "Magic does not match" << std::endl; |
| return zx::error(ZX_ERR_BAD_STATE); |
| } |
| |
| zx::status<> FvmWalker::LoadSuperblock() { |
| auto load_status = TryLoadSuperblock(0); |
| if (load_status.is_ok()) { |
| return load_status; |
| } |
| // If we fail to load the superblock, just dump the primary superblock |
| ExtentProperties properties{.extent_kind = ExtentKind::Data, .data_kind = DataKind::Unmodified}; |
| if (auto status = extractor_.AddBlocks(0, 1, properties); status.is_error()) { |
| std::cerr << "FAIL: Add primary superblock" << std::endl; |
| return status; |
| } |
| return load_status; |
| } |
| |
| } // namespace |
| |
| zx::status<> FvmExtract(fbl::unique_fd input_fd, Extractor& extractor) { |
| auto walker_or = FvmWalker::Create(std::move(input_fd), extractor); |
| if (walker_or.is_error()) { |
| std::cerr << "Walker: Init failure: " << walker_or.error_value() << std::endl; |
| return zx::error(walker_or.error_value()); |
| } |
| std::unique_ptr<FvmWalker> walker = std::move(walker_or.value()); |
| return walker->Walk(); |
| } |
| |
| } // namespace extractor |