oaknut.filesystem¶
The pluggable filesystem contract shared by the disc family. A
filesystem (Acorn DFS, Watford DFS, ADFS, AFS, …) is the unit of
extension, registered on the oaknut.filesystem entry-point axis.
Each Filesystem detects itself
(probe()), opens a region into a
Mount exposing a small core plus opt-in
capability protocols, and declares its physical
Geometry grammar.
Geometry (the physical byte→sector layout) and the filesystem (the
logical structure) are kept strictly apart.
identify() runs the registered filesystems over
an image, ranks the candidates, and recurses into reserved regions to
build a per-partition Identification tree —
depending on, and importing, no concrete filesystem package.
Every name documented here is importable directly from
oaknut.filesystem.
The filesystem contract¶
- class oaknut.filesystem.Filesystem(name, **kwargs)¶
Base class for a pluggable filesystem.
Registered under
oaknut.filesystemin a package’spyproject.toml:[project.entry-points."oaknut.filesystem"] acorn_dfs = "oaknut.dfs.filesystem:AcornDFS"
The entry-point key (
acorn-dfs) is the filesystem’s user-facing name: enumerated bydisc list-filesystems, explained bydisc describe-filesystem, and forced by--filesystem.- Parameters:
name (str)
- extensions: frozenset[str] = frozenset({})¶
Extensions conventionally used for this filesystem (lower-case, with leading dot). Consulted by the coordinator only to break ties between equally-confident candidates — never to identify.
- priority: int = 0¶
Ordering hint among same-confidence, same-extension candidates; higher wins (e.g. Watford outranks Acorn DFS, which excludes it).
- creates: frozenset[str] = frozenset({})¶
Extensions this filesystem is the default creator for —
disc createinfers the filesystem from the target’s extension via this. A subset of (or disjoint from)extensions: a niche variant (Watford) or a non-standalone filesystem (AFS, made inside an ADFS disc) leaves it empty and is reached only via--filesystem.
- wildcard_syntax: WildcardSyntax = WildcardSyntax(metacharacters=(('*', 'any sequence of characters'), ('?', 'exactly one character')))¶
This filesystem’s filename wildcard vocabulary, reported by
disc describe-filesystem. Defaults to Unix (*/?); the Acorn filing systems override it with*/#(?literal).
- abstractmethod probe(reader)¶
Inspect the region in reader; identify it, or return
None.On a match, return an
Identificationcarrying the confidence, evidence, the proposed geometry (only this filesystem can read its own capacity hints), any geometry ambiguities the bytes cannot settle, and — for a host filesystem — thereserved_regionsfor the coordinator to recurse into. Must not raise on data that simply isn’t this filesystem.- Parameters:
reader (ImageReader)
- Return type:
- abstractmethod open(reader, geometry, *, surface=0)¶
Open the region in reader at geometry, returning a mount.
The returned object implements
Mountplus whichever capability protocols this filesystem supports. surface selects which volume of a multi-volume image to open (a DFS side); the default 0 is the whole image / first volume, which is all most filesystems have.- Parameters:
reader (ImageReader)
geometry (Geometry)
surface (int)
- Return type:
- split_volume(inner_path, geometry, ambiguities=())¶
Split a leading volume token from inner_path.
Returns
(surface_index, geometry, residual_path). The returned geometry may differ from the one passed in, because the volume token can imply a geometry — a non-zero DFS drive on a length-ambiguous image implies the double-sided reading drawn from ambiguities. The default has no notion of a sub-volume: surface 0, the geometry unchanged, and the whole path. DFS overrides this to parse the Acorn:drive.prefix.
- volumes(geometry)¶
The independently-addressable volumes at geometry.
The default is a single, undesignated volume — most filesystems span the whole image. A double-sided DFS reports one per side, designated
:0/:2. Round-trips withsplit_volume(): every designation here parses back to the same surface.
- designation_for(surface, geometry)¶
The user-facing designation of surface, for diagnostics.
Looks the surface up among
volumes(), so messages quote the filesystem’s own vocabulary (:2, not the cardinal index). A surface with no enumerated volume falls back to:Nso a diagnostic about an absent second side still reads naturally.
- abstractmethod geometry_grammar()¶
The geometries this filesystem supports — presets and kinds.
Used to resolve
--geometryand to enumerate choices fordisc createanddescribe-filesystem.- Return type:
- default_geometry(suffix)¶
The geometry to create a suffix image with, or
None.Nonemeans there is no sensible default for this extension — it is ambiguous (ADFS.adfcould be S or M) or open-ended (a hard disc) — sodisc createrequires an explicit--geometry. The default implementation has none.
- oaknut.filesystem.FILESYSTEM_KIND = 'filesystem'¶
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to ‘utf-8’. errors defaults to ‘strict’.
- oaknut.filesystem.FILESYSTEM_NAMESPACE = 'oaknut.filesystem'¶
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to ‘utf-8’. errors defaults to ‘strict’.
Capabilities¶
A mount exposes a small required core (Mount)
plus whichever of these runtime_checkable protocols its filesystem
supports. Commands gate on the capability, never on the filesystem type,
so a filesystem that cannot do something simply does not implement the
protocol.
- class oaknut.filesystem.Mount(*args, **kwargs)¶
The core every mounted filesystem provides.
Paths are strings in the filesystem’s own syntax ($.DIR.FILE for Acorn, DIRFILE for FAT, D.DIR.FILE for a DDOS volume); the filesystem parses them — the CLI never does.
- join(parent, name)¶
The path of child name under directory parent.
The filesystem owns its path syntax (
$.Afor Acorn, the root sometimes nameless), so the CLI builds new paths through this rather than concatenating — needed when creating a path that does not exist yet (e.g. a bulk import target).
- iter_entries(path)¶
Yield the entries of the directory at path.
- write_bytes(path, data)¶
Write data to path, creating or replacing the file.
- remove(path, *, force=False)¶
Delete the file or directory at path.
A directory is removed if the filesystem represents one (a flat catalogue has none, so removing its notional directory is a no-op). force overrides a lock — the filesystem unlocks the entry first, owning its own locked-entry semantics so the access byte’s layout never leaks to the caller.
- class oaknut.filesystem.Entry(name, is_dir, length=0, path='')¶
One directory entry, in filesystem-agnostic terms.
Acorn-specific metadata (load/exec/access) is reached through the
AcornMetadatacapability, not carried here, so a foreign filesystem’s entries need none of it.
- class oaknut.filesystem.HierarchicalDirectories(*args, **kwargs)¶
The filesystem nests directories arbitrarily (ADFS, AFS, FAT, DDOS).
Its absence marks a flat catalogue (Acorn/Watford DFS: a single top-level directory only).
- make_directory(path, *, parents=False, exist_ok=False, title=None)¶
Create a directory at path.
parents creates missing ancestors; exist_ok tolerates an existing directory. title sets the new directory’s title where the filesystem supports per-directory titles (ADFS) and is rejected (before anything is created) where it does not (AFS).
- class oaknut.filesystem.AcornMetadata(*args, **kwargs)¶
Files carry Acorn load/exec addresses and an access byte.
- acorn_meta(path)¶
The Acorn metadata of the file at path.
- class oaknut.filesystem.Titled(*args, **kwargs)¶
The disc carries a title / name (DFS, ADFS, AFS).
- class oaknut.filesystem.DirectoryTitled(*args, **kwargs)¶
Directories carry their own title, distinct from the disc’s (ADFS).
Its absence marks a filesystem whose directories have no title field (DFS, AFS) — setting one there is rejected.
- class oaknut.filesystem.Bootable(*args, **kwargs)¶
The disc carries a
*OPT 4boot option (DFS, ADFS).- property boot_option: BootOption¶
The disc’s
*OPT 4boot option.
- set_boot_option(option)¶
Set the disc’s
*OPT 4boot option.- Parameters:
option (BootOption | int)
- Return type:
- class oaknut.filesystem.FreeSpace(*args, **kwargs)¶
The filesystem reports its free space (ADFS, AFS).
- class oaknut.filesystem.FreeMap(*args, **kwargs)¶
The filesystem can report which of its sectors are free.
- free_map()¶
The free-space map as partition-relative sector runs.
- Return type:
- class oaknut.filesystem.FreeMapData(free_regions, total_sectors)¶
A filesystem’s free space as partition-relative sector runs.
Carries no geometry: the renderer lays the total_sectors out as a sector matrix sized to the terminal, marking those in free_regions free and the rest used. Each region is
(start_sector, length).
- class oaknut.filesystem.Sized(*args, **kwargs)¶
The filesystem reports its own occupied size (its partition’s span).
- class oaknut.filesystem.PhysicalGeometry(*args, **kwargs)¶
The filesystem knows the disc’s physical geometry (ADFS, AFS).
A flat-catalogue floppy filesystem (DFS) records no geometry and does not advertise this.
- disc_geometry()¶
The disc’s physical geometry.
- Return type:
- class oaknut.filesystem.DiscGeometry(label, sectors_per_cylinder, total_sectors)¶
A disc’s physical geometry, for the
statsummary.label is a human description in the filesystem’s own vocabulary (ADFS speaks cylinders/heads/track; AFS speaks cylinders/sectors-per- cylinder). sectors_per_cylinder lets the caller place a partition’s logical-sector span into a cylinder range without parsing the label.
- class oaknut.filesystem.Compactable(*args, **kwargs)¶
The filesystem can defragment in place, consolidating free space.
- compact(*, order=())¶
Defragment, returning a filesystem-defined measure of work done.
order is a partial list of paths to lay down first, in the lowest/earliest positions (in the given order); files it does not name follow in their current order. It lets a caller place boot or loader files where they load fastest. A filesystem whose layout order is fixed or undefined rejects a non-empty order; the CLI offers
--orderonly where the mount also reports a storage order (StorageOrdered).
- class oaknut.filesystem.Validatable(*args, **kwargs)¶
The filesystem can check its on-disc structure for defects.
- class oaknut.filesystem.StatusReporting(*args, **kwargs)¶
The filesystem can report human-readable status notes for
stat.Notes are short advisories about the partition as a whole — for example that a ROMFS image is an incomplete fragment of a multi-ROM set, or is read-only because it carries code after the filing system.
disc statrenders them as a line; most filesystems have nothing to say and do not implement this.
- class oaknut.filesystem.StorageOrdered(*args, **kwargs)¶
The filesystem can order its paths by physical position on the medium.
storage_key()returns an opaque sort key for a path; sorting a directory’s siblings by it yields the order in which their data is laid down on the medium — ascending start sector for a random-access filesystem (DFS, ADFS), stream order for a sequential one (CFS/ROMFS). A multi-filecpuses it to reproduce the source’s on-disc order at the destination instead of reversing it (a flat catalogue read highest-sector-first, re-laid lowest-sector-first, would otherwise flip the order and slow loading on a seeking drive).The key is comparable only against other keys from the same mount and carries no meaning beyond ordering — the caller never inspects it. Filesystems with no storage order (a hash table) or one that is ill-defined (a fragmented AFS file spans many extents, so it has no single position) do not implement this.
- class oaknut.filesystem.WildcardMatching(*args, **kwargs)¶
The filesystem owns its filename wildcard syntax and matching.
Acorn filing systems glob with
*and#, and?is an ordinary filename character; a DOS/FAT filesystem globs with*and?. The CLI defers to the mount so a pattern is read in the filesystem’s own terms, falling back to a Unix default (*/?) for a mount that does not implement this.- property wildcard_syntax: WildcardSyntax¶
This filesystem’s wildcard vocabulary, for help and diagnostics.
- is_pattern(name)¶
Whether name contains any wildcard metacharacter of this syntax.
- class oaknut.filesystem.WildcardSyntax(metacharacters)¶
A filesystem’s filename-wildcard vocabulary, for help and errors.
Each pair is
(metacharacter, what it matches)— Acorn filing systems use*and#; a DOS/FAT filesystem uses*and?. This carries only the human-facing description; the matching itself isWildcardMatching.matches(), since a syntax like DOS 8.3 is more than a choice of metacharacters.
- class oaknut.filesystem.UserDatabase(*args, **kwargs)¶
The filesystem has user accounts (AFS passwords / quota).
- class oaknut.filesystem.RegionHost(*args, **kwargs)¶
The filesystem reserves regions another filesystem may occupy.
An ADFS host reserves tail cylinders that an AFS or (DRDOS) FAT filesystem lives in; the coordinator recurses into these. The host stays ignorant of what occupies them.
Geometry¶
The physical layout beneath a filesystem. A filesystem declares a
GeometryGrammar (named presets plus a
parameterised form), --geometry is resolved against it, and
probe() proposes one in the same
terms.
- class oaknut.filesystem.Geometry(surface_specs, label='', cylinders=None, heads=None, sectors_per_track=None)¶
A physical layout: the surface specs an image is read through.
Pure geometry — no catalogue or filesystem.
labelis a short human description (e.g."ADFS-L (80T DS interleaved)").cylinders/heads/sectors_per_trackare the optional CHS the layout was built from — a hard disc’s, carried so a filesystem can report it (the surface specs themselves linearise CHS away). They areNonewhen the source did not record CHS.- Parameters:
- class oaknut.filesystem.GeometryGrammar(presets=<factory>, kinds=())¶
How a filesystem accepts a geometry on the command line / API.
presets are the enumerable named layouts (
s/m/lfor ADFS floppies); kinds are the parameterised forms it also accepts (floppyand/orwinchester).parse()resolves a string to aGeometry, trying a preset name first, then a parameterisedkey=valueform.
- oaknut.filesystem.floppy_geometry(*, tracks, sides, sectors_per_track=10, bytes_per_sector=256, interleaved=True, label='')¶
Build a floppy
Geometry.sides is 1 or 2; for double-sided, interleaved selects the Acorn-conventional interleaved layout (side 0/side 1 alternating per track) over the sequential one.
- oaknut.filesystem.winchester_geometry(*, cylinders, heads, sectors_per_track, bytes_per_sector=256, label='')¶
Build a hard-disc
Geometryas one linear sector surface.Acorn hard-disc images present a flat linear sector space; the CHS figures determine its extent. (They are also recorded in a
.dscsidecar, but the image itself is linear.)
- oaknut.filesystem.geometry_from_dsc(dsc_bytes, *, sectors_per_track=33)¶
Build a hard-disc
Geometryfrom a 22-byte.dscsidecar.Geometry resolution, not filesystem identification: the
.dsccarries the CHS a hard-disc image’s bytes cannot, so the caller can report it. RaisesGeometryErroron a malformed sidecar.
- oaknut.filesystem.region_reader(reader, geometry, start_sector, num_sectors)¶
A reader over the logical sector run
[start_sector, +num_sectors).When the host is linear (no geometry, or a single contiguous surface — a hard disc), the run is a byte window that shares the backing, so it is cheap and inherits the host’s writability: writes to a tail filesystem reach the file. When the host is interleaved (a double-sided floppy) the run’s bytes are scattered, so the host’s
UnifiedDiscde-interleaves them into a contiguous view a tail filesystem can address linearly. That view is a copy, so it is read-only: writing to an interleaved reserved region would not reach the file, and is refused rather than silently lost.- Parameters:
reader (ImageReader)
start_sector (int)
num_sectors (int)
- Return type:
- oaknut.filesystem.FLOPPY = 'floppy'¶
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to ‘utf-8’. errors defaults to ‘strict’.
- oaknut.filesystem.WINCHESTER = 'winchester'¶
str(object=’’) -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to ‘utf-8’. errors defaults to ‘strict’.
Identification¶
The result model returned by identify(): a tree
of per-region Identification nodes, each with
a Confidence and the
Partition it describes.
- class oaknut.filesystem.Confidence(*values)¶
How sure a filesystem is it identified a region — higher is surer.
Candidates are ranked by this first, so an unambiguous magic number beats a structural heuristic, which beats a guess from size alone.
- POSSIBLE = 10¶
Only weak signals agree — image size, or nothing but the extension.
- PROBABLE = 20¶
Structural heuristics pass (e.g. a well-formed DFS catalogue).
- STRONG = 30¶
Heuristics plus an integrity check agree (checksum, redundant copy).
- CERTAIN = 40¶
An unambiguous on-disc magic number was found (e.g.
AFS0).
- class oaknut.filesystem.Identification(filesystem, confidence, evidence=(), geometry=None, ambiguities=(), partition=None, reserved_regions=(), contained=<factory>)¶
What a filesystem reports a region to be.
An empty
filesystemmarks a region that something reserved but no installed filesystem recognised — so a host disc with an unhandled tail still shows the tail honestly.- Parameters:
- confidence: Confidence¶
How sure the identification is.
- ambiguities: tuple[Geometry, ...] = ()¶
Equally-plausible geometries the content cannot distinguish.
- reserved_regions: tuple[Partition, ...] = ()¶
Regions reserved within this one, for the coordinator to recurse into.
- contained: tuple[Identification, ...]¶
Identifications of those reserved regions (filled by the coordinator).
- with_contained(contained)¶
A copy with
containedset (this dataclass is frozen).- Parameters:
contained (tuple[Identification, ...])
- Return type:
- class oaknut.filesystem.Partition(name, start_sector, num_sectors, index=0)¶
A named logical sector region within its parent.
Sectors, not bytes, so a region of an interleaved host (an ADFS-L floppy’s AFS tail) is contiguous: the bytes are scattered, but the logical sectors are a run. The coordinator materialises the bytes through the host’s geometry (see
region_reader()).name is the user-facing selector label — the filesystem the region is identified as (
"adfs","afs") — and index disambiguates several partitions of the same kind (afs.0,afs.1).
- class oaknut.filesystem.Volume(designation, surface=0)¶
One independently-addressable volume within a filesystem.
Most filesystems are a single volume spanning the whole image (a sole, undesignated
Volume). A double-sided DFS image is the exception: each physical surface is an independent volume with its own catalogue, title and free space, addressed by a filesystem-specific designation — the Acorn drive token:0/:2for the DFS family. The designation is the very string a user types to reach the volume, and is what diagnostics quote (never the internalsurfaceindex).
The coordinator¶
Discovery and enumeration over the oaknut.filesystem axis. Every
installed filesystem package contributes automatically, so the set grows
with what is installed; nothing here imports a concrete filesystem.
- oaknut.filesystem.identify(source, *, suffix_hint=None, filesystems=None)¶
Identify the filesystem(s) on source, best candidate first.
Returns the whole-image candidates ranked by confidence (extension only a tie-breaker), each with its reserved regions recursively identified in
Identification.contained. An empty list means no installed filesystem recognised the image.filesystems overrides the discovered set — used by tests and to simulate a partial install (the extensibility invariant).
- Parameters:
source (ImageReader | bytes | bytearray | memoryview | str | Path)
filesystems (dict[str, Filesystem] | None)
- Return type:
- oaknut.filesystem.filesystem_names()¶
The entry-point names of every registered filesystem.
- oaknut.filesystem.describe_filesystem(name, *, single_line=False)¶
The description of one filesystem (its class docstring).
- oaknut.filesystem.create_filesystem(name)¶
Instantiate one filesystem by name.
- Parameters:
name (str)
- Return type:
- oaknut.filesystem.creating_filesystem(suffix, *, filesystems=None)¶
The filesystem that creates suffix images by default, or
None.disc createinfers the filesystem from the target’s extension via each filesystem’screatesset. ReturnsNonewhen no installed filesystem is the default creator for the extension (so the user must pass--filesystem).
Reading images¶
- class oaknut.filesystem.ImageReader(data, *, base=0, length=None, suffix=None, writable=False, _closeables=())¶
A clamped, read-only view over a region of a disc image.
Construct via
reader_for(). A reader covers[base, base + size)of an underlying buffer (bytes,memoryviewormmap.mmap); all offsets passed toread(),find(), andwindow()are relative to this region.- property writable: bool¶
Whether
write()(and a livebuffer()) is available.True when the reader is backed by writable storage (a file mapped for writing). Windows inherit it from their parent.
- property suffix: str | None¶
The source file extension (lower-cased, with dot), if known.
A tie-breaking hint for the coordinator only — never present on a window, and filesystems identify by content and ignore it.
- read(offset, length)¶
Return up to length bytes at offset, clamped to the region.
Reading at or past the end yields
b""; a range overrunning the end yields only the bytes that exist.
- write(offset, data)¶
Write data at region-relative offset, into the backing store.
Only valid on a writable reader (see
writable()); the write reaches the underlying file/buffer directly. Writing past the end of the region is an error rather than a silent truncation.
- buffer()¶
A buffer over this region for the underlying filesystem class.
On a writable reader this is a live window onto the backing store, so mutations the filesystem makes reach the file. On a read-only reader it is a private copy, so a stray write cannot corrupt a shared (possibly read-only-mapped) backing.
- Return type:
- find(needle, start=0)¶
Region-relative offset of the first needle at/after start, or -1.
Searches only within this region, in place (via
mmap.find/bytes.find) so a large image is not copied.
- window(offset, length)¶
A sub-region reader over
[offset, offset + length).Shares this reader’s backing buffer (no copy) and is clamped to what is available. It does not own the underlying file/mmap — the top-level reader does — so closing a window is a no-op.
- Parameters:
- Return type:
- oaknut.filesystem.ImageSource¶
alias of
ImageReader|bytes|bytearray|memoryview|str|Path
- oaknut.filesystem.reader_for(source, *, suffix_hint=None, writable=False)¶
Build an
ImageReaderfor source.Accepts an existing reader (returned unchanged), an in-memory buffer, or a filesystem path. Path sources are memory-mapped so a large hard-disc image is not read wholesale; the mapping is released when the reader is closed. writable maps the file for writing, so writes through the reader (and mutations of
ImageReader.buffer()) reach the file directly — used by the mutating CLI commands.- Parameters:
source (ImageReader | bytes | bytearray | memoryview | str | Path)
writable (bool)
- Return type:
Exceptions¶
- exception oaknut.filesystem.FilesystemError(*args, exit_code=None)¶
Base for errors raised by the oaknut filesystem layer.
- exception oaknut.filesystem.GeometryError(*args, exit_code=None)¶
A geometry specification could not be parsed, or is invalid.
- exception oaknut.filesystem.NoSuchVolumeError(*args, exit_code=None)¶
A volume designation addresses a volume the image does not have.
Raised when a path names a volume (a DFS drive,
:2) that the geometry has no surface for — e.g. drive:2on a single-sided image. The addressed thing is absent, so this carries the “path not found” exit code. The message quotes the designation, never the internal surface index.
- exception oaknut.filesystem.VolumeNotFormattedError(*args, exit_code=None)¶
An addressed volume’s surface carries no valid filesystem.
The surface exists in the geometry but holds no valid structure — e.g. the second side implied for a length-ambiguous image turns out to be unformatted, so the image was single-sided after all. The data on that surface is not what the filesystem requires, so this carries the invalid-data exit code (the
FilesystemErrordefault).
- exception oaknut.filesystem.ReadOnlyFilesystemError(*args, exit_code=None)¶
A mutating operation was attempted on a read-only mount.
Raised by a
Mountwhose backing filesystem cannot be written (a ZIP archive, say). Carries the “operation not permitted” exit code so the CLI reports a clear refusal rather than a generic data error.
- exception oaknut.filesystem.FilesystemExtensionError(*args, exit_code=None)¶
A filesystem extension could not be discovered or loaded.
A missing or mis-registered filesystem plug-in is an environment problem, so this inherits the configuration exit code via
ExtensionError.