text: // Copyright 2020 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/zx/vmar.h>
  #include <lib/zx/vmo.h>
  #include <lib/zxio/inception.h>
  #include <lib/zxio/ops.h>
  #include <lib/zxio/zxio.h>
  #include <limits.h>
  #include <string.h>
  #include <zircon/syscalls.h>
  
  #include <algorithm>
  #include <vector>
  
  namespace fio = fuchsia_io;
  
  static zx_status_t read_at(zxio_t* io, void* buf, size_t len, off_t offset, size_t* out_actual) {
    size_t actual = 0u;
    zx_status_t status;
    for (;;) {
      status = zxio_read_at(io, offset, buf, len, 0, &actual);
      if (status == ZX_ERR_SHOULD_WAIT) {
        zxio_signals_t observed = ZXIO_SIGNAL_NONE;
        status = zxio_wait_one(io, ZXIO_SIGNAL_READABLE | ZXIO_SIGNAL_READ_DISABLED, ZX_TIME_INFINITE,
                               &observed);
        if (status != ZX_OK) {
          return status;
        }
        // Retry reading after waiting.
        continue;
      }
      if (status != ZX_OK) {
        return status;
      }
      // Finished |zxio_read_at| successfully.
      break;
    }
    if (actual == 0) {  // EOF (?)
      return ZX_ERR_OUT_OF_RANGE;
    }
    *out_actual = actual;
    return ZX_OK;
  }
  
  static zx_status_t read_file_into_vmo(zxio_t* io, zx_handle_t* out_vmo, size_t* out_size) {
    const size_t kPageSize = zx_system_get_page_size();
    const size_t kMinWindow = kPageSize * 4;
    constexpr size_t kMaxWindow = 64 << 20;
  
    auto current_vmar = zx::vmar::root_self();
  
    zxio_node_attributes_t attr;
    zx_status_t status = zxio_attr_get(io, &attr);
    if (status != ZX_OK) {
      return status;
    }
  
    uint64_t size = attr.content_size;
    uint64_t offset = 0;
  
    zx::vmo vmo;
    status = zx::vmo::create(size, 0, &vmo);
    if (status != ZX_OK) {
      return status;
    }
  
    while (size > 0) {
      if (size < kMinWindow) {
        // There is little enough left that copying is less overhead
        // than fiddling with the page tables.
        std::vector<char> buffer;
        buffer.resize(kPageSize);
        size_t xfer = std::min(size, buffer.size());
        size_t nread;
        status = read_at(io, buffer.data(), xfer, offset, &nread);
        if (status != ZX_OK) {
          return status;
        }
        status = vmo.write(buffer.data(), offset, nread);
        if (status != ZX_OK) {
          return status;
        }
        offset += nread;
        size -= nread;
      } else {
        // Map the VMO into our own address space so we can read into
        // it directly and avoid double-buffering.
        size_t chunk = std::min(size, kMaxWindow);
        size_t window = (chunk + kPageSize - 1) & -kPageSize;
        uintptr_t start = 0;
        status =
            current_vmar->map(ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, 0, vmo, offset, window, &start);
        if (status != ZX_OK) {
          return status;
        }
        uint8_t* buffer = reinterpret_cast<uint8_t*>(start);
        while (chunk > 0) {
          size_t nread;
          status = read_at(io, buffer, chunk, offset, &nread);
          if (status != ZX_OK) {
            current_vmar->unmap(start, window);
            return status;
          }
          buffer += nread;
          offset += nread;
          size -= nread;
          chunk -= nread;
        }
        current_vmar->unmap(start, window);
      }
    }
  
    *out_vmo = vmo.release();
    if (out_size) {
      *out_size = attr.content_size;
    }
    return ZX_OK;
  }
  
  zx_status_t zxio_vmo_get_copy(zxio_t* io, zx_handle_t* out_vmo, size_t* out_size) {
    zx_status_t status = zxio_vmo_get_clone(io, out_vmo, out_size);
    if (status == ZX_OK) {
      return ZX_OK;
    }
    zx::vmo vmo;
    status = read_file_into_vmo(io, vmo.reset_and_get_address(), out_size);
    if (status != ZX_OK) {
      return status;
    }
    status = vmo.replace(ZX_RIGHTS_BASIC | ZX_RIGHTS_PROPERTY | ZX_RIGHT_READ | ZX_RIGHT_MAP, &vmo);
    if (status != ZX_OK) {
      return status;
    }
    *out_vmo = vmo.release();
    return ZX_OK;
  }
  
  zx_status_t zxio_vmo_get_clone(zxio_t* io, zx_handle_t* out_vmo, size_t* out_size) {
    return zxio_vmo_get(io, fio::wire::kVmoFlagRead | fio::wire::kVmoFlagPrivate, out_vmo, out_size);
  }
  
  zx_status_t zxio_vmo_get_exact(zxio_t* io, zx_handle_t* out_vmo, size_t* out_size) {
    return zxio_vmo_get(io, fio::wire::kVmoFlagRead | fio::wire::kVmoFlagExact, out_vmo, out_size);
  }
  
  zx_status_t zxio_vmo_get_exec(zxio_t* io, zx_handle_t* out_vmo, size_t* out_size) {
    return zxio_vmo_get(
        io, fio::wire::kVmoFlagRead | fio::wire::kVmoFlagExec | fio::wire::kVmoFlagPrivate, out_vmo,
        out_size);
  }
  