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