[systemd-devel] python - reading the journal
Zbigniew Jędrzejewski-Szmek
zbyszek at in.waw.pl
Sun Feb 10 21:49:10 PST 2013
On Fri, Feb 08, 2013 at 10:20:57PM +0000, Steven Hiscocks wrote:
> I thought the easiest way was to just inherit the Journal (now
> _Journal), replacing __new__ with all the python RunString stuff. https://github.com/kwirk/systemd/commit/9003fdbc69577840ab6a1501a1c49f7377b6124d
Hi Steven,
comments on your patch:
0. It would be nice to define
#if PY_MAJOR_VERSION >=3
#define string_from_string PyUnicode_FromString
#else
#define string_from_string PyString_FromString
#endif
since this #if appears a number of times in the sources.
Other #ifs don't appear so many times in the same form.
Likewise in pure Python code we could use:
if sys.version_info >= (3,):
convert_unicode = functools.partial(str, encoding='utf-8')
else:
convert_unicode = functools.partial(unicode, encoding='utf-8')
1. I think the trick with __new__ isn't really necessary.
If we are splitting the abstraction levels, than all conversions
should be done in the Python code, and the C code should be a thin
layer over the journal API.
The default conversion must be bytes, otherwise every new binary
message field that this module doesn't know about will cause failure.
It is better to default to bytes.
Since we are not supporting anything below 2.6, bytes can be
always called bytes, in Python 2.6-2.7 it is aliased to str.
This will reduce the iffdefinery a bit.
Also, I don't think the dictionary should be initalized every time.
# module-level constant
DEFAULT_CONVERTERS = {
'MESSAGE_ID': uuid.UUID,
'PRIORITY': int,
...
}
class Journal(_Journal):
def __init__(self, converters=None):
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:
# suitable fallback, e.g.
self.converters = DEFAULT_CONVERTERS.copy()
if self.converters is not None:
self.converters.update(converters)
This avoids length initialization in modern Pythons, and provides
a working fallback for order versions. Semantics are slightly different,
since updating DEFAULT_CONVERTERS updates the ChainMap, but this
can be documented. Even in older Pythons, dict.copy() should
be faster than initialization, since it is implemented in C.
2. In id128.c there's a _cleanup_Py_DECREF_ macro to avoid
explicit Py_DECREFs. Works like this:
Old:
PyObject *obj;
obj = Py_BuildValue() # or whatever
Py_DECREF(obj);
New:
PyObject _cleanup_Py_DECREF_ *obj = NULL;
obj = Py_BuildValue() # or whatever
# Py_DECREF is called on exit form scope.
3. In systemd all errors should be caught. This includes memory
allocation errors as far as possible.
Also, error information should be propagated, and it is better
to exploit strerror than hardcode messages.
Now some specific comments:
diff --git Makefile.am Makefile.am
index 7885a79..2a14693 100644
--- Makefile.am
+++ Makefile.am
@@ -3326,7 +3326,8 @@ EXTRA_DIST += \
# ------------------------------------------------------------------------------
if HAVE_PYTHON_DEVEL
pkgpyexec_LTLIBRARIES = \
- _journal.la
+ _journal.la \
+ _reader.la
_journal_la_SOURCES = \
src/python-systemd/_journal.c
@@ -3346,6 +3347,25 @@ _journal_la_LIBADD = \
$(PYTHON_LIBS) \
libsystemd-journal.la
+_reader_la_SOURCES = \
+ src/python-systemd/_reader.c
+
+_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
+
dist_pkgpyexec_PYTHON = \
src/python-systemd/journal.py \
src/python-systemd/__init__.py
diff --git src/python-systemd/_reader.c src/python-systemd/_reader.c
new file mode 100644
index 0000000..69c6d02
--- /dev/null
+++ src/python-systemd/_reader.c
@@ -0,0 +1,1114 @@
+/*
+_reader - Python module that reads systemd journal similar to journalctl
+Copyright (C) 2012 Steven Hiscocks
+
+This library 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.
+
+This library 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 this library; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+*/
Hm, would be nice to copy the boilerplate from existing systemd sources,
so the formatting is the same.
+#include <systemd/sd-journal.h>
+
+#include <Python.h>
+#include <structmember.h>
+#include <datetime.h>
+
+typedef struct {
+ PyObject_HEAD
+ sd_journal *j;
+ PyObject *default_call;
This line could be removed.
+ PyObject *call_dict;
Likewise.
+} Journal;
+static PyTypeObject JournalType;
+
+static void
+Journal_dealloc(Journal* self)
+{
+ sd_journal_close(self->j);
+ Py_XDECREF(self->default_call);
Ditto.
+ Py_XDECREF(self->call_dict);
Ditto.
+ Py_TYPE(self)->tp_free((PyObject*)self);
+}
+
+static PyObject *
+Journal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
+{
+ Journal *self;
+
+ self = (Journal *)type->tp_alloc(type, 0);
+ if (self != NULL) {
+ self->call_dict = PyDict_New();
+ self->default_call = Py_None;
+ }
+
+ return (PyObject *) self;
+}
This whole function is not necessary now.
+PyDoc_STRVAR(Journal__doc__,
+"Journal([flags][, default_call][, call_dict][,path]) -> ...\n"
default_call, call_dict are gone.
+"Journal instance\n\n"
+"Returns instance of Journal, which allows filtering and return\n"
+"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 `default_call` must be a callable that accepts one\n"
+"argument which is string/bytes value of a field and returns\n"
+"python object.\n"
+"Argument `call_dict` is a dictionary where the key represents\n"
+"a field name, and value is a callable as per `default_call`.\n"
+"A set of sane defaults for `default_call` and `call_dict` are\n"
+"present.\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
+Journal_init(Journal *self, PyObject *args, PyObject *keywds)
+{
+ int flags=SD_JOURNAL_LOCAL_ONLY;
+ char *path=NULL;
+ PyObject *default_call=NULL, *call_dict=NULL;
...
+ static char *kwlist[] = {"flags", "default_call", "call_dict", "path", NULL};
+ if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iOOs", kwlist,
+ &flags, &default_call, &call_dict, &path))
+ return 1;
+
+ if (default_call) {
+ if (PyCallable_Check(default_call) || default_call == Py_None) {
+ Py_DECREF(self->default_call);
+ self->default_call = default_call;
+ Py_INCREF(self->default_call);
+ }else{
+ PyErr_SetString(PyExc_TypeError, "Default call not callable");
+ return 1;
+ }
+ }
+
+ if (call_dict) {
+ if (PyDict_Check(call_dict)) {
+ Py_DECREF(self->call_dict);
+ self->call_dict = call_dict;
+ Py_INCREF(self->call_dict);
+ }else if (call_dict == Py_None) {
+ Py_DECREF(self->call_dict);
+ self->call_dict = PyDict_New();
+ }else{
+ PyErr_SetString(PyExc_TypeError, "Call dictionary must be dict type");
+ return 1;
+ }
+ }
+
+ int r;
+ if (path) {
+ r = sd_journal_open_directory(&self->j, path, 0);
+ }else{
+ Py_BEGIN_ALLOW_THREADS
+ r = sd_journal_open(&self->j, flags);
+ Py_END_ALLOW_THREADS
+ }
+ if (r == -EINVAL) {
+ PyErr_SetString(PyExc_ValueError, "Invalid flags or path");
+ return -1;
+ }else if (r == -ENOMEM) {
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ return 1;
+ }else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error opening journal");
+ return 1;
+ }
This part should look more like this:
int r;
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
if (r < 0) {
errno = -r;
PyObject *type = r == -EINVAL ? PyExc_ValueError :
r == -ENOMEM ? PyExc_MemoryError :
PyExc_OSError;
PyErr_SetFromErrnoWithFilename(type, path);
return -1;
}
+ return 0;
+}
+
+static PyObject *
+Journal___process_field(Journal *self, PyObject *key, const void *value, ssize_t value_len)
+{
+ PyObject *callable=NULL, *return_value=NULL;
+ if (PyDict_Check(self->call_dict))
+ callable = PyDict_GetItem(self->call_dict, key);
+
+ if (PyCallable_Check(callable)) {
+#if PY_MAJOR_VERSION >=3
+ return_value = PyObject_CallFunction(callable, "y#", value, value_len);
+#else
+ return_value = PyObject_CallFunction(callable, "s#", value, value_len);
+#endif
+ if (!return_value)
+ PyErr_Clear();
+ }
+ if (!return_value && PyCallable_Check(self->default_call))
+#if PY_MAJOR_VERSION >=3
+ return_value = PyObject_CallFunction(self->default_call, "y#", value, value_len);
+#else
+ return_value = PyObject_CallFunction(self->default_call, "s#", value, value_len);
+#endif
+ if (!return_value) {
+ PyErr_Clear();
+#if PY_MAJOR_VERSION >=3
+ return_value = PyBytes_FromStringAndSize(value, value_len);
+#else
+ return_value = PyString_FromStringAndSize(value, value_len);
+#endif
+ }
+ if (!return_value) {
+ return_value = Py_None;
+ }
+ return return_value;
+}
This function should move to the Python layer?
+PyDoc_STRVAR(Journal_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 *
+Journal_get_next(Journal *self, PyObject *args)
+{
+ int64_t skip=1LL;
+ if (! PyArg_ParseTuple(args, "|L", &skip))
+ return NULL;
+
+ int r;
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
+ PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
"skip must be non-zero"
Py_BEGIN_END_THREADS
+ return NULL;
+ }
+
+ if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error getting next message");
errno = -r;
return PyErr_SetFromErrno(PyExc_OSError);
+ }else if ( r == 0) { //EOF
+ return PyDict_New();
+ }
+
+ PyObject *dict;
+ dict = PyDict_New();
+
+ const void *msg;
+ size_t msg_len;
+ const char *delim_ptr;
+ PyObject *key, *value, *cur_value, *tmp_list;
+
+ SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
+ delim_ptr = memchr(msg, '=', msg_len);
+#if PY_MAJOR_VERSION >=3
+ key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
+#else
+ key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
+#endif
+ value = Journal___process_field(self, key, delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
This should go into the Python layer.
I think we shouldn't do any conversions here.
+ if (PyDict_Contains(dict, key)) {
+ cur_value = PyDict_GetItem(dict, key);
+ if (PyList_CheckExact(cur_value) && PyList_Size(cur_value) > 1) {
+ PyList_Append(cur_value, value);
+ }else{
+ tmp_list = PyList_New(0);
+ PyList_Append(tmp_list, cur_value);
+ PyList_Append(tmp_list, value);
+ PyDict_SetItem(dict, key, tmp_list);
+ Py_DECREF(tmp_list);
+ }
+ }else{
+ PyDict_SetItem(dict, key, value);
+ }
+ Py_DECREF(key);
+ Py_DECREF(value);
+ }
+
+ uint64_t realtime;
+ if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
+ char realtime_str[20];
+ sprintf(realtime_str, "%llu", (long long unsigned) realtime);
+
+#if PY_MAJOR_VERSION >=3
+ key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
+#else
+ key = PyString_FromString("__REALTIME_TIMESTAMP");
+#endif
+ value = Journal___process_field(self, key, realtime_str, strlen(realtime_str));
+ PyDict_SetItem(dict, key, value);
+ Py_DECREF(key);
+ Py_DECREF(value);
+ }
+
+ sd_id128_t sd_id;
+ uint64_t monotonic;
+ if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
+ char monotonic_str[20];
+ sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
+#if PY_MAJOR_VERSION >=3
+ key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
+#else
+ key = PyString_FromString("__MONOTONIC_TIMESTAMP");
+#endif
+ value = Journal___process_field(self, key, monotonic_str, strlen(monotonic_str));
+
+ PyDict_SetItem(dict, key, value);
+ Py_DECREF(key);
+ Py_DECREF(value);
+ }
+
+ char *cursor;
char _cleanup_free_ *cursor = NULL;
+ if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
+#if PY_MAJOR_VERSION >=3
+ key = PyUnicode_FromString("__CURSOR");
+#else
+ key = PyString_FromString("__CURSOR");
+#endif
+ value = Journal___process_field(self, key, cursor, strlen(cursor));
+ PyDict_SetItem(dict, key, value);
+ free(cursor);
(no need to free explicitly)
+ Py_DECREF(key);
+ Py_DECREF(value);
+ }
+
+ return dict;
+}
+
+PyDoc_STRVAR(Journal_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 *
+Journal_get_previous(Journal *self, PyObject *args)
+{
+ int64_t skip=1LL;
+ if (! PyArg_ParseTuple(args, "|L", &skip))
+ return NULL;
+
+ PyObject *dict, *arg;
+ arg = Py_BuildValue("(L)", -skip);
+ dict = Journal_get_next(self, arg);
+ Py_DECREF(arg);
+ return dict;
+}
+
+PyDoc_STRVAR(Journal_add_match__doc__,
+"add_match(match, ..., field=value, ...) -> None\n\n"
+"Add a match to filter journal log entries. All matches of different\n"
+"field are combined in logical AND, and matches of the same field\n"
+"are automatically combined in logical OR.\n"
+"Matches can be passed as strings \"field=value\", or keyword\n"
+"arguments field=\"value\".");
+static PyObject *
+Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
+{
+ Py_ssize_t arg_match_len;
+ char *arg_match;
+ int i, r;
+ for (i = 0; i < PySequence_Size(args); i++) {
+#if PY_MAJOR_VERSION >=3
+ PyObject *arg;
+ arg = PySequence_Fast_GET_ITEM(args, i);
+ if (PyUnicode_Check(arg)) {
+#if PY_MINOR_VERSION >=3
+ arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
+#else
+ PyObject *temp;
+ temp = PyUnicode_AsUTF8String(arg);
+ PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
+ Py_DECREF(temp);
+#endif
+ }else if (PyBytes_Check(arg)) {
+ PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
+ }else{
+ PyErr_SetString(PyExc_TypeError, "expected bytes or string");
+ }
+#else
+ PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
+#endif
+ if (PyErr_Occurred())
+ return NULL;
+ r = sd_journal_add_match(self->j, arg_match, arg_match_len);
+ if (r == -EINVAL) {
+ PyErr_SetString(PyExc_ValueError, "Invalid match");
+ return NULL;
+ }else if (r == -ENOMEM) {
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ return NULL;
+ }else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error adding match");
+ return NULL;
+ }
Again, this should use PyErr_SetFromErrno.
+ }
+
+ if (! keywds)
+ Py_RETURN_NONE;
+
+ PyObject *key, *value;
+ Py_ssize_t pos=0, match_key_len, match_value_len;
+ int match_len;
+ char *match_key, *match_value;
+ void *match;
char *match = NULL;
+ while (PyDict_Next(keywds, &pos, &key, &value)) {
+#if PY_MAJOR_VERSION >=3
+ if (PyUnicode_Check(key)) {
+#if PY_MINOR_VERSION >=3
+ match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
+#else
+ PyObject *temp2;
+ temp2 = PyUnicode_AsUTF8String(key);
+ PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
+ Py_DECREF(temp2);
+#endif
+ }else if (PyBytes_Check(key)) {
+ PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
We need to always check for oom.
+ }else{
+ PyErr_SetString(PyExc_TypeError, "expected bytes or string");
We need to always check for oom.
+ }
+ if (PyUnicode_Check(value)) {
+#if PY_MINOR_VERSION >=3
+ match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
+#else
+ PyObject *temp3;
+ temp3 = PyUnicode_AsUTF8String(value);
+ PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
+ Py_DECREF(temp3);
+#endif
+ }else if (PyBytes_Check(value)) {
+ PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
+ }else{
+ PyErr_SetString(PyExc_TypeError, "expected bytes or string");
+ }
+#else
+ PyString_AsStringAndSize(key, &match_key, &match_key_len);
+ PyString_AsStringAndSize(value, &match_value, &match_value_len);
+#endif
+ if (PyErr_Occurred())
+ return NULL;
+
+ match_len = match_key_len + 1 + match_value_len;
+ match = malloc(match_len);
+ memcpy(match, match_key, match_key_len);
+ memcpy(match + match_key_len, "=", 1);
+ memcpy(match + match_key_len + 1, match_value, match_value_len);
match = strjoin(match_key, "=", match_value);
if (!match)
...
+
+ r = sd_journal_add_match(self->j, match, match_len);
+ free(match);
No need to free explicitly.
+ if (r == -EINVAL) {
+ PyErr_SetString(PyExc_ValueError, "Invalid match");
+ return NULL;
+ }else if (r == -ENOMEM) {
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ return NULL;
+ }else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error adding match");
+ return NULL;
+ }
+ }
Yeah, this error handling could definitly become a function.
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_add_disjunction__doc__,
+"add_disjunction() -> None\n\n"
+"Once called, all matches before and after are combined in logical\n"
+"OR.");
+static PyObject *
+Journal_add_disjunction(Journal *self, PyObject *args)
+{
+ int r;
+ r = sd_journal_add_disjunction(self->j);
+ if (r == -ENOMEM) {
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ return NULL;
+ }else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error adding disjunction");
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_flush_matches__doc__,
+"flush_matches() -> None\n\n"
+"Clears all current match filters.");
+static PyObject *
+Journal_flush_matches(Journal *self, PyObject *args)
+{
+ sd_journal_flush_matches(self->j);
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_seek__doc__,
+"seek(offset[, whence]) -> None\n\n"
+"Seek through journal by `offset` number of entries. 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 *
+Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
+{
+ int64_t offset;
+ int whence=SEEK_SET;
+ static char *kwlist[] = {"offset", "whence", NULL};
+
+ if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
+ &offset, &whence))
+ return NULL;
+
+ PyObject *arg;
+ if (whence == SEEK_SET){
+ int r;
+ Py_BEGIN_ALLOW_THREADS
+ r = sd_journal_seek_head(self->j);
+ Py_END_ALLOW_THREADS
+ if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error seeking to head");
+ return NULL;
+ }
+ if (offset > 0LL) {
+ arg = Py_BuildValue("(L)", offset);
+ Py_DECREF(Journal_get_next(self, arg));
+ Py_DECREF(arg);
+ }
+ }else if (whence == SEEK_CUR){
+ arg = Py_BuildValue("(L)", offset);
+ Py_DECREF(Journal_get_next(self, arg));
+ Py_DECREF(arg);
+ }else if (whence == SEEK_END){
+ int r;
+ Py_BEGIN_ALLOW_THREADS
+ r = sd_journal_seek_tail(self->j);
+ Py_END_ALLOW_THREADS
+ if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail");
+ return NULL;
+ }
+ arg = Py_BuildValue("(L)", -1LL);
+ Py_DECREF(Journal_get_next(self, arg));
+ Py_DECREF(arg);
+ if (offset < 0LL) {
+ arg = Py_BuildValue("(L)", offset);
+ Py_DECREF(Journal_get_next(self, arg));
+ Py_DECREF(arg);
+ }
+ }else{
+ PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_seek_realtime__doc__,
+"seek_realtime(realtime) -> None\n\n"
+"Seek to nearest matching journal entry to `realtime`. Argument\n"
+"`realtime` can be an integer unix timestamp in usecs or a "
+"datetime instance.");
+static PyObject *
+Journal_seek_realtime(Journal *self, PyObject *args)
+{
+ PyObject *arg;
+ if (! PyArg_ParseTuple(args, "O", &arg))
+ return NULL;
+
+ uint64_t timestamp=-1LL;
+ if (PyDateTime_Check(arg)) {
+ PyObject *temp;
+ char *timestamp_str;
+ temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f");
+#if PY_MAJOR_VERSION >=3
+ PyObject *temp2;
+ temp2 = PyUnicode_AsUTF8String(temp);
+ timestamp_str = PyBytes_AsString(temp2);
+ Py_DECREF(temp2);
+#else
+ timestamp_str = PyString_AsString(temp);
+#endif
+ Py_DECREF(temp);
+ timestamp = strtoull(timestamp_str, NULL, 10);
+ }else if (PyLong_Check(arg)) {
+ timestamp = PyLong_AsUnsignedLongLong(arg);
+#if PY_MAJOR_VERSION <3
+ }else if (PyInt_Check(arg)) {
+ timestamp = PyInt_AsUnsignedLongLongMask(arg);
+#endif
+ }
+ if ((int64_t) timestamp < 0LL) {
+ PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance");
+ return NULL;
+ }
+
+ int r;
+ Py_BEGIN_ALLOW_THREADS
+ r = sd_journal_seek_realtime_usec(self->j, timestamp);
+ Py_END_ALLOW_THREADS
+ if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
errno = -r;
PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_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 secs, or a\n"
+"timedelta instance.\n"
+"Argument `bootid` is a string representing which boot the\n"
+"monotonic time is reference to. Defaults to current bootid.");
+static PyObject *
+Journal_seek_monotonic(Journal *self, PyObject *args)
+{
+ PyObject *arg;
+ char *bootid=NULL;
+ if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid))
+ return NULL;
+
+ uint64_t timestamp=-1LL;
+ if PyDelta_Check(arg) {
+ PyObject *temp;
+ temp = PyObject_CallMethod(arg, "total_seconds", NULL);
+ timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6);
+ Py_DECREF(temp);
+ }else if (PyFloat_Check(arg)) {
+ timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6);
+ }else if (PyLong_Check(arg)) {
+ timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6;
+#if PY_MAJOR_VERSION <3
+ }else if (PyInt_Check(arg)) {
+ timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6;
+#endif
+
+ }
+
+ if ((int64_t) timestamp < 0LL) {
+ PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance");
+ return NULL;
+ }
+
+ sd_id128_t sd_id;
+ int r;
+ if (bootid) {
+ r = sd_id128_from_string(bootid, &sd_id);
+ if (r == -EINVAL) {
+ PyErr_SetString(PyExc_ValueError, "Invalid bootid");
+ return NULL;
+ } else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error processing bootid");
+ return NULL;
+ }
+ }else{
+ r = sd_id128_get_boot(&sd_id);
+ if (r == -EIO) {
+ PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
+ return NULL;
+ } else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
+ return NULL;
+ }
+ }
+
+ Py_BEGIN_ALLOW_THREADS
+ r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
+ Py_END_ALLOW_THREADS
+ if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_wait__doc__,
+"wait([timeout]) -> Change state (integer)\n\n"
+"Waits until there is a change in the journal. Argument `timeout`\n"
+"is the maximum number of seconds to wait before returning\n"
+"regardless if journal has changed. If `timeout` is not given or is\n"
+"0, then it will 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 *
+Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
+{
+ int64_t timeout=0LL;
+ if (! PyArg_ParseTuple(args, "|L", &timeout))
+ return NULL;
+
+ int r;
Py_BEGIN_ALLOW_THREADS
if ( timeout == 0LL)
+ r = sd_journal_wait(self->j, (uint64_t) -1);
else
+ r = sd_journal_wait(self->j, timeout * 1E6);
Py_END_ALLOW_THREADS
+#if PY_MAJOR_VERSION >=3
+ return PyLong_FromLong(r);
+#else
+ return PyInt_FromLong(r);
+#endif
+}
+
+PyDoc_STRVAR(Journal_seek_cursor__doc__,
+"seek_cursor(cursor) -> None\n\n"
+"Seeks to journal entry by given unique reference `cursor`.");
+static PyObject *
+Journal_seek_cursor(Journal *self, PyObject *args)
+{
+ const char *cursor;
+ if (! PyArg_ParseTuple(args, "s", &cursor))
+ return NULL;
+
+ int r;
+ Py_BEGIN_ALLOW_THREADS
+ r = sd_journal_seek_cursor(self->j, cursor);
+ Py_END_ALLOW_THREADS
+ if (r == -EINVAL) {
+ PyErr_SetString(PyExc_ValueError, "Invalid cursor");
+ return NULL;
+ }else if (r == -ENOMEM) {
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ return NULL;
+ }else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor");
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+static PyObject *
+Journal_iter(PyObject *self)
+{
+ Py_INCREF(self);
+ return self;
+}
+
+static PyObject *
+Journal_iternext(PyObject *self)
+{
+ Journal *iter = (Journal *)self;
+ PyObject *dict, *arg;
+ Py_ssize_t dict_size;
+
+ arg = Py_BuildValue("()");
+ dict = Journal_get_next(iter, arg);
+ Py_DECREF(arg);
+ dict_size = PyDict_Size(dict);
+ if ((int64_t) dict_size > 0LL) {
+ return dict;
+ }else{
+ Py_DECREF(dict);
+ PyErr_SetNone(PyExc_StopIteration);
+ return NULL;
+ }
+}
+
+#ifdef SD_JOURNAL_FOREACH_UNIQUE
Not necessary, we don't care about old systemd's.
+PyDoc_STRVAR(Journal_query_unique__doc__,
+"query_unique(field) -> a set of values\n\n"
+"Returns a set of unique values in journal for given `field`.\n"
+"Note this does not respect any journal matches.");
+static PyObject *
+Journal_query_unique(Journal *self, PyObject *args)
+{
+ char *query;
+ if (! PyArg_ParseTuple(args, "s", &query))
+ return NULL;
+
+ int r;
+ Py_BEGIN_ALLOW_THREADS
+ r = sd_journal_query_unique(self->j, query);
+ Py_END_ALLOW_THREADS
+ if (r == -EINVAL) {
+ PyErr_SetString(PyExc_ValueError, "Invalid field name");
+ return NULL;
+ } else if (r == -ENOMEM) {
+ PyErr_SetString(PyExc_MemoryError, "Not enough memory");
+ return NULL;
+ } else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error querying journal");
+ return NULL;
+ }
+
+ const void *uniq;
+ size_t uniq_len;
+ const char *delim_ptr;
+ PyObject *value_set, *key, *value;
+ value_set = PySet_New(0);
+
+#if PY_MAJOR_VERSION >=3
+ key = PyUnicode_FromString(query);
+#else
+ key = PyString_FromString(query);
+#endif
+
+ SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
+ delim_ptr = memchr(uniq, '=', uniq_len);
+ value = Journal___process_field(self, key, 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;
+}
+#endif //def SD_JOURNAL_FOREACH_UNIQUE
+
+PyDoc_STRVAR(Journal_log_level__doc__,
+"log_level(level) -> None\n\n"
+"Sets maximum log level by setting matches for PRIORITY.");
+static PyObject *
+Journal_log_level(Journal *self, PyObject *args)
+{
+ int level;
+ if (! PyArg_ParseTuple(args, "i", &level))
+ return NULL;
+
+ if (level < 0 || level > 7) {
+ PyErr_SetString(PyExc_ValueError, "Log level should be 0 <= level <= 7");
+ return NULL;
+ }
+ int i;
+ char level_str[2];
+ PyObject *arg, *keywds;
Those vars can go in the loop.
+ for(i = 0; i <= level; i++) {
+ sprintf(level_str, "%i", i);
+ arg = PyTuple_New(0);
Check for oom.
+ keywds = Py_BuildValue("{s:s}", "PRIORITY", level_str);
Check for oom.
+ Journal_add_match(self, arg, keywds);
Check.
+ Py_DECREF(arg);
+ Py_DECREF(keywds);
+ if (PyErr_Occurred())
+ return NULL;
+ }
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_this_boot__doc__,
+"this_boot() -> None\n\n"
+"Sets match filter for the current _BOOT_ID.");
+
+static PyObject *
+Journal_this_boot(Journal *self, PyObject *args)
+{
+ sd_id128_t sd_id;
+ int r;
+ r = sd_id128_get_boot(&sd_id);
+ if (r == -EIO) {
+ PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
+ return NULL;
+ } else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
+ return NULL;
+ }
Error handling...
+
+ char bootid[33];
+ sd_id128_to_string(sd_id, bootid);
+
+ PyObject *arg, *keywds;
+ arg = PyTuple_New(0);
+ keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid);
+ Journal_add_match(self, arg, keywds);
+ Py_DECREF(arg);
+ Py_DECREF(keywds);
+ if (PyErr_Occurred())
+ return NULL;
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(Journal_this_machine__doc__,
+"this_machine() -> None\n\n"
+"Sets match filter for the current _MACHINE_ID.");
+static PyObject *
+Journal_this_machine(Journal *self, PyObject *args)
+{
+ sd_id128_t sd_id;
+ int r;
+ r = sd_id128_get_machine(&sd_id);
+ if (r == -EIO) {
+ PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
+ return NULL;
+ } else if (r < 0) {
+ PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
+ return NULL;
+ }
+
+ char machineid[33];
+ sd_id128_to_string(sd_id, machineid);
+
+ PyObject *arg, *keywds;
+ arg = PyTuple_New(0);
+ keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid);
+ Journal_add_match(self, arg, keywds);
+ Py_DECREF(arg);
+ Py_DECREF(keywds);
+ if (PyErr_Occurred())
+ return NULL;
+
+ Py_RETURN_NONE;
+}
>From here...
+static PyObject *
+Journal_get_default_call(Journal *self, void *closure)
+{
+ Py_INCREF(self->default_call);
+ return self->default_call;
+}
+
+static int
+Journal_set_default_call(Journal *self, PyObject *value, void *closure)
+{
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError, "Cannot delete default_call");
+ return -1;
+ }
+ if (! PyCallable_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, "default_call must be callable");
+ return -1;
+ }
+ Py_DECREF(self->default_call);
+ Py_INCREF(value);
+ self->default_call = value;
+
+ return 0;
+}
+
+static PyObject *
+Journal_get_call_dict(Journal *self, void *closure)
+{
+ Py_INCREF(self->call_dict);
+ return self->call_dict;
+}
+
+static int
+Journal_set_call_dict(Journal *self, PyObject *value, void *closure)
+{
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError, "Cannot delete call_dict");
+ return -1;
+ }
+ if (! PyDict_Check(value)) {
+ PyErr_SetString(PyExc_TypeError, "call_dict must be dict type");
+ return -1;
+ }
+ Py_DECREF(self->call_dict);
+ Py_INCREF(value);
+ self->call_dict = value;
+
+ return 0;
+}
... to here can be deleted.
+static PyObject *
+Journal_get_data_threshold(Journal *self, void *closure)
+{
+ size_t cvalue;
+ PyObject *value;
+ int r;
+
+ r = sd_journal_get_data_threshold(self->j, &cvalue);
+ if (r < 0){
+ PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold");
+ return NULL;
+ }
+
+#if PY_MAJOR_VERSION >=3
+ value = PyLong_FromSize_t(cvalue);
+#else
+ value = PyInt_FromSize_t(cvalue);
+#endif
+ return value;
+}
+
+static int
+Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
+{
+ if (value == NULL) {
+ PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
+ return -1;
+ }
+#if PY_MAJOR_VERSION >=3
+ if (! PyLong_Check(value)){
+#else
+ if (! PyInt_Check(value)){
+#endif
+ PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
+ return -1;
+ }
+ int r;
+#if PY_MAJOR_VERSION >=3
+ r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
+#else
+ r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
+#endif
+ if (r < 0){
+ PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold");
+ return -1;
+ }
+ return 0;
+}
+
+static PyGetSetDef Journal_getseters[] = {
+ {"data_threshold",
+ (getter)Journal_get_data_threshold,
+ (setter)Journal_set_data_threshold,
+ "data threshold",
+ NULL},
+ {"call_dict",
+ (getter)Journal_get_call_dict,
+ (setter)Journal_set_call_dict,
+ "dictionary of calls for each field",
+ NULL},
+ {"default_call",
+ (getter)Journal_get_default_call,
+ (setter)Journal_set_default_call,
+ "default call for values for fields",
+ NULL},
+ {NULL}
+};
+
+static PyMethodDef Journal_methods[] = {
+ {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
+ Journal_get_next__doc__},
+ {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
+ Journal_get_previous__doc__},
+ {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
+ Journal_add_match__doc__},
+ {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
+ Journal_add_disjunction__doc__},
+ {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
+ Journal_flush_matches__doc__},
+ {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
+ Journal_seek__doc__},
+ {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
+ Journal_seek_realtime__doc__},
+ {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
+ Journal_seek_monotonic__doc__},
+ {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
+ Journal_wait__doc__},
+ {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
+ Journal_seek_cursor__doc__},
+#ifdef SD_JOURNAL_FOREACH_UNIQUE
+ {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
+ Journal_query_unique__doc__},
+#endif
Ifdef can go, we don't care about old systemd's.
+ {"log_level", (PyCFunction)Journal_log_level, METH_VARARGS,
+ Journal_log_level__doc__},
+ {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS,
+ Journal_this_boot__doc__},
+ {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS,
+ Journal_this_machine__doc__},
+ {NULL} /* Sentinel */
+};
+
+static PyTypeObject JournalType = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ "_reader.Journal", /*tp_name*/
+ sizeof(Journal), /*tp_basicsize*/
+ 0, /*tp_itemsize*/
+ (destructor)Journal_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*/
+ Journal__doc__, /* tp_doc */
+ 0, /* tp_traverse */
+ 0, /* tp_clear */
+ 0, /* tp_richcompare */
+ 0, /* tp_weaklistoffset */
+ Journal_iter, /* tp_iter */
+ Journal_iternext, /* tp_iternext */
+ Journal_methods, /* tp_methods */
+ 0, /* tp_members */
+ Journal_getseters, /* tp_getset */
+ 0, /* tp_base */
+ 0, /* tp_dict */
+ 0, /* tp_descr_get */
+ 0, /* tp_descr_set */
+ 0, /* tp_dictoffset */
+ (initproc)Journal_init, /* tp_init */
+ 0, /* tp_alloc */
+ Journal_new, /* tp_new */
+};
+
+#if PY_MAJOR_VERSION >= 3
+static PyModuleDef _reader_module = {
+ PyModuleDef_HEAD_INIT,
+ "_reader",
+ "Module that reads systemd journal similar to journalctl.",
+ -1,
+ NULL, NULL, NULL, NULL, NULL
+};
+#endif
+
+PyMODINIT_FUNC
+#if PY_MAJOR_VERSION >= 3
+PyInit__reader(void)
+#else
+init_reader(void)
+#endif
+{
+ PyObject* m;
+
+ PyDateTime_IMPORT;
+
+ if (PyType_Ready(&JournalType) < 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;
+#else
+ m = Py_InitModule3("_reader", NULL,
+ "Module that reads systemd journal similar to journalctl.");
+ if (m == NULL)
+ return;
+#endif
+
+ Py_INCREF(&JournalType);
+ PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
+ 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
+ return m;
+#endif
+}
diff --git src/python-systemd/journal.py src/python-systemd/journal.py
index 516ca1a..1072d92 100644
--- src/python-systemd/journal.py
+++ src/python-systemd/journal.py
@@ -17,12 +17,62 @@
# You should have received a copy of the GNU Lesser General Public License
# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+import datetime
import datetime as _datetime
+import functools
as _functools
+import sys
as _sys
+import uuid
as _uuid
import traceback as _traceback
import os as _os
import logging as _logging
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 (_Journal, NOP, APPEND, INVALIDATE,
+ LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY)
+
+class Journal(_Journal):
+ def __new__(cls, *args, **kwargs):
+ self = _Journal.__new__(cls, *args, **kwargs)
+ if sys.version_info[0] >= 3:
+ self.default_call = functools.partial(str, encoding='utf-8')
+ else:
+ self.default_call = functools.partial(unicode, encoding='utf-8')
+ self.call_dict = {
+ 'MESSAGE_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': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),
+ '__REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),
+ '_SOURCE_MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)),
+ '__MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)),
+ 'COREDUMP_PID': int,
+ 'COREDUMP_UID': int,
+ 'COREDUMP_GID': int,
+ 'COREDUMP_SESSION': int,
+ 'COREDUMP_SIGNAL': int,
+ 'COREDUMP_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),
+ }
+ if sys.version_info[0] >= 3:
+ self.call_dict['COREDUMP'] = bytes
+ else:
+ self.call_dict['COREDUMP'] = str
+ return self
Zbyszek
More information about the systemd-devel
mailing list