chiark / gitweb /
systemd-python: Journal log_level moved to python
[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 *arg;
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             Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset));
398         }
399     }else if (whence == SEEK_CUR){
400         Py_DECREF(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         Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL));
411         if (offset < 0LL) {
412             Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset));
413         }
414     }else{
415         PyErr_SetString(PyExc_ValueError, "Invalid value for whence");
416         return NULL;
417     }
418     Py_RETURN_NONE;
419 }
420
421 PyDoc_STRVAR(Journal_seek_realtime__doc__,
422 "seek_realtime(realtime) -> None\n\n"
423 "Seek to nearest matching journal entry to `realtime`. Argument\n"
424 "`realtime` can be an integer unix timestamp in usecs or a "
425 "datetime instance.");
426 static PyObject *
427 Journal_seek_realtime(Journal *self, PyObject *args)
428 {
429     PyObject *arg;
430     if (! PyArg_ParseTuple(args, "O", &arg))
431         return NULL;
432
433     uint64_t timestamp=-1LL;
434     if (PyDateTime_Check(arg)) {
435         PyObject *temp;
436         char *timestamp_str;
437         temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f");
438 #if PY_MAJOR_VERSION >=3
439         PyObject *temp2;
440         temp2 = PyUnicode_AsUTF8String(temp);
441         timestamp_str = PyBytes_AsString(temp2);
442         Py_DECREF(temp2);
443 #else
444         timestamp_str = PyString_AsString(temp);
445 #endif
446         Py_DECREF(temp);
447         timestamp = strtoull(timestamp_str, NULL, 10);
448     }else if (PyLong_Check(arg)) {
449         timestamp = PyLong_AsUnsignedLongLong(arg);
450 #if PY_MAJOR_VERSION <3
451     }else if (PyInt_Check(arg)) {
452         timestamp = PyInt_AsUnsignedLongLongMask(arg);
453 #endif
454     }
455     if ((int64_t) timestamp < 0LL) {
456         PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance");
457         return NULL;
458     }
459
460     int r;
461     Py_BEGIN_ALLOW_THREADS
462     r = sd_journal_seek_realtime_usec(self->j, timestamp);
463     Py_END_ALLOW_THREADS
464     if (r < 0) {
465         PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
466         return NULL;
467     }
468     Py_RETURN_NONE;
469 }
470
471 PyDoc_STRVAR(Journal_seek_monotonic__doc__,
472 "seek_monotonic(monotonic[, bootid]) -> None\n\n"
473 "Seek to nearest matching journal entry to `monotonic`. Argument\n"
474 "`monotonic` is an timestamp from boot in secs, or a\n"
475 "timedelta instance.\n"
476 "Argument `bootid` is a string representing which boot the\n"
477 "monotonic time is reference to. Defaults to current bootid.");
478 static PyObject *
479 Journal_seek_monotonic(Journal *self, PyObject *args)
480 {
481     PyObject *arg;
482     char *bootid=NULL;
483     if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid))
484         return NULL;
485
486     uint64_t timestamp=-1LL;
487     if PyDelta_Check(arg) {
488         PyObject *temp;
489         temp = PyObject_CallMethod(arg, "total_seconds", NULL);
490         timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6);
491         Py_DECREF(temp);
492     }else if (PyFloat_Check(arg)) {
493         timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6);
494     }else if (PyLong_Check(arg)) {
495         timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6;
496 #if PY_MAJOR_VERSION <3
497     }else if (PyInt_Check(arg)) {
498         timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6;
499 #endif
500
501     }
502
503     if ((int64_t) timestamp < 0LL) {
504         PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance");
505         return NULL;
506     }
507
508     sd_id128_t sd_id;
509     int r;
510     if (bootid) {
511         r = sd_id128_from_string(bootid, &sd_id);
512         if (r == -EINVAL) {
513             PyErr_SetString(PyExc_ValueError, "Invalid bootid");
514             return NULL;
515         } else if (r < 0) {
516             PyErr_SetString(PyExc_RuntimeError, "Error processing bootid");
517             return NULL;
518         }
519     }else{
520         r = sd_id128_get_boot(&sd_id);
521         if (r == -EIO) {
522             PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
523             return NULL;
524         } else if (r < 0) {
525             PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
526             return NULL;
527         }
528     }
529
530     Py_BEGIN_ALLOW_THREADS
531     r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp);
532     Py_END_ALLOW_THREADS
533     if (r < 0) {
534         PyErr_SetString(PyExc_RuntimeError, "Error seek to time");
535         return NULL;
536     }
537     Py_RETURN_NONE;
538 }
539  
540 PyDoc_STRVAR(Journal_wait__doc__,
541 "wait([timeout]) -> Change state (integer)\n\n"
542 "Waits until there is a change in the journal. Argument `timeout`\n"
543 "is the maximum number of seconds to wait before returning\n"
544 "regardless if journal has changed. If `timeout` is not given or is\n"
545 "0, then it will block forever.\n"
546 "Will return constants: NOP if no change; APPEND if new\n"
547 "entries have been added to the end of the journal; and\n"
548 "INVALIDATE if journal files have been added or removed.");
549 static PyObject *
550 Journal_wait(Journal *self, PyObject *args, PyObject *keywds)
551 {
552     int64_t timeout=0LL;
553     if (! PyArg_ParseTuple(args, "|L", &timeout))
554         return NULL;
555
556     int r;
557     if ( timeout == 0LL) {
558         Py_BEGIN_ALLOW_THREADS
559         r = sd_journal_wait(self->j, (uint64_t) -1);
560         Py_END_ALLOW_THREADS
561     }else{
562         Py_BEGIN_ALLOW_THREADS
563         r = sd_journal_wait(self->j, timeout * 1E6);
564         Py_END_ALLOW_THREADS
565     }
566 #if PY_MAJOR_VERSION >=3
567     return PyLong_FromLong(r);
568 #else
569     return PyInt_FromLong(r);
570 #endif
571 }
572
573 PyDoc_STRVAR(Journal_seek_cursor__doc__,
574 "seek_cursor(cursor) -> None\n\n"
575 "Seeks to journal entry by given unique reference `cursor`.");
576 static PyObject *
577 Journal_seek_cursor(Journal *self, PyObject *args)
578 {
579     const char *cursor;
580     if (! PyArg_ParseTuple(args, "s", &cursor))
581         return NULL;
582
583     int r;
584     Py_BEGIN_ALLOW_THREADS
585     r = sd_journal_seek_cursor(self->j, cursor);
586     Py_END_ALLOW_THREADS
587     if (r == -EINVAL) {
588         PyErr_SetString(PyExc_ValueError, "Invalid cursor");
589         return NULL;
590     }else if (r == -ENOMEM) {
591         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
592         return NULL;
593     }else if (r < 0) {
594         PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor");
595         return NULL;
596     }
597     Py_RETURN_NONE;
598 }
599
600 static PyObject *
601 Journal_iter(PyObject *self)
602 {
603     Py_INCREF(self);
604     return self;
605 }
606
607 static PyObject *
608 Journal_iternext(PyObject *self)
609 {
610     PyObject *dict, *arg;
611     Py_ssize_t dict_size;
612
613     dict = PyObject_CallMethod(self, "get_next", "");
614     dict_size = PyDict_Size(dict);
615     if ((int64_t) dict_size > 0LL) {
616         return dict;
617     }else{
618         Py_DECREF(dict);
619         PyErr_SetNone(PyExc_StopIteration);
620         return NULL;
621     }
622 }
623
624 #ifdef SD_JOURNAL_FOREACH_UNIQUE
625 PyDoc_STRVAR(Journal_query_unique__doc__,
626 "query_unique(field) -> a set of values\n\n"
627 "Returns a set of unique values in journal for given `field`.\n"
628 "Note this does not respect any journal matches.");
629 static PyObject *
630 Journal_query_unique(Journal *self, PyObject *args)
631 {
632     char *query;
633     if (! PyArg_ParseTuple(args, "s", &query))
634         return NULL;
635
636     int r;
637     Py_BEGIN_ALLOW_THREADS
638     r = sd_journal_query_unique(self->j, query);
639     Py_END_ALLOW_THREADS
640     if (r == -EINVAL) {
641         PyErr_SetString(PyExc_ValueError, "Invalid field name");
642         return NULL;
643     } else if (r == -ENOMEM) {
644         PyErr_SetString(PyExc_MemoryError, "Not enough memory");
645         return NULL;
646     } else if (r < 0) {
647         PyErr_SetString(PyExc_RuntimeError, "Error querying journal");
648         return NULL;
649     }
650
651     const void *uniq;
652     size_t uniq_len;
653     const char *delim_ptr;
654     PyObject *value_set, *key, *value;
655     value_set = PySet_New(0);
656
657 #if PY_MAJOR_VERSION >=3
658     key = PyUnicode_FromString(query);
659 #else
660     key = PyString_FromString(query);
661 #endif
662
663     SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) {
664         delim_ptr = memchr(uniq, '=', uniq_len);
665         value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1));
666         PySet_Add(value_set, value);
667         Py_DECREF(value);
668     }
669     Py_DECREF(key);
670     return value_set;
671 }
672 #endif //def SD_JOURNAL_FOREACH_UNIQUE
673
674 PyDoc_STRVAR(Journal_this_boot__doc__,
675 "this_boot() -> None\n\n"
676 "Sets match filter for the current _BOOT_ID.");
677 static PyObject *
678 Journal_this_boot(Journal *self, PyObject *args)
679 {
680     sd_id128_t sd_id;
681     int r;
682     r = sd_id128_get_boot(&sd_id);
683     if (r == -EIO) {
684         PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
685         return NULL;
686     } else if (r < 0) {
687         PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
688         return NULL;
689     }
690
691     char bootid[33];
692     sd_id128_to_string(sd_id, bootid);
693
694     PyObject *arg, *keywds;
695     arg = PyTuple_New(0);
696     keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid);
697     Journal_add_match(self, arg, keywds);
698     Py_DECREF(arg);
699     Py_DECREF(keywds);
700     if (PyErr_Occurred())
701         return NULL;
702
703     Py_RETURN_NONE;
704 }
705
706 PyDoc_STRVAR(Journal_this_machine__doc__,
707 "this_machine() -> None\n\n"
708 "Sets match filter for the current _MACHINE_ID.");
709 static PyObject *
710 Journal_this_machine(Journal *self, PyObject *args)
711 {
712     sd_id128_t sd_id;
713     int r;
714     r = sd_id128_get_machine(&sd_id);
715     if (r == -EIO) {
716         PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
717         return NULL;
718     } else if (r < 0) {
719         PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
720         return NULL;
721     }
722
723     char machineid[33];
724     sd_id128_to_string(sd_id, machineid);
725
726     PyObject *arg, *keywds;
727     arg = PyTuple_New(0);
728     keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid);
729     Journal_add_match(self, arg, keywds);
730     Py_DECREF(arg);
731     Py_DECREF(keywds);
732     if (PyErr_Occurred())
733         return NULL;
734
735     Py_RETURN_NONE;
736 }
737
738 static PyObject *
739 Journal_get_data_threshold(Journal *self, void *closure)
740 {
741     size_t cvalue;
742     PyObject *value;
743     int r;
744
745     r = sd_journal_get_data_threshold(self->j, &cvalue);
746     if (r < 0){
747         PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold");
748         return NULL;
749     }
750
751 #if PY_MAJOR_VERSION >=3
752     value = PyLong_FromSize_t(cvalue);
753 #else
754     value = PyInt_FromSize_t(cvalue);
755 #endif
756     return value;
757 }
758
759 static int
760 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
761 {
762     if (value == NULL) {
763         PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
764         return -1;
765     }
766 #if PY_MAJOR_VERSION >=3
767     if (! PyLong_Check(value)){
768 #else
769     if (! PyInt_Check(value)){
770 #endif
771         PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
772         return -1;
773     }
774     int r;
775 #if PY_MAJOR_VERSION >=3
776     r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
777 #else
778     r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
779 #endif
780     if (r < 0){
781         PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold");
782         return -1;
783     }
784     return 0;
785 }
786
787 static PyGetSetDef Journal_getseters[] = {
788     {"data_threshold",
789     (getter)Journal_get_data_threshold,
790     (setter)Journal_set_data_threshold,
791     "data threshold",
792     NULL},
793     {NULL}
794 };
795
796 static PyMethodDef Journal_methods[] = {
797     {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
798     Journal_get_next__doc__},
799     {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
800     Journal_get_previous__doc__},
801     {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
802     Journal_add_match__doc__},
803     {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
804     Journal_add_disjunction__doc__},
805     {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
806     Journal_flush_matches__doc__},
807     {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
808     Journal_seek__doc__},
809     {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
810     Journal_seek_realtime__doc__},
811     {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
812     Journal_seek_monotonic__doc__},
813     {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
814     Journal_wait__doc__},
815     {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
816     Journal_seek_cursor__doc__},
817 #ifdef SD_JOURNAL_FOREACH_UNIQUE
818     {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
819     Journal_query_unique__doc__},
820 #endif
821     {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS,
822     Journal_this_boot__doc__},
823     {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS,
824     Journal_this_machine__doc__},
825     {NULL}  /* Sentinel */
826 };
827
828 static PyTypeObject JournalType = {
829     PyVarObject_HEAD_INIT(NULL, 0)
830     "_reader.Journal",           /*tp_name*/
831     sizeof(Journal),                  /*tp_basicsize*/
832     0,                                /*tp_itemsize*/
833     (destructor)Journal_dealloc,      /*tp_dealloc*/
834     0,                                /*tp_print*/
835     0,                                /*tp_getattr*/
836     0,                                /*tp_setattr*/
837     0,                                /*tp_compare*/
838     0,                                /*tp_repr*/
839     0,                                /*tp_as_number*/
840     0,                                /*tp_as_sequence*/
841     0,                                /*tp_as_mapping*/
842     0,                                /*tp_hash */
843     0,                                /*tp_call*/
844     0,                                /*tp_str*/
845     0,                                /*tp_getattro*/
846     0,                                /*tp_setattro*/
847     0,                                /*tp_as_buffer*/
848     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
849     Journal__doc__,                   /* tp_doc */
850     0,                                /* tp_traverse */
851     0,                                /* tp_clear */
852     0,                                /* tp_richcompare */
853     0,                                /* tp_weaklistoffset */
854     Journal_iter,                     /* tp_iter */
855     Journal_iternext,                 /* tp_iternext */
856     Journal_methods,                  /* tp_methods */
857     0,                                /* tp_members */
858     Journal_getseters,                /* tp_getset */
859     0,                                /* tp_base */
860     0,                                /* tp_dict */
861     0,                                /* tp_descr_get */
862     0,                                /* tp_descr_set */
863     0,                                /* tp_dictoffset */
864     (initproc)Journal_init,           /* tp_init */
865     0,                                /* tp_alloc */
866     PyType_GenericNew,                /* tp_new */
867 };
868
869 #if PY_MAJOR_VERSION >= 3
870 static PyModuleDef _reader_module = {
871     PyModuleDef_HEAD_INIT,
872     "_reader",
873     "Module that reads systemd journal similar to journalctl.",
874     -1,
875     NULL, NULL, NULL, NULL, NULL
876 };
877 #endif
878
879 PyMODINIT_FUNC
880 #if PY_MAJOR_VERSION >= 3
881 PyInit__reader(void)
882 #else
883 init_reader(void) 
884 #endif
885 {
886     PyObject* m;
887
888     PyDateTime_IMPORT;
889
890     if (PyType_Ready(&JournalType) < 0)
891 #if PY_MAJOR_VERSION >= 3
892         return NULL;
893 #else
894         return;
895 #endif
896
897 #if PY_MAJOR_VERSION >= 3
898     m = PyModule_Create(&_reader_module);
899     if (m == NULL)
900         return NULL;
901 #else
902     m = Py_InitModule3("_reader", NULL,
903                    "Module that reads systemd journal similar to journalctl.");
904     if (m == NULL)
905         return;
906 #endif
907
908     Py_INCREF(&JournalType);
909     PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
910     PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
911     PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
912     PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
913     PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
914     PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
915     PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
916
917 #if PY_MAJOR_VERSION >= 3
918     return m;
919 #endif
920 }