File: du.c

package info (click to toggle)
agedu 8928-1
  • links: PTS
  • area: main
  • in suites: squeeze, wheezy
  • size: 696 kB
  • ctags: 383
  • sloc: ansic: 3,919; sh: 787; makefile: 56
file content (263 lines) | stat: -rw-r--r-- 6,312 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
/*
 * du.c: implementation of du.h.
 */

#include "agedu.h"
#include "du.h"
#include "alloc.h"

#if !defined __linux__ || !defined O_NOATIME || defined HAVE_FDOPENDIR

#ifdef HAVE_DIRENT_H
#  include <dirent.h>
#endif
#ifdef HAVE_NDIR_H
#  include <ndir.h>
#endif
#ifdef HAVE_SYS_DIR_H
#  include <sys/dir.h>
#endif
#ifdef HAVE_SYS_NDIR_H
#  include <sys/ndir.h>
#endif

/*
 * Wrappers around POSIX opendir, readdir and closedir, which
 * permit me to replace them with different wrappers in special
 * circumstances.
 */

typedef DIR *dirhandle;

int open_dir(const char *path, dirhandle *dh)
{
#if defined O_NOATIME && defined HAVE_FDOPENDIR

    /*
     * On Linux, we have the O_NOATIME flag. This means we can
     * read the contents of directories without affecting their
     * atimes, which enables us to at least try to include them in
     * the age display rather than exempting them.
     *
     * Unfortunately, opendir() doesn't let us open a directory
     * with O_NOATIME. So instead, we have to open the directory
     * with vanilla open(), and then use fdopendir() to translate
     * the fd into a POSIX dir handle.
     */
    int fd;
    
    fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_LARGEFILE |
	      O_NOATIME | O_DIRECTORY);
    if (fd < 0) {
	/*
	 * Opening a file with O_NOATIME is not unconditionally
	 * permitted by the Linux kernel. As far as I can tell,
	 * it's permitted only for files on which the user would
	 * have been able to call utime(2): in other words, files
	 * for which the user could have deliberately set the
	 * atime back to its original value after finishing with
	 * it. Hence, O_NOATIME has no security implications; it's
	 * simply a cleaner, faster and more race-condition-free
	 * alternative to stat(), a normal open(), and a utimes()
	 * when finished.
	 *
	 * The upshot of all of which, for these purposes, is that
	 * we must be prepared to try again without O_NOATIME if
	 * we receive EPERM.
	 */
	if (errno == EPERM)
	    fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY |
		      O_LARGEFILE | O_DIRECTORY);
	if (fd < 0)
	    return -1;
    }

    *dh = fdopendir(fd);
#else
    *dh = opendir(path);
#endif

    if (!*dh)
	return -1;
    return 0;
}

const char *read_dir(dirhandle *dh)
{
    struct dirent *de = readdir(*dh);
    return de ? de->d_name : NULL;
}

void close_dir(dirhandle *dh)
{
    closedir(*dh);
}

#else /* defined __linux__ && !defined HAVE_FDOPENDIR */

/*
 * Earlier versions of glibc do not have fdopendir(). Therefore,
 * if we are on Linux and still wish to make use of O_NOATIME, we
 * have no option but to talk directly to the kernel system call
 * interface which underlies the POSIX opendir/readdir machinery.
 */

#define __KERNEL__
#include <linux/types.h>
#include <linux/dirent.h>
#include <linux/unistd.h>

_syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count)

typedef struct {
    int fd;
    struct dirent data[32];
    struct dirent *curr;
    int pos, endpos;
} dirhandle;

int open_dir(const char *path, dirhandle *dh)
{
    /*
     * As above, we try with O_NOATIME and then fall back to
     * trying without it.
     */
    dh->fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY | O_LARGEFILE |
		  O_NOATIME | O_DIRECTORY);
    if (dh->fd < 0) {
	if (errno == EPERM)
	    dh->fd = open(path, O_RDONLY | O_NONBLOCK | O_NOCTTY |
			  O_LARGEFILE | O_DIRECTORY);
	if (dh->fd < 0)
	    return -1;
    }

    dh->pos = dh->endpos = 0;

    return 0;
}

const char *read_dir(dirhandle *dh)
{
    const char *ret;

    if (dh->pos >= dh->endpos) {
	dh->curr = dh->data;
	dh->pos = 0;
	dh->endpos = getdents(dh->fd, dh->data, sizeof(dh->data));
	if (dh->endpos <= 0)
	    return NULL;
    }

    ret = dh->curr->d_name;

    dh->pos += dh->curr->d_reclen;
    dh->curr = (struct dirent *)((char *)dh->data + dh->pos);

    return ret;
}

void close_dir(dirhandle *dh)
{
    close(dh->fd);
}

#endif /* !defined __linux__ || defined HAVE_FDOPENDIR */

static int str_cmp(const void *av, const void *bv)
{
    return strcmp(*(const char **)av, *(const char **)bv);
}

static void du_recurse(char **path, size_t pathlen, size_t *pathsize,
		       gotdata_fn_t gotdata, err_fn_t err, void *gotdata_ctx,
		       int toplevel)
{
    const char *name;
    dirhandle d;
    STRUCT_STAT st;
    char **names;
    size_t i, nnames, namesize;
    int statret;

    /*
     * Special case: at the very top of the scan, we follow a
     * symlink.
     */
    if (toplevel)
	statret = STAT(*path, &st);
    else
	statret = LSTAT(*path, &st);
    if (statret < 0) {
	err(gotdata_ctx, "%s: lstat: %s\n", *path, strerror(errno));
	return;
    }

    if (!gotdata(gotdata_ctx, *path, &st))
	return;

    if (!S_ISDIR(st.st_mode))
	return;

    names = NULL;
    nnames = namesize = 0;

    if (open_dir(*path, &d) < 0) {
	err(gotdata_ctx, "%s: opendir: %s\n", *path, strerror(errno));
	return;
    }
    while ((name = read_dir(&d)) != NULL) {
	if (name[0] == '.' && (!name[1] || (name[1] == '.' && !name[2]))) {
	    /* do nothing - we skip "." and ".." */
	} else {
	    if (nnames >= namesize) {
		namesize = nnames * 3 / 2 + 64;
		names = sresize(names, namesize, char *);
	    }
	    names[nnames++] = dupstr(name);
	}
    }
    close_dir(&d);

    if (nnames == 0)
	return;

    qsort(names, nnames, sizeof(*names), str_cmp);

    for (i = 0; i < nnames; i++) {
	size_t newpathlen = pathlen + 1 + strlen(names[i]);
	if (*pathsize <= newpathlen) {
	    *pathsize = newpathlen * 3 / 2 + 256;
	    *path = sresize(*path, *pathsize, char);
	}
	/*
	 * Avoid duplicating a slash if we got a trailing one to
	 * begin with (i.e. if we're starting the scan in '/' itself).
	 */
	if (pathlen > 0 && (*path)[pathlen-1] == '/') {
	    strcpy(*path + pathlen, names[i]);
	    newpathlen--;
	} else {
	    sprintf(*path + pathlen, "/%s", names[i]);
	}

	du_recurse(path, newpathlen, pathsize, gotdata, err, gotdata_ctx, 0);

	sfree(names[i]);
    }
    sfree(names);
}

void du(const char *inpath, gotdata_fn_t gotdata, err_fn_t err,
	void *gotdata_ctx)
{
    char *path;
    size_t pathlen, pathsize;

    pathlen = strlen(inpath);
    pathsize = pathlen + 256;
    path = snewn(pathsize, char);
    strcpy(path, inpath);

    du_recurse(&path, pathlen, &pathsize, gotdata, err, gotdata_ctx, 1);
}