Work across two disks: copy between them, and run a program from one on

files from the other

Two things anybody expects of a second disk, and each needed something
different.

COPYING NEEDED TWO THINGS TO REMEMBER A DRIVE.

The write stream is the only thing here that lives across service calls, so
it is the only thing whose drive can change underneath it: every
osFileBlock names its source path again and goes back to the source drive,
and then osFileWrite has to come home. It records the drive it was opened
on and returns there.

And the file lookup CACHE. It keeps the last path resolved so a reader
walking a file does not re-walk the directory for every block - and
skipping the walk skipped the drive the path named, so block one of a
cross-drive copy read the source's block numbers off the DESTINATION disk.
It only showed on files of more than one block, because a file of one is
never looked up twice. One block worked and two did not, which is a
suspicious enough shape to have suspected sooner.

RUNNING A PROGRAM FROM ELSEWHERE NEEDED A THIRD PLACE TO LOOK, and two
restorations.

The shell tried where you are and /Apps on the disk you are on. It now
tries /Apps on drive 0 as well, which is what makes the system's programs
work from a disk of your own - one with your files on it and no system,
which is most of the point of having a second disk.

The drive goes back after the load, because by then the program is in
memory and the blocks it came from mean nothing; and again when it exits,
because a program that copies between disks moves the drive as its own
paths need to and being left wherever it finished is not what was asked
for. Copy 1:/a 0:/b now leaves you exactly where you were.

The fixture disk grew an /Apps, because it kept its programs at the root
and so could not exercise the third place at all.

