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>
27 #if PY_MAJOR_VERSION >=3
28 # define unicode_FromStringAndSize PyUnicode_FromStringAndSize
29 # define unicode_FromString PyUnicode_FromString
30 # define long_FromLong PyLong_FromLong
31 # define long_FromSize_t PyLong_FromSize_t
32 # define long_Check PyLong_Check
33 # define long_AsLong PyLong_AsLong
35 /* Python 3 type naming convention is used */
36 # define unicode_FromStringAndSize PyString_FromStringAndSize
37 # define unicode_FromString PyString_FromString
38 # define long_FromLong PyInt_FromLong
39 # define long_FromSize_t PyInt_FromSize_t
40 # define long_Check PyInt_Check
41 # define long_AsLong PyInt_AsLong
48 static PyTypeObject JournalType;
50 static int set_error(int r, const char* path, const char* invalid_message) {
53 if (r == -EINVAL && invalid_message)
54 PyErr_SetString(PyExc_ValueError, invalid_message);
55 else if (r == -ENOMEM)
56 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
59 PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
65 Journal_dealloc(Journal* self)
67 sd_journal_close(self->j);
68 Py_TYPE(self)->tp_free((PyObject*)self);
71 PyDoc_STRVAR(Journal__doc__,
72 "Journal([flags][,path]) -> ...\n"
73 "Journal instance\n\n"
74 "Returns instance of Journal, which allows filtering and return\n"
75 "of journal entries.\n"
76 "Argument `flags` sets open flags of the journal, which can be one\n"
77 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
78 "journal on local machine only; RUNTIME_ONLY opens only\n"
79 "volatile journal files; and SYSTEM_ONLY opens only\n"
80 "journal files of system services and the kernel.\n"
81 "Argument `path` is the directory of journal files. Note that\n"
82 "currently flags are ignored when `path` is present as they are\n"
85 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
87 int flags=SD_JOURNAL_LOCAL_ONLY;
90 static char *kwlist[] = {"flags", "path", NULL};
91 if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iz", kwlist,
96 Py_BEGIN_ALLOW_THREADS
98 r = sd_journal_open_directory(&self->j, path, 0);
100 r = sd_journal_open(&self->j, flags);
104 return set_error(r, path, "Invalid flags or path");
107 PyDoc_STRVAR(Journal_get_next__doc__,
108 "get_next([skip]) -> dict\n\n"
109 "Return dictionary of the next log entry. Optional skip value will\n"
110 "return the `skip`th log entry.");
112 Journal_get_next(Journal *self, PyObject *args)
115 if (! PyArg_ParseTuple(args, "|L", &skip))
119 PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
124 Py_BEGIN_ALLOW_THREADS
126 r = sd_journal_next(self->j);
127 }else if (skip == -1LL) {
128 r = sd_journal_previous(self->j);
129 }else if (skip > 1LL) {
130 r = sd_journal_next_skip(self->j, skip);
131 }else if (skip < -1LL) {
132 r = sd_journal_previous_skip(self->j, -skip);
136 set_error(r, NULL, NULL);
139 else if (r == 0) /* EOF */
147 const char *delim_ptr;
148 PyObject *key, *value, *cur_value, *tmp_list;
150 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
151 delim_ptr = memchr(msg, '=', msg_len);
152 key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
153 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
154 if (PyDict_Contains(dict, key)) {
155 cur_value = PyDict_GetItem(dict, key);
156 if (PyList_CheckExact(cur_value)) {
157 PyList_Append(cur_value, value);
159 tmp_list = PyList_New(0);
160 PyList_Append(tmp_list, cur_value);
161 PyList_Append(tmp_list, value);
162 PyDict_SetItem(dict, key, tmp_list);
166 PyDict_SetItem(dict, key, value);
173 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
174 char realtime_str[20];
175 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
176 key = unicode_FromString("__REALTIME_TIMESTAMP");
177 value = PyBytes_FromString(realtime_str);
178 PyDict_SetItem(dict, key, value);
185 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
186 char monotonic_str[20];
187 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
188 key = unicode_FromString("__MONOTONIC_TIMESTAMP");
189 value = PyBytes_FromString(monotonic_str);
191 PyDict_SetItem(dict, key, value);
197 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
198 key = unicode_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 return long_FromLong(r);
424 PyDoc_STRVAR(Journal_seek_cursor__doc__,
425 "seek_cursor(cursor) -> None\n\n"
426 "Seeks to journal entry by given unique reference `cursor`.");
428 Journal_seek_cursor(Journal *self, PyObject *args)
431 if (! PyArg_ParseTuple(args, "s", &cursor))
435 Py_BEGIN_ALLOW_THREADS
436 r = sd_journal_seek_cursor(self->j, cursor);
438 if (set_error(r, NULL, "Invalid cursor"))
444 Journal_iter(PyObject *self)
451 Journal_iternext(PyObject *self)
454 Py_ssize_t dict_size;
456 dict = PyObject_CallMethod(self, "get_next", "");
457 if (PyErr_Occurred())
459 dict_size = PyDict_Size(dict);
460 if ((int64_t) dict_size > 0LL) {
464 PyErr_SetNone(PyExc_StopIteration);
469 PyDoc_STRVAR(Journal_query_unique__doc__,
470 "query_unique(field) -> a set of values\n\n"
471 "Returns a set of unique values in journal for given `field`.\n"
472 "Note this does not respect any journal matches.");
474 Journal_query_unique(Journal *self, PyObject *args)
477 if (! PyArg_ParseTuple(args, "s", &query))
481 Py_BEGIN_ALLOW_THREADS
482 r = sd_journal_query_unique(self->j, query);
484 if (set_error(r, NULL, "Invalid field name"))
489 const char *delim_ptr;
490 PyObject *value_set, *key, *value;
491 value_set = PySet_New(0);
492 key = unicode_FromString(query);
494 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
495 delim_ptr = memchr(uniq, '=', uniq_len);
496 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
497 PySet_Add(value_set, value);
505 Journal_get_data_threshold(Journal *self, void *closure)
511 r = sd_journal_get_data_threshold(self->j, &cvalue);
512 if (set_error(r, NULL, NULL))
515 return long_FromSize_t(cvalue);
519 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
522 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
525 if (!long_Check(value)){
526 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
530 r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
531 return set_error(r, NULL, NULL);
534 static PyGetSetDef Journal_getseters[] = {
536 (getter)Journal_get_data_threshold,
537 (setter)Journal_set_data_threshold,
543 static PyMethodDef Journal_methods[] = {
544 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
545 Journal_get_next__doc__},
546 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
547 Journal_get_previous__doc__},
548 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
549 Journal_add_match__doc__},
550 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
551 Journal_add_disjunction__doc__},
552 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
553 Journal_flush_matches__doc__},
554 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
555 Journal_seek__doc__},
556 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
557 Journal_seek_realtime__doc__},
558 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
559 Journal_seek_monotonic__doc__},
560 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
561 Journal_wait__doc__},
562 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
563 Journal_seek_cursor__doc__},
564 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
565 Journal_query_unique__doc__},
566 {NULL} /* Sentinel */
569 static PyTypeObject JournalType = {
570 PyVarObject_HEAD_INIT(NULL, 0)
571 "_reader._Journal", /*tp_name*/
572 sizeof(Journal), /*tp_basicsize*/
574 (destructor)Journal_dealloc, /*tp_dealloc*/
581 0, /*tp_as_sequence*/
589 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
590 Journal__doc__, /* tp_doc */
593 0, /* tp_richcompare */
594 0, /* tp_weaklistoffset */
595 Journal_iter, /* tp_iter */
596 Journal_iternext, /* tp_iternext */
597 Journal_methods, /* tp_methods */
599 Journal_getseters, /* tp_getset */
602 0, /* tp_descr_get */
603 0, /* tp_descr_set */
604 0, /* tp_dictoffset */
605 (initproc)Journal_init, /* tp_init */
607 PyType_GenericNew, /* tp_new */
610 #if PY_MAJOR_VERSION >= 3
611 static PyModuleDef _reader_module = {
612 PyModuleDef_HEAD_INIT,
614 "Module that reads systemd journal similar to journalctl.",
616 NULL, NULL, NULL, NULL, NULL
621 #if PY_MAJOR_VERSION >= 3
631 if (PyType_Ready(&JournalType) < 0)
632 #if PY_MAJOR_VERSION >= 3
638 #if PY_MAJOR_VERSION >= 3
639 m = PyModule_Create(&_reader_module);
643 m = Py_InitModule3("_reader", NULL,
644 "Module that reads systemd journal similar to journalctl.");
649 Py_INCREF(&JournalType);
650 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
651 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
652 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
653 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
654 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
655 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
656 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
658 #if PY_MAJOR_VERSION >= 3