chiark / gitweb /
systemd-python: some python3 and bug fixes
[elogind.git] / src / python-systemd / _reader.c
1 /*
2 _reader - Python module that reads systemd journal similar to journalctl
3 Copyright (C) 2012  Steven Hiscocks
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Lesser General Public
7 License as published by the Free Software Foundation; either
8 version 2.1 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 Lesser General Public License for more details.
14
15 You should have received a copy of the GNU Lesser General Public
16 License along with this library; if not, write to the Free Software
17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18 */
19 #include <systemd/sd-journal.h>
20
21 #include <Python.h>
22 #include <structmember.h>
23 #include <datetime.h>
24
25 typedef struct {
26     PyObject_HEAD
27     sd_journal *j;
28 } Journal;
29 static PyTypeObject JournalType;
30
31 static void
32 Journal_dealloc(Journal* self)
33 {
34     sd_journal_close(self->j);
35     Py_TYPE(self)->tp_free((PyObject*)self);
36 }
37
38 PyDoc_STRVAR(Journal__doc__,
39 "Journal([flags][,path]) -> ...\n"
40 "Journal instance\n\n"
41 "Returns instance of Journal, which allows filtering and return\n"
42 "of journal entries.\n"
43 "Argument `flags` sets open flags of the journal, which can be one\n"
44 "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n"
45 "journal on local machine only; RUNTIME_ONLY opens only\n"
46 "volatile journal files; and SYSTEM_ONLY opens only\n"
47 "journal files of system services and the kernel.\n"
48 "Argument `path` is the directory of journal files. Note that\n"
49 "currently flags are ignored when `path` is present as they are\n"
50 " not relevant.");
51 static int
52 Journal_init(Journal *self, PyObject *args, PyObject *keywds)
53 {
54     int flags=SD_JOURNAL_LOCAL_ONLY;
55     char *path=NULL;
56
57     static char *kwlist[] = {"flags", NULL};
58     if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist,
59                                       &flags, &path))
60         return 1;
61
62     int r;
63     if (path) {
64         r = sd_journal_open_directory(&self->j, path, 0);
65     }else{
66         Py_BEGIN_ALLOW_THREADS
67         r = sd_journal_open(&self->j, flags);
68         Py_END_ALLOW_THREADS
69     }
70     if (r == -EINVAL) {
71         PyErr_SetString(PyExc_ValueError, "Invalid flags or path");
72         return -1;
73     }else if (r == -ENOMEM) {
74         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
75         return 1;
76     }else if (r < 0) {
77         PyErr_SetString(PyExc_RuntimeError, "Error opening journal");
78         return 1;
79     }
80
81     return 0;
82 }
83
84 PyDoc_STRVAR(Journal_get_next__doc__,
85 "get_next([skip]) -> dict\n\n"
86 "Return dictionary of the next log entry. Optional skip value will\n"
87 "return the `skip`th log entry.");
88 static PyObject *
89 Journal_get_next(Journal *self, PyObject *args)
90 {
91     int64_t skip=1LL;
92     if (! PyArg_ParseTuple(args, "|L", &skip))
93         return NULL;
94
95     int r;
96     if (skip == 1LL) {
97         Py_BEGIN_ALLOW_THREADS
98         r = sd_journal_next(self->j);
99         Py_END_ALLOW_THREADS
100     }else if (skip == -1LL) {
101         Py_BEGIN_ALLOW_THREADS
102         r = sd_journal_previous(self->j);
103         Py_END_ALLOW_THREADS
104     }else if (skip > 1LL) {
105         Py_BEGIN_ALLOW_THREADS
106         r = sd_journal_next_skip(self->j, skip);
107         Py_END_ALLOW_THREADS
108     }else if (skip < -1LL) {
109         Py_BEGIN_ALLOW_THREADS
110         r = sd_journal_previous_skip(self->j, -skip);
111         Py_END_ALLOW_THREADS
112     }else{
113         PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer");
114         return NULL;
115     }
116
117     if (r < 0) {
118         PyErr_SetString(PyExc_RuntimeError, "Error getting next message");
119         return NULL;
120     }else if ( r == 0) { //EOF
121         return PyDict_New();
122     }
123
124     PyObject *dict;
125     dict = PyDict_New();
126
127     const void *msg;
128     size_t msg_len;
129     const char *delim_ptr;
130     PyObject *key, *value, *cur_value, *tmp_list;
131
132     SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) {
133         delim_ptr = memchr(msg, '=', msg_len);
134 #if PY_MAJOR_VERSION >=3
135         key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg);
136 #else
137         key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg);
138 #endif
139         value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) );
140         if (PyDict_Contains(dict, key)) {
141             cur_value = PyDict_GetItem(dict, key);
142             if (PyList_CheckExact(cur_value)) {
143                 PyList_Append(cur_value, value);
144             }else{
145                 tmp_list = PyList_New(0);
146                 PyList_Append(tmp_list, cur_value);
147                 PyList_Append(tmp_list, value);
148                 PyDict_SetItem(dict, key, tmp_list);
149                 Py_DECREF(tmp_list);
150             }
151         }else{
152             PyDict_SetItem(dict, key, value);
153         }
154         Py_DECREF(key);
155         Py_DECREF(value);
156     }
157
158     uint64_t realtime;
159     if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) {
160         char realtime_str[20];
161         sprintf(realtime_str, "%llu", (long long unsigned) realtime);
162
163 #if PY_MAJOR_VERSION >=3
164         key = PyUnicode_FromString("__REALTIME_TIMESTAMP");
165 #else
166         key = PyString_FromString("__REALTIME_TIMESTAMP");
167 #endif
168         value = PyBytes_FromString(realtime_str);
169         PyDict_SetItem(dict, key, value);
170         Py_DECREF(key);
171         Py_DECREF(value);
172     }
173
174     sd_id128_t sd_id;
175     uint64_t monotonic;
176     if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) {
177         char monotonic_str[20];
178         sprintf(monotonic_str, "%llu", (long long unsigned) monotonic);
179 #if PY_MAJOR_VERSION >=3
180         key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP");
181 #else
182         key = PyString_FromString("__MONOTONIC_TIMESTAMP");
183 #endif
184         value = PyBytes_FromString(monotonic_str);
185
186         PyDict_SetItem(dict, key, value);
187         Py_DECREF(key);
188         Py_DECREF(value);
189     }
190
191     char *cursor;
192     if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0...
193 #if PY_MAJOR_VERSION >=3
194         key = PyUnicode_FromString("__CURSOR");
195 #else
196         key = PyString_FromString("__CURSOR");
197 #endif
198         value = PyBytes_FromString(cursor);
199         PyDict_SetItem(dict, key, value);
200         free(cursor);
201         Py_DECREF(key);
202         Py_DECREF(value);
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 *
213 Journal_get_previous(Journal *self, PyObject *args)
214 {
215     int64_t skip=1LL;
216     if (! PyArg_ParseTuple(args, "|L", &skip))
217         return NULL;
218
219     return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip);
220 }
221
222 PyDoc_STRVAR(Journal_add_match__doc__,
223 "add_match(match, ..., field=value, ...) -> None\n\n"
224 "Add a match to filter journal log entries. All matches of different\n"
225 "field are combined in logical AND, and matches of the same field\n"
226 "are automatically combined in logical OR.\n"
227 "Matches can be passed as strings \"field=value\", or keyword\n"
228 "arguments field=\"value\".");
229 static PyObject *
230 Journal_add_match(Journal *self, PyObject *args, PyObject *keywds)
231 {
232     Py_ssize_t arg_match_len;
233     char *arg_match;
234     int i, r;
235     for (i = 0; i < PySequence_Size(args); i++) {
236 #if PY_MAJOR_VERSION >=3
237         PyObject *arg;
238         arg = PySequence_Fast_GET_ITEM(args, i);
239         if (PyUnicode_Check(arg)) {
240 #if PY_MINOR_VERSION >=3
241             arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len);
242 #else
243             PyObject *temp;
244             temp = PyUnicode_AsUTF8String(arg);
245             PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len);
246             Py_DECREF(temp);
247 #endif
248         }else if (PyBytes_Check(arg)) {
249             PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len);
250         }else{
251             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
252         }
253 #else
254         PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len);
255 #endif
256         if (PyErr_Occurred())
257             return NULL;
258         r = sd_journal_add_match(self->j, arg_match, arg_match_len);
259         if (r == -EINVAL) {
260             PyErr_SetString(PyExc_ValueError, "Invalid match");
261             return NULL;
262         }else if (r == -ENOMEM) {
263             PyErr_SetString(PyExc_MemoryError, "Not enough memory");
264             return NULL;
265         }else if (r < 0) {
266             PyErr_SetString(PyExc_RuntimeError, "Error adding match");
267             return NULL;
268         }
269     }
270
271     if (! keywds)
272         Py_RETURN_NONE;
273
274     PyObject *key, *value;
275     Py_ssize_t pos=0, match_key_len, match_value_len;
276     int match_len;
277     char *match_key, *match_value;
278     void *match;
279     while (PyDict_Next(keywds, &pos, &key, &value)) {
280 #if PY_MAJOR_VERSION >=3
281         if (PyUnicode_Check(key)) {
282 #if PY_MINOR_VERSION >=3
283             match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len);
284 #else
285             PyObject *temp2;
286             temp2 = PyUnicode_AsUTF8String(key);
287             PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len);
288             Py_DECREF(temp2);
289 #endif
290         }else if (PyBytes_Check(key)) {
291             PyBytes_AsStringAndSize(key, &match_key, &match_key_len);
292         }else{
293             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
294         }
295         if (PyUnicode_Check(value)) {
296 #if PY_MINOR_VERSION >=3
297             match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len);
298 #else
299             PyObject *temp3;
300             temp3 = PyUnicode_AsUTF8String(value);
301             PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len);
302             Py_DECREF(temp3);
303 #endif
304         }else if (PyBytes_Check(value)) {
305             PyBytes_AsStringAndSize(value, &match_value, &match_value_len);
306         }else{
307             PyErr_SetString(PyExc_TypeError, "expected bytes or string");
308         }
309 #else
310         PyString_AsStringAndSize(key, &match_key, &match_key_len);
311         PyString_AsStringAndSize(value, &match_value, &match_value_len);
312 #endif
313         if (PyErr_Occurred())
314             return NULL;
315
316         match_len = match_key_len + 1 + match_value_len;
317         match = malloc(match_len);
318         memcpy(match, match_key, match_key_len);
319         memcpy(match + match_key_len, "=", 1);
320         memcpy(match + match_key_len + 1, match_value, match_value_len);
321
322         r = sd_journal_add_match(self->j, match, match_len);
323         free(match);
324         if (r == -EINVAL) {
325             PyErr_SetString(PyExc_ValueError, "Invalid match");
326             return NULL;
327         }else if (r == -ENOMEM) {
328             PyErr_SetString(PyExc_MemoryError, "Not enough memory");
329             return NULL;
330         }else if (r < 0) {
331             PyErr_SetString(PyExc_RuntimeError, "Error adding match");
332             return NULL;
333         }
334     }
335
336     Py_RETURN_NONE;
337 }
338
339 PyDoc_STRVAR(Journal_add_disjunction__doc__,
340 "add_disjunction() -> None\n\n"
341 "Once called, all matches before and after are combined in logical\n"
342 "OR.");
343 static PyObject *
344 Journal_add_disjunction(Journal *self, PyObject *args)
345 {
346     int r;
347     r = sd_journal_add_disjunction(self->j);
348     if (r == -ENOMEM) {
349         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
350         return NULL;
351     }else if (r < 0) {
352         PyErr_SetString(PyExc_RuntimeError, "Error adding disjunction");
353         return NULL;
354     }
355     Py_RETURN_NONE;
356 }
357
358 PyDoc_STRVAR(Journal_flush_matches__doc__,
359 "flush_matches() -> None\n\n"
360 "Clears all current match filters.");
361 static PyObject *
362 Journal_flush_matches(Journal *self, PyObject *args)
363 {
364     sd_journal_flush_matches(self->j);
365     Py_RETURN_NONE;
366 }
367
368 PyDoc_STRVAR(Journal_seek__doc__,
369 "seek(offset[, whence]) -> None\n\n"
370 "Seek through journal by `offset` number of entries. Argument\n"
371 "`whence` defines what the offset is relative to:\n"
372 "os.SEEK_SET (default) from first match in journal;\n"
373 "os.SEEK_CUR from current position in journal;\n"
374 "and os.SEEK_END is from last match in journal.");
375 static PyObject *
376 Journal_seek(Journal *self, PyObject *args, PyObject *keywds)
377 {
378     int64_t offset;
379     int whence=SEEK_SET;
380     static char *kwlist[] = {"offset", "whence", NULL};
381
382     if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist,
383                                       &offset, &whence))
384         return NULL;
385
386     PyObject *result=NULL;
387     if (whence == SEEK_SET){
388         int r;
389         Py_BEGIN_ALLOW_THREADS
390         r = sd_journal_seek_head(self->j);
391         Py_END_ALLOW_THREADS
392         if (r < 0) {
393             PyErr_SetString(PyExc_RuntimeError, "Error seeking to head");
394             return NULL;
395         }
396         if (offset > 0LL) {
397             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
398         }
399     }else if (whence == SEEK_CUR){
400         result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
401     }else if (whence == SEEK_END){
402         int r;
403         Py_BEGIN_ALLOW_THREADS
404         r = sd_journal_seek_tail(self->j);
405         Py_END_ALLOW_THREADS
406         if (r < 0) {
407             PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail");
408             return NULL;
409         }
410         if (offset < 0LL) {
411             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset);
412         }else{
413             result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL);
414         }
415     }else{
416         PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
417     }
418
419     if (result)
420         Py_DECREF(result);
421     if (PyErr_Occurred())
422         return NULL;
423     Py_RETURN_NONE;
424 }
425
426 PyDoc_STRVAR(Journal_seek_realtime__doc__,
427 "seek_realtime(realtime) -> None\n\n"
428 "Seek to nearest matching journal entry to `realtime`. Argument\n"
429 "`realtime` can be an integer unix timestamp in usecs or a "
430 "datetime instance.");
431 static PyObject *
432 Journal_seek_realtime(Journal *self, PyObject *args)
433 {
434     PyObject *arg;
435     if (! PyArg_ParseTuple(args, "O", &arg))
436         return NULL;
437
438     uint64_t timestamp=-1LL;
439     if (PyDateTime_Check(arg)) {
440         PyObject *temp;
441         char *timestamp_str;
442         temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f");
443 #if PY_MAJOR_VERSION >=3
444         PyObject *temp2;
445         temp2 = PyUnicode_AsUTF8String(temp);
446         timestamp_str = PyBytes_AsString(temp2);
447         Py_DECREF(temp2);
448 #else
449         timestamp_str = PyString_AsString(temp);
450 #endif
451         Py_DECREF(temp);
452         timestamp = strtoull(timestamp_str, NULL, 10);
453     }else if (PyLong_Check(arg)) {
454         timestamp = PyLong_AsUnsignedLongLong(arg);
455 #if PY_MAJOR_VERSION <3
456     }else if (PyInt_Check(arg)) {
457         timestamp = PyInt_AsUnsignedLongLongMask(arg);
458 #endif
459     }
460     if ((int64_t) timestamp < 0LL) {
461         PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance");
462         return NULL;
463     }
464
465     int r;
466     Py_BEGIN_ALLOW_THREADS
467     r = sd_journal_seek_realtime_usec(self->j, timestamp);
468     Py_END_ALLOW_THREADS
469     if (r < 0) {
470         PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
471         return NULL;
472     }
473     Py_RETURN_NONE;
474 }
475
476 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
477 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
478 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
479 "`monotonic` is an timestamp from boot in secs, or a\n"
480 "timedelta instance.\n"
481 "Argument `bootid` is a string representing which boot the\n"
482 "monotonic time is reference to. Defaults to current bootid.");
483 static PyObject *
484 Journal_seek_monotonic(Journal *self, PyObject *args)
485 {
486     PyObject *arg;
487     char *bootid=NULL;
488     if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid))
489         return NULL;
490
491     uint64_t timestamp=-1LL;
492     if PyDelta_Check(arg) {
493         PyObject *temp;
494         temp = PyObject_CallMethod(arg, "total_seconds", NULL);
495         timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6);
496         Py_DECREF(temp);
497     }else if (PyFloat_Check(arg)) {
498         timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6);
499     }else if (PyLong_Check(arg)) {
500         timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6;
501 #if PY_MAJOR_VERSION <3
502     }else if (PyInt_Check(arg)) {
503         timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6;
504 #endif
505
506     }
507
508     if ((int64_t) timestamp < 0LL) {
509         PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance");
510         return NULL;
511     }
512
513     sd_id128_t sd_id;
514     int r;
515     if (bootid) {
516         r = sd_id128_from_string(bootid, &sd_id);
517         if (r == -EINVAL) {
518             PyErr_SetString(PyExc_ValueError, "Invalid bootid");
519             return NULL;
520         } else if (r < 0) {
521             PyErr_SetString(PyExc_RuntimeError, "Error processing bootid");
522             return NULL;
523         }
524     }else{
525         r = sd_id128_get_boot(&sd_id);
526         if (r == -EIO) {
527             PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
528             return NULL;
529         } else if (r < 0) {
530             PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
531             return NULL;
532         }
533     }
534
535     Py_BEGIN_ALLOW_THREADS
536     r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
537     Py_END_ALLOW_THREADS
538     if (r < 0) {
539         PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
540         return NULL;
541     }
542     Py_RETURN_NONE;
543 }
544  
545 PyDoc_STRVAR(Journal_wait__doc__,
546 "wait([timeout]) -> Change state (integer)\n\n"
547 "Waits until there is a change in the journal. Argument `timeout`\n"
548 "is the maximum number of seconds to wait before returning\n"
549 "regardless if journal has changed. If `timeout` is not given or is\n"
550 "0, then it will block forever.\n"
551 "Will return constants: NOP if no change; APPEND if new\n"
552 "entries have been added to the end of the journal; and\n"
553 "INVALIDATE if journal files have been added or removed.");
554 static PyObject *
555 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
556 {
557     int64_t timeout=0LL;
558     if (! PyArg_ParseTuple(args, "|L", &timeout))
559         return NULL;
560
561     int r;
562     if ( timeout == 0LL) {
563         Py_BEGIN_ALLOW_THREADS
564         r = sd_journal_wait(self->j, (uint64_t) -1);
565         Py_END_ALLOW_THREADS
566     }else{
567         Py_BEGIN_ALLOW_THREADS
568         r = sd_journal_wait(self->j, timeout * 1E6);
569         Py_END_ALLOW_THREADS
570     }
571 #if PY_MAJOR_VERSION >=3
572     return PyLong_FromLong(r);
573 #else
574     return PyInt_FromLong(r);
575 #endif
576 }
577
578 PyDoc_STRVAR(Journal_seek_cursor__doc__,
579 "seek_cursor(cursor) -> None\n\n"
580 "Seeks to journal entry by given unique reference `cursor`.");
581 static PyObject *
582 Journal_seek_cursor(Journal *self, PyObject *args)
583 {
584     const char *cursor;
585     if (! PyArg_ParseTuple(args, "s", &cursor))
586         return NULL;
587
588     int r;
589     Py_BEGIN_ALLOW_THREADS
590     r = sd_journal_seek_cursor(self->j, cursor);
591     Py_END_ALLOW_THREADS
592     if (r == -EINVAL) {
593         PyErr_SetString(PyExc_ValueError, "Invalid cursor");
594         return NULL;
595     }else if (r == -ENOMEM) {
596         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
597         return NULL;
598     }else if (r < 0) {
599         PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor");
600         return NULL;
601     }
602     Py_RETURN_NONE;
603 }
604
605 static PyObject *
606 Journal_iter(PyObject *self)
607 {
608     Py_INCREF(self);
609     return self;
610 }
611
612 static PyObject *
613 Journal_iternext(PyObject *self)
614 {
615     PyObject *dict;
616     Py_ssize_t dict_size;
617
618     dict = PyObject_CallMethod(self, "get_next", "");
619     dict_size = PyDict_Size(dict);
620     if ((int64_t) dict_size > 0LL) {
621         return dict;
622     }else{
623         Py_DECREF(dict);
624         PyErr_SetNone(PyExc_StopIteration);
625         return NULL;
626     }
627 }
628
629 #ifdef SD_JOURNAL_FOREACH_UNIQUE
630 PyDoc_STRVAR(Journal_query_unique__doc__,
631 "query_unique(field) -> a set of values\n\n"
632 "Returns a set of unique values in journal for given `field`.\n"
633 "Note this does not respect any journal matches.");
634 static PyObject *
635 Journal_query_unique(Journal *self, PyObject *args)
636 {
637     char *query;
638     if (! PyArg_ParseTuple(args, "s", &query))
639         return NULL;
640
641     int r;
642     Py_BEGIN_ALLOW_THREADS
643     r = sd_journal_query_unique(self->j, query);
644     Py_END_ALLOW_THREADS
645     if (r == -EINVAL) {
646         PyErr_SetString(PyExc_ValueError, "Invalid field name");
647         return NULL;
648     } else if (r == -ENOMEM) {
649         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
650         return NULL;
651     } else if (r < 0) {
652         PyErr_SetString(PyExc_RuntimeError, "Error querying journal");
653         return NULL;
654     }
655
656     const void *uniq;
657     size_t uniq_len;
658     const char *delim_ptr;
659     PyObject *value_set, *key, *value;
660     value_set = PySet_New(0);
661
662 #if PY_MAJOR_VERSION >=3
663     key = PyUnicode_FromString(query);
664 #else
665     key = PyString_FromString(query);
666 #endif
667
668     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
669         delim_ptr = memchr(uniq, '=', uniq_len);
670         value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
671         PySet_Add(value_set, value);
672         Py_DECREF(value);
673     }
674     Py_DECREF(key);
675     return value_set;
676 }
677 #endif //def SD_JOURNAL_FOREACH_UNIQUE
678
679 static PyObject *
680 Journal_get_data_threshold(Journal *self, void *closure)
681 {
682     size_t cvalue;
683     PyObject *value;
684     int r;
685
686     r = sd_journal_get_data_threshold(self->j, &cvalue);
687     if (r < 0){
688         PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold");
689         return NULL;
690     }
691
692 #if PY_MAJOR_VERSION >=3
693     value = PyLong_FromSize_t(cvalue);
694 #else
695     value = PyInt_FromSize_t(cvalue);
696 #endif
697     return value;
698 }
699
700 static int
701 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
702 {
703     if (value == NULL) {
704         PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
705         return -1;
706     }
707 #if PY_MAJOR_VERSION >=3
708     if (! PyLong_Check(value)){
709 #else
710     if (! PyInt_Check(value)){
711 #endif
712         PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
713         return -1;
714     }
715     int r;
716 #if PY_MAJOR_VERSION >=3
717     r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
718 #else
719     r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
720 #endif
721     if (r < 0){
722         PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold");
723         return -1;
724     }
725     return 0;
726 }
727
728 static PyGetSetDef Journal_getseters[] = {
729     {"data_threshold",
730     (getter)Journal_get_data_threshold,
731     (setter)Journal_set_data_threshold,
732     "data threshold",
733     NULL},
734     {NULL}
735 };
736
737 static PyMethodDef Journal_methods[] = {
738     {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
739     Journal_get_next__doc__},
740     {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
741     Journal_get_previous__doc__},
742     {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
743     Journal_add_match__doc__},
744     {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
745     Journal_add_disjunction__doc__},
746     {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
747     Journal_flush_matches__doc__},
748     {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
749     Journal_seek__doc__},
750     {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
751     Journal_seek_realtime__doc__},
752     {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
753     Journal_seek_monotonic__doc__},
754     {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
755     Journal_wait__doc__},
756     {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
757     Journal_seek_cursor__doc__},
758 #ifdef SD_JOURNAL_FOREACH_UNIQUE
759     {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
760     Journal_query_unique__doc__},
761 #endif
762     {NULL}  /* Sentinel */
763 };
764
765 static PyTypeObject JournalType = {
766     PyVarObject_HEAD_INIT(NULL, 0)
767     "_reader.Journal",           /*tp_name*/
768     sizeof(Journal),                  /*tp_basicsize*/
769     0,                                /*tp_itemsize*/
770     (destructor)Journal_dealloc,      /*tp_dealloc*/
771     0,                                /*tp_print*/
772     0,                                /*tp_getattr*/
773     0,                                /*tp_setattr*/
774     0,                                /*tp_compare*/
775     0,                                /*tp_repr*/
776     0,                                /*tp_as_number*/
777     0,                                /*tp_as_sequence*/
778     0,                                /*tp_as_mapping*/
779     0,                                /*tp_hash */
780     0,                                /*tp_call*/
781     0,                                /*tp_str*/
782     0,                                /*tp_getattro*/
783     0,                                /*tp_setattro*/
784     0,                                /*tp_as_buffer*/
785     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
786     Journal__doc__,                   /* tp_doc */
787     0,                                /* tp_traverse */
788     0,                                /* tp_clear */
789     0,                                /* tp_richcompare */
790     0,                                /* tp_weaklistoffset */
791     Journal_iter,                     /* tp_iter */
792     Journal_iternext,                 /* tp_iternext */
793     Journal_methods,                  /* tp_methods */
794     0,                                /* tp_members */
795     Journal_getseters,                /* tp_getset */
796     0,                                /* tp_base */
797     0,                                /* tp_dict */
798     0,                                /* tp_descr_get */
799     0,                                /* tp_descr_set */
800     0,                                /* tp_dictoffset */
801     (initproc)Journal_init,           /* tp_init */
802     0,                                /* tp_alloc */
803     PyType_GenericNew,                /* tp_new */
804 };
805
806 #if PY_MAJOR_VERSION >= 3
807 static PyModuleDef _reader_module = {
808     PyModuleDef_HEAD_INIT,
809     "_reader",
810     "Module that reads systemd journal similar to journalctl.",
811     -1,
812     NULL, NULL, NULL, NULL, NULL
813 };
814 #endif
815
816 PyMODINIT_FUNC
817 #if PY_MAJOR_VERSION >= 3
818 PyInit__reader(void)
819 #else
820 init_reader(void) 
821 #endif
822 {
823     PyObject* m;
824
825     PyDateTime_IMPORT;
826
827     if (PyType_Ready(&JournalType) < 0)
828 #if PY_MAJOR_VERSION >= 3
829         return NULL;
830 #else
831         return;
832 #endif
833
834 #if PY_MAJOR_VERSION >= 3
835     m = PyModule_Create(&_reader_module);
836     if (m == NULL)
837         return NULL;
838 #else
839     m = Py_InitModule3("_reader", NULL,
840                    "Module that reads systemd journal similar to journalctl.");
841     if (m == NULL)
842         return;
843 #endif
844
845     Py_INCREF(&JournalType);
846     PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
847     PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
848     PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
849     PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
850     PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
851     PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
852     PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
853
854 #if PY_MAJOR_VERSION >= 3
855     return m;
856 #endif
857 }