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
|
/* Maintainer: <contact@bellrise.net> */
#include "sys/types.h" // for off_t
#include <dirent.h> // for dirent, ssize_t, DIR, readdir_r
#include <errno.h> // for EINVAL, errno
#include <libc/dirent.h> // for linux_dirent64
#include <stddef.h> // for NULL, offsetof
#include <string.h> // for memcpy, size_t, memset
#include <syscall.h> // for __syscall_3, syscall
int readdir_r(DIR *restrict dirp, struct dirent *restrict entry, struct dirent **restrict result)
{
struct linux_dirent64 *ldir = (void *)dirp->buffer;
ssize_t nread;
int ret;
if (dirp == NULL || entry == NULL || (void *)result == NULL) {
return EINVAL;
}
/* Clear the result entry to prevent garbage data */
memset(entry, 0, sizeof(*entry));
if (dirp->cached) {
ldir = (void *)(dirp->buffer + dirp->offset);
/* Validate buffer bounds */
if (dirp->offset >= (off_t)sizeof(dirp->buffer) ||
dirp->offset + (off_t)sizeof(struct linux_dirent64) > (off_t)sizeof(dirp->buffer)) {
dirp->cached = 0;
*result = NULL;
return 0;
}
/* Validate record length */
if (ldir->d_reclen < offsetof(struct linux_dirent64, d_name) + 1 ||
dirp->offset + (off_t)ldir->d_reclen > (off_t)sizeof(dirp->buffer) || ldir->d_reclen == 0) {
dirp->cached = 0;
*result = NULL;
return 0;
}
entry->d_ino = ldir->d_ino;
/* Calculate available space for name */
size_t max_name_len = ldir->d_reclen - offsetof(struct linux_dirent64, d_name);
if (max_name_len > sizeof(entry->d_name) - 1) {
max_name_len = sizeof(entry->d_name) - 1;
}
/* Find actual string length, bounded by available space */
size_t name_len = 0;
while (name_len < max_name_len && ldir->d_name[name_len] != '\0') {
name_len++;
}
memcpy(entry->d_name, ldir->d_name, name_len);
entry->d_name[name_len] = '\0';
dirp->cached--;
dirp->offset += ldir->d_reclen;
*result = entry;
return 0;
}
/* Clear cached entries and reset offset */
dirp->cached = 0;
dirp->offset = 0;
ret = syscall(getdents64, dirp->fildes, dirp->buffer, sizeof(dirp->buffer));
if (ret < 0)
return errno;
if (ret == 0) {
*result = NULL;
return 0;
}
/* Read first entry from buffer. */
nread = ret;
/* Validate first entry bounds */
if (nread < (ssize_t)sizeof(struct linux_dirent64) ||
ldir->d_reclen < offsetof(struct linux_dirent64, d_name) + 1 || ldir->d_reclen > nread ||
ldir->d_reclen == 0) {
*result = NULL;
return EINVAL;
}
dirp->offset = ldir->d_reclen;
entry->d_ino = ldir->d_ino;
/* Calculate available space for name */
size_t max_name_len = ldir->d_reclen - offsetof(struct linux_dirent64, d_name);
if (max_name_len > sizeof(entry->d_name) - 1) {
max_name_len = sizeof(entry->d_name) - 1;
}
/* Find actual string length, bounded by available space */
size_t name_len = 0;
while (name_len < max_name_len && ldir->d_name[name_len] != '\0') {
name_len++;
}
memcpy(entry->d_name, ldir->d_name, name_len);
entry->d_name[name_len] = '\0';
*result = entry;
/* Count the amount of remaining entries we have cached from getdents.
*/
for (ssize_t buffer_offset = ldir->d_reclen; buffer_offset < nread;) {
struct linux_dirent64 *next_ldir = (void *)(dirp->buffer + buffer_offset);
/* Validate entry bounds to prevent infinite loops */
if (buffer_offset + (ssize_t)sizeof(struct linux_dirent64) > nread ||
next_ldir->d_reclen < offsetof(struct linux_dirent64, d_name) + 1 ||
buffer_offset + next_ldir->d_reclen > nread || next_ldir->d_reclen == 0) {
break;
}
buffer_offset += next_ldir->d_reclen;
dirp->cached++;
}
return 0;
}
|