diff --git a/include/gint/bfile.h b/include/gint/bfile.h index 4478e6d..0a8ab1f 100644 --- a/include/gint/bfile.h +++ b/include/gint/bfile.h @@ -1,9 +1,60 @@ //--- -// gint:bfile - BFile interface +// gint:bfile - BFile interface // -// The system's file system is a nightmare and the risk of corrupting it -// or the flash by driving it manually is too great to risk. This module -// interfaces with the BFile syscalls for file management. +// BFile is the OS's native interface to the filesystem. It has quite a bit of +// legacy, so it's not very easy to use. Please note that BFile cannot be used +// from within gint, so do a gint_world_switch() () to call BFile +// functions; otherwise the add-in is likely to crash. +// +// There are two different filesystems: +// * An older, in-house one made by CASIO and informally called CASIOWIN (which +// is the name of the OS itself), which is incomplete, hard to use, and very +// limiting, but also very fast. +// * A newer one, designed by Kyoto Software Research and called Fugue, which +// is mostly complete and behaves as a regular filesystem, though very slow. +// +// You can detect which one is used by querying gint[HWFS] (): +// * HWFS_FUGUE is the newer one; it is found in: +// - The fx-CG series (in France: Prizm and Graph 90+E) +// - The fx G-III series (in France: Graph 35+E II) +// * HWFS_CASIOWIN is the older one; it is found in all older black-and-white +// model that have a storage memory. +// +// Wherever Fugue is used, gint supports the Unix file API (open, read, write, +// etc) and the C99 standard file API (fopen, fread, fwrite, etc), so there is +// no need to call into BFile directly (you should still do a world switch +// before using these functions). +// +// You should only have to use BFile if you're dealing with the CASIOWIN +// filesystem. With Fugue, not only are the Unix/C99 APIs easier to use, but +// the meanings of arguments and return values in BFile calls also change +// compared to the CASIOWIN filesystem, so there's little to gain anyway. +// +// If you're here for the CASIOWIN filesystem, be aware of its limitations. In +// general reading files will work fine; expect no issues with that. Modifying +// files is where things get difficult. Keep the following in mind: +// +// * Non-standard meanings of arguments and return values (eg. BFile_Read() +// returns the number of readable bytes between the new position and EOF). +// * Files must be created with a fixed size indicated in BFile_Create() and +// are initialized with 0xff. +// * A write to a file can only replace 1's with 0's, meaning in practice a +// file can only be written to once. (That's why it's created with 0xff.) +// * All writes must be of even size; writing an odd number of bytes can mess +// up the file and confuse the filesystem. +// * Only one level of folders is supported; attempting to create second-level +// subfolders results in weird recursive directories (don't do that). +// +// File paths in the OS use the non-standard FONTCHARACTER encoding, which is a +// 16-bit fixed-width encoding. Most users don't care about special characters; +// in GCC, you can get a 16-bit literal string with the u"" prefix, eg. +// u"\\\\fls0\\data.bin" +// which is what you usually supply as the [uint16_t const *path] argument. +// +// All functions return nonnegative values on success. If no return value is +// described then a successful call returns 0. Unless specified otherwise, all +// functions can also return a negative error code as documented near the end +// of this header. //--- #ifndef GINT_BFILE @@ -15,88 +66,162 @@ extern "C" { #include -/* BFile_Remove(): Remove a file - Also works if the file does not exist. +//--- +// Common file access functions +//--- - @file FONTCHARACTER file path - Returns a BFile error code. */ -int BFile_Remove(uint16_t const *file); +/* Remove a file or folder (also works if the entry does not exist). */ +int BFile_Remove(uint16_t const *path); + +#define BFile_File 1 +#define BFile_Folder 5 + +/* BFile_Create(): Create a new file or folder -/* BFile_Create(): Create a new entry The file or directory must not exist. For a file the size pointer must point - to the desired file size (which is fixed), for a folder it is ignored. + to the desired file size (which is fixed), for a folder it is ignored. With + CASIOWIN this is the final size of the file. With Fugue the file can be + resized dynamically and is usually created with initial size 0. - @file FONTCHARACTER file path - @type Entry type - @size Pointer to file size if [type = BFile_File], use NULL otherwise - Returns a BFile error code. */ -enum BFile_EntryType -{ - BFile_File = 1, - BFile_Folder = 5, -}; -int BFile_Create(uint16_t const *file, enum BFile_EntryType type, int *size); + @path FONTCHARACTER file path + @type Entry type (BFile_File or BFile_Folder) + @size Pointer to file size if type is BFile_File, use NULL otherwise */ +int BFile_Create(uint16_t const *path, int type, int *size); -/* BFile_Open(): Open an existing file - The file must exist. +#define BFile_ReadOnly 0x01 +#define BFile_WriteOnly 0x02 +#define BFile_ReadWrite (BFile_ReadOnly | BFile_WriteOnly) +#define BFile_Share 0x80 +#define BFile_ReadShare (BFile_ReadOnly | BFile_Share) +#define BFile_ReadWriteShare (BFile_ReadWrite | BFile_Share) - @file FONTCHARACTER file path +/* BFile_Open(): Open a file for reading or writing + + The file must exist, even when opening in writing mode. The meaning of the + [Share] flag isn't clear; I believe it's simply ignored in the CASIOWIN + filesystem. + + @path FONTCHARACTER file path @mode Desired access mode - Returns a file descriptor on success, or a negative BFile error code. */ -enum BFile_OpenMode -{ - BFile_ReadOnly = 0x01, - BFile_WriteOnly = 0x02, - BFile_ReadWrite = BFile_ReadOnly | BFile_WriteOnly, -}; -int BFile_Open(uint16_t const *file, enum BFile_OpenMode mode); + Returns a file descriptor (or a negative error code). */ +int BFile_Open(uint16_t const *path, int mode); -/* BFile_Close(): Close a file descriptor - @fd Open file descriptor - Returns a BFile error code. */ +/* Close an open file descriptor. */ int BFile_Close(int fd); -/* BFile_Size(): Retrieve size of an open file - @fd Open file descriptor - Returns the file size in bytes, or a negative BFile error code*/ +/* Get the size of an open file. */ int BFile_Size(int fd); /* BFile_Write(): Write data to an open file - Second and third argument specify the data and length to write. - WARNING: The file systems has shown to become inconsistent if an odd number - of bytes is written with BFile_Write(). Keep it even! + WARNING: The CASIOWIN fs is known to become inconsistent if an odd number of + bytes is written with BFile_Write(). Always keep it even! - @fd File descriptor open for writing - @data Data to write - @even_size Size to write (must be even, yes) - Returns a BFile error code. */ + With CASIOWIN, returns the new file offset after writing (or an error code). + With Fugue, returns the amount of data written (or an error code). */ int BFile_Write(int fd, void const *data, int even_size); /* BFile_Read(): Read data from an open file - The second and third argument specify where to store and how much to read. - The location from where the data is read depends on [whence]: + + The extra argument [whence] specifies where data is read from in the style + of pread(2), and is supported with both filesystems. * If [whence >= 0], it is taken as an absolute location within the file; * If [whence == -1], BFile_Read() reads from the current position. - @fd File descriptor open for reading - @data Buffer of at least [size] bytes to store data to - @size Number of bytes to read - @whence Starting position of the data to read in the file - Returns a BFile error code. */ + 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. + With Fugue, returns the amount of data read (or an error code). */ int BFile_Read(int handle, void *data, int size, int whence); -/* BFile_FindFirst(): Search a directory for file with matching name - Doesn't work on Main memory. Only four search handle can be opened, you need - to close them to be able to reuse them. Search is NOT case sensitive and * - can be used as a wildcard. +/* BFile_Seek(): Seek to a relative or absolute position within an open file + + With CASIOWIN, moves [offset] bytes relative to the current position, and + returns how much data can be read from the new position. BFile_Seek(fd, 0) + combined with BFile_Size(fd) can be used to determine the current position. + + With Fugue, moves to the absolute position [offset] within the file, and + returns the amount of allocated space following the new position (usually + larger than the amount of data until end-of-file because files are allocated + in units of 4096 bytes). There is no way to seek relative to the current + position unless the target is precomputed with BFile_GetPos(). */ +int BFile_Seek(int fd, int offset); + +/* BFile_GetPos(): Get the current position in an open file + + This call does not exist in the CASIOWIN interface, so this function always + returns -1 on models with a CASIOWIN filesystem. + + This call exists in Fugue, however some Fugue models have their syscall list + inherited from the CASIOWIN era and don't have an entry point for it (or if + there's one it's escape scrutiny so far). + + * Prizm / Graph 90+E / fx-CG series: this function works as intended. + * Graph 35+E II / G-III series: the call is missing, returns -1. + + For the latter models there is no easily reliable way of knowing the current + position within an open file! */ +int BFile_GetPos(int fd); + +//--- +// Error codes +//--- + +#define BFile_EntryNotFound -1 +#define BFile_IllegalParam -2 +#define BFile_IllegalPath -3 +#define BFile_DeviceFull -4 +#define BFile_IllegalDevice -5 +#define BFile_IllegalFilesystem -6 +#define BFile_IllegalSystem -7 +#define BFile_AccessDenied -8 +#define BFile_AlreadyLocked -9 +#define BFile_IllegalTaskID -10 +#define BFile_PermissionError -11 +#define BFile_EntryFull -12 +#define BFile_AlreadyExists -13 +#define BFile_ReadOnlyFile -14 +#define BFile_IllegalFilter -15 +#define BFile_EnumerateEnd -16 +#define BFile_DeviceChanged -17 +//#define BFile_NotRecordFile -18 // Not used +#define BFile_IllegalSeekPos -19 +#define BFile_IllegalBlockFile -20 +//#define BFile_NoSuchDevice -21 // Not used +//#define BFile_EndOfFile -22 // Not used +#define BFile_NotMountDevice -23 +#define BFile_NotUnmountDevice -24 +#define BFile_CannotLockSystem -25 +#define BFile_RecordNotFound -26 +//#define BFile_NotDualRecordFile -27 // Not used +#define BFile_NoAlarmSupport -28 +#define BFile_CannotAddAlarm -29 +#define BFile_FileFindUsed -30 +#define BFile_DeviceError -31 +#define BFile_SystemNotLocked -32 +#define BFile_DeviceNotFound -33 +#define BFile_FileTypeMismatch -34 +#define BFile_NotEmpty -35 +#define BFile_BrokenSystemData -36 +#define BFile_MediaNotReady -37 +#define BFile_TooManyAlarms -38 +#define BFile_SameAlarmExists -39 +#define BFile_AccessSwapArea -40 +#define BFile_MultimediaCard -41 +#define BFile_CopyProtection -42 +#define BFile_IllegalFileData -43 + +//--- +// Search API +// +// The search API is described below. It's somewhat unreliable, with unclear +// semantics and a history of breaking in seemingly reasonable programs. One +// day probably we'll know how to use it properly and reliably. +// +// Note: always close search handles or trouble will ensue (eg. add-in +// discovery failing). +//--- - @search FONTCHARACTER file path to match - @shandle Search handle to pass to BFile_FindNext or BFile_FindClose - @founfile FONTCHARACTER found file path - @fileinfo Structure containing a lot of information on the found file - Returns 0 on success, -1 if no file found, or negative value on error. */ struct BFile_FileInfo { uint16_t index; @@ -109,36 +234,59 @@ struct BFile_FileInfo /* Address of first fragment (do not use directly) */ void *address; }; -enum BFile_FileType -{ - BFile_Type_Directory = 0x0000, - BFile_Type_File = 0x0001, - BFile_Type_Addin = 0x0002, - BFile_Type_Eact = 0x0003, - BFile_Type_Language = 0x0004, - BFile_Type_Bitmap = 0x0005, - BFile_Type_MainMem = 0x0006, - BFile_Type_Temp = 0x0007, - BFile_Type_Dot = 0x0008, - BFile_Type_DotDot = 0x0009, - BFile_Type_Volume = 0x000a, - BFile_Type_Archived = 0x0041, -}; -int BFile_FindFirst(uint16_t const *search, int *shandle, uint16_t *foundfile, + +#define BFile_Type_Directory 0x0000 +#define BFile_Type_File 0x0001 +#define BFile_Type_Addin 0x0002 +#define BFile_Type_Eact 0x0003 +#define BFile_Type_Language 0x0004 +#define BFile_Type_Bitmap 0x0005 +#define BFile_Type_MainMem 0x0006 +#define BFile_Type_Temp 0x0007 +#define BFile_Type_Dot 0x0008 +#define BFile_Type_DotDot 0x0009 +#define BFile_Type_Volume 0x000a +#define BFile_Type_Archived 0x0041 + +/* BFile_FindFirst(): Search the storage memory for paths matching a pattern + + This if for the storage memory only. There are only four search handles; + make sure to close them, there is no automatic device to close them for you + even after the add-in exists. + + Search is NOT case sensitive. '*' can be used as a wildcard, although it's + unclear whether more than one '*' is supported, whether '*' can match in a + directory name, whether multiple folders can be searched simultaneously, and + whether directories can be matched. + + @pattern FONTCHARACTER glob pattern + @shandle Will receive search handle (to use in BFile_FindNext/FindClose) + @foundfile Will receive FONTCHARACTER path of matching entry + @fileinfo Will receive metadata of matching entry + + On success, returns 0, stores the search handle in *shandle, and stores + information about the first match in foundfile and fileinfo. The negative + error code BFile_EntryNotFound should be interpreted as an empty result (ie. + no entry matched) rather than an error. + + Returns 0 on success or a negative error code. BFile_EntryNotFound should be + interpreted as an empty result (ie. no file matched). */ +int BFile_FindFirst(uint16_t const *pattern, int *shandle, uint16_t *foundfile, struct BFile_FileInfo *fileinfo); -/* BFile_FindNext(): Continue a search a directory for file with matching name +/* BFile_FindNext(): Continue a search - @shandle Search handle from BFile_FindFirst - @founfile FONTCHARACTER found file path - @fileinfo Structure containing a lot of information on the found file - Returns 0 on success, -1 if end is reached, or negative value on error. */ + Continues the search for matches. The search handle is the value set in + *shandle in BFile_FindFirst(). As before, *foundfile receives the matching + entry's path and *fileinfo its metadata. + + Returns 0 on success. The negative error code BFile_EnumerateEnd should be + interpreted as the end of the search (ie. all matching files have been + returned) rather than an error. */ int BFile_FindNext(int shandle, uint16_t *foundfile, struct BFile_FileInfo *fileinfo); -/* BFile_FindClose(): Close the specified search handle - @shandle Search handle from BFile_FindFirst - Return 0 on success or negative value on error. */ +/* Close a search handle (with or without exhausting the matches). */ int BFile_FindClose(int shandle); #ifdef __cplusplus diff --git a/src/kernel/syscalls.S b/src/kernel/syscalls.S index d4175aa..7a489d4 100644 --- a/src/kernel/syscalls.S +++ b/src/kernel/syscalls.S @@ -23,6 +23,8 @@ .global _BFile_Open .global _BFile_Close .global _BFile_Size +.global _BFile_Seek +.global _BFile_GetPos .global _BFile_Write .global _BFile_Read .global _BFile_FindFirst @@ -64,6 +66,7 @@ ___realloc: /* BFile driver */ _BFile_Remove: + mov #0, r5 syscall(0x0439) _BFile_Create: syscall(0x0434) @@ -73,6 +76,11 @@ _BFile_Close: syscall(0x042d) _BFile_Size: syscall(0x042f) +_BFile_Seek: + syscall(0x0431) +_BFile_GetPos: + rts + mov #-1, r0 _BFile_Write: syscall(0x0435) _BFile_Read: @@ -122,7 +130,6 @@ ___realloc: /* BFile driver */ _BFile_Remove: - mov #0, r5 syscall(0x1db4) _BFile_Create: syscall(0x1dae) @@ -133,6 +140,10 @@ _BFile_Close: syscall(0x1da4) _BFile_Size: syscall(0x1da6) +_BFile_Seek: + syscall(0x1da9) +_BFile_GetPos: + syscall(0x1dab) _BFile_Write: syscall(0x1daf) _BFile_Read: