Whamcloud - gitweb
36736b19d4496f3deab51fe8e754a100809e06b9
[tools/e2fsprogs.git] / e2scan / e2scan.c
1 /*
2  * e2scan.c --- Scan an ext2-type filesystem for modified inodes
3  *
4  * Copyright (c) 2007, 2010 Oracle and/or its affiliates. All rights reserved.
5  * Use is subject to license terms.
6  *
7  * Author: Vladimir Saviliev <vladimir.saviliev@sun.com>
8  *         Andreas Dilger <adilger@sun.com>
9  *
10  * %Begin-Header%
11  * This file may be redistributed under the terms of the
12  * GNU General Public License version 2.
13  * %End-Header%
14  */
15 #define _GNU_SOURCE
16 #define _FILE_OFFSET_BITS 64
17 #define _XOPEN_SOURCE           /* for getdate */
18 #define _XOPEN_SOURCE_EXTENDED  /* for getdate */
19
20 #include "config.h"
21 #include <stdio.h>
22 #include <sys/stat.h>
23 #include <time.h>
24 #include <unistd.h>
25 #include <ext2fs/ext2fs.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <sys/wait.h>
29 #include <sys/errno.h>
30
31 ext2_filsys fs;
32 const char *database = "e2scan.db";
33 int readahead_groups = 1; /* by default readahead one group inode table */
34 FILE *outfile;
35
36 void usage(char *prog)
37 {
38         fprintf(stderr,
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"
42 #else
43                 "\nUsage: %s -l [ options ] device-filename\nModes:"
44 #endif
45                 "\t-l: list recently changed files\n"
46                 "Options:\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, "
54                                                          "0 for all files)\n"
55                 "\t-o outfile: output file list to 'outfile'\n",
56                 prog, readahead_groups, database);
57         exit(1);
58 }
59
60 #define SM_NONE 0
61 #define SM_DATABASE 1 /* -f */
62 #define SM_FILELIST 2 /* -l */
63
64 struct {
65         int mode;
66         int nr;
67         union {
68                 struct {
69                         int fd;
70                         int nr_commands;
71                 } db;
72                 struct {
73                         /* number of files newer than specified time */
74                         ext2_ino_t nr_files;
75                         ext2_ino_t nr_dirs;
76                         /* number of files reported */
77                         ext2_ino_t nr_reported;
78                         time_t mtimestamp;
79                         time_t ctimestamp;
80                         int with_dirs;
81                 } fl;
82         };
83 } scan_data = { .mode = SM_FILELIST, };
84
85 /* db.c */
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,
90                                int namelen, int fd);
91
92 /* filelist.c */
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,
97                                int namelen);
98 int create_root_dentries(char *root);
99 void report_root(void);
100
101
102 static void get_timestamps(const char *filename)
103 {
104         struct stat st;
105
106         if (stat(filename, &st) == -1) {
107                 perror("failed to stat file");
108                 exit(1);
109         }
110         scan_data.fl.mtimestamp = st.st_mtime;
111         scan_data.fl.ctimestamp = st.st_ctime;
112 }
113
114 /*
115  * callback for ext2fs_block_iterate2, it adds directory leaf blocks
116  * to dblist
117  */
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)),
122                      void *priv_data)
123 {
124         int ret;
125         ext2_ino_t *ino;
126
127         if ((int) blockcnt < 0)
128                 /* skip indirect blocks */
129                 return 0;
130         ret = 0;
131         ino = priv_data;
132         if (ext2fs_add_dir_block(fs->dblist, *ino, *block_nr, (int) blockcnt))
133                 ret |= BLOCK_ABORT;
134
135         return ret;
136 }
137
138 /*
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.
143  */
144 errcode_t done_group_callback(ext2_filsys fs, ext2_inode_scan scan,
145                               dgrp_t group, void *vp)
146 {
147         dgrp_t ra_group;
148         blk64_t ra_start;
149         int ra_size;
150
151         if (readahead_groups <= 0)
152                 return 0;
153
154         if (((group + 1) % readahead_groups) != 0)
155                 return 0;
156
157         ra_group = group + 1 + readahead_groups;
158         if (ra_group >= fs->group_desc_count)
159                 return 0;
160
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;
164         else
165                 ra_size = readahead_groups;
166
167         ra_size *= fs->inode_blocks_per_group;
168         io_channel_cache_readahead(fs->io, ra_start, ra_size);
169         return 0;
170 }
171
172 #define DEFAULT_CHUNK_SIZE 16
173 __u32 chunk_size; /* in blocks */
174 int nr_chunks;
175
176 struct chunk {
177         __u64 start;
178         __u32 covered;
179         __u32 padding;
180 } *cur_chunk, *ra_chunk, *chunks;
181
182 /* callback for ext2fs_dblist_iterate */
183 static int fill_chunks(ext2_filsys fs, struct ext2_db_entry *db_info,
184                        void *priv_data)
185 {
186         __u32 cur;
187
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)
192                         cur_chunk = chunks;
193                 else
194                         cur_chunk++;
195
196                 cur_chunk->start = cur;
197                 cur_chunk->covered = 1;
198         } else
199                 cur_chunk->covered++;
200
201         return 0;
202 }
203
204 /* callback for ext2fs_dblist_iterate */
205 static int count_chunks(ext2_filsys fs, struct ext2_db_entry *db_info,
206                         void *priv_data)
207 {
208         __u32 cur;
209         static __u32 prev = (__u32)-1;
210
211         cur = db_info->blk / chunk_size;
212         if (cur != prev) {
213                 nr_chunks++;
214                 prev = cur;
215         }
216         return 0;
217 }
218
219 /* create list of chunks, readahead two first of them */
220 static void make_chunk_list(ext2_dblist dblist)
221 {
222         chunk_size = readahead_groups * DEFAULT_CHUNK_SIZE;
223         if (chunk_size == 0)
224                 return;
225
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");
230                 exit(1);
231         }
232         ext2fs_dblist_iterate(dblist, fill_chunks, NULL);
233
234         /* start readahead for two first chunks */
235         ra_chunk = chunks;
236         cur_chunk = NULL;
237
238         io_channel_cache_readahead(fs->io,
239                                    ra_chunk->start * chunk_size, chunk_size);
240         ra_chunk++;
241         if (ra_chunk < chunks + nr_chunks)
242                 io_channel_cache_readahead(fs->io,
243                                            ra_chunk->start * chunk_size,
244                                            chunk_size);
245 }
246
247 /*
248  * this is called for each directory block when it is read by dblist
249  * iterator
250  */
251 static int dblist_readahead(void *vp)
252 {
253         if (chunk_size == 0)
254                 return 0;
255         if (cur_chunk == NULL)
256                 cur_chunk = chunks;
257         if (--cur_chunk->covered == 0) {
258                 /*
259                  * last block of current chunk is read, readahead
260                  * chunk is under I/O, get new readahead chunk, move
261                  * current chunk
262                  */
263                 cur_chunk++;
264                 ra_chunk++;
265                 if (ra_chunk < chunks + nr_chunks)
266                         io_channel_cache_readahead(fs->io,
267                                                    ra_chunk->start * chunk_size,
268                                                    chunk_size);
269         }
270         return 0;
271 }
272
273 /*
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
277  */
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)),
283                              void *private)
284 {
285         int namelen;
286
287         if (offset == 0) {
288                 /* new directory block is read */
289                 scan_data.nr++;
290                 dblist_readahead(NULL);
291         }
292
293         if (dirent->inode == 0)
294                 return 0;
295
296         namelen = (dirent->name_len & 0xFF);
297         if (namelen == 2 && !strncmp(dirent->name, "..", 2))
298                 return 0;
299
300         if (namelen == 1 && !strncmp(dirent->name, ".", 1))
301                 return 0;
302
303         if (dirent->inode > fs->super->s_inodes_count) {
304                 fprintf(stderr, "too big ino %u (%.*s)\n",
305                         dirent->inode, namelen, dirent->name);
306                 exit(1);
307         }
308
309         if (scan_data.mode == SM_DATABASE)
310                 return database_dblist_iterate_cb(dirino, dirent, namelen,
311                                                   scan_data.db.fd);
312
313         return filelist_dblist_iterate_cb(dirino, dirent, namelen);
314 }
315
316 int main(int argc, char **argv)
317 {
318         char *root = "/";
319         int inode_buffer_blocks = 0;
320         errcode_t retval;
321         char *block_buf;
322         ext2_inode_scan scan;
323         struct ext2_inode inode;
324         ext2_ino_t ino;
325         dgrp_t nr;
326         time_t t;
327         pid_t pid = 0;
328         int c;
329
330         /*
331          * by default find for files which are modified less than one
332          * day ago
333          */
334         scan_data.fl.mtimestamp = time(NULL) - 60 * 60 * 24;
335         scan_data.fl.ctimestamp = scan_data.fl.mtimestamp;
336         outfile = stdout;
337
338         opterr = 0;
339 #if defined(HAVE_SQLITE3) && defined(HAVE_SQLITE3_H)
340 #define OPTF "f"
341 #else
342 #define OPTF ""
343 #endif
344         while ((c = getopt(argc, argv, "a:b:C:d:D"OPTF"hln:N:o:")) != EOF) {
345                 char *end;
346
347                 switch (c) {
348                 case 'a':
349                         if (optarg == NULL)
350                                 usage(argv[0]);
351                         readahead_groups = strtoul(optarg, &end, 0);
352                         if (*end) {
353                                 fprintf(stderr, "%s: bad -a argument '%s'\n",
354                                         argv[0], optarg);
355                                 usage(argv[0]);
356                         }
357                         break;
358                 case 'b':
359                         inode_buffer_blocks = strtoul(optarg, &end, 0);
360                         if (*end) {
361                                 fprintf(stderr, "%s: bad -b argument '%s'\n",
362                                         argv[0], optarg);
363                                 usage(argv[0]);
364                         }
365                         break;
366                 case 'C':
367                         root = optarg;
368                         break;
369                 case 'd':
370                         database = optarg;
371                         break;
372                 case 'D':
373                         scan_data.fl.with_dirs = 1;
374                         break;
375                 case 'f':
376 #if !defined(HAVE_SQLITE3) || !defined(HAVE_SQLITE3_H)
377                         fprintf(stderr,
378                                 "%s: sqlite3 was not detected on configure, "
379                                 "database creation is not supported\n",argv[0]);
380                         return 1;
381 #endif
382                         scan_data.mode = SM_DATABASE;
383                         break;
384                 case 'l':
385                         scan_data.mode = SM_FILELIST;
386                         break;
387                 case 'n':
388                         get_timestamps(optarg);
389                         break;
390                 case 'N': {
391                         const char *fmts[] = {"%c", /*date/time current locale*/
392                                               "%Ec",/*date/time alt. locale*/
393                                               "%Y-%m-%d %H:%M:%S",
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",
399                                               "%b %d %H:%M:%S %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 */
405                                               NULL,
406                                             };
407                         const char **fmt;
408                         struct tm tmptm, *tm = NULL;
409                         time_t now = time(0);
410
411                         tmptm = *localtime(&now);
412
413                         for (fmt = &fmts[0]; *fmt != NULL; fmt++) {
414                                 if (strptime(optarg, *fmt, &tmptm) != NULL) {
415                                         tm = &tmptm;
416                                         break;
417                                 }
418                         }
419
420                         if (tm == NULL) {
421                                 fprintf(stderr, "%s: bad -N argument '%s'\n",
422                                         argv[0], optarg);
423                                 usage(argv[0]);
424                         }
425                         scan_data.fl.mtimestamp = mktime(tm);
426                         scan_data.fl.ctimestamp = scan_data.fl.mtimestamp;
427                         break;
428                         }
429                 case 'o':
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));
434                                 usage(argv[0]);
435                         }
436                         break;
437                 default:
438                         fprintf(stderr, "%s: unknown option '-%c'\n",
439                                 argv[0], optopt);
440                 case 'h':
441                         usage(argv[0]);
442                 }
443         }
444
445         if (scan_data.mode == SM_NONE || argv[optind] == NULL)
446                 usage(argv[0]);
447
448
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));
454
455         retval = ext2fs_open(argv[optind], EXT2_FLAG_SOFTSUPP_FEATURES,
456                              0, 0, unix_io_manager, &fs);
457         if (retval != 0) {
458                 com_err("ext2fs_open", retval, "opening %s\n", argv[optind]);
459                 return 1;
460         }
461
462         t = time(NULL);
463
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);
468         if (retval) {
469                 com_err("ext2fs_read_inode_bitmap", retval,
470                         "opening inode bitmap on %s\n", argv[optind]);
471                 exit(1);
472         }
473         fprintf(stderr, "inode bitmap is read, %ld seconds\n", time(NULL) - t);
474
475
476         if (inode_buffer_blocks == 0)
477                 inode_buffer_blocks = fs->inode_blocks_per_group;
478
479         retval = ext2fs_open_inode_scan(fs, inode_buffer_blocks, &scan);
480         if (retval) {
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");
484                 exit(1);
485         }
486         ext2fs_set_inode_callback(scan, done_group_callback, NULL);
487
488         retval = ext2fs_init_dblist(fs, NULL);
489         if (retval) {
490                 com_err("ext2fs_init_dblist", retval,
491                         "initializing dblist\n");
492                 exit(1);
493         }
494
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",
498                         argv[0]);
499                 exit(1);
500         }
501         memset(block_buf, 0, fs->blocksize * 3);
502
503         switch (scan_data.mode) {
504         case SM_DATABASE:
505                 pid = fork_db_creation(database);
506                 break;
507
508         case SM_FILELIST:
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) >=
514                             PATH_MAX) {
515                                 fprintf(stderr, "%s: root path '%s' too long\n",
516                                         argv[0], root);
517                                 exit(1);
518                         }
519                         if (create_root_dentries(newroot) == 0)
520                                 c = 0;
521                 }
522                 if (c == ENOENT)
523                         fprintf(stderr,
524                                 "%s: visible filesystem root '%s' not found\n",
525                                 argv[0], root);
526                 else if (c == EIO)
527                         fprintf(stderr,
528                                 "%s: error reading visible root: '%s'\n",
529                                 argv[0], root);
530                 else if (c == ENOTDIR)
531                         fprintf(stderr,
532                                "%s: visible root '%s' not a directory\n",
533                                argv[0], root);
534                 if (c)
535                         exit(1);
536                 break;
537         default:
538                 break;
539         }
540
541         t = time(NULL);
542         fprintf(stderr, "scanning inode tables .. ");
543         scan_data.nr = 0;
544
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) {
548                 if (ino == 0)
549                         break;
550
551                 scan_data.nr++;
552                 if (ext2fs_fast_test_inode_bitmap2(fs->inode_map, ino) == 0)
553                         /* deleted - always skip for now */
554                         continue;
555                 switch (scan_data.mode) {
556                 case SM_DATABASE:
557                         database_iscan_action(ino, &inode, scan_data.db.fd,
558                                               block_buf);
559                         break;
560
561                 case SM_FILELIST:
562                         filelist_iscan_action(ino, &inode, block_buf);
563                         break;
564
565                 default:
566                         break;
567                 }
568         }
569
570         switch (scan_data.mode) {
571         case SM_DATABASE:
572                 fprintf(stderr,
573                         "done\n\t%d inodes, %ld seconds\n",
574                         scan_data.nr, time(NULL) - t);
575                 break;
576
577         case SM_FILELIST:
578                 fprintf(stderr, "done\n\t%d inodes, %ld seconds, %d files, "
579                         "%d dirs to find\n",
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);
584                         ext2fs_close(fs);
585                         free(block_buf);
586                         return 0;
587                 }
588                 break;
589
590         default:
591                 break;
592         }
593
594         t = time(NULL);
595         fprintf(stderr, "scanning directory blocks (%u).. ",
596                 ext2fs_dblist_count(fs->dblist));
597
598         /* root directory does not have name, handle it separately */
599         report_root();
600         /*
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
606          * issue readahead
607          */
608         make_chunk_list(fs->dblist);
609
610         scan_data.nr = 0;
611         retval = ext2fs_dblist_dir_iterate(fs->dblist,
612                                            DIRENT_FLAG_INCLUDE_EMPTY,
613                                            block_buf,
614                                            dblist_iterate_cb, NULL);
615         if (retval) {
616                 com_err("ext2fs_dblist_dir_iterate", retval,
617                         "dir iterating dblist\n");
618                 exit(1);
619         }
620         if (chunk_size)
621                 free(chunks);
622
623         switch (scan_data.mode) {
624         case SM_DATABASE:
625         {
626                 int status;
627
628                 fprintf(stderr,
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));
637                 break;
638         }
639
640         case SM_FILELIST:
641                 fprintf(stderr,
642                         "done\n\t%d blocks, %ld seconds, %d files reported\n",
643                         scan_data.nr, time(NULL) - t, scan_data.fl.nr_reported);
644                 break;
645
646         default:
647                 break;
648         }
649
650         ext2fs_close_inode_scan(scan);
651         ext2fs_close(fs);
652         free(block_buf);
653
654         return 0;
655 }