ntfsclone patch from Szaka:

This fixes all known issues I knew about or wanted to add to ntfsclone.
So it's declared ready and basically works for me for half a year. Future
work is only based on requests/needs. I only want to write the manual, if
ever have time.

Changes:
 - support block device as output file
 - handle partial read/write
 - show progress bar during cloning
 - significant cloning performance improvement
 - detect ReiserFS & warn about its extremely poor ftruncate performance
 - no error if fsync() gives EINVAL (pipe)
 - several sanity checks added
 - some cleanup, spelling corrections

(Logical change 1.151)
edge.strict_endians
cantab.net!aia21 2003-07-11 12:35:05 +00:00
parent a7f036134b
commit 05befe33eb
1 changed files with 193 additions and 79 deletions

View File

@ -3,7 +3,7 @@
*
* Copyright (c) 2003 Szabolcs Szakacsits
*
* ntfsclone clones NTFS data and/or metadata to a sparse file or stdout.
* Clone NTFS data and/or metadata to a sparse file, device or stdout.
*
* 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
@ -18,6 +18,7 @@
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/vfs.h>
#include <fcntl.h>
#include <stdarg.h>
#include <string.h>
@ -47,6 +48,7 @@ struct {
int force;
int overwrite;
int stdout;
int blkdev_out; /* output file is block device */
int metadata_only;
char *output;
char *volume;
@ -104,6 +106,9 @@ int wiped_timestamp_data = 0;
#define rounded_up_division(a, b) (((a) + (b - 1)) / (b))
#define read_all(f, p, n) io_all((f), (p), (n), 0)
#define write_all(f, p, n) io_all((f), (p), (n), 1)
GEN_PRINTF(Eprintf, stderr, NULL, FALSE)
GEN_PRINTF(Vprintf, msg_out, &opt.verbose, TRUE)
GEN_PRINTF(Qprintf, msg_out, &opt.quiet, FALSE)
@ -165,15 +170,15 @@ int perr_exit(const char *fmt, ...)
void usage()
{
Eprintf("\nUsage: %s [options] device\n"
" Clone NTFS data to a sparse file or send it to stdout.\n"
" Efficiently clone NTFS to a sparse file, device or stdandard output.\n"
"\n"
" -o FILE --output FILE Clone NTFS to the non-existent FILE\n"
" -O FILE Clone NTFS to FILE, overwriting if exists\n"
" -m --metadata Clone *only* metadata (for NTFS experts)\n"
" -f --force Force to progress (DANGEROUS)\n"
" -h --help Display this help\n"
" -o FILE --output FILE Clone NTFS to the non-existent FILE\n"
" -O FILE --overwrite FILE Clone NTFS to FILE, overwriting if exists\n"
" -m --metadata Clone *only* metadata (for NTFS experts)\n"
" -f --force Force to progress (DANGEROUS)\n"
" -h --help Display this help\n"
#ifdef DEBUG
" -d --debug Show debug information\n"
" -d --debug Show debug information\n"
#endif
"\n"
" If FILE is '-' then send NTFS data to stdout replacing non used\n"
@ -195,6 +200,7 @@ void parse_options(int argc, char **argv)
{ "help", no_argument, NULL, 'h' },
{ "metadata", no_argument, NULL, 'm' },
{ "output", required_argument, NULL, 'o' },
{ "overwrite", required_argument, NULL, 'O' },
{ NULL, 0, NULL, 0 }
};
@ -248,8 +254,29 @@ void parse_options(int argc, char **argv)
}
if (opt.metadata_only && opt.stdout)
err_exit("Cloning only metadata to stdout isn't supported yet!\n");
err_exit("Cloning only metadata to stdout isn't supported!\n");
if (!opt.stdout) {
struct stat st;
if (stat(opt.output, &st) == -1) {
if (errno != ENOENT)
perr_exit("Couldn't access '%s'", opt.output);
} else {
if (!opt.overwrite)
err_exit("Output file '%s' already exists.\n"
"Use option --overwrite if you want to"
" replace its content.\n", opt.output);
if (S_ISBLK(st.st_mode)) {
opt.blkdev_out = 1;
if (opt.metadata_only)
err_exit("Cloning only metadata to a "
"block device isn't supported!\n");
}
}
}
msg_out = stdout;
/* FIXME: this is a workaround for loosing debug info if stdout != stderr
@ -263,6 +290,27 @@ void parse_options(int argc, char **argv)
perr_exit("Couldn't open /dev/null");
}
void progress_init(struct progress_bar *p, u64 start, u64 stop, int res)
{
p->start = start;
p->stop = stop;
p->unit = 100.0 / (stop - start);
p->resolution = res;
}
void progress_update(struct progress_bar *p, u64 current)
{
float 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(msg_out);
}
/**
* nr_clusters_to_bitmap_byte_size
@ -283,7 +331,7 @@ s64 nr_clusters_to_bitmap_byte_size(s64 nr_clusters)
return bm_bsize;
}
int is_critical_meatadata(ntfs_walk_clusters_ctx *image)
int is_critical_metadata(ntfs_walk_clusters_ctx *image)
{
s64 inode;
@ -299,31 +347,38 @@ int is_critical_meatadata(ntfs_walk_clusters_ctx *image)
return 0;
}
void write_cluster(const char *buff)
int io_all(void *fd, void *buf, int count, int do_write)
{
int count;
int i;
struct ntfs_device *dev = (struct ntfs_device *)fd;
if ((count = write(fd_out, buff, vol->cluster_size)) == -1)
perr_exit("write");
if (count != vol->cluster_size)
err_exit("Partial write not yet handled\n");
while (count > 0) {
if (do_write)
i = write(*(int *)fd, buf, count);
else
i = dev->d_ops->read(dev, buf, count);
if (i < 0) {
if (errno != EAGAIN && errno != EINTR)
return -1;
} else {
count -= i;
buf = i + (char *) buf;
}
}
return 0;
}
void copy_cluster()
{
int count;
char buff[NTFS_MAX_CLUSTER_SIZE]; /* overflow checked at mount time */
/* FIXME: handle partial read/writes */
if ((count = vol->dev->d_ops->read(vol->dev, buff,
vol->cluster_size)) == -1)
perr_exit("read");
if (read_all(vol->dev, buff, vol->cluster_size) == -1)
perr_exit("read_all");
if (count != vol->cluster_size)
err_exit("Partial read not yet handled\n");
write_cluster(buff);
if (write_all(&fd_out, buff, vol->cluster_size) == -1)
perr_exit("write_all");
}
void lseek_to_cluster(s64 lcn)
@ -349,7 +404,7 @@ void dump_clusters(ntfs_walk_clusters_ctx *image, runlist *rl)
if (opt.stdout)
return;
if (opt.metadata_only && !is_critical_meatadata(image))
if (!opt.metadata_only || !is_critical_metadata(image))
return;
lseek_to_cluster(rl->lcn);
@ -359,17 +414,22 @@ void dump_clusters(ntfs_walk_clusters_ctx *image, runlist *rl)
copy_cluster();
}
void dump_to_stdout()
void clone_ntfs(u64 nr_clusters)
{
s64 i, pos, count;
u8 bm[NTFS_BUF_SIZE];
void *buff;
void *buf;
u32 csize = vol->cluster_size;
u64 p_counter = 0;
struct progress_bar progress;
Printf("Dumping NTFS to stdout ...\n");
Printf("Cloning NTFS ...\n");
if ((buff = calloc(1, vol->cluster_size)) == NULL)
if ((buf = calloc(1, csize)) == NULL)
perr_exit("dump_to_stdout");
progress_init(&progress, p_counter, nr_clusters, 100);
pos = 0;
while (1) {
count = ntfs_attr_pread(vol->lcnbmp_na, pos, NTFS_BUF_SIZE, bm);
@ -388,10 +448,17 @@ void dump_to_stdout()
return;
if (ntfs_bit_get(bm, i * 8 + cl % 8)) {
progress_update(&progress, ++p_counter);
lseek_to_cluster(cl);
copy_cluster();
} else
write_cluster(buff);
continue;
}
if (opt.stdout) {
progress_update(&progress, ++p_counter);
if (write_all(&fd_out, buf, csize) == -1)
perr_exit("write_all");
}
}
}
}
@ -577,28 +644,6 @@ void compare_bitmaps(struct bitmap *a)
}
void progress_init(struct progress_bar *p, u64 start, u64 stop, int res)
{
p->start = start;
p->stop = stop;
p->unit = 100.0 / (stop - start);
p->resolution = res;
}
void progress_update(struct progress_bar *p, u64 current)
{
float 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(msg_out);
}
int wipe_data(char *p, int pos, int len)
{
int wiped = 0;
@ -619,7 +664,7 @@ void wipe_unused_mft_data(ntfs_inode *ni)
int unused;
MFT_RECORD *m = ni->mrec;
/* FIXME: MFTMirr update is broken in libntfs */
/* FIXME: broken MFTMirr update was fixed in libntfs, check if OK now */
if (ni->mft_no <= LAST_METADATA_INODE)
return;
@ -632,7 +677,7 @@ void wipe_unused_mft(ntfs_inode *ni)
int unused;
MFT_RECORD *m = ni->mrec;
/* FIXME: MFTMirr update is broken in libntfs */
/* FIXME: broken MFTMirr update was fixed in libntfs, check if OK now */
if (ni->mft_no <= LAST_METADATA_INODE)
return;
@ -661,7 +706,7 @@ int walk_clusters(ntfs_volume *vol, struct ntfs_walk_cluster *walk)
progress_update(&progress, inode);
/* FIXME: Terribe kludge for libntfs not being able to return
/* FIXME: Terrible kludge for libntfs not being able to return
a deleted MFT record as inode */
ni = (ntfs_inode*)calloc(1, sizeof(ntfs_inode));
if (!ni)
@ -769,11 +814,10 @@ void print_volume_size(char *str, s64 bytes)
void print_disk_usage(ntfs_walk_clusters_ctx *image)
{
s64 total, used, free;
s64 total, used;
total = vol->nr_clusters * vol->cluster_size;
used = image->inuse * vol->cluster_size;
free = total - used;
Printf("Space in use : %lld MB (%.1f%%) ",
rounded_up_division(used, NTFS_MBYTE),
@ -836,12 +880,62 @@ void mount_volume(unsigned long new_mntflag)
struct ntfs_walk_cluster backup_clusters = { NULL, NULL };
int device_offset_valid(int fd, s64 ofs)
{
char ch;
if (lseek(fd, ofs, SEEK_SET) >= 0 && read(fd, &ch, 1) == 1)
return 0;
return -1;
}
s64 device_size_get(int fd)
{
s64 high, low;
#ifdef BLKGETSIZE
long size;
if (ioctl(fd, BLKGETSIZE, &size) >= 0) {
Dprintf("BLKGETSIZE nr 512 byte blocks = %ld (0x%ld)\n", size,
size);
return (s64)size * 512;
}
#endif
#ifdef FDGETPRM
{ struct floppy_struct this_floppy;
if (ioctl(fd, FDGETPRM, &this_floppy) >= 0) {
Dprintf("FDGETPRM nr 512 byte blocks = %ld (0x%ld)\n",
this_floppy.size, this_floppy.size);
return (s64)this_floppy.size * 512;
}
}
#endif
/*
* We couldn't figure it out by using a specialized ioctl,
* so do binary search to find the size of the device.
*/
low = 0LL;
for (high = 1024LL; !device_offset_valid(fd, high); high <<= 1)
low = high;
while (low < high - 1LL) {
const s64 mid = (low + high) / 2;
if (!device_offset_valid(fd, mid))
low = mid;
else
high = mid;
}
lseek(fd, 0LL, SEEK_SET);
return (low + 1LL);
}
int main(int argc, char **argv)
{
ntfs_walk_clusters_ctx image;
s64 device_size; /* in bytes */
int flags, wiped_total = 0;
int wiped_total = 0;
/* print to stderr, stdout can be an NTFS image ... */
Eprintf("%s v%s\n", EXEC_NAME, VERSION);
@ -853,8 +947,7 @@ int main(int argc, char **argv)
mount_volume(MS_RDONLY);
device_size = ntfs_device_size_get(vol->dev, vol->sector_size);
device_size *= vol->sector_size;
device_size = ntfs_device_size_get(vol->dev, 1);
if (device_size <= 0)
err_exit("Couldn't get device size (%Ld)!\n", device_size);
@ -869,15 +962,39 @@ int main(int argc, char **argv)
if ((fd_out = fileno(stdout)) == -1)
perr_exit("fileno for stdout failed");
} else {
flags = O_CREAT | O_TRUNC | O_WRONLY;
if (!opt.overwrite)
flags |= O_EXCL;
int flags = O_WRONLY;
if (!opt.blkdev_out) {
flags |= O_CREAT | O_TRUNC;
if (!opt.overwrite)
flags |= O_EXCL;
}
if ((fd_out = open(opt.output, flags, S_IRWXU)) == -1)
perr_exit("opening file '%s' failed", opt.output);
if (ftruncate(fd_out, device_size) == -1)
perr_exit("ftruncate failed for file '%s'", opt.output);
if (!opt.blkdev_out) {
struct statfs stfs;
if (fstatfs(fd_out, &stfs) == -1)
Printf("Couldn't get Linux filesystem type: "
"%s\n", strerror(errno));
else if (stfs.f_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");
}
if (ftruncate(fd_out, device_size) == -1)
perr_exit("ftruncate failed for file '%s'",
opt.output);
} else {
s64 dest_size = device_size_get(fd_out);
if (dest_size < device_size)
err_exit("Output device size (%Ld) is too small"
" to fit an NTFS clone\n", dest_size);
}
}
setup_lcn_bitmap();
@ -892,19 +1009,16 @@ int main(int argc, char **argv)
/* FIXME: save backup boot sector */
if (opt.stdout) {
dump_to_stdout();
fsync(fd_out);
if (opt.stdout || !opt.metadata_only) {
u64 nr_clusters = opt.stdout ? vol->nr_clusters : image.inuse;
clone_ntfs(nr_clusters);
Printf("Syncing ...\n");
if (fsync(fd_out) && errno != EINVAL)
perr_exit("fsync");
exit(0);
}
Printf("Syncing image file ...\n");
if (fsync(fd_out) == -1)
perr_exit("fsync");
if (!opt.metadata_only)
exit(0);
wipe = 1;
opt.volume = opt.output;
mount_volume(0);