1 /*-*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2013 Steven Hiscocks, Zbigniew Jędrzejewski-Szmek
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include <structmember.h>
27 #include <systemd/sd-journal.h>
33 #if PY_MAJOR_VERSION >=3
34 # define unicode_FromStringAndSize PyUnicode_FromStringAndSize
35 # define unicode_FromString PyUnicode_FromString
36 # define long_FromLong PyLong_FromLong
37 # define long_FromSize_t PyLong_FromSize_t
38 # define long_Check PyLong_Check
39 # define long_AsLong PyLong_AsLong
41 /* Python 3 type naming convention is used */
42 # define unicode_FromStringAndSize PyString_FromStringAndSize
43 # define unicode_FromString PyString_FromString
44 # define long_FromLong PyInt_FromLong
45 # define long_FromSize_t PyInt_FromSize_t
46 # define long_Check PyInt_Check
47 # define long_AsLong PyInt_AsLong
54 static PyTypeObject ReaderType;
56 static int set_error(int r, const char* path, const char* invalid_message) {
59 if (r == -EINVAL && invalid_message)
60 PyErr_SetString(PyExc_ValueError, invalid_message);
61 else if (r == -ENOMEM)
62 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
65 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
70 #if PY_MAJOR_VERSION >= 3
71 static PyTypeObject MonotonicType;
73 PyDoc_STRVAR(MonotonicType__doc__,
74 "A tuple of (timestamp, bootid) for holding monotonic timestamps");
76 static PyStructSequence_Field MonotonicType_fields[] = {
77 {(char*) "timestamp", (char*) "Time"},
78 {(char*) "bootid", (char*) "Unique identifier of the boot"},
82 static PyStructSequence_Desc Monotonic_desc = {
83 (char*) "journal.Monotonic",
90 static void Reader_dealloc(Reader* self)
92 sd_journal_close(self->j);
93 Py_TYPE(self)->tp_free((PyObject*)self);
96 PyDoc_STRVAR(Reader__doc__,
97 "Reader([flags | path]) -> ...\n\n"
98 "Reader allows filtering and retrieval of Journal entries.\n"
99 "Note: this is a low-level interface, and probably not what you\n"
100 "want, use systemd.journal.Reader instead.\n\n"
101 "Argument `flags` sets open flags of the journal, which can be one\n"
102 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
103 "journal on local machine only; RUNTIME_ONLY opens only\n"
104 "volatile journal files; and SYSTEM_ONLY opens only\n"
105 "journal files of system services and the kernel.\n\n"
106 "Argument `path` is the directory of journal files. Note that\n"
107 "`flags` and `path` are exclusive.\n");
108 static int Reader_init(Reader *self, PyObject *args, PyObject *keywds)
113 static const char* const kwlist[] = {"flags", "path", NULL};
114 if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iz", (char**) kwlist,
119 flags = SD_JOURNAL_LOCAL_ONLY;
122 PyErr_SetString(PyExc_ValueError, "cannot use both flags and path");
126 Py_BEGIN_ALLOW_THREADS
128 r = sd_journal_open_directory(&self->j, path, 0);
130 r = sd_journal_open(&self->j, flags);
133 return set_error(r, path, "Invalid flags or path");
136 PyDoc_STRVAR(Reader_fileno__doc__,
137 "fileno() -> int\n\n"
138 "Get a file descriptor to poll for changes in the journal.\n"
139 "This method invokes sd_journal_get_fd().\n"
140 "See man:sd_journal_get_fd(3).");
141 static PyObject* Reader_fileno(Reader *self, PyObject *args)
144 r = sd_journal_get_fd(self->j);
145 set_error(r, NULL, NULL);
148 return long_FromLong(r);
151 PyDoc_STRVAR(Reader_reliable_fd__doc__,
152 "reliable_fd() -> bool\n\n"
153 "Returns True iff the journal can be polled reliably.\n"
154 "This method invokes sd_journal_reliable_fd().\n"
155 "See man:sd_journal_reliable_fd(3).");
156 static PyObject* Reader_reliable_fd(Reader *self, PyObject *args)
159 r = sd_journal_reliable_fd(self->j);
160 set_error(r, NULL, NULL);
163 return PyBool_FromLong(r);
166 PyDoc_STRVAR(Reader_close__doc__,
167 "close() -> None\n\n"
168 "Free resources allocated by this Reader object.\n"
169 "This method invokes sd_journal_close().\n"
170 "See man:sd_journal_close(3).");
171 static PyObject* Reader_close(Reader *self, PyObject *args)
173 sd_journal_close(self->j);
178 PyDoc_STRVAR(Reader_get_next__doc__,
179 "get_next([skip]) -> dict\n\n"
180 "Return dictionary of the next log entry. Optional skip value will\n"
181 "return the `skip`\\-th log entry.");
182 static PyObject* Reader_get_next(Reader *self, PyObject *args)
190 if (!PyArg_ParseTuple(args, "|L", &skip))
194 PyErr_SetString(PyExc_ValueError, "skip must be nonzero");
198 Py_BEGIN_ALLOW_THREADS
200 r = sd_journal_next(self->j);
201 else if (skip == -1LL)
202 r = sd_journal_previous(self->j);
204 r = sd_journal_next_skip(self->j, skip);
205 else if (skip < -1LL)
206 r = sd_journal_previous_skip(self->j, -skip);
208 assert_not_reached("should not be here");
211 set_error(r, NULL, NULL);
214 else if (r == 0) /* EOF */
221 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
222 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
223 const char *delim_ptr;
225 delim_ptr = memchr(msg, '=', msg_len);
227 PyErr_SetString(PyExc_OSError,
228 "journal gave us a field without '='");
232 key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
236 value = PyBytes_FromStringAndSize(
238 (const char*) msg + msg_len - (delim_ptr + 1) );
242 if (PyDict_Contains(dict, key)) {
243 PyObject *cur_value = PyDict_GetItem(dict, key);
245 if (PyList_CheckExact(cur_value)) {
246 r = PyList_Append(cur_value, value);
250 PyObject _cleanup_Py_DECREF_ *tmp_list = PyList_New(0);
254 r = PyList_Append(tmp_list, cur_value);
258 r = PyList_Append(tmp_list, value);
262 r = PyDict_SetItem(dict, key, tmp_list);
267 r = PyDict_SetItem(dict, key, value);
274 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
277 r = sd_journal_get_realtime_usec(self->j, &realtime);
278 if (set_error(r, NULL, NULL))
281 key = unicode_FromString("__REALTIME_TIMESTAMP");
285 assert_cc(sizeof(unsigned long long) == sizeof(realtime));
286 value = PyLong_FromUnsignedLongLong(realtime);
290 if (PyDict_SetItem(dict, key, value))
295 PyObject _cleanup_Py_DECREF_
296 *key = NULL, *timestamp = NULL, *bytes = NULL, *value = NULL;
300 r = sd_journal_get_monotonic_usec(self->j, &monotonic, &id);
301 if (set_error(r, NULL, NULL))
304 assert_cc(sizeof(unsigned long long) == sizeof(monotonic));
305 key = unicode_FromString("__MONOTONIC_TIMESTAMP");
306 timestamp = PyLong_FromUnsignedLongLong(monotonic);
307 bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes));
308 #if PY_MAJOR_VERSION >= 3
309 value = PyStructSequence_New(&MonotonicType);
311 value = PyTuple_New(2);
313 if (!key || !timestamp || !bytes || !value)
316 Py_INCREF(timestamp);
319 #if PY_MAJOR_VERSION >= 3
320 PyStructSequence_SET_ITEM(value, 0, timestamp);
321 PyStructSequence_SET_ITEM(value, 1, bytes);
323 PyTuple_SET_ITEM(value, 0, timestamp);
324 PyTuple_SET_ITEM(value, 1, bytes);
327 if (PyDict_SetItem(dict, key, value))
332 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
333 char _cleanup_free_ *cursor = NULL;
335 r = sd_journal_get_cursor(self->j, &cursor);
336 if (set_error(r, NULL, NULL))
339 key = unicode_FromString("__CURSOR");
343 value = PyBytes_FromString(cursor);
347 if (PyDict_SetItem(dict, key, value))
357 PyDoc_STRVAR(Reader_get_previous__doc__,
358 "get_previous([skip]) -> dict\n\n"
359 "Return dictionary of the previous log entry. Optional skip value\n"
360 "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip).");
361 static PyObject* Reader_get_previous(Reader *self, PyObject *args)
364 if (!PyArg_ParseTuple(args, "|L", &skip))
367 return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
371 PyDoc_STRVAR(Reader_add_match__doc__,
372 "add_match(match) -> None\n\n"
373 "Add a match to filter journal log entries. All matches of different\n"
374 "fields are combined with logical AND, and matches of the same field\n"
375 "are automatically combined with logical OR.\n"
376 "Match is a string of the form \"FIELD=value\".");
377 static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds)
381 if (!PyArg_ParseTuple(args, "s#", &match, &match_len))
384 r = sd_journal_add_match(self->j, match, match_len);
385 set_error(r, NULL, "Invalid match");
392 PyDoc_STRVAR(Reader_add_disjunction__doc__,
393 "add_disjunction() -> None\n\n"
394 "Inserts a logical OR between matches added before and afterwards.");
395 static PyObject* Reader_add_disjunction(Reader *self, PyObject *args)
398 r = sd_journal_add_disjunction(self->j);
399 set_error(r, NULL, NULL);
405 PyDoc_STRVAR(Reader_flush_matches__doc__,
406 "flush_matches() -> None\n\n"
407 "Clear all current match filters.");
408 static PyObject* Reader_flush_matches(Reader *self, PyObject *args)
410 sd_journal_flush_matches(self->j);
414 PyDoc_STRVAR(Reader_seek_head__doc__,
415 "seek_head() -> None\n\n"
416 "Jump to the beginning of the journal.\n"
417 "This method invokes sd_journal_seek_head().\n"
418 "See man:sd_journal_seek_head(3).");
419 static PyObject* Reader_seek_head(Reader *self, PyObject *args)
422 Py_BEGIN_ALLOW_THREADS
423 r = sd_journal_seek_head(self->j);
425 if (set_error(r, NULL, NULL))
430 PyDoc_STRVAR(Reader_seek_tail__doc__,
431 "seek_tail() -> None\n\n"
432 "Jump to the end of the journal.\n"
433 "This method invokes sd_journal_seek_tail().\n"
434 "See man:sd_journal_seek_tail(3).");
435 static PyObject* Reader_seek_tail(Reader *self, PyObject *args)
438 Py_BEGIN_ALLOW_THREADS
439 r = sd_journal_seek_tail(self->j);
441 if (set_error(r, NULL, NULL))
446 PyDoc_STRVAR(Reader_seek_realtime__doc__,
447 "seek_realtime(realtime) -> None\n\n"
448 "Seek to nearest matching journal entry to `realtime`. Argument\n"
449 "`realtime` can must be an integer unix timestamp.");
450 static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
456 if (!PyArg_ParseTuple(args, "d", &timedouble))
459 timestamp = (uint64_t) (timedouble * 1.0E6);
460 if ((int64_t) timestamp < 0LL) {
461 PyErr_SetString(PyExc_ValueError, "Time must be a positive integer");
465 Py_BEGIN_ALLOW_THREADS
466 r = sd_journal_seek_realtime_usec(self->j, timestamp);
468 if (set_error(r, NULL, NULL))
473 PyDoc_STRVAR(Reader_seek_monotonic__doc__,
474 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
475 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
476 "`monotonic` is an timestamp from boot in seconds.\n"
477 "Argument `bootid` is a string representing which boot the\n"
478 "monotonic time is reference to. Defaults to current bootid.");
479 static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args)
487 if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
490 timestamp = (uint64_t) (timedouble * 1.0E6);
492 if ((int64_t) timestamp < 0LL) {
493 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
498 r = sd_id128_from_string(bootid, &id);
499 if (set_error(r, NULL, "Invalid bootid"))
502 Py_BEGIN_ALLOW_THREADS
503 r = sd_id128_get_boot(&id);
505 if (set_error(r, NULL, NULL))
509 Py_BEGIN_ALLOW_THREADS
510 r = sd_journal_seek_monotonic_usec(self->j, id, timestamp);
512 if (set_error(r, NULL, NULL))
517 PyDoc_STRVAR(Reader_wait__doc__,
518 "wait([timeout]) -> state change (integer)\n\n"
519 "Wait for a change in the journal. Argument `timeout` specifies\n"
520 "the maximum number of seconds to wait before returning\n"
521 "regardless of wheter the journal has changed. If `timeout` is not given\n"
522 "or is 0, then block forever.\n"
523 "Will return constants: NOP if no change; APPEND if new\n"
524 "entries have been added to the end of the journal; and\n"
525 "INVALIDATE if journal files have been added or removed.");
526 static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds)
529 int64_t timeout = 0LL;
531 if (!PyArg_ParseTuple(args, "|L", &timeout))
534 Py_BEGIN_ALLOW_THREADS
535 r = sd_journal_wait(self->j,
536 timeout == 0 ? (uint64_t) -1 : timeout * 1E6);
538 if (set_error(r, NULL, NULL) < 0)
541 return long_FromLong(r);
544 PyDoc_STRVAR(Reader_seek_cursor__doc__,
545 "seek_cursor(cursor) -> None\n\n"
546 "Seek to journal entry by given unique reference `cursor`.");
547 static PyObject* Reader_seek_cursor(Reader *self, PyObject *args)
552 if (!PyArg_ParseTuple(args, "s", &cursor))
555 Py_BEGIN_ALLOW_THREADS
556 r = sd_journal_seek_cursor(self->j, cursor);
558 if (set_error(r, NULL, "Invalid cursor"))
563 static PyObject* Reader_iter(PyObject *self)
569 static PyObject* Reader_iternext(PyObject *self)
572 Py_ssize_t dict_size;
574 dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
575 if (PyErr_Occurred())
577 dict_size = PyDict_Size(dict);
578 if ((int64_t) dict_size > 0LL) {
582 PyErr_SetNone(PyExc_StopIteration);
587 PyDoc_STRVAR(Reader_query_unique__doc__,
588 "query_unique(field) -> a set of values\n\n"
589 "Return a set of unique values appearing in journal for the\n"
590 "given `field`. Note this does not respect any journal matches.");
591 static PyObject* Reader_query_unique(Reader *self, PyObject *args)
597 PyObject *value_set, *key, *value;
599 if (!PyArg_ParseTuple(args, "s", &query))
602 Py_BEGIN_ALLOW_THREADS
603 r = sd_journal_query_unique(self->j, query);
605 if (set_error(r, NULL, "Invalid field name"))
608 value_set = PySet_New(0);
609 key = unicode_FromString(query);
611 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
612 const char *delim_ptr;
614 delim_ptr = memchr(uniq, '=', uniq_len);
615 value = PyBytes_FromStringAndSize(
617 (const char*) uniq + uniq_len - (delim_ptr + 1));
618 PySet_Add(value_set, value);
625 PyDoc_STRVAR(data_threshold__doc__,
626 "Threshold for field size truncation in bytes.\n\n"
627 "Fields longer than this will be truncated to the threshold size.\n"
628 "Defaults to 64Kb.");
630 static PyObject* Reader_get_data_threshold(Reader *self, void *closure)
635 r = sd_journal_get_data_threshold(self->j, &cvalue);
636 if (set_error(r, NULL, NULL))
639 return long_FromSize_t(cvalue);
642 static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure)
646 PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold");
649 if (!long_Check(value)){
650 PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
653 r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
654 return set_error(r, NULL, NULL);
657 static PyGetSetDef Reader_getseters[] = {
658 {(char*) "data_threshold",
659 (getter) Reader_get_data_threshold,
660 (setter) Reader_set_data_threshold,
661 (char*) data_threshold__doc__,
666 static PyMethodDef Reader_methods[] = {
667 {"fileno", (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__},
668 {"reliable_fd", (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__},
669 {"close", (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__},
670 {"get_next", (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
671 {"get_previous", (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__doc__},
672 {"add_match", (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__},
673 {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__},
674 {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
675 {"seek_head", (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__},
676 {"seek_tail", (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__},
677 {"seek_realtime", (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__},
678 {"seek_monotonic", (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__},
679 {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
680 {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
681 {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
682 {NULL} /* Sentinel */
685 static PyTypeObject ReaderType = {
686 PyVarObject_HEAD_INIT(NULL, 0)
687 "_reader._Reader", /*tp_name*/
688 sizeof(Reader), /*tp_basicsize*/
690 (destructor)Reader_dealloc, /*tp_dealloc*/
697 0, /*tp_as_sequence*/
705 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
706 Reader__doc__, /* tp_doc */
709 0, /* tp_richcompare */
710 0, /* tp_weaklistoffset */
711 Reader_iter, /* tp_iter */
712 Reader_iternext, /* tp_iternext */
713 Reader_methods, /* tp_methods */
715 Reader_getseters, /* tp_getset */
718 0, /* tp_descr_get */
719 0, /* tp_descr_set */
720 0, /* tp_dictoffset */
721 (initproc) Reader_init, /* tp_init */
723 PyType_GenericNew, /* tp_new */
727 "Module that reads the systemd journal similar to journalctl."
729 #if PY_MAJOR_VERSION >= 3
730 static PyModuleDef _reader_module = {
731 PyModuleDef_HEAD_INIT,
735 NULL, NULL, NULL, NULL, NULL
739 #if PY_MAJOR_VERSION >= 3
740 static bool initialized = false;
743 #pragma GCC diagnostic push
744 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
747 #if PY_MAJOR_VERSION >= 3
757 if (PyType_Ready(&ReaderType) < 0)
758 #if PY_MAJOR_VERSION >= 3
764 #if PY_MAJOR_VERSION >= 3
765 m = PyModule_Create(&_reader_module);
770 PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
774 m = Py_InitModule3("_reader", NULL, SUMMARY);
779 Py_INCREF(&ReaderType);
780 #if PY_MAJOR_VERSION >= 3
781 Py_INCREF(&MonotonicType);
783 if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) ||
784 #if PY_MAJOR_VERSION >= 3
785 PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) ||
787 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) ||
788 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) ||
789 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) ||
790 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) ||
791 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) ||
792 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
793 #if PY_MAJOR_VERSION >= 3
799 #if PY_MAJOR_VERSION >= 3
804 #pragma GCC diagnostic pop