Commit e5b3e196 authored by swym's avatar swym Committed by Rahix
Browse files

fix(fatfs): Refactor fatfs implementation

parent 0d8be9c3
...@@ -554,26 +554,47 @@ API(API_FILE_TELL, int epic_file_tell(int fd)); ...@@ -554,26 +554,47 @@ API(API_FILE_TELL, int epic_file_tell(int fd));
/** */ /** */
enum epic_stat_type { enum epic_stat_type {
/** */ /** basically NOENT
* although epic_file_stat returns an error for 'none', the type will still be set
* to none additinally.
* This is also used internally to track open FS objects, where we use ``EPICSTAT_NONE``
* to mark free objects.
*/
EPICSTAT_NONE,
/** normal file */
EPICSTAT_FILE, EPICSTAT_FILE,
/** */ /** directory */
EPICSTAT_DIR, EPICSTAT_DIR,
}; };
/** */ /** */
typedef struct epic_stat_t { typedef struct epic_stat_t {
/** */ /** type: file, directory or none */
enum epic_stat_type type; enum epic_stat_type type;
/* note about padding & placement of uint32_t size:
* to accomodate for future expansion, we want padding at the end of
* this struct. Since sizeof(enum epic_stat_type) can not be
* assumed to be have a certain size,
* we're placing uint32_t size here so we can be sure it will be at
* offset 4, and therefore the layout of the other fields is predictable.
*/
/** size in bytes */
uint32_t size;
/** the FAT volume (will be needed later once we distinguish
* between system and user volume)*/
uint8_t volume;
uint8_t _reserved[9];
} epic_stat_t; } epic_stat_t;
#ifndef __cplusplus
#if defined(__GNUC__) && ((__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6))
_Static_assert(sizeof(epic_stat_t) == 20, "");
#endif
#endif
/** /**
* stat path * stat path
* *
* This does not follow posix convention, but rather takes
* a path as parameter. This aligns more with libff's API and
* also this has been implemented for python import support, which
* passes the filename as well.
*
* :param const char* filename: path to stat * :param const char* filename: path to stat
* :param epic_stat_t* stat: pointer to result * :param epic_stat_t* stat: pointer to result
* *
......
...@@ -26,61 +26,21 @@ static const TCHAR *rcstrings = ...@@ -26,61 +26,21 @@ static const TCHAR *rcstrings =
_T("NOT_ENABLED\0NO_FILESYSTEM\0MKFS_ABORTED\0TIMEOUT\0LOCKED\0") _T("NOT_ENABLED\0NO_FILESYSTEM\0MKFS_ABORTED\0TIMEOUT\0LOCKED\0")
_T("NOT_ENOUGH_CORE\0TOO_MANY_OPEN_FILES\0INVALID_PARAMETER\0"); _T("NOT_ENOUGH_CORE\0TOO_MANY_OPEN_FILES\0INVALID_PARAMETER\0");
// this table converts from FRESULT to POSIX errno
const int fresult_to_errno_table[20] = {
[FR_OK] = 0,
[FR_DISK_ERR] = EIO,
[FR_INT_ERR] = EIO,
[FR_NOT_READY] = EBUSY,
[FR_NO_FILE] = ENOENT,
[FR_NO_PATH] = ENOENT,
[FR_INVALID_NAME] = EINVAL,
[FR_DENIED] = EACCES,
[FR_EXIST] = EEXIST,
[FR_INVALID_OBJECT] = EINVAL,
[FR_WRITE_PROTECTED] = EROFS,
[FR_INVALID_DRIVE] = ENODEV,
[FR_NOT_ENABLED] = ENODEV,
[FR_NO_FILESYSTEM] = ENODEV,
[FR_MKFS_ABORTED] = EIO,
[FR_TIMEOUT] = EIO,
[FR_LOCKED] = EIO,
[FR_NOT_ENOUGH_CORE] = ENOMEM,
[FR_TOO_MANY_OPEN_FILES] = EMFILE,
[FR_INVALID_PARAMETER] = EINVAL,
};
enum FatObjectType { FO_Nil, FO_File, FO_Dir };
struct FatObject {
enum FatObjectType type;
union {
FIL file;
DIR dir;
};
};
static bool mount(void); static bool mount(void);
static int
get_fat_object(int i, enum FatObjectType expected, struct FatObject **res);
DIR dir; DIR dir;
FATFS FatFs; FATFS FatFs;
static struct FatObject s_openedObjects[EPIC_FAT_MAX_OPENED];
#if (EPIC_FAT_STATIC_SEMAPHORE == 1) #if (EPIC_FAT_STATIC_SEMAPHORE == 1)
StaticSemaphore_t xSemaphoreBuffer; StaticSemaphore_t xSemaphoreBuffer;
#endif #endif
static volatile struct { static volatile bool s_fatfs_initiaized = false;
bool initiaized;
} s_state = {
.initiaized = false,
};
void fatfs_init() void fatfs_init()
{ {
if (mount()) { if (mount()) {
s_state.initiaized = true; s_fatfs_initiaized = true;
printf("FatFs mounted\n"); printf("FatFs mounted\n");
} }
} }
...@@ -188,195 +148,3 @@ void ff_rel_grant(FF_SYNC_t sobj) ...@@ -188,195 +148,3 @@ void ff_rel_grant(FF_SYNC_t sobj)
/* FreeRTOS */ /* FreeRTOS */
xSemaphoreGive(sobj); xSemaphoreGive(sobj);
} }
int get_fat_object(int i, enum FatObjectType expected, struct FatObject **res)
{
if (i < 0 || i >= EPIC_FAT_MAX_OPENED) {
*res = NULL;
return EBADF;
}
if (s_openedObjects[i].type != expected) {
*res = NULL;
return EBADF;
}
*res = &s_openedObjects[i];
return 0;
}
int epic_file_open(const char *filename, const char *modeString)
{
struct FatObject *o = NULL;
const char *mode_s = modeString;
int i;
int mode = 0;
//find free object to use
for (i = 0; i < EPIC_FAT_MAX_OPENED; ++i) {
if (s_openedObjects[i].type == FO_Nil) {
break;
}
}
if (i == EPIC_FAT_MAX_OPENED) {
return -fresult_to_errno_table[FR_TOO_MANY_OPEN_FILES];
}
o = &s_openedObjects[i];
while (*mode_s) {
switch (*mode_s++) {
case 'r':
mode |= FA_READ;
break;
case 'w':
mode |= FA_WRITE | FA_CREATE_ALWAYS;
break;
case 'x':
mode |= FA_WRITE | FA_CREATE_NEW;
break;
case 'a':
mode |= FA_WRITE | FA_OPEN_ALWAYS;
break;
case '+':
mode |= FA_READ | FA_WRITE;
break;
}
}
int res = f_open(&o->file, filename, mode);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
o->type = FO_File;
// for 'a' mode, we must begin at the end of the file
if ((mode & FA_OPEN_ALWAYS) != 0) {
f_lseek(&o->file, f_size(&o->file));
}
return i;
}
int epic_file_close(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_close(&o->file);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
o->type = FO_Nil;
return 0;
}
int epic_file_read(int fd, void *buf, size_t nbytes)
{
unsigned int nread = 0;
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_read(&o->file, buf, nbytes, &nread);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return nread;
}
int epic_file_write(int fd, const void *buf, size_t nbytes)
{
unsigned int nwritten = 0;
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_write(&o->file, buf, nbytes, &nwritten);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return nwritten;
}
int epic_file_flush(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
res = f_sync(&o->file);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return 0;
}
int epic_file_seek(int fd, long offset, int whence)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -res;
}
switch (whence) {
case SEEK_SET:
res = f_lseek(&o->file, offset);
return -fresult_to_errno_table[res];
case SEEK_CUR:
res = f_lseek(&o->file, f_tell(&o->file) + offset);
return -fresult_to_errno_table[res];
case SEEK_END:
res = f_lseek(&o->file, f_size(&o->file) + offset);
return -fresult_to_errno_table[res];
default:
return -EINVAL;
}
return 0;
}
int epic_file_tell(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, FO_File, &o);
if (res) {
return -1;
}
return f_tell(&o->file);
}
int epic_file_stat(const char *filename, epic_stat_t *stat)
{
int res;
FILINFO finfo;
res = f_stat(filename, &finfo);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
if (finfo.fattrib & AM_DIR) {
stat->type = EPICSTAT_DIR;
} else {
stat->type = EPICSTAT_FILE;
}
return 0;
}
#include <errno.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdio.h>
#include <ff.h>
#include "modules.h"
#include "epicardium.h"
#define EPIC_FAT_FD_GENERATION_BITS (31 - (EPIC_FAT_FD_INDEX_BITS))
#define EPIC_FAT_MAX_OPENED (1 << (EPIC_FAT_FD_INDEX_BITS))
#define EPIC_FAT_FD_INDEX_MASK (uint32_t)((1u << EPIC_FAT_FD_INDEX_BITS) - 1)
#define EPIC_FAT_FD_INDEX(fd) ((uint32_t)(fd)&EPIC_FAT_FD_INDEX_MASK)
#define EPIC_FAT_FD_GENERATION(fd) ((uint32_t)(fd) >> EPIC_FAT_FD_INDEX_BITS)
#define EPIC_FAT_FD_MAX_GENERATION \
(uint32_t)((1u << EPIC_FAT_FD_GENERATION_BITS) - 1)
#define EPIC_FAT_FD(idx, gen) \
(int)(((uint32_t)(gen) << EPIC_FAT_FD_INDEX_BITS) | \
((uint32_t)(idx)&EPIC_FAT_FD_INDEX_MASK))
// this table converts from FRESULT to POSIX errno
const int fresult_to_errno_table[20] = {
[FR_OK] = 0,
[FR_DISK_ERR] = EIO,
[FR_INT_ERR] = EIO,
[FR_NOT_READY] = EBUSY,
[FR_NO_FILE] = ENOENT,
[FR_NO_PATH] = ENOENT,
[FR_INVALID_NAME] = EINVAL,
[FR_DENIED] = EACCES,
[FR_EXIST] = EEXIST,
[FR_INVALID_OBJECT] = EINVAL,
[FR_WRITE_PROTECTED] = EROFS,
[FR_INVALID_DRIVE] = ENODEV,
[FR_NOT_ENABLED] = ENODEV,
[FR_NO_FILESYSTEM] = ENODEV,
[FR_MKFS_ABORTED] = EIO,
[FR_TIMEOUT] = EIO,
[FR_LOCKED] = EIO,
[FR_NOT_ENOUGH_CORE] = ENOMEM,
[FR_TOO_MANY_OPEN_FILES] = EMFILE,
[FR_INVALID_PARAMETER] = EINVAL,
};
struct FatObject {
uint32_t generation;
enum epic_stat_type type;
union {
FIL file;
DIR dir;
};
};
static int
get_fat_object(int i, enum epic_stat_type expected, struct FatObject **res);
static struct FatObject s_openedObjects[EPIC_FAT_MAX_OPENED];
static uint32_t s_fatfs_generationCount = 1;
int get_fat_object(int fd, enum epic_stat_type expected, struct FatObject **res)
{
uint32_t index = EPIC_FAT_FD_INDEX(fd);
uint32_t generation = EPIC_FAT_FD_GENERATION(fd);
if (index >= EPIC_FAT_MAX_OPENED) {
*res = NULL;
return EBADF;
}
if (generation >= EPIC_FAT_FD_MAX_GENERATION) {
*res = NULL;
return EBADF;
}
if (s_openedObjects[index].type != expected) {
*res = NULL;
return EBADF;
}
if (s_openedObjects[index].generation != generation) {
*res = NULL;
return EBADF;
}
*res = &s_openedObjects[index];
return 0;
}
/* here we're trying to mirror glibc's behaviour:
* any combination of rwax parses but only the first of those flags wins:
* - rw, ra, rr all open read-only
* a `+` at any position but the first turns into read-write
* any other character at any position yields EINVAL
*/
static inline bool parse_mode(const char *mstring, int *mode)
{
switch (mstring[0]) {
case 'r':
*mode = FA_READ;
break;
case 'w':
*mode = FA_CREATE_ALWAYS | FA_WRITE;
break;
case 'x':
//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
*mode = FA_WRITE | FA_CREATE_NEW;
break;
case 'a':
//in constrast to FA_CREATE_ALWAYS, FA_CREATE_NEW fails for existing files
*mode = FA_WRITE | FA_OPEN_APPEND;
break;
default:
return false;
}
while (*mstring) {
switch (*mstring++) {
case '+': //turns any of r,w,x into read&write
*mode |= FA_READ | FA_WRITE;
break;
case 'r': //fallthrough intentional
case 'w': //fallthrough intentional
case 'a': //fallthrough intentional
case 'x': //fallthrough intentional
break;
default:
return false;
}
}
return true;
}
int epic_file_open(const char *filename, const char *modeString)
{
struct FatObject *o = NULL;
uint32_t index, generation;
int mode = 0;
int res;
//find free object to use
for (index = 0; index < EPIC_FAT_MAX_OPENED; ++index) {
if (s_openedObjects[index].type == EPICSTAT_NONE) {
break;
}
}
if (index == EPIC_FAT_MAX_OPENED) {
return -fresult_to_errno_table[FR_TOO_MANY_OPEN_FILES];
}
generation = s_fatfs_generationCount++;
if (generation == EPIC_FAT_FD_MAX_GENERATION) {
s_fatfs_generationCount = 1;
}
o = &s_openedObjects[index];
if (!parse_mode(modeString, &mode)) {
return -EINVAL;
}
res = f_open(&o->file, filename, mode);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
o->type = EPICSTAT_FILE;
o->generation = generation;
// for 'a' mode, we must begin at the end of the file
if ((mode & FA_OPEN_APPEND) != 0) {
f_lseek(&o->file, f_size(&o->file));
}
return EPIC_FAT_FD(index, generation);
}
int epic_file_close(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, EPICSTAT_FILE, &o);
if (res) {
return -res;
}
res = f_close(&o->file);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
o->type = EPICSTAT_NONE;
o->generation = 0;
return 0;
}
int epic_file_read(int fd, void *buf, size_t nbytes)
{
unsigned int nread = 0;
int res;
struct FatObject *o;
res = get_fat_object(fd, EPICSTAT_FILE, &o);
if (res) {
return -res;
}
res = f_read(&o->file, buf, nbytes, &nread);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return (int)nread;
}
int epic_file_write(int fd, const void *buf, size_t nbytes)
{
unsigned int nwritten = 0;
int res;
struct FatObject *o;
res = get_fat_object(fd, EPICSTAT_FILE, &o);
if (res) {
return -res;
}
res = f_write(&o->file, buf, nbytes, &nwritten);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return (int)nwritten;
}
int epic_file_flush(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, EPICSTAT_FILE, &o);
if (res) {
return -res;
}
res = f_sync(&o->file);
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return 0;
}
int epic_file_seek(int fd, long offset, int whence)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, EPICSTAT_FILE, &o);
if (res) {
return -res;
}
switch (whence) {
case SEEK_SET:
res = f_lseek(&o->file, offset);
break;
case SEEK_CUR:
res = f_lseek(&o->file, f_tell(&o->file) + offset);
break;
case SEEK_END:
res = f_lseek(&o->file, f_size(&o->file) + offset);
break;
default:
return -EINVAL;
}
if (res != FR_OK) {
return -fresult_to_errno_table[res];
}
return 0;
}
int epic_file_tell(int fd)
{
int res;
struct FatObject *o;
res = get_fat_object(fd, EPICSTAT_FILE, &o);
if (res) {
return -res;
}