xref: /haiku/src/system/libroot/posix/dirent.cpp (revision fc7456e9b1ec38c941134ed6d01c438cf289381e)
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 
25 struct __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 
35 static int
36 do_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 
103 DIR*
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 
126 DIR*
127 fdopendir(int fd)
128 {
129 	if (fd < 0) {
130 		__set_errno(EBADF);
131 		return NULL;
132 	}
133 
134 	// Since our standard file descriptors can't be used as directory file
135 	// descriptors, we have to open a fresh one explicitly.
136 	int dirFD = _kern_open_dir(fd, NULL);
137 	if (dirFD < 0) {
138 		__set_errno(dirFD);
139 		return NULL;
140 	}
141 
142 	// Since applications are allowed to use the file descriptor after a call
143 	// to fdopendir() without changing its state (like for other *at()
144 	// functions), we cannot close it now.
145 	// We dup2() the new FD to the previous location instead.
146 	if (dup2(dirFD, fd) == -1) {
147 		close(fd);
148 	} else {
149 		close(dirFD);
150 		dirFD = fd;
151 		fcntl(dirFD, F_SETFD, FD_CLOEXEC);
152 			// reset close-on-exec which is cleared by dup()
153 	}
154 
155 	DIR* dir = __create_dir_struct(dirFD);
156 	if (dir == NULL) {
157 		close(dirFD);
158 		return NULL;
159 	}
160 
161 	return dir;
162 }
163 
164 
165 DIR*
166 opendir(const char* path)
167 {
168 	DIR* dir;
169 
170 	int fd = _kern_open_dir(AT_FDCWD, path);
171 	if (fd < 0) {
172 		__set_errno(fd);
173 		return NULL;
174 	}
175 
176 	// allocate the DIR structure
177 	if ((dir = __create_dir_struct(fd)) == NULL) {
178 		_kern_close(fd);
179 		return NULL;
180 	}
181 
182 	return dir;
183 }
184 
185 
186 int
187 closedir(DIR* dir)
188 {
189 	int status;
190 
191 	if (dir == NULL) {
192 		__set_errno(B_BAD_VALUE);
193 		return -1;
194 	}
195 
196 	status = _kern_close(dir->fd);
197 
198 	free(dir);
199 
200 	RETURN_AND_SET_ERRNO(status);
201 }
202 
203 
204 struct dirent*
205 readdir(DIR* dir)
206 {
207 	ssize_t count;
208 
209 	if (dir->seek_position != dir->current_position) {
210 		if (do_seek_dir(dir) != 0)
211 			return NULL;
212 	}
213 
214 	if (dir->entries_left > 0) {
215 		struct dirent *dirent
216 			= (struct dirent *)((uint8 *)&dir->first_entry + dir->next_entry);
217 
218 		dir->entries_left--;
219 		dir->next_entry += dirent->d_reclen;
220 		dir->seek_position++;
221 		dir->current_position++;
222 
223 		return dirent;
224 	}
225 
226 	// we need to retrieve new entries
227 
228 	count = _kern_read_dir(dir->fd, &dir->first_entry,
229 		(char*)dir + DIR_BUFFER_SIZE - (char*)&dir->first_entry, USHRT_MAX);
230 	if (count <= 0) {
231 		if (count < 0)
232 			__set_errno(count);
233 
234 		// end of directory
235 		return NULL;
236 	}
237 
238 	dir->entries_left = count - 1;
239 	dir->next_entry = dir->first_entry.d_reclen;
240 	dir->seek_position++;
241 	dir->current_position++;
242 
243 	return &dir->first_entry;
244 }
245 
246 
247 int
248 readdir_r(DIR* dir, struct dirent* entry, struct dirent** _result)
249 {
250 	BPrivate::ErrnoMaintainer _;
251 	errno = 0;
252 
253 	struct dirent* dirent = readdir(dir);
254 	if (dirent == NULL) {
255 		*_result = NULL;
256 		return errno;
257 	}
258 
259 	memcpy(entry, dirent, dirent->d_reclen);
260 	*_result = entry;
261 	return 0;
262 }
263 
264 
265 void
266 rewinddir(DIR* dir)
267 {
268 	dir->seek_position = 0;
269 }
270 
271 
272 void
273 seekdir(DIR* dir, long int position)
274 {
275 	dir->seek_position = position;
276 }
277 
278 
279 long int
280 telldir(DIR* dir)
281 {
282 	return dir->seek_position;
283 }
284 
285 
286 int
287 dirfd(DIR* dir)
288 {
289 	return dir->fd;
290 }
291