#include #include #include #include #include #include #include "config.h" #include #include #define PFMAX (100) static struct { s64 from; s64 to; } pagefile[PFMAX]; static int pfcount = 0; static struct { s64 min_size; int human; int brief; int force; int pagefile; int output_block_size; char *device; } options; static int utils_valid_device(const char *name, int force); static ntfs_volume * utils_mount_volume(const char *device, unsigned long flags); ATTR_RECORD * find_attribute(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx); static int utils_cluster_in_use(ntfs_volume *vol, long long lcn); static int scan_free_space(ntfs_volume *vol); static int get_pagefile_clusters(ntfs_volume *vol); // Params // // static int parse_options(int argc, char **argv) { static const char *sopt = "-m:hbfps:"; static const struct option lopt[] = { { "block-size", required_argument, NULL, 's' }, { "min-size", required_argument, NULL, 'm' }, { "human-readable", no_argument, NULL, 'h' }, { "brief", no_argument, NULL, 'b' }, { "force", no_argument, NULL, 'f' }, { "pagefile", no_argument, NULL, 'p' }, { NULL, 0, NULL, 0 } }; int c = -1; int help = 0; char *end = NULL; opterr = 0; /* We'll handle the errors, thank you. */ options.min_size = 500000000; options.human = 0; options.brief = 0; options.force = 0; options.pagefile = 0; options.output_block_size = 1; options.device = NULL; while ((c = getopt_long(argc, argv, sopt, lopt, NULL)) != -1) { switch (c) { case 1: /* A non-option argument */ if (options.device == NULL) { options.device = argv[optind-1]; } else { options.device = NULL; help = 1; } break; case 'b': options.brief = 1; break; case 'h': options.human = 1; break; case 'm': options.min_size = strtol(optarg, &end, 0); break; case 'f': options.force = 1; break; case 'p': options.pagefile = 1; break; case 's': options.output_block_size = strtol(optarg, &end, 0); break; default: ntfs_log_error("Unknown option '%s'.\n", argv[optind-1]); help = 1; break; } if (help) break; } if (options.device == NULL) { help = 1; } if (help || options.min_size < 0 || options.output_block_size < 1) { ntfs_log_error("Usage: %s [-m|--min-size ] [-h|--human-readable] [-b|--brief] [-p|--pagefile] [-s|--block-size ] \n", argv[0]); } return help; } // Main // // int main(int argc, char **argv) { ntfs_volume *vol; int flags = NTFS_MNT_RDONLY; int ret = 0; // ntfs_log_set_handler(ntfs_log_handler_outerr); if (parse_options(argc, argv) != 0) { return 1; } if (options.force) { flags |= NTFS_MNT_RECOVER; } // vol = utils_mount_volume(options.device, flags); if (vol == NULL) { ntfs_log_error("Device '%s' not found.\n", options.device); return 1; } if (options.human) { ntfs_log_info("# Clustersize %u\n", (unsigned int)vol->cluster_size); ntfs_log_info("# Output block size %u\n", (unsigned int)options.output_block_size); if (options.min_size >= 1024 * 1024) { ntfs_log_info("# Minimum range size: %lldMiB\n", (long long)options.min_size / (1024 * 1024)); } else { ntfs_log_info("# Minimum range size: %lldKiB\n", (long long)options.min_size / 1024); } } options.min_size /= vol->cluster_size; if (options.pagefile) { ret = get_pagefile_clusters(vol); } if (ret >= 0) { ret = scan_free_space(vol); } ntfs_umount(vol, FALSE); return ret > 0 ? 0 : 2; } // Helpers // // // Based on ntfsprogs/utils.* static int utils_valid_device(const char *name, int force) { unsigned long mnt_flags = 0; struct stat st; if (!name) { errno = EINVAL; return 0; } if (stat(name, &st) == -1) { if (errno == ENOENT) ntfs_log_error("The device %s doesn't exist\n", name); else ntfs_log_perror("Error getting information about %s", name); return 0; } /* Make sure the file system is not mounted. */ if (ntfs_check_if_mounted(name, &mnt_flags)) { ntfs_log_perror("Failed to determine whether %s is mounted", name); if (!force) { ntfs_log_error("Use the force option to ignore this " "error.\n"); return 0; } ntfs_log_warning("Forced to continue.\n"); } else if (mnt_flags & NTFS_MF_MOUNTED) { if (!force) { ntfs_log_error("Volume already mounted or in use otherwise.\n"); ntfs_log_error("You can use force option to avoid this " "check, but this is not recommended\n" "and may lead to data corruption.\n"); return 0; } ntfs_log_warning("Forced to continue.\n"); } return 1; } /** * utils_mount_volume - Mount an NTFS volume * Based on ntfsprogs/utils.* */ static ntfs_volume * utils_mount_volume(const char *device, unsigned long flags) { ntfs_volume *vol; if (!device) { errno = EINVAL; return NULL; } if (!utils_valid_device(device, flags & NTFS_MNT_RECOVER)) return NULL; vol = ntfs_mount(device, flags); if (!vol) { ntfs_log_perror("Failed to mount '%s'", device); if (errno == EINVAL) ntfs_log_error("%s: Not an NTFS device.\n", device); else if (errno == EIO) ntfs_log_error("Corrupted volume. Run chkdsk /f.\n"); else if (errno == EPERM) ntfs_log_error("This volume is hibernated. Please shut down Windows properly.\n"); else if (errno == EOPNOTSUPP) ntfs_log_error("NTFS journal is unclean.\n"); else if (errno == EBUSY) ntfs_log_error("%s", "Busy: Volume already in use.\n"); else if (errno == ENXIO) ntfs_log_error("SoftRAID/FakeRAID is not supported.\n"); return NULL; } if (vol->flags & VOLUME_IS_DIRTY) { if (!(flags & NTFS_MNT_RECOVER)) { ntfs_log_error("Volume is marked dirty, plase boot windows first.\n"); ntfs_umount(vol, FALSE); return NULL; } ntfs_log_error("WARNING: Dirty volume mount was forced by the " "'force' mount option.\n"); } return vol; } ATTR_RECORD * find_attribute(const ATTR_TYPES type, ntfs_attr_search_ctx *ctx) { if (!ctx) { errno = EINVAL; return NULL; } if (ntfs_attr_lookup(type, NULL, 0, 0, 0, NULL, 0, ctx) != 0) { ntfs_log_debug("find_attribute didn't find an attribute of type: 0x%02x.\n", le32_to_cpu(type)); return NULL; /* None / no more of that type */ } ntfs_log_debug("find_attribute found an attribute of type: 0x%02x.\n", le32_to_cpu(type)); return ctx->attr; } // Taken from ntfsprogs/utils.c static int utils_cluster_in_use(ntfs_volume *vol, long long lcn) { static unsigned char buffer[512]; static long long bmplcn = -(sizeof(buffer) << 3); int byte, bit; ntfs_attr *attr; if (!vol) { errno = EINVAL; return -1; } /* Does lcn lie in the section of $Bitmap we already have cached? */ if ((lcn < bmplcn) || (lcn >= (long long)(bmplcn + (sizeof(buffer) << 3)))) { ntfs_log_debug("Bit lies outside cache.\n"); attr = ntfs_attr_open(vol->lcnbmp_ni, AT_DATA, AT_UNNAMED, 0); if (!attr) { ntfs_log_perror("Couldn't open $Bitmap"); return -1; } /* Mark the buffer as in use, in case the read is shorter. */ memset(buffer, 0xFF, sizeof(buffer)); bmplcn = lcn & (~((sizeof(buffer) << 3) - 1)); if (ntfs_attr_pread(attr, (bmplcn >> 3), sizeof(buffer), buffer) < 0) { ntfs_log_perror("Couldn't read $Bitmap"); ntfs_attr_close(attr); return -1; } ntfs_log_debug("Reloaded bitmap buffer.\n"); ntfs_attr_close(attr); } bit = 1 << (lcn & 7); byte = (lcn >> 3) & (sizeof(buffer) - 1); ntfs_log_debug("cluster = %lld, bmplcn = %lld, byte = %d, bit = %d, " "in use %d\n", lcn, bmplcn, byte, bit, buffer[byte] & bit); return (buffer[byte] & bit); } #define TOBLOCK(x) ( (x) * vol->cluster_size / options.output_block_size ) #define TOSIZE(x) ( options.human ? ( (x) / (1024 * 1024 / vol->cluster_size) ) : TOBLOCK(x) ) static int scan_free_space(ntfs_volume *vol) { s64 i, j; s64 curStart = -1; s64 start = 0; s64 end = 0; int pf; const char *message = NULL; const char *summary = NULL; if (!vol) return -1; if (options.brief) { if (options.human) { summary = "Biggest range from %lld to %lld, %lldMiB\n"; } else { summary = "Biggest %lld %lld %lld\n"; } } else { if (options.human) { message = "Range from %lld to %lld, %lldMiB\n"; } else { message = "Range %lld %lld %lld\n"; } } for (i = 0; i <= vol->nr_clusters; i++) { pf = 0; for (j = 0; j < pfcount; ++j) { if (i >= pagefile[j].from && i <= pagefile[j].to) { pf = 1; break; } } if (i == vol->nr_clusters || (!pf && utils_cluster_in_use(vol, i))) { if (curStart != -1) { if (i - curStart > options.min_size) { if (message != NULL) { ntfs_log_info(message, (long long)TOBLOCK(curStart), (long long)TOBLOCK(i), (long long)TOSIZE(i - curStart)); } if (i - curStart > end - start) { start = curStart; end = i; } } } curStart = -1; } else if (curStart == -1) { curStart = i; } } if (summary != NULL) { ntfs_log_info("Biggest range from %lld to %lld, %lldMiB\n", (long long)TOBLOCK(start), (long long)TOBLOCK(end), (long long)TOSIZE(end - start)); } return 1; } static int get_pagefile_clusters(ntfs_volume *vol) { ntfs_inode *ni = NULL; ntfs_attr *na = NULL; ntfs_attr_search_ctx *ctx; int returnCode = 0; ATTR_RECORD *rec; int i, clusters; runlist *runs; ni = ntfs_pathname_to_inode(vol, NULL, "pagefile.sys"); if (!ni) { ntfs_log_debug("Failed to open inode of pagefile.sys.\n"); return 0; } if ((na = ntfs_attr_open(ni, AT_DATA, AT_UNNAMED, 0)) == NULL) { ntfs_log_debug("Failed to open pagefile.sys/$DATA.\n"); goto error_exit; } /* The $DATA attribute of the pagefile.sys has to be non-resident. */ if (!NAttrNonResident(na)) { ntfs_log_warning("pagefile.sys $DATA attribute is resident!?!\n"); goto error_exit; } /* Get length of pagefile.sys contents. */ clusters = (na->data_size + (vol->cluster_size - 1)) / vol->cluster_size; if (clusters == 0) { ntfs_log_warning("pagefile.sys has zero length.\n"); goto real_exit; } if ((na->data_flags & ATTR_COMPRESSION_MASK) != const_cpu_to_le16(0)) { ntfs_log_warning("pagefile.sys is compressed!?\n"); goto real_exit; } ntfs_attr_close(na); na = NULL; // Get complete runlist ctx = ntfs_attr_get_search_ctx(ni, NULL); while (pfcount < PFMAX && (rec = find_attribute(AT_DATA, ctx))) { if (rec->non_resident) { runs = ntfs_mapping_pairs_decompress(vol, rec, NULL); if (runs) { for (i = 0; pfcount < PFMAX && runs[i].length > 0; i++) { pagefile[pfcount].from = runs[i].lcn; pagefile[pfcount].to = runs[i].lcn + (runs[i].length - 1); pfcount++; } free(runs); } } } ntfs_attr_put_search_ctx(ctx); // All done goto real_exit; error_exit: returnCode = -1; real_exit: if (na != NULL) { ntfs_attr_close(na); } if (ni != NULL) { ntfs_inode_close(ni); } return returnCode; }