d72c73dc9d08fe5f548486f3d60e84797a8b9af1
[sgt/agedu] / agedu.c
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>
13 #include <assert.h>
14
15 #include <unistd.h>
16 #include <sys/types.h>
17 #include <fcntl.h>
18 #include <sys/mman.h>
19 #include <termios.h>
20 #include <sys/ioctl.h>
21 #include <fnmatch.h>
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
32 #define lenof(x) (sizeof((x))/sizeof(*(x)))
33
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 */
42 char pathsep = '/';
43
44 void 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
55 struct inclusion_exclusion {
56 int type;
57 const char *wildcard;
58 int path;
59 };
60
61 struct ctx {
62 triebuild *tb;
63 dev_t datafile_dev, filesystem_dev;
64 ino_t datafile_ino;
65 time_t last_output_update;
66 int progress, progwidth;
67 struct inclusion_exclusion *inex;
68 int ninex;
69 int crossfs;
70 };
71
72 static 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;
77 int i, include;
78 const char *filename;
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.
88 */
89 if (!ctx->crossfs && st->st_dev != ctx->filesystem_dev)
90 return 0;
91
92 file.blocks = st->st_blocks;
93 file.atime = st->st_atime;
94
95 /*
96 * Filter based on wildcards.
97 */
98 include = 1;
99 filename = strrchr(pathname, pathsep);
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,
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;
123 }
124
125 triebuild_add(ctx->tb, pathname, &file);
126
127 t = time(NULL);
128 if (t != ctx->last_output_update) {
129 if (ctx->progress) {
130 fprintf(stderr, "%-*.*s\r", ctx->progwidth, ctx->progwidth,
131 pathname);
132 fflush(stderr);
133 }
134 ctx->last_output_update = t;
135 }
136
137 return 1;
138 }
139
140 static void text_query(const void *mappedfile, const char *querydir,
141 time_t t, int depth)
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 */
156 sprintf(pathbuf, "%s\001", querydir);
157 xi1 = trie_before(mappedfile, querydir);
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
166 if (s1 == s2)
167 return; /* no space taken up => no display */
168
169 /* Display in units of 2 512-byte blocks = 1Kb */
170 printf("%-11llu %s\n", (s2 - s1) / 2, querydir);
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);
180 text_query(mappedfile, pathbuf, t, depth-1);
181 strcat(pathbuf, "\001");
182 xi1 = trie_before(mappedfile, pathbuf);
183 }
184 }
185 }
186
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
206 * whether the option in question takes a value
207 * - define a function which prints out brief online help for all
208 * the options.
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
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.
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
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") \
254 NOVAL(HTTPD) SHORT(w) LONG(web) LONG(server) LONG(httpd) \
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") \
259 NOVAL(PROGRESS) LONG(progress) LONG(scan_progress) \
260 HELPOPT("[--scan] report progress on stderr") \
261 NOVAL(NOPROGRESS) LONG(no_progress) LONG(no_scan_progress) \
262 HELPOPT("[--scan] do not report progress") \
263 NOVAL(TTYPROGRESS) LONG(tty_progress) LONG(tty_scan_progress) \
264 LONG(progress_tty) LONG(scan_progress_tty) \
265 HELPOPT("[--scan] report progress if stderr is a tty") \
266 NOVAL(CROSSFS) LONG(cross_fs) \
267 HELPOPT("[--scan] cross filesystem boundaries") \
268 NOVAL(NOCROSSFS) LONG(no_cross_fs) \
269 HELPOPT("[--scan] stick to one filesystem") \
270 VAL(INCLUDE) LONG(include) \
271 HELPARG("wildcard") HELPOPT("[--scan] include files matching pattern") \
272 VAL(INCLUDEPATH) LONG(include_path) \
273 HELPARG("wildcard") HELPOPT("[--scan] include pathnames matching pattern") \
274 VAL(EXCLUDE) LONG(exclude) \
275 HELPARG("wildcard") HELPOPT("[--scan] exclude files matching pattern") \
276 VAL(EXCLUDEPATH) LONG(exclude_path) \
277 HELPARG("wildcard") HELPOPT("[--scan] exclude pathnames matching pattern") \
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") \
282 VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
283 HELPARG("age") HELPOPT("[--text] include only files older than this") \
284 VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
285 HELPARG("age[-age]") HELPOPT("[--html,--web] set limits of colour coding") \
286 VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
287 LONG(server_addr) \
288 HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
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") \
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") \
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") \
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
317 #define OPTIONS(NOVAL, VAL, SHORT, LONG) \
318 OPTHELP(NOVAL, VAL, SHORT, LONG, IGNORE, IGNORE, IGNORE, IGNORE)
319
320 enum { OPTIONS(DEFENUM,DEFENUM,IGNORE,IGNORE) NOPTIONS };
321 enum { OPTIONS(IGNORE,IGNORE,SHORTTMP,IGNORE) NSHORTOPTS };
322 enum { OPTIONS(IGNORE,IGNORE,IGNORE,LONGTMP) NLONGOPTS };
323 static const int opthasval[NOPTIONS] = {OPTIONS(ZERO,ONE,IGNORE,IGNORE)};
324 static const char shortopts[] = {OPTIONS(IGNORE,IGNORE,STRINGNOCOMMA,IGNORE)};
325 static const char *const longopts[] = {OPTIONS(IGNORE,IGNORE,IGNORE,STRING)};
326 enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) };
327 enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) };
328 static const int shortvals[] = {OPTIONS(IGNORE,IGNORE,SHORTOPTVAL,IGNORE)};
329 static const int longvals[] = {OPTIONS(IGNORE,IGNORE,IGNORE,LONGOPTVAL)};
330
331 static 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
371 static 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
410 int 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";
421 char *scandir = NULL;
422 char *querydir = NULL;
423 int doing_opts = 1;
424 enum { USAGE, TEXT, HTML, SCAN, DUMP, HTTPD } mode = USAGE;
425 time_t now = time(NULL);
426 time_t textcutoff = now, htmlnewest = now, htmloldest = now;
427 int htmlautoagerange = 1;
428 const char *httpserveraddr = NULL;
429 int httpserverport = 0;
430 const char *httpauthdata = NULL;
431 int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
432 int progress = 1;
433 struct inclusion_exclusion *inex = NULL;
434 int ninex = 0, inexsize = 0;
435 int crossfs = 0;
436
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
452 while (--argc > 0) {
453 char *p = *++argv;
454
455 if (doing_opts && *p == '-') {
456 int wordstart = 1;
457
458 if (!strcmp(p, "--")) {
459 doing_opts = 0;
460 continue;
461 }
462
463 p++;
464 while (*p) {
465 int optid = -1;
466 int i;
467 char *optval;
468
469 if (wordstart && *p == '-') {
470 /*
471 * GNU-style long option.
472 */
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;
503 }
504 }
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);
516 return 1;
517 }
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 }
527 }
528 }
529
530 p += strlen(p); /* finished with this argument word */
531 } else {
532 /*
533 * Short option.
534 */
535 char c = *p++;
536
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]) {
550 if (*p) {
551 optval = p;
552 p += strlen(p);
553 } else if (--argc > 0) {
554 optval = *++argv;
555 } else {
556 fprintf(stderr, "%s: option '-%c' expects"
557 " an argument\n", PNAME, c);
558 return 1;
559 }
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:
572 usage(stdout);
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;
582 scandir = optval;
583 break;
584 case OPT_DUMP:
585 mode = DUMP;
586 break;
587 case OPT_TEXT:
588 querydir = optval;
589 mode = TEXT;
590 break;
591 case OPT_HTML:
592 mode = HTML;
593 querydir = optval;
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:
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 }
630 break;
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;
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;
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 {
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;
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;
721 case OPT_INCLUDE:
722 case OPT_INCLUDEPATH:
723 case OPT_EXCLUDE:
724 case OPT_EXCLUDEPATH:
725 case OPT_PRUNE:
726 case OPT_PRUNEPATH:
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 ||
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);
741 inex[ninex].wildcard = optval;
742 ninex++;
743 break;
744 }
745 }
746 } else {
747 fprintf(stderr, "%s: unexpected argument '%s'\n", PNAME, p);
748 return 1;
749 }
750 }
751
752 if (mode == USAGE) {
753 usage(stderr);
754 return 1;
755 } else if (mode == SCAN) {
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
764 if (stat(scandir, &st) < 0) {
765 fprintf(stderr, "%s: %s: stat: %s\n", PNAME, scandir,
766 strerror(errno));
767 return 1;
768 }
769 ctx->filesystem_dev = crossfs ? 0 : st.st_dev;
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;
777 ctx->inex = inex;
778 ctx->ninex = ninex;
779 ctx->crossfs = crossfs;
780
781 ctx->last_output_update = time(NULL);
782
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
795 /*
796 * Scan the directory tree, and write out the trie component
797 * of the data file.
798 */
799 ctx->tb = triebuild_new(fd);
800 du(scandir, gotdata, ctx);
801 count = triebuild_finish(ctx->tb);
802 triebuild_free(ctx->tb);
803
804 if (ctx->progress) {
805 fprintf(stderr, "%-*s\r", ctx->progwidth, "");
806 fflush(stderr);
807 }
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);
853 } else if (mode == TEXT) {
854 size_t pathlen;
855
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 }
872
873 /*
874 * Trim trailing slash, just in case.
875 */
876 pathlen = strlen(querydir);
877 if (pathlen > 0 && querydir[pathlen-1] == pathsep)
878 querydir[--pathlen] = '\0';
879
880 text_query(mappedfile, querydir, textcutoff, 1);
881 } else if (mode == HTML) {
882 size_t pathlen;
883 struct html_config cfg;
884 unsigned long xi;
885 char *html;
886
887 fd = open(filename, O_RDONLY);
888 if (fd < 0) {
889 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
890 strerror(errno));
891 return 1;
892 }
893 if (fstat(fd, &st) < 0) {
894 perror("agedu: fstat");
895 return 1;
896 }
897 totalsize = st.st_size;
898 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
899 if (!mappedfile) {
900 perror("agedu: mmap");
901 return 1;
902 }
903
904 /*
905 * Trim trailing slash, just in case.
906 */
907 pathlen = strlen(querydir);
908 if (pathlen > 0 && querydir[pathlen-1] == pathsep)
909 querydir[--pathlen] = '\0';
910
911 xi = trie_before(mappedfile, querydir);
912 cfg.format = NULL;
913 cfg.autoage = htmlautoagerange;
914 cfg.oldest = htmloldest;
915 cfg.newest = htmlnewest;
916 html = html_query(mappedfile, xi, &cfg);
917 fputs(html, stdout);
918 } else if (mode == DUMP) {
919 size_t maxpathlen;
920 char *buf;
921
922 fd = open(filename, O_RDONLY);
923 if (fd < 0) {
924 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
925 strerror(errno));
926 return 1;
927 }
928 if (fstat(fd, &st) < 0) {
929 perror("agedu: fstat");
930 return 1;
931 }
932 totalsize = st.st_size;
933 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
934 if (!mappedfile) {
935 perror("agedu: mmap");
936 return 1;
937 }
938
939 maxpathlen = trie_maxpathlen(mappedfile);
940 buf = snewn(maxpathlen, char);
941
942 tw = triewalk_new(mappedfile);
943 while ((tf = triewalk_next(tw, buf)) != NULL) {
944 printf("%s: %llu %llu\n", buf, tf->blocks, tf->atime);
945 }
946 triewalk_free(tw);
947 } else if (mode == HTTPD) {
948 struct html_config pcfg;
949 struct httpd_config dcfg;
950
951 fd = open(filename, O_RDONLY);
952 if (fd < 0) {
953 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
954 strerror(errno));
955 return 1;
956 }
957 if (fstat(fd, &st) < 0) {
958 perror("agedu: fstat");
959 return 1;
960 }
961 totalsize = st.st_size;
962 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
963 if (!mappedfile) {
964 perror("agedu: mmap");
965 return 1;
966 }
967
968 dcfg.address = httpserveraddr;
969 dcfg.port = httpserverport;
970 dcfg.basicauthdata = httpauthdata;
971 pcfg.format = NULL;
972 pcfg.autoage = htmlautoagerange;
973 pcfg.oldest = htmloldest;
974 pcfg.newest = htmlnewest;
975 run_httpd(mappedfile, auth, &dcfg, &pcfg);
976 }
977
978 return 0;
979 }