libnx/nx/source/devices/fs_dev.c

1437 lines
32 KiB
C

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/dirent.h>
#include <sys/iosupport.h>
#include <sys/param.h>
#include <unistd.h>
#include <switch.h>
/*! @internal
*
* @file fsdev_dev.c
*
* FS Device
*/
static int fsdev_translate_error(Result error);
static int fsdev_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode);
static int fsdev_close(struct _reent *r, void *fd);
static ssize_t fsdev_write(struct _reent *r, void *fd, const char *ptr, size_t len);
static ssize_t fsdev_write_safe(struct _reent *r, void *fd, const char *ptr, size_t len);
static ssize_t fsdev_read(struct _reent *r, void *fd, char *ptr, size_t len);
static off_t fsdev_seek(struct _reent *r, void *fd, off_t pos, int dir);
static int fsdev_fstat(struct _reent *r, void *fd, struct stat *st);
static int fsdev_stat(struct _reent *r, const char *file, struct stat *st);
static int fsdev_link(struct _reent *r, const char *existing, const char *newLink);
static int fsdev_unlink(struct _reent *r, const char *name);
static int fsdev_chdir(struct _reent *r, const char *name);
static int fsdev_rename(struct _reent *r, const char *oldName, const char *newName);
static int fsdev_mkdir(struct _reent *r, const char *path, int mode);
static DIR_ITER* fsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path);
static int fsdev_dirreset(struct _reent *r, DIR_ITER *dirState);
static int fsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat);
static int fsdev_dirclose(struct _reent *r, DIR_ITER *dirState);
static int fsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf);
static int fsdev_ftruncate(struct _reent *r, void *fd, off_t len);
static int fsdev_fsync(struct _reent *r, void *fd);
static int fsdev_chmod(struct _reent *r, const char *path, mode_t mode);
static int fsdev_fchmod(struct _reent *r, void *fd, mode_t mode);
static int fsdev_rmdir(struct _reent *r, const char *name);
/*! @cond INTERNAL */
/*! Open file struct */
typedef struct
{
FsFile fd;
int flags; /*! Flags used in open(2) */
u64 offset; /*! Current file offset */
} fsdev_file_t;
#define FSDEV_DIRITER_MAGIC 0x66736476 /* "fsdv" */
/*! Open directory struct */
typedef struct
{
u32 magic; /*! "fsdv" */
FsDir fd;
ssize_t index; /*! Current entry index */
size_t size; /*! Current batch size */
FsDirectoryEntry entry_data[32]; /*! Temporary storage for reading entries */
} fsdev_dir_t;
/*! fsdev devoptab */
static devoptab_t
fsdev_devoptab =
{
.structSize = sizeof(fsdev_file_t),
.open_r = fsdev_open,
.close_r = fsdev_close,
.write_r = fsdev_write,
.read_r = fsdev_read,
.seek_r = fsdev_seek,
.fstat_r = fsdev_fstat,
.stat_r = fsdev_stat,
.link_r = fsdev_link,
.unlink_r = fsdev_unlink,
.chdir_r = fsdev_chdir,
.rename_r = fsdev_rename,
.mkdir_r = fsdev_mkdir,
.dirStateSize = sizeof(fsdev_dir_t),
.diropen_r = fsdev_diropen,
.dirreset_r = fsdev_dirreset,
.dirnext_r = fsdev_dirnext,
.dirclose_r = fsdev_dirclose,
.statvfs_r = fsdev_statvfs,
.ftruncate_r = fsdev_ftruncate,
.fsync_r = fsdev_fsync,
.deviceData = 0,
.chmod_r = fsdev_chmod,
.fchmod_r = fsdev_fchmod,
.rmdir_r = fsdev_rmdir,
};
typedef struct
{
bool setup;
devoptab_t device;
FsFileSystem fs;
char name[32];
} fsdev_fsdevice;
static fsdev_fsdevice fsdev_fsdevices[32];
/*! @endcond */
static char __cwd[PATH_MAX+1] = "/";
static __thread char __fixedpath[PATH_MAX+1];
//static __thread uint16_t __utf16path[PATH_MAX+1];
static fsdev_fsdevice *fsdevFindDevice(const char *name)
{
u32 i;
u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice);
fsdev_fsdevice *device = NULL;
for(i=0; i<total; i++)
{
device = &fsdev_fsdevices[i];
if(name==NULL) //Find an unused device entry.
{
if(!device->setup)
return device;
}
else if(device->setup) //Find the device with the input name.
{
if(strncmp(device->name, name, strlen(device->name))==0)
return device;
}
}
return NULL;
}
static const char*
fsdev_fixpath(struct _reent *r,
const char *path,
fsdev_fsdevice **device)
{
ssize_t units;
uint32_t code;
const uint8_t *p = (const uint8_t*)path;
if(device)
*device = fsdevFindDevice(path);
// Move the path pointer to the start of the actual path
do
{
units = decode_utf8(&code, p);
if(units < 0)
{
r->_errno = EILSEQ;
return NULL;
}
p += units;
} while(code != ':' && code != 0);
// We found a colon; p points to the actual path
if(code == ':')
path = (const char*)p;
// Make sure there are no more colons and that the
// remainder of the filename is valid UTF-8
p = (const uint8_t*)path;
do
{
units = decode_utf8(&code, p);
if(units < 0)
{
r->_errno = EILSEQ;
return NULL;
}
if(code == ':')
{
r->_errno = EINVAL;
return NULL;
}
p += units;
} while(code != 0);
if(path[0] == '/')
strncpy(__fixedpath, path, PATH_MAX);
else
{
strncpy(__fixedpath, __cwd, PATH_MAX);
strncat(__fixedpath, path, PATH_MAX);
}
if(__fixedpath[PATH_MAX] != 0)
{
__fixedpath[PATH_MAX] = 0;
r->_errno = ENAMETOOLONG;
return NULL;
}
return __fixedpath;
}
static int
fsdev_getfspath(struct _reent *r,
const char *path,
fsdev_fsdevice **device,
char *outpath)
{
//ssize_t units;
if(fsdev_fixpath(r, path, device) == NULL)
return -1;
//TODO: What encoding does FS paths use?
/*units = utf8_to_utf16(__utf16path, (const uint8_t*)__fixedpath, PATH_MAX);
if(units < 0)
{
r->_errno = EILSEQ;
return fspath;
}
if(units >= PATH_MAX)
{
r->_errno = ENAMETOOLONG;
return fspath;
}
__utf16path[units] = 0;*/
memset(outpath, 0, FS_MAX_PATH);
strncpy(outpath, __fixedpath, FS_MAX_PATH);
return 0;
}
static ssize_t fsdev_convertfromfspath(uint8_t *out, uint8_t *in, size_t len)
{
//TODO: What encoding does FS paths use?
strncpy((char*)out, (char*)in, len);
return strnlen((char*)out, len);
}
extern int __system_argc;
extern char** __system_argv;
static bool fsdevInitialised = false;
static void fsdevUpdateDevices(void)
{
u32 i;
u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice);
for(i=0; i<total; i++)
{
memcpy(&fsdev_fsdevices[i].device, &fsdev_devoptab, sizeof(fsdev_devoptab));
fsdev_fsdevices[i].device.name = fsdev_fsdevices[i].name;
}
}
int fsdevMountDevice(const char *name, FsFileSystem fs)
{
fsdev_fsdevice *device = fsdevFindDevice(NULL);
if(device==NULL)
{
fsFsClose(&fs);
return -1;
}
device->fs = fs;
memset(device->name, 0, sizeof(device->name));
strncpy(device->name, name, sizeof(device->name)-1);
int dev = AddDevice(&device->device);
if(dev==-1)
{
fsFsClose(&device->fs);
return dev;
}
device->setup = 1;
return dev;
}
static int _fsdevUnmountDeviceStruct(fsdev_fsdevice *device)
{
if(!device->setup)
return 0;
RemoveDevice(device->name);
fsFsClose(&device->fs);
memset(device, 0, sizeof(fsdev_fsdevice));
return 0;
}
int fsdevUnmountDevice(const char *name)
{
fsdev_fsdevice *device;
device = fsdevFindDevice(name);
if(device==NULL)
return -1;
return _fsdevUnmountDeviceStruct(device);
}
/*! Initialize SDMC device */
Result fsdevInit(void)
{
/*ssize_t units;
uint32_t code;
char *p;*/
Result rc = 0;
FsFileSystem fs;
if(fsdevInitialised)
return rc;
memset(fsdev_fsdevices, 0, sizeof(fsdev_fsdevices));
fsdevUpdateDevices();
rc = fsMountSdcard(&fs);
if(R_SUCCEEDED(rc))
{
int dev = fsdevMountDevice("sdmc", fs);
if(dev != -1)
{
setDefaultDevice(dev);
//TODO: Re-enable this once __system_argc/__system_argv are actually defined.
/*if(__system_argc != 0 && __system_argv[0] != NULL)
{
if(FindDevice(__system_argv[0]) == dev)
{
strncpy(__fixedpath,__system_argv[0],PATH_MAX);
if(__fixedpath[PATH_MAX] != 0)
{
__fixedpath[PATH_MAX] = 0;
}
else
{
char *last_slash = NULL;
p = __fixedpath;
do
{
units = decode_utf8(&code, (const uint8_t*)p);
if(units < 0)
{
last_slash = NULL;
break;
}
if(code == '/')
last_slash = p;
p += units;
} while(code != 0);
if(last_slash != NULL)
{
last_slash[0] = 0;
chdir(__fixedpath);
}
}
}
}*/
}
}
fsdevInitialised = true;
return rc;
}
/*! Enable/disable safe fsdev_write
*
* Safe fsdev_write is enabled by default. If it is disabled, you will be
* unable to write from read-only buffers.
*
* @param[in] enable Whether to enable
*/
void fsdevWriteSafe(bool enable)
{
if(enable)
fsdev_devoptab.write_r = fsdev_write_safe;
else
fsdev_devoptab.write_r = fsdev_write;
fsdevUpdateDevices();
}
/*! Clean up fsdev devices */
Result fsdevExit(void)
{
u32 i;
u32 total = sizeof(fsdev_fsdevices) / sizeof(fsdev_fsdevice);
Result rc=0;
if(!fsdevInitialised) return rc;
for(i=0; i<total; i++)
{
_fsdevUnmountDeviceStruct(&fsdev_fsdevices[i]);
}
fsdevInitialised = false;
return 0;
}
/*! Open a file
*
* @param[in,out] r newlib reentrancy struct
* @param[out] fileStruct Pointer to file struct to fill in
* @param[in] path Path to open
* @param[in] flags Open flags from open(2)
* @param[in] mode Permissions to set on create
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_open(struct _reent *r,
void *fileStruct,
const char *path,
int flags,
int mode)
{
FsFile fd;
Result rc;
u32 fsdev_flags = 0;
u32 attributes = 0;
char fs_path[FS_MAX_PATH];
fsdev_fsdevice *device = NULL;
return -1;
if(fsdev_getfspath(r, path, &device, fs_path)==-1)
return -1;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fileStruct;
/* check access mode */
switch(flags & O_ACCMODE)
{
/* read-only: do not allow O_APPEND */
case O_RDONLY:
fsdev_flags |= FS_OPEN_READ;
if(flags & O_APPEND)
{
r->_errno = EINVAL;
return -1;
}
break;
/* write-only */
case O_WRONLY:
fsdev_flags |= FS_OPEN_WRITE;
break;
/* read and write */
case O_RDWR:
fsdev_flags |= (FS_OPEN_READ | FS_OPEN_WRITE);
break;
/* an invalid option was supplied */
default:
r->_errno = EINVAL;
return -1;
}
/* Test O_EXCL. */
if((flags & O_CREAT) && (flags & O_EXCL))
{
rc = fsFsCreateFile(&device->fs, fs_path, 0, attributes);
if(R_FAILED(rc))
{
r->_errno = fsdev_translate_error(rc);
return -1;
}
}
/* set attributes */
/*if(!(mode & S_IWUSR))
attributes |= FS_ATTRIBUTE_READONLY;*/
/* open the file */
rc = fsFsOpenFile(&device->fs, fs_path, fsdev_flags, &fd);
if(R_SUCCEEDED(rc))
{
if((flags & O_ACCMODE) != O_RDONLY && (flags & O_TRUNC))
{
rc = fsFileSetSize(&fd, 0);
if(R_FAILED(rc))
{
fsFileClose(&fd);
r->_errno = fsdev_translate_error(rc);
return -1;
}
}
file->fd = fd;
file->flags = (flags & (O_ACCMODE|O_APPEND|O_SYNC));
file->offset = 0;
return 0;
}
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Close an open file
*
* @param[in,out] r newlib reentrancy struct
* @param[in] fd Pointer to fsdev_file_t
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_close(struct _reent *r,
void *fd)
{
Result rc=0;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fd;
fsFileClose(&file->fd);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Write to an open file
*
* @param[in,out] r newlib reentrancy struct
* @param[in,out] fd Pointer to fsdev_file_t
* @param[in] ptr Pointer to data to write
* @param[in] len Length of data to write
*
* @returns number of bytes written
* @returns -1 for error
*/
static ssize_t
fsdev_write(struct _reent *r,
void *fd,
const char *ptr,
size_t len)
{
Result rc;
size_t bytes;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fd;
/* check that the file was opened with write access */
if((file->flags & O_ACCMODE) == O_RDONLY)
{
r->_errno = EBADF;
return -1;
}
if(file->flags & O_APPEND)
{
/* append means write from the end of the file */
rc = fsFileGetSize(&file->fd, &file->offset);
if(R_FAILED(rc))
{
r->_errno = fsdev_translate_error(rc);
return -1;
}
}
rc = fsFileWrite(&file->fd, file->offset, ptr, len, &bytes);
if(R_FAILED(rc))
{
r->_errno = fsdev_translate_error(rc);
return -1;
}
file->offset += bytes;
/* check if this is synchronous or not */
if(file->flags & O_SYNC)
fsFileFlush(&file->fd);
return bytes;
}
/*! Write to an open file
*
* @param[in,out] r newlib reentrancy struct
* @param[in,out] fd Pointer to fsdev_file_t
* @param[in] ptr Pointer to data to write
* @param[in] len Length of data to write
*
* @returns number of bytes written
* @returns -1 for error
*/
static ssize_t
fsdev_write_safe(struct _reent *r,
void *fd,
const char *ptr,
size_t len)
{
Result rc;
size_t bytes, bytesWritten = 0;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fd;
/* check that the file was opened with write access */
if((file->flags & O_ACCMODE) == O_RDONLY)
{
r->_errno = EBADF;
return -1;
}
if(file->flags & O_APPEND)
{
/* append means write from the end of the file */
rc = fsFileGetSize(&file->fd, &file->offset);
if(R_FAILED(rc))
{
r->_errno = fsdev_translate_error(rc);
return -1;
}
}
/* Copy to internal buffer and write in chunks.
* You cannot write from read-only memory.
*/
static __thread char tmp_buffer[8192];
while(len > 0)
{
size_t toWrite = len;
if(toWrite > sizeof(tmp_buffer))
toWrite = sizeof(tmp_buffer);
/* copy to internal buffer */
memcpy(tmp_buffer, ptr, toWrite);
/* write the data */
rc = fsFileWrite(&file->fd, file->offset, tmp_buffer, toWrite, &bytes);
if(R_FAILED(rc))
{
/* return partial transfer */
if(bytesWritten > 0)
return bytesWritten;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/* check if this is synchronous or not */
if(file->flags & O_SYNC)
fsFileFlush(&file->fd);
file->offset += bytes;
bytesWritten += bytes;
ptr += bytes;
len -= bytes;
}
return bytesWritten;
}
/*! Read from an open file
*
* @param[in,out] r newlib reentrancy struct
* @param[in,out] fd Pointer to fsdev_file_t
* @param[out] ptr Pointer to buffer to read into
* @param[in] len Length of data to read
*
* @returns number of bytes read
* @returns -1 for error
*/
static ssize_t
fsdev_read(struct _reent *r,
void *fd,
char *ptr,
size_t len)
{
Result rc;
size_t bytes;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fd;
/* check that the file was opened with read access */
if((file->flags & O_ACCMODE) == O_WRONLY)
{
r->_errno = EBADF;
return -1;
}
/* read the data */
rc = fsFileRead(&file->fd, file->offset, ptr, len, &bytes);
if(R_SUCCEEDED(rc))
{
/* update current file offset */
file->offset += bytes;
return (ssize_t)bytes;
}
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Update an open file's current offset
*
* @param[in,out] r newlib reentrancy struct
* @param[in,out] fd Pointer to fsdev_file_t
* @param[in] pos Offset to seek to
* @param[in] whence Where to seek from
*
* @returns new offset for success
* @returns -1 for error
*/
static off_t
fsdev_seek(struct _reent *r,
void *fd,
off_t pos,
int whence)
{
Result rc;
u64 offset;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fd;
/* find the offset to see from */
switch(whence)
{
/* set absolute position; start offset is 0 */
case SEEK_SET:
offset = 0;
break;
/* set position relative to the current position */
case SEEK_CUR:
offset = file->offset;
break;
/* set position relative to the end of the file */
case SEEK_END:
rc = fsFileGetSize(&file->fd, &offset);
if(R_FAILED(rc))
{
r->_errno = fsdev_translate_error(rc);
return -1;
}
break;
/* an invalid option was provided */
default:
r->_errno = EINVAL;
return -1;
}
/* TODO: A better check that prevents overflow. */
if(pos < 0 && offset < -pos)
{
/* don't allow seek to before the beginning of the file */
r->_errno = EINVAL;
return -1;
}
/* update the current offset */
file->offset = offset + pos;
return file->offset;
}
/*! Get file stats from an open file
*
* @param[in,out] r newlib reentrancy struct
* @param[in] fd Pointer to fsdev_file_t
* @param[out] st Pointer to file stats to fill
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_fstat(struct _reent *r,
void *fd,
struct stat *st)
{
Result rc;
u64 size;
fsdev_file_t *file = (fsdev_file_t*)fd;
rc = fsFileGetSize(&file->fd, &size);
if(R_SUCCEEDED(rc))
{
memset(st, 0, sizeof(struct stat));
st->st_size = (off_t)size;
st->st_nlink = 1;
st->st_mode = S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
return 0;
}
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Get file stats
*
* @param[in,out] r newlib reentrancy struct
* @param[in] file Path to file
* @param[out] st Pointer to file stats to fill
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_stat(struct _reent *r,
const char *file,
struct stat *st)
{
FsFile fd;
FsDir fdir;
Result rc;
char fs_path[FS_MAX_PATH];
fsdev_fsdevice *device = NULL;
if(fsdev_getfspath(r, file, &device, fs_path)==-1)
return -1;
if(R_SUCCEEDED(rc = fsFsOpenFile(&device->fs, fs_path, FS_OPEN_READ, &fd)))
{
fsdev_file_t tmpfd = { .fd = fd };
rc = fsdev_fstat(r, &tmpfd, st);
fsFileClose(&fd);
return rc;
}
else if(R_SUCCEEDED(rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fdir)))
{
memset(st, 0, sizeof(struct stat));
st->st_nlink = 1;
st->st_mode = S_IFDIR | S_IRWXU | S_IRWXG | S_IRWXO;
fsDirClose(&fdir);
return 0;
}
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Hard link a file
*
* @param[in,out] r newlib reentrancy struct
* @param[in] existing Path of file to link
* @param[in] newLink Path of new link
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_link(struct _reent *r,
const char *existing,
const char *newLink)
{
r->_errno = ENOSYS;
return -1;
}
/*! Unlink a file
*
* @param[in,out] r newlib reentrancy struct
* @param[in] name Path of file to unlink
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_unlink(struct _reent *r,
const char *name)
{
Result rc;
char fs_path[FS_MAX_PATH];
fsdev_fsdevice *device = NULL;
if(fsdev_getfspath(r, name, &device, fs_path)==-1)
return -1;
rc = fsFsDeleteFile(&device->fs, fs_path);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Change current working directory
*
* @param[in,out] r newlib reentrancy struct
* @param[in] name Path to new working directory
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_chdir(struct _reent *r,
const char *name)
{
FsDir fd;
Result rc;
char fs_path[FS_MAX_PATH];
fsdev_fsdevice *device = NULL;
if(fsdev_getfspath(r, name, &device, fs_path)==-1)
return -1;
rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fd);
if(R_SUCCEEDED(rc))
{
fsDirClose(&fd);
strncpy(__cwd, __fixedpath, PATH_MAX);
return 0;
}
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Rename a file
*
* @param[in,out] r newlib reentrancy struct
* @param[in] oldName Path to rename from
* @param[in] newName Path to rename to
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_rename(struct _reent *r,
const char *oldName,
const char *newName)
{
Result rc;
fsdev_fsdevice *device = NULL;
char fs_path_old[FS_MAX_PATH];
char fs_path_new[FS_MAX_PATH];
if(fsdev_getfspath(r, oldName, &device, fs_path_old)==-1)
return -1;
if(fsdev_getfspath(r, newName, NULL, fs_path_new)==-1)
return -1;
rc = fsFsRenameFile(&device->fs, fs_path_old, fs_path_new);
if(R_SUCCEEDED(rc))
return 0;
rc = fsFsRenameDirectory(&device->fs, fs_path_old, fs_path_new);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Create a directory
*
* @param[in,out] r newlib reentrancy struct
* @param[in] path Path of directory to create
* @param[in] mode Permissions of created directory
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_mkdir(struct _reent *r,
const char *path,
int mode)
{
Result rc;
char fs_path[FS_MAX_PATH];
fsdev_fsdevice *device = NULL;
if(fsdev_getfspath(r, path, &device, fs_path)==-1)
return -1;
rc = fsFsCreateDirectory(&device->fs, fs_path);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Open a directory
*
* @param[in,out] r newlib reentrancy struct
* @param[in] dirState Pointer to open directory state
* @param[in] path Path of directory to open
*
* @returns dirState for success
* @returns NULL for error
*/
static DIR_ITER*
fsdev_diropen(struct _reent *r,
DIR_ITER *dirState,
const char *path)
{
FsDir fd;
Result rc;
char fs_path[FS_MAX_PATH];
fsdev_fsdevice *device = NULL;
if(fsdev_getfspath(r, path, &device, fs_path)==-1)
return NULL;
/* get pointer to our data */
fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct);
/* open the directory */
rc = fsFsOpenDirectory(&device->fs, fs_path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, &fd);
if(R_SUCCEEDED(rc))
{
dir->magic = FSDEV_DIRITER_MAGIC;
dir->fd = fd;
dir->index = -1;
dir->size = 0;
memset(&dir->entry_data, 0, sizeof(dir->entry_data));
return dirState;
}
r->_errno = fsdev_translate_error(rc);
return NULL;
}
/*! Reset an open directory to its intial state
*
* @param[in,out] r newlib reentrancy struct
* @param[in] dirState Pointer to open directory state
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_dirreset(struct _reent *r,
DIR_ITER *dirState)
{
r->_errno = ENOSYS;
return -1;
}
/*! Fetch the next entry of an open directory
*
* @param[in,out] r newlib reentrancy struct
* @param[in] dirState Pointer to open directory state
* @param[out] filename Buffer to store entry name
* @param[out] filestat Buffer to store entry attributes
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_dirnext(struct _reent *r,
DIR_ITER *dirState,
char *filename,
struct stat *filestat)
{
Result rc;
size_t entries;
ssize_t units;
FsDirectoryEntry *entry;
/* get pointer to our data */
fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct);
static const size_t max_entries = sizeof(dir->entry_data) / sizeof(dir->entry_data[0]);
/* check if it's in the batch already */
if(++dir->index < dir->size)
{
rc = 0;
}
else
{
/* reset batch info */
dir->index = -1;
dir->size = 0;
/* fetch the next batch */
memset(dir->entry_data, 0, sizeof(dir->entry_data));
rc = fsDirRead(&dir->fd, 0, &entries, max_entries, dir->entry_data);
if(R_SUCCEEDED(rc))
{
if(entries == 0)
{
/* there are no more entries; ENOENT signals end-of-directory */
r->_errno = ENOENT;
return -1;
}
dir->index = 0;
dir->size = entries;
}
}
if(R_SUCCEEDED(rc))
{
entry = &dir->entry_data[dir->index];
/* fill in the stat info */
filestat->st_ino = 0;
if(entry->attributes & FS_ATTRIBUTE_FILE)
filestat->st_mode = S_IFREG;
else
filestat->st_mode = S_IFDIR;
/* convert name from UTF-16 to UTF-8 */
memset(filename, 0, NAME_MAX);
units = fsdev_convertfromfspath((uint8_t*)filename, (uint8_t*)entry->name, NAME_MAX);
if(units < 0)
{
r->_errno = EILSEQ;
return -1;
}
if(units >= NAME_MAX)
{
r->_errno = ENAMETOOLONG;
return -1;
}
return 0;
}
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Close an open directory
*
* @param[in,out] r newlib reentrancy struct
* @param[in] dirState Pointer to open directory state
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_dirclose(struct _reent *r,
DIR_ITER *dirState)
{
Result rc=0;
/* get pointer to our data */
fsdev_dir_t *dir = (fsdev_dir_t*)(dirState->dirStruct);
/* close the directory */
fsDirClose(&dir->fd);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Get filesystem statistics
*
* @param[in,out] r newlib reentrancy struct
* @param[in] path Path to filesystem to get statistics of
* @param[out] buf Buffer to fill
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_statvfs(struct _reent *r,
const char *path,
struct statvfs *buf)
{
Result rc=0;
//FS_ArchiveResource resource;
//bool writable = false;
//rc = FSUSER_GetSdmcArchiveResource(&resource);
if(R_SUCCEEDED(rc))
{
buf->f_bsize = 0;//resource.clusterSize;
buf->f_frsize = 0;//resource.clusterSize;
buf->f_blocks = 0;//resource.totalClusters;
buf->f_bfree = 0;//resource.freeClusters;
buf->f_bavail = 0;//resource.freeClusters;
buf->f_files = 0; //??? how to get
buf->f_ffree = 0;//resource.freeClusters;
buf->f_favail = 0;//resource.freeClusters;
buf->f_fsid = 0; //??? how to get
buf->f_flag = ST_NOSUID;
buf->f_namemax = 0; //??? how to get
/*rc = FSUSER_IsSdmcWritable(&writable);
if(R_FAILED(rc) || !writable)
buf->f_flag |= ST_RDONLY;*/
return 0;
}
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Truncate an open file
*
* @param[in,out] r newlib reentrancy struct
* @param[in] fd Pointer to fsdev_file_t
* @param[in] len Length to truncate file to
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_ftruncate(struct _reent *r,
void *fd,
off_t len)
{
Result rc;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fd;
/* make sure length is non-negative */
if(len < 0)
{
r->_errno = EINVAL;
return -1;
}
/* set the new file size */
rc = fsFileSetSize(&file->fd, len);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Synchronize a file to media
*
* @param[in,out] r newlib reentrancy struct
* @param[in] fd Pointer to fsdev_file_t
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_fsync(struct _reent *r,
void *fd)
{
Result rc;
/* get pointer to our data */
fsdev_file_t *file = (fsdev_file_t*)fd;
rc = fsFileFlush(&file->fd);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
/*! Change a file's mode
*
* @param[in,out] r newlib reentrancy struct
* @param[in] path Path to file to update
* @param[in] mode New mode to set
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_chmod(struct _reent *r,
const char *path,
mode_t mode)
{
r->_errno = ENOSYS;
return -1;
}
/*! Change an open file's mode
*
* @param[in,out] r newlib reentrancy struct
* @param[in] fd Pointer to fsdev_file_t
* @param[in] mode New mode to set
*
* @returns 0 for success
* @returns -1 for failure
*/
static int
fsdev_fchmod(struct _reent *r,
void *fd,
mode_t mode)
{
r->_errno = ENOSYS;
return -1;
}
/*! Remove a directory
*
* @param[in,out] r newlib reentrancy struct
* @param[in] name Path of directory to remove
*
* @returns 0 for success
* @returns -1 for error
*/
static int
fsdev_rmdir(struct _reent *r,
const char *name)
{
Result rc;
char fs_path[FS_MAX_PATH];
fsdev_fsdevice *device = NULL;
if(fsdev_getfspath(r, name, &device, fs_path)==-1)
return -1;
rc = fsFsDeleteDirectory(&device->fs, fs_path);
if(R_SUCCEEDED(rc))
return 0;
r->_errno = fsdev_translate_error(rc);
return -1;
}
Result
fsdev_getmtime(const char *name,
u64 *mtime)
{
//Result rc;
struct _reent r;
r._errno = ENOSYS;
if(r._errno != 0)
errno = r._errno;
return -1;
/*r._errno = 0;
fs_path = fsdev_getfspath(&r, name);
if(r._errno != 0)
errno = r._errno;
if(fs_path.data == NULL)
return -1;*/
/*if(rc == 0)
{*/
/* convert from milliseconds to seconds */
//*mtime /= 1000;
/* convert from 2000-based timestamp to UNIX timestamp */
/**mtime += 946684800;
}
return rc;*/
}
/*! Error map */
typedef struct
{
Result fs_error; //!< Error from FS service
int error; //!< POSIX errno
} error_map_t;
/*! Error table */
static const error_map_t error_table[] =
{
/* keep this list sorted! */
{ 0x202, ENOENT, },
{ 0x2EE202, EINVAL, },
{ 0x2EE602, ENAMETOOLONG, },
};
static const size_t num_errors = sizeof(error_table)/sizeof(error_table[0]);
/*! Comparison function for bsearch on error_table
*
* @param[in] p1 Left side of comparison
* @param[in] p2 Right side of comparison
*
* @returns <0 if lhs < rhs
* @returns >0 if lhs > rhs
* @returns 0 if lhs == rhs
*/
static int
error_cmp(const void *p1, const void *p2)
{
const error_map_t *lhs = (const error_map_t*)p1;
const error_map_t *rhs = (const error_map_t*)p2;
if((u32)lhs->fs_error < (u32)rhs->fs_error)
return -1;
else if((u32)lhs->fs_error > (u32)rhs->fs_error)
return 1;
return 0;
}
/*! Translate FS service error to errno
*
* @param[in] error FS service error
*
* @returns errno
*/
static int
fsdev_translate_error(Result error)
{
error_map_t key = { .fs_error = error };
const error_map_t *rc = bsearch(&key, error_table, num_errors,
sizeof(error_map_t), error_cmp);
if(rc != NULL)
return rc->error;
return (int)error;
}