From d5ecf54ce90b9c8942743e0fa5c34620f7140ae2 Mon Sep 17 00:00:00 2001 From: Eric Biggers Date: Sun, 24 Jul 2016 19:01:09 -0700 Subject: [PATCH] Add program to report NTFS volume fragmentation --- tools/ntfs_fragreport.c | 294 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 tools/ntfs_fragreport.c diff --git a/tools/ntfs_fragreport.c b/tools/ntfs_fragreport.c new file mode 100644 index 00000000..59142095 --- /dev/null +++ b/tools/ntfs_fragreport.c @@ -0,0 +1,294 @@ +/* + * ntfs_fragreport.c + * + * Use NTFS-3G to report on the fragmentation of an NTFS volume. + * + * Compile and run with something like: + * + * gcc ntfs_fragreport.c -o ntfs_fragreport -O2 -Wall -lntfs-3g + * ./ntfs_fragreport /dev/sda2 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define VERBOSE 0 +#define ARRAY_LEN(A) (sizeof(A) / sizeof((A)[0])) + +static void __attribute__((noreturn)) +fatal_error(const char *format, ...) +{ + va_list va; + + va_start(va, format); + fprintf(stderr, "ERROR: "); + vfprintf(stderr, format, va); + fprintf(stderr, ": %m\n"); + va_end(va); + + exit(1); +} + +struct inode_details { + u64 ino; + u64 num_extents; +}; + +struct frag_stats { + u64 num_files; + u64 num_resident_files; + u64 num_nonresident_sparse_files; + u64 num_nonresident_nonsparse_files; + u64 num_fragmented_files; + u64 num_extents; + struct inode_details most_fragmented_files[100]; +}; + +static struct frag_stats dir_frag_stats; +static struct frag_stats nondir_frag_stats; + +static void +print_file_details(ntfs_inode *ni, u64 num_extents, size_t i) +{ + bool first = true; + ntfs_attr_search_ctx *actx; + + printf("\t\t%zu. Inode %"PRIu64" (", i + 1, ni->mft_no); + + actx = ntfs_attr_get_search_ctx(ni, NULL); + if (!actx) { + fatal_error("getting attribute search context for " + "inode %"PRIu64, ni->mft_no); + } + + while (!ntfs_attr_lookup(AT_FILE_NAME, NULL, 0, 0, 0, NULL, 0, actx)) { + const FILE_NAME_ATTR *fn = (const FILE_NAME_ATTR *) + ((u8 *)actx->attr + + le16_to_cpu(actx->attr->value_offset)); + char *filename = NULL; + + if (fn->file_name_type == FILE_NAME_DOS) + continue; + + if (ntfs_ucstombs(fn->file_name, fn->file_name_length, + &filename, 0) < 0) { + fatal_error("translating filename for inode " + "%"PRIu64, ni->mft_no); + } + + if (!first) + printf(", "); + printf("\"%s\"", filename); + first = false; + free(filename); + } + ntfs_attr_put_search_ctx(actx); + + printf("): %"PRIu64" extents, size %"PRIi64"\n", + num_extents, ni->data_size); +} + +static void +print_frag_stats(const struct frag_stats *stats, ntfs_volume *vol) +{ + double extents_per_file; + + printf("\tFiles: %"PRIu64"\n", stats->num_files); + + printf("\tResident files: %"PRIu64"\n", stats->num_resident_files); + + printf("\tNonresident, sparse files: %"PRIu64"\n", + stats->num_nonresident_sparse_files); + + printf("\tNonresident, nonsparse files: %"PRIu64"\n", + stats->num_nonresident_nonsparse_files); + printf("\t\tFragmented files: %"PRIu64" " + "(%.3f%%)\n", stats->num_fragmented_files, + 100 * stats->num_fragmented_files / + (double)stats->num_nonresident_nonsparse_files); + extents_per_file = stats->num_extents / + (double)stats->num_nonresident_nonsparse_files; + printf("\t\tExtents per file: %.5f (%.3f%% fragmented)\n", + extents_per_file, 100 * (extents_per_file - 1)); + + if (stats->num_fragmented_files != 0) { + printf("\tMost fragmented files:\n"); + for (size_t i = 0; i < ARRAY_LEN(stats->most_fragmented_files); + i++) + { + const struct inode_details *file = + &stats->most_fragmented_files[i]; + if (file->ino != 0) { + ntfs_inode *ni = ntfs_inode_open(vol, file->ino); + if (!ni) { + fatal_error("opening inode %"PRIu64, + file->ino); + } + print_file_details(ni, file->num_extents, i); + ntfs_inode_close(ni); + } + } + } +} + +static void +insert_fragmented_file(struct frag_stats *stats, const ntfs_inode *ni, + u64 num_extents) +{ + const size_t n = ARRAY_LEN(stats->most_fragmented_files); + struct inode_details *files = stats->most_fragmented_files; + size_t i; + struct inode_details next = { + .ino = ni->mft_no, + .num_extents = num_extents, + }; + + if (num_extents <= files[n - 1].num_extents) + return; + + for (i = 0; i < n && num_extents <= files[i].num_extents; i++) + ; + + for (; i < n; i++) { + struct inode_details tmp = files[i]; + files[i] = next; + next = tmp; + } +} + +static void +process_file(ntfs_inode *ni, ATTR_TYPES type, ntfschar *name, u32 name_len) +{ + ntfs_attr *na; + runlist *rl; + u64 num_extents = 0; + struct frag_stats *stats; + + na = ntfs_attr_open(ni, type, name, name_len); + if (!na) { + if (errno == ENOENT) + return; + fatal_error("opening attribute of inode %"PRIu64, ni->mft_no); + } + + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) + stats = &dir_frag_stats; + else + stats = &nondir_frag_stats; + + stats->num_files++; + + if (NAttrNonResident(na)) { + s64 allocated_size = 0; + bool sparse = false; + + if (ntfs_attr_map_whole_runlist(na) != 0) + fatal_error("mapping runlist of attribute for " + "inode %"PRIu64, ni->mft_no); + + for (rl = na->rl; rl->length; rl++) { + if (rl->lcn == LCN_HOLE) { + sparse = true; + } else if (rl->lcn < 0) { + fatal_error("unexpected LCN: %"PRIi64, + (s64)rl->lcn); + } else { + num_extents++; + allocated_size += rl->length << + ni->vol->cluster_size_bits; + } + } + if (sparse || num_extents == 0) { + stats->num_nonresident_sparse_files++; + } else { + stats->num_nonresident_nonsparse_files++; + + if (allocated_size != na->allocated_size) { + fatal_error("allocated size inconsistency for " + "inode %"PRIu64, ni->mft_no); + } + if (num_extents > 1) { + stats->num_fragmented_files++; + insert_fragmented_file(stats, ni, num_extents); + } + stats->num_extents += num_extents; + } + } else { + stats->num_resident_files++; + } + +#if VERBOSE + printf("%"PRIu64"\t", ni->mft_no); + printf("%sdirectory\t", (stats == &nondir_frag_stats ? "non" : "")); + printf("%"PRIi64"\t", na->data_size); + printf("%"PRIi64"\t", na->allocated_size); + printf("%s\t", NAttrNonResident(na) ? "nonresident" : "resident"); + printf("%"PRIu64"\t", num_extents); + printf("\n"); +#endif + + ntfs_attr_close(na); +} + +int main(int argc, char **argv) +{ + ntfs_volume *vol; + u64 num_mft_records; + + if (argc != 2) { + fprintf(stderr, "Usage: %s VOLUME\n", argv[0]); + return 1; + } + + vol = ntfs_mount(argv[1], NTFS_MNT_RDONLY); + if (!vol) + fatal_error("mounting NTFS volume"); + +#if VERBOSE + printf("INO\tTYPE\tDATA_SIZE\tALLOCATED_SIZE\tRESIDENT\tNUM_EXTENTS\n"); +#endif + + num_mft_records = vol->mft_na->data_size >> vol->mft_record_size_bits; + for (u64 ino = FILE_first_user; ino < num_mft_records; ino++) { + ntfs_inode *ni; + + ni = ntfs_inode_open(vol, ino); + if (!ni) { + if (errno == ENOENT) + continue; + fatal_error("opening inode %"PRIu64, ino); + } + + if (ni->nr_extents >= 0) { + if (ni->mrec->flags & MFT_RECORD_IS_DIRECTORY) { + process_file(ni, AT_INDEX_ALLOCATION, + NTFS_INDEX_I30, 4); + } else { + process_file(ni, AT_DATA, AT_UNNAMED, 0); + } + } + + ntfs_inode_close(ni); + } + + printf("\n"); + printf("Directory stats:\n"); + print_frag_stats(&dir_frag_stats, vol); + + printf("\n"); + printf("Nondirectory stats:\n"); + print_frag_stats(&nondir_frag_stats, vol); + + ntfs_umount(vol, 0); + + return 0; +} -- 2.43.0