from machfs import * from machfs import bitmanip, btree from machfs.main import FinderFlags from macresources import Resource, make_file, parse_file import os import struct import time def test_upperlower(): h = Volume() h['alpha'] = File() assert h['alpha'] is h['ALPHA'] assert list(h.keys()) == ['alpha'] def test_roundtrip(): h = Volume() f = File() h['single file'] = f f.data = f.rsrc = b'1234' * 4096 copies = [h.write(800*1024)] for i in range(2): h2 = Volume() h2.read(copies[-1]) copies.append(h2.write(800*1024)) assert copies[0] == copies[1] assert copies[1] == copies[2] assert f.data in copies[-1] def test_alias_keeps_what_belongs_to_the_file(): h = Volume() h.name = 'AliasTest' target = File() target.type, target.creator = b'APPC', b'wnkl' target.data = b'target data' h['target'] = target # Neither 'fact' nor the 'vers' resource can be derived from the target. vers = Resource(b'vers', 2, data=b'\x01\x00\x80\x00\x00\x00\x041.0') stale = Resource(b'alis', 0, name='alias resource name', data=bytes(150)) alias = File() alias.flags |= FinderFlags.kIsAlias alias.type, alias.creator = b'fact', b'MACS' alias.rsrc = make_file([stale, vers]) alias.aliastarget = target h['the alias'] = alias copy = Volume() copy.read(h.write(10*1024*1024, desktopdb=False, bootable=False)) got = copy['the alias'] assert (got.type, got.creator) == (b'fact', b'MACS') resources = {(r.type, r.id): r for r in parse_file(got.rsrc)} assert resources[b'vers', 2].data == vers.data assert resources[b'alis', 0].name == 'alias resource name' # The record itself holds this volume's CNIDs, so it is rebuilt. assert resources[b'alis', 0].data != stale.data def test_open_folder(): h = Volume() h.name = 'OpenFolderTest' h['folder'] = Folder() for open_folder, expected_cnid in [ (None, 0), (h, 2), (h['folder'], 16), ]: h.open_folder = open_folder image = h.write( 10 * 1024 * 1024, desktopdb=False, bootable=False, ) mdb = struct.unpack_from( '>2sLLHHHHHLLHLH28pLHLLLHLL32sHHHL12sL12s', image, 1024 ) assert struct.unpack('>8L', mdb[22])[2] == expected_cnid copy = Volume() copy.read(image) if open_folder is None: assert copy.open_folder is None elif open_folder is h: assert copy.open_folder is copy else: assert copy.open_folder is copy['folder'] h['folder']['app'] = File() h.open_folder = None image = h.write( 10 * 1024 * 1024, desktopdb=False, bootable=False, startapp=('folder', 'app'), ) mdb = struct.unpack_from( '>2sLLHHHHHLLHLH28pLHLLLHLL32sHHHL12sL12s', image, 1024 ) finder_info = struct.unpack('>8L', mdb[22]) assert finder_info[1] == 16 assert finder_info[2] == 0 def test_file_catalog_reserved_fields(): h = Volume() h.name = 'CatalogFieldsTest' f = File() f.data = b'data fork' f.rsrc = b'resource fork' h['file'] = f image = h.write( 10 * 1024 * 1024, desktopdb=False, bootable=False, ) mdb = struct.unpack_from( '>2sLLHHHHHLLHLH28pLHLLLHLL32sHHHL12sL12s', image, 1024 ) allocation_block_size = mdb[8] allocation_block_start = mdb[10] catalog_size = mdb[-2] catalog_extents = btree.unpack_extent_record(mdb[-1]) catalog = b''.join( image[ 512 * allocation_block_start + first_block * allocation_block_size: 512 * allocation_block_start + (first_block + block_count) * allocation_block_size ] for first_block, block_count in catalog_extents )[:catalog_size] file_record = None for record in btree.dump_btree(catalog): key_length = record[0] value = record[bitmanip.pad_up(1 + key_length, 2):] if value[0] == 2: file_record = struct.unpack( '>BxBB16sLHLLHLLLLL16sH12s12sL', value ) break assert file_record is not None assert file_record[1] & 0x02 == 0 # kHFSThreadExistsMask assert file_record[5] == 0 # dataStartBlock (reserved) assert file_record[8] == 0 # rsrcStartBlock (reserved) assert file_record[18] == 0 # reserved assert btree.unpack_extent_record(file_record[16]) assert btree.unpack_extent_record(file_record[17]) def test_map_node_heights(): # Force enough leaf nodes to require more bitmap space than the header # node provides, and enough total nodes to require multiple map nodes. records = [(i.to_bytes(4, 'big'), bytes(470)) for i in range(6000)] tree = btree.make_btree(records, bthKeyLen=37, blksize=512) first_map_node, _, _, _, _ = btree._unpack_btree_node(tree, 0) assert first_map_node != 0 map_node_count = 0 map_node = first_map_node while map_node: map_node, _, node_type, node_height, _ = btree._unpack_btree_node( tree, 512 * map_node ) assert node_type == 2 assert node_height == 0 map_node_count += 1 assert map_node_count > 1 def test_macos_mount(): h = Volume() h.name = 'ElmoTest' hf = File() hf.data = b'12345' * 10 for i in reversed(range(100)): last = 'testfile-%03d' % i h[last] = hf ser = h.write(10*1024*1024) open('/tmp/SMALL.dmg','wb').write(ser) os.system('hdiutil attach /tmp/SMALL.dmg') n = 10 while 1: n += 1 assert n < 200 time.sleep(0.1) try: os.stat('/Volumes/ElmoTest/testfile-000') except: pass else: break recovered = open('/Volumes/ElmoTest/' + last,'rb').read() os.system('umount /Volumes/ElmoTest') assert recovered == hf.data h2 = Volume() h2.read(ser) assert h2['testfile-000'].data == hf.data # def test_extents_overflow(): # h = Volume() # h.read(open('SourceForEmulator.dmg','rb').read()) # assert h['aa'].data == b'a' * 278528 def test_many_sizes(): sizes = [800*1024, 1024*1024] while sizes[-1] < 4*1024*1024*1024: sizes.append(sizes[-1] * 2) # okay, we have heaps of sizes v = Volume() v.name = 'ImportantTestVol' for s in sizes: ser = v.write(s-512) open('/tmp/SMALL-%X.dmg'%s, 'wb').write(ser)