X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/agedu/blobdiff_plain/645dbd491c48a1558fe16b99c3e4410459dd1852..HEAD:/agedu.c diff --git a/agedu.c b/agedu.c index 5b836e7..1847e2c 100644 --- 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); } /* @@ -288,6 +309,8 @@ static void text_query(const void *mappedfile, const char *querydir, 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(CGI) LONG(cgi) \ + HELPOPT("do the right thing when run from a CGI script") \ HELPPFX("options") \ VAL(DATAFILE) SHORT(f) LONG(file) \ HELPARG("filename") HELPOPT("[most modes] specify index file") \ @@ -318,10 +341,16 @@ static void text_query(const void *mappedfile, const char *querydir, 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") \ + NOVAL(NOEOF) LONG(no_eof) LONG(noeof) \ + HELPOPT("[--web] do not close web server on EOF") \ NOVAL(MTIME) LONG(mtime) \ HELPOPT("[--scan] use mtime instead of atime") \ + 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") \ @@ -332,8 +361,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(HTMLTITLE) LONG(title) \ + HELPARG("title") HELPOPT("[--web,--html] title prefix for web pages") \ + 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") \ @@ -366,8 +397,8 @@ enum { OPTIONS(IGNORE,IGNORE,IGNORE,LONGTMP) NLONGOPTS }; static const int opthasval[NOPTIONS] = {OPTIONS(ZERO,ONE,IGNORE,IGNORE)}; static const char shortopts[] = {OPTIONS(IGNORE,IGNORE,STRINGNOCOMMA,IGNORE)}; static const char *const longopts[] = {OPTIONS(IGNORE,IGNORE,IGNORE,STRING)}; -enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) }; -enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) }; +enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) UNUSEDENUMVAL1 }; +enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) UNUSEDENUMVAL2 }; static const int shortvals[] = {OPTIONS(IGNORE,IGNORE,SHORTOPTVAL,IGNORE)}; static const int longvals[] = {OPTIONS(IGNORE,IGNORE,IGNORE,LONGOPTVAL)}; @@ -471,17 +502,21 @@ int main(int argc, char **argv) time_t now = time(NULL); time_t textcutoff = now, htmlnewest = now, htmloldest = now; int htmlautoagerange = 1; - const char *httpserveraddr = NULL; - int httpserverport = 0; + const char *httpserveraddr = "localhost"; + const char *httpserverport = NULL; const char *httpauthdata = NULL; + const char *outfile = NULL; + const char *html_title = PNAME; 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 closeoneof = 1; + int showfiles = 0; #ifdef DEBUG_MAD_OPTION_PARSING_MACROS { @@ -635,8 +670,6 @@ int main(int argc, char **argv) for (i = 0; licence[i]; i++) fputs(licence[i], stdout); - - return 0; } return 0; case OPT_SCAN: @@ -685,12 +718,14 @@ int main(int argc, char **argv) nactions++; break; case OPT_HTML: + case OPT_CGI: if (nactions >= actionsize) { actionsize = nactions * 3 / 2 + 16; actions = sresize(actions, actionsize, struct action); } actions[nactions].mode = HTML; - actions[nactions].arg = optval; + actions[nactions].arg = (optid == OPT_HTML ? optval : + NULL); nactions++; break; case OPT_HTTPD: @@ -732,15 +767,36 @@ 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_NOEOF: + closeoneof = 0; + 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_HTMLTITLE: + html_title = optval; + break; case OPT_MINAGE: textcutoff = parse_age(now, optval); break; @@ -765,10 +821,13 @@ int main(int argc, char **argv) else port = optval; port += strcspn(port, ":"); - if (port) + if (port && *port) *port++ = '\0'; - httpserveraddr = optval; - httpserverport = atoi(port); + if (!strcmp(optval, "ANY")) + httpserveraddr = NULL; + else + httpserveraddr = optval; + httpserverport = port; } break; case OPT_AUTH: @@ -949,7 +1008,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; @@ -1014,9 +1075,10 @@ int main(int argc, char **argv) } p++; } + } else { + p++; } *q++ = c; - p++; } *q = '\0'; triebuild_add(ctx->tb, buf, &tf); @@ -1118,9 +1180,6 @@ int main(int argc, char **argv) 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); @@ -1187,7 +1246,8 @@ int main(int argc, char **argv) indexbuild_free(ib); munmap(mappedfile, totalsize); - ftruncate(fd, realsize); + if (ftruncate(fd, realsize) < 0) + fatal("%s: truncate: %s\n", filename, strerror(errno)); close(fd); printf("Final index file size = %llu bytes\n", (unsigned long long)realsize); @@ -1212,6 +1272,11 @@ int main(int argc, char **argv) perror(PNAME ": mmap"); return 1; } + if (!trie_check_magic(mappedfile)) { + fprintf(stderr, "%s: %s: magic numbers did not match\n" + "%s: check that the index was built by this version of agedu on this platform\n", PNAME, filename, PNAME); + return 1; + } pathsep = trie_pathsep(mappedfile); /* @@ -1221,12 +1286,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; @@ -1235,36 +1316,247 @@ int main(int argc, char **argv) if (fd < 0) { fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename, strerror(errno)); + if (!querydir) { + printf("Status: 500\nContent-type: text/html\n\n" + "" + "500 Internal Server Error" + "" + "

500 Internal Server Error

" + "

agedu suffered an internal error." + "\n"); + return 0; + } return 1; } if (fstat(fd, &st) < 0) { - perror(PNAME ": fstat"); + fprintf(stderr, "%s: %s: fstat: %s\n", PNAME, filename, + strerror(errno)); + if (!querydir) { + printf("Status: 500\nContent-type: text/html\n\n" + "" + "500 Internal Server Error" + "" + "

500 Internal Server Error

" + "

agedu suffered an internal error." + "\n"); + return 0; + } return 1; } totalsize = st.st_size; mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0); if (!mappedfile) { - perror(PNAME ": mmap"); + fprintf(stderr, "%s: %s: mmap: %s\n", PNAME, filename, + strerror(errno)); + if (!querydir) { + printf("Status: 500\nContent-type: text/html\n\n" + "" + "500 Internal Server Error" + "" + "

500 Internal Server Error

" + "

agedu suffered an internal error." + "\n"); + return 0; + } + return 1; + } + if (!trie_check_magic(mappedfile)) { + fprintf(stderr, "%s: %s: magic numbers did not match\n" + "%s: check that the index was built by this version of agedu on this platform\n", PNAME, filename, PNAME); + if (!querydir) { + printf("Status: 500\nContent-type: text/html\n\n" + "" + "500 Internal Server Error" + "" + "

500 Internal Server Error

" + "

agedu suffered an internal error." + "\n"); + return 0; + } return 1; } pathsep = trie_pathsep(mappedfile); - /* - * Trim trailing slash, just in case. - */ - pathlen = strlen(querydir); - if (pathlen > 0 && querydir[pathlen-1] == pathsep) - querydir[--pathlen] = '\0'; + maxpathlen = trie_maxpathlen(mappedfile); + pathbuf = snewn(maxpathlen, char); - 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 (!querydir || !gotdepth) { + /* + * Single output file. + */ + if (!querydir) { + cfg.uriformat = "/%|/%p/%|%|/%p"; + } else { + cfg.uriformat = NULL; + } + cfg.autoage = htmlautoagerange; + cfg.oldest = htmloldest; + cfg.newest = htmlnewest; + cfg.showfiles = showfiles; + } else { + cfg.uriformat = "/index.html%|/%/p.html"; + cfg.fileformat = "/index.html%|/%/p.html"; + cfg.autoage = htmlautoagerange; + cfg.oldest = htmloldest; + cfg.newest = htmlnewest; + cfg.showfiles = showfiles; + } + cfg.html_title = html_title; + + if (!querydir) { + /* + * If we're run in --cgi mode, read PATH_INFO to get + * a numeric pathname index. + */ + char *path_info = getenv("PATH_INFO"); + + if (!path_info) + path_info = ""; + + /* + * Parse the path. + */ + if (!html_parse_path(mappedfile, path_info, &cfg, &xi)) { + printf("Status: 404\nContent-type: text/html\n\n" + "" + "404 Not Found" + "" + "

400 Not Found

" + "

Invalid agedu pathname." + "\n"); + return 0; + } + + /* + * If the path was parseable but not canonically + * expressed, return a redirect to the canonical + * version. + */ + char *canonpath = html_format_path(mappedfile, &cfg, xi); + if (strcmp(canonpath, path_info)) { + char *servername = getenv("SERVER_NAME"); + char *scriptname = getenv("SCRIPT_NAME"); + if (!servername || !scriptname) { + if (servername) + fprintf(stderr, "%s: SCRIPT_NAME unset\n", PNAME); + else if (scriptname) + fprintf(stderr, "%s: SCRIPT_NAME unset\n", PNAME); + else + fprintf(stderr, "%s: SERVER_NAME and " + "SCRIPT_NAME both unset\n", PNAME); + printf("Status: 500\nContent-type: text/html\n\n" + "" + "500 Internal Server Error" + "" + "

500 Internal Server Error

" + "

agedu suffered an internal " + "error." + "\n"); + return 0; + } + printf("Status: 301\n" + "Location: http://%s/%s%s\n" + "Content-type: text/html\n\n" + "" + "301 Moved" + "" + "

301 Moved

" + "

Moved." + "\n", + servername, scriptname, canonpath); + return 0; + } + + } else { + /* + * In ordinary --html mode, process a query + * directory passed in on the command line. + */ + + /* + * Trim trailing slash, just in case. + */ + pathlen = strlen(querydir); + if (pathlen > 0 && querydir[pathlen-1] == pathsep) + querydir[--pathlen] = '\0'; + + xi = trie_before(mappedfile, querydir); + 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)), ""); + return 1; + } else if (!index_has_root(mappedfile, xi)) { + fprintf(stderr, "%s: pathname '%s' is" + " a file, not a directory\n", PNAME, querydir); + return 1; + } + } + + if (!querydir || !gotdepth) { + /* + * Single output file. + */ + html = html_query(mappedfile, xi, &cfg, 1); + if (querydir && 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 { + if (!querydir) { + printf("Content-type: text/html\n\n"); + } + fputs(html, stdout); + } + } else { + /* + * Multiple output files. + */ + int dirlen = outfile ? 2+strlen(outfile) : 3; + char prefix[dirlen]; + if (outfile) { + if (mkdir(outfile, 0777) < 0 && errno != EEXIST) { + fprintf(stderr, "%s: %s: mkdir: %s\n", PNAME, + outfile, strerror(errno)); + return 1; + } + snprintf(prefix, dirlen, "%s/", outfile); + } else + snprintf(prefix, dirlen, "./"); + + unsigned long xi2; + /* + * pathbuf is only set up in the plain-HTML case and + * not in the CGI case; but that's OK, because the + * CGI case can't come to this branch of the if + * anyway. + */ + make_successor(pathbuf); + xi2 = trie_before(mappedfile, pathbuf); + + 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; @@ -1285,6 +1577,11 @@ int main(int argc, char **argv) perror(PNAME ": mmap"); return 1; } + if (!trie_check_magic(mappedfile)) { + fprintf(stderr, "%s: %s: magic numbers did not match\n" + "%s: check that the index was built by this version of agedu on this platform\n", PNAME, filename, PNAME); + return 1; + } pathsep = trie_pathsep(mappedfile); maxpathlen = trie_maxpathlen(mappedfile); @@ -1317,15 +1614,23 @@ int main(int argc, char **argv) perror(PNAME ": mmap"); return 1; } + if (!trie_check_magic(mappedfile)) { + fprintf(stderr, "%s: %s: magic numbers did not match\n" + "%s: check that the index was built by this version of agedu on this platform\n", PNAME, filename, PNAME); + return 1; + } pathsep = trie_pathsep(mappedfile); dcfg.address = httpserveraddr; dcfg.port = httpserverport; + dcfg.closeoneof = closeoneof; dcfg.basicauthdata = httpauthdata; - pcfg.format = NULL; + pcfg.uriformat = "/%|/%p/%|%|/%p"; pcfg.autoage = htmlautoagerange; pcfg.oldest = htmloldest; pcfg.newest = htmlnewest; + pcfg.showfiles = showfiles; + pcfg.html_title = html_title; run_httpd(mappedfile, auth, &dcfg, &pcfg); munmap(mappedfile, totalsize); } else if (mode == REMOVE) {