changeset 486:0b156b82b8c1

Irccd: rework of paths, closes #611
author David Demelier <markand@malikania.fr>
date Thu, 20 Jul 2017 15:12:41 +0200
parents daf3aa8b2ddb
children beb6c638b841
files CMakeLists.txt MIGRATING.md cmake/IrccdOptions.cmake cmake/IrccdSystem.cmake cmake/function/IrccdDefinePlugin.cmake cmake/internal/sysconfig.hpp.in doc/examples/CMakeLists.txt doc/html/api/module/Irccd.Plugin/index.md doc/html/irccd/configuring.md doc/html/irccd/paths.md irccd/main.cpp irccdctl/main.cpp libcommon/CMakeLists.txt libcommon/irccd/path.cpp libcommon/irccd/path.hpp libcommon/irccd/system.cpp libcommon/irccd/system.hpp libirccd-js/irccd/mod-directory.cpp libirccd-js/irccd/mod-plugin.cpp libirccd-js/irccd/plugin-js.cpp libirccd-js/irccd/plugin-js.hpp libirccd/CMakeLists.txt libirccd/irccd/config.cpp libirccd/irccd/config.hpp libirccd/irccd/plugin-dynlib.cpp libirccd/irccd/plugin.cpp libirccd/irccd/plugin.hpp libirccd/irccd/service.cpp libirccd/irccd/service.hpp plugins/ask/ask.js plugins/hangman/hangman.js plugins/history/history.js tests/CMakeLists.txt tests/js-timer/main.cpp tests/path/CMakeLists.txt tests/path/main.cpp tests/plugin-ask/main.cpp tests/plugin-auth/main.cpp tests/plugin-hangman/main.cpp tests/plugin-history/main.cpp tests/plugin-logger/main.cpp tests/plugin-plugin/main.cpp tests/service-plugin/CMakeLists.txt tests/service-plugin/main.cpp
diffstat 44 files changed, 1026 insertions(+), 1066 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Jul 27 16:44:57 2017 +0200
+++ b/CMakeLists.txt	Thu Jul 20 15:12:41 2017 +0200
@@ -156,6 +156,7 @@
         ${CMAKE_SOURCE_DIR}/CONTRIBUTE.md
         ${CMAKE_SOURCE_DIR}/CREDITS.md
         ${CMAKE_SOURCE_DIR}/INSTALL.md
+        ${CMAKE_SOURCE_DIR}/MIGRATING.md
         ${CMAKE_SOURCE_DIR}/README.md
         ${CMAKE_SOURCE_DIR}/STYLE.md
 )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MIGRATING.md	Thu Jul 20 15:12:41 2017 +0200
@@ -0,0 +1,27 @@
+IRC Client Daemon MIGRATING
+===========================
+
+This document is a small guide to help you migrating to a next major version.
+
+Migrating from 2.x to 3.x
+-------------------------
+
+### CMake options
+
+  - WITH_CONFDIR has been renamed to WITH_SYSCONFDIR.
+
+### Paths
+
+  - The default plugins path has been changed from **share/irccd/plugins** to
+    **libexec/irccd/plugins**.
+
+### Plugin configuration
+
+The following properties in `Irccd.Plugin` has been renamed:
+
+  - cachePath renamed to paths.cache,
+  - configPath renamed to paths.config,
+  - dataPath renamed to paths.data.
+
+Note: these paths are no more automatically detected and set with the new
+      `[paths]` and `[paths.<name>]` sections.
--- a/cmake/IrccdOptions.cmake	Thu Jul 27 16:44:57 2017 +0200
+++ b/cmake/IrccdOptions.cmake	Thu Jul 20 15:12:41 2017 +0200
@@ -38,12 +38,14 @@
 # Options that controls both installations and the irccd runtime:
 #
 # WITH_BINDIR           Binary directory for irccd, irccdctl
-# WITH_PLUGINDIR        Path where plugins must be installed
+# WITH_CACHEDIR         Path where to store temporary files
+# WITH_CMAKEDIR         Path where to install CMake configuration files
+# WITH_DATADIR          Path for data files
 # WITH_DOCDIR           Path where to install documentation
 # WITH_MANDIR           Path where to install manuals
-# WITH_CONFDIR          Path where to search configuration files
-# WITH_CACHEDIR         Path where to store temporary files
 # WITH_PKGCONFIGDIR     Path where to install pkg-config files
+# WITH_PLUGINDIR        Path where plugins must be installed
+# WITH_SYSCONFDIR       Path where to install configuration files
 # WITH_SYSTEMDDIR       Path where to install systemd unit file
 #
 
@@ -84,38 +86,17 @@
 #
 
 set(WITH_BINDIR "bin" CACHE STRING "Binary directory")
+set(WITH_CACHEDIR "var/cache/irccd" CACHE STRING "Cache directory")
+set(WITH_CMAKEDIR "lib/cmake" CACHE STRING "Directory for CMake modules")
+set(WITH_DATADIR "share/irccd" CACHE STRING "Directory for additional data")
+set(WITH_DOCDIR "share/doc/irccd" CACHE STRING "Documentation directory")
 set(WITH_MANDIR "share/man" CACHE STRING "Man directory")
-set(WITH_CONFDIR "etc" CACHE STRING "Configuration directory")
-set(WITH_CMAKEDIR "lib/cmake" CACHE STRING "Directory for CMake modules")
 set(WITH_PKGCONFIGDIR "lib/pkgconfig" CACHE STRING "Directory for pkg-config file")
+set(WITH_PLUGINDIR "libexec/irccd/plugins" CACHE STRING "Module prefix where to install")
+set(WITH_SYSCONFDIR "etc" CACHE STRING "Configuration directory")
 set(WITH_SYSTEMDDIR "/usr/lib/systemd/system" CACHE STRING "Absolute path where to install systemd files")
 
 #
-# On Windows, we install the applcation like C:/Program Files/irccd so do not append irccd to the
-# directories again.
-#
-if (WIN32)
-    set(WITH_DATADIR "share" CACHE STRING "Data directory")
-    set(WITH_CACHEDIR "var" CACHE STRING "Temporary files directory")
-    set(WITH_PLUGINDIR "share/plugins" CACHE STRING "Module prefix where to install")
-    set(WITH_DOCDIR "share/doc" CACHE STRING "Documentation directory")
-else ()
-    set(WITH_DATADIR "share/irccd" CACHE STRING "Data directory")
-    set(WITH_CACHEDIR "var/irccd" CACHE STRING "Temporary files directory")
-    set(WITH_PLUGINDIR "share/irccd/plugins" CACHE STRING "Module prefix where to install")
-    set(WITH_DOCDIR "share/doc/irccd" CACHE STRING "Documentation directory")
-endif ()
-
-#
-# Check if any of these path is absolute and raise an error if true.
-#
-foreach (d WITH_BINDIR WITH_CACHEDIR WITH_DATADIR WITH_CONFDIR WITH_PLUGINDIR)
-    if (IS_ABSOLUTE ${${d}})
-        message(FATAL_ERROR "${d} can not be absolute (${${d}} given)")
-    endif ()
-endforeach ()
-
-#
 # Internal dependencies.
 # -------------------------------------------------------------------
 #
--- a/cmake/IrccdSystem.cmake	Thu Jul 27 16:44:57 2017 +0200
+++ b/cmake/IrccdSystem.cmake	Thu Jul 20 15:12:41 2017 +0200
@@ -83,6 +83,8 @@
     set(IRCCD_SYSTEM_MAC TRUE)
 elseif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD")
     set(IRCCD_SYSTEM_FREEBSD TRUE)
+elseif (CMAKE_SYSTEM_NAME MATCHES "DragonFly")
+    set(IRCCD_SYSTEM_DRAGONFLYBSD TRUE)
 elseif (CMAKE_SYSTEM_NAME MATCHES "NetBSD")
     set(IRCCD_SYSTEM_NETBSD TRUE)
 elseif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD")
@@ -263,4 +265,7 @@
     ${CMAKE_BINARY_DIR}/irccd/sysconfig.hpp
 )
 
-include_directories(${CMAKE_BINARY_DIR})
+include_directories(
+    ${CMAKE_BINARY_DIR}
+    ${CMAKE_BINARY_DIR}/irccd
+)
--- a/cmake/function/IrccdDefinePlugin.cmake	Thu Jul 27 16:44:57 2017 +0200
+++ b/cmake/function/IrccdDefinePlugin.cmake	Thu Jul 20 15:12:41 2017 +0200
@@ -86,7 +86,7 @@
     install(
         TARGETS plugin-${PLG_NAME}
         COMPONENT ${PLG_NAME}
-        LIBRARY DESTINATION ${WITH_NPLUGINDIR}
+        LIBRARY DESTINATION ${WITH_PLUGINDIR}
     )
 endfunction()
 
