summaryrefslogtreecommitdiff
Diffstat
-rwxr-xr-xAndroid.mk138
-rwxr-xr-xAndroid.mk.bak89
-rwxr-xr-xCREDITS20
-rwxr-xr-xINSTALL200
-rwxr-xr-xMakefile715
-rwxr-xr-xMakefile.am106
-rwxr-xr-xMakefile.in955
-rwxr-xr-xNEWS4
-rwxr-xr-xREADME80
-rwxr-xr-xTODO.ntfsprogs126
-rwxr-xr-xaclocal.m47091
-rwxr-xr-xcompile21
-rwxr-xr-xconfig.guess292
-rw-r--r--[-rwxr-xr-x]config.h165
-rwxr-xr-xconfig.h.in110
-rwxr-xr-xconfig.log3290
-rwxr-xr-xconfig.status1220
-rwxr-xr-xconfig.sub104
-rwxr-xr-xconfigure26448
-rwxr-xr-xconfigure.ac291
-rwxr-xr-xdepcomp87
-rwxr-xr-xinclude/Makefile.in181
-rwxr-xr-xinclude/fuse-lite/Makefile.in112
-rwxr-xr-xinclude/fuse-lite/fuse.h61
-rwxr-xr-xinclude/fuse-lite/fuse_common.h49
-rwxr-xr-xinclude/fuse-lite/fuse_kernel.h6
-rwxr-xr-xinclude/fuse-lite/fuse_lowlevel.h20
-rwxr-xr-xinclude/ntfs-3g/Makefile.am4
-rwxr-xr-xinclude/ntfs-3g/Makefile.in163
-rwxr-xr-xinclude/ntfs-3g/acls.h3
-rwxr-xr-xinclude/ntfs-3g/attrib.h50
-rwxr-xr-xinclude/ntfs-3g/cache.h7
-rwxr-xr-xinclude/ntfs-3g/compat.h6
-rwxr-xr-xinclude/ntfs-3g/compress.h6
-rwxr-xr-xinclude/ntfs-3g/debug.h4
-rwxr-xr-xinclude/ntfs-3g/device.h18
-rwxr-xr-xinclude/ntfs-3g/device_io.h21
-rwxr-xr-xinclude/ntfs-3g/dir.h11
-rwxr-xr-xinclude/ntfs-3g/layout.h32
-rwxr-xr-xinclude/ntfs-3g/lcnalloc.h1
-rwxr-xr-xinclude/ntfs-3g/logging.h3
-rwxr-xr-xinclude/ntfs-3g/mst.h3
-rwxr-xr-xinclude/ntfs-3g/ntfstime.h12
-rwxr-xr-xinclude/ntfs-3g/param.h72
-rwxr-xr-xinclude/ntfs-3g/realpath.h24
-rwxr-xr-xinclude/ntfs-3g/runlist.h3
-rwxr-xr-xinclude/ntfs-3g/security.h20
-rwxr-xr-xinclude/ntfs-3g/types.h8
-rwxr-xr-xinclude/ntfs-3g/unistr.h13
-rwxr-xr-xinclude/ntfs-3g/volume.h78
-rwxr-xr-xinclude/ntfs-3g/xattrs.h75
-rwxr-xr-xinstall-sh5
-rwxr-xr-xlibfuse-lite/Makefile.am3
-rwxr-xr-xlibfuse-lite/Makefile.in188
-rwxr-xr-xlibfuse-lite/fuse.c418
-rwxr-xr-xlibfuse-lite/fuse_kern_chan.c2
-rwxr-xr-xlibfuse-lite/fuse_lowlevel.c53
-rwxr-xr-xlibfuse-lite/fuse_opt.c20
-rwxr-xr-xlibfuse-lite/fuse_session.c12
-rwxr-xr-xlibfuse-lite/fusermount.c97
-rwxr-xr-xlibfuse-lite/helper.c21
-rwxr-xr-xlibfuse-lite/mount.c486
-rwxr-xr-xlibfuse-lite/mount_util.c246
-rwxr-xr-xlibfuse-lite/mount_util.h11
-rwxr-xr-xlibntfs-3g/Makefile.am15
-rwxr-xr-xlibntfs-3g/Makefile.in420
-rwxr-xr-xlibntfs-3g/acls.c219
-rwxr-xr-xlibntfs-3g/attrib.c1205
-rwxr-xr-xlibntfs-3g/bootsect.c4
-rwxr-xr-xlibntfs-3g/cache.c7
-rwxr-xr-xlibntfs-3g/compress.c962
-rwxr-xr-xlibntfs-3g/device.c252
-rwxr-xr-xlibntfs-3g/dir.c374
-rwxr-xr-xlibntfs-3g/efs.c217
-rwxr-xr-xlibntfs-3g/index.c30
-rwxr-xr-xlibntfs-3g/inode.c46
-rwxr-xr-xlibntfs-3g/lcnalloc.c36
-rw-r--r--libntfs-3g/libntfs-3g.pc10
-rw-r--r--libntfs-3g/libntfs-3g.script.so2
-rwxr-xr-xlibntfs-3g/logfile.c23
-rwxr-xr-xlibntfs-3g/logging.c24
-rwxr-xr-xlibntfs-3g/mft.c12
-rwxr-xr-xlibntfs-3g/mst.c24
-rwxr-xr-xlibntfs-3g/object_id.c9
-rwxr-xr-xlibntfs-3g/realpath.c103
-rwxr-xr-xlibntfs-3g/reparse.c67
-rwxr-xr-xlibntfs-3g/runlist.c62
-rwxr-xr-xlibntfs-3g/security.c345
-rwxr-xr-xlibntfs-3g/unistr.c244
-rwxr-xr-xlibntfs-3g/unix_io.c19
-rwxr-xr-xlibntfs-3g/volume.c365
-rwxr-xr-xlibntfs-3g/win32_io.c708
-rwxr-xr-xlibntfs-3g/xattrs.c791
-rwxr-xr-xlibtool9301
-rwxr-xr-xltmain.sh8439
-rwxr-xr-xm4/libtool.m47377
-rwxr-xr-xm4/ltoptions.m4368
-rwxr-xr-xm4/ltsugar.m4123
-rwxr-xr-xm4/ltversion.m423
-rwxr-xr-xm4/lt~obsolete.m492
-rwxr-xr-xmissing49
-rwxr-xr-xntfsprogs/Makefile.am154
-rwxr-xr-xntfsprogs/Makefile.in1199
-rwxr-xr-xntfsprogs/attrdef.c168
-rwxr-xr-xntfsprogs/attrdef.h7
-rwxr-xr-xntfsprogs/boot.c268
-rwxr-xr-xntfsprogs/boot.h7
-rwxr-xr-xntfsprogs/cluster.c118
-rwxr-xr-xntfsprogs/cluster.h39
-rwxr-xr-xntfsprogs/list.h194
-rw-r--r--ntfsprogs/mkntfs.8290
-rwxr-xr-xntfsprogs/mkntfs.8.in290
-rwxr-xr-xntfsprogs/mkntfs.c5177
-rw-r--r--ntfsprogs/ntfscat.8136
-rwxr-xr-xntfsprogs/ntfscat.8.in136
-rwxr-xr-xntfsprogs/ntfscat.c440
-rwxr-xr-xntfsprogs/ntfscat.h46
-rwxr-xr-xntfsprogs/ntfsck.c883
-rw-r--r--ntfsprogs/ntfsclone.8391
-rwxr-xr-xntfsprogs/ntfsclone.8.in391
-rwxr-xr-xntfsprogs/ntfsclone.c2701
-rw-r--r--ntfsprogs/ntfscluster.8124
-rwxr-xr-xntfsprogs/ntfscluster.8.in124
-rwxr-xr-xntfsprogs/ntfscluster.c563
-rwxr-xr-xntfsprogs/ntfscluster.h63
-rw-r--r--ntfsprogs/ntfscmp.877
-rwxr-xr-xntfsprogs/ntfscmp.8.in77
-rwxr-xr-xntfsprogs/ntfscmp.c1012
-rw-r--r--ntfsprogs/ntfscp.8111
-rwxr-xr-xntfsprogs/ntfscp.8.in111
-rwxr-xr-xntfsprogs/ntfscp.c590
-rwxr-xr-xntfsprogs/ntfsdecrypt.c1436
-rwxr-xr-xntfsprogs/ntfsdump_logfile.c779
-rw-r--r--ntfsprogs/ntfsfix.881
-rwxr-xr-xntfsprogs/ntfsfix.8.in81
-rwxr-xr-xntfsprogs/ntfsfix.c1657
-rw-r--r--ntfsprogs/ntfsinfo.889
-rwxr-xr-xntfsprogs/ntfsinfo.8.in89
-rwxr-xr-xntfsprogs/ntfsinfo.c2384
-rw-r--r--ntfsprogs/ntfslabel.8118
-rwxr-xr-xntfsprogs/ntfslabel.8.in118
-rwxr-xr-xntfsprogs/ntfslabel.c458
-rw-r--r--ntfsprogs/ntfsls.8172
-rwxr-xr-xntfsprogs/ntfsls.8.in172
-rwxr-xr-xntfsprogs/ntfsls.c717
-rwxr-xr-xntfsprogs/ntfsmftalloc.c368
-rwxr-xr-xntfsprogs/ntfsmove.c923
-rwxr-xr-xntfsprogs/ntfsmove.h46
-rw-r--r--ntfsprogs/ntfsprogs.869
-rwxr-xr-xntfsprogs/ntfsprogs.8.in69
-rw-r--r--ntfsprogs/ntfsresize.8326
-rwxr-xr-xntfsprogs/ntfsresize.8.in326
-rwxr-xr-xntfsprogs/ntfsresize.c4497
-rwxr-xr-xntfsprogs/ntfstruncate.c809
-rw-r--r--ntfsprogs/ntfsundelete.8324
-rwxr-xr-xntfsprogs/ntfsundelete.8.in324
-rwxr-xr-xntfsprogs/ntfsundelete.c2490
-rwxr-xr-xntfsprogs/ntfsundelete.h112
-rwxr-xr-xntfsprogs/ntfswipe.c2131
-rwxr-xr-xntfsprogs/ntfswipe.h54
-rwxr-xr-xntfsprogs/sd.c607
-rwxr-xr-xntfsprogs/sd.h11
-rwxr-xr-xntfsprogs/utils.c1184
-rwxr-xr-xntfsprogs/utils.h137
-rwxr-xr-xprog.IAB1071
-rwxr-xr-xprog.IAD5
-rwxr-xr-xprog.IMB466
-rwxr-xr-xprog.IMD2
-rwxr-xr-xprog.PFI2
-rwxr-xr-xprog.PO1
-rwxr-xr-xprog.PR14
-rwxr-xr-xprog.PRI219
-rwxr-xr-xprog.PS979
-rwxr-xr-xprog.SearchResults3
-rwxr-xr-xprog.WK39
-rwxr-xr-xsrc/Makefile.am86
-rwxr-xr-xsrc/Makefile.in938
-rwxr-xr-xsrc/lowntfs-3g.c1538
-rw-r--r--src/ntfs-3g.8448
-rwxr-xr-xsrc/ntfs-3g.8.in217
-rwxr-xr-xsrc/ntfs-3g.c1357
-rw-r--r--src/ntfs-3g.probe.881
-rwxr-xr-xsrc/ntfs-3g.probe.8.in4
-rwxr-xr-xsrc/ntfs-3g.probe.c9
-rw-r--r--src/ntfs-3g.secaudit.8184
-rwxr-xr-xsrc/ntfs-3g.secaudit.8.in15
-rw-r--r--src/ntfs-3g.usermap.896
-rwxr-xr-xsrc/ntfs-3g_common.c745
-rwxr-xr-xsrc/ntfs-3g_common.h185
-rwxr-xr-xsrc/secaudit.c673
-rwxr-xr-xsrc/secaudit.h32
-rwxr-xr-xsrc/usermap.c3
-rw-r--r--[-rwxr-xr-x]stamp-h10
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(&sector_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;
+}