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