summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>2017-03-11 14:26:27 -0500
committerGitHub <noreply@github.com>2017-03-11 14:26:27 -0500
commit753a4ca5ffbea9e7b937dbe5c567353f9ca21f85 (patch)
tree7881ff7c011727acc5c9da7078c70b0aaa440455
parentdce0a855c3281e7051b1cbe0f73386d1c90ef320 (diff)
parentbc691d8e293a593fbd14ad1d592d06f4f490ed29 (diff)
downloadpython-systemd-753a4ca5ffbea9e7b937dbe5c567353f9ca21f85.tar.gz
Merge pull request #31 from keszybz/is_socket_sockaddr
daemon: add basic support for sd_is_socket_sockaddr
-rw-r--r--setup.py3
-rw-r--r--systemd/_daemon.c70
-rw-r--r--systemd/_reader.c6
-rw-r--r--systemd/daemon.py15
-rw-r--r--systemd/journal.py8
-rw-r--r--systemd/test/test_daemon.py45
-rw-r--r--systemd/test/test_journal.py27
-rw-r--r--systemd/util.c187
-rw-r--r--systemd/util.h33
9 files changed, 366 insertions, 28 deletions
diff --git a/setup.py b/setup.py
index 529f613..1acfbc5 100644
--- a/setup.py
+++ b/setup.py
@@ -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);