X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fpython-systemd%2F_reader.c;h=05993b33cd2644c79fb6f814ae566917270f9354;hp=42029abd4647234f12fd631f55a7fd05e770c141;hb=7fd1b19bc9e9f5574f2877936b8ac267c7706947;hpb=0eff0f3bce1d0826765f6e84ff046b10fe5a1a12 diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 42029abd4..05993b33c 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -30,23 +31,6 @@ #include "macro.h" #include "util.h" -#if PY_MAJOR_VERSION >=3 -# define unicode_FromStringAndSize PyUnicode_FromStringAndSize -# define unicode_FromString PyUnicode_FromString -# define long_FromLong PyLong_FromLong -# define long_FromSize_t PyLong_FromSize_t -# define long_Check PyLong_Check -# define long_AsLong PyLong_AsLong -#else -/* Python 3 type naming convention is used */ -# define unicode_FromStringAndSize PyString_FromStringAndSize -# define unicode_FromString PyString_FromString -# define long_FromLong PyInt_FromLong -# define long_FromSize_t PyInt_FromSize_t -# define long_Check PyInt_Check -# define long_AsLong PyInt_AsLong -#endif - typedef struct { PyObject_HEAD sd_journal *j; @@ -67,6 +51,11 @@ static int set_error(int r, const char* path, const char* invalid_message) { return -1; } + +PyDoc_STRVAR(module__doc__, + "Class to reads the systemd journal similar to journalctl."); + + #if PY_MAJOR_VERSION >= 3 static PyTypeObject MonotonicType; @@ -76,7 +65,7 @@ PyDoc_STRVAR(MonotonicType__doc__, static PyStructSequence_Field MonotonicType_fields[] = { {(char*) "timestamp", (char*) "Time"}, {(char*) "bootid", (char*) "Unique identifier of the boot"}, - {NULL, NULL} + {} /* Sentinel */ }; static PyStructSequence_Desc Monotonic_desc = { @@ -87,6 +76,7 @@ static PyStructSequence_Desc Monotonic_desc = { }; #endif + static void Reader_dealloc(Reader* self) { sd_journal_close(self->j); @@ -94,8 +84,8 @@ static void Reader_dealloc(Reader* self) } PyDoc_STRVAR(Reader__doc__, - "Reader([flags | path]) -> ...\n\n" - "Reader allows filtering and retrieval of Journal entries.\n" + "_Reader([flags | path]) -> ...\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" @@ -104,7 +94,9 @@ PyDoc_STRVAR(Reader__doc__, "volatile journal files; and SYSTEM_ONLY opens only\n" "journal files of system services and the kernel.\n\n" "Argument `path` is the directory of journal files. Note that\n" - "`flags` and `path` are exclusive.\n"); + "`flags` and `path` 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; @@ -133,6 +125,7 @@ static int Reader_init(Reader *self, PyObject *args, PyObject *keywds) 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" @@ -140,14 +133,14 @@ PyDoc_STRVAR(Reader_fileno__doc__, "See man:sd_journal_get_fd(3)."); static PyObject* Reader_fileno(Reader *self, PyObject *args) { - int r; - r = sd_journal_get_fd(self->j); - set_error(r, NULL, NULL); - if (r < 0) + int fd = sd_journal_get_fd(self->j); + set_error(fd, NULL, NULL); + if (fd < 0) return NULL; - return long_FromLong(r); + 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" @@ -155,38 +148,163 @@ PyDoc_STRVAR(Reader_reliable_fd__doc__, "See man:sd_journal_reliable_fd(3)."); static PyObject* Reader_reliable_fd(Reader *self, PyObject *args) { - int r; - r = sd_journal_reliable_fd(self->j); + 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; + + if (t == (uint64_t) -1) + return PyLong_FromLong(-1); + else { + struct timespec ts; + uint64_t n; + int msec; + + clock_gettime(CLOCK_MONOTONIC, &ts); + n = (uint64_t) ts.tv_sec * 1000000 + ts.tv_nsec / 1000; + msec = t > n ? (int) ((t - n + 999) / 1000) : 0; + + return PyLong_FromLong(msec); + } +} + + PyDoc_STRVAR(Reader_close__doc__, - "reliable_fd() -> None\n\n" + "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_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* Reader_get_next(Reader *self, PyObject *args) + +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) +{ + assert(self); + + sd_journal_close(self->j); + self->j = NULL; + Py_RETURN_NONE; +} + + +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) { - PyObject *dict; - const void *msg; - size_t msg_len; int64_t skip = 1LL; int r; - if (!PyArg_ParseTuple(args, "|L", &skip)) + if (!PyArg_ParseTuple(args, "|L:next", &skip)) return NULL; if (skip == 0LL) { @@ -210,32 +328,111 @@ static PyObject* Reader_get_next(Reader *self, PyObject *args) set_error(r, NULL, NULL); if (r < 0) return NULL; - else if (r == 0) /* EOF */ - return PyDict_New(); + 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; + + 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; + } + + 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; + int r; dict = PyDict_New(); if (!dict) return NULL; SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) { - PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL; - const char *delim_ptr; + _cleanup_Py_DECREF_ PyObject *key = NULL, *value = NULL; - delim_ptr = memchr(msg, '=', msg_len); - if (!delim_ptr) { - PyErr_SetString(PyExc_OSError, - "journal gave us a field without '='"); - goto error; - } - - key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg); - if (!key) - goto error; - - value = PyBytes_FromStringAndSize( - delim_ptr + 1, - (const char*) msg + msg_len - (delim_ptr + 1) ); - if (!value) + r = extract(msg, msg_len, &key, &value); + if (r < 0) goto error; if (PyDict_Contains(dict, key)) { @@ -246,7 +443,7 @@ static PyObject* Reader_get_next(Reader *self, PyObject *args) if (r < 0) goto error; } else { - PyObject _cleanup_Py_DECREF_ *tmp_list = PyList_New(0); + _cleanup_Py_DECREF_ PyObject *tmp_list = PyList_New(0); if (!tmp_list) goto error; @@ -269,102 +466,81 @@ static PyObject* Reader_get_next(Reader *self, PyObject *args) } } - { - PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL; - uint64_t realtime; + return dict; - r = sd_journal_get_realtime_usec(self->j, &realtime); - if (set_error(r, NULL, NULL)) - goto error; +error: + Py_DECREF(dict); + return NULL; +} - key = unicode_FromString("__REALTIME_TIMESTAMP"); - if (!key) - goto error; - assert_cc(sizeof(unsigned long long) == sizeof(realtime)); - value = PyLong_FromUnsignedLongLong(realtime); - if (!value) - goto error; +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; - if (PyDict_SetItem(dict, key, value)) - goto error; - } + assert(self); + assert(!args); - { - PyObject _cleanup_Py_DECREF_ - *key = NULL, *timestamp = NULL, *bytes = NULL, *value = NULL; - sd_id128_t id; - uint64_t monotonic; + r = sd_journal_get_realtime_usec(self->j, ×tamp); + if (set_error(r, NULL, NULL)) + return NULL; - r = sd_journal_get_monotonic_usec(self->j, &monotonic, &id); - if (set_error(r, NULL, NULL)) - goto error; + assert_cc(sizeof(unsigned long long) == sizeof(timestamp)); + return PyLong_FromUnsignedLongLong(timestamp); +} - assert_cc(sizeof(unsigned long long) == sizeof(monotonic)); - key = unicode_FromString("__MONOTONIC_TIMESTAMP"); - timestamp = PyLong_FromUnsignedLongLong(monotonic); - bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); -#if PY_MAJOR_VERSION >= 3 - value = PyStructSequence_New(&MonotonicType); -#else - value = PyTuple_New(2); -#endif - if (!key || !timestamp || !bytes || !value) - goto error; - Py_INCREF(timestamp); - Py_INCREF(bytes); +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) +{ + 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 - PyStructSequence_SET_ITEM(value, 0, timestamp); - PyStructSequence_SET_ITEM(value, 1, bytes); + tuple = PyStructSequence_New(&MonotonicType); #else - PyTuple_SET_ITEM(value, 0, timestamp); - PyTuple_SET_ITEM(value, 1, bytes); + tuple = PyTuple_New(2); #endif - - if (PyDict_SetItem(dict, key, value)) - goto error; - } - - { - PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL; - char _cleanup_free_ *cursor = NULL; - - r = sd_journal_get_cursor(self->j, &cursor); - if (set_error(r, NULL, NULL)) - goto error; - - key = unicode_FromString("__CURSOR"); - if (!key) - goto error; - - value = PyBytes_FromString(cursor); - if (!value) - goto error; - - if (PyDict_SetItem(dict, key, value)) - goto error; + if (!monotonic || !bootid || !tuple) { + Py_XDECREF(monotonic); + Py_XDECREF(bootid); + Py_XDECREF(tuple); + return NULL; } - return dict; -error: - Py_DECREF(dict); - return NULL; -} - -PyDoc_STRVAR(Reader_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* Reader_get_previous(Reader *self, PyObject *args) -{ - int64_t skip = 1LL; - if (!PyArg_ParseTuple(args, "|L", &skip)) - 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, (char*) "get_next", - (char*) "L", -skip); + return tuple; } PyDoc_STRVAR(Reader_add_match__doc__, @@ -377,7 +553,7 @@ static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds { char *match; int match_len, r; - if (!PyArg_ParseTuple(args, "s#", &match, &match_len)) + if (!PyArg_ParseTuple(args, "s#:add_match", &match, &match_len)) return NULL; r = sd_journal_add_match(self->j, match, match_len); @@ -388,6 +564,7 @@ static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds Py_RETURN_NONE; } + PyDoc_STRVAR(Reader_add_disjunction__doc__, "add_disjunction() -> None\n\n" "Inserts a logical OR between matches added before and afterwards."); @@ -401,6 +578,7 @@ static PyObject* Reader_add_disjunction(Reader *self, PyObject *args) Py_RETURN_NONE; } + PyDoc_STRVAR(Reader_flush_matches__doc__, "flush_matches() -> None\n\n" "Clear all current match filters."); @@ -410,6 +588,7 @@ static PyObject* Reader_flush_matches(Reader *self, PyObject *args) Py_RETURN_NONE; } + PyDoc_STRVAR(Reader_seek_head__doc__, "seek_head() -> None\n\n" "Jump to the beginning of the journal.\n" @@ -426,9 +605,10 @@ static PyObject* Reader_seek_head(Reader *self, PyObject *args) Py_RETURN_NONE; } + PyDoc_STRVAR(Reader_seek_tail__doc__, "seek_tail() -> None\n\n" - "Jump to the beginning of the journal.\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) @@ -442,25 +622,19 @@ static PyObject* Reader_seek_tail(Reader *self, PyObject *args) 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` can must be an integer unix timestamp."); + "`realtime` in specified in seconds."); static PyObject* Reader_seek_realtime(Reader *self, PyObject *args) { - double timedouble; uint64_t timestamp; int r; - if (!PyArg_ParseTuple(args, "d", &timedouble)) + if (!PyArg_ParseTuple(args, "K:seek_realtime", ×tamp)) return NULL; - timestamp = (uint64_t) (timedouble * 1.0E6); - if ((int64_t) timestamp < 0LL) { - PyErr_SetString(PyExc_ValueError, "Time must be a positive integer"); - return NULL; - } - Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_realtime_usec(self->j, timestamp); Py_END_ALLOW_THREADS @@ -469,30 +643,23 @@ static PyObject* Reader_seek_realtime(Reader *self, PyObject *args) Py_RETURN_NONE; } + 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 seconds.\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) { - double timedouble; char *bootid = NULL; uint64_t timestamp; sd_id128_t id; int r; - if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid)) + if (!PyArg_ParseTuple(args, "K|z:seek_monotonic", ×tamp, &bootid)) return NULL; - timestamp = (uint64_t) (timedouble * 1.0E6); - - if ((int64_t) timestamp < 0LL) { - PyErr_SetString(PyExc_ValueError, "Time must be positive number"); - return NULL; - } - if (bootid) { r = sd_id128_from_string(bootid, &id); if (set_error(r, NULL, "Invalid bootid")) @@ -510,29 +677,55 @@ static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args) Py_END_ALLOW_THREADS if (set_error(r, NULL, NULL)) return NULL; + Py_RETURN_NONE; } + +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) +{ + 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 seconds to wait before returning\n" - "regardless of wheter the journal has changed. If `timeout` is not given\n" - "or is 0, then block forever.\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."); -static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds) + "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 = 0LL; + int64_t timeout; - if (!PyArg_ParseTuple(args, "|L", &timeout)) + if (!PyArg_ParseTuple(args, "|L:wait", &timeout)) return NULL; Py_BEGIN_ALLOW_THREADS - r = sd_journal_wait(self->j, - timeout == 0 ? (uint64_t) -1 : timeout * 1E6); + r = sd_journal_wait(self->j, timeout); Py_END_ALLOW_THREADS if (set_error(r, NULL, NULL) < 0) return NULL; @@ -540,6 +733,7 @@ static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds) return long_FromLong(r); } + PyDoc_STRVAR(Reader_seek_cursor__doc__, "seek_cursor(cursor) -> None\n\n" "Seek to journal entry by given unique reference `cursor`."); @@ -548,7 +742,7 @@ static PyObject* Reader_seek_cursor(Reader *self, PyObject *args) const char *cursor; int r; - if (!PyArg_ParseTuple(args, "s", &cursor)) + if (!PyArg_ParseTuple(args, "s:seek_cursor", &cursor)) return NULL; Py_BEGIN_ALLOW_THREADS @@ -559,28 +753,48 @@ static PyObject* Reader_seek_cursor(Reader *self, PyObject *args) Py_RETURN_NONE; } -static PyObject* Reader_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* Reader_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, (char*) "get_next", (char*) ""); - if (PyErr_Occurred()) + if (!PyArg_ParseTuple(args, "s:test_cursor", &cursor)) return NULL; - dict_size = PyDict_Size(dict); - if ((int64_t) dict_size > 0LL) { - return dict; - } else { - Py_DECREF(dict); - PyErr_SetNone(PyExc_StopIteration); + + r = sd_journal_test_cursor(self->j, cursor); + set_error(r, NULL, NULL); + if (r < 0) return NULL; - } + + return PyBool_FromLong(r); } PyDoc_STRVAR(Reader_query_unique__doc__, @@ -595,7 +809,7 @@ static PyObject* Reader_query_unique(Reader *self, PyObject *args) size_t uniq_len; PyObject *value_set, *key, *value; - if (!PyArg_ParseTuple(args, "s", &query)) + if (!PyArg_ParseTuple(args, "s:query_unique", &query)) return NULL; Py_BEGIN_ALLOW_THREADS @@ -621,6 +835,78 @@ static PyObject* Reader_query_unique(Reader *self, PyObject *args) return value_set; } + +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 int 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" @@ -653,21 +939,45 @@ static int Reader_set_data_threshold(Reader *self, PyObject *value, void *closur return set_error(r, NULL, NULL); } -static PyGetSetDef Reader_getseters[] = { + +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 Reader_getsetters[] = { {(char*) "data_threshold", (getter) Reader_get_data_threshold, (setter) Reader_set_data_threshold, (char*) data_threshold__doc__, NULL}, - {NULL} + {(char*) "closed", + (getter) Reader_get_closed, + NULL, + (char*) closed__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_next", (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__}, - {"get_previous", (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__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__}, {"flush_matches", (PyCFunction) Reader_flush_matches, METH_NOARGS, Reader_flush_matches__doc__}, @@ -675,10 +985,14 @@ static PyMethodDef Reader_methods[] = { {"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__}, - {NULL} /* Sentinel */ + {"get_catalog", (PyCFunction) Reader_get_catalog, METH_NOARGS, Reader_get_catalog__doc__}, + {} /* Sentinel */ }; static PyTypeObject ReaderType = { @@ -707,11 +1021,11 @@ static PyTypeObject ReaderType = { 0, /* tp_clear */ 0, /* tp_richcompare */ 0, /* tp_weaklistoffset */ - Reader_iter, /* tp_iter */ - Reader_iternext, /* tp_iternext */ + 0, /* tp_iter */ + 0, /* tp_iternext */ Reader_methods, /* tp_methods */ 0, /* tp_members */ - Reader_getseters, /* tp_getset */ + Reader_getsetters, /* tp_getset */ 0, /* tp_base */ 0, /* tp_dict */ 0, /* tp_descr_get */ @@ -722,16 +1036,19 @@ static PyTypeObject ReaderType = { PyType_GenericNew, /* tp_new */ }; -#define SUMMARY \ - "Module that reads the systemd journal similar to journalctl." +static PyMethodDef methods[] = { + { "_get_catalog", get_catalog, METH_VARARGS, get_catalog__doc__}, + { NULL, NULL, 0, NULL } /* Sentinel */ +}; #if PY_MAJOR_VERSION >= 3 -static PyModuleDef _reader_module = { +static PyModuleDef module = { PyModuleDef_HEAD_INIT, "_reader", - SUMMARY, + module__doc__, -1, - NULL, NULL, NULL, NULL, NULL + methods, + NULL, NULL, NULL, NULL }; #endif @@ -761,7 +1078,7 @@ init_reader(void) #endif #if PY_MAJOR_VERSION >= 3 - m = PyModule_Create(&_reader_module); + m = PyModule_Create(&module); if (m == NULL) return NULL; @@ -770,7 +1087,7 @@ init_reader(void) initialized = true; } #else - m = Py_InitModule3("_reader", NULL, SUMMARY); + m = Py_InitModule3("_reader", methods, module__doc__); if (m == NULL) return; #endif