From b04c8c83e8d5670b0923c7cd7d6ea622b0187289 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 15 Mar 2013 19:01:10 -0400 Subject: [PATCH] systemd-python: add systemd.daemon wrapping sd-daemon Please see the documentation (e.g. pydoc3 systemd.daemon) for full description. As usual, systemd._daemon wraps the raw interface, while systemd.daemon provides the more pythonic API. sd_listen_fds, sd_booted, sd_is_fifo, sd_is_socket, sd_is_socket_unix, sd_is_socket_inet, sd_is_mq, and SD_LISTEN_FDS_START are currently wrapped. --- Makefile.am | 23 ++ TODO | 1 + src/python-systemd/_daemon.c | 323 +++++++++++++++++++++++++++++ src/python-systemd/_reader.c | 17 -- src/python-systemd/daemon.py | 53 +++++ src/python-systemd/docs/daemon.rst | 16 ++ src/python-systemd/docs/index.rst | 1 + src/python-systemd/pyutil.h | 19 ++ 8 files changed, 436 insertions(+), 17 deletions(-) create mode 100644 src/python-systemd/_daemon.c create mode 100644 src/python-systemd/daemon.py create mode 100644 src/python-systemd/docs/daemon.rst diff --git a/Makefile.am b/Makefile.am index f687eca0c..7e9cdfdb2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3484,6 +3484,7 @@ if HAVE_PYTHON_DEVEL pkgpyexec_LTLIBRARIES = \ _journal.la \ id128.la \ + _daemon.la \ _reader.la _journal_la_SOURCES = \ @@ -3526,6 +3527,27 @@ id128_la_LIBADD = \ $(PYTHON_LIBS) \ libsystemd-id128.la +_daemon_la_SOURCES = \ + src/python-systemd/_daemon.c \ + src/python-systemd/pyutil.c \ + src/python-systemd/pyutil.h + +_daemon_la_CFLAGS = \ + $(AM_CFLAGS) \ + -fvisibility=default \ + $(PYTHON_CFLAGS) \ + -I$(top_builddir)/src/python-systemd + +_daemon_la_LDFLAGS = \ + $(AM_LDFLAGS) \ + -shared \ + -module \ + -avoid-version + +_daemon_la_LIBADD = \ + $(PYTHON_LIBS) \ + libsystemd-daemon.la + _reader_la_SOURCES = \ src/python-systemd/_reader.c \ src/python-systemd/pyutil.c \ @@ -3550,6 +3572,7 @@ _reader_la_LIBADD = \ dist_pkgpyexec_PYTHON = \ src/python-systemd/journal.py \ + src/python-systemd/daemon.py \ src/python-systemd/__init__.py src/python-systemd/id128-constants.h: src/systemd/sd-messages.h Makefile diff --git a/TODO b/TODO index fcafba0df..addf32e47 100644 --- a/TODO +++ b/TODO @@ -587,6 +587,7 @@ Features: be just "return self->j != NULL") - figure out a simple way to wait for journal events in a way that works with ^C + - add documentation to systemd.daemon External: diff --git a/src/python-systemd/_daemon.c b/src/python-systemd/_daemon.c new file mode 100644 index 000000000..8f93d91b4 --- /dev/null +++ b/src/python-systemd/_daemon.c @@ -0,0 +1,323 @@ +/*-*- 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 . +***/ + +#define PY_SSIZE_T_CLEAN +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wredundant-decls" +#include +#pragma GCC diagnostic pop + +#include +#include +#include + +#include +#include "pyutil.h" + +PyDoc_STRVAR(module__doc__, + "Python interface to the libsystemd-daemon library.\n\n" + "Provides _listen_fds, notify, booted, and is_* functions\n" + "which wrap sd_listen_fds, sd_notify, sd_booted, sd_is_* and\n" + "useful for socket activation and checking if the system is\n" + "running under systemd." +); + +static PyObject* set_error(int r, const char* invalid_message) { + assert (r < 0); + + 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_SetFromErrno(PyExc_OSError); + } + + return NULL; +} + + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 +static int Unicode_FSConverter(PyObject* obj, void *_result) { + PyObject **result = _result; + + assert(result); + + if (!obj) + /* cleanup: we don't return Py_CLEANUP_SUPPORTED, so + * we can assume that it was PyUnicode_FSConverter. */ + return PyUnicode_FSConverter(obj, result); + + if (obj == Py_None) { + *result = NULL; + return 1; + } + + return PyUnicode_FSConverter(obj, result); +} +#endif + + +PyDoc_STRVAR(booted__doc__, + "booted() -> bool\n\n" + "Return True iff this system is running under systemd.\n" + "Wraps sd_daemon_booted(3)." +); + +static PyObject* booted(PyObject *self, PyObject *args) { + int r; + assert(args == NULL); + + r = sd_booted(); + if (r < 0) + return set_error(r, NULL); + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(listen_fds__doc__, + "_listen_fds(unset_environment=True) -> int\n\n" + "Return the number of descriptors passed to this process by the init system\n" + "as part of the socket-based activation logic.\n" + "Wraps sd_listen_fds(3)." +); + +static PyObject* listen_fds(PyObject *self, PyObject *args) { + int r; + int unset = true; + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 + if (!PyArg_ParseTuple(args, "|p:_listen_fds", &unset)) + return NULL; +#else + PyObject *obj = NULL; + if (!PyArg_ParseTuple(args, "|O:_listen_fds", &obj)) + return NULL; + if (obj != NULL) + unset = PyObject_IsTrue(obj); + if (unset < 0) + return NULL; +#endif + + r = sd_listen_fds(unset); + if (r < 0) + return set_error(r, NULL); + + return long_FromLong(r); +} + +PyDoc_STRVAR(is_fifo__doc__, + "_is_fifo(fd, path) -> bool\n\n" + "Returns True iff the descriptor refers to a FIFO or a pipe.\n" + "Wraps sd_is_fifo(3)." +); + + +static PyObject* is_fifo(PyObject *self, PyObject *args) { + int r; + int fd; + const char *path = NULL; + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + if (!PyArg_ParseTuple(args, "i|O&:_is_fifo", + &fd, Unicode_FSConverter, &path)) + return NULL; +#else + if (!PyArg_ParseTuple(args, "i|z:_is_fifo", &fd, &path)) + return NULL; +#endif + + r = sd_is_fifo(fd, path); + if (r < 0) + return set_error(r, NULL); + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(is_mq__doc__, + "_is_mq(fd, path) -> bool\n\n" + "Returns True iff the descriptor refers to a POSIX message queue.\n" + "Wraps sd_is_mq(3)." +); + +static PyObject* is_mq(PyObject *self, PyObject *args) { + int r; + int fd; + const char *path = NULL; + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + if (!PyArg_ParseTuple(args, "i|O&:_is_mq", + &fd, Unicode_FSConverter, &path)) + return NULL; +#else + if (!PyArg_ParseTuple(args, "i|z:_is_mq", &fd, &path)) + return NULL; +#endif + + r = sd_is_mq(fd, path); + if (r < 0) + return set_error(r, NULL); + + return PyBool_FromLong(r); +} + + + +PyDoc_STRVAR(is_socket__doc__, + "_is_socket(fd, family=AF_UNSPEC, type=0, listening=-1) -> bool\n\n" + "Returns True iff the descriptor refers to a socket.\n" + "Wraps sd_is_socket(3).\n\n" + "Constants for `family` are defined in the socket module." +); + +static PyObject* is_socket(PyObject *self, PyObject *args) { + int r; + int fd, family = AF_UNSPEC, type = 0, listening = -1; + + if (!PyArg_ParseTuple(args, "i|iii:_is_socket", + &fd, &family, &type, &listening)) + return NULL; + + r = sd_is_socket(fd, family, type, listening); + if (r < 0) + return set_error(r, NULL); + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(is_socket_inet__doc__, + "_is_socket_inet(fd, family=AF_UNSPEC, type=0, listening=-1, port=0) -> bool\n\n" + "Wraps sd_is_socket_inet(3).\n\n" + "Constants for `family` are defined in the socket module." +); + +static PyObject* is_socket_inet(PyObject *self, PyObject *args) { + int r; + int fd, family = AF_UNSPEC, type = 0, listening = -1, port = 0; + + if (!PyArg_ParseTuple(args, "i|iiii:_is_socket_inet", + &fd, &family, &type, &listening, &port)) + return NULL; + + if (port < 0 || port > INT16_MAX) + return set_error(-EINVAL, "port must fit into uint16_t"); + + r = sd_is_socket_inet(fd, family, type, listening, (uint16_t) port); + if (r < 0) + return set_error(r, NULL); + + return PyBool_FromLong(r); +} + + +PyDoc_STRVAR(is_socket_unix__doc__, + "_is_socket_unix(fd, type, listening, path) -> bool\n\n" + "Wraps sd_is_socket_unix(3)." +); + +static PyObject* is_socket_unix(PyObject *self, PyObject *args) { + int r; + int fd, type = 0, listening = -1; + char* path = NULL; + Py_ssize_t length = 0; + +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 1 + PyObject _cleanup_Py_DECREF_ *_path = NULL; + if (!PyArg_ParseTuple(args, "i|iiO&:_is_socket_unix", + &fd, &type, &listening, Unicode_FSConverter, &_path)) + return NULL; + if (_path) { + assert(PyBytes_Check(_path)); + if (PyBytes_AsStringAndSize(_path, &path, &length)) + return NULL; + } +#else + if (!PyArg_ParseTuple(args, "i|iiz#:_is_socket_unix", + &fd, &type, &listening, &path, &length)) + return NULL; +#endif + + r = sd_is_socket_unix(fd, type, listening, path, length); + if (r < 0) + return set_error(r, NULL); + + return PyBool_FromLong(r); +} + + +static PyMethodDef methods[] = { + { "booted", booted, METH_NOARGS, booted__doc__}, + { "_listen_fds", listen_fds, METH_VARARGS, listen_fds__doc__}, + { "_is_fifo", is_fifo, METH_VARARGS, is_fifo__doc__}, + { "_is_mq", is_mq, METH_VARARGS, is_mq__doc__}, + { "_is_socket", is_socket, METH_VARARGS, is_socket__doc__}, + { "_is_socket_inet", is_socket_inet, METH_VARARGS, is_socket_inet__doc__}, + { "_is_socket_unix", is_socket_unix, METH_VARARGS, is_socket_unix__doc__}, + { NULL, NULL, 0, NULL } /* Sentinel */ +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-prototypes" + +#if PY_MAJOR_VERSION < 3 + +PyMODINIT_FUNC init_daemon(void) { + PyObject *m; + + m = Py_InitModule3("_daemon", methods, module__doc__); + if (m == NULL) + return; + + PyModule_AddIntConstant(m, "LISTEN_FDS_START", SD_LISTEN_FDS_START); +} + +#else + +static struct PyModuleDef module = { + PyModuleDef_HEAD_INIT, + "_daemon", /* name of module */ + module__doc__, /* module documentation, may be NULL */ + 0, /* size of per-interpreter state of the module */ + methods +}; + +PyMODINIT_FUNC PyInit__daemon(void) { + PyObject *m; + + m = PyModule_Create(&module); + if (m == NULL) + return NULL; + + if (PyModule_AddIntConstant(m, "LISTEN_FDS_START", SD_LISTEN_FDS_START)) { + Py_DECREF(m); + return NULL; + } + + return m; +} + +#endif + +#pragma GCC diagnostic pop diff --git a/src/python-systemd/_reader.c b/src/python-systemd/_reader.c index 160ab69a3..96634a19c 100644 --- a/src/python-systemd/_reader.c +++ b/src/python-systemd/_reader.c @@ -30,23 +30,6 @@ #include "macro.h" #include "util.h" -#if PY_MAJOR_VERSION >=3 -# define unicode_FromStringAndSize PyUnicode_FromStringAndSize -# define unicode_FromString PyUnicode_FromString -# define long_FromLong PyLong_FromLong -# define long_FromSize_t PyLong_FromSize_t -# define long_Check PyLong_Check -# define long_AsLong PyLong_AsLong -#else -/* Python 3 type naming convention is used */ -# define unicode_FromStringAndSize PyString_FromStringAndSize -# define unicode_FromString PyString_FromString -# define long_FromLong PyInt_FromLong -# define long_FromSize_t PyInt_FromSize_t -# define long_Check PyInt_Check -# define long_AsLong PyInt_AsLong -#endif - typedef struct { PyObject_HEAD sd_journal *j; diff --git a/src/python-systemd/daemon.py b/src/python-systemd/daemon.py new file mode 100644 index 000000000..4a0220443 --- /dev/null +++ b/src/python-systemd/daemon.py @@ -0,0 +1,53 @@ +from ._daemon import (booted, + _listen_fds, + _is_fifo, + _is_socket, + _is_socket_inet, + _is_socket_unix, + _is_mq, + LISTEN_FDS_START) +from socket import AF_UNSPEC as _AF_UNSPEC + +def _convert_fileobj(fileobj): + try: + return fileobj.fileno() + except AttributeError: + return fileobj + +def is_fifo(fileobj, path=None): + fd = _convert_fileobj(fileobj) + return _is_fifo(fd, path) + +def is_socket(fileobj, family=_AF_UNSPEC, type=0, listening=-1): + fd = _convert_fileobj(fileobj) + return _is_socket(fd, family, type, listening) + +def is_socket_inet(fileobj, family=_AF_UNSPEC, type=0, listening=-1, port=0): + fd = _convert_fileobj(fileobj) + return _is_socket_inet(fd, family, type, listening) + +def is_socket_unix(fileobj, type=0, listening=-1, path=None): + fd = _convert_fileobj(fileobj) + return _is_socket_unix(fd, type, listening, path) + +def is_mq(fileobj, path=None): + fd = _convert_fileobj(fileobj) + return _is_mq(fd, path) + +def listen_fds(unset_environment=True): + """Return a list of socket activated descriptors + + Example:: + + (in primary window) + $ systemd-activate -l 2000 python3 -c \\ + 'from systemd.daemon import listen_fds; print(listen_fds())' + (in another window) + $ telnet localhost 2000 + (in primary window) + ... + Execing python3 (...) + [3] + """ + num = _listen_fds(unset_environment) + return list(range(LISTEN_FDS_START, LISTEN_FDS_START + num)) diff --git a/src/python-systemd/docs/daemon.rst b/src/python-systemd/docs/daemon.rst new file mode 100644 index 000000000..72280ca2f --- /dev/null +++ b/src/python-systemd/docs/daemon.rst @@ -0,0 +1,16 @@ +`systemd.daemon` module +======================= + +.. automodule:: systemd.daemon + :members: + :undoc-members: + :inherited-members: + + .. autoattribute:: systemd.daemon.LISTEN_FDS_START + + .. autofunction:: _listen_fds + .. autofunction:: _is_fifo + .. autofunction:: _is_socket + .. autofunction:: _is_socket_unix + .. autofunction:: _is_socket_inet + .. autofunction:: _is_mq diff --git a/src/python-systemd/docs/index.rst b/src/python-systemd/docs/index.rst index f04d5a181..8a94d0750 100644 --- a/src/python-systemd/docs/index.rst +++ b/src/python-systemd/docs/index.rst @@ -13,6 +13,7 @@ Contents: journal id128 + daemon Indices and tables ================== diff --git a/src/python-systemd/pyutil.h b/src/python-systemd/pyutil.h index 3b7bc580d..2163fda9e 100644 --- a/src/python-systemd/pyutil.h +++ b/src/python-systemd/pyutil.h @@ -1,5 +1,7 @@ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#pragma once + /*** This file is part of systemd. @@ -27,3 +29,20 @@ void cleanup_Py_DECREFp(PyObject **p); #define _cleanup_Py_DECREF_ __attribute__((cleanup(cleanup_Py_DECREFp))) + +#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 -- 2.30.2