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, root_address=2, new_map=None)

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:
property is_new_map: bool

Whether this disc uses the New Map (FileCore zoned allocation).

property uses_typed_metadata: bool

Whether load/exec default to a RISC OS filetype and datestamp.

Follows the directory format, not the map: the Arthur/RISC OS shapes (New and Big directories — including D format, which pairs a New directory with the Old map) fold a filetype and datestamp into load/exec, whereas the 8-bit S/M/L shapes (Old directories) keep genuine load and execution addresses there. Presentation layers use this to decide which reading to show by default.

property access_convention: ADFSAccessConvention

The access convention for this disc’s directory format.

Old directories (S, M and L formats) store owner execute and the private bit; New and Big directories (D, E, F, E+ and F+) do not.

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() with block exits.

Return type:

None

static from_file(filepath, *, read_only=False)

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 .dsc sidecar file alongside it containing SCSI disc geometry. Either the .dat or .dsc file 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. Pass read_only=True to force this even when the file is writable — a caller that only reads a shared or committed image uses it to guarantee the file cannot be modified.

Parameters:
  • filepath (str | PathLike) – Path to the disc image file.

  • read_only (bool) – Force a read-only mapping (default opens writable when the host permits).

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:

Iterator[ADFS]

classmethod from_buffer(buffer, *, prefer_sequential=False)

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).

At 640K the layout is ambiguous — interleaved .adl images and linearly-imaged ones share the same size — so the directory tree is walked under both to pick the one that traverses. When content cannot decide, prefer_sequential (set by the caller from the image’s extension) breaks the tie.

Parameters:
  • buffer (memoryview) – Disc image data.

  • prefer_sequential (bool) – Tiebreak toward the linear 640K layout when content alone cannot distinguish it from interleaved.

Returns:

ADFS instance.

Raises:

ADFSError – If the image is not a valid ADFS disc.

Return type:

ADFS

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:

ADFS

classmethod create_new_map_hard_disc(capacity, *, title='', big_directories=False, boot_option=0)

Create a new-map hard disc image in memory, sized to capacity.

FileCore parameters (zones, idlen, bytes-per-map-bit) are computed for the size. Pass big_directories=True for the E+/F+-style Big directory format. capacity may be an int (bytes) or a string like "20MB".

Raises:

ADFSError – If the size cannot be represented as a New Map disc.

Parameters:
Return type:

ADFS

static create_file(filepath, adfs_format=None, *, capacity=None, cylinders=None, heads=4, sectors_per_track=33, title='', boot_option=0, sidecars=('dsc',))

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 .dsc sidecar 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. int is bytes; str accepts "10MB", "40MiB", "1024kB" etc. — see oaknut.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).

  • sidecars (tuple[str, ...]) – Geometry sidecars to write beside a hard-disc .dat ("dsc" and/or "cfg"; default ("dsc",)).

Yields:

ADFS instance backed by the file.

Return type:

Iterator[ADFS]

property root: ADFSPath

The root directory ($).

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.

Parameters:

path (str) – ADFS path string, e.g. "$.Games.Elite", "$", or "$.Games.^.Docs.ReadMe".

Return type:

ADFSPath

property geometry: ADFSGeometry

Authoritative disc geometry (cylinders, heads, sectors per track).

property title: str

Disc title.

For old-map and New-directory discs this is the 19-character title stored in the root directory. Big-directory discs (E+/F+/G+) have no directory title field, so their label is the disc record’s disc name (up to 10 characters) instead.

property boot_option: BootOption

Boot option as a oaknut.file.BootOption enum.

property free_space: int

Free space in bytes.

property total_size: int

Total disc size in bytes.

property disc_name: str

Disc name (from the disc record on New Map, else the free space map).

property has_afs_partition: bool

Whether this disc carries Level 3 File Server pointers.

