devoptab: add mounts (wrapper around all mounts, exposed via MTP/FTP). lots of fixes (see below).

- updated libhaze to 81154c1.
- increase ftpsrv stack size as it would crash when modifying custom mounts.
- fix warning for unused log data in haze.
- fix eof read for nsp/xci source by instead returning 0 for bytes read, rather than error.
- add support for lstat the root of a mount.
- handle zero size reads when reading games via devoptab.
This commit is contained in:
ITotalJustice
2025-09-21 03:51:13 +01:00
parent 0a2c16db0c
commit 3c504cc85d
11 changed files with 427 additions and 50 deletions

View File

@@ -133,6 +133,7 @@ add_executable(sphaira
source/utils/devoptab_vfs.cpp source/utils/devoptab_vfs.cpp
source/utils/devoptab_fatfs.cpp source/utils/devoptab_fatfs.cpp
source/utils/devoptab_game.cpp source/utils/devoptab_game.cpp
source/utils/devoptab_mounts.cpp
source/utils/devoptab.cpp source/utils/devoptab.cpp
source/usb/base.cpp source/usb/base.cpp
@@ -399,7 +400,7 @@ endif()
if (ENABLE_LIBHAZE) if (ENABLE_LIBHAZE)
FetchContent_Declare(libhaze FetchContent_Declare(libhaze
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
GIT_TAG 6e24502 GIT_TAG 81154c1
) )
FetchContent_MakeAvailable(libhaze) FetchContent_MakeAvailable(libhaze)

View File

@@ -882,9 +882,52 @@ private:
Mutex* const m_mutex; Mutex* const m_mutex;
}; };
struct ScopedRMutex {
ScopedRMutex(RMutex* _mutex) : mutex{_mutex} {
rmutexLock(mutex);
}
~ScopedRMutex() {
rmutexUnlock(mutex);
}
ScopedRMutex(const ScopedRMutex&) = delete;
void operator=(const ScopedRMutex&) = delete;
private:
RMutex* const mutex;
};
struct ScopedRwLock {
ScopedRwLock(RwLock* _lock, bool _write) : lock{_lock}, write{_write} {
if (write) {
rwlockWriteLock(lock);
} else {
rwlockReadLock(lock);
}
}
~ScopedRwLock() {
if (write) {
rwlockWriteUnlock(lock);
} else {
rwlockReadUnlock(lock);
}
}
ScopedRwLock(const ScopedRwLock&) = delete;
void operator=(const ScopedRwLock&) = delete;
private:
RwLock* const lock;
bool const write;
};
// #define ON_SCOPE_EXIT(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }}; // #define ON_SCOPE_EXIT(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
#define ON_SCOPE_EXIT(_f) ScopeGuard ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }}; #define ON_SCOPE_EXIT(_f) ScopeGuard ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
#define SCOPED_MUTEX(_m) ScopedMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m} #define SCOPED_MUTEX(_m) ScopedMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m}
#define SCOPED_RMUTEX(_m) ScopedRMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m}
#define SCOPED_RWLOCK(_m, _write) ScopedRwLock ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m, _write}
// #define ON_SCOPE_FAIL(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_FAILED(rc)) { _f; } }}; // #define ON_SCOPE_FAIL(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_FAILED(rc)) { _f; } }};
// #define ON_SCOPE_SUCCESS(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_SUCCEEDED(rc)) { _f; } }}; // #define ON_SCOPE_SUCCESS(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_SUCCEEDED(rc)) { _f; } }};

View File

@@ -28,6 +28,7 @@ Result MountNfsAll();
Result MountSmb2All(); Result MountSmb2All();
Result MountFatfsAll(); Result MountFatfsAll();
Result MountGameAll(); Result MountGameAll();
Result MountInternalMounts();
Result GetNetworkDevices(location::StdioEntries& out); Result GetNetworkDevices(location::StdioEntries& out);
void UmountAllNeworkDevices(); void UmountAllNeworkDevices();

View File

