193 files changed, 80411 insertions, 44563 deletions
diff --git a/ntfsprogs/ntfsresize.c b/ntfsprogs/ntfsresize.c new file mode 100755 index 0000000..6f54181 --- a/dev/null +++ b/ntfsprogs/ntfsresize.c @@ -0,0 +1,4497 @@ +/** + * ntfsresize - Part of the Linux-NTFS project. + * + * Copyright (c) 2002-2006 Szabolcs Szakacsits + * Copyright (c) 2002-2005 Anton Altaparmakov + * Copyright (c) 2002-2003 Richard Russon + * Copyright (c) 2007 Yura Pakhuchiy + * Copyright (c) 2011-2013 Jean-Pierre Andre + * + * This utility will resize an NTFS volume without data loss. + * + * WARNING FOR DEVELOPERS!!! Several external tools grep for text messages + * to control execution thus if you would like to change any message + * then PLEASE think twice before doing so then don't modify it. Thanks! + * + * 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" + +#ifdef HAVE_UNISTD_H +#include <unistd.h> +#endif +#ifdef HAVE_STDLIB_H +#include <stdlib.h> +#endif +#ifdef HAVE_STDIO_H +#include <stdio.h> +#endif +#ifdef HAVE_STDARG_H +#include <stdarg.h> +#endif +#ifdef HAVE_STRING_H +#include <string.h> +#endif +#ifdef HAVE_ERRNO_H +#include <errno.h> +#endif +#ifdef HAVE_LIMITS_H +#include <limits.h> +#endif +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif +#ifdef HAVE_FCNTL_H +#include <fcntl.h> +#endif + +#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" +/* #include "version.h" */ +#include "misc.h" + +#define BAN_NEW_TEXT 1 /* Respect the ban on new messages */ +#define CLEAN_EXIT 0 /* traditionnally volume is not closed, there must be a reason */ + +static const char *EXEC_NAME = "ntfsresize"; + +static const char *resize_warning_msg = +"WARNING: Every sanity check passed and only the dangerous operations left.\n" +"Make sure that important data has been backed up! Power outage or computer\n" +"crash may result major data loss!\n"; + +static const char *resize_important_msg = +"You can go on to shrink the device for example with Linux fdisk.\n" +"IMPORTANT: When recreating the partition, make sure that you\n" +" 1) create it at the same disk sector (use sector as the unit!)\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 won't be able to access NTFS or can't boot from the disk!\n" +"If you make a mistake and don't have a partition table backup then you\n" +"can recover the partition table by TestDisk or Parted's rescue mode.\n"; + +static const char *invalid_ntfs_msg = +"The device '%s' doesn't have a valid NTFS.\n" +"Maybe you selected the wrong partition? Or the whole disk instead of a\n" +"partition (e.g. /dev/hda, not /dev/hda1)? This error might also occur\n" +"if the disk was incorrectly repartitioned (see the ntfsresize FAQ).\n"; + +static const char *corrupt_volume_msg = +"NTFS is inconsistent. Run chkdsk /f on Windows then reboot it TWICE!\n" +"The usage of the /f parameter is very IMPORTANT! No modification was\n" +"and will be made to NTFS by this software until it gets repaired.\n"; + +static const char *hibernated_volume_msg = +"The NTFS partition is hibernated. Windows must be resumed and turned off\n" +"properly, so resizing could be done safely.\n"; + +static const char *unclean_journal_msg = +"The NTFS journal file is unclean. Please shutdown Windows properly before\n" +"using this software! Note, if you have run chkdsk previously then boot\n" +"Windows again which will automatically initialize the journal correctly.\n"; + +static const char *opened_volume_msg = +"This software has detected that the NTFS volume is already opened by another\n" +"software thus it refuses to progress to preserve data consistency.\n"; + +static const char *bad_sectors_warning_msg = +"****************************************************************************\n" +"* WARNING: The disk has bad sector. This means physical damage on the disk *\n" +"* surface caused by deterioration, manufacturing faults or other reason. *\n" +"* The reliability of the disk may stay stable or degrade fast. We suggest *\n" +"* making a full backup urgently by running 'ntfsclone --rescue ...' then *\n" +"* run 'chkdsk /f /r' on Windows and rebooot it TWICE! Then you can resize *\n" +"* NTFS safely by additionally using the --bad-sectors option of ntfsresize.*\n" +"****************************************************************************\n"; + +static const char *many_bad_sectors_msg = +"***************************************************************************\n" +"* WARNING: The disk has many bad sectors. This means physical damage *\n" +"* on the disk surface caused by deterioration, manufacturing faults or *\n" +"* other reason. We suggest to get a replacement disk as soon as possible. *\n" +"***************************************************************************\n"; + +static struct { + int verbose; + int debug; + int ro_flag; + int force; + int info; + int infombonly; + int expand; + int reliable_size; + int show_progress; + int badsectors; + int check; + s64 bytes; + char *volume; +} opt; + +struct bitmap { + s64 size; + u8 *bm; +}; + +#define NTFS_PROGBAR 0x0001 +#define NTFS_PROGBAR_SUPPRESS 0x0002 + +struct progress_bar { + u64 start; + u64 stop; + int resolution; + int flags; + float unit; +}; + +struct llcn_t { + s64 lcn; /* last used LCN for a "special" file/attr type */ + s64 inode; /* inode using it */ +}; + +#define NTFSCK_PROGBAR 0x0001 + + /* runlists which have to be processed later */ +struct DELAYED { + struct DELAYED *next; + ATTR_TYPES type; + MFT_REF mref; + VCN lowest_vcn; + int name_len; + ntfschar *attr_name; + runlist_element *rl; + runlist *head_rl; +} ; + +typedef struct { + ntfs_inode *ni; /* inode being processed */ + ntfs_attr_search_ctx *ctx; /* inode attribute being processed */ + s64 inuse; /* num of clusters in use */ + int multi_ref; /* num of clusters referenced many times */ + int outsider; /* num of clusters outside the volume */ + int show_outsider; /* controls showing the above information */ + int flags; + struct bitmap lcn_bitmap; +} ntfsck_t; + +typedef struct { + ntfs_volume *vol; + ntfs_inode *ni; /* inode being processed */ + s64 new_volume_size; /* in clusters; 0 = --info w/o --size */ + MFT_REF mref; /* mft reference */ + MFT_RECORD *mrec; /* mft record */ + ntfs_attr_search_ctx *ctx; /* inode attribute being processed */ + u64 relocations; /* num of clusters to relocate */ + s64 inuse; /* num of clusters in use */ + runlist mftmir_rl; /* $MFTMirr AT_DATA's new position */ + s64 mftmir_old; /* $MFTMirr AT_DATA's old LCN */ + int dirty_inode; /* some inode data got relocated */ + int shrink; /* shrink = 1, enlarge = 0 */ + s64 badclusters; /* num of physically dead clusters */ + VCN mft_highest_vcn; /* used for relocating the $MFT */ + runlist_element *new_mft_start; /* new first run for $MFT:$DATA */ + struct DELAYED *delayed_runlists; /* runlists to process later */ + struct progress_bar progress; + struct bitmap lcn_bitmap; + /* 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 */ +} ntfs_resize_t; + +/* FIXME: This, lcn_bitmap and pos from find_free_cluster() will make a cluster + allocation related structure, attached to ntfs_resize_t */ +static s64 max_free_cluster_range = 0; + +#define NTFS_MBYTE (1000 * 1000) + +/* WARNING: don't modify the text, external tools grep for it */ +#define ERR_PREFIX "ERROR" +#define PERR_PREFIX ERR_PREFIX "(%d): " +#define NERR_PREFIX ERR_PREFIX ": " + +#define DIRTY_NONE (0) +#define DIRTY_INODE (1) +#define DIRTY_ATTRIB (2) + +#define NTFS_MAX_CLUSTER_SIZE (65536) + +static s64 rounded_up_division(s64 numer, s64 denom) +{ + return (numer + (denom - 1)) / denom; +} + +/** + * perr_printf + * + * Print an error message. + */ +__attribute__((format(printf, 1, 2))) +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); + fprintf(stdout, ": %s\n", strerror(eo)); + fflush(stdout); + fflush(stderr); +} + +__attribute__((format(printf, 1, 2))) +static void err_printf(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); +} + +/** + * err_exit + * + * Print and error message and exit the program. + */ +__attribute__((noreturn)) +__attribute__((format(printf, 1, 2))) +static void 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 + */ +__attribute__((noreturn)) +__attribute__((format(printf, 1, 2))) +static void 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 + */ +__attribute__((noreturn)) +static void usage(void) +{ + + printf("\nUsage: %s [OPTIONS] DEVICE\n" + " Resize an NTFS volume non-destructively, safely move any data if needed.\n" + "\n" + " -c, --check Check to ensure that the device is ready for resize\n" + " -i, --info Estimate the smallest shrunken size or the smallest\n" + " expansion size\n" + " -m, --info-mb-only Estimate the smallest shrunken size possible,\n" + " output size in MB only\n" + " -s, --size SIZE Resize volume to SIZE[k|M|G] bytes\n" + " -x, --expand Expand to full partition\n" + "\n" + " -n, --no-action Do not write to disk\n" + " -b, --bad-sectors Support disks having bad sectors\n" + " -f, --force Force to progress\n" + " -P, --no-progress-bar Don't show progress bar\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" + " The options -i and -x are exclusive of option -s, and -m is exclusive\n" + " of option -x. If options -i, -m, -s and -x are are all omitted\n" + " then the NTFS volume will be enlarged to the DEVICE size.\n" + "\n", EXEC_NAME); + printf("%s%s", ntfs_bugs, ntfs_home); + printf("Ntfsresize FAQ: http://linux-ntfs.sourceforge.net/info/ntfsresize.html\n"); + 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; + if (fgets(buf, sizeof(buf), stdin) + && !strchr(short_yes, buf[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-2006 Szabolcs Szakacsits\n"); + printf("Copyright (c) 2002-2005 Anton Altaparmakov\n"); + printf("Copyright (c) 2002-2003 Richard Russon\n"); + printf("Copyright (c) 2007 Yura Pakhuchiy\n"); + printf("Copyright (c) 2011-2012 Jean-Pierre Andre\n"); + printf("\n%s\n%s%s", 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) { + opt.reliable_size = 1; + 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 + fdisk 2.11x- 2^10 2^20 10^3*2^20 + fdisk 2.11y+ 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 = "-bcdfhimnPs:vVx"; + static const struct option lopt[] = { + { "bad-sectors",no_argument, NULL, 'b' }, + { "check", no_argument, NULL, 'c' }, +#ifdef DEBUG + { "debug", no_argument, NULL, 'd' }, +#endif + { "force", no_argument, NULL, 'f' }, + { "help", no_argument, NULL, 'h' }, + { "info", no_argument, NULL, 'i' }, + { "info-mb-only", no_argument, NULL, 'm' }, + { "no-action", no_argument, NULL, 'n' }, + { "no-progress-bar", no_argument, NULL, 'P' }, + { "size", required_argument, NULL, 's' }, + { "expand", no_argument, NULL, 'x' }, + { "verbose", no_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { NULL, 0, NULL, 0 } + }; + + int 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 'b': + opt.badsectors++; + break; + case 'c': + opt.check++; + break; + case 'd': + opt.debug++; + break; + case 'f': + opt.force++; + break; + case 'h': + case '?': + help++; + break; + case 'i': + opt.info++; + break; + case 'm': + opt.infombonly++; + break; + case 'n': + opt.ro_flag = NTFS_MNT_RDONLY; + break; + case 'P': + opt.show_progress = 0; + break; + case 's': + if (!err && (opt.bytes == 0)) + opt.bytes = get_new_volume_size(optarg); + else + err++; + break; + case 'v': + opt.verbose++; + ntfs_log_set_levels(NTFS_LOG_LEVEL_VERBOSE); + break; + case 'V': + ver++; + break; + case 'x': + opt.expand++; + break; + default: + if (optopt == 's') { + printf("Option '%s' requires an argument.\n", argv[optind-1]); + } else { + printf("Unknown option '%s'.\n", argv[optind-1]); + } + err++; + break; + } + } + + if (!help && !ver) { + if (opt.volume == NULL) { + if (argc > 1) + printf("You must specify exactly one device.\n"); + err++; + } + if (opt.info || opt.infombonly) { + opt.ro_flag = NTFS_MNT_RDONLY; + } + if (opt.bytes + && (opt.expand || opt.info || opt.infombonly)) { + printf(NERR_PREFIX "Options --info(-mb-only) and --expand " + "cannot be used with --size.\n"); + usage(); + } + if (opt.expand && opt.infombonly) { + printf(NERR_PREFIX "Options --info-mb-only " + "cannot be used with --expand.\n"); + usage(); + } + } + + /* Redirect stderr to stdout, note fflush()es are essential! */ + fflush(stdout); + fflush(stderr); + if (dup2(STDOUT_FILENO, STDERR_FILENO) == -1) + perr_exit("Failed to redirect stderr to stdout"); + fflush(stdout); + fflush(stderr); + +#ifdef DEBUG + if (!opt.debug) + if (!freopen("/dev/null", "w", stderr)) + perr_exit("Failed to redirect stderr to /dev/null"); +#endif + + if (ver) + version(); + if (help || err) + usage(); + + return (!err && !help && !ver); +} + +static void print_advise(ntfs_volume *vol, s64 supp_lcn) +{ + s64 old_b, new_b, freed_b, old_mb, new_mb, freed_mb; + + old_b = vol->nr_clusters * vol->cluster_size; + old_mb = rounded_up_division(old_b, NTFS_MBYTE); + + /* Take the next supported cluster (free or relocatable) + plus reserve a cluster for the backup boot sector */ + supp_lcn += 2; + + if (supp_lcn > vol->nr_clusters) { + err_printf("Very rare fragmentation type detected. " + "Sorry, it's not supported yet.\n" + "Try to defragment your NTFS, perhaps it helps.\n"); + exit(1); + } + + new_b = supp_lcn * vol->cluster_size; + new_mb = rounded_up_division(new_b, NTFS_MBYTE); + freed_b = (vol->nr_clusters - supp_lcn + 1) * vol->cluster_size; + freed_mb = freed_b / NTFS_MBYTE; + + /* WARNING: don't modify the text, external tools grep for it */ + if (!opt.infombonly) + printf("You might resize at %lld bytes ", (long long)new_b); + if ((new_mb * NTFS_MBYTE) < old_b) { + if (!opt.infombonly) + printf("or %lld MB ", (long long)new_mb); + else + printf("Minsize (in MB): %lld\n", (long long)new_mb); + } + + if (!opt.infombonly) { + printf("(freeing "); + if (freed_mb && (old_mb - new_mb)) + printf("%lld MB", (long long)(old_mb - new_mb)); + else + printf("%lld bytes", (long long)freed_b); + printf(").\n"); + + printf("Please make a test run using both the -n and -s " + "options before real resizing!\n"); + } +} + +static void rl_set(runlist *rl, VCN vcn, LCN lcn, s64 len) +{ + rl->vcn = vcn; + rl->lcn = lcn; + rl->length = len; +} + +static int rl_items(runlist *rl) +{ + int i = 0; + + while (rl[i++].length) + ; + + return i; +} + +static void dump_run(runlist_element *r) +{ + ntfs_log_verbose(" %8lld %8lld (0x%08llx) %lld\n", (long long)r->vcn, + (long long)r->lcn, (long long)r->lcn, + (long long)r->length); +} + +static void dump_runlist(runlist *rl) +{ + while (rl->length) + dump_run(rl++); +} + +/** + * nr_clusters_to_bitmap_byte_size + * + * Take the number of clusters in the volume and calculate the size of $Bitmap. + * The size must be always 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; + + return bm_bsize; +} + +static void collect_resize_constraints(ntfs_resize_t *resize, runlist *rl) +{ + s64 inode, last_lcn; + ATTR_FLAGS flags; + ATTR_TYPES atype; + struct llcn_t *llcn = NULL; + int ret, supported = 0; + + last_lcn = rl->lcn + (rl->length - 1); + + inode = resize->ni->mft_no; + flags = resize->ctx->attr->flags; + atype = resize->ctx->attr->type; + + if ((ret = ntfs_inode_badclus_bad(inode, resize->ctx->attr)) != 0) { + if (ret == -1) + perr_exit("Bad sector list check failed"); + return; + } + + if (inode == FILE_Bitmap) { + llcn = &resize->last_lcn; + if (atype == AT_DATA && NInoAttrList(resize->ni)) + err_exit("Highly fragmented $Bitmap isn't supported yet."); + + supported = 1; + + } else if (NInoAttrList(resize->ni)) { + llcn = &resize->last_multi_mft; + + if (inode != FILE_MFTMirr) + supported = 1; + + } else if (flags & ATTR_IS_SPARSE) { + llcn = &resize->last_sparse; + supported = 1; + + } else if (flags & ATTR_IS_COMPRESSED) { + llcn = &resize->last_compressed; + supported = 1; + + } else if (inode == FILE_MFTMirr) { + llcn = &resize->last_mftmir; + supported = 1; + + /* Fragmented $MFTMirr DATA attribute isn't supported yet */ + if (atype == AT_DATA) + if (rl[1].length != 0 || rl->vcn) + supported = 0; + } else { + llcn = &resize->last_lcn; + supported = 1; + } + + if (llcn->lcn < last_lcn) { + llcn->lcn = last_lcn; + llcn->inode = inode; + } + + if (supported) + return; + + if (resize->last_unsupp < last_lcn) + resize->last_unsupp = last_lcn; +} + + +static void collect_relocation_info(ntfs_resize_t *resize, runlist *rl) +{ + s64 lcn, lcn_length, start, len, inode; + s64 new_vol_size; /* (last LCN on the volume) + 1 */ + + lcn = rl->lcn; + lcn_length = rl->length; + inode = resize->ni->mft_no; + new_vol_size = resize->new_volume_size; + + if (lcn + lcn_length <= new_vol_size) + return; + + if (inode == FILE_Bitmap && resize->ctx->attr->type == AT_DATA) + return; + + start = lcn; + len = lcn_length; + + if (lcn < new_vol_size) { + start = new_vol_size; + len = lcn_length - (new_vol_size - lcn); + + if ((!opt.info && !opt.infombonly) && (inode == FILE_MFTMirr)) { + err_printf("$MFTMirr can't be split up yet. Please try " + "a different size.\n"); + print_advise(resize->vol, lcn + lcn_length - 1); + exit(1); + } + } + + resize->relocations += len; + + if ((!opt.info && !opt.infombonly) || !resize->new_volume_size) + return; + + printf("Relocation needed for inode %8lld attr 0x%x LCN 0x%08llx " + "length %6lld\n", (long long)inode, + (unsigned int)le32_to_cpu(resize->ctx->attr->type), + (unsigned long long)start, (long long)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_volume *vol, ntfsck_t *fsck) +{ + s64 inode; + ATTR_RECORD *a; + runlist *rl; + int i, j; + struct bitmap *lcn_bitmap = &fsck->lcn_bitmap; + + a = fsck->ctx->attr; + inode = fsck->ni->mft_no; + + if (!a->non_resident) + return; + + if (!(rl = ntfs_mapping_pairs_decompress(vol, a, NULL))) { + int err = errno; + perr_printf("ntfs_decompress_mapping_pairs"); + if (err == EIO) + printf("%s", corrupt_volume_msg); + exit(1); + } + + + 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", (long long)inode, + (unsigned int)le32_to_cpu(a->type), + (long long)lcn, (long long)lcn_length); + + for (j = 0; j < lcn_length; j++) { + u64 k = (u64)lcn + j; + + if (k >= (u64)vol->nr_clusters) { + long long outsiders = lcn_length - j; + + fsck->outsider += outsiders; + + if (++fsck->show_outsider <= 10 || opt.verbose) + printf("Outside of the volume reference" + " for inode %lld at %lld:%lld\n", + (long long)inode, (long long)k, + (long long)outsiders); + + break; + } + + if (ntfs_bit_get_and_set(lcn_bitmap->bm, k, 1)) { + if (++fsck->multi_ref <= 10 || opt.verbose) + printf("Cluster %lld is referenced " + "multiple times!\n", + (long long)k); + continue; + } + } + fsck->inuse += lcn_length; + } + free(rl); +} + + +static ntfs_attr_search_ctx *attr_get_search_ctx(ntfs_inode *ni, MFT_RECORD *mrec) +{ + ntfs_attr_search_ctx *ret; + + if ((ret = ntfs_attr_get_search_ctx(ni, mrec)) == NULL) + perr_printf("ntfs_attr_get_search_ctx"); + + return ret; +} + +/** + * walk_attributes + * + * For a given MFT Record, iterate through all its attributes. Any non-resident + * data runs will be marked in lcn_bitmap. + */ +static int walk_attributes(ntfs_volume *vol, ntfsck_t *fsck) +{ + if (!(fsck->ctx = attr_get_search_ctx(fsck->ni, NULL))) + return -1; + + while (!ntfs_attrs_walk(fsck->ctx)) { + if (fsck->ctx->attr->type == AT_END) + break; + build_lcn_usage_bitmap(vol, fsck); + } + + ntfs_attr_put_search_ctx(fsck->ctx); + return 0; +} + +/** + * 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(ntfs_volume *vol, struct bitmap *a) +{ + s64 i, pos, count; + int mismatch = 0; + int backup_boot = 0; + u8 bm[NTFS_BUF_SIZE]; + + if (!opt.infombonly) + 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 size is smaller than expected" + " (%lld != %lld)\n", + (long long)a->size, (long long)pos); + break; + } + + for (i = 0; i < count; i++, pos++) { + s64 cl; /* current cluster */ + + if (a->size <= pos) + goto done; + + 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 && !opt.verbose) + continue; + + printf("Cluster accounting failed at %lld " + "(0x%llx): %s cluster in " + "$Bitmap\n", (long long)cl, + (unsigned long long)cl, + bit ? "missing" : "extra"); + } + } + } +done: + if (mismatch) { + printf("Filesystem check failed! Totally %d cluster " + "accounting mismatches.\n", mismatch); + err_printf("%s", corrupt_volume_msg); + exit(1); + } +} + +/** + * progress_init + * + * Create and scale our progress bar. + */ +static void progress_init(struct progress_bar *p, u64 start, u64 stop, int flags) +{ + p->start = start; + p->stop = stop; + p->unit = 100.0 / (stop - start); + p->resolution = 100; + p->flags = flags; +} + +/** + * progress_update + * + * Update the progress bar and tell the user. + */ +static void progress_update(struct progress_bar *p, u64 current) +{ + float percent; + + if (!(p->flags & NTFS_PROGBAR)) + return; + if (p->flags & NTFS_PROGBAR_SUPPRESS) + return; + + /* WARNING: don't modify the texts, external tools grep for them */ + 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); +} + +static int inode_close(ntfs_inode *ni) +{ + if (ntfs_inode_close(ni)) { + perr_printf("ntfs_inode_close for inode %llu", + (unsigned long long)ni->mft_no); + return -1; + } + return 0; +} + +/** + * walk_inodes + * + * Read each record in the MFT, skipping the unused ones, and build up a bitmap + * from all the non-resident attributes. + */ +static int build_allocation_bitmap(ntfs_volume *vol, ntfsck_t *fsck) +{ + s64 nr_mft_records, inode = 0; + ntfs_inode *ni; + struct progress_bar progress; + int pb_flags = 0; /* progress bar flags */ + + /* WARNING: don't modify the text, external tools grep for it */ + if (!opt.infombonly) + printf("Checking filesystem consistency ...\n"); + + if (fsck->flags & NTFSCK_PROGBAR) + pb_flags |= NTFS_PROGBAR; + + nr_mft_records = vol->mft_na->initialized_size >> + vol->mft_record_size_bits; + + progress_init(&progress, inode, nr_mft_records - 1, pb_flags); + + for (; inode < nr_mft_records; inode++) { + if (!opt.infombonly) + 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_printf("Reading inode %lld failed", + (long long)inode); + return -1; + } + + if (ni->mrec->base_mft_record) + goto close_inode; + + fsck->ni = ni; + if (walk_attributes(vol, fsck) != 0) { + inode_close(ni); + return -1; + } +close_inode: + if (inode_close(ni) != 0) + return -1; + } + return 0; +} + +static void build_resize_constraints(ntfs_resize_t *resize) +{ + s64 i; + runlist *rl; + + if (!resize->ctx->attr->non_resident) + return; + + if (!(rl = ntfs_mapping_pairs_decompress(resize->vol, + resize->ctx->attr, NULL))) + perr_exit("ntfs_decompress_mapping_pairs"); + + for (i = 0; rl[i].length; i++) { + /* CHECKME: LCN_RL_NOT_MAPPED check isn't needed */ + if (rl[i].lcn == LCN_HOLE || rl[i].lcn == LCN_RL_NOT_MAPPED) + continue; + + collect_resize_constraints(resize, rl + i); + if (resize->shrink) + collect_relocation_info(resize, rl + i); + } + free(rl); +} + +static void resize_constraints_by_attributes(ntfs_resize_t *resize) +{ + if (!(resize->ctx = attr_get_search_ctx(resize->ni, NULL))) + exit(1); + + while (!ntfs_attrs_walk(resize->ctx)) { + if (resize->ctx->attr->type == AT_END) + break; + build_resize_constraints(resize); + } + + ntfs_attr_put_search_ctx(resize->ctx); +} + +static void set_resize_constraints(ntfs_resize_t *resize) +{ + s64 nr_mft_records, inode; + ntfs_inode *ni; + + if (!opt.infombonly) + printf("Collecting resizing constraints ...\n"); + + nr_mft_records = resize->vol->mft_na->initialized_size >> + resize->vol->mft_record_size_bits; + + for (inode = 0; inode < nr_mft_records; inode++) { + + ni = ntfs_inode_open(resize->vol, (MFT_REF)inode); + if (ni == NULL) { + if (errno == EIO || errno == ENOENT) + continue; + perr_exit("Reading inode %lld failed", + (long long)inode); + } + + if (ni->mrec->base_mft_record) + goto close_inode; + + resize->ni = ni; + resize_constraints_by_attributes(resize); +close_inode: + if (inode_close(ni) != 0) + exit(1); + } +} + +static void rl_fixup(runlist **rl) +{ + runlist *tmp = *rl; + + if (tmp->lcn == LCN_RL_NOT_MAPPED) { + s64 unmapped_len = tmp->length; + + ntfs_log_verbose("Skip unmapped run at the beginning ...\n"); + + if (!tmp->length) + err_exit("Empty unmapped runlist! Please report!\n"); + (*rl)++; + for (tmp = *rl; tmp->length; tmp++) + tmp->vcn -= unmapped_len; + } + + for (tmp = *rl; tmp->length; tmp++) { + if (tmp->lcn == LCN_RL_NOT_MAPPED) { + ntfs_log_verbose("Skip unmapped run at the end ...\n"); + + if (tmp[1].length) + err_exit("Unmapped runlist in the middle! " + "Please report!\n"); + tmp->lcn = LCN_ENOENT; + tmp->length = 0; + } + } +} + +/* + * Plug a replacement (partial) runlist into full runlist + * + * Returns 0 if successful + * -1 if failed + */ + +static int replace_runlist(ntfs_attr *na, const runlist_element *reprl, + VCN lowest_vcn) +{ + const runlist_element *prep; + const runlist_element *pold; + runlist_element *pnew; + runlist_element *newrl; + VCN nextvcn; + s32 oldcnt, newcnt; + s32 newsize; + int r; + + r = -1; /* default return */ + /* allocate a new runlist able to hold both */ + oldcnt = 0; + while (na->rl[oldcnt].length) + oldcnt++; + newcnt = 0; + while (reprl[newcnt].length) + newcnt++; + newsize = ((oldcnt + newcnt)*sizeof(runlist_element) + 4095) & -4096; + newrl = (runlist_element*)malloc(newsize); + if (newrl) { + /* copy old runs until reaching replaced ones */ + pnew = newrl; + pold = na->rl; + while (pold->length + && ((pold->vcn + pold->length) + <= (reprl[0].vcn + lowest_vcn))) { + *pnew = *pold; + pnew++; + pold++; + } + /* split a possible old run partially overlapped */ + if (pold->length + && (pold->vcn < (reprl[0].vcn + lowest_vcn))) { + pnew->vcn = pold->vcn; + pnew->lcn = pold->lcn; + pnew->length = reprl[0].vcn + lowest_vcn - pold->vcn; + pnew++; + } + /* copy new runs */ + prep = reprl; + nextvcn = prep->vcn + lowest_vcn; + while (prep->length) { + pnew->vcn = prep->vcn + lowest_vcn; + pnew->lcn = prep->lcn; + pnew->length = prep->length; + nextvcn = pnew->vcn + pnew->length; + pnew++; + prep++; + } + /* locate the first fully replaced old run */ + while (pold->length + && ((pold->vcn + pold->length) <= nextvcn)) { + pold++; + } + /* split a possible old run partially overlapped */ + if (pold->length + && (pold->vcn < nextvcn)) { + pnew->vcn = nextvcn; + pnew->lcn = pold->lcn + nextvcn - pold->vcn; + pnew->length = pold->length - nextvcn + pold->vcn; + pnew++; + } + /* copy old runs beyond replaced ones */ + while (pold->length) { + *pnew = *pold; + pnew++; + pold++; + } + /* the terminator is same as the old one */ + *pnew = *pold; + /* deallocate the old runlist and replace */ + free(na->rl); + na->rl = newrl; + r = 0; + } + return (r); +} + +/* + * Expand the new runlist in new extent(s) + * + * This implies allocating inode extents and, generally, creating + * an attribute list and allocating clusters for the list, and + * shuffle the existing attributes accordingly. + * + * Sometimes the runlist being reallocated is within an extent, + * so we have a partial runlist to plug into an existing one + * whose other parts have already been processed or will have + * to be processed later, and we must not interfere with the + * processing of these parts. + * + * This cannot be done on the runlist part stored in a single + * extent, it has to be done globally for the file. + * + * We use the standard library functions, so we must wait until + * the new global bitmap and the new MFT bitmap are saved to + * disk and usable for the allocation of a new extent and creation + * of an attribute list. + * + * Aborts if something goes wrong. There should be no data damage, + * because the old runlist is still in use and the bootsector has + * not been updated yet, so the initial clusters can be accessed. + */ + +static void expand_attribute_runlist(ntfs_volume *vol, struct DELAYED *delayed) +{ + ntfs_inode *ni; + ntfs_attr *na; + ATTR_TYPES type; + MFT_REF mref; + runlist_element *rl; + + /* open the inode */ + mref = delayed->mref; +#ifndef BAN_NEW_TEXT + ntfs_log_verbose("Processing a delayed update for inode %lld\n", + (long long)mref); +#endif + type = delayed->type; + rl = delayed->rl; + ni = ntfs_inode_open(vol,mref); + if (ni) { + na = ntfs_attr_open(ni, type, + delayed->attr_name, delayed->name_len); + if (na) { + if (!ntfs_attr_map_whole_runlist(na)) { + if (replace_runlist(na,rl,delayed->lowest_vcn) + || ntfs_attr_update_mapping_pairs(na,0)) + perr_exit("Could not update runlist " + "for attribute 0x%lx in inode %lld", + (long)le32_to_cpu(type),(long long)mref); + } else + perr_exit("Could not map attribute 0x%lx in inode %lld", + (long)le32_to_cpu(type),(long long)mref); + ntfs_attr_close(na); + } else + perr_exit("Could not open attribute 0x%lx in inode %lld", + (long)le32_to_cpu(type),(long long)mref); + ntfs_inode_mark_dirty(ni); + if (ntfs_inode_close(ni)) + perr_exit("Failed to close inode %lld through the library", + (long long)mref); + } else + perr_exit("Could not open inode %lld through the library", + (long long)mref); +} + +/* + * Process delayed runlist updates + */ + +static void delayed_updates(ntfs_resize_t *resize) +{ + struct DELAYED *delayed; + + while (resize->delayed_runlists) { + delayed = resize->delayed_runlists; + expand_attribute_runlist(resize->vol, delayed); + resize->delayed_runlists = resize->delayed_runlists->next; + if (delayed->attr_name) + free(delayed->attr_name); + free(delayed->head_rl); + free(delayed); + } +} + +/* + * Queue a runlist replacement for later update + * + * Store the attribute identification relative to base inode + */ + +static void replace_later(ntfs_resize_t *resize, runlist *rl, runlist *head_rl) +{ + struct DELAYED *delayed; + ATTR_RECORD *a; + MFT_REF mref; + leMFT_REF lemref; + int name_len; + ntfschar *attr_name; + + /* save the attribute parameters, to be able to find it later */ + a = resize->ctx->attr; + name_len = a->name_length; + attr_name = (ntfschar*)NULL; + if (name_len) { + attr_name = (ntfschar*)ntfs_malloc(name_len*sizeof(ntfschar)); + if (attr_name) + memcpy(attr_name,(u8*)a + le16_to_cpu(a->name_offset), + name_len*sizeof(ntfschar)); + } + delayed = (struct DELAYED*)ntfs_malloc(sizeof(struct DELAYED)); + if (delayed && (attr_name || !name_len)) { + lemref = resize->ctx->mrec->base_mft_record; + if (lemref) + mref = le64_to_cpu(lemref); + else + mref = resize->mref; + delayed->mref = MREF(mref); + delayed->type = a->type; + delayed->attr_name = attr_name; + delayed->name_len = name_len; + delayed->lowest_vcn = le64_to_cpu(a->lowest_vcn); + delayed->rl = rl; + delayed->head_rl = head_rl; + delayed->next = resize->delayed_runlists; + resize->delayed_runlists = delayed; + } else + perr_exit("Could not store delayed update data"); +} + +/* + * Replace the runlist in an attribute + * + * This sometimes requires expanding the runlist into another extent, + * which has to be done globally on the attribute. Is so, the action + * is put in a delay queue, and the caller must not free the runlist. + * + * Returns 0 if the replacement could be done + * 1 when it has been put in the delay queue. + */ + +static int replace_attribute_runlist(ntfs_resize_t *resize, runlist *rl) +{ + int mp_size, l; + int must_delay; + void *mp; + runlist *head_rl; + ntfs_volume *vol; + ntfs_attr_search_ctx *ctx; + ATTR_RECORD *a; + + vol = resize->vol; + ctx = resize->ctx; + a = ctx->attr; + head_rl = rl; + rl_fixup(&rl); + + if ((mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, 0, INT_MAX)) == -1) + perr_exit("ntfs_get_size_for_mapping_pairs"); + + if (a->name_length) { + u16 name_offs = le16_to_cpu(a->name_offset); + u16 mp_offs = le16_to_cpu(a->mapping_pairs_offset); + + if (name_offs >= mp_offs) + err_exit("Attribute name is after mapping pairs! " + "Please report!\n"); + } + + /* CHECKME: don't trust mapping_pairs is always the last item in the + attribute, instead check for the real size/space */ + l = (int)le32_to_cpu(a->length) - le16_to_cpu(a->mapping_pairs_offset); + must_delay = 0; + if (mp_size > l) { + s32 remains_size; + char *next_attr; + + ntfs_log_verbose("Enlarging attribute header ...\n"); + + mp_size = (mp_size + 7) & ~7; + + ntfs_log_verbose("Old mp size : %d\n", l); + ntfs_log_verbose("New mp size : %d\n", mp_size); + ntfs_log_verbose("Bytes in use : %u\n", (unsigned int) + le32_to_cpu(ctx->mrec->bytes_in_use)); + + next_attr = (char *)a + le32_to_cpu(a->length); + l = mp_size - l; + + ntfs_log_verbose("Bytes in use new : %u\n", l + (unsigned int) + le32_to_cpu(ctx->mrec->bytes_in_use)); + ntfs_log_verbose("Bytes allocated : %u\n", (unsigned int) + le32_to_cpu(ctx->mrec->bytes_allocated)); + + remains_size = le32_to_cpu(ctx->mrec->bytes_in_use); + remains_size -= (next_attr - (char *)ctx->mrec); + + ntfs_log_verbose("increase : %d\n", l); + ntfs_log_verbose("shift : %lld\n", + (long long)remains_size); + if (le32_to_cpu(ctx->mrec->bytes_in_use) + l > + le32_to_cpu(ctx->mrec->bytes_allocated)) { +#ifndef BAN_NEW_TEXT + ntfs_log_verbose("Queuing expansion for later processing\n"); +#endif + must_delay = 1; + replace_later(resize,rl,head_rl); + } else { + memmove(next_attr + l, next_attr, remains_size); + ctx->mrec->bytes_in_use = cpu_to_le32(l + + le32_to_cpu(ctx->mrec->bytes_in_use)); + a->length = cpu_to_le32(le32_to_cpu(a->length) + l); + } + } + + if (!must_delay) { + mp = ntfs_calloc(mp_size); + if (!mp) + perr_exit("ntfsc_calloc couldn't get memory"); + + if (ntfs_mapping_pairs_build(vol, (u8*)mp, mp_size, rl, 0, NULL)) + perr_exit("ntfs_mapping_pairs_build"); + + memmove((u8*)a + le16_to_cpu(a->mapping_pairs_offset), mp, mp_size); + + free(mp); + } + return (must_delay); +} + +static void set_bitmap_range(struct bitmap *bm, s64 pos, s64 length, u8 bit) +{ + while (length--) + ntfs_bit_set(bm->bm, pos++, bit); +} + +static void set_bitmap_clusters(struct bitmap *bm, runlist *rl, u8 bit) +{ + for (; rl->length; rl++) + set_bitmap_range(bm, rl->lcn, rl->length, bit); +} + +static void release_bitmap_clusters(struct bitmap *bm, runlist *rl) +{ + max_free_cluster_range = 0; + set_bitmap_clusters(bm, rl, 0); +} + +static void set_max_free_zone(s64 length, s64 end, runlist_element *rle) +{ + if (length > rle->length) { + rle->lcn = end - length; + rle->length = length; + } +} + +static int find_free_cluster(struct bitmap *bm, + runlist_element *rle, + s64 nr_vol_clusters, + int hint) +{ + /* FIXME: get rid of this 'static' variable */ + static s64 pos = 0; + s64 i, items = rle->length; + s64 free_zone = 0; + + if (pos >= nr_vol_clusters) + pos = 0; + if (!max_free_cluster_range) + max_free_cluster_range = nr_vol_clusters; + rle->lcn = rle->length = 0; + if (hint) + pos = nr_vol_clusters / 2; + i = pos; + + do { + if (!ntfs_bit_get(bm->bm, i)) { + if (++free_zone == items) { + set_max_free_zone(free_zone, i + 1, rle); + break; + } + } else { + set_max_free_zone(free_zone, i, rle); + free_zone = 0; + } + if (++i == nr_vol_clusters) { + set_max_free_zone(free_zone, i, rle); + i = free_zone = 0; + } + if (rle->length == max_free_cluster_range) + break; + } while (i != pos); + + if (i) + set_max_free_zone(free_zone, i, rle); + + if (!rle->lcn) { + errno = ENOSPC; + return -1; + } + if (rle->length < items && rle->length < max_free_cluster_range) { + max_free_cluster_range = rle->length; + ntfs_log_verbose("Max free range: %7lld \n", + (long long)max_free_cluster_range); + } + pos = rle->lcn + items; + if (pos == nr_vol_clusters) + pos = 0; + + set_bitmap_range(bm, rle->lcn, rle->length, 1); + return 0; +} + +static runlist *alloc_cluster(struct bitmap *bm, + s64 items, + s64 nr_vol_clusters, + int hint) +{ + runlist_element rle; + runlist *rl = NULL; + int rl_size, runs = 0; + s64 vcn = 0; + + if (items <= 0) { + errno = EINVAL; + return NULL; + } + + while (items > 0) { + + if (runs) + hint = 0; + rle.length = items; + if (find_free_cluster(bm, &rle, nr_vol_clusters, hint) == -1) + return NULL; + + rl_size = (runs + 2) * sizeof(runlist_element); + if (!(rl = (runlist *)realloc(rl, rl_size))) + return NULL; + + rl_set(rl + runs, vcn, rle.lcn, rle.length); + + vcn += rle.length; + items -= rle.length; + runs++; + } + + rl_set(rl + runs, vcn, -1LL, 0LL); + + if (runs > 1) { + ntfs_log_verbose("Multi-run allocation: \n"); + dump_runlist(rl); + } + return rl; +} + +static int read_all(struct ntfs_device *dev, void *buf, int count) +{ + int i; + + while (count > 0) { + + i = count; + if (!NDevReadOnly(dev)) + i = dev->d_ops->read(dev, buf, count); + + if (i < 0) { + if (errno != EAGAIN && errno != EINTR) + return -1; + } else if (i > 0) { + count -= i; + buf = i + (char *)buf; + } else + err_exit("Unexpected end of file!\n"); + } + return 0; +} + +static int write_all(struct ntfs_device *dev, void *buf, int count) +{ + int i; + + while (count > 0) { + + i = count; + if (!NDevReadOnly(dev)) + i = dev->d_ops->write(dev, buf, count); + + if (i < 0) { + if (errno != EAGAIN && errno != EINTR) + return -1; + } else { + count -= i; + buf = i + (char *)buf; + } + } + return 0; +} + +/** + * 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_volume *v, const MFT_REF mref, MFT_RECORD *buf) +{ + if (ntfs_mft_record_write(v, mref, buf)) + perr_exit("ntfs_mft_record_write"); + +// if (v->dev->d_ops->sync(v->dev) == -1) +// perr_exit("Failed to sync device"); + + return 0; +} + +static void lseek_to_cluster(ntfs_volume *vol, s64 lcn) +{ + off_t pos; + + pos = (off_t)(lcn * vol->cluster_size); + + if (vol->dev->d_ops->seek(vol->dev, pos, SEEK_SET) == (off_t)-1) + perr_exit("seek failed to position %lld", (long long)lcn); +} + +static void copy_clusters(ntfs_resize_t *resize, s64 dest, s64 src, s64 len) +{ + s64 i; + char buff[NTFS_MAX_CLUSTER_SIZE]; /* overflow checked at mount time */ + ntfs_volume *vol = resize->vol; + + for (i = 0; i < len; i++) { + + lseek_to_cluster(vol, src + i); + + if (read_all(vol->dev, buff, vol->cluster_size) == -1) { + perr_printf("Failed to read from the disk"); + if (errno == EIO) + printf("%s", bad_sectors_warning_msg); + exit(1); + } + + lseek_to_cluster(vol, dest + i); + + if (write_all(vol->dev, buff, vol->cluster_size) == -1) { + perr_printf("Failed to write to the disk"); + if (errno == EIO) + printf("%s", bad_sectors_warning_msg); + exit(1); + } + + resize->relocations++; + progress_update(&resize->progress, resize->relocations); + } +} + +static void relocate_clusters(ntfs_resize_t *r, runlist *dest_rl, s64 src_lcn) +{ + /* collect_shrink_constraints() ensured $MFTMir DATA is one run */ + if (r->mref == FILE_MFTMirr && r->ctx->attr->type == AT_DATA) { + if (!r->mftmir_old) { + r->mftmir_rl.lcn = dest_rl->lcn; + r->mftmir_rl.length = dest_rl->length; + r->mftmir_old = src_lcn; + } else + err_exit("Multi-run $MFTMirr. Please report!\n"); + } + + for (; dest_rl->length; src_lcn += dest_rl->length, dest_rl++) + copy_clusters(r, dest_rl->lcn, src_lcn, dest_rl->length); +} + +static void rl_split_run(runlist **rl, int run, s64 pos) +{ + runlist *rl_new, *rle_new, *rle; + int items, new_size, size_head, size_tail; + s64 len_head, len_tail; + + items = rl_items(*rl); + new_size = (items + 1) * sizeof(runlist_element); + size_head = run * sizeof(runlist_element); + size_tail = (items - run - 1) * sizeof(runlist_element); + + rl_new = ntfs_malloc(new_size); + if (!rl_new) + perr_exit("ntfs_malloc"); + + rle_new = rl_new + run; + rle = *rl + run; + + memmove(rl_new, *rl, size_head); + memmove(rle_new + 2, rle + 1, size_tail); + + len_tail = rle->length - (pos - rle->lcn); + len_head = rle->length - len_tail; + + rl_set(rle_new, rle->vcn, rle->lcn, len_head); + rl_set(rle_new + 1, rle->vcn + len_head, rle->lcn + len_head, len_tail); + + ntfs_log_verbose("Splitting run at cluster %lld:\n", (long long)pos); + dump_run(rle); dump_run(rle_new); dump_run(rle_new + 1); + + free(*rl); + *rl = rl_new; +} + +static void rl_insert_at_run(runlist **rl, int run, runlist *ins) +{ + int items, ins_items; + int new_size, size_tail; + runlist *rle; + s64 vcn; + + items = rl_items(*rl); + ins_items = rl_items(ins) - 1; + new_size = ((items - 1) + ins_items) * sizeof(runlist_element); + size_tail = (items - run - 1) * sizeof(runlist_element); + + if (!(*rl = (runlist *)realloc(*rl, new_size))) + perr_exit("realloc"); + + rle = *rl + run; + + memmove(rle + ins_items, rle + 1, size_tail); + + for (vcn = rle->vcn; ins->length; rle++, vcn += ins->length, ins++) { + rl_set(rle, vcn, ins->lcn, ins->length); +// dump_run(rle); + } + + return; + + /* FIXME: fast path if ins_items = 1 */ +// (*rl + run)->lcn = ins->lcn; +} + +static void relocate_run(ntfs_resize_t *resize, runlist **rl, int run) +{ + s64 lcn, lcn_length; + s64 new_vol_size; /* (last LCN on the volume) + 1 */ + runlist *relocate_rl; /* relocate runlist to relocate_rl */ + int hint; + + lcn = (*rl + run)->lcn; + lcn_length = (*rl + run)->length; + new_vol_size = resize->new_volume_size; + + if (lcn + lcn_length <= new_vol_size) + return; + + if (lcn < new_vol_size) { + rl_split_run(rl, run, new_vol_size); + return; + } + + hint = (resize->mref == FILE_MFTMirr) ? 1 : 0; + if ((resize->mref == FILE_MFT) + && (resize->ctx->attr->type == AT_DATA) + && !run + && resize->new_mft_start) { + relocate_rl = resize->new_mft_start; + } else + if (!(relocate_rl = alloc_cluster(&resize->lcn_bitmap, + lcn_length, new_vol_size, hint))) + perr_exit("Cluster allocation failed for %llu:%lld", + (unsigned long long)resize->mref, + (long long)lcn_length); + + /* FIXME: check $MFTMirr DATA isn't multi-run (or support it) */ + ntfs_log_verbose("Relocate record %7llu:0x%x:%08lld:0x%08llx:0x%08llx " + "--> 0x%08llx\n", (unsigned long long)resize->mref, + (unsigned int)le32_to_cpu(resize->ctx->attr->type), + (long long)lcn_length, + (unsigned long long)(*rl + run)->vcn, + (unsigned long long)lcn, + (unsigned long long)relocate_rl->lcn); + + relocate_clusters(resize, relocate_rl, lcn); + rl_insert_at_run(rl, run, relocate_rl); + + /* We don't release old clusters in the bitmap, that area isn't + used by the allocator and will be truncated later on */ + + /* Do not free the relocated MFT start */ + if ((resize->mref != FILE_MFT) + || (resize->ctx->attr->type != AT_DATA) + || run + || !resize->new_mft_start) + free(relocate_rl); + + resize->dirty_inode = DIRTY_ATTRIB; +} + +static void relocate_attribute(ntfs_resize_t *resize) +{ + ATTR_RECORD *a; + runlist *rl; + int i; + + a = resize->ctx->attr; + + if (!a->non_resident) + return; + + if (!(rl = ntfs_mapping_pairs_decompress(resize->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; + + 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 MTF %llu attr %x LCN " + "%llx length %llx\n", + (unsigned long long)resize->mref, + (unsigned int)le32_to_cpu(a->type), + (long long)lcn, (long long)lcn_length); + + relocate_run(resize, &rl, i); + } + + if (resize->dirty_inode == DIRTY_ATTRIB) { + if (!replace_attribute_runlist(resize, rl)) + free(rl); + resize->dirty_inode = DIRTY_INODE; + } else + free(rl); +} + +static int is_mftdata(ntfs_resize_t *resize) +{ + /* + * We must update the MFT own DATA record at the end of the second + * step, because the old MFT must be kept available for processing + * the other files. + */ + + if (resize->ctx->attr->type != AT_DATA) + return 0; + + if (resize->mref == 0) + return 1; + + if (MREF_LE(resize->mrec->base_mft_record) == 0 && + MSEQNO_LE(resize->mrec->base_mft_record) != 0) + return 1; + + return 0; +} + +static int handle_mftdata(ntfs_resize_t *resize, int do_mftdata) +{ + ATTR_RECORD *attr = resize->ctx->attr; + VCN highest_vcn, lowest_vcn; + + if (do_mftdata) { + + if (!is_mftdata(resize)) + return 0; + + highest_vcn = sle64_to_cpu(attr->highest_vcn); + lowest_vcn = sle64_to_cpu(attr->lowest_vcn); + + if (resize->mft_highest_vcn != highest_vcn) + return 0; + + if (lowest_vcn == 0) + resize->mft_highest_vcn = lowest_vcn; + else + resize->mft_highest_vcn = lowest_vcn - 1; + + } else if (is_mftdata(resize)) { + + highest_vcn = sle64_to_cpu(attr->highest_vcn); + + if (resize->mft_highest_vcn < highest_vcn) + resize->mft_highest_vcn = highest_vcn; + + return 0; + } + + return 1; +} + +static void relocate_attributes(ntfs_resize_t *resize, int do_mftdata) +{ + int ret; + + if (!(resize->ctx = attr_get_search_ctx(NULL, resize->mrec))) + exit(1); + + while (!ntfs_attrs_walk(resize->ctx)) { + if (resize->ctx->attr->type == AT_END) + break; + + if (handle_mftdata(resize, do_mftdata) == 0) + continue; + + ret = ntfs_inode_badclus_bad(resize->mref, resize->ctx->attr); + if (ret == -1) + perr_exit("Bad sector list check failed"); + else if (ret == 1) + continue; + + if (resize->mref == FILE_Bitmap && + resize->ctx->attr->type == AT_DATA) + continue; + + relocate_attribute(resize); + } + + ntfs_attr_put_search_ctx(resize->ctx); +} + +static void relocate_inode(ntfs_resize_t *resize, MFT_REF mref, int do_mftdata) +{ + ntfs_volume *vol = resize->vol; + + if (ntfs_file_record_read(vol, mref, &resize->mrec, NULL)) { + /* FIXME: continue only if it make sense, e.g. + MFT record not in use based on $MFT bitmap */ + if (errno == EIO || errno == ENOENT) + return; + perr_exit("ntfs_file_record_record"); + } + + if (!(resize->mrec->flags & MFT_RECORD_IN_USE)) + return; + + resize->mref = mref; + resize->dirty_inode = DIRTY_NONE; + + relocate_attributes(resize, do_mftdata); + +// if (vol->dev->d_ops->sync(vol->dev) == -1) +// perr_exit("Failed to sync device"); + /* relocate MFT during second step, even if not dirty */ + if ((mref == FILE_MFT) && do_mftdata && resize->new_mft_start) { + s64 pos; + + /* write the MFT own record at its new location */ + pos = (resize->new_mft_start->lcn + << vol->cluster_size_bits) + + (FILE_MFT << vol->mft_record_size_bits); + if (!opt.ro_flag + && (ntfs_mst_pwrite(vol->dev, pos, 1, + vol->mft_record_size, resize->mrec) != 1)) + perr_exit("Couldn't update MFT own record"); + } else { + if ((resize->dirty_inode == DIRTY_INODE) + && write_mft_record(vol, mref, resize->mrec)) { + perr_exit("Couldn't update record %llu", + (unsigned long long)mref); + } + } +} + +static void relocate_inodes(ntfs_resize_t *resize) +{ + s64 nr_mft_records; + MFT_REF mref; + VCN highest_vcn; + u64 length; + + printf("Relocating needed data ...\n"); + + progress_init(&resize->progress, 0, resize->relocations, resize->progress.flags); + resize->relocations = 0; + + resize->mrec = ntfs_malloc(resize->vol->mft_record_size); + if (!resize->mrec) + perr_exit("ntfs_malloc failed"); + + nr_mft_records = resize->vol->mft_na->initialized_size >> + resize->vol->mft_record_size_bits; + + /* + * If we need to relocate the first run of the MFT DATA, + * do it now, to have a better chance of getting at least + * 16 records in the first chunk. This is mandatory to be + * later able to read an MFT extent in record 15. + * Should this fail, we can stop with no damage, the volume + * is still in its initial state. + */ + if (!resize->vol->mft_na->rl) + err_exit("Internal error : no runlist for $MFT\n"); + + if ((resize->vol->mft_na->rl->lcn + resize->vol->mft_na->rl->length) + >= resize->new_volume_size) { + /* + * The length of the first run is normally found in + * mft_na. However in some rare circumstance, this is + * merged with the first run of an extent of MFT, + * which implies there is a single run in the base record. + * So we have to make sure not to overflow from the + * runs present in the base extent. + */ + length = resize->vol->mft_na->rl->length; + if (ntfs_file_record_read(resize->vol, FILE_MFT, + &resize->mrec, NULL) + || !(resize->ctx = attr_get_search_ctx(NULL, + resize->mrec))) { + err_exit("Could not read the base record of MFT\n"); + } + while (!ntfs_attrs_walk(resize->ctx) + && (resize->ctx->attr->type != AT_DATA)) { } + if (resize->ctx->attr->type == AT_DATA) { + le64 high_le; + + high_le = resize->ctx->attr->highest_vcn; + if (le64_to_cpu(high_le) < length) + length = le64_to_cpu(high_le) + 1; + } else { + err_exit("Could not find the DATA of MFT\n"); + } + ntfs_attr_put_search_ctx(resize->ctx); + resize->new_mft_start = alloc_cluster(&resize->lcn_bitmap, + length, resize->new_volume_size, 0); + if (!resize->new_mft_start + || (((resize->new_mft_start->length + << resize->vol->cluster_size_bits) + >> resize->vol->mft_record_size_bits) < 16)) { + err_exit("Could not allocate 16 records in" + " the first MFT chunk\n"); + } + } + + for (mref = 0; mref < (MFT_REF)nr_mft_records; mref++) + relocate_inode(resize, mref, 0); + + while (1) { + highest_vcn = resize->mft_highest_vcn; + mref = nr_mft_records; + do { + relocate_inode(resize, --mref, 1); + if (resize->mft_highest_vcn == 0) + goto done; + } while (mref); + + if (highest_vcn == resize->mft_highest_vcn) + err_exit("Sanity check failed! Highest_vcn = %lld. " + "Please report!\n", (long long)highest_vcn); + } +done: + free(resize->mrec); +} + +static void print_hint(ntfs_volume *vol, 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, (long long)runs_mb, + (long long)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) +{ + ntfs_volume *vol = resize->vol; + + if (opt.verbose) { + printf("Estimating smallest shrunken size supported ...\n"); + printf("File feature Last used at By inode\n"); + print_hint(vol, "$MFT", resize->last_mft); + print_hint(vol, "Multi-Record", resize->last_multi_mft); + print_hint(vol, "$MFTMirr", resize->last_mftmir); + print_hint(vol, "Compressed", resize->last_compressed); + print_hint(vol, "Sparse", resize->last_sparse); + print_hint(vol, "Ordinary", resize->last_lcn); + } + + print_advise(vol, resize->last_unsupp); +} + +static void rl_expand(runlist **rl, const VCN last_vcn) +{ + int len; + runlist *p = *rl; + + len = rl_items(p) - 1; + if (len <= 0) + err_exit("rl_expand: bad runlist length: %d\n", len); + + if (p[len].vcn > last_vcn) + err_exit("rl_expand: length is already more than requested " + "(%lld > %lld)\n", + (long long)p[len].vcn, (long long)last_vcn); + + if (p[len - 1].lcn == LCN_HOLE) { + + p[len - 1].length += last_vcn - p[len].vcn; + p[len].vcn = last_vcn; + + } else if (p[len - 1].lcn >= 0) { + + p = realloc(*rl, (++len + 1) * sizeof(runlist_element)); + if (!p) + perr_exit("rl_expand: realloc"); + + p[len - 1].lcn = LCN_HOLE; + p[len - 1].length = last_vcn - p[len - 1].vcn; + rl_set(p + len, last_vcn, LCN_ENOENT, 0LL); + *rl = p; + + } else + err_exit("rl_expand: bad LCN: %lld\n", + (long long)p[len - 1].lcn); +} + +static void rl_truncate(runlist **rl, const VCN last_vcn) +{ + int len; + VCN vcn; + + len = rl_items(*rl) - 1; + if (len <= 0) + err_exit("rl_truncate: bad runlist length: %d\n", len); + + vcn = (*rl)[len].vcn; + + if (vcn < last_vcn) + rl_expand(rl, last_vcn); + + else if (vcn > last_vcn) + if (ntfs_rl_truncate(rl, last_vcn) == -1) + perr_exit("ntfs_rl_truncate"); +} + +/** + * 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(ntfs_resize_t *resize) +{ + ATTR_RECORD *a; + runlist *rl_bad; + s64 nr_clusters = resize->new_volume_size; + ntfs_volume *vol = resize->vol; + + a = resize->ctx->attr; + if (!a->non_resident) + /* FIXME: handle resident attribute value */ + err_exit("Resident attribute in $BadClust isn't supported!\n"); + + if (!(rl_bad = ntfs_mapping_pairs_decompress(vol, a, NULL))) + perr_exit("ntfs_mapping_pairs_decompress"); + + rl_truncate(&rl_bad, nr_clusters); + + a->highest_vcn = cpu_to_sle64(nr_clusters - 1LL); + a->allocated_size = cpu_to_sle64(nr_clusters * vol->cluster_size); + a->data_size = cpu_to_sle64(nr_clusters * vol->cluster_size); + + if (!replace_attribute_runlist(resize, rl_bad)) + free(rl_bad); +} + +/** + * realloc_bitmap_data_attr + * + * Reallocate 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 realloc_bitmap_data_attr(ntfs_resize_t *resize, + runlist **rl, + s64 nr_bm_clusters) +{ + s64 i; + ntfs_volume *vol = resize->vol; + ATTR_RECORD *a = resize->ctx->attr; + s64 new_size = resize->new_volume_size; + struct bitmap *bm = &resize->lcn_bitmap; + + if (!(*rl = ntfs_mapping_pairs_decompress(vol, a, NULL))) + perr_exit("ntfs_mapping_pairs_decompress"); + + release_bitmap_clusters(bm, *rl); + free(*rl); + + for (i = vol->nr_clusters; i < new_size; i++) + ntfs_bit_set(bm->bm, i, 0); + + if (!(*rl = alloc_cluster(bm, nr_bm_clusters, new_size, 0))) + perr_exit("Couldn't allocate $Bitmap clusters"); +} + +static void realloc_lcn_bitmap(ntfs_resize_t *resize, s64 bm_bsize) +{ + u8 *tmp; + + if (!(tmp = realloc(resize->lcn_bitmap.bm, bm_bsize))) + perr_exit("realloc"); + + resize->lcn_bitmap.bm = tmp; + resize->lcn_bitmap.size = bm_bsize; + bitmap_file_data_fixup(resize->new_volume_size, &resize->lcn_bitmap); +} + +/** + * 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; + int truncated; + ntfs_volume *vol = resize->vol; + + a = resize->ctx->attr; + if (!a->non_resident) + /* FIXME: handle resident attribute value */ + err_exit("Resident attribute in $Bitmap isn't supported!\n"); + + bm_bsize = nr_clusters_to_bitmap_byte_size(resize->new_volume_size); + nr_bm_clusters = rounded_up_division(bm_bsize, vol->cluster_size); + + if (resize->shrink) { + realloc_bitmap_data_attr(resize, &rl, nr_bm_clusters); + realloc_lcn_bitmap(resize, bm_bsize); + } else { + realloc_lcn_bitmap(resize, bm_bsize); + realloc_bitmap_data_attr(resize, &rl, nr_bm_clusters); + } + + a->highest_vcn = cpu_to_sle64(nr_bm_clusters - 1LL); + a->allocated_size = cpu_to_sle64(nr_bm_clusters * vol->cluster_size); + a->data_size = cpu_to_sle64(bm_bsize); + a->initialized_size = cpu_to_sle64(bm_bsize); + + truncated = !replace_attribute_runlist(resize, rl); + + /* + * 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, 0, bm_bsize, resize->lcn_bitmap.bm); + if (bm_bsize != size) { + if (size == -1) + perr_exit("Couldn't write $Bitmap"); + err_exit("Couldn't write full $Bitmap file (%lld from %lld)\n", + (long long)size, (long long)bm_bsize); + } + + if (truncated) + free(rl); +} + +/** + * lookup_data_attr + * + * Find the $DATA attribute (with or without a name) for the given MFT reference + * (inode number). + */ +static void lookup_data_attr(ntfs_volume *vol, + MFT_REF mref, + const char *aname, + ntfs_attr_search_ctx **ctx) +{ + ntfs_inode *ni; + ntfschar *ustr; + int len = 0; + + if (!(ni = ntfs_inode_open(vol, mref))) + perr_exit("ntfs_open_inode"); + + if (!(*ctx = attr_get_search_ctx(ni, NULL))) + exit(1); + + if ((ustr = ntfs_str2ucs(aname, &len)) == NULL) { + perr_printf("Couldn't convert '%s' to Unicode", aname); + exit(1); + } + + if (ntfs_attr_lookup(AT_DATA, ustr, len, CASE_SENSITIVE, + 0, NULL, 0, *ctx)) + perr_exit("ntfs_lookup_attr"); + + ntfs_ucsfree(ustr); +} + +#if CLEAN_EXIT + +static void close_inode_and_context(ntfs_attr_search_ctx *ctx) +{ + ntfs_inode *ni; + + ni = ctx->base_ntfs_ino; + if (!ni) + ni = ctx->ntfs_ino; + ntfs_attr_put_search_ctx(ctx); + if (ni) + ntfs_inode_close(ni); +} + +#endif /* CLEAN_EXIT */ + +static int check_bad_sectors(ntfs_volume *vol) +{ + ntfs_attr_search_ctx *ctx; + ntfs_inode *base_ni; + runlist *rl; + s64 i, badclusters = 0; + + ntfs_log_verbose("Checking for bad sectors ...\n"); + + lookup_data_attr(vol, FILE_BadClus, "$Bad", &ctx); + + base_ni = ctx->base_ntfs_ino; + if (!base_ni) + base_ni = ctx->ntfs_ino; + + if (NInoAttrList(base_ni)) { + err_printf("Too many bad sectors have been detected!\n"); + printf("%s", many_bad_sectors_msg); + exit(1); + } + + if (!ctx->attr->non_resident) + err_exit("Resident attribute in $BadClust! Please report to " + "%s\n", NTFS_DEV_LIST); + /* + * FIXME: The below would be partial for non-base records in the + * not yet supported multi-record case. Alternatively use audited + * ntfs_attr_truncate after an umount & mount. + */ + if (!(rl = ntfs_mapping_pairs_decompress(vol, ctx->attr, NULL))) + perr_exit("Decompressing $BadClust:$Bad mapping pairs failed"); + + for (i = 0; rl[i].length; i++) { + /* CHECKME: LCN_RL_NOT_MAPPED check isn't needed */ + if (rl[i].lcn == LCN_HOLE || rl[i].lcn == LCN_RL_NOT_MAPPED) + continue; + + badclusters += rl[i].length; + ntfs_log_verbose("Bad cluster: %#8llx - %#llx (%lld)\n", + (long long)rl[i].lcn, + (long long)rl[i].lcn + rl[i].length - 1, + (long long)rl[i].length); + } + + if (badclusters) { + printf("%sThis software has detected that the disk has at least" + " %lld bad sector%s.\n", + !opt.badsectors ? NERR_PREFIX : "WARNING: ", + (long long)badclusters, badclusters - 1 ? "s" : ""); + if (!opt.badsectors) { + printf("%s", bad_sectors_warning_msg); + exit(1); + } else + printf("WARNING: Bad sectors can cause reliability " + "problems and massive data loss!!!\n"); + } + + free(rl); +#if CLEAN_EXIT + close_inode_and_context(ctx); +#else + ntfs_attr_put_search_ctx(ctx); +#endif + + return badclusters; +} + +/** + * truncate_badclust_file + * + * Shrink the $BadClus file to match the new volume size. + */ +static void truncate_badclust_file(ntfs_resize_t *resize) +{ + printf("Updating $BadClust file ...\n"); + + lookup_data_attr(resize->vol, FILE_BadClus, "$Bad", &resize->ctx); + /* FIXME: sanity_check_attr(ctx->attr); */ + truncate_badclust_bad_attr(resize); + + if (write_mft_record(resize->vol, resize->ctx->ntfs_ino->mft_no, + resize->ctx->mrec)) + perr_exit("Couldn't update $BadClust"); + +#if CLEAN_EXIT + close_inode_and_context(resize->ctx); +#else + ntfs_attr_put_search_ctx(resize->ctx); +#endif +} + +/** + * truncate_bitmap_file + * + * Shrink the $Bitmap file to match the new volume size. + */ +static void truncate_bitmap_file(ntfs_resize_t *resize) +{ + ntfs_volume *vol = resize->vol; + + printf("Updating $Bitmap file ...\n"); + + lookup_data_attr(resize->vol, FILE_Bitmap, NULL, &resize->ctx); + truncate_bitmap_data_attr(resize); + + if (resize->new_mft_start) { + s64 pos; + + /* write the MFT record at its new location */ + pos = (resize->new_mft_start->lcn << vol->cluster_size_bits) + + (FILE_Bitmap << vol->mft_record_size_bits); + if (!opt.ro_flag + && (ntfs_mst_pwrite(vol->dev, pos, 1, + vol->mft_record_size, resize->ctx->mrec) != 1)) + perr_exit("Couldn't update $Bitmap at new location"); + } else { + if (write_mft_record(vol, resize->ctx->ntfs_ino->mft_no, + resize->ctx->mrec)) + perr_exit("Couldn't update $Bitmap"); + } + +#if CLEAN_EXIT + close_inode_and_context(resize->ctx); +#else + ntfs_attr_put_search_ctx(resize->ctx); +#endif +} + +/** + * 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 int setup_lcn_bitmap(struct bitmap *bm, s64 nr_clusters) +{ + /* Determine lcn bitmap byte size and allocate it. */ + bm->size = rounded_up_division(nr_clusters, 8); + + bm->bm = ntfs_calloc(bm->size); + if (!bm->bm) + return -1; + + bitmap_file_data_fixup(nr_clusters, bm); + return 0; +} + +/** + * update_bootsector + * + * FIXME: should be done using ntfs_* functions + */ +static void update_bootsector(ntfs_resize_t *r) +{ + NTFS_BOOT_SECTOR *bs; + ntfs_volume *vol = r->vol; + s64 bs_size = vol->sector_size; + + printf("Updating Boot record ...\n"); + + bs = (NTFS_BOOT_SECTOR*)ntfs_malloc(vol->sector_size); + if (!bs) + perr_exit("ntfs_malloc"); + + 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, bs_size) == -1) + perr_exit("read() error"); + + bs->number_of_sectors = cpu_to_sle64(r->new_volume_size * + bs->bpb.sectors_per_cluster); + + if (r->mftmir_old) { + r->progress.flags |= NTFS_PROGBAR_SUPPRESS; + /* Be sure the MFTMirr holds the updated MFT runlist */ + if (r->new_mft_start) + copy_clusters(r, r->mftmir_rl.lcn, + r->new_mft_start->lcn, r->mftmir_rl.length); + else + copy_clusters(r, r->mftmir_rl.lcn, r->mftmir_old, + r->mftmir_rl.length); + bs->mftmirr_lcn = cpu_to_sle64(r->mftmir_rl.lcn); + r->progress.flags &= ~NTFS_PROGBAR_SUPPRESS; + } + /* Set the start of the relocated MFT */ + if (r->new_mft_start) { + bs->mft_lcn = cpu_to_sle64(r->new_mft_start->lcn); + /* no more need for the new MFT start */ + free(r->new_mft_start); + r->new_mft_start = (runlist_element*)NULL; + } + + 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, bs_size) == -1) + perr_exit("write() error"); + /* + * Set the backup boot sector, if the target size is + * either not defined or is defined with no multiplier + * suffix and is a multiple of the sector size. + * With these conditions we can be confident enough that + * the partition size is already defined or it will be + * later defined with the same exact value. + */ + if (!opt.ro_flag && opt.reliable_size + && !(opt.bytes % vol->sector_size)) { + if (vol->dev->d_ops->seek(vol->dev, opt.bytes + - vol->sector_size, SEEK_SET) == (off_t)-1) + perr_exit("lseek"); + if (vol->dev->d_ops->write(vol->dev, bs, bs_size) == -1) + perr_exit("write() error"); + } + free(bs); +} + +/** + * vol_size + */ +static s64 vol_size(ntfs_volume *v, s64 nr_clusters) +{ + /* add one sector_size for the backup boot sector */ + return nr_clusters * v->cluster_size + v->sector_size; +} + +/** + * print_vol_size + * + * Print the volume size in bytes and decimal megabytes. + */ +static void print_vol_size(const char *str, s64 bytes) +{ + printf("%s: %lld bytes (%lld MB)\n", str, (long long)bytes, + (long long)rounded_up_division(bytes, NTFS_MBYTE)); +} + +/** + * print_disk_usage + * + * Display the amount of disk space in use. + */ +static void print_disk_usage(ntfs_volume *vol, s64 nr_used_clusters) +{ + s64 total, used; + + total = vol->nr_clusters * vol->cluster_size; + used = nr_used_clusters * vol->cluster_size; + + /* WARNING: don't modify the text, external tools grep for it */ + if (!opt.infombonly) { + printf("Space in use : %lld MB (%.1f%%)\n", + (long long)rounded_up_division(used, NTFS_MBYTE), + 100.0 * ((float)used / total)); + } +} + +static void print_num_of_relocations(ntfs_resize_t *resize) +{ + s64 relocations = resize->relocations * resize->vol->cluster_size; + + printf("Needed relocations : %lld (%lld MB)\n", + (long long)resize->relocations, (long long) + rounded_up_division(relocations, NTFS_MBYTE)); +} + +static ntfs_volume *check_volume(void) +{ + ntfs_volume *myvol = NULL; + + /* + * Pass NTFS_MNT_FORENSIC so that the mount process does not modify the + * volume at all. We will do the logfile emptying and dirty setting + * later if needed. + */ + if (!(myvol = ntfs_mount(opt.volume, opt.ro_flag | NTFS_MNT_FORENSIC))) + { + int err = errno; + + perr_printf("Opening '%s' as NTFS failed", opt.volume); + switch (err) { + case EINVAL : + printf(invalid_ntfs_msg, opt.volume); + break; + case EIO : + printf("%s", corrupt_volume_msg); + break; + case EPERM : + printf("%s", hibernated_volume_msg); + break; + case EOPNOTSUPP : + printf("%s", unclean_journal_msg); + break; + case EBUSY : + printf("%s", opened_volume_msg); + break; + default : + break; + } + exit(1); + } + return myvol; +} + + +/** + * 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 ntfs_volume *mount_volume(void) +{ + unsigned long mntflag; + ntfs_volume *vol = NULL; + + 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); + } + vol = check_volume(); + + if (vol->flags & VOLUME_IS_DIRTY) + if (opt.force-- <= 0) + err_exit("Volume is scheduled for check.\nRun chkdsk /f" + " and please try again, or see option -f.\n"); + + if (NTFS_MAX_CLUSTER_SIZE < vol->cluster_size) + err_exit("Cluster size %u is too large!\n", + (unsigned int)vol->cluster_size); + + if (!opt.infombonly) { + printf("Device name : %s\n", opt.volume); + printf("NTFS volume version: %d.%d\n", + vol->major_ver, vol->minor_ver); + } + if (ntfs_version_is_supported(vol)) + perr_exit("Unknown NTFS version"); + + if (!opt.infombonly) { + printf("Cluster size : %u bytes\n", + (unsigned int)vol->cluster_size); + print_vol_size("Current volume size", + vol_size(vol, vol->nr_clusters)); + } + + return vol; +} + +/** + * 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(ntfs_volume *vol) +{ + printf("Schedule chkdsk for NTFS consistency check at Windows boot " + "time ...\n"); + vol->flags |= VOLUME_IS_DIRTY; + if (ntfs_volume_write_flags(vol, vol->flags)) + perr_exit("Failed to set the volume dirty"); + + /* Porting note: This flag does not exist in libntfs-3g. The dirty flag + * is never modified by libntfs-3g on unmount and we set it above. We + * can safely comment out this statement. */ + /* NVolSetWasDirty(vol); */ + + 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"); +} + +static void set_disk_usage_constraint(ntfs_resize_t *resize) +{ + /* last lcn for a filled up volume (no empty space) */ + s64 last = resize->inuse - 1; + + if (resize->last_unsupp < last) + resize->last_unsupp = last; +} + +static void check_resize_constraints(ntfs_resize_t *resize) +{ + s64 new_size = resize->new_volume_size; + + /* FIXME: resize.shrink true also if only -i is used */ + if (!resize->shrink) + return; + + if (resize->inuse == resize->vol->nr_clusters) + err_exit("Volume is full. To shrink it, " + "delete unused files.\n"); + + if (opt.info || opt.infombonly) + return; + + /* FIXME: reserve some extra space so Windows can boot ... */ + if (new_size < resize->inuse) + err_exit("New size can't be less than the space already" + " occupied by data.\nYou either need to delete unused" + " files or see the -i option.\n"); + + if (new_size <= resize->last_unsupp) + err_exit("The fragmentation type, you have, isn't " + "supported yet. Rerun ntfsresize\nwith " + "the -i option to estimate the smallest " + "shrunken volume size supported.\n"); + + print_num_of_relocations(resize); +} + +static void check_cluster_allocation(ntfs_volume *vol, ntfsck_t *fsck) +{ + memset(fsck, 0, sizeof(ntfsck_t)); + + if (opt.show_progress) + fsck->flags |= NTFSCK_PROGBAR; + + if (setup_lcn_bitmap(&fsck->lcn_bitmap, vol->nr_clusters) != 0) + perr_exit("Failed to setup allocation bitmap"); + if (build_allocation_bitmap(vol, fsck) != 0) + exit(1); + if (fsck->outsider || fsck->multi_ref) { + err_printf("Filesystem check failed!\n"); + if (fsck->outsider) + err_printf("%d clusters are referenced outside " + "of the volume.\n", fsck->outsider); + if (fsck->multi_ref) + err_printf("%d clusters are referenced multiple" + " times.\n", fsck->multi_ref); + printf("%s", corrupt_volume_msg); + exit(1); + } + + compare_bitmaps(vol, &fsck->lcn_bitmap); +} + +/* + * Following are functions to expand an NTFS file system + * to the beginning of a partition. The old metadata can be + * located according to the backup bootsector, provided it can + * still be found at the end of the partition. + * + * The data itself is kept in place, and this is only possible + * if the expanded size is a multiple of cluster size, and big + * enough to hold the new $Boot, $Bitmap and $MFT + * + * The volume cannot be mounted because the layout of data does + * not match the volume parameters. The alignments of MFT entries + * and index blocks may be different in the new volume and the old + * one. The "ntfs_volume" structure is only partially usable, + * "ntfs_inode" and "search_context" cannot be used until the + * metadata has been moved and the volume is opened. + * + * Currently, no part of this new code is called from old code, + * and the only change in old code is the processing of options. + * Deduplication of code should be done later when the code is + * proved safe. + * + */ + +typedef struct EXPAND { + ntfs_volume *vol; + u64 original_sectors; + u64 new_sectors; + u64 bitmap_allocated; + u64 bitmap_size; + u64 boot_size; + u64 mft_size; + LCN mft_lcn; + s64 byte_increment; + s64 sector_increment; + s64 cluster_increment; + u8 *bitmap; + u8 *mft_bitmap; + char *bootsector; + MFT_RECORD *mrec; + struct progress_bar *progress; + struct DELAYED *delayed_runlists; /* runlists to process later */ +} expand_t; + +/* + * Locate an attribute in an MFT record + * + * Returns NULL if not found (with no error message) + */ + +static ATTR_RECORD *find_attr(MFT_RECORD *mrec, ATTR_TYPES type, + ntfschar *name, int namelen) +{ + ATTR_RECORD *a; + u32 offset; + ntfschar *attrname; + + /* fetch the requested attribute */ + offset = le16_to_cpu(mrec->attrs_offset); + a = (ATTR_RECORD*)((char*)mrec + offset); + attrname = (ntfschar*)((char*)a + le16_to_cpu(a->name_offset)); + while ((a->type != AT_END) + && ((a->type != type) + || (a->name_length != namelen) + || (namelen && memcmp(attrname,name,2*namelen))) + && (offset < le32_to_cpu(mrec->bytes_in_use))) { + offset += le32_to_cpu(a->length); + a = (ATTR_RECORD*)((char*)mrec + offset); + if (namelen) + attrname = (ntfschar*)((char*)a + + le16_to_cpu(a->name_offset)); + } + if ((a->type != type) + || (a->name_length != namelen) + || (namelen && memcmp(attrname,name,2*namelen))) + a = (ATTR_RECORD*)NULL; + return (a); +} + +/* + * Read an MFT record and find an unnamed attribute + * + * Returns NULL if fails to read or attribute is not found + */ + +static ATTR_RECORD *get_unnamed_attr(expand_t *expand, ATTR_TYPES type, + s64 inum) +{ + ntfs_volume *vol; + ATTR_RECORD *a; + MFT_RECORD *mrec; + s64 pos; + BOOL found; + int got; + + found = FALSE; + a = (ATTR_RECORD*)NULL; + mrec = expand->mrec; + vol = expand->vol; + pos = (vol->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits) + + expand->byte_increment; + got = ntfs_mst_pread(vol->dev, pos, 1, vol->mft_record_size, mrec); + if ((got == 1) && (mrec->flags & MFT_RECORD_IN_USE)) { + a = find_attr(expand->mrec, type, NULL, 0); + found = a && (a->type == type) && !a->name_length; + } + /* not finding the attribute list is not an error */ + if (!found && (type != AT_ATTRIBUTE_LIST)) { + err_printf("Could not find attribute 0x%lx in inode %lld\n", + (long)le32_to_cpu(type), (long long)inum); + a = (ATTR_RECORD*)NULL; + } + return (a); +} + +/* + * Read an MFT record and find an unnamed attribute + * + * Returns NULL if fails + */ + +static ATTR_RECORD *read_and_get_attr(expand_t *expand, ATTR_TYPES type, + s64 inum, ntfschar *name, int namelen) +{ + ntfs_volume *vol; + ATTR_RECORD *a; + MFT_RECORD *mrec; + s64 pos; + int got; + + a = (ATTR_RECORD*)NULL; + mrec = expand->mrec; + vol = expand->vol; + pos = (vol->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits) + + expand->byte_increment; + got = ntfs_mst_pread(vol->dev, pos, 1, vol->mft_record_size, mrec); + if ((got == 1) && (mrec->flags & MFT_RECORD_IN_USE)) { + a = find_attr(expand->mrec, type, name, namelen); + } + if (!a) { + err_printf("Could not find attribute 0x%lx in inode %lld\n", + (long)le32_to_cpu(type), (long long)inum); + } + return (a); +} + +/* + * Get the size allocated to the unnamed data of some inode + * + * Returns zero if fails. + */ + +static s64 get_data_size(expand_t *expand, s64 inum) +{ + ATTR_RECORD *a; + s64 size; + + size = 0; + /* get the size of unnamed $DATA */ + a = get_unnamed_attr(expand, AT_DATA, inum); + if (a && a->non_resident) + size = le64_to_cpu(a->allocated_size); + if (!size) { + err_printf("Bad record %lld, could not get its size\n", + (long long)inum); + } + return (size); +} + +/* + * Get the MFT bitmap + * + * Returns NULL if fails. + */ + +static u8 *get_mft_bitmap(expand_t *expand) +{ + ATTR_RECORD *a; + ntfs_volume *vol; + runlist_element *rl; + runlist_element *prl; + u32 bitmap_size; + BOOL ok; + + expand->mft_bitmap = (u8*)NULL; + vol = expand->vol; + /* get the runlist of unnamed bitmap */ + a = get_unnamed_attr(expand, AT_BITMAP, FILE_MFT); + ok = TRUE; + bitmap_size = le64_to_cpu(a->allocated_size); + if (a + && a->non_resident + && ((bitmap_size << (vol->mft_record_size_bits + 3)) + >= expand->mft_size)) { +// rl in extent not implemented + rl = ntfs_mapping_pairs_decompress(expand->vol, a, + (runlist_element*)NULL); + expand->mft_bitmap = (u8*)ntfs_calloc(bitmap_size); + if (rl && expand->mft_bitmap) { + for (prl=rl; prl->length && ok; prl++) { + lseek_to_cluster(vol, + prl->lcn + expand->cluster_increment); + ok = !read_all(vol->dev, expand->mft_bitmap, + prl->length << vol->cluster_size_bits); + } + if (!ok) { + err_printf("Could not read the MFT bitmap\n"); + free(expand->mft_bitmap); + expand->mft_bitmap = (u8*)NULL; + } + free(rl); + } else { + err_printf("Could not get the MFT bitmap\n"); + } + } else + err_printf("Invalid MFT bitmap\n"); + return (expand->mft_bitmap); +} + +/* + * Check for bad sectors + * + * Deduplication to be done when proved safe + */ + +static int check_expand_bad_sectors(expand_t *expand, ATTR_RECORD *a) +{ + runlist *rl; + int res; + s64 i, badclusters = 0; + + res = 0; + ntfs_log_verbose("Checking for bad sectors ...\n"); + + if (find_attr(expand->mrec, AT_ATTRIBUTE_LIST, NULL, 0)) { + err_printf("Hopelessly many bad sectors have been detected!\n"); + err_printf("%s", many_bad_sectors_msg); + res = -1; + } else { + + /* + * FIXME: The below would be partial for non-base records in the + * not yet supported multi-record case. Alternatively use audited + * ntfs_attr_truncate after an umount & mount. + */ + rl = ntfs_mapping_pairs_decompress(expand->vol, a, NULL); + if (!rl) { + perr_printf("Decompressing $BadClust:" + "$Bad mapping pairs failed"); + res = -1; + } else { + for (i = 0; rl[i].length; i++) { + /* CHECKME: LCN_RL_NOT_MAPPED check isn't needed */ + if (rl[i].lcn == LCN_HOLE + || rl[i].lcn == LCN_RL_NOT_MAPPED) + continue; + + badclusters += rl[i].length; + ntfs_log_verbose("Bad cluster: %#8llx - %#llx" + " (%lld)\n", + (long long)rl[i].lcn, + (long long)rl[i].lcn + + rl[i].length - 1, + (long long)rl[i].length); + } + + if (badclusters) { + err_printf("%sThis software has detected that" + " the disk has at least" + " %lld bad sector%s.\n", + !opt.badsectors ? NERR_PREFIX + : "WARNING: ", + (long long)badclusters, + badclusters - 1 ? "s" : ""); + if (!opt.badsectors) { + err_printf("%s", bad_sectors_warning_msg); + res = -1; + } else + err_printf("WARNING: Bad sectors can cause" + " reliability problems" + " and massive data loss!!!\n"); + } + free(rl); + } + } + return (res); +} + +/* + * Check miscellaneous expansion constraints + */ + +static int check_expand_constraints(expand_t *expand) +{ + static ntfschar bad[] = { + const_cpu_to_le16('$'), const_cpu_to_le16('B'), + const_cpu_to_le16('a'), const_cpu_to_le16('d') + } ; + ATTR_RECORD *a; + runlist_element *rl; + VOLUME_INFORMATION *volinfo; + VOLUME_FLAGS flags; + int res; + + if (opt.verbose) + ntfs_log_verbose("Checking for expansion constraints...\n"); + res = 0; + /* extents for $MFT are not supported */ + if (get_unnamed_attr(expand, AT_ATTRIBUTE_LIST, FILE_MFT)) { + err_printf("The $MFT is too much fragmented\n"); + res = -1; + } + /* fragmented $MFTMirr is not supported */ + a = get_unnamed_attr(expand, AT_DATA, FILE_MFTMirr); + if (a) { + rl = ntfs_mapping_pairs_decompress(expand->vol, a, NULL); + if (!rl || !rl[0].length || rl[1].length) { + err_printf("$MFTMirr is bad or fragmented\n"); + res = -1; + } + free(rl); + } + /* fragmented $Boot is not supported */ + a = get_unnamed_attr(expand, AT_DATA, FILE_Boot); + if (a) { + rl = ntfs_mapping_pairs_decompress(expand->vol, a, NULL); + if (!rl || !rl[0].length || rl[1].length) { + err_printf("$Boot is bad or fragmented\n"); + res = -1; + } + free(rl); + } + /* Volume should not be marked dirty */ + a = get_unnamed_attr(expand, AT_VOLUME_INFORMATION, FILE_Volume); + if (a) { + volinfo = (VOLUME_INFORMATION*) + (le16_to_cpu(a->value_offset) + (char*)a); + flags = volinfo->flags; + if ((flags & VOLUME_IS_DIRTY) && (opt.force-- <= 0)) { + err_printf("Volume is scheduled for check.\nRun chkdsk /f" + " and please try again, or see option -f.\n"); + res = -1; + } + } else { + err_printf("Could not get Volume flags\n"); + res = -1; + } + + /* There should not be too many bad clusters */ + a = read_and_get_attr(expand, AT_DATA, FILE_BadClus, bad, 4); + if (!a || !a->non_resident) { + err_printf("Resident attribute in $BadClust! Please report to " + "%s\n", NTFS_DEV_LIST); + res = -1; + } else + if (check_expand_bad_sectors(expand,a)) + res = -1; + return (res); +} + +/* + * Compute the new sizes and check whether the NTFS file + * system can be expanded + * + * The partition has to have been expanded, + * the extra space must be able to hold the $MFT, $Boot, and $Bitmap + * the extra space must be a multiple of cluster size + * + * Returns TRUE if the partition can be expanded, + * FALSE if it canno be expanded or option --info was set + */ + +static BOOL can_expand(expand_t *expand, ntfs_volume *vol) +{ + s64 old_sector_count; + s64 sectors_needed; + s64 clusters; + s64 minimum_size; + s64 got; + s64 advice; + s64 bitmap_bits; + BOOL ok; + + ok = TRUE; + old_sector_count = vol->nr_clusters + << (vol->cluster_size_bits - vol->sector_size_bits); + /* do not include the space lost near the end */ + expand->cluster_increment = (expand->new_sectors + >> (vol->cluster_size_bits - vol->sector_size_bits)) + - vol->nr_clusters; + expand->byte_increment = expand->cluster_increment + << vol->cluster_size_bits; + expand->sector_increment = expand->byte_increment + >> vol->sector_size_bits; + printf("Sectors allocated to volume : old %lld current %lld difference %lld\n", + (long long)old_sector_count, + (long long)(old_sector_count + expand->sector_increment), + (long long)expand->sector_increment); + printf("Clusters allocated to volume : old %lld current %lld difference %lld\n", + (long long)vol->nr_clusters, + (long long)(vol->nr_clusters + + expand->cluster_increment), + (long long)expand->cluster_increment); + /* the new size must be bigger */ + if ((expand->sector_increment < 0) + || (!expand->sector_increment && !opt.info)) { + err_printf("Cannot expand volume : the partition has not been expanded\n"); + ok = FALSE; + } + /* the old bootsector must match the backup */ + got = ntfs_pread(expand->vol->dev, expand->byte_increment, + vol->sector_size, expand->mrec); + if ((got != vol->sector_size) + || memcmp(expand->bootsector,expand->mrec,vol->sector_size)) { + err_printf("The backup bootsector does not match the old bootsector\n"); + ok = FALSE; + } + if (ok) { + /* read the first MFT record, to get the MFT size */ + expand->mft_size = get_data_size(expand, FILE_MFT); + /* read the 6th MFT record, to get the $Boot size */ + expand->boot_size = get_data_size(expand, FILE_Boot); + if (!expand->mft_size || !expand->boot_size) { + ok = FALSE; + } else { + /* + * The bitmap is one bit per full cluster, + * accounting for the backup bootsector. + * When evaluating the minimal size, the bitmap + * size must be adapted to the minimal size : + * bits = clusters + ceil(clusters/clustersize) + */ + if (opt.info) { + clusters = (((expand->original_sectors + 1) + << vol->sector_size_bits) + + expand->mft_size + + expand->boot_size) + >> vol->cluster_size_bits; + bitmap_bits = ((clusters + 1) + << vol->cluster_size_bits) + / (vol->cluster_size + 1); + } else { + bitmap_bits = (expand->new_sectors + 1) + >> (vol->cluster_size_bits + - vol->sector_size_bits); + } + /* byte size must be a multiple of 8 */ + expand->bitmap_size = ((bitmap_bits + 63) >> 3) & -8; + expand->bitmap_allocated = ((expand->bitmap_size - 1) + | (vol->cluster_size - 1)) + 1; + expand->mft_lcn = (expand->boot_size + + expand->bitmap_allocated) + >> vol->cluster_size_bits; + /* + * Check whether $Boot, $Bitmap and $MFT can fit + * into the expanded space. + */ + sectors_needed = (expand->boot_size + expand->mft_size + + expand->bitmap_allocated) + >> vol->sector_size_bits; + if (!opt.info + && (sectors_needed >= expand->sector_increment)) { + err_printf("The expanded space cannot hold the new metadata\n"); + err_printf(" expanded space %lld sectors\n", + (long long)expand->sector_increment); + err_printf(" needed space %lld sectors\n", + (long long)sectors_needed); + ok = FALSE; + } + } + } + if (ok) { + advice = expand->byte_increment; + /* the increment must be an integral number of clusters */ + if (expand->byte_increment & (vol->cluster_size - 1)) { + err_printf("Cannot expand volume without copying the data :\n"); + err_printf("There are %d sectors in a cluster,\n", + (int)(vol->cluster_size/vol->sector_size)); + err_printf(" and the sector difference is not a multiple of %d\n", + (int)(vol->cluster_size/vol->sector_size)); + advice = expand->byte_increment & ~vol->cluster_size; + ok = FALSE; + } + if (!ok) + err_printf("You should increase the beginning of partition by %d sectors\n", + (int)((expand->byte_increment - advice) + >> vol->sector_size_bits)); + } + if (ok) + ok = !check_expand_constraints(expand); + if (ok && opt.info) { + minimum_size = (expand->original_sectors + << vol->sector_size_bits) + + expand->boot_size + + expand->mft_size + + expand->bitmap_allocated; + + printf("You must expand the partition to at least %lld bytes,\n", + (long long)(minimum_size + vol->sector_size)); + printf("and you may add a multiple of %ld bytes to this size.\n", + (long)vol->cluster_size); + printf("The minimum NTFS volume size is %lld bytes\n", + (long long)minimum_size); + ok = FALSE; + } + return (ok); +} + +static int set_bitmap(expand_t *expand, runlist_element *rl) +{ + int res; + s64 lcn; + s64 lcn_end; + BOOL reallocated; + + res = -1; + reallocated = FALSE; + if ((rl->lcn >= 0) + && (rl->length > 0) + && ((rl->lcn + rl->length) + <= (expand->vol->nr_clusters + expand->cluster_increment))) { + lcn = rl->lcn; + lcn_end = lcn + rl->length; + while ((lcn & 7) && (lcn < lcn_end)) { + if (expand->bitmap[lcn >> 3] & 1 << (lcn & 7)) + reallocated = TRUE; + expand->bitmap[lcn >> 3] |= 1 << (lcn & 7); + lcn++; + } + while ((lcn_end - lcn) >= 8) { + if (expand->bitmap[lcn >> 3]) + reallocated = TRUE; + expand->bitmap[lcn >> 3] = 255; + lcn += 8; + } + while (lcn < lcn_end) { + if (expand->bitmap[lcn >> 3] & 1 << (lcn & 7)) + reallocated = TRUE; + expand->bitmap[lcn >> 3] |= 1 << (lcn & 7); + lcn++; + } + if (reallocated) + err_printf("Reallocated cluster found in run" + " lcn 0x%llx length %lld\n", + (long long)rl->lcn,(long long)rl->length); + else + res = 0; + } else { + err_printf("Bad run : lcn 0x%llx length %lld\n", + (long long)rl->lcn,(long long)rl->length); + } + return (res); +} + +/* + * Write the backup bootsector + * + * When this has been done, the resizing cannot be done again + */ + +static int write_bootsector(expand_t *expand) +{ + ntfs_volume *vol; + s64 bw; + int res; + + res = -1; + vol = expand->vol; + if (opt.verbose) + ntfs_log_verbose("Rewriting the backup bootsector\n"); + if (opt.ro_flag) + bw = vol->sector_size; + else + bw = ntfs_pwrite(vol->dev, + expand->new_sectors*vol->sector_size, + vol->sector_size, expand->bootsector); + if (bw == vol->sector_size) + res = 0; + else { + if (bw != -1) + errno = EINVAL; + if (!bw) + err_printf("Failed to rewrite the bootsector (size=0)\n"); + else + err_printf("Error rewriting the bootsector"); + } + return (res); +} + +/* + * Write the new main bitmap + */ + +static int write_bitmap(expand_t *expand) +{ + ntfs_volume *vol; + s64 bw; + u64 cluster; + int res; + + res = -1; + vol = expand->vol; + cluster = vol->nr_clusters + expand->cluster_increment; + while (cluster < (expand->bitmap_size << 3)) { + expand->bitmap[cluster >> 3] |= 1 << (cluster & 7); + cluster++; + } + if (opt.verbose) + ntfs_log_verbose("Writing the new bitmap...\n"); + /* write the full allocation (to avoid having to read) */ + if (opt.ro_flag) + bw = expand->bitmap_allocated; + else + bw = ntfs_pwrite(vol->dev, expand->boot_size, + expand->bitmap_allocated, expand->bitmap); + if (bw == (s64)expand->bitmap_allocated) + res = 0; + else { + if (bw != -1) + errno = EINVAL; + if (!bw) + err_printf("Failed to write the bitmap (size=0)\n"); + else + err_printf("Error rewriting the bitmap"); + } + return (res); +} + +/* + * Copy the $MFT to $MFTMirr + * + * The $MFTMirr is not relocated as it should be kept away from $MFT. + * Apart from the backup bootsector, this is the only part which is + * overwritten. This has no effect on being able to redo the resizing + * if something goes wrong, as the $MFTMirr is never read. However + * this is done near the end of the resizing. + */ + +static int copy_mftmirr(expand_t *expand) +{ + ntfs_volume *vol; + s64 pos; + s64 inum; + int res; + u16 usa_ofs; + le16 *pusn; + u16 usn; + + if (opt.verbose) + ntfs_log_verbose("Copying $MFT to $MFTMirr...\n"); + vol = expand->vol; + res = 0; + for (inum=FILE_MFT; !res && (inum<=FILE_Volume); inum++) { + /* read the new $MFT */ + pos = (expand->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits); + if (ntfs_mst_pread(vol->dev, pos, 1, vol->mft_record_size, + expand->mrec) == 1) { + /* overwrite the old $MFTMirr */ + pos = (vol->mftmirr_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits) + + expand->byte_increment; + usa_ofs = le16_to_cpu(expand->mrec->usa_ofs); + pusn = (le16*)((u8*)expand->mrec + usa_ofs); + usn = le16_to_cpu(*pusn) - 1; + if (!usn || (usn == 0xffff)) + usn = -2; + *pusn = cpu_to_le16(usn); + if (!opt.ro_flag + && (ntfs_mst_pwrite(vol->dev, pos, 1, + vol->mft_record_size, expand->mrec) != 1)) { + err_printf("Failed to overwrite the old $MFTMirr\n"); + res = -1; + } + } else { + err_printf("Failed to write the new $MFT\n"); + res = -1; + } + } + return (res); +} + +/* + * Copy the $Boot, including the bootsector + * + * When the bootsector has been copied, repair tools are able to + * fix things, but this is dangerous if the other metadata do + * not point to actual user data. So this must be done near the end + * of resizing. + */ + +static int copy_boot(expand_t *expand) +{ + NTFS_BOOT_SECTOR *bs; + char *buf; + ntfs_volume *vol; + s64 mftmirr_lcn; + s64 written; + u32 boot_cnt; + u32 hidden_sectors; + le32 hidden_sectors_le; + int res; + + if (opt.verbose) + ntfs_log_verbose("Copying $Boot...\n"); + vol = expand->vol; + res = 0; + buf = (char*)ntfs_malloc(vol->cluster_size); + if (buf) { + /* set the new volume parameters in the bootsector */ + bs = (NTFS_BOOT_SECTOR*)expand->bootsector; + bs->number_of_sectors = cpu_to_le64(expand->new_sectors); + bs->mft_lcn = cpu_to_le64(expand->mft_lcn); + mftmirr_lcn = vol->mftmirr_lcn + expand->cluster_increment; + bs->mftmirr_lcn = cpu_to_le64(mftmirr_lcn); + /* the hidden sectors are needed to boot into windows */ + memcpy(&hidden_sectors_le,&bs->bpb.hidden_sectors,4); + /* alignment messed up on the Sparc */ + if (hidden_sectors_le) { + hidden_sectors = le32_to_cpu(hidden_sectors_le); + if (hidden_sectors >= expand->sector_increment) + hidden_sectors -= expand->sector_increment; + else + hidden_sectors = 0; + hidden_sectors_le = cpu_to_le32(hidden_sectors); + memcpy(&bs->bpb.hidden_sectors,&hidden_sectors_le,4); + } + written = 0; + boot_cnt = expand->boot_size >> vol->cluster_size_bits; + while (!res && (written < boot_cnt)) { + lseek_to_cluster(vol, expand->cluster_increment + written); + if (!read_all(vol->dev, buf, vol->cluster_size)) { + if (!written) + memcpy(buf, expand->bootsector, vol->sector_size); + lseek_to_cluster(vol, written); + if (!opt.ro_flag + && write_all(vol->dev, buf, vol->cluster_size)) { + err_printf("Failed to write the new $Boot\n"); + res = -1; + } else + written++; + } else { + err_printf("Failed to read the old $Boot\n"); + res = -1; + } + } + free(buf); + } else { + err_printf("Failed to allocate buffer\n"); + res = -1; + } + return (res); +} + +/* + * Process delayed runlist updates + * + * This is derived from delayed_updates() and they should + * both be merged when the new code is considered safe. + */ + +static void delayed_expand(ntfs_volume *vol, struct DELAYED *delayed, + struct progress_bar *progress) +{ + unsigned long count; + struct DELAYED *current; + int step = 100; + + if (delayed) { + if (opt.verbose) + ntfs_log_verbose("Delayed updating of overflowing runlists...\n"); + count = 0; + /* count by steps because of inappropriate resolution */ + for (current=delayed; current; current=current->next) + count += step; + progress_init(progress, 0, count, + (opt.show_progress ? NTFS_PROGBAR : 0)); + current = delayed; + count = 0; + while (current) { + delayed = current; + if (!opt.ro_flag) + expand_attribute_runlist(vol, delayed); + count += step; + progress_update(progress, count); + current = current->next; + if (delayed->attr_name) + free(delayed->attr_name); + free(delayed->head_rl); + free(delayed); + } + } +} + +/* + * Expand the sizes in indexes for inodes which were expanded + * + * Only the new $Bitmap sizes are identified as needed to be + * adjusted in index. The $BadClus is only expanded in an + * alternate data stream, whose sizes are not present in the index. + * + * This is modifying the initial data, and can only be done when + * the volume has been reopened after expanding. + */ + +static int expand_index_sizes(expand_t *expand) +{ + ntfs_inode *ni; + int res; + + res = -1; + ni = ntfs_inode_open(expand->vol, FILE_Bitmap); + if (ni) { + NInoSetDirty(ni); + NInoFileNameSetDirty(ni); + ntfs_inode_close(ni); + res = 0; + } + return (res); +} + +/* + * Update a runlist into an attribute + * + * This is derived from replace_attribute_runlist() and they should + * both be merged when the new code is considered safe. + */ + +static int update_runlist(expand_t *expand, s64 inum, + ATTR_RECORD *a, runlist_element *rl) +{ + ntfs_resize_t resize; + ntfs_attr_search_ctx ctx; + ntfs_volume *vol; + MFT_RECORD *mrec; + runlist *head_rl; + int mp_size; + int l; + int must_delay; + void *mp; + + vol = expand->vol; + mrec = expand->mrec; + head_rl = rl; + rl_fixup(&rl); + if ((mp_size = ntfs_get_size_for_mapping_pairs(vol, rl, + 0, INT_MAX)) == -1) + perr_exit("ntfs_get_size_for_mapping_pairs"); + + if (a->name_length) { + u16 name_offs = le16_to_cpu(a->name_offset); + u16 mp_offs = le16_to_cpu(a->mapping_pairs_offset); + + if (name_offs >= mp_offs) + err_exit("Attribute name is after mapping pairs! " + "Please report!\n"); + } + + /* CHECKME: don't trust mapping_pairs is always the last item in the + attribute, instead check for the real size/space */ + l = (int)le32_to_cpu(a->length) - le16_to_cpu(a->mapping_pairs_offset); + must_delay = 0; + if (mp_size > l) { + s32 remains_size; + char *next_attr; + + ntfs_log_verbose("Enlarging attribute header ...\n"); + + mp_size = (mp_size + 7) & ~7; + + ntfs_log_verbose("Old mp size : %d\n", l); + ntfs_log_verbose("New mp size : %d\n", mp_size); + ntfs_log_verbose("Bytes in use : %u\n", (unsigned int) + le32_to_cpu(mrec->bytes_in_use)); + + next_attr = (char *)a + le32_to_cpu(a->length); + l = mp_size - l; + + ntfs_log_verbose("Bytes in use new : %u\n", l + (unsigned int) + le32_to_cpu(mrec->bytes_in_use)); + ntfs_log_verbose("Bytes allocated : %u\n", (unsigned int) + le32_to_cpu(mrec->bytes_allocated)); + + remains_size = le32_to_cpu(mrec->bytes_in_use); + remains_size -= (next_attr - (char *)mrec); + + ntfs_log_verbose("increase : %d\n", l); + ntfs_log_verbose("shift : %lld\n", + (long long)remains_size); + if (le32_to_cpu(mrec->bytes_in_use) + l > + le32_to_cpu(mrec->bytes_allocated)) { + ntfs_log_verbose("Queuing expansion for later processing\n"); + /* hack for reusing unmodified old code ! */ + resize.ctx = &ctx; + ctx.attr = a; + ctx.mrec = mrec; + resize.mref = inum; + resize.delayed_runlists = expand->delayed_runlists; + must_delay = 1; + replace_later(&resize,rl,head_rl); + expand->delayed_runlists = resize.delayed_runlists; + } else { + memmove(next_attr + l, next_attr, remains_size); + mrec->bytes_in_use = cpu_to_le32(l + + le32_to_cpu(mrec->bytes_in_use)); + a->length = cpu_to_le32(le32_to_cpu(a->length) + l); + } + } + + if (!must_delay) { + mp = ntfs_calloc(mp_size); + if (!mp) + perr_exit("ntfsc_calloc couldn't get memory"); + + if (ntfs_mapping_pairs_build(vol, (u8*)mp, mp_size, rl, 0, NULL)) + perr_exit("ntfs_mapping_pairs_build"); + + memmove((u8*)a + le16_to_cpu(a->mapping_pairs_offset), mp, mp_size); + + free(mp); + } + return (must_delay); +} + +/* + * Create a minimal valid MFT record + */ + +static int minimal_record(expand_t *expand, MFT_RECORD *mrec) +{ + int usa_count; + u32 bytes_in_use; + + memset(mrec,0,expand->vol->mft_record_size); + mrec->magic = magic_FILE; + mrec->usa_ofs = const_cpu_to_le16(sizeof(MFT_RECORD)); + usa_count = expand->vol->mft_record_size / NTFS_BLOCK_SIZE + 1; + mrec->usa_count = cpu_to_le16(usa_count); + bytes_in_use = (sizeof(MFT_RECORD) + 2*usa_count + 7) & -8; + memset(((char*)mrec) + bytes_in_use, 255, 4); /* AT_END */ + bytes_in_use += 8; + mrec->bytes_in_use = cpu_to_le32(bytes_in_use); + mrec->bytes_allocated = cpu_to_le32(expand->vol->mft_record_size); + return (0); +} + +/* + * Rebase all runlists of an MFT record + * + * Iterate through all its attributes and offset the non resident ones + */ + +static int rebase_runlists(expand_t *expand, s64 inum) +{ + MFT_RECORD *mrec; + ATTR_RECORD *a; + runlist_element *rl; + runlist_element *prl; + u32 offset; + int res; + + res = 0; + mrec = expand->mrec; + offset = le16_to_cpu(mrec->attrs_offset); + a = (ATTR_RECORD*)((char*)mrec + offset); + while (!res && (a->type != AT_END) + && (offset < le32_to_cpu(mrec->bytes_in_use))) { + if (a->non_resident) { + rl = ntfs_mapping_pairs_decompress(expand->vol, a, + (runlist_element*)NULL); + if (rl) { + for (prl=rl; prl->length; prl++) + if (prl->lcn >= 0) { + prl->lcn += expand->cluster_increment; + if (set_bitmap(expand,prl)) + res = -1; + } + if (update_runlist(expand,inum,a,rl)) { + ntfs_log_verbose("Runlist updating has to be delayed\n"); + } else + free(rl); + } else { + err_printf("Could not get a runlist of inode %lld\n", + (long long)inum); + res = -1; + } + } + offset += le32_to_cpu(a->length); + a = (ATTR_RECORD*)((char*)mrec + offset); + } + return (res); +} + +/* + * Rebase the runlists present in records with relocated $DATA + * + * The returned runlist is the old rebased runlist for $DATA, + * which is generally different from the new computed runlist. + */ + +static runlist_element *rebase_runlists_meta(expand_t *expand, s64 inum) +{ + MFT_RECORD *mrec; + ATTR_RECORD *a; + ntfs_volume *vol; + runlist_element *rl; + runlist_element *old_rl; + runlist_element *prl; + runlist_element new_rl[2]; + s64 data_size; + s64 allocated_size; + s64 lcn; + u64 lth; + u32 offset; + BOOL keeprl; + int res; + + res = 0; + old_rl = (runlist_element*)NULL; + vol = expand->vol; + mrec = expand->mrec; + switch (inum) { + case FILE_Boot : + lcn = 0; + lth = expand->boot_size >> vol->cluster_size_bits; + data_size = expand->boot_size; + break; + case FILE_Bitmap : + lcn = expand->boot_size >> vol->cluster_size_bits; + lth = expand->bitmap_allocated >> vol->cluster_size_bits; + data_size = expand->bitmap_size; + break; + case FILE_MFT : + lcn = (expand->boot_size + expand->bitmap_allocated) + >> vol->cluster_size_bits; + lth = expand->mft_size >> vol->cluster_size_bits; + data_size = expand->mft_size; + break; + case FILE_BadClus : + lcn = 0; /* not used */ + lth = vol->nr_clusters + expand->cluster_increment; + data_size = lth << vol->cluster_size_bits; + break; + default : + lcn = lth = data_size = 0; + res = -1; + } + allocated_size = lth << vol->cluster_size_bits; + offset = le16_to_cpu(mrec->attrs_offset); + a = (ATTR_RECORD*)((char*)mrec + offset); + while (!res && (a->type != AT_END) + && (offset < le32_to_cpu(mrec->bytes_in_use))) { + if (a->non_resident) { + keeprl = FALSE; + rl = ntfs_mapping_pairs_decompress(vol, a, + (runlist_element*)NULL); + if (rl) { + /* rebase the old runlist */ + for (prl=rl; prl->length; prl++) + if (prl->lcn >= 0) { + prl->lcn += expand->cluster_increment; + if ((a->type != AT_DATA) + && set_bitmap(expand,prl)) + res = -1; + } + /* relocated unnamed data (not $BadClus) */ + if ((a->type == AT_DATA) + && !a->name_length + && (inum != FILE_BadClus)) { + old_rl = rl; + rl = new_rl; + keeprl = TRUE; + rl[0].vcn = 0; + rl[0].lcn = lcn; + rl[0].length = lth; + rl[1].vcn = lth; + rl[1].lcn = LCN_ENOENT; + rl[1].length = 0; + if (set_bitmap(expand,rl)) + res = -1; + a->data_size = cpu_to_le64(data_size); + a->initialized_size = a->data_size; + a->allocated_size + = cpu_to_le64(allocated_size); + a->highest_vcn = cpu_to_le64(lth - 1); + } + /* expand the named data for $BadClus */ + if ((a->type == AT_DATA) + && a->name_length + && (inum == FILE_BadClus)) { + old_rl = rl; + keeprl = TRUE; + prl = rl; + if (prl->length) { + while (prl[1].length) + prl++; + prl->length = lth - prl->vcn; + prl[1].vcn = lth; + } else + prl->vcn = lth; + a->data_size = cpu_to_le64(data_size); + /* do not change the initialized size */ + a->allocated_size + = cpu_to_le64(allocated_size); + a->highest_vcn = cpu_to_le64(lth - 1); + } + if (!res && update_runlist(expand,inum,a,rl)) + res = -1; + if (!keeprl) + free(rl); + } else { + err_printf("Could not get the data runlist of inode %lld\n", + (long long)inum); + res = -1; + } + } + offset += le32_to_cpu(a->length); + a = (ATTR_RECORD*)((char*)mrec + offset); + } + if (res && old_rl) { + free(old_rl); + old_rl = (runlist_element*)NULL; + } + return (old_rl); +} + +/* + * Rebase all runlists in an MFT record + * + * Read from the old $MFT, rebase the runlists, + * and write to the new $MFT + */ + +static int rebase_inode(expand_t *expand, const runlist_element *prl, + s64 inum, s64 jnum) +{ + MFT_RECORD *mrec; + runlist_element *rl; + ntfs_volume *vol; + s64 pos; + int res; + + res = 0; + vol = expand->vol; + mrec = expand->mrec; + if (expand->mft_bitmap[inum >> 3] & (1 << (inum & 7))) { + pos = (prl->lcn << vol->cluster_size_bits) + + ((inum - jnum) << vol->mft_record_size_bits); + if ((ntfs_mst_pread(vol->dev, pos, 1, + vol->mft_record_size, mrec) == 1) + && (mrec->flags & MFT_RECORD_IN_USE)) { + switch (inum) { + case FILE_Bitmap : + case FILE_Boot : + case FILE_BadClus : + rl = rebase_runlists_meta(expand, inum); + if (rl) + free(rl); + else + res = -1; + break; + default : + res = rebase_runlists(expand, inum); + break; + } + } else { + err_printf("Could not read the $MFT entry %lld\n", + (long long)inum); + res = -1; + } + } else { + /* + * Replace unused records (possibly uninitialized) + * by minimal valid records, not marked in use + */ + res = minimal_record(expand,mrec); + } + if (!res) { + pos = (expand->mft_lcn << vol->cluster_size_bits) + + (inum << vol->mft_record_size_bits); + if (opt.verbose) + ntfs_log_verbose("Rebasing inode %lld cluster 0x%llx\n", + (long long)inum, + (long long)(pos >> vol->cluster_size_bits)); + if (!opt.ro_flag + && (ntfs_mst_pwrite(vol->dev, pos, 1, + vol->mft_record_size, mrec) != 1)) { + err_printf("Could not write the $MFT entry %lld\n", + (long long)inum); + res = -1; + } + } + return (res); +} + +/* + * Rebase all runlists + * + * First get the $MFT and define its location in the expanded space, + * then rebase the other inodes and write them to the new $MFT + */ + +static int rebase_all_inodes(expand_t *expand) +{ + ntfs_volume *vol; + MFT_RECORD *mrec; + s64 inum; + s64 jnum; + s64 inodecnt; + s64 pos; + s64 got; + int res; + runlist_element *mft_rl; + runlist_element *prl; + + res = 0; + mft_rl = (runlist_element*)NULL; + vol = expand->vol; + mrec = expand->mrec; + inum = 0; + pos = (vol->mft_lcn + expand->cluster_increment) + << vol->cluster_size_bits; + got = ntfs_mst_pread(vol->dev, pos, 1, + vol->mft_record_size, mrec); + if ((got == 1) && (mrec->flags & MFT_RECORD_IN_USE)) { + pos = expand->mft_lcn << vol->cluster_size_bits; + if (opt.verbose) + ntfs_log_verbose("Rebasing inode %lld cluster 0x%llx\n", + (long long)inum, + (long long)(pos >> vol->cluster_size_bits)); + mft_rl = rebase_runlists_meta(expand, FILE_MFT); + if (!mft_rl + || (!opt.ro_flag + && (ntfs_mst_pwrite(vol->dev, pos, 1, + vol->mft_record_size, mrec) != 1))) + res = -1; + else { + for (prl=mft_rl; prl->length; prl++) { } + inodecnt = (prl->vcn << vol->cluster_size_bits) + >> vol->mft_record_size_bits; + progress_init(expand->progress, 0, inodecnt, + (opt.show_progress ? NTFS_PROGBAR : 0)); + prl = mft_rl; + jnum = 0; + do { + inum++; + while (prl->length + && ((inum << vol->mft_record_size_bits) + >= ((prl->vcn + prl->length) + << vol->cluster_size_bits))) { + prl++; + jnum = inum; + } + progress_update(expand->progress, inum); + if (prl->length) { + res = rebase_inode(expand, + prl,inum,jnum); + } + } while (!res && prl->length); + free(mft_rl); + } + } else { + err_printf("Could not read the old $MFT\n"); + res = -1; + } + return (res); +} + + + +/* + * Get the old volume parameters from the backup bootsector + * + */ + +static ntfs_volume *get_volume_data(expand_t *expand, struct ntfs_device *dev, + s32 sector_size) +{ + s64 br; + ntfs_volume *vol; + le16 sector_size_le; + NTFS_BOOT_SECTOR *bs; + BOOL ok; + + ok = FALSE; + vol = (ntfs_volume*)ntfs_malloc(sizeof(ntfs_volume)); + expand->bootsector = (char*)ntfs_malloc(sector_size); + if (vol && expand->bootsector) { + expand->vol = vol; + vol->dev = dev; + br = ntfs_pread(dev, expand->new_sectors*sector_size, + sector_size, expand->bootsector); + if (br != sector_size) { + if (br != -1) + errno = EINVAL; + if (!br) + err_printf("Failed to read the backup bootsector (size=0)\n"); + else + err_printf("Error reading the backup bootsector"); + } else { + bs = (NTFS_BOOT_SECTOR*)expand->bootsector; + /* alignment problem on Sparc, even doing memcpy() */ + sector_size_le = cpu_to_le16(sector_size); + if (!memcmp(§or_size_le, + &bs->bpb.bytes_per_sector,2) + && ntfs_boot_sector_is_ntfs(bs) + && !ntfs_boot_sector_parse(vol, bs)) { + expand->original_sectors + = le64_to_cpu(bs->number_of_sectors); + expand->mrec = (MFT_RECORD*) + ntfs_malloc(vol->mft_record_size); + if (expand->mrec + && can_expand(expand,vol)) { + ntfs_log_verbose("Resizing is possible\n"); + ok = TRUE; + } + } else + err_printf("Could not get the old volume parameters " + "from the backup bootsector\n"); + } + if (!ok) { + free(vol); + free(expand->bootsector); + } + } + return (ok ? vol : (ntfs_volume*)NULL); +} + +static int really_expand(expand_t *expand) +{ + ntfs_volume *vol; + struct ntfs_device *dev; + int res; + + res = -1; + + expand->bitmap = (u8*)ntfs_calloc(expand->bitmap_allocated); + if (expand->bitmap + && get_mft_bitmap(expand)) { + printf("\n*** WARNING ***\n\n"); + printf("Expanding a volume is an experimental new feature\n"); + if (!opt.ro_flag) + printf("A first check with option -n is recommended\n"); + printf("\nShould something go wrong during the actual" + " resizing (power outage, etc.),\n"); + printf("just restart the procedure, but DO NOT TRY to repair" + " with chkdsk or similar,\n"); + printf("until the resizing is over," + " you would LOSE YOUR DATA !\n"); + printf("\nYou have been warned !\n\n"); + if (!opt.ro_flag && (opt.force-- <= 0)) + proceed_question(); + if (!rebase_all_inodes(expand) + && !write_bitmap(expand) + && !copy_mftmirr(expand) + && !copy_boot(expand)) { + free(expand->vol); + expand->vol = (ntfs_volume*)NULL; + free(expand->mft_bitmap); + expand->mft_bitmap = (u8*)NULL; + if (!opt.ro_flag) { + /* the volume must be dirty, do not check */ + opt.force++; + vol = mount_volume(); + if (vol) { + dev = vol->dev; + ntfs_log_verbose("Remounting the updated volume\n"); + expand->vol = vol; + ntfs_log_verbose("Delayed runlist updatings\n"); + delayed_expand(vol, expand->delayed_runlists, + expand->progress); + expand->delayed_runlists + = (struct DELAYED*)NULL; + expand_index_sizes(expand); + /* rewriting the backup bootsector, no return ticket now ! */ + res = write_bootsector(expand); + if (dev->d_ops->sync(dev) == -1) { + printf("Could not sync\n"); + res = -1; + } + ntfs_umount(vol,0); + if (!res) + printf("\nResizing completed successfully\n"); + } + } else { + ntfs_log_verbose("Delayed runlist updatings\n"); + delayed_expand(expand->vol, + expand->delayed_runlists, + expand->progress); + expand->delayed_runlists + = (struct DELAYED*)NULL; + printf("\nAll checks have been completed successfully\n"); + printf("Cannot check further in no-action mode\n"); + } + free(expand->bootsector); + free(expand->mrec); + } + free(expand->bitmap); + } else { + err_printf("Failed to allocate memory\n"); + } + return (res); +} + +/* + * Expand a volume to beginning of partition + * + * We rely on the backup bootsector to determine the original + * volume size and metadata. + */ + +static int expand_to_beginning(void) +{ + expand_t expand; + struct progress_bar progress; + int ret; + ntfs_volume *vol; + struct ntfs_device *dev; + int sector_size; + s64 new_sectors; + + ret = -1; + dev = ntfs_device_alloc(opt.volume, 0, &ntfs_device_default_io_ops, + NULL); + if (dev) { + if (!(*dev->d_ops->open)(dev, + (opt.ro_flag ? O_RDONLY : O_RDWR))) { + sector_size = ntfs_device_sector_size_get(dev); + if (sector_size <= 0) { + sector_size = 512; + new_sectors = ntfs_device_size_get(dev, + sector_size); + if (!new_sectors) { + sector_size = 4096; + new_sectors = ntfs_device_size_get(dev, + sector_size); + } + } else + new_sectors = ntfs_device_size_get(dev, + sector_size); + if (new_sectors) { + new_sectors--; /* last sector not counted */ + expand.new_sectors = new_sectors; + expand.progress = &progress; + expand.delayed_runlists = (struct DELAYED*)NULL; + vol = get_volume_data(&expand,dev,sector_size); + if (vol) { + expand.vol = vol; + ret = really_expand(&expand); + } + } + (*dev->d_ops->close)(dev); + } else { + err_exit("Couldn't open volume '%s'!\n", opt.volume); + } + ntfs_device_free(dev); + } + return (ret); +} + + +int main(int argc, char **argv) +{ + ntfsck_t fsck; + ntfs_resize_t resize; + s64 new_size = 0; /* in clusters; 0 = --info w/o --size */ + s64 device_size; /* in bytes */ + ntfs_volume *vol = NULL; + + ntfs_log_set_handler(ntfs_log_handler_outerr); + + printf("%s v%s (libntfs-3g)\n", EXEC_NAME, VERSION); + + if (!parse_options(argc, argv)) + return 1; + + utils_set_locale(); + + /* + * If we're just checking the device, we'll do it first, + * and exit out, no matter what we find. + */ + if (opt.check) { + vol = check_volume(); +#if CLEAN_EXIT + if (vol) + ntfs_umount(vol,0); +#endif + exit(0); + } else { + if (opt.expand) { + /* + * If we are to expand to beginning of partition, do + * not try to mount : when merging two partitions, + * the beginning of the partition would contain an + * old filesystem which is not the one to expand. + */ + if (expand_to_beginning() && !opt.info) + exit(1); + return (0); + } + } + + if (!(vol = mount_volume())) + err_exit("Couldn't open volume '%s'!\n", opt.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", + (long long)device_size); + + if (!opt.infombonly) + print_vol_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!\nCorrupt partition table or incorrect device " + "partitioning?\n"); + + if (!opt.bytes && !opt.info && !opt.infombonly) { + opt.bytes = device_size; + opt.reliable_size = 1; + } + + /* Backup boot sector at the end of device isn't counted in NTFS + volume size thus we have to reserve space for it. */ + if (opt.bytes > vol->sector_size) + new_size = (opt.bytes - vol->sector_size) / vol->cluster_size; + else + new_size = 0; + + if (!opt.info && !opt.infombonly) { + print_vol_size("New volume size ", vol_size(vol, new_size)); + if (device_size < opt.bytes) + err_exit("New size can't be bigger than the device size" + ".\nIf you want to enlarge NTFS then first " + "enlarge the device size by e.g. fdisk.\n"); + } + + if (!opt.info && !opt.infombonly && (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); + } + + memset(&resize, 0, sizeof(resize)); + resize.vol = vol; + resize.new_volume_size = new_size; + /* This is also true if --info was used w/o --size (new_size = 0) */ + if (new_size < vol->nr_clusters) + resize.shrink = 1; + if (opt.show_progress) + resize.progress.flags |= NTFS_PROGBAR; + /* + * Checking and __reporting__ of bad sectors must be done before cluster + * allocation check because chkdsk doesn't fix $Bitmap's w/ bad sectors + * thus users would (were) quite confused why chkdsk doesn't work. + */ + resize.badclusters = check_bad_sectors(vol); + + NVolSetNoFixupWarn(vol); + check_cluster_allocation(vol, &fsck); + + print_disk_usage(vol, fsck.inuse); + + resize.inuse = fsck.inuse; + resize.lcn_bitmap = fsck.lcn_bitmap; + + set_resize_constraints(&resize); + set_disk_usage_constraint(&resize); + check_resize_constraints(&resize); + + if (opt.info || opt.infombonly) { + advise_on_resize(&resize); + exit(0); + } + + if (opt.force-- <= 0 && !opt.ro_flag) { + printf("%s", resize_warning_msg); + proceed_question(); + } + + /* FIXME: performance - relocate logfile here if it's needed */ + prepare_volume_fixup(vol); + + if (resize.relocations) + relocate_inodes(&resize); + + truncate_badclust_file(&resize); + truncate_bitmap_file(&resize); + delayed_updates(&resize); + update_bootsector(&resize); + + /* We don't create backup boot sector because we don't know where the + partition will be split. The scheduled chkdsk will fix it */ + + if (opt.ro_flag) { + printf("The read-only test run ended successfully.\n"); + exit(0); + } + + /* WARNING: don't modify the texts, external tools grep for them */ + 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 (resize.shrink) + printf("%s", resize_important_msg); +#if CLEAN_EXIT + if (resize.lcn_bitmap.bm) + free(resize.lcn_bitmap.bm); + if (vol) + ntfs_umount(vol,0); +#endif + return 0; +} |