Merge branch 'master' of https://github.com/mtheall/ftpd into upstream_changes

This commit is contained in:
Maschell 2024-11-23 09:13:11 +01:00
parent 00191956b8
commit 618b2fb136
29 changed files with 3243 additions and 437 deletions

View File

@ -18,6 +18,8 @@ jobs:
needs: clang-format needs: clang-format
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
submodules: true
- name: create version.h - name: create version.h
run: | run: |
git_hash=$(git rev-parse --short "$GITHUB_SHA") git_hash=$(git rev-parse --short "$GITHUB_SHA")

View File

@ -15,6 +15,8 @@ jobs:
needs: clang-format needs: clang-format
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
with:
submodules: true
- name: create version.h - name: create version.h
run: | run: |
git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}") git_hash=$(git rev-parse --short "${{ github.event.pull_request.head.sha }}")

3
.gitignore vendored
View File

@ -12,6 +12,7 @@
*.nso *.nso
*.pfs0 *.pfs0
*.smdh *.smdh
*~
.gdb_history .gdb_history
3ds/build 3ds/build
3ds-classic/build 3ds-classic/build
@ -24,7 +25,7 @@ switch-classic/build
switch/romfs/*.zst switch/romfs/*.zst
switch/romfs/shaders/*.dksh switch/romfs/shaders/*.dksh
.idea/ .idea/
build/ build*/
*.rpx *.rpx
*.wuhb *.wuhb
*.wps *.wps

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "3rd/gls"]
path = 3rd/gls
url = git@github.com:microsoft/GSL.git

1
3rd/gls Submodule

@ -0,0 +1 @@
Subproject commit a3534567187d2edc428efd3f13466ff75fe5805c

View File

