2
0

qcow2-to-stdout.py 17 KB

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