chiark / gitweb /
systemd-python: export sd_journal_get_usage
[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` can must be an integer unix timestamp.");
503 static PyObject* Reader_seek_realtime(Reader *self, PyObject *args)
504 {
505     double timedouble;
506     uint64_t timestamp;
507     int r;
508
509     if (!PyArg_ParseTuple(args, "d:_Reader.seek_realtime", &timedouble))
510         return NULL;
511
512     timestamp = (uint64_t) (timedouble * 1.0E6);
513     if ((int64_t) timestamp < 0LL) {
514         PyErr_SetString(PyExc_ValueError, "Time must be a positive integer");
515         return NULL;
516     }
517
518     Py_BEGIN_ALLOW_THREADS
519     r = sd_journal_seek_realtime_usec(self->j, timestamp);
520     Py_END_ALLOW_THREADS
521     if (set_error(r, NULL, NULL))
522         return NULL;
523     Py_RETURN_NONE;
524 }
525
526
527 PyDoc_STRVAR(Reader_seek_monotonic__doc__,
528              "seek_monotonic(monotonic[, bootid]) -> None\n\n"
529              "Seek to nearest matching journal entry to `monotonic`. Argument\n"
530              "`monotonic` is an timestamp from boot in seconds.\n"
531              "Argument `bootid` is a string representing which boot the\n"
532              "monotonic time is reference to. Defaults to current bootid.");
533 static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args)
534 {
535     double timedouble;
536     char *bootid = NULL;
537     uint64_t timestamp;
538     sd_id128_t id;
539     int r;
540
541     if (!PyArg_ParseTuple(args, "d|z:_Reader.seek_monotonic", &timedouble, &bootid))
542         return NULL;
543
544     timestamp = (uint64_t) (timedouble * 1.0E6);
545
546     if ((int64_t) timestamp < 0LL) {
547         PyErr_SetString(PyExc_ValueError, "Time must be positive number");
548         return NULL;
549     }
550
551     if (bootid) {
552         r = sd_id128_from_string(bootid, &id);
553         if (set_error(r, NULL, "Invalid bootid"))
554             return NULL;
555     } else {
556         Py_BEGIN_ALLOW_THREADS
557         r = sd_id128_get_boot(&id);
558         Py_END_ALLOW_THREADS
559         if (set_error(r, NULL, NULL))
560             return NULL;
561     }
562
563     Py_BEGIN_ALLOW_THREADS
564     r = sd_journal_seek_monotonic_usec(self->j, id, timestamp);
565     Py_END_ALLOW_THREADS
566     if (set_error(r, NULL, NULL))
567         return NULL;
568     Py_RETURN_NONE;
569 }
570
571
572 PyDoc_STRVAR(Reader_wait__doc__,
573              "wait([timeout]) -> state change (integer)\n\n"
574              "Wait for a change in the journal. Argument `timeout` specifies\n"
575              "the maximum number of seconds to wait before returning\n"
576              "regardless of wheter the journal has changed. If `timeout` is not given\n"
577              "or is 0, then block forever.\n"
578              "Will return constants: NOP if no change; APPEND if new\n"
579              "entries have been added to the end of the journal; and\n"
580              "INVALIDATE if journal files have been added or removed.");
581 static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds)
582 {
583     int r;
584     int64_t timeout = 0LL;
585
586     if (!PyArg_ParseTuple(args, "|L:_Reader.wait", &timeout))
587         return NULL;
588
589     Py_BEGIN_ALLOW_THREADS
590     r = sd_journal_wait(self->j,
591                         timeout == 0 ? (uint64_t) -1 : timeout * 1E6);
592     Py_END_ALLOW_THREADS
593     if (set_error(r, NULL, NULL) < 0)
594         return NULL;
595
596     return long_FromLong(r);
597 }
598
599
600 PyDoc_STRVAR(Reader_seek_cursor__doc__,
601              "seek_cursor(cursor) -> None\n\n"
602              "Seek to journal entry by given unique reference `cursor`.");
603 static PyObject* Reader_seek_cursor(Reader *self, PyObject *args)
604 {
605     const char *cursor;
606     int r;
607
608     if (!PyArg_ParseTuple(args, "s:_Reader.seek_cursor", &cursor))
609         return NULL;
610
611     Py_BEGIN_ALLOW_THREADS
612     r = sd_journal_seek_cursor(self->j, cursor);
613     Py_END_ALLOW_THREADS
614     if (set_error(r, NULL, "Invalid cursor"))
615         return NULL;
616     Py_RETURN_NONE;
617 }
618
619
620 static PyObject* Reader_iter(PyObject *self)
621 {
622     Py_INCREF(self);
623     return self;
624 }
625
626 static PyObject* Reader_iternext(PyObject *self)
627 {
628     PyObject *dict;
629     Py_ssize_t dict_size;
630
631     dict = PyObject_CallMethod(self, (char*) "get_next", (char*) "");
632     if (PyErr_Occurred())
633         return NULL;
634     dict_size = PyDict_Size(dict);
635     if ((int64_t) dict_size > 0LL) {
636         return dict;
637     } else {
638         Py_DECREF(dict);
639         PyErr_SetNone(PyExc_StopIteration);
640         return NULL;
641     }
642 }
643
644
645 PyDoc_STRVAR(Reader_query_unique__doc__,
646              "query_unique(field) -> a set of values\n\n"
647              "Return a set of unique values appearing in journal for the\n"
648              "given `field`. Note this does not respect any journal matches.");
649 static PyObject* Reader_query_unique(Reader *self, PyObject *args)
650 {
651     char *query;
652     int r;
653     const void *uniq;
654     size_t uniq_len;
655     PyObject *value_set, *key, *value;
656
657     if (!PyArg_ParseTuple(args, "s:_Reader.query_unique", &query))
658         return NULL;
659
660     Py_BEGIN_ALLOW_THREADS
661     r = sd_journal_query_unique(self->j, query);
662     Py_END_ALLOW_THREADS
663     if (set_error(r, NULL, "Invalid field name"))
664         return NULL;
665
666     value_set = PySet_New(0);
667     key = unicode_FromString(query);
668
669     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
670         const char *delim_ptr;
671
672         delim_ptr = memchr(uniq, '=', uniq_len);
673         value = PyBytes_FromStringAndSize(
674             delim_ptr + 1,
675             (const char*) uniq + uniq_len - (delim_ptr + 1));
676         PySet_Add(value_set, value);
677         Py_DECREF(value);
678     }
679     Py_DECREF(key);
680     return value_set;
681 }
682
683
684 PyDoc_STRVAR(Reader_get_catalog__doc__,
685              "get_catalog() -> str\n\n"
686              "Retrieve a message catalog entry for the current journal entry.\n"
687              "Wraps man:sd_journal_get_catalog(3).");
688 static PyObject* Reader_get_catalog(Reader *self, PyObject *args)
689 {
690     int r;
691     char _cleanup_free_ *msg = NULL;
692
693     assert(self);
694     assert(!args);
695
696     Py_BEGIN_ALLOW_THREADS
697     r = sd_journal_get_catalog(self->j, &msg);
698     Py_END_ALLOW_THREADS
699     if (set_error(r, NULL, NULL))
700         return NULL;
701
702     return unicode_FromString(msg);
703 }
704
705
706 PyDoc_STRVAR(get_catalog__doc__,
707              "get_catalog(id128) -> str\n\n"
708              "Retrieve a message catalog entry for the given id.\n"
709              "Wraps man:sd_journal_get_catalog_for_message_id(3).");
710 static PyObject* get_catalog(PyObject *self, PyObject *args)
711 {
712     int r;
713     char *id_ = NULL;
714     sd_id128_t id;
715     char _cleanup_free_ *msg = NULL;
716
717     assert(!self);
718     assert(args);
719
720     if (!PyArg_ParseTuple(args, "z:get_catalog", &id_))
721         return NULL;
722
723     r = sd_id128_from_string(id_, &id);
724     if (set_error(r, NULL, "Invalid id128"))
725         return NULL;
726
727     Py_BEGIN_ALLOW_THREADS
728     r = sd_journal_get_catalog_for_message_id(id, &msg);
729     Py_END_ALLOW_THREADS
730     if (set_error(r, NULL, NULL))
731         return NULL;
732
733     return unicode_FromString(msg);
734 }
735
736
737 PyDoc_STRVAR(data_threshold__doc__,
738              "Threshold for field size truncation in bytes.\n\n"
739              "Fields longer than this will be truncated to the threshold size.\n"
740              "Defaults to 64Kb.");
741
742 static PyObject* Reader_get_data_threshold(Reader *self, void *closure)
743 {
744     size_t cvalue;
745     int r;
746
747     r = sd_journal_get_data_threshold(self->j, &cvalue);
748     if (set_error(r, NULL, NULL))
749         return NULL;
750
751     return long_FromSize_t(cvalue);
752 }
753
754 static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure)
755 {
756     int r;
757     if (value == NULL) {
758         PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold");
759         return -1;
760     }
761     if (!long_Check(value)){
762         PyErr_SetString(PyExc_TypeError, "Data threshold must be an int");
763         return -1;
764     }
765     r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
766     return set_error(r, NULL, NULL);
767 }
768
769
770 PyDoc_STRVAR(closed__doc__,
771              "True iff journal is closed");
772 static PyObject* Reader_get_closed(Reader *self, void *closure)
773 {
774     return PyBool_FromLong(self->j == NULL);
775 }
776
777
778 static PyGetSetDef Reader_getsetters[] = {
779     {(char*) "data_threshold",
780      (getter) Reader_get_data_threshold,
781      (setter) Reader_set_data_threshold,
782      (char*) data_threshold__doc__,
783      NULL},
784     {(char*) "closed",
785      (getter) Reader_get_closed,
786      NULL,
787      (char*) closed__doc__,
788      NULL},
789     {NULL}
790 };
791
792 static PyMethodDef Reader_methods[] = {
793     {"fileno",          (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__},
794     {"reliable_fd",     (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__},
795     {"close",           (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__},
796     {"get_usage",       (PyCFunction) Reader_get_usage, METH_NOARGS, Reader_get_usage__doc__},
797     {"__enter__",       (PyCFunction) Reader___enter__, METH_NOARGS, Reader___enter____doc__},
798     {"__exit__",        (PyCFunction) Reader___exit__, METH_VARARGS, Reader___exit____doc__},
799     {"get_next",        (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__},
800     {"get_previous",    (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__doc__},
801     {"add_match",       (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__},
802     {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__},
803     {"flush_matches",   (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__},
804     {"seek_head",       (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__},
805     {"seek_tail",       (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__},
806     {"seek_realtime",   (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__},
807     {"seek_monotonic",  (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__},
808     {"wait",            (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__},
809     {"seek_cursor",     (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__},
810     {"query_unique",    (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__},
811     {"get_catalog",     (PyCFunction) Reader_get_catalog, METH_NOARGS, Reader_get_catalog__doc__},
812     {NULL}  /* Sentinel */
813 };
814
815 static PyTypeObject ReaderType = {
816     PyVarObject_HEAD_INIT(NULL, 0)
817     "_reader._Reader",                        /*tp_name*/
818     sizeof(Reader),                           /*tp_basicsize*/
819     0,                                        /*tp_itemsize*/
820     (destructor)Reader_dealloc,               /*tp_dealloc*/
821     0,                                        /*tp_print*/
822     0,                                        /*tp_getattr*/
823     0,                                        /*tp_setattr*/
824     0,                                        /*tp_compare*/
825     0,                                        /*tp_repr*/
826     0,                                        /*tp_as_number*/
827     0,                                        /*tp_as_sequence*/
828     0,                                        /*tp_as_mapping*/
829     0,                                        /*tp_hash */
830     0,                                        /*tp_call*/
831     0,                                        /*tp_str*/
832     0,                                        /*tp_getattro*/
833     0,                                        /*tp_setattro*/
834     0,                                        /*tp_as_buffer*/
835     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
836     Reader__doc__,                            /* tp_doc */
837     0,                                        /* tp_traverse */
838     0,                                        /* tp_clear */
839     0,                                        /* tp_richcompare */
840     0,                                        /* tp_weaklistoffset */
841     Reader_iter,                              /* tp_iter */
842     Reader_iternext,                          /* tp_iternext */
843     Reader_methods,                           /* tp_methods */
844     0,                                        /* tp_members */
845     Reader_getsetters,                        /* tp_getset */
846     0,                                        /* tp_base */
847     0,                                        /* tp_dict */
848     0,                                        /* tp_descr_get */
849     0,                                        /* tp_descr_set */
850     0,                                        /* tp_dictoffset */
851     (initproc) Reader_init,                   /* tp_init */
852     0,                                        /* tp_alloc */
853     PyType_GenericNew,                        /* tp_new */
854 };
855
856 static PyMethodDef methods[] = {
857         { "get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__},
858         { NULL, NULL, 0, NULL }        /* Sentinel */
859 };
860
861 #if PY_MAJOR_VERSION >= 3
862 static PyModuleDef module = {
863     PyModuleDef_HEAD_INIT,
864     "_reader",
865     module__doc__,
866     -1,
867     methods,
868     NULL, NULL, NULL, NULL
869 };
870 #endif
871
872 #if PY_MAJOR_VERSION >= 3
873 static bool initialized = false;
874 #endif
875
876 #pragma GCC diagnostic push
877 #pragma GCC diagnostic ignored "-Wmissing-prototypes"
878
879 PyMODINIT_FUNC
880 #if PY_MAJOR_VERSION >= 3
881 PyInit__reader(void)
882 #else
883 init_reader(void)
884 #endif
885 {
886     PyObject* m;
887
888     PyDateTime_IMPORT;
889
890     if (PyType_Ready(&ReaderType) < 0)
891 #if PY_MAJOR_VERSION >= 3
892         return NULL;
893 #else
894         return;
895 #endif
896
897 #if PY_MAJOR_VERSION >= 3
898     m = PyModule_Create(&module);
899     if (m == NULL)
900         return NULL;
901
902     if (!initialized) {
903         PyStructSequence_InitType(&MonotonicType, &Monotonic_desc);
904         initialized = true;
905     }
906 #else
907     m = Py_InitModule3("_reader", methods, module__doc__);
908     if (m == NULL)
909         return;
910 #endif
911
912     Py_INCREF(&ReaderType);
913 #if PY_MAJOR_VERSION >= 3
914     Py_INCREF(&MonotonicType);
915 #endif
916     if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) ||
917 #if PY_MAJOR_VERSION >= 3
918         PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) ||
919 #endif
920         PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) ||
921         PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) ||
922         PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) ||
923         PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) ||
924         PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) ||
925         PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY)) {
926 #if PY_MAJOR_VERSION >= 3
927         Py_DECREF(m);
928         return NULL;
929 #endif
930     }
931
932 #if PY_MAJOR_VERSION >= 3
933     return m;
934 #endif
935 }
936
937 #pragma GCC diagnostic pop