AppFS

The PocketSprite includes a flash filesystem that allows storing multiple ESP32 applications, as well as other files, in the ESP32 flash. This filesystem is called appfs. Because this filesystem has to be compatible with the way the ESP32 loads applications and maps memory, it is somewhat different from other filesystems: while files have names and sizes as you’d expect, accessing them is more akin to accessing the raw flash underneath it and the file access functions mirror the ESP-IDF flash functions more than they look like standard POSIX file access methods.

An appfs can only be stored in a partition in the SPI flash the ESP32 boots from, and thus has a maximum size of 16MiB. The file system has a sector size of 64KiB which lines up with the 64KiB pages of the ESP32 MMU. This, however, also implies that any file stored on the file system occupies at least 64KiB of flash. With this in mind, please store your data in one large file instead of many smaller ones whenever possible

Some quirks of this approach are:
  • Files cannot be expanded or shrunk. The filesize has to be known on creation and cannot be changed afterwards.
  • A write to a file can only reset bits (1->0). In order to overwrite existing data, you need to make sure the region you write in is erased, and if not, erase it first. Note that erasing can only happen on a 4K block.
  • The appfs on the PocketSprite is 15.3 MiB in size. With a minimum file size of 64K, this means at maximum there can be 245 files stored in the flash.

Note: PocketSprite software not running on PocketSprite hardware does not have an AppFS filesystem available.

Functions

esp_err_t appfsInit(int type, int subtype)

Initialize the appfs code and mount the appfs partition.

Run this before using any of the other AppFs APIs

Return
ESP_OK if all OK, an error from the underlying partition or flash code otherwise.
Parameters
  • type: Partition type. Normally you’d pass APPFS_PART_TYPE here.
  • subtype: Partition subtype. Normally you’d pass APPFS_PART_SUBTYPE here.

int appfsExists(const char *filename)

Check if a file with the given filename exists.

Return
1 if a file with a name which exactly matches filename exists; 0 otherwise.
Parameters
  • filename: Filename to check

bool appfsFdValid(int fd)

Check if a file descriptor is valid.

Because file descriptors are integers which are more-or-less valid over multiple sessions, they can be stored in non-volatile memory and re-used later. When doing this, a sanity check to see if the fd still points at something valid may be useful. This function provides that sanity check.

Return
True if fd points to a valid file, false otherwise.
Parameters
  • fd: File descriptor to check

appfs_handle_t appfsOpen(const char *filename)

Open a file on a mounted appfs.

Return
The filedescriptor if succesful, APPFS_INVALID_FD if not.
Parameters
  • filename: Filename of the file to open

void appfsClose(appfs_handle_t handle)

Close a file on a mounted appfs.

Note
In the current appfs implementation, this is a no-op. This may change in the future, however.
Parameters
  • handle: File descriptor to close

esp_err_t appfsDeleteFile(const char *filename)

Delete a file on the appfs.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • filename: Name of the file to delete

esp_err_t appfsCreateFile(const char *filename, size_t size, appfs_handle_t *handle)

Create a new file on the appfs.

Initially, the file will have random contents consisting of whatever used the sectors of flash it occupies earlier. Note that this function also opens the file and returns a file descriptor to it if succesful; no need for a separate appfsOpen call.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • filename: Name of the file to be created
  • size: Size of the file, in bytes
  • handle: Pointer to an appfs_handle_t which will store the file descriptor of the created file

esp_err_t appfsMmap(appfs_handle_t fd, size_t offset, size_t len, const void **out_ptr, spi_flash_mmap_memory_t memory, spi_flash_mmap_handle_t *out_handle)

Map a file into memory.

This maps a (portion of a) file into memory, where you can access it as if it was an array of bytes in RAM. This uses the MMU and flash cache of the ESP32 to accomplish this effect. The memory is read-only; trying to write to it will cause an exception.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • fd: File descriptor of the file to map.
  • offset: Offset into the file where the map starts
  • len: Lenght of the map
  • out_ptr: Pointer to a const void* variable where, if successful, a pointer to the memory is stored.
  • memory: One of SPI_FLASH_MMAP_DATA or SPI_FLASH_MMAP_INST, where the former does a map to data memory and the latter a map to instruction memory. You’d normally use the first option.
  • out_handle: Pointer to a spi_flash_mmap_handle_t variable. This variable is needed to later free the map again.

