| // Copyright 2017 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/fit/defer.h> |
| #include <unistd.h> |
| |
| #include <cstdint> |
| #include <cstdio> |
| #include <filesystem> |
| #include <iostream> |
| #include <limits> |
| #include <memory> |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| |
| #include <fbl/alloc_checker.h> |
| #include <fbl/span.h> |
| #include <safemath/checked_math.h> |
| |
| #include "range/interval-tree.h" |
| #include "src/storage/bin/fvm/mtd.h" |
| #include "src/storage/blobfs/format.h" |
| #include "src/storage/fvm/host/container.h" |
| #include "src/storage/fvm/host/file_wrapper.h" |
| #include "src/storage/fvm/host/format.h" |
| #include "src/storage/fvm/host/fvm_container.h" |
| #include "src/storage/fvm/host/fvm_reservation.h" |
| #include "src/storage/fvm/host/sparse_container.h" |
| #include "src/storage/fvm/sparse_reader.h" |
| #include "src/storage/volume_image/adapter/commands.h" |
| #include "src/storage/volume_image/ftl/ftl_image.h" |
| #include "src/storage/volume_image/ftl/ftl_raw_nand_image_writer.h" |
| #include "src/storage/volume_image/ftl/options.h" |
| #include "src/storage/volume_image/ftl/raw_nand_image.h" |
| #include "src/storage/volume_image/ftl/raw_nand_image_utils.h" |
| #include "src/storage/volume_image/fvm/fvm_sparse_image.h" |
| #include "src/storage/volume_image/fvm/fvm_sparse_image_reader.h" |
| #include "src/storage/volume_image/options.h" |
| #include "src/storage/volume_image/utils/decompressor.h" |
| #include "src/storage/volume_image/utils/fd_reader.h" |
| #include "src/storage/volume_image/utils/fd_writer.h" |
| |
| #define DEFAULT_SLICE_SIZE (8lu * (1 << 20)) |
| constexpr char kMinimumInodes[] = "--minimum-inodes"; |
| constexpr char kMinimumData[] = "--minimum-data-bytes"; |
| constexpr char kMaximumBytes[] = "--maximum-bytes"; |
| constexpr char kEmptyMinfs[] = "--with-empty-minfs"; |
| constexpr char kReserveSlices[] = "--reserve-slices"; |
| |
| enum DiskType { |
| File = 0, |
| Mtd = 1, |
| BlockDevice = 2, |
| }; |
| const char* kDiskTypeStr[] = { |
| [DiskType::File] = "file", |
| [DiskType::Mtd] = "mtd", |
| [DiskType::BlockDevice] = "block_device", |
| }; |
| |
| int usage(void) { |
| fprintf(stderr, "usage: fvm [ output_path ] [ command ] [ <flags>* ] [ <input_paths>* ]\n"); |
| fprintf(stderr, "fvm performs host-side FVM and sparse file creation\n"); |
| fprintf(stderr, "Commands:\n"); |
| fprintf(stderr, " create : Creates an FVM partition\n"); |
| fprintf(stderr, |
| " add : Adds a Minfs or Blobfs partition to an FVM (input path is" |
| " required)\n"); |
| fprintf(stderr, |
| " extend : Extends an FVM container to the specified size (length is" |
| " required)\n"); |
| fprintf(stderr, |
| " ftl-raw-nand: converts the input fvm.sparse.blk into a FTL Raw Nand Image (--sparse is " |
| "required).\n"); |
| fprintf(stderr, " sparse : Creates a sparse file. One or more input paths are required.\n"); |
| fprintf(stderr, " pave : Creates an FVM container from a sparse file.\n"); |
| fprintf(stderr, |
| " verify : Report basic information about sparse/fvm files and run fsck on" |
| " contained partitions.\n"); |
| fprintf(stderr, |
| " check : verifies that the |--sparse| image provided is valid. if |--max_disk_size| is " |
| "provided check that the maximum disk size is set to such value in the sparse image.\n"); |
| fprintf(stderr, |
| " size : Prints the minimum size required in order to pave a sparse file." |
| " If the --disk flag is provided, instead checks that the paved sparse file" |
| " will fit within a disk of this size. On success, no information is" |
| " outputted\n"); |
| fprintf(stderr, |
| " used-data-size : Prints sum of the space, in bytes, used by data on \n" |
| " different partitions. This does not include blocks used internally for \n" |
| " superblock, bitmaps, inodes, or for journal,\n"); |
| fprintf(stderr, " used-inodes : Prints the sum of used inodes on different partitions.\n"); |
| fprintf(stderr, |
| " used-size : Prints sum of the space, in bytes, used by data and by\n" |
| " superblock, bitmaps, inodes, and journal different partitions. All of the\n" |
| " reservations for non-data blocks are considered as used.\n"); |
| fprintf(stderr, |
| " decompress : Decompresses a compressed sparse/raw file. --sparse/lz4/default input " |
| "path is required. If option is set to --default, the tool will attempt to detect the " |
| "input format\n"); |
| fprintf(stderr, "Flags (neither or both of offset/length must be specified):\n"); |
| fprintf(stderr, |
| " --slice [bytes] - specify slice size - only valid on container creation.\n" |
| " (default: %zu)\n", |
| DEFAULT_SLICE_SIZE); |
| fprintf(stderr, |
| " --max-disk-size [bytes] Used for preallocating metadata. Only valid for sparse image. " |
| "(defaults to 0)\n"); |
| fprintf(stderr, " --offset [bytes] - offset at which container begins (fvm only)\n"); |
| fprintf(stderr, " --length [bytes] - length of container within file (fvm only)\n"); |
| fprintf(stderr, |
| " --compress - specify that file should be compressed (sparse and android sparse image " |
| "only)\n"); |
| fprintf(stderr, " --disk [bytes] - Size of target disk (valid for size command only)\n"); |
| fprintf(stderr, " --disk-type [%s, %s OR %s] - Type of target disk (pave only)\n", |
| kDiskTypeStr[DiskType::File], kDiskTypeStr[DiskType::Mtd], |
| kDiskTypeStr[DiskType::BlockDevice]); |
| fprintf(stderr, " --max-bad-blocks [number] - Max bad blocks for FTL (pave on mtd only)\n"); |
| fprintf(stderr, "Input options:\n"); |
| fprintf(stderr, " --blob [path] [reserve options] - Add path as blob type (must be blobfs)\n"); |
| fprintf(stderr, |
| " --data [path] [reserve options] - Add path as encrypted data type (must" |
| " be minfs)\n"); |
| fprintf(stderr, " --data-unsafe [path] - Add path as unencrypted data type (must be minfs)\n"); |
| fprintf(stderr, " --system [path] - Add path as system type (must be minfs)\n"); |
| fprintf(stderr, " --default [path] - Add generic path\n"); |
| fprintf(stderr, " --sparse [path] - Path to compressed sparse file\n"); |
| fprintf(stderr, " --lz4 [path] - Path to lz4 compressed raw file\n"); |
| fprintf(stderr, " --raw [path] - Path to raw fvm image file\n"); |
| fprintf(stderr, |
| " --resize-image-file-to-fit - When used with create/extend command, the output image " |
| "file will " |
| "be resized to just fit the metadata header and added partitions. Disk size specified in " |
| "the header remains the same. It's useful for reducing the size of the image file for " |
| "flashing\n"); |
| fprintf(stderr, |
| " --android-sparse-format - When used with create command, the image will be converted " |
| "to android sparse image.\n"); |
| fprintf( |
| stderr, |
| " --length-is-lowerbound - When used with extend command, if current disk size is already " |
| "no smaller than the specified size, the command will be no-op. If the option is not " |
| "specified, it will error out in this case.\n"); |
| fprintf(stderr, "reserve options:\n"); |
| fprintf(stderr, |
| " These options, on success, reserve additional fvm slices for data/inodes.\n" |
| " The number of bytes reserved may exceed the actual bytes needed due to\n" |
| " rounding up to slice boundary.\n"); |
| fprintf(stderr, |
| " --minimum-inodes inode_count - number of inodes to reserve\n" |
| " Blobfs inode size is %u\n" |
| " Minfs inode size is %u\n", |
| blobfs::kBlobfsInodeSize, minfs::kMinfsInodeSize); |
| |
| fprintf(stderr, |
| " --minimum-data-bytes data_bytes - number of bytes to reserve for data\n" |
| " in the fs\n" |
| " Blobfs block size is %u\n" |
| " Minfs block size is %u\n", |
| blobfs::kBlobfsBlockSize, minfs::kMinfsBlockSize); |
| fprintf(stderr, |
| " --maximum-bytes bytes - Places an upper bound of <bytes> on the total\n" |
| " number of bytes which may be used by the partition.\n" |
| " Returns an error if more space is necessary to\n" |
| " create the requested filesystem.\n"); |
| fprintf(stderr, |
| " --with-empty-minfs - Adds a placeholder partition that will be formatted on boot,\n" |
| " to minfs. The partition will be the 'data' partition.\n"); |
| fprintf( |
| stderr, |
| " --nand-page-size : Sets the hardware page size in bytes used by the targetted device.\n"); |
| fprintf(stderr, |
| " --nand-oob-size : Sets the hardware page oob size in bytes used by the targetted " |
| "device.\n"); |
| fprintf(stderr, |
| " --nand-pages-per-block : Sets the number of pages per block in the device.\n"); |
| fprintf(stderr, " --nand-block-count : Sets the number of blocks in the device.\n"); |
| exit(-1); |
| } |
| |
| int parse_size(const char* size_str, size_t* out) { |
| char* end; |
| size_t size = strtoull(size_str, &end, 10); |
| |
| switch (end[0]) { |
| case 'K': |
| case 'k': |
| size *= 1024; |
| end++; |
| break; |
| case 'M': |
| case 'm': |
| size *= (1024 * 1024); |
| end++; |
| break; |
| case 'G': |
| case 'g': |
| size *= (1024 * 1024 * 1024); |
| end++; |
| break; |
| } |
| |
| if (end[0] || size == 0) { |
| fprintf(stderr, "Bad size: %s\n", size_str); |
| return -1; |
| } |
| |
| *out = size; |
| return 0; |
| } |
| |
| class RawBlockImageWriter final : public storage::volume_image::Writer { |
| public: |
| explicit RawBlockImageWriter(storage::volume_image::Writer* writer) : writer_(writer) {} |
| |
| fit::result<void, std::string> Write(uint64_t offset, fbl::Span<const uint8_t> buffer) final { |
| ranges_.insert(range::Range<>(offset, offset + buffer.size())); |
| return writer_->Write(offset, buffer); |
| } |
| |
| fit::result<void, std::string> VisitGaps( |
| fit::function<fit::result<void, std::string>(uint64_t start, uint64_t end, Writer* writer)> |
| visitor) { |
| uint64_t last_gap_end = 0; |
| for (const auto& range : ranges_) { |
| if (range.second.Start() > last_gap_end) { |
| auto visit_result = visitor(last_gap_end, range.second.Start(), writer_); |
| if (visit_result.is_error()) { |
| return visit_result.take_error_result(); |
| } |
| } |
| last_gap_end = range.second.End(); |
| } |
| return fit::ok(); |
| } |
| |
| private: |
| // Keep track of written ranges. |
| range::IntervalTree<range::Range<>> ranges_; |
| |
| storage::volume_image::Writer* writer_ = nullptr; |
| }; |
| |
| int add_partitions(Container* container, int argc, char** argv) { |
| // If the flag |kEmptyMinfs| is set, an empty minfs partition will be added after the rest of the |
| // partitions have been processed. |
| bool add_empty_minfs = false; |
| // If the flag |kReserveSlices| is set, a reservation partition with the desired number of slices |
| // will be added after the rest of the partitions have been processed. |
| size_t slices_to_reserve = 0; |
| |
| for (int i = 0; i < argc;) { |
| if (argc - i < 2 || argv[i][0] != '-' || argv[i][1] != '-') { |
| usage(); |
| } |
| |
| const char* partition_type = argv[i] + 2; |
| const char* partition_path = argv[i + 1]; |
| if (i < argc && strcmp(argv[i], kEmptyMinfs) == 0) { |
| add_empty_minfs = true; |
| i++; |
| continue; |
| } |
| if (i < argc && strcmp(argv[i], kReserveSlices) == 0) { |
| if (parse_size(argv[i + 1], &slices_to_reserve) < 0) { |
| usage(); |
| return -1; |
| } |
| i += 2; |
| continue; |
| } |
| |
| std::optional<uint64_t> inodes = {}, data = {}, total_bytes = {}; |
| i += 2; |
| |
| while (true) { |
| size_t size; |
| if ((i + 2) <= argc && strcmp(argv[i], kMinimumInodes) == 0) { |
| if (parse_size(argv[i + 1], &size) < 0) { |
| usage(); |
| return -1; |
| } |
| inodes = safemath::checked_cast<uint64_t>(size); |
| i += 2; |
| } else if ((i + 2) <= argc && strcmp(argv[i], kMinimumData) == 0) { |
| if (parse_size(argv[i + 1], &size) < 0) { |
| usage(); |
| return -1; |
| } |
| data = safemath::checked_cast<uint64_t>(size); |
| i += 2; |
| } else if ((i + 2) <= argc && strcmp(argv[i], kMaximumBytes) == 0) { |
| if (parse_size(argv[i + 1], &size) < 0) { |
| usage(); |
| return -1; |
| } |
| total_bytes = safemath::checked_cast<uint64_t>(size); |
| i += 2; |
| } else { |
| break; |
| } |
| } |
| |
| FvmReservation reserve(inodes, data, total_bytes); |
| zx_status_t status = container->AddPartition(partition_path, partition_type, &reserve); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Failed to add partition: %d\n", status); |
| reserve.Dump(stderr); |
| return -1; |
| } |
| } |
| if (add_empty_minfs) { |
| container->AddCorruptedPartition(kDataTypeName, 0); |
| } |
| if (slices_to_reserve) { |
| container->AddSnapshotMetadataPartition(slices_to_reserve); |
| } |
| |
| return 0; |
| } |
| |
| size_t get_disk_size(const char* path, size_t offset) { |
| fbl::unique_fd fd(open(path, O_RDONLY, 0644)); |
| |
| if (fd) { |
| struct stat s; |
| if (fstat(fd.get(), &s) < 0) { |
| fprintf(stderr, "Failed to stat %s\n", path); |
| exit(-1); |
| } |
| |
| return s.st_size - offset; |
| } |
| |
| return 0; |
| } |
| |
| zx_status_t ParseDiskType(const char* type_str, DiskType* out) { |
| if (!strcmp(type_str, kDiskTypeStr[DiskType::File])) { |
| *out = DiskType::File; |
| return ZX_OK; |
| } else if (!strcmp(type_str, kDiskTypeStr[DiskType::Mtd])) { |
| *out = DiskType::Mtd; |
| return ZX_OK; |
| } else if (!strcmp(type_str, kDiskTypeStr[DiskType::BlockDevice])) { |
| *out = DiskType::BlockDevice; |
| return ZX_OK; |
| } |
| |
| fprintf(stderr, "Unknown disk type: '%s'.\n", type_str); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| bool IsRawFvmImageFile(const char* path, size_t offset) { |
| zx_status_t status = FvmContainer::Verify(path, offset); |
| return status == ZX_OK; |
| } |
| |
| zx_status_t IsFvmSparseImageFile(const char* path, bool* result) { |
| fbl::unique_fd fd(open(path, O_RDONLY, 0644)); |
| if (!fd) { |
| fprintf(stderr, "Fail to open file %s\n", path); |
| return ZX_ERR_IO; |
| } |
| std::unique_ptr<fvm::SparseReader> reader; |
| *result = fvm::SparseReader::CreateSilent(std::move(fd), &reader) == ZX_OK; |
| return ZX_OK; |
| } |
| |
| zx_status_t IsLZ4CompressedFile(const char* path, bool* result) { |
| constexpr uint32_t kLZ4Magic = 0x184D2204; |
| fbl::unique_fd fd(open(path, O_RDONLY, 0644)); |
| if (!fd) { |
| fprintf(stderr, "Fail to open file %s\n", path); |
| return ZX_ERR_IO; |
| } |
| uint32_t magic; |
| if (read(fd.get(), &magic, sizeof(magic)) != sizeof(magic)) { |
| fprintf(stderr, "Fail to read from file %s\n", path); |
| return ZX_ERR_IO; |
| } |
| *result = magic == kLZ4Magic; |
| return ZX_OK; |
| } |
| |
| const char* DetermineImageInputTypeOption(const char* input_path, size_t offset) { |
| bool result; |
| if (IsRawFvmImageFile(input_path, offset)) { |
| return "--raw"; |
| } |
| |
| if (IsFvmSparseImageFile(input_path, &result) != ZX_OK) { |
| return nullptr; |
| } |
| if (result) { |
| return "--sparse"; |
| } |
| |
| if (IsLZ4CompressedFile(input_path, &result) != ZX_OK) { |
| return nullptr; |
| } |
| if (result) { |
| return "--lz4"; |
| } |
| |
| return nullptr; |
| } |
| |
| zx_status_t CopyFile(const char* dst, const char* src) { |
| constexpr size_t kBufferLength = 1024 * 1024; |
| fbl::unique_fd fd_src(open(src, O_RDONLY, 0644)); |
| if (!fd_src) { |
| fprintf(stderr, "Unable to open source file %s\n", src); |
| return ZX_ERR_IO; |
| } |
| |
| fbl::unique_fd fd_dst(open(dst, O_RDWR | O_CREAT, 0644)); |
| if (!fd_dst) { |
| fprintf(stderr, "Unable to create output file %s\n", dst); |
| return ZX_ERR_IO; |
| } |
| |
| std::vector<uint8_t> buffer(kBufferLength); |
| while (true) { |
| ssize_t read_bytes = read(fd_src.get(), buffer.data(), kBufferLength); |
| if (read_bytes < 0) { |
| fprintf(stderr, "Failed to read data from image file\n"); |
| return ZX_ERR_IO; |
| } else if (read_bytes == 0) { |
| break; |
| } |
| |
| if (write(fd_dst.get(), buffer.data(), read_bytes) != read_bytes) { |
| fprintf(stderr, "BlockReader: failed to write to output\n"); |
| return ZX_ERR_IO; |
| } |
| } |
| return ZX_OK; |
| } |
| |
| int main(int argc, char** argv) { |
| if (argc < 3) { |
| usage(); |
| } |
| |
| std::vector<std::string_view> arguments; |
| for (int i = 0; i < argc; ++i) { |
| arguments.push_back(argv[i]); |
| } |
| |
| int i = 1; |
| const char* path = argv[i++]; // Output path |
| const char* command = argv[i++]; // Command |
| if (strcmp(path, "check") == 0) { |
| command = path; |
| i--; |
| } |
| |
| size_t length = 0; |
| size_t offset = 0; |
| size_t slice_size = DEFAULT_SLICE_SIZE; |
| size_t disk_size = 0; |
| |
| size_t max_bad_blocks = 0; |
| size_t max_disk_size = 0; |
| bool is_max_bad_blocks_set = false; |
| DiskType disk_type = DiskType::File; |
| |
| bool should_unlink = true; |
| bool resize_image_file_to_fit = false; |
| bool length_is_lower_bound = false; |
| bool convert_to_android_sparse_format = false; |
| uint32_t flags = 0; |
| size_t block_count = 0; |
| storage::volume_image::RawNandOptions options; |
| |
| while (i < argc) { |
| if (!strcmp(argv[i], "--slice") && i + 1 < argc) { |
| if (parse_size(argv[++i], &slice_size) < 0) { |
| return -1; |
| } |
| if (!slice_size || slice_size % blobfs::kBlobfsBlockSize || |
| slice_size % minfs::kMinfsBlockSize) { |
| fprintf(stderr, "Invalid slice size - must be a multiple of %u and %u\n", |
| blobfs::kBlobfsBlockSize, minfs::kMinfsBlockSize); |
| return -1; |
| } |
| } else if (!strcmp(argv[i], "--offset") && i + 1 < argc) { |
| should_unlink = false; |
| if (parse_size(argv[++i], &offset) < 0) { |
| return -1; |
| } |
| } else if (!strcmp(argv[i], "--length") && i + 1 < argc) { |
| if (parse_size(argv[++i], &length) < 0) { |
| return -1; |
| } |
| } else if (!strcmp(argv[i], "--compress")) { |
| if (!strcmp(argv[++i], "lz4")) { |
| flags |= fvm::kSparseFlagLz4; |
| } else { |
| fprintf(stderr, "Invalid compression type\n"); |
| return -1; |
| } |
| } else if (!strcmp(argv[i], "--disk-type")) { |
| if (ParseDiskType(argv[++i], &disk_type) != ZX_OK) { |
| return -1; |
| } |
| } else if (!strcmp(argv[i], "--max-bad-blocks")) { |
| max_bad_blocks = std::stoul(argv[++i], nullptr, 10); |
| is_max_bad_blocks_set = true; |
| } else if (!strcmp(argv[i], "--disk")) { |
| if (parse_size(argv[++i], &disk_size) < 0) { |
| return -1; |
| } |
| } else if (!strcmp(argv[i], "--max-disk-size") && i + 1 < argc) { |
| if (parse_size(argv[++i], &max_disk_size) < 0) { |
| return -1; |
| } |
| } else if (!strcmp(argv[i], "--resize-image-file-to-fit")) { |
| resize_image_file_to_fit = true; |
| } else if (!strcmp(argv[i], "--length-is-lowerbound")) { |
| length_is_lower_bound = true; |
| } else if (!strcmp(argv[i], "--android-sparse-format")) { |
| convert_to_android_sparse_format = true; |
| } else if (!strcmp(argv[i], "--nand-page-size")) { |
| size_t page_size = 0; |
| if (parse_size(argv[++i], &page_size) < 0) { |
| return -1; |
| } |
| options.page_size = static_cast<uint64_t>(page_size); |
| } else if (!strcmp(argv[i], "--nand-oob-size")) { |
| size_t oob_bytes_size = 0; |
| if (parse_size(argv[++i], &oob_bytes_size) < 0) { |
| return -1; |
| } |
| if (oob_bytes_size > std::numeric_limits<uint8_t>::max()) { |
| fprintf(stderr, "OOB Byte size must lower than 256 bytes.\n"); |
| return -1; |
| } |
| options.oob_bytes_size = static_cast<uint8_t>(oob_bytes_size); |
| } else if (!strcmp(argv[i], "--nand-pages-per-block")) { |
| size_t pages_per_block = 0; |
| if (parse_size(argv[++i], &pages_per_block) < 0) { |
| return -1; |
| } |
| if (pages_per_block > std::numeric_limits<uint32_t>::max()) { |
| fprintf(stderr, "Pages Per Block must be lower than 4,294,967,296.\n"); |
| return -1; |
| } |
| options.pages_per_block = static_cast<uint32_t>(pages_per_block); |
| } else if (!strcmp(argv[i], "--nand-block-count")) { |
| if (parse_size(argv[++i], &block_count) < 0) { |
| return -1; |
| } |
| } else { |
| break; |
| } |
| |
| ++i; |
| } |
| |
| if (strcmp(command, "check") == 0) { |
| char* input_type = argv[i]; |
| char* input_path = argv[i + 1]; |
| if (strcmp(input_type, "--sparse") != 0) { |
| usage(); |
| return -1; |
| } |
| // Temporary usage of internal symbols. |
| auto sparse_image_reader_or = storage::volume_image::FdReader::Create(input_path); |
| if (sparse_image_reader_or.is_error()) { |
| fprintf(stderr, "%s\n", sparse_image_reader_or.error().c_str()); |
| return -1; |
| } |
| |
| auto header_or = |
| storage::volume_image::fvm_sparse_internal::GetHeader(0, sparse_image_reader_or.value()); |
| if (header_or.is_error()) { |
| fprintf(stderr, "Failed to parse sparse image header. %s\n", header_or.error().c_str()); |
| return -1; |
| } |
| auto header = header_or.take_value(); |
| |
| if (max_disk_size != 0 && header.maximum_disk_size != max_disk_size) { |
| fprintf(stderr, "Sparse image does not match max disk size. Found %lu, expected %lu.\n", |
| static_cast<size_t>(header.maximum_disk_size), max_disk_size); |
| return -1; |
| } |
| |
| auto partitions_or = storage::volume_image::fvm_sparse_internal::GetPartitions( |
| sizeof(header), sparse_image_reader_or.value(), header); |
| if (partitions_or.is_error()) { |
| fprintf(stderr, "Failed to parse sparse image partition metadata. %s\n", |
| partitions_or.error().c_str()); |
| return -1; |
| } |
| auto partitions = partitions_or.take_value(); |
| |
| int expected_data_length = 0; |
| int total_size = sparse_image_reader_or.value().length(); |
| for (const auto& partition : partitions) { |
| for (const auto& extent : partition.extents) { |
| expected_data_length += extent.extent_length; |
| } |
| } |
| |
| auto compression_options = |
| storage::volume_image::fvm_sparse_internal::GetCompressionOptions(header); |
| // Decompress the image. |
| if (compression_options.schema != storage::volume_image::CompressionSchema::kNone) { |
| std::unique_ptr<SparseContainer> compressedContainer; |
| if (SparseContainer::CreateExisting(input_path, &compressedContainer) != ZX_OK) { |
| return -1; |
| } |
| std::string tmp_path = std::filesystem::temp_directory_path().generic_string() + |
| "/decompressed_sparse_fvm_XXXXXX"; |
| |
| fbl::unique_fd created_file(mkstemp(tmp_path.data())); |
| |
| if (!created_file.is_valid()) { |
| fprintf(stderr, "Failed to create temporary file for decompressing image. %s\n", |
| strerror(errno)); |
| return -1; |
| } |
| |
| auto cleanup_temp = fit::defer([tmp_path]() { |
| if (unlink(tmp_path.c_str()) < 0) { |
| fprintf(stderr, "Failed to delete temp file '%s': %s\n", tmp_path.c_str(), |
| strerror(errno)); |
| } |
| }); |
| if (compressedContainer->Decompress(tmp_path.c_str()) != ZX_OK) { |
| return -1; |
| } |
| |
| auto reader_or = storage::volume_image::FdReader::Create(tmp_path); |
| if (reader_or.is_error()) { |
| fprintf(stderr, "%s\n", reader_or.error().c_str()); |
| return -1; |
| } |
| total_size = reader_or.value().length() - header.header_length; |
| } |
| |
| if (expected_data_length > total_size) { |
| fprintf(stderr, "Extent accumulated length is %d, uncompressed data is %d\n", |
| expected_data_length, total_size); |
| return -1; |
| } |
| |
| fprintf(stderr, "--sparse input file is a valid FVM Sparse Image.\n"); |
| return 0; |
| } |
| |
| if (!strcmp(command, "ftl-raw-nand")) { |
| char* input_type = argv[i]; |
| char* input_path = argv[i + 1]; |
| |
| if (strcmp(input_type, "--sparse") != 0) { |
| usage(); |
| return -1; |
| } |
| |
| if (options.page_size == 0) { |
| fprintf(stderr, "Raw Nand device page size must be greater than zero.\n"); |
| return -1; |
| } |
| |
| if (options.oob_bytes_size == 0) { |
| fprintf(stderr, "Raw Nand device page oob size must be greater than zero.\n"); |
| return -1; |
| } |
| |
| if (options.pages_per_block == 0) { |
| fprintf(stderr, "Raw Nand device pages per block must be greater than zero.\n"); |
| return -1; |
| } |
| |
| if (block_count == 0) { |
| fprintf(stderr, "Raw Nand device block count must be greater than zero.\n"); |
| return -1; |
| } |
| |
| options.page_count = block_count * options.pages_per_block; |
| if (i >= argc) { |
| fprintf(stderr, "Missing input path for fvm sparse image.\n"); |
| return -1; |
| } |
| |
| auto sparse_image_reader_or = storage::volume_image::FdReader::Create(input_path); |
| if (sparse_image_reader_or.is_error()) { |
| fprintf(stderr, "%s\n", sparse_image_reader_or.error().c_str()); |
| return -1; |
| } |
| |
| // The FTL writer intentionally leaves existing content in place when |
| // opening a file, so we need to delete the output file first so that any |
| // existing file data doesn't carry over - in particular, if the existing |
| // NAND image is larger than the one we're about to generate, the excess |
| // data would be left in-place, corrupting the FTL metadata. |
| { |
| fbl::unique_fd ftl_output(open(path, O_CREAT | O_RDWR, 0644)); |
| if (!ftl_output.is_valid()) { |
| fprintf(stderr, "Failed to create output path. Error %s.\n", strerror(errno)); |
| return -1; |
| } |
| if (ftruncate(ftl_output.get(), 0) != 0) { |
| fprintf(stderr, "Failed to truncate output path. Error %s.\n", strerror(errno)); |
| return -1; |
| } |
| } |
| |
| auto sparse_image_reader = sparse_image_reader_or.take_value(); |
| auto ftl_image_writer_or = storage::volume_image::FdWriter::Create(path); |
| if (ftl_image_writer_or.is_error()) { |
| fprintf(stderr, "%s\n", ftl_image_writer_or.error().c_str()); |
| return -1; |
| } |
| auto ftl_image_writer = ftl_image_writer_or.take_value(); |
| RawBlockImageWriter raw_writer(&ftl_image_writer); |
| |
| std::optional<uint64_t> max_disk_size_opt = std::nullopt; |
| if (max_disk_size != 0) { |
| max_disk_size_opt = max_disk_size; |
| } |
| |
| auto fvm_partition_or = |
| storage::volume_image::OpenSparseImage(sparse_image_reader, max_disk_size_opt); |
| if (fvm_partition_or.is_error()) { |
| fprintf(stderr, "%s\n", fvm_partition_or.error().c_str()); |
| return -1; |
| } |
| |
| std::vector<storage::volume_image::RawNandImageFlag> flags = { |
| storage::volume_image::RawNandImageFlag::kRequireWipeBeforeFlash}; |
| auto raw_nand_image_writer_or = storage::volume_image::FtlRawNandImageWriter::Create( |
| options, flags, storage::volume_image::ImageFormat::kRawImage, &raw_writer); |
| if (raw_nand_image_writer_or.is_error()) { |
| fprintf(stderr, "%s\n", raw_nand_image_writer_or.error().c_str()); |
| return -1; |
| } |
| |
| auto [raw_nand_image_writer, ftl_options] = raw_nand_image_writer_or.take_value(); |
| auto ftl_image_write_result = storage::volume_image::FtlImageWrite( |
| ftl_options, fvm_partition_or.value(), &raw_nand_image_writer); |
| if (ftl_image_write_result.is_error()) { |
| fprintf(stderr, "%s\n", ftl_image_write_result.error().c_str()); |
| return -1; |
| } |
| |
| // Write in the gaps in the image with 0xFF or 'unwritten' bytes. |
| // For a raw image there may not be any gaps. |
| std::vector<uint8_t> filler(4 << 10, 0xFF); |
| auto fill_result = raw_writer.VisitGaps( |
| [&filler](uint64_t start, uint64_t end, |
| storage::volume_image::Writer* writer) -> fit::result<void, std::string> { |
| uint64_t length = end - start; |
| if (filler.size() < length) { |
| filler.resize(length, 0xFF); |
| } |
| return writer->Write(start, fbl::Span<const uint8_t>(filler).subspan(0, length)); |
| }); |
| |
| if (fill_result.is_error()) { |
| fprintf(stderr, "%s\n", fill_result.error().c_str()); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| // If length was not specified, use remainder of file after offset. |
| // get_disk_size may return 0 for block devices' behavior with fstat. |
| // This scenario is checked in the pave section below. |
| if (length == 0 && disk_type == DiskType::File) { |
| length = get_disk_size(path, offset); |
| } |
| |
| if (disk_type == DiskType::Mtd || disk_type == DiskType::BlockDevice) { |
| if (strcmp(command, "pave")) { |
| fprintf(stderr, "Only the pave command is supported for disk type %s.\n", |
| kDiskTypeStr[disk_type]); |
| return -1; |
| } |
| |
| if (!is_max_bad_blocks_set && disk_type == DiskType::Mtd) { |
| fprintf(stderr, "--max-bad-blocks is required when paving to MTD.\n"); |
| return -1; |
| } |
| } |
| |
| if (!strcmp(command, "create")) { |
| auto create_params_or = storage::volume_image::CreateParams::FromArguments(arguments); |
| if (create_params_or.is_error()) { |
| std::cout << create_params_or.error() << std::endl; |
| return -1; |
| } |
| |
| if (auto result = storage::volume_image::Create(create_params_or.value()); result.is_error()) { |
| std::cout << result.error() << std::endl; |
| return -1; |
| } |
| } else if (!strcmp(command, "add")) { |
| std::unique_ptr<FvmContainer> fvmContainer; |
| if (FvmContainer::CreateExisting(path, offset, &fvmContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| if (add_partitions(fvmContainer.get(), argc - i, argv + i) < 0) { |
| return -1; |
| } |
| |
| if (fvmContainer->Commit() != ZX_OK) { |
| return -1; |
| } |
| } else if (!strcmp(command, "extend")) { |
| if (length == 0 || offset > 0) { |
| usage(); |
| } |
| |
| size_t disk_size = get_disk_size(path, 0); |
| |
| if (length <= disk_size) { |
| fprintf(stderr, "Cannot extend to a value %zu less than current size %zu\n", length, |
| disk_size); |
| usage(); |
| } |
| |
| std::unique_ptr<FvmContainer> fvmContainer; |
| if (FvmContainer::CreateExisting(path, offset, &fvmContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| if (length_is_lower_bound) { |
| fvmContainer->SetExtendLengthType(FvmContainer::ExtendLengthType::LOWER_BOUND); |
| } |
| |
| if (fvmContainer->Extend(length) != ZX_OK) { |
| return -1; |
| } |
| |
| if (resize_image_file_to_fit) { |
| if (auto status = fvmContainer->ResizeImageFileToFit(); status != ZX_OK) { |
| return status; |
| } |
| } |
| } else if (!strcmp(command, "sparse")) { |
| auto create_params_or = storage::volume_image::CreateParams::FromArguments(arguments); |
| if (create_params_or.is_error()) { |
| std::cout << create_params_or.error() << std::endl; |
| return -1; |
| } |
| |
| if (auto result = storage::volume_image::Create(create_params_or.value()); result.is_error()) { |
| std::cout << result.error() << std::endl; |
| return -1; |
| } |
| } else if (!strcmp(command, "verify")) { |
| std::unique_ptr<Container> container_data; |
| if (Container::Create(path, offset, flags, &container_data) != ZX_OK) { |
| return -1; |
| } |
| |
| zx_status_t status = container_data->Verify(); |
| if (status != ZX_OK) { |
| fprintf(stderr, "Verification failed: %d\n", status); |
| return -1; |
| } |
| } else if (!strcmp(command, "decompress")) { |
| if (argc - i != 2) { |
| usage(); |
| return -1; |
| } |
| |
| const char* input_type = argv[i]; |
| const char* input_path = argv[i + 1]; |
| |
| if (!strcmp(input_type, "--default")) { |
| if (!(input_type = DetermineImageInputTypeOption(input_path, offset))) { |
| fprintf(stderr, "Fail to detect input file format\n"); |
| return -1; |
| } |
| } |
| |
| if (!strcmp(input_type, "--sparse")) { |
| std::unique_ptr<SparseContainer> compressedContainer; |
| if (SparseContainer::CreateExisting(input_path, &compressedContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| if (compressedContainer->Decompress(path) != ZX_OK) { |
| return -1; |
| } |
| |
| std::unique_ptr<SparseContainer> sparseContainer; |
| if (SparseContainer::CreateExisting(path, &sparseContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| if (sparseContainer->Verify() != ZX_OK) { |
| return -1; |
| } |
| } else if (!strcmp(input_type, "--lz4")) { |
| if (fvm::SparseReader::DecompressLZ4File(input_path, path) != ZX_OK) { |
| return -1; |
| } |
| } else if (!strcmp(input_type, "--raw")) { |
| if (CopyFile(path, input_path) != ZX_OK) { |
| return -1; |
| } |
| } else { |
| usage(); |
| return -1; |
| } |
| } else if (!strcmp(command, "size")) { |
| std::unique_ptr<SparseContainer> sparseContainer; |
| if (SparseContainer::CreateExisting(path, &sparseContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| if (disk_size == 0) { |
| printf("%" PRIu64 "\n", sparseContainer->CalculateDiskSize()); |
| } else if (sparseContainer->CheckDiskSize(disk_size) != ZX_OK) { |
| fprintf(stderr, "Sparse container will not fit in target disk size\n"); |
| return -1; |
| } |
| } else if (!strcmp(command, "used-data-size")) { |
| std::unique_ptr<SparseContainer> sparseContainer; |
| if (SparseContainer::CreateExisting(path, &sparseContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| uint64_t size; |
| |
| if (sparseContainer->UsedDataSize(&size) != ZX_OK) { |
| return -1; |
| } |
| printf("%" PRIu64 "\n", size); |
| } else if (!strcmp(command, "used-inodes")) { |
| std::unique_ptr<SparseContainer> sparseContainer; |
| if (SparseContainer::CreateExisting(path, &sparseContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| uint64_t used_inodes; |
| |
| if (sparseContainer->UsedInodes(&used_inodes) != ZX_OK) { |
| return -1; |
| } |
| printf("%" PRIu64 "\n", used_inodes); |
| } else if (!strcmp(command, "used-size")) { |
| std::unique_ptr<SparseContainer> sparseContainer; |
| if (SparseContainer::CreateExisting(path, &sparseContainer) != ZX_OK) { |
| return -1; |
| } |
| |
| uint64_t size; |
| |
| if (sparseContainer->UsedSize(&size) != ZX_OK) { |
| return -1; |
| } |
| printf("%" PRIu64 "\n", size); |
| } else if (!strcmp(command, "pave")) { |
| char* input_type = argv[i]; |
| char* input_path = argv[i + 1]; |
| |
| if (strcmp(input_type, "--sparse")) { |
| fprintf(stderr, "pave command only accepts --sparse input option\n"); |
| usage(); |
| return -1; |
| } |
| |
| std::unique_ptr<SparseContainer> sparseData; |
| if (SparseContainer::CreateExisting(input_path, &sparseData) != ZX_OK) { |
| return -1; |
| } |
| |
| std::unique_ptr<fvm::host::FileWrapper> wrapper; |
| |
| if (disk_type == DiskType::File) { |
| std::unique_ptr<fvm::host::UniqueFdWrapper> unique_fd_wrapper; |
| if (fvm::host::UniqueFdWrapper::Open(path, O_CREAT | O_WRONLY, 0644, &unique_fd_wrapper) != |
| ZX_OK) { |
| return -1; |
| } |
| |
| wrapper = std::move(unique_fd_wrapper); |
| } else if (disk_type == DiskType::Mtd) { |
| zx_status_t status = |
| CreateFileWrapperFromMtd(path, safemath::saturated_cast<uint32_t>(offset), |
| safemath::saturated_cast<uint32_t>(max_bad_blocks), &wrapper); |
| |
| if (status != ZX_OK) { |
| return -1; |
| } |
| |
| // The byte offset into the output file is handled by CreateFileWrapperFromMtd. |
| offset = 0; |
| |
| // Length may be 0 at this point if the user did not specify a size. |
| // Use all of the space reported by the FTL in this case. |
| if (length == 0) { |
| length = wrapper->Size(); |
| } |
| } else if (disk_type == DiskType::BlockDevice) { |
| std::unique_ptr<fvm::host::BlockDeviceFdWrapper> block_device_fd_wrapper; |
| if (fvm::host::BlockDeviceFdWrapper::Open(path, O_CREAT | O_WRONLY, 0644, |
| &block_device_fd_wrapper) != ZX_OK) { |
| fprintf(stderr, "BlockDeviceFd open failed: %s\n", strerror(errno)); |
| return -1; |
| } |
| |
| wrapper = std::move(block_device_fd_wrapper); |
| |
| // Length may be 0 at this point if the user did not specify a size. |
| // Get the partition size to calculate length. |
| if (length == 0) { |
| length = wrapper->Size(); |
| } |
| } else { |
| fprintf(stderr, "Unknown disk type\n"); |
| return -1; |
| } |
| |
| if (sparseData->Pave(std::move(wrapper), offset, length) != ZX_OK) { |
| return -1; |
| } |
| } else { |
| fprintf(stderr, "Unrecognized command: \"%s\"\n", command); |
| usage(); |
| } |
| |
| return 0; |
| } |