]> Sergey Matveev's repositories - bfs.git/blob - src/dir.c
Skip mtab
[bfs.git] / src / dir.c
1 // Copyright © Tavian Barnes <tavianator@tavianator.com>
2 // SPDX-License-Identifier: 0BSD
3
4 #include "dir.h"
5 #include "alloc.h"
6 #include "bfstd.h"
7 #include "config.h"
8 #include "diag.h"
9 #include "sanity.h"
10 #include "trie.h"
11 #include <dirent.h>
12 #include <errno.h>
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <string.h>
16 #include <sys/stat.h>
17 #include <unistd.h>
18
19 #if BFS_USE_GETDENTS
20 #  if __linux__
21 #    include <sys/syscall.h>
22 #  endif
23
24 /** getdents() syscall wrapper. */
25 static ssize_t bfs_getdents(int fd, void *buf, size_t size) {
26         sanitize_uninit(buf, size);
27
28 #if (__linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30)) || __ANDROID__
29         ssize_t ret = syscall(SYS_getdents64, fd, buf, size);
30 #elif __linux__
31         ssize_t ret = getdents64(fd, buf, size);
32 #else
33         ssize_t ret = getdents(fd, buf, size);
34 #endif
35
36         if (ret > 0) {
37                 sanitize_init(buf, ret);
38         }
39
40         return ret;
41 }
42
43 #endif // BFS_USE_GETDENTS
44
45 #if BFS_USE_GETDENTS && __linux__
46 /** Directory entry type for bfs_getdents() */
47 typedef struct dirent64 sys_dirent;
48 #else
49 typedef struct dirent sys_dirent;
50 #endif
51
52 enum bfs_type bfs_mode_to_type(mode_t mode) {
53         switch (mode & S_IFMT) {
54 #ifdef S_IFBLK
55         case S_IFBLK:
56                 return BFS_BLK;
57 #endif
58 #ifdef S_IFCHR
59         case S_IFCHR:
60                 return BFS_CHR;
61 #endif
62 #ifdef S_IFDIR
63         case S_IFDIR:
64                 return BFS_DIR;
65 #endif
66 #ifdef S_IFDOOR
67         case S_IFDOOR:
68                 return BFS_DOOR;
69 #endif
70 #ifdef S_IFIFO
71         case S_IFIFO:
72                 return BFS_FIFO;
73 #endif
74 #ifdef S_IFLNK
75         case S_IFLNK:
76                 return BFS_LNK;
77 #endif
78 #ifdef S_IFPORT
79         case S_IFPORT:
80                 return BFS_PORT;
81 #endif
82 #ifdef S_IFREG
83         case S_IFREG:
84                 return BFS_REG;
85 #endif
86 #ifdef S_IFSOCK
87         case S_IFSOCK:
88                 return BFS_SOCK;
89 #endif
90 #ifdef S_IFWHT
91         case S_IFWHT:
92                 return BFS_WHT;
93 #endif
94
95         default:
96                 return BFS_UNKNOWN;
97         }
98 }
99
100 /**
101  * Private directory flags.
102  */
103 enum {
104         /** We've reached the end of the directory. */
105         BFS_DIR_EOF   = BFS_DIR_PRIVATE << 0,
106         /** This directory is a union mount we need to dedup manually. */
107         BFS_DIR_UNION = BFS_DIR_PRIVATE << 1,
108 };
109
110 struct bfs_dir {
111         unsigned int flags;
112
113 #if BFS_USE_GETDENTS
114         int fd;
115         unsigned short pos;
116         unsigned short size;
117 #  if __FreeBSD__
118         struct trie trie;
119 #  endif
120         alignas(sys_dirent) char buf[];
121 #else
122         DIR *dir;
123         struct dirent *de;
124 #endif
125 };
126
127 #if BFS_USE_GETDENTS
128 #  define DIR_SIZE (64 << 10)
129 #  define BUF_SIZE (DIR_SIZE - sizeof(struct bfs_dir))
130 #else
131 #  define DIR_SIZE sizeof(struct bfs_dir)
132 #endif
133
134 struct bfs_dir *bfs_allocdir(void) {
135         return malloc(DIR_SIZE);
136 }
137
138 void bfs_dir_arena(struct arena *arena) {
139         arena_init(arena, alignof(struct bfs_dir), DIR_SIZE);
140 }
141
142 int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_dir_flags flags) {
143         int fd;
144         if (at_path) {
145                 fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY);
146                 if (fd < 0) {
147                         return -1;
148                 }
149         } else if (at_fd >= 0) {
150                 fd = at_fd;
151         } else {
152                 errno = EBADF;
153                 return -1;
154         }
155
156         dir->flags = flags;
157
158 #if BFS_USE_GETDENTS
159         dir->fd = fd;
160         dir->pos = 0;
161         dir->size = 0;
162
163 #  if __FreeBSD__ && defined(F_ISUNIONSTACK)
164         if (fcntl(fd, F_ISUNIONSTACK) > 0) {
165                 dir->flags |= BFS_DIR_UNION;
166                 trie_init(&dir->trie);
167         }
168 #  endif
169 #else // !BFS_USE_GETDENTS
170         dir->dir = fdopendir(fd);
171         if (!dir->dir) {
172                 if (at_path) {
173                         close_quietly(fd);
174                 }
175                 return -1;
176         }
177         dir->de = NULL;
178 #endif
179
180         return 0;
181 }
182
183 int bfs_dirfd(const struct bfs_dir *dir) {
184 #if BFS_USE_GETDENTS
185         return dir->fd;
186 #else
187         return dirfd(dir->dir);
188 #endif
189 }
190
191 int bfs_polldir(struct bfs_dir *dir) {
192 #if BFS_USE_GETDENTS
193         if (dir->pos < dir->size) {
194                 return 1;
195         } else if (dir->flags & BFS_DIR_EOF) {
196                 return 0;
197         }
198
199         char *buf = (char *)(dir + 1);
200         ssize_t size = bfs_getdents(dir->fd, buf, BUF_SIZE);
201         if (size == 0) {
202                 dir->flags |= BFS_DIR_EOF;
203                 return 0;
204         } else if (size < 0) {
205                 return -1;
206         }
207
208         dir->pos = 0;
209         dir->size = size;
210
211         // Like read(), getdents() doesn't indicate EOF until another call returns zero.
212         // Check that eagerly here to hopefully avoid a syscall in the last bfs_readdir().
213         size_t rest = BUF_SIZE - size;
214         if (rest >= sizeof(sys_dirent)) {
215                 size = bfs_getdents(dir->fd, buf + size, rest);
216                 if (size > 0) {
217                         dir->size += size;
218                 } else if (size == 0) {
219                         dir->flags |= BFS_DIR_EOF;
220                 }
221         }
222
223         return 1;
224 #else // !BFS_USE_GETDENTS
225         if (dir->de) {
226                 return 1;
227         } else if (dir->flags & BFS_DIR_EOF) {
228                 return 0;
229         }
230
231         errno = 0;
232         dir->de = readdir(dir->dir);
233         if (dir->de) {
234                 return 1;
235         } else if (errno == 0) {
236                 dir->flags |= BFS_DIR_EOF;
237                 return 0;
238         } else {
239                 return -1;
240         }
241 #endif
242 }
243
244 /** Read a single directory entry. */
245 static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) {
246         int ret = bfs_polldir(dir);
247         if (ret > 0) {
248 #if BFS_USE_GETDENTS
249                 char *buf = (char *)(dir + 1);
250                 *de = (const sys_dirent *)(buf + dir->pos);
251                 dir->pos += (*de)->d_reclen;
252 #else
253                 *de = dir->de;
254                 dir->de = NULL;
255 #endif
256         }
257         return ret;
258 }
259
260 /** Skip ".", "..", and deleted/empty dirents. */
261 static int bfs_skipdent(struct bfs_dir *dir, const sys_dirent *de) {
262 #if BFS_USE_GETDENTS
263 #  if __FreeBSD__
264         // Union mounts on FreeBSD have to be de-duplicated in userspace
265         if (dir->flags & BFS_DIR_UNION) {
266                 struct trie_leaf *leaf = trie_insert_str(&dir->trie, de->d_name);
267                 if (!leaf) {
268                         return -1;
269                 } else if (leaf->value) {
270                         return 1;
271                 } else {
272                         leaf->value = leaf;
273                 }
274         }
275
276         // NFS mounts on FreeBSD can return empty dirents with inode number 0
277         if (de->d_ino == 0) {
278                 return 1;
279         }
280 #  endif
281
282 #  ifdef DT_WHT
283         if (de->d_type == DT_WHT && !(dir->flags & BFS_DIR_WHITEOUTS)) {
284                 return 1;
285         }
286 #  endif
287 #endif // BFS_USE_GETDENTS
288
289         const char *name = de->d_name;
290         return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0'));
291 }
292
293 /** Convert de->d_type to a bfs_type, if it exists. */
294 static enum bfs_type bfs_d_type(const sys_dirent *de) {
295 #ifdef DTTOIF
296         return bfs_mode_to_type(DTTOIF(de->d_type));
297 #else
298         return BFS_UNKNOWN;
299 #endif
300 }
301
302 int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) {
303         while (true) {
304                 const sys_dirent *sysde;
305                 int ret = bfs_getdent(dir, &sysde);
306                 if (ret <= 0) {
307                         return ret;
308                 }
309
310                 int skip = bfs_skipdent(dir, sysde);
311                 if (skip < 0) {
312                         return skip;
313                 } else if (skip) {
314                         continue;
315                 }
316
317                 if (de) {
318                         de->type = bfs_d_type(sysde);
319                         de->name = sysde->d_name;
320                 }
321
322                 return 1;
323         }
324 }
325
326 static void bfs_destroydir(struct bfs_dir *dir) {
327 #if BFS_USE_GETDENTS && __FreeBSD__
328         if (dir->flags & BFS_DIR_UNION) {
329                 trie_destroy(&dir->trie);
330         }
331 #endif
332
333         sanitize_uninit(dir, DIR_SIZE);
334 }
335
336 int bfs_closedir(struct bfs_dir *dir) {
337 #if BFS_USE_GETDENTS
338         int ret = xclose(dir->fd);
339 #else
340         int ret = closedir(dir->dir);
341         if (ret != 0) {
342                 bfs_verify(errno != EBADF);
343         }
344 #endif
345
346         bfs_destroydir(dir);
347         return ret;
348 }
349
350 #if BFS_USE_UNWRAPDIR
351 int bfs_unwrapdir(struct bfs_dir *dir) {
352 #if BFS_USE_GETDENTS
353         int ret = dir->fd;
354 #elif __FreeBSD__
355         int ret = fdclosedir(dir->dir);
356 #endif
357
358         bfs_destroydir(dir);
359         return ret;
360 }
361 #endif