1491 lines
36 KiB
C
1491 lines
36 KiB
C
/**
|
|
* ntfsresize - Part of the Linux-NTFS project.
|
|
*
|
|
* Copyright (c) 2002-2004 Szabolcs Szakacsits
|
|
* Copyright (c) 2002-2003 Anton Altaparmakov
|
|
* Copyright (c) 2002-2003 Richard Russon
|
|
*
|
|
* This utility will resize an NTFS volume.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program (in the main directory of the Linux-NTFS
|
|
* distribution in the file COPYING); if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <stdarg.h>
|
|
#include <string.h>
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
|
|
#include "debug.h"
|
|
#include "types.h"
|
|
#include "support.h"
|
|
#include "endians.h"
|
|
#include "bootsect.h"
|
|
#include "device.h"
|
|
#include "attrib.h"
|
|
#include "volume.h"
|
|
#include "mft.h"
|
|
#include "bitmap.h"
|
|
#include "inode.h"
|
|
#include "runlist.h"
|
|
#include "utils.h"
|
|
|
|
static const char *EXEC_NAME = "ntfsresize";
|
|
|
|
static const char *resize_warning_msg =
|
|
"WARNING: Every sanity check passed and only the DANGEROUS operations left.\n"
|
|
"Please make sure all your important data had been backed up in case of an\n"
|
|
"unexpected failure!\n";
|
|
|
|
static const char *resize_important_msg =
|
|
"You can go on to shrink the device e.g. with 'fdisk'.\n"
|
|
"IMPORTANT: When recreating the partition, make sure you\n"
|
|
" 1) create it with the same starting disk cylinder\n"
|
|
" 2) create it with the same partition type (usually 7, HPFS/NTFS)\n"
|
|
" 3) do not make it smaller than the new NTFS filesystem size\n"
|
|
" 4) set the bootable flag for the partition if it existed before\n"
|
|
"Otherwise you may lose your data or can't boot your computer from the disk!\n";
|
|
|
|
static const char *fragmented_volume_msg =
|
|
"The volume end is fragmented, this case is not yet supported.\n";
|
|
|
|
struct {
|
|
int verbose;
|
|
int quiet;
|
|
int debug;
|
|
int ro_flag;
|
|
int force;
|
|
int info;
|
|
int show_progress;
|
|
s64 bytes;
|
|
char *volume;
|
|
} opt;
|
|
|
|
struct bitmap {
|
|
u8 *bm;
|
|
s64 size;
|
|
};
|
|
|
|
struct progress_bar {
|
|
u64 start;
|
|
u64 stop;
|
|
int resolution;
|
|
float unit;
|
|
};
|
|
|
|
struct llcn_t {
|
|
s64 lcn; /* last used LCN for a "special" file/attr type */
|
|
s64 inode; /* inode using it */
|
|
};
|
|
|
|
struct __ntfs_resize_t {
|
|
s64 new_volume_size; /* in clusters */
|
|
int shrink; /* shrink = 1, enlarge = 0 */
|
|
ntfs_inode *ni; /* inode being processed */
|
|
ntfs_attr_search_ctx *ctx; /* inode attribute being processed */
|
|
u64 relocations; /* num of clusters to relocate */
|
|
u64 inuse; /* num of clusters in use */
|
|
int multi_ref; /* num of clusters ref'd many times */
|
|
/* Temporary statistics until all case is supported */
|
|
struct llcn_t last_mft;
|
|
struct llcn_t last_mftmir;
|
|
struct llcn_t last_multi_mft;
|
|
struct llcn_t last_sparse;
|
|
struct llcn_t last_compressed;
|
|
struct llcn_t last_lcn;
|
|
s64 last_unsupp; /* last unsupported cluster */
|
|
};
|
|
|
|
typedef struct __ntfs_resize_t ntfs_resize_t;
|
|
|
|
ntfs_volume *vol = NULL;
|
|
struct bitmap lcn_bitmap;
|
|
|
|
#define NTFS_MBYTE (1000 * 1000)
|
|
|
|
#define ERR_PREFIX "ERROR"
|
|
#define PERR_PREFIX ERR_PREFIX "(%d): "
|
|
#define NERR_PREFIX ERR_PREFIX ": "
|
|
|
|
#define rounded_up_division(a, b) (((a) + (b - 1)) / (b))
|
|
|
|
/* FIXME: They should be included but Eprintf conflicts with mkntfs's Eprintf */
|
|
extern int Eprintf (const char *format, ...) __attribute__ ((format (printf, 1, 2)));
|
|
extern int Vprintf (const char *format, ...) __attribute__ ((format (printf, 1, 2)));
|
|
extern int Qprintf (const char *format, ...) __attribute__ ((format (printf, 1, 2)));
|
|
|
|
GEN_PRINTF (Eprintf, stderr, NULL, FALSE)
|
|
GEN_PRINTF (Vprintf, stdout, &opt.verbose, TRUE)
|
|
GEN_PRINTF (Qprintf, stdout, &opt.quiet, FALSE)
|
|
|
|
/**
|
|
* perr_printf
|
|
*
|
|
* Print an error message.
|
|
*/
|
|
static void perr_printf(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int eo = errno;
|
|
|
|
fprintf(stdout, PERR_PREFIX, eo);
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
printf(": %s\n", strerror(eo));
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
}
|
|
|
|
/**
|
|
* err_exit
|
|
*
|
|
* Print and error message and exit the program.
|
|
*/
|
|
static int err_exit(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
fprintf(stdout, NERR_PREFIX);
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* perr_exit
|
|
*
|
|
* Print and error message and exit the program
|
|
*/
|
|
static int perr_exit(const char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int eo = errno;
|
|
|
|
fprintf(stdout, PERR_PREFIX, eo);
|
|
va_start(ap, fmt);
|
|
vfprintf(stdout, fmt, ap);
|
|
va_end(ap);
|
|
printf(": %s\n", strerror(eo));
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* usage - Print a list of the parameters to the program
|
|
*
|
|
* Print a list of the parameters and options for the program.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void usage(void)
|
|
{
|
|
|
|
printf ("\nUsage: %s [options] device\n"
|
|
" Resize an NTFS volume non-destructively.\n"
|
|
"\n"
|
|
" -i --info Calculate the smallest shrunken size supported\n"
|
|
" -s num --size num Resize volume to num[k|M|G] bytes\n"
|
|
"\n"
|
|
" -n --no-action Do not write to disk\n"
|
|
" -f --force Force to progress (DANGEROUS)\n"
|
|
" -P --no-progress-bar Don't show progress bar\n"
|
|
/* " -q --quiet Less output\n"*/
|
|
/* " -v --verbose More output\n"*/
|
|
" -V --version Display version information\n"
|
|
" -h --help Display this help\n"
|
|
#ifdef DEBUG
|
|
" -d --debug Show debug information\n"
|
|
#endif
|
|
"\n"
|
|
" If -i and -s are used together then print information about relocations.\n"
|
|
" If both are omitted then the volume will be enlarged to the device size.\n"
|
|
"\n", EXEC_NAME);
|
|
printf ("%s%s\n", ntfs_bugs, ntfs_home);
|
|
exit(1);
|
|
}
|
|
|
|
/**
|
|
* proceed_question
|
|
*
|
|
* Force the user to confirm an action before performing it.
|
|
* Copy-paste from e2fsprogs
|
|
*/
|
|
static void proceed_question(void)
|
|
{
|
|
char buf[256];
|
|
const char *short_yes = "yY";
|
|
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
printf("Are you sure you want to proceed (y/[n])? ");
|
|
buf[0] = 0;
|
|
fgets(buf, sizeof(buf), stdin);
|
|
if (strchr(short_yes, buf[0]) == 0) {
|
|
printf("OK quitting. NO CHANGES have been made to your "
|
|
"NTFS volume.\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* version - Print version information about the program
|
|
*
|
|
* Print a copyright statement and a brief description of the program.
|
|
*
|
|
* Return: none
|
|
*/
|
|
static void version (void)
|
|
{
|
|
printf ("\nResize an NTFS Volume, without data loss.\n\n");
|
|
printf ("Copyright (c) 2002-2003 Szabolcs Szakacsits\n");
|
|
printf ("Copyright (c) 2002-2003 Anton Altaparmakov\n");
|
|
printf ("Copyright (c) 2002-2003 Richard Russon\n");
|
|
printf ("\n%s\n%s%s\n", ntfs_gpl, ntfs_bugs, ntfs_home);
|
|
}
|
|
|
|
/**
|
|
* get_new_volume_size
|
|
*
|
|
* Convert a user-supplied string into a size. Without any suffix the number
|
|
* will be assumed to be in bytes. If the number has a suffix of k, M or G it
|
|
* will be scaled up by 1000, 1000000, or 1000000000.
|
|
*/
|
|
static s64 get_new_volume_size(char *s)
|
|
{
|
|
s64 size;
|
|
char *suffix;
|
|
int prefix_kind = 1000;
|
|
|
|
size = strtoll(s, &suffix, 10);
|
|
if (size <= 0 || errno == ERANGE)
|
|
err_exit("Illegal new volume size\n");
|
|
|
|
if (!*suffix)
|
|
return size;
|
|
|
|
if (strlen(suffix) == 2 && suffix[1] == 'i')
|
|
prefix_kind = 1024;
|
|
else if (strlen(suffix) > 1)
|
|
usage();
|
|
|
|
/* We follow the SI prefixes:
|
|
http://physics.nist.gov/cuu/Units/prefixes.html
|
|
http://physics.nist.gov/cuu/Units/binary.html
|
|
Disk partitioning tools use prefixes as,
|
|
k M G
|
|
old fdisk 2^10 2^20 10^3*2^20
|
|
recent fdisk 10^3 10^6 10^9
|
|
cfdisk 10^3 10^6 10^9
|
|
sfdisk 2^10 2^20
|
|
parted 2^10 2^20 (may change)
|
|
fdisk (DOS) 2^10 2^20
|
|
*/
|
|
/* FIXME: check for overflow */
|
|
switch (*suffix) {
|
|
case 'G':
|
|
size *= prefix_kind;
|
|
case 'M':
|
|
size *= prefix_kind;
|
|
case 'k':
|
|
size *= prefix_kind;
|
|
break;
|
|
default:
|
|
usage();
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
/**
|
|
* parse_options - Read and validate the programs command line
|
|
*
|
|
* Read the command line, verify the syntax and parse the options.
|
|
* This function is very long, but quite simple.
|
|
*
|
|
* Return: 1 Success
|
|
* 0 Error, one or more problems
|
|
*/
|
|
static int parse_options(int argc, char **argv)
|
|
{
|
|
static const char *sopt = "-dfhinPs:vV";
|
|
static const struct option lopt[] = {
|
|
#ifdef DEBUG
|
|
{ "debug", no_argument, NULL, 'd' },
|
|
#endif
|
|
{ "force", no_argument, NULL, 'f' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "info", no_argument, NULL, 'i' },
|
|
{ "no-action", no_argument, NULL, 'n' },
|
|
/* { "quiet", no_argument, NULL, 'q' },*/
|
|
{ "size", required_argument, NULL, 's' },
|
|
{ "no-progress-bar", no_argument, NULL, 'P' },
|
|
/* { "verbose", no_argument, NULL, 'v' },*/
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
char c;
|
|
int err = 0;
|
|
int ver = 0;
|
|
int help = 0;
|
|
|
|
memset(&opt, 0, sizeof(opt));
|
|
opt.show_progress = 1;
|
|
|
|
while ((c = getopt_long (argc, argv, sopt, lopt, NULL)) != -1) {
|
|
switch (c) {
|
|
case 1: /* A non-option argument */
|
|
if (!err && !opt.volume)
|
|
opt.volume = argv[optind-1];
|
|
else
|
|
err++;
|
|
break;
|
|
case 'd':
|
|
opt.debug++;
|
|
break;
|
|
case 'f':
|
|
opt.force++;
|
|
break;
|
|
case 'h':
|
|
case '?':
|
|
help++;
|
|
break;
|
|
case 'i':
|
|
opt.info++;
|
|
break;
|
|
case 'n':
|
|
opt.ro_flag = MS_RDONLY;
|
|
break;
|
|
case 'P':
|
|
opt.show_progress = 0;
|
|
break;
|
|
case 'q':
|
|
opt.quiet++;
|
|
break;
|
|
case 's':
|
|
if (!err && (opt.bytes == 0))
|
|
opt.bytes = get_new_volume_size(optarg);
|
|
else
|
|
err++;
|
|
break;
|
|
case 'v':
|
|
opt.verbose++;
|
|
break;
|
|
case 'V':
|
|
ver++;
|
|
break;
|
|
default:
|
|
if (optopt == 's') {
|
|
Eprintf ("Option '%s' requires an argument.\n", argv[optind-1]);
|
|
} else {
|
|
Eprintf ("Unknown option '%s'.\n", argv[optind-1]);
|
|
}
|
|
err++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (help || ver) {
|
|
opt.quiet = 0;
|
|
} else {
|
|
if (opt.volume == NULL) {
|
|
if (argc > 1)
|
|
Eprintf ("You must specify exactly one device.\n");
|
|
err++;
|
|
}
|
|
|
|
/*
|
|
if (opt.quiet && opt.verbose) {
|
|
Eprintf ("You may not use --quiet and --verbose at the same time.\n");
|
|
err++;
|
|
}
|
|
*/
|
|
|
|
if (opt.info)
|
|
opt.ro_flag = MS_RDONLY;
|
|
}
|
|
|
|
stderr = stdout;
|
|
|
|
#ifdef DEBUG
|
|
if (!opt.debug)
|
|
if (!(stderr = fopen("/dev/null", "rw")))
|
|
perr_exit("Couldn't open /dev/null");
|
|
#endif
|
|
|
|
if (ver)
|
|
version();
|
|
if (help || err)
|
|
usage();
|
|
|
|
return (!err && !help && !ver);
|
|
}
|
|
|
|
/**
|
|
* nr_clusters_to_bitmap_byte_size
|
|
*
|
|
* Take the number of clusters in the volume and calculate the size of $Bitmap.
|
|
* The size will always be a multiple of 8 bytes.
|
|
*/
|
|
static s64 nr_clusters_to_bitmap_byte_size(s64 nr_clusters)
|
|
{
|
|
s64 bm_bsize;
|
|
|
|
bm_bsize = rounded_up_division(nr_clusters, 8);
|
|
|
|
bm_bsize = (bm_bsize + 7) & ~7;
|
|
Dprintf("Bitmap byte size : %lld (%lld clusters)\n",
|
|
bm_bsize, rounded_up_division(bm_bsize, vol->cluster_size));
|
|
|
|
return bm_bsize;
|
|
}
|
|
|
|
static int str2unicode(const char *aname, uchar_t **ustr, int *len)
|
|
{
|
|
if (aname && ((*len = ntfs_mbstoucs(aname, ustr, 0)) == -1))
|
|
return -1;
|
|
|
|
if (!*ustr || !*len) {
|
|
*ustr = AT_UNNAMED;
|
|
*len = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int has_bad_sectors(ntfs_resize_t *resize)
|
|
{
|
|
int len, ret = 0;
|
|
uchar_t *ustr = NULL;
|
|
ATTR_RECORD *a = resize->ctx->attr;
|
|
|
|
if (resize->ni->mft_no != 8)
|
|
return 0;
|
|
|
|
if (str2unicode("$Bad", &ustr, &len) == -1)
|
|
return -1;
|
|
|
|
if (ustr && ntfs_names_are_equal(ustr, len,
|
|
(uchar_t*)((char*)a + le16_to_cpu(a->name_offset)),
|
|
a->name_length, 0, NULL, 0))
|
|
ret = 1;
|
|
|
|
if (ustr != AT_UNNAMED)
|
|
free(ustr);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void collect_shrink_constraints(ntfs_resize_t *resize, s64 last_lcn)
|
|
{
|
|
s64 inode;
|
|
ATTR_FLAGS flags;
|
|
struct llcn_t *llcn = NULL;
|
|
int ret;
|
|
|
|
inode = resize->ni->mft_no;
|
|
flags = resize->ctx->attr->flags;
|
|
|
|
if (NInoAttrList(resize->ni))
|
|
llcn = &resize->last_multi_mft;
|
|
|
|
else if (flags & ATTR_IS_SPARSE)
|
|
llcn = &resize->last_sparse;
|
|
|
|
else if (flags & ATTR_IS_COMPRESSED)
|
|
llcn = &resize->last_compressed;
|
|
|
|
else if ((ret = has_bad_sectors(resize)) != 0) {
|
|
if (ret == -1)
|
|
perr_exit("Couldn't convert string to Unicode.");
|
|
err_exit("Device has bad sectors, not supported yet.\n");
|
|
|
|
} else if (inode == 0)
|
|
llcn = &resize->last_mft;
|
|
|
|
else if (inode == 1)
|
|
llcn = &resize->last_mftmir;
|
|
|
|
else
|
|
llcn = &resize->last_lcn;
|
|
|
|
if (llcn->lcn < last_lcn) {
|
|
llcn->lcn = last_lcn;
|
|
llcn->inode = inode;
|
|
}
|
|
|
|
if (resize->last_unsupp < last_lcn)
|
|
resize->last_unsupp = last_lcn;
|
|
}
|
|
|
|
|
|
static void collect_shrink_info(ntfs_resize_t *resize, runlist *rl)
|
|
{
|
|
s64 new_volume_size, lcn, lcn_length;
|
|
|
|
lcn = rl->lcn;
|
|
lcn_length = rl->length;
|
|
new_volume_size = resize->new_volume_size;
|
|
|
|
if (lcn + (lcn_length - 1) > new_volume_size) {
|
|
|
|
s64 start = lcn;
|
|
s64 len = lcn_length;
|
|
|
|
if (start <= new_volume_size) {
|
|
start = new_volume_size + 1;
|
|
len = lcn_length - (start - lcn);
|
|
}
|
|
|
|
resize->relocations += len;
|
|
|
|
if (opt.info && !resize->new_volume_size)
|
|
return;
|
|
|
|
printf("Relocation needed for inode %8lld attr 0x%x LCN 0x%08llx "
|
|
"length %6lld\n", resize->ni->mft_no,
|
|
resize->ctx->attr->type, start, len);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* build_lcn_usage_bitmap
|
|
*
|
|
* lcn_bitmap has one bit for each cluster on the disk. Initially, lcn_bitmap
|
|
* has no bits set. As each attribute record is read the bits in lcn_bitmap are
|
|
* checked to ensure that no other file already references that cluster.
|
|
*
|
|
* This serves as a rudimentary "chkdsk" operation.
|
|
*/
|
|
static void build_lcn_usage_bitmap(ntfs_resize_t *resize)
|
|
{
|
|
s64 inode;
|
|
ATTR_RECORD *a;
|
|
runlist *rl;
|
|
int i, j;
|
|
|
|
a = resize->ctx->attr;
|
|
inode = resize->ni->mft_no;
|
|
|
|
if (!a->non_resident)
|
|
return;
|
|
|
|
if (!(rl = ntfs_mapping_pairs_decompress(vol, a, NULL)))
|
|
perr_exit("ntfs_decompress_mapping_pairs");
|
|
|
|
for (i = 0; rl[i].length; i++) {
|
|
s64 lcn = rl[i].lcn;
|
|
s64 lcn_length = rl[i].length;
|
|
|
|
/* CHECKME: LCN_RL_NOT_MAPPED check isn't needed */
|
|
if (lcn == LCN_HOLE || lcn == LCN_RL_NOT_MAPPED)
|
|
continue;
|
|
|
|
/* FIXME: ntfs_mapping_pairs_decompress should return error */
|
|
if (lcn < 0 || lcn_length <= 0)
|
|
err_exit("Corrupt runlist in inode %lld attr %x LCN "
|
|
"%llx length %llx\n", inode,
|
|
le32_to_cpu (a->type), lcn, lcn_length);
|
|
|
|
for (j = 0; j < lcn_length; j++) {
|
|
u64 k = (u64)lcn + j;
|
|
if (ntfs_bit_get_and_set(lcn_bitmap.bm, k, 1)) {
|
|
|
|
if (++resize->multi_ref > 10)
|
|
continue;
|
|
|
|
printf("Cluster %llu (0x%llx) referenced "
|
|
"multiply times!\n", k, k);
|
|
}
|
|
}
|
|
|
|
resize->inuse += lcn_length;
|
|
|
|
collect_shrink_constraints(resize, lcn + (lcn_length - 1));
|
|
|
|
if (resize->shrink)
|
|
collect_shrink_info(resize, rl + i);
|
|
}
|
|
free(rl);
|
|
}
|
|
|
|
/**
|
|
* walk_attributes
|
|
*
|
|
* For a given MFT Record, iterate through all its attributes. Any non-resident
|
|
* data runs will be marked in lcn_bitmap.
|
|
*/
|
|
static void walk_attributes(ntfs_resize_t *resize)
|
|
{
|
|
ntfs_attr_search_ctx *ctx;
|
|
|
|
if (!(ctx = ntfs_attr_get_search_ctx(resize->ni, NULL)))
|
|
perr_exit("ntfs_get_attr_search_ctx");
|
|
|
|
while (!ntfs_attrs_walk(ctx)) {
|
|
if (ctx->attr->type == AT_END)
|
|
break;
|
|
resize->ctx = ctx;
|
|
build_lcn_usage_bitmap(resize);
|
|
}
|
|
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
}
|
|
|
|
/**
|
|
* compare_bitmaps
|
|
*
|
|
* Compare two bitmaps. In this case, $Bitmap as read from the disk and
|
|
* lcn_bitmap which we built from the MFT Records.
|
|
*/
|
|
static void compare_bitmaps(struct bitmap *a)
|
|
{
|
|
s64 i, pos, count;
|
|
int mismatch = 0;
|
|
int backup_boot = 0;
|
|
u8 bm[NTFS_BUF_SIZE];
|
|
|
|
printf("Accounting clusters ...\n");
|
|
|
|
pos = 0;
|
|
while (1) {
|
|
count = ntfs_attr_pread(vol->lcnbmp_na, pos, NTFS_BUF_SIZE, bm);
|
|
if (count == -1)
|
|
perr_exit("Couldn't get $Bitmap $DATA");
|
|
|
|
if (count == 0) {
|
|
if (a->size != pos)
|
|
err_exit("$Bitmap file size doesn't match "
|
|
"calculated size (%lld != %lld)\n",
|
|
a->size, pos);
|
|
break;
|
|
}
|
|
|
|
for (i = 0; i < count; i++, pos++) {
|
|
s64 cl; /* current cluster */
|
|
|
|
if (a->bm[pos] == bm[i])
|
|
continue;
|
|
|
|
for (cl = pos * 8; cl < (pos + 1) * 8; cl++) {
|
|
char bit;
|
|
|
|
bit = ntfs_bit_get(a->bm, cl);
|
|
if (bit == ntfs_bit_get(bm, i * 8 + cl % 8))
|
|
continue;
|
|
|
|
if (!mismatch && !bit && !backup_boot &&
|
|
cl == vol->nr_clusters / 2) {
|
|
/* FIXME: call also boot sector check */
|
|
backup_boot = 1;
|
|
printf("Found backup boot sector in "
|
|
"the middle of the volume.\n");
|
|
continue;
|
|
}
|
|
|
|
if (++mismatch > 10)
|
|
continue;
|
|
|
|
printf("Cluster accounting failed at %llu "
|
|
"(0x%llx): %s cluster in $Bitmap\n",
|
|
cl, cl, bit ? "missing" : "extra");
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mismatch) {
|
|
printf("Totally %d cluster accounting mismatches.\n",
|
|
mismatch);
|
|
err_exit("Filesystem check failed! Windows wasn't shutdown "
|
|
"properly or inconsistent\nfilesystem. Please run "
|
|
"chkdsk /f on Windows.\n");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* progress_init
|
|
*
|
|
* Create and scale our progress bar.
|
|
*/
|
|
static void progress_init(struct progress_bar *p, u64 start, u64 stop, int res)
|
|
{
|
|
p->start = start;
|
|
p->stop = stop;
|
|
p->unit = 100.0 / (stop - start);
|
|
p->resolution = res;
|
|
}
|
|
|
|
/**
|
|
* progress_update
|
|
*
|
|
* Update the progress bar and tell the user.
|
|
*/
|
|
static void progress_update(struct progress_bar *p, u64 current)
|
|
{
|
|
float percent;
|
|
|
|
if (!opt.show_progress)
|
|
return;
|
|
|
|
percent = p->unit * current;
|
|
if (current != p->stop) {
|
|
if ((current - p->start) % p->resolution)
|
|
return;
|
|
printf("%6.2f percent completed\r", percent);
|
|
} else
|
|
printf("100.00 percent completed\n");
|
|
fflush(stdout);
|
|
}
|
|
|
|
/**
|
|
* walk_inodes
|
|
*
|
|
* Read each record in the MFT, skipping the unused ones, and build up a bitmap
|
|
* from all the non-resident attributes.
|
|
*/
|
|
static void walk_inodes(ntfs_resize_t *resize)
|
|
{
|
|
s64 inode = 0;
|
|
s64 last_mft_rec;
|
|
ntfs_inode *ni;
|
|
struct progress_bar progress;
|
|
|
|
printf("Checking filesystem consistency ...\n");
|
|
|
|
last_mft_rec = vol->nr_mft_records - 1;
|
|
progress_init(&progress, inode, last_mft_rec, 100);
|
|
|
|
for (; inode <= last_mft_rec; inode++) {
|
|
progress_update(&progress, inode);
|
|
|
|
if ((ni = ntfs_inode_open(vol, (MFT_REF)inode)) == NULL) {
|
|
/* FIXME: continue only if it make sense, e.g.
|
|
MFT record not in use based on $MFT bitmap */
|
|
if (errno == EIO || errno == ENOENT)
|
|
continue;
|
|
perr_exit("Reading inode %lld failed", inode);
|
|
}
|
|
|
|
if ((ni->mrec->base_mft_record) != 0)
|
|
goto close_inode;
|
|
|
|
resize->ni = ni;
|
|
walk_attributes(resize);
|
|
close_inode:
|
|
if (ntfs_inode_close(ni))
|
|
perr_exit("ntfs_inode_close for inode %lld", inode);
|
|
}
|
|
}
|
|
|
|
static void print_hint(const char *s, struct llcn_t llcn)
|
|
{
|
|
s64 runs_b, runs_mb;
|
|
|
|
if (llcn.lcn == 0)
|
|
return;
|
|
|
|
runs_b = llcn.lcn * vol->cluster_size;
|
|
runs_mb = rounded_up_division(runs_b, NTFS_MBYTE);
|
|
printf("%-19s: %9lld MB %8lld\n", s, runs_mb, llcn.inode);
|
|
}
|
|
|
|
/**
|
|
* advise_on_resize
|
|
*
|
|
* The metadata file $Bitmap has one bit for each cluster on disk. This has
|
|
* already been read into lcn_bitmap. By looking for the last used cluster on
|
|
* the disk, we can work out by how much we can shrink the volume.
|
|
*/
|
|
static void advise_on_resize(ntfs_resize_t *resize)
|
|
{
|
|
s64 old_b, new_b, g_b, old_mb, new_mb, g_mb;
|
|
s64 supp_lcn = 0; /* smallest size supported in LCN */
|
|
int fragmanted_end;
|
|
|
|
printf("Calculating smallest shrunken size supported ...\n");
|
|
|
|
old_b = vol->nr_clusters * vol->cluster_size;
|
|
old_mb = rounded_up_division(old_b, NTFS_MBYTE);
|
|
|
|
printf("File feature Last used at By inode\n");
|
|
print_hint("$MFT", resize->last_mft);
|
|
print_hint("$MFTMirr", resize->last_mftmir);
|
|
print_hint("Compressed", resize->last_compressed);
|
|
print_hint("Sparse", resize->last_sparse);
|
|
print_hint("Multi-Record", resize->last_multi_mft);
|
|
print_hint("Ordinary", resize->last_lcn);
|
|
|
|
supp_lcn = resize->last_unsupp;
|
|
|
|
supp_lcn += 2; /* first free + one for the backup boot sector */
|
|
fragmanted_end = (supp_lcn >= vol->nr_clusters) ? 1 : 0;
|
|
|
|
if (fragmanted_end || !opt.info) {
|
|
printf(fragmented_volume_msg);
|
|
if (fragmanted_end)
|
|
return;
|
|
printf("Now ");
|
|
}
|
|
|
|
new_b = supp_lcn * vol->cluster_size;
|
|
new_mb = rounded_up_division(new_b, NTFS_MBYTE);
|
|
g_b = (vol->nr_clusters - supp_lcn) * vol->cluster_size;
|
|
g_mb = g_b / NTFS_MBYTE;
|
|
|
|
printf("You could resize at %lld bytes ", new_b);
|
|
|
|
if ((new_mb * NTFS_MBYTE) < old_b)
|
|
printf("or %lld MB ", new_mb);
|
|
|
|
printf("(freeing ");
|
|
|
|
if (g_mb && (old_mb - new_mb))
|
|
printf("%lld MB", old_mb - new_mb);
|
|
else
|
|
printf("%lld bytes", g_b);
|
|
|
|
printf(").\n");
|
|
}
|
|
|
|
/**
|
|
* rl_set
|
|
*
|
|
* Helper to set up a runlist object
|
|
*/
|
|
static void rl_set(runlist *rl, VCN vcn, LCN lcn, s64 len)
|
|
{
|
|
rl->vcn = vcn;
|
|
rl->lcn = lcn;
|
|
rl->length = len;
|
|
}
|
|
|
|
/**
|
|
* bitmap_file_data_fixup
|
|
*
|
|
* $Bitmap can overlap the end of the volume. Any bits in this region
|
|
* must be set. This region also encompasses the backup boot sector.
|
|
*/
|
|
static void bitmap_file_data_fixup(s64 cluster, struct bitmap *bm)
|
|
{
|
|
for (; cluster < bm->size << 3; cluster++)
|
|
ntfs_bit_set(bm->bm, (u64)cluster, 1);
|
|
}
|
|
|
|
/**
|
|
* truncate_badclust_bad_attr
|
|
*
|
|
* The metadata file $BadClus needs to be shrunk.
|
|
*
|
|
* FIXME: this function should go away and instead using a generalized
|
|
* "truncate_bitmap_data_attr()"
|
|
*/
|
|
static void truncate_badclust_bad_attr(ATTR_RECORD *a, s64 nr_clusters)
|
|
{
|
|
runlist *rl_bad;
|
|
int mp_size;
|
|
char *mp;
|
|
|
|
if (!a->non_resident)
|
|
/* FIXME: handle resident attribute value */
|
|
perr_exit("Resident attribute in $BadClust not supported!");
|
|
|
|
if (!(rl_bad = (runlist *)malloc(2 * sizeof(runlist))))
|
|
perr_exit("Couldn't get memory");
|
|
|
|
rl_set(rl_bad, 0LL, (LCN)LCN_HOLE, nr_clusters);
|
|
rl_set(rl_bad + 1, nr_clusters, -1LL, 0LL);
|
|
|
|
if ((mp_size = ntfs_get_size_for_mapping_pairs(vol, rl_bad)) == -1)
|
|
perr_exit("ntfs_get_size_for_mapping_pairs");
|
|
|
|
if (mp_size > (int)(le32_to_cpu (a->length) -
|
|
le16_to_cpu (a->mapping_pairs_offset)))
|
|
err_exit("Enlarging attribute header isn't supported yet.\n");
|
|
|
|
if (!(mp = (char *)calloc(1, mp_size)))
|
|
perr_exit("Couldn't get memory");
|
|
|
|
if (ntfs_mapping_pairs_build(vol, mp, mp_size, rl_bad))
|
|
perr_exit("ntfs_mapping_pairs_build");
|
|
|
|
memcpy((char *)a + le16_to_cpu (a->mapping_pairs_offset), mp, mp_size);
|
|
a->highest_vcn = cpu_to_le64(nr_clusters - 1LL);
|
|
a->allocated_size = cpu_to_le64(nr_clusters * vol->cluster_size);
|
|
a->data_size = cpu_to_le64(nr_clusters * vol->cluster_size);
|
|
|
|
free(rl_bad);
|
|
free(mp);
|
|
}
|
|
|
|
/**
|
|
* shrink_bitmap_data_attr
|
|
*
|
|
* Shrink the metadata file $Bitmap. It must be large enough for one bit per
|
|
* cluster of the shrunken volume. Also it must be a of 8 bytes in size.
|
|
*/
|
|
static void shrink_bitmap_data_attr(runlist **rlist, s64 nr_bm_clusters, s64 new_size)
|
|
{
|
|
runlist *rl = *rlist;
|
|
int i, j;
|
|
s64 k;
|
|
int trunc_at = -1; /* FIXME: -1 means unset */
|
|
|
|
/* Unallocate truncated clusters in $Bitmap */
|
|
for (i = 0; rl[i].length; i++) {
|
|
if (rl[i].vcn + rl[i].length <= nr_bm_clusters)
|
|
continue;
|
|
if (trunc_at == -1)
|
|
trunc_at = i;
|
|
if (rl[i].lcn == LCN_HOLE || rl[i].lcn == LCN_RL_NOT_MAPPED)
|
|
continue;
|
|
for (j = 0; j < rl[i].length; j++) {
|
|
if (rl[i].vcn + j < nr_bm_clusters)
|
|
continue;
|
|
|
|
k = rl[i].lcn + j;
|
|
if (k < new_size) {
|
|
ntfs_bit_set(lcn_bitmap.bm, k, 0);
|
|
Dprintf("Unallocate cluster: "
|
|
"%lld (%llx)\n", k, k);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (trunc_at != -1) {
|
|
/* NOTE: 'i' always > 0 */
|
|
i = nr_bm_clusters - rl[trunc_at].vcn;
|
|
rl[trunc_at].length = i;
|
|
rl_set(rl + trunc_at + 1, nr_bm_clusters, -1LL, 0LL);
|
|
|
|
Dprintf("Runlist truncated at index %d, "
|
|
"new cluster length %d\n", trunc_at, i);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* enlarge_bitmap_data_attr
|
|
*
|
|
* Enlarge the metadata file $Bitmap. It must be large enough for one bit per
|
|
* cluster of the shrunken volume. Also it must be a of 8 bytes in size.
|
|
*/
|
|
static void enlarge_bitmap_data_attr(runlist **rlist, s64 nr_bm_clusters, s64 new_size)
|
|
{
|
|
runlist *rl = *rlist;
|
|
s64 i, j, free_zone = 0;
|
|
|
|
for (i = 0; rl[i].length; i++)
|
|
for (j = 0; j < rl[i].length; j++)
|
|
ntfs_bit_set(lcn_bitmap.bm, rl[i].lcn + j, 0);
|
|
free(rl);
|
|
|
|
if (!(rl = *rlist = (runlist *)malloc(2 * sizeof(runlist))))
|
|
perr_exit("Couldn't get memory");
|
|
|
|
for (i = vol->nr_clusters; i < new_size; i++)
|
|
ntfs_bit_set(lcn_bitmap.bm, i, 0);
|
|
|
|
for (i = 0; i < new_size; i++) {
|
|
if (!ntfs_bit_get(lcn_bitmap.bm, i)) {
|
|
if (++free_zone == nr_bm_clusters)
|
|
break;
|
|
} else
|
|
free_zone = 0;
|
|
}
|
|
|
|
if (free_zone != nr_bm_clusters)
|
|
err_exit("Couldn't allocate $Bitmap clusters.\n");
|
|
|
|
for (; free_zone; free_zone--, i--)
|
|
ntfs_bit_set(lcn_bitmap.bm, i, 1);
|
|
|
|
rl_set(rl, 0LL, i + 1, nr_bm_clusters);
|
|
rl_set(rl + 1, nr_bm_clusters, -1LL, 0LL);
|
|
}
|
|
|
|
/**
|
|
* truncate_bitmap_data_attr
|
|
*/
|
|
static void truncate_bitmap_data_attr(ntfs_resize_t *resize)
|
|
{
|
|
ATTR_RECORD *a;
|
|
runlist *rl;
|
|
s64 bm_bsize, size;
|
|
s64 nr_bm_clusters;
|
|
s64 nr_clusters;
|
|
int mp_size;
|
|
char *mp;
|
|
u8 *tmp;
|
|
|
|
a = resize->ctx->attr;
|
|
if (!a->non_resident)
|
|
/* FIXME: handle resident attribute value */
|
|
perr_exit("Resident data attribute in $Bitmap not supported!");
|
|
|
|
nr_clusters = resize->new_volume_size;
|
|
bm_bsize = nr_clusters_to_bitmap_byte_size(nr_clusters);
|
|
nr_bm_clusters = rounded_up_division(bm_bsize, vol->cluster_size);
|
|
|
|
if (!(tmp = (u8 *)realloc(lcn_bitmap.bm, bm_bsize)))
|
|
perr_exit("realloc");
|
|
lcn_bitmap.bm = tmp;
|
|
lcn_bitmap.size = bm_bsize;
|
|
bitmap_file_data_fixup(nr_clusters, &lcn_bitmap);
|
|
|
|
if (!(rl = ntfs_mapping_pairs_decompress(vol, a, NULL)))
|
|
perr_exit("ntfs_mapping_pairs_decompress");
|
|
|
|
/* NOTE: shrink could use enlarge_bitmap_data_attr() also. Advantages:
|
|
less code, better code coverage. "Drawback": could be relocated */
|
|
if (resize->shrink)
|
|
shrink_bitmap_data_attr(&rl, nr_bm_clusters, nr_clusters);
|
|
else
|
|
enlarge_bitmap_data_attr(&rl, nr_bm_clusters, nr_clusters);
|
|
|
|
if ((mp_size = ntfs_get_size_for_mapping_pairs(vol, rl)) == -1)
|
|
perr_exit("ntfs_get_size_for_mapping_pairs");
|
|
|
|
if (mp_size > (int)(le32_to_cpu (a->length) -
|
|
le16_to_cpu (a->mapping_pairs_offset)))
|
|
err_exit("Enlarging attribute header isn't supported yet.\n");
|
|
|
|
if (!(mp = (char *)calloc(1, mp_size)))
|
|
perr_exit("Couldn't get memory");
|
|
|
|
if (ntfs_mapping_pairs_build(vol, mp, mp_size, rl))
|
|
perr_exit("ntfs_mapping_pairs_build");
|
|
|
|
memcpy((char *)a + le16_to_cpu (a->mapping_pairs_offset), mp, mp_size);
|
|
a->highest_vcn = cpu_to_le64(nr_bm_clusters - 1LL);
|
|
a->allocated_size = cpu_to_le64(nr_bm_clusters * vol->cluster_size);
|
|
a->data_size = cpu_to_le64(bm_bsize);
|
|
a->initialized_size = cpu_to_le64(bm_bsize);
|
|
|
|
/*
|
|
* FIXME: update allocated/data sizes and timestamps in $FILE_NAME
|
|
* attribute too, for now chkdsk will do this for us.
|
|
*/
|
|
|
|
size = ntfs_rl_pwrite(vol, rl, 0, bm_bsize, lcn_bitmap.bm);
|
|
if (bm_bsize != size) {
|
|
if (size == -1)
|
|
perr_exit("Couldn't write $Bitmap");
|
|
printf("Couldn't write full $Bitmap file "
|
|
"(%lld from %lld)\n", size, bm_bsize);
|
|
exit(1);
|
|
}
|
|
|
|
free(rl);
|
|
free(mp);
|
|
}
|
|
|
|
/**
|
|
* lookup_data_attr
|
|
*
|
|
* Find the $DATA attribute (with or without a name) for the given MFT reference
|
|
* (inode number).
|
|
*/
|
|
static void lookup_data_attr(MFT_REF mref, const char *aname, ntfs_attr_search_ctx **ctx)
|
|
{
|
|
ntfs_inode *ni;
|
|
uchar_t *ustr = NULL;
|
|
int len = 0;
|
|
|
|
if (!(ni = ntfs_inode_open(vol, mref)))
|
|
perr_exit("ntfs_open_inode");
|
|
|
|
if (NInoAttrList(ni))
|
|
perr_exit("Attribute list attribute not yet supported");
|
|
|
|
if (!(*ctx = ntfs_attr_get_search_ctx(ni, NULL)))
|
|
perr_exit("ntfs_get_attr_search_ctx");
|
|
|
|
if (str2unicode(aname, &ustr, &len) == -1)
|
|
perr_exit("Unable to convert string to Unicode");
|
|
|
|
if (ntfs_attr_lookup(AT_DATA, ustr, len, 0, 0, NULL, 0, *ctx))
|
|
perr_exit("ntfs_lookup_attr");
|
|
|
|
if (ustr != AT_UNNAMED)
|
|
free(ustr);
|
|
}
|
|
|
|
/**
|
|
* write_mft_record
|
|
*
|
|
* Write an MFT Record back to the disk. If the read-only command line option
|
|
* was given, this function will do nothing.
|
|
*/
|
|
static int write_mft_record(ntfs_attr_search_ctx *ctx)
|
|
{
|
|
if (ntfs_mft_record_write(vol, ctx->ntfs_ino->mft_no, ctx->mrec))
|
|
perr_exit("ntfs_mft_record_write");
|
|
|
|
if (vol->dev->d_ops->sync(vol->dev) == -1)
|
|
perr_exit("Failed to sync device");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* truncate_badclust_file
|
|
*
|
|
* Shrink the $BadClus file to match the new volume size.
|
|
*/
|
|
static void truncate_badclust_file(s64 nr_clusters)
|
|
{
|
|
ntfs_attr_search_ctx *ctx = NULL;
|
|
|
|
printf("Updating $BadClust file ...\n");
|
|
|
|
lookup_data_attr((MFT_REF)FILE_BadClus, "$Bad", &ctx);
|
|
/* FIXME: sanity_check_attr(ctx->attr); */
|
|
truncate_badclust_bad_attr(ctx->attr, nr_clusters);
|
|
|
|
if (write_mft_record(ctx))
|
|
perr_exit("Couldn't update $BadClust");
|
|
|
|
ntfs_attr_put_search_ctx(ctx);
|
|
}
|
|
|
|
/**
|
|
* truncate_bitmap_file
|
|
*
|
|
* Shrink the $Bitmap file to match the new volume size.
|
|
*/
|
|
static void truncate_bitmap_file(ntfs_resize_t *resize)
|
|
{
|
|
printf("Updating $Bitmap file ...\n");
|
|
|
|
lookup_data_attr((MFT_REF)FILE_Bitmap, NULL, &resize->ctx);
|
|
truncate_bitmap_data_attr(resize);
|
|
|
|
if (write_mft_record(resize->ctx))
|
|
perr_exit("Couldn't update $Bitmap");
|
|
|
|
ntfs_attr_put_search_ctx(resize->ctx);
|
|
}
|
|
|
|
/**
|
|
* setup_lcn_bitmap
|
|
*
|
|
* Allocate a block of memory with one bit for each cluster of the disk.
|
|
* All the bits are set to 0, except those representing the region beyond the
|
|
* end of the disk.
|
|
*/
|
|
static void setup_lcn_bitmap(void)
|
|
{
|
|
/* Determine lcn bitmap byte size and allocate it. */
|
|
lcn_bitmap.size = nr_clusters_to_bitmap_byte_size(vol->nr_clusters);
|
|
|
|
if (!(lcn_bitmap.bm = (unsigned char *)calloc(1, lcn_bitmap.size)))
|
|
perr_exit("Failed to allocate internal buffer");
|
|
|
|
bitmap_file_data_fixup(vol->nr_clusters, &lcn_bitmap);
|
|
}
|
|
|
|
/**
|
|
* update_bootsector
|
|
*
|
|
* FIXME: should be done using ntfs_* functions
|
|
*/
|
|
static void update_bootsector(s64 nr_clusters)
|
|
{
|
|
NTFS_BOOT_SECTOR bs;
|
|
|
|
printf("Updating Boot record ...\n");
|
|
|
|
if (vol->dev->d_ops->seek(vol->dev, 0, SEEK_SET) == (off_t)-1)
|
|
perr_exit("lseek");
|
|
|
|
if (vol->dev->d_ops->read(vol->dev, &bs,
|
|
sizeof(NTFS_BOOT_SECTOR)) == -1)
|
|
perr_exit("read() error");
|
|
|
|
bs.number_of_sectors = nr_clusters * bs.bpb.sectors_per_cluster;
|
|
bs.number_of_sectors = cpu_to_le64(bs.number_of_sectors);
|
|
|
|
if (vol->dev->d_ops->seek(vol->dev, 0, SEEK_SET) == (off_t)-1)
|
|
perr_exit("lseek");
|
|
|
|
if (!opt.ro_flag)
|
|
if (vol->dev->d_ops->write(vol->dev, &bs,
|
|
sizeof(NTFS_BOOT_SECTOR)) == -1)
|
|
perr_exit("write() error");
|
|
}
|
|
|
|
/**
|
|
* volume_size
|
|
*/
|
|
static s64 volume_size(ntfs_volume *v, s64 nr_clusters)
|
|
{
|
|
return nr_clusters * v->cluster_size;
|
|
}
|
|
|
|
/**
|
|
* print_volume_size
|
|
*
|
|
* Print the volume size in bytes and decimal megabytes.
|
|
*/
|
|
static void print_volume_size(const char *str, s64 bytes)
|
|
{
|
|
printf("%s: %lld bytes (%lld MB)\n",
|
|
str, bytes, rounded_up_division(bytes, NTFS_MBYTE));
|
|
}
|
|
|
|
/**
|
|
* print_disk_usage
|
|
*
|
|
* Display the amount of disk space in use.
|
|
*/
|
|
static void print_disk_usage(ntfs_resize_t *resize)
|
|
{
|
|
s64 total, used, relocations;
|
|
|
|
total = vol->nr_clusters * vol->cluster_size;
|
|
used = resize->inuse * vol->cluster_size;
|
|
relocations = resize->relocations * vol->cluster_size;
|
|
|
|
printf("Space in use : %lld MB (%.1f%%)\n",
|
|
rounded_up_division(used, NTFS_MBYTE),
|
|
100.0 * ((float)used / total));
|
|
|
|
if (opt.bytes)
|
|
printf("Needed relocations : %lld (%lld MB)\n",
|
|
resize->relocations,
|
|
rounded_up_division(relocations, NTFS_MBYTE));
|
|
}
|
|
|
|
/**
|
|
* mount_volume
|
|
*
|
|
* First perform some checks to determine if the volume is already mounted, or
|
|
* is dirty (Windows wasn't shutdown properly). If everything is OK, then mount
|
|
* the volume (load the metadata into memory).
|
|
*/
|
|
static void mount_volume(void)
|
|
{
|
|
unsigned long mntflag;
|
|
|
|
if (ntfs_check_if_mounted(opt.volume, &mntflag)) {
|
|
perr_printf("Failed to check '%s' mount state", opt.volume);
|
|
printf("Probably /etc/mtab is missing. It's too risky to "
|
|
"continue. You might try\nan another Linux distro.\n");
|
|
exit(1);
|
|
}
|
|
if (mntflag & NTFS_MF_MOUNTED) {
|
|
if (!(mntflag & NTFS_MF_READONLY))
|
|
err_exit("Device %s is mounted read-write. "
|
|
"You must 'umount' it first.\n", opt.volume);
|
|
if (!opt.ro_flag)
|
|
err_exit("Device %s is mounted. "
|
|
"You must 'umount' it first.\n", opt.volume);
|
|
}
|
|
|
|
if (!(vol = ntfs_mount(opt.volume, opt.ro_flag))) {
|
|
|
|
int err = errno;
|
|
|
|
perr_printf("ntfs_mount failed");
|
|
if (err == EINVAL) {
|
|
printf("Apparently device '%s' doesn't have a "
|
|
"valid NTFS. Maybe you selected\nthe whole "
|
|
"disk instead of a partition (e.g. /dev/hda, "
|
|
"not /dev/hda1)?\n", opt.volume);
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
if (vol->flags & VOLUME_IS_DIRTY)
|
|
if (opt.force-- <= 0)
|
|
err_exit("Volume is dirty. Run chkdsk /f and "
|
|
"please try again (or see -f option).\n");
|
|
|
|
printf("NTFS volume version: %d.%d\n", vol->major_ver, vol->minor_ver);
|
|
if (ntfs_version_is_supported(vol))
|
|
perr_exit("Unknown NTFS version");
|
|
|
|
printf("Cluster size : %u bytes\n", vol->cluster_size);
|
|
print_volume_size("Current volume size",
|
|
volume_size(vol, vol->nr_clusters));
|
|
}
|
|
|
|
/**
|
|
* prepare_volume_fixup
|
|
*
|
|
* Set the volume's dirty flag and wipe the filesystem journal. When Windows
|
|
* boots it will automatically run chkdsk to check for any problems. If the
|
|
* read-only command line option was given, this function will do nothing.
|
|
*/
|
|
static void prepare_volume_fixup(void)
|
|
{
|
|
u16 flags;
|
|
|
|
flags = vol->flags | VOLUME_IS_DIRTY;
|
|
if (vol->major_ver >= 2)
|
|
flags |= VOLUME_MOUNTED_ON_NT4;
|
|
|
|
printf("Schedule chkdsk for NTFS consistency check at Windows "
|
|
"boot time ...\n");
|
|
|
|
if (ntfs_volume_set_flags(vol, flags))
|
|
perr_exit("Failed to set $Volume dirty");
|
|
|
|
if (vol->dev->d_ops->sync(vol->dev) == -1)
|
|
perr_exit("Failed to sync device");
|
|
|
|
printf("Resetting $LogFile ... (this might take a while)\n");
|
|
|
|
if (ntfs_logfile_reset(vol))
|
|
perr_exit("Failed to reset $LogFile");
|
|
|
|
if (vol->dev->d_ops->sync(vol->dev) == -1)
|
|
perr_exit("Failed to sync device");
|
|
}
|
|
|
|
/**
|
|
* main
|
|
*
|
|
* Start here
|
|
*/
|
|
int main(int argc, char **argv)
|
|
{
|
|
ntfs_resize_t resize;
|
|
s64 new_size = 0; /* in clusters */
|
|
s64 device_size; /* in bytes */
|
|
|
|
printf("%s v%s\n", EXEC_NAME, VERSION);
|
|
|
|
if (!parse_options(argc, argv))
|
|
return 1;
|
|
|
|
utils_set_locale();
|
|
|
|
mount_volume();
|
|
|
|
device_size = ntfs_device_size_get(vol->dev, vol->sector_size);
|
|
device_size *= vol->sector_size;
|
|
if (device_size <= 0)
|
|
err_exit("Couldn't get device size (%lld)!\n", device_size);
|
|
|
|
print_volume_size("Current device size", device_size);
|
|
|
|
if (device_size < vol->nr_clusters * vol->cluster_size)
|
|
err_exit("Current NTFS volume size is bigger than the device "
|
|
"size (%lld)!\nCorrupt partition table or incorrect "
|
|
"device partitioning?\n", device_size);
|
|
|
|
if (opt.bytes) {
|
|
if (device_size < opt.bytes)
|
|
err_exit("New size can't be bigger than the "
|
|
"device size (%lld bytes).\nIf you want to "
|
|
"enlarge NTFS then first enlarge the device "
|
|
"size by e.g. fdisk.\n", device_size);
|
|
} else if (!opt.info)
|
|
opt.bytes = device_size;
|
|
|
|
/*
|
|
* Take the integer part: we don't want to make the volume bigger
|
|
* than requested. Later on we will also decrease this value to save
|
|
* room for the backup boot sector.
|
|
*/
|
|
new_size = opt.bytes / vol->cluster_size;
|
|
|
|
if (!opt.info)
|
|
print_volume_size("New volume size ",
|
|
volume_size(vol, new_size));
|
|
|
|
/* Backup boot sector at the end of device isn't counted in NTFS
|
|
volume size thus we have to reserve space for. We don't trust
|
|
the user does this for us: better to be on the safe side ;) */
|
|
if (new_size)
|
|
--new_size;
|
|
|
|
if (!opt.info && (new_size == vol->nr_clusters ||
|
|
(opt.bytes == device_size &&
|
|
new_size == vol->nr_clusters - 1))) {
|
|
printf("Nothing to do: NTFS volume size is already OK.\n");
|
|
exit(0);
|
|
}
|
|
|
|
setup_lcn_bitmap();
|
|
|
|
memset(&resize, 0, sizeof(resize));
|
|
resize.new_volume_size = new_size;
|
|
if (new_size < vol->nr_clusters)
|
|
resize.shrink = 1;
|
|
|
|
walk_inodes(&resize);
|
|
if (resize.multi_ref) {
|
|
printf("Totally %d clusters referenced multiply times.\n",
|
|
resize.multi_ref);
|
|
err_exit("Filesystem check failed! Windows wasn't shutdown "
|
|
"properly or inconsistent\nfilesystem. Please run "
|
|
"chkdsk /f on Windows.\n");
|
|
}
|
|
compare_bitmaps(&lcn_bitmap);
|
|
|
|
print_disk_usage(&resize);
|
|
|
|
if (opt.info) {
|
|
advise_on_resize(&resize);
|
|
exit(0);
|
|
}
|
|
|
|
if (resize.last_unsupp >= new_size) {
|
|
advise_on_resize(&resize);
|
|
exit(1);
|
|
}
|
|
|
|
if (opt.force-- <= 0 && !opt.ro_flag) {
|
|
printf(resize_warning_msg);
|
|
proceed_question();
|
|
}
|
|
|
|
prepare_volume_fixup();
|
|
|
|
truncate_badclust_file(new_size);
|
|
truncate_bitmap_file(&resize);
|
|
update_bootsector(new_size);
|
|
|
|
/* We don't create backup boot sector because we don't know where the
|
|
partition will be split. The scheduled chkdsk will fix it anyway */
|
|
|
|
if (opt.ro_flag) {
|
|
printf("The read-only test run ended successfully.\n");
|
|
exit(0);
|
|
}
|
|
|
|
printf("Syncing device ...\n");
|
|
if (vol->dev->d_ops->sync(vol->dev) == -1)
|
|
perr_exit("fsync");
|
|
|
|
printf("Successfully resized NTFS on device '%s'.\n", vol->dev->d_name);
|
|
if (new_size < vol->nr_clusters)
|
|
printf(resize_important_msg);
|
|
|
|
return 0;
|
|
}
|
|
|