2b6af0caae46fffcd5a9449c93c8fff84b527013
2 * Main program for agedu.
15 #include <sys/types.h>
19 #include <sys/ioctl.h>
31 void fatal(const char *fmt
, ...)
34 fprintf(stderr
, "%s: ", PNAME
);
36 vfprintf(stderr
, fmt
, ap
);
38 fprintf(stderr
, "\n");
42 struct inclusion_exclusion
{
50 dev_t datafile_dev
, filesystem_dev
;
52 time_t last_output_update
;
53 int progress
, progwidth
;
54 struct inclusion_exclusion
*inex
;
59 static int gotdata(void *vctx
, const char *pathname
, const struct stat64
*st
)
61 struct ctx
*ctx
= (struct ctx
*)vctx
;
62 struct trie_file file
;
68 * Filter out our own data file.
70 if (st
->st_dev
== ctx
->datafile_dev
&& st
->st_ino
== ctx
->datafile_ino
)
74 * Don't cross the streams^W^Wany file system boundary.
76 if (!ctx
->crossfs
&& st
->st_dev
!= ctx
->filesystem_dev
)
80 * Filter based on wildcards.
83 filename
= strrchr(pathname
, '/');
88 for (i
= 0; i
< ctx
->ninex
; i
++) {
89 if (fnmatch(ctx
->inex
[i
].wildcard
,
90 ctx
->inex
[i
].path ? pathname
: filename
,
92 include
= ctx
->inex
[i
].include
;
95 return 1; /* filter, but don't prune */
97 file
.blocks
= st
->st_blocks
;
98 file
.atime
= st
->st_atime
;
99 triebuild_add(ctx
->tb
, pathname
, &file
);
102 if (t
!= ctx
->last_output_update
) {
104 fprintf(stderr
, "%-*.*s\r", ctx
->progwidth
, ctx
->progwidth
,
108 ctx
->last_output_update
= t
;
114 static void text_query(const void *mappedfile
, const char *rootdir
,
119 unsigned long xi1
, xi2
;
120 unsigned long long s1
, s2
;
122 maxpathlen
= trie_maxpathlen(mappedfile
);
123 pathbuf
= snewn(maxpathlen
+ 1, char);
126 * We want to query everything between the supplied filename
127 * (inclusive) and that filename with a ^A on the end
128 * (exclusive). So find the x indices for each.
130 sprintf(pathbuf
, "%s\001", rootdir
);
131 xi1
= trie_before(mappedfile
, rootdir
);
132 xi2
= trie_before(mappedfile
, pathbuf
);
135 * Now do the lookups in the age index.
137 s1
= index_query(mappedfile
, xi1
, t
);
138 s2
= index_query(mappedfile
, xi2
, t
);
140 /* Display in units of 2 512-byte blocks = 1Kb */
141 printf("%-11llu %s\n", (s2
- s1
) / 2, rootdir
);
145 * Now scan for first-level subdirectories and report
150 trie_getpath(mappedfile
, xi1
, pathbuf
);
151 text_query(mappedfile
, pathbuf
, t
, depth
-1);
152 strcat(pathbuf
, "\001");
153 xi1
= trie_before(mappedfile
, pathbuf
);
158 int main(int argc
, char **argv
)
161 struct ctx actx
, *ctx
= &actx
;
163 off_t totalsize
, realsize
;
167 const struct trie_file
*tf
;
168 char *filename
= "agedu.dat";
169 char *rootdir
= NULL
;
171 enum { USAGE
, TEXT
, HTML
, SCAN
, DUMP
, HTTPD
} mode
= USAGE
;
173 int auth
= HTTPD_AUTH_MAGIC
| HTTPD_AUTH_BASIC
;
175 struct inclusion_exclusion
*inex
= NULL
;
176 int ninex
= 0, inexsize
= 0;
183 if (doing_opts
&& *p
== '-') {
184 if (!strcmp(p
, "--")) {
186 } else if (p
[1] == '-') {
187 char *optval
= strchr(p
, '=');
190 if (!strcmp(p
, "--help")) {
191 printf("FIXME: usage();\n");
193 } else if (!strcmp(p
, "--version")) {
194 printf("FIXME: version();\n");
196 } else if (!strcmp(p
, "--licence") ||
197 !strcmp(p
, "--license")) {
198 printf("FIXME: licence();\n");
200 } else if (!strcmp(p
, "--scan")) {
202 } else if (!strcmp(p
, "--dump")) {
204 } else if (!strcmp(p
, "--text")) {
206 } else if (!strcmp(p
, "--html")) {
208 } else if (!strcmp(p
, "--httpd") ||
209 !strcmp(p
, "--server")) {
211 } else if (!strcmp(p
, "--progress") ||
212 !strcmp(p
, "--scan-progress")) {
214 } else if (!strcmp(p
, "--no-progress") ||
215 !strcmp(p
, "--no-scan-progress")) {
217 } else if (!strcmp(p
, "--tty-progress") ||
218 !strcmp(p
, "--tty-scan-progress") ||
219 !strcmp(p
, "--progress-tty") ||
220 !strcmp(p
, "--scan-progress-tty")) {
222 } else if (!strcmp(p
, "--crossfs")) {
224 } else if (!strcmp(p
, "--no-crossfs")) {
226 } else if (!strcmp(p
, "--file") ||
227 !strcmp(p
, "--auth") ||
228 !strcmp(p
, "--http-auth") ||
229 !strcmp(p
, "--httpd-auth") ||
230 !strcmp(p
, "--server-auth") ||
231 !strcmp(p
, "--minimum-age") ||
232 !strcmp(p
, "--min-age") ||
233 !strcmp(p
, "--age") ||
234 !strcmp(p
, "--include") ||
235 !strcmp(p
, "--include-path") ||
236 !strcmp(p
, "--exclude") ||
237 !strcmp(p
, "--exclude-path")) {
239 * Long options requiring values.
245 fprintf(stderr
, "%s: option '%s' requires"
246 " an argument\n", PNAME
, p
);
250 if (!strcmp(p
, "--file")) {
252 } else if (!strcmp(p
, "--minimum-age") ||
253 !strcmp(p
, "--min-age") ||
254 !strcmp(p
, "--age")) {
256 } else if (!strcmp(p
, "--auth") ||
257 !strcmp(p
, "--http-auth") ||
258 !strcmp(p
, "--httpd-auth") ||
259 !strcmp(p
, "--server-auth")) {
260 if (!strcmp(optval
, "magic"))
261 auth
= HTTPD_AUTH_MAGIC
;
262 else if (!strcmp(optval
, "basic"))
263 auth
= HTTPD_AUTH_BASIC
;
264 else if (!strcmp(optval
, "none"))
265 auth
= HTTPD_AUTH_NONE
;
266 else if (!strcmp(optval
, "default"))
267 auth
= HTTPD_AUTH_MAGIC
| HTTPD_AUTH_BASIC
;
269 fprintf(stderr
, "%s: unrecognised authentication"
270 " type '%s'\n%*s options are 'magic',"
271 " 'basic', 'none', 'default'\n",
272 PNAME
, optval
, (int)strlen(PNAME
), "");
275 } else if (!strcmp(p
, "--include") ||
276 !strcmp(p
, "--include-path") ||
277 !strcmp(p
, "--exclude") ||
278 !strcmp(p
, "--exclude-path")) {
279 if (ninex
>= inexsize
) {
280 inexsize
= ninex
* 3 / 2 + 16;
281 inex
= sresize(inex
, inexsize
,
282 struct inclusion_exclusion
);
284 inex
[ninex
].path
= (!strcmp(p
, "--include-path") ||
285 !strcmp(p
, "--exclude-path"));
286 inex
[ninex
].include
= (!strcmp(p
, "--include") ||
287 !strcmp(p
, "--include-path"));
288 inex
[ninex
].wildcard
= optval
;
292 fprintf(stderr
, "%s: unrecognised option '%s'\n",
302 /* Options requiring arguments. */
308 } else if (--argc
> 0) {
311 fprintf(stderr
, "%s: option '-%c' requires"
312 " an argument\n", PNAME
, c
);
316 case 'f': /* data file name */
319 case 'a': /* maximum age */
328 fprintf(stderr
, "%s: unrecognised option '-%c'\n",
338 fprintf(stderr
, "%s: unexpected argument '%s'\n", PNAME
, p
);
348 printf("FIXME: usage();\n");
350 } else if (mode
== SCAN
) {
352 fd
= open(filename
, O_RDWR
| O_TRUNC
| O_CREAT
, S_IRWXU
);
354 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
359 if (stat(rootdir
, &st
) < 0) {
360 fprintf(stderr
, "%s: %s: stat: %s\n", PNAME
, rootdir
,
364 ctx
->filesystem_dev
= crossfs ?
0 : st
.st_dev
;
366 if (fstat(fd
, &st
) < 0) {
367 perror("agedu: fstat");
370 ctx
->datafile_dev
= st
.st_dev
;
371 ctx
->datafile_ino
= st
.st_ino
;
374 ctx
->crossfs
= crossfs
;
376 ctx
->last_output_update
= time(NULL
);
378 /* progress==1 means report progress only if stderr is a tty */
380 progress
= isatty(2) ?
2 : 0;
381 ctx
->progress
= progress
;
384 if (progress
&& ioctl(2, TIOCGWINSZ
, &ws
) == 0)
385 ctx
->progwidth
= ws
.ws_col
- 1;
391 * Scan the directory tree, and write out the trie component
394 ctx
->tb
= triebuild_new(fd
);
395 du(rootdir
, gotdata
, ctx
);
396 count
= triebuild_finish(ctx
->tb
);
397 triebuild_free(ctx
->tb
);
400 fprintf(stderr
, "%-*s\r", ctx
->progwidth
, "");
405 * Work out how much space the cumulative index trees will
406 * take; enlarge the file, and memory-map it.
408 if (fstat(fd
, &st
) < 0) {
409 perror("agedu: fstat");
413 printf("Built pathname index, %d entries, %ju bytes\n", count
,
414 (intmax_t)st
.st_size
);
416 totalsize
= index_compute_size(st
.st_size
, count
);
418 if (lseek(fd
, totalsize
-1, SEEK_SET
) < 0) {
419 perror("agedu: lseek");
422 if (write(fd
, "\0", 1) < 1) {
423 perror("agedu: write");
427 printf("Upper bound on index file size = %ju bytes\n",
428 (intmax_t)totalsize
);
430 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
|PROT_WRITE
,MAP_SHARED
, fd
, 0);
432 perror("agedu: mmap");
436 ib
= indexbuild_new(mappedfile
, st
.st_size
, count
);
437 tw
= triewalk_new(mappedfile
);
438 while ((tf
= triewalk_next(tw
, NULL
)) != NULL
)
439 indexbuild_add(ib
, tf
);
441 realsize
= indexbuild_realsize(ib
);
444 munmap(mappedfile
, totalsize
);
445 ftruncate(fd
, realsize
);
447 printf("Actual index file size = %ju bytes\n", (intmax_t)realsize
);
448 } else if (mode
== TEXT
) {
457 if (2 != sscanf(minage
, "%d%1[DdWwMmYy]", &nunits
, unit
)) {
458 fprintf(stderr
, "%s: minimum age should be a number followed by"
459 " one of d,w,m,y\n", PNAME
);
463 if (unit
[0] == 'd') {
465 } else if (unit
[0] == 'w') {
466 t
-= 86400 * 7 * nunits
;
471 ym
= tm
.tm_year
* 12 + tm
.tm_mon
;
478 tm
.tm_year
= ym
/ 12;
484 fd
= open(filename
, O_RDONLY
);
486 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
490 if (fstat(fd
, &st
) < 0) {
491 perror("agedu: fstat");
494 totalsize
= st
.st_size
;
495 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
497 perror("agedu: mmap");
502 * Trim trailing slash, just in case.
504 pathlen
= strlen(rootdir
);
505 if (pathlen
> 0 && rootdir
[pathlen
-1] == '/')
506 rootdir
[--pathlen
] = '\0';
508 text_query(mappedfile
, rootdir
, t
, 1);
509 } else if (mode
== HTML
) {
514 fd
= open(filename
, O_RDONLY
);
516 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
520 if (fstat(fd
, &st
) < 0) {
521 perror("agedu: fstat");
524 totalsize
= st
.st_size
;
525 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
527 perror("agedu: mmap");
532 * Trim trailing slash, just in case.
534 pathlen
= strlen(rootdir
);
535 if (pathlen
> 0 && rootdir
[pathlen
-1] == '/')
536 rootdir
[--pathlen
] = '\0';
538 xi
= trie_before(mappedfile
, rootdir
);
539 html
= html_query(mappedfile
, xi
, NULL
);
541 } else if (mode
== DUMP
) {
545 fd
= open(filename
, O_RDONLY
);
547 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
551 if (fstat(fd
, &st
) < 0) {
552 perror("agedu: fstat");
555 totalsize
= st
.st_size
;
556 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
558 perror("agedu: mmap");
562 maxpathlen
= trie_maxpathlen(mappedfile
);
563 buf
= snewn(maxpathlen
, char);
565 tw
= triewalk_new(mappedfile
);
566 while ((tf
= triewalk_next(tw
, buf
)) != NULL
) {
567 printf("%s: %llu %llu\n", buf
, tf
->blocks
, tf
->atime
);
570 } else if (mode
== HTTPD
) {
571 fd
= open(filename
, O_RDONLY
);
573 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
577 if (fstat(fd
, &st
) < 0) {
578 perror("agedu: fstat");
581 totalsize
= st
.st_size
;
582 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
584 perror("agedu: mmap");
588 run_httpd(mappedfile
, auth
);