@@ -57,6 +57,7 @@ class QCowUtil(CowUtil):
5757 QCOW2_MAGIC = 0x514649FB # b"QFI\xfb": Magic number for QCOW2 files
5858 QCOW2_HEADER_SIZE = 104 # In fact the last information we need is at offset 40-47
5959 QCOW2_BACKING_FILE_OFFSET = 8
60+ QCOW2_INCOMPATIBLE_FEATURE_EXTENDED_L2 = 0x10
6061
6162 def _read_qcow2 (self , path : str ):
6263 with open (path , "rb" ) as qcow2_file :
@@ -100,6 +101,7 @@ def _read_qcow2_header(file: BinaryIO) -> Dict[str, Any]:
100101 # refcount_table_clusters: u32, // Number of clusters for the refcount table
101102 # nb_snapshots: u32, // Number of snapshots in the image
102103 # snapshots_offset: u64, // Offset to the snapshot table
104+ # incompatible_features: u64, // Bitmask of incompatible features
103105
104106 file .seek (0 )
105107 header = file .read (QCowUtil .QCOW2_HEADER_SIZE )
@@ -117,7 +119,8 @@ def _read_qcow2_header(file: BinaryIO) -> Dict[str, Any]:
117119 _ ,
118120 _ ,
119121 snapshots_offset ,
120- ) = struct .unpack (">IIQIIQIIQQIIQ" , header [:72 ])
122+ incompatible_features ,
123+ ) = struct .unpack (">IIQIIQIIQQIIQQ" , header [:80 ])
121124
122125 if magic != QCowUtil .QCOW2_MAGIC :
123126 raise ValueError ("Not a valid QCOW2 file" )
@@ -135,6 +138,7 @@ def _read_qcow2_header(file: BinaryIO) -> Dict[str, Any]:
135138 "refcount_table_offset" : refcount_table_offset ,
136139 "snapshots_offset" : snapshots_offset ,
137140 "parent" : parent_name ,
141+ "incompatible_features" : incompatible_features ,
138142 }
139143
140144 @staticmethod
@@ -246,6 +250,9 @@ def _add_or_find_custom_header(self) -> int:
246250
247251 return custom_data_offset
248252
253+ def _convert_bool_to_qemu_option_value (self , option : bool ) -> str :
254+ return 'on' if option else 'off'
255+
249256 # ----
250257 # Implementation of CowUtil
251258 # ----
@@ -263,6 +270,11 @@ def getBlockSize(self, path: str) -> int:
263270 self ._read_qcow2 (path )
264271 return 1 << self .header ["cluster_bits" ]
265272
273+ @override
274+ def getExtendedL2 (self , path : str ) -> bool :
275+ self ._read_qcow2 (path )
276+ return bool (self .header ["incompatible_features" ] & self .QCOW2_INCOMPATIBLE_FEATURE_EXTENDED_L2 )
277+
266278 @override
267279 def getFooterSize (self ) -> int :
268280 return 0
@@ -282,7 +294,7 @@ def calcOverheadEmpty(self, virtual_size: int, block_size: Optional[int] = None)
282294 cluster_size = block_size
283295 else :
284296 cluster_size = QCOW2_DEFAULT_CLUSTER_SIZE
285- cmd = [QEMU_IMG , "measure" , "-O" , "qcow2" , "--output" , "json" , "-o" , f"cluster_size={ cluster_size } " , "--size" , f"{ virtual_size } " ]
297+ cmd = [QEMU_IMG , "measure" , "-O" , "qcow2" , "--output" , "json" , "-o" , f"cluster_size={ cluster_size } ,extended_l2=on " , "--size" , f"{ virtual_size } " ]
286298 output = json .loads (self ._ioretry (cmd ))
287299 return int (output ["required" ])
288300
@@ -636,9 +648,11 @@ def snapshot(
636648 if parentRaw :
637649 parent_type = RAW_TYPE
638650 cluster_size = QCOW2_DEFAULT_CLUSTER_SIZE
651+ extended_l2 = False
639652 else :
640653 parent_type = QCOW2_TYPE
641654 cluster_size = self .getBlockSize (parent )
655+ extended_l2 = self .getExtendedL2 (parent )
642656 args = ["-f" , QCOW2_TYPE , "-F" , parent_type , "-b" , parent ]
643657
644658 if is_mirror_image :
@@ -650,9 +664,9 @@ def snapshot(
650664 # Ensuring we go back to a better cluster_size for performance reasons.
651665 # This limit our images max size to 64TiB.
652666 cluster_size = 16 * 1024 # 16KiB
653- args . extend ([ "-o" , " extended_l2=on" ])
667+ extended_l2 = True
654668
655- args .extend (["-o" , f"cluster_size={ cluster_size } " ])
669+ args .extend (["-o" , f"cluster_size={ cluster_size } ,extended_l2= { self . _convert_bool_to_qemu_option_value ( extended_l2 ) } " ])
656670 cmd .extend (args )
657671 cmd .append (path )
658672
0 commit comments