1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "private.h"
6 #include "unistd.h"
7 
8 #include <fuchsia/io/c/fidl.h>
9 #include <lib/fdio/io.h>
10 #include <lib/fdio/vfs.h>
11 #include <zircon/process.h>
12 #include <zircon/syscalls.h>
13 
14 #define MIN_WINDOW (PAGE_SIZE * 4)
15 #define MAX_WINDOW ((size_t)64 << 20)
16 
read_at(fdio_t * io,void * buf,size_t len,off_t offset,size_t * actual_len)17 static zx_status_t read_at(fdio_t* io, void* buf, size_t len, off_t offset,
18                            size_t* actual_len) {
19     zx_status_t status;
20     while ((status = io->ops->read_at(io, buf, len, offset)) == ZX_ERR_SHOULD_WAIT) {
21         status = fdio_wait(io, FDIO_EVT_READABLE, ZX_TIME_INFINITE, NULL);
22         if (status != ZX_OK) {
23             return status;
24         }
25     }
26     if (status < 0) {
27         return status;
28     }
29     if (status == 0) { // EOF (?)
30         return ZX_ERR_OUT_OF_RANGE;
31     }
32     *actual_len = status;
33     return ZX_OK;
34 }
35 
read_file_into_vmo(fdio_t * io,zx_handle_t * out_vmo)36 static zx_status_t read_file_into_vmo(fdio_t* io, zx_handle_t* out_vmo) {
37     zx_handle_t current_vmar_handle = zx_vmar_root_self();
38 
39     fuchsia_io_NodeAttributes attr;
40     zx_status_t status = io->ops->get_attr(io, &attr);
41     if (status != ZX_OK) {
42         return ZX_ERR_BAD_HANDLE;
43     }
44 
45     uint64_t size = attr.content_size;
46     uint64_t offset = 0;
47 
48     status = zx_vmo_create(size, 0, out_vmo);
49     if (status != ZX_OK) {
50         return status;
51     }
52 
53     while (size > 0) {
54         if (size < MIN_WINDOW) {
55             // There is little enough left that copying is less overhead
56             // than fiddling with the page tables.
57             char buffer[PAGE_SIZE];
58             size_t xfer = size < sizeof(buffer) ? size : sizeof(buffer);
59             size_t nread;
60             status = read_at(io, buffer, xfer, offset, &nread);
61             if (status != ZX_OK) {
62                 zx_handle_close(*out_vmo);
63                 return status;
64             }
65             status = zx_vmo_write(*out_vmo, buffer, offset, nread);
66             if (status < 0) {
67                 zx_handle_close(*out_vmo);
68                 return status;
69             }
70             offset += nread;
71             size -= nread;
72         } else {
73             // Map the VMO into our own address space so we can read into
74             // it directly and avoid double-buffering.
75             size_t chunk = size < MAX_WINDOW ? size : MAX_WINDOW;
76             size_t window = (chunk + PAGE_SIZE - 1) & -PAGE_SIZE;
77             uintptr_t start = 0;
78             status = zx_vmar_map(current_vmar_handle,
79                 ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
80                 0, *out_vmo, offset, window, &start);
81             if (status != ZX_OK) {
82                 zx_handle_close(*out_vmo);
83                 return status;
84             }
85             uint8_t* buffer = (void*)start;
86             while (chunk > 0) {
87                 size_t nread;
88                 status = read_at(io, buffer, chunk, offset, &nread);
89                 if (status != ZX_OK) {
90                     zx_vmar_unmap(current_vmar_handle, start, window);
91                     zx_handle_close(*out_vmo);
92                     return status;
93                 }
94                 buffer += nread;
95                 offset += nread;
96                 size -= nread;
97                 chunk -= nread;
98             }
99             zx_vmar_unmap(current_vmar_handle, start, window);
100         }
101     }
102 
103     return ZX_OK;
104 }
105 
get_file_vmo(fdio_t * io,zx_handle_t * out_vmo)106 static zx_status_t get_file_vmo(fdio_t* io, zx_handle_t* out_vmo) {
107     return io->ops->get_vmo(io, fuchsia_io_VMO_FLAG_READ | fuchsia_io_VMO_FLAG_EXEC |
108                             fuchsia_io_VMO_FLAG_PRIVATE, out_vmo);
109 }
110 
copy_file_vmo(fdio_t * io,zx_handle_t * out_vmo)111 static zx_status_t copy_file_vmo(fdio_t* io, zx_handle_t* out_vmo) {
112     zx_status_t status = get_file_vmo(io, out_vmo);
113     if (status == ZX_OK) {
114         return ZX_OK;
115     }
116 
117     zx_handle_t vmo;
118     if ((status = read_file_into_vmo(io, &vmo)) == ZX_OK) {
119         status = zx_handle_replace(
120             vmo,
121             ZX_RIGHTS_BASIC | ZX_RIGHTS_PROPERTY |
122             ZX_RIGHT_READ | ZX_RIGHT_MAP,
123             out_vmo);
124     }
125     return status;
126 }
127 
128 __EXPORT
fdio_get_vmo_copy(int fd,zx_handle_t * out_vmo)129 zx_status_t fdio_get_vmo_copy(int fd, zx_handle_t* out_vmo) {
130     fdio_t* io = fd_to_io(fd);
131     if (io == NULL) {
132         return ZX_ERR_BAD_HANDLE;
133     }
134     zx_status_t status = copy_file_vmo(io, out_vmo);
135     fdio_release(io);
136     return status;
137 }
138 
139 __EXPORT
fdio_get_vmo_clone(int fd,zx_handle_t * out_vmo)140 zx_status_t fdio_get_vmo_clone(int fd, zx_handle_t* out_vmo) {
141     fdio_t* io = fd_to_io(fd);
142     if (io == NULL) {
143         return ZX_ERR_BAD_HANDLE;
144     }
145     zx_status_t status = get_file_vmo(io, out_vmo);
146     fdio_release(io);
147     return status;
148 }
149 
150 __EXPORT
fdio_get_vmo_exact(int fd,zx_handle_t * out_vmo)151 zx_status_t fdio_get_vmo_exact(int fd, zx_handle_t* out_vmo) {
152     fdio_t* io = fd_to_io(fd);
153     if (io == NULL) {
154         return ZX_ERR_BAD_HANDLE;
155     }
156 
157     zx_status_t status = io->ops->get_vmo(io, fuchsia_io_VMO_FLAG_READ | fuchsia_io_VMO_FLAG_EXEC |
158                                           fuchsia_io_VMO_FLAG_EXACT, out_vmo);
159     fdio_release(io);
160 
161     return status;
162 }
163