void appfsMunmap(spi_flash_mmap_handle_t handle)

Unmap a previously mmap’ped file.

This unmaps a region previously mapped with appfsMmap

Parameters
  • handle: Handle obtained in the previous appfsMmap call

esp_err_t appfsErase(appfs_handle_t fd, size_t start, size_t len)

Erase a portion of an appfs file.

This sets all bits in the region to be erased to 1, so an appfsWrite can reset selected bits to 0 again.

Return
ESP_OK if file successfully deleted, an error otherwise.
Parameters
  • fd: File descriptor of file to erase in.
  • start: Start offset of file portion to be erased. Must be aligned to 4KiB.
  • len: Length of file portion to be erased. Must be a multiple of 4KiB.

esp_err_t appfsWrite(appfs_handle_t fd, size_t start, uint8_t *buf, size_t len)

Write to a file in appfs.

Note: Because this maps directly to a write of the underlying flash memory, this call is only able to reset bits in the written area from 1 to 0. If you want to change bits from 0 to 1, call appfsErase on the area to be written before calling this function. This function will return success even if the data in flash is not the same as the data in the buffer due to bits being 1 in the buffer but 0 on flash.

If the above paragraph is confusing, just remember to erase a region before you write to it.

Return
ESP_OK if write was successful, an error otherwise.
Parameters
  • fd: File descriptor of file to write to
  • start: Offset into file to start writing
  • buf: Buffer of bytes to write
  • len: Length, in bytes, of data to be written

esp_err_t appfsRead(appfs_handle_t fd, size_t start, void *buf, size_t len)

Read a portion of a file in appfs.

This function reads len bytes of file data, starting from offset start, into the buffer buf. Note that if you do many reads, it usually is more efficient to map the file into memory and read from it that way instead.

Return
ESP_OK if read was successful, an error otherwise.
Parameters
  • fd: File descriptor of the file
  • start: Offset in the file to start reading from
  • buf: Buffer to contain the read data
  • len: Length, in bytes, of the data to read

esp_err_t appfsRename(const char *from, const char *to)

Atomically rename a file.

This atomically renames a file. If a file with the target name already exists, it will be deleted. This action is done atomically, so at any point in time, either the original or the new file will fully exist under the target name.

Return
ESP_OK if rename was successful, an error otherwise.
Parameters
  • from: Original name of file
  • to: Target name of file

void appfsEntryInfo(appfs_handle_t fd, const char **name, int *size)

Get file information.

Given a file descriptor, this returns the name and size of the file. The file descriptor needs to be valid for this to work.

Parameters
  • fd: File descriptor
  • name: Pointer to a char pointer. This will be pointed at the filename in memory. There is no need to free the pointer memory afterwards. Pointer memory is valid while the file exists / is not deleted. Can be NULL if name information is not wanted.
  • size: Pointer to an int where the size of the file will be written, or NULL if this information is not wanted.

appfs_handle_t appfsNextEntry(appfs_handle_t fd)

brief Get the next entry in the appfs.

This function can be used to list all the files existing in the appfs. Pass it APPFS_INVALID_FD when calling it for the first time to receive the first file descriptor. Pass it the result of the previous call to get the next file descriptor. When this function returns APPFS_INVALID_FD, all files have been enumerated. You can use appfsEntryInfo() to get the file name and size associated with the returned file descriptors

Return
Next file descriptor, or APPFS_INVALID_FD if all files have been enumerated
Parameters
  • fd: File descriptor returned by previous call, or APPFS_INVALID_FD to get the first file descriptor

esp_err_t appfsGetCurrentApp(appfs_handle_t *ret_app)

Get file descriptor of currently running app.

Return
ESP_OK on success, an error when e.g. the currently running code isn’t located in appfs.
Parameters
  • ret_app: Pointer to variable to hold the file descriptor

size_t appfsGetFreeMem()

Get amount of free space in appfs partition.

Return
amount of free space, in bytes

void appfsDump()

Debugging function: dump current appfs state.

Prints state of the appfs to stdout.

Macros

APPFS_PART_TYPE
APPFS_PART_SUBTYPE
APPFS_INVALID_FD

Type Definitions

typedef int appfs_handle_t