/*
 * $Id: dbus.c 241 2017-04-19 07:53:25Z wimalopaan $
 *
 *  scanbd - KMUX scanner button daemon
 *
 *  Copyright (C) 2008 - 2017 Wilhelm Meier (wilhelm.wm.meier@googlemail.com)
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 */

#include "scanbd.h"
#include "scanbd_dbus.h"
#include "scanbuttond_wrapper.h"

static DBusConnection* conn = NULL;
static pthread_t dbus_tid = 0;

#ifdef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
// this is non-portable
static pthread_mutex_t dbus_mutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
#else
static pthread_mutex_t dbus_mutex;
#endif

void dbus_send_signal_argv(const char* signal_name, char** argv) {
    // TODO: we need a better dbus mainloop integrations, so that we
    // can wakeup the main thread to sendout the messages we generate here

    DBusMessage* signal = NULL;

    if (conn == NULL) {
        if (!dbus_init()) {
            return;
        }
    }
    assert(conn);

    if (!conn) {
        slog(SLOG_DEBUG, "No dbus connection");
        return;
    }

    assert(signal_name != NULL);
    if ((signal = dbus_message_new_signal(SCANBD_DBUS_OBJECTPATH,
                                          SCANBD_DBUS_INTERFACE,
                                          signal_name)) == NULL) {
        slog(SLOG_ERROR, "Can't create signal");
        return;
    }

    if (argv != NULL) {
#if ((__STDC_VERSION__  - 0) < 201112L) || ((__GNUC__ - 0) < 5)
        DBusMessageIter args;
        DBusMessageIter sub;
#else
        DBusMessageIter args = {};
        DBusMessageIter sub = {};
#endif
        dbus_message_iter_init_append(signal, &args);
        if (dbus_message_iter_open_container(&args, DBUS_TYPE_ARRAY,
                                             DBUS_TYPE_STRING_AS_STRING,
                                             &sub) != TRUE) {
            slog(SLOG_ERROR, "Can't initialize dbus container");
        }
        while(*argv != NULL) {
            slog(SLOG_DEBUG, "append string %s to signal %s", *argv, signal_name);
            if (dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, argv) != TRUE) {
                slog(SLOG_ERROR, "Can't append signal argument");
            }
            argv++;
        }
        if (dbus_message_iter_close_container(&args, &sub) != TRUE) {
            slog(SLOG_ERROR, "Can't close dbus container");
        }
    }

    slog(SLOG_DEBUG, "now sending signal %s", signal_name);
    dbus_uint32_t serial = 0;
    if (dbus_connection_send(conn, signal, &serial) != TRUE) {
        slog(SLOG_ERROR, "Can't send signal");
    }
    slog(SLOG_DEBUG, "now flushing the dbus");
    // TODO: with the standard-main loop this would block without
    // using a timeout
    dbus_connection_flush(conn);
    slog(SLOG_DEBUG, "unref the signal");
    dbus_message_unref(signal);
}

void dbus_send_signal(const char* signal_name, const char* arg) {
    DBusMessage* signal = NULL;

    if (conn == NULL) {
        if (!dbus_init()) {
            return;
        }
    }
    assert(conn);

    if (!conn) {
        slog(SLOG_DEBUG, "No dbus connection");
        return;
    }

    assert(signal_name != NULL);
    if ((signal = dbus_message_new_signal(SCANBD_DBUS_OBJECTPATH,
                                          SCANBD_DBUS_INTERFACE,
                                          signal_name)) == NULL) {
        slog(SLOG_ERROR, "Can't create signal");
        return;
    }

    if (arg != NULL) {
#if ((__STDC_VERSION__  - 0) < 201112L) || ((__GNUC__ - 0) < 5)
        DBusMessageIter args;
#else
        DBusMessageIter args = {};
#endif
        dbus_message_iter_init_append(signal, &args);
        slog(SLOG_DEBUG, "append string %s to signal %s", arg, signal_name);
        if (dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, &arg) != TRUE) {
            slog(SLOG_ERROR, "Can't append signal argument");
        }
    }

    dbus_uint32_t serial = 0;
    slog(SLOG_DEBUG, "now sending signal %s", signal_name);
    if (dbus_connection_send(conn, signal, &serial) != TRUE) {
        slog(SLOG_ERROR, "Can't send signal");
    }
    dbus_connection_flush(conn);
    dbus_message_unref(signal);
}

