commit b3ce4a8da5d95243b805e161159815eab497719e
parent 3019081ab376cc809daceb75b8b02737f412ec0d
Author: Virgil Dupras <hsoft@hardcoded.net>
Date: Tue, 19 Jul 2022 16:08:10 -0400
Begin I/O restructuring
See doc/io for details.
Diffstat:
5 files changed, 99 insertions(+), 37 deletions(-)
diff --git a/fs/doc/io.txt b/fs/doc/io.txt
@@ -0,0 +1,65 @@
+# Input/Output
+
+(this document doesn't describe things as they are now, but as they're going to
+be soon)
+
+The lib/io subsystem offers a unified API to read and write on any device or
+filesystem. All words in this API revolves around a structures that we call the
+"IO handle". These handle represent one "place" we read and write to, saving, if
+needed, all states needed to fulfill the API's specifications.
+
+I/O operations are byte based.
+
+When "EOF" is mentioned, it means "end of file". In same case, it's a misnomer,
+but the idea is the same: if all possible data from the I/O source have been
+consumed, we're in EOF condition.
+
+Position management is opaque to the I/O words.
+
+The structure of these handle will vary depending on the driver or filesystem,
+but they all start with the same structure, which is a list of 4b pointers to
+words, in this order:
+
+readbuf
+writebuf
+flush
+
+These words have the following API. In all words, "hdl" is a pointer to a IO
+handle. If the handle has no concept of "flush", it can point to "drop" (as a
+noop).
+
+readbuf ( n hdl -- a? read-n )
+ Try to read "n" bytes from file from current position. Unless EOF is reached,
+ at least 1 byte must be read, but otherwise "read-n" can be lower than n, even
+ if that doesn't take the handle to EOF. The idea is to take the handle, at
+ most, to the end of its internal buffer. Return the number of bytes read and,
+ if it was nonzero, return an address to the beginning of that buffer.
+
+ After the read operation, advance current position by "read-n" bytes.
+
+writebuf ( a n hdl -- written-n )
+ Try to write "n" bytes from buffer "a" to file from current position, growing
+ the file if needed. The idea is the same as with freadbuf: the filesystem can
+ proceed as is best for its implementation, as long as it writes at least 1
+ byte. If no data can be written, return 0.
+
+ After the write operation, advance current position by "written-n" bytes.
+
+flush ( hdl -- )
+ If the handle's buffer is "dirty" (has been changed compared to the permanent
+ storage it represents), save that data to permanent storage and flag the
+ handle as clean.
+
+## Generic API
+
+From the base API of IO handlers, lib/io has these generic words:
+
+getc ( hdl -- b )
+ Read 1 byte from hdl an return it. Advance position by 1 byte. Return -1 on
+ EOF.
+
+putc ( b hdl -- )
+ Write 1 byte to hdl. Advance position by 1 byte. Aborts if unable to write.
+
+It also has, for each IO handler word, a helper to directly call it. For
+example, "hdl flush" simply does "hdl 8 + @ execute".
diff --git a/fs/fs/fat.fs b/fs/fs/fat.fs
@@ -74,7 +74,7 @@ $ffff const EOC
over 2 - $fff6 > if abort" cluster out of range!" then
swap FirstSectorOfCluster ( dst sec ) swap BPB_SecPerClus swap writesectors ;
-: _ ( fcursor -- ) \ fatflush
+: fatflush ( fcursor -- )
dup FCUR_dirty? not if drop exit then ( fcursor )
\ save buffer
dup FCUR_cluster over FCUR_buf( writecluster ( fcursor )
@@ -83,7 +83,6 @@ $ffff const EOC
writecursector
\ undirty the cursor
dup FCUR_flags $fffffffd and swap FCUR_flags! ;
-current to fatflush
\ grow fcursor to newsz, if needed
: fatgrow ( newsz fcursor -- )
@@ -104,3 +103,5 @@ current to fatflush
r@ FCUR_)buf r@ FCUR_bufpos - ( src n nmax )
min ( src n ) r> FCUR_bufpos swap ( src dst n )
dup >r move r> ( n ) ;
+
+: fatopen fatopenlo ['] fatwritebuf over 4 + ! ['] fatflush over 8 + ! ;
diff --git a/fs/fs/fatlo.fs b/fs/fs/fatlo.fs
@@ -141,6 +141,7 @@ here const )fnbuf
fatfindpath ( dentry ) curdir( DIRENTRYSZ move ;
\ File cursor
+\ 12b IO handler prelude
\ 4b flags. all zeroes = free cursor
\ b0 = used
\ b1 = buffer is dirty
@@ -152,25 +153,25 @@ here const )fnbuf
\ 4b file size
\ Xb current cluster X=ClusterSize
10 const FCURSORCNT \ maximum number of opened files
-: FCursorSize ClusterSize 24 + ;
+: FCursorSize ClusterSize 36 + ;
: FCUR_flags ( fcur -- n ) @ ;
: FCUR_free? ( fcur -- f ) FCUR_flags not ;
: FCUR_dirty? ( fcur -- f ) FCUR_flags 2 and ;
: FCUR_flags! ( n fcur -- ) ! ;
-: FCUR_cluster ( fcur -- n ) 4 + @ ;
-: FCUR_cluster! ( n fcur -- ) 4 + ! ;
-: FCUR_clusteridx ( fcur -- n ) 8 + @ ;
-: FCUR_clusteridx! ( n fcur -- n ) 8 + ! ;
-: FCUR_pos ( fcur -- n ) 16 + @ ;
-: FCUR_pos! ( n fcur -- n ) 16 + ! ;
-: FCUR_pos+ ( n fcur -- ) 16 + +! ;
-: FCUR_size ( fcur -- n ) 20 + @ ;
-: FCUR_size! ( n fcur -- ) 20 + ! ;
-: FCUR_buf( ( fcur -- a ) 24 + ;
+: FCUR_cluster ( fcur -- n ) 16 + @ ;
+: FCUR_cluster! ( n fcur -- ) 16 + ! ;
+: FCUR_clusteridx ( fcur -- n ) 20 + @ ;
+: FCUR_clusteridx! ( n fcur -- n ) 20 + ! ;
+: FCUR_pos ( fcur -- n ) 28 + @ ;
+: FCUR_pos! ( n fcur -- n ) 28 + ! ;
+: FCUR_pos+ ( n fcur -- ) 28 + +! ;
+: FCUR_size ( fcur -- n ) 32 + @ ;
+: FCUR_size! ( n fcur -- ) 32 + ! ;
+: FCUR_buf( ( fcur -- a ) 36 + ;
: FCUR_)buf ( fcur -- a ) FCUR_buf( ClusterSize + ;
: FCUR_bufpos ( fcur -- a ) dup FCUR_pos ClusterSize mod swap FCUR_buf( + ;
: FCUR_dirent ( fcur -- dirent )
- 12 + @ BPB_BytsPerSec /mod ( offset sec ) 1 readsector ( off ) fatbuf( + ;
+ 24 + @ BPB_BytsPerSec /mod ( offset sec ) 1 readsector ( off ) fatbuf( + ;
: FCUR_cluster0 ( fcur -- cl ) FCUR_dirent DIR_Cluster ;
create fcursors( FCursorSize FCURSORCNT * allot0
@@ -189,29 +190,12 @@ create fcursors( FCursorSize FCURSORCNT * allot0
over 2 - $fff6 > if abort" cluster out of range!" then
swap FirstSectorOfCluster ( dst sec ) swap BPB_SecPerClus swap readsectors ;
-\ Open the specified "direntry" into one of the free cursors and return that
-\ cursor.
-: openfile ( direntry -- fcursor )
- findfreecursor >r
- 0 r@ FCUR_cluster! ( dirent ) 1 r@ FCUR_flags!
- dup fatbuf( - bufsec BPB_BytsPerSec * + ( dirent doffset ) r@ 12 + !
- -1 r@ FCUR_clusteridx! 0 r@ FCUR_pos!
- DIR_FileSize r@ FCUR_size! ( ) r> ;
-
-: fatopen ( path -- fcursor ) fatfindpath openfile ;
-
-\ if the cursor is dirty, write its buffer to disk as well as its size to the
-\ direntry on disk. Unsets the dirty flag.
-\ as long as fs/fat is not loaded, we can't write anything to files, so fatflush
-\ will always be a noop.
-alias drop fatflush ( fcursor -- )
-
\ set fcursor to pos. If new pos crosses cluster boundaries compared to current
\ pos, flush current buffer and read a new sector from disk.
: fatseek ( pos fcursor -- )
over 0< if abort" can't seek to negative pos" then
over ClusterSize / over FCUR_clusteridx = not if
- dup fatflush >r ( pos )
+ dup dup 8 + @ ( 'flush ) execute >r ( pos )
dup ClusterSize / dup r@ FCUR_clusteridx! ( pos idx )
r@ FCUR_cluster0 ( pos idx cl )
swap ?dup if >r begin ( pos cl ) FAT@ next then ( pos cl )
@@ -228,4 +212,16 @@ alias drop fatflush ( fcursor -- )
rot min ( a n )
dup r> FCUR_pos+ ( a n ) ;
-: fatclose ( fcursor ) dup fatflush 0 swap FCUR_flags! ;
+\ Open the specified "path" into one of the free cursors and return that
+\ cursor. This is the "low" part. Complete open is finalized in fs/fat
+: fatopenlo ( path -- fcursor )
+ fatfindpath findfreecursor >r
+ \ write IO handle prelude: readbuf, writebuf, flush
+ ['] fatreadbuf r@ ! ['] abort r@ 4 + ! ['] drop r@ 8 + !
+ \ write the rest
+ 0 r@ FCUR_cluster! ( dirent ) 1 r@ FCUR_flags!
+ dup fatbuf( - bufsec BPB_BytsPerSec * + ( dirent doffset ) r@ 24 + !
+ -1 r@ FCUR_clusteridx! 0 r@ FCUR_pos!
+ DIR_FileSize r@ FCUR_size! ( ) r> ;
+
+: fatclose ( fcursor ) dup dup 8 + @ ( 'flush ) execute 0 swap FCUR_flags! ;
diff --git a/fs/tests/fs/fat.fs b/fs/tests/fs/fat.fs
@@ -8,8 +8,8 @@
: fatgetc ( fcursor -- c ) 1 swap fatreadbuf if c@ else -1 ( EOF ) then ;
testbegin
\ Tests for fs/fat
-S" tests/fattest" fatfindpath ( dirent )
-openfile ( fcursor ) dup fatgetc 'T' #eq
+S" tests/fattest" fatopen ( fcursor )
+dup fatgetc 'T' #eq
$100 over fatseek
dup fatgetc 'f' #eq dup fatgetc 'o' #eq dup fatgetc 'o' #eq
$200 over fatseek
@@ -30,7 +30,7 @@ S" /lib/str.fs" fatfindpath # \ found!
S" newfile" fatnewfile #
S" /newfile" fatfindpath # \ found!
\ let's try writing to it
-S" /newfile" fatfindpath openfile ( fc )
+S" /newfile" fatopen ( fc )
dup FCUR_cluster0 0 #eq \ no cluster allocated yet
dup S" 42" c@+ rot fatwritebuf 2 #eq ( fc ) fclose
f<< /newfile 42 #eq
diff --git a/fs/xcomp/glue2.fs b/fs/xcomp/glue2.fs
@@ -1,5 +1,5 @@
bootfile xcomp/glue2.fs
\ Glue code that goes between the filesystem part and boothi
-alias fatopen fopen
+alias fatopenlo fopen
alias fatreadbuf freadbuf
alias fatclose fclose