--- a/cmake/internal/sysconfig.hpp.in	Thu Jul 27 16:44:57 2017 +0200
+++ b/cmake/internal/sysconfig.hpp.in	Thu Jul 20 15:12:41 2017 +0200
@@ -54,6 +54,7 @@
 #cmakedefine IRCCD_SYSTEM_WINDOWS
 #cmakedefine IRCCD_SYSTEM_MAC
 #cmakedefine IRCCD_SYSTEM_FREEBSD
+#cmakedefine IRCCD_SYSTEM_DRAGONFLYBSD
 #cmakedefine IRCCD_SYSTEM_NETBSD
 #cmakedefine IRCCD_SYSTEM_OPENBSD
 #cmakedefine IRCCD_SYSTEM_LINUX
@@ -64,12 +65,11 @@
  * ------------------------------------------------------------------
  */
 
-#define WITH_BINDIR             "@WITH_BINDIR@"
-#define WITH_DATADIR            "@WITH_DATADIR@"
-#define WITH_CONFDIR            "@WITH_CONFDIR@"
-#define WITH_PLUGINDIR          "@WITH_PLUGINDIR@"
-#define WITH_NPLUGINDIR         "@WITH_NPLUGINDIR@"
-#define WITH_CACHEDIR           "@WITH_CACHEDIR@"
+#define WITH_CACHEDIR       "@WITH_CACHEDIR@"
+#define WITH_DATADIR        "@WITH_DATADIR@"
+#define WITH_BINDIR         "@WITH_BINDIR@"
+#define WITH_SYSCONFDIR     "@WITH_SYSCONFDIR@"
+#define WITH_PLUGINDIR      "@WITH_PLUGINDIR@"
 
 #cmakedefine WITH_JS
 #cmakedefine WITH_SSL
@@ -120,7 +120,7 @@
 #       define IRCCD_EXPORT
 #   endif
 #else
-#  define IRCCD_EXPORT
+#   define IRCCD_EXPORT
 #endif
 
 #endif // !IRCCD_SYSCONFIG_H
--- a/doc/examples/CMakeLists.txt	Thu Jul 27 16:44:57 2017 +0200
+++ b/doc/examples/CMakeLists.txt	Thu Jul 20 15:12:41 2017 +0200
@@ -33,7 +33,7 @@
         ${examples_SOURCE_DIR}/irccd.conf.sample
         ${examples_SOURCE_DIR}/irccdctl.conf.sample
     COMPONENT examples
-    DESTINATION ${WITH_CONFDIR}
+    DESTINATION ${WITH_SYSCONFDIR}
 )
 
 setg(CPACK_COMPONENT_EXAMPLES_HIDDEN On)
--- a/doc/html/api/module/Irccd.Plugin/index.md	Thu Jul 27 16:44:57 2017 +0200
+++ b/doc/html/api/module/Irccd.Plugin/index.md	Thu Jul 20 15:12:41 2017 +0200
@@ -8,22 +8,17 @@
 
 This module let you manage plugins.
 
-## Constants
+## Objects
 
-The following properties are defined:
+The following properties are defined as read in the configuration file:
 
-  - **cachePath**: (string) the path to the cache directory,
-  - **configPath**: (string) the path to the configuration directory,
-  - **dataPath**: (string) the path to the data directory.
-
-## Configuration
-
-An additional property `config` is defined with all options set in the appropriate `[plugin.<name>]` from the user
-configuration file.
+  - **config**: the `[plugin.<name>]` section,
+  - **paths**: the `[paths.<name>]` section,
+  - **format**: the `[format.<name>]` section.
 
 ### Example
 
-If the configuration file configures the plugin **xyz**:
+Assuming the configuration file is defined as following:
 
 <div class="panel panel-info">
  <div class="panel-heading">~/.config/irccd/irccd.conf</div>
@@ -32,15 +27,24 @@
 [plugin.xyz]
 foo = true
 baz = "hello"
+
+[paths.xyz]
+config = "/etc/xyz"
 ````
  </div>
 </div>
 
-Then `Irccd.Plugin.config` will have the following properties:
+The `Irccd.Plugin.config` will have the following properties:
 
   - **foo**: (string) set to "true",
   - **baz**: (string) set to "hello".
 
+The `Irccd.Plugin.paths` will have the following properties:
+
+  - **cache**: (string) set to the default cache directory,
+  - **config**: (string) set to "/etc/xyz",
+  - **data**: (string) set to the default data directory.
+
 ## Functions
 
   - [info](Irccd.Plugin.info.html)
--- a/doc/html/irccd/configuring.md	Thu Jul 27 16:44:57 2017 +0200
+++ b/doc/html/irccd/configuring.md	Thu Jul 20 15:12:41 2017 +0200
@@ -164,6 +164,46 @@
 channels = ( "#staff", "#club:secret" )
 ````
 
+# The paths section
+
+The paths section defines common paths used as defaults for all plugins.
+
+Any option in this section can be defined altough the following are used as
+common convention used in all plugins:
+
+  - **cache**: (string) path for data files written by the plugin,
+  - **data**: (string) path for data files provided by the user,
+  - **config**: (string) path for additional configuration from the user.
+
+For each of these paths, **plugin/name** is appended with the appropriate
+plugin name when loaded.
+
+The section is redefinable per plugin basis using the `[paths.<plugin>]` syntax.
+
+**Example**
+
+````ini
+#
+# Common for all plugins.
+#
+# Example with ask plugin:
+#
+#   cache  -> /var/cache/irccd/plugin/ask
+#   config -> /usr/local/etc/irccd/plugin/ask
+#   data   -> /var/data/irccd/plugin/ask
+#
+[paths]
+cache = "/var/cache/irccd"
+config = "/usr/local/etc/irccd"
+data = "/var/data/irccd"
+
+#
+# Explicit override for plugin hangman.
+#
+[paths.hangman]
+config = "/etc/hangman"
+````
+
 # The plugins section
 
 This section is used to load plugins.
--- a/doc/html/irccd/paths.md	Thu Jul 27 16:44:57 2017 +0200
+++ b/doc/html/irccd/paths.md	Thu Jul 20 15:12:41 2017 +0200
@@ -6,7 +6,6 @@
 Irccd uses different types of paths depending on the context.
 
   - Configuration
-  - Data
   - Plugins
 
 Paths prefixed by (W) means they are only used on Windows, others prefixed by
@@ -30,38 +29,6 @@
   - `C:/Program Files/irccd/etc/irccd.conf`
   - `C:/Users/john/AppData/irccd/config`
 
-# Data
-
-The data directory is only used by plugins, it is dedicated to store important
-files such as plugin assets, logs or anything that is meaningful for the user
-or the plugin.
-
-<div class="alert alert-info" role="alert">
-**Note**: The plugins never try to create the directories, instead irccd
-searches for the first available one and use it. If the directory does not exist
-it is set by default to the **system** one.
-
-It is thus recommended to create a directory into your local home folder if you
-run irccd as your user and not as a system daemon.
-</div>
-
-The following directories as searched in order:
-
-  - \(W) `%APPDATA%/irccd/share`
-  - \(U) `${XDG_DATA_HOME}/irccd`
-  - \(U) `${HOME}/.local/share/irccd` (if `XDG_DATA_HOME` is not set)
-  - \(W) `installation-directory/share`
-  - \(U) `installation-directory/share/irccd`
-
-For plugins, the path is appended with `plugin/<plugin_name>` (e.g. plugin/ask).
-
-Examples:
-
-  - `/home/john/.local/share/irccd/plugin/ask`
-  - `/usr/local/share/irccd/plugin/ask`
-  - `C:/Users/john/AppData/irccd/share/plugin/ask`
-  - `C:/Program Files/irccd/share/plugin/ask`
-
 # Plugins
 
 These directories are searched in the following order to load plugins when they
--- a/irccd/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/irccd/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -38,7 +38,6 @@
 #include "command.hpp"
 #include "logger.hpp"
 #include "options.hpp"
-#include "path.hpp"
 #include "service.hpp"
 #include "system.hpp"
 #include "config.hpp"
