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/syslog/cpp/macros.h>
  #include <stdarg.h>
  #include <stdlib.h>
  #include <string.h>
  #include <zircon/assert.h>
  
  #include <iomanip>
  #include <limits>
  
  #include <safemath/checked_math.h>
  
  #include "src/storage/blobfs/blob-layout.h"
  #include "src/storage/blobfs/format.h"
  
  #ifdef __Fuchsia__
  #include <fuchsia/hardware/block/c/fidl.h>
  #include <fuchsia/hardware/block/volume/c/fidl.h>
  
  #include "src/storage/fvm/client.h"
  #endif
  
  #include "src/storage/blobfs/common.h"
  
  namespace blobfs {
  namespace {
  
  // Dumps the content of superblock.
  std::ostream& operator<<(std::ostream& stream, const Superblock& info) {
    return stream << "\ninfo.magic0: " << info.magic0 << "\ninfo.magic1: " << info.magic1
                  << "\ninfo.format_version: " << info.format_version
                  << "\ninfo.flags: " << info.flags << "\ninfo.block_size: " << info.block_size
                  << "\ninfo.data_block_count: " << info.data_block_count
                  << "\ninfo.journal_block_count: " << info.journal_block_count
                  << "\ninfo.inode_count: " << info.inode_count
                  << "\ninfo.alloc_block_count: " << info.alloc_block_count
                  << "\ninfo.alloc_inode_count: " << info.alloc_inode_count
                  << "\ninfo.slice_size: " << info.slice_size
                  << "\ninfo.abm_slices: " << info.abm_slices
                  << "\ninfo.ino_slices: " << info.ino_slices
                  << "\ninfo.dat_slices: " << info.dat_slices
                  << "\ninfo.journal_slices: " << info.journal_slices
                  << "\ninfo.oldest_revision: " << info.oldest_revision;
  }
  
  uint32_t GetBlobfsFormatVersionFromOptions(const FilesystemOptions& options) {
    if (options.blob_layout_format == BlobLayoutFormat::kCompactMerkleTreeAtEnd) {
      return 0x9;
    }
    return 0x8;
  }
  
  bool CheckFilesystemAndDriverCompatibility(uint32_t format_version) {
    if (format_version == kBlobfsCurrentFormatVersion) {
      return true;
    }
    // Driver version 9 is compatible with filesystem version 8.
    if (format_version == 0x8 && kBlobfsCurrentFormatVersion == 0x9) {
      return true;
    }
    FX_LOGS(ERROR) << "Filesystem and Driver are incompatible. FS Version: " << std::setfill('0')
                   << std::setw(8) << std::hex << format_version
                   << ". Driver version: " << std::setw(8) << kBlobfsCurrentFormatVersion;
    return false;
  }
  
  }  // namespace
  
  // Validate the metadata for the superblock, given a maximum number of
  // available blocks.
  zx_status_t CheckSuperblock(const Superblock* info, uint64_t max, bool quiet) {
    if ((info->magic0 != kBlobfsMagic0) || (info->magic1 != kBlobfsMagic1)) {
      if (!quiet)
        FX_LOGS(ERROR) << "bad magic";
      return ZX_ERR_INVALID_ARGS;
    }
    if (!CheckFilesystemAndDriverCompatibility(info->format_version)) {
      if (!quiet)
        FX_LOGS(ERROR) << *info;
      return ZX_ERR_INVALID_ARGS;
    }
    if (info->block_size != kBlobfsBlockSize) {
      if (!quiet)
        FX_LOGS(ERROR) << "bsz " << info->block_size << " unsupported" << *info;
      return ZX_ERR_INVALID_ARGS;
    }
  
    if (info->data_block_count < kMinimumDataBlocks) {
      if (!quiet)
        FX_LOGS(ERROR) << "Not enough space for minimum data partition";
      return ZX_ERR_NO_SPACE;
    }
  
  #ifdef __Fuchsia__
    if ((info->flags & kBlobFlagClean) == 0) {
      if (!quiet)
        FX_LOGS(WARNING) << "filesystem in dirty state. Was not unmounted cleanly.";
    } else {
      if (!quiet)
        FX_LOGS(INFO) << "filesystem in clean state.";
    }
  #endif
  
    // Determine the number of blocks necessary for the block map and node map.
    uint64_t total_inode_size;
    if (mul_overflow(info->inode_count, sizeof(Inode), &total_inode_size)) {
      if (!quiet)
        FX_LOGS(ERROR) << "Multiplication overflow";
      return ZX_ERR_OUT_OF_RANGE;
    }
  
    uint64_t node_map_size;
    if (mul_overflow(NodeMapBlocks(*info), kBlobfsBlockSize, &node_map_size)) {
      if (!quiet)
        FX_LOGS(ERROR) << "Multiplication overflow";
      return ZX_ERR_OUT_OF_RANGE;
    }
  
    if (total_inode_size != node_map_size) {
      if (!quiet)
        FX_LOGS(ERROR) << "Inode table block must be entirely filled";
      return ZX_ERR_BAD_STATE;
    }
  
    if (info->journal_block_count < kMinimumJournalBlocks) {
      if (!quiet)
        FX_LOGS(ERROR) << "Not enough space for minimum journal partition";
      return ZX_ERR_NO_SPACE;
    }
  
    if ((info->flags & kBlobFlagFVM) == 0) {
      if (TotalBlocks(*info) > max) {
        if (!quiet)
          FX_LOGS(ERROR) << "too large for device" << *info;
        return ZX_ERR_INVALID_ARGS;
      }
    } else {
      const size_t blocks_per_slice = info->slice_size / info->block_size;
  
      size_t abm_blocks_needed = BlockMapBlocks(*info);
      size_t abm_blocks_allocated = info->abm_slices * blocks_per_slice;
      if (abm_blocks_needed > abm_blocks_allocated) {
        if (!quiet)
          FX_LOGS(ERROR) << "Not enough slices for block bitmap" << *info;
        return ZX_ERR_INVALID_ARGS;
      } else if (abm_blocks_allocated + BlockMapStartBlock(*info) >= NodeMapStartBlock(*info)) {
        if (!quiet)
          FX_LOGS(ERROR) << "Block bitmap collides into node map" << *info;
        return ZX_ERR_INVALID_ARGS;
      }
  
      size_t ino_blocks_needed = NodeMapBlocks(*info);
      size_t ino_blocks_allocated = info->ino_slices * blocks_per_slice;
      if (ino_blocks_needed > ino_blocks_allocated) {
        if (!quiet)
          FX_LOGS(ERROR) << "Not enough slices for node map" << *info;
        return ZX_ERR_INVALID_ARGS;
      } else if (ino_blocks_allocated + NodeMapStartBlock(*info) >= DataStartBlock(*info)) {
        if (!quiet)
          FX_LOGS(ERROR) << "Node bitmap collides into data blocks" << *info;
        return ZX_ERR_INVALID_ARGS;
      }
  
      size_t dat_blocks_needed = DataBlocks(*info);
      size_t dat_blocks_allocated = info->dat_slices * blocks_per_slice;
      if (dat_blocks_needed < kStartBlockMinimum) {
        if (!quiet)
          FX_LOGS(ERROR) << "Partition too small; no space left for data blocks" << *info;
        return ZX_ERR_INVALID_ARGS;
      } else if (dat_blocks_needed > dat_blocks_allocated) {
        if (!quiet)
          FX_LOGS(ERROR) << "Not enough slices for data blocks" << *info;
        return ZX_ERR_INVALID_ARGS;
      } else if (dat_blocks_allocated + DataStartBlock(*info) >
                 std::numeric_limits<uint32_t>::max()) {
        if (!quiet)
          FX_LOGS(ERROR) << "Data blocks overflow uint32" << *info;
        return ZX_ERR_INVALID_ARGS;
      }
    }
    return ZX_OK;
  }
  
  uint32_t CalculateVsliceCount(const Superblock& superblock) {
    // Account for an additional slice for the superblock itself.
    return safemath::checked_cast<uint32_t>(1 + static_cast<uint64_t>(superblock.abm_slices) +
                                            static_cast<uint64_t>(superblock.ino_slices) +
                                            static_cast<uint64_t>(superblock.dat_slices) +
                                            static_cast<uint64_t>(superblock.journal_slices));
  }
  
  uint32_t BlocksRequiredForInode(uint64_t inode_count) {
    return safemath::checked_cast<uint32_t>(fbl::round_up(inode_count, kBlobfsInodesPerBlock) /
                                            kBlobfsInodesPerBlock);
  }
  
  uint32_t BlocksRequiredForBits(uint64_t bit_count) {
    return safemath::checked_cast<uint32_t>(fbl::round_up(bit_count, kBlobfsBlockBits) /
                                            kBlobfsBlockBits);
  }
  
  uint32_t SuggestJournalBlocks(uint32_t current, uint32_t available) { return current + available; }
  
  void InitializeSuperblock(uint64_t block_count, const FilesystemOptions& options,
                            Superblock* info) {
    uint64_t inodes = kBlobfsDefaultInodeCount;
    memset(info, 0x00, sizeof(*info));
    info->magic0 = kBlobfsMagic0;
    info->magic1 = kBlobfsMagic1;
    info->format_version = GetBlobfsFormatVersionFromOptions(options);
    info->flags = kBlobFlagClean;
    info->block_size = kBlobfsBlockSize;
    // TODO(planders): Consider modifying the inode count if we are low on space.
    //                 It doesn't make sense to have fewer data blocks than inodes.
    info->inode_count = inodes;
    info->alloc_block_count = kStartBlockMinimum;
    info->alloc_inode_count = 0;
    info->oldest_revision = options.oldest_revision;
  
    // Temporarily set the data_block_count to the total block_count so we can estimate the number
    // of pre-data blocks.
    info->data_block_count = block_count;
  
    // The result of DataStartBlock(info) is based on the current value of info.data_block_count.
    // As a result, the block bitmap may have slightly more space allocated than is necessary.
    size_t usable_blocks =
        JournalStartBlock(*info) < block_count ? block_count - JournalStartBlock(*info) : 0;
  
    // Determine allocation for the journal vs. data blocks based on the number of blocks remaining.
    if (usable_blocks >= kDefaultJournalBlocks * 2) {
      // Regular-sized partition, capable of fitting a data region
      // at least as large as the journal. Give all excess blocks
      // to the data region.
      info->journal_block_count = kDefaultJournalBlocks;
      info->data_block_count = usable_blocks - kDefaultJournalBlocks;
    } else if (usable_blocks >= kMinimumDataBlocks + kMinimumJournalBlocks) {
      // On smaller partitions, give both regions the minimum amount of space,
      // and split the remainder. The choice of where to allocate the "remainder"
      // is arbitrary.
      const size_t remainder_blocks = usable_blocks - (kMinimumDataBlocks + kMinimumJournalBlocks);
      const size_t remainder_for_journal = remainder_blocks / 2;
      const size_t remainder_for_data = remainder_blocks - remainder_for_journal;
      info->journal_block_count = kMinimumJournalBlocks + remainder_for_journal;
      info->data_block_count = kMinimumDataBlocks + remainder_for_data;
    } else {
      // Error, partition too small.
      info->journal_block_count = 0;
      info->data_block_count = 0;
    }
  }
  
  BlobLayoutFormat GetBlobLayoutFormat(const Superblock& info) {
    if (info.format_version >= 0x9) {
      return BlobLayoutFormat::kCompactMerkleTreeAtEnd;
    }
    return BlobLayoutFormat::kPaddedMerkleTreeAtStart;
  }
  
  constexpr char kBlobVmoNamePrefix[] = "blob";
  constexpr char kBlobMerkleVmoNamePrefix[] = "mrkl";
  
  void FormatVmoName(const Inode& node, fbl::StringBuffer<ZX_MAX_NAME_LEN>* out, const char* prefix) {
    digest::Digest digest(node.merkle_root_hash);
    out->Clear();
    out->AppendPrintf("%s-%.8s", prefix, digest.ToString().c_str());
  }
  
  void FormatBlobDataVmoName(const Inode& node, fbl::StringBuffer<ZX_MAX_NAME_LEN>* out) {
    FormatVmoName(node, out, kBlobVmoNamePrefix);
  }
  
  void FormatBlobMerkleVmoName(const Inode& node, fbl::StringBuffer<ZX_MAX_NAME_LEN>* out) {
    FormatVmoName(node, out, kBlobMerkleVmoNamePrefix);
  }
  
  }  // namespace blobfs
  