1/*
2 * Copyright 2009, Ingo Weinhold, ingo_weinhold@gmx.de.
3 * Copyright 2004-2010, Axel D��rfler, axeld@pinc-software.de.
4 * Distributed under the terms of the MIT License.
5 */
6
7
8#include <dirent.h>
9#include <dirent_private.h>
10
11#include <errno.h>
12#include <limits.h>
13#include <stdlib.h>
14#include <string.h>
15
16#include <errno_private.h>
17#include <ErrnoMaintainer.h>
18#include <syscalls.h>
19#include <syscall_utils.h>
20
21
22#define DIR_BUFFER_SIZE	4096
23
24
25struct __DIR {
26	int				fd;
27	short			next_entry;
28	unsigned short	entries_left;
29	long			seek_position;
30	long			current_position;
31	struct dirent	first_entry;
32};
33
34
35static int
36do_seek_dir(DIR* dir)
37{
38	if (dir->seek_position == dir->current_position)
39		return 0;
40
41	// If the seek position lies before the current position (the usual case),
42	// rewind to the beginning.
43	if (dir->seek_position < dir->current_position) {
44		status_t status = _kern_rewind_dir(dir->fd);
45		if (status < 0) {
46			__set_errno(status);
47			return -1;
48		}
49
50		dir->current_position = 0;
51		dir->entries_left = 0;
52	}
53
54	// Now skip entries until we have reached seek_position.
55	while (dir->seek_position > dir->current_position) {
56		ssize_t count;
57		long toSkip = dir->seek_position - dir->current_position;
58		if (toSkip == dir->entries_left) {
59			// we have to skip exactly all of the currently buffered entries
60			dir->current_position = dir->seek_position;
61			dir->entries_left = 0;
62			return 0;
63		}
64
65		if (toSkip < dir->entries_left) {
66			// we have to skip only some of the buffered entries
67			for (; toSkip > 0; toSkip--) {
68				struct dirent* entry = (struct dirent*)
69					((uint8*)&dir->first_entry + dir->next_entry);
70				dir->entries_left--;
71				dir->next_entry += entry->d_reclen;
72			}
73
74			dir->current_position = dir->seek_position;
75			return 0;
76		}
77
78		// we have to skip more than the currently buffered entries
79		dir->current_position += dir->entries_left;
80		dir->entries_left = 0;
81
82		count = _kern_read_dir(dir->fd, &dir->first_entry,
83			(char*)dir + DIR_BUFFER_SIZE - (char*)&dir->first_entry, USHRT_MAX);
84		if (count <= 0) {
85			if (count < 0)
86				__set_errno(count);
87
88			// end of directory
89			return -1;
90		}
91
92		dir->next_entry = 0;
93		dir->entries_left = count;
94	}
95
96	return 0;
97}
98
99
100// #pragma mark - private API
101
102
103DIR*
104__create_dir_struct(int fd)
105{
106	/* allocate the memory for the DIR structure */
107
108	DIR* dir = (DIR*)malloc(DIR_BUFFER_SIZE);
109	if (dir == NULL) {
110		__set_errno(B_NO_MEMORY);
111		return NULL;
112	}
113
114	dir->fd = fd;
115	dir->entries_left = 0;
116	dir->seek_position = 0;
117	dir->current_position = 0;
118
119	return dir;
120}
121
122
123// #pragma mark - public API
124
125
126DIR*
127fdopendir(int fd)
128{
129	DIR* dir;
130
131	// Since our standard file descriptors can't be used as directory file
132	// descriptors, we have to open a fresh one explicitly.
133	int dirFD = _kern_open_dir(fd, NULL);
134	if (dirFD < 0) {
135		__set_errno(dirFD);
136		return NULL;
137	}
138
139	// Since applications are allowed to use the file descriptor after a call
140	// to fdopendir() without changing its state (like for other *at()
141	// functions), we cannot close it now.
142	// We dup2() the new FD to the previous location instead.
143	if (dup2(dirFD, fd) == -1)
144		close(fd);
145	else {
146		close(dirFD);
147		dirFD = fd;
148		fcntl(dirFD, F_SETFD, FD_CLOEXEC);
149			// reset close-on-exec which is cleared by dup()
150	}
151
152	dir = __create_dir_struct(dirFD);
153	if (dir == NULL) {
154		close(dirFD);
155		return NULL;
156	}
157
158	return dir;
159}
160
161
162DIR*
163opendir(const char* path)
164{
165	DIR* dir;
166
167	int fd = _kern_open_dir(-1, path);
168	if (fd < 0) {
169		__set_errno(fd);
170		return NULL;
171	}
172
173	// allocate the DIR structure
174	if ((dir = __create_dir_struct(fd)) == NULL) {
175		_kern_close(fd);
176		return NULL;
177	}
178
179	return dir;
180}
181
182
183int
184closedir(DIR* dir)
185{
186	int status;
187
188	if (dir == NULL) {
189		__set_errno(B_BAD_VALUE);
190		return -1;
191	}
192
193	status = _kern_close(dir->fd);
194
195	free(dir);
196
197	RETURN_AND_SET_ERRNO(status);
198}
199
200
201struct dirent*
202readdir(DIR* dir)
203{
204	ssize_t count;
205
206	if (dir->seek_position != dir->current_position) {
207		if (do_seek_dir(dir) != 0)
208			return NULL;
209	}
210
211	if (dir->entries_left > 0) {
212		struct dirent *dirent
213			= (struct dirent *)((uint8 *)&dir->first_entry + dir->next_entry);
214
215		dir->entries_left--;
216		dir->next_entry += dirent->d_reclen;
217		dir->seek_position++;
218		dir->current_position++;
219
220		return dirent;
221	}
222
223	// we need to retrieve new entries
224
225	count = _kern_read_dir(dir->fd, &dir->first_entry,
226		(char*)dir + DIR_BUFFER_SIZE - (char*)&dir->first_entry, USHRT_MAX);
227	if (count <= 0) {
228		if (count < 0)
229			__set_errno(count);
230
231		// end of directory
232		return NULL;
233	}
234
235	dir->entries_left = count - 1;
236	dir->next_entry = dir->first_entry.d_reclen;
237	dir->seek_position++;
238	dir->current_position++;
239
240	return &dir->first_entry;
241}
242
243
244int
245readdir_r(DIR* dir, struct dirent* entry, struct dirent** _result)
246{
247	BPrivate::ErrnoMaintainer _;
248	errno = 0;
249
250	struct dirent* dirent = readdir(dir);
251	if (dirent == NULL) {
252		*_result = NULL;
253		return errno;
254	}
255
256	memcpy(entry, dirent, dirent->d_reclen);
257	*_result = entry;
258	return 0;
259}
260
261
262void
263rewinddir(DIR* dir)
264{
265	dir->seek_position = 0;
266}
267
268
269void
270seekdir(DIR* dir, long int position)
271{
272	dir->seek_position = position;
273}
274
275
276long int
277telldir(DIR* dir)
278{
279	return dir->seek_position;
280}
281
282
283int
284dirfd(DIR* dir)
285{
286	return dir->fd;
287}
288