]> wimlib.net Git - wimlib/blob - tools/ntfs_fragreport.c
v1.14.4
[wimlib] / tools / ntfs_fragreport.c
1 /*
2  * ntfs_fragreport.c
3  *
4  * Use NTFS-3G to report on the fragmentation of an NTFS volume.
5  *
6  * Compile and run with something like:
7  *
8  *      gcc ntfs_fragreport.c -o ntfs_fragreport -O2 -Wall -lntfs-3g
9  *      ./ntfs_fragreport /dev/sda2
10  */
11
12 #include <errno.h>
13 #include <inttypes.h>
14 #include <stdarg.h>
15 #include <stdbool.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <time.h>
19
20 #include <ntfs-3g/attrib.h>
21 #include <ntfs-3g/dir.h>
22 #include <ntfs-3g/volume.h>
23
24 #define VERBOSE 0
25 #define ARRAY_LEN(A)    (sizeof(A) / sizeof((A)[0]))
26
27 static void __attribute__((noreturn))
28 fatal_error(const char *format, ...)
29 {
30         va_list va;
31
32         va_start(va, format);
33         fprintf(stderr, "ERROR: ");
34         vfprintf(stderr, format, va);
35         fprintf(stderr, ": %m\n");
36         va_end(va);
37
38         exit(1);
39 }
40
41 struct inode_details {
42         u64 ino;
43         u64 num_extents;
44 };
45
46 struct frag_stats {
47         u64 num_files;
48         u64 num_resident_files;
49         u64 num_nonresident_sparse_files;
50         u64 num_nonresident_nonsparse_files;
51         u64 num_fragmented_files;
52         u64 num_extents;
53         struct inode_details most_fragmented_files[100];
54 };
55
56 static struct frag_stats dir_frag_stats;
57 static struct frag_stats nondir_frag_stats;
58
59 static void
60 print_file_details(ntfs_inode *ni, u64 num_extents, size_t i)
61 {
62         bool first = true;
63         ntfs_attr_search_ctx *actx;
64
65         printf("\t\t%zu. Inode %"PRIu64" (", i + 1, ni->mft_no);
66
67         actx = ntfs_attr_get_search_ctx(ni, NULL);
68         if (!actx) {
69                 fatal_error("getting attribute search context for "
70                             "inode %"PRIu64, ni->mft_no);
71         }
72
73         while (!ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, actx)) {
74                 const FILE_NAME_ATTR *fn = (const FILE_NAME_ATTR *)
75                         ((u8 *)actx->attr +
76                          le16_to_cpu(actx->attr->value_offset));
77                 char *filename = NULL;
78
79                 if (fn->file_name_type == FILE_NAME_DOS)
80                         continue;
81
82                 if (ntfs_ucstombs(fn->file_name, fn->file_name_length,
83                                   &filename, 0) < 0) {
84                         fatal_error("translating filename for inode "
85                                     "%"PRIu64, ni->mft_no);
86                 }
87
88                 if (!first)
89                         printf(", ");
90                 printf("\"%s\"", filename);
91                 first = false;
92                 free(filename);
93         }
94         ntfs_attr_put_search_ctx(actx);
95
96         printf("): %"PRIu64" extents, size %"PRIi64"\n",
97               num_extents, ni->data_size);
98 }
99
100 static void
101 print_frag_stats(const struct frag_stats *stats, ntfs_volume *vol)
102 {
103         double extents_per_file;
104
105         printf("\tFiles: %"PRIu64"\n", stats->num_files);
106
107         printf("\tResident files: %"PRIu64"\n", stats->num_resident_files);
108
109         printf("\tNonresident, sparse files: %"PRIu64"\n",
110                stats->num_nonresident_sparse_files);
111
112         printf("\tNonresident, nonsparse files: %"PRIu64"\n",
113                stats->num_nonresident_nonsparse_files);
114         printf("\t\tFragmented files: %"PRIu64" "
115                "(%.3f%%)\n", stats->num_fragmented_files,
116                100 * stats->num_fragmented_files /
117                         (double)stats->num_nonresident_nonsparse_files);
118         extents_per_file = stats->num_extents /
119                            (double)stats->num_nonresident_nonsparse_files;
120         printf("\t\tExtents per file: %.5f (%.3f%% fragmented)\n",
121                extents_per_file, 100 * (extents_per_file - 1));
122
123         if (stats->num_fragmented_files != 0) {
124                 printf("\tMost fragmented files:\n");
125                 for (size_t i = 0; i < ARRAY_LEN(stats->most_fragmented_files);
126                      i++)
127                 {
128                         const struct inode_details *file =
129                                         &stats->most_fragmented_files[i];
130                         if (file->ino != 0) {
131                                 ntfs_inode *ni = ntfs_inode_open(vol, file->ino);
132                                 if (!ni) {
133                                         fatal_error("opening inode %"PRIu64,
134                                                     file->ino);
135                                 }
136                                 print_file_details(ni, file->num_extents, i);
137                                 ntfs_inode_close(ni);
138                         }
139                 }
140         }
141 }
142
143 static void
144 insert_fragmented_file(struct frag_stats *stats, const ntfs_inode *ni,
145                        u64 num_extents)
146 {
147         const size_t n = ARRAY_LEN(stats->most_fragmented_files);
148         struct inode_details *files = stats->most_fragmented_files;
149         size_t i;
150         struct inode_details next = {
151                 .ino = ni->mft_no,
152                 .num_extents = num_extents,
153         };
154
155         if (num_extents <= files[n - 1].num_extents)
156                 return;
157
158         for (i = 0; i < n && num_extents <= files[i].num_extents; i++)
159                 ;
160
161         for (; i < n; i++) {
162                 struct inode_details tmp = files[i];
163                 files[i] = next;
164                 next = tmp;
165         }
166 }
167
168 static void
169 process_file(ntfs_inode *ni, ATTR_TYPES type, ntfschar *name, u32 name_len)
170 {
171         ntfs_attr *na;
172         runlist *rl;
173         u64 num_extents = 0;
174         struct frag_stats *stats;
175
176         na = ntfs_attr_open(ni, type, name, name_len);
177         if (!na) {
178                 if (errno == ENOENT)
179                         return;
180                 fatal_error("opening attribute of inode %"PRIu64, ni->mft_no);
181         }
182
183         if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
184                 stats = &dir_frag_stats;
185         else
186                 stats = &nondir_frag_stats;
187
188         stats->num_files++;
189
190         if (NAttrNonResident(na)) {
191                 s64 allocated_size = 0;
192                 bool sparse = false;
193
194                 if (ntfs_attr_map_whole_runlist(na) != 0)
195                         fatal_error("mapping runlist of attribute for "
196                                     "inode %"PRIu64, ni->mft_no);
197
198                 for (rl = na->rl; rl->length; rl++) {
199                         if (rl->lcn == LCN_HOLE) {
200                                 sparse = true;
201                         } else if (rl->lcn < 0) {
202                                 fatal_error("unexpected LCN: %"PRIi64,
203                                             (s64)rl->lcn);
204                         } else {
205                                 num_extents++;
206                                 allocated_size += rl->length <<
207                                                   ni->vol->cluster_size_bits;
208                         }
209                 }
210                 if (sparse || num_extents == 0) {
211                         stats->num_nonresident_sparse_files++;
212                 } else {
213                         stats->num_nonresident_nonsparse_files++;
214
215                         if (allocated_size != na->allocated_size) {
216                                 fatal_error("allocated size inconsistency for "
217                                             "inode %"PRIu64, ni->mft_no);
218                         }
219                         if (num_extents > 1) {
220                                 stats->num_fragmented_files++;
221                                 insert_fragmented_file(stats, ni, num_extents);
222                         }
223                         stats->num_extents += num_extents;
224                 }
225         } else {
226                 stats->num_resident_files++;
227         }
228
229 #if VERBOSE
230         printf("%"PRIu64"\t", ni->mft_no);
231         printf("%sdirectory\t", (stats == &nondir_frag_stats ? "non" : ""));
232         printf("%"PRIi64"\t", na->data_size);
233         printf("%"PRIi64"\t", na->allocated_size);
234         printf("%s\t", NAttrNonResident(na) ? "nonresident" : "resident");
235         printf("%"PRIu64"\t", num_extents);
236         printf("\n");
237 #endif
238
239         ntfs_attr_close(na);
240 }
241
242 int main(int argc, char **argv)
243 {
244         ntfs_volume *vol;
245         u64 num_mft_records;
246
247         if (argc != 2) {
248                 fprintf(stderr, "Usage: %s VOLUME\n", argv[0]);
249                 return 1;
250         }
251
252         vol = ntfs_mount(argv[1], NTFS_MNT_RDONLY);
253         if (!vol)
254                 fatal_error("mounting NTFS volume");
255
256 #if VERBOSE
257         printf("INO\tTYPE\tDATA_SIZE\tALLOCATED_SIZE\tRESIDENT\tNUM_EXTENTS\n");
258 #endif
259
260         num_mft_records = vol->mft_na->data_size >> vol->mft_record_size_bits;
261         for (u64 ino = FILE_first_user; ino < num_mft_records; ino++) {
262                 ntfs_inode *ni;
263
264                 ni = ntfs_inode_open(vol, ino);
265                 if (!ni) {
266                         if (errno == ENOENT)
267                                 continue;
268                         fatal_error("opening inode %"PRIu64, ino);
269                 }
270
271                 if (ni->nr_extents >= 0) {
272                         if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
273                                 process_file(ni, AT_INDEX_ALLOCATION,
274                                              NTFS_INDEX_I30, 4);
275                         } else {
276                                 process_file(ni, AT_DATA, AT_UNNAMED, 0);
277                         }
278                 }
279
280                 ntfs_inode_close(ni);
281         }
282
283         printf("\n");
284         printf("Directory stats:\n");
285         print_frag_stats(&dir_frag_stats, vol);
286
287         printf("\n");
288         printf("Nondirectory stats:\n");
289         print_frag_stats(&nondir_frag_stats, vol);
290
291         ntfs_umount(vol, 0);
292
293         return 0;
294 }