Last active
June 5, 2019 00:29
-
-
Save TuxSH/c3f7385c92647ac12fcd880cdf4008ca to your computer and use it in GitHub Desktop.
fs_dev.c for fatfs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <errno.h> | |
#include <limits.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <time.h> | |
#include <fcntl.h> | |
#include <sys/iosupport.h> | |
#include <sys/param.h> | |
#include <unistd.h> | |
#include "lib/fatfs/ff.h" | |
#include "fs_dev.h" | |
/* Quite a bit of code comes from https://github.com/switchbrew/libnx/blob/master/nx/source/runtime/devices/fs_dev.c */ | |
static int fsdev_convert_rc(struct _reent *r, FRESULT rc); | |
static const char *fsdev_convert_path(struct _reent *r, char *outpath, const char *inpath); | |
static void fsdev_filinfo_to_st(struct stat *st, const FILINFO *info); | |
static int fsdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode); | |
static int fsdev_close(struct _reent *r, void *fd); | |
static ssize_t fsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len); | |
static ssize_t fsdev_read(struct _reent *r, void *fd, char *ptr, size_t len); | |
static off_t fsdev_seek(struct _reent *r, void *fd, off_t pos, int dir); | |
static int fsdev_fstat(struct _reent *r, void *fd, struct stat *st); | |
static int fsdev_stat(struct _reent *r, const char *file, struct stat *st); | |
static int fsdev_link(struct _reent *r, const char *existing, const char *newLink); | |
static int fsdev_unlink(struct _reent *r, const char *name); | |
static int fsdev_chdir(struct _reent *r, const char *name); | |
static int fsdev_rename(struct _reent *r, const char *oldName, const char *newName); | |
static int fsdev_mkdir(struct _reent *r, const char *path, int mode); | |
static DIR_ITER* fsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path); | |
static int fsdev_dirreset(struct _reent *r, DIR_ITER *dirState); | |
static int fsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); | |
static int fsdev_dirclose(struct _reent *r, DIR_ITER *dirState); | |
static int fsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf); | |
static int fsdev_ftruncate(struct _reent *r, void *fd, off_t len); | |
static int fsdev_fsync(struct _reent *r, void *fd); | |
static int fsdev_chmod(struct _reent *r, const char *path, mode_t mode); | |
static int fsdev_fchmod(struct _reent *r, void *fd, mode_t mode); | |
static int fsdev_rmdir(struct _reent *r, const char *name); | |
static devoptab_t g_fsdev_devoptab = { | |
.structSize = sizeof(FIL), | |
.open_r = fsdev_open, | |
.close_r = fsdev_close, | |
.write_r = fsdev_write, | |
.read_r = fsdev_read, | |
.seek_r = fsdev_seek, | |
.fstat_r = fsdev_fstat, | |
.stat_r = fsdev_stat, | |
.link_r = fsdev_link, | |
.unlink_r = fsdev_unlink, | |
.chdir_r = fsdev_chdir, | |
.rename_r = fsdev_rename, | |
.mkdir_r = fsdev_mkdir, | |
.dirStateSize = sizeof(DIR), | |
.diropen_r = fsdev_diropen, | |
.dirreset_r = fsdev_dirreset, | |
.dirnext_r = fsdev_dirnext, | |
.dirclose_r = fsdev_dirclose, | |
.statvfs_r = fsdev_statvfs, | |
.ftruncate_r = fsdev_ftruncate, | |
.fsync_r = fsdev_fsync, | |
.deviceData = NULL, | |
.chmod_r = fsdev_chmod, | |
.fchmod_r = fsdev_fchmod, | |
.rmdir_r = fsdev_rmdir, | |
}; | |
typedef struct fsdev_fsdevice_t { | |
uint8_t drive_number; /* FatFS drive number, ranging from 0 to 10 */ | |
char name[32+1]; | |
bool setup; | |
devoptab_t devoptab; | |
FATFS fatfs; | |
} fsdev_fsdevice_t; | |
static fsdev_fsdevice_t g_devices[10] = { 0 }; | |
#include "lib/printk.h" | |
/* Restrictions: 10 drives max, unmounting not implem */ | |
int fsdev_mount_device(const char *name, uint8_t drive_number) { | |
if (drive_number >= 10 || name[0] == '\0' || strlen(name) > 32) { | |
errno = EINVAL; | |
return -1; | |
} | |
if (FindDevice(name) != -1) { | |
errno = EEXIST; /* Device already exists */ | |
return -1; | |
} | |
/* Find a free slot and use it */ | |
for (size_t i = 0; i < 10; i++) { | |
if (!g_devices[i].setup) { | |
fsdev_fsdevice_t *device = &g_devices[i]; | |
char mp[] = "0:"; | |
FRESULT rc; | |
mp[0] = '0' + drive_number; | |
printk("Hi %s\n", mp); | |
rc = f_mount(&device->fatfs, mp, 1); | |
if (rc != FR_OK) { | |
return fsdev_convert_rc(NULL, rc); | |
} | |
device->setup = true; | |
strcpy(device->name, name); | |
device->drive_number = drive_number; | |
memcpy(&device->devoptab, &g_fsdev_devoptab, sizeof(devoptab_t)); | |
device->devoptab.name = device->name; | |
device->devoptab.deviceData = device; | |
if (AddDevice(&device->devoptab) == -1) { | |
errno = ENOMEM; | |
return -1; | |
} else { | |
return 0; | |
} | |
} | |
} | |
errno = ENOMEM; | |
return -1; | |
} | |
int fsdev_set_default_device(const char *name) { | |
#if FF_VOLUMES < 2 | |
return 0; | |
#else | |
int ret; | |
char mp[] = "0:"; | |
const devoptab_t *devoptab = GetDeviceOpTab(name); | |
const fsdev_fsdevice_t *device; | |
if (devoptab == NULL) { | |
errno = ENOENT; | |
return -1; | |
} | |
device = (const fsdev_fsdevice_t *)devoptab->deviceData; | |
mp[0] = '0' + device->drive_number; | |
ret = fsdev_convert_rc(NULL, f_chdrive(mp)); | |
if (ret == 0) { | |
setDefaultDevice(FindDevice(name)); | |
} | |
return ret; | |
#endif | |
} | |
int fsdev_unmount_device(const char *name) { | |
int ret; | |
char mp[] = "0:"; | |
const devoptab_t *devoptab = GetDeviceOpTab(name); | |
fsdev_fsdevice_t *device; | |
if (devoptab == NULL) { | |
errno = ENOENT; | |
return -1; | |
} | |
device = (fsdev_fsdevice_t *)devoptab->deviceData; | |
mp[0] = '0' + device->drive_number; | |
ret = fsdev_convert_rc(NULL, f_mount(NULL, mp, 0)); | |
if (ret == 0) { | |
RemoveDevice(name); | |
memset(device, 0, sizeof(fsdev_fsdevice_t)); | |
} | |
return ret; | |
} | |
int fs_unmount_all(void) { | |
int ret; | |
char mp[] = "0:"; | |
for (size_t i = 0; i < 10; i++) { | |
fsdev_fsdevice_t *device = &g_devices[i]; | |
if (!device->setup) { | |
continue; | |
} | |
mp[0] = '0' + device->drive_number; | |
ret = fsdev_convert_rc(NULL, f_mount(NULL, mp, 0)); | |
if (ret == 0) { | |
RemoveDevice(device->name); | |
memset(device, 0, sizeof(fsdev_fsdevice_t)); | |
} else { | |
return ret; | |
} | |
} | |
return 0; | |
} | |
/* Adapted from https://github.com/benemorius/openOBC-devboard/blob/bf0a4a33e22d24e7c299f921d185da27377310e0/lib/fatfs/FatFS.cpp#L173 */ | |
static int fsdev_convert_rc(struct _reent *r, FRESULT rc) { | |
int errornumber; | |
switch(rc) { | |
case FR_OK: /* (0) Succeeded */ | |
errornumber = 0; | |
break; | |
case FR_DISK_ERR: /* (1) A hard error occurred in the low level disk I/O layer */ | |
errornumber = EIO; | |
break; | |
case FR_INT_ERR: /* (2) Assertion failed */ | |
errornumber = EINVAL; | |
break; | |
case FR_NOT_READY: /* (3) The physical drive cannot work */ | |
errornumber = EIO; | |
break; | |
case FR_NO_FILE: /* (4) Could not find the file */ | |
errornumber = ENOENT; | |
break; | |
case FR_NO_PATH: /* (5) Could not find the path */ | |
errornumber = ENOENT; | |
break; | |
case FR_INVALID_NAME: /* (6) The path name format is invalid */ | |
errornumber = EINVAL; | |
break; | |
case FR_DENIED: /* (7) Access denied due to prohibited access or directory full */ | |
errornumber = EACCES; | |
break; | |
case FR_EXIST: /* (8) Access denied due to prohibited access */ | |
errornumber = EEXIST; | |
break; | |
case FR_INVALID_OBJECT: /* (9) The file/directory object is invalid */ | |
errornumber = EFAULT; | |
break; | |
case FR_WRITE_PROTECTED: /* (10) The physical drive is write protected */ | |
errornumber = EROFS; | |
break; | |
case FR_INVALID_DRIVE: /* (11) The logical drive number is invalid */ | |
errornumber = ENODEV; | |
break; | |
case FR_NOT_ENABLED: /* (12) The volume has no work area */ | |
errornumber = ENOEXEC; | |
break; | |
case FR_NO_FILESYSTEM: /* (13) There is no valid FAT volume */ | |
errornumber = ENFILE; | |
break; | |
case FR_MKFS_ABORTED: /* (14) The f_mkfs() aborted due to any parameter error */ | |
errornumber = ENOEXEC; | |
break; | |
case FR_TIMEOUT: /* (15) Could not get a grant to access the volume within defined period */ | |
errornumber = EAGAIN; | |
break; | |
case FR_LOCKED: /* (16) The operation is rejected according to the file sharing policy */ | |
errornumber = EBUSY; | |
break; | |
case FR_NOT_ENOUGH_CORE: /* (17) LFN working buffer could not be allocated */ | |
errornumber = ENOMEM; | |
break; | |
case FR_TOO_MANY_OPEN_FILES: /* (18) Number of open files > _FS_SHARE */ | |
errornumber = EMFILE; | |
break; | |
case FR_INVALID_PARAMETER: /* (19) Given parameter is invalid */ | |
errornumber = EINVAL; | |
break; | |
default: | |
errornumber = EPERM; | |
break; | |
} | |
if (r != NULL) { | |
r->_errno = errornumber; | |
} else { | |
errno = errornumber; | |
} | |
return errornumber == 0 ? 0 : -1; | |
} | |
static const char *fsdev_convert_path(struct _reent *r, char *outpath, const char *inpath) { | |
const char *colon = strchr(inpath, ':'); | |
/* name:/ */ | |
if (colon != NULL && colon[1] == '/') { | |
char namepart[] = "0"; | |
fsdev_fsdevice_t *dev = (fsdev_fsdevice_t *)(r->deviceData); | |
namepart[0] = '0' + dev->drive_number; | |
strcpy(outpath, namepart); | |
strcat(outpath, colon); | |
} else { | |
strcpy(outpath, inpath); | |
} | |
return outpath; | |
} | |
static void fsdev_filinfo_to_st(struct stat *st, const FILINFO *info) { | |
/* Date of last modification */ | |
struct tm date; | |
memset(st, 0, sizeof(struct stat)); | |
date.tm_mday = info->fdate & 31; | |
date.tm_mon = ((info->fdate >> 5) & 15) - 1; | |
date.tm_year = ((info->fdate >> 9) & 127) - 1980 + 1900; | |
date.tm_sec = (info->ftime << 1) & 63; | |
date.tm_min = (info->ftime >> 5) & 63; | |
date.tm_hour = (info->ftime >> 11) & 31; | |
st->st_atime = st->st_mtime = st->st_ctime = mktime(&date); | |
st->st_size = (off_t)info->fsize; | |
st->st_nlink = 1; | |
if (info->fattrib & AM_DIR) { | |
st->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; | |
} else { | |
st->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; | |
} | |
} | |
static int fsdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode) { | |
(void)mode; | |
char fpath[PATH_MAX + 1]; | |
FIL *f = (FIL *)fileStruct; | |
BYTE ff_flags = 0; | |
static const struct { | |
int posix_flag; | |
BYTE ff_flag; | |
} flag_mappings[] = { | |
{ O_APPEND, FA_OPEN_APPEND }, | |
{ O_CREAT , FA_OPEN_ALWAYS }, | |
{ O_EXCL , FA_CREATE_NEW }, | |
{ O_TRUNC , FA_CREATE_ALWAYS }, | |
{ O_RDONLY, FA_READ }, | |
{ O_WRONLY, FA_WRITE }, | |
{ O_RDWR , FA_READ | FA_WRITE }, | |
}; | |
if ((flags & O_RDONLY & O_WRONLY) || ((flags & O_RDWR) & (O_RDONLY | O_WRONLY))) { | |
r->_errno = EINVAL; | |
return -1; | |
} | |
if (flags & O_RDONLY & O_APPEND) { | |
r->_errno = EINVAL; | |
return -1; | |
} | |
for (size_t i = 0; i < sizeof(flag_mappings)/sizeof(flag_mappings[0]); i++) { | |
if (flags & flag_mappings[i].posix_flag) { | |
ff_flags |= flag_mappings[i].ff_flag; | |
} | |
} | |
/* Normalize O_CREAT|O_TRUNC */ | |
if ((ff_flags & (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS)) == (FA_CREATE_ALWAYS | FA_OPEN_ALWAYS)) { | |
ff_flags &= ~FA_OPEN_ALWAYS; | |
} | |
return fsdev_convert_rc(r, f_open(f, fsdev_convert_path(r, fpath, path), ff_flags)); | |
} | |
static int fsdev_close(struct _reent *r, void *fd) { | |
FIL *f = (FIL *)fd; | |
return fsdev_convert_rc(r, f_close(f)); | |
} | |
static ssize_t fsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len) { | |
FIL *f = (FIL *)fd; | |
UINT wr; | |
int ret = fsdev_convert_rc(r, f_write(f, ptr, (UINT)len, &wr)); | |
return ret == -1 ? -1 : (ssize_t)wr; | |
} | |
static ssize_t fsdev_read(struct _reent *r, void *fd, char *ptr, size_t len) { | |
FIL *f = (FIL *)fd; | |
UINT rd; | |
int ret = fsdev_convert_rc(r, f_read(f, ptr, (UINT)len, &rd)); | |
return ret == -1 ? -1 : (ssize_t)rd; | |
} | |
static off_t fsdev_seek(struct _reent *r, void *fd, off_t pos, int whence) { | |
FIL *f = (FIL *)f; | |
FSIZE_t off; | |
int ret; | |
switch (whence) { | |
case SEEK_SET: | |
off = 0; | |
break; | |
case SEEK_CUR: | |
off = f_tell(f); | |
break; | |
case SEEK_END: | |
off = f_size(f); | |
break; | |
default: | |
r->_errno = EINVAL; | |
return -1; | |
} | |
if(pos < 0 && off < -pos) { | |
/* don't allow seek to before the beginning of the file */ | |
r->_errno = EINVAL; | |
return -1; | |
} | |
ret = fsdev_convert_rc(r, f_lseek(f, off + (FSIZE_t)pos)); | |
return ret == -1 ? -1 : (off_t)(off + (FSIZE_t)pos); | |
} | |
static int fsdev_fstat(struct _reent *r, void *fd, struct stat *st) { | |
(void)fd; | |
(void)st; | |
r->_errno = ENOSYS; | |
return -1; | |
} | |
static int fsdev_stat(struct _reent *r, const char *file, struct stat *st) { | |
char fpath[PATH_MAX + 1]; | |
FILINFO info; | |
FRESULT rc = f_stat(fsdev_convert_path(r, fpath, file), &info); | |
if (rc == FR_OK) { | |
fsdev_filinfo_to_st(st, &info); | |
return 0; | |
} | |
return fsdev_convert_rc(r, rc); | |
} | |
static int fsdev_link(struct _reent *r, const char *existing, const char *newLink) { | |
(void)existing; | |
(void)newLink; | |
r->_errno = ENOSYS; | |
return -1; | |
} | |
static int fsdev_unlink(struct _reent *r, const char *name) { | |
char fpath[PATH_MAX + 1]; | |
return fsdev_convert_rc(r, f_unlink(fsdev_convert_path(r, fpath, name))); | |
} | |
static int fsdev_chdir(struct _reent *r, const char *name) { | |
char fpath[PATH_MAX + 1]; | |
return fsdev_convert_rc(r, f_chdir(fsdev_convert_path(r, fpath, name))); | |
} | |
static int fsdev_rename(struct _reent *r, const char *oldName, const char *newName) { | |
char fpath_old[PATH_MAX + 1], fpath_new[PATH_MAX + 1]; | |
return fsdev_convert_rc(r, f_rename(fsdev_convert_path(r, fpath_old, oldName), fsdev_convert_path(r, fpath_new, newName))); | |
} | |
static int fsdev_mkdir(struct _reent *r, const char *path, int mode) { | |
(void)mode; | |
char dpath[PATH_MAX + 1]; | |
return fsdev_convert_rc(r, f_mkdir(fsdev_convert_path(r, dpath, path))); | |
} | |
static DIR_ITER* fsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path) { | |
char dpath[PATH_MAX + 1]; | |
DIR *d = (DIR *)(dirState->dirStruct); | |
return fsdev_convert_rc(r, f_opendir(d, fsdev_convert_path(r, dpath, path))) == -1 ? NULL : dirState; | |
} | |
static int fsdev_dirreset(struct _reent *r, DIR_ITER *dirState) { | |
(void)dirState; | |
r->_errno = ENOSYS; | |
return -1; | |
} | |
static int fsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { | |
FILINFO info; | |
DIR *d = (DIR *)(dirState->dirStruct); | |
FRESULT rc = f_readdir(d, &info); | |
if (rc == FR_OK) { | |
fsdev_filinfo_to_st(filestat, &info); | |
if(info.fname[0] == '\0') { | |
/* End of directory */ | |
r->_errno = ENOENT; | |
return -1; | |
} | |
strcpy(filename, info.fname); | |
return 0; | |
} else { | |
return fsdev_convert_rc(r, rc); | |
} | |
} | |
static int fsdev_dirclose(struct _reent *r, DIR_ITER *dirState) { | |
DIR *d = (DIR *)(dirState->dirStruct); | |
return fsdev_convert_rc(r, f_closedir(d)); | |
} | |
static int fsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf) { | |
(void)path; | |
DWORD nclust; | |
FATFS *fatfs; | |
FRESULT rc; | |
char mp[] = "0:"; | |
fsdev_fsdevice_t *dev = (fsdev_fsdevice_t *)(r->deviceData); | |
mp[0] = '0' + dev->drive_number; | |
rc = f_getfree(mp, &nclust, &fatfs); | |
if (rc == FR_OK) { | |
#if FF_MAX_SS != FF_MIN_SS | |
buf->f_bsize = fatfs->ssize; | |
#else | |
buf->f_bsize = FF_MAX_SS; | |
#endif | |
buf->f_frsize = buf->f_bsize; | |
buf->f_blocks = (fatfs->n_fatent - 2) * fatfs->csize; | |
buf->f_bfree = nclust * fatfs->csize; | |
buf->f_bavail = buf->f_bfree; | |
buf->f_files = 0; | |
buf->f_ffree = 0; | |
buf->f_favail = 0; | |
buf->f_fsid = 0; | |
buf->f_flag = ST_NOSUID; | |
buf->f_namemax = FF_LFN_BUF; | |
return 0; | |
} else { | |
return fsdev_convert_rc(r, rc); | |
} | |
} | |
static int fsdev_ftruncate(struct _reent *r, void *fd, off_t len) { | |
FIL *f = (FIL *)fd; | |
FSIZE_t off = f_tell(f); /* No need to validate this */ | |
FRESULT rc = f_lseek(f, (FSIZE_t)len); | |
off = off > (FSIZE_t)len ? (FSIZE_t)len : off; | |
if (rc == FR_OK) { | |
rc = f_truncate(f); | |
/* Ignore errors while attempting restore the position */ | |
f_lseek(f, off); | |
return fsdev_convert_rc(r, rc); | |
} else { | |
return fsdev_convert_rc(r, rc); | |
} | |
} | |
static int fsdev_fsync(struct _reent *r, void *fd) { | |
FIL *f = (FIL *)fd; | |
return fsdev_convert_rc(r, f_sync(f)); | |
} | |
static int fsdev_chmod(struct _reent *r, const char *path, mode_t mode) { | |
(void)path; | |
(void)mode; | |
r->_errno = ENOSYS; | |
return -1; | |
} | |
static int fsdev_fchmod(struct _reent *r, void *fd, mode_t mode) { | |
(void)fd; | |
(void)mode; | |
r->_errno = ENOSYS; | |
return -1; | |
} | |
static int fsdev_rmdir(struct _reent *r, const char *name) { | |
char dpath[PATH_MAX + 1]; | |
return fsdev_convert_rc(r, f_unlink(fsdev_convert_path(r, dpath, name))); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment