From 51247b0dc153f746a16f7b5f881ee317eef19f41 Mon Sep 17 00:00:00 2001 From: Maschell Date: Sun, 24 Nov 2019 14:20:08 +0100 Subject: [PATCH] first commit --- .gitignore | 4 + Makefile | 272 ++++++++ makefile.mk | 50 ++ src/BackgroundThread.cpp | 53 ++ src/BackgroundThread.hpp | 50 ++ src/ftp.c | 921 ++++++++++++++++++++++++++ src/ftp.h | 43 ++ src/main.cpp | 138 ++++ src/main.h | 32 + src/net.c | 283 ++++++++ src/net.h | 63 ++ src/utils/BackgroundThreadWrapper.cpp | 30 + src/utils/BackgroundThreadWrapper.hpp | 31 + src/utils/CMutex.h | 69 ++ src/utils/CThread.h | 134 ++++ src/utils/logger.c | 82 +++ src/utils/logger.h | 38 ++ src/utils/utils.c | 41 ++ src/utils/utils.h | 35 + src/virtualpath.c | 125 ++++ src/virtualpath.h | 59 ++ src/vrt.c | 346 ++++++++++ src/vrt.h | 58 ++ 23 files changed, 2957 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 makefile.mk create mode 100644 src/BackgroundThread.cpp create mode 100644 src/BackgroundThread.hpp create mode 100644 src/ftp.c create mode 100644 src/ftp.h create mode 100644 src/main.cpp create mode 100644 src/main.h create mode 100644 src/net.c create mode 100644 src/net.h create mode 100644 src/utils/BackgroundThreadWrapper.cpp create mode 100644 src/utils/BackgroundThreadWrapper.hpp create mode 100644 src/utils/CMutex.h create mode 100644 src/utils/CThread.h create mode 100644 src/utils/logger.c create mode 100644 src/utils/logger.h create mode 100644 src/utils/utils.c create mode 100644 src/utils/utils.h create mode 100644 src/virtualpath.c create mode 100644 src/virtualpath.h create mode 100644 src/vrt.c create mode 100644 src/vrt.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..21f59a9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +build/* +*.mod +sysapp.layout +sysapp.cbp diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6f3a551 --- /dev/null +++ b/Makefile @@ -0,0 +1,272 @@ +# You probably never need to adjust this Makefile. +# All changes can be done in the makefile.mk + +#--------------------------------------------------------------------------------- +# Clear the implicit built in rules +#--------------------------------------------------------------------------------- +.SUFFIXES: +#--------------------------------------------------------------------------------- +ifeq ($(strip $(DEVKITPPC)),) +$(error "Please set DEVKITPPC in your environment. export DEVKITPPC=devkitPPC") +endif +ifeq ($(strip $(DEVKITPRO)),) +$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=devkitPRO") +endif + +export PATH := $(DEVKITPPC)/bin:$(PORTLIBS)/bin:$(PATH) +export PORTLIBS := $(DEVKITPRO)/portlibs/ppc +export WUPSDIR := $(DEVKITPRO)/wups +export GCC_VER := $(shell $(DEVKITPPC)/bin/powerpc-eabi-gcc -dumpversion) + +PREFIX := powerpc-eabi- + +export AS := $(PREFIX)as +export CC := $(PREFIX)gcc +export CXX := $(PREFIX)g++ +export LD := $(PREFIX)ld +export AR := $(PREFIX)ar +export OBJCOPY := $(PREFIX)objcopy + +#--------------------------------------------------------------------------------- +# TARGET is the name of the output +# BUILD is the directory where object files & intermediate files will be placed +# SOURCES is a list of directories containing source code +# INCLUDES is a list of directories containing extra header files +#--------------------------------------------------------------------------------- +TARGET := $(notdir $(CURDIR)) +BUILD := build + +ifeq ($(notdir $(CURDIR)),$(BUILD)) + include ../makefile.mk +else + include makefile.mk +endif + +include $(WUPSDIR)/plugin_makefile.mk + + +#MAP ?= $(TARGET:.mod=.map) + +#--------------------------------------------------------------------------------- +# options for code generation +#--------------------------------------------------------------------------------- + +MACHDEP = -DESPRESSO -mcpu=750 -meabi -mhard-float + +# -Os: optimise size +# -Wall: generate lots of warnings +# -D__wiiu__: define the symbol __wiiu__ (used in some headers) +# -mcpu=750: enable processor specific compilation +# -meabi: enable eabi specific compilation +# -mhard-float: enable hardware floating point instructions +# -nostartfiles: Do not use the standard system startup files when linking +# -ffunction-sections: split up functions so linker can garbage collect +# -fdata-sections: split up data so linker can garbage collect +COMMON_CFLAGS := -O0 -Wall $(MACHDEP) -meabi -ffunction-sections -fdata-sections -Wl,-q $(COMMON_CFLAGS) + +CFLAGS += -D__WIIU__ -D__WUT__ + +# -x c: compile as c code +# -std=c11: use the c11 standard +CFLAGS := $(COMMON_CFLAGS) -x c -std=gnu11 $(CFLAGS) + +# -x c++: compile as c++ code +# -std=gnu++11: use the c++11 standard +CXXFLAGS := $(COMMON_CFLAGS) -x c++ -std=gnu++11 $(CXXFLAGS) + +#--------------------------------------------------------------------------------- +# any extra ld flags +#-------------------------------------------------------------------------------- +# --gc-sections: remove unneeded symbols +# -Map: generate a map file +LDFLAGS += $(ARCH) -Wl,-Map,$(notdir $@).map,--gc-sections,-wrap,__gxx_personality_v0 + + +#--------------------------------------------------------------------------------- +Q := @ +MAKEFLAGS += --no-print-directory +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS += +# +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS += + +#--------------------------------------------------------------------------------- +# no real need to edit anything past this point unless you need to add additional +# rules for different file extensions +#--------------------------------------------------------------------------------- +ifneq ($(BUILD),$(notdir $(CURDIR))) +#--------------------------------------------------------------------------------- +export PROJECTDIR := $(CURDIR) +export OUTPUT := $(CURDIR)/$(TARGETDIR)/$(TARGET) +export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \ + $(foreach dir,$(DATA),$(CURDIR)/$(dir)) +export DEPSDIR := $(CURDIR)/$(BUILD) + +#--------------------------------------------------------------------------------- +# automatically build a list of object files for our project +#--------------------------------------------------------------------------------- +CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c))) +CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp))) +sFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s))) +SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.S))) +BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*))) +TTFFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.ttf))) +PNGFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.png))) + +#--------------------------------------------------------------------------------- +# use CXX for linking C++ projects, CC for standard C +#--------------------------------------------------------------------------------- +ifeq ($(strip $(CPPFILES)),) + export REAL_LD := $(CC) +else + export REAL_LD := $(CXX) +endif + +export OFILES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) \ + $(sFILES:.s=.o) $(SFILES:.S=.o) \ + $(PNGFILES:.png=.png.o) $(addsuffix .o,$(BINFILES)) + +#--------------------------------------------------------------------------------- +# build a list of include paths +#--------------------------------------------------------------------------------- +export INCLUDE_FULL += $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \ + $(foreach dir,$(LIBDIRS),-I$(dir)/include) \ + $(EXTERNAL_INCLUDE) + +#--------------------------------------------------------------------------------- +# build a list of library paths +#--------------------------------------------------------------------------------- +export LIBPATHS_FULL += $(foreach dir,$(LIBDIRS),-L$(dir)/lib) \ + $(EXTERNAL_LIBPATHS) + + +export OUTPUT := $(CURDIR)/$(TARGET) +.PHONY: $(BUILD) clean install + +#--------------------------------------------------------------------------------- +$(BUILD): + @[ -d $@ ] || mkdir -p $@ + @$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile + +#--------------------------------------------------------------------------------- +clean: + @echo clean ... + @rm -fr $(BUILD) $(OUTPUT).mod $(OUTPUT) + +#--------------------------------------------------------------------------------- +else + +DEPENDS := $(OFILES:.o=.d) + +THIS_DIR := $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) + +############################################################################### +# Rule to make everything. +PHONY += all + +all : $(OUTPUT) +############################################################################### +# Special build rules + + +# Rule to make the module file. +$(OUTPUT) : $(OFILES) + @echo "linking ... " $@ + @$(REAL_LD) $(OFILES) $(LDFLAGS) $(LIBS) $(LIBPATHS_FULL) -o $@ + +############################################################################### +# Standard build rules +#--------------------------------------------------------------------------------- +%.a: +#--------------------------------------------------------------------------------- + @echo $(notdir $@) + @rm -f $@ + @$(AR) -rc $@ $^ + +#--------------------------------------------------------------------------------- +%.o: %.cpp + @echo $(notdir $<) + @$(CXX) -MMD -MP -MF $(DEPSDIR)/$*.d $(CXXFLAGS) $(INCLUDE_FULL) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.c + @echo $(notdir $<) + @$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d $(CFLAGS) $(INCLUDE_FULL) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.o: %.S + @echo $(notdir $<) + $(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(INCLUDE_FULL) -c $< -o $@ $(ERROR_FILTER) + +#--------------------------------------------------------------------------------- +%.png.o : %.png + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.jpg.o : %.jpg + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.ttf.o : %.ttf + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.bin.o : %.bin + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.wav.o : %.wav + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.mp3.o : %.mp3 + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +#--------------------------------------------------------------------------------- +%.ogg.o : %.ogg + @echo $(notdir $<) + @bin2s -a 32 $< | $(AS) -o $(@) + +############################################################################### +# Assembly listing rules + +# Rule to make assembly listing. +PHONY += list +list : $(LIST) + +# Rule to make the listing file. +%.list : $(TARGET) + $(LOG) + -$Qmkdir -p $(dir $@) + $Q$(OBJDUMP) -d $< > $@ + +############################################################################### +# Clean rule + +# Rule to clean files. +PHONY += clean +clean : + $Qrm -rf $(wildcard $(BUILD) $(BIN)) + +############################################################################### +# Phony targets + +.PHONY : $(PHONY) + +-include $(DEPENDS) + +#--------------------------------------------------------------------------------- +endif +#--------------------------------------------------------------------------------- diff --git a/makefile.mk b/makefile.mk new file mode 100644 index 0000000..f07eb36 --- /dev/null +++ b/makefile.mk @@ -0,0 +1,50 @@ +# Compiling the projects with libutils logging code? +DO_LOGGING := 1 + +# Target filename +TARGET := $(notdir $(CURDIR)).mod + +# Source directories +SOURCES := src src/utils + +# Data directories +DATA := + +# Include directories +INCLUDES := src + +#--------------------------------------------------------------------------------- +# options for code generation and linking +#--------------------------------------------------------------------------------- +# Extra C AND C++ compiler flags +COMMON_CFLAGS := +# Extra C compiler flags +CFLAGS := +# Extra C++ compiler flags +CXXFLAGS := +# Extra linking flags for all linking steps +LDFLAGS := + +#--------------------------------------------------------------------------------- +# list of directories containing libraries, this must be the top level containing +# include and lib +#--------------------------------------------------------------------------------- +LIBDIRS := $(WUPSDIR) $(WUT_ROOT) $(PORTLIBS) + +#--------------------------------------------------------------------------------- +# any extra libraries we wish to link with the project +#--------------------------------------------------------------------------------- +LIBS := -lwups -lwut -liosuhax + +#--------------------------------------------------------------------------------- +# Will be added to the final lib paths +# example: +# -L$C:/library1/lib +#--------------------------------------------------------------------------------- +EXTERNAL_LIBPATHS := + +#--------------------------------------------------------------------------------- +# Will be added to the final include paths +# -IC:/library1/include +#--------------------------------------------------------------------------------- +EXTERNAL_INCLUDE := diff --git a/src/BackgroundThread.cpp b/src/BackgroundThread.cpp new file mode 100644 index 0000000..efc5192 --- /dev/null +++ b/src/BackgroundThread.cpp @@ -0,0 +1,53 @@ +#include "BackgroundThread.hpp" +#include +#include +#include +#include + +#include + +#include "ftp.h" +#include "net.h" + +BackgroundThread * BackgroundThread::instance = NULL; + +BackgroundThread::BackgroundThread(): BackgroundThreadWrapper(BackgroundThread::getPriority()) { + DEBUG_FUNCTION_LINE("Create new Server\n"); + mutex.lock(); + this->serverSocket = create_server(PORT); + DCFlushRange(&(this->serverSocket), 4); + mutex.unlock(); + DEBUG_FUNCTION_LINE("handle %d\n", this->serverSocket); + resumeThread(); +} + +BackgroundThread::~BackgroundThread() { + DEBUG_FUNCTION_LINE("Clean up FTP\n"); + if(this->serverSocket != -1){ + mutex.lock(); + cleanup_ftp(); + network_close(this->serverSocket); + mutex.unlock(); + this->serverSocket = -1; + } + DEBUG_FUNCTION_LINE("Cleaned up FTP\n"); +} + +BOOL BackgroundThread::whileLoop() { + if(this->serverSocket != -1){ + mutex.lock(); + network_down = process_ftp_events(this->serverSocket); + mutex.unlock(); + if(network_down) { + DEBUG_FUNCTION_LINE("Network is down %d\n", this->serverSocket); + mutex.lock(); + cleanup_ftp(); + network_close(this->serverSocket); + this->serverSocket = -1; + DCFlushRange(&(this->serverSocket), 4); + mutex.unlock(); + } + OSSleepTicks(OSMillisecondsToTicks(16)); + } + return true; +} diff --git a/src/BackgroundThread.hpp b/src/BackgroundThread.hpp new file mode 100644 index 0000000..a557498 --- /dev/null +++ b/src/BackgroundThread.hpp @@ -0,0 +1,50 @@ +#pragma once +#include "utils/BackgroundThreadWrapper.hpp" +#include +#include "utils/logger.h" + +#define PORT 21 + +class BackgroundThread: BackgroundThreadWrapper { +public: + static BackgroundThread *getInstance() { + DCFlushRange(&instance, sizeof(instance)); + ICInvalidateRange(&instance, sizeof(instance)); + if(instance == NULL) { + instance = new BackgroundThread(); + DCFlushRange(&instance, sizeof(instance)); + ICInvalidateRange(&instance, sizeof(instance)); + } + return instance; + } + + static void destroyInstance() { + DCFlushRange(&instance, sizeof(instance)); + ICInvalidateRange(&instance, sizeof(instance)); + DEBUG_FUNCTION_LINE("Instance is %08X\n", instance); + OSSleepTicks(OSSecondsToTicks(1)); + if(instance != NULL) { + delete instance; + instance = NULL; + DCFlushRange(&instance, sizeof(instance)); + ICInvalidateRange(&instance, sizeof(instance)); + } + } + + BackgroundThread(); + + virtual ~BackgroundThread(); + +private: + static int32_t getPriority() { + return 16; + } + + virtual BOOL whileLoop(); + + static BackgroundThread * instance; + + int serverSocket = -1; + int network_down = 0; + +}; diff --git a/src/ftp.c b/src/ftp.c new file mode 100644 index 0000000..9f06a02 --- /dev/null +++ b/src/ftp.c @@ -0,0 +1,921 @@ +/* + +ftpii -- an FTP server for the Wii + +Copyright (C) 2008 Joseph Jordan + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1.The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2.Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3.This notice may not be removed or altered from any source distribution. + +*/ +#include +#include +#include +#include +#include +#include +#include +#include "main.h" +#include "utils/logger.h" + +//! TODO: fix those function +#define gettime() OSGetTick() +#define errno wiiu_geterrno() + +#include "ftp.h" +#include "virtualpath.h" +#include "net.h" +#include "vrt.h" + +#define UNUSED __attribute__((unused)) + +#define FTP_BUFFER_SIZE 1024 +#define MAX_CLIENTS 5 + +static const uint16_t SRC_PORT = 20; +static const int32_t EQUIT = 696969; +static const char *CRLF = "\r\n"; +static const uint32_t CRLF_LENGTH = 2; + +static uint8_t num_clients = 0; +static uint16_t passive_port = 1024; +static char *password = NULL; + +void console_printf(const char *format, ...){ +} + +typedef int32_t (*data_connection_callback)(int32_t data_socket, void *arg); + +struct client_struct { + int32_t socket; + char representation_type; + int32_t passive_socket; + int32_t data_socket; + char cwd[MAXPATHLEN]; + char pending_rename[MAXPATHLEN]; + off_t restart_marker; + struct sockaddr_in address; + bool authenticated; + char buf[FTP_BUFFER_SIZE]; + int32_t offset; + bool data_connection_connected; + data_connection_callback data_callback; + void *data_connection_callback_arg; + void (*data_connection_cleanup)(void *arg); + uint64_t data_connection_timer; +}; + +typedef struct client_struct client_t; + +static client_t *clients[MAX_CLIENTS] = { NULL }; + +void set_ftp_password(char *new_password) { + if (password) + free(password); + if (new_password) { + password = malloc(strlen(new_password) + 1); + if (!password) + return; + + strcpy((char *)password, new_password); + } else { + password = NULL; + } +} + +static bool compare_ftp_password(char *password_attempt) { + return !password || !strcmp((char *)password, password_attempt); +} + +/* + TODO: support multi-line reply +*/ +static int32_t write_reply(client_t *client, uint16_t code, char *msg) { + uint32_t msglen = 4 + strlen(msg) + CRLF_LENGTH; + char * msgbuf = (char *) malloc(msglen + 1); + if (msgbuf == NULL) + return -ENOMEM; + sprintf(msgbuf, "%u %s\r\n", code, msg); + console_printf("Wrote reply: %s", msgbuf); + int32_t ret = send_exact(client->socket, msgbuf, msglen); + free(msgbuf); + return ret; +} + +static void close_passive_socket(client_t *client) { + if (client->passive_socket >= 0) { + network_close_blocking(client->passive_socket); + client->passive_socket = -1; + } +} + +/* + result must be able to hold up to maxsplit+1 null-terminated strings of length strlen(s) + returns the number of strings stored in the result array (up to maxsplit+1) +*/ +static uint32_t split(char *s, char sep, uint32_t maxsplit, char *result[]) { + uint32_t num_results = 0; + uint32_t result_pos = 0; + uint32_t trim_pos = 0; + bool in_word = false; + for (; *s; s++) { + if (*s == sep) { + if (num_results <= maxsplit) { + in_word = false; + continue; + } else if (!trim_pos) { + trim_pos = result_pos; + } + } else if (trim_pos) { + trim_pos = 0; + } + if (!in_word) { + in_word = true; + if (num_results <= maxsplit) { + num_results++; + result_pos = 0; + } + } + result[num_results - 1][result_pos++] = *s; + result[num_results - 1][result_pos] = '\0'; + } + if (trim_pos) { + result[num_results - 1][trim_pos] = '\0'; + } + uint32_t i = num_results; + for (i = num_results; i <= maxsplit; i++) { + result[i][0] = '\0'; + } + return num_results; +} + +static int32_t ftp_USER(client_t *client, char *username UNUSED) { + return write_reply(client, 331, "User name okay, need password."); +} + +static int32_t ftp_PASS(client_t *client, char *password_attempt) { + if (compare_ftp_password(password_attempt)) { + client->authenticated = true; + return write_reply(client, 230, "User logged in, proceed."); + } else { + return write_reply(client, 530, "Login incorrect."); + } +} + +static int32_t ftp_REIN(client_t *client, char *rest UNUSED) { + close_passive_socket(client); + strcpy(client->cwd, "/"); + client->representation_type = 'A'; + client->authenticated = false; + return write_reply(client, 220, "Service ready for new user."); +} + +static int32_t ftp_QUIT(client_t *client, char *rest UNUSED) { + // TODO: dont quit if xfer in progress + int32_t result = write_reply(client, 221, "Service closing control connection."); + return result < 0 ? result : -EQUIT; +} + +static int32_t ftp_SYST(client_t *client, char *rest UNUSED) { + return write_reply(client, 215, "UNIX Type: L8 Version: ftpii"); +} + +static int32_t ftp_TYPE(client_t *client, char *rest) { + char representation_type[FTP_BUFFER_SIZE], param[FTP_BUFFER_SIZE]; + char *args[] = { representation_type, param }; + uint32_t num_args = split(rest, ' ', 1, args); + if (num_args == 0) { + return write_reply(client, 501, "Syntax error in parameters."); + } else if ((!strcasecmp("A", representation_type) && (!*param || !strcasecmp("N", param))) || + (!strcasecmp("I", representation_type) && num_args == 1)) { + client->representation_type = *representation_type; + } else { + return write_reply(client, 501, "Syntax error in parameters."); + } + char msg[15]; + snprintf(msg, sizeof(msg), "Type set to %s.", representation_type); + return write_reply(client, 200, msg); +} + +static int32_t ftp_MODE(client_t *client, char *rest) { + if (!strcasecmp("S", rest)) { + return write_reply(client, 200, "Mode S ok."); + } else { + return write_reply(client, 501, "Syntax error in parameters."); + } +} + +static int32_t ftp_PWD(client_t *client, char *rest UNUSED) { + char msg[MAXPATHLEN + 24]; + // TODO: escape double-quotes + sprintf(msg, "\"%s\" is current directory.", client->cwd); + return write_reply(client, 257, msg); +} + +static int32_t ftp_CWD(client_t *client, char *path) { + int32_t result; + if (!vrt_chdir(client->cwd, path)) { + result = write_reply(client, 250, "CWD command successful."); + } else { + result = write_reply(client, 550, strerror(errno)); + } + return result; +} + +static int32_t ftp_CDUP(client_t *client, char *rest UNUSED) { + int32_t result; + if (!vrt_chdir(client->cwd, "..")) { + result = write_reply(client, 250, "CDUP command successful."); + } else { + result = write_reply(client, 550, strerror(errno)); + } + return result; +} + +static int32_t ftp_DELE(client_t *client, char *path) { + if (!vrt_unlink(client->cwd, path)) { + return write_reply(client, 250, "File or directory removed."); + } else { + return write_reply(client, 550, strerror(errno)); + } +} + +static int32_t ftp_MKD(client_t *client, char *path) { + if (!*path) { + return write_reply(client, 501, "Syntax error in parameters."); + } + if (!vrt_mkdir(client->cwd, path, 0777)) { + char msg[MAXPATHLEN + 21]; + char abspath[MAXPATHLEN]; + strcpy(abspath, client->cwd); + vrt_chdir(abspath, path); // TODO: error checking + // TODO: escape double-quotes + sprintf(msg, "\"%s\" directory created.", abspath); + return write_reply(client, 257, msg); + } else { + return write_reply(client, 550, strerror(errno)); + } +} + +static int32_t ftp_RNFR(client_t *client, char *path) { + strcpy(client->pending_rename, path); + return write_reply(client, 350, "Ready for RNTO."); +} + +static int32_t ftp_RNTO(client_t *client, char *path) { + if (!*client->pending_rename) { + return write_reply(client, 503, "RNFR required first."); + } + int32_t result; + if (!vrt_rename(client->cwd, client->pending_rename, path)) { + result = write_reply(client, 250, "Rename successful."); + } else { + result = write_reply(client, 550, strerror( + + errno)); + } + *client->pending_rename = '\0'; + return result; +} + +static int32_t ftp_SIZE(client_t *client, char *path) { + struct stat st; + if (!vrt_stat(client->cwd, path, &st)) { + char size_buf[12]; + sprintf(size_buf, "%llu", st.st_size); + return write_reply(client, 213, size_buf); + } else { + return write_reply(client, 550, strerror(errno)); + } +} + +static int32_t ftp_PASV(client_t *client, char *rest UNUSED) { + close_passive_socket(client); + client->passive_socket = network_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (client->passive_socket < 0) { + return write_reply(client, 520, "Unable to create listening socket."); + } + set_blocking(client->passive_socket, false); + struct sockaddr_in bindAddress; + memset(&bindAddress, 0, sizeof(bindAddress)); + bindAddress.sin_family = AF_INET; + bindAddress.sin_port = htons(passive_port++); // XXX: BUG: This will overflow eventually, with interesting results... + bindAddress.sin_addr.s_addr = htonl(INADDR_ANY); + int32_t result; + if ((result = network_bind(client->passive_socket, (struct sockaddr *)&bindAddress, sizeof(bindAddress))) < 0) { + close_passive_socket(client); + return write_reply(client, 520, "Unable to bind listening socket."); + } + if ((result = network_listen(client->passive_socket, 1)) < 0) { + close_passive_socket(client); + return write_reply(client, 520, "Unable to listen on socket."); + } + char reply[49]; + uint16_t port = bindAddress.sin_port; + uint32_t ip = network_gethostip(); + struct in_addr addr; + addr.s_addr = ip; + console_printf("Listening for data connections at %s:%u...\n", inet_ntoa(addr), port); + sprintf(reply, "Entering Passive Mode (%u,%u,%u,%u,%u,%u).", (ip >> 24) & 0xff, (ip >> 16) & 0xff, (ip >> 8) & 0xff, ip & 0xff, (port >> 8) & 0xff, port & 0xff); + return write_reply(client, 227, reply); +} + +static int32_t ftp_PORT(client_t *client, char *portspec) { + uint32_t h1, h2, h3, h4, p1, p2; + if (sscanf(portspec, "%3u,%3u,%3u,%3u,%3u,%3u", &h1, &h2, &h3, &h4, &p1, &p2) < 6) { + return write_reply(client, 501, "Syntax error in parameters."); + } + char addr_str[44]; + sprintf(addr_str, "%u.%u.%u.%u", h1, h2, h3, h4); + struct in_addr sin_addr; + if (!inet_aton(addr_str, &sin_addr)) { + return write_reply(client, 501, "Syntax error in parameters."); + } + close_passive_socket(client); + uint16_t port = ((p1 &0xff) << 8) | (p2 & 0xff); + client->address.sin_addr = sin_addr; + client->address.sin_port = htons(port); + console_printf("Set client address to %s:%u\n", addr_str, port); + return write_reply(client, 200, "PORT command successful."); +} + +typedef int32_t (*data_connection_handler)(client_t *client, data_connection_callback callback, void *arg); + +static int32_t prepare_data_connection_active(client_t *client, data_connection_callback callback UNUSED, void *arg UNUSED) { + int32_t data_socket = network_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (data_socket < 0) + return data_socket; + set_blocking(data_socket, false); + struct sockaddr_in bindAddress; + memset(&bindAddress, 0, sizeof(bindAddress)); + bindAddress.sin_family = AF_INET; + bindAddress.sin_port = htons(SRC_PORT); + bindAddress.sin_addr.s_addr = htonl(INADDR_ANY); + int32_t result; + if ((result = network_bind(data_socket, (struct sockaddr *)&bindAddress, sizeof(bindAddress))) < 0) { + network_close(data_socket); + return result; + } + + client->data_socket = data_socket; + console_printf("Attempting to connect to client at %s:%u\n", inet_ntoa(client->address.sin_addr), client->address.sin_port); + return 0; +} + +static int32_t prepare_data_connection_passive(client_t *client, data_connection_callback callback UNUSED, void *arg UNUSED) { + client->data_socket = client->passive_socket; + console_printf("Waiting for data connections...\n"); + return 0; +} + +static int32_t prepare_data_connection(client_t *client, void *callback, void *arg, void *cleanup) { + int32_t result = write_reply(client, 150, "Transferring data."); + if (result >= 0) { + data_connection_handler handler = prepare_data_connection_active; + if (client->passive_socket >= 0) + handler = prepare_data_connection_passive; + result = handler(client, (data_connection_callback)callback, arg); + if (result < 0) { + result = write_reply(client, 520, "Closing data connection, error occurred during transfer."); + } else { + client->data_connection_connected = false; + client->data_callback = callback; + client->data_connection_callback_arg = arg; + client->data_connection_cleanup = cleanup; + client->data_connection_timer = gettime() + OSSecondsToTicks(10); + } + } + return result; +} + +static int32_t send_nlst(int32_t data_socket, DIR_P *iter) { + int32_t result = 0; + char filename[MAXPATHLEN]; + struct dirent *dirent = NULL; + while ((dirent = vrt_readdir(iter)) != 0) { + size_t end_index = strlen(dirent->d_name); + if(end_index + 2 >= MAXPATHLEN) + continue; + strcpy(filename, dirent->d_name); + filename[end_index] = CRLF[0]; + filename[end_index + 1] = CRLF[1]; + filename[end_index + 2] = '\0'; + if ((result = send_exact(data_socket, filename, strlen(filename))) < 0) { + break; + } + } + return result < 0 ? result : 0; +} + +static int32_t send_list(int32_t data_socket, DIR_P *iter) { + struct stat st; + int32_t result = 0; + time_t mtime = 0; + uint64_t size = 0; + char filename[MAXPATHLEN]; + char line[MAXPATHLEN + 56 + CRLF_LENGTH + 1]; + struct dirent *dirent = NULL; + while ((dirent = vrt_readdir(iter)) != 0) { + + snprintf(filename, sizeof(filename), "%s/%s", iter->path, dirent->d_name); + if(stat(filename, &st) == 0) { + mtime = st.st_mtime; + size = st.st_size; + } else { + mtime = time(0); + size = 0; + } + + char timestamp[13]; + strftime(timestamp, sizeof(timestamp), "%b %d %Y", localtime(&mtime)); + snprintf(line, sizeof(line), "%crwxr-xr-x 1 0 0 %10llu %s %s\r\n", (dirent->d_type & DT_DIR) ? 'd' : '-', size, timestamp, dirent->d_name); + if ((result = send_exact(data_socket, line, strlen(line))) < 0) { + break; + } + } + return result < 0 ? result : 0; +} + +static int32_t ftp_NLST(client_t *client, char *path) { + if (!*path) { + path = "."; + } + + DIR_P *dir = vrt_opendir(client->cwd, path); + if (dir == NULL) { + return write_reply(client, 550, strerror(errno)); + } + + int32_t result = prepare_data_connection(client, send_nlst, dir, vrt_closedir); + if (result < 0) + vrt_closedir(dir); + return result; +} + +static int32_t ftp_LIST(client_t *client, char *path) { + if (*path == '-') { + // handle buggy clients that use "LIST -aL" or similar, at the expense of breaking paths that begin with '-' + char flags[FTP_BUFFER_SIZE]; + char rest[FTP_BUFFER_SIZE]; + char *args[] = { flags, rest }; + split(path, ' ', 1, args); + path = rest; + } + if (!*path) { + path = "."; + } + + if(path && client->cwd) { + if(strcmp(path, ".") == 0 && strcmp(client->cwd, "/") == 0) { + UnmountVirtualPaths(); + MountVirtualDevices(); + } + } + + DIR_P *dir = vrt_opendir(client->cwd, path); + if (dir == NULL) { + return write_reply(client, 550, strerror(errno)); + } + + int32_t result = prepare_data_connection(client, send_list, dir, vrt_closedir); + if (result < 0) + vrt_closedir(dir); + return result; +} + +static int32_t ftp_RETR(client_t *client, char *path) { + FILE *f = vrt_fopen(client->cwd, path, "rb"); + if (!f) { + return write_reply(client, 550, strerror(errno)); + } + + int fd = fileno(f); + if (client->restart_marker && lseek(fd, client->restart_marker, SEEK_SET) != client->restart_marker) { + int32_t lseek_error = errno; + fclose(f); + client->restart_marker = 0; + return write_reply(client, 550, strerror(lseek_error)); + } + client->restart_marker = 0; + + int32_t result = prepare_data_connection(client, send_from_file, f, fclose); + if (result < 0) + fclose(f); + return result; +} + +static int32_t stor_or_append(client_t *client, FILE *f) { + if (!f) { + return write_reply(client, 550, strerror(errno)); + } + int32_t result = prepare_data_connection(client, recv_to_file, f, fclose); + if (result < 0) + fclose(f); + return result; +} + +static int32_t ftp_STOR(client_t *client, char *path) { + FILE *f = vrt_fopen(client->cwd, path, "wb"); + int fd; + if (f) + fd = fileno(f); + if (f && client->restart_marker && lseek(fd, client->restart_marker, SEEK_SET) != client->restart_marker) { + int32_t lseek_error = errno; + fclose(f); + client->restart_marker = 0; + return write_reply(client, 550, strerror(lseek_error)); + } + client->restart_marker = 0; + + return stor_or_append(client, f); +} + +static int32_t ftp_APPE(client_t *client, char *path) { + return stor_or_append(client, vrt_fopen(client->cwd, path, "ab")); +} + +static int32_t ftp_REST(client_t *client, char *offset_str) { + off_t offset; + if (sscanf(offset_str, "%lli", &offset) < 1 || offset < 0) { + return write_reply(client, 501, "Syntax error in parameters."); + } + client->restart_marker = offset; + char msg[FTP_BUFFER_SIZE]; + sprintf(msg, "Restart position accepted (%lli).", offset); + return write_reply(client, 350, msg); +} + +static int32_t ftp_SITE_LOADER(client_t *client, char *rest UNUSED) { + int32_t result = write_reply(client, 200, "Exiting to loader."); + //set_reset_flag(); + return result; +} + +static int32_t ftp_SITE_CLEAR(client_t *client, char *rest UNUSED) { + int32_t result = write_reply(client, 200, "Cleared."); + uint32_t i; + for (i = 0; i < 18; i++) + console_printf("\n"); + //console_printf("\x1b[2;0H"); + return result; +} + +/* + This is implemented as a no-op to prevent some FTP clients + from displaying skip/abort/retry type prompts. +*/ +static int32_t ftp_SITE_CHMOD(client_t *client, char *rest UNUSED) { + return write_reply(client, 250, "SITE CHMOD command ok."); +} + +static int32_t ftp_SITE_PASSWD(client_t *client, char *new_password) { + set_ftp_password(new_password); + return write_reply(client, 200, "Password changed."); +} + +static int32_t ftp_SITE_NOPASSWD(client_t *client, char *rest UNUSED) { + set_ftp_password(NULL); + return write_reply(client, 200, "Authentication disabled."); +} + +static int32_t ftp_SITE_EJECT(client_t *client, char *rest UNUSED) { + //if (dvd_eject()) return write_reply(client, 550, "Unable to eject DVD."); + return write_reply(client, 200, "DVD ejected."); +} + +static int32_t ftp_SITE_MOUNT(client_t *client, char *path UNUSED) { + //if (!mount_virtual(path)) return write_reply(client, 550, "Unable to mount."); + return write_reply(client, 250, "Mounted."); +} + +static int32_t ftp_SITE_UNMOUNT(client_t *client, char *path UNUSED) { + //if (!unmount_virtual(path)) return write_reply(client, 550, "Unable to unmount."); + return write_reply(client, 250, "Unmounted."); +} + +static int32_t ftp_SITE_UNKNOWN(client_t *client, char *rest UNUSED) { + return write_reply(client, 501, "Unknown SITE command."); +} + +static int32_t ftp_SITE_LOAD(client_t *client, char *path UNUSED) { +// FILE *f = vrt_fopen(client->cwd, path, "rb"); +// if (!f) return write_reply(client, 550, strerror(errno)); +// char *real_path = to_real_path(client->cwd, path); +// if (!real_path) goto end; +// load_from_file(f, real_path); +// free(real_path); +// end: +// fclose(f); + return write_reply(client, 500, "Unable to load."); +} + +typedef int32_t (*ftp_command_handler)(client_t *client, char *args); + +static int32_t dispatch_to_handler(client_t *client, char *cmd_line, const char **commands, const ftp_command_handler *handlers) { + char cmd[FTP_BUFFER_SIZE], rest[FTP_BUFFER_SIZE]; + char *args[] = { cmd, rest }; + split(cmd_line, ' ', 1, args); + int32_t i; + for (i = 0; commands[i]; i++) { + if (!strcasecmp(commands[i], cmd)) + break; + } + return handlers[i](client, rest); +} + +static const char *site_commands[] = { "LOADER", "CLEAR", "CHMOD", "PASSWD", "NOPASSWD", "EJECT", "MOUNT", "UNMOUNT", "LOAD", NULL }; +static const ftp_command_handler site_handlers[] = { ftp_SITE_LOADER, ftp_SITE_CLEAR, ftp_SITE_CHMOD, ftp_SITE_PASSWD, ftp_SITE_NOPASSWD, ftp_SITE_EJECT, ftp_SITE_MOUNT, ftp_SITE_UNMOUNT, ftp_SITE_LOAD, ftp_SITE_UNKNOWN }; + +static int32_t ftp_SITE(client_t *client, char *cmd_line) { + return dispatch_to_handler(client, cmd_line, site_commands, site_handlers); +} + +static int32_t ftp_NOOP(client_t *client, char *rest UNUSED) { + return write_reply(client, 200, "NOOP command successful."); +} + +static int32_t ftp_SUPERFLUOUS(client_t *client, char *rest UNUSED) { + return write_reply(client, 202, "Command not implemented, superfluous at this site."); +} + +static int32_t ftp_NEEDAUTH(client_t *client, char *rest UNUSED) { + return write_reply(client, 530, "Please login with USER and PASS."); +} + +static int32_t ftp_UNKNOWN(client_t *client, char *rest UNUSED) { + return write_reply(client, 502, "Command not implemented."); +} + +static const char *unauthenticated_commands[] = { "USER", "PASS", "QUIT", "REIN", "NOOP", NULL }; +static const ftp_command_handler unauthenticated_handlers[] = { ftp_USER, ftp_PASS, ftp_QUIT, ftp_REIN, ftp_NOOP, ftp_NEEDAUTH }; + +static const char *authenticated_commands[] = { + "USER", "PASS", "LIST", "PWD", "CWD", "CDUP", + "SIZE", "PASV", "PORT", "TYPE", "SYST", "MODE", + "RETR", "STOR", "APPE", "REST", "DELE", "MKD", + "RMD", "RNFR", "RNTO", "NLST", "QUIT", "REIN", + "SITE", "NOOP", "ALLO", NULL +}; +static const ftp_command_handler authenticated_handlers[] = { + ftp_USER, ftp_PASS, ftp_LIST, ftp_PWD, ftp_CWD, ftp_CDUP, + ftp_SIZE, ftp_PASV, ftp_PORT, ftp_TYPE, ftp_SYST, ftp_MODE, + ftp_RETR, ftp_STOR, ftp_APPE, ftp_REST, ftp_DELE, ftp_MKD, + ftp_DELE, ftp_RNFR, ftp_RNTO, ftp_NLST, ftp_QUIT, ftp_REIN, + ftp_SITE, ftp_NOOP, ftp_SUPERFLUOUS, ftp_UNKNOWN +}; + +/* + returns negative to signal an error that requires closing the connection +*/ +static int32_t process_command(client_t *client, char *cmd_line) { + if (strlen(cmd_line) == 0) { + return 0; + } + + console_printf("Got command: %s\n", cmd_line); + + const char **commands = unauthenticated_commands; + const ftp_command_handler *handlers = unauthenticated_handlers; + + if (client->authenticated) { + commands = authenticated_commands; + handlers = authenticated_handlers; + } + + return dispatch_to_handler(client, cmd_line, commands, handlers); +} + +static void cleanup_data_resources(client_t *client) { + if (client->data_socket >= 0 && client->data_socket != client->passive_socket) { + network_close_blocking(client->data_socket); + } + client->data_socket = -1; + client->data_connection_connected = false; + client->data_callback = NULL; + if (client->data_connection_cleanup) { + client->data_connection_cleanup(client->data_connection_callback_arg); + } + client->data_connection_callback_arg = NULL; + client->data_connection_cleanup = NULL; + client->data_connection_timer = 0; +} + +static void cleanup_client(client_t *client) { + network_close_blocking(client->socket); + cleanup_data_resources(client); + close_passive_socket(client); + int client_index; + for (client_index = 0; client_index < MAX_CLIENTS; client_index++) { + if (clients[client_index] == client) { + clients[client_index] = NULL; + break; + } + } + free(client); + num_clients--; + console_printf("Client disconnected.\n"); +} + +void cleanup_ftp() { + int client_index; + for (client_index = 0; client_index < MAX_CLIENTS; client_index++) { + client_t *client = clients[client_index]; + if (client) { + write_reply(client, 421, "Service not available, closing control connection."); + cleanup_client(client); + } + } +} + +static bool process_accept_events(int32_t server) { + int32_t peer; + struct sockaddr_in client_address; + int32_t addrlen = sizeof(client_address); + while ((peer = network_accept(server, (struct sockaddr *)&client_address, &addrlen)) != -WIIU_EAGAIN) { + if (peer < 0) { + console_printf("Error accepting connection: [%i] %s\n", -peer, strerror(-peer)); + return false; + } + + console_printf("Accepted connection from %s!\n", inet_ntoa(client_address.sin_addr)); + + if (num_clients == MAX_CLIENTS) { + console_printf("Maximum of %u clients reached, not accepting client.\n", MAX_CLIENTS); + network_close(peer); + return true; + } + + client_t *client = malloc(sizeof(client_t)); + if (!client) { + console_printf("Could not allocate memory for client state, not accepting client.\n"); + network_close(peer); + return true; + } + client->socket = peer; + client->representation_type = 'A'; + client->passive_socket = -1; + client->data_socket = -1; + strcpy(client->cwd, "/"); + *client->pending_rename = '\0'; + client->restart_marker = 0; + client->authenticated = false; + client->offset = 0; + client->data_connection_connected = false; + client->data_callback = NULL; + client->data_connection_callback_arg = NULL; + client->data_connection_cleanup = NULL; + client->data_connection_timer = 0; + memcpy(&client->address, &client_address, sizeof(client_address)); + int client_index; + if (write_reply(client, 220, "ftpii") < 0) { + console_printf("Error writing greeting.\n"); + network_close_blocking(peer); + free(client); + } else { + for (client_index = 0; client_index < MAX_CLIENTS; client_index++) { + if (!clients[client_index]) { + clients[client_index] = client; + break; + } + } + num_clients++; + } + } + return true; +} + +static void process_data_events(client_t *client) { + int32_t result; + if (!client->data_connection_connected) { + if (client->passive_socket >= 0) { + struct sockaddr_in data_peer_address; + int32_t addrlen = sizeof(data_peer_address); + result = network_accept(client->passive_socket, (struct sockaddr *)&data_peer_address,&addrlen); + if (result >= 0) { + client->data_socket = result; + client->data_connection_connected = true; + } + } else { + if ((result = network_connect(client->data_socket, (struct sockaddr *)&client->address, sizeof(client->address))) < 0) { + if (result == -EINPROGRESS || result == -EALREADY) + result = -WIIU_EAGAIN; + if ((result != -WIIU_EAGAIN) && (result != -EISCONN)) { + console_printf("Unable to connect to client: [%i] %s\n", -result, strerror(-result)); + } + } + if (result >= 0 || result == -EISCONN) { + client->data_connection_connected = true; + } + } + if (client->data_connection_connected) { + result = 1; + console_printf("Connected to client! Transferring data...\n"); + } else if (gettime() > client->data_connection_timer) { + result = -2; + console_printf("Timed out waiting for data connection.\n"); + } + } else { + result = client->data_callback(client->data_socket, client->data_connection_callback_arg); + } + + if (result <= 0 && result != -WIIU_EAGAIN) { + cleanup_data_resources(client); + if (result < 0) { + result = write_reply(client, 520, "Closing data connection, error occurred during transfer."); + } else { + result = write_reply(client, 226, "Closing data connection, transfer successful."); + } + if (result < 0) { + cleanup_client(client); + } + } +} + +static void process_control_events(client_t *client) { + int32_t bytes_read; + while (client->offset < (FTP_BUFFER_SIZE - 1)) { + if (client->data_callback) { + return; + } + char *offset_buf = client->buf + client->offset; + if ((bytes_read = network_read(client->socket, offset_buf, FTP_BUFFER_SIZE - 1 - client->offset)) < 0) { + if (bytes_read != -EAGAIN) { + console_printf("Read error %i occurred, closing client.\n", bytes_read); + goto recv_loop_end; + } + return; + } else if (bytes_read == 0) { + goto recv_loop_end; // EOF from client + } + client->offset += bytes_read; + client->buf[client->offset] = '\0'; + + if (strchr(offset_buf, '\0') != (client->buf + client->offset)) { + console_printf("Received a null byte from client, closing connection ;-)\n"); // i have decided this isn't allowed =P + goto recv_loop_end; + } + + char *next; + char *end; + for (next = client->buf; (end = strstr(next, CRLF)) && !client->data_callback; next = end + CRLF_LENGTH) { + *end = '\0'; + if (strchr(next, '\n')) { + console_printf("Received a line-feed from client without preceding carriage return, closing connection ;-)\n"); // i have decided this isn't allowed =P + goto recv_loop_end; + } + + if (*next) { + int32_t result; + if ((result = process_command(client, next)) < 0) { + if (result != -EQUIT) { + console_printf("Closing connection due to error while processing command: %s\n", next); + } + goto recv_loop_end; + } + } + + } + + if (next != client->buf) { // some lines were processed + client->offset = strlen(next); + char tmp_buf[client->offset]; + memcpy(tmp_buf, next, client->offset); + memcpy(client->buf, tmp_buf, client->offset); + } + } + console_printf("Received line longer than %u bytes, closing client.\n", FTP_BUFFER_SIZE - 1); + +recv_loop_end: + cleanup_client(client); +} + +bool process_ftp_events(int32_t server) { + bool network_down = !process_accept_events(server); + int client_index; + for (client_index = 0; client_index < MAX_CLIENTS; client_index++) { + client_t *client = clients[client_index]; + if (client) { + if (client->data_callback) { + process_data_events(client); + } else { + process_control_events(client); + } + } + } + return network_down; +} diff --git a/src/ftp.h b/src/ftp.h new file mode 100644 index 0000000..e459c16 --- /dev/null +++ b/src/ftp.h @@ -0,0 +1,43 @@ +/* + +ftpii -- an FTP server for the Wii + +Copyright (C) 2008 Joseph Jordan + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1.The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2.Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3.This notice may not be removed or altered from any source distribution. + +*/ +#ifndef _FTP_H_ +#define _FTP_H_ + +#ifdef __cplusplus +extern "C"{ +#endif +#include + +void accept_ftp_client(int32_t server); +void set_ftp_password(char *new_password); +bool process_ftp_events(int32_t server); +void cleanup_ftp(); + +#ifdef __cplusplus +} +#endif + +#endif /* _FTP_H_ */ diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..edc76df --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,138 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utils/logger.h" +#include "utils/utils.h" + +#include "virtualpath.h" +#include "net.h" +#include "BackgroundThread.hpp" + +#define MAX_CONSOLE_LINES_TV 27 +#define MAX_CONSOLE_LINES_DRC 18 + +WUPS_PLUGIN_NAME("FTPiiU"); +WUPS_PLUGIN_DESCRIPTION("FTP Server"); +WUPS_PLUGIN_VERSION("0.1"); +WUPS_PLUGIN_AUTHOR("Maschell"); +WUPS_PLUGIN_LICENSE("GPL"); + +WUPS_USE_WUT_CRT() + +uint32_t hostIpAddress = 0; +int iosuhaxMount = 0; +int fsaFd = 0; + +BackgroundThread * thread = NULL; + +/* Entry point */ +ON_APPLICATION_START(args) { + WHBInitializeSocketLibrary(); + + nn::ac::ConfigIdNum configId; + + nn::ac::Initialize(); + nn::ac::GetStartupId(&configId); + nn::ac::Connect(configId); + + ACGetAssignedAddress(&hostIpAddress); + + log_init(); + + //!******************************************************************* + //! Initialize FS * + //!******************************************************************* + + int fsaFd = -1; + + DEBUG_FUNCTION_LINE("IOSUHAX_Open\n"); + int res = IOSUHAX_Open(NULL); + if(res < 0) { + DEBUG_FUNCTION_LINE("IOSUHAX_open failed\n"); + VirtualMountDevice("fs:/"); + } else { + iosuhaxMount = 1; + //fatInitDefault(); + + DEBUG_FUNCTION_LINE("IOSUHAX_FSA_Open\n"); + fsaFd = IOSUHAX_FSA_Open(); + if(fsaFd < 0) { + DEBUG_FUNCTION_LINE("IOSUHAX_FSA_Open failed\n"); + } + + DEBUG_FUNCTION_LINE("IOSUHAX_FSA_Open\n"); + + mount_fs("slccmpt01", fsaFd, "/dev/slccmpt01", "/vol/storage_slccmpt01"); + mount_fs("storage_odd_tickets", fsaFd, "/dev/odd01", "/vol/storage_odd_tickets"); + mount_fs("storage_odd_updates", fsaFd, "/dev/odd02", "/vol/storage_odd_updates"); + mount_fs("storage_odd_content", fsaFd, "/dev/odd03", "/vol/storage_odd_content"); + mount_fs("storage_odd_content2", fsaFd, "/dev/odd04", "/vol/storage_odd_content2"); + mount_fs("storage_slc", fsaFd, NULL, "/vol/system"); + mount_fs("storage_mlc", fsaFd, NULL, "/vol/storage_mlc01"); + mount_fs("storage_usb", fsaFd, NULL, "/vol/storage_usb01"); + + VirtualMountDevice("fs:/"); + VirtualMountDevice("slccmpt01:/"); + VirtualMountDevice("storage_odd_tickets:/"); + VirtualMountDevice("storage_odd_updates:/"); + VirtualMountDevice("storage_odd_content:/"); + VirtualMountDevice("storage_odd_content2:/"); + VirtualMountDevice("storage_slc:/"); + VirtualMountDevice("storage_mlc:/"); + VirtualMountDevice("storage_usb:/"); + VirtualMountDevice("usb:/"); + } + + thread = BackgroundThread::getInstance(); + DCFlushRange(&thread, 4); +} + +void stopThread(){ + BackgroundThread::destroyInstance(); +} + +ON_APPLICATION_END(){ + DEBUG_FUNCTION_LINE("Ending ftp server\n"); + stopThread(); + + if(iosuhaxMount) { + IOSUHAX_sdio_disc_interface.shutdown(); + IOSUHAX_usb_disc_interface.shutdown(); + + unmount_fs("slccmpt01"); + unmount_fs("storage_odd_tickets"); + unmount_fs("storage_odd_updates"); + unmount_fs("storage_odd_content"); + unmount_fs("storage_odd_content2"); + unmount_fs("storage_slc"); + unmount_fs("storage_mlc"); + unmount_fs("storage_usb"); + IOSUHAX_FSA_Close(fsaFd); + IOSUHAX_Close(); + } + + UnmountVirtualPaths(); +} + diff --git a/src/main.h b/src/main.h new file mode 100644 index 0000000..dfc7038 --- /dev/null +++ b/src/main.h @@ -0,0 +1,32 @@ +#ifndef _MAIN_H_ +#define _MAIN_H_ + +/* Main */ +#ifdef __cplusplus +extern "C" { +#endif +#include +#include + +#define MAXPATHLEN 256 + +#define WIIU_EAGAIN EWOULDBLOCK +#define ENODATA 1 +#define EISCONN 3 +#define EWOULDBLOCK 6 +#define EALREADY 10 +#define EAGAIN EWOULDBLOCK +#define EINVAL 11 +#define ENOMEM 18 +#define EINPROGRESS 22 + +#define wiiu_geterrno() (socketlasterr()) + +//! C wrapper for our C++ functions +int Menu_Main(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/net.c b/src/net.c new file mode 100644 index 0000000..8d85eaa --- /dev/null +++ b/src/net.c @@ -0,0 +1,283 @@ +/* + +Copyright (C) 2008 Joseph Jordan + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1.The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2.Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3.This notice may not be removed or altered from any source distribution. + +*/ +#include +#include +#include +#include +#include +#include +#include +#include "main.h" + +#define MIN(x, y) ((x) < (y) ? (x) : (y)) + +#include "net.h" + +#define MAX_NET_BUFFER_SIZE (64*1024) +#define MIN_NET_BUFFER_SIZE 4096 +#define FREAD_BUFFER_SIZE (64*1024) + +extern uint32_t hostIpAddress; + +static uint32_t NET_BUFFER_SIZE = MAX_NET_BUFFER_SIZE; + +#if 0 +void initialise_network() { + printf("Waiting for network to initialise...\n"); + int32_t result = -1; + while (!check_reset_synchronous() && result < 0) { + net_deinit(); + while (!check_reset_synchronous() && (result = net_init()) == -WIIU_EAGAIN); + if (result < 0) + printf("net_init() failed: [%i] %s, retrying...\n", result, strerror(-result)); + } + if (result >= 0) { + uint32_t ip = 0; + do { + ip = net_gethostip(); + if (!ip) + printf("net_gethostip() failed, retrying...\n"); + } while (!check_reset_synchronous() && !ip); + if (ip) { + struct in_addr addr; + addr.s_addr = ip; + printf("Network initialised. Wii IP address: %s\n", inet_ntoa(addr)); + } + } +} +#endif + +int32_t network_socket(uint32_t domain,uint32_t type,uint32_t protocol) { + int sock = socket(domain, type, protocol); + if(sock < 0) { + int err = -wiiu_geterrno(); + return (err < 0) ? err : sock; + } + return sock; +} + +int32_t network_bind(int32_t s,struct sockaddr *name,int32_t namelen) { + int res = bind(s, name, namelen); + if(res < 0) { + int err = -wiiu_geterrno(); + return (err < 0) ? err : res; + } + return res; +} + +int32_t network_listen(int32_t s,uint32_t backlog) { + int res = listen(s, backlog); + if(res < 0) { + int err = -wiiu_geterrno(); + return (err < 0) ? err : res; + } + return res; +} + +int32_t network_accept(int32_t s,struct sockaddr *addr,int32_t *addrlen) { + int res = accept(s, addr, addrlen); + if(res < 0) { + int err = -wiiu_geterrno(); + return (err < 0) ? err : res; + } + return res; +} + +int32_t network_connect(int32_t s,struct sockaddr *addr, int32_t addrlen) { + int res = connect(s, addr, addrlen); + if(res < 0) { + int err = -wiiu_geterrno(); + return (err < 0) ? err : res; + } + return res; +} + +int32_t network_read(int32_t s,void *mem,int32_t len) { + int res = recv(s, mem, len, 0); + if(res < 0) { + int err = -wiiu_geterrno(); + return (err < 0) ? err : res; + } + return res; +} + +uint32_t network_gethostip() { + return hostIpAddress; +} + +int32_t network_write(int32_t s, const void *mem,int32_t len) { + int32_t transfered = 0; + + while(len) { + int ret = send(s, mem, len, 0); + if(ret < 0) { + int err = -wiiu_geterrno(); + transfered = (err < 0) ? err : ret; + break; + } + + mem += ret; + transfered += ret; + len -= ret; + } + return transfered; +} + +int32_t network_close(int32_t s) { + if(s < 0) + return -1; + + return socketclose(s); +} + +int32_t set_blocking(int32_t s, bool blocking) { + int32_t block = !blocking; + setsockopt(s, SOL_SOCKET, SO_NONBLOCK, &block, sizeof(block)); + return 0; +} + +int32_t network_close_blocking(int32_t s) { + set_blocking(s, true); + return network_close(s); +} + +int32_t create_server(uint16_t port) { + int32_t server = network_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (server < 0) + return -1; + + + set_blocking(server, false); + uint32_t enable = 1; + setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable)); + + struct sockaddr_in bindAddress; + memset(&bindAddress, 0, sizeof(bindAddress)); + bindAddress.sin_family = AF_INET; + bindAddress.sin_port = htons(port); + bindAddress.sin_addr.s_addr = htonl(INADDR_ANY); + + int32_t ret; + if ((ret = network_bind(server, (struct sockaddr *)&bindAddress, sizeof(bindAddress))) < 0) { + network_close(server); + //gxprintf("Error binding socket: [%i] %s\n", -ret, strerror(-ret)); + return ret; + } + if ((ret = network_listen(server, 3)) < 0) { + network_close(server); + //gxprintf("Error listening on socket: [%i] %s\n", -ret, strerror(-ret)); + return ret; + } + + return server; +} + +typedef int32_t (*transferrer_type)(int32_t s, void *mem, int32_t len); +static int32_t transfer_exact(int32_t s, char *buf, int32_t length, transferrer_type transferrer) { + int32_t result = 0; + int32_t remaining = length; + int32_t bytes_transferred; + set_blocking(s, true); + while (remaining) { +try_again_with_smaller_buffer: + bytes_transferred = transferrer(s, buf, MIN(remaining, (int) NET_BUFFER_SIZE)); + if (bytes_transferred > 0) { + remaining -= bytes_transferred; + buf += bytes_transferred; + } else if (bytes_transferred < 0) { + if (bytes_transferred == -EINVAL && NET_BUFFER_SIZE == MAX_NET_BUFFER_SIZE) { + NET_BUFFER_SIZE = MIN_NET_BUFFER_SIZE; + usleep(1000); + goto try_again_with_smaller_buffer; + } + result = bytes_transferred; + break; + } else { + result = -ENODATA; + break; + } + } + set_blocking(s, false); + return result; +} + +int32_t send_exact(int32_t s, char *buf, int32_t length) { + return transfer_exact(s, buf, length, (transferrer_type)network_write); +} + +int32_t send_from_file(int32_t s, FILE *f) { + char * buf = (char *) malloc(FREAD_BUFFER_SIZE); + if(!buf) + return -1; + + int32_t bytes_read; + int32_t result = 0; + + bytes_read = fread(buf, 1, FREAD_BUFFER_SIZE, f); + if (bytes_read > 0) { + result = send_exact(s, buf, bytes_read); + if (result < 0) + goto end; + } + if (bytes_read < FREAD_BUFFER_SIZE) { + result = -!feof(f); + goto end; + } + free(buf); + return -WIIU_EAGAIN; +end: + free(buf); + return result; +} + +int32_t recv_to_file(int32_t s, FILE *f) { + char * buf = (char *) malloc(NET_BUFFER_SIZE); + if(!buf) + return -1; + + int32_t bytes_read; + while (1) { +try_again_with_smaller_buffer: + bytes_read = network_read(s, buf, NET_BUFFER_SIZE); + if (bytes_read < 0) { + if (bytes_read == -EINVAL && NET_BUFFER_SIZE == MAX_NET_BUFFER_SIZE) { + NET_BUFFER_SIZE = MIN_NET_BUFFER_SIZE; + usleep(1000); + goto try_again_with_smaller_buffer; + } + free(buf); + return bytes_read; + } else if (bytes_read == 0) { + free(buf); + return 0; + } + + int32_t bytes_written = fwrite(buf, 1, bytes_read, f); + if (bytes_written < bytes_read) { + free(buf); + return -1; + } + } + return -1; +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 0000000..2b8c553 --- /dev/null +++ b/src/net.h @@ -0,0 +1,63 @@ +/* + +Copyright (C) 2008 Joseph Jordan + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1.The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2.Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3.This notice may not be removed or altered from any source distribution. + +*/ +#ifndef _NET_H_ +#define _NET_H_ + +#ifdef __cplusplus +extern "C"{ +#endif + +#include +#include + +#if 0 +void initialise_network(); +#endif + +int32_t network_socket(uint32_t domain,uint32_t type,uint32_t protocol); +int32_t network_bind(int32_t s,struct sockaddr *name,int32_t namelen); +int32_t network_listen(int32_t s,uint32_t backlog); +int32_t network_accept(int32_t s,struct sockaddr *addr,int32_t *addrlen); +int32_t network_connect(int32_t s,struct sockaddr *,int32_t); +int32_t network_read(int32_t s,void *mem,int32_t len); +int32_t network_close(int32_t s); +uint32_t network_gethostip(); + +int32_t set_blocking(int32_t s, bool blocking); + +int32_t network_close_blocking(int32_t s); + +int32_t create_server(uint16_t port); + +int32_t send_exact(int32_t s, char *buf, int32_t length); + +int32_t send_from_file(int32_t s, FILE *f); + +int32_t recv_to_file(int32_t s, FILE *f); + +#ifdef __cplusplus +} +#endif + +#endif /* _NET_H_ */ diff --git a/src/utils/BackgroundThreadWrapper.cpp b/src/utils/BackgroundThreadWrapper.cpp new file mode 100644 index 0000000..aaff942 --- /dev/null +++ b/src/utils/BackgroundThreadWrapper.cpp @@ -0,0 +1,30 @@ +#include "BackgroundThreadWrapper.hpp" +#include +#include +#include +#include + +#include "logger.h" + +BackgroundThreadWrapper::BackgroundThreadWrapper(int32_t priority): CThread(CThread::eAttributeAffCore2, priority, 0x100000) { +} + +BackgroundThreadWrapper::~BackgroundThreadWrapper() { + exitThread = 1; + DCFlushRange((void*)&exitThread, 4); + DEBUG_FUNCTION_LINE("Exit thread\n"); +} + +void BackgroundThreadWrapper::executeThread() { + while (1) { + if(exitThread) { + DEBUG_FUNCTION_LINE("We want to exit\n"); + break; + } + if(!whileLoop()){ + break; + } + } + DEBUG_FUNCTION_LINE("Exit!\n"); +} + diff --git a/src/utils/BackgroundThreadWrapper.hpp b/src/utils/BackgroundThreadWrapper.hpp new file mode 100644 index 0000000..94ce6a1 --- /dev/null +++ b/src/utils/BackgroundThreadWrapper.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include "CThread.h" +#include "CMutex.h" +#include + +class BackgroundThreadWrapper: public CThread { +public: + BackgroundThreadWrapper(int32_t priority); + virtual ~BackgroundThreadWrapper(); +protected: + BOOL shouldExit() { + return (exitThread == 1); + } + + void setThreadPriority(int32_t priority) { + this->setThreadPriority(priority); + } + CMutex mutex; +private: + void executeThread(); + + /** + Called when a connection has be accepted. + **/ + virtual BOOL whileLoop() = 0; + + volatile int32_t exitThread = 0; + + +}; diff --git a/src/utils/CMutex.h b/src/utils/CMutex.h new file mode 100644 index 0000000..76f23ac --- /dev/null +++ b/src/utils/CMutex.h @@ -0,0 +1,69 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef _CMUTEX_H_ +#define _CMUTEX_H_ + +#include +#include + +class CMutex +{ +public: + CMutex() { + pMutex = (OSMutex*) malloc(sizeof(OSMutex)); + if(!pMutex) + return; + + OSInitMutex(pMutex); + } + virtual ~CMutex() { + if(pMutex) + free(pMutex); + } + + void lock(void) { + if(pMutex) + OSLockMutex(pMutex); + } + void unlock(void) { + if(pMutex) + OSUnlockMutex(pMutex); + } + BOOL tryLock(void) { + if(!pMutex) + return false; + + return (OSTryLockMutex(pMutex) != 0); + } +private: + OSMutex *pMutex; +}; + +class CMutexLock +{ +public: + CMutexLock() { + mutex.lock(); + } + virtual ~CMutexLock() { + mutex.unlock(); + } +private: + CMutex mutex; +}; + +#endif // _CMUTEX_H_ diff --git a/src/utils/CThread.h b/src/utils/CThread.h new file mode 100644 index 0000000..be96e90 --- /dev/null +++ b/src/utils/CThread.h @@ -0,0 +1,134 @@ +/**************************************************************************** + * Copyright (C) 2015 Dimok + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + ****************************************************************************/ +#ifndef CTHREAD_H_ +#define CTHREAD_H_ + +#include +#include +#include +#include +#include "utils/logger.h" + +class CThread { +public: + typedef void (* Callback)(CThread *thread, void *arg); + + //! constructor + CThread(int32_t iAttr, int32_t iPriority = 16, int32_t iStackSize = 0x8000, CThread::Callback callback = NULL, void *callbackArg = NULL) + : pThread(NULL) + , pThreadStack(NULL) + , pCallback(callback) + , pCallbackArg(callbackArg) { + //! save attribute assignment + iAttributes = iAttr; + //! allocate the thread + pThread = (OSThread*)memalign(8, sizeof(OSThread)); + //! allocate the stack + pThreadStack = (uint8_t *) memalign(0x20, iStackSize); + //! create the thread + if(pThread && pThreadStack) + OSCreateThread(pThread, &CThread::threadCallback, 1, (char*)this, pThreadStack+iStackSize, iStackSize, iPriority, iAttributes); + } + + //! destructor + virtual ~CThread() { + shutdownThread(); + DEBUG_FUNCTION_LINE("END\n"); + } + + static CThread *create(CThread::Callback callback, void *callbackArg, int32_t iAttr = eAttributeNone, int32_t iPriority = 16, int32_t iStackSize = 0x8000) { + return ( new CThread(iAttr, iPriority, iStackSize, callback, callbackArg) ); + } + + //! Get thread ID + virtual void* getThread() const { + return pThread; + } + //! Thread entry function + virtual void executeThread(void) { + if(pCallback) + pCallback(this, pCallbackArg); + } + //! Suspend thread + virtual void suspendThread(void) { + if(isThreadSuspended()) return; + if(pThread) OSSuspendThread(pThread); + } + //! Resume thread + virtual void resumeThread(void) { + if(!isThreadSuspended()) return; + if(pThread) OSResumeThread(pThread); + } + //! Set thread priority + virtual void setThreadPriority(int prio) { + if(pThread) OSSetThreadPriority(pThread, prio); + } + //! Check if thread is suspended + virtual BOOL isThreadSuspended(void) const { + if(pThread) return OSIsThreadSuspended(pThread); + return false; + } + //! Check if thread is terminated + virtual BOOL isThreadTerminated(void) const { + if(pThread) return OSIsThreadTerminated(pThread); + return false; + } + //! Check if thread is running + virtual BOOL isThreadRunning(void) const { + return !isThreadSuspended() && !isThreadRunning(); + } + //! Shutdown thread + virtual void shutdownThread(void) { + //! wait for thread to finish + if(pThread && !(iAttributes & eAttributeDetach)) { + if(isThreadSuspended()) + resumeThread(); + + OSJoinThread(pThread, NULL); + } + //! free the thread stack buffer + if(pThreadStack) + free(pThreadStack); + if(pThread) + free(pThread); + + pThread = NULL; + pThreadStack = NULL; + } + //! Thread attributes + enum eCThreadAttributes { + eAttributeNone = 0x07, + eAttributeAffCore0 = 0x01, + eAttributeAffCore1 = 0x02, + eAttributeAffCore2 = 0x04, + eAttributeDetach = 0x08, + eAttributePinnedAff = 0x10 + }; +private: + static int threadCallback(int argc, const char **argv) { + //! After call to start() continue with the internal function + ((CThread *) argv)->executeThread(); + return 0; + } + int iAttributes; + OSThread *pThread; + uint8_t *pThreadStack; + Callback pCallback; + void *pCallbackArg; +}; + +#endif diff --git a/src/utils/logger.c b/src/utils/logger.c new file mode 100644 index 0000000..0922230 --- /dev/null +++ b/src/utils/logger.c @@ -0,0 +1,82 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +static int log_socket __attribute__((section(".data")))= -1; +static struct sockaddr_in connect_addr __attribute__((section(".data"))); +static volatile int log_lock __attribute__((section(".data"))) = 0; + +void log_init_() { + int broadcastEnable = 1; + log_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (log_socket < 0) + return; + + setsockopt(log_socket, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, sizeof(broadcastEnable)); + + memset(&connect_addr, 0, sizeof(struct sockaddr_in)); + connect_addr.sin_family = AF_INET; + connect_addr.sin_port = 4405; + connect_addr.sin_addr.s_addr = htonl(INADDR_BROADCAST); +} + +void log_print_(const char *str) { + // socket is always 0 initially as it is in the BSS + if(log_socket < 0) { + return; + } + + while(log_lock) + OSSleepTicks(OSMicrosecondsToTicks(1000)); + log_lock = 1; + + int len = strlen(str); + int ret; + while (len > 0) { + int block = len < 1400 ? len : 1400; // take max 1400 bytes per UDP packet + ret = sendto(log_socket, str, block, 0, (struct sockaddr *)&connect_addr, sizeof(struct sockaddr_in)); + if(ret < 0) + break; + + len -= ret; + str += ret; + } + + log_lock = 0; +} + +void OSFatal_printf(const char *format, ...) { + char tmp[512]; + tmp[0] = 0; + va_list va; + va_start(va, format); + if((vsprintf(tmp, format, va) >= 0)) { + OSFatal(tmp); + } + va_end(va); +} + +void log_printf_(const char *format, ...) { + if(log_socket < 0) { + return; + } + + char tmp[512]; + tmp[0] = 0; + + va_list va; + va_start(va, format); + if((vsprintf(tmp, format, va) >= 0)) { + log_print_(tmp); + } + va_end(va); +} + diff --git a/src/utils/logger.h b/src/utils/logger.h new file mode 100644 index 0000000..d026b05 --- /dev/null +++ b/src/utils/logger.h @@ -0,0 +1,38 @@ +#ifndef __LOGGER_H_ +#define __LOGGER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +void log_init_(); +//void log_deinit_(void); +void log_print_(const char *str); +void log_printf_(const char *format, ...); +void OSFatal_printf(const char *format, ...); + +#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__) +#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__) + +#define OSFATAL_FUNCTION_LINE(FMT, ARGS...)do { \ + OSFatal_printf("[%s]%s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0) + + + +#define log_init() log_init_() +//#define log_deinit() log_deinit_() +#define log_print(str) log_print_(str) +#define log_printf(FMT, ARGS...) log_printf_(FMT, ## ARGS); + +#define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \ + log_printf("[%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \ + } while (0) + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/utils/utils.c b/src/utils/utils.c new file mode 100644 index 0000000..0042415 --- /dev/null +++ b/src/utils/utils.c @@ -0,0 +1,41 @@ +#include +#include +#include +#include +#include +#include +#include + +// https://gist.github.com/ccbrown/9722406 +void dumpHex(const void* data, size_t size) { + char ascii[17]; + size_t i, j; + ascii[16] = '\0'; + DEBUG_FUNCTION_LINE("0x%08X (0x0000): ", data); + for (i = 0; i < size; ++i) { + log_printf("%02X ", ((unsigned char*)data)[i]); + if (((unsigned char*)data)[i] >= ' ' && ((unsigned char*)data)[i] <= '~') { + ascii[i % 16] = ((unsigned char*)data)[i]; + } else { + ascii[i % 16] = '.'; + } + if ((i+1) % 8 == 0 || i+1 == size) { + log_printf(" "); + if ((i+1) % 16 == 0) { + log_printf("| %s \n", ascii); + if(i + 1 < size) { + DEBUG_FUNCTION_LINE("0x%08X (0x%04X); ", data + i + 1,i+1); + } + } else if (i+1 == size) { + ascii[(i+1) % 16] = '\0'; + if ((i+1) % 16 <= 8) { + log_printf(" "); + } + for (j = (i+1) % 16; j < 16; ++j) { + log_printf(" "); + } + log_printf("| %s \n", ascii); + } + } + } +} diff --git a/src/utils/utils.h b/src/utils/utils.h new file mode 100644 index 0000000..26caaaf --- /dev/null +++ b/src/utils/utils.h @@ -0,0 +1,35 @@ +#ifndef __UTILS_H_ +#define __UTILS_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define LIMIT(x, min, max) \ + ({ \ + typeof( x ) _x = x; \ + typeof( min ) _min = min; \ + typeof( max ) _max = max; \ + ( ( ( _x ) < ( _min ) ) ? ( _min ) : ( ( _x ) > ( _max ) ) ? ( _max) : ( _x ) ); \ +}) + +#define DegToRad(a) ( (a) * 0.01745329252f ) +#define RadToDeg(a) ( (a) * 57.29577951f ) + +#define ALIGN4(x) (((x) + 3) & ~3) +#define ALIGN32(x) (((x) + 31) & ~31) + +#define le16(i) ((((uint16_t) ((i) & 0xFF)) << 8) | ((uint16_t) (((i) & 0xFF00) >> 8))) +#define le32(i) ((((uint32_t)le16((i) & 0xFFFF)) << 16) | ((uint32_t)le16(((i) & 0xFFFF0000) >> 16))) +#define le64(i) ((((uint64_t)le32((i) & 0xFFFFFFFFLL)) << 32) | ((uint64_t)le32(((i) & 0xFFFFFFFF00000000LL) >> 32))) + +//Needs to have log_init() called beforehand. +void dumpHex(const void* data, size_t size); + +#ifdef __cplusplus +} +#endif + +#endif // __UTILS_H_ diff --git a/src/virtualpath.c b/src/virtualpath.c new file mode 100644 index 0000000..ea1508e --- /dev/null +++ b/src/virtualpath.c @@ -0,0 +1,125 @@ + /**************************************************************************** + * Copyright (C) 2008 + * Joseph Jordan + * + * Copyright (C) 2010 + * by Dimok + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#include +#include +#include "virtualpath.h" +#include "utils/logger.h" + +uint8_t MAX_VIRTUAL_PARTITIONS = 0; +VIRTUAL_PARTITION * VIRTUAL_PARTITIONS = NULL; + +void VirtualMountDevice(const char * path) +{ + if(!path) + return; + + int i = 0; + char name[255]; + char alias[255]; + char prefix[255]; + bool namestop = false; + + alias[0] = '/'; + + do + { + if(path[i] == ':') + namestop = true; + + if(!namestop) + { + name[i] = path[i]; + name[i+1] = '\0'; + alias[i+1] = path[i]; + alias[i+2] = '\0'; + } + + prefix[i] = path[i]; + prefix[i+1] = '\0'; + i++; + } + while(path[i-1] != '/'); + AddVirtualPath(name, alias, prefix); +} + +void AddVirtualPath(const char *name, const char *alias, const char *prefix) +{ + if(!VIRTUAL_PARTITIONS) + VIRTUAL_PARTITIONS = (VIRTUAL_PARTITION *) malloc(sizeof(VIRTUAL_PARTITION)); + + VIRTUAL_PARTITION * tmp = realloc(VIRTUAL_PARTITIONS, sizeof(VIRTUAL_PARTITION)*(MAX_VIRTUAL_PARTITIONS+1)); + if(!tmp) + { + free(VIRTUAL_PARTITIONS); + MAX_VIRTUAL_PARTITIONS = 0; + return; + } + + VIRTUAL_PARTITIONS = tmp; + + VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].name = strdup(name); + VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].alias = strdup(alias); + VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].prefix = strdup(prefix); + VIRTUAL_PARTITIONS[MAX_VIRTUAL_PARTITIONS].inserted = true; + + MAX_VIRTUAL_PARTITIONS++; +} + +void MountVirtualDevices() +{ + VirtualMountDevice("fs:/"); + VirtualMountDevice("slccmpt01:/"); + VirtualMountDevice("storage_odd_tickets:/"); + VirtualMountDevice("storage_odd_updates:/"); + VirtualMountDevice("storage_odd_content:/"); + VirtualMountDevice("storage_odd_content2:/"); + VirtualMountDevice("storage_slc:/"); + VirtualMountDevice("storage_mlc:/"); + VirtualMountDevice("storage_usb:/"); + VirtualMountDevice("usb:/"); +} + +void UnmountVirtualPaths() +{ + uint32_t i = 0; + for(i = 0; i < MAX_VIRTUAL_PARTITIONS; i++) + { + if(VIRTUAL_PARTITIONS[i].name) + free(VIRTUAL_PARTITIONS[i].name); + if(VIRTUAL_PARTITIONS[i].alias) + free(VIRTUAL_PARTITIONS[i].alias); + if(VIRTUAL_PARTITIONS[i].prefix) + free(VIRTUAL_PARTITIONS[i].prefix); + } + + if(VIRTUAL_PARTITIONS) + free(VIRTUAL_PARTITIONS); + VIRTUAL_PARTITIONS = NULL; + MAX_VIRTUAL_PARTITIONS = 0; +} diff --git a/src/virtualpath.h b/src/virtualpath.h new file mode 100644 index 0000000..e884867 --- /dev/null +++ b/src/virtualpath.h @@ -0,0 +1,59 @@ + /**************************************************************************** + * Copyright (C) 2010 + * by Dimok + * + * Original VIRTUAL_PART Struct + * Copyright (C) 2008 + * Joseph Jordan + * + * This software is provided 'as-is', without any express or implied + * warranty. In no event will the authors be held liable for any + * damages arising from the use of this software. + * + * Permission is granted to anyone to use this software for any + * purpose, including commercial applications, and to alter it and + * redistribute it freely, subject to the following restrictions: + * + * 1. The origin of this software must not be misrepresented; you + * must not claim that you wrote the original software. If you use + * this software in a product, an acknowledgment in the product + * documentation would be appreciated but is not required. + * + * 2. Altered source versions must be plainly marked as such, and + * must not be misrepresented as being the original software. + * + * 3. This notice may not be removed or altered from any source + * distribution. + * + * for WiiXplorer 2010 + ***************************************************************************/ +#ifndef _VIRTUALPATH_H_ +#define _VIRTUALPATH_H_ + +#ifdef __cplusplus +extern "C"{ +#endif + +#include +#include + +typedef struct { + char *name; + char *alias; + char *prefix; + bool inserted; +} VIRTUAL_PARTITION; + +extern VIRTUAL_PARTITION * VIRTUAL_PARTITIONS; +extern uint8_t MAX_VIRTUAL_PARTITIONS; + +void VirtualMountDevice(const char * devicepath); +void AddVirtualPath(const char *name, const char *alias, const char *prefix); +void MountVirtualDevices(); +void UnmountVirtualPaths(); + +#ifdef __cplusplus +} +#endif + +#endif /* _VIRTUALPART_H_ */ diff --git a/src/vrt.c b/src/vrt.c new file mode 100644 index 0000000..3d11fb5 --- /dev/null +++ b/src/vrt.c @@ -0,0 +1,346 @@ +/* + +Copyright (C) 2008 Joseph Jordan +This work is derived from Daniel Ehlers' srg_vrt branch. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1.The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2.Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3.This notice may not be removed or altered from any source distribution. + +*/ +#include +#include +#include +#include +#include +#include +#include "main.h" + +#include "virtualpath.h" +#include "vrt.h" + +static char *virtual_abspath(char *virtual_cwd, char *virtual_path) { + char *path; + if (virtual_path[0] == '/') { + path = virtual_path; + } else { + size_t path_size = strlen(virtual_cwd) + strlen(virtual_path) + 1; + if (path_size > MAXPATHLEN || !(path = malloc(path_size))) return NULL; + strcpy(path, virtual_cwd); + strcat(path, virtual_path); + } + + char *normalised_path = malloc(strlen(path) + 1); + if (!normalised_path) goto end; + *normalised_path = '\0'; + char *curr_dir = normalised_path; + + uint32_t state = 0; // 0:start, 1:slash, 2:dot, 3:dotdot + char *token = path; + while (1) { + switch (state) { + case 0: + if (*token == '/') { + state = 1; + curr_dir = normalised_path + strlen(normalised_path); + strncat(normalised_path, token, 1); + } + break; + case 1: + if (*token == '.') state = 2; + else if (*token != '/') state = 0; + break; + case 2: + if (*token == '/' || !*token) { + state = 1; + *(curr_dir + 1) = '\0'; + } else if (*token == '.') state = 3; + else state = 0; + break; + case 3: + if (*token == '/' || !*token) { + state = 1; + *curr_dir = '\0'; + char *prev_dir = strrchr(normalised_path, '/'); + if (prev_dir) curr_dir = prev_dir; + else *curr_dir = '/'; + *(curr_dir + 1) = '\0'; + } else state = 0; + break; + } + if (!*token) break; + if (state == 0 || *token != '/') strncat(normalised_path, token, 1); + token++; + } + + uint32_t end = strlen(normalised_path); + while (end > 1 && normalised_path[end - 1] == '/') { + normalised_path[--end] = '\x00'; + } + + end: + if (path != virtual_path) free(path); + return normalised_path; +} + +/* + Converts a client-visible path to a real absolute path + E.g. "/sd/foo" -> "sd:/foo" + "/sd" -> "sd:/" + "/sd/../usb" -> "usb:/" + The resulting path will fit in an array of size MAXPATHLEN + Returns NULL to indicate that the client-visible path is invalid +*/ +char *to_real_path(char *virtual_cwd, char *virtual_path) { + errno = ENOENT; + if (strchr(virtual_path, ':')) { + return NULL; // colon is not allowed in virtual path, i've decided =P + } + + virtual_path = virtual_abspath(virtual_cwd, virtual_path); + if (!virtual_path) return NULL; + + char *path = NULL; + char *rest = virtual_path; + + if (!strcmp("/", virtual_path)) { + // indicate vfs-root with "" + path = ""; + goto end; + } + + const char *prefix = NULL; + uint32_t i; + for (i = 0; i < MAX_VIRTUAL_PARTITIONS; i++) { + VIRTUAL_PARTITION *partition = VIRTUAL_PARTITIONS + i; + const char *alias = partition->alias; + size_t alias_len = strlen(alias); + if (!strcasecmp(alias, virtual_path) || (!strncasecmp(alias, virtual_path, alias_len) && virtual_path[alias_len] == '/')) { + prefix = partition->prefix; + rest += alias_len; + if (*rest == '/') rest++; + break; + } + } + if (!prefix) { + errno = ENODEV; + goto end; + } + + size_t real_path_size = strlen(prefix) + strlen(rest) + 1; + if (real_path_size > MAXPATHLEN) goto end; + + path = malloc(real_path_size); + if (!path) goto end; + strcpy(path, prefix); + strcat(path, rest); + + end: + free(virtual_path); + return path; +} + +static int checkdir(char *path) { + DIR *dir = opendir(path); + if(dir) + { + closedir(dir); + return 0; + } + return -1; +} + +typedef void * (*path_func)(char *path, ...); + +static void *with_virtual_path(void *virtual_cwd, void *void_f, char *virtual_path, int32_t failed, ...) { + char *path = to_real_path(virtual_cwd, virtual_path); + if (!path || !*path) return (void *)failed; + + path_func f = (path_func)void_f; + va_list ap; + void *args[3]; + unsigned int num_args = 0; + va_start(ap, failed); + do { + void *arg = va_arg(ap, void *); + if (!arg) break; + args[num_args++] = arg; + } while (1); + va_end(ap); + + void *result; + switch (num_args) { + case 0: result = f(path); break; + case 1: result = f(path, args[0]); break; + case 2: result = f(path, args[0], args[1]); break; + case 3: result = f(path, args[0], args[1], args[2]); break; + default: result = (void *)failed; break; + } + + free(path); + return result; +} + +FILE *vrt_fopen(char *cwd, char *path, char *mode) { + return with_virtual_path(cwd, fopen, path, 0, mode, NULL); +} + +int vrt_stat(char *cwd, char *path, struct stat *st) { + char *real_path = to_real_path(cwd, path); + if (!real_path) + { + return -1; + } + else if (!*real_path || (strcmp(path, ".") == 0) || (strlen(cwd) == 1) || ((strlen(cwd) > 1) && (strcmp(path, "..") == 0))) + { + st->st_mode = S_IFDIR; + st->st_size = 31337; + return 0; + } + free(real_path); + return (int)with_virtual_path(cwd, stat, path, -1, st, NULL); +} + +static int vrt_checkdir(char *cwd, char *path) { + char *real_path = to_real_path(cwd, path); + if (!real_path) + { + return -1; + } + else if (!*real_path || (strcmp(path, ".") == 0) || (strlen(cwd) == 1) || ((strlen(cwd) > 1) && (strcmp(path, "..") == 0))) + { + return 0; + } + free(real_path); + return (int)with_virtual_path(cwd, checkdir, path, -1, NULL); +} + +int vrt_chdir(char *cwd, char *path) { + + if (vrt_checkdir(cwd, path)) { + return -1; + } + char *abspath = virtual_abspath(cwd, path); + if (!abspath) { + errno = ENOMEM; + return -1; + } + strcpy(cwd, abspath); + if (cwd[1]) strcat(cwd, "/"); + free(abspath); + return 0; +} + +int vrt_unlink(char *cwd, char *path) { + return (int)with_virtual_path(cwd, unlink, path, -1, NULL); +} + +int vrt_mkdir(char *cwd, char *path, mode_t mode) { + return (int)with_virtual_path(cwd, mkdir, path, -1, mode, NULL); +} + +int vrt_rename(char *cwd, char *from_path, char *to_path) { + char *real_to_path = to_real_path(cwd, to_path); + if (!real_to_path || !*real_to_path) return -1; + int result = (int)with_virtual_path(cwd, rename, from_path, -1, real_to_path, NULL); + free(real_to_path); + return result; +} + +/* + When in vfs-root this creates a fake DIR_ITER. + */ +DIR_P *vrt_opendir(char *cwd, char *path) +{ + char *real_path = to_real_path(cwd, path); + if (!real_path) return NULL; + + DIR_P *iter = malloc(sizeof(DIR_P)); + if (!iter) + { + if (*real_path != 0) + free(real_path); + return NULL; + } + + iter->virt_root = 0; + iter->path = real_path; + + if (*iter->path == 0) { + iter->dir = malloc(sizeof(DIR)); + if(!iter->dir) { + // root path is not allocated + free(iter); + return NULL; + } + memset(iter->dir, 0, sizeof(DIR)); + iter->virt_root = 1; // we are at the virtual root + return iter; + } + + iter->dir = with_virtual_path(cwd, opendir, path, 0, NULL); + if(!iter->dir) + { + free(iter->path); + free(iter); + return NULL; + } + + return iter; +} + +/* + Yields virtual aliases when pDir->virt_root + */ +struct dirent *vrt_readdir(DIR_P *pDir) { + if(!pDir || !pDir->dir) return NULL; + + DIR *iter = pDir->dir; + if (pDir->virt_root) { + for (; (uint32_t)iter->position < MAX_VIRTUAL_PARTITIONS; iter->position++) { + VIRTUAL_PARTITION *partition = VIRTUAL_PARTITIONS + (int)iter->position; + if (partition->inserted) { + iter->fileData.d_type = DT_DIR; + strcpy(iter->fileData.d_name, partition->alias + 1); + iter->position++; + return &iter->fileData; + } + } + return NULL; + } + return readdir(iter); +} + +int vrt_closedir(DIR_P *iter) { + if(!iter) return -1; + + if(iter->dir) + { + if (iter->virt_root) + free(iter->dir); + else + closedir(iter->dir); + } + + // root path is not allocated + if(iter->path && *iter->path != 0) + free(iter->path); + + free(iter); + + return 0; +} diff --git a/src/vrt.h b/src/vrt.h new file mode 100644 index 0000000..6c4735e --- /dev/null +++ b/src/vrt.h @@ -0,0 +1,58 @@ +/* + +Copyright (C) 2008 Joseph Jordan +This work is derived from Daniel Ehlers' srg_vrt branch. + +This software is provided 'as-is', without any express or implied warranty. +In no event will the authors be held liable for any damages arising from +the use of this software. + +Permission is granted to anyone to use this software for any purpose, +including commercial applications, and to alter it and redistribute it +freely, subject to the following restrictions: + +1.The origin of this software must not be misrepresented; you must not +claim that you wrote the original software. If you use this software in a +product, an acknowledgment in the product documentation would be +appreciated but is not required. + +2.Altered source versions must be plainly marked as such, and must not be +misrepresented as being the original software. + +3.This notice may not be removed or altered from any source distribution. + +*/ +#ifndef _VRT_H_ +#define _VRT_H_ + +#ifdef __cplusplus +extern "C"{ +#endif + +#include +#include + +typedef struct +{ + DIR *dir; + char *path; + uint8_t virt_root; +} DIR_P; + +char *to_real_path(char *virtual_cwd, char *virtual_path); + +FILE *vrt_fopen(char *cwd, char *path, char *mode); +int vrt_stat(char *cwd, char *path, struct stat *st); +int vrt_chdir(char *cwd, char *path); +int vrt_unlink(char *cwd, char *path); +int vrt_mkdir(char *cwd, char *path, mode_t mode); +int vrt_rename(char *cwd, char *from_path, char *to_path); +DIR_P *vrt_opendir(char *cwd, char *path); +struct dirent *vrt_readdir(DIR_P *iter); +int vrt_closedir(DIR_P *iter); + +#ifdef __cplusplus +} +#endif + +#endif /* _VRT_H_ */