diff --git a/Dockerfile b/Dockerfile index 7991c07..6cea822 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM wiiuenv/devkitppc:20220710 +FROM wiiuenv/devkitppc:20220806 WORKDIR tmp_build COPY . . diff --git a/Dockerfile.buildlocal b/Dockerfile.buildlocal index 2e23903..08fc052 100644 --- a/Dockerfile.buildlocal +++ b/Dockerfile.buildlocal @@ -1,3 +1,3 @@ -FROM wiiuenv/devkitppc:20220710 +FROM wiiuenv/devkitppc:20220806 WORKDIR project \ No newline at end of file diff --git a/Makefile b/Makefile index e343a58..2c9fcb2 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,8 @@ VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_PATCH) #------------------------------------------------------------------------------- TARGET := $(notdir $(CURDIR)) BUILD := build -SOURCES := source +SOURCES := source \ + source/devoptab DATA := data INCLUDES := source \ include \ @@ -38,7 +39,7 @@ CFLAGS := -Wall -Werror -save-temps \ $(MACHDEP) \ $(BUILD_CFLAGS) -CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ +CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -fno-exceptions CXXFLAGS := $(CFLAGS) -std=gnu++20 diff --git a/README.md b/README.md index d81bdcb..06be463 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ [![Publish Docker Image](https://github.com/wiiu-env/libmocha/actions/workflows/push_image.yml/badge.svg)](https://github.com/wiiu-env/libmocha/actions/workflows/push_image.yml) +**This library is still WIP, may not work as expected or have breaking changes in the near future** + # libmocha Requires the [MochaPayload](https://github.com/wiiu-env/MochaPayload) to be running via [EnvironmentLoader](https://github.com/wiiu-env/EnvironmentLoader). Requires [wut](https://github.com/devkitPro/wut) for building. diff --git a/include/mocha/fsa.h b/include/mocha/fsa.h index d5677bd..076c59f 100644 --- a/include/mocha/fsa.h +++ b/include/mocha/fsa.h @@ -24,7 +24,7 @@ FSError FSAEx_RawOpen(FSClient *client, const char *device_path, int32_t *outHan * @param outHandle pointer where the handle of the raw device will be stored * @return */ -FSError FSAEx_RawOpenEx(int clientHandle, const char *device_path, int32_t *outHandle); +FSError FSAEx_RawOpenEx(FSAClientHandle clientHandle, const char *device_path, int32_t *outHandle); /** * Closes a devices that was previously opened via FSAEx_RawOpen @@ -40,7 +40,7 @@ FSError FSAEx_RawClose(FSClient *client, int32_t device_handle); * @param device_handle device handle * @return */ -FSError FSAEx_RawCloseEx(int clientHandle, int32_t device_handle); +FSError FSAEx_RawCloseEx(FSAClientHandle clientHandle, int32_t device_handle); /** * Read data from a device handle. @@ -66,7 +66,7 @@ FSError FSAEx_RawRead(FSClient *client, void *data, uint32_t size_bytes, uint32_ * @param device_handle valid device handle. * @return */ -FSError FSAEx_RawReadEx(int clientHandle, void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle); +FSError FSAEx_RawReadEx(FSAClientHandle clientHandle, void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle); /** @@ -93,7 +93,7 @@ FSError FSAEx_RawWrite(FSClient *client, const void *data, uint32_t size_bytes, * @param device_handle valid device handle. * @return */ -FSError FSAEx_RawWriteEx(int clientHandle, const void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle); +FSError FSAEx_RawWriteEx(FSAClientHandle clientHandle, const void *data, uint32_t size_bytes, uint32_t cnt, uint64_t blocks_offset, int device_handle); #ifdef __cplusplus } // extern "C" diff --git a/include/mocha/mocha.h b/include/mocha/mocha.h index 1dfe9b3..936d5cc 100644 --- a/include/mocha/mocha.h +++ b/include/mocha/mocha.h @@ -1,7 +1,8 @@ #pragma once +#include "commands.h" +#include "otp.h" #include -#include -#include +#include #include #ifdef __cplusplus @@ -13,6 +14,8 @@ typedef enum MochaUtilsStatus { MOCHA_RESULT_INVALID_ARGUMENT = -0x01, MOCHA_RESULT_MAX_CLIENT = -0x02, MOCHA_RESULT_OUT_OF_MEMORY = -0x03, + MOCHA_RESULT_ALREADY_EXISTS = -0x04, + MOCHA_RESULT_ADD_DEVOPTAB_FAILED = -0x05, MOCHA_RESULT_NOT_FOUND = -0x06, MOCHA_RESULT_UNSUPPORTED_API_VERSION = -0x10, MOCHA_RESULT_UNSUPPORTED_COMMAND = -0x11, @@ -253,6 +256,33 @@ MochaUtilsStatus Mocha_ODMGetDiscKey(WUDDiscKey *discKey); */ MochaUtilsStatus Mocha_SEEPROMRead(uint8_t *out_buffer, uint32_t offset, uint32_t size); +/** + * Mounts a device (dev_path) to a given path (mount_path) and make a accessible via the + * newlib devoptab (standard POSIX file I/O) + * + * Requires Mocha API Version: 1 + * @param virt_name Name which should be used for the devoptab. When choosing e.g. "storage_usb" the mounted device can be accessed via "storage_usb:/". + * @param dev_path (optional) Cafe OS internal device path (e.g. /dev/slc01). If the given dev_path is NULL, an existing mount will be used (and is expected) + * @param mount_path Path where CafeOS should mount the device to. Must be globally unique and start with "/vol/storage_" + * @return MOCHA_RESULT_SUCCESS: The device has been mounted successfully
+ * MOCHA_RESULT_MAX_CLIENT: The maximum number of FSAClients have been registered.
+ * MOCHA_RESULT_LIB_UNINITIALIZED: Library was not initialized. Call Mocha_InitLibrary() before using this function.
+ * MOCHA_RESULT_UNSUPPORTED_COMMAND: Command not supported by the currently loaded mocha version.
+ * MOCHA_RESULT_UNKNOWN_ERROR: Failed to retrieve the environment path. + */ +MochaUtilsStatus Mocha_MountFS(const char *virt_name, const char *dev_path, const char *mount_path); + +MochaUtilsStatus Mocha_MountFSEx(const char *virt_name, const char *dev_path, const char *mount_path, FSAMountFlags mountFlags, void *mountArgBuf, int mountArgBufLen); + +/** + * Unmounts a mount by it's name. + * @param virt_name Name of the mount. + * @return MOCHA_RESULT_SUCCESS: The unmount was successful
+ * MOCHA_RESULT_INVALID_ARGUMENT:
+ * MOCHA_RESULT_NOT_FOUND: No mount with the given name has been found. + */ +MochaUtilsStatus Mocha_UnmountFS(const char *virt_name); + #ifdef __cplusplus } // extern "C" #endif \ No newline at end of file diff --git a/source/devoptab/MutexWrapper.h b/source/devoptab/MutexWrapper.h new file mode 100644 index 0000000..d3b079d --- /dev/null +++ b/source/devoptab/MutexWrapper.h @@ -0,0 +1,25 @@ +#pragma once + +#include "coreinit/cache.h" +#include + +class MutexWrapper { +public: + MutexWrapper() = default; + + void init(const char *name) { + OSInitMutexEx(&mutex, name); + } + + void lock() { + OSLockMutex(&mutex); + } + + void unlock() { + OSUnlockMutex(&mutex); + OSMemoryBarrier(); + } + +private: + OSMutex mutex{}; +}; diff --git a/source/devoptab/devoptab_fsa.cpp b/source/devoptab/devoptab_fsa.cpp new file mode 100644 index 0000000..90076cd --- /dev/null +++ b/source/devoptab/devoptab_fsa.cpp @@ -0,0 +1,203 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include "mocha/mocha.h" +#include +#include +#include + +static const devoptab_t fsa_default_devoptab = { + .structSize = sizeof(__fsa_file_t), + .open_r = __fsa_open, + .close_r = __fsa_close, + .write_r = __fsa_write, + .read_r = __fsa_read, + .seek_r = __fsa_seek, + .fstat_r = __fsa_fstat, + .stat_r = __fsa_stat, + .link_r = __fsa_link, + .unlink_r = __fsa_unlink, + .chdir_r = __fsa_chdir, + .rename_r = __fsa_rename, + .mkdir_r = __fsa_mkdir, + .dirStateSize = sizeof(__fsa_dir_t), + .diropen_r = __fsa_diropen, + .dirreset_r = __fsa_dirreset, + .dirnext_r = __fsa_dirnext, + .dirclose_r = __fsa_dirclose, + .statvfs_r = __fsa_statvfs, + .ftruncate_r = __fsa_ftruncate, + .fsync_r = __fsa_fsync, + .chmod_r = __fsa_chmod, + .fchmod_r = __fsa_fchmod, + .rmdir_r = __fsa_rmdir, + .lstat_r = __fsa_stat, + .utimes_r = __fsa_utimes, +}; + +static bool fsa_initialised = false; +static FSADeviceData fsa_mounts[0x10]; + +static void fsaResetMount(FSADeviceData *mount, uint32_t id) { + *mount = {}; + memcpy(&mount->device, &fsa_default_devoptab, sizeof(fsa_default_devoptab)); + mount->device.name = mount->name; + mount->device.deviceData = mount; + mount->id = id; + mount->setup = false; + mount->mounted = false; + mount->clientHandle = -1; + mount->deviceSizeInSectors = 0; + mount->deviceSectorSize = 0; + memset(mount->mount_path, 0, sizeof(mount->mount_path)); + memset(mount->name, 0, sizeof(mount->name)); + DCFlushRange(mount, sizeof(*mount)); +} + +void fsaInit() { + if (!fsa_initialised) { + uint32_t total = sizeof(fsa_mounts) / sizeof(fsa_mounts[0]); + for (uint32_t i = 0; i < total; i++) { + fsaResetMount(&fsa_mounts[i], i); + } + fsa_initialised = true; + } +} + +std::mutex fsaMutex; + +FSADeviceData *fsa_alloc() { + uint32_t i; + uint32_t total = sizeof(fsa_mounts) / sizeof(fsa_mounts[0]); + FSADeviceData *mount; + + fsaInit(); + + for (i = 0; i < total; i++) { + mount = &fsa_mounts[i]; + if (!mount->setup) { + return mount; + } + } + + return nullptr; +} + +static void fsa_free(FSADeviceData *mount) { + FSError res; + if (mount->mounted) { + if ((res = FSAUnmount(mount->clientHandle, mount->mount_path, FSA_UNMOUNT_FLAG_FORCE)) < 0) { + DEBUG_FUNCTION_LINE_WARN("FSAUnmount %s for %s failed: %s", mount->mount_path, mount->name, FSAGetStatusStr(res)); + } + } + res = FSADelClient(mount->clientHandle); + if (res < 0) { + DEBUG_FUNCTION_LINE_WARN("FSADelClient for %s failed: %s", mount->name, FSAGetStatusStr(res)); + } + fsaResetMount(mount, mount->id); +} + +MochaUtilsStatus Mocha_UnmountFS(const char *virt_name) { + if (!virt_name) { + return MOCHA_RESULT_INVALID_ARGUMENT; + } + std::lock_guard lock(fsaMutex); + uint32_t total = sizeof(fsa_mounts) / sizeof(fsa_mounts[0]); + + fsaInit(); + + for (uint32_t i = 0; i < total; i++) { + FSADeviceData *mount = &fsa_mounts[i]; + if (!mount->setup) { + continue; + } + if (strcmp(mount->name, virt_name) == 0) { + RemoveDevice(mount->name); + fsa_free(mount); + return MOCHA_RESULT_SUCCESS; + } + } + + DEBUG_FUNCTION_LINE_WARN("Failed to find fsa mount data for %s", virt_name); + return MOCHA_RESULT_NOT_FOUND; +} +extern int mochaInitDone; + +MochaUtilsStatus Mocha_MountFS(const char *virt_name, const char *dev_path, const char *mount_path) { + return Mocha_MountFSEx(virt_name, dev_path, mount_path, FSA_MOUNT_FLAG_GLOBAL_MOUNT, nullptr, 0); +} + +MochaUtilsStatus Mocha_MountFSEx(const char *virt_name, const char *dev_path, const char *mount_path, FSAMountFlags mountFlags, void *mountArgBuf, int mountArgBufLen) { + if (!mochaInitDone) { + if (Mocha_InitLibrary() != MOCHA_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Mocha_InitLibrary failed"); + return MOCHA_RESULT_UNSUPPORTED_COMMAND; + } + } + + FSAInit(); + std::lock_guard lock(fsaMutex); + + FSADeviceData *mount = fsa_alloc(); + if (mount == nullptr) { + DEBUG_FUNCTION_LINE_ERR("fsa_alloc() failed"); + OSMemoryBarrier(); + return MOCHA_RESULT_MAX_CLIENT; + } + + mount->clientHandle = FSAAddClient(nullptr); + if (mount->clientHandle < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAAddClient() failed: %s", FSAGetStatusStr(static_cast(mount->clientHandle))); + fsa_free(mount); + return MOCHA_RESULT_MAX_CLIENT; + } + + MochaUtilsStatus status; + if ((status = Mocha_UnlockFSClientEx(mount->clientHandle)) != MOCHA_RESULT_SUCCESS) { + DEBUG_FUNCTION_LINE_ERR("Mocha_UnlockFSClientEx failed: %s", Mocha_GetStatusStr(status)); + return MOCHA_RESULT_UNSUPPORTED_COMMAND; + } + + mount->mounted = false; + + strncpy(mount->name, virt_name, sizeof(mount->name) - 1); + strncpy(mount->mount_path, mount_path, sizeof(mount->mount_path) - 1); + FSError res; + if (dev_path) { + res = FSAMount(mount->clientHandle, dev_path, mount_path, mountFlags, mountArgBuf, mountArgBufLen); + if (res < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAMount(0x%08X, %s, %s, %08X, %08X, %08X) failed: %s", mount->clientHandle, dev_path, mount_path, mountFlags, mountArgBuf, mountArgBufLen, FSAGetStatusStr(res)); + fsa_free(mount); + if (res == FS_ERROR_ALREADY_EXISTS) { + return MOCHA_RESULT_ALREADY_EXISTS; + } + return MOCHA_RESULT_UNKNOWN_ERROR; + } + mount->mounted = true; + } else { + mount->mounted = false; + } + + if ((res = FSAChangeDir(mount->clientHandle, mount->mount_path)) < 0) { + DEBUG_FUNCTION_LINE_WARN("FSAChangeDir(0x%08X, %s) failed: %s", mount->clientHandle, mount->mount_path, FSAGetStatusStr(res)); + } + + FSADeviceInfo deviceInfo; + if ((res = FSAGetDeviceInfo(mount->clientHandle, mount_path, &deviceInfo)) >= 0) { + mount->deviceSizeInSectors = deviceInfo.deviceSizeInSectors; + mount->deviceSectorSize = deviceInfo.deviceSectorSize; + } else { + mount->deviceSizeInSectors = 0xFFFFFFFF; + mount->deviceSectorSize = 512; + DEBUG_FUNCTION_LINE_WARN("Failed to get DeviceInfo for %s: %s", mount_path, FSAGetStatusStr(res)); + } + + if (AddDevice(&mount->device) < 0) { + DEBUG_FUNCTION_LINE_ERR("AddDevice failed for %s.", virt_name); + fsa_free(mount); + return MOCHA_RESULT_ADD_DEVOPTAB_FAILED; + } + + mount->setup = true; + + return MOCHA_RESULT_SUCCESS; +} diff --git a/source/devoptab/devoptab_fsa.h b/source/devoptab/devoptab_fsa.h new file mode 100644 index 0000000..2e3b7db --- /dev/null +++ b/source/devoptab/devoptab_fsa.h @@ -0,0 +1,118 @@ +#pragma once +#include "MutexWrapper.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +typedef struct FSADeviceData { + devoptab_t device{}; + bool setup{}; + bool mounted{}; + uint32_t id{}; + char name[32]{}; + char mount_path[256]{}; + FSAClientHandle clientHandle{}; + uint64_t deviceSizeInSectors{}; + uint32_t deviceSectorSize{}; +} FSADeviceData; + +/** + * Open file struct + */ +typedef struct +{ + //! FS handle + FSAFileHandle fd; + + //! Flags used in open(2) + int flags; + + //! Current file offset + uint32_t offset; + + //! Path stored for internal path tracking + char fullPath[FS_MAX_PATH + 1]; + + //! Guard file access + MutexWrapper mutex; + + //! Current file size (only valid if O_APPEND is set) + uint32_t appendOffset; +} __fsa_file_t; + +/** + * Open directory struct + */ +typedef struct { + //! Should be set to FS_DIRITER_MAGIC + uint32_t magic; + + //! FS handle + FSADirectoryHandle fd; + + //! Temporary storage for reading entries + FSADirectoryEntry entry_data; + + //! Current file path + char name[FS_MAX_PATH + 1]; + + //! Guard dir access + MutexWrapper mutex; +} __fsa_dir_t; + +#define FSA_DIRITER_MAGIC 0x77696975 + +#ifdef __cplusplus +extern "C" { +#endif + +int __fsa_open(struct _reent *r, void *fileStruct, const char *path, + int flags, int mode); +int __fsa_close(struct _reent *r, void *fd); +ssize_t __fsa_write(struct _reent *r, void *fd, const char *ptr, + size_t len); +ssize_t __fsa_read(struct _reent *r, void *fd, char *ptr, size_t len); +off_t __fsa_seek(struct _reent *r, void *fd, off_t pos, int dir); +int __fsa_fstat(struct _reent *r, void *fd, struct stat *st); +int __fsa_stat(struct _reent *r, const char *file, struct stat *st); +int __fsa_link(struct _reent *r, const char *existing, + const char *newLink); +int __fsa_unlink(struct _reent *r, const char *name); +int __fsa_chdir(struct _reent *r, const char *name); +int __fsa_rename(struct _reent *r, const char *oldName, + const char *newName); +int __fsa_mkdir(struct _reent *r, const char *path, int mode); +DIR_ITER *__fsa_diropen(struct _reent *r, DIR_ITER *dirState, + const char *path); +int __fsa_dirreset(struct _reent *r, DIR_ITER *dirState); +int __fsa_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, + struct stat *filestat); +int __fsa_dirclose(struct _reent *r, DIR_ITER *dirState); +int __fsa_statvfs(struct _reent *r, const char *path, + struct statvfs *buf); +int __fsa_ftruncate(struct _reent *r, void *fd, off_t len); +int __fsa_fsync(struct _reent *r, void *fd); +int __fsa_chmod(struct _reent *r, const char *path, mode_t mode); +int __fsa_fchmod(struct _reent *r, void *fd, mode_t mode); +int __fsa_rmdir(struct _reent *r, const char *name); +int __fsa_utimes(struct _reent *r, const char *filename, const struct timeval times[2]); + +// devoptab_fs_utils.c +char *__fsa_fixpath(struct _reent *r, const char *path); +int __fsa_translate_error(FSError error); +time_t __fsa_translate_time(FSTime timeValue); +FSMode __fsa_translate_permission_mode(mode_t mode); +mode_t __fsa_translate_stat_mode(FSAStat *fileStat); +void __fsa_translate_stat(FSAStat *fsStat, struct stat *posStat); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/source/devoptab/devoptab_fsa_chdir.cpp b/source/devoptab/devoptab_fsa_chdir.cpp new file mode 100644 index 0000000..8165bce --- /dev/null +++ b/source/devoptab/devoptab_fsa_chdir.cpp @@ -0,0 +1,31 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_chdir(struct _reent *r, + const char *path) { + FSError status; + + if (!path) { + r->_errno = EINVAL; + return -1; + } + + char *fixedPath = __fsa_fixpath(r, path); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + auto *deviceData = (FSADeviceData *) r->deviceData; + + status = FSAChangeDir(deviceData->clientHandle, fixedPath); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAChangeDir(0x%08X, %s) failed: %s", deviceData->clientHandle, fixedPath, FSAGetStatusStr(status)); + free(fixedPath); + r->_errno = __fsa_translate_error(status); + return -1; + } + free(fixedPath); + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_chmod.cpp b/source/devoptab/devoptab_fsa_chmod.cpp new file mode 100644 index 0000000..73ddb71 --- /dev/null +++ b/source/devoptab/devoptab_fsa_chmod.cpp @@ -0,0 +1,36 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include +#include + +int __fsa_chmod(struct _reent *r, + const char *path, + mode_t mode) { + FSError status; + + if (!path) { + r->_errno = EINVAL; + return -1; + } + + char *fixedPath = __fsa_fixpath(r, path); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + + FSMode translatedMode = __fsa_translate_permission_mode(mode); + + auto *deviceData = (FSADeviceData *) r->deviceData; + + status = FSAChangeMode(deviceData->clientHandle, fixedPath, translatedMode); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAChangeMode(0x%08X, %s, 0x%X) failed: %s", deviceData->clientHandle, fixedPath, translatedMode, FSAGetStatusStr(status)); + free(fixedPath); + r->_errno = __fsa_translate_error(status); + return -1; + } + free(fixedPath); + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_close.cpp b/source/devoptab/devoptab_fsa_close.cpp new file mode 100644 index 0000000..cc3783a --- /dev/null +++ b/source/devoptab/devoptab_fsa_close.cpp @@ -0,0 +1,29 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_close(struct _reent *r, + void *fd) { + FSError status; + __fsa_file_t *file; + + if (!fd) { + r->_errno = EINVAL; + return -1; + } + + file = (__fsa_file_t *) fd; + + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(file->mutex); + + status = FSACloseFile(deviceData->clientHandle, file->fd); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSACloseFile(0x%08X, 0x%08X) failed: %s", deviceData->clientHandle, file->fd, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_dirclose.cpp b/source/devoptab/devoptab_fsa_dirclose.cpp new file mode 100644 index 0000000..44ad080 --- /dev/null +++ b/source/devoptab/devoptab_fsa_dirclose.cpp @@ -0,0 +1,28 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_dirclose(struct _reent *r, + DIR_ITER *dirState) { + FSError status; + + if (!dirState) { + r->_errno = EINVAL; + return -1; + } + + auto *dir = (__fsa_dir_t *) (dirState->dirStruct); + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(dir->mutex); + + status = FSACloseDir(deviceData->clientHandle, dir->fd); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSACloseDir(0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, dir->fd, dir->name, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_dirnext.cpp b/source/devoptab/devoptab_fsa_dirnext.cpp new file mode 100644 index 0000000..f9eeaa7 --- /dev/null +++ b/source/devoptab/devoptab_fsa_dirnext.cpp @@ -0,0 +1,38 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_dirnext(struct _reent *r, + DIR_ITER *dirState, + char *filename, + struct stat *filestat) { + FSError status; + + if (!dirState || !filename || !filestat) { + r->_errno = EINVAL; + return -1; + } + + auto *dir = (__fsa_dir_t *) (dirState->dirStruct); + memset(&dir->entry_data, 0, sizeof(dir->entry_data)); + + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(dir->mutex); + + status = FSAReadDir(deviceData->clientHandle, dir->fd, &dir->entry_data); + if (status < 0) { + if (status != FS_ERROR_END_OF_DIR) { + DEBUG_FUNCTION_LINE_ERR("FSAReadDir(0x%08X, 0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, dir->fd, &dir->entry_data, dir->name, FSAGetStatusStr(status)); + } + r->_errno = __fsa_translate_error(status); + return -1; + } + + __fsa_translate_stat(&dir->entry_data.info, filestat); + + memset(filename, 0, NAME_MAX); + strncpy(filename, dir->entry_data.name, NAME_MAX - 1); + return 0; +} diff --git a/source/devoptab/devoptab_fsa_diropen.cpp b/source/devoptab/devoptab_fsa_diropen.cpp new file mode 100644 index 0000000..0886870 --- /dev/null +++ b/source/devoptab/devoptab_fsa_diropen.cpp @@ -0,0 +1,41 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +DIR_ITER * +__fsa_diropen(struct _reent *r, + DIR_ITER *dirState, + const char *path) { + FSADirectoryHandle fd; + FSError status; + + if (!dirState || !path) { + r->_errno = EINVAL; + return nullptr; + } + + char *fixedPath = __fsa_fixpath(r, path); + if (!fixedPath) { + return nullptr; + } + auto *dir = (__fsa_dir_t *) (dirState->dirStruct); + strncpy(dir->name, fixedPath, sizeof(dir->name) - 1); + free(fixedPath); + + dir->mutex.init(dir->name); + std::lock_guard lock(dir->mutex); + + auto *deviceData = (FSADeviceData *) r->deviceData; + status = FSAOpenDir(deviceData->clientHandle, dir->name, &fd); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAOpenDir(0x%08X, %s, 0x%08X) failed: %s", + deviceData->clientHandle, dir->name, &fd, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return nullptr; + } + + dir->magic = FSA_DIRITER_MAGIC; + dir->fd = fd; + memset(&dir->entry_data, 0, sizeof(dir->entry_data)); + return dirState; +} diff --git a/source/devoptab/devoptab_fsa_dirreset.cpp b/source/devoptab/devoptab_fsa_dirreset.cpp new file mode 100644 index 0000000..7fa348a --- /dev/null +++ b/source/devoptab/devoptab_fsa_dirreset.cpp @@ -0,0 +1,28 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_dirreset(struct _reent *r, + DIR_ITER *dirState) { + FSError status; + + if (!dirState) { + r->_errno = EINVAL; + return -1; + } + + auto *dir = (__fsa_dir_t *) (dirState->dirStruct); + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(dir->mutex); + + status = FSARewindDir(deviceData->clientHandle, dir->fd); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSARewindDir(0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, dir->fd, dir->name, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_fchmod.cpp b/source/devoptab/devoptab_fsa_fchmod.cpp new file mode 100644 index 0000000..e0bd6ec --- /dev/null +++ b/source/devoptab/devoptab_fsa_fchmod.cpp @@ -0,0 +1,11 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_fchmod(struct _reent *r, + void *fd, + mode_t mode) { + // FSChangeMode on open files is not possible on Cafe OS + r->_errno = ENOSYS; + return -1; +} diff --git a/source/devoptab/devoptab_fsa_fstat.cpp b/source/devoptab/devoptab_fsa_fstat.cpp new file mode 100644 index 0000000..a4e53bb --- /dev/null +++ b/source/devoptab/devoptab_fsa_fstat.cpp @@ -0,0 +1,32 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_fstat(struct _reent *r, + void *fd, + struct stat *st) { + FSError status; + FSAStat fsStat; + + if (!fd || !st) { + r->_errno = EINVAL; + return -1; + } + + auto *file = (__fsa_file_t *) fd; + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(file->mutex); + + status = FSAGetStatFile(deviceData->clientHandle, file->fd, &fsStat); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSARewindDir(0x%08X, 0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, file->fd, &fsStat, file->fullPath, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + __fsa_translate_stat(&fsStat, st); + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_fsync.cpp b/source/devoptab/devoptab_fsa_fsync.cpp new file mode 100644 index 0000000..885a71e --- /dev/null +++ b/source/devoptab/devoptab_fsa_fsync.cpp @@ -0,0 +1,29 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_fsync(struct _reent *r, + void *fd) { + FSError status; + + if (!fd) { + r->_errno = EINVAL; + return -1; + } + + auto *file = (__fsa_file_t *) fd; + + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(file->mutex); + + status = FSAFlushFile(deviceData->clientHandle, file->fd); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAFlushFile(0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, file->fd, file->fullPath, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_link.cpp b/source/devoptab/devoptab_fsa_link.cpp new file mode 100644 index 0000000..ce1697d --- /dev/null +++ b/source/devoptab/devoptab_fsa_link.cpp @@ -0,0 +1,8 @@ +#include "devoptab_fsa.h" + +int __fsa_link(struct _reent *r, + const char *existing, + const char *newLink) { + r->_errno = ENOSYS; + return -1; +} diff --git a/source/devoptab/devoptab_fsa_mkdir.cpp b/source/devoptab/devoptab_fsa_mkdir.cpp new file mode 100644 index 0000000..467567d --- /dev/null +++ b/source/devoptab/devoptab_fsa_mkdir.cpp @@ -0,0 +1,37 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_mkdir(struct _reent *r, + const char *path, + int mode) { + FSError status; + char *fixedPath; + + if (!path) { + r->_errno = EINVAL; + return -1; + } + + fixedPath = __fsa_fixpath(r, path); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + + auto *deviceData = (FSADeviceData *) r->deviceData; + + FSMode translatedMode = __fsa_translate_permission_mode(mode); + + status = FSAMakeDir(deviceData->clientHandle, fixedPath, translatedMode); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAMakeDir(0x%08X, %s, 0x%X) failed: %s", + deviceData->clientHandle, fixedPath, translatedMode, FSAGetStatusStr(status)); + free(fixedPath); + r->_errno = __fsa_translate_error(status); + return -1; + } + free(fixedPath); + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_open.cpp b/source/devoptab/devoptab_fsa_open.cpp new file mode 100644 index 0000000..faa3a22 --- /dev/null +++ b/source/devoptab/devoptab_fsa_open.cpp @@ -0,0 +1,144 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +// Extended "magic" value that allows opening files with FS_OPEN_FLAG_UNENCRYPTED in underlying FSOpenFileEx() call similar to O_DIRECTORY +#ifndef O_UNENCRYPTED +#define O_UNENCRYPTED 0x4000000 +#endif + +int __fsa_open(struct _reent *r, + void *fileStruct, + const char *path, + int flags, + int mode) { + FSAFileHandle fd; + FSError status; + const char *fsMode; + + if (!fileStruct || !path) { + r->_errno = EINVAL; + return -1; + } + + bool createFileIfNotFound = false; + bool failIfFileNotFound = false; + // Map flags to open modes + int commonFlagMask = O_CREAT | O_TRUNC | O_APPEND; + if (((flags & O_ACCMODE) == O_RDONLY) && !(flags & commonFlagMask)) { + fsMode = "r"; + } else if (((flags & O_ACCMODE) == O_RDWR) && !(flags & commonFlagMask)) { + fsMode = "r+"; + } else if (((flags & O_ACCMODE) == O_WRONLY) && ((flags & commonFlagMask) == (O_CREAT | O_TRUNC))) { + fsMode = "w"; + } else if (((flags & O_ACCMODE) == O_RDWR) && ((flags & commonFlagMask) == (O_CREAT | O_TRUNC))) { + fsMode = "w+"; + } else if (((flags & O_ACCMODE) == O_WRONLY) && ((flags & commonFlagMask) == (O_CREAT | O_APPEND))) { + fsMode = "a"; + } else if (((flags & O_ACCMODE) == O_RDWR) && ((flags & commonFlagMask) == (O_CREAT | O_APPEND))) { + fsMode = "a+"; + } else if (((flags & O_ACCMODE) == O_WRONLY) && ((flags & commonFlagMask) == (O_CREAT))) { + // Cafe OS doesn't have a matching mode for this, so we have to be creative and create the file. + createFileIfNotFound = true; + // It's not possible to open a file with write only mode which doesn't truncate the file + // Technically we could read from the file, but our read implementation is blocking this. + fsMode = "r+"; + } else if (((flags & O_ACCMODE) == O_WRONLY) && ((flags & commonFlagMask) == (O_APPEND))) { + // Cafe OS doesn't have a matching mode for this, so we have to check if the file exists. + failIfFileNotFound = true; + fsMode = "a"; + } else if (((flags & O_ACCMODE) == O_WRONLY) && ((flags & commonFlagMask) == (O_TRUNC))) { + // As above + failIfFileNotFound = true; + fsMode = "w"; + } else { + r->_errno = EINVAL; + return -1; + } + + char *fixedPath = __fsa_fixpath(r, path); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + + auto *file = (__fsa_file_t *) fileStruct; + strncpy(file->fullPath, fixedPath, sizeof(file->fullPath) - 1); + free(fixedPath); + + // Prepare flags + FSOpenFileFlags openFlags = (flags & O_UNENCRYPTED) ? FS_OPEN_FLAG_UNENCRYPTED : FS_OPEN_FLAG_NONE; + FSMode translatedMode = __fsa_translate_permission_mode(mode); + uint32_t preAllocSize = 0; + + // Init mutex and lock + file->mutex.init(file->fullPath); + std::lock_guard lock(file->mutex); + + auto *deviceData = (FSADeviceData *) r->deviceData; + + if (createFileIfNotFound || failIfFileNotFound || (flags & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT)) { + // Check if file exists + FSAStat stat; + status = FSAGetStat(deviceData->clientHandle, file->fullPath, &stat); + if (status == FS_ERROR_NOT_FOUND) { + if (createFileIfNotFound) { // Create new file if needed + status = FSAOpenFileEx(deviceData->clientHandle, file->fullPath, "w", translatedMode, + openFlags, preAllocSize, &fd); + if (status == FS_ERROR_OK) { + if (FSACloseFile(deviceData->clientHandle, fd) != FS_ERROR_OK) { + DEBUG_FUNCTION_LINE_ERR("FSACloseFile(0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, fd, file->fullPath, FSAGetStatusStr(status)); + } + fd = -1; + } else { + DEBUG_FUNCTION_LINE_ERR("FSAOpenFileEx(0x%08X, %s, %s, 0x%X, 0x%08X, 0x%08X, 0x%08X) failed: %s", + deviceData->clientHandle, file->fullPath, "w", translatedMode, openFlags, preAllocSize, &fd, + FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + } else if (failIfFileNotFound) { // Return an error if we don't we create new files + r->_errno = __fsa_translate_error(status); + return -1; + } + } else if (status == FS_ERROR_OK) { + // If O_CREAT and O_EXCL are set, open() shall fail if the file exists. + if ((flags & (O_EXCL | O_CREAT)) == (O_EXCL | O_CREAT)) { + r->_errno = EEXIST; + return -1; + } + } + } + + status = FSAOpenFileEx(deviceData->clientHandle, file->fullPath, fsMode, translatedMode, openFlags, preAllocSize, &fd); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAOpenFileEx(0x%08X, %s, %s, 0x%X, 0x%08X, 0x%08X, 0x%08X) failed: %s", + deviceData->clientHandle, file->fullPath, fsMode, translatedMode, openFlags, preAllocSize, &fd, + FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + file->fd = fd; + file->flags = (flags & (O_ACCMODE | O_APPEND | O_SYNC)); + // Is always 0, even if O_APPEND is set. + file->offset = 0; + + if (flags & O_APPEND) { + FSAStat stat; + status = FSAGetStatFile(deviceData->clientHandle, fd, &stat); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAGetStatFile(0x%08X, 0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, fd, &stat, file->fullPath, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + if (FSACloseFile(deviceData->clientHandle, fd) < 0) { + DEBUG_FUNCTION_LINE_ERR("FSACloseFile(0x%08X, 0x%08X) (%d) failed: %s", + deviceData->clientHandle, fd, file->fullPath, FSAGetStatusStr(status)); + } + return -1; + } + file->appendOffset = stat.size; + } + return 0; +} \ No newline at end of file diff --git a/source/devoptab/devoptab_fsa_read.cpp b/source/devoptab/devoptab_fsa_read.cpp new file mode 100644 index 0000000..00131ae --- /dev/null +++ b/source/devoptab/devoptab_fsa_read.cpp @@ -0,0 +1,79 @@ +#include "devoptab_fsa.h" +#include "logger.h" + +#include +#include + +ssize_t __fsa_read(struct _reent *r, void *fd, char *ptr, size_t len) { + FSError status; + if (!fd || !ptr) { + r->_errno = EINVAL; + return -1; + } + + // Check that the file was opened with read access + auto *file = (__fsa_file_t *) fd; + if ((file->flags & O_ACCMODE) == O_WRONLY) { + r->_errno = EBADF; + return -1; + } + + // cache-aligned, cache-line-sized + __attribute__((aligned(0x40))) uint8_t alignedBuffer[0x40]; + + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(file->mutex); + + size_t bytesRead = 0; + while (bytesRead < len) { + // only use input buffer if cache-aligned and read size is a multiple of cache line size + // otherwise read into alignedBuffer + uint8_t *tmp = (uint8_t *) ptr; + size_t size = len - bytesRead; + + if ((uintptr_t) ptr & 0x3F) { + // read partial cache-line front-end + tmp = alignedBuffer; + size = MIN(size, 0x40 - ((uintptr_t) ptr & 0x3F)); + } else if (size < 0x40) { + // read partial cache-line back-end + tmp = alignedBuffer; + } else { + // read whole cache lines + size &= ~0x3F; + } + + // Limit each request to 1 MiB + if (size > 0x100000) { + size = 0x100000; + } + + status = FSAReadFile(deviceData->clientHandle, tmp, 1, size, file->fd, 0); + + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAReadFile(0x%08X, 0x%08X, 1, 0x%08X, 0x%08X, 0) (%s) failed: %s", + deviceData->clientHandle, tmp, size, file->fd, file->fullPath, FSAGetStatusStr(status)); + if (bytesRead != 0) { + return bytesRead; // error after partial read + } + + r->_errno = __fsa_translate_error(status); + return -1; + } + + if (tmp == alignedBuffer) { + memcpy(ptr, alignedBuffer, status); + } + + file->offset += status; + bytesRead += status; + ptr += status; + + if ((size_t) status != size) { + return bytesRead; // partial read + } + } + + return bytesRead; +} \ No newline at end of file diff --git a/source/devoptab/devoptab_fsa_rename.cpp b/source/devoptab/devoptab_fsa_rename.cpp new file mode 100644 index 0000000..b551f93 --- /dev/null +++ b/source/devoptab/devoptab_fsa_rename.cpp @@ -0,0 +1,44 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_rename(struct _reent *r, + const char *oldName, + const char *newName) { + FSError status; + char *fixedOldPath, *fixedNewPath; + + if (!oldName || !newName) { + r->_errno = EINVAL; + return -1; + } + + fixedOldPath = __fsa_fixpath(r, oldName); + if (!fixedOldPath) { + r->_errno = ENOMEM; + return -1; + } + + fixedNewPath = __fsa_fixpath(r, newName); + if (!fixedNewPath) { + free(fixedOldPath); + r->_errno = ENOMEM; + return -1; + } + + auto *deviceData = (FSADeviceData *) r->deviceData; + + status = FSARename(deviceData->clientHandle, fixedOldPath, fixedNewPath); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSARename(0x%08X, %s, %s) failed: %s", + deviceData->clientHandle, fixedOldPath, fixedNewPath, FSAGetStatusStr(status)); + free(fixedOldPath); + free(fixedNewPath); + r->_errno = __fsa_translate_error(status); + return -1; + } + free(fixedOldPath); + free(fixedNewPath); + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_rmdir.cpp b/source/devoptab/devoptab_fsa_rmdir.cpp new file mode 100644 index 0000000..31aa59b --- /dev/null +++ b/source/devoptab/devoptab_fsa_rmdir.cpp @@ -0,0 +1,34 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_rmdir(struct _reent *r, + const char *name) { + FSError status; + + if (!name) { + r->_errno = EINVAL; + return -1; + } + + char *fixedPath = __fsa_fixpath(r, name); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + + auto *deviceData = (FSADeviceData *) r->deviceData; + + status = FSARemove(deviceData->clientHandle, fixedPath); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSARemove(0x%08X, %s) failed: %s", + deviceData->clientHandle, fixedPath, FSAGetStatusStr(status)); + free(fixedPath); + r->_errno = status == FS_ERROR_ALREADY_EXISTS ? ENOTEMPTY : __fsa_translate_error(status); + return -1; + } + + free(fixedPath); + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_seek.cpp b/source/devoptab/devoptab_fsa_seek.cpp new file mode 100644 index 0000000..69b0a6b --- /dev/null +++ b/source/devoptab/devoptab_fsa_seek.cpp @@ -0,0 +1,78 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +off_t __fsa_seek(struct _reent *r, + void *fd, + off_t pos, + int whence) { + FSError status; + FSAStat fsStat; + uint64_t offset; + + if (!fd) { + r->_errno = EINVAL; + return -1; + } + + auto *file = (__fsa_file_t *) fd; + + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(file->mutex); + + // Find the offset to see from + switch (whence) { + case SEEK_SET: { // Set absolute position; start offset is 0 + offset = 0; + break; + } + case SEEK_CUR: { // Set position relative to the current position + offset = file->offset; + break; + } + case SEEK_END: { // Set position relative to the end of the file + status = FSAGetStatFile(deviceData->clientHandle, file->fd, &fsStat); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAGetStatFile(0x%08X, 0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, file->fd, &fsStat, file->fullPath, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + offset = fsStat.size; + break; + } + default: { // An invalid option was provided + r->_errno = EINVAL; + return -1; + } + } + + if (pos < 0 && (off_t) offset < -pos) { + // Don't allow seek to before the beginning of the file + r->_errno = EINVAL; + return -1; + } else if (offset + pos > UINT32_MAX) { + // Check for overflow + r->_errno = EINVAL; + return -1; + } + + if ((uint32_t) (offset + pos) == file->offset) { + return file->offset; + } + + uint32_t old_pos = file->offset; + file->offset = offset + pos; + + status = FSASetPosFile(deviceData->clientHandle, file->fd, file->offset); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSASetPosFile(0x%08X, 0x%08X, 0x%08X) (%s) failed: %s", + deviceData->clientHandle, file->fd, file->offset, file->fullPath, FSAGetStatusStr(status)); + file->offset = old_pos; + r->_errno = __fsa_translate_error(status); + return -1; + } + + return file->offset; +} diff --git a/source/devoptab/devoptab_fsa_stat.cpp b/source/devoptab/devoptab_fsa_stat.cpp new file mode 100644 index 0000000..562b27a --- /dev/null +++ b/source/devoptab/devoptab_fsa_stat.cpp @@ -0,0 +1,36 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_stat(struct _reent *r, + const char *path, + struct stat *st) { + FSError status; + FSAStat fsStat; + + if (!path || !st) { + r->_errno = EINVAL; + return -1; + } + + char *fixedPath = __fsa_fixpath(r, path); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + + auto *deviceData = (FSADeviceData *) r->deviceData; + + status = FSAGetStat(deviceData->clientHandle, fixedPath, &fsStat); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAGetStat(0x%08X, %s, 0x%08X) failed: %s", + deviceData->clientHandle, fixedPath, &fsStat, FSAGetStatusStr(status)); + free(fixedPath); + r->_errno = __fsa_translate_error(status); + return -1; + } + free(fixedPath); + + __fsa_translate_stat(&fsStat, st); + return 0; +} \ No newline at end of file diff --git a/source/devoptab/devoptab_fsa_statvfs.cpp b/source/devoptab/devoptab_fsa_statvfs.cpp new file mode 100644 index 0000000..94574a2 --- /dev/null +++ b/source/devoptab/devoptab_fsa_statvfs.cpp @@ -0,0 +1,51 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_statvfs(struct _reent *r, + const char *path, + struct statvfs *buf) { + FSError status; + uint64_t freeSpace; + + memset(buf, 0, sizeof(struct statvfs)); + + char *fixedPath = __fsa_fixpath(r, path); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + + auto *deviceData = (FSADeviceData *) r->deviceData; + + status = FSAGetFreeSpaceSize(deviceData->clientHandle, fixedPath, &freeSpace); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAGetFreeSpaceSize(0x%08X, %s, 0x%08X) failed: %s", + deviceData->clientHandle, fixedPath, &freeSpace, FSAGetStatusStr(status)); + free(fixedPath); + r->_errno = __fsa_translate_error(status); + return -1; + } + free(fixedPath); + + // File system block size + buf->f_bsize = deviceData->deviceSectorSize; + // Fundamental file system block size + buf->f_frsize = deviceData->deviceSectorSize; + // Total number of blocks on file system in units of f_frsize + buf->f_blocks = deviceData->deviceSizeInSectors; + // Free blocks available for all and for non-privileged processes + buf->f_bfree = buf->f_bavail = (uint32_t) (freeSpace / buf->f_frsize); + // Number of inodes at this point in time + buf->f_files = 0xFFFFFFFF; + // Free inodes available for all and for non-privileged processes + buf->f_ffree = 0xFFFFFFFF; + // File system id + buf->f_fsid = (unsigned long) deviceData->clientHandle; + // Bit mask of f_flag values. + buf->f_flag = 0; + // Maximum length of filenames + buf->f_namemax = 255; + + return 0; +} \ No newline at end of file diff --git a/source/devoptab/devoptab_fsa_truncate.cpp b/source/devoptab/devoptab_fsa_truncate.cpp new file mode 100644 index 0000000..6c617d8 --- /dev/null +++ b/source/devoptab/devoptab_fsa_truncate.cpp @@ -0,0 +1,40 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_ftruncate(struct _reent *r, + void *fd, + off_t len) { + FSError status; + + // Make sure length is non-negative + if (!fd || len < 0) { + r->_errno = EINVAL; + return -1; + } + + auto *file = (__fsa_file_t *) fd; + + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(file->mutex); + + // Set the new file size + status = FSASetPosFile(deviceData->clientHandle, file->fd, len); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSASetPosFile(0x%08X, 0x%08X, 0x%08X) failed: %s", + deviceData->clientHandle, file->fd, len, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + status = FSATruncateFile(deviceData->clientHandle, file->fd); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSATruncateFile(0x%08X, 0x%08X) failed: %s", + deviceData->clientHandle, file->fd, FSAGetStatusStr(status)); + r->_errno = __fsa_translate_error(status); + return -1; + } + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_unlink.cpp b/source/devoptab/devoptab_fsa_unlink.cpp new file mode 100644 index 0000000..e55ec68 --- /dev/null +++ b/source/devoptab/devoptab_fsa_unlink.cpp @@ -0,0 +1,34 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +int __fsa_unlink(struct _reent *r, + const char *name) { + FSError status; + char *fixedPath; + + if (!name) { + r->_errno = EINVAL; + return -1; + } + + fixedPath = __fsa_fixpath(r, name); + if (!fixedPath) { + r->_errno = ENOMEM; + return -1; + } + auto *deviceData = (FSADeviceData *) r->deviceData; + + status = FSARemove(deviceData->clientHandle, fixedPath); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSARemove(0x%08X, %s) failed: %s", + deviceData->clientHandle, fixedPath, FSAGetStatusStr(status)); + free(fixedPath); + r->_errno = __fsa_translate_error(status); + return -1; + } + + free(fixedPath); + + return 0; +} diff --git a/source/devoptab/devoptab_fsa_utils.cpp b/source/devoptab/devoptab_fsa_utils.cpp new file mode 100644 index 0000000..0ed5fd6 --- /dev/null +++ b/source/devoptab/devoptab_fsa_utils.cpp @@ -0,0 +1,194 @@ +#include "devoptab_fsa.h" + +char * +__fsa_fixpath(struct _reent *r, + const char *path) { + char *p; + char *fixedPath; + + if (!path) { + r->_errno = EINVAL; + return nullptr; + } + + p = strchr(path, ':') + 1; + if (!strchr(path, ':')) { + p = (char *) path; + } + + size_t pathLength = strlen(p); + if (pathLength > FS_MAX_PATH) { + r->_errno = ENAMETOOLONG; + return nullptr; + } + + // wii u softlocks on empty strings so give expected error back + if (pathLength == 0) { + r->_errno = ENOENT; + return nullptr; + } + + fixedPath = static_cast(memalign(0x40, FS_MAX_PATH + 1)); + if (!fixedPath) { + r->_errno = ENOMEM; + return nullptr; + } + + if (p[0] == '/') { + auto *deviceData = (FSADeviceData *) r->deviceData; + strcpy(fixedPath, deviceData->mount_path); + strcat(fixedPath, p); + } else { + strcpy(fixedPath, p); + } + + return fixedPath; +} + +mode_t __fsa_translate_stat_mode(FSAStat *fileStat) { + mode_t retMode = 0; + + if ((fileStat->flags & FS_STAT_LINK) == FS_STAT_LINK) { + retMode |= S_IFLNK; + } else if ((fileStat->flags & FS_STAT_DIRECTORY) == FS_STAT_DIRECTORY) { + retMode |= S_IFDIR; + } else if ((fileStat->flags & FS_STAT_FILE) == FS_STAT_FILE) { + retMode |= S_IFREG; + } else if (fileStat->size == 0) { + // Mounted paths like /vol/external01 have no flags set. + // If no flag is set and the size is 0, it's a (root) dir + retMode |= S_IFDIR; + } else if (fileStat->size > 0) { + // Some regular Wii U files have no type info but will have a size + retMode |= S_IFREG; + } + + // Convert normal CafeOS hexadecimal permission bits into Unix octal permission bits + mode_t permissionMode = (((fileStat->mode >> 2) & S_IRWXU) | ((fileStat->mode >> 1) & S_IRWXG) | (fileStat->mode & S_IRWXO)); + + return retMode | permissionMode; +} + +void __fsa_translate_stat(FSAStat *fsStat, struct stat *posStat) { + memset(posStat, 0, sizeof(struct stat)); + posStat->st_dev = (dev_t) nullptr; + posStat->st_ino = fsStat->entryId; + posStat->st_mode = __fsa_translate_stat_mode(fsStat); + posStat->st_nlink = 1; + posStat->st_uid = fsStat->owner; + posStat->st_gid = fsStat->group; + posStat->st_rdev = posStat->st_dev; + posStat->st_size = fsStat->size; + posStat->st_atime = __fsa_translate_time(fsStat->modified); + posStat->st_ctime = __fsa_translate_time(fsStat->created); + posStat->st_mtime = __fsa_translate_time(fsStat->modified); + posStat->st_blksize = 512; + posStat->st_blocks = (posStat->st_size + posStat->st_blksize - 1) / posStat->st_size; +} + +// The Wii U FSTime epoch is at 1980, so we must map it to 1970 for gettime +#define WIIU_FSTIME_EPOCH_YEAR (1980) + +#define EPOCH_YEAR (1970) +#define EPOCH_YEARS_SINCE_LEAP 2 +#define EPOCH_YEARS_SINCE_CENTURY 70 +#define EPOCH_YEARS_SINCE_LEAP_CENTURY 370 + +#define EPOCH_DIFF_YEARS(year) (year - EPOCH_YEAR) +#define EPOCH_DIFF_DAYS(year) \ + ((EPOCH_DIFF_YEARS(year) * 365) + \ + (EPOCH_DIFF_YEARS(year) - 1 + EPOCH_YEARS_SINCE_LEAP) / 4 - \ + (EPOCH_DIFF_YEARS(year) - 1 + EPOCH_YEARS_SINCE_CENTURY) / 100 + \ + (EPOCH_DIFF_YEARS(year) - 1 + EPOCH_YEARS_SINCE_LEAP_CENTURY) / 400) +#define EPOCH_DIFF_SECS(year) (60ull * 60ull * 24ull * (uint64_t) EPOCH_DIFF_DAYS(year)) + +time_t __fsa_translate_time(FSTime timeValue) { + return (timeValue / 1000000) + EPOCH_DIFF_SECS(WIIU_FSTIME_EPOCH_YEAR); +} + +FSMode __fsa_translate_permission_mode(mode_t mode) { + // Convert normal Unix octal permission bits into CafeOS hexadecimal permission bits + return (FSMode) (((mode & S_IRWXU) << 2) | ((mode & S_IRWXG) << 1) | (mode & S_IRWXO)); +} + +int __fsa_translate_error(FSError error) { + switch (error) { + case FS_ERROR_END_OF_DIR: + case FS_ERROR_END_OF_FILE: + return ENOENT; + case FS_ERROR_ALREADY_EXISTS: + return EEXIST; + case FS_ERROR_MEDIA_ERROR: + return EIO; + case FS_ERROR_NOT_FOUND: + return ENOENT; + case FS_ERROR_PERMISSION_ERROR: + return EPERM; + case FS_ERROR_STORAGE_FULL: + return ENOSPC; + case FS_ERROR_BUSY: + return EBUSY; + case FS_ERROR_CANCELLED: + return ECANCELED; + case FS_ERROR_FILE_TOO_BIG: + return EFBIG; + case FS_ERROR_INVALID_PATH: + return ENAMETOOLONG; + case FS_ERROR_NOT_DIR: + return ENOTDIR; + case FS_ERROR_NOT_FILE: + return EISDIR; + case FS_ERROR_OUT_OF_RANGE: + return ESPIPE; + case FS_ERROR_UNSUPPORTED_COMMAND: + return ENOTSUP; + case FS_ERROR_WRITE_PROTECTED: + return EROFS; + case FS_ERROR_NOT_INIT: + return ENODEV; + // TODO + case FS_ERROR_MAX_MOUNT_POINTS: + break; + case FS_ERROR_MAX_VOLUMES: + break; + case FS_ERROR_MAX_CLIENTS: + break; + case FS_ERROR_MAX_FILES: + break; + case FS_ERROR_MAX_DIRS: + break; + case FS_ERROR_ALREADY_OPEN: + break; + case FS_ERROR_NOT_EMPTY: + break; + case FS_ERROR_ACCESS_ERROR: + break; + case FS_ERROR_DATA_CORRUPTED: + break; + case FS_ERROR_JOURNAL_FULL: + break; + case FS_ERROR_UNAVAILABLE_COMMAND: + break; + case FS_ERROR_INVALID_PARAM: + break; + case FS_ERROR_INVALID_BUFFER: + break; + case FS_ERROR_INVALID_ALIGNMENT: + break; + case FS_ERROR_INVALID_CLIENTHANDLE: + break; + case FS_ERROR_INVALID_FILEHANDLE: + break; + case FS_ERROR_INVALID_DIRHANDLE: + break; + case FS_ERROR_OUT_OF_RESOURCES: + break; + case FS_ERROR_MEDIA_NOT_READY: + break; + case FS_ERROR_INVALID_MEDIA: + break; + default: + break; + } + return (int) EIO; +} diff --git a/source/devoptab/devoptab_fsa_utimes.cpp b/source/devoptab/devoptab_fsa_utimes.cpp new file mode 100644 index 0000000..7cfc7db --- /dev/null +++ b/source/devoptab/devoptab_fsa_utimes.cpp @@ -0,0 +1,8 @@ +#include "devoptab_fsa.h" + +int __fsa_utimes(struct _reent *r, + const char *filename, + const struct timeval times[2]) { + r->_errno = ENOSYS; + return -1; +} diff --git a/source/devoptab/devoptab_fsa_write.cpp b/source/devoptab/devoptab_fsa_write.cpp new file mode 100644 index 0000000..1fc1016 --- /dev/null +++ b/source/devoptab/devoptab_fsa_write.cpp @@ -0,0 +1,84 @@ +#include "devoptab_fsa.h" +#include "logger.h" +#include + +ssize_t __fsa_write(struct _reent *r, void *fd, const char *ptr, size_t len) { + FSError status; + + if (!fd || !ptr) { + r->_errno = EINVAL; + return -1; + } + + // Check that the file was opened with write access + auto *file = (__fsa_file_t *) fd; + if ((file->flags & O_ACCMODE) == O_RDONLY) { + r->_errno = EBADF; + return -1; + } + + // cache-aligned, cache-line-sized + __attribute__((aligned(0x40))) uint8_t alignedBuffer[0x40]; + + auto *deviceData = (FSADeviceData *) r->deviceData; + + std::lock_guard lock(file->mutex); + + // If O_APPEND is set, we always write to the end of the file. + // When writing we file->offset to the file size to keep in sync. + if (file->flags & O_APPEND) { + file->offset = file->appendOffset; + } + + size_t bytesWritten = 0; + while (bytesWritten < len) { + // only use input buffer if cache-aligned and write size is a multiple of cache line size + // otherwise write from alignedBuffer + uint8_t *tmp = (uint8_t *) ptr; + size_t size = len - bytesWritten; + + if ((uintptr_t) ptr & 0x3F) { + // write partial cache-line front-end + tmp = alignedBuffer; + size = MIN(size, 0x40 - ((uintptr_t) ptr & 0x3F)); + } else if (size < 0x40) { + // write partial cache-line back-end + tmp = alignedBuffer; + } else { + // write whole cache lines + size &= ~0x3F; + } + + // Limit each request to 256 KiB + if (size > 0x40000) { + size = 0x40000; + } + + if (tmp == alignedBuffer) { + memcpy(tmp, ptr, size); + } + + status = FSAWriteFile(deviceData->clientHandle, tmp, 1, size, file->fd, 0); + if (status < 0) { + DEBUG_FUNCTION_LINE_ERR("FSAWriteFile(0x%08X, 0x%08X, 1, 0x%08X, 0x%08X, 0) (%s) failed: %s", + deviceData->clientHandle, tmp, size, file->fd, file->fullPath, FSAGetStatusStr(status)); + if (bytesWritten != 0) { + return bytesWritten; // error after partial write + } + + r->_errno = __fsa_translate_error(status); + return -1; + } + + file->appendOffset += status; + file->offset += status; + bytesWritten += status; + ptr += status; + + if ((size_t) status != size) { + return bytesWritten; // partial write + } + } + + return bytesWritten; +} diff --git a/source/fsa.cpp b/source/fsa.cpp index f48afbe..ff84e30 100644 --- a/source/fsa.cpp +++ b/source/fsa.cpp @@ -1,4 +1,5 @@ #include "mocha/fsa.h" +#include "logger.h" #include "utils.h" #include #include @@ -103,10 +104,10 @@ FSError FSAEx_RawReadEx(int clientHandle, void *data, uint32_t size_bytes, uint3 if ((uint32_t) data & 0x3F) { auto *alignedBuffer = memalign(0x40, ROUNDUP(size_bytes * cnt, 0x40)); if (!alignedBuffer) { - OSReport("## ERROR: FSAEx_RawReadEx buffer not aligned (%08X).\n", data); + DEBUG_FUNCTION_LINE_ERR("Buffer not aligned (%08X).\n", data); return FS_ERROR_INVALID_ALIGNMENT; } - OSReport("## WARNING: FSAEx_RawReadEx buffer not aligned (%08X). Align to 0x40 for best performance\n", data); + DEBUG_FUNCTION_LINE_WARN("Buffer not aligned (%08X). Align to 0x40 for best performance\n", data); tmp = alignedBuffer; } @@ -161,10 +162,10 @@ FSError FSAEx_RawWriteEx(int clientHandle, const void *data, uint32_t size_bytes if ((uint32_t) data & 0x3F) { auto *alignedBuffer = memalign(0x40, ROUNDUP(size_bytes * cnt, 0x40)); if (!alignedBuffer) { - OSReport("## ERROR: FSAEx_RawWriteEx buffer not aligned (%08X).\n", data); + DEBUG_FUNCTION_LINE_ERR("Buffer not aligned (%08X).", data); return FS_ERROR_INVALID_ALIGNMENT; } - OSReport("## WARNING: FSAEx_RawWriteEx buffer not aligned (%08X). Align to 0x40 for best performance\n", data); + DEBUG_FUNCTION_LINE_WARN("Buffer not aligned (%08X). Align to 0x40 for best performance", data); tmp = alignedBuffer; memcpy(tmp, data, size_bytes * cnt); } diff --git a/source/utils.cpp b/source/utils.cpp index 11f0828..4b6df26 100644 --- a/source/utils.cpp +++ b/source/utils.cpp @@ -33,6 +33,10 @@ const char *Mocha_GetStatusStr(MochaUtilsStatus status) { return "MOCHA_RESULT_MAX_CLIENT"; case MOCHA_RESULT_OUT_OF_MEMORY: return "MOCHA_RESULT_OUT_OF_MEMORY"; + case MOCHA_RESULT_ALREADY_EXISTS: + return "MOCHA_RESULT_ALREADY_EXISTS"; + case MOCHA_RESULT_ADD_DEVOPTAB_FAILED: + return "MOCHA_RESULT_ADD_DEVOPTAB_FAILED"; case MOCHA_RESULT_NOT_FOUND: return "MOCHA_RESULT_NOT_FOUND"; case MOCHA_RESULT_UNSUPPORTED_API_VERSION: