/**
 * collectd - src/python.c
 * Copyright (C) 2009  Sven Trenkel
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 * Authors:
 *   Sven Trenkel <collectd at semidefinite.de>
 **/

#include <Python.h>
#include <structmember.h>

#include <signal.h>

#include "collectd.h"

#include "utils/common/common.h"

#include "cpython.h"

typedef struct cpy_callback_s {
  char *name;
  PyObject *callback;
  PyObject *data;
  struct cpy_callback_s *next;
} cpy_callback_t;

static char log_doc[] = "This function sends a string to all logging plugins.";

static char get_ds_doc[] =
    "get_dataset(name) -> definition\n"
    "\n"
    "Returns the definition of a dataset specified by name.\n"
    "\n"
    "'name' is a string specifying the dataset to query.\n"
    "'definition' is a list of 4-tuples. Every tuple represents a \n"
    "    data source within the data set and its 4 values are the \n"
    "    name, type, min and max value.\n"
    "    'name' is a string.\n"
    "    'type' is a string that is equal to either DS_TYPE_COUNTER,\n"
    "        DS_TYPE_GAUGE, DS_TYPE_DERIVE or DS_TYPE_ABSOLUTE.\n"
    "    'min' and 'max' are either a float or None.";

static char flush_doc[] = "flush([plugin][, timeout][, identifier]) -> None\n"
                          "\n"
                          "Flushes the cache of another plugin.";

static char unregister_doc[] =
    "Unregisters a callback. This function needs exactly one parameter either\n"
    "the function to unregister or the callback identifier to unregister.";

static char reg_log_doc[] =
    "register_log(callback[, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function for log messages.\n"
    "\n"
    "'callback' is a callable object that will be called every time something\n"
    "    is logged.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function every time it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called with two or three parameters:\n"
    "severity: An integer that should be compared to the LOG_ constants.\n"
    "message: The text to be logged.\n"
    "data: The optional data parameter passed to the register function.\n"
    "    If the parameter was omitted it will be omitted here, too.";

static char reg_init_doc[] =
    "register_init(callback[, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function that will be executed once after the "
    "config.\n"
    "file has been read, all plugins heve been loaded and the collectd has\n"
    "forked into the background.\n"
    "\n"
    "'callback' is a callable object that will be executed.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function when it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called without parameters, except for\n"
    "data if it was supplied.";

static char reg_config_doc[] =
    "register_config(callback[, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function for config file entries.\n"
    "'callback' is a callable object that will be called for every config "
    "block.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function every time it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called with one or two parameters:\n"
    "config: A Config object.\n"
    "data: The optional data parameter passed to the register function.\n"
    "    If the parameter was omitted it will be omitted here, too.";

static char reg_read_doc[] =
    "register_read(callback[, interval][, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function for reading data. It will just be called\n"
    "in a fixed interval to signal that it's time to dispatch new values.\n"
    "'callback' is a callable object that will be called every time something\n"
    "    is logged.\n"
    "'interval' is the number of seconds between between calls to the "
    "callback\n"
    "    function. Full float precision is supported here.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function every time it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called without parameters, except for\n"
    "data if it was supplied.";

static char reg_write_doc[] =
    "register_write(callback[, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function to receive values dispatched by other "
    "plugins.\n"
    "'callback' is a callable object that will be called every time a value\n"
    "    is dispatched.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function every time it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called with one or two parameters:\n"
    "values: A Values object which is a copy of the dispatched values.\n"
    "data: The optional data parameter passed to the register function.\n"
    "    If the parameter was omitted it will be omitted here, too.";

static char reg_notification_doc[] =
    "register_notification(callback[, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function for notifications.\n"
    "'callback' is a callable object that will be called every time a "
    "notification\n"
    "    is dispatched.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function every time it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called with one or two parameters:\n"
    "notification: A copy of the notification that was dispatched.\n"
    "data: The optional data parameter passed to the register function.\n"
    "    If the parameter was omitted it will be omitted here, too.";

static char reg_flush_doc[] =
    "register_flush(callback[, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function for flush messages.\n"
    "'callback' is a callable object that will be called every time a plugin\n"
    "    requests a flush for either this or all plugins.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function every time it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called with two or three parameters:\n"
    "timeout: Indicates that only data older than 'timeout' seconds is to\n"
    "    be flushed.\n"
    "id: Specifies which values are to be flushed. Might be None.\n"
    "data: The optional data parameter passed to the register function.\n"
    "    If the parameter was omitted it will be omitted here, too.";

static char reg_shutdown_doc[] =
    "register_shutdown(callback[, data][, name]) -> identifier\n"
    "\n"
    "Register a callback function for collectd shutdown.\n"
    "'callback' is a callable object that will be called once collectd is\n"
    "    shutting down.\n"
    "'data' is an optional object that will be passed back to the callback\n"
    "    function if it is called.\n"
    "'name' is an optional identifier for this callback. The default name\n"
    "    is 'python.<module>'.\n"
    "    Every callback needs a unique identifier, so if you want to\n"
    "    register this callback multiple time from the same module you need\n"
    "    to specify a name here.\n"
    "'identifier' is the full identifier assigned to this callback.\n"
    "\n"
    "The callback function will be called with no parameters except for\n"
    "    data if it was supplied.";

static char CollectdError_doc[] =
    "Basic exception for collectd Python scripts.\n"
    "\n"
    "Throwing this exception will not cause a stacktrace to be logged, \n"
    "even if LogTraces is enabled in the config.";

static pthread_t main_thread;
static PyOS_sighandler_t python_sigint_handler;
static bool do_interactive;

/* This is our global thread state. Python saves some stuff in thread-local
 * storage. So if we allow the interpreter to run in the background
 * (the scriptwriters might have created some threads from python), we have
 * to save the state so we can resume it later after shutdown. */

static PyThreadState *state;

static PyObject *sys_path, *cpy_format_exception, *CollectdError;

static cpy_callback_t *cpy_config_callbacks;
static cpy_callback_t *cpy_init_callbacks;
static cpy_callback_t *cpy_shutdown_callbacks;

/* Make sure to hold the GIL while modifying these. */
static int cpy_shutdown_triggered;
static int cpy_num_callbacks;

static void cpy_destroy_user_data(void *data) {
  cpy_callback_t *c = data;
  free(c->name);
  CPY_LOCK_THREADS
  Py_DECREF(c->callback);
  Py_XDECREF(c->data);
  free(c);
  --cpy_num_callbacks;
  if (!cpy_num_callbacks && cpy_shutdown_triggered) {
    Py_Finalize();
    return;
  }
  CPY_RELEASE_THREADS
}

