text: // Copyright 2018 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/fvm/host/sparse_paver.h"
  
  #include <unistd.h>
  
  #include <algorithm>
  #include <memory>
  
  zx_status_t SparsePaver::Create(std::unique_ptr<fvm::host::FileWrapper> wrapper, size_t slice_size,
                                  size_t disk_offset, size_t disk_size,
                                  std::unique_ptr<SparsePaver>* out) {
    std::unique_ptr<SparsePaver> paver(new SparsePaver(disk_offset, disk_size));
  
    zx_status_t status = paver->Init(std::move(wrapper), slice_size);
  
    if (status != ZX_OK) {
      return status;
    }
  
    *out = std::move(paver);
    return status;
  }
  
  zx_status_t SparsePaver::AddPartition(const SparsePartitionInfo* partition,
                                        fvm::SparseReader* reader) {
    info_.CheckValid();
  
    // Assign random guid.
    uint8_t guid[fvm::kGuidSize];
    static unsigned int seed = static_cast<unsigned int>(time(0));
    for (size_t i = 0; i < fvm::kGuidSize; i++) {
      guid[i] = static_cast<uint8_t>(rand_r(&seed));
    }
  
    uint32_t vpart_index;
    zx_status_t status = info_.AllocatePartition(partition->descriptor, guid, &vpart_index);
    if (status != ZX_OK) {
      return status;
    }
  
    // Allocate all slices for this partition.
    for (uint32_t i = 0; i < partition->descriptor.extent_count; i++) {
      status = AddExtent(vpart_index, &partition->extents[i], reader);
      if (status != ZX_OK) {
        return status;
      }
    }
  
    return ZX_OK;
  }
  
  zx_status_t SparsePaver::Commit() {
    info_.CheckValid();
  
    if (disk_ptr_ > disk_offset_ + disk_size_) {
      fprintf(stderr, "FVM metadata size exceeds disk size\n");
      return ZX_ERR_INTERNAL;
    }
  
    zx_status_t status = info_.Write(file_.get(), disk_offset_, disk_size_);
  
    if (status != ZX_OK) {
      return status;
    }
  
    // Move pointer to the end of the designated partition size to prevent any further edits.
    disk_ptr_ = disk_offset_ + disk_size_ + 1;
    file_->Sync();
    return ZX_OK;
  }
  
  zx_status_t SparsePaver::Init(std::unique_ptr<fvm::host::FileWrapper> wrapper, size_t slice_size) {
    file_ = std::move(wrapper);
    zx_status_t status = info_.Reset(disk_size_, slice_size);
    if (status != ZX_OK) {
      return status;
    }
  
    disk_ptr_ = disk_offset_ + info_.SuperBlock().GetDataStartOffset();
    if (disk_ptr_ >= disk_offset_ + disk_size_) {
      fprintf(stderr, "FVM metadata size exceeds disk size\n");
      return ZX_ERR_INTERNAL;
    }
  
    off_t result = file_->Seek(disk_ptr_, SEEK_SET);
    if (result < 0 || static_cast<size_t>(result) != disk_ptr_) {
      return ZX_ERR_IO;
    }
  
    data_.reset(new uint8_t[info_.SliceSize()]);
    return ZX_OK;
  }
  
  zx_status_t SparsePaver::AddExtent(uint32_t vpart_index, fvm::ExtentDescriptor* extent,
                                     fvm::SparseReader* reader) {
    size_t bytes_left = extent->extent_length;
    if (extent->slice_start > std::numeric_limits<uint32_t>::max()) {
      return ZX_ERR_INTERNAL;
    }
  
    ExtentInfo extent_info{
        .vslice_start = extent->slice_start,
        .vslice_count = static_cast<uint32_t>(extent->slice_count),
    };
    auto pslice_or = info_.AllocateSlicesContiguous(vpart_index, extent_info);
    if (pslice_or.is_error()) {
      fprintf(stderr, "Failed to allocate slices: %d\n", pslice_or.status_value());
      return pslice_or.status_value();
    }
    for (unsigned i = 0; i < extent->slice_count; i++) {
      if (zx_status_t status = WriteSlice(&bytes_left, reader); status != ZX_OK) {
        return status;
      }
    }
  
    return ZX_OK;
  }
  
  zx_status_t SparsePaver::WriteSlice(size_t* bytes_left, fvm::SparseReader* reader) {
    info_.CheckValid();
    const size_t slice_size = info_.SliceSize();
  
    if (disk_ptr_ + slice_size > disk_offset_ + disk_size_) {
      fprintf(stderr, "%lu + %lu exceeds the provided disk size (%lu)\n", disk_ptr_, slice_size,
              disk_offset_ + disk_size_);
      return ZX_ERR_INVALID_ARGS;
    }
  
    size_t read_length = std::min(slice_size, *bytes_left);
  
    if (read_length > 0) {
      size_t bytes_read = 0;
      zx_status_t status = reader->ReadData(data_.get(), read_length, &bytes_read);
      if (status != ZX_OK) {
        return status;
      }
  
      if (bytes_read < read_length) {
        fprintf(stderr, "Slice data is less than expected\n");
        return ZX_ERR_IO;
      }
  
      if (read_length < slice_size) {
        memset(&data_[read_length], 0, slice_size - read_length);
      }
  
      *bytes_left -= bytes_read;
    } else {
      memset(data_.get(), 0, slice_size);
    }
  
    ssize_t result = file_->Write(data_.get(), slice_size);
    if (result < 0 || static_cast<size_t>(result) != slice_size) {
      return ZX_ERR_IO;
    }
  
    disk_ptr_ += slice_size;
    return ZX_OK;
  }
  