9a8a4a3924bb464ed89627df565103a6d7168783
[sgt/agedu] / du.c
1 /*
2 * du.c: implementation of du.h.
3 */
4
5 #define _GNU_SOURCE
6 #include <features.h>
7
8 #include <stdio.h>
9 #include <string.h>
10 #include <stdlib.h>
11 #include <errno.h>
12
13 #include <sys/types.h>
14 #include <sys/stat.h>
15 #include <unistd.h>
16
17 #include "agedu.h"
18 #include "du.h"
19 #include "alloc.h"
20
21 #if !defined __linux__ || defined HAVE_FDOPENDIR
22
23 /*
24 * Wrappers around POSIX opendir, readdir and closedir, which
25 * permit me to replace them with different wrappers in special
26 * circumstances.
27 */
28
29 #include <dirent.h>
30 typedef DIR *dirhandle;
31
32 int open_dir(const char *path, dirhandle *dh)
33 {
34 #if defined O_NOATIME && defined HAVE_FDOPENDIR
35
36 /*
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.
41 *
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.
46 */
47 int fd;
48
49 fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_LARGEFILE |
50 O_NOATIME | O_DIRECTORY);
51 if (fd < 0) {
52 /*
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()
62 * when finished.
63 *
64 * The upshot of all of which, for these purposes, is that
65 * we must be prepared to try again without O_NOATIME if
66 * we receive EPERM.
67 */
68 if (errno == EPERM)
69 fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY |
70 O_LARGEFILE | O_DIRECTORY);
71 if (fd < 0)
72 return -1;
73 }
74
75 *dh = fdopendir(fd);
76 #else
77 *dh = opendir(path);
78 #endif
79
80 if (!*dh)
81 return -1;
82 return 0;
83 }
84
85 const char *read_dir(dirhandle *dh)
86 {
87 struct dirent *de = readdir(*dh);
88 return de ? de->d_name : NULL;
89 }
90
91 void close_dir(dirhandle *dh)
92 {
93 closedir(*dh);
94 }
95
96 #else /* defined __linux__ && !defined HAVE_FDOPENDIR */
97
98 /*
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.
103 */
104
105 #define __KERNEL__
106 #include <unistd.h>
107 #include <fcntl.h>
108 #include <linux/types.h>
109 #include <linux/dirent.h>
110 #include <linux/unistd.h>
111
112 _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count)
113
114 typedef struct {
115 int fd;
116 struct dirent data[32];
117 struct dirent *curr;
118 int pos, endpos;
119 } dirhandle;
120
121 int open_dir(const char *path, dirhandle *dh)
122 {
123 /*
124 * As above, we try with O_NOATIME and then fall back to
125 * trying without it.
126 */
127 dh->fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_LARGEFILE |
128 O_NOATIME | O_DIRECTORY);
129 if (dh->fd < 0) {
130 if (errno == EPERM)
131 dh->fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY |
132 O_LARGEFILE | O_DIRECTORY);
133 if (dh->fd < 0)
134 return -1;
135 }
136
137 dh->pos = dh->endpos = 0;
138
139 return 0;
140 }
141
142 const char *read_dir(dirhandle *dh)
143 {
144 const char *ret;
145
146 if (dh->pos >= dh->endpos) {
147 dh->curr = dh->data;
148 dh->pos = 0;
149 dh->endpos = getdents(dh->fd, dh->data, sizeof(dh->data));
150 if (dh->endpos <= 0)
151 return NULL;
152 }
153
154 ret = dh->curr->d_name;
155
156 dh->pos += dh->curr->d_reclen;
157 dh->curr = (struct dirent *)((char *)dh->data + dh->pos);
158
159 return ret;
160 }
161
162 void close_dir(dirhandle *dh)
163 {
164 close(dh->fd);
165 }
166
167 #endif /* !defined __linux__ || defined HAVE_FDOPENDIR */
168
169 static int str_cmp(const void *av, const void *bv)
170 {
171 return strcmp(*(const char **)av, *(const char **)bv);
172 }
173
174 static void du_recurse(char **path, size_t pathlen, size_t *pathsize,
175 gotdata_fn_t gotdata, void *gotdata_ctx)
176 {
177 const char *name;
178 dirhandle d;
179 STRUCT_STAT st;
180 char **names;
181 size_t i, nnames, namesize;
182
183 if (LSTAT(*path, &st) < 0) {
184 fprintf(stderr, "%s: lstat: %s\n", *path, strerror(errno));
185 return;
186 }
187
188 if (!gotdata(gotdata_ctx, *path, &st))
189 return;
190
191 if (!S_ISDIR(st.st_mode))
192 return;
193
194 names = NULL;
195 nnames = namesize = 0;
196
197 if (open_dir(*path, &d) < 0) {
198 fprintf(stderr, "%s: opendir: %s\n", *path, strerror(errno));
199 return;
200 }
201 while ((name = read_dir(&d)) != NULL) {
202 if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) {
203 /* do nothing - we skip "." and ".." */
204 } else {
205 if (nnames >= namesize) {
206 namesize = nnames * 3 / 2 + 64;
207 names = sresize(names, namesize, char *);
208 }
209 names[nnames++] = dupstr(name);
210 }
211 }
212 close_dir(&d);
213
214 if (nnames == 0)
215 return;
216
217 qsort(names, nnames, sizeof(*names), str_cmp);
218
219 for (i = 0; i < nnames; i++) {
220 size_t newpathlen = pathlen + 1 + strlen(names[i]);
221 if (*pathsize <= newpathlen) {
222 *pathsize = newpathlen * 3 / 2 + 256;
223 *path = sresize(*path, *pathsize, char);
224 }
225 /*
226 * Avoid duplicating a slash if we got a trailing one to
227 * begin with (i.e. if we're starting the scan in '/' itself).
228 */
229 if (pathlen > 0 && (*path)[pathlen-1] == '/') {
230 strcpy(*path + pathlen, names[i]);
231 newpathlen--;
232 } else {
233 sprintf(*path + pathlen, "/%s", names[i]);
234 }
235
236 du_recurse(path, newpathlen, pathsize, gotdata, gotdata_ctx);
237
238 sfree(names[i]);
239 }
240 sfree(names);
241 }
242
243 void du(const char *inpath, gotdata_fn_t gotdata, void *gotdata_ctx)
244 {
245 char *path;
246 size_t pathlen, pathsize;
247
248 pathlen = strlen(inpath);
249 pathsize = pathlen + 256;
250 path = snewn(pathsize, char);
251 strcpy(path, inpath);
252
253 du_recurse(&path, pathlen, &pathsize, gotdata, gotdata_ctx);
254 }