Mercurial > irccd
changeset 290:24bb45724dc0
Irccd: split lib into libcommon, #564
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 05 Oct 2016 13:06:00 +0200 |
parents | f986f94c1510 |
children | b490853404d9 |
files | CMakeLists.txt libcommon/CMakeLists.txt libcommon/irccd/elapsed-timer.cpp libcommon/irccd/elapsed-timer.hpp libcommon/irccd/fs.cpp libcommon/irccd/fs.hpp libcommon/irccd/ini.cpp libcommon/irccd/ini.hpp libcommon/irccd/logger.cpp libcommon/irccd/logger.hpp libcommon/irccd/net.hpp libcommon/irccd/options.cpp libcommon/irccd/options.hpp libcommon/irccd/path.cpp libcommon/irccd/path.hpp libcommon/irccd/signals.hpp libcommon/irccd/system.cpp libcommon/irccd/system.hpp libcommon/irccd/util.cpp libcommon/irccd/util.hpp libcommon/irccd/xdg.hpp |
diffstat | 21 files changed, 9348 insertions(+), 7 deletions(-) [+] |
line wrap: on
line diff
--- a/CMakeLists.txt Tue Nov 08 13:29:08 2016 +0100 +++ b/CMakeLists.txt Wed Oct 05 13:06:00 2016 +0200 @@ -78,23 +78,24 @@ add_subdirectory(extern/libircclient) add_subdirectory(extern/json) add_subdirectory(doc) -add_subdirectory(lib) -add_subdirectory(irccd) -add_subdirectory(irccdctl) -add_subdirectory(contrib) +add_subdirectory(libcommon) +#add_subdirectory(lib) +#add_subdirectory(irccd) +#add_subdirectory(irccdctl) +#add_subdirectory(contrib) if (WITH_JS) - add_subdirectory(plugins) +#add_subdirectory(plugins) endif () # Platform specific. if (WIN32) - add_subdirectory(win32) + # add_subdirectory(win32) endif () # Tests. include(CTest) -add_subdirectory(tests) +# add_subdirectory(tests) message("Compiling with the following flags:") message(" General flags: ${CMAKE_CXX_FLAGS}")
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/CMakeLists.txt Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,40 @@ +project(libcommon) + +set( + HEADERS + ${libcommon_SOURCE_DIR}/irccd/elapsed-timer.hpp + ${libcommon_SOURCE_DIR}/irccd/fs.hpp + ${libcommon_SOURCE_DIR}/irccd/ini.hpp + ${libcommon_SOURCE_DIR}/irccd/logger.hpp + ${libcommon_SOURCE_DIR}/irccd/net.hpp + ${libcommon_SOURCE_DIR}/irccd/options.hpp + ${libcommon_SOURCE_DIR}/irccd/path.hpp + ${libcommon_SOURCE_DIR}/irccd/signals.hpp + ${libcommon_SOURCE_DIR}/irccd/system.hpp + ${libcommon_SOURCE_DIR}/irccd/util.hpp + ${libcommon_SOURCE_DIR}/irccd/xdg.hpp +) + +set( + SOURCES + ${libcommon_SOURCE_DIR}/irccd/elapsed-timer.cpp + ${libcommon_SOURCE_DIR}/irccd/fs.cpp + ${libcommon_SOURCE_DIR}/irccd/ini.cpp + ${libcommon_SOURCE_DIR}/irccd/logger.cpp + ${libcommon_SOURCE_DIR}/irccd/options.cpp + ${libcommon_SOURCE_DIR}/irccd/path.cpp + ${libcommon_SOURCE_DIR}/irccd/system.cpp + ${libcommon_SOURCE_DIR}/irccd/util.cpp +) + +irccd_define_library( + TARGET libcommon + SOURCES + ${libcommon_SOURCE_DIR}/CMakeLists.txt + ${HEADERS} + ${SOURCES} + LIBRARIES extern-cppformat extern-json + PUBLIC_INCLUDES + $<BUILD_INTERFACE:${IRCCD_FAKEROOTDIR}/include/irccd> + $<BUILD_INTERFACE:${libcommon_SOURCE_DIR}/irccd> +)
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/elapsed-timer.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,64 @@ +/* + * elapsed-timer.cpp -- measure elapsed time + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "elapsed-timer.hpp" + +using std::chrono::duration_cast; +using std::chrono::high_resolution_clock; +using std::chrono::milliseconds; + +namespace irccd { + +ElapsedTimer::ElapsedTimer() noexcept +{ + m_last = high_resolution_clock::now(); +} + +void ElapsedTimer::pause() noexcept +{ + /* + * When we put the timer on pause, do not forget to set the already + * elapsed time. + */ + (void)elapsed(); + m_paused = true; +} + +void ElapsedTimer::restart() noexcept +{ + m_paused = false; + m_last = high_resolution_clock::now(); +} + +void ElapsedTimer::reset() noexcept +{ + m_elapsed = 0; + m_last = high_resolution_clock::now(); +} + +unsigned ElapsedTimer::elapsed() noexcept +{ + if (!m_paused) { + m_elapsed += duration_cast<milliseconds>(high_resolution_clock::now() - m_last).count(); + m_last = high_resolution_clock::now(); + } + + return m_elapsed; +} + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/elapsed-timer.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,87 @@ +/* + * elapsed-timer.hpp -- measure elapsed time + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_ELAPSED_TIMER_HPP +#define IRCCD_ELAPSED_TIMER_HPP + +/** + * \file elapsed-timer.hpp + * \brief Measure elapsed time + */ + +#include <chrono> + +#include "sysconfig.hpp" + +namespace irccd { + +/** + * \class ElapsedTimer + * \brief Measure elapsed time + * + * This class provides an abstraction to measure elapsed time since the + * construction of the object. + * + * It uses std::chrono::high_resolution_clock for more precision and uses + * milliseconds only. + */ +class ElapsedTimer { +private: + using TimePoint = std::chrono::time_point<std::chrono::high_resolution_clock>; + + TimePoint m_last; + bool m_paused{false}; + unsigned m_elapsed{0}; + +public: + /** + * Construct the elapsed timer, start counting. + */ + IRCCD_EXPORT ElapsedTimer() noexcept; + + /** + * Virtual destructor defaulted. + */ + virtual ~ElapsedTimer() = default; + + /** + * Put the timer on pause, the already elapsed time is stored. + */ + IRCCD_EXPORT void pause() noexcept; + + /** + * Restart the timer, does not reset it. + */ + IRCCD_EXPORT void restart() noexcept; + + /** + * Reset the timer to 0. + */ + IRCCD_EXPORT void reset() noexcept; + + /** + * Get the number of elapsed milliseconds. + * + * \return the milliseconds + */ + IRCCD_EXPORT unsigned elapsed() noexcept; +}; + +} // !irccd + +#endif // !IRCCD_ELAPSED_TIMER_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/fs.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,503 @@ +/* + * fs.cpp -- filesystem operations + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#if defined(_WIN32) +# if !defined(_CRT_SECURE_NO_WARNINGS) +# define _CRT_SECURE_NO_WARNINGS +# endif +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif +#endif + +#include <algorithm> +#include <cassert> +#include <cerrno> +#include <cstdio> +#include <cstring> +#include <sstream> +#include <stdexcept> + +#if defined(_WIN32) +# include <direct.h> +# include <windows.h> +# include <shlwapi.h> +#else +# include <sys/types.h> +# include <dirent.h> +# include <unistd.h> +#endif + +#include "fs.hpp" + +namespace irccd { + +namespace fs { + +namespace { + +/* + * error. + * ------------------------------------------------------------------ + * + * Function to retrieve system error in Windows API. + */ +#if defined(_WIN32) + +std::string error() +{ + LPSTR error = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, + GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&error, 0, nullptr); + + if (error) { + errmsg = std::string(error); + LocalFree(error); + } + + return errmsg; +} + +#endif + +/* + * hasAccess. + * ------------------------------------------------------------------ + * + * Check if we have access to the file specified, mode is the same used as + * std::fopen. + */ +bool hasAccess(const std::string &path, const std::string &mode) +{ + assert(mode.length() == 1); + assert(mode[0] == 'r' || mode[0] == 'w'); + + auto fp = std::fopen(path.c_str(), mode.c_str()); + + if (fp == nullptr) + return false; + + std::fclose(fp); + + return true; +} + +/* + * typeOf. + * ------------------------------------------------------------------ + * + * Get the type of the specified file. + * + * Use GetFileAttributesA on Windows and stat if available. + * + * Receives the object as predicate parameter and return true on success. + */ +#if defined(_WIN32) + +template <typename Predicate> +bool typeOf(const std::string &path, Predicate &&predicate) +{ + DWORD result = GetFileAttributesA(path.c_str()); + + if (result == INVALID_FILE_ATTRIBUTES) + return false; + + return predicate(result); +} + +#elif defined(FS_HAVE_STAT) + +template <typename Predicate> +bool typeOf(const std::string &path, Predicate &&predicate) noexcept +{ + struct stat st; + + if (::stat(path.c_str(), &st) < 0) + return false; + + return predicate(st); +} + +#else + +template <typename Predicate> +bool typeOf(const std::string &path, Predicate &&predicate) noexcept +{ + throw std::runtime_error(std::strerror(ENOSYS)); +} + +#endif + +} // !namespace + +/* + * clean. + * ------------------------------------------------------------------ + */ +std::string clean(std::string input) +{ + if (input.empty()) + return input; + + // First, remove any duplicates. + input.erase(std::unique(input.begin(), input.end(), [&] (char c1, char c2) { + return c1 == c2 && (c1 == '/' || c1 == '\\'); + }), input.end()); + + // Add a trailing / or \\. + char c = input[input.length() - 1]; + + if (c != '/' && c != '\\') + input += separator(); + + // Now converts all / to \\ for Windows and the opposite for Unix. +#if defined(_WIN32) + std::replace(input.begin(), input.end(), '/', '\\'); +#else + std::replace(input.begin(), input.end(), '\\', '/'); +#endif + + return input; +} + +/* + * baseName. + * ------------------------------------------------------------------ + */ +std::string baseName(std::string path) +{ + auto pos = path.find_last_of("\\/"); + + if (pos != std::string::npos) + path = path.substr(pos + 1); + + return path; +} + +/* + * dirName. + * ------------------------------------------------------------------ + */ +std::string dirName(std::string path) +{ + auto pos = path.find_last_of("\\/"); + + if (pos == std::string::npos) + path = "."; + else + path = path.substr(0, pos); + + return path; +} + +/* + * isAbsolute. + * ------------------------------------------------------------------ + */ +bool isAbsolute(const std::string &path) noexcept +{ +#if defined(_WIN32) + return !isRelative(path); +#else + return path.size() > 0 && path[0] == '/'; +#endif +} + +/* + * isRelative. + * ------------------------------------------------------------------ + */ +bool isRelative(const std::string &path) noexcept +{ +#if defined(_WIN32) + return PathIsRelativeA(path.c_str()) == 1; +#else + return !isAbsolute(path); +#endif +} + +/* + * isReadable. + * ------------------------------------------------------------------ + */ +bool isReadable(const std::string &path) noexcept +{ + return hasAccess(path, "r"); +} + +/* + * isWritable. + * ------------------------------------------------------------------ + */ +bool isWritable(const std::string &path) noexcept +{ + return hasAccess(path, "w"); +} + +/* + * isFile. + * ------------------------------------------------------------------ + */ +bool isFile(const std::string &path) +{ + return typeOf(path, [] (const auto &object) { +#if defined(_WIN32) + return (object & FILE_ATTRIBUTE_ARCHIVE) == FILE_ATTRIBUTE_ARCHIVE; +#elif defined(FS_HAVE_STAT) + return S_ISREG(object.st_mode); +#endif + }); +} + +/* + * isDirectory. + * ------------------------------------------------------------------ + */ +bool isDirectory(const std::string &path) +{ + return typeOf(path, [] (const auto &object) { +#if defined(_WIN32) + return (object & FILE_ATTRIBUTE_DIRECTORY) == FILE_ATTRIBUTE_DIRECTORY; +#elif defined(FS_HAVE_STAT) + return S_ISDIR(object.st_mode); +#endif + }); +} + +/* + * isSymlink. + * ------------------------------------------------------------------ + */ +bool isSymlink(const std::string &path) +{ + return typeOf(path, [] (const auto &object) { +#if defined(_WIN32) + return (object & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT; +#elif defined(FS_HAVE_STAT) + return S_ISLNK(object.st_mode); +#endif + }); +} + +/* + * stat. + * ------------------------------------------------------------------ + */ +#if defined(FS_HAVE_STAT) + +struct stat stat(const std::string &path) +{ + struct stat st; + + if (::stat(path.c_str(), &st) < 0) + throw std::runtime_error(std::strerror(errno)); + + return st; +} + +#endif + +/* + * exists. + * ------------------------------------------------------------------ + */ +bool exists(const std::string &path) noexcept +{ +#if defined(FS_HAVE_STAT) + struct stat st; + + return ::stat(path.c_str(), &st) == 0; +#else + return hasAccess(path, "r"); +#endif +} + +/* + * readdir. + * ------------------------------------------------------------------ + */ +std::vector<Entry> readdir(const std::string &path, int flags) +{ + std::vector<Entry> entries; + +#if defined(_WIN32) + std::ostringstream oss; + HANDLE handle; + WIN32_FIND_DATA fdata; + + oss << path << "\\*"; + handle = FindFirstFile(oss.str().c_str(), &fdata); + + if (handle == nullptr) + throw std::runtime_error(error()); + + do { + Entry entry; + + entry.name = fdata.cFileName; + + if (entry.name == "." && !(flags & Dot)) + continue; + if (entry.name == ".." && !(flags & DotDot)) + continue; + + switch (fdata.dwFileAttributes) { + case FILE_ATTRIBUTE_DIRECTORY: + entry.type = Entry::Dir; + break; + case FILE_ATTRIBUTE_NORMAL: + entry.type = Entry::File; + break; + case FILE_ATTRIBUTE_REPARSE_POINT: + entry.type = Entry::Link; + break; + default: + break; + } + + entries.push_back(std::move(entry)); + } while (FindNextFile(handle, &fdata) != 0); + + FindClose(handle); +#else + DIR *dp; + struct dirent *ent; + + if ((dp = opendir(path.c_str())) == nullptr) + throw std::runtime_error(std::strerror(errno)); + + while ((ent = readdir(dp)) != nullptr) { + Entry entry; + + entry.name = ent->d_name; + if (entry.name == "." && !(flags & Dot)) + continue; + if (entry.name == ".." && !(flags & DotDot)) + continue; + + switch (ent->d_type) { + case DT_DIR: + entry.type = Entry::Dir; + break; + case DT_REG: + entry.type = Entry::File; + break; + case DT_LNK: + entry.type = Entry::Link; + break; + default: + break; + } + + entries.push_back(std::move(entry)); + } + + closedir(dp); +#endif + + return entries; +} + +/* + * mkdir. + * ------------------------------------------------------------------ + */ +void mkdir(const std::string &path, int mode) +{ + std::string::size_type next = 0; + std::string part; + + for (;;) { + next = path.find_first_of("\\/", next); + part = path.substr(0, next); + + if (!part.empty()) { +#if defined(_WIN32) + (void)mode; + + if (::_mkdir(part.c_str()) < 0 && errno != EEXIST) + throw std::runtime_error(std::strerror(errno)); +#else + if (::mkdir(part.c_str(), mode) < 0 && errno != EEXIST) + throw std::runtime_error(std::strerror(errno)); +#endif + } + + if (next++ == std::string::npos) + break; + } +} + +/* + * rmdir. + * ------------------------------------------------------------------ + */ +void rmdir(const std::string &base) noexcept +{ + try { + for (const auto &entry : readdir(base)) { + std::string path = base + separator() + entry.name; + + if (entry.type == Entry::Dir) + rmdir(path); + else + ::remove(path.c_str()); + } + } catch (...) { + // Silently discard to remove as much as possible. + } + +#if defined(_WIN32) + ::RemoveDirectoryA(base.c_str()); +#else + ::remove(base.c_str()); +#endif +} + +/* + * cwd. + * ------------------------------------------------------------------ + */ +std::string cwd() +{ +#if defined(_WIN32) + char path[MAX_PATH]; + + if (!::GetCurrentDirectoryA(sizeof (path), path)) + throw std::runtime_error("failed to get current working directory"); + + return path; +#else + char path[PATH_MAX]; + + if (::getcwd(path, sizeof (path)) == nullptr) + throw std::runtime_error{std::strerror(errno)}; + + return path; +#endif +} + +} // !irccd + +} // !fs
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/fs.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,359 @@ +/* + * fs.hpp -- filesystem operations + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef FS_HPP +#define FS_HPP + +/** + * \file fs.hpp + * \brief Filesystem operations made easy. + */ + +/** + * \cond FS_HIDDEN_SYMBOLS + */ + +#if !defined(FS_HAVE_STAT) +# if defined(_WIN32) +# define FS_HAVE_STAT +# elif defined(__linux__) +# define FS_HAVE_STAT +# elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__) +# define FS_HAVE_STAT +# elif defined(__APPLE__) +# define FS_HAVE_STAT +# endif +#endif + +/** + * \endcond + */ + +#if defined(FS_HAVE_STAT) +# include <sys/stat.h> +#endif + +#include <regex> +#include <string> +#include <vector> + +#include "sysconfig.hpp" + +namespace irccd { + +/** + * \brief Filesystem namespace. + */ +namespace fs { + +/** + * \enum Flags + * \brief Flags for readdir. + */ +enum Flags { + Dot = (1 << 0), //!< if set, also lists "." + DotDot = (1 << 1) //!< if set, also lists ".." +}; + +/** + * \class Entry + * \brief Entry in the directory list. + */ +class Entry { +public: + /** + * \brief Describe the type of an entry + */ + enum Type : char { + Unknown, //!< File type is unknown, + File, //!< File is regular type, + Dir, //!< File is directory, + Link //!< File is link + }; + + std::string name; //!< name of entry (base name) + Type type{Unknown}; //!< type of file +}; + +/** + * Check if two entries are identical. + * + * \param e1 the first entry + * \param e2 the second entry + * \return true if they are identical + */ +inline bool operator==(const Entry &e1, const Entry &e2) noexcept +{ + return e1.name == e2.name && e1.type == e2.type; +} + +/** + * Check if two entries are different. + * + * \param e1 the first entry + * \param e2 the second entry + * \return true if they are different + */ +inline bool operator!=(const Entry &e1, const Entry &e2) noexcept +{ + return !(e1 == e2); +} + +/** + * Get the separator for that system. + * + * \return \ on Windows and / otherwise + */ +inline char separator() noexcept +{ +#if defined(_WIN32) + return '\\'; +#else + return '/'; +#endif +} + +/** + * Clean a path by removing any extra / or \ and add a trailing one. + * + * \param path the path + * \return the updated path + */ +IRCCD_EXPORT std::string clean(std::string path); + +/** + * Get the base name from a path. + * + * Example, baseName("/etc/foo.conf") // foo.conf + * + * \param path the path + * \return the base name + */ +IRCCD_EXPORT std::string baseName(std::string path); + +/** + * Get the parent directory from a path. + * + * Example, dirName("/etc/foo.conf") // /etc + * + * \param path the path + * \return the parent directory + */ +IRCCD_EXPORT std::string dirName(std::string path); + +#if defined(FS_HAVE_STAT) + +/** + * Get stat information. + * + * \param path the path + * \return the stat information + * \throw std::runtime_error on failure + */ +IRCCD_EXPORT struct stat stat(const std::string &path); + +#endif // !HAVE_STAT + +/** + * Check if a file exists. + * + * If HAVE_ACCESS is defined, the function access is used, otherwise stat is + * used. + * + * \param path the path to check + * \return true if the path exists + */ +IRCCD_EXPORT bool exists(const std::string &path) noexcept; + +/** + * Check if the path is absolute. + * + * \param path the path + * \return true if the path is absolute + */ +IRCCD_EXPORT bool isAbsolute(const std::string &path) noexcept; + +/** + * Check if the path is relative. + * + * \param path the path + * \return true if the path is absolute + */ +IRCCD_EXPORT bool isRelative(const std::string &path) noexcept; + +/** + * Check if the file is readable. + * + * \param path the path + * \return true if has read access + */ +IRCCD_EXPORT bool isReadable(const std::string &path) noexcept; + +/** + * Check if the file is writable. + * + * \param path the path + * \return true if has write access + */ +IRCCD_EXPORT bool isWritable(const std::string &path) noexcept; + +/** + * Check if the file is a regular file. + * + * \param path the path + * \return true if it is a file and false if not or not readable + * \throw std::runtime_error if the operation is not supported + */ +IRCCD_EXPORT bool isFile(const std::string &path); + +/** + * Check if the file is a directory. + * + * \param path the path + * \return true if it is a directory and false if not or not readable + * \throw std::runtime_error if the operation is not supported + */ +IRCCD_EXPORT bool isDirectory(const std::string &path); + +/** + * Check if the file is a symbolic link. + * + * \param path the path + * \return true if it is a symbolic link and false if not or not readable + * \throw std::runtime_error if the operation is not supported + */ +IRCCD_EXPORT bool isSymlink(const std::string &path); + +/** + * Read a directory and return a list of entries (not recursive). + * + * \param path the directory path + * \param flags the optional flags (see Flags) + * \return the list of entries + * \throw std::runtime_error on failure + */ +IRCCD_EXPORT std::vector<Entry> readdir(const std::string &path, int flags = 0); + +/** + * Create a directory recursively. + * + * \param path the path + * \param mode the optional mode (not always supported) + * \throw std::runtime_error on failure + * \post all intermediate directories are created + */ +IRCCD_EXPORT void mkdir(const std::string &path, int mode = 0700); + +/** + * Remove a directory recursively. + * + * If errors happens, they are silently discarded to remove as much as possible. + * + * \param path the path + */ +IRCCD_EXPORT void rmdir(const std::string &path) noexcept; + +/** + * Search an item recursively. + * + * The predicate must have the following signature: + * void f(const std::string &base, const Entry &entry) + * + * Where: + * - base is the current parent directory in the tree + * - entry is the current entry + * + * \param base the base directory + * \param predicate the predicate + * \return the full path name to the file or empty string if never found + * \throw std::runtime_error on read errors + */ +template <typename Predicate> +std::string findIf(const std::string &base, Predicate &&predicate) +{ + /* + * Do not go deeply to the tree before testing all files in the current + * directory for performances reasons, we iterate this directory to search + * for the entry name and iterate again over all sub directories if not + * found. + */ + std::string path; + std::vector<Entry> entries = readdir(base); + + for (const auto &entry : entries) { + if (predicate(base, entry)) { + path = base + separator() + entry.name; + break; + } + } + + if (!path.empty()) + return path; + + for (const auto &entry : entries) { + if (entry.type != Entry::Dir) + continue; + + path = findIf(base + separator() + entry.name, std::forward<Predicate>(predicate)); + + if (!path.empty()) + break; + } + + return path; +} + +/** + * Find a file by name recursively. + * + * \param base the base directory + * \param name the file name + * \return the full path name to the file or empty string if never found + * \throw std::runtime_error on read errors + */ +inline std::string find(const std::string &base, const std::string &name) +{ + return findIf(base, [&] (const auto &, const auto &entry) { return entry.name == name; }); +} + +/** + * Overload by regular expression. + * + * \param base the base directory + * \param regex the regular expression + * \return the full path name to the file or empty string if never found + * \throw std::runtime_error on read errors + */ +inline std::string find(const std::string &base, const std::regex ®ex) +{ + return findIf(base, [&] (const auto &, const auto &entry) { return std::regex_match(entry.name, regex); }); +} + +/** + * Get the current working directory. + * + * \return the current working directory + * \throw std::runtime_error on failure + */ +IRCCD_EXPORT std::string cwd(); + +} // !fs + +} // !irccd + +#endif // !FS_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/ini.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,420 @@ +/* + * ini.cpp -- extended .ini file parser + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cctype> +#include <cstring> +#include <iostream> +#include <iterator> +#include <fstream> +#include <sstream> +#include <stdexcept> + +// for PathIsRelative. +#if defined(_WIN32) +# if !defined(WIN32_LEAN_AND_MEAN) +# define WIN32_LEAN_AND_MEAN +# endif + +# include <shlwapi.h> +#endif + +#include "ini.hpp" + +namespace irccd { + +namespace { + +using namespace ini; + +using StreamIterator = std::istreambuf_iterator<char>; +using TokenIterator = std::vector<Token>::const_iterator; + +inline bool isAbsolute(const std::string &path) noexcept +{ +#if defined(_WIN32) + return !PathIsRelative(path.c_str()); +#else + return path.size() > 0 && path[0] == '/'; +#endif +} + +inline bool isQuote(char c) noexcept +{ + return c == '\'' || c == '"'; +} + +inline bool isSpace(char c) noexcept +{ + // Custom version because std::isspace includes \n as space. + return c == ' ' || c == '\t'; +} + +inline bool isList(char c) noexcept +{ + return c == '(' || c == ')' || c == ','; +} + +inline bool isReserved(char c) noexcept +{ + return isList(c) || isQuote(c) || c == '[' || c == ']' || c == '@' || c == '#' || c == '='; +} + +void analyseLine(int &line, int &column, StreamIterator &it) noexcept +{ + assert(*it == '\n'); + + ++ line; + ++ it; + column = 0; +} + +void analyseComment(int &column, StreamIterator &it, StreamIterator end) noexcept +{ + assert(*it == '#'); + + while (it != end && *it != '\n') { + ++ column; + ++ it; + } +} + +void analyseSpaces(int &column, StreamIterator &it, StreamIterator end) noexcept +{ + assert(isSpace(*it)); + + while (it != end && isSpace(*it)) { + ++ column; + ++ it; + } +} + +void analyseList(Tokens &list, int line, int &column, StreamIterator &it) noexcept +{ + assert(isList(*it)); + + switch (*it++) { + case '(': + list.emplace_back(Token::ListBegin, line, column++); + break; + case ')': + list.emplace_back(Token::ListEnd, line, column++); + break; + case ',': + list.emplace_back(Token::Comma, line, column++); + break; + default: + break; + } +} + +void analyseSection(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + assert(*it == '['); + + std::string value; + int save = column; + + // Read section name. + ++ it; + while (it != end && *it != ']') { + if (*it == '\n') + throw Error(line, column, "section not terminated, missing ']'"); + if (isReserved(*it)) + throw Error(line, column, "section name expected after '[', got '" + std::string(1, *it) + "'"); + + ++ column; + value += *it++; + } + + if (it == end) + throw Error(line, column, "section name expected after '[', got <EOF>"); + if (value.empty()) + throw Error(line, column, "empty section name"); + + // Remove ']'. + ++ it; + + list.emplace_back(Token::Section, line, save, std::move(value)); +} + +void analyseAssign(Tokens &list, int &line, int &column, StreamIterator &it) +{ + assert(*it == '='); + + list.push_back({ Token::Assign, line, column++ }); + ++ it; +} + +void analyseQuotedWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + std::string value; + int save = column; + char quote = *it++; + + while (it != end && *it != quote) { + // TODO: escape sequence + ++ column; + value += *it++; + } + + if (it == end) + throw Error(line, column, "undisclosed '" + std::string(1, quote) + "', got <EOF>"); + + // Remove quote. + ++ it; + + list.push_back({ Token::QuotedWord, line, save, std::move(value) }); +} + +void analyseWord(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + assert(!isReserved(*it)); + + std::string value; + int save = column; + + while (it != end && !std::isspace(*it) && !isReserved(*it)) { + ++ column; + value += *it++; + } + + list.push_back({ Token::Word, line, save, std::move(value) }); +} + +void analyseInclude(Tokens &list, int &line, int &column, StreamIterator &it, StreamIterator end) +{ + assert(*it == '@'); + + std::string include; + int save = column; + + // Read include. + ++ it; + while (it != end && !isSpace(*it)) { + ++ column; + include += *it++; + } + + if (include != "include") + throw Error(line, column, "expected include after '@' token"); + + list.push_back({ Token::Include, line, save }); +} + +void parseOptionValueSimple(Option &option, TokenIterator &it) +{ + assert(it->type() == Token::Word || it->type() == Token::QuotedWord); + + option.push_back((it++)->value()); +} + +void parseOptionValueList(Option &option, TokenIterator &it, TokenIterator end) +{ + assert(it->type() == Token::ListBegin); + + TokenIterator save = it++; + + while (it != end && it->type() != Token::ListEnd) { + switch (it->type()) { + case Token::Comma: + // Previous must be a word. + if (it[-1].type() != Token::Word && it[-1].type() != Token::QuotedWord) + throw Error(it->line(), it->column(), "unexpected comma after '" + it[-1].value() + "'"); + + ++ it; + break; + case Token::Word: + case Token::QuotedWord: + option.push_back((it++)->value()); + break; + default: + throw Error(it->line(), it->column(), "unexpected '" + it[-1].value() + "' in list construct"); + break; + } + } + + if (it == end) + throw Error(save->line(), save->column(), "unterminated list construct"); + + // Remove ). + ++ it; +} + +void parseOption(Section &sc, TokenIterator &it, TokenIterator end) +{ + Option option(it->value()); + + TokenIterator save = it; + + // No '=' or something else? + if (++it == end) + throw Error(save->line(), save->column(), "expected '=' assignment, got <EOF>"); + if (it->type() != Token::Assign) + throw Error(it->line(), it->column(), "expected '=' assignment, got " + it->value()); + + // Empty options are allowed so just test for words. + if (++it != end) { + if (it->type() == Token::Word || it->type() == Token::QuotedWord) + parseOptionValueSimple(option, it); + else if (it->type() == Token::ListBegin) + parseOptionValueList(option, it, end); + } + + sc.push_back(std::move(option)); +} + +void parseInclude(Document &doc, const std::string &path, TokenIterator &it, TokenIterator end) +{ + TokenIterator save = it; + + if (++it == end) + throw Error(save->line(), save->column(), "expected file name after '@include' statement, got <EOF>"); + if (it->type() != Token::Word && it->type() != Token::QuotedWord) + throw Error(it->line(), it->column(), "expected file name after '@include' statement, got " + it->value()); + + std::string value = (it++)->value(); + std::string file; + + if (!isAbsolute(value)) +#if defined(_WIN32) + file = path + "\\" + value; +#else + file = path + "/" + value; +#endif + else + file = value; + + for (const auto &sc : readFile(file)) + doc.push_back(sc); +} + +void parseSection(Document &doc, TokenIterator &it, TokenIterator end) +{ + Section sc(it->value()); + + // Skip [section]. + ++ it; + + // Read until next section. + while (it != end && it->type() != Token::Section) { + if (it->type() != Token::Word) + throw Error(it->line(), it->column(), "unexpected token '" + it->value() + "' in section definition"); + + parseOption(sc, it, end); + } + + doc.push_back(std::move(sc)); +} + +} // !namespace + +namespace ini { + +Tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end) +{ + Tokens list; + int line = 1; + int column = 0; + + while (it != end) { + if (*it == '\n') + analyseLine(line, column, it); + else if (*it == '#') + analyseComment(column, it, end); + else if (*it == '[') + analyseSection(list, line, column, it, end); + else if (*it == '=') + analyseAssign(list, line, column, it); + else if (isSpace(*it)) + analyseSpaces(column, it, end); + else if (*it == '@') + analyseInclude(list, line, column, it, end); + else if (isQuote(*it)) + analyseQuotedWord(list, line, column, it, end); + else if (isList(*it)) + analyseList(list, line, column, it); + else + analyseWord(list, line, column, it, end); + } + + return list; +} + +Tokens analyse(std::istream &stream) +{ + return analyse(std::istreambuf_iterator<char>(stream), {}); +} + +Document parse(const Tokens &tokens, const std::string &path) +{ + Document doc; + TokenIterator it = tokens.cbegin(); + TokenIterator end = tokens.cend(); + + while (it != end) { + switch (it->type()) { + case Token::Include: + parseInclude(doc, path, it, end); + break; + case Token::Section: + parseSection(doc, it, end); + break; + default: + throw Error(it->line(), it->column(), "unexpected '" + it->value() + "' on root document"); + } + } + + return doc; +} + +Document readFile(const std::string &filename) +{ + // Get parent path. + auto parent = filename; + auto pos = parent.find_last_of("/\\"); + + if (pos != std::string::npos) + parent.erase(pos); + else + parent = "."; + + std::ifstream input(filename); + + if (!input) + throw Error(0, 0, std::strerror(errno)); + + return parse(analyse(input), parent); +} + +Document readString(const std::string &buffer) +{ + std::istringstream iss(buffer); + + return parse(analyse(iss)); +} + +void dump(const Tokens &tokens) +{ + for (const Token &token: tokens) + // TODO: add better description + std::cout << token.line() << ":" << token.column() << ": " << token.value() << std::endl; +} + +} // !ini + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/ini.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,616 @@ +/* + * ini.hpp -- extended .ini file parser + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef INI_HPP +#define INI_HPP + +/** + * \file ini.hpp + * \brief Extended .ini file parser. + * \author David Demelier <markand@malikania.fr> + */ + +/** + * \page Ini Ini + * \brief Extended .ini file parser. + * + * - \subpage ini-syntax + */ + +/** + * \page ini-syntax Syntax + * \brief File syntax. + * + * The syntax is similar to most of `.ini` implementations as: + * + * - a section is delimited by `[name]` can be redefined multiple times, + * - an option **must** always be defined in a section, + * - empty options must be surrounded by quotes, + * - lists can not includes trailing commas, + * - include statement must always be at the beginning of files (in no sections), + * - comments starts with # until the end of line, + * - options with spaces **must** use quotes. + * + * # Basic file + * + * ````ini + * # This is a comment. + * [section] + * option1 = value1 + * option2 = "value 2 with spaces" # comment is also allowed here + * ```` + * + * # Redefinition + * + * Sections can be redefined multiple times and are kept the order they are seen. + * + * ````ini + * [section] + * value = "1" + * + * [section] + * value = "2" + * ```` + * + * The ini::Document object will contains two ini::Section. + * + * # Lists + * + * Lists are defined using `()` and commas, like values, they may have quotes. + * + * ````ini + * [section] + * names = ( "x1", "x2" ) + * + * # This is also allowed + * biglist = ( + * "abc", + * "def" + * ) + * ```` + * + * # Include statement + * + * You can split a file into several pieces, if the include statement contains a relative path, the path will be relative + * to the current file being parsed. + * + * You **must** use the include statement before any section. + * + * If the file contains spaces, use quotes. + * + * ````ini + * # main.conf + * @include "foo.conf" + * + * # foo.conf + * [section] + * option1 = value1 + * ```` + */ + +#include <algorithm> +#include <cassert> +#include <exception> +#include <stdexcept> +#include <string> +#include <vector> + +#include "sysconfig.hpp" + +namespace irccd { + +/** + * Namespace for ini related classes. + */ +namespace ini { + +class Document; + +/** + * \class Error + * \brief Error in a file. + */ +class Error : public std::exception { +private: + int m_line; //!< line number + int m_column; //!< line column + std::string m_message; //!< error message + +public: + /** + * Constructor. + * + * \param line the line + * \param column the column + * \param msg the message + */ + inline Error(int line, int column, std::string msg) noexcept + : m_line(line) + , m_column(column) + , m_message(std::move(msg)) + { + } + + /** + * Get the line number. + * + * \return the line + */ + inline int line() const noexcept + { + return m_line; + } + + /** + * Get the column number. + * + * \return the column + */ + inline int column() const noexcept + { + return m_column; + } + + /** + * Return the raw error message (no line and column shown). + * + * \return the error message + */ + const char *what() const noexcept override + { + return m_message.c_str(); + } +}; + +/** + * \class Token + * \brief Describe a token read in the .ini source. + * + * This class can be used when you want to parse a .ini file yourself. + * + * \see analyze + */ +class Token { +public: + /** + * \brief Token type. + */ + enum Type { + Include, //!< include statement + Section, //!< [section] + Word, //!< word without quotes + QuotedWord, //!< word with quotes + Assign, //!< = assignment + ListBegin, //!< begin of list ( + ListEnd, //!< end of list ) + Comma //!< list separation + }; + +private: + Type m_type; + int m_line; + int m_column; + std::string m_value; + +public: + /** + * Construct a token. + * + * \param type the type + * \param line the line + * \param column the column + * \param value the value + */ + Token(Type type, int line, int column, std::string value = "") noexcept + : m_type(type) + , m_line(line) + , m_column(column) + { + switch (type) { + case Include: + m_value = "@include"; + break; + case Section: + case Word: + case QuotedWord: + m_value = value; + break; + case Assign: + m_value = "="; + break; + case ListBegin: + m_value = "("; + break; + case ListEnd: + m_value = ")"; + break; + case Comma: + m_value = ","; + break; + default: + break; + } + } + + /** + * Get the type. + * + * \return the type + */ + inline Type type() const noexcept + { + return m_type; + } + + /** + * Get the line. + * + * \return the line + */ + inline int line() const noexcept + { + return m_line; + } + + /** + * Get the column. + * + * \return the column + */ + inline int column() const noexcept + { + return m_column; + } + + /** + * Get the value. For words, quoted words and section, the value is the content. Otherwise it's the + * characters parsed. + * + * \return the value + */ + inline const std::string &value() const noexcept + { + return m_value; + } +}; + +/** + * List of tokens in order they are analyzed. + */ +using Tokens = std::vector<Token>; + +/** + * \class Option + * \brief Option definition. + */ +class Option : public std::vector<std::string> { +private: + std::string m_key; + +public: + /** + * Construct an empty option. + * + * \pre key must not be empty + * \param key the key + */ + inline Option(std::string key) noexcept + : std::vector<std::string>() + , m_key(std::move(key)) + { + assert(!m_key.empty()); + } + + /** + * Construct a single option. + * + * \pre key must not be empty + * \param key the key + * \param value the value + */ + inline Option(std::string key, std::string value) noexcept + : m_key(std::move(key)) + { + assert(!m_key.empty()); + + push_back(std::move(value)); + } + + /** + * Construct a list option. + * + * \pre key must not be empty + * \param key the key + * \param values the values + */ + inline Option(std::string key, std::vector<std::string> values) noexcept + : std::vector<std::string>(std::move(values)) + , m_key(std::move(key)) + { + assert(!m_key.empty()); + } + + /** + * Get the option key. + * + * \return the key + */ + inline const std::string &key() const noexcept + { + return m_key; + } + + /** + * Get the option value. + * + * \return the value + */ + inline const std::string &value() const noexcept + { + static std::string dummy; + + return empty() ? dummy : (*this)[0]; + } +}; + +/** + * \class Section + * \brief Section that contains one or more options. + */ +class Section : public std::vector<Option> { +private: + std::string m_key; + +public: + /** + * Construct a section with its name. + * + * \pre key must not be empty + * \param key the key + */ + inline Section(std::string key) noexcept + : m_key(std::move(key)) + { + assert(!m_key.empty()); + } + + /** + * Get the section key. + * + * \return the key + */ + inline const std::string &key() const noexcept + { + return m_key; + } + + /** + * Check if the section contains a specific option. + * + * \param key the option key + * \return true if the option exists + */ + inline bool contains(const std::string &key) const noexcept + { + return find(key) != end(); + } + + /** + * Find an option by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline iterator find(const std::string &key) noexcept + { + return std::find_if(begin(), end(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Find an option by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline const_iterator find(const std::string &key) const noexcept + { + return std::find_if(cbegin(), cend(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Access an option at the specified key. + * + * \param key the key + * \return the option + * \pre contains(key) must return true + */ + inline Option &operator[](const std::string &key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * \param key the key + * \return the option + * \pre contains(key) must return true + */ + inline const Option &operator[](const std::string &key) const + { + assert(contains(key)); + + return *find(key); + } + + /** + * Inherited operators. + */ + using std::vector<Option>::operator[]; +}; + +/** + * \class Document + * \brief Ini document description. + * \see readFile + * \see readString + */ +class Document : public std::vector<Section> { +public: + /** + * Check if a document has a specific section. + * + * \param key the key + * \return true if the document contains the section + */ + inline bool contains(const std::string &key) const noexcept + { + return std::find_if(begin(), end(), [&] (const auto &sc) { return sc.key() == key; }) != end(); + } + + /** + * Find a section by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline iterator find(const std::string &key) noexcept + { + return std::find_if(begin(), end(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Find a section by key and return an iterator. + * + * \param key the key + * \return the iterator or end() if not found + */ + inline const_iterator find(const std::string &key) const noexcept + { + return std::find_if(cbegin(), cend(), [&] (const auto &o) { + return o.key() == key; + }); + } + + /** + * Access a section at the specified key. + * + * \param key the key + * \return the section + * \pre contains(key) must return true + */ + inline Section &operator[](const std::string &key) + { + assert(contains(key)); + + return *find(key); + } + + /** + * Overloaded function. + * + * \param key the key + * \return the section + * \pre contains(key) must return true + */ + inline const Section &operator[](const std::string &key) const + { + assert(contains(key)); + + return *find(key); + } + + /** + * Inherited operators. + */ + using std::vector<Section>::operator[]; +}; + +/** + * Analyse a stream and detect potential syntax errors. This does not parse the file like including other + * files in include statement. + * + * It does only analysis, for example if an option is defined under no section, this does not trigger an + * error while it's invalid. + * + * \param it the iterator + * \param end where to stop + * \return the list of tokens + * \throws Error on errors + */ +IRCCD_EXPORT Tokens analyse(std::istreambuf_iterator<char> it, std::istreambuf_iterator<char> end); + +/** + * Overloaded function for stream. + * + * \param stream the stream + * \return the list of tokens + * \throws Error on errors + */ +IRCCD_EXPORT Tokens analyse(std::istream &stream); + +/** + * Parse the produced tokens. + * + * \param tokens the tokens + * \param path the parent path + * \return the document + * \throw Error on errors + */ +IRCCD_EXPORT Document parse(const Tokens &tokens, const std::string &path = "."); + +/** + * Parse a file. + * + * \param filename the file name + * \return the document + * \throw Error on errors + */ +IRCCD_EXPORT Document readFile(const std::string &filename); + +/** + * Parse a string. + * + * If the string contains include statements, they are relative to the current working directory. + * + * \param buffer the buffer + * \return the document + * \throw Error on errors + */ +IRCCD_EXPORT Document readString(const std::string &buffer); + +/** + * Show all tokens and their description. + * + * \param tokens the tokens + */ +IRCCD_EXPORT void dump(const Tokens &tokens); + +} // !ini + +} // !irccd + +#endif // !INI_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/logger.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,299 @@ +/* + * logger.cpp -- irccd logging + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <atomic> +#include <cassert> +#include <cerrno> +#include <cstring> +#include <fstream> +#include <iostream> +#include <stdexcept> +#include <streambuf> + +#include "logger.hpp" +#include "system.hpp" + +#if defined(HAVE_SYSLOG) +# include <syslog.h> +#endif // !HAVE_SYSLOG + +namespace irccd { + +namespace log { + +namespace { + +/* + * User definable options. + * ------------------------------------------------------------------ + */ + +std::atomic<bool> verbose{false}; +std::unique_ptr<Logger> iface{new ConsoleLogger}; +std::unique_ptr<Filter> filter{new Filter}; + +/* + * Buffer -- output buffer. + * ------------------------------------------------------------------ + * + * This class inherits from std::stringbuf and writes the messages to the + * specified interface function which is one of info, warning and debug. + */ + +class Buffer : public std::stringbuf { +public: + enum Level { + Debug, + Info, + Warning + }; + +private: + Level m_level; + + void debug(std::string line) + { + // Print only in debug mode, the buffer is flushed anyway. +#if !defined(NDEBUG) + iface->debug(filter->preDebug(std::move(line))); +#else + (void)line; +#endif + } + + void info(std::string line) + { + // Print only if verbose, the buffer will be flushed anyway. + if (verbose) + iface->info(filter->preInfo(std::move(line))); + } + + void warning(std::string line) + { + iface->warning(filter->preWarning(std::move(line))); + } + +public: + inline Buffer(Level level) noexcept + : m_level(level) + { + assert(level >= Debug && level <= Warning); + } + + virtual int sync() override + { + std::string buffer = str(); + std::string::size_type pos; + + while ((pos = buffer.find("\n")) != std::string::npos) { + std::string line = buffer.substr(0, pos); + + // Remove this line. + buffer.erase(buffer.begin(), buffer.begin() + pos + 1); + + switch (m_level) { + case Level::Debug: + debug(std::move(line)); + break; + case Level::Info: + info(std::move(line)); + break; + case Level::Warning: + warning(std::move(line)); + break; + default: + break; + } + } + + str(buffer); + + return 0; + } +}; + +/* + * Local variables. + * ------------------------------------------------------------------ + */ + +// Buffers. +Buffer bufferInfo{Buffer::Info}; +Buffer bufferWarning{Buffer::Warning}; +Buffer bufferDebug{Buffer::Debug}; + +// Stream outputs. +std::ostream streamInfo(&bufferInfo); +std::ostream streamWarning(&bufferWarning); +std::ostream streamDebug(&bufferDebug); + +} // !namespace + +/* + * ConsoleLogger + * ------------------------------------------------------------------ + */ + +void ConsoleLogger::info(const std::string &line) +{ + std::cout << line << std::endl; +} + +void ConsoleLogger::warning(const std::string &line) +{ + std::cerr << line << std::endl; +} + +void ConsoleLogger::debug(const std::string &line) +{ + std::cout << line << std::endl; +} + +/* + * FileLogger + * ------------------------------------------------------------------ + */ + +FileLogger::FileLogger(std::string normal, std::string errors) + : m_outputNormal(std::move(normal)) + , m_outputError(std::move(errors)) +{ +} + +void FileLogger::info(const std::string &line) +{ + std::ofstream(m_outputNormal, std::ofstream::out | std::ofstream::app) << line << std::endl; +} + +void FileLogger::warning(const std::string &line) +{ + std::ofstream(m_outputError, std::ofstream::out | std::ofstream::app) << line << std::endl; +} + +void FileLogger::debug(const std::string &line) +{ + std::ofstream(m_outputNormal, std::ofstream::out | std::ofstream::app) << line << std::endl; +} + +/* + * SilentLogger + * ------------------------------------------------------------------ + */ + +void SilentLogger::info(const std::string &) +{ +} + +void SilentLogger::warning(const std::string &) +{ +} + +void SilentLogger::debug(const std::string &) +{ +} + +/* + * SyslogLogger + * ------------------------------------------------------------------ + */ + +#if defined(HAVE_SYSLOG) + +SyslogLogger::SyslogLogger() +{ + openlog(sys::programName().c_str(), LOG_PID, LOG_DAEMON); +} + +SyslogLogger::~SyslogLogger() +{ + closelog(); +} + +void SyslogLogger::info(const std::string &line) +{ + syslog(LOG_INFO | LOG_USER, "%s", line.c_str()); +} + +void SyslogLogger::warning(const std::string &line) +{ + syslog(LOG_WARNING | LOG_USER, "%s", line.c_str()); +} + +void SyslogLogger::debug(const std::string &line) +{ + syslog(LOG_DEBUG | LOG_USER, "%s", line.c_str()); +} + +#endif // !HAVE_SYSLOG + +/* + * Functions + * ------------------------------------------------------------------ + */ + +void setLogger(std::unique_ptr<Logger> newiface) noexcept +{ + assert(newiface); + + iface = std::move(newiface); +} + +void setFilter(std::unique_ptr<Filter> newfilter) noexcept +{ + assert(filter); + + filter = std::move(newfilter); +} + +std::ostream &info(const std::string &message) +{ + if (!message.empty()) + streamInfo << message << std::endl; + + return streamInfo; +} + +std::ostream &warning(const std::string &message) +{ + if (!message.empty()) + streamWarning << message << std::endl; + + return streamWarning; +} + +std::ostream &debug(const std::string &message) +{ + if (!message.empty()) + streamDebug << message << std::endl; + + return streamDebug; +} + +bool isVerbose() noexcept +{ + return verbose; +} + +void setVerbose(bool mode) noexcept +{ + verbose = mode; +} + +} // !log + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/logger.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,359 @@ +/* + * logger.hpp -- irccd logging + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_LOGGER_HPP +#define IRCCD_LOGGER_HPP + +/** + * \file logger.hpp + * \brief Logging facilities. + */ + +#include <memory> +#include <sstream> +#include <utility> + +#include "sysconfig.hpp" + +namespace irccd { + +namespace log { + +/* + * Logger -- abstract logging interface + * ------------------------------------------------------------------ + */ + +/** + * \brief Interface to implement new logger mechanisms. + * + * Derive from this class and use log::setLogger() to change logging system. + * + * \see File + * \see Console + * \see Syslog + * \see Silent + */ +class Logger { +public: + /** + * Default constructor. + */ + Logger() = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~Logger() = default; + + /** + * Write a debug message. + * + * This function is called only if NDEBUG is not defined. + * + * \param line the data + * \see log::debug + */ + virtual void debug(const std::string &line) = 0; + + /** + * Write a information message. + * + * The function is called only if verbose is true. + * + * \param line the data + * \see log::info + */ + virtual void info(const std::string &line) = 0; + + /** + * Write an error message. + * + * This function is always called. + * + * \param line the data + * \see log::warning + */ + virtual void warning(const std::string &line) = 0; +}; + +/* + * Filter -- modify messages before printing + * ------------------------------------------------------------------ + */ + +/** + * \brief Filter messages before printing them. + * + * Derive from this class and use log::setFilter. + */ +class Filter { +public: + /** + * Default constructor. + */ + Filter() = default; + + /** + * Virtual destructor defaulted. + */ + virtual ~Filter() = default; + + /** + * Update the debug message. + * + * \param input the message + * \return the updated message + */ + virtual std::string preDebug(std::string input) const + { + return input; + } + + /** + * Update the information message. + * + * \param input the message + * \return the updated message + */ + virtual std::string preInfo(std::string input) const + { + return input; + } + + /** + * Update the warning message. + * + * \param input the message + * \return the updated message + */ + virtual std::string preWarning(std::string input) const + { + return input; + } +}; + +/* + * Console -- logs to console + * ------------------------------------------------------------------ + */ + +/** + * \brief Logger implementation for console output using std::cout and + * std::cerr. + */ +class ConsoleLogger : public Logger { +public: + IRCCD_EXPORT ConsoleLogger() = default; + + /** + * \copydoc Logger::debug + */ + IRCCD_EXPORT void debug(const std::string &line) override; + + /** + * \copydoc Logger::info + */ + IRCCD_EXPORT void info(const std::string &line) override; + + /** + * \copydoc Logger::warning + */ + IRCCD_EXPORT void warning(const std::string &line) override; +}; + +/* + * File -- logs to a file + * ------------------------------------------------------------------ + */ + +/** + * \brief Output to a files. + */ +class FileLogger : public Logger { +private: + std::string m_outputNormal; + std::string m_outputError; + +public: + /** + * Outputs to files. + * + * \param normal the path to the normal logs + * \param errors the path to the errors logs + */ + IRCCD_EXPORT FileLogger(std::string normal, std::string errors); + + /** + * \copydoc Logger::debug + */ + IRCCD_EXPORT void debug(const std::string &line) override; + + /** + * \copydoc Logger::info + */ + IRCCD_EXPORT void info(const std::string &line) override; + + /** + * \copydoc Logger::warning + */ + IRCCD_EXPORT void warning(const std::string &line) override; +}; + +/* + * Silent -- disable all logs + * ------------------------------------------------------------------ + */ + +/** + * \brief Use to disable logs. + * + * Useful for unit tests when some classes may emits log. + */ +class SilentLogger : public Logger { +public: + IRCCD_EXPORT SilentLogger() = default; + + /** + * \copydoc Logger::debug + */ + IRCCD_EXPORT void debug(const std::string &line) override; + + /** + * \copydoc Logger::info + */ + IRCCD_EXPORT void info(const std::string &line) override; + + /** + * \copydoc Logger::warning + */ + IRCCD_EXPORT void warning(const std::string &line) override; +}; + +/* + * Syslog -- system logger + * ------------------------------------------------------------------ + */ + +#if defined(HAVE_SYSLOG) + +/** + * \brief Implements logger into syslog. + */ +class SyslogLogger : public Logger { +public: + /** + * Open the syslog. + */ + IRCCD_EXPORT SyslogLogger(); + + /** + * Close the syslog. + */ + IRCCD_EXPORT ~SyslogLogger(); + + /** + * \copydoc Logger::debug + */ + IRCCD_EXPORT void debug(const std::string &line) override; + + /** + * \copydoc Logger::info + */ + IRCCD_EXPORT void info(const std::string &line) override; + + /** + * \copydoc Logger::warning + */ + IRCCD_EXPORT void warning(const std::string &line) override; +}; + +#endif // !HAVE_SYSLOG + +/* + * Functions + * ------------------------------------------------------------------ + */ + +/** + * Update the logger interface. + * + * \pre iface must not be null + * \param iface the new interface + */ +IRCCD_EXPORT void setLogger(std::unique_ptr<Logger> iface) noexcept; + +/** + * Set an optional filter. + * + * \pre filter must not be null + * \param filter the filter + */ +IRCCD_EXPORT void setFilter(std::unique_ptr<Filter> filter) noexcept; + +/** + * Get the stream for informational messages. + * + * If message is specified, a new line character is appended. + * + * \param message the optional message to write + * \return the stream + * \note Has no effect if verbose is set to false. + */ +IRCCD_EXPORT std::ostream &info(const std::string &message = ""); + +/** + * Get the stream for warnings. + * + * If message is specified, a new line character is appended. + * + * \param message the optional message to write + * \return the stream + */ +IRCCD_EXPORT std::ostream &warning(const std::string &message = ""); + +/** + * Get the stream for debug messages. + * + * If message is specified, a new line character is appended. + * + * \param message the optional message to write + * \return the stream + * \note Has no effect if compiled in release mode. + */ +IRCCD_EXPORT std::ostream &debug(const std::string &message = ""); + +/** + * Tells if verbose is enabled. + * + * \return true if enabled + */ +IRCCD_EXPORT bool isVerbose() noexcept; + +/** + * Set the verbosity mode. + * + * \param mode the new mode + */ +IRCCD_EXPORT void setVerbose(bool mode) noexcept; + +} // !log + +} // !irccd + +#endif // !IRCCD_LOGGER_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/net.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,3725 @@ +/* + * net.hpp -- portable C++ socket wrapper + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_NET_HPP +#define IRCCD_NET_HPP + +/** + * \file net.hpp + * \brief Networking + * \author David Demelier <markand@malikania.fr> + */ + +/** + * \defgroup net-module-tcp Network TCP support + * \brief TCP support in the networking module. + */ + +/** + * \defgroup net-module-udp Network UDP support + * \brief UDP support in networking module. + */ + +/** + * \defgroup net-module-tls Network TLS support + * \brief TLS support in networking module. + */ + +/** + * \defgroup net-module-addresses Network predefined addresses + * \brief Predefined addresses for sockets + */ + +/** + * \defgroup net-module-options Network predefined options + * \brief Predefined options for sockets + */ + +/** + * \defgroup net-module-backends Network predefined backends + * \brief Predefined backends for Listener + */ + +/** + * \defgroup net-module-resolv Network resolver + * \brief Resolv functions + */ + +/** + * \page Networking Networking + * + * - \subpage net-options + * - \subpage net-concepts + */ + +/** + * \page net-options User options + * + * The user may set the following variables before compiling these files: + * + * # General options + * + * - **NET_NO_AUTO_INIT**: (bool) Set to 0 if you don't want Socket class to + * automatically calls init function and finish functions. + * + * - **NET_NO_SSL**: (bool) Set to 0 if you don't have access to OpenSSL + * library. + * + * - **NET_NO_AUTO_SSL_INIT**: (bool) Set to 0 if you don't want Socket class + * with Tls to automatically init the OpenSSL library. You will need to call + * ssl::init and ssl::finish. + * + * # General compatibility options. + * + * The following options are auto detected but you can override them if you + * want. + * + * - **NET_HAVE_INET_PTON**: (bool) Set to 1 if you have inet_pton function. + * True for all platforms and Windows + * if _WIN32_WINNT is greater or equal to 0x0600. + * + * - **NET_HAVE_INET_NTOP**: (bool) Same as above. + * + * **Note:** On Windows, it is highly encouraged to set _WIN32_WINNT to at least + * 0x0600 on MinGW. + * + * # Options for Listener class + * + * Feature detection, multiple implementations may be avaible, for example, + * Linux has poll, select and epoll. + * + * We assume that `select(2)` is always available. + * + * Of course, you can set the variables yourself if you test it with your build + * system. + * + * - **NET_HAVE_POLL**: Defined on all BSD, Linux. Also defined on Windows + * if _WIN32_WINNT is set to 0x0600 or greater. + * - **NET_HAVE_KQUEUE**: Defined on all BSD and Apple. + * - **NET_HAVE_EPOLL**: Defined on Linux only. + * - **NET_DEFAULT_BACKEND**: Which backend to use (e.g. `Select`). + * + * The preference priority is ordered from left to right. + * + * | System | Backend | Class name | + * |---------------|-------------------------|--------------| + * | Linux | epoll(7) | Epoll | + * | *BSD | kqueue(2) | Kqueue | + * | Windows | poll(2), select(2) | Poll, Select | + * | Mac OS X | kqueue(2) | Kqueue | + */ + +/** + * \page net-concepts Concepts + * + * - \subpage net-concept-backend + * - \subpage net-concept-option + * - \subpage net-concept-stream + * - \subpage net-concept-datagram + */ + +/** + * \page net-concept-backend Backend (Concept) + * + * A backend is an interface for the Listener class. It is primarily designed to + * be the most suitable for the host environment. + * + * The backend must be default constructible, it is highly encouraged to be move + * constructible. + * + * This concepts requires the following functions: + * + * # name + * + * Get the backend name, informational only. + * + * ## Synopsis + * + * ```` + * std::string name() const; + * ```` + * + * ## Returns + * + * The backend name. + * + * # set + * + * Set one or more condition for the given handle. + * + * ## Synopsis + * + * ```` + * void set(const ListenerTable &table, Handle handle, Condition condition, + * bool add); + * ```` + * + * ## Arguments + * + * - **table**: the current table of sockets, + * - **handle**: the handle to set, + * - **condition**: the condition to add (may be OR'ed), + * - **add**: hint set to true if the handle is not currently registered. + * + * # unset + * + * Unset one or more condition for the given handle. + * + * ## Synopsis + * + * ```` + * void unset(const ListenerTable &table, Handle handle, Condition condition, + * bool remove); + * ```` + * + * ## Arguments + * + * - **table**: the current table of sockets, + * - **handle**: the handle to update, + * - **condition**: the condition to remove (may be OR'ed), + * - **remove**: hint set to true if the handle will be completely removed. + * + * # wait + * + * Wait for multiple sockets to be ready. + * + * ## Synopsis + * + * ```` + * std::vector<ListenerStatus> wait(const ListenerTable &table, int ms); + * ```` + * + * ## Arguments + * + * - **table**: the current table, + * - **ms**: the number to wait in milliseconds, negative means forever. + * + * ## Returns + * + * The list of sockets ready paired to their condition. + */ + +/** + * \page net-concept-option Option (Concept) + * + * An option can be set or get from a socket. + * + * If an operation is not available, provides the function but throws an + * exception with ENOSYS message. + * + * This concepts requires the following functions: + * + * # Option (constructor) + * + * At least one default constructor must be present. + * + * ## Synopsis + * + * ```` + * Option() noexcept; + * ```` + * + * # set + * + * Set the option. + * + * ## Synopsis + * + * ```` + * template <typename Address> + * void set(Socket &sc) const; + * ```` + * + * ## Arguments + * + * - **sc**: the socket. + * + * # get + * + * Get an option, T can be any type. + * + * ## Synopsis + * + * ```` + * template <typename Address> + * T get(Socket &sc) const; + * ```` + * + * ## Arguments + * + * - **sc**: the socket. + * + * ## Returns + * + * The value. + */ + +/** + * \page net-concept-stream Stream (Concept) + * + * This concepts requires the following functions: + * + * # type + * + * Return the type of socket, usually `SOCK_STREAM`. + * + * ## Synopsis + * + * ```` + * int type() const noexcept; + * ```` + * + * ## Returns + * + * The type of socket. + * + * # connect + * + * Connect to the given address. + * + * ## Synopsis + * + * ```` + * void connect(const sockaddr *address, socklen_t length); // (0) + * void connect(const Address &address); // 1 (Optional) + * ```` + * + * ## Arguments + * + * - **address**: the address, + * - **length**: the address length. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. + * + * # accept + * + * Accept a new client. + * + * If no pending connection is available and operation would block, the + * implementation must throw WouldBlockError. Any other error can be thrown + * otherwise a valid socket must be returned. + * + * ## Synopsis + * + * ```` + * Socket accept(); + * ```` + * + * ## Returns + * + * The new socket. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. + * + * # recv + * + * Receive data. + * + * ## Synopsis + * + * ```` + * unsigned recv(void *data, unsigned length); + * ```` + * + * ## Arguments + * + * - **data**: the destination buffer, + * - **length**: the destination buffer length. + * + * ## Returns + * + * The number of bytes sent. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. + * + * # send + * + * Send data. + * + * ## Synopsis + * + * ```` + * unsigned send(const void *data, unsigned length); + * ```` + * + * ## Arguments + * + * - **data**: the data to send, + * - **length**: the data length. + * + * ## Returns + * + * The number of bytes sent. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. + */ + +/** + * \page net-concept-datagram Datagram (Concept) + * + * This concepts requires the following functions: + * + * # type + * + * Return the type of socket, usually `SOCK_DGRAM`. + * + * ## Synopsis + * + * ```` + * int type() const noexcept; + * ```` + * + * ## Returns + * + * The type of socket. + * + * # recvfrom + * + * Receive data. + * + * ## Synopsis + * + * ```` + * unsigned recvfrom(void *data, unsigned length, sockaddr *address, + * socklen_t *addrlen); + * unsigned recvfrom(void *data, unsigned length, Address *source) + * ```` + * + * ## Arguments + * + * - **data**: the data, + * - **length**: the length, + * - **address**: the source address, + * - **addrlen**: the source address in/out length. + * + * ## Returns + * + * The number of bytes received. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. + * + * # sendto + * + * ```` + * unsigned sendto(const void *data, unsigned length, const sockaddr *address, + * socklen_t addrlen); + * unsigned sendto(const void *data, unsigned length, const Address &address); + * ```` + * + * ## Arguments + * + * - **data**: the data to send, + * - **length**: the data length, + * - **address**: the destination address, + * - **addrlen**: the destination address length. + * + * ## Returns + * + * The number of bytes sent. + * + * ## Throws + * + * - net::WouldBlockError: if the operation would block, + * - net::Error: on other error. + */ + +/* + * Headers to include. + * ------------------------------------------------------------------ + */ + +/* + * Include Windows headers before because it brings _WIN32_WINNT if not + * specified by the user. + */ +#if defined(_WIN32) +# include <winsock2.h> +# include <ws2tcpip.h> +#else +# include <sys/ioctl.h> +# include <sys/types.h> +# include <sys/socket.h> +# include <sys/un.h> + +# include <arpa/inet.h> + +# include <netinet/in.h> +# include <netinet/tcp.h> + +# include <fcntl.h> +# include <netdb.h> +# include <unistd.h> +#endif + +#include <algorithm> +#include <atomic> +#include <cassert> +#include <cerrno> +#include <chrono> +#include <climits> +#include <cstddef> +#include <cstdlib> +#include <cstring> +#include <iterator> +#include <memory> +#include <mutex> +#include <string> +#include <unordered_map> +#include <vector> + +#if !defined(NET_NO_SSL) + +#include <openssl/err.h> +#include <openssl/evp.h> +#include <openssl/ssl.h> + +#endif // !NET_NO_SSL + +/* + * Determine which I/O multiplexing implementations are available. + * ------------------------------------------------------------------ + * + * May define the following: + * + * - NET_HAVE_EPOLL + * - NET_HAVE_KQUEUE + * - NET_HAVE_POLL + */ + +#if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 && !defined(NET_HAVE_POLL) +# define NET_HAVE_POLL +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) +# if !defined(NET_HAVE_KQUEUE) +# define NET_HAVE_KQUEUE +# endif +# if !defined(NET_HAVE_POLL) +# define NET_HAVE_POLL +# endif +#elif defined(__linux__) +# if !defined(NET_HAVE_EPOLL) +# define NET_HAVE_EPOLL +# endif +# if !defined(NET_HAVE_POLL) +# define NET_HAVE_POLL +# endif +#endif + +/* + * Compatibility macros. + * ------------------------------------------------------------------ + */ + +/** + * \brief Tells if inet_pton is available + */ +#if !defined(NET_HAVE_INET_PTON) +# if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 +# define NET_HAVE_INET_PTON +# endif +# else +# define NET_HAVE_INET_PTON +# endif +#endif + +/** + * \brief Tells if inet_ntop is available + */ +#if !defined(NET_HAVE_INET_NTOP) +# if defined(_WIN32) +# if _WIN32_WINNT >= 0x0600 +# define NET_HAVE_INET_NTOP +# endif +# else +# define NET_HAVE_INET_NTOP +# endif +#endif + +/* + * Define NET_DEFAULT_BACKEND. + * ------------------------------------------------------------------ + * + * Define the default I/O multiplexing implementation to use if not specified. + */ + +/** + * \brief Defines the default backend + */ +#if defined(_WIN32) +# if !defined(NET_DEFAULT_BACKEND) +# if defined(NET_HAVE_POLL) +# define NET_DEFAULT_BACKEND Poll +# else +# define NET_DEFAULT_BACKEND Select +# endif +# endif +#elif defined(__linux__) +# include <sys/epoll.h> + +# if !defined(NET_DEFAULT_BACKEND) +# define NET_DEFAULT_BACKEND Epoll +# endif +#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__) || defined(__APPLE__) +# include <sys/types.h> +# include <sys/event.h> +# include <sys/time.h> + +# if !defined(NET_DEFAULT_BACKEND) +# define NET_DEFAULT_BACKEND Kqueue +# endif +#else +# if !defined(NET_DEFAULT_BACKEND) +# define NET_DEFAULT_BACKEND Select +# endif +#endif + +#if defined(NET_HAVE_POLL) && !defined(_WIN32) +# include <poll.h> +#endif + +namespace irccd { + +/** + * The network namespace. + */ +namespace net { + +/* + * Portables types. + * ------------------------------------------------------------------ + * + * The following types are defined differently between Unix and Windows. + */ + +#if defined(_WIN32) + +/** + * Socket type, SOCKET. + */ +using Handle = SOCKET; + +/** + * Argument to pass to set. + */ +using ConstArg = const char *; + +/** + * Argument to pass to get. + */ +using Arg = char *; + +#else + +/** + * Socket type, int. + */ +using Handle = int; + +/** + * Argument to pass to set. + */ +using ConstArg = const void *; + +/** + * Argument to pass to get. + */ +using Arg = void *; + +#endif + +/* + * Portable constants. + * ------------------------------------------------------------------ + * + * These constants are needed to check functions return codes, they are rarely + * needed in end user code. + */ + +#if defined(_WIN32) + +/** + * Socket creation failure or invalidation. + */ +const Handle Invalid{INVALID_SOCKET}; + +/** + * Socket operation failure. + */ +const int Failure{SOCKET_ERROR}; + +#else + +/** + * Socket creation failure or invalidation. + */ +const Handle Invalid{-1}; + +/** + * Socket operation failure. + */ +const int Failure{-1}; + +#endif + +/** + * Close the socket library. + */ +inline void finish() noexcept +{ +#if defined(_WIN32) + WSACleanup(); +#endif +} + +/** + * Initialize the socket library. Except if you defined NET_NO_AUTO_INIT, you + * don't need to call this + * function manually. + */ +inline void init() noexcept +{ +#if defined(_WIN32) + static std::atomic<bool> initialized; + static std::mutex mutex; + + std::lock_guard<std::mutex> lock(mutex); + + if (!initialized) { + initialized = true; + + WSADATA wsa; + WSAStartup(MAKEWORD(2, 2), &wsa); + + /* + * If NET_NO_AUTO_INIT is not set then the user must also call finish + * himself. + */ +#if !defined(NET_NO_AUTO_INIT) + atexit(finish); +#endif + } +#endif +} + +/** + * Get the last system error. + * + * \param errn the error number (errno or WSAGetLastError) + * \return the error + */ +inline std::string error(int errn) +{ +#if defined(_WIN32) + LPSTR str = nullptr; + std::string errmsg = "Unknown error"; + + FormatMessageA( + FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + NULL, + errn, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPSTR)&str, 0, NULL); + + + if (str) { + errmsg = std::string(str); + LocalFree(str); + } + + return errmsg; +#else + return strerror(errn); +#endif +} + +/** + * Get the last socket system error. The error is set from errno or from + * WSAGetLastError on Windows. + * + * \return a string message + */ +inline std::string error() +{ +#if defined(_WIN32) + return error(WSAGetLastError()); +#else + return error(errno); +#endif +} + +#if !defined(NET_NO_SSL) + +/** + * \brief SSL namespace + */ +namespace ssl { + +/** + * \enum Method + * \brief Which OpenSSL method to use. + */ +enum Method { + Tlsv1, //!< TLS v1.2 (recommended) + Sslv3 //!< SSLv3 +}; + +/** + * Initialize the OpenSSL library. Except if you defined NET_NO_AUTO_SSL_INIT, + * you don't need to call this function manually. + */ +inline void init() noexcept +{ + static std::atomic<bool> initialized; + static std::mutex mutex; + + std::lock_guard<std::mutex> lock(mutex); + + if (!initialized) { + initialized = true; + + SSL_library_init(); + SSL_load_error_strings(); + OpenSSL_add_all_algorithms(); + +#if !defined(NET_NO_AUTO_SSL_INIT) + atexit(finish); +#endif + } +} + +/** + * Close the OpenSSL library. + */ +inline void finish() noexcept +{ + ERR_free_strings(); +} + +} // !ssl + +#endif // !NET_NO_SSL + +/* + * Error class. + * ------------------------------------------------------------------ + * + * This is the main exception thrown on socket operations. + */ + +/** + * \brief Base class for sockets error. + */ +class Error : public std::exception { +private: + std::string m_message; + +public: + /** + * Construct the error using the specified error from the system. + * + * \param code the error code + * \warning the code must be a Winsock error or errno on Unix + */ + inline Error(int code) noexcept + : m_message(error(code)) + { + } + + /** + * Construct the error using the custom message. + * + * \param message the message + */ + inline Error(std::string message) noexcept + : m_message(std::move(message)) + { + } + + /** + * Construct the error using the last message from the system. + */ + inline Error() noexcept +#if defined(_WIN32) + : Error(WSAGetLastError()) +#else + : Error(errno) +#endif + { + } + + /** + * Get the error (only the error content). + * + * \return the error + */ + const char *what() const noexcept override + { + return m_message.c_str(); + } +}; + +/** + * \brief Timeout occured. + */ +class TimeoutError : public std::exception { +public: + /** + * Get the error message. + * + * \return the message + */ + const char *what() const noexcept override + { + return std::strerror(ETIMEDOUT); + } +}; + +/** + * \brief Operation would block. + */ +class WouldBlockError : public std::exception { +public: + /** + * Get the error message. + * + * \return the message + */ + const char *what() const noexcept override + { + return std::strerror(EWOULDBLOCK); + } +}; + +/** + * \brief Operation requires sending data to complete. + */ +class WantWriteError : public std::exception { +public: + /** + * Get the error message. + * + * \return the message + */ + const char *what() const noexcept override + { + return "operation requires writing to complete"; + } +}; + +/** + * \brief Operation requires reading data to complete. + */ +class WantReadError : public std::exception { +public: + /** + * Get the error message. + * + * \return the message + */ + const char *what() const noexcept override + { + return "operation requires read to complete"; + } +}; + +/* + * Condition enum + * ------------------------------------------------------------------ + * + * Defines if we must wait for reading or writing. + */ + +/** + * \enum Condition + * \brief Define the required condition for the socket. + */ +enum class Condition { + None, //!< No condition is required + Readable = (1 << 0), //!< The socket must be readable + Writable = (1 << 1) //!< The socket must be writable +}; + +/** + * Apply bitwise XOR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition operator^(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); +} + +/** + * Apply bitwise AND. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition operator&(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); +} + +/** + * Apply bitwise OR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition operator|(Condition v1, Condition v2) noexcept +{ + return static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); +} + +/** + * Apply bitwise NOT. + * + * \param v the value + * \return the complement + */ +inline Condition operator~(Condition v) noexcept +{ + return static_cast<Condition>(~static_cast<int>(v)); +} + +/** + * Assign bitwise OR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition &operator|=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) | static_cast<int>(v2)); + + return v1; +} + +/** + * Assign bitwise AND. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition &operator&=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) & static_cast<int>(v2)); + + return v1; +} + +/** + * Assign bitwise XOR. + * + * \param v1 the first value + * \param v2 the second value + * \return the new value + */ +inline Condition &operator^=(Condition &v1, Condition v2) noexcept +{ + v1 = static_cast<Condition>(static_cast<int>(v1) ^ static_cast<int>(v2)); + + return v1; +} + +/** + * \brief Generic socket address storage. + * \ingroup net-module-addresses + */ +class Address { +private: + sockaddr_storage m_storage; + socklen_t m_length; + +public: + /** + * Construct empty address. + */ + inline Address() noexcept + : m_storage{} + , m_length(0) + { + } + + /** + * Construct address from existing one. + * + * \pre address != nullptr + * \param address the address + * \param length the address length + */ + inline Address(const sockaddr *address, socklen_t length) noexcept + : m_length(length) + { + assert(address); + + std::memcpy(&m_storage, address, length); + } + + /** + * Get the underlying address. + * + * \return the address + */ + inline const sockaddr *get() const noexcept + { + return reinterpret_cast<const sockaddr *>(&m_storage); + } + + /** + * Overloaded function + * + * \return the address + */ + inline sockaddr *get() noexcept + { + return reinterpret_cast<sockaddr *>(&m_storage); + } + + /** + * Get the underlying address as the given type (e.g sockaddr_in). + * + * \return the address reference + */ + template <typename T> + inline const T &as() const noexcept + { + return reinterpret_cast<const T &>(m_storage); + } + + /** + * Overloaded function + * + * \return the address reference + */ + template <typename T> + inline T &as() noexcept + { + return reinterpret_cast<T &>(m_storage); + } + + /** + * Get the underlying address length. + * + * \return the length + */ + inline socklen_t length() const noexcept + { + return m_length; + } + + /** + * Get the address domain. + * + * \return the domain + */ + inline int domain() const noexcept + { + return m_storage.ss_family; + } +}; + +/** + * \brief Address iterator. + * \ingroup net-module-addresses + * \see resolve + * + * This iterator can be used to try to connect to an host. + * + * When you use resolve with unspecified domain or socket type, the function may + * retrieve several different addresses that you can iterate over to try to + * connect to. + * + * Example: + * + * ````cpp + * SocketTcp sc; + * AddressIterator end, it = resolve("hostname.test", "80"); + * + * while (!connected_condition && it != end) + * sc.connect(it->address(), it->length()); + * ```` + * + * When an iterator equals to a default constructed iterator, it is considered + * not dereferenceable. + */ +class AddressIterator : public std::iterator<std::forward_iterator_tag, Address> { +private: + std::vector<Address> m_addresses; + std::size_t m_index{0}; + +public: + /** + * Construct a null iterator. + * + * The default constructed iterator is not dereferenceable. + */ + inline AddressIterator() noexcept = default; + + /** + * Construct an address iterator with a set of addresses. + * + * \pre index < m_addresses.size() + * \param addresses the addresses + * \param index the first index + */ + inline AddressIterator(std::vector<Address> addresses, std::size_t index = 0) noexcept + : m_addresses(std::move(addresses)) + , m_index(index) + { + assert(index < m_addresses.size()); + } + + /** + * Get the generic address. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline const Address &operator*() const noexcept + { + assert(m_index <= m_addresses.size()); + + return m_addresses[m_index]; + } + + /** + * Overloaded function. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline Address &operator*() noexcept + { + assert(m_index <= m_addresses.size()); + + return m_addresses[m_index]; + } + + /** + * Get the generic address. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline const Address *operator->() const noexcept + { + assert(m_index <= m_addresses.size()); + + return &m_addresses[m_index]; + } + + /** + * Overloaded function. + * + * \pre this is dereferenceable + * \return the generic address + */ + inline Address *operator->() noexcept + { + assert(m_index <= m_addresses.size()); + + return &m_addresses[m_index]; + } + + /** + * Pre-increment the iterator. + * + * \return this + */ + inline AddressIterator &operator++(int) noexcept + { + if (m_index + 1 >= m_addresses.size()) { + m_addresses.clear(); + m_index = 0; + } else + m_index ++; + + return *this; + } + + /** + * Post-increment the iterator. + * + * \return copy of this + */ + inline AddressIterator operator++() noexcept + { + AddressIterator save = *this; + + if (m_index + 1 >= m_addresses.size()) { + m_addresses.clear(); + m_index = 0; + } else + m_index ++; + + return save; + } + + friend bool operator==(const AddressIterator &, const AddressIterator &) noexcept; + friend bool operator!=(const AddressIterator &, const AddressIterator &) noexcept; +}; + +/** + * Compare two address iterators. + * + * \param i1 the first iterator + * \param i2 the second iterator + * \return true if they equal + */ +inline bool operator==(const AddressIterator &i1, const AddressIterator &i2) noexcept +{ + return i1.m_addresses == i2.m_addresses && i1.m_index == i2.m_index; +} + +/** + * Compare two address iterators. + * + * \param i1 the first iterator + * \param i2 the second iterator + * \return false if they equal + */ +inline bool operator!=(const AddressIterator &i1, const AddressIterator &i2) noexcept +{ + return !(i1 == i2); +} + +/** + * Compare two generic addresses. + * + * \param a1 the first address + * \param a2 the second address + * \return true if they equal + */ +inline bool operator==(const Address &a1, const Address &a2) noexcept +{ + return a1.length() == a2.length() && std::memcmp(a1.get(), a2.get(), a1.length()) == 0; +} + +/** + * Compare two generic addresses. + * + * \param a1 the first address + * \param a2 the second address + * \return false if they equal + */ +inline bool operator!=(const Address &a1, const Address &a2) noexcept +{ + return !(a1 == a2); +} + +/** + * \brief Base socket class. + */ +class Socket { +protected: + /** + * The native handle. + */ + Handle m_handle{Invalid}; + +public: + /** + * Create a socket handle. + * + * This is the primary function and the only one that creates the socket + * handle, all other constructors are just overloaded functions. + * + * \param domain the domain AF_* + * \param type the type SOCK_* + * \param protocol the protocol + * \throw Error on errors + */ + Socket(int domain, int type, int protocol) + { +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + m_handle = ::socket(domain, type, protocol); + + if (m_handle == Invalid) + throw Error(); + } + + /** + * Create the socket with an already defined handle and its protocol. + * + * \param handle the handle + */ + explicit inline Socket(Handle handle) noexcept + : m_handle(handle) + { + } + + /** + * Create an invalid socket. Can be used when you cannot instanciate the + * socket immediately. + */ + explicit inline Socket(std::nullptr_t) noexcept + : m_handle(Invalid) + { + } + + /** + * Copy constructor deleted. + */ + Socket(const Socket &) = delete; + + /** + * Transfer ownership from other to this. + * + * \param other the other socket + */ + inline Socket(Socket &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = Invalid; + } + + /** + * Default destructor. + */ + virtual ~Socket() + { + close(); + } + + /** + * Tells if the socket is not invalid. + * + * \return true if not invalid + */ + inline bool isOpen() const noexcept + { + return m_handle != Invalid; + } + + /** + * Set an option for the socket. Wrapper of setsockopt(2). + * + * \pre isOpen() + * \param level the setting level + * \param name the name + * \param arg the value + * \throw Error on errors + */ + template <typename Argument> + inline void set(int level, int name, const Argument &arg) + { + assert(m_handle != Invalid); + + if (::setsockopt(m_handle, level, name, (ConstArg)&arg, sizeof (arg)) == Failure) + throw Error(); + } + + /** + * Object-oriented option setter. + * + * The object must have `set(Socket &) const`. + * + * \pre isOpen() + * \param option the option + * \throw Error on errors + */ + template <typename Option> + inline void set(const Option &option) + { + assert(m_handle != Invalid); + + option.set(*this); + } + + /** + * Get an option for the socket. Wrapper of getsockopt(2). + * + * \pre isOpen() + * \param level the setting level + * \param name the name + * \return the value + * \throw Error on errors + */ + template <typename Argument> + Argument get(int level, int name) + { + assert(m_handle != Invalid); + + Argument desired, result{}; + socklen_t size = sizeof (result); + + if (::getsockopt(m_handle, level, name, (Arg)&desired, &size) == Failure) + throw Error(); + + std::memcpy(&result, &desired, size); + + return result; + } + + /** + * Object-oriented option getter. + * + * The object must have `T get(Socket &) const`, T can be any type and it is + * the value returned from this function. + * + * \pre isOpen() + * \return the same value as get() in the option + * \throw Error on errors + */ + template <typename Option> + inline auto get() -> decltype(std::declval<Option>().get(*this)) + { + assert(m_handle != Invalid); + + return Option().get(*this); + } + + /** + * Get the native handle. + * + * \return the handle + * \warning Not portable + */ + inline Handle handle() const noexcept + { + return m_handle; + } + + /** + * Bind using a native address. + * + * \pre isOpen() + * \param address the address + * \param length the size + * \throw Error on errors + */ + inline void bind(const sockaddr *address, socklen_t length) + { + assert(m_handle != Invalid); + + if (::bind(m_handle, address, length) == Failure) + throw Error(); + } + + /** + * Overload that takes an address. + * + * \pre isOpen() + * \param address the address + * \throw Error on errors + */ + inline void bind(const Address &address) + { + assert(m_handle != Invalid); + + bind(address.get(), address.length()); + } + + /** + * Listen for pending connection. + * + * \pre isOpen() + * \param max the maximum number + * \throw Error on errors + */ + inline void listen(int max = 128) + { + assert(m_handle != Invalid); + + if (::listen(m_handle, max) == Failure) + throw Error(); + } + + /** + * Get the local name. This is a wrapper of getsockname(). + * + * \pre isOpen() + * \return the address + * \throw Error on failures + */ + Address getsockname() const + { + assert(m_handle != Invalid); + + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + + if (::getsockname(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) + throw Error(); + + return Address(reinterpret_cast<sockaddr *>(&ss), length); + } + + /** + * Get connected address. This is a wrapper for getpeername(). + * + * \pre isOpen() + * \return the address + * \throw Error on failures + */ + Address getpeername() const + { + assert(m_handle != Invalid); + + sockaddr_storage ss; + socklen_t length = sizeof (sockaddr_storage); + + if (::getpeername(m_handle, reinterpret_cast<sockaddr *>(&ss), &length) == Failure) + throw Error(); + + return Address(reinterpret_cast<sockaddr *>(&ss), length); + } + + /** + * Close the socket. + * + * Automatically called from the destructor. + */ + void close() + { + if (m_handle != Invalid) { +#if defined(_WIN32) + ::closesocket(m_handle); +#else + ::close(m_handle); +#endif + m_handle = Invalid; + } + } + + /** + * Assignment operator forbidden. + * + * \return *this + */ + Socket &operator=(const Socket &) = delete; + + /** + * Transfer ownership from other to this. The other socket is left + * invalid and will not be closed. + * + * \param other the other socket + * \return this + */ + Socket &operator=(Socket &&other) noexcept + { + m_handle = other.m_handle; + other.m_handle = Invalid; + + return *this; + } +}; + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if they equals + */ +inline bool operator==(const Socket &s1, const Socket &s2) +{ + return s1.handle() == s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if they are different + */ +inline bool operator!=(const Socket &s1, const Socket &s2) +{ + return s1.handle() != s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 < s2 + */ +inline bool operator<(const Socket &s1, const Socket &s2) +{ + return s1.handle() < s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 > s2 + */ +inline bool operator>(const Socket &s1, const Socket &s2) +{ + return s1.handle() > s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 <= s2 + */ +inline bool operator<=(const Socket &s1, const Socket &s2) +{ + return s1.handle() <= s2.handle(); +} + +/** + * Compare two sockets. + * + * \param s1 the first socket + * \param s2 the second socket + * \return true if s1 >= s2 + */ +inline bool operator>=(const Socket &s1, const Socket &s2) +{ + return s1.handle() >= s2.handle(); +} + +/** + * \brief Clear TCP implementation. + * \ingroup net-module-tcp + * + * This is the basic TCP protocol that implements recv, send, connect and accept + * as wrappers of the usual C functions. + */ +class TcpSocket : public Socket { +public: + /** + * Inherited constructors. + */ + using Socket::Socket; + + /** + * Construct a TCP socket. + * + * \param domain the domain + * \param protocol the protocol + * \throw Error on errors + */ + inline TcpSocket(int domain, int protocol) + : Socket(domain, SOCK_STREAM, protocol) + { + } + + /** + * Get the type of the socket. + * + * \return the type + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Initiate connection. + * + * \param address the address + * \param length the address length + * \throw WouldBlockError if the socket is marked non-blocking and + * connection cannot be established immediately + * \throw Error on other errors + */ + void connect(const sockaddr *address, socklen_t length) + { + if (::connect(this->m_handle, address, length) == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EINPROGRESS) + throw WouldBlockError(); + else + throw Error(); +#endif + } + } + + /** + * Overloaded function. + * + * \param address the address + * \throw WouldBlockError if the socket is marked non-blocking and + * connection cannot be established immediately + * \throw Error on other errors + */ + void connect(const Address &address) + { + connect(address.get(), address.length()); + } + + /** + * Accept a new client. + * + * If there are no pending connection, an invalid socket is returned. + * + * \return the new socket + * \throw WouldBlockError if the socket is marked non-blocking and no + * connection are available + * \throw Error on other errors + */ + TcpSocket accept() + { + Handle handle = ::accept(this->m_handle, nullptr, 0); + + if (handle == Invalid) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EWOULDBLOCK || errno == EAGAIN) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return TcpSocket(handle); + } + + /** + * Receive some data. + * + * \param data the destination buffer + * \param length the buffer length + * \return the number of bytes received + */ + unsigned recv(void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = ::recv(this->m_handle, (Arg)data, max, 0); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbread); + } + + /** + * Send some data. + * + * \param data the data to send + * \param length the length + * \return the number of bytes sent + */ + unsigned send(const void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = ::send(this->m_handle, (ConstArg)data, max, 0); + + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == WSAEWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbsent); + } +}; + +/** + * \brief Clear UDP type. + * + * This class is the basic implementation of UDP sockets. + */ +class UdpSocket : public Socket { +public: + /** + * Inherited constructors. + */ + using Socket::Socket; + + /** + * Construct a TCP socket. + * + * \param domain the domain + * \param protocol the protocol + * \throw Error on errors + */ + inline UdpSocket(int domain, int protocol) + : Socket(domain, SOCK_DGRAM, protocol) + { + } + + /** + * Get the type of the socket. + * + * \return the type + */ + inline int type() const noexcept + { + return SOCK_DGRAM; + } + + /** + * Receive some data. + * + * \param data the data + * \param length the length + * \param address the source address + * \param addrlen the source address in/out length + * \return the number of bytes received + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + unsigned recvfrom(void *data, unsigned length, sockaddr *address, socklen_t *addrlen) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = ::recvfrom(this->m_handle, (Arg)data, max, 0, address, addrlen); + + if (nbread == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbread); + } + + /** + * Overloaded function. + * + * \param data the data + * \param length the length + * \param source the source information (optional) + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + inline unsigned recvfrom(void *data, unsigned length, Address *source = nullptr) + { + sockaddr_storage st; + socklen_t socklen = sizeof (sockaddr_storage); + + auto nr = recvfrom(data, length, reinterpret_cast<sockaddr *>(&st), &socklen); + + if (source) + *source = Address(reinterpret_cast<const sockaddr *>(&st), socklen); + + return nr; + } + + /** + * Send some data. + * + * \param data the data to send + * \param length the data length + * \param address the destination address + * \param addrlen the destination address length + * \return the number of bytes sent + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + unsigned sendto(const void *data, unsigned length, const sockaddr *address, socklen_t addrlen) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = ::sendto(this->m_handle, (ConstArg)data, max, 0, address, addrlen); + + if (nbsent == Failure) { +#if defined(_WIN32) + int error = WSAGetLastError(); + + if (error == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(error); +#else + if (errno == EAGAIN || errno == EWOULDBLOCK) + throw WouldBlockError(); + else + throw Error(); +#endif + } + + return static_cast<unsigned>(nbsent); + } + + /** + * Overloaded function + * + * \param data the data to send + * \param length the data length + * \param address the destination address + * \return the number of bytes sent + * \throw WouldBlockError if the socket is marked non-blocking and the + * operation would block + * \throw Error on other errors + */ + inline unsigned sendto(const void *data, unsigned length, const Address &address) + { + return sendto(data, length, address.get(), address.length()); + } +}; + +#if !defined(NET_NO_SSL) + +/** + * \brief Experimental TLS support. + * \ingroup net-module-tls + * \warning This class is highly experimental. + */ +class TlsSocket : public Socket { +public: + /** + * \brief SSL connection mode. + */ + enum Mode { + Server, //!< Use Server when you accept a socket server side, + Client //!< Use Client when you connect to a server. + }; + +private: + using Context = std::shared_ptr<SSL_CTX>; + using Ssl = std::unique_ptr<SSL, void (*)(SSL *)>; + + // Determine if we created a TlsSocket from a temporary or a lvalue. + bool m_mustclose{false}; + + Context m_context; + Ssl m_ssl{nullptr, nullptr}; + + inline std::string error() + { + BIO *bio = BIO_new(BIO_s_mem()); + char *buf = nullptr; + + ERR_print_errors(bio); + + std::size_t length = BIO_get_mem_data (bio, &buf); + std::string result(buf, length); + + BIO_free(bio); + + return result; + } + + template <typename Function> + void wrap(Function &&function) + { + auto ret = function(); + + if (ret <= 0) { + int no = SSL_get_error(m_ssl.get(), ret); + + switch (no) { + case SSL_ERROR_WANT_READ: + throw WantReadError(); + case SSL_ERROR_WANT_WRITE: + throw WantWriteError(); + default: + throw Error(error()); + } + } + } + + void create(Mode mode, const SSL_METHOD *method) + { +#if !defined(NET_NO_SSL_AUTO_INIT) + ssl::init(); +#endif + m_context = Context(SSL_CTX_new(method), SSL_CTX_free); + m_ssl = Ssl(SSL_new(m_context.get()), SSL_free); + + SSL_set_fd(m_ssl.get(), this->m_handle); + + if (mode == Server) + SSL_set_accept_state(m_ssl.get()); + else + SSL_set_connect_state(m_ssl.get()); + } + +public: + /** + * Create a socket around an existing one. + * + * The original socket is moved to this instance and must not be used + * anymore. + * + * \param sock the TCP socket + * \param mode the mode + * \param method the method + */ + TlsSocket(TcpSocket &&sock, Mode mode = Server, const SSL_METHOD *method = TLSv1_method()) + : Socket(std::move(sock)) + , m_mustclose(true) + { + create(mode, method); + } + + /** + * Wrap a socket around an existing one without taking ownership. + * + * The original socket must still exist until this TlsSocket is closed. + * + * \param sock the TCP socket + * \param mode the mode + * \param method the method + */ + TlsSocket(TcpSocket &sock, Mode mode = Server, const SSL_METHOD *method = TLSv1_method()) + : Socket(sock.handle()) + { + create(mode, method); + } + + /** + * Destroy the socket if owned. + */ + ~TlsSocket() + { + /** + * If the socket has been created from a rvalue this class owns the + * socket and will close it in the parent destructor. + * + * Otherwise, when created from a lvalue, mark this socket as invalid + * to avoid double close'ing it as two sockets points to the same + * descriptor. + */ + if (!m_mustclose) + m_handle = Invalid; + } + + /** + * Get the type of socket. + * + * \return the type + */ + inline int type() const noexcept + { + return SOCK_STREAM; + } + + /** + * Use the specified private key file. + * + * \param file the path to the private key + * \param type the type of file + */ + inline void setPrivateKey(std::string file, int type = SSL_FILETYPE_PEM) + { + if (SSL_use_PrivateKey_file(m_ssl.get(), file.c_str(), type) != 1) + throw Error(error()); + } + + /** + * Use the specified certificate file. + * + * \param file the path to the file + * \param type the type of file + */ + inline void setCertificate(std::string file, int type = SSL_FILETYPE_PEM) + { + if (SSL_use_certificate_file(m_ssl.get(), file.c_str(), type) != 1) + throw Error(error()); + } + + /** + * Do handshake, needed in some case when you have non blocking sockets. + */ + void handshake() + { + wrap([this] () -> int { + return SSL_do_handshake(m_ssl.get()); + }); + } + + /** + * Receive some data. + * + * \param data the destination buffer + * \param length the buffer length + * \return the number of bytes received + */ + unsigned recv(void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbread = 0; + + wrap([&] () -> int { + return nbread = SSL_read(m_ssl.get(), data, max); + }); + + return static_cast<unsigned>(nbread < 0 ? 0 : nbread); + } + + /** + * Send some data. + * + * \param data the data to send + * \param length the length + * \return the number of bytes sent + */ + unsigned send(const void *data, unsigned length) + { + int max = length > INT_MAX ? INT_MAX : static_cast<int>(length); + int nbsent = 0; + + wrap([&] () -> int { + return nbsent = SSL_write(m_ssl.get(), data, max); + }); + + return static_cast<unsigned>(nbsent < 0 ? 0 : nbsent); + } +}; + +#endif // !NET_NO_SSL + +/** + * \brief IPv4 functions. + */ +namespace ipv4 { + +/** + * Create an address to bind on any. + * + * \param port the port + * \return the address + */ +inline Address any(std::uint16_t port) +{ + sockaddr_in sin; + socklen_t length = sizeof (sockaddr_in); + + std::memset(&sin, 0, sizeof (sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_addr.s_addr = INADDR_ANY; + sin.sin_port = htons(port); + + return Address(reinterpret_cast<const sockaddr *>(&sin), length); +} + +/** + * Create an address from a IPv4 string. + * + * \param ip the ip address + * \param port the port + * \return the address + * \throw Error if inet_pton is unavailable + */ +inline Address pton(const std::string &ip, std::uint16_t port) +{ +#if defined(NET_HAVE_INET_PTON) +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + + sockaddr_in sin; + socklen_t length = sizeof (sockaddr_in); + + std::memset(&sin, 0, sizeof (sockaddr_in)); + sin.sin_family = AF_INET; + sin.sin_port = htons(port); + + if (inet_pton(AF_INET, ip.c_str(), &sin.sin_addr) <= 0) + throw Error(); + + return Address(reinterpret_cast<const sockaddr *>(&sin), length); +#else + (void)ip; + (void)port; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the underlying ip from the given address. + * + * \pre address.domain() == AF_INET + * \param address the IPv6 address + * \return the ip address + * \throw Error if inet_ntop is unavailable + */ +inline std::string ntop(const Address &address) +{ + assert(address.domain() == AF_INET); + +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + +#if defined(NET_HAVE_INET_NTOP) + char result[INET_ADDRSTRLEN + 1]; + + std::memset(result, 0, sizeof (result)); + + if (!inet_ntop(AF_INET, const_cast<in_addr *>(&address.as<sockaddr_in>().sin_addr), result, sizeof (result))) + throw Error(); + + return result; +#else + (void)address; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the port from the IPv4 address. + * + * \pre address.domain() == AF_INET4 + * \param address the address + * \return the port + */ +inline std::uint16_t port(const Address &address) noexcept +{ + assert(address.domain() == AF_INET); + + return ntohs(address.as<sockaddr_in>().sin_port); +} + +} // !ipv4 + +/** + * \brief IPv6 functions. + */ +namespace ipv6 { + +/** + * Create an address to bind on any. + * + * \param port the port + * \return the address + */ +inline Address any(std::uint16_t port) +{ + sockaddr_in6 sin6; + socklen_t length = sizeof (sockaddr_in6); + + std::memset(&sin6, 0, sizeof (sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_addr = in6addr_any; + sin6.sin6_port = htons(port); + + return Address(reinterpret_cast<const sockaddr *>(&sin6), length); +} + +/** + * Create an address from a IPv4 string. + * + * \param ip the ip address + * \param port the port + * \return the address + * \throw Error if inet_pton is unavailable + */ +inline Address pton(const std::string &ip, std::uint16_t port) +{ +#if defined(NET_HAVE_INET_PTON) +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + + sockaddr_in6 sin6; + socklen_t length = sizeof (sockaddr_in6); + + std::memset(&sin6, 0, sizeof (sockaddr_in6)); + sin6.sin6_family = AF_INET6; + sin6.sin6_port = htons(port); + + if (inet_pton(AF_INET6, ip.c_str(), &sin6.sin6_addr) <= 0) + throw Error(); + + return Address(reinterpret_cast<const sockaddr *>(&sin6), length); +#else + (void)ip; + (void)port; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the underlying ip from the given address. + * + * \pre address.domain() == AF_INET6 + * \param address the IPv6 address + * \return the ip address + * \throw Error if inet_ntop is unavailable + */ +inline std::string ntop(const Address &address) +{ + assert(address.domain() == AF_INET6); + +#if defined(NET_HAVE_INET_NTOP) +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + + char ret[INET6_ADDRSTRLEN]; + + std::memset(ret, 0, sizeof (ret)); + + if (!inet_ntop(AF_INET6, const_cast<in6_addr *>(&address.as<sockaddr_in6>().sin6_addr), ret, sizeof (ret))) + throw Error(); + + return ret; +#else + (void)address; + + throw Error(std::strerror(ENOSYS)); +#endif +} + +/** + * Get the port from the IPv6 address. + * + * \pre address.domain() == AF_INET6 + * \param address the address + * \return the port + */ +inline std::uint16_t port(const Address &address) noexcept +{ + assert(address.domain() == AF_INET6); + + return ntohs(address.as<sockaddr_in6>().sin6_port); +} + +} // !ipv6 + +#if !defined(_WIN32) + +/** + * \brief Unix domain functions. + */ +namespace local { + +/** + * Construct an address to a path. + * + * \pre !path.empty() + * \param path the path + * \param rm remove the file before (default: false) + */ +inline Address create(const std::string &path, bool rm = false) noexcept +{ + assert(!path.empty()); + + // Silently remove the file even if it fails. + if (rm) + remove(path.c_str()); + + sockaddr_un sun; + socklen_t length; + + std::memset(sun.sun_path, 0, sizeof (sun.sun_path)); + std::strncpy(sun.sun_path, path.c_str(), sizeof (sun.sun_path) - 1); + + sun.sun_family = AF_LOCAL; + +#if defined(NET_HAVE_SUN_LEN) + length = SUN_LEN(&sun); +#else + length = sizeof (sun); +#endif + + return Address(reinterpret_cast<const sockaddr *>(&sun), length); +} + +/** + * Get the path from the address. + * + * \pre address.domain() == AF_LOCAL + * \param address the local address + * \return the path to the socket file + */ +inline std::string path(const Address &address) +{ + assert(address.domain() == AF_LOCAL); + + return reinterpret_cast<const sockaddr_un *>(address.get())->sun_path; +} + +} // !local + +#endif // !_WIN32 + +/** + * \brief Predefined options. + */ +namespace option { + +/** + * \ingroup net-module-options + * \brief Set or get the blocking-mode for a socket. + * \warning On Windows, it's not possible to check if the socket is blocking or + * not. + */ +class SockBlockMode { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default the blocking mode is set to true. + * + * \param value set to true to make blocking sockets + */ + inline SockBlockMode(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + void set(Socket &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags; + + if ((flags = fcntl(sc.handle(), F_GETFL, 0)) < 0) + flags = 0; + + if (m_value) + flags &= ~(O_NONBLOCK); + else + flags |= O_NONBLOCK; + + if (fcntl(sc.handle(), F_SETFL, flags) < 0) + throw Error(); +#else + unsigned long flags = (m_value) ? 0 : 1; + + if (ioctlsocket(sc.handle(), FIONBIO, &flags) == Failure) + throw Error(); +#endif + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + bool get(Socket &sc) const + { +#if defined(O_NONBLOCK) && !defined(_WIN32) + int flags = fcntl(sc.handle(), F_GETFL, 0); + + if (flags < 0) + throw Error(); + + return !(flags & O_NONBLOCK); +#else + (void)sc; + + throw Error(std::strerror(ENOSYS)); +#endif + } +}; + +/** + * \ingroup net-module-options + * \brief Set or get the input buffer. + */ +class SockReceiveBuffer { +private: + int m_value; + +public: + /** + * Create the option. + * + * \param size the buffer size + */ + inline SockReceiveBuffer(int size = 2048) noexcept + : m_value(size) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + inline void set(Socket &sc) const + { + sc.set(SOL_SOCKET, SO_RCVBUF, m_value); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + inline int get(Socket &sc) const + { + return sc.get<int>(SOL_SOCKET, SO_RCVBUF); + } +}; + +/** + * \ingroup net-module-options + * \brief Reuse address, must be used before calling Socket::bind + */ +class SockReuseAddress { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default the option reuses the address. + * + * \param value set to true to reuse the address + */ + inline SockReuseAddress(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + inline void set(Socket &sc) const + { + sc.set(SOL_SOCKET, SO_REUSEADDR, m_value ? 1 : 0); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + inline bool get(Socket &sc) const + { + return sc.get<int>(SOL_SOCKET, SO_REUSEADDR) != 0; + } +}; + +/** + * \ingroup net-module-options + * \brief Set or get the output buffer. + */ +class SockSendBuffer { +private: + int m_value; + +public: + /** + * Create the option. + * + * \param size the buffer size + */ + inline SockSendBuffer(int size = 2048) noexcept + : m_value(size) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + inline void set(Socket &sc) const + { + sc.set(SOL_SOCKET, SO_SNDBUF, m_value); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + inline int get(Socket &sc) const + { + return sc.get<int>(SOL_SOCKET, SO_SNDBUF); + } +}; + +/** + * \ingroup net-module-options + * \brief Set this option if you want to disable nagle's algorithm. + */ +class TcpNoDelay { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default disable TCP delay. + * + * \param value set to true to disable TCP delay + */ + inline TcpNoDelay(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + inline void set(Socket &sc) const + { + sc.set(IPPROTO_TCP, TCP_NODELAY, m_value ? 1 : 0); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + inline bool get(Socket &sc) const + { + return sc.get<int>(IPPROTO_TCP, TCP_NODELAY) != 0; + } +}; + +/** + * \ingroup net-module-options + * \brief Control IPPROTO_IPV6/IPV6_V6ONLY + * + * Note: some systems may or not set this option by default so it's a good idea + * to set it in any case to either + * false or true if portability is a concern. + */ +class Ipv6Only { +private: + bool m_value; + +public: + /** + * Create the option. + * + * By default with want IPv6 only. + * + * \param value set to true to use IPv6 only + */ + inline Ipv6Only(bool value = true) noexcept + : m_value(value) + { + } + + /** + * Set the option. + * + * \param sc the socket + * \throw Error on errors + */ + inline void set(Socket &sc) const + { + sc.set(IPPROTO_IPV6, IPV6_V6ONLY, m_value ? 1 : 0); + } + + /** + * Get the option. + * + * \param sc the socket + * \return the value + * \throw Error on errors + */ + inline bool get(Socket &sc) const + { + return sc.get<int>(IPPROTO_IPV6, IPV6_V6ONLY) != 0; + } +}; + +} // !option + +/** + * \brief Result of polling + * + * Result of a select call, returns the first ready socket found with its + * flags. + */ +class ListenerStatus { +public: + Handle socket; //!< which socket is ready + Condition flags; //!< the flags +}; + +/** + * Table used in the socket listener to store which sockets have been + * set in which directions. + */ +using ListenerTable = std::unordered_map<Handle, Condition>; + +/** + * \brief Predefined backends for Listener. + */ +namespace backend { + +#if defined(NET_HAVE_EPOLL) + +/** + * \ingroup net-module-backends + * \brief Linux's epoll. + */ +class Epoll { +private: + int m_handle{-1}; + std::vector<epoll_event> m_events; + + Epoll(const Epoll &) = delete; + Epoll &operator=(const Epoll &) = delete; + + std::uint32_t toEpoll(Condition condition) const noexcept + { + std::uint32_t events = 0; + + if ((condition & Condition::Readable) == Condition::Readable) + events |= EPOLLIN; + if ((condition & Condition::Writable) == Condition::Writable) + events |= EPOLLOUT; + + return events; + } + + Condition toCondition(std::uint32_t events) const noexcept + { + Condition condition = Condition::None; + + if ((events & EPOLLIN) || (events & EPOLLHUP)) + condition |= Condition::Readable; + if (events & EPOLLOUT) + condition |= Condition::Writable; + + return condition; + } + + void update(Handle h, int op, int eflags) + { + epoll_event ev; + + std::memset(&ev, 0, sizeof (epoll_event)); + + ev.events = eflags; + ev.data.fd = h; + + if (epoll_ctl(m_handle, op, h, &ev) < 0) + throw Error(); + } + +public: + /** + * Create epoll. + * + * \throw Error on failures + */ + inline Epoll() + : m_handle(epoll_create1(0)) + { + if (m_handle < 0) + throw Error(); + } + + /** + * Move constructor. + * + * \param other the other backend + */ + inline Epoll(Epoll &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = -1; + } + + /** + * Close the kqueue descriptor. + */ + inline ~Epoll() + { + if (m_handle != -1) + close(m_handle); + } + + /** + * Get the backend name. + * + * \return kqueue + */ + inline std::string name() const noexcept + { + return "epoll"; + } + + /** + * For set and unset, we need to apply the whole flags required, so if the + * socket was set to Connection::Readable and user **adds** + * Connection::Writable, we must set both. + * + * \param table the listener table + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw Error on failures + */ + void set(const ListenerTable &table, Handle h, Condition condition, bool add) + { + if (add) { + update(h, EPOLL_CTL_ADD, toEpoll(condition)); + m_events.resize(m_events.size() + 1); + } else + update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) | condition)); + } + + /** + * Unset is a bit complicated case because Listener tells us which + * flag to remove but to update epoll descriptor we need to pass + * the effective flags that we want to be applied. + * + * So we put the same flags that are currently effective and remove the + * requested one. + * + * \param table the listener table + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw Error on failures + */ + void unset(const ListenerTable &table, Handle h, Condition condition, bool remove) + { + if (remove) { + update(h, EPOLL_CTL_DEL, 0); + m_events.resize(m_events.size() - 1); + } else + update(h, EPOLL_CTL_MOD, toEpoll(table.at(h) & ~(condition))); + } + + /** + * Wait for sockets to be ready. + * + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int ms) + { + int ret = epoll_wait(m_handle, m_events.data(), m_events.size(), ms); + std::vector<ListenerStatus> result; + + if (ret == 0) + throw TimeoutError(); + if (ret < 0) + throw Error(); + + for (int i = 0; i < ret; ++i) + result.push_back(ListenerStatus{m_events[i].data.fd, toCondition(m_events[i].events)}); + + return result; + } + + /** + * Move operator. + * + * \param other the other + * \return this + */ + inline Epoll &operator=(Epoll &&other) + { + m_handle = other.m_handle; + other.m_handle = -1; + + return *this; + } +}; + +#endif // !NET_HAVE_EPOLL + +#if defined(NET_HAVE_KQUEUE) + +/** + * \ingroup net-module-backends + * \brief Implements kqueue(2). + * + * This implementation is available on all BSD and Mac OS X. It is better than + * poll(2) because it's O(1), however it's a bit more memory consuming. + */ +class Kqueue { +private: + std::vector<struct kevent> m_result; + int m_handle; + + Kqueue(const Kqueue &) = delete; + Kqueue &operator=(const Kqueue &) = delete; + + void update(Handle h, int filter, int kflags) + { + struct kevent ev; + + EV_SET(&ev, h, filter, kflags, 0, 0, nullptr); + + if (kevent(m_handle, &ev, 1, nullptr, 0, nullptr) < 0) + throw Error(); + } + +public: + /** + * Create kqueue. + * + * \throw Error on failures + */ + inline Kqueue() + : m_handle(kqueue()) + { + if (m_handle < 0) + throw Error(); + } + + /** + * Move constructor. + * + * \param other the other backend + */ + inline Kqueue(Kqueue &&other) noexcept + : m_handle(other.m_handle) + { + other.m_handle = -1; + } + + /** + * Close the kqueue descriptor. + */ + inline ~Kqueue() + { + if (m_handle != -1) + close(m_handle); + } + + /** + * Get the backend name. + * + * \return kqueue + */ + inline std::string name() const noexcept + { + return "kqueue"; + } + + /** + * Set socket. + * + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw Error on failures + */ + void set(const ListenerTable &, Handle h, Condition condition, bool add) + { + if ((condition & Condition::Readable) == Condition::Readable) + update(h, EVFILT_READ, EV_ADD | EV_ENABLE); + if ((condition & Condition::Writable) == Condition::Writable) + update(h, EVFILT_WRITE, EV_ADD | EV_ENABLE); + if (add) + m_result.resize(m_result.size() + 1); + } + + /** + * Unset socket. + * + * \param h the handle + * \param condition the condition + * \param remove set to true if the socket is completely removed + * \throw Error on failures + */ + void unset(const ListenerTable &, Handle h, Condition condition, bool remove) + { + if ((condition & Condition::Readable) == Condition::Readable) + update(h, EVFILT_READ, EV_DELETE); + if ((condition & Condition::Writable) == Condition::Writable) + update(h, EVFILT_WRITE, EV_DELETE); + if (remove) + m_result.resize(m_result.size() - 1); + } + + /** + * Wait for sockets to be ready. + * + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int ms) + { + std::vector<ListenerStatus> sockets; + timespec ts = { 0, 0 }; + timespec *pts = (ms <= 0) ? nullptr : &ts; + + ts.tv_sec = ms / 1000; + ts.tv_nsec = (ms % 1000) * 1000000; + + int nevents = kevent(m_handle, nullptr, 0, &m_result[0], m_result.capacity(), pts); + + if (nevents == 0) + throw TimeoutError(); + if (nevents < 0) + throw Error(); + + for (int i = 0; i < nevents; ++i) { + sockets.push_back(ListenerStatus{ + static_cast<Handle>(m_result[i].ident), + m_result[i].filter == EVFILT_READ ? Condition::Readable : Condition::Writable + }); + } + + return sockets; + } + + /** + * Move operator. + * + * \param other the other + * \return this + */ + inline Kqueue &operator=(Kqueue &&other) noexcept + { + m_handle = other.m_handle; + other.m_handle = -1; + + return *this; + } +}; + +#endif // !NET_HAVE_KQUEUE + +#if defined(NET_HAVE_POLL) + +/** + * \ingroup net-module-backends + * \brief Implements poll(2). + * + * Poll is widely supported and is better than select(2). It is still not the + * best option as selecting the sockets is O(n). + */ +class Poll { +private: + std::vector<pollfd> m_fds; + + short toPoll(Condition condition) const noexcept + { + short result = 0; + + if ((condition & Condition::Readable) == Condition::Readable) + result |= POLLIN; + if ((condition & Condition::Writable) == Condition::Writable) + result |= POLLOUT; + + return result; + } + + Condition toCondition(short &event) const noexcept + { + Condition condition = Condition::None; + + /* + * Poll implementations mark the socket differently regarding the + * disconnection of a socket. + * + * At least, even if POLLHUP or POLLIN is set, recv() always return 0 so + * we mark the socket as readable. + */ + if ((event & POLLIN) || (event & POLLHUP)) + condition |= Condition::Readable; + if (event & POLLOUT) + condition |= Condition::Writable; + + // Reset event for safety. + event = 0; + + return condition; + } + +public: + /** + * Get the backend name. + * + * \return kqueue + */ + inline std::string name() const noexcept + { + return "poll"; + } + + /** + * Set socket. + * + * \param h the handle + * \param condition the condition + * \param add set to true if the socket is new to the backend + * \throw Error on failures + */ + void set(const ListenerTable &, Handle h, Condition condition, bool add) + { + if (add) + m_fds.push_back(pollfd{h, toPoll(condition), 0}); + else { + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { + return pfd.fd == h; + }); + + it->events |= toPoll(condition); + } + } + + /** + * Unset socket. + * + * \param h the handle + * \param condition the condition + * \param remove set to true if the socket is completely removed + * \throw Error on failures + */ + void unset(const ListenerTable &, Handle h, Condition condition, bool remove) + { + auto it = std::find_if(m_fds.begin(), m_fds.end(), [&] (const pollfd &pfd) { + return pfd.fd == h; + }); + + if (remove) + m_fds.erase(it); + else + it->events &= ~(toPoll(condition)); + } + + /** + * Wait for sockets to be ready. + * + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &, int ms) + { +#if defined(_WIN32) + auto result = WSAPoll(m_fds.data(), (ULONG)m_fds.size(), ms); +#else + auto result = poll(m_fds.data(), m_fds.size(), ms); +#endif + + if (result == 0) + throw TimeoutError(); + if (result < 0) + throw Error(); + + std::vector<ListenerStatus> sockets; + + for (auto &fd : m_fds) + if (fd.revents != 0) + sockets.push_back(ListenerStatus{fd.fd, toCondition(fd.revents)}); + + return sockets; + } +}; + +#endif // !NET_HAVE_POLL + +/** + * \ingroup net-module-backends + * \brief Implements select(2) + * + * This class is the fallback of any other method, it is not preferred at all + * for many reasons. + */ +class Select { +public: + /** + * Get the backend name. + * + * \return select + */ + inline std::string name() const + { + return "select"; + } + + /** + * No-op. + */ + inline void set(const ListenerTable &, Handle, Condition, bool) noexcept + { + } + + /** + * No-op. + */ + inline void unset(const ListenerTable &, Handle, Condition, bool) noexcept + { + } + + /** + * Wait for sockets to be ready. + * + * \param table the listener table + * \param ms the milliseconds timeout + * \return the sockets ready + * \throw Error on failures + */ + std::vector<ListenerStatus> wait(const ListenerTable &table, int ms) + { + timeval maxwait, *towait; + fd_set readset; + fd_set writeset; + + FD_ZERO(&readset); + FD_ZERO(&writeset); + + Handle max = 0; + + for (const auto &pair : table) { + if ((pair.second & Condition::Readable) == Condition::Readable) + FD_SET(pair.first, &readset); + if ((pair.second & Condition::Writable) == Condition::Writable) + FD_SET(pair.first, &writeset); + if (pair.first > max) + max = pair.first; + } + + maxwait.tv_sec = 0; + maxwait.tv_usec = ms * 1000; + + // Set to nullptr for infinite timeout. + towait = (ms < 0) ? nullptr : &maxwait; + + auto error = ::select(static_cast<int>(max + 1), &readset, &writeset, nullptr, towait); + + if (error == Failure) + throw Error(); + if (error == 0) + throw TimeoutError(); + + std::vector<ListenerStatus> sockets; + + for (const auto &pair : table) { + if (FD_ISSET(pair.first, &readset)) + sockets.push_back(ListenerStatus{pair.first, Condition::Readable}); + if (FD_ISSET(pair.first, &writeset)) + sockets.push_back(ListenerStatus{pair.first, Condition::Writable}); + } + + return sockets; + } +}; + +} // !backend + +/** + * \brief Synchronous multiplexing + * + * Convenient wrapper around the select() system call. + * + * This class is implemented using a bridge pattern to allow different uses + * of listener implementation. + * + * You should not reinstanciate a new Listener at each iteartion of your + * main loop as it can be extremely costly. Instead use the same listener that + * you can safely modify on the fly. + * + * Currently, poll, epoll, select and kqueue are available. + */ +template <typename Backend = backend :: NET_DEFAULT_BACKEND> +class Listener { +private: + Backend m_backend; + ListenerTable m_table; + +public: + /** + * Construct an empty listener. + */ + Listener() = default; + + /** + * Get the backend. + * + * \return the backend + */ + inline const Backend &backend() const noexcept + { + return m_backend; + } + + /** + * Get the non-modifiable table. + * + * \return the table + */ + inline const ListenerTable &table() const noexcept + { + return m_table; + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator begin() const noexcept + { + return m_table.begin(); + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator cbegin() const noexcept + { + return m_table.cbegin(); + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator end() const noexcept + { + return m_table.end(); + } + + /** + * Overloaded function. + * + * \return the iterator + */ + inline ListenerTable::const_iterator cend() const noexcept + { + return m_table.cend(); + } + + /** + * Add or update a socket to the listener. + * + * If the socket is already placed with the appropriate flags, the + * function is a no-op. + * + * If incorrect flags are passed, the function does nothing. + * + * \param sc the socket + * \param condition the condition (may be OR'ed) + * \throw Error if the backend failed to set + */ + void set(Handle sc, Condition condition) + { + // Invalid or useless flags. + if (condition == Condition::None || static_cast<int>(condition) > 0x3) + return; + + auto it = m_table.find(sc); + + // Do not update the table if the backend failed to add or update. + if (it == m_table.end()) { + m_backend.set(m_table, sc, condition, true); + m_table.emplace(sc, condition); + } else { + // Remove flag if already present. + if ((condition & Condition::Readable) == Condition::Readable && + (it->second & Condition::Readable) == Condition::Readable) + condition &= ~(Condition::Readable); + if ((condition & Condition::Writable) == Condition::Writable && + (it->second & Condition::Writable) == Condition::Writable) + condition &= ~(Condition::Writable); + + // Still need a call? + if (condition != Condition::None) { + m_backend.set(m_table, sc, condition, false); + it->second |= condition; + } + } + } + + /** + * Unset a socket from the listener, only the flags is removed + * unless the two flagss are requested. + * + * For example, if you added a socket for both reading and writing, + * unsetting the write flags will keep the socket for reading. + * + * \param sc the socket + * \param condition the condition (may be OR'ed) + * \see remove + */ + void unset(Handle sc, Condition condition) + { + auto it = m_table.find(sc); + + // Invalid or useless flags. + if (condition == Condition::None || static_cast<int>(condition) > 0x3 || it == m_table.end()) + return; + + // Like set, do not update if the socket is already at the appropriate state. + if ((condition & Condition::Readable) == Condition::Readable && + (it->second & Condition::Readable) != Condition::Readable) + condition &= ~(Condition::Readable); + if ((condition & Condition::Writable) == Condition::Writable && + (it->second & Condition::Writable) != Condition::Writable) + condition &= ~(Condition::Writable); + + if (condition != Condition::None) { + // Determine if it's a complete removal. + bool removal = ((it->second) & ~(condition)) == Condition::None; + + m_backend.unset(m_table, sc, condition, removal); + + if (removal) + m_table.erase(it); + else + it->second &= ~(condition); + } + } + + /** + * Remove completely the socket from the listener. + * + * It is a shorthand for unset(sc, Condition::Readable | + * Condition::Writable); + * + * \param sc the socket + */ + inline void remove(Handle sc) + { + unset(sc, Condition::Readable | Condition::Writable); + } + + /** + * Remove all sockets. + */ + inline void clear() + { + while (!m_table.empty()) + remove(m_table.begin()->first); + } + + /** + * Get the number of sockets in the listener. + * + * \return the number of sockets + */ + inline ListenerTable::size_type size() const noexcept + { + return m_table.size(); + } + + /** + * Select a socket. Waits for a specific amount of time specified as the + * duration. + * + * \param duration the duration + * \return the socket ready + */ + template <typename Rep, typename Ratio> + inline ListenerStatus wait(const std::chrono::duration<Rep, Ratio> &duration) + { + auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); + auto max = cvt.count() > INT_MAX ? INT_MAX : static_cast<int>(cvt.count()); + + return m_backend.wait(m_table, max)[0]; + } + + /** + * Overload with milliseconds. + * + * \param timeout the optional timeout in milliseconds + * \return the socket ready + */ + inline ListenerStatus wait(long long int timeout = -1) + { + return wait(std::chrono::milliseconds(timeout)); + } + + /** + * Select multiple sockets. + * + * \param duration the duration + * \return the socket ready + */ + template <typename Rep, typename Ratio> + inline std::vector<ListenerStatus> waitMultiple(const std::chrono::duration<Rep, Ratio> &duration) + { + auto cvt = std::chrono::duration_cast<std::chrono::milliseconds>(duration); + + return m_backend.wait(m_table, cvt.count()); + } + + /** + * Overload with milliseconds. + * + * \param timeout the optional timeout in milliseconds + * \return the socket ready + */ + inline std::vector<ListenerStatus> waitMultiple(int timeout = -1) + { + return waitMultiple(std::chrono::milliseconds(timeout)); + } +}; + +/** + * \ingroup net-module-resolv + * + * Resolve an hostname immediately. + * + * \param host the hostname + * \param service the service (e.g. http or port number) + * \param domain the domain (e.g. AF_INET) + * \param type the type (e.g. SOCK_STREAM) + * \return the address iterator + * \throw Error on failures + */ +inline AddressIterator resolve(const std::string &host, + const std::string &service, + int domain = AF_UNSPEC, + int type = 0) +{ +#if !defined(NET_NO_AUTO_INIT) + init(); +#endif + + struct addrinfo hints, *res, *p; + + std::memset(&hints, 0, sizeof (hints)); + hints.ai_family = domain; + hints.ai_socktype = type; + + int e = getaddrinfo(host.c_str(), service.c_str(), &hints, &res); + + if (e != 0) + throw Error(gai_strerror(e)); + + std::vector<Address> addresses; + + for (p = res; p != nullptr; p = p->ai_next) + addresses.push_back(Address(p->ai_addr, p->ai_addrlen)); + + return AddressIterator(addresses, 0); +} + +/** + * \ingroup net-module-resolv + * + * Resolve the first address. + * + * \param host the hostname + * \param service the service name + * \param domain the domain (e.g. AF_INET) + * \param type the type (e.g. SOCK_STREAM) + * \return the first generic address available + * \throw Error on failures + * \note do not use AF_UNSPEC and 0 as type for this function + */ +inline Address resolveOne(const std::string &host, const std::string &service, int domain, int type) +{ + AddressIterator it = resolve(host, service, domain, type); + AddressIterator end; + + if (it == end) + throw Error("no address available"); + + return *it; +} + +} // !net + +} // !irccd + +#endif // !IRCCD_NET_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/options.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,183 @@ +/* + * options.cpp -- parse Unix command line options + * + * Copyright (c) 2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cassert> + +#include "options.hpp" + +namespace irccd { + +namespace option { + +namespace { + +using Iterator = std::vector<std::string>::iterator; +using Args = std::vector<std::string>; + +inline bool isOption(const std::string &arg) noexcept +{ + return arg.size() >= 2 && arg[0] == '-'; +} + +inline bool isLongOption(const std::string &arg) noexcept +{ + assert(isOption(arg)); + + return arg.size() >= 3 && arg[1] == '-'; +} + +inline bool isShortSimple(const std::string &arg) noexcept +{ + assert(isOption(arg)); + assert(!isLongOption(arg)); + + return arg.size() == 2; +} + +void parseLongOption(Result &result, Args &args, Iterator &it, Iterator &end, const Options &definition) +{ + auto arg = *it++; + auto opt = definition.find(arg); + + if (opt == definition.end()) + throw InvalidOption{arg}; + + // Need argument? + if (opt->second) { + if (it == end || isOption(*it)) + throw MissingValue{arg}; + + result.insert(std::make_pair(arg, *it++)); + it = args.erase(args.begin(), it); + end = args.end(); + } else { + result.insert(std::make_pair(arg, "")); + it = args.erase(args.begin()); + end = args.end(); + } +} + +void parseShortOption(Result &result, Args &args, Iterator &it, Iterator &end, const Options &definition) +{ + if (isShortSimple(*it)) { + /* + * Here two cases: + * + * -v (no option) + * -c value + */ + auto arg = *it++; + auto opt = definition.find(arg); + + if (opt == definition.end()) + throw InvalidOption{arg}; + + // Need argument? + if (opt->second) { + if (it == end || isOption(*it)) + throw MissingValue{arg}; + + result.insert(std::make_pair(arg, *it++)); + it = args.erase(args.begin(), it); + end = args.end(); + } else { + result.insert(std::make_pair(arg, "")); + it = args.erase(args.begin()); + end = args.end(); + } + } else { + /* + * Here multiple scenarios: + * + * 1. -abc (-a -b -c if all are simple boolean arguments) + * 2. -vc foo.conf (-v -c foo.conf if -c is argument dependant) + * 3. -vcfoo.conf (-v -c foo.conf also) + */ + auto value = it->substr(1); + auto len = value.length(); + int toremove = 1; + + for (decltype(len) i = 0; i < len; ++i) { + auto arg = std::string{'-'} + value[i]; + auto opt = definition.find(arg); + + if (opt == definition.end()) + throw InvalidOption{arg}; + + if (opt->second) { + if (i == (len - 1)) { + // End of string, get the next argument (see 2.). + if (++it == end || isOption(*it)) + throw MissingValue{arg}; + + result.insert(std::make_pair(arg, *it)); + toremove += 1; + } else { + result.insert(std::make_pair(arg, value.substr(i + 1))); + i = len; + } + } else + result.insert(std::make_pair(arg, "")); + } + + it = args.erase(args.begin(), args.begin() + toremove); + end = args.end(); + } +} + +} // !namespace + +Result read(std::vector<std::string> &args, const Options &definition) +{ + Result result; + + auto it = args.begin(); + auto end = args.end(); + + while (it != end) { + if (!isOption(*it)) + break; + + if (isLongOption(*it)) + parseLongOption(result, args, it, end, definition); + else + parseShortOption(result, args, it, end, definition); + } + + return result; +} + +Result read(int &argc, char **&argv, const Options &definition) +{ + std::vector<std::string> args; + + for (int i = 0; i < argc; ++i) + args.push_back(argv[i]); + + auto before = args.size(); + auto result = read(args, definition); + + argc -= before - args.size(); + argv += before - args.size(); + + return result; +} + +} // !option + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/options.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,156 @@ +/* + * options.h -- parse Unix command line options + * + * Copyright (c) 2015 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef OPTIONS_HPP +#define OPTIONS_HPP + +/** + * \file options.hpp + * \brief Basic Unix options parser. + */ + +#include <exception> +#include <map> +#include <string> +#include <utility> +#include <vector> + +#include "sysconfig.hpp" + +namespace irccd { + +/** + * Namespace for options parsing. + */ +namespace option { + +/** + * \brief This exception is thrown when an invalid option has been found. + */ +class InvalidOption : public std::exception { +private: + std::string message; + +public: + /** + * The invalid option given. + */ + std::string argument; + + /** + * Construct the exception. + * + * \param arg the argument missing + */ + inline InvalidOption(std::string arg) + : argument(std::move(arg)) + { + message = std::string("invalid option: ") + argument; + } + + /** + * Get the error message. + * + * \return the error message + */ + const char *what() const noexcept override + { + return message.c_str(); + } +}; + +/** + * \brief This exception is thrown when an option requires a value and no value has been given. + */ +class MissingValue : public std::exception { +private: + std::string m_message; + std::string m_option; + +public: + /** + * Construct the exception. + * + * \param option the option that requires a value + */ + inline MissingValue(std::string option) + : m_option(std::move(option)) + { + m_message = std::string("missing argument for: ") + m_option; + } + + /** + * Get the options that requires a value. + * + * \return the option name + */ + inline const std::string &option() const noexcept + { + return m_option; + } + + /** + * Get the error message. + * + * \return the error message + */ + const char *what() const noexcept override + { + return m_message.c_str(); + } +}; + +/** + * Packed multimap of options. + */ +using Result = std::multimap<std::string, std::string>; + +/** + * Define the allowed options. + */ +using Options = std::map<std::string, bool>; + +/** + * Extract the command line options and return a result. + * + * \param args the arguments + * \param definition + * \warning the arguments vector is modified in place to remove parsed options + * \throw MissingValue + * \throw InvalidOption + */ +IRCCD_EXPORT Result read(std::vector<std::string> &args, const Options &definition); + +/** + * Overloaded function for usage with main() arguments. + * + * \param argc the number of arguments + * \param argv the argument vector + * \param definition + * \note don't forget to remove the first argv[0] argument + * \warning the argc and argv are modified in place to remove parsed options + * \throw MissingValue + * \throw InvalidOption + */ +IRCCD_EXPORT Result read(int &argc, char **&argv, const Options &definition); + +} // !option + +} // !irccd + +#endif // !OPTIONS_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/path.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,535 @@ +/* + * path.cpp -- special paths inside irccd + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <algorithm> +#include <cassert> +#include <sstream> +#include <stdexcept> + +#include "sysconfig.hpp" + +#if defined(IRCCD_SYSTEM_WINDOWS) +# include <windows.h> +# include <shlobj.h> +#else +# if defined(IRCCD_SYSTEM_LINUX) +# include <limits.h> +# include <unistd.h> +# include <cerrno> +# include <cstring> +# include <stdexcept> +# endif + +# if defined(IRCCD_SYSTEM_FREEBSD) +# include <sys/types.h> +# include <sys/sysctl.h> +# include <limits.h> + +# include <array> +# include <cerrno> +# include <cstring> +# include <stdexcept> +# endif + +# if defined(IRCCD_SYSTEM_MAC) +# include <cerrno> +# include <cstring> +# include <unistd.h> +# include <libproc.h> +# endif + +# include "xdg.hpp" +#endif + +#include "fs.hpp" +#include "path.hpp" +#include "system.hpp" +#include "util.hpp" + +namespace irccd { + +namespace path { + +namespace { + +/* + * Base program directory + * ------------------------------------------------------------------ + * + * This variable stores the program base directory. + * + * If it is empty, the program was not able to detect it (e.g. error, not + * supported). + */ + +std::string base{"."}; + +#if defined(IRCCD_SYSTEM_WINDOWS) + +std::string executablePath() +{ + std::string result; + std::size_t size = MAX_PATH; + + result.resize(size); + + if (!(size = GetModuleFileNameA(nullptr, &result[0], size))) + throw std::runtime_error("GetModuleFileName error"); + + result.resize(size); + + return result; +} + +#elif defined(IRCCD_SYSTEM_LINUX) + +std::string executablePath() +{ + std::string result; + + result.resize(2048); + + auto size = readlink("/proc/self/exe", &result[0], 2048); + + if (size < 0) + throw std::invalid_argument(std::strerror(errno)); + + result.resize(size); + + return result; +} + +#elif defined(IRCCD_SYSTEM_FREEBSD) + +std::string executablePath() +{ + std::array<int, 4> mib{ { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 } }; + std::string result; + std::size_t size = PATH_MAX + 1; + + result.resize(size); + + if (sysctl(mib.data(), 4, &result[0], &size, nullptr, 0) < 0) + throw std::runtime_error(std::strerror(errno)); + + result.resize(size); + + return result; +} + +#elif defined(IRCCD_SYSTEM_MAC) + +std::string executablePath() +{ + std::string result; + std::size_t size = PROC_PIDPATHINFO_MAXSIZE; + + result.resize(size); + + if ((size = proc_pidpath(getpid(), &result[0], size)) == 0) + throw std::runtime_error(std::strerror(errno)); + + result.resize(size); + + return result; +} + +#else + +/* + * TODO: add support for more systems here. + * + * - NetBSD + * - OpenBSD + */ + +std::string executablePath() +{ + return ""; +} + +#endif + +/* + * System paths + * ------------------------------------------------------------------ + * + * Compute system paths. + * + * Do not call any of these functions if irccd is relocatable and base is unset. + */ + +std::string systemConfig() +{ + return base + WITH_CONFDIR; +} + +std::string systemData() +{ + return base + WITH_DATADIR; +} + +std::string systemCache() +{ + return base + WITH_CACHEDIR; +} + +std::string systemPlugins() +{ + return base + WITH_PLUGINDIR; +} + +std::string systemNativePlugins() +{ + return base + WITH_NPLUGINDIR; +} + +/* + * User paths + * ------------------------------------------------------------------ + * + * Compute user paths. + */ + +/* + * userConfig + * --------------------------------------------------------- + * + * Get the path directory to the user configuration. Example: + * + * Unix: + * + * XDG_CONFIG_HOME/irccd + * HOME/.config/irccd + * + * Windows: + * + * CSIDL_LOCAL_APPDATA/irccd/config + */ +std::string userConfig() +{ + std::ostringstream oss; + +#if defined(IRCCD_SYSTEM_WINDOWS) + char path[MAX_PATH]; + + if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK) + oss << ""; + else { + oss << path; + oss << "\\irccd\\config\\"; + } +#else + try { + Xdg xdg; + + oss << xdg.configHome(); + oss << "/irccd/"; + } catch (const std::exception &) { + const char *home = getenv("HOME"); + + if (home != nullptr) + oss << home; + + oss << "/.config/irccd/"; + } +#endif + + return oss.str(); +} + +/* + * userData + * -------------------------------------------------------- + * + * Get the path to the data application. + * + * Unix: + * + * XDG_DATA_HOME/irccd + * HOME/.local/share/irccd + * + * Windows: + * + * CSIDL_LOCAL_APPDATA + */ +std::string userData() +{ + std::ostringstream oss; + +#if defined(IRCCD_SYSTEM_WINDOWS) + char path[MAX_PATH]; + + if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK) + oss << ""; + else { + oss << path; + oss << "\\irccd\\share"; + } +#else + try { + Xdg xdg; + + oss << xdg.dataHome(); + oss << "/irccd/"; + } catch (const std::exception &) { + const char *home = getenv("HOME"); + + if (home != nullptr) + oss << home; + + oss << "/.local/share/irccd/"; + } +#endif + + return oss.str(); +} + +/* + * userCache + * -------------------------------------------------------- + * + * Directory for cache files. + * + * Unix: + * + * XDG_CACHE_HOME/irccd + * HOME/.cache/irccd + * + * Windows: + * + * %TEMP% (e.g. C:\Users\<user>\AppData\Local\Temp) + */ +std::string userCache() +{ + std::ostringstream oss; + +#if defined(IRCCD_SYSTEM_WINDOWS) + char path[MAX_PATH + 1]; + + GetTempPathA(sizeof (path), path); + + oss << path << "\\irccd\\"; +#else + try { + Xdg xdg; + + oss << xdg.cacheHome(); + oss << "/irccd/"; + } catch (const std::exception &) { + const char *home = getenv("HOME"); + + if (home != nullptr) + oss << home; + + oss << "/.cache/irccd/"; + } +#endif + + return oss.str(); +} + +/* + * userPlugins + * -------------------------------------------------------- + * + * Path to the data + plugins. + */ +std::string userPlugins() +{ + return userData() + "/plugins/"; +} + +} // !namespace + +#if defined(IRCCD_SYSTEM_WINDOWS) +const char Separator(';'); +#else +const char Separator(':'); +#endif + +void setApplicationPath(const std::string &argv0) +{ + try { + base = executablePath(); + } catch (const std::exception &) { + /* + * If an exception is thrown, that means the operating system supports a + * function to get the executable path but it failed. + * + * TODO: show a waning + */ + } + + /* + * If we could not get the application path from the native function, check + * if argv[0] is an absolute path and use that from there. + * + * Otherwise, search from the PATH. + * + * In the worst case use current working directory. + */ + if (base.empty()) { + if (fs::isAbsolute(argv0)) + base = argv0; + else { + std::string name = fs::baseName(argv0); + + for (const auto &dir : util::split(sys::env("PATH"), std::string(1, Separator))) { + std::string path = dir + fs::separator() + name; + + if (fs::exists(path)) { + base = path; + break; + } + } + + // Not found in PATH? add dummy value. + if (base.empty()) + base = std::string(".") + fs::separator() + WITH_BINDIR + fs::separator() + "dummy"; + } + } + + // Find bin/<progname>. + auto pos = base.rfind(std::string(WITH_BINDIR) + fs::separator() + fs::baseName(base)); + + if (pos != std::string::npos) + base.erase(pos); + + // Add trailing / or \\ for convenience. + base = clean(base); + + assert(!base.empty()); +} + +std::string clean(std::string input) +{ + if (input.empty()) + return input; + + // First, remove any duplicates. + input.erase(std::unique(input.begin(), input.end(), [&] (char c1, char c2) { + return c1 == c2 && (c1 == '/' || c1 == '\\'); + }), input.end()); + + // Add a trailing / or \\. + char c = input[input.length() - 1]; + if (c != '/' && c != '\\') + input += fs::separator(); + + // Now converts all / to \\ for Windows and the opposite for Unix. +#if defined(IRCCD_SYSTEM_WINDOWS) + std::replace(input.begin(), input.end(), '/', '\\'); +#else + std::replace(input.begin(), input.end(), '\\', '/'); +#endif + + return input; +} + +std::string get(Path path, Owner owner) +{ + assert(path >= PathConfig && path <= PathNativePlugins); + assert(owner >= OwnerSystem && owner <= OwnerUser); + + std::string result; + + switch (owner) { + case OwnerSystem: + switch (path) { + case PathCache: + result = clean(systemCache()); + break; + case PathConfig: + result = clean(systemConfig()); + break; + case PathData: + result = clean(systemData()); + break; + case PathPlugins: + result = clean(systemPlugins()); + break; + case PathNativePlugins: + result = clean(systemNativePlugins()); + break; + default: + break; + } + case OwnerUser: + switch (path) { + case PathCache: + result = clean(userCache()); + break; + case PathConfig: + result = clean(userConfig()); + break; + case PathData: + result = clean(userData()); + break; + case PathNativePlugins: + case PathPlugins: + result = clean(userPlugins()); + break; + default: + break; + } + default: + break; + } + + return result; +} + +std::vector<std::string> list(Path path) +{ + assert(path >= PathConfig && path <= PathNativePlugins); + + std::vector<std::string> list; + + switch (path) { + case PathCache: + list.push_back(clean(userCache())); + list.push_back(clean(systemCache())); + break; + case PathConfig: + list.push_back(clean(userConfig())); + list.push_back(clean(systemConfig())); + break; + case PathData: + list.push_back(clean(userData())); + list.push_back(clean(systemData())); + break; + case PathPlugins: + list.push_back(clean(fs::cwd())); + list.push_back(clean(userPlugins())); + list.push_back(clean(systemPlugins())); + break; + case PathNativePlugins: + list.push_back(clean(fs::cwd())); + list.push_back(clean(systemNativePlugins())); + break; + default: + break; + } + + return list; +} + +} // !path + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/path.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,111 @@ +/* + * path.hpp -- special paths inside irccd + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_PATH_HPP +#define IRCCD_PATH_HPP + +/** + * \file path.hpp + * \brief Path management. + */ + +#include <string> +#include <vector> + +#include "sysconfig.hpp" + +namespace irccd { + +/** + * \brief Namespace for paths. + */ +namespace path { + +/** + * brief PATH separator, either : or ;. + */ +extern const char Separator; + +/** + * \enum Path + * \brief Which special path to get + */ +enum Path { + PathConfig, //!< Configuration files + PathData, //!< Data directory + PathCache, //!< Cache files + PathPlugins, //!< Path to the plugins + PathNativePlugins //!< Path to native plugins +}; + +/** + * \enum Owner + * \brief For paths, get the installation path or the user ones + */ +enum Owner { + OwnerSystem, //!< System wide + OwnerUser //!< User +}; + +/** + * This function must be called before at the beginning of the main. + * + * It use system dependant program path lookup if available and fallbacks to the + * path given as argument if any failure was encoutered. + * + * \param argv0 the path to the executable (argv[0]) + */ +IRCCD_EXPORT void setApplicationPath(const std::string &argv0); + +/** + * Clean a path by removing any extra / or \ and add a trailing one. + * + * \param path the path + * \return the updated path + */ +IRCCD_EXPORT std::string clean(std::string path); + +/** + * Generic function for path retrievement. + * + * The path is always terminated by a trailing / or \\. + * + * \pre setApplicationPath must have been called + * \param path the type of path + * \param owner system or user wide + * \return the path + */ +IRCCD_EXPORT std::string get(Path path, Owner owner); + +/** + * Generic function for multiple paths. + * + * This function will add more directories than pathSystem*() and pathUser*() + * functions. + * + * \pre setApplicationPath must have been called + * \param path the type of path + * \return the list of preferred directories in order + */ +IRCCD_EXPORT std::vector<std::string> list(Path path); + +} // !path + +} // !irccd + +#endif // !IRCCD_PATH_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/signals.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,172 @@ +/* + * signals.h -- synchronous observer mechanism + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_SIGNALS_H +#define IRCCD_SIGNALS_H + +#include <functional> +#include <stack> +#include <unordered_map> +#include <vector> + +/** + * \file signals.hpp + * \brief Similar Qt signal subsystem for irccd + */ + +namespace irccd { + +/** + * \class SignalConnection + * \brief Stores the reference to the callable + * + * This class can be stored to remove a registered function from a Signal, be careful to not mix connections between different signals as + * they are just referenced by ids. + */ +class SignalConnection { +private: + unsigned m_id; + +public: + /** + * Create a signal connection. + * + * \param id the id + */ + inline SignalConnection(unsigned id) noexcept + : m_id(id) + { + } + + /** + * Get the reference object. + * + * \return the id + */ + inline unsigned id() const noexcept + { + return m_id; + } +}; + +/** + * \class Signal + * \brief Stores and call registered functions + * + * This class is intended to be use as a public field in the desired object. + * + * The user just have to call one of connect(), disconnect() or the call + * operator to use this class. + * + * It stores the callable as std::function so type-erasure is complete. + * + * The user is responsible of taking care that the object is still alive + * in case that the function takes a reference to the object. + */ +template <typename... Args> +class Signal { +private: + using Function = std::function<void (Args...)>; + using FunctionMap = std::unordered_map<unsigned, Function>; + using Stack = std::stack<unsigned>; + + FunctionMap m_functions; + Stack m_stack; + unsigned m_max{0}; + +public: + /** + * Register a new function to the signal. + * + * \param function the function + * \return the connection in case you want to remove it + */ + inline SignalConnection connect(Function function) noexcept + { + unsigned id; + + if (!m_stack.empty()) { + id = m_stack.top(); + m_stack.pop(); + } else + id = m_max ++; + + m_functions.emplace(id, std::move(function)); + + return SignalConnection{id}; + } + + /** + * Disconnect a connection. + * + * \param connection the connection + * \warning Be sure that the connection belongs to that signal + */ + inline void disconnect(const SignalConnection &connection) noexcept + { + auto value = m_functions.find(connection.id()); + + if (value != m_functions.end()) { + m_functions.erase(connection.id()); + m_stack.push(connection.id()); + } + } + + /** + * Remove all registered functions. + */ + inline void clear() + { + m_functions.clear(); + m_max = 0; + + while (!m_stack.empty()) + m_stack.pop(); + } + + /** + * Call every functions. + * + * \param args the arguments to pass to the signal + */ + void operator()(Args... args) const + { + /* + * Make a copy of the ids before iterating because the callbacks may eventually remove or modify the list. + */ + std::vector<unsigned> ids; + + for (auto &pair : m_functions) + ids.push_back(pair.first); + + /* + * Now iterate while checking if the next id is still available, however if any new signals were added while iterating, they + * will not be called immediately. + */ + for (unsigned i : ids) { + auto it = m_functions.find(i); + + if (it != m_functions.end()) + it->second(args...); + } + } +}; + +} // !irccd + +#endif // !IRCCD_SIGNALS_H
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/system.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,284 @@ +/* + * system.cpp -- platform dependent functions for system inspection + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <cstdlib> +#include <ctime> +#include <stdexcept> + +#include "sysconfig.hpp" + +#if defined(HAVE_SETPROGNAME) +# include <cstdlib> +#endif + +#if defined(IRCCD_SYSTEM_WINDOWS) +# include <sys/types.h> +# include <sys/timeb.h> +# include <windows.h> +# include <shlobj.h> +#else // All non Windows +#if defined(IRCCD_SYSTEM_MAC) +# include <sys/sysctl.h> +#endif + +#if defined(IRCCD_SYSTEM_LINUX) +# include <sys/sysinfo.h> +#endif + +# include <sys/utsname.h> +# include <sys/time.h> +# include <sys/types.h> +# include <unistd.h> + +# include <cerrno> +# include <cstring> +# include <stdexcept> +# include <ctime> + +#endif + +// For sys::setGid. +#if defined(HAVE_SETGID) +# include <sys/types.h> +# include <unistd.h> +# include <grp.h> +#endif + +// For sys::setUid. +#if defined(HAVE_SETGID) +# include <sys/types.h> +# include <unistd.h> +# include <pwd.h> +#endif + +#include "fs.hpp" +#include "logger.hpp" +#include "system.hpp" +#include "util.hpp" + +namespace irccd { + +namespace sys { + +namespace { + +/* + * setHelper + * ------------------------------------------------------------------ + * + * This is an helper for setting the uid or gid. It accepts both numeric and + * string uid and gid. + * + * If a name is specified as uid/group, the lookup function will be called and + * must be getpwname or getgrname. Then, to get the id from the returned + * structure (struct passwd, struct group), the getter function will return + * either pw_uid or gr_gid. + * + * Finally, when the id is resolved, the setter function (setuid, setgid) will + * be called. + * + * - typeName the type of id (uid or gid) + * - value the value (numeric or name) + * - lookup the lookup function to resolve the name (getpwnam or getgrnam) + * - setter the function to apply the id (setuid or setgid) + * - getter the function to get the id from the informal structure + */ +template <typename IntType, typename LookupFunc, typename SetterFunc, typename FieldGetter> +void setHelper(const std::string &typeName, const std::string &value, LookupFunc lookup, SetterFunc setter, FieldGetter getter) +{ + IntType id; + + if (util::isNumber(value)) + id = std::stoi(value); + else { + auto info = lookup(value.c_str()); + + if (info == nullptr) { + log::warning() << "irccd: invalid " << typeName << ": " << std::strerror(errno) << std::endl; + return; + } else { + id = getter(info); + log::debug() << "irccd: " << typeName << " " << value << " resolved to: " << id << std::endl; + } + } + + if (setter(id) < 0) + log::warning() << "irccd: could not set " << typeName << ": " << std::strerror(errno) << std::endl; + else + log::info() << "irccd: setting " << typeName << " to " << value << std::endl; +} + +/* + * XXX: the setprogname() function keeps a pointer without copying it so when + * main's argv is modified, we're not using the same name so create our own + * copy. + */ + +std::string programNameCopy; + +} // !namespace + +void setProgramName(std::string name) noexcept +{ + programNameCopy = std::move(name); + +#if defined(HAVE_SETPROGNAME) + setprogname(programNameCopy.c_str()); +#endif +} + +const std::string &programName() noexcept +{ + return programNameCopy; +} + +std::string name() +{ +#if defined(IRCCD_SYSTEM_LINUX) + return "Linux"; +#elif defined(IRCCD_SYSTEM_WINDOWS) + return "Windows"; +#elif defined(IRCCD_SYSTEM_FREEBSD) + return "FreeBSD"; +#elif defined(IRCCD_SYSTEM_OPENBSD) + return "OpenBSD"; +#elif defined(IRCCD_SYSTEM_NETBSD) + return "NetBSD"; +#elif defined(IRCCD_SYSTEM_MAC) + return "Mac"; +#else + return "Unknown"; +#endif +} + +std::string version() +{ +#if defined(IRCCD_SYSTEM_WINDOWS) + auto version = GetVersion(); + auto major = (DWORD)(LOBYTE(LOWORD(version))); + auto minor = (DWORD)(HIBYTE(LOWORD(version))); + + return std::to_string(major) + "." + std::to_string(minor); +#else + struct utsname uts; + + if (uname(&uts) < 0) + throw std::runtime_error(std::strerror(errno)); + + return std::string(uts.release); +#endif +} + +uint64_t uptime() +{ +#if defined(IRCCD_SYSTEM_WINDOWS) + return ::GetTickCount64() / 1000; +#elif defined(IRCCD_SYSTEM_LINUX) + struct sysinfo info; + + if (sysinfo(&info) < 0) + throw std::runtime_error(std::strerror(errno)); + + return info.uptime; +#elif defined(IRCCD_SYSTEM_MAC) + struct timeval boottime; + size_t length = sizeof (boottime); + int mib[2] = { CTL_KERN, KERN_BOOTTIME }; + + if (sysctl(mib, 2, &boottime, &length, nullptr, 0) < 0) + throw std::runtime_error(std::strerror(errno)); + + time_t bsec = boottime.tv_sec, csec = time(nullptr); + + return difftime(csec, bsec); +#else + struct timespec ts; + + if (clock_gettime(CLOCK_UPTIME, &ts) < 0) + throw std::runtime_error(std::strerror(errno)); + + return ts.tv_sec; +#endif +} + +uint64_t ticks() +{ +#if defined(IRCCD_SYSTEM_WINDOWS) + _timeb tp; + + _ftime(&tp); + + return tp.time * 1000LL + tp.millitm; +#else + struct timeval tp; + + gettimeofday(&tp, NULL); + + return tp.tv_sec * 1000LL + tp.tv_usec / 1000; +#endif +} + +std::string home() +{ +#if defined(IRCCD_SYSTEM_WINDOWS) + char path[MAX_PATH]; + + if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path) != S_OK) + return ""; + + return std::string(path); +#else + return env("HOME"); +#endif +} + +std::string env(const std::string &var) +{ + auto value = std::getenv(var.c_str()); + + if (value == nullptr) + return ""; + + return value; +} + +#if defined(HAVE_SETUID) + +void setUid(const std::string &value) +{ + setHelper<uid_t>("uid", value, &getpwnam, &setuid, [] (const struct passwd *pw) { + return pw->pw_uid; + }); +} + +#endif + +#if defined(HAVE_SETGID) + +void setGid(const std::string &value) +{ + setHelper<gid_t>("gid", value, &getgrnam, &setgid, [] (const struct group *gr) { + return gr->gr_gid; + }); +} + +#endif + +} // !sys + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/system.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,122 @@ +/* + * system.hpp -- platform dependent functions for system inspection + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_SYSTEM_HPP +#define IRCCD_SYSTEM_HPP + +/** + * \file system.hpp + * \brief System dependant functions + */ + +#include <cstdint> +#include <string> + +#include "sysconfig.hpp" + +namespace irccd { + +/** + * \brief Namespace for system functions. + */ +namespace sys { + +/** + * Set the program name, needed for some functions or some systems. + * + * \param name the program name + */ +IRCCD_EXPORT void setProgramName(std::string name) noexcept; + +/** + * Get the program name. + * + * \return the program name + */ +IRCCD_EXPORT const std::string &programName() noexcept; + +/** + * Get the system name. + * + * \return the name + */ +IRCCD_EXPORT std::string name(); + +/** + * Get the system version. + * + * \return the version + */ +IRCCD_EXPORT std::string version(); + +/** + * Get the number of seconds elapsed since the boottime. + * + * \return the number of seconds + */ +IRCCD_EXPORT uint64_t uptime(); + +/** + * Get the milliseconds elapsed since the application + * startup. + * + * \return the milliseconds + */ +IRCCD_EXPORT uint64_t ticks(); + +/** + * Get an environment variable. + * + * \return the value or empty string + */ +IRCCD_EXPORT std::string env(const std::string &var); + +/** + * Get home directory usually /home/foo + * + * \return the home directory + */ +IRCCD_EXPORT std::string home(); + +#if defined(HAVE_SETUID) + +/** + * Set the effective uid by name or numeric value. + * + * \param value the value + */ +IRCCD_EXPORT void setUid(const std::string &value); + +#endif + +#if defined(HAVE_SETGID) + +/** + * Set the effective gid by name or numeric value. + * + * \param value the value + */ +IRCCD_EXPORT void setGid(const std::string &value); + +#endif + +} // !sys + +} // !irccd + +#endif // !IRCCD_SYSTEM_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/util.cpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,421 @@ +/* + * util.cpp -- some utilities + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "sysconfig.hpp" + +#include <algorithm> +#include <cassert> +#include <cctype> +#include <cstdlib> +#include <ctime> +#include <iomanip> +#include <sstream> +#include <stdexcept> + +#if defined(HAVE_POPEN) +#include <array> +#include <cerrno> +#include <cstring> +#include <functional> +#include <memory> +#endif + +#include "util.hpp" + +using namespace std::string_literals; + +namespace irccd { + +namespace util { + +namespace { + +const std::unordered_map<std::string, int> colorTable{ + { "white", 0 }, + { "black", 1 }, + { "blue", 2 }, + { "green", 3 }, + { "red", 4 }, + { "brown", 5 }, + { "purple", 6 }, + { "orange", 7 }, + { "yellow", 8 }, + { "lightgreen", 9 }, + { "cyan", 10 }, + { "lightcyan", 11 }, + { "lightblue", 12 }, + { "pink", 13 }, + { "grey", 14 }, + { "lightgrey", 15 } +}; + +const std::unordered_map<std::string, char> attributesTable{ + { "bold", '\x02' }, + { "italic", '\x09' }, + { "strike", '\x13' }, + { "reset", '\x0f' }, + { "underline", '\x15' }, + { "underline2", '\x1f' }, + { "reverse", '\x16' } +}; + +inline bool isReserved(char token) noexcept +{ + return token == '#' || token == '@' || token == '$' || token == '!'; +} + +std::string substituteDate(const std::string &text, const Substitution ¶ms) +{ + std::ostringstream oss; + +#if defined(HAVE_STD_PUT_TIME) + oss << std::put_time(std::localtime(¶ms.time), text.c_str()); +#else + /* + * Quick and dirty hack because GCC does not have this function. + */ + char buffer[4096]; + + std::strftime(buffer, sizeof (buffer) - 1, text.c_str(), std::localtime(¶ms.time)); + + oss << buffer; +#endif + + return oss.str(); +} + +std::string substituteKeywords(const std::string &content, const Substitution ¶ms) +{ + auto value = params.keywords.find(content); + + if (value != params.keywords.end()) + return value->second; + + return ""; +} + +std::string substituteEnv(const std::string &content) +{ + auto value = std::getenv(content.c_str()); + + if (value != nullptr) + return value; + + return ""; +} + +std::string substituteAttributes(const std::string &content) +{ + std::stringstream oss; + std::vector<std::string> list = split(content, ","); + + // @{} means reset. + if (list.empty()) + return std::string(1, attributesTable.at("reset")); + + // Remove useless spaces. + std::transform(list.begin(), list.end(), list.begin(), strip); + + /* + * 0: foreground + * 1: background + * 2-n: attributes + */ + auto foreground = list[0]; + if (!foreground.empty() || list.size() >= 2) { + // Color sequence. + oss << '\x03'; + + // Foreground. + auto it = colorTable.find(foreground); + if (it != colorTable.end()) + oss << it->second; + + // Background. + if (list.size() >= 2 && (it = colorTable.find(list[1])) != colorTable.end()) + oss << "," << it->second; + + // Attributes. + for (std::size_t i = 2; i < list.size(); ++i) { + auto attribute = attributesTable.find(list[i]); + + if (attribute != attributesTable.end()) + oss << attribute->second; + } + } + + return oss.str(); +} + +std::string substituteShell(const std::string &command) +{ +#if defined(HAVE_POPEN) + std::unique_ptr<FILE, std::function<int (FILE *)>> fp(popen(command.c_str(), "r"), pclose); + + if (fp == nullptr) + throw std::runtime_error(std::strerror(errno)); + + std::string result; + std::array<char, 128> buffer; + std::size_t n; + + while ((n = std::fread(buffer.data(), 1, 128, fp.get())) > 0) + result.append(buffer.data(), n); + if (std::ferror(fp.get())) + throw std::runtime_error(std::strerror(errno)); + + // Erase final '\n'. + auto it = result.find('\n'); + if (it != std::string::npos) + result.erase(it); + + return result; +#else + throw std::runtime_error("shell template not available"); +#endif +} + +std::string substitute(std::string::const_iterator &it, std::string::const_iterator &end, char token, const Substitution ¶ms) +{ + assert(isReserved(token)); + + std::string content, value; + + if (it == end) + return ""; + + while (it != end && *it != '}') + content += *it++; + + if (it == end || *it != '}') + throw std::invalid_argument("unclosed "s + token + " construct"s); + + it++; + + // Create default original value if flag is disabled. + value = std::string(1, token) + "{"s + content + "}"s; + + switch (token) { + case '#': + if (params.flags & Substitution::Keywords) + value = substituteKeywords(content, params); + break; + case '$': + if (params.flags & Substitution::Env) + value = substituteEnv(content); + break; + case '@': + if (params.flags & Substitution::IrcAttrs) + value = substituteAttributes(content); + break; + case '!': + if (params.flags & Substitution::Shell) + value = substituteShell(content); + break; + default: + break; + } + + return value; +} + +} // !namespace + +std::string format(std::string text, const Substitution ¶ms) +{ + /* + * Change the date format before anything else to avoid interpolation with + * keywords and user input. + */ + if (params.flags & Substitution::Date) + text = substituteDate(text, params); + + std::ostringstream oss; + + for (auto it = text.cbegin(), end = text.cend(); it != end; ) { + auto token = *it; + + /* Is the current character a reserved token or not? */ + if (!isReserved(token)) { + oss << *it++; + continue; + } + + /* The token was at the end, just write it and return now. */ + if (++it == end) { + oss << token; + continue; + } + + /* The token is declaring a template variable, substitute it. */ + if (*it == '{') { + oss << substitute(++it, end, token, params); + continue; + } + + /* + * If the next token is different from the previous one, just let the + * next iteration parse the string because we can have the following + * constructs. + * + * "@#{var}" -> "@value" + */ + if (*it != token) { + oss << token; + continue; + } + + /* + * Write the token only if it's not a variable because at this step we + * may have the following constructs. + * + * "##" -> "##" + * "##hello" -> "##hello" + * "##{hello}" -> "#{hello}" + */ + if (++it == end) + oss << token << token; + else if (*it == '{') + oss << token; + } + + return oss.str(); +} + +std::string strip(std::string str) +{ + auto test = [] (char c) { return !std::isspace(c); }; + + str.erase(str.begin(), std::find_if(str.begin(), str.end(), test)); + str.erase(std::find_if(str.rbegin(), str.rend(), test).base(), str.end()); + + return str; +} + +std::vector<std::string> split(const std::string &list, const std::string &delimiters, int max) +{ + std::vector<std::string> result; + std::size_t next = -1, current; + int count = 1; + bool finished = false; + + if (list.empty()) + return result; + + do { + std::string val; + + current = next + 1; + next = list.find_first_of(delimiters, current); + + // split max, get until the end. + if (max >= 0 && count++ >= max) { + val = list.substr(current, std::string::npos); + finished = true; + } else { + val = list.substr(current, next - current); + finished = next == std::string::npos; + } + + result.push_back(val); + } while (!finished); + + return result; +} + +MessagePair parseMessage(std::string message, const std::string &cc, const std::string &name) +{ + std::string result = message; + bool iscommand = false; + + // handle special commands "!<plugin> command" + if (cc.length() > 0) { + auto pos = result.find_first_of(" \t"); + auto fullcommand = cc + name; + + /* + * If the message that comes is "!foo" without spaces we + * compare the command char + the plugin name. If there + * is a space, we check until we find a space, if not + * typing "!foo123123" will trigger foo plugin. + */ + if (pos == std::string::npos) + iscommand = result == fullcommand; + else + iscommand = result.length() >= fullcommand.length() && result.compare(0, pos, fullcommand) == 0; + + if (iscommand) { + /* + * If no space is found we just set the message to "" otherwise + * the plugin name will be passed through onCommand + */ + if (pos == std::string::npos) + result = ""; + else + result = message.substr(pos + 1); + } + } + + return MessagePair(result, ((iscommand) ? MessageType::Command : MessageType::Message)); +} + +bool isBoolean(const std::string &value) noexcept +{ + return value == "1" || value == "YES" || value == "TRUE" || value == "ON"; +} + +bool isInt(const std::string &str, int base) noexcept +{ + if (str.empty()) + return false; + + char *ptr; + + std::strtol(str.c_str(), &ptr, base); + + return *ptr == 0; +} + +bool isReal(const std::string &str) noexcept +{ + if (str.empty()) + return false; + + char *ptr; + + std::strtod(str.c_str(), &ptr); + + return *ptr == 0; +} + +std::string nextNetwork(std::string &input) +{ + std::string result; + std::string::size_type pos = input.find("\r\n\r\n"); + + if ((pos = input.find("\r\n\r\n")) != std::string::npos) { + result = input.substr(0, pos); + input.erase(input.begin(), input.begin() + pos + 4); + } + + return result; +} + +} // util + +} // !irccd
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/util.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,694 @@ +/* + * util.hpp -- some utilities + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef IRCCD_UTIL_HPP +#define IRCCD_UTIL_HPP + +/** + * \file util.hpp + * \brief Utilities. + */ + +#include <ctime> +#include <initializer_list> +#include <limits> +#include <regex> +#include <sstream> +#include <stdexcept> +#include <string> +#include <type_traits> +#include <unordered_map> +#include <vector> + +#include <format.h> +#include <json.hpp> + +#include "net.hpp" +#include "sysconfig.hpp" + +namespace irccd { + +/** + * \brief Namespace for utilities. + */ +namespace util { + +/** + * \enum MessageType + * \brief Describe which type of message has been received + * + * On channels and queries, you may have a special command or a standard message + * depending on the beginning of the message. + * + * Example: `!reminder help' may invoke the command event if a plugin reminder + * exists. + */ +enum class MessageType { + Command, //!< special command + Message //!< standard message +}; + +/** + * \brief Combine the type of message and its content. + */ +using MessagePair = std::pair<std::string, MessageType>; + +/** + * \class Substitution + * \brief Used for format() function. + */ +class Substitution { +public: + /** + * \brief Disable or enable some features. + */ + enum Flags { + Date = (1 << 0), //!< date templates + Keywords = (1 << 1), //!< keywords + Env = (1 << 2), //!< environment variables + Shell = (1 << 3), //!< command line command + IrcAttrs = (1 << 4) //!< IRC escape codes + }; + + /** + * Flags for selecting templates. + */ + std::uint8_t flags{Date | Keywords | Env | IrcAttrs}; + + /** + * Fill that field if you want a date. + */ + std::time_t time{std::time(nullptr)}; + + /** + * Fill that map if you want to replace keywords. + */ + std::unordered_map<std::string, std::string> keywords; +}; + +/** + * Format a string and update all templates. + * + * ## Syntax + * + * The syntax is <strong>?{}</strong> where <strong>?</strong> is replaced by + * one of the token defined below. Braces are mandatory and cannot be ommited. + * + * To write a literal template construct, prepend the token twice. + * + * ## Availables templates + * + * The following templates are available: + * + * - <strong>\#{name}</strong>: name will be substituted from the keywords in + * params, + * - <strong>\${name}</strong>: name will be substituted from the environment + * variable, + * - <strong>\@{attributes}</strong>: the attributes will be substituted to IRC + * colors (see below), + * - <strong>%</strong>, any format accepted by strftime(3). + * + * ## Attributes + * + * The attribute format is composed of three parts, foreground, background and + * modifiers, each separated by a comma. + * + * **Note:** you cannot omit parameters, to specify the background, you must + * specify the foreground. + * + * ## Examples + * + * ### Valid constructs + * + * - <strong>\#{target}, welcome</strong>: if target is set to "irccd", + * becomes "irccd, welcome", + * - <strong>\@{red}\#{target}</strong>: if target is specified, it is written + * in red, + * + * ### Invalid or literals constructs + * + * - <strong>\#\#{target}</strong>: will output "\#{target}", + * - <strong>\#\#</strong>: will output "\#\#", + * - <strong>\#target</strong>: will output "\#target", + * - <strong>\#{target</strong>: will throw std::invalid_argument. + * + * ### Colors & attributes + * + * - <strong>\@{red,blue}</strong>: will write text red on blue background, + * - <strong>\@{default,yellow}</strong>: will write default color text on + * yellow background, + * - <strong>\@{white,black,bold,underline}</strong>: will write white text on + * black in both bold and underline. + */ +IRCCD_EXPORT std::string format(std::string text, const Substitution ¶ms = {}); + +/** + * Remove leading and trailing spaces. + * + * \param str the string + * \return the removed white spaces + */ +IRCCD_EXPORT std::string strip(std::string str); + +/** + * Split a string by delimiters. + * + * \param list the string to split + * \param delimiters a list of delimiters + * \param max max number of split + * \return a list of string splitted + */ +IRCCD_EXPORT std::vector<std::string> split(const std::string &list, const std::string &delimiters, int max = -1); + +/** + * Join values by a separator and return a string. + * + * \param first the first iterator + * \param last the last iterator + * \param delim the optional delimiter + */ +template <typename InputIt, typename DelimType = char> +std::string join(InputIt first, InputIt last, DelimType delim = ':') +{ + std::ostringstream oss; + + if (first != last) { + oss << *first; + + while (++first != last) + oss << delim << *first; + } + + return oss.str(); +} + +/** + * Convenient overload. + * + * \param list the initializer list + * \param delim the delimiter + * \return the string + */ +template <typename T, typename DelimType = char> +inline std::string join(std::initializer_list<T> list, DelimType delim = ':') +{ + return join(list.begin(), list.end(), delim); +} + +/** + * Clamp the value between low and high. + * + * \param value the value + * \param low the minimum value + * \param high the maximum value + * \return the value between minimum and maximum + */ +template <typename T> +constexpr T clamp(T value, T low, T high) noexcept +{ + return (value < high) ? std::max(value, low) : std::min(value, high); +} + +/** + * Parse IRC message and determine if it's a command or a simple message. + * + * \param message the message line + * \param commandChar the command char (e.g '!') + * \param plugin the plugin name + * \return the pair + */ +IRCCD_EXPORT MessagePair parseMessage(std::string message, const std::string &commandChar, const std::string &plugin); + +/** + * Server and identities must have strict names. This function can + * be used to ensure that they are valid. + * + * \param name the identifier name + * \return true if is valid + */ +inline bool isIdentifierValid(const std::string &name) +{ + return std::regex_match(name, std::regex("[A-Za-z0-9-_]+")); +} + +/** + * Check if the value is a boolean, 1, yes and true are accepted. + * + * \param value the value + * \return true if is boolean + * \note this function is case-insensitive + */ +IRCCD_EXPORT bool isBoolean(const std::string &value) noexcept; + +/** + * Check if the string is an integer. + * + * \param value the input + * \param base the optional base + * \return true if integer + */ +IRCCD_EXPORT bool isInt(const std::string &value, int base = 10) noexcept; + +/** + * Check if the string is real. + * + * \param value the value + * \return true if real + */ +IRCCD_EXPORT bool isReal(const std::string &value) noexcept; + +/** + * Check if the string is a number. + * + * \param value the value + * \return true if it is a number + */ +inline bool isNumber(const std::string &value) noexcept +{ + return isInt(value) || isReal(value); +} + +/** + * Tells if a number is bound between the limits. + * + * \param value the value to check + * \param min the minimum + * \param max the maximum + * \return true if value is beyond the limits + */ +template <typename T> +constexpr bool isBound(T value, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max()) noexcept +{ + return value >= min && value <= max; +} + +/** + * Try to convert the string into number. + * + * This function will try to convert the string to number in the limits of T. + * + * If the string is not a number or if the converted value is out of range than + * specified boundaries, an exception is thrown. + * + * By default, the function will use numeric limits from T. + * + * \param number the string to convert + * \param min the minimum (defaults to T minimum) + * \param max the maximum (defaults to T maximum) + * \return the converted value + * \throw std::invalid_argument if number is not a string + * \throw std::out_of_range if the number is not between min and max + */ +template <typename T> +inline T toNumber(const std::string &number, T min = std::numeric_limits<T>::min(), T max = std::numeric_limits<T>::max()) +{ + static_assert(std::is_integral<T>::value, "T must be integer type"); + + std::conditional_t<std::is_unsigned<T>::value, unsigned long long, long long> value; + + if (std::is_unsigned<T>::value) + value = std::stoull(number); + else + value = std::stoll(number); + + if (value < min || value > max) + throw std::out_of_range("out of range"); + + return static_cast<T>(value); +} + +/** + * Parse a network message from an input buffer and remove it from it. + * + * \param input the buffer, will be updated + * \return the message or empty string if there is nothing + */ +IRCCD_EXPORT std::string nextNetwork(std::string &input); + +/** + * Use arguments to avoid compiler warnings about unused parameters. + */ +template <typename... Args> +inline void unused(Args&&...) noexcept +{ +} + +/** + * Utilities for nlohmann json. + */ +namespace json { + +/** + * Require a property. + * + * \param json the json value + * \param key the property name + * \param type the requested property type + * \return the value + * \throw std::runtime_error if the property is missing + */ +inline nlohmann::json require(const nlohmann::json &json, const std::string &key, nlohmann::json::value_t type) +{ + auto it = json.find(key); + auto dummy = nlohmann::json(type); + + if (it == json.end()) + throw std::runtime_error(fmt::format("missing '{}' property", key)); + if (it->type() != type) + throw std::runtime_error(fmt::format("invalid '{}' property ({} expected, got {})", key, it->type_name(), dummy.type_name())); + + return *it; +} + +/** + * Convenient access for booleans. + * + * \param json the json object + * \param key the property key + * \return the boolean + * \throw std::runtime_error if the property is missing or not a boolean + */ +inline bool requireBool(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::boolean); +} + +/** + * Convenient access for ints. + * + * \param json the json object + * \param key the property key + * \return the int + * \throw std::runtime_error if the property is missing or not ant int + */ +inline std::int64_t requireInt(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::number_integer); +} + +/** + * Convenient access for unsigned ints. + * + * \param json the json object + * \param key the property key + * \return the unsigned int + * \throw std::runtime_error if the property is missing or not ant int + */ +inline std::uint64_t requireUint(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::number_unsigned); +} + +/** + * Convenient access for strings. + * + * \param json the json object + * \param key the property key + * \return the string + * \throw std::runtime_error if the property is missing or not a string + */ +inline std::string requireString(const nlohmann::json &json, const std::string &key) +{ + return require(json, key, nlohmann::json::value_t::string); +} + +/** + * Convenient access for unique identifiers. + * + * \param json the json object + * \param key the property key + * \return the identifier + * \throw std::runtime_error if the property is invalid + */ +inline std::string requireIdentifier(const nlohmann::json &json, const std::string &key) +{ + auto id = requireString(json, key); + + if (!isIdentifierValid(id)) + throw std::runtime_error("invalid '{}' identifier property"); + + return id; +} + +/** + * Convert the json value to boolean. + * + * \param json the json value + * \param def the default value if not boolean + * \return a boolean + */ +inline bool toBool(const nlohmann::json &json, bool def = false) noexcept +{ + return json.is_boolean() ? json.get<bool>() : def; +} + +/** + * Convert the json value to int. + * + * \param json the json value + * \param def the default value if not an int + * \return an int + */ +inline std::int64_t toInt(const nlohmann::json &json, std::int64_t def = 0) noexcept +{ + return json.is_number_integer() ? json.get<std::int64_t>() : def; +} + +/** + * Convert the json value to unsigned. + * + * \param json the json value + * \param def the default value if not a unsigned int + * \return an unsigned int + */ +inline std::uint64_t toUint(const nlohmann::json &json, std::uint64_t def = 0) noexcept +{ + return json.is_number_unsigned() ? json.get<std::uint64_t>() : def; +} + +/** + * Convert the json value to string. + * + * \param json the json value + * \param def the default value if not a string + * \return a string + */ +inline std::string toString(const nlohmann::json &json, std::string def = "") noexcept +{ + return json.is_string() ? json.get<std::string>() : def; +} + +/** + * Get a property or return null one if not found or if json is not an object. + * + * \param json the json value + * \param property the property key + * \return the value or null one if not found + */ +inline nlohmann::json get(const nlohmann::json &json, const std::string &property) noexcept +{ + auto it = json.find(property); + + if (it == json.end()) + return nlohmann::json(); + + return *it; +} + +/** + * Convenient access for boolean with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the boolean + */ +inline bool getBool(const nlohmann::json &json, const std::string &key, bool def = false) noexcept +{ + return toBool(get(json, key), def); +} + +/** + * Convenient access for ints with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the int + */ +inline std::int64_t getInt(const nlohmann::json &json, const std::string &key, std::int64_t def = 0) noexcept +{ + return toInt(get(json, key), def); +} + +/** + * Convenient access for unsigned ints with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the unsigned int + */ +inline std::uint64_t getUint(const nlohmann::json &json, const std::string &key, std::uint64_t def = 0) noexcept +{ + return toUint(get(json, key), def); +} + +/** + * Get an integer in the given range. + * + * \param json the json value + * \param key the property key + * \param min the minimum value + * \param max the maximum value + * \return the value + */ +template <typename T> +inline T getIntRange(const nlohmann::json &json, + const std::string &key, + std::int64_t min = std::numeric_limits<T>::min(), + std::int64_t max = std::numeric_limits<T>::max()) noexcept +{ + return clamp(getInt(json, key), min, max); +} + +/** + * Get an unsigned integer in the given range. + * + * \param json the json value + * \param key the property key + * \param min the minimum value + * \param max the maximum value + * \return value + */ +template <typename T> +inline T getUintRange(const nlohmann::json &json, + const std::string &key, + std::uint64_t min = std::numeric_limits<T>::min(), + std::uint64_t max = std::numeric_limits<T>::max()) noexcept +{ + return clamp(getUint(json, key), min, max); +} + +/** + * Convenient access for strings with default value. + * + * \param json the json value + * \param key the property key + * \param def the default value + * \return the string + */ +inline std::string getString(const nlohmann::json &json, const std::string &key, std::string def = "") noexcept +{ + return toString(get(json, key), def); +} + +} // !json + +/** + * \brief Miscellaneous utilities for Pollable objects + */ +namespace poller { + +/** + * \cond HIDDEN_SYMBOLS + */ + +inline void prepare(fd_set &, fd_set &, net::Handle &) noexcept +{ +} + +/** + * \endcond + */ + +/** + * Call prepare function for every Pollable objects. + * + * \param in the input set + * \param out the output set + * \param max the maximum handle + * \param first the first Pollable object + * \param rest the additional Pollable objects + */ +template <typename Pollable, typename... Rest> +inline void prepare(fd_set &in, fd_set &out, net::Handle &max, Pollable &first, Rest&... rest) +{ + first.prepare(in, out, max); + prepare(in, out, max, rest...); +} + +/** + * \cond HIDDEN_SYMBOLS + */ + +inline void sync(fd_set &, fd_set &) noexcept +{ +} + +/** + * \endcond + */ + +/** + * Call sync function for every Pollable objects. + * + * \param in the input set + * \param out the output set + * \param first the first Pollable object + * \param rest the additional Pollable objects + */ +template <typename Pollable, typename... Rest> +inline void sync(fd_set &in, fd_set &out, Pollable &first, Rest&... rest) +{ + first.sync(in, out); + sync(in, out, rest...); +} + +/** + * Prepare and sync Pollable objects. + * + * \param timeout the timeout in milliseconds (< 0 means forever) + * \param first the the first Pollable object + * \param rest the additional Pollable objects + */ +template <typename Pollable, typename... Rest> +void poll(int timeout, Pollable &first, Rest&... rest) +{ + fd_set in, out; + timeval tv = {0, timeout * 1000}; + + FD_ZERO(&in); + FD_ZERO(&out); + + net::Handle max = 0; + + prepare(in, out, max, first, rest...); + + // Timeout or error are discarded. + if (::select(max + 1, &in, &out, nullptr, timeout < 0 ? nullptr : &tv) > 0) + sync(in, out, first, rest...); +} + +} // !poller + +} // !util + +} // !irccd + +#endif // !IRCCD_UTIL_HPP
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/libcommon/irccd/xdg.hpp Wed Oct 05 13:06:00 2016 +0200 @@ -0,0 +1,190 @@ +/* + * xdg.hpp -- XDG directory specifications + * + * Copyright (c) 2013-2016 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef XDG_HPP +#define XDG_HPP + +/** + * \file xdg.hpp + * \brief XDG directory specifications. + * \author David Demelier <markand@malikana.fr> + */ + +#include <cstdlib> +#include <sstream> +#include <stdexcept> +#include <string> +#include <vector> + +namespace irccd { + +/** + * \class Xdg + * \brief XDG directory specifications. + * + * Read and get XDG directories. + * + * This file should compiles on Windows to facilitate portability but its functions must not be used. + */ +class Xdg { +private: + std::string m_configHome; + std::string m_dataHome; + std::string m_cacheHome; + std::string m_runtimeDir; + std::vector<std::string> m_configDirs; + std::vector<std::string> m_dataDirs; + + bool isabsolute(const std::string &path) const noexcept + { + return path.length() > 0 && path[0] == '/'; + } + + std::vector<std::string> split(const std::string &arg) const + { + std::stringstream iss(arg); + std::string item; + std::vector<std::string> elems; + + while (std::getline(iss, item, ':')) + if (isabsolute(item)) + elems.push_back(item); + + return elems; + } + + std::string envOrHome(const std::string &var, const std::string &repl) const + { + auto value = std::getenv(var.c_str()); + + if (value == nullptr || !isabsolute(value)) { + auto home = std::getenv("HOME"); + + if (home == nullptr) + throw std::runtime_error("could not get home directory"); + + return std::string(home) + "/" + repl; + } + + return value; + } + + std::vector<std::string> listOrDefaults(const std::string &var, const std::vector<std::string> &list) const + { + auto value = std::getenv(var.c_str()); + + if (!value) + return list; + + // No valid item at all? Use defaults. + auto result = split(value); + + return (result.size() == 0) ? list : result; + } + +public: + /** + * Open an xdg instance and load directories. + * + * \throw std::runtime_error on failures + */ + Xdg() + { + m_configHome = envOrHome("XDG_CONFIG_HOME", ".config"); + m_dataHome = envOrHome("XDG_DATA_HOME", ".local/share"); + m_cacheHome = envOrHome("XDG_CACHE_HOME", ".cache"); + + m_configDirs = listOrDefaults("XDG_CONFIG_DIRS", { "/etc/xdg" }); + m_dataDirs = listOrDefaults("XDG_DATA_DIRS", { "/usr/local/share", "/usr/share" }); + + /* + * Runtime directory is a special case and does not have a replacement, the application should manage + * this by itself. + */ + auto runtime = std::getenv("XDG_RUNTIME_DIR"); + if (runtime && isabsolute(runtime)) + m_runtimeDir = runtime; + } + + /** + * Get the config directory. ${XDG_CONFIG_HOME} or ${HOME}/.config + * + * \return the config directory + */ + inline const std::string &configHome() const noexcept + { + return m_configHome; + } + + /** + * Get the data directory. ${XDG_DATA_HOME} or ${HOME}/.local/share + * + * \return the data directory + */ + inline const std::string &dataHome() const noexcept + { + return m_dataHome; + } + + /** + * Get the cache directory. ${XDG_CACHE_HOME} or ${HOME}/.cache + * + * \return the cache directory + */ + inline const std::string &cacheHome() const noexcept + { + return m_cacheHome; + } + + /** + * Get the runtime directory. + * + * There is no replacement for XDG_RUNTIME_DIR, if it is not set, an empty valus is returned and the user is + * responsible of using something else. + * + * \return the runtime directory + */ + inline const std::string &runtimeDir() const noexcept + { + return m_runtimeDir; + } + + /** + * Get the standard config directories. ${XDG_CONFIG_DIRS} or { "/etc/xdg" } + * + * \return the list of config directories + */ + inline const std::vector<std::string> &configDirs() const noexcept + { + return m_configDirs; + } + + /** + * Get the data directories. ${XDG_DATA_DIRS} or { "/usr/local/share", "/usr/share" } + * + * \return the list of data directories + */ + inline const std::vector<std::string> &dataDirs() const noexcept + { + return m_dataDirs; + } +}; + +} // !irccd + +#endif // !XDG_HPP