From b1608daf27a38f223afa4dda1930d4999c183178 Mon Sep 17 00:00:00 2001 From: Marcel Waldvogel Date: Sun, 24 Jun 2018 11:58:15 +0200 Subject: Added support for listen_fds_with_names() --- NEWS | 3 ++ systemd/_daemon.c | 81 +++++++++++++++++++++++++++++++++++++++++++-- systemd/daemon.py | 22 ++++++++++++ systemd/test/test_daemon.py | 42 ++++++++++++++++++++++- 4 files changed, 145 insertions(+), 3 deletions(-) diff --git a/NEWS b/NEWS index b6d208d..9cf35f4 100644 --- a/NEWS +++ b/NEWS @@ -5,6 +5,9 @@ CHANGES WITH 235: * Adapt the rename of systemd-activate to systemd-socket-activate performed in systemd 230 + * Support for the new sd_listen_fds_with_names added in systemd 227 + is added. + CHANGES WITH 234: * Support for the new sd_is_socket_sockaddr added in systemd 233 diff --git a/systemd/_daemon.c b/systemd/_daemon.c index f41095a..9119f57 100644 --- a/systemd/_daemon.c +++ b/systemd/_daemon.c @@ -41,14 +41,18 @@ # define HAVE_PID_NOTIFY_WITH_FDS #endif +#if LIBSYSTEMD_VERSION >= 227 +# define HAVE_SD_LISTEN_FDS_WITH_NAMES +#endif + #if LIBSYSTEMD_VERSION >= 233 # define HAVE_IS_SOCKET_SOCKADDR #endif 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" + "Provides _listen_fds*, notify, booted, and is_* functions\n" + "which wrap sd_listen_fds*, sd_notify, sd_booted, sd_is_*;\n" "useful for socket activation and checking if the system is\n" "running under systemd." ); @@ -204,6 +208,77 @@ static PyObject* listen_fds(PyObject *self, PyObject *args, PyObject *keywds) { return long_FromLong(r); } +PyDoc_STRVAR(listen_fds_with_names__doc__, + "_listen_fds_with_names(unset_environment=True) -> (int, str...)\n\n" + "Return the number of descriptors passed to this process by the init system\n" + "and their names as part of the socket-based activation logic.\n" + "Wraps sd_listen_fds_with_names(3).\n" + "Raises RunTimeError if compiled under systemd < 227." +); + +static void free_names(char **names) { + if (names == NULL) + return; + for (char **n = names; *n != NULL; n++) + free(*n); + free(names); +} +static PyObject* listen_fds_with_names(PyObject *self, PyObject *args, PyObject *keywds) { + int r; + int unset = false; + char **names = NULL; + PyObject *tpl, *item; + + static const char* const kwlist[] = {"unset_environment", NULL}; +#if PY_MAJOR_VERSION >=3 && PY_MINOR_VERSION >= 3 + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|p:_listen_fds_with_names", + (char**) kwlist, &unset)) + return NULL; +#else + PyObject *obj = NULL; + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|O:_listen_fds_with_names", + (char**) kwlist, &obj)) + return NULL; + if (obj != NULL) + unset = PyObject_IsTrue(obj); + if (unset < 0) + return NULL; +#endif + +#ifdef HAVE_SD_LISTEN_FDS_WITH_NAMES + r = sd_listen_fds_with_names(unset, &names); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + tpl = PyTuple_New(r+1); + if (tpl == NULL) + return NULL; + + item = long_FromLong(r); + if (item == NULL) { + Py_DECREF(tpl); + return NULL; + } + if (PyTuple_SetItem(tpl, 0, item) < 0) { + Py_DECREF(tpl); + return NULL; + } + for (int i = 0; i < r && names[i] != NULL; i++) { + item = unicode_FromString(names[i]); + if (PyTuple_SetItem(tpl, 1+i, item) < 0) { + Py_DECREF(tpl); + free_names(names); + return NULL; + } + } + free_names(names); + return tpl; +#else /* !HAVE_SD_LISTEN_FDS_WITH_NAMES */ + set_error(-ENOSYS, NULL, "Compiled without support for sd_listen_fds_with_names"); + return NULL; +#endif /* HAVE_SD_LISTEN_FDS_WITH_NAMES */ +} + 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" @@ -411,6 +486,8 @@ static PyMethodDef methods[] = { { "booted", booted, METH_NOARGS, booted__doc__}, { "notify", (PyCFunction) notify, METH_VARARGS | METH_KEYWORDS, notify__doc__}, { "_listen_fds", (PyCFunction) listen_fds, METH_VARARGS | METH_KEYWORDS, listen_fds__doc__}, + { "_listen_fds_with_names", (PyCFunction) listen_fds_with_names, + METH_VARARGS | METH_KEYWORDS, listen_fds_with_names__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__}, diff --git a/systemd/daemon.py b/systemd/daemon.py index 217b595..168e55d 100644 --- a/systemd/daemon.py +++ b/systemd/daemon.py @@ -4,6 +4,7 @@ from ._daemon import (__version__, booted, notify, _listen_fds, + _listen_fds_with_names, _is_fifo, _is_socket, _is_socket_inet, @@ -69,3 +70,24 @@ def listen_fds(unset_environment=True): """ num = _listen_fds(unset_environment) return list(range(LISTEN_FDS_START, LISTEN_FDS_START + num)) + +def listen_fds_with_names(unset_environment=True): + """Return a dictionary of socket activated descriptors as {fd: name} + + Example:: + + (in primary window) + $ systemd-socket-activate -l 2000 -l 4000 --fdname=2K:4K python3 -c \\ + 'from systemd.daemon import listen_fds_with_names; print(listen_fds_with_names())' + (in another window) + $ telnet localhost 2000 + (in primary window) + ... + Execing python3 (...) + [3] + """ + composite = _listen_fds_with_names(unset_environment) + retval = {} + for i in range(0, composite[0]): + retval[i+LISTEN_FDS_START] = composite[1+i] + return retval diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index 9f4db7d..ea680a1 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -11,7 +11,7 @@ from systemd.daemon import (booted, is_socket_unix, _is_socket_unix, is_socket_sockaddr, _is_socket_sockaddr, is_mq, _is_mq, - listen_fds, + listen_fds, listen_fds_with_names, notify) import pytest @@ -256,6 +256,46 @@ def test_listen_fds_default_unset(): assert listen_fds() == [3] assert listen_fds() == [] +def test_listen_fds_with_names_nothing(): + # make sure we have no fds to listen to, no names + os.unsetenv('LISTEN_FDS') + os.unsetenv('LISTEN_PID') + os.unsetenv('LISTEN_FDNAMES') + + assert listen_fds_with_names() == {} + assert listen_fds_with_names(True) == {} + assert listen_fds_with_names(False) == {} + +def test_listen_fds_with_names_no_names(): + # make sure we have no fds to listen to, no names + os.environ['LISTEN_FDS'] = '1' + os.environ['LISTEN_PID'] = str(os.getpid()) + os.unsetenv('LISTEN_FDNAMES') + + assert listen_fds_with_names(False) == {3: 'unknown'} + assert listen_fds_with_names(True) == {3: 'unknown'} + assert listen_fds_with_names() == {} + +def test_listen_fds_with_names_single(): + # make sure we have no fds to listen to, no names + os.environ['LISTEN_FDS'] = '1' + os.environ['LISTEN_PID'] = str(os.getpid()) + os.environ['LISTEN_FDNAMES'] = 'cmds' + + assert listen_fds_with_names(False) == {3: 'cmds'} + assert listen_fds_with_names() == {3: 'cmds'} + assert listen_fds_with_names(True) == {} + +def test_listen_fds_with_names_multiple(): + # make sure we have no fds to listen to, no names + os.environ['LISTEN_FDS'] = '3' + os.environ['LISTEN_PID'] = str(os.getpid()) + os.environ['LISTEN_FDNAMES'] = 'cmds:data:errs' + + assert listen_fds_with_names(False) == {3: 'cmds', 4: 'data', 5: 'errs'} + assert listen_fds_with_names(True) == {3: 'cmds', 4: 'data', 5: 'errs'} + assert listen_fds_with_names() == {} + def test_notify_no_socket(): assert notify('READY=1') is False with skip_enosys(): -- cgit v1.2.1