Richard W.M. Jones
2021-Jun-30 19:22 UTC
[Libguestfs] [PATCH libnbd] python: Implement nbd.aio_connect for AF_UNIX
This call previously just called abort(). Implement it for Unix domain sockets (the easy case). Implementing it for AF_INET and AF_INET6 is more complicated so that is left as a to-do. Note also that implementing this fully for Python is a bit pointless. It would be easier for a Python program to call nbd.aio_connect_tcp(host, port) instead of calling nbd.aio_connect((host, port)). Both cases would do hostname lookups but the former is already implemented. --- generator/Python.ml | 17 +++++---- python/Makefile.am | 6 ++- python/python-aio-connect-unix.sh | 36 ++++++++++++++++++ python/utils.c | 63 +++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/generator/Python.ml b/generator/Python.ml index 25f92d7..bcfd4bf 100644 --- a/generator/Python.ml +++ b/generator/Python.ml @@ -41,6 +41,8 @@ struct py_aio_buffer { extern char **nbd_internal_py_get_string_list (PyObject *); extern void nbd_internal_py_free_string_list (char **); +extern int nbd_internal_py_get_sockaddr (PyObject *, + struct sockaddr_storage *, socklen_t *); extern struct py_aio_buffer *nbd_internal_py_get_aio_buffer (PyObject *); static inline struct nbd_handle * @@ -300,10 +302,9 @@ let print_python_binding name { args; optargs; ret; may_set_error } | SizeT n -> pr " Py_ssize_t %s;\n" n | SockAddrAndLen (n, _) -> - pr " /* XXX Complicated - Python uses a tuple of different\n"; - pr " * lengths for the different socket types.\n"; - pr " */\n"; - pr " PyObject *%s;\n" n + pr " PyObject *%s;\n" n; + pr " struct sockaddr_storage %s_sa;\n" n; + pr " socklen_t %s_len;\n" n; | String n -> pr " const char *%s;\n" n | StringList n -> pr " PyObject *py_%s;\n" n; @@ -440,8 +441,10 @@ let print_python_binding name { args; optargs; ret; may_set_error } pr " %s = PyBytes_AS_STRING (py_%s);\n" n n; pr " assert (%s != NULL);\n" n | SizeT n -> () - | SockAddrAndLen _ -> - pr " abort (); /* XXX SockAddrAndLen not implemented */\n"; + | SockAddrAndLen (n, _) -> + pr " if (nbd_internal_py_get_sockaddr (%s, &%s_sa, &%s_len) == -1)\n" + n n n; + pr " goto out;\n" | String _ -> () | StringList n -> pr " %s = nbd_internal_py_get_string_list (py_%s);\n" n n; @@ -468,7 +471,7 @@ let print_python_binding name { args; optargs; ret; may_set_error } | Int64 n -> pr ", %s_i64" n | Path n -> pr ", %s" n | SizeT n -> pr ", (size_t)%s" n - | SockAddrAndLen (n, _) -> pr ", /* XXX */ (void *) %s, 0" n + | SockAddrAndLen (n, _) -> pr ", (struct sockaddr *) &%s_sa, %s_len" n n | String n -> pr ", %s" n | StringList n -> pr ", %s" n | UInt n | UIntPtr n -> pr ", %s" n diff --git a/python/Makefile.am b/python/Makefile.am index 718f891..3779410 100644 --- a/python/Makefile.am +++ b/python/Makefile.am @@ -81,9 +81,13 @@ TESTS_ENVIRONMENT = \ LIBNBD_DEBUG=1 \ MALLOC_CHECK_=1 \ MALLOC_PERTURB_=$(shell bash -c 'echo $$(( 1 + (RANDOM & 255) ))') \ + PYTHON="$(PYTHON)" \ $(NULL) LOG_COMPILER = $(top_builddir)/run -TESTS += run-python-tests +TESTS += \ + python-aio-connect-unix.sh \ + run-python-tests \ + $(NULL) endif HAVE_NBDKIT diff --git a/python/python-aio-connect-unix.sh b/python/python-aio-connect-unix.sh new file mode 100755 index 0000000..7be0f87 --- /dev/null +++ b/python/python-aio-connect-unix.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# nbd client library in userspace +# Copyright (C) 2020-2021 Red Hat Inc. +# +# 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 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 + +. ../tests/functions.sh + +set -e +set -x + +requires nbdkit --version +requires $PYTHON --version + +nbdkit -U - null --run '$PYTHON -c " +import nbd +import sys +h = nbd.NBD() +addr = sys.argv[1] +h.aio_connect(addr) +while h.aio_is_connecting(): + h.poll(1) +assert h.aio_is_ready() +" "$unixsocket"' diff --git a/python/utils.c b/python/utils.c index 0e3164c..37f0c55 100644 --- a/python/utils.c +++ b/python/utils.c @@ -26,6 +26,8 @@ #include <stdio.h> #include <stdlib.h> #include <assert.h> +#include <sys/socket.h> +#include <sys/un.h> #include <libnbd.h> @@ -92,3 +94,64 @@ nbd_internal_py_free_string_list (char **argv) free (argv[i]); free (argv); } + +/* Convert a Python object into a struct sockaddr, according to the + * general rules described here: + * https://docs.python.org/3/library/socket.html + * + * There is a function in cpython called getsockaddrarg which roughly + * does the same thing, but in cpython they know the socket family + * already (which we do not). In any case that function cannot be + * called directly. + */ +int +nbd_internal_py_get_sockaddr (PyObject *addr, + struct sockaddr_storage *ss, socklen_t *len) +{ + memset (ss, 0, sizeof *ss); + + if (PyUnicode_Check (addr)) { /* AF_UNIX */ + struct sockaddr_un *sun = (struct sockaddr_un *)ss; + const char *unixsocket; + size_t namelen; + + sun->sun_family = AF_UNIX; + unixsocket = PyUnicode_AsUTF8 (addr); + if (!unixsocket) + goto err; + namelen = strlen (unixsocket); + if (namelen > sizeof sun->sun_path) { + PyErr_SetString (PyExc_RuntimeError, + "get_sockaddr: Unix domain socket name too long"); + return -1; + } + memcpy (sun->sun_path, unixsocket, namelen); + *len = sizeof *sun; + return 0; + } + +#if 0 + else if (PyTuple_Check (addr)) { + Py_ssize_t n = PyTuple_Size (addr); + + switch (n) { + case 2: /* AF_INET */ + /* XXX TODO */ + break; + + case 4: /* AF_INET6 */ + /* XXX TODO */ + break; + + default: + goto err; + } + } +#endif + + else { + err: + PyErr_SetString (PyExc_TypeError, "get_sockaddr: unknown address type"); + return -1; + } +} -- 2.32.0
Eric Blake
2021-Jun-30 21:38 UTC
[Libguestfs] [PATCH libnbd] python: Implement nbd.aio_connect for AF_UNIX
On Wed, Jun 30, 2021 at 08:22:19PM +0100, Richard W.M. Jones wrote:> This call previously just called abort(). Implement it for Unix > domain sockets (the easy case). Implementing it for AF_INET and > AF_INET6 is more complicated so that is left as a to-do.Still, fixing it to return an error instead of abort()ing is nice.> > Note also that implementing this fully for Python is a bit pointless. > It would be easier for a Python program to call > nbd.aio_connect_tcp(host, port) instead of calling > nbd.aio_connect((host, port)). Both cases would do hostname lookups > but the former is already implemented. > ---> @@ -92,3 +94,64 @@ nbd_internal_py_free_string_list (char **argv) > free (argv[i]); > free (argv); > } > + > +/* Convert a Python object into a struct sockaddr, according to the > + * general rules described here: > + * https://docs.python.org/3/library/socket.htmlPython's representation is complex!> + * > + * There is a function in cpython called getsockaddrarg which roughly > + * does the same thing, but in cpython they know the socket family > + * already (which we do not). In any case that function cannot be > + * called directly. > + */ > +int > +nbd_internal_py_get_sockaddr (PyObject *addr, > + struct sockaddr_storage *ss, socklen_t *len) > +{ > + memset (ss, 0, sizeof *ss); > + > + if (PyUnicode_Check (addr)) { /* AF_UNIX */ > + struct sockaddr_un *sun = (struct sockaddr_un *)ss; > + const char *unixsocket; > + size_t namelen; > + > + sun->sun_family = AF_UNIX; > + unixsocket = PyUnicode_AsUTF8 (addr); > + if (!unixsocket) > + goto err;Given the PyUnicode_Check() call above, this is unlikely to fail; but checking for failure anyway is good practice.> + namelen = strlen (unixsocket); > + if (namelen > sizeof sun->sun_path) { > + PyErr_SetString (PyExc_RuntimeError, > + "get_sockaddr: Unix domain socket name too long"); > + return -1; > + } > + memcpy (sun->sun_path, unixsocket, namelen); > + *len = sizeof *sun; > + return 0; > + } > + > +#if 0 > + else if (PyTuple_Check (addr)) { > + Py_ssize_t n = PyTuple_Size (addr); > + > + switch (n) { > + case 2: /* AF_INET */ > + /* XXX TODO */ > + break; > + > + case 4: /* AF_INET6 */ > + /* XXX TODO */ > + break;The python docs implied that the last two fields were optional for AF_INET6, which makes this even more complicated. But this is all #if 0 until someone needs and implements it anyway. Patch looks good to me. -- Eric Blake, Principal Software Engineer Red Hat, Inc. +1-919-301-3266 Virtualization: qemu.org | libvirt.org