From: simon Date: Sun, 2 Nov 2008 09:47:45 +0000 (+0000) Subject: Turn my disgusting readdir macros into a reasonably clean function X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/agedu/commitdiff_plain/704fafa3ca85f378c91f8b1fdd3dfd9f34d02c8c Turn my disgusting readdir macros into a reasonably clean function call interface. In the immediate short term this permits me to cope when open(...,O_NOATIME) returns EPERM; in the longer term it should also allow me to drop in other readdir mechanisms more easily, perhaps including Windows. git-svn-id: svn://svn.tartarus.org/sgt/agedu@8250 cda61777-01e9-0310-a592-d414129be87e --- diff --git a/TODO b/TODO index 25b4fd2..e742e4b 100644 --- a/TODO +++ b/TODO @@ -3,19 +3,8 @@ TODO list for agedu Before it's non-embarrassingly releasable: - - work out what to do about atimes on directories in the absence of - the Linux syscall magic - * one option is to read them during the scan and reinstate them - after each recursion pop. Race-condition prone. - * marking them in a distinctive colour in the reports is another - option. - * a third option is simply to ignore space taken up by - directories in the first place; inaccurate but terribly simple. - * incidentally, sometimes open(...,O_NOATIME) will fail, and - then we have to fall back to ordinary open. Be prepared to do - this, which probably means getting rid of the icky macro - hackery in du.c and turning it into a more sensible run-time - abstraction layer. + - arrange to be able to identify directories and leave them on one + side of the usual age display - cross-Unix portability: + use autoconf @@ -54,20 +43,6 @@ Future possibilities: straight to terminfo: generate lines of attribute-interleaved text and display them, so we only really need the sequences "go here and display stuff", "scroll up", "scroll down". - + I think the attribute-interleaved text might be possible to do - cunningly, as well: we autodetect a basically VT-style - terminal, and add 256-colour sequences on the end. So, for - instance, we might set ANSI-yellow foreground, set ANSI-red - background, _then_ set both foreground and background to the - appropriate xterm 256-colour, and then display some - appropriate character which would have given the right blend - of the ANSI-16 fore and background colours. Then the same - display code should gracefully degrade in the face of a - terminal which doesn't support xterm-256. - * current best plan is to simulate the xterm-256 shading from - 0/5 to 5/5 by doing space, colon and hash in colour A on - colour B background, then hash, colon and space in B on A - background. + Infrastructure work before doing any of this would be to split html.c into two: one part to prepare an abstract data structure describing an HTML-like report (in particular, all diff --git a/du.c b/du.c index 6df4dd6..957c6e7 100644 --- a/du.c +++ b/du.c @@ -18,39 +18,123 @@ #include "malloc.h" #ifdef __linux__ + +/* + * 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. In later glibcs we can open one manually using + * open() and then use fdopendir() to translate the fd into a + * POSIX dir handle; in earlier glibcs fdopendir() is not + * available, so 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 #include #include -typedef int dirhandle; + +_syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count) + typedef struct { + int fd; struct dirent data[32]; struct dirent *curr; int pos, endpos; -} direntry; -_syscall3(int, getdents, uint, fd, struct dirent *, dirp, uint, count) -#define OPENDIR(f) open(f, O_RDONLY | O_NOATIME | O_DIRECTORY) -#define DIRVALID(dh) ((dh) >= 0) -#define READDIR(dh,de) ((de).curr = (de).data, (de).pos = 0, \ - ((de).endpos = getdents((dh), (de).data, sizeof((de).data))) > 0) -#define DENAME(de) ((de).curr->d_name) -#define DEDONE(de) ((de).pos >= (de).endpos) -#define DEADVANCE(de) ((de).pos += (de).curr->d_reclen, \ - (de).curr = (struct dirent *)((char *)(de).data + (de).pos)) -#define CLOSEDIR(dh) close(dh) -#else +} dirhandle; + +int open_dir(const char *path, dirhandle *dh) +{ + dh->fd = open(path, O_RDONLY | O_NOATIME | O_DIRECTORY); + if (dh->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) + dh->fd = open(path, O_RDONLY | 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); +} + +#else /* __linux__ */ + +/* + * This branch of the ifdef is a simple exercise of ordinary POSIX + * opendir/readdir. + */ + #include typedef DIR *dirhandle; -typedef struct dirent *direntry; -#define OPENDIR(f) opendir(f) -#define DIRVALID(dh) ((dh) != NULL) -#define READDIR(dh,de) (((de) = readdir(dh)) ? 1 : 0) -#define DENAME(de) ((de)->d_name) -#define DEDONE(de) ((de) == NULL) -#define DEADVANCE(de) ((de) = NULL) -#define CLOSEDIR(dh) closedir(dh) + +int open_dir(const char *path, dirhandle *dh) +{ + *dh = opendir(path); + 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); +} + #endif static int str_cmp(const void *av, const void *bv) @@ -61,7 +145,7 @@ static int str_cmp(const void *av, const void *bv) static void du_recurse(char **path, size_t pathlen, size_t *pathsize, gotdata_fn_t gotdata, void *gotdata_ctx) { - direntry de; + const char *name; dirhandle d; struct stat64 st; char **names; @@ -81,27 +165,22 @@ static void du_recurse(char **path, size_t pathlen, size_t *pathsize, names = NULL; nnames = namesize = 0; - d = OPENDIR(*path); - if (!DIRVALID(d)) { + if (open_dir(*path, &d) < 0) { fprintf(stderr, "%s: opendir: %s\n", *path, strerror(errno)); return; } - while (READDIR(d, de)) { - do { - const char *name = DENAME(de); - 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); + 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 *); } - DEADVANCE(de); - } while (!DEDONE(de)); + names[nnames++] = dupstr(name); + } } - CLOSEDIR(d); + close_dir(&d); if (nnames == 0) return;