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);
177 PyDoc_STRVAR(Reader_get_next__doc__,
178 "get_next([skip]) -> dict\n\n"
179 "Return dictionary of the next log entry. Optional skip value will\n"
180 "return the `skip`\\-th log entry.");
181 static PyObject* Reader_get_next(Reader *self, PyObject *args)
189 if (!PyArg_ParseTuple(args, "|L", &skip))
193 PyErr_SetString(PyExc_ValueError, "skip must be nonzero");
197 Py_BEGIN_ALLOW_THREADS
199 r = sd_journal_next(self->j);
200 else if (skip == -1LL)
201 r = sd_journal_previous(self->j);
203 r = sd_journal_next_skip(self->j, skip);
204 else if (skip < -1LL)
205 r = sd_journal_previous_skip(self->j, -skip);
207 assert_not_reached("should not be here");
210 set_error(r, NULL, NULL);
213 else if (r == 0) /* EOF */
220 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
221 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
222 const char *delim_ptr;
224 delim_ptr = memchr(msg, '=', msg_len);
226 PyErr_SetString(PyExc_OSError,
227 "journal gave us a field without '='");
231 key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
235 value = PyBytes_FromStringAndSize(
237 (const char*) msg + msg_len - (delim_ptr + 1) );
241 if (PyDict_Contains(dict, key)) {
242 PyObject *cur_value = PyDict_GetItem(dict, key);
244 if (PyList_CheckExact(cur_value)) {
245 r = PyList_Append(cur_value, value);
249 PyObject _cleanup_Py_DECREF_ *tmp_list = PyList_New(0);
253 r = PyList_Append(tmp_list, cur_value);
257 r = PyList_Append(tmp_list, value);
261 r = PyDict_SetItem(dict, key, tmp_list);
266 r = PyDict_SetItem(dict, key, value);
273 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
276 r = sd_journal_get_realtime_usec(self->j, &realtime);
277 if (set_error(r, NULL, NULL))
280 key = unicode_FromString("__REALTIME_TIMESTAMP");
284 assert_cc(sizeof(unsigned long long) == sizeof(realtime));
285 value = PyLong_FromUnsignedLongLong(realtime);
289 if (PyDict_SetItem(dict, key, value))
294 PyObject _cleanup_Py_DECREF_
295 *key = NULL, *timestamp = NULL, *bytes = NULL, *value = NULL;
299 r = sd_journal_get_monotonic_usec(self->j, &monotonic, &id);
300 if (set_error(r, NULL, NULL))
303 assert_cc(sizeof(unsigned long long) == sizeof(monotonic));
304 key = unicode_FromString("__MONOTONIC_TIMESTAMP");
305 timestamp = PyLong_FromUnsignedLongLong(monotonic);
306 bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes));
307 #if PY_MAJOR_VERSION >= 3
308 value = PyStructSequence_New(&MonotonicType);
310 value = PyTuple_New(2);
312 if (!key || !timestamp || !bytes || !value)
315 Py_INCREF(timestamp);
318 #if PY_MAJOR_VERSION >= 3
319 PyStructSequence_SET_ITEM(value, 0, timestamp);
320 PyStructSequence_SET_ITEM(value, 1, bytes);
322 PyTuple_SET_ITEM(value, 0, timestamp);
323 PyTuple_SET_ITEM(value, 1, bytes);
326 if (PyDict_SetItem(dict, key, value))
331 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
332 char _cleanup_free_ *cursor = NULL;
334 r = sd_journal_get_cursor(self->j, &cursor);
335 if (set_error(r, NULL, NULL))
338 key = unicode_FromString("__CURSOR");
342 value = PyBytes_FromString(cursor);
346 if (PyDict_SetItem(dict, key, value))
356 PyDoc_STRVAR(Reader_get_previous__doc__,
357 "get_previous([skip]) -> dict\n\n"
358 "Return dictionary of the previous log entry. Optional skip value\n"
359 "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip).");
360 static PyObject* Reader_get_previous(Reader *self, PyObject *args)
363 if (!PyArg_ParseTuple(args, "|L", &skip))
366 return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
370 PyDoc_STRVAR(Reader_add_match__doc__,
371 "add_match(match) -> None\n\n"
372 "Add a match to filter journal log entries. All matches of different\n"
373 "fields are combined with logical AND, and matches of the same field\n"
374 "are automatically combined with logical OR.\n"
375 "Match is a string of the form \"FIELD=value\".");
376 static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds)
380 if (!PyArg_ParseTuple(args, "s#", &match, &match_len))
383 r = sd_journal_add_match(self->j, match, match_len);
384 set_error(r, NULL, "Invalid match");
391 PyDoc_STRVAR(Reader_add_disjunction__doc__,
392 "add_disjunction() -> None\n\n"
393 "Inserts a logical OR between matches added before and afterwards.");
394 static PyObject* Reader_add_disjunction(Reader *self, PyObject *args)
397 r = sd_journal_add_disjunction(self->j);
398 set_error(r, NULL, NULL);
404 PyDoc_STRVAR(Reader_flush_matches__doc__,
405 "flush_matches() -> None\n\n"
406 "Clear all current match filters.");
407 static PyObject* Reader_flush_matches(Reader *self, PyObject *args)
409 sd_journal_flush_matches(self->j);
413 PyDoc_STRVAR(Reader_seek_head__doc__,
414 "seek_head() -> None\n\n"
415 "Jump to the beginning of the journal.\n"
416 "This method invokes sd_journal_seek_head().\n"
417 "See man:sd_journal_seek_head(3).");
418 static PyObject* Reader_seek_head(Reader *self, PyObject *args)
421 Py_BEGIN_ALLOW_THREADS
422 r = sd_journal_seek_head(self->j);
424 if (set_error(r, NULL, NULL))
429 PyDoc_STRVAR(Reader_seek_tail__doc__,
430 "seek_tail() -> None\n\n"
431 "Jump to the end of the journal.\n"
432 "This method invokes sd_journal_seek_tail().\n"
433 "See man:sd_journal_seek_tail(3).");
434 static PyObject* Reader_seek_tail(Reader *self, PyObject *args)
437 Py_BEGIN_ALLOW_THREADS
438 r = sd_journal_seek_tail(self->j);
440 if (set_error(r, NULL, NULL))
445 PyDoc_STRVAR(Reader_seek_realtime__doc__,
446 "seek_realtime(realtime) -> None\n\n"
447 "Seek to nearest matching journal entry to `realtime`. Argument\n"
448 "`realtime` can must be an integer unix timestamp.");
449 static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
455 if (!PyArg_ParseTuple(args, "d", &timedouble))
458 timestamp = (uint64_t) (timedouble * 1.0E6);
459 if ((int64_t) timestamp < 0LL) {
460 PyErr_SetString(PyExc_ValueError, "Time must be a positive integer");
464 Py_BEGIN_ALLOW_THREADS
465 r = sd_journal_seek_realtime_usec(self->j, timestamp);
467 if (set_error(r, NULL, NULL))
472 PyDoc_STRVAR(Reader_seek_monotonic__doc__,
473 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
474 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
475 "`monotonic` is an timestamp from boot in seconds.\n"
476 "Argument `bootid` is a string representing which boot the\n"
477 "monotonic time is reference to. Defaults to current bootid.");
478 static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args)
486 if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
489 timestamp = (uint64_t) (timedouble * 1.0E6);
491 if ((int64_t) timestamp < 0LL) {
492 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
497 r = sd_id128_from_string(bootid, &id);
498 if (set_error(r, NULL, "Invalid bootid"))
501 Py_BEGIN_ALLOW_THREADS
502 r = sd_id128_get_boot(&id);
504 if (set_error(r, NULL, NULL))
508 Py_BEGIN_ALLOW_THREADS
509 r = sd_journal_seek_monotonic_usec(self->j, id, timestamp);
511 if (set_error(r, NULL, NULL))
516 PyDoc_STRVAR(Reader_wait__doc__,
517 "wait([timeout]) -> state change (integer)\n\n"
518 "Wait for a change in the journal. Argument `timeout` specifies\n"
519 "the maximum number of seconds to wait before returning\n"
520 "regardless of wheter the journal has changed. If `timeout` is not given\n"
521 "or is 0, then block forever.\n"
522 "Will return constants: NOP if no change; APPEND if new\n"
523 "entries have been added to the end of the journal; and\n"
524 "INVALIDATE if journal files have been added or removed.");
525 static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds)
528 int64_t timeout = 0LL;
530 if (!PyArg_ParseTuple(args, "|L", &timeout))
533 Py_BEGIN_ALLOW_THREADS
534 r = sd_journal_wait(self->j,
535 timeout == 0 ? (uint64_t) -1 : timeout * 1E6);
537 if (set_error(r, NULL, NULL) < 0)
540 return long_FromLong(r);
543 PyDoc_STRVAR(Reader_seek_cursor__doc__,
544 "seek_cursor(cursor) -> None\n\n"
545 "Seek to journal entry by given unique reference `cursor`.");
546 static PyObject* Reader_seek_cursor(Reader *self, PyObject *args)
551 if (!PyArg_ParseTuple(args, "s", &cursor))
554 Py_BEGIN_ALLOW_THREADS
555 r = sd_journal_seek_cursor(self->j, cursor);
557 if (set_error(r, NULL, "Invalid cursor"))
562 static PyObject* Reader_iter(PyObject *self)
568 static PyObject* Reader_iternext(PyObject *self)
571 Py_ssize_t dict_size;
573 dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
574 if (PyErr_Occurred())
576 dict_size = PyDict_Size(dict);
577 if ((int64_t) dict_size > 0LL) {
581 PyErr_SetNone(PyExc_StopIteration);
586 PyDoc_STRVAR(Reader_query_unique__doc__,
587 "query_unique(field) -> a set of values\n\n"
588 "Return a set of unique values appearing in journal for the\n"
589 "given `field`. Note this does not respect any journal matches.");
590 static PyObject* Reader_query_unique(Reader *self, PyObject *args)
596 PyObject *value_set, *key, *value;
598 if (!PyArg_ParseTuple(args, "s", &query))
601 Py_BEGIN_ALLOW_THREADS
602 r = sd_journal_query_unique(self->j, query);
604 if (set_error(r, NULL, "Invalid field name"))
607 value_set = PySet_New(0);
608 key = unicode_FromString(query);
610 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
611 const char *delim_ptr;
613 delim_ptr = memchr(uniq, '=', uniq_len);
614 value = PyBytes_FromStringAndSize(
616 (const char*) uniq + uniq_len - (delim_ptr + 1));
617 PySet_Add(value_set, value);
624 PyDoc_STRVAR(data_threshold__doc__,
625 "Threshold for field size truncation in bytes.\n\n"
626 "Fields longer than this will be truncated to the threshold size.\n"
627 "Defaults to 64Kb.");
629 static PyObject* Reader_get_data_threshold(Reader *self, void *closure)
634 r = sd_journal_get_data_threshold(self->j, &cvalue);
635 if (set_error(r, NULL, NULL))
638 return long_FromSize_t(cvalue);
641 static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure)
645 PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold");
648 if (!long_Check(value)){
649 PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
652 r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
653 return set_error(r, NULL, NULL);
656 static PyGetSetDef Reader_getseters[] = {
657 {(char*) "data_threshold",
658 (getter) Reader_get_data_threshold,
659 (setter) Reader_set_data_threshold,
660 (char*) data_threshold__doc__,
665 static PyMethodDef Reader_methods[] = {
666 {"fileno", (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__},
667 {"reliable_fd", (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__},
668 {"close", (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__},
669 {"get_next", (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
670 {"get_previous", (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__doc__},
671 {"add_match", (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__},
672 {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__},
673 {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
674 {"seek_head", (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__},
675 {"seek_tail", (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__},
676 {"seek_realtime", (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__},
677 {"seek_monotonic", (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__},
678 {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
679 {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
680 {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
681 {NULL} /* Sentinel */
684 static PyTypeObject ReaderType = {
685 PyVarObject_HEAD_INIT(NULL, 0)
686 "_reader._Reader", /*tp_name*/
687 sizeof(Reader), /*tp_basicsize*/
689 (destructor)Reader_dealloc, /*tp_dealloc*/
696 0, /*tp_as_sequence*/
704 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
705 Reader__doc__, /* tp_doc */
708 0, /* tp_richcompare */
709 0, /* tp_weaklistoffset */
710 Reader_iter, /* tp_iter */
711 Reader_iternext, /* tp_iternext */
712 Reader_methods, /* tp_methods */
714 Reader_getseters, /* tp_getset */
717 0, /* tp_descr_get */
718 0, /* tp_descr_set */
719 0, /* tp_dictoffset */
720 (initproc) Reader_init, /* tp_init */
722 PyType_GenericNew, /* tp_new */
726 "Module that reads the systemd journal similar to journalctl."
728 #if PY_MAJOR_VERSION >= 3
729 static PyModuleDef _reader_module = {
730 PyModuleDef_HEAD_INIT,
734 NULL, NULL, NULL, NULL, NULL
738 #if PY_MAJOR_VERSION >= 3
739 static bool initialized = false;
742 #pragma GCC diagnostic push
743 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
746 #if PY_MAJOR_VERSION >= 3
756 if (PyType_Ready(&ReaderType) < 0)
757 #if PY_MAJOR_VERSION >= 3
763 #if PY_MAJOR_VERSION >= 3
764 m = PyModule_Create(&_reader_module);
769 PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
773 m = Py_InitModule3("_reader", NULL, SUMMARY);
778 Py_INCREF(&ReaderType);
779 #if PY_MAJOR_VERSION >= 3
780 Py_INCREF(&MonotonicType);
782 if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) ||
783 #if PY_MAJOR_VERSION >= 3
784 PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) ||
786 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) ||
787 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) ||
788 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) ||
789 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) ||
790 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) ||
791 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
792 #if PY_MAJOR_VERSION >= 3
798 #if PY_MAJOR_VERSION >= 3
803 #pragma GCC diagnostic pop