From dfa319bc27420a611311834108e6807b300fc2b3 Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" 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 + +#include +#include +#include +#include +#include + +#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