static void hook_device_ex(const char *param, const char *action_name, const char *dev_name) {
    slog(SLOG_DEBUG, "hook_device_ex");
    assert(dev_name);
    assert(param);
    assert(action_name);

    slog(SLOG_DEBUG, "hook_device_ex: parameter: %s", param);
    slog(SLOG_DEBUG, "hook_device_ex: action: %s", action_name);
    slog(SLOG_DEBUG, "hook_device_ex: device: %s", dev_name);

    cfg_t* cfg_sec_global = cfg_getsec(cfg, C_GLOBAL);
    assert(cfg_sec_global);
    const char* script = cfg_getstr(cfg_sec_global, param);

    if (!script || (strlen(script) == 0)) {
        slog(SLOG_INFO, "No hook script for inserting device: %s", dev_name);
        //script = SCANBD_NULL_STRING;
        return; // No hook script, nothing for us to do here.
    }
    slog(SLOG_INFO, "Using hook script %s for inserting device: %s", script, dev_name);

    cfg_t* global_envs = cfg_getsec(cfg_sec_global, C_ENVIRONMENT);
    // number of env-vars =
    // the values in the environment-section (2):
    // device, action
    // plus those 4:
    // PATH, PWD, USER, HOME
    // plus the sentinel
    const int number_of_envs = 4 + 2 + 1;
    char** env = calloc(number_of_envs, sizeof(char*));
    for(int e = 0; e < number_of_envs; e += 1) {
        env[e] = calloc(NAME_MAX + 1, sizeof(char));
    }
    int e = 0;
    const char* ev = "PATH";
    if (getenv(ev) != NULL) {
        snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev));
        slog(SLOG_DEBUG, "setting env: %s", env[e]);
        e += 1;
    }
    else {
        snprintf(env[e], NAME_MAX, "%s=%s", ev, "/usr/sbin:/usr/bin:/sbin:/bin");
        slog(SLOG_DEBUG, "No PATH, setting env: %s", env[e]);
        e += 1;
    }
    ev = "PWD";
    if (getenv(ev) != NULL) {
        snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev));
        slog(SLOG_DEBUG, "setting env: %s", env[e]);
        e += 1;
    }
    else {
        char buf[PATH_MAX];
        char* ptr = getcwd(buf, PATH_MAX - 1);
        if (!ptr) {
            slog(SLOG_ERROR, "can't get pwd");
        }
        else {
            assert(ptr);
            snprintf(env[e], NAME_MAX, "%s=%s", ev, ptr);
            slog(SLOG_DEBUG, "No PWD, setting env: %s", env[e]);
            e += 1;
        }
    }
    ev = "USER";
    if (getenv(ev) != NULL) {
        snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev));
        slog(SLOG_DEBUG, "setting env: %s", env[e]);
        e += 1;
    }
    else {
        struct passwd* pwd = NULL;
        pwd = getpwuid(geteuid());
        assert(pwd);
        snprintf(env[e], NAME_MAX, "%s=%s", ev, pwd->pw_name);
        slog(SLOG_DEBUG, "No USER, setting env: %s", env[e]);
        e += 1;
    }
    ev = "HOME";
    if (getenv(ev) != NULL) {
        snprintf(env[e], NAME_MAX, "%s=%s", ev, getenv(ev));
        slog(SLOG_DEBUG, "setting env: %s", env[e]);
        e += 1;
    }
    else {
        struct passwd* pwd = 0;
        pwd = getpwuid(geteuid());
        assert(pwd);
        snprintf(env[e], NAME_MAX, "%s=%s", ev, pwd->pw_dir);
        slog(SLOG_DEBUG, "No HOME, setting env: %s", env[e]);
        e += 1;
    }
    ev = cfg_getstr(global_envs, C_DEVICE);
    if (ev != NULL) {
        snprintf(env[e], NAME_MAX, "%s=%s", ev, dev_name);
        slog(SLOG_DEBUG, "setting env: %s", env[e]);
        e += 1;
    }
    ev = cfg_getstr(global_envs, C_ACTION);
    if (ev != NULL) {
        snprintf(env[e], NAME_MAX, "%s=%s", ev, action_name);
        slog(SLOG_DEBUG, "setting env: %s", env[e]);
        e += 1;
    }
    env[e] = NULL;
    assert(e == number_of_envs-1);

    char *script_abs = make_script_path_abs(script);
    assert(script_abs);
    slog(SLOG_DEBUG, "Hook script path: %s for inserting device: %s", script_abs, dev_name);

    if (strcmp(script_abs, SCANBD_NULL_STRING) != 0) {
        pid_t cpid = -1;
        if ((cpid = fork()) < 0) {
            slog(SLOG_ERROR, "Can't fork: %s", strerror(errno));
        }
        else if (cpid > 0) { // parent
            slog(SLOG_INFO, "waiting for child: %s", script_abs);
            int status;
            if (waitpid(cpid, &status, 0) < 0) {
                slog(SLOG_ERROR, "waitpid: %s", strerror(errno));
            }
            if (WIFEXITED(status)) {
                slog(SLOG_INFO, "child %s exited with status: %d",
                     script_abs, WEXITSTATUS(status));
            }
            if (WIFSIGNALED(status)) {
                slog(SLOG_INFO, "child %s signaled with signal: %d",
                     script_abs, WTERMSIG(status));
            }
        }
        else { // child
            slog(SLOG_DEBUG, "exec for %s", script_abs);
            if (execle(script_abs, script_abs, NULL, env) < 0) {
                slog(SLOG_ERROR, "execlp: %s", strerror(errno));
            }
            exit(EXIT_FAILURE); // not reached
        }
    } // script_abs == SCANBD_NULL_STRING

    assert(script_abs != NULL);
    free(script_abs);
}

