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, ..., field=value, ...) -> None\n\n"
221 "Add a match to filter journal log entries. All matches of different\n"
222 "field are combined in logical AND, and matches of the same field\n"
223 "are automatically combined in logical OR.\n"
224 "Matches can be passed as strings \"field=value\", or keyword\n"
225 "arguments field=\"value\".");
227 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
229 Py_ssize_t arg_match_len;
232 for (i = 0; i < PySequence_Size(args); i++) {
233 #if PY_MAJOR_VERSION >=3
235 arg = PySequence_Fast_GET_ITEM(args, i);
236 if (PyUnicode_Check(arg)) {
237 #if PY_MINOR_VERSION >=3
238 arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
241 temp = PyUnicode_AsUTF8String(arg);
242 PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
245 }else if (PyBytes_Check(arg)) {
246 PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
248 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
251 PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
253 if (PyErr_Occurred())
255 r = sd_journal_add_match(self->j, arg_match, arg_match_len);
258 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
259 r == -ENOMEM ? PyExc_MemoryError :
261 PyErr_SetFromErrno(errtype);
269 PyObject *key, *value;
270 Py_ssize_t pos=0, match_key_len, match_value_len;
272 char *match_key, *match_value;
274 while (PyDict_Next(keywds, &pos, &key, &value)) {
275 #if PY_MAJOR_VERSION >=3
276 if (PyUnicode_Check(key)) {
277 #if PY_MINOR_VERSION >=3
278 match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
281 temp2 = PyUnicode_AsUTF8String(key);
282 PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
285 }else if (PyBytes_Check(key)) {
286 PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
288 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
290 if (PyUnicode_Check(value)) {
291 #if PY_MINOR_VERSION >=3
292 match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
295 temp3 = PyUnicode_AsUTF8String(value);
296 PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
299 }else if (PyBytes_Check(value)) {
300 PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
302 PyErr_SetString(PyExc_TypeError, "expected bytes or string");
305 PyString_AsStringAndSize(key, &match_key, &match_key_len);
306 PyString_AsStringAndSize(value, &match_value, &match_value_len);
308 if (PyErr_Occurred())
311 match_len = match_key_len + 1 + match_value_len;
312 match = malloc(match_len);
313 memcpy(match, match_key, match_key_len);
314 memcpy(match + match_key_len, "=", 1);
315 memcpy(match + match_key_len + 1, match_value, match_value_len);
317 r = sd_journal_add_match(self->j, match, match_len);
320 PyErr_SetString(PyExc_ValueError, "Invalid match");
322 }else if (r == -ENOMEM) {
323 PyErr_SetString(PyExc_MemoryError, "Not enough memory");
326 PyErr_SetString(PyExc_RuntimeError, "Error adding match");
334 PyDoc_STRVAR(Journal_add_disjunction__doc__,
335 "add_disjunction() -> None\n\n"
336 "Once called, all matches before and after are combined in logical\n"
339 Journal_add_disjunction(Journal *self, PyObject *args)
342 r = sd_journal_add_disjunction(self->j);
345 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
347 PyErr_SetFromErrno(errtype);
353 PyDoc_STRVAR(Journal_flush_matches__doc__,
354 "flush_matches() -> None\n\n"
355 "Clears all current match filters.");
357 Journal_flush_matches(Journal *self, PyObject *args)
359 sd_journal_flush_matches(self->j);
363 PyDoc_STRVAR(Journal_seek__doc__,
364 "seek(offset[, whence]) -> None\n\n"
365 "Seek through journal by `offset` number of entries. 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.");
371 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
375 static char *kwlist[] = {"offset", "whence", NULL};
377 if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
381 PyObject *result=NULL;
382 if (whence == SEEK_SET){
384 Py_BEGIN_ALLOW_THREADS
385 r = sd_journal_seek_head(self->j);
389 PyErr_SetFromErrno(PyExc_OSError);
393 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
395 }else if (whence == SEEK_CUR){
396 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
397 }else if (whence == SEEK_END){
399 Py_BEGIN_ALLOW_THREADS
400 r = sd_journal_seek_tail(self->j);
404 PyErr_SetFromErrno(PyExc_OSError);
408 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
410 result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
413 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
418 if (PyErr_Occurred())
423 PyDoc_STRVAR(Journal_seek_realtime__doc__,
424 "seek_realtime(realtime) -> None\n\n"
425 "Seek to nearest matching journal entry to `realtime`. Argument\n"
426 "`realtime` can be an integer unix timestamp in usecs or a "
427 "datetime instance.");
429 Journal_seek_realtime(Journal *self, PyObject *args)
432 if (! PyArg_ParseTuple(args, "K", ×tamp))
435 if ((int64_t) timestamp < 0LL) {
436 PyErr_SetString(PyExc_ValueError, "Time must be positive integer");
441 Py_BEGIN_ALLOW_THREADS
442 r = sd_journal_seek_realtime_usec(self->j, timestamp);
446 PyErr_SetFromErrno(PyExc_OSError);
452 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
453 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
454 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
455 "`monotonic` is an timestamp from boot in secs, or a\n"
456 "timedelta instance.\n"
457 "Argument `bootid` is a string representing which boot the\n"
458 "monotonic time is reference to. Defaults to current bootid.");
460 Journal_seek_monotonic(Journal *self, PyObject *args)
464 if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
468 timestamp = (uint64_t) (timedouble * 1.0E6);
470 if ((int64_t) timestamp < 0LL) {
471 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
478 r = sd_id128_from_string(bootid, &sd_id);
480 PyErr_SetString(PyExc_ValueError, "Invalid bootid");
484 PyErr_SetFromErrno(PyExc_OSError);
488 r = sd_id128_get_boot(&sd_id);
490 PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
494 PyErr_SetFromErrno(PyExc_OSError);
499 Py_BEGIN_ALLOW_THREADS
500 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
504 PyErr_SetFromErrno(PyExc_OSError);
510 PyDoc_STRVAR(Journal_wait__doc__,
511 "wait([timeout]) -> Change state (integer)\n\n"
512 "Waits until there is a change in the journal. Argument `timeout`\n"
513 "is the maximum number of seconds to wait before returning\n"
514 "regardless if journal has changed. If `timeout` is not given or is\n"
515 "0, then it will block forever.\n"
516 "Will return constants: NOP if no change; APPEND if new\n"
517 "entries have been added to the end of the journal; and\n"
518 "INVALIDATE if journal files have been added or removed.");
520 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
523 if (! PyArg_ParseTuple(args, "|L", &timeout))
527 Py_BEGIN_ALLOW_THREADS
528 if ( timeout == 0LL) {
529 r = sd_journal_wait(self->j, (uint64_t) -1);
531 r = sd_journal_wait(self->j, timeout * 1E6);
536 PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
538 PyErr_SetFromErrno(errtype);
541 #if PY_MAJOR_VERSION >=3
542 return PyLong_FromLong(r);
544 return PyInt_FromLong(r);
548 PyDoc_STRVAR(Journal_seek_cursor__doc__,
549 "seek_cursor(cursor) -> None\n\n"
550 "Seeks to journal entry by given unique reference `cursor`.");
552 Journal_seek_cursor(Journal *self, PyObject *args)
555 if (! PyArg_ParseTuple(args, "s", &cursor))
559 Py_BEGIN_ALLOW_THREADS
560 r = sd_journal_seek_cursor(self->j, cursor);
564 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
565 r == -ENOMEM ? PyExc_MemoryError :
567 PyErr_SetFromErrno(errtype);
574 Journal_iter(PyObject *self)
581 Journal_iternext(PyObject *self)
584 Py_ssize_t dict_size;
586 dict = PyObject_CallMethod(self, "get_next", "");
587 dict_size = PyDict_Size(dict);
588 if ((int64_t) dict_size > 0LL) {
592 PyErr_SetNone(PyExc_StopIteration);
597 #ifdef SD_JOURNAL_FOREACH_UNIQUE
598 PyDoc_STRVAR(Journal_query_unique__doc__,
599 "query_unique(field) -> a set of values\n\n"
600 "Returns a set of unique values in journal for given `field`.\n"
601 "Note this does not respect any journal matches.");
603 Journal_query_unique(Journal *self, PyObject *args)
606 if (! PyArg_ParseTuple(args, "s", &query))
610 Py_BEGIN_ALLOW_THREADS
611 r = sd_journal_query_unique(self->j, query);
615 PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
616 r == -ENOMEM ? PyExc_MemoryError :
618 PyErr_SetFromErrno(errtype);
624 const char *delim_ptr;
625 PyObject *value_set, *key, *value;
626 value_set = PySet_New(0);
628 #if PY_MAJOR_VERSION >=3
629 key = PyUnicode_FromString(query);
631 key = PyString_FromString(query);
634 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
635 delim_ptr = memchr(uniq, '=', uniq_len);
636 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
637 PySet_Add(value_set, value);
643 #endif //def SD_JOURNAL_FOREACH_UNIQUE
646 Journal_get_data_threshold(Journal *self, void *closure)
652 r = sd_journal_get_data_threshold(self->j, &cvalue);
655 PyErr_SetFromErrno(PyExc_OSError);
659 #if PY_MAJOR_VERSION >=3
660 value = PyLong_FromSize_t(cvalue);
662 value = PyInt_FromSize_t(cvalue);
668 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
671 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
674 #if PY_MAJOR_VERSION >=3
675 if (! PyLong_Check(value)){
677 if (! PyInt_Check(value)){
679 PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
683 #if PY_MAJOR_VERSION >=3
684 r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
686 r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
690 PyErr_SetFromErrno(PyExc_OSError);
696 static PyGetSetDef Journal_getseters[] = {
698 (getter)Journal_get_data_threshold,
699 (setter)Journal_set_data_threshold,
705 static PyMethodDef Journal_methods[] = {
706 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
707 Journal_get_next__doc__},
708 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
709 Journal_get_previous__doc__},
710 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
711 Journal_add_match__doc__},
712 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
713 Journal_add_disjunction__doc__},
714 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
715 Journal_flush_matches__doc__},
716 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
717 Journal_seek__doc__},
718 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
719 Journal_seek_realtime__doc__},
720 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
721 Journal_seek_monotonic__doc__},
722 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
723 Journal_wait__doc__},
724 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
725 Journal_seek_cursor__doc__},
726 #ifdef SD_JOURNAL_FOREACH_UNIQUE
727 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
728 Journal_query_unique__doc__},
730 {NULL} /* Sentinel */
733 static PyTypeObject JournalType = {
734 PyVarObject_HEAD_INIT(NULL, 0)
735 "_reader.Journal", /*tp_name*/
736 sizeof(Journal), /*tp_basicsize*/
738 (destructor)Journal_dealloc, /*tp_dealloc*/
745 0, /*tp_as_sequence*/
753 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
754 Journal__doc__, /* tp_doc */
757 0, /* tp_richcompare */
758 0, /* tp_weaklistoffset */
759 Journal_iter, /* tp_iter */
760 Journal_iternext, /* tp_iternext */
761 Journal_methods, /* tp_methods */
763 Journal_getseters, /* tp_getset */
766 0, /* tp_descr_get */
767 0, /* tp_descr_set */
768 0, /* tp_dictoffset */
769 (initproc)Journal_init, /* tp_init */
771 PyType_GenericNew, /* tp_new */
774 #if PY_MAJOR_VERSION >= 3
775 static PyModuleDef _reader_module = {
776 PyModuleDef_HEAD_INIT,
778 "Module that reads systemd journal similar to journalctl.",
780 NULL, NULL, NULL, NULL, NULL
785 #if PY_MAJOR_VERSION >= 3
795 if (PyType_Ready(&JournalType) < 0)
796 #if PY_MAJOR_VERSION >= 3
802 #if PY_MAJOR_VERSION >= 3
803 m = PyModule_Create(&_reader_module);
807 m = Py_InitModule3("_reader", NULL,
808 "Module that reads systemd journal similar to journalctl.");
813 Py_INCREF(&JournalType);
814 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
815 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
816 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
817 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
818 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
819 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
820 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
822 #if PY_MAJOR_VERSION >= 3