@@ -111,7 +110,6 @@
 {
     // Needed for some components.
     sys::setProgramName("irccd");
-    path::setApplicationPath(argv[0]);
 
     // Default logging to console.
     log::setVerbose(false);
--- a/irccdctl/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/irccdctl/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -31,7 +31,6 @@
 #include "irccdctl.hpp"
 #include "logger.hpp"
 #include "options.hpp"
-#include "path.hpp"
 #include "system.hpp"
 #include "util.hpp"
 
@@ -560,9 +559,8 @@
         if (it != result.end() || (it = result.find("--config")) != result.end())
             read(it->second);
         else {
-            for (const std::string &dir : path::list(path::PathConfig)) {
+            for (const auto& path : sys::config_filenames("irccdctl.conf")) {
                 boost::system::error_code ec;
-                std::string path = dir + "irccdctl.conf";
 
                 if (boost::filesystem::exists(path, ec) && !ec) {
                     read(path);
--- a/libcommon/CMakeLists.txt	Thu Jul 27 16:44:57 2017 +0200
+++ b/libcommon/CMakeLists.txt	Thu Jul 20 15:12:41 2017 +0200
@@ -28,7 +28,6 @@
     ${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
@@ -42,7 +41,6 @@
     ${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
 )
--- a/libcommon/irccd/path.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,546 +0,0 @@
-/*
- * path.cpp -- special paths inside irccd
- *
- * Copyright (c) 2013-2017 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 <boost/filesystem.hpp>
-
-#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{"."};
-
-/*
- * executablePath.
- * ------------------------------------------------------------------
- *
- * Get the executable directory.
- */
-
-#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 (boost::filesystem::path(argv0).is_absolute()) {
-            base = argv0;
-        } else {
-            std::string name = fs::baseName(argv0);
-
-            for (const auto &dir : util::split(sys::env("PATH"), std::string(1, Separator))) {
-                boost::system::error_code ec;
-                std::string path = dir + fs::separator() + name;
-
-                if (boost::filesystem::exists(path, ec) && !ec) {
-                    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;
-    boost::system::error_code ec;
-
-    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(boost::filesystem::current_path(ec).string()));
-        list.push_back(clean(userPlugins()));
-        list.push_back(clean(systemPlugins()));
-        break;
-    case PathNativePlugins:
-        list.push_back(clean(boost::filesystem::current_path(ec).string()));
-        list.push_back(clean(systemNativePlugins()));
-        break;
-    default:
-        break;
-    }
-
-    return list;
-}
-
-} // !path
-
-} // !irccd
--- a/libcommon/irccd/path.hpp	Thu Jul 27 16:44:57 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,111 +0,0 @@
-/*
- * path.hpp -- special paths inside irccd
- *
- * Copyright (c) 2013-2017 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
--- a/libcommon/irccd/system.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libcommon/irccd/system.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -16,60 +16,93 @@
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
+#include <algorithm>
+#include <cassert>
 #include <cstdlib>
 #include <ctime>
 #include <stdexcept>
+#include <string>
+
+#include <boost/filesystem.hpp>
 
 #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>
+#   include <cstdlib>
 #endif
 
 #if defined(IRCCD_SYSTEM_LINUX)
-#  include <sys/sysinfo.h>
+#   include <sys/sysinfo.h>
+
+#   include <unistd.h>
+
+#   include <cerrno>
+#   include <climits>
+#   include <cstring>
+#elif defined(IRCCD_SYSTEM_FREEBSD) ||
+      defined(IRCCD_SYSTEM_DRAGONFLYBSD) ||
+      defined(IRCCD_SYSTEM_NETBSD) ||
+      defined(IRCCD_SYSTEM_OPENBSD)
+#   if defined(IRCCD_SYSTEM_NETBSD)
+#       include <sys/param.h>
+#   else
+#       include <sys/types.h>
+#   endif
+
+#   if defined(IRCCD_SYSTEM_OPENBSD)
+#       include <unistd.h>
+#   endif
+
+#   include <sys/sysctl.h>
+
+#   include <cerrno>
+#   include <climits>
+#   include <cstddef>
+#   include <cstdlib>
+#   include <cstring>
+#elif defined(IRCCD_SYSTEM_MAC)
+#   include <sys/sysctl.h>
+#   include <cerrno>
+#   include <cstring>
+#   include <libproc.h>
+#   include <unistd.h>
+#elif defined(IRCCD_SYSTEM_WINDOWS)
+#   include <sys/types.h>
+#   include <sys/timeb.h>
+#   include <windows.h>
+#   include <shlobj.h>
 #endif
 
-#  include <sys/utsname.h>
-#  include <sys/time.h>
-#  include <sys/types.h>
-#  include <unistd.h>
+#if !defined(IRCCD_SYSTEM_WINDOWS)
+#   include <sys/utsname.h>
+#   include <sys/time.h>
+#   include <sys/types.h>
+#   include <unistd.h>
 
-#  include <cerrno>
-#  include <cstring>
-#  include <stdexcept>
-#  include <ctime>
-
+#   include <cerrno>
+#   include <cstring>
+#   include <ctime>
 #endif
 
 // For sys::setGid.
 #if defined(HAVE_SETGID)
-#  include <sys/types.h>
-#  include <unistd.h>
-#  include <grp.h>
+#   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>
+#   include <sys/types.h>
+#   include <unistd.h>
+#   include <pwd.h>
 #endif
 
 #include "fs.hpp"
 #include "logger.hpp"
 #include "system.hpp"
 #include "util.hpp"
+#include "xdg.hpp"
 
 namespace irccd {
 
@@ -78,6 +111,14 @@
 namespace {
 
 /*
+ * 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;
+
+/*
  * setHelper.
  * ------------------------------------------------------------------
  *
@@ -124,12 +165,235 @@
 }
 
 /*
- * 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.
+ * executable_path
+ * ------------------------------------------------------------------
+ *
+ * Get the executable path.
+ *
+ * Example:
+ *
+ * /usr/local/bin/irccd -> /usr/local/bin
+ */
+std::string executable_path()
+{
+    std::string result;
+
+#if defined(__linux__)
+    char path[PATH_MAX + 1] = {0};
+
+    if (readlink("/proc/self/exe", path, sizeof (path) - 1) < 0)
+        throw std::runtime_error(std::strerror(errno));
+
+    result = path;
+#elif defined(__FreeBSD__) || defined(__DragonFly__)
+    int size = PATH_MAX, mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
+    char path[PATH_MAX + 1] = {0};
+
+    if (sysctl(mib, 4, path, &size, nullptr, 0) < 0)
+        throw std::runtime_error(std::strerror(errno));
+
+    result = path;
+#elif defined(__APPLE__)
+    char path[PROC_PIDPATHINFO_MAXSIZE + 1] = {0};
+
+    if ((proc_pidpath(getpid(), path, sizeof (path) - 1) == 0)
+        throw std::runtime_error(std::strerror(errno));
+
+    result = path;
+#elif defined(_WIN32)
+    char path[PATH_MAX + 1] = {0};
+
+    if (GetModuleFileNameA(nullptr, path, sizeof (path) - 1) == 0)
+        throw std::runtime_error("GetModuleFileName error");
+
+    result = path;
+#elif defined(__NetBSD__)
+    char path[4096 + 1] = {0};
+
+#   if defined(KERN_PROC_PATHNAME)
+    int mib[] = { CTL_KERN, KERN_PROC_ARGS, -1, KERN_PROC_PATHNAME };
+    int size = sizeof (path) - 1;
+
+    if (sysctl(mib, 4, path, &size, nullptr, 0) < 0)
+        throw std::runtime_error(std::strerror(errno));
+#   else
+    if (readlink("/proc/curproc/exe", path, sizeof (path) - 1) < 0)
+        throw std::runtime_error(std::strerror(errno));
+#   endif
+
+    result = path;
+#elif defined(__OpenBSD__)
+    char **paths, path[PATH_MAX + 1] = {0};
+    int length, mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
+
+    if (sysctl(mib, 4, nullptr, &length, nullptr, 0) < 0)
+        throw std::runtime_error(std::strerror(errno));
+    if ((paths = static_cast<char**>(std::malloc(length))) == nullptr)
+        throw std::runtime_error(std::strerror(errno));
+    if (sysctl(mib, 4, paths, &length, nullptr, 0) < 0) {
+        std::free(paths);
+        throw std::runtime_error(std::strerror(errno));
+    }
+
+    realpath(paths[0], path);
+    result = path;
+
+    std::free(paths);
+    std::free(path);
+#endif
+    return result;
+}
+
+/*
+ * add_config_user_path
+ * ------------------------------------------------------------------
+ *
+ * Referenced by: config_filenames.
+ *
+ * Add user config path.
  */
+void add_config_user_path(std::vector<std::string>& result, const std::string& file)
+{
+    boost::filesystem::path path;
 
-std::string programNameCopy;
+#if defined(IRCCD_SYSTEM_WINDOWS)
+    char folder[MAX_PATH] = {0};
+
+    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK)
+        path = folder + "\\irccd\\config";
+    else
+        path = ".";
+#else
+    try {
+        path = Xdg().configHome();
+    } catch (...) {
+        path = sys::env("HOME");
+        path /= ".config";
+    }
+
+    path /= "irccd";
+#endif
+
+    path /= file;
+    result.push_back(path.string());
+}
+
+/*
+ * add_plugin_user_path
+ * ------------------------------------------------------------------
+ *
+ * Referenced by: plugin_filenames.
+ *
+ * Like add add_config_user_path but for plugins.
+ */
+void add_plugin_user_path(std::vector<std::string>& result, const std::string& file)
+{
+    boost::filesystem::path path;
+
+#if defined(IRCCD_SYSTEM_WINDOWS)
+    char folder[MAX_PATH] = {0};
+
+    if (SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, folder) == S_OK)
+        path = folder + "\\irccd\\share";
+#else
+    try {
+        path = Xdg().dataHome();
+    } catch (...) {
+        path = sys::env("HOME");
+        path /= ".local/share";
+    }
+
+    path /= "irccd";
+#endif
+
+    path /= file;
+    result.push_back(path.string());
+}
+
+/*
+ * base_directory
+ * ------------------------------------------------------------------
+ *
+ * Get the base program directory.
+ *
+ * If irccd has been compiled with relative paths, the base directory is
+ * evaluated by climbing the `bindir' directory from the executable path.
+ *
+ * Otherwise, use the installation prefix.
+ */
+boost::filesystem::path base_directory()
+{
+    static const boost::filesystem::path bindir(WITH_BINDIR);
+    static const boost::filesystem::path prefix(PREFIX);
+
+    boost::filesystem::path path(".");
+
+    if (bindir.is_relative()) {
+        try {
+            path = executable_path();
+            path = path.parent_path();
+        } catch (...) {
+            path = "./";
+        }
+
+        // Compute relative base directory.
+        for (auto len = std::distance(bindir.begin(), bindir.end()); len > 0; len--)
+            path = path.parent_path();
+        if (path.empty())
+            path = ".";
+    } else
+        path = prefix;
+
+    return path;
+}
+
+/*
+ * add_system_path
+ * ------------------------------------------------------------------
+ *
+ * Referenced by: config_filenames,
+ *                plugin_filenames
+ *
+ * Add system path into the result list.
+ */
+void add_system_path(std::vector<std::string>& result,
+                     const std::string& file,
+                     const boost::filesystem::path& component)
+{
+    boost::filesystem::path path;
+
+    if (component.is_absolute())
+        path = component;
+    else {
+        path = base_directory();
+        path /= component;
+    }
+
+    path /= file;
+    result.push_back(path.string());
+}
+
+/*
+ * system_directory
+ * ------------------------------------------------------------------
+ *
+ * Compute the system wise directory path for the given component.
+ *
+ * Referenced by: cachedir,
+ *                datadir,
+ *                sysconfigdir
+ */
+std::string system_directory(const std::string& component)
+{
+    boost::filesystem::path path(component);
+
+    if (path.is_relative()) {
+        path = base_directory();
+        path /= component;
+    }
+
+    return path.string();
+}
 
 } // !namespace
 
@@ -155,6 +419,8 @@
     return "Windows";
 #elif defined(IRCCD_SYSTEM_FREEBSD)
     return "FreeBSD";
+#elif defined(IRCCD_SYSTEM_DRAGONFLYBSD)
+    return "DragonFlyBSD";
 #elif defined(IRCCD_SYSTEM_OPENBSD)
     return "OpenBSD";
 #elif defined(IRCCD_SYSTEM_NETBSD)
@@ -279,6 +545,46 @@
 
 #endif
 
+std::string cachedir()
+{
+    return system_directory(WITH_CACHEDIR);
+}
+
+std::string datadir()
+{
+    return system_directory(WITH_DATADIR);
+}
+
+std::string sysconfigdir()
+{
+    return system_directory(WITH_SYSCONFDIR);
+}
+
+std::vector<std::string> config_filenames(std::string file)
+{
+    std::vector<std::string> result;
+
+    add_config_user_path(result, file);
+    add_system_path(result, file, WITH_SYSCONFDIR);
+
+    return result;
+}
+
+std::vector<std::string> plugin_filenames(const std::string& name,
+                                          const std::vector<std::string>& extensions)
+{
+    assert(!extensions.empty());
+
+    std::vector<std::string> result;
+
+    for (const auto& ext : extensions)
+        add_plugin_user_path(result, name + ext);
+    for (const auto& ext : extensions)
+        add_system_path(result, name + ext, WITH_PLUGINDIR);
+
+    return result;
+}
+
 } // !sys
 
 } // !irccd
