2 * du.c: implementation of du.h.
9 #if !defined __linux__ || !defined O_NOATIME || defined HAVE_FDOPENDIR
20 #ifdef HAVE_SYS_NDIR_H
21 # include <sys/ndir.h>
25 * Wrappers around POSIX opendir, readdir and closedir, which
26 * permit me to replace them with different wrappers in special
30 typedef DIR *dirhandle
;
32 int open_dir(const char *path
, dirhandle
*dh
)
34 #if defined O_NOATIME && defined HAVE_FDOPENDIR
37 * On Linux, we have the O_NOATIME flag. This means we can
38 * read the contents of directories without affecting their
39 * atimes, which enables us to at least try to include them in
40 * the age display rather than exempting them.
42 * Unfortunately, opendir() doesn't let us open a directory
43 * with O_NOATIME. So instead, we have to open the directory
44 * with vanilla open(), and then use fdopendir() to translate
45 * the fd into a POSIX dir handle.
49 fd
= open(path
, O_RDONLY
| O_NONBLOCK
| O_NOCTTY
| O_LARGEFILE
|
50 O_NOATIME
| O_DIRECTORY
);
53 * Opening a file with O_NOATIME is not unconditionally
54 * permitted by the Linux kernel. As far as I can tell,
55 * it's permitted only for files on which the user would
56 * have been able to call utime(2): in other words, files
57 * for which the user could have deliberately set the
58 * atime back to its original value after finishing with
59 * it. Hence, O_NOATIME has no security implications; it's
60 * simply a cleaner, faster and more race-condition-free
61 * alternative to stat(), a normal open(), and a utimes()
64 * The upshot of all of which, for these purposes, is that
65 * we must be prepared to try again without O_NOATIME if
69 fd
= open(path
, O_RDONLY
| O_NONBLOCK
| O_NOCTTY
|
70 O_LARGEFILE
| O_DIRECTORY
);
85 const char *read_dir(dirhandle
*dh
)
87 struct dirent
*de
= readdir(*dh
);
88 return de ? de
->d_name
: NULL
;
91 void close_dir(dirhandle
*dh
)
96 #else /* defined __linux__ && !defined HAVE_FDOPENDIR */
99 * Earlier versions of glibc do not have fdopendir(). Therefore,
100 * if we are on Linux and still wish to make use of O_NOATIME, we
101 * have no option but to talk directly to the kernel system call
102 * interface which underlies the POSIX opendir/readdir machinery.
106 #include <linux/types.h>
107 #include <linux/dirent.h>
108 #include <linux/unistd.h>
110 _syscall3(int, getdents
, uint
, fd
, struct dirent
*, dirp
, uint
, count
)
114 struct dirent data
[32];
119 int open_dir(const char *path
, dirhandle
*dh
)
122 * As above, we try with O_NOATIME and then fall back to
125 dh
->fd
= open(path
, O_RDONLY
| O_NONBLOCK
| O_NOCTTY
| O_LARGEFILE
|
126 O_NOATIME
| O_DIRECTORY
);
129 dh
->fd
= open(path
, O_RDONLY
| O_NONBLOCK
| O_NOCTTY
|
130 O_LARGEFILE
| O_DIRECTORY
);
135 dh
->pos
= dh
->endpos
= 0;
140 const char *read_dir(dirhandle
*dh
)
144 if (dh
->pos
>= dh
->endpos
) {
147 dh
->endpos
= getdents(dh
->fd
, dh
->data
, sizeof(dh
->data
));
152 ret
= dh
->curr
->d_name
;
154 dh
->pos
+= dh
->curr
->d_reclen
;
155 dh
->curr
= (struct dirent
*)((char *)dh
->data
+ dh
->pos
);
160 void close_dir(dirhandle
*dh
)
165 #endif /* !defined __linux__ || defined HAVE_FDOPENDIR */
167 static int str_cmp(const void *av
, const void *bv
)
169 return strcmp(*(const char **)av
, *(const char **)bv
);
172 static void du_recurse(char **path
, size_t pathlen
, size_t *pathsize
,
173 gotdata_fn_t gotdata
, err_fn_t err
, void *gotdata_ctx
,
180 size_t i
, nnames
, namesize
;
184 * Special case: at the very top of the scan, we follow a
188 statret
= STAT(*path
, &st
);
190 statret
= LSTAT(*path
, &st
);
192 err(gotdata_ctx
, "%s: lstat: %s\n", *path
, strerror(errno
));
196 if (!gotdata(gotdata_ctx
, *path
, &st
))
199 if (!S_ISDIR(st
.st_mode
))
203 nnames
= namesize
= 0;
205 if (open_dir(*path
, &d
) < 0) {
206 err(gotdata_ctx
, "%s: opendir: %s\n", *path
, strerror(errno
));
209 while ((name
= read_dir(&d
)) != NULL
) {
210 if (name
[0] == '.' && (!name
[1] || (name
[1] == '.' && !name
[2]))) {
211 /* do nothing - we skip "." and ".." */
213 if (nnames
>= namesize
) {
214 namesize
= nnames
* 3 / 2 + 64;
215 names
= sresize(names
, namesize
, char *);
217 names
[nnames
++] = dupstr(name
);
225 qsort(names
, nnames
, sizeof(*names
), str_cmp
);
227 for (i
= 0; i
< nnames
; i
++) {
228 size_t newpathlen
= pathlen
+ 1 + strlen(names
[i
]);
229 if (*pathsize
<= newpathlen
) {
230 *pathsize
= newpathlen
* 3 / 2 + 256;
231 *path
= sresize(*path
, *pathsize
, char);
234 * Avoid duplicating a slash if we got a trailing one to
235 * begin with (i.e. if we're starting the scan in '/' itself).
237 if (pathlen
> 0 && (*path
)[pathlen
-1] == '/') {
238 strcpy(*path
+ pathlen
, names
[i
]);
241 sprintf(*path
+ pathlen
, "/%s", names
[i
]);
244 du_recurse(path
, newpathlen
, pathsize
, gotdata
, err
, gotdata_ctx
, 0);
251 void du(const char *inpath
, gotdata_fn_t gotdata
, err_fn_t err
,
255 size_t pathlen
, pathsize
;
257 pathlen
= strlen(inpath
);
258 pathsize
= pathlen
+ 256;
259 path
= snewn(pathsize
, char);
260 strcpy(path
, inpath
);
262 du_recurse(&path
, pathlen
, &pathsize
, gotdata
, err
, gotdata_ctx
, 1);