| // 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> |
| |
| namespace fio = ::llcpp::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; |
| } |
| |
| constexpr size_t kMinWindow = PAGE_SIZE * 4; |
| constexpr size_t kMaxWindow = 64 << 20; |
| |
| static zx_status_t read_file_into_vmo(zxio_t* io, zx_handle_t* out_vmo, size_t* out_size) { |
| 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. |
| char buffer[PAGE_SIZE]; |
| size_t xfer = std::min(size, sizeof(buffer)); |
| size_t nread; |
| status = read_at(io, buffer, xfer, offset, &nread); |
| if (status != ZX_OK) { |
| return status; |
| } |
| status = vmo.write(buffer, 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 + PAGE_SIZE - 1) & -PAGE_SIZE; |
| 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::VMO_FLAG_READ | fio::VMO_FLAG_PRIVATE, 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::VMO_FLAG_READ | fio::VMO_FLAG_EXACT, 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::VMO_FLAG_READ | fio::VMO_FLAG_EXEC | fio::VMO_FLAG_PRIVATE, out_vmo, |
| out_size); |
| } |