Compare commits

...

4 Commits

Author SHA1 Message Date
Elliot Nunn 4f554286e6 MakeHFS: write to pre-existing partitioned disks 2022-09-01 20:21:23 +08:00
Elliot Nunn 8dd4181d36 Log corrupt resource forks 2022-09-01 20:20:49 +08:00
Elliot Nunn 4db5dd3819 DumpHFS: create nonexistent dest dir 2022-09-01 20:20:32 +08:00
Elliot Nunn 4d0927eb65 Bump + push 2021-04-12 08:08:02 +08:00
5 changed files with 86 additions and 21 deletions

View File

@ -7,7 +7,7 @@ codes](https://en.wikipedia.org/wiki/Creator_code) are first-class
citizens.
Python interface
----------------
================
The Python API is simple. The contents of a `Volume` or a `Folder` are
accessed using the index operator `[]`. While working on a filesystem,
its entire high-level contents are stored in memory as a Python object.
@ -42,7 +42,7 @@ with open('FloppyImage.dsk', 'rb') as f:
```
Command-line interface
----------------------
======================
This package also installs the `MakeHFS` and `DumpHFS` utilities, for
working with folders on your native filesystem. Briefly, resource forks
are stored in Rez-formatted `.rdump` files, and type and creator codes
@ -56,7 +56,7 @@ Mac OS Roman encoding with Mac-style (CR) line endings.
Both commands have a `--help` argument to display their options.
Why?
----
====
I want an automated, reproducible way to compile legacy MacOS software.
Without any current operating system fully supporting HFS,
[libhfs/hfsutils](https://www.mars.org/home/rob/proj/hfs/) (a C library
@ -71,3 +71,11 @@ high-level programming language seemed like a reasonable tradeoff. As a
result, `machfs` has nearly an order of magnitude fewer lines than
`libhfs`, and is more maintainable, at a nearly negligible cost in
performance.
Changes
=======
1.3
---
- Sam Fuller's btree fix for volumes with *many* files:
https://github.com/elliotnunn/machfs/commit/b44a2f9
- Support for Aliases, represented in the native FS as symlinks

View File

@ -2,6 +2,7 @@
import argparse
from machfs import Volume
import os
args = argparse.ArgumentParser()
@ -10,6 +11,11 @@ args.add_argument('dir', metavar='OUTPUT', nargs=1, help='Destination folder')
args = args.parse_args()
try:
os.mkdir(args.dir[0])
except FileExistsError:
pass
with open(args.src[0], 'rb') as f:
v = Volume()
v.read(f.read())

View File

@ -123,17 +123,62 @@ vol.name = args.name
if args.dir: vol.read_folder(args.dir, date=args.date, mpw_dates=args.mpw_dates)
with open(args.dest[0], 'ab+') as f:
if args.size is None: args.size = hack_file_size(f)
if args.size == 0: args.size = 800 * 1024
try:
f = open(args.dest[0], 'rb+')
existed = True
except FileNotFoundError:
f = open(args.dest[0], 'wb+')
existed = False
with open(args.dest[0], 'rb+') as f:
left, gap, right = vol.write(args.size, startapp=args.app, sparse=True)
if args.size is not None:
offset = 0
size = args.size
trunc = True
nameoffset = None
f.write(left)
f.seek(gap, 1)
problem = len(left) + gap - f.tell()
if problem > 0:
f.write(bytes(problem))
f.write(right)
f.truncate()
elif not existed: # make a tiny floppy
offset = 0
size = 800 * 1024
trunc = True
nameoffset = None
elif f.read(2) == b'ER': # is partitioned disk
blksize = int.from_bytes(f.read(2), byteorder='big')
f.seek(blksize + 4)
entrycnt = int.from_bytes(f.read(4), byteorder='big')
for i in range(entrycnt):
entryoffset = blksize * (i + 1)
f.seek(entryoffset + 48)
if f.read(10) == b'Apple_HFS\x00':
f.seek(entryoffset + 8)
offset = blksize * int.from_bytes(f.read(4), byteorder='big')
size = blksize * int.from_bytes(f.read(4), byteorder='big')
trunc = False
nameoffset = entryoffset + 16
break
else:
raise ValueError("No HFS partition in this map")
else: # is raw filesystem
offset = 0
size = hack_file_size(f)
trunc = False
left, gap, right = vol.write(size, startapp=args.app, sparse=True)
f.seek(offset)
f.write(left)
f.seek(gap, 1)
problem = len(left) + gap - f.tell()
if problem > 0:
f.write(bytes(problem))
f.write(right)
if nameoffset is not None:
f.seek(nameoffset)
f.write(args.name.encode('mac_roman').ljust(32, b'\x00'))
if trunc: f.truncate()

View File

@ -2,7 +2,7 @@ from collections.abc import MutableMapping
import os
from os import path
from macresources import make_rez_code, parse_rez_code, make_file, parse_file
from warnings import warn
import sys
TEXT_TYPES = [b'TEXT', b'ttro'] # Teach Text read-only
@ -267,7 +267,7 @@ class AbstractFolder(MutableMapping):
blacklist_test = ':'.join(p) + ':'
if blacklist_test.startswith(tuple(blacklist)): continue
if _unsyncability(p[-1]):
warn('Ignoring unsyncable name: %r' % (':' + ':'.join(p)))
print('Ignoring unsyncable name: %r' % (':' + ':'.join(p)), file=sys.stderr)
blacklist.append(blacklist_test)
continue
@ -293,11 +293,17 @@ class AbstractFolder(MutableMapping):
# write a resource dump iff that fork has any bytes (dump may still be empty)
if obj.rsrc:
with open(rsrc_path, 'wb') as f:
try:
rdump = make_rez_code(parse_file(obj.rsrc), ascii_clean=True)
f.write(rdump)
except:
with open(nativepath + '.rdump.corrupt', 'wb') as f:
f.write(obj.rsrc)
print('Dumping corrupt resource fork: %r' % (':' + ':'.join(p)), file=sys.stderr)
else:
with open(rsrc_path, 'wb') as f:
f.write(rdump)
else:
_try_delete(rsrc_path)
_try_delete(rsrc_path)
# write an info dump iff either field is non-null
idump = obj.type + obj.creator

View File

@ -2,7 +2,7 @@ from setuptools import setup
setup(
name='machfs',
version='1.2.4',
version='1.3',
author='Elliot Nunn',
author_email='elliotnunn@me.com',
description='Library for reading and writing Macintosh HFS volumes',