Merge pull request #11 from Sani7/feature/llfs-extended-fs-api
Extended the llfs file system api
This commit is contained in:
114
docs/llfs.md
114
docs/llfs.md
@@ -1,17 +1,52 @@
|
|||||||
# LLFS (Linked List File System)
|
# LLFS (Linked List File System)
|
||||||
|
|
||||||
## Introduction
|
## Introduction
|
||||||
The llfs filesystem can be generated using the mkllfss utility.
|
The llfs filesystem can be generated using the [mkllfs](mkllfs.md) utility.
|
||||||
It is a simple filesystem that uses a linked list to store files.
|
The resulting C file encapsulates the filesystem data within a linked list which can be used by the llfs library.
|
||||||
The filesystem is stored in a single c file (`llfs_data.c`), which can be compiled and read by the llfs library.
|
|
||||||
The llfs filesystem is a flat filesystem, meaning that it does not support directories.
|
As a flat filesystem, llfs lacks support for directories.
|
||||||
|
Using the llfs API, information about the files in the filesystem can be retrieved,
|
||||||
|
and the files can be read using direct memory access.
|
||||||
|
Alternatively, the POSIX file functions can be used.
|
||||||
|
But this is more resource intensive, as data must be copied before using it.
|
||||||
|
|
||||||
|
It's essential to note that the llfs filesystem operates in a read-only mode,
|
||||||
|
restricting operations solely to read functions.
|
||||||
|
|
||||||
|
## Table of contents
|
||||||
|
- [Introduction](#introduction)
|
||||||
|
- [Table of contents](#table-of-contents)
|
||||||
|
- [Initialization](#initialization)
|
||||||
|
- [Usage of the llfs API](#usage-of-the-llfs-api)
|
||||||
|
- [The `llfs_file_t` struct](#the-llfs_file_t-struct)
|
||||||
|
- [Getting a list of files](#getting-a-list-of-files)
|
||||||
|
- [Iterator function (not recommended)](#iterator-function-not-recommended)
|
||||||
|
- [Reading a file](#reading-a-file)
|
||||||
|
- [Getting the number of files](#getting-the-number-of-files)
|
||||||
|
- [Using the POSIX file functions](#using-the-posix-file-functions)
|
||||||
|
|
||||||
|
## Initialization
|
||||||
|
Before using the llfs API, or the file related POSIX (fopen, fgetc, ...) functions, the filesystem must be initialized by calling `llfs_init()`.
|
||||||
|
|
||||||
## Usage of the llfs API
|
## Usage of the llfs API
|
||||||
|
### The `llfs_file_t` struct
|
||||||
|
The `llfs_file_t` struct contains information about a file in the filesystem.
|
||||||
|
```c
|
||||||
|
typedef struct {
|
||||||
|
const uint8_t* data; // Pointer to the file data (len bytes)
|
||||||
|
const char* name; // Null-terminated string with the filename
|
||||||
|
size_t len; // Length of the file data
|
||||||
|
} llfs_file_t;
|
||||||
|
```
|
||||||
|
The data pointer points to the data of the file in the filesystem, and can be used to read the file.
|
||||||
|
|
||||||
### Getting a list of files
|
### Getting a list of files
|
||||||
```c
|
```c
|
||||||
#include "llfs.h"
|
#include "llfs.h"
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
|
llfs_init();
|
||||||
|
|
||||||
// Allocate space for 10 files
|
// Allocate space for 10 files
|
||||||
llfs_file_t file_list[10];
|
llfs_file_t file_list[10];
|
||||||
|
|
||||||
@@ -33,11 +68,48 @@ Result:
|
|||||||
[Info] (2031) [main]: File: file1.txt, size: 77
|
[Info] (2031) [main]: File: file1.txt, size: 77
|
||||||
```
|
```
|
||||||
|
|
||||||
|
It is also possible to use a file extension filter (e.g. `*.bmp`, `*.txt`, `*.py`).
|
||||||
|
```c
|
||||||
|
// ...
|
||||||
|
size_t file_count = llfs_file_list(file_list, 10, "*.bmp");
|
||||||
|
// ...
|
||||||
|
```
|
||||||
|
This will only return files with the `.bmp` extension.
|
||||||
|
````
|
||||||
|
[Info] (2009) [main]: File: image.bmp, size: 9270
|
||||||
|
[Info] (2019) [main]: File: image2.bmp, size: 7738
|
||||||
|
````
|
||||||
|
|
||||||
|
#### Iterator function (not recommended)
|
||||||
|
It is also possible to iterate through the files without allocating an array.
|
||||||
|
When the memory pointer is `NULL`, the iterator will start at the beginning of the file list.
|
||||||
|
Each call to `llfs_next_file()` will return the next file in the list,
|
||||||
|
if a filter is specified files that don't match the filter will be skipped.
|
||||||
|
```c
|
||||||
|
#include "llfs.h"
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
llfs_init();
|
||||||
|
|
||||||
|
// Get the file list
|
||||||
|
void* mem = NULL; // Pointer for internal use by the llfs library
|
||||||
|
llfs_file_t* file;
|
||||||
|
while ((file = llfs_next_file(&mem, ".bmp")) != NULL) {
|
||||||
|
LOG_INFO(TAG, "File: %s", file->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
While this method doesn't require allocating memory for the file list,
|
||||||
|
it is slower than the previous method due to the overhead calling the function for each file.
|
||||||
|
Additionally, the required memory for the filelist is very small, so it's recommended to use the first method.
|
||||||
|
|
||||||
### Reading a file
|
### Reading a file
|
||||||
```c
|
```c
|
||||||
#include "llfs.h"
|
#include "llfs.h"
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
|
llfs_init();
|
||||||
|
|
||||||
// Get a file by name
|
// Get a file by name
|
||||||
llfs_file_t* file = llfs_file_open("filename with a space.txt");
|
llfs_file_t* file = llfs_file_open("filename with a space.txt");
|
||||||
|
|
||||||
@@ -55,3 +127,37 @@ Result:
|
|||||||
[Info] (2040) [main]: File found: filename with a space.txt, size: 61
|
[Info] (2040) [main]: File found: filename with a space.txt, size: 61
|
||||||
[Info] (2047) [main]: File data: This is a file with a space in it's filename.
|
[Info] (2047) [main]: File data: This is a file with a space in it's filename.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Getting the number of files
|
||||||
|
```c
|
||||||
|
#include "llfs.h"
|
||||||
|
|
||||||
|
void main(void) {
|
||||||
|
llfs_init();
|
||||||
|
|
||||||
|
// Get the number of files
|
||||||
|
size_t file_count = llfs_file_count();
|
||||||
|
|
||||||
|
// Print the number of files
|
||||||
|
LOG_INFO(TAG, "File count: %d", file_count);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using the POSIX file functions
|
||||||
|
The llfs library also supports the POSIX file functions.
|
||||||
|
As the file system is read-only, write functions are not implemented.
|
||||||
|
There is also a limit on the number of files that can be open concurrently,
|
||||||
|
this is set by the `POSIX_MAX_FILES` macro in `llfs.c`.
|
||||||
|
The default value is 10, but there are already 3 files in use (stdin, stdout, stderr),
|
||||||
|
so the maximum number of files that can be open is 7.
|
||||||
|
|
||||||
|
The following functions are tested and working, but other functions might also work:
|
||||||
|
- `fopen`
|
||||||
|
- `fclose`
|
||||||
|
- `fgetc`
|
||||||
|
- `fread`
|
||||||
|
- `fseek`
|
||||||
|
- `ftell`
|
||||||
|
- `rewind`
|
||||||
|
- `fstat`
|
||||||
|
- `fileno`
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
* @file llfs.h
|
* @file llfs.h
|
||||||
* @brief Linked List Filesystem header (llfs)
|
* @brief Linked List Filesystem header (llfs)
|
||||||
* @author Lorenz C.
|
* @author Lorenz C.
|
||||||
|
* @version 0.1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef LLFS_H
|
#ifndef LLFS_H
|
||||||
@@ -30,20 +31,25 @@ struct llfs_data_file {
|
|||||||
const struct llfs_data_file* next;
|
const struct llfs_data_file* next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Initialize the llfs filesystem
|
||||||
|
* @note This function should be called before any other llfs function or POSIX file operation (e.g. fopen, fread, ...)
|
||||||
|
* @return 0 on success
|
||||||
|
*/
|
||||||
|
int8_t llfs_init(void);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get a list of files in the filesystem
|
* @brief Get a list of files in the filesystem
|
||||||
* Get a list of all the files in the filesystem.
|
* Get a list of all the files in the filesystem.
|
||||||
*
|
*
|
||||||
* Use the filter to filter out files with a filename that do not match the filter. (e.g. "*.txt" or "*.png|*.jpg") (not
|
* Use the filter to filter out files with a filename that do not match the file extension filter.
|
||||||
* implemented yet) Multiple filters can be used by separating them with a pipe (|).
|
|
||||||
*
|
*
|
||||||
* The following members of the llfs_file_t struct are set: name, len and data. @ref llfs_file_t
|
* The following members of the llfs_file_t struct are set: name, len and data. @ref llfs_file_t
|
||||||
*
|
*
|
||||||
* @todo Implement file filter
|
|
||||||
*
|
*
|
||||||
* @param[out] file_list A pointer to an array of llfs_file_t to store the files in @ref llfs_file_t
|
* @param[out] file_list A pointer to an array of llfs_file_t to store the files in @ref llfs_file_t
|
||||||
* @param[in] max_files The maximum number of files to return (size of file_list)
|
* @param[in] max_files The maximum number of files to return (size of file_list)
|
||||||
* @param[in] filter A string with file extensions to filter out. (e.g. "*.txt" or "*.png|*.jpg")
|
* @param[in] filter A string with the file extensions to filter out. (e.g. "*.txt" or "*.png")
|
||||||
* @return The number of files returned
|
* @return The number of files returned
|
||||||
*/
|
*/
|
||||||
size_t llfs_file_list(llfs_file_t* file_list, size_t max_files, char* filter);
|
size_t llfs_file_list(llfs_file_t* file_list, size_t max_files, char* filter);
|
||||||
@@ -58,4 +64,23 @@ size_t llfs_file_list(llfs_file_t* file_list, size_t max_files, char* filter);
|
|||||||
*/
|
*/
|
||||||
llfs_file_t* llfs_file_open(const char* name);
|
llfs_file_t* llfs_file_open(const char* name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Iterate over all files in the filesystem
|
||||||
|
* For each call (with the same mem pointer) the next file in the filesystem is returned.
|
||||||
|
* The first call should be with mem = NULL.
|
||||||
|
* If a filter is specified, only files with a filename that matches the filter are returned.
|
||||||
|
*
|
||||||
|
* @param[in, out] mem A pointer to a void* that is used internally to keep track of the current file
|
||||||
|
* @param[in] filter A string with file extension to filter out. (e.g. "*.txt" or "*.png")
|
||||||
|
* @return The next file in the filesystem or NULL if there are no more files @ref llfs_file_t
|
||||||
|
*/
|
||||||
|
llfs_file_t* llfs_next_file(void** mem, char* filter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the number of files in the filesystem
|
||||||
|
*
|
||||||
|
* @return The number of files in the filesystem
|
||||||
|
*/
|
||||||
|
size_t llfs_file_count(void);
|
||||||
|
|
||||||
#endif // LLFS_H
|
#endif // LLFS_H
|
||||||
|
|||||||
@@ -2,34 +2,82 @@
|
|||||||
* @file llfs.c
|
* @file llfs.c
|
||||||
* @brief Linked List Filesystem implementation (llfs)
|
* @brief Linked List Filesystem implementation (llfs)
|
||||||
* @author Lorenz C.
|
* @author Lorenz C.
|
||||||
* @todo Implement file extension filter
|
* @version 0.1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#define LOGGER_LEVEL_WARN
|
#define LOGGER_LEVEL_WARN
|
||||||
#include "llfs.h"
|
#include "llfs.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The maximum number of files that can be opened concurrently using the POSIX API
|
||||||
|
*/
|
||||||
|
#define POSIX_MAX_FILES 10
|
||||||
|
|
||||||
extern struct llfs_data_file* llfs_root;
|
extern struct llfs_data_file* llfs_root;
|
||||||
const char* TAG = "llfs";
|
const char* TAG = "llfs";
|
||||||
|
|
||||||
// TODO: Implement file extension filter
|
|
||||||
size_t llfs_file_list(llfs_file_t* file_list, size_t max_files, char* filter) {
|
|
||||||
size_t file_count = 0;
|
size_t file_count = 0;
|
||||||
const struct llfs_data_file* file = llfs_root;
|
FILE* file_table[POSIX_MAX_FILES];
|
||||||
|
|
||||||
LOG_DEBUG(TAG, "Getting file list with filter: %s", filter);
|
static int new_file_table_entry(void);
|
||||||
|
static int free_file_table_entry(int file_id);
|
||||||
|
static FILE* file_id_to_stream(int file_id);
|
||||||
|
static uint8_t file_ext_cmp(const char* filename, const char* ext);
|
||||||
|
|
||||||
|
int8_t llfs_init(void) {
|
||||||
|
LOG_DEBUG(TAG, "Initializing llfs");
|
||||||
|
|
||||||
|
// Initialize the file table
|
||||||
|
for (int i = 0; i < POSIX_MAX_FILES; i++) {
|
||||||
|
file_table[i] = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add stdin, stdout and stderr to the file table
|
||||||
|
file_table[STDIN_FILENO] = stdin;
|
||||||
|
file_table[STDOUT_FILENO] = stdout;
|
||||||
|
file_table[STDERR_FILENO] = stderr;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t llfs_file_list(llfs_file_t* file_list, size_t max_files, char* filter) {
|
||||||
|
size_t count = 0; // Number of files found
|
||||||
|
const struct llfs_data_file* file = llfs_root; // Pointer to the current file
|
||||||
|
|
||||||
|
if (filter != NULL) {
|
||||||
|
LOG_DEBUG(TAG, "Filtering files with filter: %s", filter);
|
||||||
|
|
||||||
|
if (filter[0] == '*') {
|
||||||
|
filter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over all files in the filesystem
|
||||||
while (file != NULL && file_count < max_files) {
|
while (file != NULL && file_count < max_files) {
|
||||||
file_list[file_count].data = file->data;
|
// Filter out files with a filename that does not match the filter
|
||||||
file_list[file_count].name = file->name;
|
if (filter != NULL) {
|
||||||
file_list[file_count].len = file->len;
|
if (!file_ext_cmp(file->name, filter)) {
|
||||||
file_count++;
|
file = file->next;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the file to the file list
|
||||||
|
file_list[count].data = file->data;
|
||||||
|
file_list[count].name = file->name;
|
||||||
|
file_list[count].len = file->len;
|
||||||
|
|
||||||
|
// Move to the next file
|
||||||
|
count++;
|
||||||
file = file->next;
|
file = file->next;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_DEBUG(TAG, "Files found: %d", file_count);
|
LOG_DEBUG(TAG, "Files found: %d", count);
|
||||||
return file_count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
llfs_file_t* llfs_file_open(const char* name) {
|
llfs_file_t* llfs_file_open(const char* name) {
|
||||||
@@ -48,3 +96,341 @@ llfs_file_t* llfs_file_open(const char* name) {
|
|||||||
LOG_DEBUG(TAG, "File not found: %s", name);
|
LOG_DEBUG(TAG, "File not found: %s", name);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
llfs_file_t* llfs_next_file(void** mem, char* filter) {
|
||||||
|
struct llfs_data_file* prev_file = (struct llfs_data_file*)*mem;
|
||||||
|
uint8_t filter_ok = 0;
|
||||||
|
|
||||||
|
if (prev_file == NULL) {
|
||||||
|
prev_file = llfs_root;
|
||||||
|
} else {
|
||||||
|
prev_file = (struct llfs_data_file*)prev_file->next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a filter is specified, only return files that match the filter
|
||||||
|
if (filter != NULL) {
|
||||||
|
LOG_DEBUG(TAG, "Filtering files with filter: %s", filter);
|
||||||
|
|
||||||
|
// Remove the '*' from the filter
|
||||||
|
if (filter[0] == '*') {
|
||||||
|
filter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (prev_file != NULL) {
|
||||||
|
if (file_ext_cmp(prev_file->name, filter)) {
|
||||||
|
filter_ok = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
prev_file = (struct llfs_data_file*)prev_file->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*mem = (void*)prev_file;
|
||||||
|
return (llfs_file_t*)prev_file;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t llfs_file_count(void) {
|
||||||
|
if (file_count == 0) {
|
||||||
|
const struct llfs_data_file* file = llfs_root;
|
||||||
|
while (file != NULL) {
|
||||||
|
file_count++;
|
||||||
|
file = file->next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return file_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Newlib open implementation
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
* @param flags
|
||||||
|
* @param mode
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int _open(char* path, int flags, int mode) {
|
||||||
|
int file_id;
|
||||||
|
FILE* stream;
|
||||||
|
errno = 0;
|
||||||
|
llfs_file_t* llfs_file;
|
||||||
|
|
||||||
|
// Add a new entry to the file table
|
||||||
|
file_id = new_file_table_entry();
|
||||||
|
if (file_id < 0) {
|
||||||
|
LOG_WARN(TAG, "Failed to add new file table entry. %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stream associated with the file id
|
||||||
|
stream = file_id_to_stream(file_id);
|
||||||
|
if (stream == NULL) {
|
||||||
|
LOG_WARN(TAG, "Failed to get file table entry. %s", strerror(errno));
|
||||||
|
free_file_table_entry(file_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file from the llfs filesystem
|
||||||
|
llfs_file = llfs_file_open(path);
|
||||||
|
if (llfs_file == NULL) {
|
||||||
|
LOG_DEBUG(TAG, "Failed to open file: %s", path);
|
||||||
|
free_file_table_entry(file_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the stream
|
||||||
|
stream->_cookie = (void*)llfs_file;
|
||||||
|
stream->_offset = 0;
|
||||||
|
stream->_flags = __SRD;
|
||||||
|
|
||||||
|
return file_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Newlib close implementation
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int _close(int file_id) {
|
||||||
|
FILE* stream;
|
||||||
|
|
||||||
|
// Get the stream associated with the file id
|
||||||
|
stream = file_id_to_stream(file_id);
|
||||||
|
if (stream == NULL) {
|
||||||
|
LOG_WARN(TAG, "Failed to get file table entry. %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the entry from the file table
|
||||||
|
if (free_file_table_entry(file_id) < 0) {
|
||||||
|
LOG_WARN(TAG, "Failed to remove file table entry. %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Newlib read implementation
|
||||||
|
*
|
||||||
|
* @param file_id
|
||||||
|
* @param ptr
|
||||||
|
* @param len
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int _read(int file_id, char* ptr, int len) {
|
||||||
|
FILE* stream;
|
||||||
|
llfs_file_t* llfs_file;
|
||||||
|
size_t bytes_read;
|
||||||
|
|
||||||
|
// Read from stdin, stdout and stderr is not supported
|
||||||
|
if (file_id == STDIN_FILENO || file_id == STDOUT_FILENO || file_id == STDERR_FILENO) {
|
||||||
|
LOG_DEBUG(TAG, "Trying to read from stdin, stdout or stderr: %d", file_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stream associated with the file id
|
||||||
|
stream = file_id_to_stream(file_id);
|
||||||
|
if (stream == NULL) {
|
||||||
|
LOG_WARN(TAG, "Failed to get file table entry. %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file from the llfs filesystem associated with the stream
|
||||||
|
llfs_file = (llfs_file_t*)stream->_cookie;
|
||||||
|
if (llfs_file == NULL) {
|
||||||
|
LOG_WARN(TAG, "Failed to get llfs file associated with stream: %d", file_id);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate the number of bytes to read (limited by the file size)
|
||||||
|
bytes_read = llfs_file->len - stream->_offset;
|
||||||
|
if (bytes_read > len) {
|
||||||
|
bytes_read = len;
|
||||||
|
} else if (bytes_read == 0) { // End of file
|
||||||
|
stream->_flags |= __SEOF;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the data over to the dst buffer
|
||||||
|
memcpy(ptr, llfs_file->data + stream->_offset, bytes_read);
|
||||||
|
|
||||||
|
stream->_offset += (off_t)bytes_read;
|
||||||
|
return (int)bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Newlib isatty implementation
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @return 1 if the file is stdin, stdout or stderr, 0 otherwise
|
||||||
|
*/
|
||||||
|
int isatty(int file) {
|
||||||
|
if (file == STDIN_FILENO || file == STDOUT_FILENO || file == STDERR_FILENO) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Newlib lseek implementation
|
||||||
|
*
|
||||||
|
* @param file
|
||||||
|
* @param ptr
|
||||||
|
* @param dir
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int _lseek(int file, int ptr, int dir) {
|
||||||
|
FILE* stream;
|
||||||
|
|
||||||
|
if (file == STDIN_FILENO || file == STDOUT_FILENO || file == STDERR_FILENO) {
|
||||||
|
LOG_DEBUG(TAG, "Trying to seek stdin, stdout or stderr: %d", file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
stream = file_id_to_stream(file);
|
||||||
|
if (stream == NULL) {
|
||||||
|
LOG_WARN(TAG, "Failed to get file table entry. %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dir) {
|
||||||
|
case SEEK_SET:
|
||||||
|
stream->_offset = ptr;
|
||||||
|
break;
|
||||||
|
case SEEK_CUR:
|
||||||
|
stream->_offset += ptr;
|
||||||
|
break;
|
||||||
|
case SEEK_END:
|
||||||
|
stream->_offset = (off_t)((llfs_file_t*)stream->_cookie)->len + ptr;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
LOG_WARN(TAG, "Invalid seek direction: %d", dir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Newlib fstat implementation
|
||||||
|
*
|
||||||
|
* @param[in] file
|
||||||
|
* @param[out] st
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
int _fstat(int file, struct stat* st) {
|
||||||
|
FILE* stream;
|
||||||
|
llfs_file_t *llfs_file;
|
||||||
|
|
||||||
|
// Check if the file is stdin, stdout or stderr
|
||||||
|
if (file == STDIN_FILENO || file == STDOUT_FILENO || file == STDERR_FILENO) {
|
||||||
|
st->st_mode = S_IFCHR; // Character special file
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the stream associated with the file id
|
||||||
|
stream = file_id_to_stream(file);
|
||||||
|
if (stream == NULL) {
|
||||||
|
LOG_WARN(TAG, "Failed to get file table entry. %s", strerror(errno));
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the file from the llfs filesystem associated with the stream
|
||||||
|
llfs_file = (llfs_file_t*)stream->_cookie;
|
||||||
|
if (llfs_file == NULL) {
|
||||||
|
LOG_WARN(TAG, "Failed to get llfs file associated with stream: %d", file);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
st->st_mode = S_IFREG; // Regular file
|
||||||
|
st->st_size = (off_t)llfs_file->len;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a new entry in the file table
|
||||||
|
*
|
||||||
|
* @return The file id or -1 if an error occurred. See errno for more information.
|
||||||
|
*/
|
||||||
|
static int new_file_table_entry(void) {
|
||||||
|
FILE* stream;
|
||||||
|
|
||||||
|
// Try to find an empty entry in the file table
|
||||||
|
for (int i = 0; i < POSIX_MAX_FILES; i++) {
|
||||||
|
if (file_table[i] == NULL) {
|
||||||
|
stream = (FILE*)malloc(sizeof(FILE));
|
||||||
|
if (stream == NULL) {
|
||||||
|
errno = ENOMEM; // Out of memory
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
file_table[i] = stream;
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No empty entry found
|
||||||
|
errno = ENFILE; // Too many open files
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Remove an entry from the file table
|
||||||
|
*
|
||||||
|
* @param[in] file_id The file id to remove
|
||||||
|
* @return 0 if successful, -1 if an error occurred. See errno for more information.
|
||||||
|
*/
|
||||||
|
static int free_file_table_entry(int file_id) {
|
||||||
|
// Check if the file id is valid
|
||||||
|
if (file_id < 0 || file_id >= POSIX_MAX_FILES) {
|
||||||
|
errno = EBADF; // Bad file number
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the entry from the file table
|
||||||
|
free(file_table[file_id]);
|
||||||
|
file_table[file_id] = NULL;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the stream associated with a file id
|
||||||
|
*
|
||||||
|
* @param[in] file_id The file id to get the stream for
|
||||||
|
* @return The stream or NULL if an error occurred. See errno for more information.
|
||||||
|
*/
|
||||||
|
static FILE* file_id_to_stream(int file_id) {
|
||||||
|
if (file_id < 0 || file_id >= POSIX_MAX_FILES) {
|
||||||
|
errno = EBADF; // Bad file number
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return file_table[file_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a filename ends with a given extension
|
||||||
|
*
|
||||||
|
* @param[in] filename The filename to check
|
||||||
|
* @param[in] ext The extension to check for
|
||||||
|
* @return 1 if the filename ends with the extension, 0 otherwise
|
||||||
|
*/
|
||||||
|
static uint8_t file_ext_cmp(const char* const filename, const char* const ext) {
|
||||||
|
uint8_t ext_len = strlen(ext);
|
||||||
|
uint8_t filename_len = strlen(filename);
|
||||||
|
|
||||||
|
if (filename_len < ext_len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare backwards
|
||||||
|
for (uint8_t i = 0; i < ext_len; i++) {
|
||||||
|
if (filename[filename_len - i] != ext[ext_len - i]) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|||||||
@@ -24,8 +24,10 @@
|
|||||||
/* USER CODE BEGIN Includes */
|
/* USER CODE BEGIN Includes */
|
||||||
#define LOGGER_LEVEL_ALL
|
#define LOGGER_LEVEL_ALL
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
|
#include "llfs.h"
|
||||||
|
#include "../../Drivers/BSP/STM32746G-Discovery/stm32746g_discovery_lcd.h"
|
||||||
#include "lcd_api.h"
|
#include "lcd_api.h"
|
||||||
|
|
||||||
/* USER CODE END Includes */
|
/* USER CODE END Includes */
|
||||||
|
|
||||||
/* Private typedef -----------------------------------------------------------*/
|
/* Private typedef -----------------------------------------------------------*/
|
||||||
@@ -112,6 +114,153 @@ int main(void)
|
|||||||
MX_QUADSPI_Init();
|
MX_QUADSPI_Init();
|
||||||
/* USER CODE BEGIN 2 */
|
/* USER CODE BEGIN 2 */
|
||||||
lcd_init(true);
|
lcd_init(true);
|
||||||
|
|
||||||
|
llfs_init();
|
||||||
|
|
||||||
|
|
||||||
|
FILE *f = fopen("test.txt", "rw");
|
||||||
|
if (f == NULL) {
|
||||||
|
LOG_INFO(TAG, "File not found test.txt");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
LOG_INFO(TAG, "File found test.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test POSIX file operations
|
||||||
|
// fgetc
|
||||||
|
int c;
|
||||||
|
printf("Printing file:\n");
|
||||||
|
while ((c = fgetc(f)) != EOF) {
|
||||||
|
printf("%c", c);
|
||||||
|
}
|
||||||
|
LOG_INFO(TAG, "File printed");
|
||||||
|
|
||||||
|
// fseek
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
LOG_INFO(TAG, "File seeked to start");
|
||||||
|
|
||||||
|
// ftell
|
||||||
|
long pos = ftell(f);
|
||||||
|
LOG_INFO(TAG, "File position: %d", pos);
|
||||||
|
|
||||||
|
// fread
|
||||||
|
char buf[100];
|
||||||
|
size_t bytes_read = fread(buf, 1, 100, f);
|
||||||
|
LOG_INFO(TAG, "Read %d bytes from file", bytes_read);
|
||||||
|
printf("Read from file:\n");
|
||||||
|
for (int i = 0; i < bytes_read; i++) {
|
||||||
|
printf("%c", buf[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rewind the file
|
||||||
|
LOG_INFO(TAG, "Before File rewinded, pos: %d", ftell(f));
|
||||||
|
rewind(f);
|
||||||
|
LOG_INFO(TAG, "File rewinded, pos: %d", ftell(f));
|
||||||
|
|
||||||
|
// Get the file size fstat
|
||||||
|
struct stat st;
|
||||||
|
fstat(fileno(f), &st);
|
||||||
|
LOG_INFO(TAG, "File size: %d", st.st_size);
|
||||||
|
|
||||||
|
|
||||||
|
// Get a list of all files with the .bmp extension
|
||||||
|
llfs_file_t file_list[10];
|
||||||
|
size_t num_files = llfs_file_list(file_list, 10, "*.bmp");
|
||||||
|
LOG_INFO(TAG, "Found %d files with the .bmp extension", num_files);
|
||||||
|
for (int i = 0; i < num_files; i++) {
|
||||||
|
LOG_INFO(TAG, "File %d: %s", i, file_list[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get a list of files with .txt or .html
|
||||||
|
num_files = llfs_file_list(file_list, 10, "*.txt");
|
||||||
|
LOG_INFO(TAG, "Found %d files with the .txt or .html extension", num_files);
|
||||||
|
for (int i = 0; i < num_files; i++) {
|
||||||
|
LOG_INFO(TAG, "File %d: %s", i, file_list[i].name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all files using the iterator
|
||||||
|
LOG_INFO(TAG, "Looping over all files, using the iterator");
|
||||||
|
void *mem = NULL;
|
||||||
|
llfs_file_t *file;
|
||||||
|
while ((file = llfs_next_file(&mem, NULL)) != NULL) {
|
||||||
|
LOG_INFO(TAG, "File: %s", file->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop over all files with the .bmp extension using the iterator
|
||||||
|
LOG_INFO(TAG, "Looping over all files with the .bmp extension, using the iterator");
|
||||||
|
mem = NULL;
|
||||||
|
while ((file = llfs_next_file(&mem, "*.bmp")) != NULL) {
|
||||||
|
LOG_INFO(TAG, "File: %s", file->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the number of files in the filesystem
|
||||||
|
size_t num_files_in_fs = llfs_file_count();
|
||||||
|
LOG_INFO(TAG, "Number of files in the filesystem: %d", num_files_in_fs);
|
||||||
|
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
// Try opening multiple files
|
||||||
|
LOG_INFO(TAG, "Opening an closing multiple files");
|
||||||
|
FILE * f1 = fopen("test.txt", "rw");
|
||||||
|
if (f1 == NULL) {
|
||||||
|
LOG_INFO(TAG, "File not found f1");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
LOG_INFO(TAG, "File found f1");
|
||||||
|
}
|
||||||
|
// Get the fileno
|
||||||
|
int fd = fileno(f1);
|
||||||
|
LOG_INFO(TAG, "File descriptor f1: %d", fd);
|
||||||
|
|
||||||
|
FILE * f2 = fopen("test.txt", "rw");
|
||||||
|
if (f2 == NULL) {
|
||||||
|
LOG_INFO(TAG, "File not found f2");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
LOG_INFO(TAG, "File found f2");
|
||||||
|
}
|
||||||
|
// Get the fileno
|
||||||
|
fd = fileno(f2);
|
||||||
|
LOG_INFO(TAG, "File descriptorf2: %d", fd);
|
||||||
|
|
||||||
|
LOG_INFO(TAG, "Closing f1");
|
||||||
|
fclose(f1);
|
||||||
|
|
||||||
|
FILE * f3 = fopen("test.txt", "rw");
|
||||||
|
if (f3 == NULL) {
|
||||||
|
LOG_INFO(TAG, "File not found f3");
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
LOG_INFO(TAG, "File found f3");
|
||||||
|
}
|
||||||
|
// Get the fileno
|
||||||
|
fd = fileno(f3);
|
||||||
|
LOG_INFO(TAG, "File descriptor f3: %d", fd);
|
||||||
|
|
||||||
|
LOG_INFO(TAG, "Closing f2");
|
||||||
|
fclose(f2);
|
||||||
|
|
||||||
|
LOG_INFO(TAG, "Closing f3");
|
||||||
|
fclose(f3);
|
||||||
|
|
||||||
|
// Try opening a file multiple times, until it fails
|
||||||
|
int i = 0;
|
||||||
|
LOG_INFO(TAG, "Opening a file multiple times, until it fails");
|
||||||
|
while (1) {
|
||||||
|
f = fopen("test.txt", "rw");
|
||||||
|
LOG_INFO(TAG, "File descriptor: %d", fileno(f));
|
||||||
|
if (f == NULL) {
|
||||||
|
LOG_INFO(TAG, "File not found test.txt");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
LOG_INFO(TAG, "File found test.txt");
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
LOG_INFO(TAG, "File opened %d times", i);
|
||||||
|
|
||||||
|
|
||||||
/* USER CODE END 2 */
|
/* USER CODE END 2 */
|
||||||
/* Infinite loop */
|
/* Infinite loop */
|
||||||
/* USER CODE BEGIN WHILE */
|
/* USER CODE BEGIN WHILE */
|
||||||
|
|||||||
@@ -89,14 +89,14 @@ __attribute__((weak)) int _write(int file, char *ptr, int len)
|
|||||||
return len;
|
return len;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _close(int file)
|
__attribute__((weak)) int _close(int file)
|
||||||
{
|
{
|
||||||
(void)file;
|
(void)file;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int _fstat(int file, struct stat *st)
|
__attribute__((weak)) int _fstat(int file, struct stat *st)
|
||||||
{
|
{
|
||||||
(void)file;
|
(void)file;
|
||||||
st->st_mode = S_IFCHR;
|
st->st_mode = S_IFCHR;
|
||||||
@@ -109,7 +109,7 @@ int _isatty(int file)
|
|||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _lseek(int file, int ptr, int dir)
|
__attribute__((weak)) int _lseek(int file, int ptr, int dir)
|
||||||
{
|
{
|
||||||
(void)file;
|
(void)file;
|
||||||
(void)ptr;
|
(void)ptr;
|
||||||
@@ -117,7 +117,7 @@ int _lseek(int file, int ptr, int dir)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _open(char *path, int flags, ...)
|
__attribute__((weak)) int _open(char *path, int flags, ...)
|
||||||
{
|
{
|
||||||
(void)path;
|
(void)path;
|
||||||
(void)flags;
|
(void)flags;
|
||||||
@@ -145,7 +145,7 @@ int _times(struct tms *buf)
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int _stat(char *file, struct stat *st)
|
__attribute__((weak)) int _stat(char *file, struct stat *st)
|
||||||
{
|
{
|
||||||
(void)file;
|
(void)file;
|
||||||
st->st_mode = S_IFCHR;
|
st->st_mode = S_IFCHR;
|
||||||
|
|||||||
Reference in New Issue
Block a user