/* You must hold the GIL to call this function!
 * But if you managed to extract the callback parameter then you probably
 * already do. */

static void cpy_build_name(char *buf, size_t size, PyObject *callback,
                           const char *name) {
  const char *module = NULL;
  PyObject *mod = NULL;

  if (name != NULL) {
    ssnprintf(buf, size, "python.%s", name);
    return;
  }

  mod = PyObject_GetAttrString(callback, "__module__"); /* New reference. */
  if (mod != NULL)
    module = cpy_unicode_or_bytes_to_string(&mod);

  if (module != NULL) {
    ssnprintf(buf, size, "python.%s", module);
    Py_XDECREF(mod);
    PyErr_Clear();
    return;
  }
  Py_XDECREF(mod);

  ssnprintf(buf, size, "python.%p", callback);
  PyErr_Clear();
}

void cpy_log_exception(const char *context) {
  int l = 0, collectd_error;
  const char *typename = NULL, *message = NULL;
  PyObject *type, *value, *traceback, *tn, *m, *list;

  PyErr_Fetch(&type, &value, &traceback);
  PyErr_NormalizeException(&type, &value, &traceback);
  if (type == NULL)
    return;
  collectd_error = PyErr_GivenExceptionMatches(value, CollectdError);
  tn = PyObject_GetAttrString(type, "__name__"); /* New reference. */
  m = PyObject_Str(value);                       /* New reference. */
  if (tn != NULL)
    typename = cpy_unicode_or_bytes_to_string(&tn);
  if (m != NULL)
    message = cpy_unicode_or_bytes_to_string(&m);
  if (typename == NULL)
    typename = "NamelessException";
  if (message == NULL)
    message = "N/A";
  Py_BEGIN_ALLOW_THREADS;
  if (collectd_error) {
    WARNING("%s in %s: %s", typename, context, message);
  } else {
    ERROR("Unhandled python exception in %s: %s: %s", context, typename,
          message);
  }
  Py_END_ALLOW_THREADS;
  Py_XDECREF(tn);
  Py_XDECREF(m);
  if (!cpy_format_exception || !traceback || collectd_error) {
    PyErr_Clear();
    Py_DECREF(type);
    Py_XDECREF(value);
    Py_XDECREF(traceback);
    return;
  }
  list = PyObject_CallFunction(cpy_format_exception, "NNN", type, value,
                               traceback); /* New reference. Steals references
                                              from "type", "value" and
                                              "traceback". */
  if (list)
    l = PyObject_Length(list);

  for (int i = 0; i < l; ++i) {
    PyObject *line;
    char const *msg;
    char *cpy;

    line = PyList_GET_ITEM(list, i); /* Borrowed reference. */
    Py_INCREF(line);

    msg = cpy_unicode_or_bytes_to_string(&line);
    Py_DECREF(line);
    if (msg == NULL)
      continue;

    cpy = strdup(msg);
    if (cpy == NULL)
      continue;

    if (cpy[strlen(cpy) - 1] == '\n')
      cpy[strlen(cpy) - 1] = '\0';

    Py_BEGIN_ALLOW_THREADS;
    ERROR("%s", cpy);
    Py_END_ALLOW_THREADS;

    free(cpy);
  }

  Py_XDECREF(list);
  PyErr_Clear();
}

static int cpy_read_callback(user_data_t *data) {
  cpy_callback_t *c = data->data;
  PyObject *ret;

  CPY_LOCK_THREADS
  ret = PyObject_CallFunctionObjArgs(c->callback, c->data,
                                     (void *)0); /* New reference. */
  if (ret == NULL) {
    cpy_log_exception("read callback");
  } else {
    Py_DECREF(ret);
  }
  CPY_RELEASE_THREADS
  if (ret == NULL)
    return 1;
  return 0;
}

