chiark / gitweb /
650439640489e34cb914f3bca6f1660c3ef27b74
[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 char *kwlist[] = {"flags", "path", NULL};
91     if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iz", 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, "get_next", "L", -skip);
221 }
222
223 PyDoc_STRVAR(Journal_add_match__doc__,
224 "add_match(match) -> None\n\n"
225 "Add a match to filter journal log entries. All matches of different\n"
226 "fields are combined in logical AND, and matches of the same field\n"
227 "are automatically combined in logical OR.\n"
228 "Match is string of form \"field=value\".");
229 static PyObject *
230 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
231 {
232     char *match;
233     int match_len;
234     if (! PyArg_ParseTuple(args, "s#", &match, &match_len))
235         return NULL;
236
237     int r;
238     r = sd_journal_add_match(self->j, match, match_len);
239     set_error(r, NULL, "Invalid match");
240     if (r < 0)
241             return NULL;
242
243     Py_RETURN_NONE;
244 }
245
246 PyDoc_STRVAR(Journal_add_disjunction__doc__,
247 "add_disjunction() -> None\n\n"
248 "Once called, all matches before and after are combined in logical\n"
249 "OR.");
250 static PyObject *
251 Journal_add_disjunction(Journal *self, PyObject *args)
252 {
253     int r;
254     r = sd_journal_add_disjunction(self->j);
255     set_error(r, NULL, NULL);
256     if (r < 0)
257         return NULL;
258     Py_RETURN_NONE;
259 }
260
261 PyDoc_STRVAR(Journal_flush_matches__doc__,
262 "flush_matches() -> None\n\n"
263 "Clears all current match filters.");
264 static PyObject *
265 Journal_flush_matches(Journal *self, PyObject *args)
266 {
267     sd_journal_flush_matches(self->j);
268     Py_RETURN_NONE;
269 }
270
271 PyDoc_STRVAR(Journal_seek__doc__,
272 "seek(offset[, whence]) -> None\n\n"
273 "Seek through journal by `offset` number of entries. Argument\n"
274 "`whence` defines what the offset is relative to:\n"
275 "os.SEEK_SET (default) from first match in journal;\n"
276 "os.SEEK_CUR from current position in journal;\n"
277 "and os.SEEK_END is from last match in journal.");
278 static PyObject *
279 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
280 {
281     int64_t offset;
282     int whence=SEEK_SET;
283     static char *kwlist[] = {"offset", "whence", NULL};
284
285     if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
286                                       &offset, &whence))
287         return NULL;
288
289     PyObject *result=NULL;
290     if (whence == SEEK_SET){
291         int r;
292         Py_BEGIN_ALLOW_THREADS
293         r = sd_journal_seek_head(self->j);
294         Py_END_ALLOW_THREADS
295         if (set_error(r, NULL, NULL))
296             return NULL;
297
298         if (offset > 0LL) {
299             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
300         }
301     }else if (whence == SEEK_CUR){
302         result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
303     }else if (whence == SEEK_END){
304         int r;
305         Py_BEGIN_ALLOW_THREADS
306         r = sd_journal_seek_tail(self->j);
307         Py_END_ALLOW_THREADS
308         if (set_error(r, NULL, NULL))
309             return NULL;
310
311         if (offset < 0LL) {
312             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
313         }else{
314             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
315         }
316     }else{
317         PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
318     }
319
320     Py_XDECREF(result);
321     if (PyErr_Occurred())
322         return NULL;
323     Py_RETURN_NONE;
324 }
325
326 PyDoc_STRVAR(Journal_seek_realtime__doc__,
327 "seek_realtime(realtime) -> None\n\n"
328 "Seek to nearest matching journal entry to `realtime`. Argument\n"
329 "`realtime` can must be an integer unix timestamp.");
330 static PyObject *
331 Journal_seek_realtime(Journal *self, PyObject *args)
332 {
333     double timedouble;
334     if (! PyArg_ParseTuple(args, "d", &timedouble))
335         return NULL;
336
337     uint64_t timestamp;
338     timestamp = (uint64_t) (timedouble * 1.0E6);
339
340     if ((int64_t) timestamp < 0LL) {
341         PyErr_SetString(PyExc_ValueError, "Time must be positive integer");
342         return NULL;
343     }
344
345     int r;
346     Py_BEGIN_ALLOW_THREADS
347     r = sd_journal_seek_realtime_usec(self->j, timestamp);
348     Py_END_ALLOW_THREADS
349     if (set_error(r, NULL, NULL))
350         return NULL;
351     Py_RETURN_NONE;
352 }
353
354 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
355 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
356 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
357 "`monotonic` is an timestamp from boot in seconds.\n"
358 "Argument `bootid` is a string representing which boot the\n"
359 "monotonic time is reference to. Defaults to current bootid.");
360 static PyObject *
361 Journal_seek_monotonic(Journal *self, PyObject *args)
362 {
363     double timedouble;
364     char *bootid=NULL;
365     if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid))
366         return NULL;
367
368     uint64_t timestamp;
369     timestamp = (uint64_t) (timedouble * 1.0E6);
370
371     if ((int64_t) timestamp < 0LL) {
372         PyErr_SetString(PyExc_ValueError, "Time must be positive number");
373         return NULL;
374     }
375
376     sd_id128_t sd_id;
377     int r;
378     if (bootid) {
379         r = sd_id128_from_string(bootid, &sd_id);
380         if (set_error(r, NULL, "Invalid bootid"))
381             return NULL;
382     } else {
383         Py_BEGIN_ALLOW_THREADS
384         r = sd_id128_get_boot(&sd_id);
385         Py_END_ALLOW_THREADS
386         if (set_error(r, NULL, NULL))
387             return NULL;
388     }
389
390     Py_BEGIN_ALLOW_THREADS
391     r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
392     Py_END_ALLOW_THREADS
393     if (set_error(r, NULL, NULL))
394         return NULL;
395     Py_RETURN_NONE;
396 }
397
398 PyDoc_STRVAR(Journal_wait__doc__,
399 "wait([timeout]) -> Change state (integer)\n\n"
400 "Waits until there is a change in the journal. Argument `timeout`\n"
401 "is the maximum number of seconds to wait before returning\n"
402 "regardless if journal has changed. If `timeout` is not given or is\n"
403 "0, then it will block forever.\n"
404 "Will return constants: NOP if no change; APPEND if new\n"
405 "entries have been added to the end of the journal; and\n"
406 "INVALIDATE if journal files have been added or removed.");
407 static PyObject *
408 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
409 {
410     int64_t timeout=0LL;
411     if (! PyArg_ParseTuple(args, "|L", &timeout))
412         return NULL;
413
414     int r;
415     Py_BEGIN_ALLOW_THREADS
416     r = sd_journal_wait(self->j, timeout ==0 ? (uint64_t) -1 : timeout * 1E6);
417     Py_END_ALLOW_THREADS
418     if (set_error(r, NULL, NULL))
419         return NULL;
420
421     return long_FromLong(r);
422 }
423
424 PyDoc_STRVAR(Journal_seek_cursor__doc__,
425 "seek_cursor(cursor) -> None\n\n"
426 "Seeks to journal entry by given unique reference `cursor`.");
427 static PyObject *
428 Journal_seek_cursor(Journal *self, PyObject *args)
429 {
430     const char *cursor;
431     if (! PyArg_ParseTuple(args, "s", &cursor))
432         return NULL;
433
434     int r;
435     Py_BEGIN_ALLOW_THREADS
436     r = sd_journal_seek_cursor(self->j, cursor);
437     Py_END_ALLOW_THREADS
438     if (set_error(r, NULL, "Invalid cursor"))
439         return NULL;
440     Py_RETURN_NONE;
441 }
442
443 static PyObject *
444 Journal_iter(PyObject *self)
445 {
446     Py_INCREF(self);
447     return self;
448 }
449
450 static PyObject *
451 Journal_iternext(PyObject *self)
452 {
453     PyObject *dict;
454     Py_ssize_t dict_size;
455
456     dict = PyObject_CallMethod(self, "get_next", "");
457     if (PyErr_Occurred())
458         return NULL;
459     dict_size = PyDict_Size(dict);
460     if ((int64_t) dict_size > 0LL) {
461         return dict;
462     }else{
463         Py_DECREF(dict);
464         PyErr_SetNone(PyExc_StopIteration);
465         return NULL;
466     }
467 }
468
469 PyDoc_STRVAR(Journal_query_unique__doc__,
470 "query_unique(field) -> a set of values\n\n"
471 "Returns a set of unique values in journal for given `field`.\n"
472 "Note this does not respect any journal matches.");
473 static PyObject *
474 Journal_query_unique(Journal *self, PyObject *args)
475 {
476     char *query;
477     if (! PyArg_ParseTuple(args, "s", &query))
478         return NULL;
479
480     int r;
481     Py_BEGIN_ALLOW_THREADS
482     r = sd_journal_query_unique(self->j, query);
483     Py_END_ALLOW_THREADS
484     if (set_error(r, NULL, "Invalid field name"))
485         return NULL;
486
487     const void *uniq;
488     size_t uniq_len;
489     const char *delim_ptr;
490     PyObject *value_set, *key, *value;
491     value_set = PySet_New(0);
492     key = unicode_FromString(query);
493
494     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
495         delim_ptr = memchr(uniq, '=', uniq_len);
496         value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
497         PySet_Add(value_set, value);
498         Py_DECREF(value);
499     }
500     Py_DECREF(key);
501     return value_set;
502 }
503
504 static PyObject *
505 Journal_get_data_threshold(Journal *self, void *closure)
506 {
507     size_t cvalue;
508     PyObject *value;
509     int r;
510
511     r = sd_journal_get_data_threshold(self->j, &cvalue);
512     if (set_error(r, NULL, NULL))
513         return NULL;
514
515     return long_FromSize_t(cvalue);
516 }
517
518 static int
519 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
520 {
521     if (value == NULL) {
522         PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
523         return -1;
524     }
525     if (!long_Check(value)){
526         PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
527         return -1;
528     }
529     int r;
530     r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value));
531     return set_error(r, NULL, NULL);
532 }
533
534 static PyGetSetDef Journal_getseters[] = {
535     {"data_threshold",
536     (getter)Journal_get_data_threshold,
537     (setter)Journal_set_data_threshold,
538     "data threshold",
539     NULL},
540     {NULL}
541 };
542
543 static PyMethodDef Journal_methods[] = {
544     {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
545     Journal_get_next__doc__},
546     {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
547     Journal_get_previous__doc__},
548     {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
549     Journal_add_match__doc__},
550     {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
551     Journal_add_disjunction__doc__},
552     {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
553     Journal_flush_matches__doc__},
554     {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
555     Journal_seek__doc__},
556     {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
557     Journal_seek_realtime__doc__},
558     {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
559     Journal_seek_monotonic__doc__},
560     {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
561     Journal_wait__doc__},
562     {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
563     Journal_seek_cursor__doc__},
564     {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
565     Journal_query_unique__doc__},
566     {NULL}  /* Sentinel */
567 };
568
569 static PyTypeObject JournalType = {
570     PyVarObject_HEAD_INIT(NULL, 0)
571     "_reader._Journal",               /*tp_name*/
572     sizeof(Journal),                  /*tp_basicsize*/
573     0,                                /*tp_itemsize*/
574     (destructor)Journal_dealloc,      /*tp_dealloc*/
575     0,                                /*tp_print*/
576     0,                                /*tp_getattr*/
577     0,                                /*tp_setattr*/
578     0,                                /*tp_compare*/
579     0,                                /*tp_repr*/
580     0,                                /*tp_as_number*/
581     0,                                /*tp_as_sequence*/
582     0,                                /*tp_as_mapping*/
583     0,                                /*tp_hash */
584     0,                                /*tp_call*/
585     0,                                /*tp_str*/
586     0,                                /*tp_getattro*/
587     0,                                /*tp_setattro*/
588     0,                                /*tp_as_buffer*/
589     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
590     Journal__doc__,                   /* tp_doc */
591     0,                                /* tp_traverse */
592     0,                                /* tp_clear */
593     0,                                /* tp_richcompare */
594     0,                                /* tp_weaklistoffset */
595     Journal_iter,                     /* tp_iter */
596     Journal_iternext,                 /* tp_iternext */
597     Journal_methods,                  /* tp_methods */
598     0,                                /* tp_members */
599     Journal_getseters,                /* tp_getset */
600     0,                                /* tp_base */
601     0,                                /* tp_dict */
602     0,                                /* tp_descr_get */
603     0,                                /* tp_descr_set */
604     0,                                /* tp_dictoffset */
605     (initproc)Journal_init,           /* tp_init */
606     0,                                /* tp_alloc */
607     PyType_GenericNew,                /* tp_new */
608 };
609
610 #if PY_MAJOR_VERSION >= 3
611 static PyModuleDef _reader_module = {
612     PyModuleDef_HEAD_INIT,
613     "_reader",
614     "Module that reads systemd journal similar to journalctl.",
615     -1,
616     NULL, NULL, NULL, NULL, NULL
617 };
618 #endif
619
620 PyMODINIT_FUNC
621 #if PY_MAJOR_VERSION >= 3
622 PyInit__reader(void)
623 #else
624 init_reader(void) 
625 #endif
626 {
627     PyObject* m;
628
629     PyDateTime_IMPORT;
630
631     if (PyType_Ready(&JournalType) < 0)
632 #if PY_MAJOR_VERSION >= 3
633         return NULL;
634 #else
635         return;
636 #endif
637
638 #if PY_MAJOR_VERSION >= 3
639     m = PyModule_Create(&_reader_module);
640     if (m == NULL)
641         return NULL;
642 #else
643     m = Py_InitModule3("_reader", NULL,
644                    "Module that reads systemd journal similar to journalctl.");
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 }