SD Cards and FAT32 Filesystems in zeptoforth - tabemann/zeptoforth GitHub Wiki
zeptoforth has support for SDHC/SDXC cards for data storage, traditional PC-style master boot record partition tables on SDHC/SDXC cards (only four partitions currently supported), and FAT32 filesystems in such partitions. FAT32 filesystems provide extra convenience to the user, but raw access to SDHC/SDXC cards is also possible.
In the below tests we will be using a 3.3v SD card interface board (Raspberry Pi Picos have 3.3v logic, and we will be using SDHC/SDXC cards in "high voltage", i.e. 3.3v, mode, but some interface boards meant for Arduino have unnecessary logic to handle the fact that Arduino uses 5v logic which we will not want to deal with) with an SDHC/SDXC card inserted, which is formatted to FAT32, tied to a Raspberry Pi Pico, which we will be exercising using the SD card SPI protocol. Pin 4 (GPIO pin 2) pin on the Pico will be the SCK pin. Pin 5 (GPIO pin 3) on the Pico will be the MOSI pin. Pin 6 (GPIO pin 4) on the Pico will be the MISO pin. Pin 7 (GPIO pin 5) on the Pico will be the Chip Select (CSn) pin. Tie these pins to the appropriate pins on our SD card interface board.
Note that the GPIO pins selected for use with a given SPI peripheral selected for use with the SDHC/SDXC card interface must match that SPI peripheral. For instance, on the Raspberry Pi Pico pins 4, 5, and 6 (GPIO pins 2, 3, and 4, for SPI0 SCK, SPI0 TX, and SPI0 RX respectively) correspond to SPI peripheral 0, while pins 14. 15. and 16 (GPIO pins 10, 11, and 12, for SPI1 SCK, SPI1 TX, and SPI1 RX respectively) correspond to SPI peripheral 1. Were the latter pins to be used, one would have to specify SPI peripheral 1, and one cannot mix pins corresponding to different SPI peripherals (unless a given pin can specifically be used for multiple different SPI peripherals, as is the case for some pins on some STM32 platforms). The only pins which this restriction does not apply to are the Chip Select pins, as the SDHC/SDXC does not use the build in hardware Chip Select mechanism but rather uses a purely software-driven Chip Select, so any otherwise unused GPIO pin may be used for Chip Select, regardless of whether it may be used as a hardware Chip Select pin for any given SPI peripheral.
Before we get into the details of using SDHC/SDXC cards and FAT32 filesystems at a lower level, we will explain here the high-level, "user-friendly" interface that covers many of the simple use cases.
For an example of setting up a FAT32 filesystem using the <simple-fat32-fs>
class, which encapsulates the SPI, SDXC/SDHC card, MBR, and FAT32 filesystems, take the following:
oo import
simple-fat32 import
fat32-tools import
<simple-fat32-fs> class-size buffer: my-fs
2 3 4 5 0 <simple-fat32-fs> my-fs init-object
my-fs current-fs!
Here we import the modules oo
, which provides class-size
and init-object
; simple-fat32
, which provides the <simple-fat32-fs>
class; and fat32-tools
, which provides current-fs!
and other words we will see later. Then we allot a buffer my-fs
for an instance of <simple-fat32-fs>
given its class-size
. Afterwards we initialize an instance my-fs
of the <simple-fat32-fs>
class for SPI peripheral 0 with the GPIO pins 2, 3, and 4 (pins 4, 5 and 6) as SPI pins SPI0 SCK, SPI0 TX, and SPI0 RX respectively, and with GPIO pin 5 (pin 7) as our Chip Select pin. Note that the first three arguments, for the SPI pins, may be in any order, but the fourth argument must be the GPIO pin used for the Chip Select and the fifth argument must be the SPI peripheral index. Last but not least, we use current-fs!
to set my-fs
to be the FAT32 filesystem for use with the words in fat32-tools
.
To list the files in a directory, one may use list-dir
in the fat32-tools
module:
s" /" list-dir
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOO.FS ok
It should be noted that /
is the root directory, and for listing the root directory /
is necessary, while in other paths the initial /
is optional.
To create a directory in a filesystem, one may use create-dir
in the fat32-tools
module:
s" /TEST" create-dir ok
s" /" list-dir
TEST
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOO.FS ok
s" /TEST" list-dir
.
.. ok
Here /TEST
is the directory we are creating. This directory must not already exist or else an exception will be raised.
To create a file in a filesystem, one may use create-file
in the fat32-tools
module:
s" This is only a test!" s" /TEST/TEST.TXT" create-file ok
s" /TEST" list-dir
.
..
TEST.TXT ok
Here This is only a test!
is the data we are writing to the newly created file /TEST/TEST.TXT
. Note that the created file must not already exist or otherwise an exception will be raised.
To evaluate the contents of a file in the filesystem, one may use include
(which takes the path afterwards as a token) or included
(which takes a string address and length on the stack as an argument) in the fat32-tools
module:
s\" cr .\" This is a test. \"" s" /TEST0.FS" create-file ok
s\" include /TEST0.FS .\" This is only a test.\"" s" /TEST1.FS" create-file ok
include TEST1.FS
This is a test. This is only a test. ok
s" /TEST1.FS" included
This is a test. This is only a test. ok
Here we create two files containing Forth source code, /TEST0.FS
and /TEST1.FS
. /TEST1.FS
loads and evaluates /TEST0.FS
when it is evaluated before evaluating the rest of its own code. Then we load and evaluate /TEST1.FS
twice, once with include
, and once with included
.
To dump a file as both hex and ASCII, one may use dump-file
in the fat32-tools
module:
s" /TEST/TEST.TXT" dump-file
00000000 54 68 69 73 20 69 73 20 6F 6E 6C 79 20 61 20 74 |This is only a t|
00000010 65 73 74 21 -- -- -- -- -- -- -- -- -- -- -- -- |est!............|
ok
To dump a file as ASCII alone, one may use dump-file-ascii
in the fat32-tools
modules:
s" /TEST/TEST.TXT" dump-file-ascii
00000000 |This is only a test!............................................|
ok
To dump a file as raw data, one may use dump-file-raw
in the fat32-tools
module:
s" /TEST/TEST.TXT" dump-file-raw This is only a test! ok
To copy a file, one may use copy-file
in the fat32-tools
module:
s" /TEST1" create-dir ok
s" /TEST/TEST.TXT" s" /TEST1/TEST.TXT" copy-file ok
s" /TEST1/TEST.TXT" dump-file
00000000 54 68 69 73 20 69 73 20 6F 6E 6C 79 20 61 20 74 |This is only a t|
00000010 65 73 74 21 -- -- -- -- -- -- -- -- -- -- -- -- |est!............|
ok
Here /TEST/TEST.TXT
is the source file and /TEST1/TEST.TXT
is the destination file to be created.
To append data to a file, one may use append-file
in the fat32-tools
module:
s" Repeat, this is only a test!" s" /TEST1/TEST.TXT" append-file ok
s" /TEST1/TEST.TXT" dump-file
00000000 54 68 69 73 20 69 73 20 6F 6E 6C 79 20 61 20 74 |This is only a t|
00000010 65 73 74 21 20 52 65 70 65 61 74 2C 20 74 68 69 |est! Repeat, thi|
00000020 73 20 69 73 20 6F 6E 6C 79 20 61 20 74 65 73 74 |s is only a test|
00000030 21 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- |!...............|
Here Repeat, this is only a test!
is the data we append to /TEST1/TEST.TXT
.
To overwrite a file, and truncate it to the length of the data written, one may use write-file
in the fat32-tools
module:
s" This is not a test!" s" /TEST1/TEST.TXT" write-file ok
s" /TEST1/TEST.TXT" dump-file
00000000 54 68 69 73 20 69 73 20 6E 6F 74 20 61 20 74 65 |This is not a te|
00000010 73 74 21 -- -- -- -- -- -- -- -- -- -- -- -- -- |st!.............|
ok
Here This is not a test!
is the data we overwrite /TEST1/TEST.TXT
with. Note that the file written to must already exist or else an exception will be raised.
To overwrite a portion of a file without truncating it, one may use write-file-window
in the fat32-tools
module:
s" NOT" 8 s" /TEST1/TEST.TXT" write-file-window ok
s" /TEST1/TEST.TXT" dump-file
00000000 54 68 69 73 20 69 73 20 4E 4F 54 20 61 20 74 65 |This is NOT a te|
00000010 73 74 21 -- -- -- -- -- -- -- -- -- -- -- -- -- |st!.............|
ok
Here NOT
is the at written to /TEST1/TEST.TXT
starting at offset 8. Note that the file written to must already exist or else an exception will be raised.
To read a portion of a file, one may use read-file
in the fat32-tools
module:
256 constant buf-size ok
buf-size buffer: my-buf ok
my-buf buf-size 0 fill ok
my-buf buf-size 8 s" /TEST1/TEST.TXT" read-file . 11 ok
my-buf dup 11 + dump
20016BB4 4E 4F 54 20 61 20 74 65 73 74 21 -- -- -- -- -- |NOT a test!.....|
ok
Here we create a 256 byte buffer and read starting at offset 8 in /TEST1/TEST.TXT
into that buffer; afterwards we dump the contents of the buffer that was read into. Note that read-file
returns the actual length of the data read in bytes, in this case 11 bytes.
To get the size of a file in bytes, one may use file-size@
in the fat32-tools
module:
s" /TEST1/TEST.TXT" file-size@ . 19 ok
Here we get the size of /TEST1/TEST.TXT
in bytes, which in this case is 19 bytes.
To rename a file or directory, one may use rename
in the fat32-tools
module:
s" /TEST1" list-dir
.
..
TEST.TXT ok
/TEST1/TEST.TXT" s" TESTA.TXT" rename ok
s" /TEST1" list-dir
.
..
TESTA.TXT ok
Here we rename /TEST1/TEST.TXT
from TEST.TXT
to TESTA.TXT
. Note that the file may not be moved between different directories with this operation, hence why the second pair of arguments, TESTA.TXT
, does not include a path but only a file or directory name.
For an example of the same for moving directories:
s" /" list-dir
TEST
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOO.FS
TEST1 ok
s" /TEST1" s" TEST2" rename ok
s" /" list-dir
TEST
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOO.FS
TEST2 ok
To remove a file, one may use remove-file
in the fat32-tools
module:
s" /TEST2" list-dir
.
..
TESTA.TXT ok
s" /TEST2/TESTA.TXT" remove-file ok
s" /TEST2" list-dir
.
.. ok
Here we remove the file TESTA.TXT
from the directory /TEST2
.
To remove a directory, one may use remove-dir
in the fat32-tools
module:
s" /" list-dir
TEST
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOO.FS
TEST2 ok
s" /TEST2" remove-dir ok
s" /" list-dir
TEST
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOO.FS ok
Here we remove the directory TEST
from the root directory. Note that when removing directories, they must be empty aside from the .
and ..
entries.
The abstract API for block devices is provided by the block-dev
module, which defines a <block-dev>
class. The <sd>
class in the sd
module is a subclass of the <block-dev>
class which implements SDHC/SDXC cards as block devices.
For an example of reading the start of the first partition, at block 8192, of an SDHC/SDXC card:
oo import
block-dev import
sd import
spi import
pin import
Here we have imported the necessary modules oo
, to support object-orientation, block-dev
, for the block device API, sd
, for the <sd>
class implementing the block device API, and spi
and pin
, for configuring pins for SPI.
<sd> class-size buffer: my-sd
512 constant block-size
block-size buffer: my-buffer
0 constant my-spi ok
Here we have alloted space for the <sd>
instance for our SDHC/SDXC card and a buffer to store the block we read from it. We also define constants for the block size (which is guaranteed to be 512 bytes) and for the index of the SPI peripheral we are using (which is 0).
my-spi 2 spi-pin
my-spi 3 spi-pin
my-spi 4 spi-pin
5 output-pin
2 pull-up-pin
3 pull-up-pin
4 pull-up-pin
5 pull-up-pin
Here we configure GPIO pins 2 through 4 to be SPI pins for our SPI device and GPIO pin 5 as a generic output GPIO pin, which we will be using for our Chip Select line for selecting our SDHC/SDXC card (note that more than one SPI device may share GPIO pins 2 through 4). All four pins are also set to be pull-up.
5 my-spi <sd> my-sd init-object
my-sd init-sd
Here we instantiate my-sd as an instance of the <sd>
class implementing our SDHC/SDXC card interface, with our SPI device and GPIO pin 5 as a Chip Select pin and the initialize it.
my-buffer block-size 8192 my-sd block@
Here we read block 8192, which normally contains the first block of the first partition on the SDHC/SDXC card, into the buffer my-buffer
, which is 512 bytes in size.
my-buffer dup block-size + dump
2001282C EB 58 90 6D 6B 66 73 2E 66 61 74 00 02 20 20 00 |.X.mkfs.fat.. .|
2001283C 02 00 00 00 00 F8 00 00 20 00 40 00 00 20 00 00 |........ .@.. ..|
2001284C 00 60 B7 03 80 3B 00 00 00 00 00 00 02 00 00 00 |.`...;..........|
2001285C 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001286C 80 00 29 89 DF AA B4 4E 4F 20 4E 41 4D 45 20 20 |..)....NO NAME |
2001287C 20 20 46 41 54 33 32 20 20 20 0E 1F BE 77 7C AC | FAT32 ...w|.|
2001288C 22 C0 74 0B 56 B4 0E BB 07 00 CD 10 5E EB F0 32 |".t.V.......^..2|
2001289C E4 CD 16 CD 19 EB FE 54 68 69 73 20 69 73 20 6E |.......This is n|
200128AC 6F 74 20 61 20 62 6F 6F 74 61 62 6C 65 20 64 69 |ot a bootable di|
200128BC 73 6B 2E 20 20 50 6C 65 61 73 65 20 69 6E 73 65 |sk. Please inse|
200128CC 72 74 20 61 20 62 6F 6F 74 61 62 6C 65 20 66 6C |rt a bootable fl|
200128DC 6F 70 70 79 20 61 6E 64 0D 0A 70 72 65 73 73 20 |oppy and..press |
200128EC 61 6E 79 20 6B 65 79 20 74 6F 20 74 72 79 20 61 |any key to try a|
200128FC 67 61 69 6E 20 2E 2E 2E 20 0D 0A 00 00 00 00 00 |gain ... .......|
2001290C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001291C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001292C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001293C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001294C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001295C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001296C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001297C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001298C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001299C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
20012A0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
20012A1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA |..............U.|
ok
Now we dump the contents of my-buffer
so the user can see what is in the first block of the first filesystem on the SDHC/SDXC card.
Afterwards, we change mkfs
to QUUX
in block 8192 as seen below:
s" QUUX" my-buffer 3 + swap move
my-buffer block-size 8192 my-sd block!
my-buffer
contains the contents of block 8192, so we overwrite mkfs
at offset 3 in my-buffer
with QUUX
. Afterwards, we write the contents of my-buffer
to the block cache of the my-sd
object. Note that <sd>
instances may cache multiple blocks without writing them to the SDHC/SDXC card for optimization purposes.
my-sd flush-blocks
my-sd clear-blocks
Here we flush the block cache of the my-sd
object to the SDHC/SDXC card, ensuring that the newly written block gets written out to the media. Afterwards we clear the block cache, to ensure that the next time we read block 8192 it will be read again from the media. Note that an alternative approach is to specify, in this case, true my-sd write-through!
, which specifies that blocks are to be written immediately, even though they are still stored in the block cache to optimize re-reading; this helps ensure data integrity, but at the cost of performance and flash wear.
It should also be noted that the block cache also operates on block reads; blocks that are in the block cache are not re-read from the media.
my-buffer block-size 8192 my-sd block@
Here we re-read block 8192 from the SDHC/SDXC card, and below we dump its contents, showing that we changed mkfs
to QUUX
on the media:
my-buffer dup block-size + dump
2001282C EB 58 90 51 55 55 58 2E 66 61 74 00 02 20 20 00 |.X.QUUX.fat.. .|
2001283C 02 00 00 00 00 F8 00 00 20 00 40 00 00 20 00 00 |........ .@.. ..|
2001284C 00 60 B7 03 80 3B 00 00 00 00 00 00 02 00 00 00 |.`...;..........|
2001285C 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001286C 80 00 29 89 DF AA B4 4E 4F 20 4E 41 4D 45 20 20 |..)....NO NAME |
2001287C 20 20 46 41 54 33 32 20 20 20 0E 1F BE 77 7C AC | FAT32 ...w|.|
2001288C 22 C0 74 0B 56 B4 0E BB 07 00 CD 10 5E EB F0 32 |".t.V.......^..2|
2001289C E4 CD 16 CD 19 EB FE 54 68 69 73 20 69 73 20 6E |.......This is n|
200128AC 6F 74 20 61 20 62 6F 6F 74 61 62 6C 65 20 64 69 |ot a bootable di|
200128BC 73 6B 2E 20 20 50 6C 65 61 73 65 20 69 6E 73 65 |sk. Please inse|
200128CC 72 74 20 61 20 62 6F 6F 74 61 62 6C 65 20 66 6C |rt a bootable fl|
200128DC 6F 70 70 79 20 61 6E 64 0D 0A 70 72 65 73 73 20 |oppy and..press |
200128EC 61 6E 79 20 6B 65 79 20 74 6F 20 74 72 79 20 61 |any key to try a|
200128FC 67 61 69 6E 20 2E 2E 2E 20 0D 0A 00 00 00 00 00 |gain ... .......|
2001290C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001291C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001292C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001293C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001294C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001295C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001296C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001297C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001298C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
2001299C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129AC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129BC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129CC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129DC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129EC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
200129FC 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
20012A0C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
20012A1C 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA |..............U.|
ok
FAT32 is implemented in the fat32
module on partitions on block devices which use traditional PC-style Master Boot Records. Master Boot Records are implemented through the <mbr>
class, which are initialized for a given <sd>
instance, and can contain from one to four partitions, numbered 0 through 3, which are captured through <partition>
instances. They contain trees of directories and files, which have the traditional FAT32 restrictions without VFAT support (i.e. 8.3 filenames).
For some examples of the use of FAT32, take the following:
oo import
block-dev import
sd import
spi import
pin import
fat32 import
Here we import all the necessary modules, which are the same as those needed for simply working with SDHC/SDXC cards, but with the addition of the fat32
module needed for FAT32.
0 constant my-spi
256 constant my-read-size
my-read-size buffer: my-read-buffer
<sd> class-size buffer: my-sd
<mbr> class-size buffer: my-mbr
<partition> class-size buffer: my-partition
<fat32-fs> class-size buffer: my-fs
<fat32-dir> class-size buffer: my-dir
<fat32-entry> class-size buffer: my-entry
<fat32-file> class-size buffer: my-file
Here we define all the constants and buffers needed. Notice how buffers for class instances are alloted.
my-spi 2 spi-pin
my-spi 3 spi-pin
my-spi 4 spi-pin
5 output-pin
2 pull-up-pin
3 pull-up-pin
4 pull-up-pin
5 pull-up-pin
5 my-spi <sd> my-sd init-object
my-sd init-sd
true my-sd write-through!
Here we do all the setup necessary to use the SDHC/SDXC card interface, with the addition that we will configure the SDHC/SDXC card interface to support write-through, so writes to media occur immediately rather than occurring only when the SDHC/SDXC card interface is flushed.
my-sd <mbr> my-mbr init-object
<partition> my-partition init-object
my-partition 0 my-mbr partition@
my-partition my-sd <fat32-fs> my-fs init-object
Here we instantiate the <mbr>
class for our my-sd
object as well as an instance of the <partition>
class. Then we read the first partition table entry in the master boot record into our <partition>
instance. Finally, we instantiate the <fat32-fs>
class for our my-partition
object so we have a usable filesystem for the first partition table entry.
: ls-root ( -- )
my-dir my-fs root-dir@
begin
my-entry my-dir read-dir if
12 [:
12 my-entry file-name@
cr type space false
;] with-allot
else
true
then
until
;
Afterwards, execute:
ls-root
FOO.FS
BIGFILE1.TXT
BAR.FS
BAZ.FS ok
Here we define a word, ls-root
, that opens the root directory on the FAT32 filesystem as my-dir
and reads each directory entry as my-entry
from it, printing the name of the entry out. Note that in these cases my-dir
and my-entry
need not be instantiated; they are automatically instantiated as the <fat32-dir>
and <fat32-entry>
classes, respectively, overwriting their previous contents. Then we call ls-root
, printing out the contents of the root directory. Here we see that the root directory already contains a few files on the particular media used when this tutorial was written; your root directory will probably look different from this.
: ls ( path-addr path-u -- )
[: my-dir swap open-dir ;] my-fs with-root-path
begin
my-entry my-dir read-dir if
12 [:
12 my-entry file-name@
cr type space false
;] with-allot
else
true
then
until
;
Here we define a word like ls-root
except it takes a path relative to the root directory, opens the directory at that location with open-dir
, and prints out its contents. There is no need to initialize the directory object; it is automatically initialized, and is overwritten if already initialized. There is also no need to - and no ability to - close a directory; the only consideration is that a directory object must not be used after removing it. Parsing the path is done with with-root-path
; note that each directory in the path is allocated temporarily in the dictionary, so make sure there is enough room in the current thread's dictionary, and also do not allot any space in the dictionary that you plan on using afterwards. Also note that with-root-path
cannot parse a path for the root directory itself, as it parses a path into a base directory and a leaf name.
: my-create-dir ( path-addr path-u )
[: my-dir swap create-dir ;] my-fs with-root-path
;
Afterwards, execute:
s" FOOBAR" my-create-dir ok
ls-root
FOO.FS
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOOBAR ok
Here we define a word using with-root-path
and create-dir
to define a directory at a given path. Afterwards, we list the root directory to show that the newly-created directory, FOOBAR
, exists.
: my-create-file ( data-addr data-u path-addr path-u )
[: my-file swap create-file ;] my-fs with-root-path
my-file write-file
;
Afterwards, execute:
s" This is only a test!" s" FOOBAR/FOO.TXT" my-create-file ok
Here we define a word using with-root-path
and create-file
to create a file at a given path, and then using write-file
to write a string to the file. Then we create a file in our newly-created directory containing the text This is only a test!
.
: cat ( path-addr path-u -- )
cr
[: my-file swap open-file ;] my-fs with-root-path
begin
my-read-buffer my-read-size my-file read-file dup 0> if
my-read-buffer swap type false
else
drop true
then
until
;
Afterwards, execute:
s" FOOBAR/FOO.TXT" cat
This is only a test! ok
Here we define a word that uses with-root-path
and open-file
to open a file at a given path, and then we read data from the file with read-file
and write it out to the console until read-file
returns a non-positive value. The file object need not be initialized; it will be automatically initialized by open-file
, and will be overwritten if already initialized. Note that there is no need to - and no functionality to - close a file; the only consideration is that a file object must not be used after that file has been removed. Then we use our newly-created word to show that the file has the contents we wrote to it earlier.
: my-file-size@ ( path-addr path-u -- size-bytes )
[: my-file swap open-file my-file file-size@ ;] my-fs with-root-path
;
Afterwards, exxecute:
s" FOOBAR/FOO.TXT" my-file-size@ . 20 ok
Here we use file-size@
to get the size of a file in bytes after parsing its path with with-root-path
and opening it with open-file
: my-truncate-file-from-end ( offset path-addr path-u -- )
[:
my-file swap open-file
seek-end my-file seek-file
my-file tell-file .
my-file truncate-file
;] my-fs with-root-path
;
Afterwards, execute:
-6 s" FOOBAR/FOO.TXT" my-truncate-file-from-end 14 ok
s" FOOBAR/FOO.TXT" cat
This is only a ok
Here we use seek-file
to seek to a given offset in a file after parsing its path with with-root-path
and opening it with open-file
. seek-file
sets the current location in a file, and has the stack signature ( offset whence file -- ), where file is the file object in question, whence is one of seek-set
, seek-cur
, or seek-end
, where seek-set
is to set the current location relative to the start of the file, seek-cur
is to set the current location relative to the current location in the file, and seek-end
is to set the current location relative to the end of the file, and offset is a signed offset from whence in bytes; note that the resulting current location is clamped to the start of the file and the end of the file.
Afterwards, we use tell-file
to get the current offset from the start of a given file object in bytes. We then use truncate-file
to truncate the file to the current location in the file. Finally, we output the contents of the file to show that we indeed have truncated it by six bytes.
: my-rename ( new-name-addr new-name-u path-addr path-u -- )
['] rename my-fs with-root-path
;
Afterwards, execute:
s" BAR.TXT" s" FOOBAR/FOO.TXT" my-rename ok
s" FOOBAZ" s" FOOBAR" my-rename ok
ok
s" FOOBAZ" ls
.
..
BAR.TXT ok
ok
ls-root
FOO.FS
BIGFILE1.TXT
BAR.FS
BAZ.FS
FOOBAZ ok
Here we define a word using with-root-path
and rename
to rename files and directories at given paths. Note that rename
does not move files or directories; the files and directories cannot change parent directories with its use. It is safe to rename
a file or directory when it is in use elsewhere for this very reason, because the directory entries storing their metadata do not change location with its use. Then we use ls
and ls-root
to show the effect of the use of our word.
: my-remove-file ( path-addr path-u -- )
['] remove-file my-fs with-root-path
;
Afterwards, execute:
s" FOOBAZ/BAR.TXT" my-remove-file ok
s" FOOBAZ" ls
.
.. ok
Here we define a word using with-root-path
and remove-file
to remove a file at a given path. Then we use it to remove FOOBAZ/BAR.TXT
and we use ls
to show that it has been removed from the directory FOOBAZ
.
: my-remove-dir ( path-addr path-u -- )
['] remove-dir my-fs with-root-path
;
Afterwards, execute:
s" FOOBAZ" my-remove-dir ok
ls-root
FOO.FS
BIGFILE1.TXT
BAR.FS
BAZ.FS ok
ok
Here we define a word using with-root-path
and remove-dir
to remove a directory at a given path. Note that directories must be empty aside from the entries .
and ..
to be removed with remove-dir
. Then we use it to remove FOOBAZ
and we use ls-root
to show that it has been removed.