/* * du.c: implementation of du.h. */ #include "agedu.h" #include "du.h" #include "alloc.h" #if !defined __linux__ || !defined O_NOATIME || HAVE_FDOPENDIR #if HAVE_DIRENT_H # include #endif #if HAVE_NDIR_H # include #endif #if HAVE_SYS_DIR_H # include #endif #if HAVE_SYS_NDIR_H # include #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 && 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 O_NOATIME && !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 #include #include _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 O_NOATIME || 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_FUNC(*path, &st); else statret = LSTAT_FUNC(*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++) { /* * readdir(3) has occasionally been known to report two copies * of the identical file name, in cases involving strange file * system implementations or (possibly) race conditions. To * avoid failing an assertion in the trie code, de-duplicate. */ if (i+1 < nnames && !strcmp(names[i], names[i+1])) continue; 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); /* * Trim any trailing slashes from the input path, otherwise we'll * store them in the index with confusing effects. */ while (pathlen > 1 && inpath[pathlen-1] == '/') pathlen--; pathsize = pathlen + 256; path = snewn(pathsize, char); memcpy(path, inpath, pathlen); path[pathlen] = '\0'; du_recurse(&path, pathlen, &pathsize, gotdata, err, gotdata_ctx, 1); }