From 927e96326c195ad39a45b091f98e9c635f400982 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 5 Feb 2013 21:44:46 -0500 Subject: [PATCH 01/41] python: add systemd.id128 module uuid.UUIDs are utilized to hold UUID values. --- Makefile.am | 30 ++++++- src/python-systemd/.gitignore | 1 + src/python-systemd/_journal.c | 2 +- src/python-systemd/id128.c | 157 ++++++++++++++++++++++++++++++++++ src/python-systemd/journal.py | 8 +- 5 files changed, 193 insertions(+), 5 deletions(-) create mode 100644 src/python-systemd/.gitignore create mode 100644 src/python-systemd/id128.c diff --git a/Makefile.am b/Makefile.am index 10934eba77..42d35441ea 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3380,7 +3380,8 @@ EXTRA_DIST += \ # ------------------------------------------------------------------------------ if HAVE_PYTHON_DEVEL pkgpyexec_LTLIBRARIES = \ - _journal.la + _journal.la \ + id128.la _journal_la_SOURCES = \ src/python-systemd/_journal.c @@ -3400,9 +3401,36 @@ _journal_la_LIBADD = \ $(PYTHON_LIBS) \ libsystemd-journal.la +id128_la_SOURCES = \ + src/python-systemd/id128.c \ + src/python-systemd/id128-constants.h + +id128_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=default \ + $(PYTHON_CFLAGS) \ + -I$(top_builddir)/src/python-systemd + +id128_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -shared \ + -module \ + -avoid-version + +id128_la_LIBADD = \ + $(PYTHON_LIBS) \ + libsystemd-id128.la + dist_pkgpyexec_PYTHON = \ src/python-systemd/journal.py \ src/python-systemd/__init__.py + +src/python-systemd/id128-constants.h: src/systemd/sd-messages.h Makefile + $(AM_V_at)$(MKDIR_P) $(dir $@) + $(AM_V_GEN)$(SED) -n -r 's/,//g; s/#define (SD_MESSAGE_[A-Z0-9_]+)\s.*/add_id(m, "\1", \1);/p' <$< >$@ + +BUILT_SOURCES += \ + src/python-systemd/id128-constants.h endif # ------------------------------------------------------------------------------ diff --git a/src/python-systemd/.gitignore b/src/python-systemd/.gitignore new file mode 100644 index 0000000000..ed3a500942 --- /dev/null +++ b/src/python-systemd/.gitignore @@ -0,0 +1 @@ +/id128-constants.h diff --git a/src/python-systemd/_journal.c b/src/python-systemd/_journal.c index 0bdf709aea..ced52b2f52 100644 --- a/src/python-systemd/_journal.c +++ b/src/python-systemd/_journal.c @@ -3,7 +3,7 @@ /*** This file is part of systemd. - Copyright 2012 David Strauss + Copyright 2012 David Strauss systemd is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by diff --git a/src/python-systemd/id128.c b/src/python-systemd/id128.c new file mode 100644 index 0000000000..f82b0afb99 --- /dev/null +++ b/src/python-systemd/id128.c @@ -0,0 +1,157 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include + +#include + +#define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp))) + +static void cleanup_Py_DECREFp(PyObject **p) { + if (!*p) + return; + + Py_DECREF(*p); +} + +PyDoc_STRVAR(module__doc__, + "Python interface to the libsystemd-id128 library.\n\n" + "Provides SD_MESSAGE_* constants and functions to query and generate\n" + "128bit unique identifiers." +); + +PyDoc_STRVAR(randomize__doc__, + "randomize() -> UUID\n\n" + "Return a new random 128bit unique identifier.\n" + "Wraps sd_id128_randomize(3)." +); + +PyDoc_STRVAR(get_machine__doc__, + "get_machine() -> UUID\n\n" + "Return a 128bit unique identifier for this machine.\n" + "Wraps sd_id128_get_machine(3)." +); + +PyDoc_STRVAR(get_boot__doc__, + "get_boot() -> UUID\n\n" + "Return a 128bit unique identifier for this boot.\n" + "Wraps sd_id128_get_boot(3)." +); + +static PyObject* make_uuid(sd_id128_t id) { + PyObject _cleanup_Py_DECREF_ + *uuid = NULL, *UUID = NULL, *bytes = NULL, + *args = NULL, *kwargs = NULL, *obj = NULL; + + uuid = PyImport_ImportModule("uuid"); + if (!uuid) + return NULL; + + UUID = PyObject_GetAttrString(uuid, "UUID"); + bytes = PyBytes_FromStringAndSize((const char*) &id.bytes, sizeof(id.bytes)); + args = Py_BuildValue("()"); + kwargs = PyDict_New(); + if (!UUID || !bytes || !args || !kwargs) + return NULL; + + if (PyDict_SetItemString(kwargs, "bytes", bytes) < 0) + return NULL; + + return PyObject_Call(UUID, args, kwargs); +} + +#define helper(name) \ + static PyObject *name(PyObject *self, PyObject *args) { \ + sd_id128_t id; \ + int r; \ + \ + assert(args == NULL); \ + \ + r = sd_id128_##name(&id); \ + if (r < 0) { \ + errno = -r; \ + return PyErr_SetFromErrno(PyExc_IOError); \ + } \ + \ + return make_uuid(id); \ + } + +helper(randomize) +helper(get_machine) +helper(get_boot) + +static PyMethodDef methods[] = { + { "randomize", randomize, METH_NOARGS, randomize__doc__}, + { "get_machine", get_machine, METH_NOARGS, get_machine__doc__}, + { "get_boot", get_boot, METH_NOARGS, get_boot__doc__}, + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +static int add_id(PyObject *module, const char* name, sd_id128_t id) { + PyObject _cleanup_Py_DECREF_ *obj; + + obj = make_uuid(id); + if (!obj) + return -1; + + return PyObject_SetAttrString(module, name, obj); +} + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + +#if PY_MAJOR_VERSION < 3 + +PyMODINIT_FUNC initid128(void) { + PyObject *m; + + m = Py_InitModule3("id128", methods, module__doc__); + if (m == NULL) + return; + +#include "id128-constants.h" +} + +#else + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "id128", /* name of module */ + module__doc__, /* module documentation, may be NULL */ + 0, /* size of per-interpreter state of the module */ + methods +}; + +PyMODINIT_FUNC PyInit_id128(void) { + PyObject *m; + + m = PyModule_Create(&module); + if (m == NULL) + return NULL; + +#include "id128-constants.h" + + return m; +} + +#endif + +#pragma GCC diagnostic pop diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 516ca1ab56..fc57437c28 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -1,8 +1,10 @@ -# -*- Mode: python; indent-tabs-mode: nil -*- */ +# -*- Mode: python; coding:utf-8; indent-tabs-mode: nil -*- */ # # This file is part of systemd. # -# Copyright 2012 David Strauss +# Copyright 2012 David Strauss +# Copyright 2012 Zbigniew Jędrzejewski-Szmek +# Copyright 2012 Marti Raudsepp # # systemd is free software; you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by @@ -120,7 +122,7 @@ class JournalHandler(_logging.Handler): """Journal handler class for the Python logging framework. Please see the Python logging module documentation for an - overview: http://docs.python.org/library/logging.html + overview: http://docs.python.org/library/logging.html. To create a custom logger whose messages go only to journal: From afcd68c1498ba4d449b782f4703490a74770c5f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 9 Feb 2013 12:20:05 -0500 Subject: [PATCH 02/41] python: utilize uuid.UUID in logging --- src/python-systemd/journal.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index fc57437c28..d610b4767b 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -35,24 +35,18 @@ def _make_line(field, value): def send(MESSAGE, MESSAGE_ID=None, CODE_FILE=None, CODE_LINE=None, CODE_FUNC=None, **kwargs): - r"""Send a message to journald. + r"""Send a message to the journal. >>> journal.send('Hello world') >>> journal.send('Hello, again, world', FIELD2='Greetings!') >>> journal.send('Binary message', BINARY=b'\xde\xad\xbe\xef') Value of the MESSAGE argument will be used for the MESSAGE= - field. + field. MESSAGE must be a string and will be sent as UTF-8 to + the journal. MESSAGE_ID can be given to uniquely identify the type of - message. - - Other parts of the message can be specified as keyword - arguments. - - Both MESSAGE and MESSAGE_ID, if present, must be strings, and - will be sent as UTF-8 to journal. Other arguments can be - bytes, in which case they will be sent as-is to journal. + message. It must be a string or a uuid.UUID object. CODE_LINE, CODE_FILE, and CODE_FUNC can be specified to identify the caller. Unless at least on of the three is given, @@ -60,6 +54,11 @@ def send(MESSAGE, MESSAGE_ID=None, send(). CODE_FILE and CODE_FUNC must be strings, CODE_LINE must be an integer. + Additional fields for the journal entry can only be specified + as keyword arguments. The payload can be either a string or + bytes. A string will be sent as UTF-8, and bytes will be sent + as-is to the journal. + Other useful fields include PRIORITY, SYSLOG_FACILITY, SYSLOG_IDENTIFIER, SYSLOG_PID. """ @@ -67,7 +66,8 @@ def send(MESSAGE, MESSAGE_ID=None, args = ['MESSAGE=' + MESSAGE] if MESSAGE_ID is not None: - args.append('MESSAGE_ID=' + MESSAGE_ID) + id = getattr(MESSAGE_ID, 'hex', MESSAGE_ID) + args.append('MESSAGE_ID=' + id) if CODE_LINE == CODE_FILE == CODE_FUNC == None: CODE_FILE, CODE_LINE, CODE_FUNC = \ @@ -138,8 +138,9 @@ class JournalHandler(_logging.Handler): To attach journal MESSAGE_ID, an extra field is supported: - >>> log.warn("Message with ID", - >>> extra={'MESSAGE_ID': '22bb01335f724c959ac4799627d1cb61'}) + >>> import uuid + >>> mid = uuid.UUID('0123456789ABCDEF0123456789ABCDEF') + >>> log.warn("Message with ID", extra={'MESSAGE_ID': mid}) To redirect all logging messages to journal regardless of where they come from, attach it to the root logger: From d489071fb348cd180bc4f70e732b0e76d9804448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 10 Feb 2013 22:47:14 -0500 Subject: [PATCH 03/41] journalct: also print Python code in --new-id --- src/journal/journalctl.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/journal/journalctl.c b/src/journal/journalctl.c index 0afeef932e..0de159b89a 100644 --- a/src/journal/journalctl.c +++ b/src/journal/journalctl.c @@ -462,14 +462,17 @@ static int generate_new_id128(void) { "As UUID:\n" "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n\n" "As macro:\n" - "#define MESSAGE_XYZ SD_ID128_MAKE(", + "#define MESSAGE_XYZ SD_ID128_MAKE(", SD_ID128_FORMAT_VAL(id), SD_ID128_FORMAT_VAL(id)); - for (i = 0; i < 16; i++) printf("%02x%s", id.bytes[i], i != 15 ? "," : ""); + fputs(")\n\n", stdout); - fputs(")\n", stdout); + printf("As Python constant:\n" + ">>> import uuid\n" + ">>> MESSAGE_XYZ = uuid.UUID('" SD_ID128_FORMAT_STR "')\n", + SD_ID128_FORMAT_VAL(id)); return 0; } From 9015fa646e04fc3cb180bea24c33d34edbb48ed7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 9 Feb 2013 15:37:35 -0500 Subject: [PATCH 04/41] python: build html docs using sphinx Build instructions: make make DESTIDIR=/tmp/... install make DESTIDIR=/tmp/... sphinx-html sphinx-man sphinx-epub ... --- Makefile.am | 8 + README | 7 + configure.ac | 14 ++ man/.gitignore | 1 + src/python-systemd/.gitignore | 1 + src/python-systemd/docs/conf.py | 288 ++++++++++++++++++++++++++++ src/python-systemd/docs/id128.rst | 7 + src/python-systemd/docs/index.rst | 22 +++ src/python-systemd/docs/journal.rst | 11 ++ src/python-systemd/journal.py | 30 +-- 10 files changed, 374 insertions(+), 15 deletions(-) create mode 100644 src/python-systemd/docs/conf.py create mode 100644 src/python-systemd/docs/id128.rst create mode 100644 src/python-systemd/docs/index.rst create mode 100644 src/python-systemd/docs/journal.rst diff --git a/Makefile.am b/Makefile.am index 42d35441ea..085f4b36b2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3433,6 +3433,14 @@ BUILT_SOURCES += \ src/python-systemd/id128-constants.h endif +if ENABLE_SPHINX +PAPER = $(shell cat /etc/papersize 2>/dev/null || echo a4) +SPHINXOPTS = -D latex_paper_size=$(PAPER) +sphinx-%: + $(AM_V_GEN)PYTHONPATH=$(DESTDIR)$(pyexecdir) $(SPHINX_BUILD) -b $* $(SPHINXOPTS) $(top_srcdir)/src/python-systemd/docs $(top_builddir)/man/python-systemd/ + $(AM_V_at)echo Output has been generated in $(abs_top_builddir)/man/python-systemd/ +endif + # ------------------------------------------------------------------------------ SED_PROCESS = \ $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ diff --git a/README b/README index 297d8f79e7..1aa044be75 100644 --- a/README +++ b/README @@ -81,6 +81,7 @@ REQUIREMENTS: gperf gtkdocize (optional) python (optional) + sphinx (optional) When systemd-hostnamed is used it is strongly recommended to install nss-myhostname to ensure that in a world of @@ -93,6 +94,12 @@ REQUIREMENTS: please build D-Bus without systemd first, then build systemd, then rebuild D-Bus with systemd support. + To build HTML documentation for python-systemd using sphinx, + please first install systemd (using 'make install'), and then + invoke sphinx-build with 'make sphinx-', with + being 'html' or 'latexpdf'. If using DESTDIR for installation, + pass the same DESTDIR to 'make sphinx-html' invocation. + WARNINGS: systemd will warn you during boot if /etc/mtab is not a symlink to /proc/mounts. Please ensure that /etc/mtab is a diff --git a/configure.ac b/configure.ac index 834b12314c..5737c65257 100644 --- a/configure.ac +++ b/configure.ac @@ -180,6 +180,19 @@ AS_IF([test "x$with_python" != "xno"], [ ]) AM_CONDITIONAL([HAVE_PYTHON_DEVEL], [test "$have_python_devel" = "yes"]) +AC_ARG_ENABLE(sphinx, AS_HELP_STRING([--enable-sphinx], + [use sphinx to build documentation for python-systemd])) +AS_IF([test "x$enable_sphinx" = "xyes"], [ + AC_PATH_PROGS(SPHINX_BUILD, sphinx-build-${PYTHON_VERSION} sphinx-build) + AS_IF([test -z "$SPHINX_BUILD"], [ + AC_MSG_ERROR([*** sphinx build requested, but sphinx-build not found]) + ]) + AS_IF([test "x$have_python_devel" != "xyes"], [ + AC_MSG_ERROR([*** sphinx build requested, but python support not enabled]) + ]) +]) +AM_CONDITIONAL(ENABLE_SPHINX, [test "x$enable_sphinx" = "xyes"]) + # ------------------------------------------------------------------------------ AC_SEARCH_LIBS([mq_open], [rt], [], [AC_MSG_ERROR([*** POSIX RT library not found])]) @@ -902,6 +915,7 @@ AC_MSG_RESULT([ Python Headers: ${have_python_devel} man pages: ${have_manpages} gtk-doc: ${enable_gtk_doc} + sphinx documentation: ${enable_sphinx} Split /usr: ${enable_split_usr} SysV compatibility: ${SYSTEM_SYSV_COMPAT} diff --git a/man/.gitignore b/man/.gitignore index 18767949c1..fcf4dabd2a 100644 --- a/man/.gitignore +++ b/man/.gitignore @@ -2,3 +2,4 @@ /systemd.index.xml /systemd.unit.xml /*.[13578] +/python-systemd/ diff --git a/src/python-systemd/.gitignore b/src/python-systemd/.gitignore index ed3a500942..4124b7affd 100644 --- a/src/python-systemd/.gitignore +++ b/src/python-systemd/.gitignore @@ -1 +1,2 @@ /id128-constants.h +*.py[oc] diff --git a/src/python-systemd/docs/conf.py b/src/python-systemd/docs/conf.py new file mode 100644 index 0000000000..4a55778b7d --- /dev/null +++ b/src/python-systemd/docs/conf.py @@ -0,0 +1,288 @@ +# -*- coding: utf-8 -*- +# +# python-systemd documentation build configuration file, created by +# sphinx-quickstart on Sat Feb 9 13:49:42 2013. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +#sys.path.insert(0, os.path.abspath('.')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.coverage', 'sphinx.ext.viewcode'] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'python-systemd' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +version = '197' +# The full version, including alpha/beta/rc tags. +release = '197' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = [] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +html_show_sourcelink = False + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'python-systemddoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'python-systemd.tex', u'python-systemd Documentation', + None, 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'python-systemd', u'python-systemd Documentation', + [], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'python-systemd', u'python-systemd Documentation', + u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks', 'python-systemd', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'python-systemd' +epub_author = u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' +epub_publisher = u'David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' +epub_copyright = u'2013, David Strauss, Zbigniew Jędrzejewski-Szmek, Marti Raudsepp, Steven Hiscocks' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = {'http://docs.python.org/': None} diff --git a/src/python-systemd/docs/id128.rst b/src/python-systemd/docs/id128.rst new file mode 100644 index 0000000000..e817d803a7 --- /dev/null +++ b/src/python-systemd/docs/id128.rst @@ -0,0 +1,7 @@ +`systemd.id128` module +====================== + +.. automodule:: systemd.id128 + :members: + :undoc-members: + :inherited-members: diff --git a/src/python-systemd/docs/index.rst b/src/python-systemd/docs/index.rst new file mode 100644 index 0000000000..f04d5a181c --- /dev/null +++ b/src/python-systemd/docs/index.rst @@ -0,0 +1,22 @@ +.. python-systemd documentation master file, created by + sphinx-quickstart on Sat Feb 9 13:49:42 2013. + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +Welcome to python-systemd's documentation! +========================================== + +Contents: + +.. toctree:: + :maxdepth: 2 + + journal + id128 + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst new file mode 100644 index 0000000000..036250a764 --- /dev/null +++ b/src/python-systemd/docs/journal.rst @@ -0,0 +1,11 @@ +`systemd.journal` module +======================== + +.. automodule:: systemd.journal + :members: send, sendv, stream, stream_fd + :undoc-members: + +`JournalHandler` class +---------------------- + +.. autoclass:: JournalHandler diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index d610b4767b..47849a360c 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -96,19 +96,20 @@ def stream(identifier, priority=LOG_DEBUG, level_prefix=False): ', mode 'w' at 0x...> >>> stream.write('message...\n') - will produce the following message in the journal: + will produce the following message in the journal:: - PRIORITY=7 - SYSLOG_IDENTIFIER=myapp - MESSAGE=message... + PRIORITY=7 + SYSLOG_IDENTIFIER=myapp + MESSAGE=message... Using the interface with print might be more convinient: >>> from __future__ import print_function >>> print('message...', file=stream) - priority is the syslog priority, one of LOG_EMERG, LOG_ALERT, - LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG. + priority is the syslog priority, one of `LOG_EMERG`, + `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`, + `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`. level_prefix is a boolean. If true, kernel-style log priority level prefixes (such as '<1>') are interpreted. See @@ -131,8 +132,8 @@ class JournalHandler(_logging.Handler): >>> log.addHandler(journal.JournalHandler()) >>> log.warn("Some message: %s", detail) - Note that by default, message levels INFO and DEBUG are ignored - by the logging framework. To enable those log levels: + Note that by default, message levels `INFO` and `DEBUG` are + ignored by the logging framework. To enable those log levels: >>> log.setLevel(logging.DEBUG) @@ -147,16 +148,15 @@ class JournalHandler(_logging.Handler): >>> logging.root.addHandler(journal.JournalHandler()) - For more complex configurations when using dictConfig or - fileConfig, specify 'systemd.journal.JournalHandler' as the + For more complex configurations when using `dictConfig` or + `fileConfig`, specify `systemd.journal.JournalHandler` as the handler class. Only standard handler configuration options - are supported: level, formatter, filters. + are supported: `level`, `formatter`, `filters`. The following journal fields will be sent: - - MESSAGE, PRIORITY, THREAD_NAME, CODE_FILE, CODE_LINE, - CODE_FUNC, LOGGER (name as supplied to getLogger call), - MESSAGE_ID (optional, see above). + `MESSAGE`, `PRIORITY`, `THREAD_NAME`, `CODE_FILE`, `CODE_LINE`, + `CODE_FUNC`, `LOGGER` (name as supplied to getLogger call), + `MESSAGE_ID` (optional, see above). """ def emit(self, record): From c4e9b5b557ba956e316933c31bbefa8b48fa3f93 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 8 Feb 2013 19:41:21 +0000 Subject: [PATCH 05/41] systemd-python: add Journal class for reading journal --- Makefile.am | 22 +- src/python-systemd/_reader.c | 1163 +++++++++++++++++++++++++++++++++ src/python-systemd/journal.py | 2 + 3 files changed, 1186 insertions(+), 1 deletion(-) create mode 100644 src/python-systemd/_reader.c diff --git a/Makefile.am b/Makefile.am index 085f4b36b2..fc7e8c429e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3381,7 +3381,8 @@ EXTRA_DIST += \ if HAVE_PYTHON_DEVEL pkgpyexec_LTLIBRARIES = \ _journal.la \ - id128.la + id128.la \ + _reader.la _journal_la_SOURCES = \ src/python-systemd/_journal.c @@ -3421,6 +3422,25 @@ id128_la_LIBADD = \ $(PYTHON_LIBS) \ libsystemd-id128.la +_reader_la_SOURCES = \ + src/python-systemd/_reader.c + +_reader_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=default \ + $(PYTHON_CFLAGS) + +_reader_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -shared \ + -module \ + -avoid-version + +_reader_la_LIBADD = \ + $(PYTHON_LIBS) \ + libsystemd-journal.la \ + libsystemd-id128.la + dist_pkgpyexec_PYTHON = \ src/python-systemd/journal.py \ src/python-systemd/__init__.py diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c new file mode 100644 index 0000000000..963da11dc6 --- /dev/null +++ b/src/python-systemd/_reader.c @@ -0,0 +1,1163 @@ +/* +_reader - Python module that reads systemd journal similar to journalctl +Copyright (C) 2012 Steven Hiscocks + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ +#include + +#include +#include +#include + +typedef struct { + PyObject_HEAD + sd_journal *j; + PyObject *default_call; + PyObject *call_dict; +} Journal; +static PyTypeObject JournalType; + +static void +Journal_dealloc(Journal* self) +{ + sd_journal_close(self->j); + Py_XDECREF(self->default_call); + Py_XDECREF(self->call_dict); + Py_TYPE(self)->tp_free((PyObject*)self); +} + +static PyObject * +Journal_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + Journal *self; + + self = (Journal *)type->tp_alloc(type, 0); + if (self != NULL) { + PyObject *globals, *temp; + + globals = PyEval_GetBuiltins(); + temp = PyImport_ImportModule("functools"); + PyDict_SetItemString(globals, "functools", temp); + Py_DECREF(temp); + temp = PyImport_ImportModule("datetime"); + PyDict_SetItemString(globals, "datetime", temp); + Py_DECREF(temp); + +#if PY_MAJOR_VERSION >=3 + self->default_call = PyRun_String("functools.partial(str, encoding='utf-8')", Py_eval_input, globals, NULL); +#else + self->default_call = PyRun_String("functools.partial(unicode, encoding='utf-8')", Py_eval_input, globals, NULL); +#endif + + self->call_dict = PyRun_String("{" + "'PRIORITY': int," + "'LEADER': int," + "'SESSION_ID': int," + "'USERSPACE_USEC': int," + "'INITRD_USEC': int," + "'KERNEL_USEC': int," + "'_UID': int," + "'_GID': int," + "'_PID': int," + "'SYSLOG_FACILITY': int," + "'SYSLOG_PID': int," + "'_AUDIT_SESSION': int," + "'_AUDIT_LOGINUID': int," + "'_SYSTEMD_SESSION': int," + "'_SYSTEMD_OWNER_UID': int," + "'CODE_LINE': int," + "'ERRNO': int," + "'EXIT_STATUS': int," + "'_SOURCE_REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6)," + "'__REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6)," + "'_SOURCE_MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x))," + "'__MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x))," +#if PY_MAJOR_VERSION >=3 + "'COREDUMP': bytes," +#else + "'COREDUMP': str," +#endif + "'COREDUMP_PID': int," + "'COREDUMP_UID': int," + "'COREDUMP_GID': int," + "'COREDUMP_SESSION': int," + "'COREDUMP_SIGNAL': int," + "'COREDUMP_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6)," + "}", Py_eval_input, globals, NULL); + } + + return (PyObject *) self; +} + +PyDoc_STRVAR(Journal__doc__, +"Journal([flags][, default_call][, call_dict][,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 `default_call` must be a callable that accepts one\n" +"argument which is string/bytes value of a field and returns\n" +"python object.\n" +"Argument `call_dict` is a dictionary where the key represents\n" +"a field name, and value is a callable as per `default_call`.\n" +"A set of sane defaults for `default_call` and `call_dict` are\n" +"present.\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; + PyObject *default_call=NULL, *call_dict=NULL; + + static char *kwlist[] = {"flags", "default_call", "call_dict", "path", NULL}; + if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iOOs", kwlist, + &flags, &default_call, &call_dict, &path)) + return 1; + + if (default_call) { + if (PyCallable_Check(default_call) || default_call == Py_None) { + Py_DECREF(self->default_call); + self->default_call = default_call; + Py_INCREF(self->default_call); + }else{ + PyErr_SetString(PyExc_TypeError, "Default call not callable"); + return 1; + } + } + + if (call_dict) { + if (PyDict_Check(call_dict)) { + Py_DECREF(self->call_dict); + self->call_dict = call_dict; + Py_INCREF(self->call_dict); + }else if (call_dict == Py_None) { + Py_DECREF(self->call_dict); + self->call_dict = PyDict_New(); + }else{ + PyErr_SetString(PyExc_TypeError, "Call dictionary must be dict type"); + return 1; + } + } + + int r; + if (path) { + r = sd_journal_open_directory(&self->j, path, 0); + }else{ + Py_BEGIN_ALLOW_THREADS + r = sd_journal_open(&self->j, flags); + Py_END_ALLOW_THREADS + } + if (r == -EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid flags or path"); + return -1; + }else if (r == -ENOMEM) { + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + return 1; + }else if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error opening journal"); + return 1; + } + + return 0; +} + +static PyObject * +Journal___process_field(Journal *self, PyObject *key, const void *value, ssize_t value_len) +{ + PyObject *callable=NULL, *return_value=NULL; + if (PyDict_Check(self->call_dict)) + callable = PyDict_GetItem(self->call_dict, key); + + if (PyCallable_Check(callable)) { +#if PY_MAJOR_VERSION >=3 + return_value = PyObject_CallFunction(callable, "y#", value, value_len); +#else + return_value = PyObject_CallFunction(callable, "s#", value, value_len); +#endif + if (!return_value) + PyErr_Clear(); + } + if (!return_value && PyCallable_Check(self->default_call)) +#if PY_MAJOR_VERSION >=3 + return_value = PyObject_CallFunction(self->default_call, "y#", value, value_len); +#else + return_value = PyObject_CallFunction(self->default_call, "s#", value, value_len); +#endif + if (!return_value) { + PyErr_Clear(); +#if PY_MAJOR_VERSION >=3 + return_value = PyBytes_FromStringAndSize(value, value_len); +#else + return_value = PyString_FromStringAndSize(value, value_len); +#endif + } + if (!return_value) { + return_value = Py_None; + } + return return_value; +} + +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) +{ + int64_t skip=1LL; + if (! PyArg_ParseTuple(args, "|L", &skip)) + return NULL; + + int r; + if (skip == 1LL) { + Py_BEGIN_ALLOW_THREADS + r = sd_journal_next(self->j); + Py_END_ALLOW_THREADS + }else if (skip == -1LL) { + Py_BEGIN_ALLOW_THREADS + r = sd_journal_previous(self->j); + Py_END_ALLOW_THREADS + }else if (skip > 1LL) { + Py_BEGIN_ALLOW_THREADS + r = sd_journal_next_skip(self->j, skip); + Py_END_ALLOW_THREADS + }else if (skip < -1LL) { + Py_BEGIN_ALLOW_THREADS + r = sd_journal_previous_skip(self->j, -skip); + Py_END_ALLOW_THREADS + }else{ + PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer"); + return NULL; + } + + if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error getting next message"); + return NULL; + }else if ( r == 0) { //EOF + return PyDict_New(); + } + + PyObject *dict; + dict = PyDict_New(); + + const void *msg; + size_t msg_len; + const char *delim_ptr; + PyObject *key, *value, *cur_value, *tmp_list; + + 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 = Journal___process_field(self, key, delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) ); + if (PyDict_Contains(dict, key)) { + cur_value = PyDict_GetItem(dict, key); + if (PyList_CheckExact(cur_value) && PyList_Size(cur_value) > 1) { + 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); + } + }else{ + PyDict_SetItem(dict, key, value); + } + 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); + +#if PY_MAJOR_VERSION >=3 + key = PyUnicode_FromString("__REALTIME_TIMESTAMP"); +#else + key = PyString_FromString("__REALTIME_TIMESTAMP"); +#endif + value = Journal___process_field(self, key, realtime_str, strlen(realtime_str)); + PyDict_SetItem(dict, key, value); + Py_DECREF(key); + Py_DECREF(value); + } + + 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 = Journal___process_field(self, key, monotonic_str, strlen(monotonic_str)); + + PyDict_SetItem(dict, key, value); + Py_DECREF(key); + Py_DECREF(value); + } + + 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 = Journal___process_field(self, key, cursor, strlen(cursor)); + PyDict_SetItem(dict, key, value); + free(cursor); + Py_DECREF(key); + Py_DECREF(value); + } + + return dict; +} + +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) +{ + int64_t skip=1LL; + if (! PyArg_ParseTuple(args, "|L", &skip)) + return NULL; + + PyObject *dict, *arg; + arg = Py_BuildValue("(L)", -skip); + dict = Journal_get_next(self, arg); + Py_DECREF(arg); + return dict; +} + +PyDoc_STRVAR(Journal_add_match__doc__, +"add_match(match, ..., field=value, ...) -> None\n\n" +"Add a match to filter journal log entries. All matches of different\n" +"field are combined in logical AND, and matches of the same field\n" +"are automatically combined in logical OR.\n" +"Matches can be passed as strings \"field=value\", or keyword\n" +"arguments field=\"value\"."); +static PyObject * +Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) +{ + Py_ssize_t arg_match_len; + char *arg_match; + int i, r; + for (i = 0; i < PySequence_Size(args); i++) { +#if PY_MAJOR_VERSION >=3 + PyObject *arg; + arg = PySequence_Fast_GET_ITEM(args, i); + if (PyUnicode_Check(arg)) { +#if PY_MINOR_VERSION >=3 + arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len); +#else + PyObject *temp; + temp = PyUnicode_AsUTF8String(arg); + PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len); + Py_DECREF(temp); +#endif + }else if (PyBytes_Check(arg)) { + PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len); + }else{ + PyErr_SetString(PyExc_TypeError, "expected bytes or string"); + } +#else + PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len); +#endif + if (PyErr_Occurred()) + return NULL; + r = sd_journal_add_match(self->j, arg_match, arg_match_len); + if (r == -EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid match"); + return NULL; + }else if (r == -ENOMEM) { + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + return NULL; + }else if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error adding match"); + return NULL; + } + } + + if (! keywds) + Py_RETURN_NONE; + + PyObject *key, *value; + Py_ssize_t pos=0, match_key_len, match_value_len; + int match_len; + char *match_key, *match_value; + void *match; + while (PyDict_Next(keywds, &pos, &key, &value)) { +#if PY_MAJOR_VERSION >=3 + if (PyUnicode_Check(key)) { +#if PY_MINOR_VERSION >=3 + match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len); +#else + PyObject *temp2; + temp2 = PyUnicode_AsUTF8String(key); + PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len); + Py_DECREF(temp2); +#endif + }else if (PyBytes_Check(key)) { + PyBytes_AsStringAndSize(key, &match_key, &match_key_len); + }else{ + PyErr_SetString(PyExc_TypeError, "expected bytes or string"); + } + if (PyUnicode_Check(value)) { +#if PY_MINOR_VERSION >=3 + match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len); +#else + PyObject *temp3; + temp3 = PyUnicode_AsUTF8String(value); + PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len); + Py_DECREF(temp3); +#endif + }else if (PyBytes_Check(value)) { + PyBytes_AsStringAndSize(value, &match_value, &match_value_len); + }else{ + PyErr_SetString(PyExc_TypeError, "expected bytes or string"); + } +#else + PyString_AsStringAndSize(key, &match_key, &match_key_len); + PyString_AsStringAndSize(value, &match_value, &match_value_len); +#endif + if (PyErr_Occurred()) + return NULL; + + match_len = match_key_len + 1 + match_value_len; + match = malloc(match_len); + memcpy(match, match_key, match_key_len); + memcpy(match + match_key_len, "=", 1); + memcpy(match + match_key_len + 1, match_value, match_value_len); + + r = sd_journal_add_match(self->j, match, match_len); + free(match); + if (r == -EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid match"); + return NULL; + }else if (r == -ENOMEM) { + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + return NULL; + }else if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error adding match"); + 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) +{ + int r; + r = sd_journal_add_disjunction(self->j); + if (r == -ENOMEM) { + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + return NULL; + }else if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error adding disjunction"); + 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) +{ + sd_journal_flush_matches(self->j); + 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) +{ + int64_t offset; + int whence=SEEK_SET; + static char *kwlist[] = {"offset", "whence", NULL}; + + if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist, + &offset, &whence)) + return NULL; + + PyObject *arg; + if (whence == SEEK_SET){ + int r; + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_head(self->j); + Py_END_ALLOW_THREADS + if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error seeking to head"); + return NULL; + } + if (offset > 0LL) { + arg = Py_BuildValue("(L)", offset); + Py_DECREF(Journal_get_next(self, arg)); + Py_DECREF(arg); + } + }else if (whence == SEEK_CUR){ + arg = Py_BuildValue("(L)", offset); + Py_DECREF(Journal_get_next(self, arg)); + Py_DECREF(arg); + }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) { + PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail"); + return NULL; + } + arg = Py_BuildValue("(L)", -1LL); + Py_DECREF(Journal_get_next(self, arg)); + Py_DECREF(arg); + if (offset < 0LL) { + arg = Py_BuildValue("(L)", offset); + Py_DECREF(Journal_get_next(self, arg)); + Py_DECREF(arg); + } + }else{ + PyErr_SetString(PyExc_ValueError, "Invalid value for whence"); + 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 be an integer unix timestamp in usecs or a " +"datetime instance."); +static PyObject * +Journal_seek_realtime(Journal *self, PyObject *args) +{ + PyObject *arg; + if (! PyArg_ParseTuple(args, "O", &arg)) + return NULL; + + uint64_t timestamp=-1LL; + if (PyDateTime_Check(arg)) { + PyObject *temp; + char *timestamp_str; + temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f"); +#if PY_MAJOR_VERSION >=3 + PyObject *temp2; + temp2 = PyUnicode_AsUTF8String(temp); + timestamp_str = PyBytes_AsString(temp2); + Py_DECREF(temp2); +#else + timestamp_str = PyString_AsString(temp); +#endif + Py_DECREF(temp); + timestamp = strtoull(timestamp_str, NULL, 10); + }else if (PyLong_Check(arg)) { + timestamp = PyLong_AsUnsignedLongLong(arg); +#if PY_MAJOR_VERSION <3 + }else if (PyInt_Check(arg)) { + timestamp = PyInt_AsUnsignedLongLongMask(arg); +#endif + } + if ((int64_t) timestamp < 0LL) { + PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance"); + return NULL; + } + + int r; + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_realtime_usec(self->j, timestamp); + Py_END_ALLOW_THREADS + if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error seek to time"); + 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 secs, or a\n" +"timedelta instance.\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) +{ + PyObject *arg; + char *bootid=NULL; + if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid)) + return NULL; + + uint64_t timestamp=-1LL; + if PyDelta_Check(arg) { + PyObject *temp; + temp = PyObject_CallMethod(arg, "total_seconds", NULL); + timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6); + Py_DECREF(temp); + }else if (PyFloat_Check(arg)) { + timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6); + }else if (PyLong_Check(arg)) { + timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6; +#if PY_MAJOR_VERSION <3 + }else if (PyInt_Check(arg)) { + timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6; +#endif + + } + + if ((int64_t) timestamp < 0LL) { + PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance"); + 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) { + PyErr_SetString(PyExc_RuntimeError, "Error processing 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) { + PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID"); + return NULL; + } + } + + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp); + Py_END_ALLOW_THREADS + if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error seek to time"); + 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) +{ + int64_t timeout=0LL; + if (! PyArg_ParseTuple(args, "|L", &timeout)) + return NULL; + + int r; + if ( timeout == 0LL) { + Py_BEGIN_ALLOW_THREADS + r = sd_journal_wait(self->j, (uint64_t) -1); + Py_END_ALLOW_THREADS + }else{ + Py_BEGIN_ALLOW_THREADS + r = sd_journal_wait(self->j, timeout * 1E6); + Py_END_ALLOW_THREADS + } +#if PY_MAJOR_VERSION >=3 + return PyLong_FromLong(r); +#else + return PyInt_FromLong(r); +#endif +} + +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) +{ + const char *cursor; + if (! PyArg_ParseTuple(args, "s", &cursor)) + return NULL; + + int r; + Py_BEGIN_ALLOW_THREADS + r = sd_journal_seek_cursor(self->j, cursor); + Py_END_ALLOW_THREADS + if (r == -EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid cursor"); + return NULL; + }else if (r == -ENOMEM) { + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + return NULL; + }else if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor"); + return NULL; + } + Py_RETURN_NONE; +} + +static PyObject * +Journal_iter(PyObject *self) +{ + Py_INCREF(self); + return self; +} + +static PyObject * +Journal_iternext(PyObject *self) +{ + Journal *iter = (Journal *)self; + PyObject *dict, *arg; + Py_ssize_t dict_size; + + arg = Py_BuildValue("()"); + dict = Journal_get_next(iter, arg); + Py_DECREF(arg); + dict_size = PyDict_Size(dict); + if ((int64_t) dict_size > 0LL) { + return dict; + }else{ + Py_DECREF(dict); + PyErr_SetNone(PyExc_StopIteration); + return NULL; + } +} + +#ifdef SD_JOURNAL_FOREACH_UNIQUE +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) +{ + char *query; + if (! PyArg_ParseTuple(args, "s", &query)) + return NULL; + + int r; + Py_BEGIN_ALLOW_THREADS + r = sd_journal_query_unique(self->j, query); + Py_END_ALLOW_THREADS + if (r == -EINVAL) { + PyErr_SetString(PyExc_ValueError, "Invalid field name"); + return NULL; + } else if (r == -ENOMEM) { + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + return NULL; + } else if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error querying journal"); + 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 + + SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) { + delim_ptr = memchr(uniq, '=', uniq_len); + value = Journal___process_field(self, key, delim_ptr + 1, (const char*) uniq + uniq_len - (delim_ptr + 1)); + PySet_Add(value_set, value); + Py_DECREF(value); + } + Py_DECREF(key); + return value_set; +} +#endif //def SD_JOURNAL_FOREACH_UNIQUE + +PyDoc_STRVAR(Journal_log_level__doc__, +"log_level(level) -> None\n\n" +"Sets maximum log level by setting matches for PRIORITY."); +static PyObject * +Journal_log_level(Journal *self, PyObject *args) +{ + int level; + if (! PyArg_ParseTuple(args, "i", &level)) + return NULL; + + if (level < 0 || level > 7) { + PyErr_SetString(PyExc_ValueError, "Log level should be 0 <= level <= 7"); + return NULL; + } + int i; + char level_str[2]; + PyObject *arg, *keywds; + for(i = 0; i <= level; i++) { + sprintf(level_str, "%i", i); + arg = PyTuple_New(0); + keywds = Py_BuildValue("{s:s}", "PRIORITY", level_str); + Journal_add_match(self, arg, keywds); + Py_DECREF(arg); + Py_DECREF(keywds); + if (PyErr_Occurred()) + return NULL; + } + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Journal_this_boot__doc__, +"this_boot() -> None\n\n" +"Sets match filter for the current _BOOT_ID."); +static PyObject * +Journal_this_boot(Journal *self, PyObject *args) +{ + sd_id128_t sd_id; + int r; + 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) { + PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID"); + return NULL; + } + + char bootid[33]; + sd_id128_to_string(sd_id, bootid); + + PyObject *arg, *keywds; + arg = PyTuple_New(0); + keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid); + Journal_add_match(self, arg, keywds); + Py_DECREF(arg); + Py_DECREF(keywds); + if (PyErr_Occurred()) + return NULL; + + Py_RETURN_NONE; +} + +PyDoc_STRVAR(Journal_this_machine__doc__, +"this_machine() -> None\n\n" +"Sets match filter for the current _MACHINE_ID."); +static PyObject * +Journal_this_machine(Journal *self, PyObject *args) +{ + sd_id128_t sd_id; + int r; + r = sd_id128_get_machine(&sd_id); + if (r == -EIO) { + PyErr_SetString(PyExc_IOError, "Error getting current boot ID"); + return NULL; + } else if (r < 0) { + PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID"); + return NULL; + } + + char machineid[33]; + sd_id128_to_string(sd_id, machineid); + + PyObject *arg, *keywds; + arg = PyTuple_New(0); + keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid); + Journal_add_match(self, arg, keywds); + Py_DECREF(arg); + Py_DECREF(keywds); + if (PyErr_Occurred()) + return NULL; + + Py_RETURN_NONE; +} + +static PyObject * +Journal_get_default_call(Journal *self, void *closure) +{ + Py_INCREF(self->default_call); + return self->default_call; +} + +static int +Journal_set_default_call(Journal *self, PyObject *value, void *closure) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete default_call"); + return -1; + } + if (! PyCallable_Check(value)) { + PyErr_SetString(PyExc_TypeError, "default_call must be callable"); + return -1; + } + Py_DECREF(self->default_call); + Py_INCREF(value); + self->default_call = value; + + return 0; +} + +static PyObject * +Journal_get_call_dict(Journal *self, void *closure) +{ + Py_INCREF(self->call_dict); + return self->call_dict; +} + +static int +Journal_set_call_dict(Journal *self, PyObject *value, void *closure) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "Cannot delete call_dict"); + return -1; + } + if (! PyDict_Check(value)) { + PyErr_SetString(PyExc_TypeError, "call_dict must be dict type"); + return -1; + } + Py_DECREF(self->call_dict); + Py_INCREF(value); + self->call_dict = value; + + return 0; +} + +static PyObject * +Journal_get_data_threshold(Journal *self, void *closure) +{ + size_t cvalue; + PyObject *value; + int r; + + r = sd_journal_get_data_threshold(self->j, &cvalue); + if (r < 0){ + PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold"); + return NULL; + } + +#if PY_MAJOR_VERSION >=3 + value = PyLong_FromSize_t(cvalue); +#else + value = PyInt_FromSize_t(cvalue); +#endif + return value; +} + +static int +Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) +{ + if (value == NULL) { + PyErr_SetString(PyExc_TypeError, "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){ + PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold"); + return -1; + } + return 0; +} + +static PyGetSetDef Journal_getseters[] = { + {"data_threshold", + (getter)Journal_get_data_threshold, + (setter)Journal_set_data_threshold, + "data threshold", + NULL}, + {"call_dict", + (getter)Journal_get_call_dict, + (setter)Journal_set_call_dict, + "dictionary of calls for each field", + NULL}, + {"default_call", + (getter)Journal_get_default_call, + (setter)Journal_set_default_call, + "default call for values for fields", + NULL}, + {NULL} +}; + +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__}, +#ifdef SD_JOURNAL_FOREACH_UNIQUE + {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS, + Journal_query_unique__doc__}, +#endif + {"log_level", (PyCFunction)Journal_log_level, METH_VARARGS, + Journal_log_level__doc__}, + {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS, + Journal_this_boot__doc__}, + {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS, + Journal_this_machine__doc__}, + {NULL} /* Sentinel */ +}; + +static PyTypeObject JournalType = { + 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 */ + Journal_new, /* tp_new */ +}; + +#if PY_MAJOR_VERSION >= 3 +static PyModuleDef _reader_module = { + PyModuleDef_HEAD_INIT, + "_reader", + "Module that reads systemd journal similar to journalctl.", + -1, + NULL, NULL, NULL, NULL, NULL +}; +#endif + +PyMODINIT_FUNC +#if PY_MAJOR_VERSION >= 3 +PyInit__reader(void) +#else +init_reader(void) +#endif +{ + PyObject* m; + + PyDateTime_IMPORT; + + if (PyType_Ready(&JournalType) < 0) +#if PY_MAJOR_VERSION >= 3 + return NULL; +#else + return; +#endif + +#if PY_MAJOR_VERSION >= 3 + m = PyModule_Create(&_reader_module); + if (m == NULL) + return NULL; +#else + m = Py_InitModule3("_reader", NULL, + "Module that reads systemd journal similar to journalctl."); + 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); + +#if PY_MAJOR_VERSION >= 3 + return m; +#endif +} diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 47849a360c..a1543b884e 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -25,6 +25,8 @@ import logging as _logging from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) from ._journal import sendv, stream_fd +from ._reader import (Journal, NOP, APPEND, INVALIDATE, + LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) def _make_line(field, value): if isinstance(value, bytes): From 2d0603bd976978a62165c79129fc3a1f8d36b9e6 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 8 Feb 2013 22:04:42 +0000 Subject: [PATCH 06/41] systemd-python: moved PyRun_String to journal.py code --- src/python-systemd/_reader.c | 55 ++--------------------------------- src/python-systemd/journal.py | 48 +++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 53 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 963da11dc6..69c6d02a9e 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -46,57 +46,8 @@ Journal_new(PyTypeObject *type, PyObject *args, PyObject *kwds) self = (Journal *)type->tp_alloc(type, 0); if (self != NULL) { - PyObject *globals, *temp; - - globals = PyEval_GetBuiltins(); - temp = PyImport_ImportModule("functools"); - PyDict_SetItemString(globals, "functools", temp); - Py_DECREF(temp); - temp = PyImport_ImportModule("datetime"); - PyDict_SetItemString(globals, "datetime", temp); - Py_DECREF(temp); - -#if PY_MAJOR_VERSION >=3 - self->default_call = PyRun_String("functools.partial(str, encoding='utf-8')", Py_eval_input, globals, NULL); -#else - self->default_call = PyRun_String("functools.partial(unicode, encoding='utf-8')", Py_eval_input, globals, NULL); -#endif - - self->call_dict = PyRun_String("{" - "'PRIORITY': int," - "'LEADER': int," - "'SESSION_ID': int," - "'USERSPACE_USEC': int," - "'INITRD_USEC': int," - "'KERNEL_USEC': int," - "'_UID': int," - "'_GID': int," - "'_PID': int," - "'SYSLOG_FACILITY': int," - "'SYSLOG_PID': int," - "'_AUDIT_SESSION': int," - "'_AUDIT_LOGINUID': int," - "'_SYSTEMD_SESSION': int," - "'_SYSTEMD_OWNER_UID': int," - "'CODE_LINE': int," - "'ERRNO': int," - "'EXIT_STATUS': int," - "'_SOURCE_REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6)," - "'__REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6)," - "'_SOURCE_MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x))," - "'__MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x))," -#if PY_MAJOR_VERSION >=3 - "'COREDUMP': bytes," -#else - "'COREDUMP': str," -#endif - "'COREDUMP_PID': int," - "'COREDUMP_UID': int," - "'COREDUMP_GID': int," - "'COREDUMP_SESSION': int," - "'COREDUMP_SIGNAL': int," - "'COREDUMP_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6)," - "}", Py_eval_input, globals, NULL); + self->call_dict = PyDict_New(); + self->default_call = Py_None; } return (PyObject *) self; @@ -1149,7 +1100,7 @@ init_reader(void) #endif Py_INCREF(&JournalType); - PyModule_AddObject(m, "Journal", (PyObject *)&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); diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index a1543b884e..1f0aafc7fe 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -19,15 +19,61 @@ # You should have received a copy of the GNU Lesser General Public License # along with systemd; If not, see . +import datetime +import functools +import sys import traceback as _traceback import os as _os import logging as _logging from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) from ._journal import sendv, stream_fd -from ._reader import (Journal, NOP, APPEND, INVALIDATE, +from ._reader import (_Journal, NOP, APPEND, INVALIDATE, LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) +class Journal(_Journal): + def __new__(cls, *args, **kwargs): + self = _Journal.__new__(cls, *args, **kwargs) + if sys.version_info[0] >= 3: + self.default_call = functools.partial(str, encoding='utf-8') + else: + self.default_call = functools.partial(unicode, encoding='utf-8') + self.call_dict = { + 'PRIORITY': int, + 'LEADER': int, + 'SESSION_ID': int, + 'USERSPACE_USEC': int, + 'INITRD_USEC': int, + 'KERNEL_USEC': int, + '_UID': int, + '_GID': int, + '_PID': int, + 'SYSLOG_FACILITY': int, + 'SYSLOG_PID': int, + '_AUDIT_SESSION': int, + '_AUDIT_LOGINUID': int, + '_SYSTEMD_SESSION': int, + '_SYSTEMD_OWNER_UID': int, + 'CODE_LINE': int, + 'ERRNO': int, + 'EXIT_STATUS': int, + '_SOURCE_REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6), + '__REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6), + '_SOURCE_MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)), + '__MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)), + 'COREDUMP_PID': int, + 'COREDUMP_UID': int, + 'COREDUMP_GID': int, + 'COREDUMP_SESSION': int, + 'COREDUMP_SIGNAL': int, + 'COREDUMP_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6), + } + if sys.version_info[0] >= 3: + self.call_dict['COREDUMP'] = bytes + else: + self.call_dict['COREDUMP'] = str + return self + def _make_line(field, value): if isinstance(value, bytes): return field.encode('utf-8') + b'=' + value From 3ce2cc25bb8e623788fdfaf1ab5a804122a93473 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 8 Feb 2013 22:14:18 +0000 Subject: [PATCH 07/41] systemd-python: MESSAGE_ID as UUID for Journal --- src/python-systemd/journal.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 1f0aafc7fe..fafddaa27e 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -22,6 +22,7 @@ import datetime import functools import sys +import uuid import traceback as _traceback import os as _os import logging as _logging @@ -39,6 +40,7 @@ class Journal(_Journal): else: self.default_call = functools.partial(unicode, encoding='utf-8') self.call_dict = { + 'MESSAGE_ID': uuid.UUID, 'PRIORITY': int, 'LEADER': int, 'SESSION_ID': int, From 3aa8f0861ca00572879bd32e4f352afd578e674c Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 15 Feb 2013 16:59:50 +0000 Subject: [PATCH 08/41] systemd-python: move default call dicts from C to python --- src/python-systemd/_reader.c | 196 +++------------------------------- src/python-systemd/journal.py | 121 ++++++++++++++------- 2 files changed, 97 insertions(+), 220 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 69c6d02a9e..f047ab9e6d 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -25,8 +25,6 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA typedef struct { PyObject_HEAD sd_journal *j; - PyObject *default_call; - PyObject *call_dict; } Journal; static PyTypeObject JournalType; @@ -34,27 +32,11 @@ static void Journal_dealloc(Journal* self) { sd_journal_close(self->j); - Py_XDECREF(self->default_call); - Py_XDECREF(self->call_dict); Py_TYPE(self)->tp_free((PyObject*)self); } -static PyObject * -Journal_new(PyTypeObject *type, PyObject *args, PyObject *kwds) -{ - Journal *self; - - self = (Journal *)type->tp_alloc(type, 0); - if (self != NULL) { - self->call_dict = PyDict_New(); - self->default_call = Py_None; - } - - return (PyObject *) self; -} - PyDoc_STRVAR(Journal__doc__, -"Journal([flags][, default_call][, call_dict][,path]) -> ...\n" +"Journal([flags][,path]) -> ...\n" "Journal instance\n\n" "Returns instance of Journal, which allows filtering and return\n" "of journal entries.\n" @@ -63,13 +45,6 @@ PyDoc_STRVAR(Journal__doc__, "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 `default_call` must be a callable that accepts one\n" -"argument which is string/bytes value of a field and returns\n" -"python object.\n" -"Argument `call_dict` is a dictionary where the key represents\n" -"a field name, and value is a callable as per `default_call`.\n" -"A set of sane defaults for `default_call` and `call_dict` are\n" -"present.\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."); @@ -78,38 +53,12 @@ Journal_init(Journal *self, PyObject *args, PyObject *keywds) { int flags=SD_JOURNAL_LOCAL_ONLY; char *path=NULL; - PyObject *default_call=NULL, *call_dict=NULL; - static char *kwlist[] = {"flags", "default_call", "call_dict", "path", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iOOs", kwlist, - &flags, &default_call, &call_dict, &path)) + static char *kwlist[] = {"flags", NULL}; + if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist, + &flags, &path)) return 1; - if (default_call) { - if (PyCallable_Check(default_call) || default_call == Py_None) { - Py_DECREF(self->default_call); - self->default_call = default_call; - Py_INCREF(self->default_call); - }else{ - PyErr_SetString(PyExc_TypeError, "Default call not callable"); - return 1; - } - } - - if (call_dict) { - if (PyDict_Check(call_dict)) { - Py_DECREF(self->call_dict); - self->call_dict = call_dict; - Py_INCREF(self->call_dict); - }else if (call_dict == Py_None) { - Py_DECREF(self->call_dict); - self->call_dict = PyDict_New(); - }else{ - PyErr_SetString(PyExc_TypeError, "Call dictionary must be dict type"); - return 1; - } - } - int r; if (path) { r = sd_journal_open_directory(&self->j, path, 0); @@ -132,42 +81,6 @@ Journal_init(Journal *self, PyObject *args, PyObject *keywds) return 0; } -static PyObject * -Journal___process_field(Journal *self, PyObject *key, const void *value, ssize_t value_len) -{ - PyObject *callable=NULL, *return_value=NULL; - if (PyDict_Check(self->call_dict)) - callable = PyDict_GetItem(self->call_dict, key); - - if (PyCallable_Check(callable)) { -#if PY_MAJOR_VERSION >=3 - return_value = PyObject_CallFunction(callable, "y#", value, value_len); -#else - return_value = PyObject_CallFunction(callable, "s#", value, value_len); -#endif - if (!return_value) - PyErr_Clear(); - } - if (!return_value && PyCallable_Check(self->default_call)) -#if PY_MAJOR_VERSION >=3 - return_value = PyObject_CallFunction(self->default_call, "y#", value, value_len); -#else - return_value = PyObject_CallFunction(self->default_call, "s#", value, value_len); -#endif - if (!return_value) { - PyErr_Clear(); -#if PY_MAJOR_VERSION >=3 - return_value = PyBytes_FromStringAndSize(value, value_len); -#else - return_value = PyString_FromStringAndSize(value, value_len); -#endif - } - if (!return_value) { - return_value = Py_None; - } - return return_value; -} - PyDoc_STRVAR(Journal_get_next__doc__, "get_next([skip]) -> dict\n\n" "Return dictionary of the next log entry. Optional skip value will\n" @@ -223,10 +136,10 @@ Journal_get_next(Journal *self, PyObject *args) #else key = PyString_FromStringAndSize(msg, delim_ptr - (const char*) msg); #endif - value = Journal___process_field(self, key, delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) ); + value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) ); if (PyDict_Contains(dict, key)) { cur_value = PyDict_GetItem(dict, key); - if (PyList_CheckExact(cur_value) && PyList_Size(cur_value) > 1) { + if (PyList_CheckExact(cur_value)) { PyList_Append(cur_value, value); }else{ tmp_list = PyList_New(0); @@ -252,7 +165,7 @@ Journal_get_next(Journal *self, PyObject *args) #else key = PyString_FromString("__REALTIME_TIMESTAMP"); #endif - value = Journal___process_field(self, key, realtime_str, strlen(realtime_str)); + value = PyBytes_FromString(realtime_str); PyDict_SetItem(dict, key, value); Py_DECREF(key); Py_DECREF(value); @@ -268,7 +181,7 @@ Journal_get_next(Journal *self, PyObject *args) #else key = PyString_FromString("__MONOTONIC_TIMESTAMP"); #endif - value = Journal___process_field(self, key, monotonic_str, strlen(monotonic_str)); + value = PyBytes_FromString(monotonic_str); PyDict_SetItem(dict, key, value); Py_DECREF(key); @@ -282,7 +195,7 @@ Journal_get_next(Journal *self, PyObject *args) #else key = PyString_FromString("__CURSOR"); #endif - value = Journal___process_field(self, key, cursor, strlen(cursor)); + value = PyBytes_FromString(cursor); PyDict_SetItem(dict, key, value); free(cursor); Py_DECREF(key); @@ -303,11 +216,7 @@ Journal_get_previous(Journal *self, PyObject *args) if (! PyArg_ParseTuple(args, "|L", &skip)) return NULL; - PyObject *dict, *arg; - arg = Py_BuildValue("(L)", -skip); - dict = Journal_get_next(self, arg); - Py_DECREF(arg); - return dict; + return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip); } PyDoc_STRVAR(Journal_add_match__doc__, @@ -485,14 +394,10 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) return NULL; } if (offset > 0LL) { - arg = Py_BuildValue("(L)", offset); - Py_DECREF(Journal_get_next(self, arg)); - Py_DECREF(arg); + Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset)); } }else if (whence == SEEK_CUR){ - arg = Py_BuildValue("(L)", offset); - Py_DECREF(Journal_get_next(self, arg)); - Py_DECREF(arg); + Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset)); }else if (whence == SEEK_END){ int r; Py_BEGIN_ALLOW_THREADS @@ -502,13 +407,9 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail"); return NULL; } - arg = Py_BuildValue("(L)", -1LL); - Py_DECREF(Journal_get_next(self, arg)); - Py_DECREF(arg); + Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL)); if (offset < 0LL) { - arg = Py_BuildValue("(L)", offset); - Py_DECREF(Journal_get_next(self, arg)); - Py_DECREF(arg); + Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset)); } }else{ PyErr_SetString(PyExc_ValueError, "Invalid value for whence"); @@ -706,13 +607,10 @@ Journal_iter(PyObject *self) static PyObject * Journal_iternext(PyObject *self) { - Journal *iter = (Journal *)self; PyObject *dict, *arg; Py_ssize_t dict_size; - arg = Py_BuildValue("()"); - dict = Journal_get_next(iter, arg); - Py_DECREF(arg); + dict = PyObject_CallMethod(self, "get_next", ""); dict_size = PyDict_Size(dict); if ((int64_t) dict_size > 0LL) { return dict; @@ -764,7 +662,7 @@ Journal_query_unique(Journal *self, PyObject *args) SD_JOURNAL_FOREACH_UNIQUE(self->j, uniq, uniq_len) { delim_ptr = memchr(uniq, '=', uniq_len); - value = Journal___process_field(self, key, 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); } @@ -867,56 +765,6 @@ Journal_this_machine(Journal *self, PyObject *args) Py_RETURN_NONE; } -static PyObject * -Journal_get_default_call(Journal *self, void *closure) -{ - Py_INCREF(self->default_call); - return self->default_call; -} - -static int -Journal_set_default_call(Journal *self, PyObject *value, void *closure) -{ - if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete default_call"); - return -1; - } - if (! PyCallable_Check(value)) { - PyErr_SetString(PyExc_TypeError, "default_call must be callable"); - return -1; - } - Py_DECREF(self->default_call); - Py_INCREF(value); - self->default_call = value; - - return 0; -} - -static PyObject * -Journal_get_call_dict(Journal *self, void *closure) -{ - Py_INCREF(self->call_dict); - return self->call_dict; -} - -static int -Journal_set_call_dict(Journal *self, PyObject *value, void *closure) -{ - if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete call_dict"); - return -1; - } - if (! PyDict_Check(value)) { - PyErr_SetString(PyExc_TypeError, "call_dict must be dict type"); - return -1; - } - Py_DECREF(self->call_dict); - Py_INCREF(value); - self->call_dict = value; - - return 0; -} - static PyObject * Journal_get_data_threshold(Journal *self, void *closure) { @@ -972,16 +820,6 @@ static PyGetSetDef Journal_getseters[] = { (setter)Journal_set_data_threshold, "data threshold", NULL}, - {"call_dict", - (getter)Journal_get_call_dict, - (setter)Journal_set_call_dict, - "dictionary of calls for each field", - NULL}, - {"default_call", - (getter)Journal_get_default_call, - (setter)Journal_set_default_call, - "default call for values for fields", - NULL}, {NULL} }; @@ -1057,7 +895,7 @@ static PyTypeObject JournalType = { 0, /* tp_dictoffset */ (initproc)Journal_init, /* tp_init */ 0, /* tp_alloc */ - Journal_new, /* tp_new */ + PyType_GenericNew, /* tp_new */ }; #if PY_MAJOR_VERSION >= 3 diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index fafddaa27e..8a688f978b 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -32,49 +32,88 @@ from ._journal import sendv, stream_fd from ._reader import (_Journal, NOP, APPEND, INVALIDATE, LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) +_MONOTONIC_CONVERTER = lambda x: datetime.timedelta(microseconds=float(x)) +_REALTIME_CONVERTER = lambda x: datetime.datetime.fromtimestamp(float(x)/1E6) +DEFAULT_CONVERTERS = { + 'MESSAGE_ID': uuid.UUID, + 'PRIORITY': int, + 'LEADER': int, + 'SESSION_ID': int, + 'USERSPACE_USEC': int, + 'INITRD_USEC': int, + 'KERNEL_USEC': int, + '_UID': int, + '_GID': int, + '_PID': int, + 'SYSLOG_FACILITY': int, + 'SYSLOG_PID': int, + '_AUDIT_SESSION': int, + '_AUDIT_LOGINUID': int, + '_SYSTEMD_SESSION': int, + '_SYSTEMD_OWNER_UID': int, + 'CODE_LINE': int, + 'ERRNO': int, + 'EXIT_STATUS': int, + '_SOURCE_REALTIME_TIMESTAMP': _REALTIME_CONVERTER, + '__REALTIME_TIMESTAMP': _REALTIME_CONVERTER, + '_SOURCE_MONOTONIC_TIMESTAMP': _MONOTONIC_CONVERTER, + '__MONOTONIC_TIMESTAMP': _MONOTONIC_CONVERTER, + 'COREDUMP': bytes, + 'COREDUMP_PID': int, + 'COREDUMP_UID': int, + 'COREDUMP_GID': int, + 'COREDUMP_SESSION': int, + 'COREDUMP_SIGNAL': int, + 'COREDUMP_TIMESTAMP': _REALTIME_CONVERTER, +} + +if sys.version_info >= (3,): + _convert_unicode = functools.partial(str, encoding='utf-8') +else: + _convert_unicode = functools.partial(unicode, encoding='utf-8') + class Journal(_Journal): - def __new__(cls, *args, **kwargs): - self = _Journal.__new__(cls, *args, **kwargs) - if sys.version_info[0] >= 3: - self.default_call = functools.partial(str, encoding='utf-8') + def __init__(self, converters=None, *args, **kwargs): + super(Journal, self).__init__(*args, **kwargs) + if sys.version_info >= (3,3): + self.converters = ChainMap() + if converters is not None: + self.converters.maps.append(converters) + self.converters.maps.append(DEFAULT_CONVERTERS) else: - self.default_call = functools.partial(unicode, encoding='utf-8') - self.call_dict = { - 'MESSAGE_ID': uuid.UUID, - 'PRIORITY': int, - 'LEADER': int, - 'SESSION_ID': int, - 'USERSPACE_USEC': int, - 'INITRD_USEC': int, - 'KERNEL_USEC': int, - '_UID': int, - '_GID': int, - '_PID': int, - 'SYSLOG_FACILITY': int, - 'SYSLOG_PID': int, - '_AUDIT_SESSION': int, - '_AUDIT_LOGINUID': int, - '_SYSTEMD_SESSION': int, - '_SYSTEMD_OWNER_UID': int, - 'CODE_LINE': int, - 'ERRNO': int, - 'EXIT_STATUS': int, - '_SOURCE_REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6), - '__REALTIME_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6), - '_SOURCE_MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)), - '__MONOTONIC_TIMESTAMP': lambda x: datetime.timedelta(microseconds=float(x)), - 'COREDUMP_PID': int, - 'COREDUMP_UID': int, - 'COREDUMP_GID': int, - 'COREDUMP_SESSION': int, - 'COREDUMP_SIGNAL': int, - 'COREDUMP_TIMESTAMP': lambda x: datetime.datetime.fromtimestamp(float(x)/1E6), - } - if sys.version_info[0] >= 3: - self.call_dict['COREDUMP'] = bytes - else: - self.call_dict['COREDUMP'] = str - return self + # suitable fallback, e.g. + self.converters = DEFAULT_CONVERTERS.copy() + if converters is not None: + self.converters.update(converters) + + def _convert_field(self, key, value): + try: + result = self.converters[key](value) + except KeyError: + # Default conversion in unicode + try: + result = _convert_unicode(value) + except: + # Leave in default bytes + result = value + return result + + def _convert_entry(self, entry): + result = {} + for key, value in entry.iteritems(): + if isinstance(value, list): + result[key] = [self._convert_field(key, val) for val in value] + else: + result[key] = self._convert_field(key, value) + return result + + def get_next(self, *args, **kwargs): + return self._convert_entry( + super(Journal, self).get_next(*args, **kwargs)) + + def query_unique(self, key, *args, **kwargs): + return set(self._convert_field(key, value) + for value in super(Journal, self).query_unique(key, *args, **kwargs)) def _make_line(field, value): if isinstance(value, bytes): From c1db45c6a5b0bd2ad7ddbfd877162794ac3a48f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 16 Feb 2013 20:28:34 -0500 Subject: [PATCH 09/41] sphinx: document Journal class too --- src/python-systemd/docs/journal.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst index 036250a764..89728a2ecb 100644 --- a/src/python-systemd/docs/journal.rst +++ b/src/python-systemd/docs/journal.rst @@ -9,3 +9,16 @@ ---------------------- .. autoclass:: JournalHandler + +Accessing the Journal +--------------------- + +.. autoclass:: _Journal + :undoc-members: + :inherited-members: + +.. autoclass:: Journal + :undoc-members: + :inherited-members: + + .. automethod:: __init__ From 25523db4e1a3da09916ed69c1e90a52d027ac765 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 15 Feb 2013 17:09:47 +0000 Subject: [PATCH 10/41] systemd-python: Journal log_level moved to python --- src/python-systemd/_reader.c | 32 -------------------------------- src/python-systemd/journal.py | 8 ++++++++ 2 files changed, 8 insertions(+), 32 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index f047ab9e6d..ce6631782f 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -671,36 +671,6 @@ Journal_query_unique(Journal *self, PyObject *args) } #endif //def SD_JOURNAL_FOREACH_UNIQUE -PyDoc_STRVAR(Journal_log_level__doc__, -"log_level(level) -> None\n\n" -"Sets maximum log level by setting matches for PRIORITY."); -static PyObject * -Journal_log_level(Journal *self, PyObject *args) -{ - int level; - if (! PyArg_ParseTuple(args, "i", &level)) - return NULL; - - if (level < 0 || level > 7) { - PyErr_SetString(PyExc_ValueError, "Log level should be 0 <= level <= 7"); - return NULL; - } - int i; - char level_str[2]; - PyObject *arg, *keywds; - for(i = 0; i <= level; i++) { - sprintf(level_str, "%i", i); - arg = PyTuple_New(0); - keywds = Py_BuildValue("{s:s}", "PRIORITY", level_str); - Journal_add_match(self, arg, keywds); - Py_DECREF(arg); - Py_DECREF(keywds); - if (PyErr_Occurred()) - return NULL; - } - Py_RETURN_NONE; -} - PyDoc_STRVAR(Journal_this_boot__doc__, "this_boot() -> None\n\n" "Sets match filter for the current _BOOT_ID."); @@ -848,8 +818,6 @@ static PyMethodDef Journal_methods[] = { {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS, Journal_query_unique__doc__}, #endif - {"log_level", (PyCFunction)Journal_log_level, METH_VARARGS, - Journal_log_level__doc__}, {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS, Journal_this_boot__doc__}, {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS, diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 8a688f978b..40e40c360b 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -115,6 +115,14 @@ class Journal(_Journal): return set(self._convert_field(key, value) for value in super(Journal, self).query_unique(key, *args, **kwargs)) + def log_level(self, level): + """Sets maximum log level by setting matches for PRIORITY.""" + if 0 <= level <= 7: + for i in range(level+1): + self.add_match(PRIORITY="%s" % i) + else: + raise ValueError("Log level must be 0 <= level <= 7") + def _make_line(field, value): if isinstance(value, bytes): return field.encode('utf-8') + b'=' + value From 6a71de70e5f5a244b656d9ccb373cc7241033e15 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 15 Feb 2013 17:16:56 +0000 Subject: [PATCH 11/41] systemd-python: implement this_boot/this_machine in Python --- src/python-systemd/_reader.c | 68 ----------------------------------- src/python-systemd/journal.py | 9 +++++ 2 files changed, 9 insertions(+), 68 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index ce6631782f..06bdf1694e 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -671,70 +671,6 @@ Journal_query_unique(Journal *self, PyObject *args) } #endif //def SD_JOURNAL_FOREACH_UNIQUE -PyDoc_STRVAR(Journal_this_boot__doc__, -"this_boot() -> None\n\n" -"Sets match filter for the current _BOOT_ID."); -static PyObject * -Journal_this_boot(Journal *self, PyObject *args) -{ - sd_id128_t sd_id; - int r; - 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) { - PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID"); - return NULL; - } - - char bootid[33]; - sd_id128_to_string(sd_id, bootid); - - PyObject *arg, *keywds; - arg = PyTuple_New(0); - keywds = Py_BuildValue("{s:s}", "_BOOT_ID", bootid); - Journal_add_match(self, arg, keywds); - Py_DECREF(arg); - Py_DECREF(keywds); - if (PyErr_Occurred()) - return NULL; - - Py_RETURN_NONE; -} - -PyDoc_STRVAR(Journal_this_machine__doc__, -"this_machine() -> None\n\n" -"Sets match filter for the current _MACHINE_ID."); -static PyObject * -Journal_this_machine(Journal *self, PyObject *args) -{ - sd_id128_t sd_id; - int r; - r = sd_id128_get_machine(&sd_id); - if (r == -EIO) { - PyErr_SetString(PyExc_IOError, "Error getting current boot ID"); - return NULL; - } else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID"); - return NULL; - } - - char machineid[33]; - sd_id128_to_string(sd_id, machineid); - - PyObject *arg, *keywds; - arg = PyTuple_New(0); - keywds = Py_BuildValue("{s:s}", "_MACHINE_ID", machineid); - Journal_add_match(self, arg, keywds); - Py_DECREF(arg); - Py_DECREF(keywds); - if (PyErr_Occurred()) - return NULL; - - Py_RETURN_NONE; -} - static PyObject * Journal_get_data_threshold(Journal *self, void *closure) { @@ -818,10 +754,6 @@ static PyMethodDef Journal_methods[] = { {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS, Journal_query_unique__doc__}, #endif - {"this_boot", (PyCFunction)Journal_this_boot, METH_NOARGS, - Journal_this_boot__doc__}, - {"this_machine", (PyCFunction)Journal_this_machine, METH_NOARGS, - Journal_this_machine__doc__}, {NULL} /* Sentinel */ }; diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 40e40c360b..533a8754a4 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -31,6 +31,7 @@ from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, from ._journal import sendv, stream_fd from ._reader import (_Journal, NOP, APPEND, INVALIDATE, LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) +from . import id128 as _id128 _MONOTONIC_CONVERTER = lambda x: datetime.timedelta(microseconds=float(x)) _REALTIME_CONVERTER = lambda x: datetime.datetime.fromtimestamp(float(x)/1E6) @@ -123,6 +124,14 @@ class Journal(_Journal): else: raise ValueError("Log level must be 0 <= level <= 7") + def this_boot(self): + """Add match for _BOOT_ID equal to current boot ID.""" + self.add_match(_BOOT_ID=_id128.get_boot().hex) + + def this_machine(self): + """Add match for _MACHINE_ID equal to the ID of this machine.""" + self.add_match(_MACHINE_ID=_id128.get_machine().hex) + def _make_line(field, value): if isinstance(value, bytes): return field.encode('utf-8') + b'=' + value From bf1ced5503bf02fa8c18f0089f4cf74a5456c3c9 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 13:40:44 +0000 Subject: [PATCH 12/41] systemd-python: some python3 and bug fixes --- src/python-systemd/_reader.c | 19 ++++++++++++------- src/python-systemd/journal.py | 11 +++++++++-- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 06bdf1694e..ae3d77cfe9 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -383,7 +383,7 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) &offset, &whence)) return NULL; - PyObject *arg; + PyObject *result=NULL; if (whence == SEEK_SET){ int r; Py_BEGIN_ALLOW_THREADS @@ -394,10 +394,10 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) return NULL; } if (offset > 0LL) { - Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset)); + result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); } }else if (whence == SEEK_CUR){ - Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset)); + result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); }else if (whence == SEEK_END){ int r; Py_BEGIN_ALLOW_THREADS @@ -407,14 +407,19 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail"); return NULL; } - Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL)); if (offset < 0LL) { - Py_DECREF(PyObject_CallMethod((PyObject *)self, "get_next", "L", offset)); + 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"); - return NULL; } + + if (result) + Py_DECREF(result); + if (PyErr_Occurred()) + return NULL; Py_RETURN_NONE; } @@ -607,7 +612,7 @@ Journal_iter(PyObject *self) static PyObject * Journal_iternext(PyObject *self) { - PyObject *dict, *arg; + PyObject *dict; Py_ssize_t dict_size; dict = PyObject_CallMethod(self, "get_next", ""); diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 533a8754a4..6e82a460b5 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -26,6 +26,8 @@ import uuid import traceback as _traceback import os as _os import logging as _logging +if sys.version_info >= (3,): + from collections import ChainMap from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) from ._journal import sendv, stream_fd @@ -90,7 +92,7 @@ class Journal(_Journal): def _convert_field(self, key, value): try: result = self.converters[key](value) - except KeyError: + except: # Default conversion in unicode try: result = _convert_unicode(value) @@ -101,13 +103,18 @@ class Journal(_Journal): def _convert_entry(self, entry): result = {} - for key, value in entry.iteritems(): + for key, value in entry.items(): if isinstance(value, list): result[key] = [self._convert_field(key, val) for val in value] else: result[key] = self._convert_field(key, value) return result + def add_match(self, *args, **kwargs): + args = list(args) + args.extend(_make_line(key, val) for key, val in kwargs.items()) + super(Journal, self).add_match(*args) + def get_next(self, *args, **kwargs): return self._convert_entry( super(Journal, self).get_next(*args, **kwargs)) From 71766afa2d80ffe1b03c410f6d5ffdc140883314 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 17:29:57 +0000 Subject: [PATCH 13/41] systemd-python: Tidy up _reader error handling --- src/python-systemd/_reader.c | 154 +++++++++++++++++----------------- src/python-systemd/journal.py | 8 +- 2 files changed, 82 insertions(+), 80 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index ae3d77cfe9..5d743eb562 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -54,28 +54,26 @@ Journal_init(Journal *self, PyObject *args, PyObject *keywds) int flags=SD_JOURNAL_LOCAL_ONLY; char *path=NULL; - static char *kwlist[] = {"flags", NULL}; + static char *kwlist[] = {"flags", "path", NULL}; if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist, &flags, &path)) return 1; int r; + Py_BEGIN_ALLOW_THREADS if (path) { r = sd_journal_open_directory(&self->j, path, 0); }else{ - Py_BEGIN_ALLOW_THREADS r = sd_journal_open(&self->j, flags); - Py_END_ALLOW_THREADS } - if (r == -EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid flags or path"); + 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; - }else if (r == -ENOMEM) { - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - return 1; - }else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error opening journal"); - return 1; } return 0; @@ -92,30 +90,27 @@ Journal_get_next(Journal *self, PyObject *args) if (! PyArg_ParseTuple(args, "|L", &skip)) return NULL; - int r; - if (skip == 1LL) { - Py_BEGIN_ALLOW_THREADS - r = sd_journal_next(self->j); - Py_END_ALLOW_THREADS - }else if (skip == -1LL) { - Py_BEGIN_ALLOW_THREADS - r = sd_journal_previous(self->j); - Py_END_ALLOW_THREADS - }else if (skip > 1LL) { - Py_BEGIN_ALLOW_THREADS - r = sd_journal_next_skip(self->j, skip); - Py_END_ALLOW_THREADS - }else if (skip < -1LL) { - Py_BEGIN_ALLOW_THREADS - r = sd_journal_previous_skip(self->j, -skip); - Py_END_ALLOW_THREADS - }else{ + if (skip == 0LL) { PyErr_SetString(PyExc_ValueError, "Skip number must positive/negative integer"); return NULL; } + int r; + Py_BEGIN_ALLOW_THREADS + if (skip == 1LL) { + r = sd_journal_next(self->j); + }else if (skip == -1LL) { + r = sd_journal_previous(self->j); + }else if (skip > 1LL) { + r = sd_journal_next_skip(self->j, skip); + }else if (skip < -1LL) { + r = sd_journal_previous_skip(self->j, -skip); + } + Py_END_ALLOW_THREADS + if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error getting next message"); + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; }else if ( r == 0) { //EOF return PyDict_New(); @@ -256,14 +251,12 @@ Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) if (PyErr_Occurred()) return NULL; r = sd_journal_add_match(self->j, arg_match, arg_match_len); - if (r == -EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid match"); - return NULL; - }else if (r == -ENOMEM) { - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - return NULL; - }else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error adding match"); + if (r < 0) { + errno = -r; + PyObject *errtype = r == -EINVAL ? PyExc_ValueError : + r == -ENOMEM ? PyExc_MemoryError : + PyExc_OSError; + PyErr_SetFromErrno(errtype); return NULL; } } @@ -345,11 +338,11 @@ Journal_add_disjunction(Journal *self, PyObject *args) { int r; r = sd_journal_add_disjunction(self->j); - if (r == -ENOMEM) { - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - return NULL; - }else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error adding disjunction"); + if (r < 0) { + errno = -r; + PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError : + PyExc_OSError; + PyErr_SetFromErrno(errtype); return NULL; } Py_RETURN_NONE; @@ -390,7 +383,8 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) r = sd_journal_seek_head(self->j); Py_END_ALLOW_THREADS if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error seeking to head"); + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; } if (offset > 0LL) { @@ -404,7 +398,8 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) r = sd_journal_seek_tail(self->j); Py_END_ALLOW_THREADS if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error seeking to tail"); + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; } if (offset < 0LL) { @@ -467,7 +462,8 @@ Journal_seek_realtime(Journal *self, PyObject *args) r = sd_journal_seek_realtime_usec(self->j, timestamp); Py_END_ALLOW_THREADS if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error seek to time"); + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; } Py_RETURN_NONE; @@ -517,8 +513,9 @@ Journal_seek_monotonic(Journal *self, PyObject *args) if (r == -EINVAL) { PyErr_SetString(PyExc_ValueError, "Invalid bootid"); return NULL; - } else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error processing bootid"); + }else if (r < 0) { + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; } }else{ @@ -526,8 +523,9 @@ Journal_seek_monotonic(Journal *self, PyObject *args) if (r == -EIO) { PyErr_SetString(PyExc_IOError, "Error getting current boot ID"); return NULL; - } else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error getting current boot ID"); + }else if (r < 0) { + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; } } @@ -536,7 +534,8 @@ Journal_seek_monotonic(Journal *self, PyObject *args) r = sd_journal_seek_monotonic_usec(self->j, sd_id, timestamp); Py_END_ALLOW_THREADS if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error seek to time"); + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; } Py_RETURN_NONE; @@ -559,14 +558,19 @@ Journal_wait(Journal *self, PyObject *args, PyObject *keywds) return NULL; int r; + Py_BEGIN_ALLOW_THREADS if ( timeout == 0LL) { - Py_BEGIN_ALLOW_THREADS r = sd_journal_wait(self->j, (uint64_t) -1); - Py_END_ALLOW_THREADS }else{ - Py_BEGIN_ALLOW_THREADS r = sd_journal_wait(self->j, timeout * 1E6); - Py_END_ALLOW_THREADS + } + Py_END_ALLOW_THREADS + if (r < 0) { + errno = -r; + PyObject *errtype = r == -ENOMEM ? PyExc_MemoryError : + PyExc_OSError; + PyErr_SetFromErrno(errtype); + return NULL; } #if PY_MAJOR_VERSION >=3 return PyLong_FromLong(r); @@ -589,14 +593,12 @@ Journal_seek_cursor(Journal *self, PyObject *args) Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_cursor(self->j, cursor); Py_END_ALLOW_THREADS - if (r == -EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid cursor"); - return NULL; - }else if (r == -ENOMEM) { - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - return NULL; - }else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error seeking to cursor"); + if (r < 0) { + errno = -r; + PyObject *errtype = r == -EINVAL ? PyExc_ValueError : + r == -ENOMEM ? PyExc_MemoryError : + PyExc_OSError; + PyErr_SetFromErrno(errtype); return NULL; } Py_RETURN_NONE; @@ -642,14 +644,12 @@ Journal_query_unique(Journal *self, PyObject *args) Py_BEGIN_ALLOW_THREADS r = sd_journal_query_unique(self->j, query); Py_END_ALLOW_THREADS - if (r == -EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid field name"); - return NULL; - } else if (r == -ENOMEM) { - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - return NULL; - } else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error querying journal"); + if (r < 0) { + errno = -r; + PyObject *errtype = r == -EINVAL ? PyExc_ValueError : + r == -ENOMEM ? PyExc_MemoryError : + PyExc_OSError; + PyErr_SetFromErrno(errtype); return NULL; } @@ -684,8 +684,9 @@ Journal_get_data_threshold(Journal *self, void *closure) int r; r = sd_journal_get_data_threshold(self->j, &cvalue); - if (r < 0){ - PyErr_SetString(PyExc_RuntimeError, "Error getting data threshold"); + if (r < 0) { + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); return NULL; } @@ -718,9 +719,10 @@ Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) #else r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value)); #endif - if (r < 0){ - PyErr_SetString(PyExc_RuntimeError, "Error setting data threshold"); - return -1; + if (r < 0) { + errno = -r; + PyErr_SetFromErrno(PyExc_OSError); + return NULL; } return 0; } diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 6e82a460b5..db35ba2005 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -115,13 +115,13 @@ class Journal(_Journal): args.extend(_make_line(key, val) for key, val in kwargs.items()) super(Journal, self).add_match(*args) - def get_next(self, *args, **kwargs): + def get_next(self, skip=1): return self._convert_entry( - super(Journal, self).get_next(*args, **kwargs)) + super(Journal, self).get_next(skip)) - def query_unique(self, key, *args, **kwargs): + def query_unique(self, key): return set(self._convert_field(key, value) - for value in super(Journal, self).query_unique(key, *args, **kwargs)) + for value in super(Journal, self).query_unique(key)) def log_level(self, level): """Sets maximum log level by setting matches for PRIORITY.""" From 7a1b9cd5e43c85a3b1f22a0151ac5b6340fb0d0f Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 17:31:18 +0000 Subject: [PATCH 14/41] systemd-python: Moved _reader datetime usage to python --- src/python-systemd/_reader.c | 52 ++++++----------------------------- src/python-systemd/journal.py | 10 +++++++ 2 files changed, 18 insertions(+), 44 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 5d743eb562..f1c3175bbf 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -426,34 +426,12 @@ PyDoc_STRVAR(Journal_seek_realtime__doc__, static PyObject * Journal_seek_realtime(Journal *self, PyObject *args) { - PyObject *arg; - if (! PyArg_ParseTuple(args, "O", &arg)) + uint64_t timestamp; + if (! PyArg_ParseTuple(args, "K", ×tamp)) return NULL; - uint64_t timestamp=-1LL; - if (PyDateTime_Check(arg)) { - PyObject *temp; - char *timestamp_str; - temp = PyObject_CallMethod(arg, "strftime", "s", "%s%f"); -#if PY_MAJOR_VERSION >=3 - PyObject *temp2; - temp2 = PyUnicode_AsUTF8String(temp); - timestamp_str = PyBytes_AsString(temp2); - Py_DECREF(temp2); -#else - timestamp_str = PyString_AsString(temp); -#endif - Py_DECREF(temp); - timestamp = strtoull(timestamp_str, NULL, 10); - }else if (PyLong_Check(arg)) { - timestamp = PyLong_AsUnsignedLongLong(arg); -#if PY_MAJOR_VERSION <3 - }else if (PyInt_Check(arg)) { - timestamp = PyInt_AsUnsignedLongLongMask(arg); -#endif - } if ((int64_t) timestamp < 0LL) { - PyErr_SetString(PyExc_ValueError, "Time must be positive integer or datetime instance"); + PyErr_SetString(PyExc_ValueError, "Time must be positive integer"); return NULL; } @@ -479,30 +457,16 @@ PyDoc_STRVAR(Journal_seek_monotonic__doc__, static PyObject * Journal_seek_monotonic(Journal *self, PyObject *args) { - PyObject *arg; + double timedouble; char *bootid=NULL; - if (! PyArg_ParseTuple(args, "O|s", &arg, &bootid)) + if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid)) return NULL; - uint64_t timestamp=-1LL; - if PyDelta_Check(arg) { - PyObject *temp; - temp = PyObject_CallMethod(arg, "total_seconds", NULL); - timestamp = (uint64_t) (PyFloat_AsDouble(temp) * 1E6); - Py_DECREF(temp); - }else if (PyFloat_Check(arg)) { - timestamp = (uint64_t) (PyFloat_AsDouble(arg) * 1E6); - }else if (PyLong_Check(arg)) { - timestamp = PyLong_AsUnsignedLongLong(arg) * (uint64_t) 1E6; -#if PY_MAJOR_VERSION <3 - }else if (PyInt_Check(arg)) { - timestamp = PyInt_AsUnsignedLongLongMask(arg) * (uint64_t) 1E6; -#endif - - } + uint64_t timestamp; + timestamp = (uint64_t) (timedouble * 1.0E6); if ((int64_t) timestamp < 0LL) { - PyErr_SetString(PyExc_ValueError, "Time must be positive number or timedelta instance"); + PyErr_SetString(PyExc_ValueError, "Time must be positive number"); return NULL; } diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index db35ba2005..46affcee00 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -123,6 +123,16 @@ class Journal(_Journal): return set(self._convert_field(key, value) for value in super(Journal, self).query_unique(key)) + def seek_realtime(self, timestamp): + if isinstance(timestamp, datetime.datetime): + timestamp = int(timestamp.strftime("%s%f")) + return super(Journal, self).seek_realtime(timestamp) + + def seek_monotonic(self, timestamp, bootid=None): + if isinstance(timestamp, datetime.timedelta): + timestamp = timestamp.totalseconds() + return super(Journal, self).seek_monotonic(timestamp, bootid) + def log_level(self, level): """Sets maximum log level by setting matches for PRIORITY.""" if 0 <= level <= 7: From d2dd0265b5140ce849cfc3821ad595df2d4c9eb7 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 17:32:05 +0000 Subject: [PATCH 15/41] systemd-python: updated _reader header to standard license --- src/python-systemd/_reader.c | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index f1c3175bbf..9d7ce2da27 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -1,21 +1,23 @@ -/* -_reader - Python module that reads systemd journal similar to journalctl -Copyright (C) 2012 Steven Hiscocks +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. +/*** + This file is part of systemd. -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. + Copyright 2013 Steven Hiscocks, Zbigniew Jędrzejewski-Szmek -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA -*/ + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ #include #include From a49f4d17bfc50c5864b992e2410a41f96286afc1 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 18:23:24 +0000 Subject: [PATCH 16/41] systemd-python: correct data_threshold error return value --- src/python-systemd/_reader.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 9d7ce2da27..d2f738b9c7 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -688,7 +688,7 @@ Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) if (r < 0) { errno = -r; PyErr_SetFromErrno(PyExc_OSError); - return NULL; + return -1; } return 0; } From 5bb2b8d5e40eadde6ab9cc2941d2395c93e2a545 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 18:53:52 +0000 Subject: [PATCH 17/41] systemd-python: _reader add_match takes single string python code now takes care of multiple matches --- src/python-systemd/_reader.c | 112 ++++------------------------------ src/python-systemd/journal.py | 3 +- 2 files changed, 15 insertions(+), 100 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index d2f738b9c7..8f21678c81 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -97,7 +97,7 @@ Journal_get_next(Journal *self, PyObject *args) return NULL; } - int r; + int r = -EINVAL; Py_BEGIN_ALLOW_THREADS if (skip == 1LL) { r = sd_journal_next(self->j); @@ -226,106 +226,20 @@ PyDoc_STRVAR(Journal_add_match__doc__, static PyObject * Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) { - Py_ssize_t arg_match_len; - char *arg_match; - int i, r; - for (i = 0; i < PySequence_Size(args); i++) { -#if PY_MAJOR_VERSION >=3 - PyObject *arg; - arg = PySequence_Fast_GET_ITEM(args, i); - if (PyUnicode_Check(arg)) { -#if PY_MINOR_VERSION >=3 - arg_match = PyUnicode_AsUTF8AndSize(arg, &arg_match_len); -#else - PyObject *temp; - temp = PyUnicode_AsUTF8String(arg); - PyBytes_AsStringAndSize(temp, &arg_match, &arg_match_len); - Py_DECREF(temp); -#endif - }else if (PyBytes_Check(arg)) { - PyBytes_AsStringAndSize(arg, &arg_match, &arg_match_len); - }else{ - PyErr_SetString(PyExc_TypeError, "expected bytes or string"); - } -#else - PyString_AsStringAndSize(PySequence_Fast_GET_ITEM(args, i), &arg_match, &arg_match_len); -#endif - if (PyErr_Occurred()) - return NULL; - r = sd_journal_add_match(self->j, arg_match, arg_match_len); - if (r < 0) { - errno = -r; - PyObject *errtype = r == -EINVAL ? PyExc_ValueError : - r == -ENOMEM ? PyExc_MemoryError : - PyExc_OSError; - PyErr_SetFromErrno(errtype); - return NULL; - } - } - - if (! keywds) - Py_RETURN_NONE; - - PyObject *key, *value; - Py_ssize_t pos=0, match_key_len, match_value_len; + char *match; int match_len; - char *match_key, *match_value; - void *match; - while (PyDict_Next(keywds, &pos, &key, &value)) { -#if PY_MAJOR_VERSION >=3 - if (PyUnicode_Check(key)) { -#if PY_MINOR_VERSION >=3 - match_key = PyUnicode_AsUTF8AndSize(key, &match_key_len); -#else - PyObject *temp2; - temp2 = PyUnicode_AsUTF8String(key); - PyBytes_AsStringAndSize(temp2, &match_key, &match_key_len); - Py_DECREF(temp2); -#endif - }else if (PyBytes_Check(key)) { - PyBytes_AsStringAndSize(key, &match_key, &match_key_len); - }else{ - PyErr_SetString(PyExc_TypeError, "expected bytes or string"); - } - if (PyUnicode_Check(value)) { -#if PY_MINOR_VERSION >=3 - match_value = PyUnicode_AsUTF8AndSize(value, &match_value_len); -#else - PyObject *temp3; - temp3 = PyUnicode_AsUTF8String(value); - PyBytes_AsStringAndSize(temp3, &match_value, &match_value_len); - Py_DECREF(temp3); -#endif - }else if (PyBytes_Check(value)) { - PyBytes_AsStringAndSize(value, &match_value, &match_value_len); - }else{ - PyErr_SetString(PyExc_TypeError, "expected bytes or string"); - } -#else - PyString_AsStringAndSize(key, &match_key, &match_key_len); - PyString_AsStringAndSize(value, &match_value, &match_value_len); -#endif - if (PyErr_Occurred()) - return NULL; + if (! PyArg_ParseTuple(args, "s#", &match, &match_len)) + return NULL; - match_len = match_key_len + 1 + match_value_len; - match = malloc(match_len); - memcpy(match, match_key, match_key_len); - memcpy(match + match_key_len, "=", 1); - memcpy(match + match_key_len + 1, match_value, match_value_len); - - r = sd_journal_add_match(self->j, match, match_len); - free(match); - if (r == -EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid match"); - return NULL; - }else if (r == -ENOMEM) { - PyErr_SetString(PyExc_MemoryError, "Not enough memory"); - return NULL; - }else if (r < 0) { - PyErr_SetString(PyExc_RuntimeError, "Error adding match"); - 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; } Py_RETURN_NONE; diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 46affcee00..279662e6a6 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -113,7 +113,8 @@ class Journal(_Journal): def add_match(self, *args, **kwargs): args = list(args) args.extend(_make_line(key, val) for key, val in kwargs.items()) - super(Journal, self).add_match(*args) + for arg in args: + super(Journal, self).add_match(arg) def get_next(self, skip=1): return self._convert_entry( From 83718af6f59fb3a6359c8da1f8bc8b8fd8799106 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 19:06:46 +0000 Subject: [PATCH 18/41] systemd-python: Update _reader docstrings --- src/python-systemd/_reader.c | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 8f21678c81..7aa638b076 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -217,12 +217,11 @@ Journal_get_previous(Journal *self, PyObject *args) } PyDoc_STRVAR(Journal_add_match__doc__, -"add_match(match, ..., field=value, ...) -> None\n\n" +"add_match(match) -> None\n\n" "Add a match to filter journal log entries. All matches of different\n" -"field are combined in logical AND, and matches of the same field\n" +"fields are combined in logical AND, and matches of the same field\n" "are automatically combined in logical OR.\n" -"Matches can be passed as strings \"field=value\", or keyword\n" -"arguments field=\"value\"."); +"Match is string of form \"field=value\"."); static PyObject * Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) { @@ -337,8 +336,7 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) PyDoc_STRVAR(Journal_seek_realtime__doc__, "seek_realtime(realtime) -> None\n\n" "Seek to nearest matching journal entry to `realtime`. Argument\n" -"`realtime` can be an integer unix timestamp in usecs or a " -"datetime instance."); +"`realtime` can must be an integer unix timestamp in usecs."); static PyObject * Journal_seek_realtime(Journal *self, PyObject *args) { @@ -366,8 +364,7 @@ Journal_seek_realtime(Journal *self, PyObject *args) 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 secs, or a\n" -"timedelta instance.\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 * From 518dc5dff557116db3f072eb9d0a9492b6226778 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 19:10:49 +0000 Subject: [PATCH 19/41] systemd-python: _reader now takes unix timestamp in seconds --- src/python-systemd/_reader.c | 9 ++++++--- src/python-systemd/journal.py | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 7aa638b076..95e6094162 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -336,14 +336,17 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) 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 in usecs."); +"`realtime` can must be an integer unix timestamp."); static PyObject * Journal_seek_realtime(Journal *self, PyObject *args) { - uint64_t timestamp; - if (! PyArg_ParseTuple(args, "K", ×tamp)) + double timedouble; + if (! PyArg_ParseTuple(args, "d", &timedouble)) return NULL; + uint64_t timestamp; + timestamp = (uint64_t) (timedouble * 1.0E6); + if ((int64_t) timestamp < 0LL) { PyErr_SetString(PyExc_ValueError, "Time must be positive integer"); return NULL; diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 279662e6a6..ab8661e987 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -126,7 +126,7 @@ class Journal(_Journal): def seek_realtime(self, timestamp): if isinstance(timestamp, datetime.datetime): - timestamp = int(timestamp.strftime("%s%f")) + timestamp = float(timestamp.strftime("%s.%f")) return super(Journal, self).seek_realtime(timestamp) def seek_monotonic(self, timestamp, bootid=None): From dd37d563ba236cb0ae583d73e02e9e093084e2e5 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sat, 16 Feb 2013 19:16:09 +0000 Subject: [PATCH 20/41] systemd-python: remove unneeded ifdef for query_unique --- src/python-systemd/_reader.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 95e6094162..c16cbdcd76 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -508,7 +508,6 @@ Journal_iternext(PyObject *self) } } -#ifdef SD_JOURNAL_FOREACH_UNIQUE 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" @@ -554,7 +553,6 @@ Journal_query_unique(Journal *self, PyObject *args) Py_DECREF(key); return value_set; } -#endif //def SD_JOURNAL_FOREACH_UNIQUE static PyObject * Journal_get_data_threshold(Journal *self, void *closure) @@ -637,10 +635,8 @@ static PyMethodDef Journal_methods[] = { Journal_wait__doc__}, {"seek_cursor", (PyCFunction)Journal_seek_cursor, METH_VARARGS, Journal_seek_cursor__doc__}, -#ifdef SD_JOURNAL_FOREACH_UNIQUE {"query_unique", (PyCFunction)Journal_query_unique, METH_VARARGS, Journal_query_unique__doc__}, -#endif {NULL} /* Sentinel */ }; From 301ae164b7478ee1f153ddd529093dcf0e61933d Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 17 Feb 2013 11:48:13 +0000 Subject: [PATCH 21/41] systemd-python: Journal this_boot/machine now accepts ID --- src/python-systemd/journal.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index ab8661e987..60c8111d23 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -142,13 +142,28 @@ class Journal(_Journal): else: raise ValueError("Log level must be 0 <= level <= 7") - def this_boot(self): - """Add match for _BOOT_ID equal to current boot ID.""" - self.add_match(_BOOT_ID=_id128.get_boot().hex) + def this_boot(self, bootid=None): + """Add match for _BOOT_ID equal to current boot ID or the specified boot ID. + + bootid should be either a UUID or a 32 digit hex number. + """ + if bootid is None: + bootid = _id128.get_boot().hex + else: + bootid = getattr(bootid, 'hex', bootid) + self.add_match(_BOOT_ID=bootid) + + def this_machine(self, machineid=None): + """Add match for _MACHINE_ID equal to the ID of this machine. + + bootid should be either a UUID or a 32 digit hex number. + """ + if machineid is None: + machineid = _id128.get_machine().hex + else: + machineid = getattr(machineid, 'hex', machineid) + self.add_match(_MACHINE_ID=machineid) - def this_machine(self): - """Add match for _MACHINE_ID equal to the ID of this machine.""" - self.add_match(_MACHINE_ID=_id128.get_machine().hex) def _make_line(field, value): if isinstance(value, bytes): From b835982344834085a47254f0cd8cc437b7678719 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 17 Feb 2013 11:53:09 +0000 Subject: [PATCH 22/41] systemd-python: tidy up import names in journal --- src/python-systemd/_reader.c | 2 +- src/python-systemd/journal.py | 30 +++++++++++++++++------------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index c16cbdcd76..481da245e9 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -642,7 +642,7 @@ static PyMethodDef Journal_methods[] = { static PyTypeObject JournalType = { PyVarObject_HEAD_INIT(NULL, 0) - "_reader.Journal", /*tp_name*/ + "_reader._Journal", /*tp_name*/ sizeof(Journal), /*tp_basicsize*/ 0, /*tp_itemsize*/ (destructor)Journal_dealloc, /*tp_dealloc*/ diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 60c8111d23..a50922ca3d 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -19,14 +19,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with systemd; If not, see . -import datetime -import functools -import sys -import uuid +import sys as _sys +import datetime as _datetime +import functools as _functools +import uuid as _uuid import traceback as _traceback import os as _os +from os import SEEK_SET, SEEK_CUR, SEEK_END import logging as _logging -if sys.version_info >= (3,): +if _sys.version_info >= (3,): from collections import ChainMap from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) @@ -35,10 +36,12 @@ from ._reader import (_Journal, NOP, APPEND, INVALIDATE, LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) from . import id128 as _id128 -_MONOTONIC_CONVERTER = lambda x: datetime.timedelta(microseconds=float(x)) -_REALTIME_CONVERTER = lambda x: datetime.datetime.fromtimestamp(float(x)/1E6) +_MONOTONIC_CONVERTER = lambda x: _datetime.timedelta(microseconds=float(x)) +_REALTIME_CONVERTER = lambda x: _datetime.datetime.fromtimestamp(float(x)/1E6) DEFAULT_CONVERTERS = { - 'MESSAGE_ID': uuid.UUID, + 'MESSAGE_ID': _uuid.UUID, + '_MACHINE_ID': _uuid.UUID, + '_BOOT_ID': _uuid.UUID, 'PRIORITY': int, 'LEADER': int, 'SESSION_ID': int, @@ -70,15 +73,16 @@ DEFAULT_CONVERTERS = { 'COREDUMP_TIMESTAMP': _REALTIME_CONVERTER, } -if sys.version_info >= (3,): - _convert_unicode = functools.partial(str, encoding='utf-8') +if _sys.version_info >= (3,): + _convert_unicode = _functools.partial(str, encoding='utf-8') else: - _convert_unicode = functools.partial(unicode, encoding='utf-8') + _convert_unicode = _functools.partial(unicode, encoding='utf-8') class Journal(_Journal): def __init__(self, converters=None, *args, **kwargs): super(Journal, self).__init__(*args, **kwargs) if sys.version_info >= (3,3): + if _sys.version_info >= (3,3): self.converters = ChainMap() if converters is not None: self.converters.maps.append(converters) @@ -125,12 +129,12 @@ class Journal(_Journal): for value in super(Journal, self).query_unique(key)) def seek_realtime(self, timestamp): - if isinstance(timestamp, datetime.datetime): + if isinstance(realtime, _datetime.datetime): timestamp = float(timestamp.strftime("%s.%f")) return super(Journal, self).seek_realtime(timestamp) def seek_monotonic(self, timestamp, bootid=None): - if isinstance(timestamp, datetime.timedelta): + if isinstance(monotonic, _datetime.timedelta): timestamp = timestamp.totalseconds() return super(Journal, self).seek_monotonic(timestamp, bootid) From c71f26eba57a8263dc28c99f9f446e0a75307f34 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 17 Feb 2013 11:55:45 +0000 Subject: [PATCH 23/41] systemd-python: update Journal python docstrings --- src/python-systemd/_reader.c | 2 +- src/python-systemd/journal.py | 73 +++++++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 481da245e9..70676aceca 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -57,7 +57,7 @@ Journal_init(Journal *self, PyObject *args, PyObject *keywds) char *path=NULL; static char *kwlist[] = {"flags", "path", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, keywds, "|is", kwlist, + if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iz", kwlist, &flags, &path)) return 1; diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index a50922ca3d..4f42928f69 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -79,21 +79,34 @@ else: _convert_unicode = _functools.partial(unicode, encoding='utf-8') class Journal(_Journal): - def __init__(self, converters=None, *args, **kwargs): - super(Journal, self).__init__(*args, **kwargs) - if sys.version_info >= (3,3): + def __init__(self, converters=None, flags=LOCAL_ONLY, path=None): + """Creates instance of Journal, which allows filtering and + return of journal entries. + Argument `converters` is a dictionary which updates the + DEFAULT_CONVERTERS to convert journal field values. + Argument `flags` sets open flags of the journal, which can be one + of, or ORed combination of constants: LOCAL_ONLY (default) opens + journal on local machine only; RUNTIME_ONLY opens only + volatile journal files; and SYSTEM_ONLY opens only + journal files of system services and the kernel. + Argument `path` is the directory of journal files. Note that + currently flags are ignored when `path` is present as they are + currently not relevant. + """ + super(Journal, self).__init__(flags, path) if _sys.version_info >= (3,3): self.converters = ChainMap() if converters is not None: self.converters.maps.append(converters) self.converters.maps.append(DEFAULT_CONVERTERS) else: - # suitable fallback, e.g. self.converters = DEFAULT_CONVERTERS.copy() if converters is not None: self.converters.update(converters) def _convert_field(self, key, value): + """ Convert value based on callable from self.converters + based of field/key""" try: result = self.converters[key](value) except: @@ -106,6 +119,7 @@ class Journal(_Journal): return result def _convert_entry(self, entry): + """ Convert entire journal entry utilising _covert_field""" result = {} for key, value in entry.items(): if isinstance(value, list): @@ -115,31 +129,54 @@ class Journal(_Journal): return result def add_match(self, *args, **kwargs): + """Add one or more matches to the filter journal log entries. + All matches of different field are combined in a logical AND, + and matches of the smae field are automatically combined in a + logical OR. + Matches can be passed as strings of form "field=value", or + keyword arguments field="value".""" args = list(args) args.extend(_make_line(key, val) for key, val in kwargs.items()) for arg in args: super(Journal, self).add_match(arg) def get_next(self, skip=1): + """Return dictionary of the next log entry. Optional skip value + will return the `skip`th log entry. + Returned will be journal entry dictionary processed with + converters.""" return self._convert_entry( super(Journal, self).get_next(skip)) - def query_unique(self, key): - return set(self._convert_field(key, value) - for value in super(Journal, self).query_unique(key)) + def query_unique(self, field): + """Returns a set of unique values in journal for given `field`, + processed with converters. + Note this does not respect any journal matches.""" + return set(self._convert_field(field, value) + for value in super(Journal, self).query_unique(field)) - def seek_realtime(self, timestamp): + def seek_realtime(self, realtime): + """Seek to nearest matching journal entry to `realtime`. + Argument `realtime` can must be either an integer unix timestamp + or datetime.datetime instance.""" if isinstance(realtime, _datetime.datetime): - timestamp = float(timestamp.strftime("%s.%f")) - return super(Journal, self).seek_realtime(timestamp) + realtime = float(realtime.strftime("%s.%f")) + return super(Journal, self).seek_realtime(realtime) - def seek_monotonic(self, timestamp, bootid=None): + def seek_monotonic(self, monotonic, bootid=None): + """Seek to nearest matching journal entry to `monotonic`. + Argument `monotonic` is a timestamp from boot in either seconds + or a datetime.timedelta instance. + Argument `bootid` is a string or UUID representing which boot the + monotonic time is reference to. Defaults to current bootid.""" if isinstance(monotonic, _datetime.timedelta): - timestamp = timestamp.totalseconds() - return super(Journal, self).seek_monotonic(timestamp, bootid) + monotonic = monotonic.totalseconds() + if isinstance(bootid, _uuid.UUID): + bootid = bootid.get_hex() + return super(Journal, self).seek_monotonic(monotonic, bootid) def log_level(self, level): - """Sets maximum log level by setting matches for PRIORITY.""" + """Sets maximum log `level` by setting matches for PRIORITY.""" if 0 <= level <= 7: for i in range(level+1): self.add_match(PRIORITY="%s" % i) @@ -149,7 +186,9 @@ class Journal(_Journal): def this_boot(self, bootid=None): """Add match for _BOOT_ID equal to current boot ID or the specified boot ID. - bootid should be either a UUID or a 32 digit hex number. + If specified, bootid should be either a UUID or a 32 digit hex number. + + Equivalent to add_match(_BOOT_ID='bootid'). """ if bootid is None: bootid = _id128.get_boot().hex @@ -160,7 +199,9 @@ class Journal(_Journal): def this_machine(self, machineid=None): """Add match for _MACHINE_ID equal to the ID of this machine. - bootid should be either a UUID or a 32 digit hex number. + If specified, machineid should be either a UUID or a 32 digit hex number. + + Equivalent to add_match(_MACHINE_ID='machineid'). """ if machineid is None: machineid = _id128.get_machine().hex From 6d0c634ca32c772c72b0bb4207866e2a35f4eadb Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 17 Feb 2013 14:27:59 +0000 Subject: [PATCH 24/41] systemd-python: fix memory leak in _reader and minor bugs iternext now checks for error from get_next, and changed a DECREF to XDECREF rather than NULL check --- src/python-systemd/_reader.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 70676aceca..b754014e9b 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -326,8 +326,7 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) PyErr_SetString(PyExc_ValueError, "Invalid value for whence"); } - if (result) - Py_DECREF(result); + Py_XDECREF(result); if (PyErr_Occurred()) return NULL; Py_RETURN_NONE; @@ -498,6 +497,8 @@ Journal_iternext(PyObject *self) Py_ssize_t dict_size; dict = PyObject_CallMethod(self, "get_next", ""); + if (PyErr_Occurred()) + return NULL; dict_size = PyDict_Size(dict); if ((int64_t) dict_size > 0LL) { return dict; From 89d9a233890a11eb4e6bd3960fff20d8a5afd8ae Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Sun, 17 Feb 2013 17:46:01 +0000 Subject: [PATCH 25/41] systemd-python: add Journal method to add MESSAGE_ID match --- src/python-systemd/journal.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 4f42928f69..d61c30e124 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -183,6 +183,15 @@ class Journal(_Journal): else: raise ValueError("Log level must be 0 <= level <= 7") + def messageid_match(self, messageid): + """Sets match filter for log entries for specified `messageid`. + `messageid` can be string or UUID instance. + Standard message IDs can be found in systemd.id128 + Equivalent to add_match(MESSAGE_ID=`messageid`).""" + if isinstance(messageid, _uuid.UUID): + messageid = messageid.get_hex() + self.add_match(MESSAGE_ID=messageid) + def this_boot(self, bootid=None): """Add match for _BOOT_ID equal to current boot ID or the specified boot ID. From eccc9e74d2258e094882614db39bce9d7d1c9510 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Tue, 19 Feb 2013 20:37:55 +0000 Subject: [PATCH 26/41] systemd-python: Journal convert_unicode exception handling change Rather than catch all, is now limited to UnicodeDecodeError --- src/python-systemd/journal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index d61c30e124..5c5f5ca05b 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -113,7 +113,7 @@ class Journal(_Journal): # Default conversion in unicode try: result = _convert_unicode(value) - except: + except UnicodeDecodeError: # Leave in default bytes result = value return result From cac40fbe240565c9431f3f535b0523bd3a7fe225 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Tue, 19 Feb 2013 20:39:45 +0000 Subject: [PATCH 27/41] systemd-python: Added doc string for Journal --- src/python-systemd/journal.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 5c5f5ca05b..d63722b91d 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -79,6 +79,24 @@ else: _convert_unicode = _functools.partial(unicode, encoding='utf-8') class Journal(_Journal): + """Journal allows the access and filtering of systemd journal + entries. Note that in order to access the system journal, a + non-root user must be in the `adm` group. + + Example usage to print out all error or higher level messages + for systemd-udevd for the boot: + + >>> myjournal = journal.Journal() + >>> myjournal.add_boot_match(journal.CURRENT_BOOT) + >>> myjournal.add_loglevel_matches(journal.LOG_ERR) + >>> myjournal.add_match(_SYSTEMD_UNIT="systemd-udevd.service") + >>> from __future__ import print_function + >>> for entry in myjournal: + ... print(entry['MESSAGE']) + + See man page "systemd.journal-fields" for more info on + typical fields found in the journal. + """ def __init__(self, converters=None, flags=LOCAL_ONLY, path=None): """Creates instance of Journal, which allows filtering and return of journal entries. From e82e4f4562f2c6e9d2949d6357ff24154f758426 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sun, 17 Feb 2013 11:26:10 -0500 Subject: [PATCH 28/41] systemd-python: introduce error setting helper --- src/python-systemd/_reader.c | 137 +++++++++++------------------------ 1 file changed, 42 insertions(+), 95 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index b754014e9b..65ca06b97f 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -30,6 +30,20 @@ typedef struct { } Journal; static PyTypeObject JournalType; +static int set_error(int r, const char* path, const char* invalid_message) { + if (r >= 0) + return r; + if (r == -EINVAL && invalid_message) + PyErr_SetString(PyExc_ValueError, invalid_message); + else if (r == -ENOMEM) + PyErr_SetString(PyExc_MemoryError, "Not enough memory"); + else { + errno = -r; + PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); + } + return 1; +} + static void Journal_dealloc(Journal* self) { @@ -69,16 +83,8 @@ Journal_init(Journal *self, PyObject *args, PyObject *keywds) 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(Journal_get_next__doc__, @@ -110,13 +116,11 @@ Journal_get_next(Journal *self, PyObject *args) } Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + set_error(r, NULL, NULL); + if (r < 0) return NULL; - }else if ( r == 0) { //EOF + else if (r == 0) /* EOF */ return PyDict_New(); - } PyObject *dict; dict = PyDict_New(); @@ -232,14 +236,9 @@ Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) 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; } @@ -253,13 +252,9 @@ Journal_add_disjunction(Journal *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; } @@ -297,11 +292,9 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_head(self->j); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + if (set_error(r, NULL, NULL)) return NULL; - } + if (offset > 0LL) { result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); } @@ -312,11 +305,9 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_tail(self->j); Py_END_ALLOW_THREADS - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); + if (set_error(r, NULL, NULL)) return NULL; - } + if (offset < 0LL) { result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); }else{ @@ -355,11 +346,8 @@ Journal_seek_realtime(Journal *self, PyObject *args) 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; } @@ -389,37 +377,24 @@ Journal_seek_monotonic(Journal *self, PyObject *args) int r; if (bootid) { r = sd_id128_from_string(bootid, &sd_id); - if (r == -EINVAL) { - PyErr_SetString(PyExc_ValueError, "Invalid bootid"); + if (set_error(r, NULL, "Invalid bootid")) return NULL; - }else if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - }else{ + } else { + Py_BEGIN_ALLOW_THREADS r = sd_id128_get_boot(&sd_id); - if (r == -EIO) { - PyErr_SetString(PyExc_IOError, "Error getting current boot ID"); + Py_END_ALLOW_THREADS + if (set_error(r, NULL, NULL)) return NULL; - }else if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } } Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_monotonic_usec(self->j, sd_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" @@ -438,19 +413,11 @@ Journal_wait(Journal *self, PyObject *args, PyObject *keywds) int r; 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 ==0 ? (uint64_t) -1 : timeout * 1E6); 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)) return NULL; - } + #if PY_MAJOR_VERSION >=3 return PyLong_FromLong(r); #else @@ -472,14 +439,8 @@ Journal_seek_cursor(Journal *self, PyObject *args) 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; } @@ -524,14 +485,8 @@ Journal_query_unique(Journal *self, PyObject *args) 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; @@ -563,11 +518,8 @@ Journal_get_data_threshold(Journal *self, void *closure) 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); @@ -598,12 +550,7 @@ Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) #else r = sd_journal_set_data_threshold(self->j, (size_t) PyInt_AsLong(value)); #endif - if (r < 0) { - errno = -r; - PyErr_SetFromErrno(PyExc_OSError); - return -1; - } - return 0; + return set_error(r, NULL, NULL); } static PyGetSetDef Journal_getseters[] = { From ecb6dfe117a8890a86c7e586fb2315bf44aed882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 16 Feb 2013 21:54:09 -0500 Subject: [PATCH 29/41] systemd-python: wrap some python differences using macros --- src/python-systemd/_reader.c | 74 +++++++++++++----------------------- 1 file changed, 26 insertions(+), 48 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 65ca06b97f..6504396404 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -24,6 +24,23 @@ #include #include +#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; @@ -132,11 +149,7 @@ Journal_get_next(Journal *self, PyObject *args) 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 + key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg); value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) ); if (PyDict_Contains(dict, key)) { cur_value = PyDict_GetItem(dict, key); @@ -160,12 +173,7 @@ Journal_get_next(Journal *self, PyObject *args) if (sd_journal_get_realtime_usec(self->j, &realtime) == 0) { char realtime_str[20]; sprintf(realtime_str, "%llu", (long long unsigned) realtime); - -#if PY_MAJOR_VERSION >=3 - key = PyUnicode_FromString("__REALTIME_TIMESTAMP"); -#else - key = PyString_FromString("__REALTIME_TIMESTAMP"); -#endif + key = unicode_FromString("__REALTIME_TIMESTAMP"); value = PyBytes_FromString(realtime_str); PyDict_SetItem(dict, key, value); Py_DECREF(key); @@ -177,11 +185,7 @@ Journal_get_next(Journal *self, PyObject *args) 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 + key = unicode_FromString("__MONOTONIC_TIMESTAMP"); value = PyBytes_FromString(monotonic_str); PyDict_SetItem(dict, key, value); @@ -191,11 +195,7 @@ Journal_get_next(Journal *self, PyObject *args) 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 + key = unicode_FromString("__CURSOR"); value = PyBytes_FromString(cursor); PyDict_SetItem(dict, key, value); free(cursor); @@ -418,11 +418,7 @@ Journal_wait(Journal *self, PyObject *args, PyObject *keywds) if (set_error(r, NULL, NULL)) 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__, @@ -493,12 +489,7 @@ Journal_query_unique(Journal *self, PyObject *args) 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) { delim_ptr = memchr(uniq, '=', uniq_len); @@ -521,12 +512,7 @@ Journal_get_data_threshold(Journal *self, void *closure) 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 @@ -536,20 +522,12 @@ Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold"); return -1; } -#if PY_MAJOR_VERSION >=3 - if (! PyLong_Check(value)){ -#else - if (! PyInt_Check(value)){ -#endif + if (!long_Check(value)){ 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 + r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value)); return set_error(r, NULL, NULL); } From 118bf4bad88366fc210a92561b3bf056630a3592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 19 Feb 2013 21:58:54 -0500 Subject: [PATCH 30/41] systemd-python: add casts and fix unused variable warnings in _reader --- src/python-systemd/_reader.c | 58 +++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 6504396404..c6f29f5e33 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -84,12 +84,12 @@ PyDoc_STRVAR(Journal__doc__, static int Journal_init(Journal *self, PyObject *args, PyObject *keywds) { - int flags=SD_JOURNAL_LOCAL_ONLY; - char *path=NULL; + int flags = SD_JOURNAL_LOCAL_ONLY; + char *path = NULL; - static char *kwlist[] = {"flags", "path", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, keywds, "|iz", kwlist, - &flags, &path)) + static const char* const kwlist[] = {"flags", "path", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|iz", (char**) kwlist, + &flags, &path)) return 1; int r; @@ -217,7 +217,8 @@ Journal_get_previous(Journal *self, PyObject *args) if (! PyArg_ParseTuple(args, "|L", &skip)) return NULL; - return PyObject_CallMethod((PyObject *)self, "get_next", "L", -skip); + return PyObject_CallMethod((PyObject *)self, (char*) "get_next", + (char*) "L", -skip); } PyDoc_STRVAR(Journal_add_match__doc__, @@ -280,10 +281,10 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) { int64_t offset; int whence=SEEK_SET; - static char *kwlist[] = {"offset", "whence", NULL}; - if (! PyArg_ParseTupleAndKeywords(args, keywds, "L|i", kwlist, - &offset, &whence)) + static const char* const kwlist[] = {"offset", "whence", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "L|i", (char**) kwlist, + &offset, &whence)) return NULL; PyObject *result=NULL; @@ -296,10 +297,12 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) return NULL; if (offset > 0LL) { - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); + result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", + (char*) "L", offset); } }else if (whence == SEEK_CUR){ - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); + result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", + (char*) "L", offset); }else if (whence == SEEK_END){ int r; Py_BEGIN_ALLOW_THREADS @@ -309,9 +312,11 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) return NULL; if (offset < 0LL) { - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", offset); + result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", + (char*) "L", offset); }else{ - result = PyObject_CallMethod((PyObject *)self, "get_next", "L", -1LL); + result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", + (char*) "L", -1LL); } }else{ PyErr_SetString(PyExc_ValueError, "Invalid value for whence"); @@ -407,11 +412,12 @@ PyDoc_STRVAR(Journal_wait__doc__, static PyObject * Journal_wait(Journal *self, PyObject *args, PyObject *keywds) { - int64_t timeout=0LL; - if (! PyArg_ParseTuple(args, "|L", &timeout)) + int r; + int64_t timeout = 0LL; + + if (!PyArg_ParseTuple(args, "|L", &timeout)) return NULL; - int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_wait(self->j, timeout ==0 ? (uint64_t) -1 : timeout * 1E6); Py_END_ALLOW_THREADS @@ -453,7 +459,7 @@ Journal_iternext(PyObject *self) PyObject *dict; Py_ssize_t dict_size; - dict = PyObject_CallMethod(self, "get_next", ""); + dict = PyObject_CallMethod(self, (char*) "get_next", (char*) ""); if (PyErr_Occurred()) return NULL; dict_size = PyDict_Size(dict); @@ -505,7 +511,6 @@ static PyObject * Journal_get_data_threshold(Journal *self, void *closure) { size_t cvalue; - PyObject *value; int r; r = sd_journal_get_data_threshold(self->j, &cvalue); @@ -532,11 +537,11 @@ Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) } static PyGetSetDef Journal_getseters[] = { - {"data_threshold", - (getter)Journal_get_data_threshold, - (setter)Journal_set_data_threshold, - "data threshold", - NULL}, + {(char*) "data_threshold", + (getter)Journal_get_data_threshold, + (setter)Journal_set_data_threshold, + (char*) "data threshold", + NULL}, {NULL} }; @@ -617,11 +622,14 @@ static PyModuleDef _reader_module = { }; #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; @@ -659,3 +667,5 @@ init_reader(void) return m; #endif } + +#pragma GCC diagnostic pop From 0d92ee93dcf2f524f31eb2484157209022c68e3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 19 Feb 2013 22:11:02 -0500 Subject: [PATCH 31/41] systemd-python: downgrade _reader.c to C89 --- src/python-systemd/_reader.c | 165 ++++++++++++++++++----------------- 1 file changed, 85 insertions(+), 80 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index c6f29f5e33..d0ccb91dca 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -84,7 +84,7 @@ PyDoc_STRVAR(Journal__doc__, static int Journal_init(Journal *self, PyObject *args, PyObject *keywds) { - int flags = SD_JOURNAL_LOCAL_ONLY; + int flags = SD_JOURNAL_LOCAL_ONLY, r; char *path = NULL; static const char* const kwlist[] = {"flags", "path", NULL}; @@ -92,13 +92,11 @@ Journal_init(Journal *self, PyObject *args, PyObject *keywds) &flags, &path)) return 1; - int r; Py_BEGIN_ALLOW_THREADS - if (path) { + if (path) r = sd_journal_open_directory(&self->j, path, 0); - }else{ + else r = sd_journal_open(&self->j, flags); - } Py_END_ALLOW_THREADS return set_error(r, path, "Invalid flags or path"); @@ -111,26 +109,30 @@ PyDoc_STRVAR(Journal_get_next__doc__, static PyObject * Journal_get_next(Journal *self, PyObject *args) { - int64_t skip=1LL; + PyObject *dict; + const void *msg; + size_t msg_len; + const char *delim_ptr; + PyObject *key, *value, *cur_value, *tmp_list; + + int64_t skip = 1LL, r = -EINVAL; if (! PyArg_ParseTuple(args, "|L", &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); - } Py_END_ALLOW_THREADS set_error(r, NULL, NULL); @@ -139,14 +141,8 @@ Journal_get_next(Journal *self, PyObject *args) else if (r == 0) /* EOF */ return PyDict_New(); - PyObject *dict; dict = PyDict_New(); - const void *msg; - size_t msg_len; - const char *delim_ptr; - PyObject *key, *value, *cur_value, *tmp_list; - SD_JOURNAL_FOREACH_DATA(self->j, msg, msg_len) { delim_ptr = memchr(msg, '=', msg_len); key = unicode_FromStringAndSize(msg, delim_ptr - (const char*) msg); @@ -169,38 +165,44 @@ Journal_get_next(Journal *self, PyObject *args) 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); - key = unicode_FromString("__REALTIME_TIMESTAMP"); - value = PyBytes_FromString(realtime_str); - PyDict_SetItem(dict, key, value); - 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); + key = unicode_FromString("__REALTIME_TIMESTAMP"); + value = PyBytes_FromString(realtime_str); + PyDict_SetItem(dict, key, value); + Py_DECREF(key); + Py_DECREF(value); + } } - 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); - key = unicode_FromString("__MONOTONIC_TIMESTAMP"); - value = PyBytes_FromString(monotonic_str); + { + 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); + key = unicode_FromString("__MONOTONIC_TIMESTAMP"); + value = PyBytes_FromString(monotonic_str); - PyDict_SetItem(dict, key, value); - Py_DECREF(key); - Py_DECREF(value); + PyDict_SetItem(dict, key, value); + Py_DECREF(key); + Py_DECREF(value); + } } - char *cursor; - if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0... - key = unicode_FromString("__CURSOR"); - value = PyBytes_FromString(cursor); - PyDict_SetItem(dict, key, value); - free(cursor); - Py_DECREF(key); - Py_DECREF(value); + { + char *cursor; + if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0... + key = unicode_FromString("__CURSOR"); + value = PyBytes_FromString(cursor); + PyDict_SetItem(dict, key, value); + free(cursor); + Py_DECREF(key); + Py_DECREF(value); + } } return dict; @@ -231,11 +233,10 @@ static PyObject * Journal_add_match(Journal *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#", &match, &match_len)) return NULL; - int r; r = sd_journal_add_match(self->j, match, match_len); set_error(r, NULL, "Invalid match"); if (r < 0) @@ -280,15 +281,16 @@ static PyObject * Journal_seek(Journal *self, PyObject *args, PyObject *keywds) { int64_t offset; - int whence=SEEK_SET; + int whence = SEEK_SET; + PyObject *result = NULL; static const char* const kwlist[] = {"offset", "whence", NULL}; if (!PyArg_ParseTupleAndKeywords(args, keywds, "L|i", (char**) kwlist, &offset, &whence)) return NULL; - PyObject *result=NULL; - if (whence == SEEK_SET){ + switch(whence) { + case SEEK_SET: { int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_head(self->j); @@ -296,14 +298,16 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) if (set_error(r, NULL, NULL)) return NULL; - if (offset > 0LL) { + if (offset > 0LL) result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", (char*) "L", offset); - } - }else if (whence == SEEK_CUR){ + break; + } + case SEEK_CUR: result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", (char*) "L", offset); - }else if (whence == SEEK_END){ + break; + case SEEK_END: { int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_tail(self->j); @@ -311,14 +315,11 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) if (set_error(r, NULL, NULL)) return NULL; - if (offset < 0LL) { - result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", - (char*) "L", offset); - }else{ - result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", - (char*) "L", -1LL); - } - }else{ + result = PyObject_CallMethod((PyObject *)self, (char*) "get_next", + (char*) "L", offset < 0LL ? offset : -1LL); + break; + } + default: PyErr_SetString(PyExc_ValueError, "Invalid value for whence"); } @@ -336,18 +337,18 @@ static PyObject * Journal_seek_realtime(Journal *self, PyObject *args) { double timedouble; - if (! PyArg_ParseTuple(args, "d", &timedouble)) + uint64_t timestamp; + int r; + + if (!PyArg_ParseTuple(args, "d", &timedouble)) return NULL; - uint64_t timestamp; timestamp = (uint64_t) (timedouble * 1.0E6); - if ((int64_t) timestamp < 0LL) { - PyErr_SetString(PyExc_ValueError, "Time must be positive integer"); + PyErr_SetString(PyExc_ValueError, "Time must be a positive integer"); return NULL; } - int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_realtime_usec(self->j, timestamp); Py_END_ALLOW_THREADS @@ -367,10 +368,13 @@ Journal_seek_monotonic(Journal *self, PyObject *args) { double timedouble; char *bootid=NULL; + uint64_t timestamp; + sd_id128_t sd_id; + int r; + if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid)) return NULL; - uint64_t timestamp; timestamp = (uint64_t) (timedouble * 1.0E6); if ((int64_t) timestamp < 0LL) { @@ -378,8 +382,6 @@ Journal_seek_monotonic(Journal *self, PyObject *args) return NULL; } - sd_id128_t sd_id; - int r; if (bootid) { r = sd_id128_from_string(bootid, &sd_id); if (set_error(r, NULL, "Invalid bootid")) @@ -434,10 +436,11 @@ static PyObject * Journal_seek_cursor(Journal *self, PyObject *args) { const char *cursor; + int r; + if (! PyArg_ParseTuple(args, "s", &cursor)) return NULL; - int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_seek_cursor(self->j, cursor); Py_END_ALLOW_THREADS @@ -480,24 +483,26 @@ static PyObject * Journal_query_unique(Journal *self, PyObject *args) { char *query; + int r; + const void *uniq; + size_t uniq_len; + PyObject *value_set, *key, *value; + if (! PyArg_ParseTuple(args, "s", &query)) return NULL; - int r; Py_BEGIN_ALLOW_THREADS r = sd_journal_query_unique(self->j, query); Py_END_ALLOW_THREADS 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); 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)); PySet_Add(value_set, value); @@ -523,15 +528,15 @@ Journal_get_data_threshold(Journal *self, void *closure) static int Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) { + int r; if (value == NULL) { PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold"); return -1; } if (!long_Check(value)){ - PyErr_SetString(PyExc_TypeError, "Data threshold must be int"); + PyErr_SetString(PyExc_TypeError, "Data threshold must be an int"); return -1; } - int r; r = sd_journal_set_data_threshold(self->j, (size_t) long_AsLong(value)); return set_error(r, NULL, NULL); } From 33ed3769b5ef0e50966061bbc023a05e206396b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 19 Feb 2013 22:22:03 -0500 Subject: [PATCH 32/41] systemd-python: indenation and style tweaks --- src/python-systemd/_reader.c | 177 ++++++++++++++++------------------- 1 file changed, 80 insertions(+), 97 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index d0ccb91dca..1bca1160f3 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. @@ -61,28 +61,26 @@ static int set_error(int r, const char* path, const char* invalid_message) { return 1; } -static void -Journal_dealloc(Journal* self) +static void Journal_dealloc(Journal* 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) + "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, r; char *path = NULL; @@ -103,11 +101,10 @@ Journal_init(Journal *self, PyObject *args, PyObject *keywds) } 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) + "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) { PyObject *dict; const void *msg; @@ -116,7 +113,7 @@ Journal_get_next(Journal *self, PyObject *args) PyObject *key, *value, *cur_value, *tmp_list; int64_t skip = 1LL, r = -EINVAL; - if (! PyArg_ParseTuple(args, "|L", &skip)) + if (!PyArg_ParseTuple(args, "|L", &skip)) return NULL; if (skip == 0LL) { @@ -209,14 +206,13 @@ Journal_get_next(Journal *self, PyObject *args) } 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) + "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) { - int64_t skip=1LL; - if (! PyArg_ParseTuple(args, "|L", &skip)) + int64_t skip = 1LL; + if (!PyArg_ParseTuple(args, "|L", &skip)) return NULL; return PyObject_CallMethod((PyObject *)self, (char*) "get_next", @@ -224,13 +220,12 @@ Journal_get_previous(Journal *self, PyObject *args) } 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) + "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 a string of the form \"FIELD=value\"."); +static PyObject* Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) { char *match; int match_len, r; @@ -246,11 +241,10 @@ Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) } 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) + "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) { int r; r = sd_journal_add_disjunction(self->j); @@ -261,24 +255,22 @@ Journal_add_disjunction(Journal *self, PyObject *args) } 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) + "flush_matches() -> None\n\n" + "Clears all current match filters."); +static PyObject* Journal_flush_matches(Journal *self, PyObject *args) { sd_journal_flush_matches(self->j); 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) + "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) { int64_t offset; int whence = SEEK_SET; @@ -330,11 +322,10 @@ Journal_seek(Journal *self, PyObject *args, PyObject *keywds) } 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) + "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) { double timedouble; uint64_t timestamp; @@ -358,21 +349,20 @@ Journal_seek_realtime(Journal *self, PyObject *args) } 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) + "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; + char *bootid = NULL; uint64_t timestamp; sd_id128_t sd_id; int r; - if (! PyArg_ParseTuple(args, "d|z", &timedouble, &bootid)) + if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid)) return NULL; timestamp = (uint64_t) (timedouble * 1.0E6); @@ -403,16 +393,15 @@ Journal_seek_monotonic(Journal *self, PyObject *args) } 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) + "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) { int r; int64_t timeout = 0LL; @@ -430,15 +419,14 @@ Journal_wait(Journal *self, PyObject *args, PyObject *keywds) } 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) + "seek_cursor(cursor) -> None\n\n" + "Seeks to journal entry by given unique reference `cursor`."); +static PyObject* Journal_seek_cursor(Journal *self, PyObject *args) { const char *cursor; int r; - if (! PyArg_ParseTuple(args, "s", &cursor)) + if (!PyArg_ParseTuple(args, "s", &cursor)) return NULL; Py_BEGIN_ALLOW_THREADS @@ -449,15 +437,13 @@ Journal_seek_cursor(Journal *self, PyObject *args) Py_RETURN_NONE; } -static PyObject * -Journal_iter(PyObject *self) +static PyObject* Journal_iter(PyObject *self) { Py_INCREF(self); return self; } -static PyObject * -Journal_iternext(PyObject *self) +static PyObject* Journal_iternext(PyObject *self) { PyObject *dict; Py_ssize_t dict_size; @@ -476,11 +462,10 @@ Journal_iternext(PyObject *self) } 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) + "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) { char *query; int r; @@ -488,7 +473,7 @@ Journal_query_unique(Journal *self, PyObject *args) size_t uniq_len; PyObject *value_set, *key, *value; - if (! PyArg_ParseTuple(args, "s", &query)) + if (!PyArg_ParseTuple(args, "s", &query)) return NULL; Py_BEGIN_ALLOW_THREADS @@ -512,8 +497,7 @@ Journal_query_unique(Journal *self, PyObject *args) return value_set; } -static PyObject * -Journal_get_data_threshold(Journal *self, void *closure) +static PyObject* Journal_get_data_threshold(Journal *self, void *closure) { size_t cvalue; int r; @@ -525,8 +509,7 @@ Journal_get_data_threshold(Journal *self, void *closure) return long_FromSize_t(cvalue); } -static int -Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) +static int Journal_set_data_threshold(Journal *self, PyObject *value, void *closure) { int r; if (value == NULL) { From 2c07646764384545e5303222729d5ff93dec4347 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 19 Feb 2013 23:03:32 -0500 Subject: [PATCH 33/41] systemd-python: polish the docstrings --- src/python-systemd/_reader.c | 54 ++++++++++++++------------ src/python-systemd/id128.c | 8 ++-- src/python-systemd/journal.py | 71 +++++++++++++++++++++-------------- 3 files changed, 76 insertions(+), 57 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 1bca1160f3..207b9e76cb 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -68,10 +68,8 @@ static void Journal_dealloc(Journal* 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" + "Journal([flags][,path]) -> ...\n\n" + "Journal allows filtering and retrieval 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" @@ -79,7 +77,7 @@ PyDoc_STRVAR(Journal__doc__, "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."); + "not relevant."); static int Journal_init(Journal *self, PyObject *args, PyObject *keywds) { int flags = SD_JOURNAL_LOCAL_ONLY, r; @@ -103,7 +101,7 @@ static int Journal_init(Journal *self, PyObject *args, PyObject *keywds) 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."); + "return the `skip`\\-th log entry."); static PyObject* Journal_get_next(Journal *self, PyObject *args) { PyObject *dict; @@ -208,7 +206,7 @@ static PyObject* Journal_get_next(Journal *self, PyObject *args) 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)."); + "will return the -`skip`\\-th log entry. Equivalent to get_next(-skip)."); static PyObject* Journal_get_previous(Journal *self, PyObject *args) { int64_t skip = 1LL; @@ -222,8 +220,8 @@ static PyObject* Journal_get_previous(Journal *self, PyObject *args) 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" + "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* Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) { @@ -242,8 +240,7 @@ static PyObject* Journal_add_match(Journal *self, PyObject *args, PyObject *keyw PyDoc_STRVAR(Journal_add_disjunction__doc__, "add_disjunction() -> None\n\n" - "Once called, all matches before and after are combined in logical\n" - "OR."); + "Inserts a logical OR between matches added before and afterwards."); static PyObject* Journal_add_disjunction(Journal *self, PyObject *args) { int r; @@ -256,7 +253,7 @@ static PyObject* Journal_add_disjunction(Journal *self, PyObject *args) PyDoc_STRVAR(Journal_flush_matches__doc__, "flush_matches() -> None\n\n" - "Clears all current match filters."); + "Clear all current match filters."); static PyObject* Journal_flush_matches(Journal *self, PyObject *args) { sd_journal_flush_matches(self->j); @@ -265,7 +262,7 @@ static PyObject* Journal_flush_matches(Journal *self, PyObject *args) PyDoc_STRVAR(Journal_seek__doc__, "seek(offset[, whence]) -> None\n\n" - "Seek through journal by `offset` number of entries. Argument\n" + "Jump `offset` entries in the journal. 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" @@ -393,11 +390,11 @@ static PyObject* Journal_seek_monotonic(Journal *self, PyObject *args) } 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" + "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" "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."); @@ -420,7 +417,7 @@ static PyObject* Journal_wait(Journal *self, PyObject *args, PyObject *keywds) PyDoc_STRVAR(Journal_seek_cursor__doc__, "seek_cursor(cursor) -> None\n\n" - "Seeks to journal entry by given unique reference `cursor`."); + "Seek to journal entry by given unique reference `cursor`."); static PyObject* Journal_seek_cursor(Journal *self, PyObject *args) { const char *cursor; @@ -463,8 +460,8 @@ static PyObject* Journal_iternext(PyObject *self) 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."); + "Return a set of unique values appearing in journal for the\n" + "given `field`. Note this does not respect any journal matches."); static PyObject* Journal_query_unique(Journal *self, PyObject *args) { char *query; @@ -497,6 +494,11 @@ static PyObject* Journal_query_unique(Journal *self, PyObject *args) return value_set; } +PyDoc_STRVAR(data_threshold__doc__, + "Threshold for field size truncation.\n\n" + "Fields longer than this will be truncated to the threshold size.\n" + "Defaults to 64Kb."); + static PyObject* Journal_get_data_threshold(Journal *self, void *closure) { size_t cvalue; @@ -528,7 +530,7 @@ static PyGetSetDef Journal_getseters[] = { {(char*) "data_threshold", (getter)Journal_get_data_threshold, (setter)Journal_set_data_threshold, - (char*) "data threshold", + (char*) data_threshold__doc__, NULL}, {NULL} }; @@ -600,11 +602,14 @@ static PyTypeObject JournalType = { PyType_GenericNew, /* tp_new */ }; +#define SUMMARY \ + "Module that reads the systemd journal similar to journalctl." + #if PY_MAJOR_VERSION >= 3 static PyModuleDef _reader_module = { PyModuleDef_HEAD_INIT, "_reader", - "Module that reads systemd journal similar to journalctl.", + SUMMARY, -1, NULL, NULL, NULL, NULL, NULL }; @@ -636,8 +641,7 @@ init_reader(void) if (m == NULL) return NULL; #else - m = Py_InitModule3("_reader", NULL, - "Module that reads systemd journal similar to journalctl."); + m = Py_InitModule3("_reader", NULL, SUMMARY); if (m == NULL) return; #endif diff --git a/src/python-systemd/id128.c b/src/python-systemd/id128.c index f82b0afb99..04db786b66 100644 --- a/src/python-systemd/id128.c +++ b/src/python-systemd/id128.c @@ -35,24 +35,24 @@ static void cleanup_Py_DECREFp(PyObject **p) { PyDoc_STRVAR(module__doc__, "Python interface to the libsystemd-id128 library.\n\n" "Provides SD_MESSAGE_* constants and functions to query and generate\n" - "128bit unique identifiers." + "128-bit unique identifiers." ); PyDoc_STRVAR(randomize__doc__, "randomize() -> UUID\n\n" - "Return a new random 128bit unique identifier.\n" + "Return a new random 128-bit unique identifier.\n" "Wraps sd_id128_randomize(3)." ); PyDoc_STRVAR(get_machine__doc__, "get_machine() -> UUID\n\n" - "Return a 128bit unique identifier for this machine.\n" + "Return a 128-bit unique identifier for this machine.\n" "Wraps sd_id128_get_machine(3)." ); PyDoc_STRVAR(get_boot__doc__, "get_boot() -> UUID\n\n" - "Return a 128bit unique identifier for this boot.\n" + "Return a 128-bit unique identifier for this boot.\n" "Wraps sd_id128_get_boot(3)." ); diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index d63722b91d..892a56ff23 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -90,12 +90,11 @@ class Journal(_Journal): >>> myjournal.add_boot_match(journal.CURRENT_BOOT) >>> myjournal.add_loglevel_matches(journal.LOG_ERR) >>> myjournal.add_match(_SYSTEMD_UNIT="systemd-udevd.service") - >>> from __future__ import print_function >>> for entry in myjournal: ... print(entry['MESSAGE']) - See man page "systemd.journal-fields" for more info on - typical fields found in the journal. + See systemd.journal-fields(7) for more info on typical fields + found in the journal. """ def __init__(self, converters=None, flags=LOCAL_ONLY, path=None): """Creates instance of Journal, which allows filtering and @@ -123,7 +122,7 @@ class Journal(_Journal): self.converters.update(converters) def _convert_field(self, key, value): - """ Convert value based on callable from self.converters + """Convert value based on callable from self.converters based of field/key""" try: result = self.converters[key](value) @@ -137,7 +136,7 @@ class Journal(_Journal): return result def _convert_entry(self, entry): - """ Convert entire journal entry utilising _covert_field""" + """Convert entire journal entry utilising _covert_field""" result = {} for key, value in entry.items(): if isinstance(value, list): @@ -149,44 +148,56 @@ class Journal(_Journal): def add_match(self, *args, **kwargs): """Add one or more matches to the filter journal log entries. All matches of different field are combined in a logical AND, - and matches of the smae field are automatically combined in a + and matches of the same field are automatically combined in a logical OR. - Matches can be passed as strings of form "field=value", or - keyword arguments field="value".""" + Matches can be passed as strings of form "FIELD=value", or + keyword arguments FIELD="value". + """ args = list(args) args.extend(_make_line(key, val) for key, val in kwargs.items()) for arg in args: super(Journal, self).add_match(arg) def get_next(self, skip=1): - """Return dictionary of the next log entry. Optional skip value - will return the `skip`th log entry. - Returned will be journal entry dictionary processed with - converters.""" + """Return the next log entry as a dictionary of fields. + + Optional skip value will return the `skip`\-th log entry. + + Entries will be processed with converters specified during + Journal creation. + """ return self._convert_entry( super(Journal, self).get_next(skip)) def query_unique(self, field): - """Returns a set of unique values in journal for given `field`, - processed with converters. - Note this does not respect any journal matches.""" + """Return unique values appearing in the Journal for given `field`. + + Note this does not respect any journal matches. + + Entries will be processed with converters specified during + Journal creation. + """ return set(self._convert_field(field, value) for value in super(Journal, self).query_unique(field)) def seek_realtime(self, realtime): - """Seek to nearest matching journal entry to `realtime`. - Argument `realtime` can must be either an integer unix timestamp - or datetime.datetime instance.""" + """Seek to a matching journal entry nearest to `realtime` time. + + Argument `realtime` must be either an integer unix timestamp + or datetime.datetime instance. + """ if isinstance(realtime, _datetime.datetime): realtime = float(realtime.strftime("%s.%f")) return super(Journal, self).seek_realtime(realtime) def seek_monotonic(self, monotonic, bootid=None): - """Seek to nearest matching journal entry to `monotonic`. - Argument `monotonic` is a timestamp from boot in either seconds - or a datetime.timedelta instance. - Argument `bootid` is a string or UUID representing which boot the - monotonic time is reference to. Defaults to current bootid.""" + """Seek to a matching journal entry nearest to `monotonic` time. + + Argument `monotonic` is a timestamp from boot in either + seconds or a datetime.timedelta instance. Argument `bootid` + is a string or UUID representing which boot the monotonic time + is reference to. Defaults to current bootid. + """ if isinstance(monotonic, _datetime.timedelta): monotonic = monotonic.totalseconds() if isinstance(bootid, _uuid.UUID): @@ -194,7 +205,8 @@ class Journal(_Journal): return super(Journal, self).seek_monotonic(monotonic, bootid) def log_level(self, level): - """Sets maximum log `level` by setting matches for PRIORITY.""" + """Set maximum log `level` by setting matches for PRIORITY. + """ if 0 <= level <= 7: for i in range(level+1): self.add_match(PRIORITY="%s" % i) @@ -202,10 +214,13 @@ class Journal(_Journal): raise ValueError("Log level must be 0 <= level <= 7") def messageid_match(self, messageid): - """Sets match filter for log entries for specified `messageid`. - `messageid` can be string or UUID instance. - Standard message IDs can be found in systemd.id128 - Equivalent to add_match(MESSAGE_ID=`messageid`).""" + """Add match for log entries with specified `messageid`. + + `messageid` can be string of hexadicimal digits or a UUID + instance. Standard message IDs can be found in systemd.id128. + + Equivalent to add_match(MESSAGE_ID=`messageid`). + """ if isinstance(messageid, _uuid.UUID): messageid = messageid.get_hex() self.add_match(MESSAGE_ID=messageid) From 603c0b7b14a6b59a87ede20b2fdb6765e773c0ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 20 Feb 2013 09:20:35 +0100 Subject: [PATCH 34/41] build-sys: make sphinx support uncoditional It needs to be invoked explicitly, so there's no need to check explicitly. --- Makefile.am | 3 +-- configure.ac | 15 +-------------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/Makefile.am b/Makefile.am index fc7e8c429e..dfe70a570d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3453,13 +3453,12 @@ BUILT_SOURCES += \ src/python-systemd/id128-constants.h endif -if ENABLE_SPHINX PAPER = $(shell cat /etc/papersize 2>/dev/null || echo a4) SPHINXOPTS = -D latex_paper_size=$(PAPER) sphinx-%: + $(AM_V_at)test -n "$(SPHINX_BUILD)" || { echo " *** sphinx-build is not available"; exit 1; } $(AM_V_GEN)PYTHONPATH=$(DESTDIR)$(pyexecdir) $(SPHINX_BUILD) -b $* $(SPHINXOPTS) $(top_srcdir)/src/python-systemd/docs $(top_builddir)/man/python-systemd/ $(AM_V_at)echo Output has been generated in $(abs_top_builddir)/man/python-systemd/ -endif # ------------------------------------------------------------------------------ SED_PROCESS = \ diff --git a/configure.ac b/configure.ac index 5737c65257..397ce82e20 100644 --- a/configure.ac +++ b/configure.ac @@ -176,23 +176,11 @@ AS_IF([test "x$with_python" != "xno"], [ PYTHON_LIBS="`$PYTHON_CONFIG --ldflags`" AC_SUBST(PYTHON_CFLAGS) AC_SUBST(PYTHON_LIBS) + AC_PATH_PROGS(SPHINX_BUILD, sphinx-build-${PYTHON_VERSION} sphinx-build) ]) ]) AM_CONDITIONAL([HAVE_PYTHON_DEVEL], [test "$have_python_devel" = "yes"]) -AC_ARG_ENABLE(sphinx, AS_HELP_STRING([--enable-sphinx], - [use sphinx to build documentation for python-systemd])) -AS_IF([test "x$enable_sphinx" = "xyes"], [ - AC_PATH_PROGS(SPHINX_BUILD, sphinx-build-${PYTHON_VERSION} sphinx-build) - AS_IF([test -z "$SPHINX_BUILD"], [ - AC_MSG_ERROR([*** sphinx build requested, but sphinx-build not found]) - ]) - AS_IF([test "x$have_python_devel" != "xyes"], [ - AC_MSG_ERROR([*** sphinx build requested, but python support not enabled]) - ]) -]) -AM_CONDITIONAL(ENABLE_SPHINX, [test "x$enable_sphinx" = "xyes"]) - # ------------------------------------------------------------------------------ AC_SEARCH_LIBS([mq_open], [rt], [], [AC_MSG_ERROR([*** POSIX RT library not found])]) @@ -915,7 +903,6 @@ AC_MSG_RESULT([ Python Headers: ${have_python_devel} man pages: ${have_manpages} gtk-doc: ${enable_gtk_doc} - sphinx documentation: ${enable_sphinx} Split /usr: ${enable_split_usr} SysV compatibility: ${SYSTEM_SYSV_COMPAT} From a244c095c2fc18095ec793b52a72f3a40d9973e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 21 Feb 2013 14:23:57 +0100 Subject: [PATCH 35/41] systemd-python: use PyModule_AddObject in id128 --- src/python-systemd/id128.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python-systemd/id128.c b/src/python-systemd/id128.c index 04db786b66..42f247d108 100644 --- a/src/python-systemd/id128.c +++ b/src/python-systemd/id128.c @@ -106,13 +106,13 @@ static PyMethodDef methods[] = { }; static int add_id(PyObject *module, const char* name, sd_id128_t id) { - PyObject _cleanup_Py_DECREF_ *obj; + PyObject *obj; obj = make_uuid(id); if (!obj) return -1; - return PyObject_SetAttrString(module, name, obj); + return PyModule_AddObject(module, name, obj); } #pragma GCC diagnostic push From df2795d9f94ec1e17a5b780261b21ac8d9f22101 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 21 Feb 2013 15:07:39 +0100 Subject: [PATCH 36/41] systemd-python: document attributes In id128 it would be better to add everything automatically, but sphinx cannot do this right now. --- src/python-systemd/docs/id128.rst | 31 +++++++++++++++++++++++++++++ src/python-systemd/docs/journal.rst | 23 +++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/python-systemd/docs/id128.rst b/src/python-systemd/docs/id128.rst index e817d803a7..12c28f362b 100644 --- a/src/python-systemd/docs/id128.rst +++ b/src/python-systemd/docs/id128.rst @@ -5,3 +5,34 @@ :members: :undoc-members: :inherited-members: + + .. autoattribute:: systemd.id128.SD_MESSAGE_COREDUMP + .. autoattribute:: systemd.id128.SD_MESSAGE_FORWARD_SYSLOG_MISSED + .. autoattribute:: systemd.id128.SD_MESSAGE_HIBERNATE_KEY + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_DROPPED + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_MISSED + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_START + .. autoattribute:: systemd.id128.SD_MESSAGE_JOURNAL_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_LID_CLOSED + .. autoattribute:: systemd.id128.SD_MESSAGE_LID_OPENED + .. autoattribute:: systemd.id128.SD_MESSAGE_OVERMOUNTING + .. autoattribute:: systemd.id128.SD_MESSAGE_POWER_KEY + .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_START + .. autoattribute:: systemd.id128.SD_MESSAGE_SEAT_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_START + .. autoattribute:: systemd.id128.SD_MESSAGE_SESSION_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_SHUTDOWN + .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_START + .. autoattribute:: systemd.id128.SD_MESSAGE_SLEEP_STOP + .. autoattribute:: systemd.id128.SD_MESSAGE_SPAWN_FAILED + .. autoattribute:: systemd.id128.SD_MESSAGE_STARTUP_FINISHED + .. autoattribute:: systemd.id128.SD_MESSAGE_SUSPEND_KEY + .. autoattribute:: systemd.id128.SD_MESSAGE_TIMEZONE_CHANGE + .. autoattribute:: systemd.id128.SD_MESSAGE_TIME_CHANGE + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_FAILED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_RELOADING + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STARTING + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPED + .. autoattribute:: systemd.id128.SD_MESSAGE_UNIT_STOPPING diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst index 89728a2ecb..9d627ce3ed 100644 --- a/src/python-systemd/docs/journal.rst +++ b/src/python-systemd/docs/journal.rst @@ -22,3 +22,26 @@ Accessing the Journal :inherited-members: .. automethod:: __init__ + +.. autoattribute:: systemd.journal.DEFAULT_CONVERTERS + +Whence constants +~~~~~~~~~~~~~~~~ + +.. autoattribute:: systemd.journal.SEEK_SET +.. autoattribute:: systemd.journal.SEEK_CUR +.. autoattribute:: systemd.journal.SEEK_END + +Journal access types +~~~~~~~~~~~~~~~~~~~~ + +.. autoattribute:: systemd.journal.LOCAL_ONLY +.. autoattribute:: systemd.journal.RUNTIME_ONLY +.. autoattribute:: systemd.journal.SYSTEM_ONLY + +Journal event types +~~~~~~~~~~~~~~~~~~~ + +.. autoattribute:: systemd.journal.NOP +.. autoattribute:: systemd.journal.APPEND +.. autoattribute:: systemd.journal.INVALIDATE From 5c083fa815d7357d107e76ffb9a5161956c3aec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 21 Feb 2013 15:08:01 +0100 Subject: [PATCH 37/41] systemd-python: hide ChainMap import --- src/python-systemd/journal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 892a56ff23..4d71564c65 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -28,7 +28,7 @@ import os as _os from os import SEEK_SET, SEEK_CUR, SEEK_END import logging as _logging if _sys.version_info >= (3,): - from collections import ChainMap + from collections import ChainMap as _ChainMap from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) from ._journal import sendv, stream_fd @@ -112,7 +112,7 @@ class Journal(_Journal): """ super(Journal, self).__init__(flags, path) if _sys.version_info >= (3,3): - self.converters = ChainMap() + self.converters = _ChainMap() if converters is not None: self.converters.maps.append(converters) self.converters.maps.append(DEFAULT_CONVERTERS) From d426d8c8632ba0be408f8b6b56514b4598abab74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 21 Feb 2013 18:10:08 +0100 Subject: [PATCH 38/41] build-sys: upload python documentation to freedesktop.org --- Makefile.am | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/Makefile.am b/Makefile.am index dfe70a570d..4ff0cff8bb 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3457,9 +3457,15 @@ PAPER = $(shell cat /etc/papersize 2>/dev/null || echo a4) SPHINXOPTS = -D latex_paper_size=$(PAPER) sphinx-%: $(AM_V_at)test -n "$(SPHINX_BUILD)" || { echo " *** sphinx-build is not available"; exit 1; } - $(AM_V_GEN)PYTHONPATH=$(DESTDIR)$(pyexecdir) $(SPHINX_BUILD) -b $* $(SPHINXOPTS) $(top_srcdir)/src/python-systemd/docs $(top_builddir)/man/python-systemd/ + $(AM_V_GEN)PYTHONPATH=$(DESTDIR)$(pyexecdir) LD_LIBRARY_PATH=$(DESTDIR)$(libdir) $(SPHINX_BUILD) -b $* $(SPHINXOPTS) $(top_srcdir)/src/python-systemd/docs $(top_builddir)/man/python-systemd/ $(AM_V_at)echo Output has been generated in $(abs_top_builddir)/man/python-systemd/ +destdir-sphinx: all + dir="`mktemp -d /tmp/systemd-install.XXXXXX`" && \ + $(MAKE) DESTDIR="$$dir" install && \ + $(MAKE) DESTDIR="$$dir" sphinx-html && \ + rm -rf "$$dir" + # ------------------------------------------------------------------------------ SED_PROCESS = \ $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ @@ -3780,17 +3786,19 @@ upload: all distcheck scp systemd-$(VERSION).tar.xz fdo:/srv/www.freedesktop.org/www/software/systemd/ scp man/*.html tango:public/systemd-man/ -doc-sync: all +www_target = www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd +doc-sync: all destdir-sphinx gtkdoc-rebase --html-dir=docs/libudev/html --online - rsync -av --delete docs/libudev/html/ --omit-dir-times www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd/libudev/ + rsync -av --delete docs/libudev/html/ --omit-dir-times $(www_target)/libudev/ gtkdoc-rebase --html-dir=docs/gudev/html --online - rsync -av --delete docs/gudev/html/ --omit-dir-times www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd/gudev/ - rsync -av --delete-excluded --include="*.html" --exclude="*" --omit-dir-times man/ www.freedesktop.org:/srv/www.freedesktop.org/www/software/systemd/man/ + rsync -av --delete docs/gudev/html/ --omit-dir-times $(www_target)/gudev/ + rsync -av --delete-excluded --include="*.html" --exclude="*" --omit-dir-times man/ $(www_target)/man/ + rsync -av --delete --omit-dir-times man/python-systemd/ $(www_target)/man/python-systemd/ git-tag: git tag "v$(VERSION)" -m "systemd $(VERSION)" install-tree: all rm -rf $(abs_srcdir)/install-tree - make install DESTDIR=$(abs_srcdir)/install-tree + $(MAKE) install DESTDIR=$(abs_srcdir)/install-tree tree $(abs_srcdir)/install-tree From 6a6633a16a510b40c6ad30cd0858699619384a44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 22 Feb 2013 13:33:06 +0100 Subject: [PATCH 39/41] python-systemd: check all errors and use automatic cleanup __REALTIME_TIMESTAMP and __MONOTONIC_TIMESTAMP return ints. It doesn't make sense to convert to string, just to convert back to a number later on. Also try to follow systemd rules for indentation. --- Makefile.am | 13 +- src/python-systemd/_reader.c | 291 ++++++++++++++++++++-------------- src/python-systemd/id128.c | 21 ++- src/python-systemd/journal.py | 6 +- src/python-systemd/pyutil.c | 30 ++++ src/python-systemd/pyutil.h | 29 ++++ 6 files changed, 261 insertions(+), 129 deletions(-) create mode 100644 src/python-systemd/pyutil.c create mode 100644 src/python-systemd/pyutil.h diff --git a/Makefile.am b/Makefile.am index 4ff0cff8bb..f1c2ce05b5 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3404,7 +3404,9 @@ _journal_la_LIBADD = \ id128_la_SOURCES = \ src/python-systemd/id128.c \ - src/python-systemd/id128-constants.h + src/python-systemd/id128-constants.h \ + src/python-systemd/pyutil.c \ + src/python-systemd/pyutil.h id128_la_CFLAGS = \ $(AM_CFLAGS) \ @@ -3423,7 +3425,9 @@ id128_la_LIBADD = \ libsystemd-id128.la _reader_la_SOURCES = \ - src/python-systemd/_reader.c + src/python-systemd/_reader.c \ + src/python-systemd/pyutil.c \ + src/python-systemd/pyutil.h _reader_la_CFLAGS = \ $(AM_CFLAGS) \ @@ -3439,7 +3443,8 @@ _reader_la_LDFLAGS = \ _reader_la_LIBADD = \ $(PYTHON_LIBS) \ libsystemd-journal.la \ - libsystemd-id128.la + libsystemd-id128.la \ + libsystemd-shared.la dist_pkgpyexec_PYTHON = \ src/python-systemd/journal.py \ @@ -3447,7 +3452,7 @@ dist_pkgpyexec_PYTHON = \ src/python-systemd/id128-constants.h: src/systemd/sd-messages.h Makefile $(AM_V_at)$(MKDIR_P) $(dir $@) - $(AM_V_GEN)$(SED) -n -r 's/,//g; s/#define (SD_MESSAGE_[A-Z0-9_]+)\s.*/add_id(m, "\1", \1);/p' <$< >$@ + $(AM_V_GEN)$(SED) -n -r 's/,//g; s/#define (SD_MESSAGE_[A-Z0-9_]+)\s.*/add_id(m, "\1", \1) JOINER/p' <$< >$@ BUILT_SOURCES += \ src/python-systemd/id128-constants.h diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 207b9e76cb..9262c89e4d 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -18,11 +18,17 @@ 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 "pyutil.h" +#include "macro.h" +#include "util.h" #if PY_MAJOR_VERSION >=3 # define unicode_FromStringAndSize PyUnicode_FromStringAndSize @@ -107,10 +113,9 @@ static PyObject* Journal_get_next(Journal *self, PyObject *args) PyObject *dict; const void *msg; size_t msg_len; - const char *delim_ptr; - PyObject *key, *value, *cur_value, *tmp_list; + int64_t skip = 1LL; + int r; - int64_t skip = 1LL, r = -EINVAL; if (!PyArg_ParseTuple(args, "|L", &skip)) return NULL; @@ -128,6 +133,8 @@ static PyObject* Journal_get_next(Journal *self, PyObject *args) r = sd_journal_next_skip(self->j, skip); else if (skip < -1LL) r = sd_journal_previous_skip(self->j, -skip); + else + assert_not_reached("should not be here"); Py_END_ALLOW_THREADS set_error(r, NULL, NULL); @@ -137,70 +144,128 @@ static PyObject* Journal_get_next(Journal *self, PyObject *args) return PyDict_New(); 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; + 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); - value = PyBytes_FromStringAndSize(delim_ptr + 1, (const char*) msg + msg_len - (delim_ptr + 1) ); + if (!key) + goto error; + + value = PyBytes_FromStringAndSize( + delim_ptr + 1, + (const char*) msg + msg_len - (delim_ptr + 1) ); + if (!value) + 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); + r = PyList_Append(cur_value, value); + if (r < 0) + goto error; + } else { + PyObject _cleanup_Py_DECREF_ *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; + PyDict_SetItem(dict, key, tmp_list); - Py_DECREF(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); } { + PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL; 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); - key = unicode_FromString("__REALTIME_TIMESTAMP"); - value = PyBytes_FromString(realtime_str); - PyDict_SetItem(dict, key, value); - Py_DECREF(key); - Py_DECREF(value); - } + + r = sd_journal_get_realtime_usec(self->j, &realtime); + if (set_error(r, NULL, NULL)) + goto error; + + 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; + + if (PyDict_SetItem(dict, key, value)) + goto error; } { + PyObject _cleanup_Py_DECREF_ *key = NULL, *value = 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); - key = unicode_FromString("__MONOTONIC_TIMESTAMP"); - value = PyBytes_FromString(monotonic_str); - PyDict_SetItem(dict, key, value); - Py_DECREF(key); - Py_DECREF(value); - } + r = sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id); + if (set_error(r, NULL, NULL)) + goto error; + + key = unicode_FromString("__MONOTONIC_TIMESTAMP"); + if (!key) + goto error; + + assert_cc(sizeof(unsigned long long) == sizeof(monotonic)); + value = PyLong_FromUnsignedLongLong(monotonic); + if (!value) + goto error; + + if (PyDict_SetItem(dict, key, value)) + goto error; } { - char *cursor; - if (sd_journal_get_cursor(self->j, &cursor) > 0) { //Should return 0... - key = unicode_FromString("__CURSOR"); - value = PyBytes_FromString(cursor); - PyDict_SetItem(dict, key, value); - free(cursor); - Py_DECREF(key); - Py_DECREF(value); - } + 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; } return dict; +error: + Py_DECREF(dict); + return NULL; } PyDoc_STRVAR(Journal_get_previous__doc__, @@ -451,7 +516,7 @@ static PyObject* Journal_iternext(PyObject *self) dict_size = PyDict_Size(dict); if ((int64_t) dict_size > 0LL) { return dict; - }else{ + } else { Py_DECREF(dict); PyErr_SetNone(PyExc_StopIteration); return NULL; @@ -486,7 +551,9 @@ static PyObject* Journal_query_unique(Journal *self, PyObject *args) 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); } @@ -495,7 +562,7 @@ static PyObject* Journal_query_unique(Journal *self, PyObject *args) } PyDoc_STRVAR(data_threshold__doc__, - "Threshold for field size truncation.\n\n" + "Threshold for field size truncation in bytes.\n\n" "Fields longer than this will be truncated to the threshold size.\n" "Defaults to 64Kb."); @@ -515,7 +582,7 @@ static int Journal_set_data_threshold(Journal *self, PyObject *value, void *clos { int r; if (value == NULL) { - PyErr_SetString(PyExc_TypeError, "Cannot delete data threshold"); + PyErr_SetString(PyExc_AttributeError, "Cannot delete data threshold"); return -1; } if (!long_Check(value)){ @@ -528,78 +595,67 @@ static int Journal_set_data_threshold(Journal *self, PyObject *value, void *clos static PyGetSetDef Journal_getseters[] = { {(char*) "data_threshold", - (getter)Journal_get_data_threshold, - (setter)Journal_set_data_threshold, + (getter) Journal_get_data_threshold, + (setter) Journal_set_data_threshold, (char*) data_threshold__doc__, NULL}, {NULL} }; 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__}, + {"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 PyTypeObject JournalType = { 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 */ + "_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 */ }; #define SUMMARY \ @@ -647,13 +703,18 @@ init_reader(void) #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); + if (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)) { +#if PY_MAJOR_VERSION >= 3 + Py_DECREF(m); + return NULL; +#endif + } #if PY_MAJOR_VERSION >= 3 return m; diff --git a/src/python-systemd/id128.c b/src/python-systemd/id128.c index 42f247d108..a6711a5bd5 100644 --- a/src/python-systemd/id128.c +++ b/src/python-systemd/id128.c @@ -19,18 +19,13 @@ along with systemd; If not, see . ***/ +#include + #include #include -#define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp))) - -static void cleanup_Py_DECREFp(PyObject **p) { - if (!*p) - return; - - Py_DECREF(*p); -} +#include "pyutil.h" PyDoc_STRVAR(module__doc__, "Python interface to the libsystemd-id128 library.\n\n" @@ -127,7 +122,10 @@ PyMODINIT_FUNC initid128(void) { if (m == NULL) return; + /* a series of lines like 'add_id() ;' follow */ +#define JOINER ; #include "id128-constants.h" +#undef JOINER } #else @@ -147,7 +145,14 @@ PyMODINIT_FUNC PyInit_id128(void) { if (m == NULL) return NULL; + if ( /* a series of lines like 'add_id() ||' follow */ +#define JOINER || #include "id128-constants.h" +#undef JOINER + false) { + Py_DECREF(m); + return NULL; + } return m; } diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index 4d71564c65..d94934cfa8 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -19,6 +19,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with systemd; If not, see . +from __future__ import division + import sys as _sys import datetime as _datetime import functools as _functools @@ -36,8 +38,8 @@ from ._reader import (_Journal, NOP, APPEND, INVALIDATE, LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) from . import id128 as _id128 -_MONOTONIC_CONVERTER = lambda x: _datetime.timedelta(microseconds=float(x)) -_REALTIME_CONVERTER = lambda x: _datetime.datetime.fromtimestamp(float(x)/1E6) +_MONOTONIC_CONVERTER = lambda x: _datetime.timedelta(microseconds=x) +_REALTIME_CONVERTER = lambda x: _datetime.datetime.fromtimestamp(x / 1E6) DEFAULT_CONVERTERS = { 'MESSAGE_ID': _uuid.UUID, '_MACHINE_ID': _uuid.UUID, diff --git a/src/python-systemd/pyutil.c b/src/python-systemd/pyutil.c new file mode 100644 index 0000000000..79065a11c0 --- /dev/null +++ b/src/python-systemd/pyutil.c @@ -0,0 +1,30 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include "pyutil.h" + +void cleanup_Py_DECREFp(PyObject **p) { + if (!*p) + return; + + Py_DECREF(*p); +} diff --git a/src/python-systemd/pyutil.h b/src/python-systemd/pyutil.h new file mode 100644 index 0000000000..3b7bc580df --- /dev/null +++ b/src/python-systemd/pyutil.h @@ -0,0 +1,29 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2013 Zbigniew Jędrzejewski-Szmek + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#ifndef Py_TYPE +/* avoid duplication warnings from errors in Python 2.7 headers */ +# include +#endif + +void cleanup_Py_DECREFp(PyObject **p); + +#define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp))) From 86e3d32a1d52cd2ccba184e8375da8828914d3ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Sat, 23 Feb 2013 01:11:36 +0100 Subject: [PATCH 40/41] systemd-python: return both parts of sd_journal_get_monotonic_usec In Python 3, a named tuple is used. In Python 2, a simple tuple is used. In either case, the pair is (timestamp, bootid). --- src/python-systemd/_reader.c | 74 ++++++++++++++++++++++++----- src/python-systemd/docs/journal.rst | 2 + src/python-systemd/journal.py | 8 +++- 3 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 9262c89e4d..7645cb9a84 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -67,6 +67,26 @@ static int set_error(int r, const char* path, const char* invalid_message) { return 1; } +#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"}, + {NULL, NULL} +}; + +static PyStructSequence_Desc Monotonic_desc = { + (char*) "journal.Monotonic", + MonotonicType__doc__, + MonotonicType_fields, + 2, +}; +#endif + static void Journal_dealloc(Journal* self) { sd_journal_close(self->j); @@ -221,22 +241,37 @@ static PyObject* Journal_get_next(Journal *self, PyObject *args) } { - PyObject _cleanup_Py_DECREF_ *key = NULL, *value = NULL; - sd_id128_t sd_id; + PyObject _cleanup_Py_DECREF_ + *key = NULL, *timestamp = NULL, *bytes = NULL, *value = NULL; + sd_id128_t id; uint64_t monotonic; - r = sd_journal_get_monotonic_usec(self->j, &monotonic, &sd_id); + 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(monotonic)); key = unicode_FromString("__MONOTONIC_TIMESTAMP"); - if (!key) + 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; - assert_cc(sizeof(unsigned long long) == sizeof(monotonic)); - value = PyLong_FromUnsignedLongLong(monotonic); - if (!value) - goto error; + Py_INCREF(timestamp); + Py_INCREF(bytes); + +#if PY_MAJOR_VERSION >= 3 + PyStructSequence_SET_ITEM(value, 0, timestamp); + PyStructSequence_SET_ITEM(value, 1, bytes); +#else + PyTuple_SET_ITEM(value, 0, timestamp); + PyTuple_SET_ITEM(value, 1, bytes); +#endif if (PyDict_SetItem(dict, key, value)) goto error; @@ -421,7 +456,7 @@ static PyObject* Journal_seek_monotonic(Journal *self, PyObject *args) double timedouble; char *bootid = NULL; uint64_t timestamp; - sd_id128_t sd_id; + sd_id128_t id; int r; if (!PyArg_ParseTuple(args, "d|z", &timedouble, &bootid)) @@ -435,19 +470,19 @@ static PyObject* Journal_seek_monotonic(Journal *self, PyObject *args) } if (bootid) { - r = sd_id128_from_string(bootid, &sd_id); + r = sd_id128_from_string(bootid, &id); if (set_error(r, NULL, "Invalid bootid")) return NULL; } else { Py_BEGIN_ALLOW_THREADS - r = sd_id128_get_boot(&sd_id); + 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 (set_error(r, NULL, NULL)) return NULL; @@ -671,6 +706,10 @@ static PyModuleDef _reader_module = { }; #endif +#if PY_MAJOR_VERSION >= 3 +static bool initialized = false; +#endif + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-prototypes" @@ -696,6 +735,11 @@ init_reader(void) m = PyModule_Create(&_reader_module); if (m == NULL) return NULL; + + if (!initialized) { + PyStructSequence_InitType(&MonotonicType, &Monotonic_desc); + initialized = true; + } #else m = Py_InitModule3("_reader", NULL, SUMMARY); if (m == NULL) @@ -703,7 +747,13 @@ init_reader(void) #endif Py_INCREF(&JournalType); +#if PY_MAJOR_VERSION >= 3 + Py_INCREF(&MonotonicType); +#endif if (PyModule_AddObject(m, "_Journal", (PyObject *) &JournalType) || +#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) || diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst index 9d627ce3ed..38ab57e45b 100644 --- a/src/python-systemd/docs/journal.rst +++ b/src/python-systemd/docs/journal.rst @@ -23,6 +23,8 @@ Accessing the Journal .. automethod:: __init__ +.. autoclass:: Monotonic + .. autoattribute:: systemd.journal.DEFAULT_CONVERTERS Whence constants diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index d94934cfa8..a5641e98b2 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -38,7 +38,13 @@ from ._reader import (_Journal, NOP, APPEND, INVALIDATE, LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) from . import id128 as _id128 -_MONOTONIC_CONVERTER = lambda x: _datetime.timedelta(microseconds=x) +if _sys.version_info >= (3,): + from ._reader import Monotonic +else: + Monotonic = tuple + +_MONOTONIC_CONVERTER = lambda p: Monotonic((_datetime.timedelta(microseconds=p[0]), + _uuid.UUID(bytes=p[1]))) _REALTIME_CONVERTER = lambda x: _datetime.datetime.fromtimestamp(x / 1E6) DEFAULT_CONVERTERS = { 'MESSAGE_ID': _uuid.UUID, From 7f41820b07ccb8a6da5552c466818fcce3d085bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 28 Feb 2013 19:32:31 -0500 Subject: [PATCH 41/41] python-systemd: rename Journal to Reader It seems inevitable that we'll also grow a writing interface, and then it'll be cumbersome to have a "Journal" for reading, and a "Writer" for writing. --- src/python-systemd/_reader.c | 122 ++++++++++++++-------------- src/python-systemd/docs/journal.rst | 4 +- src/python-systemd/journal.py | 28 +++---- 3 files changed, 77 insertions(+), 77 deletions(-) diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 7645cb9a84..7f200d53f6 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -50,8 +50,8 @@ typedef struct { PyObject_HEAD sd_journal *j; -} Journal; -static PyTypeObject JournalType; +} Reader; +static PyTypeObject ReaderType; static int set_error(int r, const char* path, const char* invalid_message) { if (r >= 0) @@ -87,15 +87,15 @@ static PyStructSequence_Desc Monotonic_desc = { }; #endif -static void Journal_dealloc(Journal* self) +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\n" - "Journal allows filtering and retrieval of Journal entries.\n" +PyDoc_STRVAR(Reader__doc__, + "Reader([flags][,path]) -> ...\n\n" + "Reader allows filtering and retrieval 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" @@ -104,7 +104,7 @@ PyDoc_STRVAR(Journal__doc__, "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) +static int Reader_init(Reader *self, PyObject *args, PyObject *keywds) { int flags = SD_JOURNAL_LOCAL_ONLY, r; char *path = NULL; @@ -124,11 +124,11 @@ static int Journal_init(Journal *self, PyObject *args, PyObject *keywds) return set_error(r, path, "Invalid flags or path"); } -PyDoc_STRVAR(Journal_get_next__doc__, +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* Journal_get_next(Journal *self, PyObject *args) +static PyObject* Reader_get_next(Reader *self, PyObject *args) { PyObject *dict; const void *msg; @@ -303,11 +303,11 @@ error: return NULL; } -PyDoc_STRVAR(Journal_get_previous__doc__, +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* Journal_get_previous(Journal *self, PyObject *args) +static PyObject* Reader_get_previous(Reader *self, PyObject *args) { int64_t skip = 1LL; if (!PyArg_ParseTuple(args, "|L", &skip)) @@ -317,13 +317,13 @@ static PyObject* Journal_get_previous(Journal *self, PyObject *args) (char*) "L", -skip); } -PyDoc_STRVAR(Journal_add_match__doc__, +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* Journal_add_match(Journal *self, PyObject *args, PyObject *keywds) +static PyObject* Reader_add_match(Reader *self, PyObject *args, PyObject *keywds) { char *match; int match_len, r; @@ -338,10 +338,10 @@ static PyObject* Journal_add_match(Journal *self, PyObject *args, PyObject *keyw Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_add_disjunction__doc__, +PyDoc_STRVAR(Reader_add_disjunction__doc__, "add_disjunction() -> None\n\n" "Inserts a logical OR between matches added before and afterwards."); -static PyObject* Journal_add_disjunction(Journal *self, PyObject *args) +static PyObject* Reader_add_disjunction(Reader *self, PyObject *args) { int r; r = sd_journal_add_disjunction(self->j); @@ -351,23 +351,23 @@ static PyObject* Journal_add_disjunction(Journal *self, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_flush_matches__doc__, +PyDoc_STRVAR(Reader_flush_matches__doc__, "flush_matches() -> None\n\n" "Clear all current match filters."); -static PyObject* Journal_flush_matches(Journal *self, PyObject *args) +static PyObject* Reader_flush_matches(Reader *self, PyObject *args) { sd_journal_flush_matches(self->j); Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_seek__doc__, +PyDoc_STRVAR(Reader_seek__doc__, "seek(offset[, whence]) -> None\n\n" "Jump `offset` entries in the journal. 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) +static PyObject* Reader_seek(Reader *self, PyObject *args, PyObject *keywds) { int64_t offset; int whence = SEEK_SET; @@ -418,11 +418,11 @@ static PyObject* Journal_seek(Journal *self, PyObject *args, PyObject *keywds) Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_seek_realtime__doc__, +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."); -static PyObject* Journal_seek_realtime(Journal *self, PyObject *args) +static PyObject* Reader_seek_realtime(Reader *self, PyObject *args) { double timedouble; uint64_t timestamp; @@ -445,13 +445,13 @@ static PyObject* Journal_seek_realtime(Journal *self, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_seek_monotonic__doc__, +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" "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) +static PyObject* Reader_seek_monotonic(Reader *self, PyObject *args) { double timedouble; char *bootid = NULL; @@ -489,7 +489,7 @@ static PyObject* Journal_seek_monotonic(Journal *self, PyObject *args) Py_RETURN_NONE; } -PyDoc_STRVAR(Journal_wait__doc__, +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" @@ -498,7 +498,7 @@ PyDoc_STRVAR(Journal_wait__doc__, "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) +static PyObject* Reader_wait(Reader *self, PyObject *args, PyObject *keywds) { int r; int64_t timeout = 0LL; @@ -515,10 +515,10 @@ static PyObject* Journal_wait(Journal *self, PyObject *args, PyObject *keywds) return long_FromLong(r); } -PyDoc_STRVAR(Journal_seek_cursor__doc__, +PyDoc_STRVAR(Reader_seek_cursor__doc__, "seek_cursor(cursor) -> None\n\n" "Seek to journal entry by given unique reference `cursor`."); -static PyObject* Journal_seek_cursor(Journal *self, PyObject *args) +static PyObject* Reader_seek_cursor(Reader *self, PyObject *args) { const char *cursor; int r; @@ -534,13 +534,13 @@ static PyObject* Journal_seek_cursor(Journal *self, PyObject *args) Py_RETURN_NONE; } -static PyObject* Journal_iter(PyObject *self) +static PyObject* Reader_iter(PyObject *self) { Py_INCREF(self); return self; } -static PyObject* Journal_iternext(PyObject *self) +static PyObject* Reader_iternext(PyObject *self) { PyObject *dict; Py_ssize_t dict_size; @@ -558,11 +558,11 @@ static PyObject* Journal_iternext(PyObject *self) } } -PyDoc_STRVAR(Journal_query_unique__doc__, +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* Journal_query_unique(Journal *self, PyObject *args) +static PyObject* Reader_query_unique(Reader *self, PyObject *args) { char *query; int r; @@ -601,7 +601,7 @@ PyDoc_STRVAR(data_threshold__doc__, "Fields longer than this will be truncated to the threshold size.\n" "Defaults to 64Kb."); -static PyObject* Journal_get_data_threshold(Journal *self, void *closure) +static PyObject* Reader_get_data_threshold(Reader *self, void *closure) { size_t cvalue; int r; @@ -613,7 +613,7 @@ static PyObject* Journal_get_data_threshold(Journal *self, void *closure) 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) { @@ -628,36 +628,36 @@ static int Journal_set_data_threshold(Journal *self, PyObject *value, void *clos return set_error(r, NULL, NULL); } -static PyGetSetDef Journal_getseters[] = { +static PyGetSetDef Reader_getseters[] = { {(char*) "data_threshold", - (getter) Journal_get_data_threshold, - (setter) Journal_set_data_threshold, + (getter) Reader_get_data_threshold, + (setter) Reader_set_data_threshold, (char*) data_threshold__doc__, NULL}, {NULL} }; -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__}, +static PyMethodDef Reader_methods[] = { + {"get_next", (PyCFunction) Reader_get_next, METH_VARARGS, Reader_get_next__doc__}, + {"get_previous", (PyCFunction) Reader_get_previous, METH_VARARGS, Reader_get_previous__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__}, + {"seek", (PyCFunction) Reader_seek, METH_VARARGS | METH_KEYWORDS, Reader_seek__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__}, + {"wait", (PyCFunction) Reader_wait, METH_VARARGS, Reader_wait__doc__}, + {"seek_cursor", (PyCFunction) Reader_seek_cursor, METH_VARARGS, Reader_seek_cursor__doc__}, + {"query_unique", (PyCFunction) Reader_query_unique, METH_VARARGS, Reader_query_unique__doc__}, {NULL} /* Sentinel */ }; -static PyTypeObject JournalType = { +static PyTypeObject ReaderType = { PyVarObject_HEAD_INIT(NULL, 0) - "_reader._Journal", /*tp_name*/ - sizeof(Journal), /*tp_basicsize*/ + "_reader._Reader", /*tp_name*/ + sizeof(Reader), /*tp_basicsize*/ 0, /*tp_itemsize*/ - (destructor)Journal_dealloc, /*tp_dealloc*/ + (destructor)Reader_dealloc, /*tp_dealloc*/ 0, /*tp_print*/ 0, /*tp_getattr*/ 0, /*tp_setattr*/ @@ -673,22 +673,22 @@ static PyTypeObject JournalType = { 0, /*tp_setattro*/ 0, /*tp_as_buffer*/ Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ - Journal__doc__, /* tp_doc */ + Reader__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 */ + Reader_iter, /* tp_iter */ + Reader_iternext, /* tp_iternext */ + Reader_methods, /* tp_methods */ 0, /* tp_members */ - Journal_getseters, /* tp_getset */ + Reader_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 */ + (initproc) Reader_init, /* tp_init */ 0, /* tp_alloc */ PyType_GenericNew, /* tp_new */ }; @@ -724,7 +724,7 @@ init_reader(void) PyDateTime_IMPORT; - if (PyType_Ready(&JournalType) < 0) + if (PyType_Ready(&ReaderType) < 0) #if PY_MAJOR_VERSION >= 3 return NULL; #else @@ -746,11 +746,11 @@ init_reader(void) return; #endif - Py_INCREF(&JournalType); + Py_INCREF(&ReaderType); #if PY_MAJOR_VERSION >= 3 Py_INCREF(&MonotonicType); #endif - if (PyModule_AddObject(m, "_Journal", (PyObject *) &JournalType) || + if (PyModule_AddObject(m, "_Reader", (PyObject *) &ReaderType) || #if PY_MAJOR_VERSION >= 3 PyModule_AddObject(m, "Monotonic", (PyObject*) &MonotonicType) || #endif diff --git a/src/python-systemd/docs/journal.rst b/src/python-systemd/docs/journal.rst index 38ab57e45b..78b831afff 100644 --- a/src/python-systemd/docs/journal.rst +++ b/src/python-systemd/docs/journal.rst @@ -13,11 +13,11 @@ Accessing the Journal --------------------- -.. autoclass:: _Journal +.. autoclass:: _Reader :undoc-members: :inherited-members: -.. autoclass:: Journal +.. autoclass:: Reader :undoc-members: :inherited-members: diff --git a/src/python-systemd/journal.py b/src/python-systemd/journal.py index a5641e98b2..23e1d65747 100644 --- a/src/python-systemd/journal.py +++ b/src/python-systemd/journal.py @@ -34,7 +34,7 @@ if _sys.version_info >= (3,): from syslog import (LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG) from ._journal import sendv, stream_fd -from ._reader import (_Journal, NOP, APPEND, INVALIDATE, +from ._reader import (_Reader, NOP, APPEND, INVALIDATE, LOCAL_ONLY, RUNTIME_ONLY, SYSTEM_ONLY) from . import id128 as _id128 @@ -86,15 +86,15 @@ if _sys.version_info >= (3,): else: _convert_unicode = _functools.partial(unicode, encoding='utf-8') -class Journal(_Journal): - """Journal allows the access and filtering of systemd journal +class Reader(_Reader): + """Reader allows the access and filtering of systemd journal entries. Note that in order to access the system journal, a non-root user must be in the `adm` group. Example usage to print out all error or higher level messages for systemd-udevd for the boot: - >>> myjournal = journal.Journal() + >>> myjournal = journal.Reader() >>> myjournal.add_boot_match(journal.CURRENT_BOOT) >>> myjournal.add_loglevel_matches(journal.LOG_ERR) >>> myjournal.add_match(_SYSTEMD_UNIT="systemd-udevd.service") @@ -105,7 +105,7 @@ class Journal(_Journal): found in the journal. """ def __init__(self, converters=None, flags=LOCAL_ONLY, path=None): - """Creates instance of Journal, which allows filtering and + """Create an instance of Reader, which allows filtering and return of journal entries. Argument `converters` is a dictionary which updates the DEFAULT_CONVERTERS to convert journal field values. @@ -118,7 +118,7 @@ class Journal(_Journal): currently flags are ignored when `path` is present as they are currently not relevant. """ - super(Journal, self).__init__(flags, path) + super(Reader, self).__init__(flags, path) if _sys.version_info >= (3,3): self.converters = _ChainMap() if converters is not None: @@ -164,7 +164,7 @@ class Journal(_Journal): args = list(args) args.extend(_make_line(key, val) for key, val in kwargs.items()) for arg in args: - super(Journal, self).add_match(arg) + super(Reader, self).add_match(arg) def get_next(self, skip=1): """Return the next log entry as a dictionary of fields. @@ -172,21 +172,21 @@ class Journal(_Journal): Optional skip value will return the `skip`\-th log entry. Entries will be processed with converters specified during - Journal creation. + Reader creation. """ return self._convert_entry( - super(Journal, self).get_next(skip)) + super(Reader, self).get_next(skip)) def query_unique(self, field): - """Return unique values appearing in the Journal for given `field`. + """Return unique values appearing in the journal for given `field`. Note this does not respect any journal matches. Entries will be processed with converters specified during - Journal creation. + Reader creation. """ return set(self._convert_field(field, value) - for value in super(Journal, self).query_unique(field)) + for value in super(Reader, self).query_unique(field)) def seek_realtime(self, realtime): """Seek to a matching journal entry nearest to `realtime` time. @@ -196,7 +196,7 @@ class Journal(_Journal): """ if isinstance(realtime, _datetime.datetime): realtime = float(realtime.strftime("%s.%f")) - return super(Journal, self).seek_realtime(realtime) + return super(Reader, self).seek_realtime(realtime) def seek_monotonic(self, monotonic, bootid=None): """Seek to a matching journal entry nearest to `monotonic` time. @@ -210,7 +210,7 @@ class Journal(_Journal): monotonic = monotonic.totalseconds() if isinstance(bootid, _uuid.UUID): bootid = bootid.get_hex() - return super(Journal, self).seek_monotonic(monotonic, bootid) + return super(Reader, self).seek_monotonic(monotonic, bootid) def log_level(self, level): """Set maximum log `level` by setting matches for PRIORITY.