change strncmp(...) != 0 to STRNEQLEN(...)
[libguestfs.git] / daemon / guestfsd.c
index b4fb15e..61a6236 100644 (file)
 #include <sys/wait.h>
 #include <sys/stat.h>
 #include <fcntl.h>
-#include <ctype.h>
 #include <signal.h>
 #include <printf.h>
 
+#include "c-ctype.h"
+#include "ignore-value.h"
+
 #include "daemon.h"
 
-static void usage (void);
 static char *read_cmdline (void);
 
 /* Also in guestfs.c */
@@ -65,18 +66,28 @@ static int print_arginfo (const struct printf_info *info, size_t n, int *argtype
 const char *sysroot = "/sysroot"; /* No trailing slash. */
 int sysroot_len = 8;
 
+static void
+usage (void)
+{
+  fprintf (stderr,
+    "guestfsd [-f|--foreground] [-c|--channel vmchannel] [-v|--verbose]\n");
+}
+
 int
 main (int argc, char *argv[])
 {
-  static const char *options = "f?";
+  static const char *options = "fc:v?";
   static const struct option long_options[] = {
+    { "channel", required_argument, 0, 'c' },
     { "foreground", 0, 0, 'f' },
     { "help", 0, 0, '?' },
+    { "verbose", 0, 0, 'v' },
     { 0, 0, 0, 0 }
   };
   int c;
   int dont_fork = 0;
   char *cmdline;
+  char *vmchannel = NULL;
 
 #ifdef HAVE_REGISTER_PRINTF_SPECIFIER
   /* http://udrepper.livejournal.com/20948.html */
@@ -96,10 +107,18 @@ main (int argc, char *argv[])
     if (c == -1) break;
 
     switch (c) {
+    case 'c':
+      vmchannel = optarg;
+      break;
+
     case 'f':
       dont_fork = 1;
       break;
 
+    case 'v':
+      verbose = 1;
+      break;
+
     case '?':
       usage ();
       exit (0);
@@ -118,7 +137,8 @@ main (int argc, char *argv[])
   cmdline = read_cmdline ();
 
   /* Set the verbose flag. */
-  verbose = cmdline && strstr (cmdline, "guestfs_verbose=1") != NULL;
+  verbose = verbose ||
+    (cmdline && strstr (cmdline, "guestfs_verbose=1") != NULL);
   if (verbose)
     printf ("verbose daemon enabled\n");
 
@@ -148,38 +168,129 @@ main (int argc, char *argv[])
   /* We document that umask defaults to 022 (it should be this anyway). */
   umask (022);
 
-  /* Resolve the hostname. */
-  struct addrinfo *res, *rr;
-  struct addrinfo hints;
-  int r;
-  memset (&hints, 0, sizeof hints);
-  hints.ai_socktype = SOCK_STREAM;
-  hints.ai_flags = AI_ADDRCONFIG;
-  r = getaddrinfo (GUESTFWD_ADDR, GUESTFWD_PORT, &hints, &res);
-  if (r != 0) {
-    fprintf (stderr, "%s:%s: %s\n",
-             GUESTFWD_ADDR, GUESTFWD_PORT, gai_strerror (r));
-    exit (1);
+  /* Get the vmchannel string.
+   *
+   * Sources:
+   *   --channel/-c option on the command line
+   *   guestfs_vmchannel=... from the kernel command line
+   *   guestfs=... from the kernel command line
+   *   built-in default
+   *
+   * At the moment we expect this to contain "tcp:ip:port" but in
+   * future it might contain a device name, eg. "/dev/vcon4" for
+   * virtio-console vmchannel.
+   */
+  if (vmchannel == NULL && cmdline) {
+    char *p;
+    size_t len;
+
+    p = strstr (cmdline, "guestfs_vmchannel=");
+    if (p) {
+      len = strcspn (p + 18, " \t\n");
+      vmchannel = strndup (p + 18, len);
+      if (!vmchannel) {
+        perror ("strndup");
+        exit (1);
+      }
+    }
+
+    /* Old libraries passed guestfs=host:port.  Rewrite it as tcp:host:port. */
+    if (vmchannel == NULL) {
+      /* We will rewrite it part of the "guestfs=" string with
+       *                       "tcp:"       hence p + 4 below.    */
+      p = strstr (cmdline, "guestfs=");
+      if (p) {
+        len = strcspn (p + 4, " \t\n");
+        vmchannel = strndup (p + 4, len);
+        if (!vmchannel) {
+          perror ("strndup");
+          exit (1);
+        }
+        memcpy (vmchannel, "tcp:", 4);
+      }
+    }
+  }
+
+  /* Default vmchannel. */
+  if (vmchannel == NULL) {
+    vmchannel = strdup ("tcp:" GUESTFWD_ADDR ":" GUESTFWD_PORT);
+    if (!vmchannel) {
+      perror ("strdup");
+      exit (1);
+    }
   }
 
-  /* Connect to the given TCP socket. */
+  if (verbose)
+    printf ("vmchannel: %s\n", vmchannel);
+
+  /* Connect to vmchannel. */
   int sock = -1;
-  for (rr = res; rr != NULL; rr = rr->ai_next) {
-    sock = socket (rr->ai_family, rr->ai_socktype, rr->ai_protocol);
-    if (sock != -1) {
-      if (connect (sock, rr->ai_addr, rr->ai_addrlen) == 0)
-        break;
-      perror ("connect");
-
-      close (sock);
-      sock = -1;
+
+  if (strncmp (vmchannel, "tcp:", 4) == 0) {
+    /* Resolve the hostname. */
+    struct addrinfo *res, *rr;
+    struct addrinfo hints;
+    int r;
+    char *host, *port;
+
+    host = vmchannel+4;
+    port = strchr (host, ':');
+    if (port) {
+      port[0] = '\0';
+      port++;
+    } else {
+      fprintf (stderr, "vmchannel: expecting \"tcp:<ip>:<port>\": %s\n",
+               vmchannel);
+      exit (1);
     }
+
+    memset (&hints, 0, sizeof hints);
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_ADDRCONFIG;
+    r = getaddrinfo (host, port, &hints, &res);
+    if (r != 0) {
+      fprintf (stderr, "%s:%s: %s\n",
+               host, port, gai_strerror (r));
+      exit (1);
+    }
+
+    /* Connect to the given TCP socket. */
+    for (rr = res; rr != NULL; rr = rr->ai_next) {
+      sock = socket (rr->ai_family, rr->ai_socktype, rr->ai_protocol);
+      if (sock != -1) {
+        if (connect (sock, rr->ai_addr, rr->ai_addrlen) == 0)
+          break;
+        perror ("connect");
+
+        close (sock);
+        sock = -1;
+      }
+    }
+    freeaddrinfo (res);
+  } else {
+    fprintf (stderr,
+             "unknown vmchannel connection type: %s\n"
+             "expecting \"tcp:<ip>:<port>\"\n",
+             vmchannel);
+    exit (1);
   }
-  freeaddrinfo (res);
 
   if (sock == -1) {
-    fprintf (stderr, "connection to %s:%s failed\n",
-             GUESTFWD_ADDR, GUESTFWD_PORT);
+    fprintf (stderr,
+             "\n"
+             "Failed to connect to any vmchannel implementation.\n"
+             "vmchannel: %s\n"
+             "\n"
+             "This is a fatal error and the appliance will now exit.\n"
+             "\n"
+             "Usually this error is caused by either QEMU or the appliance\n"
+             "kernel not supporting the vmchannel method that the\n"
+             "libguestfs library chose to use.  Please run\n"
+             "'libguestfs-test-tool' and provide the complete, unedited\n"
+             "output to the libguestfs developers, either in a bug report\n"
+             "or on the libguestfs redhat com mailing list.\n"
+             "\n",
+             vmchannel);
     exit (1);
   }
 
@@ -323,12 +434,6 @@ xread (int sock, void *v_buf, size_t len)
   return 0;
 }
 
-static void
-usage (void)
-{
-  fprintf (stderr, "guestfsd [-f]\n");
-}
-
 int
 add_string (char ***argv, int *size, int *alloc, const char *str)
 {
@@ -405,13 +510,11 @@ free_stringslen (char **argv, int len)
   free (argv);
 }
 
-/* This is a more sane version of 'system(3)' for running external
- * commands.  It uses fork/execvp, so we don't need to worry about
- * quoting of parameters, and it allows us to capture any error
- * messages in a buffer.
+/* Easy ways to run external commands.  For full documentation, see
+ * 'commandrvf' below.
  */
 int
-command (char **stdoutput, char **stderror, const char *name, ...)
+commandf (char **stdoutput, char **stderror, int flags, const char *name, ...)
 {
   va_list args;
   const char **argv;
@@ -445,7 +548,7 @@ command (char **stdoutput, char **stderror, const char *name, ...)
 
   va_end (args);
 
-  r = commandv (stdoutput, stderror, (char **) argv);
+  r = commandvf (stdoutput, stderror, flags, (const char * const*) argv);
 
   /* NB: Mustn't free the strings which are on the stack. */
   free (argv);
@@ -458,7 +561,7 @@ command (char **stdoutput, char **stderror, const char *name, ...)
  * We still return -1 if there was some other error.
  */
 int
-commandr (char **stdoutput, char **stderror, const char *name, ...)
+commandrf (char **stdoutput, char **stderror, int flags, const char *name, ...)
 {
   va_list args;
   const char **argv;
@@ -492,7 +595,7 @@ commandr (char **stdoutput, char **stderror, const char *name, ...)
 
   va_end (args);
 
-  r = commandrv (stdoutput, stderror, argv);
+  r = commandrvf (stdoutput, stderror, flags, argv);
 
   /* NB: Mustn't free the strings which are on the stack. */
   free (argv);
@@ -502,19 +605,43 @@ commandr (char **stdoutput, char **stderror, const char *name, ...)
 
 /* Same as 'command', but passing an argv. */
 int
-commandv (char **stdoutput, char **stderror, char *const *argv)
+commandvf (char **stdoutput, char **stderror, int flags,
+           char const *const *argv)
 {
   int r;
 
-  r = commandrv (stdoutput, stderror, (void *) argv);
+  r = commandrvf (stdoutput, stderror, flags, (void *) argv);
   if (r == 0)
     return 0;
   else
     return -1;
 }
 
+/* This is a more sane version of 'system(3)' for running external
+ * commands.  It uses fork/execvp, so we don't need to worry about
+ * quoting of parameters, and it allows us to capture any error
+ * messages in a buffer.
+ *
+ * If stdoutput is not NULL, then *stdoutput will return the stdout
+ * of the command.
+ *
+ * If stderror is not NULL, then *stderror will return the stderr
+ * of the command.  If there is a final \n character, it is removed
+ * so you can use the error string directly in a call to
+ * reply_with_error.
+ *
+ * Flags:
+ *
+ * COMMAND_FLAG_FOLD_STDOUT_ON_STDERR: For broken external commands
+ * that send error messages to stdout (hello, parted) but that don't
+ * have any useful stdout information, use this flag to capture the
+ * error messages in the *stderror buffer.  If using this flag,
+ * you should pass stdoutput as NULL because nothing could ever be
+ * captured in that buffer.
+ */
 int
-commandrv (char **stdoutput, char **stderror, char const* const *argv)
+commandrvf (char **stdoutput, char **stderror, int flags,
+            char const* const *argv)
 {
   int so_size = 0, se_size = 0;
   int so_fd[2], se_fd[2];
@@ -551,9 +678,13 @@ commandrv (char **stdoutput, char **stderror, char const* const *argv)
 
   if (pid == 0) {              /* Child process. */
     close (0);
+    open ("/dev/null", O_RDONLY); /* Set stdin to /dev/null (ignore failure) */
     close (so_fd[0]);
     close (se_fd[0]);
-    dup2 (so_fd[1], 1);
+    if (!(flags & COMMAND_FLAG_FOLD_STDOUT_ON_STDERR))
+      dup2 (so_fd[1], 1);
+    else
+      dup2 (se_fd[1], 1);
     dup2 (se_fd[1], 2);
     close (so_fd[1]);
     close (se_fd[1]);
@@ -614,15 +745,20 @@ commandrv (char **stdoutput, char **stderror, char const* const *argv)
       }
       if (r == 0) { FD_CLR (se_fd[0], &rset); quit++; }
 
-      if (r > 0 && stderror) {
-        se_size += r;
-        p = realloc (*stderror, se_size);
-        if (p == NULL) {
-          perror ("realloc");
-          goto quit;
+      if (r > 0) {
+        if (verbose)
+          ignore_value (write (2, buf, r));
+
+        if (stderror) {
+          se_size += r;
+          p = realloc (*stderror, se_size);
+          if (p == NULL) {
+            perror ("realloc");
+            goto quit;
+          }
+          *stderror = p;
+          memcpy (*stderror + se_size - r, buf, r);
         }
-        *stderror = p;
-        memcpy (*stderror + se_size - r, buf, r);
       }
     }
   }
@@ -732,7 +868,7 @@ print_shell_quote (FILE *stream,
                    const struct printf_info *info ATTRIBUTE_UNUSED,
                    const void *const *args)
 {
-#define SAFE(c) (isalnum((c)) ||                                       \
+#define SAFE(c) (c_isalnum((c)) ||                                     \
                  (c) == '/' || (c) == '-' || (c) == '_' || (c) == '.')
   int i, len;
   const char *str = *((const char **) (args[0]));
@@ -809,7 +945,7 @@ device_name_translation (char *device, const char *func)
   }
 
   /* If the name begins with "/dev/sd" then try the alternatives. */
-  if (strncmp (device, "/dev/sd", 7) != 0)
+  if (STRNEQLEN (device, "/dev/sd", 7))
     goto error;
 
   device[5] = 'h';             /* /dev/hd (old IDE driver) */