oaknut.adfs¶
ADFS — the Acorn Advanced Disc Filing System used by the BBC Master, the Archimedes, and RISC OS machines. Unlike DFS it has a directory hierarchy and a free-space map; it spans small (S), medium (M), and large (L) floppy layouts as well as hard-disc images addressed through an explicit geometry.
Every name documented here is importable directly from oaknut.adfs.
The filesystem¶
- class oaknut.adfs.ADFS(unified_disc, dir_format, fsm, geometry)¶
Handle to an open ADFS disc image.
The ADFS object provides disc-level metadata and serves as the factory for ADFSPath objects. File and directory operations are performed through ADFSPath.
Example:
with ADFS.from_file("games.adf") as adfs: games = adfs.root / "Games" for entry in games: print(entry.name, entry.stat().length) data = (games / "Elite").read_bytes()
- Parameters:
unified_disc (UnifiedDisc)
dir_format (ADFSDirectoryFormat)
fsm (OldFreeSpaceMap)
geometry (ADFSGeometry)
- property closed: bool¶
Whether this handle has been closed.
Once closed, any I/O operation raises
oaknut.file.FilesystemClosedError. Pure path manipulation on path objects bound to this handle continues to work.
- close()¶
Mark this handle as closed; idempotent.
Normally invoked automatically when the
from_file()/create_file()withblock exits.- Return type:
- static from_file(filepath)¶
Open an ADFS disc image file as a context manager.
For floppy images (
.adf,.adl), auto-detects the format from the image size.For hard disc images (
.dat), requires a companion.dscsidecar file alongside it containing SCSI disc geometry. Either the.dator.dscfile may be specified — the companion is located by swapping the extension.The image is opened writable when host filesystem permissions allow, read-only otherwise. Mutations attempted against a read-only-backed image raise from the mmap layer at the point of write.
- Parameters:
- Yields:
ADFS instance backed by the file.
- Raises:
FileNotFoundError – If the file or its companion does not exist.
ADFSError – If the image is not a valid ADFS disc.
- Return type:
- classmethod from_buffer(buffer)¶
Create ADFS from a buffer, auto-detecting format.
For known floppy sizes (160KB, 320KB, 640KB), uses the corresponding ADFS S/M/L format. For other sizes, treats the buffer as a flat hard disc image (single surface).
- Parameters:
buffer (memoryview) – Disc image data.
- Returns:
ADFS instance.
- Raises:
ADFSError – If the image is not a valid ADFS disc.
- Return type:
- classmethod create(adfs_format, *, title='', boot_option=0)¶
Create a new in-memory ADFS disc image with an empty root directory.
- Parameters:
adfs_format (ADFSFormat) – ADFS format (ADFS_S, ADFS_M, or ADFS_L).
title (str) – Disc title (default empty).
boot_option (int) – Boot option 0–3 (default 0).
- Returns:
ADFS instance backed by an in-memory buffer.
- Return type:
- static create_file(filepath, adfs_format=None, *, capacity=None, cylinders=None, heads=4, sectors_per_track=33, title='', boot_option=0)¶
Create a new ADFS disc image file with an empty root directory.
For floppy images, pass an
ADFSFormat:with ADFS.create_file("disc.adl", ADFS_L, title="MyDisc") as adfs: ...
For hard disc images (
.dat), specify either a capacity or explicit geometry. A companion.dscsidecar file is written automatically:# By capacity (str or int bytes). Geometry chosen automatically. with ADFS.create_file("scsi0.dat", capacity="10MB") as adfs: ... with ADFS.create_file("scsi0.dat", capacity=10 * 1024 * 1024) as adfs: ... # By explicit geometry with ADFS.create_file("scsi0.dat", cylinders=306, heads=4) as adfs: ...
- Parameters:
filepath (str | PathLike) – Path for the new disc image file.
adfs_format (ADFSFormat) – Floppy format (ADFS_S, ADFS_M, or ADFS_L).
capacity (int | str | None) – Minimum hard disc capacity.
intis bytes;straccepts"10MB","40MiB","1024kB"etc. — seeoaknut.file.capacity.parse_capacity()for the full suffix table.cylinders (int) – Number of cylinders (hard disc).
heads (int) – Number of heads (default 4, hard disc only).
sectors_per_track (int) – Sectors per track (default 33, hard disc only).
title (str) – Disc title (default empty).
boot_option (int) – Boot option 0–3 (default 0).
- Yields:
ADFS instance backed by the file.
- Return type:
- path(path)¶
Create an ADFSPath from a path string.
Routes through
ADFSPath.__truediv__()so the Acorn-shell^parent token (and consecutive^^) are interpreted the same way as in a slash-joined chain.
- property geometry: ADFSGeometry¶
Authoritative disc geometry (cylinders, heads, sectors per track).
- property boot_option: BootOption¶
Boot option as a
oaknut.file.BootOptionenum.
- property has_afs_partition: bool¶
Whether this disc carries Level 3 File Server pointers.
Truewhen an AFS partition is installed in the tail of this old-map ADFS disc (info-sector pointers at&F6/&1F6of the old map are non-zero and parse cleanly). Cheap to call — does not construct an AFS handle.
- property afs_partition¶
Return the AFS partition handle for this disc.
Returns an
oaknut.afs.AFShandle sharing this disc’sUnifiedDisc. Cached on first access so repeated reads return the same instance until that instance is closed.The returned handle does not own the underlying file — keep this ADFS context manager alive for as long as the AFS handle is in use. The caller is responsible for the AFS handle’s lifecycle: either call
AFS.close()explicitly or useopen_afs_partition()which yields the same handle as a context manager.- Raises:
AFSNotPresentError – If the disc has no AFS pointers. Use
has_afs_partitionto test for presence without provoking the exception.
- open_afs_partition()¶
Open the AFS partition as a context manager.
Yields the
afs_partitionhandle and callsAFS.close()on clean exit orAFS.discard()if the body raises — so the in-flight writes survive only when thewithblock completes successfully. This is the preferred idiom for AFS operations that need a lifecycle bound to a scope.- Raises:
AFSNotPresentError – If the disc has no AFS partition.
- validate()¶
Validate filesystem integrity.
Returns a list of
oaknut.adfs.exceptions.ADFSValidationErrorinstances — empty when the image is clean. Callers iterate the list to present every defect rather than aborting on the first.- Return type:
list[ADFSValidationError]
- compact()¶
Defragment the disc by rebuilding with sequential sector allocation.
Reads all files and directories into memory, reinitialises the disc structures, and writes everything back with contiguous sector allocation. The result is a single free space region at the end of the disc.
- Returns:
Number of objects (files and directories, excluding root) written.
- Return type:
- class oaknut.adfs.ADFSPath(adfs, path)¶
-
- EntryExistsError¶
alias of
ADFSEntryExistsError
- DirectoryError¶
alias of
ADFSPathError
- supports_title = True¶
A path within an ADFS filesystem, inspired by pathlib.Path.
ADFSPath objects are lightweight handles that reference an ADFS filesystem and a normalised absolute path string. They do not cache directory contents, so they always reflect the current state of the disc image.
Navigation uses the
/operator:games = adfs.root / "Games" elite = games / "Elite"
Iterate over directory contents:
for child in games: print(child.name)
Read file data:
data = elite.read_bytes()
- stat()¶
Return metadata for this path.
- Raises:
ADFSPathError – If the path does not exist.
- Return type:
- property title: str¶
Directory title (up to 19 characters).
Distinct from the directory name: the name is the structural component used in paths; the title is a human-readable label stored inside the directory block.
- Raises:
ADFSPathError – If this path is a file, not a directory.
- iterdir()¶
Iterate over directory contents.
- read_bytes()¶
Read file contents.
- Raises:
ADFSPathError – If the path doesn’t exist or is a directory.
- Return type:
- read_basic()¶
Read a BBC BASIC program and return its detokenised source.
Composes
read_bytes()withoaknut.dfs.basic.detokenise(). Never compose a BASIC program withread_text()— tokenised BASIC is bytecode, not text, and decoding it through a character codec will produce garbage.- Raises:
ADFSPathError – If the path doesn’t exist or is a directory.
NotImplementedError – Until the detokeniser is implemented.
- Return type:
- write_bytes(data, *, load_address=0, exec_address=0, access=None, date=None)¶
Write file contents, creating or overwriting the file.
accessaccepts aoaknut.file.Accessvalue, orNonefor the filesystem default (unlocked + owner R+W). Only the locked bit is honoured at the catalogue-write layer; richer flags (owner R/W/E, public R/W) must be applied viachmod()after the write.dateis accepted for cross-filesystem signature uniformity but silently ignored at this layer.- Parameters:
- Raises:
ADFSPathError – If this path is the root directory.
ADFSDiscFullError – If the disc has insufficient free space.
ADFSDirectoryFullError – If the parent directory is full.
- Return type:
- write_basic(source, *, load_address=6400, exec_address=0, access=None)¶
Write a BBC BASIC program, tokenising the source first.
Composes
oaknut.dfs.basic.tokenise()withwrite_bytes(). Defaults the load address to the BBC Micro’s canonical0x1900; passoaknut.dfs.basic.ELECTRON_BASIC_LOAD_ADDRESSfor Electron programs.- Parameters:
- Raises:
ADFSPathError – If this path is the root directory.
ADFSDiscFullError – If the disc has insufficient free space.
ADFSDirectoryFullError – If the parent directory is full.
NotImplementedError – Until the tokeniser is implemented.
- Return type:
- unlink()¶
Delete this file.
- Raises:
ADFSPathError – If the path is root, doesn’t exist, or is a directory.
ADFSFileLockedError – If the file is locked.
- Return type:
- mkdir(*, parents=False, exist_ok=False)¶
Create a new directory at this path.
Mirrors
pathlib.Path.mkdir().- Parameters:
- Raises:
ADFSPathError – If the path is root, parent is not found (and
parentsisFalse), or already exists as a file. Also whenexist_okisFalseand the path already exists.ADFSDiscFullError – If the disc has insufficient free space.
ADFSDirectoryFullError – If the parent directory is full.
- Return type:
- rmdir()¶
Remove an empty directory.
- Raises:
ADFSPathError – If the path is root, doesn’t exist, is not a directory, or is not empty.
ADFSFileLockedError – If the directory is locked.
- Return type:
- rename(target)¶
Rename this file or directory, returning the new path.
Moving across directories is supported.
- lock()¶
Lock this file.
- Raises:
ADFSPathError – If the path is root or doesn’t exist.
- Return type:
- unlock()¶
Unlock this file.
- Raises:
ADFSPathError – If the path is root or doesn’t exist.
- Return type:
- chmod(access)¶
Set access attributes, replacing the current ones.
Uses the
AccessIntFlag enum:from oaknut.adfs.directory import Access path.chmod(Access.R | Access.W | Access.L)
Only the owner R, W, L, and E attributes are affected. Public/private NFS attributes are preserved.
- set_load_address(address)¶
Set the load address without rewriting the file data.
- set_exec_address(address)¶
Set the exec address without rewriting the file data.
- export_file(target_filepath, *, meta_format=MetaFormat.INF_TRAD, owner=0)¶
Export file to host filesystem, emitting Acorn metadata.
- Parameters:
target_filepath (str | PathLike) – Destination path on the host.
meta_format (MetaFormat | None) – How to encode metadata. Defaults to traditional INF sidecar. Pass
Noneto write only the data. Filename-encoded formats rewrite the target filename.owner (int) – Econet owner ID, used only by PiEconetBridge formats.
- Returns:
The actual path that was written. Equal to target_filepath except for filename-encoded formats.
- Return type:
- import_file(source_filepath, *, meta_formats=(MetaFormat.INF_TRAD, MetaFormat.XATTR_ACORN, MetaFormat.FILENAME_RISCOS))¶
Import a file from the host filesystem.
The ADFS filename is taken from this
ADFSPath, not from the source file or any sidecar. Metadata is resolved by trying meta_formats in order; the first reader to match wins. The full Acorn attribute byte (R/W/E/L/PR/PW) is applied viachmod()after the data has been written, so owner-execute and public permissions round-trip losslessly viaMetaFormat.INF_PIEBor either xattr format.- Parameters:
source_filepath (str | PathLike) – Path to the source file on the host.
meta_formats (Sequence[MetaFormat]) – Ordered cascade of metadata schemes to try. Defaults to
DEFAULT_IMPORT_META_FORMATS.
- Return type:
- class oaknut.adfs.ADFSStat(length, load_address, exec_address, locked, owner_read, owner_write, owner_execute, public_read, public_write, public_execute, is_directory)¶
File/directory metadata, analogous to os.stat_result.
Conforms to
oaknut.file.Stat—accessis synthesised from the per-owner / per-public bits, anddateis alwaysNone(ADFS dates live at the directory level and are not currently surfaced here).- Parameters:
Disc formats¶
An ADFSFormat describes one ADFS layout. The three
standard floppy formats are provided as constants, and
IMAGE_FORMAT_BY_EXTENSION maps a filename extension
to its format (None for the extensions whose size must be measured
instead).
- class oaknut.adfs.ADFSFormat(surface_specs, total_sectors, total_bytes, label)¶
ADFS disc format specification.
- Parameters:
surface_specs (list[SurfaceSpec])
total_sectors (int)
total_bytes (int)
label (str)
- oaknut.adfs.ADFS_S = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=40, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=4096)], total_sectors=640, total_bytes=163840, label='S')¶
ADFS disc format specification.
- oaknut.adfs.ADFS_M = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=80, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=4096)], total_sectors=1280, total_bytes=327680, label='M')¶
ADFS disc format specification.
- oaknut.adfs.ADFS_L = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=80, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=8192), SurfaceSpec(num_tracks=80, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=4096, track_stride_bytes=8192)], total_sectors=2560, total_bytes=655360, label='L')¶
ADFS disc format specification.
- oaknut.adfs.IMAGE_FORMAT_BY_EXTENSION = {'.adf': None, '.adl': ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=80, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=8192), SurfaceSpec(num_tracks=80, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=4096, track_stride_bytes=8192)], total_sectors=2560, total_bytes=655360, label='L'), '.adm': ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=80, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=4096)], total_sectors=1280, total_bytes=327680, label='M'), '.ads': ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=40, sectors_per_track=16, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=4096)], total_sectors=640, total_bytes=163840, label='S'), '.dat': None}¶
dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object’s
(key, value) pairs
- dict(iterable) -> new dictionary initialized as if via:
d = {} for k, v in iterable:
d[k] = v
- dict(**kwargs) -> new dictionary initialized with the name=value pairs
in the keyword argument list. For example: dict(one=1, two=2)
Hard-disc geometry¶
Hard-disc images carry an explicit cylinders/heads/sectors geometry,
held in an ADFSGeometry and persisted alongside
the image in a 22-byte .dsc sidecar.
- class oaknut.adfs.ADFSGeometry(cylinders, heads, sectors_per_track=33)¶
Authoritative disc geometry.
For hard disc images this comes from the
.dscsidecar file. For floppies it is derived from the format constants (ADFS_S/M/L).
- oaknut.adfs.geometry_for_capacity(capacity_bytes, *, heads=4, sectors_per_track=33)¶
Compute a disc geometry that meets or exceeds a requested capacity.
Returns a
ADFSGeometrywith the minimum number of cylinders needed to provide at least capacity_bytes of storage.- Parameters:
- Returns:
Geometry with cylinders computed from the capacity.
- Raises:
ValueError – If capacity_bytes is not positive.
- Return type:
- oaknut.adfs.write_dsc(filepath, geometry)¶
Write a 22-byte
.dscsidecar file with SCSI disc geometry.The sidecar carries cylinders/heads/sectors-per-track for a hard disc image so
ADFS.from_file()can address sectors via CHS.- Parameters:
geometry (ADFSGeometry)
- Return type:
See also¶
ADFS access bits use the shared Access enum, which
belongs to oaknut.file (see also
File metadata); import it from there.