mirror of
https://github.com/wiiu-env/libmocha.git
synced 2024-11-22 03:19:20 +01:00
Add devoptab support to replace the libioushax devoptab
This commit is contained in:
parent
6019c6947a
commit
2ac50e4392
@ -1,4 +1,4 @@
|
|||||||
FROM wiiuenv/devkitppc:20220710
|
FROM wiiuenv/devkitppc:20220806
|
||||||
|
|
||||||
WORKDIR tmp_build
|
WORKDIR tmp_build
|
||||||
COPY . .
|
COPY . .
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
FROM wiiuenv/devkitppc:20220710
|
FROM wiiuenv/devkitppc:20220806
|
||||||
|
|
||||||
WORKDIR project
|
WORKDIR project
|
5
Makefile
5
Makefile
@ -25,7 +25,8 @@ VERSION := $(VER_MAJOR).$(VER_MINOR).$(VER_PATCH)
|
|||||||
#-------------------------------------------------------------------------------
|
#-------------------------------------------------------------------------------
|
||||||
TARGET := $(notdir $(CURDIR))
|
TARGET := $(notdir $(CURDIR))
|
||||||
BUILD := build
|
BUILD := build
|
||||||
SOURCES := source
|
SOURCES := source \
|
||||||
|
source/devoptab
|
||||||
DATA := data
|
DATA := data
|
||||||
INCLUDES := source \
|
INCLUDES := source \
|
||||||
include \
|
include \
|
||||||
@ -38,7 +39,7 @@ CFLAGS := -Wall -Werror -save-temps \
|
|||||||
$(MACHDEP) \
|
$(MACHDEP) \
|
||||||
$(BUILD_CFLAGS)
|
$(BUILD_CFLAGS)
|
||||||
|
|
||||||
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__
|
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -fno-exceptions
|
||||||
|
|
||||||
CXXFLAGS := $(CFLAGS) -std=gnu++20
|
CXXFLAGS := $(CFLAGS) -std=gnu++20
|
||||||
|
|
||||||
|
@ -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)
|
[![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
|
# libmocha
|
||||||
Requires the [MochaPayload](https://github.com/wiiu-env/MochaPayload) to be running via [EnvironmentLoader](https://github.com/wiiu-env/EnvironmentLoader).
|
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.
|
Requires [wut](https://github.com/devkitPro/wut) for building.
|
||||||
|
@ -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
|
* @param outHandle pointer where the handle of the raw device will be stored
|
||||||
* @return
|
* @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
|
* 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
|
* @param device_handle device handle
|
||||||
* @return
|
* @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.
|
* 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.
|
* @param device_handle valid device handle.
|
||||||
* @return
|
* @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.
|
* @param device_handle valid device handle.
|
||||||
* @return
|
* @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
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include "commands.h"
|
||||||
|
#include "otp.h"
|
||||||
#include <coreinit/filesystem.h>
|
#include <coreinit/filesystem.h>
|
||||||
#include <mocha/commands.h>
|
#include <coreinit/filesystem_fsa.h>
|
||||||
#include <mocha/otp.h>
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
@ -13,6 +14,8 @@ typedef enum MochaUtilsStatus {
|
|||||||
MOCHA_RESULT_INVALID_ARGUMENT = -0x01,
|
MOCHA_RESULT_INVALID_ARGUMENT = -0x01,
|
||||||
MOCHA_RESULT_MAX_CLIENT = -0x02,
|
MOCHA_RESULT_MAX_CLIENT = -0x02,
|
||||||
MOCHA_RESULT_OUT_OF_MEMORY = -0x03,
|
MOCHA_RESULT_OUT_OF_MEMORY = -0x03,
|
||||||
|
MOCHA_RESULT_ALREADY_EXISTS = -0x04,
|
||||||
|
MOCHA_RESULT_ADD_DEVOPTAB_FAILED = -0x05,
|
||||||
MOCHA_RESULT_NOT_FOUND = -0x06,
|
MOCHA_RESULT_NOT_FOUND = -0x06,
|
||||||
MOCHA_RESULT_UNSUPPORTED_API_VERSION = -0x10,
|
MOCHA_RESULT_UNSUPPORTED_API_VERSION = -0x10,
|
||||||
MOCHA_RESULT_UNSUPPORTED_COMMAND = -0x11,
|
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);
|
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
|
#ifdef __cplusplus
|
||||||
} // extern "C"
|
} // extern "C"
|
||||||
#endif
|
#endif
|
25
source/devoptab/MutexWrapper.h
Normal file
25
source/devoptab/MutexWrapper.h
Normal 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{};
|
||||||
|
};
|
203
source/devoptab/devoptab_fsa.cpp
Normal file
203
source/devoptab/devoptab_fsa.cpp
Normal 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;
|
||||||
|
}
|
118
source/devoptab/devoptab_fsa.h
Normal file
118
source/devoptab/devoptab_fsa.h
Normal 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
|
31
source/devoptab/devoptab_fsa_chdir.cpp
Normal file
31
source/devoptab/devoptab_fsa_chdir.cpp
Normal 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;
|
||||||
|
}
|
36
source/devoptab/devoptab_fsa_chmod.cpp
Normal file
36
source/devoptab/devoptab_fsa_chmod.cpp
Normal 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;
|
||||||
|
}
|
29
source/devoptab/devoptab_fsa_close.cpp
Normal file
29
source/devoptab/devoptab_fsa_close.cpp
Normal 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;
|
||||||
|
}
|
28
source/devoptab/devoptab_fsa_dirclose.cpp
Normal file
28
source/devoptab/devoptab_fsa_dirclose.cpp
Normal 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;
|
||||||
|
}
|
38
source/devoptab/devoptab_fsa_dirnext.cpp
Normal file
38
source/devoptab/devoptab_fsa_dirnext.cpp
Normal 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;
|
||||||
|
}
|
41
source/devoptab/devoptab_fsa_diropen.cpp
Normal file
41
source/devoptab/devoptab_fsa_diropen.cpp
Normal 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;
|
||||||
|
}
|
28
source/devoptab/devoptab_fsa_dirreset.cpp
Normal file
28
source/devoptab/devoptab_fsa_dirreset.cpp
Normal 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;
|
||||||
|
}
|
11
source/devoptab/devoptab_fsa_fchmod.cpp
Normal file
11
source/devoptab/devoptab_fsa_fchmod.cpp
Normal 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;
|
||||||
|
}
|
32
source/devoptab/devoptab_fsa_fstat.cpp
Normal file
32
source/devoptab/devoptab_fsa_fstat.cpp
Normal 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;
|
||||||
|
}
|
29
source/devoptab/devoptab_fsa_fsync.cpp
Normal file
29
source/devoptab/devoptab_fsa_fsync.cpp
Normal 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;
|
||||||
|
}
|
8
source/devoptab/devoptab_fsa_link.cpp
Normal file
8
source/devoptab/devoptab_fsa_link.cpp
Normal 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;
|
||||||
|
}
|
37
source/devoptab/devoptab_fsa_mkdir.cpp
Normal file
37
source/devoptab/devoptab_fsa_mkdir.cpp
Normal 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;
|
||||||
|
}
|
144
source/devoptab/devoptab_fsa_open.cpp
Normal file
144
source/devoptab/devoptab_fsa_open.cpp
Normal 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;
|
||||||
|
}
|
79
source/devoptab/devoptab_fsa_read.cpp
Normal file
79
source/devoptab/devoptab_fsa_read.cpp
Normal 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;
|
||||||
|
}
|
44
source/devoptab/devoptab_fsa_rename.cpp
Normal file
44
source/devoptab/devoptab_fsa_rename.cpp
Normal 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;
|
||||||
|
}
|
34
source/devoptab/devoptab_fsa_rmdir.cpp
Normal file
34
source/devoptab/devoptab_fsa_rmdir.cpp
Normal 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;
|
||||||
|
}
|
78
source/devoptab/devoptab_fsa_seek.cpp
Normal file
78
source/devoptab/devoptab_fsa_seek.cpp
Normal 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;
|
||||||
|
}
|
36
source/devoptab/devoptab_fsa_stat.cpp
Normal file
36
source/devoptab/devoptab_fsa_stat.cpp
Normal 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;
|
||||||
|
}
|
51
source/devoptab/devoptab_fsa_statvfs.cpp
Normal file
51
source/devoptab/devoptab_fsa_statvfs.cpp
Normal 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;
|
||||||
|
}
|
40
source/devoptab/devoptab_fsa_truncate.cpp
Normal file
40
source/devoptab/devoptab_fsa_truncate.cpp
Normal 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;
|
||||||
|
}
|
34
source/devoptab/devoptab_fsa_unlink.cpp
Normal file
34
source/devoptab/devoptab_fsa_unlink.cpp
Normal 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;
|
||||||
|
}
|
194
source/devoptab/devoptab_fsa_utils.cpp
Normal file
194
source/devoptab/devoptab_fsa_utils.cpp
Normal 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;
|
||||||
|
}
|
8
source/devoptab/devoptab_fsa_utimes.cpp
Normal file
8
source/devoptab/devoptab_fsa_utimes.cpp
Normal 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;
|
||||||
|
}
|
84
source/devoptab/devoptab_fsa_write.cpp
Normal file
84
source/devoptab/devoptab_fsa_write.cpp
Normal 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;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
#include "mocha/fsa.h"
|
#include "mocha/fsa.h"
|
||||||
|
#include "logger.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
#include <coreinit/debug.h>
|
#include <coreinit/debug.h>
|
||||||
#include <coreinit/filesystem.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) {
|
if ((uint32_t) data & 0x3F) {
|
||||||
auto *alignedBuffer = memalign(0x40, ROUNDUP(size_bytes * cnt, 0x40));
|
auto *alignedBuffer = memalign(0x40, ROUNDUP(size_bytes * cnt, 0x40));
|
||||||
if (!alignedBuffer) {
|
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;
|
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;
|
tmp = alignedBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,10 +162,10 @@ FSError FSAEx_RawWriteEx(int clientHandle, const void *data, uint32_t size_bytes
|
|||||||
if ((uint32_t) data & 0x3F) {
|
if ((uint32_t) data & 0x3F) {
|
||||||
auto *alignedBuffer = memalign(0x40, ROUNDUP(size_bytes * cnt, 0x40));
|
auto *alignedBuffer = memalign(0x40, ROUNDUP(size_bytes * cnt, 0x40));
|
||||||
if (!alignedBuffer) {
|
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;
|
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;
|
tmp = alignedBuffer;
|
||||||
memcpy(tmp, data, size_bytes * cnt);
|
memcpy(tmp, data, size_bytes * cnt);
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,10 @@ const char *Mocha_GetStatusStr(MochaUtilsStatus status) {
|
|||||||
return "MOCHA_RESULT_MAX_CLIENT";
|
return "MOCHA_RESULT_MAX_CLIENT";
|
||||||
case MOCHA_RESULT_OUT_OF_MEMORY:
|
case MOCHA_RESULT_OUT_OF_MEMORY:
|
||||||
return "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:
|
case MOCHA_RESULT_NOT_FOUND:
|
||||||
return "MOCHA_RESULT_NOT_FOUND";
|
return "MOCHA_RESULT_NOT_FOUND";
|
||||||
case MOCHA_RESULT_UNSUPPORTED_API_VERSION:
|
case MOCHA_RESULT_UNSUPPORTED_API_VERSION:
|
||||||
|
Loading…
Reference in New Issue
Block a user