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