chiark / gitweb /
systemd-python: add Journal class for reading journal
[elogind.git] / src / python-systemd / _reader.c
1 /*
2 _reader - Python module that reads systemd journal similar to journalctl
3 Copyright (C) 2012  Steven Hiscocks
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 #include <systemd/sd-journal.h>
20
21 #include <Python.h>
22 #include <structmember.h>
23 #include <datetime.h>
24
25 typedef struct {
26     PyObject_HEAD
27     sd_journal *j;
28     PyObject *default_call;
29     PyObject *call_dict;
30 } Journal;
31 static PyTypeObject JournalType;
32
33 static void
34 Journal_dealloc(Journal* self)
35 {
36     sd_journal_close(self->j);
37     Py_XDECREF(self->default_call);
38     Py_XDECREF(self->call_dict);
39     Py_TYPE(self)->tp_free((PyObject*)self);
40 }
41
42 static PyObject *
43 Journal_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
44 {
45     Journal *self;
46
47     self = (Journal *)type->tp_alloc(type, 0);
48     if (self != NULL) {
49         PyObject *globals, *temp;
50
51         globals = PyEval_GetBuiltins();
52         temp = PyImport_ImportModule("functools");
53         PyDict_SetItemString(globals, "functools", temp);
54         Py_DECREF(temp);
55         temp = PyImport_ImportModule("datetime");
56         PyDict_SetItemString(globals, "datetime", temp);
57         Py_DECREF(temp);
58
59 #if PY_MAJOR_VERSION >=3
60         self->default_call = PyRun_String("functools.partial(str, encoding='utf-8')", Py_eval_input, globals, NULL);
61 #else
62         self->default_call = PyRun_String("functools.partial(unicode, encoding='utf-8')", Py_eval_input, globals, NULL);
63 #endif
64
65         self->call_dict = PyRun_String("{"
66             "'PRIORITY': int,"
67             "'LEADER': int,"
68             "'SESSION_ID': int,"
69             "'USERSPACE_USEC': int,"
70             "'INITRD_USEC': int,"
71             "'KERNEL_USEC': int,"
72             "'_UID': int,"
73             "'_GID': int,"
74             "'_PID': int,"
75             "'SYSLOG_FACILITY': int,"
76             "'SYSLOG_PID': int,"
77             "'_AUDIT_SESSION': int,"
78             "'_AUDIT_LOGINUID': int,"
79             "'_SYSTEMD_SESSION': int,"
80             "'_SYSTEMD_OWNER_UID': int,"
81             "'CODE_LINE': int,"
82             "'ERRNO': int,"
83             "'EXIT_STATUS': int,"
84             "'_SOURCE_REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),"
85             "'__REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),"
86             "'_SOURCE_MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)),"
87             "'__MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)),"
88 #if PY_MAJOR_VERSION >=3
89             "'COREDUMP': bytes,"
90 #else
91             "'COREDUMP': str,"
92 #endif
93             "'COREDUMP_PID': int,"
94             "'COREDUMP_UID': int,"
95             "'COREDUMP_GID': int,"
96             "'COREDUMP_SESSION': int,"
97             "'COREDUMP_SIGNAL': int,"
98             "'COREDUMP_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6),"
99             "}", Py_eval_input, globals, NULL);
100     }
101
102     return (PyObject *) self;
103 }
104
105 PyDoc_STRVAR(Journal__doc__,
106 "Journal([flags][, default_call][, call_dict][,path]) -> ...\n"
107 "Journal instance\n\n"
108 "Returns instance of Journal, which allows filtering and return\n"
109 "of journal entries.\n"
110 "Argument `flags` sets open flags of the journal, which can be one\n"
111 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
112 "journal on local machine only; RUNTIME_ONLY opens only\n"
113 "volatile journal files; and SYSTEM_ONLY opens only\n"
114 "journal files of system services and the kernel.\n"
115 "Argument `default_call` must be a callable that accepts one\n"
116 "argument which is string/bytes value of a field and returns\n"
117 "python object.\n"
118 "Argument `call_dict` is a dictionary where the key represents\n"
119 "a field name, and value is a callable as per `default_call`.\n"
120 "A set of sane defaults for `default_call` and `call_dict` are\n"
121 "present.\n"
122 "Argument `path` is the directory of journal files. Note that\n"
123 "currently flags are ignored when `path` is present as they are\n"
124 " not relevant.");
125 static int
126 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
127 {
128     int flags=SD_JOURNAL_LOCAL_ONLY;
129     char *path=NULL;
130     PyObject *default_call=NULL, *call_dict=NULL;
131
132     static char *kwlist[] = {"flags", "default_call", "call_dict", "path", NULL};
133     if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iOOs", kwlist,
134                                       &flags, &default_call, &call_dict, &path))
135         return 1;
136
137     if (default_call) {
138         if (PyCallable_Check(default_call) || default_call == Py_None) {
139             Py_DECREF(self->default_call);
140             self->default_call = default_call;
141             Py_INCREF(self->default_call);
142         }else{
143             PyErr_SetString(PyExc_TypeError, "Default call not callable");
144             return 1;
145         }
146     }
147
148     if (call_dict) {
149         if (PyDict_Check(call_dict)) {
150             Py_DECREF(self->call_dict);
151             self->call_dict = call_dict;
152             Py_INCREF(self->call_dict);
153         }else if (call_dict == Py_None) {
154             Py_DECREF(self->call_dict);
155             self->call_dict = PyDict_New();
156         }else{
157             PyErr_SetString(PyExc_TypeError, "Call dictionary must be dict type");
158             return 1;
159         }
160     }
161
162     int r;
163     if (path) {
164         r = sd_journal_open_directory(&self->j, path, 0);
165     }else{
166         Py_BEGIN_ALLOW_THREADS
167         r = sd_journal_open(&self->j, flags);
168         Py_END_ALLOW_THREADS
169     }
170     if (r == -EINVAL) {
171         PyErr_SetString(PyExc_ValueError, "Invalid flags or path");
172         return -1;
173     }else if (r == -ENOMEM) {
174         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
175         return 1;
176     }else if (r < 0) {
177         PyErr_SetString(PyExc_RuntimeError, "Error opening journal");
178         return 1;
179     }
180
181     return 0;
182 }
183
184 static PyObject *
185 Journal___process_field(Journal *self, PyObject *key, const void *value, ssize_t value_len)
186 {
187     PyObject *callable=NULL, *return_value=NULL;
188     if (PyDict_Check(self->call_dict))
189         callable = PyDict_GetItem(self->call_dict, key);
190
191     if (PyCallable_Check(callable)) {
192 #if PY_MAJOR_VERSION >=3
193         return_value = PyObject_CallFunction(callable, "y#", value, value_len);
194 #else
195         return_value = PyObject_CallFunction(callable, "s#", value, value_len);
196 #endif
197         if (!return_value)
198             PyErr_Clear();
199     }
200     if (!return_value && PyCallable_Check(self->default_call))
201 #if PY_MAJOR_VERSION >=3
202         return_value = PyObject_CallFunction(self->default_call, "y#", value, value_len);
203 #else
204         return_value = PyObject_CallFunction(self->default_call, "s#", value, value_len);
205 #endif
206     if (!return_value) {
207         PyErr_Clear();
208 #if PY_MAJOR_VERSION >=3
209         return_value = PyBytes_FromStringAndSize(value, value_len);
210 #else
211         return_value = PyString_FromStringAndSize(value, value_len);
212 #endif
213     }
214     if (!return_value) {
215         return_value = Py_None;
216     }
217     return return_value;
218 }
219
220 PyDoc_STRVAR(Journal_get_next__doc__,
221 "get_next([skip]) -> dict\n\n"
222 "Return dictionary of the next log entry. Optional skip value will\n"
223 "return the `skip`th log entry.");
224 static PyObject *
225 Journal_get_next(Journal *self, PyObject *args)
226 {
227     int64_t skip=1LL;
228     if (! PyArg_ParseTuple(args, "|L", &skip))
229         return NULL;
230
231     int r;
232     if (skip == 1LL) {
233         Py_BEGIN_ALLOW_THREADS
234         r = sd_journal_next(self->j);
235         Py_END_ALLOW_THREADS
236     }else if (skip == -1LL) {
237         Py_BEGIN_ALLOW_THREADS
238         r = sd_journal_previous(self->j);
239         Py_END_ALLOW_THREADS
240     }else if (skip > 1LL) {
241         Py_BEGIN_ALLOW_THREADS
242         r = sd_journal_next_skip(self->j, skip);
243         Py_END_ALLOW_THREADS
244     }else if (skip < -1LL) {
245         Py_BEGIN_ALLOW_THREADS
246         r = sd_journal_previous_skip(self->j, -skip);
247         Py_END_ALLOW_THREADS
248     }else{
249         PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
250         return NULL;
251     }
252
253     if (r < 0) {
254         PyErr_SetString(PyExc_RuntimeError, "Error getting next message");
255         return NULL;
256     }else if ( r == 0) { //EOF
257         return PyDict_New();
258     }
259
260     PyObject *dict;
261     dict = PyDict_New();
262
263     const void *msg;
264     size_t msg_len;
265     const char *delim_ptr;
266     PyObject *key, *value, *cur_value, *tmp_list;
267
268     SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
269         delim_ptr = memchr(msg, '=', msg_len);
270 #if PY_MAJOR_VERSION >=3
271         key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
272 #else
273         key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
274 #endif
275         value = Journal___process_field(self, key, delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
276         if (PyDict_Contains(dict, key)) {
277             cur_value = PyDict_GetItem(dict, key);
278             if (PyList_CheckExact(cur_value) && PyList_Size(cur_value) > 1) {
279                 PyList_Append(cur_value, value);
280             }else{
281                 tmp_list = PyList_New(0);
282                 PyList_Append(tmp_list, cur_value);
283                 PyList_Append(tmp_list, value);
284                 PyDict_SetItem(dict, key, tmp_list);
285                 Py_DECREF(tmp_list);
286             }
287         }else{
288             PyDict_SetItem(dict, key, value);
289         }
290         Py_DECREF(key);
291         Py_DECREF(value);
292     }
293
294     uint64_t realtime;
295     if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
296         char realtime_str[20];
297         sprintf(realtime_str, "%llu", (long long unsigned) realtime);
298
299 #if PY_MAJOR_VERSION >=3
300         key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
301 #else
302         key = PyString_FromString("__REALTIME_TIMESTAMP");
303 #endif
304         value = Journal___process_field(self, key, realtime_str, strlen(realtime_str));
305         PyDict_SetItem(dict, key, value);
306         Py_DECREF(key);
307         Py_DECREF(value);
308     }
309
310     sd_id128_t sd_id;
311     uint64_t monotonic;
312     if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
313         char monotonic_str[20];
314         sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
315 #if PY_MAJOR_VERSION >=3
316         key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
317 #else
318         key = PyString_FromString("__MONOTONIC_TIMESTAMP");
319 #endif
320         value = Journal___process_field(self, key, monotonic_str, strlen(monotonic_str));
321
322         PyDict_SetItem(dict, key, value);
323         Py_DECREF(key);
324         Py_DECREF(value);
325     }
326
327     char *cursor;
328     if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
329 #if PY_MAJOR_VERSION >=3
330         key = PyUnicode_FromString("__CURSOR");
331 #else
332         key = PyString_FromString("__CURSOR");
333 #endif
334         value = Journal___process_field(self, key, cursor, strlen(cursor));
335         PyDict_SetItem(dict, key, value);
336         free(cursor);
337         Py_DECREF(key);
338         Py_DECREF(value);
339     }
340
341     return dict;
342 }
343
344 PyDoc_STRVAR(Journal_get_previous__doc__,
345 "get_previous([skip]) -> dict\n\n"
346 "Return dictionary of the previous log entry. Optional skip value\n"
347 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
348 static PyObject *
349 Journal_get_previous(Journal *self, PyObject *args)
350 {
351     int64_t skip=1LL;
352     if (! PyArg_ParseTuple(args, "|L", &skip))
353         return NULL;
354
355     PyObject *dict, *arg;
356     arg = Py_BuildValue("(L)", -skip);
357     dict = Journal_get_next(self, arg);
358     Py_DECREF(arg);
359     return dict;
360 }
361
362 PyDoc_STRVAR(Journal_add_match__doc__,
363 "add_match(match, ..., field=value, ...) -> None\n\n"
364 "Add a match to filter journal log entries. All matches of different\n"
365 "field are combined in logical AND, and matches of the same field\n"
366 "are automatically combined in logical OR.\n"
367 "Matches can be passed as strings \"field=value\", or keyword\n"
368 "arguments field=\"value\".");
369 static PyObject *
370 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
371 {
372     Py_ssize_t arg_match_len;
373     char *arg_match;
374     int i, r;
375     for (i = 0; i < PySequence_Size(args); i++) {
376 #if PY_MAJOR_VERSION >=3
377         PyObject *arg;
378         arg = PySequence_Fast_GET_ITEM(args, i);
379         if (PyUnicode_Check(arg)) {
380 #if PY_MINOR_VERSION >=3
381             arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
382 #else
383             PyObject *temp;
384             temp = PyUnicode_AsUTF8String(arg);
385             PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
386             Py_DECREF(temp);
387 #endif
388         }else if (PyBytes_Check(arg)) {
389             PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
390         }else{
391             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
392         }
393 #else
394         PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
395 #endif
396         if (PyErr_Occurred())
397             return NULL;
398         r = sd_journal_add_match(self->j, arg_match, arg_match_len);
399         if (r == -EINVAL) {
400             PyErr_SetString(PyExc_ValueError, "Invalid match");
401             return NULL;
402         }else if (r == -ENOMEM) {
403             PyErr_SetString(PyExc_MemoryError, "Not enough memory");
404             return NULL;
405         }else if (r < 0) {
406             PyErr_SetString(PyExc_RuntimeError, "Error adding match");
407             return NULL;
408         }
409     }
410
411     if (! keywds)
412         Py_RETURN_NONE;
413
414     PyObject *key, *value;
415     Py_ssize_t pos=0, match_key_len, match_value_len;
416     int match_len;
417     char *match_key, *match_value;
418     void *match;
419     while (PyDict_Next(keywds, &pos, &key, &value)) {
420 #if PY_MAJOR_VERSION >=3
421         if (PyUnicode_Check(key)) {
422 #if PY_MINOR_VERSION >=3
423             match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
424 #else
425             PyObject *temp2;
426             temp2 = PyUnicode_AsUTF8String(key);
427             PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
428             Py_DECREF(temp2);
429 #endif
430         }else if (PyBytes_Check(key)) {
431             PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
432         }else{
433             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
434         }
435         if (PyUnicode_Check(value)) {
436 #if PY_MINOR_VERSION >=3
437             match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
438 #else
439             PyObject *temp3;
440             temp3 = PyUnicode_AsUTF8String(value);
441             PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
442             Py_DECREF(temp3);
443 #endif
444         }else if (PyBytes_Check(value)) {
445             PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
446         }else{
447             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
448         }
449 #else
450         PyString_AsStringAndSize(key, &match_key, &match_key_len);
451         PyString_AsStringAndSize(value, &match_value, &match_value_len);
452 #endif
453         if (PyErr_Occurred())
454             return NULL;
455
456         match_len = match_key_len + 1 + match_value_len;
457         match = malloc(match_len);
458         memcpy(match, match_key, match_key_len);
459         memcpy(match + match_key_len, "=", 1);
460         memcpy(match + match_key_len + 1, match_value, match_value_len);
461
462         r = sd_journal_add_match(self->j, match, match_len);
463         free(match);
464         if (r == -EINVAL) {
465             PyErr_SetString(PyExc_ValueError, "Invalid match");
466             return NULL;
467         }else if (r == -ENOMEM) {
468             PyErr_SetString(PyExc_MemoryError, "Not enough memory");
469             return NULL;
470         }else if (r < 0) {
471             PyErr_SetString(PyExc_RuntimeError, "Error adding match");
472             return NULL;
473         }
474     }
475
476     Py_RETURN_NONE;
477 }
478
479 PyDoc_STRVAR(Journal_add_disjunction__doc__,
480 "add_disjunction() -> None\n\n"
481 "Once called, all matches before and after are combined in logical\n"
482 "OR.");
483 static PyObject *
484 Journal_add_disjunction(Journal *self, PyObject *args)
485 {
486     int r;
487     r = sd_journal_add_disjunction(self->j);
488     if (r == -ENOMEM) {
489         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
490         return NULL;
491     }else if (r < 0) {
492         PyErr_SetString(PyExc_RuntimeError, "Error adding disjunction");
493         return NULL;
494     }
495     Py_RETURN_NONE;
496 }
497
498 PyDoc_STRVAR(Journal_flush_matches__doc__,
499 "flush_matches() -> None\n\n"
500 "Clears all current match filters.");
501 static PyObject *
502 Journal_flush_matches(Journal *self, PyObject *args)
503 {
504     sd_journal_flush_matches(self->j);
505     Py_RETURN_NONE;
506 }
507
508 PyDoc_STRVAR(Journal_seek__doc__,
509 "seek(offset[, whence]) -> None\n\n"
510 "Seek through journal by `offset` number of entries. Argument\n"
511 "`whence` defines what the offset is relative to:\n"
512 "os.SEEK_SET (default) from first match in journal;\n"
513 "os.SEEK_CUR from current position in journal;\n"
514 "and os.SEEK_END is from last match in journal.");
515 static PyObject *
516 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
517 {
518     int64_t offset;
519     int whence=SEEK_SET;
520     static char *kwlist[] = {"offset", "whence", NULL};
521
522     if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
523                                       &offset, &whence))
524         return NULL;
525
526     PyObject *arg;
527     if (whence == SEEK_SET){
528         int r;
529         Py_BEGIN_ALLOW_THREADS
530         r = sd_journal_seek_head(self->j);
531         Py_END_ALLOW_THREADS
532         if (r < 0) {
533             PyErr_SetString(PyExc_RuntimeError, "Error seeking to head");
534             return NULL;
535         }
536         if (offset > 0LL) {
537             arg = Py_BuildValue("(L)", offset);
538             Py_DECREF(Journal_get_next(self, arg));
539             Py_DECREF(arg);
540         }
541     }else if (whence == SEEK_CUR){
542         arg = Py_BuildValue("(L)", offset);
543         Py_DECREF(Journal_get_next(self, arg));
544         Py_DECREF(arg);
545     }else if (whence == SEEK_END){
546         int r;
547         Py_BEGIN_ALLOW_THREADS
548         r = sd_journal_seek_tail(self->j);
549         Py_END_ALLOW_THREADS
550         if (r < 0) {
551             PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail");
552             return NULL;
553         }
554         arg = Py_BuildValue("(L)", -1LL);
555         Py_DECREF(Journal_get_next(self, arg));
556         Py_DECREF(arg);
557         if (offset < 0LL) {
558             arg = Py_BuildValue("(L)", offset);
559             Py_DECREF(Journal_get_next(self, arg));
560             Py_DECREF(arg);
561         }
562     }else{
563         PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
564         return NULL;
565     }
566     Py_RETURN_NONE;
567 }
568
569 PyDoc_STRVAR(Journal_seek_realtime__doc__,
570 "seek_realtime(realtime) -> None\n\n"
571 "Seek to nearest matching journal entry to `realtime`. Argument\n"
572 "`realtime` can be an integer unix timestamp in usecs or a "
573 "datetime instance.");
574 static PyObject *
575 Journal_seek_realtime(Journal *self, PyObject *args)
576 {
577     PyObject *arg;
578     if (! PyArg_ParseTuple(args, "O", &arg))
579         return NULL;
580
581     uint64_t timestamp=-1LL;
582     if (PyDateTime_Check(arg)) {
583         PyObject *temp;
584         char *timestamp_str;
585         temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f");
586 #if PY_MAJOR_VERSION >=3
587         PyObject *temp2;
588         temp2 = PyUnicode_AsUTF8String(temp);
589         timestamp_str = PyBytes_AsString(temp2);
590         Py_DECREF(temp2);
591 #else
592         timestamp_str = PyString_AsString(temp);
593 #endif
594         Py_DECREF(temp);
595         timestamp = strtoull(timestamp_str, NULL, 10);
596     }else if (PyLong_Check(arg)) {
597         timestamp = PyLong_AsUnsignedLongLong(arg);
598 #if PY_MAJOR_VERSION <3
599     }else if (PyInt_Check(arg)) {
600         timestamp = PyInt_AsUnsignedLongLongMask(arg);
601 #endif
602     }
603     if ((int64_t) timestamp < 0LL) {
604         PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance");
605         return NULL;
606     }
607
608     int r;
609     Py_BEGIN_ALLOW_THREADS
610     r = sd_journal_seek_realtime_usec(self->j, timestamp);
611     Py_END_ALLOW_THREADS
612     if (r < 0) {
613         PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
614         return NULL;
615     }
616     Py_RETURN_NONE;
617 }
618
619 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
620 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
621 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
622 "`monotonic` is an timestamp from boot in secs, or a\n"
623 "timedelta instance.\n"
624 "Argument `bootid` is a string representing which boot the\n"
625 "monotonic time is reference to. Defaults to current bootid.");
626 static PyObject *
627 Journal_seek_monotonic(Journal *self, PyObject *args)
628 {
629     PyObject *arg;
630     char *bootid=NULL;
631     if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid))
632         return NULL;
633
634     uint64_t timestamp=-1LL;
635     if PyDelta_Check(arg) {
636         PyObject *temp;
637         temp = PyObject_CallMethod(arg, "total_seconds", NULL);
638         timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6);
639         Py_DECREF(temp);
640     }else if (PyFloat_Check(arg)) {
641         timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6);
642     }else if (PyLong_Check(arg)) {
643         timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6;
644 #if PY_MAJOR_VERSION <3
645     }else if (PyInt_Check(arg)) {
646         timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6;
647 #endif
648
649     }
650
651     if ((int64_t) timestamp < 0LL) {
652         PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance");
653         return NULL;
654     }
655
656     sd_id128_t sd_id;
657     int r;
658     if (bootid) {
659         r = sd_id128_from_string(bootid, &sd_id);
660         if (r == -EINVAL) {
661             PyErr_SetString(PyExc_ValueError, "Invalid bootid");
662             return NULL;
663         } else if (r < 0) {
664             PyErr_SetString(PyExc_RuntimeError, "Error processing bootid");
665             return NULL;
666         }
667     }else{
668         r = sd_id128_get_boot(&sd_id);
669         if (r == -EIO) {
670             PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
671             return NULL;
672         } else if (r < 0) {
673             PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
674             return NULL;
675         }
676     }
677
678     Py_BEGIN_ALLOW_THREADS
679     r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
680     Py_END_ALLOW_THREADS
681     if (r < 0) {
682         PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
683         return NULL;
684     }
685     Py_RETURN_NONE;
686 }
687  
688 PyDoc_STRVAR(Journal_wait__doc__,
689 "wait([timeout]) -> Change state (integer)\n\n"
690 "Waits until there is a change in the journal. Argument `timeout`\n"
691 "is the maximum number of seconds to wait before returning\n"
692 "regardless if journal has changed. If `timeout` is not given or is\n"
693 "0, then it will block forever.\n"
694 "Will return constants: NOP if no change; APPEND if new\n"
695 "entries have been added to the end of the journal; and\n"
696 "INVALIDATE if journal files have been added or removed.");
697 static PyObject *
698 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
699 {
700     int64_t timeout=0LL;
701     if (! PyArg_ParseTuple(args, "|L", &timeout))
702         return NULL;
703
704     int r;
705     if ( timeout == 0LL) {
706         Py_BEGIN_ALLOW_THREADS
707         r = sd_journal_wait(self->j, (uint64_t) -1);
708         Py_END_ALLOW_THREADS
709     }else{
710         Py_BEGIN_ALLOW_THREADS
711         r = sd_journal_wait(self->j, timeout * 1E6);
712         Py_END_ALLOW_THREADS
713     }
714 #if PY_MAJOR_VERSION >=3
715     return PyLong_FromLong(r);
716 #else
717     return PyInt_FromLong(r);
718 #endif
719 }
720
721 PyDoc_STRVAR(Journal_seek_cursor__doc__,
722 "seek_cursor(cursor) -> None\n\n"
723 "Seeks to journal entry by given unique reference `cursor`.");
724 static PyObject *
725 Journal_seek_cursor(Journal *self, PyObject *args)
726 {
727     const char *cursor;
728     if (! PyArg_ParseTuple(args, "s", &cursor))
729         return NULL;
730
731     int r;
732     Py_BEGIN_ALLOW_THREADS
733     r = sd_journal_seek_cursor(self->j, cursor);
734     Py_END_ALLOW_THREADS
735     if (r == -EINVAL) {
736         PyErr_SetString(PyExc_ValueError, "Invalid cursor");
737         return NULL;
738     }else if (r == -ENOMEM) {
739         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
740         return NULL;
741     }else if (r < 0) {
742         PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor");
743         return NULL;
744     }
745     Py_RETURN_NONE;
746 }
747
748 static PyObject *
749 Journal_iter(PyObject *self)
750 {
751     Py_INCREF(self);
752     return self;
753 }
754
755 static PyObject *
756 Journal_iternext(PyObject *self)
757 {
758     Journal *iter = (Journal *)self;
759     PyObject *dict, *arg;
760     Py_ssize_t dict_size;
761
762     arg =  Py_BuildValue("()");
763     dict = Journal_get_next(iter, arg);
764     Py_DECREF(arg);
765     dict_size = PyDict_Size(dict);
766     if ((int64_t) dict_size > 0LL) {
767         return dict;
768     }else{
769         Py_DECREF(dict);
770         PyErr_SetNone(PyExc_StopIteration);
771         return NULL;
772     }
773 }
774
775 #ifdef SD_JOURNAL_FOREACH_UNIQUE
776 PyDoc_STRVAR(Journal_query_unique__doc__,
777 "query_unique(field) -> a set of values\n\n"
778 "Returns a set of unique values in journal for given `field`.\n"
779 "Note this does not respect any journal matches.");
780 static PyObject *
781 Journal_query_unique(Journal *self, PyObject *args)
782 {
783     char *query;
784     if (! PyArg_ParseTuple(args, "s", &query))
785         return NULL;
786
787     int r;
788     Py_BEGIN_ALLOW_THREADS
789     r = sd_journal_query_unique(self->j, query);
790     Py_END_ALLOW_THREADS
791     if (r == -EINVAL) {
792         PyErr_SetString(PyExc_ValueError, "Invalid field name");
793         return NULL;
794     } else if (r == -ENOMEM) {
795         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
796         return NULL;
797     } else if (r < 0) {
798         PyErr_SetString(PyExc_RuntimeError, "Error querying journal");
799         return NULL;
800     }
801
802     const void *uniq;
803     size_t uniq_len;
804     const char *delim_ptr;
805     PyObject *value_set, *key, *value;
806     value_set = PySet_New(0);
807
808 #if PY_MAJOR_VERSION >=3
809     key = PyUnicode_FromString(query);
810 #else
811     key = PyString_FromString(query);
812 #endif
813
814     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
815         delim_ptr = memchr(uniq, '=', uniq_len);
816         value = Journal___process_field(self, key, delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
817         PySet_Add(value_set, value);
818         Py_DECREF(value);
819     }
820     Py_DECREF(key);
821     return value_set;
822 }
823 #endif //def SD_JOURNAL_FOREACH_UNIQUE
824
825 PyDoc_STRVAR(Journal_log_level__doc__,
826 "log_level(level) -> None\n\n"
827 "Sets maximum log level by setting matches for PRIORITY.");
828 static PyObject *
829 Journal_log_level(Journal *self, PyObject *args)
830 {
831     int level;
832     if (! PyArg_ParseTuple(args, "i", &level))
833         return NULL;
834
835     if (level < 0 || level > 7) {
836         PyErr_SetString(PyExc_ValueError, "Log level should be 0 <= level <= 7");
837         return NULL;
838     }
839     int i;
840     char level_str[2];
841     PyObject *arg, *keywds;
842     for(i = 0; i <= level; i++) {
843         sprintf(level_str, "%i", i);
844         arg = PyTuple_New(0);
845         keywds = Py_BuildValue("{s:s}", "PRIORITY", level_str);
846         Journal_add_match(self, arg, keywds);
847         Py_DECREF(arg);
848         Py_DECREF(keywds);
849         if (PyErr_Occurred())
850             return NULL;
851     }
852     Py_RETURN_NONE;
853 }
854
855 PyDoc_STRVAR(Journal_this_boot__doc__,
856 "this_boot() -> None\n\n"
857 "Sets match filter for the current _BOOT_ID.");
858 static PyObject *
859 Journal_this_boot(Journal *self, PyObject *args)
860 {
861     sd_id128_t sd_id;
862     int r;
863     r = sd_id128_get_boot(&sd_id);
864     if (r == -EIO) {
865         PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
866         return NULL;
867     } else if (r < 0) {
868         PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
869         return NULL;
870     }
871
872     char bootid[33];
873     sd_id128_to_string(sd_id, bootid);
874
875     PyObject *arg, *keywds;
876     arg = PyTuple_New(0);
877     keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid);
878     Journal_add_match(self, arg, keywds);
879     Py_DECREF(arg);
880     Py_DECREF(keywds);
881     if (PyErr_Occurred())
882         return NULL;
883
884     Py_RETURN_NONE;
885 }
886
887 PyDoc_STRVAR(Journal_this_machine__doc__,
888 "this_machine() -> None\n\n"
889 "Sets match filter for the current _MACHINE_ID.");
890 static PyObject *
891 Journal_this_machine(Journal *self, PyObject *args)
892 {
893     sd_id128_t sd_id;
894     int r;
895     r = sd_id128_get_machine(&sd_id);
896     if (r == -EIO) {
897         PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
898         return NULL;
899     } else if (r < 0) {
900         PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
901         return NULL;
902     }
903
904     char machineid[33];
905     sd_id128_to_string(sd_id, machineid);
906
907     PyObject *arg, *keywds;
908     arg = PyTuple_New(0);
909     keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid);
910     Journal_add_match(self, arg, keywds);
911     Py_DECREF(arg);
912     Py_DECREF(keywds);
913     if (PyErr_Occurred())
914         return NULL;
915
916     Py_RETURN_NONE;
917 }
918
919 static PyObject *
920 Journal_get_default_call(Journal *self, void *closure)
921 {
922     Py_INCREF(self->default_call);
923     return self->default_call;
924 }
925
926 static int
927 Journal_set_default_call(Journal *self, PyObject *value, void *closure)
928 {
929     if (value == NULL) {
930         PyErr_SetString(PyExc_TypeError, "Cannot delete default_call");
931         return -1;
932     }
933     if (! PyCallable_Check(value)) {
934         PyErr_SetString(PyExc_TypeError, "default_call must be callable");
935         return -1;
936     }
937     Py_DECREF(self->default_call);
938     Py_INCREF(value);
939     self->default_call = value;
940
941     return 0;
942 }
943
944 static PyObject *
945 Journal_get_call_dict(Journal *self, void *closure)
946 {
947     Py_INCREF(self->call_dict);
948     return self->call_dict;
949 }
950
951 static int
952 Journal_set_call_dict(Journal *self, PyObject *value, void *closure)
953 {
954     if (value == NULL) {
955         PyErr_SetString(PyExc_TypeError, "Cannot delete call_dict");
956         return -1;
957     }
958     if (! PyDict_Check(value)) {
959         PyErr_SetString(PyExc_TypeError, "call_dict must be dict type");
960         return -1;
961     }
962     Py_DECREF(self->call_dict);
963     Py_INCREF(value);
964     self->call_dict = value;
965
966     return 0;
967 }
968
969 static PyObject *
970 Journal_get_data_threshold(Journal *self, void *closure)
971 {
972     size_t cvalue;
973     PyObject *value;
974     int r;
975
976     r = sd_journal_get_data_threshold(self->j, &cvalue);
977     if (r < 0){
978         PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold");
979         return NULL;
980     }
981
982 #if PY_MAJOR_VERSION >=3
983     value = PyLong_FromSize_t(cvalue);
984 #else
985     value = PyInt_FromSize_t(cvalue);
986 #endif
987     return value;
988 }
989
990 static int
991 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
992 {
993     if (value == NULL) {
994         PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
995         return -1;
996     }
997 #if PY_MAJOR_VERSION >=3
998     if (! PyLong_Check(value)){
999 #else
1000     if (! PyInt_Check(value)){
1001 #endif
1002         PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
1003         return -1;
1004     }
1005     int r;
1006 #if PY_MAJOR_VERSION >=3
1007     r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
1008 #else
1009     r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
1010 #endif
1011     if (r < 0){
1012         PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold");
1013         return -1;
1014     }
1015     return 0;
1016 }
1017
1018 static PyGetSetDef Journal_getseters[] = {
1019     {"data_threshold",
1020     (getter)Journal_get_data_threshold,
1021     (setter)Journal_set_data_threshold,
1022     "data threshold",
1023     NULL},
1024     {"call_dict",
1025     (getter)Journal_get_call_dict,
1026     (setter)Journal_set_call_dict,
1027     "dictionary of calls for each field",
1028     NULL},
1029     {"default_call",
1030     (getter)Journal_get_default_call,
1031     (setter)Journal_set_default_call,
1032     "default call for values for fields",
1033     NULL},
1034     {NULL}
1035 };
1036
1037 static PyMethodDef Journal_methods[] = {
1038     {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
1039     Journal_get_next__doc__},
1040     {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
1041     Journal_get_previous__doc__},
1042     {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
1043     Journal_add_match__doc__},
1044     {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
1045     Journal_add_disjunction__doc__},
1046     {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
1047     Journal_flush_matches__doc__},
1048     {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
1049     Journal_seek__doc__},
1050     {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
1051     Journal_seek_realtime__doc__},
1052     {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
1053     Journal_seek_monotonic__doc__},
1054     {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
1055     Journal_wait__doc__},
1056     {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
1057     Journal_seek_cursor__doc__},
1058 #ifdef SD_JOURNAL_FOREACH_UNIQUE
1059     {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
1060     Journal_query_unique__doc__},
1061 #endif
1062     {"log_level", (PyCFunction)Journal_log_level, METH_VARARGS,
1063     Journal_log_level__doc__},
1064     {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS,
1065     Journal_this_boot__doc__},
1066     {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS,
1067     Journal_this_machine__doc__},
1068     {NULL}  /* Sentinel */
1069 };
1070
1071 static PyTypeObject JournalType = {
1072     PyVarObject_HEAD_INIT(NULL, 0)
1073     "_reader.Journal",           /*tp_name*/
1074     sizeof(Journal),                  /*tp_basicsize*/
1075     0,                                /*tp_itemsize*/
1076     (destructor)Journal_dealloc,      /*tp_dealloc*/
1077     0,                                /*tp_print*/
1078     0,                                /*tp_getattr*/
1079     0,                                /*tp_setattr*/
1080     0,                                /*tp_compare*/
1081     0,                                /*tp_repr*/
1082     0,                                /*tp_as_number*/
1083     0,                                /*tp_as_sequence*/
1084     0,                                /*tp_as_mapping*/
1085     0,                                /*tp_hash */
1086     0,                                /*tp_call*/
1087     0,                                /*tp_str*/
1088     0,                                /*tp_getattro*/
1089     0,                                /*tp_setattro*/
1090     0,                                /*tp_as_buffer*/
1091     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
1092     Journal__doc__,                   /* tp_doc */
1093     0,                                /* tp_traverse */
1094     0,                                /* tp_clear */
1095     0,                                /* tp_richcompare */
1096     0,                                /* tp_weaklistoffset */
1097     Journal_iter,                     /* tp_iter */
1098     Journal_iternext,                 /* tp_iternext */
1099     Journal_methods,                  /* tp_methods */
1100     0,                                /* tp_members */
1101     Journal_getseters,                /* tp_getset */
1102     0,                                /* tp_base */
1103     0,                                /* tp_dict */
1104     0,                                /* tp_descr_get */
1105     0,                                /* tp_descr_set */
1106     0,                                /* tp_dictoffset */
1107     (initproc)Journal_init,           /* tp_init */
1108     0,                                /* tp_alloc */
1109     Journal_new,                      /* tp_new */
1110 };
1111
1112 #if PY_MAJOR_VERSION >= 3
1113 static PyModuleDef _reader_module = {
1114     PyModuleDef_HEAD_INIT,
1115     "_reader",
1116     "Module that reads systemd journal similar to journalctl.",
1117     -1,
1118     NULL, NULL, NULL, NULL, NULL
1119 };
1120 #endif
1121
1122 PyMODINIT_FUNC
1123 #if PY_MAJOR_VERSION >= 3
1124 PyInit__reader(void)
1125 #else
1126 init_reader(void) 
1127 #endif
1128 {
1129     PyObject* m;
1130
1131     PyDateTime_IMPORT;
1132
1133     if (PyType_Ready(&JournalType) < 0)
1134 #if PY_MAJOR_VERSION >= 3
1135         return NULL;
1136 #else
1137         return;
1138 #endif
1139
1140 #if PY_MAJOR_VERSION >= 3
1141     m = PyModule_Create(&_reader_module);
1142     if (m == NULL)
1143         return NULL;
1144 #else
1145     m = Py_InitModule3("_reader", NULL,
1146                    "Module that reads systemd journal similar to journalctl.");
1147     if (m == NULL)
1148         return;
1149 #endif
1150
1151     Py_INCREF(&JournalType);
1152     PyModule_AddObject(m, "Journal", (PyObject *)&JournalType);
1153     PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
1154     PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
1155     PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
1156     PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
1157     PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
1158     PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
1159
1160 #if PY_MAJOR_VERSION >= 3
1161     return m;
1162 #endif
1163 }