@ -22,9 +22,9 @@ WUPS_ROOT := $(DEVKITPRO)/wups
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
TARGET := ftpiiu TARGET := ftpiiu
BUILD := build BUILD := build
SOURCES := source source/wiiu SOURCES := source source/wiiu source/posix
DATA := data DATA := data
INCLUDES := source include INCLUDES := source include 3rd/gls/include
#------------------------------------------------------------------------------- #-------------------------------------------------------------------------------
# options for code generation # options for code generation

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -22,6 +22,8 @@
#include "ioBuffer.h" #include "ioBuffer.h"
#include <gsl/gsl>
#include <dirent.h> #include <dirent.h>
#include <cstdint> #include <cstdint>
@ -29,6 +31,7 @@
#include <cstdlib> #include <cstdlib>
#include <memory> #include <memory>
#include <string_view> #include <string_view>
#include <vector>
namespace fs namespace fs
{ {
@ -69,7 +72,7 @@ public:
/// \brief Open file /// \brief Open file
/// \param path_ Path to open /// \param path_ Path to open
/// \param mode_ Access mode (\sa std::fopen) /// \param mode_ Access mode (\sa std::fopen)
bool open (char const *path_, char const *mode_ = "rb"); bool open (gsl::not_null<gsl::czstring> path_, gsl::not_null<gsl::czstring> mode_ = "rb");
/// \brief Close file /// \brief Close file
void close (); void close ();
@ -77,13 +80,13 @@ public:
/// \brief Seek to file position /// \brief Seek to file position
/// \param pos_ File position /// \param pos_ File position
/// \param origin_ Reference position (\sa std::fseek) /// \param origin_ Reference position (\sa std::fseek)
std::make_signed_t<std::size_t> seek (std::size_t pos_, int origin_); std::make_signed_t<std::size_t> seek (std::make_signed_t<std::size_t> pos_, int origin_);
/// \brief Read data /// \brief Read data
/// \param buffer_ Output buffer /// \param buffer_ Output buffer
/// \param size_ Size to read /// \param size_ Size to read
/// \note Can return partial reads /// \note Can return partial reads
std::make_signed_t<std::size_t> read (void *buffer_, std::size_t size_); std::make_signed_t<std::size_t> read (gsl::not_null<void *> buffer_, std::size_t size_);
/// \brief Read data /// \brief Read data
/// \param buffer_ Output buffer /// \param buffer_ Output buffer
@ -97,13 +100,13 @@ public:
/// \param buffer_ Output buffer /// \param buffer_ Output buffer
/// \param size_ Size to read /// \param size_ Size to read
/// \note Fails on partial reads and errors /// \note Fails on partial reads and errors
bool readAll (void *buffer_, std::size_t size_); bool readAll (gsl::not_null<void *> buffer_, std::size_t size_);
/// \brief Write data /// \brief Write data
/// \param buffer_ Input data /// \param buffer_ Input data
/// \param size_ Size to write /// \param size_ Size to write
/// \note Can return partial writes /// \note Can return partial writes
std::make_signed_t<std::size_t> write (void const *buffer_, std::size_t size_); std::make_signed_t<std::size_t> write (gsl::not_null<void const *> buffer_, std::size_t size_);
/// \brief Write data /// \brief Write data
/// \param buffer_ Input data /// \param buffer_ Input data
@ -114,20 +117,17 @@ public:
/// \param buffer_ Input data /// \param buffer_ Input data
/// \param size_ Size to write /// \param size_ Size to write
/// \note Fails on partials writes and errors /// \note Fails on partials writes and errors
bool writeAll (void const *buffer_, std::size_t size_); bool writeAll (gsl::not_null<void const *> buffer_, std::size_t size_);
private: private:
/// \brief Underlying std::FILE* /// \brief Underlying std::FILE*
std::unique_ptr<std::FILE, int (*) (std::FILE *)> m_fp{nullptr, nullptr}; std::unique_ptr<std::FILE, int (*) (std::FILE *)> m_fp{nullptr, nullptr};
/// \brief Buffer /// \brief Buffer
std::unique_ptr<char[]> m_buffer; std::vector<char> m_buffer;
/// \brief Buffer size
std::size_t m_bufferSize = 0;
/// \brief Line buffer /// \brief Line buffer
char *m_lineBuffer = nullptr; gsl::owner<char *> m_lineBuffer = nullptr;
/// \brief Line buffer size /// \brief Line buffer size
std::size_t m_lineBufferSize = 0; std::size_t m_lineBufferSize = 0;
@ -161,14 +161,14 @@ public:
/// \brief Open directory /// \brief Open directory
/// \param path_ Path to open /// \param path_ Path to open
bool open (char const *const path_); bool open (gsl::not_null<gsl::czstring> path_);
/// \brief Close directory /// \brief Close directory
void close (); void close ();
/// \brief Read a directory entry /// \brief Read a directory entry
/// \note Returns nullptr on end-of-directory or error; check errno /// \note Returns nullptr on end-of-directory or error; check errno
struct dirent *read (); dirent *read ();
private: private:
/// \brief Underlying DIR* /// \brief Underlying DIR*

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2022 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -22,10 +22,13 @@
#include "platform.h" #include "platform.h"
#include <gsl/gsl>
#include <cstdint> #include <cstdint>
#include <memory> #include <memory>
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <string_view>
class FtpConfig; class FtpConfig;
using UniqueFtpConfig = std::unique_ptr<FtpConfig>; using UniqueFtpConfig = std::unique_ptr<FtpConfig>;
@ -41,15 +44,15 @@ public:
/// \brief Load config /// \brief Load config
/// \param path_ Path to config file /// \param path_ Path to config file
static UniqueFtpConfig load (char const *path_); static UniqueFtpConfig load (gsl::not_null<gsl::czstring> path_);
#ifndef NDS #ifndef __NDS__
std::scoped_lock<platform::Mutex> lockGuard (); std::scoped_lock<platform::Mutex> lockGuard ();
#endif #endif
/// \brief Save config /// \brief Save config
/// \param path_ Path to config file /// \param path_ Path to config file
bool save (char const *path_); bool save (gsl::not_null<gsl::czstring> path_);
/// \brief Get user /// \brief Get user
std::string const &user () const; std::string const &user () const;
@ -57,6 +60,9 @@ public:
/// \brief Get password /// \brief Get password
std::string const &pass () const; std::string const &pass () const;
/// \brief Get hostname
std::string const &hostname () const;
/// \brief Get port /// \brief Get port
std::uint16_t port () const; std::uint16_t port () const;
@ -79,15 +85,19 @@ public:
/// \brief Set user /// \brief Set user
/// \param user_ User /// \param user_ User
void setUser (std::string const &user_); void setUser (std::string user_);
/// \brief Set password /// \brief Set password
/// \param pass_ Password /// \param pass_ Password
void setPass (std::string const &pass_); void setPass (std::string pass_);
/// \brief Set hostname
/// \param hostname_ Hostname
void setHostname (std::string hostname_);
/// \brief Set listen port /// \brief Set listen port
/// \param port_ Listen port /// \param port_ Listen port
bool setPort (std::string const &port_); bool setPort (std::string_view port_);
/// \brief Set listen port /// \brief Set listen port
/// \param port_ Listen port /// \param port_ Listen port
@ -106,17 +116,17 @@ public:
/// \brief Set access point SSID /// \brief Set access point SSID
/// \param ssid_ SSID /// \param ssid_ SSID
void setSSID (std::string const &ssid_); void setSSID (std::string_view ssid_);
/// \brief Set access point passphrase /// \brief Set access point passphrase
/// \param passphrase_ Passphrase /// \param passphrase_ Passphrase
void setPassphrase (std::string const &passphrase_); void setPassphrase (std::string_view passphrase_);
#endif #endif
private: private:
FtpConfig (); FtpConfig ();
#ifndef NDS #ifndef __NDS__
/// \brief Mutex /// \brief Mutex
mutable platform::Mutex m_lock; mutable platform::Mutex m_lock;
#endif #endif
@ -127,6 +137,9 @@ private:
/// \brief Password /// \brief Password
std::string m_pass; std::string m_pass;
/// \brief Hostname
std::string m_hostname;
/// \brief Listen port /// \brief Listen port
std::uint16_t m_port; std::uint16_t m_port;

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2022 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -48,6 +48,9 @@ public:
/// \brief Draw server and all of its sessions /// \brief Draw server and all of its sessions
void draw (); void draw ();
/// \brief Whether server wants to quit
bool quit ();
/// \brief Create server /// \brief Create server
static UniqueFtpServer create (); static UniqueFtpServer create ();
@ -60,6 +63,11 @@ public:
/// \brief Server start time /// \brief Server start time
static std::time_t startTime (); static std::time_t startTime ();
#ifdef __3DS__
/// \brief Get timezone offset in seconds (only used on 3DS)
static int tzOffset ();
#endif
private: private:
/// \brief Paramterized constructor /// \brief Paramterized constructor
/// \param config_ FTP config /// \param config_ FTP config
@ -88,7 +96,7 @@ private:
/// \brief Thread entry point /// \brief Thread entry point
void threadFunc (); void threadFunc ();
#ifndef NDS #ifndef __NDS__
/// \brief Thread /// \brief Thread
platform::Thread m_thread; platform::Thread m_thread;
@ -102,6 +110,11 @@ private:
/// \brief Listen socket /// \brief Listen socket
UniqueSocket m_socket; UniqueSocket m_socket;
#ifndef __NDS__
/// \brief mDNS socket
UniqueSocket m_mdnsSocket;
#endif
/// \brief ImGui window name /// \brief ImGui window name
std::string m_name; std::string m_name;
@ -109,7 +122,7 @@ private:
std::vector<UniqueFtpSession> m_sessions; std::vector<UniqueFtpSession> m_sessions;
/// \brief Whether thread should quit /// \brief Whether thread should quit
std::atomic<bool> m_quit; std::atomic_bool m_quit = false;
#ifndef CLASSIC #ifndef CLASSIC
/// \brief Log upload cURL context /// \brief Log upload cURL context
@ -143,8 +156,11 @@ private:
/// \brief Password setting /// \brief Password setting
std::string m_passSetting; std::string m_passSetting;
/// \brief Hostname setting
std::string m_hostnameSetting;
/// \brief Port setting /// \brief Port setting
std::uint16_t m_portSetting; std::uint16_t m_portSetting = 0;
#ifdef __3DS__ #ifdef __3DS__
/// \brief getMTime setting /// \brief getMTime setting
@ -153,7 +169,7 @@ private:
#ifdef __SWITCH__ #ifdef __SWITCH__
/// \brief Whether an error occurred enabling access point /// \brief Whether an error occurred enabling access point
std::atomic<bool> m_apError = false; std::atomic_bool m_apError = false;
/// \brief Enable access point setting /// \brief Enable access point setting
bool m_enableAPSetting; bool m_enableAPSetting;

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2023 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -26,8 +26,20 @@
#include "platform.h" #include "platform.h"
#include "socket.h" #include "socket.h"
#if __has_include(<glob.h>)
#include <glob.h>
#define FTPD_HAS_GLOB 1
#else
#define FTPD_HAS_GLOB 0
#endif
#include <sys/stat.h>
using stat_t = struct stat;
#include <chrono> #include <chrono>
#include <ctime>
#include <memory> #include <memory>
#include <optional>
#include <string_view> #include <string_view>
#include <utility> #include <utility>
#include <vector> #include <vector>
@ -47,6 +59,9 @@ public:
/// \brief Draw session status /// \brief Draw session status
void draw (); void draw ();
/// \brief Draw session connections
void drawConnections ();
/// \brief Create session /// \brief Create session
/// \param config_ FTP config /// \param config_ FTP config
/// \param commandSocket_ Command socket /// \param commandSocket_ Command socket
@ -60,7 +75,7 @@ private:
/// \brief Command buffer size /// \brief Command buffer size
constexpr static auto COMMAND_BUFFERSIZE = 4096; constexpr static auto COMMAND_BUFFERSIZE = 4096;
#ifdef NDS #ifdef __NDS__
/// \brief Response buffer size /// \brief Response buffer size
constexpr static auto RESPONSE_BUFFERSIZE = 4096; constexpr static auto RESPONSE_BUFFERSIZE = 4096;
@ -77,7 +92,7 @@ private:
/// \brief File buffersize /// \brief File buffersize
constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE; constexpr static auto FILE_BUFFERSIZE = 4 * XFER_BUFFERSIZE;
#if defined(NDS) #if defined(__NDS__)
/// \brief Socket buffer size /// \brief Socket buffer size
constexpr static auto SOCK_BUFFERSIZE = 4096; constexpr static auto SOCK_BUFFERSIZE = 4096;
@ -156,11 +171,21 @@ private:
/// \brief Connect data socket /// \brief Connect data socket
bool dataConnect (); bool dataConnect ();
/// \brief Perform stat and apply tz offset to mtime
/// \param path_ Path to stat
/// \param st_ Output stat
int tzStat (char const *const path_, stat_t *st_);
/// \brief Perform lstat and apply tz offset to mtime
/// \param path_ Path to lstat
/// \param st_ Output stat
int tzLStat (char const *const path_, stat_t *st_);
/// \brief Fill directory entry /// \brief Fill directory entry
/// \param st_ Entry status /// \param st_ Entry status
/// \param path_ Path name /// \param path_ Path name
/// \param type_ MLST type /// \param type_ MLST type
int fillDirent (struct stat const &st_, std::string_view path_, char const *type_ = nullptr); int fillDirent (stat_t const &st_, std::string_view path_, char const *type_ = nullptr);
/// \brief Fill directory entry /// \brief Fill directory entry
/// \param path_ Path name /// \param path_ Path name
@ -199,13 +224,18 @@ private:
/// \brief Transfer directory list /// \brief Transfer directory list
bool listTransfer (); bool listTransfer ();
#if FTPD_HAS_GLOB
/// \brief Transfer glob list
bool globTransfer ();
#endif
/// \brief Transfer download /// \brief Transfer download
bool retrieveTransfer (); bool retrieveTransfer ();
/// \brief Transfer upload /// \brief Transfer upload
bool storeTransfer (); bool storeTransfer ();
#ifndef NDS #ifndef __NDS__
/// \brief Mutex /// \brief Mutex
platform::Mutex m_lock; platform::Mutex m_lock;
#endif #endif
@ -285,10 +315,41 @@ private:
/// \brief Directory being transferred /// \brief Directory being transferred
fs::Dir m_dir; fs::Dir m_dir;
#if FTPD_HAS_GLOB
/// \brief Glob wrappre
class Glob
{
public:
~Glob () noexcept;
Glob () noexcept;
/// \brief Perform glob
/// \param pattern_ Glob pattern
bool glob (char const *pattern_) noexcept;
/// \brief Get next glob result
/// \note returns nullptr when no more entries exist
char const *next () noexcept;
private:
/// \brief Clear glob
void clear () noexcept;
/// \brief Glob result
std::optional<glob_t> m_glob = std::nullopt;
/// \brief Result counter
unsigned m_offset = 0;
};
/// \brief Glob
Glob m_glob;
#endif
/// \brief Directory transfer mode /// \brief Directory transfer mode
XferDirMode m_xferDirMode; XferDirMode m_xferDirMode;
/// \brief Last command timestamp /// \brief Last activity timestamp
time_t m_timestamp; time_t m_timestamp;
/// \brief Whether user has been authorized /// \brief Whether user has been authorized

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2022 Michael Theall // Copyright (C) 2023 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -24,6 +24,10 @@
#include <string> #include <string>
#include <string_view> #include <string_view>
#ifdef DEBUG
#undef DEBUG
#endif
/// \brief Log level /// \brief Log level
enum LogLevel enum LogLevel
{ {

40
include/mdns.h Normal file
View File

@ -0,0 +1,40 @@
// ftpd is a server implementation based on the following:
// - RFC 959 (https://datatracker.ietf.org/doc/html/rfc959)
// - RFC 3659 (https://datatracker.ietf.org/doc/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
//
// ftpd implements mdns based on the following:
// - RFC 1035 (https://datatracker.ietf.org/doc/html/rfc1035)
// - RFC 6762 (https://datatracker.ietf.org/doc/html/rfc6762)
//
// Copyright (C) 2024 Michael Theall
//
// 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 <https://www.gnu.org/licenses/>.
#pragma once
#include "sockAddr.h"
#include "socket.h"
#include <cstddef>
namespace mdns
{
void setHostname (std::string hostname_);
UniqueSocket createSocket ();
void handleSocket (Socket *socket_, SockAddr const &addr_);
}

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -22,7 +22,7 @@
#include "sockAddr.h" #include "sockAddr.h"
#if defined(NDS) #if defined(__NDS__)
#include <nds.h> #include <nds.h>
#elif defined(__3DS__) #elif defined(__3DS__)
#include <3ds.h> #include <3ds.h>
@ -37,6 +37,7 @@
#include <cstdint> #include <cstdint>
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <string>
#if defined(CLASSIC) && !defined(__WIIU__) #if defined(CLASSIC) && !defined(__WIIU__)
extern PrintConsole g_statusConsole; extern PrintConsole g_statusConsole;
@ -74,6 +75,9 @@ bool networkVisible ();
/// \param[out] addr_ Network address /// \param[out] addr_ Network address
bool networkAddress (SockAddr &addr_); bool networkAddress (SockAddr &addr_);
/// \brief Get hostname
std::string const &hostname ();
/// \brief Platform loop /// \brief Platform loop
bool loop (); bool loop ();
@ -110,7 +114,7 @@ struct steady_clock
using steady_clock = std::chrono::steady_clock; using steady_clock = std::chrono::steady_clock;
#endif #endif
#ifndef NDS #ifndef __NDS__
/// \brief Platform thread /// \brief Platform thread
class Thread class Thread
{ {

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -23,24 +23,55 @@
#include <netinet/in.h> #include <netinet/in.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <compare>
#include <cstdint> #include <cstdint>
#ifdef NDS
struct sockaddr_storage
{
unsigned short ss_family;
char ss_data[sizeof (struct sockaddr_in) - sizeof (unsigned short)];
};
#endif
/// \brief Socket address /// \brief Socket address
class SockAddr class SockAddr
{ {
public: public:
enum class Domain
{
IPv4 = AF_INET,
#ifndef NO_IPV6
IPv6 = AF_INET6,
#endif
};
/// \brief 0.0.0.0
static SockAddr const AnyIPv4;
#ifndef NO_IPV6
/// \brief ::
static SockAddr const AnyIPv6;
#endif
~SockAddr (); ~SockAddr ();
SockAddr (); SockAddr ();
/// \brief Parameterized constructor
/// \param domain_ Socket domain
/// \note Initial address is INADDR_ANY/in6addr_any
SockAddr (Domain domain_);
/// \brief Parameterized constructor
/// \param addr_ Socket address (network byte order)
/// \param port_ Socket port (host byte order)
SockAddr (in_addr_t addr_, std::uint16_t port_ = 0);
/// \brief Parameterized constructor
/// \param addr_ Socket address (network byte order)
/// \param port_ Socket port (host byte order)
SockAddr (in_addr const &addr_, std::uint16_t port_ = 0);
#ifndef NO_IPV6
/// \brief Parameterized constructor
/// \param addr_ Socket address
/// \param port_ Socket port (host byte order)
SockAddr (in6_addr const &addr_, std::uint16_t port_ = 0);
#endif
/// \brief Copy constructor /// \brief Copy constructor
/// \param that_ Object to copy /// \param that_ Object to copy
SockAddr (SockAddr const &that_); SockAddr (SockAddr const &that_);
@ -57,46 +88,69 @@ public:
/// \param that_ Object to move from /// \param that_ Object to move from
SockAddr &operator= (SockAddr &&that_); SockAddr &operator= (SockAddr &&that_);
/// \param Parameterized constructor /// \brief Parameterized constructor
/// \param addr_ Address /// \param addr_ Address (network byte order)
SockAddr (struct sockaddr const &addr_); SockAddr (sockaddr_in const &addr_);
/// \param Parameterized constructor #ifndef NO_IPV6
/// \param addr_ Address /// \brief Parameterized constructor
SockAddr (struct sockaddr_in const &addr_); /// \param addr_ Address (network byte order)
SockAddr (sockaddr_in6 const &addr_);
#ifndef __3DS__
/// \param Parameterized constructor
/// \param addr_ Address
SockAddr (struct sockaddr_in6 const &addr_);
#endif #endif
/// \param Parameterized constructor /// \brief Parameterized constructor
/// \param addr_ Address /// \param addr_ Address (network byte order)
SockAddr (struct sockaddr_storage const &addr_); SockAddr (sockaddr_storage const &addr_);
/// \param sockaddr_in cast operator /// \brief sockaddr_in cast operator (network byte order)
operator struct sockaddr_in const & () const; operator sockaddr_in const & () const;
#ifndef __3DS__ #ifndef NO_IPV6
/// \param sockaddr_in6 cast operator /// \brief sockaddr_in6 cast operator (network byte order)
operator struct sockaddr_in6 const & () const; operator sockaddr_in6 const & () const;
#endif #endif
/// \param sockaddr_storage cast operator /// \brief sockaddr_storage cast operator (network byte order)
operator struct sockaddr_storage const & () const; operator sockaddr_storage const & () const;
/// \param sockaddr* cast operator /// \brief sockaddr* cast operator (network byte order)
operator struct sockaddr * (); operator sockaddr * ();
/// \param sockaddr const* cast operator
operator struct sockaddr const * () const;
/// \brief Address port /// \brief sockaddr const* cast operator (network byte order)
operator sockaddr const * () const;
/// \brief Equality operator
bool operator== (SockAddr const &that_) const;
/// \brief Comparison operator
std::strong_ordering operator<=> (SockAddr const &that_) const;
/// \brief sockaddr domain
Domain domain () const;
/// \brief sockaddr size
socklen_t size () const;
/// \brief Set address
/// \param addr_ Address to set (network byte order)
void setAddr (in_addr_t addr_);
/// \brief Set address
/// \param addr_ Address to set (network byte order)
void setAddr (in_addr const &addr_);
#ifndef NO_IPV6
/// \brief Set address
/// \param addr_ Address to set (network byte order)
void setAddr (in6_addr const &addr_);
#endif
/// \brief Address port (host byte order)
std::uint16_t port () const; std::uint16_t port () const;
/// \brief Set address port /// \brief Set address port
/// \param port_ Port to set /// \param port_ Port to set (host byte order)
bool setPort (std::uint16_t port_); void setPort (std::uint16_t port_);
/// \brief Address name /// \brief Address name
/// \param buffer_ Buffer to hold name /// \param buffer_ Buffer to hold name
@ -111,6 +165,6 @@ public:
char const *name () const; char const *name () const;
private: private:
/// \brief Address storage /// \brief Address storage (network byte order)
struct sockaddr_storage m_addr = {}; sockaddr_storage m_addr = {};
}; };

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -26,7 +26,7 @@
#include <chrono> #include <chrono>
#include <memory> #include <memory>
#ifdef NDS #ifdef __NDS__
struct pollfd struct pollfd
{ {
int fd; int fd;
@ -34,10 +34,9 @@ struct pollfd
int revents; int revents;
}; };
using socklen_t = int; using nfds_t = unsigned int;
using nfds_t = unsigned int;
extern "C" int poll (struct pollfd *fds_, nfds_t nfds_, int timeout_); extern "C" int poll (pollfd *fds_, nfds_t nfds_, int timeout_);
#define POLLIN (1 << 0) #define POLLIN (1 << 0)
#define POLLPRI (1 << 1) #define POLLPRI (1 << 1)
@ -56,6 +55,12 @@ using SharedSocket = std::shared_ptr<Socket>;
class Socket class Socket
{ {
public: public:
enum Type
{
eStream = SOCK_STREAM, ///< Stream socket
eDatagram = SOCK_DGRAM, ///< Datagram socket
};
/// \brief Poll info /// \brief Poll info
struct PollInfo struct PollInfo
{ {
@ -116,6 +121,18 @@ public:
/// \param size_ Buffer size /// \param size_ Buffer size
bool setSendBufferSize (std::size_t size_); bool setSendBufferSize (std::size_t size_);
#ifndef __NDS__
/// \brief Join multicast group
/// \param addr_ Multicast group address
/// \param iface_ Interface address
bool joinMulticastGroup (SockAddr const &addr_, SockAddr const &iface_);
/// \brief Drop multicast group
/// \param addr_ Multicast group address
/// \param iface_ Interface address
bool dropMulticastGroup (SockAddr const &addr_, SockAddr const &iface_);
#endif
/// \brief Read data /// \brief Read data
/// \param buffer_ Output buffer /// \param buffer_ Output buffer
/// \param size_ Size to read /// \param size_ Size to read
@ -127,6 +144,12 @@ public:
/// \param oob_ Whether to read from out-of-band /// \param oob_ Whether to read from out-of-band
std::make_signed_t<std::size_t> read (IOBuffer &buffer_, bool oob_ = false); std::make_signed_t<std::size_t> read (IOBuffer &buffer_, bool oob_ = false);
/// \brief Read data
/// \param buffer_ Output buffer
/// \param size_ Size to read
/// \param[out] addr_ Source address
std::make_signed_t<std::size_t> readFrom (void *buffer_, std::size_t size_, SockAddr &addr_);
/// \brief Write data /// \brief Write data
/// \param buffer_ Input buffer /// \param buffer_ Input buffer
/// \param size_ Size to write /// \param size_ Size to write
@ -137,13 +160,21 @@ public:
/// \param size_ Size to write /// \param size_ Size to write
std::make_signed_t<std::size_t> write (IOBuffer &buffer_); std::make_signed_t<std::size_t> write (IOBuffer &buffer_);
/// \brief Write data
/// \param buffer_ Input buffer
/// \param size_ Size to write
/// \param[out] addr_ Destination address
std::make_signed_t<std::size_t>
writeTo (void const *buffer_, std::size_t size_, SockAddr const &addr_);
/// \brief Local name /// \brief Local name
SockAddr const &sockName () const; SockAddr const &sockName () const;
/// \brief Peer name /// \brief Peer name
SockAddr const &peerName () const; SockAddr const &peerName () const;
/// \brief Create socket /// \brief Create socket
static UniqueSocket create (); /// \param type_ Socket type
static UniqueSocket create (Type type_);
/// \brief Poll sockets /// \brief Poll sockets
/// \param info_ Poll info /// \param info_ Poll info

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -20,13 +20,26 @@
#include "fs.h" #include "fs.h"
#include "IOAbstraction.h" #include "IOAbstraction.h"
#include "ioBuffer.h"
#include <gsl/pointers>
#include <gsl/util>
#include <array>
#include <cassert> #include <cassert>
#include <cerrno>
#include <cinttypes> #include <cinttypes>
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib>
#include <dirent.h>
#include <memory>
#include <string> #include <string>
#include <string_view>
#include <type_traits>
#include <utility>
#if defined(NDS) || defined(__3DS__) || defined(__SWITCH__) || defined(__WIIU__) #if defined(__NDS__) || defined(__3DS__) || defined(__SWITCH__) || defined(__WIIU__)
#define getline __getline #define getline __getline
#endif #endif
@ -39,7 +52,7 @@ std::string fs::printSize (std::uint64_t const size_)
constexpr std::uint64_t const PiB = 1024 * TiB; constexpr std::uint64_t const PiB = 1024 * TiB;
constexpr std::uint64_t const EiB = 1024 * PiB; constexpr std::uint64_t const EiB = 1024 * PiB;
char buffer[64] = {}; std::array<char, 64> buffer{};
for (auto const &[name, bin] : { for (auto const &[name, bin] : {
// clang-format off // clang-format off
@ -57,30 +70,32 @@ std::string fs::printSize (std::uint64_t const size_)
if (size_ >= 100 * bin) if (size_ >= 100 * bin)
{ {
// >= 100, print xxxXiB // >= 100, print xxxXiB
std::sprintf (buffer, "%" PRIu64 "%s", whole, name); std::size_t const size = std::sprintf (buffer.data (), "%" PRIu64 "%s", whole, name);
return buffer; return {buffer.data (), size};
} }
// get the fractional portion of the number // get the fractional portion of the number
auto const frac = size_ - whole * bin; auto const frac = size_ - (whole * bin);
if (size_ >= 10 * bin) if (size_ >= 10 * bin)
{ {
// >= 10, print xx.xXiB // >= 10, print xx.xXiB
std::sprintf (buffer, "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name); std::size_t const size = std::sprintf (
return buffer; buffer.data (), "%" PRIu64 ".%" PRIu64 "%s", whole, frac * 10 / bin, name);
return {buffer.data (), size};
} }
if (size_ >= 1000 * (bin / KiB)) if (size_ >= 1000 * (bin / KiB))
{ {
// >= 1000 of lesser bin, print x.xxXiB // >= 1000 of lesser bin, print x.xxXiB
std::sprintf (buffer, "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name); std::size_t const size = std::sprintf (
return buffer; buffer.data (), "%" PRIu64 ".%02" PRIu64 "%s", whole, frac * 100 / bin, name);
return {buffer.data (), size};
} }
} }
// < 1KiB, just print the number // < 1KiB, just print the number
std::sprintf (buffer, "%" PRIu64 "B", size_); std::size_t const size = std::sprintf (buffer.data (), "%" PRIu64 "B", size_);
return buffer; return {buffer.data (), size};
} }
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
@ -107,26 +122,24 @@ fs::File::operator FILE * () const
void fs::File::setBufferSize (std::size_t const size_) void fs::File::setBufferSize (std::size_t const size_)
{ {
if (m_bufferSize != size_) if (m_buffer.size () != size_)
{ m_buffer.resize (size_);
m_buffer = std::make_unique<char[]> (size_);
m_bufferSize = size_;
}
if (m_fp) if (m_fp)
std::setvbuf (m_fp.get (), m_buffer.get (), _IOFBF, m_bufferSize); (void)std::setvbuf (m_fp.get (), m_buffer.data (), _IOFBF, m_buffer.size ());
} }
bool fs::File::open (char const *const path_, char const *const mode_) bool fs::File::open (gsl::not_null<char const *> const path_,
gsl::not_null<char const *> const mode_)
{ {
auto const fp = IOAbstraction::fopen (path_, mode_); gsl::owner<FILE *> fp = IOAbstraction::fopen (path_, mode_);
if (!fp) if (!fp)
return false; return false;
m_fp = std::unique_ptr<std::FILE, int (*) (std::FILE *)> (fp, &std::fclose); m_fp = std::unique_ptr<std::FILE, int (*) (std::FILE *)> (fp, &std::fclose);
if (m_buffer) if (!m_buffer.empty ())
std::setvbuf (m_fp.get (), m_buffer.get (), _IOFBF, m_bufferSize); (void)std::setvbuf (m_fp.get (), m_buffer.data (), _IOFBF, m_buffer.size ());
return true; return true;
} }
@ -136,17 +149,27 @@ void fs::File::close ()
m_fp.reset (); m_fp.reset ();
} }
std::make_signed_t<std::size_t> fs::File::seek (std::size_t const pos_, int const origin_) std::make_signed_t<std::size_t> fs::File::seek (std::make_signed_t<std::size_t> const pos_,
int const origin_)
{ {
return IOAbstraction::fseek (m_fp.get (), pos_, origin_); return IOAbstraction::fseek (m_fp.get (), pos_, origin_);
} }
std::make_signed_t<std::size_t> fs::File::read (void *const buffer_, std::size_t const size_) std::make_signed_t<std::size_t> fs::File::read (gsl::not_null<void *> const buffer_,
std::size_t const size_)
{ {
assert (buffer_); assert (buffer_);
assert (size_ > 0); assert (size_ > 0);
return IOAbstraction::fread (buffer_, 1, size_, m_fp.get ()); auto const rc = IOAbstraction::fread (buffer_, 1, size_, m_fp.get ());
if (rc == 0)
{
if (std::feof (m_fp.get ()))
return 0;
return -1;
}
return gsl::narrow_cast<std::make_signed_t<std::size_t>> (rc);
} }
std::make_signed_t<std::size_t> fs::File::read (IOBuffer &buffer_) std::make_signed_t<std::size_t> fs::File::read (IOBuffer &buffer_)
@ -177,37 +200,41 @@ std::string_view fs::File::readLine ()
} }
if (rc > 0) if (rc > 0)
return std::string_view (m_lineBuffer, rc); return {m_lineBuffer, gsl::narrow_cast<std::size_t> (rc)};
} }
} }
bool fs::File::readAll (void *const buffer_, std::size_t const size_) bool fs::File::readAll (gsl::not_null<void *> const buffer_, std::size_t const size_)
{ {
assert (buffer_); assert (buffer_);
assert (size_ > 0); assert (size_ > 0);
auto p = static_cast<char *> (buffer_); auto const p = static_cast<char *> (buffer_.get ());
std::size_t bytes = 0; std::size_t bytes = 0;
while (bytes < size_) while (bytes < size_)
{ {
auto const rc = read (p, size_ - bytes); auto const rc = read (p + bytes, size_ - bytes);
if (rc <= 0) if (rc <= 0)
return false; return false;
p += rc;
bytes += rc; bytes += rc;
} }
return true; return true;
} }
std::make_signed_t<std::size_t> fs::File::write (void const *const buffer_, std::size_t const size_) std::make_signed_t<std::size_t> fs::File::write (gsl::not_null<void const *> const buffer_,
std::size_t const size_)
{ {
assert (buffer_); assert (buffer_);
assert (size_ > 0); assert (size_ > 0);
return IOAbstraction::fwrite (buffer_, 1, size_, m_fp.get ()); auto const rc = IOAbstraction::fwrite (buffer_, 1, size_, m_fp.get ());
if (rc == 0)
return -1;
return gsl::narrow_cast<std::make_signed_t<std::size_t>> (rc);
} }
std::make_signed_t<std::size_t> fs::File::write (IOBuffer &buffer_) std::make_signed_t<std::size_t> fs::File::write (IOBuffer &buffer_)
@ -221,21 +248,20 @@ std::make_signed_t<std::size_t> fs::File::write (IOBuffer &buffer_)
return rc; return rc;
} }
bool fs::File::writeAll (void const *const buffer_, std::size_t const size_) bool fs::File::writeAll (gsl::not_null<void const *> const buffer_, std::size_t const size_)
{ {
assert (buffer_); assert (buffer_);
assert (size_ > 0); assert (size_ > 0);
auto p = static_cast<char const *> (buffer_); auto const p = static_cast<char const *> (buffer_.get ());
std::size_t bytes = 0; std::size_t bytes = 0;
while (bytes < size_) while (bytes < size_)
{ {
auto const rc = write (p, size_ - bytes); auto const rc = write (p + bytes, size_ - bytes);
if (rc <= 0) if (rc <= 0)
return false; return false;
p += rc;
bytes += rc; bytes += rc;
} }
@ -261,7 +287,7 @@ fs::Dir::operator DIR * () const
return m_dp.get (); return m_dp.get ();
} }
bool fs::Dir::open (char const *const path_) bool fs::Dir::open (gsl::not_null<char const *> const path_)
{ {
auto const dp = IOAbstraction::opendir (path_); auto const dp = IOAbstraction::opendir (path_);
if (!dp) if (!dp)
@ -276,7 +302,7 @@ void fs::Dir::close ()
m_dp.reset (); m_dp.reset ();
} }
struct dirent *fs::Dir::read () dirent *fs::Dir::read ()
{ {
errno = 0; errno = 0;
return IOAbstraction::readdir (m_dp.get ()); return IOAbstraction::readdir (m_dp.get ());

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2022 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -22,11 +22,23 @@
#include "fs.h" #include "fs.h"
#include "log.h" #include "log.h"
#include "platform.h"
#include <gsl/pointers>
#include <sys/stat.h> #include <sys/stat.h>
using stat_t = struct stat;
#include <limits> #include <cctype>
#include <cerrno>
#include <charconv>
#include <cstdint>
#include <cstdio>
#include <mutex> #include <mutex>
#include <string>
#include <string_view>
#include <system_error>
#include <utility>
namespace namespace
{ {
@ -36,14 +48,14 @@ constexpr std::uint16_t DEFAULT_PORT = 21;
constexpr std::uint16_t DEFAULT_PORT = 5000; constexpr std::uint16_t DEFAULT_PORT = 5000;
#endif #endif
bool mkdirParent (std::string const &path_) bool mkdirParent (std::string_view const path_)
{ {
auto pos = path_.find_first_of ('/'); auto pos = path_.find_first_of ('/');
while (pos != std::string::npos) while (pos != std::string::npos)
{ {
auto const dir = path_.substr (0, pos); auto const dir = std::string (path_.substr (0, pos));
struct stat st; stat_t st{};
auto const rc = ::stat (dir.c_str (), &st); auto const rc = ::stat (dir.c_str (), &st);
if (rc < 0 && errno != ENOENT) if (rc < 0 && errno != ENOENT)
return false; return false;
@ -61,14 +73,13 @@ bool mkdirParent (std::string const &path_)
return true; return true;
} }
std::string strip (std::string const &str_) std::string_view strip (std::string_view const str_)
{ {
auto const start = str_.find_first_not_of (" \t"); auto const start = str_.find_first_not_of (" \t");
if (start == std::string::npos) if (start == std::string::npos)
return {}; return {};
auto const end = str_.find_last_not_of (" \t"); auto const end = str_.find_last_not_of (" \t");
if (end == std::string::npos) if (end == std::string::npos)
return str_.substr (start); return str_.substr (start);
@ -76,37 +87,21 @@ std::string strip (std::string const &str_)
} }
template <typename T> template <typename T>
bool parseInt (T &out_, std::string const &val_) bool parseInt (T &out_, std::string_view const val_)
{ {
T val = 0; auto const rc = std::from_chars (val_.data (), val_.data () + val_.size (), out_);
if (rc.ec != std::errc{})
for (auto const &c : val_)
{ {
if (!std::isdigit (c)) errno = static_cast<int> (rc.ec);
{ return false;
errno = EINVAL; }
return false;
} if (rc.ptr != val_.data () + val_.size ())
{
if (std::numeric_limits<T>::max () / 10 < val) errno = EINVAL;
{ return false;
errno = EOVERFLOW;
return false;
}
val *= 10;
auto const v = c - '0';
if (std::numeric_limits<T>::max () - v < val)
{
errno = EOVERFLOW;
return false;
}
val += v;
} }
out_ = val;
return true; return true;
} }
} }
@ -123,7 +118,7 @@ UniqueFtpConfig FtpConfig::create ()
return UniqueFtpConfig (new FtpConfig ()); return UniqueFtpConfig (new FtpConfig ());
} }
UniqueFtpConfig FtpConfig::load (char const *const path_) UniqueFtpConfig FtpConfig::load (gsl::not_null<gsl::czstring> const path_)
{ {
auto config = create (); auto config = create ();
@ -143,8 +138,8 @@ UniqueFtpConfig FtpConfig::load (char const *const path_)
continue; continue;
} }
auto const key = strip (line.substr (0, pos)); auto const key = strip (std::string_view (line).substr (0, pos));
auto const val = strip (line.substr (pos + 1)); auto const val = strip (std::string_view (line).substr (pos + 1));
if (key.empty () || val.empty ()) if (key.empty () || val.empty ())
{ {
error ("Ignoring '%s'\n", line.c_str ()); error ("Ignoring '%s'\n", line.c_str ());
@ -165,7 +160,9 @@ UniqueFtpConfig FtpConfig::load (char const *const path_)
else if (val == "1") else if (val == "1")
config->m_getMTime = true; config->m_getMTime = true;
else else
error ("Invalid value for mtime: %s\n", val.c_str ()); error ("Invalid value for mtime: %.*s\n",
gsl::narrow_cast<int> (val.size ()),
val.data ());
} }
#endif #endif
#ifdef __SWITCH__ #ifdef __SWITCH__
@ -176,7 +173,9 @@ UniqueFtpConfig FtpConfig::load (char const *const path_)
else if (val == "1") else if (val == "1")
config->m_enableAP = true; config->m_enableAP = true;
else else
error ("Invalid value for ap: %s\n", val.c_str ()); error ("Invalid value for ap: %.*s\n",
gsl::narrow_cast<int> (val.size ()),
val.data ());
} }
else if (key == "ssid") else if (key == "ssid")
config->m_ssid = val; config->m_ssid = val;
@ -190,16 +189,16 @@ UniqueFtpConfig FtpConfig::load (char const *const path_)
return config; return config;
} }
#ifndef NDS #ifndef __NDS__
std::scoped_lock<platform::Mutex> FtpConfig::lockGuard () std::scoped_lock<platform::Mutex> FtpConfig::lockGuard ()
{ {
return std::scoped_lock<platform::Mutex> (m_lock); return std::scoped_lock<platform::Mutex> (m_lock);
} }
#endif #endif
bool FtpConfig::save (char const *const path_) bool FtpConfig::save (gsl::not_null<gsl::czstring> const path_)
{ {
if (!mkdirParent (path_)) if (!mkdirParent (path_.get ()))
return false; return false;
auto fp = fs::File (); auto fp = fs::File ();
@ -207,21 +206,21 @@ bool FtpConfig::save (char const *const path_)
return false; return false;
if (!m_user.empty ()) if (!m_user.empty ())
std::fprintf (fp, "user=%s\n", m_user.c_str ()); (void)std::fprintf (fp, "user=%s\n", m_user.c_str ());
if (!m_pass.empty ()) if (!m_pass.empty ())
std::fprintf (fp, "pass=%s\n", m_pass.c_str ()); (void)std::fprintf (fp, "pass=%s\n", m_pass.c_str ());
std::fprintf (fp, "port=%u\n", m_port); (void)std::fprintf (fp, "port=%u\n", m_port);
#ifdef __3DS__ #ifdef __3DS__
std::fprintf (fp, "mtime=%u\n", m_getMTime); (void)std::fprintf (fp, "mtime=%u\n", m_getMTime);
#endif #endif
#ifdef __SWITCH__ #ifdef __SWITCH__
std::fprintf (fp, "ap=%u\n", m_enableAP); (void)std::fprintf (fp, "ap=%u\n", m_enableAP);
if (!m_ssid.empty ()) if (!m_ssid.empty ())
std::fprintf (fp, "ssid=%s\n", m_ssid.c_str ()); (void)std::fprintf (fp, "ssid=%s\n", m_ssid.c_str ());
if (!m_passphrase.empty ()) if (!m_passphrase.empty ())
std::fprintf (fp, "passphrase=%s\n", m_passphrase.c_str ()); (void)std::fprintf (fp, "passphrase=%s\n", m_passphrase.c_str ());
#endif #endif
return true; return true;
@ -237,6 +236,11 @@ std::string const &FtpConfig::pass () const
return m_pass; return m_pass;
} }
std::string const &FtpConfig::hostname () const
{
return m_hostname;
}
std::uint16_t FtpConfig::port () const std::uint16_t FtpConfig::port () const
{ {
return m_port; return m_port;
@ -266,19 +270,24 @@ std::string const &FtpConfig::passphrase () const
} }
#endif #endif
void FtpConfig::setUser (std::string const &user_) void FtpConfig::setUser (std::string user_)
{ {
m_user = user_.substr (0, user_.find_first_of ('\0')); m_user = std::move (user_);
} }
void FtpConfig::setPass (std::string const &pass_) void FtpConfig::setPass (std::string pass_)
{ {
m_pass = pass_.substr (0, pass_.find_first_of ('\0')); m_pass = std::move (pass_);
} }
bool FtpConfig::setPort (std::string const &port_) void FtpConfig::setHostname (std::string hostname_)
{ {
std::uint16_t parsed; m_hostname = std::move (hostname_);
}
bool FtpConfig::setPort (std::string_view const port_)
{
std::uint16_t parsed{};
if (!parseInt (parsed, port_)) if (!parseInt (parsed, port_))
return false; return false;
@ -294,7 +303,7 @@ bool FtpConfig::setPort (std::uint16_t const port_)
errno = EPERM; errno = EPERM;
return false; return false;
} }
#elif defined(NDS) || defined(__3DS__) #elif defined(__NDS__) || defined(__3DS__)
// 3DS is allowed < 1024, but not 0 // 3DS is allowed < 1024, but not 0
// NDS is allowed < 1024, but 0 crashes the app // NDS is allowed < 1024, but 0 crashes the app
if (port_ == 0) if (port_ == 0)
@ -321,12 +330,12 @@ void FtpConfig::setEnableAP (bool const enable_)
m_enableAP = enable_; m_enableAP = enable_;
} }
void FtpConfig::setSSID (std::string const &ssid_) void FtpConfig::setSSID (std::string_view const ssid_)
{ {
m_ssid = ssid_.substr (0, ssid_.find_first_of ('\0')); m_ssid = ssid_.substr (0, ssid_.find_first_of ('\0'));
} }
void FtpConfig::setPassphrase (std::string const &passphrase_) void FtpConfig::setPassphrase (std::string_view const passphrase_)
{ {
m_passphrase = passphrase_.substr (0, passphrase_.find_first_of ('\0')); m_passphrase = passphrase_.substr (0, passphrase_.find_first_of ('\0'));
} }

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2023 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -21,36 +21,62 @@
#include "ftpServer.h" #include "ftpServer.h"
#include "fs.h" #include "fs.h"
#ifndef __WIIU__ #include "ftpConfig.h"
#include "licenses.h" #include "ftpSession.h"
#endif
#include "log.h" #include "log.h"
#include "platform.h" #include "platform.h"
#include "sockAddr.h"
#include "socket.h" #include "socket.h"
#ifndef __WIIU__ #ifndef __WIIU__
#include "imgui.h" #include "imgui.h"
#include "licenses.h"
#endif
#ifndef __NDS__
#include "mdns.h"
#endif #endif
#ifdef NDS #ifdef __NDS__
#include <dswifi9.h> #include <dswifi9.h>
#endif #endif
#include <arpa/inet.h> #ifdef __3DS__
#include <citro3d.h>
#endif
#ifndef CLASSIC
#include <imgui.h>
#include <jansson.h>
#include <curl/easy.h>
#include <curl/multi.h>
#ifndef NDEBUG
#include <curl/curl.h>
#endif
#endif
#include <sys/statvfs.h> #include <sys/statvfs.h>
#include <unistd.h> using statvfs_t = struct statvfs;
#include <algorithm> #include <algorithm>
#include <array>
#include <atomic>
#include <cctype> #include <cctype>
#include <chrono> #include <chrono>
#include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstring> #include <cstring>
#include <ctime>
#include <functional>
#include <mutex> #include <mutex>
#include <string>
#include <string_view> #include <string_view>
#include <thread> #include <utility>
#include <vector>
using namespace std::chrono_literals; using namespace std::chrono_literals;
#ifdef NDS #ifdef __NDS__
#define LOCKED(x) x #define LOCKED(x) x
#else #else
#define LOCKED(x) \ #define LOCKED(x) \
@ -66,7 +92,12 @@ namespace
/// \brief Application start time /// \brief Application start time
auto const s_startTime = std::time (nullptr); auto const s_startTime = std::time (nullptr);
#ifndef NDS #ifdef __3DS__
/// \brief Timezone offset in seconds (only used on 3DS)
int s_tzOffset = 0;
#endif
#ifndef __NDS__
/// \brief Mutex for s_freeSpace /// \brief Mutex for s_freeSpace
platform::Mutex s_lock; platform::Mutex s_lock;
#endif #endif
@ -76,21 +107,33 @@ std::string s_freeSpace;
#ifndef CLASSIC #ifndef CLASSIC
#ifndef NDEBUG #ifndef NDEBUG
std::string printable (char *const data_, std::size_t const size_) std::string printable (std::string_view const data_)
{ {
std::string result; unsigned count = 0;
result.reserve (size_); for (auto const &c : data_)
for (std::size_t i = 0; i < size_; ++i)
{ {
if (std::isprint (data_[i]) || std::isspace (data_[i])) if (c != '%' && (std::isprint (c) || std::isspace (c)))
result.push_back (data_[i]); ++count;
else
count += 3;
}
std::string result;
result.reserve (count);
for (auto const &c : data_)
{
if (c != '%' && (std::isprint (c) || std::isspace (c)))
result.push_back (c);
else else
{ {
char buffer[5]; result.push_back ('%');
std::snprintf (
buffer, sizeof (buffer), "%%%02u", static_cast<unsigned char> (data_[i])); auto const upper = (static_cast<unsigned char> (c) >> 4u) & 0xF;
result += buffer; auto const lower = (static_cast<unsigned char> (c) >> 0u) & 0xF;
result.push_back (gsl::narrow_cast<char> (upper < 10 ? upper + '0' : upper + 'A' - 10));
result.push_back (gsl::narrow_cast<char> (lower < 10 ? lower + '0' : lower + 'A' - 10));
} }
} }
@ -103,9 +146,10 @@ int curlDebug (CURL *const handle_,
std::size_t const size_, std::size_t const size_,
void *const user_) void *const user_)
{ {
(void)handle_;
(void)user_; (void)user_;
auto const text = printable (data_, size_); auto const text = printable (std::string_view (data_, size_));
switch (type_) switch (type_)
{ {
@ -167,7 +211,7 @@ FtpServer::~FtpServer ()
{ {
m_quit = true; m_quit = true;
#ifndef NDS #ifndef __NDS__
m_thread.join (); m_thread.join ();
#endif #endif
@ -184,23 +228,36 @@ FtpServer::~FtpServer ()
#endif #endif
} }
FtpServer::FtpServer (UniqueFtpConfig config_) : m_config (std::move (config_)), m_quit (false) FtpServer::FtpServer (UniqueFtpConfig config_)
: m_config (std::move (config_))
#ifndef CLASSIC
,
m_hostnameSetting (m_config->hostname ())
#endif
{ {
#ifndef NDS #ifndef __NDS__
mdns::setHostname (m_config->hostname ());
m_thread = platform::Thread (std::bind (&FtpServer::threadFunc, this)); m_thread = platform::Thread (std::bind (&FtpServer::threadFunc, this));
#endif #endif
#ifdef __3DS__
s64 tzOffsetMinutes;
if (R_SUCCEEDED (svcGetSystemInfo (&tzOffsetMinutes, 0x10000, 0x103)))
s_tzOffset = tzOffsetMinutes * 60;
#endif
} }
void FtpServer::draw () void FtpServer::draw ()
{ {
#ifdef NDS #ifdef __NDS__
loop (); loop ();
#endif #endif
#ifdef CLASSIC #ifdef CLASSIC
{ {
char port[7]; char port[7];
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (m_lock); auto const lock = std::scoped_lock (m_lock);
#endif #endif
if (m_socket) if (m_socket)
@ -214,7 +271,7 @@ void FtpServer::draw ()
m_socket ? port : ""); m_socket ? port : "");
#endif #endif
#ifndef NDS #ifndef __NDS__
char timeBuffer[16]; char timeBuffer[16];
auto const now = std::time (nullptr); auto const now = std::time (nullptr);
std::strftime (timeBuffer, sizeof (timeBuffer), "%H:%M:%S", std::localtime (&now)); std::strftime (timeBuffer, sizeof (timeBuffer), "%H:%M:%S", std::localtime (&now));
@ -227,7 +284,7 @@ void FtpServer::draw ()
} }
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (s_lock); auto const lock = std::scoped_lock (s_lock);
#endif #endif
if (!s_freeSpace.empty ()) if (!s_freeSpace.empty ())
@ -244,7 +301,7 @@ void FtpServer::draw ()
} }
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (m_lock); auto const lock = std::scoped_lock (m_lock);
#endif #endif
#ifndef NO_CONSOLE #ifndef NO_CONSOLE
@ -274,17 +331,17 @@ void FtpServer::draw ()
ImGui::SetNextWindowSize (ImVec2 (width, height)); ImGui::SetNextWindowSize (ImVec2 (width, height));
#endif #endif
{ {
char title[64]; std::array<char, 64> title{};
{ {
auto const serverLock = std::scoped_lock (m_lock); auto const serverLock = std::scoped_lock (m_lock);
std::snprintf (title, std::snprintf (title.data (),
sizeof (title), title.size (),
STATUS_STRING " %s###ftpd", STATUS_STRING " %s###ftpd",
m_socket ? m_name.c_str () : "Waiting for WiFi..."); m_socket ? m_name.c_str () : "Waiting for WiFi...");
} }
ImGui::Begin (title, ImGui::Begin (title.data (),
nullptr, nullptr,
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize
#ifndef __3DS__ #ifndef __3DS__
@ -332,6 +389,11 @@ void FtpServer::draw ()
#endif #endif
} }
bool FtpServer::quit ()
{
return m_quit;
}
UniqueFtpServer FtpServer::create () UniqueFtpServer FtpServer::create ()
{ {
updateFreeSpace (); updateFreeSpace ();
@ -343,7 +405,7 @@ UniqueFtpServer FtpServer::create ()
std::string FtpServer::getFreeSpace () std::string FtpServer::getFreeSpace ()
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (s_lock); auto const lock = std::scoped_lock (s_lock);
#endif #endif
return s_freeSpace; return s_freeSpace;
@ -351,8 +413,8 @@ std::string FtpServer::getFreeSpace ()
void FtpServer::updateFreeSpace () void FtpServer::updateFreeSpace ()
{ {
struct statvfs st; statvfs_t st = {};
#if defined(NDS) || defined(__3DS__) || defined(__SWITCH__) #if defined(__NDS__) || defined(__3DS__) || defined(__SWITCH__)
if (::statvfs ("sdmc:/", &st) != 0) if (::statvfs ("sdmc:/", &st) != 0)
#else #else
if (::statvfs ("/", &st) != 0) if (::statvfs ("/", &st) != 0)
@ -361,7 +423,7 @@ void FtpServer::updateFreeSpace ()
auto freeSpace = fs::printSize (static_cast<std::uint64_t> (st.f_bsize) * st.f_bfree); auto freeSpace = fs::printSize (static_cast<std::uint64_t> (st.f_bsize) * st.f_bfree);
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (s_lock); auto const lock = std::scoped_lock (s_lock);
#endif #endif
if (freeSpace != s_freeSpace) if (freeSpace != s_freeSpace)
@ -373,6 +435,13 @@ std::time_t FtpServer::startTime ()
return s_startTime; return s_startTime;
} }
#ifdef __3DS__
int FtpServer::tzOffset ()
{
return s_tzOffset;
}
#endif
void FtpServer::handleNetworkFound () void FtpServer::handleNetworkFound ()
{ {
SockAddr addr; SockAddr addr;
@ -382,7 +451,7 @@ void FtpServer::handleNetworkFound ()
std::uint16_t port; std::uint16_t port;
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = m_config->lockGuard (); auto const lock = m_config->lockGuard ();
#endif #endif
port = m_config->port (); port = m_config->port ();
@ -390,7 +459,7 @@ void FtpServer::handleNetworkFound ()
addr.setPort (port); addr.setPort (port);
auto socket = Socket::create (); auto socket = Socket::create (Socket::eStream);
if (!socket) if (!socket)
return; return;
@ -412,6 +481,14 @@ void FtpServer::handleNetworkFound ()
info ("Started server at %s\n", m_name.c_str ()); info ("Started server at %s\n", m_name.c_str ());
LOCKED (m_socket = std::move (socket)); LOCKED (m_socket = std::move (socket));
#ifndef __NDS__
socket = mdns::createSocket ();
if (!socket)
return;
LOCKED (m_mdnsSocket = std::move (socket));
#endif
} }
void FtpServer::handleNetworkLost () void FtpServer::handleNetworkLost ()
@ -423,9 +500,15 @@ void FtpServer::handleNetworkLost ()
} }
{ {
// destroy command socket
UniqueSocket sock; UniqueSocket sock;
// destroy command socket
LOCKED (sock = std::move (m_socket)); LOCKED (sock = std::move (m_socket));
#ifndef __NDS__
// destroy mDNS socket
LOCKED (sock = std::move (m_mdnsSocket));
#endif
} }
info ("Stopped server at %s\n", m_name.c_str ()); info ("Stopped server at %s\n", m_name.c_str ());
@ -450,7 +533,7 @@ void FtpServer::showMenu ()
if (ImGui::MenuItem ("Upload Log")) if (ImGui::MenuItem ("Upload Log"))
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (m_lock); auto const lock = std::scoped_lock (m_lock);
#endif #endif
if (!m_uploadLogCurlM) if (!m_uploadLogCurlM)
@ -462,11 +545,6 @@ void FtpServer::showMenu ()
auto const handle = curl_easy_init (); auto const handle = curl_easy_init ();
#ifdef __3DS__
// 3DS CA fails peer verification, so add CA here
curl_easy_setopt (handle, CURLOPT_CAINFO, "romfs:/sni.cloudflaressl.com.ca");
#endif
#ifndef NDEBUG #ifndef NDEBUG
curl_easy_setopt (handle, CURLOPT_DEBUGFUNCTION, &curlDebug); curl_easy_setopt (handle, CURLOPT_DEBUGFUNCTION, &curlDebug);
curl_easy_setopt (handle, CURLOPT_DEBUGDATA, nullptr); curl_easy_setopt (handle, CURLOPT_DEBUGDATA, nullptr);
@ -479,9 +557,9 @@ void FtpServer::showMenu ()
curl_easy_setopt (handle, CURLOPT_WRITEDATA, &m_uploadLogResult); curl_easy_setopt (handle, CURLOPT_WRITEDATA, &m_uploadLogResult);
// set headers // set headers
static char contentType[] = "Content-Type: multipart/form-data"; static char contentType[] = "Content-Type: text/plain";
static curl_slist const headers = {contentType, nullptr}; static curl_slist const headers = {contentType, nullptr};
curl_easy_setopt (handle, CURLOPT_URL, "https://hastebin.com/documents"); curl_easy_setopt (handle, CURLOPT_URL, "https://pastie.io/documents");
curl_easy_setopt (handle, CURLOPT_HTTPHEADER, &headers); curl_easy_setopt (handle, CURLOPT_HTTPHEADER, &headers);
// set form data // set form data
@ -500,9 +578,16 @@ void FtpServer::showMenu ()
} }
} }
ImGui::Separator ();
if (ImGui::MenuItem ("About")) if (ImGui::MenuItem ("About"))
m_showAbout = true; m_showAbout = true;
ImGui::Separator ();
if (ImGui::MenuItem ("Quit"))
m_quit = true;
ImGui::EndMenu (); ImGui::EndMenu ();
} }
ImGui::EndMenuBar (); ImGui::EndMenuBar ();
@ -512,7 +597,7 @@ void FtpServer::showMenu ()
{ {
if (!prevShowSettings) if (!prevShowSettings)
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = m_config->lockGuard (); auto const lock = m_config->lockGuard ();
#endif #endif
@ -522,6 +607,9 @@ void FtpServer::showMenu ()
m_passSetting = m_config->pass (); m_passSetting = m_config->pass ();
m_passSetting.resize (32); m_passSetting.resize (32);
m_hostnameSetting = m_config->hostname ();
m_hostnameSetting.resize (32);
m_portSetting = m_config->port (); m_portSetting = m_config->port ();
#ifdef __3DS__ #ifdef __3DS__
@ -579,6 +667,11 @@ void FtpServer::showSettings ()
m_passSetting.size (), m_passSetting.size (),
ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_Password); ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_Password);
ImGui::InputText ("Hostname",
m_hostnameSetting.data (),
m_hostnameSetting.size (),
ImGuiInputTextFlags_AutoSelectAll);
ImGui::InputScalar ("Port", ImGui::InputScalar ("Port",
ImGuiDataType_U16, ImGuiDataType_U16,
&m_portSetting, &m_portSetting,
@ -645,12 +738,13 @@ void FtpServer::showSettings ()
m_showSettings = false; m_showSettings = false;
ImGui::CloseCurrentPopup (); ImGui::CloseCurrentPopup ();
#ifndef NDS #ifndef __NDS__
auto const lock = m_config->lockGuard (); auto const lock = m_config->lockGuard ();
#endif #endif
m_config->setUser (m_userSetting); m_config->setUser (m_userSetting);
m_config->setPass (m_passSetting); m_config->setPass (m_passSetting);
m_config->setHostname (m_hostnameSetting);
m_config->setPort (m_portSetting); m_config->setPort (m_portSetting);
#ifdef __3DS__ #ifdef __3DS__
@ -666,11 +760,13 @@ void FtpServer::showSettings ()
UniqueSocket socket; UniqueSocket socket;
LOCKED (socket = std::move (m_socket)); LOCKED (socket = std::move (m_socket));
mdns::setHostname (m_hostnameSetting);
} }
if (save) if (save)
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = m_config->lockGuard (); auto const lock = m_config->lockGuard ();
#endif #endif
if (!m_config->save (FTPDCONFIG)) if (!m_config->save (FTPDCONFIG))
@ -681,9 +777,10 @@ void FtpServer::showSettings ()
{ {
static auto const defaults = FtpConfig::create (); static auto const defaults = FtpConfig::create ();
m_userSetting = defaults->user (); m_userSetting = defaults->user ();
m_passSetting = defaults->pass (); m_passSetting = defaults->pass ();
m_portSetting = defaults->port (); m_hostnameSetting = defaults->hostname ();
m_portSetting = defaults->port ();
#ifdef __3DS__ #ifdef __3DS__
m_getMTimeSetting = defaults->getMTime (); m_getMTimeSetting = defaults->getMTime ();
#endif #endif
@ -723,17 +820,31 @@ void FtpServer::showAbout ()
ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize)) ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize))
{ {
ImGui::TextUnformatted (STATUS_STRING); ImGui::TextUnformatted (STATUS_STRING);
ImGui::TextWrapped ("Copyright © 2023 Michael Theall, Dave Murphy, TuxSH"); ImGui::TextWrapped ("Copyright © 2024 Michael Theall, Dave Murphy, TuxSH");
ImGui::Separator (); ImGui::Separator ();
ImGui::Text ("Platform: %s", io.BackendPlatformName); ImGui::Text ("Platform: %s", io.BackendPlatformName);
ImGui::Text ("Renderer: %s", io.BackendRendererName); ImGui::Text ("Renderer: %s", io.BackendRendererName);
#ifdef __3DS__
ImGui::Text ("Command Buffer Usage: %.1f%%", 100.0f * C3D_GetCmdBufUsage ());
ImGui::Text ("GPU Processing Usage: %.1f%%", 6.0f * C3D_GetProcessingTime ());
ImGui::Text ("GPU Drawing Usage: %.1f%%", 6.0f * C3D_GetDrawingTime ());
#endif
if (ImGui::Button ("OK", ImVec2 (100, 0))) if (ImGui::Button ("OK", ImVec2 (100, 0)))
{ {
m_showAbout = false; m_showAbout = false;
ImGui::CloseCurrentPopup (); ImGui::CloseCurrentPopup ();
} }
ImGui::Separator ();
if (ImGui::TreeNode ("Connections"))
{
for (auto const &session : m_sessions)
session->drawConnections ();
ImGui::TreePop ();
}
ImGui::Separator (); ImGui::Separator ();
if (ImGui::TreeNode (g_dearImGuiVersion)) if (ImGui::TreeNode (g_dearImGuiVersion))
{ {
@ -743,7 +854,7 @@ void FtpServer::showAbout ()
ImGui::TreePop (); ImGui::TreePop ();
} }
#if defined(NDS) #if defined(__NDS__)
#elif defined(__3DS__) #elif defined(__3DS__)
if (ImGui::TreeNode (g_libctruVersion)) if (ImGui::TreeNode (g_libctruVersion))
{ {
@ -782,7 +893,7 @@ void FtpServer::showAbout ()
{ {
ImGui::TextWrapped ("%s", g_zstdCopyright); ImGui::TextWrapped ("%s", g_zstdCopyright);
ImGui::Separator (); ImGui::Separator ();
ImGui::TextWrapped ("%s", g_bsdLicense); ImGui::TextWrapped ("%s", g_zstdLicense);
ImGui::TreePop (); ImGui::TreePop ();
} }
#else #else
@ -795,6 +906,24 @@ void FtpServer::showAbout ()
} }
#endif #endif
#if defined(__NDS__) || defined(__3DS__) || defined(__SWITCH__)
if (ImGui::TreeNode (g_globVersion))
{
ImGui::TextWrapped ("%s", g_globCopyright);
ImGui::Separator ();
ImGui::TextWrapped ("%s", g_globLicense);
ImGui::TreePop ();
}
if (ImGui::TreeNode (g_collateVersion))
{
ImGui::TextWrapped ("%s", g_collateCopyright);
ImGui::Separator ();
ImGui::TextWrapped ("%s", g_collateLicense);
ImGui::TreePop ();
}
#endif
ImGui::EndPopup (); ImGui::EndPopup ();
} }
} }
@ -854,11 +983,19 @@ void FtpServer::loop ()
if (msg->data.result != CURLE_OK) if (msg->data.result != CURLE_OK)
info ("cURL finished with status %d\n", msg->data.result); info ("cURL finished with status %d\n", msg->data.result);
if (m_uploadLogResult.starts_with ("{\"key\":\"")) json_error_t err;
auto const root = json_loads (m_uploadLogResult.c_str (), 0, &err);
if (json_is_object (root))
{ {
auto const key = m_uploadLogResult.substr (8, 10); auto const key = json_object_get (root, "key");
info ("https://hastebin.com/%s\n", key.c_str ()); if (json_is_string (key))
info (
"Log uploaded to https://pastie.io/%s\n", json_string_value (key));
} }
else
error ("Failed to upload log\n");
json_decref (root);
curl_multi_remove_handle (m_uploadLogCurlM, m_uploadLogCurl); curl_multi_remove_handle (m_uploadLogCurlM, m_uploadLogCurl);
curl_easy_cleanup (m_uploadLogCurl); curl_easy_cleanup (m_uploadLogCurl);
@ -898,11 +1035,17 @@ void FtpServer::loop ()
} }
} }
#ifndef __NDS__
// poll mDNS socket
if (m_socket && m_mdnsSocket)
mdns::handleSocket (m_mdnsSocket.get (), m_socket->sockName ());
#endif
{ {
std::vector<UniqueFtpSession> deadSessions; std::vector<UniqueFtpSession> deadSessions;
{ {
// remove dead sessions // remove dead sessions
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (m_lock); auto const lock = std::scoped_lock (m_lock);
#endif #endif
auto it = std::begin (m_sessions); auto it = std::begin (m_sessions);
@ -926,7 +1069,7 @@ void FtpServer::loop ()
if (!FtpSession::poll (m_sessions)) if (!FtpSession::poll (m_sessions))
handleNetworkLost (); handleNetworkLost ();
} }
#ifndef NDS #ifndef __NDS__
// avoid busy polling in background thread // avoid busy polling in background thread
else else
platform::Thread::sleep (16ms); platform::Thread::sleep (16ms);

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2022 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -22,7 +22,7 @@
#include "platform.h" #include "platform.h"
#ifndef __WIIU__ #if !defined(__WIIU__) && !defined(CLASSIC)
#include "imgui.h" #include "imgui.h"
#endif #endif
@ -77,7 +77,7 @@ struct Message
/// \brief Log messages /// \brief Log messages
std::vector<Message> s_messages; std::vector<Message> s_messages;
#ifndef NDS #ifndef __NDS__
/// \brief Log lock /// \brief Log lock
platform::Mutex s_lock; platform::Mutex s_lock;
#endif #endif
@ -85,7 +85,7 @@ platform::Mutex s_lock;
void drawLog () void drawLog ()
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (s_lock); auto const lock = std::scoped_lock (s_lock);
#endif #endif
@ -166,7 +166,7 @@ void drawLog ()
#ifndef CLASSIC #ifndef CLASSIC
std::string getLog () std::string getLog ()
{ {
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (s_lock); auto const lock = std::scoped_lock (s_lock);
#endif #endif
@ -204,6 +204,8 @@ void debug (char const *const fmt_, ...)
va_start (ap, fmt_); va_start (ap, fmt_);
addLog (DEBUGLOG, fmt_, ap); addLog (DEBUGLOG, fmt_, ap);
va_end (ap); va_end (ap);
#else
(void)fmt_;
#endif #endif
} }
@ -253,7 +255,8 @@ void addLog (LogLevel const level_, char const *const fmt_, va_list ap_)
if (level_ == DEBUGLOG) if (level_ == DEBUGLOG)
return; return;
#endif #endif
#ifndef NDS
#ifndef __NDS__
auto const lock = std::scoped_lock (s_lock); auto const lock = std::scoped_lock (s_lock);
#endif #endif
@ -296,7 +299,7 @@ void addLog (LogLevel const level_, std::string_view const message_)
c = '?'; c = '?';
} }
#ifndef NDS #ifndef __NDS__
auto const lock = std::scoped_lock (s_lock); auto const lock = std::scoped_lock (s_lock);
#endif #endif
#ifndef NDEBUG #ifndef NDEBUG

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2022 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -27,13 +27,15 @@
#endif #endif
#ifndef CLASSIC #ifndef CLASSIC
#include <imgui.h>
#include <curl/curl.h> #include <curl/curl.h>
#endif #endif
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
int main (int argc_, char *argv_[]) int main ()
{ {
#ifndef CLASSIC #ifndef CLASSIC
curl_global_init (CURL_GLOBAL_ALL); curl_global_init (CURL_GLOBAL_ALL);
@ -58,7 +60,7 @@ int main (int argc_, char *argv_[])
auto server = FtpServer::create (); auto server = FtpServer::create ();
while (platform::loop ()) while (!server->quit () && platform::loop ())
{ {
#ifndef NO_CONSOLE #ifndef NO_CONSOLE
server->draw (); server->draw ();

597
source/mdns.cpp Normal file
View File

@ -0,0 +1,597 @@
// ftpd is a server implementation based on the following:
// - RFC 959 (https://datatracker.ietf.org/doc/html/rfc959)
// - RFC 3659 (https://datatracker.ietf.org/doc/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
//
// ftpd implements mdns based on the following:
// - RFC 1035 (https://datatracker.ietf.org/doc/html/rfc1035)
// - RFC 6762 (https://datatracker.ietf.org/doc/html/rfc6762)
//
// Copyright (C) 2024 Michael Theall
//
// 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 <https://www.gnu.org/licenses/>.
#include "mdns.h"
#include "log.h"
#include "platform.h"
#include <arpa/inet.h>
#include <algorithm>
#include <array>
#include <bit>
#include <chrono>
#include <concepts>
#include <cstdlib>
#include <cstring>
#include <string>
#include <type_traits>
#include <vector>
using namespace std::chrono_literals;
static_assert (
std::endian::native == std::endian::big || std::endian::native == std::endian::little);
static_assert (sizeof (in_addr_t) == 4);
namespace
{
constexpr auto MDNS_TTL = 120;
SockAddr const s_multicastAddress{inet_addr ("224.0.0.251"), 5353};
platform::steady_clock::time_point s_lastAnnounce{};
platform::steady_clock::time_point s_lastProbe{};
std::string s_hostname = platform::hostname ();
std::string s_hostnameLocal = s_hostname + ".local";
enum class State
{
Probe1,
Probe2,
Probe3,
Announce1,
Announce2,
Complete,
Conflict,
};
auto s_state = State::Probe1;
#if __has_cpp_attribute(__cpp_lib_byteswap)
template <std::integral T>
using byteswap = std::byteswap<T>;
#else
template <std::integral T>
constexpr T byteswap (T const value_) noexcept
{
static_assert (std::has_unique_object_representations_v<T>, "T may not have padding bits");
auto buffer = std::bit_cast<std::array<std::byte, sizeof (T)>> (value_);
std::ranges::reverse (buffer);
return std::bit_cast<T> (buffer);
}
#endif
template <std::integral T>
constexpr T hton (T const value_) noexcept
{
if constexpr (std::endian::native == std::endian::big)
return value_;
else
return byteswap (value_);
}
template <std::integral T>
constexpr T ntoh (T const value_) noexcept
{
if constexpr (std::endian::native == std::endian::big)
return value_;
else
return byteswap (value_);
}
template <std::integral T, std::integral U>
void const *decode (void const *const buffer_, U &size_, T &out_, bool networkToHost_ = true)
{
if (!buffer_)
return nullptr;
if (size_ < 0 || static_cast<std::make_unsigned_t<T>> (size_) < sizeof (T))
return nullptr;
std::memcpy (&out_, buffer_, sizeof (T));
if (networkToHost_)
out_ = ntoh (out_);
size_ -= sizeof (T);
return static_cast<std::uint8_t const *> (buffer_) + sizeof (T);
}
template <std::integral T>
void const *decode (void const *buffer_, T &size_, std::string &out_)
{
auto p = static_cast<char const *> (buffer_);
auto const end = p + size_;
std::string result;
result.reserve (size_);
while (p < end && *p)
{
auto const len = *p++;
// punt on compressed labels
if (len & 0xC0)
return nullptr;
if (p + len >= end)
return nullptr;
if (!result.empty ())
result.push_back ('.');
result.insert (std::end (result), p, p + len);
p += len;
}
++p;
out_ = std::move (result);
size_ = end - p;
return p;
}
template <std::integral T, std::integral U>
void *encode (void *const buffer_, U &size_, T in_, bool hostToNetwork_ = true)
{
if (!buffer_)
return nullptr;
if (size_ < sizeof (T))
return nullptr;
if (hostToNetwork_)
in_ = hton (in_);
std::memcpy (buffer_, &in_, sizeof (T));
size_ -= sizeof (T);
return static_cast<std::uint8_t *> (buffer_) + sizeof (T);
}
template <std::integral T>
void *encode (void *const buffer_, T &size_, std::string const &in_)
{
// names are limited to 255 bytes
if (in_.size () > 0xFF)
return nullptr;
auto p = static_cast<char *> (buffer_);
auto const end = p + size_;
std::string::size_type prev = 0;
std::string::size_type pos = 0;
while (p < end && pos != std::string::npos)
{
pos = in_.find ('.', prev);
auto const label = std::string_view (in_).substr (prev, pos);
// labels are limited to 63 bytes
if (label.size () >= size_ || label.size () > 0x3F)
return nullptr;
p = static_cast<char *> (encode<std::uint8_t> (p, size_, label.size ()));
if (!p)
return nullptr;
std::memcpy (p, label.data (), label.size ());
p += label.size ();
if (pos != std::string::npos)
prev = pos + 1;
}
if (p == end)
return nullptr;
*p++ = 0;
size_ = end - p;
return p;
}
struct DNSHeader
{
std::uint16_t id{};
std::uint16_t flags{};
std::uint16_t qdCount{};
std::uint16_t anCount{};
std::uint16_t nsCount{};
std::uint16_t arCount{};
template <std::integral T>
void const *decode (void const *const buffer_, T &size_)
{
auto in = ::decode (buffer_, size_, id);
in = ::decode (buffer_, size_, flags);
in = ::decode (buffer_, size_, qdCount);
in = ::decode (buffer_, size_, anCount);
in = ::decode (buffer_, size_, nsCount);
in = ::decode (buffer_, size_, arCount);
return buffer_;
}
template <std::integral T>
void *encode (void *buffer_, T &size_)
{
buffer_ = ::encode (buffer_, size_, id);
buffer_ = ::encode (buffer_, size_, flags);
buffer_ = ::encode (buffer_, size_, qdCount);
buffer_ = ::encode (buffer_, size_, anCount);
buffer_ = ::encode (buffer_, size_, nsCount);
buffer_ = ::encode (buffer_, size_, arCount);
return buffer_;
}
};
struct QueryRecord
{
std::string qname{};
std::uint16_t qtype{};
std::uint16_t qclass{};
template <std::integral T>
void const *decode (void const *buffer_, T &size_)
{
buffer_ = ::decode (buffer_, size_, qname);
buffer_ = ::decode (buffer_, size_, qtype);
buffer_ = ::decode (buffer_, size_, qclass);
return buffer_;
}
template <std::integral T>
void *encode (void *buffer_, T &size_)
{
buffer_ = ::encode (buffer_, size_, qname);
buffer_ = ::encode (buffer_, size_, qtype);
buffer_ = ::encode (buffer_, size_, qclass);
return buffer_;
}
};
struct ResourceRecord
{
std::string rname{};
std::uint16_t rtype{};
std::uint16_t rclass{};
std::uint32_t rttl{};
std::uint16_t rlen{};
std::vector<std::uint8_t> rdata{};
template <std::integral T>
void const *decode (void const *buffer_, T &size_)
{
buffer_ = ::decode (buffer_, size_, rname);
buffer_ = ::decode (buffer_, size_, rtype);
buffer_ = ::decode (buffer_, size_, rclass);
buffer_ = ::decode (buffer_, size_, rttl);
buffer_ = ::decode (buffer_, size_, rlen);
return buffer_;
}
template <std::integral T>
void *encode (void *buffer_, T &size_)
{
if (rttl > std::numeric_limits<std::int32_t>::max ())
return nullptr;
buffer_ = ::encode (buffer_, size_, rname);
buffer_ = ::encode (buffer_, size_, rtype);
buffer_ = ::encode (buffer_, size_, rclass);
buffer_ = ::encode (buffer_, size_, rttl);
buffer_ = ::encode (buffer_, size_, rlen);
if (rlen > size_)
return nullptr;
rdata.resize (rlen);
std::memcpy (rdata.data (), buffer_, rlen);
size_ -= rlen;
return static_cast<std::uint8_t *> (buffer_) + rlen;
}
};
void probe (Socket *const socket_, std::string const &qname_)
{
std::vector<std::uint8_t> response (65536);
auto available = response.size ();
auto out = DNSHeader{.qdCount = 1}.encode (response.data (), available);
out = QueryRecord{.qname = qname_, .qtype = 255, .qclass = 1}.encode (out, available);
if (!out)
return;
info ("Probe mDNS %s\n", qname_.c_str ());
socket_->writeTo (response.data (), response.size () - available, s_multicastAddress);
s_lastProbe = platform::steady_clock::now ();
}
void announce (Socket *const socket_,
SockAddr const *srcAddr_,
std::uint16_t const id_,
std::uint16_t const flags_,
QueryRecord const &record_,
SockAddr const &addr_)
{
std::vector<std::uint8_t> response (65536);
auto available = response.size ();
// header
auto out = encode<std::uint16_t> (response.data (), available, id_);
out =
encode<std::uint16_t> (out, available, flags_ | (1 << 15) | (1 << 10)); // mark response/AA
out = encode<std::uint16_t> (out, available, 0);
out = encode<std::uint16_t> (out, available, 1);
out = encode<std::uint16_t> (out, available, 0);
out = encode<std::uint16_t> (out, available, 0);
// answer section
out = encode (out, available, record_.qname);
out = encode<std::uint16_t> (out, available, record_.qtype);
out = encode<std::uint16_t> (out, available, record_.qclass | (1 << 15)); // mark unique/flush
out = encode<std::uint32_t> (out, available, MDNS_TTL);
out = encode<std::uint16_t> (out, available, sizeof (in_addr_t));
out = encode<in_addr_t> (
out, available, static_cast<sockaddr_in const &> (addr_).sin_addr.s_addr, false);
if (!out)
return;
auto const preferUnicast = srcAddr_ && ((record_.qclass >> 15) & 0x1);
if (preferUnicast)
{
auto const name = std::string (addr_.name ());
info (
"Respond mDNS %s %s to %s\n", record_.qname.c_str (), name.c_str (), srcAddr_->name ());
socket_->writeTo (response.data (), response.size () - available, *srcAddr_);
}
auto const now = platform::steady_clock::now ();
if (!preferUnicast || now - s_lastAnnounce > std::chrono::seconds (MDNS_TTL / 4))
{
info ("Announce mDNS %s %s\n", record_.qname.c_str (), addr_.name ());
socket_->writeTo (response.data (), response.size () - available, s_multicastAddress);
s_lastAnnounce = now;
}
}
}
void mdns::setHostname (std::string hostname_)
{
if (hostname_.empty ())
hostname_ = platform::hostname ();
if (s_hostname == hostname_)
return;
s_hostname = std::move (hostname_);
s_hostnameLocal = s_hostname + ".local";
s_state = State::Probe1;
s_lastProbe = platform::steady_clock::now ();
}
UniqueSocket mdns::createSocket ()
{
auto socket = Socket::create (Socket::eDatagram);
if (!socket)
return nullptr;
if (!socket->setReuseAddress ())
return nullptr;
auto iface = SockAddr::AnyIPv4;
iface.setPort (s_multicastAddress.port ());
if (!socket->bind (iface))
return nullptr;
if (!socket->joinMulticastGroup (s_multicastAddress, iface))
return nullptr;
s_state = State::Probe1;
s_lastProbe = platform::steady_clock::now ();
return socket;
}
void mdns::handleSocket (Socket *socket_, SockAddr const &addr_)
{
if (!socket_)
return;
// only support IPv4 for now
if (addr_.domain () != SockAddr::Domain::IPv4)
return;
auto const now = platform::steady_clock::now ();
switch (s_state)
{
case State::Probe1:
case State::Probe2:
case State::Probe3:
if (now - s_lastProbe > 250ms)
{
probe (socket_, s_hostname);
s_state = static_cast<State> (static_cast<int> (s_state) + 1);
}
break;
case State::Announce1:
case State::Announce2:
if (now - s_lastAnnounce > 1s)
{
announce (socket_,
nullptr,
0,
0,
QueryRecord{.qname = s_hostname, .qtype = 1, .qclass = 1},
addr_);
s_state = static_cast<State> (static_cast<int> (s_state) + 1);
}
default:
break;
}
Socket::PollInfo pollInfo{*socket_, POLLIN, 0};
auto const rc = Socket::poll (&pollInfo, 1, 0ms);
if (rc <= 0 || !(pollInfo.revents & POLLIN))
return;
SockAddr srcAddr;
std::vector<std::uint8_t> buffer (65536);
auto bytes = socket_->readFrom (buffer.data (), buffer.size (), srcAddr);
if (bytes <= 0)
return;
// only support IPv4 for now
if (srcAddr.domain () != SockAddr::Domain::IPv4)
return;
// ignore loopback
if (std::memcmp (&reinterpret_cast<sockaddr_in const &> (srcAddr).sin_addr.s_addr,
&reinterpret_cast<sockaddr_in const &> (addr_).sin_addr.s_addr,
sizeof (in_addr_t)) == 0)
return;
std::uint16_t id;
std::uint16_t flags;
std::uint16_t qdCount;
std::uint16_t anCount;
std::uint16_t nsCount;
std::uint16_t arCount;
// parse header
auto in = decode (buffer.data (), bytes, id);
in = decode (in, bytes, flags);
in = decode (in, bytes, qdCount);
in = decode (in, bytes, anCount);
in = decode (in, bytes, nsCount);
in = decode (in, bytes, arCount);
if (!in)
return;
auto const qr = (flags >> 15) & 0x1;
// ill-formed on queries and responses
auto const opcode = (flags >> 11) & 0xF;
if (opcode != 0)
return;
// ill-formed on queries
if (!qr && ((flags >> 10) & 0x1))
return;
// punt on truncated messages
if ((flags >> 9) & 0x1)
return;
// ill-formed on queries
if (!qr && ((flags >> 7) & 0x1))
return;
// must be zero
if ((flags >> 4) & 0x7)
return;
// ill-formed on queries and responses
if ((flags >> 0) & 0xF)
return;
// std::vector<std::uint8_t> response (65536);
// void *out = response.data ();
// auto available = response.size ();
std::vector<ResourceRecord> answers;
bool announced = false;
for (unsigned i = 0; i < qdCount; ++i)
{
QueryRecord record;
in = record.decode (in, bytes);
if (!in)
return;
// only respond to queries
if (qr)
continue;
// only accept A or ANY type
if (record.qtype != 1 && record.qtype != 255)
continue;
// only accept IN or ANY class
if ((record.qclass & 0x7FFF) != 1 && (record.qclass & 0x7FFF) != 255)
continue;
if (record.qname != s_hostname && record.qname != s_hostnameLocal)
continue;
if (!announced)
{
std::vector<std::uint8_t> data (sizeof (in_addr_t));
auto n = data.size ();
encode (
data.data (), n, static_cast<sockaddr_in const &> (addr_).sin_addr.s_addr, false);
answers.emplace_back (ResourceRecord{// answer
.rname = record.qname,
.rtype = 1,
.rclass = static_cast<std::uint16_t> (1 | (1 << 15)),
.rttl = MDNS_TTL,
.rlen = sizeof (in_addr_t),
.rdata = std::move (data)});
announce (socket_, &srcAddr, id, flags, record, addr_);
announced = true;
}
}
for (unsigned i = 0; i < anCount; ++i)
{
ResourceRecord record;
in = record.decode (in, bytes);
if (!in)
return;
}
}

209
source/posix/collate.c Normal file
View File

@ -0,0 +1,209 @@
/*-
* Copyright (c) 1995 Alex Tatmanjants <alex@elvisti.kiev.ua>
* at Electronni Visti IA, Kiev, Ukraine.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "collate.h"
extern char *_PathLocale;
int __collate_load_error = 1;
int __collate_substitute_nontrivial;
char __collate_version[STR_LEN];
u_char __collate_substitute_table[UCHAR_MAX + 1][STR_LEN];
struct __collate_st_char_pri __collate_char_pri_table[UCHAR_MAX + 1];
struct __collate_st_chain_pri __collate_chain_pri_table[TABLE_SIZE];
#define FREAD(a, b, c, d) \
do \
{ \
if (fread (a, b, c, d) != c) \
{ \
fclose (d); \
return -1; \
} \
} while (0)
void __collate_err (int ex, const char *f);
int __collate_load_tables (encoding)
char *encoding;
{
char buf[PATH_MAX];
FILE *fp;
int i, save_load_error;
save_load_error = __collate_load_error;
__collate_load_error = 1;
if (!encoding)
{
__collate_load_error = save_load_error;
return -1;
}
if (!strcmp (encoding, "C") || !strcmp (encoding, "POSIX"))
return 0;
if (!_PathLocale)
{
__collate_load_error = save_load_error;
return -1;
}
/* Range checking not needed, encoding has fixed size */
(void)strcpy (buf, _PathLocale);
(void)strcat (buf, "/");
(void)strcat (buf, encoding);
(void)strcat (buf, "/LC_COLLATE");
if ((fp = fopen (buf, "r")) == NULL)
{
__collate_load_error = save_load_error;
return -1;
}
FREAD (__collate_version, sizeof (__collate_version), 1, fp);
if (strcmp (__collate_version, COLLATE_VERSION) != 0)
{
fclose (fp);
return -1;
}
FREAD (__collate_substitute_table, sizeof (__collate_substitute_table), 1, fp);
FREAD (__collate_char_pri_table, sizeof (__collate_char_pri_table), 1, fp);
FREAD (__collate_chain_pri_table, sizeof (__collate_chain_pri_table), 1, fp);
fclose (fp);
__collate_load_error = 0;
__collate_substitute_nontrivial = 0;
for (i = 0; i < UCHAR_MAX + 1; i++)
{
if (__collate_substitute_table[i][0] != i || __collate_substitute_table[i][1] != 0)
{
__collate_substitute_nontrivial = 1;
break;
}
}
return 0;
}
u_char *__collate_substitute (s) const u_char *s;
{
int dest_len, len, nlen;
int delta = strlen ((const char *)s);
u_char *dest_str = NULL;
if (s == NULL || *s == '\0')
return __collate_strdup ((u_char *)"");
delta += delta / 8;
dest_str = (u_char *)malloc (dest_len = delta);
if (dest_str == NULL)
__collate_err (EXIT_FAILURE, __FUNCTION__);
len = 0;
while (*s)
{
nlen = len + strlen ((const char *)__collate_substitute_table[*s]);
if (dest_len <= nlen)
{
dest_str = reallocf (dest_str, dest_len = nlen + delta);
if (dest_str == NULL)
__collate_err (EXIT_FAILURE, __FUNCTION__);
}
strcpy ((char *)dest_str + len, (const char *)__collate_substitute_table[*s++]);
len = nlen;
}
return dest_str;
}
void __collate_lookup (t, len, prim, sec) const u_char *t;
int *len, *prim, *sec;
{
struct __collate_st_chain_pri *p2;
*len = 1;
*prim = *sec = 0;
for (p2 = __collate_chain_pri_table; p2->str[0]; p2++)
{
if (strncmp ((const char *)t, (const char *)p2->str, strlen ((const char *)p2->str)) == 0)
{
*len = strlen ((const char *)p2->str);
*prim = p2->prim;
*sec = p2->sec;
return;
}
}
*prim = __collate_char_pri_table[*t].prim;
*sec = __collate_char_pri_table[*t].sec;
}
u_char *__collate_strdup (s)
u_char *s;
{
u_char *t = (u_char *)strdup ((const char *)s);
if (t == NULL)
__collate_err (EXIT_FAILURE, __FUNCTION__);
return t;
}
void __collate_err (int ex, const char *f)
{
const char *s;
int serrno = errno;
int dummy;
/* Be careful to change write counts if you change the strings */
write (STDERR_FILENO, "collate_error: ", 15);
write (STDERR_FILENO, f, strlen (f));
write (STDERR_FILENO, ": ", 2);
s = _strerror_r (_REENT, serrno, 1, &dummy);
write (STDERR_FILENO, s, strlen (s));
write (STDERR_FILENO, "\n", 1);
exit (ex);
}
#ifdef COLLATE_DEBUG
void __collate_print_tables ()
{
int i;
struct __collate_st_chain_pri *p2;
printf ("Substitute table:\n");
for (i = 0; i < UCHAR_MAX + 1; i++)
if (i != *__collate_substitute_table[i])
printf ("\t'%c' --> \"%s\"\n", i, __collate_substitute_table[i]);
printf ("Chain priority table:\n");
for (p2 = __collate_chain_pri_table; p2->str[0]; p2++)
printf ("\t\"%s\" : %d %d\n\n", p2->str, p2->prim, p2->sec);
printf ("Char priority table:\n");
for (i = 0; i < UCHAR_MAX + 1; i++)
printf ("\t'%c' : %d %d\n",
i,
__collate_char_pri_table[i].prim,
__collate_char_pri_table[i].sec);
}
#endif

69
source/posix/collate.h Normal file
View File

@ -0,0 +1,69 @@
/*-
* Copyright (c) 1995 Alex Tatmanjants <alex@elvisti.kiev.ua>
* at Electronni Visti IA, Kiev, Ukraine.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD: src/lib/libc/locale/collate.h,v 1.11 2002/03/21 22:46:54 obrien Exp $
*/
#ifndef _COLLATE_H_
#define _COLLATE_H_
#include <limits.h>
#include <sys/cdefs.h>
#include <sys/types.h>
#define STR_LEN 10
#define TABLE_SIZE 100
#define COLLATE_VERSION "1.0\n"
struct __collate_st_char_pri
{
int prim, sec;
};
struct __collate_st_chain_pri
{
u_char str[STR_LEN];
int prim, sec;
};
extern int __collate_load_error;
extern int __collate_substitute_nontrivial;
extern char __collate_version[STR_LEN];
extern u_char __collate_substitute_table[UCHAR_MAX + 1][STR_LEN];
extern struct __collate_st_char_pri __collate_char_pri_table[UCHAR_MAX + 1];
extern struct __collate_st_chain_pri __collate_chain_pri_table[TABLE_SIZE];
__BEGIN_DECLS
u_char *__collate_strdup (u_char *);
u_char *__collate_substitute (const u_char *);
int __collate_load_tables (char *);
void __collate_lookup (const u_char *, int *, int *, int *);
int __collate_range_cmp (int, int);
#ifdef COLLATE_DEBUG
void __collate_print_tables (void);
#endif
__END_DECLS
#endif /* !_COLLATE_H_ */

88
source/posix/collcmp.c Normal file
View File

@ -0,0 +1,88 @@
/*
* Copyright (C) 1996 by Andrey A. Chernov, Moscow, Russia.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#include <sys/cdefs.h>
#define ASCII_COMPATIBLE_COLLATE /* see share/colldef */
#include "collate.h"
#include <string.h>
#ifndef ASCII_COMPATIBLE_COLLATE
#include <ctype.h>
#endif
/*
* Compare two characters converting collate information
* into ASCII-compatible range, it allows to handle
* "[a-z]"-type ranges with national characters.
*/
int __collate_range_cmp (c1, c2)
int c1, c2;
{
static char s1[2], s2[2];
int ret;
#ifndef ASCII_COMPATIBLE_COLLATE
int as1, as2, al1, al2;
#endif
c1 &= UCHAR_MAX;
c2 &= UCHAR_MAX;
if (c1 == c2)
return (0);
#ifndef ASCII_COMPATIBLE_COLLATE
as1 = isascii (c1);
as2 = isascii (c2);
al1 = isalpha (c1);
al2 = isalpha (c2);
if (as1 || as2 || al1 || al2)
{
if ((as1 && as2) || (!al1 && !al2))
return (c1 - c2);
if (al1 && !al2)
{
if (isupper (c1))
return ('A' - c2);
else
return ('a' - c2);
}
else if (al2 && !al1)
{
if (isupper (c2))
return (c1 - 'A');
else
return (c1 - 'a');
}
}
#endif
s1[0] = c1;
s2[0] = c2;
if ((ret = strcoll (s1, s2)) != 0)
return (ret);
return (c1 - c2);
}

839
source/posix/glob.c Normal file
View File

@ -0,0 +1,839 @@
/*
* Copyright (c) 1989, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Guido van Rossum.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifdef __CYGWIN__
#define _NO_GLOB /* Cygwin provides its own glob. */
#endif
#ifndef _NO_GLOB
#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93";
#endif /* LIBC_SCCS and not lint */
#include <sys/cdefs.h>
/*
* glob(3) -- a superset of the one defined in POSIX 1003.2.
*
* The [!...] convention to negate a range is supported (SysV, Posix, ksh).
*
* Optional extra services, controlled by flags not defined by POSIX:
*
* GLOB_QUOTE:
* Escaping convention: \ inhibits any special meaning the following
* character might have (except \ at end of string is retained).
* GLOB_MAGCHAR:
* Set in gl_flags if pattern contained a globbing character.
* GLOB_NOMAGIC:
* Same as GLOB_NOCHECK, but it will only append pattern if it did
* not contain any magic characters. [Used in csh style globbing]
* GLOB_ALTDIRFUNC:
* Use alternately specified directory access functions.
* GLOB_TILDE:
* expand ~user/foo to the /home/dir/of/user/foo
* GLOB_BRACE:
* expand {1,2}{a,b} to 1a 1b 2a 2b
* gl_matchc:
* Number of matches in the current invocation of glob.
*/
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <glob.h>
#include <limits.h>
#include <pwd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "collate.h"
#define DOLLAR '$'
#define DOT '.'
#define EOS '\0'
#define LBRACKET '['
#define NOT '!'
#define QUESTION '?'
#define QUOTE '\\'
#define RANGE '-'
#define RBRACKET ']'
#define SEP '/'
#define STAR '*'
#define TILDE '~'
#define UNDERSCORE '_'
#define LBRACE '{'
#define RBRACE '}'
#define SLASH '/'
#define COMMA ','
#ifndef DEBUG
#define M_QUOTE 0x8000
#define M_PROTECT 0x4000
#define M_MASK 0xffff
#define M_ASCII 0x00ff
typedef u_short Char;
#else
#define M_QUOTE 0x80
#define M_PROTECT 0x40
#define M_MASK 0xff
#define M_ASCII 0x7f
typedef char Char;
#endif
#define CHAR(c) ((Char)((c)&M_ASCII))
#define META(c) ((Char)((c) | M_QUOTE))
#define M_ALL META ('*')
#define M_END META (']')
#define M_NOT META ('!')
#define M_ONE META ('?')
#define M_RNG META ('-')
#define M_SET META ('[')
#define ismeta(c) (((c)&M_QUOTE) != 0)
static int compare (const void *, const void *);
static int g_Ctoc (const Char *, char *, u_int);
static int g_lstat (Char *, struct stat *, glob_t *);
static DIR *g_opendir (Char *, glob_t *);
static Char *g_strchr (Char *, int);
#ifdef notdef
static Char *g_strcat (Char *, const Char *);
#endif
static int g_stat (Char *, struct stat *, glob_t *);
static int glob0 (const Char *, glob_t *, int *);
static int glob1 (Char *, glob_t *, int *);
static int glob2 (Char *, Char *, Char *, Char *, glob_t *, int *);
static int glob3 (Char *, Char *, Char *, Char *, Char *, glob_t *, int *);
static int globextend (const Char *, glob_t *, int *);
static int globexp1 (const Char *, glob_t *, int *);
static int globexp2 (const Char *, const Char *, glob_t *, int *, int *);
static int match (Char *, Char *, Char *);
#ifdef DEBUG
static void qprintf (const char *, Char *);
#endif
int glob (pattern, flags, errfunc, pglob) const char *__restrict pattern;
int flags, (*errfunc) (const char *, int);
glob_t *__restrict pglob;
{
const u_char *patnext;
int c, limit;
Char *bufnext, *bufend, patbuf[MAXPATHLEN];
patnext = (u_char *)pattern;
if (!(flags & GLOB_APPEND))
{
pglob->gl_pathc = 0;
pglob->gl_pathv = NULL;
if (!(flags & GLOB_DOOFFS))
pglob->gl_offs = 0;
}
if (flags & GLOB_LIMIT)
{
limit = pglob->gl_matchc;
if (limit == 0)
limit = ARG_MAX;
}
else
limit = 0;
pglob->gl_flags = flags & ~GLOB_MAGCHAR;
pglob->gl_errfunc = errfunc;
pglob->gl_matchc = 0;
bufnext = patbuf;
bufend = bufnext + MAXPATHLEN - 1;
if (flags & GLOB_QUOTE)
{
/* Protect the quoted characters. */
while (bufnext < bufend && (c = *patnext++) != EOS)
if (c == QUOTE)
{
if ((c = *patnext++) == EOS)
{
c = QUOTE;
--patnext;
}
*bufnext++ = c | M_PROTECT;
}
else
*bufnext++ = c;
}
else
while (bufnext < bufend && (c = *patnext++) != EOS)
*bufnext++ = c;
*bufnext = EOS;
if (flags & GLOB_BRACE)
return globexp1 (patbuf, pglob, &limit);
else
return glob0 (patbuf, pglob, &limit);
}
/*
* Expand recursively a glob {} pattern. When there is no more expansion
* invoke the standard globbing routine to glob the rest of the magic
* characters
*/
static int globexp1 (pattern, pglob, limit) const Char *pattern;
glob_t *pglob;
int *limit;
{
const Char *ptr = pattern;
int rv;
/* Protect a single {}, for find(1), like csh */
if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
return glob0 (pattern, pglob, limit);
while ((ptr = (const Char *)g_strchr ((Char *)ptr, LBRACE)) != NULL)
if (!globexp2 (ptr, pattern, pglob, &rv, limit))
return rv;
return glob0 (pattern, pglob, limit);
}
/*
* Recursive brace globbing helper. Tries to expand a single brace.
* If it succeeds then it invokes globexp1 with the new pattern.
* If it fails then it tries to glob the rest of the pattern and returns.
*/
static int globexp2 (ptr, pattern, pglob, rv, limit) const Char *ptr, *pattern;
glob_t *pglob;
int *rv, *limit;
{
int i;
Char *lm, *ls;
const Char *pe, *pm, *pl;
Char patbuf[MAXPATHLEN];
/* copy part up to the brace */
for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
continue;
*lm = EOS;
ls = lm;
/* Find the balanced brace */
for (i = 0, pe = ++ptr; *pe; pe++)
if (*pe == LBRACKET)
{
/* Ignore everything between [] */
for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
continue;
if (*pe == EOS)
{
/*
* We could not find a matching RBRACKET.
* Ignore and just look for RBRACE
*/
pe = pm;
}
}
else if (*pe == LBRACE)
i++;
else if (*pe == RBRACE)
{
if (i == 0)
break;
i--;
}
/* Non matching braces; just glob the pattern */
if (i != 0 || *pe == EOS)
{
*rv = glob0 (patbuf, pglob, limit);
return 0;
}
for (i = 0, pl = pm = ptr; pm <= pe; pm++)
switch (*pm)
{
case LBRACKET:
/* Ignore everything between [] */
for (pl = pm++; *pm != RBRACKET && *pm != EOS; pm++)
continue;
if (*pm == EOS)
{
/*
* We could not find a matching RBRACKET.
* Ignore and just look for RBRACE
*/
pm = pl;
}
break;
case LBRACE:
i++;
break;
case RBRACE:
if (i)
{
i--;
break;
}
/* FALLTHROUGH */
case COMMA:
if (i && *pm == COMMA)
break;
else
{
/* Append the current string */
for (lm = ls; (pl < pm); *lm++ = *pl++)
continue;
/*
* Append the rest of the pattern after the
* closing brace
*/
for (pl = pe + 1; (*lm++ = *pl++) != EOS;)
continue;
/* Expand the current pattern */
#ifdef DEBUG
qprintf ("globexp2:", patbuf);
#endif
*rv = globexp1 (patbuf, pglob, limit);
/* move after the comma, to the next string */
pl = pm + 1;
}
break;
default:
break;
}
*rv = 0;
return 0;
}
/*
* The main glob() routine: compiles the pattern (optionally processing
* quotes), calls glob1() to do the real pattern matching, and finally
* sorts the list (unless unsorted operation is requested). Returns 0
* if things went well, nonzero if errors occurred. It is not an error
* to find no matches.
*/
static int glob0 (pattern, pglob, limit) const Char *pattern;
glob_t *pglob;
int *limit;
{
const Char *qpatnext;
int c, err, oldpathc;
Char *bufnext, patbuf[MAXPATHLEN];
qpatnext = pattern;
oldpathc = pglob->gl_pathc;
bufnext = patbuf;
/* We don't need to check for buffer overflow any more. */
while ((c = *qpatnext++) != EOS)
{
switch (c)
{
case LBRACKET:
c = *qpatnext;
if (c == NOT)
++qpatnext;
if (*qpatnext == EOS || g_strchr ((Char *)qpatnext + 1, RBRACKET) == NULL)
{
*bufnext++ = LBRACKET;
if (c == NOT)
--qpatnext;
break;
}
*bufnext++ = M_SET;
if (c == NOT)
*bufnext++ = M_NOT;
c = *qpatnext++;
do
{
*bufnext++ = CHAR (c);
if (*qpatnext == RANGE && (c = qpatnext[1]) != RBRACKET)
{
*bufnext++ = M_RNG;
*bufnext++ = CHAR (c);
qpatnext += 2;
}
} while ((c = *qpatnext++) != RBRACKET);
pglob->gl_flags |= GLOB_MAGCHAR;
*bufnext++ = M_END;
break;
case QUESTION:
pglob->gl_flags |= GLOB_MAGCHAR;
*bufnext++ = M_ONE;
break;
case STAR:
pglob->gl_flags |= GLOB_MAGCHAR;
/* collapse adjacent stars to one,
* to avoid exponential behavior
*/
if (bufnext == patbuf || bufnext[-1] != M_ALL)
*bufnext++ = M_ALL;
break;
default:
*bufnext++ = CHAR (c);
break;
}
}
*bufnext = EOS;
#ifdef DEBUG
qprintf ("glob0:", patbuf);
#endif
if ((err = glob1 (patbuf, pglob, limit)) != 0)
return (err);
/*
* If there was no match we are going to append the pattern
* if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
* and the pattern did not contain any magic characters
* GLOB_NOMAGIC is there just for compatibility with csh.
*/
if (pglob->gl_pathc == oldpathc &&
((pglob->gl_flags & GLOB_NOCHECK) ||
((pglob->gl_flags & GLOB_NOMAGIC) && !(pglob->gl_flags & GLOB_MAGCHAR))))
return (globextend (pattern, pglob, limit));
else if (!(pglob->gl_flags & GLOB_NOSORT))
qsort (pglob->gl_pathv + pglob->gl_offs + oldpathc,
pglob->gl_pathc - oldpathc,
sizeof (char *),
compare);
return (0);
}
static int compare (p, q) const void *p, *q;
{
return (strcmp (*(char **)p, *(char **)q));
}
static int glob1 (pattern, pglob, limit)
Char *pattern;
glob_t *pglob;
int *limit;
{
Char pathbuf[MAXPATHLEN];
/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
if (*pattern == EOS)
return (0);
return (glob2 (pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, pattern, pglob, limit));
}
/*
* The functions glob2 and glob3 are mutually recursive; there is one level
* of recursion for each segment in the pattern that contains one or more
* meta characters.
*/
static int glob2 (pathbuf, pathend, pathend_last, pattern, pglob, limit)
Char *pathbuf, *pathend, *pathend_last, *pattern;
glob_t *pglob;
int *limit;
{
struct stat sb;
Char *p, *q;
int anymeta;
/*
* Loop over pattern segments until end of pattern or until
* segment with meta character found.
*/
for (anymeta = 0;;)
{
if (*pattern == EOS)
{ /* End of pattern? */
*pathend = EOS;
if (g_lstat (pathbuf, &sb, pglob))
return (0);
if (((pglob->gl_flags & GLOB_MARK) && pathend[-1] != SEP) &&
(S_ISDIR (sb.st_mode) ||
(S_ISLNK (sb.st_mode) && (g_stat (pathbuf, &sb, pglob) == 0) &&
S_ISDIR (sb.st_mode))))
{
if (pathend + 1 > pathend_last)
return (1);
*pathend++ = SEP;
*pathend = EOS;
}
++pglob->gl_matchc;
return (globextend (pathbuf, pglob, limit));
}
/* Find end of next segment, copy tentatively to pathend. */
q = pathend;
p = pattern;
while (*p != EOS && *p != SEP)
{
if (ismeta (*p))
anymeta = 1;
if (q + 1 > pathend_last)
return (1);
*q++ = *p++;
}
if (!anymeta)
{ /* No expansion, do next segment. */
pathend = q;
pattern = p;
while (*pattern == SEP)
{
if (pathend + 1 > pathend_last)
return (1);
*pathend++ = *pattern++;
}
}
else /* Need expansion, recurse. */
return (glob3 (pathbuf, pathend, pathend_last, pattern, p, pglob, limit));
}
/* NOTREACHED */
}
static int glob3 (pathbuf, pathend, pathend_last, pattern, restpattern, pglob, limit)
Char *pathbuf, *pathend, *pathend_last, *pattern, *restpattern;
glob_t *pglob;
int *limit;
{
struct dirent *dp;
DIR *dirp;
int err;
char buf[MAXPATHLEN];
/*
* The readdirfunc declaration can't be prototyped, because it is
* assigned, below, to two functions which are prototyped in glob.h
* and dirent.h as taking pointers to differently typed opaque
* structures.
*/
struct dirent *(*readdirfunc) ();
if (pathend > pathend_last)
return (1);
*pathend = EOS;
errno = 0;
if ((dirp = g_opendir (pathbuf, pglob)) == NULL)
{
/* TODO: don't call for ENOENT or ENOTDIR? */
if (pglob->gl_errfunc)
{
if (g_Ctoc (pathbuf, buf, sizeof (buf)))
return (GLOB_ABEND);
if (pglob->gl_errfunc (buf, errno) || pglob->gl_flags & GLOB_ERR)
return (GLOB_ABEND);
}
return (0);
}
err = 0;
/* Search directory for matching names. */
if (pglob->gl_flags & GLOB_ALTDIRFUNC)
readdirfunc = pglob->gl_readdir;
else
readdirfunc = readdir;
while ((dp = (*readdirfunc) (dirp)))
{
u_char *sc;
Char *dc;
/* Initial DOT must be matched literally. */
if (dp->d_name[0] == DOT && *pattern != DOT)
continue;
dc = pathend;
sc = (u_char *)dp->d_name;
while (dc < pathend_last && (*dc++ = *sc++) != EOS)
;
if (!match (pathend, pattern, restpattern))
{
*pathend = EOS;
continue;
}
err = glob2 (pathbuf, --dc, pathend_last, restpattern, pglob, limit);
if (err)
break;
}
if (pglob->gl_flags & GLOB_ALTDIRFUNC)
(*pglob->gl_closedir) (dirp);
else
closedir (dirp);
return (err);
}
/*
* Extend the gl_pathv member of a glob_t structure to accomodate a new item,
* add the new item, and update gl_pathc.
*
* This assumes the BSD realloc, which only copies the block when its size
* crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
* behavior.
*
* Return 0 if new item added, error code if memory couldn't be allocated.
*
* Invariant of the glob_t structure:
* Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
* gl_pathv points to (gl_offs + gl_pathc + 1) items.
*/
static int globextend (path, pglob, limit) const Char *path;
glob_t *pglob;
int *limit;
{
char **pathv;
int i;
u_int newsize, len;
char *copy;
const Char *p;
if (*limit && pglob->gl_pathc > *limit)
{
errno = 0;
return (GLOB_NOSPACE);
}
newsize = sizeof (*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);
pathv = pglob->gl_pathv ? realloc ((char *)pglob->gl_pathv, newsize) : malloc (newsize);
if (pathv == NULL)
{
if (pglob->gl_pathv)
{
free (pglob->gl_pathv);
pglob->gl_pathv = NULL;
}
return (GLOB_NOSPACE);
}
if (pglob->gl_pathv == NULL && pglob->gl_offs > 0)
{
/* first time around -- clear initial gl_offs items */
pathv += pglob->gl_offs;
for (i = pglob->gl_offs; --i >= 0;)
*--pathv = NULL;
}
pglob->gl_pathv = pathv;
for (p = path; *p++;)
continue;
len = (size_t)(p - path);
if ((copy = malloc (len)) != NULL)
{
if (g_Ctoc (path, copy, len))
{
free (copy);
return (GLOB_NOSPACE);
}
pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
}
pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
return (copy == NULL ? GLOB_NOSPACE : 0);
}
/*
* pattern matching function for filenames. Each occurrence of the *
* pattern causes a recursion level.
*/
static int match (name, pat, patend)
Char *name, *pat, *patend;
{
int ok, negate_range;
Char c, k;
while (pat < patend)
{
c = *pat++;
switch (c & M_MASK)
{
case M_ALL:
if (pat == patend)
return (1);
do
if (match (name, pat, patend))
return (1);
while (*name++ != EOS);
return (0);
case M_ONE:
if (*name++ == EOS)
return (0);
break;
case M_SET:
ok = 0;
if ((k = *name++) == EOS)
return (0);
if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
++pat;
while (((c = *pat++) & M_MASK) != M_END)
if ((*pat & M_MASK) == M_RNG)
{
if (__collate_load_error
? CHAR (c) <= CHAR (k) && CHAR (k) <= CHAR (pat[1])
: __collate_range_cmp (CHAR (c), CHAR (k)) <= 0 &&
__collate_range_cmp (CHAR (k), CHAR (pat[1])) <= 0)
ok = 1;
pat += 2;
}
else if (c == k)
ok = 1;
if (ok == negate_range)
return (0);
break;
default:
if (*name++ != c)
return (0);
break;
}
}
return (*name == EOS);
}
/* Free allocated data belonging to a glob_t structure. */
void globfree (pglob) glob_t *pglob;
{
int i;
char **pp;
if (pglob->gl_pathv != NULL)
{
pp = pglob->gl_pathv + pglob->gl_offs;
for (i = pglob->gl_pathc; i--; ++pp)
if (*pp)
free (*pp);
free (pglob->gl_pathv);
pglob->gl_pathv = NULL;
}
}
static DIR *g_opendir (str, pglob)
Char *str;
glob_t *pglob;
{
char buf[MAXPATHLEN];
if (!*str)
strcpy (buf, ".");
else
{
if (g_Ctoc (str, buf, sizeof (buf)))
return (NULL);
}
if (pglob->gl_flags & GLOB_ALTDIRFUNC)
return ((*pglob->gl_opendir) (buf));
return (opendir (buf));
}
static int g_lstat (fn, sb, pglob)
Char *fn;
struct stat *sb;
glob_t *pglob;
{
char buf[MAXPATHLEN];
if (g_Ctoc (fn, buf, sizeof (buf)))
{
errno = ENAMETOOLONG;
return (-1);
}
if (pglob->gl_flags & GLOB_ALTDIRFUNC)
return ((*pglob->gl_lstat) (buf, sb));
return (lstat (buf, sb));
}
static int g_stat (fn, sb, pglob)
Char *fn;
struct stat *sb;
glob_t *pglob;
{
char buf[MAXPATHLEN];
if (g_Ctoc (fn, buf, sizeof (buf)))
{
errno = ENAMETOOLONG;
return (-1);
}
if (pglob->gl_flags & GLOB_ALTDIRFUNC)
return ((*pglob->gl_stat) (buf, sb));
return (stat (buf, sb));
}
static Char *g_strchr (str, ch)
Char *str;
int ch;
{
do
{
if (*str == ch)
return (str);
} while (*str++);
return (NULL);
}
static int g_Ctoc (str, buf, len) const Char *str;
char *buf;
u_int len;
{
while (len--)
{
if ((*buf++ = *str++) == '\0')
return (0);
}
return (1);
}
#ifdef DEBUG
static void qprintf (str, s) const char *str;
Char *s;
{
Char *p;
(void)printf ("%s:\n", str);
for (p = s; *p; p++)
(void)printf ("%c", CHAR (*p));
(void)printf ("\n");
for (p = s; *p; p++)
(void)printf ("%c", *p & M_PROTECT ? '"' : ' ');
(void)printf ("\n");
for (p = s; *p; p++)
(void)printf ("%c", ismeta (*p) ? '_' : ' ');
(void)printf ("\n");
}
#endif
#endif /* !_NO_GLOB */

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -26,11 +26,79 @@
#include <cstdlib> #include <cstdlib>
#include <cstring> #include <cstring>
#ifdef __3DS__
static_assert (sizeof (sockaddr_storage) == 0x1c);
#endif
namespace
{
in_addr inaddr_any = {.s_addr = htonl (INADDR_ANY)};
std::strong_ordering
strongMemCompare (void const *const a_, void const *const b_, std::size_t const size_)
{
auto const cmp = std::memcmp (a_, b_, size_);
if (cmp < 0)
return std::strong_ordering::less;
if (cmp > 0)
return std::strong_ordering::greater;
return std::strong_ordering::equal;
}
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
SockAddr const SockAddr::AnyIPv4{inaddr_any};
#ifndef NO_IPV6
SockAddr const SockAddr::AnyIPv6{in6addr_any};
#endif
SockAddr::~SockAddr () = default; SockAddr::~SockAddr () = default;
SockAddr::SockAddr () = default; SockAddr::SockAddr () = default;
SockAddr::SockAddr (Domain const domain_)
{
switch (domain_)
{
case Domain::IPv4:
*this = AnyIPv4;
break;
#ifndef NO_IPV6
case Domain::IPv6:
*this = AnyIPv6;
break;
#endif
default:
std::abort ();
}
}
SockAddr::SockAddr (in_addr_t const addr_, std::uint16_t const port_)
: SockAddr (in_addr{.s_addr = addr_}, port_)
{
}
SockAddr::SockAddr (in_addr const &addr_, std::uint16_t const port_)
{
std::memset (&m_addr, 0, sizeof (m_addr));
m_addr.ss_family = AF_INET;
setAddr (addr_);
setPort (port_);
}
#ifndef NO_IPV6
SockAddr::SockAddr (in6_addr const &addr_, std::uint16_t const port_)
{
std::memset (&m_addr, 0, sizeof (m_addr));
m_addr.ss_family = AF_INET6;
setAddr (addr_);
setPort (port_);
}
#endif
SockAddr::SockAddr (SockAddr const &that_) = default; SockAddr::SockAddr (SockAddr const &that_) = default;
SockAddr::SockAddr (SockAddr &&that_) = default; SockAddr::SockAddr (SockAddr &&that_) = default;
@ -39,109 +107,237 @@ SockAddr &SockAddr::operator= (SockAddr const &that_) = default;
SockAddr &SockAddr::operator= (SockAddr &&that_) = default; SockAddr &SockAddr::operator= (SockAddr &&that_) = default;
SockAddr::SockAddr (struct sockaddr const &addr_) SockAddr::SockAddr (sockaddr_in const &addr_)
{ {
switch (addr_.sa_family) assert (addr_.sin_family == AF_INET);
std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in));
}
#ifndef NO_IPV6
SockAddr::SockAddr (sockaddr_in6 const &addr_)
{
assert (addr_.sin6_family == AF_INET6);
std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in6));
}
#endif
SockAddr::SockAddr (sockaddr_storage const &addr_)
{
switch (addr_.ss_family)
{ {
case AF_INET: case AF_INET:
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in)); std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in));
break; break;
#ifndef NO_IPV6 #ifndef NO_IPV6
case AF_INET6: case AF_INET6:
std::memcpy (&m_addr, &addr_, sizeof (struct sockaddr_in6)); std::memcpy (&m_addr, &addr_, sizeof (sockaddr_in6));
break; break;
#endif #endif
default: default:
std::abort (); std::abort ();
break;
} }
} }
SockAddr::SockAddr (struct sockaddr_in const &addr_) SockAddr::operator sockaddr_in const & () const
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
{ {
assert (m_addr.ss_family == AF_INET); assert (m_addr.ss_family == AF_INET);
return reinterpret_cast<sockaddr_in const &> (m_addr);
} }
#if !defined(__3DS__) && !defined(__WIIU__) #ifndef NO_IPV6
SockAddr::SockAddr (struct sockaddr_in6 const &addr_) SockAddr::operator sockaddr_in6 const & () const
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
{ {
assert (m_addr.ss_family == AF_INET6); assert (m_addr.ss_family == AF_INET6);
return reinterpret_cast<sockaddr_in6 const &> (m_addr);
} }
#endif #endif
SockAddr::SockAddr (struct sockaddr_storage const &addr_) SockAddr::operator sockaddr_storage const & () const
: SockAddr (reinterpret_cast<struct sockaddr const &> (addr_))
{
}
SockAddr::operator struct sockaddr_in const & () const
{
assert (m_addr.ss_family == AF_INET);
return reinterpret_cast<struct sockaddr_in const &> (m_addr);
}
#if !defined(__3DS__) && !defined(__WIIU__)
SockAddr::operator struct sockaddr_in6 const & () const
{
assert (m_addr.ss_family == AF_INET6);
return reinterpret_cast<struct sockaddr_in6 const &> (m_addr);
}
#endif
SockAddr::operator struct sockaddr_storage const & () const
{ {
return m_addr; return m_addr;
} }
SockAddr::operator struct sockaddr * () SockAddr::operator sockaddr * ()
{ {
return reinterpret_cast<struct sockaddr *> (&m_addr); return reinterpret_cast<sockaddr *> (&m_addr);
} }
SockAddr::operator struct sockaddr const * () const SockAddr::operator sockaddr const * () const
{ {
return reinterpret_cast<struct sockaddr const *> (&m_addr); return reinterpret_cast<sockaddr const *> (&m_addr);
} }
bool SockAddr::setPort (std::uint16_t const port_) bool SockAddr::operator== (SockAddr const &that_) const
{ {
if (m_addr.ss_family != that_.m_addr.ss_family)
return false;
switch (m_addr.ss_family) switch (m_addr.ss_family)
{ {
case AF_INET: case AF_INET:
reinterpret_cast<struct sockaddr_in *> (&m_addr)->sin_port = htons (port_); if (port () != that_.port ())
return true; return false;
// ignore sin_zero
return static_cast<sockaddr_in const &> (*this).sin_addr.s_addr ==
static_cast<sockaddr_in const &> (that_).sin_addr.s_addr;
#ifndef NO_IPV6 #ifndef NO_IPV6
case AF_INET6: case AF_INET6:
reinterpret_cast<struct sockaddr_in6 *> (&m_addr)->sin6_port = htons (port_); return std::memcmp (&m_addr, &that_.m_addr, sizeof (sockaddr_in6)) == 0;
return true;
#endif #endif
default: default:
std::abort (); std::abort ();
break;
} }
} }
std::strong_ordering SockAddr::operator<=> (SockAddr const &that_) const
{
if (m_addr.ss_family != that_.m_addr.ss_family)
return m_addr.ss_family <=> that_.m_addr.ss_family;
switch (m_addr.ss_family)
{
case AF_INET:
{
auto const cmp =
strongMemCompare (&static_cast<sockaddr_in const &> (*this).sin_addr.s_addr,
&static_cast<sockaddr_in const &> (that_).sin_addr.s_addr,
sizeof (in_addr_t));
if (cmp != std::strong_ordering::equal)
return cmp;
return port () <=> that_.port ();
}
#ifndef NO_IPV6
case AF_INET6:
{
auto const &addr1 = static_cast<sockaddr_in6 const &> (*this);
auto const &addr2 = static_cast<sockaddr_in6 const &> (that_);
if (auto const cmp =
strongMemCompare (&addr1.sin6_addr, &addr2.sin6_addr, sizeof (in6_addr));
cmp != std::strong_ordering::equal)
return cmp;
auto const p1 = port ();
auto const p2 = that_.port ();
if (p1 < p2)
return std::strong_ordering::less;
else if (p1 > p2)
return std::strong_ordering::greater;
if (auto const cmp = strongMemCompare (
&addr1.sin6_flowinfo, &addr2.sin6_flowinfo, sizeof (std::uint32_t));
cmp != std::strong_ordering::equal)
return cmp;
return strongMemCompare (
&addr1.sin6_flowinfo, &addr2.sin6_flowinfo, sizeof (std::uint32_t));
}
#endif
default:
std::abort ();
}
}
void SockAddr::setAddr (in_addr_t const addr_)
{
setAddr (in_addr{.s_addr = addr_});
}
void SockAddr::setAddr (in_addr const &addr_)
{
if (m_addr.ss_family != AF_INET)
std::abort ();
std::memcpy (&reinterpret_cast<sockaddr_in &> (m_addr).sin_addr, &addr_, sizeof (addr_));
;
}
#ifndef NO_IPV6
void SockAddr::setAddr (in6_addr const &addr_)
{
if (m_addr.ss_family != AF_INET6)
std::abort ();
std::memcpy (&reinterpret_cast<sockaddr_in6 &> (m_addr).sin6_addr, &addr_, sizeof (addr_));
;
}
#endif
std::uint16_t SockAddr::port () const std::uint16_t SockAddr::port () const
{ {
switch (m_addr.ss_family) switch (m_addr.ss_family)
{ {
case AF_INET: case AF_INET:
return ntohs (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_port); return ntohs (reinterpret_cast<sockaddr_in const *> (&m_addr)->sin_port);
#ifndef NO_IPV6 #ifndef NO_IPV6
case AF_INET6: case AF_INET6:
return ntohs (reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_port); return ntohs (reinterpret_cast<sockaddr_in6 const *> (&m_addr)->sin6_port);
#endif #endif
default: default:
std::abort (); std::abort ();
}
}
void SockAddr::setPort (std::uint16_t const port_)
{
switch (m_addr.ss_family)
{
case AF_INET:
reinterpret_cast<sockaddr_in *> (&m_addr)->sin_port = htons (port_);
break; break;
#ifndef NO_IPV6
case AF_INET6:
reinterpret_cast<sockaddr_in6 *> (&m_addr)->sin6_port = htons (port_);
break;
#endif
default:
std::abort ();
}
}
SockAddr::Domain SockAddr::domain () const
{
switch (m_addr.ss_family)
{
case AF_INET:
#ifndef NO_IPV6
case AF_INET6:
#endif
return static_cast<Domain> (m_addr.ss_family);
default:
std::abort ();
}
}
socklen_t SockAddr::size () const
{
switch (m_addr.ss_family)
{
case AF_INET:
return sizeof (sockaddr_in);
#ifndef NO_IPV6
case AF_INET6:
return sizeof (sockaddr_in6);
#endif
default:
std::abort ();
} }
} }
@ -150,33 +346,30 @@ char const *SockAddr::name (char *buffer_, std::size_t size_) const
switch (m_addr.ss_family) switch (m_addr.ss_family)
{ {
case AF_INET: case AF_INET:
#ifdef NDS #ifdef __NDS__
return inet_ntoa (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr); (void)buffer_;
(void)size_;
return inet_ntoa (reinterpret_cast<sockaddr_in const *> (&m_addr)->sin_addr);
#else #else
return inet_ntop (AF_INET, return inet_ntop (
&reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr, AF_INET, &reinterpret_cast<sockaddr_in const *> (&m_addr)->sin_addr, buffer_, size_);
buffer_,
size_);
#endif #endif
#ifndef NO_IPV6 #ifndef NO_IPV6
case AF_INET6: case AF_INET6:
return inet_ntop (AF_INET6, return inet_ntop (
&reinterpret_cast<struct sockaddr_in6 const *> (&m_addr)->sin6_addr, AF_INET6, &reinterpret_cast<sockaddr_in6 const *> (&m_addr)->sin6_addr, buffer_, size_);
buffer_,
size_);
#endif #endif
default: default:
std::abort (); std::abort ();
break;
} }
} }
char const *SockAddr::name () const char const *SockAddr::name () const
{ {
#if defined(NDS) || defined(__WIIU__) #if defined(__NDS__) || defined(__WIIU__)
return inet_ntoa (reinterpret_cast<struct sockaddr_in const *> (&m_addr)->sin_addr); return inet_ntoa (reinterpret_cast<sockaddr_in const *> (&m_addr)->sin_addr);
#else #else
#ifdef NO_IPV6 #ifdef NO_IPV6
thread_local static char buffer[INET_ADDRSTRLEN]; thread_local static char buffer[INET_ADDRSTRLEN];

View File

@ -3,7 +3,7 @@
// - RFC 3659 (https://tools.ietf.org/html/rfc3659) // - RFC 3659 (https://tools.ietf.org/html/rfc3659)
// - suggested implementation details from https://cr.yp.to/ftp/filesystem.html // - suggested implementation details from https://cr.yp.to/ftp/filesystem.html
// //
// Copyright (C) 2020 Michael Theall // Copyright (C) 2024 Michael Theall
// //
// This program is free software: you can redistribute it and/or modify // 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 // it under the terms of the GNU General Public License as published by
@ -24,6 +24,7 @@
#include <chrono> #include <chrono>
#include <fcntl.h> #include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <unistd.h> #include <unistd.h>
@ -43,7 +44,7 @@ Socket::~Socket ()
if (m_connected) if (m_connected)
info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ()); info ("Closing connection to [%s]:%u\n", m_peerName.name (), m_peerName.port ());
#ifdef NDS #ifdef __NDS__
if (::closesocket (m_fd) != 0) if (::closesocket (m_fd) != 0)
error ("closesocket: %s\n", std::strerror (errno)); error ("closesocket: %s\n", std::strerror (errno));
#else #else
@ -68,7 +69,7 @@ Socket::Socket (int const fd_, SockAddr const &sockName_, SockAddr const &peerNa
UniqueSocket Socket::accept () UniqueSocket Socket::accept ()
{ {
SockAddr addr; SockAddr addr;
socklen_t addrLen = sizeof (struct sockaddr_storage); socklen_t addrLen = sizeof (sockaddr_storage);
auto const fd = ::accept (m_fd, addr, &addrLen); auto const fd = ::accept (m_fd, addr, &addrLen);
if (fd < 0) if (fd < 0)
@ -83,7 +84,7 @@ UniqueSocket Socket::accept ()
int Socket::atMark () int Socket::atMark ()
{ {
#ifdef NDS #ifdef __NDS__
errno = ENOSYS; errno = ENOSYS;
return -1; return -1;
#else #else
@ -98,38 +99,16 @@ int Socket::atMark ()
bool Socket::bind (SockAddr const &addr_) bool Socket::bind (SockAddr const &addr_)
{ {
switch (static_cast<struct sockaddr_storage const &> (addr_).ss_family) if (::bind (m_fd, addr_, addr_.size ()) != 0)
{ {
case AF_INET:
if (::bind (m_fd, addr_, sizeof (struct sockaddr_in)) != 0)
{
platform::Thread::sleep (5000ms);
error ("bind: %s\n", std::strerror (errno));
return false;
}
break;
#ifndef NO_IPV6
case AF_INET6:
if (::bind (m_fd, addr_, sizeof (struct sockaddr_in6)) != 0)
{
error ("bind: %s\n", std::strerror (errno));
platform::Thread::sleep (5000ms);
return false;
}
break;
#endif
default:
errno = EINVAL;
error ("bind: %s\n", std::strerror (errno)); error ("bind: %s\n", std::strerror (errno));
break; return false;
} }
if (addr_.port () == 0) if (addr_.port () == 0)
{ {
// get socket name due to request for ephemeral port // get socket name due to request for ephemeral port
socklen_t addrLen = sizeof (struct sockaddr_storage); socklen_t addrLen = sizeof (sockaddr_storage);
if (::getsockname (m_fd, m_sockName, &addrLen) != 0) if (::getsockname (m_fd, m_sockName, &addrLen) != 0)
error ("getsockname: %s\n", std::strerror (errno)); error ("getsockname: %s\n", std::strerror (errno));
} }
@ -141,7 +120,7 @@ bool Socket::bind (SockAddr const &addr_)
bool Socket::connect (SockAddr const &addr_) bool Socket::connect (SockAddr const &addr_)
{ {
if (::connect (m_fd, addr_, sizeof (struct sockaddr_storage)) != 0) if (::connect (m_fd, addr_, addr_.size ()) != 0)
{ {
if (errno != EINPROGRESS) if (errno != EINPROGRESS)
error ("connect: %s\n", std::strerror (errno)); error ("connect: %s\n", std::strerror (errno));
@ -185,11 +164,13 @@ bool Socket::shutdown (int const how_)
bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_) bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_)
{ {
#ifdef NDS #ifdef __NDS__
(void)enable_;
(void)time_;
errno = ENOSYS; errno = ENOSYS;
return -1; return -1;
#else #else
struct linger linger; linger linger;
linger.l_onoff = enable_; linger.l_onoff = enable_;
linger.l_linger = time_.count (); linger.l_linger = time_.count ();
@ -209,7 +190,7 @@ bool Socket::setLinger (bool const enable_, std::chrono::seconds const time_)
bool Socket::setNonBlocking (bool const nonBlocking_) bool Socket::setNonBlocking (bool const nonBlocking_)
{ {
#ifdef NDS #ifdef __NDS__
unsigned long enable = nonBlocking_; unsigned long enable = nonBlocking_;
auto const rc = ::ioctl (m_fd, FIONBIO, &enable); auto const rc = ::ioctl (m_fd, FIONBIO, &enable);
@ -290,6 +271,38 @@ bool Socket::setSendBufferSize (std::size_t const size_)
return true; return true;
} }
#ifndef __NDS__
bool Socket::joinMulticastGroup (SockAddr const &addr_, SockAddr const &iface_)
{
ip_mreq group;
group.imr_multiaddr = static_cast<sockaddr_in const &> (addr_).sin_addr;
group.imr_interface = static_cast<sockaddr_in const &> (iface_).sin_addr;
if (::setsockopt (m_fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof (group)) != 0)
{
error ("setsockopt(IP_ADD_MEMBERSHIP, %s): %s\n", addr_.name (), std::strerror (errno));
return false;
}
return true;
}
bool Socket::dropMulticastGroup (SockAddr const &addr_, SockAddr const &iface_)
{
ip_mreq group;
group.imr_multiaddr = static_cast<sockaddr_in const &> (addr_).sin_addr;
group.imr_interface = static_cast<sockaddr_in const &> (iface_).sin_addr;
if (::setsockopt (m_fd, IPPROTO_IP, IP_DROP_MEMBERSHIP, &group, sizeof (group)) != 0)
{
error ("setsockopt(IP_DROP_MEMBERSHIP, %s): %s\n", addr_.name (), std::strerror (errno));
return false;
}
return true;
}
#endif
std::make_signed_t<std::size_t> std::make_signed_t<std::size_t>
Socket::read (void *const buffer_, std::size_t const size_, bool const oob_) Socket::read (void *const buffer_, std::size_t const size_, bool const oob_)
{ {
@ -314,6 +327,21 @@ std::make_signed_t<std::size_t> Socket::read (IOBuffer &buffer_, bool const oob_
return rc; return rc;
} }
std::make_signed_t<std::size_t>
Socket::readFrom (void *const buffer_, std::size_t const size_, SockAddr &addr_)
{
assert (buffer_);
assert (size_);
socklen_t addrLen = sizeof (sockaddr_storage);
auto const rc = ::recvfrom (m_fd, buffer_, size_, 0, addr_, &addrLen);
if (rc < 0 && errno != EWOULDBLOCK)
error ("recvfrom: %s\n", std::strerror (errno));
return rc;
}
std::make_signed_t<std::size_t> Socket::write (void const *const buffer_, std::size_t const size_) std::make_signed_t<std::size_t> Socket::write (void const *const buffer_, std::size_t const size_)
{ {
assert (buffer_); assert (buffer_);
@ -337,6 +365,19 @@ std::make_signed_t<std::size_t> Socket::write (IOBuffer &buffer_)
return rc; return rc;
} }
std::make_signed_t<std::size_t>
Socket::writeTo (void const *buffer_, std::size_t size_, SockAddr const &addr_)
{
assert (buffer_);
assert (size_ > 0);
auto const rc = ::sendto (m_fd, buffer_, size_, 0, addr_, addr_.size ());
if (rc < 0 && errno != EWOULDBLOCK)
error ("sendto: %s\n", std::strerror (errno));
return rc;
}
SockAddr const &Socket::sockName () const SockAddr const &Socket::sockName () const
{ {
return m_sockName; return m_sockName;
@ -347,9 +388,9 @@ SockAddr const &Socket::peerName () const
return m_peerName; return m_peerName;
} }
UniqueSocket Socket::create () UniqueSocket Socket::create (Type const type_)
{ {
auto const fd = ::socket (AF_INET, SOCK_STREAM, 0); auto const fd = ::socket (AF_INET, static_cast<int> (type_), 0);
if (fd < 0) if (fd < 0)
{ {
error ("socket: %s\n", std::strerror (errno)); error ("socket: %s\n", std::strerror (errno));
@ -366,7 +407,7 @@ int Socket::poll (PollInfo *const info_,
if (count_ == 0) if (count_ == 0)
return 0; return 0;
auto const pfd = std::make_unique<struct pollfd[]> (count_); auto const pfd = std::make_unique<pollfd[]> (count_);
for (std::size_t i = 0; i < count_; ++i) for (std::size_t i = 0; i < count_; ++i)
{ {
pfd[i].fd = info_[i].socket.get ().m_fd; pfd[i].fd = info_[i].socket.get ().m_fd;
@ -387,8 +428,8 @@ int Socket::poll (PollInfo *const info_,
return rc; return rc;
} }
#ifdef NDS #ifdef __NDS__
extern "C" int poll (struct pollfd *const fds_, nfds_t const nfds_, int const timeout_) extern "C" int poll (pollfd *const fds_, nfds_t const nfds_, int const timeout_)
{ {
fd_set readFds; fd_set readFds;
fd_set writeFds; fd_set writeFds;
@ -406,7 +447,7 @@ extern "C" int poll (struct pollfd *const fds_, nfds_t const nfds_, int const ti
FD_SET (fds_[i].fd, &writeFds); FD_SET (fds_[i].fd, &writeFds);
} }
struct timeval tv; timeval tv;
tv.tv_sec = timeout_ / 1000; tv.tv_sec = timeout_ / 1000;
tv.tv_usec = (timeout_ % 1000) * 1000; tv.tv_usec = (timeout_ % 1000) * 1000;
auto const rc = ::select (nfds_, &readFds, &writeFds, &exceptFds, &tv); auto const rc = ::select (nfds_, &readFds, &writeFds, &exceptFds, &tv);

View File

@ -459,6 +459,12 @@ void platform::Thread::sleep (std::chrono::milliseconds const timeout_)
std::this_thread::sleep_for (timeout_); std::this_thread::sleep_for (timeout_);
} }
std::string const &platform::hostname ()
{
static std::string const hostname = "wiiu-ftpd";
return hostname;
}
/////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////
#define USE_STD_MUTEX 1 #define USE_STD_MUTEX 1