|
14 | 14 | # You should have received a copy of the GNU General Public License |
15 | 15 | # along with this program. If not, see <https://www.gnu.org/licenses/>. |
16 | 16 |
|
17 | | -from sm_typing import Any, Callable, Dict, Final, List, Optional, Tuple, cast, override |
| 17 | +from sm_typing import Any, Callable, Dict, Final, Optional, Tuple, cast, override |
18 | 18 | from typing import BinaryIO |
19 | 19 |
|
20 | 20 | import errno |
@@ -56,84 +56,12 @@ class QCowUtil(CowUtil): |
56 | 56 |
|
57 | 57 | QCOW2_MAGIC = 0x514649FB # b"QFI\xfb": Magic number for QCOW2 files |
58 | 58 | QCOW2_HEADER_SIZE = 104 # In fact the last information we need is at offset 40-47 |
59 | | - QCOW2_L2_SIZE = QCOW2_DEFAULT_CLUSTER_SIZE |
60 | 59 | QCOW2_BACKING_FILE_OFFSET = 8 |
61 | 60 |
|
62 | | - ALLOCATED_ENTRY_BIT = ( |
63 | | - 0x8000_0000_0000_0000 # Bit 63 is the allocated bit for standard cluster |
64 | | - ) |
65 | | - CLUSTER_TYPE_BIT = 0x4000_0000_0000_0000 # 0 for standard, 1 for compressed cluster |
66 | | - L2_OFFSET_MASK = 0x00FF_FFFF_FFFF_FF00 # Bits 9-55 are offset of L2 table. |
67 | | - CLUSTER_DESCRIPTION_MASK = 0x3FFF_FFFF_FFFF_FFFF # Bit 0-61 is cluster description |
68 | | - STANDARD_CLUSTER_OFFSET_MASK = ( |
69 | | - 0x00FF_FFFF_FFFF_FF00 # Bits 9-55 are offset of standard cluster |
70 | | - ) |
71 | | - |
72 | | - def __init__(self): |
73 | | - self.qcow_read = False |
74 | | - |
75 | | - def _read_qcow2(self, path: str, read_clusters: bool = False): |
76 | | - phys_disk_size = self.getSizePhys(path) |
| 61 | + def _read_qcow2(self, path: str): |
77 | 62 | with open(path, "rb") as qcow2_file: |
78 | 63 | self.filename = path # Keep the filename if clean is called |
79 | 64 | self.header = self._read_qcow2_header(qcow2_file) |
80 | | - if read_clusters: |
81 | | - self.l1 = self._get_l1_entries(qcow2_file) |
82 | | - # The l1_to_l2 allows to get L2 entries for a given L1. If L1 entry |
83 | | - # is not allocated we store an empty list. |
84 | | - self.l1_to_l2: Dict[int, List[int]] = {} |
85 | | - |
86 | | - for l1_entry in self.l1: |
87 | | - l2_offset = l1_entry & self.L2_OFFSET_MASK |
88 | | - if l2_offset == 0: |
89 | | - self.l1_to_l2[l1_entry] = [] |
90 | | - elif l2_offset > phys_disk_size: #TODO: This sometime happen for a correct VDI (while coalescing online?) |
91 | | - raise xs_errors.XenError("VDISize", "L2 Offset is bigger than physical disk {}".format(path)) |
92 | | - else: |
93 | | - self.l1_to_l2[l1_entry] = self._get_l2_entries( |
94 | | - qcow2_file, l2_offset |
95 | | - ) |
96 | | - self.qcow_read = True |
97 | | - |
98 | | - def _get_l1_entries(self, file: BinaryIO) -> List[int]: |
99 | | - """Returns the list of all L1 entries. |
100 | | -
|
101 | | - Args: |
102 | | - file: The qcow2 file object. |
103 | | -
|
104 | | - Returns: |
105 | | - list: List of all L1 entries |
106 | | - """ |
107 | | - l1_table_offset = self.header["l1_table_offset"] |
108 | | - file.seek(l1_table_offset) |
109 | | - |
110 | | - l1_table_size = self.header["l1_size"] * 8 # Each L1 entry is 8 bytes |
111 | | - l1_table = file.read(l1_table_size) |
112 | | - |
113 | | - return [ |
114 | | - struct.unpack(">Q", l1_table[i : i + 8])[0] |
115 | | - for i in range(0, len(l1_table), 8) |
116 | | - ] |
117 | | - |
118 | | - @staticmethod |
119 | | - def _get_l2_entries(file: BinaryIO, l2_offset: int) -> List[int]: |
120 | | - """Returns the list of all L2 entries at a given L2 offset. |
121 | | -
|
122 | | - Args: |
123 | | - file: The qcow2 file. |
124 | | - l2_offset: the L2 offset where to look for entries |
125 | | -
|
126 | | - Returns: |
127 | | - list: List of all L2 entries |
128 | | - """ |
129 | | - # The size of L2 is 65536 bytes and each entry is 8 bytes. |
130 | | - file.seek(l2_offset) |
131 | | - l2_table = file.read(QCowUtil.QCOW2_L2_SIZE) |
132 | | - |
133 | | - return [ |
134 | | - struct.unpack(">Q", l2_table[i : i + 8])[0] |
135 | | - for i in range(0, len(l2_table), 8) |
136 | | - ] |
137 | 65 |
|
138 | 66 | @staticmethod |
139 | 67 | def _read_qcow2_backingfile(file: BinaryIO, backing_file_offset: int , backing_file_size: int) -> str: |
@@ -209,78 +137,6 @@ def _read_qcow2_header(file: BinaryIO) -> Dict[str, Any]: |
209 | 137 | "parent": parent_name, |
210 | 138 | } |
211 | 139 |
|
212 | | - @staticmethod |
213 | | - def _is_l1_allocated(entry: int) -> bool: |
214 | | - """Checks if the given L1 entry is allocated. |
215 | | -
|
216 | | - If the offset is 0 then the L2 table and all clusters described |
217 | | - by this L2 table are unallocated. |
218 | | -
|
219 | | - Args: |
220 | | - entry: L1 entry |
221 | | -
|
222 | | - Returns: |
223 | | - bool: True if the L1 entry is allocated (ie has a valid offset). |
224 | | - False otherwise. |
225 | | - """ |
226 | | - return (entry & QCowUtil.L2_OFFSET_MASK) != 0 |
227 | | - |
228 | | - @staticmethod |
229 | | - def _is_l2_allocated(entry: int) -> bool: |
230 | | - """Checks if a given entry is allocated. |
231 | | -
|
232 | | - Currently we only support standard clusters. And for standard clusters |
233 | | - the bit 63 is set to 1 for allocated ones or offset is not 0. |
234 | | -
|
235 | | - Args: |
236 | | - entry: L2 entry |
237 | | -
|
238 | | - Returns: |
239 | | - bool: Returns True if the L2 entry is allocated, False otherwise |
240 | | -
|
241 | | - Raises: |
242 | | - raise an exception if the cluster is not a standard one. |
243 | | - """ |
244 | | - assert entry & QCowUtil.CLUSTER_TYPE_BIT == 0 |
245 | | - return (entry & QCowUtil.ALLOCATED_ENTRY_BIT != 0) or ( |
246 | | - entry & QCowUtil.STANDARD_CLUSTER_OFFSET_MASK != 0 |
247 | | - ) |
248 | | - |
249 | | - @staticmethod |
250 | | - def _get_allocated_clusters(l2_entries: List[int]) -> List[int]: |
251 | | - """Get all allocated clusters in a given list of L2 entries. |
252 | | -
|
253 | | - Args: |
254 | | - l2_entries: A list of L2 entries. |
255 | | -
|
256 | | - Returns: |
257 | | - A list of all allocated entries |
258 | | - """ |
259 | | - return [entry for entry in l2_entries if QCowUtil._is_l2_allocated(entry)] |
260 | | - |
261 | | - @staticmethod |
262 | | - def _get_cluster_to_byte(clusters: int, cluster_bits: int) -> int: |
263 | | - # (1 << cluster_bits) give cluster size in byte |
264 | | - return clusters * (1 << cluster_bits) |
265 | | - |
266 | | - def _get_number_of_allocated_clusters(self) -> int: |
267 | | - """Get the number of allocated clusters. |
268 | | -
|
269 | | - Args: |
270 | | - self: A QcowInfo object. |
271 | | -
|
272 | | - Returns: |
273 | | - An integer that is the list of allocated clusters. |
274 | | - """ |
275 | | - assert(self.qcow_read) |
276 | | - |
277 | | - allocated_clusters = 0 |
278 | | - |
279 | | - for l2_entries in self.l1_to_l2.values(): |
280 | | - allocated_clusters += len(self._get_allocated_clusters(l2_entries)) |
281 | | - |
282 | | - return allocated_clusters |
283 | | - |
284 | 140 | @staticmethod |
285 | 141 | def _move_backing_file( |
286 | 142 | f: BinaryIO, old_offset: int, new_offset: int, data_size: int |
@@ -323,8 +179,6 @@ def _add_or_find_custom_header(self) -> int: |
323 | 179 | If data offset is 0 something weird happens. |
324 | 180 | The qcow2 file in self.filename can be modified. |
325 | 181 | """ |
326 | | - assert self.qcow_read |
327 | | - |
328 | 182 | header_length = 72 # This is the default value for version 2 images |
329 | 183 |
|
330 | 184 | custom_header_type = 0x76617465 # vate: it is easy to recognize with hexdump -C |
@@ -392,39 +246,6 @@ def _add_or_find_custom_header(self) -> int: |
392 | 246 |
|
393 | 247 | return custom_data_offset |
394 | 248 |
|
395 | | - def _set_l1_zero(self): |
396 | | - zero = int(0).to_bytes(1, "little") |
397 | | - nb_of_entries_per_cluster = QCOW2_DEFAULT_CLUSTER_SIZE/8 |
398 | | - return list(zero * int(nb_of_entries_per_cluster/8)) |
399 | | - |
400 | | - def _set_l2_zero(self, b, i): |
401 | | - return b & ~(1 << i) |
402 | | - |
403 | | - def _set_l2_one(self, b, i): |
404 | | - return b | (1 << i) |
405 | | - |
406 | | - def _create_bitmap(self) -> bytes: |
407 | | - idx: int = 0 |
408 | | - bitmap = list() |
409 | | - b = 0 |
410 | | - for l1_entry in self.l1: |
411 | | - if not self._is_l1_allocated(l1_entry): |
412 | | - bitmap.extend(self._set_l1_zero()) |
413 | | - continue |
414 | | - |
415 | | - l2_table = self.l1_to_l2[l1_entry] #L2 is cluster_size/8 entries of cluster_size page |
416 | | - for l2_entry in l2_table: |
417 | | - if self._is_l2_allocated(l2_entry): |
418 | | - b = self._set_l2_one(b, idx) |
419 | | - else: |
420 | | - b = self._set_l2_zero(b, idx) |
421 | | - idx += 1 |
422 | | - if idx == 8: |
423 | | - bitmap.append(b) |
424 | | - b = 0 |
425 | | - idx = 0 |
426 | | - return struct.pack("B"*len(bitmap), *bitmap) |
427 | | - |
428 | 249 | # ---- |
429 | 250 | # Implementation of CowUtil |
430 | 251 | # ---- |
@@ -687,7 +508,7 @@ def killData(self, path: str) -> None: |
687 | 508 | Returns: |
688 | 509 | nothing. |
689 | 510 | """ |
690 | | - self._read_qcow2(path, read_clusters=True) |
| 511 | + self._read_qcow2(path) |
691 | 512 | # We need to reset L1 entries and then just truncate the file right |
692 | 513 | # after L1 entries |
693 | 514 | with open(self.filename, "r+b") as file: |
|
0 commit comments