diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index ebb2e4f..b839084 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -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: diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index c6931f8..6124339 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -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: diff --git a/Programs/CosmOS/Source/sbfs.asm b/Programs/CosmOS/Source/sbfs.asm index 0f9b5e6..35d5d99 100644 --- a/Programs/CosmOS/Source/sbfs.asm +++ b/Programs/CosmOS/Source/sbfs.asm @@ -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: diff --git a/SplitBit Test Manual.md b/SplitBit Test Manual.md index 250b27e..0848619 100644 --- a/SplitBit Test Manual.md +++ b/SplitBit Test Manual.md @@ -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. diff --git a/Tests/expected/cosmosCrossDisk.out b/Tests/expected/cosmosCrossDisk.out new file mode 100644 index 0000000..77d9c60 --- /dev/null +++ b/Tests/expected/cosmosCrossDisk.out @@ -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 +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] diff --git a/Tests/expected/cosmosDrives.out b/Tests/expected/cosmosDrives.out index 0f02d1a..e2544a4 100644 --- a/Tests/expected/cosmosDrives.out +++ b/Tests/expected/cosmosDrives.out @@ -9,6 +9,7 @@ Say.sbx 156 Break.sbx 149 Grid.sbx 543 notes.txt 21 +Apps 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 2things -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. diff --git a/Tests/expected/cosmosGrid.out b/Tests/expected/cosmosGrid.out index ec1328e..0232830 100644 --- a/Tests/expected/cosmosGrid.out +++ b/Tests/expected/cosmosGrid.out @@ -13,6 +13,7 @@ Say.sbx 156 Break.sbx 149 Grid.sbx 543 notes.txt 21 +Apps 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] diff --git a/Tests/expected/cosmosRun.out b/Tests/expected/cosmosRun.out index f402531..3ac3f21 100644 --- a/Tests/expected/cosmosRun.out +++ b/Tests/expected/cosmosRun.out @@ -9,6 +9,7 @@ Say.sbx 156 Break.sbx 149 Grid.sbx 543 notes.txt 21 +Apps 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 diff --git a/Tests/expected/cosmosSlowDisk.out b/Tests/expected/cosmosSlowDisk.out index ebbec3a..5b1c8c2 100644 --- a/Tests/expected/cosmosSlowDisk.out +++ b/Tests/expected/cosmosSlowDisk.out @@ -8,6 +8,7 @@ Say.sbx 156 Break.sbx 149 Grid.sbx 543 notes.txt 21 +Apps 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 diff --git a/Tests/input/cosmosCrossDisk.in b/Tests/input/cosmosCrossDisk.in new file mode 100644 index 0000000..76a5339 --- /dev/null +++ b/Tests/input/cosmosCrossDisk.in @@ -0,0 +1,8 @@ +drive 1 +Copy 1:/twoblocks.txt 0:/crossed.txt +drive +Say from drive one +drive +cd 0:/ +dir +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 4fca8f5..566620c 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -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 ---- # diff --git a/Tests/manifest b/Tests/manifest index 4a5c0c8..7329cab 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -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 | - | -