From d32a327c5ff105a2326ea8337ce6d908e1daa451 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Fri, 11 Sep 2026 23:15:47 +0200 Subject: [PATCH] Stop rewriting an alias file's own metadata Writing a volume rebuilds every alias file, because the `alis` record names its target by CNID and CNIDs are assigned afresh on each write. It rebuilt more than the record, though: the file's type and creator were overwritten with ones guessed from the target, the rest of the resource fork was thrown away, and the data fork was cleared. Round-tripping the Apple Menu's Control Panels alias out of a System 7 image therefore turned `fact` into `fdrp`, dropped its `vers` resources, and renamed the `alis` from "Control Panels alias" to "Control Panels". Aliases to applications lost their `adrp` for whatever the target's type happened to be -- `APPC`, `cdev`. Only the `alis` resource is replaced now, in place and under its own name; type and creator are guessed only for an alias built here, which has neither. Reading and rewriting the 29 infinite-mac images machfs can parse touches 73 alias files: type and creator used to change on 10 of them and now change on none, and 28 resource forks used to lose more than the `alis` record and now lose nothing else. (Their data forks were already empty, so clearing those had cost nothing in practice.) For mihaip/infinite-mac#84 --- machfs/main.py | 51 ++++++++++++++++++++++++++++++++++---------------- test_all.py | 34 +++++++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 16 deletions(-) diff --git a/machfs/main.py b/machfs/main.py index 6a41b4e..4f1deb0 100644 --- a/machfs/main.py +++ b/machfs/main.py @@ -474,23 +474,26 @@ class Volume(AbstractFolder): aliastarget = (self.name,) + aliastarget # match the convention for this function targetobj = path2wrap[aliastarget].of # probe the target to set some metadata - if isinstance(targetobj, Folder): - wrap.creator = b'MACS' - wrap.type = b'fdrp' + # An alias read from a volume carries the type and creator the + # Finder gave it, which the guesses below do not reproduce: + # 'fact' for Control Panels, 'faet' for Automated Tasks. So + # only guess for an alias built here, which has neither. + if obj.type == b'????' and obj.creator == b'????': + if isinstance(targetobj, Folder): + wrap.creator = b'MACS' + wrap.type = b'fdrp' - elif isinstance(targetobj, Volume): - wrap.creator = b'MACS' - wrap.type = b'hdsk' if size > 1440*1024 else b'flpy' + elif isinstance(targetobj, Volume): + wrap.creator = b'MACS' + wrap.type = b'hdsk' if size > 1440*1024 else b'flpy' - elif isinstance(targetobj, File): - wrap.creator = targetobj.creator + elif isinstance(targetobj, File): + wrap.creator = targetobj.creator - if targetobj.type == b'APPL': - wrap.type = b'adrp' - else: - wrap.type = targetobj.type - - wrap.data = b'' + if targetobj.type == b'APPL': + wrap.type = b'adrp' + else: + wrap.type = targetobj.type userType = b'' aliasSize = 9999 # fill this short at offset 4 @@ -514,7 +517,19 @@ class Volume(AbstractFolder): # Stress test: find file by name, not CNID # fileNum = 0 - alis = Resource(b'alis', 0, name=path[-1]) + # The record below holds CNIDs, so it has to be rebuilt for this + # volume. The rest of the fork belongs to the file: Control + # Panels keeps its 'vers', and the 'alis' keeps its own name. + resources = list(parse_file(obj.rsrc)) if obj.rsrc else [] + alisIndex = next((i for i, r in enumerate(resources) + if (r.type, r.id) == (b'alis', 0)), None) + + if alisIndex is None: + alis = Resource(b'alis', 0, name=path[-1]) + else: + oldalis = resources[alisIndex] + alis = Resource(b'alis', 0, name=oldalis.name) + alis.attribs = oldalis.attribs alis.data[:] = struct.pack('>4s H hh 28p L 2s hL 64p LL 4s4s HHLh', userType, aliasSize, aliasVersion, \ thisAliasKind, volumeName, volumeCrDate, \ @@ -531,7 +546,11 @@ class Volume(AbstractFolder): # open('/tmp/creating','wb').write(alis.data) - wrap.rsrc = make_file([alis]) + if alisIndex is None: + resources.append(alis) + else: + resources[alisIndex] = alis + wrap.rsrc = make_file(resources) if isinstance(obj, File): wrap.dfrk = wrap.rfrk = (0, 0) diff --git a/test_all.py b/test_all.py index d0346cc..46c3713 100644 --- a/test_all.py +++ b/test_all.py @@ -1,5 +1,7 @@ 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 @@ -27,6 +29,38 @@ def test_roundtrip(): 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'