1a375bf9adde7bbab6f2439bf21711f25657d1f5
[sgt/agedu] / du.c
1 /*
2 * du.c: implementation of du.h.
3 */
4
5 #include "agedu.h" /* for config.h */
6
7 #ifdef HAVE_FEATURES_H
8 #define _GNU_SOURCE
9 #include <features.h>
10 #endif
11
12 #include <stdio.h>
13 #include <string.h>
14 #include <stdlib.h>
15 #include <errno.h>
16
17 #include <sys/types.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20
21 #include "du.h"
22 #include "alloc.h"
23
24 #if !defined __linux__ || defined HAVE_FDOPENDIR
25
26 /*
27 * Wrappers around POSIX opendir, readdir and closedir, which
28 * permit me to replace them with different wrappers in special
29 * circumstances.
30 */
31
32 #include <dirent.h>
33 typedef DIR *dirhandle;
34
35 int open_dir(const char *path, dirhandle *dh)
36 {
37 #if defined O_NOATIME && defined HAVE_FDOPENDIR
38
39 /*
40 * On Linux, we have the O_NOATIME flag. This means we can
41 * read the contents of directories without affecting their
42 * atimes, which enables us to at least try to include them in
43 * the age display rather than exempting them.
44 *
45 * Unfortunately, opendir() doesn't let us open a directory
46 * with O_NOATIME. So instead, we have to open the directory
47 * with vanilla open(), and then use fdopendir() to translate
48 * the fd into a POSIX dir handle.
49 */
50 int fd;
51
52 fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_LARGEFILE |
53 O_NOATIME | O_DIRECTORY);
54 if (fd < 0) {
55 /*
56 * Opening a file with O_NOATIME is not unconditionally
57 * permitted by the Linux kernel. As far as I can tell,
58 * it's permitted only for files on which the user would
59 * have been able to call utime(2): in other words, files
60 * for which the user could have deliberately set the
61 * atime back to its original value after finishing with
62 * it. Hence, O_NOATIME has no security implications; it's
63 * simply a cleaner, faster and more race-condition-free
64 * alternative to stat(), a normal open(), and a utimes()
65 * when finished.
66 *
67 * The upshot of all of which, for these purposes, is that
68 * we must be prepared to try again without O_NOATIME if
69 * we receive EPERM.
70 */
71 if (errno == EPERM)
72 fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY |
73 O_LARGEFILE | O_DIRECTORY);
74 if (fd < 0)
75 return -1;
76 }
77
78 *dh = fdopendir(fd);
79 #else
80 *dh = opendir(path);
81 #endif
82
83 if (!*dh)
84 return -1;
85 return 0;
86 }
87
88 const char *read_dir(dirhandle *dh)
89 {
90 struct dirent *de = readdir(*dh);
91 return de ? de->d_name : NULL;
92 }
93
94 void close_dir(dirhandle *dh)
95 {
96 closedir(*dh);
97 }
98
99 #else /* defined __linux__ && !defined HAVE_FDOPENDIR */
100
101 /*
102 * Earlier versions of glibc do not have fdopendir(). Therefore,
103 * if we are on Linux and still wish to make use of O_NOATIME, we
104 * have no option but to talk directly to the kernel system call
105 * interface which underlies the POSIX opendir/readdir machinery.
106 */
107
108 #define __KERNEL__
109 #include <unistd.h>
110 #include <fcntl.h>
111 #include <linux/types.h>
112 #include <linux/dirent.h>
113 #include <linux/unistd.h>
114
115 _syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count)
116
117 typedef struct {
118 int fd;
119 struct dirent data[32];
120 struct dirent *curr;
121 int pos, endpos;
122 } dirhandle;
123
124 int open_dir(const char *path, dirhandle *dh)
125 {
126 /*
127 * As above, we try with O_NOATIME and then fall back to
128 * trying without it.
129 */
130 dh->fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_LARGEFILE |
131 O_NOATIME | O_DIRECTORY);
132 if (dh->fd < 0) {
133 if (errno == EPERM)
134 dh->fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY |
135 O_LARGEFILE | O_DIRECTORY);
136 if (dh->fd < 0)
137 return -1;
138 }
139
140 dh->pos = dh->endpos = 0;
141
142 return 0;
143 }
144
145 const char *read_dir(dirhandle *dh)
146 {
147 const char *ret;
148
149 if (dh->pos >= dh->endpos) {
150 dh->curr = dh->data;
151 dh->pos = 0;
152 dh->endpos = getdents(dh->fd, dh->data, sizeof(dh->data));
153 if (dh->endpos <= 0)
154 return NULL;
155 }
156
157 ret = dh->curr->d_name;
158
159 dh->pos += dh->curr->d_reclen;
160 dh->curr = (struct dirent *)((char *)dh->data + dh->pos);
161
162 return ret;
163 }
164
165 void close_dir(dirhandle *dh)
166 {
167 close(dh->fd);
168 }
169
170 #endif /* !defined __linux__ || defined HAVE_FDOPENDIR */
171
172 static int str_cmp(const void *av, const void *bv)
173 {
174 return strcmp(*(const char **)av, *(const char **)bv);
175 }
176
177 static void du_recurse(char **path, size_t pathlen, size_t *pathsize,
178 gotdata_fn_t gotdata, void *gotdata_ctx)
179 {
180 const char *name;
181 dirhandle d;
182 STRUCT_STAT st;
183 char **names;
184 size_t i, nnames, namesize;
185
186 if (LSTAT(*path, &st) < 0) {
187 fprintf(stderr, "%s: lstat: %s\n", *path, strerror(errno));
188 return;
189 }
190
191 if (!gotdata(gotdata_ctx, *path, &st))
192 return;
193
194 if (!S_ISDIR(st.st_mode))
195 return;
196
197 names = NULL;
198 nnames = namesize = 0;
199
200 if (open_dir(*path, &d) < 0) {
201 fprintf(stderr, "%s: opendir: %s\n", *path, strerror(errno));
202 return;
203 }
204 while ((name = read_dir(&d)) != NULL) {
205 if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) {
206 /* do nothing - we skip "." and ".." */
207 } else {
208 if (nnames >= namesize) {
209 namesize = nnames * 3 / 2 + 64;
210 names = sresize(names, namesize, char *);
211 }
212 names[nnames++] = dupstr(name);
213 }
214 }
215 close_dir(&d);
216
217 if (nnames == 0)
218 return;
219
220 qsort(names, nnames, sizeof(*names), str_cmp);
221
222 for (i = 0; i < nnames; i++) {
223 size_t newpathlen = pathlen + 1 + strlen(names[i]);
224 if (*pathsize <= newpathlen) {
225 *pathsize = newpathlen * 3 / 2 + 256;
226 *path = sresize(*path, *pathsize, char);
227 }
228 /*
229 * Avoid duplicating a slash if we got a trailing one to
230 * begin with (i.e. if we're starting the scan in '/' itself).
231 */
232 if (pathlen > 0 && (*path)[pathlen-1] == '/') {
233 strcpy(*path + pathlen, names[i]);
234 newpathlen--;
235 } else {
236 sprintf(*path + pathlen, "/%s", names[i]);
237 }
238
239 du_recurse(path, newpathlen, pathsize, gotdata, gotdata_ctx);
240
241 sfree(names[i]);
242 }
243 sfree(names);
244 }
245
246 void du(const char *inpath, gotdata_fn_t gotdata, void *gotdata_ctx)
247 {
248 char *path;
249 size_t pathlen, pathsize;
250
251 pathlen = strlen(inpath);
252 pathsize = pathlen + 256;
253 path = snewn(pathsize, char);
254 strcpy(path, inpath);
255
256 du_recurse(&path, pathlen, &pathsize, gotdata, gotdata_ctx);
257 }