static int cpy_write_callback(const data_set_t *ds,
                              const value_list_t *value_list,
                              user_data_t *data) {
  cpy_callback_t *c = data->data;
  PyObject *ret, *list, *temp, *dict = NULL;
  Values *v;

  CPY_LOCK_THREADS
  list = PyList_New(value_list->values_len); /* New reference. */
  if (list == NULL) {
    cpy_log_exception("write callback");
    CPY_RETURN_FROM_THREADS 0;
  }
  for (size_t i = 0; i < value_list->values_len; ++i) {
    if (ds->ds[i].type == DS_TYPE_COUNTER) {
      PyList_SetItem(
          list, i, PyLong_FromUnsignedLongLong(value_list->values[i].counter));
    } else if (ds->ds[i].type == DS_TYPE_GAUGE) {
      PyList_SetItem(list, i, PyFloat_FromDouble(value_list->values[i].gauge));
    } else if (ds->ds[i].type == DS_TYPE_DERIVE) {
      PyList_SetItem(list, i,
                     PyLong_FromLongLong(value_list->values[i].derive));
    } else if (ds->ds[i].type == DS_TYPE_ABSOLUTE) {
      PyList_SetItem(
          list, i, PyLong_FromUnsignedLongLong(value_list->values[i].absolute));
    } else {
      Py_BEGIN_ALLOW_THREADS;
      ERROR("cpy_write_callback: Unknown value type %d.", ds->ds[i].type);
      Py_END_ALLOW_THREADS;
      Py_DECREF(list);
      CPY_RETURN_FROM_THREADS 0;
    }
    if (PyErr_Occurred() != NULL) {
      cpy_log_exception("value building for write callback");
      Py_DECREF(list);
      CPY_RETURN_FROM_THREADS 0;
    }
  }
  dict = PyDict_New(); /* New reference. */
  if (value_list->meta) {
    char **table = NULL;
    meta_data_t *meta = value_list->meta;

    int num = meta_data_toc(meta, &table);
    for (int i = 0; i < num; ++i) {
      int type;
      char *string;
      int64_t si;
      uint64_t ui;
      double d;
      bool b;

      type = meta_data_type(meta, table[i]);
      if (type == MD_TYPE_STRING) {
        if (meta_data_get_string(meta, table[i], &string))
          continue;
        temp = cpy_string_to_unicode_or_bytes(string); /* New reference. */
        free(string);
        PyDict_SetItemString(dict, table[i], temp);
        Py_XDECREF(temp);
      } else if (type == MD_TYPE_SIGNED_INT) {
        if (meta_data_get_signed_int(meta, table[i], &si))
          continue;
        PyObject *sival = PyLong_FromLongLong(si); /* New reference */
        temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival,
                                            (void *)0); /* New reference. */
        PyDict_SetItemString(dict, table[i], temp);
        Py_XDECREF(temp);
        Py_XDECREF(sival);
      } else if (type == MD_TYPE_UNSIGNED_INT) {
        if (meta_data_get_unsigned_int(meta, table[i], &ui))
          continue;
        PyObject *uval = PyLong_FromUnsignedLongLong(ui); /* New reference */
        temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval,
                                            (void *)0); /* New reference. */
        PyDict_SetItemString(dict, table[i], temp);
        Py_XDECREF(temp);
        Py_XDECREF(uval);
      } else if (type == MD_TYPE_DOUBLE) {
        if (meta_data_get_double(meta, table[i], &d))
          continue;
        temp = PyFloat_FromDouble(d); /* New reference. */
        PyDict_SetItemString(dict, table[i], temp);
        Py_XDECREF(temp);
      } else if (type == MD_TYPE_BOOLEAN) {
        if (meta_data_get_boolean(meta, table[i], &b))
          continue;
        if (b)
          PyDict_SetItemString(dict, table[i], Py_True);
        else
          PyDict_SetItemString(dict, table[i], Py_False);
      }
      free(table[i]);
    }
    free(table);
  }
  v = (Values *)Values_New(); /* New reference. */
  sstrncpy(v->data.host, value_list->host, sizeof(v->data.host));
  sstrncpy(v->data.type, value_list->type, sizeof(v->data.type));
  sstrncpy(v->data.type_instance, value_list->type_instance,
           sizeof(v->data.type_instance));
  sstrncpy(v->data.plugin, value_list->plugin, sizeof(v->data.plugin));
  sstrncpy(v->data.plugin_instance, value_list->plugin_instance,
           sizeof(v->data.plugin_instance));
  v->data.time = CDTIME_T_TO_DOUBLE(value_list->time);
  v->interval = CDTIME_T_TO_DOUBLE(value_list->interval);
  Py_CLEAR(v->values);
  v->values = list;
  Py_CLEAR(v->meta);
  v->meta = dict; /* Steals a reference. */
  ret = PyObject_CallFunctionObjArgs(c->callback, v, c->data,
                                     (void *)0); /* New reference. */
  Py_XDECREF(v);
  if (ret == NULL) {
    cpy_log_exception("write callback");
  } else {
    Py_DECREF(ret);
  }
  CPY_RELEASE_THREADS
  return 0;
}

static int cpy_notification_callback(const notification_t *notification,
                                     user_data_t *data) {
  cpy_callback_t *c = data->data;
  PyObject *ret, *notify;
  Notification *n;

  CPY_LOCK_THREADS
  PyObject *dict = PyDict_New(); /* New reference. */
  for (notification_meta_t *meta = notification->meta; meta != NULL;
       meta = meta->next) {
    PyObject *temp = NULL;
    if (meta->type == NM_TYPE_STRING) {
      temp = cpy_string_to_unicode_or_bytes(
          meta->nm_value.nm_string); /* New reference. */
      PyDict_SetItemString(dict, meta->name, temp);
      Py_XDECREF(temp);
    } else if (meta->type == NM_TYPE_SIGNED_INT) {
      PyObject *sival = PyLong_FromLongLong(meta->nm_value.nm_signed_int);
      temp = PyObject_CallFunctionObjArgs((void *)&SignedType, sival,
                                          (void *)0); /* New reference. */
      PyDict_SetItemString(dict, meta->name, temp);
      Py_XDECREF(temp);
      Py_XDECREF(sival);
    } else if (meta->type == NM_TYPE_UNSIGNED_INT) {
      PyObject *uval =
          PyLong_FromUnsignedLongLong(meta->nm_value.nm_unsigned_int);
      temp = PyObject_CallFunctionObjArgs((void *)&UnsignedType, uval,
                                          (void *)0); /* New reference. */
      PyDict_SetItemString(dict, meta->name, temp);
      Py_XDECREF(temp);
      Py_XDECREF(uval);
    } else if (meta->type == NM_TYPE_DOUBLE) {
      temp = PyFloat_FromDouble(meta->nm_value.nm_double); /* New reference. */
      PyDict_SetItemString(dict, meta->name, temp);
      Py_XDECREF(temp);
    } else if (meta->type == NM_TYPE_BOOLEAN) {
      PyDict_SetItemString(dict, meta->name,
                           meta->nm_value.nm_boolean ? Py_True : Py_False);
    }
  }
  notify = Notification_New(); /* New reference. */
  n = (Notification *)notify;
  sstrncpy(n->data.host, notification->host, sizeof(n->data.host));
  sstrncpy(n->data.type, notification->type, sizeof(n->data.type));
  sstrncpy(n->data.type_instance, notification->type_instance,
           sizeof(n->data.type_instance));
  sstrncpy(n->data.plugin, notification->plugin, sizeof(n->data.plugin));
  sstrncpy(n->data.plugin_instance, notification->plugin_instance,
           sizeof(n->data.plugin_instance));
  n->data.time = CDTIME_T_TO_DOUBLE(notification->time);
  sstrncpy(n->message, notification->message, sizeof(n->message));
  n->severity = notification->severity;
  Py_CLEAR(n->meta);
  n->meta = dict; /* Steals a reference. */
  ret = PyObject_CallFunctionObjArgs(c->callback, n, c->data,
                                     (void *)0); /* New reference. */
  Py_XDECREF(notify);
  if (ret == NULL) {
    cpy_log_exception("notification callback");
  } else {
    Py_DECREF(ret);
  }
  CPY_RELEASE_THREADS
  return 0;
}

static void cpy_log_callback(int severity, const char *message,
                             user_data_t *data) {
  cpy_callback_t *c = data->data;
  PyObject *ret, *text;

  CPY_LOCK_THREADS
  text = cpy_string_to_unicode_or_bytes(message); /* New reference. */
  if (c->data == NULL)
    ret = PyObject_CallFunction(
        c->callback, "iN", severity,
        text); /* New reference. Steals a reference from "text". */
  else
    ret = PyObject_CallFunction(
        c->callback, "iNO", severity, text,
        c->data); /* New reference. Steals a reference from "text". */

  if (ret == NULL) {
    /* FIXME */
    /* Do we really want to trigger a log callback because a log callback
     * failed?
     * Probably not. */
    PyErr_Print();
    /* In case someone wanted to be clever, replaced stderr and failed at that.
     */
    PyErr_Clear();
  } else {
    Py_DECREF(ret);
  }
  CPY_RELEASE_THREADS
}

