Make the existing -d (depth) option apply to the -H (static HTML
[sgt/agedu] / agedu.c
diff --git a/agedu.c b/agedu.c
index c1ed291..463104f 100644 (file)
--- a/agedu.c
+++ b/agedu.c
@@ -56,14 +56,18 @@ struct ctx {
 static void dump_line(const char *pathname, const struct trie_file *tf)
 {
     const char *p;
-    printf("%llu %llu ", tf->size, tf->atime);
+    if (printf("%llu %llu ", tf->size, tf->atime) < 0) goto error;
     for (p = pathname; *p; p++) {
-       if (*p >= ' ' && *p < 127 && *p != '%')
-           putchar(*p);
-       else
-           printf("%%%02x", (unsigned char)*p);
+       if (*p >= ' ' && *p < 127 && *p != '%') {
+           if (putchar(*p) == EOF) goto error;
+       } else {
+           if (printf("%%%02x", (unsigned char)*p) < 0) goto error;
+       }
     }
-    putchar('\n');
+    if (putchar('\n') == EOF) goto error;
+    return;
+    error:
+    fatal("standard output: %s", strerror(errno));
 }
 
 static int gotdata(void *vctx, const char *pathname, const STRUCT_STAT *st)
@@ -90,7 +94,7 @@ static int gotdata(void *vctx, const char *pathname, const STRUCT_STAT *st)
     if (ctx->usemtime || (ctx->fakeatimes && S_ISDIR(st->st_mode)))
        file.atime = st->st_mtime;
     else
-       file.atime = st->st_atime;
+       file.atime = max(st->st_mtime, st->st_atime);
 
     /*
      * Filter based on wildcards.
@@ -159,12 +163,12 @@ static void scan_error(void *vctx, const char *fmt, ...)
 }
 
 static void text_query(const void *mappedfile, const char *querydir,
-                      time_t t, int depth)
+                      time_t t, int showfiles, int depth, FILE *fp)
 {
     size_t maxpathlen;
     char *pathbuf;
     unsigned long xi1, xi2;
-    unsigned long long s1, s2;
+    unsigned long long size;
 
     maxpathlen = trie_maxpathlen(mappedfile);
     pathbuf = snewn(maxpathlen + 1, char);
@@ -179,34 +183,51 @@ static void text_query(const void *mappedfile, const char *querydir,
     xi1 = trie_before(mappedfile, querydir);
     xi2 = trie_before(mappedfile, pathbuf);
 
-    if (xi2 - xi1 == 1)
+    if (!showfiles && xi2 - xi1 == 1)
        return;                        /* file, or empty dir => no display */
 
     /*
      * Now do the lookups in the age index.
      */
-    s1 = index_query(mappedfile, xi1, t);
-    s2 = index_query(mappedfile, xi2, t);
+    if (xi2 - xi1 == 1) {
+       /*
+        * We are querying an individual file, so we should not
+        * depend on the index entries either side of the node,
+        * since they almost certainly don't both exist. Instead,
+        * just look up the file's size and atime in the main trie.
+        */
+       const struct trie_file *f = trie_getfile(mappedfile, xi1);
+       if (f->atime < t)
+           size = f->size;
+       else
+           size = 0;
+    } else {
+       unsigned long long s1, s2;
+       s1 = index_query(mappedfile, xi1, t);
+       s2 = index_query(mappedfile, xi2, t);
+       size = s2 - s1;
+    }
 
-    if (s1 == s2)
+    if (size == 0)
        return;                        /* no space taken up => no display */
 
-    if (depth > 0) {
+    if (depth != 0) {
        /*
         * Now scan for first-level subdirectories and report
         * those too.
         */
+       int newdepth = (depth > 0 ? depth - 1 : depth);
        xi1++;
        while (xi1 < xi2) {
            trie_getpath(mappedfile, xi1, pathbuf);
-           text_query(mappedfile, pathbuf, t, depth-1);
+           text_query(mappedfile, pathbuf, t, showfiles, newdepth, fp);
            make_successor(pathbuf);
            xi1 = trie_before(mappedfile, pathbuf);
        }
     }
 
     /* Display in units of 1Kb */
-    printf("%-11llu %s\n", (s2 - s1) / 1024, querydir);
+    fprintf(fp, "%-11llu %s\n", (size) / 1024, querydir);
 }
 
 /*
@@ -320,10 +341,12 @@ static void text_query(const void *mappedfile, const char *querydir,
         HELPOPT("[--scan,--load] fake atimes on directories") \
     NOVAL(MTIME) LONG(mtime) \
         HELPOPT("[--scan] use mtime instead of atime") \
-    NOVAL(FULL) LONG(full_index) \
-        HELPOPT("[--scan] index every file individually") \
+    NOVAL(SHOWFILES) LONG(files) \
+        HELPOPT("[--web,--html,--text] list individual files") \
     VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
         HELPARG("age[-age]") HELPOPT("[--web,--html] set limits of colour coding") \
+    VAL(OUTFILE) SHORT(o) LONG(output) \
+       HELPARG("filename") HELPOPT("[--html] specify output file or directory name") \
     VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
               LONG(server_addr) \
         HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
@@ -334,8 +357,8 @@ 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(DEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
+        HELPARG("levels") HELPOPT("[--text,--html] 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") \
@@ -476,15 +499,16 @@ int main(int argc, char **argv)
     const char *httpserveraddr = NULL;
     int httpserverport = 0;
     const char *httpauthdata = NULL;
+    const char *outfile = NULL;
     int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
     int progress = 1;
     struct inclusion_exclusion *inex = NULL;
     int ninex = 0, inexsize = 0;
     int crossfs = 0;
-    int tqdepth = 1;
+    int depth = -1, gotdepth = 0;
     int fakediratimes = 1;
     int mtime = 0;
-    int fullindex = 0;
+    int showfiles = 0;
 
 #ifdef DEBUG_MAD_OPTION_PARSING_MACROS
     {
@@ -735,17 +759,29 @@ int main(int argc, char **argv)
                  case OPT_NODIRATIME:
                    fakediratimes = 1;
                    break;
+                 case OPT_SHOWFILES:
+                   showfiles = 1;
+                   break;
                  case OPT_MTIME:
                    mtime = 1;
                    break;
-                 case OPT_FULL:
-                   fullindex = 1;
-                   break;
                  case OPT_DATAFILE:
                    filename = optval;
                    break;
-                 case OPT_TQDEPTH:
-                   tqdepth = atoi(optval);
+                 case OPT_DEPTH:
+                   if (!strcasecmp(optval, "unlimited") ||
+                       !strcasecmp(optval, "infinity") ||
+                       !strcasecmp(optval, "infinite") ||
+                       !strcasecmp(optval, "inf") ||
+                       !strcasecmp(optval, "maximum") ||
+                       !strcasecmp(optval, "max"))
+                       depth = -1;
+                   else
+                       depth = atoi(optval);
+                   gotdepth = 1;
+                   break;
+                 case OPT_OUTFILE:
+                   outfile = optval;
                    break;
                  case OPT_MINAGE:
                    textcutoff = parse_age(now, optval);
@@ -955,7 +991,9 @@ int main(int argc, char **argv)
            ctx->progress = progress;
            {
                struct winsize ws;
-               if (progress && ioctl(2, TIOCGWINSZ, &ws) == 0)
+               if (progress &&
+                   ioctl(2, TIOCGWINSZ, &ws) == 0 &&
+                   ws.ws_col > 0)
                    ctx->progwidth = ws.ws_col - 1;
                else
                    ctx->progwidth = 79;
@@ -1020,9 +1058,10 @@ int main(int argc, char **argv)
                                }
                                p++;
                            }
+                       } else {
+                           p++;
                        }
                        *q++ = c;
-                       p++;
                    }
                    *q = '\0';
                    triebuild_add(ctx->tb, buf, &tf);
@@ -1034,6 +1073,7 @@ int main(int argc, char **argv)
            }
            if (mode != SCANDUMP) {
                size_t maxpathlen;
+               size_t delta;
                char *buf, *prevbuf;
 
                count = triebuild_finish(ctx->tb);
@@ -1053,10 +1093,12 @@ int main(int argc, char **argv)
                    return 1;
                }
 
-               printf("Built pathname index, %d entries, %llu bytes\n", count,
+               printf("Built pathname index, %d entries,"
+                      " %llu bytes of index\n", count,
                       (unsigned long long)st.st_size);
 
-               totalsize = index_compute_size(st.st_size, count);
+               totalsize = index_initial_size(st.st_size, count);
+               totalsize += totalsize / 10;
 
                if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
                    perror(PNAME ": lseek");
@@ -1067,9 +1109,6 @@ int main(int argc, char **argv)
                    return 1;
                }
 
-               printf("Upper bound on index file size = %llu bytes\n",
-                      (unsigned long long)totalsize);
-
                mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
                if (!mappedfile) {
                    perror(PNAME ": mmap");
@@ -1082,7 +1121,7 @@ int main(int argc, char **argv)
                }
 
                printf("Building index\n");
-               ib = indexbuild_new(mappedfile, st.st_size, count);
+               ib = indexbuild_new(mappedfile, st.st_size, count, &delta);
                maxpathlen = trie_maxpathlen(mappedfile);
                buf = snewn(maxpathlen, char);
                prevbuf = snewn(maxpathlen, char);
@@ -1093,6 +1132,45 @@ int main(int argc, char **argv)
                while (1) {
                    int i;
 
+                   if (totalsize - indexbuild_realsize(ib) < delta) {
+                       const void *oldfile = mappedfile;
+                       ptrdiff_t diff;
+
+                       /*
+                        * Unmap the file, grow it, and remap it.
+                        */
+                       munmap(mappedfile, totalsize);
+
+                       totalsize += delta;
+                       totalsize += totalsize / 10;
+
+                       if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
+                           perror(PNAME ": lseek");
+                           return 1;
+                       }
+                       if (write(fd, "\0", 1) < 1) {
+                           perror(PNAME ": write");
+                           return 1;
+                       }
+
+                       mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
+                       if (!mappedfile) {
+                           perror(PNAME ": mmap");
+                           return 1;
+                       }
+
+                       indexbuild_rebase(ib, mappedfile);
+                       triewalk_rebase(tw, mappedfile);
+                       diff = (const unsigned char *)mappedfile -
+                           (const unsigned char *)oldfile;
+                       if (prevtf)
+                           prevtf = (const struct trie_file *)
+                               (((const unsigned char *)prevtf) + diff);
+                       if (tf)
+                           tf = (const struct trie_file *)
+                               (((const unsigned char *)tf) + diff);
+                   }
+
                    /*
                     * Get the next file from the index. So we are
                     * currently holding, and have not yet
@@ -1136,12 +1214,6 @@ int main(int argc, char **argv)
                        indexbuild_tag(ib);
                        break;
                    }
-                       
-                   /*
-                    * In full-index mode, index everything.
-                    */
-                   if (fullindex)
-                       indexbuild_tag(ib);
 
                    /*
                     * If prevbuf was a filename inside some
@@ -1162,7 +1234,7 @@ int main(int argc, char **argv)
                munmap(mappedfile, totalsize);
                ftruncate(fd, realsize);
                close(fd);
-               printf("Actual index file size = %llu bytes\n",
+               printf("Final index file size = %llu bytes\n",
                       (unsigned long long)realsize);
            }
        } else if (mode == TEXT) {
@@ -1194,12 +1266,28 @@ int main(int argc, char **argv)
            if (pathlen > 0 && querydir[pathlen-1] == pathsep)
                querydir[--pathlen] = '\0';
 
-           text_query(mappedfile, querydir, textcutoff, tqdepth);
+           if (!gotdepth)
+               depth = 1;             /* default for text mode */
+           if (outfile != NULL) {
+               FILE *fp = fopen(outfile, "w");
+               if (!fp) {
+                   fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+                           outfile, strerror(errno));
+                   return 1;
+               }
+               text_query(mappedfile, querydir, textcutoff, showfiles,
+                          depth, fp);
+               fclose(fp);
+           } else {
+               text_query(mappedfile, querydir, textcutoff, showfiles,
+                          depth, stdout);
+           }
 
            munmap(mappedfile, totalsize);
        } else if (mode == HTML) {
            char *querydir = actions[action].arg;
-           size_t pathlen;
+           size_t pathlen, maxpathlen;
+           char *pathbuf;
            struct html_config cfg;
            unsigned long xi;
            char *html;
@@ -1222,6 +1310,9 @@ int main(int argc, char **argv)
            }
            pathsep = trie_pathsep(mappedfile);
 
