mirror of
https://github.com/wiiu-env/ftpiiu_plugin.git
synced 2024-12-28 05:11:48 +01:00
Merge branch 'master' of https://github.com/mtheall/ftpd into upstream_changes
This commit is contained in:
parent
00191956b8
commit
618b2fb136
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -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")
|
||||||
|
2
.github/workflows/pr.yml
vendored
2
.github/workflows/pr.yml
vendored
@ -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
3
.gitignore
vendored
@ -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
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "3rd/gls"]
|
||||||
|
path = 3rd/gls
|
||||||
|
url = git@github.com:microsoft/GSL.git
|
1
3rd/gls
Submodule
1
3rd/gls
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit a3534567187d2edc428efd3f13466ff75fe5805c
|
4
Makefile
4
Makefile
@ -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
|
||||||
|
28
include/fs.h
28
include/fs.h
@ -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*
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
40
include/mdns.h
Normal 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_);
|
||||||
|
}
|
@ -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
|
||||||
{
|
{
|
||||||
|
@ -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 = {};
|
||||||
};
|
};
|
||||||
|
@ -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
|
||||||
|
102
source/fs.cpp
102
source/fs.cpp
@ -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 ());
|
||||||
|
@ -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'));
|
||||||
}
|
}
|
||||||
|
@ -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
@ -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
|
||||||
|
@ -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
597
source/mdns.cpp
Normal 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
209
source/posix/collate.c
Normal 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
69
source/posix/collate.h
Normal 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
88
source/posix/collcmp.c
Normal 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
839
source/posix/glob.c
Normal 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 */
|
@ -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];
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user