1 /*-*- Mode: C; c-basic-offset: 8; 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/>.
21 #include <systemd/sd-journal.h>
24 #include <structmember.h>
31 static PyTypeObject JournalType;
33 static int set_error(int r, const char* path, const char* invalid_message) {
36 if (r == -EINVAL && invalid_message)
37 PyErr_SetString(PyExc_ValueError, invalid_message);
38 else if (r == -ENOMEM)
39 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
42 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
48 Journal_dealloc(Journal* self)
50 sd_journal_close(self->j);
51 Py_TYPE(self)->tp_free((PyObject*)self);
54 PyDoc_STRVAR(Journal__doc__,
55 "Journal([flags][,path]) -> ...\n"
56 "Journal instance\n\n"
57 "Returns instance of Journal, which allows filtering and return\n"
58 "of journal entries.\n"
59 "Argument `flags` sets open flags of the journal, which can be one\n"
60 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
61 "journal on local machine only; RUNTIME_ONLY opens only\n"
62 "volatile journal files; and SYSTEM_ONLY opens only\n"
63 "journal files of system services and the kernel.\n"
64 "Argument `path` is the directory of journal files. Note that\n"
65 "currently flags are ignored when `path` is present as they are\n"
68 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
70 int flags=SD_JOURNAL_LOCAL_ONLY;
73 static char *kwlist[] = {"flags", "path", NULL};
74 if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iz", kwlist,
79 Py_BEGIN_ALLOW_THREADS
81 r = sd_journal_open_directory(&self->j, path, 0);
83 r = sd_journal_open(&self->j, flags);
87 return set_error(r, path, "Invalid flags or path");
90 PyDoc_STRVAR(Journal_get_next__doc__,
91 "get_next([skip]) -> dict\n\n"
92 "Return dictionary of the next log entry. Optional skip value will\n"
93 "return the `skip`th log entry.");
95 Journal_get_next(Journal *self, PyObject *args)
98 if (! PyArg_ParseTuple(args, "|L", &skip))
102 PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
107 Py_BEGIN_ALLOW_THREADS
109 r = sd_journal_next(self->j);
110 }else if (skip == -1LL) {
111 r = sd_journal_previous(self->j);
112 }else if (skip > 1LL) {
113 r = sd_journal_next_skip(self->j, skip);
114 }else if (skip < -1LL) {
115 r = sd_journal_previous_skip(self->j, -skip);
119 set_error(r, NULL, NULL);
122 else if (r == 0) /* EOF */
130 const char *delim_ptr;
131 PyObject *key, *value, *cur_value, *tmp_list;
133 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
134 delim_ptr = memchr(msg, '=', msg_len);
135 #if PY_MAJOR_VERSION >=3
136 key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
138 key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
140 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
141 if (PyDict_Contains(dict, key)) {
142 cur_value = PyDict_GetItem(dict, key);
143 if (PyList_CheckExact(cur_value)) {
144 PyList_Append(cur_value, value);
146 tmp_list = PyList_New(0);
147 PyList_Append(tmp_list, cur_value);
148 PyList_Append(tmp_list, value);
149 PyDict_SetItem(dict, key, tmp_list);
153 PyDict_SetItem(dict, key, value);
160 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
161 char realtime_str[20];
162 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
164 #if PY_MAJOR_VERSION >=3
165 key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
167 key = PyString_FromString("__REALTIME_TIMESTAMP");
169 value = PyBytes_FromString(realtime_str);
170 PyDict_SetItem(dict, key, value);
177 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
178 char monotonic_str[20];
179 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
180 #if PY_MAJOR_VERSION >=3
181 key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
183 key = PyString_FromString("__MONOTONIC_TIMESTAMP");
185 value = PyBytes_FromString(monotonic_str);
187 PyDict_SetItem(dict, key, value);
193 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
194 #if PY_MAJOR_VERSION >=3
195 key = PyUnicode_FromString("__CURSOR");
197 key = PyString_FromString("__CURSOR");
199 value = PyBytes_FromString(cursor);
200 PyDict_SetItem(dict, key, value);
209 PyDoc_STRVAR(Journal_get_previous__doc__,
210 "get_previous([skip]) -> dict\n\n"
211 "Return dictionary of the previous log entry. Optional skip value\n"
212 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
214 Journal_get_previous(Journal *self, PyObject *args)
217 if (! PyArg_ParseTuple(args, "|L", &skip))
220 return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip);
223 PyDoc_STRVAR(Journal_add_match__doc__,
224 "add_match(match) -> None\n\n"
225 "Add a match to filter journal log entries. All matches of different\n"
226 "fields are combined in logical AND, and matches of the same field\n"
227 "are automatically combined in logical OR.\n"
228 "Match is string of form \"field=value\".");
230 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
234 if (! PyArg_ParseTuple(args, "s#", &match, &match_len))
238 r = sd_journal_add_match(self->j, match, match_len);
239 set_error(r, NULL, "Invalid match");
246 PyDoc_STRVAR(Journal_add_disjunction__doc__,
247 "add_disjunction() -> None\n\n"
248 "Once called, all matches before and after are combined in logical\n"
251 Journal_add_disjunction(Journal *self, PyObject *args)
254 r = sd_journal_add_disjunction(self->j);
255 set_error(r, NULL, NULL);
261 PyDoc_STRVAR(Journal_flush_matches__doc__,
262 "flush_matches() -> None\n\n"
263 "Clears all current match filters.");
265 Journal_flush_matches(Journal *self, PyObject *args)
267 sd_journal_flush_matches(self->j);
271 PyDoc_STRVAR(Journal_seek__doc__,
272 "seek(offset[, whence]) -> None\n\n"
273 "Seek through journal by `offset` number of entries. Argument\n"
274 "`whence` defines what the offset is relative to:\n"
275 "os.SEEK_SET (default) from first match in journal;\n"
276 "os.SEEK_CUR from current position in journal;\n"
277 "and os.SEEK_END is from last match in journal.");
279 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
283 static char *kwlist[] = {"offset", "whence", NULL};
285 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
289 PyObject *result=NULL;
290 if (whence == SEEK_SET){
292 Py_BEGIN_ALLOW_THREADS
293 r = sd_journal_seek_head(self->j);
295 if (set_error(r, NULL, NULL))
299 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
301 }else if (whence == SEEK_CUR){
302 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
303 }else if (whence == SEEK_END){
305 Py_BEGIN_ALLOW_THREADS
306 r = sd_journal_seek_tail(self->j);
308 if (set_error(r, NULL, NULL))
312 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
314 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
317 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
321 if (PyErr_Occurred())
326 PyDoc_STRVAR(Journal_seek_realtime__doc__,
327 "seek_realtime(realtime) -> None\n\n"
328 "Seek to nearest matching journal entry to `realtime`. Argument\n"
329 "`realtime` can must be an integer unix timestamp.");
331 Journal_seek_realtime(Journal *self, PyObject *args)
334 if (! PyArg_ParseTuple(args, "d", &timedouble))
338 timestamp = (uint64_t) (timedouble * 1.0E6);
340 if ((int64_t) timestamp < 0LL) {
341 PyErr_SetString(PyExc_ValueError, "Time must be positive integer");
346 Py_BEGIN_ALLOW_THREADS
347 r = sd_journal_seek_realtime_usec(self->j, timestamp);
349 if (set_error(r, NULL, NULL))
354 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
355 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
356 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
357 "`monotonic` is an timestamp from boot in seconds.\n"
358 "Argument `bootid` is a string representing which boot the\n"
359 "monotonic time is reference to. Defaults to current bootid.");
361 Journal_seek_monotonic(Journal *self, PyObject *args)
365 if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
369 timestamp = (uint64_t) (timedouble * 1.0E6);
371 if ((int64_t) timestamp < 0LL) {
372 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
379 r = sd_id128_from_string(bootid, &sd_id);
380 if (set_error(r, NULL, "Invalid bootid"))
383 Py_BEGIN_ALLOW_THREADS
384 r = sd_id128_get_boot(&sd_id);
386 if (set_error(r, NULL, NULL))
390 Py_BEGIN_ALLOW_THREADS
391 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
393 if (set_error(r, NULL, NULL))
398 PyDoc_STRVAR(Journal_wait__doc__,
399 "wait([timeout]) -> Change state (integer)\n\n"
400 "Waits until there is a change in the journal. Argument `timeout`\n"
401 "is the maximum number of seconds to wait before returning\n"
402 "regardless if journal has changed. If `timeout` is not given or is\n"
403 "0, then it will block forever.\n"
404 "Will return constants: NOP if no change; APPEND if new\n"
405 "entries have been added to the end of the journal; and\n"
406 "INVALIDATE if journal files have been added or removed.");
408 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
411 if (! PyArg_ParseTuple(args, "|L", &timeout))
415 Py_BEGIN_ALLOW_THREADS
416 r = sd_journal_wait(self->j, timeout ==0 ? (uint64_t) -1 : timeout * 1E6);
418 if (set_error(r, NULL, NULL))
421 #if PY_MAJOR_VERSION >=3
422 return PyLong_FromLong(r);
424 return PyInt_FromLong(r);
428 PyDoc_STRVAR(Journal_seek_cursor__doc__,
429 "seek_cursor(cursor) -> None\n\n"
430 "Seeks to journal entry by given unique reference `cursor`.");
432 Journal_seek_cursor(Journal *self, PyObject *args)
435 if (! PyArg_ParseTuple(args, "s", &cursor))
439 Py_BEGIN_ALLOW_THREADS
440 r = sd_journal_seek_cursor(self->j, cursor);
442 if (set_error(r, NULL, "Invalid cursor"))
448 Journal_iter(PyObject *self)
455 Journal_iternext(PyObject *self)
458 Py_ssize_t dict_size;
460 dict = PyObject_CallMethod(self, "get_next", "");
461 if (PyErr_Occurred())
463 dict_size = PyDict_Size(dict);
464 if ((int64_t) dict_size > 0LL) {
468 PyErr_SetNone(PyExc_StopIteration);
473 PyDoc_STRVAR(Journal_query_unique__doc__,
474 "query_unique(field) -> a set of values\n\n"
475 "Returns a set of unique values in journal for given `field`.\n"
476 "Note this does not respect any journal matches.");
478 Journal_query_unique(Journal *self, PyObject *args)
481 if (! PyArg_ParseTuple(args, "s", &query))
485 Py_BEGIN_ALLOW_THREADS
486 r = sd_journal_query_unique(self->j, query);
488 if (set_error(r, NULL, "Invalid field name"))
493 const char *delim_ptr;
494 PyObject *value_set, *key, *value;
495 value_set = PySet_New(0);
497 #if PY_MAJOR_VERSION >=3
498 key = PyUnicode_FromString(query);
500 key = PyString_FromString(query);
503 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
504 delim_ptr = memchr(uniq, '=', uniq_len);
505 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
506 PySet_Add(value_set, value);
514 Journal_get_data_threshold(Journal *self, void *closure)
520 r = sd_journal_get_data_threshold(self->j, &cvalue);
521 if (set_error(r, NULL, NULL))
524 #if PY_MAJOR_VERSION >=3
525 value = PyLong_FromSize_t(cvalue);
527 value = PyInt_FromSize_t(cvalue);
533 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
536 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
539 #if PY_MAJOR_VERSION >=3
540 if (! PyLong_Check(value)){
542 if (! PyInt_Check(value)){
544 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
548 #if PY_MAJOR_VERSION >=3
549 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
551 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
553 return set_error(r, NULL, NULL);
556 static PyGetSetDef Journal_getseters[] = {
558 (getter)Journal_get_data_threshold,
559 (setter)Journal_set_data_threshold,
565 static PyMethodDef Journal_methods[] = {
566 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
567 Journal_get_next__doc__},
568 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
569 Journal_get_previous__doc__},
570 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
571 Journal_add_match__doc__},
572 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
573 Journal_add_disjunction__doc__},
574 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
575 Journal_flush_matches__doc__},
576 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
577 Journal_seek__doc__},
578 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
579 Journal_seek_realtime__doc__},
580 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
581 Journal_seek_monotonic__doc__},
582 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
583 Journal_wait__doc__},
584 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
585 Journal_seek_cursor__doc__},
586 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
587 Journal_query_unique__doc__},
588 {NULL} /* Sentinel */
591 static PyTypeObject JournalType = {
592 PyVarObject_HEAD_INIT(NULL, 0)
593 "_reader._Journal", /*tp_name*/
594 sizeof(Journal), /*tp_basicsize*/
596 (destructor)Journal_dealloc, /*tp_dealloc*/
603 0, /*tp_as_sequence*/
611 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
612 Journal__doc__, /* tp_doc */
615 0, /* tp_richcompare */
616 0, /* tp_weaklistoffset */
617 Journal_iter, /* tp_iter */
618 Journal_iternext, /* tp_iternext */
619 Journal_methods, /* tp_methods */
621 Journal_getseters, /* tp_getset */
624 0, /* tp_descr_get */
625 0, /* tp_descr_set */
626 0, /* tp_dictoffset */
627 (initproc)Journal_init, /* tp_init */
629 PyType_GenericNew, /* tp_new */
632 #if PY_MAJOR_VERSION >= 3
633 static PyModuleDef _reader_module = {
634 PyModuleDef_HEAD_INIT,
636 "Module that reads systemd journal similar to journalctl.",
638 NULL, NULL, NULL, NULL, NULL
643 #if PY_MAJOR_VERSION >= 3
653 if (PyType_Ready(&JournalType) < 0)
654 #if PY_MAJOR_VERSION >= 3
660 #if PY_MAJOR_VERSION >= 3
661 m = PyModule_Create(&_reader_module);
665 m = Py_InitModule3("_reader", NULL,
666 "Module that reads systemd journal similar to journalctl.");
671 Py_INCREF(&JournalType);
672 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
673 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
674 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
675 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
676 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
677 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
678 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
680 #if PY_MAJOR_VERSION >= 3