Build the HTML man page for my website, as part of the build script.
[sgt/agedu] / agedu.c
diff --git a/agedu.c b/agedu.c
index a104dd7..c3ac6a7 100644 (file)
--- a/agedu.c
+++ b/agedu.c
 #include <sys/ioctl.h>
 #include <fnmatch.h>
 
+#include "agedu.h"
 #include "du.h"
 #include "trie.h"
 #include "index.h"
-#include "malloc.h"
+#include "alloc.h"
 #include "html.h"
 #include "httpd.h"
 #include "fgetline.h"
 
-#define PNAME "agedu"
-
-#define lenof(x) (sizeof((x))/sizeof(*(x)))
-
 /*
  * Path separator. This global variable affects the behaviour of
  * various parts of the code when they need to deal with path
@@ -69,6 +66,7 @@ struct ctx {
     struct inclusion_exclusion *inex;
     int ninex;
     int crossfs;
+    int fakeatimes;
 };
 
 static void dump_line(const char *pathname, const struct trie_file *tf)
@@ -84,7 +82,7 @@ static void dump_line(const char *pathname, const struct trie_file *tf)
     putchar('\n');
 }
 
-static int gotdata(void *vctx, const char *pathname, const struct stat64 *st)
+static int gotdata(void *vctx, const char *pathname, const STRUCT_STAT *st)
 {
     struct ctx *ctx = (struct ctx *)vctx;
     struct trie_file file;
@@ -105,7 +103,10 @@ static int gotdata(void *vctx, const char *pathname, const struct stat64 *st)
        return 0;
 
     file.size = (unsigned long long)512 * st->st_blocks;
-    file.atime = st->st_atime;
+    if (ctx->fakeatimes && S_ISDIR(st->st_mode))
+       file.atime = st->st_mtime;
+    else
+       file.atime = st->st_atime;
 
     /*
      * Filter based on wildcards.
@@ -176,6 +177,9 @@ static void text_query(const void *mappedfile, const char *querydir,
     xi1 = trie_before(mappedfile, querydir);
     xi2 = trie_before(mappedfile, pathbuf);
 
+    if (xi2 - xi1 == 1)
+       return;                        /* file, or empty dir => no display */
+
     /*
      * Now do the lookups in the age index.
      */
@@ -258,56 +262,62 @@ static void text_query(const void *mappedfile, const char *querydir,
  * not bother defining logical identifiers for them at all - those
  * would be automatically generated, since I wouldn't have any
  * need to specify them manually in another part of the code.)
+ *
+ * One other helpful consequence of the enum-based structure here
+ * is that it causes a compiler error if I accidentally try to
+ * define the same option (short or long) twice.
  */
 
 #define OPTHELP(NOVAL, VAL, SHORT, LONG, HELPPFX, HELPARG, HELPLINE, HELPOPT) \
-    HELPPFX("usage") HELPLINE("agedu [options] action [action...]") \
+    HELPPFX("usage") HELPLINE(PNAME " [options] action [action...]") \
     HELPPFX("actions") \
     VAL(SCAN) SHORT(s) LONG(scan) \
        HELPARG("directory") HELPOPT("scan and index a directory") \
-    NOVAL(DUMP) SHORT(d) LONG(dump) HELPOPT("dump the index file on stdout") \
-    VAL(SCANDUMP) SHORT(S) LONG(scan_dump) \
-       HELPARG("directory") HELPOPT("scan only, generating a dump") \
-    NOVAL(LOAD) SHORT(l) LONG(load) \
-       HELPOPT("load and index a dump file") \
+    NOVAL(HTTPD) SHORT(w) LONG(web) LONG(server) LONG(httpd) \
+        HELPOPT("serve HTML reports from a temporary web server") \
     VAL(TEXT) SHORT(t) LONG(text) \
        HELPARG("subdir") HELPOPT("print a plain text report on a subdirectory") \
+    NOVAL(REMOVE) SHORT(R) LONG(remove) LONG(delete) LONG(unlink) \
+        HELPOPT("remove the index file") \
+    NOVAL(DUMP) SHORT(D) LONG(dump) HELPOPT("dump the index file on stdout") \
+    NOVAL(LOAD) SHORT(L) LONG(load) \
+       HELPOPT("load and index a dump file") \
+    VAL(SCANDUMP) SHORT(S) LONG(scan_dump) \
+       HELPARG("directory") HELPOPT("scan only, generating a dump") \
     VAL(HTML) SHORT(H) LONG(html) \
        HELPARG("subdir") HELPOPT("print an HTML report on a subdirectory") \
