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 &regex)
+{
+    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 &params)
+{
+    std::ostringstream oss;
+
+#if defined(HAVE_STD_PUT_TIME)
+    oss << std::put_time(std::localtime(&params.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(&params.time));
+
+    oss << buffer;
+#endif
+
+    return oss.str();
+}
+
+std::string substituteKeywords(const std::string &content, const Substitution &params)
+{
+    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 &params)
+{
+    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 &params)
+{
+    /*
+     * 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 &params = {});
+
+/**
+ * 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