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