Change the index file format to explicitly indicate the appropriate
[sgt/agedu] / agedu.c
CommitLineData
70322ae3 1/*
2 * Main program for agedu.
3 */
4
5#define _GNU_SOURCE
6#include <stdio.h>
7#include <errno.h>
8#include <stdarg.h>
9#include <stdlib.h>
10#include <stdint.h>
11#include <string.h>
12#include <time.h>
e9e7a1bf 13#include <assert.h>
70322ae3 14
15#include <unistd.h>
16#include <sys/types.h>
17#include <fcntl.h>
18#include <sys/mman.h>
8b1f55d6 19#include <termios.h>
20#include <sys/ioctl.h>
9d0b9596 21#include <fnmatch.h>
70322ae3 22
23#include "du.h"
24#include "trie.h"
25#include "index.h"
26#include "malloc.h"
27#include "html.h"
28#include "httpd.h"
29
30#define PNAME "agedu"
31
56fa1896 32#define lenof(x) (sizeof((x))/sizeof(*(x)))
33
373a02e5 34/*
35 * Path separator. This global variable affects the behaviour of
36 * various parts of the code when they need to deal with path
37 * separators. The path separator appropriate to a particular data
38 * set is encoded in the index file storing that data set; data
39 * sets generated on Unix will of course have the default '/', but
40 * foreign data sets are conceivable and must be handled correctly.
41 */
42char pathsep = '/';
43
70322ae3 44void fatal(const char *fmt, ...)
45{
46 va_list ap;
47 fprintf(stderr, "%s: ", PNAME);
48 va_start(ap, fmt);
49 vfprintf(stderr, fmt, ap);
50 va_end(ap);
51 fprintf(stderr, "\n");
52 exit(1);
53}
54
9d0b9596 55struct inclusion_exclusion {
0ba55302 56 int type;
9d0b9596 57 const char *wildcard;
58 int path;
59};
60
70322ae3 61struct ctx {
62 triebuild *tb;
63 dev_t datafile_dev, filesystem_dev;
64 ino_t datafile_ino;
65 time_t last_output_update;
8b1f55d6 66 int progress, progwidth;
9d0b9596 67 struct inclusion_exclusion *inex;
68 int ninex;
69 int crossfs;
70322ae3 70};
71
72static int gotdata(void *vctx, const char *pathname, const struct stat64 *st)
73{
74 struct ctx *ctx = (struct ctx *)vctx;
75 struct trie_file file;
76 time_t t;
9d0b9596 77 int i, include;
78 const char *filename;
70322ae3 79
80 /*
81 * Filter out our own data file.
82 */
83 if (st->st_dev == ctx->datafile_dev && st->st_ino == ctx->datafile_ino)
84 return 0;
85
86 /*
87 * Don't cross the streams^W^Wany file system boundary.
70322ae3 88 */
9d0b9596 89 if (!ctx->crossfs && st->st_dev != ctx->filesystem_dev)
70322ae3 90 return 0;
91
0ba55302 92 file.blocks = st->st_blocks;
93 file.atime = st->st_atime;
94
70322ae3 95 /*
9d0b9596 96 * Filter based on wildcards.
70322ae3 97 */
9d0b9596 98 include = 1;
373a02e5 99 filename = strrchr(pathname, pathsep);
9d0b9596 100 if (!filename)
101 filename = pathname;
102 else
103 filename++;
104 for (i = 0; i < ctx->ninex; i++) {
105 if (fnmatch(ctx->inex[i].wildcard,
0ba55302 106 ctx->inex[i].path ? pathname : filename, 0) == 0)
107 include = ctx->inex[i].type;
108 }
109 if (include == -1)
110 return 0; /* ignore this entry and any subdirs */
111 if (include == 0) {
112 /*
113 * Here we are supposed to be filtering an entry out, but
114 * still recursing into it if it's a directory. However,
115 * we can't actually leave out any directory whose
116 * subdirectories we then look at. So we cheat, in that
117 * case, by setting the size to zero.
118 */
119 if (!S_ISDIR(st->st_mode))
120 return 0; /* just ignore */
121 else
122 file.blocks = 0;
9d0b9596 123 }
70322ae3 124
70322ae3 125 triebuild_add(ctx->tb, pathname, &file);
126
127 t = time(NULL);
128 if (t != ctx->last_output_update) {
8b1f55d6 129 if (ctx->progress) {
130 fprintf(stderr, "%-*.*s\r", ctx->progwidth, ctx->progwidth,
131 pathname);
132 fflush(stderr);
133 }
70322ae3 134 ctx->last_output_update = t;
135 }
136
137 return 1;
138}
139
e9e7a1bf 140static void text_query(const void *mappedfile, const char *querydir,
7cf11b75 141 time_t t, int depth)
70322ae3 142{
143 size_t maxpathlen;
144 char *pathbuf;
145 unsigned long xi1, xi2;
146 unsigned long long s1, s2;
147
148 maxpathlen = trie_maxpathlen(mappedfile);
149 pathbuf = snewn(maxpathlen + 1, char);
150
151 /*
152 * We want to query everything between the supplied filename
153 * (inclusive) and that filename with a ^A on the end
154 * (exclusive). So find the x indices for each.
155 */
e9e7a1bf 156 sprintf(pathbuf, "%s\001", querydir);
157 xi1 = trie_before(mappedfile, querydir);
70322ae3 158 xi2 = trie_before(mappedfile, pathbuf);
159
160 /*
161 * Now do the lookups in the age index.
162 */
163 s1 = index_query(mappedfile, xi1, t);
164 s2 = index_query(mappedfile, xi2, t);
165
010dd2a2 166 if (s1 == s2)
167 return; /* no space taken up => no display */
168
70322ae3 169 /* Display in units of 2 512-byte blocks = 1Kb */
e9e7a1bf 170 printf("%-11llu %s\n", (s2 - s1) / 2, querydir);
70322ae3 171
172 if (depth > 0) {
173 /*
174 * Now scan for first-level subdirectories and report
175 * those too.
176 */
177 xi1++;
178 while (xi1 < xi2) {
179 trie_getpath(mappedfile, xi1, pathbuf);
7cf11b75 180 text_query(mappedfile, pathbuf, t, depth-1);
70322ae3 181 strcat(pathbuf, "\001");
182 xi1 = trie_before(mappedfile, pathbuf);
183 }
184 }
185}
186
56fa1896 187/*
188 * Largely frivolous way to define all my command-line options. I
189 * present here a parametric macro which declares a series of
190 * _logical_ option identifiers, and for each one declares zero or
191 * more short option characters and zero or more long option
192 * words. Then I repeatedly invoke that macro with its arguments
193 * defined to be various other macros, which allows me to
194 * variously:
195 *
196 * - define an enum allocating a distinct integer value to each
197 * logical option id
198 * - define a string consisting of precisely all the short option
199 * characters
200 * - define a string array consisting of all the long option
201 * strings
202 * - define (with help from auxiliary enums) integer arrays
203 * parallel to both of the above giving the logical option id
204 * for each physical short and long option
205 * - define an array indexed by logical option id indicating
e9e7a1bf 206 * whether the option in question takes a value
207 * - define a function which prints out brief online help for all
208 * the options.
56fa1896 209 *
210 * It's not at all clear to me that this trickery is actually
211 * particularly _efficient_ - it still, after all, requires going
212 * linearly through the option list at run time and doing a
213 * strcmp, whereas in an ideal world I'd have liked the lists of
214 * long and short options to be pre-sorted so that a binary search
215 * or some other more efficient lookup was possible. (Not that
216 * asymptotic algorithmic complexity is remotely vital in option
217 * parsing, but if I were doing this in, say, Lisp or something
218 * with an equivalently powerful preprocessor then once I'd had
219 * the idea of preparing the option-parsing data structures at
220 * compile time I would probably have made the effort to prepare
221 * them _properly_. I could have Perl generate me a source file
222 * from some sort of description, I suppose, but that would seem
223 * like overkill. And in any case, it's more of a challenge to
224 * achieve as much as possible by cunning use of cpp and enum than
225 * to just write some sensible and logical code in a Turing-
226 * complete language. I said it was largely frivolous :-)
227 *
228 * This approach does have the virtue that it brings together the
e9e7a1bf 229 * option ids, option spellings and help text into a single
230 * combined list and defines them all in exactly one place. If I
231 * want to add a new option, or a new spelling for an option, I
232 * only have to modify the main OPTHELP macro below and then add
233 * code to process the new logical id.
56fa1896 234 *
235 * (Though, really, even that isn't ideal, since it still involves
236 * modifying the source file in more than one place. In a
237 * _properly_ ideal world, I'd be able to interleave the option
238 * definitions with the code fragments that process them. And then
239 * not bother defining logical identifiers for them at all - those
240 * would be automatically generated, since I wouldn't have any
241 * need to specify them manually in another part of the code.)
242 */
243
e9e7a1bf 244#define OPTHELP(NOVAL, VAL, SHORT, LONG, HELPPFX, HELPARG, HELPLINE, HELPOPT) \
245 HELPPFX("usage") HELPLINE("agedu [options] action") \
246 HELPPFX("actions") \
247 VAL(SCAN) SHORT(s) LONG(scan) \
248 HELPARG("directory") HELPOPT("scan and index a directory") \
249 NOVAL(DUMP) SHORT(d) LONG(dump) HELPOPT("dump the index file") \
250 VAL(TEXT) SHORT(t) LONG(text) \
251 HELPARG("subdir") HELPOPT("print a plain text report on a subdirectory") \
252 VAL(HTML) SHORT(H) LONG(html) \
253 HELPARG("subdir") HELPOPT("print an HTML report on a subdirectory") \
56fa1896 254 NOVAL(HTTPD) SHORT(w) LONG(web) LONG(server) LONG(httpd) \
e9e7a1bf 255 HELPOPT("serve reports from a temporary web server") \
256 HELPPFX("options") \
257 VAL(DATAFILE) SHORT(f) LONG(file) \
258 HELPARG("filename") HELPOPT("[all modes] specify index file") \
56fa1896 259 NOVAL(PROGRESS) LONG(progress) LONG(scan_progress) \
e9e7a1bf 260 HELPOPT("[--scan] report progress on stderr") \
56fa1896 261 NOVAL(NOPROGRESS) LONG(no_progress) LONG(no_scan_progress) \
e9e7a1bf 262 HELPOPT("[--scan] do not report progress") \
56fa1896 263 NOVAL(TTYPROGRESS) LONG(tty_progress) LONG(tty_scan_progress) \
264 LONG(progress_tty) LONG(scan_progress_tty) \
e9e7a1bf 265 HELPOPT("[--scan] report progress if stderr is a tty") \
56fa1896 266 NOVAL(CROSSFS) LONG(cross_fs) \
e9e7a1bf 267 HELPOPT("[--scan] cross filesystem boundaries") \
56fa1896 268 NOVAL(NOCROSSFS) LONG(no_cross_fs) \
e9e7a1bf 269 HELPOPT("[--scan] stick to one filesystem") \
56fa1896 270 VAL(INCLUDE) LONG(include) \
e9e7a1bf 271 HELPARG("wildcard") HELPOPT("[--scan] include files matching pattern") \
56fa1896 272 VAL(INCLUDEPATH) LONG(include_path) \
e9e7a1bf 273 HELPARG("wildcard") HELPOPT("[--scan] include pathnames matching pattern") \
56fa1896 274 VAL(EXCLUDE) LONG(exclude) \
e9e7a1bf 275 HELPARG("wildcard") HELPOPT("[--scan] exclude files matching pattern") \
276 VAL(EXCLUDEPATH) LONG(exclude_path) \
277 HELPARG("wildcard") HELPOPT("[--scan] exclude pathnames matching pattern") \
0ba55302 278 VAL(PRUNE) LONG(prune) \
279 HELPARG("wildcard") HELPOPT("[--scan] prune files matching pattern") \
280 VAL(PRUNEPATH) LONG(prune_path) \
281 HELPARG("wildcard") HELPOPT("[--scan] prune pathnames matching pattern") \
e9e7a1bf 282 VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
283 HELPARG("age") HELPOPT("[--text] include only files older than this") \
f2e52893 284 VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
285 HELPARG("age[-age]") HELPOPT("[--html,--web] set limits of colour coding") \
1e8d78b9 286 VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
287 LONG(server_addr) \
288 HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
e9e7a1bf 289 VAL(AUTH) LONG(auth) LONG(http_auth) LONG(httpd_auth) \
290 LONG(server_auth) LONG(web_auth) \
291 HELPARG("type") HELPOPT("[--web] specify HTTP authentication method") \
1e8d78b9 292 VAL(AUTHFILE) LONG(auth_file) \
293 HELPARG("filename") HELPOPT("[--web] read HTTP Basic user/pass from file") \
294 VAL(AUTHFD) LONG(auth_fd) \
295 HELPARG("fd") HELPOPT("[--web] read HTTP Basic user/pass from fd") \
e9e7a1bf 296 HELPPFX("also") \
297 NOVAL(HELP) SHORT(h) LONG(help) HELPOPT("display this help text") \
298 NOVAL(VERSION) SHORT(V) LONG(version) HELPOPT("report version number") \
299 NOVAL(LICENCE) LONG(licence) LONG(license) \
300 HELPOPT("display (MIT) licence text") \
56fa1896 301
302#define IGNORE(x)
303#define DEFENUM(x) OPT_ ## x,
304#define ZERO(x) 0,
305#define ONE(x) 1,
306#define STRING(x) #x ,
307#define STRINGNOCOMMA(x) #x
308#define SHORTNEWOPT(x) SHORTtmp_ ## x = OPT_ ## x,
309#define SHORTTHISOPT(x) SHORTtmp2_ ## x, SHORTVAL_ ## x = SHORTtmp2_ ## x - 1,
310#define SHORTOPTVAL(x) SHORTVAL_ ## x,
311#define SHORTTMP(x) SHORTtmp3_ ## x,
312#define LONGNEWOPT(x) LONGtmp_ ## x = OPT_ ## x,
313#define LONGTHISOPT(x) LONGtmp2_ ## x, LONGVAL_ ## x = LONGtmp2_ ## x - 1,
314#define LONGOPTVAL(x) LONGVAL_ ## x,
315#define LONGTMP(x) SHORTtmp3_ ## x,
316
e9e7a1bf 317#define OPTIONS(NOVAL, VAL, SHORT, LONG) \
318 OPTHELP(NOVAL, VAL, SHORT, LONG, IGNORE, IGNORE, IGNORE, IGNORE)
319
56fa1896 320enum { OPTIONS(DEFENUM,DEFENUM,IGNORE,IGNORE) NOPTIONS };
321enum { OPTIONS(IGNORE,IGNORE,SHORTTMP,IGNORE) NSHORTOPTS };
322enum { OPTIONS(IGNORE,IGNORE,IGNORE,LONGTMP) NLONGOPTS };
323static const int opthasval[NOPTIONS] = {OPTIONS(ZERO,ONE,IGNORE,IGNORE)};
324static const char shortopts[] = {OPTIONS(IGNORE,IGNORE,STRINGNOCOMMA,IGNORE)};
325static const char *const longopts[] = {OPTIONS(IGNORE,IGNORE,IGNORE,STRING)};
326enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) };
327enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) };
328static const int shortvals[] = {OPTIONS(IGNORE,IGNORE,SHORTOPTVAL,IGNORE)};
329static const int longvals[] = {OPTIONS(IGNORE,IGNORE,IGNORE,LONGOPTVAL)};
330
e9e7a1bf 331static void usage(FILE *fp)
332{
333 char longbuf[80];
334 const char *prefix, *shortopt, *longopt, *optarg;
335 int i, optex;
336
337#define HELPRESET prefix = shortopt = longopt = optarg = NULL, optex = -1
338#define HELPNOVAL(s) optex = 0;
339#define HELPVAL(s) optex = 1;
340#define HELPSHORT(s) if (!shortopt) shortopt = "-" #s;
341#define HELPLONG(s) if (!longopt) { \
342 strcpy(longbuf, "--" #s); longopt = longbuf; \
343 for (i = 0; longbuf[i]; i++) if (longbuf[i] == '_') longbuf[i] = '-'; }
344#define HELPPFX(s) prefix = s;
345#define HELPARG(s) optarg = s;
346#define HELPLINE(s) assert(optex == -1); \
347 fprintf(fp, "%7s%c %s\n", prefix?prefix:"", prefix?':':' ', s); \
348 HELPRESET;
349#define HELPOPT(s) assert((optex == 1 && optarg) || (optex == 0 && !optarg)); \
350 assert(shortopt || longopt); \
351 i = fprintf(fp, "%7s%c %s%s%s%s%s", prefix?prefix:"", prefix?':':' ', \
352 shortopt?shortopt:"", shortopt&&longopt?", ":"", longopt?longopt:"", \
353 optarg?" ":"", optarg?optarg:""); \
354 fprintf(fp, "%*s %s\n", i<32?32-i:0,"",s); HELPRESET;
355
356 HELPRESET;
357 OPTHELP(HELPNOVAL, HELPVAL, HELPSHORT, HELPLONG,
358 HELPPFX, HELPARG, HELPLINE, HELPOPT);
359
360#undef HELPRESET
361#undef HELPNOVAL
362#undef HELPVAL
363#undef HELPSHORT
364#undef HELPLONG
365#undef HELPPFX
366#undef HELPARG
367#undef HELPLINE
368#undef HELPOPT
369}
370
f2e52893 371static time_t parse_age(time_t now, const char *agestr)
372{
373 time_t t;
374 struct tm tm;
375 int nunits;
376 char unit[2];
377
378 t = now;
379
380 if (2 != sscanf(agestr, "%d%1[DdWwMmYy]", &nunits, unit)) {
381 fprintf(stderr, "%s: age specification should be a number followed by"
382 " one of d,w,m,y\n", PNAME);
383 exit(1);
384 }
385
386 if (unit[0] == 'd') {
387 t -= 86400 * nunits;
388 } else if (unit[0] == 'w') {
389 t -= 86400 * 7 * nunits;
390 } else {
391 int ym;
392
393 tm = *localtime(&t);
394 ym = tm.tm_year * 12 + tm.tm_mon;
395
396 if (unit[0] == 'm')
397 ym -= nunits;
398 else
399 ym -= 12 * nunits;
400
401 tm.tm_year = ym / 12;
402 tm.tm_mon = ym % 12;
403
404 t = mktime(&tm);
405 }
406
407 return t;
408}
409
70322ae3 410int main(int argc, char **argv)
411{
412 int fd, count;
413 struct ctx actx, *ctx = &actx;
414 struct stat st;
415 off_t totalsize, realsize;
416 void *mappedfile;
417 triewalk *tw;
418 indexbuild *ib;
419 const struct trie_file *tf;
420 char *filename = "agedu.dat";
e9e7a1bf 421 char *scandir = NULL;
422 char *querydir = NULL;
70322ae3 423 int doing_opts = 1;
7cf11b75 424 enum { USAGE, TEXT, HTML, SCAN, DUMP, HTTPD } mode = USAGE;
f2e52893 425 time_t now = time(NULL);
426 time_t textcutoff = now, htmlnewest = now, htmloldest = now;
427 int htmlautoagerange = 1;
1e8d78b9 428 const char *httpserveraddr = NULL;
429 int httpserverport = 0;
430 const char *httpauthdata = NULL;
812e4bf2 431 int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
8b1f55d6 432 int progress = 1;
9d0b9596 433 struct inclusion_exclusion *inex = NULL;
434 int ninex = 0, inexsize = 0;
435 int crossfs = 0;
70322ae3 436
56fa1896 437#ifdef DEBUG_MAD_OPTION_PARSING_MACROS
438 {
439 static const char *const optnames[NOPTIONS] = {
440 OPTIONS(STRING,STRING,IGNORE,IGNORE)
441 };
442 int i;
443 for (i = 0; i < NSHORTOPTS; i++)
444 printf("-%c == %s [%s]\n", shortopts[i], optnames[shortvals[i]],
445 opthasval[shortvals[i]] ? "value" : "no value");
446 for (i = 0; i < NLONGOPTS; i++)
447 printf("--%s == %s [%s]\n", longopts[i], optnames[longvals[i]],
448 opthasval[longvals[i]] ? "value" : "no value");
449 }
450#endif
451
70322ae3 452 while (--argc > 0) {
453 char *p = *++argv;
70322ae3 454
455 if (doing_opts && *p == '-') {
56fa1896 456 int wordstart = 1;
457
70322ae3 458 if (!strcmp(p, "--")) {
459 doing_opts = 0;
56fa1896 460 continue;
461 }
462
463 p++;
464 while (*p) {
465 int optid = -1;
466 int i;
467 char *optval;
468
469 if (wordstart && *p == '-') {
70322ae3 470 /*
56fa1896 471 * GNU-style long option.
70322ae3 472 */
56fa1896 473 p++;
474 optval = strchr(p, '=');
475 if (optval)
476 *optval++ = '\0';
477
478 for (i = 0; i < NLONGOPTS; i++) {
479 const char *opt = longopts[i], *s = p;
480 int match = 1;
481 /*
482 * The underscores in the option names
483 * defined above may be given by the user
484 * as underscores or dashes, or omitted
485 * entirely.
486 */
487 while (*opt) {
488 if (*opt == '_') {
489 if (*s == '-' || *s == '_')
490 s++;
491 } else {
492 if (*opt != *s) {
493 match = 0;
494 break;
495 }
496 s++;
497 }
498 opt++;
499 }
500 if (match && !*s) {
501 optid = longvals[i];
502 break;
70322ae3 503 }
504 }
56fa1896 505
506 if (optid < 0) {
507 fprintf(stderr, "%s: unrecognised option '--%s'\n",
508 PNAME, p);
509 return 1;
510 }
511
512 if (!opthasval[optid]) {
513 if (optval) {
514 fprintf(stderr, "%s: unexpected argument to option"
515 " '--%s'\n", PNAME, p);
812e4bf2 516 return 1;
517 }
56fa1896 518 } else {
519 if (!optval) {
520 if (--argc > 0) {
521 optval = *++argv;
522 } else {
523 fprintf(stderr, "%s: option '--%s' expects"
524 " an argument\n", PNAME, p);
525 return 1;
526 }
9d0b9596 527 }
70322ae3 528 }
56fa1896 529
530 p += strlen(p); /* finished with this argument word */
70322ae3 531 } else {
56fa1896 532 /*
533 * Short option.
534 */
70322ae3 535 char c = *p++;
536
56fa1896 537 for (i = 0; i < NSHORTOPTS; i++)
538 if (c == shortopts[i]) {
539 optid = shortvals[i];
540 break;
541 }
542
543 if (optid < 0) {
544 fprintf(stderr, "%s: unrecognised option '-%c'\n",
545 PNAME, c);
546 return 1;
547 }
548
549 if (opthasval[optid]) {
70322ae3 550 if (*p) {
551 optval = p;
552 p += strlen(p);
553 } else if (--argc > 0) {
554 optval = *++argv;
555 } else {
56fa1896 556 fprintf(stderr, "%s: option '-%c' expects"
70322ae3 557 " an argument\n", PNAME, c);
558 return 1;
559 }
56fa1896 560 } else {
561 optval = NULL;
562 }
563 }
564
565 wordstart = 0;
566
567 /*
568 * Now actually process the option.
569 */
570 switch (optid) {
571 case OPT_HELP:
e9e7a1bf 572 usage(stdout);
56fa1896 573 return 0;
574 case OPT_VERSION:
575 printf("FIXME: version();\n");
576 return 0;
577 case OPT_LICENCE:
578 printf("FIXME: licence();\n");
579 return 0;
580 case OPT_SCAN:
581 mode = SCAN;
e9e7a1bf 582 scandir = optval;
56fa1896 583 break;
584 case OPT_DUMP:
585 mode = DUMP;
586 break;
587 case OPT_TEXT:
e9e7a1bf 588 querydir = optval;
56fa1896 589 mode = TEXT;
590 break;
591 case OPT_HTML:
592 mode = HTML;
e9e7a1bf 593 querydir = optval;
56fa1896 594 break;
595 case OPT_HTTPD:
596 mode = HTTPD;
597 break;
598 case OPT_PROGRESS:
599 progress = 2;
600 break;
601 case OPT_NOPROGRESS:
602 progress = 0;
603 break;
604 case OPT_TTYPROGRESS:
605 progress = 1;
606 break;
607 case OPT_CROSSFS:
608 crossfs = 1;
609 break;
610 case OPT_NOCROSSFS:
611 crossfs = 0;
612 break;
613 case OPT_DATAFILE:
614 filename = optval;
615 break;
616 case OPT_MINAGE:
f2e52893 617 textcutoff = parse_age(now, optval);
618 break;
619 case OPT_AGERANGE:
620 if (!strcmp(optval, "auto")) {
621 htmlautoagerange = 1;
622 } else {
623 char *q = optval + strcspn(optval, "-:");
624 if (*q)
625 *q++ = '\0';
626 htmloldest = parse_age(now, optval);
627 htmlnewest = *q ? parse_age(now, q) : now;
628 htmlautoagerange = 0;
629 }
56fa1896 630 break;
1e8d78b9 631 case OPT_SERVERADDR:
632 {
633 char *port;
634 if (optval[0] == '[' &&
635 (port = strchr(optval, ']')) != NULL)
636 port++;
637 else
638 port = optval;
639 port += strcspn(port, ":");
640 if (port)
641 *port++ = '\0';
642 httpserveraddr = optval;
643 httpserverport = atoi(port);
644 }
645 break;
56fa1896 646 case OPT_AUTH:
647 if (!strcmp(optval, "magic"))
648 auth = HTTPD_AUTH_MAGIC;
649 else if (!strcmp(optval, "basic"))
650 auth = HTTPD_AUTH_BASIC;
651 else if (!strcmp(optval, "none"))
652 auth = HTTPD_AUTH_NONE;
653 else if (!strcmp(optval, "default"))
654 auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
f2e52893 655 else if (!strcmp(optval, "help") ||
656 !strcmp(optval, "list")) {
657 printf("agedu: supported HTTP authentication types"
658 " are:\n"
659 " magic use Linux /proc/net/tcp to"
660 " determine owner of peer socket\n"
661 " basic HTTP Basic username and"
662 " password authentication\n"
663 " default use 'magic' if possible, "
664 " otherwise fall back to 'basic'\n"
665 " none unauthenticated HTTP (if"
666 " the data file is non-confidential)\n");
667 return 0;
668 } else {
56fa1896 669 fprintf(stderr, "%s: unrecognised authentication"
670 " type '%s'\n%*s options are 'magic',"
671 " 'basic', 'none', 'default'\n",
672 PNAME, optval, (int)strlen(PNAME), "");
673 return 1;
674 }
675 break;
1e8d78b9 676 case OPT_AUTHFILE:
677 case OPT_AUTHFD:
678 {
679 int fd;
680 char namebuf[40];
681 const char *name;
682 char *authbuf;
683 int authlen, authsize;
684 int ret;
685
686 if (optid == OPT_AUTHFILE) {
687 fd = open(optval, O_RDONLY);
688 if (fd < 0) {
689 fprintf(stderr, "%s: %s: open: %s\n", PNAME,
690 optval, strerror(errno));
691 return 1;
692 }
693 name = optval;
694 } else {
695 fd = atoi(optval);
696 name = namebuf;
697 sprintf(namebuf, "fd %d", fd);
698 }
699
700 authlen = 0;
701 authsize = 256;
702 authbuf = snewn(authsize, char);
703 while ((ret = read(fd, authbuf+authlen,
704 authsize-authlen)) > 0) {
705 authlen += ret;
706 if ((authsize - authlen) < (authsize / 16)) {
707 authsize = authlen * 3 / 2 + 4096;
708 authbuf = sresize(authbuf, authsize, char);
709 }
710 }
711 if (ret < 0) {
712 fprintf(stderr, "%s: %s: read: %s\n", PNAME,
713 name, strerror(errno));
714 return 1;
715 }
716 if (optid == OPT_AUTHFILE)
717 close(fd);
718 httpauthdata = authbuf;
719 }
720 break;
56fa1896 721 case OPT_INCLUDE:
722 case OPT_INCLUDEPATH:
723 case OPT_EXCLUDE:
724 case OPT_EXCLUDEPATH:
0ba55302 725 case OPT_PRUNE:
726 case OPT_PRUNEPATH:
56fa1896 727 if (ninex >= inexsize) {
728 inexsize = ninex * 3 / 2 + 16;
729 inex = sresize(inex, inexsize,
730 struct inclusion_exclusion);
731 }
732 inex[ninex].path = (optid == OPT_INCLUDEPATH ||
0ba55302 733 optid == OPT_EXCLUDEPATH ||
734 optid == OPT_PRUNEPATH);
735 inex[ninex].type = (optid == OPT_INCLUDE ? 1 :
736 optid == OPT_INCLUDEPATH ? 1 :
737 optid == OPT_EXCLUDE ? 0 :
738 optid == OPT_EXCLUDEPATH ? 0 :
739 optid == OPT_PRUNE ? -1 :
740 /* optid == OPT_PRUNEPATH ? */ -1);
56fa1896 741 inex[ninex].wildcard = optval;
742 ninex++;
743 break;
744 }
745 }
70322ae3 746 } else {
e9e7a1bf 747 fprintf(stderr, "%s: unexpected argument '%s'\n", PNAME, p);
748 return 1;
70322ae3 749 }
750 }
751
7cf11b75 752 if (mode == USAGE) {
e9e7a1bf 753 usage(stderr);
754 return 1;
7cf11b75 755 } else if (mode == SCAN) {
70322ae3 756
757 fd = open(filename, O_RDWR | O_TRUNC | O_CREAT, S_IRWXU);
758 if (fd < 0) {
759 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
760 strerror(errno));
761 return 1;
762 }
763
e9e7a1bf 764 if (stat(scandir, &st) < 0) {
765 fprintf(stderr, "%s: %s: stat: %s\n", PNAME, scandir,
70322ae3 766 strerror(errno));
767 return 1;
768 }
9d0b9596 769 ctx->filesystem_dev = crossfs ? 0 : st.st_dev;
70322ae3 770
771 if (fstat(fd, &st) < 0) {
772 perror("agedu: fstat");
773 return 1;
774 }
775 ctx->datafile_dev = st.st_dev;
776 ctx->datafile_ino = st.st_ino;
9d0b9596 777 ctx->inex = inex;
778 ctx->ninex = ninex;
779 ctx->crossfs = crossfs;
70322ae3 780
781 ctx->last_output_update = time(NULL);
782
8b1f55d6 783 /* progress==1 means report progress only if stderr is a tty */
784 if (progress == 1)
785 progress = isatty(2) ? 2 : 0;
786 ctx->progress = progress;
787 {
788 struct winsize ws;
789 if (progress && ioctl(2, TIOCGWINSZ, &ws) == 0)
790 ctx->progwidth = ws.ws_col - 1;
791 else
792 ctx->progwidth = 79;
793 }
794
70322ae3 795 /*
796 * Scan the directory tree, and write out the trie component
797 * of the data file.
798 */
799 ctx->tb = triebuild_new(fd);
e9e7a1bf 800 du(scandir, gotdata, ctx);
70322ae3 801 count = triebuild_finish(ctx->tb);
802 triebuild_free(ctx->tb);
803
8b1f55d6 804 if (ctx->progress) {
805 fprintf(stderr, "%-*s\r", ctx->progwidth, "");
806 fflush(stderr);
807 }
70322ae3 808
809 /*
810 * Work out how much space the cumulative index trees will
811 * take; enlarge the file, and memory-map it.
812 */
813 if (fstat(fd, &st) < 0) {
814 perror("agedu: fstat");
815 return 1;
816 }
817
818 printf("Built pathname index, %d entries, %ju bytes\n", count,
819 (intmax_t)st.st_size);
820
821 totalsize = index_compute_size(st.st_size, count);
822
823 if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
824 perror("agedu: lseek");
825 return 1;
826 }
827 if (write(fd, "\0", 1) < 1) {
828 perror("agedu: write");
829 return 1;
830 }
831
832 printf("Upper bound on index file size = %ju bytes\n",
833 (intmax_t)totalsize);
834
835 mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
836 if (!mappedfile) {
837 perror("agedu: mmap");
838 return 1;
839 }
840
841 ib = indexbuild_new(mappedfile, st.st_size, count);
842 tw = triewalk_new(mappedfile);
843 while ((tf = triewalk_next(tw, NULL)) != NULL)
844 indexbuild_add(ib, tf);
845 triewalk_free(tw);
846 realsize = indexbuild_realsize(ib);
847 indexbuild_free(ib);
848
849 munmap(mappedfile, totalsize);
850 ftruncate(fd, realsize);
851 close(fd);
852 printf("Actual index file size = %ju bytes\n", (intmax_t)realsize);
7cf11b75 853 } else if (mode == TEXT) {
70322ae3 854 size_t pathlen;
855
70322ae3 856 fd = open(filename, O_RDONLY);
857 if (fd < 0) {
858 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
859 strerror(errno));
860 return 1;
861 }
862 if (fstat(fd, &st) < 0) {
863 perror("agedu: fstat");
864 return 1;
865 }
866 totalsize = st.st_size;
867 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
868 if (!mappedfile) {
869 perror("agedu: mmap");
870 return 1;
871 }
269fa2d1 872 pathsep = trie_pathsep(mappedfile);
70322ae3 873
874 /*
875 * Trim trailing slash, just in case.
876 */
e9e7a1bf 877 pathlen = strlen(querydir);
373a02e5 878 if (pathlen > 0 && querydir[pathlen-1] == pathsep)
e9e7a1bf 879 querydir[--pathlen] = '\0';
70322ae3 880
f2e52893 881 text_query(mappedfile, querydir, textcutoff, 1);
70322ae3 882 } else if (mode == HTML) {
883 size_t pathlen;
f2e52893 884 struct html_config cfg;
70322ae3 885 unsigned long xi;
886 char *html;
887
888 fd = open(filename, O_RDONLY);
889 if (fd < 0) {
890 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
891 strerror(errno));
892 return 1;
893 }
894 if (fstat(fd, &st) < 0) {
895 perror("agedu: fstat");
896 return 1;
897 }
898 totalsize = st.st_size;
899 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
900 if (!mappedfile) {
901 perror("agedu: mmap");
902 return 1;
903 }
269fa2d1 904 pathsep = trie_pathsep(mappedfile);
70322ae3 905
906 /*
907 * Trim trailing slash, just in case.
908 */
e9e7a1bf 909 pathlen = strlen(querydir);
373a02e5 910 if (pathlen > 0 && querydir[pathlen-1] == pathsep)
e9e7a1bf 911 querydir[--pathlen] = '\0';
70322ae3 912
e9e7a1bf 913 xi = trie_before(mappedfile, querydir);
f2e52893 914 cfg.format = NULL;
915 cfg.autoage = htmlautoagerange;
916 cfg.oldest = htmloldest;
917 cfg.newest = htmlnewest;
918 html = html_query(mappedfile, xi, &cfg);
70322ae3 919 fputs(html, stdout);
920 } else if (mode == DUMP) {
921 size_t maxpathlen;
922 char *buf;
923
924 fd = open(filename, O_RDONLY);
925 if (fd < 0) {
926 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
927 strerror(errno));
928 return 1;
929 }
930 if (fstat(fd, &st) < 0) {
931 perror("agedu: fstat");
932 return 1;
933 }
934 totalsize = st.st_size;
935 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
936 if (!mappedfile) {
937 perror("agedu: mmap");
938 return 1;
939 }
269fa2d1 940 pathsep = trie_pathsep(mappedfile);
70322ae3 941
942 maxpathlen = trie_maxpathlen(mappedfile);
943 buf = snewn(maxpathlen, char);
944
945 tw = triewalk_new(mappedfile);
946 while ((tf = triewalk_next(tw, buf)) != NULL) {
947 printf("%s: %llu %llu\n", buf, tf->blocks, tf->atime);
948 }
949 triewalk_free(tw);
950 } else if (mode == HTTPD) {
1e8d78b9 951 struct html_config pcfg;
952 struct httpd_config dcfg;
f2e52893 953
70322ae3 954 fd = open(filename, O_RDONLY);
955 if (fd < 0) {
956 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
957 strerror(errno));
958 return 1;
959 }
960 if (fstat(fd, &st) < 0) {
961 perror("agedu: fstat");
962 return 1;
963 }
964 totalsize = st.st_size;
965 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
966 if (!mappedfile) {
967 perror("agedu: mmap");
968 return 1;
969 }
269fa2d1 970 pathsep = trie_pathsep(mappedfile);
70322ae3 971
1e8d78b9 972 dcfg.address = httpserveraddr;
973 dcfg.port = httpserverport;
974 dcfg.basicauthdata = httpauthdata;
975 pcfg.format = NULL;
976 pcfg.autoage = htmlautoagerange;
977 pcfg.oldest = htmloldest;
978 pcfg.newest = htmlnewest;
979 run_httpd(mappedfile, auth, &dcfg, &pcfg);
70322ae3 980 }
981
982 return 0;
983}