Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/unblob/handlers/archive/partclone.py: 54%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

35 statements  

1import binascii 

2import io 

3from math import ceil 

4 

5from unblob.extractors import Command 

6from unblob.file_utils import File, InvalidInputFormat, get_endian_short 

7from unblob.models import ( 

8 HandlerDoc, 

9 HandlerType, 

10 Reference, 

11 Regex, 

12 StructHandler, 

13 ValidChunk, 

14) 

15 

16C_DEFINITIONS = r""" 

17 typedef struct partclone_header{ 

18 char magic[16]; 

19 char partclone_version[14]; 

20 char image_version_txt[4]; 

21 char endian[2]; 

22 char fs_type[16]; 

23 uint64 fs_size; 

24 uint64 fs_total_block_count; 

25 uint64 fs_used_block_count_superblock; 

26 uint64 fs_used_block_count_bitmap; 

27 uint32 fs_block_size; 

28 uint32 feature_size; 

29 uint16 image_version; 

30 uint16 number_of_bits_for_CPU; 

31 uint16 checksum_mode; 

32 uint16 checksum_size; 

33 uint32 blocks_per_checksum; 

34 uint8 reseed_checksum; 

35 uint8 bitmap_mode; 

36 uint32 crc32; 

37 } partclone_header_t; 

38""" 

39 

40HEADER_STRUCT = "partclone_header_t" 

41BIG_ENDIAN_MAGIC = 0xC0DE 

42ENDIAN_OFFSET = 34 

43 

44 

45class PartcloneHandler(StructHandler): 

46 NAME = "partclone" 

47 PATTERNS = [Regex(r"partclone-image\x00\d+\.\d+\.\d+.*?0002(\xde\xc0|\xc0\xde)")] 

48 HEADER_STRUCT = HEADER_STRUCT 

49 C_DEFINITIONS = C_DEFINITIONS 

50 EXTRACTOR = Command( 

51 "partclone.restore", 

52 "-W", 

53 "-s", 

54 "{inpath}", 

55 "-o", 

56 "{outdir}/partclone.restored", 

57 "-L", 

58 "/dev/stdout", 

59 ) 

60 DOC = HandlerDoc( 

61 name="Partclone", 

62 description="Partclone is a utility used for backing up and restoring partitions. Many cloning tools (such as Clonezilla) rely on it to create block-level images that include filesystem metadata.", 

63 handler_type=HandlerType.ARCHIVE, 

64 vendor=None, 

65 references=[ 

66 Reference( 

67 title="Partclone GitHub Repository", 

68 url="https://github.com/Thomas-Tsai/partclone", 

69 ), 

70 Reference( 

71 title="Clonezilla Official Documentation", 

72 url="https://clonezilla.org/", 

73 ), 

74 ], 

75 limitations=[], 

76 ) 

77 

78 def is_valid_header(self, header) -> bool: 

79 calculated_crc = binascii.crc32(header.dumps()[0:-4]) 

80 return ( 

81 header.crc32 ^ 0xFFFFFFFF 

82 ) == calculated_crc # partclone does not final XOR 

83 

84 def calculate_chunk(self, file: File, start_offset: int) -> ValidChunk | None: 

85 file.seek(start_offset + ENDIAN_OFFSET, io.SEEK_SET) # go to endian 

86 endian = get_endian_short(file, BIG_ENDIAN_MAGIC) 

87 file.seek(start_offset, io.SEEK_SET) # go to beginning of file 

88 header = self.parse_header(file, endian) 

89 

90 if not self.is_valid_header(header): 

91 raise InvalidInputFormat("Invalid partclone header.") 

92 

93 end_offset = start_offset + len(header) # header 

94 end_offset += header.checksum_size # checksum size 

95 end_offset += ceil(header.fs_total_block_count / 8) # bitmap, as bytes 

96 

97 if header.checksum_mode != 0: 

98 checksum_blocks = ceil( 

99 header.fs_used_block_count_bitmap / header.blocks_per_checksum 

100 ) 

101 end_offset += checksum_blocks * header.checksum_size 

102 

103 end_offset += header.fs_used_block_count_bitmap * header.fs_block_size # Data 

104 return ValidChunk(start_offset=start_offset, end_offset=end_offset)