1*df957115SAlberto Garcia#!/usr/bin/env python3 2*df957115SAlberto Garcia 3*df957115SAlberto Garcia# This tool reads a disk image in any format and converts it to qcow2, 4*df957115SAlberto Garcia# writing the result directly to stdout. 5*df957115SAlberto Garcia# 6*df957115SAlberto Garcia# Copyright (C) 2024 Igalia, S.L. 7*df957115SAlberto Garcia# 8*df957115SAlberto Garcia# Authors: Alberto Garcia <berto@igalia.com> 9*df957115SAlberto Garcia# Madeeha Javed <javed@igalia.com> 10*df957115SAlberto Garcia# 11*df957115SAlberto Garcia# SPDX-License-Identifier: GPL-2.0-or-later 12*df957115SAlberto Garcia# 13*df957115SAlberto Garcia# qcow2 files produced by this script are always arranged like this: 14*df957115SAlberto Garcia# 15*df957115SAlberto Garcia# - qcow2 header 16*df957115SAlberto Garcia# - refcount table 17*df957115SAlberto Garcia# - refcount blocks 18*df957115SAlberto Garcia# - L1 table 19*df957115SAlberto Garcia# - L2 tables 20*df957115SAlberto Garcia# - Data clusters 21*df957115SAlberto Garcia# 22*df957115SAlberto Garcia# A note about variable names: in qcow2 there is one refcount table 23*df957115SAlberto Garcia# and one (active) L1 table, although each can occupy several 24*df957115SAlberto Garcia# clusters. For the sake of simplicity the code sometimes talks about 25*df957115SAlberto Garcia# refcount tables and L1 tables when referring to those clusters. 26*df957115SAlberto Garcia 27*df957115SAlberto Garciaimport argparse 28*df957115SAlberto Garciaimport errno 29*df957115SAlberto Garciaimport math 30*df957115SAlberto Garciaimport os 31*df957115SAlberto Garciaimport signal 32*df957115SAlberto Garciaimport struct 33*df957115SAlberto Garciaimport subprocess 34*df957115SAlberto Garciaimport sys 35*df957115SAlberto Garciaimport tempfile 36*df957115SAlberto Garciaimport time 37*df957115SAlberto Garciafrom contextlib import contextmanager 38*df957115SAlberto Garcia 39*df957115SAlberto GarciaQCOW2_DEFAULT_CLUSTER_SIZE = 65536 40*df957115SAlberto GarciaQCOW2_DEFAULT_REFCOUNT_BITS = 16 41*df957115SAlberto GarciaQCOW2_FEATURE_NAME_TABLE = 0x6803F857 42*df957115SAlberto GarciaQCOW2_DATA_FILE_NAME_STRING = 0x44415441 43*df957115SAlberto GarciaQCOW2_V3_HEADER_LENGTH = 112 # Header length in QEMU 9.0. Must be a multiple of 8 44*df957115SAlberto GarciaQCOW2_INCOMPAT_DATA_FILE_BIT = 2 45*df957115SAlberto GarciaQCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT = 1 46*df957115SAlberto GarciaQCOW_OFLAG_COPIED = 1 << 63 47*df957115SAlberto GarciaQEMU_STORAGE_DAEMON = "qemu-storage-daemon" 48*df957115SAlberto Garcia 49*df957115SAlberto Garcia 50*df957115SAlberto Garciadef bitmap_set(bitmap, idx): 51*df957115SAlberto Garcia bitmap[idx // 8] |= 1 << (idx % 8) 52*df957115SAlberto Garcia 53*df957115SAlberto Garcia 54*df957115SAlberto Garciadef bitmap_is_set(bitmap, idx): 55*df957115SAlberto Garcia return (bitmap[idx // 8] & (1 << (idx % 8))) != 0 56*df957115SAlberto Garcia 57*df957115SAlberto Garcia 58*df957115SAlberto Garciadef bitmap_iterator(bitmap, length): 59*df957115SAlberto Garcia for idx in range(length): 60*df957115SAlberto Garcia if bitmap_is_set(bitmap, idx): 61*df957115SAlberto Garcia yield idx 62*df957115SAlberto Garcia 63*df957115SAlberto Garcia 64*df957115SAlberto Garciadef align_up(num, d): 65*df957115SAlberto Garcia return d * math.ceil(num / d) 66*df957115SAlberto Garcia 67*df957115SAlberto Garcia 68*df957115SAlberto Garcia# Holes in the input file contain only zeroes so we can skip them and 69*df957115SAlberto Garcia# save time. This function returns the indexes of the clusters that 70*df957115SAlberto Garcia# are known to contain data. Those are the ones that we need to read. 71*df957115SAlberto Garciadef clusters_with_data(fd, cluster_size): 72*df957115SAlberto Garcia data_to = 0 73*df957115SAlberto Garcia while True: 74*df957115SAlberto Garcia try: 75*df957115SAlberto Garcia data_from = os.lseek(fd, data_to, os.SEEK_DATA) 76*df957115SAlberto Garcia data_to = align_up(os.lseek(fd, data_from, os.SEEK_HOLE), cluster_size) 77*df957115SAlberto Garcia for idx in range(data_from // cluster_size, data_to // cluster_size): 78*df957115SAlberto Garcia yield idx 79*df957115SAlberto Garcia except OSError as err: 80*df957115SAlberto Garcia if err.errno == errno.ENXIO: # End of file reached 81*df957115SAlberto Garcia break 82*df957115SAlberto Garcia raise err 83*df957115SAlberto Garcia 84*df957115SAlberto Garcia 85*df957115SAlberto Garcia# write_qcow2_content() expects a raw input file. If we have a different 86*df957115SAlberto Garcia# format we can use qemu-storage-daemon to make it appear as raw. 87*df957115SAlberto Garcia@contextmanager 88*df957115SAlberto Garciadef get_input_as_raw_file(input_file, input_format): 89*df957115SAlberto Garcia if input_format == "raw": 90*df957115SAlberto Garcia yield input_file 91*df957115SAlberto Garcia return 92*df957115SAlberto Garcia try: 93*df957115SAlberto Garcia temp_dir = tempfile.mkdtemp() 94*df957115SAlberto Garcia pid_file = os.path.join(temp_dir, "pid") 95*df957115SAlberto Garcia raw_file = os.path.join(temp_dir, "raw") 96*df957115SAlberto Garcia open(raw_file, "wb").close() 97*df957115SAlberto Garcia ret = subprocess.run( 98*df957115SAlberto Garcia [ 99*df957115SAlberto Garcia QEMU_STORAGE_DAEMON, 100*df957115SAlberto Garcia "--daemonize", 101*df957115SAlberto Garcia "--pidfile", pid_file, 102*df957115SAlberto Garcia "--blockdev", f"driver=file,node-name=file0,driver=file,filename={input_file},read-only=on", 103*df957115SAlberto Garcia "--blockdev", f"driver={input_format},node-name=disk0,file=file0,read-only=on", 104*df957115SAlberto Garcia "--export", f"type=fuse,id=export0,node-name=disk0,mountpoint={raw_file},writable=off", 105*df957115SAlberto Garcia ], 106*df957115SAlberto Garcia capture_output=True, 107*df957115SAlberto Garcia ) 108*df957115SAlberto Garcia if ret.returncode != 0: 109*df957115SAlberto Garcia sys.exit("[Error] Could not start the qemu-storage-daemon:\n" + 110*df957115SAlberto Garcia ret.stderr.decode().rstrip('\n')) 111*df957115SAlberto Garcia yield raw_file 112*df957115SAlberto Garcia finally: 113*df957115SAlberto Garcia # Kill the storage daemon on exit 114*df957115SAlberto Garcia # and remove all temporary files 115*df957115SAlberto Garcia if os.path.exists(pid_file): 116*df957115SAlberto Garcia with open(pid_file, "r") as f: 117*df957115SAlberto Garcia pid = int(f.readline()) 118*df957115SAlberto Garcia os.kill(pid, signal.SIGTERM) 119*df957115SAlberto Garcia while os.path.exists(pid_file): 120*df957115SAlberto Garcia time.sleep(0.1) 121*df957115SAlberto Garcia os.unlink(raw_file) 122*df957115SAlberto Garcia os.rmdir(temp_dir) 123*df957115SAlberto Garcia 124*df957115SAlberto Garcia 125*df957115SAlberto Garciadef write_features(cluster, offset, data_file_name): 126*df957115SAlberto Garcia if data_file_name is not None: 127*df957115SAlberto Garcia encoded_name = data_file_name.encode("utf-8") 128*df957115SAlberto Garcia padded_name_len = align_up(len(encoded_name), 8) 129*df957115SAlberto Garcia struct.pack_into(f">II{padded_name_len}s", cluster, offset, 130*df957115SAlberto Garcia QCOW2_DATA_FILE_NAME_STRING, 131*df957115SAlberto Garcia len(encoded_name), 132*df957115SAlberto Garcia encoded_name) 133*df957115SAlberto Garcia offset += 8 + padded_name_len 134*df957115SAlberto Garcia 135*df957115SAlberto Garcia qcow2_features = [ 136*df957115SAlberto Garcia # Incompatible 137*df957115SAlberto Garcia (0, 0, "dirty bit"), 138*df957115SAlberto Garcia (0, 1, "corrupt bit"), 139*df957115SAlberto Garcia (0, 2, "external data file"), 140*df957115SAlberto Garcia (0, 3, "compression type"), 141*df957115SAlberto Garcia (0, 4, "extended L2 entries"), 142*df957115SAlberto Garcia # Compatible 143*df957115SAlberto Garcia (1, 0, "lazy refcounts"), 144*df957115SAlberto Garcia # Autoclear 145*df957115SAlberto Garcia (2, 0, "bitmaps"), 146*df957115SAlberto Garcia (2, 1, "raw external data"), 147*df957115SAlberto Garcia ] 148*df957115SAlberto Garcia struct.pack_into(">I", cluster, offset, QCOW2_FEATURE_NAME_TABLE) 149*df957115SAlberto Garcia struct.pack_into(">I", cluster, offset + 4, len(qcow2_features) * 48) 150*df957115SAlberto Garcia offset += 8 151*df957115SAlberto Garcia for feature_type, feature_bit, feature_name in qcow2_features: 152*df957115SAlberto Garcia struct.pack_into(">BB46s", cluster, offset, 153*df957115SAlberto Garcia feature_type, feature_bit, feature_name.encode("ascii")) 154*df957115SAlberto Garcia offset += 48 155*df957115SAlberto Garcia 156*df957115SAlberto Garcia 157*df957115SAlberto Garciadef write_qcow2_content(input_file, cluster_size, refcount_bits, data_file_name, data_file_raw): 158*df957115SAlberto Garcia # Some basic values 159*df957115SAlberto Garcia l1_entries_per_table = cluster_size // 8 160*df957115SAlberto Garcia l2_entries_per_table = cluster_size // 8 161*df957115SAlberto Garcia refcounts_per_table = cluster_size // 8 162*df957115SAlberto Garcia refcounts_per_block = cluster_size * 8 // refcount_bits 163*df957115SAlberto Garcia 164*df957115SAlberto Garcia # Virtual disk size, number of data clusters and L1 entries 165*df957115SAlberto Garcia disk_size = align_up(os.path.getsize(input_file), 512) 166*df957115SAlberto Garcia total_data_clusters = math.ceil(disk_size / cluster_size) 167*df957115SAlberto Garcia l1_entries = math.ceil(total_data_clusters / l2_entries_per_table) 168*df957115SAlberto Garcia allocated_l1_tables = math.ceil(l1_entries / l1_entries_per_table) 169*df957115SAlberto Garcia 170*df957115SAlberto Garcia # Max L1 table size is 32 MB (QCOW_MAX_L1_SIZE in block/qcow2.h) 171*df957115SAlberto Garcia if (l1_entries * 8) > (32 * 1024 * 1024): 172*df957115SAlberto Garcia sys.exit("[Error] The image size is too large. Try using a larger cluster size.") 173*df957115SAlberto Garcia 174*df957115SAlberto Garcia # Two bitmaps indicating which L1 and L2 entries are set 175*df957115SAlberto Garcia l1_bitmap = bytearray(allocated_l1_tables * l1_entries_per_table // 8) 176*df957115SAlberto Garcia l2_bitmap = bytearray(l1_entries * l2_entries_per_table // 8) 177*df957115SAlberto Garcia allocated_l2_tables = 0 178*df957115SAlberto Garcia allocated_data_clusters = 0 179*df957115SAlberto Garcia 180*df957115SAlberto Garcia if data_file_raw: 181*df957115SAlberto Garcia # If data_file_raw is set then all clusters are allocated and 182*df957115SAlberto Garcia # we don't need to read the input file at all. 183*df957115SAlberto Garcia allocated_l2_tables = l1_entries 184*df957115SAlberto Garcia for idx in range(l1_entries): 185*df957115SAlberto Garcia bitmap_set(l1_bitmap, idx) 186*df957115SAlberto Garcia for idx in range(total_data_clusters): 187*df957115SAlberto Garcia bitmap_set(l2_bitmap, idx) 188*df957115SAlberto Garcia else: 189*df957115SAlberto Garcia # Open the input file for reading 190*df957115SAlberto Garcia fd = os.open(input_file, os.O_RDONLY) 191*df957115SAlberto Garcia zero_cluster = bytes(cluster_size) 192*df957115SAlberto Garcia # Read all the clusters that contain data 193*df957115SAlberto Garcia for idx in clusters_with_data(fd, cluster_size): 194*df957115SAlberto Garcia cluster = os.pread(fd, cluster_size, cluster_size * idx) 195*df957115SAlberto Garcia # If the last cluster is smaller than cluster_size pad it with zeroes 196*df957115SAlberto Garcia if len(cluster) < cluster_size: 197*df957115SAlberto Garcia cluster += bytes(cluster_size - len(cluster)) 198*df957115SAlberto Garcia # If a cluster has non-zero data then it must be allocated 199*df957115SAlberto Garcia # in the output file and its L2 entry must be set 200*df957115SAlberto Garcia if cluster != zero_cluster: 201*df957115SAlberto Garcia bitmap_set(l2_bitmap, idx) 202*df957115SAlberto Garcia allocated_data_clusters += 1 203*df957115SAlberto Garcia # Allocated data clusters also need their corresponding L1 entry and L2 table 204*df957115SAlberto Garcia l1_idx = math.floor(idx / l2_entries_per_table) 205*df957115SAlberto Garcia if not bitmap_is_set(l1_bitmap, l1_idx): 206*df957115SAlberto Garcia bitmap_set(l1_bitmap, l1_idx) 207*df957115SAlberto Garcia allocated_l2_tables += 1 208*df957115SAlberto Garcia 209*df957115SAlberto Garcia # Total amount of allocated clusters excluding the refcount blocks and table 210*df957115SAlberto Garcia total_allocated_clusters = 1 + allocated_l1_tables + allocated_l2_tables 211*df957115SAlberto Garcia if data_file_name is None: 212*df957115SAlberto Garcia total_allocated_clusters += allocated_data_clusters 213*df957115SAlberto Garcia 214*df957115SAlberto Garcia # Clusters allocated for the refcount blocks and table 215*df957115SAlberto Garcia allocated_refcount_blocks = math.ceil(total_allocated_clusters / refcounts_per_block) 216*df957115SAlberto Garcia allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table) 217*df957115SAlberto Garcia 218*df957115SAlberto Garcia # Now we have a problem because allocated_refcount_blocks and allocated_refcount_tables... 219*df957115SAlberto Garcia # (a) increase total_allocated_clusters, and 220*df957115SAlberto Garcia # (b) need to be recalculated when total_allocated_clusters is increased 221*df957115SAlberto Garcia # So we need to repeat the calculation as long as the numbers change 222*df957115SAlberto Garcia while True: 223*df957115SAlberto Garcia new_total_allocated_clusters = total_allocated_clusters + allocated_refcount_tables + allocated_refcount_blocks 224*df957115SAlberto Garcia new_allocated_refcount_blocks = math.ceil(new_total_allocated_clusters / refcounts_per_block) 225*df957115SAlberto Garcia if new_allocated_refcount_blocks > allocated_refcount_blocks: 226*df957115SAlberto Garcia allocated_refcount_blocks = new_allocated_refcount_blocks 227*df957115SAlberto Garcia allocated_refcount_tables = math.ceil(allocated_refcount_blocks / refcounts_per_table) 228*df957115SAlberto Garcia else: 229*df957115SAlberto Garcia break 230*df957115SAlberto Garcia 231*df957115SAlberto Garcia # Now that we have the final numbers we can update total_allocated_clusters 232*df957115SAlberto Garcia total_allocated_clusters += allocated_refcount_tables + allocated_refcount_blocks 233*df957115SAlberto Garcia 234*df957115SAlberto Garcia # At this point we have the exact number of clusters that the output 235*df957115SAlberto Garcia # image is going to use so we can calculate all the offsets. 236*df957115SAlberto Garcia current_cluster_idx = 1 237*df957115SAlberto Garcia 238*df957115SAlberto Garcia refcount_table_offset = current_cluster_idx * cluster_size 239*df957115SAlberto Garcia current_cluster_idx += allocated_refcount_tables 240*df957115SAlberto Garcia 241*df957115SAlberto Garcia refcount_block_offset = current_cluster_idx * cluster_size 242*df957115SAlberto Garcia current_cluster_idx += allocated_refcount_blocks 243*df957115SAlberto Garcia 244*df957115SAlberto Garcia l1_table_offset = current_cluster_idx * cluster_size 245*df957115SAlberto Garcia current_cluster_idx += allocated_l1_tables 246*df957115SAlberto Garcia 247*df957115SAlberto Garcia l2_table_offset = current_cluster_idx * cluster_size 248*df957115SAlberto Garcia current_cluster_idx += allocated_l2_tables 249*df957115SAlberto Garcia 250*df957115SAlberto Garcia data_clusters_offset = current_cluster_idx * cluster_size 251*df957115SAlberto Garcia 252*df957115SAlberto Garcia # Calculate some values used in the qcow2 header 253*df957115SAlberto Garcia if allocated_l1_tables == 0: 254*df957115SAlberto Garcia l1_table_offset = 0 255*df957115SAlberto Garcia 256*df957115SAlberto Garcia hdr_cluster_bits = int(math.log2(cluster_size)) 257*df957115SAlberto Garcia hdr_refcount_bits = int(math.log2(refcount_bits)) 258*df957115SAlberto Garcia hdr_length = QCOW2_V3_HEADER_LENGTH 259*df957115SAlberto Garcia hdr_incompat_features = 0 260*df957115SAlberto Garcia if data_file_name is not None: 261*df957115SAlberto Garcia hdr_incompat_features |= 1 << QCOW2_INCOMPAT_DATA_FILE_BIT 262*df957115SAlberto Garcia hdr_autoclear_features = 0 263*df957115SAlberto Garcia if data_file_raw: 264*df957115SAlberto Garcia hdr_autoclear_features |= 1 << QCOW2_AUTOCLEAR_DATA_FILE_RAW_BIT 265*df957115SAlberto Garcia 266*df957115SAlberto Garcia ### Write qcow2 header 267*df957115SAlberto Garcia cluster = bytearray(cluster_size) 268*df957115SAlberto Garcia struct.pack_into(">4sIQIIQIIQQIIQQQQII", cluster, 0, 269*df957115SAlberto Garcia b"QFI\xfb", # QCOW magic string 270*df957115SAlberto Garcia 3, # version 271*df957115SAlberto Garcia 0, # backing file offset 272*df957115SAlberto Garcia 0, # backing file sizes 273*df957115SAlberto Garcia hdr_cluster_bits, 274*df957115SAlberto Garcia disk_size, 275*df957115SAlberto Garcia 0, # encryption method 276*df957115SAlberto Garcia l1_entries, 277*df957115SAlberto Garcia l1_table_offset, 278*df957115SAlberto Garcia refcount_table_offset, 279*df957115SAlberto Garcia allocated_refcount_tables, 280*df957115SAlberto Garcia 0, # number of snapshots 281*df957115SAlberto Garcia 0, # snapshot table offset 282*df957115SAlberto Garcia hdr_incompat_features, 283*df957115SAlberto Garcia 0, # compatible features 284*df957115SAlberto Garcia hdr_autoclear_features, 285*df957115SAlberto Garcia hdr_refcount_bits, 286*df957115SAlberto Garcia hdr_length, 287*df957115SAlberto Garcia ) 288*df957115SAlberto Garcia 289*df957115SAlberto Garcia write_features(cluster, hdr_length, data_file_name) 290*df957115SAlberto Garcia 291*df957115SAlberto Garcia sys.stdout.buffer.write(cluster) 292*df957115SAlberto Garcia 293*df957115SAlberto Garcia ### Write refcount table 294*df957115SAlberto Garcia cur_offset = refcount_block_offset 295*df957115SAlberto Garcia remaining_refcount_table_entries = allocated_refcount_blocks # Each entry is a pointer to a refcount block 296*df957115SAlberto Garcia while remaining_refcount_table_entries > 0: 297*df957115SAlberto Garcia cluster = bytearray(cluster_size) 298*df957115SAlberto Garcia to_write = min(remaining_refcount_table_entries, refcounts_per_table) 299*df957115SAlberto Garcia remaining_refcount_table_entries -= to_write 300*df957115SAlberto Garcia for idx in range(to_write): 301*df957115SAlberto Garcia struct.pack_into(">Q", cluster, idx * 8, cur_offset) 302*df957115SAlberto Garcia cur_offset += cluster_size 303*df957115SAlberto Garcia sys.stdout.buffer.write(cluster) 304*df957115SAlberto Garcia 305*df957115SAlberto Garcia ### Write refcount blocks 306*df957115SAlberto Garcia remaining_refcount_block_entries = total_allocated_clusters # One entry for each allocated cluster 307*df957115SAlberto Garcia for tbl in range(allocated_refcount_blocks): 308*df957115SAlberto Garcia cluster = bytearray(cluster_size) 309*df957115SAlberto Garcia to_write = min(remaining_refcount_block_entries, refcounts_per_block) 310*df957115SAlberto Garcia remaining_refcount_block_entries -= to_write 311*df957115SAlberto Garcia # All refcount entries contain the number 1. The only difference 312*df957115SAlberto Garcia # is their bit width, defined when the image is created. 313*df957115SAlberto Garcia for idx in range(to_write): 314*df957115SAlberto Garcia if refcount_bits == 64: 315*df957115SAlberto Garcia struct.pack_into(">Q", cluster, idx * 8, 1) 316*df957115SAlberto Garcia elif refcount_bits == 32: 317*df957115SAlberto Garcia struct.pack_into(">L", cluster, idx * 4, 1) 318*df957115SAlberto Garcia elif refcount_bits == 16: 319*df957115SAlberto Garcia struct.pack_into(">H", cluster, idx * 2, 1) 320*df957115SAlberto Garcia elif refcount_bits == 8: 321*df957115SAlberto Garcia cluster[idx] = 1 322*df957115SAlberto Garcia elif refcount_bits == 4: 323*df957115SAlberto Garcia cluster[idx // 2] |= 1 << ((idx % 2) * 4) 324*df957115SAlberto Garcia elif refcount_bits == 2: 325*df957115SAlberto Garcia cluster[idx // 4] |= 1 << ((idx % 4) * 2) 326*df957115SAlberto Garcia elif refcount_bits == 1: 327*df957115SAlberto Garcia cluster[idx // 8] |= 1 << (idx % 8) 328*df957115SAlberto Garcia sys.stdout.buffer.write(cluster) 329*df957115SAlberto Garcia 330*df957115SAlberto Garcia ### Write L1 table 331*df957115SAlberto Garcia cur_offset = l2_table_offset 332*df957115SAlberto Garcia for tbl in range(allocated_l1_tables): 333*df957115SAlberto Garcia cluster = bytearray(cluster_size) 334*df957115SAlberto Garcia for idx in range(l1_entries_per_table): 335*df957115SAlberto Garcia l1_idx = tbl * l1_entries_per_table + idx 336*df957115SAlberto Garcia if bitmap_is_set(l1_bitmap, l1_idx): 337*df957115SAlberto Garcia struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED) 338*df957115SAlberto Garcia cur_offset += cluster_size 339*df957115SAlberto Garcia sys.stdout.buffer.write(cluster) 340*df957115SAlberto Garcia 341*df957115SAlberto Garcia ### Write L2 tables 342*df957115SAlberto Garcia cur_offset = data_clusters_offset 343*df957115SAlberto Garcia for tbl in range(l1_entries): 344*df957115SAlberto Garcia # Skip the empty L2 tables. We can identify them because 345*df957115SAlberto Garcia # there is no L1 entry pointing at them. 346*df957115SAlberto Garcia if bitmap_is_set(l1_bitmap, tbl): 347*df957115SAlberto Garcia cluster = bytearray(cluster_size) 348*df957115SAlberto Garcia for idx in range(l2_entries_per_table): 349*df957115SAlberto Garcia l2_idx = tbl * l2_entries_per_table + idx 350*df957115SAlberto Garcia if bitmap_is_set(l2_bitmap, l2_idx): 351*df957115SAlberto Garcia if data_file_name is None: 352*df957115SAlberto Garcia struct.pack_into(">Q", cluster, idx * 8, cur_offset | QCOW_OFLAG_COPIED) 353*df957115SAlberto Garcia cur_offset += cluster_size 354*df957115SAlberto Garcia else: 355*df957115SAlberto Garcia struct.pack_into(">Q", cluster, idx * 8, (l2_idx * cluster_size) | QCOW_OFLAG_COPIED) 356*df957115SAlberto Garcia sys.stdout.buffer.write(cluster) 357*df957115SAlberto Garcia 358*df957115SAlberto Garcia ### Write data clusters 359*df957115SAlberto Garcia if data_file_name is None: 360*df957115SAlberto Garcia for idx in bitmap_iterator(l2_bitmap, total_data_clusters): 361*df957115SAlberto Garcia cluster = os.pread(fd, cluster_size, cluster_size * idx) 362*df957115SAlberto Garcia # If the last cluster is smaller than cluster_size pad it with zeroes 363*df957115SAlberto Garcia if len(cluster) < cluster_size: 364*df957115SAlberto Garcia cluster += bytes(cluster_size - len(cluster)) 365*df957115SAlberto Garcia sys.stdout.buffer.write(cluster) 366*df957115SAlberto Garcia 367*df957115SAlberto Garcia if not data_file_raw: 368*df957115SAlberto Garcia os.close(fd) 369*df957115SAlberto Garcia 370*df957115SAlberto Garcia 371*df957115SAlberto Garciadef main(): 372*df957115SAlberto Garcia # Command-line arguments 373*df957115SAlberto Garcia parser = argparse.ArgumentParser( 374*df957115SAlberto Garcia description="This program converts a QEMU disk image to qcow2 " 375*df957115SAlberto Garcia "and writes it to the standard output" 376*df957115SAlberto Garcia ) 377*df957115SAlberto Garcia parser.add_argument("input_file", help="name of the input file") 378*df957115SAlberto Garcia parser.add_argument( 379*df957115SAlberto Garcia "-f", 380*df957115SAlberto Garcia dest="input_format", 381*df957115SAlberto Garcia metavar="input_format", 382*df957115SAlberto Garcia help="format of the input file (default: raw)", 383*df957115SAlberto Garcia default="raw", 384*df957115SAlberto Garcia ) 385*df957115SAlberto Garcia parser.add_argument( 386*df957115SAlberto Garcia "-c", 387*df957115SAlberto Garcia dest="cluster_size", 388*df957115SAlberto Garcia metavar="cluster_size", 389*df957115SAlberto Garcia help=f"qcow2 cluster size (default: {QCOW2_DEFAULT_CLUSTER_SIZE})", 390*df957115SAlberto Garcia default=QCOW2_DEFAULT_CLUSTER_SIZE, 391*df957115SAlberto Garcia type=int, 392*df957115SAlberto Garcia choices=[1 << x for x in range(9, 22)], 393*df957115SAlberto Garcia ) 394*df957115SAlberto Garcia parser.add_argument( 395*df957115SAlberto Garcia "-r", 396*df957115SAlberto Garcia dest="refcount_bits", 397*df957115SAlberto Garcia metavar="refcount_bits", 398*df957115SAlberto Garcia help=f"width of the reference count entries (default: {QCOW2_DEFAULT_REFCOUNT_BITS})", 399*df957115SAlberto Garcia default=QCOW2_DEFAULT_REFCOUNT_BITS, 400*df957115SAlberto Garcia type=int, 401*df957115SAlberto Garcia choices=[1 << x for x in range(7)], 402*df957115SAlberto Garcia ) 403*df957115SAlberto Garcia parser.add_argument( 404*df957115SAlberto Garcia "-d", 405*df957115SAlberto Garcia dest="data_file", 406*df957115SAlberto Garcia help="create an image with input_file as an external data file", 407*df957115SAlberto Garcia action="store_true", 408*df957115SAlberto Garcia ) 409*df957115SAlberto Garcia parser.add_argument( 410*df957115SAlberto Garcia "-R", 411*df957115SAlberto Garcia dest="data_file_raw", 412*df957115SAlberto Garcia help="enable data_file_raw on the generated image (implies -d)", 413*df957115SAlberto Garcia action="store_true", 414*df957115SAlberto Garcia ) 415*df957115SAlberto Garcia args = parser.parse_args() 416*df957115SAlberto Garcia 417*df957115SAlberto Garcia if args.data_file_raw: 418*df957115SAlberto Garcia args.data_file = True 419*df957115SAlberto Garcia 420*df957115SAlberto Garcia if not os.path.isfile(args.input_file): 421*df957115SAlberto Garcia sys.exit(f"[Error] {args.input_file} does not exist or is not a regular file.") 422*df957115SAlberto Garcia 423*df957115SAlberto Garcia if args.data_file and args.input_format != "raw": 424*df957115SAlberto Garcia sys.exit("[Error] External data files can only be used with raw input images") 425*df957115SAlberto Garcia 426*df957115SAlberto Garcia # A 512 byte header is too small for the data file name extension 427*df957115SAlberto Garcia if args.data_file and args.cluster_size == 512: 428*df957115SAlberto Garcia sys.exit("[Error] External data files require a larger cluster size") 429*df957115SAlberto Garcia 430*df957115SAlberto Garcia if sys.stdout.isatty(): 431*df957115SAlberto Garcia sys.exit("[Error] Refusing to write to a tty. Try redirecting stdout.") 432*df957115SAlberto Garcia 433*df957115SAlberto Garcia if args.data_file: 434*df957115SAlberto Garcia data_file_name = args.input_file 435*df957115SAlberto Garcia else: 436*df957115SAlberto Garcia data_file_name = None 437*df957115SAlberto Garcia 438*df957115SAlberto Garcia with get_input_as_raw_file(args.input_file, args.input_format) as raw_file: 439*df957115SAlberto Garcia write_qcow2_content( 440*df957115SAlberto Garcia raw_file, 441*df957115SAlberto Garcia args.cluster_size, 442*df957115SAlberto Garcia args.refcount_bits, 443*df957115SAlberto Garcia data_file_name, 444*df957115SAlberto Garcia args.data_file_raw, 445*df957115SAlberto Garcia ) 446*df957115SAlberto Garcia 447*df957115SAlberto Garcia 448*df957115SAlberto Garciaif __name__ == "__main__": 449*df957115SAlberto Garcia main() 450