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_fileno__doc__,
128 "fileno() -> int\n\n"
129 "Get a file descriptor to poll for changes in the journal.\n"
130 "This method invokes sd_journal_get_fd().\n"
131 "See man:sd_journal_get_fd(3).");
132 static PyObject* Reader_fileno(Reader *self, PyObject *args)
135 r = sd_journal_get_fd(self->j);
136 set_error(r, NULL, NULL);
139 return long_FromLong(r);
142 PyDoc_STRVAR(Reader_reliable_fd__doc__,
143 "reliable_fd() -> bool\n\n"
144 "Returns True iff the journal can be polled reliably.\n"
145 "This method invokes sd_journal_reliable_fd().\n"
146 "See man:sd_journal_reliable_fd(3).");
147 static PyObject* Reader_reliable_fd(Reader *self, PyObject *args)
150 r = sd_journal_reliable_fd(self->j);
151 set_error(r, NULL, NULL);
154 return PyBool_FromLong(r);
157 PyDoc_STRVAR(Reader_close__doc__,
158 "reliable_fd() -> None\n\n"
159 "Free resources allocated by this Reader object.\n"
160 "This method invokes sd_journal_close().\n"
161 "See man:sd_journal_close(3).");
162 static PyObject* Reader_close(Reader *self, PyObject *args)
164 sd_journal_close(self->j);
168 PyDoc_STRVAR(Reader_get_next__doc__,
169 "get_next([skip]) -> dict\n\n"
170 "Return dictionary of the next log entry. Optional skip value will\n"
171 "return the `skip`\\-th log entry.");
172 static PyObject* Reader_get_next(Reader *self, PyObject *args)
180 if (!PyArg_ParseTuple(args, "|L", &skip))
184 PyErr_SetString(PyExc_ValueError, "skip must be nonzero");
188 Py_BEGIN_ALLOW_THREADS
190 r = sd_journal_next(self->j);
191 else if (skip == -1LL)
192 r = sd_journal_previous(self->j);
194 r = sd_journal_next_skip(self->j, skip);
195 else if (skip < -1LL)
196 r = sd_journal_previous_skip(self->j, -skip);
198 assert_not_reached("should not be here");
201 set_error(r, NULL, NULL);
204 else if (r == 0) /* EOF */
211 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
212 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
213 const char *delim_ptr;
215 delim_ptr = memchr(msg, '=', msg_len);
217 PyErr_SetString(PyExc_OSError,
218 "journal gave us a field without '='");
222 key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
226 value = PyBytes_FromStringAndSize(
228 (const char*) msg + msg_len - (delim_ptr + 1) );
232 if (PyDict_Contains(dict, key)) {
233 PyObject *cur_value = PyDict_GetItem(dict, key);
235 if (PyList_CheckExact(cur_value)) {
236 r = PyList_Append(cur_value, value);
240 PyObject _cleanup_Py_DECREF_ *tmp_list = PyList_New(0);
244 r = PyList_Append(tmp_list, cur_value);
248 r = PyList_Append(tmp_list, value);
252 r = PyDict_SetItem(dict, key, tmp_list);
257 r = PyDict_SetItem(dict, key, value);
264 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
267 r = sd_journal_get_realtime_usec(self->j, &realtime);
268 if (set_error(r, NULL, NULL))
271 key = unicode_FromString("__REALTIME_TIMESTAMP");
275 assert_cc(sizeof(unsigned long long) == sizeof(realtime));
276 value = PyLong_FromUnsignedLongLong(realtime);
280 if (PyDict_SetItem(dict, key, value))
285 PyObject _cleanup_Py_DECREF_
286 *key = NULL, *timestamp = NULL, *bytes = NULL, *value = NULL;
290 r = sd_journal_get_monotonic_usec(self->j, &monotonic, &id);
291 if (set_error(r, NULL, NULL))
294 assert_cc(sizeof(unsigned long long) == sizeof(monotonic));
295 key = unicode_FromString("__MONOTONIC_TIMESTAMP");
296 timestamp = PyLong_FromUnsignedLongLong(monotonic);
297 bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes));
298 #if PY_MAJOR_VERSION >= 3
299 value = PyStructSequence_New(&MonotonicType);
301 value = PyTuple_New(2);
303 if (!key || !timestamp || !bytes || !value)
306 Py_INCREF(timestamp);
309 #if PY_MAJOR_VERSION >= 3
310 PyStructSequence_SET_ITEM(value, 0, timestamp);
311 PyStructSequence_SET_ITEM(value, 1, bytes);
313 PyTuple_SET_ITEM(value, 0, timestamp);
314 PyTuple_SET_ITEM(value, 1, bytes);
317 if (PyDict_SetItem(dict, key, value))
322 PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
323 char _cleanup_free_ *cursor = NULL;
325 r = sd_journal_get_cursor(self->j, &cursor);
326 if (set_error(r, NULL, NULL))
329 key = unicode_FromString("__CURSOR");
333 value = PyBytes_FromString(cursor);
337 if (PyDict_SetItem(dict, key, value))
347 PyDoc_STRVAR(Reader_get_previous__doc__,
348 "get_previous([skip]) -> dict\n\n"
349 "Return dictionary of the previous log entry. Optional skip value\n"
350 "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip).");
351 static PyObject* Reader_get_previous(Reader *self, PyObject *args)
354 if (!PyArg_ParseTuple(args, "|L", &skip))
357 return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
361 PyDoc_STRVAR(Reader_add_match__doc__,
362 "add_match(match) -> None\n\n"
363 "Add a match to filter journal log entries. All matches of different\n"
364 "fields are combined with logical AND, and matches of the same field\n"
365 "are automatically combined with logical OR.\n"
366 "Match is a string of the form \"FIELD=value\".");
367 static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds)
371 if (!PyArg_ParseTuple(args, "s#", &match, &match_len))
374 r = sd_journal_add_match(self->j, match, match_len);
375 set_error(r, NULL, "Invalid match");
382 PyDoc_STRVAR(Reader_add_disjunction__doc__,
383 "add_disjunction() -> None\n\n"
384 "Inserts a logical OR between matches added before and afterwards.");
385 static PyObject* Reader_add_disjunction(Reader *self, PyObject *args)
388 r = sd_journal_add_disjunction(self->j);
389 set_error(r, NULL, NULL);
395 PyDoc_STRVAR(Reader_flush_matches__doc__,
396 "flush_matches() -> None\n\n"
397 "Clear all current match filters.");
398 static PyObject* Reader_flush_matches(Reader *self, PyObject *args)
400 sd_journal_flush_matches(self->j);
404 PyDoc_STRVAR(Reader_seek_head__doc__,
405 "seek_head() -> None\n\n"
406 "Jump to the beginning of the journal.\n"
407 "This method invokes sd_journal_seek_head().\n"
408 "See man:sd_journal_seek_head(3).");
409 static PyObject* Reader_seek_head(Reader *self, PyObject *args)
412 Py_BEGIN_ALLOW_THREADS
413 r = sd_journal_seek_head(self->j);
415 if (set_error(r, NULL, NULL))
420 PyDoc_STRVAR(Reader_seek_tail__doc__,
421 "seek_tail() -> None\n\n"
422 "Jump to the beginning of the journal.\n"
423 "This method invokes sd_journal_seek_tail().\n"
424 "See man:sd_journal_seek_tail(3).");
425 static PyObject* Reader_seek_tail(Reader *self, PyObject *args)
428 Py_BEGIN_ALLOW_THREADS
429 r = sd_journal_seek_tail(self->j);
431 if (set_error(r, NULL, NULL))
436 PyDoc_STRVAR(Reader_seek_realtime__doc__,
437 "seek_realtime(realtime) -> None\n\n"
438 "Seek to nearest matching journal entry to `realtime`. Argument\n"
439 "`realtime` can must be an integer unix timestamp.");
440 static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
446 if (!PyArg_ParseTuple(args, "d", &timedouble))
449 timestamp = (uint64_t) (timedouble * 1.0E6);
450 if ((int64_t) timestamp < 0LL) {
451 PyErr_SetString(PyExc_ValueError, "Time must be a positive integer");
455 Py_BEGIN_ALLOW_THREADS
456 r = sd_journal_seek_realtime_usec(self->j, timestamp);
458 if (set_error(r, NULL, NULL))
463 PyDoc_STRVAR(Reader_seek_monotonic__doc__,
464 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
465 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
466 "`monotonic` is an timestamp from boot in seconds.\n"
467 "Argument `bootid` is a string representing which boot the\n"
468 "monotonic time is reference to. Defaults to current bootid.");
469 static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args)
477 if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
480 timestamp = (uint64_t) (timedouble * 1.0E6);
482 if ((int64_t) timestamp < 0LL) {
483 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
488 r = sd_id128_from_string(bootid, &id);
489 if (set_error(r, NULL, "Invalid bootid"))
492 Py_BEGIN_ALLOW_THREADS
493 r = sd_id128_get_boot(&id);
495 if (set_error(r, NULL, NULL))
499 Py_BEGIN_ALLOW_THREADS
500 r = sd_journal_seek_monotonic_usec(self->j, id, timestamp);
502 if (set_error(r, NULL, NULL))
507 PyDoc_STRVAR(Reader_wait__doc__,
508 "wait([timeout]) -> state change (integer)\n\n"
509 "Wait for a change in the journal. Argument `timeout` specifies\n"
510 "the maximum number of seconds to wait before returning\n"
511 "regardless of wheter the journal has changed. If `timeout` is not given\n"
512 "or is 0, then block forever.\n"
513 "Will return constants: NOP if no change; APPEND if new\n"
514 "entries have been added to the end of the journal; and\n"
515 "INVALIDATE if journal files have been added or removed.");
516 static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds)
519 int64_t timeout = 0LL;
521 if (!PyArg_ParseTuple(args, "|L", &timeout))
524 Py_BEGIN_ALLOW_THREADS
525 r = sd_journal_wait(self->j,
526 timeout == 0 ? (uint64_t) -1 : timeout * 1E6);
528 if (set_error(r, NULL, NULL) < 0)
531 return long_FromLong(r);
534 PyDoc_STRVAR(Reader_seek_cursor__doc__,
535 "seek_cursor(cursor) -> None\n\n"
536 "Seek to journal entry by given unique reference `cursor`.");
537 static PyObject* Reader_seek_cursor(Reader *self, PyObject *args)
542 if (!PyArg_ParseTuple(args, "s", &cursor))
545 Py_BEGIN_ALLOW_THREADS
546 r = sd_journal_seek_cursor(self->j, cursor);
548 if (set_error(r, NULL, "Invalid cursor"))
553 static PyObject* Reader_iter(PyObject *self)
559 static PyObject* Reader_iternext(PyObject *self)
562 Py_ssize_t dict_size;
564 dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
565 if (PyErr_Occurred())
567 dict_size = PyDict_Size(dict);
568 if ((int64_t) dict_size > 0LL) {
572 PyErr_SetNone(PyExc_StopIteration);
577 PyDoc_STRVAR(Reader_query_unique__doc__,
578 "query_unique(field) -> a set of values\n\n"
579 "Return a set of unique values appearing in journal for the\n"
580 "given `field`. Note this does not respect any journal matches.");
581 static PyObject* Reader_query_unique(Reader *self, PyObject *args)
587 PyObject *value_set, *key, *value;
589 if (!PyArg_ParseTuple(args, "s", &query))
592 Py_BEGIN_ALLOW_THREADS
593 r = sd_journal_query_unique(self->j, query);
595 if (set_error(r, NULL, "Invalid field name"))
598 value_set = PySet_New(0);
599 key = unicode_FromString(query);
601 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
602 const char *delim_ptr;
604 delim_ptr = memchr(uniq, '=', uniq_len);
605 value = PyBytes_FromStringAndSize(
607 (const char*) uniq + uniq_len - (delim_ptr + 1));
608 PySet_Add(value_set, value);
615 PyDoc_STRVAR(data_threshold__doc__,
616 "Threshold for field size truncation in bytes.\n\n"
617 "Fields longer than this will be truncated to the threshold size.\n"
618 "Defaults to 64Kb.");
620 static PyObject* Reader_get_data_threshold(Reader *self, void *closure)
625 r = sd_journal_get_data_threshold(self->j, &cvalue);
626 if (set_error(r, NULL, NULL))
629 return long_FromSize_t(cvalue);
632 static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure)
636 PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold");
639 if (!long_Check(value)){
640 PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
643 r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
644 return set_error(r, NULL, NULL);
647 static PyGetSetDef Reader_getseters[] = {
648 {(char*) "data_threshold",
649 (getter) Reader_get_data_threshold,
650 (setter) Reader_set_data_threshold,
651 (char*) data_threshold__doc__,
656 static PyMethodDef Reader_methods[] = {
657 {"fileno", (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__},
658 {"reliable_fd", (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__},
659 {"close", (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__},
660 {"get_next", (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
661 {"get_previous", (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__doc__},
662 {"add_match", (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__},
663 {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__},
664 {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
665 {"seek_head", (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__},
666 {"seek_tail", (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__},
667 {"seek_realtime", (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__},
668 {"seek_monotonic", (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__},
669 {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
670 {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
671 {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
672 {NULL} /* Sentinel */
675 static PyTypeObject ReaderType = {
676 PyVarObject_HEAD_INIT(NULL, 0)
677 "_reader._Reader", /*tp_name*/
678 sizeof(Reader), /*tp_basicsize*/
680 (destructor)Reader_dealloc, /*tp_dealloc*/
687 0, /*tp_as_sequence*/
695 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
696 Reader__doc__, /* tp_doc */
699 0, /* tp_richcompare */
700 0, /* tp_weaklistoffset */
701 Reader_iter, /* tp_iter */
702 Reader_iternext, /* tp_iternext */
703 Reader_methods, /* tp_methods */
705 Reader_getseters, /* tp_getset */
708 0, /* tp_descr_get */
709 0, /* tp_descr_set */
710 0, /* tp_dictoffset */
711 (initproc) Reader_init, /* tp_init */
713 PyType_GenericNew, /* tp_new */
717 "Module that reads the systemd journal similar to journalctl."
719 #if PY_MAJOR_VERSION >= 3
720 static PyModuleDef _reader_module = {
721 PyModuleDef_HEAD_INIT,
725 NULL, NULL, NULL, NULL, NULL
729 #if PY_MAJOR_VERSION >= 3
730 static bool initialized = false;
733 #pragma GCC diagnostic push
734 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
737 #if PY_MAJOR_VERSION >= 3
747 if (PyType_Ready(&ReaderType) < 0)
748 #if PY_MAJOR_VERSION >= 3
754 #if PY_MAJOR_VERSION >= 3
755 m = PyModule_Create(&_reader_module);
760 PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
764 m = Py_InitModule3("_reader", NULL, SUMMARY);
769 Py_INCREF(&ReaderType);
770 #if PY_MAJOR_VERSION >= 3
771 Py_INCREF(&MonotonicType);
773 if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) ||
774 #if PY_MAJOR_VERSION >= 3
775 PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) ||
777 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) ||
778 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) ||
779 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) ||
780 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) ||
781 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) ||
782 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
783 #if PY_MAJOR_VERSION >= 3
789 #if PY_MAJOR_VERSION >= 3
794 #pragma GCC diagnostic pop