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