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