From ef03aa4cbe50079b6edc9977cf677ea9fd0e409a Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Thu, 21 Dec 2023 16:51:26 +0100 Subject: [PATCH] Custom devoptab wrappers (part 5). This commit implements a devoptab wrapper for RomFS sections within NCAs, using code from romfs.c/h. Other changes include: * devoptab: add additional safety checks and some minor tweaks to both hfs_dev and pfs_dev calls. --- source/devoptab/hfs_dev.c | 11 +- source/devoptab/nxdt_devoptab.c | 17 +- source/devoptab/nxdt_romfs_dev.c | 542 +++++++++++++++++++++++++++++++ source/devoptab/pfs_dev.c | 7 +- 4 files changed, 557 insertions(+), 20 deletions(-) create mode 100644 source/devoptab/nxdt_romfs_dev.c diff --git a/source/devoptab/hfs_dev.c b/source/devoptab/hfs_dev.c index 0d465db..3bd4a28 100644 --- a/source/devoptab/hfs_dev.c +++ b/source/devoptab/hfs_dev.c @@ -114,7 +114,7 @@ static int hfsdev_open(struct _reent *r, void *fd, const char *path, int flags, HFS_DEV_INIT_FS_ACCESS; /* Validate input. */ - if (!file || (flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC | O_EXCL))) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + if (!file || (flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC | O_EXCL))) DEVOPTAB_SET_ERROR_AND_EXIT(EROFS); /* Get truncated path. */ if (!(path = hfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; @@ -124,7 +124,7 @@ static int hfsdev_open(struct _reent *r, void *fd, const char *path, int flags, /* Reset file descriptor. */ memset(file, 0, sizeof(HashFileSystemFileState)); - /* Get information about the requested Partition FS entry. */ + /* Get information about the requested Hash FS entry. */ if (!hfsGetEntryIndexByName(fs_ctx, path, &(file->index)) || !(file->hfs_entry = hfsGetEntryByIndex(fs_ctx, file->index)) || \ !(file->name = hfsGetEntryNameByIndex(fs_ctx, file->index))) DEVOPTAB_SET_ERROR(ENOENT); @@ -202,7 +202,7 @@ static off_t hfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir) offset += pos; /* Don't allow positive seeks beyond the end of file. */ - if (offset > (off_t)file->hfs_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + if (offset > (off_t)file->hfs_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EOVERFLOW); LOG_MSG_DEBUG("Seeking to offset 0x%lX from \"%s:/%s\".", offset, dev_ctx->name, file->name); @@ -247,7 +247,7 @@ static int hfsdev_stat(struct _reent *r, const char *file, struct stat *st) LOG_MSG_DEBUG("Getting file stats for \"%s:/%s\".", dev_ctx->name, file); - /* Get information about the requested Partition FS entry. */ + /* Get information about the requested Hash FS entry. */ if (!hfsGetEntryIndexByName(fs_ctx, file, &index) || !(hfs_entry = hfsGetEntryByIndex(fs_ctx, index))) DEVOPTAB_SET_ERROR(ENOENT); /* Fill stat info. */ @@ -315,7 +315,7 @@ static int hfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, /* Fill bogus directory entry. */ memset(filestat, 0, sizeof(struct stat)); - filestat->st_nlink = 1; + filestat->st_nlink = (2 + hfsGetEntryCount(fs_ctx)); // One for self, one for parent. filestat->st_mode = (S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH); filestat->st_atime = filestat->st_mtime = filestat->st_ctime = dev_ctx->mount_time; @@ -332,6 +332,7 @@ static int hfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, /* Get Hash FS entry. */ if (!(hfs_entry = hfsGetEntryByIndex(fs_ctx, dir->index)) || !(fname = hfsGetEntryName(fs_ctx, hfs_entry))) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); + if (strlen(fname) > NAME_MAX) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); /* Copy filename. */ strcpy(filename, fname); diff --git a/source/devoptab/nxdt_devoptab.c b/source/devoptab/nxdt_devoptab.c index 8a6251d..426e30d 100644 --- a/source/devoptab/nxdt_devoptab.c +++ b/source/devoptab/nxdt_devoptab.c @@ -43,6 +43,7 @@ static const u32 g_devoptabDeviceCount = MAX_ELEMENTS(g_devoptabDevices); const devoptab_t *pfsdev_get_devoptab(); const devoptab_t *hfsdev_get_devoptab(); +const devoptab_t *romfsdev_get_devoptab(); static bool devoptabMountDevice(void *fs_ctx, const char *name, u8 type); static DevoptabDeviceContext *devoptabFindDevice(const char *name); @@ -58,10 +59,7 @@ bool devoptabMountPartitionFileSystemDevice(PartitionFileSystemContext *pfs_ctx, bool ret = false; - SCOPED_LOCK(&g_devoptabMutex) - { - ret = devoptabMountDevice(pfs_ctx, name, DevoptabDeviceType_PartitionFileSystem); - } + SCOPED_LOCK(&g_devoptabMutex) ret = devoptabMountDevice(pfs_ctx, name, DevoptabDeviceType_PartitionFileSystem); return ret; } @@ -76,10 +74,7 @@ bool devoptabMountHashFileSystemDevice(HashFileSystemContext *hfs_ctx, const cha bool ret = false; - SCOPED_LOCK(&g_devoptabMutex) - { - ret = devoptabMountDevice(hfs_ctx, name, DevoptabDeviceType_HashFileSystem); - } + SCOPED_LOCK(&g_devoptabMutex) ret = devoptabMountDevice(hfs_ctx, name, DevoptabDeviceType_HashFileSystem); return ret; } @@ -94,10 +89,7 @@ bool devoptabMountRomFileSystemDevice(RomFileSystemContext *romfs_ctx, const cha bool ret = false; - SCOPED_LOCK(&g_devoptabMutex) - { - ret = devoptabMountDevice(romfs_ctx, name, DevoptabDeviceType_RomFileSystem); - } + SCOPED_LOCK(&g_devoptabMutex) ret = devoptabMountDevice(romfs_ctx, name, DevoptabDeviceType_RomFileSystem); return ret; } @@ -176,6 +168,7 @@ static bool devoptabMountDevice(void *fs_ctx, const char *name, u8 type) device = hfsdev_get_devoptab(); break; case DevoptabDeviceType_RomFileSystem: + device = romfsdev_get_devoptab(); break; default: break; diff --git a/source/devoptab/nxdt_romfs_dev.c b/source/devoptab/nxdt_romfs_dev.c new file mode 100644 index 0000000..5c5a0a9 --- /dev/null +++ b/source/devoptab/nxdt_romfs_dev.c @@ -0,0 +1,542 @@ +/* + * nxdt_romfs_dev.c + * + * Loosely based on romfs_dev.c from libnx, et al. + * + * Copyright (c) 2020-2023, DarkMatterCore . + * + * This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool). + * + * nxdumptool is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * nxdumptool is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "nxdt_utils.h" +#include "nxdt_devoptab.h" +#include "ro_dev.h" + +/* Helper macros. */ + +#define ROMFS_DEV_INIT_VARS DEVOPTAB_INIT_VARS(RomFileSystemContext) +#define ROMFS_DEV_INIT_FILE_VARS DEVOPTAB_INIT_FILE_VARS(RomFileSystemContext, RomFileSystemFileState) +#define ROMFS_DEV_INIT_DIR_VARS DEVOPTAB_INIT_DIR_VARS(RomFileSystemContext, RomFileSystemDirectoryState) +#define ROMFS_DEV_INIT_FS_ACCESS DEVOPTAB_DECL_FS_CTX(RomFileSystemContext) + +#define ROMFS_FILE_INODE(file) ((u64)(file - fs_ctx->file_table) + (fs_ctx->dir_table_size / 4)) +#define ROMFS_DIR_INODE(dir) (u64)(dir - fs_ctx->dir_table) + +/* Type definitions. */ + +typedef struct { + RomFileSystemFileEntry *file_entry; ///< RomFS file entry metadata. + u64 data_offset; ///< Current offset within RomFS file entry data. +} RomFileSystemFileState; + +typedef struct { + RomFileSystemDirectoryEntry *dir_entry; ///< RomFS directory entry metadata. + u8 state; ///< 0: "." entry; 1: ".." entry; 2: actual RomFS entry. + u64 cur_dir_offset; ///< Offset to current child directory entry within the RomFS directory table. + u64 cur_file_offset; ///< Offset to current child file entry within the RomFS file table. +} RomFileSystemDirectoryState; + +/* Function prototypes. */ + +static int romfsdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode); +static int romfsdev_close(struct _reent *r, void *fd); +static ssize_t romfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len); +static off_t romfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir); +static int romfsdev_fstat(struct _reent *r, void *fd, struct stat *st); +static int romfsdev_stat(struct _reent *r, const char *file, struct stat *st); +static DIR_ITER* romfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path); +static int romfsdev_dirreset(struct _reent *r, DIR_ITER *dirState); +static int romfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat); +static int romfsdev_dirclose(struct _reent *r, DIR_ITER *dirState); +static int romfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf); + +static const char *romfsdev_get_truncated_path(struct _reent *r, const char *path); + +static void romfsdev_fill_file_stat(struct stat *st, const RomFileSystemContext *fs_ctx, const RomFileSystemFileEntry *file_entry, time_t mount_time); +static void romfsdev_fill_dir_stat(struct stat *st, RomFileSystemContext *fs_ctx, RomFileSystemDirectoryEntry *dir_entry, time_t mount_time); + +static nlink_t romfsdev_get_dir_nlink(RomFileSystemContext *ctx, RomFileSystemDirectoryEntry *dir_entry); + +/* Global variables. */ + +static const devoptab_t romfsdev_devoptab = { + .name = NULL, + .structSize = sizeof(RomFileSystemFileState), + .open_r = romfsdev_open, + .close_r = romfsdev_close, + .write_r = rodev_write, ///< Not supported by RomFS sections. + .read_r = romfsdev_read, + .seek_r = romfsdev_seek, + .fstat_r = romfsdev_fstat, + .stat_r = romfsdev_stat, + .link_r = rodev_link, ///< Not supported by RomFS sections. + .unlink_r = rodev_unlink, ///< Not supported by RomFS sections. + .chdir_r = rodev_chdir, ///< No need to deal with cwd shenanigans, so we won't support it. + .rename_r = rodev_rename, ///< Not supported by RomFS sections. + .mkdir_r = rodev_mkdir, ///< Not supported by RomFS sections. + .dirStateSize = sizeof(RomFileSystemDirectoryState), + .diropen_r = romfsdev_diropen, + .dirreset_r = romfsdev_dirreset, + .dirnext_r = romfsdev_dirnext, + .dirclose_r = romfsdev_dirclose, + .statvfs_r = romfsdev_statvfs, + .ftruncate_r = rodev_ftruncate, ///< Not supported by RomFS sections. + .fsync_r = rodev_fsync, ///< Not supported by RomFS sections. + .deviceData = NULL, + .chmod_r = rodev_chmod, ///< Not supported by RomFS sections. + .fchmod_r = rodev_fchmod, ///< Not supported by RomFS sections. + .rmdir_r = rodev_rmdir, ///< Not supported by RomFS sections. + .lstat_r = romfsdev_stat, ///< Symlinks aren't supported, so we'll just alias lstat() to stat(). + .utimes_r = rodev_utimes, ///< Not supported by RomFS sections. + .fpathconf_r = rodev_fpathconf, ///< Not supported by RomFS sections. + .pathconf_r = rodev_pathconf, ///< Not supported by RomFS sections. + .symlink_r = rodev_symlink, ///< Not supported by RomFS sections. + .readlink_r = rodev_readlink ///< Not supported by RomFS sections. +}; + +const devoptab_t *romfsdev_get_devoptab() +{ + return &romfsdev_devoptab; +} + +static int romfsdev_open(struct _reent *r, void *fd, const char *path, int flags, int mode) +{ + NX_IGNORE_ARG(mode); + + ROMFS_DEV_INIT_FILE_VARS; + ROMFS_DEV_INIT_FS_ACCESS; + + /* Validate input. */ + if (!file || (flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC | O_EXCL))) DEVOPTAB_SET_ERROR_AND_EXIT(EROFS); + + /* Get truncated path. */ + if (!(path = romfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; + + LOG_MSG_DEBUG("Opening \"%s:%s\" with flags 0x%X.", dev_ctx->name, path, flags); + + /* Reset file descriptor. */ + memset(file, 0, sizeof(RomFileSystemFileState)); + + /* Get information about the requested RomFS file entry. */ + if (!(file->file_entry = romfsGetFileEntryByPath(fs_ctx, path))) DEVOPTAB_SET_ERROR(ENOENT); + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static int romfsdev_close(struct _reent *r, void *fd) +{ + ROMFS_DEV_INIT_FILE_VARS; + + /* Sanity check. */ + if (!file) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + LOG_MSG_DEBUG("Closing file \"%.*s\" from \"%s:\".", (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); + + /* Reset file descriptor. */ + memset(file, 0, sizeof(RomFileSystemFileState)); + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static ssize_t romfsdev_read(struct _reent *r, void *fd, char *ptr, size_t len) +{ + ROMFS_DEV_INIT_FILE_VARS; + ROMFS_DEV_INIT_FS_ACCESS; + + /* Sanity check. */ + if (!file || !ptr || !len) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + LOG_MSG_DEBUG("Reading 0x%lX byte(s) at offset 0x%lX from file \"%.*s\" in \"%s:\".", len, file->data_offset, (int)file->file_entry->name_length, file->file_entry->name, \ + dev_ctx->name); + + /* Read file data. */ + if (!romfsReadFileEntryData(fs_ctx, file->file_entry, ptr, len, file->data_offset)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); + + /* Adjust offset. */ + file->data_offset += len; + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT((ssize_t)len); +} + +static off_t romfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir) +{ + off_t offset = 0; + + ROMFS_DEV_INIT_FILE_VARS; + + /* Sanity check. */ + if (!file) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + /* Find the offset to seek from. */ + switch(dir) + { + case SEEK_SET: /* Set absolute position relative to zero (start offset). */ + break; + case SEEK_CUR: /* Set position relative to the current position. */ + offset = (off_t)file->data_offset; + break; + case SEEK_END: /* Set position relative to EOF. */ + offset = (off_t)file->file_entry->size; + break; + default: /* Invalid option. */ + DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + } + + /* Don't allow negative seeks beyond the beginning of file. */ + if (pos < 0 && offset < -pos) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + /* Calculate actual offset. */ + offset += pos; + + /* Don't allow positive seeks beyond the end of file. */ + if (offset > (off_t)file->file_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EOVERFLOW); + + LOG_MSG_DEBUG("Seeking to offset 0x%lX from file \"%.*s\" in \"%s:\".", offset, (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); + + /* Adjust offset. */ + file->data_offset = (u64)offset; + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(offset); +} + +static int romfsdev_fstat(struct _reent *r, void *fd, struct stat *st) +{ + ROMFS_DEV_INIT_FILE_VARS; + ROMFS_DEV_INIT_FS_ACCESS; + + /* Sanity check. */ + if (!file || !st) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + LOG_MSG_DEBUG("Getting stats for file \"%.*s\" in \"%s:\".", (int)file->file_entry->name_length, file->file_entry->name, dev_ctx->name); + + /* Fill stat info. */ + romfsdev_fill_file_stat(st, fs_ctx, file->file_entry, dev_ctx->mount_time); + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static int romfsdev_stat(struct _reent *r, const char *file, struct stat *st) +{ + RomFileSystemFileEntry *file_entry = NULL; + + ROMFS_DEV_INIT_VARS; + ROMFS_DEV_INIT_FS_ACCESS; + + /* Sanity check. */ + if (!st) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + /* Get truncated path. */ + if (!(file = romfsdev_get_truncated_path(r, file))) DEVOPTAB_EXIT; + + LOG_MSG_DEBUG("Getting file stats for \"%s:%s\".", dev_ctx->name, file); + + /* Get information about the requested RomFS file entry. */ + if (!(file_entry = romfsGetFileEntryByPath(fs_ctx, file))) DEVOPTAB_SET_ERROR(ENOENT); + + /* Fill stat info. */ + romfsdev_fill_file_stat(st, fs_ctx, file_entry, dev_ctx->mount_time); + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static DIR_ITER *romfsdev_diropen(struct _reent *r, DIR_ITER *dirState, const char *path) +{ + DIR_ITER *ret = NULL; + + ROMFS_DEV_INIT_DIR_VARS; + ROMFS_DEV_INIT_FS_ACCESS; + + /* Get truncated path. */ + if (!(path = romfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; + + LOG_MSG_DEBUG("Opening directory \"%s:%s\".", dev_ctx->name, path); + + /* Reset directory state. */ + memset(dir, 0, sizeof(RomFileSystemDirectoryState)); + + /* Get information about the requested RomFS directory entry. */ + if (!(dir->dir_entry = romfsGetDirectoryEntryByPath(fs_ctx, path))) DEVOPTAB_SET_ERROR(ENOENT); + + dir->cur_dir_offset = dir->dir_entry->directory_offset; + dir->cur_file_offset = dir->dir_entry->file_offset; + + /* Update return value. */ + ret = dirState; + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_PTR(ret); +} + +static int romfsdev_dirreset(struct _reent *r, DIR_ITER *dirState) +{ + ROMFS_DEV_INIT_DIR_VARS; + + LOG_MSG_DEBUG("Resetting state for directory \"%.*s\" in \"%s:\".", (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name); + + /* Reset directory state. */ + dir->state = 0; + dir->cur_dir_offset = dir->dir_entry->directory_offset; + dir->cur_file_offset = dir->dir_entry->file_offset; + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static int romfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) +{ + ROMFS_DEV_INIT_DIR_VARS; + ROMFS_DEV_INIT_FS_ACCESS; + + /* Sanity check. */ + if (!filename || !filestat) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + LOG_MSG_DEBUG("Getting info for next entry from directory \"%.*s\" in \"%s:\" (state %u, cur_dir_offset 0x%lX, cur_file_offset 0x%lX).", \ + (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name, dir->state, dir->cur_dir_offset, dir->cur_file_offset); + + if (dir->state < 2) + { + RomFileSystemDirectoryEntry *dir_entry = (dir->state == 0 ? dir->dir_entry : romfsGetDirectoryEntryByOffset(fs_ctx, dir->dir_entry->parent_offset)); + if (!dir_entry) DEVOPTAB_SET_ERROR_AND_EXIT(EFAULT); + + /* Fill directory entry. */ + romfsdev_fill_dir_stat(filestat, fs_ctx, dir_entry, dev_ctx->mount_time); + strcpy(filename, dir->state == 0 ? "." : ".."); + + /* Update state. */ + dir->state++; + + DEVOPTAB_EXIT; + } + + if (dir->cur_dir_offset != ROMFS_VOID_ENTRY) + { + /* Get next directory entry. */ + RomFileSystemDirectoryEntry *dir_entry = romfsGetDirectoryEntryByOffset(fs_ctx, dir->cur_dir_offset); + if (!dir_entry) DEVOPTAB_SET_ERROR_AND_EXIT(EFAULT); + if (dir_entry->name_length > NAME_MAX) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); + + /* Fill directory entry. */ + romfsdev_fill_dir_stat(filestat, fs_ctx, dir_entry, dev_ctx->mount_time); + snprintf(filename, NAME_MAX + 1, "%.*s", (int)dir_entry->name_length, dir_entry->name); + + /* Update child directory offset. */ + dir->cur_dir_offset = dir_entry->next_offset; + + DEVOPTAB_EXIT; + } + + if (dir->cur_file_offset != ROMFS_VOID_ENTRY) + { + /* Get next file entry. */ + RomFileSystemFileEntry *file_entry = romfsGetFileEntryByOffset(fs_ctx, dir->cur_file_offset); + if (!file_entry) DEVOPTAB_SET_ERROR_AND_EXIT(EFAULT); + if (file_entry->name_length > NAME_MAX) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); + + /* Fill file entry. */ + romfsdev_fill_file_stat(filestat, fs_ctx, file_entry, dev_ctx->mount_time); + snprintf(filename, NAME_MAX + 1, "%.*s", (int)file_entry->name_length, file_entry->name); + + /* Update child file offset. */ + dir->cur_file_offset = file_entry->next_offset; + + DEVOPTAB_EXIT; + } + + /* We have reached EOD. */ + DEVOPTAB_SET_ERROR(ENOENT); + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static int romfsdev_dirclose(struct _reent *r, DIR_ITER *dirState) +{ + ROMFS_DEV_INIT_DIR_VARS; + + LOG_MSG_DEBUG("Closing directory \"%.*s\" in \"%s:\".", (int)dir->dir_entry->name_length, dir->dir_entry->name, dev_ctx->name); + + /* Reset directory state. */ + memset(dir, 0, sizeof(RomFileSystemDirectoryState)); + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static int romfsdev_statvfs(struct _reent *r, const char *path, struct statvfs *buf) +{ + NX_IGNORE_ARG(path); + + u64 ext_fs_size = 0; + + ROMFS_DEV_INIT_VARS; + ROMFS_DEV_INIT_FS_ACCESS; + + /* Sanity check. */ + if (!buf) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + LOG_MSG_DEBUG("Getting filesystem stats for \"%s:\"", dev_ctx->name); + + /* Get RomFS total data size. */ + if (!romfsGetTotalDataSize(fs_ctx, false, &ext_fs_size)) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); + + /* Fill filesystem stats. */ + memset(buf, 0, sizeof(struct statvfs)); + + buf->f_bsize = 1; + buf->f_frsize = 1; + buf->f_blocks = ext_fs_size; + buf->f_bfree = 0; + buf->f_bavail = 0; + buf->f_files = 0; + buf->f_ffree = 0; + buf->f_favail = 0; + buf->f_fsid = 0; + buf->f_flag = ST_NOSUID; + buf->f_namemax = FS_MAX_PATH; + +end: + DEVOPTAB_DEINIT_VARS; + DEVOPTAB_RETURN_INT(0); +} + +static const char *romfsdev_get_truncated_path(struct _reent *r, const char *path) +{ + const u8 *p = (const u8*)path; + ssize_t units = 0; + u32 code = 0; + size_t len = 0; + + DEVOPTAB_DECL_ERROR_STATE; + + if (!r || !path || !*path) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + LOG_MSG_DEBUG("Input path: \"%s\".", path); + + /* Move the path pointer to the start of the actual path. */ + do { + units = decode_utf8(&code, p); + if (units < 0) DEVOPTAB_SET_ERROR_AND_EXIT(EILSEQ); + p += units; + } while(code >= ' ' && code != ':'); + + /* We found a colon; p points to the actual path. */ + if (code == ':') path = (const char*)p; + + /* Make sure the provided path starts with a slash. */ + if (path[0] != '/') DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + + /* Make sure there are no more colons and that the remainder of the string is valid UTF-8. */ + p = (const u8*)path; + + do { + units = decode_utf8(&code, p); + if (units < 0) DEVOPTAB_SET_ERROR_AND_EXIT(EILSEQ); + if (code == ':') DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + p += units; + } while(code >= ' '); + + /* Verify fixed path length. */ + len = strlen(path); + if (len >= FS_MAX_PATH) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); + + LOG_MSG_DEBUG("Truncated path: \"%s\".", path); + +end: + DEVOPTAB_RETURN_PTR(path); +} + +static void romfsdev_fill_file_stat(struct stat *st, const RomFileSystemContext *fs_ctx, const RomFileSystemFileEntry *file_entry, time_t mount_time) +{ + /* Clear stat struct. */ + memset(st, 0, sizeof(struct stat)); + + /* Fill stat struct. */ + st->st_ino = ROMFS_FILE_INODE(file_entry); + st->st_mode = (S_IFREG | S_IRUSR | S_IRGRP | S_IROTH); + st->st_nlink = 1; + st->st_size = (off_t)file_entry->size; + st->st_atime = st->st_mtime = st->st_ctime = mount_time; +} + +static void romfsdev_fill_dir_stat(struct stat *st, RomFileSystemContext *fs_ctx, RomFileSystemDirectoryEntry *dir_entry, time_t mount_time) +{ + /* Clear stat struct. */ + memset(st, 0, sizeof(struct stat)); + + /* Fill stat struct. */ + st->st_ino = ROMFS_DIR_INODE(dir_entry); + st->st_mode = (S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH); + st->st_nlink = romfsdev_get_dir_nlink(fs_ctx, dir_entry); + st->st_size = ALIGN_UP(sizeof(RomFileSystemDirectoryEntry) + dir_entry->name_length, ROMFS_TABLE_ENTRY_ALIGNMENT); + st->st_atime = st->st_mtime = st->st_ctime = mount_time; +} + +static nlink_t romfsdev_get_dir_nlink(RomFileSystemContext *fs_ctx, RomFileSystemDirectoryEntry *dir_entry) +{ + u64 cur_entry_offset = 0; + nlink_t count = 2; // One for self, one for parent. + + /* Short-circuit: check if we're dealing with an empty directory. */ + if (dir_entry->file_offset == ROMFS_VOID_ENTRY && dir_entry->directory_offset == ROMFS_VOID_ENTRY) return count; + + /* Loop through the child file entries' linked list. */ + cur_entry_offset = dir_entry->file_offset; + while(cur_entry_offset != ROMFS_VOID_ENTRY) + { + /* Get current file entry. */ + RomFileSystemFileEntry *cur_file_entry = romfsGetFileEntryByOffset(fs_ctx, cur_entry_offset); + if (!cur_file_entry) break; + + /* Update count. */ + count++; + + /* Update current file entry offset. */ + cur_entry_offset = cur_file_entry->next_offset; + } + + /* Loop through the child directory entries' linked list. */ + cur_entry_offset = dir_entry->directory_offset; + while(cur_entry_offset != ROMFS_VOID_ENTRY) + { + /* Get current directory entry. */ + RomFileSystemDirectoryEntry *cur_dir_entry = romfsGetDirectoryEntryByOffset(fs_ctx, cur_entry_offset); + if (!cur_dir_entry) break; + + /* Update count. */ + count++; + + /* Update current directory entry offset. */ + cur_entry_offset = cur_dir_entry->next_offset; + } + + return count; +} diff --git a/source/devoptab/pfs_dev.c b/source/devoptab/pfs_dev.c index 375779e..1de6d6b 100644 --- a/source/devoptab/pfs_dev.c +++ b/source/devoptab/pfs_dev.c @@ -114,7 +114,7 @@ static int pfsdev_open(struct _reent *r, void *fd, const char *path, int flags, PFS_DEV_INIT_FS_ACCESS; /* Validate input. */ - if (!file || (flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC | O_EXCL))) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + if (!file || (flags & (O_WRONLY | O_RDWR | O_APPEND | O_CREAT | O_TRUNC | O_EXCL))) DEVOPTAB_SET_ERROR_AND_EXIT(EROFS); /* Get truncated path. */ if (!(path = pfsdev_get_truncated_path(r, path))) DEVOPTAB_EXIT; @@ -202,7 +202,7 @@ static off_t pfsdev_seek(struct _reent *r, void *fd, off_t pos, int dir) offset += pos; /* Don't allow positive seeks beyond the end of file. */ - if (offset > (off_t)file->pfs_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EINVAL); + if (offset > (off_t)file->pfs_entry->size) DEVOPTAB_SET_ERROR_AND_EXIT(EOVERFLOW); LOG_MSG_DEBUG("Seeking to offset 0x%lX from \"%s:/%s\".", offset, dev_ctx->name, file->name); @@ -315,7 +315,7 @@ static int pfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, /* Fill bogus directory entry. */ memset(filestat, 0, sizeof(struct stat)); - filestat->st_nlink = 1; + filestat->st_nlink = (2 + pfsGetEntryCount(fs_ctx)); // One for self, one for parent. filestat->st_mode = (S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH); filestat->st_atime = filestat->st_mtime = filestat->st_ctime = dev_ctx->mount_time; @@ -332,6 +332,7 @@ static int pfsdev_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, /* Get Partition FS entry. */ if (!(pfs_entry = pfsGetEntryByIndex(fs_ctx, dir->index)) || !(fname = pfsGetEntryName(fs_ctx, pfs_entry))) DEVOPTAB_SET_ERROR_AND_EXIT(EIO); + if (strlen(fname) > NAME_MAX) DEVOPTAB_SET_ERROR_AND_EXIT(ENAMETOOLONG); /* Copy filename. */ strcpy(filename, fname);