static void hook_device_insert(const char *dev_name) {
    slog(SLOG_DEBUG, "hook_device_insert");
    hook_device_ex(C_DEVICE_INSERT_SCRIPT, "insert", dev_name);
}

static void hook_device_remove(const char *dev_name) {
    hook_device_ex(C_DEVICE_REMOVE_SCRIPT, "remove", dev_name);
}

#ifdef USE_HAL
static void hal_device_added(LibHalContext* ctx, const char *udi) {
    (void)ctx;
    slog(SLOG_DEBUG, "New Device: %s", udi);

    DBusError dbus_error;
    dbus_error_init(&dbus_error);

    if (libhal_device_query_capability(ctx, udi,
                                       HAL_SCANNER_CAPABILITY, &dbus_error) == TRUE) {
        // found new scanner
        slog(SLOG_INFO, "New Scanner: %s", udi);
#ifdef USE_SANE
        stop_sane_threads();
#else
        stop_scbtn_threads();
#endif
#ifdef USE_SANE
# ifdef SANE_REINIT
        slog(SLOG_DEBUG, "sane_exit");
        sane_exit();
# endif
#else
        scbtn_shutdown();
#endif

#ifdef SANE_REINIT_TIMEOUT
        sleep(SANE_REINIT_TIMEOUT); // TODO: don't know if this is
        // really neccessary
#endif

        hook_device_insert(udi);

        slog(SLOG_DEBUG, "sane_init");
#ifdef USE_SANE
# ifdef SANE_REINIT        
        sane_init(NULL, NULL);
# endif
        get_sane_devices();
        start_sane_threads();
#else
        if (scanbtnd_init() < 0) {
            slog(SLOG_INFO, "Could not initialize scanbuttond modules!\n");
            exit(EXIT_FAILURE);
        }
        assert(backend);

        get_scbtn_devices();
        start_scbtn_threads();
#endif
    }
}
#endif