--- a/libcommon/irccd/system.hpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libcommon/irccd/system.hpp	Thu Jul 20 15:12:41 2017 +0200
@@ -26,6 +26,9 @@
 
 #include <cstdint>
 #include <string>
+#include <vector>
+
+#include <boost/filesystem.hpp>
 
 #include "sysconfig.hpp"
 
@@ -115,6 +118,61 @@
 
 #endif
 
+/**
+ * Get the cache directory as specified as compile time option WITH_CACHEDIR, if
+ * the value is absolute, it is returned as-is.
+ *
+ * If the component is relative, it is evaluated using the binary executable
+ * path.
+ *
+ * \return the evaluated cache directory.
+ * \see datadir
+ * \see configdir
+ */
+std::string cachedir();
+
+/**
+ * Like cachedir but for WITH_DATADIR.
+ *
+ * \return the evaluated data directory.
+ * \see cachedir
+ * \see datadir
+ */
+std::string datadir();
+
+/**
+ * Like cachedir but for WITH_SYSCONFIGDIR.
+ *
+ * \return the evaluated config directory.
+ * \see cachedir
+ * \see datadir
+ * \note use config_filenames for irccd.conf, irccdctl.conf files
+ */
+std::string sysconfigdir();
+
+
+/**
+ * Construct a list of paths to read configuration files from.
+ *
+ * This function does not test the presence of the files as a condition race
+ * may occur.
+ *
+ * The caller is responsible of opening files for each path.
+ *
+ * \param file the filename to append for convenience
+ * \return the list of paths to check in order
+ */
+std::vector<std::string> config_filenames(std::string file);
+
+/**
+ * Construct a list of paths for reading plugins.
+ *
+ * \param name the plugin id (without extension)
+ * \param extensions the list of extensions supported
+ */
+std::vector<std::string> plugin_filenames(const std::string& name,
+                                          const std::vector<std::string>& extensions);
+
 } // !sys
 
 } // !irccd
--- a/libirccd-js/irccd/mod-directory.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd-js/irccd/mod-directory.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -30,7 +30,6 @@
 #include "fs.hpp"
 #include "mod-directory.hpp"
 #include "mod-irccd.hpp"
-#include "path.hpp"
 #include "plugin-js.hpp"
 #include "sysconfig.hpp"
 
