/* * ff_dev.c * * Copyright (c) 2020-2023, DarkMatterCore . * Copyright (c) 2020-2021, XorTroll. * * This file is part of libusbhsfs (https://github.com/DarkMatterCore/libusbhsfs). * * Loosely based on fs_dev.c from libnx, et al. */ #include #include #include "../usbhsfs_manager.h" #include "../usbhsfs_mount.h" /* Helper macros. */ #define ff_end goto end #define ff_ended_with_error (_errno != 0) #define ff_set_error(x) r->_errno = _errno = (x) #define ff_set_error_and_exit(x) \ do { \ ff_set_error((x)); \ ff_end; \ } while(0) #define ff_declare_error_state int _errno = 0 #define ff_declare_file_state FIL *file = (FIL*)fd #define ff_declare_dir_state DIR *dir = (DIR*)dirState->dirStruct #define ff_declare_fs_ctx UsbHsFsDriveLogicalUnitFileSystemContext *fs_ctx = (UsbHsFsDriveLogicalUnitFileSystemContext*)r->deviceData #define ff_declare_lun_ctx UsbHsFsDriveLogicalUnitContext *lun_ctx = (UsbHsFsDriveLogicalUnitContext*)fs_ctx->lun_ctx #define ff_declare_drive_ctx UsbHsFsDriveContext *drive_ctx = (UsbHsFsDriveContext*)lun_ctx->drive_ctx #define ff_declare_vol_state FATFS *fatfs = fs_ctx->fatfs #define ff_lock_drive_ctx ff_declare_fs_ctx; \ ff_declare_lun_ctx; \ ff_declare_drive_ctx; \ bool drive_ctx_valid = usbHsFsManagerIsDriveContextPointerValid(drive_ctx); \ if (!drive_ctx_valid) ff_set_error_and_exit(ENODEV) #define ff_unlock_drive_ctx if (drive_ctx_valid) mutexUnlock(&(drive_ctx->mutex)) #define ff_return(x) return (ff_ended_with_error ? -1 : (x)) #define ff_return_ptr(x) return (ff_ended_with_error ? NULL : (x)) #define ff_return_bool return (ff_ended_with_error ? false : true) /* Function prototypes. */ static int ffdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode); static int ffdev_close(struct _reent *r, void *fd); static ssize_t ffdev_write(struct _reent *r, void *fd, const char *ptr, size_t len); static ssize_t ffdev_read(struct _reent *r, void *fd, char *ptr, size_t len); static off_t ffdev_seek(struct _reent *r, void *fd, off_t pos, int dir); static int ffdev_fstat(struct _reent *r, void *fd, struct stat *st); static int ffdev_stat(struct _reent *r, const char *file, struct stat *st); static int ffdev_link(struct _reent *r, const char *existing, const char *newLink); static int ffdev_unlink(struct _reent *r, const char *name); static int ffdev_chdir(struct _reent *r, const char *name); static int ffdev_rename(struct _reent *r, const char *oldName, const char *newName); static int ffdev_mkdir(struct _reent *r, const char *path, int mode); static DIR_ITER* ffdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path); static int ffdev_dirreset(struct _reent *r, DIR_ITER *dirState); static int ffdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); static int ffdev_dirclose(struct _reent *r, DIR_ITER *dirState); static int ffdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf); static int ffdev_ftruncate(struct _reent *r, void *fd, off_t len); static int ffdev_fsync(struct _reent *r, void *fd); static int ffdev_chmod(struct _reent *r, const char *path, mode_t mode); static int ffdev_fchmod(struct _reent *r, void *fd, mode_t mode); static int ffdev_rmdir(struct _reent *r, const char *name); static int ffdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2]); static bool ffdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath); static void ffdev_fill_stat(struct stat *st, const FILINFO *info); static int ffdev_translate_error(FRESULT res); /* Global variables. */ static const devoptab_t ffdev_devoptab = { .name = NULL, .structSize = sizeof(FIL), .open_r = ffdev_open, .close_r = ffdev_close, .write_r = ffdev_write, .read_r = ffdev_read, .seek_r = ffdev_seek, .fstat_r = ffdev_fstat, ///< Not supported by FatFs. .stat_r = ffdev_stat, .link_r = ffdev_link, ///< Not supported by FatFs. .unlink_r = ffdev_unlink, .chdir_r = ffdev_chdir, .rename_r = ffdev_rename, .mkdir_r = ffdev_mkdir, .dirStateSize = sizeof(DIR), .diropen_r = ffdev_diropen, .dirreset_r = ffdev_dirreset, .dirnext_r = ffdev_dirnext, .dirclose_r = ffdev_dirclose, .statvfs_r = ffdev_statvfs, .ftruncate_r = ffdev_ftruncate, .fsync_r = ffdev_fsync, .deviceData = NULL, .chmod_r = ffdev_chmod, ///< Not supported by FatFs. .fchmod_r = ffdev_fchmod, ///< Not supported by FatFs. .rmdir_r = ffdev_rmdir, .lstat_r = ffdev_stat, ///< Symlinks aren't supported, so we'll just alias lstat() to stat(). .utimes_r = ffdev_utimes }; const devoptab_t *ffdev_get_devoptab() { return &ffdev_devoptab; } static int ffdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode) { (void)mode; BYTE ffdev_flags = 0; FRESULT res = FR_OK; ff_declare_error_state; ff_declare_file_state; ff_lock_drive_ctx; /* Sanity check. */ if (!file) ff_set_error_and_exit(EINVAL); /* Fix input path. */ if (!ffdev_fixpath(r, path, &fs_ctx, NULL)) ff_end; /* Check access mode. */ switch(flags & O_ACCMODE) { case O_RDONLY: /* Read-only. Don't allow append flag. */ if (flags & O_APPEND) ff_set_error_and_exit(EINVAL); ffdev_flags |= FA_READ; break; case O_WRONLY: /* Write-only. */ ffdev_flags |= FA_WRITE; break; case O_RDWR: /* Read and write. */ ffdev_flags |= (FA_READ | FA_WRITE); break; default: /* Invalid option. */ ff_set_error_and_exit(EINVAL); } if ((flags & O_ACCMODE) != O_RDONLY) { if (flags & O_TRUNC) { /* Create a new file. If the file exists, it will be truncated and overwritten. */ ffdev_flags |= FA_CREATE_ALWAYS; } else if (flags & O_CREAT) { /* O_EXCL set: create a new file. Fail if the file already exists. */ /* O_EXCL cleared: */ /* - O_APPEND set: open file. If it doesn't exist, it will be created. The file pointer will be set to EOF before each write. */ /* - O_APPEND cleared: open file. If it doesn't exist, it will be created. */ ffdev_flags |= ((flags & O_EXCL) ? FA_CREATE_NEW : ((flags & O_APPEND) ? FA_OPEN_APPEND : FA_OPEN_ALWAYS)); } else { /* Open file. Fail if the file doesn't exist. */ ffdev_flags |= FA_OPEN_EXISTING; } } else { /* Open file. Fail if the file doesn't exist. */ ffdev_flags |= FA_OPEN_EXISTING; } USBHSFS_LOG_MSG("Opening file \"%s\" (\"%s\") with flags 0x%X (0x%X).", path, __usbhsfs_dev_path_buf, flags, ffdev_flags); /* Reset file descriptor. */ memset(file, 0, sizeof(FIL)); /* Open file. */ res = ff_open(file, __usbhsfs_dev_path_buf, ffdev_flags); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_close(struct _reent *r, void *fd) { FRESULT res = FR_OK; ff_declare_error_state; ff_declare_file_state; ff_lock_drive_ctx; /* Sanity check. */ if (!file) ff_set_error_and_exit(EINVAL); USBHSFS_LOG_MSG("Closing file from \"%u:\".", file->obj.fs->pdrv); /* Close file. */ res = ff_close(file); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Reset file descriptor. */ memset(file, 0, sizeof(FIL)); end: ff_unlock_drive_ctx; ff_return(0); } static ssize_t ffdev_write(struct _reent *r, void *fd, const char *ptr, size_t len) { UINT bw = 0; FRESULT res = FR_OK; ff_declare_error_state; ff_declare_file_state; ff_lock_drive_ctx; /* Sanity check. */ if (!file || !ptr || !len) ff_set_error_and_exit(EINVAL); /* Check if the file was opened with write access. */ if (!(file->flag & FA_WRITE)) ff_set_error_and_exit(EBADF); /* Check if the append flag is enabled. */ if ((file->flag & (FA_OPEN_APPEND & ~FA_OPEN_ALWAYS)) && !ff_eof(file)) { /* Seek to EOF. */ res = ff_lseek(file, ff_size(file)); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); } USBHSFS_LOG_MSG("Writing 0x%lX byte(s) to file in \"%u:\" at offset 0x%lX.", len, file->obj.fs->pdrv, ff_tell(file)); /* Write file data. */ res = ff_write(file, ptr, (UINT)len, &bw); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return((ssize_t)bw); } static ssize_t ffdev_read(struct _reent *r, void *fd, char *ptr, size_t len) { UINT br = 0; FRESULT res = FR_OK; ff_declare_error_state; ff_declare_file_state; ff_lock_drive_ctx; /* Sanity check. */ if (!file || !ptr || !len) ff_set_error_and_exit(EINVAL); /* Check if the file was opened with read access. */ if (!(file->flag & FA_READ)) ff_set_error_and_exit(EBADF); USBHSFS_LOG_MSG("Reading 0x%lX byte(s) from file in \"%u:\" at offset 0x%lX.", len, file->obj.fs->pdrv, ff_tell(file)); /* Read file data. */ res = ff_read(file, ptr, (UINT)len, &br); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return((ssize_t)br); } static off_t ffdev_seek(struct _reent *r, void *fd, off_t pos, int dir) { off_t offset = 0; FRESULT res = FR_OK; ff_declare_error_state; ff_declare_file_state; ff_lock_drive_ctx; /* Sanity check. */ if (!file) ff_set_error_and_exit(EINVAL); /* Find the offset to seek from. */ switch(dir) { case SEEK_SET: /* Set absolute position relative to zero (start offset). */ break; case SEEK_CUR: /* Set position relative to the current position. */ offset = (off_t)ff_tell(file); break; case SEEK_END: /* Set position relative to EOF. */ offset = (off_t)ff_size(file); break; default: /* Invalid option. */ ff_set_error_and_exit(EINVAL); } /* Don't allow negative seeks beyond the beginning of the file. */ if (pos < 0 && offset < -pos) ff_set_error_and_exit(EINVAL); /* Calculate actual offset. */ offset += pos; USBHSFS_LOG_MSG("Seeking to offset 0x%lX from file in \"%u:\".", offset, file->obj.fs->pdrv); /* Perform file seek. */ res = ff_lseek(file, (FSIZE_t)offset); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(offset); } static int ffdev_fstat(struct _reent *r, void *fd, struct stat *st) { (void)fd; (void)st; /* Not supported by FatFs. */ r->_errno = ENOSYS; return -1; } static int ffdev_stat(struct _reent *r, const char *file, struct stat *st) { FILINFO info = {0}; FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Sanity check. */ if (!st) ff_set_error_and_exit(EINVAL); /* Fix input path. */ if (!ffdev_fixpath(r, file, &fs_ctx, NULL)) ff_end; USBHSFS_LOG_MSG("Getting stats for \"%s\" (\"%s\").", file, __usbhsfs_dev_path_buf); /* Get stats. */ res = ff_stat(__usbhsfs_dev_path_buf, &info); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Fill stat info. */ ffdev_fill_stat(st, &info); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_link(struct _reent *r, const char *existing, const char *newLink) { (void)existing; (void)newLink; /* Not supported by FatFs. */ r->_errno = ENOSYS; return -1; } static int ffdev_unlink(struct _reent *r, const char *name) { FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Fix input path. */ if (!ffdev_fixpath(r, name, &fs_ctx, NULL)) ff_end; USBHSFS_LOG_MSG("Deleting \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf); /* Delete file. */ res = ff_unlink(__usbhsfs_dev_path_buf); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_chdir(struct _reent *r, const char *name) { DIR dir = {0}; FRESULT res = FR_OK; size_t cwd_len = 0; ff_declare_error_state; ff_lock_drive_ctx; /* Fix input path. */ if (!ffdev_fixpath(r, name, &fs_ctx, NULL)) ff_end; USBHSFS_LOG_MSG("Changing current directory to \"%s\" (\"%s\").", name, __usbhsfs_dev_path_buf); /* Open directory. */ res = ff_opendir(&dir, __usbhsfs_dev_path_buf); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Close directory. */ ff_closedir(&dir); /* Update current working directory. */ sprintf(fs_ctx->cwd, "%s", strchr(__usbhsfs_dev_path_buf, '/')); cwd_len = strlen(fs_ctx->cwd); if (fs_ctx->cwd[cwd_len - 1] != '/') { fs_ctx->cwd[cwd_len] = '/'; fs_ctx->cwd[cwd_len + 1] = '\0'; } /* Set default devoptab device. */ usbHsFsMountSetDefaultDevoptabDevice(fs_ctx); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_rename(struct _reent *r, const char *oldName, const char *newName) { char old_path[MAX_PATH_LENGTH] = {0}; char *new_path = __usbhsfs_dev_path_buf; FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Fix input paths. */ if (!ffdev_fixpath(r, oldName, &fs_ctx, old_path) || !ffdev_fixpath(r, newName, &fs_ctx, new_path)) ff_end; USBHSFS_LOG_MSG("Renaming \"%s\" (\"%s\") to \"%s\" (\"%s\").", oldName, old_path, newName, new_path); /* Rename entry. */ res = _ff_rename(old_path, new_path); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_mkdir(struct _reent *r, const char *path, int mode) { (void)mode; FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Fix input path. */ if (!ffdev_fixpath(r, path, &fs_ctx, NULL)) ff_end; USBHSFS_LOG_MSG("Creating directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf); /* Create directory. */ res = ff_mkdir(__usbhsfs_dev_path_buf); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static DIR_ITER *ffdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path) { FRESULT res = FR_OK; DIR_ITER *ret = NULL; ff_declare_error_state; ff_lock_drive_ctx; /* Sanity check. */ if (!dirState) ff_set_error_and_exit(EINVAL); ff_declare_dir_state; /* Fix input path. */ if (!ffdev_fixpath(r, path, &fs_ctx, NULL)) ff_end; USBHSFS_LOG_MSG("Opening directory \"%s\" (\"%s\").", path, __usbhsfs_dev_path_buf); /* Reset directory state. */ memset(dir, 0, sizeof(DIR)); /* Open directory. */ res = ff_opendir(dir, __usbhsfs_dev_path_buf); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Update return value. */ ret = dirState; end: ff_unlock_drive_ctx; ff_return_ptr(ret); } static int ffdev_dirreset(struct _reent *r, DIR_ITER *dirState) { FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Sanity check. */ if (!dirState) ff_set_error_and_exit(EINVAL); ff_declare_dir_state; USBHSFS_LOG_MSG("Resetting directory state from \"%u:\".", dir->obj.fs->pdrv); /* Reset directory state. */ res = ff_rewinddir(dir); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { FILINFO info = {0}; FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Sanity check. */ if (!dirState || !filename || !filestat) ff_set_error_and_exit(EINVAL); ff_declare_dir_state; USBHSFS_LOG_MSG("Getting info from next directory entry in \"%u:\".", dir->obj.fs->pdrv); /* Read directory. */ res = ff_readdir(dir, &info); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Check if we haven't reached EOD. */ /* FatFs returns an empty string if so. */ if (info.fname[0]) { /* Copy filename. */ strcpy(filename, info.fname); /* Fill stat info. */ ffdev_fill_stat(filestat, &info); } else { /* ENOENT signals EOD. */ ff_set_error(ENOENT); } end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_dirclose(struct _reent *r, DIR_ITER *dirState) { FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Sanity check. */ if (!dirState) ff_set_error_and_exit(EINVAL); ff_declare_dir_state; USBHSFS_LOG_MSG("Closing directory from \"%u:\".", dir->obj.fs->pdrv); /* Close directory. */ res = ff_closedir(dir); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Reset directory state. */ memset(dir, 0, sizeof(DIR)); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf) { (void)path; char name[MOUNT_NAME_LENGTH] = {0}; DWORD free_clusters = 0; FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; ff_declare_vol_state; /* Sanity check. */ if (!buf) ff_set_error_and_exit(EINVAL); /* Generate volume name. */ sprintf(name, "%u:", fatfs->pdrv); USBHSFS_LOG_MSG("Getting filesystem stats for \"%s\" (\"%s\").", path, name); /* Get volume information. */ res = ff_getfree(name, &free_clusters, &fatfs); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Fill filesystem stats. */ memset(buf, 0, sizeof(struct statvfs)); buf->f_bsize = fatfs->ssize; /* Sector size. */ buf->f_frsize = fatfs->ssize; /* Sector size. */ buf->f_blocks = ((fatfs->n_fatent - 2) * (DWORD)fatfs->csize); /* Total cluster count * cluster size in sectors. */ buf->f_bfree = (free_clusters * (DWORD)fatfs->csize); /* Free cluster count * cluster size in sectors. */ buf->f_bavail = buf->f_bfree; /* Free cluster count * cluster size in sectors. */ buf->f_files = 0; buf->f_ffree = 0; buf->f_favail = 0; buf->f_fsid = fs_ctx->device_id; buf->f_flag = ST_NOSUID; buf->f_namemax = FF_LFN_BUF; end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_ftruncate(struct _reent *r, void *fd, off_t len) { FRESULT res = FR_OK; ff_declare_error_state; ff_declare_file_state; ff_lock_drive_ctx; /* Sanity check. */ if (!file || len < 0) ff_set_error_and_exit(EINVAL); /* Check if the file was opened with write access. */ if (!(file->flag & FA_WRITE)) ff_set_error_and_exit(EBADF); USBHSFS_LOG_MSG("Truncating file in \"%u:\" to 0x%lX bytes.", file->obj.fs->pdrv, len); /* Seek to the provided offset. */ res = ff_lseek(file, (FSIZE_t)len); if (res != FR_OK) ff_set_error_and_exit(ffdev_translate_error(res)); /* Truncate file. */ res = ff_truncate(file); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_fsync(struct _reent *r, void *fd) { FRESULT res = FR_OK; ff_declare_error_state; ff_declare_file_state; ff_lock_drive_ctx; /* Sanity check. */ if (!file) ff_set_error_and_exit(EINVAL); USBHSFS_LOG_MSG("Synchronizing data for file in \"%u:\".", file->obj.fs->pdrv); /* Synchronize file. */ res = ff_sync(file); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static int ffdev_chmod(struct _reent *r, const char *path, mode_t mode) { (void)path; (void)mode; /* Not supported by FatFs. */ r->_errno = ENOSYS; return -1; } static int ffdev_fchmod(struct _reent *r, void *fd, mode_t mode) { (void)fd; (void)mode; /* Not supported by FatFs. */ r->_errno = ENOSYS; return -1; } static int ffdev_rmdir(struct _reent *r, const char *name) { /* Exactly the same as ffdev_unlink(). */ return ffdev_unlink(r, name); } static int ffdev_utimes(struct _reent *r, const char *filename, const struct timeval times[2]) { Result rc = 0; time_t mtime = 0; TimeCalendarTime caltime = {0}; DWORD timestamp = 0; FILINFO info = {0}; FRESULT res = FR_OK; ff_declare_error_state; ff_lock_drive_ctx; /* Fix input path. */ if (!ffdev_fixpath(r, filename, &fs_ctx, NULL)) ff_end; /* Check if we should use the current time. */ /* We can only modify the last modification date and time. */ if (!times) { /* Get current time. */ mtime = time(NULL); } else { /* Only use full second precision from the provided timeval value. */ mtime = times[1].tv_sec; } /* Convert POSIX timestamp to calendar time. */ rc = timeToCalendarTimeWithMyRule((u64)mtime, &caltime, NULL); if (R_SUCCEEDED(rc)) { /* Generate FAT timestamp. */ timestamp = FAT_TIMESTAMP(caltime.year, caltime.month, caltime.day, caltime.hour, caltime.minute, caltime.second); /* Fill FILINFO time data. */ info.fdate = (WORD)(timestamp >> 16); info.ftime = (WORD)(timestamp & 0xFF); } USBHSFS_LOG_MSG("Setting last modification time for \"%s\" (\"%s\") to %u-%02u-%02u %02u:%02u:%02u (0x%04X%04X).", filename, __usbhsfs_dev_path_buf, caltime.year, caltime.month, caltime.day, caltime.hour, \ caltime.minute, caltime.second, info.fdate, info.ftime); /* Change timestamp. */ res = ff_utime(__usbhsfs_dev_path_buf, &info); if (res != FR_OK) ff_set_error(ffdev_translate_error(res)); end: ff_unlock_drive_ctx; ff_return(0); } static bool ffdev_fixpath(struct _reent *r, const char *path, UsbHsFsDriveLogicalUnitFileSystemContext **fs_ctx, char *outpath) { FATFS *fatfs = NULL; const u8 *p = (const u8*)path; ssize_t units = 0; u32 code = 0; size_t len = 0; char name[MOUNT_NAME_LENGTH] = {0}, *outptr = (outpath ? outpath : __usbhsfs_dev_path_buf), *cwd = NULL; ff_declare_error_state; if (!r || !path || !*path || !fs_ctx || !*fs_ctx || !(fatfs = (*fs_ctx)->fatfs) || !(cwd = (*fs_ctx)->cwd)) ff_set_error_and_exit(EINVAL); USBHSFS_LOG_MSG("Input path: \"%s\".", path); /* Generate FatFs mount name ID. */ sprintf(name, "%u:", fatfs->pdrv); /* Move the path pointer to the start of the actual path. */ do { units = decode_utf8(&code, p); if (units < 0) ff_set_error_and_exit(EILSEQ); p += units; } while(code >= ' ' && code != ':'); /* We found a colon; p points to the actual path. */ if (code == ':') path = (const char*)p; /* Make sure there are no more colons and that the remainder of the string is valid UTF-8. */ p = (const u8*)path; do { units = decode_utf8(&code, p); if (units < 0) ff_set_error_and_exit(EILSEQ); if (code == ':') ff_set_error_and_exit(EINVAL); p += units; } while(code >= ' '); /* Verify fixed path length. */ len = (strlen(name) + strlen(path)); if (path[0] != '/') len += strlen(cwd); if (len >= MAX_PATH_LENGTH) ff_set_error_and_exit(ENAMETOOLONG); /* Generate fixed path. */ if (path[0] == '/') { sprintf(outptr, "%s%s", name, path); } else { sprintf(outptr, "%s%s%s", name, cwd, path); } USBHSFS_LOG_MSG("Fixed path: \"%s\".", outptr); end: ff_return_bool; } static void ffdev_fill_stat(struct stat *st, const FILINFO *info) { struct tm timeinfo = {0}; /* Clear stat struct. */ memset(st, 0, sizeof(struct stat)); /* Fill stat struct. */ st->st_nlink = 1; if (info->fattrib & AM_DIR) { /* We're dealing with a directory entry. */ st->st_mode = (S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO); } else { /* We're dealing with a file entry. */ st->st_size = (off_t)info->fsize; st->st_mode = (S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); } /* Convert date/time into an actual UTC POSIX timestamp using the system local time. */ timeinfo.tm_year = (((info->fdate >> 9) & 0x7F) + 80); /* DOS time: offset since 1980. POSIX time: offset since 1900. */ timeinfo.tm_mon = (((info->fdate >> 5) & 0xF) - 1); /* DOS time: 1-12 range (inclusive). POSIX time: 0-11 range (inclusive). */ timeinfo.tm_mday = (info->fdate & 0x1F); timeinfo.tm_hour = ((info->ftime >> 11) & 0x1F); timeinfo.tm_min = ((info->ftime >> 5) & 0x3F); timeinfo.tm_sec = ((info->ftime & 0x1F) << 1); /* DOS time: 2-second intervals with a 0-29 range (inclusive, 58 seconds max). POSIX time: 0-59 range (inclusive). */ st->st_atime = 0; /* Not returned by FatFs + only available under exFAT. */ st->st_mtime = mktime(&timeinfo); st->st_ctime = 0; /* Not returned by FatFs + only available under exFAT. */ USBHSFS_LOG_MSG("DOS timestamp: 0x%04X%04X. Generated POSIX timestamp: %lu.", info->fdate, info->ftime, st->st_mtime); } static int ffdev_translate_error(FRESULT res) { int ret; switch(res) { case FR_OK: ret = 0; break; case FR_DISK_ERR: case FR_NOT_READY: ret = EIO; break; case FR_INT_ERR: case FR_INVALID_NAME: case FR_INVALID_PARAMETER: ret = EINVAL; break; case FR_NO_FILE: case FR_NO_PATH: ret = ENOENT; break; case FR_DENIED: ret = EACCES; break; case FR_EXIST: ret = EEXIST; break; case FR_INVALID_OBJECT: ret = EFAULT; break; case FR_WRITE_PROTECTED: ret = EROFS; break; case FR_INVALID_DRIVE: ret = ENODEV; break; case FR_NOT_ENABLED: ret = ENOEXEC; break; case FR_NO_FILESYSTEM: ret = ENFILE; break; case FR_TIMEOUT: ret = EAGAIN; break; case FR_LOCKED: ret = EBUSY; break; case FR_NOT_ENOUGH_CORE: ret = ENOMEM; break; case FR_TOO_MANY_OPEN_FILES: ret = EMFILE; break; default: ret = EPERM; break; } USBHSFS_LOG_MSG("FRESULT: %u. Translated errno: %d.", res, ret); return ret; }