+           maxpathlen = trie_maxpathlen(mappedfile);
+           pathbuf = snewn(maxpathlen, char);
+
            /*
             * Trim trailing slash, just in case.
             */
@@ -1230,14 +1321,75 @@ int main(int argc, char **argv)
                querydir[--pathlen] = '\0';
 
            xi = trie_before(mappedfile, querydir);
-           cfg.format = NULL;
-           cfg.autoage = htmlautoagerange;
-           cfg.oldest = htmloldest;
-           cfg.newest = htmlnewest;
-           html = html_query(mappedfile, xi, &cfg);
-           fputs(html, stdout);
+           if (xi >= trie_count(mappedfile) ||
+               (trie_getpath(mappedfile, xi, pathbuf),
+                strcmp(pathbuf, querydir))) {
+               fprintf(stderr, "%s: pathname '%s' does not exist in index\n"
+                       "%*s(check it is spelled exactly as it is in the "
+                       "index, including\n%*sany leading './')\n",
+                       PNAME, querydir,
+                       (int)(1+sizeof(PNAME)), "",
+                       (int)(1+sizeof(PNAME)), "");
+           } else if (!index_has_root(mappedfile, xi)) {
+               fprintf(stderr, "%s: pathname '%s' is"
+                       " a file, not a directory\n", PNAME, querydir);
+           } else if (!gotdepth) {
+               /*
+                * Single output file.
+                */
+               cfg.format = NULL;
+               cfg.rootpage = NULL;
+               cfg.autoage = htmlautoagerange;
+               cfg.oldest = htmloldest;
+               cfg.newest = htmlnewest;
+               cfg.showfiles = showfiles;
+               html = html_query(mappedfile, xi, &cfg, 0);
+               if (outfile != NULL) {
+                   FILE *fp = fopen(outfile, "w");
+                   if (!fp) {
+                       fprintf(stderr, "%s: %s: open: %s\n", PNAME,
+                               outfile, strerror(errno));
+                       return 1;
+                   } else if (fputs(html, fp) < 0) {
+                       fprintf(stderr, "%s: %s: write: %s\n", PNAME,
+                               outfile, strerror(errno));
+                       fclose(fp);
+                       return 1;
+                   } else if (fclose(fp) < 0) {
+                       fprintf(stderr, "%s: %s: fclose: %s\n", PNAME,
+                               outfile, strerror(errno));
+                       return 1;
+                   }
+               } else {
+                   fputs(html, stdout);
+               }
+           } else {
+               /*
+                * Multiple output files.
+                */
+               int dirlen = outfile ? 2+strlen(outfile) : 3;
+               char prefix[dirlen];
+               if (outfile)
+                   snprintf(prefix, dirlen, "%s/", outfile);
+               else
+                   snprintf(prefix, dirlen, "./");
+
+               unsigned long xi2;
+               make_successor(pathbuf);
+               xi2 = trie_before(mappedfile, pathbuf);
+
+               cfg.format = "%lu.html";
+               cfg.rootpage = "index.html";
+               cfg.autoage = htmlautoagerange;
+               cfg.oldest = htmloldest;
+               cfg.newest = htmlnewest;
+               cfg.showfiles = showfiles;
+               if (html_dump(mappedfile, xi, xi2, depth, &cfg, prefix))
+                   return 1;
+           }
 
            munmap(mappedfile, totalsize);
+           sfree(pathbuf);
        } else if (mode == DUMP) {
            size_t maxpathlen;
            char *buf;
@@ -1296,9 +1448,11 @@ int main(int argc, char **argv)
            dcfg.port = httpserverport;
            dcfg.basicauthdata = httpauthdata;
            pcfg.format = NULL;
+           pcfg.rootpage = NULL;
            pcfg.autoage = htmlautoagerange;
            pcfg.oldest = htmloldest;
            pcfg.newest = htmlnewest;
+           pcfg.showfiles = showfiles;
            run_httpd(mappedfile, auth, &dcfg, &pcfg);
            munmap(mappedfile, totalsize);
        } else if (mode == REMOVE) {