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
133
134
135
136
137
138
139
140
141
142
|
/* Maintainer: <contact@bellrise.net> */
#include "asm/unistd_64.h" // for __NR_getdents64
#include "stdio.h" // for NULL
#include "sys/types.h" // for off_t
#include <__dirent.h> // for linux_dirent64
#include <dirent.h> // for dirent, size_t, ssize_t, DIR, readdir_r
#include <errno.h> // for EINVAL, errno
#include <stddef.h> // for offsetof
#include <string.h> // for memcpy, 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 || 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;
}
|