chiark / gitweb /
c128cf3c1475ca8f08c609973e1a48b756b6d8fa
[elogind.git] / src / python-systemd / _reader.c
1 /*-*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Steven Hiscocks, Zbigniew JÄ™drzejewski-Szmek
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <Python.h>
23 #include <structmember.h>
24 #include <datetime.h>
25 #include <stdio.h>
26
27 #include <systemd/sd-journal.h>
28
29 #include "pyutil.h"
30 #include "macro.h"
31 #include "util.h"
32
33 typedef struct {
34     PyObject_HEAD
35     sd_journal *j;
36 } Reader;
37 static PyTypeObject ReaderType;
38
39 static int set_error(int r, const char* path, const char* invalid_message) {
40     if (r >= 0)
41         return r;
42     if (r == -EINVAL && invalid_message)
43         PyErr_SetString(PyExc_ValueError, invalid_message);
44     else if (r == -ENOMEM)
45         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
46     else {
47         errno = -r;
48         PyErr_SetFromErrnoWithFilename(PyExc_OSError, path);
49     }
50     return -1;
51 }
52
53
54 PyDoc_STRVAR(module__doc__,
55              "Class to reads the systemd journal similar to journalctl.");
56
57
58 #if PY_MAJOR_VERSION >= 3
59 static PyTypeObject MonotonicType;
60
61 PyDoc_STRVAR(MonotonicType__doc__,
62              "A tuple of (timestamp, bootid) for holding monotonic timestamps");
63
64 static PyStructSequence_Field MonotonicType_fields[] = {
65     {(char*) "timestamp", (char*) "Time"},
66     {(char*) "bootid", (char*) "Unique identifier of the boot"},
67     {NULL, NULL}
68 };
69
70 static PyStructSequence_Desc Monotonic_desc = {
71     (char*) "journal.Monotonic",
72     MonotonicType__doc__,
73     MonotonicType_fields,
74     2,
75 };
76 #endif
77
78
79 static void Reader_dealloc(Reader* self)
80 {
81     sd_journal_close(self->j);
82     Py_TYPE(self)->tp_free((PyObject*)self);
83 }
84
85 PyDoc_STRVAR(Reader__doc__,
86              "_Reader([flags | path]) -> ...\n\n"
87              "_Reader allows filtering and retrieval of Journal entries.\n"
88              "Note: this is a low-level interface, and probably not what you\n"
89              "want, use systemd.journal.Reader instead.\n\n"
90              "Argument `flags` sets open flags of the journal, which can be one\n"
91              "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
92              "journal on local machine only; RUNTIME_ONLY opens only\n"
93              "volatile journal files; and SYSTEM_ONLY opens only\n"
94              "journal files of system services and the kernel.\n\n"
95              "Argument `path` is the directory of journal files. Note that\n"
96              "`flags` and `path` are exclusive.\n\n"
97              "_Reader implements the context manager protocol: the journal\n"
98              "will be closed when exiting the block.");
99 static int Reader_init(Reader *self, PyObject *args, PyObject *keywds)
100 {
101     int flags = 0, r;
102     char *path = NULL;
103
104     static const char* const kwlist[] = {"flags", "path", NULL};
105     if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iz", (char**) kwlist,
106                                      &flags, &path))
107         return -1;
108
109     if (!flags)
110         flags = SD_JOURNAL_LOCAL_ONLY;
111     else
112         if (path) {
113             PyErr_SetString(PyExc_ValueError, "cannot use both flags and path");
114             return -1;
115         }
116
117     Py_BEGIN_ALLOW_THREADS
118     if (path)
119         r = sd_journal_open_directory(&self->j, path, 0);
120     else
121         r = sd_journal_open(&self->j, flags);
122     Py_END_ALLOW_THREADS
123
124     return set_error(r, path, "Invalid flags or path");
125 }
126
127
128 PyDoc_STRVAR(Reader_fileno__doc__,
129              "fileno() -> int\n\n"
130              "Get a file descriptor to poll for changes in the journal.\n"
131              "This method invokes sd_journal_get_fd().\n"
132              "See man:sd_journal_get_fd(3).");
133 static PyObject* Reader_fileno(Reader *self, PyObject *args)
134 {
135     int fd;
136     fd = sd_journal_get_fd(self->j);
137     set_error(fd, NULL, NULL);
138     if (fd < 0)
139         return NULL;
140     return long_FromLong(fd);
141 }
142
143
144 PyDoc_STRVAR(Reader_reliable_fd__doc__,
145              "reliable_fd() -> bool\n\n"
146              "Returns True iff the journal can be polled reliably.\n"
147              "This method invokes sd_journal_reliable_fd().\n"
148              "See man:sd_journal_reliable_fd(3).");
149 static PyObject* Reader_reliable_fd(Reader *self, PyObject *args)
150 {
151     int r;
152     r = sd_journal_reliable_fd(self->j);
153     set_error(r, NULL, NULL);
154     if (r < 0)
155         return NULL;
156     return PyBool_FromLong(r);
157 }
158
159
160 PyDoc_STRVAR(Reader_close__doc__,
161              "close() -> None\n\n"
162              "Free resources allocated by this Reader object.\n"
163              "This method invokes sd_journal_close().\n"
164              "See man:sd_journal_close(3).");
165 static PyObject* Reader_close(Reader *self, PyObject *args)
166 {
167     assert(self);
168     assert(!args);
169
170     sd_journal_close(self->j);
171     self->j = NULL;
172     Py_RETURN_NONE;
173 }
174
175
176 PyDoc_STRVAR(Reader_get_usage__doc__,
177              "get_usage() -> int\n\n"
178              "Returns the total disk space currently used by journal"
179              "files (in bytes). If `SD_JOURNAL_LOCAL_ONLY` was"
180              "passed when opening the journal this value will only reflect"
181              "the size of journal files of the local host, otherwise"
182              "of all hosts.\n\n"
183              "This method invokes sd_journal_get_usage().\n"
184              "See man:sd_journal_get_usage(3).");
185 static PyObject* Reader_get_usage(Reader *self, PyObject *args)
186 {
187     int r;
188     uint64_t bytes;
189
190     r = sd_journal_get_usage(self->j, &bytes);
191     if (set_error(r, NULL, NULL))
192         return NULL;
193
194     assert_cc(sizeof(unsigned long long) == sizeof(bytes));
195     return PyLong_FromUnsignedLongLong(bytes);
196 }
197
198
199 PyDoc_STRVAR(Reader___enter____doc__,
200              "__enter__() -> self\n\n"
201              "Part of the context manager protocol.\n"
202              "Returns self.\n");
203 static PyObject* Reader___enter__(PyObject *self, PyObject *args)
204 {
205     assert(self);
206     assert(!args);
207
208     Py_INCREF(self);
209     return self;
210 }
211
212 PyDoc_STRVAR(Reader___exit____doc__,
213              "__exit__(type, value, traceback) -> None\n\n"
214              "Part of the context manager protocol.\n"
215              "Closes the journal.\n");
216 static PyObject* Reader___exit__(Reader *self, PyObject *args)
217 {
218     assert(self);
219
220     sd_journal_close(self->j);
221     self->j = NULL;
222     Py_RETURN_NONE;
223 }
224
225
226 PyDoc_STRVAR(Reader_next__doc__,
227              "next([skip]) -> bool\n\n"
228              "Go to the next log entry. Optional skip value means to go to\n"
229              "the `skip`\\-th log entry.\n"
230              "Returns False if at end of file, True otherwise.");
231 static PyObject* Reader_next(Reader *self, PyObject *args)
232 {
233     int64_t skip = 1LL;
234     int r;
235
236     if (!PyArg_ParseTuple(args, "|L:next", &skip))
237         return NULL;
238
239     if (skip == 0LL) {
240         PyErr_SetString(PyExc_ValueError, "skip must be nonzero");
241         return NULL;
242     }
243
244     Py_BEGIN_ALLOW_THREADS
245     if (skip == 1LL)
246         r = sd_journal_next(self->j);
247     else if (skip == -1LL)
248         r = sd_journal_previous(self->j);
249     else if (skip > 1LL)
250         r = sd_journal_next_skip(self->j, skip);
251     else if (skip < -1LL)
252         r = sd_journal_previous_skip(self->j, -skip);
253     else
254         assert_not_reached("should not be here");
255     Py_END_ALLOW_THREADS
256
257     set_error(r, NULL, NULL);
258     if (r < 0)
259         return NULL;
260     return PyBool_FromLong(r);
261 }
262
263
264 PyDoc_STRVAR(Reader_get_next__doc__,
265              "get_next([skip]) -> dict\n\n"
266              "Return dictionary of the next log entry. Optional skip value will\n"
267              "return the `skip`\\-th log entry. Returns an empty dict on EOF.");
268 static PyObject* Reader_get_next(Reader *self, PyObject *args)
269 {
270     PyObject _cleanup_Py_DECREF_ *tmp = NULL;
271     PyObject *dict;
272     const void *msg;
273     size_t msg_len;
274     int r;
275
276     tmp = Reader_next(self, args);
277     if (!tmp)
278         return NULL;
279     if (tmp == Py_False) /* EOF */
280         return PyDict_New();
281
282     dict = PyDict_New();
283     if (!dict)
284             return NULL;
285
286     SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
287         PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
288         const char *delim_ptr;
289
290         delim_ptr = memchr(msg, '=', msg_len);
291         if (!delim_ptr) {
292             PyErr_SetString(PyExc_OSError,
293                             "journal gave us a field without '='");
294             goto error;
295         }
296
297         key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
298         if (!key)
299             goto error;
300
301         value = PyBytes_FromStringAndSize(
302                 delim_ptr + 1,
303                 (const char*) msg + msg_len - (delim_ptr + 1) );
304         if (!value)
305             goto error;
306
307         if (PyDict_Contains(dict, key)) {
308             PyObject *cur_value = PyDict_GetItem(dict, key);
309
310             if (PyList_CheckExact(cur_value)) {
311                 r = PyList_Append(cur_value, value);
312                 if (r < 0)
313                     goto error;
314             } else {
315                 PyObject _cleanup_Py_DECREF_ *tmp_list = PyList_New(0);
316                 if (!tmp_list)
317                     goto error;
318
319                 r = PyList_Append(tmp_list, cur_value);
320                 if (r < 0)
321                     goto error;
322
323                 r = PyList_Append(tmp_list, value);
324                 if (r < 0)
325                     goto error;
326
327                 r = PyDict_SetItem(dict, key, tmp_list);
328                 if (r < 0)
329                     goto error;
330             }
331         } else {
332             r = PyDict_SetItem(dict, key, value);
333             if (r < 0)
334                 goto error;
335         }
336     }
337
338     return dict;
339 error:
340     Py_DECREF(dict);
341     return NULL;
342 }
343
344
345 PyDoc_STRVAR(Reader_get_realtime__doc__,
346              "get_realtime() -> int\n\n"
347              "Return the realtime timestamp for the current journal entry\n"
348              "in microseconds.\n\n"
349              "Wraps sd_journal_get_realtime_usec().\n"
350              "See man:sd_journal_get_realtime_usec(3).");
351 static PyObject* Reader_get_realtime(Reader *self, PyObject *args)
352 {
353     uint64_t timestamp;
354     int r;
355
356     assert(self);
357     assert(!args);
358
359     r = sd_journal_get_realtime_usec(self->j, &timestamp);
360     if (set_error(r, NULL, NULL))
361         return NULL;
362
363     assert_cc(sizeof(unsigned long long) == sizeof(timestamp));
364     return PyLong_FromUnsignedLongLong(timestamp);
365 }
366
367
368 PyDoc_STRVAR(Reader_get_monotonic__doc__,
369              "get_monotonic() -> (timestamp, bootid)\n\n"
370              "Return the monotonic timestamp for the current journal entry\n"
371              "as a tuple of time in microseconds and bootid.\n\n"
372              "Wraps sd_journal_get_monotonic_usec().\n"
373              "See man:sd_journal_get_monotonic_usec(3).");
374 static PyObject* Reader_get_monotonic(Reader *self, PyObject *args)
375 {
376     uint64_t timestamp;
377     sd_id128_t id;
378     PyObject *monotonic, *bootid, *tuple;
379     int r;
380
381     assert(self);
382     assert(!args);
383
384     r = sd_journal_get_monotonic_usec(self->j, &timestamp, &id);
385     if (set_error(r, NULL, NULL))
386         return NULL;
387
388     assert_cc(sizeof(unsigned long long) == sizeof(timestamp));
389     monotonic = PyLong_FromUnsignedLongLong(timestamp);
390     bootid = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes));
391 #if PY_MAJOR_VERSION >= 3
392     tuple = PyStructSequence_New(&MonotonicType);
393 #else
394     tuple = PyTuple_New(2);
395 #endif
396     if (!monotonic || !bootid || !tuple) {
397         Py_XDECREF(monotonic);
398         Py_XDECREF(bootid);
399         Py_XDECREF(tuple);
400         return NULL;
401     }
402
403 #if PY_MAJOR_VERSION >= 3
404     PyStructSequence_SET_ITEM(tuple, 0, monotonic);
405     PyStructSequence_SET_ITEM(tuple, 1, bootid);
406 #else
407     PyTuple_SET_ITEM(tuple, 0, monotonic);
408     PyTuple_SET_ITEM(tuple, 1, bootid);
409 #endif
410
411     return tuple;
412 }
413
414
415 PyDoc_STRVAR(Reader_get_previous__doc__,
416              "get_previous([skip]) -> dict\n\n"
417              "Return dictionary of the previous log entry. Optional skip value\n"
418              "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip).");
419 static PyObject* Reader_get_previous(Reader *self, PyObject *args)
420 {
421     int64_t skip = 1LL;
422     if (!PyArg_ParseTuple(args, "|L:get_previous", &skip))
423         return NULL;
424
425     return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
426                                (char*) "L", -skip);
427 }
428
429
430 PyDoc_STRVAR(Reader_add_match__doc__,
431              "add_match(match) -> None\n\n"
432              "Add a match to filter journal log entries. All matches of different\n"
433              "fields are combined with logical AND, and matches of the same field\n"
434              "are automatically combined with logical OR.\n"
435              "Match is a string of the form \"FIELD=value\".");
436 static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds)
437 {
438     char *match;
439     int match_len, r;
440     if (!PyArg_ParseTuple(args, "s#:add_match", &match, &match_len))
441         return NULL;
442
443     r = sd_journal_add_match(self->j, match, match_len);
444     set_error(r, NULL, "Invalid match");
445     if (r < 0)
446             return NULL;
447
448     Py_RETURN_NONE;
449 }
450
451
452 PyDoc_STRVAR(Reader_add_disjunction__doc__,
453              "add_disjunction() -> None\n\n"
454              "Inserts a logical OR between matches added before and afterwards.");
455 static PyObject* Reader_add_disjunction(Reader *self, PyObject *args)
456 {
457     int r;
458     r = sd_journal_add_disjunction(self->j);
459     set_error(r, NULL, NULL);
460     if (r < 0)
461         return NULL;
462     Py_RETURN_NONE;
463 }
464
465
466 PyDoc_STRVAR(Reader_flush_matches__doc__,
467              "flush_matches() -> None\n\n"
468              "Clear all current match filters.");
469 static PyObject* Reader_flush_matches(Reader *self, PyObject *args)
470 {
471     sd_journal_flush_matches(self->j);
472     Py_RETURN_NONE;
473 }
474
475
476 PyDoc_STRVAR(Reader_seek_head__doc__,
477              "seek_head() -> None\n\n"
478              "Jump to the beginning of the journal.\n"
479              "This method invokes sd_journal_seek_head().\n"
480              "See man:sd_journal_seek_head(3).");
481 static PyObject* Reader_seek_head(Reader *self, PyObject *args)
482 {
483     int r;
484     Py_BEGIN_ALLOW_THREADS
485     r = sd_journal_seek_head(self->j);
486     Py_END_ALLOW_THREADS
487     if (set_error(r, NULL, NULL))
488         return NULL;
489     Py_RETURN_NONE;
490 }
491
492
493 PyDoc_STRVAR(Reader_seek_tail__doc__,
494              "seek_tail() -> None\n\n"
495              "Jump to the end of the journal.\n"
496              "This method invokes sd_journal_seek_tail().\n"
497              "See man:sd_journal_seek_tail(3).");
498 static PyObject* Reader_seek_tail(Reader *self, PyObject *args)
499 {
500     int r;
501     Py_BEGIN_ALLOW_THREADS
502     r = sd_journal_seek_tail(self->j);
503     Py_END_ALLOW_THREADS
504     if (set_error(r, NULL, NULL))
505         return NULL;
506     Py_RETURN_NONE;
507 }
508
509
510 PyDoc_STRVAR(Reader_seek_realtime__doc__,
511              "seek_realtime(realtime) -> None\n\n"
512              "Seek to nearest matching journal entry to `realtime`. Argument\n"
513              "`realtime` in specified in seconds.");
514 static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
515 {
516     uint64_t timestamp;
517     int r;
518
519     if (!PyArg_ParseTuple(args, "K:seek_realtime", &timestamp))
520         return NULL;
521
522     Py_BEGIN_ALLOW_THREADS
523     r = sd_journal_seek_realtime_usec(self->j, timestamp);
524     Py_END_ALLOW_THREADS
525     if (set_error(r, NULL, NULL))
526         return NULL;
527     Py_RETURN_NONE;
528 }
529
530
531 PyDoc_STRVAR(Reader_seek_monotonic__doc__,
532              "seek_monotonic(monotonic[, bootid]) -> None\n\n"
533              "Seek to nearest matching journal entry to `monotonic`. Argument\n"
534              "`monotonic` is an timestamp from boot in microseconds.\n"
535              "Argument `bootid` is a string representing which boot the\n"
536              "monotonic time is reference to. Defaults to current bootid.");
537 static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args)
538 {
539     char *bootid = NULL;
540     uint64_t timestamp;
541     sd_id128_t id;
542     int r;
543
544     if (!PyArg_ParseTuple(args, "K|z:seek_monotonic", &timestamp, &bootid))
545         return NULL;
546
547     if (bootid) {
548         r = sd_id128_from_string(bootid, &id);
549         if (set_error(r, NULL, "Invalid bootid"))
550             return NULL;
551     } else {
552         Py_BEGIN_ALLOW_THREADS
553         r = sd_id128_get_boot(&id);
554         Py_END_ALLOW_THREADS
555         if (set_error(r, NULL, NULL))
556             return NULL;
557     }
558
559     Py_BEGIN_ALLOW_THREADS
560     r = sd_journal_seek_monotonic_usec(self->j, id, timestamp);
561     Py_END_ALLOW_THREADS
562     if (set_error(r, NULL, NULL))
563         return NULL;
564
565     Py_RETURN_NONE;
566 }
567
568
569 PyDoc_STRVAR(Reader_wait__doc__,
570              "wait([timeout]) -> state change (integer)\n\n"
571              "Wait for a change in the journal. Argument `timeout` specifies\n"
572              "the maximum number of microseconds to wait before returning\n"
573              "regardless of wheter the journal has changed. If `timeout` is -1,\n"
574              "then block forever.\n\n"
575              "Will return constants: NOP if no change; APPEND if new\n"
576              "entries have been added to the end of the journal; and\n"
577              "INVALIDATE if journal files have been added or removed.");
578 static PyObject* Reader_wait(Reader *self, PyObject *args)
579 {
580     int r;
581     int64_t timeout;
582
583     if (!PyArg_ParseTuple(args, "|L:wait", &timeout))
584         return NULL;
585
586     Py_BEGIN_ALLOW_THREADS
587     r = sd_journal_wait(self->j, timeout);
588     Py_END_ALLOW_THREADS
589     if (set_error(r, NULL, NULL) < 0)
590         return NULL;
591
592     return long_FromLong(r);
593 }
594
595
596 PyDoc_STRVAR(Reader_seek_cursor__doc__,
597              "seek_cursor(cursor) -> None\n\n"
598              "Seek to journal entry by given unique reference `cursor`.");
599 static PyObject* Reader_seek_cursor(Reader *self, PyObject *args)
600 {
601     const char *cursor;
602     int r;
603
604     if (!PyArg_ParseTuple(args, "s:seek_cursor", &cursor))
605         return NULL;
606
607     Py_BEGIN_ALLOW_THREADS
608     r = sd_journal_seek_cursor(self->j, cursor);
609     Py_END_ALLOW_THREADS
610     if (set_error(r, NULL, "Invalid cursor"))
611         return NULL;
612     Py_RETURN_NONE;
613 }
614
615
616 PyDoc_STRVAR(Reader_get_cursor__doc__,
617              "get_cursor() -> str\n\n"
618              "Return a cursor string for the current journal entry.\n\n"
619              "Wraps sd_journal_get_cursor(). See man:sd_journal_get_cursor(3).");
620 static PyObject* Reader_get_cursor(Reader *self, PyObject *args)
621 {
622     char _cleanup_free_ *cursor = NULL;
623     int r;
624
625     assert(self);
626     assert(!args);
627
628     r = sd_journal_get_cursor(self->j, &cursor);
629     if (set_error(r, NULL, NULL))
630         return NULL;
631
632     return unicode_FromString(cursor);
633 }
634
635
636 PyDoc_STRVAR(Reader_test_cursor__doc__,
637              "test_cursor(str) -> bool\n\n"
638              "Test whether the cursor string matches current journal entry.\n\n"
639              "Wraps sd_journal_test_cursor(). See man:sd_journal_test_cursor(3).");
640 static PyObject* Reader_test_cursor(Reader *self, PyObject *args)
641 {
642     const char *cursor;
643     int r;
644
645     assert(self);
646     assert(args);
647
648     if (!PyArg_ParseTuple(args, "s:test_cursor", &cursor))
649         return NULL;
650
651     r = sd_journal_test_cursor(self->j, cursor);
652     set_error(r, NULL, NULL);
653     if (r < 0)
654         return NULL;
655
656     return PyBool_FromLong(r);
657 }
658
659
660 static PyObject* Reader_iter(PyObject *self)
661 {
662     Py_INCREF(self);
663     return self;
664 }
665
666 static PyObject* Reader_iternext(PyObject *self)
667 {
668     PyObject *dict;
669     Py_ssize_t dict_size;
670
671     dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
672     if (PyErr_Occurred())
673         return NULL;
674     dict_size = PyDict_Size(dict);
675     if ((int64_t) dict_size > 0LL) {
676         return dict;
677     } else {
678         Py_DECREF(dict);
679         PyErr_SetNone(PyExc_StopIteration);
680         return NULL;
681     }
682 }
683
684
685 PyDoc_STRVAR(Reader_query_unique__doc__,
686              "query_unique(field) -> a set of values\n\n"
687              "Return a set of unique values appearing in journal for the\n"
688              "given `field`. Note this does not respect any journal matches.");
689 static PyObject* Reader_query_unique(Reader *self, PyObject *args)
690 {
691     char *query;
692     int r;
693     const void *uniq;
694     size_t uniq_len;
695     PyObject *value_set, *key, *value;
696
697     if (!PyArg_ParseTuple(args, "s:query_unique", &query))
698         return NULL;
699
700     Py_BEGIN_ALLOW_THREADS
701     r = sd_journal_query_unique(self->j, query);
702     Py_END_ALLOW_THREADS
703     if (set_error(r, NULL, "Invalid field name"))
704         return NULL;
705
706     value_set = PySet_New(0);
707     key = unicode_FromString(query);
708
709     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
710         const char *delim_ptr;
711
712         delim_ptr = memchr(uniq, '=', uniq_len);
713         value = PyBytes_FromStringAndSize(
714             delim_ptr + 1,
715             (const char*) uniq + uniq_len - (delim_ptr + 1));
716         PySet_Add(value_set, value);
717         Py_DECREF(value);
718     }
719     Py_DECREF(key);
720     return value_set;
721 }
722
723
724 PyDoc_STRVAR(Reader_get_catalog__doc__,
725              "get_catalog() -> str\n\n"
726              "Retrieve a message catalog entry for the current journal entry.\n"
727              "Wraps man:sd_journal_get_catalog(3).");
728 static PyObject* Reader_get_catalog(Reader *self, PyObject *args)
729 {
730     int r;
731     char _cleanup_free_ *msg = NULL;
732
733     assert(self);
734     assert(!args);
735
736     Py_BEGIN_ALLOW_THREADS
737     r = sd_journal_get_catalog(self->j, &msg);
738     Py_END_ALLOW_THREADS
739     if (set_error(r, NULL, NULL))
740         return NULL;
741
742     return unicode_FromString(msg);
743 }
744
745
746 PyDoc_STRVAR(get_catalog__doc__,
747              "get_catalog(id128) -> str\n\n"
748              "Retrieve a message catalog entry for the given id.\n"
749              "Wraps man:sd_journal_get_catalog_for_message_id(3).");
750 static PyObject* get_catalog(PyObject *self, PyObject *args)
751 {
752     int r;
753     char *id_ = NULL;
754     sd_id128_t id;
755     char _cleanup_free_ *msg = NULL;
756
757     assert(!self);
758     assert(args);
759
760     if (!PyArg_ParseTuple(args, "z:get_catalog", &id_))
761         return NULL;
762
763     r = sd_id128_from_string(id_, &id);
764     if (set_error(r, NULL, "Invalid id128"))
765         return NULL;
766
767     Py_BEGIN_ALLOW_THREADS
768     r = sd_journal_get_catalog_for_message_id(id, &msg);
769     Py_END_ALLOW_THREADS
770     if (set_error(r, NULL, NULL))
771         return NULL;
772
773     return unicode_FromString(msg);
774 }
775
776
777 PyDoc_STRVAR(data_threshold__doc__,
778              "Threshold for field size truncation in bytes.\n\n"
779              "Fields longer than this will be truncated to the threshold size.\n"
780              "Defaults to 64Kb.");
781
782 static PyObject* Reader_get_data_threshold(Reader *self, void *closure)
783 {
784     size_t cvalue;
785     int r;
786
787     r = sd_journal_get_data_threshold(self->j, &cvalue);
788     if (set_error(r, NULL, NULL))
789         return NULL;
790
791     return long_FromSize_t(cvalue);
792 }
793
794 static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure)
795 {
796     int r;
797     if (value == NULL) {
798         PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold");
799         return -1;
800     }
801     if (!long_Check(value)){
802         PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
803         return -1;
804     }
805     r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
806     return set_error(r, NULL, NULL);
807 }
808
809
810 PyDoc_STRVAR(closed__doc__,
811              "True iff journal is closed");
812 static PyObject* Reader_get_closed(Reader *self, void *closure)
813 {
814     return PyBool_FromLong(self->j == NULL);
815 }
816
817
818 static PyGetSetDef Reader_getsetters[] = {
819     {(char*) "data_threshold",
820      (getter) Reader_get_data_threshold,
821      (setter) Reader_set_data_threshold,
822      (char*) data_threshold__doc__,
823      NULL},
824     {(char*) "closed",
825      (getter) Reader_get_closed,
826      NULL,
827      (char*) closed__doc__,
828      NULL},
829     {NULL}
830 };
831
832 static PyMethodDef Reader_methods[] = {
833     {"fileno",          (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__},
834     {"reliable_fd",     (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__},
835     {"close",           (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__},
836     {"get_usage",       (PyCFunction) Reader_get_usage, METH_NOARGS, Reader_get_usage__doc__},
837     {"__enter__",       (PyCFunction) Reader___enter__, METH_NOARGS, Reader___enter____doc__},
838     {"__exit__",        (PyCFunction) Reader___exit__, METH_VARARGS, Reader___exit____doc__},
839     {"next",            (PyCFunction) Reader_next, METH_VARARGS, Reader_next__doc__},
840     {"get_next",        (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
841     {"get_previous",    (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__doc__},
842     {"get_realtime",    (PyCFunction) Reader_get_realtime, METH_NOARGS, Reader_get_realtime__doc__},
843     {"get_monotonic",   (PyCFunction) Reader_get_monotonic, METH_NOARGS, Reader_get_monotonic__doc__},
844     {"add_match",       (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__},
845     {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__},
846     {"flush_matches",   (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
847     {"seek_head",       (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__},
848     {"seek_tail",       (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__},
849     {"seek_realtime",   (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__},
850     {"seek_monotonic",  (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__},
851     {"wait",            (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
852     {"seek_cursor",     (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
853     {"get_cursor",      (PyCFunction) Reader_get_cursor, METH_NOARGS, Reader_get_cursor__doc__},
854     {"test_cursor",     (PyCFunction) Reader_test_cursor, METH_VARARGS, Reader_test_cursor__doc__},
855     {"query_unique",    (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
856     {"get_catalog",     (PyCFunction) Reader_get_catalog, METH_NOARGS, Reader_get_catalog__doc__},
857     {NULL}  /* Sentinel */
858 };
859
860 static PyTypeObject ReaderType = {
861     PyVarObject_HEAD_INIT(NULL, 0)
862     "_reader._Reader",                        /*tp_name*/
863     sizeof(Reader),                           /*tp_basicsize*/
864     0,                                        /*tp_itemsize*/
865     (destructor)Reader_dealloc,               /*tp_dealloc*/
866     0,                                        /*tp_print*/
867     0,                                        /*tp_getattr*/
868     0,                                        /*tp_setattr*/
869     0,                                        /*tp_compare*/
870     0,                                        /*tp_repr*/
871     0,                                        /*tp_as_number*/
872     0,                                        /*tp_as_sequence*/
873     0,                                        /*tp_as_mapping*/
874     0,                                        /*tp_hash */
875     0,                                        /*tp_call*/
876     0,                                        /*tp_str*/
877     0,                                        /*tp_getattro*/
878     0,                                        /*tp_setattro*/
879     0,                                        /*tp_as_buffer*/
880     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
881     Reader__doc__,                            /* tp_doc */
882     0,                                        /* tp_traverse */
883     0,                                        /* tp_clear */
884     0,                                        /* tp_richcompare */
885     0,                                        /* tp_weaklistoffset */
886     Reader_iter,                              /* tp_iter */
887     Reader_iternext,                          /* tp_iternext */
888     Reader_methods,                           /* tp_methods */
889     0,                                        /* tp_members */
890     Reader_getsetters,                        /* tp_getset */
891     0,                                        /* tp_base */
892     0,                                        /* tp_dict */
893     0,                                        /* tp_descr_get */
894     0,                                        /* tp_descr_set */
895     0,                                        /* tp_dictoffset */
896     (initproc) Reader_init,                   /* tp_init */
897     0,                                        /* tp_alloc */
898     PyType_GenericNew,                        /* tp_new */
899 };
900
901 static PyMethodDef methods[] = {
902         { "get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__},
903         { NULL, NULL, 0, NULL }        /* Sentinel */
904 };
905
906 #if PY_MAJOR_VERSION >= 3
907 static PyModuleDef module = {
908     PyModuleDef_HEAD_INIT,
909     "_reader",
910     module__doc__,
911     -1,
912     methods,
913     NULL, NULL, NULL, NULL
914 };
915 #endif
916
917 #if PY_MAJOR_VERSION >= 3
918 static bool initialized = false;
919 #endif
920
921 #pragma GCC diagnostic push
922 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
923
924 PyMODINIT_FUNC
925 #if PY_MAJOR_VERSION >= 3
926 PyInit__reader(void)
927 #else
928 init_reader(void)
929 #endif
930 {
931     PyObject* m;
932
933     PyDateTime_IMPORT;
934
935     if (PyType_Ready(&ReaderType) < 0)
936 #if PY_MAJOR_VERSION >= 3
937         return NULL;
938 #else
939         return;
940 #endif
941
942 #if PY_MAJOR_VERSION >= 3
943     m = PyModule_Create(&module);
944     if (m == NULL)
945         return NULL;
946
947     if (!initialized) {
948         PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
949         initialized = true;
950     }
951 #else
952     m = Py_InitModule3("_reader", methods, module__doc__);
953     if (m == NULL)
954         return;
955 #endif
956
957     Py_INCREF(&ReaderType);
958 #if PY_MAJOR_VERSION >= 3
959     Py_INCREF(&MonotonicType);
960 #endif
961     if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) ||
962 #if PY_MAJOR_VERSION >= 3
963         PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) ||
964 #endif
965         PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) ||
966         PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) ||
967         PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) ||
968         PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) ||
969         PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) ||
970         PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
971 #if PY_MAJOR_VERSION >= 3
972         Py_DECREF(m);
973         return NULL;
974 #endif
975     }
976
977 #if PY_MAJOR_VERSION >= 3
978     return m;
979 #endif
980 }
981
982 #pragma GCC diagnostic pop