Richard W.M. Jones
2021-Apr-07 16:23 UTC
[Libguestfs] [PATCH libnbd 1/2] lib: New API nbd_get_uri to get an NBD URI for a connection.
This will make a best-effort attempt to construct an NBD URI for connecting back to the current server. In many cases this is not really possible (eg. if we were connected with nbd_connect_socket), and it's not guaranteed to be correct. --- generator/API.ml | 31 ++++++++++- generator/C.ml | 8 +-- lib/uri.c | 129 +++++++++++++++++++++++++++++++++++++++++++ tests/connect-tcp.c | 20 +++++++ tests/connect-unix.c | 20 +++++++ tests/connect-uri.c | 17 ++++++ 6 files changed, 218 insertions(+), 7 deletions(-) diff --git a/generator/API.ml b/generator/API.ml index dd66fdb..2f1baa8 100644 --- a/generator/API.ml +++ b/generator/API.ml @@ -1383,7 +1383,7 @@ compiled with gnutls; you can test whether this is the case with L<nbd_supports_tls(3)>."; see_also = [URLLink "https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md"; Link "set_export_name"; Link "set_tls"; - Link "set_opt_mode"]; + Link "set_opt_mode"; Link "get_uri"]; }; "connect_unix", { @@ -2875,7 +2875,30 @@ to support TLS encryption, or false if not."; longdesc = "\ Returns true if libnbd was compiled with libxml2 which is required to support NBD URIs, or false if not."; - see_also = [Link "connect_uri"; Link "aio_connect_uri"]; + see_also = [Link "connect_uri"; Link "aio_connect_uri"; + Link "get_uri"]; + }; + + "get_uri", { + default_call with + args = []; ret = RString; + permitted_states = [ Connecting; Negotiating; Connected; Closed; Dead ]; + shortdesc = "construct an NBD URI for a connection"; + longdesc = "\ +This makes a best effort attempt to construct an NBD URI which +could be used to connect to this NBD server (eg. using +L<nbd_connect_uri(3)>). + +The URI returned is not guaranteed to work, and in some cases +(eg. if connected with L<nbd_connect_socket(3)>) it is not possible +at all. Even if a URI is returned it may not be optimal. + +On error, L<nbd_get_errno(3)> will be set to C<ENOTSUP> if the +library was compiled without support for URIs. In other error +cases, L<nbd_get_errno(3)> and L<nbd_get_error(3)> should contain +information about why constructing a URI was not possible."; + see_also = [Link "connect_uri"; Link "aio_connect_uri"; + Link "supports_uri"]; }; ] @@ -3009,6 +3032,7 @@ let first_version = [ (* Added in 1.7.x development cycle, will be stable and supported in 1.8. *) "set_private_data", (1, 8); "get_private_data", (1, 8); + "get_uri", (1, 8); (* These calls are proposed for a future version of libnbd, but * have not been added to any released version so far. @@ -3045,7 +3069,8 @@ let pod_of_link = function let verify_link let pages = List.map fst handle_calls in function - | Link "create" | Link "close" -> () + | Link "create" | Link "close" + | Link "get_error" | Link "get_errno" -> () | Link page -> if not (List.mem page pages) then failwithf "verify_link: page nbd_%s does not exist" page diff --git a/generator/C.ml b/generator/C.ml index fe8eafc..3e9975f 100644 --- a/generator/C.ml +++ b/generator/C.ml @@ -416,10 +416,10 @@ let generate_lib_unlocked_h () pr "\n"; pr "#endif /* LIBNBD_UNLOCKED_H */\n" -let permitted_state_text permitted_states +let permitted_state_text ?(fold=false) permitted_states assert (permitted_states <> []); - String.concat - ", or " + let sep = if fold then ", or\n" else ", or " in + String.concat sep (List.map ( function | Created -> "newly created" @@ -913,7 +913,7 @@ let generate_docs_nbd_pod name { args; optargs; ret; pr "=head1 HANDLE STATE\n"; pr "\n"; pr "The handle must be\n"; - pr "%s,\n" (permitted_state_text permitted_states); + pr "%s,\n" (permitted_state_text ~fold:true permitted_states); pr "otherwise this call will return an error.\n"; pr "\n" ); diff --git a/lib/uri.c b/lib/uri.c index 9f5a290..e75b04f 100644 --- a/lib/uri.c +++ b/lib/uri.c @@ -25,6 +25,13 @@ #include <string.h> #include <errno.h> #include <assert.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#ifdef HAVE_LINUX_VM_SOCKETS_H +#include <linux/vm_sockets.h> +#endif #include "internal.h" #include "vector.h" @@ -335,6 +342,121 @@ cleanup: return ret; } +static int +append_query_params (char **query_params, const char *key, const char *value) +{ + char *old_query_params = *query_params; + + if (asprintf (query_params, "%s%s%s=%s", + old_query_params ? : "", + old_query_params ? "&" : "", + key, value) == -1) + return -1; + free (old_query_params); + return 0; +} + +/* This is best effort. If we didn't save enough information when + * connecting then return NULL but try to set errno and the error + * string to something useful. + */ +char * +nbd_unlocked_get_uri (struct nbd_handle *h) +{ + xmlURI uri = { 0 }; + bool using_tls; + char *server = NULL; + char *query_params = NULL; + char *path = NULL; + char *ret = NULL; + + if (h->tls == 2) /* TLS == require */ + using_tls = true; + else if (h->tls_negotiated) + using_tls = true; + else + using_tls = false; + + /* Set scheme, server or socket. */ + if (h->hostname && h->port) { + uri.scheme = using_tls ? "nbds" : "nbd"; + if (asprintf (&server, "%s:%s", h->hostname, h->port) == -1) { + set_error (errno, "asprintf"); + goto out; + } + uri.server = server; + } + else if (h->connaddrlen > 0) { + switch (h->connaddr.ss_family) { + case AF_UNIX: { + struct sockaddr_un *sun = (struct sockaddr_un *) &h->connaddr; + + uri.scheme = using_tls ? "nbds+unix" : "nbd+unix"; + if (append_query_params (&query_params, "socket", sun->sun_path) == -1) { + set_error (errno, "asprintf"); + goto out; + } + /* You have to set this otherwise xmlSaveUri generates bogus + * URIs "nbd+unix:/?socket=..." + */ + uri.server = ""; + break; + } +#ifdef AF_VSOCK + case AF_VSOCK: { + struct sockaddr_vm *svm = (struct sockaddr_vm *) &h->connaddr; + + uri.scheme = using_tls ? "nbds+vsock" : "nbd+vsock"; + if (asprintf (&server, "%u:%u", svm->svm_cid, svm->svm_port) == -1) { + set_error (errno, "asprintf"); + goto out; + } + uri.server = server; + break; + } +#endif + default: + set_error (EAFNOSUPPORT, + "address family %d not supported", h->connaddr.ss_family); + goto out; + } + } + else { + set_error (EINVAL, "cannot construct a URI for this connection type"); + goto out; + } + + /* Set other fields. */ + if (h->tls_username) + uri.user = h->tls_username; + if (h->export_name) { + if (asprintf (&path, "/%s", h->export_name) == -1) { + set_error (errno, "asprintf"); + goto out; + } + uri.path = path; + } + if (h->tls_psk_file) { + if (append_query_params (&query_params, + "tls-psk-file", h->tls_psk_file) == -1) { + set_error (errno, "asprintf"); + goto out; + } + } + + uri.query_raw = query_params; + + /* Construct the final URI and return it. */ + ret = (char *) xmlSaveUri (&uri); + if (ret == NULL) + set_error (errno, "xmlSaveUri failed"); + out: + free (server); + free (query_params); + free (path); + return ret; +} + #else /* !HAVE_LIBXML2 */ #define NOT_SUPPORTED_ERROR \ @@ -354,4 +476,11 @@ nbd_unlocked_aio_connect_uri (struct nbd_handle *h, const char *raw_uri) return -1; } +char * +nbd_unlocked_get_uri (struct nbd_handle *h) +{ + set_error (ENOTSUP, NOT_SUPPORTED_ERROR); + return NULL; +} + #endif /* !HAVE_LIBXML2 */ diff --git a/tests/connect-tcp.c b/tests/connect-tcp.c index d7a36e4..e96b1ec 100644 --- a/tests/connect-tcp.c +++ b/tests/connect-tcp.c @@ -22,6 +22,7 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <fcntl.h> #include <unistd.h> #include <time.h> @@ -38,6 +39,7 @@ main (int argc, char *argv[]) char port_str[16]; pid_t pid; size_t i; + char *actual_uri, *expected_uri; unlink (PIDFILE); @@ -79,6 +81,24 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } + /* libnbd should be able to construct a URI for this connection. */ + if (asprintf (&expected_uri, "nbd://localhost:%s/", port_str) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + actual_uri = nbd_get_uri (nbd); + if (actual_uri == NULL) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (strcmp (actual_uri, expected_uri) != 0) { + fprintf (stderr, "%s: actual URI %s != expected URI %s\n", + argv[0], actual_uri, expected_uri); + exit (EXIT_FAILURE); + } + free (actual_uri); + free (expected_uri); + if (nbd_shutdown (nbd, 0) == -1) { fprintf (stderr, "%s\n", nbd_get_error ()); exit (EXIT_FAILURE); diff --git a/tests/connect-unix.c b/tests/connect-unix.c index 8c18166..0000b98 100644 --- a/tests/connect-unix.c +++ b/tests/connect-unix.c @@ -22,6 +22,7 @@ #include <stdio.h> #include <stdlib.h> +#include <string.h> #include <fcntl.h> #include <unistd.h> @@ -36,6 +37,7 @@ main (int argc, char *argv[]) struct nbd_handle *nbd; pid_t pid; size_t i; + char *actual_uri, *expected_uri; if (mkstemp (socket) == -1) { perror (socket); @@ -77,6 +79,24 @@ main (int argc, char *argv[]) exit (EXIT_FAILURE); } + /* libnbd should be able to construct a URI for this connection. */ + if (asprintf (&expected_uri, "nbd+unix:///?socket=%s", socket) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + actual_uri = nbd_get_uri (nbd); + if (actual_uri == NULL) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (strcmp (actual_uri, expected_uri) != 0) { + fprintf (stderr, "%s: actual URI %s != expected URI %s\n", + argv[0], actual_uri, expected_uri); + exit (EXIT_FAILURE); + } + free (actual_uri); + free (expected_uri); + if (nbd_shutdown (nbd, 0) == -1) { fprintf (stderr, "%s\n", nbd_get_error ()); exit (EXIT_FAILURE); diff --git a/tests/connect-uri.c b/tests/connect-uri.c index 6e7d168..9093247 100644 --- a/tests/connect-uri.c +++ b/tests/connect-uri.c @@ -35,6 +35,7 @@ main (int argc, char *argv[]) struct nbd_handle *nbd; pid_t pid; size_t i; + char *get_uri; #ifdef SOCKET unlink (SOCKET); @@ -89,6 +90,22 @@ main (int argc, char *argv[]) } } + /* Usually the URI returned by nbd_get_uri should be the same as the + * one passed to nbd_connect_uri, or at least it will be in our test + * cases. + */ + get_uri = nbd_get_uri (nbd); + if (get_uri == NULL) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } + if (strcmp (URI, get_uri) != 0) { + fprintf (stderr, "%s: connect URI %s != get URI %s\n", + argv[0], URI, get_uri); + exit (EXIT_FAILURE); + } + free (get_uri); + if (nbd_shutdown (nbd, 0) == -1) { fprintf (stderr, "%s\n", nbd_get_error ()); exit (EXIT_FAILURE); -- 2.29.0.rc2
Richard W.M. Jones
2021-Apr-07 16:23 UTC
[Libguestfs] [PATCH libnbd 2/2] info: Print the URI in the output, if possible.
--- info/Makefile.am | 1 + info/info-list-uris.sh | 46 ++++++++++++++++++++++++++++++++++++++++++ info/info-text.sh | 1 + info/nbdinfo.c | 12 +++++++++++ info/nbdinfo.pod | 2 ++ 5 files changed, 62 insertions(+) diff --git a/info/Makefile.am b/info/Makefile.am index 1f0a6a3..c19c394 100644 --- a/info/Makefile.am +++ b/info/Makefile.am @@ -22,6 +22,7 @@ info_sh_files = \ info-list-json.sh \ info-list-qemu.sh \ info-list-json-qemu.sh \ + info-list-uris.sh \ info-json.sh \ info-oldstyle.sh \ info-null.sh \ diff --git a/info/info-list-uris.sh b/info/info-list-uris.sh new file mode 100755 index 0000000..28693aa --- /dev/null +++ b/info/info-list-uris.sh @@ -0,0 +1,46 @@ +#!/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 nbdkit file --version + +# This test requires nbdkit >= 1.22. +minor=$( nbdkit --dump-config | grep ^version_minor | cut -d= -f2 ) +requires test $minor -gt 22 + +out=info-list-uris.out +cleanup_fn rm -f $out + +# nbdinfo --list is not very stable in the particular case where +# exports come and go while it is running. This happens if we set the +# directory to be the current directory since other tests create +# temporary files here. So point this to a more stable directory. + +nbdkit -U - file dir=$srcdir/../examples \ + --run '$VG nbdinfo --list "$uri"' > $out +cat $out + +# We expect to see URIs corresponding to some well-known files +# (ie. exports) in the examples directory. +grep "uri: nbd+unix:///LICENSE-FOR-EXAMPLES?socket=" $out +grep "uri: nbd+unix:///get-size.c?socket=" $out diff --git a/info/info-text.sh b/info/info-text.sh index bf98405..5585a48 100755 --- a/info/info-text.sh +++ b/info/info-text.sh @@ -31,4 +31,5 @@ nbdkit -U - memory size=1M \ --run '$VG nbdinfo "nbd+unix:///?socket=$unixsocket"' > $out cat $out grep "export-size: $((1024*1024))" $out +grep "uri: nbd+unix:///?socket=" $out sed -n '/contexts:/ { N; p; q }; $ q1' $out diff --git a/info/nbdinfo.c b/info/nbdinfo.c index 3dfc463..b35e682 100644 --- a/info/nbdinfo.c +++ b/info/nbdinfo.c @@ -407,6 +407,7 @@ list_one_export (struct nbd_handle *nbd, const char *desc, char *export_name = NULL; char *export_desc = NULL; char *content = NULL; + char *uri = NULL; int is_rotational, is_read_only; int can_cache, can_df, can_fast_zero, can_flush, can_fua, can_multi_conn, can_trim, can_zero; @@ -433,6 +434,8 @@ list_one_export (struct nbd_handle *nbd, const char *desc, exit (EXIT_FAILURE); } + uri = nbd_get_uri (nbd); + /* Prefer the server's version of the name, if available */ export_name = nbd_get_canonical_export_name (nbd); if (export_name == NULL) @@ -475,6 +478,8 @@ list_one_export (struct nbd_handle *nbd, const char *desc, fprintf (fp, "\texport-size: %" PRIi64 "\n", size); if (content) fprintf (fp, "\tcontent: %s\n", content); + if (uri) + fprintf (fp, "\turi: %s\n", uri); if (show_context) { fprintf (fp, "\tcontexts:\n"); for (i = 0; i < contexts.size; ++i) @@ -533,6 +538,12 @@ list_one_export (struct nbd_handle *nbd, const char *desc, fprintf (fp, ",\n"); } + if (uri) { + fprintf (fp, "\t\"uri\": "); + print_json_string (uri); + fprintf (fp, ",\n"); + } + if (show_context) { fprintf (fp, "\t\"contexts\": [\n"); for (i = 0; i < contexts.size; ++i) { @@ -600,6 +611,7 @@ list_one_export (struct nbd_handle *nbd, const char *desc, free (content); free (export_name); free (export_desc); + free (uri); return true; } diff --git a/info/nbdinfo.pod b/info/nbdinfo.pod index 99c74a2..5bc624c 100644 --- a/info/nbdinfo.pod +++ b/info/nbdinfo.pod @@ -23,6 +23,7 @@ L<https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md>): export="": export-size: 1048576 content: data + uri: nbd://localhost:10809/ is_rotational: false is_read_only: false can_cache: true @@ -63,6 +64,7 @@ the I<--json> parameter: { "export-name": "", "content": "DOS/MBR boot sector; partition 1 : ID=0xc, start-CHS (0x3ff,254,63), end-CHS (0x3ff,254,63), startsector 2048, 4148704 sectors", + "uri": "nbd://localhost:10809/", "is_rotational": false, "is_read_only": true, "can_cache": true, -- 2.29.0.rc2