| /* |
| * 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% |
| */ |
| |
| #define _LARGEFILE_SOURCE |
| #define _LARGEFILE64_SOURCE |
| |
| #include "config.h" |
| #include <fcntl.h> |
| #include <grp.h> |
| #ifdef HAVE_GETOPT_H |
| #include <getopt.h> |
| #else |
| extern char *optarg; |
| extern int optind; |
| #endif |
| #include <pwd.h> |
| #include <stdio.h> |
| #ifdef HAVE_STDLIB_H |
| #include <stdlib.h> |
| #endif |
| #include <string.h> |
| #include <time.h> |
| #include <unistd.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <sys/stat.h> |
| #include <sys/types.h> |
| #include <assert.h> |
| #include <signal.h> |
| |
| #include "ext2fs/ext2_fs.h" |
| #include "ext2fs/ext2fs.h" |
| #include "et/com_err.h" |
| #include "uuid/uuid.h" |
| #include "e2p/e2p.h" |
| #include "ext2fs/e2image.h" |
| #include "ext2fs/qcow2.h" |
| |
| #include "../version.h" |
| #include "nls-enable.h" |
| |
| #define QCOW_OFLAG_COPIED (1LL << 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 ] [ -fr ] 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"), 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 = 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 = 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 = 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 = 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 = 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 = fs->blocksize; |
| |
| if (stat(device_name, &st) == 0) |
| hdr.fs_device = st.st_rdev; |
| |
| if (fstat(fd, &st) == 0) { |
| hdr.image_device = st.st_dev; |
| hdr.image_inode = st.st_ino; |
| } |
| memcpy(hdr.fs_uuid, fs->super->s_uuid, sizeof(hdr.fs_uuid)); |
| |
| hdr.image_time = 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 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; |
| |
| 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 && |
| EXT2_HAS_RO_COMPAT_FEATURE(fs->super, |
| EXT4_FEATURE_RO_COMPAT_GDT_CSUM)) |
| 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) / ((1024 * 1024) / (b))) / (d)) |
| |
| static int print_progress(blk64_t num, blk64_t total) |
| { |
| return fprintf(stderr, _("%llu / %llu blocks (%d%%)"), num, 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"), 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 "), |
| total_written, 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 int 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; |
| |
| /* 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 += 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 sequentialy. |
| */ |
| 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"), 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; |
| } |
| } |
| 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) |
| { |
| 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); |
| } |
| } |
| |
| mark_table_blocks(fs); |
| if (show_progress) |
| printf("%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) && |
| ext2fs_inode_has_valid_blocks2(fs, &inode)) || |
| ino == fs->super->s_journal_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; |
| 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, 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 (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; |
| 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; |
| |
| #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; |
| add_error_table(&et_ext2_error_table); |
| while ((c = getopt(argc, argv, "nrsIQafo:O:pc")) != EOF) |
| switch (c) { |
| 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 ((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", source_offset); |
| retval = ext2fs_open2(device_name, offset_opt, open_flag, 0, 0, |
| 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); |
| 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 (S_ISBLK(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); |
| if (ret == -QCOW_ENCRYPTED) |
| fprintf(stderr, _("Image (%s) is encrypted\n"), |
| image_fn); |
| com_err(program_name, ret, |
| _("while trying to convert qcow2 image" |
| " (%s) into raw image (%s)"), |
| device_name, image_fn); |
| } |
| 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); |
| else |
| write_image_file(fs, fd); |
| |
| ext2fs_close (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; |
| } |