diff --git a/Programs/CosmOS/README.md b/Programs/CosmOS/README.md index d76a186..045cc37 100644 --- a/Programs/CosmOS/README.md +++ b/Programs/CosmOS/README.md @@ -185,6 +185,29 @@ has moved about on looks exactly as it always did. Nothing stores the path: the directory is an entry index and two bytes, and the text on the prompt is worked out again each time by walking the chain of parents upward. +That walk goes from where you are up to the root, so the names arrive deepest first and +are written into the buffer **backwards, from its end**. When they do not all fit, what is +already down is the deep end of the path, which is the end worth keeping - so the prompt +is cut at the front and says so: + +``` +...opqrst03/abcdefghijklmnopqrst04/.../abcdefghijklmnopqrst08> +``` + +**Three limits, and the smallest is not the one you would guess.** A path handed to any +one operation is capped at 95 characters up to the last separator plus a name of 22; the +host tool carries 512, which only means it can build a tree CosmOS cannot name in one +piece. Neither of those binds anything: the longest path on a full install is 21 +characters. What binds is the prompt's 127 bytes - and **nothing caps how deep the +directories go**, because `mkdir a` and `cd a` are each well inside every limit and can be +typed all day. + +Before the walk was bounded it wrote past the front of that buffer and into whatever the +assembler had put below it, which was the shell's own command names. Six directories of 22 +characters was enough. The first five bytes to go were the word `exit`, so the shell +stopped recognising the command for leaving - a fault with no plausible connection to the +directory you happened to be standing in. + `dir` lists the directory you are in rather than the whole disk. A program can move too, with `osChangeDir`, and **the shell puts the working directory diff --git a/Programs/CosmOS/Source/cosmos.asm b/Programs/CosmOS/Source/cosmos.asm index 2267633..dbb79f8 100644 --- a/Programs/CosmOS/Source/cosmos.asm +++ b/Programs/CosmOS/Source/cosmos.asm @@ -1235,6 +1235,23 @@ shellPath: SETD.1 CwdAt STD.0.1 + ; HOW MUCH THERE IS TO WRITE INTO, COUNTED DOWN. Nothing bounds how deep the directories + ; go: a path is capped at what one operation can name, but "mkdir a" and "cd a" are each + ; well inside that and can be typed all day. This walk writes BACKWARDS from the end, so + ; running out means walking off the FRONT of the buffer and into whatever the assembler + ; put below it - which was the shell's own command names. Six directories of twenty two + ; characters was enough, and the first five bytes to go were the word "exit", so the + ; shell stopped recognising the command for leaving. + ; + ; A hundred and twenty three of the hundred and twenty six, the other three being kept + ; back for the dots that say it was cut. + INIA 0d123 + SETD.0 CwdRoom + STA.0 + RSTA + SETD.0 CwdCut + STA.0 + ; Where the walk up starts. SETD.0 SbfsCwd SETD.1 CwdWalk @@ -1259,6 +1276,9 @@ shellPathStep: ; The name goes in front of what is there, and a separator in front of that. SETD.0 SbfsName CALL shellPathPrepend + SETD.0 CwdCut + LDA.0 + BNA shellPathReady ; It would not all fit, so there is nothing above worth asking for. SETD.0 SbfsUpParent SETD.1 CwdWalk @@ -1307,6 +1327,13 @@ pathBack: BRA pathSeparator DECA STA.1 + + SETD.1 CwdRoom + LDA.1 + BRA pathCut ; The front of the buffer, and the next byte would be past it. + DECA + STA.1 + DECD.0 DECD.2 LDA.0 @@ -1314,6 +1341,12 @@ pathBack: BRI pathBack pathSeparator: + SETD.1 CwdRoom + LDA.1 + BRA pathCut + DECA + STA.1 + DECD.2 INIA 0x2F STA.2 @@ -1321,6 +1354,28 @@ pathSeparator: STD.2.1 RET +; Stopping rather than writing past the front. The names go on backwards, so what is +; already down is the DEEP end of the path - which is the end worth showing: the prompt +; says where you are, and the last two directories say that better than the first two do. +; +; Three dots in front, out of the room that was never counted, so there is always +; somewhere to put them. No separator: the name they sit against brought its own, or was +; cut off half way, and "..." reads correctly against either. +pathCut: + INIA 0x2E + DECD.2 + STA.2 + DECD.2 + STA.2 + DECD.2 + STA.2 + SETD.1 CwdAt + STD.2.1 + INIA 0x01 + SETD.1 CwdCut + STA.1 + RET + ; ---- delete and rename ---- ; ; The two things a disk needs that reading and writing do not provide, and the two that @@ -3534,6 +3589,15 @@ CwdWalk: PathLength: 0x00 +; What is left of CwdText to write into, and whether the path ran out of it. Counted down +; rather than compared against an address, because the check is on every byte written and +; a subtraction of two pointers is a great deal more than a byte that is already going to +; be tested for zero. +CwdRoom: + 0x00 +CwdCut: + 0x00 + ; What the working directory was when a program was started, so that it can be put back ; when the program stops. SavedCwd: diff --git a/Tests/expected/cosmosDeep.out b/Tests/expected/cosmosDeep.out new file mode 100644 index 0000000..10b6043 --- /dev/null +++ b/Tests/expected/cosmosDeep.out @@ -0,0 +1,16 @@ +CosmOS +> /abcdefghijklmnopqrst01> /abcdefghijklmnopqrst01/abcdefghijklmnopqrst02> /abcdefghijklmnopqrst01/abcdefghijklmnopqrst02/abcdefghijklmnopqrst03> /abcdefghijklmnopqrst01/abcdefghijklmnopqrst02/abcdefghijklmnopqrst03/abcdefghijklmnopqrst04> /abcdefghijklmnopqrst01/abcdefghijklmnopqrst02/abcdefghijklmnopqrst03/abcdefghijklmnopqrst04/abcdefghijklmnopqrst05> ...opqrst01/abcdefghijklmnopqrst02/abcdefghijklmnopqrst03/abcdefghijklmnopqrst04/abcdefghijklmnopqrst05/abcdefghijklmnopqrst06> ...opqrst02/abcdefghijklmnopqrst03/abcdefghijklmnopqrst04/abcdefghijklmnopqrst05/abcdefghijklmnopqrst06/abcdefghijklmnopqrst07> ...opqrst03/abcdefghijklmnopqrst04/abcdefghijklmnopqrst05/abcdefghijklmnopqrst06/abcdefghijklmnopqrst07/abcdefghijklmnopqrst08> dir list what is on the disk +load read a program off the disk +run [words] start what was loaded, and tell it those words + [words] look where you are and then in /Apps, and start that +cd [path] go to a directory, or to the root with nothing after it +mkdir make a directory +rmdir remove an empty one +delete take it off the disk +rename call it something else +monitor look at memory, change it, and jump into it +help this +exit stop, or leave the monitor if you are in it +...opqrst03/abcdefghijklmnopqrst04/abcdefghijklmnopqrst05/abcdefghijklmnopqrst06/abcdefghijklmnopqrst07/abcdefghijklmnopqrst08> > halted +Execution halted. +[exit 0] diff --git a/Tests/input/cosmosDeep.in b/Tests/input/cosmosDeep.in new file mode 100644 index 0000000..4262020 --- /dev/null +++ b/Tests/input/cosmosDeep.in @@ -0,0 +1,11 @@ +cd abcdefghijklmnopqrst01 +cd abcdefghijklmnopqrst02 +cd abcdefghijklmnopqrst03 +cd abcdefghijklmnopqrst04 +cd abcdefghijklmnopqrst05 +cd abcdefghijklmnopqrst06 +cd abcdefghijklmnopqrst07 +cd abcdefghijklmnopqrst08 +help +cd +exit diff --git a/Tests/makedisks.sh b/Tests/makedisks.sh index 5f80996..190d51d 100755 --- a/Tests/makedisks.sh +++ b/Tests/makedisks.sh @@ -326,3 +326,19 @@ cp "$ROOT/Programs/Examples/hello.asm" hello.asm "$ROOT/Assembler" -I "$ROOT/Programs/CosmOS/Source" -I "$ROOT/Programs/CosmOS/Assembler" \ "$ROOT/Programs/CosmOS/Assembler/tokenTest.asm" -o "$WORK/tokenTest.sbx" >/dev/null "$TOOL" put "$DISKS/asm.img" "$WORK/tokenTest.sbx" >/dev/null + +# A tree deeper than the prompt can print, for the buffer that used to walk off the front +# of itself. Eight directories of twenty two characters is 184 characters of path, and the +# prompt is built backwards into 127 bytes - so before this was bounded, the walk wrote +# down past the start of the buffer and over the shell's own command names. The word +# "exit" was the first casualty, five bytes below. +# +# MADE HERE RATHER THAN BY THE TEST, because a path this long cannot be given to mkdir in +# one piece: naming it is capped well below what walking into it a level at a time can +# reach, which is exactly why the depth is unbounded in the first place. +"$TOOL" format "$DISKS/deep.img" 512 8 >/dev/null +DEEPPATH="" +for i in 1 2 3 4 5 6 7 8; do + DEEPPATH="$DEEPPATH/abcdefghijklmnopqrst0$i" + "$TOOL" mkdir "$DISKS/deep.img" "$DEEPPATH" >/dev/null +done diff --git a/Tests/manifest b/Tests/manifest index 93d0e5c..4a90cec 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -333,6 +333,18 @@ cosmosMore | CosmOS/Source/cosmos.asm | run | cosmosMor # part-block and 84,000-byte files are copied through one buffer; Compare checks the copies # and a same-sized file whose only difference is deep into the input. cosmosCopyCompare | CosmOS/Source/cosmos.asm | run | cosmosCopyCompare.in | - | disks/copycompare.img + +# A working directory deeper than the prompt can print. The prompt is built BACKWARDS into +# 127 bytes, and nothing bounds how deep the directories go - a path is capped at what one +# operation can name, but "cd" a level at a time is well inside that and can be repeated. +# So the walk wrote down past the front of its own buffer and into what the assembler put +# below it, which was the shell's command names: at six directories of twenty two +# characters the word "exit" was gone and the shell no longer knew how to stop. +# +# WHAT IS RECORDED IS THAT THE COMMANDS STILL WORK, and that is the whole point of running +# help and cd and exit from down there rather than just looking at the prompt. A prompt +# that is merely wrong is a cosmetic fault; this one was writing into other variables. +cosmosDeep | CosmOS/Source/cosmos.asm | run | cosmosDeep.in | - | disks/deep.img # Reading a disk that has directories on it. The machine can walk a path at this point but # cannot make a directory, so the disk is built by the host tool and read here - which is # the two implementations checking each other rather than either checking itself.