fatfs: use devoptab mounting. devoptab: add config for hidding from fs and dump, fix http being writeable.
This commit is contained in:
@@ -90,7 +90,6 @@ add_executable(sphaira
|
||||
source/threaded_file_transfer.cpp
|
||||
source/title_info.cpp
|
||||
source/minizip_helper.cpp
|
||||
source/fatfs.cpp
|
||||
source/usbdvd.cpp
|
||||
|
||||
source/utils/utils.cpp
|
||||
@@ -111,6 +110,7 @@ add_executable(sphaira
|
||||
source/utils/devoptab_ftp.cpp
|
||||
source/utils/devoptab_webdav.cpp
|
||||
source/utils/devoptab_vfs.cpp
|
||||
source/utils/devoptab_fatfs.cpp
|
||||
|
||||
source/usb/base.cpp
|
||||
source/usb/usbds.cpp
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::fatfs {
|
||||
|
||||
Result MountAll();
|
||||
void UnmountAll();
|
||||
|
||||
} // namespace sphaira::fatfs
|
||||
@@ -23,12 +23,15 @@ struct StdioEntry {
|
||||
u32 flags{};
|
||||
// optional dump path inside the mount point.
|
||||
std::string dump_path{};
|
||||
// set to hide for filebrowser.
|
||||
bool fs_hidden{};
|
||||
// set to hide in dump list.
|
||||
bool dump_hidden{};
|
||||
};
|
||||
|
||||
using StdioEntries = std::vector<StdioEntry>;
|
||||
|
||||
// set write=true to filter out write protected devices.
|
||||
auto GetStdio(bool write) -> StdioEntries;
|
||||
auto GetFat() -> StdioEntries;
|
||||
|
||||
} // namespace sphaira::location
|
||||
|
||||
@@ -39,6 +39,7 @@ Result MountHttpAll();
|
||||
Result MountFtpAll();
|
||||
Result MountNfsAll();
|
||||
Result MountSmb2All();
|
||||
Result MountFatfsAll();
|
||||
|
||||
Result GetNetworkDevices(location::StdioEntries& out);
|
||||
void UmountAllNeworkDevices();
|
||||
|
||||
@@ -134,6 +134,8 @@ struct MountConfig {
|
||||
bool read_only{};
|
||||
bool no_stat_file{true};
|
||||
bool no_stat_dir{true};
|
||||
bool fs_hidden{};
|
||||
bool dump_hidden{};
|
||||
|
||||
std::unordered_map<std::string, std::string> extra{};
|
||||
};
|
||||
@@ -212,6 +214,9 @@ private:
|
||||
};
|
||||
|
||||
using CreateDeviceCallback = std::function<std::unique_ptr<MountDevice>(const MountConfig& config)>;
|
||||
Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name);
|
||||
Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, bool force_read_only = false);
|
||||
|
||||
// same as above but takes in the device and expects the mount name to be set.
|
||||
bool MountNetworkDevice2(std::unique_ptr<MountDevice>&& device, const MountConfig& config, size_t file_size, size_t dir_size, const char* name, const char* mount_name);
|
||||
|
||||
} // namespace sphaira::devoptab::common
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
#include "haze_helper.hpp"
|
||||
#include "web.hpp"
|
||||
#include "swkbd.hpp"
|
||||
#include "fatfs.hpp"
|
||||
#include "usbdvd.hpp"
|
||||
|
||||
#include "utils/profile.hpp"
|
||||
@@ -1579,13 +1578,6 @@ App::App(const char* argv0) {
|
||||
usbHsFsInitialize(1);
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("fat init");
|
||||
if (R_FAILED(fatfs::MountAll())) {
|
||||
log_write("[FAT] failed to mount bis\n");
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("usbdvd init");
|
||||
if (R_FAILED(usbdvd::MountAll())) {
|
||||
@@ -1629,6 +1621,11 @@ App::App(const char* argv0) {
|
||||
devoptab::MountSmb2All();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("fatfs init");
|
||||
devoptab::MountFatfsAll();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("timestamp init");
|
||||
// ini_putl(GetExePath(), "timestamp", m_start_timestamp, App::PLAYLOG_PATH);
|
||||
@@ -2226,11 +2223,6 @@ App::~App() {
|
||||
usbHsFsExit();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("fatfs exit");
|
||||
fatfs::UnmountAll();
|
||||
}
|
||||
|
||||
// this has to come before curl exit as it uses curl global.
|
||||
{
|
||||
SCOPED_TIMESTAMP("devoptab exit");
|
||||
|
||||
@@ -494,11 +494,17 @@ void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocat
|
||||
ui::PopupList::Items items;
|
||||
std::vector<DumpEntry> dump_entries;
|
||||
|
||||
out.stdio = location::GetStdio(true);
|
||||
const auto stdio_entries = location::GetStdio(true);
|
||||
if (location_flags & (1 << DumpLocationType_Stdio)) {
|
||||
for (s32 i = 0; i < std::size(out.stdio); i++) {
|
||||
dump_entries.emplace_back(DumpLocationType_Stdio, i);
|
||||
items.emplace_back(out.stdio[i].name);
|
||||
for (auto& e : stdio_entries) {
|
||||
if (e.dump_hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto index = out.stdio.size();
|
||||
dump_entries.emplace_back(DumpLocationType_Stdio, index);
|
||||
items.emplace_back(e.name);
|
||||
out.stdio.emplace_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
#include "usbdvd.hpp"
|
||||
#include "utils/devoptab.hpp"
|
||||
|
||||
#include <ff.h>
|
||||
#include <cstring>
|
||||
#include <minIni.h>
|
||||
#include <usbhsfs.h>
|
||||
|
||||
namespace sphaira::location {
|
||||
@@ -17,13 +15,17 @@ namespace {
|
||||
auto GetStdio(bool write) -> StdioEntries {
|
||||
StdioEntries out{};
|
||||
|
||||
const auto add_from_entries = [](const StdioEntries& entries, StdioEntries& out, bool write) {
|
||||
for (const auto& e : entries) {
|
||||
const auto add_from_entries = [](StdioEntries& entries, StdioEntries& out, bool write) {
|
||||
for (auto& e : entries) {
|
||||
if (write && (e.flags & FsEntryFlag::FsEntryFlag_ReadOnly)) {
|
||||
log_write("[STDIO] skipping read only mount: %s\n", e.name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (e.flags & FsEntryFlag::FsEntryFlag_ReadOnly) {
|
||||
e.name += " (Read Only)";
|
||||
}
|
||||
|
||||
out.emplace_back(e);
|
||||
}
|
||||
};
|
||||
@@ -80,20 +82,4 @@ auto GetStdio(bool write) -> StdioEntries {
|
||||
return out;
|
||||
}
|
||||
|
||||
auto GetFat() -> StdioEntries {
|
||||
StdioEntries out{};
|
||||
|
||||
for (auto& e : VolumeStr) {
|
||||
char path[64];
|
||||
std::snprintf(path, sizeof(path), "%s:/", e);
|
||||
|
||||
char display_name[0x100];
|
||||
std::snprintf(display_name, sizeof(display_name), "%s (Read Only)", path);
|
||||
|
||||
out.emplace_back(path, display_name, FsEntryFlag::FsEntryFlag_ReadOnly);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace sphaira::location
|
||||
|
||||
@@ -1595,12 +1595,6 @@ void FsView::DisplayOptions() {
|
||||
SidebarEntryArray::Items mount_items;
|
||||
std::vector<FsEntry> fs_entries;
|
||||
|
||||
const auto stdio_locations = location::GetStdio(false);
|
||||
for (const auto& e: stdio_locations) {
|
||||
fs_entries.emplace_back(e.name, e.mount, FsType::Stdio, e.flags);
|
||||
mount_items.push_back(e.name);
|
||||
}
|
||||
|
||||
for (const auto& e: FS_ENTRIES) {
|
||||
fs_entries.emplace_back(e);
|
||||
mount_items.push_back(i18n::get(e.name));
|
||||
@@ -1611,8 +1605,12 @@ void FsView::DisplayOptions() {
|
||||
mount_items.push_back(m_menu->m_custom_fs_entry.name);
|
||||
}
|
||||
|
||||
const auto fat_entries = location::GetFat();
|
||||
for (const auto& e: fat_entries) {
|
||||
const auto stdio_locations = location::GetStdio(false);
|
||||
for (const auto& e: stdio_locations) {
|
||||
if (e.fs_hidden) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fs_entries.emplace_back(e.name, e.mount, FsType::Stdio, e.flags);
|
||||
mount_items.push_back(e.name);
|
||||
}
|
||||
|
||||
@@ -759,7 +759,68 @@ void update_devoptab_for_read_only(devoptab_t* devoptab, bool read_only) {
|
||||
}
|
||||
}
|
||||
|
||||
Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name) {
|
||||
bool MountNetworkDevice2(std::unique_ptr<MountDevice>&& device, const MountConfig& config, size_t file_size, size_t dir_size, const char* name, const char* mount_name) {
|
||||
if (!device) {
|
||||
log_write("[DEVOPTAB] No device for %s\n", mount_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool already_mounted = false;
|
||||
for (const auto& entry : g_entries) {
|
||||
if (entry && entry->mount == mount_name) {
|
||||
already_mounted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (already_mounted) {
|
||||
log_write("[DEVOPTAB] Already mounted %s, skipping\n", mount_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// otherwise, find next free entry.
|
||||
auto itr = std::ranges::find_if(g_entries, [](auto& e){
|
||||
return !e;
|
||||
});
|
||||
|
||||
if (itr == g_entries.end()) {
|
||||
log_write("[DEVOPTAB] No free entries to mount %s\n", mount_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto entry = std::make_unique<Entry>();
|
||||
entry->device.mount_device = std::forward<decltype(device)>(device);
|
||||
entry->device.file_size = file_size;
|
||||
entry->device.dir_size = dir_size;
|
||||
entry->device.config = config;
|
||||
|
||||
if (!entry->device.mount_device) {
|
||||
log_write("[DEVOPTAB] Failed to create device for %s\n", config.url.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
entry->devoptab = DEVOPTAB;
|
||||
entry->devoptab.name = entry->name;
|
||||
entry->devoptab.deviceData = &entry->device;
|
||||
std::snprintf(entry->name, sizeof(entry->name), "%s", name);
|
||||
std::snprintf(entry->mount, sizeof(entry->mount), "%s", mount_name);
|
||||
common::update_devoptab_for_read_only(&entry->devoptab, config.read_only);
|
||||
|
||||
if (AddDevice(&entry->devoptab) < 0) {
|
||||
log_write("[DEVOPTAB] Failed to add device %s\n", mount_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
log_write("[DEVOPTAB] DEVICE SUCCESS %s %s\n", name, mount_name);
|
||||
|
||||
entry->ref_count++;
|
||||
*itr = std::move(entry);
|
||||
log_write("[DEVOPTAB] Mounted %s at /%s\n", name, mount_name);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, bool force_read_only) {
|
||||
{
|
||||
static Mutex rw_lock_init_mutex{};
|
||||
SCOPED_MUTEX(&rw_lock_init_mutex);
|
||||
@@ -809,6 +870,10 @@ Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file
|
||||
e->back().no_stat_file = ini_parse_getbool(Value, e->back().no_stat_file);
|
||||
} else if (!std::strcmp(Key, "no_stat_dir")) {
|
||||
e->back().no_stat_dir = ini_parse_getbool(Value, e->back().no_stat_dir);
|
||||
} else if (!std::strcmp(Key, "fs_hidden")) {
|
||||
e->back().fs_hidden = ini_parse_getbool(Value, e->back().fs_hidden);
|
||||
} else if (!std::strcmp(Key, "dump_hidden")) {
|
||||
e->back().dump_hidden = ini_parse_getbool(Value, e->back().dump_hidden);
|
||||
} else {
|
||||
log_write("[DEVOPTAB] INI: extra key %s=%s\n", Key, Value);
|
||||
e->back().extra.emplace(Key, Value);
|
||||
@@ -824,55 +889,31 @@ Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file
|
||||
ini_browse(cb, &configs, config_path);
|
||||
log_write("[DEVOPTAB] Found %zu mount configs\n", configs.size());
|
||||
|
||||
for (const auto& config : configs) {
|
||||
// check if we already have the http mounted.
|
||||
bool already_mounted = false;
|
||||
for (const auto& entry : g_entries) {
|
||||
if (entry && entry->mount == config.name) {
|
||||
already_mounted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (already_mounted) {
|
||||
log_write("[DEVOPTAB] Already mounted %s, skipping\n", config.name.c_str());
|
||||
for (auto& config : configs) {
|
||||
if (config.name.empty()) {
|
||||
log_write("[DEVOPTAB] Skipping empty name\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
// otherwise, find next free entry.
|
||||
auto itr = std::ranges::find_if(g_entries, [](auto& e){
|
||||
return !e;
|
||||
});
|
||||
|
||||
if (itr == g_entries.end()) {
|
||||
log_write("[DEVOPTAB] No free entries to mount %s\n", config.name.c_str());
|
||||
break;
|
||||
}
|
||||
|
||||
auto entry = std::make_unique<Entry>();
|
||||
entry->device.mount_device = create_device(config);
|
||||
entry->device.file_size = file_size;
|
||||
entry->device.dir_size = dir_size;
|
||||
entry->device.config = config;
|
||||
|
||||
if (!entry->device.mount_device) {
|
||||
log_write("[DEVOPTAB] Failed to create device for %s\n", config.url.c_str());
|
||||
if (config.url.empty()) {
|
||||
log_write("[DEVOPTAB] Skipping empty url for %s\n", config.name.c_str());
|
||||
continue;
|
||||
}
|
||||
|
||||
entry->devoptab = DEVOPTAB;
|
||||
entry->devoptab.name = entry->name;
|
||||
entry->devoptab.deviceData = &entry->device;
|
||||
std::snprintf(entry->name, sizeof(entry->name), "[%s] %s", name, config.name.c_str());
|
||||
std::snprintf(entry->mount, sizeof(entry->mount), "[%s] %s:/", name, config.name.c_str());
|
||||
common::update_devoptab_for_read_only(&entry->devoptab, config.read_only);
|
||||
if (force_read_only) {
|
||||
config.read_only = true;
|
||||
}
|
||||
|
||||
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
|
||||
log_write("[DEVOPTAB] DEVICE SUCCESS %s %s\n", entry->device.config.url.c_str(), entry->name);
|
||||
fs::FsPath _name{};
|
||||
std::snprintf(_name, sizeof(_name), "[%s] %s", name, config.name.c_str());
|
||||
|
||||
entry->ref_count++;
|
||||
*itr = std::move(entry);
|
||||
log_write("[DEVOPTAB] Mounted %s at /%s\n", config.url.c_str(), config.name.c_str());
|
||||
fs::FsPath _mount{};
|
||||
std::snprintf(_mount, sizeof(_mount), "[%s] %s:/", name, config.name.c_str());
|
||||
|
||||
if (!MountNetworkDevice2(create_device(config), config, file_size, dir_size, _name, _mount)) {
|
||||
log_write("[DEVOPTAB] Failed to mount %s\n", config.name.c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
@@ -1373,18 +1414,20 @@ Result GetNetworkDevices(location::StdioEntries& out) {
|
||||
|
||||
for (const auto& entry : g_entries) {
|
||||
if (entry) {
|
||||
const auto& config = entry->device.config;
|
||||
|
||||
u32 flags = 0;
|
||||
if (entry->device.config.read_only) {
|
||||
if (config.read_only) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_ReadOnly;
|
||||
}
|
||||
if (entry->device.config.no_stat_file) {
|
||||
if (config.no_stat_file) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatFile;
|
||||
}
|
||||
if (entry->device.config.no_stat_dir) {
|
||||
if (config.no_stat_dir) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatDir;
|
||||
}
|
||||
|
||||
out.emplace_back(entry->mount, entry->name, flags, entry->device.config.dump_path);
|
||||
out.emplace_back(entry->mount, entry->name, flags, config.dump_path, config.fs_hidden, config.dump_hidden);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,27 +1,49 @@
|
||||
#include "utils/devoptab.hpp"
|
||||
#include "utils/devoptab_common.hpp"
|
||||
#include "fatfs.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "utils/profile.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "ff.h"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <array>
|
||||
#include <algorithm>
|
||||
#include <span>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
#include <cerrno>
|
||||
#include <sys/iosupport.h>
|
||||
#include <sys/stat.h>
|
||||
#include <ff.h>
|
||||
|
||||
namespace sphaira::fatfs {
|
||||
namespace sphaira::devoptab {
|
||||
namespace {
|
||||
|
||||
auto is_archive(BYTE attr) -> bool {
|
||||
const auto archive_attr = AM_DIR | AM_ARC;
|
||||
return (attr & archive_attr) == archive_attr;
|
||||
}
|
||||
enum BisMountType {
|
||||
BisMountType_PRODINFOF,
|
||||
BisMountType_SAFE,
|
||||
BisMountType_USER,
|
||||
BisMountType_SYSTEM,
|
||||
};
|
||||
|
||||
struct FatStorageEntry {
|
||||
FsStorage storage;
|
||||
std::unique_ptr<common::LruBufferedData> buffered;
|
||||
FATFS fs;
|
||||
};
|
||||
|
||||
struct BisMountEntry {
|
||||
const FsBisPartitionId id;
|
||||
const char* volume_name;
|
||||
const char* mount_name;
|
||||
};
|
||||
|
||||
constexpr BisMountEntry BIS_MOUNT_ENTRIES[] {
|
||||
[BisMountType_PRODINFOF] = { FsBisPartitionId_CalibrationFile, "PRODINFOF", "PRODINFOF:/" },
|
||||
[BisMountType_SAFE] = { FsBisPartitionId_SafeMode, "SAFE", "SAFE:/" },
|
||||
[BisMountType_USER] = { FsBisPartitionId_User, "USER", "USER:/" },
|
||||
[BisMountType_SYSTEM] = { FsBisPartitionId_System, "SYSTEM", "SYSTEM:/" },
|
||||
};
|
||||
static_assert(std::size(BIS_MOUNT_ENTRIES) == FF_VOLUMES);
|
||||
|
||||
FatStorageEntry g_fat_storage[FF_VOLUMES];
|
||||
|
||||
// todo: replace with off+size and have the data be in another struct
|
||||
// in order to be more lcache efficient.
|
||||
@@ -48,14 +70,51 @@ struct File {
|
||||
FIL* files;
|
||||
u32 file_count;
|
||||
size_t off;
|
||||
char path[256];
|
||||
char path[PATH_MAX];
|
||||
};
|
||||
|
||||
struct Dir {
|
||||
FDIR dir;
|
||||
char path[256];
|
||||
char path[PATH_MAX];
|
||||
};
|
||||
|
||||
struct Device final : common::MountDevice {
|
||||
Device(BisMountType type, const common::MountConfig& _config)
|
||||
: MountDevice{_config}
|
||||
, m_type{type} {
|
||||
|
||||
}
|
||||
|
||||
~Device();
|
||||
|
||||
private:
|
||||
bool fix_path(const char* str, char* out, bool strip_leading_slash = false) override {
|
||||
std::strcpy(out, str);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Mount() override;
|
||||
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;
|
||||
off_t devoptab_seek(void *fd, off_t pos, int dir) override;
|
||||
int devoptab_fstat(void *fd, struct stat *st) 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;
|
||||
|
||||
private:
|
||||
const BisMountType m_type;
|
||||
bool mounted{};
|
||||
};
|
||||
|
||||
auto is_archive(BYTE attr) -> bool {
|
||||
const auto archive_attr = AM_DIR | AM_ARC;
|
||||
return (attr & archive_attr) == archive_attr;
|
||||
}
|
||||
|
||||
u64 get_size_from_files(const File* file) {
|
||||
u64 size = 0;
|
||||
for (u32 i = 0; i < file->file_count; i++) {
|
||||
@@ -90,38 +149,8 @@ void set_current_file_pos(File* file) {
|
||||
}
|
||||
}
|
||||
|
||||
enum BisMountType {
|
||||
BisMountType_PRODINFOF,
|
||||
BisMountType_SAFE,
|
||||
BisMountType_USER,
|
||||
BisMountType_SYSTEM,
|
||||
};
|
||||
|
||||
struct FatStorageEntry {
|
||||
FsStorage storage;
|
||||
std::unique_ptr<devoptab::common::LruBufferedData> buffered;
|
||||
FATFS fs;
|
||||
devoptab_t devoptab;
|
||||
};
|
||||
|
||||
struct BisMountEntry {
|
||||
const FsBisPartitionId id;
|
||||
const char* volume_name;
|
||||
const char* mount_name;
|
||||
};
|
||||
|
||||
constexpr BisMountEntry BIS_MOUNT_ENTRIES[] {
|
||||
[BisMountType_PRODINFOF] = { FsBisPartitionId_CalibrationFile, "PRODINFOF", "PRODINFOF:/" },
|
||||
[BisMountType_SAFE] = { FsBisPartitionId_SafeMode, "SAFE", "SAFE:/" },
|
||||
[BisMountType_USER] = { FsBisPartitionId_User, "USER", "USER:/" },
|
||||
[BisMountType_SYSTEM] = { FsBisPartitionId_System, "SYSTEM", "SYSTEM:/" },
|
||||
};
|
||||
static_assert(std::size(BIS_MOUNT_ENTRIES) == FF_VOLUMES);
|
||||
|
||||
FatStorageEntry g_fat_storage[FF_VOLUMES];
|
||||
|
||||
void fill_stat(const char* path, const FILINFO* fno, struct stat *st) {
|
||||
memset(st, 0, sizeof(*st));
|
||||
std::memset(st, 0, sizeof(*st));
|
||||
|
||||
st->st_nlink = 1;
|
||||
|
||||
@@ -133,7 +162,7 @@ void fill_stat(const char* path, const FILINFO* fno, struct stat *st) {
|
||||
tm.tm_mon = ((fno->fdate >> 5) & 0xF) - 1;
|
||||
tm.tm_year = (fno->fdate >> 9) + 80;
|
||||
|
||||
st->st_atime = mktime(&tm);
|
||||
st->st_atime = std::mktime(&tm);
|
||||
st->st_mtime = st->st_atime;
|
||||
st->st_ctime = st->st_atime;
|
||||
|
||||
@@ -143,7 +172,7 @@ void fill_stat(const char* path, const FILINFO* fno, struct stat *st) {
|
||||
char file_path[256];
|
||||
for (u16 i = 0; i < 256; i++) {
|
||||
std::snprintf(file_path, sizeof(file_path), "%s/%02u", path, i);
|
||||
FILINFO file_info;
|
||||
FILINFO file_info{};
|
||||
if (FR_OK != f_stat(file_path, &file_info)) {
|
||||
break;
|
||||
}
|
||||
@@ -152,8 +181,7 @@ void fill_stat(const char* path, const FILINFO* fno, struct stat *st) {
|
||||
}
|
||||
|
||||
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
} else
|
||||
if (fno->fattrib & AM_DIR) {
|
||||
} else if (fno->fattrib & AM_DIR) {
|
||||
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||
} else {
|
||||
st->st_size = fno->fsize;
|
||||
@@ -161,31 +189,80 @@ void fill_stat(const char* path, const FILINFO* fno, struct stat *st) {
|
||||
}
|
||||
}
|
||||
|
||||
static int set_errno(struct _reent *r, int err) {
|
||||
r->_errno = err;
|
||||
return -1;
|
||||
Device::~Device() {
|
||||
if (mounted) {
|
||||
auto& fat = g_fat_storage[m_type];
|
||||
f_unmount(BIS_MOUNT_ENTRIES[m_type].mount_name);
|
||||
fat.buffered.reset();
|
||||
fsStorageClose(&fat.storage);
|
||||
}
|
||||
}
|
||||
|
||||
int fat_open(struct _reent *r, void *fileStruct, const char *path, int flags, int mode) {
|
||||
bool Device::Mount() {
|
||||
if (mounted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
auto& fat = g_fat_storage[m_type];
|
||||
|
||||
if (!serviceIsActive(&fat.storage.s)) {
|
||||
const auto res = fsOpenBisStorage(&fat.storage, BIS_MOUNT_ENTRIES[m_type].id);
|
||||
if (R_FAILED(res)) {
|
||||
log_write("[FATFS] fsOpenBisStorage(%d) failed: 0x%x\n", BIS_MOUNT_ENTRIES[m_type].id, res);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
log_write("[FATFS] Storage for %s already opened\n", BIS_MOUNT_ENTRIES[m_type].mount_name);
|
||||
}
|
||||
|
||||
if (!fat.buffered) {
|
||||
auto source = std::make_shared<FsStorageSource>(&fat.storage);
|
||||
|
||||
s64 size;
|
||||
if (R_FAILED(source->GetSize(&size))) {
|
||||
log_write("[FATFS] Failed to get size of storage source\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
fat.buffered = std::make_unique<common::LruBufferedData>(source, size);
|
||||
if (!fat.buffered) {
|
||||
log_write("[FATFS] Failed to create LruBufferedData\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (FR_OK != f_mount(&fat.fs, BIS_MOUNT_ENTRIES[m_type].mount_name, 1)) {
|
||||
log_write("[FATFS] f_mount(%s) failed\n", BIS_MOUNT_ENTRIES[m_type].mount_name);
|
||||
return false;
|
||||
}
|
||||
|
||||
log_write("[FATFS] Mounted %s at %s\n", BIS_MOUNT_ENTRIES[m_type].volume_name, BIS_MOUNT_ENTRIES[m_type].mount_name);
|
||||
return mounted = true;
|
||||
}
|
||||
|
||||
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
|
||||
auto file = static_cast<File*>(fileStruct);
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
|
||||
// todo: init array
|
||||
// todo: handle dir.
|
||||
FIL fil{};
|
||||
if (FR_OK == f_open(&fil, path, FA_READ)) {
|
||||
file->file_count = 1;
|
||||
file->files = (FIL*)std::malloc(sizeof(*file->files));
|
||||
if (!file->files) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
file->file_count = 1;
|
||||
std::memcpy(file->files, &fil, sizeof(*file->files));
|
||||
// todo: check what error code is returned here.
|
||||
} else {
|
||||
FILINFO info{};
|
||||
if (FR_OK != f_stat(path, &info)) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (!(info.fattrib & AM_ARC)) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
char file_path[256];
|
||||
@@ -198,33 +275,35 @@ int fat_open(struct _reent *r, void *fileStruct, const char *path, int flags, in
|
||||
}
|
||||
|
||||
file->files = (FIL*)std::realloc(file->files, (i + 1) * sizeof(*file->files));
|
||||
if (!file->files) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
std::memcpy(&file->files[i], &fil, sizeof(fil));
|
||||
file->file_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if (!file->files) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
std::snprintf(file->path, sizeof(file->path), "%s", path);
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fat_close(struct _reent *r, void *fd) {
|
||||
int Device::devoptab_close(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
if (file->files) {
|
||||
for (u32 i = 0; i < file->file_count; i++) {
|
||||
f_close(&file->files[i]);
|
||||
}
|
||||
free(file->files);
|
||||
|
||||
std::free(file->files);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
ssize_t fat_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||
ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
UINT total_bytes_read = 0;
|
||||
|
||||
@@ -233,11 +312,11 @@ ssize_t fat_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||
auto fil = get_current_file(file);
|
||||
if (!fil) {
|
||||
log_write("[FATFS] failed to get fil\n");
|
||||
return set_errno(r, ENOENT);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (FR_OK != f_read(fil, ptr, len, &bytes_read)) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (!bytes_read) {
|
||||
@@ -252,7 +331,7 @@ ssize_t fat_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||
return total_bytes_read;
|
||||
}
|
||||
|
||||
off_t fat_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||
off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
const auto size = get_size_from_files(file);
|
||||
|
||||
@@ -265,11 +344,10 @@ off_t fat_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||
file->off = std::clamp<u64>(pos, 0, size);
|
||||
set_current_file_pos(file);
|
||||
|
||||
r->_errno = 0;
|
||||
return file->off;
|
||||
}
|
||||
|
||||
int fat_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||
int Device::devoptab_fstat(void *fd, struct stat *st) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
/* Only fill the attr and size field, leaving the timestamp blank. */
|
||||
@@ -279,173 +357,110 @@ int fat_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||
/* Fill stat info. */
|
||||
fill_stat(nullptr, &info, st);
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
DIR_ITER* fat_diropen(struct _reent *r, DIR_ITER *dirState, const char *path) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
std::memset(dir, 0, sizeof(*dir));
|
||||
int Device::devoptab_diropen(void* fd, const char *path) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
log_write("[FATFS] diropen: %s\n", path);
|
||||
if (FR_OK != f_opendir(&dir->dir, path)) {
|
||||
set_errno(r, ENOENT);
|
||||
return NULL;
|
||||
log_write("[FATFS] f_opendir(%s) failed\n", path);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
r->_errno = 0;
|
||||
return dirState;
|
||||
log_write("[FATFS] Opened dir: %s\n", path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fat_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
int Device::devoptab_dirreset(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
if (FR_OK != f_rewinddir(&dir->dir)) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fat_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
FILINFO fno{};
|
||||
|
||||
if (FR_OK != f_readdir(&dir->dir, &fno)) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (!fno.fname[0]) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
strcpy(filename, fno.fname);
|
||||
std::strcpy(filename, fno.fname);
|
||||
fill_stat(dir->path, &fno, filestat);
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fat_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
int Device::devoptab_dirclose(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
if (FR_OK != f_closedir(&dir->dir)) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fat_statvfs(struct _reent *r, const char *path, struct statvfs *buf) {
|
||||
memset(buf, 0, sizeof(*buf));
|
||||
|
||||
// todo: find out how to calculate free size in read only.
|
||||
const auto fat = (FatStorageEntry*)r->deviceData;
|
||||
buf->f_bsize = FF_MAX_SS;
|
||||
buf->f_frsize = FF_MAX_SS;
|
||||
buf->f_blocks = ((fat->fs.n_fatent - 2) * (DWORD)fat->fs.csize);
|
||||
buf->f_namemax = FF_LFN_BUF;
|
||||
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
int fat_lstat(struct _reent *r, const char *file, struct stat *st) {
|
||||
int Device::devoptab_lstat(const char *path, struct stat *st) {
|
||||
FILINFO fno;
|
||||
if (FR_OK != f_stat(file, &fno)) {
|
||||
return set_errno(r, ENOENT);
|
||||
if (FR_OK != f_stat(path, &fno)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
fill_stat(file, &fno, st);
|
||||
return r->_errno = 0;
|
||||
fill_stat(path, &fno, st);
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr devoptab_t DEVOPTAB = {
|
||||
.structSize = sizeof(File),
|
||||
.open_r = fat_open,
|
||||
.close_r = fat_close,
|
||||
.read_r = fat_read,
|
||||
.seek_r = fat_seek,
|
||||
.fstat_r = fat_fstat,
|
||||
.stat_r = fat_lstat,
|
||||
.dirStateSize = sizeof(Dir),
|
||||
.diropen_r = fat_diropen,
|
||||
.dirreset_r = fat_dirreset,
|
||||
.dirnext_r = fat_dirnext,
|
||||
.dirclose_r = fat_dirclose,
|
||||
.statvfs_r = fat_statvfs,
|
||||
.lstat_r = fat_lstat,
|
||||
};
|
||||
|
||||
Mutex g_mutex{};
|
||||
bool g_is_init{};
|
||||
|
||||
} // namespace
|
||||
|
||||
Result MountAll() {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
Result MountFatfsAll() {
|
||||
for (u32 i = 0; i < FF_VOLUMES; i++) {
|
||||
const auto& bis = BIS_MOUNT_ENTRIES[i];
|
||||
|
||||
common::MountConfig config{};
|
||||
config.url = "fatfs dummy url";
|
||||
config.read_only = true;
|
||||
config.dump_hidden = true;
|
||||
|
||||
if (!common::MountNetworkDevice2(
|
||||
std::make_unique<Device>((BisMountType)i, config),
|
||||
config,
|
||||
sizeof(File), sizeof(Dir),
|
||||
bis.volume_name, bis.mount_name
|
||||
)) {
|
||||
log_write("[FATFS] Failed to mount %s\n", bis.volume_name);
|
||||
}
|
||||
}
|
||||
|
||||
if (g_is_init) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < FF_VOLUMES; i++) {
|
||||
auto& fat = g_fat_storage[i];
|
||||
const auto& bis = BIS_MOUNT_ENTRIES[i];
|
||||
|
||||
// log_write("[FAT] %s\n", bis.volume_name);
|
||||
|
||||
fat.devoptab = DEVOPTAB;
|
||||
fat.devoptab.name = bis.volume_name;
|
||||
fat.devoptab.deviceData = &fat;
|
||||
|
||||
R_TRY(fsOpenBisStorage(&fat.storage, bis.id));
|
||||
auto source = std::make_shared<FsStorageSource>(&fat.storage);
|
||||
|
||||
s64 size;
|
||||
R_TRY(source->GetSize(&size));
|
||||
// log_write("[FAT] BIS SUCCESS %s\n", bis.volume_name);
|
||||
|
||||
fat.buffered = std::make_unique<devoptab::common::LruBufferedData>(source, size);
|
||||
|
||||
R_UNLESS(FR_OK == f_mount(&fat.fs, bis.mount_name, 1), 0x1);
|
||||
// log_write("[FAT] MOUNT SUCCESS %s\n", bis.volume_name);
|
||||
|
||||
R_UNLESS(AddDevice(&fat.devoptab) >= 0, 0x1);
|
||||
// log_write("[FAT] DEVICE SUCCESS %s\n", bis.volume_name);
|
||||
}
|
||||
|
||||
g_is_init = true;
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void UnmountAll() {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
if (!g_is_init) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < FF_VOLUMES; i++) {
|
||||
auto& fat = g_fat_storage[i];
|
||||
const auto& bis = BIS_MOUNT_ENTRIES[i];
|
||||
|
||||
RemoveDevice(bis.mount_name);
|
||||
f_unmount(bis.mount_name);
|
||||
fsStorageClose(&fat.storage);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace sphaira::fatfs
|
||||
} // namespace sphaira::devoptab
|
||||
|
||||
extern "C" {
|
||||
|
||||
const char* VolumeStr[] {
|
||||
sphaira::fatfs::BIS_MOUNT_ENTRIES[0].volume_name,
|
||||
sphaira::fatfs::BIS_MOUNT_ENTRIES[1].volume_name,
|
||||
sphaira::fatfs::BIS_MOUNT_ENTRIES[2].volume_name,
|
||||
sphaira::fatfs::BIS_MOUNT_ENTRIES[3].volume_name,
|
||||
sphaira::devoptab::BIS_MOUNT_ENTRIES[0].volume_name,
|
||||
sphaira::devoptab::BIS_MOUNT_ENTRIES[1].volume_name,
|
||||
sphaira::devoptab::BIS_MOUNT_ENTRIES[2].volume_name,
|
||||
sphaira::devoptab::BIS_MOUNT_ENTRIES[3].volume_name,
|
||||
};
|
||||
|
||||
Result fatfs_read(u8 num, void* dst, u64 offset, u64 size) {
|
||||
// log_write("[FAT] num: %u\n", num);
|
||||
auto& fat = sphaira::fatfs::g_fat_storage[num];
|
||||
log_write("[FAT] read: %s, off: 0x%lx, size: 0x%lx\n", VolumeStr[num], offset, size);
|
||||
auto& fat = sphaira::devoptab::g_fat_storage[num];
|
||||
log_write("[FAT] buffered: %p\n", fat.buffered.get());
|
||||
return fat.buffered->Read2(dst, offset, size);
|
||||
}
|
||||
|
||||
@@ -410,7 +410,8 @@ Result MountHttpAll() {
|
||||
return std::make_unique<Device>(config);
|
||||
},
|
||||
sizeof(File), sizeof(Dir),
|
||||
"HTTP"
|
||||
"HTTP",
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user