ntfsclone: support writing a ddrescue domain mapfile

pull/40/head
Jeffrey Bosboom 2022-05-29 20:25:16 -07:00
parent 875a1d4e90
commit 6866fa7f26
1 changed files with 111 additions and 12 deletions

View File

@ -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
@ -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;
@ -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"
@ -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' },
@ -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' },
@ -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;
@ -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");
@ -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
@ -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");
}
}
}
@ -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));
@ -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)
@ -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)
@ -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;
@ -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)
@ -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");
@ -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
@ -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) {
@ -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);
}
@ -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);