text: // 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 <inttypes.h>
  #include <lib/fit/defer.h>
  #include <lib/stdcompat/span.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 <safemath/checked_math.h>
  
  #include "fbl/unique_fd.h"
  #include "range/interval-tree.h"
  #include "src/storage/blobfs/format.h"
  #include "src/storage/fvm/sparse_reader.h"
  #include "src/storage/minfs/format.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/fvm/fvm_unpack.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"
  
  enum DiskType {
    File = 0,
    Mtd = 1,
    BlockDevice = 2,
  };
  #pragma GCC diagnostic push
  #pragma GCC diagnostic ignored "-Wc99-designator"
  const char* kDiskTypeStr[] = {
      [DiskType::File] = "file",
      [DiskType::Mtd] = "mtd",
      [DiskType::BlockDevice] = "block_device",
  };
  #pragma GCC diagnostic pop
  
  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,
            " 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,
            " 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,
            " 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,
            " unpack : Unpacks an input raw fvm image where the output path is used as a prefix for "
            "output files for partitions by appending the partition name. Resulting output paths "
            "will have dashes replaced with underscores, and duplicate names will get an additional "
            "dash and numerical suffix.\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");
    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 [type] - specify that file should be compressed (sparse and android sparse "
            "image only). Currently, the only supported type is \"lz4\".\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 %zu\n"
            "                                   Minfs block size is %u\n",
            size_t{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,
            " --with-empty-account-partition - Adds a placeholder partition with a label of \n"
            "                                  'account'. This will be formatted on account \n"
            "                                  creation to minfs.\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) {}
  
    fpromise::result<void, std::string> Write(uint64_t offset,
                                              cpp20::span<const uint8_t> buffer) final {
      ranges_.insert(range::Range<>(offset, offset + buffer.size()));
      return writer_->Write(offset, buffer);
    }
  
    fpromise::result<void, std::string> VisitGaps(fit::function<fpromise::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 fpromise::ok();
    }
  
   private:
    // Keep track of written ranges.
    range::IntervalTree<range::Range<>> ranges_;
  
    storage::volume_image::Writer* writer_ = nullptr;
  };
  
  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;
  }
  
  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();
      return EXIT_FAILURE;
    }
  
    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 disk_size = 0;
  
    size_t max_disk_size = 0;
    bool is_max_bad_blocks_set = false;
    DiskType disk_type = DiskType::File;
  
    size_t block_count = 0;
    storage::volume_image::RawNandOptions options;
  
    while (i < argc) {
      // Not all arguments are parsed here; some of them get parsed below and some are passed through
      // to the volume image library and get parsed there (e.g. the --sparse argument).
      if (!strcmp(argv[i], "--offset") && i + 1 < argc) {
        if (parse_size(argv[++i], &offset) < 0) {
          return EXIT_FAILURE;
        }
      } else if (!strcmp(argv[i], "--length") && i + 1 < argc) {
        if (parse_size(argv[++i], &length) < 0) {
          return EXIT_FAILURE;
        }
      } else if (!strcmp(argv[i], "--compress")) {
        if (!strcmp(argv[++i], "lz4")) {
          // This flag does nothing.
        } else {
          fprintf(stderr, "Invalid compression type\n");
          return EXIT_FAILURE;
        }
      } else if (!strcmp(argv[i], "--disk-type")) {
        if (ParseDiskType(argv[++i], &disk_type) != ZX_OK) {
          return EXIT_FAILURE;
        }
      } else if (!strcmp(argv[i], "--max-bad-blocks")) {
        is_max_bad_blocks_set = true;
      } else if (!strcmp(argv[i], "--disk")) {
        if (parse_size(argv[++i], &disk_size) < 0) {
          return EXIT_FAILURE;
        }
      } else if (!strcmp(argv[i], "--max-disk-size") && i + 1 < argc) {
        if (parse_size(argv[++i], &max_disk_size) < 0) {
          return EXIT_FAILURE;
        }
      } else if (!strcmp(argv[i], "--resize-image-file-to-fit")) {
        // This flag does nothing.
      } else if (!strcmp(argv[i], "--length-is-lowerbound")) {
        // This flag does nothing.
      } else if (!strcmp(argv[i], "--android-sparse-format")) {
        // This flag does not do anything.
      } else if (!strcmp(argv[i], "--nand-page-size")) {
        size_t page_size = 0;
        if (parse_size(argv[++i], &page_size) < 0) {
          return EXIT_FAILURE;
        }
        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 EXIT_FAILURE;
        }
        if (oob_bytes_size > std::numeric_limits<uint8_t>::max()) {
          fprintf(stderr, "OOB Byte size must lower than 256 bytes.\n");
          return EXIT_FAILURE;
        }
        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 EXIT_FAILURE;
        }
        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 EXIT_FAILURE;
        }
        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 EXIT_FAILURE;
        }
      } else {
        break;
      }
  
      ++i;
    }
  
    if (strcmp(command, "check") == 0) {
      const char* input_path;
      // For convenience, allow output path to be used as an input path.
      if (command == path) {
        if (argc != i + 2) {
          usage();
          return EXIT_FAILURE;
        }
        char* input_type = argv[i++];
        if (strcmp(input_type, "--sparse") != 0) {
          usage();
          return EXIT_FAILURE;
        }
        input_path = argv[i++];
      } else {
        if (argc != i) {
          usage();
          return EXIT_FAILURE;
        }
        input_path = path;
      }
      // 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 EXIT_FAILURE;
      }
  
      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 EXIT_FAILURE;
      }
      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 EXIT_FAILURE;
      }
  
      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 EXIT_FAILURE;
      }
      auto partitions = partitions_or.take_value();
  
      uint64_t expected_data_length = 0;
      uint64_t 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) {
        auto reader_or = storage::volume_image::FdReader::Create(input_path);
        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 EXIT_FAILURE;
        }
        auto cleanup = fit::defer([&]() { unlink(tmp_path.c_str()); });
  
        auto writer = storage::volume_image::FdWriter(std::move(created_file));
        auto decompress_or =
            storage::volume_image::FvmSparseDecompressImage(0, reader_or.value(), writer);
        if (decompress_or.is_error()) {
          std::cout << decompress_or.error();
          return EXIT_FAILURE;
        }
  
        auto decompressed_reader_or = storage::volume_image::FdReader::Create(tmp_path);
        if (reader_or.is_error()) {
          fprintf(stderr, "%s\n", reader_or.error().c_str());
          return EXIT_FAILURE;
        }
        total_size = decompressed_reader_or.value().length() - header.header_length;
      }
  
      if (expected_data_length > total_size) {
        fprintf(stderr,
                "Extent accumulated length is %" PRIu64 ", uncompressed data is %" PRIu64 "\n",
                expected_data_length, total_size);
        return EXIT_FAILURE;
      }
  
      fprintf(stderr, "Sparse input file is a valid FVM Sparse Image.\n");
      return EXIT_SUCCESS;
    }
  
    if (!strcmp(command, "ftl-raw-nand")) {
      if (argc != i + 2) {
        fprintf(stderr, "Invalid arguments for 'ftl-raw-nand' command.\n");
        return EXIT_FAILURE;
      }
  
      char* input_type = argv[i];
      char* input_path = argv[i + 1];
  
      if (strcmp(input_type, "--sparse") != 0) {
        usage();
        return EXIT_FAILURE;
      }
  
      if (options.page_size == 0) {
        fprintf(stderr, "Raw Nand device page size must be greater than zero.\n");
        return EXIT_FAILURE;
      }
  
      if (options.oob_bytes_size == 0) {
        fprintf(stderr, "Raw Nand device page oob size must be greater than zero.\n");
        return EXIT_FAILURE;
      }
  
      if (options.pages_per_block == 0) {
        fprintf(stderr, "Raw Nand device pages per block must be greater than zero.\n");
        return EXIT_FAILURE;
      }
  
      if (block_count == 0) {
        fprintf(stderr, "Raw Nand device block count must be greater than zero.\n");
        return EXIT_FAILURE;
      }
  
      options.page_count = safemath::CheckMul<uint32_t>(safemath::checked_cast<uint32_t>(block_count),
                                                        options.pages_per_block)
                               .ValueOrDie();
  
      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 EXIT_FAILURE;
      }
  
      // 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 EXIT_FAILURE;
        }
        if (ftruncate(ftl_output.get(), 0) != 0) {
          fprintf(stderr, "Failed to truncate output path. Error %s.\n", strerror(errno));
          return EXIT_FAILURE;
        }
      }
  
      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 EXIT_FAILURE;
      }
      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 EXIT_FAILURE;
      }
  
      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 EXIT_FAILURE;
      }
  
      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 EXIT_FAILURE;
      }
  
      // 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) -> fpromise::result<void, std::string> {
            uint64_t length = end - start;
            if (filler.size() < length) {
              filler.resize(length, 0xFF);
            }
            return writer->Write(start, cpp20::span<const uint8_t>(filler).subspan(0, length));
          });
  
      if (fill_result.is_error()) {
        fprintf(stderr, "%s\n", fill_result.error().c_str());
        return EXIT_FAILURE;
      }
  
      return EXIT_SUCCESS;
    }
  
    // 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 EXIT_FAILURE;
      }
  
      if (!is_max_bad_blocks_set && disk_type == DiskType::Mtd) {
        fprintf(stderr, "--max-bad-blocks is required when paving to MTD.\n");
        return EXIT_FAILURE;
      }
    }
  
    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 EXIT_FAILURE;
      }
  
      if (auto result = storage::volume_image::Create(create_params_or.value()); result.is_error()) {
        std::cout << result.error() << std::endl;
        return EXIT_FAILURE;
      }
    } else if (!strcmp(command, "extend")) {
      auto extend_params_or = storage::volume_image::ExtendParams::FromArguments(arguments);
      if (extend_params_or.is_error()) {
        std::cout << extend_params_or.error() << std::endl;
        return EXIT_FAILURE;
      }
  
      if (auto extend_res = Extend(extend_params_or.value()); extend_res.is_error()) {
        std::cout << extend_res.error() << std::endl;
        return EXIT_FAILURE;
      }
    } 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 EXIT_FAILURE;
      }
  
      if (auto result = storage::volume_image::Create(create_params_or.value()); result.is_error()) {
        std::cout << result.error() << std::endl;
        return EXIT_FAILURE;
      }
    } else if (!strcmp(command, "decompress")) {
      if (argc - i != 2) {
        usage();
        return EXIT_FAILURE;
      }
  
      const char* input_type = argv[i];
      const char* input_path = argv[i + 1];
  
      if (!strcmp(input_type, "--default")) {
        // Look at the magic values and update input_type to match the right one.
        auto reader_or = storage::volume_image::FdReader::Create(input_path);
        if (reader_or.is_error()) {
          std::cout << "Failed to read image. " << reader_or.error() << std::endl;
          return EXIT_FAILURE;
        }
  
        // Check the first 16 bytes of the file to try and figure out the input type.
        std::array<uint8_t, 16> magic_bytes = {};
        reader_or.value().Read(0, magic_bytes);
        constexpr uint32_t kLZ4Magic = 0x184D2204;
  
        if (memcmp(magic_bytes.data(), &fvm::kMagic, sizeof(fvm::kMagic)) == 0) {
          input_type = "--raw";
        } else if (memcmp(magic_bytes.data(), &kLZ4Magic, sizeof(kLZ4Magic)) == 0) {
          input_type = "--lz4";
        } else if (memcmp(magic_bytes.data(), &fvm::kSparseFormatMagic,
                          sizeof(fvm::kSparseFormatMagic)) == 0) {
          input_type = "--sparse";
        }
      }
  
      if (!strcmp(input_type, "--sparse")) {
        auto sparse_image_reader_or = storage::volume_image::FdReader::Create(input_path);
        if (sparse_image_reader_or.is_error()) {
          std::cout << "Failed to read image. " << sparse_image_reader_or.error() << std::endl;
          return EXIT_FAILURE;
        }
  
        auto header_or =
            storage::volume_image::fvm_sparse_internal::GetHeader(0, sparse_image_reader_or.value());
        if (header_or.is_error()) {
          std::cout << "Failed to parse sparse image header. " << header_or.error() << std::endl;
          return EXIT_FAILURE;
        }
        auto header = header_or.take_value();
  
        auto compression_options =
            storage::volume_image::fvm_sparse_internal::GetCompressionOptions(header);
        // Decompress the image.
        if (compression_options.schema != storage::volume_image::CompressionSchema::kNone) {
          auto reader_or = storage::volume_image::FdReader::Create(input_path);
          auto writer_or = storage::volume_image::FdWriter::Create(path);
          if (writer_or.is_error()) {
            std::cout << writer_or.error() << std::endl;
            return EXIT_FAILURE;
          }
  
          auto decompress_or = storage::volume_image::FvmSparseDecompressImage(0, reader_or.value(),
                                                                               writer_or.value());
          if (decompress_or.is_error()) {
            std::cout << decompress_or.error();
            return EXIT_FAILURE;
          }
        }
      } else if (!strcmp(input_type, "--lz4")) {
        if (fvm::SparseReader::DecompressLZ4File(input_path, path) != ZX_OK) {
          return EXIT_FAILURE;
        }
      } else if (!strcmp(input_type, "--raw")) {
        if (CopyFile(path, input_path) != ZX_OK) {
          return EXIT_FAILURE;
        }
      } else {
        usage();
        return EXIT_FAILURE;
      }
    } else if (!strcmp(command, "size")) {
      auto size_params_or = storage::volume_image::SizeParams::FromArguments(arguments);
      if (size_params_or.is_error()) {
        std::cout << size_params_or.error() << std::endl;
        return EXIT_FAILURE;
      }
  
      auto size_or = storage::volume_image::Size(size_params_or.value());
      if (size_or.is_error()) {
        std::cout << size_or.error() << std::endl;
        return EXIT_FAILURE;
      }
  
      // If we are not doing a check of whether this image fits in a specified size, print the minimum
      // allocated size.
      if (!size_params_or.value().length.has_value()) {
        std::cout << size_or.value() << std::endl;
      }
    } else if (!strcmp(command, "pave")) {
      auto pave_params_or = storage::volume_image::PaveParams::FromArguments(arguments);
      if (pave_params_or.is_error()) {
        std::cout << "Failed to parse pave params. " << pave_params_or.error() << std::endl;
        return EXIT_FAILURE;
      }
  
      if (auto result = storage::volume_image::Pave(pave_params_or.value()); result.is_error()) {
        std::cout << "Failed to pave. " << result.error() << std::endl;
        return EXIT_FAILURE;
      }
    } else if (!strcmp(command, "unpack")) {
      if (argc - i != 1) {
        usage();
        return EXIT_FAILURE;
      }
      auto reader_or = storage::volume_image::FdReader::Create(arguments[i]);
      if (reader_or.is_error()) {
        fprintf(stderr, "%s\n", reader_or.take_error().c_str());
        return EXIT_FAILURE;
      }
      const auto& reader = reader_or.take_value();
  
      if (auto result = storage::volume_image::UnpackRawFvm(reader, path); result.is_error()) {
        fprintf(stderr, "Failed to unpack: %s\n", result.take_error().c_str());
        return EXIT_FAILURE;
      }
    } else {
      fprintf(stderr, "Unrecognized command: \"%s\"\n", command);
      usage();
      return EXIT_FAILURE;
    }
  
    return EXIT_SUCCESS;
  }
  