osFileStart sets an extent aside and osFileWrite refuses a block index outside it, so writing off the end was already barred. Committing a larger size was not, and reaches the same neighbour by simply claiming it: a directory entry is the only record of what a file owns, so an entry claiming a block it was never given owns it, and so does whatever owned it before. Both files then look perfectly well formed. The free count went backwards past zero on the same path. Found by ChatGPT's review of the streaming work, in NOTES.md. I had bounded the index because writing off the end was the obvious way to reach a neighbour, and had not noticed that the other end of the same reservation was open. THE SIZE IS COMPARED, NOT THE ROOM IT TAKES UP. One block and a tail occupies exactly what two whole blocks occupy, so bounding the blocks alone would let a file reserve the first, commit the second, claim no block it was not given, and still report two hundred and forty six bytes that were never written to it - whatever the disk had there before. Checked before anything is touched, which is why the temporary is found twice. The old file is deleted a few lines down and a refusal after that point would have destroyed the thing it was protecting. AND IT CAUGHT A REAL ONE IMMEDIATELY. The assembler reserves the file plus room for its vectors, and asked for four bytes per vector DECLARED - which looks like a safe bound and is not, because a device is declared during the SECOND pass, in the line that implements it. A program with a device installs a vector that was not counted when the room was measured. CosmOS reserved 14,163 bytes and committed 14,167, writing four bytes past what it had been given on every build since S2. It landed inside the last block it owned, and would not have if the boundary had fallen four bytes earlier. It reserves against the vector table's LIMIT now, which cannot go stale whenever things are counted. Claim.asm is the program that tries it: reserve one block and a tail of ten, write them, then tell osFileDone the file came to two whole blocks. The refusal and the honest commit that follows are both recorded. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
103 lines
2.0 KiB
NASM
103 lines
2.0 KiB
NASM
; Tries to commit a file bigger than the room it reserved.
|
|
;
|
|
; osFileStart sets aside an extent and osFileWrite refuses a block index outside it, so the
|
|
; obvious way to reach a neighbouring file - writing off the end - is already barred. This
|
|
; is the other way to the same place: reserve one block, write the one block, and then tell
|
|
; osFileDone the file came to two.
|
|
;
|
|
; NOTHING WOULD SAY SO IF THAT WERE ALLOWED. A directory entry is the only record of what a
|
|
; file owns, so an entry claiming a block it was never given simply owns it, and whatever
|
|
; owned it before owns it too. Both files then look perfectly well formed.
|
|
;
|
|
; Correct behaviour is a refusal, and the file left as it was. The reservation is one block
|
|
; and a tail of ten, so:
|
|
;
|
|
; two blocks and no tail more than was reserved refused
|
|
; one block and a tail exactly what was reserved allowed
|
|
;
|
|
; Written by Anachronaut
|
|
|
|
#Include services.asm
|
|
#Program
|
|
#Base 0x4000
|
|
|
|
start:
|
|
; One block, and ten bytes after it.
|
|
SETD.0 Name
|
|
SETD.3 0x00 0x01
|
|
INIA 0d10
|
|
SWI osFileStart
|
|
BNQ noStart
|
|
|
|
SETD.1 Block
|
|
RSTA
|
|
RSTB
|
|
SWI osFileWrite
|
|
BNQ noWrite
|
|
SETD.1 Block
|
|
RSTA
|
|
INIB 0d1
|
|
SWI osFileWrite
|
|
BNQ noWrite
|
|
|
|
; Two whole blocks, which is more than one block and a tail.
|
|
SETD.3 0x00 0x02
|
|
RSTA
|
|
SWI osFileDone
|
|
BNQ refused
|
|
SETD.0 Allowed
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
refused:
|
|
SETD.0 Refused
|
|
SWI osPrintString
|
|
|
|
; And the honest size, which is what was reserved.
|
|
SETD.3 0x00 0x01
|
|
INIA 0d10
|
|
SWI osFileDone
|
|
BNQ noHonest
|
|
SETD.0 Honest
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
noHonest:
|
|
SETD.0 NoHonest
|
|
SWI osPrintString
|
|
SWI osExit
|
|
noStart:
|
|
SETD.0 NoStart
|
|
SWI osPrintString
|
|
SWI osExit
|
|
noWrite:
|
|
SETD.0 NoWrite
|
|
SWI osPrintString
|
|
SWI osExit
|
|
|
|
#Data
|
|
#Base 0x2000
|
|
|
|
Name:
|
|
"claim.dat"
|
|
Block:
|
|
#Reserve 0d256
|
|
Allowed:
|
|
"claiming more than was reserved was ALLOWED
|
|
"
|
|
Refused:
|
|
"claiming more than was reserved was refused
|
|
"
|
|
Honest:
|
|
"and the size it really came to was taken
|
|
"
|
|
NoHonest:
|
|
"the honest size was refused too
|
|
"
|
|
NoStart:
|
|
"no start
|
|
"
|
|
NoWrite:
|
|
"no write
|
|
"
|