From ecd3941a0253db282643dd94ed1ae3edbce3ad6b Mon Sep 17 00:00:00 2001 From: kivr Date: Wed, 20 May 2020 16:14:36 -0500 Subject: [PATCH 01/13] Add sys-ftp-light files. --- source/Sysmodule/source/console.c | 54 + source/Sysmodule/source/console.h | 40 + source/Sysmodule/source/ftp.c | 4145 +++++++++++++++++ source/Sysmodule/source/ftp.h | 17 + source/Sysmodule/source/led.c | 56 + source/Sysmodule/source/led.h | 5 + source/Sysmodule/source/main.cpp | 103 +- source/Sysmodule/source/minIni.c | 928 ++++ source/Sysmodule/source/minIni.h | 184 + .../Sysmodule/source/minIni/minGlue-FatFs.h | 37 + source/Sysmodule/source/minIni/minGlue-ccs.h | 64 + source/Sysmodule/source/minIni/minGlue-efsl.h | 63 + source/Sysmodule/source/minIni/minGlue-ffs.h | 26 + source/Sysmodule/source/minIni/minGlue-mdd.h | 59 + .../Sysmodule/source/minIni/minGlue-stdio.h | 31 + source/Sysmodule/source/minIni/minGlue.h | 31 + source/Sysmodule/source/minIni/wxMinIni.h | 126 + source/Sysmodule/source/util.c | 147 + source/Sysmodule/source/util.h | 21 + 19 files changed, 6134 insertions(+), 3 deletions(-) create mode 100644 source/Sysmodule/source/console.c create mode 100644 source/Sysmodule/source/console.h create mode 100644 source/Sysmodule/source/ftp.c create mode 100644 source/Sysmodule/source/ftp.h create mode 100644 source/Sysmodule/source/led.c create mode 100644 source/Sysmodule/source/led.h create mode 100644 source/Sysmodule/source/minIni.c create mode 100644 source/Sysmodule/source/minIni.h create mode 100644 source/Sysmodule/source/minIni/minGlue-FatFs.h create mode 100644 source/Sysmodule/source/minIni/minGlue-ccs.h create mode 100644 source/Sysmodule/source/minIni/minGlue-efsl.h create mode 100644 source/Sysmodule/source/minIni/minGlue-ffs.h create mode 100644 source/Sysmodule/source/minIni/minGlue-mdd.h create mode 100644 source/Sysmodule/source/minIni/minGlue-stdio.h create mode 100644 source/Sysmodule/source/minIni/minGlue.h create mode 100644 source/Sysmodule/source/minIni/wxMinIni.h create mode 100644 source/Sysmodule/source/util.c create mode 100644 source/Sysmodule/source/util.h diff --git a/source/Sysmodule/source/console.c b/source/Sysmodule/source/console.c new file mode 100644 index 00000000..0044a7b3 --- /dev/null +++ b/source/Sysmodule/source/console.c @@ -0,0 +1,54 @@ +// This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) + +#define ENABLE_LOGGING 1 +#include "console.h" +#include +#include +#include +#include +#include +#include + +/* this is a lot easier when you have a real console */ + +int should_log = 0; + +void console_init(void) +{ +} + +void console_set_status(const char* fmt, ...) +{ +} + +void console_print(const char* fmt, ...) +{ + if (should_log) + { + stdout = stderr = fopen("/config/sys-ftpd/logs/ftpd.log", "a"); + va_list ap; + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); + fclose(stdout); + } +} + +void debug_print(const char* fmt, ...) +{ + if (should_log) + { + stdout = stderr = fopen("/config/sys-ftpd/logs/ftpd.log", "a"); +#ifdef ENABLE_LOGGING + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +#endif + fclose(stdout); + } +} + +void console_render(void) +{ +} diff --git a/source/Sysmodule/source/console.h b/source/Sysmodule/source/console.h new file mode 100644 index 00000000..39e418b0 --- /dev/null +++ b/source/Sysmodule/source/console.h @@ -0,0 +1,40 @@ +// This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) + +#pragma once + +#ifdef _3DS +# include <3ds.h> +# define ESC(x) "\x1b[" # x +# define RESET ESC(0m) +# define BLACK ESC(30m) +# define RED ESC(31; 1m) +# define GREEN ESC(32; 1m) +# define YELLOW ESC(33; 1m) +# define BLUE ESC(34; 1m) +# define MAGENTA ESC(35; 1m) +# define CYAN ESC(36; 1m) +# define WHITE ESC(37; 1m) +#else +# define ESC(x) +# define RESET +# define BLACK +# define RED +# define GREEN +# define YELLOW +# define BLUE +# define MAGENTA +# define CYAN +# define WHITE +#endif + +extern int should_log; + +void console_init(void); + +__attribute__((format(printf, 1, 2))) void console_set_status(const char* fmt, ...); + +__attribute__((format(printf, 1, 2))) void console_print(const char* fmt, ...); + +__attribute__((format(printf, 1, 2))) void debug_print(const char* fmt, ...); + +void console_render(void); \ No newline at end of file diff --git a/source/Sysmodule/source/ftp.c b/source/Sysmodule/source/ftp.c new file mode 100644 index 00000000..0e49f231 --- /dev/null +++ b/source/Sysmodule/source/ftp.c @@ -0,0 +1,4145 @@ +// This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) + +#define ENABLE_LOGGING 1 +/* This FTP server implementation is based on RFC 959, + * (https://tools.ietf.org/html/rfc959), RFC 3659 + * (https://tools.ietf.org/html/rfc3659) and suggested implementation details + * from https://cr.yp.to/ftp/filesystem.html + */ +#include "ftp.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef _3DS +# include <3ds.h> +# define lstat stat +#elif defined(__SWITCH__) +# include +# define lstat stat +#else +# include +# define BIT(x) (1 << (x)) +#endif +#include "console.h" +#include "led.h" +#include "util.h" + +#define POLL_UNKNOWN (~(POLLIN | POLLPRI | POLLOUT)) + +#define XFER_BUFFERSIZE 0x4000 +#define SOCK_BUFFERSIZE 0x4000 +#define FILE_BUFFERSIZE 0x8000 +#define CMD_BUFFERSIZE 0x1000 + +int LISTEN_PORT; +//#define LISTEN_PORT 5000 +#ifdef _3DS +# define DATA_PORT (LISTEN_PORT + 1) +#else +# define DATA_PORT 0 /* ephemeral port */ +#endif + +#include "minIni.h" +#include + +int Callback(const char* section, const char* key, const char* value, void* userdata) +{ + (void)userdata; /* this parameter is not used in this example */ + printf(" [%s]\t%s=%s\n", section, key, value); + return 1; +} + +typedef struct ftp_session_t ftp_session_t; + +#define FTP_DECLARE(x) static void x(ftp_session_t* session, const char* args) +FTP_DECLARE(ABOR); +FTP_DECLARE(ALLO); +FTP_DECLARE(APPE); +FTP_DECLARE(CDUP); +FTP_DECLARE(CWD); +FTP_DECLARE(DELE); +FTP_DECLARE(FEAT); +FTP_DECLARE(HELP); +FTP_DECLARE(LIST); +FTP_DECLARE(MDTM); +FTP_DECLARE(MKD); +FTP_DECLARE(MLSD); +FTP_DECLARE(MLST); +FTP_DECLARE(MODE); +FTP_DECLARE(NLST); +FTP_DECLARE(NOOP); +FTP_DECLARE(OPTS); +FTP_DECLARE(PASS); +FTP_DECLARE(PASV); +FTP_DECLARE(PORT); +FTP_DECLARE(PWD); +FTP_DECLARE(QUIT); +FTP_DECLARE(REST); +FTP_DECLARE(RETR); +FTP_DECLARE(RMD); +FTP_DECLARE(RNFR); +FTP_DECLARE(RNTO); +FTP_DECLARE(SIZE); +FTP_DECLARE(STAT); +FTP_DECLARE(STOR); +FTP_DECLARE(STOU); +FTP_DECLARE(STRU); +FTP_DECLARE(SYST); +FTP_DECLARE(TYPE); +FTP_DECLARE(USER); + +/*! session state */ +typedef enum +{ + COMMAND_STATE, /*!< waiting for a command */ + DATA_CONNECT_STATE, /*!< waiting for connection after PASV command */ + DATA_TRANSFER_STATE, /*!< data transfer in progress */ +} session_state_t; + +/*! ftp_session_set_state flags */ +typedef enum +{ + CLOSE_PASV = BIT(0), /*!< Close the pasv_fd */ + CLOSE_DATA = BIT(1), /*!< Close the data_fd */ +} set_state_flags_t; + +/*! ftp_session_t flags */ +typedef enum +{ + SESSION_BINARY = BIT(0), /*!< data transfers in binary mode */ + SESSION_PASV = BIT(1), /*!< have pasv_addr ready for data transfer command */ + SESSION_PORT = BIT(2), /*!< have peer_addr ready for data transfer command */ + SESSION_RECV = BIT(3), /*!< data transfer in source mode */ + SESSION_SEND = BIT(4), /*!< data transfer in sink mode */ + SESSION_RENAME = BIT(5), /*!< last command was RNFR and buffer contains path */ + SESSION_URGENT = BIT(6), /*!< in telnet urgent mode */ +} session_flags_t; + +/*! ftp_xfer_dir mode */ +typedef enum +{ + XFER_DIR_LIST, /*!< Long list */ + XFER_DIR_MLSD, /*!< Machine list directory */ + XFER_DIR_MLST, /*!< Machine list */ + XFER_DIR_NLST, /*!< Short list */ + XFER_DIR_STAT, /*!< Stat command */ +} xfer_dir_mode_t; + +typedef enum +{ + SESSION_MLST_TYPE = BIT(0), + SESSION_MLST_SIZE = BIT(1), + SESSION_MLST_MODIFY = BIT(2), + SESSION_MLST_PERM = BIT(3), + SESSION_MLST_UNIX_MODE = BIT(4), +} session_mlst_flags_t; + +/*! ftp session */ +struct ftp_session_t +{ + char cwd[4096]; /*!< current working directory */ + char lwd[4096]; /*!< list working directory */ + struct sockaddr_in peer_addr; /*!< peer address for data connection */ + struct sockaddr_in pasv_addr; /*!< listen address for PASV connection */ + int cmd_fd; /*!< socket for command connection */ + int pasv_fd; /*!< listen socket for PASV */ + int data_fd; /*!< socket for data transfer */ + time_t timestamp; /*!< time from last command */ + session_flags_t flags; /*!< session flags */ + xfer_dir_mode_t dir_mode; /*!< dir transfer mode */ + session_mlst_flags_t mlst_flags; /*!< session MLST flags */ + session_state_t state; /*!< session state */ + ftp_session_t* next; /*!< link to next session */ + ftp_session_t* prev; /*!< link to prev session */ + + loop_status_t (*transfer)(ftp_session_t*); /*! data transfer callback */ + char buffer[XFER_BUFFERSIZE]; /*! persistent data between callbacks */ + char file_buffer[FILE_BUFFERSIZE]; /*! stdio file buffer */ + char cmd_buffer[CMD_BUFFERSIZE]; /*! command buffer */ + size_t bufferpos; /*! persistent buffer position between callbacks */ + size_t buffersize; /*! persistent buffer size between callbacks */ + size_t cmd_buffersize; + uint64_t filepos; /*! persistent file position between callbacks */ + uint64_t filesize; /*! persistent file size between callbacks */ + FILE* fp; /*! persistent open file pointer between callbacks */ + DIR* dp; /*! persistent open directory pointer between callbacks */ + bool user_ok; + bool pass_ok; + bool led; +}; + +/*! ftp command descriptor */ +typedef struct ftp_command +{ + const char* name; /*!< command name */ + void (*handler)(ftp_session_t*, const char*); /*!< command callback */ +} ftp_command_t; + +/*! ftp command list */ +static ftp_command_t ftp_commands[] = + { +/*! ftp command */ +#define FTP_COMMAND(x) \ + { \ +# x, x, \ + } +/*! ftp alias */ +#define FTP_ALIAS(x, y) \ + { \ +# x, y, \ + } + FTP_COMMAND(ABOR), + FTP_COMMAND(ALLO), + FTP_COMMAND(APPE), + FTP_COMMAND(CDUP), + FTP_COMMAND(CWD), + FTP_COMMAND(DELE), + FTP_COMMAND(FEAT), + FTP_COMMAND(HELP), + FTP_COMMAND(LIST), + FTP_COMMAND(MDTM), + FTP_COMMAND(MKD), + FTP_COMMAND(MLSD), + FTP_COMMAND(MLST), + FTP_COMMAND(MODE), + FTP_COMMAND(NLST), + FTP_COMMAND(NOOP), + FTP_COMMAND(OPTS), + FTP_COMMAND(PASS), + FTP_COMMAND(PASV), + FTP_COMMAND(PORT), + FTP_COMMAND(PWD), + FTP_COMMAND(QUIT), + FTP_COMMAND(REST), + FTP_COMMAND(RETR), + FTP_COMMAND(RMD), + FTP_COMMAND(RNFR), + FTP_COMMAND(RNTO), + FTP_COMMAND(SIZE), + FTP_COMMAND(STAT), + FTP_COMMAND(STOR), + FTP_COMMAND(STOU), + FTP_COMMAND(STRU), + FTP_COMMAND(SYST), + FTP_COMMAND(TYPE), + FTP_COMMAND(USER), + FTP_ALIAS(XCUP, CDUP), + FTP_ALIAS(XCWD, CWD), + FTP_ALIAS(XMKD, MKD), + FTP_ALIAS(XPWD, PWD), + FTP_ALIAS(XRMD, RMD), +}; +/*! number of ftp commands */ +static const size_t num_ftp_commands = sizeof(ftp_commands) / sizeof(ftp_commands[0]); + +static void update_free_space(void); + +/*! compare ftp command descriptors + * + * @param[in] p1 left side of comparison (ftp_command_t*) + * @param[in] p2 right side of comparison (ftp_command_t*) + * + * @returns <0 if p1 < p2 + * @returns 0 if p1 == p2 + * @returns >0 if p1 > p2 + */ +static int +ftp_command_cmp(const void* p1, + const void* p2) +{ + ftp_command_t* c1 = (ftp_command_t*)p1; + ftp_command_t* c2 = (ftp_command_t*)p2; + + /* ordered by command name */ + return strcasecmp(c1->name, c2->name); +} + +#ifdef _3DS +/*! SOC service buffer */ +static u32* SOCU_buffer = NULL; + +/*! Whether LCD is powered */ +static bool lcd_power = true; + +/*! aptHook cookie */ +static aptHookCookie cookie; +#elif defined(__SWITCH__) + +/*! appletHook cookie */ +static AppletHookCookie cookie; +#endif + +/*! server listen address */ +static struct sockaddr_in serv_addr; +/*! listen file descriptor */ +static int listenfd = -1; +#ifdef _3DS +/*! current data port */ +static in_port_t data_port = DATA_PORT; +#endif +/*! list of ftp sessions */ +static ftp_session_t* sessions = NULL; +/*! socket buffersize */ +static int sock_buffersize = SOCK_BUFFERSIZE; +/*! server start time */ +static time_t start_time = 0; + +/*! Allocate a new data port + * + * @returns next data port + */ +static in_port_t +next_data_port(void) +{ +#ifdef _3DS + if (++data_port >= 10000) + data_port = DATA_PORT; + return data_port; +#else + return 0; /* ephemeral port */ +#endif +} + +/*! set a socket to non-blocking + * + * @param[in] fd socket + * + * @returns error + */ +static int +ftp_set_socket_nonblocking(int fd) +{ + int rc, flags; + + /* get the socket flags */ + flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) + { + console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + /* add O_NONBLOCK to the socket flags */ + rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK); + if (rc != 0) + { + console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + return 0; +} + +/*! set socket options + * + * @param[in] fd socket + * + * @returns failure + */ +static int +ftp_set_socket_options(int fd) +{ + int rc; + + /* increase receive buffer size */ + rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, + &sock_buffersize, sizeof(sock_buffersize)); + if (rc != 0) + { + console_print(RED "setsockopt: SO_RCVBUF %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + /* increase send buffer size */ + rc = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, + &sock_buffersize, sizeof(sock_buffersize)); + if (rc != 0) + { + console_print(RED "setsockopt: SO_SNDBUF %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + return 0; +} + +/*! close a socket + * + * @param[in] fd socket to close + * @param[in] connected whether this socket is connected + */ +static void +ftp_closesocket(int fd, + bool connected) +{ + int rc; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + struct pollfd pollinfo; + + // console_print("0x%X\n", socketGetLastBsdResult()); + + if (connected) + { + /* get peer address and print */ + rc = getpeername(fd, (struct sockaddr*)&addr, &addrlen); + if (rc != 0) + { + console_print(RED "getpeername: %d %s\n" RESET, errno, strerror(errno)); + console_print(YELLOW "closing connection to fd=%d\n" RESET, fd); + } + else + console_print(YELLOW "closing connection to %s:%u\n" RESET, + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + /* shutdown connection */ + rc = shutdown(fd, SHUT_WR); + if (rc != 0) + console_print(RED "shutdown: %d %s\n" RESET, errno, strerror(errno)); + + /* wait for client to close connection */ + pollinfo.fd = fd; + pollinfo.events = POLLIN; + pollinfo.revents = 0; + rc = poll(&pollinfo, 1, 250); + if (rc < 0) + console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); + } + + /* set linger to 0 */ + struct linger linger; + linger.l_onoff = 1; + linger.l_linger = 0; + rc = setsockopt(fd, SOL_SOCKET, SO_LINGER, + &linger, sizeof(linger)); + if (rc != 0) + console_print(RED "setsockopt: SO_LINGER %d %s\n" RESET, + errno, strerror(errno)); + + /* close socket */ + rc = close(fd); + if (rc != 0) + console_print(RED "close: %d %s\n" RESET, errno, strerror(errno)); +} + +/*! close command socket on ftp session + * + * @param[in] session ftp session + */ +static void +ftp_session_close_cmd(ftp_session_t* session) +{ + /* close command socket */ + if (session->cmd_fd >= 0) + ftp_closesocket(session->cmd_fd, true); + session->cmd_fd = -1; +} + +/*! close listen socket on ftp session + * + * @param[in] session ftp session + */ +static void +ftp_session_close_pasv(ftp_session_t* session) +{ + /* close pasv socket */ + if (session->pasv_fd >= 0) + { + console_print(YELLOW "stop listening on %s:%u\n" RESET, + inet_ntoa(session->pasv_addr.sin_addr), + ntohs(session->pasv_addr.sin_port)); + + ftp_closesocket(session->pasv_fd, false); + } + + session->pasv_fd = -1; +} + +/*! close data socket on ftp session + * + * @param[in] session ftp session + */ +static void +ftp_session_close_data(ftp_session_t* session) +{ + /* close data connection */ + if (session->data_fd >= 0 && session->data_fd != session->cmd_fd) + ftp_closesocket(session->data_fd, true); + session->data_fd = -1; + + /* clear send/recv flags */ + session->flags &= ~(SESSION_RECV | SESSION_SEND); +} + +/*! close open file for ftp session + * + * @param[in] session ftp session + */ +static void +ftp_session_close_file(ftp_session_t* session) +{ + int rc; + + if (session->fp != NULL) + { + rc = fclose(session->fp); + if (rc != 0) + console_print(RED "fclose: %d %s\n" RESET, errno, strerror(errno)); + } + + session->fp = NULL; + session->filepos = 0; +} + +/*! open file for reading for ftp session + * + * @param[in] session ftp session + * + * @returns -1 for error + */ +static int +ftp_session_open_file_read(ftp_session_t* session) +{ + int rc; + struct stat st; + + /* open file in read mode */ + if (!strcmp("/config/sys-ftpd/logs/ftpd.log", session->buffer)) + { + console_print(RED "Tried to open ftpd.log for reading. That's not allowed!\n"); + return -1; + } + + session->fp = fopen(session->buffer, "rb"); + if (session->fp == NULL) + { + console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + return -1; + } + + /* it's okay if this fails */ + errno = 0; + rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); + if (rc != 0) + { + console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); + } + + /* get the file size */ + rc = fstat(fileno(session->fp), &st); + if (rc != 0) + { + console_print(RED "fstat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + return -1; + } + session->filesize = st.st_size; + + if (session->filepos != 0) + { + rc = fseek(session->fp, session->filepos, SEEK_SET); + if (rc != 0) + { + console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + return -1; + } + } + + return 0; +} + +/*! read from an open file for ftp session + * + * @param[in] session ftp session + * + * @returns bytes read + */ +static ssize_t +ftp_session_read_file(ftp_session_t* session) +{ + ssize_t rc; + + /* read file at current position */ + rc = fread(session->buffer, 1, sizeof(session->buffer), session->fp); + if (rc < 0) + { + console_print(RED "fread: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + /* adjust file position */ + session->filepos += rc; + + return rc; +} + +/*! open file for writing for ftp session + * + * @param[in] session ftp session + * @param[in] append whether to append + * + * @returns -1 for error + * + * @note truncates file + */ +static int +ftp_session_open_file_write(ftp_session_t* session, + bool append) +{ + int rc; + const char* mode = "wb"; + + if (!strcmp("/config/sys-ftpd/logs/ftpd.log", session->buffer)) + { + console_print(RED "Tried to open ftpd.log for writing. That's not allowed!"); + return -1; + } + + if (append) + mode = "ab"; + else if (session->filepos != 0) + { + mode = "r+b"; + } + + if (!append) + { + unlink(session->buffer); + // Opening an exisiting file for writing can apparently result in corruption D: + } + + /* open file in write mode */ + session->fp = fopen(session->buffer, mode); + if (session->fp == NULL) + { + console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + return -1; + } + + update_free_space(); + + /* it's okay if this fails */ + errno = 0; + rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); + if (rc != 0) + { + console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); + } + + /* check if this had REST but not APPE */ + if (session->filepos != 0 && !append) + { + /* seek to the REST offset */ + rc = fseek(session->fp, session->filepos, SEEK_SET); + if (rc != 0) + { + console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + return -1; + } + } + + return 0; +} + +/*! write to an open file for ftp session + * + * @param[in] session ftp session + * + * @returns bytes written + */ +static ssize_t +ftp_session_write_file(ftp_session_t* session) +{ + ssize_t rc; + + /* write to file at current position */ + rc = fwrite(session->buffer + session->bufferpos, + 1, session->buffersize - session->bufferpos, + session->fp); + if (rc < 0) + { + console_print(RED "fwrite: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + else if (rc == 0) + console_print(RED "fwrite: wrote 0 bytes\n" RESET); + + /* adjust file position */ + session->filepos += rc; + + update_free_space(); + return rc; +} + +/*! close current working directory for ftp session + * + * @param[in] session ftp session + */ +static void +ftp_session_close_cwd(ftp_session_t* session) +{ + int rc; + + /* close open directory pointer */ + if (session->dp != NULL) + { + rc = closedir(session->dp); + if (rc != 0) + console_print(RED "closedir: %d %s\n" RESET, errno, strerror(errno)); + } + session->dp = NULL; +} + +/*! open current working directory for ftp session + * + * @param[in] session ftp session + * + * @return -1 for failure + */ +static int +ftp_session_open_cwd(ftp_session_t* session) +{ + /* open current working directory */ + session->dp = opendir(session->cwd); + if (session->dp == NULL) + { + console_print(RED "opendir '%s': %d %s\n" RESET, session->cwd, errno, strerror(errno)); + return -1; + } + + return 0; +} + +/*! set state for ftp session + * + * @param[in] session ftp session + * @param[in] state state to set + * @param[in] flags flags + */ +static void +ftp_session_set_state(ftp_session_t* session, + session_state_t state, + set_state_flags_t flags) +{ + session->state = state; + + /* close pasv and data sockets */ + if (flags & CLOSE_PASV) + ftp_session_close_pasv(session); + if (flags & CLOSE_DATA) + ftp_session_close_data(session); + + if (state == COMMAND_STATE) + { + /* close file/cwd */ + ftp_session_close_file(session); + ftp_session_close_cwd(session); + } +} + +/*! fill directory entry + * + * @param[in] session ftp session + * @param[in] st stat data + * @param[in] path path to fill + * @param[in] len path length + * @param[in] type type fact + * + * @returns errno + */ +static int +ftp_session_fill_dirent_type(ftp_session_t* session, const struct stat* st, + const char* path, size_t len, const char* type) +{ + session->buffersize = 0; + + if (session->dir_mode == XFER_DIR_MLSD || session->dir_mode == XFER_DIR_MLST) + { + if (session->dir_mode == XFER_DIR_MLST) + session->buffer[session->buffersize++] = ' '; + + if (session->mlst_flags & SESSION_MLST_TYPE) + { + /* type fact */ + if (!type) + { + type = "???"; + if (S_ISREG(st->st_mode)) + type = "file"; + else if (S_ISDIR(st->st_mode)) + type = "dir"; +#if !defined(_3DS) && !defined(__SWITCH__) + else if (S_ISLNK(st->st_mode)) + type = "os.unix=symlink"; + else if (S_ISCHR(st->st_mode)) + type = "os.unix=character"; + else if (S_ISBLK(st->st_mode)) + type = "os.unix=block"; + else if (S_ISFIFO(st->st_mode)) + type = "os.unix=fifo"; + else if (S_ISSOCK(st->st_mode)) + type = "os.unix=socket"; +#endif + } + + session->buffersize += + sprintf(session->buffer + session->buffersize, "Type=%s;", type); + } + + if (session->mlst_flags & SESSION_MLST_SIZE) + { + /* size fact */ + session->buffersize += + sprintf(session->buffer + session->buffersize, "Size=%lld;", + (signed long long)st->st_size); + } + + if (session->mlst_flags & SESSION_MLST_MODIFY) + { + /* mtime fact */ + struct tm* tm = gmtime(&st->st_mtime); + if (tm == NULL) + return errno; + + session->buffersize += + strftime(session->buffer + session->buffersize, + sizeof(session->buffer) - session->buffersize, + "Modify=%Y%m%d%H%M%S;", tm); + if (session->buffersize == 0) + return EOVERFLOW; + } + + if (session->mlst_flags & SESSION_MLST_PERM) + { + /* permission fact */ + strcpy(session->buffer + session->buffersize, "Perm="); + session->buffersize += strlen("Perm="); + + /* append permission */ + if (S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) + session->buffer[session->buffersize++] = 'a'; + + /* create permission */ + if (S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) + session->buffer[session->buffersize++] = 'c'; + + /* delete permission */ + session->buffer[session->buffersize++] = 'd'; + + /* chdir permission */ + if (S_ISDIR(st->st_mode) && (st->st_mode & S_IXUSR)) + session->buffer[session->buffersize++] = 'e'; + + /* rename permission */ + session->buffer[session->buffersize++] = 'f'; + + /* list permission */ + if (S_ISDIR(st->st_mode) && (st->st_mode & S_IRUSR)) + session->buffer[session->buffersize++] = 'l'; + + /* mkdir permission */ + if (S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) + session->buffer[session->buffersize++] = 'm'; + + /* delete permission */ + if (S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) + session->buffer[session->buffersize++] = 'p'; + + /* read permission */ + if (S_ISREG(st->st_mode) && (st->st_mode & S_IRUSR)) + session->buffer[session->buffersize++] = 'r'; + + /* write permission */ + if (S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) + session->buffer[session->buffersize++] = 'w'; + + session->buffer[session->buffersize++] = ';'; + } + + if (session->mlst_flags & SESSION_MLST_UNIX_MODE) + { + /* unix mode fact */ + mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX | S_ISGID | S_ISUID; + session->buffersize += + sprintf(session->buffer + session->buffersize, "UNIX.mode=0%lo;", + (unsigned long)(st->st_mode & mask)); + } + + /* make sure space precedes name */ + if (session->buffer[session->buffersize - 1] != ' ') + session->buffer[session->buffersize++] = ' '; + } + else if (session->dir_mode != XFER_DIR_NLST) + { + if (session->dir_mode == XFER_DIR_STAT) + session->buffer[session->buffersize++] = ' '; + + /* perms nlinks owner group size */ + session->buffersize += + sprintf(session->buffer + session->buffersize, + "%c%c%c%c%c%c%c%c%c%c %lu 3DS 3DS %lld ", + S_ISREG(st->st_mode) ? '-' : S_ISDIR(st->st_mode) ? 'd' : +#if !defined(_3DS) && !defined(__SWITCH__) + S_ISLNK(st->st_mode) ? 'l' : S_ISCHR(st->st_mode) ? 'c' : S_ISBLK(st->st_mode) ? 'b' : S_ISFIFO(st->st_mode) ? 'p' : S_ISSOCK(st->st_mode) ? 's' : +#endif + '?', + st->st_mode & S_IRUSR ? 'r' : '-', + st->st_mode & S_IWUSR ? 'w' : '-', + st->st_mode & S_IXUSR ? 'x' : '-', + st->st_mode & S_IRGRP ? 'r' : '-', + st->st_mode & S_IWGRP ? 'w' : '-', + st->st_mode & S_IXGRP ? 'x' : '-', + st->st_mode & S_IROTH ? 'r' : '-', + st->st_mode & S_IWOTH ? 'w' : '-', + st->st_mode & S_IXOTH ? 'x' : '-', + (unsigned long)st->st_nlink, + (signed long long)st->st_size); + + /* timestamp */ + struct tm* tm = gmtime(&st->st_mtime); + if (tm) + { + const char* fmt = "%b %e %Y "; + if (session->timestamp > st->st_mtime && session->timestamp - st->st_mtime < (60 * 60 * 24 * 365 / 2)) + { + fmt = "%b %e %H:%M "; + } + + session->buffersize += + strftime(session->buffer + session->buffersize, + sizeof(session->buffer) - session->buffersize, + fmt, tm); + } + else + { + session->buffersize += + sprintf(session->buffer + session->buffersize, "Jan 1 1970 "); + } + } + + if (session->buffersize + len + 2 > sizeof(session->buffer)) + { + /* buffer will overflow */ + return EOVERFLOW; + } + + /* copy path */ + memcpy(session->buffer + session->buffersize, path, len); + len = session->buffersize + len; + session->buffer[len++] = '\r'; + session->buffer[len++] = '\n'; + session->buffersize = len; + + return 0; +} + +/*! fill directory entry + * + * @param[in] session ftp session + * @param[in] st stat data + * @param[in] path path to fill + * @param[in] len path length + * + * @returns errno + */ +static int +ftp_session_fill_dirent(ftp_session_t* session, const struct stat* st, + const char* path, size_t len) +{ + return ftp_session_fill_dirent_type(session, st, path, len, NULL); +} + +/*! transfer loop + * + * Try to transfer as much data as the sockets will allow without blocking + * + * @param[in] session ftp session + */ +static void +ftp_session_transfer(ftp_session_t* session) +{ + int rc; + do + { + rc = session->transfer(session); + } while (rc == 0); +} + +/*! encode a path + * + * @param[in] path path to encode + * @param[in,out] len path length + * @param[in] quotes whether to encode quotes + * + * @returns encoded path + * + * @note The caller must free the returned path + */ +static char* +encode_path(const char* path, + size_t* len, + bool quotes) +{ + bool enc = false; + size_t i, diff = 0; + char *out, *p = (char*)path; + + /* check for \n that needs to be encoded */ + if (memchr(p, '\n', *len) != NULL) + enc = true; + + if (quotes) + { + /* check for " that needs to be encoded */ + p = (char*)path; + do + { + p = memchr(p, '"', path + *len - p); + if (p != NULL) + { + ++p; + ++diff; + } + } while (p != NULL); + } + + /* check if an encode was needed */ + if (!enc && diff == 0) + return strdup(path); + + /* allocate space for encoded path */ + p = out = (char*)malloc(*len + diff); + if (out == NULL) + return NULL; + + /* copy the path while performing encoding */ + for (i = 0; i < *len; ++i) + { + if (*path == '\n') + { + /* encoded \n is \0 */ + *p++ = 0; + } + else if (quotes && *path == '"') + { + /* encoded " is "" */ + *p++ = '"'; + *p++ = '"'; + } + else + *p++ = *path; + ++path; + } + + *len += diff; + return out; +} + +/*! decode a path + * + * @param[in] session ftp session + * @param[in] len command length + */ +static void +decode_path(ftp_session_t* session, + size_t len) +{ + size_t i; + + /* decode \0 from the first command */ + for (i = 0; i < len; ++i) + { + /* this is an encoded \n */ + if (session->cmd_buffer[i] == 0) + session->cmd_buffer[i] = '\n'; + } +} + +/*! fill cdir directory entry + * + * @param[in] session ftp session + * @param[in] path path to fill + * + * @returns errno + */ +static int +ftp_session_fill_dirent_cdir(ftp_session_t* session, const char* path) +{ + int rc; + struct stat st; + char* buffer; + size_t len; + + rc = stat(path, &st); + /* double-check this was a directory */ + if (rc == 0 && !S_ISDIR(st.st_mode)) + { + /* shouldn't happen but just in case */ + rc = -1; + errno = ENOTDIR; + } + if (rc != 0) + return errno; + + /* encode \n in path */ + len = strlen(path); + buffer = encode_path(path, &len, false); + if (!buffer) + return ENOMEM; + + /* fill dirent with listed directory as type=cdir */ + rc = ftp_session_fill_dirent_type(session, &st, buffer, len, "cdir"); + free(buffer); + + return rc; +} + +/*! send a response on the command socket + * + * @param[in] session ftp session + * @param[in] buffer buffer to send + * @param[in] len buffer length + */ +static void +ftp_send_response_buffer(ftp_session_t* session, + const char* buffer, + size_t len) +{ + ssize_t rc, to_send; + + if (session->cmd_fd < 0) + return; + + /* send response */ + to_send = len; + console_print(GREEN "%s" RESET, buffer); + rc = send(session->cmd_fd, buffer, to_send, 0); + if (rc < 0) + { + console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_cmd(session); + } + else if (rc != to_send) + { + console_print(RED "only sent %u/%u bytes\n" RESET, + (unsigned int)rc, (unsigned int)to_send); + ftp_session_close_cmd(session); + } +} + +__attribute__((format(printf, 3, 4))) +/*! send ftp response to ftp session's peer + * + * @param[in] session ftp session + * @param[in] code response code + * @param[in] fmt format string + * @param[in] ... format arguments + */ +static void +ftp_send_response(ftp_session_t* session, + int code, + const char* fmt, ...) +{ + static char buffer[CMD_BUFFERSIZE]; + ssize_t rc; + va_list ap; + + if (session->cmd_fd < 0) + return; + + /* print response code and message to buffer */ + va_start(ap, fmt); + if (code > 0) + rc = sprintf(buffer, "%d ", code); + else + rc = sprintf(buffer, "%d-", -code); + rc += vsnprintf(buffer + rc, sizeof(buffer) - rc, fmt, ap); + va_end(ap); + + if (rc >= sizeof(buffer)) + { + /* couldn't fit message; just send code */ + console_print(RED "%s: buffersize too small\n" RESET, __func__); + if (code > 0) + rc = sprintf(buffer, "%d \r\n", code); + else + rc = sprintf(buffer, "%d-\r\n", -code); + } + + ftp_send_response_buffer(session, buffer, rc); +} + +/*! destroy ftp session + * + * @param[in] session ftp session + * + * @returns the next session in the list + */ +static ftp_session_t* +ftp_session_destroy(ftp_session_t* session) +{ + ftp_session_t* next = session->next; + + /* close all sockets/files */ + ftp_session_close_cmd(session); + ftp_session_close_pasv(session); + ftp_session_close_data(session); + ftp_session_close_file(session); + ftp_session_close_cwd(session); + + /* unlink from sessions list */ + if (session->next) + session->next->prev = session->prev; + if (session == sessions) + sessions = session->next; + else + { + session->prev->next = session->next; + if (session == sessions->prev) + sessions->prev = session->prev; + } + + /* deallocate */ + free(session); + + return next; +} + +/*! allocate new ftp session + * + * @param[in] listen_fd socket to accept connection from + */ +static int +ftp_session_new(int listen_fd) +{ + ssize_t rc; + int new_fd; + ftp_session_t* session; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + + /* accept connection */ + new_fd = accept(listen_fd, (struct sockaddr*)&addr, &addrlen); + if (new_fd < 0) + { + console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + console_print(CYAN "accepted connection from %s:%u\n" RESET, + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + /* allocate a new session */ + session = (ftp_session_t*)calloc(1, sizeof(ftp_session_t)); + if (session == NULL) + { + console_print(RED "failed to allocate session\n" RESET); + ftp_closesocket(new_fd, true); + return -1; + } + + /* initialize session */ + strcpy(session->cwd, "/"); + session->peer_addr.sin_addr.s_addr = INADDR_ANY; + session->cmd_fd = new_fd; + session->pasv_fd = -1; + session->data_fd = -1; + session->mlst_flags = SESSION_MLST_TYPE | SESSION_MLST_SIZE | SESSION_MLST_MODIFY | SESSION_MLST_PERM; + session->state = COMMAND_STATE; + session->user_ok = false; + session->pass_ok = false; + session->led = true; + + /* link to the sessions list */ + if (sessions == NULL) + { + sessions = session; + session->prev = session; + } + else + { + sessions->prev->next = session; + session->prev = sessions->prev; + sessions->prev = session; + } + + /* copy socket address to pasv address */ + addrlen = sizeof(session->pasv_addr); + rc = getsockname(new_fd, (struct sockaddr*)&session->pasv_addr, &addrlen); + if (rc != 0) + { + console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); + ftp_send_response(session, 451, "Failed to get connection info\r\n"); + ftp_session_destroy(session); + return -1; + } + + session->cmd_fd = new_fd; + + /* send initiator response */ + ftp_send_response(session, 220, "Hello!\r\n"); + return 0; +} + +/*! accept PASV connection for ftp session + * + * @param[in] session ftp session + * + * @returns -1 for failure + */ +static int +ftp_session_accept(ftp_session_t* session) +{ + int rc, new_fd; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + + if (session->flags & SESSION_PASV) + { + /* clear PASV flag */ + session->flags &= ~SESSION_PASV; + + /* tell the peer that we're ready to accept the connection */ + ftp_send_response(session, 150, "Ready\r\n"); + + /* accept connection from peer */ + new_fd = accept(session->pasv_fd, (struct sockaddr*)&addr, &addrlen); + if (new_fd < 0) + { + console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 425, "Failed to establish connection\r\n"); + return -1; + } + + /* set the socket to non-blocking */ + rc = ftp_set_socket_nonblocking(new_fd); + if (rc != 0) + { + ftp_closesocket(new_fd, true); + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 425, "Failed to establish connection\r\n"); + return -1; + } + + console_print(CYAN "accepted connection from %s:%u\n" RESET, + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + /* we are ready to transfer data */ + ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); + session->data_fd = new_fd; + + return 0; + } + else + { + /* peer didn't send PASV command */ + ftp_send_response(session, 503, "Bad sequence of commands\r\n"); + return -1; + } +} + +/*! connect to peer for ftp session + * + * @param[in] session ftp session + * + * @returns -1 for failure + */ +static int +ftp_session_connect(ftp_session_t* session) +{ + int rc; + + /* clear PORT flag */ + session->flags &= ~SESSION_PORT; + + /* create a new socket */ + session->data_fd = socket(AF_INET, SOCK_STREAM, 0); + if (session->data_fd < 0) + { + console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + /* set socket options */ + rc = ftp_set_socket_options(session->data_fd); + if (rc != 0) + { + ftp_closesocket(session->data_fd, false); + session->data_fd = -1; + return -1; + } + + /* set socket to non-blocking */ + rc = ftp_set_socket_nonblocking(session->data_fd); + if (rc != 0) + return -1; + + /* connect to peer */ + rc = connect(session->data_fd, (struct sockaddr*)&session->peer_addr, + sizeof(session->peer_addr)); + if (rc != 0) + { + if (errno != EINPROGRESS) + { + console_print(RED "connect: %d %s\n" RESET, errno, strerror(errno)); + ftp_closesocket(session->data_fd, false); + session->data_fd = -1; + return -1; + } + } + else + { + console_print(CYAN "connected to %s:%u\n" RESET, + inet_ntoa(session->peer_addr.sin_addr), + ntohs(session->peer_addr.sin_port)); + + ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); + ftp_send_response(session, 150, "Ready\r\n"); + } + + return 0; +} + +static bool +ftp_auth_oncommand(ftp_session_t* session, const char* command) +{ + if (command && (strcasecmp("USER", command) == 0 || strcasecmp("PASS", command) == 0 || strcasecmp("QUIT", command) == 0)) + { + return true; + } + + return session->user_ok && session->pass_ok; +} + +static void +ftp_auth_check(ftp_session_t* session, const char* user, const char* pass) +{ + char str_user[100]; + ini_gets("User", "user:", "dummy", str_user, sizearray(str_user), CONFIGPATH); + char str_pass[100]; + ini_gets("Password", "password:", "dummy", str_pass, sizearray(str_pass), CONFIGPATH); + char str_anony[100]; + ini_gets("Anonymous", "anonymous:", "dummy", str_anony, sizearray(str_anony), CONFIGPATH); + char str_led[100]; + ini_gets("LED", "led:", "1", str_led, sizearray(str_led), CONFIGPATH); + + session->led = (strcmp("1", str_led) == 0); + + if (strcmp("1", str_anony) == 0) + { + session->user_ok = false; + session->pass_ok = false; + ftp_send_response(session, 230, "OK, Huh Anonymous is that you ???\r\n"); + if (session->led) + { + flash_led_connect(); + } + return; + } + + if (user) + { + if (strcmp(str_user, user) == 0) + { + session->user_ok = true; + } + else + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 430, "Unknown user, Please check /config/sys-ftpd/config.ini\r\n"); + ftp_session_close_cmd(session); + return; + } + } + + if (pass) + { + if (strcmp(str_pass, pass) == 0) + { + session->pass_ok = true; + } + else + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 430, "Wrong password, Please check /config/sys-ftpd/config.ini\r\n"); + ftp_session_close_cmd(session); + return; + } + } + + ftp_session_set_state(session, COMMAND_STATE, 0); + if (ftp_auth_oncommand(session, NULL)) + { + ftp_send_response(session, 230, "OK\r\n"); + if (session->led) + { + flash_led_connect(); + } + return; + } + else + { + ftp_send_response(session, 331, "next step required\r\n"); + return; + } +} + +/*! read command for ftp session + * + * @param[in] session ftp session + * @param[in] events poll events + */ +static void +ftp_session_read_command(ftp_session_t* session, + int events) +{ + char *buffer, *args, *next = NULL; + size_t i, len; + int atmark; + ssize_t rc; + ftp_command_t key, *command; + + /* check out-of-band data */ + if (events & POLLPRI) + { + session->flags |= SESSION_URGENT; + + /* check if we are at the urgent marker */ + atmark = sockatmark(session->cmd_fd); + if (atmark < 0) + { + console_print(RED "sockatmark: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_cmd(session); + return; + } + + if (!atmark) + { + /* discard in-band data */ + rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), 0); + if (rc < 0 && errno != EWOULDBLOCK) + { + console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_cmd(session); + } + + return; + } + + /* retrieve the urgent data */ + rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), MSG_OOB); + if (rc < 0) + { + /* EWOULDBLOCK means out-of-band data is on the way */ + if (errno == EWOULDBLOCK) + return; + + /* error retrieving out-of-band data */ + console_print(RED "recv (oob): %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_cmd(session); + return; + } + + /* reset the command buffer */ + session->cmd_buffersize = 0; + return; + } + + /* prepare to receive data */ + buffer = session->cmd_buffer + session->cmd_buffersize; + len = sizeof(session->cmd_buffer) - session->cmd_buffersize; + if (len == 0) + { + /* error retrieving command */ + console_print(RED "Exceeded command buffer size\n" RESET); + ftp_session_close_cmd(session); + return; + } + + /* retrieve command data */ + rc = recv(session->cmd_fd, buffer, len, 0); + if (rc < 0) + { + /* error retrieving command */ + console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_cmd(session); + return; + } + if (rc == 0) + { + /* peer closed connection */ + debug_print("peer closed connection\n"); + ftp_session_close_cmd(session); + return; + } + else + { + session->cmd_buffersize += rc; + len = sizeof(session->cmd_buffer) - session->cmd_buffersize; + + if (session->flags & SESSION_URGENT) + { + /* look for telnet data mark */ + for (i = 0; i < session->cmd_buffersize; ++i) + { + if ((unsigned char)session->cmd_buffer[i] == 0xF2) + { + /* ignore all data that precedes the data mark */ + if (i < session->cmd_buffersize - 1) + memmove(session->cmd_buffer, session->cmd_buffer + i + 1, len - i - 1); + session->cmd_buffersize -= i + 1; + session->flags &= ~SESSION_URGENT; + break; + } + } + } + + /* loop through commands */ + while (true) + { + /* must have at least enough data for the delimiter */ + if (session->cmd_buffersize < 1) + return; + + /* look for \r\n or \n delimiter */ + for (i = 0; i < session->cmd_buffersize; ++i) + { + if (i < session->cmd_buffersize - 1 && session->cmd_buffer[i] == '\r' && session->cmd_buffer[i + 1] == '\n') + { + /* we found a \r\n delimiter */ + session->cmd_buffer[i] = 0; + next = &session->cmd_buffer[i + 2]; + break; + } + else if (session->cmd_buffer[i] == '\n') + { + /* we found a \n delimiter */ + session->cmd_buffer[i] = 0; + next = &session->cmd_buffer[i + 1]; + break; + } + } + + /* check if a delimiter was found */ + if (i == session->cmd_buffersize) + return; + + /* decode the command */ + decode_path(session, i); + + /* split command from arguments */ + args = buffer = session->cmd_buffer; + while (*args && !isspace((int)*args)) + ++args; + if (*args) + *args++ = 0; + + /* look up the command */ + key.name = buffer; + command = bsearch(&key, ftp_commands, + num_ftp_commands, sizeof(ftp_command_t), + ftp_command_cmp); + + /* update command timestamp */ + session->timestamp = time(NULL); + + /* execute the command */ + if (command == NULL) + { + /* send header */ + ftp_send_response(session, 502, "Invalid command \""); + + /* send command */ + len = strlen(buffer); + buffer = encode_path(buffer, &len, false); + if (buffer != NULL) + ftp_send_response_buffer(session, buffer, len); + else + ftp_send_response_buffer(session, key.name, strlen(key.name)); + free(buffer); + + /* send args (if any) */ + if (*args != 0) + { + ftp_send_response_buffer(session, " ", 1); + + len = strlen(args); + buffer = encode_path(args, &len, false); + if (buffer != NULL) + ftp_send_response_buffer(session, buffer, len); + else + ftp_send_response_buffer(session, args, strlen(args)); + free(buffer); + } + + /* send footer */ + ftp_send_response_buffer(session, "\"\r\n", 3); + } + else if (session->state != COMMAND_STATE) + { + /* only some commands are available during data transfer */ + if (strcasecmp(command->name, "ABOR") != 0 && strcasecmp(command->name, "STAT") != 0 && strcasecmp(command->name, "QUIT") != 0) + { + ftp_send_response(session, 503, "Invalid command during transfer\r\n"); + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_session_close_cmd(session); + } + else + command->handler(session, args); + } + else + { + /* clear RENAME flag for all commands except RNTO */ + if (strcasecmp(command->name, "RNTO") != 0) + session->flags &= ~SESSION_RENAME; + + command->handler(session, args); + } + + /* remove executed command from the command buffer */ + len = session->cmd_buffer + session->cmd_buffersize - next; + if (len > 0) + memmove(session->cmd_buffer, next, len); + session->cmd_buffersize = len; + } + } +} + +/*! poll sockets for ftp session + * + * @param[in] session ftp session + * + * @returns next session + */ +static ftp_session_t* +ftp_session_poll(ftp_session_t* session) +{ + int rc; + struct pollfd pollinfo[2]; + nfds_t nfds = 1; + + /* the first pollfd is the command socket */ + pollinfo[0].fd = session->cmd_fd; + pollinfo[0].events = POLLIN | POLLPRI; + pollinfo[0].revents = 0; + + switch (session->state) + { + case COMMAND_STATE: + /* we are waiting to read a command */ + break; + + case DATA_CONNECT_STATE: + if (session->flags & SESSION_PASV) + { + /* we are waiting for a PASV connection */ + pollinfo[1].fd = session->pasv_fd; + pollinfo[1].events = POLLIN; + } + else + { + /* we are waiting to complete a PORT connection */ + pollinfo[1].fd = session->data_fd; + pollinfo[1].events = POLLOUT; + } + pollinfo[1].revents = 0; + nfds = 2; + break; + + case DATA_TRANSFER_STATE: + /* we need to transfer data */ + pollinfo[1].fd = session->data_fd; + if (session->flags & SESSION_RECV) + pollinfo[1].events = POLLIN; + else + pollinfo[1].events = POLLOUT; + pollinfo[1].revents = 0; + nfds = 2; + break; + } + + /* poll the selected sockets */ + rc = poll(pollinfo, nfds, 0); + if (rc < 0) + { + console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_cmd(session); + } + else if (rc > 0) + { + /* check the command socket */ + if (pollinfo[0].revents != 0) + { + /* handle command */ + if (pollinfo[0].revents & POLL_UNKNOWN) + console_print(YELLOW "cmd_fd: revents=0x%08X\n" RESET, pollinfo[0].revents); + + /* we need to read a new command */ + if (pollinfo[0].revents & (POLLERR | POLLHUP)) + { + debug_print("cmd revents=0x%x\n", pollinfo[0].revents); + ftp_session_close_cmd(session); + } + else if (pollinfo[0].revents & (POLLIN | POLLPRI)) + ftp_session_read_command(session, pollinfo[0].revents); + } + + /* check the data/pasv socket */ + if (nfds > 1 && pollinfo[1].revents != 0) + { + switch (session->state) + { + case COMMAND_STATE: + /* this shouldn't happen? */ + break; + + case DATA_CONNECT_STATE: + if (pollinfo[1].revents & POLL_UNKNOWN) + console_print(YELLOW "pasv_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); + + /* we need to accept the PASV connection */ + if (pollinfo[1].revents & (POLLERR | POLLHUP)) + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 426, "Data connection failed\r\n"); + } + else if (pollinfo[1].revents & POLLIN) + { + if (ftp_session_accept(session) != 0) + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + } + else if (pollinfo[1].revents & POLLOUT) + { + + console_print(CYAN "connected to %s:%u\n" RESET, + inet_ntoa(session->peer_addr.sin_addr), + ntohs(session->peer_addr.sin_port)); + + ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); + ftp_send_response(session, 150, "Ready\r\n"); + } + break; + + case DATA_TRANSFER_STATE: + if (pollinfo[1].revents & POLL_UNKNOWN) + console_print(YELLOW "data_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); + + /* we need to transfer data */ + if (pollinfo[1].revents & (POLLERR | POLLHUP)) + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 426, "Data connection failed\r\n"); + } + else if (pollinfo[1].revents & (POLLIN | POLLOUT)) + ftp_session_transfer(session); + break; + } + } + } + + /* still connected to peer; return next session */ + if (session->cmd_fd >= 0) + return session->next; + + /* disconnected from peer; destroy it and return next session */ + debug_print("disconnected from peer\n"); + if (session->led) + { + flash_led_disconnect(); + } + + return ftp_session_destroy(session); +} + +/* Update free space in status bar */ +static void +update_free_space(void) +{ +#if defined(_3DS) || defined(__SWITCH__) +# define KiB (1024.0) +# define MiB (1024.0 * KiB) +# define GiB (1024.0 * MiB) + char buffer[16]; + struct statvfs st; + double bytes_free; + int rc, len; + + rc = statvfs("sdmc:/", &st); + if (rc != 0) + console_print(RED "statvfs: %d %s\n" RESET, errno, strerror(errno)); + else + { + bytes_free = (double)st.f_bsize * st.f_bfree; + + if (bytes_free < 1000.0) + len = snprintf(buffer, sizeof(buffer), "%.0lfB", bytes_free); + else if (bytes_free < 10.0 * KiB) + len = snprintf(buffer, sizeof(buffer), "%.2lfKiB", floor((bytes_free * 100.0) / KiB) / 100.0); + else if (bytes_free < 100.0 * KiB) + len = snprintf(buffer, sizeof(buffer), "%.1lfKiB", floor((bytes_free * 10.0) / KiB) / 10.0); + else if (bytes_free < 1000.0 * KiB) + len = snprintf(buffer, sizeof(buffer), "%.0lfKiB", floor(bytes_free / KiB)); + else if (bytes_free < 10.0 * MiB) + len = snprintf(buffer, sizeof(buffer), "%.2lfMiB", floor((bytes_free * 100.0) / MiB) / 100.0); + else if (bytes_free < 100.0 * MiB) + len = snprintf(buffer, sizeof(buffer), "%.1lfMiB", floor((bytes_free * 10.0) / MiB) / 10.0); + else if (bytes_free < 1000.0 * MiB) + len = snprintf(buffer, sizeof(buffer), "%.0lfMiB", floor(bytes_free / MiB)); + else if (bytes_free < 10.0 * GiB) + len = snprintf(buffer, sizeof(buffer), "%.2lfGiB", floor((bytes_free * 100.0) / GiB) / 100.0); + else if (bytes_free < 100.0 * GiB) + len = snprintf(buffer, sizeof(buffer), "%.1lfGiB", floor((bytes_free * 10.0) / GiB) / 10.0); + else + len = snprintf(buffer, sizeof(buffer), "%.0lfGiB", floor(bytes_free / GiB)); + + console_set_status("\x1b[0;%dH" GREEN "%s", 50 - len, buffer); + } +#endif +} + +/*! Update status bar */ +static int +update_status(void) +{ +#if defined(_3DS) || defined(__SWITCH__) +// console_set_status("\n" GREEN STATUS_STRING " " +# ifdef ENABLE_LOGGING +// "DEBUG " +# endif + // CYAN "%s:%u" RESET, + // inet_ntoa(serv_addr.sin_addr), + // ntohs(serv_addr.sin_port)); + update_free_space(); +#elif 0 //defined(__SWITCH__) + char hostname[128]; + socklen_t addrlen = sizeof(serv_addr); + int rc; + rc = gethostname(hostname, sizeof(hostname)); + if (rc != 0) + { + console_print(RED "gethostname: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + console_set_status("\n" GREEN STATUS_STRING " test " +# ifdef ENABLE_LOGGING + "DEBUG " +# endif + CYAN "%s:%u" RESET, + hostname, + ntohs(serv_addr.sin_port)); + update_free_space(); +#else + char hostname[128]; + socklen_t addrlen = sizeof(serv_addr); + int rc; + + rc = getsockname(listenfd, (struct sockaddr*)&serv_addr, &addrlen); + if (rc != 0) + { + console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + rc = gethostname(hostname, sizeof(hostname)); + if (rc != 0) + { + console_print(RED "gethostname: %d %s\n" RESET, errno, strerror(errno)); + return -1; + } + + console_set_status(GREEN STATUS_STRING " " +# ifdef ENABLE_LOGGING + "DEBUG " +# endif + YELLOW "IP:" CYAN "%s " YELLOW "Port:" CYAN "%u" RESET, + hostname, + ntohs(serv_addr.sin_port)); +#endif + + return 0; +} + +#ifdef _3DS +/*! Handle apt events + * + * @param[in] type Event type + * @param[in] closure Callback closure + */ +static void +apt_hook(APT_HookType type, + void* closure) +{ + switch (type) + { + case APTHOOK_ONSUSPEND: + case APTHOOK_ONSLEEP: + /* turn on backlight, or you can't see the home menu! */ + if (R_SUCCEEDED(gspLcdInit())) + { + GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH); + gspLcdExit(); + } + break; + + case APTHOOK_ONRESTORE: + case APTHOOK_ONWAKEUP: + /* restore backlight power state */ + if (R_SUCCEEDED(gspLcdInit())) + { + (lcd_power ? GSPLCD_PowerOnBacklight : GSPLCD_PowerOffBacklight)(GSPLCD_SCREEN_BOTH); + gspLcdExit(); + } + break; + + default: + break; + } +} +#elif defined(__SWITCH__) +/*! Handle applet events + * + * @param[in] type Event type + * @param[in] closure Callback closure + */ +static void +applet_hook(AppletHookType type, + void* closure) +{ + (void)closure; + (void)type; + /* stubbed for now */ + switch (type) + { + default: + break; + } +} +#endif + +void ftp_pre_init(void) +{ + start_time = time(NULL); + + /* register applet hook */ + appletHook(&cookie, applet_hook, NULL); +} + +/*! initialize ftp subsystem */ +int ftp_init(void) +{ + int rc = 0; + + /* allocate socket to listen for clients */ + listenfd = socket(AF_INET, SOCK_STREAM, 0); + if (listenfd < 0) + { + console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); + ftp_exit(); + return -1; + } + + /* get address to listen on */ + serv_addr.sin_family = AF_INET; + + serv_addr.sin_addr.s_addr = INADDR_ANY; + char str_port[100]; + ini_gets("Port", "port:", "dummy", str_port, sizearray(str_port), CONFIGPATH); + LISTEN_PORT = atoi(str_port); + serv_addr.sin_port = htons(LISTEN_PORT); + + /* reuse address */ + { + int yes = 1; + rc = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + if (rc != 0) + { + console_print(RED "setsockopt: %d %s\n" RESET, errno, strerror(errno)); + ftp_exit(); + return -1; + } + } + + /* bind socket to listen address */ + rc = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + if (rc != 0) + { + console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); + ftp_exit(); + return -1; + } + + /* listen on socket */ + rc = listen(listenfd, 5); + if (rc != 0) + { + console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); + ftp_exit(); + return -1; + } + + /* print server address */ + rc = update_status(); + if (rc != 0) + { + ftp_exit(); + return -1; + } + + return 0; +} + +/*! deinitialize ftp subsystem */ +void ftp_exit(void) +{ + + debug_print("exiting ftp server\n"); + + /* clean up all sessions */ + while (sessions != NULL) + ftp_session_destroy(sessions); + + /* stop listening for new clients */ + if (listenfd >= 0) + ftp_closesocket(listenfd, false); + + /* deinitialize socket driver */ + console_render(); + console_print(CYAN "Waiting for socketExit()...\n" RESET); +} + +void ftp_post_exit(void) +{ +} + +/*! ftp look + * + * @returns whether to keep looping + */ +loop_status_t +ftp_loop(void) +{ + int rc; + struct pollfd pollinfo; + ftp_session_t* session; + + /* we will poll for new client connections */ + pollinfo.fd = listenfd; + pollinfo.events = POLLIN; + pollinfo.revents = 0; + + /* poll for a new client */ + rc = poll(&pollinfo, 1, 0); + if (rc < 0) + { + /* wifi got disabled */ + console_print(RED "poll: FAILED!\n" RESET); + + if (errno == ENETDOWN) + return LOOP_RESTART; + + console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); + return LOOP_EXIT; + } + else if (rc > 0) + { + if (pollinfo.revents & POLLIN) + { + /* we got a new client */ + if (ftp_session_new(listenfd) != 0) + { + return LOOP_RESTART; + } + } + else + { + console_print(YELLOW "listenfd: revents=0x%08X\n" RESET, pollinfo.revents); + } + } + + /* poll each session */ + session = sessions; + while (session != NULL) + session = ftp_session_poll(session); + +#ifdef _3DS + /* check if the user wants to exit */ + hidScanInput(); + u32 down = hidKeysDown(); + + if (down & KEY_B) + return LOOP_EXIT; + + /* check if the user wants to toggle the LCD power */ + if (down & KEY_START) + { + lcd_power = !lcd_power; + apt_hook(APTHOOK_ONRESTORE, NULL); + } +#elif defined(__SWITCH__) + /* check if the user wants to exit */ +#endif + + return LOOP_CONTINUE; +} + +/*! change to parent directory + * + * @param[in] session ftp session + */ +static void +cd_up(ftp_session_t* session) +{ + char *slash = NULL, *p; + + /* remove basename from cwd */ + for (p = session->cwd; *p; ++p) + { + if (*p == '/') + slash = p; + } + *slash = 0; + if (strlen(session->cwd) == 0) + strcat(session->cwd, "/"); +} + +/*! validate a path + * + * @param[in] args path to validate + */ +static int +validate_path(const char* args) +{ + const char* p; + + /* make sure no path components are '..' */ + p = args; + while ((p = strstr(p, "/..")) != NULL) + { + if (p[3] == 0 || p[3] == '/') + return -1; + } + + /* make sure there are no '//' */ + if (strstr(args, "//") != NULL) + return -1; + + return 0; +} + +/*! get a path relative to cwd + * + * @param[in] session ftp session + * @param[in] cwd working directory + * @param[in] args path to make + * + * @returns error + * + * @note the output goes to session->buffer + */ +static int +build_path(ftp_session_t* session, + const char* cwd, + const char* args) +{ + int rc; + char* p; + + session->buffersize = 0; + memset(session->buffer, 0, sizeof(session->buffer)); + + /* make sure the input is a valid path */ + if (validate_path(args) != 0) + { + errno = EINVAL; + return -1; + } + + if (args[0] == '/') + { + /* this is an absolute path */ + size_t len = strlen(args); + if (len > sizeof(session->buffer) - 1) + { + errno = ENAMETOOLONG; + return -1; + } + + memcpy(session->buffer, args, len); + session->buffersize = len; + } + else + { + /* this is a relative path */ + if (strcmp(cwd, "/") == 0) + rc = snprintf(session->buffer, sizeof(session->buffer), "/%s", + args); + else + rc = snprintf(session->buffer, sizeof(session->buffer), "%s/%s", + cwd, args); + + if (rc >= sizeof(session->buffer)) + { + errno = ENAMETOOLONG; + return -1; + } + + session->buffersize = rc; + } + + /* remove trailing / */ + p = session->buffer + session->buffersize; + while (p > session->buffer && *--p == '/') + { + *p = 0; + --session->buffersize; + } + + /* if we ended with an empty path, it is the root directory */ + if (session->buffersize == 0) + session->buffer[session->buffersize++] = '/'; + + return 0; +} + +/*! transfer a directory listing + * + * @param[in] session ftp session + * + * @returns whether to call again + */ +static loop_status_t +list_transfer(ftp_session_t* session) +{ + ssize_t rc; + size_t len; + char* buffer; + struct stat st; + struct dirent* dent; + + /* check if we sent all available data */ + if (session->bufferpos == session->buffersize) + { + /* check xfer dir type */ + if (session->dir_mode == XFER_DIR_STAT) + rc = 213; + else + rc = 226; + + /* check if this was for a file */ + if (session->dp == NULL) + { + /* we already sent the file's listing */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, rc, "OK\r\n"); + return LOOP_EXIT; + } + + /* get the next directory entry */ + dent = readdir(session->dp); + if (dent == NULL) + { + /* we have exhausted the directory listing */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, rc, "OK\r\n"); + return LOOP_EXIT; + } + + /* TODO I think we are supposed to return entries for . and .. */ + if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) + return LOOP_CONTINUE; + + /* check if this was a NLST */ + if (session->dir_mode == XFER_DIR_NLST) + { + /* NLST gives the whole path name */ + session->buffersize = 0; + if (build_path(session, session->lwd, dent->d_name) == 0) + { + /* encode \n in path */ + len = session->buffersize; + buffer = encode_path(session->buffer, &len, false); + if (buffer != NULL) + { + /* copy to the session buffer to send */ + memcpy(session->buffer, buffer, len); + free(buffer); + session->buffer[len++] = '\r'; + session->buffer[len++] = '\n'; + session->buffersize = len; + } + } + } + else + { +#ifdef _3DS + /* the sdmc directory entry already has the type and size, so no need to do a slow stat */ + u32 magic = *(u32*)session->dp->dirData->dirStruct; + + if (magic == SDMC_DIRITER_MAGIC) + { + sdmc_dir_t* dir = (sdmc_dir_t*)session->dp->dirData->dirStruct; + FS_DirectoryEntry* entry = &dir->entry_data[dir->index]; + + if (entry->attributes & FS_ATTRIBUTE_DIRECTORY) + st.st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; + else + st.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + + if (!(entry->attributes & FS_ATTRIBUTE_READ_ONLY)) + st.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; + + st.st_size = entry->fileSize; + st.st_mtime = 0; + + bool getmtime = true; + if (session->dir_mode == XFER_DIR_MLSD || session->dir_mode == XFER_DIR_MLST) + { + if (!(session->mlst_flags & SESSION_MLST_MODIFY)) + getmtime = false; + } + else if (session->dir_mode == XFER_DIR_NLST) + getmtime = false; + + if ((rc = build_path(session, session->lwd, dent->d_name)) != 0) + console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); + else if (getmtime) + { + uint64_t mtime = 0; + if ((rc = sdmc_getmtime(session->buffer, &mtime)) != 0) + console_print(RED "sdmc_getmtime '%s': 0x%x\n" RESET, session->buffer, rc); + else + st.st_mtime = mtime; + } + } + else + { + /* lstat the entry */ + if ((rc = build_path(session, session->lwd, dent->d_name)) != 0) + console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); + else if ((rc = lstat(session->buffer, &st)) != 0) + console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + + if (rc != 0) + { + /* an error occurred */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "unavailable\r\n"); + return LOOP_EXIT; + } + } +#else + /* lstat the entry */ + if ((rc = build_path(session, session->lwd, dent->d_name)) != 0) + console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); + else if ((rc = lstat(session->buffer, &st)) != 0) + console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + + if (rc != 0) + { + /* an error occurred */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "unavailable\r\n"); + return LOOP_EXIT; + } +#endif + /* encode \n in path */ + len = strlen(dent->d_name); + buffer = encode_path(dent->d_name, &len, false); + if (buffer != NULL) + { + rc = ftp_session_fill_dirent(session, &st, buffer, len); + free(buffer); + if (rc != 0) + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 425, "%s\r\n", strerror(rc)); + return LOOP_EXIT; + } + } + else + session->buffersize = 0; + } + session->bufferpos = 0; + } + + /* send any pending data */ + rc = send(session->data_fd, session->buffer + session->bufferpos, + session->buffersize - session->bufferpos, 0); + if (rc <= 0) + { + /* error sending data */ + if (rc < 0) + { + if (errno == EWOULDBLOCK) + return LOOP_EXIT; + console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); + } + else + console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); + + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 426, "Connection broken during transfer\r\n"); + return LOOP_EXIT; + } + + /* we can try to send more data */ + session->bufferpos += rc; + return LOOP_CONTINUE; +} + +/*! send a file to the client + * + * @param[in] session ftp session + * + * @returns whether to call again + */ +static loop_status_t +retrieve_transfer(ftp_session_t* session) +{ + ssize_t rc; + + if (session->bufferpos == session->buffersize) + { + /* we have sent all the data so read some more */ + rc = ftp_session_read_file(session); + if (rc <= 0) + { + /* can't read any more data */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + if (rc < 0) + ftp_send_response(session, 451, "Failed to read file\r\n"); + else + ftp_send_response(session, 226, "OK\r\n"); + return LOOP_EXIT; + } + + /* we read some data so reset the session buffer to send */ + session->bufferpos = 0; + session->buffersize = rc; + } + + /* send any pending data */ + size_t send_size = session->buffersize - session->bufferpos; + if (send_size > 0x1000) + send_size = 0x1000; + rc = send(session->data_fd, session->buffer + session->bufferpos, + send_size, 0); + if (rc <= 0) + { + /* error sending data */ + if (rc < 0) + { + if (errno == EWOULDBLOCK) + return LOOP_EXIT; + console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); + } + else + console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); + + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 426, "Connection broken during transfer\r\n"); + return LOOP_EXIT; + } + + /* we can try to send more data */ + session->bufferpos += rc; + return LOOP_CONTINUE; +} + +/*! send a file to the client + * + * @param[in] session ftp session + * + * @returns whether to call again + */ +static loop_status_t +store_transfer(ftp_session_t* session) +{ + ssize_t rc; + + if (session->bufferpos == session->buffersize) + { + /* we have written all the received data, so try to get some more */ + rc = recv(session->data_fd, session->buffer, sizeof(session->buffer), 0); + if (rc <= 0) + { + /* can't read any more data */ + if (rc < 0) + { + if (errno == EWOULDBLOCK) + return LOOP_EXIT; + console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); + } + + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + + if (rc == 0) + ftp_send_response(session, 226, "OK\r\n"); + else + ftp_send_response(session, 426, "Connection broken during transfer\r\n"); + return LOOP_EXIT; + } + + /* we received some data so reset the session buffer to write */ + session->bufferpos = 0; + session->buffersize = rc; + } + + rc = ftp_session_write_file(session); + if (rc <= 0) + { + /* error writing data */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 451, "Failed to write file\r\n"); + return LOOP_EXIT; + } + + /* we can try to receive more data */ + session->bufferpos += rc; + return LOOP_CONTINUE; +} + +/*! ftp_xfer_file mode */ +typedef enum +{ + XFER_FILE_RETR, /*!< Retrieve a file */ + XFER_FILE_STOR, /*!< Store a file */ + XFER_FILE_APPE, /*!< Append a file */ +} xfer_file_mode_t; + +/*! Transfer a file + * + * @param[in] session ftp session + * @param[in] args ftp arguments + * @param[in] mode transfer mode + * + * @returns failure + */ +static void +ftp_xfer_file(ftp_session_t* session, + const char* args, + xfer_file_mode_t mode) +{ + int rc; + + /* build the path of the file to transfer */ + if (build_path(session, session->cwd, args) != 0) + { + rc = errno; + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 553, "%s\r\n", strerror(rc)); + return; + } + + /* open the file for retrieving or storing */ + if (mode == XFER_FILE_RETR) + rc = ftp_session_open_file_read(session); + else + rc = ftp_session_open_file_write(session, mode == XFER_FILE_APPE); + + if (rc != 0) + { + /* error opening the file */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 450, "failed to open file\r\n"); + return; + } + + if (session->flags & (SESSION_PORT | SESSION_PASV)) + { + ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); + + if (session->flags & SESSION_PORT) + { + /* setup connection */ + rc = ftp_session_connect(session); + if (rc != 0) + { + /* error connecting */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 425, "can't open data connection\r\n"); + return; + } + } + + /* set up the transfer */ + session->flags &= ~(SESSION_RECV | SESSION_SEND); + if (mode == XFER_FILE_RETR) + { + session->flags |= SESSION_SEND; + session->transfer = retrieve_transfer; + } + else + { + session->flags |= SESSION_RECV; + session->transfer = store_transfer; + } + + session->bufferpos = 0; + session->buffersize = 0; + + return; + } + + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 503, "Bad sequence of commands\r\n"); +} + +/*! Transfer a directory + * + * @param[in] session ftp session + * @param[in] args ftp arguments + * @param[in] mode transfer mode + * @param[in] workaround whether to workaround LIST -a + */ +static void +ftp_xfer_dir(ftp_session_t* session, + const char* args, + xfer_dir_mode_t mode, + bool workaround) +{ + ssize_t rc; + size_t len; + struct stat st; + char* buffer; + + /* set up the transfer */ + session->dir_mode = mode; + session->flags &= ~SESSION_RECV; + session->flags |= SESSION_SEND; + + session->transfer = list_transfer; + session->buffersize = 0; + session->bufferpos = 0; + + if (strlen(args) > 0) + { + /* an argument was provided */ + if (build_path(session, session->cwd, args) != 0) + { + /* error building path */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "%s\r\n", strerror(errno)); + return; + } + + /* check if this is a directory */ + session->dp = opendir(session->buffer); + if (session->dp == NULL) + { + /* not a directory; check if it is a file */ + rc = stat(session->buffer, &st); + if (rc != 0) + { + /* error getting stat */ + rc = errno; + + /* work around broken clients that think LIST -a is valid */ + if (workaround && mode == XFER_DIR_LIST) + { + if (args[0] == '-' && (args[1] == 'a' || args[1] == 'l')) + { + if (args[2] == 0) + buffer = strdup(args + 2); + else + buffer = strdup(args + 3); + + if (buffer != NULL) + { + ftp_xfer_dir(session, buffer, mode, false); + free(buffer); + return; + } + + rc = ENOMEM; + } + } + + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "%s\r\n", strerror(rc)); + return; + } + else if (mode == XFER_DIR_MLSD) + { + /* specified file instead of directory for MLSD */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); + return; + } + else if (mode == XFER_DIR_NLST) + { + /* NLST uses full path name */ + len = session->buffersize; + buffer = encode_path(session->buffer, &len, false); + } + else + { + /* everything else uses base name */ + const char* base = strrchr(session->buffer, '/') + 1; + + len = strlen(base); + buffer = encode_path(base, &len, false); + } + + if (buffer) + { + rc = ftp_session_fill_dirent(session, &st, buffer, len); + free(buffer); + } + else + rc = ENOMEM; + + if (rc != 0) + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "%s\r\n", strerror(rc)); + return; + } + } + else + { + /* it was a directory, so set it as the lwd */ + memcpy(session->lwd, session->buffer, session->buffersize); + session->lwd[session->buffersize] = 0; + session->buffersize = 0; + + if (session->dir_mode == XFER_DIR_MLSD && (session->mlst_flags & SESSION_MLST_TYPE)) + { + /* send this directory as type=cdir */ + rc = ftp_session_fill_dirent_cdir(session, session->lwd); + if (rc != 0) + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "%s\r\n", strerror(rc)); + return; + } + } + } + } + else if (ftp_session_open_cwd(session) != 0) + { + /* no argument, but opening cwd failed */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "%s\r\n", strerror(errno)); + return; + } + else + { + /* set the cwd as the lwd */ + strcpy(session->lwd, session->cwd); + session->buffersize = 0; + + if (session->dir_mode == XFER_DIR_MLSD && (session->mlst_flags & SESSION_MLST_TYPE)) + { + /* send this directory as type=cdir */ + rc = ftp_session_fill_dirent_cdir(session, session->lwd); + if (rc != 0) + { + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "%s\r\n", strerror(rc)); + return; + } + } + } + + if (mode == XFER_DIR_MLST || mode == XFER_DIR_STAT) + { + /* this is a little different; we have to send the data over the command socket */ + ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV | CLOSE_DATA); + session->data_fd = session->cmd_fd; + session->flags |= SESSION_SEND; + ftp_send_response(session, -213, "Status\r\n"); + return; + } + else if (session->flags & (SESSION_PORT | SESSION_PASV)) + { + ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); + + if (session->flags & SESSION_PORT) + { + /* setup connection */ + rc = ftp_session_connect(session); + if (rc != 0) + { + /* error connecting */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 425, "can't open data connection\r\n"); + } + } + + return; + } + + /* we must have got LIST/MLSD/MLST/NLST without a preceding PORT or PASV */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 503, "Bad sequence of commands\r\n"); +} + +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * + * * + * F T P C O M M A N D S * + * * + * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +/*! @fn static void ABOR(ftp_session_t *session, const char *args) + * + * @brief abort a transfer + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(ABOR) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + if (session->state == COMMAND_STATE) + { + ftp_send_response(session, 225, "No transfer to abort\r\n"); + return; + } + + /* abort the transfer */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + + /* send response for this request */ + ftp_send_response(session, 225, "Aborted\r\n"); + + /* send response for transfer */ + ftp_send_response(session, 425, "Transfer aborted\r\n"); +} + +/*! @fn static void ALLO(ftp_session_t *session, const char *args) + * + * @brief allocate space + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(ALLO) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + ftp_send_response(session, 202, "superfluous command\r\n"); +} + +/*! @fn static void APPE(ftp_session_t *session, const char *args) + * + * @brief append data to a file + * + * @note requires a PASV or PORT connection + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(APPE) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* open the file in append mode */ + ftp_xfer_file(session, args, XFER_FILE_APPE); +} + +/*! @fn static void CDUP(ftp_session_t *session, const char *args) + * + * @brief CWD to parent directory + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(CDUP) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* change to parent directory */ + cd_up(session); + + ftp_send_response(session, 200, "OK\r\n"); +} + +/*! @fn static void CWD(ftp_session_t *session, const char *args) + * + * @brief change working directory + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(CWD) +{ + struct stat st; + int rc; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* .. is equivalent to CDUP */ + if (strcmp(args, "..") == 0) + { + cd_up(session); + ftp_send_response(session, 200, "OK\r\n"); + return; + } + + /* build the new cwd path */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 553, "%s\r\n", strerror(errno)); + return; + } + + /* get the path status */ + rc = stat(session->buffer, &st); + if (rc != 0) + { + console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + ftp_send_response(session, 550, "unavailable\r\n"); + return; + } + + /* make sure it is a directory */ + if (!S_ISDIR(st.st_mode)) + { + ftp_send_response(session, 553, "not a directory\r\n"); + return; + } + + /* copy the path into the cwd */ + strncpy(session->cwd, session->buffer, sizeof(session->cwd)); + + ftp_send_response(session, 200, "OK\r\n"); +} + +/*! @fn static void DELE(ftp_session_t *session, const char *args) + * + * @brief delete a file + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(DELE) +{ + int rc; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* build the file path */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 553, "%s\r\n", strerror(errno)); + return; + } + + /* try to unlink the path */ + rc = unlink(session->buffer); + if (rc != 0) + { + /* error unlinking the file */ + console_print(RED "unlink: %d %s\n" RESET, errno, strerror(errno)); + ftp_send_response(session, 550, "failed to delete file\r\n"); + return; + } + + update_free_space(); + ftp_send_response(session, 250, "OK\r\n"); +} + +/*! @fn static void FEAT(ftp_session_t *session, const char *args) + * + * @brief list server features + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(FEAT) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* list our features */ + ftp_send_response(session, -211, "\r\n" + " MDTM\r\n" + " MLST Type%s;Size%s;Modify%s;Perm%s;UNIX.mode%s;\r\n" + " PASV\r\n" + " SIZE\r\n" + " TVFS\r\n" + " UTF8\r\n" + "\r\n" + "211 End\r\n", + session->mlst_flags & SESSION_MLST_TYPE ? "*" : "", + session->mlst_flags & SESSION_MLST_SIZE ? "*" : "", + session->mlst_flags & SESSION_MLST_MODIFY ? "*" : "", + session->mlst_flags & SESSION_MLST_PERM ? "*" : "", + session->mlst_flags & SESSION_MLST_UNIX_MODE ? "*" : ""); +} + +/*! @fn static void HELP(ftp_session_t *session, const char *args) + * + * @brief print server help + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(HELP) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* list our accepted commands */ + ftp_send_response(session, -214, + "The following commands are recognized\r\n" + " ABOR ALLO APPE CDUP CWD DELE FEAT HELP LIST MDTM MKD MLSD MLST MODE\r\n" + " NLST NOOP OPTS PASS PASV PORT PWD QUIT REST RETR RMD RNFR RNTO STAT\r\n" + " STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD XPWD XRMD\r\n" + "214 End\r\n"); +} + +/*! @fn static void LIST(ftp_session_t *session, const char *args) + * + * @brief retrieve a directory listing + * + * @note Requires a PORT or PASV connection + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(LIST) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* open the path in LIST mode */ + ftp_xfer_dir(session, args, XFER_DIR_LIST, true); +} + +/*! @fn static void MDTM(ftp_session_t *session, const char *args) + * + * @brief get last modification time + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(MDTM) +{ + int rc; +#ifdef _3DS + uint64_t mtime; +#else + struct stat st; +#endif + time_t t_mtime; + struct tm* tm; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* build the path */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 553, "%s\r\n", strerror(errno)); + return; + } + +#ifdef _3DS + rc = sdmc_getmtime(session->buffer, &mtime); + if (rc != 0) + { + ftp_send_response(session, 550, "Error getting mtime\r\n"); + return; + } + t_mtime = mtime; +#else + rc = stat(session->buffer, &st); + if (rc != 0) + { + ftp_send_response(session, 550, "Error getting mtime\r\n"); + return; + } + t_mtime = st.st_mtime; +#endif + + tm = gmtime(&t_mtime); + if (tm == NULL) + { + ftp_send_response(session, 550, "Error getting mtime\r\n"); + return; + } + + session->buffersize = strftime(session->buffer, sizeof(session->buffer), "%Y%m%d%H%M%S", tm); + if (session->buffersize == 0) + { + ftp_send_response(session, 550, "Error getting mtime\r\n"); + return; + } + + session->buffer[session->buffersize] = 0; + + ftp_send_response(session, 213, "%s\r\n", session->buffer); +} +/*! @fn static void MKD(ftp_session_t *session, const char *args) + * + * @brief create a directory + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(MKD) +{ + int rc; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* build the path */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 553, "%s\r\n", strerror(errno)); + return; + } + + /* try to create the directory */ + rc = mkdir(session->buffer, 0755); + if (rc != 0 && errno != EEXIST) + { + /* mkdir failure */ + console_print(RED "mkdir: %d %s\n" RESET, errno, strerror(errno)); + ftp_send_response(session, 550, "failed to create directory\r\n"); + return; + } + + update_free_space(); + ftp_send_response(session, 250, "OK\r\n"); +} + +/*! @fn static void MLSD(ftp_session_t *session, const char *args) + * + * @brief set transfer mode + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(MLSD) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* open the path in MLSD mode */ + ftp_xfer_dir(session, args, XFER_DIR_MLSD, true); +} + +/*! @fn static void MLST(ftp_session_t *session, const char *args) + * + * @brief set transfer mode + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(MLST) +{ + struct stat st; + int rc; + char* path; + size_t len; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* build the path */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 501, "%s\r\n", strerror(errno)); + return; + } + + /* stat path */ + rc = lstat(session->buffer, &st); + if (rc != 0) + { + ftp_send_response(session, 550, "%s\r\n", strerror(errno)); + return; + } + + /* encode \n in path */ + len = session->buffersize; + path = encode_path(session->buffer, &len, true); + if (!path) + { + ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); + return; + } + + session->dir_mode = XFER_DIR_MLST; + rc = ftp_session_fill_dirent(session, &st, path, len); + free(path); + if (rc != 0) + { + ftp_send_response(session, 550, "%s\r\n", strerror(errno)); + return; + } + + path = malloc(session->buffersize + 1); + if (!path) + { + ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); + return; + } + + memcpy(path, session->buffer, session->buffersize); + path[session->buffersize] = 0; + ftp_send_response(session, -250, "Status\r\n%s250 End\r\n", path); + free(path); +} + +/*! @fn static void MODE(ftp_session_t *session, const char *args) + * + * @brief set transfer mode + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(MODE) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* we only accept S (stream) mode */ + if (strcasecmp(args, "S") == 0) + { + ftp_send_response(session, 200, "OK\r\n"); + return; + } + + ftp_send_response(session, 504, "unavailable\r\n"); +} + +/*! @fn static void NLST(ftp_session_t *session, const char *args) + * + * @brief retrieve a name list + * + * @note Requires a PASV or PORT connection + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(NLST) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* open the path in NLST mode */ + return ftp_xfer_dir(session, args, XFER_DIR_NLST, false); +} + +/*! @fn static void NOOP(ftp_session_t *session, const char *args) + * + * @brief no-op + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(NOOP) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* this is a no-op */ + ftp_send_response(session, 200, "OK\r\n"); +} + +/*! @fn static void OPTS(ftp_session_t *session, const char *args) + * + * @brief set options + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(OPTS) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* we accept the following UTF8 options */ + if (strcasecmp(args, "UTF8") == 0 || strcasecmp(args, "UTF8 ON") == 0 || strcasecmp(args, "UTF8 NLST") == 0) + { + ftp_send_response(session, 200, "OK\r\n"); + return; + } + + /* check MLST options */ + if (strncasecmp(args, "MLST ", 5) == 0) + { + static const struct + { + const char* name; + session_mlst_flags_t flag; + } mlst_flags[] = + { + { + "Type;", + SESSION_MLST_TYPE, + }, + { + "Size;", + SESSION_MLST_SIZE, + }, + { + "Modify;", + SESSION_MLST_MODIFY, + }, + { + "Perm;", + SESSION_MLST_PERM, + }, + { + "UNIX.mode;", + SESSION_MLST_UNIX_MODE, + }, + }; + static const size_t num_mlst_flags = sizeof(mlst_flags) / sizeof(mlst_flags[0]); + + session_mlst_flags_t flags = 0; + args += 5; + const char* p = args; + while (*p) + { + for (size_t i = 0; i < num_mlst_flags; ++i) + { + if (strncasecmp(mlst_flags[i].name, p, strlen(mlst_flags[i].name)) == 0) + { + flags |= mlst_flags[i].flag; + p += strlen(mlst_flags[i].name) - 1; + break; + } + } + + while (*p && *p != ';') + ++p; + + if (*p == ';') + ++p; + } + + session->mlst_flags = flags; + ftp_send_response(session, 200, "MLST OPTS%s%s%s%s%s%s\r\n", + flags ? " " : "", + flags & SESSION_MLST_TYPE ? "Type;" : "", + flags & SESSION_MLST_SIZE ? "Size;" : "", + flags & SESSION_MLST_MODIFY ? "Modify;" : "", + flags & SESSION_MLST_PERM ? "Perm;" : "", + flags & SESSION_MLST_UNIX_MODE ? "UNIX.mode;" : ""); + return; + } + + ftp_send_response(session, 504, "invalid argument\r\n"); +} + +/*! @fn static void PASS(ftp_session_t *session, const char *args) + * + * @brief provide password + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(PASS) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + ftp_auth_check(session, NULL, args); +} + +/*! @fn static void PASV(ftp_session_t *session, const char *args) + * + * @brief request an address to connect to + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(PASV) +{ + int rc; + char buffer[INET_ADDRSTRLEN + 10]; + char* p; + in_port_t port; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + memset(buffer, 0, sizeof(buffer)); + + /* reset the state */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + session->flags &= ~(SESSION_PASV | SESSION_PORT); + + /* create a socket to listen on */ + session->pasv_fd = socket(AF_INET, SOCK_STREAM, 0); + if (session->pasv_fd < 0) + { + console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); + ftp_send_response(session, 451, "\r\n"); + return; + } + + /* set the socket options */ + rc = ftp_set_socket_options(session->pasv_fd); + if (rc != 0) + { + /* failed to set socket options */ + ftp_session_close_pasv(session); + ftp_send_response(session, 451, "\r\n"); + return; + } + + /* grab a new port */ + session->pasv_addr.sin_port = htons(next_data_port()); + +#if defined(_3DS) || defined(__SWITCH__) + console_print(YELLOW "binding to %s:%u\n" RESET, + inet_ntoa(session->pasv_addr.sin_addr), + ntohs(session->pasv_addr.sin_port)); +#endif + + /* bind to the port */ + rc = bind(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, + sizeof(session->pasv_addr)); + if (rc != 0) + { + /* failed to bind */ + console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_pasv(session); + ftp_send_response(session, 451, "\r\n"); + return; + } + + /* listen on the socket */ + rc = listen(session->pasv_fd, 1); + if (rc != 0) + { + /* failed to listen */ + console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_pasv(session); + ftp_send_response(session, 451, "\r\n"); + return; + } + +#ifndef _3DS + { + /* get the socket address since we requested an ephemeral port */ + socklen_t addrlen = sizeof(session->pasv_addr); + rc = getsockname(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, + &addrlen); + if (rc != 0) + { + /* failed to get socket address */ + console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); + ftp_session_close_pasv(session); + ftp_send_response(session, 451, "\r\n"); + return; + } + } +#endif + + /* we are now listening on the socket */ + console_print(YELLOW "listening on %s:%u\n" RESET, + inet_ntoa(session->pasv_addr.sin_addr), + ntohs(session->pasv_addr.sin_port)); + session->flags |= SESSION_PASV; + + /* print the address in the ftp format */ + port = ntohs(session->pasv_addr.sin_port); + strcpy(buffer, inet_ntoa(session->pasv_addr.sin_addr)); + sprintf(buffer + strlen(buffer), ",%u,%u", + port >> 8, port & 0xFF); + for (p = buffer; *p; ++p) + { + if (*p == '.') + *p = ','; + } + + ftp_send_response(session, 227, "%s\r\n", buffer); +} + +/*! @fn static void PORT(ftp_session_t *session, const char *args) + * + * @brief provide an address for the server to connect to + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(PORT) +{ + char *addrstr, *p, *portstr; + int commas = 0, rc; + short port = 0; + unsigned long val; + struct sockaddr_in addr; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* reset the state */ + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + session->flags &= ~(SESSION_PASV | SESSION_PORT); + + /* dup the args since they are const and we need to change it */ + addrstr = strdup(args); + if (addrstr == NULL) + { + ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); + return; + } + + /* replace a,b,c,d,e,f with a.b.c.d\0e.f */ + for (p = addrstr; *p; ++p) + { + if (*p == ',') + { + if (commas != 3) + *p = '.'; + else + { + *p = 0; + portstr = p + 1; + } + ++commas; + } + } + + /* make sure we got the right number of values */ + if (commas != 5) + { + free(addrstr); + ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); + return; + } + + /* parse the address */ + rc = inet_aton(addrstr, &addr.sin_addr); + if (rc == 0) + { + free(addrstr); + ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); + return; + } + + /* parse the port */ + val = 0; + port = 0; + for (p = portstr; *p; ++p) + { + if (!isdigit((int)*p)) + { + if (p == portstr || *p != '.' || val > 0xFF) + { + free(addrstr); + ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); + return; + } + port <<= 8; + port += val; + val = 0; + } + else + { + val *= 10; + val += *p - '0'; + } + } + + /* validate the port */ + if (val > 0xFF || port > 0xFF) + { + free(addrstr); + ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); + return; + } + port <<= 8; + port += val; + + /* fill in the address port and family */ + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + + free(addrstr); + + memcpy(&session->peer_addr, &addr, sizeof(addr)); + + /* we are ready to connect to the client */ + session->flags |= SESSION_PORT; + ftp_send_response(session, 200, "OK\r\n"); +} + +/*! @fn static void PWD(ftp_session_t *session, const char *args) + * + * @brief print working directory + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(PWD) +{ + static char buffer[CMD_BUFFERSIZE]; + size_t len = sizeof(buffer), i; + char* path; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* encode the cwd */ + len = strlen(session->cwd); + path = encode_path(session->cwd, &len, true); + if (path != NULL) + { + i = sprintf(buffer, "257 \""); + if (i + len + 3 > sizeof(buffer)) + { + /* buffer will overflow */ + free(path); + ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); + ftp_send_response(session, 550, "unavailable\r\n"); + ftp_send_response(session, 425, "%s\r\n", strerror(EOVERFLOW)); + return; + } + memcpy(buffer + i, path, len); + free(path); + len += i; + buffer[len++] = '"'; + buffer[len++] = '\r'; + buffer[len++] = '\n'; + + ftp_send_response_buffer(session, buffer, len); + return; + } + + ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); +} + +/*! @fn static void QUIT(ftp_session_t *session, const char *args) + * + * @brief terminate ftp session + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(QUIT) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* disconnect from the client */ + ftp_send_response(session, 221, "disconnecting\r\n"); + ftp_session_close_cmd(session); +} + +/*! @fn static void REST(ftp_session_t *session, const char *args) + * + * @brief restart a transfer + * + * @note sets file position for a subsequent STOR operation + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(REST) +{ + const char* p; + uint64_t pos = 0; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* make sure an argument is provided */ + if (args == NULL) + { + ftp_send_response(session, 504, "invalid argument\r\n"); + return; + } + + /* parse the offset */ + for (p = args; *p; ++p) + { + if (!isdigit((int)*p)) + { + ftp_send_response(session, 504, "invalid argument\r\n"); + return; + } + + if (UINT64_MAX / 10 < pos) + { + ftp_send_response(session, 504, "invalid argument\r\n"); + return; + } + + pos *= 10; + + if (UINT64_MAX - (*p - '0') < pos) + { + ftp_send_response(session, 504, "invalid argument\r\n"); + return; + } + + pos += (*p - '0'); + } + + /* set the restart offset */ + session->filepos = pos; + ftp_send_response(session, 200, "OK\r\n"); +} + +/*! @fn static void RETR(ftp_session_t *session, const char *args) + * + * @brief retrieve a file + * + * @note Requires a PASV or PORT connection + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(RETR) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* open the file to retrieve */ + return ftp_xfer_file(session, args, XFER_FILE_RETR); +} + +/*! @fn static void RMD(ftp_session_t *session, const char *args) + * + * @brief remove a directory + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(RMD) +{ + int rc; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* build the path to remove */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 553, "%s\r\n", strerror(errno)); + return; + } + + /* remove the directory */ + rc = rmdir(session->buffer); + if (rc != 0) + { + /* rmdir error */ + console_print(RED "rmdir: %d %s\n" RESET, errno, strerror(errno)); + ftp_send_response(session, 550, "failed to delete directory\r\n"); + return; + } + + update_free_space(); + ftp_send_response(session, 250, "OK\r\n"); +} + +/*! @fn static void RNFR(ftp_session_t *session, const char *args) + * + * @brief rename from + * + * @note Must be followed by RNTO + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(RNFR) +{ + int rc; + struct stat st; + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* build the path to rename from */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 553, "%s\r\n", strerror(errno)); + return; + } + + /* make sure the path exists */ + rc = lstat(session->buffer, &st); + if (rc != 0) + { + /* error getting path status */ + console_print(RED "lstat: %d %s\n" RESET, errno, strerror(errno)); + ftp_send_response(session, 450, "no such file or directory\r\n"); + return; + } + + /* we are ready for RNTO */ + session->flags |= SESSION_RENAME; + ftp_send_response(session, 350, "OK\r\n"); +} + +/*! @fn static void RNTO(ftp_session_t *session, const char *args) + * + * @brief rename to + * + * @note Must be preceded by RNFR + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(RNTO) +{ + static char rnfr[XFER_BUFFERSIZE]; // rename-from buffer + int rc; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* make sure the previous command was RNFR */ + if (!(session->flags & SESSION_RENAME)) + { + ftp_send_response(session, 503, "Bad sequence of commands\r\n"); + return; + } + + /* clear the rename state */ + session->flags &= ~SESSION_RENAME; + + /* copy the RNFR path */ + memcpy(rnfr, session->buffer, XFER_BUFFERSIZE); + + /* build the path to rename to */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 554, "%s\r\n", strerror(errno)); + return; + } + + /* rename the file */ + rc = rename(rnfr, session->buffer); + if (rc != 0) + { + /* rename failure */ + console_print(RED "rename: %d %s\n" RESET, errno, strerror(errno)); + ftp_send_response(session, 550, "failed to rename file/directory\r\n"); + return; + } + + update_free_space(); + ftp_send_response(session, 250, "OK\r\n"); +} + +/*! @fn static void SIZE(ftp_session_t *session, const char *args) + * + * @brief get file size + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(SIZE) +{ + int rc; + struct stat st; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* build the path to stat */ + if (build_path(session, session->cwd, args) != 0) + { + ftp_send_response(session, 553, "%s\r\n", strerror(errno)); + return; + } + + rc = stat(session->buffer, &st); + if (rc != 0 || !S_ISREG(st.st_mode)) + { + ftp_send_response(session, 550, "Could not get file size.\r\n"); + return; + } + + ftp_send_response(session, 213, "%" PRIu64 "\r\n", + (uint64_t)st.st_size); +} + +/*! @fn static void STAT(ftp_session_t *session, const char *args) + * + * @brief get status + * + * @note If no argument is supplied, and a transfer is occurring, get the + * current transfer status. If no argument is supplied, and no transfer + * is occurring, get the server status. If an argument is supplied, this + * is equivalent to LIST, except the data is sent over the command + * socket. + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(STAT) +{ + time_t uptime = time(NULL) - start_time; + int hours = uptime / 3600; + int minutes = (uptime / 60) % 60; + int seconds = uptime % 60; + + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + if (session->state == DATA_CONNECT_STATE) + { + /* we are waiting to connect to the client */ + ftp_send_response(session, -211, "FTP server status\r\n" + " Waiting for data connection\r\n" + "211 End\r\n"); + return; + } + else if (session->state == DATA_TRANSFER_STATE) + { + /* we are in the middle of a transfer */ + ftp_send_response(session, -211, "FTP server status\r\n" + " Transferred %" PRIu64 " bytes\r\n" + "211 End\r\n", + session->filepos); + return; + } + + if (strlen(args) == 0) + { + /* no argument provided, send the server status */ + ftp_send_response(session, -211, "FTP server status\r\n" + " Uptime: %02d:%02d:%02d\r\n" + "211 End\r\n", + hours, minutes, seconds); + return; + } + + /* argument provided, open the path in STAT mode */ + ftp_xfer_dir(session, args, XFER_DIR_STAT, false); +} + +/*! @fn static void STOR(ftp_session_t *session, const char *args) + * + * @brief store a file + * + * @note Requires a PASV or PORT connection + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(STOR) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* open the file to store */ + return ftp_xfer_file(session, args, XFER_FILE_STOR); +} + +/*! @fn static void STOU(ftp_session_t *session, const char *args) + * + * @brief store a unique file + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(STOU) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + /* we do not support this yet */ + ftp_session_set_state(session, COMMAND_STATE, 0); + + ftp_send_response(session, 502, "unavailable\r\n"); +} + +/*! @fn static void STRU(ftp_session_t *session, const char *args) + * + * @brief set file structure + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(STRU) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* we only support F (no structure) mode */ + if (strcasecmp(args, "F") == 0) + { + ftp_send_response(session, 200, "OK\r\n"); + return; + } + + ftp_send_response(session, 504, "unavailable\r\n"); +} + +/*! @fn static void SYST(ftp_session_t *session, const char *args) + * + * @brief identify system + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(SYST) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* we are UNIX compliant with 8-bit characters */ + ftp_send_response(session, 215, "UNIX Type: L8\r\n"); +} + +/*! @fn static void TYPE(ftp_session_t *session, const char *args) + * + * @brief set transfer mode + * + * @note transfer mode is always binary + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(TYPE) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + + ftp_session_set_state(session, COMMAND_STATE, 0); + + /* we always transfer in binary mode */ + ftp_send_response(session, 200, "OK\r\n"); +} + +/*! @fn static void USER(ftp_session_t *session, const char *args) + * + * @brief provide user name + * + * @param[in] session ftp session + * @param[in] args arguments + */ +FTP_DECLARE(USER) +{ + console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); + ftp_auth_check(session, args, NULL); +} diff --git a/source/Sysmodule/source/ftp.h b/source/Sysmodule/source/ftp.h new file mode 100644 index 00000000..fc7eae30 --- /dev/null +++ b/source/Sysmodule/source/ftp.h @@ -0,0 +1,17 @@ +// This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) + +#pragma once + +/*! Loop status */ +typedef enum +{ + LOOP_CONTINUE, /*!< Continue looping */ + LOOP_RESTART, /*!< Reinitialize */ + LOOP_EXIT, /*!< Terminate looping */ +} loop_status_t; + +void ftp_pre_init(void); +int ftp_init(void); +loop_status_t ftp_loop(void); +void ftp_exit(void); +void ftp_post_exit(void); diff --git a/source/Sysmodule/source/led.c b/source/Sysmodule/source/led.c new file mode 100644 index 00000000..cf9b3d43 --- /dev/null +++ b/source/Sysmodule/source/led.c @@ -0,0 +1,56 @@ +#include "led.h" +#include +#include + +#include "util.h" + +void flash_led_connect() +{ + HidsysNotificationLedPattern pattern; + memset(&pattern, 0, sizeof(pattern)); + + // Setup Breathing effect pattern data. + pattern.baseMiniCycleDuration = 0x8; // 100ms. + pattern.totalMiniCycles = 0x2; // 3 mini cycles. Last one 12.5ms. + pattern.totalFullCycles = 0x0; // Repeat forever. + pattern.startIntensity = 0x2; // 13%. + + pattern.miniCycles[0].ledIntensity = 0xF; // 100%. + pattern.miniCycles[0].transitionSteps = 0xF; // 15 steps. Transition time 1.5s. + pattern.miniCycles[0].finalStepDuration = 0x0; // Forced 12.5ms. + pattern.miniCycles[1].ledIntensity = 0x2; // 13%. + pattern.miniCycles[1].transitionSteps = 0xF; // 15 steps. Transition time 1.5s. + pattern.miniCycles[1].finalStepDuration = 0x0; // Forced 12.5ms. + + u64 uniquePadIds[5] = {0}; + + s32 total_entries = 0; + + Result rc = hidsysGetUniquePadIds(uniquePadIds, 5, &total_entries); + if (R_FAILED(rc) && rc != MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer)) + fatalThrow(rc); + + for (int i = 0; i < total_entries; i++) + { + hidsysSetNotificationLedPattern(&pattern, uniquePadIds[i]); + hidsysSetNotificationLedPatternWithTimeout(&pattern, uniquePadIds[i], LED_TIMEOUT); + } +} + +void flash_led_disconnect() +{ + HidsysNotificationLedPattern pattern; + memset(&pattern, 0, sizeof(pattern)); + + u64 uniquePadIds[2]; + memset(uniquePadIds, 0, sizeof(uniquePadIds)); + + s32 total_entries = 0; + + Result rc = hidsysGetUniquePadsFromNpad(hidGetHandheldMode() ? CONTROLLER_HANDHELD : CONTROLLER_PLAYER_1, uniquePadIds, 2, &total_entries); + if (R_FAILED(rc) && rc != MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer)) + fatalThrow(rc); + + for (int i = 0; i < total_entries; i++) + hidsysSetNotificationLedPattern(&pattern, uniquePadIds[i]); +} diff --git a/source/Sysmodule/source/led.h b/source/Sysmodule/source/led.h new file mode 100644 index 00000000..e9ccec4a --- /dev/null +++ b/source/Sysmodule/source/led.h @@ -0,0 +1,5 @@ +#pragma once +#define LED_TIMEOUT 1e+10 + +void flash_led_connect(); +void flash_led_disconnect(); \ No newline at end of file diff --git a/source/Sysmodule/source/main.cpp b/source/Sysmodule/source/main.cpp index 57b1e820..dcb44052 100644 --- a/source/Sysmodule/source/main.cpp +++ b/source/Sysmodule/source/main.cpp @@ -8,6 +8,15 @@ #include "psc_module.h" #include "SwitchHDLHandler.h" +extern "C" { + #include "ftp.h" + #include "util.h" + #include "console.h" + #include "minIni.h" +} + +#include + #define APP_VERSION "0.6.4" // libnx fake heap initialization @@ -20,7 +29,7 @@ extern "C" // We don't need to reserve memory for fsdev, so don't use it. u32 __nx_fsdev_direntry_cache_size = 1; -#define INNER_HEAP_SIZE 0x40'000 +#define INNER_HEAP_SIZE 0xE7'000 size_t nx_inner_heap_size = INNER_HEAP_SIZE; char nx_inner_heap[INNER_HEAP_SIZE]; @@ -55,6 +64,20 @@ namespace ams extern "C" void __appInit(void) { + static const SocketInitConfig socketInitConfig = { + .bsdsockets_version = 1, + + .tcp_tx_buf_size = 0x800, + .tcp_rx_buf_size = 0x800, + .tcp_tx_buf_max_size = 0x25000, + .tcp_rx_buf_max_size = 0x25000, + + //We don't use UDP, set all UDP buffers to 0 + .udp_tx_buf_size = 0, + .udp_rx_buf_size = 0, + + .sb_efficiency = 1, + }; R_ABORT_UNLESS(smInitialize()); // ams::sm::DoWithSession([] { @@ -65,12 +88,18 @@ extern "C" void __appInit(void) hosversionSet(MAKEHOSVERSION(fw.major, fw.minor, fw.micro)); setsysExit(); + R_ABORT_UNLESS(hiddbgInitialize()); + + R_ABORT_UNLESS(hidInitialize()); + R_ABORT_UNLESS(hidsysInitialize()); + if (hosversionAtLeast(7, 0, 0)) R_ABORT_UNLESS(hiddbgAttachHdlsWorkBuffer(&SwitchHDLHandler::GetHdlsSessionId())); R_ABORT_UNLESS(usbHsInitialize()); R_ABORT_UNLESS(pscmInitialize()); R_ABORT_UNLESS(fsInitialize()); + R_ABORT_UNLESS(socketInitialize(&socketInitConfig)); } // ); smExit(); @@ -80,6 +109,10 @@ extern "C" void __appInit(void) extern "C" void __appExit(void) { + socketExit(); + hidsysExit(); + hidExit(); + pscmExit(); usbHsExit(); hiddbgReleaseHdlsWorkBuffer(SwitchHDLHandler::GetHdlsSessionId()); @@ -90,6 +123,23 @@ extern "C" void __appExit(void) using namespace syscon; +static loop_status_t loop(loop_status_t (*callback)(void)) +{ + loop_status_t status = LOOP_CONTINUE; + + while (true) + { + svcSleepThread(1e+7); + status = callback(); + console_render(); + if (status != LOOP_CONTINUE) + return status; + if (isPaused()) + return LOOP_RESTART; + } + return LOOP_EXIT; +} + int main(int argc, char *argv[]) { WriteToLog("\n\nNew sysmodule session started on version " APP_VERSION); @@ -98,11 +148,58 @@ int main(int argc, char *argv[]) usb::Initialize(); psc::Initialize(); - while (true) + FILE* should_log_file = fopen("/config/sys-ftpd/logs/ftpd_log_enabled", "r"); + if (should_log_file != NULL) + { + should_log = true; + fclose(should_log_file); + + mkdir("/config/sys-ftpd/logs", 0700); + unlink("/config/sys-ftpd/logs/ftpd.log"); + } + + char buffer[100]; + ini_gets("Pause", "disabled:", "0", buffer, 100, CONFIGPATH); + + //Checks if pausing is disabled in the config file, in which case it skips the entire pause initialization + if (strncmp(buffer, "1", 4) != 0) { - svcSleepThread(1e+8L); + Result rc = pauseInit(); + if (R_FAILED(rc)) + fatalThrow(rc); } + loop_status_t status = LOOP_RESTART; + + WriteToLog("Going to pre_init"); + + ftp_pre_init(); + + WriteToLog("pre_init completed"); + + while (status == LOOP_RESTART) + { + while (isPaused()) + { + svcSleepThread(1e+9); + } + + /* initialize ftp subsystem */ + if (ftp_init() == 0) + { + /* ftp loop */ + status = loop(ftp_loop); + + /* done with ftp */ + ftp_exit(); + } + else + status = LOOP_EXIT; + } + ftp_post_exit(); + + pauseExit(); + psc::Exit(); usb::Exit(); controllers::Exit(); diff --git a/source/Sysmodule/source/minIni.c b/source/Sysmodule/source/minIni.c new file mode 100644 index 00000000..dd57e418 --- /dev/null +++ b/source/Sysmodule/source/minIni.c @@ -0,0 +1,928 @@ +/* minIni - Multi-Platform INI file parser, suitable for embedded systems + * + * These routines are in part based on the article "Multiplatform .INI Files" + * by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal. + * + * Copyright (c) CompuPhase, 2008-2017 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ + */ + +#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY +# if !defined UNICODE /* for Windows */ +# define UNICODE +# endif +# if !defined _UNICODE /* for C library */ +# define _UNICODE +# endif +#endif + +#define MININI_IMPLEMENTATION +#include "minIni.h" +#if defined NDEBUG +# define assert(e) +#else +# include +#endif + +#if !defined __T || defined INI_ANSIONLY +# include +# include +# include +# define TCHAR char +# define __T(s) s +# define _tcscat strcat +# define _tcschr strchr +# define _tcscmp strcmp +# define _tcscpy strcpy +# define _tcsicmp stricmp +# define _tcslen strlen +# define _tcsncmp strncmp +# define _tcsncmp strncmp +# define _tcsrchr strrchr +# define _tcstol strtol +# define _tcstod strtod +# define _totupper toupper +# define _stprintf sprintf +# define _tfgets fgets +# define _tfputs fputs +# define _tfopen fopen +# define _tremove remove +# define _trename rename +#endif + +#if defined __linux || defined __linux__ +# define __LINUX__ +#elif defined FREEBSD && !defined __FreeBSD__ +# define __FreeBSD__ +#elif defined(_MSC_VER) +# pragma warning(disable : 4996) /* for Microsoft Visual C/C++ */ +#endif +#if !defined strncmp && !defined PORTABLE_strncmp +# if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ +# define strncmp strncasecmp +# endif +#endif +#if !defined _totupper +# define _totupper toupper +#endif + +#if !defined INI_LINETERM +# if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ +# define INI_LINETERM __T("\n") +# else +# define INI_LINETERM __T("\r\n") +# endif +#endif +#if !defined INI_FILETYPE +# error Missing definition for INI_FILETYPE. +#endif + +#if !defined sizearray +# define sizearray(a) (sizeof(a) / sizeof((a)[0])) +#endif + +enum quote_option +{ + QUOTE_NONE, + QUOTE_ENQUOTE, + QUOTE_DEQUOTE, +}; + +#if defined PORTABLE_strncmp +int strncmp(const TCHAR* s1, const TCHAR* s2, size_t n) +{ + while (n-- != 0 && (*s1 || *s2)) + { + register int c1, c2; + c1 = *s1++; + if ('a' <= c1 && c1 <= 'z') + c1 += ('A' - 'a'); + c2 = *s2++; + if ('a' <= c2 && c2 <= 'z') + c2 += ('A' - 'a'); + if (c1 != c2) + return c1 - c2; + } /* while */ + return 0; +} +#endif /* PORTABLE_strncmp */ + +static TCHAR* skipleading(const TCHAR* str) +{ + assert(str != NULL); + while ('\0' < *str && *str <= ' ') + str++; + return (TCHAR*)str; +} + +static TCHAR* skiptrailing(const TCHAR* str, const TCHAR* base) +{ + assert(str != NULL); + assert(base != NULL); + while (str > base && '\0' < *(str - 1) && *(str - 1) <= ' ') + str--; + return (TCHAR*)str; +} + +static TCHAR* striptrailing(TCHAR* str) +{ + TCHAR* ptr = skiptrailing(_tcschr(str, '\0'), str); + assert(ptr != NULL); + *ptr = '\0'; + return str; +} + +static TCHAR* ini_strncpy(TCHAR* dest, const TCHAR* source, size_t maxlen, enum quote_option option) +{ + size_t d, s; + + assert(maxlen > 0); + assert(source != NULL && dest != NULL); + assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source)); + if (option == QUOTE_ENQUOTE && maxlen < 3) + option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */ + + switch (option) + { + case QUOTE_NONE: + for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++) + dest[d] = source[d]; + assert(d < maxlen); + dest[d] = '\0'; + break; + case QUOTE_ENQUOTE: + d = 0; + dest[d++] = '"'; + for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) + { + if (source[s] == '"') + { + if (d >= maxlen - 3) + break; /* no space to store the escape character plus the one that follows it */ + dest[d++] = '\\'; + } /* if */ + dest[d] = source[s]; + } /* for */ + dest[d++] = '"'; + dest[d] = '\0'; + break; + case QUOTE_DEQUOTE: + for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) + { + if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"') + s++; + dest[d] = source[s]; + } /* for */ + dest[d] = '\0'; + break; + default: + assert(0); + } /* switch */ + + return dest; +} + +static TCHAR* cleanstring(TCHAR* string, enum quote_option* quotes) +{ + int isstring; + TCHAR* ep; + + assert(string != NULL); + assert(quotes != NULL); + + /* Remove a trailing comment */ + isstring = 0; + for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) + { + if (*ep == '"') + { + if (*(ep + 1) == '"') + ep++; /* skip "" (both quotes) */ + else + isstring = !isstring; /* single quote, toggle isstring */ + } + else if (*ep == '\\' && *(ep + 1) == '"') + { + ep++; /* skip \" (both quotes */ + } /* if */ + } /* for */ + assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#')); + *ep = '\0'; /* terminate at a comment */ + striptrailing(string); + /* Remove double quotes surrounding a value */ + *quotes = QUOTE_NONE; + if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') + { + string++; + *--ep = '\0'; + *quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */ + } /* if */ + return string; +} + +static int getkeystring(INI_FILETYPE* fp, const TCHAR* Section, const TCHAR* Key, + int idxSection, int idxKey, TCHAR* Buffer, int BufferSize, + INI_FILEPOS* mark) +{ + TCHAR *sp, *ep; + int len, idx; + enum quote_option quotes; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + + assert(fp != NULL); + /* Move through file 1 line at a time until a section is matched or EOF. If + * parameter Section is NULL, only look at keys above the first section. If + * idxSection is positive, copy the relevant section name. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0 || idxSection >= 0) + { + assert(idxSection >= 0 || Section != NULL); + idx = -1; + do + { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp)) + return 0; + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + } while (*sp != '[' || ep == NULL || + (((int)(ep - sp - 1) != len || Section == NULL || _tcsncmp(sp + 1, Section, len) != 0) && ++idx != idxSection)); + if (idxSection >= 0) + { + if (idx == idxSection) + { + assert(ep != NULL); + assert(*ep == ']'); + *ep = '\0'; + ini_strncpy(Buffer, sp + 1, BufferSize, QUOTE_NONE); + return 1; + } /* if */ + return 0; /* no more section found */ + } /* if */ + } /* if */ + + /* Now that the section has been found, find the entry. + * Stop searching upon leaving the section's area. + */ + assert(Key != NULL || idxKey >= 0); + len = (Key != NULL) ? (int)_tcslen(Key) : 0; + idx = -1; + do + { + if (mark != NULL) + ini_tell(fp, mark); /* optionally keep the mark to the start of the line */ + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp) || *(sp = skipleading(LocalBuffer)) == '[') + return 0; + sp = skipleading(LocalBuffer); + ep = _tcschr(sp, '='); /* Parse out the equal sign */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + } while (*sp == ';' || *sp == '#' || ep == NULL || ((len == 0 || (int)(skiptrailing(ep, sp) - sp) != len || _tcsncmp(sp, Key, len) != 0) && ++idx != idxKey)); + if (idxKey >= 0) + { + if (idx == idxKey) + { + assert(ep != NULL); + assert(*ep == '=' || *ep == ':'); + *ep = '\0'; + striptrailing(sp); + ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE); + return 1; + } /* if */ + return 0; /* no more key found (in this section) */ + } /* if */ + + /* Copy up to BufferSize chars to buffer */ + assert(ep != NULL); + assert(*ep == '=' || *ep == ':'); + sp = skipleading(ep + 1); + sp = cleanstring(sp, "es); /* Remove a trailing comment */ + ini_strncpy(Buffer, sp, BufferSize, quotes); + return 1; +} + +/** ini_gets() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue default string in the event of a failed read + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_gets(const TCHAR* Section, const TCHAR* Key, const TCHAR* DefValue, + TCHAR* Buffer, int BufferSize, const TCHAR* Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || Key == NULL) + return 0; + if (ini_openread(Filename, &fp)) + { + ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE); + return (int)_tcslen(Buffer); +} + +/** ini_getl() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue the default value in the event of a failed read + * \param Filename the name of the .ini file to read from + * + * \return the value located at Key + */ +long ini_getl(const TCHAR* Section, const TCHAR* Key, long DefValue, const TCHAR* Filename) +{ + TCHAR LocalBuffer[64]; + int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + return (len == 0) ? DefValue + : ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16) + : _tcstol(LocalBuffer, NULL, 10)); +} + +#if defined INI_REAL +/** ini_getf() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue the default value in the event of a failed read + * \param Filename the name of the .ini file to read from + * + * \return the value located at Key + */ +INI_REAL ini_getf(const TCHAR* Section, const TCHAR* Key, INI_REAL DefValue, const TCHAR* Filename) +{ + TCHAR LocalBuffer[64]; + int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + return (len == 0) ? DefValue : ini_atof(LocalBuffer); +} +#endif + +/** ini_getbool() + * \param Section the name of the section to search for + * \param Key the name of the entry to find the value of + * \param DefValue default value in the event of a failed read; it should + * zero (0) or one (1). + * \param Filename the name and full path of the .ini file to read from + * + * A true boolean is found if one of the following is matched: + * - A string starting with 'y' or 'Y' + * - A string starting with 't' or 'T' + * - A string starting with '1' + * + * A false boolean is found if one of the following is matched: + * - A string starting with 'n' or 'N' + * - A string starting with 'f' or 'F' + * - A string starting with '0' + * + * \return the true/false flag as interpreted at Key + */ +int ini_getbool(const TCHAR* Section, const TCHAR* Key, int DefValue, const TCHAR* Filename) +{ + TCHAR LocalBuffer[2] = __T(""); + int ret; + + ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); + LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]); + if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T') + ret = 1; + else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F') + ret = 0; + else + ret = DefValue; + + return (ret); +} + +/** ini_getsection() + * \param idx the zero-based sequence number of the section to return + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_getsection(int idx, TCHAR* Buffer, int BufferSize, const TCHAR* Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || idx < 0) + return 0; + if (ini_openread(Filename, &fp)) + { + ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + *Buffer = '\0'; + return (int)_tcslen(Buffer); +} + +/** ini_getkey() + * \param Section the name of the section to browse through, or NULL to + * browse through the keys outside any section + * \param idx the zero-based sequence number of the key to return + * \param Buffer a pointer to the buffer to copy into + * \param BufferSize the maximum number of characters to copy + * \param Filename the name and full path of the .ini file to read from + * + * \return the number of characters copied into the supplied buffer + */ +int ini_getkey(const TCHAR* Section, int idx, TCHAR* Buffer, int BufferSize, const TCHAR* Filename) +{ + INI_FILETYPE fp; + int ok = 0; + + if (Buffer == NULL || BufferSize <= 0 || idx < 0) + return 0; + if (ini_openread(Filename, &fp)) + { + ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL); + (void)ini_close(&fp); + } /* if */ + if (!ok) + *Buffer = '\0'; + return (int)_tcslen(Buffer); +} + +#if !defined INI_NOBROWSE +/** ini_browse() + * \param Callback a pointer to a function that will be called for every + * setting in the INI file. + * \param UserData arbitrary data, which the function passes on the + * \c Callback function + * \param Filename the name and full path of the .ini file to read from + * + * \return 1 on success, 0 on failure (INI file not found) + * + * \note The \c Callback function must return 1 to continue + * browsing through the INI file, or 0 to stop. Even when the + * callback stops the browsing, this function will return 1 + * (for success). + */ +int ini_browse(INI_CALLBACK Callback, void* UserData, const TCHAR* Filename) +{ + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int lenSec, lenKey; + enum quote_option quotes; + INI_FILETYPE fp; + + if (Callback == NULL) + return 0; + if (!ini_openread(Filename, &fp)) + return 0; + + LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */ + lenSec = (int)_tcslen(LocalBuffer) + 1; + for (;;) + { + TCHAR *sp, *ep; + if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp)) + break; + sp = skipleading(LocalBuffer + lenSec); + /* ignore empty strings and comments */ + if (*sp == '\0' || *sp == ';' || *sp == '#') + continue; + /* see whether we reached a new section */ + ep = _tcsrchr(sp, ']'); + if (*sp == '[' && ep != NULL) + { + *ep = '\0'; + ini_strncpy(LocalBuffer, sp + 1, INI_BUFFERSIZE, QUOTE_NONE); + lenSec = (int)_tcslen(LocalBuffer) + 1; + continue; + } /* if */ + /* not a new section, test for a key/value pair */ + ep = _tcschr(sp, '='); /* test for the equal sign or colon */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + if (ep == NULL) + continue; /* invalid line, ignore */ + *ep++ = '\0'; /* split the key from the value */ + striptrailing(sp); + ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE); + lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1; + /* clean up the value */ + sp = skipleading(ep); + sp = cleanstring(sp, "es); /* Remove a trailing comment */ + ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes); + /* call the callback */ + if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData)) + break; + } /* for */ + + (void)ini_close(&fp); + return 1; +} +#endif /* INI_NOBROWSE */ + +#if !defined INI_READONLY +static void ini_tempname(TCHAR* dest, const TCHAR* source, int maxlength) +{ + TCHAR* p; + + ini_strncpy(dest, source, maxlength, QUOTE_NONE); + p = _tcsrchr(dest, '\0'); + assert(p != NULL); + *(p - 1) = '~'; +} + +static enum quote_option check_enquote(const TCHAR* Value) +{ + const TCHAR* p; + + /* run through the value, if it has trailing spaces, or '"', ';' or '#' + * characters, enquote it + */ + assert(Value != NULL); + for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++) + /* nothing */; + return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE; +} + +static void writesection(TCHAR* LocalBuffer, const TCHAR* Section, INI_FILETYPE* fp) +{ + if (Section != NULL && _tcslen(Section) > 0) + { + TCHAR* p; + LocalBuffer[0] = '['; + ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + *p++ = ']'; + _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ + if (fp != NULL) + (void)ini_write(LocalBuffer, fp); + } /* if */ +} + +static void writekey(TCHAR* LocalBuffer, const TCHAR* Key, const TCHAR* Value, INI_FILETYPE* fp) +{ + TCHAR* p; + enum quote_option option = check_enquote(Value); + ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + *p++ = '='; + ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */ + p = _tcsrchr(LocalBuffer, '\0'); + assert(p != NULL); + _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ + if (fp != NULL) + (void)ini_write(LocalBuffer, fp); +} + +static int cache_accum(const TCHAR* string, int* size, int max) +{ + int len = (int)_tcslen(string); + if (*size + len >= max) + return 0; + *size += len; + return 1; +} + +static int cache_flush(TCHAR* buffer, int* size, + INI_FILETYPE* rfp, INI_FILETYPE* wfp, INI_FILEPOS* mark) +{ + int terminator_len = (int)_tcslen(INI_LINETERM); + int pos = 0; + + (void)ini_seek(rfp, mark); + assert(buffer != NULL); + buffer[0] = '\0'; + assert(size != NULL); + assert(*size <= INI_BUFFERSIZE); + while (pos < *size) + { + (void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp); + while (pos < *size && buffer[pos] != '\0') + pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */ + } /* while */ + if (buffer[0] != '\0') + { + assert(pos > 0 && pos <= INI_BUFFERSIZE); + if (pos == INI_BUFFERSIZE) + pos--; + buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */ + (void)ini_write(buffer, wfp); + } + ini_tell(rfp, mark); /* update mark */ + *size = 0; + /* return whether the buffer ended with a line termination */ + return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0); +} + +static int close_rename(INI_FILETYPE* rfp, INI_FILETYPE* wfp, const TCHAR* filename, TCHAR* buffer) +{ + (void)ini_close(rfp); + (void)ini_close(wfp); + (void)ini_remove(filename); + (void)ini_tempname(buffer, filename, INI_BUFFERSIZE); + (void)ini_rename(buffer, filename); + return 1; +} + +/** ini_puts() + * \param Section the name of the section to write the string in + * \param Key the name of the entry to write, or NULL to erase all keys in the section + * \param Value a pointer to the buffer the string, or NULL to erase the key + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_puts(const TCHAR* Section, const TCHAR* Key, const TCHAR* Value, const TCHAR* Filename) +{ + INI_FILETYPE rfp; + INI_FILETYPE wfp; + INI_FILEPOS mark; + INI_FILEPOS head, tail; + TCHAR *sp, *ep; + TCHAR LocalBuffer[INI_BUFFERSIZE]; + int len, match, flag, cachelen; + + assert(Filename != NULL); + if (!ini_openread(Filename, &rfp)) + { + /* If the .ini file doesn't exist, make a new file */ + if (Key != NULL && Value != NULL) + { + if (!ini_openwrite(Filename, &wfp)) + return 0; + writesection(LocalBuffer, Section, &wfp); + writekey(LocalBuffer, Key, Value, &wfp); + (void)ini_close(&wfp); + } /* if */ + return 1; + } /* if */ + + /* If parameters Key and Value are valid (so this is not an "erase" request) + * and the setting already exists, there are two short-cuts to avoid rewriting + * the INI file. + */ + if (Key != NULL && Value != NULL) + { + ini_tell(&rfp, &mark); + match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head); + if (match) + { + /* if the current setting is identical to the one to write, there is + * nothing to do. + */ + if (_tcscmp(LocalBuffer, Value) == 0) + { + (void)ini_close(&rfp); + return 1; + } /* if */ +/* if the new setting has the same length as the current setting, and the + * glue file permits file read/write access, we can modify in place. + */ +# if defined ini_openrewrite + /* we already have the start of the (raw) line, get the end too */ + ini_tell(&rfp, &tail); + /* create new buffer (without writing it to file) */ + writekey(LocalBuffer, Key, Value, NULL); + if (_tcslen(LocalBuffer) == (size_t)(tail - head)) + { + /* length matches, close the file & re-open for read/write, then + * write at the correct position + */ + (void)ini_close(&rfp); + if (!ini_openrewrite(Filename, &wfp)) + return 0; + (void)ini_seek(&wfp, &head); + (void)ini_write(LocalBuffer, &wfp); + (void)ini_close(&wfp); + return 1; + } /* if */ +# endif + } /* if */ + /* key not found, or different value & length -> proceed (but rewind the + * input file first) + */ + (void)ini_seek(&rfp, &mark); + } /* if */ + + /* Get a temporary file name to copy to. Use the existing name, but with + * the last character set to a '~'. + */ + ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); + if (!ini_openwrite(LocalBuffer, &wfp)) + { + (void)ini_close(&rfp); + return 0; + } /* if */ + (void)ini_tell(&rfp, &mark); + cachelen = 0; + + /* Move through the file one line at a time until a section is + * matched or until EOF. Copy to temp file as it is read. + */ + len = (Section != NULL) ? (int)_tcslen(Section) : 0; + if (len > 0) + { + do + { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) + { + /* Failed to find section, so add one to the end */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key != NULL && Value != NULL) + { + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + writesection(LocalBuffer, Section, &wfp); + writekey(LocalBuffer, Key, Value, &wfp); + } /* if */ + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + /* Copy the line from source to dest, but not if this is the section that + * we are looking for and this section must be removed + */ + sp = skipleading(LocalBuffer); + ep = _tcsrchr(sp, ']'); + match = (*sp == '[' && ep != NULL && (int)(ep - sp - 1) == len && _tcsncmp(sp + 1, Section, len) == 0); + if (!match || Key != NULL) + { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) + { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } while (!match); + } /* if */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + /* when deleting a section, the section head that was just found has not been + * copied to the output file, but because this line was not "accumulated" in + * the cache, the position in the input file was reset to the point just + * before the section; this must now be skipped (again) + */ + if (Key == NULL) + { + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + (void)ini_tell(&rfp, &mark); + } /* if */ + + /* Now that the section has been found, find the entry. Stop searching + * upon leaving the section's area. Copy the file as it is read + * and create an entry if one is not found. + */ + len = (Key != NULL) ? (int)_tcslen(Key) : 0; + for (;;) + { + if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) + { + /* EOF without an entry so make one */ + flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key != NULL && Value != NULL) + { + if (!flag) + (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ + writekey(LocalBuffer, Key, Value, &wfp); + } /* if */ + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ + } /* if */ + sp = skipleading(LocalBuffer); + ep = _tcschr(sp, '='); /* Parse out the equal sign */ + if (ep == NULL) + ep = _tcschr(sp, ':'); + match = (ep != NULL && len > 0 && (int)(skiptrailing(ep, sp) - sp) == len && _tcsncmp(sp, Key, len) == 0); + if ((Key != NULL && match) || *sp == '[') + break; /* found the key, or found a new section */ + /* copy other keys in the section */ + if (Key == NULL) + { + (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ + } + else + { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) + { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* if */ + } /* for */ + /* the key was found, or we just dropped on the next section (meaning that it + * wasn't found); in both cases we need to write the key, but in the latter + * case, we also need to write the line starting the new section after writing + * the key + */ + flag = (*sp == '['); + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + if (Key != NULL && Value != NULL) + writekey(LocalBuffer, Key, Value, &wfp); + /* cache_flush() reset the "read pointer" to the start of the line with the + * previous key or the new section; read it again (because writekey() destroyed + * the buffer) + */ + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + if (flag) + { + /* the new section heading needs to be copied to the output file */ + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } + else + { + /* forget the old key line */ + (void)ini_tell(&rfp, &mark); + } /* if */ + /* Copy the rest of the INI file */ + while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) + { + if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) + { + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); + cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); + } /* if */ + } /* while */ + cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); + return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ +} + +/* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */ +# define ABS(v) ((v) < 0 ? -(v) : (v)) + +static void strreverse(TCHAR* str) +{ + int i, j; + for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) + { + TCHAR t = str[i]; + str[i] = str[j]; + str[j] = t; + } /* for */ +} + +static void long2str(long value, TCHAR* str) +{ + int i = 0; + long sign = value; + + /* generate digits in reverse order */ + do + { + int n = (int)(value % 10); /* get next lowest digit */ + str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */ + } while (value /= 10); /* delete the lowest digit */ + if (sign < 0) + str[i++] = '-'; + str[i] = '\0'; + + strreverse(str); +} + +/** ini_putl() + * \param Section the name of the section to write the value in + * \param Key the name of the entry to write + * \param Value the value to write + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_putl(const TCHAR* Section, const TCHAR* Key, long Value, const TCHAR* Filename) +{ + TCHAR LocalBuffer[32]; + long2str(Value, LocalBuffer); + return ini_puts(Section, Key, LocalBuffer, Filename); +} + +# if defined INI_REAL +/** ini_putf() + * \param Section the name of the section to write the value in + * \param Key the name of the entry to write + * \param Value the value to write + * \param Filename the name and full path of the .ini file to write to + * + * \return 1 if successful, otherwise 0 + */ +int ini_putf(const TCHAR* Section, const TCHAR* Key, INI_REAL Value, const TCHAR* Filename) +{ + TCHAR LocalBuffer[64]; + ini_ftoa(LocalBuffer, Value); + return ini_puts(Section, Key, LocalBuffer, Filename); +} +# endif /* INI_REAL */ +#endif /* !INI_READONLY */ diff --git a/source/Sysmodule/source/minIni.h b/source/Sysmodule/source/minIni.h new file mode 100644 index 00000000..9861f41f --- /dev/null +++ b/source/Sysmodule/source/minIni.h @@ -0,0 +1,184 @@ +/* minIni - Multi-Platform INI file parser, suitable for embedded systems + * + * Copyright (c) CompuPhase, 2008-2017 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: minIni.h 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ + */ +#ifndef MININI_H +#define MININI_H + +#include "minIni/minGlue.h" + +#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY +# include +# define mTCHAR TCHAR +#else +/* force TCHAR to be "char", but only for minIni */ +# define mTCHAR char +#endif + +#if !defined INI_BUFFERSIZE +# define INI_BUFFERSIZE 512 +#endif + +#if defined __cplusplus +extern "C" +{ +#endif + + int ini_getbool(const mTCHAR* Section, const mTCHAR* Key, int DefValue, const mTCHAR* Filename); + long ini_getl(const mTCHAR* Section, const mTCHAR* Key, long DefValue, const mTCHAR* Filename); + int ini_gets(const mTCHAR* Section, const mTCHAR* Key, const mTCHAR* DefValue, mTCHAR* Buffer, int BufferSize, const mTCHAR* Filename); + int ini_getsection(int idx, mTCHAR* Buffer, int BufferSize, const mTCHAR* Filename); + int ini_getkey(const mTCHAR* Section, int idx, mTCHAR* Buffer, int BufferSize, const mTCHAR* Filename); + +#if defined INI_REAL + INI_REAL ini_getf(const mTCHAR* Section, const mTCHAR* Key, INI_REAL DefValue, const mTCHAR* Filename); +#endif + +#if !defined INI_READONLY + int ini_putl(const mTCHAR* Section, const mTCHAR* Key, long Value, const mTCHAR* Filename); + int ini_puts(const mTCHAR* Section, const mTCHAR* Key, const mTCHAR* Value, const mTCHAR* Filename); +# if defined INI_REAL + int ini_putf(const mTCHAR* Section, const mTCHAR* Key, INI_REAL Value, const mTCHAR* Filename); +# endif +#endif /* INI_READONLY */ + +#if !defined INI_NOBROWSE + typedef int (*INI_CALLBACK)(const mTCHAR* Section, const mTCHAR* Key, const mTCHAR* Value, void* UserData); + int ini_browse(INI_CALLBACK Callback, void* UserData, const mTCHAR* Filename); +#endif /* INI_NOBROWSE */ + +#if defined __cplusplus +} +#endif + +#if defined __cplusplus + +# if defined __WXWINDOWS__ +# include "wxMinIni.h" +# else +# include + +/* The C++ class in minIni.h was contributed by Steven Van Ingelgem. */ +class minIni +{ +public: + minIni(const std::string& filename) : iniFilename(filename) + { + } + + bool getbool(const std::string& Section, const std::string& Key, bool DefValue = false) const + { + return ini_getbool(Section.c_str(), Key.c_str(), int(DefValue), iniFilename.c_str()) != 0; + } + + long getl(const std::string& Section, const std::string& Key, long DefValue = 0) const + { + return ini_getl(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); + } + + int geti(const std::string& Section, const std::string& Key, int DefValue = 0) const + { + return static_cast(this->getl(Section, Key, long(DefValue))); + } + + std::string gets(const std::string& Section, const std::string& Key, const std::string& DefValue = "") const + { + char buffer[INI_BUFFERSIZE]; + ini_gets(Section.c_str(), Key.c_str(), DefValue.c_str(), buffer, INI_BUFFERSIZE, iniFilename.c_str()); + return buffer; + } + + std::string getsection(int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); + return buffer; + } + + std::string getkey(const std::string& Section, int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getkey(Section.c_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); + return buffer; + } + +# if defined INI_REAL + INI_REAL getf(const std::string& Section, const std::string& Key, INI_REAL DefValue = 0) const + { + return ini_getf(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); + } +# endif + +# if !defined INI_READONLY + bool put(const std::string& Section, const std::string& Key, long Value) + { + return ini_putl(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; + } + + bool put(const std::string& Section, const std::string& Key, int Value) + { + return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; + } + + bool put(const std::string& Section, const std::string& Key, bool Value) + { + return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; + } + + bool put(const std::string& Section, const std::string& Key, const std::string& Value) + { + return ini_puts(Section.c_str(), Key.c_str(), Value.c_str(), iniFilename.c_str()) != 0; + } + + bool put(const std::string& Section, const std::string& Key, const char* Value) + { + return ini_puts(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; + } + +# if defined INI_REAL + bool put(const std::string& Section, const std::string& Key, INI_REAL Value) + { + return ini_putf(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; + } +# endif + + bool del(const std::string& Section, const std::string& Key) + { + return ini_puts(Section.c_str(), Key.c_str(), 0, iniFilename.c_str()) != 0; + } + + bool del(const std::string& Section) + { + return ini_puts(Section.c_str(), 0, 0, iniFilename.c_str()) != 0; + } +# endif + +# if !defined INI_NOBROWSE + bool browse(INI_CALLBACK Callback, void* UserData) const + { + return ini_browse(Callback, UserData, iniFilename.c_str()) != 0; + } +# endif + +private: + std::string iniFilename; +}; + +# endif /* __WXWINDOWS__ */ +#endif /* __cplusplus */ + +#endif /* MININI_H */ diff --git a/source/Sysmodule/source/minIni/minGlue-FatFs.h b/source/Sysmodule/source/minIni/minGlue-FatFs.h new file mode 100644 index 00000000..3873cd83 --- /dev/null +++ b/source/Sysmodule/source/minIni/minGlue-FatFs.h @@ -0,0 +1,37 @@ +/* Glue functions for the minIni library, based on the FatFs and Petit-FatFs + * libraries, see http://elm-chan.org/fsw/ff/00index_e.html + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The FatFs and Petit-FatFs libraries are copyright by ChaN and licensed at + * its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +/* You must set _USE_STRFUNC to 1 or 2 in the include file ff.h (or tff.h) + * to enable the "string functions" fgets() and fputs(). + */ +#include "ff.h" /* include tff.h for Tiny-FatFs */ + +#define INI_FILETYPE FIL +#define ini_openread(filename, file) (f_open((file), (filename), FA_READ + FA_OPEN_EXISTING) == FR_OK) +#define ini_openwrite(filename, file) (f_open((file), (filename), FA_WRITE + FA_CREATE_ALWAYS) == FR_OK) +#define ini_close(file) (f_close(file) == FR_OK) +#define ini_read(buffer, size, file) f_gets((buffer), (size), (file)) +#define ini_write(buffer, file) f_puts((buffer), (file)) +#define ini_remove(filename) (f_unlink(filename) == FR_OK) + +#define INI_FILEPOS DWORD +#define ini_tell(file, pos) (*(pos) = f_tell((file))) +#define ini_seek(file, pos) (f_lseek((file), *(pos)) == FR_OK) + +static int ini_rename(TCHAR* source, const TCHAR* dest) +{ + /* Function f_rename() does not allow drive letters in the destination file */ + char* drive = strchr(dest, ':'); + drive = (drive == NULL) ? dest : drive + 1; + return (f_rename(source, drive) == FR_OK); +} diff --git a/source/Sysmodule/source/minIni/minGlue-ccs.h b/source/Sysmodule/source/minIni/minGlue-ccs.h new file mode 100644 index 00000000..7a51e54b --- /dev/null +++ b/source/Sysmodule/source/minIni/minGlue-ccs.h @@ -0,0 +1,64 @@ +/* minIni glue functions for FAT library by CCS, Inc. (as provided with their + * PIC MCU compiler) + * + * By CompuPhase, 2011-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The FAT library is copyright (c) 2007 Custom Computer Services, and + * licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +#ifndef FAT_PIC_C +# error FAT library must be included before this module +#endif +#define const /* keyword not supported by CCS */ + +#define INI_FILETYPE FILE +#define ini_openread(filename, file) (fatopen((filename), "r", (file)) == GOODEC) +#define ini_openwrite(filename, file) (fatopen((filename), "w", (file)) == GOODEC) +#define ini_close(file) (fatclose((file)) == 0) +#define ini_read(buffer, size, file) (fatgets((buffer), (size), (file)) != NULL) +#define ini_write(buffer, file) (fatputs((buffer), (file)) == GOODEC) +#define ini_remove(filename) (rm_file((filename)) == 0) + +#define INI_FILEPOS fatpos_t +#define ini_tell(file, pos) (fatgetpos((file), (pos)) == 0) +#define ini_seek(file, pos) (fatsetpos((file), (pos)) == 0) + +#ifndef INI_READONLY +/* CCS FAT library lacks a rename function, so instead we copy the file to the + * new name and delete the old file + */ +static int ini_rename(char* source, char* dest) +{ + FILE fr, fw; + int n; + + if (fatopen(source, "r", &fr) != GOODEC) + return 0; + if (rm_file(dest) != 0) + return 0; + if (fatopen(dest, "w", &fw) != GOODEC) + return 0; + + /* With some "insider knowledge", we can save some memory: the "source" + * parameter holds a filename that was built from the "dest" parameter. It + * was built in a local buffer with the size INI_BUFFERSIZE. We can reuse + * this buffer for copying the file. + */ + while (n = fatread(source, 1, INI_BUFFERSIZE, &fr)) + fatwrite(source, 1, n, &fw); + + fatclose(&fr); + fatclose(&fw); + + /* Now we need to delete the source file. However, we have garbled the buffer + * that held the filename of the source. So we need to build it again. + */ + ini_tempname(source, dest, INI_BUFFERSIZE); + return rm_file(source) == 0; +} +#endif diff --git a/source/Sysmodule/source/minIni/minGlue-efsl.h b/source/Sysmodule/source/minIni/minGlue-efsl.h new file mode 100644 index 00000000..272692c3 --- /dev/null +++ b/source/Sysmodule/source/minIni/minGlue-efsl.h @@ -0,0 +1,63 @@ +/* Glue functions for the minIni library, based on the EFS Library, see + * http://www.efsl.be/ + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (EFSL is copyright 2005-2006 Lennart Ysboodt and Michael De Nil, and + * licensed under the GPL with an exception clause for static linking.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ +#define INI_LINETERM "\r\n" /* set line termination explicitly */ + +#include "efs.h" +extern EmbeddedFileSystem g_efs; + +#define INI_FILETYPE EmbeddedFile +#define ini_openread(filename, file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'r') == 0) +#define ini_openwrite(filename, file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'w') == 0) +#define ini_close(file) file_fclose(file) +#define ini_read(buffer, size, file) (file_read((file), (size), (buffer)) > 0) +#define ini_write(buffer, file) (file_write((file), strlen(buffer), (char*)(buffer)) > 0) +#define ini_remove(filename) rmfile(&g_efs.myFs, (char*)(filename)) + +#define INI_FILEPOS euint32 +#define ini_tell(file, pos) (*(pos) = (file)->FilePtr)) +#define ini_seek(file, pos) file_setpos((file), (*pos)) + +#if !defined INI_READONLY +/* EFSL lacks a rename function, so instead we copy the file to the new name + * and delete the old file + */ +static int ini_rename(char* source, const char* dest) +{ + EmbeddedFile fr, fw; + int n; + + if (file_fopen(&fr, &g_efs.myFs, source, 'r') != 0) + return 0; + if (rmfile(&g_efs.myFs, (char*)dest) != 0) + return 0; + if (file_fopen(&fw, &g_efs.myFs, (char*)dest, 'w') != 0) + return 0; + + /* With some "insider knowledge", we can save some memory: the "source" + * parameter holds a filename that was built from the "dest" parameter. It + * was built in buffer and this buffer has the size INI_BUFFERSIZE. We can + * reuse this buffer for copying the file. + */ + while (n = file_read(&fr, INI_BUFFERSIZE, source)) + file_write(&fw, n, source); + + file_fclose(&fr); + file_fclose(&fw); + + /* Now we need to delete the source file. However, we have garbled the buffer + * that held the filename of the source. So we need to build it again. + */ + ini_tempname(source, dest, INI_BUFFERSIZE); + return rmfile(&g_efs.myFs, source) == 0; +} +#endif diff --git a/source/Sysmodule/source/minIni/minGlue-ffs.h b/source/Sysmodule/source/minIni/minGlue-ffs.h new file mode 100644 index 00000000..e0a818a2 --- /dev/null +++ b/source/Sysmodule/source/minIni/minGlue-ffs.h @@ -0,0 +1,26 @@ +/* Glue functions for the minIni library, based on the "FAT Filing System" + * library by embedded-code.com + * + * By CompuPhase, 2008-2012 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The "FAT Filing System" library itself is copyright embedded-code.com, and + * licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ +#include + +#define INI_FILETYPE FFS_FILE* +#define ini_openread(filename, file) ((*(file) = ffs_fopen((filename), "r")) != NULL) +#define ini_openwrite(filename, file) ((*(file) = ffs_fopen((filename), "w")) != NULL) +#define ini_close(file) (ffs_fclose(*(file)) == 0) +#define ini_read(buffer, size, file) (ffs_fgets((buffer), (size), *(file)) != NULL) +#define ini_write(buffer, file) (ffs_fputs((buffer), *(file)) >= 0) +#define ini_rename(source, dest) (ffs_rename((source), (dest)) == 0) +#define ini_remove(filename) (ffs_remove(filename) == 0) + +#define INI_FILEPOS long +#define ini_tell(file, pos) (ffs_fgetpos(*(file), (pos)) == 0) +#define ini_seek(file, pos) (ffs_fsetpos(*(file), (pos)) == 0) diff --git a/source/Sysmodule/source/minIni/minGlue-mdd.h b/source/Sysmodule/source/minIni/minGlue-mdd.h new file mode 100644 index 00000000..96ec2250 --- /dev/null +++ b/source/Sysmodule/source/minIni/minGlue-mdd.h @@ -0,0 +1,59 @@ +/* minIni glue functions for Microchip's "Memory Disk Drive" file system + * library, as presented in Microchip application note AN1045. + * + * By CompuPhase, 2011-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + * + * (The "Microchip Memory Disk Drive File System" is copyright (c) Microchip + * Technology Incorporated, and licensed at its own terms.) + */ + +#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ + +#include "MDD File System\fsio.h" +#include + +#define INI_FILETYPE FSFILE* +#define ini_openread(filename, file) ((*(file) = FSfopen((filename), FS_READ)) != NULL) +#define ini_openwrite(filename, file) ((*(file) = FSfopen((filename), FS_WRITE)) != NULL) +#define ini_openrewrite(filename, file) ((*(file) = fopen((filename), FS_READPLUS)) != NULL) +#define ini_close(file) (FSfclose(*(file)) == 0) +#define ini_write(buffer, file) (FSfwrite((buffer), 1, strlen(buffer), (*file)) > 0) +#define ini_remove(filename) (FSremove((filename)) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file, pos) (*(pos) = FSftell(*(file))) +#define ini_seek(file, pos) (FSfseek(*(file), *(pos), SEEK_SET) == 0) + +/* Since the Memory Disk Drive file system library reads only blocks of files, + * the function to read a text line does so by "over-reading" a block of the + * of the maximum size and truncating it behind the end-of-line. + */ +static int ini_read(char* buffer, int size, INI_FILETYPE* file) +{ + size_t numread = size; + char* eol; + + if ((numread = FSfread(buffer, 1, size, *file)) == 0) + return 0; /* at EOF */ + if ((eol = strchr(buffer, '\n')) == NULL) + eol = strchr(buffer, '\r'); + if (eol != NULL) + { + /* terminate the buffer */ + *++eol = '\0'; + /* "unread" the data that was read too much */ + FSfseek(*file, -(int)(numread - (size_t)(eol - buffer)), SEEK_CUR); + } /* if */ + return 1; +} + +#ifndef INI_READONLY +static int ini_rename(const char* source, const char* dest) +{ + FSFILE* ftmp = FSfopen((source), FS_READ); + FSrename((dest), ftmp); + return FSfclose(ftmp) == 0; +} +#endif diff --git a/source/Sysmodule/source/minIni/minGlue-stdio.h b/source/Sysmodule/source/minIni/minGlue-stdio.h new file mode 100644 index 00000000..31888637 --- /dev/null +++ b/source/Sysmodule/source/minIni/minGlue-stdio.h @@ -0,0 +1,31 @@ +/* Glue functions for the minIni library, based on the C/C++ stdio library + * + * Or better said: this file contains macros that maps the function interface + * used by minIni to the standard C/C++ file I/O functions. + * + * By CompuPhase, 2008-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + */ + +/* map required file I/O types and functions to the standard C library */ +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename, file) ((*(file) = fopen((filename), "rb")) != NULL) +#define ini_openwrite(filename, file) ((*(file) = fopen((filename), "wb")) != NULL) +#define ini_openrewrite(filename, file) ((*(file) = fopen((filename), "r+b")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer, size, file) (fgets((buffer), (size), *(file)) != NULL) +#define ini_write(buffer, file) (fputs((buffer), *(file)) >= 0) +#define ini_rename(source, dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file, pos) (*(pos) = ftell(*(file))) +#define ini_seek(file, pos) (fseek(*(file), *(pos), SEEK_SET) == 0) + +/* for floating-point support, define additional types and functions */ +#define INI_REAL float +#define ini_ftoa(string, value) sprintf((string), "%f", (value)) +#define ini_atof(string) (INI_REAL) strtod((string), NULL) diff --git a/source/Sysmodule/source/minIni/minGlue.h b/source/Sysmodule/source/minIni/minGlue.h new file mode 100644 index 00000000..31888637 --- /dev/null +++ b/source/Sysmodule/source/minIni/minGlue.h @@ -0,0 +1,31 @@ +/* Glue functions for the minIni library, based on the C/C++ stdio library + * + * Or better said: this file contains macros that maps the function interface + * used by minIni to the standard C/C++ file I/O functions. + * + * By CompuPhase, 2008-2014 + * This "glue file" is in the public domain. It is distributed without + * warranties or conditions of any kind, either express or implied. + */ + +/* map required file I/O types and functions to the standard C library */ +#include + +#define INI_FILETYPE FILE* +#define ini_openread(filename, file) ((*(file) = fopen((filename), "rb")) != NULL) +#define ini_openwrite(filename, file) ((*(file) = fopen((filename), "wb")) != NULL) +#define ini_openrewrite(filename, file) ((*(file) = fopen((filename), "r+b")) != NULL) +#define ini_close(file) (fclose(*(file)) == 0) +#define ini_read(buffer, size, file) (fgets((buffer), (size), *(file)) != NULL) +#define ini_write(buffer, file) (fputs((buffer), *(file)) >= 0) +#define ini_rename(source, dest) (rename((source), (dest)) == 0) +#define ini_remove(filename) (remove(filename) == 0) + +#define INI_FILEPOS long int +#define ini_tell(file, pos) (*(pos) = ftell(*(file))) +#define ini_seek(file, pos) (fseek(*(file), *(pos), SEEK_SET) == 0) + +/* for floating-point support, define additional types and functions */ +#define INI_REAL float +#define ini_ftoa(string, value) sprintf((string), "%f", (value)) +#define ini_atof(string) (INI_REAL) strtod((string), NULL) diff --git a/source/Sysmodule/source/minIni/wxMinIni.h b/source/Sysmodule/source/minIni/wxMinIni.h new file mode 100644 index 00000000..3c4538f2 --- /dev/null +++ b/source/Sysmodule/source/minIni/wxMinIni.h @@ -0,0 +1,126 @@ +/* minIni - Multi-Platform INI file parser, wxWidgets interface + * + * Copyright (c) CompuPhase, 2008-2012 + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy + * of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + * Version: $Id: wxMinIni.h 44 2012-01-04 15:52:56Z thiadmer.riemersma@gmail.com $ + */ +#ifndef WXMININI_H +#define WXMININI_H + +#include "minIni.h" +#include + +class minIni +{ +public: + minIni(const wxString& filename) : iniFilename(filename) + { + } + + bool getbool(const wxString& Section, const wxString& Key, bool DefValue = false) const + { + return ini_getbool(Section.utf8_str(), Key.utf8_str(), int(DefValue), iniFilename.utf8_str()) != 0; + } + + long getl(const wxString& Section, const wxString& Key, long DefValue = 0) const + { + return ini_getl(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); + } + + int geti(const wxString& Section, const wxString& Key, int DefValue = 0) const + { + return static_cast(ini_getl(Section.utf8_str(), Key.utf8_str(), (long)DefValue, iniFilename.utf8_str())); + } + + wxString gets(const wxString& Section, const wxString& Key, const wxString& DefValue = wxT("")) const + { + char buffer[INI_BUFFERSIZE]; + ini_gets(Section.utf8_str(), Key.utf8_str(), DefValue.utf8_str(), buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + + wxString getsection(int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + + wxString getkey(const wxString& Section, int idx) const + { + char buffer[INI_BUFFERSIZE]; + ini_getkey(Section.utf8_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); + wxString result = wxString::FromUTF8(buffer); + return result; + } + +#if defined INI_REAL + INI_REAL getf(const wxString& Section, wxString& Key, INI_REAL DefValue = 0) const + { + return ini_getf(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); + } +#endif + +#if !defined INI_READONLY + bool put(const wxString& Section, const wxString& Key, long Value) const + { + return ini_putl(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; + } + + bool put(const wxString& Section, const wxString& Key, int Value) const + { + return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; + } + + bool put(const wxString& Section, const wxString& Key, bool Value) const + { + return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; + } + + bool put(const wxString& Section, const wxString& Key, const wxString& Value) const + { + return ini_puts(Section.utf8_str(), Key.utf8_str(), Value.utf8_str(), iniFilename.utf8_str()) != 0; + } + + bool put(const wxString& Section, const wxString& Key, const char* Value) const + { + return ini_puts(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; + } + +# if defined INI_REAL + bool put(const wxString& Section, const wxString& Key, INI_REAL Value) const + { + return ini_putf(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; + } +# endif + + bool del(const wxString& Section, const wxString& Key) const + { + return ini_puts(Section.utf8_str(), Key.utf8_str(), 0, iniFilename.utf8_str()) != 0; + } + + bool del(const wxString& Section) const + { + return ini_puts(Section.utf8_str(), 0, 0, iniFilename.utf8_str()) != 0; + } +#endif + +private: + wxString iniFilename; +}; + +#endif /* WXMININI_H */ diff --git a/source/Sysmodule/source/util.c b/source/Sysmodule/source/util.c new file mode 100644 index 00000000..d0b3741f --- /dev/null +++ b/source/Sysmodule/source/util.c @@ -0,0 +1,147 @@ +#include "util.h" +#include +#include +#include +#include +#include + +#include "minIni.h" +#include + +static bool inputThreadRunning = true; +static bool paused = false; +static Mutex pausedMutex = 0; +static Thread pauseThread; +static HidControllerKeys comboKeys[8] = {}; + +void inputPoller() +{ + do + { + hidScanInput(); + u64 kHeld = 0; + for (u8 controller = 0; controller != 10; controller++) + kHeld |= hidKeysHeld(controller); + + u64 keyCombo = 0; + for (u8 i = 0; i != sizearray(comboKeys); ++i) + keyCombo |= comboKeys[i]; + + static bool keyComboPressed = false; + + if ((kHeld & keyCombo) == keyCombo) + { + if (!keyComboPressed) + { + keyComboPressed = true; + setPaused(!isPaused()); + } + } + else + { + keyComboPressed = false; + } + svcSleepThread(1e+8); + } while (inputThreadRunning); +} + +const char* buttons[] = { + "A", + "B", + "X", + "Y", + "LS", + "RS", + "L", + "R", + "ZL", + "ZR", + "PLUS", + "MINUS", + "DLEFT", + "DUP", + "DRIGHT", + "DDOWN", +}; + +HidControllerKeys GetKey(const char* text) +{ + for (u8 i = 0; i != sizearray(buttons); ++i) + { + if (strcmp(text, buttons[i]) == 0) + { + return BIT(i); + } + } + return 0; +} + +Result pauseInit() +{ + Result rc; + mutexLock(&pausedMutex); + + FILE* should_pause_file = fopen("/config/sys-ftpd/ftpd_paused", "r"); + if (should_pause_file != NULL) + { + paused = true; + fclose(should_pause_file); + } + + { + char buffer[128]; + ini_gets("Pause", "keycombo:", "PLUS+MINUS+X", buffer, 128, CONFIGPATH); + char* token = strtok(buffer, "+ "); + int i = 0; + while (token != NULL && i != sizearray(comboKeys)) + { + comboKeys[i++] = GetKey(token); + token = strtok(NULL, "+ "); + }; + } + + inputThreadRunning = true; + + rc = threadCreate(&pauseThread, inputPoller, NULL, NULL, 0x300, 0x3B, -2); + if (R_FAILED(rc)) + goto exit; + + rc = threadStart(&pauseThread); + if (R_FAILED(rc)) + goto exit; + +exit: + mutexUnlock(&pausedMutex); + return rc; +} + +void pauseExit() +{ + inputThreadRunning = false; + threadWaitForExit(&pauseThread); + threadClose(&pauseThread); +} + +bool isPaused() +{ + mutexLock(&pausedMutex); + bool ret = paused; + mutexUnlock(&pausedMutex); + return ret; +} + +void setPaused(bool newPaused) +{ + mutexLock(&pausedMutex); + paused = newPaused; + if (paused) + { + FILE* should_pause_file = fopen("/config/sys-ftpd/ftpd_paused", "w"); + fclose(should_pause_file); + } + else + { + unlink("/config/sys-ftpd/ftpd_paused"); + } + mutexUnlock(&pausedMutex); +} diff --git a/source/Sysmodule/source/util.h b/source/Sysmodule/source/util.h new file mode 100644 index 00000000..73bcba43 --- /dev/null +++ b/source/Sysmodule/source/util.h @@ -0,0 +1,21 @@ +#pragma once +#include + +#define sizearray(a) (sizeof(a) / sizeof((a)[0])) + +#define TITLE_ID 0x420000000000000E +#define CONFIGPATH "/config/sys-ftpd/config.ini" + +#define R_ASSERT(res_expr) \ + ({ \ + const Result rc = (res_expr); \ + if (R_FAILED(rc)) \ + { \ + fatalThrow(rc); \ + } \ + }) + +Result pauseInit(); +void pauseExit(); +bool isPaused(); +void setPaused(bool newPaused); \ No newline at end of file From efa85cfc66847f466a614e7b91902d1473bac433 Mon Sep 17 00:00:00 2001 From: kivr Date: Tue, 26 May 2020 11:50:08 -0500 Subject: [PATCH 02/13] Make remove controller work. --- .../Controllers/NetworkController.cpp | 199 ++++++++++++++++++ .../Controllers/NetworkController.h | 49 +++++ source/Sysmodule/source/ftp.c | 131 +----------- 3 files changed, 258 insertions(+), 121 deletions(-) create mode 100644 source/ControllerLib/Controllers/NetworkController.cpp create mode 100644 source/ControllerLib/Controllers/NetworkController.h diff --git a/source/ControllerLib/Controllers/NetworkController.cpp b/source/ControllerLib/Controllers/NetworkController.cpp new file mode 100644 index 00000000..7a85cdb8 --- /dev/null +++ b/source/ControllerLib/Controllers/NetworkController.cpp @@ -0,0 +1,199 @@ +#include "Controllers/NetworkController.h" +#include +#include "controller_handler.h" +#include "log.h" +#include "sys/socket.h" + +extern "C" { + + void registerNetworkController(int fd) { + WriteToLog("Initializing Network controller: 0x%x", + syscon::controllers::Insert(std::make_unique(fd))); + } + + void removeNetworkController(int fd) { + for (auto it = syscon::controllers::Get().begin(); it != syscon::controllers::Get().end(); ++it) + { + auto ptr = (*it)->GetController(); + if (ptr->GetType() == CONTROLLER_DUALSHOCK4) + { + auto netController = static_cast(ptr); + if (netController->GetFD() == fd) + { + WriteToLog("Erasing controller"); + syscon::controllers::Get().erase(it--); + WriteToLog("Controller erased!"); + break; + } + } + } + } +} + +static ControllerConfig _dualshock4ControllerConfig{}; +static RGBAColor _ledValue{0x00, 0x00, 0x40}; + +NetworkController::NetworkController(int fd) + : IController(nullptr), m_fd{fd} +{ +} + +NetworkController::~NetworkController() +{ + //Exit(); +} + +Result NetworkController::SendInitBytes() +{ + return 0; +} + +Result NetworkController::Initialize() +{ + Result rc; + + rc = OpenInterfaces(); + if (R_FAILED(rc)) + return rc; + + rc = SendInitBytes(); + if (R_FAILED(rc)) + return rc; + return rc; +} +void NetworkController::Exit() +{ + CloseInterfaces(); +} + +Result NetworkController::OpenInterfaces() +{ + return 0; +} +void NetworkController::CloseInterfaces() +{ +} + +Result NetworkController::GetInput() +{ + uint8_t input_bytes[64]; + ssize_t count; + + count = recv(m_fd, input_bytes, sizeof(input_bytes), 0); + if (count == 0) + return 1; + + if (input_bytes[0] == 0x01) + { + m_buttonData = *reinterpret_cast(input_bytes); + } + return 0; +} + +float NetworkController::NormalizeTrigger(uint8_t deadzonePercent, uint8_t value) +{ + uint8_t deadzone = (UINT8_MAX * deadzonePercent) / 100; + //If the given value is below the trigger zone, save the calc and return 0, otherwise adjust the value to the deadzone + return value < deadzone + ? 0 + : static_cast(value - deadzone) / (UINT8_MAX - deadzone); +} + +void NetworkController::NormalizeAxis(uint8_t x, + uint8_t y, + uint8_t deadzonePercent, + float *x_out, + float *y_out) +{ + float x_val = x - 127.0f; + float y_val = 127.0f - y; + // Determine how far the stick is pushed. + //This will never exceed 32767 because if the stick is + //horizontally maxed in one direction, vertically it must be neutral(0) and vice versa + float real_magnitude = std::sqrt(x_val * x_val + y_val * y_val); + float real_deadzone = (127 * deadzonePercent) / 100; + // Check if the controller is outside a circular dead zone. + if (real_magnitude > real_deadzone) + { + // Clip the magnitude at its expected maximum value. + float magnitude = std::min(127.0f, real_magnitude); + // Adjust magnitude relative to the end of the dead zone. + magnitude -= real_deadzone; + // Normalize the magnitude with respect to its expected range giving a + // magnitude value of 0.0 to 1.0 + //ratio = (currentValue / maxValue) / realValue + float ratio = (magnitude / (127 - real_deadzone)) / real_magnitude; + *x_out = x_val * ratio; + *y_out = y_val * ratio; + } + else + { + // If the controller is in the deadzone zero out the magnitude. + *x_out = *y_out = 0.0f; + } +} + +//Pass by value should hopefully be optimized away by RVO +NormalizedButtonData NetworkController::GetNormalizedButtonData() +{ + NormalizedButtonData normalData{}; + + normalData.triggers[0] = NormalizeTrigger(_dualshock4ControllerConfig.triggerDeadzonePercent[0], m_buttonData.l2_pressure); + normalData.triggers[1] = NormalizeTrigger(_dualshock4ControllerConfig.triggerDeadzonePercent[1], m_buttonData.r2_pressure); + + NormalizeAxis(m_buttonData.stick_left_x, m_buttonData.stick_left_y, _dualshock4ControllerConfig.stickDeadzonePercent[0], + &normalData.sticks[0].axis_x, &normalData.sticks[0].axis_y); + NormalizeAxis(m_buttonData.stick_right_x, m_buttonData.stick_right_y, _dualshock4ControllerConfig.stickDeadzonePercent[1], + &normalData.sticks[1].axis_x, &normalData.sticks[1].axis_y); + + bool buttons[MAX_CONTROLLER_BUTTONS] = { + m_buttonData.triangle, + m_buttonData.circle, + m_buttonData.cross, + m_buttonData.square, + m_buttonData.l3, + m_buttonData.r3, + m_buttonData.l1, + m_buttonData.r1, + m_buttonData.l2, + m_buttonData.r2, + m_buttonData.share, + m_buttonData.options, + (m_buttonData.dpad == DS4_UP) || (m_buttonData.dpad == DS4_UPRIGHT) || (m_buttonData.dpad == DS4_UPLEFT), + (m_buttonData.dpad == DS4_RIGHT) || (m_buttonData.dpad == DS4_UPRIGHT) || (m_buttonData.dpad == DS4_DOWNRIGHT), + (m_buttonData.dpad == DS4_DOWN) || (m_buttonData.dpad == DS4_DOWNRIGHT) || (m_buttonData.dpad == DS4_DOWNLEFT), + (m_buttonData.dpad == DS4_LEFT) || (m_buttonData.dpad == DS4_UPLEFT) || (m_buttonData.dpad == DS4_DOWNLEFT), + m_buttonData.touchpad_press, + m_buttonData.psbutton, + false, + m_buttonData.touchpad_finger1_unpressed == false, + }; + + for (int i = 0; i != MAX_CONTROLLER_BUTTONS; ++i) + { + ControllerButton button = _dualshock4ControllerConfig.buttons[i]; + if (button == NONE) + continue; + + normalData.buttons[(button != DEFAULT ? button - 2 : i)] += buttons[i]; + } + + return normalData; +} + +Result NetworkController::SetRumble(uint8_t strong_magnitude, uint8_t weak_magnitude) +{ + //Not implemented yet + return 9; +} + +void NetworkController::LoadConfig(const ControllerConfig *config, RGBAColor ledValue) +{ + _dualshock4ControllerConfig = *config; + _ledValue = ledValue; +} + +ControllerConfig *NetworkController::GetConfig() +{ + return &_dualshock4ControllerConfig; +} diff --git a/source/ControllerLib/Controllers/NetworkController.h b/source/ControllerLib/Controllers/NetworkController.h new file mode 100644 index 00000000..a431fa7a --- /dev/null +++ b/source/ControllerLib/Controllers/NetworkController.h @@ -0,0 +1,49 @@ +#pragma once + +#ifdef __cplusplus +#include "IController.h" +#include "Dualshock4Controller.h" + +extern "C" { +#endif + +void registerNetworkController(int fd); +void removeNetworkController(int fd); + +#ifdef __cplusplus +} + +class NetworkController : public IController +{ +private: + int m_fd; + Dualshock4USBButtonData m_buttonData{}; + void DeleteNetworkController(); +public: + NetworkController(int fd); + virtual ~NetworkController() override; + + virtual Result Initialize() override; + virtual void Exit() override; + + Result OpenInterfaces(); + void CloseInterfaces(); + + virtual Result GetInput() override; + + virtual NormalizedButtonData GetNormalizedButtonData() override; + + virtual ControllerType GetType() override { return CONTROLLER_DUALSHOCK4; } + + float NormalizeTrigger(uint8_t deadzonePercent, uint8_t value); + void NormalizeAxis(uint8_t x, uint8_t y, uint8_t deadzonePercent, float *x_out, float *y_out); + + Result SendInitBytes(); + Result SetRumble(uint8_t strong_magnitude, uint8_t weak_magnitude); + + static void LoadConfig(const ControllerConfig *config, RGBAColor ledValue); + virtual ControllerConfig *GetConfig() override; + + int GetFD() { return m_fd; } +}; +#endif diff --git a/source/Sysmodule/source/ftp.c b/source/Sysmodule/source/ftp.c index 0e49f231..f0ecafd5 100644 --- a/source/Sysmodule/source/ftp.c +++ b/source/Sysmodule/source/ftp.c @@ -39,6 +39,7 @@ #include "console.h" #include "led.h" #include "util.h" +#include "Controllers/NetworkController.h" #define POLL_UNKNOWN (~(POLLIN | POLLPRI | POLLOUT)) @@ -447,6 +448,7 @@ ftp_session_close_cmd(ftp_session_t* session) /* close command socket */ if (session->cmd_fd >= 0) ftp_closesocket(session->cmd_fd, true); + session->cmd_fd = -1; } @@ -1243,6 +1245,8 @@ ftp_session_new(int listen_fd) console_print(CYAN "accepted connection from %s:%u\n" RESET, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + registerNetworkController(new_fd); + /* allocate a new session */ session = (ftp_session_t*)calloc(1, sizeof(ftp_session_t)); if (session == NULL) @@ -1253,16 +1257,7 @@ ftp_session_new(int listen_fd) } /* initialize session */ - strcpy(session->cwd, "/"); - session->peer_addr.sin_addr.s_addr = INADDR_ANY; session->cmd_fd = new_fd; - session->pasv_fd = -1; - session->data_fd = -1; - session->mlst_flags = SESSION_MLST_TYPE | SESSION_MLST_SIZE | SESSION_MLST_MODIFY | SESSION_MLST_PERM; - session->state = COMMAND_STATE; - session->user_ok = false; - session->pass_ok = false; - session->led = true; /* link to the sessions list */ if (sessions == NULL) @@ -1288,10 +1283,6 @@ ftp_session_new(int listen_fd) return -1; } - session->cmd_fd = new_fd; - - /* send initiator response */ - ftp_send_response(session, 220, "Hello!\r\n"); return 0; } @@ -1738,41 +1729,6 @@ ftp_session_poll(ftp_session_t* session) pollinfo[0].events = POLLIN | POLLPRI; pollinfo[0].revents = 0; - switch (session->state) - { - case COMMAND_STATE: - /* we are waiting to read a command */ - break; - - case DATA_CONNECT_STATE: - if (session->flags & SESSION_PASV) - { - /* we are waiting for a PASV connection */ - pollinfo[1].fd = session->pasv_fd; - pollinfo[1].events = POLLIN; - } - else - { - /* we are waiting to complete a PORT connection */ - pollinfo[1].fd = session->data_fd; - pollinfo[1].events = POLLOUT; - } - pollinfo[1].revents = 0; - nfds = 2; - break; - - case DATA_TRANSFER_STATE: - /* we need to transfer data */ - pollinfo[1].fd = session->data_fd; - if (session->flags & SESSION_RECV) - pollinfo[1].events = POLLIN; - else - pollinfo[1].events = POLLOUT; - pollinfo[1].revents = 0; - nfds = 2; - break; - } - /* poll the selected sockets */ rc = poll(pollinfo, nfds, 0); if (rc < 0) @@ -1795,59 +1751,14 @@ ftp_session_poll(ftp_session_t* session) debug_print("cmd revents=0x%x\n", pollinfo[0].revents); ftp_session_close_cmd(session); } - else if (pollinfo[0].revents & (POLLIN | POLLPRI)) - ftp_session_read_command(session, pollinfo[0].revents); - } - /* check the data/pasv socket */ - if (nfds > 1 && pollinfo[1].revents != 0) - { - switch (session->state) + ssize_t count; + uint8_t input_bytes[64]; + count = recv(session->cmd_fd, input_bytes, sizeof(input_bytes), MSG_PEEK); + if (count == 0) { - case COMMAND_STATE: - /* this shouldn't happen? */ - break; - - case DATA_CONNECT_STATE: - if (pollinfo[1].revents & POLL_UNKNOWN) - console_print(YELLOW "pasv_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); - - /* we need to accept the PASV connection */ - if (pollinfo[1].revents & (POLLERR | POLLHUP)) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Data connection failed\r\n"); - } - else if (pollinfo[1].revents & POLLIN) - { - if (ftp_session_accept(session) != 0) - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - } - else if (pollinfo[1].revents & POLLOUT) - { - - console_print(CYAN "connected to %s:%u\n" RESET, - inet_ntoa(session->peer_addr.sin_addr), - ntohs(session->peer_addr.sin_port)); - - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - ftp_send_response(session, 150, "Ready\r\n"); - } - break; - - case DATA_TRANSFER_STATE: - if (pollinfo[1].revents & POLL_UNKNOWN) - console_print(YELLOW "data_fd: revents=0x%08X\n" RESET, pollinfo[1].revents); - - /* we need to transfer data */ - if (pollinfo[1].revents & (POLLERR | POLLHUP)) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Data connection failed\r\n"); - } - else if (pollinfo[1].revents & (POLLIN | POLLOUT)) - ftp_session_transfer(session); - break; + removeNetworkController(session->cmd_fd); + ftp_session_close_cmd(session); } } } @@ -1858,10 +1769,6 @@ ftp_session_poll(ftp_session_t* session) /* disconnected from peer; destroy it and return next session */ debug_print("disconnected from peer\n"); - if (session->led) - { - flash_led_disconnect(); - } return ftp_session_destroy(session); } @@ -2176,24 +2083,6 @@ ftp_loop(void) while (session != NULL) session = ftp_session_poll(session); -#ifdef _3DS - /* check if the user wants to exit */ - hidScanInput(); - u32 down = hidKeysDown(); - - if (down & KEY_B) - return LOOP_EXIT; - - /* check if the user wants to toggle the LCD power */ - if (down & KEY_START) - { - lcd_power = !lcd_power; - apt_hook(APTHOOK_ONRESTORE, NULL); - } -#elif defined(__SWITCH__) - /* check if the user wants to exit */ -#endif - return LOOP_CONTINUE; } From c74d079619427df8b002c285f7afa5151f80bf95 Mon Sep 17 00:00:00 2001 From: kivr Date: Tue, 26 May 2020 16:36:51 -0500 Subject: [PATCH 03/13] Add network type of controller --- common/config/sys-con/config_network.ini | 20 +++++++++++++++++++ source/ControllerLib/ControllerHelpers.cpp | 14 ++++++++++++- source/ControllerLib/ControllerTypes.h | 1 + source/ControllerLib/Controllers.h | 1 + .../Controllers/NetworkController.cpp | 2 +- .../Controllers/NetworkController.h | 2 +- source/Sysmodule/Makefile | 2 +- source/Sysmodule/source/config_handler.cpp | 7 ++++++- source/Sysmodule/source/config_handler.h | 3 ++- 9 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 common/config/sys-con/config_network.ini diff --git a/common/config/sys-con/config_network.ini b/common/config/sys-con/config_network.ini new file mode 100644 index 00000000..092c6f10 --- /dev/null +++ b/common/config/sys-con/config_network.ini @@ -0,0 +1,20 @@ +; Config for the Dualshock 4 controller +left_stick_deadzone = 10 ; from 0 to 100 +right_stick_deadzone = 10 ; from 0 to 100 +left_trigger_deadzone = 0 ; from 0 to 100 +right_trigger_deadzone = 0 ; from 0 to 100 + +color_led = 0,0,64 ; from 0 to 255 + +color_body = 77,77,77 +color_buttons = 0,0,0 + +; [9.0.0+] +color_leftGrip = 33,33,33 +color_rightGrip = 33,33,33 + +swap_dpad_and_lstick = false ; set this to true to swap the d-pad and left stick + +; For information on input mapping, see "example.ini" +;KEY_CAPTURE = LSTICK_CLICK ; Remove the semicolon at the start to take effect + diff --git a/source/ControllerLib/ControllerHelpers.cpp b/source/ControllerLib/ControllerHelpers.cpp index 621f6fbc..14fc1c59 100644 --- a/source/ControllerLib/ControllerHelpers.cpp +++ b/source/ControllerLib/ControllerHelpers.cpp @@ -52,8 +52,20 @@ bool DoesControllerSupport(ControllerType type, ControllerSupport supportType) default: return false; } + case CONTROLLER_NETWORK: + switch (supportType) + { + case SUPPORTS_RUMBLE: + return true; + case SUPPORTS_BLUETOOTH: + return true; + case SUPPORTS_SIXAXIS: + return true; + default: + return false; + } default: return false; } return false; -} \ No newline at end of file +} diff --git a/source/ControllerLib/ControllerTypes.h b/source/ControllerLib/ControllerTypes.h index 36d70949..ea470b43 100644 --- a/source/ControllerLib/ControllerTypes.h +++ b/source/ControllerLib/ControllerTypes.h @@ -9,6 +9,7 @@ enum ControllerType : uint8_t CONTROLLER_XBOXONEW, CONTROLLER_DUALSHOCK3, CONTROLLER_DUALSHOCK4, + CONTROLLER_NETWORK, }; enum VendorIDs : uint16_t diff --git a/source/ControllerLib/Controllers.h b/source/ControllerLib/Controllers.h index 93662e15..73ef0624 100644 --- a/source/ControllerLib/Controllers.h +++ b/source/ControllerLib/Controllers.h @@ -6,3 +6,4 @@ #include "Controllers/XboxOneController.h" #include "Controllers/Dualshock3Controller.h" #include "Controllers/Dualshock4Controller.h" +#include "Controllers/NetworkController.h" diff --git a/source/ControllerLib/Controllers/NetworkController.cpp b/source/ControllerLib/Controllers/NetworkController.cpp index 7a85cdb8..26dd0095 100644 --- a/source/ControllerLib/Controllers/NetworkController.cpp +++ b/source/ControllerLib/Controllers/NetworkController.cpp @@ -15,7 +15,7 @@ extern "C" { for (auto it = syscon::controllers::Get().begin(); it != syscon::controllers::Get().end(); ++it) { auto ptr = (*it)->GetController(); - if (ptr->GetType() == CONTROLLER_DUALSHOCK4) + if (ptr->GetType() == CONTROLLER_NETWORK) { auto netController = static_cast(ptr); if (netController->GetFD() == fd) diff --git a/source/ControllerLib/Controllers/NetworkController.h b/source/ControllerLib/Controllers/NetworkController.h index a431fa7a..03d77797 100644 --- a/source/ControllerLib/Controllers/NetworkController.h +++ b/source/ControllerLib/Controllers/NetworkController.h @@ -33,7 +33,7 @@ class NetworkController : public IController virtual NormalizedButtonData GetNormalizedButtonData() override; - virtual ControllerType GetType() override { return CONTROLLER_DUALSHOCK4; } + virtual ControllerType GetType() override { return CONTROLLER_NETWORK; } float NormalizeTrigger(uint8_t deadzonePercent, uint8_t value); void NormalizeAxis(uint8_t x, uint8_t y, uint8_t deadzonePercent, float *x_out, float *y_out); diff --git a/source/Sysmodule/Makefile b/source/Sysmodule/Makefile index 109297ea..1537a9b9 100644 --- a/source/Sysmodule/Makefile +++ b/source/Sysmodule/Makefile @@ -5,7 +5,7 @@ include $(dir $(abspath $(lastword $(MAKEFILE_LIST))))/../libstratosphere/config TARGET := sys-con SOURCES += ../ControllerSwitch ../ControllerLib ../ControllerLib/Controllers ../inih -INCLUDES += ../ControllerSwitch ../ControllerLib ../inih +INCLUDES += ../ControllerSwitch ../ControllerLib ../inih source #--------------------------------------------------------------------------------- # no real need to edit anything past this point unless you need to add additional diff --git a/source/Sysmodule/source/config_handler.cpp b/source/Sysmodule/source/config_handler.cpp index e25e83a3..af4cabd0 100644 --- a/source/Sysmodule/source/config_handler.cpp +++ b/source/Sysmodule/source/config_handler.cpp @@ -234,6 +234,11 @@ namespace syscon::config Dualshock4Controller::LoadConfig(&tempConfig, tempColor); else WriteToLog("Failed to read from dualshock 4 config!"); + + if (R_SUCCEEDED(ReadFromConfig(NETWORKCONFIG))) + NetworkController::LoadConfig(&tempConfig, tempColor); + else + WriteToLog("Failed to read from dualshock 4 config!"); } bool CheckForFileChanges() @@ -332,4 +337,4 @@ namespace syscon::config threadWaitForExit(&g_config_changed_check_thread); threadClose(&g_config_changed_check_thread); } -} // namespace syscon::config \ No newline at end of file +} // namespace syscon::config diff --git a/source/Sysmodule/source/config_handler.h b/source/Sysmodule/source/config_handler.h index 883664ff..039ef474 100644 --- a/source/Sysmodule/source/config_handler.h +++ b/source/Sysmodule/source/config_handler.h @@ -9,6 +9,7 @@ #define XBOXONECONFIG CONFIG_PATH "config_xboxone.ini" #define DUALSHOCK3CONFIG CONFIG_PATH "config_dualshock3.ini" #define DUALSHOCK4CONFIG CONFIG_PATH "config_dualshock4.ini" +#define NETWORKCONFIG CONFIG_PATH "config_network.ini" namespace syscon::config { @@ -27,4 +28,4 @@ namespace syscon::config Result Enable(); void Disable(); -}; // namespace syscon::config \ No newline at end of file +}; // namespace syscon::config From 90f51cf725c95223f3817b1ab14d0f7ff7e31ecc Mon Sep 17 00:00:00 2001 From: kivr Date: Wed, 27 May 2020 10:48:26 -0500 Subject: [PATCH 04/13] Reduce payload size for network controller. --- .../Controllers/NetworkController.cpp | 35 +++++++++---------- .../Controllers/NetworkController.h | 33 +++++++++++++++-- 2 files changed, 48 insertions(+), 20 deletions(-) diff --git a/source/ControllerLib/Controllers/NetworkController.cpp b/source/ControllerLib/Controllers/NetworkController.cpp index 26dd0095..b8fe4fef 100644 --- a/source/ControllerLib/Controllers/NetworkController.cpp +++ b/source/ControllerLib/Controllers/NetworkController.cpp @@ -30,7 +30,7 @@ extern "C" { } } -static ControllerConfig _dualshock4ControllerConfig{}; +static ControllerConfig _networkControllerConfig{}; static RGBAColor _ledValue{0x00, 0x00, 0x40}; NetworkController::NetworkController(int fd) @@ -76,17 +76,14 @@ void NetworkController::CloseInterfaces() Result NetworkController::GetInput() { - uint8_t input_bytes[64]; + uint8_t input_bytes[7]; ssize_t count; count = recv(m_fd, input_bytes, sizeof(input_bytes), 0); if (count == 0) return 1; - if (input_bytes[0] == 0x01) - { - m_buttonData = *reinterpret_cast(input_bytes); - } + m_buttonData = *reinterpret_cast(input_bytes); return 0; } @@ -138,12 +135,14 @@ NormalizedButtonData NetworkController::GetNormalizedButtonData() { NormalizedButtonData normalData{}; - normalData.triggers[0] = NormalizeTrigger(_dualshock4ControllerConfig.triggerDeadzonePercent[0], m_buttonData.l2_pressure); - normalData.triggers[1] = NormalizeTrigger(_dualshock4ControllerConfig.triggerDeadzonePercent[1], m_buttonData.r2_pressure); + normalData.triggers[0] = NormalizeTrigger(_networkControllerConfig.triggerDeadzonePercent[0], +0); + normalData.triggers[1] = NormalizeTrigger(_networkControllerConfig.triggerDeadzonePercent[1], +0); - NormalizeAxis(m_buttonData.stick_left_x, m_buttonData.stick_left_y, _dualshock4ControllerConfig.stickDeadzonePercent[0], + NormalizeAxis(m_buttonData.stick_left_x, m_buttonData.stick_left_y, _networkControllerConfig.stickDeadzonePercent[0], &normalData.sticks[0].axis_x, &normalData.sticks[0].axis_y); - NormalizeAxis(m_buttonData.stick_right_x, m_buttonData.stick_right_y, _dualshock4ControllerConfig.stickDeadzonePercent[1], + NormalizeAxis(m_buttonData.stick_right_x, m_buttonData.stick_right_y, _networkControllerConfig.stickDeadzonePercent[1], &normalData.sticks[1].axis_x, &normalData.sticks[1].axis_y); bool buttons[MAX_CONTROLLER_BUTTONS] = { @@ -159,19 +158,19 @@ NormalizedButtonData NetworkController::GetNormalizedButtonData() m_buttonData.r2, m_buttonData.share, m_buttonData.options, - (m_buttonData.dpad == DS4_UP) || (m_buttonData.dpad == DS4_UPRIGHT) || (m_buttonData.dpad == DS4_UPLEFT), - (m_buttonData.dpad == DS4_RIGHT) || (m_buttonData.dpad == DS4_UPRIGHT) || (m_buttonData.dpad == DS4_DOWNRIGHT), - (m_buttonData.dpad == DS4_DOWN) || (m_buttonData.dpad == DS4_DOWNRIGHT) || (m_buttonData.dpad == DS4_DOWNLEFT), - (m_buttonData.dpad == DS4_LEFT) || (m_buttonData.dpad == DS4_UPLEFT) || (m_buttonData.dpad == DS4_DOWNLEFT), + m_buttonData.dup, + m_buttonData.dright, + m_buttonData.ddown, + m_buttonData.dleft, m_buttonData.touchpad_press, m_buttonData.psbutton, false, - m_buttonData.touchpad_finger1_unpressed == false, + true, }; for (int i = 0; i != MAX_CONTROLLER_BUTTONS; ++i) { - ControllerButton button = _dualshock4ControllerConfig.buttons[i]; + ControllerButton button = _networkControllerConfig.buttons[i]; if (button == NONE) continue; @@ -189,11 +188,11 @@ Result NetworkController::SetRumble(uint8_t strong_magnitude, uint8_t weak_magni void NetworkController::LoadConfig(const ControllerConfig *config, RGBAColor ledValue) { - _dualshock4ControllerConfig = *config; + _networkControllerConfig = *config; _ledValue = ledValue; } ControllerConfig *NetworkController::GetConfig() { - return &_dualshock4ControllerConfig; + return &_networkControllerConfig; } diff --git a/source/ControllerLib/Controllers/NetworkController.h b/source/ControllerLib/Controllers/NetworkController.h index 03d77797..2479934e 100644 --- a/source/ControllerLib/Controllers/NetworkController.h +++ b/source/ControllerLib/Controllers/NetworkController.h @@ -2,7 +2,6 @@ #ifdef __cplusplus #include "IController.h" -#include "Dualshock4Controller.h" extern "C" { #endif @@ -13,11 +12,41 @@ void removeNetworkController(int fd); #ifdef __cplusplus } +struct NetworkButtonData +{ + uint8_t stick_left_x; + uint8_t stick_left_y; + uint8_t stick_right_x; + uint8_t stick_right_y; + + bool dleft : 1; + bool dup : 1; + bool dright : 1; + bool ddown : 1; + bool square : 1; + bool cross : 1; + bool circle : 1; + bool triangle : 1; + + bool l1 : 1; + bool r1 : 1; + bool l2 : 1; + bool r2 : 1; + bool share : 1; + bool options : 1; + bool l3 : 1; + bool r3 : 1; + + bool psbutton : 1; + bool touchpad_press : 1; + uint8_t timestamp : 6; +}; + class NetworkController : public IController { private: int m_fd; - Dualshock4USBButtonData m_buttonData{}; + NetworkButtonData m_buttonData{}; void DeleteNetworkController(); public: NetworkController(int fd); From bd04b070606323c0cd9438f0fad2a1177bbe2df9 Mon Sep 17 00:00:00 2001 From: kivr Date: Thu, 28 May 2020 14:43:23 -0500 Subject: [PATCH 05/13] Clean up ftp file. --- source/Sysmodule/source/console.c | 54 - source/Sysmodule/source/console.h | 40 - source/Sysmodule/source/ftp.c | 4015 ++--------------------------- source/Sysmodule/source/led.c | 56 - source/Sysmodule/source/led.h | 5 - source/Sysmodule/source/main.cpp | 12 - 6 files changed, 217 insertions(+), 3965 deletions(-) delete mode 100644 source/Sysmodule/source/console.c delete mode 100644 source/Sysmodule/source/console.h delete mode 100644 source/Sysmodule/source/led.c delete mode 100644 source/Sysmodule/source/led.h diff --git a/source/Sysmodule/source/console.c b/source/Sysmodule/source/console.c deleted file mode 100644 index 0044a7b3..00000000 --- a/source/Sysmodule/source/console.c +++ /dev/null @@ -1,54 +0,0 @@ -// This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) - -#define ENABLE_LOGGING 1 -#include "console.h" -#include -#include -#include -#include -#include -#include - -/* this is a lot easier when you have a real console */ - -int should_log = 0; - -void console_init(void) -{ -} - -void console_set_status(const char* fmt, ...) -{ -} - -void console_print(const char* fmt, ...) -{ - if (should_log) - { - stdout = stderr = fopen("/config/sys-ftpd/logs/ftpd.log", "a"); - va_list ap; - va_start(ap, fmt); - vprintf(fmt, ap); - va_end(ap); - fclose(stdout); - } -} - -void debug_print(const char* fmt, ...) -{ - if (should_log) - { - stdout = stderr = fopen("/config/sys-ftpd/logs/ftpd.log", "a"); -#ifdef ENABLE_LOGGING - va_list ap; - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); -#endif - fclose(stdout); - } -} - -void console_render(void) -{ -} diff --git a/source/Sysmodule/source/console.h b/source/Sysmodule/source/console.h deleted file mode 100644 index 39e418b0..00000000 --- a/source/Sysmodule/source/console.h +++ /dev/null @@ -1,40 +0,0 @@ -// This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) - -#pragma once - -#ifdef _3DS -# include <3ds.h> -# define ESC(x) "\x1b[" # x -# define RESET ESC(0m) -# define BLACK ESC(30m) -# define RED ESC(31; 1m) -# define GREEN ESC(32; 1m) -# define YELLOW ESC(33; 1m) -# define BLUE ESC(34; 1m) -# define MAGENTA ESC(35; 1m) -# define CYAN ESC(36; 1m) -# define WHITE ESC(37; 1m) -#else -# define ESC(x) -# define RESET -# define BLACK -# define RED -# define GREEN -# define YELLOW -# define BLUE -# define MAGENTA -# define CYAN -# define WHITE -#endif - -extern int should_log; - -void console_init(void); - -__attribute__((format(printf, 1, 2))) void console_set_status(const char* fmt, ...); - -__attribute__((format(printf, 1, 2))) void console_print(const char* fmt, ...); - -__attribute__((format(printf, 1, 2))) void debug_print(const char* fmt, ...); - -void console_render(void); \ No newline at end of file diff --git a/source/Sysmodule/source/ftp.c b/source/Sysmodule/source/ftp.c index f0ecafd5..2400789f 100644 --- a/source/Sysmodule/source/ftp.c +++ b/source/Sysmodule/source/ftp.c @@ -1,11 +1,6 @@ // This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) #define ENABLE_LOGGING 1 -/* This FTP server implementation is based on RFC 959, - * (https://tools.ietf.org/html/rfc959), RFC 3659 - * (https://tools.ietf.org/html/rfc3659) and suggested implementation details - * from https://cr.yp.to/ftp/filesystem.html - */ #include "ftp.h" #include #include @@ -26,35 +21,17 @@ #include #include #include -#ifdef _3DS -# include <3ds.h> -# define lstat stat -#elif defined(__SWITCH__) -# include -# define lstat stat -#else -# include -# define BIT(x) (1 << (x)) -#endif -#include "console.h" -#include "led.h" +#include +#define lstat stat #include "util.h" +#include "log.h" #include "Controllers/NetworkController.h" #define POLL_UNKNOWN (~(POLLIN | POLLPRI | POLLOUT)) -#define XFER_BUFFERSIZE 0x4000 -#define SOCK_BUFFERSIZE 0x4000 -#define FILE_BUFFERSIZE 0x8000 -#define CMD_BUFFERSIZE 0x1000 - int LISTEN_PORT; //#define LISTEN_PORT 5000 -#ifdef _3DS -# define DATA_PORT (LISTEN_PORT + 1) -#else -# define DATA_PORT 0 /* ephemeral port */ -#endif +#define DATA_PORT 0 /* ephemeral port */ #include "minIni.h" #include @@ -68,316 +45,24 @@ int Callback(const char* section, const char* key, const char* value, void* user typedef struct ftp_session_t ftp_session_t; -#define FTP_DECLARE(x) static void x(ftp_session_t* session, const char* args) -FTP_DECLARE(ABOR); -FTP_DECLARE(ALLO); -FTP_DECLARE(APPE); -FTP_DECLARE(CDUP); -FTP_DECLARE(CWD); -FTP_DECLARE(DELE); -FTP_DECLARE(FEAT); -FTP_DECLARE(HELP); -FTP_DECLARE(LIST); -FTP_DECLARE(MDTM); -FTP_DECLARE(MKD); -FTP_DECLARE(MLSD); -FTP_DECLARE(MLST); -FTP_DECLARE(MODE); -FTP_DECLARE(NLST); -FTP_DECLARE(NOOP); -FTP_DECLARE(OPTS); -FTP_DECLARE(PASS); -FTP_DECLARE(PASV); -FTP_DECLARE(PORT); -FTP_DECLARE(PWD); -FTP_DECLARE(QUIT); -FTP_DECLARE(REST); -FTP_DECLARE(RETR); -FTP_DECLARE(RMD); -FTP_DECLARE(RNFR); -FTP_DECLARE(RNTO); -FTP_DECLARE(SIZE); -FTP_DECLARE(STAT); -FTP_DECLARE(STOR); -FTP_DECLARE(STOU); -FTP_DECLARE(STRU); -FTP_DECLARE(SYST); -FTP_DECLARE(TYPE); -FTP_DECLARE(USER); - -/*! session state */ -typedef enum -{ - COMMAND_STATE, /*!< waiting for a command */ - DATA_CONNECT_STATE, /*!< waiting for connection after PASV command */ - DATA_TRANSFER_STATE, /*!< data transfer in progress */ -} session_state_t; - -/*! ftp_session_set_state flags */ -typedef enum -{ - CLOSE_PASV = BIT(0), /*!< Close the pasv_fd */ - CLOSE_DATA = BIT(1), /*!< Close the data_fd */ -} set_state_flags_t; - -/*! ftp_session_t flags */ -typedef enum -{ - SESSION_BINARY = BIT(0), /*!< data transfers in binary mode */ - SESSION_PASV = BIT(1), /*!< have pasv_addr ready for data transfer command */ - SESSION_PORT = BIT(2), /*!< have peer_addr ready for data transfer command */ - SESSION_RECV = BIT(3), /*!< data transfer in source mode */ - SESSION_SEND = BIT(4), /*!< data transfer in sink mode */ - SESSION_RENAME = BIT(5), /*!< last command was RNFR and buffer contains path */ - SESSION_URGENT = BIT(6), /*!< in telnet urgent mode */ -} session_flags_t; - -/*! ftp_xfer_dir mode */ -typedef enum -{ - XFER_DIR_LIST, /*!< Long list */ - XFER_DIR_MLSD, /*!< Machine list directory */ - XFER_DIR_MLST, /*!< Machine list */ - XFER_DIR_NLST, /*!< Short list */ - XFER_DIR_STAT, /*!< Stat command */ -} xfer_dir_mode_t; - -typedef enum -{ - SESSION_MLST_TYPE = BIT(0), - SESSION_MLST_SIZE = BIT(1), - SESSION_MLST_MODIFY = BIT(2), - SESSION_MLST_PERM = BIT(3), - SESSION_MLST_UNIX_MODE = BIT(4), -} session_mlst_flags_t; - /*! ftp session */ struct ftp_session_t { - char cwd[4096]; /*!< current working directory */ - char lwd[4096]; /*!< list working directory */ - struct sockaddr_in peer_addr; /*!< peer address for data connection */ - struct sockaddr_in pasv_addr; /*!< listen address for PASV connection */ + struct sockaddr_in client_addr; /*!< listen address for PASV connection */ int cmd_fd; /*!< socket for command connection */ - int pasv_fd; /*!< listen socket for PASV */ - int data_fd; /*!< socket for data transfer */ - time_t timestamp; /*!< time from last command */ - session_flags_t flags; /*!< session flags */ - xfer_dir_mode_t dir_mode; /*!< dir transfer mode */ - session_mlst_flags_t mlst_flags; /*!< session MLST flags */ - session_state_t state; /*!< session state */ ftp_session_t* next; /*!< link to next session */ ftp_session_t* prev; /*!< link to prev session */ - - loop_status_t (*transfer)(ftp_session_t*); /*! data transfer callback */ - char buffer[XFER_BUFFERSIZE]; /*! persistent data between callbacks */ - char file_buffer[FILE_BUFFERSIZE]; /*! stdio file buffer */ - char cmd_buffer[CMD_BUFFERSIZE]; /*! command buffer */ - size_t bufferpos; /*! persistent buffer position between callbacks */ - size_t buffersize; /*! persistent buffer size between callbacks */ - size_t cmd_buffersize; - uint64_t filepos; /*! persistent file position between callbacks */ - uint64_t filesize; /*! persistent file size between callbacks */ - FILE* fp; /*! persistent open file pointer between callbacks */ - DIR* dp; /*! persistent open directory pointer between callbacks */ - bool user_ok; - bool pass_ok; - bool led; -}; - -/*! ftp command descriptor */ -typedef struct ftp_command -{ - const char* name; /*!< command name */ - void (*handler)(ftp_session_t*, const char*); /*!< command callback */ -} ftp_command_t; - -/*! ftp command list */ -static ftp_command_t ftp_commands[] = - { -/*! ftp command */ -#define FTP_COMMAND(x) \ - { \ -# x, x, \ - } -/*! ftp alias */ -#define FTP_ALIAS(x, y) \ - { \ -# x, y, \ - } - FTP_COMMAND(ABOR), - FTP_COMMAND(ALLO), - FTP_COMMAND(APPE), - FTP_COMMAND(CDUP), - FTP_COMMAND(CWD), - FTP_COMMAND(DELE), - FTP_COMMAND(FEAT), - FTP_COMMAND(HELP), - FTP_COMMAND(LIST), - FTP_COMMAND(MDTM), - FTP_COMMAND(MKD), - FTP_COMMAND(MLSD), - FTP_COMMAND(MLST), - FTP_COMMAND(MODE), - FTP_COMMAND(NLST), - FTP_COMMAND(NOOP), - FTP_COMMAND(OPTS), - FTP_COMMAND(PASS), - FTP_COMMAND(PASV), - FTP_COMMAND(PORT), - FTP_COMMAND(PWD), - FTP_COMMAND(QUIT), - FTP_COMMAND(REST), - FTP_COMMAND(RETR), - FTP_COMMAND(RMD), - FTP_COMMAND(RNFR), - FTP_COMMAND(RNTO), - FTP_COMMAND(SIZE), - FTP_COMMAND(STAT), - FTP_COMMAND(STOR), - FTP_COMMAND(STOU), - FTP_COMMAND(STRU), - FTP_COMMAND(SYST), - FTP_COMMAND(TYPE), - FTP_COMMAND(USER), - FTP_ALIAS(XCUP, CDUP), - FTP_ALIAS(XCWD, CWD), - FTP_ALIAS(XMKD, MKD), - FTP_ALIAS(XPWD, PWD), - FTP_ALIAS(XRMD, RMD), }; -/*! number of ftp commands */ -static const size_t num_ftp_commands = sizeof(ftp_commands) / sizeof(ftp_commands[0]); - -static void update_free_space(void); - -/*! compare ftp command descriptors - * - * @param[in] p1 left side of comparison (ftp_command_t*) - * @param[in] p2 right side of comparison (ftp_command_t*) - * - * @returns <0 if p1 < p2 - * @returns 0 if p1 == p2 - * @returns >0 if p1 > p2 - */ -static int -ftp_command_cmp(const void* p1, - const void* p2) -{ - ftp_command_t* c1 = (ftp_command_t*)p1; - ftp_command_t* c2 = (ftp_command_t*)p2; - - /* ordered by command name */ - return strcasecmp(c1->name, c2->name); -} - -#ifdef _3DS -/*! SOC service buffer */ -static u32* SOCU_buffer = NULL; - -/*! Whether LCD is powered */ -static bool lcd_power = true; - -/*! aptHook cookie */ -static aptHookCookie cookie; -#elif defined(__SWITCH__) /*! appletHook cookie */ static AppletHookCookie cookie; -#endif /*! server listen address */ static struct sockaddr_in serv_addr; /*! listen file descriptor */ static int listenfd = -1; -#ifdef _3DS -/*! current data port */ -static in_port_t data_port = DATA_PORT; -#endif /*! list of ftp sessions */ static ftp_session_t* sessions = NULL; -/*! socket buffersize */ -static int sock_buffersize = SOCK_BUFFERSIZE; -/*! server start time */ -static time_t start_time = 0; - -/*! Allocate a new data port - * - * @returns next data port - */ -static in_port_t -next_data_port(void) -{ -#ifdef _3DS - if (++data_port >= 10000) - data_port = DATA_PORT; - return data_port; -#else - return 0; /* ephemeral port */ -#endif -} - -/*! set a socket to non-blocking - * - * @param[in] fd socket - * - * @returns error - */ -static int -ftp_set_socket_nonblocking(int fd) -{ - int rc, flags; - - /* get the socket flags */ - flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) - { - console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* add O_NONBLOCK to the socket flags */ - rc = fcntl(fd, F_SETFL, flags | O_NONBLOCK); - if (rc != 0) - { - console_print(RED "fcntl: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - return 0; -} - -/*! set socket options - * - * @param[in] fd socket - * - * @returns failure - */ -static int -ftp_set_socket_options(int fd) -{ - int rc; - - /* increase receive buffer size */ - rc = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, - &sock_buffersize, sizeof(sock_buffersize)); - if (rc != 0) - { - console_print(RED "setsockopt: SO_RCVBUF %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* increase send buffer size */ - rc = setsockopt(fd, SOL_SOCKET, SO_SNDBUF, - &sock_buffersize, sizeof(sock_buffersize)); - if (rc != 0) - { - console_print(RED "setsockopt: SO_SNDBUF %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - return 0; -} /*! close a socket * @@ -393,7 +78,7 @@ ftp_closesocket(int fd, socklen_t addrlen = sizeof(addr); struct pollfd pollinfo; - // console_print("0x%X\n", socketGetLastBsdResult()); + // WriteToLog("0x%X\n", socketGetLastBsdResult()); if (connected) { @@ -401,17 +86,17 @@ ftp_closesocket(int fd, rc = getpeername(fd, (struct sockaddr*)&addr, &addrlen); if (rc != 0) { - console_print(RED "getpeername: %d %s\n" RESET, errno, strerror(errno)); - console_print(YELLOW "closing connection to fd=%d\n" RESET, fd); + WriteToLog("getpeername: %d %s\n", errno, strerror(errno)); + WriteToLog("closing connection to fd=%d\n", fd); } else - console_print(YELLOW "closing connection to %s:%u\n" RESET, + WriteToLog("closing connection to %s:%u\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); /* shutdown connection */ rc = shutdown(fd, SHUT_WR); if (rc != 0) - console_print(RED "shutdown: %d %s\n" RESET, errno, strerror(errno)); + WriteToLog("shutdown: %d %s\n", errno, strerror(errno)); /* wait for client to close connection */ pollinfo.fd = fd; @@ -419,7 +104,7 @@ ftp_closesocket(int fd, pollinfo.revents = 0; rc = poll(&pollinfo, 1, 250); if (rc < 0) - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); + WriteToLog("poll: %d %s\n", errno, strerror(errno)); } /* set linger to 0 */ @@ -429,13 +114,13 @@ ftp_closesocket(int fd, rc = setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); if (rc != 0) - console_print(RED "setsockopt: SO_LINGER %d %s\n" RESET, + WriteToLog("setsockopt: SO_LINGER %d %s\n", errno, strerror(errno)); /* close socket */ rc = close(fd); if (rc != 0) - console_print(RED "close: %d %s\n" RESET, errno, strerror(errno)); + WriteToLog("close: %d %s\n", errno, strerror(errno)); } /*! close command socket on ftp session @@ -452,3583 +137,317 @@ ftp_session_close_cmd(ftp_session_t* session) session->cmd_fd = -1; } -/*! close listen socket on ftp session +/*! destroy ftp session * * @param[in] session ftp session - */ -static void -ftp_session_close_pasv(ftp_session_t* session) -{ - /* close pasv socket */ - if (session->pasv_fd >= 0) - { - console_print(YELLOW "stop listening on %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); - - ftp_closesocket(session->pasv_fd, false); - } - - session->pasv_fd = -1; -} - -/*! close data socket on ftp session * - * @param[in] session ftp session + * @returns the next session in the list */ -static void -ftp_session_close_data(ftp_session_t* session) +static ftp_session_t* +ftp_session_destroy(ftp_session_t* session) { - /* close data connection */ - if (session->data_fd >= 0 && session->data_fd != session->cmd_fd) - ftp_closesocket(session->data_fd, true); - session->data_fd = -1; - - /* clear send/recv flags */ - session->flags &= ~(SESSION_RECV | SESSION_SEND); -} + ftp_session_t* next = session->next; -/*! close open file for ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_file(ftp_session_t* session) -{ - int rc; + /* close all sockets/files */ + ftp_session_close_cmd(session); - if (session->fp != NULL) + /* unlink from sessions list */ + if (session->next) + session->next->prev = session->prev; + if (session == sessions) + sessions = session->next; + else { - rc = fclose(session->fp); - if (rc != 0) - console_print(RED "fclose: %d %s\n" RESET, errno, strerror(errno)); + session->prev->next = session->next; + if (session == sessions->prev) + sessions->prev = session->prev; } - session->fp = NULL; - session->filepos = 0; + /* deallocate */ + free(session); + + return next; } -/*! open file for reading for ftp session - * - * @param[in] session ftp session +/*! allocate new ftp session * - * @returns -1 for error + * @param[in] listen_fd socket to accept connection from */ static int -ftp_session_open_file_read(ftp_session_t* session) +ftp_session_new(int listen_fd) { - int rc; - struct stat st; + ssize_t rc; + int new_fd; + ftp_session_t* session; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); - /* open file in read mode */ - if (!strcmp("/config/sys-ftpd/logs/ftpd.log", session->buffer)) + /* accept connection */ + new_fd = accept(listen_fd, (struct sockaddr*)&addr, &addrlen); + if (new_fd < 0) { - console_print(RED "Tried to open ftpd.log for reading. That's not allowed!\n"); + WriteToLog("accept: %d %s\n", errno, strerror(errno)); return -1; } - session->fp = fopen(session->buffer, "rb"); - if (session->fp == NULL) + WriteToLog("accepted connection from %s:%u\n", + inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); + + registerNetworkController(new_fd); + + /* allocate a new session */ + session = (ftp_session_t*)calloc(1, sizeof(ftp_session_t)); + if (session == NULL) { - console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + WriteToLog("failed to allocate session\n"); + ftp_closesocket(new_fd, true); return -1; } - /* it's okay if this fails */ - errno = 0; - rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); - if (rc != 0) + /* initialize session */ + session->cmd_fd = new_fd; + + /* link to the sessions list */ + if (sessions == NULL) { - console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); + sessions = session; + session->prev = session; } - - /* get the file size */ - rc = fstat(fileno(session->fp), &st); - if (rc != 0) + else { - console_print(RED "fstat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; + sessions->prev->next = session; + session->prev = sessions->prev; + sessions->prev = session; } - session->filesize = st.st_size; - if (session->filepos != 0) + /* copy socket address to pasv address */ + addrlen = sizeof(session->client_addr); + rc = getsockname(new_fd, (struct sockaddr*)&session->client_addr, &addrlen); + if (rc != 0) { - rc = fseek(session->fp, session->filepos, SEEK_SET); - if (rc != 0) - { - console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - return -1; - } + WriteToLog("getsockname: %d %s\n", errno, strerror(errno)); + ftp_session_destroy(session); + return -1; } return 0; } -/*! read from an open file for ftp session +/*! poll sockets for ftp session * * @param[in] session ftp session * - * @returns bytes read + * @returns next session */ -static ssize_t -ftp_session_read_file(ftp_session_t* session) +static ftp_session_t* +ftp_session_poll(ftp_session_t* session) { - ssize_t rc; + int rc; + struct pollfd pollinfo[2]; + nfds_t nfds = 1; + + /* the first pollfd is the command socket */ + pollinfo[0].fd = session->cmd_fd; + pollinfo[0].events = POLLIN | POLLPRI; + pollinfo[0].revents = 0; - /* read file at current position */ - rc = fread(session->buffer, 1, sizeof(session->buffer), session->fp); + /* poll the selected sockets */ + rc = poll(pollinfo, nfds, 0); if (rc < 0) { - console_print(RED "fread: %d %s\n" RESET, errno, strerror(errno)); - return -1; + WriteToLog("poll: %d %s\n", errno, strerror(errno)); + ftp_session_close_cmd(session); + } + else if (rc > 0) + { + /* check the command socket */ + if (pollinfo[0].revents != 0) + { + /* handle command */ + if (pollinfo[0].revents & POLL_UNKNOWN) + WriteToLog("cmd_fd: revents=0x%08X\n", pollinfo[0].revents); + + /* we need to read a new command */ + if (pollinfo[0].revents & (POLLERR | POLLHUP)) + { + WriteToLog("cmd revents=0x%x\n", pollinfo[0].revents); + ftp_session_close_cmd(session); + } + + ssize_t count; + uint8_t input_bytes[64]; + count = recv(session->cmd_fd, input_bytes, sizeof(input_bytes), MSG_PEEK); + if (count == 0) + { + removeNetworkController(session->cmd_fd); + ftp_session_close_cmd(session); + } + } } - /* adjust file position */ - session->filepos += rc; + /* still connected to peer; return next session */ + if (session->cmd_fd >= 0) + return session->next; + + /* disconnected from peer; destroy it and return next session */ + WriteToLog("disconnected from peer\n"); - return rc; + return ftp_session_destroy(session); } -/*! open file for writing for ftp session - * - * @param[in] session ftp session - * @param[in] append whether to append - * - * @returns -1 for error +/*! Handle applet events * - * @note truncates file + * @param[in] type Event type + * @param[in] closure Callback closure */ -static int -ftp_session_open_file_write(ftp_session_t* session, - bool append) +static void +applet_hook(AppletHookType type, + void* closure) { - int rc; - const char* mode = "wb"; - - if (!strcmp("/config/sys-ftpd/logs/ftpd.log", session->buffer)) + (void)closure; + (void)type; + /* stubbed for now */ + switch (type) { - console_print(RED "Tried to open ftpd.log for writing. That's not allowed!"); - return -1; + default: + break; } +} - if (append) - mode = "ab"; - else if (session->filepos != 0) - { - mode = "r+b"; - } +void ftp_pre_init(void) +{ + /* register applet hook */ + appletHook(&cookie, applet_hook, NULL); +} - if (!append) - { - unlink(session->buffer); - // Opening an exisiting file for writing can apparently result in corruption D: - } +/*! initialize ftp subsystem */ +int ftp_init(void) +{ + int rc = 0; - /* open file in write mode */ - session->fp = fopen(session->buffer, mode); - if (session->fp == NULL) + /* allocate socket to listen for clients */ + listenfd = socket(AF_INET, SOCK_STREAM, 0); + if (listenfd < 0) { - console_print(RED "fopen '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + WriteToLog("socket: %d %s\n", errno, strerror(errno)); + ftp_exit(); return -1; } - update_free_space(); + /* get address to listen on */ + serv_addr.sin_family = AF_INET; - /* it's okay if this fails */ - errno = 0; - rc = setvbuf(session->fp, session->file_buffer, _IOFBF, FILE_BUFFERSIZE); - if (rc != 0) - { - console_print(RED "setvbuf: %d %s\n" RESET, errno, strerror(errno)); - } + serv_addr.sin_addr.s_addr = INADDR_ANY; + char str_port[100]; + ini_gets("Port", "port:", "dummy", str_port, sizearray(str_port), CONFIGPATH); + LISTEN_PORT = atoi(str_port); + serv_addr.sin_port = htons(LISTEN_PORT); - /* check if this had REST but not APPE */ - if (session->filepos != 0 && !append) + /* reuse address */ { - /* seek to the REST offset */ - rc = fseek(session->fp, session->filepos, SEEK_SET); + int yes = 1; + rc = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); if (rc != 0) { - console_print(RED "fseek '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); + WriteToLog("setsockopt: %d %s\n", errno, strerror(errno)); + ftp_exit(); return -1; } } - return 0; -} - -/*! write to an open file for ftp session - * - * @param[in] session ftp session - * - * @returns bytes written - */ -static ssize_t -ftp_session_write_file(ftp_session_t* session) -{ - ssize_t rc; - - /* write to file at current position */ - rc = fwrite(session->buffer + session->bufferpos, - 1, session->buffersize - session->bufferpos, - session->fp); - if (rc < 0) + /* bind socket to listen address */ + rc = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); + if (rc != 0) { - console_print(RED "fwrite: %d %s\n" RESET, errno, strerror(errno)); + WriteToLog("bind: %d %s\n", errno, strerror(errno)); + ftp_exit(); return -1; } - else if (rc == 0) - console_print(RED "fwrite: wrote 0 bytes\n" RESET); - - /* adjust file position */ - session->filepos += rc; - - update_free_space(); - return rc; -} - -/*! close current working directory for ftp session - * - * @param[in] session ftp session - */ -static void -ftp_session_close_cwd(ftp_session_t* session) -{ - int rc; - - /* close open directory pointer */ - if (session->dp != NULL) - { - rc = closedir(session->dp); - if (rc != 0) - console_print(RED "closedir: %d %s\n" RESET, errno, strerror(errno)); - } - session->dp = NULL; -} -/*! open current working directory for ftp session - * - * @param[in] session ftp session - * - * @return -1 for failure - */ -static int -ftp_session_open_cwd(ftp_session_t* session) -{ - /* open current working directory */ - session->dp = opendir(session->cwd); - if (session->dp == NULL) + /* listen on socket */ + rc = listen(listenfd, 5); + if (rc != 0) { - console_print(RED "opendir '%s': %d %s\n" RESET, session->cwd, errno, strerror(errno)); + WriteToLog("listen: %d %s\n", errno, strerror(errno)); + ftp_exit(); return -1; } return 0; } -/*! set state for ftp session - * - * @param[in] session ftp session - * @param[in] state state to set - * @param[in] flags flags - */ -static void -ftp_session_set_state(ftp_session_t* session, - session_state_t state, - set_state_flags_t flags) -{ - session->state = state; - - /* close pasv and data sockets */ - if (flags & CLOSE_PASV) - ftp_session_close_pasv(session); - if (flags & CLOSE_DATA) - ftp_session_close_data(session); - - if (state == COMMAND_STATE) - { - /* close file/cwd */ - ftp_session_close_file(session); - ftp_session_close_cwd(session); - } -} - -/*! fill directory entry - * - * @param[in] session ftp session - * @param[in] st stat data - * @param[in] path path to fill - * @param[in] len path length - * @param[in] type type fact - * - * @returns errno - */ -static int -ftp_session_fill_dirent_type(ftp_session_t* session, const struct stat* st, - const char* path, size_t len, const char* type) +/*! deinitialize ftp subsystem */ +void ftp_exit(void) { - session->buffersize = 0; - - if (session->dir_mode == XFER_DIR_MLSD || session->dir_mode == XFER_DIR_MLST) - { - if (session->dir_mode == XFER_DIR_MLST) - session->buffer[session->buffersize++] = ' '; - - if (session->mlst_flags & SESSION_MLST_TYPE) - { - /* type fact */ - if (!type) - { - type = "???"; - if (S_ISREG(st->st_mode)) - type = "file"; - else if (S_ISDIR(st->st_mode)) - type = "dir"; -#if !defined(_3DS) && !defined(__SWITCH__) - else if (S_ISLNK(st->st_mode)) - type = "os.unix=symlink"; - else if (S_ISCHR(st->st_mode)) - type = "os.unix=character"; - else if (S_ISBLK(st->st_mode)) - type = "os.unix=block"; - else if (S_ISFIFO(st->st_mode)) - type = "os.unix=fifo"; - else if (S_ISSOCK(st->st_mode)) - type = "os.unix=socket"; -#endif - } - - session->buffersize += - sprintf(session->buffer + session->buffersize, "Type=%s;", type); - } - - if (session->mlst_flags & SESSION_MLST_SIZE) - { - /* size fact */ - session->buffersize += - sprintf(session->buffer + session->buffersize, "Size=%lld;", - (signed long long)st->st_size); - } - - if (session->mlst_flags & SESSION_MLST_MODIFY) - { - /* mtime fact */ - struct tm* tm = gmtime(&st->st_mtime); - if (tm == NULL) - return errno; - - session->buffersize += - strftime(session->buffer + session->buffersize, - sizeof(session->buffer) - session->buffersize, - "Modify=%Y%m%d%H%M%S;", tm); - if (session->buffersize == 0) - return EOVERFLOW; - } - - if (session->mlst_flags & SESSION_MLST_PERM) - { - /* permission fact */ - strcpy(session->buffer + session->buffersize, "Perm="); - session->buffersize += strlen("Perm="); - - /* append permission */ - if (S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'a'; - - /* create permission */ - if (S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'c'; - - /* delete permission */ - session->buffer[session->buffersize++] = 'd'; - - /* chdir permission */ - if (S_ISDIR(st->st_mode) && (st->st_mode & S_IXUSR)) - session->buffer[session->buffersize++] = 'e'; - - /* rename permission */ - session->buffer[session->buffersize++] = 'f'; - - /* list permission */ - if (S_ISDIR(st->st_mode) && (st->st_mode & S_IRUSR)) - session->buffer[session->buffersize++] = 'l'; - - /* mkdir permission */ - if (S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'm'; - /* delete permission */ - if (S_ISDIR(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'p'; - - /* read permission */ - if (S_ISREG(st->st_mode) && (st->st_mode & S_IRUSR)) - session->buffer[session->buffersize++] = 'r'; - - /* write permission */ - if (S_ISREG(st->st_mode) && (st->st_mode & S_IWUSR)) - session->buffer[session->buffersize++] = 'w'; - - session->buffer[session->buffersize++] = ';'; - } - - if (session->mlst_flags & SESSION_MLST_UNIX_MODE) - { - /* unix mode fact */ - mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO | S_ISVTX | S_ISGID | S_ISUID; - session->buffersize += - sprintf(session->buffer + session->buffersize, "UNIX.mode=0%lo;", - (unsigned long)(st->st_mode & mask)); - } - - /* make sure space precedes name */ - if (session->buffer[session->buffersize - 1] != ' ') - session->buffer[session->buffersize++] = ' '; - } - else if (session->dir_mode != XFER_DIR_NLST) - { - if (session->dir_mode == XFER_DIR_STAT) - session->buffer[session->buffersize++] = ' '; - - /* perms nlinks owner group size */ - session->buffersize += - sprintf(session->buffer + session->buffersize, - "%c%c%c%c%c%c%c%c%c%c %lu 3DS 3DS %lld ", - S_ISREG(st->st_mode) ? '-' : S_ISDIR(st->st_mode) ? 'd' : -#if !defined(_3DS) && !defined(__SWITCH__) - S_ISLNK(st->st_mode) ? 'l' : S_ISCHR(st->st_mode) ? 'c' : S_ISBLK(st->st_mode) ? 'b' : S_ISFIFO(st->st_mode) ? 'p' : S_ISSOCK(st->st_mode) ? 's' : -#endif - '?', - st->st_mode & S_IRUSR ? 'r' : '-', - st->st_mode & S_IWUSR ? 'w' : '-', - st->st_mode & S_IXUSR ? 'x' : '-', - st->st_mode & S_IRGRP ? 'r' : '-', - st->st_mode & S_IWGRP ? 'w' : '-', - st->st_mode & S_IXGRP ? 'x' : '-', - st->st_mode & S_IROTH ? 'r' : '-', - st->st_mode & S_IWOTH ? 'w' : '-', - st->st_mode & S_IXOTH ? 'x' : '-', - (unsigned long)st->st_nlink, - (signed long long)st->st_size); - - /* timestamp */ - struct tm* tm = gmtime(&st->st_mtime); - if (tm) - { - const char* fmt = "%b %e %Y "; - if (session->timestamp > st->st_mtime && session->timestamp - st->st_mtime < (60 * 60 * 24 * 365 / 2)) - { - fmt = "%b %e %H:%M "; - } - - session->buffersize += - strftime(session->buffer + session->buffersize, - sizeof(session->buffer) - session->buffersize, - fmt, tm); - } - else - { - session->buffersize += - sprintf(session->buffer + session->buffersize, "Jan 1 1970 "); - } - } + WriteToLog("exiting ftp server\n"); - if (session->buffersize + len + 2 > sizeof(session->buffer)) - { - /* buffer will overflow */ - return EOVERFLOW; - } + /* clean up all sessions */ + while (sessions != NULL) + ftp_session_destroy(sessions); - /* copy path */ - memcpy(session->buffer + session->buffersize, path, len); - len = session->buffersize + len; - session->buffer[len++] = '\r'; - session->buffer[len++] = '\n'; - session->buffersize = len; + /* stop listening for new clients */ + if (listenfd >= 0) + ftp_closesocket(listenfd, false); - return 0; + /* deinitialize socket driver */ + WriteToLog("Waiting for socketExit()...\n"); } -/*! fill directory entry - * - * @param[in] session ftp session - * @param[in] st stat data - * @param[in] path path to fill - * @param[in] len path length - * - * @returns errno - */ -static int -ftp_session_fill_dirent(ftp_session_t* session, const struct stat* st, - const char* path, size_t len) +void ftp_post_exit(void) { - return ftp_session_fill_dirent_type(session, st, path, len, NULL); } -/*! transfer loop - * - * Try to transfer as much data as the sockets will allow without blocking +/*! ftp look * - * @param[in] session ftp session + * @returns whether to keep looping */ -static void -ftp_session_transfer(ftp_session_t* session) +loop_status_t +ftp_loop(void) { int rc; - do - { - rc = session->transfer(session); - } while (rc == 0); -} - -/*! encode a path - * - * @param[in] path path to encode - * @param[in,out] len path length - * @param[in] quotes whether to encode quotes - * - * @returns encoded path - * - * @note The caller must free the returned path - */ -static char* -encode_path(const char* path, - size_t* len, - bool quotes) -{ - bool enc = false; - size_t i, diff = 0; - char *out, *p = (char*)path; + struct pollfd pollinfo; + ftp_session_t* session; - /* check for \n that needs to be encoded */ - if (memchr(p, '\n', *len) != NULL) - enc = true; + /* we will poll for new client connections */ + pollinfo.fd = listenfd; + pollinfo.events = POLLIN; + pollinfo.revents = 0; - if (quotes) + /* poll for a new client */ + rc = poll(&pollinfo, 1, 0); + if (rc < 0) { - /* check for " that needs to be encoded */ - p = (char*)path; - do - { - p = memchr(p, '"', path + *len - p); - if (p != NULL) - { - ++p; - ++diff; - } - } while (p != NULL); - } - - /* check if an encode was needed */ - if (!enc && diff == 0) - return strdup(path); + /* wifi got disabled */ + WriteToLog("poll: FAILED!\n"); - /* allocate space for encoded path */ - p = out = (char*)malloc(*len + diff); - if (out == NULL) - return NULL; + if (errno == ENETDOWN) + return LOOP_RESTART; - /* copy the path while performing encoding */ - for (i = 0; i < *len; ++i) + WriteToLog("poll: %d %s\n", errno, strerror(errno)); + return LOOP_EXIT; + } + else if (rc > 0) { - if (*path == '\n') + if (pollinfo.revents & POLLIN) { - /* encoded \n is \0 */ - *p++ = 0; + /* we got a new client */ + if (ftp_session_new(listenfd) != 0) + { + return LOOP_RESTART; + } } - else if (quotes && *path == '"') + else { - /* encoded " is "" */ - *p++ = '"'; - *p++ = '"'; + WriteToLog("listenfd: revents=0x%08X\n", pollinfo.revents); } - else - *p++ = *path; - ++path; - } - - *len += diff; - return out; -} - -/*! decode a path - * - * @param[in] session ftp session - * @param[in] len command length - */ -static void -decode_path(ftp_session_t* session, - size_t len) -{ - size_t i; - - /* decode \0 from the first command */ - for (i = 0; i < len; ++i) - { - /* this is an encoded \n */ - if (session->cmd_buffer[i] == 0) - session->cmd_buffer[i] = '\n'; - } -} - -/*! fill cdir directory entry - * - * @param[in] session ftp session - * @param[in] path path to fill - * - * @returns errno - */ -static int -ftp_session_fill_dirent_cdir(ftp_session_t* session, const char* path) -{ - int rc; - struct stat st; - char* buffer; - size_t len; - - rc = stat(path, &st); - /* double-check this was a directory */ - if (rc == 0 && !S_ISDIR(st.st_mode)) - { - /* shouldn't happen but just in case */ - rc = -1; - errno = ENOTDIR; - } - if (rc != 0) - return errno; - - /* encode \n in path */ - len = strlen(path); - buffer = encode_path(path, &len, false); - if (!buffer) - return ENOMEM; - - /* fill dirent with listed directory as type=cdir */ - rc = ftp_session_fill_dirent_type(session, &st, buffer, len, "cdir"); - free(buffer); - - return rc; -} - -/*! send a response on the command socket - * - * @param[in] session ftp session - * @param[in] buffer buffer to send - * @param[in] len buffer length - */ -static void -ftp_send_response_buffer(ftp_session_t* session, - const char* buffer, - size_t len) -{ - ssize_t rc, to_send; - - if (session->cmd_fd < 0) - return; - - /* send response */ - to_send = len; - console_print(GREEN "%s" RESET, buffer); - rc = send(session->cmd_fd, buffer, to_send, 0); - if (rc < 0) - { - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - else if (rc != to_send) - { - console_print(RED "only sent %u/%u bytes\n" RESET, - (unsigned int)rc, (unsigned int)to_send); - ftp_session_close_cmd(session); - } -} - -__attribute__((format(printf, 3, 4))) -/*! send ftp response to ftp session's peer - * - * @param[in] session ftp session - * @param[in] code response code - * @param[in] fmt format string - * @param[in] ... format arguments - */ -static void -ftp_send_response(ftp_session_t* session, - int code, - const char* fmt, ...) -{ - static char buffer[CMD_BUFFERSIZE]; - ssize_t rc; - va_list ap; - - if (session->cmd_fd < 0) - return; - - /* print response code and message to buffer */ - va_start(ap, fmt); - if (code > 0) - rc = sprintf(buffer, "%d ", code); - else - rc = sprintf(buffer, "%d-", -code); - rc += vsnprintf(buffer + rc, sizeof(buffer) - rc, fmt, ap); - va_end(ap); - - if (rc >= sizeof(buffer)) - { - /* couldn't fit message; just send code */ - console_print(RED "%s: buffersize too small\n" RESET, __func__); - if (code > 0) - rc = sprintf(buffer, "%d \r\n", code); - else - rc = sprintf(buffer, "%d-\r\n", -code); - } - - ftp_send_response_buffer(session, buffer, rc); -} - -/*! destroy ftp session - * - * @param[in] session ftp session - * - * @returns the next session in the list - */ -static ftp_session_t* -ftp_session_destroy(ftp_session_t* session) -{ - ftp_session_t* next = session->next; - - /* close all sockets/files */ - ftp_session_close_cmd(session); - ftp_session_close_pasv(session); - ftp_session_close_data(session); - ftp_session_close_file(session); - ftp_session_close_cwd(session); - - /* unlink from sessions list */ - if (session->next) - session->next->prev = session->prev; - if (session == sessions) - sessions = session->next; - else - { - session->prev->next = session->next; - if (session == sessions->prev) - sessions->prev = session->prev; } - /* deallocate */ - free(session); + /* poll each session */ + session = sessions; + while (session != NULL) + session = ftp_session_poll(session); - return next; + return LOOP_CONTINUE; } -/*! allocate new ftp session - * - * @param[in] listen_fd socket to accept connection from - */ -static int -ftp_session_new(int listen_fd) -{ - ssize_t rc; - int new_fd; - ftp_session_t* session; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - - /* accept connection */ - new_fd = accept(listen_fd, (struct sockaddr*)&addr, &addrlen); - if (new_fd < 0) - { - console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - console_print(CYAN "accepted connection from %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - registerNetworkController(new_fd); - - /* allocate a new session */ - session = (ftp_session_t*)calloc(1, sizeof(ftp_session_t)); - if (session == NULL) - { - console_print(RED "failed to allocate session\n" RESET); - ftp_closesocket(new_fd, true); - return -1; - } - - /* initialize session */ - session->cmd_fd = new_fd; - - /* link to the sessions list */ - if (sessions == NULL) - { - sessions = session; - session->prev = session; - } - else - { - sessions->prev->next = session; - session->prev = sessions->prev; - sessions->prev = session; - } - - /* copy socket address to pasv address */ - addrlen = sizeof(session->pasv_addr); - rc = getsockname(new_fd, (struct sockaddr*)&session->pasv_addr, &addrlen); - if (rc != 0) - { - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 451, "Failed to get connection info\r\n"); - ftp_session_destroy(session); - return -1; - } - - return 0; -} - -/*! accept PASV connection for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for failure - */ -static int -ftp_session_accept(ftp_session_t* session) -{ - int rc, new_fd; - struct sockaddr_in addr; - socklen_t addrlen = sizeof(addr); - - if (session->flags & SESSION_PASV) - { - /* clear PASV flag */ - session->flags &= ~SESSION_PASV; - - /* tell the peer that we're ready to accept the connection */ - ftp_send_response(session, 150, "Ready\r\n"); - - /* accept connection from peer */ - new_fd = accept(session->pasv_fd, (struct sockaddr*)&addr, &addrlen); - if (new_fd < 0) - { - console_print(RED "accept: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "Failed to establish connection\r\n"); - return -1; - } - - /* set the socket to non-blocking */ - rc = ftp_set_socket_nonblocking(new_fd); - if (rc != 0) - { - ftp_closesocket(new_fd, true); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "Failed to establish connection\r\n"); - return -1; - } - - console_print(CYAN "accepted connection from %s:%u\n" RESET, - inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); - - /* we are ready to transfer data */ - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - session->data_fd = new_fd; - - return 0; - } - else - { - /* peer didn't send PASV command */ - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); - return -1; - } -} - -/*! connect to peer for ftp session - * - * @param[in] session ftp session - * - * @returns -1 for failure - */ -static int -ftp_session_connect(ftp_session_t* session) -{ - int rc; - - /* clear PORT flag */ - session->flags &= ~SESSION_PORT; - - /* create a new socket */ - session->data_fd = socket(AF_INET, SOCK_STREAM, 0); - if (session->data_fd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - /* set socket options */ - rc = ftp_set_socket_options(session->data_fd); - if (rc != 0) - { - ftp_closesocket(session->data_fd, false); - session->data_fd = -1; - return -1; - } - - /* set socket to non-blocking */ - rc = ftp_set_socket_nonblocking(session->data_fd); - if (rc != 0) - return -1; - - /* connect to peer */ - rc = connect(session->data_fd, (struct sockaddr*)&session->peer_addr, - sizeof(session->peer_addr)); - if (rc != 0) - { - if (errno != EINPROGRESS) - { - console_print(RED "connect: %d %s\n" RESET, errno, strerror(errno)); - ftp_closesocket(session->data_fd, false); - session->data_fd = -1; - return -1; - } - } - else - { - console_print(CYAN "connected to %s:%u\n" RESET, - inet_ntoa(session->peer_addr.sin_addr), - ntohs(session->peer_addr.sin_port)); - - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV); - ftp_send_response(session, 150, "Ready\r\n"); - } - - return 0; -} - -static bool -ftp_auth_oncommand(ftp_session_t* session, const char* command) -{ - if (command && (strcasecmp("USER", command) == 0 || strcasecmp("PASS", command) == 0 || strcasecmp("QUIT", command) == 0)) - { - return true; - } - - return session->user_ok && session->pass_ok; -} - -static void -ftp_auth_check(ftp_session_t* session, const char* user, const char* pass) -{ - char str_user[100]; - ini_gets("User", "user:", "dummy", str_user, sizearray(str_user), CONFIGPATH); - char str_pass[100]; - ini_gets("Password", "password:", "dummy", str_pass, sizearray(str_pass), CONFIGPATH); - char str_anony[100]; - ini_gets("Anonymous", "anonymous:", "dummy", str_anony, sizearray(str_anony), CONFIGPATH); - char str_led[100]; - ini_gets("LED", "led:", "1", str_led, sizearray(str_led), CONFIGPATH); - - session->led = (strcmp("1", str_led) == 0); - - if (strcmp("1", str_anony) == 0) - { - session->user_ok = false; - session->pass_ok = false; - ftp_send_response(session, 230, "OK, Huh Anonymous is that you ???\r\n"); - if (session->led) - { - flash_led_connect(); - } - return; - } - - if (user) - { - if (strcmp(str_user, user) == 0) - { - session->user_ok = true; - } - else - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 430, "Unknown user, Please check /config/sys-ftpd/config.ini\r\n"); - ftp_session_close_cmd(session); - return; - } - } - - if (pass) - { - if (strcmp(str_pass, pass) == 0) - { - session->pass_ok = true; - } - else - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 430, "Wrong password, Please check /config/sys-ftpd/config.ini\r\n"); - ftp_session_close_cmd(session); - return; - } - } - - ftp_session_set_state(session, COMMAND_STATE, 0); - if (ftp_auth_oncommand(session, NULL)) - { - ftp_send_response(session, 230, "OK\r\n"); - if (session->led) - { - flash_led_connect(); - } - return; - } - else - { - ftp_send_response(session, 331, "next step required\r\n"); - return; - } -} - -/*! read command for ftp session - * - * @param[in] session ftp session - * @param[in] events poll events - */ -static void -ftp_session_read_command(ftp_session_t* session, - int events) -{ - char *buffer, *args, *next = NULL; - size_t i, len; - int atmark; - ssize_t rc; - ftp_command_t key, *command; - - /* check out-of-band data */ - if (events & POLLPRI) - { - session->flags |= SESSION_URGENT; - - /* check if we are at the urgent marker */ - atmark = sockatmark(session->cmd_fd); - if (atmark < 0) - { - console_print(RED "sockatmark: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - - if (!atmark) - { - /* discard in-band data */ - rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), 0); - if (rc < 0 && errno != EWOULDBLOCK) - { - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - - return; - } - - /* retrieve the urgent data */ - rc = recv(session->cmd_fd, session->cmd_buffer, sizeof(session->cmd_buffer), MSG_OOB); - if (rc < 0) - { - /* EWOULDBLOCK means out-of-band data is on the way */ - if (errno == EWOULDBLOCK) - return; - - /* error retrieving out-of-band data */ - console_print(RED "recv (oob): %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - - /* reset the command buffer */ - session->cmd_buffersize = 0; - return; - } - - /* prepare to receive data */ - buffer = session->cmd_buffer + session->cmd_buffersize; - len = sizeof(session->cmd_buffer) - session->cmd_buffersize; - if (len == 0) - { - /* error retrieving command */ - console_print(RED "Exceeded command buffer size\n" RESET); - ftp_session_close_cmd(session); - return; - } - - /* retrieve command data */ - rc = recv(session->cmd_fd, buffer, len, 0); - if (rc < 0) - { - /* error retrieving command */ - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - return; - } - if (rc == 0) - { - /* peer closed connection */ - debug_print("peer closed connection\n"); - ftp_session_close_cmd(session); - return; - } - else - { - session->cmd_buffersize += rc; - len = sizeof(session->cmd_buffer) - session->cmd_buffersize; - - if (session->flags & SESSION_URGENT) - { - /* look for telnet data mark */ - for (i = 0; i < session->cmd_buffersize; ++i) - { - if ((unsigned char)session->cmd_buffer[i] == 0xF2) - { - /* ignore all data that precedes the data mark */ - if (i < session->cmd_buffersize - 1) - memmove(session->cmd_buffer, session->cmd_buffer + i + 1, len - i - 1); - session->cmd_buffersize -= i + 1; - session->flags &= ~SESSION_URGENT; - break; - } - } - } - - /* loop through commands */ - while (true) - { - /* must have at least enough data for the delimiter */ - if (session->cmd_buffersize < 1) - return; - - /* look for \r\n or \n delimiter */ - for (i = 0; i < session->cmd_buffersize; ++i) - { - if (i < session->cmd_buffersize - 1 && session->cmd_buffer[i] == '\r' && session->cmd_buffer[i + 1] == '\n') - { - /* we found a \r\n delimiter */ - session->cmd_buffer[i] = 0; - next = &session->cmd_buffer[i + 2]; - break; - } - else if (session->cmd_buffer[i] == '\n') - { - /* we found a \n delimiter */ - session->cmd_buffer[i] = 0; - next = &session->cmd_buffer[i + 1]; - break; - } - } - - /* check if a delimiter was found */ - if (i == session->cmd_buffersize) - return; - - /* decode the command */ - decode_path(session, i); - - /* split command from arguments */ - args = buffer = session->cmd_buffer; - while (*args && !isspace((int)*args)) - ++args; - if (*args) - *args++ = 0; - - /* look up the command */ - key.name = buffer; - command = bsearch(&key, ftp_commands, - num_ftp_commands, sizeof(ftp_command_t), - ftp_command_cmp); - - /* update command timestamp */ - session->timestamp = time(NULL); - - /* execute the command */ - if (command == NULL) - { - /* send header */ - ftp_send_response(session, 502, "Invalid command \""); - - /* send command */ - len = strlen(buffer); - buffer = encode_path(buffer, &len, false); - if (buffer != NULL) - ftp_send_response_buffer(session, buffer, len); - else - ftp_send_response_buffer(session, key.name, strlen(key.name)); - free(buffer); - - /* send args (if any) */ - if (*args != 0) - { - ftp_send_response_buffer(session, " ", 1); - - len = strlen(args); - buffer = encode_path(args, &len, false); - if (buffer != NULL) - ftp_send_response_buffer(session, buffer, len); - else - ftp_send_response_buffer(session, args, strlen(args)); - free(buffer); - } - - /* send footer */ - ftp_send_response_buffer(session, "\"\r\n", 3); - } - else if (session->state != COMMAND_STATE) - { - /* only some commands are available during data transfer */ - if (strcasecmp(command->name, "ABOR") != 0 && strcasecmp(command->name, "STAT") != 0 && strcasecmp(command->name, "QUIT") != 0) - { - ftp_send_response(session, 503, "Invalid command during transfer\r\n"); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_session_close_cmd(session); - } - else - command->handler(session, args); - } - else - { - /* clear RENAME flag for all commands except RNTO */ - if (strcasecmp(command->name, "RNTO") != 0) - session->flags &= ~SESSION_RENAME; - - command->handler(session, args); - } - - /* remove executed command from the command buffer */ - len = session->cmd_buffer + session->cmd_buffersize - next; - if (len > 0) - memmove(session->cmd_buffer, next, len); - session->cmd_buffersize = len; - } - } -} - -/*! poll sockets for ftp session - * - * @param[in] session ftp session - * - * @returns next session - */ -static ftp_session_t* -ftp_session_poll(ftp_session_t* session) -{ - int rc; - struct pollfd pollinfo[2]; - nfds_t nfds = 1; - - /* the first pollfd is the command socket */ - pollinfo[0].fd = session->cmd_fd; - pollinfo[0].events = POLLIN | POLLPRI; - pollinfo[0].revents = 0; - - /* poll the selected sockets */ - rc = poll(pollinfo, nfds, 0); - if (rc < 0) - { - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_cmd(session); - } - else if (rc > 0) - { - /* check the command socket */ - if (pollinfo[0].revents != 0) - { - /* handle command */ - if (pollinfo[0].revents & POLL_UNKNOWN) - console_print(YELLOW "cmd_fd: revents=0x%08X\n" RESET, pollinfo[0].revents); - - /* we need to read a new command */ - if (pollinfo[0].revents & (POLLERR | POLLHUP)) - { - debug_print("cmd revents=0x%x\n", pollinfo[0].revents); - ftp_session_close_cmd(session); - } - - ssize_t count; - uint8_t input_bytes[64]; - count = recv(session->cmd_fd, input_bytes, sizeof(input_bytes), MSG_PEEK); - if (count == 0) - { - removeNetworkController(session->cmd_fd); - ftp_session_close_cmd(session); - } - } - } - - /* still connected to peer; return next session */ - if (session->cmd_fd >= 0) - return session->next; - - /* disconnected from peer; destroy it and return next session */ - debug_print("disconnected from peer\n"); - - return ftp_session_destroy(session); -} - -/* Update free space in status bar */ -static void -update_free_space(void) -{ -#if defined(_3DS) || defined(__SWITCH__) -# define KiB (1024.0) -# define MiB (1024.0 * KiB) -# define GiB (1024.0 * MiB) - char buffer[16]; - struct statvfs st; - double bytes_free; - int rc, len; - - rc = statvfs("sdmc:/", &st); - if (rc != 0) - console_print(RED "statvfs: %d %s\n" RESET, errno, strerror(errno)); - else - { - bytes_free = (double)st.f_bsize * st.f_bfree; - - if (bytes_free < 1000.0) - len = snprintf(buffer, sizeof(buffer), "%.0lfB", bytes_free); - else if (bytes_free < 10.0 * KiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfKiB", floor((bytes_free * 100.0) / KiB) / 100.0); - else if (bytes_free < 100.0 * KiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfKiB", floor((bytes_free * 10.0) / KiB) / 10.0); - else if (bytes_free < 1000.0 * KiB) - len = snprintf(buffer, sizeof(buffer), "%.0lfKiB", floor(bytes_free / KiB)); - else if (bytes_free < 10.0 * MiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfMiB", floor((bytes_free * 100.0) / MiB) / 100.0); - else if (bytes_free < 100.0 * MiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfMiB", floor((bytes_free * 10.0) / MiB) / 10.0); - else if (bytes_free < 1000.0 * MiB) - len = snprintf(buffer, sizeof(buffer), "%.0lfMiB", floor(bytes_free / MiB)); - else if (bytes_free < 10.0 * GiB) - len = snprintf(buffer, sizeof(buffer), "%.2lfGiB", floor((bytes_free * 100.0) / GiB) / 100.0); - else if (bytes_free < 100.0 * GiB) - len = snprintf(buffer, sizeof(buffer), "%.1lfGiB", floor((bytes_free * 10.0) / GiB) / 10.0); - else - len = snprintf(buffer, sizeof(buffer), "%.0lfGiB", floor(bytes_free / GiB)); - - console_set_status("\x1b[0;%dH" GREEN "%s", 50 - len, buffer); - } -#endif -} - -/*! Update status bar */ -static int -update_status(void) -{ -#if defined(_3DS) || defined(__SWITCH__) -// console_set_status("\n" GREEN STATUS_STRING " " -# ifdef ENABLE_LOGGING -// "DEBUG " -# endif - // CYAN "%s:%u" RESET, - // inet_ntoa(serv_addr.sin_addr), - // ntohs(serv_addr.sin_port)); - update_free_space(); -#elif 0 //defined(__SWITCH__) - char hostname[128]; - socklen_t addrlen = sizeof(serv_addr); - int rc; - rc = gethostname(hostname, sizeof(hostname)); - if (rc != 0) - { - console_print(RED "gethostname: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - console_set_status("\n" GREEN STATUS_STRING " test " -# ifdef ENABLE_LOGGING - "DEBUG " -# endif - CYAN "%s:%u" RESET, - hostname, - ntohs(serv_addr.sin_port)); - update_free_space(); -#else - char hostname[128]; - socklen_t addrlen = sizeof(serv_addr); - int rc; - - rc = getsockname(listenfd, (struct sockaddr*)&serv_addr, &addrlen); - if (rc != 0) - { - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - rc = gethostname(hostname, sizeof(hostname)); - if (rc != 0) - { - console_print(RED "gethostname: %d %s\n" RESET, errno, strerror(errno)); - return -1; - } - - console_set_status(GREEN STATUS_STRING " " -# ifdef ENABLE_LOGGING - "DEBUG " -# endif - YELLOW "IP:" CYAN "%s " YELLOW "Port:" CYAN "%u" RESET, - hostname, - ntohs(serv_addr.sin_port)); -#endif - - return 0; -} - -#ifdef _3DS -/*! Handle apt events - * - * @param[in] type Event type - * @param[in] closure Callback closure - */ -static void -apt_hook(APT_HookType type, - void* closure) -{ - switch (type) - { - case APTHOOK_ONSUSPEND: - case APTHOOK_ONSLEEP: - /* turn on backlight, or you can't see the home menu! */ - if (R_SUCCEEDED(gspLcdInit())) - { - GSPLCD_PowerOnBacklight(GSPLCD_SCREEN_BOTH); - gspLcdExit(); - } - break; - - case APTHOOK_ONRESTORE: - case APTHOOK_ONWAKEUP: - /* restore backlight power state */ - if (R_SUCCEEDED(gspLcdInit())) - { - (lcd_power ? GSPLCD_PowerOnBacklight : GSPLCD_PowerOffBacklight)(GSPLCD_SCREEN_BOTH); - gspLcdExit(); - } - break; - - default: - break; - } -} -#elif defined(__SWITCH__) -/*! Handle applet events - * - * @param[in] type Event type - * @param[in] closure Callback closure - */ -static void -applet_hook(AppletHookType type, - void* closure) -{ - (void)closure; - (void)type; - /* stubbed for now */ - switch (type) - { - default: - break; - } -} -#endif - -void ftp_pre_init(void) -{ - start_time = time(NULL); - - /* register applet hook */ - appletHook(&cookie, applet_hook, NULL); -} - -/*! initialize ftp subsystem */ -int ftp_init(void) -{ - int rc = 0; - - /* allocate socket to listen for clients */ - listenfd = socket(AF_INET, SOCK_STREAM, 0); - if (listenfd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* get address to listen on */ - serv_addr.sin_family = AF_INET; - - serv_addr.sin_addr.s_addr = INADDR_ANY; - char str_port[100]; - ini_gets("Port", "port:", "dummy", str_port, sizearray(str_port), CONFIGPATH); - LISTEN_PORT = atoi(str_port); - serv_addr.sin_port = htons(LISTEN_PORT); - - /* reuse address */ - { - int yes = 1; - rc = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - if (rc != 0) - { - console_print(RED "setsockopt: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - } - - /* bind socket to listen address */ - rc = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); - if (rc != 0) - { - console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* listen on socket */ - rc = listen(listenfd, 5); - if (rc != 0) - { - console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); - ftp_exit(); - return -1; - } - - /* print server address */ - rc = update_status(); - if (rc != 0) - { - ftp_exit(); - return -1; - } - - return 0; -} - -/*! deinitialize ftp subsystem */ -void ftp_exit(void) -{ - - debug_print("exiting ftp server\n"); - - /* clean up all sessions */ - while (sessions != NULL) - ftp_session_destroy(sessions); - - /* stop listening for new clients */ - if (listenfd >= 0) - ftp_closesocket(listenfd, false); - - /* deinitialize socket driver */ - console_render(); - console_print(CYAN "Waiting for socketExit()...\n" RESET); -} - -void ftp_post_exit(void) -{ -} - -/*! ftp look - * - * @returns whether to keep looping - */ -loop_status_t -ftp_loop(void) -{ - int rc; - struct pollfd pollinfo; - ftp_session_t* session; - - /* we will poll for new client connections */ - pollinfo.fd = listenfd; - pollinfo.events = POLLIN; - pollinfo.revents = 0; - - /* poll for a new client */ - rc = poll(&pollinfo, 1, 0); - if (rc < 0) - { - /* wifi got disabled */ - console_print(RED "poll: FAILED!\n" RESET); - - if (errno == ENETDOWN) - return LOOP_RESTART; - - console_print(RED "poll: %d %s\n" RESET, errno, strerror(errno)); - return LOOP_EXIT; - } - else if (rc > 0) - { - if (pollinfo.revents & POLLIN) - { - /* we got a new client */ - if (ftp_session_new(listenfd) != 0) - { - return LOOP_RESTART; - } - } - else - { - console_print(YELLOW "listenfd: revents=0x%08X\n" RESET, pollinfo.revents); - } - } - - /* poll each session */ - session = sessions; - while (session != NULL) - session = ftp_session_poll(session); - - return LOOP_CONTINUE; -} - -/*! change to parent directory - * - * @param[in] session ftp session - */ -static void -cd_up(ftp_session_t* session) -{ - char *slash = NULL, *p; - - /* remove basename from cwd */ - for (p = session->cwd; *p; ++p) - { - if (*p == '/') - slash = p; - } - *slash = 0; - if (strlen(session->cwd) == 0) - strcat(session->cwd, "/"); -} - -/*! validate a path - * - * @param[in] args path to validate - */ -static int -validate_path(const char* args) -{ - const char* p; - - /* make sure no path components are '..' */ - p = args; - while ((p = strstr(p, "/..")) != NULL) - { - if (p[3] == 0 || p[3] == '/') - return -1; - } - - /* make sure there are no '//' */ - if (strstr(args, "//") != NULL) - return -1; - - return 0; -} - -/*! get a path relative to cwd - * - * @param[in] session ftp session - * @param[in] cwd working directory - * @param[in] args path to make - * - * @returns error - * - * @note the output goes to session->buffer - */ -static int -build_path(ftp_session_t* session, - const char* cwd, - const char* args) -{ - int rc; - char* p; - - session->buffersize = 0; - memset(session->buffer, 0, sizeof(session->buffer)); - - /* make sure the input is a valid path */ - if (validate_path(args) != 0) - { - errno = EINVAL; - return -1; - } - - if (args[0] == '/') - { - /* this is an absolute path */ - size_t len = strlen(args); - if (len > sizeof(session->buffer) - 1) - { - errno = ENAMETOOLONG; - return -1; - } - - memcpy(session->buffer, args, len); - session->buffersize = len; - } - else - { - /* this is a relative path */ - if (strcmp(cwd, "/") == 0) - rc = snprintf(session->buffer, sizeof(session->buffer), "/%s", - args); - else - rc = snprintf(session->buffer, sizeof(session->buffer), "%s/%s", - cwd, args); - - if (rc >= sizeof(session->buffer)) - { - errno = ENAMETOOLONG; - return -1; - } - - session->buffersize = rc; - } - - /* remove trailing / */ - p = session->buffer + session->buffersize; - while (p > session->buffer && *--p == '/') - { - *p = 0; - --session->buffersize; - } - - /* if we ended with an empty path, it is the root directory */ - if (session->buffersize == 0) - session->buffer[session->buffersize++] = '/'; - - return 0; -} - -/*! transfer a directory listing - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -list_transfer(ftp_session_t* session) -{ - ssize_t rc; - size_t len; - char* buffer; - struct stat st; - struct dirent* dent; - - /* check if we sent all available data */ - if (session->bufferpos == session->buffersize) - { - /* check xfer dir type */ - if (session->dir_mode == XFER_DIR_STAT) - rc = 213; - else - rc = 226; - - /* check if this was for a file */ - if (session->dp == NULL) - { - /* we already sent the file's listing */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, rc, "OK\r\n"); - return LOOP_EXIT; - } - - /* get the next directory entry */ - dent = readdir(session->dp); - if (dent == NULL) - { - /* we have exhausted the directory listing */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, rc, "OK\r\n"); - return LOOP_EXIT; - } - - /* TODO I think we are supposed to return entries for . and .. */ - if (strcmp(dent->d_name, ".") == 0 || strcmp(dent->d_name, "..") == 0) - return LOOP_CONTINUE; - - /* check if this was a NLST */ - if (session->dir_mode == XFER_DIR_NLST) - { - /* NLST gives the whole path name */ - session->buffersize = 0; - if (build_path(session, session->lwd, dent->d_name) == 0) - { - /* encode \n in path */ - len = session->buffersize; - buffer = encode_path(session->buffer, &len, false); - if (buffer != NULL) - { - /* copy to the session buffer to send */ - memcpy(session->buffer, buffer, len); - free(buffer); - session->buffer[len++] = '\r'; - session->buffer[len++] = '\n'; - session->buffersize = len; - } - } - } - else - { -#ifdef _3DS - /* the sdmc directory entry already has the type and size, so no need to do a slow stat */ - u32 magic = *(u32*)session->dp->dirData->dirStruct; - - if (magic == SDMC_DIRITER_MAGIC) - { - sdmc_dir_t* dir = (sdmc_dir_t*)session->dp->dirData->dirStruct; - FS_DirectoryEntry* entry = &dir->entry_data[dir->index]; - - if (entry->attributes & FS_ATTRIBUTE_DIRECTORY) - st.st_mode = S_IFDIR | S_IRUSR | S_IRGRP | S_IROTH; - else - st.st_mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; - - if (!(entry->attributes & FS_ATTRIBUTE_READ_ONLY)) - st.st_mode |= S_IWUSR | S_IWGRP | S_IWOTH; - - st.st_size = entry->fileSize; - st.st_mtime = 0; - - bool getmtime = true; - if (session->dir_mode == XFER_DIR_MLSD || session->dir_mode == XFER_DIR_MLST) - { - if (!(session->mlst_flags & SESSION_MLST_MODIFY)) - getmtime = false; - } - else if (session->dir_mode == XFER_DIR_NLST) - getmtime = false; - - if ((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if (getmtime) - { - uint64_t mtime = 0; - if ((rc = sdmc_getmtime(session->buffer, &mtime)) != 0) - console_print(RED "sdmc_getmtime '%s': 0x%x\n" RESET, session->buffer, rc); - else - st.st_mtime = mtime; - } - } - else - { - /* lstat the entry */ - if ((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if ((rc = lstat(session->buffer, &st)) != 0) - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - - if (rc != 0) - { - /* an error occurred */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - return LOOP_EXIT; - } - } -#else - /* lstat the entry */ - if ((rc = build_path(session, session->lwd, dent->d_name)) != 0) - console_print(RED "build_path: %d %s\n" RESET, errno, strerror(errno)); - else if ((rc = lstat(session->buffer, &st)) != 0) - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - - if (rc != 0) - { - /* an error occurred */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - return LOOP_EXIT; - } -#endif - /* encode \n in path */ - len = strlen(dent->d_name); - buffer = encode_path(dent->d_name, &len, false); - if (buffer != NULL) - { - rc = ftp_session_fill_dirent(session, &st, buffer, len); - free(buffer); - if (rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "%s\r\n", strerror(rc)); - return LOOP_EXIT; - } - } - else - session->buffersize = 0; - } - session->bufferpos = 0; - } - - /* send any pending data */ - rc = send(session->data_fd, session->buffer + session->bufferpos, - session->buffersize - session->bufferpos, 0); - if (rc <= 0) - { - /* error sending data */ - if (rc < 0) - { - if (errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - } - else - console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we can try to send more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! send a file to the client - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -retrieve_transfer(ftp_session_t* session) -{ - ssize_t rc; - - if (session->bufferpos == session->buffersize) - { - /* we have sent all the data so read some more */ - rc = ftp_session_read_file(session); - if (rc <= 0) - { - /* can't read any more data */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - if (rc < 0) - ftp_send_response(session, 451, "Failed to read file\r\n"); - else - ftp_send_response(session, 226, "OK\r\n"); - return LOOP_EXIT; - } - - /* we read some data so reset the session buffer to send */ - session->bufferpos = 0; - session->buffersize = rc; - } - - /* send any pending data */ - size_t send_size = session->buffersize - session->bufferpos; - if (send_size > 0x1000) - send_size = 0x1000; - rc = send(session->data_fd, session->buffer + session->bufferpos, - send_size, 0); - if (rc <= 0) - { - /* error sending data */ - if (rc < 0) - { - if (errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "send: %d %s\n" RESET, errno, strerror(errno)); - } - else - console_print(YELLOW "send: %d %s\n" RESET, ECONNRESET, strerror(ECONNRESET)); - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we can try to send more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! send a file to the client - * - * @param[in] session ftp session - * - * @returns whether to call again - */ -static loop_status_t -store_transfer(ftp_session_t* session) -{ - ssize_t rc; - - if (session->bufferpos == session->buffersize) - { - /* we have written all the received data, so try to get some more */ - rc = recv(session->data_fd, session->buffer, sizeof(session->buffer), 0); - if (rc <= 0) - { - /* can't read any more data */ - if (rc < 0) - { - if (errno == EWOULDBLOCK) - return LOOP_EXIT; - console_print(RED "recv: %d %s\n" RESET, errno, strerror(errno)); - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - - if (rc == 0) - ftp_send_response(session, 226, "OK\r\n"); - else - ftp_send_response(session, 426, "Connection broken during transfer\r\n"); - return LOOP_EXIT; - } - - /* we received some data so reset the session buffer to write */ - session->bufferpos = 0; - session->buffersize = rc; - } - - rc = ftp_session_write_file(session); - if (rc <= 0) - { - /* error writing data */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 451, "Failed to write file\r\n"); - return LOOP_EXIT; - } - - /* we can try to receive more data */ - session->bufferpos += rc; - return LOOP_CONTINUE; -} - -/*! ftp_xfer_file mode */ -typedef enum -{ - XFER_FILE_RETR, /*!< Retrieve a file */ - XFER_FILE_STOR, /*!< Store a file */ - XFER_FILE_APPE, /*!< Append a file */ -} xfer_file_mode_t; - -/*! Transfer a file - * - * @param[in] session ftp session - * @param[in] args ftp arguments - * @param[in] mode transfer mode - * - * @returns failure - */ -static void -ftp_xfer_file(ftp_session_t* session, - const char* args, - xfer_file_mode_t mode) -{ - int rc; - - /* build the path of the file to transfer */ - if (build_path(session, session->cwd, args) != 0) - { - rc = errno; - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 553, "%s\r\n", strerror(rc)); - return; - } - - /* open the file for retrieving or storing */ - if (mode == XFER_FILE_RETR) - rc = ftp_session_open_file_read(session); - else - rc = ftp_session_open_file_write(session, mode == XFER_FILE_APPE); - - if (rc != 0) - { - /* error opening the file */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 450, "failed to open file\r\n"); - return; - } - - if (session->flags & (SESSION_PORT | SESSION_PASV)) - { - ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); - - if (session->flags & SESSION_PORT) - { - /* setup connection */ - rc = ftp_session_connect(session); - if (rc != 0) - { - /* error connecting */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "can't open data connection\r\n"); - return; - } - } - - /* set up the transfer */ - session->flags &= ~(SESSION_RECV | SESSION_SEND); - if (mode == XFER_FILE_RETR) - { - session->flags |= SESSION_SEND; - session->transfer = retrieve_transfer; - } - else - { - session->flags |= SESSION_RECV; - session->transfer = store_transfer; - } - - session->bufferpos = 0; - session->buffersize = 0; - - return; - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); -} - -/*! Transfer a directory - * - * @param[in] session ftp session - * @param[in] args ftp arguments - * @param[in] mode transfer mode - * @param[in] workaround whether to workaround LIST -a - */ -static void -ftp_xfer_dir(ftp_session_t* session, - const char* args, - xfer_dir_mode_t mode, - bool workaround) -{ - ssize_t rc; - size_t len; - struct stat st; - char* buffer; - - /* set up the transfer */ - session->dir_mode = mode; - session->flags &= ~SESSION_RECV; - session->flags |= SESSION_SEND; - - session->transfer = list_transfer; - session->buffersize = 0; - session->bufferpos = 0; - - if (strlen(args) > 0) - { - /* an argument was provided */ - if (build_path(session, session->cwd, args) != 0) - { - /* error building path */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - /* check if this is a directory */ - session->dp = opendir(session->buffer); - if (session->dp == NULL) - { - /* not a directory; check if it is a file */ - rc = stat(session->buffer, &st); - if (rc != 0) - { - /* error getting stat */ - rc = errno; - - /* work around broken clients that think LIST -a is valid */ - if (workaround && mode == XFER_DIR_LIST) - { - if (args[0] == '-' && (args[1] == 'a' || args[1] == 'l')) - { - if (args[2] == 0) - buffer = strdup(args + 2); - else - buffer = strdup(args + 3); - - if (buffer != NULL) - { - ftp_xfer_dir(session, buffer, mode, false); - free(buffer); - return; - } - - rc = ENOMEM; - } - } - - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - else if (mode == XFER_DIR_MLSD) - { - /* specified file instead of directory for MLSD */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - else if (mode == XFER_DIR_NLST) - { - /* NLST uses full path name */ - len = session->buffersize; - buffer = encode_path(session->buffer, &len, false); - } - else - { - /* everything else uses base name */ - const char* base = strrchr(session->buffer, '/') + 1; - - len = strlen(base); - buffer = encode_path(base, &len, false); - } - - if (buffer) - { - rc = ftp_session_fill_dirent(session, &st, buffer, len); - free(buffer); - } - else - rc = ENOMEM; - - if (rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - else - { - /* it was a directory, so set it as the lwd */ - memcpy(session->lwd, session->buffer, session->buffersize); - session->lwd[session->buffersize] = 0; - session->buffersize = 0; - - if (session->dir_mode == XFER_DIR_MLSD && (session->mlst_flags & SESSION_MLST_TYPE)) - { - /* send this directory as type=cdir */ - rc = ftp_session_fill_dirent_cdir(session, session->lwd); - if (rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - } - } - else if (ftp_session_open_cwd(session) != 0) - { - /* no argument, but opening cwd failed */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - else - { - /* set the cwd as the lwd */ - strcpy(session->lwd, session->cwd); - session->buffersize = 0; - - if (session->dir_mode == XFER_DIR_MLSD && (session->mlst_flags & SESSION_MLST_TYPE)) - { - /* send this directory as type=cdir */ - rc = ftp_session_fill_dirent_cdir(session, session->lwd); - if (rc != 0) - { - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "%s\r\n", strerror(rc)); - return; - } - } - } - - if (mode == XFER_DIR_MLST || mode == XFER_DIR_STAT) - { - /* this is a little different; we have to send the data over the command socket */ - ftp_session_set_state(session, DATA_TRANSFER_STATE, CLOSE_PASV | CLOSE_DATA); - session->data_fd = session->cmd_fd; - session->flags |= SESSION_SEND; - ftp_send_response(session, -213, "Status\r\n"); - return; - } - else if (session->flags & (SESSION_PORT | SESSION_PASV)) - { - ftp_session_set_state(session, DATA_CONNECT_STATE, CLOSE_DATA); - - if (session->flags & SESSION_PORT) - { - /* setup connection */ - rc = ftp_session_connect(session); - if (rc != 0) - { - /* error connecting */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 425, "can't open data connection\r\n"); - } - } - - return; - } - - /* we must have got LIST/MLSD/MLST/NLST without a preceding PORT or PASV */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); -} - -/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * - * * - * F T P C O M M A N D S * - * * - * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ - -/*! @fn static void ABOR(ftp_session_t *session, const char *args) - * - * @brief abort a transfer - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(ABOR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - if (session->state == COMMAND_STATE) - { - ftp_send_response(session, 225, "No transfer to abort\r\n"); - return; - } - - /* abort the transfer */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - - /* send response for this request */ - ftp_send_response(session, 225, "Aborted\r\n"); - - /* send response for transfer */ - ftp_send_response(session, 425, "Transfer aborted\r\n"); -} - -/*! @fn static void ALLO(ftp_session_t *session, const char *args) - * - * @brief allocate space - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(ALLO) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 202, "superfluous command\r\n"); -} - -/*! @fn static void APPE(ftp_session_t *session, const char *args) - * - * @brief append data to a file - * - * @note requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(APPE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the file in append mode */ - ftp_xfer_file(session, args, XFER_FILE_APPE); -} - -/*! @fn static void CDUP(ftp_session_t *session, const char *args) - * - * @brief CWD to parent directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(CDUP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* change to parent directory */ - cd_up(session); - - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void CWD(ftp_session_t *session, const char *args) - * - * @brief change working directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(CWD) -{ - struct stat st; - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* .. is equivalent to CDUP */ - if (strcmp(args, "..") == 0) - { - cd_up(session); - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - /* build the new cwd path */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* get the path status */ - rc = stat(session->buffer, &st); - if (rc != 0) - { - console_print(RED "stat '%s': %d %s\n" RESET, session->buffer, errno, strerror(errno)); - ftp_send_response(session, 550, "unavailable\r\n"); - return; - } - - /* make sure it is a directory */ - if (!S_ISDIR(st.st_mode)) - { - ftp_send_response(session, 553, "not a directory\r\n"); - return; - } - - /* copy the path into the cwd */ - strncpy(session->cwd, session->buffer, sizeof(session->cwd)); - - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void DELE(ftp_session_t *session, const char *args) - * - * @brief delete a file - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(DELE) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the file path */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* try to unlink the path */ - rc = unlink(session->buffer); - if (rc != 0) - { - /* error unlinking the file */ - console_print(RED "unlink: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to delete file\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void FEAT(ftp_session_t *session, const char *args) - * - * @brief list server features - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(FEAT) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* list our features */ - ftp_send_response(session, -211, "\r\n" - " MDTM\r\n" - " MLST Type%s;Size%s;Modify%s;Perm%s;UNIX.mode%s;\r\n" - " PASV\r\n" - " SIZE\r\n" - " TVFS\r\n" - " UTF8\r\n" - "\r\n" - "211 End\r\n", - session->mlst_flags & SESSION_MLST_TYPE ? "*" : "", - session->mlst_flags & SESSION_MLST_SIZE ? "*" : "", - session->mlst_flags & SESSION_MLST_MODIFY ? "*" : "", - session->mlst_flags & SESSION_MLST_PERM ? "*" : "", - session->mlst_flags & SESSION_MLST_UNIX_MODE ? "*" : ""); -} - -/*! @fn static void HELP(ftp_session_t *session, const char *args) - * - * @brief print server help - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(HELP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* list our accepted commands */ - ftp_send_response(session, -214, - "The following commands are recognized\r\n" - " ABOR ALLO APPE CDUP CWD DELE FEAT HELP LIST MDTM MKD MLSD MLST MODE\r\n" - " NLST NOOP OPTS PASS PASV PORT PWD QUIT REST RETR RMD RNFR RNTO STAT\r\n" - " STOR STOU STRU SYST TYPE USER XCUP XCWD XMKD XPWD XRMD\r\n" - "214 End\r\n"); -} - -/*! @fn static void LIST(ftp_session_t *session, const char *args) - * - * @brief retrieve a directory listing - * - * @note Requires a PORT or PASV connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(LIST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in LIST mode */ - ftp_xfer_dir(session, args, XFER_DIR_LIST, true); -} - -/*! @fn static void MDTM(ftp_session_t *session, const char *args) - * - * @brief get last modification time - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MDTM) -{ - int rc; -#ifdef _3DS - uint64_t mtime; -#else - struct stat st; -#endif - time_t t_mtime; - struct tm* tm; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - -#ifdef _3DS - rc = sdmc_getmtime(session->buffer, &mtime); - if (rc != 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - t_mtime = mtime; -#else - rc = stat(session->buffer, &st); - if (rc != 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - t_mtime = st.st_mtime; -#endif - - tm = gmtime(&t_mtime); - if (tm == NULL) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - - session->buffersize = strftime(session->buffer, sizeof(session->buffer), "%Y%m%d%H%M%S", tm); - if (session->buffersize == 0) - { - ftp_send_response(session, 550, "Error getting mtime\r\n"); - return; - } - - session->buffer[session->buffersize] = 0; - - ftp_send_response(session, 213, "%s\r\n", session->buffer); -} -/*! @fn static void MKD(ftp_session_t *session, const char *args) - * - * @brief create a directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MKD) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* try to create the directory */ - rc = mkdir(session->buffer, 0755); - if (rc != 0 && errno != EEXIST) - { - /* mkdir failure */ - console_print(RED "mkdir: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to create directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void MLSD(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MLSD) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in MLSD mode */ - ftp_xfer_dir(session, args, XFER_DIR_MLSD, true); -} - -/*! @fn static void MLST(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MLST) -{ - struct stat st; - int rc; - char* path; - size_t len; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 501, "%s\r\n", strerror(errno)); - return; - } - - /* stat path */ - rc = lstat(session->buffer, &st); - if (rc != 0) - { - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - /* encode \n in path */ - len = session->buffersize; - path = encode_path(session->buffer, &len, true); - if (!path) - { - ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); - return; - } - - session->dir_mode = XFER_DIR_MLST; - rc = ftp_session_fill_dirent(session, &st, path, len); - free(path); - if (rc != 0) - { - ftp_send_response(session, 550, "%s\r\n", strerror(errno)); - return; - } - - path = malloc(session->buffersize + 1); - if (!path) - { - ftp_send_response(session, 550, "%s\r\n", strerror(ENOMEM)); - return; - } - - memcpy(path, session->buffer, session->buffersize); - path[session->buffersize] = 0; - ftp_send_response(session, -250, "Status\r\n%s250 End\r\n", path); - free(path); -} - -/*! @fn static void MODE(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(MODE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we only accept S (stream) mode */ - if (strcasecmp(args, "S") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - ftp_send_response(session, 504, "unavailable\r\n"); -} - -/*! @fn static void NLST(ftp_session_t *session, const char *args) - * - * @brief retrieve a name list - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(NLST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the path in NLST mode */ - return ftp_xfer_dir(session, args, XFER_DIR_NLST, false); -} - -/*! @fn static void NOOP(ftp_session_t *session, const char *args) - * - * @brief no-op - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(NOOP) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* this is a no-op */ - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void OPTS(ftp_session_t *session, const char *args) - * - * @brief set options - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(OPTS) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we accept the following UTF8 options */ - if (strcasecmp(args, "UTF8") == 0 || strcasecmp(args, "UTF8 ON") == 0 || strcasecmp(args, "UTF8 NLST") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - /* check MLST options */ - if (strncasecmp(args, "MLST ", 5) == 0) - { - static const struct - { - const char* name; - session_mlst_flags_t flag; - } mlst_flags[] = - { - { - "Type;", - SESSION_MLST_TYPE, - }, - { - "Size;", - SESSION_MLST_SIZE, - }, - { - "Modify;", - SESSION_MLST_MODIFY, - }, - { - "Perm;", - SESSION_MLST_PERM, - }, - { - "UNIX.mode;", - SESSION_MLST_UNIX_MODE, - }, - }; - static const size_t num_mlst_flags = sizeof(mlst_flags) / sizeof(mlst_flags[0]); - - session_mlst_flags_t flags = 0; - args += 5; - const char* p = args; - while (*p) - { - for (size_t i = 0; i < num_mlst_flags; ++i) - { - if (strncasecmp(mlst_flags[i].name, p, strlen(mlst_flags[i].name)) == 0) - { - flags |= mlst_flags[i].flag; - p += strlen(mlst_flags[i].name) - 1; - break; - } - } - - while (*p && *p != ';') - ++p; - - if (*p == ';') - ++p; - } - - session->mlst_flags = flags; - ftp_send_response(session, 200, "MLST OPTS%s%s%s%s%s%s\r\n", - flags ? " " : "", - flags & SESSION_MLST_TYPE ? "Type;" : "", - flags & SESSION_MLST_SIZE ? "Size;" : "", - flags & SESSION_MLST_MODIFY ? "Modify;" : "", - flags & SESSION_MLST_PERM ? "Perm;" : "", - flags & SESSION_MLST_UNIX_MODE ? "UNIX.mode;" : ""); - return; - } - - ftp_send_response(session, 504, "invalid argument\r\n"); -} - -/*! @fn static void PASS(ftp_session_t *session, const char *args) - * - * @brief provide password - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PASS) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - ftp_auth_check(session, NULL, args); -} - -/*! @fn static void PASV(ftp_session_t *session, const char *args) - * - * @brief request an address to connect to - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PASV) -{ - int rc; - char buffer[INET_ADDRSTRLEN + 10]; - char* p; - in_port_t port; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - memset(buffer, 0, sizeof(buffer)); - - /* reset the state */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - session->flags &= ~(SESSION_PASV | SESSION_PORT); - - /* create a socket to listen on */ - session->pasv_fd = socket(AF_INET, SOCK_STREAM, 0); - if (session->pasv_fd < 0) - { - console_print(RED "socket: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* set the socket options */ - rc = ftp_set_socket_options(session->pasv_fd); - if (rc != 0) - { - /* failed to set socket options */ - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* grab a new port */ - session->pasv_addr.sin_port = htons(next_data_port()); - -#if defined(_3DS) || defined(__SWITCH__) - console_print(YELLOW "binding to %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); -#endif - - /* bind to the port */ - rc = bind(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, - sizeof(session->pasv_addr)); - if (rc != 0) - { - /* failed to bind */ - console_print(RED "bind: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - - /* listen on the socket */ - rc = listen(session->pasv_fd, 1); - if (rc != 0) - { - /* failed to listen */ - console_print(RED "listen: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - -#ifndef _3DS - { - /* get the socket address since we requested an ephemeral port */ - socklen_t addrlen = sizeof(session->pasv_addr); - rc = getsockname(session->pasv_fd, (struct sockaddr*)&session->pasv_addr, - &addrlen); - if (rc != 0) - { - /* failed to get socket address */ - console_print(RED "getsockname: %d %s\n" RESET, errno, strerror(errno)); - ftp_session_close_pasv(session); - ftp_send_response(session, 451, "\r\n"); - return; - } - } -#endif - - /* we are now listening on the socket */ - console_print(YELLOW "listening on %s:%u\n" RESET, - inet_ntoa(session->pasv_addr.sin_addr), - ntohs(session->pasv_addr.sin_port)); - session->flags |= SESSION_PASV; - - /* print the address in the ftp format */ - port = ntohs(session->pasv_addr.sin_port); - strcpy(buffer, inet_ntoa(session->pasv_addr.sin_addr)); - sprintf(buffer + strlen(buffer), ",%u,%u", - port >> 8, port & 0xFF); - for (p = buffer; *p; ++p) - { - if (*p == '.') - *p = ','; - } - - ftp_send_response(session, 227, "%s\r\n", buffer); -} - -/*! @fn static void PORT(ftp_session_t *session, const char *args) - * - * @brief provide an address for the server to connect to - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PORT) -{ - char *addrstr, *p, *portstr; - int commas = 0, rc; - short port = 0; - unsigned long val; - struct sockaddr_in addr; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* reset the state */ - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - session->flags &= ~(SESSION_PASV | SESSION_PORT); - - /* dup the args since they are const and we need to change it */ - addrstr = strdup(args); - if (addrstr == NULL) - { - ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); - return; - } - - /* replace a,b,c,d,e,f with a.b.c.d\0e.f */ - for (p = addrstr; *p; ++p) - { - if (*p == ',') - { - if (commas != 3) - *p = '.'; - else - { - *p = 0; - portstr = p + 1; - } - ++commas; - } - } - - /* make sure we got the right number of values */ - if (commas != 5) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - - /* parse the address */ - rc = inet_aton(addrstr, &addr.sin_addr); - if (rc == 0) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - - /* parse the port */ - val = 0; - port = 0; - for (p = portstr; *p; ++p) - { - if (!isdigit((int)*p)) - { - if (p == portstr || *p != '.' || val > 0xFF) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - port <<= 8; - port += val; - val = 0; - } - else - { - val *= 10; - val += *p - '0'; - } - } - - /* validate the port */ - if (val > 0xFF || port > 0xFF) - { - free(addrstr); - ftp_send_response(session, 501, "%s\r\n", strerror(EINVAL)); - return; - } - port <<= 8; - port += val; - - /* fill in the address port and family */ - addr.sin_family = AF_INET; - addr.sin_port = htons(port); - - free(addrstr); - - memcpy(&session->peer_addr, &addr, sizeof(addr)); - - /* we are ready to connect to the client */ - session->flags |= SESSION_PORT; - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void PWD(ftp_session_t *session, const char *args) - * - * @brief print working directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(PWD) -{ - static char buffer[CMD_BUFFERSIZE]; - size_t len = sizeof(buffer), i; - char* path; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* encode the cwd */ - len = strlen(session->cwd); - path = encode_path(session->cwd, &len, true); - if (path != NULL) - { - i = sprintf(buffer, "257 \""); - if (i + len + 3 > sizeof(buffer)) - { - /* buffer will overflow */ - free(path); - ftp_session_set_state(session, COMMAND_STATE, CLOSE_PASV | CLOSE_DATA); - ftp_send_response(session, 550, "unavailable\r\n"); - ftp_send_response(session, 425, "%s\r\n", strerror(EOVERFLOW)); - return; - } - memcpy(buffer + i, path, len); - free(path); - len += i; - buffer[len++] = '"'; - buffer[len++] = '\r'; - buffer[len++] = '\n'; - - ftp_send_response_buffer(session, buffer, len); - return; - } - - ftp_send_response(session, 425, "%s\r\n", strerror(ENOMEM)); -} - -/*! @fn static void QUIT(ftp_session_t *session, const char *args) - * - * @brief terminate ftp session - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(QUIT) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* disconnect from the client */ - ftp_send_response(session, 221, "disconnecting\r\n"); - ftp_session_close_cmd(session); -} - -/*! @fn static void REST(ftp_session_t *session, const char *args) - * - * @brief restart a transfer - * - * @note sets file position for a subsequent STOR operation - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(REST) -{ - const char* p; - uint64_t pos = 0; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* make sure an argument is provided */ - if (args == NULL) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - /* parse the offset */ - for (p = args; *p; ++p) - { - if (!isdigit((int)*p)) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - if (UINT64_MAX / 10 < pos) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - pos *= 10; - - if (UINT64_MAX - (*p - '0') < pos) - { - ftp_send_response(session, 504, "invalid argument\r\n"); - return; - } - - pos += (*p - '0'); - } - - /* set the restart offset */ - session->filepos = pos; - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void RETR(ftp_session_t *session, const char *args) - * - * @brief retrieve a file - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RETR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the file to retrieve */ - return ftp_xfer_file(session, args, XFER_FILE_RETR); -} - -/*! @fn static void RMD(ftp_session_t *session, const char *args) - * - * @brief remove a directory - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RMD) -{ - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to remove */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* remove the directory */ - rc = rmdir(session->buffer); - if (rc != 0) - { - /* rmdir error */ - console_print(RED "rmdir: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to delete directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void RNFR(ftp_session_t *session, const char *args) - * - * @brief rename from - * - * @note Must be followed by RNTO - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RNFR) -{ - int rc; - struct stat st; - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to rename from */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - /* make sure the path exists */ - rc = lstat(session->buffer, &st); - if (rc != 0) - { - /* error getting path status */ - console_print(RED "lstat: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 450, "no such file or directory\r\n"); - return; - } - - /* we are ready for RNTO */ - session->flags |= SESSION_RENAME; - ftp_send_response(session, 350, "OK\r\n"); -} - -/*! @fn static void RNTO(ftp_session_t *session, const char *args) - * - * @brief rename to - * - * @note Must be preceded by RNFR - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(RNTO) -{ - static char rnfr[XFER_BUFFERSIZE]; // rename-from buffer - int rc; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* make sure the previous command was RNFR */ - if (!(session->flags & SESSION_RENAME)) - { - ftp_send_response(session, 503, "Bad sequence of commands\r\n"); - return; - } - - /* clear the rename state */ - session->flags &= ~SESSION_RENAME; - - /* copy the RNFR path */ - memcpy(rnfr, session->buffer, XFER_BUFFERSIZE); - - /* build the path to rename to */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 554, "%s\r\n", strerror(errno)); - return; - } - - /* rename the file */ - rc = rename(rnfr, session->buffer); - if (rc != 0) - { - /* rename failure */ - console_print(RED "rename: %d %s\n" RESET, errno, strerror(errno)); - ftp_send_response(session, 550, "failed to rename file/directory\r\n"); - return; - } - - update_free_space(); - ftp_send_response(session, 250, "OK\r\n"); -} - -/*! @fn static void SIZE(ftp_session_t *session, const char *args) - * - * @brief get file size - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(SIZE) -{ - int rc; - struct stat st; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* build the path to stat */ - if (build_path(session, session->cwd, args) != 0) - { - ftp_send_response(session, 553, "%s\r\n", strerror(errno)); - return; - } - - rc = stat(session->buffer, &st); - if (rc != 0 || !S_ISREG(st.st_mode)) - { - ftp_send_response(session, 550, "Could not get file size.\r\n"); - return; - } - - ftp_send_response(session, 213, "%" PRIu64 "\r\n", - (uint64_t)st.st_size); -} - -/*! @fn static void STAT(ftp_session_t *session, const char *args) - * - * @brief get status - * - * @note If no argument is supplied, and a transfer is occurring, get the - * current transfer status. If no argument is supplied, and no transfer - * is occurring, get the server status. If an argument is supplied, this - * is equivalent to LIST, except the data is sent over the command - * socket. - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STAT) -{ - time_t uptime = time(NULL) - start_time; - int hours = uptime / 3600; - int minutes = (uptime / 60) % 60; - int seconds = uptime % 60; - - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - if (session->state == DATA_CONNECT_STATE) - { - /* we are waiting to connect to the client */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Waiting for data connection\r\n" - "211 End\r\n"); - return; - } - else if (session->state == DATA_TRANSFER_STATE) - { - /* we are in the middle of a transfer */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Transferred %" PRIu64 " bytes\r\n" - "211 End\r\n", - session->filepos); - return; - } - - if (strlen(args) == 0) - { - /* no argument provided, send the server status */ - ftp_send_response(session, -211, "FTP server status\r\n" - " Uptime: %02d:%02d:%02d\r\n" - "211 End\r\n", - hours, minutes, seconds); - return; - } - - /* argument provided, open the path in STAT mode */ - ftp_xfer_dir(session, args, XFER_DIR_STAT, false); -} - -/*! @fn static void STOR(ftp_session_t *session, const char *args) - * - * @brief store a file - * - * @note Requires a PASV or PORT connection - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STOR) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* open the file to store */ - return ftp_xfer_file(session, args, XFER_FILE_STOR); -} - -/*! @fn static void STOU(ftp_session_t *session, const char *args) - * - * @brief store a unique file - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STOU) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - /* we do not support this yet */ - ftp_session_set_state(session, COMMAND_STATE, 0); - - ftp_send_response(session, 502, "unavailable\r\n"); -} - -/*! @fn static void STRU(ftp_session_t *session, const char *args) - * - * @brief set file structure - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(STRU) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we only support F (no structure) mode */ - if (strcasecmp(args, "F") == 0) - { - ftp_send_response(session, 200, "OK\r\n"); - return; - } - - ftp_send_response(session, 504, "unavailable\r\n"); -} - -/*! @fn static void SYST(ftp_session_t *session, const char *args) - * - * @brief identify system - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(SYST) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we are UNIX compliant with 8-bit characters */ - ftp_send_response(session, 215, "UNIX Type: L8\r\n"); -} - -/*! @fn static void TYPE(ftp_session_t *session, const char *args) - * - * @brief set transfer mode - * - * @note transfer mode is always binary - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(TYPE) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - - ftp_session_set_state(session, COMMAND_STATE, 0); - - /* we always transfer in binary mode */ - ftp_send_response(session, 200, "OK\r\n"); -} - -/*! @fn static void USER(ftp_session_t *session, const char *args) - * - * @brief provide user name - * - * @param[in] session ftp session - * @param[in] args arguments - */ -FTP_DECLARE(USER) -{ - console_print(CYAN "%s %s\n" RESET, __func__, args ? args : ""); - ftp_auth_check(session, args, NULL); -} diff --git a/source/Sysmodule/source/led.c b/source/Sysmodule/source/led.c deleted file mode 100644 index cf9b3d43..00000000 --- a/source/Sysmodule/source/led.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "led.h" -#include -#include - -#include "util.h" - -void flash_led_connect() -{ - HidsysNotificationLedPattern pattern; - memset(&pattern, 0, sizeof(pattern)); - - // Setup Breathing effect pattern data. - pattern.baseMiniCycleDuration = 0x8; // 100ms. - pattern.totalMiniCycles = 0x2; // 3 mini cycles. Last one 12.5ms. - pattern.totalFullCycles = 0x0; // Repeat forever. - pattern.startIntensity = 0x2; // 13%. - - pattern.miniCycles[0].ledIntensity = 0xF; // 100%. - pattern.miniCycles[0].transitionSteps = 0xF; // 15 steps. Transition time 1.5s. - pattern.miniCycles[0].finalStepDuration = 0x0; // Forced 12.5ms. - pattern.miniCycles[1].ledIntensity = 0x2; // 13%. - pattern.miniCycles[1].transitionSteps = 0xF; // 15 steps. Transition time 1.5s. - pattern.miniCycles[1].finalStepDuration = 0x0; // Forced 12.5ms. - - u64 uniquePadIds[5] = {0}; - - s32 total_entries = 0; - - Result rc = hidsysGetUniquePadIds(uniquePadIds, 5, &total_entries); - if (R_FAILED(rc) && rc != MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer)) - fatalThrow(rc); - - for (int i = 0; i < total_entries; i++) - { - hidsysSetNotificationLedPattern(&pattern, uniquePadIds[i]); - hidsysSetNotificationLedPatternWithTimeout(&pattern, uniquePadIds[i], LED_TIMEOUT); - } -} - -void flash_led_disconnect() -{ - HidsysNotificationLedPattern pattern; - memset(&pattern, 0, sizeof(pattern)); - - u64 uniquePadIds[2]; - memset(uniquePadIds, 0, sizeof(uniquePadIds)); - - s32 total_entries = 0; - - Result rc = hidsysGetUniquePadsFromNpad(hidGetHandheldMode() ? CONTROLLER_HANDHELD : CONTROLLER_PLAYER_1, uniquePadIds, 2, &total_entries); - if (R_FAILED(rc) && rc != MAKERESULT(Module_Libnx, LibnxError_IncompatSysVer)) - fatalThrow(rc); - - for (int i = 0; i < total_entries; i++) - hidsysSetNotificationLedPattern(&pattern, uniquePadIds[i]); -} diff --git a/source/Sysmodule/source/led.h b/source/Sysmodule/source/led.h deleted file mode 100644 index e9ccec4a..00000000 --- a/source/Sysmodule/source/led.h +++ /dev/null @@ -1,5 +0,0 @@ -#pragma once -#define LED_TIMEOUT 1e+10 - -void flash_led_connect(); -void flash_led_disconnect(); \ No newline at end of file diff --git a/source/Sysmodule/source/main.cpp b/source/Sysmodule/source/main.cpp index dcb44052..c1447958 100644 --- a/source/Sysmodule/source/main.cpp +++ b/source/Sysmodule/source/main.cpp @@ -11,7 +11,6 @@ extern "C" { #include "ftp.h" #include "util.h" - #include "console.h" #include "minIni.h" } @@ -131,7 +130,6 @@ static loop_status_t loop(loop_status_t (*callback)(void)) { svcSleepThread(1e+7); status = callback(); - console_render(); if (status != LOOP_CONTINUE) return status; if (isPaused()) @@ -148,16 +146,6 @@ int main(int argc, char *argv[]) usb::Initialize(); psc::Initialize(); - FILE* should_log_file = fopen("/config/sys-ftpd/logs/ftpd_log_enabled", "r"); - if (should_log_file != NULL) - { - should_log = true; - fclose(should_log_file); - - mkdir("/config/sys-ftpd/logs", 0700); - unlink("/config/sys-ftpd/logs/ftpd.log"); - } - char buffer[100]; ini_gets("Pause", "disabled:", "0", buffer, 100, CONFIGPATH); From e6b268fe1460654bf3ef14087e100e5309bf3211 Mon Sep 17 00:00:00 2001 From: kivr Date: Thu, 28 May 2020 16:16:25 -0500 Subject: [PATCH 06/13] Rename ftp to network. --- common/config/sys-con/port_config.ini | 2 + source/Sysmodule/source/main.cpp | 34 +---- source/Sysmodule/source/{ftp.c => network.c} | 125 ++++++++-------- source/Sysmodule/source/{ftp.h => network.h} | 10 +- source/Sysmodule/source/util.c | 147 ------------------- source/Sysmodule/source/util.h | 21 --- 6 files changed, 77 insertions(+), 262 deletions(-) create mode 100644 common/config/sys-con/port_config.ini rename source/Sysmodule/source/{ftp.c => network.c} (79%) rename source/Sysmodule/source/{ftp.h => network.h} (68%) delete mode 100644 source/Sysmodule/source/util.c delete mode 100644 source/Sysmodule/source/util.h diff --git a/common/config/sys-con/port_config.ini b/common/config/sys-con/port_config.ini new file mode 100644 index 00000000..0cd99d4e --- /dev/null +++ b/common/config/sys-con/port_config.ini @@ -0,0 +1,2 @@ +[Port] +port:=8080 diff --git a/source/Sysmodule/source/main.cpp b/source/Sysmodule/source/main.cpp index c1447958..9d7d230c 100644 --- a/source/Sysmodule/source/main.cpp +++ b/source/Sysmodule/source/main.cpp @@ -9,9 +9,7 @@ #include "SwitchHDLHandler.h" extern "C" { - #include "ftp.h" - #include "util.h" - #include "minIni.h" + #include "network.h" } #include @@ -132,8 +130,6 @@ static loop_status_t loop(loop_status_t (*callback)(void)) status = callback(); if (status != LOOP_CONTINUE) return status; - if (isPaused()) - return LOOP_RESTART; } return LOOP_EXIT; } @@ -146,47 +142,29 @@ int main(int argc, char *argv[]) usb::Initialize(); psc::Initialize(); - char buffer[100]; - ini_gets("Pause", "disabled:", "0", buffer, 100, CONFIGPATH); - - //Checks if pausing is disabled in the config file, in which case it skips the entire pause initialization - if (strncmp(buffer, "1", 4) != 0) - { - Result rc = pauseInit(); - if (R_FAILED(rc)) - fatalThrow(rc); - } - loop_status_t status = LOOP_RESTART; WriteToLog("Going to pre_init"); - ftp_pre_init(); + network_pre_init(); WriteToLog("pre_init completed"); while (status == LOOP_RESTART) { - while (isPaused()) - { - svcSleepThread(1e+9); - } - /* initialize ftp subsystem */ - if (ftp_init() == 0) + if (network_init() == 0) { /* ftp loop */ - status = loop(ftp_loop); + status = loop(network_loop); /* done with ftp */ - ftp_exit(); + network_exit(); } else status = LOOP_EXIT; } - ftp_post_exit(); - - pauseExit(); + network_post_exit(); psc::Exit(); usb::Exit(); diff --git a/source/Sysmodule/source/ftp.c b/source/Sysmodule/source/network.c similarity index 79% rename from source/Sysmodule/source/ftp.c rename to source/Sysmodule/source/network.c index 2400789f..988b845e 100644 --- a/source/Sysmodule/source/ftp.c +++ b/source/Sysmodule/source/network.c @@ -1,12 +1,10 @@ // This file is under the terms of the unlicense (https://github.com/DavidBuchanan314/ftpd/blob/master/LICENSE) #define ENABLE_LOGGING 1 -#include "ftp.h" +#include "network.h" #include #include -#include #include -#include #include #include #include @@ -17,13 +15,10 @@ #include #include #include -#include -#include #include #include #include #define lstat stat -#include "util.h" #include "log.h" #include "Controllers/NetworkController.h" @@ -31,9 +26,9 @@ int LISTEN_PORT; //#define LISTEN_PORT 5000 -#define DATA_PORT 0 /* ephemeral port */ +#define CONFIGPATH "/config/sys-con/port_config.ini" -#include "minIni.h" +#include "ini.h" #include int Callback(const char* section, const char* key, const char* value, void* userdata) @@ -43,15 +38,15 @@ int Callback(const char* section, const char* key, const char* value, void* user return 1; } -typedef struct ftp_session_t ftp_session_t; +typedef struct network_session_t network_session_t; -/*! ftp session */ -struct ftp_session_t +/*! network session */ +struct network_session_t { struct sockaddr_in client_addr; /*!< listen address for PASV connection */ int cmd_fd; /*!< socket for command connection */ - ftp_session_t* next; /*!< link to next session */ - ftp_session_t* prev; /*!< link to prev session */ + network_session_t* next; /*!< link to next session */ + network_session_t* prev; /*!< link to prev session */ }; /*! appletHook cookie */ @@ -61,8 +56,8 @@ static AppletHookCookie cookie; static struct sockaddr_in serv_addr; /*! listen file descriptor */ static int listenfd = -1; -/*! list of ftp sessions */ -static ftp_session_t* sessions = NULL; +/*! list of network sessions */ +static network_session_t* sessions = NULL; /*! close a socket * @@ -70,7 +65,7 @@ static ftp_session_t* sessions = NULL; * @param[in] connected whether this socket is connected */ static void -ftp_closesocket(int fd, +network_closesocket(int fd, bool connected) { int rc; @@ -123,33 +118,33 @@ ftp_closesocket(int fd, WriteToLog("close: %d %s\n", errno, strerror(errno)); } -/*! close command socket on ftp session +/*! close command socket on network session * - * @param[in] session ftp session + * @param[in] session network session */ static void -ftp_session_close_cmd(ftp_session_t* session) +network_session_close_cmd(network_session_t* session) { /* close command socket */ if (session->cmd_fd >= 0) - ftp_closesocket(session->cmd_fd, true); + network_closesocket(session->cmd_fd, true); session->cmd_fd = -1; } -/*! destroy ftp session +/*! destroy network session * - * @param[in] session ftp session + * @param[in] session network session * * @returns the next session in the list */ -static ftp_session_t* -ftp_session_destroy(ftp_session_t* session) +static network_session_t* +network_session_destroy(network_session_t* session) { - ftp_session_t* next = session->next; + network_session_t* next = session->next; /* close all sockets/files */ - ftp_session_close_cmd(session); + network_session_close_cmd(session); /* unlink from sessions list */ if (session->next) @@ -169,16 +164,16 @@ ftp_session_destroy(ftp_session_t* session) return next; } -/*! allocate new ftp session +/*! allocate new network session * * @param[in] listen_fd socket to accept connection from */ static int -ftp_session_new(int listen_fd) +network_session_new(int listen_fd) { ssize_t rc; int new_fd; - ftp_session_t* session; + network_session_t* session; struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); @@ -196,11 +191,11 @@ ftp_session_new(int listen_fd) registerNetworkController(new_fd); /* allocate a new session */ - session = (ftp_session_t*)calloc(1, sizeof(ftp_session_t)); + session = (network_session_t*)calloc(1, sizeof(network_session_t)); if (session == NULL) { WriteToLog("failed to allocate session\n"); - ftp_closesocket(new_fd, true); + network_closesocket(new_fd, true); return -1; } @@ -226,21 +221,21 @@ ftp_session_new(int listen_fd) if (rc != 0) { WriteToLog("getsockname: %d %s\n", errno, strerror(errno)); - ftp_session_destroy(session); + network_session_destroy(session); return -1; } return 0; } -/*! poll sockets for ftp session +/*! poll sockets for network session * - * @param[in] session ftp session + * @param[in] session network session * * @returns next session */ -static ftp_session_t* -ftp_session_poll(ftp_session_t* session) +static network_session_t* +network_session_poll(network_session_t* session) { int rc; struct pollfd pollinfo[2]; @@ -256,7 +251,7 @@ ftp_session_poll(ftp_session_t* session) if (rc < 0) { WriteToLog("poll: %d %s\n", errno, strerror(errno)); - ftp_session_close_cmd(session); + network_session_close_cmd(session); } else if (rc > 0) { @@ -271,7 +266,7 @@ ftp_session_poll(ftp_session_t* session) if (pollinfo[0].revents & (POLLERR | POLLHUP)) { WriteToLog("cmd revents=0x%x\n", pollinfo[0].revents); - ftp_session_close_cmd(session); + network_session_close_cmd(session); } ssize_t count; @@ -280,7 +275,7 @@ ftp_session_poll(ftp_session_t* session) if (count == 0) { removeNetworkController(session->cmd_fd); - ftp_session_close_cmd(session); + network_session_close_cmd(session); } } } @@ -292,7 +287,7 @@ ftp_session_poll(ftp_session_t* session) /* disconnected from peer; destroy it and return next session */ WriteToLog("disconnected from peer\n"); - return ftp_session_destroy(session); + return network_session_destroy(session); } /*! Handle applet events @@ -314,14 +309,24 @@ applet_hook(AppletHookType type, } } -void ftp_pre_init(void) +void network_pre_init(void) { /* register applet hook */ appletHook(&cookie, applet_hook, NULL); } -/*! initialize ftp subsystem */ -int ftp_init(void) +static int network_parse_config_line(void *dummy, const char *section, const char *name, const char *value) +{ + if (strncmp(name, "port:", 5) == 0) + { + LISTEN_PORT = atoi(value); + } + + return 0; +} + +/*! initialize network subsystem */ +int network_init(void) { int rc = 0; @@ -330,7 +335,7 @@ int ftp_init(void) if (listenfd < 0) { WriteToLog("socket: %d %s\n", errno, strerror(errno)); - ftp_exit(); + network_exit(); return -1; } @@ -338,9 +343,7 @@ int ftp_init(void) serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; - char str_port[100]; - ini_gets("Port", "port:", "dummy", str_port, sizearray(str_port), CONFIGPATH); - LISTEN_PORT = atoi(str_port); + ini_parse(CONFIGPATH, network_parse_config_line, NULL); serv_addr.sin_port = htons(LISTEN_PORT); /* reuse address */ @@ -350,7 +353,7 @@ int ftp_init(void) if (rc != 0) { WriteToLog("setsockopt: %d %s\n", errno, strerror(errno)); - ftp_exit(); + network_exit(); return -1; } } @@ -360,7 +363,7 @@ int ftp_init(void) if (rc != 0) { WriteToLog("bind: %d %s\n", errno, strerror(errno)); - ftp_exit(); + network_exit(); return -1; } @@ -369,45 +372,45 @@ int ftp_init(void) if (rc != 0) { WriteToLog("listen: %d %s\n", errno, strerror(errno)); - ftp_exit(); + network_exit(); return -1; } return 0; } -/*! deinitialize ftp subsystem */ -void ftp_exit(void) +/*! deinitialize network subsystem */ +void network_exit(void) { - WriteToLog("exiting ftp server\n"); + WriteToLog("exiting network server\n"); /* clean up all sessions */ while (sessions != NULL) - ftp_session_destroy(sessions); + network_session_destroy(sessions); /* stop listening for new clients */ if (listenfd >= 0) - ftp_closesocket(listenfd, false); + network_closesocket(listenfd, false); /* deinitialize socket driver */ WriteToLog("Waiting for socketExit()...\n"); } -void ftp_post_exit(void) +void network_post_exit(void) { } -/*! ftp look +/*! network look * * @returns whether to keep looping */ loop_status_t -ftp_loop(void) +network_loop(void) { int rc; struct pollfd pollinfo; - ftp_session_t* session; + network_session_t* session; /* we will poll for new client connections */ pollinfo.fd = listenfd; @@ -432,7 +435,7 @@ ftp_loop(void) if (pollinfo.revents & POLLIN) { /* we got a new client */ - if (ftp_session_new(listenfd) != 0) + if (network_session_new(listenfd) != 0) { return LOOP_RESTART; } @@ -446,7 +449,7 @@ ftp_loop(void) /* poll each session */ session = sessions; while (session != NULL) - session = ftp_session_poll(session); + session = network_session_poll(session); return LOOP_CONTINUE; } diff --git a/source/Sysmodule/source/ftp.h b/source/Sysmodule/source/network.h similarity index 68% rename from source/Sysmodule/source/ftp.h rename to source/Sysmodule/source/network.h index fc7eae30..341708fe 100644 --- a/source/Sysmodule/source/ftp.h +++ b/source/Sysmodule/source/network.h @@ -10,8 +10,8 @@ typedef enum LOOP_EXIT, /*!< Terminate looping */ } loop_status_t; -void ftp_pre_init(void); -int ftp_init(void); -loop_status_t ftp_loop(void); -void ftp_exit(void); -void ftp_post_exit(void); +void network_pre_init(void); +int network_init(void); +loop_status_t network_loop(void); +void network_exit(void); +void network_post_exit(void); diff --git a/source/Sysmodule/source/util.c b/source/Sysmodule/source/util.c deleted file mode 100644 index d0b3741f..00000000 --- a/source/Sysmodule/source/util.c +++ /dev/null @@ -1,147 +0,0 @@ -#include "util.h" -#include -#include -#include -#include -#include - -#include "minIni.h" -#include - -static bool inputThreadRunning = true; -static bool paused = false; -static Mutex pausedMutex = 0; -static Thread pauseThread; -static HidControllerKeys comboKeys[8] = {}; - -void inputPoller() -{ - do - { - hidScanInput(); - u64 kHeld = 0; - for (u8 controller = 0; controller != 10; controller++) - kHeld |= hidKeysHeld(controller); - - u64 keyCombo = 0; - for (u8 i = 0; i != sizearray(comboKeys); ++i) - keyCombo |= comboKeys[i]; - - static bool keyComboPressed = false; - - if ((kHeld & keyCombo) == keyCombo) - { - if (!keyComboPressed) - { - keyComboPressed = true; - setPaused(!isPaused()); - } - } - else - { - keyComboPressed = false; - } - svcSleepThread(1e+8); - } while (inputThreadRunning); -} - -const char* buttons[] = { - "A", - "B", - "X", - "Y", - "LS", - "RS", - "L", - "R", - "ZL", - "ZR", - "PLUS", - "MINUS", - "DLEFT", - "DUP", - "DRIGHT", - "DDOWN", -}; - -HidControllerKeys GetKey(const char* text) -{ - for (u8 i = 0; i != sizearray(buttons); ++i) - { - if (strcmp(text, buttons[i]) == 0) - { - return BIT(i); - } - } - return 0; -} - -Result pauseInit() -{ - Result rc; - mutexLock(&pausedMutex); - - FILE* should_pause_file = fopen("/config/sys-ftpd/ftpd_paused", "r"); - if (should_pause_file != NULL) - { - paused = true; - fclose(should_pause_file); - } - - { - char buffer[128]; - ini_gets("Pause", "keycombo:", "PLUS+MINUS+X", buffer, 128, CONFIGPATH); - char* token = strtok(buffer, "+ "); - int i = 0; - while (token != NULL && i != sizearray(comboKeys)) - { - comboKeys[i++] = GetKey(token); - token = strtok(NULL, "+ "); - }; - } - - inputThreadRunning = true; - - rc = threadCreate(&pauseThread, inputPoller, NULL, NULL, 0x300, 0x3B, -2); - if (R_FAILED(rc)) - goto exit; - - rc = threadStart(&pauseThread); - if (R_FAILED(rc)) - goto exit; - -exit: - mutexUnlock(&pausedMutex); - return rc; -} - -void pauseExit() -{ - inputThreadRunning = false; - threadWaitForExit(&pauseThread); - threadClose(&pauseThread); -} - -bool isPaused() -{ - mutexLock(&pausedMutex); - bool ret = paused; - mutexUnlock(&pausedMutex); - return ret; -} - -void setPaused(bool newPaused) -{ - mutexLock(&pausedMutex); - paused = newPaused; - if (paused) - { - FILE* should_pause_file = fopen("/config/sys-ftpd/ftpd_paused", "w"); - fclose(should_pause_file); - } - else - { - unlink("/config/sys-ftpd/ftpd_paused"); - } - mutexUnlock(&pausedMutex); -} diff --git a/source/Sysmodule/source/util.h b/source/Sysmodule/source/util.h deleted file mode 100644 index 73bcba43..00000000 --- a/source/Sysmodule/source/util.h +++ /dev/null @@ -1,21 +0,0 @@ -#pragma once -#include - -#define sizearray(a) (sizeof(a) / sizeof((a)[0])) - -#define TITLE_ID 0x420000000000000E -#define CONFIGPATH "/config/sys-ftpd/config.ini" - -#define R_ASSERT(res_expr) \ - ({ \ - const Result rc = (res_expr); \ - if (R_FAILED(rc)) \ - { \ - fatalThrow(rc); \ - } \ - }) - -Result pauseInit(); -void pauseExit(); -bool isPaused(); -void setPaused(bool newPaused); \ No newline at end of file From 40fed29c7e119fd9fe658cc04b612772d2533be0 Mon Sep 17 00:00:00 2001 From: kivr Date: Thu, 28 May 2020 18:41:18 -0500 Subject: [PATCH 07/13] Fix get port from port_config.ini --- common/config/sys-con/port_config.ini | 2 +- source/Sysmodule/source/network.c | 54 ++++++++++++++------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/common/config/sys-con/port_config.ini b/common/config/sys-con/port_config.ini index 0cd99d4e..f523c062 100644 --- a/common/config/sys-con/port_config.ini +++ b/common/config/sys-con/port_config.ini @@ -1,2 +1,2 @@ [Port] -port:=8080 +port=8080 diff --git a/source/Sysmodule/source/network.c b/source/Sysmodule/source/network.c index 988b845e..84582f02 100644 --- a/source/Sysmodule/source/network.c +++ b/source/Sysmodule/source/network.c @@ -73,7 +73,7 @@ network_closesocket(int fd, socklen_t addrlen = sizeof(addr); struct pollfd pollinfo; - // WriteToLog("0x%X\n", socketGetLastBsdResult()); + // WriteToLog("0x%X", socketGetLastBsdResult()); if (connected) { @@ -81,17 +81,17 @@ network_closesocket(int fd, rc = getpeername(fd, (struct sockaddr*)&addr, &addrlen); if (rc != 0) { - WriteToLog("getpeername: %d %s\n", errno, strerror(errno)); - WriteToLog("closing connection to fd=%d\n", fd); + WriteToLog("getpeername: %d %s", errno, strerror(errno)); + WriteToLog("closing connection to fd=%d", fd); } else - WriteToLog("closing connection to %s:%u\n", + WriteToLog("closing connection to %s:%u", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); /* shutdown connection */ rc = shutdown(fd, SHUT_WR); if (rc != 0) - WriteToLog("shutdown: %d %s\n", errno, strerror(errno)); + WriteToLog("shutdown: %d %s", errno, strerror(errno)); /* wait for client to close connection */ pollinfo.fd = fd; @@ -99,7 +99,7 @@ network_closesocket(int fd, pollinfo.revents = 0; rc = poll(&pollinfo, 1, 250); if (rc < 0) - WriteToLog("poll: %d %s\n", errno, strerror(errno)); + WriteToLog("poll: %d %s", errno, strerror(errno)); } /* set linger to 0 */ @@ -109,13 +109,13 @@ network_closesocket(int fd, rc = setsockopt(fd, SOL_SOCKET, SO_LINGER, &linger, sizeof(linger)); if (rc != 0) - WriteToLog("setsockopt: SO_LINGER %d %s\n", + WriteToLog("setsockopt: SO_LINGER %d %s", errno, strerror(errno)); /* close socket */ rc = close(fd); if (rc != 0) - WriteToLog("close: %d %s\n", errno, strerror(errno)); + WriteToLog("close: %d %s", errno, strerror(errno)); } /*! close command socket on network session @@ -181,11 +181,11 @@ network_session_new(int listen_fd) new_fd = accept(listen_fd, (struct sockaddr*)&addr, &addrlen); if (new_fd < 0) { - WriteToLog("accept: %d %s\n", errno, strerror(errno)); + WriteToLog("accept: %d %s", errno, strerror(errno)); return -1; } - WriteToLog("accepted connection from %s:%u\n", + WriteToLog("accepted connection from %s:%u", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port)); registerNetworkController(new_fd); @@ -194,7 +194,7 @@ network_session_new(int listen_fd) session = (network_session_t*)calloc(1, sizeof(network_session_t)); if (session == NULL) { - WriteToLog("failed to allocate session\n"); + WriteToLog("failed to allocate session"); network_closesocket(new_fd, true); return -1; } @@ -220,7 +220,7 @@ network_session_new(int listen_fd) rc = getsockname(new_fd, (struct sockaddr*)&session->client_addr, &addrlen); if (rc != 0) { - WriteToLog("getsockname: %d %s\n", errno, strerror(errno)); + WriteToLog("getsockname: %d %s", errno, strerror(errno)); network_session_destroy(session); return -1; } @@ -250,7 +250,7 @@ network_session_poll(network_session_t* session) rc = poll(pollinfo, nfds, 0); if (rc < 0) { - WriteToLog("poll: %d %s\n", errno, strerror(errno)); + WriteToLog("poll: %d %s", errno, strerror(errno)); network_session_close_cmd(session); } else if (rc > 0) @@ -260,12 +260,12 @@ network_session_poll(network_session_t* session) { /* handle command */ if (pollinfo[0].revents & POLL_UNKNOWN) - WriteToLog("cmd_fd: revents=0x%08X\n", pollinfo[0].revents); + WriteToLog("cmd_fd: revents=0x%08X", pollinfo[0].revents); /* we need to read a new command */ if (pollinfo[0].revents & (POLLERR | POLLHUP)) { - WriteToLog("cmd revents=0x%x\n", pollinfo[0].revents); + WriteToLog("cmd revents=0x%x", pollinfo[0].revents); network_session_close_cmd(session); } @@ -285,7 +285,7 @@ network_session_poll(network_session_t* session) return session->next; /* disconnected from peer; destroy it and return next session */ - WriteToLog("disconnected from peer\n"); + WriteToLog("disconnected from peer"); return network_session_destroy(session); } @@ -317,9 +317,10 @@ void network_pre_init(void) static int network_parse_config_line(void *dummy, const char *section, const char *name, const char *value) { - if (strncmp(name, "port:", 5) == 0) + if (strncmp(name, "port", 4) == 0) { LISTEN_PORT = atoi(value); + return 1; } return 0; @@ -334,7 +335,7 @@ int network_init(void) listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { - WriteToLog("socket: %d %s\n", errno, strerror(errno)); + WriteToLog("socket: %d %s", errno, strerror(errno)); network_exit(); return -1; } @@ -345,6 +346,7 @@ int network_init(void) serv_addr.sin_addr.s_addr = INADDR_ANY; ini_parse(CONFIGPATH, network_parse_config_line, NULL); serv_addr.sin_port = htons(LISTEN_PORT); + WriteToLog("server port: %d", LISTEN_PORT); /* reuse address */ { @@ -352,7 +354,7 @@ int network_init(void) rc = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); if (rc != 0) { - WriteToLog("setsockopt: %d %s\n", errno, strerror(errno)); + WriteToLog("setsockopt: %d %s", errno, strerror(errno)); network_exit(); return -1; } @@ -362,7 +364,7 @@ int network_init(void) rc = bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); if (rc != 0) { - WriteToLog("bind: %d %s\n", errno, strerror(errno)); + WriteToLog("bind: %d %s", errno, strerror(errno)); network_exit(); return -1; } @@ -371,7 +373,7 @@ int network_init(void) rc = listen(listenfd, 5); if (rc != 0) { - WriteToLog("listen: %d %s\n", errno, strerror(errno)); + WriteToLog("listen: %d %s", errno, strerror(errno)); network_exit(); return -1; } @@ -383,7 +385,7 @@ int network_init(void) void network_exit(void) { - WriteToLog("exiting network server\n"); + WriteToLog("exiting network server"); /* clean up all sessions */ while (sessions != NULL) @@ -394,7 +396,7 @@ void network_exit(void) network_closesocket(listenfd, false); /* deinitialize socket driver */ - WriteToLog("Waiting for socketExit()...\n"); + WriteToLog("Waiting for socketExit()..."); } void network_post_exit(void) @@ -422,12 +424,12 @@ network_loop(void) if (rc < 0) { /* wifi got disabled */ - WriteToLog("poll: FAILED!\n"); + WriteToLog("poll: FAILED!"); if (errno == ENETDOWN) return LOOP_RESTART; - WriteToLog("poll: %d %s\n", errno, strerror(errno)); + WriteToLog("poll: %d %s", errno, strerror(errno)); return LOOP_EXIT; } else if (rc > 0) @@ -442,7 +444,7 @@ network_loop(void) } else { - WriteToLog("listenfd: revents=0x%08X\n", pollinfo.revents); + WriteToLog("listenfd: revents=0x%08X", pollinfo.revents); } } From 577e4442b1db2c72f78492f639c3871f43f4d6c9 Mon Sep 17 00:00:00 2001 From: kivr Date: Thu, 28 May 2020 18:45:33 -0500 Subject: [PATCH 08/13] Remove unused minIni. --- source/Sysmodule/source/minIni.c | 928 ------------------ source/Sysmodule/source/minIni.h | 184 ---- .../Sysmodule/source/minIni/minGlue-FatFs.h | 37 - source/Sysmodule/source/minIni/minGlue-ccs.h | 64 -- source/Sysmodule/source/minIni/minGlue-efsl.h | 63 -- source/Sysmodule/source/minIni/minGlue-ffs.h | 26 - source/Sysmodule/source/minIni/minGlue-mdd.h | 59 -- .../Sysmodule/source/minIni/minGlue-stdio.h | 31 - source/Sysmodule/source/minIni/minGlue.h | 31 - source/Sysmodule/source/minIni/wxMinIni.h | 126 --- 10 files changed, 1549 deletions(-) delete mode 100644 source/Sysmodule/source/minIni.c delete mode 100644 source/Sysmodule/source/minIni.h delete mode 100644 source/Sysmodule/source/minIni/minGlue-FatFs.h delete mode 100644 source/Sysmodule/source/minIni/minGlue-ccs.h delete mode 100644 source/Sysmodule/source/minIni/minGlue-efsl.h delete mode 100644 source/Sysmodule/source/minIni/minGlue-ffs.h delete mode 100644 source/Sysmodule/source/minIni/minGlue-mdd.h delete mode 100644 source/Sysmodule/source/minIni/minGlue-stdio.h delete mode 100644 source/Sysmodule/source/minIni/minGlue.h delete mode 100644 source/Sysmodule/source/minIni/wxMinIni.h diff --git a/source/Sysmodule/source/minIni.c b/source/Sysmodule/source/minIni.c deleted file mode 100644 index dd57e418..00000000 --- a/source/Sysmodule/source/minIni.c +++ /dev/null @@ -1,928 +0,0 @@ -/* minIni - Multi-Platform INI file parser, suitable for embedded systems - * - * These routines are in part based on the article "Multiplatform .INI Files" - * by Joseph J. Graf in the March 1994 issue of Dr. Dobb's Journal. - * - * Copyright (c) CompuPhase, 2008-2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Version: $Id: minIni.c 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ - */ - -#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY -# if !defined UNICODE /* for Windows */ -# define UNICODE -# endif -# if !defined _UNICODE /* for C library */ -# define _UNICODE -# endif -#endif - -#define MININI_IMPLEMENTATION -#include "minIni.h" -#if defined NDEBUG -# define assert(e) -#else -# include -#endif - -#if !defined __T || defined INI_ANSIONLY -# include -# include -# include -# define TCHAR char -# define __T(s) s -# define _tcscat strcat -# define _tcschr strchr -# define _tcscmp strcmp -# define _tcscpy strcpy -# define _tcsicmp stricmp -# define _tcslen strlen -# define _tcsncmp strncmp -# define _tcsncmp strncmp -# define _tcsrchr strrchr -# define _tcstol strtol -# define _tcstod strtod -# define _totupper toupper -# define _stprintf sprintf -# define _tfgets fgets -# define _tfputs fputs -# define _tfopen fopen -# define _tremove remove -# define _trename rename -#endif - -#if defined __linux || defined __linux__ -# define __LINUX__ -#elif defined FREEBSD && !defined __FreeBSD__ -# define __FreeBSD__ -#elif defined(_MSC_VER) -# pragma warning(disable : 4996) /* for Microsoft Visual C/C++ */ -#endif -#if !defined strncmp && !defined PORTABLE_strncmp -# if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ -# define strncmp strncasecmp -# endif -#endif -#if !defined _totupper -# define _totupper toupper -#endif - -#if !defined INI_LINETERM -# if defined __LINUX__ || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__ -# define INI_LINETERM __T("\n") -# else -# define INI_LINETERM __T("\r\n") -# endif -#endif -#if !defined INI_FILETYPE -# error Missing definition for INI_FILETYPE. -#endif - -#if !defined sizearray -# define sizearray(a) (sizeof(a) / sizeof((a)[0])) -#endif - -enum quote_option -{ - QUOTE_NONE, - QUOTE_ENQUOTE, - QUOTE_DEQUOTE, -}; - -#if defined PORTABLE_strncmp -int strncmp(const TCHAR* s1, const TCHAR* s2, size_t n) -{ - while (n-- != 0 && (*s1 || *s2)) - { - register int c1, c2; - c1 = *s1++; - if ('a' <= c1 && c1 <= 'z') - c1 += ('A' - 'a'); - c2 = *s2++; - if ('a' <= c2 && c2 <= 'z') - c2 += ('A' - 'a'); - if (c1 != c2) - return c1 - c2; - } /* while */ - return 0; -} -#endif /* PORTABLE_strncmp */ - -static TCHAR* skipleading(const TCHAR* str) -{ - assert(str != NULL); - while ('\0' < *str && *str <= ' ') - str++; - return (TCHAR*)str; -} - -static TCHAR* skiptrailing(const TCHAR* str, const TCHAR* base) -{ - assert(str != NULL); - assert(base != NULL); - while (str > base && '\0' < *(str - 1) && *(str - 1) <= ' ') - str--; - return (TCHAR*)str; -} - -static TCHAR* striptrailing(TCHAR* str) -{ - TCHAR* ptr = skiptrailing(_tcschr(str, '\0'), str); - assert(ptr != NULL); - *ptr = '\0'; - return str; -} - -static TCHAR* ini_strncpy(TCHAR* dest, const TCHAR* source, size_t maxlen, enum quote_option option) -{ - size_t d, s; - - assert(maxlen > 0); - assert(source != NULL && dest != NULL); - assert((dest < source || (dest == source && option != QUOTE_ENQUOTE)) || dest > source + strlen(source)); - if (option == QUOTE_ENQUOTE && maxlen < 3) - option = QUOTE_NONE; /* cannot store two quotes and a terminating zero in less than 3 characters */ - - switch (option) - { - case QUOTE_NONE: - for (d = 0; d < maxlen - 1 && source[d] != '\0'; d++) - dest[d] = source[d]; - assert(d < maxlen); - dest[d] = '\0'; - break; - case QUOTE_ENQUOTE: - d = 0; - dest[d++] = '"'; - for (s = 0; source[s] != '\0' && d < maxlen - 2; s++, d++) - { - if (source[s] == '"') - { - if (d >= maxlen - 3) - break; /* no space to store the escape character plus the one that follows it */ - dest[d++] = '\\'; - } /* if */ - dest[d] = source[s]; - } /* for */ - dest[d++] = '"'; - dest[d] = '\0'; - break; - case QUOTE_DEQUOTE: - for (d = s = 0; source[s] != '\0' && d < maxlen - 1; s++, d++) - { - if ((source[s] == '"' || source[s] == '\\') && source[s + 1] == '"') - s++; - dest[d] = source[s]; - } /* for */ - dest[d] = '\0'; - break; - default: - assert(0); - } /* switch */ - - return dest; -} - -static TCHAR* cleanstring(TCHAR* string, enum quote_option* quotes) -{ - int isstring; - TCHAR* ep; - - assert(string != NULL); - assert(quotes != NULL); - - /* Remove a trailing comment */ - isstring = 0; - for (ep = string; *ep != '\0' && ((*ep != ';' && *ep != '#') || isstring); ep++) - { - if (*ep == '"') - { - if (*(ep + 1) == '"') - ep++; /* skip "" (both quotes) */ - else - isstring = !isstring; /* single quote, toggle isstring */ - } - else if (*ep == '\\' && *(ep + 1) == '"') - { - ep++; /* skip \" (both quotes */ - } /* if */ - } /* for */ - assert(ep != NULL && (*ep == '\0' || *ep == ';' || *ep == '#')); - *ep = '\0'; /* terminate at a comment */ - striptrailing(string); - /* Remove double quotes surrounding a value */ - *quotes = QUOTE_NONE; - if (*string == '"' && (ep = _tcschr(string, '\0')) != NULL && *(ep - 1) == '"') - { - string++; - *--ep = '\0'; - *quotes = QUOTE_DEQUOTE; /* this is a string, so remove escaped characters */ - } /* if */ - return string; -} - -static int getkeystring(INI_FILETYPE* fp, const TCHAR* Section, const TCHAR* Key, - int idxSection, int idxKey, TCHAR* Buffer, int BufferSize, - INI_FILEPOS* mark) -{ - TCHAR *sp, *ep; - int len, idx; - enum quote_option quotes; - TCHAR LocalBuffer[INI_BUFFERSIZE]; - - assert(fp != NULL); - /* Move through file 1 line at a time until a section is matched or EOF. If - * parameter Section is NULL, only look at keys above the first section. If - * idxSection is positive, copy the relevant section name. - */ - len = (Section != NULL) ? (int)_tcslen(Section) : 0; - if (len > 0 || idxSection >= 0) - { - assert(idxSection >= 0 || Section != NULL); - idx = -1; - do - { - if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp)) - return 0; - sp = skipleading(LocalBuffer); - ep = _tcsrchr(sp, ']'); - } while (*sp != '[' || ep == NULL || - (((int)(ep - sp - 1) != len || Section == NULL || _tcsncmp(sp + 1, Section, len) != 0) && ++idx != idxSection)); - if (idxSection >= 0) - { - if (idx == idxSection) - { - assert(ep != NULL); - assert(*ep == ']'); - *ep = '\0'; - ini_strncpy(Buffer, sp + 1, BufferSize, QUOTE_NONE); - return 1; - } /* if */ - return 0; /* no more section found */ - } /* if */ - } /* if */ - - /* Now that the section has been found, find the entry. - * Stop searching upon leaving the section's area. - */ - assert(Key != NULL || idxKey >= 0); - len = (Key != NULL) ? (int)_tcslen(Key) : 0; - idx = -1; - do - { - if (mark != NULL) - ini_tell(fp, mark); /* optionally keep the mark to the start of the line */ - if (!ini_read(LocalBuffer, INI_BUFFERSIZE, fp) || *(sp = skipleading(LocalBuffer)) == '[') - return 0; - sp = skipleading(LocalBuffer); - ep = _tcschr(sp, '='); /* Parse out the equal sign */ - if (ep == NULL) - ep = _tcschr(sp, ':'); - } while (*sp == ';' || *sp == '#' || ep == NULL || ((len == 0 || (int)(skiptrailing(ep, sp) - sp) != len || _tcsncmp(sp, Key, len) != 0) && ++idx != idxKey)); - if (idxKey >= 0) - { - if (idx == idxKey) - { - assert(ep != NULL); - assert(*ep == '=' || *ep == ':'); - *ep = '\0'; - striptrailing(sp); - ini_strncpy(Buffer, sp, BufferSize, QUOTE_NONE); - return 1; - } /* if */ - return 0; /* no more key found (in this section) */ - } /* if */ - - /* Copy up to BufferSize chars to buffer */ - assert(ep != NULL); - assert(*ep == '=' || *ep == ':'); - sp = skipleading(ep + 1); - sp = cleanstring(sp, "es); /* Remove a trailing comment */ - ini_strncpy(Buffer, sp, BufferSize, quotes); - return 1; -} - -/** ini_gets() - * \param Section the name of the section to search for - * \param Key the name of the entry to find the value of - * \param DefValue default string in the event of a failed read - * \param Buffer a pointer to the buffer to copy into - * \param BufferSize the maximum number of characters to copy - * \param Filename the name and full path of the .ini file to read from - * - * \return the number of characters copied into the supplied buffer - */ -int ini_gets(const TCHAR* Section, const TCHAR* Key, const TCHAR* DefValue, - TCHAR* Buffer, int BufferSize, const TCHAR* Filename) -{ - INI_FILETYPE fp; - int ok = 0; - - if (Buffer == NULL || BufferSize <= 0 || Key == NULL) - return 0; - if (ini_openread(Filename, &fp)) - { - ok = getkeystring(&fp, Section, Key, -1, -1, Buffer, BufferSize, NULL); - (void)ini_close(&fp); - } /* if */ - if (!ok) - ini_strncpy(Buffer, (DefValue != NULL) ? DefValue : __T(""), BufferSize, QUOTE_NONE); - return (int)_tcslen(Buffer); -} - -/** ini_getl() - * \param Section the name of the section to search for - * \param Key the name of the entry to find the value of - * \param DefValue the default value in the event of a failed read - * \param Filename the name of the .ini file to read from - * - * \return the value located at Key - */ -long ini_getl(const TCHAR* Section, const TCHAR* Key, long DefValue, const TCHAR* Filename) -{ - TCHAR LocalBuffer[64]; - int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); - return (len == 0) ? DefValue - : ((len >= 2 && _totupper((int)LocalBuffer[1]) == 'X') ? _tcstol(LocalBuffer, NULL, 16) - : _tcstol(LocalBuffer, NULL, 10)); -} - -#if defined INI_REAL -/** ini_getf() - * \param Section the name of the section to search for - * \param Key the name of the entry to find the value of - * \param DefValue the default value in the event of a failed read - * \param Filename the name of the .ini file to read from - * - * \return the value located at Key - */ -INI_REAL ini_getf(const TCHAR* Section, const TCHAR* Key, INI_REAL DefValue, const TCHAR* Filename) -{ - TCHAR LocalBuffer[64]; - int len = ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); - return (len == 0) ? DefValue : ini_atof(LocalBuffer); -} -#endif - -/** ini_getbool() - * \param Section the name of the section to search for - * \param Key the name of the entry to find the value of - * \param DefValue default value in the event of a failed read; it should - * zero (0) or one (1). - * \param Filename the name and full path of the .ini file to read from - * - * A true boolean is found if one of the following is matched: - * - A string starting with 'y' or 'Y' - * - A string starting with 't' or 'T' - * - A string starting with '1' - * - * A false boolean is found if one of the following is matched: - * - A string starting with 'n' or 'N' - * - A string starting with 'f' or 'F' - * - A string starting with '0' - * - * \return the true/false flag as interpreted at Key - */ -int ini_getbool(const TCHAR* Section, const TCHAR* Key, int DefValue, const TCHAR* Filename) -{ - TCHAR LocalBuffer[2] = __T(""); - int ret; - - ini_gets(Section, Key, __T(""), LocalBuffer, sizearray(LocalBuffer), Filename); - LocalBuffer[0] = (TCHAR)_totupper((int)LocalBuffer[0]); - if (LocalBuffer[0] == 'Y' || LocalBuffer[0] == '1' || LocalBuffer[0] == 'T') - ret = 1; - else if (LocalBuffer[0] == 'N' || LocalBuffer[0] == '0' || LocalBuffer[0] == 'F') - ret = 0; - else - ret = DefValue; - - return (ret); -} - -/** ini_getsection() - * \param idx the zero-based sequence number of the section to return - * \param Buffer a pointer to the buffer to copy into - * \param BufferSize the maximum number of characters to copy - * \param Filename the name and full path of the .ini file to read from - * - * \return the number of characters copied into the supplied buffer - */ -int ini_getsection(int idx, TCHAR* Buffer, int BufferSize, const TCHAR* Filename) -{ - INI_FILETYPE fp; - int ok = 0; - - if (Buffer == NULL || BufferSize <= 0 || idx < 0) - return 0; - if (ini_openread(Filename, &fp)) - { - ok = getkeystring(&fp, NULL, NULL, idx, -1, Buffer, BufferSize, NULL); - (void)ini_close(&fp); - } /* if */ - if (!ok) - *Buffer = '\0'; - return (int)_tcslen(Buffer); -} - -/** ini_getkey() - * \param Section the name of the section to browse through, or NULL to - * browse through the keys outside any section - * \param idx the zero-based sequence number of the key to return - * \param Buffer a pointer to the buffer to copy into - * \param BufferSize the maximum number of characters to copy - * \param Filename the name and full path of the .ini file to read from - * - * \return the number of characters copied into the supplied buffer - */ -int ini_getkey(const TCHAR* Section, int idx, TCHAR* Buffer, int BufferSize, const TCHAR* Filename) -{ - INI_FILETYPE fp; - int ok = 0; - - if (Buffer == NULL || BufferSize <= 0 || idx < 0) - return 0; - if (ini_openread(Filename, &fp)) - { - ok = getkeystring(&fp, Section, NULL, -1, idx, Buffer, BufferSize, NULL); - (void)ini_close(&fp); - } /* if */ - if (!ok) - *Buffer = '\0'; - return (int)_tcslen(Buffer); -} - -#if !defined INI_NOBROWSE -/** ini_browse() - * \param Callback a pointer to a function that will be called for every - * setting in the INI file. - * \param UserData arbitrary data, which the function passes on the - * \c Callback function - * \param Filename the name and full path of the .ini file to read from - * - * \return 1 on success, 0 on failure (INI file not found) - * - * \note The \c Callback function must return 1 to continue - * browsing through the INI file, or 0 to stop. Even when the - * callback stops the browsing, this function will return 1 - * (for success). - */ -int ini_browse(INI_CALLBACK Callback, void* UserData, const TCHAR* Filename) -{ - TCHAR LocalBuffer[INI_BUFFERSIZE]; - int lenSec, lenKey; - enum quote_option quotes; - INI_FILETYPE fp; - - if (Callback == NULL) - return 0; - if (!ini_openread(Filename, &fp)) - return 0; - - LocalBuffer[0] = '\0'; /* copy an empty section in the buffer */ - lenSec = (int)_tcslen(LocalBuffer) + 1; - for (;;) - { - TCHAR *sp, *ep; - if (!ini_read(LocalBuffer + lenSec, INI_BUFFERSIZE - lenSec, &fp)) - break; - sp = skipleading(LocalBuffer + lenSec); - /* ignore empty strings and comments */ - if (*sp == '\0' || *sp == ';' || *sp == '#') - continue; - /* see whether we reached a new section */ - ep = _tcsrchr(sp, ']'); - if (*sp == '[' && ep != NULL) - { - *ep = '\0'; - ini_strncpy(LocalBuffer, sp + 1, INI_BUFFERSIZE, QUOTE_NONE); - lenSec = (int)_tcslen(LocalBuffer) + 1; - continue; - } /* if */ - /* not a new section, test for a key/value pair */ - ep = _tcschr(sp, '='); /* test for the equal sign or colon */ - if (ep == NULL) - ep = _tcschr(sp, ':'); - if (ep == NULL) - continue; /* invalid line, ignore */ - *ep++ = '\0'; /* split the key from the value */ - striptrailing(sp); - ini_strncpy(LocalBuffer + lenSec, sp, INI_BUFFERSIZE - lenSec, QUOTE_NONE); - lenKey = (int)_tcslen(LocalBuffer + lenSec) + 1; - /* clean up the value */ - sp = skipleading(ep); - sp = cleanstring(sp, "es); /* Remove a trailing comment */ - ini_strncpy(LocalBuffer + lenSec + lenKey, sp, INI_BUFFERSIZE - lenSec - lenKey, quotes); - /* call the callback */ - if (!Callback(LocalBuffer, LocalBuffer + lenSec, LocalBuffer + lenSec + lenKey, UserData)) - break; - } /* for */ - - (void)ini_close(&fp); - return 1; -} -#endif /* INI_NOBROWSE */ - -#if !defined INI_READONLY -static void ini_tempname(TCHAR* dest, const TCHAR* source, int maxlength) -{ - TCHAR* p; - - ini_strncpy(dest, source, maxlength, QUOTE_NONE); - p = _tcsrchr(dest, '\0'); - assert(p != NULL); - *(p - 1) = '~'; -} - -static enum quote_option check_enquote(const TCHAR* Value) -{ - const TCHAR* p; - - /* run through the value, if it has trailing spaces, or '"', ';' or '#' - * characters, enquote it - */ - assert(Value != NULL); - for (p = Value; *p != '\0' && *p != '"' && *p != ';' && *p != '#'; p++) - /* nothing */; - return (*p != '\0' || (p > Value && *(p - 1) == ' ')) ? QUOTE_ENQUOTE : QUOTE_NONE; -} - -static void writesection(TCHAR* LocalBuffer, const TCHAR* Section, INI_FILETYPE* fp) -{ - if (Section != NULL && _tcslen(Section) > 0) - { - TCHAR* p; - LocalBuffer[0] = '['; - ini_strncpy(LocalBuffer + 1, Section, INI_BUFFERSIZE - 4, QUOTE_NONE); /* -1 for '[', -1 for ']', -2 for '\r\n' */ - p = _tcsrchr(LocalBuffer, '\0'); - assert(p != NULL); - *p++ = ']'; - _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ - if (fp != NULL) - (void)ini_write(LocalBuffer, fp); - } /* if */ -} - -static void writekey(TCHAR* LocalBuffer, const TCHAR* Key, const TCHAR* Value, INI_FILETYPE* fp) -{ - TCHAR* p; - enum quote_option option = check_enquote(Value); - ini_strncpy(LocalBuffer, Key, INI_BUFFERSIZE - 3, QUOTE_NONE); /* -1 for '=', -2 for '\r\n' */ - p = _tcsrchr(LocalBuffer, '\0'); - assert(p != NULL); - *p++ = '='; - ini_strncpy(p, Value, INI_BUFFERSIZE - (p - LocalBuffer) - 2, option); /* -2 for '\r\n' */ - p = _tcsrchr(LocalBuffer, '\0'); - assert(p != NULL); - _tcscpy(p, INI_LINETERM); /* copy line terminator (typically "\n") */ - if (fp != NULL) - (void)ini_write(LocalBuffer, fp); -} - -static int cache_accum(const TCHAR* string, int* size, int max) -{ - int len = (int)_tcslen(string); - if (*size + len >= max) - return 0; - *size += len; - return 1; -} - -static int cache_flush(TCHAR* buffer, int* size, - INI_FILETYPE* rfp, INI_FILETYPE* wfp, INI_FILEPOS* mark) -{ - int terminator_len = (int)_tcslen(INI_LINETERM); - int pos = 0; - - (void)ini_seek(rfp, mark); - assert(buffer != NULL); - buffer[0] = '\0'; - assert(size != NULL); - assert(*size <= INI_BUFFERSIZE); - while (pos < *size) - { - (void)ini_read(buffer + pos, INI_BUFFERSIZE - pos, rfp); - while (pos < *size && buffer[pos] != '\0') - pos++; /* cannot use _tcslen() because buffer may not be zero-terminated */ - } /* while */ - if (buffer[0] != '\0') - { - assert(pos > 0 && pos <= INI_BUFFERSIZE); - if (pos == INI_BUFFERSIZE) - pos--; - buffer[pos] = '\0'; /* force zero-termination (may be left unterminated in the above while loop) */ - (void)ini_write(buffer, wfp); - } - ini_tell(rfp, mark); /* update mark */ - *size = 0; - /* return whether the buffer ended with a line termination */ - return (pos > terminator_len) && (_tcscmp(buffer + pos - terminator_len, INI_LINETERM) == 0); -} - -static int close_rename(INI_FILETYPE* rfp, INI_FILETYPE* wfp, const TCHAR* filename, TCHAR* buffer) -{ - (void)ini_close(rfp); - (void)ini_close(wfp); - (void)ini_remove(filename); - (void)ini_tempname(buffer, filename, INI_BUFFERSIZE); - (void)ini_rename(buffer, filename); - return 1; -} - -/** ini_puts() - * \param Section the name of the section to write the string in - * \param Key the name of the entry to write, or NULL to erase all keys in the section - * \param Value a pointer to the buffer the string, or NULL to erase the key - * \param Filename the name and full path of the .ini file to write to - * - * \return 1 if successful, otherwise 0 - */ -int ini_puts(const TCHAR* Section, const TCHAR* Key, const TCHAR* Value, const TCHAR* Filename) -{ - INI_FILETYPE rfp; - INI_FILETYPE wfp; - INI_FILEPOS mark; - INI_FILEPOS head, tail; - TCHAR *sp, *ep; - TCHAR LocalBuffer[INI_BUFFERSIZE]; - int len, match, flag, cachelen; - - assert(Filename != NULL); - if (!ini_openread(Filename, &rfp)) - { - /* If the .ini file doesn't exist, make a new file */ - if (Key != NULL && Value != NULL) - { - if (!ini_openwrite(Filename, &wfp)) - return 0; - writesection(LocalBuffer, Section, &wfp); - writekey(LocalBuffer, Key, Value, &wfp); - (void)ini_close(&wfp); - } /* if */ - return 1; - } /* if */ - - /* If parameters Key and Value are valid (so this is not an "erase" request) - * and the setting already exists, there are two short-cuts to avoid rewriting - * the INI file. - */ - if (Key != NULL && Value != NULL) - { - ini_tell(&rfp, &mark); - match = getkeystring(&rfp, Section, Key, -1, -1, LocalBuffer, sizearray(LocalBuffer), &head); - if (match) - { - /* if the current setting is identical to the one to write, there is - * nothing to do. - */ - if (_tcscmp(LocalBuffer, Value) == 0) - { - (void)ini_close(&rfp); - return 1; - } /* if */ -/* if the new setting has the same length as the current setting, and the - * glue file permits file read/write access, we can modify in place. - */ -# if defined ini_openrewrite - /* we already have the start of the (raw) line, get the end too */ - ini_tell(&rfp, &tail); - /* create new buffer (without writing it to file) */ - writekey(LocalBuffer, Key, Value, NULL); - if (_tcslen(LocalBuffer) == (size_t)(tail - head)) - { - /* length matches, close the file & re-open for read/write, then - * write at the correct position - */ - (void)ini_close(&rfp); - if (!ini_openrewrite(Filename, &wfp)) - return 0; - (void)ini_seek(&wfp, &head); - (void)ini_write(LocalBuffer, &wfp); - (void)ini_close(&wfp); - return 1; - } /* if */ -# endif - } /* if */ - /* key not found, or different value & length -> proceed (but rewind the - * input file first) - */ - (void)ini_seek(&rfp, &mark); - } /* if */ - - /* Get a temporary file name to copy to. Use the existing name, but with - * the last character set to a '~'. - */ - ini_tempname(LocalBuffer, Filename, INI_BUFFERSIZE); - if (!ini_openwrite(LocalBuffer, &wfp)) - { - (void)ini_close(&rfp); - return 0; - } /* if */ - (void)ini_tell(&rfp, &mark); - cachelen = 0; - - /* Move through the file one line at a time until a section is - * matched or until EOF. Copy to temp file as it is read. - */ - len = (Section != NULL) ? (int)_tcslen(Section) : 0; - if (len > 0) - { - do - { - if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) - { - /* Failed to find section, so add one to the end */ - flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - if (Key != NULL && Value != NULL) - { - if (!flag) - (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ - writesection(LocalBuffer, Section, &wfp); - writekey(LocalBuffer, Key, Value, &wfp); - } /* if */ - return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ - } /* if */ - /* Copy the line from source to dest, but not if this is the section that - * we are looking for and this section must be removed - */ - sp = skipleading(LocalBuffer); - ep = _tcsrchr(sp, ']'); - match = (*sp == '[' && ep != NULL && (int)(ep - sp - 1) == len && _tcsncmp(sp + 1, Section, len) == 0); - if (!match || Key != NULL) - { - if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) - { - cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); - cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); - } /* if */ - } /* if */ - } while (!match); - } /* if */ - cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - /* when deleting a section, the section head that was just found has not been - * copied to the output file, but because this line was not "accumulated" in - * the cache, the position in the input file was reset to the point just - * before the section; this must now be skipped (again) - */ - if (Key == NULL) - { - (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); - (void)ini_tell(&rfp, &mark); - } /* if */ - - /* Now that the section has been found, find the entry. Stop searching - * upon leaving the section's area. Copy the file as it is read - * and create an entry if one is not found. - */ - len = (Key != NULL) ? (int)_tcslen(Key) : 0; - for (;;) - { - if (!ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) - { - /* EOF without an entry so make one */ - flag = cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - if (Key != NULL && Value != NULL) - { - if (!flag) - (void)ini_write(INI_LINETERM, &wfp); /* force a new line behind the last line of the INI file */ - writekey(LocalBuffer, Key, Value, &wfp); - } /* if */ - return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ - } /* if */ - sp = skipleading(LocalBuffer); - ep = _tcschr(sp, '='); /* Parse out the equal sign */ - if (ep == NULL) - ep = _tcschr(sp, ':'); - match = (ep != NULL && len > 0 && (int)(skiptrailing(ep, sp) - sp) == len && _tcsncmp(sp, Key, len) == 0); - if ((Key != NULL && match) || *sp == '[') - break; /* found the key, or found a new section */ - /* copy other keys in the section */ - if (Key == NULL) - { - (void)ini_tell(&rfp, &mark); /* we are deleting the entire section, so update the read position */ - } - else - { - if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) - { - cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); - cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); - } /* if */ - } /* if */ - } /* for */ - /* the key was found, or we just dropped on the next section (meaning that it - * wasn't found); in both cases we need to write the key, but in the latter - * case, we also need to write the line starting the new section after writing - * the key - */ - flag = (*sp == '['); - cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - if (Key != NULL && Value != NULL) - writekey(LocalBuffer, Key, Value, &wfp); - /* cache_flush() reset the "read pointer" to the start of the line with the - * previous key or the new section; read it again (because writekey() destroyed - * the buffer) - */ - (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); - if (flag) - { - /* the new section heading needs to be copied to the output file */ - cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); - } - else - { - /* forget the old key line */ - (void)ini_tell(&rfp, &mark); - } /* if */ - /* Copy the rest of the INI file */ - while (ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp)) - { - if (!cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE)) - { - cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - (void)ini_read(LocalBuffer, INI_BUFFERSIZE, &rfp); - cache_accum(LocalBuffer, &cachelen, INI_BUFFERSIZE); - } /* if */ - } /* while */ - cache_flush(LocalBuffer, &cachelen, &rfp, &wfp, &mark); - return close_rename(&rfp, &wfp, Filename, LocalBuffer); /* clean up and rename */ -} - -/* Ansi C "itoa" based on Kernighan & Ritchie's "Ansi C" book. */ -# define ABS(v) ((v) < 0 ? -(v) : (v)) - -static void strreverse(TCHAR* str) -{ - int i, j; - for (i = 0, j = (int)_tcslen(str) - 1; i < j; i++, j--) - { - TCHAR t = str[i]; - str[i] = str[j]; - str[j] = t; - } /* for */ -} - -static void long2str(long value, TCHAR* str) -{ - int i = 0; - long sign = value; - - /* generate digits in reverse order */ - do - { - int n = (int)(value % 10); /* get next lowest digit */ - str[i++] = (TCHAR)(ABS(n) + '0'); /* handle case of negative digit */ - } while (value /= 10); /* delete the lowest digit */ - if (sign < 0) - str[i++] = '-'; - str[i] = '\0'; - - strreverse(str); -} - -/** ini_putl() - * \param Section the name of the section to write the value in - * \param Key the name of the entry to write - * \param Value the value to write - * \param Filename the name and full path of the .ini file to write to - * - * \return 1 if successful, otherwise 0 - */ -int ini_putl(const TCHAR* Section, const TCHAR* Key, long Value, const TCHAR* Filename) -{ - TCHAR LocalBuffer[32]; - long2str(Value, LocalBuffer); - return ini_puts(Section, Key, LocalBuffer, Filename); -} - -# if defined INI_REAL -/** ini_putf() - * \param Section the name of the section to write the value in - * \param Key the name of the entry to write - * \param Value the value to write - * \param Filename the name and full path of the .ini file to write to - * - * \return 1 if successful, otherwise 0 - */ -int ini_putf(const TCHAR* Section, const TCHAR* Key, INI_REAL Value, const TCHAR* Filename) -{ - TCHAR LocalBuffer[64]; - ini_ftoa(LocalBuffer, Value); - return ini_puts(Section, Key, LocalBuffer, Filename); -} -# endif /* INI_REAL */ -#endif /* !INI_READONLY */ diff --git a/source/Sysmodule/source/minIni.h b/source/Sysmodule/source/minIni.h deleted file mode 100644 index 9861f41f..00000000 --- a/source/Sysmodule/source/minIni.h +++ /dev/null @@ -1,184 +0,0 @@ -/* minIni - Multi-Platform INI file parser, suitable for embedded systems - * - * Copyright (c) CompuPhase, 2008-2017 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Version: $Id: minIni.h 53 2015-01-18 13:35:11Z thiadmer.riemersma@gmail.com $ - */ -#ifndef MININI_H -#define MININI_H - -#include "minIni/minGlue.h" - -#if (defined _UNICODE || defined __UNICODE__ || defined UNICODE) && !defined INI_ANSIONLY -# include -# define mTCHAR TCHAR -#else -/* force TCHAR to be "char", but only for minIni */ -# define mTCHAR char -#endif - -#if !defined INI_BUFFERSIZE -# define INI_BUFFERSIZE 512 -#endif - -#if defined __cplusplus -extern "C" -{ -#endif - - int ini_getbool(const mTCHAR* Section, const mTCHAR* Key, int DefValue, const mTCHAR* Filename); - long ini_getl(const mTCHAR* Section, const mTCHAR* Key, long DefValue, const mTCHAR* Filename); - int ini_gets(const mTCHAR* Section, const mTCHAR* Key, const mTCHAR* DefValue, mTCHAR* Buffer, int BufferSize, const mTCHAR* Filename); - int ini_getsection(int idx, mTCHAR* Buffer, int BufferSize, const mTCHAR* Filename); - int ini_getkey(const mTCHAR* Section, int idx, mTCHAR* Buffer, int BufferSize, const mTCHAR* Filename); - -#if defined INI_REAL - INI_REAL ini_getf(const mTCHAR* Section, const mTCHAR* Key, INI_REAL DefValue, const mTCHAR* Filename); -#endif - -#if !defined INI_READONLY - int ini_putl(const mTCHAR* Section, const mTCHAR* Key, long Value, const mTCHAR* Filename); - int ini_puts(const mTCHAR* Section, const mTCHAR* Key, const mTCHAR* Value, const mTCHAR* Filename); -# if defined INI_REAL - int ini_putf(const mTCHAR* Section, const mTCHAR* Key, INI_REAL Value, const mTCHAR* Filename); -# endif -#endif /* INI_READONLY */ - -#if !defined INI_NOBROWSE - typedef int (*INI_CALLBACK)(const mTCHAR* Section, const mTCHAR* Key, const mTCHAR* Value, void* UserData); - int ini_browse(INI_CALLBACK Callback, void* UserData, const mTCHAR* Filename); -#endif /* INI_NOBROWSE */ - -#if defined __cplusplus -} -#endif - -#if defined __cplusplus - -# if defined __WXWINDOWS__ -# include "wxMinIni.h" -# else -# include - -/* The C++ class in minIni.h was contributed by Steven Van Ingelgem. */ -class minIni -{ -public: - minIni(const std::string& filename) : iniFilename(filename) - { - } - - bool getbool(const std::string& Section, const std::string& Key, bool DefValue = false) const - { - return ini_getbool(Section.c_str(), Key.c_str(), int(DefValue), iniFilename.c_str()) != 0; - } - - long getl(const std::string& Section, const std::string& Key, long DefValue = 0) const - { - return ini_getl(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); - } - - int geti(const std::string& Section, const std::string& Key, int DefValue = 0) const - { - return static_cast(this->getl(Section, Key, long(DefValue))); - } - - std::string gets(const std::string& Section, const std::string& Key, const std::string& DefValue = "") const - { - char buffer[INI_BUFFERSIZE]; - ini_gets(Section.c_str(), Key.c_str(), DefValue.c_str(), buffer, INI_BUFFERSIZE, iniFilename.c_str()); - return buffer; - } - - std::string getsection(int idx) const - { - char buffer[INI_BUFFERSIZE]; - ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); - return buffer; - } - - std::string getkey(const std::string& Section, int idx) const - { - char buffer[INI_BUFFERSIZE]; - ini_getkey(Section.c_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.c_str()); - return buffer; - } - -# if defined INI_REAL - INI_REAL getf(const std::string& Section, const std::string& Key, INI_REAL DefValue = 0) const - { - return ini_getf(Section.c_str(), Key.c_str(), DefValue, iniFilename.c_str()); - } -# endif - -# if !defined INI_READONLY - bool put(const std::string& Section, const std::string& Key, long Value) - { - return ini_putl(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; - } - - bool put(const std::string& Section, const std::string& Key, int Value) - { - return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; - } - - bool put(const std::string& Section, const std::string& Key, bool Value) - { - return ini_putl(Section.c_str(), Key.c_str(), (long)Value, iniFilename.c_str()) != 0; - } - - bool put(const std::string& Section, const std::string& Key, const std::string& Value) - { - return ini_puts(Section.c_str(), Key.c_str(), Value.c_str(), iniFilename.c_str()) != 0; - } - - bool put(const std::string& Section, const std::string& Key, const char* Value) - { - return ini_puts(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; - } - -# if defined INI_REAL - bool put(const std::string& Section, const std::string& Key, INI_REAL Value) - { - return ini_putf(Section.c_str(), Key.c_str(), Value, iniFilename.c_str()) != 0; - } -# endif - - bool del(const std::string& Section, const std::string& Key) - { - return ini_puts(Section.c_str(), Key.c_str(), 0, iniFilename.c_str()) != 0; - } - - bool del(const std::string& Section) - { - return ini_puts(Section.c_str(), 0, 0, iniFilename.c_str()) != 0; - } -# endif - -# if !defined INI_NOBROWSE - bool browse(INI_CALLBACK Callback, void* UserData) const - { - return ini_browse(Callback, UserData, iniFilename.c_str()) != 0; - } -# endif - -private: - std::string iniFilename; -}; - -# endif /* __WXWINDOWS__ */ -#endif /* __cplusplus */ - -#endif /* MININI_H */ diff --git a/source/Sysmodule/source/minIni/minGlue-FatFs.h b/source/Sysmodule/source/minIni/minGlue-FatFs.h deleted file mode 100644 index 3873cd83..00000000 --- a/source/Sysmodule/source/minIni/minGlue-FatFs.h +++ /dev/null @@ -1,37 +0,0 @@ -/* Glue functions for the minIni library, based on the FatFs and Petit-FatFs - * libraries, see http://elm-chan.org/fsw/ff/00index_e.html - * - * By CompuPhase, 2008-2012 - * This "glue file" is in the public domain. It is distributed without - * warranties or conditions of any kind, either express or implied. - * - * (The FatFs and Petit-FatFs libraries are copyright by ChaN and licensed at - * its own terms.) - */ - -#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ - -/* You must set _USE_STRFUNC to 1 or 2 in the include file ff.h (or tff.h) - * to enable the "string functions" fgets() and fputs(). - */ -#include "ff.h" /* include tff.h for Tiny-FatFs */ - -#define INI_FILETYPE FIL -#define ini_openread(filename, file) (f_open((file), (filename), FA_READ + FA_OPEN_EXISTING) == FR_OK) -#define ini_openwrite(filename, file) (f_open((file), (filename), FA_WRITE + FA_CREATE_ALWAYS) == FR_OK) -#define ini_close(file) (f_close(file) == FR_OK) -#define ini_read(buffer, size, file) f_gets((buffer), (size), (file)) -#define ini_write(buffer, file) f_puts((buffer), (file)) -#define ini_remove(filename) (f_unlink(filename) == FR_OK) - -#define INI_FILEPOS DWORD -#define ini_tell(file, pos) (*(pos) = f_tell((file))) -#define ini_seek(file, pos) (f_lseek((file), *(pos)) == FR_OK) - -static int ini_rename(TCHAR* source, const TCHAR* dest) -{ - /* Function f_rename() does not allow drive letters in the destination file */ - char* drive = strchr(dest, ':'); - drive = (drive == NULL) ? dest : drive + 1; - return (f_rename(source, drive) == FR_OK); -} diff --git a/source/Sysmodule/source/minIni/minGlue-ccs.h b/source/Sysmodule/source/minIni/minGlue-ccs.h deleted file mode 100644 index 7a51e54b..00000000 --- a/source/Sysmodule/source/minIni/minGlue-ccs.h +++ /dev/null @@ -1,64 +0,0 @@ -/* minIni glue functions for FAT library by CCS, Inc. (as provided with their - * PIC MCU compiler) - * - * By CompuPhase, 2011-2012 - * This "glue file" is in the public domain. It is distributed without - * warranties or conditions of any kind, either express or implied. - * - * (The FAT library is copyright (c) 2007 Custom Computer Services, and - * licensed at its own terms.) - */ - -#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ - -#ifndef FAT_PIC_C -# error FAT library must be included before this module -#endif -#define const /* keyword not supported by CCS */ - -#define INI_FILETYPE FILE -#define ini_openread(filename, file) (fatopen((filename), "r", (file)) == GOODEC) -#define ini_openwrite(filename, file) (fatopen((filename), "w", (file)) == GOODEC) -#define ini_close(file) (fatclose((file)) == 0) -#define ini_read(buffer, size, file) (fatgets((buffer), (size), (file)) != NULL) -#define ini_write(buffer, file) (fatputs((buffer), (file)) == GOODEC) -#define ini_remove(filename) (rm_file((filename)) == 0) - -#define INI_FILEPOS fatpos_t -#define ini_tell(file, pos) (fatgetpos((file), (pos)) == 0) -#define ini_seek(file, pos) (fatsetpos((file), (pos)) == 0) - -#ifndef INI_READONLY -/* CCS FAT library lacks a rename function, so instead we copy the file to the - * new name and delete the old file - */ -static int ini_rename(char* source, char* dest) -{ - FILE fr, fw; - int n; - - if (fatopen(source, "r", &fr) != GOODEC) - return 0; - if (rm_file(dest) != 0) - return 0; - if (fatopen(dest, "w", &fw) != GOODEC) - return 0; - - /* With some "insider knowledge", we can save some memory: the "source" - * parameter holds a filename that was built from the "dest" parameter. It - * was built in a local buffer with the size INI_BUFFERSIZE. We can reuse - * this buffer for copying the file. - */ - while (n = fatread(source, 1, INI_BUFFERSIZE, &fr)) - fatwrite(source, 1, n, &fw); - - fatclose(&fr); - fatclose(&fw); - - /* Now we need to delete the source file. However, we have garbled the buffer - * that held the filename of the source. So we need to build it again. - */ - ini_tempname(source, dest, INI_BUFFERSIZE); - return rm_file(source) == 0; -} -#endif diff --git a/source/Sysmodule/source/minIni/minGlue-efsl.h b/source/Sysmodule/source/minIni/minGlue-efsl.h deleted file mode 100644 index 272692c3..00000000 --- a/source/Sysmodule/source/minIni/minGlue-efsl.h +++ /dev/null @@ -1,63 +0,0 @@ -/* Glue functions for the minIni library, based on the EFS Library, see - * http://www.efsl.be/ - * - * By CompuPhase, 2008-2012 - * This "glue file" is in the public domain. It is distributed without - * warranties or conditions of any kind, either express or implied. - * - * (EFSL is copyright 2005-2006 Lennart Ysboodt and Michael De Nil, and - * licensed under the GPL with an exception clause for static linking.) - */ - -#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ -#define INI_LINETERM "\r\n" /* set line termination explicitly */ - -#include "efs.h" -extern EmbeddedFileSystem g_efs; - -#define INI_FILETYPE EmbeddedFile -#define ini_openread(filename, file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'r') == 0) -#define ini_openwrite(filename, file) (file_fopen((file), &g_efs.myFs, (char*)(filename), 'w') == 0) -#define ini_close(file) file_fclose(file) -#define ini_read(buffer, size, file) (file_read((file), (size), (buffer)) > 0) -#define ini_write(buffer, file) (file_write((file), strlen(buffer), (char*)(buffer)) > 0) -#define ini_remove(filename) rmfile(&g_efs.myFs, (char*)(filename)) - -#define INI_FILEPOS euint32 -#define ini_tell(file, pos) (*(pos) = (file)->FilePtr)) -#define ini_seek(file, pos) file_setpos((file), (*pos)) - -#if !defined INI_READONLY -/* EFSL lacks a rename function, so instead we copy the file to the new name - * and delete the old file - */ -static int ini_rename(char* source, const char* dest) -{ - EmbeddedFile fr, fw; - int n; - - if (file_fopen(&fr, &g_efs.myFs, source, 'r') != 0) - return 0; - if (rmfile(&g_efs.myFs, (char*)dest) != 0) - return 0; - if (file_fopen(&fw, &g_efs.myFs, (char*)dest, 'w') != 0) - return 0; - - /* With some "insider knowledge", we can save some memory: the "source" - * parameter holds a filename that was built from the "dest" parameter. It - * was built in buffer and this buffer has the size INI_BUFFERSIZE. We can - * reuse this buffer for copying the file. - */ - while (n = file_read(&fr, INI_BUFFERSIZE, source)) - file_write(&fw, n, source); - - file_fclose(&fr); - file_fclose(&fw); - - /* Now we need to delete the source file. However, we have garbled the buffer - * that held the filename of the source. So we need to build it again. - */ - ini_tempname(source, dest, INI_BUFFERSIZE); - return rmfile(&g_efs.myFs, source) == 0; -} -#endif diff --git a/source/Sysmodule/source/minIni/minGlue-ffs.h b/source/Sysmodule/source/minIni/minGlue-ffs.h deleted file mode 100644 index e0a818a2..00000000 --- a/source/Sysmodule/source/minIni/minGlue-ffs.h +++ /dev/null @@ -1,26 +0,0 @@ -/* Glue functions for the minIni library, based on the "FAT Filing System" - * library by embedded-code.com - * - * By CompuPhase, 2008-2012 - * This "glue file" is in the public domain. It is distributed without - * warranties or conditions of any kind, either express or implied. - * - * (The "FAT Filing System" library itself is copyright embedded-code.com, and - * licensed at its own terms.) - */ - -#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ -#include - -#define INI_FILETYPE FFS_FILE* -#define ini_openread(filename, file) ((*(file) = ffs_fopen((filename), "r")) != NULL) -#define ini_openwrite(filename, file) ((*(file) = ffs_fopen((filename), "w")) != NULL) -#define ini_close(file) (ffs_fclose(*(file)) == 0) -#define ini_read(buffer, size, file) (ffs_fgets((buffer), (size), *(file)) != NULL) -#define ini_write(buffer, file) (ffs_fputs((buffer), *(file)) >= 0) -#define ini_rename(source, dest) (ffs_rename((source), (dest)) == 0) -#define ini_remove(filename) (ffs_remove(filename) == 0) - -#define INI_FILEPOS long -#define ini_tell(file, pos) (ffs_fgetpos(*(file), (pos)) == 0) -#define ini_seek(file, pos) (ffs_fsetpos(*(file), (pos)) == 0) diff --git a/source/Sysmodule/source/minIni/minGlue-mdd.h b/source/Sysmodule/source/minIni/minGlue-mdd.h deleted file mode 100644 index 96ec2250..00000000 --- a/source/Sysmodule/source/minIni/minGlue-mdd.h +++ /dev/null @@ -1,59 +0,0 @@ -/* minIni glue functions for Microchip's "Memory Disk Drive" file system - * library, as presented in Microchip application note AN1045. - * - * By CompuPhase, 2011-2014 - * This "glue file" is in the public domain. It is distributed without - * warranties or conditions of any kind, either express or implied. - * - * (The "Microchip Memory Disk Drive File System" is copyright (c) Microchip - * Technology Incorporated, and licensed at its own terms.) - */ - -#define INI_BUFFERSIZE 256 /* maximum line length, maximum path length */ - -#include "MDD File System\fsio.h" -#include - -#define INI_FILETYPE FSFILE* -#define ini_openread(filename, file) ((*(file) = FSfopen((filename), FS_READ)) != NULL) -#define ini_openwrite(filename, file) ((*(file) = FSfopen((filename), FS_WRITE)) != NULL) -#define ini_openrewrite(filename, file) ((*(file) = fopen((filename), FS_READPLUS)) != NULL) -#define ini_close(file) (FSfclose(*(file)) == 0) -#define ini_write(buffer, file) (FSfwrite((buffer), 1, strlen(buffer), (*file)) > 0) -#define ini_remove(filename) (FSremove((filename)) == 0) - -#define INI_FILEPOS long int -#define ini_tell(file, pos) (*(pos) = FSftell(*(file))) -#define ini_seek(file, pos) (FSfseek(*(file), *(pos), SEEK_SET) == 0) - -/* Since the Memory Disk Drive file system library reads only blocks of files, - * the function to read a text line does so by "over-reading" a block of the - * of the maximum size and truncating it behind the end-of-line. - */ -static int ini_read(char* buffer, int size, INI_FILETYPE* file) -{ - size_t numread = size; - char* eol; - - if ((numread = FSfread(buffer, 1, size, *file)) == 0) - return 0; /* at EOF */ - if ((eol = strchr(buffer, '\n')) == NULL) - eol = strchr(buffer, '\r'); - if (eol != NULL) - { - /* terminate the buffer */ - *++eol = '\0'; - /* "unread" the data that was read too much */ - FSfseek(*file, -(int)(numread - (size_t)(eol - buffer)), SEEK_CUR); - } /* if */ - return 1; -} - -#ifndef INI_READONLY -static int ini_rename(const char* source, const char* dest) -{ - FSFILE* ftmp = FSfopen((source), FS_READ); - FSrename((dest), ftmp); - return FSfclose(ftmp) == 0; -} -#endif diff --git a/source/Sysmodule/source/minIni/minGlue-stdio.h b/source/Sysmodule/source/minIni/minGlue-stdio.h deleted file mode 100644 index 31888637..00000000 --- a/source/Sysmodule/source/minIni/minGlue-stdio.h +++ /dev/null @@ -1,31 +0,0 @@ -/* Glue functions for the minIni library, based on the C/C++ stdio library - * - * Or better said: this file contains macros that maps the function interface - * used by minIni to the standard C/C++ file I/O functions. - * - * By CompuPhase, 2008-2014 - * This "glue file" is in the public domain. It is distributed without - * warranties or conditions of any kind, either express or implied. - */ - -/* map required file I/O types and functions to the standard C library */ -#include - -#define INI_FILETYPE FILE* -#define ini_openread(filename, file) ((*(file) = fopen((filename), "rb")) != NULL) -#define ini_openwrite(filename, file) ((*(file) = fopen((filename), "wb")) != NULL) -#define ini_openrewrite(filename, file) ((*(file) = fopen((filename), "r+b")) != NULL) -#define ini_close(file) (fclose(*(file)) == 0) -#define ini_read(buffer, size, file) (fgets((buffer), (size), *(file)) != NULL) -#define ini_write(buffer, file) (fputs((buffer), *(file)) >= 0) -#define ini_rename(source, dest) (rename((source), (dest)) == 0) -#define ini_remove(filename) (remove(filename) == 0) - -#define INI_FILEPOS long int -#define ini_tell(file, pos) (*(pos) = ftell(*(file))) -#define ini_seek(file, pos) (fseek(*(file), *(pos), SEEK_SET) == 0) - -/* for floating-point support, define additional types and functions */ -#define INI_REAL float -#define ini_ftoa(string, value) sprintf((string), "%f", (value)) -#define ini_atof(string) (INI_REAL) strtod((string), NULL) diff --git a/source/Sysmodule/source/minIni/minGlue.h b/source/Sysmodule/source/minIni/minGlue.h deleted file mode 100644 index 31888637..00000000 --- a/source/Sysmodule/source/minIni/minGlue.h +++ /dev/null @@ -1,31 +0,0 @@ -/* Glue functions for the minIni library, based on the C/C++ stdio library - * - * Or better said: this file contains macros that maps the function interface - * used by minIni to the standard C/C++ file I/O functions. - * - * By CompuPhase, 2008-2014 - * This "glue file" is in the public domain. It is distributed without - * warranties or conditions of any kind, either express or implied. - */ - -/* map required file I/O types and functions to the standard C library */ -#include - -#define INI_FILETYPE FILE* -#define ini_openread(filename, file) ((*(file) = fopen((filename), "rb")) != NULL) -#define ini_openwrite(filename, file) ((*(file) = fopen((filename), "wb")) != NULL) -#define ini_openrewrite(filename, file) ((*(file) = fopen((filename), "r+b")) != NULL) -#define ini_close(file) (fclose(*(file)) == 0) -#define ini_read(buffer, size, file) (fgets((buffer), (size), *(file)) != NULL) -#define ini_write(buffer, file) (fputs((buffer), *(file)) >= 0) -#define ini_rename(source, dest) (rename((source), (dest)) == 0) -#define ini_remove(filename) (remove(filename) == 0) - -#define INI_FILEPOS long int -#define ini_tell(file, pos) (*(pos) = ftell(*(file))) -#define ini_seek(file, pos) (fseek(*(file), *(pos), SEEK_SET) == 0) - -/* for floating-point support, define additional types and functions */ -#define INI_REAL float -#define ini_ftoa(string, value) sprintf((string), "%f", (value)) -#define ini_atof(string) (INI_REAL) strtod((string), NULL) diff --git a/source/Sysmodule/source/minIni/wxMinIni.h b/source/Sysmodule/source/minIni/wxMinIni.h deleted file mode 100644 index 3c4538f2..00000000 --- a/source/Sysmodule/source/minIni/wxMinIni.h +++ /dev/null @@ -1,126 +0,0 @@ -/* minIni - Multi-Platform INI file parser, wxWidgets interface - * - * Copyright (c) CompuPhase, 2008-2012 - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy - * of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - * - * Version: $Id: wxMinIni.h 44 2012-01-04 15:52:56Z thiadmer.riemersma@gmail.com $ - */ -#ifndef WXMININI_H -#define WXMININI_H - -#include "minIni.h" -#include - -class minIni -{ -public: - minIni(const wxString& filename) : iniFilename(filename) - { - } - - bool getbool(const wxString& Section, const wxString& Key, bool DefValue = false) const - { - return ini_getbool(Section.utf8_str(), Key.utf8_str(), int(DefValue), iniFilename.utf8_str()) != 0; - } - - long getl(const wxString& Section, const wxString& Key, long DefValue = 0) const - { - return ini_getl(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); - } - - int geti(const wxString& Section, const wxString& Key, int DefValue = 0) const - { - return static_cast(ini_getl(Section.utf8_str(), Key.utf8_str(), (long)DefValue, iniFilename.utf8_str())); - } - - wxString gets(const wxString& Section, const wxString& Key, const wxString& DefValue = wxT("")) const - { - char buffer[INI_BUFFERSIZE]; - ini_gets(Section.utf8_str(), Key.utf8_str(), DefValue.utf8_str(), buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); - wxString result = wxString::FromUTF8(buffer); - return result; - } - - wxString getsection(int idx) const - { - char buffer[INI_BUFFERSIZE]; - ini_getsection(idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); - wxString result = wxString::FromUTF8(buffer); - return result; - } - - wxString getkey(const wxString& Section, int idx) const - { - char buffer[INI_BUFFERSIZE]; - ini_getkey(Section.utf8_str(), idx, buffer, INI_BUFFERSIZE, iniFilename.utf8_str()); - wxString result = wxString::FromUTF8(buffer); - return result; - } - -#if defined INI_REAL - INI_REAL getf(const wxString& Section, wxString& Key, INI_REAL DefValue = 0) const - { - return ini_getf(Section.utf8_str(), Key.utf8_str(), DefValue, iniFilename.utf8_str()); - } -#endif - -#if !defined INI_READONLY - bool put(const wxString& Section, const wxString& Key, long Value) const - { - return ini_putl(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; - } - - bool put(const wxString& Section, const wxString& Key, int Value) const - { - return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; - } - - bool put(const wxString& Section, const wxString& Key, bool Value) const - { - return ini_putl(Section.utf8_str(), Key.utf8_str(), (long)Value, iniFilename.utf8_str()) != 0; - } - - bool put(const wxString& Section, const wxString& Key, const wxString& Value) const - { - return ini_puts(Section.utf8_str(), Key.utf8_str(), Value.utf8_str(), iniFilename.utf8_str()) != 0; - } - - bool put(const wxString& Section, const wxString& Key, const char* Value) const - { - return ini_puts(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; - } - -# if defined INI_REAL - bool put(const wxString& Section, const wxString& Key, INI_REAL Value) const - { - return ini_putf(Section.utf8_str(), Key.utf8_str(), Value, iniFilename.utf8_str()) != 0; - } -# endif - - bool del(const wxString& Section, const wxString& Key) const - { - return ini_puts(Section.utf8_str(), Key.utf8_str(), 0, iniFilename.utf8_str()) != 0; - } - - bool del(const wxString& Section) const - { - return ini_puts(Section.utf8_str(), 0, 0, iniFilename.utf8_str()) != 0; - } -#endif - -private: - wxString iniFilename; -}; - -#endif /* WXMININI_H */ From 787a015d8981c3aacf68a747ab1f45cc371bbccf Mon Sep 17 00:00:00 2001 From: kivr Date: Fri, 29 May 2020 11:33:22 -0500 Subject: [PATCH 09/13] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index ca1077c8..9c64f1bc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,14 @@ +# sys-con-plus-mitm + +#### Adding man-in-the-middle support to the sys-con sysmodule + +## Description +This sysmodule aims to add **Wireless** support for third-party controllers to the Nintendo Switch. This will be useless once sys-con sysmodule achieves the direct bluetooth support. + +This sysmodule is inspired by the hid-mitm sysmodule (https://github.com/jakibaki/hid-mitm) which is now obsolete. + +The **USB** support provided by the original sys-con is intended to remain untouched so that both wireless and wired controllers can work together. + # sys-con #### A Nintendo Switch custom sysmodule for third-party controller support. No man-in-the-middle required! From 68a36ca20e339763d4af1beb1d959d2d89230916 Mon Sep 17 00:00:00 2001 From: Kristian Date: Fri, 29 May 2020 15:55:34 -0500 Subject: [PATCH 10/13] Add linux/raspberry_pi client. --- network_clients/linux_or_raspberry/Makefile | 10 + .../linux_or_raspberry/sys-con-client.c | 210 ++++++++++++++++++ 2 files changed, 220 insertions(+) create mode 100644 network_clients/linux_or_raspberry/Makefile create mode 100644 network_clients/linux_or_raspberry/sys-con-client.c diff --git a/network_clients/linux_or_raspberry/Makefile b/network_clients/linux_or_raspberry/Makefile new file mode 100644 index 00000000..01c207e0 --- /dev/null +++ b/network_clients/linux_or_raspberry/Makefile @@ -0,0 +1,10 @@ +SOURCES=sys-con-client.c +OUT=sys-con-client + +$(OUT): $(SOURCES) + gcc -o $@ $< + +.PHONY: clean + +clean: + rm $(OUT) diff --git a/network_clients/linux_or_raspberry/sys-con-client.c b/network_clients/linux_or_raspberry/sys-con-client.c new file mode 100644 index 00000000..63b560a2 --- /dev/null +++ b/network_clients/linux_or_raspberry/sys-con-client.c @@ -0,0 +1,210 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 7 + +static unsigned char buffer[BUFFER_SIZE] = {0}; + +struct button_map +{ + unsigned char index; + unsigned char position; +}; + +enum hat_state +{ + HAT_UP, + HAT_UPRIGHT, + HAT_RIGHT, + HAT_DOWNRIGHT, + HAT_DOWN, + HAT_DOWNLEFT, + HAT_LEFT, + HAT_UPLEFT, + HAT_UNPRESSED, +}; + +static struct button_map buttonMaps[] = +{ + {4, 5}, + {4, 6}, + {4, 7}, + {4, 4}, + {5, 0}, + {5, 1}, + {5, 2}, + {5, 3}, + {5, 4}, + {5, 5}, + {6, 0}, + {5, 6}, + {5, 7}, + {4, 1}, + {4, 3}, + {4, 0}, + {4, 2}, +}; + +int axisToIndexMap[] = +{ + 0, + 1, + -1, + 2, + 3, +}; + +int read_event(int fd, struct js_event *event) +{ + ssize_t bytes; + + bytes = read(fd, event, sizeof(*event)); + + if (bytes == sizeof(*event)) + return 0; + + /* Error, could not read full event. */ + return -1; +} + +int main(int argc, char *argv[]) +{ + char device[] = "/dev/input/js0"; + int js, gd, yes = 1; + struct js_event event; + struct sockaddr_in saddr; + + if(argc != 3) + { + printf("\n Usage: %s \n",argv[0]); + return 1; + } + + device[13] = argv[2][0]; + + js = open(device, O_RDONLY); + + if (js == -1) + perror("Could not open joystick"); + + gd = socket(AF_INET, SOCK_STREAM, 0); + if (gd == -1) + perror("Could not open socket"); + + memset(&saddr, 0, sizeof(saddr)); + + saddr.sin_family = AF_INET; + saddr.sin_port = htons(8080); + + if (inet_pton(AF_INET, argv[1], &saddr.sin_addr) <= 0) + { + printf("\n inet_pton error occured\n"); + return 1; + } + + if (connect(gd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0) + { + printf("\n Error : Connect Failed \n"); + return 1; + } + + if (setsockopt(gd, IPPROTO_TCP, TCP_NODELAY, (void *)&yes, sizeof(yes)) < 0) + { + printf("\n Error : TCP NODELAY Failed \n"); + return 1; + } + + memset(buffer, 0, sizeof(buffer)); + + // Init axis at rest + buffer[0] = 127; + buffer[1] = 127; + buffer[2] = 127; + buffer[3] = 127; + + /* This loop will exit if the controller is unplugged. */ + while (read_event(js, &event) == 0) + { + switch (event.type) + { + case JS_EVENT_BUTTON: + printf("Button %u %s\n", event.number, event.value ? + "pressed" : "released"); + + struct button_map map = buttonMaps[event.number]; + if (event.value) + { + buffer[map.index] |= 1 << map.position; + } + else + { + buffer[map.index] &= ~(1 << map.position); + } + + write(gd, buffer, BUFFER_SIZE); + break; + case JS_EVENT_AXIS: + printf("Axis %zu value %6d\n", event.number, event.value); + + switch (event.number) + { + case 6: + case 7: + { + int buttonNumber, buttonValue = 1; + buttonNumber = (event.number == 6 && event.value < 0) ? 15 : + (event.number == 6 && event.value > 0) ? 16 : + (event.number == 7 && event.value < 0) ? 13 : + (event.number == 7 && event.value > 0) ? 14 : -1; + + if (buttonNumber == -1) + { + int i; + for (i = 0; i < 2; i++) + { + struct button_map map = buttonMaps[(event.number == 7 ? 13 : 15) + i]; + buffer[map.index] &= ~(1 << map.position); + } + } + else + { + struct button_map map = buttonMaps[buttonNumber]; + buffer[map.index] |= 1 << map.position; + } + } + break; + + case 0: + case 1: + case 3: + case 4: + { + int index = axisToIndexMap[event.number]; + buffer[index] = (unsigned char) + (((int)event.value + 32767) >> 8); + printf("Normalized value %3d\n", + (unsigned char)buffer[index]); + } + break; + } + write(gd, buffer, BUFFER_SIZE); + break; + + default: + /* Ignore init events. */ + printf("Weird event: %d\n", event.type); + break; + } + + fflush(stdout); + } + + close(js); + return 0; +} From f05e9cc1498bf3c80d721a936aeb26c35cdf2108 Mon Sep 17 00:00:00 2001 From: kivr Date: Mon, 1 Jun 2020 14:43:45 -0500 Subject: [PATCH 11/13] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 9c64f1bc..481470ea 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,12 @@ This sysmodule is inspired by the hid-mitm sysmodule (https://github.com/jakibak The **USB** support provided by the original sys-con is intended to remain untouched so that both wireless and wired controllers can work together. +## Example setup + +🎮 + ))) Bluetooth ((( Raspberry PI ))) WiFi ((( Nintendo Switch +🎮 + # sys-con #### A Nintendo Switch custom sysmodule for third-party controller support. No man-in-the-middle required! From 2d8ee404e6c9a02e3519ed239c6cdf2b8597cfd8 Mon Sep 17 00:00:00 2001 From: kivr Date: Mon, 1 Jun 2020 14:44:21 -0500 Subject: [PATCH 12/13] Update README.md --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index 481470ea..45fd0804 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,7 @@ The **USB** support provided by the original sys-con is intended to remain untou ## Example setup -🎮 - ))) Bluetooth ((( Raspberry PI ))) WiFi ((( Nintendo Switch -🎮 +🎮 ))) Bluetooth ((( RaspberryPI ))) WiFi ((( NintendoSwitch # sys-con From 78b0e4121147f513fcb1cebcf0e94d9ef76301bb Mon Sep 17 00:00:00 2001 From: kivr Date: Tue, 22 Mar 2022 00:11:22 -0600 Subject: [PATCH 13/13] Fix build error. --- source/Sysmodule/source/log.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/Sysmodule/source/log.h b/source/Sysmodule/source/log.h index f52eece7..a85f77e4 100644 --- a/source/Sysmodule/source/log.h +++ b/source/Sysmodule/source/log.h @@ -1,6 +1,6 @@ #pragma once -#include "config_handler.h" +#define CONFIG_PATH "/config/sys-con/" #define LOG_PATH CONFIG_PATH "log.txt" #ifdef __cplusplus