This is almost pure code motion, but I changed the name and prototype of the function 'wait_nbd' to make its purpose clearer, and to remove the unnecessary timeout setting (which is hard-coded). --- p2v/Makefile.am | 1 + p2v/conversion.c | 329 +------------------------------------ p2v/main.c | 86 +--------- p2v/nbd.c | 488 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ p2v/p2v.h | 16 +- 5 files changed, 506 insertions(+), 414 deletions(-) create mode 100644 p2v/nbd.c diff --git a/p2v/Makefile.am b/p2v/Makefile.am index 0a2c2a8..93d7f47 100644 --- a/p2v/Makefile.am +++ b/p2v/Makefile.am @@ -81,6 +81,7 @@ virt_p2v_SOURCES = \ kernel.c \ kernel-cmdline.c \ main.c \ + nbd.c \ p2v.h \ ssh.c \ utils.c \ diff --git a/p2v/conversion.c b/p2v/conversion.c index aa9cd4c..44a3c58 100644 --- a/p2v/conversion.c +++ b/p2v/conversion.c @@ -82,21 +82,14 @@ xmlBufferDetach (xmlBufferPtr buf) } #endif -/* How long to wait for the NBD server to start (seconds). */ -#define WAIT_NBD_TIMEOUT 10 - /* Data per NBD connection / physical disk. */ struct data_conn { mexp_h *h; /* miniexpect handle to ssh */ - pid_t nbd_pid; /* qemu pid */ + pid_t nbd_pid; /* NBD server PID */ int nbd_local_port; /* local NBD port on physical machine */ int nbd_remote_port; /* remote NBD port on conversion server */ }; -static pid_t start_nbd (int nbd_local_port, const char *device); -static pid_t start_qemu_nbd (int nbd_local_port, const char *device); -static pid_t start_nbdkit (int nbd_local_port, const char *device); -static int wait_nbd (int nbd_local_port, int timeout_seconds); static void cleanup_data_conns (struct data_conn *data_conns, size_t nr); static void generate_name (struct config *, const char *filename); static void generate_libvirt_xml (struct config *, struct data_conn *, const char *filename); @@ -288,14 +281,17 @@ start_conversion (struct config *config, /* Start NBD server listening on the given port number. */ data_conns[i].nbd_pid = - start_nbd (data_conns[i].nbd_local_port, device); - if (data_conns[i].nbd_pid == 0) + start_nbd_server (data_conns[i].nbd_local_port, device); + if (data_conns[i].nbd_pid == 0) { + set_conversion_error ("NBD server error: %s", get_nbd_error ()); goto out; + } - /* Wait for NBD server to listen */ - if (wait_nbd (data_conns[i].nbd_local_port, - WAIT_NBD_TIMEOUT) == -1) + /* Wait for NBD server to start up and listen. */ + if (wait_for_nbd_server_to_start (data_conns[i].nbd_local_port) == -1) { + set_conversion_error ("NBD server error: %s", get_nbd_error ()); goto out; + } } /* Create a remote directory name which will be used for libvirt @@ -465,313 +461,6 @@ cancel_conversion (void) set_cancel_requested (1); } -/** - * Start the first server found in the C<nbd_servers> list. - * - * We previously tested all NBD servers (see C<test_nbd_servers>) so - * we only need to run the first server in the list. - * - * Returns the process ID (E<gt> 0) or C<0> if there is an error. - */ -static pid_t -start_nbd (int port, const char *device) -{ - size_t i; - - for (i = 0; i < NR_NBD_SERVERS; ++i) { - switch (nbd_servers[i]) { - case NO_SERVER: - /* ignore */ - break; - - case QEMU_NBD: - return start_qemu_nbd (port, device); - - case NBDKIT: - return start_nbdkit (port, device); - - default: - abort (); - } - } - - /* This should never happen because of the checks in - * test_nbd_servers. - */ - abort (); -} - -/** - * Start a local L<qemu-nbd(1)> process. - * - * Returns the process ID (E<gt> 0) or C<0> if there is an error. - */ -static pid_t -start_qemu_nbd (int port, const char *device) -{ - pid_t pid; - char port_str[64]; - -#if DEBUG_STDERR - fprintf (stderr, "starting qemu-nbd for %s on port %d\n", device, port); -#endif - - snprintf (port_str, sizeof port_str, "%d", port); - - pid = fork (); - if (pid == -1) { - set_conversion_error ("fork: %m"); - return 0; - } - - if (pid == 0) { /* Child. */ - close (0); - open ("/dev/null", O_RDONLY); - - execlp ("qemu-nbd", - "qemu-nbd", - "-r", /* readonly (vital!) */ - "-p", port_str, /* listening port */ - "-t", /* persistent */ - "-f", "raw", /* force raw format */ - "-b", "localhost", /* listen only on loopback interface */ - "--cache=unsafe", /* use unsafe caching for speed */ - device, /* a device like /dev/sda */ - NULL); - perror ("qemu-nbd"); - _exit (EXIT_FAILURE); - } - - /* Parent. */ - return pid; -} - -/** - * Start a local L<nbdkit(1)> process using the - * L<nbdkit-file-plugin(1)>. - * - * Returns the process ID (E<gt> 0) or C<0> if there is an error. - */ -static pid_t -start_nbdkit (int port, const char *device) -{ - pid_t pid; - char port_str[64]; - CLEANUP_FREE char *file_str = NULL; - -#if DEBUG_STDERR - fprintf (stderr, "starting nbdkit for %s on port %d\n", device, port); -#endif - - snprintf (port_str, sizeof port_str, "%d", port); - - if (asprintf (&file_str, "file=%s", device) == -1) - error (EXIT_FAILURE, errno, "asprintf"); - - pid = fork (); - if (pid == -1) { - set_conversion_error ("fork: %m"); - return 0; - } - - if (pid == 0) { /* Child. */ - close (0); - open ("/dev/null", O_RDONLY); - - execlp ("nbdkit", - "nbdkit", - "-r", /* readonly (vital!) */ - "-p", port_str, /* listening port */ - "-i", "localhost", /* listen only on loopback interface */ - "-f", /* don't fork */ - "file", /* file plugin */ - file_str, /* a device like file=/dev/sda */ - NULL); - perror ("nbdkit"); - _exit (EXIT_FAILURE); - } - - /* Parent. */ - return pid; -} - -static int bind_source_port (int sockfd, int family, int source_port); - -/** - * Connect to C<hostname:dest_port>, resolving the address using - * L<getaddrinfo(3)>. - * - * This also sets the source port of the connection to the first free - * port number E<ge> C<source_port>. - * - * This may involve multiple connections - to IPv4 and IPv6 for - * instance. - */ -static int -connect_with_source_port (const char *hostname, int dest_port, int source_port) -{ - struct addrinfo hints; - struct addrinfo *results, *rp; - char dest_port_str[16]; - int r, sockfd = -1; - int reuseaddr = 1; - - snprintf (dest_port_str, sizeof dest_port_str, "%d", dest_port); - - memset (&hints, 0, sizeof hints); - hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */ - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_NUMERICSERV; /* numeric dest port number */ - hints.ai_protocol = 0; /* any protocol */ - - r = getaddrinfo (hostname, dest_port_str, &hints, &results); - if (r != 0) { - set_conversion_error ("getaddrinfo: %s/%s: %s", - hostname, dest_port_str, gai_strerror (r)); - return -1; - } - - for (rp = results; rp != NULL; rp = rp->ai_next) { - sockfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); - if (sockfd == -1) - continue; - - /* If we run p2v repeatedly (say, running the tests in a loop), - * there's a decent chance we'll end up trying to bind() to a port - * that is in TIME_WAIT from a prior run. Handle that gracefully - * with SO_REUSEADDR. - */ - if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, - &reuseaddr, sizeof reuseaddr) == -1) - perror ("warning: setsockopt"); - - /* Need to bind the source port. */ - if (bind_source_port (sockfd, rp->ai_family, source_port) == -1) { - close (sockfd); - sockfd = -1; - continue; - } - - /* Connect. */ - if (connect (sockfd, rp->ai_addr, rp->ai_addrlen) == -1) { - set_conversion_error ("waiting for NBD server to start: " - "connect to %s/%s: %m", - hostname, dest_port_str); - close (sockfd); - sockfd = -1; - continue; - } - - break; - } - - freeaddrinfo (results); - return sockfd; -} - -static int -bind_source_port (int sockfd, int family, int source_port) -{ - struct addrinfo hints; - struct addrinfo *results, *rp; - char source_port_str[16]; - int r; - - snprintf (source_port_str, sizeof source_port_str, "%d", source_port); - - memset (&hints, 0, sizeof (hints)); - hints.ai_family = family; - hints.ai_socktype = SOCK_STREAM; - hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; /* numeric port number */ - hints.ai_protocol = 0; /* any protocol */ - - r = getaddrinfo ("localhost", source_port_str, &hints, &results); - if (r != 0) { - set_conversion_error ("getaddrinfo (bind): localhost/%s: %s", - source_port_str, gai_strerror (r)); - return -1; - } - - for (rp = results; rp != NULL; rp = rp->ai_next) { - if (bind (sockfd, rp->ai_addr, rp->ai_addrlen) == 0) - goto bound; - } - - set_conversion_error ("waiting for NBD server to start: " - "bind to source port %d: %m", - source_port); - freeaddrinfo (results); - return -1; - - bound: - freeaddrinfo (results); - return 0; -} - -static int -wait_nbd (int nbd_local_port, int timeout_seconds) -{ - int sockfd = -1; - int result = -1; - time_t start_t, now_t; - struct timespec half_sec = { .tv_sec = 0, .tv_nsec = 500000000 }; - struct timeval timeout = { .tv_usec = 0 }; - char magic[8]; /* NBDMAGIC */ - size_t bytes_read = 0; - ssize_t recvd; - - time (&start_t); - - for (;;) { - time (&now_t); - - if (now_t - start_t >= timeout_seconds) - goto cleanup; - - /* Source port for probing NBD server should be one greater than - * nbd_local_port. It's not guaranteed to always bind to this - * port, but it will hint the kernel to start there and try - * incrementally higher ports if needed. This avoids the case - * where the kernel selects nbd_local_port as our source port, and - * we immediately connect to ourself. See: - * https://bugzilla.redhat.com/show_bug.cgi?id=1167774#c9 - */ - sockfd = connect_with_source_port ("localhost", nbd_local_port, - nbd_local_port+1); - if (sockfd >= 0) - break; - - nanosleep (&half_sec, NULL); - } - - time (&now_t); - timeout.tv_sec = (start_t + timeout_seconds) - now_t; - setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout); - - do { - recvd = recv (sockfd, magic, sizeof magic - bytes_read, 0); - - if (recvd == -1) { - set_conversion_error ("waiting for NBD server to start: recv: %m"); - goto cleanup; - } - - bytes_read += recvd; - } while (bytes_read < sizeof magic); - - if (memcmp (magic, "NBDMAGIC", sizeof magic) != 0) { - set_conversion_error ("waiting for NBD server to start: " - "'NBDMAGIC' was not received from NBD server"); - goto cleanup; - } - - result = 0; - cleanup: - close (sockfd); - - return result; -} - static void cleanup_data_conns (struct data_conn *data_conns, size_t nr) { diff --git a/p2v/main.c b/p2v/main.c index f616b4f..d1eece6 100644 --- a/p2v/main.c +++ b/p2v/main.c @@ -54,7 +54,6 @@ int is_iso_environment = 0; int nbd_local_port; int feature_colours_option = 0; int force_colour = 0; -enum nbd_server nbd_servers[NR_NBD_SERVERS] = { QEMU_NBD, NBDKIT }; static const char *test_disk = NULL; static void udevadm_settle (void); @@ -62,7 +61,6 @@ static void set_config_defaults (struct config *config); static void find_all_disks (void); static void find_all_interfaces (void); static int cpuinfo_flags (void); -static void test_nbd_servers (void); enum { HELP_OPTION = CHAR_MAX + 1 }; static const char options[] = "Vv"; @@ -133,35 +131,6 @@ display_long_options (const struct option *long_options) exit (EXIT_SUCCESS); } -static void -set_nbd_servers (const char *opt) -{ - size_t i; - CLEANUP_FREE_STRING_LIST char **strs - = guestfs_int_split_string (',', opt); - - if (strs == NULL) - error (EXIT_FAILURE, errno, _("malloc")); - - if (strs[0] == NULL) - error (EXIT_FAILURE, 0, _("--nbd option cannot be empty")); - - for (i = 0; strs[i] != NULL; ++i) { - if (i >= NR_NBD_SERVERS) - error (EXIT_FAILURE, 0, _("too many --nbd servers")); - - if (STREQ (strs[i], "qemu-nbd") || STREQ (strs[i], "qemu")) - nbd_servers[i] = QEMU_NBD; - else if (STREQ (strs[i], "nbdkit")) - nbd_servers[i] = NBDKIT; - else - error (EXIT_FAILURE, 0, _("--nbd: unknown server: %s"), strs[i]); - } - - for (; i < NR_NBD_SERVERS; ++i) - nbd_servers[i] = NO_SERVER; -} - int main (int argc, char *argv[]) { @@ -214,7 +183,7 @@ main (int argc, char *argv[]) is_iso_environment = 1; } else if (STREQ (long_options[option_index].name, "nbd")) { - set_nbd_servers (optarg); + set_nbd_option (optarg); /* in nbd.c */ } else if (STREQ (long_options[option_index].name, "test-disk")) { if (test_disk != NULL) @@ -309,59 +278,6 @@ udevadm_settle (void) ignore_value (system ("udevadm settle")); } -/* Test the nbd_servers[] array to see which servers are actually - * installed and appear to be working. If a server is not installed - * we set the corresponding nbd_servers[i] = NO_SERVER. - */ -static void -test_nbd_servers (void) -{ - size_t i; - int r; - - for (i = 0; i < NR_NBD_SERVERS; ++i) { - switch (nbd_servers[i]) { - case NO_SERVER: - /* ignore entry */ - break; - - case QEMU_NBD: - r = system ("qemu-nbd --version" -#ifndef DEBUG_STDERR - " >/dev/null 2>&1" -#endif - ); - if (r != 0) - nbd_servers[i] = NO_SERVER; - break; - - case NBDKIT: - r = system ("nbdkit file --version" -#ifndef DEBUG_STDERR - " >/dev/null 2>&1" -#endif - ); - if (r != 0) - nbd_servers[i] = NO_SERVER; - break; - - default: - abort (); - } - } - - for (i = 0; i < NR_NBD_SERVERS; ++i) - if (nbd_servers[i] != NO_SERVER) - return; - - /* If we reach here, there are no servers left, so we have to exit. */ - fprintf (stderr, - _("%s: no working NBD server was found, cannot continue.\n" - "Please check the --nbd option in the virt-p2v(1) man page.\n"), - getprogname ()); - exit (EXIT_FAILURE); -} - static void set_config_defaults (struct config *config) { diff --git a/p2v/nbd.c b/p2v/nbd.c new file mode 100644 index 0000000..519774f --- /dev/null +++ b/p2v/nbd.c @@ -0,0 +1,488 @@ +/* virt-p2v + * Copyright (C) 2009-2017 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/** + * This file handles the virt-p2v I<--nbd> command line option + * and running either L<qemu-nbd(8)> or L<nbdkit(1)>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <inttypes.h> +#include <fcntl.h> +#include <unistd.h> +#include <time.h> +#include <netdb.h> +#include <errno.h> +#include <error.h> +#include <libintl.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include "getprogname.h" + +#include "p2v.h" + +/* How long to wait for the NBD server to start (seconds). */ +#define WAIT_NBD_TIMEOUT 10 + +/* virt-p2v --nbd option. */ +enum nbd_server { + NO_SERVER = 0, + QEMU_NBD = 1, + NBDKIT = 2, +#define NR_NBD_SERVERS 2 +}; +static enum nbd_server nbd_servers[NR_NBD_SERVERS] = { QEMU_NBD, NBDKIT }; + +static pid_t start_qemu_nbd (int nbd_local_port, const char *device); +static pid_t start_nbdkit (int nbd_local_port, const char *device); +static int connect_with_source_port (const char *hostname, int dest_port, int source_port); +static int bind_source_port (int sockfd, int family, int source_port); + +static char *nbd_error; + +static void set_nbd_error (const char *fs, ...) + __attribute__((format(printf,1,2))); + +static void +set_nbd_error (const char *fs, ...) +{ + va_list args; + char *msg; + int len; + + va_start (args, fs); + len = vasprintf (&msg, fs, args); + va_end (args); + + if (len < 0) + error (EXIT_FAILURE, errno, + "vasprintf (original error format string: %s)", fs); + + free (nbd_error); + nbd_error = msg; +} + +const char * +get_nbd_error (void) +{ + return nbd_error; +} + +/** + * The main program calls this to set the I<--nbd> option. + */ +void +set_nbd_option (const char *opt) +{ + size_t i; + CLEANUP_FREE_STRING_LIST char **strs + = guestfs_int_split_string (',', opt); + + if (strs == NULL) + error (EXIT_FAILURE, errno, _("malloc")); + + if (strs[0] == NULL) + error (EXIT_FAILURE, 0, _("--nbd option cannot be empty")); + + for (i = 0; strs[i] != NULL; ++i) { + if (i >= NR_NBD_SERVERS) + error (EXIT_FAILURE, 0, _("too many --nbd servers")); + + if (STREQ (strs[i], "qemu-nbd") || STREQ (strs[i], "qemu")) + nbd_servers[i] = QEMU_NBD; + else if (STREQ (strs[i], "nbdkit")) + nbd_servers[i] = NBDKIT; + else + error (EXIT_FAILURE, 0, _("--nbd: unknown server: %s"), strs[i]); + } + + for (; i < NR_NBD_SERVERS; ++i) + nbd_servers[i] = NO_SERVER; +} + +/** + * Test the I<--nbd> option (or built-in default list) to see which + * servers are actually installed and appear to be working. + * + * If a server is not installed we set the corresponding + * C<nbd_servers[i] = NO_SERVER>. + */ +void +test_nbd_servers (void) +{ + size_t i; + int r; + + for (i = 0; i < NR_NBD_SERVERS; ++i) { + switch (nbd_servers[i]) { + case NO_SERVER: + /* ignore entry */ + break; + + case QEMU_NBD: + r = system ("qemu-nbd --version" +#ifndef DEBUG_STDERR + " >/dev/null 2>&1" +#endif + ); + if (r != 0) + nbd_servers[i] = NO_SERVER; + break; + + case NBDKIT: + r = system ("nbdkit file --version" +#ifndef DEBUG_STDERR + " >/dev/null 2>&1" +#endif + ); + if (r != 0) + nbd_servers[i] = NO_SERVER; + break; + + default: + abort (); + } + } + + for (i = 0; i < NR_NBD_SERVERS; ++i) + if (nbd_servers[i] != NO_SERVER) + return; + + /* If we reach here, there are no servers left, so we have to exit. */ + fprintf (stderr, + _("%s: no working NBD server was found, cannot continue.\n" + "Please check the --nbd option in the virt-p2v(1) man page.\n"), + getprogname ()); + exit (EXIT_FAILURE); +} + +/** + * Start the first server found in the C<nbd_servers> list. + * + * We previously tested all NBD servers (see C<test_nbd_servers>) so + * we only need to run the first server in the list. + * + * Returns the process ID (E<gt> 0) or C<0> if there is an error. + */ +pid_t +start_nbd_server (int port, const char *device) +{ + size_t i; + + for (i = 0; i < NR_NBD_SERVERS; ++i) { + switch (nbd_servers[i]) { + case NO_SERVER: + /* ignore */ + break; + + case QEMU_NBD: + return start_qemu_nbd (port, device); + + case NBDKIT: + return start_nbdkit (port, device); + + default: + abort (); + } + } + + /* This should never happen because of the checks in + * test_nbd_servers. + */ + abort (); +} + +/** + * Start a local L<qemu-nbd(1)> process. + * + * Returns the process ID (E<gt> 0) or C<0> if there is an error. + */ +static pid_t +start_qemu_nbd (int port, const char *device) +{ + pid_t pid; + char port_str[64]; + +#if DEBUG_STDERR + fprintf (stderr, "starting qemu-nbd for %s on port %d\n", device, port); +#endif + + snprintf (port_str, sizeof port_str, "%d", port); + + pid = fork (); + if (pid == -1) { + set_nbd_error ("fork: %m"); + return 0; + } + + if (pid == 0) { /* Child. */ + close (0); + open ("/dev/null", O_RDONLY); + + execlp ("qemu-nbd", + "qemu-nbd", + "-r", /* readonly (vital!) */ + "-p", port_str, /* listening port */ + "-t", /* persistent */ + "-f", "raw", /* force raw format */ + "-b", "localhost", /* listen only on loopback interface */ + "--cache=unsafe", /* use unsafe caching for speed */ + device, /* a device like /dev/sda */ + NULL); + perror ("qemu-nbd"); + _exit (EXIT_FAILURE); + } + + /* Parent. */ + return pid; +} + +/** + * Start a local L<nbdkit(1)> process using the + * L<nbdkit-file-plugin(1)>. + * + * Returns the process ID (E<gt> 0) or C<0> if there is an error. + */ +static pid_t +start_nbdkit (int port, const char *device) +{ + pid_t pid; + char port_str[64]; + CLEANUP_FREE char *file_str = NULL; + +#if DEBUG_STDERR + fprintf (stderr, "starting nbdkit for %s on port %d\n", device, port); +#endif + + snprintf (port_str, sizeof port_str, "%d", port); + + if (asprintf (&file_str, "file=%s", device) == -1) + error (EXIT_FAILURE, errno, "asprintf"); + + pid = fork (); + if (pid == -1) { + set_nbd_error ("fork: %m"); + return 0; + } + + if (pid == 0) { /* Child. */ + close (0); + open ("/dev/null", O_RDONLY); + + execlp ("nbdkit", + "nbdkit", + "-r", /* readonly (vital!) */ + "-p", port_str, /* listening port */ + "-i", "localhost", /* listen only on loopback interface */ + "-f", /* don't fork */ + "file", /* file plugin */ + file_str, /* a device like file=/dev/sda */ + NULL); + perror ("nbdkit"); + _exit (EXIT_FAILURE); + } + + /* Parent. */ + return pid; +} + +/** + * Wait for a local NBD server to start and be listening for + * connections. + */ +int +wait_for_nbd_server_to_start (int nbd_local_port) +{ + int sockfd = -1; + int result = -1; + time_t start_t, now_t; + struct timespec half_sec = { .tv_sec = 0, .tv_nsec = 500000000 }; + struct timeval timeout = { .tv_usec = 0 }; + char magic[8]; /* NBDMAGIC */ + size_t bytes_read = 0; + ssize_t recvd; + + time (&start_t); + + for (;;) { + time (&now_t); + + if (now_t - start_t >= WAIT_NBD_TIMEOUT) { + set_nbd_error ("timed out waiting for NBD server to start"); + goto cleanup; + } + + /* Source port for probing NBD server should be one greater than + * nbd_local_port. It's not guaranteed to always bind to this + * port, but it will hint the kernel to start there and try + * incrementally higher ports if needed. This avoids the case + * where the kernel selects nbd_local_port as our source port, and + * we immediately connect to ourself. See: + * https://bugzilla.redhat.com/show_bug.cgi?id=1167774#c9 + */ + sockfd = connect_with_source_port ("localhost", nbd_local_port, + nbd_local_port+1); + if (sockfd >= 0) + break; + + nanosleep (&half_sec, NULL); + } + + time (&now_t); + timeout.tv_sec = (start_t + WAIT_NBD_TIMEOUT) - now_t; + setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof timeout); + + do { + recvd = recv (sockfd, magic, sizeof magic - bytes_read, 0); + + if (recvd == -1) { + set_nbd_error ("waiting for NBD server to start: recv: %m"); + goto cleanup; + } + + bytes_read += recvd; + } while (bytes_read < sizeof magic); + + if (memcmp (magic, "NBDMAGIC", sizeof magic) != 0) { + set_nbd_error ("waiting for NBD server to start: " + "'NBDMAGIC' was not received from NBD server"); + goto cleanup; + } + + result = 0; + cleanup: + close (sockfd); + + return result; +} + +/** + * Connect to C<hostname:dest_port>, resolving the address using + * L<getaddrinfo(3)>. + * + * This also sets the source port of the connection to the first free + * port number E<ge> C<source_port>. + * + * This may involve multiple connections - to IPv4 and IPv6 for + * instance. + */ +static int +connect_with_source_port (const char *hostname, int dest_port, int source_port) +{ + struct addrinfo hints; + struct addrinfo *results, *rp; + char dest_port_str[16]; + int r, sockfd = -1; + int reuseaddr = 1; + + snprintf (dest_port_str, sizeof dest_port_str, "%d", dest_port); + + memset (&hints, 0, sizeof hints); + hints.ai_family = AF_UNSPEC; /* allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICSERV; /* numeric dest port number */ + hints.ai_protocol = 0; /* any protocol */ + + r = getaddrinfo (hostname, dest_port_str, &hints, &results); + if (r != 0) { + set_nbd_error ("getaddrinfo: %s/%s: %s", + hostname, dest_port_str, gai_strerror (r)); + return -1; + } + + for (rp = results; rp != NULL; rp = rp->ai_next) { + sockfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol); + if (sockfd == -1) + continue; + + /* If we run p2v repeatedly (say, running the tests in a loop), + * there's a decent chance we'll end up trying to bind() to a port + * that is in TIME_WAIT from a prior run. Handle that gracefully + * with SO_REUSEADDR. + */ + if (setsockopt (sockfd, SOL_SOCKET, SO_REUSEADDR, + &reuseaddr, sizeof reuseaddr) == -1) + perror ("warning: setsockopt"); + + /* Need to bind the source port. */ + if (bind_source_port (sockfd, rp->ai_family, source_port) == -1) { + close (sockfd); + sockfd = -1; + continue; + } + + /* Connect. */ + if (connect (sockfd, rp->ai_addr, rp->ai_addrlen) == -1) { + set_nbd_error ("waiting for NBD server to start: " + "connect to %s/%s: %m", + hostname, dest_port_str); + close (sockfd); + sockfd = -1; + continue; + } + + break; + } + + freeaddrinfo (results); + return sockfd; +} + +static int +bind_source_port (int sockfd, int family, int source_port) +{ + struct addrinfo hints; + struct addrinfo *results, *rp; + char source_port_str[16]; + int r; + + snprintf (source_port_str, sizeof source_port_str, "%d", source_port); + + memset (&hints, 0, sizeof (hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV; /* numeric port number */ + hints.ai_protocol = 0; /* any protocol */ + + r = getaddrinfo ("localhost", source_port_str, &hints, &results); + if (r != 0) { + set_nbd_error ("getaddrinfo (bind): localhost/%s: %s", + source_port_str, gai_strerror (r)); + return -1; + } + + for (rp = results; rp != NULL; rp = rp->ai_next) { + if (bind (sockfd, rp->ai_addr, rp->ai_addrlen) == 0) + goto bound; + } + + set_nbd_error ("waiting for NBD server to start: " + "bind to source port %d: %m", + source_port); + freeaddrinfo (results); + return -1; + + bound: + freeaddrinfo (results); + return 0; +} diff --git a/p2v/p2v.h b/p2v/p2v.h index d805aaf..800862d 100644 --- a/p2v/p2v.h +++ b/p2v/p2v.h @@ -63,15 +63,6 @@ extern int feature_colours_option; /* virt-p2v --colours option (used by ansi_* macros). */ extern int force_colour; -/* virt-p2v --nbd option. */ -enum nbd_server { - NO_SERVER = 0, - QEMU_NBD = 1, - NBDKIT = 2, -#define NR_NBD_SERVERS 2 -}; -extern enum nbd_server nbd_servers[NR_NBD_SERVERS]; - /* config.c */ struct config { char *server; @@ -144,6 +135,13 @@ extern mexp_h *start_remote_connection (struct config *, const char *remote_dir) extern const char *get_ssh_error (void); extern int scp_file (struct config *config, const char *localfile, const char *remotefile); +/* nbd.c */ +extern void set_nbd_option (const char *opt); +extern void test_nbd_servers (void); +extern pid_t start_nbd_server (int nbd_local_port, const char *device); +extern int wait_for_nbd_server_to_start (int nbd_local_port); +const char *get_nbd_error (void); + /* utils.c */ extern uint64_t get_blockdev_size (const char *dev); extern char *get_blockdev_model (const char *dev); -- 2.9.3