CLI cookbook¶
Recipes that compose disc with shell tooling for real
end-to-end tasks. Example data lives under
tests/data/images/ in the project’s test fixtures, so every
recipe is runnable as-is.
Finding files by pattern¶
To locate files by name, walk the catalogue with disc find. The
pattern is an Acorn wildcard expression sitting in the INNER_PATH
half of the COMPOUND_PATH, so it is quoted the same way as any other
in-image path:
$ disc find 'infinity.ssd:*Edit'
matches
┏━━━━━━━━━━━┓
┃ Path ┃
┡━━━━━━━━━━━┩
│ $.MapEdit │
│ $.DefEdit │
│ $.SprEdit │
└───────────┘
$ disc find 'infinity.ssd:MDROM*'
matches
┏━━━━━━━━━━┓
┃ Path ┃
┡━━━━━━━━━━┩
│ $.MDROM4 │
│ $.MDROM7 │
│ $.MDROM6 │
│ $.MDROM5 │
└──────────┘
The two patterns demonstrate the complementary shapes — *Edit
finds every file ending in the literal text Edit (the
editor suite), and MDROM* finds every file starting with
MDROM (the sideways ROM images). Matching is case-insensitive
and the * may appear anywhere in the pattern.
For the full wildcard grammar (including # for “any single
character”) and the shell quoting that keeps the pattern out of the
shell’s hands, see Wildcards.
Bulk-export a disc to your host filesystem¶
To extract a whole disc to your host filesystem, use
disc export. Each file is written alongside an .inf sidecar
so the load / exec / length / attribute metadata survives the
crossing.
$ disc export infinity.ssd extracted
$ ls -C 'extracted/$'
!BOOT GAME2 Linker MDROM6 MapEdit SprEdit
!BOOT.inf GAME2.inf Linker.inf MDROM6.inf MapEdit.inf SprEdit.inf
DefEdit LOAD MDROM4 MDROM7 REPTON
DefEdit.inf LOAD.inf MDROM4.inf MDROM7.inf REPTON.inf
GAME LOADER MDROM5 MENU Screen
GAME.inf LOADER.inf MDROM5.inf MENU.inf Screen.inf
$ cat 'extracted/$/LOAD.inf'
LOAD 00031900 00038023 0000010F 03
The .inf file is the traditional Acorn metadata format: one
line of five whitespace-separated fields — the Acorn filename, the
load address, the exec address, the length in bytes, and the access
byte. The default --meta-format inf-trad produces this form;
modern alternatives (xattr-acorn, filename-riscos, etc.) are
documented in File metadata.
The host tree round-trips back onto a disc with disc import,
preserving everything .inf captured. Inspect or edit on the host
using your normal tools, then push the changes back.
Copying files across filing-system formats¶
Copies span any combination of DFS, ADFS, and AFS images: source
and destination need not share a format because disc cp maps
Acorn metadata across them for you. (AFS here is the Acorn Level 3
File Server’s partition format — sometimes called AFS0 after the
magic at its head; see the glossary for the
longer note.)
$ disc cp 'infinity.ssd:$.MENU' 'stash.adl:$.Menu'
$ disc cp 'infinity.ssd:$.REPTON' 'stash.adl:$.Repton'
$ disc ls stash.adl
stash.adl — STASH (adfs)
┏━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ Menu │ file │ 0x00001100 │ 0x00001100 │ 2195 │ WR/ │
│ Repton │ file │ 0x00002B00 │ 0x00002E00 │ 1280 │ WR/ │
└────────┴──────┴────────────┴────────────┴────────┴──────┘
Free: 649,984 bytes
Notice that the Repton MENU and REPTON files came from a
DFS catalogue (no per-file access bits, just a “locked” flag) and
arrived on an ADFS disc with the full WR/R access pair —
disc cp filled in the defaults the source format could not
provide. Load and exec addresses survived intact, and the Acorn
case rule (case-preserving, case-insensitive) means renaming on
the way from $.MENU to $.Menu is a real change to how the
file displays, not a no-op.
The full attribute-mapping rules (which bits map across which filesystems, and where information is lost in either direction) live in File metadata.
Browse a ZIP archive¶
A ZIP archive is a filesystem too. disc recognises it by content
like any disc, and the same ls / tree / cat / get
commands work against it — so a ZIP of RISC OS files is browsable
without unpacking it first.
$ disc identify riscos.zip
riscos.zip
┏━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Confidence ┃ Filesystem ┃ Partition ┃ Evidence ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ CERTAIN │ zip │ zip │ ZIP signature (local file header) at │
│ │ │ │ offset 0 │
└────────────┴────────────┴───────────┴────────────────────────────────────────┘
The archive here holds RISC OS files whose filetype is carried in the
,xxx filename suffix. disc presents the flat ZIP namespace as a
directory tree — synthesising the Docs directory the archive only
implies — and decodes the suffix into the filetyped load address:
$ disc ls riscos.zip
riscos.zip (zip)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !Boot │ file │ 0xFFFFEB00 │ 0x00000000 │ 7 │ │
│ Docs │ dir │ │ │ 2 │ │
│ Sprites │ file │ 0xFFFFF900 │ 0x00000000 │ 11 │ │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
$ disc tree riscos.zip
riscos.zip
├── !Boot
├── Docs
│ ├── Manual
│ └── ReadMe
└── Sprites
disc get extracts a member to the host with its metadata sidecar, so
the filetype survives the trip out:
$ disc get 'riscos.zip:Sprites' Sprites
$ cat Sprites.inf
Sprites FFFFF900 00000000 0000000B
The mount is read-only: disc put / rm / mv into a ZIP are not
supported. The metadata recovery itself — SparkFS extras, bundled
.inf sidecars, and filename encoding — belongs to the oaknut-zip
package, which the ZIP filesystem wraps.
Archive a folder of SSDs to one ADFS hard disc¶
You have a directory full of DFS .ssd floppies on your host
and want them all sitting on a single ADFS hard disc, each under
its own subdirectory named for the source.
Three lines of shell — a for loop wrapping a single
disc cp -r per SSD — do the work.
1. Create an empty archive disc.
$ disc create games.dat --geometry capacity=10MB --title Games
A 10 MB ADFS hard-disc image is plenty for three DFS floppies-
worth of content; --title Games sets the name that *CAT
will display.
2. Look at the source filenames.
$ ls *.ssd
Disc001-PlanetoidAKADefender.ssd
Disc001-SnapperV2.ssd
Disc002-Arcadians.ssd
Disc003-Zalaga.ssd
Each SSD is named DiscNNN-Title.ssd — a disc-number prefix
followed by the game title. The loop in the next step pulls the
title’s first word out of each filename and uses it as the
subdirectory name on the archive disc.
3. Loop the SSDs, copying each into its own subdirectory.
$ for ssd in *.ssd; do
> name="$(basename "$ssd" .ssd | sed -E 's/.*-([A-Z][a-z]+).*/\1/')"
> disc cp -r "$ssd:\$" "games.dat:\$.$name"
> done
The interesting moves:
The
sed -E 's/.*-([A-Z][a-z]+).*/\1/'expression captures the first PascalCase word after the hyphen, yieldingPlanetoid/Arcadians/Zalaga. Longer titles likePlanetoidAKADefenderget truncated at the first uppercase letter, which fits comfortably inside ADFS’s 10-character filename limit.disc cp -r SOURCE:$ TARGET:$.NAMErecursively copies every file under the DFS directory$into$.NAMEon the archive disc. The destination directory is created automatically — same convention as Unixcp -r SRC DESTwhenDESTdoes not exist. No explicitdisc mkdiris required.The disc-side
$characters appear as\$inside the double-quoted shell arguments: the arguments must be double-quoted (not single-quoted) so$ssdand$nameexpand, and inside double quotes the shell would otherwise treat the bare$as the start of a variable name. Escaping with a backslash passes a literal$through todisc. See Shell quoting cheat sheet for the broader rules.
Note the silence: each successful disc cp -r writes nothing,
so the 18-file copy across three SSDs produces no stdout chatter.
4. Verify the archive.
$ disc ls games.dat
games.dat — Games (adfs)
┏━━━━━━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━╇━━━━━━━━╇━━━━━━┩
│ Arcadians │ dir │ │ │ 4 │ │
│ Planetoid │ dir │ │ │ 5 │ │
│ Snapper │ dir │ │ │ 5 │ │
│ Zalaga │ dir │ │ │ 4 │ │
└───────────┴──────┴──────┴──────┴────────┴──────┘
Free: 9,922,816 bytes
$ disc tree games.dat
games.dat
├── Arcadians
│ ├── !BOOT
│ ├── ARC
│ ├── Arcadi2
│ └── ARCADIA
├── Planetoid
│ ├── !BOOT
│ ├── PLANET
│ ├── Planet1
│ ├── Planet2
│ └── PLANETO
├── Snapper
│ ├── !BOOT
│ ├── SNAP
│ ├── Snap2
│ ├── Snappe3
│ └── SNAPPER
└── Zalaga
├── !BOOT
├── ZALAG-L
├── ZALAGA
└── ZALAGA?
The top level of the archive holds three sibling directories named
for the games — one per SSD. Walking the whole thing with
disc tree then exposes each SSD’s catalogue under the matching
directory.
Assemble a double-sided DSD from two SSDs¶
A double-sided disc holds two independent DFS volumes — Acorn
drives :0 and :2 — one per physical surface. To combine two
single-sided .ssd floppies onto one .dsd, create a blank
double-sided image and copy one SSD onto each side. The second side
is addressed with verbatim Acorn drive syntax: image::2.$.
1. The two source SSDs.
$ disc ls arcadians.ssd:$
arcadians.ssd — ARC (acorn-dfs)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !BOOT │ file │ 0x00000000 │ 0x00000000 │ 44 │ LWR/ │
│ ARC │ file │ 0x00001900 │ 0x00001900 │ 2304 │ LWR/ │
│ Arcadi2 │ file │ 0x00001900 │ 0x00003F00 │ 19456 │ LWR/ │
│ ARCADIA │ file │ 0x00001900 │ 0x0000801F │ 1203 │ LWR/ │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
Free: 180,992 bytes
$ disc ls zalaga.ssd:$
zalaga.ssd — Zalaga (acorn-dfs)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !BOOT │ file │ 0x00000000 │ 0x00000000 │ 48 │ LWR/ │
│ ZALAG-L │ file │ 0x00001900 │ 0x00001900 │ 3328 │ LWR/ │
│ ZALAGA │ file │ 0x000023EE │ 0x00002400 │ 2816 │ LWR/ │
│ ZALAGA? │ file │ 0x00003000 │ 0x00004522 │ 11557 │ LWR/ │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
Free: 186,112 bytes
Two single-sided game floppies — Arcadians and Zalaga — each with a
handful of files under $.
2. Create a blank double-sided disc.
$ disc create compendium.dsd
disc create with a .dsd extension lays down an 80-track
double-sided image and formats both sides as empty catalogues, so
each side is a usable volume from the outset. No --title here — both
sides start blank and are named symmetrically further down.
3. Copy one SSD onto each side.
$ disc cp 'arcadians.ssd:$.*' 'compendium.dsd::0.$.'
$ disc cp 'zalaga.ssd:$.*' 'compendium.dsd::2.$.'
The moves:
Each side is addressed explicitly:
compendium.dsd::0.…is drive 0,compendium.dsd::2.…is drive 2. The two colons are the CLI’s image delimiter followed by the Acorn drive colon, preserved verbatim;:0/:2is the drive,$the directory.$.*globs every file in the source’s$directory. The trailing.on the destination —$.— is the Acorn directory marker: “copy into the$directory”. It plays the role Unixcpgives a trailing/, but keeps the path native Acorn. (/is still accepted if you prefer it.) The marker matters because an empty DFS side has no$entry yet, so the destination directory has to be named as such rather than detected.
Each side is an independent volume with its own title, set after the
copy. Addressing a side’s disc title takes the drive with no
path — compendium.dsd::0 / compendium.dsd::2. (::2.$ would
instead ask for the $ directory’s title, which DFS has no concept
of.) There are two ways to supply the name, shown in turn.
4. Name side 0 directly.
$ disc title 'compendium.dsd::0' Arcadians
The straightforward way: type the title. A DFS title holds up to 12
characters, so Arcadians (nine) fits with room to spare.
5. Name side 2 from its source.
$ disc title 'compendium.dsd::2' `disc title zalaga.ssd`
When the source floppy is already named the way you want, read the title
straight off it rather than retyping. The inner `disc title
zalaga.ssd` prints the source’s disc title; the outer disc title
writes it onto side 2 — a command substitution carrying the name across.
6. Verify both sides.
$ disc stat compendium.dsd
Drive :0
┏━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Title ┃ Arcadians ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Size │ 200.0 KiB │
│ Free │ 176.8 KiB │
│ Boot option │ OFF (0) │
│ Files │ 4 │
└─────────────┴───────────┘
Drive :2
┏━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Title ┃ Zalaga ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Size │ 200.0 KiB │
│ Free │ 181.8 KiB │
│ Boot option │ OFF (0) │
│ Files │ 4 │
└─────────────┴───────────┘
$ disc ls compendium.dsd:$
compendium.dsd — Arcadians (acorn-dfs)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !BOOT │ file │ 0x00000000 │ 0x00000000 │ 44 │ LWR/ │
│ ARC │ file │ 0x00001900 │ 0x00001900 │ 2304 │ LWR/ │
│ ARCADI2 │ file │ 0x00001900 │ 0x00003F00 │ 19456 │ LWR/ │
│ ARCADIA │ file │ 0x00001900 │ 0x0000801F │ 1203 │ LWR/ │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
Free: 180,992 bytes
$ disc ls 'compendium.dsd::2.$'
compendium.dsd — Zalaga (acorn-dfs)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !BOOT │ file │ 0x00000000 │ 0x00000000 │ 48 │ LWR/ │
│ ZALAG-L │ file │ 0x00001900 │ 0x00001900 │ 3328 │ LWR/ │
│ ZALAGA │ file │ 0x000023EE │ 0x00002400 │ 2816 │ LWR/ │
│ ZALAGA? │ file │ 0x00003000 │ 0x00004522 │ 11557 │ LWR/ │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
Free: 186,112 bytes
disc stat lists the disc as two volumes, each under the
designation that addresses it — Drive :0 and Drive :2 — with
its own title, file count and free space. Listing each side then shows
the game that now lives there.
Split a double-sided DSD into two SSDs¶
The reverse: lift each side of a .dsd out into its own
single-sided .ssd. Each side is an independent volume, so this is
just two copies — one per side — into two freshly-created SSDs.
1. The two-sided source disc.
$ disc stat compendium.dsd
Drive :0
┏━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Title ┃ Arcadians ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Size │ 200.0 KiB │
│ Free │ 176.8 KiB │
│ Boot option │ OFF (0) │
│ Files │ 4 │
└─────────────┴───────────┘
Drive :2
┏━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Title ┃ Zalaga ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Size │ 200.0 KiB │
│ Free │ 181.8 KiB │
│ Boot option │ OFF (0) │
│ Files │ 4 │
└─────────────┴───────────┘
disc stat shows the DSD as two volumes, Drive :0 and Drive
:2, each a full DFS catalogue.
2. Create the two destination SSDs.
$ disc create side-0.ssd
$ disc create side-2.ssd
3. Copy each side out to its own SSD.
$ disc cp 'compendium.dsd::0.$.*' 'side-0.ssd:$.'
$ disc cp 'compendium.dsd::2.$.*' 'side-2.ssd:$.'
Each side is addressed explicitly — compendium.dsd::0.… and
compendium.dsd::2.…. The $.* glob lifts every file out of each
side’s $ directory into the target SSD, whose own $. names the
destination directory.
4. Verify each extracted SSD.
$ disc ls side-0.ssd:$
side-0.ssd (acorn-dfs)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !BOOT │ file │ 0x00000000 │ 0x00000000 │ 44 │ LWR/ │
│ ARC │ file │ 0x00001900 │ 0x00001900 │ 2304 │ LWR/ │
│ ARCADI2 │ file │ 0x00001900 │ 0x00003F00 │ 19456 │ LWR/ │
│ ARCADIA │ file │ 0x00001900 │ 0x0000801F │ 1203 │ LWR/ │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
Free: 180,992 bytes
$ disc ls side-2.ssd:$
side-2.ssd (acorn-dfs)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !BOOT │ file │ 0x00000000 │ 0x00000000 │ 48 │ LWR/ │
│ ZALAG-L │ file │ 0x00001900 │ 0x00001900 │ 3328 │ LWR/ │
│ ZALAGA │ file │ 0x000023EE │ 0x00002400 │ 2816 │ LWR/ │
│ ZALAGA? │ file │ 0x00003000 │ 0x00004522 │ 11557 │ LWR/ │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
Free: 186,112 bytes
Each single-sided image now holds exactly one side’s catalogue — the DSD has been separated back into the two floppies it was assembled from.
Consolidate Acorn DFS discs onto a higher-capacity Watford DFS disc¶
Acorn DFS caps a disc at 31 files; Watford DFS extends the catalogue to 62 files per side. That difference is the whole point of this recipe. Daily telemetry — one file per day — fills an Acorn disc in a month (up to 31 days), so four months of 1984 temperature readings for Cambridge sit on four separate single-sided Acorn floppies. A single double-sided Watford disc swallows all four: two months a side. It is also a copy from one DFS variant to another.
1. The four monthly Acorn discs.
$ ls *.ssd
telem-8401.ssd
telem-8402.ssd
telem-8403.ssd
telem-8404.ssd
$ disc cat telem-8401.ssd:$.840115
-0.5
-1.1
-1.4
-1.5
-1.4
-1.1
-0.5
0.3
1.1
2.1
3.0
3.9
4.6
5.2
5.5
5.7
5.5
5.2
4.6
3.9
3.0
2.1
1.1
0.3
$ disc stat telem-8401.ssd
ACORN-DFS
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ Title ┃ Jan 84 Temps ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ Size │ 100.0 KiB │
│ Free │ 91.8 KiB │
│ Boot option │ OFF (0) │
│ Files │ 31 │
└─────────────┴──────────────┘
Each .ssd is one month, holding files named 84MMDD — a day’s 24
hourly temperatures in degrees Celsius, carriage-return separated (the
Acorn line ending). January’s catalogue holds 31 files: Acorn’s ceiling,
hit exactly.
2. Create a blank Watford disc.
$ disc create telem.dsd --filesystem watford-dfs
--filesystem watford-dfs is required: disc create infers Acorn
DFS from the .dsd extension, so the Watford variant must be named
explicitly. The double-sided geometry gives two 62-file catalogues.
3. Copy two months onto each side.
$ disc cp 'telem-8401.ssd:$.*' 'telem.dsd::0.$.'
$ disc cp 'telem-8402.ssd:$.*' 'telem.dsd::0.$.'
$ disc cp 'telem-8403.ssd:$.*' 'telem.dsd::2.$.'
$ disc cp 'telem-8404.ssd:$.*' 'telem.dsd::2.$.'
Side 0 takes January and February, side 2 March and April —
telem.dsd::0.$. and telem.dsd::2.$.. Each disc cp reads an
Acorn catalogue and writes a Watford one; the file data and load/exec
addresses carry across unchanged.
4. Name each side.
$ disc title 'telem.dsd::0' 'Jan-Feb 84'
$ disc title 'telem.dsd::2' 'Mar-Apr 84'
A Watford title is 10 characters (two fewer than Acorn’s 12), so
Jan-Feb 84 and Mar-Apr 84 fit exactly.
5. Verify the consolidation.
$ disc stat telem.dsd
Drive :0
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Title ┃ Jan-Feb 84 ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Size │ 200.0 KiB │
│ Free │ 184.0 KiB │
│ Boot option │ OFF (0) │
│ Files │ 60 │
└─────────────┴────────────┘
Drive :2
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━┓
┃ Title ┃ Mar-Apr 84 ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━┩
│ Size │ 200.0 KiB │
│ Free │ 183.8 KiB │
│ Boot option │ OFF (0) │
│ Files │ 61 │
└─────────────┴────────────┘
Side 0 carries 60 files (31 + 29 — 1984 was a leap year) and side 2 carries 61 (31 + 30). Both are well past the 31 a single Acorn disc could hold, which is exactly why the four discs became one.
Creating a Level 3 File Server disc¶
The full walkthrough builds a bootable L3FS hard disc from a fresh
ADFS envelope plus the file-server executable shipped on
tests/data/images/cookbook/FS3v126.ssd.
1. Lay down an empty ADFS hard-disc envelope.
$ disc create scsi0.dat --geometry capacity=10MB --title Server
Here disc create reserves the file on the host and writes the
ADFS catalogue + free-space map. The .dat extension selects ADFS;
--geometry capacity=10MB sizes the hard disc (disc derives a
cylinders/heads/sectors layout for that capacity); and --title
sets the on-disc title that *CAT will display.
The command is silent on success — see Exit codes for the broader contract.
2. Install the file-server binary onto the new disc.
$ disc cp 'FS3v126.ssd:$.FS3v126' 'scsi0.dat:$.FS3v126'
A classic cross-format disc cp — the source $.FS3v126 lives
on a DFS floppy, the destination is the same name on the ADFS
partition of the hard disc we just created. Load and exec
addresses survive the crossing; see File metadata
for the attribute-mapping table.
3. Write a !BOOT command file and turn on autoboot.
$ printf '*RUN $.FS3v126\r' | disc put 'scsi0.dat:$.!BOOT' -
$ disc opt scsi0.dat
Boot option: 0 (OFF)
$ disc opt scsi0.dat EXEC
The !BOOT command file, which will be *EXEC-uted at boot,
contains *RUN $.FS3v126\r — the *RUN invocation plus the
Acorn carriage-return line ending — so that loading the disc
launches the file-server executable. Here printf builds those
bytes on stdout, the shell pipes them in, and the trailing hyphen
tells disc put to read from stdin (the standard Unix
convention). We use printf rather than echo because echo
appends \n on every common shell, and we need \r — see
Getting started for the line-ending rationale.
With no value, disc opt scsi0.dat reads the current boot option
(0 / OFF on a freshly-created disc); passing EXEC sets
it. Symbolic names (OFF / LOAD / RUN / EXEC) are
accepted alongside the numeric forms (0 / 1 / 2 /
3); disc opt --help lists the full mapping.
EXEC is the right choice here because !BOOT is a command
file, not a binary — pressing SHIFT-BREAK runs
*EXEC $.!BOOT, which effectively types the *RUN $.FS3v126
line at the OS prompt.
4. Plan the AFS partition (optional).
$ disc afs plan scsi0.dat
Disc geometry
┏━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Shape ┃ 296 cylinders, 4 heads, 33 sectors/track ┃
┡━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Total │ 39072 sectors (10,002,432 bytes) │
└───────┴──────────────────────────────────────────┘
ADFS occupancy
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Used sectors ┃ 142 ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━┩
│ Free sectors │ 38930 │
│ Free bytes │ 9,966,080 │
└──────────────┴───────────┘
Proposed AFS partition
┏━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ AFS region ┃ 294 cylinders (38808 sectors, 9,934,848 bytes) ┃
┡━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Start cylinder │ 2 │
│ ADFS retained │ 2 cylinders │
│ Compaction │ not required │
│ Suggested command │ disc afs init scsi0.dat --disc-name NAME --cylinders 294 │
└───────────────────┴──────────────────────────────────────────────────────────┘
The afs plan command is a dry-run that shows the disc’s geometry,
how many sectors ADFS currently occupies, and what an AFS partition built
from the remaining free space would look like. Nothing is written
— the step is there to let you review the proposed shape before
committing. Skip it if you know what you want.
5. Initialise the AFS partition.
$ disc afs init scsi0.dat --disc-name Server --user RJS:2MB --omit-user Welcome --emplace Library --emplace Library1
The afs init command carves out the AFS partition for real, adds
an RJS regular user, omits the provided-by-default Welcome
account, and emplaces two shipped library images.
Note the absence of --cylinders: when omitted, afs init
claims the existing free space, which is exactly what afs plan
suggested. Pass an explicit value if you want a smaller AFS region
and ADFS retained beyond what is strictly necessary.
The --emplace option accepts a shipped name (Library,
Library1, ArthurLib) or a path to any ADFS .adl; the
contents land in a directory of the same name on the AFS partition.
6. Inspect the new AFS partition.
$ disc afs users scsi0.dat
users
┏━━━━━━┳━━━━━━━━┳━━━━━━━━━━━┓
┃ User ┃ System ┃ Quota ┃
┡━━━━━━╇━━━━━━━━╇━━━━━━━━━━━┩
│ Syst │ yes │ 190.0 KiB │
│ Boot │ │ 257.0 KiB │
│ RJS │ │ 1.9 MiB │
└──────┴────────┴───────────┘
The disc afs users command confirms the resulting account list:
Syst, Boot, and RJS are present; Welcome is not. The
Syst and Boot accounts are not created explicitly — they
are built-ins and arrive for free with every freshly-initialised
AFS partition (Welcome would too, but for the explicit
omission). To change a built-in’s quota instead of dropping it,
supply --user NAME:QUOTA and the spec overrides the default.
No account has a password unless you ask for one — a freshly
initialised disc leaves even the system account Syst open. The
Level 3 File Server stores passwords as up to six cleartext ASCII
characters (there is no encryption), so the only thing guarding the
file on a real disc is its hidden access byte. Passwords live outside
the --user spec — a password may itself contain a colon, which the
colon-delimited spec could not represent — and are set with their own
--user-password NAME=VALUE option, split once on the first =.
To ship the disc with the system account already protected, add it at
initialisation:
disc afs init scsi0.dat --disc-name Server --user-password Syst=secret
NAME matches a --user or a built-in; set a password later with
disc afs passwd IMAGE NAME --password VALUE.
7. Verify the dual-partition shape and walk the disc.
$ disc stat scsi0.dat
Disc
┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Geometry ┃ 296 cylinders × 4 heads × 33 sectors/track ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ Size │ 9.5 MiB │
└──────────┴────────────────────────────────────────────┘
Partition 1: ADFS
┏━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Title ┃ Server ┃
┡━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ Range │ cylinders 0-1 │
│ Size │ 66.0 KiB │
│ Free │ 30.5 KiB │
│ Boot option │ EXEC (3) │
└─────────────┴───────────────┘
Partition 2: AFS
┏━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ Title ┃ Server ┃
┡━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ Range │ cylinders 2-295 │
│ Size │ 9.5 MiB │
│ Free │ 9.3 MiB │
└───────┴─────────────────┘
$ disc tree scsi0.dat
scsi0.dat
├── adfs
│ ├── !BOOT
│ └── FS3v126
└── afs
├── Library
│ ├── CLOSE
│ ├── CopyFiles
│ ├── Date
│ ├── Discs
│ ├── FindLib
│ ├── FLIP
│ ├── Free
│ ├── FS
│ ├── LCAT
│ ├── LEX
│ ├── NETMON
│ ├── NOTIFY
│ ├── PROT
│ ├── PS
│ ├── RDFREE
│ ├── REMOTE
│ ├── SETFREE
│ ├── SetStation
│ ├── TIME
│ ├── TreeCopy
│ ├── UNPROT
│ ├── USERS
│ └── VIEW
├── Library1
│ ├── Bas128
│ ├── BasObj
│ ├── Discs
│ ├── Free
│ ├── NetMon
│ ├── Notify
│ ├── ReadFree
│ ├── Remote
│ ├── Set
│ ├── SetFree
│ ├── Users
│ └── View
├── Passwords
└── RJS
The stat report confirms the three-block layout — a Disc
envelope carrying the physical geometry, then Partition 1: ADFS
holding the boot configuration and the FS binary, then
Partition 2: AFS ready to serve files over Econet. The
single-partition collapsed form documented in
Output formats: --as does not apply here because the
two partitions genuinely carry different things; the envelope is
the natural umbrella.
Walking the whole image with disc tree then exposes both
halves. The ADFS half is tiny — just !BOOT and the FS3 binary,
which is all the boot needs to load before handing off to AFS.
The AFS half shows the two emplaced library trees in full, with
the BBC-era utilities (LCAT, NETMON, PROT, USERS,
…) that the Level 3 File Server’s clients reach for via
*<command> once the server is up.
A checksum table for every file on a disc¶
To pair every in-image path with a checksum of its bytes — to verify
a transfer, compare two copies, or spot-check a build — disc
for-each writes the table in one command. The default
--mode content pipes each file’s bytes to the command; the
command’s stdout becomes that file’s row. When stdout is captured,
disc writes TSV:
disc for-each 'image.ssd:*' -- <command> > checksums.tsv
Three choices for <command>.
A CRC32 from cksum¶
On stdin, cksum emits <crc32> <bytes>:
$ disc for-each 'archive.ssd:*' -- cksum
# Path Output
$.GAME 3018728591 4096
$.LOADER 4215202376 256
$.!BOOT 604583140 15
$.README 4000662245 28
CRC32 is a compact fingerprint, fine for spotting differences between two copies of a file. It’s also computable on the BBC Micro itself.
An MD5 from md5sum¶
md5sum (GNU coreutils) gives a longer fingerprint — a
32-character hex digest that downstream tooling and published
archives commonly cite:
$ disc for-each 'archive.ssd:*' -- md5sum
# Path Output
$.GAME 620f0b67a91f7f74151bc5be745b7110 -
$.LOADER 348a9791dc41b89796ec3808b5b5262f -
$.!BOOT c0e767f1599f992d06b6dda9476724ce -
$.README fe753d767f0e17ac224753ef86619feb -
The trailing `` -`` is md5sum’s standard marker for input read
from stdin.
Trimming the marker¶
For a clean <path>\t<hash> table, strip the `` -`` from the
stream:
$ disc for-each 'archive.ssd:*' -- md5sum | sed 's/ -$//'
# Path Output
$.GAME 620f0b67a91f7f74151bc5be745b7110
$.LOADER 348a9791dc41b89796ec3808b5b5262f
$.!BOOT c0e767f1599f992d06b6dda9476724ce
$.README fe753d767f0e17ac224753ef86619feb
The for-each output is text; the rest of the shell’s text tools work
normally — awk to rename columns, sort for ordering, grep
-v to drop rows by pattern.
Files containing a string¶
To find every file on a disc whose bytes contain a string — here, the
BBC BASIC keyword PROC — pair disc for-each with grep -c.
The match count for each file becomes the output column:
$ disc for-each 'code.ssd:*' -- grep -c PROC
# Path Output
$.DATA 0
$.HELLO 0
$.MENU 1
$.MAIN 1
For just the paths of the files that matched, strip the header with
--no-header and filter on the count column:
$ disc for-each 'code.ssd:*' --no-header -- grep -c PROC | awk -F'\t' '$2 > 0 { print $1 }'
$.MENU
$.MAIN
Create a game cartridge ROM¶
ROMFS is the paged-ROM filing system of the BBC Micro and Acorn
Electron — a sideways ROM or cartridge. disc create makes one (the
.rom extension infers ROMFS), and the disc romfs commands query
and set its paged-ROM header properties.
Here we put the BBC Micro game Snapper (Acornsoft, 1982) onto a
cartridge: create a fresh 16 KiB ROM with a title and the publisher’s
copyright, then copy the whole game off its DFS floppy with disc cp,
which carries each file’s load and execution addresses across.
$ disc create SNAPPER.rom --title Snapper
$ disc romfs set-copyright SNAPPER.rom '(C) Acornsoft 1982'
$ disc cp 'snapper.ssd:$.*' SNAPPER.rom
$ disc stat SNAPPER.rom
ACORN-ROMFS
┏━━━━━━━┳━━━━━━━━━┓
┃ Title ┃ Snapper ┃
┡━━━━━━━╇━━━━━━━━━┩
│ Files │ 5 │
└───────┴─────────┘
$ disc ls SNAPPER.rom
SNAPPER.rom — Snapper (acorn-romfs)
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━┓
┃ Name ┃ Type ┃ Load ┃ Exec ┃ Length ┃ Attr ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━┩
│ !BOOT │ file │ 0x00000000 │ 0x00000000 │ 45 │ / │
│ SNAP │ file │ 0x00001900 │ 0x00001900 │ 1280 │ / │
│ Snap2 │ file │ 0x00005000 │ 0x00005000 │ 674 │ / │
│ Snappe3 │ file │ 0x00001E00 │ 0x00004500 │ 10112 │ / │
│ SNAPPER │ file │ 0x00001900 │ 0x00001900 │ 1536 │ / │
└─────────┴──────┴────────────┴────────────┴────────┴──────┘
The cartridge responds to *HELP with its title. To use it, switch to
the ROM filing system with *ROM and then use the ordinary filing-system
commands — *CAT to list the files, *EXEC !BOOT to start Snapper,
and *RUN / *LOAD / CHAIN as usual.
Loaded into a BBC Micro (here, an emulator), a session looks like this:
BBC Computer 32K
BASIC
>*HELP
Snapper
OS 1.20
>*ROM
>*CAT
*Snapper*
Snappe3
Snap2
SNAPPER
SNAP
!BOOT
>*EXEC !BOOT
Control storage order to manage seek times¶
A floppy drive — and an emulator faithful to one — seeks from file to file as they are read, so the order files lie in on the disc decides how much the head travels. The fast arrangement keeps the files read first — a boot file, a loader, opening data — in the low-numbered sectors, where the head starts out.
The disc storage-order command reports this physical order — the
files in the order they lie on the disc, from the lowest sector up:
$ disc storage-order snapper.ssd
storage order
┏━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Path ┃ Size ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━┩
│ $.Snappe3 │ 9.9 KiB │
│ $.SNAPPER │ 1.5 KiB │
│ $.Snap2 │ 674 bytes │
│ $.!BOOT │ 45 bytes │
│ $.SNAP │ 1.2 KiB │
└───────────┴───────────┘
$ disc compact snapper.ssd --order '$.!BOOT,$.SNAP'
$ disc storage-order snapper.ssd
storage order
┏━━━━━━━━━━━┳━━━━━━━━━━━┓
┃ Path ┃ Size ┃
┡━━━━━━━━━━━╇━━━━━━━━━━━┩
│ $.!BOOT │ 45 bytes │
│ $.SNAP │ 1.2 KiB │
│ $.Snappe3 │ 9.9 KiB │
│ $.SNAPPER │ 1.5 KiB │
│ $.Snap2 │ 674 bytes │
└───────────┴───────────┘
Here a big 10K file sits in the lowest sectors, so reaching !BOOT
means seeking across the whole disc before loading can even begin.
disc compact --order rewrites the layout, laying the named files down
first, in the lowest sectors; every file it does not name follows in its
existing physical order. The list is a prefix, so naming !BOOT and
the loader is enough to bring them to the front and leave the rest where
they are. The second disc storage-order confirms the result.