use ftpsrv config and mountpoints, improve ftp performance by using its vfs.

This commit is contained in:
ITotalJustice
2024-12-30 21:09:32 +00:00
parent cdb38f27a7
commit cb7fb0e506
8 changed files with 124 additions and 296 deletions

View File

@@ -24,7 +24,7 @@ jobs:
- name: Configure CMake - name: Configure CMake
run: | run: |
cmake --preset ${{ matrix.preset }} cmake --preset ${{ matrix.preset }} -DUSE_VFS_GC=0
- name: Build - name: Build
run: cmake --build --preset ${{ matrix.preset }} --parallel 4 run: cmake --build --preset ${{ matrix.preset }} --parallel 4

View File

@@ -26,7 +26,8 @@ please include:
## ftp ## ftp
ftp can be enabled via the network menu and listens on port 5000, no username or password is required. ftp can be enabled via the network menu. It uses the same config as ftpsrv `/config/ftpsrv/config.ini`. [See here for the full list
of all configs available](https://github.com/ITotalJustice/ftpsrv/blob/master/assets/config.ini.template).
## mtp ## mtp

View File

@@ -86,7 +86,7 @@ set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(ftpsrv FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
GIT_TAG 8d5a14e GIT_TAG 1.2.1
) )
FetchContent_Declare(libhaze FetchContent_Declare(libhaze
@@ -114,12 +114,12 @@ FetchContent_Declare(yyjson
GIT_TAG 0.10.0 GIT_TAG 0.10.0
) )
FetchContent_Declare(minIni-sphaira FetchContent_Declare(minIni
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
GIT_TAG 63ec295 GIT_TAG 63ec295
) )
set(MININI_LIB_NAME minIni-sphaira) set(MININI_LIB_NAME minIni)
set(MININI_USE_STDIO ON) set(MININI_USE_STDIO ON)
set(MININI_USE_NX ON) set(MININI_USE_NX ON)
set(MININI_USE_FLOAT OFF) set(MININI_USE_FLOAT OFF)
@@ -149,18 +149,48 @@ set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
set(FTPSRV_LIB_BUILD TRUE) set(FTPSRV_LIB_BUILD TRUE)
set(FTPSRV_LIB_SOCK_UNISTD TRUE) set(FTPSRV_LIB_SOCK_UNISTD TRUE)
set(FTPSRV_LIB_VFS_CUSTOM ${CMAKE_CURRENT_SOURCE_DIR}/include/ftpsrv_helper.hpp) set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
set(FTPSRV_LIB_PATH_SIZE 0x301) set(FTPSRV_LIB_PATH_SIZE 0x301)
set(FTPSRV_LIB_SESSIONS 32) set(FTPSRV_LIB_SESSIONS 32)
set(FTPSRV_LIB_BUF_SIZE 1024*64) set(FTPSRV_LIB_BUF_SIZE 1024*64)
add_library(ftpsrv_helper
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_save.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
)
# workaround until a64 container has latest libnx release.
if (NOT DEFINED USE_VFS_GC)
set(USE_VFS_GC TRUE)
endif()
if (USE_VFS_GC)
target_sources(ftpsrv_helper PRIVATE
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_gc.c
)
endif()
set(FTPSRV_LIB_CUSTOM_DEFINES
USE_VFS_SAVE=$<BOOL:TRUE>
USE_VFS_STORAGE=$<BOOL:TRUE>
USE_VFS_GC=$<BOOL:${USE_VFS_GC}>
)
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv)
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
FetchContent_MakeAvailable( FetchContent_MakeAvailable(
ftpsrv ftpsrv
libhaze libhaze
libpulsar libpulsar
nanovg nanovg
stb stb
minIni-sphaira minIni
yyjson yyjson
) )
@@ -241,10 +271,10 @@ set_target_properties(sphaira PROPERTIES
) )
target_link_libraries(sphaira PRIVATE target_link_libraries(sphaira PRIVATE
ftpsrv ftpsrv_helper
libhaze libhaze
libpulsar libpulsar
minIni-sphaira minIni
nanovg nanovg
stb stb
yyjson yyjson

View File

@@ -1,33 +1,8 @@
#pragma once #pragma once
#include <switch.h>
struct FtpVfsFile {
FsFile fd;
s64 off;
s64 buf_off;
s64 buf_size;
bool is_write;
bool is_valid;
u8 buf[1024 * 1024 * 1];
};
struct FtpVfsDir {
FsDir dir;
bool is_valid;
};
struct FtpVfsDirEntry {
FsDirectoryEntry buf;
};
#ifdef __cplusplus
namespace sphaira::ftpsrv { namespace sphaira::ftpsrv {
bool Init(); bool Init();
void Exit(); void Exit();
} // namespace sphaira::ftpsrv } // namespace sphaira::ftpsrv
#endif // __cplusplus

View File

@@ -2,12 +2,15 @@
#define sphaira_USE_LOG 1 #define sphaira_USE_LOG 1
#include <cstdarg>
#if sphaira_USE_LOG #if sphaira_USE_LOG
auto log_file_init() -> bool; auto log_file_init() -> bool;
auto log_nxlink_init() -> bool; auto log_nxlink_init() -> bool;
void log_file_exit(); void log_file_exit();
void log_nxlink_exit(); void log_nxlink_exit();
void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2))); void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2)));
void log_write_arg(const char* s, std::va_list& v);
#else #else
inline auto log_file_init() -> bool { inline auto log_file_init() -> bool {
return true; return true;

View File

@@ -1,24 +1,25 @@
#include "ftpsrv_helper.hpp" #include "ftpsrv_helper.hpp"
#include <ftpsrv.h>
#include <ftpsrv_vfs.h>
#include "app.hpp" #include "app.hpp"
#include "fs.hpp" #include "fs.hpp"
#include "log.hpp" #include "log.hpp"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <mutex> #include <mutex>
#include <algorithm> #include <algorithm>
#include <minIni.h>
#include <ftpsrv.h>
#include <ftpsrv_vfs.h>
#include <nx/vfs_nx.h>
#include <nx/utils.h>
namespace { namespace {
const char* INI_PATH = "/config/ftpsrv/config.ini";
FtpSrvConfig g_ftpsrv_config = {0}; FtpSrvConfig g_ftpsrv_config = {0};
volatile bool g_should_exit = false; volatile bool g_should_exit = false;
bool g_is_running{false}; bool g_is_running{false};
Thread g_thread; Thread g_thread;
std::mutex g_mutex{}; std::mutex g_mutex{};
FsFileSystem* g_fs;
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) { void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
sphaira::App::NotifyFlashLed(); sphaira::App::NotifyFlashLed();
@@ -28,32 +29,6 @@ void ftp_progress_callback(void) {
sphaira::App::NotifyFlashLed(); sphaira::App::NotifyFlashLed();
} }
int vfs_fs_set_errno(Result rc) {
switch (rc) {
case FsError_TargetLocked: errno = EBUSY; break;
case FsError_PathNotFound: errno = ENOENT; break;
case FsError_PathAlreadyExists: errno = EEXIST; break;
case FsError_UsableSpaceNotEnoughMmcCalibration: errno = ENOSPC; break;
case FsError_UsableSpaceNotEnoughMmcSafe: errno = ENOSPC; break;
case FsError_UsableSpaceNotEnoughMmcUser: errno = ENOSPC; break;
case FsError_UsableSpaceNotEnoughMmcSystem: errno = ENOSPC; break;
case FsError_UsableSpaceNotEnoughSdCard: errno = ENOSPC; break;
case FsError_OutOfRange: errno = ESPIPE; break;
case FsError_TooLongPath: errno = ENAMETOOLONG; break;
case FsError_UnsupportedWriteForReadOnlyFileSystem: errno = EROFS; break;
default: errno = EIO; break;
}
return -1;
}
Result flush_buffered_write(struct FtpVfsFile* f) {
Result rc;
if (R_SUCCEEDED(rc = fsFileSetSize(&f->fd, f->off + f->buf_off))) {
rc = fsFileWrite(&f->fd, f->off, f->buf, f->buf_off, FsWriteOption_None);
}
return rc;
}
void loop(void* arg) { void loop(void* arg) {
while (!g_should_exit) { while (!g_should_exit) {
ftpsrv_init(&g_ftpsrv_config); ftpsrv_init(&g_ftpsrv_config);
@@ -77,16 +52,51 @@ bool Init() {
return false; return false;
} }
g_fs = fsdevGetDeviceFileSystem("sdmc"); if (R_FAILED(fsdev_wrapMountSdmc())) {
return false;
}
g_ftpsrv_config.log_callback = ftp_log_callback; g_ftpsrv_config.log_callback = ftp_log_callback;
g_ftpsrv_config.progress_callback = ftp_progress_callback; g_ftpsrv_config.progress_callback = ftp_progress_callback;
g_ftpsrv_config.anon = true; g_ftpsrv_config.anon = ini_getbool("Login", "anon", 0, INI_PATH);
g_ftpsrv_config.timeout = 15; int user_len = ini_gets("Login", "user", "", g_ftpsrv_config.user, sizeof(g_ftpsrv_config.user), INI_PATH);
g_ftpsrv_config.port = 5000; int pass_len = ini_gets("Login", "pass", "", g_ftpsrv_config.pass, sizeof(g_ftpsrv_config.pass), INI_PATH);
g_ftpsrv_config.port = ini_getl("Network", "port", 5000, INI_PATH); // 5000 to keep compat with older sphaira
g_ftpsrv_config.timeout = ini_getl("Network", "timeout", 0, INI_PATH);
g_ftpsrv_config.use_localtime = ini_getbool("Misc", "use_localtime", 0, INI_PATH);
bool log_enabled = ini_getbool("Log", "log", 0, INI_PATH);
// get nx config
bool mount_devices = ini_getbool("Nx", "mount_devices", 1, INI_PATH);
bool mount_bis = ini_getbool("Nx", "mount_bis", 0, INI_PATH);
bool save_writable = ini_getbool("Nx", "save_writable", 0, INI_PATH);
g_ftpsrv_config.port = ini_getl("Nx", "app_port", g_ftpsrv_config.port, INI_PATH); // compat
// get Nx-App overrides
g_ftpsrv_config.anon = ini_getbool("Nx-App", "anon", g_ftpsrv_config.anon, INI_PATH);
user_len = ini_gets("Nx-App", "user", g_ftpsrv_config.user, g_ftpsrv_config.user, sizeof(g_ftpsrv_config.user), INI_PATH);
pass_len = ini_gets("Nx-App", "pass", g_ftpsrv_config.pass, g_ftpsrv_config.pass, sizeof(g_ftpsrv_config.pass), INI_PATH);
g_ftpsrv_config.port = ini_getl("Nx-App", "port", g_ftpsrv_config.port, INI_PATH);
g_ftpsrv_config.timeout = ini_getl("Nx-App", "timeout", g_ftpsrv_config.timeout, INI_PATH);
g_ftpsrv_config.use_localtime = ini_getbool("Nx-App", "use_localtime", g_ftpsrv_config.use_localtime, INI_PATH);
log_enabled = ini_getbool("Nx-App", "log", log_enabled, INI_PATH);
mount_devices = ini_getbool("Nx-App", "mount_devices", mount_devices, INI_PATH);
mount_bis = ini_getbool("Nx-App", "mount_bis", mount_bis, INI_PATH);
save_writable = ini_getbool("Nx-App", "save_writable", save_writable, INI_PATH);
if (!g_ftpsrv_config.port) {
return false;
}
// keep compat with older sphaira
if (!user_len && !pass_len) {
g_ftpsrv_config.anon = true;
}
vfs_nx_init(mount_devices, save_writable, mount_bis);
Result rc; Result rc;
if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*64, 0x2C, 2))) { if (R_FAILED(rc = threadCreate(&g_thread, loop, nullptr, nullptr, 1024*16, 0x2C, 2))) {
log_write("failed to create nxlink thread: 0x%X\n", rc); log_write("failed to create nxlink thread: 0x%X\n", rc);
return false; return false;
} }
@@ -108,232 +118,24 @@ void Exit() {
g_should_exit = true; g_should_exit = true;
threadWaitForExit(&g_thread); threadWaitForExit(&g_thread);
threadClose(&g_thread); threadClose(&g_thread);
vfs_nx_exit();
fsdev_wrapUnmountAll();
} }
} // namespace sphaira::ftpsrv } // namespace sphaira::ftpsrv
extern "C" { extern "C" {
#define VFS_NX_BUFFER_IO 1 void log_file_write(const char* msg) {
log_write("%s", msg);
int ftp_vfs_open(struct FtpVfsFile* f, const char* path, enum FtpVfsOpenMode mode) {
u32 open_mode;
if (mode == FtpVfsOpenMode_READ) {
open_mode = FsOpenMode_Read;
f->is_write = false;
} else {
fsFsCreateFile(g_fs, path, 0, 0);
open_mode = FsOpenMode_Write | FsOpenMode_Append;
#if !VFS_NX_BUFFER_IO
open_mode |= FsOpenMode_Append;
#endif
f->is_write = true;
}
Result rc;
if (R_FAILED(rc = fsFsOpenFile(g_fs, path, open_mode, &f->fd))) {
return vfs_fs_set_errno(rc);
}
f->off = f->buf_off = f->buf_size = 0;
if (mode == FtpVfsOpenMode_WRITE) {
if (R_FAILED(rc = fsFileSetSize(&f->fd, 0))) {
goto fail_close;
}
} else if (mode == FtpVfsOpenMode_APPEND) {
if (R_FAILED(rc = fsFileGetSize(&f->fd, &f->off))) {
goto fail_close;
}
}
f->is_valid = true;
return 0;
fail_close:
fsFileClose(&f->fd);
return vfs_fs_set_errno(rc);
} }
int ftp_vfs_read(struct FtpVfsFile* f, void* buf, size_t size) { void log_file_fwrite(const char* fmt, ...) {
Result rc; std::va_list v{};
va_start(v, fmt);
#if VFS_NX_BUFFER_IO log_write_arg(fmt, v);
if (f->buf_off == f->buf_size) { va_end(v);
u64 bytes_read;
if (R_FAILED(rc = fsFileRead(&f->fd, f->off, f->buf, sizeof(f->buf), FsReadOption_None, &bytes_read))) {
return vfs_fs_set_errno(rc);
}
f->buf_off = 0;
f->buf_size = bytes_read;
}
if (!f->buf_size) {
return 0;
}
size = size < f->buf_size - f->buf_off ? size : f->buf_size - f->buf_off;
memcpy(buf, f->buf + f->buf_off, size);
f->off += size;
f->buf_off += size;
return size;
#else
u64 bytes_read;
if (R_FAILED(rc = fsFileRead(&f->fd, f->off, buf, size, FsReadOption_None, &bytes_read))) {
return vfs_fs_set_errno(rc);
}
f->off += bytes_read;
return bytes_read;
#endif
}
int ftp_vfs_write(struct FtpVfsFile* f, const void* buf, size_t size) {
Result rc;
#if VFS_NX_BUFFER_IO
const size_t ret = size;
while (size) {
if (f->buf_off + size > sizeof(f->buf)) {
const u64 sz = sizeof(f->buf) - f->buf_off;
memcpy(f->buf + f->buf_off, buf, sz);
f->buf_off += sz;
if (R_FAILED(rc = flush_buffered_write(f))) {
return vfs_fs_set_errno(rc);
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpointer-arith"
buf += sz;
#pragma GCC diagnostic pop
size -= sz;
f->off += f->buf_off;
f->buf_off = 0;
} else {
memcpy(f->buf + f->buf_off, buf, size);
f->buf_off += size;
size = 0;
}
}
return ret;
#else
if (R_FAILED(rc = fsFileWrite(&f->fd, f->off, buf, size, FsWriteOption_None))) {
return vfs_fs_set_errno(rc);
}
f->off += size;
return size;
const size_t ret = size;
#endif
}
// buf and size is the amount of data sent.
int ftp_vfs_seek(struct FtpVfsFile* f, const void* buf, size_t size, size_t off) {
#if VFS_NX_BUFFER_IO
if (!f->is_write) {
f->buf_off -= f->off - off;
}
#endif
f->off = off;
return 0;
}
int ftp_vfs_close(struct FtpVfsFile* f) {
if (!ftp_vfs_isfile_open(f)) {
return -1;
}
if (f->is_write && f->buf_off) {
flush_buffered_write(f);
}
fsFileClose(&f->fd);
f->is_valid = false;
return 0;
}
int ftp_vfs_isfile_open(struct FtpVfsFile* f) {
return f->is_valid;
}
int ftp_vfs_opendir(struct FtpVfsDir* f, const char* path) {
Result rc;
if (R_FAILED(rc = fsFsOpenDirectory(g_fs, path, FsDirOpenMode_ReadDirs | FsDirOpenMode_ReadFiles | FsDirOpenMode_NoFileSize, &f->dir))) {
return vfs_fs_set_errno(rc);
}
f->is_valid = true;
return 0;
}
const char* ftp_vfs_readdir(struct FtpVfsDir* f, struct FtpVfsDirEntry* entry) {
Result rc;
s64 total_entries;
if (R_FAILED(rc = fsDirRead(&f->dir, &total_entries, 1, &entry->buf))) {
vfs_fs_set_errno(rc);
return NULL;
}
if (total_entries <= 0) {
return NULL;
}
return entry->buf.name;
}
int ftp_vfs_dirlstat(struct FtpVfsDir* f, const struct FtpVfsDirEntry* entry, const char* path, struct stat* st) {
return lstat(path, st);
}
int ftp_vfs_closedir(struct FtpVfsDir* f) {
if (!ftp_vfs_isdir_open(f)) {
return -1;
}
fsDirClose(&f->dir);
f->is_valid = false;
return 0;
}
int ftp_vfs_isdir_open(struct FtpVfsDir* f) {
return f->is_valid;
}
int ftp_vfs_stat(const char* path, struct stat* st) {
return stat(path, st);
}
int ftp_vfs_lstat(const char* path, struct stat* st) {
return lstat(path, st);
}
int ftp_vfs_mkdir(const char* path) {
return mkdir(path, 0777);
}
int ftp_vfs_unlink(const char* path) {
return unlink(path);
}
int ftp_vfs_rmdir(const char* path) {
return rmdir(path);
}
int ftp_vfs_rename(const char* src, const char* dst) {
return rename(src, dst);
}
int ftp_vfs_readlink(const char* path, char* buf, size_t buflen) {
return -1;
}
const char* ftp_vfs_getpwuid(const struct stat* st) {
return "unknown";
}
const char* ftp_vfs_getgrgid(const struct stat* st) {
return "unknown";
} }
} // extern "C" } // extern "C"

View File

@@ -14,6 +14,16 @@ std::FILE* file{};
int nxlink_socket{}; int nxlink_socket{};
std::mutex mutex{}; std::mutex mutex{};
void log_write_arg_internal(const char* s, std::va_list& v) {
if (file) {
std::vfprintf(file, s, v);
std::fflush(file);
}
if (nxlink_socket) {
std::vprintf(s, v);
}
}
} // namespace } // namespace
auto log_file_init() -> bool { auto log_file_init() -> bool {
@@ -60,13 +70,17 @@ void log_write(const char* s, ...) {
std::va_list v{}; std::va_list v{};
va_start(v, s); va_start(v, s);
if (file) { log_write_arg_internal(s, v);
std::vfprintf(file, s, v);
std::fflush(file);
}
if (nxlink_socket) {
std::vprintf(s, v);
}
va_end(v); va_end(v);
} }
void log_write_arg(const char* s, std::va_list& v) {
std::scoped_lock lock{mutex};
if (!file && !nxlink_socket) {
return;
}
log_write_arg_internal(s, v);
}
#endif #endif

View File

@@ -63,6 +63,8 @@ void userAppInit(void) {
diagAbortWithResult(rc); diagAbortWithResult(rc);
if (R_FAILED(rc = hidsysInitialize())) if (R_FAILED(rc = hidsysInitialize()))
diagAbortWithResult(rc); diagAbortWithResult(rc);
if (R_FAILED(rc = ncmInitialize()))
diagAbortWithResult(rc);
log_nxlink_init(); log_nxlink_init();
} }
@@ -70,6 +72,7 @@ void userAppInit(void) {
void userAppExit(void) { void userAppExit(void) {
log_nxlink_exit(); log_nxlink_exit();
ncmExit();
hidsysExit(); hidsysExit();
setExit(); setExit();
accountExit(); accountExit();