True when an AFS partition is installed in the tail of this old-map ADFS disc (info-sector pointers at &F6 / &1F6 of 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.AFS handle sharing this disc’s UnifiedDisc. 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 use open_afs_partition() which yields the same handle as a context manager.

Raises:

AFSNotPresentError – If the disc has no AFS pointers. Use has_afs_partition to test for presence without provoking the exception.

open_afs_partition()

Open the AFS partition as a context manager.

Yields the afs_partition handle and calls AFS.close() on clean exit or AFS.discard() if the body raises — so the in-flight writes survive only when the with block 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.ADFSValidationError instances — 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:

int

export_all(target_dirpath, *, meta_format=MetaFormat.INF_TRAD, owner=0)

Export entire filesystem preserving directory structure.

Parameters:
  • target_dirpath (str | PathLike) – Host directory to export into. Created if missing.

  • meta_format (MetaFormat | None) – Metadata encoding, as for ADFSPath.export_file(). Defaults to traditional INF sidecars; None writes data files only.

  • owner (int) – Econet owner ID, used only by PiEconetBridge formats.

Return type:

None

class oaknut.adfs.ADFSPath(adfs, path)
Parameters:
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()
property parent: ADFSPath

Parent directory.

property name: str

Final component of the path.

property parts: tuple[str, ...]

Path components as a tuple, e.g. ("$", "Games", "Elite").

property path: str

Full path string, e.g. "$.Games.Elite".

exists()

Check whether this path exists on disc.

Return type:

bool

is_dir()

Check whether this path is a directory.

Return type:

bool

is_file()

Check whether this path is a file (not a directory).

Return type:

bool

stat()

Return metadata for this path.

Raises:

ADFSPathError – If the path does not exist.

Return type:

ADFSStat

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.

Raises:

ADFSPathError – If this path is not a directory or doesn’t exist.

Return type:

Iterator[ADFSPath]

read_bytes()

Read file contents.

Raises:

ADFSPathError – If the path doesn’t exist or is a directory.

Return type:

bytes

read_basic()

Read a BBC BASIC program and return its detokenised source.

Composes read_bytes() with oaknut.dfs.basic.detokenise(). Never compose a BASIC program with read_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.

  • DetokeniseError – If the stored program is not valid tokenised BBC BASIC.

Return type:

str

write_bytes(data, *, load_address=0, exec_address=0, access=None, date=None)

Write file contents, creating or overwriting the file.

access accepts a oaknut.file.Access value, applied through the disc’s access convention (so a New-format disc drops owner execute). None gives a new file the default WR/R and leaves a replaced file’s access as it was.

date is accepted for cross-filesystem signature uniformity but silently ignored at this layer.

Parameters:
  • data (bytes) – File contents.

  • load_address (int) – Load address (default 0).

  • exec_address (int) – Execution address (default 0).

  • access (Access | None)

  • date (object)

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:

None

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() with write_bytes(). Defaults the load address to the BBC Micro’s canonical 0x1900; pass oaknut.dfs.basic.ELECTRON_BASIC_LOAD_ADDRESS for Electron programs.

Parameters:
  • source (str) – BBC BASIC source text.

  • load_address (int) – Load address (default 0x1900).

  • exec_address (int) – Execution address (default 0).

  • access (Access | None) – Access flags (see write_bytes()).

Raises:
  • ADFSPathError – If this path is the root directory.

  • ADFSDiscFullError – If the disc has insufficient free space.

  • ADFSDirectoryFullError – If the parent directory is full.

  • TokeniseError – If the source is not valid BBC BASIC.

Return type:

None

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:

None

mkdir(*, parents=False, exist_ok=False)

Create a new directory at this path.

Mirrors pathlib.Path.mkdir().

Parameters:
  • parents (bool) – If True, missing intermediate directories are created. Default False raises when any ancestor is absent.

  • exist_ok (bool) – If True, do not raise when this path already resolves to a directory. A non-directory at the path still raises. Default False.

Raises:
  • ADFSPathError – If the path is root, parent is not found (and parents is False), or already exists as a file. Also when exist_ok is False and the path already exists.

  • ADFSDiscFullError – If the disc has insufficient free space.

  • ADFSDirectoryFullError – If the parent directory is full.

Return type:

None

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:

None

rename(target)

Rename this file or directory, returning the new path.

Moving across directories is supported.

Parameters:

target (str | ADFSPath) – New path (ADFSPath or string like "$.NewName").

Returns:

ADFSPath for the new location.

Raises:

ADFSPathError – If this path doesn’t exist, or target already exists.

Return type:

ADFSPath

lock()

Lock this file.

Raises:

ADFSPathError – If the path is root or doesn’t exist.

Return type:

None

unlock()

Unlock this file.

Raises:

ADFSPathError – If the path is root or doesn’t exist.

Return type:

None

chmod(access)

Set access attributes, replacing the current ones.

Uses the Access IntFlag enum:

from oaknut.adfs.directory import Access
path.chmod(Access.R | Access.W | Access.L)

The owner R, W, E and L and public R and W attributes are replaced. The directory, public-execute and private attributes, which the canonical Access word cannot express, are kept.

Parameters:

access (int) – Combination of Access flags.

Raises:

ADFSPathError – If the path is root or doesn’t exist.

Return type:

None

set_load_address(address)

Set the load address without rewriting the file data.

Raises:

ADFSPathError – If the path doesn’t exist.

Parameters:

address (int)

Return type:

None

set_exec_address(address)

Set the exec address without rewriting the file data.

Raises:

ADFSPathError – If the path doesn’t exist.

Parameters:

address (int)

Return type:

None

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 None to 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:

Path

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 via chmod() after the data has been written, so owner-execute and public permissions round-trip losslessly via MetaFormat.INF_PIEB or 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:

None

class oaknut.adfs.ADFSStat(length, load_address, exec_address, locked, owner_read, owner_write, owner_execute, public_read, public_write, public_execute, is_directory, private=False)

File/directory metadata, analogous to os.stat_result.

Conforms to oaknut.file.Stat — access is synthesised from the per-owner / per-public bits, and date is always None (ADFS dates live at the directory level and are not currently surfaced here).

Parameters:
private: bool = False

The private bit, canonical PL (bit 7).

property access: Access

Access flags as an Access IntFlag, suitable for chmod().

Read through the ADFS access convention.

property date: None

ADFS does not surface per-entry dates here — always None.

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, new_map=False, new_directory=False, big_directories=False)

