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 run_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 run_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 { QUERY
, HTML
, SCAN
, DUMP
, HTTPD
} mode
= QUERY
;
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
, "--html")) {
206 } else if (!strcmp(p
, "--httpd") ||
207 !strcmp(p
, "--server")) {
209 } else if (!strcmp(p
, "--progress") ||
210 !strcmp(p
, "--scan-progress")) {
212 } else if (!strcmp(p
, "--no-progress") ||
213 !strcmp(p
, "--no-scan-progress")) {
215 } else if (!strcmp(p
, "--tty-progress") ||
216 !strcmp(p
, "--tty-scan-progress") ||
217 !strcmp(p
, "--progress-tty") ||
218 !strcmp(p
, "--scan-progress-tty")) {
220 } else if (!strcmp(p
, "--crossfs")) {
222 } else if (!strcmp(p
, "--no-crossfs")) {
224 } else if (!strcmp(p
, "--file") ||
225 !strcmp(p
, "--auth") ||
226 !strcmp(p
, "--http-auth") ||
227 !strcmp(p
, "--httpd-auth") ||
228 !strcmp(p
, "--server-auth") ||
229 !strcmp(p
, "--minimum-age") ||
230 !strcmp(p
, "--min-age") ||
231 !strcmp(p
, "--age") ||
232 !strcmp(p
, "--include") ||
233 !strcmp(p
, "--include-path") ||
234 !strcmp(p
, "--exclude") ||
235 !strcmp(p
, "--exclude-path")) {
237 * Long options requiring values.
243 fprintf(stderr
, "%s: option '%s' requires"
244 " an argument\n", PNAME
, p
);
248 if (!strcmp(p
, "--file")) {
250 } else if (!strcmp(p
, "--minimum-age") ||
251 !strcmp(p
, "--min-age") ||
252 !strcmp(p
, "--age")) {
254 } else if (!strcmp(p
, "--auth") ||
255 !strcmp(p
, "--http-auth") ||
256 !strcmp(p
, "--httpd-auth") ||
257 !strcmp(p
, "--server-auth")) {
258 if (!strcmp(optval
, "magic"))
259 auth
= HTTPD_AUTH_MAGIC
;
260 else if (!strcmp(optval
, "basic"))
261 auth
= HTTPD_AUTH_BASIC
;
262 else if (!strcmp(optval
, "none"))
263 auth
= HTTPD_AUTH_NONE
;
264 else if (!strcmp(optval
, "default"))
265 auth
= HTTPD_AUTH_MAGIC
| HTTPD_AUTH_BASIC
;
267 fprintf(stderr
, "%s: unrecognised authentication"
268 " type '%s'\n%*s options are 'magic',"
269 " 'basic', 'none', 'default'\n",
270 PNAME
, optval
, (int)strlen(PNAME
), "");
273 } else if (!strcmp(p
, "--include") ||
274 !strcmp(p
, "--include-path") ||
275 !strcmp(p
, "--exclude") ||
276 !strcmp(p
, "--exclude-path")) {
277 if (ninex
>= inexsize
) {
278 inexsize
= ninex
* 3 / 2 + 16;
279 inex
= sresize(inex
, inexsize
,
280 struct inclusion_exclusion
);
282 inex
[ninex
].path
= (!strcmp(p
, "--include-path") ||
283 !strcmp(p
, "--exclude-path"));
284 inex
[ninex
].include
= (!strcmp(p
, "--include") ||
285 !strcmp(p
, "--include-path"));
286 inex
[ninex
].wildcard
= optval
;
290 fprintf(stderr
, "%s: unrecognised option '%s'\n",
300 /* Options requiring arguments. */
306 } else if (--argc
> 0) {
309 fprintf(stderr
, "%s: option '-%c' requires"
310 " an argument\n", PNAME
, c
);
314 case 'f': /* data file name */
317 case 'a': /* maximum age */
326 fprintf(stderr
, "%s: unrecognised option '-%c'\n",
336 fprintf(stderr
, "%s: unexpected argument '%s'\n", PNAME
, p
);
347 fd
= open(filename
, O_RDWR
| O_TRUNC
| O_CREAT
, S_IRWXU
);
349 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
354 if (stat(rootdir
, &st
) < 0) {
355 fprintf(stderr
, "%s: %s: stat: %s\n", PNAME
, rootdir
,
359 ctx
->filesystem_dev
= crossfs ?
0 : st
.st_dev
;
361 if (fstat(fd
, &st
) < 0) {
362 perror("agedu: fstat");
365 ctx
->datafile_dev
= st
.st_dev
;
366 ctx
->datafile_ino
= st
.st_ino
;
369 ctx
->crossfs
= crossfs
;
371 ctx
->last_output_update
= time(NULL
);
373 /* progress==1 means report progress only if stderr is a tty */
375 progress
= isatty(2) ?
2 : 0;
376 ctx
->progress
= progress
;
379 if (progress
&& ioctl(2, TIOCGWINSZ
, &ws
) == 0)
380 ctx
->progwidth
= ws
.ws_col
- 1;
386 * Scan the directory tree, and write out the trie component
389 ctx
->tb
= triebuild_new(fd
);
390 du(rootdir
, gotdata
, ctx
);
391 count
= triebuild_finish(ctx
->tb
);
392 triebuild_free(ctx
->tb
);
395 fprintf(stderr
, "%-*s\r", ctx
->progwidth
, "");
400 * Work out how much space the cumulative index trees will
401 * take; enlarge the file, and memory-map it.
403 if (fstat(fd
, &st
) < 0) {
404 perror("agedu: fstat");
408 printf("Built pathname index, %d entries, %ju bytes\n", count
,
409 (intmax_t)st
.st_size
);
411 totalsize
= index_compute_size(st
.st_size
, count
);
413 if (lseek(fd
, totalsize
-1, SEEK_SET
) < 0) {
414 perror("agedu: lseek");
417 if (write(fd
, "\0", 1) < 1) {
418 perror("agedu: write");
422 printf("Upper bound on index file size = %ju bytes\n",
423 (intmax_t)totalsize
);
425 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
|PROT_WRITE
,MAP_SHARED
, fd
, 0);
427 perror("agedu: mmap");
431 ib
= indexbuild_new(mappedfile
, st
.st_size
, count
);
432 tw
= triewalk_new(mappedfile
);
433 while ((tf
= triewalk_next(tw
, NULL
)) != NULL
)
434 indexbuild_add(ib
, tf
);
436 realsize
= indexbuild_realsize(ib
);
439 munmap(mappedfile
, totalsize
);
440 ftruncate(fd
, realsize
);
442 printf("Actual index file size = %ju bytes\n", (intmax_t)realsize
);
443 } else if (mode
== QUERY
) {
452 if (2 != sscanf(minage
, "%d%1[DdWwMmYy]", &nunits
, unit
)) {
453 fprintf(stderr
, "%s: minimum age should be a number followed by"
454 " one of d,w,m,y\n", PNAME
);
458 if (unit
[0] == 'd') {
460 } else if (unit
[0] == 'w') {
461 t
-= 86400 * 7 * nunits
;
466 ym
= tm
.tm_year
* 12 + tm
.tm_mon
;
473 tm
.tm_year
= ym
/ 12;
479 fd
= open(filename
, O_RDONLY
);
481 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
485 if (fstat(fd
, &st
) < 0) {
486 perror("agedu: fstat");
489 totalsize
= st
.st_size
;
490 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
492 perror("agedu: mmap");
497 * Trim trailing slash, just in case.
499 pathlen
= strlen(rootdir
);
500 if (pathlen
> 0 && rootdir
[pathlen
-1] == '/')
501 rootdir
[--pathlen
] = '\0';
503 run_query(mappedfile
, rootdir
, t
, 1);
504 } else if (mode
== HTML
) {
509 fd
= open(filename
, O_RDONLY
);
511 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
515 if (fstat(fd
, &st
) < 0) {
516 perror("agedu: fstat");
519 totalsize
= st
.st_size
;
520 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
522 perror("agedu: mmap");
527 * Trim trailing slash, just in case.
529 pathlen
= strlen(rootdir
);
530 if (pathlen
> 0 && rootdir
[pathlen
-1] == '/')
531 rootdir
[--pathlen
] = '\0';
533 xi
= trie_before(mappedfile
, rootdir
);
534 html
= html_query(mappedfile
, xi
, NULL
);
536 } else if (mode
== DUMP
) {
540 fd
= open(filename
, O_RDONLY
);
542 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
546 if (fstat(fd
, &st
) < 0) {
547 perror("agedu: fstat");
550 totalsize
= st
.st_size
;
551 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
553 perror("agedu: mmap");
557 maxpathlen
= trie_maxpathlen(mappedfile
);
558 buf
= snewn(maxpathlen
, char);
560 tw
= triewalk_new(mappedfile
);
561 while ((tf
= triewalk_next(tw
, buf
)) != NULL
) {
562 printf("%s: %llu %llu\n", buf
, tf
->blocks
, tf
->atime
);
565 } else if (mode
== HTTPD
) {
566 fd
= open(filename
, O_RDONLY
);
568 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
572 if (fstat(fd
, &st
) < 0) {
573 perror("agedu: fstat");
576 totalsize
= st
.st_size
;
577 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
579 perror("agedu: mmap");
583 run_httpd(mappedfile
, auth
);