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