Blob Blame History Raw
From dfa319bc27420a611311834108e6807b300fc2b3 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Tue, 9 Jun 2015 17:53:05 +0100
Subject: [PATCH] p2v: Correct parsing of /proc/cmdline, including quoting.

Fix the parsing of /proc/cmdline, including allowing double quoting of
parameters.

This allows you to pass parameters to p2v on the command line which
include spaces.

(cherry picked from commit 716244c33718c866edce9e7ee8f21ee612f53337)
---
 p2v/Makefile.am      |   1 +
 p2v/kernel-cmdline.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++
 p2v/kernel.c         | 143 ++++++++++++++++----------------------
 p2v/main.c           |  55 ++++-----------
 p2v/p2v.h            |  10 ++-
 po/POTFILES          |   1 +
 6 files changed, 277 insertions(+), 126 deletions(-)
 create mode 100644 p2v/kernel-cmdline.c

diff --git a/p2v/Makefile.am b/p2v/Makefile.am
index c9d6b6f..cafe597 100644
--- a/p2v/Makefile.am
+++ b/p2v/Makefile.am
@@ -49,6 +49,7 @@ virt_p2v_SOURCES = \
 	copying.c \
 	gui.c \
 	kernel.c \
+	kernel-cmdline.c \
 	main.c \
 	miniexpect.c \
 	miniexpect.h \
diff --git a/p2v/kernel-cmdline.c b/p2v/kernel-cmdline.c
new file mode 100644
index 0000000..142108a
--- /dev/null
+++ b/p2v/kernel-cmdline.c
@@ -0,0 +1,193 @@
+/* virt-p2v
+ * Copyright (C) 2015 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.
+ */
+
+/* Read /proc/cmdline.
+ *
+ * We only support double quoting, consistent with the Linux
+ * documentation.
+ * https://www.kernel.org/doc/Documentation/kernel-parameters.txt
+ *
+ * systemd supports single and double quoting and single character
+ * escaping, but we don't support all that.
+ *
+ * Returns a list of key, value pairs, terminated by NULL.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "p2v.h"
+
+static void
+add_null (char ***argv, size_t *lenp)
+{
+  (*lenp)++;
+  *argv = realloc (*argv, *lenp * sizeof (char *));
+  if (*argv == NULL) {
+    perror ("realloc");
+    exit (EXIT_FAILURE);
+  }
+  (*argv)[(*lenp)-1] = NULL;
+}
+
+static void
+add_string (char ***argv, size_t *lenp, const char *str, size_t len)
+{
+  add_null (argv, lenp);
+  (*argv)[(*lenp)-1] = strndup (str, len);
+  if ((*argv)[(*lenp)-1] == NULL) {
+    perror ("strndup");
+    exit (EXIT_FAILURE);
+  }
+}
+
+char **
+parse_cmdline_string (const char *cmdline)
+{
+  char **ret = NULL;
+  size_t len = 0;
+  const char *p, *key = NULL, *value = NULL;
+  enum {
+    KEY_START = 0,
+    KEY,
+    VALUE_START,
+    VALUE,
+    VALUE_QUOTED
+  } state = 0;
+
+  for (p = cmdline; *p; p++) {
+    switch (state) {
+    case KEY_START:             /* looking for the start of a key */
+      if (*p == ' ') continue;
+      key = p;
+      state = KEY;
+      break;
+
+    case KEY:                   /* reading key */
+      if (*p == ' ') {
+        add_string (&ret, &len, key, p-key);
+        add_string (&ret, &len, "", 0);
+        state = KEY_START;
+      }
+      else if (*p == '=') {
+        add_string (&ret, &len, key, p-key);
+        state = VALUE_START;
+      }
+      break;
+
+    case VALUE_START:           /* looking for the start of a value */
+      if (*p == ' ') {
+        add_string (&ret, &len, "", 0);
+        state = KEY_START;
+      }
+      else if (*p == '"') {
+        value = p+1;
+        state = VALUE_QUOTED;
+      }
+      else {
+        value = p;
+        state = VALUE;
+      }
+      break;
+
+    case VALUE:                 /* reading unquoted value */
+      if (*p == ' ') {
+        add_string (&ret, &len, value, p-value);
+        state = KEY_START;
+      }
+      break;
+
+    case VALUE_QUOTED:          /* reading quoted value */
+      if (*p == '"') {
+        add_string (&ret, &len, value, p-value);
+        state = KEY_START;
+      }
+      break;
+    }
+  }
+
+  switch (state) {
+  case KEY_START: break;
+  case KEY:                     /* key followed by end of string */
+    add_string (&ret, &len, key, p-key);
+    add_string (&ret, &len, "", 0);
+    break;
+  case VALUE_START:             /* key= followed by end of string */
+    add_string (&ret, &len, "", 0);
+    break;
+  case VALUE:                   /* key=value followed by end of string */
+    add_string (&ret, &len, value, p-value);
+    break;
+  case VALUE_QUOTED:            /* unterminated key="value" */
+    fprintf (stderr, "%s: warning: unterminated quoted string on kernel command line\n",
+             guestfs_int_program_name);
+    add_string (&ret, &len, value, p-value);
+  }
+
+  add_null (&ret, &len);
+
+  return ret;
+}
+
+char **
+parse_proc_cmdline (void)
+{
+  CLEANUP_FCLOSE FILE *fp = NULL;
+  CLEANUP_FREE char *cmdline = NULL;
+  size_t len = 0;
+
+  fp = fopen ("/proc/cmdline", "re");
+  if (fp == NULL) {
+    perror ("/proc/cmdline");
+    return NULL;
+  }
+
+  if (getline (&cmdline, &len, fp) == -1) {
+    perror ("getline");
+    return NULL;
+  }
+
+  /* 'len' is not the length of the string, but the length of the
+   * buffer.  We need to chomp the string.
+   */
+  len = strlen (cmdline);
+
+  if (len >= 1 && cmdline[len-1] == '\n')
+    cmdline[len-1] = '\0';
+
+  return parse_cmdline_string (cmdline);
+}
+
+const char *
+get_cmdline_key (char **argv, const char *key)
+{
+  size_t i;
+
+  for (i = 0; argv[i] != NULL; i += 2) {
+    if (STREQ (argv[i], key))
+      return argv[i+1];
+  }
+
+  /* Not found. */
+  return NULL;
+}
diff --git a/p2v/kernel.c b/p2v/kernel.c
index 481ac78..88d18bd 100644
--- a/p2v/kernel.c
+++ b/p2v/kernel.c
@@ -35,46 +35,38 @@
 static void notify_ui_callback (int type, const char *data);
 
 void
