Add devoptab support to replace the libioushax devoptab

This commit is contained in:
Maschell 2022-07-22 14:46:29 +02:00
parent 6019c6947a
commit 2ac50e4392
35 changed files with 1572 additions and 14 deletions

View File

@ -1,4 +1,4 @@
FROM wiiuenv/devkitppc:20220710
FROM wiiuenv/devkitppc:20220806
WORKDIR tmp_build
COPY . .

View File

@ -1,3 +1,3 @@
FROM wiiuenv/devkitppc:20220710
FROM wiiuenv/devkitppc:20220806
WORKDIR project

View File

@ -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

View File

@ -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.

View File

@ -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"

View File

@ -1,7 +1,8 @@
#pragma once
#include "commands.h"
#include "otp.h"
#include <coreinit/filesystem.h>
#include <mocha/commands.h>
#include <mocha/otp.h>
#include <coreinit/filesystem_fsa.h>
#include <stdint.h>
#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 <br>
* MOCHA_RESULT_MAX_CLIENT: The maximum number of FSAClients have been registered.<br>
* MOCHA_RESULT_LIB_UNINITIALIZED: Library was not initialized. Call Mocha_InitLibrary() before using this function.<br>
* MOCHA_RESULT_UNSUPPORTED_COMMAND: Command not supported by the currently loaded mocha version.<br>
* 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 <br>
* MOCHA_RESULT_INVALID_ARGUMENT: <br>
* 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

View File

@ -0,0 +1,25 @@
#pragma once
#include "coreinit/cache.h"
#include <coreinit/mutex.h>
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{};
};

View File

@ -0,0 +1,203 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include "mocha/mocha.h"
#include <coreinit/cache.h>
#include <coreinit/filesystem_fsa.h>
#include <mutex>
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<std::mutex> 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<std::mutex> 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<FSError>(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;
}

View File

@ -0,0 +1,118 @@
#pragma once
#include "MutexWrapper.h"
#include <cerrno>
#include <climits>
#include <coreinit/filesystem_fsa.h>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <malloc.h>
#include <sys/dirent.h>
#include <sys/iosupport.h>
#include <sys/param.h>
#include <unistd.h>
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

View File

@ -0,0 +1,31 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -0,0 +1,36 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
#include <sys/stat.h>
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;
}

View File

@ -0,0 +1,29 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,28 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,38 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,41 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,28 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,11 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -0,0 +1,32 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,29 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -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;
}

View File

@ -0,0 +1,37 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -0,0 +1,144 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
// 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<MutexWrapper> 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;
}

View File

@ -0,0 +1,79 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
#include <sys/param.h>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,44 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -0,0 +1,34 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -0,0 +1,78 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,36 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -0,0 +1,51 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -0,0 +1,40 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -0,0 +1,34 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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;
}

View File

@ -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<char *>(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;
}

View File

@ -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;
}

View File

@ -0,0 +1,84 @@
#include "devoptab_fsa.h"
#include "logger.h"
#include <mutex>
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<MutexWrapper> 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;
}

View File

@ -1,4 +1,5 @@
#include "mocha/fsa.h"
#include "logger.h"
#include "utils.h"
#include <coreinit/debug.h>
#include <coreinit/filesystem.h>
@ -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);
}

View File

@ -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: