Atmosphere/fusee/fusee-secondary/src/fs_dev.c

654 lines
19 KiB
C

/*
* Copyright (c) 2018-2019 Atmosphère-NX
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
#include <fcntl.h>
#include <sys/iosupport.h>
#include <sys/param.h>
#include <unistd.h>
#include "lib/fatfs/ff.h"
#if FF_VOLUMES != 10
#error "FatFs misconfigured, expected FF_VOLUMES == 10"
#endif
#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 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_device_t {
devoptab_t devoptab;
device_partition_t devpart;
FATFS fatfs;
char name[32+1];
bool setup, registered;
} fsdev_device_t;
static fsdev_device_t g_fsdev_devices[FF_VOLUMES] = { 0 };
/* Required by ff.c */
/* FF_VOLUMES = 10 */
#define FKNAM "\xff$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" /* Workaround for fatfs. */
const char *VolumeStr[FF_VOLUMES] = { FKNAM, FKNAM, FKNAM, FKNAM, FKNAM, FKNAM, FKNAM, FKNAM, FKNAM, FKNAM };
/* For diskio.c code */
device_partition_t *g_volume_to_devparts[FF_VOLUMES] = { NULL };
static fsdev_device_t *fsdev_find_device(const char *name) {
for (size_t i = 0; i < FF_VOLUMES; i++) {
if (g_fsdev_devices[i].setup && strcmp(g_fsdev_devices[i].name, name) == 0) {
return &g_fsdev_devices[i];
}
}
return NULL;
}
int fsdev_mount_device(const char *name, const device_partition_t *devpart, bool initialize_immediately) {
fsdev_device_t *device = fsdev_find_device(name);
FRESULT rc;
char drname[40];
if (device != NULL) {
errno = EEXIST; /* Device already exists */
return -1;
}
strcpy(drname, name);
strcat(drname, ":");
if (name[0] == '\0' || strcmp(name, FKNAM) == 0 || devpart == NULL) {
errno = EINVAL;
return -1;
}
if (strlen(name) > 32) {
errno = ENAMETOOLONG;
return -1;
}
/* Find an unused slot. */
for(size_t i = 0; i < FF_VOLUMES; i++) {
if (!g_fsdev_devices[i].setup) {
device = &g_fsdev_devices[i];
break;
}
}
if (device == NULL) {
errno = ENOMEM;
return -1;
}
memset(device, 0, sizeof(fsdev_device_t));
strcpy(device->name, name);
device->devoptab = g_fsdev_devoptab;
device->devpart = *devpart;
device->devoptab.name = device->name;
device->devoptab.deviceData = device;
VolumeStr[device - g_fsdev_devices] = device->name;
g_volume_to_devparts[device - g_fsdev_devices] = &device->devpart;
rc = f_mount(&device->fatfs, drname, initialize_immediately ? 1 : 0);
if (rc != FR_OK) {
g_volume_to_devparts[device - g_fsdev_devices] = NULL;
VolumeStr[device - g_fsdev_devices] = FKNAM;
return fsdev_convert_rc(NULL, rc);
}
device->setup = true;
device->registered = false;
return 0;
}
int fsdev_register_device(const char *name) {
fsdev_device_t *device = fsdev_find_device(name);
if (device == NULL) {
errno = ENOENT;
return -1;
}
if (device->registered) {
/* Do nothing if the device is already registered. */
return 0;
}
if (AddDevice(&device->devoptab) == -1) {
errno = ENOMEM;
return -1;
} else {
device->registered = true;
return 0;
}
}
int fsdev_unregister_device(const char *name) {
fsdev_device_t *device = fsdev_find_device(name);
char drname[40];
if (device == NULL) {
errno = ENOENT;
return -1;
}
if (!device->registered) {
/* Do nothing if the device is not registered. */
return 0;
}
strcpy(drname, name);
strcat(drname, ":");
if (RemoveDevice(drname) == -1) {
errno = ENOENT;
return -1;
} else {
device->registered = false;
return 0;
}
}
int fsdev_set_default_device(const char *name) {
int ret = 0;
int devid;
char drname[40];
strcpy(drname, name);
strcat(drname, ":");
devid = FindDevice(drname);
if (devid == -1 || fsdev_find_device(name) == NULL) {
errno = ENOENT;
return -1;
}
ret = fsdev_convert_rc(NULL, f_chdrive(drname));
if (ret == 0) {
setDefaultDevice(devid);
}
return ret;
}
int fsdev_is_exfat(const char *name) {
fsdev_device_t *device = fsdev_find_device(name);
if (device != NULL) {
if (device->registered) {
return ((device->fatfs.fs_type == FS_EXFAT) ? 1 : 0);
}
}
errno = ENOENT;
return -1;
}
int fsdev_unmount_device(const char *name) {
int ret;
char drname[40];
fsdev_device_t *device = fsdev_find_device(name);
if (device == NULL) {
errno = ENOENT;
return -1;
}
ret = fsdev_unregister_device(name);
if (ret == -1) {
return -1;
}
strcpy(drname, name);
strcat(drname, ":");
ret = fsdev_convert_rc(NULL, f_unmount(drname));
if (ret == 0) {
VolumeStr[device - g_fsdev_devices] = FKNAM;
g_volume_to_devparts[device - g_fsdev_devices] = NULL;
device->devpart.finalizer(&device->devpart);
memset(device, 0, sizeof(fsdev_device_t));
}
return ret;
}
int fsdev_unmount_all(void) {
for (size_t i = 0; i < FF_VOLUMES; i++) {
int ret = fsdev_unmount_device(g_fsdev_devices[i].name);
if (ret != 0) {
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 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;
date.tm_isdst = 0;
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;
}
st->st_blksize = 512;
st->st_blocks = st->st_size / st->st_blksize;
}
static int fsdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode) {
(void)mode;
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_RDONLY is 0*/
{ O_WRONLY, FA_WRITE },
{ O_RDWR , FA_READ | FA_WRITE },
};
if ((flags & O_RDWR) & (O_RDONLY | O_WRONLY)) {
r->_errno = EINVAL;
return -1;
}
if ((flags & O_APPEND) && (flags & O_ACCMODE) == O_RDONLY) {
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;
}
}
if ((flags & O_ACCMODE) == O_RDONLY) {
ff_flags |= FA_READ;
}
/* 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, 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 *)fd;
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 ((FSIZE_t)pos < 0 && (FSIZE_t)pos + off < 0) {
/* 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) {
FILINFO info;
FRESULT rc = f_stat(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) {
return fsdev_convert_rc(r, f_unlink(name));
}
static int fsdev_chdir(struct _reent *r, const char *name) {
return fsdev_convert_rc(r, f_chdir(name));
}
static int fsdev_rename(struct _reent *r, const char *oldName, const char *newName) {
return fsdev_convert_rc(r, f_rename(oldName, newName));
}
static int fsdev_mkdir(struct _reent *r, const char *path, int mode) {
(void)mode;
return fsdev_convert_rc(r, f_mkdir(path));
}
static DIR_ITER* fsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path) {
DIR *d = (DIR *)(dirState->dirStruct);
return fsdev_convert_rc(r, f_opendir(d, 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;
rc = f_getfree(path, &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) {
return fsdev_convert_rc(r, f_unlink(name));
}