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);
}
|