diff options
| author | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2015-07-05 14:16:08 -0400 |
|---|---|---|
| committer | Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> | 2015-07-05 14:19:22 -0400 |
| commit | 022c9b5be4a1edf77bf73566a89ce41397f72147 (patch) | |
| tree | bfbb6fa60200df1edca05a5856810ad257114076 | |
| parent | 5f531b971e8c75cd9c93044dcdd46880c65d0502 (diff) | |
| parent | 2a3fc158a5160e8da77852fc71ee49c6f359fd56 (diff) | |
| download | python-systemd-022c9b5be4a1edf77bf73566a89ce41397f72147.tar.gz | |
Merge development in systemd upstream
| -rw-r--r-- | systemd/.gitignore | 2 | ||||
| -rw-r--r-- | systemd/__init__.py | 18 | ||||
| -rw-r--r-- | systemd/_daemon.c | 331 | ||||
| -rw-r--r-- | systemd/_journal.c | 223 | ||||
| -rw-r--r-- | systemd/_reader.c | 1106 | ||||
| -rw-r--r-- | systemd/daemon.py | 55 | ||||
| -rw-r--r-- | systemd/docs/.gitignore | 1 | ||||
| -rw-r--r-- | systemd/docs/conf.py | 279 | ||||
| -rw-r--r-- | systemd/docs/daemon.rst | 18 | ||||
| -rw-r--r-- | systemd/docs/default.css | 196 | ||||
| -rw-r--r-- | systemd/docs/id128.rst | 40 | ||||
| -rw-r--r-- | systemd/docs/index.rst | 24 | ||||
| -rw-r--r-- | systemd/docs/journal.rst | 64 | ||||
| -rw-r--r-- | systemd/docs/layout.html | 15 | ||||
| -rw-r--r-- | systemd/docs/login.rst | 28 | ||||
| -rw-r--r-- | systemd/id128.c | 163 | ||||
| -rw-r--r-- | systemd/journal.py | 577 | ||||
| -rw-r--r-- | systemd/login.c | 376 | ||||
| -rw-r--r-- | systemd/pyutil.c | 80 | ||||
| -rw-r--r-- | systemd/pyutil.h | 54 |
20 files changed, 3489 insertions, 161 deletions
diff --git a/systemd/.gitignore b/systemd/.gitignore new file mode 100644 index 0000000..4124b7a --- /dev/null +++ b/systemd/.gitignore @@ -0,0 +1,2 @@ +/id128-constants.h +*.py[oc] diff --git a/systemd/__init__.py b/systemd/__init__.py index e69de29..0d56b99 100644 --- a/systemd/__init__.py +++ b/systemd/__init__.py @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2012 David Strauss +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. diff --git a/systemd/_daemon.c b/systemd/_daemon.c new file mode 100644 index 0000000..7c5f1b2 --- /dev/null +++ b/systemd/_daemon.c @@ -0,0 +1,331 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#define PY_SSIZE_T_CLEAN +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#include <Python.h> +#pragma GCC diagnostic pop + +#include <stdbool.h> +#include <assert.h> +#include <sys/socket.h> + +#include "systemd/sd-daemon.h" +#include "pyutil.h" +#include "macro.h" + +PyDoc_STRVAR(module__doc__, + "Python interface to the libsystemd-daemon library.\n\n" + "Provides _listen_fds, notify, booted, and is_* functions\n" + "which wrap sd_listen_fds, sd_notify, sd_booted, sd_is_* and\n" + "useful for socket activation and checking if the system is\n" + "running under systemd." +); + +PyDoc_STRVAR(booted__doc__, + "booted() -> bool\n\n" + "Return True iff this system is running under systemd.\n" + "Wraps sd_daemon_booted(3)." +); + +static PyObject* booted(PyObject *self, PyObject *args) { + int r; + assert(args == NULL); + + r = sd_booted(); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + +PyDoc_STRVAR(notify__doc__, + "notify(status, unset_environment=False) -> bool\n\n" + "Send a message to the init system about a status change.\n" + "Wraps sd_notify(3)."); + +static PyObject* notify(PyObject *self, PyObject *args, PyObject *keywds) { + int r; + const char* msg; + int unset = false; + + static const char* const kwlist[] = { + "status", + "unset_environment", + NULL, + }; +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|p:notify", + (char**) kwlist, &msg, &unset)) + return NULL; +#else + PyObject *obj = NULL; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "s|O:notify", + (char**) kwlist, &msg, &obj)) + return NULL; + if (obj != NULL) + unset = PyObject_IsTrue(obj); + if (unset < 0) + return NULL; +#endif + + r = sd_notify(unset, msg); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(listen_fds__doc__, + "_listen_fds(unset_environment=True) -> int\n\n" + "Return the number of descriptors passed to this process by the init system\n" + "as part of the socket-based activation logic.\n" + "Wraps sd_listen_fds(3)." +); + +static PyObject* listen_fds(PyObject *self, PyObject *args, PyObject *keywds) { + int r; + int unset = true; + + static const char* const kwlist[] = {"unset_environment", NULL}; +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|p:_listen_fds", + (char**) kwlist, &unset)) + return NULL; +#else + PyObject *obj = NULL; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|O:_listen_fds", + (char**) kwlist, &obj)) + return NULL; + if (obj != NULL) + unset = PyObject_IsTrue(obj); + if (unset < 0) + return NULL; +#endif + + r = sd_listen_fds(unset); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return long_FromLong(r); +} + +PyDoc_STRVAR(is_fifo__doc__, + "_is_fifo(fd, path) -> bool\n\n" + "Returns True iff the descriptor refers to a FIFO or a pipe.\n" + "Wraps sd_is_fifo(3)." +); + + +static PyObject* is_fifo(PyObject *self, PyObject *args) { + int r; + int fd; + const char *path = NULL; + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + if (!PyArg_ParseTuple(args, "i|O&:_is_fifo", + &fd, Unicode_FSConverter, &path)) + return NULL; +#else + if (!PyArg_ParseTuple(args, "i|z:_is_fifo", &fd, &path)) + return NULL; +#endif + + r = sd_is_fifo(fd, path); + if (set_error(r, path, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(is_mq__doc__, + "_is_mq(fd, path) -> bool\n\n" + "Returns True iff the descriptor refers to a POSIX message queue.\n" + "Wraps sd_is_mq(3)." +); + +static PyObject* is_mq(PyObject *self, PyObject *args) { + int r; + int fd; + const char *path = NULL; + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + if (!PyArg_ParseTuple(args, "i|O&:_is_mq", + &fd, Unicode_FSConverter, &path)) + return NULL; +#else + if (!PyArg_ParseTuple(args, "i|z:_is_mq", &fd, &path)) + return NULL; +#endif + + r = sd_is_mq(fd, path); + if (set_error(r, path, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + + + +PyDoc_STRVAR(is_socket__doc__, + "_is_socket(fd, family=AF_UNSPEC, type=0, listening=-1) -> bool\n\n" + "Returns True iff the descriptor refers to a socket.\n" + "Wraps sd_is_socket(3).\n\n" + "Constants for `family` are defined in the socket module." +); + +static PyObject* is_socket(PyObject *self, PyObject *args) { + int r; + int fd, family = AF_UNSPEC, type = 0, listening = -1; + + if (!PyArg_ParseTuple(args, "i|iii:_is_socket", + &fd, &family, &type, &listening)) + return NULL; + + r = sd_is_socket(fd, family, type, listening); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(is_socket_inet__doc__, + "_is_socket_inet(fd, family=AF_UNSPEC, type=0, listening=-1, port=0) -> bool\n\n" + "Wraps sd_is_socket_inet(3).\n\n" + "Constants for `family` are defined in the socket module." +); + +static PyObject* is_socket_inet(PyObject *self, PyObject *args) { + int r; + int fd, family = AF_UNSPEC, type = 0, listening = -1, port = 0; + + if (!PyArg_ParseTuple(args, "i|iiii:_is_socket_inet", + &fd, &family, &type, &listening, &port)) + return NULL; + + if (port < 0 || port > UINT16_MAX) { + set_error(-EINVAL, NULL, "port must fit into uint16_t"); + return NULL; + } + + r = sd_is_socket_inet(fd, family, type, listening, (uint16_t) port); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(is_socket_unix__doc__, + "_is_socket_unix(fd, type, listening, path) -> bool\n\n" + "Wraps sd_is_socket_unix(3)." +); + +static PyObject* is_socket_unix(PyObject *self, PyObject *args) { + int r; + int fd, type = 0, listening = -1; + char* path = NULL; + Py_ssize_t length = 0; + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + _cleanup_Py_DECREF_ PyObject *_path = NULL; + if (!PyArg_ParseTuple(args, "i|iiO&:_is_socket_unix", + &fd, &type, &listening, Unicode_FSConverter, &_path)) + return NULL; + if (_path) { + assert(PyBytes_Check(_path)); + if (PyBytes_AsStringAndSize(_path, &path, &length)) + return NULL; + } +#else + if (!PyArg_ParseTuple(args, "i|iiz#:_is_socket_unix", + &fd, &type, &listening, &path, &length)) + return NULL; +#endif + + r = sd_is_socket_unix(fd, type, listening, path, length); + if (set_error(r, path, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + + +static PyMethodDef methods[] = { + { "booted", booted, METH_NOARGS, booted__doc__}, + { "notify", (PyCFunction) notify, METH_VARARGS | METH_KEYWORDS, notify__doc__}, + { "_listen_fds", (PyCFunction) listen_fds, METH_VARARGS | METH_KEYWORDS, listen_fds__doc__}, + { "_is_fifo", is_fifo, METH_VARARGS, is_fifo__doc__}, + { "_is_mq", is_mq, METH_VARARGS, is_mq__doc__}, + { "_is_socket", is_socket, METH_VARARGS, is_socket__doc__}, + { "_is_socket_inet", is_socket_inet, METH_VARARGS, is_socket_inet__doc__}, + { "_is_socket_unix", is_socket_unix, METH_VARARGS, is_socket_unix__doc__}, + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +#if PY_MAJOR_VERSION < 3 + +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC init_daemon(void) { + PyObject *m; + + m = Py_InitModule3("_daemon", methods, module__doc__); + if (m == NULL) + return; + + PyModule_AddIntConstant(m, "LISTEN_FDS_START", SD_LISTEN_FDS_START); + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); +} +REENABLE_WARNING; + +#else + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "_daemon", /* name of module */ + module__doc__, /* module documentation, may be NULL */ + 0, /* size of per-interpreter state of the module */ + methods +}; + +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC PyInit__daemon(void) { + PyObject *m; + + m = PyModule_Create(&module); + if (m == NULL) + return NULL; + + if (PyModule_AddIntConstant(m, "LISTEN_FDS_START", SD_LISTEN_FDS_START) || + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { + Py_DECREF(m); + return NULL; + } + + return m; +} +REENABLE_WARNING; + +#endif diff --git a/systemd/_journal.c b/systemd/_journal.c index b766ec6..456e4a2 100644 --- a/systemd/_journal.c +++ b/systemd/_journal.c @@ -1,134 +1,157 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2012 David Strauss <david@davidstrauss.net> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + #include <Python.h> + +#include <alloca.h> +#include "util.h" + #define SD_JOURNAL_SUPPRESS_LOCATION -#include <systemd/sd-journal.h> +#include "systemd/sd-journal.h" PyDoc_STRVAR(journal_sendv__doc__, "sendv('FIELD=value', 'FIELD=value', ...) -> None\n\n" "Send an entry to the journal." - ); - -static PyObject * -journal_sendv(PyObject *self, PyObject *args) { - struct iovec *iov = NULL; - int argc = PyTuple_Size(args); - int i, r; - PyObject *ret = NULL; - - PyObject **encoded = calloc(argc, sizeof(PyObject*)); - if (!encoded) { - ret = PyErr_NoMemory(); - goto out1; - } - - // Allocate sufficient iovector space for the arguments. - iov = malloc(argc * sizeof(struct iovec)); - if (!iov) { - ret = PyErr_NoMemory(); - goto out; - } - - // Iterate through the Python arguments and fill the iovector. - for (i = 0; i < argc; ++i) { - PyObject *item = PyTuple_GetItem(args, i); - char *stritem; - Py_ssize_t length; - - if (PyUnicode_Check(item)) { - encoded[i] = PyUnicode_AsEncodedString(item, "utf-8", "strict"); - if (encoded[i] == NULL) - goto out; - item = encoded[i]; +); + +static PyObject *journal_sendv(PyObject *self, PyObject *args) { + struct iovec *iov = NULL; + int argc; + int i, r; + PyObject *ret = NULL; + PyObject **encoded; + + /* Allocate an array for the argument strings */ + argc = PyTuple_Size(args); + encoded = alloca0(argc * sizeof(PyObject*)); + + /* Allocate sufficient iovector space for the arguments. */ + iov = alloca(argc * sizeof(struct iovec)); + + /* Iterate through the Python arguments and fill the iovector. */ + for (i = 0; i < argc; ++i) { + PyObject *item = PyTuple_GetItem(args, i); + char *stritem; + Py_ssize_t length; + + if (PyUnicode_Check(item)) { + encoded[i] = PyUnicode_AsEncodedString(item, "utf-8", "strict"); + if (encoded[i] == NULL) + goto out; + item = encoded[i]; + } + if (PyBytes_AsStringAndSize(item, &stritem, &length)) + goto out; + + iov[i].iov_base = stritem; + iov[i].iov_len = length; } - if (PyBytes_AsStringAndSize(item, &stritem, &length)) - goto out; - - iov[i].iov_base = stritem; - iov[i].iov_len = length; - } - - // Clear errno, because sd_journal_sendv will not set it by - // itself, unless an error occurs in one of the system calls. - errno = 0; - // Send the iovector to the journal. - r = sd_journal_sendv(iov, argc); - - if (r) { - if (errno) - PyErr_SetFromErrno(PyExc_IOError); - else - PyErr_SetString(PyExc_ValueError, "invalid message format"); - goto out; - } + /* Send the iovector to the journal. */ + r = sd_journal_sendv(iov, argc); + if (r < 0) { + errno = -r; + PyErr_SetFromErrno(PyExc_IOError); + goto out; + } - // End with success. - Py_INCREF(Py_None); - ret = Py_None; + /* End with success. */ + Py_INCREF(Py_None); + ret = Py_None; out: - for (i = 0; i < argc; ++i) - Py_XDECREF(encoded[i]); + for (i = 0; i < argc; ++i) + Py_XDECREF(encoded[i]); - free(encoded); - -out1: - // Free the iovector. The actual strings - // are already managed by Python. - free(iov); - - return ret; + return ret; } PyDoc_STRVAR(journal_stream_fd__doc__, "stream_fd(identifier, priority, level_prefix) -> fd\n\n" "Open a stream to journal by calling sd_journal_stream_fd(3)." - ); - -static PyObject* -journal_stream_fd(PyObject *self, PyObject *args) { - const char* identifier; - int priority, level_prefix; - int fd; - if (!PyArg_ParseTuple(args, "sii:stream_fd", - &identifier, &priority, &level_prefix)) - return NULL; - - fd = sd_journal_stream_fd(identifier, priority, level_prefix); - if (fd < 0) - return PyErr_SetFromErrno(PyExc_IOError); - - return PyLong_FromLong(fd); +); + +static PyObject* journal_stream_fd(PyObject *self, PyObject *args) { + const char* identifier; + int priority, level_prefix; + int fd; + + if (!PyArg_ParseTuple(args, "sii:stream_fd", + &identifier, &priority, &level_prefix)) + return NULL; + + fd = sd_journal_stream_fd(identifier, priority, level_prefix); + if (fd < 0) { + errno = -fd; + return PyErr_SetFromErrno(PyExc_IOError); + } + + return PyLong_FromLong(fd); } static PyMethodDef methods[] = { - {"sendv", journal_sendv, METH_VARARGS, journal_sendv__doc__}, - {"stream_fd", journal_stream_fd, METH_VARARGS, - journal_stream_fd__doc__}, - {NULL, NULL, 0, NULL} /* Sentinel */ + { "sendv", journal_sendv, METH_VARARGS, journal_sendv__doc__ }, + { "stream_fd", journal_stream_fd, METH_VARARGS, journal_stream_fd__doc__ }, + { NULL, NULL, 0, NULL } /* Sentinel */ }; #if PY_MAJOR_VERSION < 3 -PyMODINIT_FUNC -init_journal(void) -{ - (void) Py_InitModule("_journal", methods); +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC init_journal(void) { + PyObject *m; + + m = Py_InitModule("_journal", methods); + if (m == NULL) + return; + + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); } +REENABLE_WARNING; #else static struct PyModuleDef module = { - PyModuleDef_HEAD_INIT, - "_journal", /* name of module */ - NULL, /* module documentation, may be NULL */ - 0, /* size of per-interpreter state of the module */ - methods + PyModuleDef_HEAD_INIT, + "_journal", /* name of module */ + NULL, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module */ + methods }; -PyMODINIT_FUNC -PyInit__journal(void) -{ - return PyModule_Create(&module); +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC PyInit__journal(void) { + PyObject *m; + + m = PyModule_Create(&module); + if (m == NULL) + return NULL; + + if (PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { + Py_DECREF(m); + return NULL; + } + + return m; } +REENABLE_WARNING; #endif diff --git a/systemd/_reader.c b/systemd/_reader.c new file mode 100644 index 0000000..3a56126 --- /dev/null +++ b/systemd/_reader.c @@ -0,0 +1,1106 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Steven Hiscocks, Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <Python.h> +#include <structmember.h> +#include <datetime.h> +#include <time.h> +#include <stdio.h> + +#include "systemd/sd-journal.h" + +#include "pyutil.h" +#include "macro.h" +#include "util.h" +#include "strv.h" +#include "build.h" + +typedef struct { + PyObject_HEAD + sd_journal *j; +} Reader; +static PyTypeObject ReaderType; + +PyDoc_STRVAR(module__doc__, + "Class to reads the systemd journal similar to journalctl."); + + +#if PY_MAJOR_VERSION >= 3 +static PyTypeObject MonotonicType; + +PyDoc_STRVAR(MonotonicType__doc__, + "A tuple of (timestamp, bootid) for holding monotonic timestamps"); + +static PyStructSequence_Field MonotonicType_fields[] = { + {(char*) "timestamp", (char*) "Time"}, + {(char*) "bootid", (char*) "Unique identifier of the boot"}, + {} /* Sentinel */ +}; + +static PyStructSequence_Desc Monotonic_desc = { + (char*) "journal.Monotonic", + MonotonicType__doc__, + MonotonicType_fields, + 2, +}; +#endif + +/** + * Convert a Python sequence object into a strv (char**), and + * None into a NULL pointer. + */ +static int strv_converter(PyObject* obj, void *_result) { + char ***result = _result; + Py_ssize_t i, len; + + assert(result); + + if (!obj) + return 0; + + if (obj == Py_None) { + *result = NULL; + return 1; + } + + if (!PySequence_Check(obj)) + return 0; + + len = PySequence_Length(obj); + *result = new0(char*, len + 1); + if (!*result) { + set_error(-ENOMEM, NULL, NULL); + return 0; + } + + for (i = 0; i < len; i++) { + PyObject *item; +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + int r; + PyObject *bytes; +#endif + char *s, *s2; + + item = PySequence_ITEM(obj, i); +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + r = PyUnicode_FSConverter(item, &bytes); + if (r == 0) + goto cleanup; + + s = PyBytes_AsString(bytes); +#else + s = PyString_AsString(item); +#endif + if (!s) + goto cleanup; + + s2 = strdup(s); + if (!s2) + log_oom(); + + (*result)[i] = s2; + } + + return 1; + +cleanup: + strv_free(*result); + *result = NULL; + + return 0; +} + +static void Reader_dealloc(Reader* self) { + sd_journal_close(self->j); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +PyDoc_STRVAR(Reader__doc__, + "_Reader([flags | path | files]) -> ...\n\n" + "_Reader allows filtering and retrieval of Journal entries.\n" + "Note: this is a low-level interface, and probably not what you\n" + "want, use systemd.journal.Reader instead.\n\n" + "Argument `flags` sets open flags of the journal, which can be one\n" + "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n" + "journal on local machine only; RUNTIME_ONLY opens only\n" + "volatile journal files; and SYSTEM opens journal files of\n" + "system services and the kernel, and CURRENT_USER opens files\n" + "of the current user.\n\n" + "Argument `path` is the directory of journal files.\n" + "Argument `files` is a list of files. Note that\n" + "`flags`, `path`, and `files` are exclusive.\n\n" + "_Reader implements the context manager protocol: the journal\n" + "will be closed when exiting the block."); +static int Reader_init(Reader *self, PyObject *args, PyObject *keywds) { + int flags = 0, r; + char *path = NULL; + char **files = NULL; + + static const char* const kwlist[] = {"flags", "path", "files", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izO&:__init__", (char**) kwlist, + &flags, &path, strv_converter, &files)) + return -1; + + if (!!flags + !!path + !!files > 1) { + PyErr_SetString(PyExc_ValueError, "cannot use more than one of flags, path, and files"); + return -1; + } + + if (!flags) + flags = SD_JOURNAL_LOCAL_ONLY; + + Py_BEGIN_ALLOW_THREADS + if (path) + r = sd_journal_open_directory(&self->j, path, 0); + else if (files) + r = sd_journal_open_files(&self->j, (const char**) files, 0); + else + r = sd_journal_open(&self->j, flags); + Py_END_ALLOW_THREADS + + return set_error(r, path, "Invalid flags or path"); +} + +PyDoc_STRVAR(Reader_fileno__doc__, + "fileno() -> int\n\n" + "Get a file descriptor to poll for changes in the journal.\n" + "This method invokes sd_journal_get_fd().\n" + "See man:sd_journal_get_fd(3)."); +static PyObject* Reader_fileno(Reader *self, PyObject *args) { + int fd; + + fd = sd_journal_get_fd(self->j); + set_error(fd, NULL, NULL); + if (fd < 0) + return NULL; + return long_FromLong(fd); +} + +PyDoc_STRVAR(Reader_reliable_fd__doc__, + "reliable_fd() -> bool\n\n" + "Returns True iff the journal can be polled reliably.\n" + "This method invokes sd_journal_reliable_fd().\n" + "See man:sd_journal_reliable_fd(3)."); +static PyObject* Reader_reliable_fd(Reader *self, PyObject *args) { + int r; + + r = sd_journal_reliable_fd(self->j); + if (set_error(r, NULL, NULL) < 0) + return NULL; + return PyBool_FromLong(r); +} + +PyDoc_STRVAR(Reader_get_events__doc__, + "get_events() -> int\n\n" + "Returns a mask of poll() events to wait for on the file\n" + "descriptor returned by .fileno().\n\n" + "See man:sd_journal_get_events(3) for further discussion."); +static PyObject* Reader_get_events(Reader *self, PyObject *args) { + int r; + + r = sd_journal_get_events(self->j); + if (set_error(r, NULL, NULL) < 0) + return NULL; + return long_FromLong(r); +} + +PyDoc_STRVAR(Reader_get_timeout__doc__, + "get_timeout() -> int or None\n\n" + "Returns a timeout value for usage in poll(), the time since the\n" + "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" + "is necessary.\n\n" + "The return value must be converted to a relative timeout in\n" + "milliseconds if it is to be used as an argument for poll().\n" + "See man:sd_journal_get_timeout(3) for further discussion."); +static PyObject* Reader_get_timeout(Reader *self, PyObject *args) { + int r; + uint64_t t; + + r = sd_journal_get_timeout(self->j, &t); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + if (t == (uint64_t) -1) + Py_RETURN_NONE; + + assert_cc(sizeof(unsigned long long) == sizeof(t)); + return PyLong_FromUnsignedLongLong(t); +} + +PyDoc_STRVAR(Reader_get_timeout_ms__doc__, + "get_timeout_ms() -> int\n\n" + "Returns a timeout value suitable for usage in poll(), the value\n" + "returned by .get_timeout() converted to relative ms, or -1 if\n" + "no timeout is necessary."); +static PyObject* Reader_get_timeout_ms(Reader *self, PyObject *args) { + int r; + uint64_t t; + + r = sd_journal_get_timeout(self->j, &t); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return absolute_timeout(t); +} + +PyDoc_STRVAR(Reader_close__doc__, + "close() -> None\n\n" + "Free resources allocated by this Reader object.\n" + "This method invokes sd_journal_close().\n" + "See man:sd_journal_close(3)."); +static PyObject* Reader_close(Reader *self, PyObject *args) { + assert(self); + assert(!args); + + sd_journal_close(self->j); + self->j = NULL; + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_get_usage__doc__, + "get_usage() -> int\n\n" + "Returns the total disk space currently used by journal\n" + "files (in bytes). If `SD_JOURNAL_LOCAL_ONLY` was\n" + "passed when opening the journal this value will only reflect\n" + "the size of journal files of the local host, otherwise\n" + "of all hosts.\n\n" + "This method invokes sd_journal_get_usage().\n" + "See man:sd_journal_get_usage(3)."); +static PyObject* Reader_get_usage(Reader *self, PyObject *args) { + int r; + uint64_t bytes; + + r = sd_journal_get_usage(self->j, &bytes); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + assert_cc(sizeof(unsigned long long) == sizeof(bytes)); + return PyLong_FromUnsignedLongLong(bytes); +} + +PyDoc_STRVAR(Reader___enter____doc__, + "__enter__() -> self\n\n" + "Part of the context manager protocol.\n" + "Returns self.\n"); +static PyObject* Reader___enter__(PyObject *self, PyObject *args) { + assert(self); + assert(!args); + + Py_INCREF(self); + return self; +} + +PyDoc_STRVAR(Reader___exit____doc__, + "__exit__(type, value, traceback) -> None\n\n" + "Part of the context manager protocol.\n" + "Closes the journal.\n"); +static PyObject* Reader___exit__(Reader *self, PyObject *args) { + return Reader_close(self, NULL); +} + +PyDoc_STRVAR(Reader_next__doc__, + "next([skip]) -> bool\n\n" + "Go to the next log entry. Optional skip value means to go to\n" + "the `skip`\\-th log entry.\n" + "Returns False if at end of file, True otherwise."); +static PyObject* Reader_next(Reader *self, PyObject *args) { + int64_t skip = 1LL; + int r; + + if (!PyArg_ParseTuple(args, "|L:next", &skip)) + return NULL; + + if (skip == 0LL) { + PyErr_SetString(PyExc_ValueError, "skip must be nonzero"); + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + if (skip == 1LL) + r = sd_journal_next(self->j); + else if (skip == -1LL) + r = sd_journal_previous(self->j); + else if (skip > 1LL) + r = sd_journal_next_skip(self->j, skip); + else if (skip < -1LL) + r = sd_journal_previous_skip(self->j, -skip); + else + assert_not_reached("should not be here"); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + return PyBool_FromLong(r); +} + +PyDoc_STRVAR(Reader_previous__doc__, + "previous([skip]) -> bool\n\n" + "Go to the previous log entry. Optional skip value means to \n" + "go to the `skip`\\-th previous log entry.\n" + "Returns False if at start of file, True otherwise."); +static PyObject* Reader_previous(Reader *self, PyObject *args) { + int64_t skip = 1LL; + if (!PyArg_ParseTuple(args, "|L:previous", &skip)) + return NULL; + + return PyObject_CallMethod((PyObject *)self, (char*) "_next", + (char*) "L", -skip); +} + +static int extract(const char* msg, size_t msg_len, + PyObject **key, PyObject **value) { + PyObject *k = NULL, *v; + const char *delim_ptr; + + delim_ptr = memchr(msg, '=', msg_len); + if (!delim_ptr) { + PyErr_SetString(PyExc_OSError, + "journal gave us a field without '='"); + return -1; + } + + if (key) { + k = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg); + if (!k) + return -1; + } + + if (value) { + v = PyBytes_FromStringAndSize(delim_ptr + 1, + (const char*) msg + msg_len - (delim_ptr + 1)); + if (!v) { + Py_XDECREF(k); + return -1; + } + + *value = v; + } + + if (key) + *key = k; + + return 0; +} + +PyDoc_STRVAR(Reader_get__doc__, + "get(str) -> str\n\n" + "Return data associated with this key in current log entry.\n" + "Throws KeyError is the data is not available."); +static PyObject* Reader_get(Reader *self, PyObject *args) { + const char* field; + const void* msg; + size_t msg_len; + PyObject *value; + int r; + + assert(self); + assert(args); + + if (!PyArg_ParseTuple(args, "s:get", &field)) + return NULL; + + r = sd_journal_get_data(self->j, field, &msg, &msg_len); + if (r == -ENOENT) { + PyErr_SetString(PyExc_KeyError, field); + return NULL; + } + if (set_error(r, NULL, "field name is not valid") < 0) + return NULL; + + r = extract(msg, msg_len, NULL, &value); + if (r < 0) + return NULL; + return value; +} + +PyDoc_STRVAR(Reader_get_all__doc__, + "_get_all() -> dict\n\n" + "Return dictionary of the current log entry."); +static PyObject* Reader_get_all(Reader *self, PyObject *args) { + PyObject *dict; + const void *msg; + size_t msg_len; + int r; + + dict = PyDict_New(); + if (!dict) + return NULL; + + SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) { + _cleanup_Py_DECREF_ PyObject *key = NULL, *value = NULL; + + r = extract(msg, msg_len, &key, &value); + if (r < 0) + goto error; + + if (PyDict_Contains(dict, key)) { + PyObject *cur_value = PyDict_GetItem(dict, key); + + if (PyList_CheckExact(cur_value)) { + r = PyList_Append(cur_value, value); + if (r < 0) + goto error; + } else { + _cleanup_Py_DECREF_ PyObject *tmp_list = PyList_New(0); + if (!tmp_list) + goto error; + + r = PyList_Append(tmp_list, cur_value); + if (r < 0) + goto error; + + r = PyList_Append(tmp_list, value); + if (r < 0) + goto error; + + r = PyDict_SetItem(dict, key, tmp_list); + if (r < 0) + goto error; + } + } else { + r = PyDict_SetItem(dict, key, value); + if (r < 0) + goto error; + } + } + + return dict; + +error: + Py_DECREF(dict); + return NULL; +} + +PyDoc_STRVAR(Reader_get_realtime__doc__, + "get_realtime() -> int\n\n" + "Return the realtime timestamp for the current journal entry\n" + "in microseconds.\n\n" + "Wraps sd_journal_get_realtime_usec().\n" + "See man:sd_journal_get_realtime_usec(3)."); +static PyObject* Reader_get_realtime(Reader *self, PyObject *args) { + uint64_t timestamp; + int r; + + assert(self); + assert(!args); + + r = sd_journal_get_realtime_usec(self->j, ×tamp); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + assert_cc(sizeof(unsigned long long) == sizeof(timestamp)); + return PyLong_FromUnsignedLongLong(timestamp); +} + +PyDoc_STRVAR(Reader_get_monotonic__doc__, + "get_monotonic() -> (timestamp, bootid)\n\n" + "Return the monotonic timestamp for the current journal entry\n" + "as a tuple of time in microseconds and bootid.\n\n" + "Wraps sd_journal_get_monotonic_usec().\n" + "See man:sd_journal_get_monotonic_usec(3)."); +static PyObject* Reader_get_monotonic(Reader *self, PyObject *args) { + uint64_t timestamp; + sd_id128_t id; + PyObject *monotonic, *bootid, *tuple; + int r; + + assert(self); + assert(!args); + + r = sd_journal_get_monotonic_usec(self->j, ×tamp, &id); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + assert_cc(sizeof(unsigned long long) == sizeof(timestamp)); + monotonic = PyLong_FromUnsignedLongLong(timestamp); + bootid = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); +#if PY_MAJOR_VERSION >= 3 + tuple = PyStructSequence_New(&MonotonicType); +#else + tuple = PyTuple_New(2); +#endif + if (!monotonic || !bootid || !tuple) { + Py_XDECREF(monotonic); + Py_XDECREF(bootid); + Py_XDECREF(tuple); + return NULL; + } + +#if PY_MAJOR_VERSION >= 3 + PyStructSequence_SET_ITEM(tuple, 0, monotonic); + PyStructSequence_SET_ITEM(tuple, 1, bootid); +#else + PyTuple_SET_ITEM(tuple, 0, monotonic); + PyTuple_SET_ITEM(tuple, 1, bootid); +#endif + + return tuple; +} + +PyDoc_STRVAR(Reader_add_match__doc__, + "add_match(match) -> None\n\n" + "Add a match to filter journal log entries. All matches of different\n" + "fields are combined with logical AND, and matches of the same field\n" + "are automatically combined with logical OR.\n" + "Match is a string of the form \"FIELD=value\"."); +static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds) { + char *match; + int match_len, r; + if (!PyArg_ParseTuple(args, "s#:add_match", &match, &match_len)) + return NULL; + + r = sd_journal_add_match(self->j, match, match_len); + if (set_error(r, NULL, "Invalid match") < 0) + return NULL; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_add_disjunction__doc__, + "add_disjunction() -> None\n\n" + "Inserts a logical OR between matches added since previous\n" + "add_disjunction() or add_conjunction() and the next\n" + "add_disjunction() or add_conjunction().\n\n" + "See man:sd_journal_add_disjunction(3) for explanation."); +static PyObject* Reader_add_disjunction(Reader *self, PyObject *args) { + int r; + r = sd_journal_add_disjunction(self->j); + if (set_error(r, NULL, NULL) < 0) + return NULL; + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_add_conjunction__doc__, + "add_conjunction() -> None\n\n" + "Inserts a logical AND between matches added since previous\n" + "add_disjunction() or add_conjunction() and the next\n" + "add_disjunction() or add_conjunction().\n\n" + "See man:sd_journal_add_disjunction(3) for explanation."); +static PyObject* Reader_add_conjunction(Reader *self, PyObject *args) { + int r; + r = sd_journal_add_conjunction(self->j); + if (set_error(r, NULL, NULL) < 0) + return NULL; + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_flush_matches__doc__, + "flush_matches() -> None\n\n" + "Clear all current match filters."); +static PyObject* Reader_flush_matches(Reader *self, PyObject *args) { + sd_journal_flush_matches(self->j); + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_seek_head__doc__, + "seek_head() -> None\n\n" + "Jump to the beginning of the journal.\n" + "This method invokes sd_journal_seek_head().\n" + "See man:sd_journal_seek_head(3)."); +static PyObject* Reader_seek_head(Reader *self, PyObject *args) { + int r; + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_head(self->j); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_seek_tail__doc__, + "seek_tail() -> None\n\n" + "Jump to the end of the journal.\n" + "This method invokes sd_journal_seek_tail().\n" + "See man:sd_journal_seek_tail(3)."); +static PyObject* Reader_seek_tail(Reader *self, PyObject *args) { + int r; + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_tail(self->j); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_seek_realtime__doc__, + "seek_realtime(realtime) -> None\n\n" + "Seek to nearest matching journal entry to `realtime`. Argument\n" + "`realtime` in specified in seconds."); +static PyObject* Reader_seek_realtime(Reader *self, PyObject *args) { + uint64_t timestamp; + int r; + + if (!PyArg_ParseTuple(args, "K:seek_realtime", ×tamp)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_realtime_usec(self->j, timestamp); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_seek_monotonic__doc__, + "seek_monotonic(monotonic[, bootid]) -> None\n\n" + "Seek to nearest matching journal entry to `monotonic`. Argument\n" + "`monotonic` is an timestamp from boot in microseconds.\n" + "Argument `bootid` is a string representing which boot the\n" + "monotonic time is reference to. Defaults to current bootid."); +static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args) { + char *bootid = NULL; + uint64_t timestamp; + sd_id128_t id; + int r; + + if (!PyArg_ParseTuple(args, "K|z:seek_monotonic", ×tamp, &bootid)) + return NULL; + + if (bootid) { + r = sd_id128_from_string(bootid, &id); + if (set_error(r, NULL, "Invalid bootid") < 0) + return NULL; + } else { + Py_BEGIN_ALLOW_THREADS + r = sd_id128_get_boot(&id); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_monotonic_usec(self->j, id, timestamp); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Reader_process__doc__, + "process() -> state change (integer)\n\n" + "Process events and reset the readable state of the file\n" + "descriptor returned by .fileno().\n\n" + "Will return constants: NOP if no change; APPEND if new\n" + "entries have been added to the end of the journal; and\n" + "INVALIDATE if journal files have been added or removed.\n\n" + "See man:sd_journal_process(3) for further discussion."); +static PyObject* Reader_process(Reader *self, PyObject *args) { + int r; + + assert(!args); + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_process(self->j); + Py_END_ALLOW_THREADS + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return long_FromLong(r); +} + +PyDoc_STRVAR(Reader_wait__doc__, + "wait([timeout]) -> state change (integer)\n\n" + "Wait for a change in the journal. Argument `timeout` specifies\n" + "the maximum number of microseconds to wait before returning\n" + "regardless of wheter the journal has changed. If `timeout` is -1,\n" + "then block forever.\n\n" + "Will return constants: NOP if no change; APPEND if new\n" + "entries have been added to the end of the journal; and\n" + "INVALIDATE if journal files have been added or removed.\n\n" + "See man:sd_journal_wait(3) for further discussion."); +static PyObject* Reader_wait(Reader *self, PyObject *args) { + int r; + int64_t timeout; + + if (!PyArg_ParseTuple(args, "|L:wait", &timeout)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_wait(self->j, timeout); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return long_FromLong(r); +} + +PyDoc_STRVAR(Reader_seek_cursor__doc__, + "seek_cursor(cursor) -> None\n\n" + "Seek to journal entry by given unique reference `cursor`."); +static PyObject* Reader_seek_cursor(Reader *self, PyObject *args) { + const char *cursor; + int r; + + if (!PyArg_ParseTuple(args, "s:seek_cursor", &cursor)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_cursor(self->j, cursor); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, "Invalid cursor") < 0) + return NULL; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_get_cursor__doc__, + "get_cursor() -> str\n\n" + "Return a cursor string for the current journal entry.\n\n" + "Wraps sd_journal_get_cursor(). See man:sd_journal_get_cursor(3)."); +static PyObject* Reader_get_cursor(Reader *self, PyObject *args) { + _cleanup_free_ char *cursor = NULL; + int r; + + assert(self); + assert(!args); + + r = sd_journal_get_cursor(self->j, &cursor); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return unicode_FromString(cursor); +} + +PyDoc_STRVAR(Reader_test_cursor__doc__, + "test_cursor(str) -> bool\n\n" + "Test whether the cursor string matches current journal entry.\n\n" + "Wraps sd_journal_test_cursor(). See man:sd_journal_test_cursor(3)."); +static PyObject* Reader_test_cursor(Reader *self, PyObject *args) { + const char *cursor; + int r; + + assert(self); + assert(args); + + if (!PyArg_ParseTuple(args, "s:test_cursor", &cursor)) + return NULL; + + r = sd_journal_test_cursor(self->j, cursor); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} + +PyDoc_STRVAR(Reader_query_unique__doc__, + "query_unique(field) -> a set of values\n\n" + "Return a set of unique values appearing in journal for the\n" + "given `field`. Note this does not respect any journal matches."); +static PyObject* Reader_query_unique(Reader *self, PyObject *args) { + char *query; + int r; + const void *uniq; + size_t uniq_len; + PyObject *value_set, *key, *value; + + if (!PyArg_ParseTuple(args, "s:query_unique", &query)) + return NULL; + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_query_unique(self->j, query); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, "Invalid field name") < 0) + return NULL; + + value_set = PySet_New(0); + key = unicode_FromString(query); + + SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) { + const char *delim_ptr; + + delim_ptr = memchr(uniq, '=', uniq_len); + value = PyBytes_FromStringAndSize( + delim_ptr + 1, + (const char*) uniq + uniq_len - (delim_ptr + 1)); + PySet_Add(value_set, value); + Py_DECREF(value); + } + + Py_DECREF(key); + return value_set; +} + +PyDoc_STRVAR(Reader_get_catalog__doc__, + "get_catalog() -> str\n\n" + "Retrieve a message catalog entry for the current journal entry.\n" + "Will throw IndexError if the entry has no MESSAGE_ID\n" + "and KeyError is the id is specified, but hasn't been found\n" + "in the catalog.\n\n" + "Wraps man:sd_journal_get_catalog(3)."); +static PyObject* Reader_get_catalog(Reader *self, PyObject *args) { + int r; + _cleanup_free_ char *msg = NULL; + + assert(self); + assert(!args); + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_get_catalog(self->j, &msg); + Py_END_ALLOW_THREADS + + if (r == -ENOENT) { + const void* mid; + size_t mid_len; + + r = sd_journal_get_data(self->j, "MESSAGE_ID", &mid, &mid_len); + if (r == 0) { + const size_t l = sizeof("MESSAGE_ID"); + assert(mid_len > l); + PyErr_Format(PyExc_KeyError, "%.*s", (int) (mid_len - l), + (const char*) mid + l); + } else if (r == -ENOENT) + PyErr_SetString(PyExc_IndexError, "no MESSAGE_ID field"); + else + set_error(r, NULL, NULL); + return NULL; + } + + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return unicode_FromString(msg); +} + +PyDoc_STRVAR(get_catalog__doc__, + "get_catalog(id128) -> str\n\n" + "Retrieve a message catalog entry for the given id.\n" + "Wraps man:sd_journal_get_catalog_for_message_id(3)."); +static PyObject* get_catalog(PyObject *self, PyObject *args) { + int r; + char *id_ = NULL; + sd_id128_t id; + _cleanup_free_ char *msg = NULL; + + assert(args); + + if (!PyArg_ParseTuple(args, "z:get_catalog", &id_)) + return NULL; + + r = sd_id128_from_string(id_, &id); + if (set_error(r, NULL, "Invalid id128") < 0) + return NULL; + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_get_catalog_for_message_id(id, &msg); + Py_END_ALLOW_THREADS + + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return unicode_FromString(msg); +} + +PyDoc_STRVAR(data_threshold__doc__, + "Threshold for field size truncation in bytes.\n\n" + "Fields longer than this will be truncated to the threshold size.\n" + "Defaults to 64Kb."); + +static PyObject* Reader_get_data_threshold(Reader *self, void *closure) { + size_t cvalue; + int r; + + r = sd_journal_get_data_threshold(self->j, &cvalue); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return long_FromSize_t(cvalue); +} + +static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure) { + int r; + + if (value == NULL) { + PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold"); + return -1; + } + + if (!long_Check(value)){ + PyErr_SetString(PyExc_TypeError, "Data threshold must be an int"); + return -1; + } + + r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value)); + return set_error(r, NULL, NULL); +} + +PyDoc_STRVAR(closed__doc__, + "True iff journal is closed"); +static PyObject* Reader_get_closed(Reader *self, void *closure) { + return PyBool_FromLong(self->j == NULL); +} + +static PyGetSetDef Reader_getsetters[] = { + { (char*) "data_threshold", + (getter) Reader_get_data_threshold, + (setter) Reader_set_data_threshold, + (char*) data_threshold__doc__, + NULL }, + { (char*) "closed", + (getter) Reader_get_closed, + NULL, + (char*) closed__doc__, + NULL }, + {} /* Sentinel */ +}; + +static PyMethodDef Reader_methods[] = { + {"fileno", (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__}, + {"reliable_fd", (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__}, + {"get_events", (PyCFunction) Reader_get_events, METH_NOARGS, Reader_get_events__doc__}, + {"get_timeout", (PyCFunction) Reader_get_timeout, METH_NOARGS, Reader_get_timeout__doc__}, + {"get_timeout_ms", (PyCFunction) Reader_get_timeout_ms, METH_NOARGS, Reader_get_timeout_ms__doc__}, + {"close", (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__}, + {"get_usage", (PyCFunction) Reader_get_usage, METH_NOARGS, Reader_get_usage__doc__}, + {"__enter__", (PyCFunction) Reader___enter__, METH_NOARGS, Reader___enter____doc__}, + {"__exit__", (PyCFunction) Reader___exit__, METH_VARARGS, Reader___exit____doc__}, + {"_next", (PyCFunction) Reader_next, METH_VARARGS, Reader_next__doc__}, + {"_previous", (PyCFunction) Reader_previous, METH_VARARGS, Reader_previous__doc__}, + {"_get", (PyCFunction) Reader_get, METH_VARARGS, Reader_get__doc__}, + {"_get_all", (PyCFunction) Reader_get_all, METH_NOARGS, Reader_get_all__doc__}, + {"_get_realtime", (PyCFunction) Reader_get_realtime, METH_NOARGS, Reader_get_realtime__doc__}, + {"_get_monotonic", (PyCFunction) Reader_get_monotonic, METH_NOARGS, Reader_get_monotonic__doc__}, + {"add_match", (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__}, + {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__}, + {"add_conjunction", (PyCFunction) Reader_add_conjunction, METH_NOARGS, Reader_add_conjunction__doc__}, + {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__}, + {"seek_head", (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__}, + {"seek_tail", (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__}, + {"seek_realtime", (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__}, + {"seek_monotonic", (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__}, + {"process", (PyCFunction) Reader_process, METH_NOARGS, Reader_process__doc__}, + {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__}, + {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__}, + {"_get_cursor", (PyCFunction) Reader_get_cursor, METH_NOARGS, Reader_get_cursor__doc__}, + {"test_cursor", (PyCFunction) Reader_test_cursor, METH_VARARGS, Reader_test_cursor__doc__}, + {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__}, + {"get_catalog", (PyCFunction) Reader_get_catalog, METH_NOARGS, Reader_get_catalog__doc__}, + {} /* Sentinel */ +}; + +static PyTypeObject ReaderType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "_reader._Reader", + .tp_basicsize = sizeof(Reader), + .tp_dealloc = (destructor) Reader_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = Reader__doc__, + .tp_methods = Reader_methods, + .tp_getset = Reader_getsetters, + .tp_init = (initproc) Reader_init, + .tp_new = PyType_GenericNew, +}; + +static PyMethodDef methods[] = { + { "_get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__}, + {} /* Sentinel */ +}; + +#if PY_MAJOR_VERSION >= 3 +static PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "_reader", + module__doc__, + -1, + methods, +}; +#endif + +#if PY_MAJOR_VERSION >= 3 +static bool initialized = false; +#endif + +DISABLE_WARNING_MISSING_PROTOTYPES; + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION >= 3 +PyInit__reader(void) +#else +init_reader(void) +#endif +{ + PyObject* m; + + PyDateTime_IMPORT; + + if (PyType_Ready(&ReaderType) < 0) +#if PY_MAJOR_VERSION >= 3 + return NULL; +#else + return; +#endif + +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&module); + if (m == NULL) + return NULL; + + if (!initialized) { + PyStructSequence_InitType(&MonotonicType, &Monotonic_desc); + initialized = true; + } +#else + m = Py_InitModule3("_reader", methods, module__doc__); + if (m == NULL) + return; +#endif + + Py_INCREF(&ReaderType); +#if PY_MAJOR_VERSION >= 3 + Py_INCREF(&MonotonicType); +#endif + if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) || +#if PY_MAJOR_VERSION >= 3 + PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) || +#endif + PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) || + PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) || + PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) || + PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) || + PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) || + PyModule_AddIntConstant(m, "SYSTEM", SD_JOURNAL_SYSTEM) || + PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY) || + PyModule_AddIntConstant(m, "CURRENT_USER", SD_JOURNAL_CURRENT_USER) || + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { +#if PY_MAJOR_VERSION >= 3 + Py_DECREF(m); + return NULL; +#endif + } + +#if PY_MAJOR_VERSION >= 3 + return m; +#endif +} + +REENABLE_WARNING; diff --git a/systemd/daemon.py b/systemd/daemon.py new file mode 100644 index 0000000..82011ca --- /dev/null +++ b/systemd/daemon.py @@ -0,0 +1,55 @@ +from ._daemon import (__version__, + booted, + notify, + _listen_fds, + _is_fifo, + _is_socket, + _is_socket_inet, + _is_socket_unix, + _is_mq, + LISTEN_FDS_START) +from socket import AF_UNSPEC as _AF_UNSPEC + +def _convert_fileobj(fileobj): + try: + return fileobj.fileno() + except AttributeError: + return fileobj + +def is_fifo(fileobj, path=None): + fd = _convert_fileobj(fileobj) + return _is_fifo(fd, path) + +def is_socket(fileobj, family=_AF_UNSPEC, type=0, listening=-1): + fd = _convert_fileobj(fileobj) + return _is_socket(fd, family, type, listening) + +def is_socket_inet(fileobj, family=_AF_UNSPEC, type=0, listening=-1, port=0): + fd = _convert_fileobj(fileobj) + return _is_socket_inet(fd, family, type, listening, port) + +def is_socket_unix(fileobj, type=0, listening=-1, path=None): + fd = _convert_fileobj(fileobj) + return _is_socket_unix(fd, type, listening, path) + +def is_mq(fileobj, path=None): + fd = _convert_fileobj(fileobj) + return _is_mq(fd, path) + +def listen_fds(unset_environment=True): + """Return a list of socket activated descriptors + + Example:: + + (in primary window) + $ systemd-activate -l 2000 python3 -c \\ + 'from systemd.daemon import listen_fds; print(listen_fds())' + (in another window) + $ telnet localhost 2000 + (in primary window) + ... + Execing python3 (...) + [3] + """ + num = _listen_fds(unset_environment) + return list(range(LISTEN_FDS_START, LISTEN_FDS_START + num)) diff --git a/systemd/docs/.gitignore b/systemd/docs/.gitignore new file mode 100644 index 0000000..b06a965 --- /dev/null +++ b/systemd/docs/.gitignore @@ -0,0 +1 @@ +!layout.html diff --git a/systemd/docs/conf.py b/systemd/docs/conf.py new file mode 100644 index 0000000..1919170 --- /dev/null +++ b/systemd/docs/conf.py @@ -0,0 +1,279 @@ +# -*- coding: utf-8 -*- +# +# python-systemd documentation build configuration file, created by +# sphinx-quickstart on Sat Feb 9 13:49:42 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['.'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'python-systemd' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['.'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'python-systemddoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'python-systemd.tex', u'python-systemd Documentation', + None, 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'python-systemd', u'python-systemd Documentation', + [], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'python-systemd', u'python-systemd Documentation', + u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks', 'python-systemd', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'python-systemd' +epub_author = u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' +epub_publisher = u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' +epub_copyright = u'2013, David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/systemd/docs/daemon.rst b/systemd/docs/daemon.rst new file mode 100644 index 0000000..0ad11ed --- /dev/null +++ b/systemd/docs/daemon.rst @@ -0,0 +1,18 @@ +`systemd.daemon` module +======================= + +.. automodule:: systemd.daemon + :members: + :undoc-members: + :inherited-members: + + .. autoattribute:: systemd.daemon.LISTEN_FDS_START + + .. autofunction:: _listen_fds + .. autofunction:: _is_fifo + .. autofunction:: _is_socket + .. autofunction:: _is_socket_unix + .. autofunction:: _is_socket_inet + .. autofunction:: _is_mq + .. autofunction:: notify + .. autofunction:: booted diff --git a/systemd/docs/default.css b/systemd/docs/default.css new file mode 100644 index 0000000..7c097d6 --- /dev/null +++ b/systemd/docs/default.css @@ -0,0 +1,196 @@ +@import url("basic.css"); + +/* -- page layout ----------------------------------------------------------- */ + +div.documentwrapper { + float: left; + width: 100%; +} + +div.bodywrapper { + margin: 0 0 0 230px; +} + +div.body { + background-color: #ffffff; + color: #000000; + padding: 0 20px 30px 20px; +} + +div.footer { + color: #ffffff; + width: 100%; + padding: 9px 0 9px 0; + text-align: center; + font-size: 75%; +} + +div.footer a { + color: #ffffff; + text-decoration: underline; +} + +div.related { + background-color: #133f52; + line-height: 30px; + color: #ffffff; +} + +div.related a { + color: #ffffff; +} + +div.sphinxsidebar { + background-color: #dddddd; +} + +div.sphinxsidebar p.topless { + margin: 5px 10px 10px 10px; +} + +div.sphinxsidebar ul { + margin: 10px; + padding: 0; +} + +div.sphinxsidebar input { + border: 1px solid #000000; + font-family: sans-serif; + font-size: 1em; +} + + + +/* -- hyperlink styles ------------------------------------------------------ */ + +a { + text-decoration: none; +} + +a:hover { + text-decoration: underline; +} + + + +/* -- body styles ----------------------------------------------------------- */ + +div.body h1, +div.body h2, +div.body h3, +div.body h4, +div.body h5, +div.body h6 { + font-family: 'Trebuchet MS', sans-serif; + background-color: #f2f2f2; + font-weight: normal; + color: #20435c; + border-bottom: 1px solid #ccc; + margin: 20px -20px 10px -20px; + padding: 3px 0 3px 10px; +} + +div.body h1 { margin-top: 0; font-size: 200%; } +div.body h2 { font-size: 160%; } +div.body h3 { font-size: 140%; } +div.body h4 { font-size: 120%; } +div.body h5 { font-size: 110%; } +div.body h6 { font-size: 100%; } + +a.headerlink { + color: #c60f0f; + font-size: 0.8em; + padding: 0 4px 0 4px; + text-decoration: none; +} + +a.headerlink:hover { + background-color: #c60f0f; + color: white; +} + +div.body p, div.body dd, div.body li { + text-align: justify; + line-height: 130%; +} + +div.admonition p.admonition-title + p { + display: inline; +} + +div.admonition p { + margin-bottom: 5px; +} + +div.admonition pre { + margin-bottom: 5px; +} + +div.admonition ul, div.admonition ol { + margin-bottom: 5px; +} + +div.note { + background-color: #eee; + border: 1px solid #ccc; +} + +div.seealso { + background-color: #ffc; + border: 1px solid #ff6; +} + +div.topic { + background-color: #eee; +} + +div.warning { + background-color: #ffe4e4; + border: 1px solid #f66; +} + +p.admonition-title { + display: inline; +} + +p.admonition-title:after { + content: ":"; +} + +pre { + padding: 5px; + background-color: #eeffcc; + color: #333333; + line-height: 120%; + border: 1px solid #ac9; + border-left: none; + border-right: none; +} + +tt { + background-color: #ecf0f3; + padding: 0 1px 0 1px; + font-size: 0.95em; +} + +th { + background-color: #ede; +} + +.warning tt { + background: #efc2c2; +} + +.note tt { + background: #d6d6d6; +} + +.viewcode-back { + font-family: sans-serif; +} + +div.viewcode-block:target { + background-color: #f4debf; + border-top: 1px solid #ac9; + border-bottom: 1px solid #ac9; +} diff --git a/systemd/docs/id128.rst b/systemd/docs/id128.rst new file mode 100644 index 0000000..89c37f3 --- /dev/null +++ b/systemd/docs/id128.rst @@ -0,0 +1,40 @@ +`systemd.id128` module +====================== + +.. automodule:: systemd.id128 + :members: + :undoc-members: + :inherited-members: + + .. autoattribute:: systemd.id128.SD_MESSAGE_COREDUMP + .. autoattribute:: systemd.id128.SD_MESSAGE_FORWARD_SYSLOG_MISSED + .. autoattribute:: systemd.id128.SD_MESSAGE_HIBERNATE_KEY + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_DROPPED + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_MISSED + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_START + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_LID_CLOSED + .. autoattribute:: systemd.id128.SD_MESSAGE_LID_OPENED + .. autoattribute:: systemd.id128.SD_MESSAGE_OVERMOUNTING + .. autoattribute:: systemd.id128.SD_MESSAGE_POWER_KEY + .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_START + .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_START + .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_SHUTDOWN + .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_START + .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_SPAWN_FAILED + .. autoattribute:: systemd.id128.SD_MESSAGE_STARTUP_FINISHED + .. autoattribute:: systemd.id128.SD_MESSAGE_SUSPEND_KEY + .. autoattribute:: systemd.id128.SD_MESSAGE_TIMEZONE_CHANGE + .. autoattribute:: systemd.id128.SD_MESSAGE_TIME_CHANGE + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_FAILED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADING + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTING + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPING + .. autoattribute:: systemd.id128.SD_MESSAGE_CONFIG_ERROR + .. autoattribute:: systemd.id128.SD_MESSAGE_BOOTCHART diff --git a/systemd/docs/index.rst b/systemd/docs/index.rst new file mode 100644 index 0000000..e78d966 --- /dev/null +++ b/systemd/docs/index.rst @@ -0,0 +1,24 @@ +.. python-systemd documentation master file, created by + sphinx-quickstart on Sat Feb 9 13:49:42 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to python-systemd's documentation! +========================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + journal + id128 + daemon + login + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/systemd/docs/journal.rst b/systemd/docs/journal.rst new file mode 100644 index 0000000..ea74cf8 --- /dev/null +++ b/systemd/docs/journal.rst @@ -0,0 +1,64 @@ +`systemd.journal` module +======================== + +.. automodule:: systemd.journal + :members: send, sendv, stream, stream_fd + :undoc-members: + +`JournalHandler` class +---------------------- + +.. autoclass:: JournalHandler + +Accessing the Journal +--------------------- + +.. autoclass:: _Reader + :undoc-members: + :inherited-members: + +.. autoclass:: Reader + :undoc-members: + :inherited-members: + + .. automethod:: __init__ + +.. autofunction:: _get_catalog +.. autofunction:: get_catalog + +.. autoclass:: Monotonic + +.. autoattribute:: systemd.journal.DEFAULT_CONVERTERS + +Example: polling for journal events +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows that journal events can be waited for (using +e.g. `poll`). This makes it easy to integrate Reader in an external +event loop: + + >>> import select + >>> from systemd import journal + >>> j = journal.Reader() + >>> j.seek_tail() + >>> p = select.poll() + >>> p.register(j, j.get_events()) + >>> p.poll() + [(3, 1)] + >>> j.get_next() + + +Journal access types +~~~~~~~~~~~~~~~~~~~~ + +.. autoattribute:: systemd.journal.LOCAL_ONLY +.. autoattribute:: systemd.journal.RUNTIME_ONLY +.. autoattribute:: systemd.journal.SYSTEM +.. autoattribute:: systemd.journal.CURRENT_USER + +Journal event types +~~~~~~~~~~~~~~~~~~~ + +.. autoattribute:: systemd.journal.NOP +.. autoattribute:: systemd.journal.APPEND +.. autoattribute:: systemd.journal.INVALIDATE diff --git a/systemd/docs/layout.html b/systemd/docs/layout.html new file mode 100644 index 0000000..930a6a7 --- /dev/null +++ b/systemd/docs/layout.html @@ -0,0 +1,15 @@ +{% extends "!layout.html" %} + +{% block relbar1 %} + <a href="../man/systemd.index.html">Index </a>· + <a href="../man/systemd.directives.html">Directives </a>· + <a href="index.html">Python </a>· + <span style="float:right">systemd {{release}}</span> + <hr /> +{% endblock %} + +{# remove the lower relbar #} +{% block relbar2 %} {% endblock %} + +{# remove the footer #} +{% block footer %} {% endblock %} diff --git a/systemd/docs/login.rst b/systemd/docs/login.rst new file mode 100644 index 0000000..6b4de64 --- /dev/null +++ b/systemd/docs/login.rst @@ -0,0 +1,28 @@ +`systemd.login` module +======================= + +.. automodule:: systemd.login + :members: + +.. autoclass:: Monitor + :undoc-members: + :inherited-members: + +Example: polling for events +~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +This example shows that session/uid/seat/machine events can be waited +for (using e.g. `poll`). This makes it easy to integrate Monitor in an +external event loop: + + >>> import select + >>> from systemd import login + >>> m = login.Monitor("machine") + >>> p = select.poll() + >>> p.register(m, m.get_events()) + >>> login.machine_names() + [] + >>> p.poll() + [(3, 1)] + >>> login.machine_names() + ['fedora-19.nspawn'] diff --git a/systemd/id128.c b/systemd/id128.c new file mode 100644 index 0000000..5ec7309 --- /dev/null +++ b/systemd/id128.c @@ -0,0 +1,163 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <Python.h> + +#include "systemd/sd-messages.h" + +#include "pyutil.h" +#include "log.h" +#include "util.h" +#include "macro.h" + +PyDoc_STRVAR(module__doc__, + "Python interface to the libsystemd-id128 library.\n\n" + "Provides SD_MESSAGE_* constants and functions to query and generate\n" + "128-bit unique identifiers." +); + +PyDoc_STRVAR(randomize__doc__, + "randomize() -> UUID\n\n" + "Return a new random 128-bit unique identifier.\n" + "Wraps sd_id128_randomize(3)." +); + +PyDoc_STRVAR(get_machine__doc__, + "get_machine() -> UUID\n\n" + "Return a 128-bit unique identifier for this machine.\n" + "Wraps sd_id128_get_machine(3)." +); + +PyDoc_STRVAR(get_boot__doc__, + "get_boot() -> UUID\n\n" + "Return a 128-bit unique identifier for this boot.\n" + "Wraps sd_id128_get_boot(3)." +); + +static PyObject* make_uuid(sd_id128_t id) { + _cleanup_Py_DECREF_ PyObject + *uuid = NULL, *UUID = NULL, *bytes = NULL, + *args = NULL, *kwargs = NULL; + + uuid = PyImport_ImportModule("uuid"); + if (!uuid) + return NULL; + + UUID = PyObject_GetAttrString(uuid, "UUID"); + bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); + args = Py_BuildValue("()"); + kwargs = PyDict_New(); + if (!UUID || !bytes || !args || !kwargs) + return NULL; + + if (PyDict_SetItemString(kwargs, "bytes", bytes) < 0) + return NULL; + + return PyObject_Call(UUID, args, kwargs); +} + +#define helper(name) \ + static PyObject *name(PyObject *self, PyObject *args) { \ + sd_id128_t id; \ + int r; \ + \ + assert(args == NULL); \ + \ + r = sd_id128_##name(&id); \ + if (r < 0) { \ + errno = -r; \ + return PyErr_SetFromErrno(PyExc_IOError); \ + } \ + \ + return make_uuid(id); \ + } + +helper(randomize) +helper(get_machine) +helper(get_boot) + +static PyMethodDef methods[] = { + { "randomize", randomize, METH_NOARGS, randomize__doc__}, + { "get_machine", get_machine, METH_NOARGS, get_machine__doc__}, + { "get_boot", get_boot, METH_NOARGS, get_boot__doc__}, + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +static int add_id(PyObject *module, const char* name, sd_id128_t id) { + PyObject *obj; + + obj = make_uuid(id); + if (!obj) + return -1; + + return PyModule_AddObject(module, name, obj); +} + +#if PY_MAJOR_VERSION < 3 + +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC initid128(void) { + PyObject *m; + + m = Py_InitModule3("id128", methods, module__doc__); + if (m == NULL) + return; + + /* a series of lines like 'add_id() ;' follow */ +#define JOINER ; +#include "id128-constants.h" +#undef JOINER + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); +} +REENABLE_WARNING; + +#else + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "id128", /* name of module */ + module__doc__, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module */ + methods +}; + +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC PyInit_id128(void) { + PyObject *m; + + m = PyModule_Create(&module); + if (m == NULL) + return NULL; + + if ( /* a series of lines like 'add_id() ||' follow */ +#define JOINER || +#include "id128-constants.h" +#undef JOINER + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { + Py_DECREF(m); + return NULL; + } + + return m; +} +REENABLE_WARNING; + +#endif diff --git a/systemd/journal.py b/systemd/journal.py index 53e992b..dd1f229 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -1,93 +1,548 @@ +# -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */ +# +# This file is part of systemd. +# +# Copyright 2012 David Strauss <david@davidstrauss.net> +# Copyright 2012 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> +# Copyright 2012 Marti Raudsepp <marti@juffo.org> +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. +# +# systemd 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 +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with systemd; If not, see <http://www.gnu.org/licenses/>. + +from __future__ import division + +import sys as _sys +import datetime as _datetime +import uuid as _uuid import traceback as _traceback import os as _os +import logging as _logging +if _sys.version_info >= (3,3): + from collections import ChainMap as _ChainMap from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) -from ._journal import sendv, stream_fd +from ._journal import __version__, sendv, stream_fd +from ._reader import (_Reader, NOP, APPEND, INVALIDATE, + LOCAL_ONLY, RUNTIME_ONLY, + SYSTEM, SYSTEM_ONLY, CURRENT_USER, + _get_catalog) +from . import id128 as _id128 + +if _sys.version_info >= (3,): + from ._reader import Monotonic +else: + Monotonic = tuple + +def _convert_monotonic(m): + return Monotonic((_datetime.timedelta(microseconds=m[0]), + _uuid.UUID(bytes=m[1]))) + +def _convert_source_monotonic(s): + return _datetime.timedelta(microseconds=int(s)) + +def _convert_realtime(t): + return _datetime.datetime.fromtimestamp(t / 1000000) + +def _convert_timestamp(s): + return _datetime.datetime.fromtimestamp(int(s) / 1000000) + +def _convert_trivial(x): + return x + +if _sys.version_info >= (3,): + def _convert_uuid(s): + return _uuid.UUID(s.decode()) +else: + _convert_uuid = _uuid.UUID + +DEFAULT_CONVERTERS = { + 'MESSAGE_ID': _convert_uuid, + '_MACHINE_ID': _convert_uuid, + '_BOOT_ID': _convert_uuid, + 'PRIORITY': int, + 'LEADER': int, + 'SESSION_ID': int, + 'USERSPACE_USEC': int, + 'INITRD_USEC': int, + 'KERNEL_USEC': int, + '_UID': int, + '_GID': int, + '_PID': int, + 'SYSLOG_FACILITY': int, + 'SYSLOG_PID': int, + '_AUDIT_SESSION': int, + '_AUDIT_LOGINUID': int, + '_SYSTEMD_SESSION': int, + '_SYSTEMD_OWNER_UID': int, + 'CODE_LINE': int, + 'ERRNO': int, + 'EXIT_STATUS': int, + '_SOURCE_REALTIME_TIMESTAMP': _convert_timestamp, + '__REALTIME_TIMESTAMP': _convert_realtime, + '_SOURCE_MONOTONIC_TIMESTAMP': _convert_source_monotonic, + '__MONOTONIC_TIMESTAMP': _convert_monotonic, + '__CURSOR': _convert_trivial, + 'COREDUMP': bytes, + 'COREDUMP_PID': int, + 'COREDUMP_UID': int, + 'COREDUMP_GID': int, + 'COREDUMP_SESSION': int, + 'COREDUMP_SIGNAL': int, + 'COREDUMP_TIMESTAMP': _convert_timestamp, +} + +_IDENT_LETTER = set('ABCDEFGHIJKLMNOPQRTSUVWXYZ_') + +def _valid_field_name(s): + return not (set(s) - _IDENT_LETTER) + +class Reader(_Reader): + """Reader allows the access and filtering of systemd journal + entries. Note that in order to access the system journal, a + non-root user must be in the `systemd-journal` group. + + Example usage to print out all informational or higher level + messages for systemd-udevd for this boot: + + >>> j = journal.Reader() + >>> j.this_boot() + >>> j.log_level(journal.LOG_INFO) + >>> j.add_match(_SYSTEMD_UNIT="systemd-udevd.service") + >>> for entry in j: + ... print(entry['MESSAGE']) + + See systemd.journal-fields(7) for more info on typical fields + found in the journal. + """ + def __init__(self, flags=0, path=None, files=None, converters=None): + """Create an instance of Reader, which allows filtering and + return of journal entries. + + Argument `flags` sets open flags of the journal, which can be one + of, or ORed combination of constants: LOCAL_ONLY (default) opens + journal on local machine only; RUNTIME_ONLY opens only + volatile journal files; and SYSTEM_ONLY opens only + journal files of system services and the kernel. + + Argument `path` is the directory of journal files. Note that + `flags` and `path` are exclusive. + + Argument `converters` is a dictionary which updates the + DEFAULT_CONVERTERS to convert journal field values. Field + names are used as keys into this dictionary. The values must + be single argument functions, which take a `bytes` object and + return a converted value. When there's no entry for a field + name, then the default UTF-8 decoding will be attempted. If + the conversion fails with a ValueError, unconverted bytes + object will be returned. (Note that ValueEror is a superclass + of UnicodeDecodeError). + + Reader implements the context manager protocol: the journal + will be closed when exiting the block. + """ + super(Reader, self).__init__(flags, path, files) + if _sys.version_info >= (3,3): + self.converters = _ChainMap() + if converters is not None: + self.converters.maps.append(converters) + self.converters.maps.append(DEFAULT_CONVERTERS) + else: + self.converters = DEFAULT_CONVERTERS.copy() + if converters is not None: + self.converters.update(converters) + + def _convert_field(self, key, value): + """Convert value using self.converters[key] + + If `key` is not present in self.converters, a standard unicode + decoding will be attempted. If the conversion (either + key-specific or the default one) fails with a ValueError, the + original bytes object will be returned. + """ + convert = self.converters.get(key, bytes.decode) + try: + return convert(value) + except ValueError: + # Leave in default bytes + return value + + def _convert_entry(self, entry): + """Convert entire journal entry utilising _covert_field""" + result = {} + for key, value in entry.items(): + if isinstance(value, list): + result[key] = [self._convert_field(key, val) for val in value] + else: + result[key] = self._convert_field(key, value) + return result + + def __iter__(self): + """Part of iterator protocol. + Returns self. + """ + return self + + def __next__(self): + """Part of iterator protocol. + Returns self.get_next() or raises StopIteration. + """ + ans = self.get_next() + if ans: + return ans + else: + raise StopIteration() + + if _sys.version_info < (3,): + next = __next__ + + def add_match(self, *args, **kwargs): + """Add one or more matches to the filter journal log entries. + All matches of different field are combined in a logical AND, + and matches of the same field are automatically combined in a + logical OR. + Matches can be passed as strings of form "FIELD=value", or + keyword arguments FIELD="value". + """ + args = list(args) + args.extend(_make_line(key, val) for key, val in kwargs.items()) + for arg in args: + super(Reader, self).add_match(arg) + + def get_next(self, skip=1): + """Return the next log entry as a mapping type, currently + a standard dictionary of fields. + + Optional skip value will return the `skip`\-th log entry. + + Entries will be processed with converters specified during + Reader creation. + """ + if super(Reader, self)._next(skip): + entry = super(Reader, self)._get_all() + if entry: + entry['__REALTIME_TIMESTAMP'] = self._get_realtime() + entry['__MONOTONIC_TIMESTAMP'] = self._get_monotonic() + entry['__CURSOR'] = self._get_cursor() + return self._convert_entry(entry) + return dict() + + def get_previous(self, skip=1): + """Return the previous log entry as a mapping type, + currently a standard dictionary of fields. + + Optional skip value will return the -`skip`\-th log entry. + + Entries will be processed with converters specified during + Reader creation. + + Equivalent to get_next(-skip). + """ + return self.get_next(-skip) + + def query_unique(self, field): + """Return unique values appearing in the journal for given `field`. + + Note this does not respect any journal matches. + + Entries will be processed with converters specified during + Reader creation. + """ + return set(self._convert_field(field, value) + for value in super(Reader, self).query_unique(field)) + + def wait(self, timeout=None): + """Wait for a change in the journal. `timeout` is the maximum + time in seconds to wait, or None, to wait forever. + + Returns one of NOP (no change), APPEND (new entries have been + added to the end of the journal), or INVALIDATE (journal files + have been added or removed). + """ + us = -1 if timeout is None else int(timeout * 1000000) + return super(Reader, self).wait(us) + + def seek_realtime(self, realtime): + """Seek to a matching journal entry nearest to `realtime` time. + + Argument `realtime` must be either an integer unix timestamp + or datetime.datetime instance. + """ + if isinstance(realtime, _datetime.datetime): + realtime = float(realtime.strftime("%s.%f")) * 1000000 + return super(Reader, self).seek_realtime(int(realtime)) + + def seek_monotonic(self, monotonic, bootid=None): + """Seek to a matching journal entry nearest to `monotonic` time. + + Argument `monotonic` is a timestamp from boot in either + seconds or a datetime.timedelta instance. Argument `bootid` + is a string or UUID representing which boot the monotonic time + is reference to. Defaults to current bootid. + """ + if isinstance(monotonic, _datetime.timedelta): + monotonic = monotonic.totalseconds() + monotonic = int(monotonic * 1000000) + if isinstance(bootid, _uuid.UUID): + bootid = bootid.hex + return super(Reader, self).seek_monotonic(monotonic, bootid) + + def log_level(self, level): + """Set maximum log `level` by setting matches for PRIORITY. + """ + if 0 <= level <= 7: + for i in range(level+1): + self.add_match(PRIORITY="%d" % i) + else: + raise ValueError("Log level must be 0 <= level <= 7") + + def messageid_match(self, messageid): + """Add match for log entries with specified `messageid`. + + `messageid` can be string of hexadicimal digits or a UUID + instance. Standard message IDs can be found in systemd.id128. + + Equivalent to add_match(MESSAGE_ID=`messageid`). + """ + if isinstance(messageid, _uuid.UUID): + messageid = messageid.hex + self.add_match(MESSAGE_ID=messageid) + + def this_boot(self, bootid=None): + """Add match for _BOOT_ID equal to current boot ID or the specified boot ID. + + If specified, bootid should be either a UUID or a 32 digit hex number. + + Equivalent to add_match(_BOOT_ID='bootid'). + """ + if bootid is None: + bootid = _id128.get_boot().hex + else: + bootid = getattr(bootid, 'hex', bootid) + self.add_match(_BOOT_ID=bootid) + + def this_machine(self, machineid=None): + """Add match for _MACHINE_ID equal to the ID of this machine. + + If specified, machineid should be either a UUID or a 32 digit hex number. + + Equivalent to add_match(_MACHINE_ID='machineid'). + """ + if machineid is None: + machineid = _id128.get_machine().hex + else: + machineid = getattr(machineid, 'hex', machineid) + self.add_match(_MACHINE_ID=machineid) + + +def get_catalog(mid): + if isinstance(mid, _uuid.UUID): + mid = mid.hex + return _get_catalog(mid) def _make_line(field, value): - if isinstance(value, bytes): - return field.encode('utf-8') + b'=' + value - else: - return field + '=' + value + if isinstance(value, bytes): + return field.encode('utf-8') + b'=' + value + elif isinstance(value, int): + return field + '=' + str(value) + else: + return field + '=' + value def send(MESSAGE, MESSAGE_ID=None, CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None, **kwargs): - r"""Send a message to journald. - - >>> journal.send('Hello world') - >>> journal.send('Hello, again, world', FIELD2='Greetings!') - >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef') + r"""Send a message to the journal. - Value of the MESSAGE argument will be used for the MESSAGE= field. + >>> journal.send('Hello world') + >>> journal.send('Hello, again, world', FIELD2='Greetings!') + >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef') - MESSAGE_ID can be given to uniquely identify the type of message. + Value of the MESSAGE argument will be used for the MESSAGE= + field. MESSAGE must be a string and will be sent as UTF-8 to + the journal. - Other parts of the message can be specified as keyword arguments. + MESSAGE_ID can be given to uniquely identify the type of + message. It must be a string or a uuid.UUID object. - Both MESSAGE and MESSAGE_ID, if present, must be strings, and will - be sent as UTF-8 to journal. Other arguments can be bytes, in - which case they will be sent as-is to journal. + CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to + identify the caller. Unless at least on of the three is given, + values are extracted from the stack frame of the caller of + send(). CODE_FILE and CODE_FUNC must be strings, CODE_LINE + must be an integer. - CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to identify - the caller. Unless at least on of the three is given, values are - extracted from the stack frame of the caller of send(). CODE_FILE - and CODE_FUNC must be strings, CODE_LINE must be an integer. + Additional fields for the journal entry can only be specified + as keyword arguments. The payload can be either a string or + bytes. A string will be sent as UTF-8, and bytes will be sent + as-is to the journal. - Other useful fields include PRIORITY, SYSLOG_FACILITY, - SYSLOG_IDENTIFIER, SYSLOG_PID. - """ + Other useful fields include PRIORITY, SYSLOG_FACILITY, + SYSLOG_IDENTIFIER, SYSLOG_PID. + """ - args = ['MESSAGE=' + MESSAGE] + args = ['MESSAGE=' + MESSAGE] - if MESSAGE_ID is not None: - args.append('MESSAGE_ID=' + MESSAGE_ID) + if MESSAGE_ID is not None: + id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID) + args.append('MESSAGE_ID=' + id) - if CODE_LINE == CODE_FILE == CODE_FUNC == None: - CODE_FILE, CODE_LINE, CODE_FUNC = \ - _traceback.extract_stack(limit=2)[0][:3] - if CODE_FILE is not None: - args.append('CODE_FILE=' + CODE_FILE) - if CODE_LINE is not None: - args.append('CODE_LINE={:d}'.format(CODE_LINE)) - if CODE_FUNC is not None: - args.append('CODE_FUNC=' + CODE_FUNC) + if CODE_LINE == CODE_FILE == CODE_FUNC == None: + CODE_FILE, CODE_LINE, CODE_FUNC = \ + _traceback.extract_stack(limit=2)[0][:3] + if CODE_FILE is not None: + args.append('CODE_FILE=' + CODE_FILE) + if CODE_LINE is not None: + args.append('CODE_LINE={:d}'.format(CODE_LINE)) + if CODE_FUNC is not None: + args.append('CODE_FUNC=' + CODE_FUNC) - args.extend(_make_line(key, val) for key, val in kwargs.items()) - return sendv(*args) + args.extend(_make_line(key, val) for key, val in kwargs.items()) + return sendv(*args) def stream(identifier, priority=LOG_DEBUG, level_prefix=False): - r"""Return a file object wrapping a stream to journal. + r"""Return a file object wrapping a stream to journal. - Log messages written to this file as simple newline sepearted - text strings are written to the journal. + Log messages written to this file as simple newline sepearted + text strings are written to the journal. - The file will be line buffered, so messages are actually sent - after a newline character is written. + The file will be line buffered, so messages are actually sent + after a newline character is written. - >>> stream = journal.stream('myapp') - >>> stream - <open file '<fdopen>', mode 'w' at 0x...> - >>> stream.write('message...\n') + >>> stream = journal.stream('myapp') + >>> stream + <open file '<fdopen>', mode 'w' at 0x...> + >>> stream.write('message...\n') - will produce the following message in the journal: + will produce the following message in the journal:: - PRIORITY=7 - SYSLOG_IDENTIFIER=myapp - MESSAGE=message... + PRIORITY=7 + SYSLOG_IDENTIFIER=myapp + MESSAGE=message... - Using the interface with print might be more convinient: + Using the interface with print might be more convinient: - >>> from __future__ import print_function - >>> print('message...', file=stream) + >>> from __future__ import print_function + >>> print('message...', file=stream) - priority is the syslog priority, one of LOG_EMERG, LOG_ALERT, - LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. + priority is the syslog priority, one of `LOG_EMERG`, + `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`, + `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`. - level_prefix is a boolean. If true, kernel-style log priority - level prefixes (such as '<1>') are interpreted. See sd-daemon(3) - for more information. - """ + level_prefix is a boolean. If true, kernel-style log priority + level prefixes (such as '<1>') are interpreted. See + sd-daemon(3) for more information. + """ + + fd = stream_fd(identifier, priority, level_prefix) + return _os.fdopen(fd, 'w', 1) + +class JournalHandler(_logging.Handler): + """Journal handler class for the Python logging framework. + + Please see the Python logging module documentation for an + overview: http://docs.python.org/library/logging.html. + + To create a custom logger whose messages go only to journal: + + >>> log = logging.getLogger('custom_logger_name') + >>> log.propagate = False + >>> log.addHandler(journal.JournalHandler()) + >>> log.warn("Some message: %s", detail) + + Note that by default, message levels `INFO` and `DEBUG` are + ignored by the logging framework. To enable those log levels: + + >>> log.setLevel(logging.DEBUG) + + To redirect all logging messages to journal regardless of where + they come from, attach it to the root logger: + + >>> logging.root.addHandler(journal.JournalHandler()) + + For more complex configurations when using `dictConfig` or + `fileConfig`, specify `systemd.journal.JournalHandler` as the + handler class. Only standard handler configuration options + are supported: `level`, `formatter`, `filters`. + + To attach journal MESSAGE_ID, an extra field is supported: + + >>> import uuid + >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF') + >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid}) + + Fields to be attached to all messages sent through this + handler can be specified as keyword arguments. This probably + makes sense only for SYSLOG_IDENTIFIER and similar fields + which are constant for the whole program: + + >>> journal.JournalHandler(SYSLOG_IDENTIFIER='my-cool-app') + + The following journal fields will be sent: + `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`, + `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call), + `MESSAGE_ID` (optional, see above), `SYSLOG_IDENTIFIER` (defaults + to sys.argv[0]). + """ + + def __init__(self, level=_logging.NOTSET, **kwargs): + super(JournalHandler, self).__init__(level) + + for name in kwargs: + if not _valid_field_name(name): + raise ValueError('Invalid field name: ' + name) + if 'SYSLOG_IDENTIFIER' not in kwargs: + kwargs['SYSLOG_IDENTIFIER'] = _sys.argv[0] + self._extra = kwargs + + def emit(self, record): + """Write record as journal event. + + MESSAGE is taken from the message provided by the + user, and PRIORITY, LOGGER, THREAD_NAME, + CODE_{FILE,LINE,FUNC} fields are appended + automatically. In addition, record.MESSAGE_ID will be + used if present. + """ + try: + msg = self.format(record) + pri = self.mapPriority(record.levelno) + mid = getattr(record, 'MESSAGE_ID', None) + send(msg, + MESSAGE_ID=mid, + PRIORITY=format(pri), + LOGGER=record.name, + THREAD_NAME=record.threadName, + CODE_FILE=record.pathname, + CODE_LINE=record.lineno, + CODE_FUNC=record.funcName, + **self._extra) + except Exception: + self.handleError(record) + + @staticmethod + def mapPriority(levelno): + """Map logging levels to journald priorities. - fd = stream_fd(identifier, priority, level_prefix) - return _os.fdopen(fd, 'w', 1) + Since Python log level numbers are "sparse", we have + to map numbers in between the standard levels too. + """ + if levelno <= _logging.DEBUG: + return LOG_DEBUG + elif levelno <= _logging.INFO: + return LOG_INFO + elif levelno <= _logging.WARNING: + return LOG_WARNING + elif levelno <= _logging.ERROR: + return LOG_ERR + elif levelno <= _logging.CRITICAL: + return LOG_CRIT + else: + return LOG_ALERT diff --git a/systemd/login.c b/systemd/login.c new file mode 100644 index 0000000..e844f5f --- /dev/null +++ b/systemd/login.c @@ -0,0 +1,376 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#define PY_SSIZE_T_CLEAN +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#include <Python.h> +#pragma GCC diagnostic pop + +#include "systemd/sd-login.h" +#include "pyutil.h" +#include "util.h" +#include "strv.h" + +PyDoc_STRVAR(module__doc__, + "Python interface to the libsystemd-login library." +); + +#define helper(name) \ +static PyObject* name(PyObject *self, PyObject *args) { \ + _cleanup_strv_free_ char **list = NULL; \ + int r; \ + PyObject *ans; \ + \ + assert(args == NULL); \ + \ + r = sd_get_##name(&list); \ + if (r < 0) { \ + errno = -r; \ + return PyErr_SetFromErrno(PyExc_IOError); \ + } \ + \ + ans = PyList_New(r); \ + if (!ans) \ + return NULL; \ + \ + for (r--; r >= 0; r--) { \ + PyObject *s = unicode_FromString(list[r]); \ + if (!s) { \ + Py_DECREF(ans); \ + return NULL; \ + } \ + \ + PyList_SetItem(ans, r, s); \ + } \ + \ + return ans; \ +} + +helper(seats) +helper(sessions) +helper(machine_names) +#undef helper + +static PyObject* uids(PyObject *self, PyObject *args) { + _cleanup_free_ uid_t *list = NULL; + int r; + PyObject *ans; + + assert(args == NULL); + + r = sd_get_uids(&list); + if (r < 0) { + errno = -r; + return PyErr_SetFromErrno(PyExc_IOError); + } + + ans = PyList_New(r); + if (!ans) + return NULL; + + for (r--; r >= 0; r--) { + PyObject *s = long_FromLong(list[r]); + if (!s) { + Py_DECREF(ans); + return NULL; + } + + PyList_SetItem(ans, r, s); + } + + return ans; +} + +PyDoc_STRVAR(seats__doc__, + "seats() -> list\n\n" + "Returns a list of currently available local seats.\n" + "Wraps sd_get_seats(3)." +); + +PyDoc_STRVAR(sessions__doc__, + "sessions() -> list\n\n" + "Returns a list of current login sessions.\n" + "Wraps sd_get_sessions(3)." +); + +PyDoc_STRVAR(machine_names__doc__, + "machine_names() -> list\n\n" + "Returns a list of currently running virtual machines\n" + "and containers on the system.\n" + "Wraps sd_get_machine_names(3)." +); + +PyDoc_STRVAR(uids__doc__, + "uids() -> list\n\n" + "Returns a list of uids of users who currently have login sessions.\n" + "Wraps sd_get_uids(3)." +); + +static PyMethodDef methods[] = { + { "seats", seats, METH_NOARGS, seats__doc__}, + { "sessions", sessions, METH_NOARGS, sessions__doc__}, + { "machine_names", machine_names, METH_NOARGS, machine_names__doc__}, + { "uids", uids, METH_NOARGS, uids__doc__}, + {} /* Sentinel */ +}; + + +typedef struct { + PyObject_HEAD + sd_login_monitor *monitor; +} Monitor; +static PyTypeObject MonitorType; + +static void Monitor_dealloc(Monitor* self) { + sd_login_monitor_unref(self->monitor); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +PyDoc_STRVAR(Monitor__doc__, + "Monitor([category]) -> ...\n\n" + "Monitor may be used to monitor login sessions, users, seats,\n" + "and virtual machines/containers. Monitor provides a file\n" + "descriptor which can be integrated in an external event loop.\n" + "See man:sd_login_monitor_new(3) for the details about what\n" + "can be monitored."); +static int Monitor_init(Monitor *self, PyObject *args, PyObject *keywds) { + const char *category = NULL; + int r; + + static const char* const kwlist[] = {"category", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|z:__init__", (char**) kwlist, + &category)) + return -1; + + Py_BEGIN_ALLOW_THREADS + r = sd_login_monitor_new(category, &self->monitor); + Py_END_ALLOW_THREADS + + return set_error(r, NULL, "Invalid category"); +} + + +PyDoc_STRVAR(Monitor_fileno__doc__, + "fileno() -> int\n\n" + "Get a file descriptor to poll for events.\n" + "This method wraps sd_login_monitor_get_fd(3)."); +static PyObject* Monitor_fileno(Monitor *self, PyObject *args) { + int fd = sd_login_monitor_get_fd(self->monitor); + set_error(fd, NULL, NULL); + if (fd < 0) + return NULL; + return long_FromLong(fd); +} + + +PyDoc_STRVAR(Monitor_get_events__doc__, + "get_events() -> int\n\n" + "Returns a mask of poll() events to wait for on the file\n" + "descriptor returned by .fileno().\n\n" + "See man:sd_login_monitor_get_events(3) for further discussion."); +static PyObject* Monitor_get_events(Monitor *self, PyObject *args) { + int r = sd_login_monitor_get_events(self->monitor); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + return long_FromLong(r); +} + + +PyDoc_STRVAR(Monitor_get_timeout__doc__, + "get_timeout() -> int or None\n\n" + "Returns a timeout value for usage in poll(), the time since the\n" + "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" + "is necessary.\n\n" + "The return value must be converted to a relative timeout in\n" + "milliseconds if it is to be used as an argument for poll().\n" + "See man:sd_login_monitor_get_timeout(3) for further discussion."); +static PyObject* Monitor_get_timeout(Monitor *self, PyObject *args) { + int r; + uint64_t t; + + r = sd_login_monitor_get_timeout(self->monitor, &t); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + + if (t == (uint64_t) -1) + Py_RETURN_NONE; + + assert_cc(sizeof(unsigned long long) == sizeof(t)); + return PyLong_FromUnsignedLongLong(t); +} + + +PyDoc_STRVAR(Monitor_get_timeout_ms__doc__, + "get_timeout_ms() -> int\n\n" + "Returns a timeout value suitable for usage in poll(), the value\n" + "returned by .get_timeout() converted to relative ms, or -1 if\n" + "no timeout is necessary."); +static PyObject* Monitor_get_timeout_ms(Monitor *self, PyObject *args) { + int r; + uint64_t t; + + r = sd_login_monitor_get_timeout(self->monitor, &t); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + + return absolute_timeout(t); +} + + +PyDoc_STRVAR(Monitor_close__doc__, + "close() -> None\n\n" + "Free resources allocated by this Monitor object.\n" + "This method invokes sd_login_monitor_unref().\n" + "See man:sd_login_monitor_unref(3)."); +static PyObject* Monitor_close(Monitor *self, PyObject *args) { + assert(self); + assert(!args); + + sd_login_monitor_unref(self->monitor); + self->monitor = NULL; + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Monitor_flush__doc__, + "flush() -> None\n\n" + "Reset the wakeup state of the monitor object.\n" + "This method invokes sd_login_monitor_flush().\n" + "See man:sd_login_monitor_flush(3)."); +static PyObject* Monitor_flush(Monitor *self, PyObject *args) { + assert(self); + assert(!args); + + Py_BEGIN_ALLOW_THREADS + sd_login_monitor_flush(self->monitor); + Py_END_ALLOW_THREADS + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Monitor___enter____doc__, + "__enter__() -> self\n\n" + "Part of the context manager protocol.\n" + "Returns self.\n"); +static PyObject* Monitor___enter__(PyObject *self, PyObject *args) { + assert(self); + assert(!args); + + Py_INCREF(self); + return self; +} + + +PyDoc_STRVAR(Monitor___exit____doc__, + "__exit__(type, value, traceback) -> None\n\n" + "Part of the context manager protocol.\n" + "Closes the monitor..\n"); +static PyObject* Monitor___exit__(Monitor *self, PyObject *args) { + return Monitor_close(self, args); +} + + +static PyMethodDef Monitor_methods[] = { + {"fileno", (PyCFunction) Monitor_fileno, METH_NOARGS, Monitor_fileno__doc__}, + {"get_events", (PyCFunction) Monitor_get_events, METH_NOARGS, Monitor_get_events__doc__}, + {"get_timeout", (PyCFunction) Monitor_get_timeout, METH_NOARGS, Monitor_get_timeout__doc__}, + {"get_timeout_ms", (PyCFunction) Monitor_get_timeout_ms, METH_NOARGS, Monitor_get_timeout_ms__doc__}, + {"close", (PyCFunction) Monitor_close, METH_NOARGS, Monitor_close__doc__}, + {"flush", (PyCFunction) Monitor_flush, METH_NOARGS, Monitor_flush__doc__}, + {"__enter__", (PyCFunction) Monitor___enter__, METH_NOARGS, Monitor___enter____doc__}, + {"__exit__", (PyCFunction) Monitor___exit__, METH_VARARGS, Monitor___exit____doc__}, + {} /* Sentinel */ +}; + +static PyTypeObject MonitorType = { + PyVarObject_HEAD_INIT(NULL, 0) + .tp_name = "login.Monitor", + .tp_basicsize = sizeof(Monitor), + .tp_dealloc = (destructor) Monitor_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = Monitor__doc__, + .tp_methods = Monitor_methods, + .tp_init = (initproc) Monitor_init, + .tp_new = PyType_GenericNew, +}; + +#if PY_MAJOR_VERSION < 3 + +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC initlogin(void) { + PyObject *m; + + if (PyType_Ready(&MonitorType) < 0) + return; + + m = Py_InitModule3("login", methods, module__doc__); + if (m == NULL) + return; + + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION); + + Py_INCREF(&MonitorType); + PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType); +} +REENABLE_WARNING; + +#else + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "login", /* name of module */ + module__doc__, /* module documentation, may be NULL */ + -1, /* size of per-interpreter state of the module */ + methods +}; + +DISABLE_WARNING_MISSING_PROTOTYPES; +PyMODINIT_FUNC PyInit_login(void) { + PyObject *m; + + if (PyType_Ready(&MonitorType) < 0) + return NULL; + + m = PyModule_Create(&module); + if (m == NULL) + return NULL; + + if (PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { + Py_DECREF(m); + return NULL; + } + + Py_INCREF(&MonitorType); + if (PyModule_AddObject(m, "Monitor", (PyObject *) &MonitorType)) { + Py_DECREF(&MonitorType); + Py_DECREF(m); + return NULL; + } + + return m; +} +REENABLE_WARNING; + +#endif diff --git a/systemd/pyutil.c b/systemd/pyutil.c new file mode 100644 index 0000000..722c4f5 --- /dev/null +++ b/systemd/pyutil.c @@ -0,0 +1,80 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <Python.h> +#include "pyutil.h" + +void cleanup_Py_DECREFp(PyObject **p) { + if (!*p) + return; + + Py_DECREF(*p); +} + +PyObject* absolute_timeout(uint64_t t) { + if (t == (uint64_t) -1) + return PyLong_FromLong(-1); + else { + struct timespec ts; + uint64_t n; + int msec; + + clock_gettime(CLOCK_MONOTONIC, &ts); + n = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; + msec = t > n ? (int) ((t - n + 999) / 1000) : 0; + + return PyLong_FromLong(msec); + } +} + +int set_error(int r, const char* path, const char* invalid_message) { + if (r >= 0) + return r; + if (r == -EINVAL && invalid_message) + PyErr_SetString(PyExc_ValueError, invalid_message); + else if (r == -ENOMEM) + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + else { + errno = -r; + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + } + return -1; +} + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 +int Unicode_FSConverter(PyObject* obj, void *_result) { + PyObject **result = _result; + + assert(result); + + if (!obj) + /* cleanup: we don't return Py_CLEANUP_SUPPORTED, so + * we can assume that it was PyUnicode_FSConverter. */ + return PyUnicode_FSConverter(obj, result); + + if (obj == Py_None) { + *result = NULL; + return 1; + } + + return PyUnicode_FSConverter(obj, result); +} +#endif diff --git a/systemd/pyutil.h b/systemd/pyutil.h new file mode 100644 index 0000000..1477e7b --- /dev/null +++ b/systemd/pyutil.h @@ -0,0 +1,54 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#pragma once + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#ifndef Py_TYPE +/* avoid duplication warnings from errors in Python 2.7 headers */ +# include <Python.h> +#endif + +void cleanup_Py_DECREFp(PyObject **p); +PyObject* absolute_timeout(uint64_t t); +int set_error(int r, const char* path, const char* invalid_message); + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 +int Unicode_FSConverter(PyObject* obj, void *_result); +#endif + +#define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp))) + +#if PY_MAJOR_VERSION >=3 +# define unicode_FromStringAndSize PyUnicode_FromStringAndSize +# define unicode_FromString PyUnicode_FromString +# define long_FromLong PyLong_FromLong +# define long_FromSize_t PyLong_FromSize_t +# define long_Check PyLong_Check +# define long_AsLong PyLong_AsLong +#else +/* Python 3 type naming convention is used */ +# define unicode_FromStringAndSize PyString_FromStringAndSize +# define unicode_FromString PyString_FromString +# define long_FromLong PyInt_FromLong +# define long_FromSize_t PyInt_FromSize_t +# define long_Check PyInt_Check +# define long_AsLong PyInt_AsLong +#endif |
