Corrections: put trailing slash on URL, and adjust svn:ignore.
[sgt/agedu] / agedu.c
CommitLineData
70322ae3 1/*
2 * Main program for agedu.
3 */
4
353bc75d 5#include "agedu.h"
50e82fdc 6
70322ae3 7#include "du.h"
8#include "trie.h"
9#include "index.h"
995db599 10#include "alloc.h"
70322ae3 11#include "html.h"
12#include "httpd.h"
84849cbd 13#include "fgetline.h"
70322ae3 14
373a02e5 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 */
23char pathsep = '/';
24
70322ae3 25void 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
9d0b9596 36struct inclusion_exclusion {
0ba55302 37 int type;
9d0b9596 38 const char *wildcard;
39 int path;
40};
41
70322ae3 42struct ctx {
43 triebuild *tb;
44 dev_t datafile_dev, filesystem_dev;
45 ino_t datafile_ino;
46 time_t last_output_update;
8b1f55d6 47 int progress, progwidth;
84849cbd 48 int straight_to_dump;
9d0b9596 49 struct inclusion_exclusion *inex;
50 int ninex;
51 int crossfs;
f59a5d34 52 int usemtime;
05b0f827 53 int fakeatimes;
70322ae3 54};
55
84849cbd 56static void dump_line(const char *pathname, const struct trie_file *tf)
57{
58 const char *p;
44d82778 59 if (printf("%llu %llu ", tf->size, tf->atime) < 0) goto error;
84849cbd 60 for (p = pathname; *p; p++) {
44d82778 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 }
84849cbd 66 }
44d82778 67 if (putchar('\n') == EOF) goto error;
68 return;
69 error:
70 fatal("standard output: %s", strerror(errno));
84849cbd 71}
72
9c6e61f2 73static int gotdata(void *vctx, const char *pathname, const STRUCT_STAT *st)
70322ae3 74{
75 struct ctx *ctx = (struct ctx *)vctx;
76 struct trie_file file;
77 time_t t;
9d0b9596 78 int i, include;
79 const char *filename;
70322ae3 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.
70322ae3 89 */
9d0b9596 90 if (!ctx->crossfs && st->st_dev != ctx->filesystem_dev)
70322ae3 91 return 0;
92
84849cbd 93 file.size = (unsigned long long)512 * st->st_blocks;
f59a5d34 94 if (ctx->usemtime || (ctx->fakeatimes && S_ISDIR(st->st_mode)))
05b0f827 95 file.atime = st->st_mtime;
96 else
7e25423c 97 file.atime = max(st->st_mtime, st->st_atime);
0ba55302 98
70322ae3 99 /*
9d0b9596 100 * Filter based on wildcards.
70322ae3 101 */
9d0b9596 102 include = 1;
373a02e5 103 filename = strrchr(pathname, pathsep);
9d0b9596 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,
0ba55302 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
84849cbd 126 file.size = 0;
9d0b9596 127 }
70322ae3 128
84849cbd 129 if (ctx->straight_to_dump)
130 dump_line(pathname, &file);
131 else
132 triebuild_add(ctx->tb, pathname, &file);
70322ae3 133
84849cbd 134 if (ctx->progress) {
135 t = time(NULL);
136 if (t != ctx->last_output_update) {
8b1f55d6 137 fprintf(stderr, "%-*.*s\r", ctx->progwidth, ctx->progwidth,
138 pathname);
139 fflush(stderr);
84849cbd 140 ctx->last_output_update = t;
8b1f55d6 141 }
70322ae3 142 }
143
144 return 1;
145}
146
09fd7619 147static 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
e9e7a1bf 165static void text_query(const void *mappedfile, const char *querydir,
16139d21 166 time_t t, int showfiles, int depth)
70322ae3 167{
168 size_t maxpathlen;
169 char *pathbuf;
170 unsigned long xi1, xi2;
16139d21 171 unsigned long long size;
70322ae3 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 */
256c29a2 181 strcpy(pathbuf, querydir);
182 make_successor(pathbuf);
e9e7a1bf 183 xi1 = trie_before(mappedfile, querydir);
70322ae3 184 xi2 = trie_before(mappedfile, pathbuf);
185
16139d21 186 if (!showfiles && xi2 - xi1 == 1)
0313b788 187 return; /* file, or empty dir => no display */
188
70322ae3 189 /*
190 * Now do the lookups in the age index.
191 */
16139d21 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 }
70322ae3 210
16139d21 211 if (size == 0)
010dd2a2 212 return; /* no space taken up => no display */
213
70322ae3 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);
16139d21 222 text_query(mappedfile, pathbuf, t, showfiles, depth-1);
256c29a2 223 make_successor(pathbuf);
70322ae3 224 xi1 = trie_before(mappedfile, pathbuf);
225 }
226 }
16e591d6 227
228 /* Display in units of 1Kb */
16139d21 229 printf("%-11llu %s\n", (size) / 1024, querydir);
70322ae3 230}
231
56fa1896 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
e9e7a1bf 251 * whether the option in question takes a value
252 * - define a function which prints out brief online help for all
253 * the options.
56fa1896 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
e9e7a1bf 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.
56fa1896 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.)
c5c3510f 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.
56fa1896 291 */
292
e9e7a1bf 293#define OPTHELP(NOVAL, VAL, SHORT, LONG, HELPPFX, HELPARG, HELPLINE, HELPOPT) \
bf53e756 294 HELPPFX("usage") HELPLINE(PNAME " [options] action [action...]") \
e9e7a1bf 295 HELPPFX("actions") \
296 VAL(SCAN) SHORT(s) LONG(scan) \
297 HELPARG("directory") HELPOPT("scan and index a directory") \
67159944 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") \
c5c3510f 304 NOVAL(DUMP) SHORT(D) LONG(dump) HELPOPT("dump the index file on stdout") \
c5c3510f 305 NOVAL(LOAD) SHORT(L) LONG(load) \
84849cbd 306 HELPOPT("load and index a dump file") \
67159944 307 VAL(SCANDUMP) SHORT(S) LONG(scan_dump) \
308 HELPARG("directory") HELPOPT("scan only, generating a dump") \
e9e7a1bf 309 VAL(HTML) SHORT(H) LONG(html) \
310 HELPARG("subdir") HELPOPT("print an HTML report on a subdirectory") \
e9e7a1bf 311 HELPPFX("options") \
312 VAL(DATAFILE) SHORT(f) LONG(file) \
c5c3510f 313 HELPARG("filename") HELPOPT("[most modes] specify index file") \
56fa1896 314 NOVAL(CROSSFS) LONG(cross_fs) \
e9e7a1bf 315 HELPOPT("[--scan] cross filesystem boundaries") \
56fa1896 316 NOVAL(NOCROSSFS) LONG(no_cross_fs) \
e9e7a1bf 317 HELPOPT("[--scan] stick to one filesystem") \
0ba55302 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") \
67159944 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") \
05b0f827 337 NOVAL(DIRATIME) LONG(dir_atime) LONG(dir_atimes) \
67159944 338 HELPOPT("[--scan,--load] keep real atimes on directories") \
05b0f827 339 NOVAL(NODIRATIME) LONG(no_dir_atime) LONG(no_dir_atimes) \
67159944 340 HELPOPT("[--scan,--load] fake atimes on directories") \
f59a5d34 341 NOVAL(MTIME) LONG(mtime) \
342 HELPOPT("[--scan] use mtime instead of atime") \
16139d21 343 NOVAL(SHOWFILES) LONG(files) \
344 HELPOPT("[--web,--html,--text] list individual files") \
f2e52893 345 VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
67159944 346 HELPARG("age[-age]") HELPOPT("[--web,--html] set limits of colour coding") \
1e8d78b9 347 VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
348 LONG(server_addr) \
349 HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
e9e7a1bf 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") \
1e8d78b9 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") \
67159944 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") \
e9e7a1bf 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") \
56fa1896 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
e9e7a1bf 382#define OPTIONS(NOVAL, VAL, SHORT, LONG) \
383 OPTHELP(NOVAL, VAL, SHORT, LONG, IGNORE, IGNORE, IGNORE, IGNORE)
384
56fa1896 385enum { OPTIONS(DEFENUM,DEFENUM,IGNORE,IGNORE) NOPTIONS };
386enum { OPTIONS(IGNORE,IGNORE,SHORTTMP,IGNORE) NSHORTOPTS };
387enum { OPTIONS(IGNORE,IGNORE,IGNORE,LONGTMP) NLONGOPTS };
388static const int opthasval[NOPTIONS] = {OPTIONS(ZERO,ONE,IGNORE,IGNORE)};
389static const char shortopts[] = {OPTIONS(IGNORE,IGNORE,STRINGNOCOMMA,IGNORE)};
390static const char *const longopts[] = {OPTIONS(IGNORE,IGNORE,IGNORE,STRING)};
391enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) };
392enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) };
393static const int shortvals[] = {OPTIONS(IGNORE,IGNORE,SHORTOPTVAL,IGNORE)};
394static const int longvals[] = {OPTIONS(IGNORE,IGNORE,IGNORE,LONGOPTVAL)};
395
e9e7a1bf 396static 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
f2e52893 436static 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
70322ae3 475int 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;
14601b5d 484 const struct trie_file *tf, *prevtf;
bf53e756 485 char *filename = PNAME ".dat";
70322ae3 486 int doing_opts = 1;
355c3af7 487 enum { TEXT, HTML, SCAN, DUMP, SCANDUMP, LOAD, HTTPD, REMOVE };
444c684c 488 struct action {
489 int mode;
490 char *arg;
491 } *actions = NULL;
492 int nactions = 0, actionsize = 0, action;
f2e52893 493 time_t now = time(NULL);
494 time_t textcutoff = now, htmlnewest = now, htmloldest = now;
495 int htmlautoagerange = 1;
1e8d78b9 496 const char *httpserveraddr = NULL;
497 int httpserverport = 0;
498 const char *httpauthdata = NULL;
812e4bf2 499 int auth = HTTPD_AUTH_MAGIC | HTTPD_AUTH_BASIC;
8b1f55d6 500 int progress = 1;
9d0b9596 501 struct inclusion_exclusion *inex = NULL;
502 int ninex = 0, inexsize = 0;
503 int crossfs = 0;
16e591d6 504 int tqdepth = 1;
05b0f827 505 int fakediratimes = 1;
f59a5d34 506 int mtime = 0;
16139d21 507 int showfiles = 0;
70322ae3 508
56fa1896 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
70322ae3 524 while (--argc > 0) {
525 char *p = *++argv;
70322ae3 526
527 if (doing_opts && *p == '-') {
56fa1896 528 int wordstart = 1;
529
70322ae3 530 if (!strcmp(p, "--")) {
531 doing_opts = 0;
56fa1896 532 continue;
533 }
534
535 p++;
536 while (*p) {
537 int optid = -1;
538 int i;
539 char *optval;
540
541 if (wordstart && *p == '-') {
70322ae3 542 /*
56fa1896 543 * GNU-style long option.
70322ae3 544 */
56fa1896 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;
70322ae3 575 }
576 }
56fa1896 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);
812e4bf2 588 return 1;
589 }
56fa1896 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 }
9d0b9596 599 }
70322ae3 600 }
56fa1896 601
602 p += strlen(p); /* finished with this argument word */
70322ae3 603 } else {
56fa1896 604 /*
605 * Short option.
606 */
70322ae3 607 char c = *p++;
608
56fa1896 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]) {
70322ae3 622 if (*p) {
623 optval = p;
624 p += strlen(p);
625 } else if (--argc > 0) {
626 optval = *++argv;
627 } else {
56fa1896 628 fprintf(stderr, "%s: option '-%c' expects"
70322ae3 629 " an argument\n", PNAME, c);
630 return 1;
631 }
56fa1896 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:
e9e7a1bf 644 usage(stdout);
56fa1896 645 return 0;
646 case OPT_VERSION:
e6fde1f7 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
56fa1896 653 return 0;
654 case OPT_LICENCE:
5a29503d 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 }
56fa1896 664 return 0;
665 case OPT_SCAN:
444c684c 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++;
56fa1896 673 break;
84849cbd 674 case OPT_SCANDUMP:
444c684c 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++;
84849cbd 682 break;
56fa1896 683 case OPT_DUMP:
444c684c 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++;
56fa1896 691 break;
84849cbd 692 case OPT_LOAD:
444c684c 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++;
84849cbd 700 break;
56fa1896 701 case OPT_TEXT:
444c684c 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++;
56fa1896 709 break;
710 case OPT_HTML:
444c684c 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++;
56fa1896 718 break;
719 case OPT_HTTPD:
444c684c 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++;
56fa1896 727 break;
355c3af7 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;
56fa1896 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;
05b0f827 752 case OPT_DIRATIME:
753 fakediratimes = 0;
754 break;
755 case OPT_NODIRATIME:
756 fakediratimes = 1;
757 break;
16139d21 758 case OPT_SHOWFILES:
759 showfiles = 1;
760 break;
f59a5d34 761 case OPT_MTIME:
762 mtime = 1;
763 break;
56fa1896 764 case OPT_DATAFILE:
765 filename = optval;
766 break;
16e591d6 767 case OPT_TQDEPTH:
768 tqdepth = atoi(optval);
769 break;
56fa1896 770 case OPT_MINAGE:
f2e52893 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 }
56fa1896 784 break;
1e8d78b9 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;
56fa1896 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;
f2e52893 809 else if (!strcmp(optval, "help") ||
810 !strcmp(optval, "list")) {
bf53e756 811 printf(PNAME ": supported HTTP authentication types"
f2e52893 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 {
56fa1896 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;
1e8d78b9 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;
56fa1896 875 case OPT_INCLUDE:
876 case OPT_INCLUDEPATH:
877 case OPT_EXCLUDE:
878 case OPT_EXCLUDEPATH:
0ba55302 879 case OPT_PRUNE:
880 case OPT_PRUNEPATH:
56fa1896 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 ||
0ba55302 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);
56fa1896 895 inex[ninex].wildcard = optval;
896 ninex++;
897 break;
898 }
899 }
70322ae3 900 } else {
e9e7a1bf 901 fprintf(stderr, "%s: unexpected argument '%s'\n", PNAME, p);
902 return 1;
70322ae3 903 }
904 }
905
444c684c 906 if (nactions == 0) {
e9e7a1bf 907 usage(stderr);
908 return 1;
444c684c 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;
14601b5d 916
444c684c 917 if (mode == LOAD) {
918 char *buf = fgetline(stdin);
919 unsigned newpathsep;
920 buf[strcspn(buf, "\r\n")] = '\0';
bf53e756 921 if (1 != sscanf(buf, DUMPHDR "%x",
444c684c 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);
84849cbd 929 }
70322ae3 930
444c684c 931 if (mode == SCAN || mode == LOAD) {
932 /*
933 * Prepare to write out the index file.
934 */
cc7db507 935 fd = open(filename, O_RDWR | O_TRUNC | O_CREAT,
936 S_IRUSR | S_IWUSR);
444c684c 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) {
bf53e756 943 perror(PNAME ": fstat");
444c684c 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;
84849cbd 953 }
444c684c 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;
84849cbd 962 }
70322ae3 963
444c684c 964 ctx->inex = inex;
965 ctx->ninex = ninex;
966 ctx->crossfs = crossfs;
05b0f827 967 ctx->fakeatimes = fakediratimes;
f59a5d34 968 ctx->usemtime = mtime;
444c684c 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;
9cb5a01c 978 if (progress &&
979 ioctl(2, TIOCGWINSZ, &ws) == 0 &&
980 ws.ws_col > 0)
444c684c 981 ctx->progwidth = ws.ws_col - 1;
982 else
983 ctx->progwidth = 79;
84849cbd 984 }
84849cbd 985
444c684c 986 if (mode == SCANDUMP)
bf53e756 987 printf(DUMPHDR "%02x\n", (unsigned char)pathsep);
8b1f55d6 988
444c684c 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++) {
de693987 1032 c *= 16;
444c684c 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 }
1f651677 1045 } else {
1046 p++;
444c684c 1047 }
1048 *q++ = c;
444c684c 1049 }
1050 *q = '\0';
1051 triebuild_add(ctx->tb, buf, &tf);
1052 sfree(buf);
de693987 1053 line++;
444c684c 1054 }
1055 } else {
09fd7619 1056 du(scandir, gotdata, scan_error, ctx);
444c684c 1057 }
1058 if (mode != SCANDUMP) {
14601b5d 1059 size_t maxpathlen;
522edd92 1060 size_t delta;
14601b5d 1061 char *buf, *prevbuf;
1062
444c684c 1063 count = triebuild_finish(ctx->tb);
1064 triebuild_free(ctx->tb);
84849cbd 1065
444c684c 1066 if (ctx->progress) {
1067 fprintf(stderr, "%-*s\r", ctx->progwidth, "");
1068 fflush(stderr);
1069 }
84849cbd 1070
444c684c 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) {
bf53e756 1076 perror(PNAME ": fstat");
444c684c 1077 return 1;
1078 }
84849cbd 1079
522edd92 1080 printf("Built pathname index, %d entries,"
1081 " %llu bytes of index\n", count,
50e82fdc 1082 (unsigned long long)st.st_size);
444c684c 1083
522edd92 1084 totalsize = index_initial_size(st.st_size, count);
1085 totalsize += totalsize / 10;
444c684c 1086
1087 if (lseek(fd, totalsize-1, SEEK_SET) < 0) {
bf53e756 1088 perror(PNAME ": lseek");
84849cbd 1089 return 1;
1090 }
444c684c 1091 if (write(fd, "\0", 1) < 1) {
bf53e756 1092 perror(PNAME ": write");
84849cbd 1093 return 1;
1094 }
444c684c 1095
444c684c 1096 mappedfile = mmap(NULL, totalsize, PROT_READ|PROT_WRITE,MAP_SHARED, fd, 0);
1097 if (!mappedfile) {
bf53e756 1098 perror(PNAME ": mmap");
444c684c 1099 return 1;
84849cbd 1100 }
444c684c 1101
05b0f827 1102 if (fakediratimes) {
1103 printf("Faking directory atimes\n");
1104 trie_fake_dir_atimes(mappedfile);
1105 }
1106
1107 printf("Building index\n");
522edd92 1108 ib = indexbuild_new(mappedfile, st.st_size, count, &delta);
14601b5d 1109 maxpathlen = trie_maxpathlen(mappedfile);
1110 buf = snewn(maxpathlen, char);
1111 prevbuf = snewn(maxpathlen, char);
444c684c 1112 tw = triewalk_new(mappedfile);
14601b5d 1113 prevbuf[0] = '\0';
1114 tf = triewalk_next(tw, buf);
1115 assert(tf);
1116 while (1) {
1117 int i;
1118
522edd92 1119 if (totalsize - indexbuild_realsize(ib) < delta) {
645dbd49 1120 const void *oldfile = mappedfile;
1121 ptrdiff_t diff;
1122
522edd92 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);
645dbd49 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);
522edd92 1156 }
1157
14601b5d 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 }
14601b5d 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
444c684c 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);
522edd92 1221 printf("Final index file size = %llu bytes\n",
50e82fdc 1222 (unsigned long long)realsize);
84849cbd 1223 }
444c684c 1224 } else if (mode == TEXT) {
1225 char *querydir = actions[action].arg;
1226 size_t pathlen;
70322ae3 1227
444c684c 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) {
bf53e756 1235 perror(PNAME ": fstat");
444c684c 1236 return 1;
1237 }
1238 totalsize = st.st_size;
1239 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
1240 if (!mappedfile) {
bf53e756 1241 perror(PNAME ": mmap");
444c684c 1242 return 1;
84849cbd 1243 }
444c684c 1244 pathsep = trie_pathsep(mappedfile);
70322ae3 1245
84849cbd 1246 /*
444c684c 1247 * Trim trailing slash, just in case.
84849cbd 1248 */
444c684c 1249 pathlen = strlen(querydir);
1250 if (pathlen > 0 && querydir[pathlen-1] == pathsep)
1251 querydir[--pathlen] = '\0';
1252
16139d21 1253 text_query(mappedfile, querydir, textcutoff, showfiles, tqdepth);
56cae6e1 1254
1255 munmap(mappedfile, totalsize);
444c684c 1256 } else if (mode == HTML) {
1257 char *querydir = actions[action].arg;
92d3b326 1258 size_t pathlen, maxpathlen;
1259 char *pathbuf;
444c684c 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 }
84849cbd 1270 if (fstat(fd, &st) < 0) {
bf53e756 1271 perror(PNAME ": fstat");
84849cbd 1272 return 1;
1273 }
444c684c 1274 totalsize = st.st_size;
1275 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
1276 if (!mappedfile) {
bf53e756 1277 perror(PNAME ": mmap");
444c684c 1278 return 1;
1279 }
1280 pathsep = trie_pathsep(mappedfile);
70322ae3 1281
92d3b326 1282 maxpathlen = trie_maxpathlen(mappedfile);
1283 pathbuf = snewn(maxpathlen, char);
1284
444c684c 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);
92d3b326 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;
16139d21 1310 cfg.showfiles = showfiles;
92d3b326 1311 html = html_query(mappedfile, xi, &cfg);
1312 fputs(html, stdout);
1313 }
56cae6e1 1314
1315 munmap(mappedfile, totalsize);
92d3b326 1316 sfree(pathbuf);
444c684c 1317 } else if (mode == DUMP) {
1318 size_t maxpathlen;
1319 char *buf;
70322ae3 1320
444c684c 1321 fd = open(filename, O_RDONLY);
1322 if (fd < 0) {
1323 fprintf(stderr, "%s: %s: open: %s\n", PNAME, filename,
1324 strerror(errno));
84849cbd 1325 return 1;
1326 }
444c684c 1327 if (fstat(fd, &st) < 0) {
bf53e756 1328 perror(PNAME ": fstat");
84849cbd 1329 return 1;
1330 }
444c684c 1331 totalsize = st.st_size;
1332 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
84849cbd 1333 if (!mappedfile) {
bf53e756 1334 perror(PNAME ": mmap");
84849cbd 1335 return 1;
1336 }
444c684c 1337 pathsep = trie_pathsep(mappedfile);
1338
1339 maxpathlen = trie_maxpathlen(mappedfile);
1340 buf = snewn(maxpathlen, char);
84849cbd 1341
bf53e756 1342 printf(DUMPHDR "%02x\n", (unsigned char)pathsep);
84849cbd 1343 tw = triewalk_new(mappedfile);
444c684c 1344 while ((tf = triewalk_next(tw, buf)) != NULL)
1345 dump_line(buf, tf);
84849cbd 1346 triewalk_free(tw);
56cae6e1 1347
1348 munmap(mappedfile, totalsize);
444c684c 1349 } else if (mode == HTTPD) {
1350 struct html_config pcfg;
1351 struct httpd_config dcfg;
70322ae3 1352
444c684c 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) {
bf53e756 1360 perror(PNAME ": fstat");
444c684c 1361 return 1;
1362 }
1363 totalsize = st.st_size;
1364 mappedfile = mmap(NULL, totalsize, PROT_READ, MAP_SHARED, fd, 0);
1365 if (!mappedfile) {
bf53e756 1366 perror(PNAME ": mmap");
444c684c 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;
16139d21 1378 pcfg.showfiles = showfiles;
444c684c 1379 run_httpd(mappedfile, auth, &dcfg, &pcfg);
56cae6e1 1380 munmap(mappedfile, totalsize);
355c3af7 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 }
70322ae3 1387 }
70322ae3 1388 }
1389
1390 return 0;
1391}