#ifdef USE_HAL
static void hal_device_removed(LibHalContext* ctx, const char *udi) {
    (void)ctx;
    slog(SLOG_DEBUG, "Removed Device: %s", udi);

    DBusError dbus_error;
    dbus_error_init(&dbus_error);

    // if the device is removed, the device informations isn't
    // available anymore, so we unconditionally stop/start the sane threads
    if (libhal_device_query_capability(ctx, udi,
                                       HAL_SCANNER_CAPABILITY, &dbus_error) == TRUE) {
        slog(SLOG_INFO, "Removed Scanner: %s", udi);
    }
#ifdef USE_SANE
    stop_sane_threads();
#else
    stop_scbtn_threads();
#endif

#ifdef USE_SANE
# ifdef SANE_REINIT
    slog(SLOG_DEBUG, "sane_exit");
    sane_exit();
# endif
#else
    scbtn_shutdown();
#endif

#ifdef SANE_REINIT_TIMEOUT
    sleep(SANE_REINIT_TIMEOUT);// TODO: don't know if this is really neccessary
#endif

    hook_device_remove(udi);

    slog(SLOG_DEBUG, "sane_init");
#ifdef USE_SANE
# ifdef SANE_REINIT
    sane_init(NULL, NULL);
# endif
    get_sane_devices();
    start_sane_threads();
#else
    if (scanbtnd_init() < 0) {
        slog(SLOG_INFO, "Could not initialize scanbuttond modules!\n");
        exit(EXIT_FAILURE);
    }
    assert(backend);

    get_scbtn_devices();
    start_scbtn_threads();
#endif
}
#endif

// this function is used from dbus_thread and udev_thread
void dbus_signal_device_added(void) {
    if (pthread_mutex_lock(&dbus_mutex)) {
        slog(SLOG_ERROR, "Can't lock mutex");
    }
#ifndef USE_HAL
    slog(SLOG_DEBUG, "dbus_signal_device_added");
    // look for new scanner
#ifdef USE_SANE
    stop_sane_threads();
#else
    stop_scbtn_threads();
#endif // USE_SANE
#ifdef USE_SANE
# ifdef SANE_REINIT
    slog(SLOG_DEBUG, "sane_exit");
    sane_exit();
# endif
#else
    scbtn_shutdown();
#endif // USE_SANE

#ifdef SANE_REINIT_TIMEOUT
    sleep(SANE_REINIT_TIMEOUT); // TODO: don't know if this is
    // really neccessary
#endif

    hook_device_insert("dbus device");

#ifdef USE_SANE
# ifdef SANE_REINIT
    slog(SLOG_DEBUG, "sane_init");
    sane_init(NULL, NULL);
# endif
    slog(SLOG_DEBUG, "get new devices");
    get_sane_devices();
    start_sane_threads();
#else
    if (scanbtnd_init() < 0) {
        slog(SLOG_INFO, "Could not initialize scanbuttond modules!\n");
        exit(EXIT_FAILURE);
    }
    assert(backend);

    get_scbtn_devices();
    start_scbtn_threads();
#endif // USE_SANE
#endif // USE_HAL
    if (pthread_mutex_unlock(&dbus_mutex)) {
        slog(SLOG_ERROR, "Can't unlock mutex");
    }
}

void dbus_signal_device_removed(void) {
    if (pthread_mutex_lock(&dbus_mutex)) {
        slog(SLOG_ERROR, "Can't lock mutex");
    }
#ifndef USE_HAL
    slog(SLOG_DEBUG, "dbus_signal_device_removed");
    // look for removed scanner
#ifdef USE_SANE
    stop_sane_threads();
#else
    stop_scbtn_threads();
#endif

#ifdef USE_SANE
# ifdef SANE_REINIT
    slog(SLOG_DEBUG, "sane_exit");
    sane_exit();
# endif
#else
    scbtn_shutdown();
#endif

#ifdef SANE_REINIT_TIMEOUT
    sleep(SANE_REINIT_TIMEOUT); // TODO: don't know if this is
    // really neccessary
#endif

    hook_device_remove("dbus device");

#ifdef USE_SANE
# ifdef SANE_REINIT
    slog(SLOG_DEBUG, "sane_init");
    sane_init(NULL, NULL);
# endif
    slog(SLOG_DEBUG, "get new devices");
    get_sane_devices();
    start_sane_threads();
#else
    if (scanbtnd_init() < 0) {
        slog(SLOG_INFO, "Could not initialize scanbuttond modules!\n");
        exit(EXIT_FAILURE);
    }
    assert(backend);
#endif

#endif
    if (pthread_mutex_unlock(&dbus_mutex)) {
        slog(SLOG_ERROR, "Can't unlock mutex");
    }
}

