1818
1919MAX_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
2525MAX_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 ]:
0 commit comments