@@ -129,8 +128,6 @@
  */
 duk_ret_t find(duk_context *ctx, std::string base, bool recursive, int patternIndex)
 {
-    base = path::clean(base);
-
     try {
         std::string path;
 
--- a/libirccd-js/irccd/mod-plugin.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd-js/irccd/mod-plugin.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -112,7 +112,7 @@
  * get
  * ------------------------------------------------------------------
  *
- * Get the Irccd.plugin->(config|format) property.
+ * Get the Irccd.plugin->(config|format|paths) property.
  */
 duk_ret_t get(duk_context *ctx, const char *name)
 {
@@ -166,7 +166,29 @@
 }
 
 /*
- * Function: Irccd.plugin->info([name])
+ * setPaths
+ * ------------------------------------------------------------------
+ *
+ * Wrap setter for Irccd.plugin->format property.
+ */
+duk_ret_t setPaths(duk_context *ctx)
+{
+    return set(ctx, JsPlugin::PathsProperty);
+}
+
+/*
+ * getPaths
+ * ------------------------------------------------------------------
+ *
+ * Wrap getter for Irccd.plugin->format property.
+ */
+duk_ret_t getPaths(duk_context *ctx)
+{
+    return get(ctx, JsPlugin::PathsProperty);
+}
+
+/*
+ * Function: Irccd.plugin.info([name])
  * ------------------------------------------------------------------
  *
  * Get information about a plugin->
@@ -337,6 +359,12 @@
     duk_push_c_function(plugin->context(), setFormat, 1);
     duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
 
+    // 'format' property.
+    duk_push_string(plugin->context(), "paths");
+    duk_push_c_function(plugin->context(), getPaths, 0);
+    duk_push_c_function(plugin->context(), setPaths, 1);
+    duk_def_prop(plugin->context(), -4, DUK_DEFPROP_HAVE_GETTER | DUK_DEFPROP_HAVE_SETTER);
+
     duk_put_prop_string(plugin->context(), -2, "Plugin");
     duk_pop(plugin->context());
 }
--- a/libirccd-js/irccd/plugin-js.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd-js/irccd/plugin-js.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -39,6 +39,7 @@
 
 const char JsPlugin::ConfigProperty[] = "\xff""\xff""irccd-plugin-config";
 const char JsPlugin::FormatProperty[] = "\xff""\xff""irccd-plugin-format";
+const char JsPlugin::PathsProperty[] = "\xff""\xff""irccd-plugin-paths";
 
 std::unordered_map<std::string, std::string> JsPlugin::getTable(const char *name) const
 {
@@ -98,35 +99,6 @@
     duk_put_global_string(m_context, "\xff""\xff""path");
 }
 
-void JsPlugin::putPath(const std::string &varname, const std::string &append, path::Path type)
-{
-    StackAssert sa(m_context);
-
-    bool found = true;
-    std::string foundpath;
-
-    // Use the first existing directory available.
-    for (const auto &p : path::list(type)) {
-        boost::system::error_code ec;
-        foundpath = path::clean(p + append);
-
-        if (boost::filesystem::exists(foundpath, ec) && !ec) {
-            found = true;
-            break;
-        }
-    }
-
-    // Use the system as default.
-    if (!found)
-        foundpath = path::clean(path::get(type, path::OwnerSystem) + append);
-
-    duk_get_global_string(m_context, "Irccd");
-    duk_get_prop_string(m_context, -1, "Plugin");
-    dukx_push_std_string(m_context, foundpath);
-    duk_put_prop_string(m_context, -2, varname.c_str());
-    duk_pop_2(m_context);
-}
-
 JsPlugin::JsPlugin(std::string name, std::string path)
     : Plugin(name, path)
 {
@@ -136,6 +108,7 @@
      *
      *   - Irccd.Plugin.config
      *   - Irccd.Plugin.format
+     *   - Irccd.Plugin.paths
      *
      * In mod-plugin.cpp.
      */
@@ -143,6 +116,8 @@
     duk_put_global_string(m_context, ConfigProperty);
     duk_push_object(m_context);
     duk_put_global_string(m_context, FormatProperty);
+    duk_push_object(m_context);
+    duk_put_global_string(m_context, PathsProperty);
 
     // Used by many Javascript APIs.
     duk_push_object(m_context);
@@ -238,15 +213,6 @@
         throw std::runtime_error(std::strerror(errno));
 #endif
 
-    /*
-     * dataPath: DATA + plugin/name (e.g ~/.local/share/irccd/plugins/<name>/)
-     * configPath: CONFIG + plugin/name (e.g ~/.config/irccd/plugin/<name>/)
-     */
-    putVars();
-    putPath("dataPath", "plugin/" + name(), path::PathData);
-    putPath("configPath", "plugin/" + name(), path::PathConfig);
-    putPath("cachePath", "plugin/" + name(), path::PathCache);
-
     // Try to load the file (does not call onLoad yet).
     dukx_peval_file(m_context, path());
     duk_pop(m_context);
@@ -256,8 +222,10 @@
      * calling onLoad to allow the plugin adding configuration to
      * Irccd.Plugin.(config|format) before the user.
      */
+    putVars();
     setConfig(irccd.plugins().config(name()));
     setFormats(irccd.plugins().formats(name()));
+    setPaths(irccd.plugins().paths(name()));
 
     // Read metadata .
     duk_get_global_string(m_context, "info");
@@ -426,7 +394,8 @@
 }
 
 JsPluginLoader::JsPluginLoader(Irccd &irccd) noexcept
-    : m_irccd(irccd)
+    : PluginLoader({}, { ".js" })
+    , m_irccd(irccd)
 {
 }
 
@@ -462,20 +431,4 @@
     return nullptr;
 }
 
-std::shared_ptr<Plugin> JsPluginLoader::find(const std::string &id) noexcept
-{
-    for (const auto &dir : path::list(path::PathPlugins)) {
-        auto path = dir + id + ".js";
-
-        if (!fs::isReadable(path))
-            continue;
-
-        log::info() << "plugin " << id << ": trying " << path << std::endl;
-
-        return open(id, path);
-    }
-
-    return nullptr;
-}
-
 } // !irccd
--- a/libirccd-js/irccd/plugin-js.hpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd-js/irccd/plugin-js.hpp	Thu Jul 20 15:12:41 2017 +0200
@@ -27,7 +27,6 @@
 #include <vector>
 
 #include "duktape.hpp"
-#include "path.hpp"
 #include "plugin.hpp"
 
 namespace irccd {
@@ -50,6 +49,11 @@
      */
     static const char FormatProperty[];
 
+    /**
+     * Global property where paths are defined (object).
+     */
+    static const char PathsProperty[];
+
 private:
     // JavaScript context
     UniqueContext m_context;
@@ -59,7 +63,6 @@
     void putTable(const char *name, const std::unordered_map<std::string, std::string> &vars);
     void call(const std::string &name, unsigned nargs = 0);
     void putVars();
-    void putPath(const std::string &varname, const std::string &append, path::Path type);
 
 public:
     /**
@@ -113,6 +116,22 @@
     }
 
     /**
+     * \copydoc Plugin::paths
+     */
+    PluginPaths paths() override
+    {
+        return getTable(PathsProperty);
+    }
+
+    /**
+     * \copydoc Plugin::set_paths
+     */
+    void setPaths(PluginPaths paths) override
+    {
+        putTable(PathsProperty, std::move(paths));
+    }
+
+    /**
      * \copydoc Plugin::onCommand
      */
     IRCCD_EXPORT void onCommand(Irccd &irccd, const MessageEvent &event) override;
@@ -238,11 +257,6 @@
      */
     std::shared_ptr<Plugin> open(const std::string &id,
                                  const std::string &path) noexcept override;
-
-    /**
-     * \copydoc PluginLoader::find
-     */
-    std::shared_ptr<Plugin> find(const std::string &id) noexcept override;
 };
 
 } // !irccd
--- a/libirccd/CMakeLists.txt	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd/CMakeLists.txt	Thu Jul 20 15:12:41 2017 +0200
@@ -37,6 +37,7 @@
     ${libirccd_SOURCE_DIR}/irccd/command.cpp
     ${libirccd_SOURCE_DIR}/irccd/config.cpp
     ${libirccd_SOURCE_DIR}/irccd/irccd.cpp
+    ${libirccd_SOURCE_DIR}/irccd/plugin.cpp
     ${libirccd_SOURCE_DIR}/irccd/plugin-dynlib.cpp
     ${libirccd_SOURCE_DIR}/irccd/rule.cpp
     ${libirccd_SOURCE_DIR}/irccd/server.cpp
--- a/libirccd/irccd/config.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd/irccd/config.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -18,18 +18,20 @@
 
 #include <cassert>
 
+#include <boost/filesystem.hpp>
+
 #include <format.h>
 
 #include "config.hpp"
 #include "fs.hpp"
 #include "irccd.hpp"
 #include "logger.hpp"
-#include "path.hpp"
 #include "plugin.hpp"
 #include "rule.hpp"
 #include "server.hpp"
 #include "service.hpp"
 #include "sysconfig.hpp"
+#include "system.hpp"
 #include "transport.hpp"
 #include "util.hpp"
 
@@ -100,6 +102,16 @@
     return config;
 }
 