// is called when saned exited
static void dbus_method_release(void) {
    slog(SLOG_DEBUG, "dbus_method_release");
    // start all threads
#ifdef USE_SANE
    start_sane_threads();
#else
    start_scbtn_threads();
#endif
}

// is called before saned started
static void dbus_method_acquire(void) {
    slog(SLOG_DEBUG, "dbus_method_acquire");
    // stop all threads
#ifdef USE_SANE
    stop_sane_threads();
#else
    stop_scbtn_threads();
#endif
}

struct sane_trigger_arg {
    void (*f)(void*);
    int device;
    int action;
};
typedef struct sane_trigger_arg sane_trigger_arg_t;

void* dbus_call_sane_trigger_action_thread(void* arg) {
    sane_trigger_arg_t* a = (sane_trigger_arg_t*) arg;
    assert(a != NULL);
#ifdef USE_SANE
    sane_trigger_action(a->device, a->action);
#else
    scbtn_trigger_action(a->device, a->action);
#endif
    return NULL;
}

void sane_trigger_action_async(int device, int action) {
    sane_trigger_arg_t* arg = calloc(1, sizeof(sane_trigger_arg_t));
    assert(arg != NULL);

    arg->device = device;
    arg->action = action;

    pthread_t tid;
    if (pthread_create(&tid, NULL, dbus_call_sane_trigger_action_thread, arg) < 0) {
        slog(SLOG_WARN, "pthread_create: %s", strerror(errno));
    }
}

static void dbus_method_trigger(DBusMessage *message) {
#if ((__STDC_VERSION__  - 0) < 201112L) || ((__GNUC__ - 0) < 5)
        DBusMessageIter args;
#else
        DBusMessageIter args = {};
#endif

    dbus_uint32_t device = -1;
    dbus_uint32_t action = -1;

    if (!dbus_message_iter_init(message, &args)) {
        slog(SLOG_WARN, "trigger has no arguments");
        return;
    }
    if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_UINT32) {
        dbus_message_iter_get_basic(&args, &device);
        slog(SLOG_INFO, "trigger device %d", device);
    }
    else {
        slog(SLOG_WARN, "trigger has wrong argument type");
        return;
    }
    if (!dbus_message_iter_next(&args)) {
        slog(SLOG_WARN, "trigger has too few arguments");
        return;
    }
    if (dbus_message_iter_get_arg_type(&args) == DBUS_TYPE_UINT32) {
        dbus_message_iter_get_basic(&args, &action);
        slog(SLOG_INFO, "trigger action %d", action);
    }
    else {
        slog(SLOG_WARN, "trigger has wrong argument type");
        return;
    }
    sane_trigger_action_async(device, action);
}

static void unregister_func(DBusConnection* connection, void* user_data) {
    (void)connection;
    (void)user_data;
    slog(SLOG_DEBUG, "unregister_func_called");
}

