]>
Commit | Line | Data |
---|---|---|
320054e8 DG |
1 | // Copyright (c) 2015-2017 Nuxi, https://nuxi.nl/ |
2 | // | |
3 | // SPDX-License-Identifier: BSD-2-Clause | |
4 | ||
5 | #include <sys/stat.h> | |
be1ffd6a DG |
6 | #include <sys/types.h> |
7 | #include <unistd.h> | |
8 | #include <fcntl.h> | |
320054e8 DG |
9 | |
10 | #include <assert.h> | |
446cb3f1 | 11 | #include <wasi/api.h> |
320054e8 DG |
12 | #include <dirent.h> |
13 | #include <errno.h> | |
14 | #include <stddef.h> | |
15 | #include <stdlib.h> | |
16 | #include <string.h> | |
17 | ||
18 | #include "dirent_impl.h" | |
19 | ||
20 | static_assert(DT_BLK == __WASI_FILETYPE_BLOCK_DEVICE, "Value mismatch"); | |
21 | static_assert(DT_CHR == __WASI_FILETYPE_CHARACTER_DEVICE, "Value mismatch"); | |
22 | static_assert(DT_DIR == __WASI_FILETYPE_DIRECTORY, "Value mismatch"); | |
23 | static_assert(DT_FIFO == __WASI_FILETYPE_SOCKET_STREAM, "Value mismatch"); | |
24 | static_assert(DT_LNK == __WASI_FILETYPE_SYMBOLIC_LINK, "Value mismatch"); | |
25 | static_assert(DT_REG == __WASI_FILETYPE_REGULAR_FILE, "Value mismatch"); | |
26 | static_assert(DT_UNKNOWN == __WASI_FILETYPE_UNKNOWN, "Value mismatch"); | |
27 | ||
28 | // Grows a buffer to be large enough to hold a certain amount of data. | |
29 | #define GROW(buffer, buffer_size, target_size) \ | |
30 | do { \ | |
31 | if ((buffer_size) < (target_size)) { \ | |
32 | size_t new_size = (buffer_size); \ | |
33 | while (new_size < (target_size)) \ | |
34 | new_size *= 2; \ | |
35 | void *new_buffer = realloc(buffer, new_size); \ | |
36 | if (new_buffer == NULL) \ | |
37 | return NULL; \ | |
38 | (buffer) = new_buffer; \ | |
39 | (buffer_size) = new_size; \ | |
40 | } \ | |
41 | } while (0) | |
42 | ||
43 | struct dirent *readdir(DIR *dirp) { | |
44 | for (;;) { | |
45 | // Extract the next dirent header. | |
46 | size_t buffer_left = dirp->buffer_used - dirp->buffer_processed; | |
47 | if (buffer_left < sizeof(__wasi_dirent_t)) { | |
48 | // End-of-file. | |
49 | if (dirp->buffer_used < dirp->buffer_size) | |
50 | return NULL; | |
51 | goto read_entries; | |
52 | } | |
53 | __wasi_dirent_t entry; | |
54 | memcpy(&entry, dirp->buffer + dirp->buffer_processed, sizeof(entry)); | |
55 | ||
56 | size_t entry_size = sizeof(__wasi_dirent_t) + entry.d_namlen; | |
57 | if (entry.d_namlen == 0) { | |
58 | // Invalid pathname length. Skip the entry. | |
59 | dirp->buffer_processed += entry_size; | |
60 | continue; | |
61 | } | |
62 | ||
63 | // The entire entry must be present in buffer space. If not, read | |
64 | // the entry another time. Ensure that the read buffer is large | |
65 | // enough to fit at least this single entry. | |
66 | if (buffer_left < entry_size) { | |
67 | GROW(dirp->buffer, dirp->buffer_size, entry_size); | |
68 | goto read_entries; | |
69 | } | |
70 | ||
71 | // Skip entries having null bytes in the filename. | |
72 | const char *name = dirp->buffer + dirp->buffer_processed + sizeof(entry); | |
73 | if (memchr(name, '\0', entry.d_namlen) != NULL) { | |
74 | dirp->buffer_processed += entry_size; | |
75 | continue; | |
76 | } | |
77 | ||
78 | // Return the next directory entry. Ensure that the dirent is large | |
79 | // enough to fit the filename. | |
80 | GROW(dirp->dirent, dirp->dirent_size, | |
81 | offsetof(struct dirent, d_name) + entry.d_namlen + 1); | |
82 | struct dirent *dirent = dirp->dirent; | |
320054e8 DG |
83 | dirent->d_type = entry.d_type; |
84 | memcpy(dirent->d_name, name, entry.d_namlen); | |
85 | dirent->d_name[entry.d_namlen] = '\0'; | |
be1ffd6a DG |
86 | |
87 | // `fd_readdir` implementations may set the inode field to zero if the | |
88 | // the inode number is unknown. In that case, do an `fstatat` to get the | |
89 | // inode number. | |
90 | off_t d_ino = entry.d_ino; | |
91 | unsigned char d_type = entry.d_type; | |
6cd1be1f | 92 | if (d_ino == 0 && strcmp(dirent->d_name, "..") != 0) { |
be1ffd6a DG |
93 | struct stat statbuf; |
94 | if (fstatat(dirp->fd, dirent->d_name, &statbuf, AT_SYMLINK_NOFOLLOW) != 0) { | |
95 | if (errno == ENOENT) { | |
96 | // The file disappeared before we could read it, so skip it. | |
6cd1be1f | 97 | dirp->buffer_processed += entry_size; |
be1ffd6a DG |
98 | continue; |
99 | } | |
100 | return NULL; | |
101 | } | |
102 | ||
103 | // Fill in the inode. | |
104 | d_ino = statbuf.st_ino; | |
105 | ||
106 | // In case someone raced with us and replaced the object with this name | |
107 | // with another of a different type, update the type too. | |
108 | d_type = __wasilibc_iftodt(statbuf.st_mode & S_IFMT); | |
109 | } | |
110 | dirent->d_ino = d_ino; | |
111 | dirent->d_type = d_type; | |
112 | ||
320054e8 DG |
113 | dirp->cookie = entry.d_next; |
114 | dirp->buffer_processed += entry_size; | |
115 | return dirent; | |
116 | ||
117 | read_entries: | |
118 | // Discard data currently stored in the input buffer. | |
119 | dirp->buffer_used = dirp->buffer_processed = dirp->buffer_size; | |
120 | ||
121 | // Load more directory entries and continue. | |
122 | __wasi_errno_t error = | |
446cb3f1 DG |
123 | // TODO: Remove the cast on `dirp->buffer` once the witx is updated with char8 support. |
124 | __wasi_fd_readdir(dirp->fd, (uint8_t *)dirp->buffer, dirp->buffer_size, | |
320054e8 DG |
125 | dirp->cookie, &dirp->buffer_used); |
126 | if (error != 0) { | |
127 | errno = error; | |
128 | return NULL; | |
129 | } | |
130 | dirp->buffer_processed = 0; | |
131 | } | |
132 | } |