view libcommon/irccd/system.cpp @ 553:c135001322a1

Irccd: remove post function, closes #733
author David Demelier <markand@malikania.fr>
date Fri, 24 Nov 2017 20:24:55 +0100
parents e03521cf207b
children 2c3122f23a04
line wrap: on
line source

/*
 * system.cpp -- platform dependent functions for system inspection
 *
 * 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 <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_LINUX)
#   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

#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 <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

// For sys::username
#if defined(HAVE_GETLOGIN)
#   include <unistd.h>
#endif

#include "logger.hpp"
#include "system.hpp"
#include "string_util.hpp"
#include "xdg.hpp"

namespace irccd {

namespace sys {

namespace {

/*
 * 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 program_name_value;

/*
 * set_privileges.
 * ------------------------------------------------------------------
 *
 * 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 set_privileges(const std::string& type_name,
                    const std::string& value,
                    LookupFunc lookup,
                    SetterFunc setter,
                    FieldGetter getter)
{
    IntType id = 0;

    if (string_util::is_int(value))
        id = std::stoi(value);
    else {
        auto info = lookup(value.c_str());

        if (info == nullptr) {
            log::warning() << "irccd: invalid " << type_name << ": " << std::strerror(errno) << std::endl;
            return;
        } else {
            id = getter(info);
            log::debug() << "irccd: " << type_name << " " << value << " resolved to: " << id << std::endl;
        }
    }

    if (setter(id) < 0)
        log::warning() << "irccd: could not set " << type_name << ": " << std::strerror(errno) << std::endl;
    else
        log::info() << "irccd: setting " << type_name << " to " << value << std::endl;
}

/*
 * executable_path
 * ------------------------------------------------------------------
 *
 * Get the executable path.
 *
 * Example:
 *
 * /usr/local/bin/irccd -> /usr/local/bin
 */
std::string executable_path()
{
    std::string result;

#if defined(IRCCD_SYSTEM_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(IRCCD_SYSTEM_FREEBSD) || defined(IRCCD_SYSTEM_DRAGONFLYBSD)
    int mib[] = { CTL_KERN, KERN_PROC, KERN_PROC_PATHNAME, -1 };
    char path[PATH_MAX + 1] = {0};
    size_t size = PATH_MAX;

    if (sysctl(mib, 4, path, &size, nullptr, 0) < 0)
        throw std::runtime_error(std::strerror(errno));

    result = path;
#elif defined(IRCCD_SYSTEM_MAC)
    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(IRCCD_SYSTEM_WINDOWS)
    char path[PATH_MAX + 1] = {0};

    if (GetModuleFileNameA(nullptr, path, sizeof (path) - 1) == 0)
        throw std::runtime_error("GetModuleFileName error");

    result = path;
#elif defined(IRCCD_SYSTEM_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(IRCCD_SYSTEM_OPENBSD)
    char **paths, path[PATH_MAX + 1] = {0};
    int mib[] = { CTL_KERN, KERN_PROC_ARGS, getpid(), KERN_PROC_ARGV };
    size_t length = 0;

    if (sysctl(mib, 4, 0, &length, 0, 0) < 0)
        throw std::runtime_error(std::strerror(errno));
    if (!(paths = static_cast<char**>(std::malloc(length))))
        throw std::runtime_error(std::strerror(errno));
    if (sysctl(mib, 4, paths, &length, 0, 0) < 0) {
        std::free(paths);
        throw std::runtime_error(std::strerror(errno));
    }

    realpath(paths[0], path);
    result = path;

    std::free(paths);
#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;

#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

void set_program_name(std::string name) noexcept
{
    program_name_value = std::move(name);

#if defined(HAVE_SETPROGNAME)
    setprogname(program_name_value.c_str());
#endif
}

const std::string& program_name() noexcept
{
    return program_name_value;
}

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_DRAGONFLYBSD)
    return "DragonFlyBSD";
#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 set_uid(const std::string& value)
{
    set_privileges<uid_t>("uid", value, &getpwnam, &setuid, [] (auto pw) {
        return pw->pw_uid;
    });
}

#endif

#if defined(HAVE_SETGID)

void set_gid(const std::string& value)
{
    set_privileges<gid_t>("gid", value, &getgrnam, &setgid, [] (auto gr) {
        return gr->gr_gid;
    });
}

#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::string username()
{
#if defined(HAVE_GETLOGIN)
    auto v = getlogin();

    if (v)
        return v;
#endif

    return "";
}

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