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/>.
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);
64 static void Journal_dealloc(Journal* self)
66 sd_journal_close(self->j);
67 Py_TYPE(self)->tp_free((PyObject*)self);
70 PyDoc_STRVAR(Journal__doc__,
71 "Journal([flags][,path]) -> ...\n"
72 "Journal instance\n\n"
73 "Returns instance of Journal, which allows filtering and return\n"
74 "of journal entries.\n"
75 "Argument `flags` sets open flags of the journal, which can be one\n"
76 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
77 "journal on local machine only; RUNTIME_ONLY opens only\n"
78 "volatile journal files; and SYSTEM_ONLY opens only\n"
79 "journal files of system services and the kernel.\n"
80 "Argument `path` is the directory of journal files. Note that\n"
81 "currently flags are ignored when `path` is present as they are\n"
83 static int Journal_init(Journal *self, PyObject *args, PyObject *keywds)
85 int flags = SD_JOURNAL_LOCAL_ONLY, r;
88 static const char* const kwlist[] = {"flags", "path", NULL};
89 if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iz", (char**) kwlist,
93 Py_BEGIN_ALLOW_THREADS
95 r = sd_journal_open_directory(&self->j, path, 0);
97 r = sd_journal_open(&self->j, flags);
100 return set_error(r, path, "Invalid flags or path");
103 PyDoc_STRVAR(Journal_get_next__doc__,
104 "get_next([skip]) -> dict\n\n"
105 "Return dictionary of the next log entry. Optional skip value will\n"
106 "return the `skip`th log entry.");
107 static PyObject* Journal_get_next(Journal *self, PyObject *args)
112 const char *delim_ptr;
113 PyObject *key, *value, *cur_value, *tmp_list;
115 int64_t skip = 1LL, r = -EINVAL;
116 if (!PyArg_ParseTuple(args, "|L", &skip))
120 PyErr_SetString(PyExc_ValueError, "skip must be nonzero");
124 Py_BEGIN_ALLOW_THREADS
126 r = sd_journal_next(self->j);
127 else if (skip == -1LL)
128 r = sd_journal_previous(self->j);
130 r = sd_journal_next_skip(self->j, skip);
131 else if (skip < -1LL)
132 r = sd_journal_previous_skip(self->j, -skip);
135 set_error(r, NULL, NULL);
138 else if (r == 0) /* EOF */
143 SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
144 delim_ptr = memchr(msg, '=', msg_len);
145 key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
146 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
147 if (PyDict_Contains(dict, key)) {
148 cur_value = PyDict_GetItem(dict, key);
149 if (PyList_CheckExact(cur_value)) {
150 PyList_Append(cur_value, value);
152 tmp_list = PyList_New(0);
153 PyList_Append(tmp_list, cur_value);
154 PyList_Append(tmp_list, value);
155 PyDict_SetItem(dict, key, tmp_list);
159 PyDict_SetItem(dict, key, value);
167 if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
168 char realtime_str[20];
169 sprintf(realtime_str, "%llu", (long long unsigned) realtime);
170 key = unicode_FromString("__REALTIME_TIMESTAMP");
171 value = PyBytes_FromString(realtime_str);
172 PyDict_SetItem(dict, key, value);
181 if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
182 char monotonic_str[20];
183 sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
184 key = unicode_FromString("__MONOTONIC_TIMESTAMP");
185 value = PyBytes_FromString(monotonic_str);
187 PyDict_SetItem(dict, key, value);
195 if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
196 key = unicode_FromString("__CURSOR");
197 value = PyBytes_FromString(cursor);
198 PyDict_SetItem(dict, key, value);
208 PyDoc_STRVAR(Journal_get_previous__doc__,
209 "get_previous([skip]) -> dict\n\n"
210 "Return dictionary of the previous log entry. Optional skip value\n"
211 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
212 static PyObject* Journal_get_previous(Journal *self, PyObject *args)
215 if (!PyArg_ParseTuple(args, "|L", &skip))
218 return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
222 PyDoc_STRVAR(Journal_add_match__doc__,
223 "add_match(match) -> None\n\n"
224 "Add a match to filter journal log entries. All matches of different\n"
225 "fields are combined in logical AND, and matches of the same field\n"
226 "are automatically combined in logical OR.\n"
227 "Match is a string of the form \"FIELD=value\".");
228 static PyObject* Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
232 if (!PyArg_ParseTuple(args, "s#", &match, &match_len))
235 r = sd_journal_add_match(self->j, match, match_len);
236 set_error(r, NULL, "Invalid match");
243 PyDoc_STRVAR(Journal_add_disjunction__doc__,
244 "add_disjunction() -> None\n\n"
245 "Once called, all matches before and after are combined in logical\n"
247 static PyObject* Journal_add_disjunction(Journal *self, PyObject *args)
250 r = sd_journal_add_disjunction(self->j);
251 set_error(r, NULL, NULL);
257 PyDoc_STRVAR(Journal_flush_matches__doc__,
258 "flush_matches() -> None\n\n"
259 "Clears all current match filters.");
260 static PyObject* Journal_flush_matches(Journal *self, PyObject *args)
262 sd_journal_flush_matches(self->j);
266 PyDoc_STRVAR(Journal_seek__doc__,
267 "seek(offset[, whence]) -> None\n\n"
268 "Seek through journal by `offset` number of entries. Argument\n"
269 "`whence` defines what the offset is relative to:\n"
270 "os.SEEK_SET (default) from first match in journal;\n"
271 "os.SEEK_CUR from current position in journal;\n"
272 "and os.SEEK_END is from last match in journal.");
273 static PyObject* Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
276 int whence = SEEK_SET;
277 PyObject *result = NULL;
279 static const char* const kwlist[] = {"offset", "whence", NULL};
280 if (!PyArg_ParseTupleAndKeywords(args, keywds, "L|i", (char**) kwlist,
287 Py_BEGIN_ALLOW_THREADS
288 r = sd_journal_seek_head(self->j);
290 if (set_error(r, NULL, NULL))
294 result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
295 (char*) "L", offset);
299 result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
300 (char*) "L", offset);
304 Py_BEGIN_ALLOW_THREADS
305 r = sd_journal_seek_tail(self->j);
307 if (set_error(r, NULL, NULL))
310 result = PyObject_CallMethod((PyObject *)self, (char*) "get_next",
311 (char*) "L", offset < 0LL ? offset : -1LL);
315 PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
319 if (PyErr_Occurred())
324 PyDoc_STRVAR(Journal_seek_realtime__doc__,
325 "seek_realtime(realtime) -> None\n\n"
326 "Seek to nearest matching journal entry to `realtime`. Argument\n"
327 "`realtime` can must be an integer unix timestamp.");
328 static PyObject* Journal_seek_realtime(Journal *self, PyObject *args)
334 if (!PyArg_ParseTuple(args, "d", &timedouble))
337 timestamp = (uint64_t) (timedouble * 1.0E6);
338 if ((int64_t) timestamp < 0LL) {
339 PyErr_SetString(PyExc_ValueError, "Time must be a positive integer");
343 Py_BEGIN_ALLOW_THREADS
344 r = sd_journal_seek_realtime_usec(self->j, timestamp);
346 if (set_error(r, NULL, NULL))
351 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
352 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
353 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
354 "`monotonic` is an timestamp from boot in seconds.\n"
355 "Argument `bootid` is a string representing which boot the\n"
356 "monotonic time is reference to. Defaults to current bootid.");
357 static PyObject* Journal_seek_monotonic(Journal *self, PyObject *args)
365 if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
368 timestamp = (uint64_t) (timedouble * 1.0E6);
370 if ((int64_t) timestamp < 0LL) {
371 PyErr_SetString(PyExc_ValueError, "Time must be positive number");
376 r = sd_id128_from_string(bootid, &sd_id);
377 if (set_error(r, NULL, "Invalid bootid"))
380 Py_BEGIN_ALLOW_THREADS
381 r = sd_id128_get_boot(&sd_id);
383 if (set_error(r, NULL, NULL))
387 Py_BEGIN_ALLOW_THREADS
388 r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
390 if (set_error(r, NULL, NULL))
395 PyDoc_STRVAR(Journal_wait__doc__,
396 "wait([timeout]) -> Change state (integer)\n\n"
397 "Waits until there is a change in the journal. Argument `timeout`\n"
398 "is the maximum number of seconds to wait before returning\n"
399 "regardless if journal has changed. If `timeout` is not given or is\n"
400 "0, then it will block forever.\n"
401 "Will return constants: NOP if no change; APPEND if new\n"
402 "entries have been added to the end of the journal; and\n"
403 "INVALIDATE if journal files have been added or removed.");
404 static PyObject* Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
407 int64_t timeout = 0LL;
409 if (!PyArg_ParseTuple(args, "|L", &timeout))
412 Py_BEGIN_ALLOW_THREADS
413 r = sd_journal_wait(self->j, timeout ==0 ? (uint64_t) -1 : timeout * 1E6);
415 if (set_error(r, NULL, NULL))
418 return long_FromLong(r);
421 PyDoc_STRVAR(Journal_seek_cursor__doc__,
422 "seek_cursor(cursor) -> None\n\n"
423 "Seeks to journal entry by given unique reference `cursor`.");
424 static PyObject* Journal_seek_cursor(Journal *self, PyObject *args)
429 if (!PyArg_ParseTuple(args, "s", &cursor))
432 Py_BEGIN_ALLOW_THREADS
433 r = sd_journal_seek_cursor(self->j, cursor);
435 if (set_error(r, NULL, "Invalid cursor"))
440 static PyObject* Journal_iter(PyObject *self)
446 static PyObject* Journal_iternext(PyObject *self)
449 Py_ssize_t dict_size;
451 dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
452 if (PyErr_Occurred())
454 dict_size = PyDict_Size(dict);
455 if ((int64_t) dict_size > 0LL) {
459 PyErr_SetNone(PyExc_StopIteration);
464 PyDoc_STRVAR(Journal_query_unique__doc__,
465 "query_unique(field) -> a set of values\n\n"
466 "Returns a set of unique values in journal for given `field`.\n"
467 "Note this does not respect any journal matches.");
468 static PyObject* Journal_query_unique(Journal *self, PyObject *args)
474 PyObject *value_set, *key, *value;
476 if (!PyArg_ParseTuple(args, "s", &query))
479 Py_BEGIN_ALLOW_THREADS
480 r = sd_journal_query_unique(self->j, query);
482 if (set_error(r, NULL, "Invalid field name"))
485 value_set = PySet_New(0);
486 key = unicode_FromString(query);
488 SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
489 const char *delim_ptr;
491 delim_ptr = memchr(uniq, '=', uniq_len);
492 value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
493 PySet_Add(value_set, value);
500 static PyObject* Journal_get_data_threshold(Journal *self, void *closure)
505 r = sd_journal_get_data_threshold(self->j, &cvalue);
506 if (set_error(r, NULL, NULL))
509 return long_FromSize_t(cvalue);
512 static int Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
516 PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
519 if (!long_Check(value)){
520 PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
523 r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
524 return set_error(r, NULL, NULL);
527 static PyGetSetDef Journal_getseters[] = {
528 {(char*) "data_threshold",
529 (getter)Journal_get_data_threshold,
530 (setter)Journal_set_data_threshold,
531 (char*) "data threshold",
536 static PyMethodDef Journal_methods[] = {
537 {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
538 Journal_get_next__doc__},
539 {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
540 Journal_get_previous__doc__},
541 {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
542 Journal_add_match__doc__},
543 {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
544 Journal_add_disjunction__doc__},
545 {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
546 Journal_flush_matches__doc__},
547 {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
548 Journal_seek__doc__},
549 {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
550 Journal_seek_realtime__doc__},
551 {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
552 Journal_seek_monotonic__doc__},
553 {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
554 Journal_wait__doc__},
555 {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
556 Journal_seek_cursor__doc__},
557 {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
558 Journal_query_unique__doc__},
559 {NULL} /* Sentinel */
562 static PyTypeObject JournalType = {
563 PyVarObject_HEAD_INIT(NULL, 0)
564 "_reader._Journal", /*tp_name*/
565 sizeof(Journal), /*tp_basicsize*/
567 (destructor)Journal_dealloc, /*tp_dealloc*/
574 0, /*tp_as_sequence*/
582 Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
583 Journal__doc__, /* tp_doc */
586 0, /* tp_richcompare */
587 0, /* tp_weaklistoffset */
588 Journal_iter, /* tp_iter */
589 Journal_iternext, /* tp_iternext */
590 Journal_methods, /* tp_methods */
592 Journal_getseters, /* tp_getset */
595 0, /* tp_descr_get */
596 0, /* tp_descr_set */
597 0, /* tp_dictoffset */
598 (initproc)Journal_init, /* tp_init */
600 PyType_GenericNew, /* tp_new */
603 #if PY_MAJOR_VERSION >= 3
604 static PyModuleDef _reader_module = {
605 PyModuleDef_HEAD_INIT,
607 "Module that reads systemd journal similar to journalctl.",
609 NULL, NULL, NULL, NULL, NULL
613 #pragma GCC diagnostic push
614 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
617 #if PY_MAJOR_VERSION >= 3
627 if (PyType_Ready(&JournalType) < 0)
628 #if PY_MAJOR_VERSION >= 3
634 #if PY_MAJOR_VERSION >= 3
635 m = PyModule_Create(&_reader_module);
639 m = Py_InitModule3("_reader", NULL,
640 "Module that reads systemd journal similar to journalctl.");
645 Py_INCREF(&JournalType);
646 PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
647 PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
648 PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
649 PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
650 PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
651 PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
652 PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
654 #if PY_MAJOR_VERSION >= 3
659 #pragma GCC diagnostic pop