From 927e96326c195ad39a45b091f98e9c635f400982 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Tue, 5 Feb 2013 21:44:46 -0500 Subject: python: add systemd.id128 module uuid.UUIDs are utilized to hold UUID values. --- Makefile.am | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) (limited to 'Makefile.am') 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 # ------------------------------------------------------------------------------ -- cgit v1.2.3-54-g00ecf From 9015fa646e04fc3cb180bea24c33d34edbb48ed7 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Sat, 9 Feb 2013 15:37:35 -0500 Subject: 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 (limited to 'Makefile.am') 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): -- cgit v1.2.3-54-g00ecf From c4e9b5b557ba956e316933c31bbefa8b48fa3f93 Mon Sep 17 00:00:00 2001 From: Steven Hiscocks Date: Fri, 8 Feb 2013 19:41:21 +0000 Subject: 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 (limited to 'Makefile.am') 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): -- cgit v1.2.3-54-g00ecf From 603c0b7b14a6b59a87ede20b2fdb6765e773c0ed Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Wed, 20 Feb 2013 09:20:35 +0100 Subject: 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(-) (limited to 'Makefile.am') 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} -- cgit v1.2.3-54-g00ecf From d426d8c8632ba0be408f8b6b56514b4598abab74 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Thu, 21 Feb 2013 18:10:08 +0100 Subject: build-sys: upload python documentation to freedesktop.org --- Makefile.am | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) (limited to 'Makefile.am') 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 -- cgit v1.2.3-54-g00ecf From 6a6633a16a510b40c6ad30cd0858699619384a44 Mon Sep 17 00:00:00 2001 From: Zbigniew Jędrzejewski-Szmek Date: Fri, 22 Feb 2013 13:33:06 +0100 Subject: 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 | 293 +++++++++++++++++++++++++----------------- 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, 262 insertions(+), 130 deletions(-) create mode 100644 src/python-systemd/pyutil.c create mode 100644 src/python-systemd/pyutil.h (limited to 'Makefile.am') 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))) -- cgit v1.2.3-54-g00ecf