fs: cast BFile support into generic file descriptors

This paves the way for standard streams, USB streams, and some more.
This commit is contained in:
Lephe 2021-12-13 18:38:47 +01:00
parent 321d6937e0
commit 6903bd58d5
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
16 changed files with 498 additions and 164 deletions

View file

@ -35,17 +35,20 @@ set(SOURCES_COMMON
src/dma/inth.s src/dma/inth.s
src/dma/memcpy.c src/dma/memcpy.c
src/dma/memset.c src/dma/memset.c
# Filesystem interface (Fugue only) # Filesystem interface
src/fs/close.c src/fs/close.c
src/fs/creat.c src/fs/creat.c
src/fs/fs.c
src/fs/lseek.c src/fs/lseek.c
src/fs/open.c src/fs/open.c
src/fs/pread.c src/fs/pread.c
src/fs/pwrite.c src/fs/pwrite.c
src/fs/read.c src/fs/read.c
src/fs/unlink.c src/fs/unlink.c
src/fs/util.c
src/fs/write.c src/fs/write.c
# Filesystem interface to Fugue
src/fs/fugue/fugue.c
src/fs/fugue/util.c
# Interrupt Controller driver # Interrupt Controller driver
src/intc/intc.c src/intc/intc.c
src/intc/inth.s src/intc/inth.s

View file

@ -129,6 +129,11 @@ int BFile_Write(int fd, void const *data, int even_size);
* If [whence >= 0], it is taken as an absolute location within the file; * If [whence >= 0], it is taken as an absolute location within the file;
* If [whence == -1], BFile_Read() reads from the current position. * If [whence == -1], BFile_Read() reads from the current position.
With Fugue this function can read past end of file and return the requested
amount of bytes even when the file does not have enough data to read that
much. It seems that extra bytes read as zeros. Reading past the end does
*not* extend the file size.
With CASIOWIN, returns how much data can be read from the updated file With CASIOWIN, returns how much data can be read from the updated file
offset (ie. how many bytes until end of file), or an error code. offset (ie. how many bytes until end of file), or an error code.
With Fugue, returns the amount of data read (or an error code). */ With Fugue, returns the amount of data read (or an error code). */

94
include/gint/fs.h Normal file
View file

@ -0,0 +1,94 @@
//---
// gint:fs - Filesystem abstraction
//---
#ifndef GINT_FS
#define GINT_FS
#ifdef __cplusplus
extern "C" {
#endif
#include <sys/types.h>
#include <stddef.h>
/* Maximum number of file descriptors */
#define FS_FD_MAX 32
/* fs_descriptor_type_t: Overloaded file descriptor functions
This structure provides the base functions for every type of file
descriptor. The prototypes are standard, except that the first argument
(int fd) is replaced by (void *data) which points to the custom data
allocator for the descriptor.
pread() is normally implemented in terms of lseek() and read(), however on
Fugue file descriptors on the G-III series there is no way to determine the
current position so this method won't work, and pread() is instead provided
by a non-standard parameter of BFile_Read(). The overloaded entry supports
this alternative. */
typedef struct {
/* See <unistd.h> for a description of these functions */
ssize_t (*read)(void *data, void *buf, size_t size);
ssize_t (*pread)(void *data, void *buf, size_t size, off_t offset);
ssize_t (*write)(void *data, void const *buf, size_t size);
off_t (*lseek)(void *data, off_t offset, int whence);
int (*close)(void *data);
} fs_descriptor_type_t;
/* fs_descriptor_t: File descriptor information
This internal type describes the entries of the descriptor table. */
typedef struct {
/* Interface functions */
fs_descriptor_type_t const *type;
/* Custom data (can also be an integer cast to (void *)) */
void *data;
} fs_descriptor_t;
/* fs_get_descriptor(): Get a file descriptor's data
This function is used internally in order to implement read(), write(), and
other standard functions functions. It could be useful for other APIs that
want to present their resources as file descriptors but still implement
extra non-standard functions; this allows them to use the file descriptor as
input and still access the custom data.
Returns NULL if there is no file descriptor with this number, in which case
the caller probably needs to set errno = EBADF. */
fs_descriptor_t const *fs_get_descriptor(int fd);
/* fs_create_descriptor(): Create a new file descriptor
This function is used in open() and its variants to allocate new file
descriptors. The descriptor's data is created with a copy of the provided
structure, which must include a non-NULL type attribute.
This function always returns the smallest file descriptor that is unused by
the application and is not 0, 1 or 2; unless it runs out of file
descriptors, in which case it returns -1 and the caller might want to set
errno = ENFILE. */
int fs_create_descriptor(fs_descriptor_t const *data);
/* fs_free_descriptor(): Clsoe a file descriptor's slot
This function frees the specified file descriptor. It is automatically
called by close() after the descriptor type's close() function, so there is
normally no need to call this function directly. */
void fs_free_descriptor(int fd);
/* open_generic(): Open a file descriptor using custom file functions
Opens a new file descriptor of the specified type with the provided user
data. If reuse_fd < 0, a new file descriptor is allocated, otherwise the
exact file descriptor reuse_fd is used. (This is useful to reopen standard
streams.) In this case, the only possible return values are -1 and reuse_fd
itself. */
int open_generic(fs_descriptor_type_t *type, void *data, int reuse_fd);
#ifdef __cplusplus
}
#endif
#endif /* GINT_FS */

View file

@ -1,14 +1,19 @@
#include <unistd.h> #include <unistd.h>
#include "util.h" #include <gint/fs.h>
#include <errno.h>
int close(int fd) int close(int fd)
{ {
ENOTSUP_IF_NOT_FUGUE(-1); fs_descriptor_t const *d = fs_get_descriptor(fd);
if(!d) {
errno = EBADF;
return (ssize_t)-1;
}
int err = BFile_Close(fd); int rc = 0;
if(err < 0) { if(d->type->close)
errno = bfile_error_to_errno(err); rc = d->type->close(d->data);
return -1;
} fs_free_descriptor(fd);
return 0; return rc;
} }

96
src/fs/fs.c Normal file
View file

@ -0,0 +1,96 @@
#include <gint/fs.h>
#include <gint/defs/attributes.h>
#include <unistd.h>
#include <errno.h>
/* File descriptor table */
static fs_descriptor_t fdtable[FS_FD_MAX] = { 0 };
fs_descriptor_t const *fs_get_descriptor(int fd)
{
if((unsigned)fd >= FS_FD_MAX)
return NULL;
if(fdtable[fd].type == NULL)
return NULL;
return &fdtable[fd];
}
int fs_create_descriptor(fs_descriptor_t const *data)
{
if(data->type == NULL)
return -1;
/* Leave 0/1/2 for stdin, stdout and stderr */
for(int i = 3; i < FS_FD_MAX; i++) {
if(fdtable[i].type == NULL) {
fdtable[i] = *data;
return i;
}
}
return -1;
}
void fs_free_descriptor(int fd)
{
if((unsigned)fd >= FS_FD_MAX)
return;
fdtable[fd].type = NULL;
fdtable[fd].data = NULL;
}
int open_generic(fs_descriptor_type_t *type, void *data, int fd)
{
if(!type) {
errno = EINVAL;
return -1;
}
fs_descriptor_t d = {
.type = type,
.data = data
};
/* Re-use file descriptor mode */
if(fd >= 0) {
if(fd >= FS_FD_MAX) {
errno = EBADF;
return -1;
}
if(fdtable[fd].type) {
errno = ENFILE;
return -1;
}
fdtable[fd] = d;
return fd;
}
/* Normal allocation mode */
else {
return fs_create_descriptor(&d);
}
}
/* Standard streams */
static fs_descriptor_type_t devnull = {
.read = NULL,
.pread = NULL,
.write = NULL,
.lseek = NULL,
.close = NULL,
};
GCONSTRUCTOR static void init_standard_streams(void)
{
fdtable[STDIN_FILENO].type = &devnull;
fdtable[STDIN_FILENO].data = NULL;
fdtable[STDOUT_FILENO].type = &devnull;
fdtable[STDOUT_FILENO].data = NULL;
fdtable[STDERR_FILENO].type = &devnull;
fdtable[STDERR_FILENO].data = NULL;
}

209
src/fs/fugue/fugue.c Normal file
View file

@ -0,0 +1,209 @@
#include <gint/fs.h>
#include <gint/hardware.h>
#include <gint/bfile.h>
#include <fcntl.h>
#include <errno.h>
#include "util.h"
ssize_t fugue_read(void *data, void *buf, size_t size)
{
int fugue_fd = (int)data;
int rc = BFile_Read(fugue_fd, buf, size, -1);
if(rc < 0) {
errno = bfile_error_to_errno(rc);
return -1;
}
return size;
}
ssize_t fugue_pread(void *data, void *buf, size_t size, off_t offset)
{
int fugue_fd = (int)data;
/* Thanks to the extra argument to BFile_Read(), we can perform this
call without knowing the current offset, even on G-III models */
int rc = BFile_Read(fugue_fd, buf, size, offset);
if(rc < 0) {
errno = bfile_error_to_errno(rc);
return -1;
}
BFile_Seek(fugue_fd, -size);
return rc;
}
ssize_t fugue_write(void *data, const void *buf, size_t size)
{
int fugue_fd = (int)data;
int rc = BFile_Write(fugue_fd, buf, size);
if(rc < 0) {
errno = bfile_error_to_errno(rc);
return -1;
}
return rc;
}
off_t fugue_lseek(void *data, off_t offset, int whence)
{
int fugue_fd = (int)data;
if(whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END) {
errno = EINVAL;
return (off_t)-1;
}
/* On Graph 35+E II, there is no documented way to know the offset. */
if(gint[HWCALC] == HWCALC_G35PE2 && whence == SEEK_CUR) {
errno = ENOTSUP;
return (off_t)-1;
}
off_t destination;
if(whence == SEEK_SET)
destination = offset;
else if(whence == SEEK_CUR)
destination = BFile_GetPos(fugue_fd) + offset;
else if(whence == SEEK_END)
destination = BFile_Size(fugue_fd) + offset;
int rc = BFile_Seek(fugue_fd, destination);
if(rc < 0) {
errno = bfile_error_to_errno(rc);
return -1;
}
/* rc is the amount of space left in the file (including pre-allocated
space), so instead just return destination directly */
return (off_t)destination;
}
int fugue_close(void *data)
{
int fugue_fd = (int)data;
int rc = BFile_Close(fugue_fd);
if(rc < 0) {
errno = bfile_error_to_errno(rc);
return -1;
}
return 0;
}
static const fs_descriptor_type_t fugue_descriptor_type = {
.read = fugue_read,
.pread = fugue_pread,
.write = fugue_write,
.lseek = fugue_lseek,
.close = fugue_close,
};
int fugue_open(char const *path, int flags, GUNUSED mode_t mode)
{
if(gint[HWFS] != HWFS_FUGUE) {
errno = ENOTSUP;
return -1;
}
uint16_t *fcpath = utf8_to_fc_alloc(path, u"\\\\fls0\\");
int fugue_fd, err, rc = -1;
if(!fcpath) {
errno = ENOMEM;
return -1;
}
/* Open mode */
int bfile_mode = BFile_ReadOnly;
if(flags & O_WRONLY)
bfile_mode = BFile_WriteOnly;
else if(flags & O_RDWR)
bfile_mode = BFile_ReadWrite;
/* Exclusive creation requires the file to be created by the call */
bool excl = (flags & O_EXCL) && (flags & O_CREAT);
/* Truncation requires the file to be removed/recreated */
bool trunc = (flags & O_TRUNC) && (flags & O_CREAT);
/* Try and open the file normally, unless O_TRUNC is specified without
O_EXCL, in which case we simply delete and recreate the file. */
fugue_fd = BFile_EntryNotFound;
if(excl || !trunc)
fugue_fd = BFile_Open(fcpath, bfile_mode);
/* If the file exists and O_EXCL was requested, fail. */
if(fugue_fd >= 0 && excl) {
BFile_Close(fugue_fd);
errno = EEXIST;
return -1;
}
/* If O_TRUNC is requested and either the file exists or O_CREAT is
set, temporarily remove it. */
if((flags & O_TRUNC) && (fugue_fd >= 0 || (flags & O_CREAT))) {
if(fugue_fd >= 0) BFile_Close(fugue_fd);
BFile_Remove(fcpath);
fugue_fd = BFile_EntryNotFound;
}
/* If the file does not exist and O_CREAT is set, create it */
if((flags & O_CREAT) && ((flags & O_TRUNC) || fugue_fd < 0)) {
int size = 0;
err = BFile_Create(fcpath, BFile_File, &size);
if(err < 0) {
errno = bfile_error_to_errno(err);
goto end;
}
fugue_fd = BFile_Open(fcpath, bfile_mode);
}
if(fugue_fd < 0) {
errno = bfile_error_to_errno(fugue_fd);
goto end;
}
/* If O_APPEND is set, move to the end of the file */
if((flags & O_APPEND))
BFile_Seek(fugue_fd, BFile_Size(fugue_fd));
/* Return the now-open file descriptor */
fs_descriptor_t data = {
.type = &fugue_descriptor_type,
.data = (void *)fugue_fd,
};
int fd = fs_create_descriptor(&data);
if(fd == -1) {
BFile_Close(fugue_fd);
errno = ENFILE;
goto end;
}
rc = fd;
end:
free(fcpath);
return rc;
}
int fugue_unlink(char const *path)
{
if(gint[HWFS] != HWFS_FUGUE) {
errno = ENOTSUP;
return -1;
}
uint16_t *fcpath = utf8_to_fc_alloc(path, u"\\\\fls0\\");
if(!fcpath) {
errno = ENOMEM;
return -1;
}
int err = BFile_Remove(fcpath);
if(err < 0) {
errno = bfile_error_to_errno(err);
return -1;
}
return 0;
}

13
src/fs/fugue/fugue.h Normal file
View file

@ -0,0 +1,13 @@
#ifndef FS_FUGUE_FUGUE_H
#define FS_FUGUE_FUGUE_H
#include <fcntl.h>
#include <unistd.h>
/* Specific implementation of open() */
int fugue_open(char const *path, int flags, mode_t mode);
/* Specific implementation of unlink() */
int fugue_unlink(char const *path);
#endif /* FS_FUGUE_FUGUE_H */

View file

@ -1,6 +1,8 @@
#include "util.h" #include "util.h"
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
#include <errno.h>
#include <bfile.h>
int bfile_error_to_errno(int e) int bfile_error_to_errno(int e)
{ {

View file

@ -7,13 +7,7 @@ extern "C" {
#include <stdint.h> #include <stdint.h>
#include <stddef.h> #include <stddef.h>
#include <errno.h>
#include <bfile.h>
#include <stdlib.h> #include <stdlib.h>
#include <gint/hardware.h>
#define ENOTSUP_IF_NOT_FUGUE(rc) \
if(gint[HWFS] != HWFS_FUGUE) { errno = ENOTSUP; return rc; }
/* Translate common BFile error codes to errno values. */ /* Translate common BFile error codes to errno values. */
extern int bfile_error_to_errno(int bfile_error_code); extern int bfile_error_to_errno(int bfile_error_code);

View file

@ -1,37 +1,18 @@
#include <unistd.h> #include <unistd.h>
#include "util.h" #include <gint/fs.h>
#include <errno.h>
off_t lseek(int fd, off_t offset, int whence) off_t lseek(int fd, off_t offset, int whence)
{ {
ENOTSUP_IF_NOT_FUGUE((off_t)-1); fs_descriptor_t const *d = fs_get_descriptor(fd);
if(!d) {
if(whence != SEEK_SET && whence != SEEK_CUR && whence != SEEK_END) { errno = EBADF;
errno = EINVAL; return (ssize_t)-1;
return (off_t)-1;
} }
/* On Graph 35+E II, there is no documented way to know the offset. */ if(d->type->lseek)
if(gint[HWCALC] == HWCALC_G35PE2 && whence == SEEK_CUR) { return d->type->lseek(d->data, offset, whence);
errno = ENOTSUP;
return (off_t)-1; /* No seek function: cannot seek */
} return 0;
off_t destination;
if(whence == SEEK_SET)
destination = offset;
else if(whence == SEEK_CUR)
destination = BFile_GetPos(fd) + offset;
else if(whence == SEEK_END)
destination = BFile_Size(fd) - offset;
int rc = BFile_Seek(fd, destination);
if(rc < 0) {
errno = bfile_error_to_errno(rc);
return -1;
}
/* rc is the amount of space left in the file (including pre-allocated
space), so instead just return destination directly */
return (off_t)destination;
} }

View file

@ -1,74 +1,14 @@
#include <fcntl.h> #include <fcntl.h>
#include "util.h" #include <stdarg.h>
#include "fugue/fugue.h"
int open(char const *path, int flags, ...) int open(char const *path, int flags, ...)
{ {
ENOTSUP_IF_NOT_FUGUE(-1); va_list args;
va_start(args, flags);
mode_t mode = va_arg(args, int);
va_end(args);
uint16_t *fcpath = utf8_to_fc_alloc(path, u"\\\\fls0\\"); /* Standard open() is the Fugue filesystem only */
int fd, err, rc = -1; return fugue_open(path, flags, mode);
if(!fcpath) {
errno = ENOMEM;
return -1;
}
/* Open mode */
int bfile_mode = BFile_ReadOnly;
if(flags & O_WRONLY)
bfile_mode = BFile_WriteOnly;
else if(flags & O_RDWR)
bfile_mode = BFile_ReadWrite;
/* Exclusive creation requires the file to be created by the call */
bool excl = (flags & O_EXCL) && (flags & O_CREAT);
/* Truncation requires the file to be removed/recreated */
bool trunc = (flags & O_TRUNC) && (flags & O_CREAT);
/* Try and open the file normally, unless O_TRUNC is specified without
O_EXCL, in which case we simply delete and recreate the file. */
fd = BFile_EntryNotFound;
if(excl || !trunc)
fd = BFile_Open(fcpath, bfile_mode);
/* If the file exists and O_EXCL was requested, fail. */
if(fd >= 0 && excl) {
BFile_Close(fd);
errno = EEXIST;
return -1;
}
/* If O_TRUNC is requested and either the file exists or O_CREAT is
set, temporarily remove it. */
if((flags & O_TRUNC) && (fd >= 0 || (flags & O_CREAT))) {
if(fd >= 0) BFile_Close(fd);
BFile_Remove(fcpath);
fd = BFile_EntryNotFound;
}
/* If the file does not exist and O_CREAT is set, create it */
if((flags & O_CREAT) && ((flags & O_TRUNC) || fd < 0)) {
int size = 0;
err = BFile_Create(fcpath, BFile_File, &size);
if(err < 0) {
errno = bfile_error_to_errno(err);
goto end;
}
fd = BFile_Open(fcpath, bfile_mode);
}
if(fd < 0) {
errno = bfile_error_to_errno(fd);
goto end;
}
/* If O_APPEND is set, move to the end of the file */
if((flags & O_APPEND))
BFile_Seek(fd, BFile_Size(fd));
/* Return the now-open file descriptor */
rc = fd;
end:
free(fcpath);
return rc;
} }

View file

@ -1,17 +1,18 @@
#include <unistd.h> #include <unistd.h>
#include "util.h" #include <gint/fs.h>
#include <errno.h>
ssize_t pread(int fd, void *buf, size_t size, off_t offset) ssize_t pread(int fd, void *buf, size_t size, off_t offset)
{ {
ENOTSUP_IF_NOT_FUGUE(-1); fs_descriptor_t const *d = fs_get_descriptor(fd);
if(!d) {
errno = EBADF;
return (ssize_t)-1;
}
/* Thanks to the extra argument to BFile_Read(), we can perform this if(d->type->pread)
call without knowing the current offset, even on G-III models */ return d->type->pread(d->data, buf, size, offset);
int rc = BFile_Read(fd, buf, size, offset);
if(rc < 0) { /* No seek function: cannot seek */
errno = bfile_error_to_errno(rc); return 0;
return -1;
}
BFile_Seek(fd, -size);
return rc;
} }

View file

@ -1,25 +1,21 @@
#include <unistd.h> #include <unistd.h>
#include "util.h"
ssize_t pwrite(int fd, const void *buf, size_t size, off_t offset) ssize_t pwrite(int fd, const void *buf, size_t size, off_t offset)
{ {
ENOTSUP_IF_NOT_FUGUE((off_t)-1);
off_t current = lseek(fd, 0, SEEK_CUR); off_t current = lseek(fd, 0, SEEK_CUR);
/* This fails on G-III BFile and non-seekable file descriptors */
if(current == (off_t)-1) if(current == (off_t)-1)
return (ssize_t)-1; return (ssize_t)-1;
ssize_t rc = -1; ssize_t rc = -1;
if(lseek(fd, 0, offset) == (off_t)-1) if(lseek(fd, offset, SEEK_SET) == (off_t)-1)
goto end; goto end;
rc = BFile_Write(fd, buf, size); rc = write(fd, buf, size);
if(rc < 0) { if(rc < 0)
errno = bfile_error_to_errno(rc);
goto end; goto end;
}
end: end:
/* At the end, always try to restore the current position */ /* At the end, always try to restore the current position */

View file

@ -1,14 +1,18 @@
#include <unistd.h> #include <unistd.h>
#include "util.h" #include <gint/fs.h>
#include <errno.h>
ssize_t read(int fd, void *buf, size_t size) ssize_t read(int fd, void *buf, size_t size)
{ {
ENOTSUP_IF_NOT_FUGUE(-1); fs_descriptor_t const *d = fs_get_descriptor(fd);
if(!d) {
errno = EBADF;
return (ssize_t)-1;
}
int err = BFile_Read(fd, buf, size, -1); if(d->type->read)
if(err < 0) { return d->type->read(d->data, buf, size);
errno = bfile_error_to_errno(err);
return -1; /* No read function: we can't read anything */
} return 0;
return size;
} }

View file

@ -1,21 +1,8 @@
#include <unistd.h> #include <unistd.h>
#include "util.h" #include "fugue/fugue.h"
int unlink(char const *path) int unlink(char const *path)
{ {
ENOTSUP_IF_NOT_FUGUE(-1); /* Standard unlink() is the Fugue filesystem only */
return fugue_unlink(path);
uint16_t *fcpath = utf8_to_fc_alloc(path, u"\\\\fls0\\");
if(!fcpath) {
errno = ENOMEM;
return -1;
}
int err = BFile_Remove(fcpath);
if(err < 0) {
errno = bfile_error_to_errno(err);
return -1;
}
return 0;
} }

View file

@ -1,14 +1,18 @@
#include <unistd.h> #include <unistd.h>
#include "util.h" #include <gint/fs.h>
#include <errno.h>
ssize_t write(int fd, const void *buf, size_t size) ssize_t write(int fd, const void *buf, size_t size)
{ {
ENOTSUP_IF_NOT_FUGUE(-1); fs_descriptor_t const *d = fs_get_descriptor(fd);
if(!d) {
errno = EBADF;
return (ssize_t)-1;
}
int rc = BFile_Write(fd, buf, size); if(d->type->write)
if(rc < 0) { return d->type->write(d->data, buf, size);
errno = bfile_error_to_errno(rc);
return -1; /* No write function: discard the contents but show no error */
} return size;
return rc;
} }