2 #define _FILE_OFFSET_BITS 64
3 #define _XOPEN_SOURCE /* for getdate */
4 #define _XOPEN_SOURCE_EXTENDED /* for getdate */
10 #include <ext2fs/ext2fs.h>
14 #include <sys/errno.h>
17 const char *database = "e2scan.db";
18 int readahead_groups = 1; /* by default readahead one group inode table */
21 void usage(char *prog)
24 #if defined(HAVE_SQLITE3) && defined(HAVE_SQLITE3_H)
25 "\nUsage: %s {-l | -f} [ options ] device-filename\nModes:"
26 "\t-f: create file database\n"
28 "\nUsage: %s -l [ options ] device-filename\nModes:"
30 "\t-l: list recently changed files\n"
32 "\t-a groups: readahead 'groups' inode tables (default %d)\n"
33 "\t-b blocks: buffer 'blocks' inode table blocks\n"
34 "\t-C chdir: list files relative to 'chdir' in filesystem\n"
35 "\t-d database: output database filename (default %s)\n"
36 "\t-D: list not only files, but directories as well\n"
37 "\t-n filename: list files newer than 'filename'\n"
38 "\t-N date: list files newer than 'date' (default 1 day, "
40 "\t-o outfile: output file list to 'outfile'\n",
41 prog, readahead_groups, database);
46 #define SM_DATABASE 1 /* -f */
47 #define SM_FILELIST 2 /* -l */
58 /* number of files newer than specified time */
61 /* number of files reported */
62 ext2_ino_t nr_reported;
68 } scan_data = { .mode = SM_FILELIST, };
71 pid_t fork_db_creation(const char *database);
72 void database_iscan_action(ext2_ino_t ino,
73 struct ext2_inode *inode, int fd, char *buf);
74 int database_dblist_iterate_cb(ext2_ino_t dir, struct ext2_dir_entry *dirent,
78 void filelist_iscan_action(ext2_ino_t ino,
79 struct ext2_inode *inode, char *buf);
80 int filelist_dblist_iterate_cb(ext2_ino_t dirino,
81 struct ext2_dir_entry *dirent,
83 int create_root_dentries(char *root);
84 void report_root(void);
87 static void get_timestamps(const char *filename)
91 if (stat(filename, &st) == -1) {
92 perror("failed to stat file");
95 scan_data.fl.mtimestamp = st.st_mtime;
96 scan_data.fl.ctimestamp = st.st_ctime;
100 * callback for ext2fs_block_iterate2, it adds directory leaf blocks
103 int block_iterate_cb(ext2_filsys fs, blk_t *block_nr,
104 e2_blkcnt_t blockcnt,
105 blk_t ref_block EXT2FS_ATTR((unused)),
106 int ref_offset EXT2FS_ATTR((unused)),
112 if ((int) blockcnt < 0)
113 /* skip indirect blocks */
117 if (ext2fs_add_dir_block(fs->dblist, *ino, *block_nr, (int) blockcnt))
124 * done_group callback for inode scan.
125 * When i-th group of inodes is scanned over, readahead for i+2-th
126 * group is issued. Inode table readahead for two first groups is
127 * issued before scan begin.
129 errcode_t done_group_callback(ext2_filsys fs, ext2_inode_scan scan,
130 dgrp_t group, void *vp)
133 unsigned long ra_start;
136 if (readahead_groups <= 0)
139 if (((group + 1) % readahead_groups) != 0)
142 ra_group = group + 1 + readahead_groups;
143 if (ra_group >= fs->group_desc_count)
146 ra_start = ext2fs_inode_table_loc(fs, ra_group);
147 if (ra_group + readahead_groups > fs->group_desc_count)
148 ra_size = fs->group_desc_count - ra_group;
150 ra_size = readahead_groups;
152 ra_size *= fs->inode_blocks_per_group;
153 io_channel_readahead(fs->io, ra_start, ra_size);
157 #define DEFAULT_CHUNK_SIZE 16
158 __u32 chunk_size; /* in blocks */
164 } *cur_chunk, *ra_chunk, *chunks;
166 /* callback for ext2fs_dblist_iterate */
167 static int fill_chunks(ext2_filsys fs, struct ext2_db_entry *db_info,
172 cur = db_info->blk / chunk_size;
173 if (cur_chunk == NULL || cur != cur_chunk->start) {
174 /* new sweep starts */
175 if (cur_chunk == NULL)
180 cur_chunk->start = cur;
181 cur_chunk->covered = 1;
183 cur_chunk->covered ++;
188 /* callback for ext2fs_dblist_iterate */
189 static int count_chunks(ext2_filsys fs, struct ext2_db_entry *db_info,
193 static __u32 prev = (__u32)-1;
195 cur = db_info->blk / chunk_size;
203 /* create list of chunks, readahead two first of them */
204 static void make_chunk_list(ext2_dblist dblist)
206 chunk_size = readahead_groups * DEFAULT_CHUNK_SIZE;
210 ext2fs_dblist_iterate(dblist, count_chunks, NULL);
211 chunks = malloc(sizeof(struct chunk) * nr_chunks);
212 if (chunks == NULL) {
213 fprintf(stderr, "malloc failed\n");
216 ext2fs_dblist_iterate(dblist, fill_chunks, NULL);
218 /* start readahead for two first chunks */
222 io_channel_readahead(fs->io,
223 ra_chunk->start * chunk_size,
226 if (ra_chunk < chunks + nr_chunks)
227 io_channel_readahead(fs->io,
228 ra_chunk->start * chunk_size,
233 * this is called for each directory block when it is read by dblist
236 static int dblist_readahead(void *vp)
240 if (cur_chunk == NULL)
242 if (--cur_chunk->covered == 0) {
244 * last block of current chunk is read, readahead
245 * chunk is under I/O, get new readahead chunk, move
250 if (ra_chunk < chunks + nr_chunks)
251 io_channel_readahead(fs->io,
252 ra_chunk->start * chunk_size,
259 * callback for ext2fs_dblist_dir_iterate to be called for each
260 * directory entry, perform actions common for both database and
261 * filelist modes, call specific functions depending on the mode
263 static int dblist_iterate_cb(ext2_ino_t dirino, int entry,
264 struct ext2_dir_entry *dirent,
265 int offset EXT2FS_ATTR((unused)),
266 int blocksize EXT2FS_ATTR((unused)),
267 char *buf EXT2FS_ATTR((unused)),
273 /* new directory block is read */
275 dblist_readahead(NULL);
278 if (dirent->inode == 0)
281 namelen = (dirent->name_len & 0xFF);
282 if (namelen == 2 && !strncmp(dirent->name, "..", 2))
285 if (namelen == 1 && !strncmp(dirent->name, ".", 1))
288 if (dirent->inode > fs->super->s_inodes_count) {
289 fprintf(stderr, "too big ino %u (%.*s)\n",
290 dirent->inode, namelen, dirent->name);
294 if (scan_data.mode == SM_DATABASE)
295 return database_dblist_iterate_cb(dirino, dirent, namelen,
298 return filelist_dblist_iterate_cb(dirino, dirent, namelen);
301 int main(int argc, char **argv)
304 int inode_buffer_blocks = 0;
307 ext2_inode_scan scan;
308 struct ext2_inode inode;
316 * by default find for files which are modified less than one
319 scan_data.fl.mtimestamp = time(NULL) - 60 * 60 * 24;
320 scan_data.fl.ctimestamp = scan_data.fl.mtimestamp;
324 #if defined(HAVE_SQLITE3) && defined(HAVE_SQLITE3_H)
329 while ((c = getopt(argc, argv, "a:b:C:d:D"OPTF"hln:N:o:")) != EOF) {
336 readahead_groups = strtoul(optarg, &end, 0);
338 fprintf(stderr, "%s: bad -a argument '%s'\n",
344 inode_buffer_blocks = strtoul(optarg, &end, 0);
346 fprintf(stderr, "%s: bad -b argument '%s'\n",
358 scan_data.fl.with_dirs = 1;
361 #if !defined(HAVE_SQLITE3) || !defined(HAVE_SQLITE3_H)
363 "%s: sqlite3 was not detected on configure, "
364 "database creation is not supported\n",argv[0]);
367 scan_data.mode = SM_DATABASE;
370 scan_data.mode = SM_FILELIST;
373 get_timestamps(optarg);
376 const char *fmts[] = {"%c", /*date/time current locale*/
377 "%Ec",/*date/time alt. locale*/
378 "%a%t%b%t%d,%t%Y%t%H:%M:%S",
379 "%a,%t%d%t%b%t%Y%t%H:%M:%S",
380 "%a%t%b%t%d%t%H:%M:%S%t%Z%t%Y",
381 "%a%t%b%t%d%t%H:%M:%S%t%Y",
382 "%b%t%d%t%H:%M:%S%t%Z%t%Y",
383 "%b%t%d%t%H:%M:%S%t%Y",
384 "%x%t%X",/*date time*/
385 "%Ex%t%EX",/*alternate date time*/
386 "%F", /*ISO 8601 date*/
387 "%+", /*`date` format*/
388 "%s", /*seconds since epoch */
392 struct tm tmptm, *tm = NULL;
393 time_t now = time(0);
395 tmptm = *localtime(&now);
397 for (fmt = &fmts[0]; *fmt != NULL; fmt++) {
398 if (strptime(optarg, *fmt, &tmptm) != NULL) {
405 fprintf(stderr, "%s: bad -N argument '%s'\n",
409 scan_data.fl.mtimestamp = mktime(tm);
410 scan_data.fl.ctimestamp = scan_data.fl.mtimestamp;
414 outfile = fopen(optarg, "w");
415 if (outfile == NULL) {
416 fprintf(stderr, "%s: can't open '%s': %s\n",
417 argv[0], optarg, strerror(errno));
422 fprintf(stderr, "%s: unknown option '-%c'\n",
429 if (scan_data.mode == SM_NONE || argv[optind] == NULL)
433 fprintf(stderr, "generating list of files with\n"
434 "\tmtime newer than %s"
435 "\tctime newer than %s",
436 ctime(&scan_data.fl.mtimestamp),
437 ctime(&scan_data.fl.ctimestamp));
439 retval = ext2fs_open(argv[optind], EXT2_FLAG_SOFTSUPP_FEATURES,
440 0, 0, unix_io_manager, &fs);
442 com_err("ext2fs_open", retval, "opening %s\n", argv[optind]);
448 for (nr = 0; nr < fs->group_desc_count; nr ++)
449 io_channel_readahead(fs->io,
450 ext2fs_inode_bitmap_loc(fs, nr), 1);
451 retval = ext2fs_read_inode_bitmap(fs);
453 com_err("ext2fs_read_inode_bitmap", retval,
454 "opening inode bitmap on %s\n", argv[optind]);
457 fprintf(stderr, "inode bitmap is read, %ld seconds\n", time(NULL) - t);
460 if (inode_buffer_blocks == 0)
461 inode_buffer_blocks = fs->inode_blocks_per_group;
463 retval = ext2fs_open_inode_scan(fs, inode_buffer_blocks, &scan);
465 com_err("ext2fs_open_inode_scan", retval,
466 "opening inode scan on %s\n", argv[optind]);
467 fprintf(stderr, "failed to open inode scan\n");
470 ext2fs_set_inode_callback(scan, done_group_callback, NULL);
472 retval = ext2fs_init_dblist(fs, NULL);
474 com_err("ext2fs_init_dblist", retval,
475 "initializing dblist\n");
479 block_buf = (char *)malloc(fs->blocksize * 3);
480 if (block_buf == NULL) {
481 fprintf(stderr, "%s: failed to allocate memory for block_buf\n",
485 memset(block_buf, 0, fs->blocksize * 3);
487 switch (scan_data.mode) {
489 pid = fork_db_creation(database);
493 c = create_root_dentries(root);
494 if (c == ENOENT && strncmp(root, "/ROOT", 5) != 0) {
495 /* Try again with prepending "/ROOT" */
496 char newroot[PATH_MAX];
497 if (snprintf(newroot, PATH_MAX, "/ROOT/%s", root) >=
499 fprintf(stderr, "%s: root path '%s' too long\n",
503 if (create_root_dentries(newroot) == 0)
508 "%s: visible filesystem root '%s' not found\n",
512 "%s: error reading visible root: '%s'\n",
514 else if (c == ENOTDIR)
516 "%s: visible root '%s' not a directory\n",
526 fprintf(stderr, "scanning inode tables .. ");
529 done_group_callback(fs, scan, -readahead_groups * 2, NULL);
530 done_group_callback(fs, scan, -readahead_groups, NULL);
531 while (ext2fs_get_next_inode(scan, &ino, &inode) == 0) {
536 if (ext2fs_fast_test_inode_bitmap2(fs->inode_map, ino) == 0)
537 /* deleted - always skip for now */
539 switch (scan_data.mode) {
541 database_iscan_action(ino, &inode, scan_data.db.fd,
546 filelist_iscan_action(ino, &inode, block_buf);
554 switch (scan_data.mode) {
557 "done\n\t%d inodes, %ld seconds\n",
558 scan_data.nr, time(NULL) - t);
562 fprintf(stderr, "done\n\t%d inodes, %ld seconds, %d files, "
564 scan_data.nr, time(NULL) - t, scan_data.fl.nr_files,
565 scan_data.fl.nr_dirs);
566 if (scan_data.fl.nr_files == 0 && scan_data.fl.nr_dirs == 0) {
567 ext2fs_close_inode_scan(scan);
579 fprintf(stderr, "scanning directory blocks (%u).. ",
580 ext2fs_dblist_count(fs->dblist));
582 /* root directory does not have name, handle it separately */
585 * we have a list of directory leaf blocks, blocks are sorted,
586 * but can be not very sequential. If such blocks are close to
587 * each other, read throughput can be improved if blocks are
588 * read not sequentially, but all at once in a big
589 * chunk. Create list of those chunks, it will be then used to
592 make_chunk_list(fs->dblist);
595 retval = ext2fs_dblist_dir_iterate(fs->dblist,
596 DIRENT_FLAG_INCLUDE_EMPTY,
598 dblist_iterate_cb, NULL);
600 com_err("ext2fs_dblist_dir_iterate", retval,
601 "dir iterating dblist\n");
607 switch (scan_data.mode) {
613 "done\n\t%d blocks, %ld seconds, "
614 "%d records sent to database\n",
615 scan_data.nr, time(NULL) - t, scan_data.db.nr_commands);
616 close(scan_data.db.fd);
617 waitpid(pid, &status, 0);
618 if (WIFEXITED(status))
619 fprintf(stderr, "database creation exited with %d\n",
620 WEXITSTATUS(status));
626 "done\n\t%d blocks, %ld seconds, %d files reported\n",
627 scan_data.nr, time(NULL) - t, scan_data.fl.nr_reported);
634 ext2fs_close_inode_scan(scan);