-    NOVAL(HTTPD) SHORT(w) LONG(web) LONG(server) LONG(httpd) \
-        HELPOPT("serve HTML reports from a temporary web server") \
     HELPPFX("options") \
     VAL(DATAFILE) SHORT(f) LONG(file) \
-        HELPARG("filename") HELPOPT("[all modes] specify index file") \
-    NOVAL(PROGRESS) LONG(progress) LONG(scan_progress) \
-        HELPOPT("[--scan] report progress on stderr") \
-    NOVAL(NOPROGRESS) LONG(no_progress) LONG(no_scan_progress) \
-        HELPOPT("[--scan] do not report progress") \
-    NOVAL(TTYPROGRESS) LONG(tty_progress) LONG(tty_scan_progress) \
-                      LONG(progress_tty) LONG(scan_progress_tty) \
-        HELPOPT("[--scan] report progress if stderr is a tty") \
+        HELPARG("filename") HELPOPT("[most modes] specify index file") \
     NOVAL(CROSSFS) LONG(cross_fs) \
         HELPOPT("[--scan] cross filesystem boundaries") \
     NOVAL(NOCROSSFS) LONG(no_cross_fs) \
         HELPOPT("[--scan] stick to one filesystem") \
-    VAL(INCLUDE) LONG(include) \
-        HELPARG("wildcard") HELPOPT("[--scan] include files matching pattern") \
-    VAL(INCLUDEPATH) LONG(include_path) \
-        HELPARG("wildcard") HELPOPT("[--scan] include pathnames matching pattern") \
-    VAL(EXCLUDE) LONG(exclude) \
-        HELPARG("wildcard") HELPOPT("[--scan] exclude files matching pattern") \
-    VAL(EXCLUDEPATH) LONG(exclude_path) \
-        HELPARG("wildcard") HELPOPT("[--scan] exclude pathnames matching pattern") \
     VAL(PRUNE) LONG(prune) \
         HELPARG("wildcard") HELPOPT("[--scan] prune files matching pattern") \
     VAL(PRUNEPATH) LONG(prune_path) \
         HELPARG("wildcard") HELPOPT("[--scan] prune pathnames matching pattern") \
-    VAL(TQDEPTH) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
-        HELPARG("levels") HELPOPT("[--text] recurse to this many levels") \
-    VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
-        HELPARG("age") HELPOPT("[--text] include only files older than this") \
+    VAL(EXCLUDE) LONG(exclude) \
+        HELPARG("wildcard") HELPOPT("[--scan] exclude files matching pattern") \
+    VAL(EXCLUDEPATH) LONG(exclude_path) \
+        HELPARG("wildcard") HELPOPT("[--scan] exclude pathnames matching pattern") \
+    VAL(INCLUDE) LONG(include) \
+        HELPARG("wildcard") HELPOPT("[--scan] include files matching pattern") \
+    VAL(INCLUDEPATH) LONG(include_path) \
+        HELPARG("wildcard") HELPOPT("[--scan] include pathnames matching pattern") \
+    NOVAL(PROGRESS) LONG(progress) LONG(scan_progress) \
+        HELPOPT("[--scan] report progress on stderr") \
+    NOVAL(NOPROGRESS) LONG(no_progress) LONG(no_scan_progress) \
+        HELPOPT("[--scan] do not report progress") \
+    NOVAL(TTYPROGRESS) LONG(tty_progress) LONG(tty_scan_progress) \
+                      LONG(progress_tty) LONG(scan_progress_tty) \
+        HELPOPT("[--scan] report progress if stderr is a tty") \
+    NOVAL(DIRATIME) LONG(dir_atime) LONG(dir_atimes) \
+        HELPOPT("[--scan,--load] keep real atimes on directories") \
+    NOVAL(NODIRATIME) LONG(no_dir_atime) LONG(no_dir_atimes) \
+        HELPOPT("[--scan,--load] fake atimes on directories") \
     VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
-        HELPARG("age[-age]") HELPOPT("[--html,--web] set limits of colour coding") \
+        HELPARG("age[-age]") HELPOPT("[--web,--html] set limits of colour coding") \
     VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
               LONG(server_addr) \
         HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
