From 464df1d5e5ab1322e2dd0a7796939fff1aeefa9a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 17:49:25 +0200 Subject: Adding upstream version 1.47.0. Signed-off-by: Daniel Baumann --- misc/e2image.c | 1745 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1745 insertions(+) create mode 100644 misc/e2image.c (limited to 'misc/e2image.c') diff --git a/misc/e2image.c b/misc/e2image.c new file mode 100644 index 0000000..1ae0300 --- /dev/null +++ b/misc/e2image.c @@ -0,0 +1,1745 @@ +/* + * e2image.c --- Program which writes an image file backing up + * critical metadata for the filesystem. + * + * Copyright 2000, 2001 by Theodore Ts'o. + * + * %Begin-Header% + * This file may be redistributed under the terms of the GNU Public + * License. + * %End-Header% + */ + +#ifndef _LARGEFILE_SOURCE +#define _LARGEFILE_SOURCE +#endif +#ifndef _LARGEFILE64_SOURCE +#define _LARGEFILE64_SOURCE +#endif + +#include "config.h" +#include +#include +#ifdef HAVE_GETOPT_H +#include +#else +extern char *optarg; +extern int optind; +#endif +#include +#include +#ifdef HAVE_STDLIB_H +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "ext2fs/ext2fsP.h" +#include "et/com_err.h" +#include "uuid/uuid.h" +#include "e2p/e2p.h" +#include "ext2fs/e2image.h" +#include "ext2fs/qcow2.h" + +#include "support/nls-enable.h" +#include "support/plausible.h" +#include "support/quotaio.h" +#include "../version.h" + +#define QCOW_OFLAG_COPIED (1ULL << 63) +#define NO_BLK ((blk64_t) -1) + +/* Image types */ +#define E2IMAGE_RAW 1 +#define E2IMAGE_QCOW2 2 + +/* Image flags */ +#define E2IMAGE_INSTALL_FLAG 1 +#define E2IMAGE_SCRAMBLE_FLAG 2 +#define E2IMAGE_IS_QCOW2_FLAG 4 +#define E2IMAGE_CHECK_ZERO_FLAG 8 + +static const char * program_name = "e2image"; +static char * device_name = NULL; +static char all_data; +static char output_is_blk; +static char nop_flag; +/* writing to blk device: don't skip zeroed blocks */ +static blk64_t source_offset, dest_offset; +static char move_mode; +static char show_progress; +static char *check_buf; +static int skipped_blocks; + +static blk64_t align_offset(blk64_t offset, unsigned int n) +{ + return (offset + n - 1) & ~((blk64_t) n - 1); +} + +static int get_bits_from_size(size_t size) +{ + int res = 0; + + if (size == 0) + return -1; + + while (size != 1) { + /* Not a power of two */ + if (size & 1) + return -1; + + size >>= 1; + res++; + } + return res; +} + +static void usage(void) +{ + fprintf(stderr, _("Usage: %s [ -r|-Q ] [ -f ] [ -b superblock ] [ -B blocksize ] " + "device image-file\n"), + program_name); + fprintf(stderr, _(" %s -I device image-file\n"), program_name); + fprintf(stderr, _(" %s -ra [ -cfnp ] [ -o src_offset ] " + "[ -O dest_offset ] src_fs [ dest_fs ]\n"), + program_name); + exit (1); +} + +static ext2_loff_t seek_relative(int fd, int offset) +{ + ext2_loff_t ret = ext2fs_llseek(fd, offset, SEEK_CUR); + if (ret < 0) { + perror("seek_relative"); + exit(1); + } + return ret; +} + +static ext2_loff_t seek_set(int fd, ext2_loff_t offset) +{ + ext2_loff_t ret = ext2fs_llseek(fd, offset, SEEK_SET); + if (ret < 0) { + perror("seek_set"); + exit(1); + } + return ret; +} + +/* + * Returns true if the block we are about to write is identical to + * what is already on the disk. + */ +static int check_block(int fd, void *buf, void *cbuf, int blocksize) +{ + char *cp = cbuf; + int count = blocksize, ret; + + if (cbuf == NULL) + return 0; + + while (count > 0) { + ret = read(fd, cp, count); + if (ret < 0) { + perror("check_block"); + exit(1); + } + count -= ret; + cp += ret; + } + ret = memcmp(buf, cbuf, blocksize); + seek_relative(fd, -blocksize); + return (ret == 0) ? 1 : 0; +} + +static void generic_write(int fd, void *buf, int blocksize, blk64_t block) +{ + int count, free_buf = 0; + errcode_t err; + + if (!blocksize) + return; + + if (!buf) { + free_buf = 1; + err = ext2fs_get_arrayzero(1, blocksize, &buf); + if (err) { + com_err(program_name, err, "%s", + _("while allocating buffer")); + exit(1); + } + } + if (nop_flag) { + printf(_("Writing block %llu\n"), (unsigned long long) block); + if (fd != 1) + seek_relative(fd, blocksize); + goto free_and_return; + } + count = write(fd, buf, blocksize); + if (count != blocksize) { + if (count == -1) + err = errno; + else + err = 0; + + if (block) + com_err(program_name, err, + _("error writing block %llu"), + (unsigned long long) block); + else + com_err(program_name, err, "%s", + _("error in generic_write()")); + + exit(1); + } +free_and_return: + if (free_buf) + ext2fs_free_mem(&buf); +} + +static void write_header(int fd, void *hdr, int hdr_size, int wrt_size) +{ + char *header_buf; + int ret; + + /* Sanity check */ + if (hdr_size > wrt_size) { + fprintf(stderr, "%s", + _("Error: header size is bigger than wrt_size\n")); + } + + ret = ext2fs_get_mem(wrt_size, &header_buf); + if (ret) { + fputs(_("Couldn't allocate header buffer\n"), stderr); + exit(1); + } + + seek_set(fd, 0); + memset(header_buf, 0, wrt_size); + + if (hdr) + memcpy(header_buf, hdr, hdr_size); + + generic_write(fd, header_buf, wrt_size, NO_BLK); + + ext2fs_free_mem(&header_buf); +} + +static void write_image_file(ext2_filsys fs, int fd) +{ + struct ext2_image_hdr hdr; + struct stat st; + errcode_t retval; + + write_header(fd, NULL, sizeof(struct ext2_image_hdr), fs->blocksize); + memset(&hdr, 0, sizeof(struct ext2_image_hdr)); + + hdr.offset_super = ext2fs_cpu_to_le32(seek_relative(fd, 0)); + retval = ext2fs_image_super_write(fs, fd, 0); + if (retval) { + com_err(program_name, retval, "%s", + _("while writing superblock")); + exit(1); + } + + hdr.offset_inode = ext2fs_cpu_to_le32(seek_relative(fd, 0)); + retval = ext2fs_image_inode_write(fs, fd, + (fd != 1) ? IMAGER_FLAG_SPARSEWRITE : 0); + if (retval) { + com_err(program_name, retval, "%s", + _("while writing inode table")); + exit(1); + } + + hdr.offset_blockmap = ext2fs_cpu_to_le32(seek_relative(fd, 0)); + retval = ext2fs_image_bitmap_write(fs, fd, 0); + if (retval) { + com_err(program_name, retval, "%s", + _("while writing block bitmap")); + exit(1); + } + + hdr.offset_inodemap = ext2fs_cpu_to_le32(seek_relative(fd, 0)); + retval = ext2fs_image_bitmap_write(fs, fd, IMAGER_FLAG_INODEMAP); + if (retval) { + com_err(program_name, retval, "%s", + _("while writing inode bitmap")); + exit(1); + } + + hdr.magic_number = ext2fs_cpu_to_le32(EXT2_ET_MAGIC_E2IMAGE); + strcpy(hdr.magic_descriptor, "Ext2 Image 1.0"); + gethostname(hdr.fs_hostname, sizeof(hdr.fs_hostname)); + strncpy(hdr.fs_device_name, device_name, sizeof(hdr.fs_device_name)-1); + hdr.fs_device_name[sizeof(hdr.fs_device_name) - 1] = 0; + hdr.fs_blocksize = ext2fs_cpu_to_le32(fs->blocksize); + + if (stat(device_name, &st) == 0) + hdr.fs_device = ext2fs_cpu_to_le32(st.st_rdev); + + if (fstat(fd, &st) == 0) { + hdr.image_device = ext2fs_cpu_to_le32(st.st_dev); + hdr.image_inode = ext2fs_cpu_to_le32(st.st_ino); + } + memcpy(hdr.fs_uuid, fs->super->s_uuid, sizeof(hdr.fs_uuid)); + + hdr.image_time = ext2fs_cpu_to_le32(time(0)); + write_header(fd, &hdr, sizeof(struct ext2_image_hdr), fs->blocksize); +} + +/* + * These set of functions are used to write a RAW image file. + */ +static ext2fs_block_bitmap meta_block_map; +static ext2fs_block_bitmap scramble_block_map; /* Directory blocks to be scrambled */ +static blk64_t meta_blocks_count; + +struct process_block_struct { + ext2_ino_t ino; + int is_dir; +}; + +/* + * These subroutines short circuits ext2fs_get_blocks and + * ext2fs_check_directory; we use them since we already have the inode + * structure, so there's no point in letting the ext2fs library read + * the inode again. + */ +static ext2_ino_t stashed_ino = 0; +static struct ext2_inode *stashed_inode; + +static errcode_t meta_get_blocks(ext2_filsys fs EXT2FS_ATTR((unused)), + ext2_ino_t ino, + blk_t *blocks) +{ + int i; + + if ((ino != stashed_ino) || !stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + + for (i=0; i < EXT2_N_BLOCKS; i++) + blocks[i] = stashed_inode->i_block[i]; + return 0; +} + +static errcode_t meta_check_directory(ext2_filsys fs EXT2FS_ATTR((unused)), + ext2_ino_t ino) +{ + if ((ino != stashed_ino) || !stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + + if (!LINUX_S_ISDIR(stashed_inode->i_mode)) + return EXT2_ET_NO_DIRECTORY; + return 0; +} + +static errcode_t meta_read_inode(ext2_filsys fs EXT2FS_ATTR((unused)), + ext2_ino_t ino, + struct ext2_inode *inode) +{ + if ((ino != stashed_ino) || !stashed_inode) + return EXT2_ET_CALLBACK_NOTHANDLED; + *inode = *stashed_inode; + return 0; +} + +static void use_inode_shortcuts(ext2_filsys fs, int use_shortcuts) +{ + if (use_shortcuts) { + fs->get_blocks = meta_get_blocks; + fs->check_directory = meta_check_directory; + fs->read_inode = meta_read_inode; + stashed_ino = 0; + } else { + fs->get_blocks = 0; + fs->check_directory = 0; + fs->read_inode = 0; + } +} + +static int process_dir_block(ext2_filsys fs EXT2FS_ATTR((unused)), + blk64_t *block_nr, + e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)), + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data EXT2FS_ATTR((unused))) +{ + struct process_block_struct *p; + + p = (struct process_block_struct *) priv_data; + + ext2fs_mark_block_bitmap2(meta_block_map, *block_nr); + meta_blocks_count++; + if (scramble_block_map && p->is_dir && blockcnt >= 0) + ext2fs_mark_block_bitmap2(scramble_block_map, *block_nr); + return 0; +} + +static int process_file_block(ext2_filsys fs EXT2FS_ATTR((unused)), + blk64_t *block_nr, + e2_blkcnt_t blockcnt, + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *priv_data EXT2FS_ATTR((unused))) +{ + if (blockcnt < 0 || all_data) { + ext2fs_mark_block_bitmap2(meta_block_map, *block_nr); + meta_blocks_count++; + } + return 0; +} + +static void mark_table_blocks(ext2_filsys fs) +{ + blk64_t first_block, b; + unsigned int i,j; + + first_block = fs->super->s_first_data_block; + /* + * Mark primary superblock + */ + ext2fs_mark_block_bitmap2(meta_block_map, first_block); + meta_blocks_count++; + + /* + * Mark the primary superblock descriptors + */ + for (j = 0; j < fs->desc_blocks; j++) { + ext2fs_mark_block_bitmap2(meta_block_map, + ext2fs_descriptor_block_loc2(fs, first_block, j)); + } + meta_blocks_count += fs->desc_blocks; + + /* + * Mark MMP block + */ + if (fs->super->s_feature_incompat & EXT4_FEATURE_INCOMPAT_MMP) { + ext2fs_mark_block_bitmap2(meta_block_map, fs->super->s_mmp_block); + meta_blocks_count++; + } + + for (i = 0; i < fs->group_desc_count; i++) { + /* + * Mark the blocks used for the inode table + */ + if ((output_is_blk || + !ext2fs_bg_flags_test(fs, i, EXT2_BG_INODE_UNINIT)) && + ext2fs_inode_table_loc(fs, i)) { + unsigned int end = (unsigned) fs->inode_blocks_per_group; + /* skip unused blocks */ + if (!output_is_blk && ext2fs_has_group_desc_csum(fs)) + end -= (ext2fs_bg_itable_unused(fs, i) / + EXT2_INODES_PER_BLOCK(fs->super)); + for (j = 0, b = ext2fs_inode_table_loc(fs, i); + j < end; + j++, b++) { + ext2fs_mark_block_bitmap2(meta_block_map, b); + meta_blocks_count++; + } + } + + /* + * Mark block used for the block bitmap + */ + if (!ext2fs_bg_flags_test(fs, i, EXT2_BG_BLOCK_UNINIT) && + ext2fs_block_bitmap_loc(fs, i)) { + ext2fs_mark_block_bitmap2(meta_block_map, + ext2fs_block_bitmap_loc(fs, i)); + meta_blocks_count++; + } + + /* + * Mark block used for the inode bitmap + */ + if (!ext2fs_bg_flags_test(fs, i, EXT2_BG_INODE_UNINIT) && + ext2fs_inode_bitmap_loc(fs, i)) { + ext2fs_mark_block_bitmap2(meta_block_map, + ext2fs_inode_bitmap_loc(fs, i)); + meta_blocks_count++; + } + } +} + +/* + * This function returns 1 if the specified block is all zeros + */ +static int check_zero_block(char *buf, int blocksize) +{ + char *cp = buf; + int left = blocksize; + + if (output_is_blk) + return 0; + while (left > 0) { + if (*cp++) + return 0; + left--; + } + return 1; +} + +static int name_id[256]; + +#define EXT4_MAX_REC_LEN ((1<<16)-1) + +static void scramble_dir_block(ext2_filsys fs, blk64_t blk, char *buf) +{ + char *p, *end, *cp; + struct ext2_dir_entry_2 *dirent; + unsigned int rec_len; + int id, len; + + end = buf + fs->blocksize; + for (p = buf; p < end-8; p += rec_len) { + dirent = (struct ext2_dir_entry_2 *) p; + rec_len = dirent->rec_len; +#ifdef WORDS_BIGENDIAN + rec_len = ext2fs_swab16(rec_len); +#endif + if (rec_len == EXT4_MAX_REC_LEN || rec_len == 0) + rec_len = fs->blocksize; + else + rec_len = (rec_len & 65532) | ((rec_len & 3) << 16); +#if 0 + printf("rec_len = %d, name_len = %d\n", rec_len, dirent->name_len); +#endif + if (rec_len < 8 || (rec_len % 4) || + (p+rec_len > end)) { + printf(_("Corrupt directory block %llu: " + "bad rec_len (%d)\n"), + (unsigned long long) blk, rec_len); + rec_len = end - p; + (void) ext2fs_set_rec_len(fs, rec_len, + (struct ext2_dir_entry *) dirent); +#ifdef WORDS_BIGENDIAN + dirent->rec_len = ext2fs_swab16(dirent->rec_len); +#endif + continue; + } + if (dirent->name_len + 8U > rec_len) { + printf(_("Corrupt directory block %llu: " + "bad name_len (%d)\n"), + (unsigned long long) blk, dirent->name_len); + dirent->name_len = rec_len - 8; + continue; + } + cp = p+8; + len = rec_len - dirent->name_len - 8; + if (len > 0) + memset(cp+dirent->name_len, 0, len); + if (dirent->name_len==1 && cp[0] == '.') + continue; + if (dirent->name_len==2 && cp[0] == '.' && cp[1] == '.') + continue; + + memset(cp, 'A', dirent->name_len); + len = dirent->name_len; + id = name_id[len]++; + while ((len > 0) && (id > 0)) { + *cp += id % 26; + id = id / 26; + cp++; + len--; + } + } +} + +static char got_sigint; + +static void sigint_handler(int unused EXT2FS_ATTR((unused))) +{ + got_sigint = 1; + signal (SIGINT, SIG_DFL); +} + +#define calc_percent(a, b) ((int) ((100.0 * (((float) (a)) / \ + ((float) (b)))) + 0.5)) +#define calc_rate(t, b, d) (((float)(t) / ((float)(1024 * 1024) / (b))) / (d)) + +static int print_progress(blk64_t num, blk64_t total) +{ + return fprintf(stderr, _("%llu / %llu blocks (%d%%)"), + (unsigned long long) num, + (unsigned long long) total, + calc_percent(num, total)); +} + +static void output_meta_data_blocks(ext2_filsys fs, int fd, int flags) +{ + errcode_t retval; + blk64_t blk; + char *buf, *zero_buf; + int sparse = 0; + blk64_t start = 0; + blk64_t distance = 0; + blk64_t end = ext2fs_blocks_count(fs->super); + time_t last_update = 0; + time_t start_time = 0; + blk64_t total_written = 0; + int bscount = 0; + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) { + com_err(program_name, retval, "%s", + _("while allocating buffer")); + exit(1); + } + retval = ext2fs_get_memzero(fs->blocksize, &zero_buf); + if (retval) { + com_err(program_name, retval, "%s", + _("while allocating buffer")); + exit(1); + } + if (show_progress) { + fprintf(stderr, "%s", _("Copying ")); + bscount = print_progress(total_written, meta_blocks_count); + fflush(stderr); + last_update = time(NULL); + start_time = time(NULL); + } + /* when doing an in place move to the right, you can't start + at the beginning or you will overwrite data, so instead + divide the fs up into distance size chunks and write them + in reverse. */ + if (move_mode && dest_offset > source_offset) { + distance = (dest_offset - source_offset) / fs->blocksize; + if (distance < ext2fs_blocks_count(fs->super)) + start = ext2fs_blocks_count(fs->super) - distance; + } + if (move_mode) + signal (SIGINT, sigint_handler); +more_blocks: + if (distance) + seek_set(fd, (start * fs->blocksize) + dest_offset); + for (blk = start; blk < end; blk++) { + if (got_sigint) { + if (distance) { + /* moving to the right */ + if (distance >= ext2fs_blocks_count(fs->super)|| + start == ext2fs_blocks_count(fs->super) - + distance) + kill(getpid(), SIGINT); + } else { + /* moving to the left */ + if (blk < (source_offset - dest_offset) / + fs->blocksize) + kill(getpid(), SIGINT); + } + if (show_progress) + fputc('\r', stderr); + fprintf(stderr, "%s", + _("Stopping now will destroy the filesystem, " + "interrupt again if you are sure\n")); + if (show_progress) { + fprintf(stderr, "%s", _("Copying ")); + bscount = print_progress(total_written, + meta_blocks_count); + fflush(stderr); + } + + got_sigint = 0; + } + if (show_progress && last_update != time(NULL)) { + time_t duration; + last_update = time(NULL); + while (bscount--) + fputc('\b', stderr); + bscount = print_progress(total_written, + meta_blocks_count); + duration = time(NULL) - start_time; + if (duration > 5 && total_written) { + time_t est = (duration * meta_blocks_count / + total_written) - duration; + char buff[30]; + strftime(buff, 30, "%T", gmtime(&est)); + bscount += + fprintf(stderr, + _(" %s remaining at %.2f MB/s"), + buff, calc_rate(total_written, + fs->blocksize, + duration)); + } + fflush (stderr); + } + if ((blk >= fs->super->s_first_data_block) && + ext2fs_test_block_bitmap2(meta_block_map, blk)) { + retval = io_channel_read_blk64(fs->io, blk, 1, buf); + if (retval) { + com_err(program_name, retval, + _("error reading block %llu"), + (unsigned long long) blk); + } + total_written++; + if (scramble_block_map && + ext2fs_test_block_bitmap2(scramble_block_map, blk)) + scramble_dir_block(fs, blk, buf); + if ((flags & E2IMAGE_CHECK_ZERO_FLAG) && + check_zero_block(buf, fs->blocksize)) + goto sparse_write; + if (sparse) + seek_relative(fd, sparse); + sparse = 0; + if (check_block(fd, buf, check_buf, fs->blocksize)) { + seek_relative(fd, fs->blocksize); + skipped_blocks++; + } else + generic_write(fd, buf, fs->blocksize, blk); + } else { + sparse_write: + if (fd == 1) { + if (!nop_flag) + generic_write(fd, zero_buf, + fs->blocksize, blk); + continue; + } + sparse += fs->blocksize; + if (sparse > 1024*1024) { + seek_relative(fd, 1024*1024); + sparse -= 1024*1024; + } + } + } + if (distance && start) { + if (start < distance) { + end = start; + start = 0; + } else { + end -= distance; + start -= distance; + if (end < distance) { + /* past overlap, do rest in one go */ + end = start; + start = 0; + } + } + sparse = 0; + goto more_blocks; + } + signal (SIGINT, SIG_DFL); + if (show_progress) { + time_t duration = time(NULL) - start_time; + char buff[30]; + fputc('\r', stderr); + strftime(buff, 30, "%T", gmtime(&duration)); + fprintf(stderr, _("Copied %llu / %llu blocks (%d%%) in %s "), + (unsigned long long) total_written, + (unsigned long long) meta_blocks_count, + calc_percent(total_written, meta_blocks_count), buff); + if (duration) + fprintf(stderr, _("at %.2f MB/s"), + calc_rate(total_written, fs->blocksize, duration)); + fputs(" \n", stderr); + } +#ifdef HAVE_FTRUNCATE64 + if (sparse) { + ext2_loff_t offset; + if (distance) + offset = seek_set(fd, + fs->blocksize * ext2fs_blocks_count(fs->super) + dest_offset); + else + offset = seek_relative(fd, sparse); + + if (ftruncate64(fd, offset) < 0) { + seek_relative(fd, -1); + generic_write(fd, zero_buf, 1, NO_BLK); + } + } +#else + if (sparse && !distance) { + seek_relative(fd, sparse-1); + generic_write(fd, zero_buf, 1, NO_BLK); + } +#endif + ext2fs_free_mem(&zero_buf); + ext2fs_free_mem(&buf); +} + +static void init_l1_table(struct ext2_qcow2_image *image) +{ + __u64 *l1_table; + errcode_t ret; + + ret = ext2fs_get_arrayzero(image->l1_size, sizeof(__u64), &l1_table); + if (ret) { + com_err(program_name, ret, "%s", + _("while allocating l1 table")); + exit(1); + } + + image->l1_table = l1_table; +} + +static void init_l2_cache(struct ext2_qcow2_image *image) +{ + unsigned int count, i; + struct ext2_qcow2_l2_cache *cache; + struct ext2_qcow2_l2_table *table; + errcode_t ret; + + ret = ext2fs_get_arrayzero(1, sizeof(struct ext2_qcow2_l2_cache), + &cache); + if (ret) + goto alloc_err; + + count = (image->l1_size > L2_CACHE_PREALLOC) ? L2_CACHE_PREALLOC : + image->l1_size; + + cache->count = count; + cache->free = count; + cache->next_offset = image->l2_offset; + + for (i = 0; i < count; i++) { + ret = ext2fs_get_arrayzero(1, + sizeof(struct ext2_qcow2_l2_table), &table); + if (ret) + goto alloc_err; + + ret = ext2fs_get_arrayzero(image->l2_size, + sizeof(__u64), &table->data); + if (ret) + goto alloc_err; + + table->next = cache->free_head; + cache->free_head = table; + } + + image->l2_cache = cache; + return; + +alloc_err: + com_err(program_name, ret, "%s", _("while allocating l2 cache")); + exit(1); +} + +static void put_l2_cache(struct ext2_qcow2_image *image) +{ + struct ext2_qcow2_l2_cache *cache = image->l2_cache; + struct ext2_qcow2_l2_table *tmp, *table; + + if (!cache) + return; + + table = cache->free_head; + cache->free_head = NULL; +again: + while (table) { + tmp = table; + table = table->next; + ext2fs_free_mem(&tmp->data); + ext2fs_free_mem(&tmp); + } + + if (cache->free != cache->count) { + fprintf(stderr, "%s", _("Warning: There are still tables in " + "the cache while putting the cache, " + "data will be lost so the image may " + "not be valid.\n")); + table = cache->used_head; + cache->used_head = NULL; + goto again; + } + + ext2fs_free_mem(&cache); +} + +static int init_refcount(struct ext2_qcow2_image *img, blk64_t table_offset) +{ + struct ext2_qcow2_refcount *ref; + blk64_t table_clusters; + errcode_t ret; + + ref = &(img->refcount); + + /* + * One refcount block addresses 2048 clusters, one refcount table + * addresses cluster/sizeof(__u64) refcount blocks, and we need + * to address meta_blocks_count clusters + qcow2 metadata clusters + * in the worst case. + */ + table_clusters = meta_blocks_count + (table_offset >> + img->cluster_bits); + table_clusters >>= (img->cluster_bits + 6 - 1); + table_clusters = (table_clusters == 0) ? 1 : table_clusters; + + ref->refcount_table_offset = table_offset; + ref->refcount_table_clusters = table_clusters; + ref->refcount_table_index = 0; + ref->refcount_block_index = 0; + + /* Allocate refcount table */ + ret = ext2fs_get_arrayzero(ref->refcount_table_clusters, + img->cluster_size, &ref->refcount_table); + if (ret) + return ret; + + /* Allocate refcount block */ + ret = ext2fs_get_arrayzero(1, img->cluster_size, &ref->refcount_block); + if (ret) + ext2fs_free_mem(&ref->refcount_table); + + return ret; +} + +static errcode_t initialize_qcow2_image(int fd, ext2_filsys fs, + struct ext2_qcow2_image *image) +{ + struct ext2_qcow2_hdr *header; + blk64_t total_size, offset; + int shift, l2_bits, header_size, l1_size, ret; + int cluster_bits = get_bits_from_size(fs->blocksize); + struct ext2_super_block *sb = fs->super; + + /* Sbould never happen, but just in case... */ + if (cluster_bits < 0) + return EXT2_FILSYS_CORRUPTED; + + /* Allocate header */ + ret = ext2fs_get_memzero(sizeof(struct ext2_qcow2_hdr), &header); + if (ret) + return ret; + + total_size = ext2fs_blocks_count(sb) << cluster_bits; + image->cluster_size = fs->blocksize; + image->l2_size = 1 << (cluster_bits - 3); + image->cluster_bits = cluster_bits; + image->fd = fd; + + header->magic = ext2fs_cpu_to_be32(QCOW_MAGIC); + header->version = ext2fs_cpu_to_be32(QCOW_VERSION); + header->size = ext2fs_cpu_to_be64(total_size); + header->cluster_bits = ext2fs_cpu_to_be32(cluster_bits); + + header_size = (sizeof(struct ext2_qcow2_hdr) + 7) & ~7; + offset = align_offset(header_size, image->cluster_size); + + header->l1_table_offset = ext2fs_cpu_to_be64(offset); + image->l1_offset = offset; + + l2_bits = cluster_bits - 3; + shift = cluster_bits + l2_bits; + l1_size = ((total_size + (1LL << shift) - 1) >> shift); + header->l1_size = ext2fs_cpu_to_be32(l1_size); + image->l1_size = l1_size; + + /* Make space for L1 table */ + offset += align_offset(l1_size * sizeof(blk64_t), image->cluster_size); + + /* Initialize refcounting */ + ret = init_refcount(image, offset); + if (ret) { + ext2fs_free_mem(&header); + return ret; + } + header->refcount_table_offset = ext2fs_cpu_to_be64(offset); + header->refcount_table_clusters = + ext2fs_cpu_to_be32(image->refcount.refcount_table_clusters); + offset += image->cluster_size; + offset += (blk64_t) image->refcount.refcount_table_clusters << + image->cluster_bits; + + /* Make space for L2 tables */ + image->l2_offset = offset; + offset += image->cluster_size; + + /* Make space for first refcount block */ + image->refcount.refcount_block_offset = offset; + + image->hdr = header; + /* Initialize l1 and l2 tables */ + init_l1_table(image); + init_l2_cache(image); + + return 0; +} + +static void free_qcow2_image(struct ext2_qcow2_image *img) +{ + if (!img) + return; + + if (img->hdr) + ext2fs_free_mem(&img->hdr); + + if (img->l1_table) + ext2fs_free_mem(&img->l1_table); + + if (img->refcount.refcount_table) + ext2fs_free_mem(&img->refcount.refcount_table); + if (img->refcount.refcount_block) + ext2fs_free_mem(&img->refcount.refcount_block); + + put_l2_cache(img); + + ext2fs_free_mem(&img); +} + +/** + * Put table from used list (used_head) into free list (free_head). + * l2_table is used to return pointer to the next used table (used_head). + */ +static void put_used_table(struct ext2_qcow2_image *img, + struct ext2_qcow2_l2_table **l2_table) +{ + struct ext2_qcow2_l2_cache *cache = img->l2_cache; + struct ext2_qcow2_l2_table *table; + + table = cache->used_head; + cache->used_head = table->next; + + assert(table); + if (!table->next) + cache->used_tail = NULL; + + /* Clean the table for case we will need to use it again */ + memset(table->data, 0, img->cluster_size); + table->next = cache->free_head; + cache->free_head = table; + + cache->free++; + + *l2_table = cache->used_head; +} + +static void flush_l2_cache(struct ext2_qcow2_image *image) +{ + blk64_t seek = 0; + ext2_loff_t offset; + struct ext2_qcow2_l2_cache *cache = image->l2_cache; + struct ext2_qcow2_l2_table *table = cache->used_head; + int fd = image->fd; + + /* Store current position */ + offset = seek_relative(fd, 0); + + assert(table); + while (cache->free < cache->count) { + if (seek != table->offset) { + seek_set(fd, table->offset); + seek = table->offset; + } + + generic_write(fd, (char *)table->data, image->cluster_size, + NO_BLK); + put_used_table(image, &table); + seek += image->cluster_size; + } + + /* Restore previous position */ + seek_set(fd, offset); +} + +/** + * Get first free table (from free_head) and put it into tail of used list + * (to used_tail). + * l2_table is used to return pointer to moved table. + * Returns 1 if the cache is full, 0 otherwise. + */ +static void get_free_table(struct ext2_qcow2_image *image, + struct ext2_qcow2_l2_table **l2_table) +{ + struct ext2_qcow2_l2_table *table; + struct ext2_qcow2_l2_cache *cache = image->l2_cache; + + if (0 == cache->free) + flush_l2_cache(image); + + table = cache->free_head; + assert(table); + cache->free_head = table->next; + + if (cache->used_tail) + cache->used_tail->next = table; + else + /* First item in the used list */ + cache->used_head = table; + + cache->used_tail = table; + cache->free--; + + *l2_table = table; +} + +static int add_l2_item(struct ext2_qcow2_image *img, blk64_t blk, + blk64_t data, blk64_t next) +{ + struct ext2_qcow2_l2_cache *cache = img->l2_cache; + struct ext2_qcow2_l2_table *table = cache->used_tail; + blk64_t l1_index = blk / img->l2_size; + blk64_t l2_index = blk & (img->l2_size - 1); + int ret = 0; + + /* + * Need to create new table if it does not exist, + * or if it is full + */ + if (!table || (table->l1_index != l1_index)) { + get_free_table(img, &table); + table->l1_index = l1_index; + table->offset = cache->next_offset; + cache->next_offset = next; + img->l1_table[l1_index] = + ext2fs_cpu_to_be64(table->offset | QCOW_OFLAG_COPIED); + ret++; + } + + table->data[l2_index] = ext2fs_cpu_to_be64(data | QCOW_OFLAG_COPIED); + return ret; +} + +static int update_refcount(int fd, struct ext2_qcow2_image *img, + blk64_t offset, blk64_t rfblk_pos) +{ + struct ext2_qcow2_refcount *ref; + __u32 table_index; + int ret = 0; + + ref = &(img->refcount); + table_index = offset >> (2 * img->cluster_bits - 1); + + /* + * Need to create new refcount block when the offset addresses + * another item in the refcount table + */ + if (table_index != ref->refcount_table_index) { + + seek_set(fd, ref->refcount_block_offset); + + generic_write(fd, (char *)ref->refcount_block, + img->cluster_size, NO_BLK); + memset(ref->refcount_block, 0, img->cluster_size); + + ref->refcount_table[ref->refcount_table_index] = + ext2fs_cpu_to_be64(ref->refcount_block_offset); + ref->refcount_block_offset = rfblk_pos; + ref->refcount_block_index = 0; + ref->refcount_table_index = table_index; + ret++; + } + + /* + * We are relying on the fact that we are creating the qcow2 + * image sequentially, hence we will always allocate refcount + * block items sequentially. + */ + ref->refcount_block[ref->refcount_block_index] = ext2fs_cpu_to_be16(1); + ref->refcount_block_index++; + return ret; +} + +static int sync_refcount(int fd, struct ext2_qcow2_image *img) +{ + struct ext2_qcow2_refcount *ref; + + ref = &(img->refcount); + + ref->refcount_table[ref->refcount_table_index] = + ext2fs_cpu_to_be64(ref->refcount_block_offset); + seek_set(fd, ref->refcount_table_offset); + generic_write(fd, (char *)ref->refcount_table, + ref->refcount_table_clusters << img->cluster_bits, NO_BLK); + + seek_set(fd, ref->refcount_block_offset); + generic_write(fd, (char *)ref->refcount_block, img->cluster_size, + NO_BLK); + return 0; +} + +static void output_qcow2_meta_data_blocks(ext2_filsys fs, int fd) +{ + errcode_t retval; + blk64_t blk, offset, size, end; + char *buf; + struct ext2_qcow2_image *img; + unsigned int header_size; + + /* allocate struct ext2_qcow2_image */ + retval = ext2fs_get_mem(sizeof(struct ext2_qcow2_image), &img); + if (retval) { + com_err(program_name, retval, "%s", + _("while allocating ext2_qcow2_image")); + exit(1); + } + + retval = initialize_qcow2_image(fd, fs, img); + if (retval) { + com_err(program_name, retval, "%s", + _("while initializing ext2_qcow2_image")); + exit(1); + } + header_size = align_offset(sizeof(struct ext2_qcow2_hdr), + img->cluster_size); + write_header(fd, img->hdr, sizeof(struct ext2_qcow2_hdr), header_size); + + /* Refcount all qcow2 related metadata up to refcount_block_offset */ + end = img->refcount.refcount_block_offset; + seek_set(fd, end); + blk = end + img->cluster_size; + for (offset = 0; offset <= end; offset += img->cluster_size) { + if (update_refcount(fd, img, offset, blk)) { + blk += img->cluster_size; + /* + * If we create new refcount block, we need to refcount + * it as well. + */ + end += img->cluster_size; + } + } + seek_set(fd, offset); + + retval = ext2fs_get_mem(fs->blocksize, &buf); + if (retval) { + com_err(program_name, retval, "%s", + _("while allocating buffer")); + exit(1); + } + /* Write qcow2 data blocks */ + for (blk = 0; blk < ext2fs_blocks_count(fs->super); blk++) { + if ((blk >= fs->super->s_first_data_block) && + ext2fs_test_block_bitmap2(meta_block_map, blk)) { + retval = io_channel_read_blk64(fs->io, blk, 1, buf); + if (retval) { + com_err(program_name, retval, + _("error reading block %llu"), + (unsigned long long) blk); + continue; + } + if (scramble_block_map && + ext2fs_test_block_bitmap2(scramble_block_map, blk)) + scramble_dir_block(fs, blk, buf); + if (check_zero_block(buf, fs->blocksize)) + continue; + + if (update_refcount(fd, img, offset, offset)) { + /* Make space for another refcount block */ + offset += img->cluster_size; + seek_set(fd, offset); + /* + * We have created the new refcount block, this + * means that we need to refcount it as well. + * So the previous update_refcount refcounted + * the block itself and now we are going to + * create refcount for data. New refcount + * block should not be created! + */ + if (update_refcount(fd, img, offset, offset)) { + fprintf(stderr, "%s", + _("Programming error: multiple " + "sequential refcount blocks " + "created!\n")); + exit(1); + } + } + + generic_write(fd, buf, fs->blocksize, blk); + + if (add_l2_item(img, blk, offset, + offset + img->cluster_size)) { + offset += img->cluster_size; + if (update_refcount(fd, img, offset, + offset + img->cluster_size)) { + offset += img->cluster_size; + if (update_refcount(fd, img, offset, + offset)) { + fprintf(stderr, "%s", + _("Programming error: multiple sequential refcount " + "blocks created!\n")); + exit(1); + } + } + offset += img->cluster_size; + seek_set(fd, offset); + continue; + } + + offset += img->cluster_size; + } + } + (void) update_refcount(fd, img, offset, offset); + flush_l2_cache(img); + sync_refcount(fd, img); + + /* Write l1_table*/ + seek_set(fd, img->l1_offset); + size = img->l1_size * sizeof(__u64); + generic_write(fd, (char *)img->l1_table, size, NO_BLK); + + ext2fs_free_mem(&buf); + free_qcow2_image(img); +} + +static void write_raw_image_file(ext2_filsys fs, int fd, int type, int flags, + blk64_t superblock) +{ + struct process_block_struct pb; + struct ext2_inode inode; + ext2_inode_scan scan; + ext2_ino_t ino; + errcode_t retval; + char * block_buf; + + meta_blocks_count = 0; + retval = ext2fs_allocate_block_bitmap(fs, _("in-use block map"), + &meta_block_map); + if (retval) { + com_err(program_name, retval, "%s", + _("while allocating block bitmap")); + exit(1); + } + + if (flags & E2IMAGE_SCRAMBLE_FLAG) { + retval = ext2fs_allocate_block_bitmap(fs, "scramble block map", + &scramble_block_map); + if (retval) { + com_err(program_name, retval, "%s", + _("while allocating scramble block bitmap")); + exit(1); + } + } + + if (superblock) { + unsigned int j; + + ext2fs_mark_block_bitmap2(meta_block_map, superblock); + meta_blocks_count++; + + /* + * Mark the backup superblock descriptors + */ + for (j = 0; j < fs->desc_blocks; j++) { + ext2fs_mark_block_bitmap2(meta_block_map, + ext2fs_descriptor_block_loc2(fs, superblock, j)); + } + meta_blocks_count += fs->desc_blocks; + } + + mark_table_blocks(fs); + if (show_progress) + fprintf(stderr, "%s", _("Scanning inodes...\n")); + + retval = ext2fs_open_inode_scan(fs, 0, &scan); + if (retval) { + com_err(program_name, retval, "%s", + _("while opening inode scan")); + exit(1); + } + + retval = ext2fs_get_mem(fs->blocksize * 3, &block_buf); + if (retval) { + com_err(program_name, 0, "%s", + _("Can't allocate block buffer")); + exit(1); + } + + use_inode_shortcuts(fs, 1); + stashed_inode = &inode; + while (1) { + retval = ext2fs_get_next_inode(scan, &ino, &inode); + if (retval == EXT2_ET_BAD_BLOCK_IN_INODE_TABLE) + continue; + if (retval) { + com_err(program_name, retval, "%s", + _("while getting next inode")); + exit(1); + } + if (ino == 0) + break; + if (!inode.i_links_count) + continue; + if (ext2fs_file_acl_block(fs, &inode)) { + ext2fs_mark_block_bitmap2(meta_block_map, + ext2fs_file_acl_block(fs, &inode)); + meta_blocks_count++; + } + if (!ext2fs_inode_has_valid_blocks2(fs, &inode)) + continue; + + stashed_ino = ino; + pb.ino = ino; + pb.is_dir = LINUX_S_ISDIR(inode.i_mode); + if (LINUX_S_ISDIR(inode.i_mode) || + LINUX_S_ISLNK(inode.i_mode) || + ino == fs->super->s_journal_inum || + ino == quota_type2inum(USRQUOTA, fs->super) || + ino == quota_type2inum(GRPQUOTA, fs->super) || + ino == quota_type2inum(PRJQUOTA, fs->super) || + ino == fs->super->s_orphan_file_inum) { + retval = ext2fs_block_iterate3(fs, ino, + BLOCK_FLAG_READ_ONLY, block_buf, + process_dir_block, &pb); + if (retval) { + com_err(program_name, retval, + _("while iterating over inode %u"), + ino); + exit(1); + } + } else { + if ((inode.i_flags & EXT4_EXTENTS_FL) || + inode.i_block[EXT2_IND_BLOCK] || + inode.i_block[EXT2_DIND_BLOCK] || + inode.i_block[EXT2_TIND_BLOCK] || all_data) { + retval = ext2fs_block_iterate3(fs, + ino, BLOCK_FLAG_READ_ONLY, block_buf, + process_file_block, &pb); + if (retval) { + com_err(program_name, retval, + _("while iterating over inode %u"), ino); + exit(1); + } + } + } + } + use_inode_shortcuts(fs, 0); + + if (type & E2IMAGE_QCOW2) + output_qcow2_meta_data_blocks(fs, fd); + else + output_meta_data_blocks(fs, fd, flags); + + ext2fs_free_mem(&block_buf); + ext2fs_close_inode_scan(scan); + ext2fs_free_block_bitmap(meta_block_map); + if (type & E2IMAGE_SCRAMBLE_FLAG) + ext2fs_free_block_bitmap(scramble_block_map); +} + +static void install_image(char *device, char *image_fn, int type) +{ + errcode_t retval; + ext2_filsys fs; + int open_flag = EXT2_FLAG_IMAGE_FILE | EXT2_FLAG_64BITS | + EXT2_FLAG_IGNORE_CSUM_ERRORS; + int fd = 0; + io_manager io_ptr; + io_channel io; + + if (type) { + com_err(program_name, 0, "%s", + _("Raw and qcow2 images cannot be installed")); + exit(1); + } + +#ifdef CONFIG_TESTIO_DEBUG + if (getenv("TEST_IO_FLAGS") || getenv("TEST_IO_BLOCK")) { + io_ptr = test_io_manager; + test_io_backing_manager = unix_io_manager; + } else +#endif + io_ptr = unix_io_manager; + + retval = ext2fs_open (image_fn, open_flag, 0, 0, + io_ptr, &fs); + if (retval) { + com_err(program_name, retval, _("while trying to open %s"), + image_fn); + exit(1); + } + + retval = ext2fs_read_bitmaps (fs); + if (retval) { + com_err(program_name, retval, "%s", _("error reading bitmaps")); + exit(1); + } + + fd = ext2fs_open_file(image_fn, O_RDONLY, 0); + if (fd < 0) { + perror(image_fn); + exit(1); + } + + retval = io_ptr->open(device, IO_FLAG_RW, &io); + if (retval) { + com_err(device, 0, "%s", _("while opening device file")); + exit(1); + } + + ext2fs_rewrite_to_io(fs, io); + + seek_set(fd, ext2fs_le32_to_cpu(fs->image_header->offset_inode)); + + retval = ext2fs_image_inode_read(fs, fd, 0); + if (retval) { + com_err(image_fn, 0, "%s", + _("while restoring the image table")); + exit(1); + } + + close(fd); + ext2fs_close_free(&fs); +} + +static struct ext2_qcow2_hdr *check_qcow2_image(int *fd, char *name) +{ + + *fd = ext2fs_open_file(name, O_RDONLY, 0600); + if (*fd < 0) + return NULL; + + return qcow2_read_header(*fd); +} + +int main (int argc, char ** argv) +{ + int c; + errcode_t retval; + ext2_filsys fs; + char *image_fn, offset_opt[64]; + struct ext2_qcow2_hdr *header = NULL; + int open_flag = EXT2_FLAG_64BITS | EXT2_FLAG_THREADS | + EXT2_FLAG_IGNORE_CSUM_ERRORS; + int img_type = 0; + int flags = 0; + int mount_flags = 0; + int qcow2_fd = 0; + int fd = 0; + int ret = 0; + int ignore_rw_mount = 0; + int check = 0; + struct stat st; + blk64_t superblock = 0; + int blocksize = 0; + +#ifdef ENABLE_NLS + setlocale(LC_MESSAGES, ""); + setlocale(LC_CTYPE, ""); + bindtextdomain(NLS_CAT_NAME, LOCALEDIR); + textdomain(NLS_CAT_NAME); + set_com_err_gettext(gettext); +#endif + fprintf (stderr, "e2image %s (%s)\n", E2FSPROGS_VERSION, + E2FSPROGS_DATE); + if (argc && *argv) + program_name = *argv; + else + usage(); + add_error_table(&et_ext2_error_table); + while ((c = getopt(argc, argv, "b:B:nrsIQafo:O:pc")) != EOF) + switch (c) { + case 'b': + superblock = strtoull(optarg, NULL, 0); + break; + case 'B': + blocksize = strtoul(optarg, NULL, 0); + break; + case 'I': + flags |= E2IMAGE_INSTALL_FLAG; + break; + case 'Q': + if (img_type) + usage(); + img_type |= E2IMAGE_QCOW2; + break; + case 'r': + if (img_type) + usage(); + img_type |= E2IMAGE_RAW; + break; + case 's': + flags |= E2IMAGE_SCRAMBLE_FLAG; + break; + case 'a': + all_data = 1; + break; + case 'f': + ignore_rw_mount = 1; + break; + case 'n': + nop_flag = 1; + break; + case 'o': + source_offset = strtoull(optarg, NULL, 0); + break; + case 'O': + dest_offset = strtoull(optarg, NULL, 0); + break; + case 'p': + show_progress = 1; + break; + case 'c': + check = 1; + break; + default: + usage(); + } + if (optind == argc - 1 && + (source_offset || dest_offset)) + move_mode = 1; + else if (optind != argc - 2 ) + usage(); + + if (all_data && !img_type) { + com_err(program_name, 0, "%s", _("-a option can only be used " + "with raw or QCOW2 images.")); + exit(1); + } + if (superblock && !img_type) { + com_err(program_name, 0, "%s", _("-b option can only be used " + "with raw or QCOW2 images.")); + exit(1); + } + if ((source_offset || dest_offset) && img_type != E2IMAGE_RAW) { + com_err(program_name, 0, "%s", + _("Offsets are only allowed with raw images.")); + exit(1); + } + if (move_mode && img_type != E2IMAGE_RAW) { + com_err(program_name, 0, "%s", + _("Move mode is only allowed with raw images.")); + exit(1); + } + if (move_mode && !all_data) { + com_err(program_name, 0, "%s", + _("Move mode requires all data mode.")); + exit(1); + } + device_name = argv[optind]; + if (move_mode) + image_fn = device_name; + else image_fn = argv[optind+1]; + + retval = ext2fs_check_if_mounted(device_name, &mount_flags); + if (retval) { + com_err(program_name, retval, "%s", _("checking if mounted")); + exit(1); + } + + if (img_type && !ignore_rw_mount && + (mount_flags & EXT2_MF_MOUNTED) && + !(mount_flags & EXT2_MF_READONLY)) { + fprintf(stderr, "%s", _("\nRunning e2image on a R/W mounted " + "filesystem can result in an\n" + "inconsistent image which will not be useful " + "for debugging purposes.\n" + "Use -f option if you really want to do that.\n")); + exit(1); + } + + if (flags & E2IMAGE_INSTALL_FLAG) { + install_image(device_name, image_fn, img_type); + exit (0); + } + + if (img_type & E2IMAGE_RAW) { + header = check_qcow2_image(&qcow2_fd, device_name); + if (header) { + flags |= E2IMAGE_IS_QCOW2_FLAG; + goto skip_device; + } + } + sprintf(offset_opt, "offset=%llu", (unsigned long long) source_offset); + retval = ext2fs_open2(device_name, offset_opt, open_flag, + superblock, blocksize, unix_io_manager, &fs); + if (retval) { + com_err (program_name, retval, _("while trying to open %s"), + device_name); + fputs(_("Couldn't find valid filesystem superblock.\n"), stdout); + if (retval == EXT2_ET_BAD_MAGIC) + check_plausibility(device_name, CHECK_FS_EXIST, NULL); + exit(1); + } + +skip_device: + if (strcmp(image_fn, "-") == 0) + fd = 1; + else { + int o_flags = O_CREAT|O_RDWR; + + if (img_type != E2IMAGE_RAW) + o_flags |= O_TRUNC; + if (access(image_fn, F_OK) != 0) + flags |= E2IMAGE_CHECK_ZERO_FLAG; + fd = ext2fs_open_file(image_fn, o_flags, 0600); + if (fd < 0) { + com_err(program_name, errno, + _("while trying to open %s"), image_fn); + exit(1); + } + } + if (dest_offset) + seek_set(fd, dest_offset); + + if ((img_type & E2IMAGE_QCOW2) && (fd == 1)) { + com_err(program_name, 0, "%s", + _("QCOW2 image can not be written to the stdout!\n")); + exit(1); + } + if (fd != 1) { + if (fstat(fd, &st)) { + com_err(program_name, 0, "%s", + _("Can not stat output\n")); + exit(1); + } + if (ext2fsP_is_disk_device(st.st_mode)) + output_is_blk = 1; + } + if (flags & E2IMAGE_IS_QCOW2_FLAG) { + ret = qcow2_write_raw_image(qcow2_fd, fd, header); + if (ret) { + if (ret == -QCOW_COMPRESSED) + fprintf(stderr, _("Image (%s) is compressed\n"), + image_fn); + else if (ret == -QCOW_ENCRYPTED) + fprintf(stderr, _("Image (%s) is encrypted\n"), + image_fn); + else if (ret == -QCOW_CORRUPTED) + fprintf(stderr, _("Image (%s) is corrupted\n"), + image_fn); + else + com_err(program_name, ret, + _("while trying to convert qcow2 image" + " (%s) into raw image (%s)"), + image_fn, device_name); + ret = 1; + } + goto out; + } + + if (check) { + if (img_type != E2IMAGE_RAW) { + fprintf(stderr, "%s", _("The -c option only supported " + "in raw mode\n")); + exit(1); + } + if (fd == 1) { + fprintf(stderr, "%s", _("The -c option not supported " + "when writing to stdout\n")); + exit(1); + } + retval = ext2fs_get_mem(fs->blocksize, &check_buf); + if (retval) { + com_err(program_name, retval, "%s", + _("while allocating check_buf")); + exit(1); + } + } + if (show_progress && (img_type != E2IMAGE_RAW)) { + fprintf(stderr, "%s", + _("The -p option only supported in raw mode\n")); + exit(1); + } + if (img_type) + write_raw_image_file(fs, fd, img_type, flags, superblock); + else + write_image_file(fs, fd); + + ext2fs_close_free(&fs); + if (check) + printf(_("%d blocks already contained the data to be copied\n"), + skipped_blocks); + +out: + if (header) + free(header); + if (qcow2_fd) + close(qcow2_fd); + remove_error_table(&et_ext2_error_table); + return ret; +} -- cgit v1.2.3