X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fpython-systemd%2F_reader.c;h=a678f69318055c94b14179aa3f13a7771d5b1a21;hp=c16cbdcd763b46f1bb3cadf8b665ed76c6a8a406;hb=fd587c876d480863d56bbbdc5e9fcf7e735d98f3;hpb=dd37d563ba236cb0ae583d73e02e9e093084e2e5 diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index c16cbdcd7..a678f6931 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -1,4 +1,4 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +/*-*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. @@ -18,535 +18,864 @@ You should have received a copy of the GNU Lesser General Public License along with systemd; If not, see . ***/ -#include #include #include #include +#include +#include + +#include + +#include "pyutil.h" +#include "macro.h" +#include "util.h" +#include "strv.h" +#include "build.h" typedef struct { PyObject_HEAD sd_journal *j; -} Journal; -static PyTypeObject JournalType; +} Reader; +static PyTypeObject ReaderType; + + +PyDoc_STRVAR(module__doc__, + "Class to reads the systemd journal similar to journalctl."); + + +#if PY_MAJOR_VERSION >= 3 +static PyTypeObject MonotonicType; + +PyDoc_STRVAR(MonotonicType__doc__, + "A tuple of (timestamp, bootid) for holding monotonic timestamps"); + +static PyStructSequence_Field MonotonicType_fields[] = { + {(char*) "timestamp", (char*) "Time"}, + {(char*) "bootid", (char*) "Unique identifier of the boot"}, + {} /* Sentinel */ +}; -static void -Journal_dealloc(Journal* self) +static PyStructSequence_Desc Monotonic_desc = { + (char*) "journal.Monotonic", + MonotonicType__doc__, + MonotonicType_fields, + 2, +}; +#endif + +static int strv_converter(PyObject* obj, void *_result) { + char ***result = _result; + Py_ssize_t i, len; + + assert(result); + + if (!obj) + goto cleanup; + + if (!PySequence_Check(obj)) + return 0; + + len = PySequence_Length(obj); + *result = new0(char*, len + 1); + + for (i = 0; i < len; i++) { + PyObject *item; +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + int r; + PyObject *bytes; +#endif + char *s, *s2; + + item = PySequence_ITEM(obj, i); +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + r = PyUnicode_FSConverter(item, &bytes); + if (r == 0) + goto cleanup; + + s = PyBytes_AsString(bytes); +#else + s = PyString_AsString(item); +#endif + if (!s) + goto cleanup; + + s2 = strdup(s); + if (!s2) + log_oom(); + + (*result)[i] = s2; + } + + return 1; + +cleanup: + strv_free(*result); + *result = NULL; + + return 0; +} + +static void Reader_dealloc(Reader* self) { sd_journal_close(self->j); Py_TYPE(self)->tp_free((PyObject*)self); } -PyDoc_STRVAR(Journal__doc__, -"Journal([flags][,path]) -> ...\n" -"Journal instance\n\n" -"Returns instance of Journal, which allows filtering and return\n" -"of journal entries.\n" -"Argument `flags` sets open flags of the journal, which can be one\n" -"of, or ORed combination of constants: LOCAL_ONLY (default) opens\n" -"journal on local machine only; RUNTIME_ONLY opens only\n" -"volatile journal files; and SYSTEM_ONLY opens only\n" -"journal files of system services and the kernel.\n" -"Argument `path` is the directory of journal files. Note that\n" -"currently flags are ignored when `path` is present as they are\n" -" not relevant."); -static int -Journal_init(Journal *self, PyObject *args, PyObject *keywds) -{ - int flags=SD_JOURNAL_LOCAL_ONLY; - char *path=NULL; - - static char *kwlist[] = {"flags", "path", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist, - &flags, &path)) - return 1; +PyDoc_STRVAR(Reader__doc__, + "_Reader([flags | path | files]) -> ...\n\n" + "_Reader allows filtering and retrieval of Journal entries.\n" + "Note: this is a low-level interface, and probably not what you\n" + "want, use systemd.journal.Reader instead.\n\n" + "Argument `flags` sets open flags of the journal, which can be one\n" + "of, or ORed combination of constants: LOCAL_ONLY (default) opens\n" + "journal on local machine only; RUNTIME_ONLY opens only\n" + "volatile journal files; and SYSTEM opens journal files of\n" + "system services and the kernel, and CURRENT_USER opens files\n" + "of the current user.\n\n" + "Argument `path` is the directory of journal files.\n" + "Argument `files` is a list of files. Note that\n" + "`flags`, `path`, and `files` are exclusive.\n\n" + "_Reader implements the context manager protocol: the journal\n" + "will be closed when exiting the block."); +static int Reader_init(Reader *self, PyObject *args, PyObject *keywds) +{ + int flags = 0, r; + char *path = NULL; + char **files = NULL; + + static const char* const kwlist[] = {"flags", "path", "files", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|izO&", (char**) kwlist, + &flags, &path, strv_converter, &files)) + return -1; + + if (!!flags + !!path + !!files > 1) { + PyErr_SetString(PyExc_ValueError, "cannot use more than one of flags, path, and files"); + return -1; + } + + if (!flags) + flags = SD_JOURNAL_LOCAL_ONLY; - int r; Py_BEGIN_ALLOW_THREADS - if (path) { + if (path) r = sd_journal_open_directory(&self->j, path, 0); - }else{ + else if (files) + r = sd_journal_open_files(&self->j, (const char**) files, 0); + else r = sd_journal_open(&self->j, flags); - } Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyObject *errtype = r == -EINVAL ? PyExc_ValueError : - r == -ENOMEM ? PyExc_MemoryError : - PyExc_OSError; - PyErr_SetFromErrnoWithFilename(errtype, path); - return -1; - } - return 0; + return set_error(r, path, "Invalid flags or path"); +} + + +PyDoc_STRVAR(Reader_fileno__doc__, + "fileno() -> int\n\n" + "Get a file descriptor to poll for changes in the journal.\n" + "This method invokes sd_journal_get_fd().\n" + "See man:sd_journal_get_fd(3)."); +static PyObject* Reader_fileno(Reader *self, PyObject *args) +{ + int fd = sd_journal_get_fd(self->j); + set_error(fd, NULL, NULL); + if (fd < 0) + return NULL; + return long_FromLong(fd); +} + + +PyDoc_STRVAR(Reader_reliable_fd__doc__, + "reliable_fd() -> bool\n\n" + "Returns True iff the journal can be polled reliably.\n" + "This method invokes sd_journal_reliable_fd().\n" + "See man:sd_journal_reliable_fd(3)."); +static PyObject* Reader_reliable_fd(Reader *self, PyObject *args) +{ + int r = sd_journal_reliable_fd(self->j); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(Reader_get_events__doc__, + "get_events() -> int\n\n" + "Returns a mask of poll() events to wait for on the file\n" + "descriptor returned by .fileno().\n\n" + "See man:sd_journal_get_events(3) for further discussion."); +static PyObject* Reader_get_events(Reader *self, PyObject *args) +{ + int r = sd_journal_get_events(self->j); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + return long_FromLong(r); +} + + +PyDoc_STRVAR(Reader_get_timeout__doc__, + "get_timeout() -> int or None\n\n" + "Returns a timeout value for usage in poll(), the time since the\n" + "epoch of clock_gettime(2) in microseconds, or None if no timeout\n" + "is necessary.\n\n" + "The return value must be converted to a relative timeout in\n" + "milliseconds if it is to be used as an argument for poll().\n" + "See man:sd_journal_get_timeout(3) for further discussion."); +static PyObject* Reader_get_timeout(Reader *self, PyObject *args) +{ + int r; + uint64_t t; + + r = sd_journal_get_timeout(self->j, &t); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + + if (t == (uint64_t) -1) + Py_RETURN_NONE; + + assert_cc(sizeof(unsigned long long) == sizeof(t)); + return PyLong_FromUnsignedLongLong(t); +} + + +PyDoc_STRVAR(Reader_get_timeout_ms__doc__, + "get_timeout_ms() -> int\n\n" + "Returns a timeout value suitable for usage in poll(), the value\n" + "returned by .get_timeout() converted to relative ms, or -1 if\n" + "no timeout is necessary."); +static PyObject* Reader_get_timeout_ms(Reader *self, PyObject *args) +{ + int r; + uint64_t t; + + r = sd_journal_get_timeout(self->j, &t); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + + return absolute_timeout(t); +} + + +PyDoc_STRVAR(Reader_close__doc__, + "close() -> None\n\n" + "Free resources allocated by this Reader object.\n" + "This method invokes sd_journal_close().\n" + "See man:sd_journal_close(3)."); +static PyObject* Reader_close(Reader *self, PyObject *args) +{ + assert(self); + assert(!args); + + sd_journal_close(self->j); + self->j = NULL; + Py_RETURN_NONE; +} + + +PyDoc_STRVAR(Reader_get_usage__doc__, + "get_usage() -> int\n\n" + "Returns the total disk space currently used by journal\n" + "files (in bytes). If `SD_JOURNAL_LOCAL_ONLY` was\n" + "passed when opening the journal this value will only reflect\n" + "the size of journal files of the local host, otherwise\n" + "of all hosts.\n\n" + "This method invokes sd_journal_get_usage().\n" + "See man:sd_journal_get_usage(3)."); +static PyObject* Reader_get_usage(Reader *self, PyObject *args) +{ + int r; + uint64_t bytes; + + r = sd_journal_get_usage(self->j, &bytes); + if (set_error(r, NULL, NULL)) + return NULL; + + assert_cc(sizeof(unsigned long long) == sizeof(bytes)); + return PyLong_FromUnsignedLongLong(bytes); +} + + +PyDoc_STRVAR(Reader___enter____doc__, + "__enter__() -> self\n\n" + "Part of the context manager protocol.\n" + "Returns self.\n"); +static PyObject* Reader___enter__(PyObject *self, PyObject *args) +{ + assert(self); + assert(!args); + + Py_INCREF(self); + return self; +} + +PyDoc_STRVAR(Reader___exit____doc__, + "__exit__(type, value, traceback) -> None\n\n" + "Part of the context manager protocol.\n" + "Closes the journal.\n"); +static PyObject* Reader___exit__(Reader *self, PyObject *args) +{ + return Reader_close(self, args); } -PyDoc_STRVAR(Journal_get_next__doc__, -"get_next([skip]) -> dict\n\n" -"Return dictionary of the next log entry. Optional skip value will\n" -"return the `skip`th log entry."); -static PyObject * -Journal_get_next(Journal *self, PyObject *args) + +PyDoc_STRVAR(Reader_next__doc__, + "next([skip]) -> bool\n\n" + "Go to the next log entry. Optional skip value means to go to\n" + "the `skip`\\-th log entry.\n" + "Returns False if at end of file, True otherwise."); +static PyObject* Reader_next(Reader *self, PyObject *args) { - int64_t skip=1LL; - if (! PyArg_ParseTuple(args, "|L", &skip)) + int64_t skip = 1LL; + int r; + + if (!PyArg_ParseTuple(args, "|L:next", &skip)) return NULL; if (skip == 0LL) { - PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer"); + PyErr_SetString(PyExc_ValueError, "skip must be nonzero"); return NULL; } - int r = -EINVAL; Py_BEGIN_ALLOW_THREADS - if (skip == 1LL) { + if (skip == 1LL) r = sd_journal_next(self->j); - }else if (skip == -1LL) { + else if (skip == -1LL) r = sd_journal_previous(self->j); - }else if (skip > 1LL) { + else if (skip > 1LL) r = sd_journal_next_skip(self->j, skip); - }else if (skip < -1LL) { + else if (skip < -1LL) r = sd_journal_previous_skip(self->j, -skip); - } + else + assert_not_reached("should not be here"); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + return PyBool_FromLong(r); +} + +PyDoc_STRVAR(Reader_previous__doc__, + "previous([skip]) -> bool\n\n" + "Go to the previous log entry. Optional skip value means to \n" + "go to the `skip`\\-th previous log entry.\n" + "Returns False if at start of file, True otherwise."); +static PyObject* Reader_previous(Reader *self, PyObject *args) +{ + int64_t skip = 1LL; + if (!PyArg_ParseTuple(args, "|L:previous", &skip)) return NULL; - }else if ( r == 0) { //EOF - return PyDict_New(); + + return PyObject_CallMethod((PyObject *)self, (char*) "_next", + (char*) "L", -skip); +} + + +static int extract(const char* msg, size_t msg_len, + PyObject **key, PyObject **value) { + PyObject *k = NULL, *v; + const char *delim_ptr; + + delim_ptr = memchr(msg, '=', msg_len); + if (!delim_ptr) { + PyErr_SetString(PyExc_OSError, + "journal gave us a field without '='"); + return -1; } - PyObject *dict; - dict = PyDict_New(); + if (key) { + k = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg); + if (!k) + return -1; + } + + if (value) { + v = PyBytes_FromStringAndSize(delim_ptr + 1, + (const char*) msg + msg_len - (delim_ptr + 1)); + if (!v) { + Py_XDECREF(k); + return -1; + } + + *value = v; + } + if (key) + *key = k; + + return 0; +} + +PyDoc_STRVAR(Reader_get__doc__, + "get(str) -> str\n\n" + "Return data associated with this key in current log entry.\n" + "Throws KeyError is the data is not available."); +static PyObject* Reader_get(Reader *self, PyObject *args) +{ + const char* field; + const void* msg; + size_t msg_len; + PyObject *value; + int r; + + assert(self); + assert(args); + + if (!PyArg_ParseTuple(args, "s:get", &field)) + return NULL; + + r = sd_journal_get_data(self->j, field, &msg, &msg_len); + if (r == -ENOENT) { + PyErr_SetString(PyExc_KeyError, field); + return NULL; + } else if (set_error(r, NULL, "field name is not valid")) + return NULL; + + r = extract(msg, msg_len, NULL, &value); + if (r < 0) + return NULL; + return value; +} + + +PyDoc_STRVAR(Reader_get_all__doc__, + "_get_all() -> dict\n\n" + "Return dictionary of the current log entry."); +static PyObject* Reader_get_all(Reader *self, PyObject *args) +{ + PyObject *dict; const void *msg; size_t msg_len; - const char *delim_ptr; - PyObject *key, *value, *cur_value, *tmp_list; + int r; + + dict = PyDict_New(); + if (!dict) + return NULL; SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) { - delim_ptr = memchr(msg, '=', msg_len); -#if PY_MAJOR_VERSION >=3 - key = PyUnicode_FromStringAndSize(msg, delim_ptr - (const char*) msg); -#else - key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg); -#endif - value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) ); + _cleanup_Py_DECREF_ PyObject *key = NULL, *value = NULL; + + r = extract(msg, msg_len, &key, &value); + if (r < 0) + goto error; + if (PyDict_Contains(dict, key)) { - cur_value = PyDict_GetItem(dict, key); + PyObject *cur_value = PyDict_GetItem(dict, key); + if (PyList_CheckExact(cur_value)) { - PyList_Append(cur_value, value); - }else{ - tmp_list = PyList_New(0); - PyList_Append(tmp_list, cur_value); - PyList_Append(tmp_list, value); - PyDict_SetItem(dict, key, tmp_list); - Py_DECREF(tmp_list); + r = PyList_Append(cur_value, value); + if (r < 0) + goto error; + } else { + _cleanup_Py_DECREF_ PyObject *tmp_list = PyList_New(0); + if (!tmp_list) + goto error; + + r = PyList_Append(tmp_list, cur_value); + if (r < 0) + goto error; + + r = PyList_Append(tmp_list, value); + if (r < 0) + goto error; + + r = PyDict_SetItem(dict, key, tmp_list); + if (r < 0) + goto error; } - }else{ - PyDict_SetItem(dict, key, value); + } else { + r = PyDict_SetItem(dict, key, value); + if (r < 0) + goto error; } - Py_DECREF(key); - Py_DECREF(value); } - uint64_t realtime; - if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) { - char realtime_str[20]; - sprintf(realtime_str, "%llu", (long long unsigned) realtime); + return dict; -#if PY_MAJOR_VERSION >=3 - key = PyUnicode_FromString("__REALTIME_TIMESTAMP"); -#else - key = PyString_FromString("__REALTIME_TIMESTAMP"); -#endif - value = PyBytes_FromString(realtime_str); - PyDict_SetItem(dict, key, value); - Py_DECREF(key); - Py_DECREF(value); - } +error: + Py_DECREF(dict); + return NULL; +} - sd_id128_t sd_id; - uint64_t monotonic; - if (sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id) == 0) { - char monotonic_str[20]; - sprintf(monotonic_str, "%llu", (long long unsigned) monotonic); -#if PY_MAJOR_VERSION >=3 - key = PyUnicode_FromString("__MONOTONIC_TIMESTAMP"); -#else - key = PyString_FromString("__MONOTONIC_TIMESTAMP"); -#endif - value = PyBytes_FromString(monotonic_str); - PyDict_SetItem(dict, key, value); - Py_DECREF(key); - Py_DECREF(value); - } +PyDoc_STRVAR(Reader_get_realtime__doc__, + "get_realtime() -> int\n\n" + "Return the realtime timestamp for the current journal entry\n" + "in microseconds.\n\n" + "Wraps sd_journal_get_realtime_usec().\n" + "See man:sd_journal_get_realtime_usec(3)."); +static PyObject* Reader_get_realtime(Reader *self, PyObject *args) +{ + uint64_t timestamp; + int r; - char *cursor; - if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0... -#if PY_MAJOR_VERSION >=3 - key = PyUnicode_FromString("__CURSOR"); -#else - key = PyString_FromString("__CURSOR"); -#endif - value = PyBytes_FromString(cursor); - PyDict_SetItem(dict, key, value); - free(cursor); - Py_DECREF(key); - Py_DECREF(value); - } + assert(self); + assert(!args); - return dict; + r = sd_journal_get_realtime_usec(self->j, ×tamp); + if (set_error(r, NULL, NULL)) + return NULL; + + assert_cc(sizeof(unsigned long long) == sizeof(timestamp)); + return PyLong_FromUnsignedLongLong(timestamp); } -PyDoc_STRVAR(Journal_get_previous__doc__, -"get_previous([skip]) -> dict\n\n" -"Return dictionary of the previous log entry. Optional skip value\n" -"will return the -`skip`th log entry. Equivalent to get_next(-skip)."); -static PyObject * -Journal_get_previous(Journal *self, PyObject *args) + +PyDoc_STRVAR(Reader_get_monotonic__doc__, + "get_monotonic() -> (timestamp, bootid)\n\n" + "Return the monotonic timestamp for the current journal entry\n" + "as a tuple of time in microseconds and bootid.\n\n" + "Wraps sd_journal_get_monotonic_usec().\n" + "See man:sd_journal_get_monotonic_usec(3)."); +static PyObject* Reader_get_monotonic(Reader *self, PyObject *args) { - int64_t skip=1LL; - if (! PyArg_ParseTuple(args, "|L", &skip)) + uint64_t timestamp; + sd_id128_t id; + PyObject *monotonic, *bootid, *tuple; + int r; + + assert(self); + assert(!args); + + r = sd_journal_get_monotonic_usec(self->j, ×tamp, &id); + if (set_error(r, NULL, NULL)) + return NULL; + + assert_cc(sizeof(unsigned long long) == sizeof(timestamp)); + monotonic = PyLong_FromUnsignedLongLong(timestamp); + bootid = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); +#if PY_MAJOR_VERSION >= 3 + tuple = PyStructSequence_New(&MonotonicType); +#else + tuple = PyTuple_New(2); +#endif + if (!monotonic || !bootid || !tuple) { + Py_XDECREF(monotonic); + Py_XDECREF(bootid); + Py_XDECREF(tuple); return NULL; + } + +#if PY_MAJOR_VERSION >= 3 + PyStructSequence_SET_ITEM(tuple, 0, monotonic); + PyStructSequence_SET_ITEM(tuple, 1, bootid); +#else + PyTuple_SET_ITEM(tuple, 0, monotonic); + PyTuple_SET_ITEM(tuple, 1, bootid); +#endif - return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip); + return tuple; } -PyDoc_STRVAR(Journal_add_match__doc__, -"add_match(match) -> None\n\n" -"Add a match to filter journal log entries. All matches of different\n" -"fields are combined in logical AND, and matches of the same field\n" -"are automatically combined in logical OR.\n" -"Match is string of form \"field=value\"."); -static PyObject * -Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) +PyDoc_STRVAR(Reader_add_match__doc__, + "add_match(match) -> None\n\n" + "Add a match to filter journal log entries. All matches of different\n" + "fields are combined with logical AND, and matches of the same field\n" + "are automatically combined with logical OR.\n" + "Match is a string of the form \"FIELD=value\"."); +static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds) { char *match; - int match_len; - if (! PyArg_ParseTuple(args, "s#", &match, &match_len)) + int match_len, r; + if (!PyArg_ParseTuple(args, "s#:add_match", &match, &match_len)) return NULL; - int r; r = sd_journal_add_match(self->j, match, match_len); - if (r < 0) { - errno = -r; - PyObject *errtype = r == -EINVAL ? PyExc_ValueError : - r == -ENOMEM ? PyExc_MemoryError : - PyExc_OSError; - PyErr_SetFromErrno(errtype); - return NULL; - } + set_error(r, NULL, "Invalid match"); + if (r < 0) + return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_add_disjunction__doc__, -"add_disjunction() -> None\n\n" -"Once called, all matches before and after are combined in logical\n" -"OR."); -static PyObject * -Journal_add_disjunction(Journal *self, PyObject *args) + +PyDoc_STRVAR(Reader_add_disjunction__doc__, + "add_disjunction() -> None\n\n" + "Inserts a logical OR between matches added since previous\n" + "add_disjunction() or add_conjunction() and the next\n" + "add_disjunction() or add_conjunction().\n\n" + "See man:sd_journal_add_disjunction(3) for explanation."); +static PyObject* Reader_add_disjunction(Reader *self, PyObject *args) { int r; r = sd_journal_add_disjunction(self->j); - if (r < 0) { - errno = -r; - PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError : - PyExc_OSError; - PyErr_SetFromErrno(errtype); + set_error(r, NULL, NULL); + if (r < 0) return NULL; - } Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_flush_matches__doc__, -"flush_matches() -> None\n\n" -"Clears all current match filters."); -static PyObject * -Journal_flush_matches(Journal *self, PyObject *args) + +PyDoc_STRVAR(Reader_add_conjunction__doc__, + "add_conjunction() -> None\n\n" + "Inserts a logical AND between matches added since previous\n" + "add_disjunction() or add_conjunction() and the next\n" + "add_disjunction() or add_conjunction().\n\n" + "See man:sd_journal_add_disjunction(3) for explanation."); +static PyObject* Reader_add_conjunction(Reader *self, PyObject *args) { - sd_journal_flush_matches(self->j); + int r; + r = sd_journal_add_conjunction(self->j); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_seek__doc__, -"seek(offset[, whence]) -> None\n\n" -"Seek through journal by `offset` number of entries. Argument\n" -"`whence` defines what the offset is relative to:\n" -"os.SEEK_SET (default) from first match in journal;\n" -"os.SEEK_CUR from current position in journal;\n" -"and os.SEEK_END is from last match in journal."); -static PyObject * -Journal_seek(Journal *self, PyObject *args, PyObject *keywds) + +PyDoc_STRVAR(Reader_flush_matches__doc__, + "flush_matches() -> None\n\n" + "Clear all current match filters."); +static PyObject* Reader_flush_matches(Reader *self, PyObject *args) { - int64_t offset; - int whence=SEEK_SET; - static char *kwlist[] = {"offset", "whence", NULL}; + sd_journal_flush_matches(self->j); + Py_RETURN_NONE; +} - if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist, - &offset, &whence)) - return NULL; - PyObject *result=NULL; - if (whence == SEEK_SET){ - int r; - Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_head(self->j); - Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - if (offset > 0LL) { - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); - } - }else if (whence == SEEK_CUR){ - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); - }else if (whence == SEEK_END){ - int r; - Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_tail(self->j); - Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - if (offset < 0LL) { - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); - }else{ - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL); - } - }else{ - PyErr_SetString(PyExc_ValueError, "Invalid value for whence"); - } - - if (result) - Py_DECREF(result); - if (PyErr_Occurred()) +PyDoc_STRVAR(Reader_seek_head__doc__, + "seek_head() -> None\n\n" + "Jump to the beginning of the journal.\n" + "This method invokes sd_journal_seek_head().\n" + "See man:sd_journal_seek_head(3)."); +static PyObject* Reader_seek_head(Reader *self, PyObject *args) +{ + int r; + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_head(self->j); + Py_END_ALLOW_THREADS + if (set_error(r, NULL, NULL)) return NULL; Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_seek_realtime__doc__, -"seek_realtime(realtime) -> None\n\n" -"Seek to nearest matching journal entry to `realtime`. Argument\n" -"`realtime` can must be an integer unix timestamp."); -static PyObject * -Journal_seek_realtime(Journal *self, PyObject *args) + +PyDoc_STRVAR(Reader_seek_tail__doc__, + "seek_tail() -> None\n\n" + "Jump to the end of the journal.\n" + "This method invokes sd_journal_seek_tail().\n" + "See man:sd_journal_seek_tail(3)."); +static PyObject* Reader_seek_tail(Reader *self, PyObject *args) { - double timedouble; - if (! PyArg_ParseTuple(args, "d", &timedouble)) + int r; + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_tail(self->j); + Py_END_ALLOW_THREADS + if (set_error(r, NULL, NULL)) return NULL; + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Reader_seek_realtime__doc__, + "seek_realtime(realtime) -> None\n\n" + "Seek to nearest matching journal entry to `realtime`. Argument\n" + "`realtime` in specified in seconds."); +static PyObject* Reader_seek_realtime(Reader *self, PyObject *args) +{ uint64_t timestamp; - timestamp = (uint64_t) (timedouble * 1.0E6); + int r; - if ((int64_t) timestamp < 0LL) { - PyErr_SetString(PyExc_ValueError, "Time must be positive integer"); + if (!PyArg_ParseTuple(args, "K:seek_realtime", ×tamp)) return NULL; - } - int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_realtime_usec(self->j, timestamp); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + if (set_error(r, NULL, NULL)) return NULL; - } Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_seek_monotonic__doc__, -"seek_monotonic(monotonic[, bootid]) -> None\n\n" -"Seek to nearest matching journal entry to `monotonic`. Argument\n" -"`monotonic` is an timestamp from boot in seconds.\n" -"Argument `bootid` is a string representing which boot the\n" -"monotonic time is reference to. Defaults to current bootid."); -static PyObject * -Journal_seek_monotonic(Journal *self, PyObject *args) -{ - double timedouble; - char *bootid=NULL; - if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid)) - return NULL; +PyDoc_STRVAR(Reader_seek_monotonic__doc__, + "seek_monotonic(monotonic[, bootid]) -> None\n\n" + "Seek to nearest matching journal entry to `monotonic`. Argument\n" + "`monotonic` is an timestamp from boot in microseconds.\n" + "Argument `bootid` is a string representing which boot the\n" + "monotonic time is reference to. Defaults to current bootid."); +static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args) +{ + char *bootid = NULL; uint64_t timestamp; - timestamp = (uint64_t) (timedouble * 1.0E6); + sd_id128_t id; + int r; - if ((int64_t) timestamp < 0LL) { - PyErr_SetString(PyExc_ValueError, "Time must be positive number"); + if (!PyArg_ParseTuple(args, "K|z:seek_monotonic", ×tamp, &bootid)) return NULL; - } - sd_id128_t sd_id; - int r; if (bootid) { - r = sd_id128_from_string(bootid, &sd_id); - if (r == -EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid bootid"); - return NULL; - }else if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + r = sd_id128_from_string(bootid, &id); + if (set_error(r, NULL, "Invalid bootid")) return NULL; - } - }else{ - r = sd_id128_get_boot(&sd_id); - if (r == -EIO) { - PyErr_SetString(PyExc_IOError, "Error getting current boot ID"); - return NULL; - }else if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + } else { + Py_BEGIN_ALLOW_THREADS + r = sd_id128_get_boot(&id); + Py_END_ALLOW_THREADS + if (set_error(r, NULL, NULL)) return NULL; - } } Py_BEGIN_ALLOW_THREADS - r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp); + r = sd_journal_seek_monotonic_usec(self->j, id, timestamp); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + if (set_error(r, NULL, NULL)) return NULL; - } + Py_RETURN_NONE; } - -PyDoc_STRVAR(Journal_wait__doc__, -"wait([timeout]) -> Change state (integer)\n\n" -"Waits until there is a change in the journal. Argument `timeout`\n" -"is the maximum number of seconds to wait before returning\n" -"regardless if journal has changed. If `timeout` is not given or is\n" -"0, then it will block forever.\n" -"Will return constants: NOP if no change; APPEND if new\n" -"entries have been added to the end of the journal; and\n" -"INVALIDATE if journal files have been added or removed."); -static PyObject * -Journal_wait(Journal *self, PyObject *args, PyObject *keywds) + + +PyDoc_STRVAR(Reader_process__doc__, + "process() -> state change (integer)\n\n" + "Process events and reset the readable state of the file\n" + "descriptor returned by .fileno().\n\n" + "Will return constants: NOP if no change; APPEND if new\n" + "entries have been added to the end of the journal; and\n" + "INVALIDATE if journal files have been added or removed.\n\n" + "See man:sd_journal_process(3) for further discussion."); +static PyObject* Reader_process(Reader *self, PyObject *args) { - int64_t timeout=0LL; - if (! PyArg_ParseTuple(args, "|L", &timeout)) + int r; + + assert(!args); + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_process(self->j); + Py_END_ALLOW_THREADS + if (set_error(r, NULL, NULL) < 0) return NULL; + return long_FromLong(r); +} + + +PyDoc_STRVAR(Reader_wait__doc__, + "wait([timeout]) -> state change (integer)\n\n" + "Wait for a change in the journal. Argument `timeout` specifies\n" + "the maximum number of microseconds to wait before returning\n" + "regardless of wheter the journal has changed. If `timeout` is -1,\n" + "then block forever.\n\n" + "Will return constants: NOP if no change; APPEND if new\n" + "entries have been added to the end of the journal; and\n" + "INVALIDATE if journal files have been added or removed.\n\n" + "See man:sd_journal_wait(3) for further discussion."); +static PyObject* Reader_wait(Reader *self, PyObject *args) +{ int r; + int64_t timeout; + + if (!PyArg_ParseTuple(args, "|L:wait", &timeout)) + return NULL; + Py_BEGIN_ALLOW_THREADS - if ( timeout == 0LL) { - r = sd_journal_wait(self->j, (uint64_t) -1); - }else{ - r = sd_journal_wait(self->j, timeout * 1E6); - } + r = sd_journal_wait(self->j, timeout); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError : - PyExc_OSError; - PyErr_SetFromErrno(errtype); + if (set_error(r, NULL, NULL) < 0) return NULL; - } -#if PY_MAJOR_VERSION >=3 - return PyLong_FromLong(r); -#else - return PyInt_FromLong(r); -#endif + + return long_FromLong(r); } -PyDoc_STRVAR(Journal_seek_cursor__doc__, -"seek_cursor(cursor) -> None\n\n" -"Seeks to journal entry by given unique reference `cursor`."); -static PyObject * -Journal_seek_cursor(Journal *self, PyObject *args) + +PyDoc_STRVAR(Reader_seek_cursor__doc__, + "seek_cursor(cursor) -> None\n\n" + "Seek to journal entry by given unique reference `cursor`."); +static PyObject* Reader_seek_cursor(Reader *self, PyObject *args) { const char *cursor; - if (! PyArg_ParseTuple(args, "s", &cursor)) + int r; + + if (!PyArg_ParseTuple(args, "s:seek_cursor", &cursor)) return NULL; - int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_cursor(self->j, cursor); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyObject *errtype = r == -EINVAL ? PyExc_ValueError : - r == -ENOMEM ? PyExc_MemoryError : - PyExc_OSError; - PyErr_SetFromErrno(errtype); + if (set_error(r, NULL, "Invalid cursor")) return NULL; - } Py_RETURN_NONE; } -static PyObject * -Journal_iter(PyObject *self) + +PyDoc_STRVAR(Reader_get_cursor__doc__, + "get_cursor() -> str\n\n" + "Return a cursor string for the current journal entry.\n\n" + "Wraps sd_journal_get_cursor(). See man:sd_journal_get_cursor(3)."); +static PyObject* Reader_get_cursor(Reader *self, PyObject *args) { - Py_INCREF(self); - return self; + _cleanup_free_ char *cursor = NULL; + int r; + + assert(self); + assert(!args); + + r = sd_journal_get_cursor(self->j, &cursor); + if (set_error(r, NULL, NULL)) + return NULL; + + return unicode_FromString(cursor); } -static PyObject * -Journal_iternext(PyObject *self) + +PyDoc_STRVAR(Reader_test_cursor__doc__, + "test_cursor(str) -> bool\n\n" + "Test whether the cursor string matches current journal entry.\n\n" + "Wraps sd_journal_test_cursor(). See man:sd_journal_test_cursor(3)."); +static PyObject* Reader_test_cursor(Reader *self, PyObject *args) { - PyObject *dict; - Py_ssize_t dict_size; + const char *cursor; + int r; + + assert(self); + assert(args); - dict = PyObject_CallMethod(self, "get_next", ""); - dict_size = PyDict_Size(dict); - if ((int64_t) dict_size > 0LL) { - return dict; - }else{ - Py_DECREF(dict); - PyErr_SetNone(PyExc_StopIteration); + if (!PyArg_ParseTuple(args, "s:test_cursor", &cursor)) return NULL; - } + + r = sd_journal_test_cursor(self->j, cursor); + set_error(r, NULL, NULL); + if (r < 0) + return NULL; + + return PyBool_FromLong(r); } -PyDoc_STRVAR(Journal_query_unique__doc__, -"query_unique(field) -> a set of values\n\n" -"Returns a set of unique values in journal for given `field`.\n" -"Note this does not respect any journal matches."); -static PyObject * -Journal_query_unique(Journal *self, PyObject *args) +PyDoc_STRVAR(Reader_query_unique__doc__, + "query_unique(field) -> a set of values\n\n" + "Return a set of unique values appearing in journal for the\n" + "given `field`. Note this does not respect any journal matches."); +static PyObject* Reader_query_unique(Reader *self, PyObject *args) { char *query; - if (! PyArg_ParseTuple(args, "s", &query)) + int r; + const void *uniq; + size_t uniq_len; + PyObject *value_set, *key, *value; + + if (!PyArg_ParseTuple(args, "s:query_unique", &query)) return NULL; - int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_query_unique(self->j, query); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyObject *errtype = r == -EINVAL ? PyExc_ValueError : - r == -ENOMEM ? PyExc_MemoryError : - PyExc_OSError; - PyErr_SetFromErrno(errtype); + if (set_error(r, NULL, "Invalid field name")) return NULL; - } - const void *uniq; - size_t uniq_len; - const char *delim_ptr; - PyObject *value_set, *key, *value; value_set = PySet_New(0); - -#if PY_MAJOR_VERSION >=3 - key = PyUnicode_FromString(query); -#else - key = PyString_FromString(query); -#endif + key = unicode_FromString(query); SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) { + const char *delim_ptr; + delim_ptr = memchr(uniq, '=', uniq_len); - value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1)); + value = PyBytes_FromStringAndSize( + delim_ptr + 1, + (const char*) uniq + uniq_len - (delim_ptr + 1)); PySet_Add(value_set, value); Py_DECREF(value); } @@ -554,155 +883,214 @@ Journal_query_unique(Journal *self, PyObject *args) return value_set; } -static PyObject * -Journal_get_data_threshold(Journal *self, void *closure) + +PyDoc_STRVAR(Reader_get_catalog__doc__, + "get_catalog() -> str\n\n" + "Retrieve a message catalog entry for the current journal entry.\n" + "Will throw IndexError if the entry has no MESSAGE_ID\n" + "and KeyError is the id is specified, but hasn't been found\n" + "in the catalog.\n\n" + "Wraps man:sd_journal_get_catalog(3)."); +static PyObject* Reader_get_catalog(Reader *self, PyObject *args) +{ + int r; + _cleanup_free_ char *msg = NULL; + + assert(self); + assert(!args); + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_get_catalog(self->j, &msg); + Py_END_ALLOW_THREADS + if (r == -ENOENT) { + const void* mid; + size_t mid_len; + + r = sd_journal_get_data(self->j, "MESSAGE_ID", &mid, &mid_len); + if (r == 0) { + const size_t l = sizeof("MESSAGE_ID"); + assert(mid_len > l); + PyErr_Format(PyExc_KeyError, "%.*s", (int) (mid_len - l), + (const char*) mid + l); + } else if (r == -ENOENT) + PyErr_SetString(PyExc_IndexError, "no MESSAGE_ID field"); + else + set_error(r, NULL, NULL); + return NULL; + } else if (set_error(r, NULL, NULL)) + return NULL; + + return unicode_FromString(msg); +} + + +PyDoc_STRVAR(get_catalog__doc__, + "get_catalog(id128) -> str\n\n" + "Retrieve a message catalog entry for the given id.\n" + "Wraps man:sd_journal_get_catalog_for_message_id(3)."); +static PyObject* get_catalog(PyObject *self, PyObject *args) +{ + int r; + char *id_ = NULL; + sd_id128_t id; + _cleanup_free_ char *msg = NULL; + + assert(!self); + assert(args); + + if (!PyArg_ParseTuple(args, "z:get_catalog", &id_)) + return NULL; + + r = sd_id128_from_string(id_, &id); + if (set_error(r, NULL, "Invalid id128")) + return NULL; + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_get_catalog_for_message_id(id, &msg); + Py_END_ALLOW_THREADS + if (set_error(r, NULL, NULL)) + return NULL; + + return unicode_FromString(msg); +} + + +PyDoc_STRVAR(data_threshold__doc__, + "Threshold for field size truncation in bytes.\n\n" + "Fields longer than this will be truncated to the threshold size.\n" + "Defaults to 64Kb."); + +static PyObject* Reader_get_data_threshold(Reader *self, void *closure) { size_t cvalue; - PyObject *value; int r; r = sd_journal_get_data_threshold(self->j, &cvalue); - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + if (set_error(r, NULL, NULL)) return NULL; - } -#if PY_MAJOR_VERSION >=3 - value = PyLong_FromSize_t(cvalue); -#else - value = PyInt_FromSize_t(cvalue); -#endif - return value; + return long_FromSize_t(cvalue); } -static int -Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) +static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closure) { + int r; if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold"); + PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold"); return -1; } -#if PY_MAJOR_VERSION >=3 - if (! PyLong_Check(value)){ -#else - if (! PyInt_Check(value)){ -#endif - PyErr_SetString(PyExc_TypeError, "Data threshold must be int"); - return -1; - } - int r; -#if PY_MAJOR_VERSION >=3 - r = sd_journal_set_data_threshold(self->j, (size_t) PyLong_AsLong(value)); -#else - r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value)); -#endif - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + if (!long_Check(value)){ + PyErr_SetString(PyExc_TypeError, "Data threshold must be an int"); return -1; } - return 0; + r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value)); + return set_error(r, NULL, NULL); +} + + +PyDoc_STRVAR(closed__doc__, + "True iff journal is closed"); +static PyObject* Reader_get_closed(Reader *self, void *closure) +{ + return PyBool_FromLong(self->j == NULL); } -static PyGetSetDef Journal_getseters[] = { - {"data_threshold", - (getter)Journal_get_data_threshold, - (setter)Journal_set_data_threshold, - "data threshold", - NULL}, - {NULL} + +static PyGetSetDef Reader_getsetters[] = { + {(char*) "data_threshold", + (getter) Reader_get_data_threshold, + (setter) Reader_set_data_threshold, + (char*) data_threshold__doc__, + NULL}, + {(char*) "closed", + (getter) Reader_get_closed, + NULL, + (char*) closed__doc__, + NULL}, + {} /* Sentinel */ }; -static PyMethodDef Journal_methods[] = { - {"get_next", (PyCFunction)Journal_get_next, METH_VARARGS, - Journal_get_next__doc__}, - {"get_previous", (PyCFunction)Journal_get_previous, METH_VARARGS, - Journal_get_previous__doc__}, - {"add_match", (PyCFunction)Journal_add_match, METH_VARARGS|METH_KEYWORDS, - Journal_add_match__doc__}, - {"add_disjunction", (PyCFunction)Journal_add_disjunction, METH_NOARGS, - Journal_add_disjunction__doc__}, - {"flush_matches", (PyCFunction)Journal_flush_matches, METH_NOARGS, - Journal_flush_matches__doc__}, - {"seek", (PyCFunction)Journal_seek, METH_VARARGS | METH_KEYWORDS, - Journal_seek__doc__}, - {"seek_realtime", (PyCFunction)Journal_seek_realtime, METH_VARARGS, - Journal_seek_realtime__doc__}, - {"seek_monotonic", (PyCFunction)Journal_seek_monotonic, METH_VARARGS, - Journal_seek_monotonic__doc__}, - {"wait", (PyCFunction)Journal_wait, METH_VARARGS, - Journal_wait__doc__}, - {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS, - Journal_seek_cursor__doc__}, - {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS, - Journal_query_unique__doc__}, - {NULL} /* Sentinel */ +static PyMethodDef Reader_methods[] = { + {"fileno", (PyCFunction) Reader_fileno, METH_NOARGS, Reader_fileno__doc__}, + {"reliable_fd", (PyCFunction) Reader_reliable_fd, METH_NOARGS, Reader_reliable_fd__doc__}, + {"get_events", (PyCFunction) Reader_get_events, METH_NOARGS, Reader_get_events__doc__}, + {"get_timeout", (PyCFunction) Reader_get_timeout, METH_NOARGS, Reader_get_timeout__doc__}, + {"get_timeout_ms", (PyCFunction) Reader_get_timeout_ms, METH_NOARGS, Reader_get_timeout_ms__doc__}, + {"close", (PyCFunction) Reader_close, METH_NOARGS, Reader_close__doc__}, + {"get_usage", (PyCFunction) Reader_get_usage, METH_NOARGS, Reader_get_usage__doc__}, + {"__enter__", (PyCFunction) Reader___enter__, METH_NOARGS, Reader___enter____doc__}, + {"__exit__", (PyCFunction) Reader___exit__, METH_VARARGS, Reader___exit____doc__}, + {"_next", (PyCFunction) Reader_next, METH_VARARGS, Reader_next__doc__}, + {"_previous", (PyCFunction) Reader_previous, METH_VARARGS, Reader_previous__doc__}, + {"_get", (PyCFunction) Reader_get, METH_VARARGS, Reader_get__doc__}, + {"_get_all", (PyCFunction) Reader_get_all, METH_NOARGS, Reader_get_all__doc__}, + {"_get_realtime", (PyCFunction) Reader_get_realtime, METH_NOARGS, Reader_get_realtime__doc__}, + {"_get_monotonic", (PyCFunction) Reader_get_monotonic, METH_NOARGS, Reader_get_monotonic__doc__}, + {"add_match", (PyCFunction) Reader_add_match, METH_VARARGS|METH_KEYWORDS, Reader_add_match__doc__}, + {"add_disjunction", (PyCFunction) Reader_add_disjunction, METH_NOARGS, Reader_add_disjunction__doc__}, + {"add_conjunction", (PyCFunction) Reader_add_conjunction, METH_NOARGS, Reader_add_conjunction__doc__}, + {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__}, + {"seek_head", (PyCFunction) Reader_seek_head, METH_NOARGS, Reader_seek_head__doc__}, + {"seek_tail", (PyCFunction) Reader_seek_tail, METH_NOARGS, Reader_seek_tail__doc__}, + {"seek_realtime", (PyCFunction) Reader_seek_realtime, METH_VARARGS, Reader_seek_realtime__doc__}, + {"seek_monotonic", (PyCFunction) Reader_seek_monotonic, METH_VARARGS, Reader_seek_monotonic__doc__}, + {"process", (PyCFunction) Reader_process, METH_NOARGS, Reader_process__doc__}, + {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__}, + {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__}, + {"_get_cursor", (PyCFunction) Reader_get_cursor, METH_NOARGS, Reader_get_cursor__doc__}, + {"test_cursor", (PyCFunction) Reader_test_cursor, METH_VARARGS, Reader_test_cursor__doc__}, + {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__}, + {"get_catalog", (PyCFunction) Reader_get_catalog, METH_NOARGS, Reader_get_catalog__doc__}, + {} /* Sentinel */ }; -static PyTypeObject JournalType = { +static PyTypeObject ReaderType = { PyVarObject_HEAD_INIT(NULL, 0) - "_reader.Journal", /*tp_name*/ - sizeof(Journal), /*tp_basicsize*/ - 0, /*tp_itemsize*/ - (destructor)Journal_dealloc, /*tp_dealloc*/ - 0, /*tp_print*/ - 0, /*tp_getattr*/ - 0, /*tp_setattr*/ - 0, /*tp_compare*/ - 0, /*tp_repr*/ - 0, /*tp_as_number*/ - 0, /*tp_as_sequence*/ - 0, /*tp_as_mapping*/ - 0, /*tp_hash */ - 0, /*tp_call*/ - 0, /*tp_str*/ - 0, /*tp_getattro*/ - 0, /*tp_setattro*/ - 0, /*tp_as_buffer*/ - Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,/*tp_flags*/ - Journal__doc__, /* tp_doc */ - 0, /* tp_traverse */ - 0, /* tp_clear */ - 0, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ - Journal_iter, /* tp_iter */ - Journal_iternext, /* tp_iternext */ - Journal_methods, /* tp_methods */ - 0, /* tp_members */ - Journal_getseters, /* tp_getset */ - 0, /* tp_base */ - 0, /* tp_dict */ - 0, /* tp_descr_get */ - 0, /* tp_descr_set */ - 0, /* tp_dictoffset */ - (initproc)Journal_init, /* tp_init */ - 0, /* tp_alloc */ - PyType_GenericNew, /* tp_new */ + .tp_name = "_reader._Reader", + .tp_basicsize = sizeof(Reader), + .tp_dealloc = (destructor) Reader_dealloc, + .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, + .tp_doc = Reader__doc__, + .tp_methods = Reader_methods, + .tp_getset = Reader_getsetters, + .tp_init = (initproc) Reader_init, + .tp_new = PyType_GenericNew, +}; + +static PyMethodDef methods[] = { + { "_get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__}, + {} /* Sentinel */ }; #if PY_MAJOR_VERSION >= 3 -static PyModuleDef _reader_module = { +static PyModuleDef module = { PyModuleDef_HEAD_INIT, "_reader", - "Module that reads systemd journal similar to journalctl.", + module__doc__, -1, - NULL, NULL, NULL, NULL, NULL + methods, }; #endif +#if PY_MAJOR_VERSION >= 3 +static bool initialized = false; +#endif + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + PyMODINIT_FUNC #if PY_MAJOR_VERSION >= 3 PyInit__reader(void) #else -init_reader(void) +init_reader(void) #endif { PyObject* m; PyDateTime_IMPORT; - if (PyType_Ready(&JournalType) < 0) + if (PyType_Ready(&ReaderType) < 0) #if PY_MAJOR_VERSION >= 3 return NULL; #else @@ -710,26 +1098,46 @@ init_reader(void) #endif #if PY_MAJOR_VERSION >= 3 - m = PyModule_Create(&_reader_module); + m = PyModule_Create(&module); if (m == NULL) return NULL; + + if (!initialized) { + PyStructSequence_InitType(&MonotonicType, &Monotonic_desc); + initialized = true; + } #else - m = Py_InitModule3("_reader", NULL, - "Module that reads systemd journal similar to journalctl."); + m = Py_InitModule3("_reader", methods, module__doc__); if (m == NULL) return; #endif - Py_INCREF(&JournalType); - PyModule_AddObject(m, "_Journal", (PyObject *)&JournalType); - PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP); - PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND); - PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE); - PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY); - PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY); - PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY); + Py_INCREF(&ReaderType); +#if PY_MAJOR_VERSION >= 3 + Py_INCREF(&MonotonicType); +#endif + if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) || +#if PY_MAJOR_VERSION >= 3 + PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) || +#endif + PyModule_AddIntConstant(m, "NOP", SD_JOURNAL_NOP) || + PyModule_AddIntConstant(m, "APPEND", SD_JOURNAL_APPEND) || + PyModule_AddIntConstant(m, "INVALIDATE", SD_JOURNAL_INVALIDATE) || + PyModule_AddIntConstant(m, "LOCAL_ONLY", SD_JOURNAL_LOCAL_ONLY) || + PyModule_AddIntConstant(m, "RUNTIME_ONLY", SD_JOURNAL_RUNTIME_ONLY) || + PyModule_AddIntConstant(m, "SYSTEM", SD_JOURNAL_SYSTEM) || + PyModule_AddIntConstant(m, "SYSTEM_ONLY", SD_JOURNAL_SYSTEM_ONLY) || + PyModule_AddIntConstant(m, "CURRENT_USER", SD_JOURNAL_CURRENT_USER) || + PyModule_AddStringConstant(m, "__version__", PACKAGE_VERSION)) { +#if PY_MAJOR_VERSION >= 3 + Py_DECREF(m); + return NULL; +#endif + } #if PY_MAJOR_VERSION >= 3 return m; #endif } + +#pragma GCC diagnostic pop