ntfsclone.c revision 7e7bd3dccbfe8f79e25e5c1554b5bc3a9aaca321
/**
* ntfsclone - Part of the Linux-NTFS project.
*
* Copyright (c) 2003-2006 Szabolcs Szakacsits
* Copyright (c) 2004-2006 Anton Altaparmakov
* Special image format support copyright (c) 2004 Per Olofsson
*
*
* 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.
*/
#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_SYS_TYPES_H
#endif
#ifdef HAVE_SYS_STAT_H
#endif
#ifdef HAVE_SYS_IOCTL_H
#endif
#ifdef HAVE_SYS_VFS_H
#endif
#ifdef HAVE_SYS_STATVFS_H
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.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_GETOPT_H
#include <getopt.h>
#endif
/*
* FIXME: ntfsclone do bad things about endians handling. Fix it and remove
* this note and define.
*/
#define NTFS_DO_NOT_CHECK_ENDIANS
#include "compat.h"
#include "debug.h"
#include "types.h"
#include "support.h"
#include "endians.h"
#include "bootsect.h"
#include "device.h"
#include "attrib.h"
#include "mst.h"
#include "volume.h"
#include "mft.h"
#include "bitmap.h"
#include "inode.h"
#include "index.h"
#include "dir.h"
#include "runlist.h"
#include "ntfstime.h"
#include "utils.h"
#include "version.h"
#endif
#endif
static const char *EXEC_NAME = "ntfsclone";
static const char *bad_sectors_warning_msg =
"*************************************************************************\n"
"* WARNING: The disk has bad sector. This means physical damage on the *\n"
"* disk surface caused by deterioration, manufacturing faults or other *\n"
"* reason. The reliability of the disk may stay stable or degrade fast. *\n"
"* Use the --rescue option to efficiently save as much data as possible! *\n"
"*************************************************************************\n";
static const char *dirty_volume_msg =
"Volume '%s' is scheduled for a check or it was shutdown \n"
"uncleanly. Please boot Windows or use the --force option to progress.\n";
static struct {
int verbose;
int quiet;
int debug;
int force;
int overwrite;
int std_out;
int blkdev_out; /* output file is block device */
int metadata; /* metadata only cloning */
int ignore_fs_check;
int rescue;
int save_image;
int restore_image;
char *output;
char *volume;
#ifdef __sun
#else
#endif
} opt;
struct bitmap {
};
struct progress_bar {
int resolution;
float unit;
};
typedef struct {
struct ntfs_walk_cluster {
};
static struct bitmap lcn_bitmap;
static int fd_in;
static int fd_out;
static int wipe = 0;
static unsigned int nr_used_mft_records = 0;
static unsigned int wiped_unused_mft_data = 0;
static unsigned int wiped_unused_mft = 0;
static unsigned int wiped_resident_data = 0;
static unsigned int wiped_timestamp_data = 0;
#define IMAGE_MAGIC "\0ntfsclone-image"
#define IMAGE_MAGIC_SIZE 16
/* This is the first endianness safe format version. */
#define NTFSCLONE_IMG_VER_MAJOR_ENDIANNESS_SAFE 10
#define NTFSCLONE_IMG_VER_MINOR_ENDIANNESS_SAFE 0
/*
* Set the version to 10.0 to avoid colisions with old ntfsclone which
* stupidly used the volume version as the image version... )-: I hope NTFS
* never reaches version 10.0 and if it does one day I hope no-one is using
* such an old ntfsclone by then...
*
* NOTE: Only bump the minor version if the image format and header are still
* backwards compatible. Otherwise always bump the major version. If in
* doubt, bump the major version.
*/
#define NTFSCLONE_IMG_VER_MAJOR 10
#define NTFSCLONE_IMG_VER_MINOR 0
/* All values are in little endian. */
#ifdef __sun
#pragma pack(1)
#endif
static struct image_hdr {
char magic[IMAGE_MAGIC_SIZE];
#ifdef __sun
#pragma pack()
#endif
#ifdef __sun
#define NTFSCLONE_IMG_HEADER_SIZE_OLD \
#else
#define NTFSCLONE_IMG_HEADER_SIZE_OLD \
#endif
#define ERR_PREFIX "ERROR"
#define LAST_METADATA_INODE 11
#define NTFS_MAX_CLUSTER_SIZE 65536
#define NTFS_SECTOR_SIZE 512
#define rounded_up_division(a, b) (((a) + (b - 1)) / (b))
{
}
static void perr_printf(const char *fmt, ...)
{
}
static void err_printf(const char *fmt, ...)
{
}
{
exit(1);
}
{
exit(1);
}
static void usage(void)
{
" Efficiently clone NTFS to a sparse file, image, device or standard output.\n"
"\n"
" -o, --output FILE Clone NTFS to the non-existent FILE\n"
" -O, --overwrite FILE Clone NTFS to FILE, overwriting if exists\n"
" -s, --save-image Save to the special image format\n"
" -r, --restore-image Restore from the special image format\n"
" --rescue Continue after disk read errors\n"
" -m, --metadata Clone *only* metadata (for NTFS experts)\n"
" --ignore-fs-check Ignore the filesystem check result\n"
" -f, --force Force to progress (DANGEROUS)\n"
" -h, --help Display this help\n"
#ifdef DEBUG
" -d, --debug Show debug information\n"
#endif
"\n"
" If FILE is '-' then send the image to the standard output. If SOURCE is '-'\n"
" and --restore-image is used then read the image from the standard input.\n"
"\n", EXEC_NAME);
exit(1);
}
{
static const char *sopt = "-dfhmo:O:rs";
#ifdef DEBUG
#endif
};
int c;
switch (c) {
case 1: /* A non-option argument */
usage();
break;
case 'd':
break;
case 'f':
break;
case 'h':
case '?':
usage();
case 'm':
break;
case 'O':
case 'o':
usage();
break;
case 'r':
opt.restore_image++;
break;
case 'C':
break;
case 'R':
break;
case 's':
opt.save_image++;
break;
default:
usage();
}
}
err_printf("You must specify an output file.\n");
usage();
}
err_printf("You must specify a device file.\n");
usage();
}
err_exit("Saving only metadata to an image is not "
"supported!\n");
err_exit("Restoring only metadata from an image is not "
"supported!\n");
err_exit("Cloning only metadata to stdout isn't supported!\n");
err_exit("Filesystem check can be ignored only for metadata "
"cloning!\n");
err_exit("Saving and restoring an image at the same time "
"is not supported!\n");
} else {
err_exit("Output file '%s' already exists.\n"
"Use option --overwrite if you want to"
err_exit("Cloning only metadata to a "
"block device isn't supported!\n");
}
}
}
/* FIXME: this is a workaround for losing debug info if stdout != stderr
and for the uncontrollable verbose messages in libntfs. Ughhh. */
/* Redirect stderr to stdout, note fflush()es are essential! */
perror("Failed to redirect stderr to stdout");
exit(1);
}
} else {
}
}
{
p->resolution = res;
}
{
return;
} else
Printf("100.00 percent completed\n");
}
{
if (inode <= LAST_METADATA_INODE) {
/* Don't save bad sectors (both $Bad and unnamed are ignored */
return 0;
if (inode != FILE_LogFile)
/* Save at least the first 16 KiB of FILE_LogFile */
if (s > 0) {
return s;
}
return 0;
}
}
return 0;
}
{
int i;
while (count > 0) {
if (do_write)
else if (opt.restore_image)
else
if (i < 0) {
return -1;
} else {
count -= i;
}
}
return 0;
}
{
const char *badsector_magic = "BadSectoR\0";
if (opt.restore_image) {
perr_exit("lseek");
} else {
perr_exit("seek input");
}
Printf("WARNING: Can't read sector at %llu, lost data.\n",
(unsigned long long)pos);
}
}
{
/* vol is NULL if opt.restore_image is set */
if (!opt.restore_image) {
}
perr_exit("read_all");
else if (rescue){
u32 i;
for (i = 0; i < csize; i += NTFS_SECTOR_SIZE)
} else {
err_exit("Disk is faulty, can't make full backup!");
}
}
if (opt.save_image) {
char cmd = 1;
perr_exit("write_all");
}
perr_printf("Write failed");
#ifndef __sun
Printf("Apparently you tried to clone to a remote "
"Windows computer but they don't\nhave "
"efficient sparse file handling by default. "
"Please try a different method.\n");
#endif /* !defined(__sun) */
exit(1);
}
}
{
perr_exit("lseek input");
return;
perr_exit("lseek output");
}
{
#ifdef __sun
#else
#endif
buff[0] = 0;
perr_exit("write_all");
}
}
{
return;
return;
/* FIXME: this could give pretty suboptimal performance */
for (i = 0; i < len; i++)
}
{
void *buf;
struct progress_bar progress;
if (opt.save_image)
Printf("Saving NTFS to image ...\n");
else
Printf("Cloning NTFS ...\n");
if (!buf)
perr_exit("clone_ntfs");
if (opt.save_image) {
perr_exit("write_all");
}
continue;
}
perr_exit("write_all");
}
}
}
{
s64 i;
char buff[NTFS_MAX_CLUSTER_SIZE];
for (i = 0; i < count; i++) {
perr_exit("write_all");
}
}
static void restore_image(void)
{
char cmd;
struct progress_bar progress;
Printf("Restoring NTFS from image ...\n");
100);
perr_exit("read_all");
if (cmd == 0) {
perr_exit("read_all");
if (!image_is_host_endian)
else {
(off_t)-1)
perr_exit("restore_image: lseek");
}
} else if (cmd == 1) {
copy_cluster(0, 0);
pos++;
} else
err_exit("Invalid command code in image\n");
}
}
static void wipe_index_entry_timestams(INDEX_ENTRY *e)
{
/* FIXME: can fall into infinite loop if corrupted */
while (!(e->flags & INDEX_ENTRY_END)) {
wiped_timestamp_data += 32;
}
}
{
int bit;
if (!indexr) {
perr_printf("Failed to read $INDEX_ROOT attribute of inode "
return;
}
goto out_indexr;
if (!byte) {
perr_printf("Failed to read $BITMAP attribute");
goto out_indexr;
}
if (!na) {
perr_printf("Failed to open $INDEX_ALLOCATION attribute");
goto out_bitmap;
}
goto out_na;
if (!tmp_indexa)
goto out_na;
perr_printf("Failed to read $INDEX_ALLOCATION attribute");
goto out_indexa;
}
bit = 0;
indexr->index_block_size))) {
perr_printf("Damaged INDX record");
goto out_indexa;
}
perr_exit("ntfs_mft_usn_dec");
indexr->index_block_size))) {
perr_printf("INDX write fixup failed");
goto out_indexa;
}
}
bit++;
if (bit > 7) {
bit = 0;
byte++;
}
}
}
{
wiped_timestamp_data += 32;
} else if (ntfs_names_are_equal(NTFS_INDEX_Q,
/*
* FIXME: no guarantee it's indeed /$Extend/$Quota:$Q.
* For now, as a minimal safeguard, we check only for
* quota version 2 ...
*/
wiped_timestamp_data += 4;
}
}
}
}
do { \
\
\
wiped_timestamp_data += 32; \
\
} while (0)
{
if (a->type == AT_FILE_NAME)
else if (a->type == AT_STANDARD_INFORMATION)
else if (a->type == AT_INDEX_ROOT)
}
{
ATTR_RECORD *a;
u32 i;
int n = 0;
u8 *p;
return;
return;
if (p[i]) {
p[i] = 0;
n++;
}
}
wiped_resident_data += n;
}
{
while (1) {
if (lcn < 0)
break;
if (offset == 0)
else
offset <<= 1;
}
}
{
int i, j;
ATTR_RECORD *a;
if (!a->non_resident) {
if (wipe) {
}
return;
}
perr_exit("ntfs_decompress_mapping_pairs");
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 "
if (!wipe)
for (j = 0; j < lcn_length; j++) {
err_exit("Cluster %llu referenced twice!\n"
"You didn't shutdown your Windows"
"properly?\n", (unsigned long long)k);
}
}
}
{
perr_exit("ntfs_get_attr_search_ctx");
while (!ntfs_attrs_walk(ctx)) {
break;
}
}
static void compare_bitmaps(struct bitmap *a)
{
int mismatch = 0;
Printf("Accounting clusters ...\n");
pos = 0;
while (1) {
if (count == -1)
perr_exit("Couldn't get $Bitmap $DATA");
if (count == 0) {
err_exit("$Bitmap size is smaller than expected"
break;
}
goto done;
continue;
char bit;
continue;
if (opt.ignore_fs_check) {
}
if (++mismatch > 10)
continue;
Printf("Cluster accounting failed at %lld "
"(0x%llx): %s cluster in $Bitmap\n",
}
}
}
done:
if (mismatch) {
if (opt.ignore_fs_check) {
Printf("WARNING: The NTFS inconsistency was overruled "
"by the --ignore-fs-check option.\n");
return;
}
err_exit("Filesystem check failed! Windows wasn't shutdown "
"properly or inconsistent\nfilesystem. Please run "
"chkdsk /f on Windows then reboot it TWICE.\n");
}
}
{
int wiped = 0;
if (p[len]) {
p[len] = 0;
wiped++;
}
}
return wiped;
}
{
int unused;
/* FIXME: broken MFTMirr update was fixed in libntfs, check if OK now */
return;
wiped_unused_mft_data += wipe_data((char *)m,
}
{
int unused;
/* FIXME: broken MFTMirr update was fixed in libntfs, check if OK now */
return;
}
{
perr_exit("ntfs_mft_usn_dec");
perr_exit("ntfs_mft_record_write");
}
{
s32 i;
if (ni->nr_extents <= 0)
return;
for (i = 0; i < ni->nr_extents; ++i) {
}
}
{
ntfs_inode *ni;
struct progress_bar progress;
Printf("Scanning volume ...\n");
int err, deleted_inode;
/* FIXME: Terrible kludge for libntfs not being able to return
a deleted MFT record as inode */
if (!ni)
perr_exit("walk_clusters");
if (err == -1) {
continue;
}
if (deleted_inode) {
if (wipe) {
}
}
if (deleted_inode)
continue;
/* FIXME: continue only if it make sense, e.g.
MFT record not in use based on $MFT bitmap */
continue;
}
if (wipe)
goto out;
out:
if (wipe) {
}
if (ntfs_inode_close(ni))
}
return 0;
}
/*
* $Bitmap can overlap the end of the volume. Any bits in this region
* must be set. This region also encompasses the backup boot sector.
*/
{
}
/*
* Allocate a block of memory with one bit for each cluster of the disk.
* All the bits are set to 0, except those representing the region beyond the
* end of the disk.
*/
static void setup_lcn_bitmap(void)
{
/* Determine lcn bitmap byte size and allocate it. */
if (!lcn_bitmap.bm)
perr_exit("Failed to allocate internal buffer");
}
{
}
{
}
{
Printf("\n");
}
static void print_image_info(void)
{
Printf("Ntfsclone image version: %d.%d\n",
Printf("Cluster size : %u bytes\n",
print_volume_size("Image volume size ",
Printf("Image device size : %lld bytes\n",
Printf("Offset to image data : %u (0x%x) bytes\n",
}
{
unsigned long mntflag;
if (mntflag & NTFS_MF_MOUNTED) {
if (!(mntflag & NTFS_MF_READONLY))
err_exit("Device '%s' is mounted read-write. "
"You must 'umount' it first.\n", device);
if (!new_mntflag)
err_exit("Device '%s' is mounted. "
"You must 'umount' it first.\n", device);
}
}
/**
* mount_volume -
*
* First perform some checks to determine if the volume is already mounted, or
* is dirty (Windows wasn't shutdown properly). If everything is OK, then mount
* the volume (load the metadata into memory).
*/
static void mount_volume(unsigned long new_mntflag)
{
Printf("Apparently device '%s' doesn't have a "
"valid NTFS. Maybe you selected\nthe whole "
}
exit(1);
}
if (NVolWasDirty(vol))
err_exit("Cluster size %u is too large!\n",
(unsigned int)vol->cluster_size);
if (ntfs_version_is_supported(vol))
perr_exit("Unknown NTFS version");
Printf("Cluster size : %u bytes\n",
(unsigned int)vol->cluster_size);
print_volume_size("Current volume size",
}
{
char ch;
return 0;
return -1;
}
{
#ifdef BLKGETSIZE64
ntfs_log_debug("BLKGETSIZE64 nr bytes = %llu "
"(0x%llx).\n", (unsigned long long)size,
(unsigned long long)size);
}
}
#endif
#ifdef BLKGETSIZE
{ unsigned long size;
ntfs_log_debug("BLKGETSIZE nr 512 byte blocks = %lu "
}
}
#endif
#ifdef FDGETPRM
{ struct floppy_struct this_floppy;
ntfs_log_debug("FDGETPRM nr 512 byte blocks = %lu "
}
}
#endif
/*
* We couldn't figure it out by using a specialized ioctl,
* so do binary search to find the size of the device.
*/
else
}
return (low + 1LL);
}
static void fsync_clone(int fd)
{
Printf("Syncing ...\n");
perr_exit("fsync");
}
{
#ifdef __sun
Printf("WARNING: Couldn't get filesystem type: "
Printf("WARNING: You're using PCFS, it does not support "
"sparse files so the next operation might take "
"a while. You should consider using the more "
"efficient --save-image option of ntfsclone. Use "
"the --restore-image option to restore the image.\n");
Printf("Destination filesystem type is %s\n",
Printf("Your system or the destination filesystem "
"doesn't support large files.\n");
exit(1);
}
#else /* !defined(__sun) */
long fs_type = 0; /* Unknown filesystem type */
Printf("WARNING: Couldn't get filesystem type: "
else
if (fs_type == 0x52654973)
Printf("WARNING: You're using ReiserFS, it has very poor "
"performance creating\nlarge sparse files. The next "
"operation might take a very long time!\n"
"Creating sparse output file ...\n");
else if (fs_type == 0x517b)
Printf("WARNING: You're using SMBFS and if the remote share "
"isn't Samba but a Windows\ncomputer then the clone "
"operation will be very inefficient and may fail!\n");
if (fs_type)
Printf("Destination filesystem type is 0x%lx.\n",
(unsigned long)fs_type);
Printf("Your system or the destination filesystem "
"doesn't support large files.\n");
if (fs_type == 0x517b) {
Printf("SMBFS needs minimum Linux kernel "
"version 2.4.25 and\n the 'lfs' option"
"\nfor smbmount to have large "
"file support.\n");
}
Printf("Apparently the destination filesystem doesn't "
"support sparse files.\nYou can overcome this "
"by using the more efficient --save-image "
"option\nof ntfsclone. Use the --restore-image "
"option to restore the image.\n");
}
exit(1);
}
#endif /* defined(__sun) */
}
static s64 open_image(void)
{
perr_exit("fileno for stdout failed");
} else {
perr_exit("failed to open image");
}
perr_exit("read_all");
err_exit("Input file is not an image! (invalid magic)\n");
#if (__BYTE_ORDER == __BIG_ENDIAN)
Printf("Old image format detected. If the image was created "
"on a little endian architecture it will not "
"work. Use a more recent version of "
"ntfsclone to recreate the image.\n");
#endif
} else {
#ifdef __sun
#else
#endif
int delta;
err_exit("Do not know how to handle image format "
"version %d.%d. Please obtain a "
"newer version of ntfsclone.\n",
/* Read the image header data offset. */
sizeof(offset_to_image_data)) == -1)
perr_exit("read_all");
/*
* Read any fields from the header that we have not read yet so
* that the input stream is positioned correctly. This means
* we can support future minor versions that just extend the
* header in a backwards compatible way.
*/
sizeof(image_hdr.offset_to_image_data));
if (delta > 0) {
char *dummy_buf;
if (!dummy_buf)
perr_exit("malloc dummy_buffer");
perr_exit("read_all");
}
}
}
static s64 open_volume(void)
{
if (device_size <= 0)
err_exit("Current NTFS volume size is bigger than the device "
"size (%lld)!\nCorrupt partition table or incorrect "
"device partitioning?\n", device_size);
return device_size;
}
{
~7);
}
{
if (opt.blkdev_out) {
if (dest_size < input_size)
err_exit("Output device is too small (%lld) to fit the "
} else
}
{
perr_printf("ntfs_attr_get_search_ctx");
return ret;
}
/**
* lookup_data_attr
*
* Find the $DATA attribute (with or without a name) for the given ntfs inode.
*/
{
int len = 0;
return NULL;
goto error_out;
}
perr_printf("ntfs_attr_lookup");
goto error_out;
}
return ctx;
return NULL;
}
{
ntfs_inode *ni;
s64 nr_bad_clusters = 0;
perr_exit("ntfs_open_inode");
exit(1);
perr_exit("ntfs_mapping_pairs_decompress");
continue;
}
}
if (nr_bad_clusters)
Printf("WARNING: The disk has %lld or more bad sectors"
" (hardware faults).\n", nr_bad_clusters);
if (ntfs_inode_close(ni))
perr_exit("ntfs_inode_close failed for $BadClus");
}
{
return;
/*
* TODO: save_image needs a bit more space than src_bytes
* due to the free space encoding overhead.
*/
Printf("WARNING: Unknown free space on the destination: %s\n",
return;
}
/* If file is a FIFO then there is no point in checking the size. */
return;
} else
if (!dest_bytes)
if (dest_bytes < src_bytes)
err_exit("Destination doesn't have enough free space: "
"%llu MB < %llu MB\n",
}
{
unsigned int wiped_total = 0;
/* print to stderr, stdout can be an NTFS image ... */
if (opt.restore_image) {
device_size = open_image();
} else {
device_size = open_volume();
}
// FIXME: This needs to be the cluster size...
perr_exit("fileno for stdout failed");
} else {
/* device_size_get() might need to read() */
if (!opt.blkdev_out) {
}
if (!opt.save_image)
}
if (opt.restore_image) {
exit(0);
}
if (opt.save_image)
/* FIXME: save backup boot sector */
exit(0);
}
wipe = 1;
/* 'force' again mount for dirty volumes (e.g. after resize).
FIXME: use mount flags to avoid potential side-effects in future */
mount_volume(0);
Printf("Num of MFT records = %10lld\n",
exit(0);
}