static void cpy_flush_callback(int timeout, const char *id, user_data_t *data) {
  cpy_callback_t *c = data->data;
  PyObject *ret, *text;

  CPY_LOCK_THREADS
  if (id) {
    text = cpy_string_to_unicode_or_bytes(id);
  } else {
    text = Py_None;
    Py_INCREF(text);
  }
  if (c->data == NULL)
    ret = PyObject_CallFunction(c->callback, "iN", timeout,
                                text); /* New reference. */
  else
    ret = PyObject_CallFunction(c->callback, "iNO", timeout, text,
                                c->data); /* New reference. */

  if (ret == NULL) {
    cpy_log_exception("flush callback");
  } else {
    Py_DECREF(ret);
  }
  CPY_RELEASE_THREADS
}

static PyObject *cpy_register_generic(cpy_callback_t **list_head,
                                      PyObject *args, PyObject *kwds) {
  char buf[512];
  cpy_callback_t *c;
  char *name = NULL;
  PyObject *callback = NULL, *data = NULL, *mod = NULL;
  static char *kwlist[] = {"callback", "data", "name", NULL};

  if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data,
                                  NULL, &name) == 0)
    return NULL;
  if (PyCallable_Check(callback) == 0) {
    PyMem_Free(name);
    PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
    return NULL;
  }
  cpy_build_name(buf, sizeof(buf), callback, name);

  Py_INCREF(callback);
  Py_XINCREF(data);

  c = calloc(1, sizeof(*c));
  if (c == NULL)
    return NULL;

  c->name = strdup(buf);
  c->callback = callback;
  c->data = data;
  c->next = *list_head;
  ++cpy_num_callbacks;
  *list_head = c;
  Py_XDECREF(mod);
  PyMem_Free(name);
  return cpy_string_to_unicode_or_bytes(buf);
}

static PyObject *float_or_none(float number) {
  if (isnan(number)) {
    Py_RETURN_NONE;
  }
  return PyFloat_FromDouble(number);
}

static PyObject *cpy_get_dataset(PyObject *self, PyObject *args) {
  char *name;
  const data_set_t *ds;
  PyObject *list, *tuple;

  if (PyArg_ParseTuple(args, "et", NULL, &name) == 0)
    return NULL;
  ds = plugin_get_ds(name);
  PyMem_Free(name);
  if (ds == NULL) {
    PyErr_Format(PyExc_TypeError, "Dataset %s not found", name);
    return NULL;
  }
  list = PyList_New(ds->ds_num); /* New reference. */
  for (size_t i = 0; i < ds->ds_num; ++i) {
    tuple = PyTuple_New(4);
    PyTuple_SET_ITEM(tuple, 0, cpy_string_to_unicode_or_bytes(ds->ds[i].name));
    PyTuple_SET_ITEM(
        tuple, 1,
        cpy_string_to_unicode_or_bytes(DS_TYPE_TO_STRING(ds->ds[i].type)));
    PyTuple_SET_ITEM(tuple, 2, float_or_none(ds->ds[i].min));
    PyTuple_SET_ITEM(tuple, 3, float_or_none(ds->ds[i].max));
    PyList_SET_ITEM(list, i, tuple);
  }
  return list;
}

static PyObject *cpy_flush(PyObject *self, PyObject *args, PyObject *kwds) {
  int timeout = -1;
  char *plugin = NULL, *identifier = NULL;
  static char *kwlist[] = {"plugin", "timeout", "identifier", NULL};

  if (PyArg_ParseTupleAndKeywords(args, kwds, "|etiet", kwlist, NULL, &plugin,
                                  &timeout, NULL, &identifier) == 0)
    return NULL;
  Py_BEGIN_ALLOW_THREADS;
  plugin_flush(plugin, timeout, identifier);
  Py_END_ALLOW_THREADS;
  PyMem_Free(plugin);
  PyMem_Free(identifier);
  Py_RETURN_NONE;
}

static PyObject *cpy_register_config(PyObject *self, PyObject *args,
                                     PyObject *kwds) {
  return cpy_register_generic(&cpy_config_callbacks, args, kwds);
}

static PyObject *cpy_register_init(PyObject *self, PyObject *args,
                                   PyObject *kwds) {
  return cpy_register_generic(&cpy_init_callbacks, args, kwds);
}

typedef int reg_function_t(const char *name, void *callback, void *data);

static PyObject *cpy_register_generic_userdata(void *reg, void *handler,
                                               PyObject *args, PyObject *kwds) {
  char buf[512];
  reg_function_t *register_function = (reg_function_t *)reg;
  cpy_callback_t *c = NULL;
  char *name = NULL;
  PyObject *callback = NULL, *data = NULL;
  static char *kwlist[] = {"callback", "data", "name", NULL};

  if (PyArg_ParseTupleAndKeywords(args, kwds, "O|Oet", kwlist, &callback, &data,
                                  NULL, &name) == 0)
    return NULL;
  if (PyCallable_Check(callback) == 0) {
    PyMem_Free(name);
    PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
    return NULL;
  }
  cpy_build_name(buf, sizeof(buf), callback, name);
  PyMem_Free(name);

  Py_INCREF(callback);
  Py_XINCREF(data);

  c = calloc(1, sizeof(*c));
  if (c == NULL)
    return NULL;

  c->name = strdup(buf);
  c->callback = callback;
  c->data = data;
  c->next = NULL;

  register_function(buf, handler,
                    &(user_data_t){
                        .data = c,
                        .free_func = cpy_destroy_user_data,
                    });

  ++cpy_num_callbacks;
  return cpy_string_to_unicode_or_bytes(buf);
}

