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;
34 Journal_dealloc(Journal* self)
36 sd_journal_close(self->j);
37 Py_TYPE(self)->tp_free((PyObject*)self);
40 PyDoc_STRVAR(Journal__doc__,
41 "Journal([flags][,path]) -> ...\n"
42 "Journal instance\n\n"
43 "Returns instance of Journal, which allows filtering and return\n"
44 "of journal entries.\n"
45 "Argument `flags` sets open flags of the journal, which can be one\n"
46 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
47 "journal on local machine only; RUNTIME_ONLY opens only\n"
48 "volatile journal files; and SYSTEM_ONLY opens only\n"
49 "journal files of system services and the kernel.\n"
50 "Argument `path` is the directory of journal files. Note that\n"
51 "currently flags are ignored when `path` is present as they are\n"
54 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
56 int flags=SD_JOURNAL_LOCAL_ONLY;
59 static char *kwlist[] = {"flags", "path", NULL};
60 if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist,
65 Py_BEGIN_ALLOW_THREADS
67 r = sd_journal_open_directory(&self->j, path, 0);
69 r = sd_journal_open(&self->j, flags);
74 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
75 r == -ENOMEM ? PyExc_MemoryError :
77 PyErr_SetFromErrnoWithFilename(errtype, path);
84 PyDoc_STRVAR(Journal_get_next__doc__,
85 "get_next([skip]) -> dict\n\n"
86 "Return dictionary of the next log entry. Optional skip value will\n"
87 "return the `skip`th log entry.");
89 Journal_get_next(Journal *self, PyObject *args)
92 if (! PyArg_ParseTuple(args, "|L", &skip))
96 PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
101 Py_BEGIN_ALLOW_THREADS
103 r = sd_journal_next(self->j);
104 }else if (skip == -1LL) {
105 r = sd_journal_previous(self->j);
106 }else if (skip > 1LL) {
107 r = sd_journal_next_skip(self->j, skip);
108 }else if (skip < -1LL) {
109 r = sd_journal_previous_skip(self->j, -skip);
115 PyErr_SetFromErrno(PyExc_OSError);
117 }else if ( r == 0) { //EOF
126 const char *delim_ptr;
127 PyObject *key, *value, *cur_value, *tmp_list;
129 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
130 delim_ptr = memchr(msg, '=', msg_len);
131 #if PY_MAJOR_VERSION >=3
132 key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
134 key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
136 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
137 if (PyDict_Contains(dict, key)) {
138 cur_value = PyDict_GetItem(dict, key);
139 if (PyList_CheckExact(cur_value)) {
140 PyList_Append(cur_value, value);
142 tmp_list = PyList_New(0);
143 PyList_Append(tmp_list, cur_value);
144 PyList_Append(tmp_list, value);
145 PyDict_SetItem(dict, key, tmp_list);
149 PyDict_SetItem(dict, key, value);
156 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
157 char realtime_str[20];
158 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
160 #if PY_MAJOR_VERSION >=3
161 key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
163 key = PyString_FromString("__REALTIME_TIMESTAMP");
165 value = PyBytes_FromString(realtime_str);
166 PyDict_SetItem(dict, key, value);
173 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
174 char monotonic_str[20];
175 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
176 #if PY_MAJOR_VERSION >=3
177 key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
179 key = PyString_FromString("__MONOTONIC_TIMESTAMP");
181 value = PyBytes_FromString(monotonic_str);
183 PyDict_SetItem(dict, key, value);
189 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
190 #if PY_MAJOR_VERSION >=3
191 key = PyUnicode_FromString("__CURSOR");
193 key = PyString_FromString("__CURSOR");
195 value = PyBytes_FromString(cursor);
196 PyDict_SetItem(dict, key, value);
205 PyDoc_STRVAR(Journal_get_previous__doc__,
206 "get_previous([skip]) -> dict\n\n"
207 "Return dictionary of the previous log entry. Optional skip value\n"
208 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
210 Journal_get_previous(Journal *self, PyObject *args)
213 if (! PyArg_ParseTuple(args, "|L", &skip))
216 return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip);
219 PyDoc_STRVAR(Journal_add_match__doc__,
220 "add_match(match) -> None\n\n"
221 "Add a match to filter journal log entries. All matches of different\n"
222 "fields are combined in logical AND, and matches of the same field\n"
223 "are automatically combined in logical OR.\n"
224 "Match is string of form \"field=value\".");
226 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
230 if (! PyArg_ParseTuple(args, "s#", &match, &match_len))
234 r = sd_journal_add_match(self->j, match, match_len);
237 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
238 r == -ENOMEM ? PyExc_MemoryError :
240 PyErr_SetFromErrno(errtype);
247 PyDoc_STRVAR(Journal_add_disjunction__doc__,
248 "add_disjunction() -> None\n\n"
249 "Once called, all matches before and after are combined in logical\n"
252 Journal_add_disjunction(Journal *self, PyObject *args)
255 r = sd_journal_add_disjunction(self->j);
258 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
260 PyErr_SetFromErrno(errtype);
266 PyDoc_STRVAR(Journal_flush_matches__doc__,
267 "flush_matches() -> None\n\n"
268 "Clears all current match filters.");
270 Journal_flush_matches(Journal *self, PyObject *args)
272 sd_journal_flush_matches(self->j);
276 PyDoc_STRVAR(Journal_seek__doc__,
277 "seek(offset[, whence]) -> None\n\n"
278 "Seek through journal by `offset` number of entries. Argument\n"
279 "`whence` defines what the offset is relative to:\n"
280 "os.SEEK_SET (default) from first match in journal;\n"
281 "os.SEEK_CUR from current position in journal;\n"
282 "and os.SEEK_END is from last match in journal.");
284 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
288 static char *kwlist[] = {"offset", "whence", NULL};
290 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
294 PyObject *result=NULL;
295 if (whence == SEEK_SET){
297 Py_BEGIN_ALLOW_THREADS
298 r = sd_journal_seek_head(self->j);
302 PyErr_SetFromErrno(PyExc_OSError);
306 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
308 }else if (whence == SEEK_CUR){
309 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
310 }else if (whence == SEEK_END){
312 Py_BEGIN_ALLOW_THREADS
313 r = sd_journal_seek_tail(self->j);
317 PyErr_SetFromErrno(PyExc_OSError);
321 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
323 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
326 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
331 if (PyErr_Occurred())
336 PyDoc_STRVAR(Journal_seek_realtime__doc__,
337 "seek_realtime(realtime) -> None\n\n"
338 "Seek to nearest matching journal entry to `realtime`. Argument\n"
339 "`realtime` can must be an integer unix timestamp.");
341 Journal_seek_realtime(Journal *self, PyObject *args)
344 if (! PyArg_ParseTuple(args, "d", &timedouble))
348 timestamp = (uint64_t) (timedouble * 1.0E6);
350 if ((int64_t) timestamp < 0LL) {
351 PyErr_SetString(PyExc_ValueError, "Time must be positive integer");
356 Py_BEGIN_ALLOW_THREADS
357 r = sd_journal_seek_realtime_usec(self->j, timestamp);
361 PyErr_SetFromErrno(PyExc_OSError);
367 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
368 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
369 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
370 "`monotonic` is an timestamp from boot in seconds.\n"
371 "Argument `bootid` is a string representing which boot the\n"
372 "monotonic time is reference to. Defaults to current bootid.");
374 Journal_seek_monotonic(Journal *self, PyObject *args)
378 if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
382 timestamp = (uint64_t) (timedouble * 1.0E6);
384 if ((int64_t) timestamp < 0LL) {
385 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
392 r = sd_id128_from_string(bootid, &sd_id);
394 PyErr_SetString(PyExc_ValueError, "Invalid bootid");
398 PyErr_SetFromErrno(PyExc_OSError);
402 r = sd_id128_get_boot(&sd_id);
404 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
408 PyErr_SetFromErrno(PyExc_OSError);
413 Py_BEGIN_ALLOW_THREADS
414 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
418 PyErr_SetFromErrno(PyExc_OSError);
424 PyDoc_STRVAR(Journal_wait__doc__,
425 "wait([timeout]) -> Change state (integer)\n\n"
426 "Waits until there is a change in the journal. Argument `timeout`\n"
427 "is the maximum number of seconds to wait before returning\n"
428 "regardless if journal has changed. If `timeout` is not given or is\n"
429 "0, then it will block forever.\n"
430 "Will return constants: NOP if no change; APPEND if new\n"
431 "entries have been added to the end of the journal; and\n"
432 "INVALIDATE if journal files have been added or removed.");
434 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
437 if (! PyArg_ParseTuple(args, "|L", &timeout))
441 Py_BEGIN_ALLOW_THREADS
442 if ( timeout == 0LL) {
443 r = sd_journal_wait(self->j, (uint64_t) -1);
445 r = sd_journal_wait(self->j, timeout * 1E6);
450 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
452 PyErr_SetFromErrno(errtype);
455 #if PY_MAJOR_VERSION >=3
456 return PyLong_FromLong(r);
458 return PyInt_FromLong(r);
462 PyDoc_STRVAR(Journal_seek_cursor__doc__,
463 "seek_cursor(cursor) -> None\n\n"
464 "Seeks to journal entry by given unique reference `cursor`.");
466 Journal_seek_cursor(Journal *self, PyObject *args)
469 if (! PyArg_ParseTuple(args, "s", &cursor))
473 Py_BEGIN_ALLOW_THREADS
474 r = sd_journal_seek_cursor(self->j, cursor);
478 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
479 r == -ENOMEM ? PyExc_MemoryError :
481 PyErr_SetFromErrno(errtype);
488 Journal_iter(PyObject *self)
495 Journal_iternext(PyObject *self)
498 Py_ssize_t dict_size;
500 dict = PyObject_CallMethod(self, "get_next", "");
501 dict_size = PyDict_Size(dict);
502 if ((int64_t) dict_size > 0LL) {
506 PyErr_SetNone(PyExc_StopIteration);
511 PyDoc_STRVAR(Journal_query_unique__doc__,
512 "query_unique(field) -> a set of values\n\n"
513 "Returns a set of unique values in journal for given `field`.\n"
514 "Note this does not respect any journal matches.");
516 Journal_query_unique(Journal *self, PyObject *args)
519 if (! PyArg_ParseTuple(args, "s", &query))
523 Py_BEGIN_ALLOW_THREADS
524 r = sd_journal_query_unique(self->j, query);
528 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
529 r == -ENOMEM ? PyExc_MemoryError :
531 PyErr_SetFromErrno(errtype);
537 const char *delim_ptr;
538 PyObject *value_set, *key, *value;
539 value_set = PySet_New(0);
541 #if PY_MAJOR_VERSION >=3
542 key = PyUnicode_FromString(query);
544 key = PyString_FromString(query);
547 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
548 delim_ptr = memchr(uniq, '=', uniq_len);
549 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
550 PySet_Add(value_set, value);
558 Journal_get_data_threshold(Journal *self, void *closure)
564 r = sd_journal_get_data_threshold(self->j, &cvalue);
567 PyErr_SetFromErrno(PyExc_OSError);
571 #if PY_MAJOR_VERSION >=3
572 value = PyLong_FromSize_t(cvalue);
574 value = PyInt_FromSize_t(cvalue);
580 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
583 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
586 #if PY_MAJOR_VERSION >=3
587 if (! PyLong_Check(value)){
589 if (! PyInt_Check(value)){
591 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
595 #if PY_MAJOR_VERSION >=3
596 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
598 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
602 PyErr_SetFromErrno(PyExc_OSError);
608 static PyGetSetDef Journal_getseters[] = {
610 (getter)Journal_get_data_threshold,
611 (setter)Journal_set_data_threshold,
617 static PyMethodDef Journal_methods[] = {
618 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
619 Journal_get_next__doc__},
620 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
621 Journal_get_previous__doc__},
622 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
623 Journal_add_match__doc__},
624 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
625 Journal_add_disjunction__doc__},
626 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
627 Journal_flush_matches__doc__},
628 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
629 Journal_seek__doc__},
630 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
631 Journal_seek_realtime__doc__},
632 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
633 Journal_seek_monotonic__doc__},
634 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
635 Journal_wait__doc__},
636 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
637 Journal_seek_cursor__doc__},
638 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
639 Journal_query_unique__doc__},
640 {NULL} /* Sentinel */
643 static PyTypeObject JournalType = {
644 PyVarObject_HEAD_INIT(NULL, 0)
645 "_reader.Journal", /*tp_name*/
646 sizeof(Journal), /*tp_basicsize*/
648 (destructor)Journal_dealloc, /*tp_dealloc*/
655 0, /*tp_as_sequence*/
663 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
664 Journal__doc__, /* tp_doc */
667 0, /* tp_richcompare */
668 0, /* tp_weaklistoffset */
669 Journal_iter, /* tp_iter */
670 Journal_iternext, /* tp_iternext */
671 Journal_methods, /* tp_methods */
673 Journal_getseters, /* tp_getset */
676 0, /* tp_descr_get */
677 0, /* tp_descr_set */
678 0, /* tp_dictoffset */
679 (initproc)Journal_init, /* tp_init */
681 PyType_GenericNew, /* tp_new */
684 #if PY_MAJOR_VERSION >= 3
685 static PyModuleDef _reader_module = {
686 PyModuleDef_HEAD_INIT,
688 "Module that reads systemd journal similar to journalctl.",
690 NULL, NULL, NULL, NULL, NULL
695 #if PY_MAJOR_VERSION >= 3
705 if (PyType_Ready(&JournalType) < 0)
706 #if PY_MAJOR_VERSION >= 3
712 #if PY_MAJOR_VERSION >= 3
713 m = PyModule_Create(&_reader_module);
717 m = Py_InitModule3("_reader", NULL,
718 "Module that reads systemd journal similar to journalctl.");
723 Py_INCREF(&JournalType);
724 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
725 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
726 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
727 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
728 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
729 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
730 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
732 #if PY_MAJOR_VERSION >= 3