static DBusHandlerResult message_func(DBusConnection *connection, DBusMessage *message,
                                      void *user_data) {
    (void)connection;
    (void)user_data;

    slog(SLOG_DEBUG, "message_func");
    DBusMessage* reply = NULL;
    if (dbus_message_is_method_call(message,
                                    SCANBD_DBUS_INTERFACE,
                                    SCANBD_DBUS_METHOD_ACQUIRE)) {
        dbus_method_acquire();
    }
    else if (dbus_message_is_method_call(message,
                                         SCANBD_DBUS_INTERFACE,
                                         SCANBD_DBUS_METHOD_RELEASE)) {
        dbus_method_release();
    }
    else if (dbus_message_is_method_call(message,
                                         SCANBD_DBUS_INTERFACE,
                                         SCANBD_DBUS_METHOD_TRIGGER)) {
        dbus_method_trigger(message);
    }
    else if (dbus_message_is_signal(message,
                                    DBUS_HAL_INTERFACE,
                                    DBUS_HAL_SIGNAL_DEV_ADDED)) {
        dbus_signal_device_added();
    }
    else if (dbus_message_is_signal(message,
                                    DBUS_HAL_INTERFACE,
                                    DBUS_HAL_SIGNAL_DEV_REMOVED)) {
        dbus_signal_device_removed();
    }

    /* If the message was handled, send back the reply */
    if (reply != NULL) {
        dbus_connection_send(conn, reply, NULL);
        dbus_message_unref(reply);
    }
    return reply ? DBUS_HANDLER_RESULT_HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}

static DBusObjectPathVTable dbus_vtable = {
    unregister_func,
    message_func,
    NULL,
    NULL,
    NULL,
    NULL
};

void dbus_thread_cleanup(void* arg) {
    assert(arg);
    slog(SLOG_DEBUG, "dbus_thread_cleanup");
    (void)arg;
}

static void* dbus_thread(void* arg) {
    (void)arg;
    // we only expect the main thread to handle signals
    sigset_t mask;
    sigfillset(&mask);
    pthread_sigmask(SIG_BLOCK, &mask, NULL);

    cfg_t* cfg_sec_global = NULL;
    cfg_sec_global = cfg_getsec(cfg, C_GLOBAL);
    assert(cfg_sec_global);

    int timeout = cfg_getint(cfg_sec_global, C_TIMEOUT);
    if (timeout <= 0) {
        timeout = C_TIMEOUT_DEF;
    }
    slog(SLOG_DEBUG, "timeout: %d ms", timeout);

    while(dbus_connection_read_write_dispatch(conn, timeout)) {
        slog(SLOG_DEBUG, "Iteration on dbus call");
        usleep(timeout * 1000);
    }
    return NULL;
}

bool dbus_init(void) {
    slog(SLOG_DEBUG, "dbus_init");

#ifndef PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP
    slog(SLOG_INFO, "dbus init mutex");
    pthread_mutexattr_t mutexattr;
    if (pthread_mutexattr_init(&mutexattr) < 0) {
        slog(SLOG_ERROR, "Can't initialize mutex attr");
        exit(EXIT_FAILURE);
    }
    if (pthread_mutexattr_settype(&mutexattr, PTHREAD_MUTEX_RECURSIVE) < 0) {
        slog(SLOG_ERROR, "Can't set mutex attr");
        exit(EXIT_FAILURE);
    }
    if (pthread_mutex_init(&dbus_mutex, &mutexattr) < 0) {
        slog(SLOG_ERROR, "Can't init mutex");
        exit(EXIT_FAILURE);
    }
#endif

    if (!dbus_threads_init_default()) {
        slog(SLOG_ERROR, "DBus thread initialization failure");
    }

    DBusError dbus_error;
    dbus_error_init(&dbus_error);

    if (conn == NULL) {
        conn = dbus_bus_get(DBUS_BUS_SYSTEM, &dbus_error);
        if (dbus_error_is_set(&dbus_error)) {
            conn = NULL;
            slog(SLOG_ERROR, "DBus connection error: %s", dbus_error.message);
            dbus_error_free(&dbus_error);
            return false;
        }
    }
    else {
        slog(SLOG_WARN, "dbus connection already established");
    }
    if (!conn) {
        slog(SLOG_DEBUG, "No dbus connection");
        return false;
    }

    assert(conn);

#ifdef USE_HAL
    LibHalContext *hal_ctx = NULL;

    hal_ctx = (LibHalContext*)libhal_ctx_new();
    if (!hal_ctx) {
        slog(SLOG_WARN, "Failed to create HAL context!");
        return false;
    }

    libhal_ctx_set_dbus_connection(hal_ctx, conn);

    libhal_ctx_set_device_added(hal_ctx, hal_device_added);
    libhal_ctx_set_device_removed(hal_ctx, hal_device_removed);

    if (!libhal_ctx_init(hal_ctx, &dbus_error)) {
        slog(SLOG_WARN, "Couldn't initialise HAL!");
        return false;
    }
#else
    char match[PATH_MAX] = {};
    snprintf(match, PATH_MAX, "type='signal',interface='%s'", DBUS_HAL_INTERFACE);
    slog(SLOG_ERROR, "dbus match %s", match);
    dbus_bus_add_match(conn, match, &dbus_error);
    if (dbus_error_is_set(&dbus_error)) {
        slog(SLOG_ERROR, "DBus match error: %s", dbus_error.message);
        dbus_error_free(&dbus_error);
        return false;
    }
    dbus_connection_flush(conn);
    if (dbus_error_is_set(&dbus_error)) {
        slog(SLOG_ERROR, "DBus match error: %s", dbus_error.message);
        dbus_error_free(&dbus_error);
        return false;
    }
#endif
    return true;
}

void dbus_start_dbus_thread(void) {
    slog(SLOG_DEBUG, "start dbus thread");
    if (conn == NULL) {
        if (!dbus_init()) {
            return;
        }
    }
    assert(conn);

    if (!conn) {
        slog(SLOG_DEBUG, "No dbus connection");
        return;
    }

    DBusError dbus_error;

    dbus_error_init(&dbus_error);
    if (dbus_connection_register_object_path(conn, SCANBD_DBUS_OBJECTPATH,
                                             &dbus_vtable, NULL) == FALSE) {
        slog(SLOG_ERROR, "Can't register object path: %s", SCANBD_DBUS_OBJECTPATH);
        return;
    }

    dbus_error_init(&dbus_error);
    int ret = dbus_bus_request_name(conn, SCANBD_DBUS_ADDRESS,
                                    0, &dbus_error);

    if (DBUS_REQUEST_NAME_REPLY_PRIMARY_OWNER != ret) {
        slog(SLOG_WARN, "Not Primary Owner (%d)", ret);
        if (dbus_error_is_set(&dbus_error)) {
            slog(SLOG_WARN, "Name Error (%s)", dbus_error.message);
            dbus_error_free(&dbus_error);
            return;
        }
    }
    if (dbus_tid != 0) {
        slog(SLOG_DEBUG, "dbus thread already running");
        dbus_stop_dbus_thread();
    }
    if (pthread_create(&dbus_tid, NULL, dbus_thread, NULL) < 0){
        slog(SLOG_ERROR, "Can't create dbus thread: %s", strerror(errno));
        return;
    }
    return;
}

void dbus_stop_dbus_thread(void) {
    slog(SLOG_DEBUG, "stop dbus thread");
    if (dbus_tid == 0) {
        return;
    }
    if (pthread_cancel(dbus_tid) < 0) {
        if (errno == ESRCH) {
            slog(SLOG_WARN, "dbus thread was already cancelled");
        }
        else {
            slog(SLOG_WARN, "unknown error from pthread_cancel: %s", strerror(errno));
        }
    }
    slog(SLOG_DEBUG, "join dbus thread");
    if (pthread_join(dbus_tid, NULL) < 0) {
        slog(SLOG_ERROR, "pthread_join: %s", strerror(errno));
    }
    dbus_tid = 0;
}