static PyObject *cpy_register_read(PyObject *self, PyObject *args,
                                   PyObject *kwds) {
  char buf[512];
  cpy_callback_t *c = NULL;
  double interval = 0;
  char *name = NULL;
  PyObject *callback = NULL, *data = NULL;
  static char *kwlist[] = {"callback", "interval", "data", "name", NULL};

  if (PyArg_ParseTupleAndKeywords(args, kwds, "O|dOet", kwlist, &callback,
                                  &interval, &data, NULL, &name) == 0)
    return NULL;
  if (PyCallable_Check(callback) == 0) {
    PyMem_Free(name);
    PyErr_SetString(PyExc_TypeError, "callback needs a be a callable object.");
    return NULL;
  }
  cpy_build_name(buf, sizeof(buf), callback, name);
  PyMem_Free(name);

  Py_INCREF(callback);
  Py_XINCREF(data);

  c = calloc(1, sizeof(*c));
  if (c == NULL)
    return NULL;

  c->name = strdup(buf);
  c->callback = callback;
  c->data = data;
  c->next = NULL;

  plugin_register_complex_read(
      /* group = */ "python", buf, cpy_read_callback,
      DOUBLE_TO_CDTIME_T(interval),
      &(user_data_t){
          .data = c,
          .free_func = cpy_destroy_user_data,
      });
  ++cpy_num_callbacks;
  return cpy_string_to_unicode_or_bytes(buf);
}

static PyObject *cpy_register_log(PyObject *self, PyObject *args,
                                  PyObject *kwds) {
  return cpy_register_generic_userdata((void *)plugin_register_log,
                                       (void *)cpy_log_callback, args, kwds);
}

static PyObject *cpy_register_write(PyObject *self, PyObject *args,
                                    PyObject *kwds) {
  return cpy_register_generic_userdata((void *)plugin_register_write,
                                       (void *)cpy_write_callback, args, kwds);
}

static PyObject *cpy_register_notification(PyObject *self, PyObject *args,
                                           PyObject *kwds) {
  return cpy_register_generic_userdata((void *)plugin_register_notification,
                                       (void *)cpy_notification_callback, args,
                                       kwds);
}

static PyObject *cpy_register_flush(PyObject *self, PyObject *args,
                                    PyObject *kwds) {
  return cpy_register_generic_userdata((void *)plugin_register_flush,
                                       (void *)cpy_flush_callback, args, kwds);
}

static PyObject *cpy_register_shutdown(PyObject *self, PyObject *args,
                                       PyObject *kwds) {
  return cpy_register_generic(&cpy_shutdown_callbacks, args, kwds);
}

static PyObject *cpy_error(PyObject *self, PyObject *args) {
  char *text;
  if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
    return NULL;
  Py_BEGIN_ALLOW_THREADS;
  plugin_log(LOG_ERR, "%s", text);
  Py_END_ALLOW_THREADS;
  PyMem_Free(text);
  Py_RETURN_NONE;
}

static PyObject *cpy_warning(PyObject *self, PyObject *args) {
  char *text;
  if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
    return NULL;
  Py_BEGIN_ALLOW_THREADS;
  plugin_log(LOG_WARNING, "%s", text);
  Py_END_ALLOW_THREADS;
  PyMem_Free(text);
  Py_RETURN_NONE;
}

static PyObject *cpy_notice(PyObject *self, PyObject *args) {
  char *text;
  if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
    return NULL;
  Py_BEGIN_ALLOW_THREADS;
  plugin_log(LOG_NOTICE, "%s", text);
  Py_END_ALLOW_THREADS;
  PyMem_Free(text);
  Py_RETURN_NONE;
}

static PyObject *cpy_info(PyObject *self, PyObject *args) {
  char *text;
  if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
    return NULL;
  Py_BEGIN_ALLOW_THREADS;
  plugin_log(LOG_INFO, "%s", text);
  Py_END_ALLOW_THREADS;
  PyMem_Free(text);
  Py_RETURN_NONE;
}

static PyObject *cpy_debug(PyObject *self, PyObject *args) {
#ifdef COLLECT_DEBUG
  char *text;
  if (PyArg_ParseTuple(args, "et", NULL, &text) == 0)
    return NULL;
  Py_BEGIN_ALLOW_THREADS;
  plugin_log(LOG_DEBUG, "%s", text);
  Py_END_ALLOW_THREADS;
  PyMem_Free(text);
#endif
  Py_RETURN_NONE;
}

static PyObject *cpy_unregister_generic(cpy_callback_t **list_head,
                                        PyObject *arg, const char *desc) {
  char buf[512];
  const char *name;
  cpy_callback_t *prev = NULL, *tmp;

  Py_INCREF(arg);
  name = cpy_unicode_or_bytes_to_string(&arg);
  if (name == NULL) {
    PyErr_Clear();
    if (!PyCallable_Check(arg)) {
      PyErr_SetString(PyExc_TypeError, "This function needs a string or a "
                                       "callable object as its only "
                                       "parameter.");
      Py_DECREF(arg);
      return NULL;
    }
    cpy_build_name(buf, sizeof(buf), arg, NULL);
    name = buf;
  }
  for (tmp = *list_head; tmp; prev = tmp, tmp = tmp->next)
    if (strcmp(name, tmp->name) == 0)
      break;

  Py_DECREF(arg);
  if (tmp == NULL) {
    PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.",
                 desc, name);
    return NULL;
  }
  /* Yes, this is actually safe. To call this function the caller has to
   * hold the GIL. Well, safe as long as there is only one GIL anyway ... */
  if (prev == NULL)
    *list_head = tmp->next;
  else
    prev->next = tmp->next;
  cpy_destroy_user_data(tmp);
  Py_RETURN_NONE;
}

static void cpy_unregister_list(cpy_callback_t **list_head) {
  cpy_callback_t *cur, *next;
  for (cur = *list_head; cur; cur = next) {
    next = cur->next;
    cpy_destroy_user_data(cur);
  }
  *list_head = NULL;
}

typedef int cpy_unregister_function_t(const char *name);

static PyObject *
cpy_unregister_generic_userdata(cpy_unregister_function_t *unreg, PyObject *arg,
                                const char *desc) {
  char buf[512];
  const char *name;

  Py_INCREF(arg);
  name = cpy_unicode_or_bytes_to_string(&arg);
  if (name == NULL) {
    PyErr_Clear();
    if (!PyCallable_Check(arg)) {
      PyErr_SetString(PyExc_TypeError, "This function needs a string or a "
                                       "callable object as its only "
                                       "parameter.");
      Py_DECREF(arg);
      return NULL;
    }
    cpy_build_name(buf, sizeof(buf), arg, NULL);
    name = buf;
  }
  if (unreg(name) == 0) {
    Py_DECREF(arg);
    Py_RETURN_NONE;
  }
  PyErr_Format(PyExc_RuntimeError, "Unable to unregister %s callback '%s'.",
               desc, name);
  Py_DECREF(arg);
  return NULL;
}

static PyObject *cpy_unregister_log(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic_userdata(plugin_unregister_log, arg, "log");
}

