About time I wrote up the data structure used in agedu, since it's
[sgt/agedu] / agedu.c
1 /*
2 * Main program for agedu.
3 */
4
5 #include "agedu.h"
6
7 #include "du.h"
8 #include "trie.h"
9 #include "index.h"
10 #include "alloc.h"
11 #include "html.h"
12 #include "httpd.h"
13 #include "fgetline.h"
14
15 /*
16 * Path separator. This global variable affects the behaviour of
17 * various parts of the code when they need to deal with path
18 * separators. The path separator appropriate to a particular data
19 * set is encoded in the index file storing that data set; data
20 * sets generated on Unix will of course have the default '/', but
21 * foreign data sets are conceivable and must be handled correctly.
22 */
23 char pathsep = '/';
24
25 void fatal(const char *fmt, ...)
26 {
27 va_list ap;
28 fprintf(stderr, "%s: ", PNAME);
29 va_start(ap, fmt);
30 vfprintf(stderr, fmt, ap);
31 va_end(ap);
32 fprintf(stderr, "\n");
33 exit(1);
34 }
35
36 struct inclusion_exclusion {
37 int type;
38 const char *wildcard;
39 int path;
40 };
41
42 struct ctx {
43 triebuild *tb;
44 dev_t datafile_dev, filesystem_dev;
45 ino_t datafile_ino;
46 time_t last_output_update;
47 int progress, progwidth;
48 int straight_to_dump;
49 struct inclusion_exclusion *inex;
50 int ninex;
51 int crossfs;
52 int usemtime;
53 int fakeatimes;
54 };
55
56 static void dump_line(const char *pathname, const struct trie_file *tf)
57 {
58 const char *p;
59 if (printf("%llu %llu ", tf->size, tf->atime) < 0) goto error;
60 for (p = pathname; *p; p++) {
61 if (*p >= ' ' && *p < 127 && *p != '%') {
62 if (putchar(*p) == EOF) goto error;
63 } else {
64 if (printf("%%%02x", (unsigned char)*p) < 0) goto error;
65 }
66 }
67 if (putchar('\n') == EOF) goto error;
68 return;
69 error:
70 fatal("standard output: %s", strerror(errno));
71 }
72
73 static int gotdata(void *vctx, const char *pathname, const STRUCT_STAT *st)
74 {
75 struct ctx *ctx = (struct ctx *)vctx;
76 struct trie_file file;
77 time_t t;
78 int i, include;
79 const char *filename;
80
81 /*
82 * Filter out our own data file.
83 */
84 if (st->st_dev == ctx->datafile_dev && st->st_ino == ctx->datafile_ino)
85 return 0;
86
87 /*
88 * Don't cross the streams^W^Wany file system boundary.
89 */
90 if (!ctx->crossfs && st->st_dev != ctx->filesystem_dev)
91 return 0;
92
93 file.size = (unsigned long long)512 * st->st_blocks;
94 if (ctx->usemtime || (ctx->fakeatimes && S_ISDIR(st->st_mode)))
95 file.atime = st->st_mtime;
96 else
97 file.atime = max(st->st_mtime, st->st_atime);
98
99 /*
100 * Filter based on wildcards.
101 */
102 include = 1;
103 filename = strrchr(pathname, pathsep);
104 if (!filename)
105 filename = pathname;
106 else
107 filename++;
108 for (i = 0; i < ctx->ninex; i++) {
109 if (fnmatch(ctx->inex[i].wildcard,
110 ctx->inex[i].path ? pathname : filename, 0) == 0)
111 include = ctx->inex[i].type;
112 }
113 if (include == -1)
114 return 0; /* ignore this entry and any subdirs */
115 if (include == 0) {
116 /*
117 * Here we are supposed to be filtering an entry out, but
118 * still recursing into it if it's a directory. However,
119 * we can't actually leave out any directory whose
120 * subdirectories we then look at. So we cheat, in that
121 * case, by setting the size to zero.
122 */
123 if (!S_ISDIR(st->st_mode))
124 return 0; /* just ignore */
125 else
126 file.size = 0;
127 }
128
129 if (ctx->straight_to_dump)
130 dump_line(pathname, &file);
131 else
132 triebuild_add(ctx->tb, pathname, &file);
133
134 if (ctx->progress) {
135 t = time(NULL);
136 if (t != ctx->last_output_update) {
137 fprintf(stderr, "%-*.*s\r", ctx->progwidth, ctx->progwidth,
138 pathname);
139 fflush(stderr);
140 ctx->last_output_update = t;
141 }
142 }
143
144 return 1;
145 }
146
147 static void scan_error(void *vctx, const char *fmt, ...)
148 {
149 struct ctx *ctx = (struct ctx *)vctx;
150 va_list ap;
151
152 if (ctx->progress) {
153 fprintf(stderr, "%-*s\r", ctx->progwidth, "");
154 fflush(stderr);
155 }
156
157 fprintf(stderr, "%s: ", PNAME);
158 va_start(ap, fmt);
159 vfprintf(stderr, fmt, ap);
160 va_end(ap);
161
162 ctx->last_output_update--; /* force a progress report next time */
163 }
164
165 static void text_query(const void *mappedfile, const char *querydir,
166 time_t t, int showfiles, int depth)
167 {
168 size_t maxpathlen;
169 char *pathbuf;
170 unsigned long xi1, xi2;
171 unsigned long long size;
172
173 maxpathlen = trie_maxpathlen(mappedfile);
174 pathbuf = snewn(maxpathlen + 1, char);
175
176 /*
177 * We want to query everything between the supplied filename
178 * (inclusive) and that filename with a ^A on the end
179 * (exclusive). So find the x indices for each.
180 */
181 strcpy(pathbuf, querydir);
182 make_successor(pathbuf);
183 xi1 = trie_before(mappedfile, querydir);
184 xi2 = trie_before(mappedfile, pathbuf);
185
186 if (!showfiles && xi2 - xi1 == 1)
187 return; /* file, or empty dir => no display */
188
189 /*
190 * Now do the lookups in the age index.
191 */
192 if (xi2 - xi1 == 1) {
193 /*
194 * We are querying an individual file, so we should not
195 * depend on the index entries either side of the node,
196 * since they almost certainly don't both exist. Instead,
197 * just look up the file's size and atime in the main trie.
198 */
199 const struct trie_file *f = trie_getfile(mappedfile, xi1);
200 if (f->atime < t)
201 size = f->size;
202 else
203 size = 0;
204 } else {
205 unsigned long long s1, s2;
206 s1 = index_query(mappedfile, xi1, t);
207 s2 = index_query(mappedfile, xi2, t);
208 size = s2 - s1;
209 }
210
211 if (size == 0)
212 return; /* no space taken up => no display */
213
214 if (depth > 0) {
215 /*
216 * Now scan for first-level subdirectories and report
217 * those too.
218 */
219 xi1++;
220 while (xi1 < xi2) {
221 trie_getpath(mappedfile, xi1, pathbuf);
222 text_query(mappedfile, pathbuf, t, showfiles, depth-1);
223 make_successor(pathbuf);
224 xi1 = trie_before(mappedfile, pathbuf);
225 }
226 }
227
228 /* Display in units of 1Kb */
229 printf("%-11llu %s\n", (size) / 1024, querydir);
230 }
231
232 /*
233 * Largely frivolous way to define all my command-line options. I
234 * present here a parametric macro which declares a series of
235 * _logical_ option identifiers, and for each one declares zero or
236 * more short option characters and zero or more long option
237 * words. Then I repeatedly invoke that macro with its arguments
238 * defined to be various other macros, which allows me to
239 * variously:
240 *
241 * - define an enum allocating a distinct integer value to each
242 * logical option id
243 * - define a string consisting of precisely all the short option
244 * characters
245 * - define a string array consisting of all the long option
246 * strings
247 * - define (with help from auxiliary enums) integer arrays
248 * parallel to both of the above giving the logical option id
249 * for each physical short and long option
250 * - define an array indexed by logical option id indicating
251 * whether the option in question takes a value
252 * - define a function which prints out brief online help for all
253 * the options.
254 *
255 * It's not at all clear to me that this trickery is actually
256 * particularly _efficient_ - it still, after all, requires going
257 * linearly through the option list at run time and doing a
258 * strcmp, whereas in an ideal world I'd have liked the lists of
259 * long and short options to be pre-sorted so that a binary search
260 * or some other more efficient lookup was possible. (Not that
261 * asymptotic algorithmic complexity is remotely vital in option
262 * parsing, but if I were doing this in, say, Lisp or something
263 * with an equivalently powerful preprocessor then once I'd had
264 * the idea of preparing the option-parsing data structures at
265 * compile time I would probably have made the effort to prepare
266 * them _properly_. I could have Perl generate me a source file
267 * from some sort of description, I suppose, but that would seem
268 * like overkill. And in any case, it's more of a challenge to
269 * achieve as much as possible by cunning use of cpp and enum than
270 * to just write some sensible and logical code in a Turing-
271 * complete language. I said it was largely frivolous :-)
272 *
273 * This approach does have the virtue that it brings together the
274 * option ids, option spellings and help text into a single
275 * combined list and defines them all in exactly one place. If I
276 * want to add a new option, or a new spelling for an option, I
277 * only have to modify the main OPTHELP macro below and then add
278 * code to process the new logical id.
279 *
280 * (Though, really, even that isn't ideal, since it still involves
281 * modifying the source file in more than one place. In a
282 * _properly_ ideal world, I'd be able to interleave the option
283 * definitions with the code fragments that process them. And then
284 * not bother defining logical identifiers for them at all - those
285 * would be automatically generated, since I wouldn't have any
286 * need to specify them manually in another part of the code.)
287 *
288 * One other helpful consequence of the enum-based structure here
289 * is that it causes a compiler error if I accidentally try to
290 * define the same option (short or long) twice.
291 */
292
293 #define OPTHELP(NOVAL, VAL, SHORT, LONG, HELPPFX, HELPARG, HELPLINE, HELPOPT) \
294 HELPPFX("usage") HELPLINE(PNAME " [options] action [action...]") \
295 HELPPFX("actions") \
296 VAL(SCAN) SHORT(s) LONG(scan) \
297 HELPARG("directory") HELPOPT("scan and index a directory") \
298 NOVAL(HTTPD) SHORT(w) LONG(web) LONG(server) LONG(httpd) \
299 HELPOPT("serve HTML reports from a temporary web server") \
300 VAL(TEXT) SHORT(t) LONG(text) \
301 HELPARG("subdir") HELPOPT("print a plain text report on a subdirectory") \
302 NOVAL(REMOVE) SHORT(R) LONG(remove) LONG(delete) LONG(unlink) \
303 HELPOPT("remove the index file") \
304 NOVAL(DUMP) SHORT(D) LONG(dump) HELPOPT("dump the index file on stdout") \
305 NOVAL(LOAD) SHORT(L) LONG(load) \
306 HELPOPT("load and index a dump file") \
307 VAL(SCANDUMP) SHORT(S) LONG(scan_dump) \
308 HELPARG("directory") HELPOPT("scan only, generating a dump") \
309 VAL(HTML) SHORT(H) LONG(html) \
310 HELPARG("subdir") HELPOPT("print an HTML report on a subdirectory") \
311 HELPPFX("options") \
312 VAL(DATAFILE) SHORT(f) LONG(file) \
313 HELPARG("filename") HELPOPT("[most modes] specify index file") \
314 NOVAL(CROSSFS) LONG(cross_fs) \
315 HELPOPT("[--scan] cross filesystem boundaries") \
316 NOVAL(NOCROSSFS) LONG(no_cross_fs) \
317 HELPOPT("[--scan] stick to one filesystem") \
318 VAL(PRUNE) LONG(prune) \
319 HELPARG("wildcard") HELPOPT("[--scan] prune files matching pattern") \
320 VAL(PRUNEPATH) LONG(prune_path) \
321 HELPARG("wildcard") HELPOPT("[--scan] prune pathnames matching pattern") \
322 VAL(EXCLUDE) LONG(exclude) \
323 HELPARG("wildcard") HELPOPT("[--scan] exclude files matching pattern") \
324 VAL(EXCLUDEPATH) LONG(exclude_path) \
325 HELPARG("wildcard") HELPOPT("[--scan] exclude pathnames matching pattern") \
326 VAL(INCLUDE) LONG(include) \
327 HELPARG("wildcard") HELPOPT("[--scan] include files matching pattern") \
328 VAL(INCLUDEPATH) LONG(include_path) \
329 HELPARG("wildcard") HELPOPT("[--scan] include pathnames matching pattern") \
330 NOVAL(PROGRESS) LONG(progress) LONG(scan_progress) \
331 HELPOPT("[--scan] report progress on stderr") \
332 NOVAL(NOPROGRESS) LONG(no_progress) LONG(no_scan_progress) \
333 HELPOPT("[--scan] do not report progress") \
334 NOVAL(TTYPROGRESS) LONG(tty_progress) LONG(tty_scan_progress) \
335 LONG(progress_tty) LONG(scan_progress_tty) \
336 HELPOPT("[--scan] report progress if stderr is a tty") \
337 NOVAL(DIRATIME) LONG(dir_atime) LONG(dir_atimes) \
338 HELPOPT("[--scan,--load] keep real atimes on directories") \
339 NOVAL(NODIRATIME) LONG(no_dir_atime) LONG(no_dir_atimes) \
340 HELPOPT("[--scan,--load] fake atimes on directories") \
341 NOVAL(MTIME) LONG(mtime) \
342 HELPOPT("[--scan] use mtime instead of atime") \
343 NOVAL(SHOWFILES) LONG(files) \
344 HELPOPT("[--web,--html,--text] list individual files") \
345 VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
346 HELPARG("age[-age]") HELPOPT("[--web,--html] set limits of colour coding") \
347 VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
348 LONG(server_addr) \
349 HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
350 VAL(AUTH) LONG(auth) LONG(http_auth) LONG(httpd_auth) \
351 LONG(server_auth) LONG(web_auth) \
352 HELPARG("type") HELPOPT("[--web] specify HTTP authentication method") \
353 VAL(AUTHFILE) LONG(auth_file) \
354 HELPARG("filename") HELPOPT("[--web] read HTTP Basic user/pass from file") \
355 VAL(AUTHFD) LONG(auth_fd) \
356 HELPARG("fd") HELPOPT("[--web] read HTTP Basic user/pass from fd") \
357 VAL(TQDEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
358 HELPARG("levels") HELPOPT("[--text] recurse to this many levels") \
359 VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
360 HELPARG("age") HELPOPT("[--text] include only files older than this") \
361 HELPPFX("also") \
362 NOVAL(HELP) SHORT(h) LONG(help) HELPOPT("display this help text") \
363 NOVAL(VERSION) SHORT(V) LONG(version) HELPOPT("report version number") \
364 NOVAL(LICENCE) LONG(licence) LONG(license) \
365 HELPOPT("display (MIT) licence text") \
366
367 #define IGNORE(x)
368 #define DEFENUM(x) OPT_ ## x,
369 #define ZERO(x) 0,
370 #define ONE(x) 1,
371 #define STRING(x) #x ,
372 #define STRINGNOCOMMA(x) #x
373 #define SHORTNEWOPT(x) SHORTtmp_ ## x = OPT_ ## x,
374 #define SHORTTHISOPT(x) SHORTtmp2_ ## x, SHORTVAL_ ## x = SHORTtmp2_ ## x - 1,
375 #define SHORTOPTVAL(x) SHORTVAL_ ## x,
376 #define SHORTTMP(x) SHORTtmp3_ ## x,
377 #define LONGNEWOPT(x) LONGtmp_ ## x = OPT_ ## x,
378 #define LONGTHISOPT(x) LONGtmp2_ ## x, LONGVAL_ ## x = LONGtmp2_ ## x - 1,
379 #define LONGOPTVAL(x) LONGVAL_ ## x,
380 #define LONGTMP(x) SHORTtmp3_ ## x,
381
382 #define OPTIONS(NOVAL, VAL, SHORT, LONG) \
383 OPTHELP(NOVAL, VAL, SHORT, LONG, IGNORE, IGNORE, IGNORE, IGNORE)
384
385 enum { OPTIONS(DEFENUM,DEFENUM,IGNORE,IGNORE) NOPTIONS };
386 enum { OPTIONS(IGNORE,IGNORE,SHORTTMP,IGNORE) NSHORTOPTS };
387 enum { OPTIONS(IGNORE,IGNORE,IGNORE,LONGTMP) NLONGOPTS };
388 static const int opthasval[NOPTIONS] = {OPTIONS(ZERO,ONE,IGNORE,IGNORE)};
389 static const char shortopts[] = {OPTIONS(IGNORE,IGNORE,STRINGNOCOMMA,IGNORE)};
390 static const char *const longopts[] = {OPTIONS(IGNORE,IGNORE,IGNORE,STRING)};
391 enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) };
392 enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) };
393 static const int shortvals[] = {OPTIONS(IGNORE,IGNORE,SHORTOPTVAL,IGNORE)};
394 static const int longvals[] = {OPTIONS(IGNORE,IGNORE,IGNORE,LONGOPTVAL)};
395
396 static void usage(FILE *fp)
397 {
398 char longbuf[80];
399 const char *prefix, *shortopt, *longopt, *optarg;
400 int i, optex;
401
402 #define HELPRESET prefix = shortopt = longopt = optarg = NULL, optex = -1
403 #define HELPNOVAL(s) optex = 0;
404 #define HELPVAL(s) optex = 1;
405 #define HELPSHORT(s) if (!shortopt) shortopt = "-" #s;
406 #define HELPLONG(s) if (!longopt) { \
407 strcpy(longbuf, "--" #s); longopt = longbuf; \
408 for (i = 0; longbuf[i]; i++) if (longbuf[i] == '_') longbuf[i] = '-'; }
409 #define HELPPFX(s) prefix = s;
410 #define HELPARG(s) optarg = s;
411 #define HELPLINE(s) assert(optex == -1); \
412 fprintf(fp, "%7s%c %s\n", prefix?prefix:"", prefix?':':' ', s); \
413 HELPRESET;
414 #define HELPOPT(s) assert((optex == 1 && optarg) || (optex == 0 && !optarg)); \
415 assert(shortopt || longopt); \
416 i = fprintf(fp, "%7s%c %s%s%s%s%s", prefix?prefix:"", prefix?':':' ', \
417 shortopt?shortopt:"", shortopt&&longopt?", ":"", longopt?longopt:"", \
418 optarg?" ":"", optarg?optarg:""); \
419 fprintf(fp, "%*s %s\n", i<32?32-i:0,"",s); HELPRESET;
420
421 HELPRESET;
422 OPTHELP(HELPNOVAL, HELPVAL, HELPSHORT, HELPLONG,
423 HELPPFX, HELPARG, HELPLINE, HELPOPT);
424
425 #undef HELPRESET
426 #undef HELPNOVAL
427 #undef HELPVAL
428 #undef HELPSHORT
429 #undef HELPLONG
430 #undef HELPPFX
431 #undef HELPARG
432 #undef HELPLINE
433 #undef HELPOPT
434 }
435
436 static time_t parse_age(time_t now, const char *agestr)
437 {
438 time_t t;
439 struct tm tm;
440 int nunits;
441 char unit[2];
442
443 t = now;
444
445 if (2 != sscanf(agestr, "%d%1[DdWwMmYy]", &nunits, unit)) {
446 fprintf(stderr, "%s: age specification should be a number followed by"
447 " one of d,w,m,y\n", PNAME);
448 exit(1);
449 }
450
451 if (unit[0] == 'd') {
452 t -= 86400 * nunits;
453 } else if (unit[0] == 'w') {
454 t -= 86400 * 7 * nunits;
455 } else {
456 int ym;
457
458 tm = *localtime(&t);
459 ym = tm.tm_year * 12 + tm.tm_mon;
460
461 if (unit[0] == 'm')
462 ym -= nunits;
463 else
464 ym -= 12 * nunits;
465
466 tm.tm_year = ym / 12;
467 tm.tm_mon = ym % 12;
468
469 t = mktime(&tm);
470 }
471
472 return t;
473 }
474
475 int main(int argc, char **argv)
476 {
477 int fd, count;
478 struct ctx actx, *ctx = &actx;
479 struct stat st;
480 off_t totalsize, realsize;
481 void *mappedfile;
482 triewalk *tw;
483 indexbuild *ib;
484 const struct trie_file *tf, *prevtf;
485 char *filename = PNAME ".dat";
486 int doing_opts = 1;
487 enum { TEXT, HTML, SCAN, DUMP, SCANDUMP, LOAD, HTTPD, REMOVE };
488 struct action {
489 int mode;
490 char *arg;
491 } *actions = NULL;
492 int nactions = 0, actionsize = 0, action;
493 time_t now = time(NULL);
494 time_t textcutoff = now, htmlnewest = now, htmloldest = now;
495 int htmlautoagerange = 1;
496 const char *httpserveraddr = NULL;
497 int httpserverport = 0;
498 const char *httpauthdata = NULL;
499 int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
500 int progress = 1;
501 struct inclusion_exclusion *inex = NULL;
502 int ninex = 0, inexsize = 0;
503 int crossfs = 0;
504 int tqdepth = 1;
505 int fakediratimes = 1;
506 int mtime = 0;
507 int showfiles = 0;
508
509 #ifdef DEBUG_MAD_OPTION_PARSING_MACROS
510 {
511 static const char *const optnames[NOPTIONS] = {
512 OPTIONS(STRING,STRING,IGNORE,IGNORE)
513 };
514 int i;
515 for (i = 0; i < NSHORTOPTS; i++)
516 printf("-%c == %s [%s]\n", shortopts[i], optnames[shortvals[i]],
517 opthasval[shortvals[i]] ? "value" : "no value");
518 for (i = 0; i < NLONGOPTS; i++)
519 printf("--%s == %s [%s]\n", longopts[i], optnames[longvals[i]],
520 opthasval[longvals[i]] ? "value" : "no value");
521 }
522 #endif
523
524 while (--argc > 0) {
525 char *p = *++argv;
526
527 if (doing_opts && *p == '-') {
528 int wordstart = 1;
529
530 if (!strcmp(p, "--")) {
531 doing_opts = 0;
532 continue;
533 }
534
535 p++;
536 while (*p) {
537 int optid = -1;
538 int i;
539 char *optval;
540
541 if (wordstart && *p == '-') {
542 /*
543 * GNU-style long option.
544 */
545 p++;
546 optval = strchr(p, '=');
547 if (optval)
548 *optval++ = '\0';
549
550 for (i = 0; i < NLONGOPTS; i++) {
551 const char *opt = longopts[i], *s = p;
552 int match = 1;
553 /*
554 * The underscores in the option names
555 * defined above may be given by the user
556 * as underscores or dashes, or omitted
557 * entirely.
558 */
559 while (*opt) {
560 if (*opt == '_') {
561 if (*s == '-' || *s == '_')
562 s++;
563 } else {
564 if (*opt != *s) {
565 match = 0;
566 break;
567 }
568 s++;
569 }
570 opt++;
571 }
572 if (match && !*s) {
573 optid = longvals[i];
574 break;
575 }
576 }
577
578 if (optid < 0) {
579 fprintf(stderr, "%s: unrecognised option '--%s'\n",
580 PNAME, p);
581 return 1;
582 }
583
584 if (!opthasval[optid]) {
585 if (optval) {
586 fprintf(stderr, "%s: unexpected argument to option"
587 " '--%s'\n", PNAME, p);
588 return 1;
589 }
590 } else {
591 if (!optval) {
592 if (--argc > 0) {
593 optval = *++argv;
594 } else {
595 fprintf(stderr, "%s: option '--%s' expects"
596 " an argument\n", PNAME, p);
597 return 1;
598 }
599 }
600 }
601
602 p += strlen(p); /* finished with this argument word */
603 } else {
604 /*
605 * Short option.
606 */
607 char c = *p++;
608
609 for (i = 0; i < NSHORTOPTS; i++)
610 if (c == shortopts[i]) {
611 optid = shortvals[i];
612 break;
613 }
614
615 if (optid < 0) {
616 fprintf(stderr, "%s: unrecognised option '-%c'\n",
617 PNAME, c);
618 return 1;
619 }
620
621 if (opthasval[optid]) {
622 if (*p) {
623 optval = p;
624 p += strlen(p);
625 } else if (--argc > 0) {
626 optval = *++argv;
627 } else {
628 fprintf(stderr, "%s: option '-%c' expects"
629 " an argument\n", PNAME, c);
630 return 1;
631 }
632 } else {
633 optval = NULL;
634 }
635 }
636
637 wordstart = 0;
638
639 /*
640 * Now actually process the option.
641 */
642 switch (optid) {
643 case OPT_HELP:
644 usage(stdout);
645 return 0;
646 case OPT_VERSION:
647 #ifdef PACKAGE_VERSION
648 printf("%s, revision %s\n", PNAME, PACKAGE_VERSION);
649 #else
650 printf("%s: version number not available when not built"
651 " via automake\n", PNAME);
652 #endif
653 return 0;
654 case OPT_LICENCE:
655 {
656 extern const char *const licence[];
657 int i;
658
659 for (i = 0; licence[i]; i++)
660 fputs(licence[i], stdout);
661
662 return 0;
663 }
664 return 0;
665 case OPT_SCAN:
666 if (nactions >= actionsize) {
667 actionsize = nactions * 3 / 2 + 16;
668 actions = sresize(actions, actionsize, struct action);
669 }
670 actions[nactions].mode = SCAN;
671 actions[nactions].arg = optval;
672 nactions++;
673 break;
674 case OPT_SCANDUMP:
675 if (nactions >= actionsize) {
676 actionsize = nactions * 3 / 2 + 16;
677 actions = sresize(actions, actionsize, struct action);
678 }
679 actions[nactions].mode = SCANDUMP;
680 actions[nactions].arg = optval;
681 nactions++;
682 break;
683 case OPT_DUMP:
684 if (nactions >= actionsize) {
685 actionsize = nactions * 3 / 2 + 16;
686 actions = sresize(actions, actionsize, struct action);
687 }
688 actions[nactions].mode = DUMP;
689 actions[nactions].arg = NULL;
690 nactions++;
691 break;
692 case OPT_LOAD:
693 if (nactions >= actionsize) {
694 actionsize = nactions * 3 / 2 + 16;
695 actions = sresize(actions, actionsize, struct action);
696 }
697 actions[nactions].mode = LOAD;
698 actions[nactions].arg = NULL;
699 nactions++;
700 break;
701 case OPT_TEXT:
702 if (nactions >= actionsize) {
703 actionsize = nactions * 3 / 2 + 16;
704 actions = sresize(actions, actionsize, struct action);
705 }
706 actions[nactions].mode = TEXT;
707 actions[nactions].arg = optval;
708 nactions++;
709 break;
710 case OPT_HTML:
711 if (nactions >= actionsize) {
712 actionsize = nactions * 3 / 2 + 16;
713 actions = sresize(actions, actionsize, struct action);
714 }
715 actions[nactions].mode = HTML;
716 actions[nactions].arg = optval;
717 nactions++;
718 break;
719 case OPT_HTTPD:
720 if (nactions >= actionsize) {
721 actionsize = nactions * 3 / 2 + 16;
722 actions = sresize(actions, actionsize, struct action);
723 }
724 actions[nactions].mode = HTTPD;
725 actions[nactions].arg = NULL;
726 nactions++;
727 break;
728 case OPT_REMOVE:
729 if (nactions >= actionsize) {
730 actionsize = nactions * 3 / 2 + 16;
731 actions = sresize(actions, actionsize, struct action);
732 }
733 actions[nactions].mode = REMOVE;
734 actions[nactions].arg = NULL;
735 nactions++;
736 break;
737 case OPT_PROGRESS:
738 progress = 2;
739 break;
740 case OPT_NOPROGRESS:
741 progress = 0;
742 break;
743 case OPT_TTYPROGRESS:
744 progress = 1;
745 break;
746 case OPT_CROSSFS:
747 crossfs = 1;
748 break;
749 case OPT_NOCROSSFS:
750 crossfs = 0;
751 break;
752 case OPT_DIRATIME:
753 fakediratimes = 0;
754 break;
755 case OPT_NODIRATIME:
756 fakediratimes = 1;
757 break;
758 case OPT_SHOWFILES:
759 showfiles = 1;
760 break;
761 case OPT_MTIME:
762 mtime = 1;
763 break;
764 case OPT_DATAFILE:
765 filename = optval;
766 break;
767 case OPT_TQDEPTH:
768 tqdepth = atoi(optval);
769 break;
770 case OPT_MINAGE:
771 textcutoff = parse_age(now, optval);
772 break;
773 case OPT_AGERANGE:
774 if (!strcmp(optval, "auto")) {
775 htmlautoagerange = 1;
776 } else {
777 char *q = optval + strcspn(optval, "-:");
778 if (*q)
779 *q++ = '\0';
780 htmloldest = parse_age(now, optval);
781 htmlnewest = *q ? parse_age(now, q) : now;
782 htmlautoagerange = 0;
783 }
784 break;
785 case OPT_SERVERADDR:
786 {
787 char *port;
788 if (optval[0] == '[' &&
789 (port = strchr(optval, ']')) != NULL)
790 port++;
791 else
792 port = optval;
793 port += strcspn(port, ":");
794 if (port)
795 *port++ = '\0';
796 httpserveraddr = optval;
797 httpserverport = atoi(port);
798 }
799 break;
800 case OPT_AUTH:
801 if (!strcmp(optval, "magic"))
802 auth = HTTPD_AUTH_MAGIC;
803 else if (!strcmp(optval, "basic"))
804 auth = HTTPD_AUTH_BASIC;
805 else if (!strcmp(optval, "none"))
806 auth = HTTPD_AUTH_NONE;
807 else if (!strcmp(optval, "default"))
808 auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
809 else if (!strcmp(optval, "help") ||
810 !strcmp(optval, "list")) {
811 printf(PNAME ": supported HTTP authentication types"
812 " are:\n"
813 " magic use Linux /proc/net/tcp to"
814 " determine owner of peer socket\n"
815 " basic HTTP Basic username and"
816 " password authentication\n"
817 " default use 'magic' if possible, "
818 " otherwise fall back to 'basic'\n"
819 " none unauthenticated HTTP (if"
820 " the data file is non-confidential)\n");
821 return 0;
822 } else {
823 fprintf(stderr, "%s: unrecognised authentication"
824 " type '%s'\n%*s options are 'magic',"
825 " 'basic', 'none', 'default'\n",
826 PNAME, optval, (int)strlen(PNAME), "");
827 return 1;
828 }
829 break;
830 case OPT_AUTHFILE:
831 case OPT_AUTHFD:
832 {
833 int fd;
834 char namebuf[40];
835 const char *name;
836 char *authbuf;
837 int authlen, authsize;
838 int ret;
839
840 if (optid == OPT_AUTHFILE) {
841 fd = open(optval, O_RDONLY);
842 if (fd < 0) {
843 fprintf(stderr, "%s: %s: open: %s\n", PNAME,
844 optval, strerror(errno));
845 return 1;
846 }
847 name = optval;
848 } else {
849 fd = atoi(optval);
850 name = namebuf;
851 sprintf(namebuf, "fd %d", fd);
852 }
853
854 authlen = 0;
855 authsize = 256;
856 authbuf = snewn(authsize, char);
857 while ((ret = read(fd, authbuf+authlen,
858 authsize-authlen)) > 0) {
859 authlen += ret;
860 if ((authsize - authlen) < (authsize / 16)) {
861 authsize = authlen * 3 / 2 + 4096;
862 authbuf = sresize(authbuf, authsize, char);
863 }
864 }
865 if (ret < 0) {
866 fprintf(stderr, "%s: %s: read: %s\n", PNAME,
867 name, strerror(errno));
868 return 1;
869 }
870 if (optid == OPT_AUTHFILE)
871 close(fd);
872 httpauthdata = authbuf;
873 }
874 break;
875 case OPT_INCLUDE:
876 case OPT_INCLUDEPATH:
877 case OPT_EXCLUDE:
878 case OPT_EXCLUDEPATH:
879 case OPT_PRUNE:
880 case OPT_PRUNEPATH:
881 if (ninex >= inexsize) {
882 inexsize = ninex * 3 / 2 + 16;
883 inex = sresize(inex, inexsize,
884 struct inclusion_exclusion);
885 }
886 inex[ninex].path = (optid == OPT_INCLUDEPATH ||
887 optid == OPT_EXCLUDEPATH ||
888 optid == OPT_PRUNEPATH);
889 inex[ninex].type = (optid == OPT_INCLUDE ? 1 :
890 optid == OPT_INCLUDEPATH ? 1 :
891 optid == OPT_EXCLUDE ? 0 :
892 optid == OPT_EXCLUDEPATH ? 0 :
893 optid == OPT_PRUNE ? -1 :
894 /* optid == OPT_PRUNEPATH ? */ -1);
895 inex[ninex].wildcard = optval;
896 ninex++;
897 break;
898 }
899 }
900 } else {
901 fprintf(stderr, "%s: unexpected argument '%s'\n", PNAME, p);
902 return 1;
903 }
904 }
905
906 if (nactions == 0) {
907 usage(stderr);
908 return 1;
909 }
910
911 for (action = 0; action < nactions; action++) {
912 int mode = actions[action].mode;
913
914 if (mode == SCAN || mode == SCANDUMP || mode == LOAD) {
915 const char *scandir = actions[action].arg;
916
917 if (mode == LOAD) {
918 char *buf = fgetline(stdin);
919 unsigned newpathsep;
920 buf[strcspn(buf, "\r\n")] = '\0';
921 if (1 != sscanf(buf, DUMPHDR "%x",
922 &newpathsep)) {
923 fprintf(stderr, "%s: header in dump file not recognised\n",
924 PNAME);
925 return 1;
926 }
927 pathsep = (char)newpathsep;
928 sfree(buf);
929 }
930
931 if (mode == SCAN || mode == LOAD) {
932 /*
933 * Prepare to write out the index file.
934 */
935 fd = open(filename, O_RDWR | O_TRUNC | O_CREAT,
936 S_IRUSR | S_IWUSR);
937 if (fd < 0) {
938 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
939 strerror(errno));
940 return 1;
941 }
942 if (fstat(fd, &st) < 0) {
943 perror(PNAME ": fstat");
944 return 1;
945 }
946 ctx->datafile_dev = st.st_dev;
947 ctx->datafile_ino = st.st_ino;
948 ctx->straight_to_dump = 0;
949 } else {
950 ctx->datafile_dev = -1;
951 ctx->datafile_ino = -1;
952 ctx->straight_to_dump = 1;
953 }
954
955 if (mode == SCAN || mode == SCANDUMP) {
956 if (stat(scandir, &st) < 0) {
957 fprintf(stderr, "%s: %s: stat: %s\n", PNAME, scandir,
958 strerror(errno));
959 return 1;
960 }
961 ctx->filesystem_dev = crossfs ? 0 : st.st_dev;
962 }
963
964 ctx->inex = inex;
965 ctx->ninex = ninex;
966 ctx->crossfs = crossfs;
967 ctx->fakeatimes = fakediratimes;
968 ctx->usemtime = mtime;
969
970 ctx->last_output_update = time(NULL);
971
972 /* progress==1 means report progress only if stderr is a tty */
973 if (progress == 1)
974 progress = isatty(2) ? 2 : 0;
975 ctx->progress = progress;
976 {
977 struct winsize ws;
978 if (progress &&
979 ioctl(2, TIOCGWINSZ, &ws) == 0 &&
980 ws.ws_col > 0)
981 ctx->progwidth = ws.ws_col - 1;
982 else
983 ctx->progwidth = 79;
984 }
985
986 if (mode == SCANDUMP)
987 printf(DUMPHDR "%02x\n", (unsigned char)pathsep);
988
989 /*
990 * Scan the directory tree, and write out the trie component
991 * of the data file.
992 */
993 if (mode != SCANDUMP) {
994 ctx->tb = triebuild_new(fd);
995 }
996 if (mode == LOAD) {
997 char *buf;
998 int line = 2;
999 while ((buf = fgetline(stdin)) != NULL) {
1000 struct trie_file tf;
1001 char *p, *q;
1002
1003 buf[strcspn(buf, "\r\n")] = '\0';
1004
1005 p = buf;
1006 q = p;
1007 while (*p && *p != ' ') p++;
1008 if (!*p) {
1009 fprintf(stderr, "%s: dump file line %d: expected at least"
1010 " three fields\n", PNAME, line);
1011 return 1;
1012 }
1013 *p++ = '\0';
1014 tf.size = strtoull(q, NULL, 10);
1015 q = p;
1016 while (*p && *p != ' ') p++;
1017 if (!*p) {
1018 fprintf(stderr, "%s: dump file line %d: expected at least"
1019 " three fields\n", PNAME, line);
1020 return 1;
1021 }
1022 *p++ = '\0';
1023 tf.atime = strtoull(q, NULL, 10);
1024 q = buf;
1025 while (*p) {
1026 int c = *p;
1027 if (*p == '%') {
1028 int i;
1029 p++;
1030 c = 0;
1031 for (i = 0; i < 2; i++) {
1032 c *= 16;
1033 if (*p >= '0' && *p <= '9')
1034 c += *p - '0';
1035 else if (*p >= 'A' && *p <= 'F')
1036 c += *p - ('A' - 10);
1037 else if (*p >= 'a' && *p <= 'f')
1038 c += *p - ('a' - 10);
1039 else {
1040 fprintf(stderr, "%s: dump file line %d: unable"
1041 " to parse hex escape\n", PNAME, line);
1042 }
1043 p++;
1044 }
1045 } else {
1046 p++;
1047 }
1048 *q++ = c;
1049 }
1050 *q = '\0';
1051 triebuild_add(ctx->tb, buf, &tf);
1052 sfree(buf);
1053 line++;
1054 }
1055 } else {
1056 du(scandir, gotdata, scan_error, ctx);
1057 }
1058 if (mode != SCANDUMP) {
1059 size_t maxpathlen;
1060 size_t delta;
1061 char *buf, *prevbuf;
1062
1063 count = triebuild_finish(ctx->tb);
1064 triebuild_free(ctx->tb);
1065
1066 if (ctx->progress) {
1067 fprintf(stderr, "%-*s\r", ctx->progwidth, "");
1068 fflush(stderr);
1069 }
1070
1071 /*
1072 * Work out how much space the cumulative index trees
1073 * will take; enlarge the file, and memory-map it.
1074 */
1075 if (fstat(fd, &st) < 0) {
1076 perror(PNAME ": fstat");
1077 return 1;
1078 }
1079
1080 printf("Built pathname index, %d entries,"
1081 " %llu bytes of index\n", count,
1082 (unsigned long long)st.st_size);
1083
1084 totalsize = index_initial_size(st.st_size, count);
1085 totalsize += totalsize / 10;
1086
1087 if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
1088 perror(PNAME ": lseek");
1089 return 1;
1090 }
1091 if (write(fd, "\0", 1) < 1) {
1092 perror(PNAME ": write");
1093 return 1;
1094 }
1095
1096 mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
1097 if (!mappedfile) {
1098 perror(PNAME ": mmap");
1099 return 1;
1100 }
1101
1102 if (fakediratimes) {
1103 printf("Faking directory atimes\n");
1104 trie_fake_dir_atimes(mappedfile);
1105 }
1106
1107 printf("Building index\n");
1108 ib = indexbuild_new(mappedfile, st.st_size, count, &delta);
1109 maxpathlen = trie_maxpathlen(mappedfile);
1110 buf = snewn(maxpathlen, char);
1111 prevbuf = snewn(maxpathlen, char);
1112 tw = triewalk_new(mappedfile);
1113 prevbuf[0] = '\0';
1114 tf = triewalk_next(tw, buf);
1115 assert(tf);
1116 while (1) {
1117 int i;
1118
1119 if (totalsize - indexbuild_realsize(ib) < delta) {
1120 const void *oldfile = mappedfile;
1121 ptrdiff_t diff;
1122
1123 /*
1124 * Unmap the file, grow it, and remap it.
1125 */
1126 munmap(mappedfile, totalsize);
1127
1128 totalsize += delta;
1129 totalsize += totalsize / 10;
1130
1131 if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
1132 perror(PNAME ": lseek");
1133 return 1;
1134 }
1135 if (write(fd, "\0", 1) < 1) {
1136 perror(PNAME ": write");
1137 return 1;
1138 }
1139
1140 mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
1141 if (!mappedfile) {
1142 perror(PNAME ": mmap");
1143 return 1;
1144 }
1145
1146 indexbuild_rebase(ib, mappedfile);
1147 triewalk_rebase(tw, mappedfile);
1148 diff = (const unsigned char *)mappedfile -
1149 (const unsigned char *)oldfile;
1150 if (prevtf)
1151 prevtf = (const struct trie_file *)
1152 (((const unsigned char *)prevtf) + diff);
1153 if (tf)
1154 tf = (const struct trie_file *)
1155 (((const unsigned char *)tf) + diff);
1156 }
1157
1158 /*
1159 * Get the next file from the index. So we are
1160 * currently holding, and have not yet
1161 * indexed, prevtf (with pathname prevbuf) and
1162 * tf (with pathname buf).
1163 */
1164 prevtf = tf;
1165 memcpy(prevbuf, buf, maxpathlen);
1166 tf = triewalk_next(tw, buf);
1167
1168 if (!tf)
1169 buf[0] = '\0';
1170
1171 /*
1172 * Find the first differing character position
1173 * between our two pathnames.
1174 */
1175 for (i = 0; prevbuf[i] && prevbuf[i] == buf[i]; i++);
1176
1177 /*
1178 * If prevbuf was a directory name and buf is
1179 * something inside that directory, then
1180 * trie_before() will be called on prevbuf
1181 * itself. Hence we must drop a tag before it,
1182 * so that the resulting index is usable.
1183 */
1184 if ((!prevbuf[i] && (buf[i] == pathsep ||
1185 (i > 0 && buf[i-1] == pathsep))))
1186 indexbuild_tag(ib);
1187
1188 /*
1189 * Add prevtf to the index.
1190 */
1191 indexbuild_add(ib, prevtf);
1192
1193 if (!tf) {
1194 /*
1195 * Drop an unconditional final tag, and
1196 * get out of this loop.
1197 */
1198 indexbuild_tag(ib);
1199 break;
1200 }
1201
1202 /*
1203 * If prevbuf was a filename inside some
1204 * directory which buf is outside, then
1205 * trie_before() will be called on some
1206 * pathname either equal to buf or epsilon
1207 * less than it. Either way, we're going to
1208 * need to drop a tag after prevtf.
1209 */
1210 if (strchr(prevbuf+i, pathsep) || !tf)
1211 indexbuild_tag(ib);
1212 }
1213
1214 triewalk_free(tw);
1215 realsize = indexbuild_realsize(ib);
1216 indexbuild_free(ib);
1217
1218 munmap(mappedfile, totalsize);
1219 ftruncate(fd, realsize);
1220 close(fd);
1221 printf("Final index file size = %llu bytes\n",
1222 (unsigned long long)realsize);
1223 }
1224 } else if (mode == TEXT) {
1225 char *querydir = actions[action].arg;
1226 size_t pathlen;
1227
1228 fd = open(filename, O_RDONLY);
1229 if (fd < 0) {
1230 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
1231 strerror(errno));
1232 return 1;
1233 }
1234 if (fstat(fd, &st) < 0) {
1235 perror(PNAME ": fstat");
1236 return 1;
1237 }
1238 totalsize = st.st_size;
1239 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
1240 if (!mappedfile) {
1241 perror(PNAME ": mmap");
1242 return 1;
1243 }
1244 pathsep = trie_pathsep(mappedfile);
1245
1246 /*
1247 * Trim trailing slash, just in case.
1248 */
1249 pathlen = strlen(querydir);
1250 if (pathlen > 0 && querydir[pathlen-1] == pathsep)
1251 querydir[--pathlen] = '\0';
1252
1253 text_query(mappedfile, querydir, textcutoff, showfiles, tqdepth);
1254
1255 munmap(mappedfile, totalsize);
1256 } else if (mode == HTML) {
1257 char *querydir = actions[action].arg;
1258 size_t pathlen, maxpathlen;
1259 char *pathbuf;
1260 struct html_config cfg;
1261 unsigned long xi;
1262 char *html;
1263
1264 fd = open(filename, O_RDONLY);
1265 if (fd < 0) {
1266 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
1267 strerror(errno));
1268 return 1;
1269 }
1270 if (fstat(fd, &st) < 0) {
1271 perror(PNAME ": fstat");
1272 return 1;
1273 }
1274 totalsize = st.st_size;
1275 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
1276 if (!mappedfile) {
1277 perror(PNAME ": mmap");
1278 return 1;
1279 }
1280 pathsep = trie_pathsep(mappedfile);
1281
1282 maxpathlen = trie_maxpathlen(mappedfile);
1283 pathbuf = snewn(maxpathlen, char);
1284
1285 /*
1286 * Trim trailing slash, just in case.
1287 */
1288 pathlen = strlen(querydir);
1289 if (pathlen > 0 && querydir[pathlen-1] == pathsep)
1290 querydir[--pathlen] = '\0';
1291
1292 xi = trie_before(mappedfile, querydir);
1293 if (xi >= trie_count(mappedfile) ||
1294 (trie_getpath(mappedfile, xi, pathbuf),
1295 strcmp(pathbuf, querydir))) {
1296 fprintf(stderr, "%s: pathname '%s' does not exist in index\n"
1297 "%*s(check it is spelled exactly as it is in the "
1298 "index, including\n%*sany leading './')\n",
1299 PNAME, querydir,
1300 (int)(1+sizeof(PNAME)), "",
1301 (int)(1+sizeof(PNAME)), "");
1302 } else if (!index_has_root(mappedfile, xi)) {
1303 fprintf(stderr, "%s: pathname '%s' is"
1304 " a file, not a directory\n", PNAME, querydir);
1305 } else {
1306 cfg.format = NULL;
1307 cfg.autoage = htmlautoagerange;
1308 cfg.oldest = htmloldest;
1309 cfg.newest = htmlnewest;
1310 cfg.showfiles = showfiles;
1311 html = html_query(mappedfile, xi, &cfg);
1312 fputs(html, stdout);
1313 }
1314
1315 munmap(mappedfile, totalsize);
1316 sfree(pathbuf);
1317 } else if (mode == DUMP) {
1318 size_t maxpathlen;
1319 char *buf;
1320
1321 fd = open(filename, O_RDONLY);
1322 if (fd < 0) {
1323 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
1324 strerror(errno));
1325 return 1;
1326 }
1327 if (fstat(fd, &st) < 0) {
1328 perror(PNAME ": fstat");
1329 return 1;
1330 }
1331 totalsize = st.st_size;
1332 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
1333 if (!mappedfile) {
1334 perror(PNAME ": mmap");
1335 return 1;
1336 }
1337 pathsep = trie_pathsep(mappedfile);
1338
1339 maxpathlen = trie_maxpathlen(mappedfile);
1340 buf = snewn(maxpathlen, char);
1341
1342 printf(DUMPHDR "%02x\n", (unsigned char)pathsep);
1343 tw = triewalk_new(mappedfile);
1344 while ((tf = triewalk_next(tw, buf)) != NULL)
1345 dump_line(buf, tf);
1346 triewalk_free(tw);
1347
1348 munmap(mappedfile, totalsize);
1349 } else if (mode == HTTPD) {
1350 struct html_config pcfg;
1351 struct httpd_config dcfg;
1352
1353 fd = open(filename, O_RDONLY);
1354 if (fd < 0) {
1355 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
1356 strerror(errno));
1357 return 1;
1358 }
1359 if (fstat(fd, &st) < 0) {
1360 perror(PNAME ": fstat");
1361 return 1;
1362 }
1363 totalsize = st.st_size;
1364 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
1365 if (!mappedfile) {
1366 perror(PNAME ": mmap");
1367 return 1;
1368 }
1369 pathsep = trie_pathsep(mappedfile);
1370
1371 dcfg.address = httpserveraddr;
1372 dcfg.port = httpserverport;
1373 dcfg.basicauthdata = httpauthdata;
1374 pcfg.format = NULL;
1375 pcfg.autoage = htmlautoagerange;
1376 pcfg.oldest = htmloldest;
1377 pcfg.newest = htmlnewest;
1378 pcfg.showfiles = showfiles;
1379 run_httpd(mappedfile, auth, &dcfg, &pcfg);
1380 munmap(mappedfile, totalsize);
1381 } else if (mode == REMOVE) {
1382 if (remove(filename) < 0) {
1383 fprintf(stderr, "%s: %s: remove: %s\n", PNAME, filename,
1384 strerror(errno));
1385 return 1;
1386 }
1387 }
1388 }
1389
1390 return 0;
1391 }