diff options
-rw-r--r-- | setup.py | 3 | ||||
-rw-r--r-- | systemd/_daemon.c | 70 | ||||
-rw-r--r-- | systemd/_reader.c | 6 | ||||
-rw-r--r-- | systemd/daemon.py | 15 | ||||
-rw-r--r-- | systemd/journal.py | 8 | ||||
-rw-r--r-- | systemd/test/test_daemon.py | 45 | ||||
-rw-r--r-- | systemd/test/test_journal.py | 27 | ||||
-rw-r--r-- | systemd/util.c | 187 | ||||
-rw-r--r-- | systemd/util.h | 33 |
9 files changed, 366 insertions, 28 deletions
@@ -61,7 +61,8 @@ _reader = Extension('systemd/_reader', **lib('libsystemd', 'libsystemd-journal', **defines)) _daemon = Extension('systemd/_daemon', sources = ['systemd/_daemon.c', - 'systemd/pyutil.c'], + 'systemd/pyutil.c', + 'systemd/util.c'], extra_compile_args=['-Werror=implicit-function-declaration'], **lib('libsystemd', 'libsystemd-daemon', **defines)) id128 = Extension('systemd/id128', diff --git a/systemd/_daemon.c b/systemd/_daemon.c index a041b1b..90cf205 100644 --- a/systemd/_daemon.c +++ b/systemd/_daemon.c @@ -2,7 +2,7 @@ /*** - Copyright 2013 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> + Copyright 2013-2016 Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl> python-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 @@ -31,12 +31,18 @@ #include "systemd/sd-daemon.h" #include "pyutil.h" #include "macro.h" +#include "util.h" #if LIBSYSTEMD_VERSION >= 214 # define HAVE_PID_NOTIFY -# if LIBSYSTEMD_VERSION >= 219 -# define HAVE_PID_NOTIFY_WITH_FDS -# endif +#endif + +#if LIBSYSTEMD_VERSION >= 219 +# define HAVE_PID_NOTIFY_WITH_FDS +#endif + +#if LIBSYSTEMD_VERSION >= 233 +# define HAVE_IS_SOCKET_SOCKADDR #endif PyDoc_STRVAR(module__doc__, @@ -145,14 +151,14 @@ static PyObject* notify(PyObject *self, PyObject *args, PyObject *keywds) { #ifdef HAVE_PID_NOTIFY r = sd_pid_notify(pid, unset, msg); #else - PyErr_SetString(PyExc_NotImplementedError, "Compiled without support for sd_pid_notify"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_pid_notify"); return NULL; #endif } else { #ifdef HAVE_PID_NOTIFY_WITH_FDS r = sd_pid_notify_with_fds(pid, unset, msg, arr, n_fds); #else - PyErr_SetString(PyExc_NotImplementedError, "Compiled without support for sd_pid_notify_with_fds"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_pid_notify_with_fds"); return NULL; #endif } @@ -311,6 +317,53 @@ static PyObject* is_socket_inet(PyObject *self, PyObject *args) { return PyBool_FromLong(r); } +#ifdef HAVE_IS_SOCKET_SOCKADDR +PyDoc_STRVAR(is_socket_sockaddr__doc__, + "_is_socket_sockaddr(fd, address, type=0, flowinfo=0, listening=-1) -> bool\n\n" + "Wraps sd_is_socket_inet_sockaddr(3).\n" + "`address` is a systemd-style numerical IPv4 or IPv6 address as used in\n" + "ListenStream=. A port may be included after a colon (\":\"). See\n" + "systemd.socket(5) for details.\n\n" + "Constants for `family` are defined in the socket module." +); + +static PyObject* is_socket_sockaddr(PyObject *self, PyObject *args) { + int r; + int fd, type = 0, flowinfo = 0, listening = -1; + const char *address; + union sockaddr_union addr = {}; + unsigned addr_len; + + if (!PyArg_ParseTuple(args, "is|iii:_is_socket_sockaddr", + &fd, + &address, + &type, + &flowinfo, + &listening)) + return NULL; + + r = parse_sockaddr(address, &addr, &addr_len); + if (r < 0) { + set_error(r, NULL, "Cannot parse address"); + return NULL; + } + + if (flowinfo != 0) { + if (addr.sa.sa_family != AF_INET6) { + set_error(-EINVAL, NULL, "flowinfo is only applicable to IPv6 addresses"); + return NULL; + } + + addr.in6.sin6_flowinfo = flowinfo; + } + + r = sd_is_socket_sockaddr(fd, type, &addr.sa, addr_len, listening); + if (set_error(r, NULL, NULL) < 0) + return NULL; + + return PyBool_FromLong(r); +} +#endif PyDoc_STRVAR(is_socket_unix__doc__, "_is_socket_unix(fd, type, listening, path) -> bool\n\n" @@ -355,8 +408,11 @@ static PyMethodDef methods[] = { { "_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__}, +#ifdef HAVE_IS_SOCKET_SOCKADDR + { "_is_socket_sockaddr", is_socket_sockaddr, METH_VARARGS, is_socket_sockaddr__doc__}, +#endif { "_is_socket_unix", is_socket_unix, METH_VARARGS, is_socket_unix__doc__}, - { NULL, NULL, 0, NULL } /* Sentinel */ + {} /* Sentinel */ }; #if PY_MAJOR_VERSION < 3 diff --git a/systemd/_reader.c b/systemd/_reader.c index 3a2c218..5b7e191 100644 --- a/systemd/_reader.c +++ b/systemd/_reader.c @@ -1048,7 +1048,7 @@ static PyObject* Reader_enumerate_fields(Reader *self, PyObject *args) { _value_set = NULL; return value_set; #else - set_error(-ENOSYS, NULL, "Not implemented"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_journal_enumerate_fields"); return NULL; #endif } @@ -1069,7 +1069,7 @@ static PyObject* Reader_has_runtime_files(Reader *self, PyObject *args) { return PyBool_FromLong(r); #else - set_error(-ENOSYS, NULL, "Not implemented"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_journal_has_runtime_files"); return NULL; #endif } @@ -1090,7 +1090,7 @@ static PyObject* Reader_has_persistent_files(Reader *self, PyObject *args) { return PyBool_FromLong(r); #else - set_error(-ENOSYS, NULL, "Not implemented"); + set_error(-ENOSYS, NULL, "Compiled without support for sd_journal_has_persistent_files"); return NULL; #endif } diff --git a/systemd/daemon.py b/systemd/daemon.py index 82011ca..5d398f2 100644 --- a/systemd/daemon.py +++ b/systemd/daemon.py @@ -5,6 +5,7 @@ from ._daemon import (__version__, _is_fifo, _is_socket, _is_socket_inet, + _is_socket_sockaddr, _is_socket_unix, _is_mq, LISTEN_FDS_START) @@ -28,6 +29,20 @@ 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, port) +def is_socket_sockaddr(fileobj, address, type=0, flowinfo=0, listening=-1): + """Check socket type, address and/or port, flowinfo, listening state. + + Wraps sd_is_socket_inet_sockaddr(3). + + `address` is a systemd-style numerical IPv4 or IPv6 address as used in + ListenStream=. A port may be included after a colon (":"). + See systemd.socket(5) for details. + + Constants for `family` are defined in the socket module. + """ + fd = _convert_fileobj(fileobj) + return _is_socket_sockaddr(fd, address, type, flowinfo, listening) + def is_socket_unix(fileobj, type=0, listening=-1, path=None): fd = _convert_fileobj(fileobj) return _is_socket_unix(fd, type, listening, path) diff --git a/systemd/journal.py b/systemd/journal.py index 48a36b4..83e3004 100644 --- a/systemd/journal.py +++ b/systemd/journal.py @@ -456,8 +456,8 @@ def stream(identifier=None, priority=LOG_INFO, level_prefix=False): newline character is written. >>> from systemd import journal - >>> stream = journal.stream('myapp') - >>> res = stream.write('message...\n') + >>> stream = journal.stream('myapp') # doctest: +SKIP + >>> res = stream.write('message...\n') # doctest: +SKIP will produce the following message in the journal:: @@ -470,8 +470,8 @@ def stream(identifier=None, priority=LOG_INFO, level_prefix=False): This interface can be used conveniently with the print function: >>> from __future__ import print_function - >>> fileobj = journal.stream() - >>> print('message...', file=fileobj) # doctest: +SKIP + >>> stream = journal.stream() # doctest: +SKIP + >>> print('message...', file=stream) # doctest: +SKIP priority is the syslog priority, one of `LOG_EMERG`, `LOG_ALERT`, `LOG_CRIT`, `LOG_ERR`, `LOG_WARNING`, `LOG_NOTICE`, `LOG_INFO`, `LOG_DEBUG`. diff --git a/systemd/test/test_daemon.py b/systemd/test/test_daemon.py index 7733552..c1e08c7 100644 --- a/systemd/test/test_daemon.py +++ b/systemd/test/test_daemon.py @@ -9,6 +9,7 @@ from systemd.daemon import (booted, is_socket, _is_socket, is_socket_inet, _is_socket_inet, is_socket_unix, _is_socket_unix, + is_socket_sockaddr, _is_socket_sockaddr, is_mq, _is_mq, listen_fds, notify) @@ -122,15 +123,18 @@ def test_no_mismatch(): assert not is_fifo(sock) assert not is_mq_wrapper(sock) assert not is_socket_inet(sock) + assert not is_socket_sockaddr(sock, '127.0.0.1:2000') fd = sock.fileno() assert not is_fifo(fd) assert not is_mq_wrapper(fd) assert not is_socket_inet(fd) + assert not is_socket_sockaddr(fd, '127.0.0.1:2000') assert not _is_fifo(fd) assert not _is_mq_wrapper(fd) assert not _is_socket_inet(fd) + assert not _is_socket_sockaddr(fd, '127.0.0.1:2000') def test_is_socket(): with closing_socketpair(socket.AF_UNIX) as pair: @@ -141,12 +145,43 @@ def test_is_socket(): assert not is_socket(arg, socket.AF_INET) assert is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) assert not is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) + assert not is_socket_sockaddr(arg, '8.8.8.8:2000', socket.SOCK_DGRAM, 0, 0) + + assert _is_socket(arg) + assert _is_socket(arg, socket.AF_UNIX) + assert not _is_socket(arg, socket.AF_INET) + assert _is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) + assert not _is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) + assert not _is_socket_sockaddr(arg, '8.8.8.8:2000', socket.SOCK_DGRAM, 0, 0) + +def test_is_socket_sockaddr(): + with contextlib.closing(socket.socket(socket.AF_INET)) as sock: + sock.bind(('127.0.0.1', 0)) + addr, port = sock.getsockname() + port = ':{}'.format(port) + + for listening in (0, 1): + for arg in (sock, sock.fileno()): + assert is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_STREAM) + assert is_socket_sockaddr(arg, '127.0.0.1' + port, socket.SOCK_STREAM) - assert is_socket(sock) - assert is_socket(arg, socket.AF_UNIX) - assert not is_socket(arg, socket.AF_INET) - assert is_socket(arg, socket.AF_UNIX, socket.SOCK_STREAM) - assert not is_socket(arg, socket.AF_INET, socket.SOCK_DGRAM) + assert is_socket_sockaddr(arg, '127.0.0.1' + port, listening=listening) + assert is_socket_sockaddr(arg, '127.0.0.1' + port, listening=-1) + assert not is_socket_sockaddr(arg, '127.0.0.1' + port, listening=not listening) + + with pytest.raises(ValueError): + is_socket_sockaddr(arg, '127.0.0.1', flowinfo=123456) + + assert not is_socket_sockaddr(arg, '129.168.11.11:23', socket.SOCK_STREAM) + assert not is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) + + with pytest.raises(ValueError): + _is_socket_sockaddr(arg, '127.0.0.1', 0, 123456) + + assert not _is_socket_sockaddr(arg, '129.168.11.11:23', socket.SOCK_STREAM) + assert not _is_socket_sockaddr(arg, '127.0.0.1', socket.SOCK_DGRAM) + + sock.listen(11) def test__is_socket(): with closing_socketpair(socket.AF_UNIX) as pair: diff --git a/systemd/test/test_journal.py b/systemd/test/test_journal.py index fd13cd0..fd38036 100644 --- a/systemd/test/test_journal.py +++ b/systemd/test/test_journal.py @@ -1,3 +1,4 @@ +from __future__ import print_function import contextlib import datetime import errno @@ -40,11 +41,11 @@ class MockSender: self.buf.append(args) @contextlib.contextmanager -def skip_enosys(): +def skip_oserror(code): try: yield - except OSError as e: - if e.errno == errno.ENOSYS: + except (OSError, IOError) as e: + if e.errno == code: pytest.skip() raise @@ -132,7 +133,7 @@ def test_reader_init_path_nondirectory_fd(): def test_reader_init_path_fd(tmpdir): fd = os.open(tmpdir.strpath, os.O_RDONLY) - with skip_enosys(): + with skip_oserror(errno.ENOSYS): j1 = journal.Reader(path=fd) assert list(j1) == [] @@ -175,7 +176,7 @@ def test_reader_this_machine(tmpdir): def test_reader_query_unique(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.query_unique('FOOBAR') assert isinstance(ans, set) assert ans == set() @@ -183,7 +184,7 @@ def test_reader_query_unique(tmpdir): def test_reader_enumerate_fields(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.enumerate_fields() assert isinstance(ans, set) assert ans == set() @@ -191,14 +192,14 @@ def test_reader_enumerate_fields(tmpdir): def test_reader_has_runtime_files(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.has_runtime_files() assert ans == False def test_reader_has_persistent_files(tmpdir): j = journal.Reader(path=tmpdir.strpath) with j: - with skip_enosys(): + with skip_oserror(errno.ENOSYS): ans = j.has_runtime_files() assert ans == False @@ -236,3 +237,13 @@ def test_seek_realtime(tmpdir): long_ago = datetime.datetime(1970, 5, 4) j.seek_realtime(long_ago) + +def test_journal_stream(): + # This will fail when running in a bare chroot without /run/systemd/journal/stdout + with skip_oserror(errno.ENOENT): + stream = journal.stream('test_journal.py') + + res = stream.write('message...\n') + assert res in (11, None) # Python2 returns None + + print('printed message...', file=stream) diff --git a/systemd/util.c b/systemd/util.c new file mode 100644 index 0000000..e02c825 --- /dev/null +++ b/systemd/util.c @@ -0,0 +1,187 @@ +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + 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 <http://www.gnu.org/licenses/>. +***/ + +/* stuff imported from systemd without any changes */ + +#ifndef _GNU_SOURCE +# define _GNU_SOURCE +#endif + +#include <stdlib.h> +#include <string.h> +#include <stdbool.h> +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <net/if.h> + +#include "util.h" + +int safe_atou(const char *s, unsigned *ret_u) { + char *x = NULL; + unsigned long l; + + assert(s); + assert(ret_u); + + /* strtoul() is happy to parse negative values, and silently + * converts them to unsigned values without generating an + * error. We want a clean error, hence let's look for the "-" + * prefix on our own, and generate an error. But let's do so + * only after strtoul() validated that the string is clean + * otherwise, so that we return EINVAL preferably over + * ERANGE. */ + + errno = 0; + l = strtoul(s, &x, 0); + if (errno > 0) + return -errno; + if (!x || x == s || *x) + return -EINVAL; + if (s[0] == '-') + return -ERANGE; + if ((unsigned long) (unsigned) l != l) + return -ERANGE; + + *ret_u = (unsigned) l; + return 0; +} + +static bool socket_ipv6_is_supported(void) { + if (access("/proc/net/if_inet6", F_OK) != 0) + return false; + + return true; +} + +static int assign_address(const char *s, + uint16_t port, + union sockaddr_union *addr, unsigned *addr_len) { + int r; + + /* IPv4 in w.x.y.z:p notation? */ + r = inet_pton(AF_INET, s, &addr->in.sin_addr); + if (r < 0) + return -errno; + + if (r > 0) { + /* Gotcha, it's a traditional IPv4 address */ + addr->in.sin_family = AF_INET; + addr->in.sin_port = htobe16(port); + *addr_len = sizeof(struct sockaddr_in); + } else { + unsigned idx; + + if (strlen(s) > IF_NAMESIZE-1) + return -EINVAL; + + /* Uh, our last resort, an interface name */ + idx = if_nametoindex(s); + if (idx == 0) + return -EINVAL; + + addr->in6.sin6_family = AF_INET6; + addr->in6.sin6_port = htobe16(port); + addr->in6.sin6_scope_id = idx; + addr->in6.sin6_addr = in6addr_any; + *addr_len = sizeof(struct sockaddr_in6); + } + + return 0; +} + + +int parse_sockaddr(const char *s, + union sockaddr_union *addr, unsigned *addr_len) { + + char *e, *n; + unsigned u; + int r; + + if (*s == '[') { + /* IPv6 in [x:.....:z]:p notation */ + + e = strchr(s+1, ']'); + if (!e) + return -EINVAL; + + n = strndupa(s+1, e-s-1); + + errno = 0; + if (inet_pton(AF_INET6, n, &addr->in6.sin6_addr) <= 0) + return errno > 0 ? -errno : -EINVAL; + + e++; + if (*e) { + if (*e != ':') + return -EINVAL; + + e++; + r = safe_atou(e, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + addr->in6.sin6_port = htobe16((uint16_t)u); + } + + addr->in6.sin6_family = AF_INET6; + *addr_len = sizeof(struct sockaddr_in6); + + } else { + e = strchr(s, ':'); + if (e) { + r = safe_atou(e+1, &u); + if (r < 0) + return r; + + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + n = strndupa(s, e-s); + return assign_address(n, u, addr, addr_len); + + } else { + r = safe_atou(s, &u); + if (r < 0) + return assign_address(s, 0, addr, addr_len); + + /* Just a port */ + if (u <= 0 || u > 0xFFFF) + return -EINVAL; + + if (socket_ipv6_is_supported()) { + addr->in6.sin6_family = AF_INET6; + addr->in6.sin6_port = htobe16((uint16_t)u); + addr->in6.sin6_addr = in6addr_any; + *addr_len = sizeof(struct sockaddr_in6); + } else { + addr->in.sin_family = AF_INET; + addr->in.sin_port = htobe16((uint16_t)u); + addr->in.sin_addr.s_addr = INADDR_ANY; + *addr_len = sizeof(struct sockaddr_in); + } + } + } + + return 0; +} diff --git a/systemd/util.h b/systemd/util.h new file mode 100644 index 0000000..337920c --- /dev/null +++ b/systemd/util.h @@ -0,0 +1,33 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + 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 <http://www.gnu.org/licenses/>. +***/ + +#include <netinet/ip.h> +#include <arpa/inet.h> + +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in; + struct sockaddr_in6 in6; +}; + +int safe_atou(const char *s, unsigned *ret_u); +int parse_sockaddr(const char *s, + union sockaddr_union *addr, unsigned *addr_len); |