X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/agedu/blobdiff_plain/5a29503dccf98dc8f549d682f543f65d7648869e..56cae6e16d22dfba1aafe91dc3465a684d808124:/agedu.c diff --git a/agedu.c b/agedu.c index d015824..533dc4c 100644 --- a/agedu.c +++ b/agedu.c @@ -2,36 +2,16 @@ * Main program for agedu. */ -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include +#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 +49,8 @@ struct ctx { struct inclusion_exclusion *inex; int ninex; int crossfs; + int usemtime; + int fakeatimes; }; static void dump_line(const char *pathname, const struct trie_file *tf) @@ -84,7 +66,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 +87,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->usemtime || (ctx->fakeatimes && S_ISDIR(st->st_mode))) + file.atime = st->st_mtime; + else + file.atime = st->st_atime; /* * Filter based on wildcards. @@ -176,6 +161,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 +246,64 @@ 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") \ + NOVAL(MTIME) LONG(mtime) \ + HELPOPT("[--scan] use mtime instead of atime") \ 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 +314,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 +442,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 +462,8 @@ int main(int argc, char **argv) int ninex = 0, inexsize = 0; int crossfs = 0; int tqdepth = 1; + int fakediratimes = 1; + int mtime = 0; #ifdef DEBUG_MAD_OPTION_PARSING_MACROS { @@ -601,7 +603,12 @@ 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: { @@ -677,6 +684,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; @@ -692,6 +708,15 @@ 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_MTIME: + mtime = 1; + break; case OPT_DATAFILE: filename = optval; break; @@ -739,7 +764,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" @@ -848,7 +873,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); @@ -869,7 +894,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; @@ -893,6 +918,8 @@ int main(int argc, char **argv) ctx->inex = inex; ctx->ninex = ninex; ctx->crossfs = crossfs; + ctx->fakeatimes = fakediratimes; + ctx->usemtime = mtime; ctx->last_output_update = time(NULL); @@ -909,7 +936,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 @@ -954,6 +981,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') @@ -973,6 +1001,7 @@ int main(int argc, char **argv) *q = '\0'; triebuild_add(ctx->tb, buf, &tf); sfree(buf); + line++; } } else { du(scandir, gotdata, ctx); @@ -991,33 +1020,39 @@ 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; } - printf("Built pathname index, %d entries, %ju bytes\n", count, - (intmax_t)st.st_size); + printf("Built pathname index, %d entries, %llu bytes\n", count, + (unsigned long long)st.st_size); 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; } - printf("Upper bound on index file size = %ju bytes\n", - (intmax_t)totalsize); + 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("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) @@ -1029,7 +1064,8 @@ int main(int argc, char **argv) munmap(mappedfile, totalsize); ftruncate(fd, realsize); close(fd); - printf("Actual index file size = %ju bytes\n", (intmax_t)realsize); + printf("Actual index file size = %llu bytes\n", + (unsigned long long)realsize); } } else if (mode == TEXT) { char *querydir = actions[action].arg; @@ -1042,13 +1078,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); @@ -1061,6 +1097,8 @@ int main(int argc, char **argv) querydir[--pathlen] = '\0'; text_query(mappedfile, querydir, textcutoff, tqdepth); + + munmap(mappedfile, totalsize); } else if (mode == HTML) { char *querydir = actions[action].arg; size_t pathlen; @@ -1075,13 +1113,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); @@ -1100,6 +1138,8 @@ int main(int argc, char **argv) cfg.newest = htmlnewest; html = html_query(mappedfile, xi, &cfg); fputs(html, stdout); + + munmap(mappedfile, totalsize); } else if (mode == DUMP) { size_t maxpathlen; char *buf; @@ -1111,13 +1151,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); @@ -1125,11 +1165,13 @@ 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); triewalk_free(tw); + + munmap(mappedfile, totalsize); } else if (mode == HTTPD) { struct html_config pcfg; struct httpd_config dcfg; @@ -1141,13 +1183,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); @@ -1160,6 +1202,13 @@ int main(int argc, char **argv) pcfg.oldest = htmloldest; pcfg.newest = htmlnewest; run_httpd(mappedfile, auth, &dcfg, &pcfg); + munmap(mappedfile, totalsize); + } else if (mode == REMOVE) { + if (remove(filename) < 0) { + fprintf(stderr, "%s: %s: remove: %s\n", PNAME, filename, + strerror(errno)); + return 1; + } } }