ADFS disc format specification.

Parameters:
new_map: bool = False

Whether this shape uses the New Map (FileCore zoned allocation) rather than the Old free-space map. Governs which structures create lays down.

new_directory: bool = False

Whether this shape uses New directories (2048-byte, ROR-13 check byte). The New Map shapes always do; the D format pairs New directories with the Old map, so this is set independently. S/M/L use Old directories.

big_directories: bool = False

Whether this shape uses Big directories (the New Map + formats).

validate_title(title)

Raise unless a disc of this format can store title as its title.

The rules match ADFS.title: a Big-directory disc’s title is its 10-character disc name; otherwise it is the root directory’s 19-character title, ASCII in Old directories and Latin-1 in New ones.

Raises:

InvalidTitleError – If title cannot be stored.

Parameters:

title (str)

Return type:

None

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', new_map=False, new_directory=False, big_directories=False)

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', new_map=False, new_directory=False, big_directories=False)

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', new_map=False, new_directory=False, big_directories=False)

ADFS disc format specification.

oaknut.adfs.ADFS_D = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=1, sectors_per_track=3200, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=819200)], total_sectors=3200, total_bytes=819200, label='D', new_map=False, new_directory=True, big_directories=False)

ADFS disc format specification.

oaknut.adfs.ADFS_E = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=1, sectors_per_track=3200, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=819200)], total_sectors=3200, total_bytes=819200, label='E', new_map=True, new_directory=False, big_directories=False)

ADFS disc format specification.

oaknut.adfs.ADFS_E_PLUS = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=1, sectors_per_track=3200, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=819200)], total_sectors=3200, total_bytes=819200, label='E+', new_map=True, new_directory=False, big_directories=True)

ADFS disc format specification.

oaknut.adfs.ADFS_F = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=1, sectors_per_track=6400, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=1638400)], total_sectors=6400, total_bytes=1638400, label='F', new_map=True, new_directory=False, big_directories=False)

ADFS disc format specification.

oaknut.adfs.ADFS_F_PLUS = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=1, sectors_per_track=6400, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=1638400)], total_sectors=6400, total_bytes=1638400, label='F+', new_map=True, new_directory=False, big_directories=True)

ADFS disc format specification.

