4 * Use NTFS-3G to report on the fragmentation of an NTFS volume.
6 * Compile and run with something like:
8 * gcc ntfs_fragreport.c -o ntfs_fragreport -O2 -Wall -lntfs-3g
9 * ./ntfs_fragreport /dev/sda2
20 #include <ntfs-3g/attrib.h>
21 #include <ntfs-3g/dir.h>
22 #include <ntfs-3g/volume.h>
25 #define ARRAY_LEN(A) (sizeof(A) / sizeof((A)[0]))
27 static void __attribute__((noreturn))
28 fatal_error(const char *format, ...)
33 fprintf(stderr, "ERROR: ");
34 vfprintf(stderr, format, va);
35 fprintf(stderr, ": %m\n");
41 struct inode_details {
48 u64 num_resident_files;
49 u64 num_nonresident_sparse_files;
50 u64 num_nonresident_nonsparse_files;
51 u64 num_fragmented_files;
53 struct inode_details most_fragmented_files[100];
56 static struct frag_stats dir_frag_stats;
57 static struct frag_stats nondir_frag_stats;
60 print_file_details(ntfs_inode *ni, u64 num_extents, size_t i)
63 ntfs_attr_search_ctx *actx;
65 printf("\t\t%zu. Inode %"PRIu64" (", i + 1, ni->mft_no);
67 actx = ntfs_attr_get_search_ctx(ni, NULL);
69 fatal_error("getting attribute search context for "
70 "inode %"PRIu64, ni->mft_no);
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 *)
76 le16_to_cpu(actx->attr->value_offset));
77 char *filename = NULL;
79 if (fn->file_name_type == FILE_NAME_DOS)
82 if (ntfs_ucstombs(fn->file_name, fn->file_name_length,
84 fatal_error("translating filename for inode "
85 "%"PRIu64, ni->mft_no);
90 printf("\"%s\"", filename);
94 ntfs_attr_put_search_ctx(actx);
96 printf("): %"PRIu64" extents, size %"PRIi64"\n",
97 num_extents, ni->data_size);
101 print_frag_stats(const struct frag_stats *stats, ntfs_volume *vol)
103 double extents_per_file;
105 printf("\tFiles: %"PRIu64"\n", stats->num_files);
107 printf("\tResident files: %"PRIu64"\n", stats->num_resident_files);
109 printf("\tNonresident, sparse files: %"PRIu64"\n",
110 stats->num_nonresident_sparse_files);
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));
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);
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);
133 fatal_error("opening inode %"PRIu64,
136 print_file_details(ni, file->num_extents, i);
137 ntfs_inode_close(ni);
144 insert_fragmented_file(struct frag_stats *stats, const ntfs_inode *ni,
147 const size_t n = ARRAY_LEN(stats->most_fragmented_files);
148 struct inode_details *files = stats->most_fragmented_files;
150 struct inode_details next = {
152 .num_extents = num_extents,
155 if (num_extents <= files[n - 1].num_extents)
158 for (i = 0; i < n && num_extents <= files[i].num_extents; i++)
162 struct inode_details tmp = files[i];
169 process_file(ntfs_inode *ni, ATTR_TYPES type, ntfschar *name, u32 name_len)
174 struct frag_stats *stats;
176 na = ntfs_attr_open(ni, type, name, name_len);
180 fatal_error("opening attribute of inode %"PRIu64, ni->mft_no);
183 if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY)
184 stats = &dir_frag_stats;
186 stats = &nondir_frag_stats;
190 if (NAttrNonResident(na)) {
191 s64 allocated_size = 0;
194 if (ntfs_attr_map_whole_runlist(na) != 0)
195 fatal_error("mapping runlist of attribute for "
196 "inode %"PRIu64, ni->mft_no);
198 for (rl = na->rl; rl->length; rl++) {
199 if (rl->lcn == LCN_HOLE) {
201 } else if (rl->lcn < 0) {
202 fatal_error("unexpected LCN: %"PRIi64,
206 allocated_size += rl->length <<
207 ni->vol->cluster_size_bits;
210 if (sparse || num_extents == 0) {
211 stats->num_nonresident_sparse_files++;
213 stats->num_nonresident_nonsparse_files++;
215 if (allocated_size != na->allocated_size) {
216 fatal_error("allocated size inconsistency for "
217 "inode %"PRIu64, ni->mft_no);
219 if (num_extents > 1) {
220 stats->num_fragmented_files++;
221 insert_fragmented_file(stats, ni, num_extents);
223 stats->num_extents += num_extents;
226 stats->num_resident_files++;
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);
242 int main(int argc, char **argv)
248 fprintf(stderr, "Usage: %s VOLUME\n", argv[0]);
252 vol = ntfs_mount(argv[1], NTFS_MNT_RDONLY);
254 fatal_error("mounting NTFS volume");
257 printf("INO\tTYPE\tDATA_SIZE\tALLOCATED_SIZE\tRESIDENT\tNUM_EXTENTS\n");
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++) {
264 ni = ntfs_inode_open(vol, ino);
268 fatal_error("opening inode %"PRIu64, ino);
271 if (ni->nr_extents >= 0) {
272 if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) {
273 process_file(ni, AT_INDEX_ALLOCATION,
276 process_file(ni, AT_DATA, AT_UNNAMED, 0);
280 ntfs_inode_close(ni);
284 printf("Directory stats:\n");
285 print_frag_stats(&dir_frag_stats, vol);
288 printf("Nondirectory stats:\n");
289 print_frag_stats(&nondir_frag_stats, vol);