Disc image sector abstractions shared by Acorn filesystem packages
Project description
oaknut-discimage
Sector-level disc-image abstractions shared by the oaknut filesystem
packages. This is the geometry layer: it turns a flat image file into
addressable sectors, and is deliberately blind to what those sectors
mean. The disc-based filing systems — oaknut-dfs, oaknut-adfs, and
oaknut-afs — build the sector→files layer on top of it.
(The sequential filing systems are a different shape. oaknut-romfs, a
Cassette-Filing-System block stream on a paged ROM, has no real sectors:
it scans its byte stream directly and uses only SurfaceSpec to hand the
framework a single trivial linear surface — see
ROMFS, below.)
Part of the oaknut monorepo.
The model
A disc image is a flat run of bytes; a filing system wants numbered sectors. The mapping between the two is physical geometry — track count, sector size, sidedness, interleave — and it is exactly what this package models, in a small stack of abstractions that compose bottom-up:
raw bytes (mmap / bytearray) the image file
↓ described by
SurfaceSpec ── one per surface ── byte ⇄ sector geometry
↓ gathered into
DiscImage(buffer, [SurfaceSpec, …]) the whole image
↓ .surface(i)
Surface one physical side
↓ .sector_range(start, count)
SectorsView a read/write window of sectors
Above a Surface, a filing system reads and writes through the
SectorsView it hands back. Crucially that view is a live window onto
the backing buffer: writing through it lands in the mmap and thus on
disc. The package never copies sector data.
The abstractions
SurfaceSpec — the geometry of one surface
A frozen dataclass describing how one surface's sectors are laid out in the image buffer:
| Field | Meaning |
|---|---|
num_tracks |
tracks on the surface (e.g. 40 or 80) |
sectors_per_track |
sectors per track (10 for Acorn DFS) |
bytes_per_sector |
usually 256 (BYTES_PER_SECTOR) |
track_zero_offset_bytes |
where this surface's track 0 begins in the buffer |
track_stride_bytes |
byte distance from one of this surface's tracks to the next |
The offset and stride are where interleaving lives. A single-sided image is contiguous: offset 0, stride one track. An interleaved double-sided image alternates the two sides track by track, so each side's stride is two tracks and side 1 starts one track in. A sequential double-sided image stores all of side 0 then all of side 1, so side 1's offset is the whole first half. The layers above never see any of this — they ask for logical sectors and the spec does the arithmetic.
DiscImage — the buffer plus its surfaces
DiscImage(buffer, surface_specs) pairs the raw memoryview with one or
more SurfaceSpecs, validates that the surfaces fit and do not overlap,
and vends a Surface per spec:
image = DiscImage(buffer, specs)
side0 = image.surface(0) # a Surface
image.num_surfaces # 1 for SSD, 2 for DSD
Surface — one physical side
A single side of the disc. Its job is to resolve a logical sector range to the underlying bytes:
view = side0.sector_range(start_sector=2, num_sectors=1) # → SectorsView
side0.num_sectors, side0.num_tracks
SectorsView — a logical buffer over sectors
Presents one or more (possibly non-contiguous, thanks to track stride)
memoryviews as a single addressable buffer. It indexes and slices like a
bytearray, and writes pass straight through to the backing image:
view[0] # read a byte
view[0:8] = b"DISK " # write — persists to the buffer (and the file)
view.tobytes() # a plain bytes copy
This is the read/write surface every filing system actually operates on.
UnifiedDisc — many surfaces as one address space
Some filing systems treat a double-sided disc as a single logical disc
with one continuous sector numbering (all of side 0, then all of side
1); others treat each side as an independent drive. UnifiedDisc is the
former view — it maps a unified sector number to the right
(surface, sector) pair and delegates to Surface:
disc = UnifiedDisc(image)
disc.sector_range(start_sector, num_sectors) # spans both sides seamlessly
ADFS reads a double-sided floppy through a UnifiedDisc; DFS, where each
side is a separate *DRIVE, talks to each Surface directly. For a
single-sided disc a UnifiedDisc is a trivial pass-through.
DiscFormat — geometry plus filing system
DiscFormat(surface_specs, catalogue_name) is a complete format: the
surface layout together with the name of the filing system that lives on
it. Filesystem packages compose these into named constants and ship them
alongside the filing system they describe — for example
oaknut.dfs.formats.ACORN_DFS_80T_SINGLE_SIDED.
Geometry helpers
Building SurfaceSpec lists by hand is fiddly, so the common layouts
have constructors:
single_sided_spec(num_tracks, sectors_per_track, bytes_per_sector=256)interleaved_double_sided_specs(...)sequential_double_sided_specs(...)
BYTES_PER_SECTOR (256) is the Acorn sector size, and
open_image_mmap(path) opens an image with the right access — writable
when the host file permits, read-only otherwise (mutations then raise at
the mmap layer), so DFS, ADFS, and AFS all behave identically.
Putting it together
from oaknut.discimage import DiscImage, single_sided_spec
buffer = memoryview(bytearray(b"\x00" * (80 * 10 * 256))) # 80T × 10 sectors
image = DiscImage(buffer, [single_sided_spec(num_tracks=80, sectors_per_track=10)])
surface = image.surface(0)
sector0 = surface.sector_range(0, 1)
sector0[0:8] = b"MYDISC " # write the catalogue title into sector 0
assert bytes(buffer[0:8]) == b"MYDISC " # it landed in the backing buffer
ROMFS: a sequential exception
Not every Acorn filing system is sector-addressed. The ROM Filing System
(oaknut-romfs) stores files as a Cassette-Filing-System block stream on
a paged ROM — a flat, sequential byte run with no tracks or sectors. It
reads that stream directly and never calls sector_range. It depends on
this package only to borrow SurfaceSpec and BYTES_PER_SECTOR for a
single, trivial linear surface (num_tracks=1, the whole ROM as one
"track"), purely so it can present a Geometry to the oaknut-filesystem
framework. So while ROMFS lists this package as a dependency, it is not
a consumer of the sector abstractions described above.
Relationship to the rest of oaknut
This package owns the physical mapping (bytes ⇄ sectors); a filing
system owns the logical mapping (sectors ⇄ files and directories).
That separation mirrors oaknut-filesystem, whose Geometry /
Filesystem split sits one level up: a filing system reads logical
sectors and is blind to interleave, sidedness, and on-disc byte offsets —
all of which live here.
Licence
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oaknut_discimage-12.10.0.tar.gz.
File metadata
- Download URL: oaknut_discimage-12.10.0.tar.gz
- Upload date:
- Size: 20.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1057fbcaa578b3a29591a61bf64194bd834bdb3358cb982b3d4b22f2e245a02
|
|
| MD5 |
971c204139f6d3727ef833dc30d66967
|
|
| BLAKE2b-256 |
3e7a86840a88ec163f69de1c965b3dafa5eaeb76e517c8a3852d7b3a98379014
|
File details
Details for the file oaknut_discimage-12.10.0-py3-none-any.whl.
File metadata
- Download URL: oaknut_discimage-12.10.0-py3-none-any.whl
- Upload date:
- Size: 15.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.23 {"installer":{"name":"uv","version":"0.11.23","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
276c6b5ddb3b7a28096da4a4df2f1818daae434519165706b3f00242b9b7dee9
|
|
| MD5 |
9a393211f103a037dd95c8542ae758f4
|
|
| BLAKE2b-256 |
730963e7fd331ba5bbda9906b219722ae23e3c8feb32f8b4f0486b9c4b97ffa2
|