1 : /*
2 : * Copyright 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 : * Exercise the NaClHostDir NaClHostDirGetdents interface.
9 : *
10 : * We use testdata input pairs: a file containing an expected
11 : * directory listing, and a sample directory. The contents are just
12 : * the d_name portion of the directory entry, since none of the other
13 : * data will be preserved across SCM checkouts, etc. We check, as a
14 : * heurstic, that the d_ino part is identical to that which results
15 : * from a NaClHostDescStat call (platform library) after
16 : * NaClAbiStatHostDescStatXlate processing (desc library),
17 : *
18 : * NB: the interface being tested is not uniform/cross platform. In
19 : * particular, unless UNC paths are used on Windows (which we are not
20 : * doing), the total path name for a file is MAX_PATH, or 260
21 : * characters. Since this maximum depends on where in the directory
22 : * tree is a file located, it is impossible to know a priori whether
23 : * creating a file will succeed or not without constructing the full
24 : * path. Anyway, since directory descriptors are not made available
25 : * except with the -d flag, and the likely use of this code, if at
26 : * all, will be in non-browser sandboxing contexts where we won't run
27 : * on that many host OSes, we're not going to try to abstract away the
28 : * host OS differences. For WebFS, there will be a separate directory
29 : * abstraction that is provided by (and is the responsibility of) the
30 : * host browser, and different code paths will be used, hopefully
31 : * providing a host-OS platform independent view.
32 : */
33 :
34 : #include <stdio.h>
35 : #include <stdlib.h>
36 : #include <string.h>
37 : #include <sys/types.h>
38 : #include <sys/stat.h>
39 :
40 : #include "native_client/src/include/portability_io.h"
41 :
42 : #include "native_client/src/shared/platform/nacl_check.h"
43 : #include "native_client/src/shared/platform/nacl_host_desc.h"
44 : #include "native_client/src/shared/platform/nacl_host_dir.h"
45 : #include "native_client/src/shared/platform/platform_init.h"
46 : #include "native_client/src/trusted/desc/nacl_desc_base.h"
47 : #include "native_client/src/trusted/service_runtime/include/sys/dirent.h"
48 : #include "native_client/src/trusted/service_runtime/include/sys/errno.h"
49 : #include "native_client/src/trusted/service_runtime/include/sys/stat.h"
50 :
51 : #define MAXLINE 1024 /* 256 = NAME_MAX+1 should suffice */
52 : #define JOINED_MAX 4096
53 :
54 : struct string_array {
55 : char **strings;
56 : size_t nelts;
57 : size_t allocated_elts;
58 : };
59 :
60 : #if NACL_WINDOWS
61 : static char const path_sep = '\\';
62 : #else
63 : static char const path_sep = '/';
64 : #endif
65 :
66 : int verbosity = 0;
67 :
68 1 : void StringArrayCtor(struct string_array *self) {
69 1 : self->strings = NULL;
70 1 : self->nelts = 0;
71 1 : self->allocated_elts = 0;
72 1 : }
73 :
74 1 : void StringArrayGrow(struct string_array *self) {
75 1 : size_t desired = 2 * self->allocated_elts;
76 : char **new_strings;
77 :
78 1 : if (0 == desired) desired = 16;
79 : new_strings = (char **) realloc(self->strings,
80 1 : desired * sizeof *self->strings);
81 1 : CHECK(NULL != new_strings);
82 1 : self->strings = new_strings;
83 1 : self->allocated_elts = desired;
84 1 : }
85 :
86 : void StringArrayAdd(struct string_array *self,
87 1 : char *entry) {
88 1 : CHECK(self->nelts <= self->allocated_elts);
89 1 : if (self->nelts == self->allocated_elts) {
90 1 : StringArrayGrow(self);
91 : }
92 1 : self->strings[self->nelts++] = entry;
93 1 : }
94 :
95 1 : static int strcmp_wrapper(const void *left, const void *right) {
96 1 : return strcmp(*(const char **) left, *(const char **) right);
97 1 : }
98 :
99 1 : void StringArraySort(struct string_array *self) {
100 1 : qsort(self->strings, self->nelts, sizeof(*self->strings), strcmp_wrapper);
101 1 : }
102 :
103 : uint32_t ExtractNames(struct string_array *dest_array,
104 : void *buffer,
105 : size_t buffer_size,
106 1 : char const *dir_path) {
107 : struct nacl_abi_dirent *nadp;
108 : char path[4096];
109 : nacl_host_stat_t host_stat;
110 : int rv;
111 : int32_t rv32;
112 : struct nacl_abi_stat nabi_stat;
113 1 : uint32_t error_count = 0;
114 :
115 1 : while (buffer_size > 0) {
116 1 : nadp = (struct nacl_abi_dirent *) buffer;
117 1 : StringArrayAdd(dest_array, strdup(nadp->nacl_abi_d_name));
118 :
119 : rv = SNPRINTF(path, sizeof path, "%s%c%s",
120 1 : dir_path, path_sep, nadp->nacl_abi_d_name);
121 1 : if (rv < 0) {
122 0 : fprintf(stderr, "snprintf failed?!? name %s\n", nadp->nacl_abi_d_name);
123 0 : ++error_count;
124 0 : goto next_entry;
125 : }
126 1 : if ((size_t) rv >= sizeof path) {
127 0 : fprintf(stderr, "path too long\n");
128 0 : ++error_count;
129 0 : goto next_entry;
130 : }
131 1 : if (0 != (rv = NaClHostDescStat(path, &host_stat))) {
132 0 : fprintf(stderr, "could not stat %s: %d\n", path, rv);
133 0 : ++error_count;
134 0 : goto next_entry;
135 : }
136 1 : rv32 = NaClAbiStatHostDescStatXlateCtor(&nabi_stat, &host_stat);
137 1 : if (0 != rv32) {
138 0 : fprintf(stderr, "NaClAbiStatHostDescStatXlateCtor failed: %d\n", rv32);
139 0 : ++error_count;
140 0 : goto next_entry;
141 : }
142 : /*
143 : * the inode number may be masked, but both
144 : * NaClAbiStatHostDescStatXlateCtor and NaClHostDirGetdents should
145 : * mask identically.
146 : */
147 1 : if (nabi_stat.nacl_abi_st_ino != nadp->nacl_abi_d_ino) {
148 : fprintf(stderr,
149 : "inode values differ: expected 0x%"NACL_PRIx64
150 : ", got 0x%"NACL_PRIx64"\n",
151 0 : nabi_stat.nacl_abi_st_ino, nadp->nacl_abi_d_ino);
152 0 : ++error_count;
153 : }
154 : next_entry:
155 1 : buffer = (void *) ((char *) buffer + nadp->nacl_abi_d_reclen);
156 1 : CHECK(buffer_size >= nadp->nacl_abi_d_reclen);
157 1 : buffer_size -= nadp->nacl_abi_d_reclen;
158 1 : }
159 1 : return error_count;
160 1 : }
161 :
162 : int OperateOnDir(char *dir_name, struct string_array *file_list,
163 1 : int (*op)(char const *path)) {
164 : char joined[JOINED_MAX];
165 : size_t ix;
166 1 : int errors = 0;
167 :
168 1 : for (ix = 0; ix < file_list->nelts; ++ix) {
169 : if (0 == strcmp(file_list->strings[ix], ".") ||
170 1 : 0 == strcmp(file_list->strings[ix], "..")) {
171 1 : continue;
172 : }
173 : if ((size_t) SNPRINTF(joined, sizeof joined, "%s%c%s",
174 : dir_name, path_sep, file_list->strings[ix])
175 1 : >= sizeof joined) {
176 : fprintf(stderr, "path buffer too small for %s%c%s\n",
177 0 : dir_name, path_sep, file_list->strings[ix]);
178 0 : ++errors;
179 0 : continue;
180 : }
181 : #if NACL_WINDOWS
182 1 : if (strlen(joined) > MAX_PATH) {
183 : /*
184 : * Yuck. We remove this entry from the list, since later we are
185 : * going to compare against what's actually read in, and if an
186 : * ignored entry is left in, then the comparison will fail. We
187 : * mark the entry as deleted, and then we repair the string
188 : * list.
189 : */
190 1 : free(file_list->strings[ix]);
191 1 : file_list->strings[ix] = NULL;
192 1 : continue;
193 : }
194 : #endif
195 1 : errors += (*op)(joined);
196 1 : }
197 : #if NACL_WINDOWS
198 1 : if (1) {
199 : size_t dest_ix;
200 1 : for (ix = dest_ix = 0; ix < file_list->nelts; ++ix) {
201 1 : if (0 != file_list->strings[ix]) {
202 1 : file_list->strings[dest_ix] = file_list->strings[ix];
203 1 : ++dest_ix;
204 : }
205 1 : }
206 1 : file_list->nelts = dest_ix;
207 : }
208 : #endif
209 1 : return errors;
210 1 : }
211 :
212 1 : static int NaClCreateFile(char const *joined) {
213 : int fd;
214 :
215 1 : if (1 < verbosity) {
216 0 : printf("CreateFile(%s)\n", joined);
217 : }
218 1 : fd = OPEN(joined, O_CREAT | O_TRUNC | O_WRONLY, 0777);
219 1 : if (-1 == fd) {
220 0 : perror("nacl_host_dir_test:CreateFile");
221 0 : fprintf(stderr, "could not create file %s\n", joined);
222 0 : return 1;
223 : }
224 1 : (void) CLOSE(fd);
225 1 : return 0;
226 1 : }
227 :
228 1 : int PopulateDirectory(char *dir_name, struct string_array *file_list) {
229 : /*
230 : * Typical use is from SCons, where Python's tempfile module picks a
231 : * directory name and creates it, so it would pre-exist. For ease
232 : * of direct, non-SCons test execution, we try to create the
233 : * directory here.
234 : */
235 1 : (void) MKDIR(dir_name, 0777);
236 1 : return OperateOnDir(dir_name, file_list, NaClCreateFile);
237 1 : }
238 :
239 1 : static int NaClDeleteFile(char const *joined) {
240 : int retval;
241 :
242 1 : if (1 < verbosity) {
243 0 : printf("DeleteFile(%s)\n", joined);
244 : }
245 1 : retval = UNLINK(joined);
246 1 : if (-1 == retval) {
247 0 : perror("nacl_hst_dir_test:DeleteFile");
248 0 : return 1;
249 : }
250 1 : return 0;
251 1 : }
252 :
253 1 : int CleanupDirectory(char *dirname, struct string_array *file_list) {
254 1 : int errors = 0;
255 :
256 1 : if (0 != (errors += OperateOnDir(dirname, file_list, NaClDeleteFile))) {
257 0 : return errors;
258 : }
259 1 : if (1 < verbosity) {
260 0 : printf("rmdir(%s)\n", dirname);
261 : }
262 1 : if (0 != rmdir(dirname)) {
263 1 : perror("nacl_host_dir_test: rmdir");
264 : #if !NACL_WINDOWS
265 : /*
266 : * Spurious errors found on Windows due to other processes looking
267 : * cross-eyed at the directory. A similar problem could occur, in
268 : * principle, on the unlink, but so far this has't been observed.
269 : */
270 : ++errors;
271 : #endif
272 : }
273 1 : return errors;
274 1 : }
275 :
276 : struct dirent_buffers {
277 : char const *description;
278 : size_t buffer_bytes;
279 : };
280 :
281 1 : int main(int argc, char **argv) {
282 : int opt;
283 1 : char *test_dir = NULL;
284 1 : char *test_file = NULL;
285 : FILE *test_iop;
286 : struct string_array expected;
287 : char line_buf[MAXLINE];
288 : size_t ix;
289 : ssize_t nbytes;
290 : int retval;
291 : struct NaClHostDir nhd;
292 : struct string_array actual;
293 :
294 : union {
295 : struct nacl_abi_dirent nad;
296 : char buffer[1024 + sizeof(uint64_t)];
297 : } actual_buffer;
298 : void *aligned_buffer =
299 : (void *) (((uintptr_t) &actual_buffer.buffer + sizeof(uint64_t) - 1)
300 1 : & ~(sizeof(uint64_t) - 1));
301 : /*
302 : * our ABI requires -malign-double in untrusted code but our trusted
303 : * build does not (and cannot, since this affects the interface to
304 : * system libraries), so we have to manually align.
305 : */
306 :
307 : struct dirent_buffers buffers[] = {
308 1 : { "small", 20, },
309 1 : { "medium", 24, },
310 1 : { "large", 300, },
311 1 : { "extra large", 1024, },
312 : };
313 :
314 : size_t min_elts;
315 1 : uint32_t error_count = 0;
316 :
317 1 : NaClPlatformInit();
318 :
319 1 : while (EOF != (opt = getopt(argc, argv, "f:d:v"))) {
320 1 : switch (opt) {
321 : case 'f':
322 1 : test_file = optarg;
323 1 : break;
324 : case 'd':
325 1 : test_dir = optarg;
326 1 : break;
327 : case 'v':
328 0 : ++verbosity;
329 0 : break;
330 : default:
331 : fprintf(stderr,
332 : "Usage: nacl_host_dir_test [-v] [-f file] [-d dir]\n"
333 : "\n"
334 : " -v increases verbosity level\n"
335 : " -f <file> specifes a file containing the expected\n"
336 : " directory contents,\n"
337 : " -d <dir> is the test directory to be scanned\n"
338 0 : " using the NaClHostDir routines.\n");
339 0 : return -1;
340 : }
341 1 : }
342 1 : if (NULL == test_file) {
343 : fprintf(stderr,
344 : "nacl_host_dir_test: expected directory content file"
345 0 : " (-f) required\n");
346 0 : return 1;
347 : }
348 1 : if (NULL == test_dir) {
349 : fprintf(stderr,
350 : "nacl_host_dir_test: test directory"
351 0 : " (-d) required\n");
352 0 : return 2;
353 : }
354 1 : test_iop = fopen(test_file, "r");
355 1 : if (NULL == test_iop) {
356 : fprintf(stderr,
357 : "nacl_host_dir_test: could not open expected directory"
358 : " content file %s\n",
359 0 : test_file);
360 0 : return 3;
361 : }
362 1 : StringArrayCtor(&expected);
363 1 : while (NULL != fgets(line_buf, sizeof line_buf, test_iop)) {
364 : /*
365 : * We do not have newlines in the file names, though they are
366 : * permitted on Linux -- actually, any POSIX-compliant system.
367 : * Ideally, the test data would use NUL as filename terminators.
368 : *
369 : * On Windows, we do not support names greater than NAME_MAX
370 : * characters...
371 : */
372 1 : size_t len = strlen(line_buf);
373 :
374 1 : CHECK(0 < len);
375 1 : CHECK('\n' == line_buf[len-1]);
376 1 : line_buf[len-1] = '\0';
377 1 : StringArrayAdd(&expected, strdup(line_buf));
378 1 : }
379 1 : StringArraySort(&expected);
380 :
381 1 : printf("\n\nAttempting to create directory contents:\n\n");
382 1 : for (ix = 0; ix < expected.nelts; ++ix) {
383 1 : printf("%s\n", expected.strings[ix]);
384 1 : }
385 :
386 1 : if (0 != PopulateDirectory(test_dir, &expected)) {
387 0 : retval = 4;
388 0 : goto cleanup;
389 : }
390 :
391 : printf("\n\nExpected actual directory contents"
392 1 : " (some may get omitted on Windows):\n\n");
393 1 : for (ix = 0; ix < expected.nelts; ++ix) {
394 1 : printf("%s\n", expected.strings[ix]);
395 1 : }
396 :
397 :
398 1 : StringArrayCtor(&actual);
399 :
400 1 : retval = NaClHostDirOpen(&nhd, test_dir);
401 1 : if (0 != retval) {
402 0 : fprintf(stderr, "Could not open directory %s\n", test_dir);
403 0 : fprintf(stderr, "Error code %d\n", retval);
404 0 : goto cleanup;
405 : }
406 : for (;;) {
407 1 : for (ix = 0; ix < sizeof buffers/sizeof buffers[0]; ++ix) {
408 1 : if (0 < verbosity) {
409 0 : printf("Using %s buffer\n", buffers[ix].description);
410 : }
411 : nbytes = NaClHostDirGetdents(&nhd,
412 : aligned_buffer,
413 1 : buffers[ix].buffer_bytes);
414 1 : if (0 == nbytes) {
415 1 : if (0 != ix) {
416 0 : fprintf(stderr, "EOF on larger buffer but not smallest?!?\n");
417 0 : retval = 5;
418 0 : goto cleanup;
419 1 : }
420 : goto double_break;
421 1 : } else if (0 < nbytes) {
422 1 : error_count += ExtractNames(&actual, aligned_buffer, nbytes, test_dir);
423 1 : break;
424 : }
425 : /* nbytes < 0 must hold */
426 1 : if (-NACL_ABI_EINVAL != nbytes) {
427 0 : fprintf(stderr, "Unexpected error return %d\n", retval);
428 0 : retval = 6;
429 0 : goto cleanup;
430 : }
431 1 : if (ix + 1 == sizeof buffers/sizeof buffers[0]) {
432 0 : fprintf(stderr, "Largest buffer insufficient!?!\n");
433 0 : retval = 7;
434 0 : goto cleanup;
435 : }
436 1 : }
437 1 : }
438 : double_break:
439 1 : StringArraySort(&actual);
440 :
441 1 : printf("\n\nActual directory contents:\n\n");
442 1 : for (ix = 0; ix < actual.nelts; ++ix) {
443 1 : printf("%s\n", actual.strings[ix]);
444 1 : }
445 :
446 1 : printf("Comparing...\n");
447 1 : retval = 0;
448 1 : if (expected.nelts != actual.nelts) {
449 : fprintf(stderr,
450 : "Number of directory entries differ"
451 : " (%"NACL_PRIuS" expected, vs %"NACL_PRIuS" actual)\n",
452 0 : expected.nelts, actual.nelts);
453 0 : retval = 8;
454 : }
455 :
456 1 : min_elts = (expected.nelts > actual.nelts) ? actual.nelts : expected.nelts;
457 :
458 1 : for (ix = 0; ix < min_elts; ++ix) {
459 1 : if (0 != strcmp(expected.strings[ix], actual.strings[ix])) {
460 : fprintf(stderr,
461 : "Entry %"NACL_PRIuS" differs: expected %s, actual %s\n",
462 0 : ix, expected.strings[ix], actual.strings[ix]);
463 0 : retval = 9;
464 : }
465 1 : }
466 1 : if (0 == retval && 0 != error_count) {
467 0 : retval = 10;
468 : }
469 : cleanup:
470 1 : if (0 != CleanupDirectory(test_dir, &expected)) {
471 0 : if (0 == retval) {
472 0 : retval = 11;
473 : }
474 : }
475 :
476 : /*
477 : * Negative tests: open file as dir, expect consistent error.
478 : */
479 :
480 1 : printf(0 == retval ? "PASS\n" : "FAIL\n");
481 :
482 1 : return retval;
483 1 : }
|