+PluginPaths readPaths(const ini::Section& sc)
+{
+    PluginPaths paths;
+
+    for (const auto& opt : sc)
+        paths.emplace(opt.key(), opt.value());
+
+    return paths;
+}
+
 std::unique_ptr<log::Logger> loadLogFile(const ini::Section &sc)
 {
     /*
@@ -374,16 +386,14 @@
 
 Config Config::find()
 {
-    for (const auto &path : path::list(path::PathConfig)) {
-        std::string fullpath = path + "irccd.conf";
+    for (const auto& path : sys::config_filenames("irccd.conf")) {
+        try {
+            boost::system::error_code ec;
 
-        if (!fs::isReadable(fullpath))
-            continue;
-
-        try {
-            return Config(fullpath);
+            if (boost::filesystem::exists(path, ec) && !ec)
+                return Config(path);
         } catch (const std::exception &ex) {
-            throw std::runtime_error("{}: {}"_format(fullpath, ex.what()));
+            log::warning() << path << ": " << ex.what() << std::endl;
         }
     }
 
@@ -449,6 +459,18 @@
     return formats;
 }
 
+PluginPaths Config::findPluginPaths(const std::string& name) const
+{
+    assert(util::isIdentifierValid(name));
+
+    auto section = m_document.find(std::string("paths.") + name);
+
+    if (section == m_document.end())
+        return PluginPaths();
+
+    return readPaths(*section);
+}
+
 bool Config::isVerbose() const noexcept
 {
     return util::isBoolean(get(m_document, "logs", "verbose"));
@@ -559,15 +581,32 @@
     return servers;
 }
 
+PluginPaths Config::loadPaths() const
+{
+    auto section = m_document.find("paths");
+
+    if (section == m_document.end())
+        return {};
+
+    return readPaths(*section);
+}
+
 void Config::loadPlugins(Irccd &irccd) const
 {
     auto it = m_document.find("plugins");
 
+    irccd.plugins().setPaths(loadPaths());
+
     if (it != m_document.end()) {
         for (const auto &option : *it) {
             if (!util::isIdentifierValid(option.key()))
                 continue;
 
+            auto paths = findPluginPaths(option.key());
+
+            if (!paths.empty())
+                irccd.plugins().setPaths(std::move(paths));
+
             irccd.plugins().setConfig(option.key(), findPluginConfig(option.key()));
             irccd.plugins().setFormats(option.key(), findPluginFormats(option.key()));
             irccd.plugins().load(option.key(), option.value());
--- a/libirccd/irccd/config.hpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd/irccd/config.hpp	Thu Jul 20 15:12:41 2017 +0200
@@ -104,6 +104,14 @@
     IRCCD_EXPORT PluginFormats findPluginFormats(const std::string &name) const;
 
     /**
+     * Find plugin paths if defined.
+     *
+     * \pre util::isValidIdentifier(name)
+     * \param name the plugin name
+     */
+    IRCCD_EXPORT PluginPaths findPluginPaths(const std::string& name) const;
+
+    /**
      * Get the path to the pidfile.
      *
      * \return the path or empty if not defined
@@ -170,6 +178,13 @@
     IRCCD_EXPORT std::vector<std::shared_ptr<Server>> loadServers() const;
 
     /**
+     * Load default paths for plugins.
+     *
+     * \return the map of paths
+     */
+    IRCCD_EXPORT PluginPaths loadPaths() const;
+
+    /**
      * Get the list of defined plugins.
      *
      * \param irccd the irccd instance
--- a/libirccd/irccd/plugin-dynlib.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd/irccd/plugin-dynlib.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -18,7 +18,6 @@
 
 #include "fs.hpp"
 #include "logger.hpp"
-#include "path.hpp"
 #include "plugin-dynlib.hpp"
 
 namespace irccd {
@@ -176,34 +175,16 @@
     call(m_onWhois, irccd, ev);
 }
 
-std::shared_ptr<Plugin> DynlibPluginLoader::open(const std::string &id,
-                                                 const std::string &path) noexcept
+std::shared_ptr<Plugin> DynlibPluginLoader::open(const std::string &,
+                                                 const std::string &) noexcept
 {
-    if (path.rfind(DYNLIB_SUFFIX) == std::string::npos)
-        return nullptr;
-
-    try {
-        return std::make_shared<DynlibPlugin>(id, path);
-    } catch (const std::exception &ex) {
-        log::warning() << "plugin " << id << ": " << ex.what() << std::endl;
-    }
-
+    // TODO: dynlib plugins are unsupported for now.
     return nullptr;
 }
 
-std::shared_ptr<Plugin> DynlibPluginLoader::find(const std::string &id) noexcept
+std::shared_ptr<Plugin> DynlibPluginLoader::find(const std::string &) noexcept
 {
-    for (const auto &dir : path::list(path::PathNativePlugins)) {
-        auto path = dir + id + DYNLIB_SUFFIX;
-
-        if (!fs::isReadable(path))
-            continue;
-
-        log::info() << "plugin " << id << ": trying " << path << std::endl;
-
-        return open(id, path);
-    }
-
+    // TODO: dynlib plugins are unsupported for now.
     return nullptr;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/libirccd/irccd/plugin.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -0,0 +1,71 @@
+/*
+ * plugin.cpp -- irccd JavaScript plugin interface
+ *
+ * Copyright (c) 2013-2017 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 <boost/filesystem.hpp>
+
+#include "plugin.hpp"
+#include "system.hpp"
+
+namespace fs = boost::filesystem;
+
+namespace irccd {
+
+namespace {
+
+} // !namespace
+
+PluginLoader::PluginLoader(std::vector<std::string> directories,
+                           std::vector<std::string> extensions)
+    : directories_(std::move(directories))
+    , extensions_(std::move(extensions))
+{
+}
+
+std::shared_ptr<Plugin> PluginLoader::find(const std::string& name) noexcept
+{
+    if (extensions_.empty())
+        return nullptr;
+
+    std::vector<std::string> filenames;
+
+    if (directories_.empty())
+        filenames = sys::plugin_filenames(name, extensions_);
+    else {
+        for (const auto& dir : directories_)
+            for (const auto& ext : extensions_)
+                filenames.push_back(dir + "/" + name + ext);
+    }
+
+    std::shared_ptr<Plugin> plugin;
+
+    for (const auto& candidate : filenames) {
+        boost::system::error_code ec;
+
+        if (!boost::filesystem::exists(candidate, ec) || ec)
+            continue;
+
+        plugin = open(name, candidate);
+
+        if (plugin)
+            break;
+    }
+
+    return plugin;
+}
+
+} // !irccd
--- a/libirccd/irccd/plugin.hpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd/irccd/plugin.hpp	Thu Jul 20 15:12:41 2017 +0200
@@ -53,6 +53,11 @@
 using PluginFormats = std::unordered_map<std::string, std::string>;
 
 /**
+ * \brief Paths for plugins.
+ */
+using PluginPaths = std::unordered_map<std::string, std::string>;
+
+/**
  * \ingroup plugins
  * \brief Abstract plugin.
  *
@@ -231,6 +236,26 @@
     }
 
     /**
+     * Access the plugin paths.
+     *
+     * \return the paths
+     */
+    virtual PluginPaths paths()
+    {
+        return {};
+    }
+
+    /**
+     * Set the paths.
+     *
+     * \param paths the paths
+     */
+    virtual void setPaths(PluginPaths paths)
+    {
+        util::unused(paths);
+    }
+
+    /**
      * On channel message. This event will call onMessage or
      * onCommand if the messages starts with the command character
      * plus the plugin name.
@@ -474,8 +499,47 @@
  * \see JsPluginLoader
  */
 class PluginLoader {
+private:
+    std::vector<std::string> directories_;
+    std::vector<std::string> extensions_;
+
 public:
     /**
+     * Construct the loader with a predefined set of directories and extensions.
+     *
+     * If directories is not specified, a sensible default list of system and
+     * user paths are searched.
+     *
+     * If extensions is empty, default find function implementation does
+     * nothing.
+     *
+     * \param directories directories to search
+     * \param extensions the list of extensions supported
+     */
+    PluginLoader(std::vector<std::string> directories = {},
+                 std::vector<std::string> extensions = {});
+
+    /**
+     * Set directories where to search plugins.
+     *
+     * \param dirs the directories
+     */
+    inline void set_directories(std::vector<std::string> dirs)
+    {
+        directories_ = std::move(dirs);
+    }
+
+    /**
+     * Set supported extensions for this loader.
+     *
+     * \param extensions the extensions (with the dot)
+     */
+    inline void set_extensions(std::vector<std::string> extensions)
+    {
+        extensions_ = std::move(extensions);
+    }
+
+    /**
      * Try to open the plugin specified by path.
      *
      * The implementation must test if the plugin is suitable for opening, by
@@ -492,7 +556,7 @@
      * \param id the plugin id
      * \return the plugin
      */
-    virtual std::shared_ptr<Plugin> find(const std::string &id) noexcept = 0;
+    virtual std::shared_ptr<Plugin> find(const std::string &id) noexcept;
 };
 
 } // !irccd
--- a/libirccd/irccd/service.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd/irccd/service.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -26,9 +26,11 @@
 #include "irccd.hpp"
 #include "logger.hpp"
 #include "service.hpp"
+#include "system.hpp"
 #include "transport.hpp"
 
 using namespace fmt::literals;
+using namespace std::string_literals;
 
 namespace irccd {
 
@@ -125,6 +127,9 @@
 PluginService::PluginService(Irccd &irccd) noexcept
     : m_irccd(irccd)
 {
+    m_default_paths.emplace("cache", sys::cachedir());
+    m_default_paths.emplace("data", sys::datadir());
+    m_default_paths.emplace("config", sys::sysconfigdir());
 }
 
 PluginService::~PluginService()
@@ -202,6 +207,40 @@
     return PluginFormats();
 }
 
