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 "Argument `flags` sets open flags of the journal, which can be one\n"
100 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
101 "journal on local machine only; RUNTIME_ONLY opens only\n"
102 "volatile journal files; and SYSTEM_ONLY opens only\n"
103 "journal files of system services and the kernel.\n"
104 "Argument `path` is the directory of journal files. Note that\n"
105 "currently flags are ignored when `path` is present as they are\n"
107 static int Reader_init(Reader *self, PyObject *args, PyObject *keywds)
109 int flags = SD_JOURNAL_LOCAL_ONLY, r;
112 static const char* const kwlist[] = {"flags", "path", NULL};
113 if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iz", (char**) kwlist,
117 Py_BEGIN_ALLOW_THREADS
119 r = sd_journal_open_directory(&self->j, path, 0);
121 r = sd_journal_open(&self->j, flags);
124 return set_error(r, path, "Invalid flags or path");
127 PyDoc_STRVAR(Reader_get_next__doc__,
128 "get_next([skip]) -> dict\n\n"
129 "Return dictionary of the next log entry. Optional skip value will\n"
130 "return the `skip`\\-th log entry.");
131 static PyObject* Reader_get_next(Reader *self, PyObject *args)
139 if (!PyArg_ParseTuple(args, "|L", &skip))
143 PyErr_SetString(PyExc_ValueError, "skip must be nonzero");
147 Py_BEGIN_ALLOW_THREADS
149 r = sd_journal_next(self->j);
150 else if (skip == -1LL)
151 r = sd_journal_previous(self->j);
153 r = sd_journal_next_skip(self->j, skip);
154 else if (skip < -1LL)
155 r = sd_journal_previous_skip(self->j, -skip);
157 assert_not_reached("should not be here");
160 set_error(r, NULL, NULL);
163 else if (r == 0) /* EOF */
170 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
171 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
172 const char *delim_ptr;
174 delim_ptr = memchr(msg, '=', msg_len);
176 PyErr_SetString(PyExc_OSError,
177 "journal gave us a field without '='");
181 key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
185 value = PyBytes_FromStringAndSize(
187 (const char*) msg + msg_len - (delim_ptr + 1) );
191 if (PyDict_Contains(dict, key)) {
192 PyObject *cur_value = PyDict_GetItem(dict, key);
194 if (PyList_CheckExact(cur_value)) {
195 r = PyList_Append(cur_value, value);
199 PyObject _cleanup_Py_DECREF_ *tmp_list = PyList_New(0);
203 r = PyList_Append(tmp_list, cur_value);
207 r = PyList_Append(tmp_list, value);
211 r = PyDict_SetItem(dict, key, tmp_list);
216 r = PyDict_SetItem(dict, key, value);
223 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
226 r = sd_journal_get_realtime_usec(self->j, &realtime);
227 if (set_error(r, NULL, NULL))
230 key = unicode_FromString("__REALTIME_TIMESTAMP");
234 assert_cc(sizeof(unsigned long long) == sizeof(realtime));
235 value = PyLong_FromUnsignedLongLong(realtime);
239 if (PyDict_SetItem(dict, key, value))
244 PyObject _cleanup_Py_DECREF_
245 *key = NULL, *timestamp = NULL, *bytes = NULL, *value = NULL;
249 r = sd_journal_get_monotonic_usec(self->j, &monotonic, &id);
250 if (set_error(r, NULL, NULL))
253 assert_cc(sizeof(unsigned long long) == sizeof(monotonic));
254 key = unicode_FromString("__MONOTONIC_TIMESTAMP");
255 timestamp = PyLong_FromUnsignedLongLong(monotonic);
256 bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes));
257 #if PY_MAJOR_VERSION >= 3
258 value = PyStructSequence_New(&MonotonicType);
260 value = PyTuple_New(2);
262 if (!key || !timestamp || !bytes || !value)
265 Py_INCREF(timestamp);
268 #if PY_MAJOR_VERSION >= 3
269 PyStructSequence_SET_ITEM(value, 0, timestamp);
270 PyStructSequence_SET_ITEM(value, 1, bytes);
272 PyTuple_SET_ITEM(value, 0, timestamp);
273 PyTuple_SET_ITEM(value, 1, bytes);
276 if (PyDict_SetItem(dict, key, value))
281 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
282 char _cleanup_free_ *cursor = NULL;
284 r = sd_journal_get_cursor(self->j, &cursor);
285 if (set_error(r, NULL, NULL))
288 key = unicode_FromString("__CURSOR");
292 value = PyBytes_FromString(cursor);
296 if (PyDict_SetItem(dict, key, value))
306 PyDoc_STRVAR(Reader_get_previous__doc__,
307 "get_previous([skip]) -> dict\n\n"
308 "Return dictionary of the previous log entry. Optional skip value\n"
309 "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip).");
310 static PyObject* Reader_get_previous(Reader *self, PyObject *args)
313 if (!PyArg_ParseTuple(args, "|L", &skip))
316 return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
320 PyDoc_STRVAR(Reader_add_match__doc__,
321 "add_match(match) -> None\n\n"
322 "Add a match to filter journal log entries. All matches of different\n"
323 "fields are combined with logical AND, and matches of the same field\n"
324 "are automatically combined with logical OR.\n"
325 "Match is a string of the form \"FIELD=value\".");
326 static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds)
330 if (!PyArg_ParseTuple(args, "s#", &match, &match_len))
333 r = sd_journal_add_match(self->j, match, match_len);
334 set_error(r, NULL, "Invalid match");
341 PyDoc_STRVAR(Reader_add_disjunction__doc__,
342 "add_disjunction() -> None\n\n"
343 "Inserts a logical OR between matches added before and afterwards.");
344 static PyObject* Reader_add_disjunction(Reader *self, PyObject *args)
347 r = sd_journal_add_disjunction(self->j);
348 set_error(r, NULL, NULL);
354 PyDoc_STRVAR(Reader_flush_matches__doc__,
355 "flush_matches() -> None\n\n"
356 "Clear all current match filters.");
357 static PyObject* Reader_flush_matches(Reader *self, PyObject *args)
359 sd_journal_flush_matches(self->j);
363 PyDoc_STRVAR(Reader_seek__doc__,
364 "seek(offset[, whence]) -> None\n\n"
365 "Jump `offset` entries in the journal. Argument\n"
366 "`whence` defines what the offset is relative to:\n"
367 "os.SEEK_SET (default) from first match in journal;\n"
368 "os.SEEK_CUR from current position in journal;\n"
369 "and os.SEEK_END is from last match in journal.");
370 static PyObject* Reader_seek(Reader *self, PyObject *args, PyObject *keywds)
373 int whence = SEEK_SET;
374 PyObject *result = NULL;
376 static const char* const kwlist[] = {"offset", "whence", NULL};
377 if (!PyArg_ParseTupleAndKeywords(args, keywds, "L|i", (char**) kwlist,
384 Py_BEGIN_ALLOW_THREADS
385 r = sd_journal_seek_head(self->j);
387 if (set_error(r, NULL, NULL))
391 result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
392 (char*) "L", offset);
396 result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
397 (char*) "L", offset);
401 Py_BEGIN_ALLOW_THREADS
402 r = sd_journal_seek_tail(self->j);
404 if (set_error(r, NULL, NULL))
407 result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
408 (char*) "L", offset < 0LL ? offset : -1LL);
412 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
416 if (PyErr_Occurred())
421 PyDoc_STRVAR(Reader_seek_realtime__doc__,
422 "seek_realtime(realtime) -> None\n\n"
423 "Seek to nearest matching journal entry to `realtime`. Argument\n"
424 "`realtime` can must be an integer unix timestamp.");
425 static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
431 if (!PyArg_ParseTuple(args, "d", &timedouble))
434 timestamp = (uint64_t) (timedouble * 1.0E6);
435 if ((int64_t) timestamp < 0LL) {
436 PyErr_SetString(PyExc_ValueError, "Time must be a positive integer");
440 Py_BEGIN_ALLOW_THREADS
441 r = sd_journal_seek_realtime_usec(self->j, timestamp);
443 if (set_error(r, NULL, NULL))
448 PyDoc_STRVAR(Reader_seek_monotonic__doc__,
449 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
450 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
451 "`monotonic` is an timestamp from boot in seconds.\n"
452 "Argument `bootid` is a string representing which boot the\n"
453 "monotonic time is reference to. Defaults to current bootid.");
454 static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args)
462 if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
465 timestamp = (uint64_t) (timedouble * 1.0E6);
467 if ((int64_t) timestamp < 0LL) {
468 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
473 r = sd_id128_from_string(bootid, &id);
474 if (set_error(r, NULL, "Invalid bootid"))
477 Py_BEGIN_ALLOW_THREADS
478 r = sd_id128_get_boot(&id);
480 if (set_error(r, NULL, NULL))
484 Py_BEGIN_ALLOW_THREADS
485 r = sd_journal_seek_monotonic_usec(self->j, id, timestamp);
487 if (set_error(r, NULL, NULL))
492 PyDoc_STRVAR(Reader_wait__doc__,
493 "wait([timeout]) -> state change (integer)\n\n"
494 "Wait for a change in the journal. Argument `timeout` specifies\n"
495 "the maximum number of seconds to wait before returning\n"
496 "regardless of wheter the journal has changed. If `timeout` is not given\n"
497 "or is 0, then block forever.\n"
498 "Will return constants: NOP if no change; APPEND if new\n"
499 "entries have been added to the end of the journal; and\n"
500 "INVALIDATE if journal files have been added or removed.");
501 static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds)
504 int64_t timeout = 0LL;
506 if (!PyArg_ParseTuple(args, "|L", &timeout))
509 Py_BEGIN_ALLOW_THREADS
510 r = sd_journal_wait(self->j,
511 timeout == 0 ? (uint64_t) -1 : timeout * 1E6);
513 if (set_error(r, NULL, NULL) < 0)
516 return long_FromLong(r);
519 PyDoc_STRVAR(Reader_seek_cursor__doc__,
520 "seek_cursor(cursor) -> None\n\n"
521 "Seek to journal entry by given unique reference `cursor`.");
522 static PyObject* Reader_seek_cursor(Reader *self, PyObject *args)
527 if (!PyArg_ParseTuple(args, "s", &cursor))
530 Py_BEGIN_ALLOW_THREADS
531 r = sd_journal_seek_cursor(self->j, cursor);
533 if (set_error(r, NULL, "Invalid cursor"))
538 static PyObject* Reader_iter(PyObject *self)
544 static PyObject* Reader_iternext(PyObject *self)
547 Py_ssize_t dict_size;
549 dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
550 if (PyErr_Occurred())
552 dict_size = PyDict_Size(dict);
553 if ((int64_t) dict_size > 0LL) {
557 PyErr_SetNone(PyExc_StopIteration);
562 PyDoc_STRVAR(Reader_query_unique__doc__,
563 "query_unique(field) -> a set of values\n\n"
564 "Return a set of unique values appearing in journal for the\n"
565 "given `field`. Note this does not respect any journal matches.");
566 static PyObject* Reader_query_unique(Reader *self, PyObject *args)
572 PyObject *value_set, *key, *value;
574 if (!PyArg_ParseTuple(args, "s", &query))
577 Py_BEGIN_ALLOW_THREADS
578 r = sd_journal_query_unique(self->j, query);
580 if (set_error(r, NULL, "Invalid field name"))
583 value_set = PySet_New(0);
584 key = unicode_FromString(query);
586 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
587 const char *delim_ptr;
589 delim_ptr = memchr(uniq, '=', uniq_len);
590 value = PyBytes_FromStringAndSize(
592 (const char*) uniq + uniq_len - (delim_ptr + 1));
593 PySet_Add(value_set, value);
600 PyDoc_STRVAR(data_threshold__doc__,
601 "Threshold for field size truncation in bytes.\n\n"
602 "Fields longer than this will be truncated to the threshold size.\n"
603 "Defaults to 64Kb.");
605 static PyObject* Reader_get_data_threshold(Reader *self, void *closure)
610 r = sd_journal_get_data_threshold(self->j, &cvalue);
611 if (set_error(r, NULL, NULL))
614 return long_FromSize_t(cvalue);
617 static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure)
621 PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold");
624 if (!long_Check(value)){
625 PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
628 r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
629 return set_error(r, NULL, NULL);
632 static PyGetSetDef Reader_getseters[] = {
633 {(char*) "data_threshold",
634 (getter) Reader_get_data_threshold,
635 (setter) Reader_set_data_threshold,
636 (char*) data_threshold__doc__,
641 static PyMethodDef Reader_methods[] = {
642 {"get_next", (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
643 {"get_previous", (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__doc__},
644 {"add_match", (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__},
645 {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__},
646 {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
647 {"seek", (PyCFunction) Reader_seek, METH_VARARGS | METH_KEYWORDS, Reader_seek__doc__},
648 {"seek_realtime", (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__},
649 {"seek_monotonic", (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__},
650 {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
651 {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
652 {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
653 {NULL} /* Sentinel */
656 static PyTypeObject ReaderType = {
657 PyVarObject_HEAD_INIT(NULL, 0)
658 "_reader._Reader", /*tp_name*/
659 sizeof(Reader), /*tp_basicsize*/
661 (destructor)Reader_dealloc, /*tp_dealloc*/
668 0, /*tp_as_sequence*/
676 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
677 Reader__doc__, /* tp_doc */
680 0, /* tp_richcompare */
681 0, /* tp_weaklistoffset */
682 Reader_iter, /* tp_iter */
683 Reader_iternext, /* tp_iternext */
684 Reader_methods, /* tp_methods */
686 Reader_getseters, /* tp_getset */
689 0, /* tp_descr_get */
690 0, /* tp_descr_set */
691 0, /* tp_dictoffset */
692 (initproc) Reader_init, /* tp_init */
694 PyType_GenericNew, /* tp_new */
698 "Module that reads the systemd journal similar to journalctl."
700 #if PY_MAJOR_VERSION >= 3
701 static PyModuleDef _reader_module = {
702 PyModuleDef_HEAD_INIT,
706 NULL, NULL, NULL, NULL, NULL
710 #if PY_MAJOR_VERSION >= 3
711 static bool initialized = false;
714 #pragma GCC diagnostic push
715 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
718 #if PY_MAJOR_VERSION >= 3
728 if (PyType_Ready(&ReaderType) < 0)
729 #if PY_MAJOR_VERSION >= 3
735 #if PY_MAJOR_VERSION >= 3
736 m = PyModule_Create(&_reader_module);
741 PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
745 m = Py_InitModule3("_reader", NULL, SUMMARY);
750 Py_INCREF(&ReaderType);
751 #if PY_MAJOR_VERSION >= 3
752 Py_INCREF(&MonotonicType);
754 if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) ||
755 #if PY_MAJOR_VERSION >= 3
756 PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) ||
758 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) ||
759 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) ||
760 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) ||
761 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) ||
762 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) ||
763 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
764 #if PY_MAJOR_VERSION >= 3
770 #if PY_MAJOR_VERSION >= 3
775 #pragma GCC diagnostic pop