2 * du.c: implementation of du.h.
13 #include <sys/types.h>
23 * On Linux, we have the O_NOATIME flag. This means we can read
24 * the contents of directories without affecting their atimes,
25 * which enables us to at least try to include them in the age
26 * display rather than exempting them.
28 * Unfortunately, opendir() doesn't let us open a directory with
29 * O_NOATIME. In later glibcs we can open one manually using
30 * open() and then use fdopendir() to translate the fd into a
31 * POSIX dir handle; in earlier glibcs fdopendir() is not
32 * available, so we have no option but to talk directly to the
33 * kernel system call interface which underlies the POSIX
34 * opendir/readdir machinery.
40 #include <linux/types.h>
41 #include <linux/dirent.h>
42 #include <linux/unistd.h>
44 _syscall3(int, getdents
, uint
, fd
, struct dirent
*, dirp
, uint
, count
)
48 struct dirent data
[32];
53 int open_dir(const char *path
, dirhandle
*dh
)
55 dh
->fd
= open(path
, O_RDONLY
| O_NOATIME
| O_DIRECTORY
);
58 * Opening a file with O_NOATIME is not unconditionally
59 * permitted by the Linux kernel. As far as I can tell,
60 * it's permitted only for files on which the user would
61 * have been able to call utime(2): in other words, files
62 * for which the user could have deliberately set the
63 * atime back to its original value after finishing with
64 * it. Hence, O_NOATIME has no security implications; it's
65 * simply a cleaner, faster and more race-condition-free
66 * alternative to stat(), a normal open(), and a utimes()
69 * The upshot of all of which, for these purposes, is that
70 * we must be prepared to try again without O_NOATIME if
74 dh
->fd
= open(path
, O_RDONLY
| O_DIRECTORY
);
79 dh
->pos
= dh
->endpos
= 0;
84 const char *read_dir(dirhandle
*dh
)
88 if (dh
->pos
>= dh
->endpos
) {
91 dh
->endpos
= getdents(dh
->fd
, dh
->data
, sizeof(dh
->data
));
96 ret
= dh
->curr
->d_name
;
98 dh
->pos
+= dh
->curr
->d_reclen
;
99 dh
->curr
= (struct dirent
*)((char *)dh
->data
+ dh
->pos
);
104 void close_dir(dirhandle
*dh
)
109 #else /* __linux__ */
112 * This branch of the ifdef is a simple exercise of ordinary POSIX
117 typedef DIR *dirhandle
;
119 int open_dir(const char *path
, dirhandle
*dh
)
127 const char *read_dir(dirhandle
*dh
)
129 struct dirent
*de
= readdir(*dh
);
130 return de ? de
->d_name
: NULL
;
133 void close_dir(dirhandle
*dh
)
140 static int str_cmp(const void *av
, const void *bv
)
142 return strcmp(*(const char **)av
, *(const char **)bv
);
145 static void du_recurse(char **path
, size_t pathlen
, size_t *pathsize
,
146 gotdata_fn_t gotdata
, void *gotdata_ctx
)
152 size_t i
, nnames
, namesize
;
154 if (lstat64(*path
, &st
) < 0) {
155 fprintf(stderr
, "%s: lstat: %s\n", *path
, strerror(errno
));
159 if (!gotdata(gotdata_ctx
, *path
, &st
))
162 if (!S_ISDIR(st
.st_mode
))
166 nnames
= namesize
= 0;
168 if (open_dir(*path
, &d
) < 0) {
169 fprintf(stderr
, "%s: opendir: %s\n", *path
, strerror(errno
));
172 while ((name
= read_dir(&d
)) != NULL
) {
173 if (name
[0] == '.' && (!name
[1] || (name
[1] == '.' && !name
[2]))) {
174 /* do nothing - we skip "." and ".." */
176 if (nnames
>= namesize
) {
177 namesize
= nnames
* 3 / 2 + 64;
178 names
= sresize(names
, namesize
, char *);
180 names
[nnames
++] = dupstr(name
);
188 qsort(names
, nnames
, sizeof(*names
), str_cmp
);
190 for (i
= 0; i
< nnames
; i
++) {
191 size_t newpathlen
= pathlen
+ 1 + strlen(names
[i
]);
192 if (*pathsize
<= newpathlen
) {
193 *pathsize
= newpathlen
* 3 / 2 + 256;
194 *path
= sresize(*path
, *pathsize
, char);
197 * Avoid duplicating a slash if we got a trailing one to
198 * begin with (i.e. if we're starting the scan in '/' itself).
200 if (pathlen
> 0 && (*path
)[pathlen
-1] == '/') {
201 strcpy(*path
+ pathlen
, names
[i
]);
204 sprintf(*path
+ pathlen
, "/%s", names
[i
]);
207 du_recurse(path
, newpathlen
, pathsize
, gotdata
, gotdata_ctx
);
214 void du(const char *inpath
, gotdata_fn_t gotdata
, void *gotdata_ctx
)
217 size_t pathlen
, pathsize
;
219 pathlen
= strlen(inpath
);
220 pathsize
= pathlen
+ 256;
221 path
= snewn(pathsize
, char);
222 strcpy(path
, inpath
);
224 du_recurse(&path
, pathlen
, &pathsize
, gotdata
, gotdata_ctx
);