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