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
4 : * be 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 2 : void StringArrayCtor(struct string_array *self) {
69 2 : self->strings = NULL;
70 2 : self->nelts = 0;
71 2 : self->allocated_elts = 0;
72 2 : }
73 :
74 2 : void StringArrayGrow(struct string_array *self) {
75 2 : size_t desired = 2 * self->allocated_elts;
76 2 : char **new_strings;
77 :
78 4 : if (0 == desired) desired = 16;
79 2 : new_strings = (char **) realloc(self->strings,
80 : desired * sizeof *self->strings);
81 6 : CHECK(NULL != new_strings);
82 2 : self->strings = new_strings;
83 2 : self->allocated_elts = desired;
84 2 : }
85 :
86 18 : void StringArrayAdd(struct string_array *self,
87 18 : char *entry) {
88 54 : CHECK(self->nelts <= self->allocated_elts);
89 18 : if (self->nelts == self->allocated_elts) {
90 2 : StringArrayGrow(self);
91 2 : }
92 18 : self->strings[self->nelts++] = entry;
93 18 : }
94 :
95 38 : static int strcmp_wrapper(const void *left, const void *right) {
96 38 : return strcmp(*(const char **) left, *(const char **) right);
97 : }
98 :
99 2 : void StringArraySort(struct string_array *self) {
100 2 : qsort(self->strings, self->nelts, sizeof(*self->strings), strcmp_wrapper);
101 2 : }
102 :
103 5 : uint32_t ExtractNames(struct string_array *dest_array,
104 5 : void *buffer,
105 5 : size_t buffer_size,
106 5 : char const *dir_path) {
107 5 : struct nacl_abi_dirent *nadp;
108 5 : char path[4096];
109 5 : nacl_host_stat_t host_stat;
110 5 : int rv;
111 5 : int32_t rv32;
112 5 : struct nacl_abi_stat nabi_stat;
113 5 : uint32_t error_count = 0;
114 :
115 19 : while (buffer_size > 0) {
116 9 : nadp = (struct nacl_abi_dirent *) buffer;
117 9 : StringArrayAdd(dest_array, strdup(nadp->nacl_abi_d_name));
118 :
119 9 : rv = SNPRINTF(path, sizeof path, "%s%c%s",
120 : dir_path, path_sep, nadp->nacl_abi_d_name);
121 9 : 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 9 : 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 9 : 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 9 : rv32 = NaClAbiStatHostDescStatXlateCtor(&nabi_stat, &host_stat);
137 9 : 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 9 : if (nabi_stat.nacl_abi_st_ino != nadp->nacl_abi_d_ino) {
148 0 : fprintf(stderr,
149 : "inode values differ: expected 0x%"NACL_PRIx64
150 : ", got 0x%"NACL_PRIx64"\n",
151 : nabi_stat.nacl_abi_st_ino, nadp->nacl_abi_d_ino);
152 0 : ++error_count;
153 9 : }
154 : next_entry:
155 9 : buffer = (void *) ((char *) buffer + nadp->nacl_abi_d_reclen);
156 27 : CHECK(buffer_size >= nadp->nacl_abi_d_reclen);
157 9 : buffer_size -= nadp->nacl_abi_d_reclen;
158 9 : }
159 5 : return error_count;
160 : }
161 :
162 2 : int OperateOnDir(char *dir_name, struct string_array *file_list,
163 2 : int (*op)(char const *path)) {
164 2 : char joined[JOINED_MAX];
165 2 : size_t ix;
166 2 : int errors = 0;
167 :
168 40 : for (ix = 0; ix < file_list->nelts; ++ix) {
169 18 : if (0 == strcmp(file_list->strings[ix], ".") ||
170 16 : 0 == strcmp(file_list->strings[ix], "..")) {
171 4 : continue;
172 : }
173 14 : if ((size_t) SNPRINTF(joined, sizeof joined, "%s%c%s",
174 : dir_name, path_sep, file_list->strings[ix])
175 : >= sizeof joined) {
176 0 : fprintf(stderr, "path buffer too small for %s%c%s\n",
177 : dir_name, path_sep, file_list->strings[ix]);
178 0 : ++errors;
179 0 : continue;
180 : }
181 : #if NACL_WINDOWS
182 : 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 : free(file_list->strings[ix]);
191 : file_list->strings[ix] = NULL;
192 : continue;
193 : }
194 : #endif
195 14 : errors += (*op)(joined);
196 14 : }
197 : #if NACL_WINDOWS
198 : if (1) {
199 : size_t dest_ix;
200 : for (ix = dest_ix = 0; ix < file_list->nelts; ++ix) {
201 : if (0 != file_list->strings[ix]) {
202 : file_list->strings[dest_ix] = file_list->strings[ix];
203 : ++dest_ix;
204 : }
205 : }
206 : file_list->nelts = dest_ix;
207 : }
208 : #endif
209 2 : return errors;
210 : }
211 :
212 7 : static int NaClCreateFile(char const *joined) {
213 7 : int fd;
214 :
215 7 : if (1 < verbosity) {
216 0 : printf("CreateFile(%s)\n", joined);
217 0 : }
218 7 : fd = OPEN(joined, O_CREAT | O_TRUNC | O_WRONLY, 0777);
219 7 : 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 7 : (void) CLOSE(fd);
225 7 : return 0;
226 7 : }
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 : }
238 :
239 7 : static int NaClDeleteFile(char const *joined) {
240 7 : int retval;
241 :
242 7 : if (1 < verbosity) {
243 0 : printf("DeleteFile(%s)\n", joined);
244 0 : }
245 7 : retval = UNLINK(joined);
246 7 : if (-1 == retval) {
247 0 : perror("nacl_hst_dir_test:DeleteFile");
248 0 : return 1;
249 : }
250 7 : return 0;
251 7 : }
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 0 : }
262 1 : if (0 != rmdir(dirname)) {
263 0 : 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 0 : ++errors;
271 : #endif
272 0 : }
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 1 : int opt;
283 1 : char *test_dir = NULL;
284 1 : char *test_file = NULL;
285 1 : FILE *test_iop;
286 1 : struct string_array expected;
287 1 : char line_buf[MAXLINE];
288 1 : size_t ix;
289 1 : ssize_t nbytes;
290 1 : int retval;
291 1 : struct NaClHostDir nhd;
292 1 : struct string_array actual;
293 :
294 : union {
295 : struct nacl_abi_dirent nad;
296 : char buffer[1024 + sizeof(uint64_t)];
297 1 : } actual_buffer;
298 1 : void *aligned_buffer =
299 : (void *) (((uintptr_t) &actual_buffer.buffer + sizeof(uint64_t) - 1)
300 : & ~(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 1 : struct dirent_buffers buffers[] = {
308 : { "small", 20, },
309 : { "medium", 24, },
310 : { "large", 300, },
311 : { "extra large", 1024, },
312 : };
313 :
314 1 : size_t min_elts;
315 1 : uint32_t error_count = 0;
316 :
317 1 : NaClPlatformInit();
318 :
319 4 : while (EOF != (opt = getopt(argc, argv, "f:d:v"))) {
320 2 : 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 0 : 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 : " using the NaClHostDir routines.\n");
339 0 : return -1;
340 : }
341 2 : }
342 1 : if (NULL == test_file) {
343 0 : fprintf(stderr,
344 : "nacl_host_dir_test: expected directory content file"
345 : " (-f) required\n");
346 0 : return 1;
347 : }
348 1 : if (NULL == test_dir) {
349 0 : fprintf(stderr,
350 : "nacl_host_dir_test: test directory"
351 : " (-d) required\n");
352 0 : return 2;
353 : }
354 1 : test_iop = fopen(test_file, "r");
355 1 : if (NULL == test_iop) {
356 0 : fprintf(stderr,
357 : "nacl_host_dir_test: could not open expected directory"
358 : " content file %s\n",
359 : test_file);
360 0 : return 3;
361 : }
362 1 : StringArrayCtor(&expected);
363 11 : 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 9 : size_t len = strlen(line_buf);
373 :
374 27 : CHECK(0 < len);
375 27 : CHECK('\n' == line_buf[len-1]);
376 9 : line_buf[len-1] = '\0';
377 9 : StringArrayAdd(&expected, strdup(line_buf));
378 9 : }
379 1 : StringArraySort(&expected);
380 :
381 1 : printf("\n\nAttempting to create directory contents:\n\n");
382 20 : for (ix = 0; ix < expected.nelts; ++ix) {
383 9 : printf("%s\n", expected.strings[ix]);
384 9 : }
385 :
386 1 : if (0 != PopulateDirectory(test_dir, &expected)) {
387 0 : retval = 4;
388 0 : goto cleanup;
389 : }
390 :
391 1 : printf("\n\nExpected actual directory contents"
392 : " (some may get omitted on Windows):\n\n");
393 20 : for (ix = 0; ix < expected.nelts; ++ix) {
394 9 : printf("%s\n", expected.strings[ix]);
395 9 : }
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 1 : for (;;) {
407 28 : for (ix = 0; ix < sizeof buffers/sizeof buffers[0]; ++ix) {
408 14 : if (0 < verbosity) {
409 0 : printf("Using %s buffer\n", buffers[ix].description);
410 0 : }
411 14 : nbytes = NaClHostDirGetdents(&nhd,
412 : aligned_buffer,
413 : buffers[ix].buffer_bytes);
414 14 : 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 : }
420 1 : goto double_break;
421 13 : } else if (0 < nbytes) {
422 5 : error_count += ExtractNames(&actual, aligned_buffer, nbytes, test_dir);
423 5 : break;
424 : }
425 : /* nbytes < 0 must hold */
426 8 : 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 8 : 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 8 : }
437 5 : }
438 : double_break:
439 1 : StringArraySort(&actual);
440 :
441 1 : printf("\n\nActual directory contents:\n\n");
442 20 : for (ix = 0; ix < actual.nelts; ++ix) {
443 9 : printf("%s\n", actual.strings[ix]);
444 9 : }
445 :
446 1 : printf("Comparing...\n");
447 1 : retval = 0;
448 1 : if (expected.nelts != actual.nelts) {
449 0 : fprintf(stderr,
450 : "Number of directory entries differ"
451 : " (%"NACL_PRIuS" expected, vs %"NACL_PRIuS" actual)\n",
452 : expected.nelts, actual.nelts);
453 0 : retval = 8;
454 0 : }
455 :
456 3 : min_elts = (expected.nelts > actual.nelts) ? actual.nelts : expected.nelts;
457 :
458 20 : for (ix = 0; ix < min_elts; ++ix) {
459 9 : if (0 != strcmp(expected.strings[ix], actual.strings[ix])) {
460 0 : fprintf(stderr,
461 : "Entry %"NACL_PRIuS" differs: expected %s, actual %s\n",
462 : ix, expected.strings[ix], actual.strings[ix]);
463 0 : retval = 9;
464 0 : }
465 9 : }
466 2 : if (0 == retval && 0 != error_count) {
467 0 : retval = 10;
468 1 : }
469 : cleanup:
470 1 : if (0 != CleanupDirectory(test_dir, &expected)) {
471 0 : if (0 == retval) {
472 0 : retval = 11;
473 0 : }
474 0 : }
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 : }
|