chiark / gitweb /
Merge branch 'python-systemd-reader'
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 1 Mar 2013 00:53:42 +0000 (19:53 -0500)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Fri, 1 Mar 2013 01:05:12 +0000 (20:05 -0500)
* python-systemd-reader:
  python-systemd: rename Journal to Reader
  build-sys: upload python documentation to freedesktop.org
  systemd-python: add Journal class for reading journal
  python: build html docs using sphinx
  journalct: also print Python code in --new-id
  python: utilize uuid.UUID in logging
  python: add systemd.id128 module
  ... and 34 other commits

In short: python module systemd.id128 is added, and existing
systemd.journal gains a new class systemd.journal.Reader, which can be
used to iterate over journal entries. Documentation is provided, and
accessible under e.g.
    pydoc3 systemd.journal.Reader
or
    firefox http://www.freedesktop.org/software/systemd/man/python-systemd/

16 files changed:
Makefile.am
README
configure.ac
man/.gitignore
src/journal/journalctl.c
src/python-systemd/.gitignore [new file with mode: 0644]
src/python-systemd/_journal.c
src/python-systemd/_reader.c [new file with mode: 0644]
src/python-systemd/docs/conf.py [new file with mode: 0644]
src/python-systemd/docs/id128.rst [new file with mode: 0644]
src/python-systemd/docs/index.rst [new file with mode: 0644]
src/python-systemd/docs/journal.rst [new file with mode: 0644]
src/python-systemd/id128.c [new file with mode: 0644]
src/python-systemd/journal.py
src/python-systemd/pyutil.c [new file with mode: 0644]
src/python-systemd/pyutil.h [new file with mode: 0644]

index d00ca11..790e501 100644 (file)
@@ -3398,7 +3398,9 @@ EXTRA_DIST += \
 # ------------------------------------------------------------------------------
 if HAVE_PYTHON_DEVEL
 pkgpyexec_LTLIBRARIES = \
-       _journal.la
+       _journal.la \
+       id128.la \
+       _reader.la
 
 _journal_la_SOURCES = \
        src/python-systemd/_journal.c
@@ -3418,11 +3420,75 @@ _journal_la_LIBADD = \
        $(PYTHON_LIBS) \
        libsystemd-journal.la
 
+id128_la_SOURCES = \
+       src/python-systemd/id128.c \
+       src/python-systemd/id128-constants.h \
+       src/python-systemd/pyutil.c \
+       src/python-systemd/pyutil.h
+
+id128_la_CFLAGS = \
+       $(AM_CFLAGS) \
+        -fvisibility=default \
+       $(PYTHON_CFLAGS) \
+       -I$(top_builddir)/src/python-systemd
+
+id128_la_LDFLAGS = \
+       $(AM_LDFLAGS) \
+       -shared \
+       -module \
+       -avoid-version
+
+id128_la_LIBADD = \
+       $(PYTHON_LIBS) \
+       libsystemd-id128.la
+
+_reader_la_SOURCES = \
+       src/python-systemd/_reader.c \
+       src/python-systemd/pyutil.c \
+       src/python-systemd/pyutil.h
+
+_reader_la_CFLAGS = \
+       $(AM_CFLAGS) \
+        -fvisibility=default \
+       $(PYTHON_CFLAGS)
+
+_reader_la_LDFLAGS = \
+       $(AM_LDFLAGS) \
+       -shared \
+       -module \
+       -avoid-version
+
+_reader_la_LIBADD = \
+       $(PYTHON_LIBS) \
+       libsystemd-journal.la \
+       libsystemd-id128.la \
+       libsystemd-shared.la
+
 dist_pkgpyexec_PYTHON = \
        src/python-systemd/journal.py \
        src/python-systemd/__init__.py
+
+src/python-systemd/id128-constants.h: src/systemd/sd-messages.h Makefile
+       $(AM_V_at)$(MKDIR_P) $(dir $@)
+       $(AM_V_GEN)$(SED) -n -r 's/,//g; s/#define (SD_MESSAGE_[A-Z0-9_]+)\s.*/add_id(m, "\1", \1) JOINER/p' <$< >$@
+
+BUILT_SOURCES += \
+       src/python-systemd/id128-constants.h
 endif
 