Two hours of the debugging above were spent on a stale disk image. The
machine boots the system that is ON the image, so a rebuilt cosmos.bin
means nothing until the image is rebuilt too - and the trace said my new
code never ran, which was true. Third time this project has been misled by
one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
This commit is contained in:
Anachronaut
2026-08-31 16:14:53 -04:00
co-authored by Claude Opus 5
parent 4cc6393f5b
commit e4f4bae762
12 changed files with 223 additions and 21 deletions
+22 -4
View File
@@ -836,10 +836,28 @@ tells the two apart. `2things` is a directory; `2:` is a drive.
A drive the machine cannot read makes the whole path unfindable, and says so as `no such
file` - which it is, since there is nowhere for the rest of it to be.
**Copying between two disks is still not one command.** Each path resolves on its own drive
and the drive stays where the last path left it, so a copy would read from one and write to
the other with only one of them selected. That wants the copy itself to change drives between
blocks, which is a change to `Copy` rather than to paths.
**Copying between two disks is one command**: `Copy 1:/notes.txt 0:/keep.txt`. Every
`osFileBlock` names its path again and so goes back to the source drive; the write stream
remembers the drive it was opened on and returns there for each block. Between them the copy
walks back and forth without `Copy` itself knowing there is more than one disk.
### Where A Program Is Looked For:
Three places, tried in order:
1. Where you are.
2. `/Apps` on the disk you are on.
3. `/Apps` on drive 0.
The first makes a program you are working on the one that runs. The second makes `Snake` work
from any directory. **The third makes the system's programs work from a disk of your own** -
one with your files on it and no system - which is most of the point of having a second disk.
**Fetching a program does not move you, and neither does running one.** The drive is put back
after the load, because by then the program is in memory and the block numbers it came from
mean nothing; and put back again when the program exits, because a program that copies between
two disks moves the drive as its own paths need it to and being left wherever it finished is
not what anybody asked for. So `Copy 1:/a 0:/b` leaves you exactly where you were.
### Bank Numbers Are One Namespace:
+99 -11
View File
@@ -437,14 +437,24 @@ promptUnknown:
LDA.0
BRA promptSayUnknown ; No filesystem, so there is nothing to look through.
; TWO PLACES, TRIED IN ORDER: where you are, and then the system's own place for
; programs. The first is what makes a program you are working on the one that runs; the
; second is what makes Snake work from anywhere without a copy of it in every directory.
; Neither is stored anywhere, so there is nothing to configure and nothing to go stale.
; THREE PLACES, TRIED IN ORDER: where you are, the system's own place for programs on the
; disk you are on, and then that same place on drive 0. The first is what makes a program
; you are working on the one that runs; the second is what makes Snake work from anywhere
; without a copy of it in every directory; the third is what makes it work from a disk of
; your own, which has your files on it and no system.
;
; None of them is stored anywhere, so there is nothing to configure and nothing to go
; stale.
RSTA
SETD.0 NamePrefix
STA.0
; Where the person is, kept so it can be given back. A program is fetched from wherever it
; lives and then runs on the files of whoever ran it.
INA 0x24
SETD.0 SearchDrive
STA.0
promptSearch:
CALL nameProgram
SETD.0 NameOk
@@ -454,7 +464,22 @@ promptSearch:
CALL loadProgram
SETD.0 LoadStatus
LDA.0
BRA runLoaded ; It loaded, and the machine is its now.
BNA promptNotLoaded
; ---- Loaded, so the drive goes back to the person ----
;
; The program is in memory now and the block numbers it came from mean nothing any more,
; which is what makes this safe here and not in the middle of a path. A program fetched
; from the system disk then runs on the disk its user was standing on - which is the whole
; point of being able to keep a disk of your own.
SETD.0 SearchDrive
LDA.0
CALL sbfsUse
BRI runLoaded ; It loaded, and the machine is its now.
promptNotLoaded:
SETD.0 LoadStatus
LDA.0
; No file of that name is not a fault. It is the ordinary case of a word this shell does
; not know, and it is the only answer worth looking somewhere else for. Anything else
@@ -467,10 +492,13 @@ promptSearch:
BNQ loadFailed
promptElsewhere:
; Was that already the second place?
; Was that the last place there is?
SETD.0 NamePrefix
LDA.0
BNA promptSayUnknown
INIB 0d2
CCF
SUB
BRQ promptGaveUp
; A word beginning with a separator has said where to look, and looking somewhere else
; would be answering a different question from the one asked.
@@ -481,11 +509,19 @@ promptElsewhere:
SUB
BRQ promptSayUnknown
INIA 0x01
SETD.0 NamePrefix
LDA.0
INCA
STA.0
BRI promptSearch
promptGaveUp:
; Every place has been tried, and the drive is put back before anybody is told anything:
; the search moved it, and a word the shell does not know should not move somebody either.
SETD.0 SearchDrive
LDA.0
CALL sbfsUse
promptSayUnknown:
; Saying which word was not understood is worth the four instructions: it tells somebody
; who mistyped what they actually typed.
@@ -760,7 +796,15 @@ nameProgram:
SETD.0 NamePrefix
LDA.0
BRA nameFromLine
; One is the system's place on this disk; two is the same place on drive 0.
INIB 0d2
CCF
SUB
BRQ nameSystemApps
SETD.0 AppsPrefix
BRI namePrefixCopy
nameSystemApps:
SETD.0 SystemAppsPrefix
namePrefixCopy:
LDA.0
BRA nameFromLine
@@ -1789,6 +1833,13 @@ scriptNoName:
SETD.0 ScriptUsage
BRI fileComplain
; Where the person was standing when a program was started, so it can be given back when the
; program has finished with the machine. A program that copies between two disks moves the
; drive as its own paths need it to; that is its business, and being left on the disk it
; happened to finish with is not what the person asked for.
;
; SET WHEREVER A PROGRAM STARTS, of which there are two: run, and typing a program's name.
; Restored in handleExit, which is the one place they both come back through.
; ---- run ----
;
; Hands the machine to whatever was loaded. Where the Stack is now is written down first,
@@ -1805,6 +1856,9 @@ doRun:
; program's own name it is what followed the name, which is the same thing meaning the
; same thing.
runLoaded:
INA 0x24
SETD.0 RunDrive
STA.0
MVSD.0
SETD.1 SystemStack
STD.0.1
@@ -2151,9 +2205,20 @@ fileLookup:
CALL textSame
BNQ fileLookupSearch
; The same file as last time. The description still has to be put back, because anything
; that went to the disk in between - a directory listing, a program being loaded - left
; its own answer in those three.
; ---- The same file as last time, and possibly not the same disk ----
;
; The description has to be put back, because anything that went to the disk in between - a
; directory listing, a program being loaded - left its own answer in those three.
;
; AND THE DRIVE WITH IT. Skipping the walk skips the drive the path named, so a cache hit
; on a machine that had moved read the right block numbers off the wrong disk. Copying
; between two disks is exactly that: block 0 walks and goes to the source, the write goes to
; the destination, and block 1 hits this cache. It only showed on files of more than one
; block, because a file of one is never looked up twice.
SETD.0 FileCacheDrive
LDA.0
CALL sbfsUse
SETD.0 SbfsFileStart
SETD.2 FileCacheStart
CALL sbfsSetWord
@@ -2196,6 +2261,14 @@ fileLookupSearch:
SETD.0 FileCacheTail
STA.0
; ---- And which disk those block numbers are on ----
;
; A start block means nothing without it. The walk above went to whatever drive the path
; named, so the answer is the drive now.
INA 0x24
SETD.0 FileCacheDrive
STA.0
; Marked good last, so that a cache half filled is never a cache believed.
INIA 0d1
SETD.0 FileCacheValid
@@ -2622,6 +2695,13 @@ handleExit:
SETD.1 LastStatus
STA.1
; The drive the person was on, whatever the program did with it.
PSHA
SETD.1 RunDrive
LDA.1
CALL sbfsUse
POPA
; And the line that started it failed, if the program says it did. The script reader asks
; one question - did this line work - and a program answering "no" is one of the ways it
; can be answered.
@@ -3831,6 +3911,8 @@ Separator:
; next, and there is no such place yet.
AppsPrefix:
"/Apps/"
SystemAppsPrefix:
"0:/Apps/"
IsDirectory:
"that is a directory"
AndText:
@@ -4106,6 +4188,12 @@ PrintNumber:
;
; The name is twenty three bytes for a name of twenty two, so that a name filling the
; field still has a zero after it and can be compared as a string.
FileCacheDrive:
0x00
SearchDrive:
0x00
RunDrive:
0x00
FileCacheValid:
0x00
FileCacheName:
+22
View File
@@ -2932,6 +2932,16 @@ sbfsStreamNoTemp:
SETD.2 SbfsFileStart
CALL sbfsSetWord
; ---- And which disk all of that is on ----
;
; A write stream is the one thing here that lives across service calls, so it is the one
; thing that can have the drive changed underneath it. Copying between two disks is exactly
; that: every osFileBlock re-resolves the SOURCE path and goes to its drive, and then
; osFileWrite has to come back here. The path was walked above, so the drive is right now.
INA 0x24
SETD.0 SbfsStreamDrive
STA.0
INIA 0x01
SETD.0 SbfsStreamOpen
STA.0
@@ -2941,6 +2951,13 @@ sbfsStreamNoTemp:
ADD
RET
; Back to the disk the open stream belongs to, whatever has been read in between.
sbfsStreamHere:
SETD.0 SbfsStreamDrive
LDA.0
CALL sbfsUse
RET
sbfsStreamNo:
RSTA
INIB 0d1
@@ -2954,6 +2971,7 @@ sbfsStreamWrite:
SETD.0 SbfsStreamOpen
LDA.0
BRA sbfsStreamNo
CALL sbfsStreamHere
; Where the file is and how big it is, said again rather than looked up: all three were
; settled when the temporary was made, and everything since has been describing whatever
@@ -2980,6 +2998,7 @@ sbfsStreamFetch:
SETD.0 SbfsStreamOpen
LDA.0
BRA sbfsStreamNo
CALL sbfsStreamHere
SETD.0 SbfsStreamTo
STD.1.0
@@ -3016,6 +3035,7 @@ sbfsStreamDone:
SETD.0 SbfsStreamOpen
LDA.0
BRA sbfsStreamNo
CALL sbfsStreamHere
; What it really came to, put aside before anything walks the disk.
SETD.0 SbfsStreamNewBlocks
@@ -3639,6 +3659,8 @@ SbfsTempName:
"sbfs.part"
; ---- What a file being written a block at a time keeps ----
SbfsStreamDrive:
0x00
SbfsStreamOpen:
0x00
SbfsStreamPath:
+1 -1
View File
@@ -79,7 +79,7 @@ from `make`, not from here.
### 1. Recorded output
`Tests/run.sh` assembles each program named in `Tests/manifest`, runs it, and compares
everything it printed against a file in `Tests/expected`. 181 tests, of which 119 run, 35
everything it printed against a file in `Tests/expected`. 182 tests, of which 120 run, 35
only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image
given at all.
+30
View File
@@ -0,0 +1,30 @@
CosmOS
> > copied
finished
> 1
> it says: from drive one
finished
> 1
> > greet.sbx 211
hello.sbx 53
Life.sbx 1396
Snake.sbx 2164
Keys.sbx 664
Say.sbx 156
Break.sbx 149
Grid.sbx 543
notes.txt 21
Apps <dir>
hi.script 121
bad.script 45
plain.script 24
cross.script 280
nonl.script 38
outer.script 376
inner.script 44
loop.script 35
crossed.txt 560
18 files, 1 directory
> halted
Execution halted.
[exit 0]
+4 -2
View File
@@ -9,6 +9,7 @@ Say.sbx 156
Break.sbx 149
Grid.sbx 543
notes.txt 21
Apps <dir>
hi.script 121
bad.script 45
plain.script 24
@@ -17,11 +18,12 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
17 files
17 files, 1 directory
> > other.txt 28
notes <dir>
2things <dir>
1 file, 2 directories
twoblocks.txt 560
2 files, 2 directories
> /notes> > /notes> > /notes> drive: this machine has no such drive
/notes> halted
Execution halted.
+2 -1
View File
@@ -13,6 +13,7 @@ Say.sbx 156
Break.sbx 149
Grid.sbx 543
notes.txt 21
Apps <dir>
hi.script 121
bad.script 45
plain.script 24
@@ -21,7 +22,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
17 files
17 files, 1 directory
> halted
Execution halted.
[exit 0]
+2 -1
View File
@@ -9,6 +9,7 @@ Say.sbx 156
Break.sbx 149
Grid.sbx 543
notes.txt 21
Apps <dir>
hi.script 121
bad.script 45
plain.script 24
@@ -17,7 +18,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
17 files
17 files, 1 directory
> load what?
> no such file
> not a program
+2 -1
View File
@@ -8,6 +8,7 @@ Say.sbx 156
Break.sbx 149
Grid.sbx 543
notes.txt 21
Apps <dir>
hi.script 121
bad.script 45
plain.script 24
@@ -16,7 +17,7 @@ nonl.script 38
outer.script 376
inner.script 44
loop.script 35
17 files
17 files, 1 directory
> loaded, starting at 4000
> it says: the disk took its time
finished
+8
View File
@@ -0,0 +1,8 @@
drive 1
Copy 1:/twoblocks.txt 0:/crossed.txt
drive
Say from drive one
drive
cd 0:/
dir
exit
+17
View File
@@ -127,6 +127,23 @@ printf 'this lives on the other disk' > other.txt
# A name that begins with a digit, because a drive prefix is a digit and a colon and the
# colon is the whole of what tells them apart. Without it this would be drive 2.
"$TOOL" mkdir "$DISKS/other.img" /2things >/dev/null
# A file of more than one block, because a cross-drive copy of a single block never looks
# the path up twice and so never meets the cache that used to hand back the right blocks of
# the wrong disk.
python3 -c "open('twoblocks.txt','w').write('the second disk, at length. ' * 20)"
"$TOOL" put "$DISKS/other.img" twoblocks.txt >/dev/null
# ---- And a /Apps on drive 0, because that is where a system keeps its programs ----
#
# The apps above sit at the root of this image, which is where they were put before there
# were directories and is fine for finding a program on the disk you are standing on. The
# THIRD place the shell looks is /Apps on drive 0, and nothing could exercise it while this
# disk had no such place.
"$TOOL" mkdir "$DISKS/cosmos.img" /Apps >/dev/null
"$ROOT/Assembler" -I "$ROOT/Programs/Libraries" -I "$ROOT/Programs/CosmOS/Source" \
"$ROOT/Programs/CosmOS/Apps/Copy.asm" -o "$WORK/Copy.sbx" >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Copy.sbx" /Apps/Copy.sbx >/dev/null
"$TOOL" put "$DISKS/cosmos.img" "$WORK/Say.sbx" /Apps/Say.sbx >/dev/null
# ---- Scripts, including the ones that are meant to go wrong ----
#
+14
View File
@@ -824,6 +824,20 @@ cosmosDrives | CosmOS/Source/cosmos.asm | run | cosmosDri
# system arrives, so it works for anything that takes one. A name beginning with a digit is
# still a name: the colon is the whole of what tells the two apart.
cosmosDrivePath | CosmOS/Source/cosmos.asm | run | cosmosDrivePath.in | 60000000 | disks/cosmos.img+disks/other.img
# ---- Working across two disks ----
#
# The two things anybody expects of a second disk: copying to it, and running a program that
# lives on one disk over files that live on the other.
#
# THE FILE IS MORE THAN ONE BLOCK ON PURPOSE. A copy of a single block looks its source up
# once and never meets the cache that skips the walk - and skipping the walk skipped the
# drive, so block one of every cross-drive copy came off the destination. One block worked
# and two did not.
#
# Say lives in /Apps on drive 0 and is run while standing on drive 1, which is the third
# place the shell looks. The drive it says afterwards is the check that fetching a program
# did not move the person who ran it.
cosmosCrossDisk | CosmOS/Source/cosmos.asm | run | cosmosCrossDisk.in | 90000000 | disks/cosmos.img+disks/other.img
printDecimalTest | testPrograms/printDecimalTest.asm | xfail | - | -
printDigitTest | testPrograms/printDigitTest.asm | xfail | - | -
printHexTest | testPrograms/printHexTest.asm | xfail | - | -