chiark / gitweb /
systemd-python: Tidy up _reader error handling
[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 } Journal;
29 static PyTypeObject JournalType;
30
31 static void
32 Journal_dealloc(Journal* self)
33 {
34     sd_journal_close(self->j);
35     Py_TYPE(self)->tp_free((PyObject*)self);
36 }
37
38 PyDoc_STRVAR(Journal__doc__,
39 "Journal([flags][,path]) -> ...\n"
40 "Journal instance\n\n"
41 "Returns instance of Journal, which allows filtering and return\n"
42 "of journal entries.\n"
43 "Argument `flags` sets open flags of the journal, which can be one\n"
44 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
45 "journal on local machine only; RUNTIME_ONLY opens only\n"
46 "volatile journal files; and SYSTEM_ONLY opens only\n"
47 "journal files of system services and the kernel.\n"
48 "Argument `path` is the directory of journal files. Note that\n"
49 "currently flags are ignored when `path` is present as they are\n"
50 " not relevant.");
51 static int
52 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
53 {
54     int flags=SD_JOURNAL_LOCAL_ONLY;
55     char *path=NULL;
56
57     static char *kwlist[] = {"flags", "path", NULL};
58     if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist,
59                                       &flags, &path))
60         return 1;
61
62     int r;
63     Py_BEGIN_ALLOW_THREADS
64     if (path) {
65         r = sd_journal_open_directory(&self->j, path, 0);
66     }else{
67         r = sd_journal_open(&self->j, flags);
68     }
69     Py_END_ALLOW_THREADS
70     if (r < 0) {
71         errno = -r;
72         PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
73                             r == -ENOMEM ? PyExc_MemoryError :
74                             PyExc_OSError;
75         PyErr_SetFromErrnoWithFilename(errtype, path);
76         return -1;
77     }
78
79     return 0;
80 }
81
82 PyDoc_STRVAR(Journal_get_next__doc__,
83 "get_next([skip]) -> dict\n\n"
84 "Return dictionary of the next log entry. Optional skip value will\n"
85 "return the `skip`th log entry.");
86 static PyObject *
87 Journal_get_next(Journal *self, PyObject *args)
88 {
89     int64_t skip=1LL;
90     if (! PyArg_ParseTuple(args, "|L", &skip))
91         return NULL;
92
93     if (skip == 0LL) {
94         PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
95         return NULL;
96     }
97
98     int r;
99     Py_BEGIN_ALLOW_THREADS
100     if (skip == 1LL) {
101         r = sd_journal_next(self->j);
102     }else if (skip == -1LL) {
103         r = sd_journal_previous(self->j);
104     }else if (skip > 1LL) {
105         r = sd_journal_next_skip(self->j, skip);
106     }else if (skip < -1LL) {
107         r = sd_journal_previous_skip(self->j, -skip);
108     }
109     Py_END_ALLOW_THREADS
110
111     if (r < 0) {
112         errno = -r;
113         PyErr_SetFromErrno(PyExc_OSError);
114         return NULL;
115     }else if ( r == 0) { //EOF
116         return PyDict_New();
117     }
118
119     PyObject *dict;
120     dict = PyDict_New();
121
122     const void *msg;
123     size_t msg_len;
124     const char *delim_ptr;
125     PyObject *key, *value, *cur_value, *tmp_list;
126
127     SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
128         delim_ptr = memchr(msg, '=', msg_len);
129 #if PY_MAJOR_VERSION >=3
130         key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
131 #else
132         key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
133 #endif
134         value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
135         if (PyDict_Contains(dict, key)) {
136             cur_value = PyDict_GetItem(dict, key);
137             if (PyList_CheckExact(cur_value)) {
138                 PyList_Append(cur_value, value);
139             }else{
140                 tmp_list = PyList_New(0);
141                 PyList_Append(tmp_list, cur_value);
142                 PyList_Append(tmp_list, value);
143                 PyDict_SetItem(dict, key, tmp_list);
144                 Py_DECREF(tmp_list);
145             }
146         }else{
147             PyDict_SetItem(dict, key, value);
148         }
149         Py_DECREF(key);
150         Py_DECREF(value);
151     }
152
153     uint64_t realtime;
154     if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
155         char realtime_str[20];
156         sprintf(realtime_str, "%llu", (long long unsigned) realtime);
157
158 #if PY_MAJOR_VERSION >=3
159         key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
160 #else
161         key = PyString_FromString("__REALTIME_TIMESTAMP");
162 #endif
163         value = PyBytes_FromString(realtime_str);
164         PyDict_SetItem(dict, key, value);
165         Py_DECREF(key);
166         Py_DECREF(value);
167     }
168
169     sd_id128_t sd_id;
170     uint64_t monotonic;
171     if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
172         char monotonic_str[20];
173         sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
174 #if PY_MAJOR_VERSION >=3
175         key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
176 #else
177         key = PyString_FromString("__MONOTONIC_TIMESTAMP");
178 #endif
179         value = PyBytes_FromString(monotonic_str);
180
181         PyDict_SetItem(dict, key, value);
182         Py_DECREF(key);
183         Py_DECREF(value);
184     }
185
186     char *cursor;
187     if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
188 #if PY_MAJOR_VERSION >=3
189         key = PyUnicode_FromString("__CURSOR");
190 #else
191         key = PyString_FromString("__CURSOR");
192 #endif
193         value = PyBytes_FromString(cursor);
194         PyDict_SetItem(dict, key, value);
195         free(cursor);
196         Py_DECREF(key);
197         Py_DECREF(value);
198     }
199
200     return dict;
201 }
202
203 PyDoc_STRVAR(Journal_get_previous__doc__,
204 "get_previous([skip]) -> dict\n\n"
205 "Return dictionary of the previous log entry. Optional skip value\n"
206 "will return the -`skip`th log entry. Equivalent to get_next(-skip).");
207 static PyObject *
208 Journal_get_previous(Journal *self, PyObject *args)
209 {
210     int64_t skip=1LL;
211     if (! PyArg_ParseTuple(args, "|L", &skip))
212         return NULL;
213
214     return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip);
215 }
216
217 PyDoc_STRVAR(Journal_add_match__doc__,
218 "add_match(match, ..., field=value, ...) -> None\n\n"
219 "Add a match to filter journal log entries. All matches of different\n"
220 "field are combined in logical AND, and matches of the same field\n"
221 "are automatically combined in logical OR.\n"
222 "Matches can be passed as strings \"field=value\", or keyword\n"
223 "arguments field=\"value\".");
224 static PyObject *
225 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
226 {
227     Py_ssize_t arg_match_len;
228     char *arg_match;
229     int i, r;
230     for (i = 0; i < PySequence_Size(args); i++) {
231 #if PY_MAJOR_VERSION >=3
232         PyObject *arg;
233         arg = PySequence_Fast_GET_ITEM(args, i);
234         if (PyUnicode_Check(arg)) {
235 #if PY_MINOR_VERSION >=3
236             arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
237 #else
238             PyObject *temp;
239             temp = PyUnicode_AsUTF8String(arg);
240             PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
241             Py_DECREF(temp);
242 #endif
243         }else if (PyBytes_Check(arg)) {
244             PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
245         }else{
246             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
247         }
248 #else
249         PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
250 #endif
251         if (PyErr_Occurred())
252             return NULL;
253         r = sd_journal_add_match(self->j, arg_match, arg_match_len);
254         if (r < 0) {
255             errno = -r;
256             PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
257                                 r == -ENOMEM ? PyExc_MemoryError :
258                                 PyExc_OSError;
259             PyErr_SetFromErrno(errtype);
260             return NULL;
261         }
262     }
263
264     if (! keywds)
265         Py_RETURN_NONE;
266
267     PyObject *key, *value;
268     Py_ssize_t pos=0, match_key_len, match_value_len;
269     int match_len;
270     char *match_key, *match_value;
271     void *match;
272     while (PyDict_Next(keywds, &pos, &key, &value)) {
273 #if PY_MAJOR_VERSION >=3
274         if (PyUnicode_Check(key)) {
275 #if PY_MINOR_VERSION >=3
276             match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
277 #else
278             PyObject *temp2;
279             temp2 = PyUnicode_AsUTF8String(key);
280             PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
281             Py_DECREF(temp2);
282 #endif
283         }else if (PyBytes_Check(key)) {
284             PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
285         }else{
286             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
287         }
288         if (PyUnicode_Check(value)) {
289 #if PY_MINOR_VERSION >=3
290             match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
291 #else
292             PyObject *temp3;
293             temp3 = PyUnicode_AsUTF8String(value);
294             PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
295             Py_DECREF(temp3);
296 #endif
297         }else if (PyBytes_Check(value)) {
298             PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
299         }else{
300             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
301         }
302 #else
303         PyString_AsStringAndSize(key, &match_key, &match_key_len);
304         PyString_AsStringAndSize(value, &match_value, &match_value_len);
305 #endif
306         if (PyErr_Occurred())
307             return NULL;
308
309         match_len = match_key_len + 1 + match_value_len;
310         match = malloc(match_len);
311         memcpy(match, match_key, match_key_len);
312         memcpy(match + match_key_len, "=", 1);
313         memcpy(match + match_key_len + 1, match_value, match_value_len);
314
315         r = sd_journal_add_match(self->j, match, match_len);
316         free(match);
317         if (r == -EINVAL) {
318             PyErr_SetString(PyExc_ValueError, "Invalid match");
319             return NULL;
320         }else if (r == -ENOMEM) {
321             PyErr_SetString(PyExc_MemoryError, "Not enough memory");
322             return NULL;
323         }else if (r < 0) {
324             PyErr_SetString(PyExc_RuntimeError, "Error adding match");
325             return NULL;
326         }
327     }
328
329     Py_RETURN_NONE;
330 }
331
332 PyDoc_STRVAR(Journal_add_disjunction__doc__,
333 "add_disjunction() -> None\n\n"
334 "Once called, all matches before and after are combined in logical\n"
335 "OR.");
336 static PyObject *
337 Journal_add_disjunction(Journal *self, PyObject *args)
338 {
339     int r;
340     r = sd_journal_add_disjunction(self->j);
341     if (r < 0) {
342         errno = -r;
343         PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
344                             PyExc_OSError;
345         PyErr_SetFromErrno(errtype);
346         return NULL;
347     }
348     Py_RETURN_NONE;
349 }
350
351 PyDoc_STRVAR(Journal_flush_matches__doc__,
352 "flush_matches() -> None\n\n"
353 "Clears all current match filters.");
354 static PyObject *
355 Journal_flush_matches(Journal *self, PyObject *args)
356 {
357     sd_journal_flush_matches(self->j);
358     Py_RETURN_NONE;
359 }
360
361 PyDoc_STRVAR(Journal_seek__doc__,
362 "seek(offset[, whence]) -> None\n\n"
363 "Seek through journal by `offset` number of entries. Argument\n"
364 "`whence` defines what the offset is relative to:\n"
365 "os.SEEK_SET (default) from first match in journal;\n"
366 "os.SEEK_CUR from current position in journal;\n"
367 "and os.SEEK_END is from last match in journal.");
368 static PyObject *
369 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
370 {
371     int64_t offset;
372     int whence=SEEK_SET;
373     static char *kwlist[] = {"offset", "whence", NULL};
374
375     if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
376                                       &offset, &whence))
377         return NULL;
378
379     PyObject *result=NULL;
380     if (whence == SEEK_SET){
381         int r;
382         Py_BEGIN_ALLOW_THREADS
383         r = sd_journal_seek_head(self->j);
384         Py_END_ALLOW_THREADS
385         if (r < 0) {
386             errno = -r;
387             PyErr_SetFromErrno(PyExc_OSError);
388             return NULL;
389         }
390         if (offset > 0LL) {
391             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
392         }
393     }else if (whence == SEEK_CUR){
394         result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
395     }else if (whence == SEEK_END){
396         int r;
397         Py_BEGIN_ALLOW_THREADS
398         r = sd_journal_seek_tail(self->j);
399         Py_END_ALLOW_THREADS
400         if (r < 0) {
401             errno = -r;
402             PyErr_SetFromErrno(PyExc_OSError);
403             return NULL;
404         }
405         if (offset < 0LL) {
406             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
407         }else{
408             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
409         }
410     }else{
411         PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
412     }
413
414     if (result)
415         Py_DECREF(result);
416     if (PyErr_Occurred())
417         return NULL;
418     Py_RETURN_NONE;
419 }
420
421 PyDoc_STRVAR(Journal_seek_realtime__doc__,
422 "seek_realtime(realtime) -> None\n\n"
423 "Seek to nearest matching journal entry to `realtime`. Argument\n"
424 "`realtime` can be an integer unix timestamp in usecs or a "
425 "datetime instance.");
426 static PyObject *
427 Journal_seek_realtime(Journal *self, PyObject *args)
428 {
429     PyObject *arg;
430     if (! PyArg_ParseTuple(args, "O", &arg))
431         return NULL;
432
433     uint64_t timestamp=-1LL;
434     if (PyDateTime_Check(arg)) {
435         PyObject *temp;
436         char *timestamp_str;
437         temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f");
438 #if PY_MAJOR_VERSION >=3
439         PyObject *temp2;
440         temp2 = PyUnicode_AsUTF8String(temp);
441         timestamp_str = PyBytes_AsString(temp2);
442         Py_DECREF(temp2);
443 #else
444         timestamp_str = PyString_AsString(temp);
445 #endif
446         Py_DECREF(temp);
447         timestamp = strtoull(timestamp_str, NULL, 10);
448     }else if (PyLong_Check(arg)) {
449         timestamp = PyLong_AsUnsignedLongLong(arg);
450 #if PY_MAJOR_VERSION <3
451     }else if (PyInt_Check(arg)) {
452         timestamp = PyInt_AsUnsignedLongLongMask(arg);
453 #endif
454     }
455     if ((int64_t) timestamp < 0LL) {
456         PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance");
457         return NULL;
458     }
459
460     int r;
461     Py_BEGIN_ALLOW_THREADS
462     r = sd_journal_seek_realtime_usec(self->j, timestamp);
463     Py_END_ALLOW_THREADS
464     if (r < 0) {
465         errno = -r;
466         PyErr_SetFromErrno(PyExc_OSError);
467         return NULL;
468     }
469     Py_RETURN_NONE;
470 }
471
472 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
473 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
474 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
475 "`monotonic` is an timestamp from boot in secs, or a\n"
476 "timedelta instance.\n"
477 "Argument `bootid` is a string representing which boot the\n"
478 "monotonic time is reference to. Defaults to current bootid.");
479 static PyObject *
480 Journal_seek_monotonic(Journal *self, PyObject *args)
481 {
482     PyObject *arg;
483     char *bootid=NULL;
484     if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid))
485         return NULL;
486
487     uint64_t timestamp=-1LL;
488     if PyDelta_Check(arg) {
489         PyObject *temp;
490         temp = PyObject_CallMethod(arg, "total_seconds", NULL);
491         timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6);
492         Py_DECREF(temp);
493     }else if (PyFloat_Check(arg)) {
494         timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6);
495     }else if (PyLong_Check(arg)) {
496         timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6;
497 #if PY_MAJOR_VERSION <3
498     }else if (PyInt_Check(arg)) {
499         timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6;
500 #endif
501
502     }
503
504     if ((int64_t) timestamp < 0LL) {
505         PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance");
506         return NULL;
507     }
508
509     sd_id128_t sd_id;
510     int r;
511     if (bootid) {
512         r = sd_id128_from_string(bootid, &sd_id);
513         if (r == -EINVAL) {
514             PyErr_SetString(PyExc_ValueError, "Invalid bootid");
515             return NULL;
516         }else if (r < 0) {
517             errno = -r;
518             PyErr_SetFromErrno(PyExc_OSError);
519             return NULL;
520         }
521     }else{
522         r = sd_id128_get_boot(&sd_id);
523         if (r == -EIO) {
524             PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
525             return NULL;
526         }else if (r < 0) {
527             errno = -r;
528             PyErr_SetFromErrno(PyExc_OSError);
529             return NULL;
530         }
531     }
532
533     Py_BEGIN_ALLOW_THREADS
534     r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
535     Py_END_ALLOW_THREADS
536     if (r < 0) {
537         errno = -r;
538         PyErr_SetFromErrno(PyExc_OSError);
539         return NULL;
540     }
541     Py_RETURN_NONE;
542 }
543  
544 PyDoc_STRVAR(Journal_wait__doc__,
545 "wait([timeout]) -> Change state (integer)\n\n"
546 "Waits until there is a change in the journal. Argument `timeout`\n"
547 "is the maximum number of seconds to wait before returning\n"
548 "regardless if journal has changed. If `timeout` is not given or is\n"
549 "0, then it will block forever.\n"
550 "Will return constants: NOP if no change; APPEND if new\n"
551 "entries have been added to the end of the journal; and\n"
552 "INVALIDATE if journal files have been added or removed.");
553 static PyObject *
554 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
555 {
556     int64_t timeout=0LL;
557     if (! PyArg_ParseTuple(args, "|L", &timeout))
558         return NULL;
559
560     int r;
561     Py_BEGIN_ALLOW_THREADS
562     if ( timeout == 0LL) {
563         r = sd_journal_wait(self->j, (uint64_t) -1);
564     }else{
565         r = sd_journal_wait(self->j, timeout * 1E6);
566     }
567     Py_END_ALLOW_THREADS
568     if (r < 0) {
569         errno = -r;
570         PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError :
571                             PyExc_OSError;
572         PyErr_SetFromErrno(errtype);
573         return NULL;
574     }
575 #if PY_MAJOR_VERSION >=3
576     return PyLong_FromLong(r);
577 #else
578     return PyInt_FromLong(r);
579 #endif
580 }
581
582 PyDoc_STRVAR(Journal_seek_cursor__doc__,
583 "seek_cursor(cursor) -> None\n\n"
584 "Seeks to journal entry by given unique reference `cursor`.");
585 static PyObject *
586 Journal_seek_cursor(Journal *self, PyObject *args)
587 {
588     const char *cursor;
589     if (! PyArg_ParseTuple(args, "s", &cursor))
590         return NULL;
591
592     int r;
593     Py_BEGIN_ALLOW_THREADS
594     r = sd_journal_seek_cursor(self->j, cursor);
595     Py_END_ALLOW_THREADS
596     if (r < 0) {
597         errno = -r;
598         PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
599                             r == -ENOMEM ? PyExc_MemoryError :
600                             PyExc_OSError;
601         PyErr_SetFromErrno(errtype);
602         return NULL;
603     }
604     Py_RETURN_NONE;
605 }
606
607 static PyObject *
608 Journal_iter(PyObject *self)
609 {
610     Py_INCREF(self);
611     return self;
612 }
613
614 static PyObject *
615 Journal_iternext(PyObject *self)
616 {
617     PyObject *dict;
618     Py_ssize_t dict_size;
619
620     dict = PyObject_CallMethod(self, "get_next", "");
621     dict_size = PyDict_Size(dict);
622     if ((int64_t) dict_size > 0LL) {
623         return dict;
624     }else{
625         Py_DECREF(dict);
626         PyErr_SetNone(PyExc_StopIteration);
627         return NULL;
628     }
629 }
630
631 #ifdef SD_JOURNAL_FOREACH_UNIQUE
632 PyDoc_STRVAR(Journal_query_unique__doc__,
633 "query_unique(field) -> a set of values\n\n"
634 "Returns a set of unique values in journal for given `field`.\n"
635 "Note this does not respect any journal matches.");
636 static PyObject *
637 Journal_query_unique(Journal *self, PyObject *args)
638 {
639     char *query;
640     if (! PyArg_ParseTuple(args, "s", &query))
641         return NULL;
642
643     int r;
644     Py_BEGIN_ALLOW_THREADS
645     r = sd_journal_query_unique(self->j, query);
646     Py_END_ALLOW_THREADS
647     if (r < 0) {
648         errno = -r;
649         PyObject *errtype = r == -EINVAL ? PyExc_ValueError :
650                             r == -ENOMEM ? PyExc_MemoryError :
651                             PyExc_OSError;
652         PyErr_SetFromErrno(errtype);
653         return NULL;
654     }
655
656     const void *uniq;
657     size_t uniq_len;
658     const char *delim_ptr;
659     PyObject *value_set, *key, *value;
660     value_set = PySet_New(0);
661
662 #if PY_MAJOR_VERSION >=3
663     key = PyUnicode_FromString(query);
664 #else
665     key = PyString_FromString(query);
666 #endif
667
668     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
669         delim_ptr = memchr(uniq, '=', uniq_len);
670         value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
671         PySet_Add(value_set, value);
672         Py_DECREF(value);
673     }
674     Py_DECREF(key);
675     return value_set;
676 }
677 #endif //def SD_JOURNAL_FOREACH_UNIQUE
678
679 static PyObject *
680 Journal_get_data_threshold(Journal *self, void *closure)
681 {
682     size_t cvalue;
683     PyObject *value;
684     int r;
685
686     r = sd_journal_get_data_threshold(self->j, &cvalue);
687     if (r < 0) {
688         errno = -r;
689         PyErr_SetFromErrno(PyExc_OSError);
690         return NULL;
691     }
692
693 #if PY_MAJOR_VERSION >=3
694     value = PyLong_FromSize_t(cvalue);
695 #else
696     value = PyInt_FromSize_t(cvalue);
697 #endif
698     return value;
699 }
700
701 static int
702 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
703 {
704     if (value == NULL) {
705         PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
706         return -1;
707     }
708 #if PY_MAJOR_VERSION >=3
709     if (! PyLong_Check(value)){
710 #else
711     if (! PyInt_Check(value)){
712 #endif
713         PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
714         return -1;
715     }
716     int r;
717 #if PY_MAJOR_VERSION >=3
718     r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
719 #else
720     r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
721 #endif
722     if (r < 0) {
723         errno = -r;
724         PyErr_SetFromErrno(PyExc_OSError);
725         return NULL;
726     }
727     return 0;
728 }
729
730 static PyGetSetDef Journal_getseters[] = {
731     {"data_threshold",
732     (getter)Journal_get_data_threshold,
733     (setter)Journal_set_data_threshold,
734     "data threshold",
735     NULL},
736     {NULL}
737 };
738
739 static PyMethodDef Journal_methods[] = {
740     {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
741     Journal_get_next__doc__},
742     {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
743     Journal_get_previous__doc__},
744     {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
745     Journal_add_match__doc__},
746     {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
747     Journal_add_disjunction__doc__},
748     {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
749     Journal_flush_matches__doc__},
750     {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
751     Journal_seek__doc__},
752     {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
753     Journal_seek_realtime__doc__},
754     {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
755     Journal_seek_monotonic__doc__},
756     {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
757     Journal_wait__doc__},
758     {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
759     Journal_seek_cursor__doc__},
760 #ifdef SD_JOURNAL_FOREACH_UNIQUE
761     {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
762     Journal_query_unique__doc__},
763 #endif
764     {NULL}  /* Sentinel */
765 };
766
767 static PyTypeObject JournalType = {
768     PyVarObject_HEAD_INIT(NULL, 0)
769     "_reader.Journal",           /*tp_name*/
770     sizeof(Journal),                  /*tp_basicsize*/
771     0,                                /*tp_itemsize*/
772     (destructor)Journal_dealloc,      /*tp_dealloc*/
773     0,                                /*tp_print*/
774     0,                                /*tp_getattr*/
775     0,                                /*tp_setattr*/
776     0,                                /*tp_compare*/
777     0,                                /*tp_repr*/
778     0,                                /*tp_as_number*/
779     0,                                /*tp_as_sequence*/
780     0,                                /*tp_as_mapping*/
781     0,                                /*tp_hash */
782     0,                                /*tp_call*/
783     0,                                /*tp_str*/
784     0,                                /*tp_getattro*/
785     0,                                /*tp_setattro*/
786     0,                                /*tp_as_buffer*/
787     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
788     Journal__doc__,                   /* tp_doc */
789     0,                                /* tp_traverse */
790     0,                                /* tp_clear */
791     0,                                /* tp_richcompare */
792     0,                                /* tp_weaklistoffset */
793     Journal_iter,                     /* tp_iter */
794     Journal_iternext,                 /* tp_iternext */
795     Journal_methods,                  /* tp_methods */
796     0,                                /* tp_members */
797     Journal_getseters,                /* tp_getset */
798     0,                                /* tp_base */
799     0,                                /* tp_dict */
800     0,                                /* tp_descr_get */
801     0,                                /* tp_descr_set */
802     0,                                /* tp_dictoffset */
803     (initproc)Journal_init,           /* tp_init */
804     0,                                /* tp_alloc */
805     PyType_GenericNew,                /* tp_new */
806 };
807
808 #if PY_MAJOR_VERSION >= 3
809 static PyModuleDef _reader_module = {
810     PyModuleDef_HEAD_INIT,
811     "_reader",
812     "Module that reads systemd journal similar to journalctl.",
813     -1,
814     NULL, NULL, NULL, NULL, NULL
815 };
816 #endif
817
818 PyMODINIT_FUNC
819 #if PY_MAJOR_VERSION >= 3
820 PyInit__reader(void)
821 #else
822 init_reader(void) 
823 #endif
824 {
825     PyObject* m;
826
827     PyDateTime_IMPORT;
828
829     if (PyType_Ready(&JournalType) < 0)
830 #if PY_MAJOR_VERSION >= 3
831         return NULL;
832 #else
833         return;
834 #endif
835
836 #if PY_MAJOR_VERSION >= 3
837     m = PyModule_Create(&_reader_module);
838     if (m == NULL)
839         return NULL;
840 #else
841     m = Py_InitModule3("_reader", NULL,
842                    "Module that reads systemd journal similar to journalctl.");
843     if (m == NULL)
844         return;
845 #endif
846
847     Py_INCREF(&JournalType);
848     PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
849     PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
850     PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
851     PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
852     PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
853     PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
854     PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
855
856 #if PY_MAJOR_VERSION >= 3
857     return m;
858 #endif
859 }