summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Rettberg2017-02-10 14:42:07 +0100
committerSimon Rettberg2017-02-10 14:42:07 +0100
commit4b6a44e7082e52f667348b583d1d3c8bd46892f0 (patch)
treeafc3edd3929f43b0131c1631332bd7ccce19a2a8
downloadntfsfree-4b6a44e7082e52f667348b583d1d3c8bd46892f0.tar.gz
ntfsfree-4b6a44e7082e52f667348b583d1d3c8bd46892f0.tar.xz
ntfsfree-4b6a44e7082e52f667348b583d1d3c8bd46892f0.zip
Initial commit
-rw-r--r--.gitignore5
-rw-r--r--EXAMPLE7
-rw-r--r--Makefile2
-rw-r--r--config.h84
-rw-r--r--main.c427
5 files changed, 525 insertions, 0 deletions
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 <stdio.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+
+#include "config.h"
+#include <ntfs-3g/volume.h>
+#include <ntfs-3g/dir.h>
+
+#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 <bytes>] [-h|--human-readable] [-b|--brief] [-p|--pagefile] <device>\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;
+
+}
+