From 06af7e7fbb852d4ab6213b017593712a28c5ca58 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Sun, 6 Sep 2026 13:23:31 -0400 Subject: [PATCH] Reading a script must not move the person who started it A script is fetched a block at a time while its lines run, and its name is resolved afresh for every block. A name with a drive in front of it moves the machine to that drive on the way past - sbfsWalk calls sbfsUse - so a script found in the system's place on drive 0, started by somebody standing on a disk of their own, ran its lines on the system disk. Always possible with "do 0:/Apps/setup.sh", and reachable by typing a name now that the search finds scripts the same three places it finds programs. The drive is kept across each fetch and put back after it, at both places a script's name is resolved. The test has to work for it. A script that fits in one block is read entirely while it is being opened, and the opening was never the hard part; and the keep in scriptFill cannot be broken on its own, because scriptOpen has already written the variable down. So the script on the disk crosses two block boundaries and moves itself between them: what it says about where it is standing is 1 before the move and 0 after. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW --- Programs/CosmOS/Source/script.asm | 43 ++++++++++++++++++++++++++++ SplitBit Test Manual.md | 2 +- Tests/expected/cosmosScriptDrive.out | 18 ++++++++++++ Tests/input/cosmosScriptDrive.in | 5 ++++ Tests/makedisks.sh | 22 ++++++++++++++ Tests/manifest | 13 +++++++++ 6 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 Tests/expected/cosmosScriptDrive.out create mode 100644 Tests/input/cosmosScriptDrive.in diff --git a/Programs/CosmOS/Source/script.asm b/Programs/CosmOS/Source/script.asm index 940649d..d92ee0c 100644 --- a/Programs/CosmOS/Source/script.asm +++ b/Programs/CosmOS/Source/script.asm @@ -154,8 +154,10 @@ scriptOpenFirst: INIB 0d63 CALL copyText + CALL scriptKeepDrive SETD.0 ScriptName SWI osFileInfo + CALL scriptGiveDriveBack BRQ scriptOpenThere INIA 0x01 BRI scriptOpenFailed @@ -426,6 +428,7 @@ scriptFill: OR BRQ scriptFillNoMore + CALL scriptKeepDrive SETD.0 ScriptName SETD.1 ScriptBlock SETD.2 ScriptIndex @@ -433,6 +436,7 @@ scriptFill: INCD.2 LDB.2 SWI osFileBlock + CALL scriptGiveDriveBack BNQ scriptFillNoMore ; DP3 is how many bytes came back. The nought goes after them. @@ -488,6 +492,40 @@ scriptFillNoMore: ADD RET +; ---- Reading a script must not move the person ---- +; +; The name is resolved afresh for every block, and a name with a drive in front of it moves +; the machine to that drive as a side effect of being resolved - sbfsWalk calls sbfsUse on +; the way past. So a script found in the system's place on drive 0, started by somebody +; standing on a disk of their own, would run its lines on the system disk instead of theirs. +; +; It matters more now than it used to. `do 0:/Apps/setup.sh` was always able to do this; +; typing a name now finds scripts the same three places a program is looked for, and the +; third of them is drive-qualified. +; +; The answer that survives the CALL: Q into A and the byte count onto the stack, because +; sbfsUse writes Q and RET puts DP3 back the way it found it. +scriptKeepDrive: + INA 0x24 + SETD.1 ScriptDrive + STA.1 + RET + +scriptGiveDriveBack: + PSHD.3 + MVQA + PSHA + SETD.1 ScriptDrive + LDA.1 + CALL sbfsUse + POPA + POPD.3 + ; A holds what Q held. Adding nought to it is how it becomes Q again. + RSTB + CCF + ADD + RET + ; Everything up to and including the next line feed, thrown away. Used for the shebang. scriptSkipLine: CALL scriptByte @@ -541,6 +579,11 @@ LoudWord: ScriptDepth: 0x00 +; The drive the person is standing on, kept across a block being fetched. Not part of the +; saved state below: it is only ever live for the length of one read. +ScriptDrive: + 0x00 + ; ---- Seventy bytes, and they are next to each other on purpose ---- ; ; Name, blocks left, next block, where in the block, and whether it is echoing: the whole of diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index 3967bc0..716a7e1 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -125,7 +125,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`. 212 tests, of which 150 run, 35 +everything it printed against a file in `Tests/expected`. 213 tests, of which 151 run, 35 only assemble, 16 are expected to fail to assemble, and 11 boot from ROM with no image given at all. diff --git a/Tests/expected/cosmosScriptDrive.out b/Tests/expected/cosmosScriptDrive.out new file mode 100644 index 0000000..6561a81 --- /dev/null +++ b/Tests/expected/cosmosScriptDrive.out @@ -0,0 +1,18 @@ +CosmOS +> drive 1 +> drive +1 +> where.sh +> echo the script is running +the script is running +> drive +1 +> drive 0 +> drive +0 +> drive +0 +> exit +halted +Execution halted. +[exit 0] diff --git a/Tests/input/cosmosScriptDrive.in b/Tests/input/cosmosScriptDrive.in new file mode 100644 index 0000000..8433866 --- /dev/null +++ b/Tests/input/cosmosScriptDrive.in @@ -0,0 +1,5 @@ +drive 1 +drive +where.sh +drive +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 15d4609..865cd71 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -227,6 +227,28 @@ python3 -c "open('twoblocks.txt','w').write('the second disk, at length. ' * 20) "$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 +# A SCRIPT in the system's place, which is the one thing on this disk that is fetched from +# drive 0 a block at a time WHILE it runs. Its own name carries the drive in front of it, +# and resolving a name like that moves the machine to that drive - so a script started from +# another disk would run its lines on this one unless something puts the drive back between +# every block. All it does is say where it thinks it is, twice, with a move in between. +python3 -c " +# LONGER THAN A BLOCK on purpose, twice over. A script that fits in one block is read +# entirely while it is being opened, and the opening is not the hard part - the refills are, +# because they happen after the drive has been given back to whoever typed the name. +# +# And it moves itself between the two refills, which is the second half of the same rule: a +# script that goes to another disk has to stay there, so a refill puts back the drive the +# script left the machine on and not the one it was opened from. +def pad(so_far): + out = '' + while so_far + len(out) < 300: + out += '; pad\\n' + return out +one = '#!\\necho the script is running\\ndrive\\n' +two = 'drive 0\\n' +open('where.sh', 'w').write(one + pad(len(one)) + two + pad(len(two)) + 'drive\\n')" +"$TOOL" put "$DISKS/cosmos.img" where.sh /Apps/where.sh >/dev/null # ---- Scripts, including the ones that are meant to go wrong ---- # diff --git a/Tests/manifest b/Tests/manifest index 537a780..1f871e6 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -1123,6 +1123,19 @@ cosmosDrivePath | CosmOS/Source/cosmos.asm | run | cosmosDri # 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 +# ---- And a SCRIPT run from the other disk ---- +# +# The same question one step harder. A program is wholly in memory before its first +# instruction runs, so fetching it can move the drive and putting it back afterwards is +# enough. A script is read a block at a time WHILE its lines run, and its name carries the +# drive it lives on - so every refill resolves "0:/Apps/where.sh" and every resolution moves +# the machine to drive 0 unless something puts it back. What the script says about where it +# is standing is the check, and it must say 1. +# +# THEN IT MOVES ITSELF and says so again, across another block boundary. A refill has to put +# back the drive the script left the machine on rather than the one it was opened from, or a +# script that goes to another disk to work is dragged home between two of its own lines. +cosmosScriptDrive | CosmOS/Source/cosmos.asm | run | cosmosScriptDrive.in | 60000000 | disks/cosmos.img+disks/other.img # ---- A beat a program sets for itself ---- # # That reading the status is what takes the tick down, and that without the repeat bit it