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

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

36 statements  

1import binascii 

2import io 

3from math import ceil 

4from typing import Optional 

5 

6from unblob.extractors import Command 

7from unblob.file_utils import File, InvalidInputFormat, get_endian_short 

8from unblob.models import ( 

9 HandlerDoc, 

10 HandlerType, 

11 Reference, 

12 Regex, 

13 StructHandler, 

14 ValidChunk, 

15) 

16 

17C_DEFINITIONS = r""" 

18 typedef struct partclone_header{ 

19 char magic[16]; 

20 char partclone_version[14]; 

21 char image_version_txt[4]; 

22 char endian[2]; 

23 char fs_type[16]; 

24 uint64 fs_size; 

25 uint64 fs_total_block_count; 

26 uint64 fs_used_block_count_superblock; 

27 uint64 fs_used_block_count_bitmap; 

28 uint32 fs_block_size; 

29 uint32 feature_size; 

30 uint16 image_version; 

31 uint16 number_of_bits_for_CPU; 

32 uint16 checksum_mode; 

33 uint16 checksum_size; 

34 uint32 blocks_per_checksum; 

35 uint8 reseed_checksum; 

36 uint8 bitmap_mode; 

37 uint32 crc32; 

38 } partclone_header_t; 

39""" 

40 

41HEADER_STRUCT = "partclone_header_t" 

42BIG_ENDIAN_MAGIC = 0xC0DE 

43ENDIAN_OFFSET = 34 

44 

45 

46class PartcloneHandler(StructHandler): 

47 NAME = "partclone" 

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

49 HEADER_STRUCT = HEADER_STRUCT 

50 C_DEFINITIONS = C_DEFINITIONS 

51 EXTRACTOR = Command( 

52 "partclone.restore", 

53 "-W", 

54 "-s", 

55 "{inpath}", 

56 "-o", 

57 "{outdir}/partclone.restored", 

58 "-L", 

59 "/dev/stdout", 

60 ) 

61 DOC = HandlerDoc( 

62 name="Partclone", 

63 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.", 

64 handler_type=HandlerType.ARCHIVE, 

65 vendor=None, 

66 references=[ 

67 Reference( 

68 title="Partclone GitHub Repository", 

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

70 ), 

71 Reference( 

72 title="Clonezilla Official Documentation", 

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

74 ), 

75 ], 

76 limitations=[], 

77 ) 

78 

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

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

81 return ( 

82 header.crc32 ^ 0xFFFFFFFF 

83 ) == calculated_crc # partclone does not final XOR 

84 

85 def calculate_chunk(self, file: File, start_offset: int) -> Optional[ValidChunk]: 

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

87 endian = get_endian_short(file, BIG_ENDIAN_MAGIC) 

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

89 header = self.parse_header(file, endian) 

90 

91 if not self.is_valid_header(header): 

92 raise InvalidInputFormat("Invalid partclone header.") 

93 

94 end_offset = start_offset + len(header) # header 

95 end_offset += header.checksum_size # checksum size 

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

97 

98 if header.checksum_mode != 0: 

99 checksum_blocks = ceil( 

100 header.fs_used_block_count_bitmap / header.blocks_per_checksum 

101 ) 

102 end_offset += checksum_blocks * header.checksum_size 

103 

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

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