@@ -318,6 +328,10 @@ static void text_query(const void *mappedfile, const char *querydir,
         HELPARG("filename") HELPOPT("[--web] read HTTP Basic user/pass from file") \
     VAL(AUTHFD) LONG(auth_fd) \
         HELPARG("fd") HELPOPT("[--web] read HTTP Basic user/pass from fd") \
+    VAL(TQDEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
+        HELPARG("levels") HELPOPT("[--text] recurse to this many levels") \
+    VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
+        HELPARG("age") HELPOPT("[--text] include only files older than this") \
     HELPPFX("also") \
     NOVAL(HELP) SHORT(h) LONG(help) HELPOPT("display this help text") \
     NOVAL(VERSION) SHORT(V) LONG(version) HELPOPT("report version number") \
@@ -442,9 +456,9 @@ int main(int argc, char **argv)
     triewalk *tw;
     indexbuild *ib;
     const struct trie_file *tf;
-    char *filename = "agedu.dat";
+    char *filename = PNAME ".dat";
     int doing_opts = 1;
-    enum { TEXT, HTML, SCAN, DUMP, SCANDUMP, LOAD, HTTPD };
+    enum { TEXT, HTML, SCAN, DUMP, SCANDUMP, LOAD, HTTPD, REMOVE };
     struct action {
        int mode;
        char *arg;
@@ -462,6 +476,7 @@ int main(int argc, char **argv)
     int ninex = 0, inexsize = 0;
     int crossfs = 0;
     int tqdepth = 1;
+    int fakediratimes = 1;
 
 #ifdef DEBUG_MAD_OPTION_PARSING_MACROS
     {
@@ -601,10 +616,23 @@ int main(int argc, char **argv)
                    usage(stdout);
                    return 0;
                  case OPT_VERSION:
-                   printf("FIXME: version();\n");
+#ifdef PACKAGE_VERSION
+                   printf("%s, revision %s\n", PNAME, PACKAGE_VERSION);
+#else
+                   printf("%s: version number not available when not built"
+                          " via automake\n", PNAME);
+#endif
                    return 0;
                  case OPT_LICENCE:
-                   printf("FIXME: licence();\n");
+                   {
+                       extern const char *const licence[];
+                       int i;
+
+                       for (i = 0; licence[i]; i++)
+                           fputs(licence[i], stdout);
+
+                       return 0;
+                   }
                    return 0;
                  case OPT_SCAN:
                    if (nactions >= actionsize) {
@@ -669,6 +697,15 @@ int main(int argc, char **argv)
                    actions[nactions].arg = NULL;
                    nactions++;
                    break;
+                 case OPT_REMOVE:
+                   if (nactions >= actionsize) {
+                       actionsize = nactions * 3 / 2 + 16;
+                       actions = sresize(actions, actionsize, struct action);
+                   }
+                   actions[nactions].mode = REMOVE;
+                   actions[nactions].arg = NULL;
+                   nactions++;
+                   break;
                  case OPT_PROGRESS:
                    progress = 2;
                    break;
@@ -684,6 +721,12 @@ int main(int argc, char **argv)
                  case OPT_NOCROSSFS:
                    crossfs = 0;
                    break;
+                 case OPT_DIRATIME:
+                   fakediratimes = 0;
+                   break;
+                 case OPT_NODIRATIME:
+                   fakediratimes = 1;
+                   break;
                  case OPT_DATAFILE:
                    filename = optval;
                    break;
@@ -731,7 +774,7 @@ int main(int argc, char **argv)
                        auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
                    else if (!strcmp(optval, "help") ||
                             !strcmp(optval, "list")) {
-                       printf("agedu: supported HTTP authentication types"
+                       printf(PNAME ": supported HTTP authentication types"
                               " are:\n"
                               "       magic      use Linux /proc/net/tcp to"
                               " determine owner of peer socket\n"
@@ -840,7 +883,7 @@ int main(int argc, char **argv)
                char *buf = fgetline(stdin);
                unsigned newpathsep;
                buf[strcspn(buf, "\r\n")] = '\0';
-               if (1 != sscanf(buf, "agedu dump file. pathsep=%x",
+               if (1 != sscanf(buf, DUMPHDR "%x",
                                &newpathsep)) {
                    fprintf(stderr, "%s: header in dump file not recognised\n",
                            PNAME);
@@ -861,7 +904,7 @@ int main(int argc, char **argv)
                    return 1;
                }
                if (fstat(fd, &st) < 0) {
-                   perror("agedu: fstat");
+                   perror(PNAME ": fstat");
                    return 1;
                }
                ctx->datafile_dev = st.st_dev;
@@ -885,6 +928,7 @@ int main(int argc, char **argv)
            ctx->inex = inex;
            ctx->ninex = ninex;
            ctx->crossfs = crossfs;
+           ctx->fakeatimes = fakediratimes;
 
            ctx->last_output_update = time(NULL);
 
@@ -901,7 +945,7 @@ int main(int argc, char **argv)
            }
 
            if (mode == SCANDUMP)
-               printf("agedu dump file. pathsep=%02x\n", (unsigned char)pathsep);
+               printf(DUMPHDR "%02x\n", (unsigned char)pathsep);
 
            /*
             * Scan the directory tree, and write out the trie component
@@ -946,6 +990,7 @@ int main(int argc, char **argv)
                            p++;
                            c = 0;
                            for (i = 0; i < 2; i++) {
+                               c *= 16;
                                if (*p >= '0' && *p <= '9')
                                    c += *p - '0';
                                else if (*p >= 'A' && *p <= 'F')
@@ -965,6 +1010,7 @@ int main(int argc, char **argv)
                    *q = '\0';
                    triebuild_add(ctx->tb, buf, &tf);
                    sfree(buf);
+                   line++;
                }
            } else {
                du(scandir, gotdata, ctx);
@@ -983,7 +1029,7 @@ int main(int argc, char **argv)
                 * will take; enlarge the file, and memory-map it.
                 */
                if (fstat(fd, &st) < 0) {
-                   perror("agedu: fstat");
+                   perror(PNAME ": fstat");
                    return 1;
                }
 
@@ -993,11 +1039,11 @@ int main(int argc, char **argv)
                totalsize = index_compute_size(st.st_size, count);
 
                if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
-                   perror("agedu: lseek");
+                   perror(PNAME ": lseek");
                    return 1;
                }
                if (write(fd, "\0", 1) < 1) {
-                   perror("agedu: write");
+                   perror(PNAME ": write");
                    return 1;
                }
 
@@ -1006,10 +1052,16 @@ int main(int argc, char **argv)
 
                mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
                if (!mappedfile) {
-                   perror("agedu: mmap");
+                   perror(PNAME ": mmap");
                    return 1;
                }
 
+               if (fakediratimes) {
+                   printf("Faking directory atimes\n");
+                   trie_fake_dir_atimes(mappedfile);
+               }
+
+               printf("Building index\n");
                ib = indexbuild_new(mappedfile, st.st_size, count);
                tw = triewalk_new(mappedfile);
                while ((tf = triewalk_next(tw, NULL)) != NULL)
@@ -1034,13 +1086,13 @@ int main(int argc, char **argv)
                return 1;
            }
            if (fstat(fd, &st) < 0) {
-               perror("agedu: fstat");
+               perror(PNAME ": fstat");
                return 1;
            }
            totalsize = st.st_size;
            mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
            if (!mappedfile) {
-               perror("agedu: mmap");
+               perror(PNAME ": mmap");
                return 1;
            }
            pathsep = trie_pathsep(mappedfile);
@@ -1067,13 +1119,13 @@ int main(int argc, char **argv)
                return 1;
            }
            if (fstat(fd, &st) < 0) {
-               perror("agedu: fstat");
+               perror(PNAME ": fstat");
                return 1;
            }
            totalsize = st.st_size;
            mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
            if (!mappedfile) {
-               perror("agedu: mmap");
+               perror(PNAME ": mmap");
                return 1;
            }
            pathsep = trie_pathsep(mappedfile);
@@ -1103,13 +1155,13 @@ int main(int argc, char **argv)
                return 1;
            }
            if (fstat(fd, &st) < 0) {
-               perror("agedu: fstat");
+               perror(PNAME ": fstat");
                return 1;
            }
            totalsize = st.st_size;
            mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
            if (!mappedfile) {
-               perror("agedu: mmap");
+               perror(PNAME ": mmap");
                return 1;
            }
            pathsep = trie_pathsep(mappedfile);
@@ -1117,7 +1169,7 @@ int main(int argc, char **argv)
            maxpathlen = trie_maxpathlen(mappedfile);
            buf = snewn(maxpathlen, char);
 
-           printf("agedu dump file. pathsep=%02x\n", (unsigned char)pathsep);
+           printf(DUMPHDR "%02x\n", (unsigned char)pathsep);
            tw = triewalk_new(mappedfile);
            while ((tf = triewalk_next(tw, buf)) != NULL)
                dump_line(buf, tf);
@@ -1133,13 +1185,13 @@ int main(int argc, char **argv)
                return 1;
            }
            if (fstat(fd, &st) < 0) {
-               perror("agedu: fstat");
+               perror(PNAME ": fstat");
                return 1;
            }
            totalsize = st.st_size;
            mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
            if (!mappedfile) {
-               perror("agedu: mmap");
+               perror(PNAME ": mmap");
                return 1;
            }
            pathsep = trie_pathsep(mappedfile);
@@ -1152,6 +1204,12 @@ int main(int argc, char **argv)
            pcfg.oldest = htmloldest;
            pcfg.newest = htmlnewest;
            run_httpd(mappedfile, auth, &dcfg, &pcfg);
+       } else if (mode == REMOVE) {
+           if (remove(filename) < 0) {
+               fprintf(stderr, "%s: %s: remove: %s\n", PNAME, filename,
+                       strerror(errno));
+               return 1;
+           }
        }
     }