/* * Copyright (c) 2018-2020 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 . */ #include #include #include #include #include #include #include #include #include #include #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_register_keys(const char *name, unsigned int target_firmware, BisPartition part) { fsdev_device_t *device = fsdev_find_device(name); if (device == NULL) { errno = ENOENT; return -1; } derive_bis_key(device->devpart.keys, part, target_firmware); return 0; } 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; } int fsdev_set_attr(const char *file, int attr, int mask) { return fsdev_convert_rc(NULL, f_chmod(file, (BYTE)attr, (BYTE)mask)); } int fsdev_get_attr(const char *file) { FILINFO info; FRESULT rc = f_stat(file, &info); if (rc == FR_OK) { return info.fattrib; } return fsdev_convert_rc(NULL, rc); } /* 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)); }