361 lines
12 KiB
C
361 lines
12 KiB
C
/*
|
|
* VAS_EBOOT -- GRand Unified Bootloader
|
|
* Copyright (C) 2025 Free Software Foundation, Inc.
|
|
*
|
|
* VAS_EBOOT 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 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* VAS_EBOOT 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 VAS_EBOOT. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <VasEBoot/env.h>
|
|
#include <VasEBoot/misc.h>
|
|
#include <VasEBoot/mm.h>
|
|
#include <VasEBoot/xen.h>
|
|
#include <VasEBoot/err.h>
|
|
|
|
enum splitter_state
|
|
{
|
|
SPLITTER_NORMAL = 0x0,
|
|
SPLITTER_HIT_BACKSLASH = 0x1,
|
|
SPLITTER_IN_SINGLE_QUOTES = 0x2,
|
|
SPLITTER_IN_DOUBLE_QUOTES = 0x4,
|
|
};
|
|
typedef enum splitter_state splitter_state_t;
|
|
|
|
/*
|
|
* The initial size of the current_word buffer. The buffer may be resized as
|
|
* needed.
|
|
*/
|
|
#define PARSER_BASE_WORD_SIZE 32
|
|
|
|
struct parser_state
|
|
{
|
|
char **words;
|
|
VasEBoot_size_t words_count;
|
|
char *current_word;
|
|
VasEBoot_size_t current_word_len;
|
|
VasEBoot_size_t current_word_pos;
|
|
};
|
|
typedef struct parser_state parser_state_t;
|
|
|
|
static VasEBoot_err_t
|
|
append_char_to_word (parser_state_t *ps, char c, bool allow_null)
|
|
{
|
|
/*
|
|
* We ban any chars that are not in the ASCII printable range. If
|
|
* allow_null == true, we make an exception for NUL. (This is needed so that
|
|
* append_word_to_list can add a NUL terminator to the word).
|
|
*/
|
|
if (!VasEBoot_isprint (c) && allow_null == false)
|
|
return VAS_EBOOT_ERR_BAD_ARGUMENT;
|
|
else if (allow_null == true && c != '\0')
|
|
return VAS_EBOOT_ERR_BAD_ARGUMENT;
|
|
|
|
if (ps->current_word_pos == ps->current_word_len)
|
|
{
|
|
ps->current_word = VasEBoot_realloc (ps->current_word, ps->current_word_len *= 2);
|
|
if (ps->current_word == NULL)
|
|
{
|
|
ps->current_word_len /= 2;
|
|
return VasEBoot_errno;
|
|
}
|
|
}
|
|
|
|
ps->current_word[ps->current_word_pos++] = c;
|
|
return VAS_EBOOT_ERR_NONE;
|
|
}
|
|
|
|
static VasEBoot_err_t
|
|
append_word_to_list (parser_state_t *ps)
|
|
{
|
|
/* No-op on empty words. */
|
|
if (ps->current_word_pos == 0)
|
|
return VAS_EBOOT_ERR_NONE;
|
|
|
|
if (append_char_to_word (ps, '\0', true) != VAS_EBOOT_ERR_NONE)
|
|
VasEBoot_fatal ("couldn't append NUL terminator to word during Xen cmdline parsing");
|
|
|
|
ps->current_word_len = VasEBoot_strlen (ps->current_word) + 1;
|
|
ps->current_word = VasEBoot_realloc (ps->current_word, ps->current_word_len);
|
|
if (ps->current_word == NULL)
|
|
return VasEBoot_errno;
|
|
ps->words = VasEBoot_realloc (ps->words, ++ps->words_count * sizeof (char *));
|
|
if (ps->words == NULL)
|
|
return VasEBoot_errno;
|
|
ps->words[ps->words_count - 1] = ps->current_word;
|
|
|
|
ps->current_word_len = PARSER_BASE_WORD_SIZE;
|
|
ps->current_word_pos = 0;
|
|
ps->current_word = VasEBoot_malloc (ps->current_word_len);
|
|
if (ps->current_word == NULL)
|
|
return VasEBoot_errno;
|
|
|
|
return VAS_EBOOT_ERR_NONE;
|
|
}
|
|
|
|
static bool
|
|
is_key_safe (char *key, VasEBoot_size_t len)
|
|
{
|
|
VasEBoot_size_t i;
|
|
|
|
for (i = 0; i < len; i++)
|
|
if (!VasEBoot_isalpha (key[i]) && key[i] != '_')
|
|
return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
VasEBoot_parse_xen_cmdline (void)
|
|
{
|
|
parser_state_t ps = {0};
|
|
splitter_state_t ss = SPLITTER_NORMAL;
|
|
|
|
const char *cmdline = (const char *) VasEBoot_xen_start_page_addr->cmd_line;
|
|
VasEBoot_size_t cmdline_len;
|
|
bool cmdline_valid = false;
|
|
char **param_keys = NULL;
|
|
char **param_vals = NULL;
|
|
VasEBoot_size_t param_dict_len = 0;
|
|
VasEBoot_size_t param_dict_pos = 0;
|
|
char current_char = '\0';
|
|
VasEBoot_size_t i = 0;
|
|
|
|
/*
|
|
* The following algorithm is used to parse the Xen command line:
|
|
*
|
|
* - The command line is split into space-separated words.
|
|
* - Single and double quotes may be used to suppress the splitting
|
|
* behavior of spaces.
|
|
* - Double quotes are appended to the current word verbatim if they
|
|
* appear within a single-quoted string portion, and vice versa.
|
|
* - Backslashes may be used to cause the next character to be
|
|
* appended to the current word verbatim. This is only useful when
|
|
* used to escape quotes, spaces, and backslashes, but for simplicity
|
|
* we allow backslash-escaping anything.
|
|
* - After splitting the command line into words, each word is checked to
|
|
* see if it contains an equals sign.
|
|
* - If it does, it is split on the equals sign into a key-value pair. The
|
|
* key is then treated as an variable name, and the value is treated as
|
|
* the variable's value.
|
|
* - If it does not, the entire word is treated as a variable name. The
|
|
* variable's value is implicitly considered to be `1`.
|
|
* - All variables detected on the command line are checked to see if their
|
|
* names begin with the string `xen_VasEBoot_env_`. Variables that do not pass
|
|
* this check are discarded, variables that do pass this check are
|
|
* exported so they are available to the VAS_EBOOT configuration.
|
|
*
|
|
* This behavior is intended to somewhat mimic the splitter behavior in Bash
|
|
* and in VAS_EBOOT's config file parser.
|
|
*/
|
|
|
|
ps.current_word_len = PARSER_BASE_WORD_SIZE;
|
|
ps.current_word = VasEBoot_malloc (ps.current_word_len);
|
|
if (ps.current_word == NULL)
|
|
goto cleanup_main;
|
|
|
|
for (i = 0; i < VAS_EBOOT_XEN_MAX_GUEST_CMDLINE; i++)
|
|
{
|
|
if (cmdline[i] == '\0')
|
|
{
|
|
cmdline_valid = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cmdline_valid == false)
|
|
{
|
|
VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT,
|
|
"command line from Xen is not NUL-terminated");
|
|
VasEBoot_print_error ();
|
|
goto cleanup_main;
|
|
}
|
|
|
|
cmdline_len = VasEBoot_strlen (cmdline);
|
|
for (i = 0; i < cmdline_len; i++)
|
|
{
|
|
current_char = cmdline[i];
|
|
|
|
/*
|
|
* If the previous character was a backslash, append the current
|
|
* character to the word verbatim
|
|
*/
|
|
if (ss & SPLITTER_HIT_BACKSLASH)
|
|
{
|
|
ss &= ~SPLITTER_HIT_BACKSLASH;
|
|
if (append_char_to_word (&ps, current_char, false) != VAS_EBOOT_ERR_NONE)
|
|
goto cleanup_main;
|
|
continue;
|
|
}
|
|
|
|
switch (current_char)
|
|
{
|
|
case '\\':
|
|
/* Backslashes escape arbitrary characters. */
|
|
ss |= SPLITTER_HIT_BACKSLASH;
|
|
break;
|
|
|
|
case '\'':
|
|
/*
|
|
* Single quotes suppress word splitting and double quoting until
|
|
* the next single quote is encountered.
|
|
*/
|
|
if (ss & SPLITTER_IN_DOUBLE_QUOTES)
|
|
{
|
|
if (append_char_to_word (&ps, current_char, false) != VAS_EBOOT_ERR_NONE)
|
|
goto cleanup_main;
|
|
break;
|
|
}
|
|
|
|
ss ^= SPLITTER_IN_SINGLE_QUOTES;
|
|
break;
|
|
|
|
case '"':
|
|
/*
|
|
* Double quotes suppress word splitting and single quoting until
|
|
* the next double quote is encountered.
|
|
*/
|
|
if (ss & SPLITTER_IN_SINGLE_QUOTES)
|
|
{
|
|
if (append_char_to_word (&ps, current_char, false) != VAS_EBOOT_ERR_NONE)
|
|
goto cleanup_main;
|
|
break;
|
|
}
|
|
|
|
ss ^= SPLITTER_IN_DOUBLE_QUOTES;
|
|
break;
|
|
|
|
case ' ':
|
|
/* Spaces separate words in the command line from each other. */
|
|
if (ss & SPLITTER_IN_SINGLE_QUOTES ||
|
|
ss & SPLITTER_IN_DOUBLE_QUOTES)
|
|
{
|
|
if (append_char_to_word (&ps, current_char, false) != VAS_EBOOT_ERR_NONE)
|
|
goto cleanup_main;
|
|
break;
|
|
}
|
|
|
|
if (append_word_to_list (&ps) != VAS_EBOOT_ERR_NONE)
|
|
goto cleanup_main;
|
|
break;
|
|
|
|
default:
|
|
if (append_char_to_word (&ps, current_char, false) != VAS_EBOOT_ERR_NONE)
|
|
goto cleanup_main;
|
|
}
|
|
}
|
|
|
|
if (append_word_to_list (&ps) != VAS_EBOOT_ERR_NONE)
|
|
goto cleanup_main;
|
|
|
|
param_keys = VasEBoot_malloc (ps.words_count * sizeof (char *));
|
|
if (param_keys == NULL)
|
|
goto cleanup_main;
|
|
param_vals = VasEBoot_malloc (ps.words_count * sizeof (char *));
|
|
if (param_vals == NULL)
|
|
goto cleanup_main;
|
|
|
|
for (i = 0; i < ps.words_count; i++)
|
|
{
|
|
char *eq_pos;
|
|
|
|
ps.current_word = ps.words[i];
|
|
ps.current_word_len = VasEBoot_strlen (ps.current_word) + 1;
|
|
eq_pos = VasEBoot_strchr (ps.current_word, '=');
|
|
|
|
if (eq_pos != NULL)
|
|
{
|
|
/*
|
|
* Both pre_eq_len and post_eq_len represent substring lengths
|
|
* without a NUL terminator.
|
|
*/
|
|
VasEBoot_size_t pre_eq_len = (VasEBoot_size_t) (eq_pos - ps.current_word);
|
|
/*
|
|
* ps.current_word_len includes the NUL terminator, so we subtract
|
|
* one to get rid of the terminator, and one more to get rid of the
|
|
* equals sign.
|
|
*/
|
|
VasEBoot_size_t post_eq_len = (ps.current_word_len - 2) - pre_eq_len;
|
|
|
|
if (is_key_safe (ps.current_word, pre_eq_len) == true)
|
|
{
|
|
param_dict_pos = param_dict_len++;
|
|
param_keys[param_dict_pos] = VasEBoot_malloc (pre_eq_len + 1);
|
|
if (param_keys == NULL)
|
|
goto cleanup_main;
|
|
param_vals[param_dict_pos] = VasEBoot_malloc (post_eq_len + 1);
|
|
if (param_vals == NULL)
|
|
goto cleanup_main;
|
|
|
|
VasEBoot_strncpy (param_keys[param_dict_pos], ps.current_word, pre_eq_len);
|
|
VasEBoot_strncpy (param_vals[param_dict_pos],
|
|
ps.current_word + pre_eq_len + 1, post_eq_len);
|
|
param_keys[param_dict_pos][pre_eq_len] = '\0';
|
|
param_vals[param_dict_pos][post_eq_len] = '\0';
|
|
}
|
|
}
|
|
else if (is_key_safe (ps.current_word, ps.current_word_len - 1) == true)
|
|
{
|
|
param_dict_pos = param_dict_len++;
|
|
param_keys[param_dict_pos] = VasEBoot_malloc (ps.current_word_len);
|
|
if (param_keys == NULL)
|
|
goto cleanup_main;
|
|
param_vals[param_dict_pos] = VasEBoot_zalloc (2);
|
|
if (param_vals == NULL)
|
|
goto cleanup_main;
|
|
|
|
VasEBoot_strncpy (param_keys[param_dict_pos], ps.current_word,
|
|
ps.current_word_len);
|
|
if (param_keys[param_dict_pos][ps.current_word_len - 1] != '\0' )
|
|
VasEBoot_fatal ("NUL terminator missing from key during Xen cmdline parsing");
|
|
*param_vals[param_dict_pos] = '1';
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < param_dict_len; i++)
|
|
{
|
|
/*
|
|
* Find keys that start with "xen_VasEBoot_env_" and export them
|
|
* as environment variables.
|
|
*/
|
|
if (VasEBoot_strncmp (param_keys[i],
|
|
"xen_VasEBoot_env_",
|
|
sizeof ("xen_VasEBoot_env_") - 1) != 0)
|
|
continue;
|
|
|
|
if (VasEBoot_env_set (param_keys[i], param_vals[i]) != VAS_EBOOT_ERR_NONE)
|
|
{
|
|
VasEBoot_printf ("warning: could not set environment variable `%s' to value `%s'\n",
|
|
param_keys[i], param_vals[i]);
|
|
continue;
|
|
}
|
|
|
|
if (VasEBoot_env_export (param_keys[i]) != VAS_EBOOT_ERR_NONE)
|
|
VasEBoot_printf ("warning: could not export environment variable `%s'",
|
|
param_keys[i]);
|
|
}
|
|
|
|
cleanup_main:
|
|
for (i = 0; i < ps.words_count; i++)
|
|
VasEBoot_free (ps.words[i]);
|
|
|
|
for (i = 0; i < param_dict_len; i++)
|
|
{
|
|
VasEBoot_free (param_keys[i]);
|
|
VasEBoot_free (param_vals[i]);
|
|
}
|
|
|
|
VasEBoot_free (param_keys);
|
|
VasEBoot_free (param_vals);
|
|
VasEBoot_free (ps.words);
|
|
}
|