devoptab: add webdav, refactor network devices, multi thread r/w to improve perf and support webdav uploads.
This commit is contained in:
@@ -109,6 +109,7 @@ add_executable(sphaira
|
||||
source/utils/devoptab_nfs.cpp
|
||||
source/utils/devoptab_smb2.cpp
|
||||
source/utils/devoptab_ftp.cpp
|
||||
source/utils/devoptab_webdav.cpp
|
||||
|
||||
source/usb/base.cpp
|
||||
source/usb/usbds.cpp
|
||||
@@ -269,6 +270,15 @@ FetchContent_Declare(libsmb2
|
||||
GIT_TAG 867beea
|
||||
)
|
||||
|
||||
FetchContent_Declare(pugixml
|
||||
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
||||
GIT_TAG v1.15
|
||||
)
|
||||
|
||||
set(PUGIXML_NO_EXCEPTIONS ON)
|
||||
# set(PUGIXML_NO_XPATH ON)
|
||||
set(PUGIXML_WCHAR_MODE OFF)
|
||||
|
||||
set(USE_NEW_ZSTD ON)
|
||||
# has issues with some homebrew and game icons (oxenfree, overwatch2).
|
||||
set(USE_NVJPG OFF)
|
||||
@@ -331,6 +341,7 @@ FetchContent_MakeAvailable(
|
||||
libusbdvd
|
||||
libnfs
|
||||
libsmb2
|
||||
pugixml
|
||||
)
|
||||
|
||||
set(FTPSRV_LIB_BUILD TRUE)
|
||||
@@ -464,6 +475,7 @@ target_link_libraries(sphaira PRIVATE
|
||||
libusbdvd
|
||||
nfs
|
||||
smb2
|
||||
pugixml
|
||||
|
||||
${minizip_lib}
|
||||
ZLIB::ZLIB
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <dirent.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/syslimits.h>
|
||||
@@ -307,7 +308,7 @@ Result IsDirEmpty(fs::Fs* m_fs, const fs::FsPath& path, bool* out);
|
||||
|
||||
// helpers.
|
||||
Result read_entire_file(Fs* fs, const FsPath& path, std::vector<u8>& out);
|
||||
Result write_entire_file(Fs* fs, const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = true);
|
||||
Result write_entire_file(Fs* fs, const FsPath& path, std::span<const u8> in, bool ignore_read_only = true);
|
||||
Result copy_entire_file(Fs* fs, const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
|
||||
|
||||
struct Fs {
|
||||
@@ -354,7 +355,7 @@ struct Fs {
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out) {
|
||||
return fs::read_entire_file(this, path, out);
|
||||
}
|
||||
Result write_entire_file(const FsPath& path, const std::vector<u8>& in) {
|
||||
Result write_entire_file(const FsPath& path, std::span<const u8> in) {
|
||||
return fs::write_entire_file(this, path, in, m_ignore_read_only);
|
||||
}
|
||||
Result copy_entire_file(const FsPath& dst, const FsPath& src) {
|
||||
|
||||
@@ -33,20 +33,13 @@ void UmountBfsar(const fs::FsPath& mount);
|
||||
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
void UmountNro(const fs::FsPath& mount);
|
||||
|
||||
Result MountWebdavAll();
|
||||
Result MountHttpAll();
|
||||
Result GetHttpMounts(location::StdioEntries& out);
|
||||
void UnmountHttpAll();
|
||||
|
||||
Result MountFtpAll();
|
||||
Result GetFtpMounts(location::StdioEntries& out);
|
||||
void UnmountFtpAll();
|
||||
|
||||
Result MountNfsAll();
|
||||
Result GetNfsMounts(location::StdioEntries& out);
|
||||
void UnmountNfsAll();
|
||||
|
||||
Result MountSmb2All();
|
||||
Result GetSmb2Mounts(location::StdioEntries& out);
|
||||
void UnmountSmb2All();
|
||||
|
||||
Result GetNetworkDevices(location::StdioEntries& out);
|
||||
void UmountAllNeworkDevices();
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
|
||||
@@ -2,8 +2,13 @@
|
||||
|
||||
#include "yati/source/file.hpp"
|
||||
#include "utils/lru.hpp"
|
||||
#include "location.hpp"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace sphaira::devoptab::common {
|
||||
|
||||
@@ -85,4 +90,127 @@ 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);
|
||||
|
||||
struct PushPullThreadData {
|
||||
PushPullThreadData(CURL* _curl);
|
||||
virtual ~PushPullThreadData();
|
||||
Result CreateAndStart();
|
||||
|
||||
void Cancel();
|
||||
bool IsRunning();
|
||||
|
||||
size_t PullData(char* data, size_t total_size);
|
||||
size_t PushData(const char* data, size_t total_size);
|
||||
|
||||
static size_t push_thread_callback(const char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
static size_t pull_thread_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
|
||||
private:
|
||||
static void thread_func(void* arg);
|
||||
|
||||
public:
|
||||
CURL* const curl{};
|
||||
std::vector<char> buffer{};
|
||||
Mutex mutex{};
|
||||
CondVar can_push{};
|
||||
CondVar can_pull{};
|
||||
|
||||
long code{};
|
||||
bool error{};
|
||||
bool finished{};
|
||||
bool started{};
|
||||
|
||||
private:
|
||||
Thread thread{};
|
||||
};
|
||||
|
||||
struct MountConfig {
|
||||
std::string name{};
|
||||
std::string url{};
|
||||
std::string user{};
|
||||
std::string pass{};
|
||||
std::optional<long> port{};
|
||||
int timeout{3000}; // 3 seconds.
|
||||
bool read_only{};
|
||||
bool no_stat_file{true};
|
||||
bool no_stat_dir{true};
|
||||
|
||||
std::unordered_map<std::string, std::string> extra{};
|
||||
};
|
||||
|
||||
struct PullThreadData final : PushPullThreadData {
|
||||
using PushPullThreadData::PushPullThreadData;
|
||||
~PullThreadData();
|
||||
};
|
||||
|
||||
|
||||
struct PushThreadData final : PushPullThreadData {
|
||||
using PushPullThreadData::PushPullThreadData;
|
||||
~PushThreadData();
|
||||
};
|
||||
|
||||
struct MountDevice {
|
||||
MountDevice(const MountConfig& _config) : config{_config} {}
|
||||
virtual ~MountDevice() = default;
|
||||
|
||||
virtual bool fix_path(const char* str, char* out, bool strip_leading_slash = false) {
|
||||
return common::fix_path(str, out, strip_leading_slash);
|
||||
}
|
||||
|
||||
virtual bool Mount() = 0;
|
||||
virtual int devoptab_open(void *fileStruct, const char *path, int flags, int mode) { return -EIO; }
|
||||
virtual int devoptab_close(void *fd) { return -EIO; }
|
||||
virtual ssize_t devoptab_read(void *fd, char *ptr, size_t len) { return -EIO; }
|
||||
virtual ssize_t devoptab_write(void *fd, const char *ptr, size_t len) { return -EIO; }
|
||||
virtual off_t devoptab_seek(void *fd, off_t pos, int dir) { return 0; }
|
||||
virtual int devoptab_fstat(void *fd, struct stat *st) { return -EIO; }
|
||||
virtual int devoptab_unlink(const char *path) { return -EIO; }
|
||||
virtual int devoptab_rename(const char *oldName, const char *newName) { return -EIO; }
|
||||
virtual int devoptab_mkdir(const char *path, int mode) { return -EIO; }
|
||||
virtual int devoptab_rmdir(const char *path) { return -EIO; }
|
||||
virtual int devoptab_diropen(void* fd, const char *path) { return -EIO; }
|
||||
virtual int devoptab_dirreset(void* fd) { return -EIO; }
|
||||
virtual int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) { return -EIO; }
|
||||
virtual int devoptab_dirclose(void* fd) { return -EIO; }
|
||||
virtual int devoptab_lstat(const char *path, struct stat *st) { return -EIO; }
|
||||
virtual int devoptab_ftruncate(void *fd, off_t len) { return -EIO; }
|
||||
virtual int devoptab_statvfs(const char *_path, struct statvfs *buf) { return -EIO; }
|
||||
virtual int devoptab_fsync(void *fd) { return -EIO; }
|
||||
virtual int devoptab_utimes(const char *_path, const struct timeval times[2]) { return -EIO; }
|
||||
|
||||
const MountConfig config;
|
||||
};
|
||||
|
||||
struct MountCurlDevice : MountDevice {
|
||||
using MountDevice::MountDevice;
|
||||
// MountCurlDevice(const MountConfig& _config);
|
||||
virtual ~MountCurlDevice();
|
||||
|
||||
PushThreadData* CreatePushData(CURL* curl, const std::string& url, size_t offset);
|
||||
PullThreadData* CreatePullData(CURL* curl, const std::string& url, bool append = false);
|
||||
|
||||
virtual bool Mount();
|
||||
virtual void curl_set_common_options(CURL* curl, const std::string& url);
|
||||
static size_t write_memory_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
static size_t write_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
static size_t read_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
static std::string html_decode(const std::string_view& str);
|
||||
static std::string url_decode(const std::string& str);
|
||||
std::string build_url(const std::string& path, bool is_dir);
|
||||
|
||||
protected:
|
||||
CURL* curl{};
|
||||
CURL* transfer_curl{};
|
||||
|
||||
private:
|
||||
// path extracted from the url.
|
||||
std::string m_url_path{};
|
||||
CURLU* curlu{};
|
||||
CURLSH* m_curl_share{};
|
||||
RwLock m_rwlocks[CURL_LOCK_DATA_LAST]{};
|
||||
bool m_mounted{};
|
||||
};
|
||||
|
||||
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* config_path, const char* name);
|
||||
|
||||
} // namespace sphaira::devoptab::common
|
||||
|
||||
@@ -543,7 +543,14 @@ void App::Loop() {
|
||||
}
|
||||
|
||||
auto App::Push(std::unique_ptr<ui::Widget>&& widget) -> void {
|
||||
log_write("[Mui] pushing widget\n");
|
||||
log_write("[APP] pushing widget\n");
|
||||
|
||||
// when freeing widges, this may cancel a transfer which causes it to push
|
||||
// an error box, so check if we are quitting first before adding.
|
||||
if (g_app->m_quit) {
|
||||
log_write("[APP] is quitting, not pushing widget\n");
|
||||
return;
|
||||
}
|
||||
|
||||
// check if the widget wants to pop before adding.
|
||||
// this can happen if something failed in the constructor and the widget wants to exit.
|
||||
@@ -1597,6 +1604,11 @@ App::App(const char* argv0) {
|
||||
devoptab::MountHttpAll();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("webdav init");
|
||||
devoptab::MountWebdavAll();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("ftp init");
|
||||
devoptab::MountFtpAll();
|
||||
@@ -2178,6 +2190,16 @@ App::~App() {
|
||||
curl::ExitSignal();
|
||||
}
|
||||
|
||||
// this has to be called before any cleanup to ensure the lifetime of
|
||||
// nvg is still active as some widgets may need to free images.
|
||||
// clear in reverse order as the widgets are a stack.
|
||||
{
|
||||
SCOPED_TIMESTAMP("widget exit");
|
||||
while (!m_widgets.empty()) {
|
||||
m_widgets.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
utils::Async async_exit([this](){
|
||||
{
|
||||
SCOPED_TIMESTAMP("usbdvd_exit");
|
||||
@@ -2206,23 +2228,8 @@ App::~App() {
|
||||
|
||||
// this has to come before curl exit as it uses curl global.
|
||||
{
|
||||
SCOPED_TIMESTAMP("http exit");
|
||||
devoptab::UnmountHttpAll();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("ftp exit");
|
||||
devoptab::UnmountFtpAll();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("nfs exit");
|
||||
devoptab::UnmountNfsAll();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("smb exit");
|
||||
devoptab::UnmountSmb2All();
|
||||
SCOPED_TIMESTAMP("devoptab exit");
|
||||
devoptab::UmountAllNeworkDevices();
|
||||
}
|
||||
|
||||
// do these last as they were signalled to exit.
|
||||
@@ -2248,25 +2255,6 @@ App::~App() {
|
||||
}
|
||||
});
|
||||
|
||||
// destroy this first as it seems to prevent a crash when exiting the appstore
|
||||
// when an image that was being drawn is displayed
|
||||
// replicate: saves -> homebrew -> misc -> appstore -> sphaira -> changelog -> exit
|
||||
// it will crash when deleting image 43.
|
||||
{
|
||||
SCOPED_TIMESTAMP("destroy frame buffer resources");
|
||||
this->destroyFramebufferResources();
|
||||
}
|
||||
|
||||
// this has to be called before any cleanup to ensure the lifetime of
|
||||
// nvg is still active as some widgets may need to free images.
|
||||
// clear in reverse order as the widgets are a stack (todo: just use a stack?)
|
||||
{
|
||||
SCOPED_TIMESTAMP("widget exit");
|
||||
while (!m_widgets.empty()) {
|
||||
m_widgets.pop_back();
|
||||
}
|
||||
}
|
||||
|
||||
// do not async close theme as it frees textures.
|
||||
{
|
||||
SCOPED_TIMESTAMP("theme exit");
|
||||
@@ -2274,6 +2262,11 @@ App::~App() {
|
||||
CloseTheme();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("destroy frame buffer resources");
|
||||
this->destroyFramebufferResources();
|
||||
}
|
||||
|
||||
{
|
||||
SCOPED_TIMESTAMP("nvg exit");
|
||||
nvgDeleteImage(vg, m_default_image);
|
||||
|
||||
@@ -134,7 +134,7 @@ Result read_entire_file(Fs* fs, const FsPath& path, std::vector<u8>& out) {
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Result write_entire_file(Fs* fs, const FsPath& path, const std::vector<u8>& in, bool ignore_read_only) {
|
||||
Result write_entire_file(Fs* fs, const FsPath& path, std::span<const u8> in, bool ignore_read_only) {
|
||||
R_UNLESS(ignore_read_only || !is_read_only(path), Result_FsReadOnly);
|
||||
|
||||
if (auto rc = fs->CreateFile(path, in.size(), 0); R_FAILED(rc) && rc != FsError_PathAlreadyExists) {
|
||||
@@ -586,6 +586,7 @@ void File::Close() {
|
||||
}
|
||||
} else {
|
||||
if (m_stdio) {
|
||||
log_write("[FS] closing stdio file\n");
|
||||
std::fclose(m_stdio);
|
||||
m_stdio = {};
|
||||
}
|
||||
|
||||
@@ -8,12 +8,15 @@
|
||||
namespace sphaira::i18n {
|
||||
namespace {
|
||||
|
||||
std::vector<u8> g_i18n_data;
|
||||
yyjson_doc* json;
|
||||
yyjson_val* root;
|
||||
std::unordered_map<std::string, std::string> g_tr_cache;
|
||||
std::vector<u8> g_i18n_data{};
|
||||
yyjson_doc* json{};
|
||||
yyjson_val* root{};
|
||||
std::unordered_map<std::string, std::string> g_tr_cache{};
|
||||
Mutex g_mutex{};
|
||||
|
||||
std::string get_internal(std::string_view str) {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
const std::string kkey = {str.data(), str.length()};
|
||||
|
||||
if (auto it = g_tr_cache.find(kkey); it != g_tr_cache.end()) {
|
||||
@@ -50,6 +53,8 @@ std::string get_internal(std::string_view str) {
|
||||
} // namespace
|
||||
|
||||
bool init(long index) {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
g_tr_cache.clear();
|
||||
R_TRY_RESULT(romfsInit(), false);
|
||||
ON_SCOPE_EXIT( romfsExit() );
|
||||
@@ -128,6 +133,8 @@ bool init(long index) {
|
||||
}
|
||||
|
||||
void exit() {
|
||||
SCOPED_MUTEX(&g_mutex);
|
||||
|
||||
if (json) {
|
||||
yyjson_doc_free(json);
|
||||
json = nullptr;
|
||||
|
||||
@@ -93,32 +93,8 @@ auto GetStdio(bool write) -> StdioEntries {
|
||||
|
||||
{
|
||||
StdioEntries entries;
|
||||
if (R_SUCCEEDED(devoptab::GetNfsMounts(entries))) {
|
||||
log_write("[NFS] got nfs mounts: %zu\n", entries.size());
|
||||
add_from_entries(entries, out, write);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
StdioEntries entries;
|
||||
if (R_SUCCEEDED(devoptab::GetSmb2Mounts(entries))) {
|
||||
log_write("[SMB2] got smb2 mounts: %zu\n", entries.size());
|
||||
add_from_entries(entries, out, write);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
StdioEntries entries;
|
||||
if (R_SUCCEEDED(devoptab::GetHttpMounts(entries))) {
|
||||
log_write("[HTTP] got http mounts: %zu\n", entries.size());
|
||||
add_from_entries(entries, out, write);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
StdioEntries entries;
|
||||
if (R_SUCCEEDED(devoptab::GetFtpMounts(entries))) {
|
||||
log_write("[FTP] got ftp mounts: %zu\n", entries.size());
|
||||
if (R_SUCCEEDED(devoptab::GetNetworkDevices(entries))) {
|
||||
log_write("[LOCATION] got devoptab mounts: %zu\n", entries.size());
|
||||
add_from_entries(entries, out, write);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -19,37 +19,6 @@
|
||||
namespace sphaira::devoptab {
|
||||
namespace {
|
||||
|
||||
constexpr long DEFAULT_HTTP_TIMEOUT = 3000; // 3 seconds.
|
||||
|
||||
#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{};
|
||||
std::optional<long> port{};
|
||||
long timeout{DEFAULT_HTTP_TIMEOUT};
|
||||
bool no_stat_file{true};
|
||||
bool no_stat_dir{true};
|
||||
};
|
||||
using HttpMountConfigs = std::vector<HttpMountConfig>;
|
||||
|
||||
struct Device {
|
||||
CURL* curl{};
|
||||
HttpMountConfig config;
|
||||
Mutex mutex{};
|
||||
bool mounted{};
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
std::string name{};
|
||||
std::string href{};
|
||||
@@ -63,115 +32,51 @@ struct FileEntry {
|
||||
};
|
||||
|
||||
struct File {
|
||||
Device* client;
|
||||
FileEntry* entry;
|
||||
common::PushPullThreadData* push_pull_thread_data;
|
||||
size_t off;
|
||||
size_t last_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);
|
||||
struct Device final : common::MountCurlDevice {
|
||||
using MountCurlDevice::MountCurlDevice;
|
||||
|
||||
// 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);
|
||||
}
|
||||
private:
|
||||
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;
|
||||
|
||||
// store the data.
|
||||
const auto offset = data->size();
|
||||
data->resize(offset + realsize);
|
||||
std::memcpy(data->data() + offset, ptr, realsize);
|
||||
bool http_dirlist(const std::string& path, DirEntries& out);
|
||||
bool http_stat(const std::string& path, struct stat* st, bool is_dir);
|
||||
|
||||
return realsize;
|
||||
}
|
||||
private:
|
||||
bool mounted{};
|
||||
};
|
||||
|
||||
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 rsize = std::min(size * nmemb, data->size());
|
||||
|
||||
std::memcpy(data->data(), ptr, rsize);
|
||||
*data = data->subspan(rsize);
|
||||
return rsize;
|
||||
}
|
||||
|
||||
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());
|
||||
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_TIMEOUT, client.config.timeout);
|
||||
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_CONNECTTIMEOUT, client.config.timeout);
|
||||
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);
|
||||
|
||||
if (client.config.port) {
|
||||
CURL_EASY_SETOPT_LOG(client.curl, CURLOPT_PORT, client.config.port.value());
|
||||
}
|
||||
|
||||
// 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);
|
||||
bool Device::http_dirlist(const std::string& path, DirEntries& out) {
|
||||
const auto url = build_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);
|
||||
curl_set_common_options(this->curl, url);
|
||||
curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, write_memory_callback);
|
||||
curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, (void *)&chunk);
|
||||
|
||||
const auto res = curl_easy_perform(client.curl);
|
||||
const auto res = curl_easy_perform(this->curl);
|
||||
if (res != CURLE_OK) {
|
||||
log_write("[HTTP] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||
return false;
|
||||
@@ -218,22 +123,19 @@ bool http_dirlist(Device& client, const std::string& path, DirEntries& out) {
|
||||
}
|
||||
|
||||
pos = name_end + anchor_tag_end.length();
|
||||
const auto href_view = table_view.substr(href_begin, href_end - href_begin);
|
||||
const auto name_view = table_view.substr(name_begin, name_end - name_begin);
|
||||
const auto href = url_decode(std::string{table_view.substr(href_begin, href_end - href_begin)});
|
||||
auto name = url_decode(std::string{table_view.substr(name_begin, name_end - name_begin)});
|
||||
|
||||
// skip empty names/links, root dir entry and links that are not actual files/dirs (e.g. sorting/filter controls).
|
||||
if (name_view.empty() || href_view.empty() || name_view == "/" || href_view.starts_with('?') || href_view.starts_with('#')) {
|
||||
if (name.empty() || href.empty() || name == "/" || href.starts_with('?') || href.starts_with('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip parent directory entry and external links.
|
||||
if (href_view == ".." || name_view == ".." || href_view.starts_with("../") || name_view.starts_with("../") || href_view.find("://") != std::string::npos) {
|
||||
if (href == ".." || name == ".." || href.starts_with("../") || name.starts_with("../") || href.find("://") != std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string name{name_view};
|
||||
const std::string href{href_view};
|
||||
|
||||
const auto is_dir = name.ends_with('/');
|
||||
if (is_dir) {
|
||||
name.pop_back(); // remove the trailing '/'
|
||||
@@ -248,38 +150,38 @@ bool http_dirlist(Device& client, const std::string& path, DirEntries& out) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool http_stat(Device& client, const std::string& path, struct stat* st, bool is_dir) {
|
||||
bool Device::http_stat(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);
|
||||
const auto url = build_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);
|
||||
curl_set_common_options(this->curl, url);
|
||||
curl_easy_setopt(this->curl, CURLOPT_NOBODY, 1L);
|
||||
curl_easy_setopt(this->curl, CURLOPT_FILETIME, 1L);
|
||||
|
||||
const auto res = curl_easy_perform(client.curl);
|
||||
const auto res = curl_easy_perform(this->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_easy_getinfo(this->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_easy_getinfo(this->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);
|
||||
curl_easy_getinfo(this->curl, CURLINFO_FILETIME_T, &file_time);
|
||||
|
||||
const char* content_type{};
|
||||
CURL_EASY_GETINFO_LOG(client.curl, CURLINFO_CONTENT_TYPE, &content_type);
|
||||
curl_easy_getinfo(this->curl, CURLINFO_CONTENT_TYPE, &content_type);
|
||||
|
||||
const char* effective_url{};
|
||||
CURL_EASY_GETINFO_LOG(client.curl, CURLINFO_EFFECTIVE_URL, &effective_url);
|
||||
curl_easy_getinfo(this->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);
|
||||
log_write("[WEBDAV] Unexpected HTTP response code: %ld\n", response_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -308,113 +210,80 @@ bool http_stat(Device& client, const std::string& path, struct stat* st, bool is
|
||||
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);
|
||||
bool Device::Mount() {
|
||||
if (mounted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
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));
|
||||
if (!MountCurlDevice::Mount()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
// todo: query server with OPTIONS to see if it supports range requests.
|
||||
// todo: see ftp for example.
|
||||
|
||||
return mounted = 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;
|
||||
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));
|
||||
SCOPED_MUTEX(&device->mutex);
|
||||
|
||||
// todo: add this check to all devoptabs.
|
||||
if ((flags & O_ACCMODE) != O_RDONLY) {
|
||||
log_write("[HTTP] Only read-only mode is supported\n");
|
||||
return set_errno(r, EINVAL);
|
||||
}
|
||||
|
||||
char path[PATH_MAX]{};
|
||||
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)) {
|
||||
if (!http_stat(path, &st, false)) {
|
||||
log_write("[HTTP] http_stat() failed for file: %s\n", path);
|
||||
return set_errno(r, ENOENT);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (st.st_mode & S_IFDIR) {
|
||||
log_write("[HTTP] Attempted to open a directory as a file: %s\n", path);
|
||||
return set_errno(r, EISDIR);
|
||||
return -EISDIR;
|
||||
}
|
||||
|
||||
file->client = device;
|
||||
file->entry = new FileEntry{path, st};
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_close(struct _reent *r, void *fd) {
|
||||
int Device::devoptab_close(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
SCOPED_MUTEX(&file->client->mutex);
|
||||
|
||||
delete file->push_pull_thread_data;
|
||||
delete file->entry;
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t devoptab_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);
|
||||
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);
|
||||
if (file->off != file->last_off) {
|
||||
log_write("[HTTP] File offset changed from %zu to %zu, resetting download thread\n", file->last_off, file->off);
|
||||
file->last_off = file->off;
|
||||
delete file->push_pull_thread_data;
|
||||
file->push_pull_thread_data = nullptr;
|
||||
}
|
||||
|
||||
file->off += len;
|
||||
return len;
|
||||
if (!file->push_pull_thread_data) {
|
||||
log_write("[HTTP] Creating download thread data for file: %s\n", file->entry->path.c_str());
|
||||
file->push_pull_thread_data = CreatePushData(this->transfer_curl, build_url(file->entry->path, false), file->off);
|
||||
if (!file->push_pull_thread_data) {
|
||||
log_write("[HTTP] Failed to create download thread data for file: %s\n", file->entry->path.c_str());
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||
const auto ret = file->push_pull_thread_data->PullData(ptr, len);
|
||||
|
||||
file->off += ret;
|
||||
file->last_off = file->off;
|
||||
return ret;
|
||||
}
|
||||
|
||||
off_t Device::devoptab_seek(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;
|
||||
@@ -422,63 +291,43 @@ off_t devoptab_seek(struct _reent *r, void *fd, off_t pos, int dir) {
|
||||
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) {
|
||||
int Device::devoptab_fstat(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;
|
||||
return 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[PATH_MAX];
|
||||
if (!common::fix_path(_path, path)) {
|
||||
set_errno(r, ENOENT);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!mount_http(*device)) {
|
||||
set_errno(r, EIO);
|
||||
return NULL;
|
||||
}
|
||||
int Device::devoptab_diropen(void* fd, const char *path) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
log_write("[HTTP] Opening directory: %s\n", path);
|
||||
auto entries = new DirEntries();
|
||||
if (!http_dirlist(*device, path, *entries)) {
|
||||
if (!http_dirlist(path, *entries)) {
|
||||
delete entries;
|
||||
set_errno(r, ENOENT);
|
||||
return NULL;
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
dir->client = device;
|
||||
log_write("[HTTP] Opened directory: %s with %zu entries\n", path, entries->size());
|
||||
dir->entries = entries;
|
||||
r->_errno = 0;
|
||||
return dirState;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_dirreset(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
SCOPED_MUTEX(&dir->client->mutex);
|
||||
int Device::devoptab_dirreset(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
dir->index = 0;
|
||||
return r->_errno = 0;
|
||||
return 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);
|
||||
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
if (dir->index >= dir->entries->size()) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
auto& entry = (*dir->entries)[dir->index];
|
||||
@@ -492,188 +341,34 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
|
||||
std::strcpy(filename, entry.name.c_str());
|
||||
|
||||
dir->index++;
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
SCOPED_MUTEX(&dir->client->mutex);
|
||||
int Device::devoptab_dirclose(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
delete dir->entries;
|
||||
std::memset(dir, 0, sizeof(*dir));
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_lstat(struct _reent *r, const char *_path, struct stat *st) {
|
||||
auto device = (Device*)r->deviceData;
|
||||
SCOPED_MUTEX(&device->mutex);
|
||||
|
||||
char path[PATH_MAX];
|
||||
if (!common::fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
int Device::devoptab_lstat(const char *path, struct stat *st) {
|
||||
if (!http_stat(path, st, false) && !http_stat(path, st, true)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (!mount_http(*device)) {
|
||||
return set_errno(r, EIO);
|
||||
return 0;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (!std::strcmp(Key, "url")) {
|
||||
e->back().url = 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, "port")) {
|
||||
// todo: idk what the default should be.
|
||||
e->back().port = ini_parse_getl(Value, 8000);
|
||||
} else if (!std::strcmp(Key, "timeout")) {
|
||||
e->back().timeout = ini_parse_getl(Value, e->back().timeout);
|
||||
} else if (!std::strcmp(Key, "no_stat_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 {
|
||||
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), "[HTTP] %s", config.name.c_str());
|
||||
std::snprintf(entry->mount, sizeof(entry->mount), "[HTTP] %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) {
|
||||
u32 flags = 0;
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_ReadOnly;
|
||||
if (entry->device.config.no_stat_file) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatFile;
|
||||
}
|
||||
if (entry->device.config.no_stat_dir) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatDir;
|
||||
}
|
||||
|
||||
out.emplace_back(entry->mount, entry->name, flags);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
return common::MountNetworkDevice([](const common::MountConfig& config) {
|
||||
return std::make_unique<Device>(config);
|
||||
},
|
||||
sizeof(File), sizeof(Dir),
|
||||
"/config/sphaira/http.ini",
|
||||
"HTTP"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
|
||||
@@ -15,159 +15,179 @@
|
||||
namespace sphaira::devoptab {
|
||||
namespace {
|
||||
|
||||
constexpr int DEFAULT_NFS_UID = 0;
|
||||
constexpr int DEFAULT_NFS_GID = 0;
|
||||
constexpr int DEFAULT_NFS_VERSION = 3;
|
||||
constexpr int DEFAULT_NFS_TIMEOUT = 3000; // 3 seconds.
|
||||
struct Device final : common::MountDevice {
|
||||
Device(const common::MountConfig& cfg) : MountDevice{cfg} {}
|
||||
~Device();
|
||||
|
||||
struct NfsMountConfig {
|
||||
std::string name{};
|
||||
std::string url{};
|
||||
int uid{DEFAULT_NFS_UID};
|
||||
int gid{DEFAULT_NFS_GID};
|
||||
int version{DEFAULT_NFS_VERSION};
|
||||
int timeout{DEFAULT_NFS_TIMEOUT};
|
||||
bool read_only{};
|
||||
bool no_stat_file{false};
|
||||
bool no_stat_dir{true};
|
||||
};
|
||||
using NfsMountConfigs = std::vector<NfsMountConfig>;
|
||||
private:
|
||||
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;
|
||||
ssize_t devoptab_write(void *fd, const 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_unlink(const char *path) override;
|
||||
int devoptab_rename(const char *oldName, const char *newName) override;
|
||||
int devoptab_mkdir(const char *path, int mode) override;
|
||||
int devoptab_rmdir(const char *path) override;
|
||||
int devoptab_diropen(void* fd, const char *path) override;
|
||||
int devoptab_dirreset(void* fd) override;
|
||||
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
|
||||
int devoptab_dirclose(void* fd) override;
|
||||
int devoptab_lstat(const char *path, struct stat *st) override;
|
||||
int devoptab_ftruncate(void *fd, off_t len) override;
|
||||
int devoptab_statvfs(const char *path, struct statvfs *buf) override;
|
||||
int devoptab_fsync(void *fd) override;
|
||||
int devoptab_utimes(const char *path, const struct timeval times[2]) override;
|
||||
|
||||
struct Device {
|
||||
private:
|
||||
nfs_context* nfs{};
|
||||
nfs_url* url{};
|
||||
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) {
|
||||
Device::~Device() {
|
||||
if (nfs) {
|
||||
if (mounted) {
|
||||
nfs_umount(nfs);
|
||||
}
|
||||
|
||||
nfs_destroy_context(nfs);
|
||||
}
|
||||
}
|
||||
|
||||
bool Device::Mount() {
|
||||
if (mounted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!device.nfs) {
|
||||
device.nfs = nfs_init_context();
|
||||
if (!device.nfs) {
|
||||
log_write("[NFS] Mounting %s\n", this->config.url.c_str());
|
||||
|
||||
if (!nfs) {
|
||||
nfs = nfs_init_context();
|
||||
if (!nfs) {
|
||||
log_write("[NFS] nfs_init_context() failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
nfs_set_uid(device.nfs, device.config.uid);
|
||||
nfs_set_gid(device.nfs, device.config.gid);
|
||||
nfs_set_version(device.nfs, device.config.version);
|
||||
nfs_set_timeout(device.nfs, device.config.timeout);
|
||||
nfs_set_readonly(device.nfs, device.config.read_only);
|
||||
// nfs_set_mountport(device.nfs, device.url->port);
|
||||
const auto uid = this->config.extra.find("uid");
|
||||
if (uid != this->config.extra.end()) {
|
||||
const auto uid_val = ini_parse_getl(uid->second.c_str(), -1);
|
||||
if (uid_val < 0) {
|
||||
log_write("[NFS] Invalid uid value: %s\n", uid->second.c_str());
|
||||
} else {
|
||||
log_write("[NFS] Setting uid: %ld\n", uid_val);
|
||||
nfs_set_uid(nfs, uid_val);
|
||||
}
|
||||
}
|
||||
|
||||
const auto gid = this->config.extra.find("gid");
|
||||
if (gid != this->config.extra.end()) {
|
||||
const auto gid_val = ini_parse_getl(gid->second.c_str(), -1);
|
||||
if (gid_val < 0) {
|
||||
log_write("[NFS] Invalid gid value: %s\n", gid->second.c_str());
|
||||
} else {
|
||||
log_write("[NFS] Setting gid: %ld\n", gid_val);
|
||||
nfs_set_gid(nfs, gid_val);
|
||||
}
|
||||
}
|
||||
|
||||
const auto version = this->config.extra.find("version");
|
||||
if (version != this->config.extra.end()) {
|
||||
const auto version_val = ini_parse_getl(version->second.c_str(), -1);
|
||||
if (version_val != 3 && version_val != 4) {
|
||||
log_write("[NFS] Invalid version value: %s\n", version->second.c_str());
|
||||
} else {
|
||||
log_write("[NFS] Setting version: %ld\n", version_val);
|
||||
nfs_set_version(nfs, version_val);
|
||||
}
|
||||
}
|
||||
|
||||
nfs_set_timeout(nfs, this->config.timeout);
|
||||
nfs_set_readonly(nfs, this->config.read_only);
|
||||
// nfs_set_mountport(nfs, url->port);
|
||||
}
|
||||
|
||||
// fix the url if needed.
|
||||
if (!device.config.url.starts_with("nfs://")) {
|
||||
log_write("[NFS] Prepending nfs:// to url: %s\n", device.config.url.c_str());
|
||||
device.config.url = "nfs://" + device.config.url;
|
||||
auto url = this->config.url;
|
||||
if (!url.starts_with("nfs://")) {
|
||||
log_write("[NFS] Prepending nfs:// to url: %s\n", url.c_str());
|
||||
url = "nfs://" + url;
|
||||
}
|
||||
|
||||
if (!device.url) {
|
||||
// todo: check all parse options.
|
||||
device.url = nfs_parse_url_dir(device.nfs, device.config.url.c_str());
|
||||
if (!device.url) {
|
||||
log_write("[NFS] nfs_parse_url() failed for url: %s\n", device.config.url.c_str());
|
||||
auto nfs_url = nfs_parse_url_dir(nfs, url.c_str());
|
||||
if (!nfs_url) {
|
||||
log_write("[NFS] nfs_parse_url() failed for url: %s\n", url.c_str());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
ON_SCOPE_EXIT(nfs_destroy_url(nfs_url));
|
||||
|
||||
const auto ret = nfs_mount(device.nfs, device.url->server, device.url->path);
|
||||
const auto ret = nfs_mount(nfs, nfs_url->server, nfs_url->path);
|
||||
if (ret) {
|
||||
log_write("[NFS] nfs_mount() failed: %s errno: %s\n", nfs_get_error(device.nfs), std::strerror(-ret));
|
||||
log_write("[NFS] nfs_mount() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
device.mounted = true;
|
||||
return true;
|
||||
log_write("[NFS] Mounted %s\n", this->config.url.c_str());
|
||||
return mounted = 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);
|
||||
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));
|
||||
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[PATH_MAX]{};
|
||||
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);
|
||||
const auto ret = nfs_open(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);
|
||||
log_write("[NFS] nfs_open() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
file->device = device;
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_close(struct _reent *r, void *fd) {
|
||||
int Device::devoptab_close(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
SCOPED_MUTEX(&file->device->mutex);
|
||||
|
||||
if (file->fd) {
|
||||
nfs_close(file->device->nfs, file->fd);
|
||||
if (file && file->fd) {
|
||||
nfs_close(nfs, file->fd);
|
||||
file->fd = nullptr;
|
||||
}
|
||||
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t devoptab_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);
|
||||
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);
|
||||
const auto ret = nfs_read(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);
|
||||
log_write("[NFS] nfs_read() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
#else
|
||||
// work around for bug upsteam.
|
||||
const auto max_read = nfs_get_readmax(file->device->nfs);
|
||||
const auto max_read = nfs_get_readmax(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);
|
||||
const auto ret = nfs_read(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);
|
||||
log_write("[NFS] nfs_read() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptr += ret;
|
||||
@@ -182,21 +202,20 @@ ssize_t devoptab_read(struct _reent *r, void *fd, char *ptr, size_t len) {
|
||||
#endif
|
||||
}
|
||||
|
||||
ssize_t devoptab_write(struct _reent *r, void *fd, const char *ptr, size_t len) {
|
||||
ssize_t Device::devoptab_write(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;
|
||||
const auto max_write = nfs_get_writemax(nfs);
|
||||
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);
|
||||
const auto ret = nfs_write(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);
|
||||
log_write("[NFS] nfs_write() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
ptr += ret;
|
||||
@@ -210,171 +229,103 @@ ssize_t devoptab_write(struct _reent *r, void *fd, const char *ptr, size_t len)
|
||||
return written;
|
||||
}
|
||||
|
||||
off_t devoptab_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);
|
||||
SCOPED_MUTEX(&file->device->mutex);
|
||||
|
||||
u64 current_off;
|
||||
const auto ret = nfs_lseek(file->device->nfs, file->fd, pos, dir, ¤t_off);
|
||||
u64 current_offset = 0;
|
||||
const auto ret = nfs_lseek(nfs, file->fd, pos, dir, ¤t_offset);
|
||||
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);
|
||||
log_write("[NFS] nfs_lseek() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
r->_errno = 0;
|
||||
return current_off;
|
||||
return current_offset;
|
||||
}
|
||||
|
||||
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||
int Device::devoptab_fstat(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[PATH_MAX]{};
|
||||
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[PATH_MAX]{};
|
||||
if (!common::fix_path(_oldName, oldName)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
char newName[PATH_MAX]{};
|
||||
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[PATH_MAX]{};
|
||||
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);
|
||||
const auto ret = nfs_fstat(nfs, file->fd, st);
|
||||
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);
|
||||
log_write("[NFS] nfs_fstat() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_rmdir(struct _reent *r, const char *_path) {
|
||||
auto device = static_cast<Device*>(r->deviceData);
|
||||
SCOPED_MUTEX(&device->mutex);
|
||||
|
||||
char path[PATH_MAX]{};
|
||||
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);
|
||||
int Device::devoptab_unlink(const char *path) {
|
||||
const auto ret = nfs_unlink(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);
|
||||
log_write("[NFS] nfs_unlink() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 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[PATH_MAX]{};
|
||||
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);
|
||||
int Device::devoptab_rename(const char *oldName, const char *newName) {
|
||||
const auto ret = nfs_rename(nfs, oldName, newName);
|
||||
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;
|
||||
log_write("[NFS] nfs_rename() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
dir->device = device;
|
||||
return dirState;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 Device::devoptab_mkdir(const char *path, int mode) {
|
||||
const auto ret = nfs_mkdir(nfs, path);
|
||||
if (ret) {
|
||||
log_write("[NFS] nfs_mkdir() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto entry = nfs_readdir(dir->device->nfs, dir->dir);
|
||||
int Device::devoptab_rmdir(const char *path) {
|
||||
const auto ret = nfs_rmdir(nfs, path);
|
||||
if (ret) {
|
||||
log_write("[NFS] nfs_rmdir() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_diropen(void* fd, const char *path) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
const auto ret = nfs_opendir(nfs, path, &dir->dir);
|
||||
if (ret) {
|
||||
log_write("[NFS] nfs_opendir() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_dirreset(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
if (!dir->dir) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
nfs_rewinddir(nfs, dir->dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
if (!dir->dir) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
const auto entry = nfs_readdir(nfs, dir->dir);
|
||||
if (!entry) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
std::strncpy(filename, entry->name, NAME_MAX);
|
||||
@@ -394,297 +345,88 @@ int devoptab_dirnext(struct _reent *r, DIR_ITER *dirState, char *filename, struc
|
||||
filestat->st_blksize = entry->blksize;
|
||||
filestat->st_blocks = entry->blocks;
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
SCOPED_MUTEX(&dir->device->mutex);
|
||||
int Device::devoptab_dirclose(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
if (dir->dir) {
|
||||
nfs_closedir(dir->device->nfs, dir->dir);
|
||||
if (dir && dir->dir) {
|
||||
nfs_closedir(nfs, dir->dir);
|
||||
dir->dir = nullptr;
|
||||
}
|
||||
|
||||
std::memset(dir, 0, sizeof(*dir));
|
||||
return r->_errno = 0;
|
||||
return 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[PATH_MAX]{};
|
||||
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);
|
||||
int Device::devoptab_lstat(const char *path, struct stat *st) {
|
||||
const auto ret = nfs_stat(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);
|
||||
log_write("[NFS] nfs_stat() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_ftruncate(struct _reent *r, void *fd, off_t len) {
|
||||
int Device::devoptab_ftruncate(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);
|
||||
const auto ret = nfs_ftruncate(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);
|
||||
log_write("[NFS] nfs_ftruncate() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 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[PATH_MAX]{};
|
||||
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);
|
||||
int Device::devoptab_statvfs(const char *path, struct statvfs *buf) {
|
||||
const auto ret = nfs_statvfs(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);
|
||||
log_write("[NFS] nfs_statvfs() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_fsync(struct _reent *r, void *fd) {
|
||||
int Device::devoptab_fsync(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
SCOPED_MUTEX(&file->device->mutex);
|
||||
|
||||
const auto ret = nfs_fsync(file->device->nfs, file->fd);
|
||||
const auto ret = nfs_fsync(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);
|
||||
log_write("[NFS] nfs_fsync() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return 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[PATH_MAX]{};
|
||||
if (!common::fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
if (!mount_nfs(*device)) {
|
||||
return set_errno(r, EIO);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_utimes(const char *path, const struct timeval times[2]) {
|
||||
// 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);
|
||||
const auto ret = nfs_utimes(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);
|
||||
log_write("[NFS] nfs_utimes() failed: %s errno: %s\n", nfs_get_error(nfs), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 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);
|
||||
}
|
||||
|
||||
if (device.url) {
|
||||
nfs_destroy_url(device.url);
|
||||
}
|
||||
|
||||
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, "name")) {
|
||||
e->back().name = Value;
|
||||
} else if (!std::strcmp(Key, "uid")) {
|
||||
e->back().uid = ini_parse_getl(Value, e->back().uid);
|
||||
} else if (!std::strcmp(Key, "gid")) {
|
||||
e->back().gid = ini_parse_getl(Value, e->back().gid);
|
||||
} else if (!std::strcmp(Key, "version")) {
|
||||
e->back().version = ini_parse_getl(Value, e->back().version);
|
||||
} else if (!std::strcmp(Key, "timeout")) {
|
||||
e->back().timeout = ini_parse_getl(Value, e->back().timeout);
|
||||
} else if (!std::strcmp(Key, "read_only")) {
|
||||
e->back().read_only = ini_parse_getbool(Value, e->back().read_only);
|
||||
} else if (!std::strcmp(Key, "no_stat_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 {
|
||||
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), "[NFS] %s", config.name.c_str());
|
||||
std::snprintf(entry->mount, sizeof(entry->mount), "[NFS] %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) {
|
||||
u32 flags = 0;
|
||||
if (entry->device.config.read_only) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_ReadOnly;
|
||||
}
|
||||
if (entry->device.config.no_stat_file) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatFile;
|
||||
}
|
||||
if (entry->device.config.no_stat_dir) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatDir;
|
||||
}
|
||||
|
||||
out.emplace_back(entry->mount, entry->name, flags);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
return common::MountNetworkDevice([](const common::MountConfig& cfg) {
|
||||
return std::make_unique<Device>(cfg);
|
||||
},
|
||||
sizeof(File), sizeof(Dir),
|
||||
"/config/sphaira/nfs.ini",
|
||||
"NFS"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#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>
|
||||
@@ -16,91 +14,48 @@
|
||||
namespace sphaira::devoptab {
|
||||
namespace {
|
||||
|
||||
constexpr int DEFAULT_SMB2_TIMEOUT = 3000; // 3 seconds.
|
||||
struct Device final : common::MountDevice {
|
||||
Device(const common::MountConfig& cfg) : MountDevice{cfg} {}
|
||||
~Device();
|
||||
|
||||
struct Smb2MountConfig {
|
||||
std::string name{};
|
||||
std::string url{};
|
||||
std::string user{};
|
||||
std::string pass{};
|
||||
std::string domain{};
|
||||
std::string workstation{};
|
||||
int timeout{DEFAULT_SMB2_TIMEOUT};
|
||||
bool read_only{};
|
||||
bool no_stat_file{false};
|
||||
bool no_stat_dir{true};
|
||||
};
|
||||
using Smb2MountConfigs = std::vector<Smb2MountConfig>;
|
||||
private:
|
||||
bool fix_path(const char* str, char* out, bool strip_leading_slash = false) override {
|
||||
return common::fix_path(str, out, true);
|
||||
}
|
||||
|
||||
struct Device {
|
||||
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;
|
||||
ssize_t devoptab_write(void *fd, const 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_unlink(const char *path) override;
|
||||
int devoptab_rename(const char *oldName, const char *newName) override;
|
||||
int devoptab_mkdir(const char *path, int mode) override;
|
||||
int devoptab_rmdir(const char *path) override;
|
||||
int devoptab_diropen(void* fd, const char *path) override;
|
||||
int devoptab_dirreset(void* fd) override;
|
||||
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
|
||||
int devoptab_dirclose(void* fd) override;
|
||||
int devoptab_lstat(const char *path, struct stat *st) override;
|
||||
int devoptab_ftruncate(void *fd, off_t len) override;
|
||||
int devoptab_statvfs(const char *path, struct statvfs *buf) override;
|
||||
int devoptab_fsync(void *fd) override;
|
||||
|
||||
private:
|
||||
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.config.domain.empty()) {
|
||||
smb2_set_domain(device.smb2, device.config.domain.c_str());
|
||||
}
|
||||
|
||||
if (!device.config.workstation.empty()) {
|
||||
smb2_set_workstation(device.smb2, device.config.workstation.c_str());
|
||||
}
|
||||
|
||||
smb2_set_timeout(device.smb2, device.config.timeout);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -121,325 +76,256 @@ void fill_stat(struct stat* st, const smb2_stat_64* smb2_st) {
|
||||
st->st_ctime = smb2_st->smb2_ctime;
|
||||
}
|
||||
|
||||
bool fix_path(const char* str, char* out) {
|
||||
return common::fix_path(str, out, true);
|
||||
Device::~Device() {
|
||||
if (this->smb2) {
|
||||
if (this->mounted) {
|
||||
smb2_disconnect_share(this->smb2);
|
||||
}
|
||||
|
||||
int set_errno(struct _reent *r, int err) {
|
||||
r->_errno = err;
|
||||
return -1;
|
||||
smb2_destroy_context(this->smb2);
|
||||
}
|
||||
}
|
||||
|
||||
int devoptab_open(struct _reent *r, void *fileStruct, const char *_path, int flags, int mode) {
|
||||
auto device = static_cast<Device*>(r->deviceData);
|
||||
bool Device::Mount() {
|
||||
if (mounted) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this->smb2) {
|
||||
this->smb2 = smb2_init_context();
|
||||
if (!this->smb2) {
|
||||
log_write("[SMB2] smb2_init_context() failed\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
smb2_set_security_mode(this->smb2, SMB2_NEGOTIATE_SIGNING_ENABLED);
|
||||
|
||||
if (!this->config.user.empty()) {
|
||||
smb2_set_user(this->smb2, this->config.user.c_str());
|
||||
}
|
||||
|
||||
if (!this->config.pass.empty()) {
|
||||
smb2_set_password(this->smb2, this->config.pass.c_str());
|
||||
}
|
||||
|
||||
const auto domain = this->config.extra.find("domain");
|
||||
if (domain != this->config.extra.end()) {
|
||||
smb2_set_domain(this->smb2, domain->second.c_str());
|
||||
}
|
||||
|
||||
const auto workstation = this->config.extra.find("workstation");
|
||||
if (workstation != this->config.extra.end()) {
|
||||
smb2_set_workstation(this->smb2, workstation->second.c_str());
|
||||
}
|
||||
|
||||
smb2_set_timeout(this->smb2, this->config.timeout);
|
||||
}
|
||||
|
||||
auto smb2_url = smb2_parse_url(this->smb2, this->config.url.c_str());
|
||||
if (!smb2_url) {
|
||||
log_write("[SMB2] smb2_parse_url() failed: %s\n", smb2_get_error(this->smb2));
|
||||
return false;
|
||||
}
|
||||
ON_SCOPE_EXIT(smb2_destroy_url(smb2_url));
|
||||
|
||||
const auto ret = smb2_connect_share(this->smb2, smb2_url->server, smb2_url->share, smb2_url->user);
|
||||
if (ret) {
|
||||
log_write("[SMB2] smb2_connect_share() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return false;
|
||||
}
|
||||
|
||||
this->mounted = true;
|
||||
return 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));
|
||||
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[PATH_MAX]{};
|
||||
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);
|
||||
file->fd = smb2_open(this->smb2, path, flags);
|
||||
if (!file->fd) {
|
||||
log_write("[SMB2] smb2_open() failed: %s\n", smb2_get_error(device->smb2));
|
||||
return set_errno(r, EIO);
|
||||
log_write("[SMB2] smb2_open() failed: %s\n", smb2_get_error(this->smb2));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
file->device = device;
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_close(struct _reent *r, void *fd) {
|
||||
int Device::devoptab_close(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
SCOPED_MUTEX(&file->device->mutex);
|
||||
|
||||
if (file->fd) {
|
||||
smb2_close(file->device->smb2, file->fd);
|
||||
smb2_close(this->smb2, file->fd);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::memset(file, 0, sizeof(*file));
|
||||
return r->_errno = 0;
|
||||
}
|
||||
|
||||
ssize_t devoptab_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);
|
||||
SCOPED_MUTEX(&file->device->mutex);
|
||||
|
||||
const auto ret = smb2_read(file->device->smb2, file->fd, reinterpret_cast<uint8_t*>(ptr), len);
|
||||
const auto ret = smb2_read(this->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);
|
||||
log_write("[SMB2] smb2_read() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t devoptab_write(struct _reent *r, void *fd, const char *ptr, size_t len) {
|
||||
ssize_t Device::devoptab_write(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);
|
||||
const auto ret = smb2_write(this->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);
|
||||
log_write("[SMB2] smb2_write() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
off_t devoptab_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);
|
||||
SCOPED_MUTEX(&file->device->mutex);
|
||||
|
||||
const auto ret = smb2_lseek(file->device->smb2, file->fd, pos, dir, nullptr);
|
||||
u64 current_offset = 0;
|
||||
const auto ret = smb2_lseek(this->smb2, file->fd, pos, dir, ¤t_offset);
|
||||
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;
|
||||
log_write("[SMB2] smb2_lseek() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int devoptab_fstat(struct _reent *r, void *fd, struct stat *st) {
|
||||
return current_offset;
|
||||
}
|
||||
|
||||
int Device::devoptab_fstat(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);
|
||||
const auto ret = smb2_fstat(this->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);
|
||||
log_write("[SMB2] smb2_fstat() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
fill_stat(st, &smb2_st);
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_unlink(struct _reent *r, const char *_path) {
|
||||
auto device = static_cast<Device*>(r->deviceData);
|
||||
SCOPED_MUTEX(&device->mutex);
|
||||
|
||||
char path[PATH_MAX]{};
|
||||
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[PATH_MAX]{};
|
||||
if (!fix_path(_oldName, oldName)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
char newName[PATH_MAX]{};
|
||||
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[PATH_MAX]{};
|
||||
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);
|
||||
int Device::devoptab_unlink(const char *path) {
|
||||
const auto ret = smb2_unlink(this->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);
|
||||
log_write("[SMB2] smb2_unlink() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_rmdir(struct _reent *r, const char *_path) {
|
||||
auto device = static_cast<Device*>(r->deviceData);
|
||||
SCOPED_MUTEX(&device->mutex);
|
||||
|
||||
char path[PATH_MAX]{};
|
||||
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);
|
||||
int Device::devoptab_rename(const char *oldName, const char *newName) {
|
||||
const auto ret = smb2_rename(this->smb2, oldName, newName);
|
||||
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);
|
||||
log_write("[SMB2] smb2_rename() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 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[PATH_MAX]{};
|
||||
if (!fix_path(_path, path)) {
|
||||
set_errno(r, ENOENT);
|
||||
return nullptr;
|
||||
int Device::devoptab_mkdir(const char *path, int mode) {
|
||||
const auto ret = smb2_mkdir(this->smb2, path);
|
||||
if (ret) {
|
||||
log_write("[SMB2] smb2_mkdir() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!mount_smb2(*device)) {
|
||||
set_errno(r, EIO);
|
||||
return nullptr;
|
||||
return 0;
|
||||
}
|
||||
|
||||
dir->dir = smb2_opendir(device->smb2, path);
|
||||
int Device::devoptab_rmdir(const char *path) {
|
||||
const auto ret = smb2_rmdir(this->smb2, path);
|
||||
if (ret) {
|
||||
log_write("[SMB2] smb2_rmdir() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_diropen(void* fd, const char *path) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
dir->dir = smb2_opendir(this->smb2, path);
|
||||
if (!dir->dir) {
|
||||
log_write("[SMB2] smb2_opendir() failed: %s\n", smb2_get_error(device->smb2));
|
||||
set_errno(r, EIO);
|
||||
return nullptr;
|
||||
log_write("[SMB2] smb2_opendir() failed: %s\n", smb2_get_error(this->smb2));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
dir->device = device;
|
||||
return dirState;
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 Device::devoptab_dirreset(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
if (!dir->dir) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
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);
|
||||
smb2_rewinddir(this->smb2, dir->dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto entry = smb2_readdir(dir->device->smb2, dir->dir);
|
||||
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
if (!dir->dir) {
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
const auto entry = smb2_readdir(this->smb2, dir->dir);
|
||||
if (!entry) {
|
||||
return set_errno(r, ENOENT);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
std::strncpy(filename, entry->name, NAME_MAX);
|
||||
filename[NAME_MAX - 1] = '\0';
|
||||
fill_stat(filestat, &entry->st);
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_dirclose(struct _reent *r, DIR_ITER *dirState) {
|
||||
auto dir = static_cast<Dir*>(dirState->dirStruct);
|
||||
SCOPED_MUTEX(&dir->device->mutex);
|
||||
int Device::devoptab_dirclose(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
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[PATH_MAX]{};
|
||||
if (!fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
if (!mount_smb2(*device)) {
|
||||
return set_errno(r, EIO);
|
||||
smb2_closedir(this->smb2, dir->dir);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_lstat(const char *path, struct stat *st) {
|
||||
smb2_stat_64 smb2_st{};
|
||||
const auto ret = smb2_stat(device->smb2, path, &smb2_st);
|
||||
const auto ret = smb2_stat(this->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);
|
||||
log_write("[SMB2] smb2_stat() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
fill_stat(st, &smb2_st);
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_ftruncate(struct _reent *r, void *fd, off_t len) {
|
||||
int Device::devoptab_ftruncate(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);
|
||||
const auto ret = smb2_ftruncate(this->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);
|
||||
log_write("[SMB2] smb2_ftruncate() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return 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[PATH_MAX]{};
|
||||
if (!fix_path(_path, path)) {
|
||||
return set_errno(r, ENOENT);
|
||||
}
|
||||
|
||||
if (!mount_smb2(*device)) {
|
||||
return set_errno(r, EIO);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_statvfs(const char *path, struct statvfs *buf) {
|
||||
struct smb2_statvfs smb2_st{};
|
||||
const auto ret = smb2_statvfs(device->smb2, path, &smb2_st);
|
||||
const auto ret = smb2_statvfs(this->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);
|
||||
log_write("[SMB2] smb2_statvfs() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
buf->f_bsize = smb2_st.f_bsize;
|
||||
@@ -454,198 +340,31 @@ int devoptab_statvfs(struct _reent *r, const char *_path, struct statvfs *buf) {
|
||||
buf->f_flag = smb2_st.f_flag;
|
||||
buf->f_namemax = smb2_st.f_namemax;
|
||||
|
||||
return r->_errno = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int devoptab_fsync(struct _reent *r, void *fd) {
|
||||
int Device::devoptab_fsync(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
SCOPED_MUTEX(&file->device->mutex);
|
||||
|
||||
const auto ret = smb2_fsync(file->device->smb2, file->fd);
|
||||
const auto ret = smb2_fsync(this->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);
|
||||
log_write("[SMB2] smb2_fsync() failed: %s errno: %s\n", smb2_get_error(this->smb2), std::strerror(-ret));
|
||||
return ret;
|
||||
}
|
||||
|
||||
return r->_errno = 0;
|
||||
return 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, "domain")) {
|
||||
e->back().domain = Value;
|
||||
} else if (!std::strcmp(Key, "workstation")) {
|
||||
e->back().workstation = Value;
|
||||
} else if (!std::strcmp(Key, "timeout")) {
|
||||
e->back().timeout = ini_parse_getl(Value, e->back().timeout);
|
||||
} else if (!std::strcmp(Key, "read_only")) {
|
||||
e->back().read_only = ini_parse_getbool(Value, e->back().read_only);
|
||||
} else if (!std::strcmp(Key, "no_stat_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 {
|
||||
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), "[SMB] %s", config.name.c_str());
|
||||
std::snprintf(entry->mount, sizeof(entry->mount), "[SMB] %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) {
|
||||
u32 flags = 0;
|
||||
if (entry->device.config.read_only) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_ReadOnly;
|
||||
}
|
||||
if (entry->device.config.no_stat_file) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatFile;
|
||||
}
|
||||
if (entry->device.config.no_stat_dir) {
|
||||
flags |= location::FsEntryFlag::FsEntryFlag_NoStatDir;
|
||||
}
|
||||
|
||||
out.emplace_back(entry->mount, entry->name, flags);
|
||||
}
|
||||
}
|
||||
|
||||
R_SUCCEED();
|
||||
return common::MountNetworkDevice([](const common::MountConfig& cfg) {
|
||||
return std::make_unique<Device>(cfg);
|
||||
},
|
||||
sizeof(File), sizeof(Dir),
|
||||
"/config/sphaira/smb.ini",
|
||||
"SMB"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
|
||||
581
sphaira/source/utils/devoptab_webdav.cpp
Normal file
581
sphaira/source/utils/devoptab_webdav.cpp
Normal file
@@ -0,0 +1,581 @@
|
||||
#include "utils/devoptab_common.hpp"
|
||||
#include "utils/profile.hpp"
|
||||
|
||||
#include "log.hpp"
|
||||
#include "defines.hpp"
|
||||
#include <fcntl.h>
|
||||
#include <curl/curl.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <cstring>
|
||||
#include <optional>
|
||||
#include <sys/stat.h>
|
||||
|
||||
// todo: try to reduce binary size by using a smaller xml parser.
|
||||
#include <pugixml.hpp>
|
||||
|
||||
namespace sphaira::devoptab {
|
||||
namespace {
|
||||
|
||||
constexpr const char* XPATH_RESPONSE = "//*[local-name()='response']";
|
||||
constexpr const char* XPATH_HREF = ".//*[local-name()='href']";
|
||||
constexpr const char* XPATH_PROPSTAT_PROP = ".//*[local-name()='propstat']/*[local-name()='prop']";
|
||||
constexpr const char* XPATH_PROP = ".//*[local-name()='prop']";
|
||||
constexpr const char* XPATH_RESOURCETYPE = ".//*[local-name()='resourcetype']";
|
||||
constexpr const char* XPATH_COLLECTION = ".//*[local-name()='collection']";
|
||||
|
||||
struct DirEntry {
|
||||
std::string name{};
|
||||
bool is_dir{};
|
||||
};
|
||||
using DirEntries = std::vector<DirEntry>;
|
||||
|
||||
struct FileEntry {
|
||||
std::string path{};
|
||||
struct stat st{};
|
||||
};
|
||||
|
||||
struct File {
|
||||
FileEntry* entry;
|
||||
common::PushPullThreadData* push_pull_thread_data;
|
||||
size_t off;
|
||||
size_t last_off;
|
||||
bool write_mode;
|
||||
};
|
||||
|
||||
struct Dir {
|
||||
DirEntries* entries;
|
||||
size_t index;
|
||||
};
|
||||
|
||||
struct Device final : common::MountCurlDevice {
|
||||
using MountCurlDevice::MountCurlDevice;
|
||||
|
||||
private:
|
||||
int devoptab_open(void *fileStruct, const char *path, int flags, int mode) override;
|
||||
int devoptab_close(void *fd) override;
|
||||
ssize_t devoptab_read(void *fd, char *ptr, size_t len) override;
|
||||
ssize_t devoptab_write(void *fd, const 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_unlink(const char *path) override;
|
||||
int devoptab_rename(const char *oldName, const char *newName) override;
|
||||
int devoptab_mkdir(const char *path, int mode) override;
|
||||
int devoptab_rmdir(const char *path) override;
|
||||
int devoptab_diropen(void* fd, const char *path) override;
|
||||
int devoptab_dirreset(void* fd) override;
|
||||
int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) override;
|
||||
int devoptab_dirclose(void* fd) override;
|
||||
int devoptab_lstat(const char *path, struct stat *st) override;
|
||||
int devoptab_ftruncate(void *fd, off_t len) override;
|
||||
int devoptab_fsync(void *fd) override;
|
||||
|
||||
std::pair<bool, long> webdav_custom_command(const std::string& path, const std::string& cmd, std::string_view postfields, std::span<const std::string> headers, bool is_dir, std::vector<char>* response_data = nullptr);
|
||||
bool webdav_dirlist(const std::string& path, DirEntries& out);
|
||||
bool webdav_stat(const std::string& path, struct stat* st, bool is_dir);
|
||||
bool webdav_remove_file_folder(const std::string& path, bool is_dir);
|
||||
bool webdav_unlink(const std::string& path);
|
||||
bool webdav_rename(const std::string& old_path, const std::string& new_path, bool is_dir);
|
||||
bool webdav_mkdir(const std::string& path);
|
||||
bool webdav_rmdir(const std::string& path);
|
||||
};
|
||||
|
||||
size_t dummy_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata) {
|
||||
return size * nmemb;
|
||||
}
|
||||
|
||||
std::pair<bool, long> Device::webdav_custom_command(const std::string& path, const std::string& cmd, std::string_view postfields, std::span<const std::string> headers, bool is_dir, std::vector<char>* response_data) {
|
||||
const auto url = build_url(path, is_dir);
|
||||
|
||||
curl_slist* header_list{};
|
||||
ON_SCOPE_EXIT(curl_slist_free_all(header_list));
|
||||
|
||||
for (const auto& header : headers) {
|
||||
log_write("[WEBDAV] Header: %s\n", header.c_str());
|
||||
header_list = curl_slist_append(header_list, header.c_str());
|
||||
}
|
||||
|
||||
log_write("[WEBDAV] %s %s\n", cmd.c_str(), url.c_str());
|
||||
curl_set_common_options(this->curl, url);
|
||||
curl_easy_setopt(this->curl, CURLOPT_HTTPHEADER, header_list);
|
||||
curl_easy_setopt(this->curl, CURLOPT_CUSTOMREQUEST, cmd.c_str());
|
||||
if (!postfields.empty()) {
|
||||
log_write("[WEBDAV] Post fields: %.*s\n", (int)postfields.length(), postfields.data());
|
||||
curl_easy_setopt(this->curl, CURLOPT_POSTFIELDS, postfields.data());
|
||||
curl_easy_setopt(this->curl, CURLOPT_POSTFIELDSIZE, (long)postfields.length());
|
||||
}
|
||||
|
||||
if (response_data) {
|
||||
response_data->clear();
|
||||
curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, write_memory_callback);
|
||||
curl_easy_setopt(this->curl, CURLOPT_WRITEDATA, (void *)response_data);
|
||||
} else {
|
||||
curl_easy_setopt(this->curl, CURLOPT_WRITEFUNCTION, dummy_data_callback);
|
||||
}
|
||||
|
||||
const auto res = curl_easy_perform(this->curl);
|
||||
if (res != CURLE_OK) {
|
||||
log_write("[WEBDAV] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||
return {false, 0};
|
||||
}
|
||||
|
||||
long response_code = 0;
|
||||
curl_easy_getinfo(this->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
return {true, response_code};
|
||||
}
|
||||
|
||||
bool Device::webdav_dirlist(const std::string& path, DirEntries& out) {
|
||||
const std::string_view post_fields =
|
||||
"<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
|
||||
"<d:propfind xmlns:d=\"DAV:\">"
|
||||
"<d:prop>"
|
||||
// "<d:getcontentlength/>"
|
||||
"<d:resourcetype/>"
|
||||
"</d:prop>"
|
||||
"</d:propfind>";
|
||||
|
||||
const std::string custom_headers[] = {
|
||||
"Content-Type: application/xml; charset=utf-8",
|
||||
"Depth: 1"
|
||||
};
|
||||
|
||||
std::vector<char> chunk;
|
||||
const auto [success, response_code] = webdav_custom_command(path, "PROPFIND", post_fields, custom_headers, true, &chunk);
|
||||
if (!success || response_code != 207) {
|
||||
log_write("[WEBDAV] PROPFIND failed or returned HTTP error code: %ld\n", response_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
SCOPED_TIMESTAMP("webdav_dirlist parse");
|
||||
|
||||
pugi::xml_document doc;
|
||||
const auto result = doc.load_buffer_inplace(chunk.data(), chunk.size());
|
||||
if (!result) {
|
||||
log_write("[WEBDAV] Failed to parse XML: %s\n", result.description());
|
||||
return false;
|
||||
}
|
||||
|
||||
log_write("\n[WEBDAV] XML parsed successfully\n");
|
||||
|
||||
auto requested_path = url_decode(path);
|
||||
if (!requested_path.empty() && requested_path.back() == '/') {
|
||||
requested_path.pop_back();
|
||||
}
|
||||
|
||||
const auto responses = doc.select_nodes(XPATH_RESPONSE);
|
||||
|
||||
for (const auto& rnode : responses) {
|
||||
const auto response = rnode.node();
|
||||
if (!response) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto href_x = response.select_node(XPATH_HREF);
|
||||
if (!href_x) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto href = url_decode(href_x.node().text().as_string());
|
||||
if (href.empty() || href == requested_path || href == requested_path + '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// propstat/prop/resourcetype
|
||||
auto prop_x = response.select_node(XPATH_PROPSTAT_PROP);
|
||||
if (!prop_x) {
|
||||
// try direct prop if structure differs
|
||||
prop_x = response.select_node(XPATH_PROP);
|
||||
if (!prop_x) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const auto prop = prop_x.node();
|
||||
const auto rtype_x = prop.select_node(XPATH_RESOURCETYPE);
|
||||
bool is_dir = false;
|
||||
if (rtype_x && rtype_x.node().select_node(XPATH_COLLECTION)) {
|
||||
is_dir = true;
|
||||
}
|
||||
|
||||
auto name = href;
|
||||
if (!name.empty() && name.back() == '/') {
|
||||
name.pop_back();
|
||||
}
|
||||
|
||||
const auto pos = name.find_last_of('/');
|
||||
if (pos != std::string::npos) {
|
||||
name = name.substr(pos + 1);
|
||||
}
|
||||
|
||||
// skip root entry
|
||||
if (name.empty() || name == ".") {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.emplace_back(name, is_dir);
|
||||
}
|
||||
|
||||
log_write("[WEBDAV] Parsed %zu entries from directory listing\n", out.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// todo: use PROPFIND to get file size and time, although it is slower...
|
||||
bool Device::webdav_stat(const std::string& path, struct stat* st, bool is_dir) {
|
||||
std::memset(st, 0, sizeof(*st));
|
||||
const auto url = build_url(path, is_dir);
|
||||
|
||||
curl_set_common_options(this->curl, url);
|
||||
curl_easy_setopt(this->curl, CURLOPT_NOBODY, 1L);
|
||||
curl_easy_setopt(this->curl, CURLOPT_FILETIME, 1L);
|
||||
|
||||
const auto res = curl_easy_perform(this->curl);
|
||||
if (res != CURLE_OK) {
|
||||
log_write("[WEBDAV] curl_easy_perform() failed: %s\n", curl_easy_strerror(res));
|
||||
return false;
|
||||
}
|
||||
|
||||
long response_code = 0;
|
||||
curl_easy_getinfo(this->curl, CURLINFO_RESPONSE_CODE, &response_code);
|
||||
|
||||
curl_off_t file_size = 0;
|
||||
curl_easy_getinfo(this->curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &file_size);
|
||||
|
||||
curl_off_t file_time = 0;
|
||||
curl_easy_getinfo(this->curl, CURLINFO_FILETIME_T, &file_time);
|
||||
|
||||
const char* content_type{};
|
||||
curl_easy_getinfo(this->curl, CURLINFO_CONTENT_TYPE, &content_type);
|
||||
|
||||
const char* effective_url{};
|
||||
curl_easy_getinfo(this->curl, CURLINFO_EFFECTIVE_URL, &effective_url);
|
||||
|
||||
// handle error codes.
|
||||
if (response_code != 200 && response_code != 206) {
|
||||
log_write("[WEBDAV] 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 Device::webdav_remove_file_folder(const std::string& path, bool is_dir) {
|
||||
const auto [success, response_code] = webdav_custom_command(path, "DELETE", "", {}, is_dir);
|
||||
if (!success || (response_code != 200 && response_code != 204)) {
|
||||
log_write("[WEBDAV] DELETE command failed with response code: %ld\n", response_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Device::webdav_unlink(const std::string& path) {
|
||||
return webdav_remove_file_folder(path, false);
|
||||
}
|
||||
|
||||
bool Device::webdav_rename(const std::string& old_path, const std::string& new_path, bool is_dir) {
|
||||
log_write("[WEBDAV] Renaming %s to %s\n", old_path.c_str(), new_path.c_str());
|
||||
|
||||
const std::string custom_headers[] = {
|
||||
"Destination: " + build_url(new_path, is_dir),
|
||||
"Overwrite: T",
|
||||
};
|
||||
|
||||
const auto [success, response_code] = webdav_custom_command(old_path, "MOVE", "", custom_headers, is_dir);
|
||||
if (!success || (response_code != 200 && response_code != 201 && response_code != 204)) {
|
||||
log_write("[WEBDAV] MOVE command failed with response code: %ld\n", response_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Device::webdav_mkdir(const std::string& path) {
|
||||
const auto [success, response_code] = webdav_custom_command(path, "MKCOL", "", {}, true);
|
||||
if (!success || response_code != 201) {
|
||||
log_write("[WEBDAV] MKCOL command failed with response code: %ld\n", response_code);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Device::webdav_rmdir(const std::string& path) {
|
||||
return webdav_remove_file_folder(path, true);
|
||||
}
|
||||
|
||||
int Device::devoptab_open(void *fileStruct, const char *path, int flags, int mode) {
|
||||
auto file = static_cast<File*>(fileStruct);
|
||||
struct stat st{};
|
||||
|
||||
// append mode is not supported.
|
||||
if (flags & O_APPEND) {
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
if ((flags & O_ACCMODE) == O_RDONLY) {
|
||||
// ensure the file exists and get its size.
|
||||
if (!webdav_stat(path, &st, false)) {
|
||||
log_write("[WEBDAV] File not found: %s\n", path);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
if (st.st_mode & S_IFDIR) {
|
||||
log_write("[WEBDAV] Path is a directory, not a file: %s\n", path);
|
||||
return -EISDIR;
|
||||
}
|
||||
}
|
||||
|
||||
log_write("[WEBDAV] Opening file: %s\n", path);
|
||||
file->entry = new FileEntry{path, st};
|
||||
file->write_mode = (flags & (O_WRONLY | O_RDWR));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_close(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
log_write("[WEBDAV] Closing file: %s\n", file->entry->path.c_str());
|
||||
delete file->push_pull_thread_data;
|
||||
delete file->entry;
|
||||
return 0;
|
||||
}
|
||||
|
||||
ssize_t Device::devoptab_read(void *fd, char *ptr, size_t len) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
len = std::min(len, file->entry->st.st_size - file->off);
|
||||
|
||||
if (file->write_mode) {
|
||||
log_write("[WEBDAV] Attempt to read from a write-only file\n");
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
if (!len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (file->off != file->last_off) {
|
||||
log_write("[WEBDAV] File offset changed from %zu to %zu, resetting download thread\n", file->last_off, file->off);
|
||||
file->last_off = file->off;
|
||||
delete file->push_pull_thread_data;
|
||||
file->push_pull_thread_data = nullptr;
|
||||
}
|
||||
|
||||
if (!file->push_pull_thread_data) {
|
||||
log_write("[WEBDAV] Creating download thread data for file: %s\n", file->entry->path.c_str());
|
||||
file->push_pull_thread_data = CreatePushData(this->transfer_curl, build_url(file->entry->path, false), file->off);
|
||||
if (!file->push_pull_thread_data) {
|
||||
log_write("[WEBDAV] Failed to create download thread data for file: %s\n", file->entry->path.c_str());
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
const auto ret = file->push_pull_thread_data->PullData(ptr, len);
|
||||
file->off += ret;
|
||||
file->last_off = file->off;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
ssize_t Device::devoptab_write(void *fd, const char *ptr, size_t len) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
if (!file->write_mode) {
|
||||
log_write("[WEBDAV] Attempt to write to a read-only file\n");
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
if (!len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!file->push_pull_thread_data) {
|
||||
log_write("[WEBDAV] Creating upload thread data for file: %s\n", file->entry->path.c_str());
|
||||
file->push_pull_thread_data = CreatePullData(this->transfer_curl, build_url(file->entry->path, false));
|
||||
if (!file->push_pull_thread_data) {
|
||||
log_write("[WEBDAV] Failed to create upload thread data for file: %s\n", file->entry->path.c_str());
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
const auto ret = file->push_pull_thread_data->PushData(ptr, len);
|
||||
|
||||
file->off += ret;
|
||||
file->entry->st.st_size = std::max<off_t>(file->entry->st.st_size, file->off);
|
||||
return ret;
|
||||
}
|
||||
|
||||
off_t Device::devoptab_seek(void *fd, off_t pos, int dir) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
if (dir == SEEK_CUR) {
|
||||
pos += file->off;
|
||||
} else if (dir == SEEK_END) {
|
||||
pos = file->entry->st.st_size;
|
||||
}
|
||||
|
||||
// for now, random access writes are disabled.
|
||||
if (file->write_mode && pos != file->off) {
|
||||
log_write("[WEBDAV] Random access writes are not supported\n");
|
||||
return file->off;
|
||||
}
|
||||
|
||||
return file->off = std::clamp<u64>(pos, 0, file->entry->st.st_size);
|
||||
}
|
||||
|
||||
int Device::devoptab_fstat(void *fd, struct stat *st) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
std::memcpy(st, &file->entry->st, sizeof(*st));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_unlink(const char *path) {
|
||||
if (!webdav_unlink(path)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_rename(const char *oldName, const char *newName) {
|
||||
if (!webdav_rename(oldName, newName, false) && !webdav_rename(oldName, newName, true)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_mkdir(const char *path, int mode) {
|
||||
if (!webdav_mkdir(path)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_rmdir(const char *path) {
|
||||
if (!webdav_rmdir(path)) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_diropen(void* fd, const char *path) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
auto entries = new DirEntries();
|
||||
if (!webdav_dirlist(path, *entries)) {
|
||||
delete entries;
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
dir->entries = entries;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_dirreset(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
dir->index = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_dirnext(void* fd, char *filename, struct stat *filestat) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
if (dir->index >= dir->entries->size()) {
|
||||
return -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 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_dirclose(void* fd) {
|
||||
auto dir = static_cast<Dir*>(fd);
|
||||
|
||||
delete dir->entries;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_lstat(const char *path, struct stat *st) {
|
||||
if (!webdav_stat(path, st, false) && !webdav_stat(path, st, true)) {
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_ftruncate(void *fd, off_t len) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
if (!file->write_mode) {
|
||||
log_write("[WEBDAV] Attempt to truncate a read-only file\n");
|
||||
return EBADF;
|
||||
}
|
||||
|
||||
file->entry->st.st_size = len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Device::devoptab_fsync(void *fd) {
|
||||
auto file = static_cast<File*>(fd);
|
||||
|
||||
if (!file->write_mode) {
|
||||
log_write("[WEBDAV] Attempt to fsync a read-only file\n");
|
||||
return -EBADF;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Result MountWebdavAll() {
|
||||
return common::MountNetworkDevice([](const common::MountConfig& config) {
|
||||
return std::make_unique<Device>(config);
|
||||
},
|
||||
sizeof(File), sizeof(Dir),
|
||||
"/config/sphaira/webdav.ini",
|
||||
"WEBDAV"
|
||||
);
|
||||
}
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
Reference in New Issue
Block a user