From 3b0e4328751a5a1319d7164452e295515df1218d Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Tue, 16 Apr 2013 20:22:42 +0100 Subject: [PATCH] fish: options: Allow Unix domain sockets to be specified in --add URIs. You can now use a URI such as: guestfish -a nbd://?socket=/tmp/sock (a NBD connection to Unix domain socket /tmp/sock). --- fish/guestfish.pod | 29 ++++++--- fish/options.c | 158 ++++++++++++++++++++++++++++++++++++++++----- fish/options.h | 1 + 3 files changed, 162 insertions(+), 26 deletions(-) diff --git a/fish/guestfish.pod b/fish/guestfish.pod index 80c34e9e8..09c69c679 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -1102,7 +1102,7 @@ block devices using a URI-style format, for example: URIs I be used with the L command. The equivalent command using the API directly is: - > add /disk.img protocol:ssh server:example.com username:root + > add /disk.img protocol:ssh server:tcp:example.com username:root The possible I<-a URI> formats are described below. @@ -1120,19 +1120,28 @@ The server is the one running C, and may be C. The equivalent API command would be: - > add /disk protocol:gluster server:example.com + > add /disk protocol:gluster server:tcp:example.com -=head2 B<-a nbd://example.com[:port][/exportname]> +=head2 B<-a nbd://example.com[:port]> + +=head2 B<-a nbd://example.com[:port]/exportname> + +=head2 B<-a nbd://?socket=/socket> + +=head2 B<-a nbd:///exportname?socket=/socket> Add a disk located on Network Block Device (nbd) storage. -The URI syntax only supports TCP. Libguestfs can use Unix domain -sockets if you use the L API directly. The I part -of the URI specifies an NBD export name, but is usually left empty. +The I part of the URI specifies an NBD export name, but +is usually left empty. + +The optional I parameter can be used to specify a Unix domain +socket that we talk to the NBD server over. Note that you cannot mix +server name (ie. TCP/IP) and socket path. The equivalent API command would be (no export name): - > add "" protocol:nbd server:example.com + > add "" protocol:nbd server:[tcp:example.com|unix:/socket] =head2 B<-a rbd://example.com[:port]/disk> @@ -1143,7 +1152,7 @@ server can be specified when using this URI syntax. The equivalent API command would be: - > add /disk protocol:rbd server:example.com + > add /disk protocol:rbd server:tcp:example.com =head2 B<-a sheepdog://[example.com[:port]]/volume/image> @@ -1155,7 +1164,7 @@ when using this URI syntax. The equivalent API command would be: - > add /disk protocol:sheepdog [server:example.com] + > add /disk protocol:sheepdog [server:tcp:example.com] =head2 B<-a ssh://[user@]example.com[:port]/disk.img> @@ -1165,7 +1174,7 @@ major SSH servers. The equivalent API command would be: - > add /disk protocol:ssh server:example.com [username:user] + > add /disk protocol:ssh server:tcp:example.com [username:user] =head1 PROGRESS BARS diff --git a/fish/options.c b/fish/options.c index 3fcb74eda..94a4cb4c3 100644 --- a/fish/options.c +++ b/fish/options.c @@ -35,6 +35,7 @@ static int is_uri (const char *arg); static void parse_uri (const char *arg, const char *format, struct drv *drv); +static char *query_get (xmlURIPtr uri, const char *search_name); /* Handle the '-a' option when passed on the command line. */ void @@ -96,6 +97,7 @@ static void parse_uri (const char *arg, const char *format, struct drv *drv) { xmlURIPtr uri; + char *socket; uri = xmlParseURI (arg); if (!uri) { @@ -110,35 +112,160 @@ parse_uri (const char *arg, const char *format, struct drv *drv) */ if (uri->scheme == NULL) { /* Probably can never happen. */ - fprintf (stderr, _("%s: --add: scheme of URI '%s' is NULL\n"), + fprintf (stderr, _("%s: --add %s: scheme of URI is NULL\n"), program_name, arg); exit (EXIT_FAILURE); } + socket = query_get (uri, "socket"); + + if (uri->server && STRNEQ (uri->server, "") && socket) { + fprintf (stderr, _("%s: --add %s: cannot both a server name and a socket query parameter\n"), + program_name, arg); + exit (EXIT_FAILURE); + } + + /* Is this needed? XXX + if (socket && socket[0] != '/') { + fprintf (stderr, _("%s: --add %s: socket query parameter must be an absolute path\n"), + program_name, arg); + exit (EXIT_FAILURE); + } + */ + drv->type = drv_uri; drv->nr_drives = -1; drv->uri.uri = uri; + drv->uri.socket = socket; drv->uri.format = format; } +/* Code inspired by libvirt src/util/viruri.c, written by danpb, + * released under a compatible license. + */ +static char * +query_get (xmlURIPtr uri, const char *search_name) +{ + /* XXX libvirt uses deprecated uri->query field. Why? */ + const char *query = uri->query_raw; + const char *end, *eq; + + if (!query || STREQ (query, "")) + return NULL; + + while (*query) { + CLEANUP_FREE char *name = NULL; + char *value = NULL; + + /* Find the next separator, or end of the string. */ + end = strchr (query, '&'); + if (!end) + end = strchr(query, ';'); + if (!end) + end = query + strlen (query); + + /* Find the first '=' character between here and end. */ + eq = strchr(query, '='); + if (eq && eq >= end) eq = NULL; + + /* Empty section (eg. "&&"). */ + if (end == query) + goto next; + + /* If there is no '=' character, then we have just "name" + * and consistent with CGI.pm we assume value is "". + */ + else if (!eq) { + name = xmlURIUnescapeString (query, end - query, NULL); + if (!name) goto no_memory; + } + /* Or if we have "name=" here (works around annoying + * problem when calling xmlURIUnescapeString with len = 0). + */ + else if (eq+1 == end) { + name = xmlURIUnescapeString (query, eq - query, NULL); + if (!name) goto no_memory; + } + /* If the '=' character is at the beginning then we have + * "=value" and consistent with CGI.pm we _ignore_ this. + */ + else if (query == eq) + goto next; + + /* Otherwise it's "name=value". */ + else { + name = xmlURIUnescapeString (query, eq - query, NULL); + if (!name) + goto no_memory; + value = xmlURIUnescapeString (eq+1, end - (eq+1), NULL); + if (!value) { + goto no_memory; + } + } + + /* Is it the name we're looking for? */ + if (STREQ (name, search_name)) { + if (!value) { + value = strdup (""); + if (!value) + goto no_memory; + } + return value; + } + + free (value); + + next: + query = end; + if (*query) + query++; /* skip '&' separator */ + } + + /* search_name not found */ + return NULL; + + no_memory: + perror ("malloc"); + exit (EXIT_FAILURE); +} + +/* Construct either a tcp: server list of a unix: server list or + * nothing at all from '-a' option URI. + */ static char ** -make_server (xmlURIPtr uri) +make_server (xmlURIPtr uri, const char *socket) { char **ret; char *server; - if (uri->port == 0) { - if (asprintf (&server, "tcp:%s", uri->server) == -1) { - perror ("asprintf"); - exit (EXIT_FAILURE); - } - } - else { - if (asprintf (&server, "tcp:%s:%d", uri->server, uri->port) == -1) { + /* If the server part of the URI is specified, then this is a TCP + * connection. + */ + if (uri->server && STRNEQ (uri->server, "")) { + if (uri->port == 0) { + if (asprintf (&server, "tcp:%s", uri->server) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + } + else { + if (asprintf (&server, "tcp:%s:%d", uri->server, uri->port) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + } + } + /* Otherwise, ?socket query parameter means it's a Unix domain + * socket connection. + */ + else if (socket != NULL) { + if (asprintf (&server, "unix:%s", socket) == -1) { perror ("asprintf"); exit (EXIT_FAILURE); } } + /* Otherwise, no server parameter is needed. */ + else return NULL; /* The .server parameter is in fact a list of strings, although * only a singleton is passed by us. @@ -155,6 +282,7 @@ add_drives (struct drv *drv, char next_drive) { int r; struct guestfs_add_drive_opts_argv ad_optargs; + char **server; if (next_drive > 'z') { fprintf (stderr, @@ -206,12 +334,9 @@ add_drives (struct drv *drv, char next_drive) } ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_PROTOCOL_BITMASK; ad_optargs.protocol = drv->uri.uri->scheme; - if (drv->uri.uri->server && STRNEQ (drv->uri.uri->server, "")) { + ad_optargs.server = server = make_server (drv->uri.uri, drv->uri.socket); + if (server) ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_SERVER_BITMASK; - ad_optargs.server = make_server (drv->uri.uri); - } - else - ad_optargs.server = NULL; if (drv->uri.uri->user && STRNEQ (drv->uri.uri->user, "")) { ad_optargs.bitmask |= GUESTFS_ADD_DRIVE_OPTS_USERNAME_BITMASK; ad_optargs.username = drv->uri.uri->user; @@ -222,7 +347,7 @@ add_drives (struct drv *drv, char next_drive) if (r == -1) exit (EXIT_FAILURE); - guestfs___free_string_list ((char **) ad_optargs.server); + guestfs___free_string_list (server); drv->nr_drives = 1; next_drive++; @@ -334,6 +459,7 @@ free_drives (struct drv *drv) break; case drv_uri: xmlFreeURI (drv->uri.uri); + free (drv->uri.socket); break; case drv_d: /* d.filename is optarg, don't free it */ diff --git a/fish/options.h b/fish/options.h index 8d3da7618..ed1325cd3 100644 --- a/fish/options.h +++ b/fish/options.h @@ -65,6 +65,7 @@ struct drv { } a; struct { xmlURIPtr uri; /* URI */ + char *socket; /* ?socket parameter from URI. */ const char *format; /* format (NULL == autodetect) */ } uri; struct {