SD Cards and FAT32 Filesystems in zeptoforth - tabemann/zeptoforth GitHub Wiki

Introduction

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.

Doing Things The Easy Way

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.

Low-Level SDHC/SDXC Card Usage

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

Low-Level FAT32 Usage

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.

⚠️ **GitHub.com Fallback** ⚠️