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 |
33 | void 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 |
44 | struct inclusion_exclusion { |
45 | int include; |
46 | const char *wildcard; |
47 | int path; |
48 | }; |
49 | |
70322ae3 |
50 | struct 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 | |
61 | static 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 |
116 | static 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 | |
254 | enum { OPTIONS(DEFENUM,DEFENUM,IGNORE,IGNORE) NOPTIONS }; |
255 | enum { OPTIONS(IGNORE,IGNORE,SHORTTMP,IGNORE) NSHORTOPTS }; |
256 | enum { OPTIONS(IGNORE,IGNORE,IGNORE,LONGTMP) NLONGOPTS }; |
257 | static const int opthasval[NOPTIONS] = {OPTIONS(ZERO,ONE,IGNORE,IGNORE)}; |
258 | static const char shortopts[] = {OPTIONS(IGNORE,IGNORE,STRINGNOCOMMA,IGNORE)}; |
259 | static const char *const longopts[] = {OPTIONS(IGNORE,IGNORE,IGNORE,STRING)}; |
260 | enum { OPTIONS(SHORTNEWOPT,SHORTNEWOPT,SHORTTHISOPT,IGNORE) }; |
261 | enum { OPTIONS(LONGNEWOPT,LONGNEWOPT,IGNORE,LONGTHISOPT) }; |
262 | static const int shortvals[] = {OPTIONS(IGNORE,IGNORE,SHORTOPTVAL,IGNORE)}; |
263 | static const int longvals[] = {OPTIONS(IGNORE,IGNORE,IGNORE,LONGOPTVAL)}; |
264 | |
70322ae3 |
265 | int 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 | } |