static PyObject *cpy_unregister_init(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic(&cpy_init_callbacks, arg, "init");
}

static PyObject *cpy_unregister_config(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic(&cpy_config_callbacks, arg, "config");
}

static PyObject *cpy_unregister_read(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic_userdata(plugin_unregister_read, arg, "read");
}

static PyObject *cpy_unregister_write(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic_userdata(plugin_unregister_write, arg, "write");
}

static PyObject *cpy_unregister_notification(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic_userdata(plugin_unregister_notification, arg,
                                         "notification");
}

static PyObject *cpy_unregister_flush(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic_userdata(plugin_unregister_flush, arg, "flush");
}

static PyObject *cpy_unregister_shutdown(PyObject *self, PyObject *arg) {
  return cpy_unregister_generic(&cpy_shutdown_callbacks, arg, "shutdown");
}

static PyMethodDef cpy_methods[] = {
    {"debug", cpy_debug, METH_VARARGS, log_doc},
    {"info", cpy_info, METH_VARARGS, log_doc},
    {"notice", cpy_notice, METH_VARARGS, log_doc},
    {"warning", cpy_warning, METH_VARARGS, log_doc},
    {"error", cpy_error, METH_VARARGS, log_doc},
    {"get_dataset", (PyCFunction)cpy_get_dataset, METH_VARARGS, get_ds_doc},
    {"flush", (PyCFunction)cpy_flush, METH_VARARGS | METH_KEYWORDS, flush_doc},
    {"register_log", (PyCFunction)cpy_register_log,
     METH_VARARGS | METH_KEYWORDS, reg_log_doc},
    {"register_init", (PyCFunction)cpy_register_init,
     METH_VARARGS | METH_KEYWORDS, reg_init_doc},
    {"register_config", (PyCFunction)cpy_register_config,
     METH_VARARGS | METH_KEYWORDS, reg_config_doc},
    {"register_read", (PyCFunction)cpy_register_read,
     METH_VARARGS | METH_KEYWORDS, reg_read_doc},
    {"register_write", (PyCFunction)cpy_register_write,
     METH_VARARGS | METH_KEYWORDS, reg_write_doc},
    {"register_notification", (PyCFunction)cpy_register_notification,
     METH_VARARGS | METH_KEYWORDS, reg_notification_doc},
    {"register_flush", (PyCFunction)cpy_register_flush,
     METH_VARARGS | METH_KEYWORDS, reg_flush_doc},
    {"register_shutdown", (PyCFunction)cpy_register_shutdown,
     METH_VARARGS | METH_KEYWORDS, reg_shutdown_doc},
    {"unregister_log", cpy_unregister_log, METH_O, unregister_doc},
    {"unregister_init", cpy_unregister_init, METH_O, unregister_doc},
    {"unregister_config", cpy_unregister_config, METH_O, unregister_doc},
    {"unregister_read", cpy_unregister_read, METH_O, unregister_doc},
    {"unregister_write", cpy_unregister_write, METH_O, unregister_doc},
    {"unregister_notification", cpy_unregister_notification, METH_O,
     unregister_doc},
    {"unregister_flush", cpy_unregister_flush, METH_O, unregister_doc},
    {"unregister_shutdown", cpy_unregister_shutdown, METH_O, unregister_doc},
    {0, 0, 0, 0}};

static int cpy_shutdown(void) {
  PyObject *ret;

  if (!state) {
    printf(
        "================================================================\n");
    printf(
        "collectd shutdown while running an interactive session. This will\n");
    printf("probably leave your terminal in a mess.\n");
    printf("Run the command \"reset\" to get it back into a usable state.\n");
    printf("You can press Ctrl+D in the interactive session to\n");
    printf("close collectd and avoid this problem in the future.\n");
    printf(
        "================================================================\n");
  }

  CPY_LOCK_THREADS

  for (cpy_callback_t *c = cpy_shutdown_callbacks; c; c = c->next) {
    ret = PyObject_CallFunctionObjArgs(c->callback, c->data,
                                       (void *)0); /* New reference. */
    if (ret == NULL)
      cpy_log_exception("shutdown callback");
    else
      Py_DECREF(ret);
  }
  PyErr_Print();

  Py_BEGIN_ALLOW_THREADS;
  cpy_unregister_list(&cpy_config_callbacks);
  cpy_unregister_list(&cpy_init_callbacks);
  cpy_unregister_list(&cpy_shutdown_callbacks);
  cpy_shutdown_triggered = 1;
  Py_END_ALLOW_THREADS;

  if (!cpy_num_callbacks) {
    Py_Finalize();
    return 0;
  }

  CPY_RELEASE_THREADS
  return 0;
}

static void *cpy_interactive(void *pipefd) {
  PyOS_sighandler_t cur_sig;

  /* Signal handler in a plugin? Bad stuff, but the best way to
   * handle it I guess. In an interactive session people will
   * press Ctrl+C at some time, which will generate a SIGINT.
   * This will cause collectd to shutdown, thus killing the
   * interactive interpreter, and leaving the terminal in a
   * mess. Chances are, this isn't what the user wanted to do.
   *
   * So this is the plan:
   * 1. Restore Python's own signal handler
   * 2. Tell Python we just forked so it will accept this thread
   *    as the main one. No version of Python will ever handle
   *    interrupts anywhere but in the main thread.
   * 3. After the interactive loop is done, restore collectd's
   *    SIGINT handler.
   * 4. Raise SIGINT for a clean shutdown. The signal is sent to
   *    the main thread to ensure it wakes up the main interval
   *    sleep so that collectd shuts down immediately not in 10
   *    seconds.
   *
   * This will make sure that SIGINT won't kill collectd but
   * still interrupt syscalls like sleep and pause. */

  if (PyImport_ImportModule("readline") == NULL) {
    /* This interactive session will suck. */
    cpy_log_exception("interactive session init");
  }
  cur_sig = PyOS_setsig(SIGINT, python_sigint_handler);
#if PY_VERSION_HEX < 0x03070000
  PyOS_AfterFork();
#else
  PyOS_AfterFork_Child();
#endif
  PyEval_InitThreads();
  close(*(int *)pipefd);
  PyRun_InteractiveLoop(stdin, "<stdin>");
  PyOS_setsig(SIGINT, cur_sig);
  PyErr_Print();
  state = PyEval_SaveThread();
  NOTICE("python: Interactive interpreter exited, stopping collectd ...");
  pthread_kill(main_thread, SIGINT);
  return NULL;
}

