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