Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ntfsclone: support writing a ddrescue domain mapfile #40

Open
wants to merge 1 commit into
base: edge
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 111 additions & 12 deletions ntfsprogs/ntfsclone.c
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ static struct {
int preserve_timestamps;
int full_logfile;
int restore_image;
int domain_mapfile;
char *output;
char *volume;
#ifndef NO_STATFS
Expand Down Expand Up @@ -197,6 +198,11 @@ struct ntfs_walk_cluster {
ntfs_walk_clusters_ctx *image;
};

typedef struct {
off_t begin; //inclusive
off_t end; //exclusive
} domain_extent;
static domain_extent *domain_first = NULL, *domain_last = NULL, *domain_capacity = NULL;

static ntfs_volume *vol = NULL;
static struct bitmap lcn_bitmap;
Expand Down Expand Up @@ -360,6 +366,7 @@ static void usage(int ret)
" -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"
" -D, --domain-mapfile Write a ddrescue domain mapfile\n"
" --rescue Continue after disk read errors\n"
" -m, --metadata Clone *only* metadata (for NTFS experts)\n"
" -n, --no-action Test restoring, without outputting anything\n"
Expand Down Expand Up @@ -400,7 +407,7 @@ static void version(void)

static void parse_options(int argc, char **argv)
{
static const char *sopt = "-dfhmno:O:qrstV";
static const char *sopt = "-dfhmno:O:qrstDV";
static const struct option lopt[] = {
#ifdef DEBUG
{ "debug", no_argument, NULL, 'd' },
Expand All @@ -413,6 +420,7 @@ static void parse_options(int argc, char **argv)
{ "output", required_argument, NULL, 'o' },
{ "overwrite", required_argument, NULL, 'O' },
{ "restore-image", no_argument, NULL, 'r' },
{ "domain-mapfile", no_argument, NULL, 'D' },
{ "ignore-fs-check", no_argument, NULL, 'C' },
{ "rescue", no_argument, NULL, 'R' },
{ "new-serial", no_argument, NULL, 'I' },
Expand Down Expand Up @@ -486,6 +494,10 @@ static void parse_options(int argc, char **argv)
case 't':
opt.preserve_timestamps++;
break;
case 'D':
opt.domain_mapfile++;
opt.ignore_fs_check++;
break;
case 'V':
version();
break;
Expand Down Expand Up @@ -525,7 +537,7 @@ static void parse_options(int argc, char **argv)
if (opt.metadata && !opt.metadata_image && opt.std_out)
err_exit("Cloning only metadata to stdout isn't supported!\n");

if (opt.ignore_fs_check && !opt.metadata && !opt.rescue)
if (opt.ignore_fs_check && !opt.metadata && !opt.rescue && !opt.domain_mapfile)
err_exit("Filesystem check can be ignored only for metadata "
"cloning or rescue situations!\n");

Expand All @@ -539,6 +551,9 @@ static void parse_options(int argc, char **argv)
if (opt.no_action && opt.output)
err_exit("A restoring test requires not defining any output!\n");

if (opt.domain_mapfile && (opt.save_image || opt.restore_image || opt.metadata_image))
err_exit("Cannot perform other actions when writing a domain mapfile.\n");

if (!opt.no_action && !opt.std_out) {
struct stat st;
#ifdef HAVE_WINDOWS_H
Expand Down Expand Up @@ -574,6 +589,8 @@ static void parse_options(int argc, char **argv)
"but this time add the force "
"option, i.e. add '--force' to "
"the command line arguments.");
if (opt.domain_mapfile)
err_exit("Writing a domain mapfile to a block device is nonsensical.\n");
}
}
}
Expand Down Expand Up @@ -663,6 +680,75 @@ static s64 is_critical_metadata(ntfs_walk_clusters_ctx *image, runlist *rl)
return 0;
}

static void domain_add(off_t offset, int count)
{
if (!domain_first) {
domain_first = malloc(64 * sizeof(domain_extent));
if (!domain_first)
err_exit("failed to malloc rescue domain extent array\n");
domain_capacity = domain_first + 64;
domain_last = domain_first;
domain_last->begin = offset;
domain_last->end = offset + count;
} else if (offset == domain_last->end)
domain_last->end += count;
else {
++domain_last;
if (domain_last == domain_capacity) {
size_t old_size = domain_capacity - domain_first, new_size = 2 * old_size;
domain_extent* domain_new = realloc(domain_first, new_size * sizeof(domain_extent));
if (!domain_new)
err_exit("failed to realloc rescue domain extent array (%zu)\n", new_size);
domain_first = domain_new;
domain_last = domain_new + old_size;
domain_capacity = domain_new + new_size;
}
domain_last->begin = offset;
domain_last->end = offset + count;
}
}

static int domain_cmp(const void* va, const void* vb)
{
const domain_extent *a = (const domain_extent*)va;
const domain_extent *b = (const domain_extent*)vb;
if (a->begin < b->begin) return -1;
if (a->begin > b->begin) return 1;
if (a->end < b->end) return -1;
if (a->end > b->end) return 1;
return 0;
}