static int cpy_init(void) {
  PyObject *ret;
  int pipefd[2];
  char buf;
  static pthread_t thread;

  if (!Py_IsInitialized()) {
    WARNING("python: Plugin loaded but not configured.");
    plugin_unregister_shutdown("python");
    Py_Finalize();
    return 0;
  }
  main_thread = pthread_self();
  if (do_interactive) {
    if (pipe(pipefd)) {
      ERROR("python: Unable to create pipe.");
      return 1;
    }
    if (plugin_thread_create(&thread, cpy_interactive, pipefd + 1,
                             "python interpreter")) {
      ERROR("python: Error creating thread for interactive interpreter.");
    }
    if (read(pipefd[0], &buf, 1))
      ;
    (void)close(pipefd[0]);
  } else {
    PyEval_InitThreads();
    state = PyEval_SaveThread();
  }
  CPY_LOCK_THREADS
  for (cpy_callback_t *c = cpy_init_callbacks; c; c = c->next) {
    ret = PyObject_CallFunctionObjArgs(c->callback, c->data,
                                       (void *)0); /* New reference. */
    if (ret == NULL)
      cpy_log_exception("init callback");
    else
      Py_DECREF(ret);
  }
  CPY_RELEASE_THREADS

  return 0;
}

static PyObject *cpy_oconfig_to_pyconfig(oconfig_item_t *ci, PyObject *parent) {
  PyObject *item, *values, *children, *tmp;

  if (parent == NULL)
    parent = Py_None;

  values = PyTuple_New(ci->values_num); /* New reference. */
  for (int i = 0; i < ci->values_num; ++i) {
    if (ci->values[i].type == OCONFIG_TYPE_STRING) {
      PyTuple_SET_ITEM(
          values, i,
          cpy_string_to_unicode_or_bytes(ci->values[i].value.string));
    } else if (ci->values[i].type == OCONFIG_TYPE_NUMBER) {
      PyTuple_SET_ITEM(values, i,
                       PyFloat_FromDouble(ci->values[i].value.number));
    } else if (ci->values[i].type == OCONFIG_TYPE_BOOLEAN) {
      PyTuple_SET_ITEM(values, i, PyBool_FromLong(ci->values[i].value.boolean));
    }
  }

  tmp = cpy_string_to_unicode_or_bytes(ci->key);
  item = PyObject_CallFunction((void *)&ConfigType, "NONO", tmp, parent, values,
                               Py_None);
  if (item == NULL)
    return NULL;
  children = PyTuple_New(ci->children_num); /* New reference. */
  for (int i = 0; i < ci->children_num; ++i) {
    PyTuple_SET_ITEM(children, i,
                     cpy_oconfig_to_pyconfig(ci->children + i, item));
  }
  tmp = ((Config *)item)->children;
  ((Config *)item)->children = children;
  Py_XDECREF(tmp);
  return item;
}

#ifdef IS_PY3K
static struct PyModuleDef collectdmodule = {
    PyModuleDef_HEAD_INIT, "collectd",  /* name of module */
    "The python interface to collectd", /* module documentation, may be NULL */
    -1, cpy_methods};

PyMODINIT_FUNC PyInit_collectd(void) {
  return PyModule_Create(&collectdmodule);
}
#endif

static int cpy_init_python(void) {
  PyOS_sighandler_t cur_sig;
  PyObject *sys, *errordict;
  PyObject *module;

#ifdef IS_PY3K
  wchar_t *argv = L"";
  /* Add a builtin module, before Py_Initialize */
  PyImport_AppendInittab("collectd", PyInit_collectd);
#else
  char *argv = "";
#endif

  /* Chances are the current signal handler is already SIG_DFL, but let's make
   * sure. */
  cur_sig = PyOS_setsig(SIGINT, SIG_DFL);
  Py_Initialize();
  python_sigint_handler = PyOS_setsig(SIGINT, cur_sig);

  if (PyType_Ready(&ConfigType) == -1) {
    cpy_log_exception("python initialization: ConfigType");
    return 1;
  }
  if (PyType_Ready(&PluginDataType) == -1) {
    cpy_log_exception("python initialization: PluginDataType");
    return 1;
  }
  ValuesType.tp_base = &PluginDataType;
  if (PyType_Ready(&ValuesType) == -1) {
    cpy_log_exception("python initialization: ValuesType");
    return 1;
  }
  NotificationType.tp_base = &PluginDataType;
  if (PyType_Ready(&NotificationType) == -1) {
    cpy_log_exception("python initialization: NotificationType");
    return 1;
  }
  SignedType.tp_base = &PyLong_Type;
  if (PyType_Ready(&SignedType) == -1) {
    cpy_log_exception("python initialization: SignedType");
    return 1;
  }
  UnsignedType.tp_base = &PyLong_Type;
  if (PyType_Ready(&UnsignedType) == -1) {
    cpy_log_exception("python initialization: UnsignedType");
    return 1;
  }
  errordict = PyDict_New();
  PyDict_SetItemString(
      errordict, "__doc__",
      cpy_string_to_unicode_or_bytes(CollectdError_doc)); /* New reference. */
  CollectdError = PyErr_NewException("collectd.CollectdError", NULL, errordict);
  sys = PyImport_ImportModule("sys"); /* New reference. */
  if (sys == NULL) {
    cpy_log_exception("python initialization");
    return 1;
  }
  sys_path = PyObject_GetAttrString(sys, "path"); /* New reference. */
  Py_DECREF(sys);
  if (sys_path == NULL) {
    cpy_log_exception("python initialization");
    return 1;
  }
  PySys_SetArgv(1, &argv);
  PyList_SetSlice(sys_path, 0, 1, NULL);

#ifdef IS_PY3K
  module = PyImport_ImportModule("collectd");
#else
  module = Py_InitModule("collectd", cpy_methods); /* Borrowed reference. */
#endif
  PyModule_AddObject(module, "Config",
                     (void *)&ConfigType); /* Steals a reference. */
  PyModule_AddObject(module, "Values",
                     (void *)&ValuesType); /* Steals a reference. */
  PyModule_AddObject(module, "Notification",
                     (void *)&NotificationType); /* Steals a reference. */
  PyModule_AddObject(module, "Signed",
                     (void *)&SignedType); /* Steals a reference. */
  PyModule_AddObject(module, "Unsigned",
                     (void *)&UnsignedType); /* Steals a reference. */
  Py_XINCREF(CollectdError);
  PyModule_AddObject(module, "CollectdError",
                     CollectdError); /* Steals a reference. */
  PyModule_AddIntConstant(module, "LOG_DEBUG", LOG_DEBUG);
  PyModule_AddIntConstant(module, "LOG_INFO", LOG_INFO);
  PyModule_AddIntConstant(module, "LOG_NOTICE", LOG_NOTICE);
  PyModule_AddIntConstant(module, "LOG_WARNING", LOG_WARNING);
  PyModule_AddIntConstant(module, "LOG_ERROR", LOG_ERR);
  PyModule_AddIntConstant(module, "NOTIF_FAILURE", NOTIF_FAILURE);
  PyModule_AddIntConstant(module, "NOTIF_WARNING", NOTIF_WARNING);
  PyModule_AddIntConstant(module, "NOTIF_OKAY", NOTIF_OKAY);
  PyModule_AddStringConstant(module, "DS_TYPE_COUNTER",
                             DS_TYPE_TO_STRING(DS_TYPE_COUNTER));
  PyModule_AddStringConstant(module, "DS_TYPE_GAUGE",
                             DS_TYPE_TO_STRING(DS_TYPE_GAUGE));
  PyModule_AddStringConstant(module, "DS_TYPE_DERIVE",
                             DS_TYPE_TO_STRING(DS_TYPE_DERIVE));
  PyModule_AddStringConstant(module, "DS_TYPE_ABSOLUTE",
                             DS_TYPE_TO_STRING(DS_TYPE_ABSOLUTE));
  return 0;
}

