chiark / gitweb /
systemd-python: move default call dicts from C 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_log_level__doc__,
675 "log_level(level) -> None\n\n"
676 "Sets maximum log level by setting matches for PRIORITY.");
677 static PyObject *
678 Journal_log_level(Journal *self, PyObject *args)
679 {
680     int level;
681     if (! PyArg_ParseTuple(args, "i", &level))
682         return NULL;
683
684     if (level < 0 || level > 7) {
685         PyErr_SetString(PyExc_ValueError, "Log level should be 0 <= level <= 7");
686         return NULL;
687     }
688     int i;
689     char level_str[2];
690     PyObject *arg, *keywds;
691     for(i = 0; i <= level; i++) {
692         sprintf(level_str, "%i", i);
693         arg = PyTuple_New(0);
694         keywds = Py_BuildValue("{s:s}", "PRIORITY", level_str);
695         Journal_add_match(self, arg, keywds);
696         Py_DECREF(arg);
697         Py_DECREF(keywds);
698         if (PyErr_Occurred())
699             return NULL;
700     }
701     Py_RETURN_NONE;
702 }
703
704 PyDoc_STRVAR(Journal_this_boot__doc__,
705 "this_boot() -> None\n\n"
706 "Sets match filter for the current _BOOT_ID.");
707 static PyObject *
708 Journal_this_boot(Journal *self, PyObject *args)
709 {
710     sd_id128_t sd_id;
711     int r;
712     r = sd_id128_get_boot(&sd_id);
713     if (r == -EIO) {
714         PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
715         return NULL;
716     } else if (r < 0) {
717         PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
718         return NULL;
719     }
720
721     char bootid[33];
722     sd_id128_to_string(sd_id, bootid);
723
724     PyObject *arg, *keywds;
725     arg = PyTuple_New(0);
726     keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid);
727     Journal_add_match(self, arg, keywds);
728     Py_DECREF(arg);
729     Py_DECREF(keywds);
730     if (PyErr_Occurred())
731         return NULL;
732
733     Py_RETURN_NONE;
734 }
735
736 PyDoc_STRVAR(Journal_this_machine__doc__,
737 "this_machine() -> None\n\n"
738 "Sets match filter for the current _MACHINE_ID.");
739 static PyObject *
740 Journal_this_machine(Journal *self, PyObject *args)
741 {
742     sd_id128_t sd_id;
743     int r;
744     r = sd_id128_get_machine(&sd_id);
745     if (r == -EIO) {
746         PyErr_SetString(PyExc_IOError, "Error getting current boot ID");
747         return NULL;
748     } else if (r < 0) {
749         PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID");
750         return NULL;
751     }
752
753     char machineid[33];
754     sd_id128_to_string(sd_id, machineid);
755
756     PyObject *arg, *keywds;
757     arg = PyTuple_New(0);
758     keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid);
759     Journal_add_match(self, arg, keywds);
760     Py_DECREF(arg);
761     Py_DECREF(keywds);
762     if (PyErr_Occurred())
763         return NULL;
764
765     Py_RETURN_NONE;
766 }
767
768 static PyObject *
769 Journal_get_data_threshold(Journal *self, void *closure)
770 {
771     size_t cvalue;
772     PyObject *value;
773     int r;
774
775     r = sd_journal_get_data_threshold(self->j, &cvalue);
776     if (r < 0){
777         PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold");
778         return NULL;
779     }
780
781 #if PY_MAJOR_VERSION >=3
782     value = PyLong_FromSize_t(cvalue);
783 #else
784     value = PyInt_FromSize_t(cvalue);
785 #endif
786     return value;
787 }
788
789 static int
790 Journal_set_data_threshold(Journal *self, PyObject *value, void *closure)
791 {
792     if (value == NULL) {
793         PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold");
794         return -1;
795     }
796 #if PY_MAJOR_VERSION >=3
797     if (! PyLong_Check(value)){
798 #else
799     if (! PyInt_Check(value)){
800 #endif
801         PyErr_SetString(PyExc_TypeError, "Data threshold must be int");
802         return -1;
803     }
804     int r;
805 #if PY_MAJOR_VERSION >=3
806     r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value));
807 #else
808     r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value));
809 #endif
810     if (r < 0){
811         PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold");
812         return -1;
813     }
814     return 0;
815 }
816
817 static PyGetSetDef Journal_getseters[] = {
818     {"data_threshold",
819     (getter)Journal_get_data_threshold,
820     (setter)Journal_set_data_threshold,
821     "data threshold",
822     NULL},
823     {NULL}
824 };
825
826 static PyMethodDef Journal_methods[] = {
827     {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS,
828     Journal_get_next__doc__},
829     {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS,
830     Journal_get_previous__doc__},
831     {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS,
832     Journal_add_match__doc__},
833     {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS,
834     Journal_add_disjunction__doc__},
835     {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS,
836     Journal_flush_matches__doc__},
837     {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS,
838     Journal_seek__doc__},
839     {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS,
840     Journal_seek_realtime__doc__},
841     {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS,
842     Journal_seek_monotonic__doc__},
843     {"wait", (PyCFunction)Journal_wait, METH_VARARGS,
844     Journal_wait__doc__},
845     {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS,
846     Journal_seek_cursor__doc__},
847 #ifdef SD_JOURNAL_FOREACH_UNIQUE
848     {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS,
849     Journal_query_unique__doc__},
850 #endif
851     {"log_level", (PyCFunction)Journal_log_level, METH_VARARGS,
852     Journal_log_level__doc__},
853     {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS,
854     Journal_this_boot__doc__},
855     {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS,
856     Journal_this_machine__doc__},
857     {NULL}  /* Sentinel */
858 };
859
860 static PyTypeObject JournalType = {
861     PyVarObject_HEAD_INIT(NULL, 0)
862     "_reader.Journal",           /*tp_name*/
863     sizeof(Journal),                  /*tp_basicsize*/
864     0,                                /*tp_itemsize*/
865     (destructor)Journal_dealloc,      /*tp_dealloc*/
866     0,                                /*tp_print*/
867     0,                                /*tp_getattr*/
868     0,                                /*tp_setattr*/
869     0,                                /*tp_compare*/
870     0,                                /*tp_repr*/
871     0,                                /*tp_as_number*/
872     0,                                /*tp_as_sequence*/
873     0,                                /*tp_as_mapping*/
874     0,                                /*tp_hash */
875     0,                                /*tp_call*/
876     0,                                /*tp_str*/
877     0,                                /*tp_getattro*/
878     0,                                /*tp_setattro*/
879     0,                                /*tp_as_buffer*/
880     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/
881     Journal__doc__,                   /* tp_doc */
882     0,                                /* tp_traverse */
883     0,                                /* tp_clear */
884     0,                                /* tp_richcompare */
885     0,                                /* tp_weaklistoffset */
886     Journal_iter,                     /* tp_iter */
887     Journal_iternext,                 /* tp_iternext */
888     Journal_methods,                  /* tp_methods */
889     0,                                /* tp_members */
890     Journal_getseters,                /* tp_getset */
891     0,                                /* tp_base */
892     0,                                /* tp_dict */
893     0,                                /* tp_descr_get */
894     0,                                /* tp_descr_set */
895     0,                                /* tp_dictoffset */
896     (initproc)Journal_init,           /* tp_init */
897     0,                                /* tp_alloc */
898     PyType_GenericNew,                /* tp_new */
899 };
900
901 #if PY_MAJOR_VERSION >= 3
902 static PyModuleDef _reader_module = {
903     PyModuleDef_HEAD_INIT,
904     "_reader",
905     "Module that reads systemd journal similar to journalctl.",
906     -1,
907     NULL, NULL, NULL, NULL, NULL
908 };
909 #endif
910
911 PyMODINIT_FUNC
912 #if PY_MAJOR_VERSION >= 3
913 PyInit__reader(void)
914 #else
915 init_reader(void) 
916 #endif
917 {
918     PyObject* m;
919
920     PyDateTime_IMPORT;
921
922     if (PyType_Ready(&JournalType) < 0)
923 #if PY_MAJOR_VERSION >= 3
924         return NULL;
925 #else
926         return;
927 #endif
928
929 #if PY_MAJOR_VERSION >= 3
930     m = PyModule_Create(&_reader_module);
931     if (m == NULL)
932         return NULL;
933 #else
934     m = Py_InitModule3("_reader", NULL,
935                    "Module that reads systemd journal similar to journalctl.");
936     if (m == NULL)
937         return;
938 #endif
939
940     Py_INCREF(&JournalType);
941     PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType);
942     PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP);
943     PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND);
944     PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE);
945     PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY);
946     PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY);
947     PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY);
948
949 #if PY_MAJOR_VERSION >= 3
950     return m;
951 #endif
952 }