#include #include #include #include #include #include #include #include #include #include #include #include static int fs_translate_error(FSStatus error); static int fs_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode); static int fs_close(struct _reent *r, int fd); static ssize_t fs_write(struct _reent *r, int fd, const char *ptr, size_t len); static ssize_t fs_write_safe(struct _reent *r, int fd, const char *ptr, size_t len); static ssize_t fs_read(struct _reent *r, int fd, char *ptr, size_t len); static off_t fs_seek(struct _reent *r, int fd, off_t pos, int dir); static int fs_fstat(struct _reent *r, int fd, struct stat *st); static int fs_stat(struct _reent *r, const char *file, struct stat *st); static int fs_link(struct _reent *r, const char *existing, const char *newLink); static int fs_unlink(struct _reent *r, const char *name); static int fs_chdir(struct _reent *r, const char *name); static int fs_rename(struct _reent *r, const char *oldName, const char *newName); static int fs_mkdir(struct _reent *r, const char *path, int mode); static DIR_ITER* fs_diropen(struct _reent *r, DIR_ITER *dirState, const char *path); static int fs_dirreset(struct _reent *r, DIR_ITER *dirState); static int fs_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); static int fs_dirclose(struct _reent *r, DIR_ITER *dirState); static int fs_statvfs(struct _reent *r, const char *path, struct statvfs *buf); static int fs_ftruncate(struct _reent *r, int fd, off_t len); static int fs_fsync(struct _reent *r, int fd); static int fs_chmod(struct _reent *r, const char *path, mode_t mode); static int fs_fchmod(struct _reent *r, int fd, mode_t mode); static int fs_rmdir(struct _reent *r, const char *name); /** * Open file struct */ typedef struct { //! FS handle FSFileHandle fd; //! Flags used in open(2) int flags; //! Current file offset uint32_t offset; } fs_file_t; // "wiiu" #define FS_DIRITER_MAGIC 0x77696975 /** * Open directory struct */ typedef struct { //! Should be set to FS_DIRITER_MAGIC u32 magic; //! FS handle FSDirectoryHandle fd; //! Temporary storage for reading entries FSDirectoryEntry entry_data; } fs_dir_t; /** * Wii U FS devoptab */ static devoptab_t fs_devoptab = { .name = "fs", .structSize = sizeof(fs_file_t), .open_r = fs_open, .close_r = fs_close, .write_r = fs_write_safe, .read_r = fs_read, .seek_r = fs_seek, .fstat_r = fs_fstat, .stat_r = fs_stat, .link_r = fs_link, .unlink_r = fs_unlink, .chdir_r = fs_chdir, .rename_r = fs_rename, .mkdir_r = fs_mkdir, .dirStateSize = sizeof(fs_dir_t), .diropen_r = fs_diropen, .dirreset_r = fs_dirreset, .dirnext_r = fs_dirnext, .dirclose_r = fs_dirclose, .statvfs_r = fs_statvfs, .ftruncate_r = fs_ftruncate, .fsync_r = fs_fsync, .deviceData = NULL, .chmod_r = fs_chmod, .fchmod_r = fs_fchmod, //.rmdir_r = fs_rmdir, }; FSClient * fsClient = NULL; static bool fsInitialised = false; /* Initialize device */ FSStatus fsDevInit() { FSStatus rc = 0; if (fsInitialised) { return rc; } fsClient = memalign(0x20, sizeof(FSClient)); FSCmdBlock fsCmd; FSMountSource mountSource; char mountPath[0x80]; char workDir[0x83]; FSInit(); rc = FSAddClient(fsClient, -1); if (rc < 0) { free(fsClient); return rc; } FSInitCmdBlock(&fsCmd); if (rc >= 0) { int dev = AddDevice(&fs_devoptab); if(dev != -1) { setDefaultDevice(dev); fsInitialised = true; // Mount the SD card rc = FSGetMountSource(fsClient, &fsCmd, FS_MOUNT_SOURCE_SD, &mountSource, -1); if (rc < 0) { return rc; } rc = FSMount(fsClient, &fsCmd, &mountSource, mountPath, 0x80, -1); if (rc >= 0) { // chdir to SD root for general use strcpy(workDir, "fs:"); strcat(workDir, mountPath); chdir(workDir); } } else { FSDelClient(fsClient, -1); free(fsClient); return dev; } } return rc; } static char* fs_fixpath(struct _reent *r, const char *path) { char *p = strchr(path, ':')+1; if(!strchr(path, ':')) { p = (char*)path; } if (strlen(p) > PATH_MAX) { r->_errno = ENAMETOOLONG; return NULL; } char *__fixedpath = memalign(0x40, PATH_MAX+1); if (__fixedpath == NULL) { return NULL; } // cwd is handled by coreinit, so just strip the 'fs:' if it exists strcpy(__fixedpath, p); return __fixedpath; } void fsWriteSafe(bool enable) { if (enable) { fs_devoptab.write_r = fs_write_safe; } else { fs_devoptab.write_r = fs_write; } } FSStatus fsDevExit() { FSStatus rc = 0; if (!fsInitialised) { return rc; } FSDelClient(fsClient, -1); free(fsClient); return rc; } static int fs_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode) { FSFileHandle fd; FSStatus rc; if (path == NULL) { return -1; } char *path_fixed = fs_fixpath(r,path); if (!path_fixed) { r->_errno = ENOMEM; return -1; } // Get pointer to our data fs_file_t *file = (fs_file_t*)fileStruct; const char *fs_mode; // Map flags to open modes if (flags == 0) { fs_mode = "r"; } else if (flags == 2) { fs_mode = "r+"; } else if (flags == 0x601) { fs_mode = "w"; } else if(flags == 0x602) { fs_mode = "w+"; } else if(flags == 0x209) { fs_mode = "a"; } else if(flags == 0x20A) { fs_mode = "a+"; } else { free(path_fixed); r->_errno = EINVAL; return -1; } // Set attributes /* if(!(mode & S_IWUSR)) { attributes |= FS_ATTRIBUTE_READONLY; } */ // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); // Open the file rc = FSOpenFile(fsClient, &fsCmd, path_fixed, fs_mode, &fd, -1); if (rc >= 0) { file->fd = fd; file->flags = (flags & (O_ACCMODE|O_APPEND|O_SYNC)); FSGetPosFile(fsClient, &fsCmd, fd, &file->offset, -1); free(path_fixed); return 0; } free(path_fixed); r->_errno = fs_translate_error(rc); return -1; } static int fs_close(struct _reent *r, int fd) { FSStatus rc; fs_file_t *file = (fs_file_t*)fd; // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSCloseFile(fsClient, &fsCmd, file->fd, -1); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static ssize_t fs_write(struct _reent *r, int fd, const char *ptr, size_t len) { FSStatus rc; u32 bytes, bytesWritten = 0; fs_file_t *file = (fs_file_t*)fd; // Check that the file was opened with write access if ((file->flags & O_ACCMODE) == O_RDONLY) { r->_errno = EBADF; return -1; } // Copy to internal buffer and write in chunks. u8 *tmp_buffer = memalign(0x40, 8192); while(len > 0) { size_t toWrite = len; if (toWrite > 8192) { toWrite = 8192; } // Copy to internal buffer memcpy(tmp_buffer, ptr, toWrite); // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); // Write the data rc = FSWriteFile(fsClient, &fsCmd, tmp_buffer, 1, toWrite, file->fd, 0, -1); if(rc < 0) { free(tmp_buffer); // Return partial transfer if (bytesWritten > 0) { return bytesWritten; } r->_errno = fs_translate_error(rc); return -1; } else { bytes = rc; } file->offset += bytes; bytesWritten += bytes; ptr += bytes; len -= bytes; } free(tmp_buffer); return bytesWritten; } static ssize_t fs_write_safe(struct _reent *r, int fd, const char *ptr, size_t len) { FSStatus rc; u32 bytes, bytesWritten = 0; fs_file_t *file = (fs_file_t*)fd; // Check that the file was opened with write access if ((file->flags & O_ACCMODE) == O_RDONLY) { r->_errno = EBADF; return -1; } // Copy to internal buffer and write in chunks. u8 *tmp_buffer = memalign(0x40, 8192); while(len > 0) { size_t toWrite = len; if (toWrite > 8192) { toWrite = 8192; } // Copy to internal buffer memcpy(tmp_buffer, ptr, toWrite); // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); // Write the data rc = FSWriteFile(fsClient, &fsCmd, tmp_buffer, 1, toWrite, file->fd, 0, -1); if (rc < 0) { free(tmp_buffer); // Return partial transfer if (bytesWritten > 0) { return bytesWritten; } r->_errno = fs_translate_error(rc); return -1; } else { bytes = rc; } file->offset += bytes; bytesWritten += bytes; ptr += bytes; len -= bytes; } free(tmp_buffer); return bytesWritten; } static ssize_t fs_read(struct _reent *r, int fd, char *ptr, size_t len) { FSStatus rc; u32 bytes, bytesRead = 0; fs_file_t *file = (fs_file_t*)fd; // Check that the file was opened with read access if ((file->flags & O_ACCMODE) == O_WRONLY) { r->_errno = EBADF; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); FSStat fsstat; rc = FSGetStatFile(fsClient, &fsCmd, file->fd, &fsstat, -1); if(rc < 0) { r->_errno = fs_translate_error(rc); return -1; } // Copy to internal buffer and read in chunks. u8 *tmp_buffer = memalign(0x40, 8192); while(len > 0) { size_t toRead = len; if (toRead > 8192) { toRead = 8192; } // Write the data rc = FSReadFile(fsClient, &fsCmd, tmp_buffer, 1, toRead, file->fd, 0, -1); if(rc <= 0) { free(tmp_buffer); // Return partial transfer if (bytesRead > 0) { return bytesRead; } r->_errno = fs_translate_error(rc); return -1; } else { bytes = rc; } // Copy to internal buffer memcpy(ptr, tmp_buffer, bytes); file->offset += bytes; bytesRead += bytes; ptr += bytes; len -= bytes; } free(tmp_buffer); return bytesRead; } static off_t fs_seek(struct _reent *r, int fd, off_t pos, int whence) { FSStatus rc; u64 offset; fs_file_t *file = (fs_file_t*)fd; // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); FSStat fsstat; rc = FSGetStatFile(fsClient, &fsCmd, file->fd, &fsstat, -1); if (rc < 0) { r->_errno = fs_translate_error(rc); return -1; } // Find the offset to see from switch(whence) { // Set absolute position; start offset is 0 case SEEK_SET: offset = 0; break; // Set position relative to the current position case SEEK_CUR: offset = file->offset; break; // Set position relative to the end of the file case SEEK_END: offset = fsstat.size; break; // An invalid option was provided default: r->_errno = EINVAL; return -1; } // TODO: A better check that prevents overflow. if(pos < 0 && offset < -pos) { // Don't allow seek to before the beginning of the file r->_errno = EINVAL; return -1; } // Update the current offset file->offset = offset + pos; FSStatus result = FSSetPosFile(fsClient, &fsCmd, file->fd, file->offset, -1); if (result < 0) { return result; } return file->offset; } static int fs_fstat(struct _reent *r, int fd, struct stat *st) { FSStatus rc; FSStat fsstat; fs_file_t *file = (fs_file_t*)fd; // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSGetStatFile(fsClient, &fsCmd, file->fd, &fsstat, -1); if (rc >= 0) { memset(st, 0, sizeof(struct stat)); st->st_size = fsstat.size; st->st_uid = fsstat.owner; st->st_gid = fsstat.group; st->st_nlink = 1; st->st_mode = fsstat.mode; return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_stat(struct _reent *r, const char *file, struct stat *st) { int fd; FSStatus rc; if (file == NULL) { return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); // First try open as file rc = FSOpenFile(fsClient, &fsCmd, file, "r", (FSFileHandle*)&fd, -1); if (rc >= 0) { fs_file_t tmpfd = { .fd = fd }; rc = fs_fstat(r, (int)&tmpfd, st); FSCloseFile(fsClient, &fsCmd, fd, -1); return rc; } // File failed, so lets try open as directory rc = FSOpenDir(fsClient, &fsCmd, file, (FSDirectoryHandle*)&fd, -1); if (rc >= 0) { memset(st, 0, sizeof(struct stat)); st->st_nlink = 1; st->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO; FSCloseDir(fsClient, &fsCmd, fd, -1); return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_link(struct _reent *r, const char *existing, const char *newLink) { r->_errno = ENOSYS; return -1; } static int fs_unlink(struct _reent *r, const char *name) { FSStatus rc; if (name == NULL) { return -1; } char *path_fix = fs_fixpath(r, name); if (!path_fix) { r->_errno = ENOMEM; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSRemove(fsClient, &fsCmd, path_fix, -1); free(path_fix); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_chdir(struct _reent *r, const char *name) { FSStatus rc; if (name == NULL) { return -1; } char *path = fs_fixpath(r, name); if (!path) { r->_errno = ENOMEM; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSChangeDir(fsClient, &fsCmd, path, -1); free(path); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_rename(struct _reent *r, const char *oldName, const char *newName) { FSStatus rc; if (oldName == NULL) { return -1; } if (newName == NULL) { return -1; } char *path_old = fs_fixpath(r, oldName); if (!path_old) { r->_errno = ENOMEM; return -1; } char *path_new = fs_fixpath(r, newName); if (!path_new) { r->_errno = ENOMEM; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSRename(fsClient, &fsCmd, path_old, path_new, -1); free(path_old); free(path_new); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_mkdir(struct _reent *r, const char *path, int mode) { FSError rc; if (path == NULL) { return -1; } char *path_fix = fs_fixpath(r, path); if (!path_fix) { r->_errno = ENOMEM; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); // TODO: Use mode to set directory attributes. rc = FSMakeDir(fsClient, &fsCmd, path_fix, -1); free(path_fix); if (rc == FS_ERROR_ALREADY_EXISTS) { r->_errno = EEXIST; return -1; } else if(rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static DIR_ITER* fs_diropen(struct _reent *r, DIR_ITER *dirState, const char *path) { FSDirectoryHandle fd; FSStatus rc; if (path == NULL) { return NULL; } char *path_fixed = fs_fixpath(r,path); if (!path_fixed) { r->_errno = ENOMEM; return NULL; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); fs_dir_t *dir = (fs_dir_t*)(dirState->dirStruct); rc = FSOpenDir(fsClient, &fsCmd, path_fixed, &fd, -1); if (rc >= 0) { dir->magic = FS_DIRITER_MAGIC; dir->fd = fd; memset(&dir->entry_data, 0, sizeof(dir->entry_data)); free(path_fixed); return dirState; } free(path_fixed); r->_errno = fs_translate_error(rc); return NULL; } static int fs_dirreset(struct _reent *r, DIR_ITER *dirState) { FSStatus rc; // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); fs_dir_t *dir = (fs_dir_t*)(dirState->dirStruct); rc = FSRewindDir(fsClient, &fsCmd, dir->fd, -1); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) { FSStatus rc; fs_dir_t *dir = (fs_dir_t*)(dirState->dirStruct); // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); // Fetch the next dir memset(&dir->entry_data, 0, sizeof(dir->entry_data)); rc = FSReadDir(fsClient, &fsCmd, dir->fd, &dir->entry_data, -1); if (rc < 0) { // There are no more entries; ENOENT signals end-of-directory r->_errno = ENOENT; return -1; } if (rc >= 0) { memset(filestat, 0, sizeof(struct stat)); // Fill in the stat info filestat->st_ino = 0; if (dir->entry_data.info.flags & FS_STAT_DIRECTORY) { filestat->st_mode = S_IFDIR; } else { filestat->st_mode = S_IFREG; } filestat->st_uid = dir->entry_data.info.owner; filestat->st_gid = dir->entry_data.info.group; filestat->st_size = dir->entry_data.info.size; memset(filename, 0, NAME_MAX); strcpy(filename, dir->entry_data.name); return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_dirclose(struct _reent *r, DIR_ITER *dirState) { FSStatus rc; // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); fs_dir_t *dir = (fs_dir_t*)(dirState->dirStruct); rc = FSCloseDir(fsClient, &fsCmd, dir->fd, -1); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_statvfs(struct _reent *r, const char *path, struct statvfs *buf) { FSStatus rc; bool writable = false; char *path_fix = fs_fixpath(r, path); if (!path_fix) { r->_errno = ENOMEM; return -1; } //TODO: FSGetFileSystemInfo free(path_fix); r->_errno = ENOSYS; return -1; } static int fs_ftruncate(struct _reent *r, int fd, off_t len) { FSStatus rc; fs_file_t *file = (fs_file_t*)fd; // Make sure length is non-negative if (len < 0) { r->_errno = EINVAL; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); // Set the new file size rc = FSSetPosFile(fsClient, &fsCmd, file->fd, len, -1); if (rc >= 0) { return 0; } rc = FSTruncateFile(fsClient, &fsCmd, file->fd, -1); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_fsync(struct _reent *r, int fd) { FSStatus rc; fs_file_t *file = (fs_file_t*)fd; // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSFlushFile(fsClient, &fsCmd, file->fd, -1); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_chmod(struct _reent *r, const char *path, mode_t mode) { FSStatus rc; char *path_fix = fs_fixpath(r, path); if (!path_fix) { r->_errno = ENOMEM; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSChangeMode(fsClient, &fsCmd, path_fix, (FSMode)mode, -1); free(path_fix); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } static int fs_fchmod(struct _reent *r, int fd, mode_t mode) { //TODO: FSChangeMode and FSStatFile? r->_errno = ENOSYS; return -1; } static int fs_rmdir(struct _reent *r, const char *name) { FSStatus rc; if (name == NULL) { return -1; } char *path_fix = fs_fixpath(r, name); if (!path_fix) { r->_errno = ENOMEM; return -1; } // Set up command block FSCmdBlock fsCmd; FSInitCmdBlock(&fsCmd); rc = FSRemove(fsClient, &fsCmd, path_fix, -1); free(path_fix); if (rc >= 0) { return 0; } r->_errno = fs_translate_error(rc); return -1; } int fs_getmtime(const char *name, u64 *mtime) { // TODO: Last modified time can probably be get via FSGetStatFile return -1; } // Error map typedef struct { //! Error from FS service FSStatus fs_error; //! POSIX errno int error; } error_map_t; // Error table static const error_map_t error_table[] = { // NOTE: Keep this list sorted! { FS_STATUS_CANCELLED, EINVAL, }, { FS_STATUS_EXISTS, EEXIST, }, { FS_STATUS_NOT_FOUND, ENOENT, }, { FS_STATUS_STORAGE_FULL, ENOSPC, }, { FS_ERROR_INVALID_PATH, ENAMETOOLONG, }, }; static const size_t num_errors = sizeof(error_table)/sizeof(error_table[0]); static int error_cmp(const void *p1, const void *p2) { const error_map_t *lhs = (const error_map_t*)p1; const error_map_t *rhs = (const error_map_t*)p2; if ((u32)lhs->fs_error < (u32)rhs->fs_error) { return -1; } else if((u32)lhs->fs_error > (u32)rhs->fs_error) { return 1; } return 0; } static int fs_translate_error(FSStatus error) { error_map_t key = { .fs_error = error }; const error_map_t *rc = bsearch(&key, error_table, num_errors, sizeof(error_map_t), error_cmp); if (rc != NULL) { return rc->error; } return (int)error; }