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.

584 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. call fill
  101. ret
  102. ; *** Navigation ***
  103. ; Seek to the beginning. Errors out if no FS is mounted.
  104. ; Sets Z if success, unset if error
  105. fsBegin:
  106. call fsIsOn
  107. ret nz
  108. push hl
  109. push de
  110. push af
  111. ld de, (FS_START)
  112. ld hl, (FS_START+2)
  113. ld a, BLOCKDEV_SEEK_ABSOLUTE
  114. call fsblkSeek
  115. pop af
  116. pop de
  117. pop hl
  118. call fsReadMeta
  119. jp fsIsValid ; sets Z, returns
  120. ; Change current position to the next block with metadata. If it can't (if this
  121. ; is the last valid block), doesn't move.
  122. ; Sets Z according to whether we moved.
  123. fsNext:
  124. push bc
  125. push hl
  126. ld a, (FS_META+FS_META_ALLOC_OFFSET)
  127. or a ; cp 0
  128. jr z, .error ; if our block allocates 0 blocks, this is the
  129. ; end of the line.
  130. ld b, a ; we will seek A times
  131. .loop:
  132. ld a, BLOCKDEV_SEEK_FORWARD
  133. ld hl, FS_BLOCKSIZE
  134. call fsblkSeek
  135. djnz .loop
  136. call fsReadMeta
  137. jr nz, .createChainEnd
  138. call fsIsValid
  139. jr nz, .createChainEnd
  140. ; We're good! We have a valid FS block.
  141. ; Meta is already read. Nothing to do!
  142. cp a ; ensure Z
  143. jr .end
  144. .createChainEnd:
  145. ; We are on an invalid block where a valid block should be. This is
  146. ; the end of the line, but we should mark it a bit more explicitly.
  147. ; Let's initialize an empty block
  148. call fsInitMeta
  149. call fsWriteMeta
  150. ; continue out to error condition: we're still at the end of the line.
  151. .error:
  152. call unsetZ
  153. .end:
  154. pop hl
  155. pop bc
  156. ret
  157. ; Reads metadata at current fsblk and place it in FS_META.
  158. ; Returns Z according to whether the operation succeeded.
  159. fsReadMeta:
  160. push bc
  161. push hl
  162. ld b, FS_METASIZE
  163. ld hl, FS_META
  164. call fsblkRead ; Sets Z
  165. pop hl
  166. pop bc
  167. ret nz
  168. ; Only rewind on success
  169. jr _fsRewindAfterMeta
  170. ; Writes metadata in FS_META at current fsblk.
  171. ; Returns Z according to whether the fsblkWrite operation succeeded.
  172. fsWriteMeta:
  173. push bc
  174. push hl
  175. ld b, FS_METASIZE
  176. ld hl, FS_META
  177. call fsblkWrite ; Sets Z
  178. pop hl
  179. pop bc
  180. ret nz
  181. ; Only rewind on success
  182. jr _fsRewindAfterMeta
  183. _fsRewindAfterMeta:
  184. ; return back to before the read op
  185. push af
  186. push hl
  187. ld a, BLOCKDEV_SEEK_BACKWARD
  188. ld hl, FS_METASIZE
  189. call fsblkSeek
  190. pop hl
  191. pop af
  192. ret
  193. ; Initializes FS_META with "CFS" followed by zeroes
  194. fsInitMeta:
  195. push af
  196. push bc
  197. push de
  198. push hl
  199. ld hl, P_FS_MAGIC
  200. ld de, FS_META
  201. ld bc, 3
  202. ldir
  203. xor a
  204. ld hl, FS_META+3
  205. ld b, FS_METASIZE-3
  206. call fill
  207. pop hl
  208. pop de
  209. pop bc
  210. pop af
  211. ret
  212. ; Create a new file with A blocks allocated to it and with its new name at
  213. ; (HL).
  214. ; Before doing so, enumerate all blocks in search of a deleted file with
  215. ; allocated space big enough. If it does, it will either take the whole space
  216. ; if the allocated space asked is exactly the same, or of it isn't, split the
  217. ; free space in 2 and create a new deleted metadata block next to the newly
  218. ; created block.
  219. ; Places fsblk to the newly allocated block. You have to write the new
  220. ; filename yourself.
  221. fsAlloc:
  222. push bc
  223. push de
  224. ld c, a ; Let's store our A arg somewhere...
  225. call fsBegin
  226. jr nz, .end ; not a valid block? hum, something's wrong
  227. ; First step: find last block
  228. push hl ; keep HL for later
  229. .loop1:
  230. call fsNext
  231. jr nz, .found ; end of the line
  232. call fsIsDeleted
  233. jr nz, .loop1 ; not deleted? loop
  234. ; This is a deleted block. Maybe it fits...
  235. ld a, (FS_META+FS_META_ALLOC_OFFSET)
  236. cp c ; Same as asked size?
  237. jr z, .found ; yes? great!
  238. ; TODO: handle case where C < A (block splitting)
  239. jr .loop1
  240. .found:
  241. ; We've reached last block. Two situations are possible at this point:
  242. ; 1 - the block is the "end of line" block
  243. ; 2 - the block is a deleted block that we we're re-using.
  244. ; In both case, the processing is the same: write new metadata.
  245. ; At this point, the blockdev is placed right where we want to allocate
  246. ; But first, let's prepare the FS_META we're going to write
  247. call fsInitMeta
  248. ld a, c ; C == the number of blocks user asked for
  249. ld (FS_META+FS_META_ALLOC_OFFSET), a
  250. pop hl ; now we want our HL arg
  251. ; TODO: stop after null char. we're filling meta with garbage here.
  252. ld de, FS_META+FS_META_FNAME_OFFSET
  253. ld bc, FS_MAX_NAME_SIZE
  254. ldir
  255. ; Good, FS_META ready.
  256. ; Ok, now we can write our metadata
  257. call fsWriteMeta
  258. .end:
  259. pop de
  260. pop bc
  261. ret
  262. ; Place fsblk to the filename with the name in (HL).
  263. ; Sets Z on success, unset when not found.
  264. fsFindFN:
  265. push de
  266. call fsBegin
  267. jr nz, .end ; nothing to find, Z is unset
  268. ld a, FS_MAX_NAME_SIZE
  269. .loop:
  270. ld de, FS_META+FS_META_FNAME_OFFSET
  271. call strncmp
  272. jr z, .end ; Z is set
  273. call fsNext
  274. jr z, .loop
  275. ; End of the chain, not found
  276. call unsetZ
  277. .end:
  278. pop de
  279. ret
  280. ; *** Metadata ***
  281. ; Sets Z according to whether the current block in FS_META is valid.
  282. ; Don't call other FS routines without checking block validity first: other
  283. ; routines don't do checks.
  284. fsIsValid:
  285. push hl
  286. push de
  287. ld a, 3
  288. ld hl, FS_META
  289. ld de, P_FS_MAGIC
  290. call strncmp
  291. ; The result of Z is our result.
  292. pop de
  293. pop hl
  294. ret
  295. ; Returns whether current block is deleted in Z flag.
  296. fsIsDeleted:
  297. ld a, (FS_META+FS_META_FNAME_OFFSET)
  298. cp 0 ; Z flag is our answer
  299. ret
  300. ; *** blkdev methods ***
  301. ; When "mounting" a FS, we copy the current blkdev's routine privately so that
  302. ; we can still access the FS even if blkdev selection changes. These routines
  303. ; below mimic blkdev's methods, but for our private mount.
  304. fsblkGetB:
  305. push ix
  306. ld ix, FS_BLK
  307. call _blkGetB
  308. pop ix
  309. ret
  310. fsblkRead:
  311. push ix
  312. ld ix, FS_BLK
  313. call _blkRead
  314. pop ix
  315. ret
  316. fsblkPutB:
  317. push ix
  318. ld ix, FS_BLK
  319. call _blkPutB
  320. pop ix
  321. ret
  322. fsblkWrite:
  323. push ix
  324. ld ix, FS_BLK
  325. call _blkWrite
  326. pop ix
  327. ret
  328. fsblkSeek:
  329. push ix
  330. ld ix, FS_BLK
  331. call _blkSeek
  332. pop ix
  333. ret
  334. fsblkTell:
  335. push ix
  336. ld ix, FS_BLK
  337. call _blkTell
  338. pop ix
  339. ret
  340. ; *** Handling ***
  341. ; Open file at current position into handle at (IX)
  342. fsOpen:
  343. push hl
  344. push af
  345. ; Starting pos
  346. ld a, (FS_BLK+4)
  347. ld (ix), a
  348. ld a, (FS_BLK+5)
  349. ld (ix+1), a
  350. ld a, (FS_BLK+6)
  351. ld (ix+2), a
  352. ld a, (FS_BLK+7)
  353. ld (ix+3), a
  354. ; file size
  355. ld hl, (FS_META+FS_META_FSIZE_OFFSET)
  356. ld (ix+4), l
  357. ld (ix+5), h
  358. pop af
  359. pop hl
  360. ret
  361. ; Place FS blockdev at proper position for file handle in (IX) at position HL.
  362. fsPlaceH:
  363. push af
  364. push de
  365. push hl
  366. ; Move fsdev to beginning of block
  367. ld e, (ix)
  368. ld d, (ix+1)
  369. ld l, (ix+2)
  370. ld h, (ix+3)
  371. ld a, BLOCKDEV_SEEK_ABSOLUTE
  372. call fsblkSeek
  373. ; skip metadata
  374. ld a, BLOCKDEV_SEEK_FORWARD
  375. ld hl, FS_METASIZE
  376. call fsblkSeek
  377. pop hl
  378. pop de
  379. ; go to specified pos
  380. ld a, BLOCKDEV_SEEK_FORWARD
  381. call fsblkSeek
  382. pop af
  383. ret
  384. ; Sets Z according to whether HL is within bounds for file handle at (IX), that
  385. ; is, if it is smaller than file size.
  386. fsWithinBounds:
  387. push de
  388. ; file size
  389. ld e, (ix+4)
  390. ld d, (ix+5)
  391. call cpHLDE
  392. pop de
  393. jr nc, .outOfBounds ; HL >= DE
  394. cp a ; ensure Z
  395. ret
  396. .outOfBounds:
  397. jp unsetZ ; returns
  398. ; Set size of file handle (IX) to value in HL.
  399. ; This writes directly in handle's metadata.
  400. fsSetSize:
  401. push hl ; --> lvl 1
  402. ld hl, 0
  403. call fsPlaceH ; fs blkdev is now at beginning of content
  404. ; we need the blkdev to be on filesize's offset
  405. ld hl, FS_METASIZE-FS_META_FSIZE_OFFSET
  406. ld a, BLOCKDEV_SEEK_BACKWARD
  407. call fsblkSeek
  408. pop hl ; <-- lvl 1
  409. ; blkdev is at the right spot, HL is back to its original value, let's
  410. ; write it both in the metadata block and in its file handle's cache.
  411. push hl ; --> lvl 1
  412. ; now let's write our new filesize both in blkdev and in file handle's
  413. ; cache.
  414. ld a, l
  415. ld (ix+4), a
  416. call fsblkPutB
  417. ld a, h
  418. ld (ix+5), a
  419. call fsblkPutB
  420. pop hl ; <-- lvl 1
  421. xor a ; ensure Z
  422. ret
  423. ; Read a byte in handle at (IX) at position HL and put it into A.
  424. ; Z is set on success, unset if handle is at the end of the file.
  425. fsGetB:
  426. call fsWithinBounds
  427. jr z, .proceed
  428. ; We want to unset Z, but also return 0 to ensure that a GetB that
  429. ; doesn't check Z doesn't end up with false data.
  430. xor a
  431. jp unsetZ ; returns
  432. .proceed:
  433. push hl
  434. call fsPlaceH
  435. call fsblkGetB
  436. cp a ; ensure Z
  437. pop hl
  438. ret
  439. ; Write byte A in handle (IX) at position HL.
  440. ; Z is set on success, unset if handle is at the end of the file.
  441. ; TODO: detect end of block alloc
  442. fsPutB:
  443. push hl
  444. call fsPlaceH
  445. call fsblkPutB
  446. pop hl
  447. ; if HL is out of bounds, increase bounds
  448. call fsWithinBounds
  449. ret z
  450. inc hl ; our filesize is now HL+1
  451. jp fsSetSize
  452. ; Mount the fs subsystem upon the currently selected blockdev at current offset.
  453. ; Verify is block is valid and error out if its not, mounting nothing.
  454. ; Upon mounting, copy currently selected device in FS_BLK.
  455. fsOn:
  456. push hl
  457. push de
  458. push bc
  459. ; We have to set blkdev routines early before knowing whether the
  460. ; mounting succeeds because methods like fsReadMeta uses fsblk* methods.
  461. ld hl, BLOCKDEV_SEL
  462. ld de, FS_BLK
  463. ld bc, BLOCKDEV_SIZE
  464. ldir ; copy!
  465. call fsblkTell
  466. ld (FS_START), de
  467. ld (FS_START+2), hl
  468. call fsReadMeta
  469. jr nz, .error
  470. call fsIsValid
  471. jr nz, .error
  472. ; success
  473. xor a
  474. jr .end
  475. .error:
  476. ; couldn't mount. Let's reset our variables.
  477. xor a
  478. ld b, FS_META-FS_BLK ; reset routine pointers and FS ptrs
  479. ld hl, FS_BLK
  480. call fill
  481. ld a, FS_ERR_NO_FS
  482. .end:
  483. pop bc
  484. pop de
  485. pop hl
  486. ret
  487. ; Sets Z according to whether we have a filesystem mounted.
  488. fsIsOn:
  489. ; check whether (FS_BLK) is zero
  490. push hl
  491. push de
  492. ld hl, (FS_BLK)
  493. ld de, 0
  494. call cpHLDE
  495. jr nz, .mounted
  496. ; if equal, it means our FS is not mounted
  497. call unsetZ
  498. jr .end
  499. .mounted:
  500. cp a ; ensure Z
  501. .end:
  502. pop de
  503. pop hl
  504. ret
  505. ; Iterate over files in active file system and, for each file, call (IY) with
  506. ; the file's metadata currently placed. HL is set to FS_META.
  507. ; Sets Z on success, unset on error.
  508. ; There are no error condition happening midway. If you get an error, then (IY)
  509. ; was never called.
  510. fsIter:
  511. call fsIsOn
  512. ret nz
  513. call fsBegin
  514. ret nz
  515. .loop:
  516. call fsIsDeleted
  517. ld hl, FS_META
  518. call nz, callIY
  519. call fsNext
  520. jr z, .loop ; Z set? fsNext was successful
  521. cp a ; ensure Z
  522. ret
  523. ; Delete currently active file
  524. ; Sets Z on success, unset on error.
  525. fsDel:
  526. call fsIsValid
  527. ret nz
  528. xor a
  529. ; Set filename to zero to flag it as deleted
  530. ld (FS_META+FS_META_FNAME_OFFSET), a
  531. jp fsWriteMeta
  532. ; Given a handle index in A, set DE to point to the proper handle.
  533. fsHandle:
  534. ld de, FS_HANDLES
  535. or a ; cp 0
  536. ret z ; DE already point to correct handle
  537. push bc
  538. ld b, a
  539. .loop:
  540. ld a, FS_HANDLE_SIZE
  541. call addDE
  542. djnz .loop
  543. pop bc
  544. ret