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