diff --git a/libctru/source/sdmc_dev.c b/libctru/source/sdmc_dev.c new file mode 100644 index 0000000..98be3c5 --- /dev/null +++ b/libctru/source/sdmc_dev.c @@ -0,0 +1,802 @@ +#include +#include +#include +#include +#include <3ds.h> + +/*! @internal + * + * @file sdmc_dev.c + * + * SDMC Device + */ + +static int sdmc_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode); +static int sdmc_close(struct _reent *r, int fd); +static ssize_t sdmc_write(struct _reent *r, int fd, const char *ptr, size_t len); +static ssize_t sdmc_read(struct _reent *r, int fd, char *ptr, size_t len); +static off_t sdmc_seek(struct _reent *r, int fd, off_t pos, int dir); +static int sdmc_fstat(struct _reent *r, int fd, struct stat *st); +static int sdmc_stat(struct _reent *r, const char *file, struct stat *st); +static int sdmc_link(struct _reent *r, const char *existing, const char *newLink); +static int sdmc_unlink(struct _reent *r, const char *name); +static int sdmc_chdir(struct _reent *r, const char *name); +static int sdmc_rename(struct _reent *r, const char *oldName, const char *newName); +static int sdmc_mkdir(struct _reent *r, const char *path, int mode); +static DIR_ITER* sdmc_diropen(struct _reent *r, DIR_ITER *dirState, const char *path); +static int sdmc_dirreset(struct _reent *r, DIR_ITER *dirState); +static int sdmc_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); +static int sdmc_dirclose(struct _reent *r, DIR_ITER *dirState); +static int sdmc_statvfs(struct _reent *r, const char *path, struct statvfs *buf); +static int sdmc_ftruncate(struct _reent *r, int fd, off_t len); +static int sdmc_fsync(struct _reent *r, int fd); +static int sdmc_chmod(struct _reent *r, const char *path, mode_t mode); +static int sdmc_fchmod(struct _reent *r, int fd, mode_t mode); + +/*! @cond INTERNAL */ + +/*! Open file struct */ +typedef struct +{ + Handle fd; /*! CTRU handle */ + int flags; /*! Flags used in open(2) */ + u64 offset; /*! Current file offset */ +} sdmc_file_t; + +/*! Open directory struct */ +typedef struct +{ + Handle fd; /*! CTRU handle */ + FS_dirent entry_data; /*! Temporary storage for reading entries */ +} sdmc_dir_t; + +/*! SDMC devoptab */ +static devoptab_t +sdmc_devoptab = +{ + .name = "sdmc", + .structSize = sizeof(sdmc_file_t), + .open_r = sdmc_open, + .close_r = sdmc_close, + .write_r = sdmc_write, + .read_r = sdmc_read, + .seek_r = sdmc_seek, + .fstat_r = sdmc_fstat, + .stat_r = sdmc_stat, + .link_r = sdmc_link, + .unlink_r = sdmc_unlink, + .chdir_r = sdmc_chdir, + .rename_r = sdmc_rename, + .mkdir_r = sdmc_mkdir, + .dirStateSize = sizeof(sdmc_dir_t), + .diropen_r = sdmc_diropen, + .dirreset_r = sdmc_dirreset, + .dirnext_r = sdmc_dirnext, + .dirclose_r = sdmc_dirclose, + .statvfs_r = sdmc_statvfs, + .ftruncate_r = sdmc_ftruncate, + .fsync_r = sdmc_fsync, + .deviceData = NULL, + .chmod_r = sdmc_chmod, + .fchmod_r = sdmc_fchmod, +}; + +/*! SDMC archive handle */ +static FS_archive sdmcArchive = +{ + .id = 0x00000009, + .lowPath = + { + .type = PATH_EMPTY, + .size = 1, + .data = (u8*)"", + }, +}; + +/*! @endcond */ + +/*! Initialize SDMC device */ +Result sdmcInit(void) +{ + Result rc; + + rc = FSUSER_OpenArchive(NULL, &sdmcArchive); + if(rc == 0) + AddDevice(&sdmc_devoptab); + + return rc; +} + +/*! Clean up SDMC device */ +Result sdmcExit(void) +{ + Result rc; + + rc = FSUSER_CloseArchive(NULL, &sdmcArchive); + if(rc == 0) + RemoveDevice("sdmc"); + + return rc; +} + +/*! 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 +sdmc_open(struct _reent *r, + void *fileStruct, + const char *path, + int flags, + int mode) +{ + Handle fd; + Result rc; + u32 sdmc_flags = 0; + u32 attributes = FS_ATTRIBUTE_NONE; + + /* get pointer to our data */ + sdmc_file_t *file = (sdmc_file_t*)fileStruct; + + /* check access mode */ + switch(flags & O_ACCMODE) + { + /* read-only: do not allow O_APPEND */ + case O_RDONLY: + sdmc_flags |= FS_OPEN_READ; + if(flags & O_APPEND) + { + r->_errno = EINVAL; + return -1; + } + break; + + /* write-only */ + case O_WRONLY: + sdmc_flags |= FS_OPEN_WRITE; + break; + + /* read and write */ + case O_RDWR: + sdmc_flags |= (FS_OPEN_READ | FS_OPEN_WRITE); + break; + + /* an invalid option was supplied */ + default: + r->_errno = EINVAL; + return -1; + } + + /* create file */ + if(flags & O_CREAT) + sdmc_flags |= FS_OPEN_CREATE; + + /* TODO: Test O_EXCL. */ + + /* set attributes */ + if(!(mode & S_IWUSR)) + attributes |= FS_ATTRIBUTE_READONLY; + + /* open the file */ + rc = FSUSER_OpenFile(NULL, &fd, sdmcArchive, FS_makePath(PATH_CHAR, path), + sdmc_flags, attributes); + if(rc == 0) + { + file->fd = fd; + file->flags = (flags & (O_ACCMODE|O_APPEND|O_SYNC)); + file->offset = 0; + return 0; + } + + r->_errno = rc; + return -1; +} + +/*! Close an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to sdmc_file_t + * + * @returns 0 for success + * @returns -1 for error + */ +static int +sdmc_close(struct _reent *r, + int fd) +{ + Result rc; + + /* get pointer to our data */ + sdmc_file_t *file = (sdmc_file_t*)fd; + + rc = FSFILE_Close(file->fd); + if(rc == 0) + return 0; + + r->_errno = rc; + return -1; +} + +/*! Write to an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in,out] fd Pointer to sdmc_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 +sdmc_write(struct _reent *r, + int fd, + const char *ptr, + size_t len) +{ + Result rc; + u32 bytes; + u32 sync = 0; + u64 offset; + + /* get pointer to our data */ + sdmc_file_t *file = (sdmc_file_t*)fd; + + /* check that the file was opened with write access */ + if((file->flags & O_ACCMODE) == O_RDONLY) + { + r->_errno = EBADF; + return -1; + } + + /* check if this is synchronous or not */ + if(file->flags & O_SYNC) + sync = 0x10001; + + /* initialize offset */ + offset = file->offset; + if(file->flags & O_APPEND) + { + /* append means write from the end of the file */ + rc = FSFILE_GetSize(file->fd, &offset); + if(rc != 0) + { + r->_errno = rc; + return -1; + } + } + + /* TODO: Copy to internal buffer and write in chunks. + * You cannot write from read-only memory. + */ + + /* write the data */ + rc = FSFILE_Write(file->fd, &bytes, offset, (u32*)ptr, (u32)len, sync); + if(rc == 0) + { + /* update current file offset; if O_APPEND, this moves it to the + * new end-of-file + */ + file->offset = offset + bytes; + return (ssize_t)bytes; + } + + r->_errno = rc; + return -1; +} + +/*! Read from an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in,out] fd Pointer to sdmc_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 +sdmc_read(struct _reent *r, + int fd, + char *ptr, + size_t len) +{ + Result rc; + u32 bytes; + + /* get pointer to our data */ + sdmc_file_t *file = (sdmc_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 = FSFILE_Read(file->fd, &bytes, file->offset, (u32*)ptr, (u32)len); + if(rc == 0) + { + /* update current file offset */ + file->offset += bytes; + return (ssize_t)bytes; + } + + r->_errno = rc; + return -1; +} + +/*! Update an open file's current offset + * + * @param[in,out] r newlib reentrancy struct + * @param[in,out] fd Pointer to sdmc_file_t + * @param[in] pos Offset to seek to + * @param[in] whence Where to seek from + * + * @returns 0 for success + * @returns -1 for error + */ +static off_t +sdmc_seek(struct _reent *r, + int fd, + off_t pos, + int whence) +{ + Result rc; + u64 offset; + + /* get pointer to our data */ + sdmc_file_t *file = (sdmc_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 = FSFILE_GetSize(file->fd, &offset); + if(rc != 0) + { + r->_errno = 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 0; +} + +/*! Get file stats from an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to sdmc_file_t + * @param[out] st Pointer to file stats to fill + * + * @returns 0 for success + * @returns -1 for error + */ +static int +sdmc_fstat(struct _reent *r, + int fd, + struct stat *st) +{ + r->_errno = ENOSYS; + 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 +sdmc_stat(struct _reent *r, + const char *file, + struct stat *st) +{ + r->_errno = ENOSYS; + return -1; +} + +/*! Hard link a file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] existing Path of ile to link + * @param[in] newLink Path of new link + * + * @returns 0 for success + * @returns -1 for error + */ +static int +sdmc_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 +sdmc_unlink(struct _reent *r, + const char *name) +{ + r->_errno = ENOSYS; + 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 +sdmc_chdir(struct _reent *r, + const char *name) +{ + r->_errno = ENOSYS; + 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 +sdmc_rename(struct _reent *r, + const char *oldName, + const char *newName) +{ + r->_errno = ENOSYS; + 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 +sdmc_mkdir(struct _reent *r, + const char *path, + int mode) +{ + Result rc; + + /* TODO: Use mode to set directory attributes. */ + + rc = FSUSER_CreateDirectory(NULL, sdmcArchive, FS_makePath(PATH_CHAR, path)); + if(rc == 0) + return 0; + + r->_errno = ENOSYS; + 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* +sdmc_diropen(struct _reent *r, + DIR_ITER *dirState, + const char *path) +{ + Handle fd; + Result rc; + + /* get pointer to our data */ + sdmc_dir_t *dir = (sdmc_dir_t*)(dirState->dirStruct); + + /* open the directory */ + rc = FSUSER_OpenDirectory(NULL, &fd, sdmcArchive, FS_makePath(PATH_CHAR, path)); + if(rc == 0) + { + dir->fd = fd; + memset(&dir->entry_data, 0, sizeof(dir->entry_data)); + return dirState; + } + + r->_errno = 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 +sdmc_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 +sdmc_dirnext(struct _reent *r, + DIR_ITER *dirState, + char *filename, + struct stat *filestat) +{ + Result rc; + u32 entries; + u16 *name; + + /* get pointer to our data */ + sdmc_dir_t *dir = (sdmc_dir_t*)(dirState->dirStruct); + + /* fetch the next entry */ + rc = FSDIR_Read(dir->fd, &entries, 1, &dir->entry_data); + if(rc == 0) + { + if(entries == 0) + { + /* there are no more entries; ENOENT signals end-of-directory */ + r->_errno = ENOENT; + return -1; + } + + /* fill in the stat info */ + filestat->st_ino = 0; + if(dir->entry_data.isDirectory) + filestat->st_mode = S_IFDIR; + else + filestat->st_mode = S_IFREG; + + /* copy the name */ + name = dir->entry_data.name; + while(*name) + *filename++ = *name++; + *filename = 0; + + return 0; + } + + r->_errno = 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 +sdmc_dirclose(struct _reent *r, + DIR_ITER *dirState) +{ + Result rc; + + /* get pointer to our data */ + sdmc_dir_t *dir = (sdmc_dir_t*)(dirState->dirStruct); + + /* close the directory */ + rc = FSDIR_Close(dir->fd); + if(rc == 0) + return 0; + + r->_errno = 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 +sdmc_statvfs(struct _reent *r, + const char *path, + struct statvfs *buf) +{ + Result rc; + u32 clusterSize, numClusters, freeClusters; + u32 writable = 0; + + rc = FSUSER_GetSdmcArchiveResource(NULL, + NULL, + &clusterSize, + &numClusters, + &freeClusters); + + if(rc == 0) + { + buf->f_bsize = clusterSize; + buf->f_frsize = clusterSize; + buf->f_blocks = numClusters; + buf->f_bfree = freeClusters; + buf->f_bavail = freeClusters; + buf->f_files = 0; //??? how to get + buf->f_ffree = freeClusters; + buf->f_favail = freeClusters; + buf->f_fsid = 0; //??? how to get + buf->f_flag = ST_NOSUID; + buf->f_namemax = 0; //??? how to get + + rc = FSUSER_IsSdmcWritable(NULL, &writable); + if(rc != 0 || !writable) + buf->f_flag |= ST_RDONLY; + + return 0; + } + + r->_errno = rc; + return -1; +} + +/*! Truncate an open file + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to sdmc_file_t + * @param[in] len Length to truncate file to + * + * @returns 0 for success + * @returns -1 for error + */ +static int +sdmc_ftruncate(struct _reent *r, + int fd, + off_t len) +{ + Result rc; + + /* get pointer to our data */ + sdmc_file_t *file = (sdmc_file_t*)fd; + + /* make sure length is non-negative */ + if(len < 0) + { + r->_errno = EINVAL; + return -1; + } + + /* set the new file size */ + rc = FSFILE_SetSize(file->fd, len); + if(rc == 0) + return 0; + + r->_errno = rc; + return -1; +} + +/*! Synchronize a file to media + * + * @param[in,out] r newlib reentrancy struct + * @param[in] fd Pointer to sdmc_file_t + * + * @returns 0 for success + * @returns -1 for error + */ +static int +sdmc_fsync(struct _reent *r, + int fd) +{ + Result rc; + + /* get pointer to our data */ + sdmc_file_t *file = (sdmc_file_t*)fd; + + rc = FSFILE_Flush(file->fd); + if(rc == 0) + return 0; + + r->_errno = 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 +sdmc_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 sdmc_file_t + * @param[in] mode New mode to set + * + * @returns 0 for success + * @returns -1 for failure + */ +static int +sdmc_fchmod(struct _reent *r, + int fd, + mode_t mode) +{ + r->_errno = ENOSYS; + return -1; +}