void dbus_call_method(const char* method, const char* value) {
    slog(SLOG_DEBUG, "dbus_call_method");
    if (conn == NULL) {
        if (!dbus_init()) {
            return;
        }
    }
    assert(conn);

    if (!conn) {
        slog(SLOG_DEBUG, "No dbus connection");
        return;
    }

    DBusMessage* msg = NULL;
    if ((msg = dbus_message_new_method_call(SCANBD_DBUS_ADDRESS,
                                            SCANBD_DBUS_OBJECTPATH,
                                            SCANBD_DBUS_INTERFACE,
                                            method)) == NULL) {
        slog(SLOG_ERROR, "Can't compose message");
        return;
    }
    assert(msg);

    if (value != NULL) {
#if ((__STDC_VERSION__  - 0) < 201112L) || ((__GNUC__ - 0) < 5)
        DBusMessageIter args;
#else
        DBusMessageIter args = {};
#endif
        dbus_message_iter_init_append(msg, &args);
        if (dbus_message_iter_append_basic(&args, DBUS_TYPE_STRING, value) != TRUE) {
            slog(SLOG_ERROR, "Can't compose message");
            return;
        }
    }

    DBusPendingCall* pending = NULL;

    // send message and get a handle for a reply
    if (!dbus_connection_send_with_reply (conn, msg, &pending, -1)) {
        slog(SLOG_WARN, "Can't send message");
        return;
    }
    if (NULL == pending) {
        slog(SLOG_ERROR, "Disconnected from bus");
        return;
    }
    dbus_connection_flush(conn);

    assert(msg);
    dbus_message_unref(msg);

    slog(SLOG_DEBUG, "waiting for reply");
    assert(pending);
    dbus_pending_call_block(pending);

    // get the reply message
    DBusMessage* reply = NULL;
    if ((reply = dbus_pending_call_steal_reply(pending)) == NULL) {
        slog(SLOG_DEBUG, "Reply Null\n");
        return;
    }
    dbus_pending_call_unref(pending);

#if ((__STDC_VERSION__  - 0) < 201112L) || ((__GNUC__ - 0) < 5)
        DBusMessageIter args;
#else
        DBusMessageIter args = {};
#endif
    if (!dbus_message_iter_init(reply, &args)) {
        slog(SLOG_INFO, "Reply has no arguments");
    }

    return;
}

void dbus_call_trigger(unsigned int device, unsigned int action) {
    slog(SLOG_DEBUG, "dbus_call_trigger for dev %d, action %d", device, action);
    if (conn == NULL) {
        if (!dbus_init()) {
            return;
        }
    }
    assert(conn);

    if (!conn) {
        slog(SLOG_DEBUG, "No dbus connection");
        return;
    }

    DBusMessage* msg = NULL;
    if ((msg = dbus_message_new_method_call(SCANBD_DBUS_ADDRESS,
                                            SCANBD_DBUS_OBJECTPATH,
                                            SCANBD_DBUS_INTERFACE,
                                            SCANBD_DBUS_METHOD_TRIGGER)) == NULL) {
        slog(SLOG_ERROR, "Can't compose message");
        return;
    }
    assert(msg);

    dbus_uint32_t dev = device;
    dbus_uint32_t act = action;
#if ((__STDC_VERSION__  - 0) < 201112L) || ((__GNUC__ - 0) < 5)
        DBusMessageIter args;
#else
        DBusMessageIter args = {};
#endif
    dbus_message_iter_init_append(msg, &args);
    if (dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &dev) != TRUE) {
        slog(SLOG_ERROR, "Can't compose message");
        return;
    }
    if (dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &act) != TRUE) {
        slog(SLOG_ERROR, "Can't compose message");
        return;
    }

    DBusPendingCall* pending = NULL;

    // send message and get a handle for a reply
    if (!dbus_connection_send_with_reply (conn, msg, &pending, -1)) {
        slog(SLOG_WARN, "Can't send message");
        return;
    }
    if (NULL == pending) {
        slog(SLOG_ERROR, "Disconnected from bus");
        return;
    }
    dbus_connection_flush(conn);

    assert(msg);
    dbus_message_unref(msg);

    slog(SLOG_DEBUG, "waiting for reply");
    assert(pending);
    dbus_pending_call_block(pending);

    // get the reply message
    DBusMessage* reply = NULL;
    if ((reply = dbus_pending_call_steal_reply(pending)) == NULL) {
        slog(SLOG_DEBUG, "Reply Null\n");
        return;
    }
    dbus_pending_call_unref(pending);

    if (!dbus_message_iter_init(reply, &args)) {
        slog(SLOG_INFO, "Reply has no arguments");
    }

    return;
}
