blob: 8e3998db06c0896703838d3aa135c65c4d6f6eb6 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2007 Silicon Graphics, Inc.
* All Rights Reserved.
*/
#include "libxfs.h"
#include "xfs_metadump.h"
#include <libfrog/platform.h>
#include "libfrog/div64.h"
union mdrestore_headers {
__be32 magic;
struct xfs_metablock v1;
struct xfs_metadump_header v2;
};
struct mdrestore_ops {
void (*read_header)(union mdrestore_headers *header, FILE *md_fp);
void (*show_info)(union mdrestore_headers *header, const char *md_file);
void (*restore)(union mdrestore_headers *header, FILE *md_fp,
int ddev_fd, bool is_data_target_file, int logdev_fd,
bool is_log_target_file);
};
static struct mdrestore {
struct mdrestore_ops *mdrops;
bool show_progress;
bool show_info;
bool progress_since_warning;
bool external_log;
} mdrestore;
static void
fatal(const char *msg, ...)
{
va_list args;
va_start(args, msg);
fprintf(stderr, "%s: ", progname);
vfprintf(stderr, msg, args);
exit(1);
}
static void
print_progress(const char *fmt, ...)
{
char buf[60];
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
buf[sizeof(buf)-1] = '\0';
printf("\r%-59s", buf);
fflush(stdout);
mdrestore.progress_since_warning = true;
}
static inline void
maybe_print_progress(
int64_t *cursor,
int64_t bytes_read)
{
int64_t mb_now = bytes_read >> 20;
if (!mdrestore.show_progress)
return;
if (mb_now != *cursor) {
print_progress("%lld MB read", mb_now);
*cursor = mb_now;
}
}
static inline void
final_print_progress(
int64_t *cursor,
int64_t bytes_read)
{
if (!mdrestore.show_progress)
goto done;
if (bytes_read <= (*cursor << 20))
goto done;
print_progress("%lld MB read", howmany_64(bytes_read, 1U << 20));
done:
if (mdrestore.progress_since_warning)
putchar('\n');
}
static void
fixup_superblock(
int ddev_fd,
char *block_buffer,
struct xfs_sb *sb)
{
memset(block_buffer, 0, sb->sb_sectsize);
sb->sb_inprogress = 0;
libxfs_sb_to_disk((struct xfs_dsb *)block_buffer, sb);
if (xfs_sb_version_hascrc(sb)) {
xfs_update_cksum(block_buffer, sb->sb_sectsize,
offsetof(struct xfs_sb, sb_crc));
}
if (pwrite(ddev_fd, block_buffer, sb->sb_sectsize, 0) < 0)
fatal("error writing primary superblock: %s\n", strerror(errno));
}
static int
open_device(
char *path,
bool *is_file)
{
struct stat statbuf;
int open_flags;
int fd;
open_flags = O_RDWR;
*is_file = false;
if (stat(path, &statbuf) < 0) {
/* ok, assume it's a file and create it */
open_flags |= O_CREAT;
*is_file = true;
} else if (S_ISREG(statbuf.st_mode)) {
open_flags |= O_TRUNC;
*is_file = true;
} else if (platform_check_ismounted(path, NULL, &statbuf, 0)) {
/*
* check to make sure a filesystem isn't mounted on the device
*/
fatal("a filesystem is mounted on target device \"%s\","
" cannot restore to a mounted filesystem.\n",
path);
}
fd = open(path, open_flags, 0644);
if (fd < 0)
fatal("couldn't open \"%s\"\n", path);
return fd;
}
static void
verify_device_size(
int dev_fd,
bool is_file,
xfs_rfsblock_t nr_blocks,
uint32_t blocksize)
{
if (is_file) {
/* ensure regular files are correctly sized */
if (ftruncate(dev_fd, nr_blocks * blocksize))
fatal("cannot set filesystem image size: %s\n",
strerror(errno));
} else {
/* ensure device is sufficiently large enough */
char lb[XFS_MAX_SECTORSIZE] = { 0 };
off64_t off;
off = nr_blocks * blocksize - sizeof(lb);
if (pwrite(dev_fd, lb, sizeof(lb), off) < 0)
fatal("failed to write last block, is target too "
"small? (error: %s)\n", strerror(errno));
}
}
static void
read_header_v1(
union mdrestore_headers *h,
FILE *md_fp)
{
if (fread((uint8_t *)&(h->v1.mb_count),
sizeof(h->v1) - sizeof(h->magic), 1, md_fp) != 1)
fatal("error reading from metadump file\n");
}
static void
show_info_v1(
union mdrestore_headers *h,
const char *md_file)
{
if (h->v1.mb_info & XFS_METADUMP_INFO_FLAGS) {
printf("%s: %sobfuscated, %s log, %s metadata blocks\n",
md_file,
h->v1.mb_info & XFS_METADUMP_OBFUSCATED ? "":"not ",
h->v1.mb_info & XFS_METADUMP_DIRTYLOG ? "dirty":"clean",
h->v1.mb_info & XFS_METADUMP_FULLBLOCKS ? "full":"zeroed");
} else {
printf("%s: no informational flags present\n", md_file);
}
}
static void
restore_v1(
union mdrestore_headers *h,
FILE *md_fp,
int ddev_fd,
bool is_data_target_file,
int logdev_fd,
bool is_log_target_file)
{
struct xfs_metablock *metablock; /* header + index + blocks */
__be64 *block_index;
char *block_buffer;
int block_size;
int max_indices;
int cur_index;
int mb_count;
xfs_sb_t sb;
int64_t bytes_read;
int64_t mb_read = 0;
block_size = 1 << h->v1.mb_blocklog;
max_indices = (block_size - sizeof(xfs_metablock_t)) / sizeof(__be64);
metablock = (xfs_metablock_t *)calloc(max_indices + 1, block_size);
if (metablock == NULL)
fatal("memory allocation failure\n");
mb_count = be16_to_cpu(h->v1.mb_count);
if (mb_count == 0 || mb_count > max_indices)
fatal("bad block count: %u\n", mb_count);
block_index = (__be64 *)((char *)metablock + sizeof(xfs_metablock_t));
block_buffer = (char *)metablock + block_size;
if (fread(block_index, block_size - sizeof(struct xfs_metablock), 1,
md_fp) != 1)
fatal("error reading from metadump file\n");
if (block_index[0] != 0)
fatal("first block is not the primary superblock\n");
if (fread(block_buffer, mb_count << h->v1.mb_blocklog, 1, md_fp) != 1)
fatal("error reading from metadump file\n");
libxfs_sb_from_disk(&sb, (struct xfs_dsb *)block_buffer);
if (sb.sb_magicnum != XFS_SB_MAGIC)
fatal("bad magic number for primary superblock\n");
/*
* Normally the upper bound would be simply XFS_MAX_SECTORSIZE
* but the metadump format has a maximum number of BBSIZE blocks
* it can store in a single metablock.
*/
if (sb.sb_sectsize < XFS_MIN_SECTORSIZE ||
sb.sb_sectsize > XFS_MAX_SECTORSIZE ||
sb.sb_sectsize > max_indices * block_size)
fatal("bad sector size %u in metadump image\n", sb.sb_sectsize);
((struct xfs_dsb*)block_buffer)->sb_inprogress = 1;
verify_device_size(ddev_fd, is_data_target_file, sb.sb_dblocks,
sb.sb_blocksize);
bytes_read = 0;
for (;;) {
maybe_print_progress(&mb_read, bytes_read);
for (cur_index = 0; cur_index < mb_count; cur_index++) {
if (pwrite(ddev_fd, &block_buffer[cur_index <<
h->v1.mb_blocklog], block_size,
be64_to_cpu(block_index[cur_index]) <<
BBSHIFT) < 0)
fatal("error writing block %llu: %s\n",
be64_to_cpu(block_index[cur_index]) << BBSHIFT,
strerror(errno));
}
if (mb_count < max_indices)
break;
if (fread(metablock, block_size, 1, md_fp) != 1)
fatal("error reading from metadump file\n");
mb_count = be16_to_cpu(metablock->mb_count);
if (mb_count == 0)
break;
if (mb_count > max_indices)
fatal("bad block count: %u\n", mb_count);
if (fread(block_buffer, mb_count << h->v1.mb_blocklog,
1, md_fp) != 1)
fatal("error reading from metadump file\n");
bytes_read += block_size + (mb_count << h->v1.mb_blocklog);
}
final_print_progress(&mb_read, bytes_read);
fixup_superblock(ddev_fd, block_buffer, &sb);
free(metablock);
}
static struct mdrestore_ops mdrestore_ops_v1 = {
.read_header = read_header_v1,
.show_info = show_info_v1,
.restore = restore_v1,
};
static void
read_header_v2(
union mdrestore_headers *h,
FILE *md_fp)
{
unsigned int compat;
if (fread((uint8_t *)&(h->v2) + sizeof(h->v2.xmh_magic),
sizeof(h->v2) - sizeof(h->v2.xmh_magic), 1, md_fp) != 1)
fatal("error reading from metadump file\n");
if (h->v2.xmh_incompat_flags != 0)
fatal("Metadump header has unknown incompat flags set\n");
if (h->v2.xmh_reserved != 0)
fatal("Metadump header's reserved field has a non-zero value\n");
compat = be32_to_cpu(h->v2.xmh_compat_flags);
if (!mdrestore.external_log && (compat & XFS_MD2_COMPAT_EXTERNALLOG))
fatal("External Log device is required\n");
}
static void
show_info_v2(
union mdrestore_headers *h,
const char *md_file)
{
uint32_t compat_flags;
compat_flags = be32_to_cpu(h->v2.xmh_compat_flags);
printf("%s: %sobfuscated, %s log, external log contents are %sdumped, %s metadata blocks,\n",
md_file,
compat_flags & XFS_MD2_COMPAT_OBFUSCATED ? "":"not ",
compat_flags & XFS_MD2_COMPAT_DIRTYLOG ? "dirty":"clean",
compat_flags & XFS_MD2_COMPAT_EXTERNALLOG ? "":"not ",
compat_flags & XFS_MD2_COMPAT_FULLBLOCKS ? "full":"zeroed");
}
#define MDR_IO_BUF_SIZE (8 * 1024 * 1024)
static void
restore_meta_extent(
FILE *md_fp,
int dev_fd,
char *device,
void *buf,
uint64_t offset,
int len)
{
int io_size;
io_size = min(len, MDR_IO_BUF_SIZE);
do {
if (fread(buf, io_size, 1, md_fp) != 1)
fatal("error reading from metadump file\n");
if (pwrite(dev_fd, buf, io_size, offset) < 0)
fatal("error writing to %s device at offset %llu: %s\n",
device, offset, strerror(errno));
len -= io_size;
offset += io_size;
io_size = min(len, io_size);
} while (len);
}
static void
restore_v2(
union mdrestore_headers *h,
FILE *md_fp,
int ddev_fd,
bool is_data_target_file,
int logdev_fd,
bool is_log_target_file)
{
struct xfs_sb sb;
struct xfs_meta_extent xme;
char *block_buffer;
int64_t mb_read = 0;
int64_t bytes_read;
uint64_t offset;
int len;
block_buffer = malloc(MDR_IO_BUF_SIZE);
if (block_buffer == NULL)
fatal("Unable to allocate input buffer memory\n");
if (fread(&xme, sizeof(xme), 1, md_fp) != 1)
fatal("error reading from metadump file\n");
if (xme.xme_addr != 0 || xme.xme_len == 1 ||
(be64_to_cpu(xme.xme_addr) & XME_ADDR_DEVICE_MASK) !=
XME_ADDR_DATA_DEVICE)
fatal("Invalid superblock disk address/length\n");
len = BBTOB(be32_to_cpu(xme.xme_len));
if (fread(block_buffer, len, 1, md_fp) != 1)
fatal("error reading from metadump file\n");
libxfs_sb_from_disk(&sb, (struct xfs_dsb *)block_buffer);
if (sb.sb_magicnum != XFS_SB_MAGIC)
fatal("bad magic number for primary superblock\n");
((struct xfs_dsb *)block_buffer)->sb_inprogress = 1;
verify_device_size(ddev_fd, is_data_target_file, sb.sb_dblocks,
sb.sb_blocksize);
if (sb.sb_logstart == 0) {
ASSERT(mdrestore.external_log == true);
verify_device_size(logdev_fd, is_log_target_file, sb.sb_logblocks,
sb.sb_blocksize);
}
if (pwrite(ddev_fd, block_buffer, len, 0) < 0)
fatal("error writing primary superblock: %s\n",
strerror(errno));
bytes_read = len;
do {
char *device;
int fd;
maybe_print_progress(&mb_read, bytes_read);
if (fread(&xme, sizeof(xme), 1, md_fp) != 1) {
if (feof(md_fp))
break;
fatal("error reading from metadump file\n");
}
offset = BBTOB(be64_to_cpu(xme.xme_addr) & XME_ADDR_DADDR_MASK);
switch (be64_to_cpu(xme.xme_addr) & XME_ADDR_DEVICE_MASK) {
case XME_ADDR_DATA_DEVICE:
device = "data";
fd = ddev_fd;
break;
case XME_ADDR_LOG_DEVICE:
device = "log";
fd = logdev_fd;
break;
default:
fatal("Invalid device found in metadump\n");
break;
}
len = BBTOB(be32_to_cpu(xme.xme_len));
restore_meta_extent(md_fp, fd, device, block_buffer, offset,
len);
bytes_read += len;
} while (1);
final_print_progress(&mb_read, bytes_read);
fixup_superblock(ddev_fd, block_buffer, &sb);
free(block_buffer);
}
static struct mdrestore_ops mdrestore_ops_v2 = {
.read_header = read_header_v2,
.show_info = show_info_v2,
.restore = restore_v2,
};
static void
usage(void)
{
fprintf(stderr, "Usage: %s [-V] [-g] [-i] [-l logdev] source target\n",
progname);
exit(1);
}
int
main(
int argc,
char **argv)
{
union mdrestore_headers headers;
FILE *src_f;
char *logdev = NULL;
int data_dev_fd = -1;
int log_dev_fd = -1;
int c;
bool is_data_dev_file = false;
bool is_log_dev_file = false;
mdrestore.show_progress = false;
mdrestore.show_info = false;
mdrestore.progress_since_warning = false;
mdrestore.external_log = false;
progname = basename(argv[0]);
while ((c = getopt(argc, argv, "gil:V")) != EOF) {
switch (c) {
case 'g':
mdrestore.show_progress = true;
break;
case 'i':
mdrestore.show_info = true;
break;
case 'l':
logdev = optarg;
mdrestore.external_log = true;
break;
case 'V':
printf("%s version %s\n", progname, VERSION);
exit(0);
default:
usage();
}
}
if (argc - optind < 1 || argc - optind > 2)
usage();
/* show_info without a target is ok */
if (!mdrestore.show_info && argc - optind != 2)
usage();
/*
* open source and test if this really is a dump. The first metadump
* block will be passed to mdrestore_ops->restore() which will continue
* to read the file from this point. This avoids rewind the stream,
* which causes restore to fail when source was being read from stdin.
*/
if (strcmp(argv[optind], "-") == 0) {
src_f = stdin;
if (isatty(fileno(stdin)))
fatal("cannot read from a terminal\n");
} else {
src_f = fopen(argv[optind], "rb");
if (src_f == NULL)
fatal("cannot open source dump file\n");
}
if (fread(&headers.magic, sizeof(headers.magic), 1, src_f) != 1)
fatal("Unable to read metadump magic from metadump file\n");
switch (be32_to_cpu(headers.magic)) {
case XFS_MD_MAGIC_V1:
if (logdev != NULL)
usage();
mdrestore.mdrops = &mdrestore_ops_v1;
break;
case XFS_MD_MAGIC_V2:
mdrestore.mdrops = &mdrestore_ops_v2;
break;
default:
fatal("specified file is not a metadata dump\n");
break;
}
mdrestore.mdrops->read_header(&headers, src_f);
if (mdrestore.show_info) {
mdrestore.mdrops->show_info(&headers, argv[optind]);
if (argc - optind == 1)
exit(0);
}
optind++;
/* check and open data device */
data_dev_fd = open_device(argv[optind], &is_data_dev_file);
if (mdrestore.external_log)
/* check and open log device */
log_dev_fd = open_device(logdev, &is_log_dev_file);
mdrestore.mdrops->restore(&headers, src_f, data_dev_fd,
is_data_dev_file, log_dev_fd, is_log_dev_file);
close(data_dev_fd);
if (mdrestore.external_log)
close(log_dev_fd);
if (src_f != stdin)
fclose(src_f);
return 0;
}