diff --git a/.github/workflows/build_presets.yml b/.github/workflows/build_presets.yml index 84477d5..fbfd84c 100644 --- a/.github/workflows/build_presets.yml +++ b/.github/workflows/build_presets.yml @@ -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 diff --git a/README.md b/README.md index 944b4fd..2891abf 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/sphaira/CMakeLists.txt b/sphaira/CMakeLists.txt index e27b2dc..602b4d5 100644 --- a/sphaira/CMakeLists.txt +++ b/sphaira/CMakeLists.txt @@ -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=$ + USE_VFS_STORAGE=$ + 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 diff --git a/sphaira/include/ftpsrv_helper.hpp b/sphaira/include/ftpsrv_helper.hpp index 77871ae..012a02d 100644 --- a/sphaira/include/ftpsrv_helper.hpp +++ b/sphaira/include/ftpsrv_helper.hpp @@ -1,33 +1,8 @@ #pragma once -#include - -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 diff --git a/sphaira/include/log.hpp b/sphaira/include/log.hpp index 231d147..b21e8d9 100644 --- a/sphaira/include/log.hpp +++ b/sphaira/include/log.hpp @@ -2,12 +2,15 @@ #define sphaira_USE_LOG 1 +#include + #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; diff --git a/sphaira/source/ftpsrv_helper.cpp b/sphaira/source/ftpsrv_helper.cpp index ef8507d..4c2f6e7 100644 --- a/sphaira/source/ftpsrv_helper.cpp +++ b/sphaira/source/ftpsrv_helper.cpp @@ -1,24 +1,25 @@ #include "ftpsrv_helper.hpp" -#include -#include + #include "app.hpp" #include "fs.hpp" #include "log.hpp" -#include -#include -#include #include #include +#include +#include +#include +#include +#include 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" diff --git a/sphaira/source/log.cpp b/sphaira/source/log.cpp index b2c8d0e..e875656 100644 --- a/sphaira/source/log.cpp +++ b/sphaira/source/log.cpp @@ -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 diff --git a/sphaira/source/main.cpp b/sphaira/source/main.cpp index 8bc833a..7889dec 100644 --- a/sphaira/source/main.cpp +++ b/sphaira/source/main.cpp @@ -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();