+PAPER = $(shell cat /etc/papersize 2>/dev/null || echo a4)
+SPHINXOPTS = -D latex_paper_size=$(PAPER)
+sphinx-%:
+       $(AM_V_at)test -n "$(SPHINX_BUILD)" || { echo " *** sphinx-build is not available"; exit 1; }
+       $(AM_V_GEN)PYTHONPATH=$(DESTDIR)$(pyexecdir) LD_LIBRARY_PATH=$(DESTDIR)$(libdir) $(SPHINX_BUILD) -b $* $(SPHINXOPTS) $(top_srcdir)/src/python-systemd/docs $(top_builddir)/man/python-systemd/
+       $(AM_V_at)echo Output has been generated in $(abs_top_builddir)/man/python-systemd/
+
+destdir-sphinx: all
+       dir="`mktemp -d /tmp/systemd-install.XXXXXX`" && \
+               $(MAKE) DESTDIR="$$dir" install && \
+               $(MAKE) DESTDIR="$$dir" sphinx-html && \
+               rm -rf "$$dir"
+
 # ------------------------------------------------------------------------------
 SED_PROCESS = \
        $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \
@@ -3743,17 +3809,19 @@ upload: all distcheck
        scp systemd-$(VERSION).tar.xz fdo:/srv/www.freedesktop.org/www/software/systemd/
        scp man/*.html tango:public/systemd-man/
 
-doc-sync: all
+www_target = www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd
+doc-sync: all destdir-sphinx
        gtkdoc-rebase --html-dir=docs/libudev/html --online
-       rsync -av --delete docs/libudev/html/ --omit-dir-times www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd/libudev/
+       rsync -av --delete docs/libudev/html/ --omit-dir-times $(www_target)/libudev/
        gtkdoc-rebase --html-dir=docs/gudev/html --online
-       rsync -av --delete docs/gudev/html/ --omit-dir-times www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd/gudev/
-       rsync -av --delete-excluded --include="*.html" --exclude="*" --omit-dir-times man/ www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd/man/
+       rsync -av --delete docs/gudev/html/ --omit-dir-times $(www_target)/gudev/
+       rsync -av --delete-excluded --include="*.html" --exclude="*" --omit-dir-times man/ $(www_target)/man/
+       rsync -av --delete --omit-dir-times man/python-systemd/ $(www_target)/man/python-systemd/
 
 git-tag:
        git tag "v$(VERSION)" -m "systemd $(VERSION)"
 
 install-tree: all
        rm -rf $(abs_srcdir)/install-tree
-       make install DESTDIR=$(abs_srcdir)/install-tree
+       $(MAKE) install DESTDIR=$(abs_srcdir)/install-tree
        tree $(abs_srcdir)/install-tree
diff --git a/README b/README
index 297d8f7..1aa044b 100644 (file)
--- a/README
+++ b/README
@@ -81,6 +81,7 @@ REQUIREMENTS:
         gperf
         gtkdocize (optional)
         python (optional)
+        sphinx (optional)
 
         When systemd-hostnamed is used it is strongly recommended to
         install nss-myhostname to ensure that in a world of
@@ -93,6 +94,12 @@ REQUIREMENTS:
         please build D-Bus without systemd first, then build systemd,
         then rebuild D-Bus with systemd support.
 
+        To build HTML documentation for python-systemd using sphinx,
+        please first install systemd (using 'make install'), and then
+        invoke sphinx-build with 'make sphinx-<target>', with <target>
+        being 'html' or 'latexpdf'. If using DESTDIR for installation,
+        pass the same DESTDIR to 'make sphinx-html' invocation.
+
 WARNINGS:
         systemd will warn you during boot if /etc/mtab is not a
         symlink to /proc/mounts. Please ensure that /etc/mtab is a
index b7a587c..ea9fafe 100644 (file)
@@ -176,6 +176,7 @@ AS_IF([test "x$with_python" != "xno"], [
               PYTHON_LIBS="`$PYTHON_CONFIG --ldflags`"
               AC_SUBST(PYTHON_CFLAGS)
               AC_SUBST(PYTHON_LIBS)
+              AC_PATH_PROGS(SPHINX_BUILD, sphinx-build-${PYTHON_VERSION} sphinx-build)
         ])
 ])
 AM_CONDITIONAL([HAVE_PYTHON_DEVEL], [test "$have_python_devel" = "yes"])
index 1876794..fcf4dab 100644 (file)
@@ -2,3 +2,4 @@
 /systemd.index.xml
 /systemd.unit.xml
 /*.[13578]
+/python-systemd/
index 0b3a79b..9084509 100644 (file)
@@ -462,14 +462,17 @@ static int generate_new_id128(void) {
                "As UUID:\n"
                "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n"
                "As macro:\n"
-              "#define MESSAGE_XYZ SD_ID128_MAKE(",
+               "#define MESSAGE_XYZ SD_ID128_MAKE(",
                SD_ID128_FORMAT_VAL(id),
                SD_ID128_FORMAT_VAL(id));
-
         for (i = 0; i < 16; i++)
                 printf("%02x%s", id.bytes[i], i != 15 ? "," : "");
+        fputs(")\n\n", stdout);
 
-        fputs(")\n", stdout);
+        printf("As Python constant:\n"
+               ">>> import uuid\n"
+               ">>> MESSAGE_XYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')\n",
+               SD_ID128_FORMAT_VAL(id));
 
         return 0;
 }
diff --git a/src/python-systemd/.gitignore b/src/python-systemd/.gitignore
new file mode 100644 (file)
index 0000000..4124b7a
--- /dev/null
@@ -0,0 +1,2 @@
+/id128-constants.h
+*.py[oc]
index 0bdf709..ced52b2 100644 (file)
@@ -3,7 +3,7 @@
 /***
   This file is part of systemd.
 
-  Copyright 2012 David Strauss
+  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
diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c
new file mode 100644 (file)
index 0000000..7f200d5
--- /dev/null
@@ -0,0 +1,774 @@
+/*-*- Mode: C; c-basic-offset: 4; 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 <stdio.h>
+
+#include <systemd/sd-journal.h>
+
+#include "pyutil.h"
+#include "macro.h"
+#include "util.h"
+
+#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
+
+typedef struct {
+    PyObject_HEAD
+    sd_journal *j;
+} Reader;
+static PyTypeObject ReaderType;
+
+static 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
+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"},
+    {NULL, NULL}
+};
+
+static PyStructSequence_Desc Monotonic_desc = {
+    (char*) "journal.Monotonic",
+    MonotonicType__doc__,
+    MonotonicType_fields,
+    2,
+};
+#endif
+
+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]) -> ...\n\n"
+             "Reader allows filtering and retrieval of Journal entries.\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_ONLY opens only\n"
+             "journal files of system services and the kernel.\n"
+             "Argument `path` is the directory of journal files. Note that\n"
+             "currently flags are ignored when `path` is present as they are\n"
+             "not relevant.");
+static int Reader_init(Reader *self, PyObject *args, PyObject *keywds)
+{
+    int flags = SD_JOURNAL_LOCAL_ONLY, r;
+    char *path = NULL;
+
+    static const char* const kwlist[] = {"flags", "path", NULL};
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iz", (char**) kwlist,
+                                     &flags, &path))
+        return 1;
+
+    Py_BEGIN_ALLOW_THREADS
+    if (path)
+        r = sd_journal_open_directory(&self->j, path, 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_get_next__doc__,
+             "get_next([skip]) -> dict\n\n"
+             "Return dictionary of the next log entry. Optional skip value will\n"
+             "return the `skip`\\-th log entry.");
+static PyObject* Reader_get_next(Reader *self, PyObject *args)
+{
+    PyObject *dict;
+    const void *msg;
+    size_t msg_len;
+    int64_t skip = 1LL;
+    int r;
+
+    if (!PyArg_ParseTuple(args, "|L", &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
+
+    set_error(r, NULL, NULL);
+    if (r < 0)
+        return NULL;
+    else if (r == 0) /* EOF */
+        return PyDict_New();
+
+    dict = PyDict_New();
+    if (!dict)
+            return NULL;
+
+    SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
+        PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
+        const char *delim_ptr;
+
+        delim_ptr = memchr(msg, '=', msg_len);
+        if (!delim_ptr) {
+            PyErr_SetString(PyExc_OSError,
+                            "journal gave us a field without '='");
+            goto error;
+        }
+
+        key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
+        if (!key)
+            goto error;
+
+        value = PyBytes_FromStringAndSize(
+                delim_ptr + 1,
+                (const char*) msg + msg_len - (delim_ptr + 1) );
+        if (!value)
+            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 {
+                PyObject _cleanup_Py_DECREF_ *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;
+
+                PyDict_SetItem(dict, key, tmp_list);
+                if (r < 0)
+                    goto error;
+            }
+        } else {
+            r = PyDict_SetItem(dict, key, value);
+            if (r < 0)
+                goto error;
+        }
+    }
+
+    {
+        PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
+        uint64_t realtime;
+
+        r = sd_journal_get_realtime_usec(self->j, &realtime);
+        if (set_error(r, NULL, NULL))
+            goto error;
+
+        key = unicode_FromString("__REALTIME_TIMESTAMP");
+        if (!key)
+            goto error;
+
+        assert_cc(sizeof(unsigned long long) == sizeof(realtime));
+        value = PyLong_FromUnsignedLongLong(realtime);
+        if (!value)
+            goto error;
+
+        if (PyDict_SetItem(dict, key, value))
+            goto error;
+    }
+
+    {
+        PyObject _cleanup_Py_DECREF_
+            *key = NULL, *timestamp = NULL, *bytes = NULL, *value = NULL;
+        sd_id128_t id;
+        uint64_t monotonic;
+
+        r = sd_journal_get_monotonic_usec(self->j, &monotonic, &id);
+        if (set_error(r, NULL, NULL))
+            goto error;
+
+        assert_cc(sizeof(unsigned long long) == sizeof(monotonic));
+        key = unicode_FromString("__MONOTONIC_TIMESTAMP");
+        timestamp = PyLong_FromUnsignedLongLong(monotonic);
+        bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes));
+#if PY_MAJOR_VERSION >= 3
+        value = PyStructSequence_New(&MonotonicType);
+#else
+        value = PyTuple_New(2);
+#endif
+        if (!key || !timestamp || !bytes || !value)
+            goto error;
+
+        Py_INCREF(timestamp);
+        Py_INCREF(bytes);
+
+#if PY_MAJOR_VERSION >= 3
+        PyStructSequence_SET_ITEM(value, 0, timestamp);
+        PyStructSequence_SET_ITEM(value, 1, bytes);
+#else
+        PyTuple_SET_ITEM(value, 0, timestamp);
+        PyTuple_SET_ITEM(value, 1, bytes);
+#endif
+
+        if (PyDict_SetItem(dict, key, value))
+            goto error;
+    }
+
+    {
+        PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
+        char _cleanup_free_ *cursor = NULL;
+
+        r = sd_journal_get_cursor(self->j, &cursor);
+        if (set_error(r, NULL, NULL))
+            goto error;
+
+        key = unicode_FromString("__CURSOR");
+        if (!key)
+            goto error;
+
+        value = PyBytes_FromString(cursor);
+        if (!value)
+            goto error;
+
+        if (PyDict_SetItem(dict, key, value))
+            goto error;
+    }
+
+    return dict;
+error:
+    Py_DECREF(dict);
+    return NULL;
+}
+
+PyDoc_STRVAR(Reader_get_previous__doc__,
+             "get_previous([skip]) -> dict\n\n"
+             "Return dictionary of the previous log entry. Optional skip value\n"
+             "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip).");
+static PyObject* Reader_get_previous(Reader *self, PyObject *args)
+{
+    int64_t skip = 1LL;
+    if (!PyArg_ParseTuple(args, "|L", &skip))
+        return NULL;
+
+    return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
+                               (char*) "L", -skip);
+}
+
+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#", &match, &match_len))
+        return NULL;
+
+    r = sd_journal_add_match(self->j, match, match_len);
+    set_error(r, NULL, "Invalid match");
+    if (r < 0)
+            return NULL;
+
+    Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Reader_add_disjunction__doc__,
+             "add_disjunction() -> None\n\n"
+             "Inserts a logical OR between matches added before and afterwards.");
+static PyObject* Reader_add_disjunction(Reader *self, PyObject *args)
+{
+    int r;
+    r = sd_journal_add_disjunction(self->j);
+    set_error(r, NULL, NULL);
+    if (r < 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__doc__,
+             "seek(offset[, whence]) -> None\n\n"
+             "Jump `offset` entries in the journal. Argument\n"
+             "`whence` defines what the offset is relative to:\n"
+             "os.SEEK_SET (default) from first match in journal;\n"
+             "os.SEEK_CUR from current position in journal;\n"
+             "and os.SEEK_END is from last match in journal.");
+static PyObject* Reader_seek(Reader *self, PyObject *args, PyObject *keywds)
+{
+    int64_t offset;
+    int whence = SEEK_SET;
+    PyObject *result = NULL;
+
+    static const char* const kwlist[] = {"offset", "whence", NULL};
+    if (!PyArg_ParseTupleAndKeywords(args, keywds, "L|i", (char**) kwlist,
+                                     &offset, &whence))
+        return NULL;
+
+    switch(whence) {
+    case SEEK_SET: {
+        int r;
+        Py_BEGIN_ALLOW_THREADS
+        r = sd_journal_seek_head(self->j);
+        Py_END_ALLOW_THREADS
+        if (set_error(r, NULL, NULL))
+            return NULL;
+
+        if (offset > 0LL)
+            result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
+                                         (char*) "L", offset);
+        break;
+    }
+    case SEEK_CUR:
+        result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
+                                     (char*) "L", offset);
+        break;
+    case SEEK_END: {
+        int r;
+        Py_BEGIN_ALLOW_THREADS
+        r = sd_journal_seek_tail(self->j);
+        Py_END_ALLOW_THREADS
+        if (set_error(r, NULL, NULL))
+            return NULL;
+
+        result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
+                                     (char*) "L", offset < 0LL ? offset : -1LL);
+        break;
+    }
+    default:
+        PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
+    }
+
+    Py_XDECREF(result);
+    if (PyErr_Occurred())
+        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` can must be an integer unix timestamp.");
+static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
+{
+    double timedouble;
+    uint64_t timestamp;
+    int r;
+
+    if (!PyArg_ParseTuple(args, "d", &timedouble))
+        return NULL;
+
+    timestamp = (uint64_t) (timedouble * 1.0E6);
+    if ((int64_t) timestamp < 0LL) {
+        PyErr_SetString(PyExc_ValueError, "Time must be a positive integer");
+        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))
+        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 seconds.\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)
+{
+    double timedouble;
+    char *bootid = NULL;
+    uint64_t timestamp;
+    sd_id128_t id;
+    int r;
+
+    if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
+        return NULL;
+
+    timestamp = (uint64_t) (timedouble * 1.0E6);
+
+    if ((int64_t) timestamp < 0LL) {
+        PyErr_SetString(PyExc_ValueError, "Time must be positive number");
+        return NULL;
+    }
+
+    if (bootid) {
+        r = sd_id128_from_string(bootid, &id);
+        if (set_error(r, NULL, "Invalid bootid"))
+            return NULL;
+    } else {
+        Py_BEGIN_ALLOW_THREADS
+        r = sd_id128_get_boot(&id);
+        Py_END_ALLOW_THREADS
+        if (set_error(r, NULL, NULL))
+            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))
+        return NULL;
+    Py_RETURN_NONE;
+}
+
+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 seconds to wait before returning\n"
+             "regardless of wheter the journal has changed. If `timeout` is not given\n"
+             "or is 0, then block forever.\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.");
+static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds)
+{
+    int r;
+    int64_t timeout = 0LL;
+
+    if (!PyArg_ParseTuple(args, "|L", &timeout))
+        return NULL;
+
+    Py_BEGIN_ALLOW_THREADS
+    r = sd_journal_wait(self->j, timeout ==0 ? (uint64_t) -1 : timeout * 1E6);
+    Py_END_ALLOW_THREADS
+    if (set_error(r, NULL, NULL))
+        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", &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"))
+        return NULL;
+    Py_RETURN_NONE;
+}
+
+static PyObject* Reader_iter(PyObject *self)
+{
+    Py_INCREF(self);
+    return self;
+}
+
+static PyObject* Reader_iternext(PyObject *self)
+{
+    PyObject *dict;
+    Py_ssize_t dict_size;
+
+    dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
+    if (PyErr_Occurred())
+        return NULL;
+    dict_size = PyDict_Size(dict);
+    if ((int64_t) dict_size > 0LL) {
+        return dict;
+    } else {
+        Py_DECREF(dict);
+        PyErr_SetNone(PyExc_StopIteration);
+        return NULL;
+    }
+}
+
+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))
+        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"))
+        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(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))
+        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);
+}
+
+static PyGetSetDef Reader_getseters[] = {
+    {(char*) "data_threshold",
+     (getter) Reader_get_data_threshold,
+     (setter) Reader_set_data_threshold,
+     (char*) data_threshold__doc__,
+     NULL},
+    {NULL}
+};
+
+static PyMethodDef Reader_methods[] = {
+    {"get_next",        (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
+    {"get_previous",    (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__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__},
+    {"flush_matches",   (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
+    {"seek",            (PyCFunction) Reader_seek, METH_VARARGS | METH_KEYWORDS,  Reader_seek__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__},
+    {"wait",            (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
+    {"seek_cursor",     (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
+    {"query_unique",    (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
+    {NULL}  /* Sentinel */
+};
+
+static PyTypeObject ReaderType = {
+    PyVarObject_HEAD_INIT(NULL, 0)
+    "_reader._Reader",                        /*tp_name*/
+    sizeof(Reader),                           /*tp_basicsize*/
+    0,                                        /*tp_itemsize*/
+    (destructor)Reader_dealloc,               /*tp_dealloc*/
+    0,                                        /*tp_print*/
+    0,                                        /*tp_getattr*/
+    0,                                        /*tp_setattr*/
+    0,                                        /*tp_compare*/
+    0,                                        /*tp_repr*/
+    0,                                        /*tp_as_number*/
+    0,                                        /*tp_as_sequence*/
+    0,                                        /*tp_as_mapping*/
+    0,                                        /*tp_hash */
+    0,                                        /*tp_call*/
+    0,                                        /*tp_str*/
+    0,                                        /*tp_getattro*/
+    0,                                        /*tp_setattro*/
+    0,                                        /*tp_as_buffer*/
+    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
+    Reader__doc__,                            /* tp_doc */
+    0,                                        /* tp_traverse */
+    0,                                        /* tp_clear */
+    0,                                        /* tp_richcompare */
+    0,                                        /* tp_weaklistoffset */
+    Reader_iter,                              /* tp_iter */
+    Reader_iternext,                          /* tp_iternext */
+    Reader_methods,                           /* tp_methods */
+    0,                                        /* tp_members */
+    Reader_getseters,                         /* tp_getset */
+    0,                                        /* tp_base */
+    0,                                        /* tp_dict */
+    0,                                        /* tp_descr_get */
+    0,                                        /* tp_descr_set */
+    0,                                        /* tp_dictoffset */
+    (initproc) Reader_init,                   /* tp_init */
+    0,                                        /* tp_alloc */
+    PyType_GenericNew,                        /* tp_new */
+};
+
+#define SUMMARY \
+    "Module that reads the systemd journal similar to journalctl."
+
+#if PY_MAJOR_VERSION >= 3
+static PyModuleDef _reader_module = {
+    PyModuleDef_HEAD_INIT,
+    "_reader",
+    SUMMARY,
+    -1,
+    NULL, NULL, NULL, NULL, NULL
+};
+#endif
+
+#if PY_MAJOR_VERSION >= 3
+static bool initialized = false;
+#endif
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-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(&_reader_module);
+    if (m == NULL)
+        return NULL;
+
+    if (!initialized) {
+        PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
+        initialized = true;
+    }
+#else
+    m = Py_InitModule3("_reader", NULL, SUMMARY);
+    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_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
+#if PY_MAJOR_VERSION >= 3
+        Py_DECREF(m);
+        return NULL;
+#endif
+    }
+
+#if PY_MAJOR_VERSION >= 3
+    return m;
+#endif
+}
+
+#pragma GCC diagnostic pop
diff --git a/src/python-systemd/docs/conf.py b/src/python-systemd/docs/conf.py
new file mode 100644 (file)
index 0000000..4a55778
--- /dev/null
@@ -0,0 +1,288 @@
+# -*- 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 = ['_templates']
+
+# 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 version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '197'
+# The full version, including alpha/beta/rc tags.
+release = '197'
+
+# 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/src/python-systemd/docs/id128.rst b/src/python-systemd/docs/id128.rst
new file mode 100644 (file)
index 0000000..12c28f3
--- /dev/null
@@ -0,0 +1,38 @@
+`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
diff --git a/src/python-systemd/docs/index.rst b/src/python-systemd/docs/index.rst
new file mode 100644 (file)
index 0000000..f04d5a1
--- /dev/null
@@ -0,0 +1,22 @@
+.. 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
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst
new file mode 100644 (file)
index 0000000..78b831a
--- /dev/null
@@ -0,0 +1,49 @@
+`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__
+
+.. autoclass:: Monotonic
+
+.. autoattribute:: systemd.journal.DEFAULT_CONVERTERS
+
+Whence constants
+~~~~~~~~~~~~~~~~
+
+.. autoattribute:: systemd.journal.SEEK_SET
+.. autoattribute:: systemd.journal.SEEK_CUR
+.. autoattribute:: systemd.journal.SEEK_END
+
+Journal access types
+~~~~~~~~~~~~~~~~~~~~
+
+.. autoattribute:: systemd.journal.LOCAL_ONLY
+.. autoattribute:: systemd.journal.RUNTIME_ONLY
+.. autoattribute:: systemd.journal.SYSTEM_ONLY
+
+Journal event types
+~~~~~~~~~~~~~~~~~~~
+
+.. autoattribute:: systemd.journal.NOP
+.. autoattribute:: systemd.journal.APPEND
+.. autoattribute:: systemd.journal.INVALIDATE
diff --git a/src/python-systemd/id128.c b/src/python-systemd/id128.c
new file mode 100644 (file)
index 0000000..a6711a5
--- /dev/null
@@ -0,0 +1,162 @@
+/*-*- 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 <stdbool.h>
+
+#include <Python.h>
+
+#include <systemd/sd-messages.h>
+
+#include "pyutil.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) {
+        PyObject _cleanup_Py_DECREF_
+                *uuid = NULL, *UUID = NULL, *bytes = NULL,
+                *args = NULL, *kwargs = NULL, *obj = 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);
+}
+
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wmissing-prototypes"
+
+#if PY_MAJOR_VERSION < 3
+
+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
+}
+
+#else
+
+static struct PyModuleDef module = {
+        PyModuleDef_HEAD_INIT,
+        "id128", /* name of module */
+        module__doc__, /* module documentation, may be NULL */
+        0, /* size of per-interpreter state of the module */
+        methods
+};
+
+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
+                false) {
+                Py_DECREF(m);
+                return NULL;
+        }
+
+        return m;
+}
+
+#endif
+
+#pragma GCC diagnostic pop
index 516ca1a..23e1d65 100644 (file)
@@ -1,8 +1,10 @@
-#  -*- Mode: python; indent-tabs-mode: nil -*- */
+#  -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */
 #
 #  This file is part of systemd.
 #
-#  Copyright 2012 David Strauss
+#  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
 #  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 functools as _functools
+import uuid as _uuid
 import traceback as _traceback
 import os as _os
+from os import SEEK_SET, SEEK_CUR, SEEK_END
 import logging as _logging
+if _sys.version_info >= (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 ._reader import (_Reader, NOP, APPEND, INVALIDATE,
+                      LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY)
+from . import id128 as _id128
+
+if _sys.version_info >= (3,):
+    from ._reader import Monotonic
+else:
+    Monotonic = tuple
+
+_MONOTONIC_CONVERTER = lambda p: Monotonic((_datetime.timedelta(microseconds=p[0]),
+                                            _uuid.UUID(bytes=p[1])))
+_REALTIME_CONVERTER = lambda x: _datetime.datetime.fromtimestamp(x / 1E6)
+DEFAULT_CONVERTERS = {
+    'MESSAGE_ID': _uuid.UUID,
+    '_MACHINE_ID': _uuid.UUID,
+    '_BOOT_ID': _uuid.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': _REALTIME_CONVERTER,
+    '__REALTIME_TIMESTAMP': _REALTIME_CONVERTER,
+    '_SOURCE_MONOTONIC_TIMESTAMP': _MONOTONIC_CONVERTER,
+    '__MONOTONIC_TIMESTAMP': _MONOTONIC_CONVERTER,
+    'COREDUMP': bytes,
+    'COREDUMP_PID': int,
+    'COREDUMP_UID': int,
+    'COREDUMP_GID': int,
+    'COREDUMP_SESSION': int,
+    'COREDUMP_SIGNAL': int,
+    'COREDUMP_TIMESTAMP': _REALTIME_CONVERTER,
+}
+
+if _sys.version_info >= (3,):
+    _convert_unicode = _functools.partial(str, encoding='utf-8')
+else:
+    _convert_unicode = _functools.partial(unicode, encoding='utf-8')
+
+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 `adm` group.
+
+    Example usage to print out all error or higher level messages
+    for systemd-udevd for the boot:
+
+    >>> myjournal = journal.Reader()
+    >>> myjournal.add_boot_match(journal.CURRENT_BOOT)
+    >>> myjournal.add_loglevel_matches(journal.LOG_ERR)
+    >>> myjournal.add_match(_SYSTEMD_UNIT="systemd-udevd.service")
+    >>> for entry in myjournal:
+    ...    print(entry['MESSAGE'])
+
+    See systemd.journal-fields(7) for more info on typical fields
+    found in the journal.
+    """
+    def __init__(self, converters=None, flags=LOCAL_ONLY, path=None):
+        """Create an instance of Reader, which allows filtering and
+        return of journal entries.
+        Argument `converters` is a dictionary which updates the
+        DEFAULT_CONVERTERS to convert journal field values.
+        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
+        currently flags are ignored when `path` is present as they are
+        currently not relevant.
+        """
+        super(Reader, self).__init__(flags, path)
+        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 based on callable from self.converters
+        based of field/key"""
+        try:
+            result = self.converters[key](value)
+        except:
+            # Default conversion in unicode
+            try:
+                result = _convert_unicode(value)
+            except UnicodeDecodeError:
+                # Leave in default bytes
+                result = value
+        return result
+
+    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 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 dictionary of fields.
+
+        Optional skip value will return the `skip`\-th log entry.
+
+        Entries will be processed with converters specified during
+        Reader creation.
+        """
+        return self._convert_entry(
+            super(Reader, 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 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"))
+        return super(Reader, self).seek_realtime(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()
+        if isinstance(bootid, _uuid.UUID):
+            bootid = bootid.get_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="%s" % 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.get_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 _make_line(field, value):
         if isinstance(value, bytes):
@@ -33,24 +269,18 @@ def _make_line(field, value):
 def send(MESSAGE, MESSAGE_ID=None,
          CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None,
          **kwargs):
-        r"""Send a message to journald.
+        r"""Send a message to the journal.
 
         >>> journal.send('Hello world')
         >>> journal.send('Hello, again, world', FIELD2='Greetings!')
         >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef')
 
         Value of the MESSAGE argument will be used for the MESSAGE=
-        field.
+        field. MESSAGE must be a string and will be sent as UTF-8 to
+        the journal.
 
         MESSAGE_ID can be given to uniquely identify the type of
-        message.
-
-        Other parts of the message can be specified as keyword
-        arguments.
-
-        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.
+        message. It must be a string or a uuid.UUID object.
 
         CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to
         identify the caller. Unless at least on of the three is given,
@@ -58,6 +288,11 @@ def send(MESSAGE, MESSAGE_ID=None,
         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.
         """
@@ -65,7 +300,8 @@ def send(MESSAGE, MESSAGE_ID=None,
         args = ['MESSAGE=' + MESSAGE]
 
         if MESSAGE_ID is not None:
-                args.append('MESSAGE_ID=' + MESSAGE_ID)
+                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 = \
@@ -94,19 +330,20 @@ def stream(identifier, priority=LOG_DEBUG, level_prefix=False):
         <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:
 
         >>> 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
@@ -120,7 +357,7 @@ 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
+        overview: http://docs.python.org/library/logging.html.
 
         To create a custom logger whose messages go only to journal:
 
@@ -129,31 +366,31 @@ class JournalHandler(_logging.Handler):
         >>> 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:
+        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 attach journal MESSAGE_ID, an extra field is supported:
 
-        >>> log.warn("Message with ID",
-        >>>     extra={'MESSAGE_ID': '22bb01335f724c959ac4799627d1cb61'})
+        >>> import uuid
+        >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF')
+        >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid})
 
         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
+        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.
+        are supported: `level`, `formatter`, `filters`.
 
         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).
+        `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`,
+        `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call),
+        `MESSAGE_ID` (optional, see above).
         """
 
         def emit(self, record):
diff --git a/src/python-systemd/pyutil.c b/src/python-systemd/pyutil.c
new file mode 100644 (file)
index 0000000..79065a1
--- /dev/null
@@ -0,0 +1,30 @@
+/*-*- 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);
+}
diff --git a/src/python-systemd/pyutil.h b/src/python-systemd/pyutil.h
new file mode 100644 (file)
index 0000000..3b7bc58
--- /dev/null
@@ -0,0 +1,29 @@
+/*-*- 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/>.
+***/
+
+#ifndef Py_TYPE
+/* avoid duplication warnings from errors in Python 2.7 headers */
+# include <Python.h>
+#endif
+
+void cleanup_Py_DECREFp(PyObject **p);
+
+#define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp)))