summaryrefslogblamecommitdiffstats
path: root/main.c
blob: 493aa9ba70227d896ae2754660dba201fb99d4a5 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11










                           
                   











                       
                              














                                                                                 
                                              
                                             
                                                                    


















                                                                    
                                      



























                                                                       


                                                                            






                                                                                 


                                     
 

                                                                                                                                                                     



























                                                                           


                                                                                                   




                                                                                                                      
         







































































































































































                                                                                                                


                                                                                              






































                                                                                     
                                                                                                                                                             













                                                                         
                                                                                                                                                                


























                                                                             
                                                                                  





                                                                                 
                                                                    



                                                                               
                                                                   











                                                                             
                                                                                         
























                                                                                                  
#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 (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 <bytes>] [-h|--human-readable] [-b|--brief] [-p|--pagefile] [-s|--block-size <bytes>] <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;
	}
	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;

}