2 * Main program for agedu.
15 #include <sys/types.h>
28 void fatal(const char *fmt
, ...)
31 fprintf(stderr
, "%s: ", PNAME
);
33 vfprintf(stderr
, fmt
, ap
);
35 fprintf(stderr
, "\n");
41 dev_t datafile_dev
, filesystem_dev
;
43 time_t last_output_update
;
46 static int gotdata(void *vctx
, const char *pathname
, const struct stat64
*st
)
48 struct ctx
*ctx
= (struct ctx
*)vctx
;
49 struct trie_file file
;
53 * Filter out our own data file.
55 if (st
->st_dev
== ctx
->datafile_dev
&& st
->st_ino
== ctx
->datafile_ino
)
59 * Don't cross the streams^W^Wany file system boundary.
60 * (FIXME: this should be a configurable option.)
62 if (st
->st_dev
!= ctx
->filesystem_dev
)
66 * FIXME: other filtering in gotdata will be needed, when we
67 * implement serious filtering.
70 file
.blocks
= st
->st_blocks
;
71 file
.atime
= st
->st_atime
;
72 triebuild_add(ctx
->tb
, pathname
, &file
);
75 if (t
!= ctx
->last_output_update
) {
76 fprintf(stderr
, "%-79.79s\r", pathname
);
78 ctx
->last_output_update
= t
;
84 static void run_query(const void *mappedfile
, const char *rootdir
,
89 unsigned long xi1
, xi2
;
90 unsigned long long s1
, s2
;
92 maxpathlen
= trie_maxpathlen(mappedfile
);
93 pathbuf
= snewn(maxpathlen
+ 1, char);
96 * We want to query everything between the supplied filename
97 * (inclusive) and that filename with a ^A on the end
98 * (exclusive). So find the x indices for each.
100 sprintf(pathbuf
, "%s\001", rootdir
);
101 xi1
= trie_before(mappedfile
, rootdir
);
102 xi2
= trie_before(mappedfile
, pathbuf
);
105 * Now do the lookups in the age index.
107 s1
= index_query(mappedfile
, xi1
, t
);
108 s2
= index_query(mappedfile
, xi2
, t
);
110 /* Display in units of 2 512-byte blocks = 1Kb */
111 printf("%-11llu %s\n", (s2
- s1
) / 2, rootdir
);
115 * Now scan for first-level subdirectories and report
120 trie_getpath(mappedfile
, xi1
, pathbuf
);
121 run_query(mappedfile
, pathbuf
, t
, depth
-1);
122 strcat(pathbuf
, "\001");
123 xi1
= trie_before(mappedfile
, pathbuf
);
128 int main(int argc
, char **argv
)
131 struct ctx actx
, *ctx
= &actx
;
133 off_t totalsize
, realsize
;
137 const struct trie_file
*tf
;
138 char *filename
= "agedu.dat";
139 char *rootdir
= NULL
;
141 enum { QUERY
, HTML
, SCAN
, DUMP
, HTTPD
} mode
= QUERY
;
148 if (doing_opts
&& *p
== '-') {
149 if (!strcmp(p
, "--")) {
151 } else if (p
[1] == '-') {
152 char *optval
= strchr(p
, '=');
155 if (!strcmp(p
, "--help")) {
156 printf("FIXME: usage();\n");
158 } else if (!strcmp(p
, "--version")) {
159 printf("FIXME: version();\n");
161 } else if (!strcmp(p
, "--licence") ||
162 !strcmp(p
, "--license")) {
163 printf("FIXME: licence();\n");
165 } else if (!strcmp(p
, "--scan")) {
167 } else if (!strcmp(p
, "--dump")) {
169 } else if (!strcmp(p
, "--html")) {
171 } else if (!strcmp(p
, "--httpd") ||
172 !strcmp(p
, "--server")) {
174 } else if (!strcmp(p
, "--file") ||
175 !strcmp(p
, "--minimum-age") ||
176 !strcmp(p
, "--min-age") ||
177 !strcmp(p
, "--age")) {
179 * Long options requiring values.
185 fprintf(stderr
, "%s: option '%s' requires"
186 " an argument\n", PNAME
, p
);
190 if (!strcmp(p
, "--file")) {
192 } else if (!strcmp(p
, "--minimum-age") ||
193 !strcmp(p
, "--min-age") ||
194 !strcmp(p
, "--age")) {
198 fprintf(stderr
, "%s: unrecognised option '%s'\n",
208 /* Options requiring arguments. */
214 } else if (--argc
> 0) {
217 fprintf(stderr
, "%s: option '-%c' requires"
218 " an argument\n", PNAME
, c
);
222 case 'f': /* data file name */
225 case 'a': /* maximum age */
234 fprintf(stderr
, "%s: unrecognised option '-%c'\n",
244 fprintf(stderr
, "%s: unexpected argument '%s'\n", PNAME
, p
);
255 fd
= open(filename
, O_RDWR
| O_TRUNC
| O_CREAT
, S_IRWXU
);
257 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
262 if (stat(rootdir
, &st
) < 0) {
263 fprintf(stderr
, "%s: %s: stat: %s\n", PNAME
, rootdir
,
267 ctx
->filesystem_dev
= st
.st_dev
;
269 if (fstat(fd
, &st
) < 0) {
270 perror("agedu: fstat");
273 ctx
->datafile_dev
= st
.st_dev
;
274 ctx
->datafile_ino
= st
.st_ino
;
276 ctx
->last_output_update
= time(NULL
);
279 * Scan the directory tree, and write out the trie component
282 ctx
->tb
= triebuild_new(fd
);
283 du(rootdir
, gotdata
, ctx
);
284 count
= triebuild_finish(ctx
->tb
);
285 triebuild_free(ctx
->tb
);
287 fprintf(stderr
, "%-79s\r", "");
291 * Work out how much space the cumulative index trees will
292 * take; enlarge the file, and memory-map it.
294 if (fstat(fd
, &st
) < 0) {
295 perror("agedu: fstat");
299 printf("Built pathname index, %d entries, %ju bytes\n", count
,
300 (intmax_t)st
.st_size
);
302 totalsize
= index_compute_size(st
.st_size
, count
);
304 if (lseek(fd
, totalsize
-1, SEEK_SET
) < 0) {
305 perror("agedu: lseek");
308 if (write(fd
, "\0", 1) < 1) {
309 perror("agedu: write");
313 printf("Upper bound on index file size = %ju bytes\n",
314 (intmax_t)totalsize
);
316 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
|PROT_WRITE
,MAP_SHARED
, fd
, 0);
318 perror("agedu: mmap");
322 ib
= indexbuild_new(mappedfile
, st
.st_size
, count
);
323 tw
= triewalk_new(mappedfile
);
324 while ((tf
= triewalk_next(tw
, NULL
)) != NULL
)
325 indexbuild_add(ib
, tf
);
327 realsize
= indexbuild_realsize(ib
);
330 munmap(mappedfile
, totalsize
);
331 ftruncate(fd
, realsize
);
333 printf("Actual index file size = %ju bytes\n", (intmax_t)realsize
);
334 } else if (mode
== QUERY
) {
343 if (2 != sscanf(minage
, "%d%1[DdWwMmYy]", &nunits
, unit
)) {
344 fprintf(stderr
, "%s: minimum age should be a number followed by"
345 " one of d,w,m,y\n", PNAME
);
349 if (unit
[0] == 'd') {
351 } else if (unit
[0] == 'w') {
352 t
-= 86400 * 7 * nunits
;
357 ym
= tm
.tm_year
* 12 + tm
.tm_mon
;
364 tm
.tm_year
= ym
/ 12;
370 fd
= open(filename
, O_RDONLY
);
372 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
376 if (fstat(fd
, &st
) < 0) {
377 perror("agedu: fstat");
380 totalsize
= st
.st_size
;
381 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
383 perror("agedu: mmap");
388 * Trim trailing slash, just in case.
390 pathlen
= strlen(rootdir
);
391 if (pathlen
> 0 && rootdir
[pathlen
-1] == '/')
392 rootdir
[--pathlen
] = '\0';
394 run_query(mappedfile
, rootdir
, t
, 1);
395 } else if (mode
== HTML
) {
400 fd
= open(filename
, O_RDONLY
);
402 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
406 if (fstat(fd
, &st
) < 0) {
407 perror("agedu: fstat");
410 totalsize
= st
.st_size
;
411 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
413 perror("agedu: mmap");
418 * Trim trailing slash, just in case.
420 pathlen
= strlen(rootdir
);
421 if (pathlen
> 0 && rootdir
[pathlen
-1] == '/')
422 rootdir
[--pathlen
] = '\0';
424 xi
= trie_before(mappedfile
, rootdir
);
425 html
= html_query(mappedfile
, xi
, NULL
);
427 } else if (mode
== DUMP
) {
431 fd
= open(filename
, O_RDONLY
);
433 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
437 if (fstat(fd
, &st
) < 0) {
438 perror("agedu: fstat");
441 totalsize
= st
.st_size
;
442 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
444 perror("agedu: mmap");
448 maxpathlen
= trie_maxpathlen(mappedfile
);
449 buf
= snewn(maxpathlen
, char);
451 tw
= triewalk_new(mappedfile
);
452 while ((tf
= triewalk_next(tw
, buf
)) != NULL
) {
453 printf("%s: %llu %llu\n", buf
, tf
->blocks
, tf
->atime
);
456 } else if (mode
== HTTPD
) {
457 fd
= open(filename
, O_RDONLY
);
459 fprintf(stderr
, "%s: %s: open: %s\n", PNAME
, filename
,
463 if (fstat(fd
, &st
) < 0) {
464 perror("agedu: fstat");
467 totalsize
= st
.st_size
;
468 mappedfile
= mmap(NULL
, totalsize
, PROT_READ
, MAP_SHARED
, fd
, 0);
470 perror("agedu: mmap");
474 run_httpd(mappedfile
);