Centralise command-line option definitions, using disgusting macros
[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>
13
14#include <unistd.h>
15#include <sys/types.h>
16#include <fcntl.h>
17#include <sys/mman.h>
8b1f55d6 18#include <termios.h>
19#include <sys/ioctl.h>
9d0b9596 20#include <fnmatch.h>
70322ae3 21
22#include "du.h"
23#include "trie.h"
24#include "index.h"
25#include "malloc.h"
26#include "html.h"
27#include "httpd.h"
28
29#define PNAME "agedu"
30
56fa1896 31#define lenof(x) (sizeof((x))/sizeof(*(x)))
32
70322ae3 33void fatal(const char *fmt, ...)
34{
35 va_list ap;
36 fprintf(stderr, "%s: ", PNAME);
37 va_start(ap, fmt);
38 vfprintf(stderr, fmt, ap);
39 va_end(ap);
40 fprintf(stderr, "\n");
41 exit(1);
42}
43
9d0b9596 44struct inclusion_exclusion {
45 int include;
46 const char *wildcard;
47 int path;
48};
49
70322ae3 50struct ctx {
51 triebuild *tb;
52 dev_t datafile_dev, filesystem_dev;
53 ino_t datafile_ino;
54 time_t last_output_update;
8b1f55d6 55 int progress, progwidth;
9d0b9596 56 struct inclusion_exclusion *inex;
57 int ninex;
58 int crossfs;
70322ae3 59};
60
61static int gotdata(void *vctx, const char *pathname, const struct stat64 *st)
62{
63 struct ctx *ctx = (struct ctx *)vctx;
64 struct trie_file file;
65 time_t t;
9d0b9596 66 int i, include;
67 const char *filename;
70322ae3 68
69 /*
70 * Filter out our own data file.
71 */
72 if (st->st_dev == ctx->datafile_dev && st->st_ino == ctx->datafile_ino)
73 return 0;
74
75 /*
76 * Don't cross the streams^W^Wany file system boundary.
70322ae3 77 */
9d0b9596 78 if (!ctx->crossfs && st->st_dev != ctx->filesystem_dev)
70322ae3 79 return 0;
80
81 /*
9d0b9596 82 * Filter based on wildcards.
70322ae3 83 */
9d0b9596 84 include = 1;
85 filename = strrchr(pathname, '/');
86 if (!filename)
87 filename = pathname;
88 else
89 filename++;
90 for (i = 0; i < ctx->ninex; i++) {
91 if (fnmatch(ctx->inex[i].wildcard,
92 ctx->inex[i].path ? pathname : filename,
93 FNM_PATHNAME) == 0)
94 include = ctx->inex[i].include;
95 }
96 if (!include)
97 return 1; /* filter, but don't prune */
70322ae3 98
99 file.blocks = st->st_blocks;
100 file.atime = st->st_atime;
101 triebuild_add(ctx->tb, pathname, &file);
102
103 t = time(NULL);
104 if (t != ctx->last_output_update) {
8b1f55d6 105 if (ctx->progress) {
106 fprintf(stderr, "%-*.*s\r", ctx->progwidth, ctx->progwidth,
107 pathname);
108 fflush(stderr);
109 }
70322ae3 110 ctx->last_output_update = t;
111 }
112
113 return 1;
114}
115
7cf11b75 116static void text_query(const void *mappedfile, const char *rootdir,
117 time_t t, int depth)
70322ae3 118{
119 size_t maxpathlen;
120 char *pathbuf;
121 unsigned long xi1, xi2;
122 unsigned long long s1, s2;
123
124 maxpathlen = trie_maxpathlen(mappedfile);
125 pathbuf = snewn(maxpathlen + 1, char);
126
127 /*
128 * We want to query everything between the supplied filename
129 * (inclusive) and that filename with a ^A on the end
130 * (exclusive). So find the x indices for each.
131 */
132 sprintf(pathbuf, "%s\001", rootdir);
133 xi1 = trie_before(mappedfile, rootdir);
134 xi2 = trie_before(mappedfile, pathbuf);
135
136 /*
137 * Now do the lookups in the age index.
138 */
139 s1 = index_query(mappedfile, xi1, t);
140 s2 = index_query(mappedfile, xi2, t);
141
142 /* Display in units of 2 512-byte blocks = 1Kb */
143 printf("%-11llu %s\n", (s2 - s1) / 2, rootdir);
144
145 if (depth > 0) {
146 /*
147 * Now scan for first-level subdirectories and report
148 * those too.
149 */
150 xi1++;
151 while (xi1 < xi2) {
152 trie_getpath(mappedfile, xi1, pathbuf);
7cf11b75 153 text_query(mappedfile, pathbuf, t, depth-1);
70322ae3 154 strcat(pathbuf, "\001");
155 xi1 = trie_before(mappedfile, pathbuf);
156 }
157 }
158}
159
56fa1896 160/*
161 * Largely frivolous way to define all my command-line options. I
162 * present here a parametric macro which declares a series of
163 * _logical_ option identifiers, and for each one declares zero or
164 * more short option characters and zero or more long option
165 * words. Then I repeatedly invoke that macro with its arguments
166 * defined to be various other macros, which allows me to
167 * variously:
168 *
169 * - define an enum allocating a distinct integer value to each
170 * logical option id
171 * - define a string consisting of precisely all the short option
172 * characters
173 * - define a string array consisting of all the long option
174 * strings
175 * - define (with help from auxiliary enums) integer arrays
176 * parallel to both of the above giving the logical option id
177 * for each physical short and long option
178 * - define an array indexed by logical option id indicating
179 * whether the option in question takes a value.
180 *
181 * It's not at all clear to me that this trickery is actually
182 * particularly _efficient_ - it still, after all, requires going
183 * linearly through the option list at run time and doing a
184 * strcmp, whereas in an ideal world I'd have liked the lists of
185 * long and short options to be pre-sorted so that a binary search
186 * or some other more efficient lookup was possible. (Not that
187 * asymptotic algorithmic complexity is remotely vital in option
188 * parsing, but if I were doing this in, say, Lisp or something
189 * with an equivalently powerful preprocessor then once I'd had
190 * the idea of preparing the option-parsing data structures at
191 * compile time I would probably have made the effort to prepare
192 * them _properly_. I could have Perl generate me a source file
193 * from some sort of description, I suppose, but that would seem
194 * like overkill. And in any case, it's more of a challenge to
195 * achieve as much as possible by cunning use of cpp and enum than
196 * to just write some sensible and logical code in a Turing-
197 * complete language. I said it was largely frivolous :-)
198 *
199 * This approach does have the virtue that it brings together the
200 * option ids and option spellings into a single combined list and
201 * defines them all in exactly one place. If I want to add a new
202 * option, or a new spelling for an option, I only have to modify
203 * the main OPTIONS macro below and then add code to process the
204 * new logical id.
205 *
206 * (Though, really, even that isn't ideal, since it still involves
207 * modifying the source file in more than one place. In a
208 * _properly_ ideal world, I'd be able to interleave the option
209 * definitions with the code fragments that process them. And then
210 * not bother defining logical identifiers for them at all - those
211 * would be automatically generated, since I wouldn't have any
212 * need to specify them manually in another part of the code.)
213 */
214
215#define OPTIONS(NOVAL, VAL, SHORT, LONG) \
216 NOVAL(HELP) SHORT(h) LONG(help) \
217 NOVAL(VERSION) SHORT(V) LONG(version) \
218 NOVAL(LICENCE) LONG(licence) LONG(license) \
219 NOVAL(SCAN) SHORT(s) LONG(scan) \
220 NOVAL(DUMP) SHORT(d) LONG(dump) \
221 NOVAL(TEXT) SHORT(t) LONG(text) \
222 NOVAL(HTML) SHORT(H) LONG(html) \
223 NOVAL(HTTPD) SHORT(w) LONG(web) LONG(server) LONG(httpd) \
224 NOVAL(PROGRESS) LONG(progress) LONG(scan_progress) \
225 NOVAL(NOPROGRESS) LONG(no_progress) LONG(no_scan_progress) \
226 NOVAL(TTYPROGRESS) LONG(tty_progress) LONG(tty_scan_progress) \
227 LONG(progress_tty) LONG(scan_progress_tty) \
228 NOVAL(CROSSFS) LONG(cross_fs) \
229 NOVAL(NOCROSSFS) LONG(no_cross_fs) \
230 VAL(DATAFILE) SHORT(f) LONG(file) \
231 VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
232 VAL(AUTH) LONG(auth) LONG(http_auth) LONG(httpd_auth) \
233 LONG(server_auth) LONG(web_auth) \
234 VAL(INCLUDE) LONG(include) \
235 VAL(INCLUDEPATH) LONG(include_path) \
236 VAL(EXCLUDE) LONG(exclude) \
237 VAL(EXCLUDEPATH) LONG(exclude_path)
238
239#define IGNORE(x)
240#define DEFENUM(x) OPT_ ## x,
241#define ZERO(x) 0,
242#define ONE(x) 1,
243#define STRING(x) #x ,
244#define STRINGNOCOMMA(x) #x
245#define SHORTNEWOPT(x) SHORTtmp_ ## x = OPT_ ## x,
246#define SHORTTHISOPT(x) SHORTtmp2_ ## x, SHORTVAL_ ## x = SHORTtmp2_ ## x - 1,
247#define SHORTOPTVAL(x) SHORTVAL_ ## x,
248#define SHORTTMP(x) SHORTtmp3_ ## x,
249#define LONGNEWOPT(x) LONGtmp_ ## x = OPT_ ## x,
250#define LONGTHISOPT(x) LONGtmp2_ ## x, LONGVAL_ ## x = LONGtmp2_ ## x - 1,
251#define LONGOPTVAL(x) LONGVAL_ ## x,
252#define LONGTMP(x) SHORTtmp3_ ## x,
253
254enum { OPTIONS(DEFENUM,DEFENUM,IGNORE,IGNORE) NOPTIONS };
255enum { OPTIONS(IGNORE,IGNORE,SHORTTMP,IGNORE) NSHORTOPTS };
256enum { OPTIONS(IGNORE,IGNORE,IGNORE,LONGTMP) NLONGOPTS };
257static const int opthasval[NOPTIONS] = {OPTIONS(ZERO,ONE,IGNORE,IGNORE)};
258static const char shortopts[] = {OPTIONS(IGNORE,IGNORE,STRINGNOCOMMA,IGNORE)};
259static const char *const longopts[] = {OPTIONS(IGNORE,IGNORE,IGNORE,STRING)};
260enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) };
261enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) };
262static const int shortvals[] = {OPTIONS(IGNORE,IGNORE,SHORTOPTVAL,IGNORE)};
263static const int longvals[] = {OPTIONS(IGNORE,IGNORE,IGNORE,LONGOPTVAL)};
264
70322ae3 265int main(int argc, char **argv)
266{
267 int fd, count;
268 struct ctx actx, *ctx = &actx;
269 struct stat st;
270 off_t totalsize, realsize;
271 void *mappedfile;
272 triewalk *tw;
273 indexbuild *ib;
274 const struct trie_file *tf;
275 char *filename = "agedu.dat";
276 char *rootdir = NULL;
277 int doing_opts = 1;
7cf11b75 278 enum { USAGE, TEXT, HTML, SCAN, DUMP, HTTPD } mode = USAGE;
70322ae3 279 char *minage = "0d";
812e4bf2 280 int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
8b1f55d6 281 int progress = 1;
9d0b9596 282 struct inclusion_exclusion *inex = NULL;
283 int ninex = 0, inexsize = 0;
284 int crossfs = 0;
70322ae3 285
56fa1896 286#ifdef DEBUG_MAD_OPTION_PARSING_MACROS
287 {
288 static const char *const optnames[NOPTIONS] = {
289 OPTIONS(STRING,STRING,IGNORE,IGNORE)
290 };
291 int i;
292 for (i = 0; i < NSHORTOPTS; i++)
293 printf("-%c == %s [%s]\n", shortopts[i], optnames[shortvals[i]],
294 opthasval[shortvals[i]] ? "value" : "no value");
295 for (i = 0; i < NLONGOPTS; i++)
296 printf("--%s == %s [%s]\n", longopts[i], optnames[longvals[i]],
297 opthasval[longvals[i]] ? "value" : "no value");
298 }
299#endif
300
70322ae3 301 while (--argc > 0) {
302 char *p = *++argv;
70322ae3 303
304 if (doing_opts && *p == '-') {
56fa1896 305 int wordstart = 1;
306
70322ae3 307 if (!strcmp(p, "--")) {
308 doing_opts = 0;
56fa1896 309 continue;
310 }
311
312 p++;
313 while (*p) {
314 int optid = -1;
315 int i;
316 char *optval;
317
318 if (wordstart && *p == '-') {
70322ae3 319 /*
56fa1896 320 * GNU-style long option.
70322ae3 321 */
56fa1896 322 p++;
323 optval = strchr(p, '=');
324 if (optval)
325 *optval++ = '\0';
326
327 for (i = 0; i < NLONGOPTS; i++) {
328 const char *opt = longopts[i], *s = p;
329 int match = 1;
330 /*
331 * The underscores in the option names
332 * defined above may be given by the user
333 * as underscores or dashes, or omitted
334 * entirely.
335 */
336 while (*opt) {
337 if (*opt == '_') {
338 if (*s == '-' || *s == '_')
339 s++;
340 } else {
341 if (*opt != *s) {
342 match = 0;
343 break;
344 }
345 s++;
346 }
347 opt++;
348 }
349 if (match && !*s) {
350 optid = longvals[i];
351 break;
70322ae3 352 }
353 }
56fa1896 354
355 if (optid < 0) {
356 fprintf(stderr, "%s: unrecognised option '--%s'\n",
357 PNAME, p);
358 return 1;
359 }
360
361 if (!opthasval[optid]) {
362 if (optval) {
363 fprintf(stderr, "%s: unexpected argument to option"
364 " '--%s'\n", PNAME, p);
812e4bf2 365 return 1;
366 }
56fa1896 367 } else {
368 if (!optval) {
369 if (--argc > 0) {
370 optval = *++argv;
371 } else {
372 fprintf(stderr, "%s: option '--%s' expects"
373 " an argument\n", PNAME, p);
374 return 1;
375 }
9d0b9596 376 }
70322ae3 377 }
56fa1896 378
379 p += strlen(p); /* finished with this argument word */
70322ae3 380 } else {
56fa1896 381 /*
382 * Short option.
383 */
70322ae3 384 char c = *p++;
385
56fa1896 386 for (i = 0; i < NSHORTOPTS; i++)
387 if (c == shortopts[i]) {
388 optid = shortvals[i];
389 break;
390 }
391
392 if (optid < 0) {
393 fprintf(stderr, "%s: unrecognised option '-%c'\n",
394 PNAME, c);
395 return 1;
396 }
397
398 if (opthasval[optid]) {
70322ae3 399 if (*p) {
400 optval = p;
401 p += strlen(p);
402 } else if (--argc > 0) {
403 optval = *++argv;
404 } else {
56fa1896 405 fprintf(stderr, "%s: option '-%c' expects"
70322ae3 406 " an argument\n", PNAME, c);
407 return 1;
408 }
56fa1896 409 } else {
410 optval = NULL;
411 }
412 }
413
414 wordstart = 0;
415
416 /*
417 * Now actually process the option.
418 */
419 switch (optid) {
420 case OPT_HELP:
421 printf("FIXME: usage();\n");
422 return 0;
423 case OPT_VERSION:
424 printf("FIXME: version();\n");
425 return 0;
426 case OPT_LICENCE:
427 printf("FIXME: licence();\n");
428 return 0;
429 case OPT_SCAN:
430 mode = SCAN;
431 break;
432 case OPT_DUMP:
433 mode = DUMP;
434 break;
435 case OPT_TEXT:
436 mode = TEXT;
437 break;
438 case OPT_HTML:
439 mode = HTML;
440 break;
441 case OPT_HTTPD:
442 mode = HTTPD;
443 break;
444 case OPT_PROGRESS:
445 progress = 2;
446 break;
447 case OPT_NOPROGRESS:
448 progress = 0;
449 break;
450 case OPT_TTYPROGRESS:
451 progress = 1;
452 break;
453 case OPT_CROSSFS:
454 crossfs = 1;
455 break;
456 case OPT_NOCROSSFS:
457 crossfs = 0;
458 break;
459 case OPT_DATAFILE:
460 filename = optval;
461 break;
462 case OPT_MINAGE:
463 minage = optval;
464 break;
465 case OPT_AUTH:
466 if (!strcmp(optval, "magic"))
467 auth = HTTPD_AUTH_MAGIC;
468 else if (!strcmp(optval, "basic"))
469 auth = HTTPD_AUTH_BASIC;
470 else if (!strcmp(optval, "none"))
471 auth = HTTPD_AUTH_NONE;
472 else if (!strcmp(optval, "default"))
473 auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
474 else {
475 fprintf(stderr, "%s: unrecognised authentication"
476 " type '%s'\n%*s options are 'magic',"
477 " 'basic', 'none', 'default'\n",
478 PNAME, optval, (int)strlen(PNAME), "");
479 return 1;
480 }
481 break;
482 case OPT_INCLUDE:
483 case OPT_INCLUDEPATH:
484 case OPT_EXCLUDE:
485 case OPT_EXCLUDEPATH:
486 if (ninex >= inexsize) {
487 inexsize = ninex * 3 / 2 + 16;
488 inex = sresize(inex, inexsize,
489 struct inclusion_exclusion);
490 }
491 inex[ninex].path = (optid == OPT_INCLUDEPATH ||
492 optid == OPT_EXCLUDEPATH);
493 inex[ninex].include = (optid == OPT_INCLUDE ||
494 optid == OPT_INCLUDEPATH);
495 inex[ninex].wildcard = optval;
496 ninex++;
497 break;
498 }
499 }
70322ae3 500 } else {
501 if (!rootdir) {
502 rootdir = p;
503 } else {
504 fprintf(stderr, "%s: unexpected argument '%s'\n", PNAME, p);
505 return 1;
506 }
507 }
508 }
509
510 if (!rootdir)
511 rootdir = ".";
512
7cf11b75 513 if (mode == USAGE) {
514 printf("FIXME: usage();\n");
515 return 0;
516 } else if (mode == SCAN) {
70322ae3 517
518 fd = open(filename, O_RDWR | O_TRUNC | O_CREAT, S_IRWXU);
519 if (fd < 0) {
520 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
521 strerror(errno));
522 return 1;
523 }
524
525 if (stat(rootdir, &st) < 0) {
526 fprintf(stderr, "%s: %s: stat: %s\n", PNAME, rootdir,
527 strerror(errno));
528 return 1;
529 }
9d0b9596 530 ctx->filesystem_dev = crossfs ? 0 : st.st_dev;
70322ae3 531
532 if (fstat(fd, &st) < 0) {
533 perror("agedu: fstat");
534 return 1;
535 }
536 ctx->datafile_dev = st.st_dev;
537 ctx->datafile_ino = st.st_ino;
9d0b9596 538 ctx->inex = inex;
539 ctx->ninex = ninex;
540 ctx->crossfs = crossfs;
70322ae3 541
542 ctx->last_output_update = time(NULL);
543
8b1f55d6 544 /* progress==1 means report progress only if stderr is a tty */
545 if (progress == 1)
546 progress = isatty(2) ? 2 : 0;
547 ctx->progress = progress;
548 {
549 struct winsize ws;
550 if (progress && ioctl(2, TIOCGWINSZ, &ws) == 0)
551 ctx->progwidth = ws.ws_col - 1;
552 else
553 ctx->progwidth = 79;
554 }
555
70322ae3 556 /*
557 * Scan the directory tree, and write out the trie component
558 * of the data file.
559 */
560 ctx->tb = triebuild_new(fd);
561 du(rootdir, gotdata, ctx);
562 count = triebuild_finish(ctx->tb);
563 triebuild_free(ctx->tb);
564
8b1f55d6 565 if (ctx->progress) {
566 fprintf(stderr, "%-*s\r", ctx->progwidth, "");
567 fflush(stderr);
568 }
70322ae3 569
570 /*
571 * Work out how much space the cumulative index trees will
572 * take; enlarge the file, and memory-map it.
573 */
574 if (fstat(fd, &st) < 0) {
575 perror("agedu: fstat");
576 return 1;
577 }
578
579 printf("Built pathname index, %d entries, %ju bytes\n", count,
580 (intmax_t)st.st_size);
581
582 totalsize = index_compute_size(st.st_size, count);
583
584 if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
585 perror("agedu: lseek");
586 return 1;
587 }
588 if (write(fd, "\0", 1) < 1) {
589 perror("agedu: write");
590 return 1;
591 }
592
593 printf("Upper bound on index file size = %ju bytes\n",
594 (intmax_t)totalsize);
595
596 mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
597 if (!mappedfile) {
598 perror("agedu: mmap");
599 return 1;
600 }
601
602 ib = indexbuild_new(mappedfile, st.st_size, count);
603 tw = triewalk_new(mappedfile);
604 while ((tf = triewalk_next(tw, NULL)) != NULL)
605 indexbuild_add(ib, tf);
606 triewalk_free(tw);
607 realsize = indexbuild_realsize(ib);
608 indexbuild_free(ib);
609
610 munmap(mappedfile, totalsize);
611 ftruncate(fd, realsize);
612 close(fd);
613 printf("Actual index file size = %ju bytes\n", (intmax_t)realsize);
7cf11b75 614 } else if (mode == TEXT) {
70322ae3 615 time_t t;
616 struct tm tm;
617 int nunits;
618 char unit[2];
619 size_t pathlen;
620
621 t = time(NULL);
622
623 if (2 != sscanf(minage, "%d%1[DdWwMmYy]", &nunits, unit)) {
624 fprintf(stderr, "%s: minimum age should be a number followed by"
625 " one of d,w,m,y\n", PNAME);
626 return 1;
627 }
628
629 if (unit[0] == 'd') {
630 t -= 86400 * nunits;
631 } else if (unit[0] == 'w') {
632 t -= 86400 * 7 * nunits;
633 } else {
634 int ym;
635
636 tm = *localtime(&t);
637 ym = tm.tm_year * 12 + tm.tm_mon;
638
639 if (unit[0] == 'm')
640 ym -= nunits;
641 else
642 ym -= 12 * nunits;
643
644 tm.tm_year = ym / 12;
645 tm.tm_mon = ym % 12;
646
647 t = mktime(&tm);
648 }
649
650 fd = open(filename, O_RDONLY);
651 if (fd < 0) {
652 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
653 strerror(errno));
654 return 1;
655 }
656 if (fstat(fd, &st) < 0) {
657 perror("agedu: fstat");
658 return 1;
659 }
660 totalsize = st.st_size;
661 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
662 if (!mappedfile) {
663 perror("agedu: mmap");
664 return 1;
665 }
666
667 /*
668 * Trim trailing slash, just in case.
669 */
670 pathlen = strlen(rootdir);
671 if (pathlen > 0 && rootdir[pathlen-1] == '/')
672 rootdir[--pathlen] = '\0';
673
7cf11b75 674 text_query(mappedfile, rootdir, t, 1);
70322ae3 675 } else if (mode == HTML) {
676 size_t pathlen;
677 unsigned long xi;
678 char *html;
679
680 fd = open(filename, O_RDONLY);
681 if (fd < 0) {
682 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
683 strerror(errno));
684 return 1;
685 }
686 if (fstat(fd, &st) < 0) {
687 perror("agedu: fstat");
688 return 1;
689 }
690 totalsize = st.st_size;
691 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
692 if (!mappedfile) {
693 perror("agedu: mmap");
694 return 1;
695 }
696
697 /*
698 * Trim trailing slash, just in case.
699 */
700 pathlen = strlen(rootdir);
701 if (pathlen > 0 && rootdir[pathlen-1] == '/')
702 rootdir[--pathlen] = '\0';
703
704 xi = trie_before(mappedfile, rootdir);
705 html = html_query(mappedfile, xi, NULL);
706 fputs(html, stdout);
707 } else if (mode == DUMP) {
708 size_t maxpathlen;
709 char *buf;
710
711 fd = open(filename, O_RDONLY);
712 if (fd < 0) {
713 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
714 strerror(errno));
715 return 1;
716 }
717 if (fstat(fd, &st) < 0) {
718 perror("agedu: fstat");
719 return 1;
720 }
721 totalsize = st.st_size;
722 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
723 if (!mappedfile) {
724 perror("agedu: mmap");
725 return 1;
726 }
727
728 maxpathlen = trie_maxpathlen(mappedfile);
729 buf = snewn(maxpathlen, char);
730
731 tw = triewalk_new(mappedfile);
732 while ((tf = triewalk_next(tw, buf)) != NULL) {
733 printf("%s: %llu %llu\n", buf, tf->blocks, tf->atime);
734 }
735 triewalk_free(tw);
736 } else if (mode == HTTPD) {
737 fd = open(filename, O_RDONLY);
738 if (fd < 0) {
739 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
740 strerror(errno));
741 return 1;
742 }
743 if (fstat(fd, &st) < 0) {
744 perror("agedu: fstat");
745 return 1;
746 }
747 totalsize = st.st_size;
748 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
749 if (!mappedfile) {
750 perror("agedu: mmap");
751 return 1;
752 }
753
812e4bf2 754 run_httpd(mappedfile, auth);
70322ae3 755 }
756
757 return 0;
758}