static void domain_write()
{
if (!domain_first)
err_exit("Can't happen: entered domain_write with no domain extents\n");
const domain_extent* const domain_end = domain_last + 1;
qsort(domain_first, domain_end - domain_first, sizeof(domain_extent), &domain_cmp);
if (domain_first->begin != 0)
err_exit("Can't happen: domain doesn't include boot sector? first domain (%ld, %ld)\n",
domain_first->begin, domain_first->end);

fprintf(stream_out, "# Domain mapfile created by %s v%s (libntfs-3g)\n", EXEC_NAME, VERSION);
fprintf(stream_out, "0x0 ? 1 # (status line not relevant for domain mapfiles)\n");
fprintf(stream_out, "# pos size status\n");
const domain_extent* d = domain_first;
while (d != domain_end) {
off_t begin = d->begin, end = d->begin;
do {
if (d->end < end)
err_exit("Can't happen: domain extents not sorted\n");
end = d->end;
++d;
} while (d != domain_end && end >= d->begin);
fprintf(stream_out, "0x%012lX 0x%012lX +\n", begin, end - begin);
begin = end;
end = (d != domain_end) ? d->begin : full_device_size;
if (begin != end)
fprintf(stream_out, "0x%012lX 0x%012lX ?\n", begin, end - begin);
}
}

static off_t tellin(int in)
{
return (lseek(in, 0, SEEK_CUR));
Expand All @@ -675,7 +761,7 @@ static int io_all(void *fd, void *buf, int count, int do_write)

while (count > 0) {
if (do_write) {
if (opt.no_action) {
if (opt.no_action || opt.domain_mapfile) {
i = count;
} else {
if (opt.save_image || opt.metadata_image)
Expand All @@ -690,7 +776,13 @@ static int io_all(void *fd, void *buf, int count, int do_write)
}
} else if (opt.restore_image)
i = read(*(int *)fd, buf, count);
else
else if (opt.domain_mapfile) {
if ((int*)fd == &fd_in)
domain_add(tellin(fd_in), count);
else
domain_add(dev->d_ops->seek(dev, 0, SEEK_CUR), count);
i = count;
} else
i = dev->d_ops->read(dev, buf, count);
if (i < 0) {
if (errno != EAGAIN && errno != EINTR)
Expand Down Expand Up @@ -847,7 +939,7 @@ static void copy_cluster(int rescue, u64 rescue_lcn, u64 lcn)
perr_exit("write_all");
}

if ((!opt.metadata_image || wipe)
if (!opt.domain_mapfile && (!opt.metadata_image || wipe)
&& (write_all(&fd_out, buff, csize) == -1)) {
#ifndef NO_STATFS
int err = errno;
Expand Down Expand Up @@ -885,7 +977,7 @@ static void lseek_to_cluster(s64 lcn)
if (vol->dev->d_ops->seek(vol->dev, pos, SEEK_SET) == (off_t)-1)
perr_exit("lseek input");

if (opt.std_out || opt.save_image || opt.metadata_image)
if (opt.std_out || opt.save_image || opt.metadata_image || opt.domain_mapfile)
return;

if (lseek_out(fd_out, pos, SEEK_SET) == (off_t)-1)
Expand Down Expand Up @@ -2297,7 +2389,7 @@ static s64 device_size_get(int fd)
static void fsync_clone(int fd)
{
Printf("Syncing ...\n");
if (opt.save_image && stream_out && fflush(stream_out))
if ((opt.domain_mapfile || opt.save_image) && stream_out && fflush(stream_out))
perr_exit("fflush");
if (fsync(fd) && errno != EINVAL)
perr_exit("fsync");
Expand Down Expand Up @@ -2554,7 +2646,7 @@ static void check_dest_free_space(u64 src_bytes)
struct statvfs stvfs;
struct stat st;

if (opt.metadata || opt.blkdev_out || opt.std_out)
if (opt.metadata || opt.domain_mapfile || opt.blkdev_out || opt.std_out)
return;
/*
* TODO: save_image needs a bit more space than src_bytes
Expand Down Expand Up @@ -2644,6 +2736,11 @@ int main(int argc, char **argv)
perr_exit("Opening file '%s' failed",
opt.output);
fd_out = fileno(stream_out);
} else if (opt.domain_mapfile) {
stream_out = fopen(opt.output, opt.overwrite ? "w" : "wx");
if (!stream_out)
perr_exit("Opening file '%s' failed", opt.output);
fd_out = fileno(stream_out);
} else {
#ifdef HAVE_WINDOWS_H
if (!opt.no_action) {
Expand All @@ -2663,7 +2760,7 @@ int main(int argc, char **argv)
#endif
}

if (!opt.save_image && !opt.metadata_image && !opt.no_action)
if (!opt.save_image && !opt.metadata_image && !opt.domain_mapfile && !opt.no_action)
check_output_device(ntfs_size);
}

Expand Down Expand Up @@ -2691,15 +2788,17 @@ int main(int argc, char **argv)
if (opt.save_image)
initialise_image_hdr(device_size, image.inuse);

if ((opt.std_out && !opt.metadata_image) || !opt.metadata) {
if ((opt.std_out && !opt.metadata_image) || !opt.metadata || opt.domain_mapfile) {
s64 nr_clusters_to_save = image.inuse;
if (opt.std_out && !opt.save_image)
if (opt.std_out && !opt.save_image && !opt.domain_mapfile)
nr_clusters_to_save = vol->nr_clusters;
nr_clusters_to_save++; /* account for the backup boot sector */

clone_ntfs(nr_clusters_to_save, image.more_use);
if (opt.domain_mapfile)
domain_write();
fsync_clone(fd_out);
if (opt.save_image)
if (opt.save_image || opt.domain_mapfile)
fclose(stream_out);
ntfs_umount(vol,FALSE);
free(lcn_bitmap.bm);
Expand Down