Change the magic number used to introduce a trie file, so that instead
[sgt/agedu] / du.c
diff --git a/du.c b/du.c
index 6df4dd6..fb39201 100644 (file)
--- a/du.c
+++ b/du.c
  * du.c: implementation of du.h.
  */
 
-#define _GNU_SOURCE
-#include <features.h>
+#include "agedu.h"
+#include "du.h"
+#include "alloc.h"
 
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <errno.h>
+#if !defined __linux__ || !defined O_NOATIME || defined HAVE_FDOPENDIR
 
-#include <sys/types.h>
-#include <sys/stat.h>
-#include <unistd.h>
+#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
 
-#include "du.h"
-#include "malloc.h"
+/*
+ * 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.
+ */
 
-#ifdef __linux__
 #define __KERNEL__
-#include <unistd.h>
-#include <fcntl.h>
 #include <linux/types.h>
 #include <linux/dirent.h>
 #include <linux/unistd.h>
-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
-#include <dirent.h>
-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)
-#endif
+} 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)
 {
@@ -59,16 +170,26 @@ 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)
+                      gotdata_fn_t gotdata, err_fn_t err, void *gotdata_ctx,
+                      int toplevel)
 {
-    direntry de;
+    const char *name;
     dirhandle d;
-    struct stat64 st;
+    STRUCT_STAT st;
     char **names;
     size_t i, nnames, namesize;
+    int statret;
 
-    if (lstat64(*path, &st) < 0) {
-       fprintf(stderr, "%s: lstat: %s\n", *path, strerror(errno));
+    /*
+     * 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;
     }
 
@@ -81,27 +202,22 @@ static void du_recurse(char **path, size_t pathlen, size_t *pathsize,
     names = NULL;
     nnames = namesize = 0;
 
-    d = OPENDIR(*path);
-    if (!DIRVALID(d)) {
-       fprintf(stderr, "%s: opendir: %s\n", *path, strerror(errno));
+    if (open_dir(*path, &d) < 0) {
+       err(gotdata_ctx, "%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;
@@ -125,22 +241,32 @@ static void du_recurse(char **path, size_t pathlen, size_t *pathsize,
            sprintf(*path + pathlen, "/%s", names[i]);
        }
 
-       du_recurse(path, newpathlen, pathsize, gotdata, gotdata_ctx);
+       du_recurse(path, newpathlen, pathsize, gotdata, err, gotdata_ctx, 0);
 
        sfree(names[i]);
     }
     sfree(names);
 }
 
-void du(const char *inpath, gotdata_fn_t gotdata, void *gotdata_ctx)
+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);
-    strcpy(path, inpath);
+    memcpy(path, inpath, pathlen);
+    path[pathlen] = '\0';
 
-    du_recurse(&path, pathlen, &pathsize, gotdata, gotdata_ctx);
+    du_recurse(&path, pathlen, &pathsize, gotdata, err, gotdata_ctx, 1);
 }