Extended the llfs file system api

Implemented file extension filters
Implemented newlib stubs to be able to use the POSIX file API
This commit is contained in:
L-diy
2023-11-11 00:36:18 +01:00
parent 419fbc5663
commit af46235eba
4 changed files with 939 additions and 526 deletions

View File

@@ -2,34 +2,82 @@
* @file llfs.c
* @brief Linked List Filesystem implementation (llfs)
* @author Lorenz C.
* @todo Implement file extension filter
* @version 0.1.1
*/
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#define LOGGER_LEVEL_WARN
#include "llfs.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;
const char* TAG = "llfs";
size_t file_count = 0;
FILE* file_table[POSIX_MAX_FILES];
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;
}
// 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;
const struct llfs_data_file* file = llfs_root;
size_t count = 0; // Number of files found
const struct llfs_data_file* file = llfs_root; // Pointer to the current file
LOG_DEBUG(TAG, "Getting file list with filter: %s", filter);
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) {
file_list[file_count].data = file->data;
file_list[file_count].name = file->name;
file_list[file_count].len = file->len;
file_count++;
// Filter out files with a filename that does not match the filter
if (filter != NULL) {
if (!file_ext_cmp(file->name, filter)) {
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;
}
LOG_DEBUG(TAG, "Files found: %d", file_count);
return file_count;
LOG_DEBUG(TAG, "Files found: %d", count);
return count;
}
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);
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;
}