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