oaknut.adfs.ADFS_G = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=1, sectors_per_track=12800, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=3276800)], total_sectors=12800, total_bytes=3276800, label='G', new_map=True, new_directory=False, big_directories=False)

ADFS disc format specification.

oaknut.adfs.ADFS_G_PLUS = ADFSFormat(surface_specs=[SurfaceSpec(num_tracks=1, sectors_per_track=12800, bytes_per_sector=256, track_zero_offset_bytes=0, track_stride_bytes=3276800)], total_sectors=12800, total_bytes=3276800, label='G+', new_map=True, new_directory=False, big_directories=True)

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', new_map=False, new_directory=False, big_directories=False), '.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', new_map=False, new_directory=False, big_directories=False), '.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', new_map=False, new_directory=False, big_directories=False), '.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, or a richer BeebSCSI/Pi1MHz .cfg extended-attributes file that also records sectors-per-track.

class oaknut.adfs.ADFSGeometry(cylinders, heads, sectors_per_track=33)

Authoritative disc geometry.

For hard disc images this comes from the .dsc sidecar file. For floppies it is derived from the format constants (ADFS_S/M/L).

Parameters:
  • cylinders (int)

  • heads (int)

  • sectors_per_track (int)

property sectors_per_cylinder: int

Sectors per cylinder (heads x sectors per track).

property total_sectors: int

Total sectors on the disc.

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 ADFSGeometry with the minimum number of cylinders needed to provide at least capacity_bytes of storage.

Parameters:
  • capacity_bytes (int) – Minimum disc capacity in bytes.

  • heads (int) – Number of heads (default 4).

  • sectors_per_track (int) – Sectors per track (default 33).

Returns:

Geometry with cylinders computed from the capacity.

Raises:

ValueError – If capacity_bytes is not positive.

Return type:

ADFSGeometry

oaknut.adfs.write_dsc(filepath, geometry)

Write a 22-byte .dsc sidecar 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:
Return type:

None

oaknut.adfs.write_cfg(filepath, geometry, *, title='')

Write a BeebSCSI/Pi1MHz .cfg extended-attributes sidecar.

The richer counterpart to write_dsc(): its SCSI mode pages record sectors-per-track, so a non-default geometry (an IDE 4x64 layout, say) round-trips faithfully rather than being reported as the Acorn default of 33 the way a .dsc would.

Parameters:
Return type:

None

Access

An old-format ADFS directory entry carries owner read, write, execute and locked bits and public read and write bits. The ADFS access convention maps them to and from the Access word (see AccessConvention).

class oaknut.adfs.ADFSAccessConvention

ADFS access: owner R/W/E/L and public R/W/E map to the canonical bits.

The private bit is canonical bit 7, PL. The directory bit says what an object is rather than who may use it, so writing keeps it from the entry’s current attributes.

oaknut.adfs.ADFS_ACCESS = <oaknut.adfs.access.ADFSAccessConvention object>

owner R/W/E/L and public R/W/E map to the canonical bits.

The private bit is canonical bit 7, PL. The directory bit says what an object is rather than who may use it, so writing keeps it from the entry’s current attributes.

Type:

ADFS access

New and Big directories (the D, E, F, E+ and F+ formats) have no owner execute, public execute or private bit, so they use their own convention; ADFS.access_convention gives the one for a disc.

class oaknut.adfs.ADFSNewDirectoryAccessConvention

ADFS access in New and Big directories (D, E, F, E+ and F+ formats).

The NewDirAtts byte stores owner read, write and locked, public read and write, and the directory bit — but no owner execute, public execute or private bit. Writing therefore drops E, PE and PL, so a run-only file cannot be represented.

oaknut.adfs.ADFS_NEW_DIRECTORY_ACCESS = <oaknut.adfs.access.ADFSNewDirectoryAccessConvention object>

ADFS access in New and Big directories (D, E, F, E+ and F+ formats).

The NewDirAtts byte stores owner read, write and locked, public read and write, and the directory bit — but no owner execute, public execute or private bit. Writing therefore drops E, PE and PL, so a run-only file cannot be represented.

See also

ADFS access bits use the shared Access enum, which belongs to oaknut.file (see also File metadata); import it from there.