Mirror of CollapseOS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

576 lines
14KB

  1. ; fs
  2. ;
  3. ; Collapse OS filesystem (CFS) is not made to be convenient, but to be simple.
  4. ; This is little more than "named storage blocks". Characteristics:
  5. ;
  6. ; * a filesystem sits upon a blockdev. It needs GetB, PutB, Seek.
  7. ; * No directory. Use filename prefix to group.
  8. ; * First block of each file has metadata. Others are raw data.
  9. ; * No FAT. Files are a chain of blocks of a predefined size. To enumerate
  10. ; files, you go through metadata blocks.
  11. ; * Fixed allocation. File size is determined at allocation time and cannot be
  12. ; grown, only shrunk.
  13. ; * New allocations try to find spots to fit in, but go at the end if no spot is
  14. ; large enough.
  15. ; * Block size is 0x100, max block count per file is 8bit, that means that max
  16. ; file size: 64k - metadata overhead.
  17. ;
  18. ; *** Selecting a "source" blockdev
  19. ;
  20. ; This unit exposes "fson" shell command to "mount" CFS upon the currently
  21. ; selected device, at the point where its seekptr currently sits. This checks
  22. ; if we have a valid first block and spits an error otherwise.
  23. ;
  24. ; "fson" takes an optional argument which is a number. If non-zero, we don't
  25. ; error out if there's no metadata: we create a new CFS fs with an empty block.
  26. ;
  27. ; The can only be one "mounted" fs at once. Selecting another blockdev through
  28. ; "bsel" doesn't affect the currently mounted fs, which can still be interacted
  29. ; with (which is important if we want to move data around).
  30. ;
  31. ; *** Block metadata
  32. ;
  33. ; At the beginning of the first block of each file, there is this data
  34. ; structure:
  35. ;
  36. ; 3b: Magic number "CFS"
  37. ; 1b: Allocated block count, including the first one. Except for the "ending"
  38. ; block, this is never zero.
  39. ; 2b: Size of file in bytes (actually written). Little endian.
  40. ; 26b: file name, null terminated. last byte must be null.
  41. ;
  42. ; That gives us 32 bytes of metadata for first first block, leaving a maximum
  43. ; file size of 0xffe0.
  44. ;
  45. ; *** Last block of the chain
  46. ;
  47. ; The last block of the chain is either a block that has no valid block next to
  48. ; it or a block that reports a 0 allocated block count.
  49. ;
  50. ; However, to simplify processing, whenever fsNext encounter a chain end of the
  51. ; first type (a valid block with > 0 allocated blocks), it places an empty block
  52. ; at the end of the chain. This makes the whole "end of chain" processing much
  53. ; easier: we assume that we always have a 0 block at the end.
  54. ;
  55. ; *** Deleted files
  56. ;
  57. ; When a file is deleted, its name is set to null. This indicates that the
  58. ; allocated space is up for grabs.
  59. ;
  60. ; *** File "handles"
  61. ;
  62. ; Programs will not typically open files themselves. How it works with CFS is
  63. ; that it exposes an API to plug target files in a blockdev ID. This all
  64. ; depends on how you glue parts together, but ideally, you'll have two
  65. ; fs-related blockdev IDs: one for reading, one for writing.
  66. ;
  67. ; Being plugged into the blockdev system, programs will access the files as they
  68. ; would with any other block device.
  69. ;
  70. ; *** Creating a new FS
  71. ;
  72. ; A valid Collapse OS filesystem is nothing more than the 3 bytes 'C', 'F', 'S'
  73. ; next to each other. Placing them at the right place is all you have to do to
  74. ; create your FS.
  75. ; *** DEFINES ***
  76. ; Number of handles we want to support
  77. ; FS_HANDLE_COUNT
  78. ;
  79. ; *** VARIABLES ***
  80. ; A copy of BLOCKDEV_SEL when the FS was mounted. 0 if no FS is mounted.
  81. .equ FS_BLK FS_RAMSTART
  82. ; Offset at which our FS start on mounted device
  83. ; This pointer is 32 bits. 32 bits pointers are a bit awkward: first two bytes
  84. ; are high bytes *low byte first*, and then the low two bytes, same order.
  85. ; When loaded in HL/DE, the four bytes are loaded in this order: E, D, L, H
  86. .equ FS_START @+BLOCKDEV_SIZE
  87. ; This variable below contain the metadata of the last block we moved
  88. ; to. We read this data in memory to avoid constant seek+read operations.
  89. .equ FS_META @+4
  90. .equ FS_HANDLES @+FS_METASIZE
  91. .equ FS_RAMEND @+FS_HANDLE_COUNT*FS_HANDLE_SIZE
  92. ; *** DATA ***
  93. P_FS_MAGIC:
  94. .db "CFS", 0
  95. ; *** CODE ***
  96. fsInit:
  97. xor a
  98. ld hl, FS_BLK
  99. ld b, FS_RAMEND-FS_BLK
  100. jp fill
  101. ; *** Navigation ***
  102. ; Seek to the beginning. Errors out if no FS is mounted.
  103. ; Sets Z if success, unset if error
  104. fsBegin:
  105. call fsIsOn
  106. ret nz
  107. push hl
  108. push de
  109. push af
  110. ld de, (FS_START)
  111. ld hl, (FS_START+2)
  112. ld a, BLOCKDEV_SEEK_ABSOLUTE
  113. call fsblkSeek
  114. pop af
  115. pop de
  116. pop hl
  117. call fsReadMeta
  118. jp fsIsValid ; sets Z, returns
  119. ; Change current position to the next block with metadata. If it can't (if this
  120. ; is the last valid block), doesn't move.
  121. ; Sets Z according to whether we moved.
  122. fsNext:
  123. push bc
  124. push hl
  125. ld a, (FS_META+FS_META_ALLOC_OFFSET)
  126. or a ; cp 0
  127. jr z, .error ; if our block allocates 0 blocks, this is the
  128. ; end of the line.
  129. ld b, a ; we will seek A times
  130. .loop:
  131. ld a, BLOCKDEV_SEEK_FORWARD
  132. ld hl, FS_BLOCKSIZE
  133. call fsblkSeek
  134. djnz .loop
  135. call fsReadMeta
  136. jr nz, .createChainEnd
  137. call fsIsValid
  138. jr nz, .createChainEnd
  139. ; We're good! We have a valid FS block.
  140. ; Meta is already read. Nothing to do!
  141. cp a ; ensure Z
  142. jr .end
  143. .createChainEnd:
  144. ; We are on an invalid block where a valid block should be. This is
  145. ; the end of the line, but we should mark it a bit more explicitly.
  146. ; Let's initialize an empty block
  147. call fsInitMeta
  148. call fsWriteMeta
  149. ; continue out to error condition: we're still at the end of the line.
  150. .error:
  151. call unsetZ
  152. .end:
  153. pop hl
  154. pop bc
  155. ret
  156. ; Reads metadata at current fsblk and place it in FS_META.
  157. ; Returns Z according to whether the operation succeeded.
  158. fsReadMeta:
  159. push bc
  160. push hl
  161. ld b, FS_METASIZE
  162. ld hl, FS_META
  163. call fsblkRead ; Sets Z
  164. pop hl
  165. pop bc
  166. ret nz
  167. ; Only rewind on success
  168. jr _fsRewindAfterMeta
  169. ; Writes metadata in FS_META at current fsblk.
  170. ; Returns Z according to whether the fsblkWrite operation succeeded.
  171. fsWriteMeta:
  172. push bc
  173. push hl
  174. ld b, FS_METASIZE
  175. ld hl, FS_META
  176. call fsblkWrite ; Sets Z
  177. pop hl
  178. pop bc
  179. ret nz
  180. ; Only rewind on success
  181. jr _fsRewindAfterMeta
  182. _fsRewindAfterMeta:
  183. ; return back to before the read op
  184. push af
  185. push hl
  186. ld a, BLOCKDEV_SEEK_BACKWARD
  187. ld hl, FS_METASIZE
  188. call fsblkSeek
  189. pop hl
  190. pop af
  191. ret
  192. ; Initializes FS_META with "CFS" followed by zeroes
  193. fsInitMeta:
  194. push af
  195. push bc
  196. push de
  197. push hl
  198. ld hl, P_FS_MAGIC
  199. ld de, FS_META
  200. ld bc, 3
  201. ldir
  202. xor a
  203. ld hl, FS_META+3
  204. ld b, FS_METASIZE-3
  205. call fill
  206. pop hl
  207. pop de
  208. pop bc
  209. pop af
  210. ret
  211. ; Create a new file with A blocks allocated to it and with its new name at
  212. ; (HL).
  213. ; Before doing so, enumerate all blocks in search of a deleted file with
  214. ; allocated space big enough. If it does, it will either take the whole space
  215. ; if the allocated space asked is exactly the same, or of it isn't, split the
  216. ; free space in 2 and create a new deleted metadata block next to the newly
  217. ; created block.
  218. ; Places fsblk to the newly allocated block. You have to write the new
  219. ; filename yourself.
  220. fsAlloc:
  221. push bc
  222. push de
  223. ld c, a ; Let's store our A arg somewhere...
  224. call fsBegin
  225. jr nz, .end ; not a valid block? hum, something's wrong
  226. ; First step: find last block
  227. push hl ; keep HL for later
  228. .loop1:
  229. call fsNext
  230. jr nz, .found ; end of the line
  231. call fsIsDeleted
  232. jr nz, .loop1 ; not deleted? loop
  233. ; This is a deleted block. Maybe it fits...
  234. ld a, (FS_META+FS_META_ALLOC_OFFSET)
  235. cp c ; Same as asked size?
  236. jr z, .found ; yes? great!
  237. ; TODO: handle case where C < A (block splitting)
  238. jr .loop1
  239. .found:
  240. ; We've reached last block. Two situations are possible at this point:
  241. ; 1 - the block is the "end of line" block
  242. ; 2 - the block is a deleted block that we we're re-using.
  243. ; In both case, the processing is the same: write new metadata.
  244. ; At this point, the blockdev is placed right where we want to allocate
  245. ; But first, let's prepare the FS_META we're going to write
  246. call fsInitMeta
  247. ld a, c ; C == the number of blocks user asked for
  248. ld (FS_META+FS_META_ALLOC_OFFSET), a
  249. pop hl ; now we want our HL arg
  250. ; TODO: stop after null char. we're filling meta with garbage here.
  251. ld de, FS_META+FS_META_FNAME_OFFSET
  252. ld bc, FS_MAX_NAME_SIZE
  253. ldir
  254. ; Good, FS_META ready.
  255. ; Ok, now we can write our metadata
  256. call fsWriteMeta
  257. .end:
  258. pop de
  259. pop bc
  260. ret
  261. ; Place fsblk to the filename with the name in (HL).
  262. ; Sets Z on success, unset when not found.
  263. fsFindFN:
  264. push de
  265. call fsBegin
  266. jr nz, .end ; nothing to find, Z is unset
  267. ld a, FS_MAX_NAME_SIZE
  268. .loop:
  269. ld de, FS_META+FS_META_FNAME_OFFSET
  270. call strncmp
  271. jr z, .end ; Z is set
  272. call fsNext
  273. jr z, .loop
  274. ; End of the chain, not found
  275. ; Z already unset
  276. .end:
  277. pop de
  278. ret
  279. ; *** Metadata ***
  280. ; Sets Z according to whether the current block in FS_META is valid.
  281. ; Don't call other FS routines without checking block validity first: other
  282. ; routines don't do checks.
  283. fsIsValid:
  284. push hl
  285. push de
  286. ld a, 3
  287. ld hl, FS_META
  288. ld de, P_FS_MAGIC
  289. call strncmp
  290. ; The result of Z is our result.
  291. pop de
  292. pop hl
  293. ret
  294. ; Returns whether current block is deleted in Z flag.
  295. fsIsDeleted:
  296. ld a, (FS_META+FS_META_FNAME_OFFSET)
  297. or a ; Z flag is our answer
  298. ret
  299. ; *** blkdev methods ***
  300. ; When "mounting" a FS, we copy the current blkdev's routine privately so that
  301. ; we can still access the FS even if blkdev selection changes. These routines
  302. ; below mimic blkdev's methods, but for our private mount.
  303. fsblkGetB:
  304. push ix
  305. ld ix, FS_BLK
  306. call _blkGetB
  307. pop ix
  308. ret
  309. fsblkRead:
  310. push ix
  311. ld ix, FS_BLK
  312. call _blkRead
  313. pop ix
  314. ret
  315. fsblkPutB:
  316. push ix
  317. ld ix, FS_BLK
  318. call _blkPutB
  319. pop ix
  320. ret
  321. fsblkWrite:
  322. push ix
  323. ld ix, FS_BLK
  324. call _blkWrite
  325. pop ix
  326. ret
  327. fsblkSeek:
  328. push ix
  329. ld ix, FS_BLK
  330. call _blkSeek
  331. pop ix
  332. ret
  333. fsblkTell:
  334. push ix
  335. ld ix, FS_BLK
  336. call _blkTell
  337. pop ix
  338. ret
  339. ; *** Handling ***
  340. ; Open file at current position into handle at (IX)
  341. fsOpen:
  342. push hl
  343. push af
  344. ; Starting pos
  345. ld a, (FS_BLK+4)
  346. ld (ix), a
  347. ld a, (FS_BLK+5)
  348. ld (ix+1), a
  349. ld a, (FS_BLK+6)
  350. ld (ix+2), a
  351. ld a, (FS_BLK+7)
  352. ld (ix+3), a
  353. ; file size
  354. ld hl, (FS_META+FS_META_FSIZE_OFFSET)
  355. ld (ix+4), l
  356. ld (ix+5), h
  357. pop af
  358. pop hl
  359. ret
  360. ; Place FS blockdev at proper position for file handle in (IX) at position HL.
  361. fsPlaceH:
  362. push af
  363. push de
  364. push hl
  365. ; Move fsdev to beginning of block
  366. ld e, (ix)
  367. ld d, (ix+1)
  368. ld l, (ix+2)
  369. ld h, (ix+3)
  370. ld a, BLOCKDEV_SEEK_ABSOLUTE
  371. call fsblkSeek
  372. ; skip metadata
  373. ld a, BLOCKDEV_SEEK_FORWARD
  374. ld hl, FS_METASIZE
  375. call fsblkSeek
  376. pop hl
  377. pop de
  378. ; go to specified pos
  379. ld a, BLOCKDEV_SEEK_FORWARD
  380. call fsblkSeek
  381. pop af
  382. ret
  383. ; Sets Z according to whether HL is within bounds for file handle at (IX), that
  384. ; is, if it is smaller than file size.
  385. fsWithinBounds:
  386. ld a, h
  387. cp (ix+5)
  388. jr c, .within ; H < (IX+5)
  389. jp nz, unsetZ ; H > (IX+5)
  390. ; H == (IX+5)
  391. ld a, l
  392. cp (ix+4)
  393. jp nc, unsetZ ; L >= (IX+4)
  394. .within:
  395. cp a ; ensure Z
  396. ret
  397. ; Set size of file handle (IX) to value in HL.
  398. ; This writes directly in handle's metadata.
  399. fsSetSize:
  400. push hl ; --> lvl 1
  401. ld hl, 0
  402. call fsPlaceH ; fs blkdev is now at beginning of content
  403. ; we need the blkdev to be on filesize's offset
  404. ld hl, FS_METASIZE-FS_META_FSIZE_OFFSET
  405. ld a, BLOCKDEV_SEEK_BACKWARD
  406. call fsblkSeek
  407. pop hl ; <-- lvl 1
  408. ; blkdev is at the right spot, HL is back to its original value, let's
  409. ; write it both in the metadata block and in its file handle's cache.
  410. push hl ; --> lvl 1
  411. ; now let's write our new filesize both in blkdev and in file handle's
  412. ; cache.
  413. ld a, l
  414. ld (ix+4), a
  415. call fsblkPutB
  416. ld a, h
  417. ld (ix+5), a
  418. call fsblkPutB
  419. pop hl ; <-- lvl 1
  420. xor a ; ensure Z
  421. ret
  422. ; Read a byte in handle at (IX) at position HL and put it into A.
  423. ; Z is set on success, unset if handle is at the end of the file.
  424. fsGetB:
  425. call fsWithinBounds
  426. jr z, .proceed
  427. ; We want to unset Z, but also return 0 to ensure that a GetB that
  428. ; doesn't check Z doesn't end up with false data.
  429. xor a
  430. jp unsetZ ; returns
  431. .proceed:
  432. push hl
  433. call fsPlaceH
  434. call fsblkGetB
  435. cp a ; ensure Z
  436. pop hl
  437. ret
  438. ; Write byte A in handle (IX) at position HL.
  439. ; Z is set on success, unset if handle is at the end of the file.
  440. ; TODO: detect end of block alloc
  441. fsPutB:
  442. push hl
  443. call fsPlaceH
  444. call fsblkPutB
  445. pop hl
  446. ; if HL is out of bounds, increase bounds
  447. call fsWithinBounds
  448. ret z
  449. inc hl ; our filesize is now HL+1
  450. jp fsSetSize
  451. ; Mount the fs subsystem upon the currently selected blockdev at current offset.
  452. ; Verify is block is valid and error out if its not, mounting nothing.
  453. ; Upon mounting, copy currently selected device in FS_BLK.
  454. fsOn:
  455. push hl
  456. push de
  457. push bc
  458. ; We have to set blkdev routines early before knowing whether the
  459. ; mounting succeeds because methods like fsReadMeta uses fsblk* methods.
  460. ld hl, BLOCKDEV_SEL
  461. ld de, FS_BLK
  462. ld bc, BLOCKDEV_SIZE
  463. ldir ; copy!
  464. call fsblkTell
  465. ld (FS_START), de
  466. ld (FS_START+2), hl
  467. call fsReadMeta
  468. jr nz, .error
  469. call fsIsValid
  470. jr nz, .error
  471. ; success
  472. xor a
  473. jr .end
  474. .error:
  475. ; couldn't mount. Let's reset our variables.
  476. call fsInit
  477. ld a, FS_ERR_NO_FS
  478. or a ; unset Z
  479. .end:
  480. pop bc
  481. pop de
  482. pop hl
  483. ret
  484. ; Sets Z according to whether we have a filesystem mounted.
  485. fsIsOn:
  486. ; check whether (FS_BLK) is zero
  487. push hl
  488. ld hl, (FS_BLK)
  489. ld a, h
  490. or l
  491. jr nz, .mounted
  492. ; not mounted, unset Z
  493. inc a
  494. jr .end
  495. .mounted:
  496. cp a ; ensure Z
  497. .end:
  498. pop hl
  499. ret
  500. ; Iterate over files in active file system and, for each file, call (IY) with
  501. ; the file's metadata currently placed. HL is set to FS_META.
  502. ; Sets Z on success, unset on error.
  503. ; There are no error condition happening midway. If you get an error, then (IY)
  504. ; was never called.
  505. fsIter:
  506. call fsBegin
  507. ret nz
  508. .loop:
  509. call fsIsDeleted
  510. ld hl, FS_META
  511. call nz, callIY
  512. call fsNext
  513. jr z, .loop ; Z set? fsNext was successful
  514. cp a ; ensure Z
  515. ret
  516. ; Delete currently active file
  517. ; Sets Z on success, unset on error.
  518. fsDel:
  519. call fsIsValid
  520. ret nz
  521. xor a
  522. ; Set filename to zero to flag it as deleted
  523. ld (FS_META+FS_META_FNAME_OFFSET), a
  524. jp fsWriteMeta
  525. ; Given a handle index in A, set DE to point to the proper handle.
  526. fsHandle:
  527. ld de, FS_HANDLES
  528. or a ; cp 0
  529. ret z ; DE already point to correct handle
  530. push bc
  531. ld b, a
  532. .loop:
  533. ld a, FS_HANDLE_SIZE
  534. call addDE
  535. djnz .loop
  536. pop bc
  537. ret