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