[Libguestfs] [PATCH 1/5] p2v: Move NBD-related functions into a separate file.

Richard W.M. Jones rjones at redhat.com
Fri Feb 3 14:21:14 UTC 2017


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




More information about the Libguestfs mailing list