blob: 1cd399dbca4602cf8b00b0f288a342846b4cc841 [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"
static int show_progress = 0;
static int show_info = 0;
static int progress_since_warning = 0;
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);
progress_since_warning = 1;
}
/*
* perform_restore() -- do the actual work to restore the metadump
*
* @src_f: A FILE pointer to the source metadump
* @dst_fd: the file descriptor for the target file
* @is_target_file: designates whether the target is a regular file
* @mbp: pointer to metadump's first xfs_metablock, read and verified by the caller
*
* src_f should be positioned just past a read the previously validated metablock
*/
static void
perform_restore(
FILE *src_f,
int dst_fd,
int is_target_file,
const struct xfs_metablock *mbp)
{
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;
block_size = 1 << mbp->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(mbp->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, src_f) != 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 << mbp->mb_blocklog, 1, src_f) != 1)
fatal("error reading from metadump file\n");
libxfs_sb_from_disk(&sb, (xfs_dsb_t *)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);
((xfs_dsb_t*)block_buffer)->sb_inprogress = 1;
if (is_target_file) {
/* ensure regular files are correctly sized */
if (ftruncate(dst_fd, sb.sb_dblocks * sb.sb_blocksize))
fatal("cannot set filesystem image size: %s\n",
strerror(errno));
} else {
/* ensure device is sufficiently large enough */
char *lb[XFS_MAX_SECTORSIZE] = { NULL };
off64_t off;
off = sb.sb_dblocks * sb.sb_blocksize - sizeof(lb);
if (pwrite(dst_fd, lb, sizeof(lb), off) < 0)
fatal("failed to write last block, is target too "
"small? (error: %s)\n", strerror(errno));
}
bytes_read = 0;
for (;;) {
if (show_progress && (bytes_read & ((1 << 20) - 1)) == 0)
print_progress("%lld MB read", bytes_read >> 20);
for (cur_index = 0; cur_index < mb_count; cur_index++) {
if (pwrite(dst_fd, &block_buffer[cur_index <<
mbp->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, src_f) != 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 << mbp->mb_blocklog,
1, src_f) != 1)
fatal("error reading from metadump file\n");
bytes_read += block_size + (mb_count << mbp->mb_blocklog);
}
if (progress_since_warning)
putchar('\n');
memset(block_buffer, 0, sb.sb_sectsize);
sb.sb_inprogress = 0;
libxfs_sb_to_disk((xfs_dsb_t *)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(dst_fd, block_buffer, sb.sb_sectsize, 0) < 0)
fatal("error writing primary superblock: %s\n", strerror(errno));
free(metablock);
}
static void
usage(void)
{
fprintf(stderr, "Usage: %s [-V] [-g] [-i] source target\n", progname);
exit(1);
}
extern int platform_check_ismounted(char *, char *, struct stat *, int);
int
main(
int argc,
char **argv)
{
FILE *src_f;
int dst_fd;
int c;
int open_flags;
struct stat statbuf;
int is_target_file;
struct xfs_metablock mb;
progname = basename(argv[0]);
while ((c = getopt(argc, argv, "giV")) != EOF) {
switch (c) {
case 'g':
show_progress = 1;
break;
case 'i':
show_info = 1;
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 (!show_info && argc - optind != 2)
usage();
/*
* open source and test if this really is a dump. The first metadump block
* will be passed to perform_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(&mb, sizeof(mb), 1, src_f) != 1)
fatal("error reading from metadump file\n");
if (mb.mb_magic != cpu_to_be32(XFS_MD_MAGIC))
fatal("specified file is not a metadata dump\n");
if (show_info) {
if (mb.mb_info & XFS_METADUMP_INFO_FLAGS) {
printf("%s: %sobfuscated, %s log, %s metadata blocks\n",
argv[optind],
mb.mb_info & XFS_METADUMP_OBFUSCATED ? "":"not ",
mb.mb_info & XFS_METADUMP_DIRTYLOG ? "dirty":"clean",
mb.mb_info & XFS_METADUMP_FULLBLOCKS ? "full":"zeroed");
} else {
printf("%s: no informational flags present\n",
argv[optind]);
}
if (argc - optind == 1)
exit(0);
}
optind++;
/* check and open target */
open_flags = O_RDWR;
is_target_file = 0;
if (stat(argv[optind], &statbuf) < 0) {
/* ok, assume it's a file and create it */
open_flags |= O_CREAT;
is_target_file = 1;
} else if (S_ISREG(statbuf.st_mode)) {
open_flags |= O_TRUNC;
is_target_file = 1;
} else {
/*
* check to make sure a filesystem isn't mounted on the device
*/
if (platform_check_ismounted(argv[optind], NULL, &statbuf, 0))
fatal("a filesystem is mounted on target device \"%s\","
" cannot restore to a mounted filesystem.\n",
argv[optind]);
}
dst_fd = open(argv[optind], open_flags, 0644);
if (dst_fd < 0)
fatal("couldn't open target \"%s\"\n", argv[optind]);
perform_restore(src_f, dst_fd, is_target_file, &mb);
close(dst_fd);
if (src_f != stdin)
fclose(src_f);
return 0;
}