7a9b164e5f7161e121c9fef26394d2f4af4ed92f
2 * Main program for agedu.
16 #include <sys/types.h>
20 #include <sys/ioctl.h>
33 * Path separator. This global variable affects the behaviour of
34 * various parts of the code when they need to deal with path
35 * separators. The path separator appropriate to a particular data
36 * set is encoded in the index file storing that data set; data
37 * sets generated on Unix will of course have the default '/', but
38 * foreign data sets are conceivable and must be handled correctly.
42 void fatal(const char *fmt
, ...)
45 fprintf(stderr
, "%s: ", PNAME
);
47 vfprintf(stderr
, fmt
, ap
);
49 fprintf(stderr
, "\n");
53 struct inclusion_exclusion
{
61 dev_t datafile_dev
, filesystem_dev
;
63 time_t last_output_update
;
64 int progress
, progwidth
;
66 struct inclusion_exclusion
*inex
;
72 static void dump_line(const char *pathname
, const struct trie_file
*tf
)
75 printf("%llu %llu ", tf
->size
, tf
->atime
);
76 for (p
= pathname
; *p
; p
++) {
77 if (*p
>= ' ' && *p
< 127 && *p
!= '%')
80 printf("%%%02x", (unsigned char)*p
);
85 static int gotdata(void *vctx
, const char *pathname
, const struct stat64
*st
)
87 struct ctx
*ctx
= (struct ctx
*)vctx
;
88 struct trie_file file
;
94 * Filter out our own data file.
96 if (st
->st_dev
== ctx
->datafile_dev
&& st
->st_ino
== ctx
->datafile_ino
)
100 * Don't cross the streams^W^Wany file system boundary.
102 if (!ctx
->crossfs
&& st
->st_dev
!= ctx
->filesystem_dev
)
105 file
.size
= (unsigned long long)512 * st
->st_blocks
;
106 if (ctx
->fakeatimes
&& S_ISDIR(st
->st_mode
))
107 file
.atime
= st
->st_mtime
;
109 file
.atime
= st
->st_atime
;
112 * Filter based on wildcards.
115 filename
= strrchr(pathname
, pathsep
);
120 for (i
= 0; i
< ctx
->ninex
; i
++) {
121 if (fnmatch(ctx
->inex
[i
].wildcard
,
122 ctx
->inex
[i
].path ? pathname
: filename
, 0) == 0)
123 include
= ctx
->inex
[i
].type
;
126 return 0; /* ignore this entry and any subdirs */
129 * Here we are supposed to be filtering an entry out, but
130 * still recursing into it if it's a directory. However,
131 * we can't actually leave out any directory whose
132 * subdirectories we then look at. So we cheat, in that
133 * case, by setting the size to zero.
135 if (!S_ISDIR(st
->st_mode
))
136 return 0; /* just ignore */
141 if (ctx
->straight_to_dump
)
142 dump_line(pathname
, &file
);
144 triebuild_add(ctx
->tb
, pathname
, &file
);
148 if (t
!= ctx
->last_output_update
) {
149 fprintf(stderr
, "%-*.*s\r", ctx
->progwidth
, ctx
->progwidth
,
152 ctx
->last_output_update
= t
;
159 static void text_query(const void *mappedfile
, const char *querydir
,
164 unsigned long xi1
, xi2
;
165 unsigned long long s1
, s2
;
167 maxpathlen
= trie_maxpathlen(mappedfile
);
168 pathbuf
= snewn(maxpathlen
+ 1, char);
171 * We want to query everything between the supplied filename
172 * (inclusive) and that filename with a ^A on the end
173 * (exclusive). So find the x indices for each.
175 strcpy(pathbuf
, querydir
);
176 make_successor(pathbuf
);
177 xi1
= trie_before(mappedfile
, querydir
);
178 xi2
= trie_before(mappedfile
, pathbuf
);
181 return; /* file, or empty dir => no display */
184 * Now do the lookups in the age index.
186 s1
= index_query(mappedfile
, xi1
, t
);
187 s2
= index_query(mappedfile
, xi2
, t
);
190 return; /* no space taken up => no display */
194 * Now scan for first-level subdirectories and report
199 trie_getpath(mappedfile
, xi1
, pathbuf
);
200 text_query(mappedfile
, pathbuf
, t
, depth
-1);
201 make_successor(pathbuf
);
202 xi1
= trie_before(mappedfile
, pathbuf
);
206 /* Display in units of 1Kb */
207 printf("%-11llu %s\n", (s2
- s1
) / 1024, querydir
);
211 * Largely frivolous way to define all my command-line options. I
212 * present here a parametric macro which declares a series of
213 * _logical_ option identifiers, and for each one declares zero or
214 * more short option characters and zero or more long option
215 * words. Then I repeatedly invoke that macro with its arguments
216 * defined to be various other macros, which allows me to
219 * - define an enum allocating a distinct integer value to each
221 * - define a string consisting of precisely all the short option
223 * - define a string array consisting of all the long option
225 * - define (with help from auxiliary enums) integer arrays
226 * parallel to both of the above giving the logical option id
227 * for each physical short and long option
228 * - define an array indexed by logical option id indicating
229 * whether the option in question takes a value
230 * - define a function which prints out brief online help for all
233 * It's not at all clear to me that this trickery is actually
234 * particularly _efficient_ - it still, after all, requires going
235 * linearly through the option list at run time and doing a
236 * strcmp, whereas in an ideal world I'd have liked the lists of
237 * long and short options to be pre-sorted so that a binary search
238 * or some other more efficient lookup was possible. (Not that
239 * asymptotic algorithmic complexity is remotely vital in option
240 * parsing, but if I were doing this in, say, Lisp or something
241 * with an equivalently powerful preprocessor then once I'd had
242 * the idea of preparing the option-parsing data structures at
243 * compile time I would probably have made the effort to prepare
244 * them _properly_. I could have Perl generate me a source file
245 * from some sort of description, I suppose, but that would seem
246 * like overkill. And in any case, it's more of a challenge to
247 * achieve as much as possible by cunning use of cpp and enum than
248 * to just write some sensible and logical code in a Turing-
249 * complete language. I said it was largely frivolous :-)
251 * This approach does have the virtue that it brings together the
252 * option ids, option spellings and help text into a single
253 * combined list and defines them all in exactly one place. If I
254 * want to add a new option, or a new spelling for an option, I
255 * only have to modify the main OPTHELP macro below and then add
256 * code to process the new logical id.
258 * (Though, really, even that isn't ideal, since it still involves
259 * modifying the source file in more than one place. In a
260 * _properly_ ideal world, I'd be able to interleave the option
261 * definitions with the code fragments that process them. And then
262 * not bother defining logical identifiers for them at all - those
263 * would be automatically generated, since I wouldn't have any
264 * need to specify them manually in another part of the code.)
266 * One other helpful consequence of the enum-based structure here
267 * is that it causes a compiler error if I accidentally try to
268 * define the same option (short or long) twice.
271 #define OPTHELP(NOVAL, VAL, SHORT, LONG, HELPPFX, HELPARG, HELPLINE, HELPOPT) \
272 HELPPFX("usage") HELPLINE(PNAME " [options] action [action...]") \
274 VAL(SCAN) SHORT(s) LONG(scan) \
275 HELPARG("directory") HELPOPT("scan and index a directory") \
276 NOVAL(DUMP) SHORT(D) LONG(dump) HELPOPT("dump the index file on stdout") \
277 VAL(SCANDUMP) SHORT(S) LONG(scan_dump) \
278 HELPARG("directory") HELPOPT("scan only, generating a dump") \
279 NOVAL(LOAD) SHORT(L) LONG(load) \
280 HELPOPT("load and index a dump file") \
281 VAL(TEXT) SHORT(t) LONG(text) \
282 HELPARG("subdir") HELPOPT("print a plain text report on a subdirectory") \
283 VAL(HTML) SHORT(H) LONG(html) \
284 HELPARG("subdir") HELPOPT("print an HTML report on a subdirectory") \
285 NOVAL(HTTPD) SHORT(w) LONG(web) LONG(server) LONG(httpd) \
286 HELPOPT("serve HTML reports from a temporary web server") \
288 VAL(DATAFILE) SHORT(f) LONG(file) \
289 HELPARG("filename") HELPOPT("[most modes] specify index file") \
290 NOVAL(PROGRESS) LONG(progress) LONG(scan_progress) \
291 HELPOPT("[--scan] report progress on stderr") \
292 NOVAL(NOPROGRESS) LONG(no_progress) LONG(no_scan_progress) \
293 HELPOPT("[--scan] do not report progress") \
294 NOVAL(TTYPROGRESS) LONG(tty_progress) LONG(tty_scan_progress) \
295 LONG(progress_tty) LONG(scan_progress_tty) \
296 HELPOPT("[--scan] report progress if stderr is a tty") \
297 NOVAL(CROSSFS) LONG(cross_fs) \
298 HELPOPT("[--scan] cross filesystem boundaries") \
299 NOVAL(NOCROSSFS) LONG(no_cross_fs) \
300 HELPOPT("[--scan] stick to one filesystem") \
301 VAL(INCLUDE) LONG(include) \
302 HELPARG("wildcard") HELPOPT("[--scan] include files matching pattern") \
303 VAL(INCLUDEPATH) LONG(include_path) \
304 HELPARG("wildcard") HELPOPT("[--scan] include pathnames matching pattern") \
305 VAL(EXCLUDE) LONG(exclude) \
306 HELPARG("wildcard") HELPOPT("[--scan] exclude files matching pattern") \
307 VAL(EXCLUDEPATH) LONG(exclude_path) \
308 HELPARG("wildcard") HELPOPT("[--scan] exclude pathnames matching pattern") \
309 VAL(PRUNE) LONG(prune) \
310 HELPARG("wildcard") HELPOPT("[--scan] prune files matching pattern") \
311 VAL(PRUNEPATH) LONG(prune_path) \
312 HELPARG("wildcard") HELPOPT("[--scan] prune pathnames matching pattern") \
313 NOVAL(DIRATIME) LONG(dir_atime) LONG(dir_atimes) \
314 HELPOPT("[--scan] keep real atimes on directories") \
315 NOVAL(NODIRATIME) LONG(no_dir_atime) LONG(no_dir_atimes) \
316 HELPOPT("[--scan] fake atimes on directories") \
317 VAL(TQDEPTH) SHORT(d) LONG(depth) LONG(max_depth) LONG(maximum_depth) \
318 HELPARG("levels") HELPOPT("[--text] recurse to this many levels") \
319 VAL(MINAGE) SHORT(a) LONG(age) LONG(min_age) LONG(minimum_age) \
320 HELPARG("age") HELPOPT("[--text] include only files older than this") \
321 VAL(AGERANGE) SHORT(r) LONG(age_range) LONG(range) LONG(ages) \
322 HELPARG("age[-age]") HELPOPT("[--html,--web] set limits of colour coding") \
323 VAL(SERVERADDR) LONG(address) LONG(addr) LONG(server_address) \
325 HELPARG("addr[:port]") HELPOPT("[--web] specify HTTP server address") \
326 VAL(AUTH) LONG(auth) LONG(http_auth) LONG(httpd_auth) \
327 LONG(server_auth) LONG(web_auth) \
328 HELPARG("type") HELPOPT("[--web] specify HTTP authentication method") \
329 VAL(AUTHFILE) LONG(auth_file) \
330 HELPARG("filename") HELPOPT("[--web] read HTTP Basic user/pass from file") \
331 VAL(AUTHFD) LONG(auth_fd) \
332 HELPARG("fd") HELPOPT("[--web] read HTTP Basic user/pass from fd") \
334 NOVAL(HELP) SHORT(h) LONG(help) HELPOPT("display this help text") \
335 NOVAL(VERSION) SHORT(V) LONG(version) HELPOPT("report version number") \
336 NOVAL(LICENCE) LONG(licence) LONG(license) \
337 HELPOPT("display (MIT) licence text") \
340 #define DEFENUM(x) OPT_ ## x,
343 #define STRING(x) #x ,
344 #define STRINGNOCOMMA(x) #x
345 #define SHORTNEWOPT(x) SHORTtmp_ ## x = OPT_ ## x,
346 #define SHORTTHISOPT(x) SHORTtmp2_ ## x, SHORTVAL_ ## x = SHORTtmp2_ ## x - 1,
347 #define SHORTOPTVAL(x) SHORTVAL_ ## x,
348 #define SHORTTMP(x) SHORTtmp3_ ## x,
349 #define LONGNEWOPT(x) LONGtmp_ ## x = OPT_ ## x,
350 #define LONGTHISOPT(x) LONGtmp2_ ## x, LONGVAL_ ## x = LONGtmp2_ ## x - 1,
351 #define LONGOPTVAL(x) LONGVAL_ ## x,
352 #define LONGTMP(x) SHORTtmp3_ ## x,
354 #define OPTIONS(NOVAL, VAL, SHORT, LONG) \
355 OPTHELP(NOVAL, VAL, SHORT, LONG, IGNORE, IGNORE, IGNORE, IGNORE)
357 enum { OPTIONS(DEFENUM
,DEFENUM
,IGNORE
,IGNORE
) NOPTIONS
};
358 enum { OPTIONS(IGNORE
,IGNORE
,SHORTTMP
,IGNORE
) NSHORTOPTS
};
359 enum { OPTIONS(IGNORE
,IGNORE
,IGNORE
,LONGTMP
) NLONGOPTS
};
360 static const int opthasval
[NOPTIONS
] = {OPTIONS(ZERO
,ONE
,IGNORE
,IGNORE
)};
361 static const char shortopts
[] = {OPTIONS(IGNORE
,IGNORE
,STRINGNOCOMMA
,IGNORE
)};
362 static const char *const longopts
[] = {OPTIONS(IGNORE
,IGNORE
,IGNORE
,STRING
)};
363 enum { OPTIONS(SHORTNEWOPT
,SHORTNEWOPT
,SHORTTHISOPT
,IGNORE
) };
364 enum { OPTIONS(LONGNEWOPT
,LONGNEWOPT
,IGNORE
,LONGTHISOPT
) };
365 static const int shortvals
[] = {OPTIONS(IGNORE
,IGNORE
,SHORTOPTVAL
,IGNORE
)};
366 static const int longvals
[] = {OPTIONS(IGNORE
,IGNORE
,IGNORE
,LONGOPTVAL
)};
368 static void usage(FILE *fp
)
371 const char *prefix
, *shortopt
, *longopt
, *optarg
;
374 #define HELPRESET prefix = shortopt = longopt = optarg = NULL, optex = -1
375 #define HELPNOVAL(s) optex = 0;
376 #define HELPVAL(s) optex = 1;
377 #define HELPSHORT(s) if (!shortopt) shortopt = "-" #s;
378 #define HELPLONG(s) if (!longopt) { \
379 strcpy(longbuf, "--" #s); longopt = longbuf; \
380 for (i = 0; longbuf[i]; i++) if (longbuf[i] == '_') longbuf[i] = '-'; }
381 #define HELPPFX(s) prefix = s;
382 #define HELPARG(s) optarg = s;
383 #define HELPLINE(s) assert(optex == -1); \
384 fprintf(fp, "%7s%c %s\n", prefix?prefix:"", prefix?':':' ', s); \
386 #define HELPOPT(s) assert((optex == 1 && optarg) || (optex == 0 && !optarg)); \
387 assert(shortopt || longopt); \
388 i = fprintf(fp, "%7s%c %s%s%s%s%s", prefix?prefix:"", prefix?':':' ', \
389 shortopt?shortopt:"", shortopt&&longopt?", ":"", longopt?longopt:"", \
390 optarg?" ":"", optarg?optarg:""); \
391 fprintf(fp, "%*s %s\n", i<32?32-i:0,"",s); HELPRESET;
394 OPTHELP(HELPNOVAL
, HELPVAL
, HELPSHORT
, HELPLONG
,
395 HELPPFX
, HELPARG
, HELPLINE
, HELPOPT
);
408 static time_t parse_age(time_t now
, const char *agestr
)
417 if (2 != sscanf(agestr
, "%d%1[DdWwMmYy]", &nunits
, unit
)) {
418 fprintf(stderr
, "%s: age specification should be a number followed by"
419 " one of d,w,m,y\n", PNAME
);
423 if (unit
[0] == 'd') {
425 } else if (unit
[0] == 'w') {
426 t
-= 86400 * 7 * nunits
;
431 ym
= tm
.tm_year
* 12 + tm
.tm_mon
;
438 tm
.tm_year
= ym
/ 12;
447 int main(int argc
, char **argv
)
450 struct ctx actx
, *ctx
= &actx
;
452 off_t totalsize
, realsize
;
456 const struct trie_file
*tf
;
457 char *filename
= PNAME
".dat";
459 enum { TEXT
, HTML
, SCAN
, DUMP
, SCANDUMP
, LOAD
, HTTPD
};
464 int nactions
= 0, actionsize
= 0, action
;
465 time_t now
= time(NULL
);
466 time_t textcutoff
= now
, htmlnewest
= now
, htmloldest
= now
;
467 int htmlautoagerange
= 1;
468 const char *httpserveraddr
= NULL
;
469 int httpserverport
= 0;
470 const char *httpauthdata
= NULL
;
471 int auth
= HTTPD_AUTH_MAGIC
| HTTPD_AUTH_BASIC
;
473 struct inclusion_exclusion
*inex
= NULL
;
474 int ninex
= 0, inexsize
= 0;
477 int fakediratimes
= 1;
479 #ifdef DEBUG_MAD_OPTION_PARSING_MACROS
481 static const char *const optnames
[NOPTIONS
] = {
482 OPTIONS(STRING
,STRING
,IGNORE
,IGNORE
)
485 for (i
= 0; i
< NSHORTOPTS
; i
++)
486 printf("-%c == %s [%s]\n", shortopts
[i
], optnames
[shortvals
[i
]],
487 opthasval
[shortvals
[i
]] ?
"value" : "no value");
488 for (i
= 0; i
< NLONGOPTS
; i
++)
489 printf("--%s == %s [%s]\n", longopts
[i
], optnames
[longvals
[i
]],
490 opthasval
[longvals
[i
]] ?
"value" : "no value");
497 if (doing_opts
&& *p
== '-') {
500 if (!strcmp(p
, "--")) {
511 if (wordstart
&& *p
== '-') {
513 * GNU-style long option.
516 optval
= strchr(p
, '=');
520 for (i
= 0; i
< NLONGOPTS
; i
++) {
521 const char *opt
= longopts
[i
], *s
= p
;
524 * The underscores in the option names
525 * defined above may be given by the user
526 * as underscores or dashes, or omitted
531 if (*s
== '-' || *s
== '_')
549 fprintf(stderr
, "%s: unrecognised option '--%s'\n",
554 if (!opthasval
[optid
]) {
556 fprintf(stderr
, "%s: unexpected argument to option"
557 " '--%s'\n", PNAME
, p
);
565 fprintf(stderr
, "%s: option '--%s' expects"
566 " an argument\n", PNAME
, p
);
572 p
+= strlen(p
); /* finished with this argument word */
579 for (i
= 0; i
< NSHORTOPTS
; i
++)
580 if (c
== shortopts
[i
]) {
581 optid
= shortvals
[i
];
586 fprintf(stderr
, "%s: unrecognised option '-%c'\n",
591 if (opthasval
[optid
]) {
595 } else if (--argc
> 0) {
598 fprintf(stderr
, "%s: option '-%c' expects"
599 " an argument\n", PNAME
, c
);
610 * Now actually process the option.
617 printf("FIXME: version();\n");
621 extern const char *const licence
[];
624 for (i
= 0; licence
[i
]; i
++)
625 fputs(licence
[i
], stdout
);
631 if (nactions
>= actionsize
) {
632 actionsize
= nactions
* 3 / 2 + 16;
633 actions
= sresize(actions
, actionsize
, struct action
);
635 actions
[nactions
].mode
= SCAN
;
636 actions
[nactions
].arg
= optval
;
640 if (nactions
>= actionsize
) {
641 actionsize
= nactions
* 3 / 2 + 16;
642 actions
= sresize(actions
, actionsize
, struct action
);
644 actions
[nactions
].mode
= SCANDUMP
;
645 actions
[nactions
].arg
= optval
;
649 if (nactions
>= actionsize
) {
650 actionsize
= nactions
* 3 / 2 + 16;
651 actions
= sresize(actions
, actionsize
, struct action
);
653 actions
[nactions
].mode
= DUMP
;
654 actions
[nactions
].arg
= NULL
;
658 if (nactions
>= actionsize
) {
659 actionsize
= nactions
* 3 / 2 + 16;
660 actions
= sresize(actions
, actionsize
, struct action
);
662 actions
[nactions
].mode
= LOAD
;
663 actions
[nactions
].arg
= NULL
;
667 if (nactions
>= actionsize
) {
668 actionsize
= nactions
* 3 / 2 + 16;
669 actions
= sresize(actions
, actionsize
, struct action
);
671 actions
[nactions
].mode
= TEXT
;
672 actions
[nactions
].arg
= optval
;
676 if (nactions
>= actionsize
) {
677 actionsize
= nactions
* 3 / 2 + 16;
678 actions
= sresize(actions
, actionsize
, struct action
);
680 actions
[nactions
].mode
= HTML
;
681 actions
[nactions
].arg
= optval
;
685 if (nactions
>= actionsize
) {
686 actionsize
= nactions
* 3 / 2 + 16;
687 actions
= sresize(actions
, actionsize
, struct action
);
689 actions
[nactions
].mode
= HTTPD
;
690 actions
[nactions
].arg
= NULL
;
699 case OPT_TTYPROGRESS
:
718 tqdepth
= atoi(optval
);
721 textcutoff
= parse_age(now
, optval
);
724 if (!strcmp(optval
, "auto")) {
725 htmlautoagerange
= 1;
727 char *q
= optval
+ strcspn(optval
, "-:");
730 htmloldest
= parse_age(now
, optval
);
731 htmlnewest
= *q ?
parse_age(now
, q
) : now
;
732 htmlautoagerange
= 0;
738 if (optval
[0] == '[' &&
739 (port
= strchr(optval
, ']')) != NULL
)
743 port
+= strcspn(port
, ":");
746 httpserveraddr
= optval
;
747 httpserverport
= atoi(port
);
751 if (!strcmp(optval
, "magic"))
752 auth
= HTTPD_AUTH_MAGIC
;
753 else if (!strcmp(optval
, "basic"))
754 auth
= HTTPD_AUTH_BASIC
;
755 else if (!strcmp(optval
, "none"))
756 auth
= HTTPD_AUTH_NONE
;
757 else if (!strcmp(optval
, "default"))
758 auth
= HTTPD_AUTH_MAGIC
| HTTPD_AUTH_BASIC
;
759 else if (!strcmp(optval
, "help") ||
760 !strcmp(optval
, "list")) {
761 printf(PNAME
": supported HTTP authentication types"
763 " magic use Linux /proc/net/tcp to"
764 " determine owner of peer socket\n"
765 " basic HTTP Basic username and"
766 " password authentication\n"
767 " default use 'magic' if possible, "
768 " otherwise fall back to 'basic'\n"
769 " none unauthenticated HTTP (if"
770 " the data file is non-confidential)\n");
773 fprintf(stderr
, "%s: unrecognised authentication"
774 " type '%s'\n%*s options are 'magic',"
775 " 'basic', 'none', 'default'\n",
776 PNAME
, optval
, (int)strlen(PNAME
), "");
787 int authlen
, authsize
;
790 if (optid
== OPT_AUTHFILE
) {
791 fd
= open(optval
, O_RDONLY
);
793 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
,
794 optval
, strerror(errno
));
801 sprintf(namebuf
, "fd %d", fd
);
806 authbuf
= snewn(authsize
, char);
807 while ((ret
= read(fd
, authbuf
+authlen
,
808 authsize
-authlen
)) > 0) {
810 if ((authsize
- authlen
) < (authsize
/ 16)) {
811 authsize
= authlen
* 3 / 2 + 4096;
812 authbuf
= sresize(authbuf
, authsize
, char);
816 fprintf(stderr
, "%s: %s: read: %s\n", PNAME
,
817 name
, strerror(errno
));
820 if (optid
== OPT_AUTHFILE
)
822 httpauthdata
= authbuf
;
826 case OPT_INCLUDEPATH
:
828 case OPT_EXCLUDEPATH
:
831 if (ninex
>= inexsize
) {
832 inexsize
= ninex
* 3 / 2 + 16;
833 inex
= sresize(inex
, inexsize
,
834 struct inclusion_exclusion
);
836 inex
[ninex
].path
= (optid
== OPT_INCLUDEPATH
||
837 optid
== OPT_EXCLUDEPATH
||
838 optid
== OPT_PRUNEPATH
);
839 inex
[ninex
].type
= (optid
== OPT_INCLUDE ?
1 :
840 optid
== OPT_INCLUDEPATH ?
1 :
841 optid
== OPT_EXCLUDE ?
0 :
842 optid
== OPT_EXCLUDEPATH ?
0 :
843 optid
== OPT_PRUNE ?
-1 :
844 /* optid == OPT_PRUNEPATH ? */ -1);
845 inex
[ninex
].wildcard
= optval
;
851 fprintf(stderr
, "%s: unexpected argument '%s'\n", PNAME
, p
);
861 for (action
= 0; action
< nactions
; action
++) {
862 int mode
= actions
[action
].mode
;
864 if (mode
== SCAN
|| mode
== SCANDUMP
|| mode
== LOAD
) {
865 const char *scandir
= actions
[action
].arg
;
867 char *buf
= fgetline(stdin
);
869 buf
[strcspn(buf
, "\r\n")] = '\0';
870 if (1 != sscanf(buf
, DUMPHDR
"%x",
872 fprintf(stderr
, "%s: header in dump file not recognised\n",
876 pathsep
= (char)newpathsep
;
880 if (mode
== SCAN
|| mode
== LOAD
) {
882 * Prepare to write out the index file.
884 fd
= open(filename
, O_RDWR
| O_TRUNC
| O_CREAT
, S_IRWXU
);
886 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
890 if (fstat(fd
, &st
) < 0) {
891 perror(PNAME
": fstat");
894 ctx
->datafile_dev
= st
.st_dev
;
895 ctx
->datafile_ino
= st
.st_ino
;
896 ctx
->straight_to_dump
= 0;
898 ctx
->datafile_dev
= -1;
899 ctx
->datafile_ino
= -1;
900 ctx
->straight_to_dump
= 1;
903 if (mode
== SCAN
|| mode
== SCANDUMP
) {
904 if (stat(scandir
, &st
) < 0) {
905 fprintf(stderr
, "%s: %s: stat: %s\n", PNAME
, scandir
,
909 ctx
->filesystem_dev
= crossfs ?
0 : st
.st_dev
;
914 ctx
->crossfs
= crossfs
;
915 ctx
->fakeatimes
= fakediratimes
;
917 ctx
->last_output_update
= time(NULL
);
919 /* progress==1 means report progress only if stderr is a tty */
921 progress
= isatty(2) ?
2 : 0;
922 ctx
->progress
= progress
;
925 if (progress
&& ioctl(2, TIOCGWINSZ
, &ws
) == 0)
926 ctx
->progwidth
= ws
.ws_col
- 1;
931 if (mode
== SCANDUMP
)
932 printf(DUMPHDR
"%02x\n", (unsigned char)pathsep
);
935 * Scan the directory tree, and write out the trie component
938 if (mode
!= SCANDUMP
) {
939 ctx
->tb
= triebuild_new(fd
);
944 while ((buf
= fgetline(stdin
)) != NULL
) {
948 buf
[strcspn(buf
, "\r\n")] = '\0';
952 while (*p
&& *p
!= ' ') p
++;
954 fprintf(stderr
, "%s: dump file line %d: expected at least"
955 " three fields\n", PNAME
, line
);
959 tf
.size
= strtoull(q
, NULL
, 10);
961 while (*p
&& *p
!= ' ') p
++;
963 fprintf(stderr
, "%s: dump file line %d: expected at least"
964 " three fields\n", PNAME
, line
);
968 tf
.atime
= strtoull(q
, NULL
, 10);
976 for (i
= 0; i
< 2; i
++) {
978 if (*p
>= '0' && *p
<= '9')
980 else if (*p
>= 'A' && *p
<= 'F')
981 c
+= *p
- ('A' - 10);
982 else if (*p
>= 'a' && *p
<= 'f')
983 c
+= *p
- ('a' - 10);
985 fprintf(stderr
, "%s: dump file line %d: unable"
986 " to parse hex escape\n", PNAME
, line
);
995 triebuild_add(ctx
->tb
, buf
, &tf
);
1000 du(scandir
, gotdata
, ctx
);
1002 if (mode
!= SCANDUMP
) {
1003 count
= triebuild_finish(ctx
->tb
);
1004 triebuild_free(ctx
->tb
);
1006 if (ctx
->progress
) {
1007 fprintf(stderr
, "%-*s\r", ctx
->progwidth
, "");
1012 * Work out how much space the cumulative index trees
1013 * will take; enlarge the file, and memory-map it.
1015 if (fstat(fd
, &st
) < 0) {
1016 perror(PNAME
": fstat");
1020 printf("Built pathname index, %d entries, %ju bytes\n", count
,
1021 (intmax_t)st
.st_size
);
1023 totalsize
= index_compute_size(st
.st_size
, count
);
1025 if (lseek(fd
, totalsize
-1, SEEK_SET
) < 0) {
1026 perror(PNAME
": lseek");
1029 if (write(fd
, "\0", 1) < 1) {
1030 perror(PNAME
": write");
1034 printf("Upper bound on index file size = %ju bytes\n",
1035 (intmax_t)totalsize
);
1037 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
|PROT_WRITE
,MAP_SHARED
, fd
, 0);
1039 perror(PNAME
": mmap");
1043 if (fakediratimes
) {
1044 printf("Faking directory atimes\n");
1045 trie_fake_dir_atimes(mappedfile
);
1048 printf("Building index\n");
1049 ib
= indexbuild_new(mappedfile
, st
.st_size
, count
);
1050 tw
= triewalk_new(mappedfile
);
1051 while ((tf
= triewalk_next(tw
, NULL
)) != NULL
)
1052 indexbuild_add(ib
, tf
);
1054 realsize
= indexbuild_realsize(ib
);
1055 indexbuild_free(ib
);
1057 munmap(mappedfile
, totalsize
);
1058 ftruncate(fd
, realsize
);
1060 printf("Actual index file size = %ju bytes\n", (intmax_t)realsize
);
1062 } else if (mode
== TEXT
) {
1063 char *querydir
= actions
[action
].arg
;
1066 fd
= open(filename
, O_RDONLY
);
1068 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
1072 if (fstat(fd
, &st
) < 0) {
1073 perror(PNAME
": fstat");
1076 totalsize
= st
.st_size
;
1077 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
1079 perror(PNAME
": mmap");
1082 pathsep
= trie_pathsep(mappedfile
);
1085 * Trim trailing slash, just in case.
1087 pathlen
= strlen(querydir
);
1088 if (pathlen
> 0 && querydir
[pathlen
-1] == pathsep
)
1089 querydir
[--pathlen
] = '\0';
1091 text_query(mappedfile
, querydir
, textcutoff
, tqdepth
);
1092 } else if (mode
== HTML
) {
1093 char *querydir
= actions
[action
].arg
;
1095 struct html_config cfg
;
1099 fd
= open(filename
, O_RDONLY
);
1101 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
1105 if (fstat(fd
, &st
) < 0) {
1106 perror(PNAME
": fstat");
1109 totalsize
= st
.st_size
;
1110 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
1112 perror(PNAME
": mmap");
1115 pathsep
= trie_pathsep(mappedfile
);
1118 * Trim trailing slash, just in case.
1120 pathlen
= strlen(querydir
);
1121 if (pathlen
> 0 && querydir
[pathlen
-1] == pathsep
)
1122 querydir
[--pathlen
] = '\0';
1124 xi
= trie_before(mappedfile
, querydir
);
1126 cfg
.autoage
= htmlautoagerange
;
1127 cfg
.oldest
= htmloldest
;
1128 cfg
.newest
= htmlnewest
;
1129 html
= html_query(mappedfile
, xi
, &cfg
);
1130 fputs(html
, stdout
);
1131 } else if (mode
== DUMP
) {
1135 fd
= open(filename
, O_RDONLY
);
1137 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
1141 if (fstat(fd
, &st
) < 0) {
1142 perror(PNAME
": fstat");
1145 totalsize
= st
.st_size
;
1146 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
1148 perror(PNAME
": mmap");
1151 pathsep
= trie_pathsep(mappedfile
);
1153 maxpathlen
= trie_maxpathlen(mappedfile
);
1154 buf
= snewn(maxpathlen
, char);
1156 printf(DUMPHDR
"%02x\n", (unsigned char)pathsep
);
1157 tw
= triewalk_new(mappedfile
);
1158 while ((tf
= triewalk_next(tw
, buf
)) != NULL
)
1161 } else if (mode
== HTTPD
) {
1162 struct html_config pcfg
;
1163 struct httpd_config dcfg
;
1165 fd
= open(filename
, O_RDONLY
);
1167 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
1171 if (fstat(fd
, &st
) < 0) {
1172 perror(PNAME
": fstat");
1175 totalsize
= st
.st_size
;
1176 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
1178 perror(PNAME
": mmap");
1181 pathsep
= trie_pathsep(mappedfile
);
1183 dcfg
.address
= httpserveraddr
;
1184 dcfg
.port
= httpserverport
;
1185 dcfg
.basicauthdata
= httpauthdata
;
1187 pcfg
.autoage
= htmlautoagerange
;
1188 pcfg
.oldest
= htmloldest
;
1189 pcfg
.newest
= htmlnewest
;
1190 run_httpd(mappedfile
, auth
, &dcfg
, &pcfg
);