Skip to content

Commit 78cead5

Browse files
committed
Remove limitation on blocksize
Add a `block_size` parameter to `CowUtil.create` and implement it for QCOW2. Add a check for the parent blocksize for snapshot, normal qemu-img behavior is to use default blocksize (i.e. 64k) for blocksize even if the parent has another blocksize. Signed-off-by: Damien Thenot <damien.thenot@vates.tech>
1 parent 37aafac commit 78cead5

3 files changed

Lines changed: 22 additions & 19 deletions

File tree

drivers/cowutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ def coalesce(self, path: str) -> int:
219219
pass
220220

221221
@abstractmethod
222-
def create(self, path: str, size: int, static: bool, msize: int = 0) -> None:
222+
def create(self, path: str, size: int, static: bool, msize: int = 0, block_size: Optional[int] = None) -> None:
223223
pass
224224

225225
@abstractmethod

drivers/qcow2util.py

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818

1919
MAX_QCOW_CHAIN_LENGTH: Final = 30
2020

21-
QCOW_CLUSTER_SIZE: Final = 64 * 1024 # 64 KiB
21+
QCOW2_DEFAULT_CLUSTER_SIZE: Final = 64 * 1024 # 64 KiB
2222

23-
MIN_QCOW_SIZE: Final = QCOW_CLUSTER_SIZE
23+
MIN_QCOW_SIZE: Final = QCOW2_DEFAULT_CLUSTER_SIZE
2424

2525
MAX_QCOW_SIZE: Final = 16 * 1024 * 1024 * 1024 * 1024
2626

@@ -37,7 +37,7 @@ class QCowUtil(CowUtil):
3737

3838
QCOW2_MAGIC = 0x514649FB # b"QFI\xfb": Magic number for QCOW2 files
3939
QCOW2_HEADER_SIZE = 104 # In fact the last information we need is at offset 40-47
40-
QCOW2_L2_SIZE = QCOW_CLUSTER_SIZE
40+
QCOW2_L2_SIZE = QCOW2_DEFAULT_CLUSTER_SIZE
4141
QCOW2_BACKING_FILE_OFFSET = 8
4242

4343
ALLOCATED_ENTRY_BIT = (
@@ -175,9 +175,6 @@ def _read_qcow2_header(file: BinaryIO) -> Dict[str, Any]:
175175
if magic != QCowUtil.QCOW2_MAGIC:
176176
raise ValueError("Not a valid QCOW2 file")
177177

178-
if cluster_bits != 16:
179-
raise ValueError("Only default cluster size of 64K is supported")
180-
181178
parent_name = QCowUtil._read_qcow2_backingfile(file, backing_file_offset, backing_file_size)
182179

183180
return {
@@ -378,7 +375,7 @@ def _add_or_find_custom_header(self) -> int:
378375

379376
def _set_l1_zero(self):
380377
zero = int(0).to_bytes(1, "little")
381-
nb_of_entries_per_cluster = QCOW_CLUSTER_SIZE/8
378+
nb_of_entries_per_cluster = QCOW2_DEFAULT_CLUSTER_SIZE/8
382379
return list(zero * int(nb_of_entries_per_cluster/8))
383380

384381
def _set_l2_zero(self, b, i):
@@ -423,7 +420,8 @@ def getMaxImageSize(self) -> int:
423420

424421
@override
425422
def getBlockSize(self, path: str) -> int:
426-
return QCOW_CLUSTER_SIZE
423+
self._read_qcow2(path)
424+
return 1 << self.header["cluster_bits"]
427425

428426
@override
429427
def getFooterSize(self) -> int:
@@ -439,13 +437,13 @@ def getMaxChainLength(self) -> int:
439437

440438
@override
441439
def calcOverheadEmpty(self, virtual_size: int) -> int:
442-
size_l1 = QCOW_CLUSTER_SIZE
443-
size_header = QCOW_CLUSTER_SIZE
444-
size_l2 = (virtual_size * 8) / QCOW_CLUSTER_SIZE #It is only an estimation
440+
size_l1 = QCOW2_DEFAULT_CLUSTER_SIZE
441+
size_header = QCOW2_DEFAULT_CLUSTER_SIZE
442+
size_l2 = (virtual_size * 8) / QCOW2_DEFAULT_CLUSTER_SIZE #It is only an estimation
445443

446444
size = size_l1 + size_l2 + size_header
447445

448-
return util.roundup(QCOW_CLUSTER_SIZE, size)
446+
return util.roundup(QCOW2_DEFAULT_CLUSTER_SIZE, size)
449447

450448
@override
451449
def calcOverheadBitmap(self, virtual_size: int) -> int:
@@ -767,10 +765,12 @@ def coalesce(self, path: str) -> int:
767765
return allocated_blocks
768766

769767
@override
770-
def create(self, path: str, size: int, static: bool, msize: int = 0) -> None:
768+
def create(self, path: str, size: int, static: bool, msize: int = 0, block_size: Optional[int] = None) -> None:
771769
cmd = [QEMU_IMG, "create", "-f", QCOW2_TYPE, path, str(size)]
772770
if static:
773771
cmd.extend(["-o", "preallocation=full"])
772+
if block_size:
773+
cmd.extend(["-o", f"cluster_size={str(block_size)}"])
774774
self._ioretry(cmd)
775775
self.setHidden(path, False) #We add hidden header at creation
776776

@@ -783,11 +783,14 @@ def snapshot(
783783
msize: int = 0,
784784
checkEmpty: bool = True
785785
) -> None:
786-
parent_type = QCOW2_TYPE
787786
if parentRaw:
788787
parent_type = RAW_TYPE
789-
# TODO: checkEmpty? If it is False, then the parent could be empty and should still be used for snapshot
790-
cmd = [QEMU_IMG, "create", "-f", QCOW2_TYPE, "-b", parent, "-F", parent_type, path]
788+
parent_cluster_size = QCOW2_DEFAULT_CLUSTER_SIZE
789+
else:
790+
parent_type = QCOW2_TYPE
791+
parent_cluster_size = self.getBlockSize(parent)
792+
793+
cmd = [QEMU_IMG, "create", "-f", QCOW2_TYPE, "-b", parent, "-F", parent_type, "-o", f"cluster_size={parent_cluster_size}", path]
791794
self._ioretry(cmd)
792795
self.setHidden(path, False) #We add hidden header at creation
793796

@@ -832,7 +835,7 @@ def validateAndRoundImageSize(self, size: int) -> int:
832835
opterr="VDI size must be between {} MB and {} MB".format(MIN_QCOW_SIZE // (1024*1024), MAX_QCOW_SIZE // (1024 * 1024))
833836
)
834837

835-
return util.roundup(QCOW_CLUSTER_SIZE, size)
838+
return util.roundup(QCOW2_DEFAULT_CLUSTER_SIZE, size)
836839

837840
@override
838841
def getKeyHash(self, path: str) -> Optional[str]:

drivers/vhdutil.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ def coalesce(self, path: str) -> int:
340340
return 0
341341

342342
@override
343-
def create(self, path: str, size: int, static: bool, msize: int = 0) -> None:
343+
def create(self, path: str, size: int, static: bool, msize: int = 0, block_size: Optional[int] = None) -> None:
344344
cmd = [VHD_UTIL, "create", OPT_LOG_ERR, "-n", path, "-s", str(size // (1024 * 1024))]
345345
if static:
346346
cmd.append("-r")

0 commit comments

Comments
 (0)