2 * e2scan.c --- Scan an ext2-type filesystem for modified inodes
4 * Copyright (c) 2007, 2010 Oracle and/or its affiliates. All rights reserved.
5 * Use is subject to license terms.
7 * Author: Vladimir Saviliev <vladimir.saviliev@sun.com>
8 * Andreas Dilger <adilger@sun.com>
11 * This file may be redistributed under the terms of the
12 * GNU General Public License version 2.
16 #define _FILE_OFFSET_BITS 64
17 #define _XOPEN_SOURCE /* for getdate */
18 #define _XOPEN_SOURCE_EXTENDED /* for getdate */
25 #include <ext2fs/ext2fs.h>
29 #include <sys/errno.h>
32 const char *database = "e2scan.db";
33 int readahead_groups = 1; /* by default readahead one group inode table */
36 void usage(char *prog)
39 #if defined(HAVE_SQLITE3) && defined(HAVE_SQLITE3_H)
40 "\nUsage: %s {-l | -f} [ options ] device-filename\nModes:"
41 "\t-f: create file database\n"
43 "\nUsage: %s -l [ options ] device-filename\nModes:"
45 "\t-l: list recently changed files\n"
47 "\t-a groups: readahead 'groups' inode tables (default %d)\n"
48 "\t-b blocks: buffer 'blocks' inode table blocks\n"
49 "\t-C chdir: list files relative to 'chdir' in filesystem\n"
50 "\t-d database: output database filename (default %s)\n"
51 "\t-D: list not only files, but directories as well\n"
52 "\t-n filename: list files newer than 'filename'\n"
53 "\t-N date: list files newer than 'date' (default 1 day, "
55 "\t-o outfile: output file list to 'outfile'\n",
56 prog, readahead_groups, database);
61 #define SM_DATABASE 1 /* -f */
62 #define SM_FILELIST 2 /* -l */
73 /* number of files newer than specified time */
76 /* number of files reported */
77 ext2_ino_t nr_reported;
83 } scan_data = { .mode = SM_FILELIST, };
86 pid_t fork_db_creation(const char *database);
87 void database_iscan_action(ext2_ino_t ino,
88 struct ext2_inode *inode, int fd, char *buf);
89 int database_dblist_iterate_cb(ext2_ino_t dir, struct ext2_dir_entry *dirent,
93 void filelist_iscan_action(ext2_ino_t ino,
94 struct ext2_inode *inode, char *buf);
95 int filelist_dblist_iterate_cb(ext2_ino_t dirino,
96 struct ext2_dir_entry *dirent,
98 int create_root_dentries(char *root);
99 void report_root(void);
102 static void get_timestamps(const char *filename)
106 if (stat(filename, &st) == -1) {
107 perror("failed to stat file");
110 scan_data.fl.mtimestamp = st.st_mtime;
111 scan_data.fl.ctimestamp = st.st_ctime;
115 * callback for ext2fs_block_iterate2, it adds directory leaf blocks
118 int block_iterate_cb(ext2_filsys fs, blk_t *block_nr,
119 e2_blkcnt_t blockcnt,
120 blk_t ref_block EXT2FS_ATTR((unused)),
121 int ref_offset EXT2FS_ATTR((unused)),
127 if ((int) blockcnt < 0)
128 /* skip indirect blocks */
132 if (ext2fs_add_dir_block(fs->dblist, *ino, *block_nr, (int) blockcnt))
139 * done_group callback for inode scan.
140 * When i-th group of inodes is scanned over, readahead for i+2-th
141 * group is issued. Inode table readahead for two first groups is
142 * issued before scan begin.
144 errcode_t done_group_callback(ext2_filsys fs, ext2_inode_scan scan,
145 dgrp_t group, void *vp)
151 if (readahead_groups <= 0)
154 if (((group + 1) % readahead_groups) != 0)
157 ra_group = group + 1 + readahead_groups;
158 if (ra_group >= fs->group_desc_count)
161 ra_start = ext2fs_inode_table_loc(fs, ra_group);
162 if (ra_group + readahead_groups > fs->group_desc_count)
163 ra_size = fs->group_desc_count - ra_group;
165 ra_size = readahead_groups;
167 ra_size *= fs->inode_blocks_per_group;
168 io_channel_cache_readahead(fs->io, ra_start, ra_size);
172 #define DEFAULT_CHUNK_SIZE 16
173 __u32 chunk_size; /* in blocks */
180 } *cur_chunk, *ra_chunk, *chunks;
182 /* callback for ext2fs_dblist_iterate */
183 static int fill_chunks(ext2_filsys fs, struct ext2_db_entry *db_info,
188 cur = db_info->blk / chunk_size;
189 if (cur_chunk == NULL || cur != cur_chunk->start) {
190 /* new sweep starts */
191 if (cur_chunk == NULL)
196 cur_chunk->start = cur;
197 cur_chunk->covered = 1;
199 cur_chunk->covered++;
204 /* callback for ext2fs_dblist_iterate */
205 static int count_chunks(ext2_filsys fs, struct ext2_db_entry *db_info,
209 static __u32 prev = (__u32)-1;
211 cur = db_info->blk / chunk_size;
219 /* create list of chunks, readahead two first of them */
220 static void make_chunk_list(ext2_dblist dblist)
222 chunk_size = readahead_groups * DEFAULT_CHUNK_SIZE;
226 ext2fs_dblist_iterate(dblist, count_chunks, NULL);
227 chunks = malloc(sizeof(struct chunk) * nr_chunks);
228 if (chunks == NULL) {
229 fprintf(stderr, "malloc failed\n");
232 ext2fs_dblist_iterate(dblist, fill_chunks, NULL);
234 /* start readahead for two first chunks */
238 io_channel_cache_readahead(fs->io,
239 ra_chunk->start * chunk_size, chunk_size);
241 if (ra_chunk < chunks + nr_chunks)
242 io_channel_cache_readahead(fs->io,
243 ra_chunk->start * chunk_size,
248 * this is called for each directory block when it is read by dblist
251 static int dblist_readahead(void *vp)
255 if (cur_chunk == NULL)
257 if (--cur_chunk->covered == 0) {
259 * last block of current chunk is read, readahead
260 * chunk is under I/O, get new readahead chunk, move
265 if (ra_chunk < chunks + nr_chunks)
266 io_channel_cache_readahead(fs->io,
267 ra_chunk->start * chunk_size,
274 * callback for ext2fs_dblist_dir_iterate to be called for each
275 * directory entry, perform actions common for both database and
276 * filelist modes, call specific functions depending on the mode
278 static int dblist_iterate_cb(ext2_ino_t dirino, int entry,
279 struct ext2_dir_entry *dirent,
280 int offset EXT2FS_ATTR((unused)),
281 int blocksize EXT2FS_ATTR((unused)),
282 char *buf EXT2FS_ATTR((unused)),
288 /* new directory block is read */
290 dblist_readahead(NULL);
293 if (dirent->inode == 0)
296 namelen = (dirent->name_len & 0xFF);
297 if (namelen == 2 && !strncmp(dirent->name, "..", 2))
300 if (namelen == 1 && !strncmp(dirent->name, ".", 1))
303 if (dirent->inode > fs->super->s_inodes_count) {
304 fprintf(stderr, "too big ino %u (%.*s)\n",
305 dirent->inode, namelen, dirent->name);
309 if (scan_data.mode == SM_DATABASE)
310 return database_dblist_iterate_cb(dirino, dirent, namelen,
313 return filelist_dblist_iterate_cb(dirino, dirent, namelen);
316 int main(int argc, char **argv)
319 int inode_buffer_blocks = 0;
322 ext2_inode_scan scan;
323 struct ext2_inode inode;
331 * by default find for files which are modified less than one
334 scan_data.fl.mtimestamp = time(NULL) - 60 * 60 * 24;
335 scan_data.fl.ctimestamp = scan_data.fl.mtimestamp;
339 #if defined(HAVE_SQLITE3) && defined(HAVE_SQLITE3_H)
344 while ((c = getopt(argc, argv, "a:b:C:d:D"OPTF"hln:N:o:")) != EOF) {
351 readahead_groups = strtoul(optarg, &end, 0);
353 fprintf(stderr, "%s: bad -a argument '%s'\n",
359 inode_buffer_blocks = strtoul(optarg, &end, 0);
361 fprintf(stderr, "%s: bad -b argument '%s'\n",
373 scan_data.fl.with_dirs = 1;
376 #if !defined(HAVE_SQLITE3) || !defined(HAVE_SQLITE3_H)
378 "%s: sqlite3 was not detected on configure, "
379 "database creation is not supported\n",argv[0]);
382 scan_data.mode = SM_DATABASE;
385 scan_data.mode = SM_FILELIST;
388 get_timestamps(optarg);
391 const char *fmts[] = {"%c", /*date/time current locale*/
392 "%Ec",/*date/time alt. locale*/
394 "%a %b %d, %Y %H:%M:%S",
395 "%a, %d %b %Y %H:%M:%S",
396 "%a %b %d %H:%M:%S %Z %Y",
397 "%a %b %d %H:%M:%S %Y",
398 "%b %d %H:%M:%S %Z %Y",
400 "%x %X",/*date time*/
401 "%Ex %EX",/*alternate date time*/
402 "%F", /*ISO 8601 date*/
403 "%+", /*`date` format*/
404 "%s", /*seconds since epoch */
408 struct tm tmptm, *tm = NULL;
409 time_t now = time(0);
411 tmptm = *localtime(&now);
413 for (fmt = &fmts[0]; *fmt != NULL; fmt++) {
414 if (strptime(optarg, *fmt, &tmptm) != NULL) {
421 fprintf(stderr, "%s: bad -N argument '%s'\n",
425 scan_data.fl.mtimestamp = mktime(tm);
426 scan_data.fl.ctimestamp = scan_data.fl.mtimestamp;
430 outfile = fopen(optarg, "w");
431 if (outfile == NULL) {
432 fprintf(stderr, "%s: can't open '%s': %s\n",
433 argv[0], optarg, strerror(errno));
438 fprintf(stderr, "%s: unknown option '-%c'\n",
445 if (scan_data.mode == SM_NONE || argv[optind] == NULL)
449 fprintf(stderr, "generating list of files with\n"
450 "\tmtime newer than %s"
451 "\tctime newer than %s",
452 ctime(&scan_data.fl.mtimestamp),
453 ctime(&scan_data.fl.ctimestamp));
455 retval = ext2fs_open(argv[optind], EXT2_FLAG_SOFTSUPP_FEATURES,
456 0, 0, unix_io_manager, &fs);
458 com_err("ext2fs_open", retval, "opening %s\n", argv[optind]);
464 for (nr = 0; nr < fs->group_desc_count; nr++)
465 io_channel_cache_readahead(fs->io,
466 ext2fs_inode_bitmap_loc(fs, nr), 1);
467 retval = ext2fs_read_inode_bitmap(fs);
469 com_err("ext2fs_read_inode_bitmap", retval,
470 "opening inode bitmap on %s\n", argv[optind]);
473 fprintf(stderr, "inode bitmap is read, %ld seconds\n", time(NULL) - t);
476 if (inode_buffer_blocks == 0)
477 inode_buffer_blocks = fs->inode_blocks_per_group;
479 retval = ext2fs_open_inode_scan(fs, inode_buffer_blocks, &scan);
481 com_err("ext2fs_open_inode_scan", retval,
482 "opening inode scan on %s\n", argv[optind]);
483 fprintf(stderr, "failed to open inode scan\n");
486 ext2fs_set_inode_callback(scan, done_group_callback, NULL);
488 retval = ext2fs_init_dblist(fs, NULL);
490 com_err("ext2fs_init_dblist", retval,
491 "initializing dblist\n");
495 block_buf = (char *)malloc(fs->blocksize * 3);
496 if (block_buf == NULL) {
497 fprintf(stderr, "%s: failed to allocate memory for block_buf\n",
501 memset(block_buf, 0, fs->blocksize * 3);
503 switch (scan_data.mode) {
505 pid = fork_db_creation(database);
509 c = create_root_dentries(root);
510 if (c == ENOENT && strncmp(root, "/ROOT", 5) != 0) {
511 /* Try again with prepending "/ROOT" */
512 char newroot[PATH_MAX];
513 if (snprintf(newroot, PATH_MAX, "/ROOT/%s", root) >=
515 fprintf(stderr, "%s: root path '%s' too long\n",
519 if (create_root_dentries(newroot) == 0)
524 "%s: visible filesystem root '%s' not found\n",
528 "%s: error reading visible root: '%s'\n",
530 else if (c == ENOTDIR)
532 "%s: visible root '%s' not a directory\n",
542 fprintf(stderr, "scanning inode tables .. ");
545 done_group_callback(fs, scan, -readahead_groups * 2, NULL);
546 done_group_callback(fs, scan, -readahead_groups, NULL);
547 while (ext2fs_get_next_inode(scan, &ino, &inode) == 0) {
552 if (ext2fs_fast_test_inode_bitmap2(fs->inode_map, ino) == 0)
553 /* deleted - always skip for now */
555 switch (scan_data.mode) {
557 database_iscan_action(ino, &inode, scan_data.db.fd,
562 filelist_iscan_action(ino, &inode, block_buf);
570 switch (scan_data.mode) {
573 "done\n\t%d inodes, %ld seconds\n",
574 scan_data.nr, time(NULL) - t);
578 fprintf(stderr, "done\n\t%d inodes, %ld seconds, %d files, "
580 scan_data.nr, time(NULL) - t, scan_data.fl.nr_files,
581 scan_data.fl.nr_dirs);
582 if (scan_data.fl.nr_files == 0 && scan_data.fl.nr_dirs == 0) {
583 ext2fs_close_inode_scan(scan);
595 fprintf(stderr, "scanning directory blocks (%u).. ",
596 ext2fs_dblist_count(fs->dblist));
598 /* root directory does not have name, handle it separately */
601 * we have a list of directory leaf blocks, blocks are sorted,
602 * but can be not very sequential. If such blocks are close to
603 * each other, read throughput can be improved if blocks are
604 * read not sequentially, but all at once in a big
605 * chunk. Create list of those chunks, it will be then used to
608 make_chunk_list(fs->dblist);
611 retval = ext2fs_dblist_dir_iterate(fs->dblist,
612 DIRENT_FLAG_INCLUDE_EMPTY,
614 dblist_iterate_cb, NULL);
616 com_err("ext2fs_dblist_dir_iterate", retval,
617 "dir iterating dblist\n");
623 switch (scan_data.mode) {
629 "done\n\t%d blocks, %ld seconds, "
630 "%d records sent to database\n",
631 scan_data.nr, time(NULL) - t, scan_data.db.nr_commands);
632 close(scan_data.db.fd);
633 waitpid(pid, &status, 0);
634 if (WIFEXITED(status))
635 fprintf(stderr, "database creation exited with %d\n",
636 WEXITSTATUS(status));
642 "done\n\t%d blocks, %ld seconds, %d files reported\n",
643 scan_data.nr, time(NULL) - t, scan_data.fl.nr_reported);
650 ext2fs_close_inode_scan(scan);