]> git.proxmox.com Git - wasi-libc.git/blame - libc-bottom-half/cloudlibc/src/libc/dirent/readdir.c
Fix logic errors in the zero-inode path. (#352)
[wasi-libc.git] / libc-bottom-half / cloudlibc / src / libc / dirent / readdir.c
CommitLineData
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
20static_assert(DT_BLK == __WASI_FILETYPE_BLOCK_DEVICE, "Value mismatch");
21static_assert(DT_CHR == __WASI_FILETYPE_CHARACTER_DEVICE, "Value mismatch");
22static_assert(DT_DIR == __WASI_FILETYPE_DIRECTORY, "Value mismatch");
23static_assert(DT_FIFO == __WASI_FILETYPE_SOCKET_STREAM, "Value mismatch");
24static_assert(DT_LNK == __WASI_FILETYPE_SYMBOLIC_LINK, "Value mismatch");
25static_assert(DT_REG == __WASI_FILETYPE_REGULAR_FILE, "Value mismatch");
26static_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
43struct 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}