From 4b6a44e7082e52f667348b583d1d3c8bd46892f0 Mon Sep 17 00:00:00 2001 From: Simon Rettberg Date: Fri, 10 Feb 2017 14:42:07 +0100 Subject: Initial commit --- .gitignore | 5 + EXAMPLE | 7 + Makefile | 2 + config.h | 84 ++++++++++++ main.c | 427 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 525 insertions(+) create mode 100644 .gitignore create mode 100644 EXAMPLE create mode 100644 Makefile create mode 100644 config.h create mode 100644 main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fc40a45 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.swp +*~ +*.o +/ntfsfree + diff --git a/EXAMPLE b/EXAMPLE new file mode 100644 index 0000000..2c1505a --- /dev/null +++ b/EXAMPLE @@ -0,0 +1,7 @@ +# Get all free ranges that are at least 500MB, +# pagefile.sys will be ignored (considered free space) +# Then sort them by size, descending +./ntfsfree -m 50000000 -p /tmp/test.ntfs | sort -r -k 4,4 -n -b + +# Human readable listing of all free ranges of at least 1MB +./ntfsfree -m 1000000 -h /dev/sda5 diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..399a370 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +all: + gcc -Wall -Wextra -pedantic -o ntfsfree main.c -lntfs-3g diff --git a/config.h b/config.h new file mode 100644 index 0000000..c35518a --- /dev/null +++ b/config.h @@ -0,0 +1,84 @@ +#define HAVE_ATEXIT 1 +#define HAVE_BASENAME 1 +#define HAVE_BYTESWAP_H 1 +#define HAVE_CLOCK_GETTIME 1 +#define HAVE_CTYPE_H 1 +#define HAVE_DAEMON 1 +#define HAVE_DLFCN_H 1 +#define HAVE_DUP2 1 +#define HAVE_ENDIAN_H 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCNTL_H 1 +#define HAVE_FDATASYNC 1 +#define HAVE_FEATURES_H 1 +#define HAVE_FFS 1 +#define HAVE_FORK 1 +#define HAVE_GETMNTENT 1 +#define HAVE_GETOPT_H 1 +#define HAVE_GETOPT_LONG 1 +#define HAVE_GETTIMEOFDAY 1 +#define HAVE_HASMNTOPT 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_LIBGEN_H 1 +#define HAVE_LIBINTL_H 1 +#define HAVE_LIMITS_H 1 +#define HAVE_LINUX_FD_H 1 +#define HAVE_LINUX_FS_H 1 +#define HAVE_LINUX_HDREG_H 1 +#define HAVE_LINUX_MAJOR_H 1 +#define HAVE_LOCALE_H 1 +#define HAVE_MALLOC_H 1 +#define HAVE_MBRTOWC 1 +#define HAVE_MBSINIT 1 +#define HAVE_MEMCPY 1 +#define HAVE_MEMMOVE 1 +#define HAVE_MEMORY_H 1 +#define HAVE_MEMSET 1 +#define HAVE_MNTENT_H 1 +#define HAVE_PWD_H 1 +#define HAVE_RANDOM 1 +#define HAVE_REALPATH 1 +#define HAVE_REGCOMP 1 +#define HAVE_REGEX_H 1 +#define HAVE_SETLOCALE 1 +#define HAVE_SETXATTR 1 +#define HAVE_SNPRINTF 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDBOOL_H 1 +#define HAVE_STDDEF_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDIO_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRCHR 1 +#define HAVE_STRDUP 1 +#define HAVE_STRERROR 1 +#define HAVE_STRFTIME 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRING_H 1 +#define HAVE_STRNLEN 1 +#define HAVE_STRSEP 1 +#define HAVE_STRTOL 1 +#define HAVE_STRTOUL 1 +#define HAVE_STRUCT_STAT_ST_ATIM 1 +#define HAVE_STRUCT_STAT_ST_BLOCKS 1 +#define HAVE_STRUCT_STAT_ST_RDEV 1 +#define HAVE_ST_BLOCKS 1 +#define HAVE_SYSCONF 1 +#define HAVE_SYSLOG_H 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_MOUNT_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_STATVFS_H 1 +#define HAVE_SYS_STAT_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_VFS_H 1 +#define HAVE_TIME_H 1 +#define HAVE_UNISTD_H 1 +#define HAVE_UTIME 1 +#define HAVE_UTIMENSAT 1 +#define HAVE_UTIME_H 1 +#define HAVE_UTIME_NULL 1 +#define HAVE_VPRINTF 1 +#define HAVE_WCHAR_H 1 +#define HAVE__BOOL 1 diff --git a/main.c b/main.c new file mode 100644 index 0000000..b55e791 --- /dev/null +++ b/main.c @@ -0,0 +1,427 @@ +#include +#include +#include +#include +#include +#include + +#include "config.h" +#include +#include + +#define PFMAX (10) +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; + 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:hbfp"; + static const struct option lopt[] = { + { "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.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; + default: + ntfs_log_error("Unknown option '%s'.\n", argv[optind-1]); + help = 1; + break; + } + if (help) break; + } + + if (help || options.min_size < 0) { + ntfs_log_error("Usage: %s [-m|--min-size ] [-h|--human-readable] [-b|--brief] [-p|--pagefile] \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; + } + 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); +} + +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)curStart, (long long)i, (long long)((i - curStart) / (1024ll * 1024ll / vol->cluster_size))); + } + 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)start, (long long)end, (long long)((end - start) / (1024ll * 1024ll / vol->cluster_size))); + } + 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_debug("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_debug("pagefile.sys has zero length.\n"); + goto real_exit; + } + + if ((na->data_flags & ATTR_COMPRESSION_MASK) != const_cpu_to_le16(0)) { + ntfs_log_debug("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; 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; + +} + -- cgit v1.2.3-55-g7522