chiark / gitweb /
systemd-python: cleanup up usec_t handling
[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     {
378         PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL;
379         char _cleanup_free_ *cursor = NULL;
380
381         r = sd_journal_get_cursor(self->j, &cursor);
382         if (set_error(r, NULL, NULL))
383             goto error;
384
385         key = unicode_FromString("__CURSOR");
386         if (!key)
387             goto error;
388
389         value = PyBytes_FromString(cursor);
390         if (!value)
391             goto error;
392
393         if (PyDict_SetItem(dict, key, value))
394             goto error;
395     }
396
397     return dict;
398 error:
399     Py_DECREF(dict);
400     return NULL;
401 }
402
403
404 PyDoc_STRVAR(Reader_get_previous__doc__,
405              "get_previous([skip]) -> dict\n\n"
406              "Return dictionary of the previous log entry. Optional skip value\n"
407              "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip).");
408 static PyObject* Reader_get_previous(Reader *self, PyObject *args)
409 {
410     int64_t skip = 1LL;
411     if (!PyArg_ParseTuple(args, "|L:_Reader.get_previous", &skip))
412         return NULL;
413
414     return PyObject_CallMethod((PyObject *)self, (char*) "get_next",
415                                (char*) "L", -skip);
416 }
417
418
419 PyDoc_STRVAR(Reader_add_match__doc__,
420              "add_match(match) -> None\n\n"
421              "Add a match to filter journal log entries. All matches of different\n"
422              "fields are combined with logical AND, and matches of the same field\n"
423              "are automatically combined with logical OR.\n"
424              "Match is a string of the form \"FIELD=value\".");
425 static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds)
426 {
427     char *match;
428     int match_len, r;
429     if (!PyArg_ParseTuple(args, "s#:_Reader.add_match", &match, &match_len))
430         return NULL;
431
432     r = sd_journal_add_match(self->j, match, match_len);
433     set_error(r, NULL, "Invalid match");
434     if (r < 0)
435             return NULL;
436
437     Py_RETURN_NONE;
438 }
439
440
441 PyDoc_STRVAR(Reader_add_disjunction__doc__,
442              "add_disjunction() -> None\n\n"
443              "Inserts a logical OR between matches added before and afterwards.");
444 static PyObject* Reader_add_disjunction(Reader *self, PyObject *args)
445 {
446     int r;
447     r = sd_journal_add_disjunction(self->j);
448     set_error(r, NULL, NULL);
449     if (r < 0)
450         return NULL;
451     Py_RETURN_NONE;
452 }
453
454
455 PyDoc_STRVAR(Reader_flush_matches__doc__,
456              "flush_matches() -> None\n\n"
457              "Clear all current match filters.");
458 static PyObject* Reader_flush_matches(Reader *self, PyObject *args)
459 {
460     sd_journal_flush_matches(self->j);
461     Py_RETURN_NONE;
462 }
463
464
465 PyDoc_STRVAR(Reader_seek_head__doc__,
466              "seek_head() -> None\n\n"
467              "Jump to the beginning of the journal.\n"
468              "This method invokes sd_journal_seek_head().\n"
469              "See man:sd_journal_seek_head(3).");
470 static PyObject* Reader_seek_head(Reader *self, PyObject *args)
471 {
472     int r;
473     Py_BEGIN_ALLOW_THREADS
474     r = sd_journal_seek_head(self->j);
475     Py_END_ALLOW_THREADS
476     if (set_error(r, NULL, NULL))
477         return NULL;
478     Py_RETURN_NONE;
479 }
480
481
482 PyDoc_STRVAR(Reader_seek_tail__doc__,
483              "seek_tail() -> None\n\n"
484              "Jump to the end of the journal.\n"
485              "This method invokes sd_journal_seek_tail().\n"
486              "See man:sd_journal_seek_tail(3).");
487 static PyObject* Reader_seek_tail(Reader *self, PyObject *args)
488 {
489     int r;
490     Py_BEGIN_ALLOW_THREADS
491     r = sd_journal_seek_tail(self->j);
492     Py_END_ALLOW_THREADS
493     if (set_error(r, NULL, NULL))
494         return NULL;
495     Py_RETURN_NONE;
496 }
497
498
499 PyDoc_STRVAR(Reader_seek_realtime__doc__,
500              "seek_realtime(realtime) -> None\n\n"
501              "Seek to nearest matching journal entry to `realtime`. Argument\n"
502              "`realtime` in specified in seconds.");
503 static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
504 {
505     uint64_t timestamp;
506     int r;
507
508     if (!PyArg_ParseTuple(args, "K:seek_realtime", &timestamp))
509         return NULL;
510
511     Py_BEGIN_ALLOW_THREADS
512     r = sd_journal_seek_realtime_usec(self->j, timestamp);
513     Py_END_ALLOW_THREADS
514     if (set_error(r, NULL, NULL))
515         return NULL;
516     Py_RETURN_NONE;
517 }
518
519
520 PyDoc_STRVAR(Reader_seek_monotonic__doc__,
521              "seek_monotonic(monotonic[, bootid]) -> None\n\n"
522              "Seek to nearest matching journal entry to `monotonic`. Argument\n"
523              "`monotonic` is an timestamp from boot in microseconds.\n"
524              "Argument `bootid` is a string representing which boot the\n"
525              "monotonic time is reference to. Defaults to current bootid.");
526 static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args)
527 {
528     char *bootid = NULL;
529     uint64_t timestamp;
530     sd_id128_t id;
531     int r;
532
533     if (!PyArg_ParseTuple(args, "K|z:seek_monotonic", &timestamp, &bootid))
534         return NULL;
535
536     if (bootid) {
537         r = sd_id128_from_string(bootid, &id);
538         if (set_error(r, NULL, "Invalid bootid"))
539             return NULL;
540     } else {
541         Py_BEGIN_ALLOW_THREADS
542         r = sd_id128_get_boot(&id);
543         Py_END_ALLOW_THREADS
544         if (set_error(r, NULL, NULL))
545             return NULL;
546     }
547
548     Py_BEGIN_ALLOW_THREADS
549     r = sd_journal_seek_monotonic_usec(self->j, id, timestamp);
550     Py_END_ALLOW_THREADS
551     if (set_error(r, NULL, NULL))
552         return NULL;
553
554     Py_RETURN_NONE;
555 }
556
557
558 PyDoc_STRVAR(Reader_wait__doc__,
559              "wait([timeout]) -> state change (integer)\n\n"
560              "Wait for a change in the journal. Argument `timeout` specifies\n"
561              "the maximum number of microseconds to wait before returning\n"
562              "regardless of wheter the journal has changed. If `timeout` is -1,\n"
563              "then block forever.\n\n"
564              "Will return constants: NOP if no change; APPEND if new\n"
565              "entries have been added to the end of the journal; and\n"
566              "INVALIDATE if journal files have been added or removed.");
567 static PyObject* Reader_wait(Reader *self, PyObject *args)
568 {
569     int r;
570     int64_t timeout;
571
572     if (!PyArg_ParseTuple(args, "|L:wait", &timeout))
573         return NULL;
574
575     Py_BEGIN_ALLOW_THREADS
576     r = sd_journal_wait(self->j, timeout);
577     Py_END_ALLOW_THREADS
578     if (set_error(r, NULL, NULL) < 0)
579         return NULL;
580
581     return long_FromLong(r);
582 }
583
584
585 PyDoc_STRVAR(Reader_seek_cursor__doc__,
586              "seek_cursor(cursor) -> None\n\n"
587              "Seek to journal entry by given unique reference `cursor`.");
588 static PyObject* Reader_seek_cursor(Reader *self, PyObject *args)
589 {
590     const char *cursor;
591     int r;
592
593     if (!PyArg_ParseTuple(args, "s:_Reader.seek_cursor", &cursor))
594         return NULL;
595
596     Py_BEGIN_ALLOW_THREADS
597     r = sd_journal_seek_cursor(self->j, cursor);
598     Py_END_ALLOW_THREADS
599     if (set_error(r, NULL, "Invalid cursor"))
600         return NULL;
601     Py_RETURN_NONE;
602 }
603
604
605 static PyObject* Reader_iter(PyObject *self)
606 {
607     Py_INCREF(self);
608     return self;
609 }
610
611 static PyObject* Reader_iternext(PyObject *self)
612 {
613     PyObject *dict;
614     Py_ssize_t dict_size;
615
616     dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
617     if (PyErr_Occurred())
618         return NULL;
619     dict_size = PyDict_Size(dict);
620     if ((int64_t) dict_size > 0LL) {
621         return dict;
622     } else {
623         Py_DECREF(dict);
624         PyErr_SetNone(PyExc_StopIteration);
625         return NULL;
626     }
627 }
628
629
630 PyDoc_STRVAR(Reader_query_unique__doc__,
631              "query_unique(field) -> a set of values\n\n"
632              "Return a set of unique values appearing in journal for the\n"
633              "given `field`. Note this does not respect any journal matches.");
634 static PyObject* Reader_query_unique(Reader *self, PyObject *args)
635 {
636     char *query;
637     int r;
638     const void *uniq;
639     size_t uniq_len;
640     PyObject *value_set, *key, *value;
641
642     if (!PyArg_ParseTuple(args, "s:_Reader.query_unique", &query))
643         return NULL;
644
645     Py_BEGIN_ALLOW_THREADS
646     r = sd_journal_query_unique(self->j, query);
647     Py_END_ALLOW_THREADS
648     if (set_error(r, NULL, "Invalid field name"))
649         return NULL;
650
651     value_set = PySet_New(0);
652     key = unicode_FromString(query);
653
654     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
655         const char *delim_ptr;
656
657         delim_ptr = memchr(uniq, '=', uniq_len);
658         value = PyBytes_FromStringAndSize(
659             delim_ptr + 1,
660             (const char*) uniq + uniq_len - (delim_ptr + 1));
661         PySet_Add(value_set, value);
662         Py_DECREF(value);
663     }
664     Py_DECREF(key);
665     return value_set;
666 }
667
668
669 PyDoc_STRVAR(Reader_get_catalog__doc__,
670              "get_catalog() -> str\n\n"
671              "Retrieve a message catalog entry for the current journal entry.\n"
672              "Wraps man:sd_journal_get_catalog(3).");
673 static PyObject* Reader_get_catalog(Reader *self, PyObject *args)
674 {
675     int r;
676     char _cleanup_free_ *msg = NULL;
677
678     assert(self);
679     assert(!args);
680
681     Py_BEGIN_ALLOW_THREADS
682     r = sd_journal_get_catalog(self->j, &msg);
683     Py_END_ALLOW_THREADS
684     if (set_error(r, NULL, NULL))
685         return NULL;
686
687     return unicode_FromString(msg);
688 }
689
690
691 PyDoc_STRVAR(get_catalog__doc__,
692              "get_catalog(id128) -> str\n\n"
693              "Retrieve a message catalog entry for the given id.\n"
694              "Wraps man:sd_journal_get_catalog_for_message_id(3).");
695 static PyObject* get_catalog(PyObject *self, PyObject *args)
696 {
697     int r;
698     char *id_ = NULL;
699     sd_id128_t id;
700     char _cleanup_free_ *msg = NULL;
701
702     assert(!self);
703     assert(args);
704
705     if (!PyArg_ParseTuple(args, "z:get_catalog", &id_))
706         return NULL;
707
708     r = sd_id128_from_string(id_, &id);
709     if (set_error(r, NULL, "Invalid id128"))
710         return NULL;
711
712     Py_BEGIN_ALLOW_THREADS
713     r = sd_journal_get_catalog_for_message_id(id, &msg);
714     Py_END_ALLOW_THREADS
715     if (set_error(r, NULL, NULL))
716         return NULL;
717
718     return unicode_FromString(msg);
719 }
720
721
722 PyDoc_STRVAR(data_threshold__doc__,
723              "Threshold for field size truncation in bytes.\n\n"
724              "Fields longer than this will be truncated to the threshold size.\n"
725              "Defaults to 64Kb.");
726
727 static PyObject* Reader_get_data_threshold(Reader *self, void *closure)
728 {
729     size_t cvalue;
730     int r;
731
732     r = sd_journal_get_data_threshold(self->j, &cvalue);
733     if (set_error(r, NULL, NULL))
734         return NULL;
735
736     return long_FromSize_t(cvalue);
737 }
738
739 static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure)
740 {
741     int r;
742     if (value == NULL) {
743         PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold");
744         return -1;
745     }
746     if (!long_Check(value)){
747         PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
748         return -1;
749     }
750     r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
751     return set_error(r, NULL, NULL);
752 }
753
754
755 PyDoc_STRVAR(closed__doc__,
756              "True iff journal is closed");
757 static PyObject* Reader_get_closed(Reader *self, void *closure)
758 {
759     return PyBool_FromLong(self->j == NULL);
760 }
761
762
763 static PyGetSetDef Reader_getsetters[] = {
764     {(char*) "data_threshold",
765      (getter) Reader_get_data_threshold,
766      (setter) Reader_set_data_threshold,
767      (char*) data_threshold__doc__,
768      NULL},
769     {(char*) "closed",
770      (getter) Reader_get_closed,
771      NULL,
772      (char*) closed__doc__,
773      NULL},
774     {NULL}
775 };
776
777 static PyMethodDef Reader_methods[] = {
778     {"fileno",          (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__},
779     {"reliable_fd",     (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__},
780     {"close",           (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__},
781     {"get_usage",       (PyCFunction) Reader_get_usage, METH_NOARGS, Reader_get_usage__doc__},
782     {"__enter__",       (PyCFunction) Reader___enter__, METH_NOARGS, Reader___enter____doc__},
783     {"__exit__",        (PyCFunction) Reader___exit__, METH_VARARGS, Reader___exit____doc__},
784     {"get_next",        (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
785     {"get_previous",    (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__doc__},
786     {"add_match",       (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__},
787     {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__},
788     {"flush_matches",   (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
789     {"seek_head",       (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__},
790     {"seek_tail",       (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__},
791     {"seek_realtime",   (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__},
792     {"seek_monotonic",  (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__},
793     {"wait",            (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
794     {"seek_cursor",     (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
795     {"query_unique",    (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
796     {"get_catalog",     (PyCFunction) Reader_get_catalog, METH_NOARGS, Reader_get_catalog__doc__},
797     {NULL}  /* Sentinel */
798 };
799
800 static PyTypeObject ReaderType = {
801     PyVarObject_HEAD_INIT(NULL, 0)
802     "_reader._Reader",                        /*tp_name*/
803     sizeof(Reader),                           /*tp_basicsize*/
804     0,                                        /*tp_itemsize*/
805     (destructor)Reader_dealloc,               /*tp_dealloc*/
806     0,                                        /*tp_print*/
807     0,                                        /*tp_getattr*/
808     0,                                        /*tp_setattr*/
809     0,                                        /*tp_compare*/
810     0,                                        /*tp_repr*/
811     0,                                        /*tp_as_number*/
812     0,                                        /*tp_as_sequence*/
813     0,                                        /*tp_as_mapping*/
814     0,                                        /*tp_hash */
815     0,                                        /*tp_call*/
816     0,                                        /*tp_str*/
817     0,                                        /*tp_getattro*/
818     0,                                        /*tp_setattro*/
819     0,                                        /*tp_as_buffer*/
820     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
821     Reader__doc__,                            /* tp_doc */
822     0,                                        /* tp_traverse */
823     0,                                        /* tp_clear */
824     0,                                        /* tp_richcompare */
825     0,                                        /* tp_weaklistoffset */
826     Reader_iter,                              /* tp_iter */
827     Reader_iternext,                          /* tp_iternext */
828     Reader_methods,                           /* tp_methods */
829     0,                                        /* tp_members */
830     Reader_getsetters,                        /* tp_getset */
831     0,                                        /* tp_base */
832     0,                                        /* tp_dict */
833     0,                                        /* tp_descr_get */
834     0,                                        /* tp_descr_set */
835     0,                                        /* tp_dictoffset */
836     (initproc) Reader_init,                   /* tp_init */
837     0,                                        /* tp_alloc */
838     PyType_GenericNew,                        /* tp_new */
839 };
840
841 static PyMethodDef methods[] = {
842         { "get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__},
843         { NULL, NULL, 0, NULL }        /* Sentinel */
844 };
845
846 #if PY_MAJOR_VERSION >= 3
847 static PyModuleDef module = {
848     PyModuleDef_HEAD_INIT,
849     "_reader",
850     module__doc__,
851     -1,
852     methods,
853     NULL, NULL, NULL, NULL
854 };
855 #endif
856
857 #if PY_MAJOR_VERSION >= 3
858 static bool initialized = false;
859 #endif
860
861 #pragma GCC diagnostic push
862 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
863
864 PyMODINIT_FUNC
865 #if PY_MAJOR_VERSION >= 3
866 PyInit__reader(void)
867 #else
868 init_reader(void)
869 #endif
870 {
871     PyObject* m;
872
873     PyDateTime_IMPORT;
874
875     if (PyType_Ready(&ReaderType) < 0)
876 #if PY_MAJOR_VERSION >= 3
877         return NULL;
878 #else
879         return;
880 #endif
881
882 #if PY_MAJOR_VERSION >= 3
883     m = PyModule_Create(&module);
884     if (m == NULL)
885         return NULL;
886
887     if (!initialized) {
888         PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
889         initialized = true;
890     }
891 #else
892     m = Py_InitModule3("_reader", methods, module__doc__);
893     if (m == NULL)
894         return;
895 #endif
896
897     Py_INCREF(&ReaderType);
898 #if PY_MAJOR_VERSION >= 3
899     Py_INCREF(&MonotonicType);
900 #endif
901     if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) ||
902 #if PY_MAJOR_VERSION >= 3
903         PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) ||
904 #endif
905         PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) ||
906         PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) ||
907         PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) ||
908         PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) ||
909         PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) ||
910         PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
911 #if PY_MAJOR_VERSION >= 3
912         Py_DECREF(m);
913         return NULL;
914 #endif
915     }
916
917 #if PY_MAJOR_VERSION >= 3
918     return m;
919 #endif
920 }
921
922 #pragma GCC diagnostic pop