@@ -35,17 +35,17 @@ class NotADirectoryError(VBKError):
3535class VBK :
3636 """Veeam Backup (VBK) file implementation.
3737
38- Args:
39- fh: The file handle of the VBK file to read.
40- verify: Whether to verify checksums.
41-
4238 References:
4339 - CMeta
4440 - CStgFormat
4541
4642 Notes:
4743 - **TODO**: Encryption
4844 - **TODO**: Incrememental backups
45+
46+ Args:
47+ fh: The file handle of the VBK file to read.
48+ verify: Whether to verify checksums.
4949 """
5050
5151 def __init__ (self , fh : BinaryIO , verify : bool = True ):
@@ -95,7 +95,7 @@ def page(self, idx: int) -> bytes:
9595 """
9696 return self .active_slot .page (idx )
9797
98- def read_meta_blob (self , page : int ) -> bytes :
98+ def get_meta_blob (self , page : int ) -> MetaBlob :
9999 """Read a meta blob from the VBK file.
100100
101101 Args:
@@ -124,10 +124,6 @@ def get(self, path: str, item: DirItem | None = None) -> DirItem:
124124class SnapshotSlot :
125125 """A snapshot slot in the VBK file.
126126
127- Args:
128- vbk: The VBK object that the snapshot slot is part of.
129- offset: The offset of the snapshot slot in the file.
130-
131127 References:
132128 - CSlotHdr
133129 - SSnapshotDescriptor
@@ -142,6 +138,10 @@ class SnapshotSlot:
142138 - **TODO**: Free blocks index (CFreeBlocksIndex, SFreeBlockIndexItem)
143139 - **TODO**: Deduplication index (CDedupIndex, SDedupIndexItem)
144140 - **TODO**: Crypto store (CCryptoStore, SCryptoStoreRec)
141+
142+ Args:
143+ vbk: The VBK object that the snapshot slot is part of.
144+ offset: The offset of the snapshot slot in the file.
145145 """
146146
147147 def __init__ (self , vbk : VBK , offset : int ):
@@ -214,41 +214,26 @@ def page(self, page: int) -> bytes:
214214 """
215215 return self .banks [page >> 32 ].page (page & 0xFFFFFFFF )
216216
217- def _get_meta_blob (self , page : int ) -> bytes :
218- """Read a meta blob from the snapshot slot.
219-
220- A meta blob is a list of pages that are linked together. Each page has a header (``MetaBlobHeader``) with
221- a ``NextPage`` field that points to the next page in the blob. The last page has a ``NextPage`` field of -1.
217+ def _get_meta_blob (self , page : int ) -> MetaBlob :
218+ """Get a meta blob from the snapshot slot.
222219
223220 Args:
224221 page: The page of the first page in the meta blob.
225-
226- References:
227- - CMetaBlobRW
228222 """
229- result = []
230-
231- while page != - 1 :
232- buf = self .page (page )
233- result .append (buf )
234-
235- # Read the next page from the header
236- page = int .from_bytes (buf [:8 ], "little" , signed = True )
237-
238- return b"" .join (result )
223+ return MetaBlob (self , page )
239224
240225
241226class Bank :
242227 """A bank in the snapshot slot. A bank is a collection of pages.
243228
229+ References:
230+ - SBankHdr
231+ - CBankHdrPage
232+
244233 Args:
245234 vbk: The VBK object that the bank is part of.
246235 offset: The offset of the bank in the file.
247236 size: The size of the bank in the file.
248-
249- References:
250- - SBankHdr
251- - CBankHdrPage
252237 """
253238
254239 def __init__ (self , vbk : VBK , offset : int , size : int ):
@@ -748,20 +733,20 @@ def keyset_id(self) -> bytes:
748733class PropertiesDictionary (dict ):
749734 """A dictionary of properties in the VBK file.
750735
751- Args:
752- vbk: The VBK object that the properties dictionary is part of.
753- page: The page number of the meta blob of the properties dictionary.
754-
755736 References:
756737 - CPropsDictionary
757738 - CDirElemPropsRW
739+
740+ Args:
741+ vbk: The VBK object that the properties dictionary is part of.
742+ page: The page number of the meta blob of the properties dictionary.
758743 """
759744
760745 def __init__ (self , vbk : VBK , page : int ):
761746 self .vbk = vbk
762747 self .page = page
763748
764- buf = BytesIO (self .vbk .read_meta_blob (page ))
749+ buf = BytesIO (self .vbk .get_meta_blob (page ). data ( ))
765750 buf .seek (len (c_vbk .MetaBlobHeader ))
766751
767752 while True :
@@ -790,20 +775,57 @@ def __init__(self, vbk: VBK, page: int):
790775 self [name ] = value
791776
792777
778+ class MetaBlob :
779+ """A meta blob in the VBK file.
780+
781+ A meta blob is a list of pages that are linked together. Each page has a header (``MetaBlobHeader``) with
782+ a ``NextPage`` field that points to the next page in the blob. The last page has a ``NextPage`` field of -1.
783+
784+ References:
785+ - CMetaBlobRW
786+
787+ Args:
788+ slot: The snapshot slot that the meta blob is part of.
789+ root: The page number of the first page in the meta blob.
790+ """
791+
792+ def __init__ (self , slot : SnapshotSlot , root : int ):
793+ self .slot = slot
794+ self .root = root
795+
796+ def __repr__ (self ) -> str :
797+ return f"<MetaBlob root={ self .root } >"
798+
799+ def _read (self ) -> Iterator [int , memoryview ]:
800+ page = self .root
801+
802+ while page != - 1 :
803+ buf = self .slot .page (page )
804+ yield page , buf
805+
806+ page = int .from_bytes (buf [:8 ], "little" , signed = True )
807+
808+ def pages (self ) -> Iterator [int ]:
809+ return (page for page , _ in self ._read ())
810+
811+ def data (self ) -> bytes :
812+ return b"" .join (buf for _ , buf in self ._read ())
813+
814+
793815T = TypeVar ("T" , bound = MetaItem )
794816
795817
796818class MetaVector (Generic [T ]):
797819 """A vector of meta items in the VBK file.
798820
821+ References:
822+ - CMetaVec
823+
799824 Args:
800825 vbk: The VBK object that the vector is part of.
801826 type_: The type of the items in the vector.
802827 page: The page number of the first page in the vector.
803828 count: The number of items in the vector.
804-
805- References:
806- - CMetaVec
807829 """
808830
809831 def __new__ (cls , vbk : VBK , * args , ** kwargs ):
@@ -819,13 +841,7 @@ def __init__(self, vbk: VBK, type_: type[T], page: int, count: int):
819841
820842 self ._entry_size = len (self .type .__struct__ )
821843 self ._entries_per_page = PAGE_SIZE // self ._entry_size
822- self ._pages = []
823-
824- while page != - 1 :
825- self ._pages .append (page )
826-
827- buf = self .vbk .page (page )
828- page = int .from_bytes (buf [:8 ], "little" , signed = True )
844+ self ._pages = list (self .vbk .get_meta_blob (page ).pages ())
829845
830846 self .get = lru_cache (128 )(self .get )
831847
@@ -863,27 +879,33 @@ def get(self, idx: int) -> T:
863879class MetaVector2 (MetaVector [T ]):
864880 """A vector of meta items in the VBK file. Version 2.
865881
882+ MetaVector2 is essentially a table of page numbers that contain the vector entries.
883+ The table pages of a MetaVector2 have a 8-32 byte header, so we can hold a maximum of 508-511 entries per page.
884+ Read the comments in _lookup_page for more information.
885+
886+ References:
887+ - CMetaVec2
888+
866889 Args:
867890 vbk: The VBK object that the vector is part of.
868891 type_: The type of the items in the vector.
869892 page: The page number of the first page in the vector.
870893 count: The number of items in the vector.
871-
872- References:
873- - CMetaVec2
874894 """
875895
876- # MetaVector2 is essentially a table of page numbers that contain the vector entries
877- # The table pages of a MetaVector2 have a 8-16 byte header, so we can hold a maximum of 510-511 entries per page
878- # Read the comments in _lookup_page for more information
879- MAX_TABLE_ENTRIES_PER_PAGE = (PAGE_SIZE - 8 ) // 8
896+ _MAX_TABLE_ENTRIES_PER_PAGE = PAGE_SIZE // 8
897+ _MAX_TABLE_ENTRIES_LOOKUP = (
898+ _MAX_TABLE_ENTRIES_PER_PAGE - 1 ,
899+ _MAX_TABLE_ENTRIES_PER_PAGE - 4 ,
900+ _MAX_TABLE_ENTRIES_PER_PAGE - 1 ,
901+ )
880902
881903 def __init__ (self , vbk : VBK , type_ : type [T ], page : int , count : int ):
882904 super ().__init__ (vbk , type_ , page , count )
883905
884906 # It's not actually a meta blob, but the same mechanism is used (next page pointer in the header)
885907 # The table itself is essentially a big array of 64 bit integers, so cast it to a memoryview of that
886- self ._table = xmemoryview (self .vbk .read_meta_blob (page ), "<q" )
908+ self ._table = xmemoryview (self .vbk .get_meta_blob (page ). data ( ), "<q" )
887909 self ._lookup_page = lru_cache (128 )(self ._lookup_page )
888910
889911 def _lookup_page (self , idx : int ) -> int :
@@ -892,7 +914,6 @@ def _lookup_page(self, idx: int) -> int:
892914 Args:
893915 idx: The page index to lookup the page number for.
894916 """
895- page_id = idx + 1
896917
897918 # MetaVec2 pages are a little special
898919 # The first page has a 16 byte header:
@@ -909,20 +930,22 @@ def _lookup_page(self, idx: int) -> int:
909930 # We've not seen a table large enough to see this repeat more than once, but presumably it does
910931 #
911932 # This means that the first page can hold 510 entries, the second 508, and the third and fourth 511 each
912- # Reverse engineering gives us this funny looking loop, but it works, so use it for now
913- if page_id > self .MAX_TABLE_ENTRIES_PER_PAGE - 1 :
914- page_id_offset = 1
915- while True :
916- prev_page_id_offset = page_id_offset
917- page_id_offset *= 4
918-
919- if (4 * (self .MAX_TABLE_ENTRIES_PER_PAGE - 1 )) * prev_page_id_offset >= page_id :
920- break
933+ # The fifth page can hold 508 entries again, and so on
934+
935+ if idx < self ._MAX_TABLE_ENTRIES_PER_PAGE - 2 :
936+ return self ._table [idx + 2 ] # Skip the header
921937
922- page_id = page_id_offset + idx
938+ idx -= self ._MAX_TABLE_ENTRIES_PER_PAGE - 2
939+ table_idx = 1
940+ while True :
941+ max_entries = self ._MAX_TABLE_ENTRIES_LOOKUP [table_idx % 3 ]
923942
924- table_id , page_id_in_table = divmod (page_id , self .MAX_TABLE_ENTRIES_PER_PAGE )
925- return self ._table [table_id * (PAGE_SIZE // 8 ) + 1 + page_id_in_table ]
943+ if idx < max_entries :
944+ table_offset = table_idx * self ._MAX_TABLE_ENTRIES_PER_PAGE
945+ return self ._table [table_offset + (self ._MAX_TABLE_ENTRIES_PER_PAGE - max_entries ) + idx ]
946+
947+ idx -= max_entries
948+ table_idx += 1
926949
927950 def data (self , idx : int ) -> bytes :
928951 """Read the data for an entry in the vector.
@@ -940,13 +963,13 @@ def data(self, idx: int) -> bytes:
940963class FibMetaSparseTable :
941964 """A sparse table of FIB (File In Backup) blocks in the VBK file.
942965
966+ References:
967+ - CFibMetaSparseTable
968+
943969 Args:
944970 vbk: The VBK object that the sparse table is part of.
945971 page: The page number of the first page in the table.
946972 count: The number of entries in the table.
947-
948- References:
949- - CFibMetaSparseTable
950973 """
951974
952975 # This seems hardcoded? Probably calculated from something but unknown for now
0 commit comments