summaryrefslogtreecommitdiff
path: root/lib/libc/dirent/readdir_r.c
blob: 049865f227e55e68a1f6b5d7e2f76a69dca19901 (plain)
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;
}