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