+const PluginPaths& PluginService::paths() const noexcept
+{
+    return m_default_paths;
+}
+
+PluginPaths PluginService::paths(const std::string& name) const
+{
+    auto result = m_default_paths;
+    auto overriden = m_paths.find(name);
+
+    // For all default paths, append the plugin name.
+    for (auto& pair : result)
+        pair.second += "/plugin/"s + name;
+
+    // Now, mere overriden paths.
+    if (overriden != m_paths.end())
+        for (const auto& pair : overriden->second)
+            result[pair.first] = pair.second;
+
+    return result;
+}
+
+void PluginService::setPaths(PluginPaths paths)
+{
+    // If the paths is empty or not complete, do not erase default items.
+    for (const auto& pair : paths)
+        m_default_paths[pair.first] = pair.second;
+}
+
+void PluginService::setPaths(const std::string& name, PluginPaths paths)
+{
+    m_paths.emplace(name, std::move(paths));
+}
+
 std::shared_ptr<Plugin> PluginService::open(const std::string &id,
                                             const std::string &path)
 {
@@ -243,6 +282,7 @@
         if (plugin) {
             plugin->setConfig(m_config[name]);
             plugin->setFormats(m_formats[name]);
+            plugin->setPaths(paths(name));
             plugin->onLoad(m_irccd);
 
             add(std::move(plugin));
--- a/libirccd/irccd/service.hpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/libirccd/irccd/service.hpp	Thu Jul 20 15:12:41 2017 +0200
@@ -138,10 +138,12 @@
 class PluginService {
 private:
     Irccd &m_irccd;
+    PluginPaths m_default_paths;
     std::vector<std::shared_ptr<Plugin>> m_plugins;
     std::vector<std::unique_ptr<PluginLoader>> m_loaders;
     std::unordered_map<std::string, PluginConfig> m_config;
     std::unordered_map<std::string, PluginFormats> m_formats;
+    std::unordered_map<std::string, PluginPaths> m_paths;
 
 public:
     /**
@@ -242,6 +244,36 @@
     IRCCD_EXPORT PluginFormats formats(const std::string &name) const;
 
     /**
+     * Get the default paths for plugins.
+     *
+     * \return the paths
+     */
+    IRCCD_EXPORT const PluginPaths& paths() const noexcept;
+
+    /**
+     * Get the paths for the specified plugin.
+     *
+     * \param name the plugin
+     * \return the paths
+     */
+    IRCCD_EXPORT PluginPaths paths(const std::string& name) const;
+
+    /**
+     * Set default paths.
+     *
+     * \param paths the default paths (for all plugins)
+     */
+    IRCCD_EXPORT void setPaths(PluginPaths paths);
+
+    /**
+     * Override paths for the specified plugin.
+     *
+     * \param name the plugin name
+     * \param paths the paths
+     */
+    void setPaths(const std::string& name, PluginPaths paths);
+
+    /**
      * Generic function for opening the plugin at the given path.
      *
      * This function will search for every PluginLoader and call open() on it,
--- a/plugins/ask/ask.js	Thu Jul 27 16:44:57 2017 +0200
+++ b/plugins/ask/ask.js	Thu Jul 20 15:12:41 2017 +0200
@@ -43,7 +43,7 @@
         if (Plugin.config["file"])
             path = Plugin.config["file"];
         else
-            path = Plugin.configPath + "answers.conf";
+            path = Plugin.paths.config + "/answers.conf";
 
         var file = new File(path, "r");
         var line;
--- a/plugins/hangman/hangman.js	Thu Jul 27 16:44:57 2017 +0200
+++ b/plugins/hangman/hangman.js	Thu Jul 20 15:12:41 2017 +0200
@@ -132,7 +132,7 @@
     if (Plugin.config["file"])
         path = Plugin.config["file"];
     else
-        path = Plugin.configPath + "words.conf";
+        path = Plugin.paths.config + "/words.conf";
 
     try {
         Logger.info("loading words...");
@@ -289,6 +289,7 @@
 
 function onLoad()
 {
+    Logger.warning("TAMERE");
     Hangman.loadFormats();
     Hangman.loadWords();
 }
--- a/plugins/history/history.js	Thu Jul 27 16:44:57 2017 +0200
+++ b/plugins/history/history.js	Thu Jul 20 15:12:41 2017 +0200
@@ -60,7 +60,7 @@
             "channel":    channel
         });
     } else
-        p = Plugin.cachePath + "db.json";
+        p = Plugin.paths.cache + "/db.json";
 
     return p;
 }
--- a/tests/CMakeLists.txt	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/CMakeLists.txt	Thu Jul 20 15:12:41 2017 +0200
@@ -53,10 +53,12 @@
     # Misc
     add_subdirectory(elapsedtimer)
     add_subdirectory(logger)
-    add_subdirectory(path)
     add_subdirectory(rules)
     add_subdirectory(util)
 
+    # Services
+    add_subdirectory(service-plugin)
+
     # JS API
     if (WITH_JS)
         add_subdirectory(js)
--- a/tests/js-timer/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/js-timer/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -103,7 +103,6 @@
 {
     // Needed for some components.
     sys::setProgramName("irccd");
-    path::setApplicationPath(argv[0]);
     log::setLogger(std::make_unique<log::SilentLogger>());
     log::setVerbose(true);
     testing::InitGoogleTest(&argc, argv);
--- a/tests/path/CMakeLists.txt	Thu Jul 27 16:44:57 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,23 +0,0 @@
-#
-# CMakeLists.txt -- CMake build system for irccd
-#
-# Copyright (c) 2013-2017 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.
-#
-
-irccd_define_test(
-    NAME path
-    SOURCES main.cpp
-    LIBRARIES libirccd
-)
--- a/tests/path/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,145 +0,0 @@
-/*
- * main.cpp -- test path functions
- *
- * Copyright (c) 2013-2017 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 <gtest/gtest.h>
-
-#include <irccd/sysconfig.hpp>
-#include <irccd/logger.hpp>
-#include <irccd/path.hpp>
-
-namespace irccd {
-
-/* --------------------------------------------------------
- * Back slashes
- * -------------------------------------------------------- */
-
-#if defined(IRCCD_SYSTEM_WINDOWS)
-
-TEST(Back, nochange)
-{
-    std::string path = "\\usr\\local\\etc\\";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ(path, result);
-}
-
-TEST(Back, duplicateBegin)
-{
-    std::string path = "\\\\usr\\local\\etc\\";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("\\usr\\local\\etc\\", result);
-}
-
-TEST(Back, duplicateEnd)
-{
-    std::string path = "\\usr\\local\\etc\\\\";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("\\usr\\local\\etc\\", result);
-}
-
-TEST(Back, duplicateEverywhere)
-{
-    std::string path = "\\\\usr\\\\local\\\\etc\\\\";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("\\usr\\local\\etc\\", result);
-}
-
-TEST(Back, missingTrailing)
-{
-    std::string path = "\\usr\\local\\etc";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("\\usr\\local\\etc\\", result);
-}
-
-#else
-
-/* --------------------------------------------------------
- * Forward slashes
- * -------------------------------------------------------- */
-
-TEST(Forward, nochange)
-{
-    std::string path = "/usr/local/etc/";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ(path, result);
-}
-
-TEST(Forward, duplicateBegin)
-{
-    std::string path = "//usr/local/etc/";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("/usr/local/etc/", result);
-}
-
-TEST(Forward, duplicateEnd)
-{
-    std::string path = "/usr/local/etc//";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("/usr/local/etc/", result);
-}
-
-TEST(Forward, duplicateEverywhere)
-{
-    std::string path = "//usr//local//etc//";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("/usr/local/etc/", result);
-}
-
-TEST(Forward, missingTrailing)
-{
-    std::string path = "/usr/local/etc";
-    std::string result = path::clean(path);
-
-    ASSERT_EQ("/usr/local/etc/", result);
-}
-
-#endif
-
-} // !irccd
-
-using namespace irccd;
-
-int main(int argc, char **argv)
-{
-    /*
-     * Just show everything for test purpose.
-     */
-    path::setApplicationPath(argv[0]);
-    log::debug() << "System paths:" << std::endl;
-    log::debug() << "  config(system):  " << path::get(path::PathConfig, path::OwnerSystem) << std::endl;
-    log::debug() << "  data(system):    " << path::get(path::PathData, path::OwnerSystem) << std::endl;
-    log::debug() << "  plugins(system): " << path::get(path::PathPlugins, path::OwnerSystem) << std::endl;
-    log::debug() << "  cache(system):   " << path::get(path::PathCache, path::OwnerSystem) << std::endl;
-    log::debug() << "User paths:" << std::endl;
-    log::debug() << "  config(user):    " << path::get(path::PathConfig, path::OwnerUser) << std::endl;
-    log::debug() << "  data(user):      " << path::get(path::PathData, path::OwnerUser) << std::endl;
-    log::debug() << "  plugins(user):   " << path::get(path::PathPlugins, path::OwnerUser) << std::endl;
-    log::debug() << "  cache(user):     " << path::get(path::PathCache, path::OwnerUser) << std::endl;
-
-    testing::InitGoogleTest(&argc, argv);
-
-    return RUN_ALL_TESTS();
-}
--- a/tests/plugin-ask/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/plugin-ask/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -21,7 +21,6 @@
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
-#include <irccd/path.hpp>
 
 #include "plugin-tester.hpp"
 
@@ -87,7 +86,6 @@
 
 int main(int argc, char **argv)
 {
-    path::setApplicationPath(argv[0]);
     testing::InitGoogleTest(&argc, argv);
 
     return RUN_ALL_TESTS();
--- a/tests/plugin-auth/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/plugin-auth/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -21,7 +21,6 @@
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
-#include <irccd/path.hpp>
 
 #include "plugin-tester.hpp"
 
@@ -99,7 +98,6 @@
 
 int main(int argc, char **argv)
 {
-    path::setApplicationPath(argv[0]);
     testing::InitGoogleTest(&argc, argv);
 
     return RUN_ALL_TESTS();
--- a/tests/plugin-hangman/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/plugin-hangman/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -24,7 +24,6 @@
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
-#include <irccd/path.hpp>
 
 #include "plugin-tester.hpp"
 
@@ -293,7 +292,6 @@
 
 int main(int argc, char **argv)
 {
-    path::setApplicationPath(argv[0]);
     testing::InitGoogleTest(&argc, argv);
 
     return RUN_ALL_TESTS();
--- a/tests/plugin-history/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/plugin-history/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -23,7 +23,6 @@
 #include <irccd/irccd.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
-#include <irccd/path.hpp>
 
 #include "plugin-tester.hpp"
 
@@ -141,7 +140,6 @@
 
 int main(int argc, char **argv)
 {
-    path::setApplicationPath(argv[0]);
     testing::InitGoogleTest(&argc, argv);
 
     return RUN_ALL_TESTS();
--- a/tests/plugin-logger/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/plugin-logger/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -25,7 +25,6 @@
 #include <irccd/logger.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
-#include <irccd/path.hpp>
 
 #include "plugin-tester.hpp"
 
@@ -193,7 +192,6 @@
 
 int main(int argc, char **argv)
 {
-    path::setApplicationPath(argv[0]);
     testing::InitGoogleTest(&argc, argv);
     log::setLogger(std::make_unique<log::SilentLogger>());
 
--- a/tests/plugin-plugin/main.cpp	Thu Jul 27 16:44:57 2017 +0200
+++ b/tests/plugin-plugin/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -24,7 +24,6 @@
 #include <irccd/logger.hpp>
 #include <irccd/server.hpp>
 #include <irccd/service.hpp>
-#include <irccd/path.hpp>
 
 #include "plugin-tester.hpp"
 
@@ -124,7 +123,6 @@
 
 int main(int argc, char **argv)
 {
-    path::setApplicationPath(argv[0]);
     testing::InitGoogleTest(&argc, argv);
     log::setLogger(std::make_unique<log::SilentLogger>());
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/service-plugin/CMakeLists.txt	Thu Jul 20 15:12:41 2017 +0200
@@ -0,0 +1,24 @@
+#
+# CMakeLists.txt -- CMake build system for irccd
+#
+# Copyright (c) 2013-2017 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.
+#
+
+irccd_define_test(
+    NAME service-plugin
+    SOURCES main.cpp
+    LIBRARIES libirccd
+)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/service-plugin/main.cpp	Thu Jul 20 15:12:41 2017 +0200
@@ -0,0 +1,153 @@
+/*
+ * main.cpp -- test irccd rules
+ *
+ * Copyright (c) 2013-2017 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 <gtest/gtest.h>
+
+#include <irccd/irccd.hpp>
+#include <irccd/service.hpp>
+
+namespace irccd {
+
+TEST(service_plugin, default_paths)
+{
+    Irccd irccd;
+
+    irccd.plugins().setPaths({
+        { "cache",  "/var/cache/irccd"          },
+        { "config", "/etc/irccd"                },
+        { "data",   "/usr/local/share/irccd"    }
+    });
+
+    auto paths = irccd.plugins().paths("ask");
+
+    ASSERT_EQ("/var/cache/irccd/plugin/ask", paths["cache"]);
+    ASSERT_EQ("/etc/irccd/plugin/ask", paths["config"]);
+    ASSERT_EQ("/usr/local/share/irccd/plugin/ask", paths["data"]);
+}
+
+TEST(service_plugin, override_cache)
+{
+    Irccd irccd;
+
+    irccd.plugins().setPaths({
+        { "cache",  "/var/cache/irccd"          },
+        { "config", "/etc/irccd"                },
+        { "data",   "/usr/local/share/irccd"    }
+    });
+    irccd.plugins().setPaths("ask", {
+        { "cache",  "/opt/cache/ask"            }
+    });
+
+    auto paths = irccd.plugins().paths("ask");
+
+    ASSERT_EQ("/opt/cache/ask", paths["cache"]);
+    ASSERT_EQ("/etc/irccd/plugin/ask", paths["config"]);
+    ASSERT_EQ("/usr/local/share/irccd/plugin/ask", paths["data"]);
+}
+
+TEST(service_plugin, override_config)
+{
+    Irccd irccd;
+
+    irccd.plugins().setPaths({
+        { "cache",  "/var/cache/irccd"          },
+        { "config", "/etc/irccd"                },
+        { "data",   "/usr/local/share/irccd"    }
+    });
+    irccd.plugins().setPaths("ask", {
+        { "config", "/opt/config/ask"           }
+    });
+
+    auto paths = irccd.plugins().paths("ask");
+
+    ASSERT_EQ("/var/cache/irccd/plugin/ask", paths["cache"]);
+    ASSERT_EQ("/opt/config/ask", paths["config"]);
+    ASSERT_EQ("/usr/local/share/irccd/plugin/ask", paths["data"]);
+}
+
+TEST(service_plugin, override_data)
+{
+    Irccd irccd;
+
+    irccd.plugins().setPaths({
+        { "cache",  "/var/cache/irccd"          },
+        { "config", "/etc/irccd"                },
+        { "data",   "/usr/local/share/irccd"    }
+    });
+    irccd.plugins().setPaths("ask", {
+        { "data",   "/opt/data/ask"             }
+    });
+
+    auto paths = irccd.plugins().paths("ask");
+
+    ASSERT_EQ("/var/cache/irccd/plugin/ask", paths["cache"]);
+    ASSERT_EQ("/etc/irccd/plugin/ask", paths["config"]);
+    ASSERT_EQ("/opt/data/ask", paths["data"]);
+}
+
+TEST(service_plugin, override_all)
+{
+    Irccd irccd;
+
+    irccd.plugins().setPaths({
+        { "cache",  "/var/cache/irccd"          },
+        { "config", "/etc/irccd"                },
+        { "data",   "/usr/local/share/irccd"    }
+    });
+    irccd.plugins().setPaths("ask", {
+        { "cache",  "/opt/cache/ask"            },
+        { "config", "/opt/config/ask"           },
+        { "data",   "/opt/data/ask"             }
+    });
+
+    auto paths = irccd.plugins().paths("ask");
+
+    ASSERT_EQ("/opt/cache/ask", paths["cache"]);
+    ASSERT_EQ("/opt/config/ask", paths["config"]);
+    ASSERT_EQ("/opt/data/ask", paths["data"]);
+}
+
+TEST(service_plugin, extra_paths)
+{
+    Irccd irccd;
+
+    irccd.plugins().setPaths({
+        { "cache",  "/var/cache/irccd"          },
+        { "config", "/etc/irccd"                },
+        { "data",   "/usr/local/share/irccd"    }
+    });
+    irccd.plugins().setPaths("ask", {
+        { "extra",  "/opt/magic"                }
+    });
+
+    auto paths = irccd.plugins().paths("ask");
+
+    ASSERT_EQ("/var/cache/irccd/plugin/ask", paths["cache"]);
+    ASSERT_EQ("/etc/irccd/plugin/ask", paths["config"]);
+    ASSERT_EQ("/usr/local/share/irccd/plugin/ask", paths["data"]);
+    ASSERT_EQ("/opt/magic", paths["extra"]);
+}
+
+} // !irccd
+
+int main(int argc, char **argv)
+{
+    testing::InitGoogleTest(&argc, argv);
+
+    return RUN_ALL_TESTS();
+}