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
This commit is contained in:
dualfroz committed 2026-09-13 15:20:49 +02:00
1 parent 1f8e7b5288
commit d32a327c5f
2 files changed
+69 -16

No files matched your search

+35 -16
View File
@@ -474,23 +474,26 @@ class Volume(AbstractFolder):
aliastarget = (self.name,) + aliastarget # match the convention for this function aliastarget = (self.name,) + aliastarget # match the convention for this function
targetobj = path2wrap[aliastarget].of # probe the target to set some metadata targetobj = path2wrap[aliastarget].of # probe the target to set some metadata
if isinstance(targetobj, Folder): # An alias read from a volume carries the type and creator the
wrap.creator = b'MACS' # Finder gave it, which the guesses below do not reproduce:
wrap.type = b'fdrp' # '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): elif isinstance(targetobj, Volume):
wrap.creator = b'MACS' wrap.creator = b'MACS'
wrap.type = b'hdsk' if size > 1440*1024 else b'flpy' wrap.type = b'hdsk' if size > 1440*1024 else b'flpy'
elif isinstance(targetobj, File): elif isinstance(targetobj, File):
wrap.creator = targetobj.creator wrap.creator = targetobj.creator
if targetobj.type == b'APPL': if targetobj.type == b'APPL':
wrap.type = b'adrp' wrap.type = b'adrp'
else: else:
wrap.type = targetobj.type wrap.type = targetobj.type
wrap.data = b''
userType = b'' userType = b''
aliasSize = 9999 # fill this short at offset 4 aliasSize = 9999 # fill this short at offset 4
@@ -514,7 +517,19 @@ class Volume(AbstractFolder):
# Stress test: find file by name, not CNID # Stress test: find file by name, not CNID
# fileNum = 0 # 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', alis.data[:] = struct.pack('>4s H hh 28p L 2s hL 64p LL 4s4s HHLh',
userType, aliasSize, aliasVersion, \ userType, aliasSize, aliasVersion, \
thisAliasKind, volumeName, volumeCrDate, \ thisAliasKind, volumeName, volumeCrDate, \
@@ -531,7 +546,11 @@ class Volume(AbstractFolder):
# open('/tmp/creating','wb').write(alis.data) # 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): if isinstance(obj, File):
wrap.dfrk = wrap.rfrk = (0, 0) wrap.dfrk = wrap.rfrk = (0, 0)
+34
View File
@@ -1,5 +1,7 @@
from machfs import * from machfs import *
from machfs import bitmanip, btree from machfs import bitmanip, btree
from machfs.main import FinderFlags
from macresources import Resource, make_file, parse_file
import os import os
import struct import struct
import time import time
@@ -27,6 +29,38 @@ def test_roundtrip():
assert f.data in copies[-1] 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(): def test_open_folder():
h = Volume() h = Volume()
h.name = 'OpenFolderTest' h.name = 'OpenFolderTest'