1 : /*
2 : * Copyright (c) 2011 The Native Client Authors. All rights reserved.
3 : * Use of this source code is governed by a BSD-style license that can be
4 : * found in the LICENSE file.
5 : */
6 :
7 : /*
8 : * NaCl Service Runtime. Directory descriptor / Handle abstraction.
9 : *
10 : * Note that we avoid using the thread-specific data / thread local
11 : * storage access to the "errno" variable, and instead use the raw
12 : * system call return interface of small negative numbers as errors.
13 : */
14 :
15 : #include <dirent.h>
16 : #include <errno.h>
17 : #include <fcntl.h>
18 : #include <limits.h>
19 : #include <linux/types.h>
20 : #include <linux/unistd.h>
21 : #include <stddef.h>
22 : #include <stdint.h>
23 : #include <string.h>
24 : #include <sys/mman.h>
25 : #include <sys/stat.h>
26 : #include <sys/types.h>
27 : #include <unistd.h>
28 :
29 : #include "native_client/src/shared/platform/nacl_check.h"
30 : #include "native_client/src/shared/platform/nacl_host_desc.h"
31 : #include "native_client/src/shared/platform/nacl_host_dir.h"
32 : #include "native_client/src/shared/platform/nacl_log.h"
33 : #include "native_client/src/shared/platform/nacl_sync.h"
34 : #include "native_client/src/shared/platform/nacl_sync_checked.h"
35 :
36 : #include "native_client/src/trusted/service_runtime/nacl_config.h"
37 :
38 : #include "native_client/src/trusted/service_runtime/include/bits/mman.h"
39 : #include "native_client/src/trusted/service_runtime/include/sys/dirent.h"
40 : #include "native_client/src/trusted/service_runtime/include/sys/errno.h"
41 : #include "native_client/src/trusted/service_runtime/include/sys/fcntl.h"
42 : #include "native_client/src/trusted/service_runtime/include/sys/stat.h"
43 :
44 : #ifdef _syscall3
45 : _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count)
46 :
47 : int getdents(unsigned int fd, struct dirent* dirp, unsigned int count);
48 : #else
49 : # include <sys/syscall.h>
50 9 : int getdents(unsigned int fd, struct dirent* dirp, unsigned int count) {
51 9 : return syscall(__NR_getdents, fd, dirp, count);
52 : }
53 : #endif
54 :
55 : struct linux_dirent { /* offsets, ILP32 and LP64 */
56 : unsigned long d_ino; /* 0, 0 */
57 : unsigned long d_off; /* 4, 8 */
58 : unsigned short d_reclen; /* 8, 16 */
59 : char d_name[1]; /* 10, 18 */
60 : /* actual length is d_reclen - 2 - offsetof(struct linux_dirent, d_name) */
61 : /*
62 : * char pad; / Zero padding byte
63 : * char d_type; / File type (only since Linux 2.6.4; offset is d_reclen - 1)
64 : */
65 : };
66 :
67 :
68 : /*
69 : * from native_client/src/trusted/service_runtime/include/sys/dirent.h:
70 :
71 : struct nacl_abi_dirent { offsets, NaCl
72 : nacl_abi_ino_t nacl_abi_d_ino; 0
73 : nacl_abi_off_t nacl_abi_d_off; 8
74 : uint16_t nacl_abi_d_reclen; 16
75 : char nacl_abi_d_name[NACL_ABI_MAXNAMLEN + 1]; 18
76 : };
77 :
78 : * It would be nice if we didn't have to buffer dirent data. Is it
79 : * possible, when the untrusted NaCl module invokes getdent, to
80 : * determine a (different) buffer size value to use with the host OS
81 : * (Linux)?
82 : *
83 : * Since the actual length is d_reclen, do we know if d_reclen is
84 : * padded out to a multiple of the alignment restriction for longs?
85 : * Best to assume that either may hold. On x86 where unaligned
86 : * accesses are okay, this might be fine; on other architectures where
87 : * it isn't, then presumably d_reclen will include the padding, and
88 : * d_type (which we don't use) is on the far end of the padding.
89 : *
90 : * Given a user-supplied buffer of N bytes to hold nacl_abi_dirent
91 : * entries, what is the size of the buffer that should be supplied to
92 : * the Linux kernel so that we will never end up with more information
93 : * than we can copy / return back to the user?
94 : *
95 : * For LP64, the answer is relatively simple: everything is the same
96 : * size, except that the kernel is going to use one more byte for the
97 : * d_type entry. Since copying to the nacl_abi_dirent omits that, the
98 : * transfer shrinks the space needed.
99 : *
100 : * For ILP32, the answer is a little harder. The linux_dirent use 8
101 : * fewer bytes for the entries before d_name, but one more at the end
102 : * for d_type. So, when does the worst case expansion occur? The
103 : * number of dirent entries is multiplied by the expansion, so we need
104 : * to determine the smallest dirent entry. Assuming single character
105 : * file names, a user's buffer of size N can hold int(N/20) dirents.
106 : * The linux_dirent, with no alignment pads, but with d_type (we
107 : * assume newer kernels only), will take 13 bytes per entry, so we had
108 : * better not claim to have more than 13*int(N/20) bytes of space
109 : * available to the Linux kernel. (We don't need to check in our
110 : * platform qualification check since "older" kernels are pre 2.6.4,
111 : * and all reasonable Linux distributions use newer kernels.)
112 : *
113 : * Suppose the user gave us a buffer of 40 bytes. This is enough for
114 : * two dirent structures containing information about files each with
115 : * a single character name. So, we invoke Linux's getdents with a 26
116 : * byte buffer. What happens if the next actual directory entry's
117 : * name is 40-18-1=21 bytes long? It would have fit in the user's
118 : * buffer, but the linux_dirent buffer required for that entry is
119 : * 10+21+2 bytes in length, or 33 bytes. We supplied linux with a 26
120 : * byte buffer, so it should respond with EINVAL since the buffer
121 : * space is too small.
122 : *
123 : * This argues against a simple scheme that avoids buffering.
124 : *
125 : * We could, when we encounter EINVAL, increase the buffer size used
126 : * with the host OS. How do we expand and could that might result in
127 : * too much being read in?
128 : */
129 :
130 4 : int NaClHostDirCtor(struct NaClHostDir *d,
131 : int dir_desc) {
132 4 : if (!NaClMutexCtor(&d->mu)) {
133 0 : return -NACL_ABI_ENOMEM;
134 : }
135 4 : d->fd = dir_desc;
136 4 : d->cur_byte = 0;
137 4 : d->nbytes = 0;
138 4 : NaClLog(3, "NaClHostDirCtor: success.\n");
139 4 : return 0;
140 : }
141 :
142 4 : int NaClHostDirOpen(struct NaClHostDir *d,
143 : char *path) {
144 : int fd;
145 : struct stat stbuf;
146 : int rv;
147 :
148 4 : NaClLog(3, "NaClHostDirOpen(0x%08"NACL_PRIxPTR", %s)\n", (uintptr_t) d, path);
149 4 : if (NULL == d) {
150 0 : NaClLog(LOG_FATAL, "NaClHostDirOpen: 'this' is NULL\n");
151 : }
152 :
153 4 : NaClLog(3, "NaClHostDirOpen: invoking open(%s)\n", path);
154 4 : fd = open(path, O_RDONLY);
155 4 : NaClLog(3, "NaClHostDirOpen: got DIR* %d\n", fd);
156 4 : if (-1 == fd) {
157 0 : NaClLog(LOG_ERROR,
158 0 : "NaClHostDirOpen: open returned -1, errno %d\n", errno);
159 0 : return -NaClXlateErrno(errno);
160 : }
161 : /* check that it is really a directory */
162 4 : if (-1 == fstat(fd, &stbuf)) {
163 0 : NaClLog(LOG_ERROR,
164 0 : "NaClHostDirOpen: fstat failed?!? errno %d\n", errno);
165 0 : (void) close(fd);
166 0 : return -NaClXlateErrno(errno);
167 : }
168 4 : if (!S_ISDIR(stbuf.st_mode)) {
169 0 : (void) close(fd);
170 0 : return -NACL_ABI_ENOTDIR;
171 : }
172 4 : rv = NaClHostDirCtor(d, fd);
173 4 : return rv;
174 : }
175 :
176 : /*
177 : * Copy and translate a single linux_dirent to nacl_abi_dirent.
178 : * Returns number of bytes consumed (includes alignment adjustment for
179 : * next entry).
180 : *
181 : * TODO(bsy): add filesystem info argument to specify which
182 : * directories are "root" inodes, to rewrite the inode number of '..'
183 : * as appropriate.
184 : */
185 92 : static ssize_t NaClCopyDirent(struct NaClHostDir *d,
186 : void *buf,
187 : size_t len) {
188 92 : struct linux_dirent *ldp = (struct linux_dirent *) (
189 92 : d->dirent_buf
190 92 : + d->cur_byte);
191 : struct nacl_abi_dirent volatile *nadp;
192 : size_t adjusted_size;
193 :
194 : /* make sure the buffer is aligned */
195 92 : CHECK(0 == ((sizeof(nacl_abi_ino_t) - 1) & (uintptr_t) buf));
196 :
197 92 : if (d->cur_byte == d->nbytes) {
198 9 : return 0; /* none available */
199 : }
200 83 : CHECK(d->cur_byte < d->nbytes);
201 83 : CHECK(ldp->d_reclen <= d->nbytes - d->cur_byte);
202 : /* no partial record transferred. */
203 :
204 83 : nadp = (struct nacl_abi_dirent volatile *) buf;
205 :
206 : /*
207 : * is there enough space? assume Linux is sane, so no ssize_t
208 : * overflow in the adjusted_size computation. (NAME_MAX is small.)
209 : */
210 : CHECK(NAME_MAX < 256);
211 83 : adjusted_size = offsetof(struct nacl_abi_dirent, nacl_abi_d_name)
212 83 : + strlen(ldp->d_name) + 1; /* NUL termination */
213 : /* pad for alignment for access to d_ino */
214 83 : adjusted_size = (adjusted_size + (sizeof(nacl_abi_ino_t) - 1))
215 : & ~(sizeof(nacl_abi_ino_t) - 1);
216 83 : if (len < adjusted_size) {
217 12 : return -NACL_ABI_EINVAL; /* result buffer is too small */
218 : }
219 :
220 : #if defined(NACL_MASK_INODES)
221 71 : nadp->nacl_abi_d_ino = NACL_FAKE_INODE_NUM;
222 : #else
223 : nadp->nacl_abi_d_ino = ldp->d_ino;
224 : #endif
225 71 : nadp->nacl_abi_d_off = ldp->d_off;
226 71 : nadp->nacl_abi_d_reclen = adjusted_size;
227 71 : NaClLog(4, "NaClCopyDirent: %s\n", ldp->d_name);
228 71 : strcpy((char *) nadp->nacl_abi_d_name, ldp->d_name);
229 : /* NB: some padding bytes may not get overwritten */
230 :
231 71 : d->cur_byte += ldp->d_reclen;
232 :
233 71 : NaClLog(4, "NaClCopyDirent: returning %"NACL_PRIuS"\n", adjusted_size);
234 71 : return (ssize_t) adjusted_size;
235 : }
236 :
237 21 : static ssize_t NaClStreamDirents(struct NaClHostDir *d,
238 : void *buf,
239 : size_t len) {
240 : ssize_t retval;
241 21 : size_t xferred = 0;
242 : ssize_t entry_size;
243 :
244 21 : NaClXMutexLock(&d->mu);
245 116 : while (len > 0) {
246 92 : NaClLog(4, "NaClStreamDirents: loop, xferred = %"NACL_PRIuS"\n", xferred);
247 92 : entry_size = NaClCopyDirent(d, buf, len);
248 92 : if (0 == entry_size) {
249 9 : CHECK(d->cur_byte == d->nbytes);
250 9 : retval = getdents(d->fd,
251 9 : (struct dirent *) d->dirent_buf,
252 : sizeof d->dirent_buf);
253 9 : if (-1 == retval) {
254 0 : if (xferred > 0) {
255 : /* next time through, we'll pick up the error again */
256 0 : goto cleanup;
257 : } else {
258 0 : xferred = -NaClXlateErrno(errno);
259 0 : goto cleanup;
260 : }
261 9 : } else if (0 == retval) {
262 6 : goto cleanup;
263 : }
264 3 : d->cur_byte = 0;
265 3 : d->nbytes = retval;
266 83 : } else if (entry_size < 0) {
267 : /*
268 : * The only error return from NaClCopyDirent is NACL_ABI_EINVAL
269 : * due to destinaton buffer too small for the current entry. If
270 : * we had copied some entries before, we were successful;
271 : * otherwise report that the buffer is too small for the next
272 : * directory entry.
273 : */
274 12 : if (xferred > 0) {
275 5 : goto cleanup;
276 : } else {
277 7 : xferred = entry_size;
278 7 : goto cleanup;
279 : }
280 : }
281 : /* entry_size > 0, maybe copy another */
282 74 : buf = (void *) ((char *) buf + entry_size);
283 74 : CHECK(len >= (size_t) entry_size);
284 74 : len -= entry_size;
285 74 : xferred += entry_size;
286 : }
287 : /* perfect fit! */
288 : cleanup:
289 21 : NaClXMutexUnlock(&d->mu);
290 21 : return xferred;
291 : }
292 :
293 21 : ssize_t NaClHostDirGetdents(struct NaClHostDir *d,
294 : void *buf,
295 : size_t len) {
296 : int retval;
297 :
298 21 : if (NULL == d) {
299 0 : NaClLog(LOG_FATAL, "NaClHostDirGetdents: 'this' is NULL\n");
300 : }
301 21 : NaClLog(3, "NaClHostDirGetdents(0x%08"NACL_PRIxPTR", %"NACL_PRIuS"):\n",
302 : (uintptr_t) buf, len);
303 :
304 21 : if (0 != ((__alignof__(struct nacl_abi_dirent) - 1) & (uintptr_t) buf)) {
305 0 : retval = -NACL_ABI_EINVAL;
306 0 : goto cleanup;
307 : }
308 :
309 21 : retval = NaClStreamDirents(d, buf, len);
310 : cleanup:
311 21 : NaClLog(3, "NaClHostDirGetdents: returned %d\n", retval);
312 21 : return retval;
313 : }
314 :
315 1 : int NaClHostDirRewind(struct NaClHostDir *d) {
316 1 : if (NULL == d) {
317 0 : NaClLog(LOG_FATAL, "NaClHostDirRewind: 'this' is NULL\n");
318 : }
319 1 : return -NaClXlateErrno(lseek64(d->fd, 0, SEEK_SET));
320 : }
321 :
322 3 : int NaClHostDirClose(struct NaClHostDir *d) {
323 : int retval;
324 :
325 3 : if (NULL == d) {
326 0 : NaClLog(LOG_FATAL, "NaClHostDirClose: 'this' is NULL\n");
327 : }
328 3 : NaClLog(3, "NaClHostDirClose(%d)\n", d->fd);
329 3 : retval = close(d->fd);
330 3 : d->fd = -1;
331 3 : NaClMutexDtor(&d->mu);
332 3 : return (-1 == retval) ? -NaClXlateErrno(errno) : retval;
333 : }
|