Mirror of CollapseOS
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

556 lignes
13KB

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