-kernel_configuration (struct config *config, const char *cmdline)
+kernel_configuration (struct config *config, char **cmdline, int cmdline_source)
 {
-  const char *r;
-  size_t len;
+  const char *p;
 
-  r = strstr (cmdline, "p2v.server=");
-  assert (r); /* checked by caller */
-  r += 5+6;
-  len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.server");
+  assert (p); /* checked by caller */
   free (config->server);
-  config->server = strndup (r, len);
+  config->server = strdup (p);
 
-  r = strstr (cmdline, "p2v.port=");
-  if (r) {
-    r += 5+4;
-    if (sscanf (r, "%d", &config->port) != 1) {
+  p = get_cmdline_key (cmdline, "p2v.port");
+  if (p) {
+    if (sscanf (p, "%d", &config->port) != 1) {
       fprintf (stderr, "%s: cannot parse p2v.port from kernel command line",
                guestfs_int_program_name);
       exit (EXIT_FAILURE);
     }
   }
 
-  r = strstr (cmdline, "p2v.username=");
-  if (r) {
-    r += 5+8;
-    len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.username");
+  if (p) {
     free (config->username);
-    config->username = strndup (r, len);
+    config->username = strdup (p);
   }
 
-  r = strstr (cmdline, "p2v.password=");
-  if (r) {
-    r += 5+8;
-    len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.password");
+  if (p) {
     free (config->password);
-    config->password = strndup (r, len);
+    config->password = strdup (p);
   }
 
-  r = strstr (cmdline, "p2v.sudo");
-  if (r)
+  p = get_cmdline_key (cmdline, "p2v.sudo");
+  if (p)
     config->sudo = 1;
 
   /* We should now be able to connect and interrogate virt-v2v
@@ -88,30 +80,26 @@ kernel_configuration (struct config *config, const char *cmdline)
     exit (EXIT_FAILURE);
   }
 
-  r = strstr (cmdline, "p2v.name=");
-  if (r) {
-    r += 5+4;
-    len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.name");
+  if (p) {
     free (config->guestname);
-    config->guestname = strndup (r, len);
+    config->guestname = strdup (p);
   }
 
-  r = strstr (cmdline, "p2v.vcpus=");
-  if (r) {
-    r += 5+5;
-    if (sscanf (r, "%d", &config->vcpus) != 1) {
+  p = get_cmdline_key (cmdline, "p2v.vcpus");
+  if (p) {
+    if (sscanf (p, "%d", &config->vcpus) != 1) {
       fprintf (stderr, "%s: cannot parse p2v.vcpus from kernel command line\n",
                guestfs_int_program_name);
       exit (EXIT_FAILURE);
     }
   }
 
-  r = strstr (cmdline, "p2v.memory=");
-  if (r) {
+  p = get_cmdline_key (cmdline, "p2v.memory");
+  if (p) {
     char mem_code[2];
 
-    r += 5+6;
-    if (sscanf (r, "%" SCNu64 "%c", &config->memory, mem_code) != 1) {
+    if (sscanf (p, "%" SCNu64 "%c", &config->memory, mem_code) != 1) {
       fprintf (stderr, "%s: cannot parse p2v.memory from kernel command line\n",
                guestfs_int_program_name);
       exit (EXIT_FAILURE);
@@ -128,88 +116,75 @@ kernel_configuration (struct config *config, const char *cmdline)
     }
   }
 
-  r = strstr (cmdline, "p2v.disks=");
-  if (r) {
+  p = get_cmdline_key (cmdline, "p2v.disks");
+  if (p) {
     CLEANUP_FREE char *t;
 
-    r += 5+5;
-    len = strcspn (r, " ");
-    t = strndup (r, len);
+    t = strdup (p);
     guestfs_int_free_string_list (config->disks);
     config->disks = guestfs_int_split_string (',', t);
   }
 
-  r = strstr (cmdline, "p2v.removable=");
-  if (r) {
+  p = get_cmdline_key (cmdline, "p2v.removable");
+  if (p) {
     CLEANUP_FREE char *t;
 
-    r += 5+9;
-    len = strcspn (r, " ");
-    t = strndup (r, len);
+    t = strdup (p);
     guestfs_int_free_string_list (config->removable);
     config->removable = guestfs_int_split_string (',', t);
   }
 
-  r = strstr (cmdline, "p2v.interfaces=");
-  if (r) {
+  p = get_cmdline_key (cmdline, "p2v.interfaces");
+  if (p) {
     CLEANUP_FREE char *t;
 
-    r += 5+10;
-    len = strcspn (r, " ");
-    t = strndup (r, len);
+    t = strdup (p);
     guestfs_int_free_string_list (config->interfaces);
     config->interfaces = guestfs_int_split_string (',', t);
   }
 
-  r = strstr (cmdline, "p2v.network=");
-  if (r) {
+  p = get_cmdline_key (cmdline, "p2v.network");
+  if (p) {
     CLEANUP_FREE char *t;
 
-    r += 5+7;
-    len = strcspn (r, " ");
-    t = strndup (r, len);
+    t = strdup (p);
     guestfs_int_free_string_list (config->network_map);
     config->network_map = guestfs_int_split_string (',', t);
   }
 
-  r = strstr (cmdline, "p2v.o=");
-  if (r) {
-    r += 5+1;
-    len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.o");
+  if (p) {
     free (config->output);
-    config->output = strndup (r, len);
+    config->output = strdup (p);
   }
 
-  r = strstr (cmdline, "p2v.oa=sparse");
-  if (r)
-    config->output_allocation = OUTPUT_ALLOCATION_SPARSE;
-
-  r = strstr (cmdline, "p2v.oa=preallocated");
-  if (r)
-    config->output_allocation = OUTPUT_ALLOCATION_PREALLOCATED;
+  p = get_cmdline_key (cmdline, "p2v.oa");
+  if (p) {
+    if (STREQ (p, "sparse"))
+      config->output_allocation = OUTPUT_ALLOCATION_SPARSE;
+    else if (STREQ (p, "preallocated"))
+      config->output_allocation = OUTPUT_ALLOCATION_PREALLOCATED;
+    else
+      fprintf (stderr, "%s: warning: don't know what p2v.oa=%s means\n",
+               guestfs_int_program_name, p);
+  }
 
-  r = strstr (cmdline, "p2v.oc=");
-  if (r) {
-    r += 5+2;
-    len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.oc");
+  if (p) {
     free (config->output_connection);
-    config->output_connection = strndup (r, len);
+    config->output_connection = strdup (p);
   }
 
-  r = strstr (cmdline, "p2v.of=");
-  if (r) {
-    r += 5+2;
-    len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.of");
+  if (p) {
     free (config->output_format);
-    config->output_format = strndup (r, len);
+    config->output_format = strdup (p);
   }
 
-  r = strstr (cmdline, "p2v.os=");
-  if (r) {
-    r += 5+2;
-    len = strcspn (r, " ");
+  p = get_cmdline_key (cmdline, "p2v.os");
+  if (p) {
     free (config->output_storage);
-    config->output_storage = strndup (r, len);
+    config->output_storage = strdup (p);
   }
 
   /* Perform the conversion in text mode. */
diff --git a/p2v/main.c b/p2v/main.c
index 2dba1b8..4e67992 100644
--- a/p2v/main.c
+++ b/p2v/main.c
@@ -44,7 +44,6 @@ char **all_interfaces;
 static void set_config_defaults (struct config *config);
 static void find_all_disks (void);
 static void find_all_interfaces (void);
-static char *read_cmdline (void);
 static int cpuinfo_flags (void);
 
 enum { HELP_OPTION = CHAR_MAX + 1 };
@@ -99,7 +98,8 @@ main (int argc, char *argv[])
   gboolean gui_possible;
   int c;
   int option_index;
-  char *cmdline = NULL;
+  char **cmdline = NULL;
+  int cmdline_source = 0;
   struct config *config = new_config ();
 
   setlocale (LC_ALL, "");
@@ -120,7 +120,8 @@ main (int argc, char *argv[])
         display_long_options (long_options);
       }
       else if (STREQ (long_options[option_index].name, "cmdline")) {
-        cmdline = strdup (optarg);
+        cmdline = parse_cmdline_string (optarg);
+        cmdline_source = CMDLINE_SOURCE_COMMAND_LINE;
       }
       else {
         fprintf (stderr, _("%s: unknown long option: %s (%d)\n"),
@@ -158,16 +159,18 @@ main (int argc, char *argv[])
    * If /proc/cmdline contains p2v.debug then we enable verbose mode
    * even for interactive configuration.
    */
-  if (cmdline == NULL)
-    cmdline = read_cmdline ();
-  if (cmdline == NULL)
-    goto gui;
+  if (cmdline == NULL) {
+    cmdline = parse_proc_cmdline ();
+    if (cmdline == NULL)
+      goto gui;
+    cmdline_source = CMDLINE_SOURCE_PROC_CMDLINE;
+  }
 
-  if (strstr (cmdline, "p2v.debug"))
+  if (get_cmdline_key (cmdline, "p2v.debug") != NULL)
     config->verbose = 1;
 
-  if (strstr (cmdline, "p2v.server="))
-    kernel_configuration (config, cmdline);
+  if (get_cmdline_key (cmdline, "p2v.server") != NULL)
+    kernel_configuration (config, cmdline, cmdline_source);
   else {
   gui:
     if (!gui_possible)
@@ -176,7 +179,7 @@ main (int argc, char *argv[])
     gui_application (config);
   }
 
-  free (cmdline);
+  guestfs_int_free_string_list (cmdline);
 
   exit (EXIT_SUCCESS);
 }
@@ -498,36 +501,6 @@ find_all_interfaces (void)
     qsort (all_interfaces, nr_interfaces, sizeof (char *), compare);
 }
 
-/* Read /proc/cmdline. */
-static char *
-read_cmdline (void)
-{
-  CLEANUP_FCLOSE FILE *fp = NULL;
-  char *ret = NULL;
-  size_t len;
-
-  fp = fopen ("/proc/cmdline", "re");
-  if (fp == NULL) {
-    perror ("/proc/cmdline");
-    return NULL;
-  }
-
-  if (getline (&ret, &len, fp) == -1) {
-    perror ("getline");
-    return NULL;
-  }
-
-  /* 'len' is not the length of the string, but the length of the
-   * buffer.  We need to chomp the string.
-   */
-  len = strlen (ret);
-
-  if (len >= 1 && ret[len-1] == '\n')
-    ret[len-1] = '\0';
-
-  return ret;
-}
-
 /* Read the list of flags from /proc/cpuinfo. */
 static int
 cpuinfo_flags (void)
diff --git a/p2v/p2v.h b/p2v/p2v.h
index c5427a7..41d305d 100644
--- a/p2v/p2v.h
+++ b/p2v/p2v.h
@@ -85,8 +85,16 @@ extern struct config *new_config (void);
 extern struct config *copy_config (struct config *);
 extern void free_config (struct config *);
 
+/* kernel-cmdline.c */
+extern char **parse_cmdline_string (const char *cmdline);
+extern char **parse_proc_cmdline (void);
+extern const char *get_cmdline_key (char **cmdline, const char *key);
+
+#define CMDLINE_SOURCE_COMMAND_LINE 1 /* --cmdline */
+#define CMDLINE_SOURCE_PROC_CMDLINE 2 /* /proc/cmdline */
+
 /* kernel.c */
-extern void kernel_configuration (struct config *, const char *cmdline);
+extern void kernel_configuration (struct config *, char **cmdline, int cmdline_source);
 
 /* gui.c */
 extern void gui_application (struct config *);
diff --git a/po/POTFILES b/po/POTFILES
index 320710f..7c99fd0 100644
--- a/po/POTFILES
+++ b/po/POTFILES
@@ -258,6 +258,7 @@ p2v/config.c
 p2v/conversion.c
 p2v/copying.c
 p2v/gui.c
+p2v/kernel-cmdline.c
 p2v/kernel.c
 p2v/main.c
 p2v/miniexpect.c
-- 
1.8.3.1