static int cpy_config(oconfig_item_t *ci) {
  PyObject *tb;
  int status = 0;

  /* Ok in theory we shouldn't do initialization at this point
   * but we have to. In order to give python scripts a chance
   * to register a config callback we need to be able to execute
   * python code during the config callback so we have to start
   * the interpreter here. */
  /* Do *not* use the python "thread" module at this point! */

  if (!Py_IsInitialized() && cpy_init_python())
    return 1;

  for (int i = 0; i < ci->children_num; ++i) {
    oconfig_item_t *item = ci->children + i;

    if (strcasecmp(item->key, "Interactive") == 0) {
      if (cf_util_get_boolean(item, &do_interactive) != 0) {
        status = 1;
        continue;
      }
    } else if (strcasecmp(item->key, "Encoding") == 0) {
      char *encoding = NULL;
      if (cf_util_get_string(item, &encoding) != 0) {
        status = 1;
        continue;
      }
#ifdef IS_PY3K
      ERROR("python: \"Encoding\" was used in the config file but Python3 was "
            "used, which does not support changing encodings");
      status = 1;
      sfree(encoding);
      continue;
#else
      /* Why is this even necessary? And undocumented? */
      if (PyUnicode_SetDefaultEncoding(encoding)) {
        cpy_log_exception("setting default encoding");
        status = 1;
      }
#endif
      sfree(encoding);
    } else if (strcasecmp(item->key, "LogTraces") == 0) {
      bool log_traces;
      if (cf_util_get_boolean(item, &log_traces) != 0) {
        status = 1;
        continue;
      }
      if (!log_traces) {
        Py_XDECREF(cpy_format_exception);
        cpy_format_exception = NULL;
        continue;
      }
      if (cpy_format_exception)
        continue;
      tb = PyImport_ImportModule("traceback"); /* New reference. */
      if (tb == NULL) {
        cpy_log_exception("python initialization");
        status = 1;
        continue;
      }
      cpy_format_exception =
          PyObject_GetAttrString(tb, "format_exception"); /* New reference. */
      Py_DECREF(tb);
      if (cpy_format_exception == NULL) {
        cpy_log_exception("python initialization");
        status = 1;
      }
    } else if (strcasecmp(item->key, "ModulePath") == 0) {
      char *dir = NULL;
      PyObject *dir_object;

      if (cf_util_get_string(item, &dir) != 0) {
        status = 1;
        continue;
      }
      dir_object = cpy_string_to_unicode_or_bytes(dir); /* New reference. */
      if (dir_object == NULL) {
        ERROR("python plugin: Unable to convert \"%s\" to "
              "a python object.",
              dir);
        free(dir);
        cpy_log_exception("python initialization");
        status = 1;
        continue;
      }
      if (PyList_Insert(sys_path, 0, dir_object) != 0) {
        ERROR("python plugin: Unable to prepend \"%s\" to "
              "python module path.",
              dir);
        cpy_log_exception("python initialization");
        status = 1;
      }
      Py_DECREF(dir_object);
      free(dir);
    } else if (strcasecmp(item->key, "Import") == 0) {
      char *module_name = NULL;
      PyObject *module;

      if (cf_util_get_string(item, &module_name) != 0) {
        status = 1;
        continue;
      }
      module = PyImport_ImportModule(module_name); /* New reference. */
      if (module == NULL) {
        ERROR("python plugin: Error importing module \"%s\".", module_name);
        cpy_log_exception("importing module");
        status = 1;
      }
      free(module_name);
      Py_XDECREF(module);
    } else if (strcasecmp(item->key, "Module") == 0) {
      char *name = NULL;
      cpy_callback_t *c;
      PyObject *ret;

      if (cf_util_get_string(item, &name) != 0) {
        status = 1;
        continue;
      }
      for (c = cpy_config_callbacks; c; c = c->next) {
        if (strcasecmp(c->name + 7, name) == 0)
          break;
      }
      if (c == NULL) {
        WARNING("python plugin: Found a configuration for the \"%s\" plugin, "
                "but the plugin isn't loaded or didn't register "
                "a configuration callback.",
                name);
        free(name);
        continue;
      }
      free(name);
      if (c->data == NULL)
        ret = PyObject_CallFunction(
            c->callback, "N",
            cpy_oconfig_to_pyconfig(item, NULL)); /* New reference. */
      else
        ret = PyObject_CallFunction(c->callback, "NO",
                                    cpy_oconfig_to_pyconfig(item, NULL),
                                    c->data); /* New reference. */
      if (ret == NULL) {
        cpy_log_exception("loading module");
        status = 1;
      } else
        Py_DECREF(ret);
    } else {
      ERROR("python plugin: Unknown config key \"%s\".", item->key);
      status = 1;
    }
  }
  return status;
}

void module_register(void) {
  plugin_register_complex_config("python", cpy_config);
  plugin_register_init("python", cpy_init);
  plugin_register_shutdown("python", cpy_shutdown);
}