@@ -1731,6 +1731,11 @@ App::App(const char* argv0) {
devoptab::MountFatfsAll(); devoptab::MountFatfsAll();
} }
{
SCOPED_TIMESTAMP("mounts init");
devoptab::MountInternalMounts();
}
{ {
SCOPED_TIMESTAMP("timestamp init"); SCOPED_TIMESTAMP("timestamp init");
// ini_putl(GetExePath(), "timestamp", m_start_timestamp, App::PLAYLOG_PATH); // ini_putl(GetExePath(), "timestamp", m_start_timestamp, App::PLAYLOG_PATH);

View File

@@ -39,11 +39,11 @@ Mutex g_mutex{};
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) { void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
log_write("[FTPSRV] %s\n", msg); log_write("[FTPSRV] %s\n", msg);
sphaira::App::NotifyFlashLed(); App::NotifyFlashLed();
} }
void ftp_progress_callback(void) { void ftp_progress_callback(void) {
sphaira::App::NotifyFlashLed(); App::NotifyFlashLed();
} }
InstallSharedData g_shared_data{}; InstallSharedData g_shared_data{};
@@ -389,6 +389,17 @@ const char* vfs_stdio_readdir(void* user, void* user_entry) {
} }
int vfs_stdio_dirlstat(void* user, const void* user_entry, const char* _path, struct stat* st) { int vfs_stdio_dirlstat(void* user, const void* user_entry, const char* _path, struct stat* st) {
// could probably be optimised to th below, but we won't know its r/w perms.
#if 0
auto entry = static_cast<FtpVfsDirEntry*>(user_entry);
if (entry->buf->d_type == DT_DIR) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
st->st_nlink = 1;
return 0;
}
#else
#endif
const auto path = vfs_stdio_fix_path(_path); const auto path = vfs_stdio_fix_path(_path);
return lstat(path, st); return lstat(path, st);
} }
@@ -512,6 +523,11 @@ void loop(void* arg) {
.user = NULL, .user = NULL,
.func = &g_vfs_stdio, .func = &g_vfs_stdio,
}, },
{
.name = "mounts",
.user = NULL,
.func = &g_vfs_stdio,
},
{ {
.name = "install", .name = "install",
.user = NULL, .user = NULL,
@@ -559,7 +575,7 @@ bool Init() {
// or load everything in the init thread. // or load everything in the init thread.
Result rc; Result rc;
if (R_FAILED(rc = utils::CreateThread(&g_thread, loop, nullptr, 1024*16))) { if (R_FAILED(rc = utils::CreateThread(&g_thread, loop, nullptr))) {
log_write("[FTP] failed to create nxlink thread: 0x%X\n", rc); log_write("[FTP] failed to create nxlink thread: 0x%X\n", rc);
return false; return false;
} }

View File

@@ -65,11 +65,6 @@ struct FsProxyBase : haze::FileSystemProxyImpl {
fs::FsPath buf; fs::FsPath buf;
const auto len = std::strlen(GetName()); const auto len = std::strlen(GetName());
// if (!base || !base[0]) {
// std::strcpy(buf, path);
// return buf;
// }
if (len && !strncasecmp(path, GetName(), len)) { if (len && !strncasecmp(path, GetName(), len)) {
std::snprintf(buf, sizeof(buf), "%s/%s", base, path + len); std::snprintf(buf, sizeof(buf), "%s/%s", base, path + len);
} else { } else {
@@ -636,9 +631,9 @@ struct FsInstallProxy final : FsProxyVfs {
haze::FsEntries g_fs_entries{}; haze::FsEntries g_fs_entries{};
void haze_callback(const haze::CallbackData *data) { void haze_callback(const haze::CallbackData *data) {
#if 0
auto& e = *data; auto& e = *data;
#if 0
switch (e.type) { switch (e.type) {
case haze::CallbackType_OpenSession: log_write("[LIBHAZE] Opening Session\n"); break; case haze::CallbackType_OpenSession: log_write("[LIBHAZE] Opening Session\n"); break;
case haze::CallbackType_CloseSession: log_write("[LIBHAZE] Closing Session\n"); break; case haze::CallbackType_CloseSession: log_write("[LIBHAZE] Closing Session\n"); break;
@@ -677,6 +672,7 @@ bool Init() {
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeSd>(), "", "microSD card")); g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeSd>(), "", "microSD card"));
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeImage>(FsImageDirectoryId_Sd), "Album", "Album (Image SD)")); g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsNativeImage>(FsImageDirectoryId_Sd), "Album", "Album (Image SD)"));
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsStdio>(true, "games:/"), "Games", "Games")); g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsStdio>(true, "games:/"), "Games", "Games"));
g_fs_entries.emplace_back(std::make_shared<FsProxy>(std::make_unique<fs::FsStdio>(true, "mounts:/"), "Mounts", "Mounts"));
g_fs_entries.emplace_back(std::make_shared<FsDevNullProxy>("DevNull", "DevNull (Speed Test)")); g_fs_entries.emplace_back(std::make_shared<FsDevNullProxy>("DevNull", "DevNull (Speed Test)"));
g_fs_entries.emplace_back(std::make_shared<FsInstallProxy>("install", "Install (NSP, XCI, NSZ, XCZ)")); g_fs_entries.emplace_back(std::make_shared<FsInstallProxy>("install", "Install (NSP, XCI, NSZ, XCZ)"));

