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
run: |
cmake --preset ${{ matrix.preset }}
cmake --preset ${{ matrix.preset }} -DUSE_VFS_GC=0
- name: Build
run: cmake --build --preset ${{ matrix.preset }} --parallel 4

View File

@@ -26,7 +26,8 @@ please include:
## 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

View File

@@ -86,7 +86,7 @@ set(FETCHCONTENT_QUIET FALSE)
FetchContent_Declare(ftpsrv
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
GIT_TAG 8d5a14e
GIT_TAG 1.2.1
)
FetchContent_Declare(libhaze
@@ -114,12 +114,12 @@ FetchContent_Declare(yyjson
GIT_TAG 0.10.0
)
FetchContent_Declare(minIni-sphaira
FetchContent_Declare(minIni
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
GIT_TAG 63ec295
)
set(MININI_LIB_NAME minIni-sphaira)
set(MININI_LIB_NAME minIni)
set(MININI_USE_STDIO ON)
set(MININI_USE_NX ON)
set(MININI_USE_FLOAT OFF)
@@ -149,18 +149,48 @@ set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
set(FTPSRV_LIB_BUILD 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_SESSIONS 32)
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(
ftpsrv
libhaze
libpulsar
nanovg
stb
minIni-sphaira
minIni
yyjson
)
@@ -241,10 +271,10 @@ set_target_properties(sphaira PROPERTIES
)
target_link_libraries(sphaira PRIVATE
ftpsrv
ftpsrv_helper
libhaze
libpulsar
minIni-sphaira
minIni
nanovg
stb
yyjson

View File

@@ -1,33 +1,8 @@
#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 {
bool Init();
void Exit();
} // namespace sphaira::ftpsrv
#endif // __cplusplus

View File

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

View File

@@ -1,24 +1,25 @@
#include "ftpsrv_helper.hpp"
#include <ftpsrv.h>
#include <ftpsrv_vfs.h>
#include "app.hpp"
#include "fs.hpp"
#include "log.hpp"
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <mutex>
#include <algorithm>
#include <minIni.h>
#include <ftpsrv.h>
#include <ftpsrv_vfs.h>
#include <nx/vfs_nx.h>
#include <nx/utils.h>
namespace {
const char* INI_PATH = "/config/ftpsrv/config.ini";
FtpSrvConfig g_ftpsrv_config = {0};
volatile bool g_should_exit = false;
bool g_is_running{false};
Thread g_thread;
std::mutex g_mutex{};
FsFileSystem* g_fs;
void ftp_log_callback(enum FTP_API_LOG_TYPE type, const char* msg) {
sphaira::App::NotifyFlashLed();
@@ -28,32 +29,6 @@ void ftp_progress_callback(void) {
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) {
while (!g_should_exit) {
ftpsrv_init(&g_ftpsrv_config);
@@ -77,16 +52,51 @@ bool Init() {
return false;
}
g_fs = fsdevGetDeviceFileSystem("sdmc");
if (R_FAILED(fsdev_wrapMountSdmc())) {
return false;
}
g_ftpsrv_config.log_callback = ftp_log_callback;
g_ftpsrv_config.progress_callback = ftp_progress_callback;
g_ftpsrv_config.anon = true;
g_ftpsrv_config.timeout = 15;
g_ftpsrv_config.port = 5000;
g_ftpsrv_config.anon = ini_getbool("Login", "anon", 0, INI_PATH);
int user_len = ini_gets("Login", "user", "", g_ftpsrv_config.user, sizeof(g_ftpsrv_config.user), INI_PATH);
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;
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);
return false;
}
@@ -108,232 +118,24 @@ void Exit() {
g_should_exit = true;
threadWaitForExit(&g_thread);
threadClose(&g_thread);
vfs_nx_exit();
fsdev_wrapUnmountAll();
}
} // namespace sphaira::ftpsrv
extern "C" {
#define VFS_NX_BUFFER_IO 1
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);
void log_file_write(const char* msg) {
log_write("%s", msg);
}
int ftp_vfs_read(struct FtpVfsFile* f, void* buf, size_t size) {
Result rc;
#if VFS_NX_BUFFER_IO
if (f->buf_off == f->buf_size) {
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";
void log_file_fwrite(const char* fmt, ...) {
std::va_list v{};
va_start(v, fmt);
log_write_arg(fmt, v);
va_end(v);
}
} // extern "C"

View File

@@ -14,6 +14,16 @@ std::FILE* file{};
int nxlink_socket{};
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
auto log_file_init() -> bool {
@@ -60,13 +70,17 @@ void log_write(const char* s, ...) {
std::va_list v{};
va_start(v, s);
if (file) {
std::vfprintf(file, s, v);
std::fflush(file);
}
if (nxlink_socket) {
std::vprintf(s, v);
}
log_write_arg_internal(s, 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

View File

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