devoptab: add http, nfs and smb mount. nca: zero init ncz structs. fs: fix FsPath compare.
This commit is contained in:
@@ -105,6 +105,9 @@ add_executable(sphaira
|
|||||||
source/utils/devoptab_xci.cpp
|
source/utils/devoptab_xci.cpp
|
||||||
source/utils/devoptab_zip.cpp
|
source/utils/devoptab_zip.cpp
|
||||||
source/utils/devoptab_bfsar.cpp
|
source/utils/devoptab_bfsar.cpp
|
||||||
|
source/utils/devoptab_http.cpp
|
||||||
|
source/utils/devoptab_nfs.cpp
|
||||||
|
source/utils/devoptab_smb2.cpp
|
||||||
|
|
||||||
source/usb/base.cpp
|
source/usb/base.cpp
|
||||||
source/usb/usbds.cpp
|
source/usb/usbds.cpp
|
||||||
@@ -255,6 +258,16 @@ FetchContent_Declare(libusbdvd
|
|||||||
GIT_TAG 3cb0613
|
GIT_TAG 3cb0613
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(libnfs
|
||||||
|
GIT_REPOSITORY https://github.com/ITotalJustice/libnfs.git
|
||||||
|
GIT_TAG 65f3e11
|
||||||
|
)
|
||||||
|
|
||||||
|
FetchContent_Declare(libsmb2
|
||||||
|
GIT_REPOSITORY https://github.com/ITotalJustice/libsmb2.git
|
||||||
|
GIT_TAG 867beea
|
||||||
|
)
|
||||||
|
|
||||||
set(USE_NEW_ZSTD ON)
|
set(USE_NEW_ZSTD ON)
|
||||||
# has issues with some homebrew and game icons (oxenfree, overwatch2).
|
# has issues with some homebrew and game icons (oxenfree, overwatch2).
|
||||||
set(USE_NVJPG OFF)
|
set(USE_NVJPG OFF)
|
||||||
@@ -315,6 +328,8 @@ FetchContent_MakeAvailable(
|
|||||||
dr_libs
|
dr_libs
|
||||||
id3v2lib
|
id3v2lib
|
||||||
libusbdvd
|
libusbdvd
|
||||||
|
libnfs
|
||||||
|
libsmb2
|
||||||
)
|
)
|
||||||
|
|
||||||
set(FTPSRV_LIB_BUILD TRUE)
|
set(FTPSRV_LIB_BUILD TRUE)
|
||||||
@@ -425,6 +440,13 @@ set_target_properties(sphaira PROPERTIES
|
|||||||
CXX_EXTENSIONS ON
|
CXX_EXTENSIONS ON
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# todo: fix this upstream as nfs should export these folders.
|
||||||
|
target_include_directories(sphaira PRIVATE
|
||||||
|
${libnfs_SOURCE_DIR}/include
|
||||||
|
${libnfs_SOURCE_DIR}/include/nfsc
|
||||||
|
${libnfs_SOURCE_DIR}/nfs
|
||||||
|
)
|
||||||
|
|
||||||
target_link_libraries(sphaira PRIVATE
|
target_link_libraries(sphaira PRIVATE
|
||||||
ftpsrv_helper
|
ftpsrv_helper
|
||||||
libhaze
|
libhaze
|
||||||
@@ -439,6 +461,8 @@ target_link_libraries(sphaira PRIVATE
|
|||||||
dr_libs
|
dr_libs
|
||||||
id3v2lib
|
id3v2lib
|
||||||
libusbdvd
|
libusbdvd
|
||||||
|
nfs
|
||||||
|
smb2
|
||||||
|
|
||||||
${minizip_lib}
|
${minizip_lib}
|
||||||
ZLIB::ZLIB
|
ZLIB::ZLIB
|
||||||
|
|||||||
@@ -823,11 +823,40 @@ enum : Result {
|
|||||||
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
|
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
|
||||||
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
|
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
|
||||||
|
|
||||||
#define ON_SCOPE_EXIT(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
|
template<typename Function>
|
||||||
|
struct ScopeGuard {
|
||||||
|
ScopeGuard(Function&& function) : m_function(std::forward<Function>(function)) {
|
||||||
|
|
||||||
|
}
|
||||||
|
~ScopeGuard() {
|
||||||
|
m_function();
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopeGuard(const ScopeGuard&) = delete;
|
||||||
|
void operator=(const ScopeGuard&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const Function m_function;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ScopedMutex {
|
||||||
|
ScopedMutex(Mutex* mutex) : m_mutex{mutex} {
|
||||||
|
mutexLock(m_mutex);
|
||||||
|
}
|
||||||
|
~ScopedMutex() {
|
||||||
|
mutexUnlock(m_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
ScopedMutex(const ScopedMutex&) = delete;
|
||||||
|
void operator=(const ScopedMutex&) = delete;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Mutex* const m_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
// #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 SCOPED_MUTEX(_m) ScopedMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m}
|
||||||
|
|
||||||
// #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; } }};
|
||||||
|
|
||||||
// mutex helpers.
|
|
||||||
#define SCOPED_MUTEX(mutex) \
|
|
||||||
mutexLock(mutex); \
|
|
||||||
ON_SCOPE_EXIT(mutexUnlock(mutex))
|
|
||||||
|
|||||||
@@ -138,20 +138,24 @@ struct FsPath {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static constexpr bool path_equal(std::string_view a, std::string_view b) {
|
||||||
|
return a.length() == b.length() && !strncasecmp(a.data(), b.data(), a.length());
|
||||||
|
}
|
||||||
|
|
||||||
constexpr bool operator==(const FsPath& v) const noexcept {
|
constexpr bool operator==(const FsPath& v) const noexcept {
|
||||||
return !strcasecmp(*this, v);
|
return path_equal(*this, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool operator==(const char* v) const noexcept {
|
constexpr bool operator==(const char* v) const noexcept {
|
||||||
return !strcasecmp(*this, v);
|
return path_equal(*this, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool operator==(const std::string& v) const noexcept {
|
constexpr bool operator==(const std::string& v) const noexcept {
|
||||||
return !strncasecmp(*this, v.data(), v.length());
|
return path_equal(*this, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr bool operator==(const std::string_view v) const noexcept {
|
constexpr bool operator==(const std::string_view v) const noexcept {
|
||||||
return !strncasecmp(*this, v.data(), v.length());
|
return path_equal(*this, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
static consteval bool Test(const auto& str) {
|
static consteval bool Test(const auto& str) {
|
||||||
@@ -229,7 +233,6 @@ struct File {
|
|||||||
fs::Fs* m_fs{};
|
fs::Fs* m_fs{};
|
||||||
FsFile m_native{};
|
FsFile m_native{};
|
||||||
std::FILE* m_stdio{};
|
std::FILE* m_stdio{};
|
||||||
s64 m_stdio_off{};
|
|
||||||
u32 m_mode{};
|
u32 m_mode{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include "yati/source/base.hpp"
|
#include "yati/source/base.hpp"
|
||||||
|
#include "location.hpp"
|
||||||
|
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -32,4 +33,16 @@ void UmountBfsar(const fs::FsPath& mount);
|
|||||||
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||||
void UmountNro(const fs::FsPath& mount);
|
void UmountNro(const fs::FsPath& mount);
|
||||||
|
|
||||||
|
Result MountHttpAll();
|
||||||
|
Result GetHttpMounts(location::StdioEntries& out);
|
||||||
|
void UnmountHttpAll();
|
||||||
|
|
||||||
|
Result MountNfsAll();
|
||||||
|
Result GetNfsMounts(location::StdioEntries& out);
|
||||||
|
void UnmountNfsAll();
|
||||||
|
|
||||||
|
Result MountSmb2All();
|
||||||
|
Result GetSmb2Mounts(location::StdioEntries& out);
|
||||||
|
void UnmountSmb2All();
|
||||||
|
|
||||||
} // namespace sphaira::devoptab
|
} // namespace sphaira::devoptab
|
||||||
|
|||||||
@@ -81,6 +81,8 @@ private:
|
|||||||
std::vector<BufferedFileData> buffered_large{}; // 1MiB
|
std::vector<BufferedFileData> buffered_large{}; // 1MiB
|
||||||
};
|
};
|
||||||
|
|
||||||
bool fix_path(const char* str, char* out);
|
bool fix_path(const char* str, char* out, bool strip_leading_slash = false);
|
||||||
|
|
||||||
|
void update_devoptab_for_read_only(devoptab_t* devoptab, bool read_only);
|
||||||
|
|
||||||
} // namespace sphaira::devoptab::common
|
} // namespace sphaira::devoptab::common
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
|
|
||||||
#include "utils/profile.hpp"
|
#include "utils/profile.hpp"
|
||||||
#include "utils/thread.hpp"
|
#include "utils/thread.hpp"
|
||||||
|
#include "utils/devoptab.hpp"
|
||||||
|
|
||||||
#include <nanovg_dk.h>
|
#include <nanovg_dk.h>
|
||||||
#include <minIni.h>
|
#include <minIni.h>
|
||||||
@@ -1590,6 +1591,22 @@ App::App(const char* argv0) {
|
|||||||
curl::Init();
|
curl::Init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this has to come after curl init as it inits curl global.
|
||||||
|
{
|
||||||
|
SCOPED_TIMESTAMP("http init");
|
||||||
|
devoptab::MountHttpAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SCOPED_TIMESTAMP("nfs init");
|
||||||
|
devoptab::MountNfsAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SCOPED_TIMESTAMP("smb init");
|
||||||
|
devoptab::MountSmb2All();
|
||||||
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
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);
|
||||||
@@ -2200,6 +2217,22 @@ App::~App() {
|
|||||||
fatfs::UnmountAll();
|
fatfs::UnmountAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// this has to come before curl exit as it uses curl global.
|
||||||
|
{
|
||||||
|
SCOPED_TIMESTAMP("http exit");
|
||||||
|
devoptab::UnmountHttpAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SCOPED_TIMESTAMP("nfs exit");
|
||||||
|
devoptab::UnmountNfsAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
SCOPED_TIMESTAMP("smb exit");
|
||||||
|
devoptab::UnmountSmb2All();
|
||||||
|
}
|
||||||
|
|
||||||
// do these last as they were signalled to exit.
|
// do these last as they were signalled to exit.
|
||||||
{
|
{
|
||||||
SCOPED_TIMESTAMP("audio_exit");
|
SCOPED_TIMESTAMP("audio_exit");
|
||||||
|
|||||||
@@ -500,9 +500,9 @@ Result File::Read( s64 off, void* buf, u64 read_size, u32 option, u64* bytes_rea
|
|||||||
} else {
|
} else {
|
||||||
R_UNLESS(m_stdio, Result_FsUnknownStdioError);
|
R_UNLESS(m_stdio, Result_FsUnknownStdioError);
|
||||||
|
|
||||||
if (m_stdio_off != off) {
|
if (std::ftell(m_stdio) != off) {
|
||||||
m_stdio_off = off;
|
const auto ret = std::fseek(m_stdio, off, SEEK_SET);
|
||||||
std::fseek(m_stdio, off, SEEK_SET);
|
R_UNLESS(ret == 0, Result_FsUnknownStdioError);
|
||||||
}
|
}
|
||||||
|
|
||||||
*bytes_read = std::fread(buf, 1, read_size, m_stdio);
|
*bytes_read = std::fread(buf, 1, read_size, m_stdio);
|
||||||
@@ -510,11 +510,10 @@ Result File::Read( s64 off, void* buf, u64 read_size, u32 option, u64* bytes_rea
|
|||||||
// if we read less bytes than expected, check if there was an error (ignoring eof).
|
// if we read less bytes than expected, check if there was an error (ignoring eof).
|
||||||
if (*bytes_read < read_size) {
|
if (*bytes_read < read_size) {
|
||||||
if (!std::feof(m_stdio) && std::ferror(m_stdio)) {
|
if (!std::feof(m_stdio) && std::ferror(m_stdio)) {
|
||||||
|
log_write("[FS] fread error: %d\n", std::ferror(m_stdio));
|
||||||
R_THROW(Result_FsUnknownStdioError);
|
R_THROW(Result_FsUnknownStdioError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_stdio_off += *bytes_read;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
@@ -528,17 +527,14 @@ Result File::Write(s64 off, const void* buf, u64 write_size, u32 option) {
|
|||||||
} else {
|
} else {
|
||||||
R_UNLESS(m_stdio, Result_FsUnknownStdioError);
|
R_UNLESS(m_stdio, Result_FsUnknownStdioError);
|
||||||
|
|
||||||
if (m_stdio_off != off) {
|
if (std::ftell(m_stdio) != off) {
|
||||||
log_write("[FS] diff seek\n");
|
const auto ret = std::fseek(m_stdio, off, SEEK_SET);
|
||||||
m_stdio_off = off;
|
R_UNLESS(ret == 0, Result_FsUnknownStdioError);
|
||||||
std::fseek(m_stdio, off, SEEK_SET);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto result = std::fwrite(buf, 1, write_size, m_stdio);
|
const auto result = std::fwrite(buf, 1, write_size, m_stdio);
|
||||||
// log_write("[FS] fwrite res: %zu vs %zu\n", result, write_size);
|
// log_write("[FS] fwrite res: %zu vs %zu\n", result, write_size);
|
||||||
R_UNLESS(result == write_size, Result_FsUnknownStdioError);
|
R_UNLESS(result == write_size, Result_FsUnknownStdioError);
|
||||||
|
|
||||||
m_stdio_off += write_size;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "fs.hpp"
|
#include "fs.hpp"
|
||||||
#include "app.hpp"
|
#include "app.hpp"
|
||||||
#include "usbdvd.hpp"
|
#include "usbdvd.hpp"
|
||||||
|
#include "utils/devoptab.hpp"
|
||||||
|
|
||||||
#include <ff.h>
|
#include <ff.h>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -79,6 +80,30 @@ auto Load() -> Entries {
|
|||||||
auto GetStdio(bool write) -> StdioEntries {
|
auto GetStdio(bool write) -> StdioEntries {
|
||||||
StdioEntries out{};
|
StdioEntries out{};
|
||||||
|
|
||||||
|
{
|
||||||
|
StdioEntries entries;
|
||||||
|
if (R_SUCCEEDED(devoptab::GetNfsMounts(entries))) {
|
||||||
|
log_write("[NFS] got nfs mounts: %zu\n", entries.size());
|
||||||
|
out.insert(out.end(), entries.begin(), entries.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
StdioEntries entries;
|
||||||
|
if (R_SUCCEEDED(devoptab::GetSmb2Mounts(entries))) {
|
||||||
|
log_write("[SMB2] got smb2 mounts: %zu\n", entries.size());
|
||||||
|
out.insert(out.end(), entries.begin(), entries.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
StdioEntries entries;
|
||||||
|
if (R_SUCCEEDED(devoptab::GetHttpMounts(entries))) {
|
||||||
|
log_write("[HTTP] got http mounts: %zu\n", entries.size());
|
||||||
|
out.insert(out.end(), entries.begin(), entries.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// try and load usbdvd entry.
|
// try and load usbdvd entry.
|
||||||
// todo: check if more than 1 entry is supported.
|
// todo: check if more than 1 entry is supported.
|
||||||
// todo: only call if usbdvd is init.
|
// todo: only call if usbdvd is init.
|
||||||
|
|||||||
@@ -624,8 +624,10 @@ void FsView::Draw(NVGcontext* vg, Theme* theme) {
|
|||||||
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->GetColour(text_id), "%02u/%02u/%u", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
|
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) + 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP, theme->GetColour(text_id), "%02u/%02u/%u", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900);
|
||||||
if ((double)e.file_size / 1024.0 / 1024.0 <= 0.009) {
|
if ((double)e.file_size / 1024.0 / 1024.0 <= 0.009) {
|
||||||
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%.2f KiB", (double)e.file_size / 1024.0);
|
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%.2f KiB", (double)e.file_size / 1024.0);
|
||||||
} else {
|
} else if ((double)e.file_size / 1024.0 / 1024.0 / 1024.0 <= 0.009) {
|
||||||
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%.2f MiB", (double)e.file_size / 1024.0 / 1024.0);
|
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%.2f MiB", (double)e.file_size / 1024.0 / 1024.0);
|
||||||
|
} else {
|
||||||
|
gfx::drawTextArgs(vg, x + w - text_xoffset, y + (h / 2.f) - 3, 16.f, NVG_ALIGN_RIGHT | NVG_ALIGN_BOTTOM, theme->GetColour(text_id), "%.2f GiB", (double)e.file_size / 1024.0 / 1024.0 / 1024.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -156,7 +156,7 @@ Result LruBufferedData::Read(void *_buffer, s64 file_off, s64 read_size, u64* by
|
|||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fix_path(const char* str, char* out) {
|
bool fix_path(const char* str, char* out, bool strip_leading_slash) {
|
||||||
// log_write("[SAVE] got path: %s\n", str);
|
// log_write("[SAVE] got path: %s\n", str);
|
||||||
|
|
||||||
str = std::strrchr(str, ':');
|
str = std::strrchr(str, ':');
|
||||||
@@ -175,9 +175,16 @@ bool fix_path(const char* str, char* out) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// add leading slash.
|
if (!i) {
|
||||||
if (!i && str[i] != '/') {
|
// skip leading slash.
|
||||||
out[len++] = '/';
|
if (strip_leading_slash && str[i] == '/') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add leading slash.
|
||||||
|
if (!strip_leading_slash && str[i] != '/') {
|
||||||
|
out[len++] = '/';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// save single char.
|
// save single char.
|
||||||
@@ -197,4 +204,20 @@ bool fix_path(const char* str, char* out) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void update_devoptab_for_read_only(devoptab_t* devoptab, bool read_only) {
|
||||||
|
// remove write functions if read_only is set.
|
||||||
|
if (read_only) {
|
||||||
|
devoptab->write_r = nullptr;
|
||||||
|
devoptab->link_r = nullptr;
|
||||||
|
devoptab->unlink_r = nullptr;
|
||||||
|
devoptab->rename_r = nullptr;
|
||||||
|
devoptab->mkdir_r = nullptr;
|
||||||
|
devoptab->ftruncate_r = nullptr;
|
||||||
|
devoptab->fsync_r = nullptr;
|
||||||
|
devoptab->rmdir_r = nullptr;
|
||||||
|
devoptab->utimes_r = nullptr;
|
||||||
|
devoptab->symlink_r = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // sphaira::devoptab::common
|
} // sphaira::devoptab::common
|
||||||
|
|||||||
647
sphaira/source/utils/devoptab_http.cpp
Normal file
647
sphaira/source/utils/devoptab_http.cpp
Normal file
@@ -0,0 +1,647 @@
|
|||||||
|
#include "utils/devoptab_common.hpp"
|
||||||
|
#include "utils/profile.hpp"
|
||||||
|
|
||||||
|
#include "location.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include <sys/iosupport.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
|
#include <minIni.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
|
||||||
|
namespace sphaira::devoptab {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
#define CURL_EASY_SETOPT_LOG(handle, opt, v) \
|
||||||
|
if (auto r = curl_easy_setopt(handle, opt, v); r != CURLE_OK) { \
|
||||||
|
log_write("curl_easy_setopt(%s, %s) msg: %s\n", #opt, #v, curl_easy_strerror(r)); \
|
||||||
|
} \
|
||||||
|
|
||||||
|
#define CURL_EASY_GETINFO_LOG(handle, opt, v) \
|
||||||
|
if (auto r = curl_easy_getinfo(handle, opt, v); r != CURLE_OK) { \
|
||||||
|
log_write("curl_easy_getinfo(%s, %s) msg: %s\n", #opt, #v, curl_easy_strerror(r)); \
|
||||||
|
} \
|
||||||
|
|
||||||
|
struct HttpMountConfig {
|
||||||
|
std::string name{};
|
||||||
|
std::string url{};
|
||||||
|
std::string user{};
|
||||||
|
std::string pass{};
|
||||||
|
};
|
||||||
|
using HttpMountConfigs = std::vector<HttpMountConfig>;
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
CURL* curl{};
|
||||||
|
HttpMountConfig config;
|
||||||
|
Mutex mutex{};
|
||||||
|
bool mounted{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DirEntry {
|
||||||
|
std::string name{};
|
||||||
|
std::string href{};
|
||||||
|
bool is_dir{};
|
||||||
|
};
|
||||||
|
using DirEntries = std::vector<DirEntry>;
|
||||||
|
|
||||||
|
struct FileEntry {
|
||||||
|
std::string path{};
|
||||||
|
struct stat st{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
Device* client;
|
||||||
|
FileEntry* entry;
|
||||||
|
size_t off;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Dir {
|
||||||
|
Device* client;
|
||||||
|
DirEntries* entries;
|
||||||
|
size_t index;
|
||||||
|
};
|
||||||
|
|
||||||
|
size_t dirlist_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||||
|
auto data = static_cast<std::vector<char>*>(userdata);
|
||||||
|
|
||||||
|
// increase by chunk size.
|
||||||
|
const auto realsize = size * nmemb;
|
||||||
|
if (data->capacity() < data->size() + realsize) {
|
||||||
|
const auto rsize = std::max(realsize, data->size() + 1024 * 1024);
|
||||||
|
data->reserve(rsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
// store the data.
|
||||||
|
const auto offset = data->size();
|
||||||
|
data->resize(offset + realsize);
|
||||||
|
std::memcpy(data->data() + offset, ptr, realsize);
|
||||||
|
|
||||||
|
return realsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t write_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||||
|
auto data = static_cast<std::span<char>*>(userdata);
|
||||||
|
|
||||||
|
const auto realsize = size * nmemb;
|
||||||
|
if (data->size() < realsize) {
|
||||||
|
log_write("[HTTP] buffer is too small: %zu < %zu\n", data->size(), realsize);
|
||||||
|
return 0; // buffer is too small
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memcpy(data->data(), ptr, realsize);
|
||||||
|
*data = data->subspan(realsize); // advance the span
|
||||||
|
|
||||||
|
return realsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string url_encode(const std::string& str) {
|
||||||
|
auto escaped = curl_escape(str.c_str(), str.length());
|
||||||
|
if (!escaped) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string result(escaped);
|
||||||
|
curl_free(escaped);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string build_url(const std::string& base, const std::string& path, bool is_dir) {
|
||||||
|
std::string url = base;
|
||||||
|
if (!url.ends_with('/')) {
|
||||||
|
url += '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
url += url_encode(path);
|
||||||
|
if (is_dir && !url.ends_with('/')) {
|
||||||
|
url += '/'; // append trailing slash for folder.
|
||||||
|
}
|
||||||
|
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
void http_set_common_options(Device& client, const std::string& url) {
|
||||||
|
curl_easy_reset(client.curl);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_URL, url.c_str());
|
||||||
|
// todo: make the timeouts configurable.
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_TIMEOUT, 5L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_CONNECTTIMEOUT, 5L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_AUTOREFERER, 1L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_FOLLOWLOCATION, 1L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_SSL_VERIFYHOST, 0L);
|
||||||
|
// disabled as i want to see the http core.
|
||||||
|
// CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_FAILONERROR, 1L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_NOPROGRESS, 0L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_BUFFERSIZE, 1024L * 512L);
|
||||||
|
|
||||||
|
// enable all forms of compression supported by libcurl.
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_ACCEPT_ENCODING, "");
|
||||||
|
|
||||||
|
// in most cases, this will use CURLAUTH_BASIC.
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_HTTPAUTH, (long)CURLAUTH_BASIC);
|
||||||
|
|
||||||
|
// enable TE is server supports it.
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_TRANSFER_ENCODING, 1L);
|
||||||
|
|
||||||
|
if (!client.config.user.empty()) {
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_USERNAME, client.config.user.c_str());
|
||||||
|
}
|
||||||
|
if (!client.config.pass.empty()) {
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_PASSWORD, client.config.pass.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http_dirlist(Device& client, const std::string& path, DirEntries& out) {
|
||||||
|
const auto url = build_url(client.config.url, path, true);
|
||||||
|
std::vector<char> chunk;
|
||||||
|
|
||||||
|
log_write("[HTTP] Listing URL: %s path: %s\n", url.c_str(), path.c_str());
|
||||||
|
|
||||||
|
http_set_common_options(client, url);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_WRITEFUNCTION, dirlist_callback);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_WRITEDATA, (void *)&chunk);
|
||||||
|
|
||||||
|
const auto res = curl_easy_perform(client.curl);
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
chunk.emplace_back('\0'); // null-terminate the chunk
|
||||||
|
const char* dilim = "<a href=\"";
|
||||||
|
const char* ptr = chunk.data();
|
||||||
|
|
||||||
|
// try and parse the href links.
|
||||||
|
// it works with python http.serve, npm http-server and rclone http server.
|
||||||
|
while ((ptr = std::strstr(ptr, dilim))) {
|
||||||
|
// skip the delimiter.
|
||||||
|
ptr += std::strlen(dilim);
|
||||||
|
|
||||||
|
const auto href_begin = ptr;
|
||||||
|
const auto href_end = std::strstr(href_begin, "\">");
|
||||||
|
if (!href_end) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto href_len = href_end - href_begin;
|
||||||
|
|
||||||
|
const auto name_begin = href_end + std::strlen("\">");
|
||||||
|
const auto name_end = std::strstr(name_begin, "</a>");
|
||||||
|
if (!name_end) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const auto name_len = name_end - name_begin;
|
||||||
|
|
||||||
|
if (href_len <= 0 || name_len <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip if inside <script> or <style> tags (simple check)
|
||||||
|
const auto script_tag = std::strstr(href_begin - 32, "<script");
|
||||||
|
const auto style_tag = std::strstr(href_begin - 32, "<style");
|
||||||
|
if ((script_tag && script_tag < href_begin) || (style_tag && style_tag < href_begin)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string href(href_begin, href_len);
|
||||||
|
std::string name(name_begin, name_len);
|
||||||
|
|
||||||
|
// skip parent directory entry and external links.
|
||||||
|
if (href == ".." || name == ".." || href.starts_with("../") || name.starts_with("../") || href.find("://") != std::string::npos) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// skip links that are not actual files/dirs (e.g. sorting/filter controls)
|
||||||
|
if (href.starts_with('?')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.empty() || href.empty() || name == "/" || href.starts_with('?') || href.starts_with('#')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto is_dir = name.ends_with('/');
|
||||||
|
if (is_dir) {
|
||||||
|
name.pop_back(); // remove the trailing '/'
|
||||||
|
}
|
||||||
|
|
||||||
|
out.emplace_back(name, href, is_dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http_stat(Device& client, const std::string& path, struct stat* st, bool is_dir) {
|
||||||
|
std::memset(st, 0, sizeof(*st));
|
||||||
|
const auto url = build_url(client.config.url, path, is_dir);
|
||||||
|
|
||||||
|
http_set_common_options(client, url);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_NOBODY, 1L);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_FILETIME, 1L);
|
||||||
|
|
||||||
|
const auto res = curl_easy_perform(client.curl);
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
long response_code = 0;
|
||||||
|
CURL_EASY_GETINFO_LOG(client.curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||||
|
|
||||||
|
curl_off_t file_size = 0;
|
||||||
|
CURL_EASY_GETINFO_LOG(client.curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &file_size);
|
||||||
|
|
||||||
|
curl_off_t file_time = 0;
|
||||||
|
CURL_EASY_GETINFO_LOG(client.curl, CURLINFO_FILETIME_T, &file_time);
|
||||||
|
|
||||||
|
const char* content_type{};
|
||||||
|
CURL_EASY_GETINFO_LOG(client.curl, CURLINFO_CONTENT_TYPE, &content_type);
|
||||||
|
|
||||||
|
const char* effective_url{};
|
||||||
|
CURL_EASY_GETINFO_LOG(client.curl, CURLINFO_EFFECTIVE_URL, &effective_url);
|
||||||
|
|
||||||
|
// handle error codes.
|
||||||
|
if (response_code != 200 && response_code != 206) {
|
||||||
|
log_write("[HTTP] Unexpected HTTP response code: %ld\n", response_code);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (effective_url) {
|
||||||
|
if (std::string_view{effective_url}.ends_with('/')) {
|
||||||
|
is_dir = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content_type && !std::strcmp(content_type, "text/html")) {
|
||||||
|
is_dir = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_dir) {
|
||||||
|
st->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
} else {
|
||||||
|
st->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
st->st_size = file_size > 0 ? file_size : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
st->st_mtime = file_time > 0 ? file_time : 0;
|
||||||
|
st->st_atime = st->st_mtime;
|
||||||
|
st->st_ctime = st->st_mtime;
|
||||||
|
st->st_nlink = 1;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool http_read_file_chunk(Device& client, const std::string& path, size_t start, std::span<char> buffer) {
|
||||||
|
SCOPED_TIMESTAMP("http_read_file_chunk");
|
||||||
|
const auto url = build_url(client.config.url, path, false);
|
||||||
|
|
||||||
|
char range[64];
|
||||||
|
std::snprintf(range, sizeof(range), "%zu-%zu", start, start + buffer.size() - 1);
|
||||||
|
log_write("[HTTP] Requesting range: %s\n", range);
|
||||||
|
|
||||||
|
http_set_common_options(client, url);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_RANGE, range);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_WRITEFUNCTION, write_data_callback);
|
||||||
|
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_WRITEDATA, (void *)&buffer);
|
||||||
|
|
||||||
|
const auto res = curl_easy_perform(client.curl);
|
||||||
|
if (res != CURLE_OK) {
|
||||||
|
log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mount_http(Device& client) {
|
||||||
|
if (client.curl) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
client.curl = curl_easy_init();
|
||||||
|
if (!client.curl) {
|
||||||
|
log_write("[HTTP] curl_easy_init() failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_errno(struct _reent *r, int err) {
|
||||||
|
r->_errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
|
||||||
|
auto device = (Device*)r->deviceData;
|
||||||
|
auto file = static_cast<File*>(fileStruct);
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
// todo: add this check to all devoptabs.
|
||||||
|
if ((flags & O_ACCMODE) != O_RDONLY) {
|
||||||
|
return set_errno(r, EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_http(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct stat st;
|
||||||
|
if (!http_stat(*device, path, &st, false)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (st.st_mode & S_IFDIR) {
|
||||||
|
return set_errno(r, EISDIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
file->client = device;
|
||||||
|
file->entry = new FileEntry{path, st};
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_close(struct _reent *r, void *fd) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->client->mutex);
|
||||||
|
|
||||||
|
delete file->entry;
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->client->mutex);
|
||||||
|
len = std::min(len, file->entry->st.st_size - file->off);
|
||||||
|
|
||||||
|
if (!len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!http_read_file_chunk(*file->client, file->entry->path, file->off, {ptr, len})) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
file->off += len;
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->client->mutex);
|
||||||
|
|
||||||
|
if (dir == SEEK_CUR) {
|
||||||
|
pos += file->off;
|
||||||
|
} else if (dir == SEEK_END) {
|
||||||
|
pos = file->entry->st.st_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
r->_errno = 0;
|
||||||
|
return file->off = std::clamp<u64>(pos, 0, file->entry->st.st_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->client->mutex);
|
||||||
|
|
||||||
|
std::memcpy(st, &file->entry->st, sizeof(*st));
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) {
|
||||||
|
auto device = (Device*)r->deviceData;
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_http(*device)) {
|
||||||
|
set_errno(r, EIO);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto entries = new DirEntries();
|
||||||
|
if (!http_dirlist(*device, path, *entries)) {
|
||||||
|
delete entries;
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->client = device;
|
||||||
|
dir->entries = entries;
|
||||||
|
r->_errno = 0;
|
||||||
|
return dirState;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
SCOPED_MUTEX(&dir->client->mutex);
|
||||||
|
|
||||||
|
dir->index = 0;
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(filestat, 0, sizeof(*filestat));
|
||||||
|
SCOPED_MUTEX(&dir->client->mutex);
|
||||||
|
|
||||||
|
if (dir->index >= dir->entries->size()) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& entry = (*dir->entries)[dir->index];
|
||||||
|
if (entry.is_dir) {
|
||||||
|
filestat->st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
} else {
|
||||||
|
filestat->st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
filestat->st_nlink = 1;
|
||||||
|
std::strcpy(filename, entry.name.c_str());
|
||||||
|
|
||||||
|
dir->index++;
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
SCOPED_MUTEX(&dir->client->mutex);
|
||||||
|
|
||||||
|
delete dir->entries;
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||||
|
auto device = (Device*)r->deviceData;
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH];
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_http(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!http_stat(*device, path, st, false) && !http_stat(*device, path, st, true)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr devoptab_t DEVOPTAB = {
|
||||||
|
.structSize = sizeof(File),
|
||||||
|
.open_r = devoptab_open,
|
||||||
|
.close_r = devoptab_close,
|
||||||
|
.read_r = devoptab_read,
|
||||||
|
.seek_r = devoptab_seek,
|
||||||
|
.fstat_r = devoptab_fstat,
|
||||||
|
.stat_r = devoptab_lstat,
|
||||||
|
.dirStateSize = sizeof(Dir),
|
||||||
|
.diropen_r = devoptab_diropen,
|
||||||
|
.dirreset_r = devoptab_dirreset,
|
||||||
|
.dirnext_r = devoptab_dirnext,
|
||||||
|
.dirclose_r = devoptab_dirclose,
|
||||||
|
.lstat_r = devoptab_lstat,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
Device device{};
|
||||||
|
devoptab_t devoptab{};
|
||||||
|
fs::FsPath mount{};
|
||||||
|
char name[32]{};
|
||||||
|
s32 ref_count{};
|
||||||
|
|
||||||
|
~Entry() {
|
||||||
|
if (device.curl) {
|
||||||
|
curl_easy_cleanup(device.curl);
|
||||||
|
}
|
||||||
|
RemoveDevice(mount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mutex g_mutex;
|
||||||
|
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Result MountHttpAll() {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
static const auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||||
|
auto e = static_cast<HttpMountConfigs*>(UserData);
|
||||||
|
if (!Section || !Key || !Value) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new entry if use section changed.
|
||||||
|
if (e->empty() || std::strcmp(Section, e->back().name.c_str())) {
|
||||||
|
e->emplace_back(Section);
|
||||||
|
}
|
||||||
|
|
||||||
|
HttpMountConfig config;
|
||||||
|
if (std::strcmp(Key, "url") == 0) {
|
||||||
|
config.url = Value;
|
||||||
|
} else if (std::strcmp(Key, "user") == 0) {
|
||||||
|
config.user = Value;
|
||||||
|
} else if (std::strcmp(Key, "pass") == 0) {
|
||||||
|
config.pass = Value;
|
||||||
|
} else {
|
||||||
|
log_write("[HTTP] INI: Unknown key %s=%s\n", Key, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpMountConfigs configs;
|
||||||
|
ini_browse(cb, &configs, "/config/sphaira/http.ini");
|
||||||
|
log_write("[HTTP] 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("[HTTP] Already mounted %s, skipping\n", config.name.c_str());
|
||||||
|
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("[HTTP] No free entries to mount %s\n", config.name.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto entry = std::make_unique<Entry>();
|
||||||
|
entry->devoptab = DEVOPTAB;
|
||||||
|
entry->devoptab.name = entry->name;
|
||||||
|
entry->devoptab.deviceData = &entry->device;
|
||||||
|
entry->device.config = config;
|
||||||
|
std::snprintf(entry->name, sizeof(entry->name), "%s", config.name.c_str());
|
||||||
|
std::snprintf(entry->mount, sizeof(entry->mount), "%s:/", config.name.c_str());
|
||||||
|
|
||||||
|
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
|
||||||
|
log_write("[HTTP] DEVICE SUCCESS %s %s\n", entry->device.config.url.c_str(), entry->name);
|
||||||
|
|
||||||
|
entry->ref_count++;
|
||||||
|
*itr = std::move(entry);
|
||||||
|
log_write("[HTTP] Mounted %s at /%s\n", config.url.c_str(), config.name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnmountHttpAll() {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
for (auto& entry : g_entries) {
|
||||||
|
if (entry) {
|
||||||
|
entry.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetHttpMounts(location::StdioEntries& out) {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
out.clear();
|
||||||
|
|
||||||
|
for (const auto& entry : g_entries) {
|
||||||
|
if (entry) {
|
||||||
|
out.emplace_back(entry->mount, entry->name, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::devoptab
|
||||||
@@ -413,7 +413,7 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& s
|
|||||||
log_write("[NCA] got header, type: %s\n", nca::GetContentTypeStr(header.content_type));
|
log_write("[NCA] got header, type: %s\n", nca::GetContentTypeStr(header.content_type));
|
||||||
|
|
||||||
// check if this is a ncz.
|
// check if this is a ncz.
|
||||||
ncz::Header ncz_header;
|
ncz::Header ncz_header{};
|
||||||
R_TRY(source->Read2(&ncz_header, NCZ_NORMAL_SIZE, sizeof(ncz_header)));
|
R_TRY(source->Read2(&ncz_header, NCZ_NORMAL_SIZE, sizeof(ncz_header)));
|
||||||
|
|
||||||
if (ncz_header.magic == NCZ_SECTION_MAGIC) {
|
if (ncz_header.magic == NCZ_SECTION_MAGIC) {
|
||||||
@@ -423,7 +423,7 @@ Result MountNcaInternal(fs::Fs* fs, const std::shared_ptr<yati::source::Base>& s
|
|||||||
R_TRY(source->Read2(ncz_sections.data(), ncz_offset, ncz_sections.size() * sizeof(ncz::Section)));
|
R_TRY(source->Read2(ncz_sections.data(), ncz_offset, ncz_sections.size() * sizeof(ncz::Section)));
|
||||||
|
|
||||||
ncz_offset += ncz_sections.size() * sizeof(ncz::Section);
|
ncz_offset += ncz_sections.size() * sizeof(ncz::Section);
|
||||||
ncz::BlockHeader ncz_block_header;
|
ncz::BlockHeader ncz_block_header{};
|
||||||
R_TRY(source->Read2(&ncz_block_header, ncz_offset, sizeof(ncz_block_header)));
|
R_TRY(source->Read2(&ncz_block_header, ncz_offset, sizeof(ncz_block_header)));
|
||||||
|
|
||||||
// ensure this is a block compressed nsz, otherwise bail out
|
// ensure this is a block compressed nsz, otherwise bail out
|
||||||
|
|||||||
631
sphaira/source/utils/devoptab_nfs.cpp
Normal file
631
sphaira/source/utils/devoptab_nfs.cpp
Normal file
@@ -0,0 +1,631 @@
|
|||||||
|
#include "utils/devoptab_common.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "location.hpp"
|
||||||
|
|
||||||
|
#include <sys/iosupport.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
#include <libnfs.h>
|
||||||
|
#include <minIni.h>
|
||||||
|
|
||||||
|
namespace sphaira::devoptab {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct NfsMountConfig {
|
||||||
|
std::string name{};
|
||||||
|
std::string url{};
|
||||||
|
std::string path{};
|
||||||
|
bool read_only{};
|
||||||
|
};
|
||||||
|
using NfsMountConfigs = std::vector<NfsMountConfig>;
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
nfs_context* nfs{};
|
||||||
|
NfsMountConfig config;
|
||||||
|
bool mounted{};
|
||||||
|
Mutex mutex{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
Device* device;
|
||||||
|
nfsfh* fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Dir {
|
||||||
|
Device* device;
|
||||||
|
nfsdir* dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool mount_nfs(Device& device) {
|
||||||
|
if (device.mounted) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.nfs) {
|
||||||
|
device.nfs = nfs_init_context();
|
||||||
|
if (!device.nfs) {
|
||||||
|
log_write("[NFS] nfs_init_context() failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_mount(device.nfs, device.config.url.c_str(), device.config.path.c_str());
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_mount() failed: %s errno: %s\n", nfs_get_error(device.nfs), std::strerror(-ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.mounted = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_errno(struct _reent *r, int err) {
|
||||||
|
r->_errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
auto file = static_cast<File*>(fileStruct);
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
if (device->config.read_only && (flags & (O_WRONLY | O_RDWR | O_CREAT | O_TRUNC | O_APPEND))) {
|
||||||
|
return set_errno(r, EROFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_open(device->nfs, path, flags, &file->fd);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_open() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
file->device = device;
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_close(struct _reent *r, void *fd) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
if (file->fd) {
|
||||||
|
nfs_close(file->device->nfs, file->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
// todo: uncomment this when it's fixed upstream.
|
||||||
|
#if 0
|
||||||
|
const auto ret = nfs_read(file->device->nfs, file->fd, ptr, len);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[NFS] nfs_read() failed: %s errno: %s\n", nfs_get_error(file->device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
#else
|
||||||
|
// work around for bug upsteam.
|
||||||
|
const auto max_read = nfs_get_readmax(file->device->nfs);
|
||||||
|
size_t bytes_read = 0;
|
||||||
|
|
||||||
|
while (bytes_read < len) {
|
||||||
|
const auto to_read = std::min<size_t>(len - bytes_read, max_read);
|
||||||
|
const auto ret = nfs_read(file->device->nfs, file->fd, ptr, to_read);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[NFS] nfs_read() failed: %s errno: %s\n", nfs_get_error(file->device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += ret;
|
||||||
|
bytes_read += ret;
|
||||||
|
|
||||||
|
if (ret < to_read) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes_read;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t devoptab_write(struct _reent *r, void *fd, const char *ptr, size_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
// unlike read, writing the max size seems to work fine.
|
||||||
|
const auto max_write = nfs_get_writemax(file->device->nfs) - 1;
|
||||||
|
size_t written = 0;
|
||||||
|
|
||||||
|
while (written < len) {
|
||||||
|
const auto to_write = std::min<size_t>(len - written, max_write);
|
||||||
|
const auto ret = nfs_write(file->device->nfs, file->fd, ptr, to_write);
|
||||||
|
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[NFS] nfs_write() failed: %s errno: %s\n", nfs_get_error(file->device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
ptr += ret;
|
||||||
|
written += ret;
|
||||||
|
|
||||||
|
if (ret < to_write) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
u64 current_off;
|
||||||
|
const auto ret = nfs_lseek(file->device->nfs, file->fd, pos, dir, ¤t_off);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[NFS] nfs_lseek() failed: %s errno: %s\n", nfs_get_error(file->device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
r->_errno = 0;
|
||||||
|
return current_off;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = nfs_fstat(file->device->nfs, file->fd, st);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[NFS] nfs_fstat() failed: %s errno: %s\n", nfs_get_error(file->device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_unlink(struct _reent *r, const char *_path) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_unlink(device->nfs, path);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[NFS] nfs_unlink() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_rename(struct _reent *r, const char *_oldName, const char *_newName) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char oldName[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_oldName, oldName)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
char newName[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_newName, newName)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_rename(device->nfs, oldName, newName);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[NFS] nfs_rename() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_mkdir(struct _reent *r, const char *_path, int mode) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_mkdir(device->nfs, path);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_mkdir() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_rmdir(struct _reent *r, const char *_path) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_rmdir(device->nfs, path);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_rmdir() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
set_errno(r, EIO);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_opendir(device->nfs, path, &dir->dir);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_opendir() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
set_errno(r, -ret);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->device = device;
|
||||||
|
return dirState;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
SCOPED_MUTEX(&dir->device->mutex);
|
||||||
|
|
||||||
|
nfs_rewinddir(dir->device->nfs, dir->dir);
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(filestat, 0, sizeof(*filestat));
|
||||||
|
SCOPED_MUTEX(&dir->device->mutex);
|
||||||
|
|
||||||
|
const auto entry = nfs_readdir(dir->device->nfs, dir->dir);
|
||||||
|
if (!entry) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::strncpy(filename, entry->name, NAME_MAX);
|
||||||
|
filename[NAME_MAX - 1] = '\0';
|
||||||
|
|
||||||
|
// not everything is needed, however we may as well fill it all in.
|
||||||
|
filestat->st_dev = entry->dev;
|
||||||
|
filestat->st_ino = entry->inode;
|
||||||
|
filestat->st_mode = entry->mode;
|
||||||
|
filestat->st_nlink = entry->nlink;
|
||||||
|
filestat->st_uid = entry->uid;
|
||||||
|
filestat->st_gid = entry->gid;
|
||||||
|
filestat->st_size = entry->size;
|
||||||
|
filestat->st_atime = entry->atime.tv_sec;
|
||||||
|
filestat->st_mtime = entry->mtime.tv_sec;
|
||||||
|
filestat->st_ctime = entry->ctime.tv_sec;
|
||||||
|
filestat->st_blksize = entry->blksize;
|
||||||
|
filestat->st_blocks = entry->blocks;
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
SCOPED_MUTEX(&dir->device->mutex);
|
||||||
|
|
||||||
|
if (dir->dir) {
|
||||||
|
nfs_closedir(dir->device->nfs, dir->dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_stat(device->nfs, path, st);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_lstat() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_ftruncate(struct _reent *r, void *fd, off_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = nfs_ftruncate(file->device->nfs, file->fd, len);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_ftruncate() failed: %s errno: %s\n", nfs_get_error(file->device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_statvfs(struct _reent *r, const char *_path, struct statvfs *buf) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = nfs_statvfs(device->nfs, path, buf);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_statvfs() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_fsync(struct _reent *r, void *fd) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = nfs_fsync(file->device->nfs, file->fd);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_fsync() failed: %s errno: %s\n", nfs_get_error(file->device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_utimes(struct _reent *r, const char *_path, const struct timeval times[2]) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
if (!times) {
|
||||||
|
log_write("[NFS] devoptab_utimes() times is null\n");
|
||||||
|
return set_errno(r, EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!common::fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_nfs(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: nfs should accept const times, pr the fix.
|
||||||
|
struct timeval times_copy[2];
|
||||||
|
std::memcpy(times_copy, times, sizeof(times_copy));
|
||||||
|
|
||||||
|
const auto ret = nfs_utimes(device->nfs, path, times_copy);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[NFS] nfs_utimes() failed: %s errno: %s\n", nfs_get_error(device->nfs), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr devoptab_t DEVOPTAB = {
|
||||||
|
.structSize = sizeof(File),
|
||||||
|
.open_r = devoptab_open,
|
||||||
|
.close_r = devoptab_close,
|
||||||
|
.write_r = devoptab_write,
|
||||||
|
.read_r = devoptab_read,
|
||||||
|
.seek_r = devoptab_seek,
|
||||||
|
.fstat_r = devoptab_fstat,
|
||||||
|
.stat_r = devoptab_lstat,
|
||||||
|
.unlink_r = devoptab_unlink,
|
||||||
|
.rename_r = devoptab_rename,
|
||||||
|
.mkdir_r = devoptab_mkdir,
|
||||||
|
.dirStateSize = sizeof(Dir),
|
||||||
|
.diropen_r = devoptab_diropen,
|
||||||
|
.dirreset_r = devoptab_dirreset,
|
||||||
|
.dirnext_r = devoptab_dirnext,
|
||||||
|
.dirclose_r = devoptab_dirclose,
|
||||||
|
.statvfs_r = devoptab_statvfs,
|
||||||
|
.ftruncate_r = devoptab_ftruncate,
|
||||||
|
.fsync_r = devoptab_fsync,
|
||||||
|
.rmdir_r = devoptab_rmdir,
|
||||||
|
.lstat_r = devoptab_lstat,
|
||||||
|
.utimes_r = devoptab_utimes,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
Device device{};
|
||||||
|
devoptab_t devoptab{};
|
||||||
|
fs::FsPath mount{};
|
||||||
|
char name[32]{};
|
||||||
|
s32 ref_count{};
|
||||||
|
|
||||||
|
~Entry() {
|
||||||
|
if (device.nfs) {
|
||||||
|
if (device.mounted) {
|
||||||
|
nfs_umount(device.nfs);
|
||||||
|
}
|
||||||
|
nfs_destroy_context(device.nfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveDevice(mount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mutex g_mutex;
|
||||||
|
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Result MountNfsAll() {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
static const auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||||
|
auto e = static_cast<NfsMountConfigs*>(UserData);
|
||||||
|
if (!Section || !Key || !Value) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new entry if use section changed.
|
||||||
|
if (e->empty() || std::strcmp(Section, e->back().name.c_str())) {
|
||||||
|
e->emplace_back(Section);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::strcmp(Key, "url")) {
|
||||||
|
e->back().url = Value;
|
||||||
|
} else if (!std::strcmp(Key, "path")) {
|
||||||
|
e->back().path = Value;
|
||||||
|
} else if (!std::strcmp(Key, "name")) {
|
||||||
|
e->back().name = Value;
|
||||||
|
} else if (!std::strcmp(Key, "read_only")) {
|
||||||
|
e->back().read_only = ini_parse_getbool(Value, false);
|
||||||
|
} else {
|
||||||
|
log_write("[NFS] INI: Unknown key %s=%s\n", Key, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
NfsMountConfigs configs;
|
||||||
|
ini_browse(cb, &configs, "/config/sphaira/nfs.ini");
|
||||||
|
log_write("[NFS] 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("[NFS] Already mounted %s, skipping\n", config.name.c_str());
|
||||||
|
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("[NFS] No free entries to mount %s\n", config.name.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto entry = std::make_unique<Entry>();
|
||||||
|
|
||||||
|
entry->devoptab = DEVOPTAB;
|
||||||
|
entry->devoptab.name = entry->name;
|
||||||
|
entry->devoptab.deviceData = &entry->device;
|
||||||
|
entry->device.config = config;
|
||||||
|
std::snprintf(entry->name, sizeof(entry->name), "%s", config.name.c_str());
|
||||||
|
std::snprintf(entry->mount, sizeof(entry->mount), "%s:/", config.name.c_str());
|
||||||
|
common::update_devoptab_for_read_only(&entry->devoptab, config.read_only);
|
||||||
|
|
||||||
|
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
|
||||||
|
log_write("[NFS] DEVICE SUCCESS %s %s\n", entry->device.config.url.c_str(), entry->name);
|
||||||
|
|
||||||
|
entry->ref_count++;
|
||||||
|
*itr = std::move(entry);
|
||||||
|
log_write("[NFS] Mounted %s at /%s\n", config.url.c_str(), config.name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnmountNfsAll() {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
for (auto& entry : g_entries) {
|
||||||
|
if (entry) {
|
||||||
|
entry.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetNfsMounts(location::StdioEntries& out) {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
out.clear();
|
||||||
|
|
||||||
|
for (const auto& entry : g_entries) {
|
||||||
|
if (entry) {
|
||||||
|
out.emplace_back(entry->mount, entry->name, entry->device.config.read_only);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::devoptab
|
||||||
617
sphaira/source/utils/devoptab_smb2.cpp
Normal file
617
sphaira/source/utils/devoptab_smb2.cpp
Normal file
@@ -0,0 +1,617 @@
|
|||||||
|
#include "utils/devoptab_common.hpp"
|
||||||
|
#include "defines.hpp"
|
||||||
|
#include "log.hpp"
|
||||||
|
#include "location.hpp"
|
||||||
|
|
||||||
|
#include <sys/iosupport.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <string>
|
||||||
|
#include <cstring>
|
||||||
|
#include <smb2/smb2.h>
|
||||||
|
#include <smb2/libsmb2.h>
|
||||||
|
#include <minIni.h>
|
||||||
|
|
||||||
|
namespace sphaira::devoptab {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct Smb2MountConfig {
|
||||||
|
std::string name{};
|
||||||
|
std::string url{};
|
||||||
|
std::string user{};
|
||||||
|
std::string pass{};
|
||||||
|
bool read_only{};
|
||||||
|
};
|
||||||
|
using Smb2MountConfigs = std::vector<Smb2MountConfig>;
|
||||||
|
|
||||||
|
struct Device {
|
||||||
|
smb2_context* smb2{};
|
||||||
|
smb2_url* url{};
|
||||||
|
Smb2MountConfig config;
|
||||||
|
bool mounted{};
|
||||||
|
Mutex mutex{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct File {
|
||||||
|
Device* device;
|
||||||
|
smb2fh* fd;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Dir {
|
||||||
|
Device* device;
|
||||||
|
smb2dir* dir;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool mount_smb2(Device& device) {
|
||||||
|
if (device.mounted) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.smb2) {
|
||||||
|
device.smb2 = smb2_init_context();
|
||||||
|
if (!device.smb2) {
|
||||||
|
log_write("[SMB2] smb2_init_context() failed\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
smb2_set_security_mode(device.smb2, SMB2_NEGOTIATE_SIGNING_ENABLED);
|
||||||
|
|
||||||
|
if (!device.config.user.empty()) {
|
||||||
|
smb2_set_user(device.smb2, device.config.user.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.config.pass.empty()) {
|
||||||
|
smb2_set_password(device.smb2, device.config.pass.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device.url) {
|
||||||
|
device.url = smb2_parse_url(device.smb2, device.config.url.c_str());
|
||||||
|
if (!device.url) {
|
||||||
|
log_write("[SMB2] smb2_parse_url() failed: %s\n", smb2_get_error(device.smb2));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log_write("[SMB2] Connecting to %s/%s as %s\n",
|
||||||
|
device.url->server,
|
||||||
|
device.url->share,
|
||||||
|
device.url->user ? device.url->user : "guest");
|
||||||
|
const auto ret = smb2_connect_share(device.smb2, device.url->server, device.url->share, device.url->user);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[SMB2] smb2_connect_share() failed: %s errno: %s\n", smb2_get_error(device.smb2), std::strerror(-ret));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
device.mounted = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void fill_stat(struct stat* st, const smb2_stat_64* smb2_st) {
|
||||||
|
if (smb2_st->smb2_type == SMB2_TYPE_FILE) {
|
||||||
|
st->st_mode = S_IFREG;
|
||||||
|
} else if (smb2_st->smb2_type == SMB2_TYPE_DIRECTORY) {
|
||||||
|
st->st_mode = S_IFDIR;
|
||||||
|
} else if (smb2_st->smb2_type == SMB2_TYPE_LINK) {
|
||||||
|
st->st_mode = S_IFLNK;
|
||||||
|
} else {
|
||||||
|
log_write("[SMB2] Unknown file type: %u\n", smb2_st->smb2_type);
|
||||||
|
st->st_mode = S_IFCHR; // will be skipped by stdio readdir wrapper.
|
||||||
|
}
|
||||||
|
|
||||||
|
st->st_ino = smb2_st->smb2_ino;
|
||||||
|
st->st_nlink = smb2_st->smb2_nlink;
|
||||||
|
st->st_size = smb2_st->smb2_size;
|
||||||
|
st->st_atime = smb2_st->smb2_atime;
|
||||||
|
st->st_mtime = smb2_st->smb2_mtime;
|
||||||
|
st->st_ctime = smb2_st->smb2_ctime;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool fix_path(const char* str, char* out) {
|
||||||
|
return common::fix_path(str, out, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int set_errno(struct _reent *r, int err) {
|
||||||
|
r->_errno = err;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
auto file = static_cast<File*>(fileStruct);
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
if (device->config.read_only && (flags & (O_WRONLY | O_RDWR | O_CREAT | O_TRUNC | O_APPEND))) {
|
||||||
|
return set_errno(r, EROFS);
|
||||||
|
}
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
file->fd = smb2_open(device->smb2, path, flags);
|
||||||
|
if (!file->fd) {
|
||||||
|
log_write("[SMB2] smb2_open() failed: %s\n", smb2_get_error(device->smb2));
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
file->device = device;
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_close(struct _reent *r, void *fd) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
if (file->fd) {
|
||||||
|
smb2_close(file->device->smb2, file->fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(file, 0, sizeof(*file));
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = smb2_read(file->device->smb2, file->fd, reinterpret_cast<uint8_t*>(ptr), len);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[SMB2] smb2_read() failed: %s errno: %s\n", smb2_get_error(file->device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t devoptab_write(struct _reent *r, void *fd, const char *ptr, size_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = smb2_write(file->device->smb2, file->fd, reinterpret_cast<const uint8_t*>(ptr), len);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[SMB2] smb2_write() failed: %s errno: %s\n", smb2_get_error(file->device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = smb2_lseek(file->device->smb2, file->fd, pos, dir, nullptr);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[SMB2] smb2_lseek() failed: %s errno: %s\n", smb2_get_error(file->device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
r->_errno = 0;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
smb2_stat_64 smb2_st{};
|
||||||
|
const auto ret = smb2_fstat(file->device->smb2, file->fd, &smb2_st);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[SMB2] smb2_fstat() failed: %s errno: %s\n", smb2_get_error(file->device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_stat(st, &smb2_st);
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_unlink(struct _reent *r, const char *_path) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = smb2_unlink(device->smb2, path);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[SMB2] smb2_unlink() failed: %s errno: %s\n", smb2_get_error(device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_rename(struct _reent *r, const char *_oldName, const char *_newName) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char oldName[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_oldName, oldName)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
char newName[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_newName, newName)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = smb2_rename(device->smb2, oldName, newName);
|
||||||
|
if (ret < 0) {
|
||||||
|
log_write("[SMB2] smb2_rename() failed: %s errno: %s\n", smb2_get_error(device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_mkdir(struct _reent *r, const char *_path, int mode) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = smb2_mkdir(device->smb2, path);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[SMB2] smb2_mkdir() failed: %s errno: %s\n", smb2_get_error(device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_rmdir(struct _reent *r, const char *_path) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto ret = smb2_rmdir(device->smb2, path);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[SMB2] smb2_rmdir() failed: %s errno: %s\n", smb2_get_error(device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DIR_ITER* devoptab_diropen(struct _reent *r, DIR_ITER *dirState, const char *_path) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
set_errno(r, ENOENT);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
set_errno(r, EIO);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->dir = smb2_opendir(device->smb2, path);
|
||||||
|
if (!dir->dir) {
|
||||||
|
log_write("[SMB2] smb2_opendir() failed: %s\n", smb2_get_error(device->smb2));
|
||||||
|
set_errno(r, EIO);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
dir->device = device;
|
||||||
|
return dirState;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
SCOPED_MUTEX(&dir->device->mutex);
|
||||||
|
|
||||||
|
smb2_rewinddir(dir->device->smb2, dir->dir);
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struct stat *filestat) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
std::memset(filestat, 0, sizeof(*filestat));
|
||||||
|
SCOPED_MUTEX(&dir->device->mutex);
|
||||||
|
|
||||||
|
const auto entry = smb2_readdir(dir->device->smb2, dir->dir);
|
||||||
|
if (!entry) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::strncpy(filename, entry->name, NAME_MAX);
|
||||||
|
filename[NAME_MAX - 1] = '\0';
|
||||||
|
fill_stat(filestat, &entry->st);
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||||
|
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||||
|
SCOPED_MUTEX(&dir->device->mutex);
|
||||||
|
|
||||||
|
if (dir->dir) {
|
||||||
|
smb2_closedir(dir->device->smb2, dir->dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::memset(dir, 0, sizeof(*dir));
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
std::memset(st, 0, sizeof(*st));
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
smb2_stat_64 smb2_st{};
|
||||||
|
const auto ret = smb2_stat(device->smb2, path, &smb2_st);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[SMB2] smb2_lstat() failed: %s errno: %s\n", smb2_get_error(device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
fill_stat(st, &smb2_st);
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_ftruncate(struct _reent *r, void *fd, off_t len) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = smb2_ftruncate(file->device->smb2, file->fd, len);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[SMB2] smb2_ftruncate() failed: %s errno: %s\n", smb2_get_error(file->device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_statvfs(struct _reent *r, const char *_path, struct statvfs *buf) {
|
||||||
|
auto device = static_cast<Device*>(r->deviceData);
|
||||||
|
SCOPED_MUTEX(&device->mutex);
|
||||||
|
|
||||||
|
char path[FS_MAX_PATH]{};
|
||||||
|
if (!fix_path(_path, path)) {
|
||||||
|
return set_errno(r, ENOENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mount_smb2(*device)) {
|
||||||
|
return set_errno(r, EIO);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct smb2_statvfs smb2_st{};
|
||||||
|
const auto ret = smb2_statvfs(device->smb2, path, &smb2_st);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[SMB2] smb2_statvfs() failed: %s errno: %s\n", smb2_get_error(device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->f_bsize = smb2_st.f_bsize;
|
||||||
|
buf->f_frsize = smb2_st.f_frsize;
|
||||||
|
buf->f_blocks = smb2_st.f_blocks;
|
||||||
|
buf->f_bfree = smb2_st.f_bfree;
|
||||||
|
buf->f_bavail = smb2_st.f_bavail;
|
||||||
|
buf->f_files = smb2_st.f_files;
|
||||||
|
buf->f_ffree = smb2_st.f_ffree;
|
||||||
|
buf->f_favail = smb2_st.f_favail;
|
||||||
|
buf->f_fsid = smb2_st.f_fsid;
|
||||||
|
buf->f_flag = smb2_st.f_flag;
|
||||||
|
buf->f_namemax = smb2_st.f_namemax;
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int devoptab_fsync(struct _reent *r, void *fd) {
|
||||||
|
auto file = static_cast<File*>(fd);
|
||||||
|
SCOPED_MUTEX(&file->device->mutex);
|
||||||
|
|
||||||
|
const auto ret = smb2_fsync(file->device->smb2, file->fd);
|
||||||
|
if (ret) {
|
||||||
|
log_write("[SMB2] smb2_fsync() failed: %s errno: %s\n", smb2_get_error(file->device->smb2), std::strerror(-ret));
|
||||||
|
return set_errno(r, -ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
return r->_errno = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr devoptab_t DEVOPTAB = {
|
||||||
|
.structSize = sizeof(File),
|
||||||
|
.open_r = devoptab_open,
|
||||||
|
.close_r = devoptab_close,
|
||||||
|
.write_r = devoptab_write,
|
||||||
|
.read_r = devoptab_read,
|
||||||
|
.seek_r = devoptab_seek,
|
||||||
|
.fstat_r = devoptab_fstat,
|
||||||
|
.stat_r = devoptab_lstat,
|
||||||
|
.unlink_r = devoptab_unlink,
|
||||||
|
.rename_r = devoptab_rename,
|
||||||
|
.mkdir_r = devoptab_mkdir,
|
||||||
|
.dirStateSize = sizeof(Dir),
|
||||||
|
.diropen_r = devoptab_diropen,
|
||||||
|
.dirreset_r = devoptab_dirreset,
|
||||||
|
.dirnext_r = devoptab_dirnext,
|
||||||
|
.dirclose_r = devoptab_dirclose,
|
||||||
|
.statvfs_r = devoptab_statvfs,
|
||||||
|
.ftruncate_r = devoptab_ftruncate,
|
||||||
|
.fsync_r = devoptab_fsync,
|
||||||
|
.rmdir_r = devoptab_rmdir,
|
||||||
|
.lstat_r = devoptab_lstat,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Entry {
|
||||||
|
Device device{};
|
||||||
|
devoptab_t devoptab{};
|
||||||
|
fs::FsPath mount{};
|
||||||
|
char name[32]{};
|
||||||
|
s32 ref_count{};
|
||||||
|
|
||||||
|
~Entry() {
|
||||||
|
if (device.smb2) {
|
||||||
|
if (device.mounted) {
|
||||||
|
smb2_disconnect_share(device.smb2);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.url) {
|
||||||
|
smb2_destroy_url(device.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
smb2_destroy_context(device.smb2);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveDevice(mount);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Mutex g_mutex;
|
||||||
|
std::array<std::unique_ptr<Entry>, common::MAX_ENTRIES> g_entries;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
Result MountSmb2All() {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
static const auto cb = [](const mTCHAR *Section, const mTCHAR *Key, const mTCHAR *Value, void *UserData) -> int {
|
||||||
|
auto e = static_cast<Smb2MountConfigs*>(UserData);
|
||||||
|
if (!Section || !Key || !Value) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add new entry if use section changed.
|
||||||
|
if (e->empty() || std::strcmp(Section, e->back().name.c_str())) {
|
||||||
|
e->emplace_back(Section);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!std::strcmp(Key, "url")) {
|
||||||
|
e->back().url = Value;
|
||||||
|
} else if (!std::strcmp(Key, "name")) {
|
||||||
|
e->back().name = Value;
|
||||||
|
} else if (!std::strcmp(Key, "user")) {
|
||||||
|
e->back().user = Value;
|
||||||
|
} else if (!std::strcmp(Key, "pass")) {
|
||||||
|
e->back().pass = Value;
|
||||||
|
} else if (!std::strcmp(Key, "read_only")) {
|
||||||
|
e->back().read_only = ini_parse_getbool(Value, false);
|
||||||
|
} else {
|
||||||
|
log_write("[SMB2] INI: Unknown key %s=%s\n", Key, Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Smb2MountConfigs configs{};
|
||||||
|
ini_browse(cb, &configs, "/config/sphaira/smb.ini");
|
||||||
|
log_write("[SMB2] 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("[SMB2] Already mounted %s, skipping\n", config.name.c_str());
|
||||||
|
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("[SMB2] No free entries to mount %s\n", config.name.c_str());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto entry = std::make_unique<Entry>();
|
||||||
|
|
||||||
|
entry->devoptab = DEVOPTAB;
|
||||||
|
entry->devoptab.name = entry->name;
|
||||||
|
entry->devoptab.deviceData = &entry->device;
|
||||||
|
entry->device.config = config;
|
||||||
|
std::snprintf(entry->name, sizeof(entry->name), "%s", config.name.c_str());
|
||||||
|
std::snprintf(entry->mount, sizeof(entry->mount), "%s:/", config.name.c_str());
|
||||||
|
common::update_devoptab_for_read_only(&entry->devoptab, config.read_only);
|
||||||
|
|
||||||
|
R_UNLESS(AddDevice(&entry->devoptab) >= 0, 0x1);
|
||||||
|
log_write("[SMB2] DEVICE SUCCESS %s %s\n", entry->device.config.url.c_str(), entry->name);
|
||||||
|
|
||||||
|
entry->ref_count++;
|
||||||
|
*itr = std::move(entry);
|
||||||
|
log_write("[SMB2] Mounted %s at /%s\n", config.user.c_str(), config.name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UnmountSmb2All() {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
|
||||||
|
for (auto& entry : g_entries) {
|
||||||
|
if (entry) {
|
||||||
|
entry.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Result GetSmb2Mounts(location::StdioEntries& out) {
|
||||||
|
SCOPED_MUTEX(&g_mutex);
|
||||||
|
out.clear();
|
||||||
|
|
||||||
|
for (const auto& entry : g_entries) {
|
||||||
|
if (entry) {
|
||||||
|
out.emplace_back(entry->mount, entry->name, entry->device.config.read_only);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace sphaira::devoptab
|
||||||
Reference in New Issue
Block a user