View File

@@ -234,6 +234,12 @@ Result CreateSave(u64 app_id, AccountUid uid) {
} // namespace } // namespace
Result NspEntry::Read(void* buf, s64 off, s64 size, u64* bytes_read) { Result NspEntry::Read(void* buf, s64 off, s64 size, u64* bytes_read) {
if (off == nsp_size) {
log_write("[NspEntry::Read] read at eof...\n");
*bytes_read = 0;
R_SUCCEED();
}
if (off < nsp_data.size()) { if (off < nsp_data.size()) {
*bytes_read = size = ClipSize(off, size, nsp_data.size()); *bytes_read = size = ClipSize(off, size, nsp_data.size());
std::memcpy(buf, nsp_data.data() + off, size); std::memcpy(buf, nsp_data.data() + off, size);

View File

@@ -235,6 +235,12 @@ struct XciSource final : dump::BaseSource {
int icon{}; int icon{};
Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override { Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) override {
if (off == xci_size) {
log_write("[XciSource::Read] read at eof...\n");
*bytes_read = 0;
R_SUCCEED();
}
if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI)) || path.ends_with(GetDumpTypeStr(DumpFileType_XCZ))) { if (path.ends_with(GetDumpTypeStr(DumpFileType_XCI)) || path.ends_with(GetDumpTypeStr(DumpFileType_XCZ))) {
size = ClipSize(off, size, xci_size); size = ClipSize(off, size, xci_size);
*bytes_read = size; *bytes_read = size;

View File

@@ -47,30 +47,6 @@ const char* curl_url_strerror_wrap(CURLUcode code) {
} }
} }
struct ScopedRwLock {
ScopedRwLock(RwLock* _lock, bool _write) : lock{_lock}, write{_write} {
if (write) {
rwlockWriteLock(lock);
} else {
rwlockReadLock(lock);
}
}
~ScopedRwLock() {
if (write) {
rwlockWriteUnlock(lock);
} else {
rwlockReadUnlock(lock);
}
}
private:
RwLock* const lock;
bool const write;
};
#define SCOPED_RWLOCK(_m, _write) ScopedRwLock ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m, _write}
struct Device { struct Device {
std::unique_ptr<MountDevice> mount_device; std::unique_ptr<MountDevice> mount_device;
size_t file_size; size_t file_size;
@@ -408,6 +384,14 @@ int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
SCOPED_RWLOCK(&g_rwlock, false); SCOPED_RWLOCK(&g_rwlock, false);
SCOPED_MUTEX(&device->mutex); SCOPED_MUTEX(&device->mutex);
// special case: root of the device.
const auto dilem = std::strchr(_path, ':');
if (dilem && (dilem > _path) && (dilem[1] == '\0' || (dilem[1] == '/' && dilem[2] == '\0'))) {
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
st->st_nlink = 1;
return r->_errno = 0;
}
char path[PATH_MAX]{}; char path[PATH_MAX]{};
if (!device->mount_device->fix_path(_path, path)) { if (!device->mount_device->fix_path(_path, path)) {
return set_errno(r, ENOENT); return set_errno(r, ENOENT);
@@ -494,7 +478,7 @@ int devoptab_utimes(struct _reent *r, const char *_path, const struct timeval ti
SCOPED_MUTEX(&device->mutex); SCOPED_MUTEX(&device->mutex);
if (!times) { if (!times) {
log_write("[NFS] devoptab_utimes() times is null\n"); log_write("[DEVOPTAB] devoptab_utimes() times is null\n");
return set_errno(r, EINVAL); return set_errno(r, EINVAL);
} }

View File

@@ -299,9 +299,13 @@ ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
const auto& nsp = file->nsp; const auto& nsp = file->nsp;
len = std::min<u64>(len, nsp->nsp_size - file->off); len = std::min<u64>(len, nsp->nsp_size - file->off);
if (!len) {
return 0;
}
u64 bytes_read; u64 bytes_read;
if (R_FAILED(nsp->Read(ptr, file->off, len, &bytes_read))) { if (R_FAILED(nsp->Read(ptr, file->off, len, &bytes_read))) {
log_write("[GAME] failed to read from nsp %s off: %zu len: %zu size: %zu\n", nsp->path.s, file->off, len, nsp->nsp_size);
return -EIO; return -EIO;
} }
@@ -398,28 +402,32 @@ int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
filestat->st_nlink = 1; filestat->st_nlink = 1;
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
std::strcpy(filename, entry.name.c_str()); std::strcpy(filename, entry.name.c_str());
dir->index++;
} else { } else {
auto& entry = dir->entry; auto& entry = dir->entry;
if (dir->index >= entry->contents.size()) { do {
log_write("[GAME] dirnext: no more entries\n"); if (dir->index >= entry->contents.size()) {
return -ENOENT; log_write("[GAME] dirnext: no more entries\n");
}
const auto& content = entry->contents[dir->index];
if (!content.nsp) {
if (!FindNspFromEntry(*entry, content.status.application_id)) {
log_write("[GAME] failed to find nsp for content id: %016lx\n", content.status.application_id);
return -ENOENT; return -ENOENT;
} }
}
filestat->st_nlink = 1; const auto& content = entry->contents[dir->index];
filestat->st_size = content.nsp->nsp_size; if (!content.nsp) {
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; if (!FindNspFromEntry(*entry, content.status.application_id)) {
std::snprintf(filename, NAME_MAX, "%s", content.nsp->path.s); log_write("[GAME] failed to find nsp for content id: %016lx\n", content.status.application_id);
continue;
}
}
filestat->st_nlink = 1;
filestat->st_size = content.nsp->nsp_size;
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
std::snprintf(filename, NAME_MAX, "%s", content.nsp->path.s);
dir->index++;
break;
} while (dir->index++);
} }
dir->index++;
return 0; return 0;
} }

View File

@@ -0,0 +1,311 @@
#include "utils/devoptab.hpp"
#include "utils/devoptab_common.hpp"
#include "defines.hpp"
#include "log.hpp"
#include "location.hpp"
#include <cstring>
#include <cerrno>
#include <array>
#include <memory>
#include <algorithm>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
namespace sphaira::devoptab {
namespace {
struct File {
int fd;
};
struct Dir {
DIR* dir;
location::StdioEntries* entries;
u32 index;
};
struct Device final : common::MountDevice {
Device(const common::MountConfig& _config)
: MountDevice{_config} {
}
private:
bool Mount() override { return true; }
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
int devoptab_close(void *fd) override;
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
ssize_t devoptab_seek(void *fd, off_t pos, int dir) override;
int devoptab_fstat(void *fd, struct stat *st) override;
int devoptab_unlink(const char *path) override;
int devoptab_rename(const char *oldName, const char *newName) override;
int devoptab_mkdir(const char *path, int mode) override;
int devoptab_rmdir(const char *path) override;
int devoptab_diropen(void* fd, const char *path) override;
int devoptab_dirreset(void* fd) override;
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
int devoptab_dirclose(void* fd) override;
int devoptab_lstat(const char *path, struct stat *st) override;
int devoptab_ftruncate(void *fd, off_t len) override;
int devoptab_statvfs(const char *path, struct statvfs *buf) override;
int devoptab_fsync(void *fd) override;
int devoptab_utimes(const char *path, const struct timeval times[2]) override;
};
// converts "/[SMB] pi:/folder/file.txt" to "pi:"
auto FixPath(const char* path) -> std::pair<fs::FsPath, std::string_view> {
while (*path == '/') {
path++;
}
std::string_view mount_name = path;
const auto dilem = mount_name.find_first_of(':');
if (dilem == std::string_view::npos) {
return {path, {}};
}
mount_name = mount_name.substr(0, dilem + 1);
fs::FsPath fixed_path = path;
if (fixed_path.ends_with(":")) {
fixed_path += '/';
}
log_write("[MOUNTS] FixPath: %s -> %s, mount: %.*s\n", path, fixed_path.s, (int)mount_name.size(), mount_name.data());
return {fixed_path, mount_name};
}
int Device::devoptab_open(void *fileStruct, const char *_path, int flags, int mode) {
auto file = static_cast<File*>(fileStruct);
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
log_write("[MOUNTS] devoptab_open: invalid path: %s\n", _path);
return -ENOENT;
}
file->fd = open(path, flags, mode);
if (file->fd < 0) {
log_write("[MOUNTS] devoptab_open: failed to open %s: %s\n", path.s, std::strerror(errno));
return -errno;
}
return 0;
}
int Device::devoptab_close(void *fd) {
auto file = static_cast<File*>(fd);
std::memset(file, 0, sizeof(*file));
return 0;
}
ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
auto file = static_cast<File*>(fd);
return read(file->fd, ptr, len);
}
ssize_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
auto file = static_cast<File*>(fd);
return lseek(file->fd, pos, dir);
}
int Device::devoptab_fstat(void *fd, struct stat *st) {
auto file = static_cast<File*>(fd);
return fstat(file->fd, st);
}
int Device::devoptab_unlink(const char *_path) {
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
log_write("[MOUNTS] devoptab_unlink: invalid path: %s\n", _path);
return -ENOENT;
}
return unlink(path);
}
int Device::devoptab_rename(const char *_oldName, const char *_newName) {
const auto [oldName, old_mount_name] = FixPath(_oldName);
const auto [newName, new_mount_name] = FixPath(_newName);
if (old_mount_name.empty() || new_mount_name.empty() || old_mount_name != new_mount_name) {
log_write("[MOUNTS] devoptab_rename: invalid path: %s or %s\n", _oldName, _newName);
return -ENOENT;
}
return rename(oldName, newName);
}
int Device::devoptab_mkdir(const char *_path, int mode) {
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
log_write("[MOUNTS] devoptab_mkdir: invalid path: %s\n", _path);
return -ENOENT;
}
return mkdir(path, mode);
}
int Device::devoptab_rmdir(const char *_path) {
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
log_write("[MOUNTS] devoptab_rmdir: invalid path: %s\n", _path);
return -ENOENT;
}
return rmdir(path);
}
int Device::devoptab_diropen(void* fd, const char *_path) {
auto dir = static_cast<Dir*>(fd);
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
dir->entries = new location::StdioEntries();
const auto entries = location::GetStdio(false);
for (auto& entry : entries) {
if (entry.fs_hidden) {
continue;
}
dir->entries->emplace_back(std::move(entry));
}
return 0;
} else {
dir->dir = opendir(path);
if (!dir->dir) {
log_write("[MOUNTS] devoptab_diropen: failed to open dir %s: %s\n", path.s, std::strerror(errno));
return -errno;
}
return 0;
}
return -ENOENT;
}
int Device::devoptab_dirreset(void* fd) {
auto dir = static_cast<Dir*>(fd);
if (dir->dir) {
rewinddir(dir->dir);
} else {
dir->index = 0;
}
return 0;
}
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
log_write("[MOUNTS] devoptab_dirnext\n");
auto dir = static_cast<Dir*>(fd);
if (dir->dir) {
const auto entry = readdir(dir->dir);
if (!entry) {
log_write("[MOUNTS] devoptab_dirnext: no more entries\n");
return -ENOENT;
}
// todo: verify this.
filestat->st_nlink = 1;
filestat->st_mode = entry->d_type == DT_DIR ? S_IFDIR : S_IFREG;
std::snprintf(filename, NAME_MAX, "%s", entry->d_name);
} else {
if (dir->index >= dir->entries->size()) {
return -ENOENT;
}
const auto& entry = (*dir->entries)[dir->index];
filestat->st_nlink = 1;
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
if (entry.mount.ends_with(":/")) {
std::snprintf(filename, NAME_MAX, "%s", entry.mount.substr(0, entry.mount.size() - 1).c_str());
} else {
std::snprintf(filename, NAME_MAX, "%s", entry.mount.c_str());
}
}
dir->index++;
return 0;
}
int Device::devoptab_dirclose(void* fd) {
auto dir = static_cast<Dir*>(fd);
if (dir->dir) {
closedir(dir->dir);
} else if (dir->entries) {
delete dir->entries;
}
return 0;
}
int Device::devoptab_lstat(const char *_path, struct stat *st) {
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
st->st_nlink = 1;
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
} else {
return lstat(path, st);
}
return -ENOENT;
}
int Device::devoptab_ftruncate(void *fd, off_t len) {
auto file = static_cast<File*>(fd);
return ftruncate(file->fd, len);
}
int Device::devoptab_statvfs(const char *_path, struct statvfs *buf) {
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
log_write("[MOUNTS] devoptab_statvfs: invalid path: %s\n", _path);
return -ENOENT;
}
return statvfs(path, buf);
}
int Device::devoptab_fsync(void *fd) {
auto file = static_cast<File*>(fd);
return fsync(file->fd);
}
int Device::devoptab_utimes(const char *_path, const struct timeval times[2]) {
const auto [path, mount_name] = FixPath(_path);
if (mount_name.empty()) {
log_write("[MOUNTS] devoptab_utimes: invalid path: %s\n", _path);
return -ENOENT;
}
return utimes(path, times);
}
} // namespace
Result MountInternalMounts() {
common::MountConfig config{};
config.fs_hidden = true;
config.dump_hidden = true;
if (!common::MountNetworkDevice2(
std::make_unique<Device>(config),
config,
sizeof(File), sizeof(Dir),
"mounts", "mounts:/"
)) {
log_write("[MOUNTS] Failed to mount\n");
R_THROW(0x1);
}
R_SUCCEED();
}
} // namespace sphaira::devoptab