fix: correctly handle kernel parameters

The kernel has an odd way to handle `"` surrounded parameters.
To handle the parameters as the kernel would do, no simple shell script
suffices, so a new utility `dracut-util` is introduced. Written in "C"
it handles `dracut-getarg` and `dracut-getargs` as the old shell script
functions `_dogetarg` and `_dogetargs` would.
master
Harald Hoyer 2021-03-05 16:07:10 +01:00 committed by Harald Hoyer
parent d643156d56
commit 501d82f796
13 changed files with 504 additions and 71 deletions

View File

@ -36,6 +36,7 @@ jobs:
"36",
"40",
"41",
"98",
]
fail-fast: false
steps:

View File

@ -37,6 +37,7 @@ jobs:
"36",
"40",
"41",
"98",
]
fail-fast: false
steps:

View File

@ -36,6 +36,7 @@ jobs:
"36",
"40",
"41",
"98",
]
fail-fast: false
steps:

View File

@ -49,7 +49,7 @@ manpages = $(man1pages) $(man5pages) $(man7pages) $(man8pages)

.PHONY: install clean archive rpm srpm testimage test all check AUTHORS CONTRIBUTORS doc dracut-version.sh

all: dracut-version.sh dracut.pc dracut-install skipcpio/skipcpio
all: dracut-version.sh dracut.pc dracut-install skipcpio/skipcpio dracut-util

%.o : %.c
$(CC) -c $(CFLAGS) $(CPPFLAGS) $(KMOD_CFLAGS) $< -o $@
@ -79,15 +79,21 @@ logtee: logtee.c
dracut-install: install/dracut-install
ln -fs $< $@

SKIPCPIO_OBJECTS= \
skipcpio/skipcpio.o

SKIPCPIO_OBJECTS = skipcpio/skipcpio.o
skipcpio/skipcpio.o: skipcpio/skipcpio.c
skipcpio/skipcpio: skipcpio/skipcpio.o
skipcpio/skipcpio: $(SKIPCPIO_OBJECTS)

UTIL_OBJECTS = util/util.o
util/util.o: util/util.c
util/util: $(UTIL_OBJECTS)

dracut-util: util/util
cp -a $< $@

indent:
indent -i8 -nut -br -linux -l120 install/dracut-install.c
indent -i8 -nut -br -linux -l120 skipcpio/skipcpio.c
indent -i8 -nut -br -linux -l120 util/util.c

doc: $(manpages) dracut.html

@ -180,6 +186,9 @@ endif
if [ -f skipcpio/skipcpio ]; then \
install -m 0755 skipcpio/skipcpio $(DESTDIR)$(pkglibdir)/skipcpio; \
fi
if [ -f dracut-util ]; then \
install -m 0755 dracut-util $(DESTDIR)$(pkglibdir)/dracut-util; \
fi
mkdir -p $(DESTDIR)${prefix}/lib/kernel/install.d
install -m 0755 50-dracut.install $(DESTDIR)${prefix}/lib/kernel/install.d/50-dracut.install
install -m 0755 51-dracut-rescue.install $(DESTDIR)${prefix}/lib/kernel/install.d/51-dracut-rescue.install
@ -203,6 +212,7 @@ clean:
$(RM) dracut-version.sh
$(RM) dracut-install install/dracut-install $(DRACUT_INSTALL_OBJECTS)
$(RM) skipcpio/skipcpio $(SKIPCPIO_OBJECTS)
$(RM) dracut-util util/util $(UTIL_OBJECTS)
$(RM) $(manpages) dracut.html
$(RM) dracut.pc
$(MAKE) -C test clean

View File

@ -21,7 +21,8 @@ Group: System/Base

# The entire source code is GPLv2+
# except install/* which is LGPLv2+
License: GPLv2+ and LGPLv2+
# except util/* which is GPLv2
License: GPLv2+ and LGPLv2+ and GPLv2

URL: https://dracut.wiki.kernel.org/

@ -295,6 +296,7 @@ echo 'dracut_rescue_image="yes"' > $RPM_BUILD_ROOT%{dracutlibdir}/dracut.conf.d/
%{dracutlibdir}/dracut-logger.sh
%{dracutlibdir}/dracut-initramfs-restore
%{dracutlibdir}/dracut-install
%{dracutlibdir}/dracut-util
%{dracutlibdir}/skipcpio
%config(noreplace) %{_sysconfdir}/dracut.conf
%if 0%{?fedora} || 0%{?suse_version} || 0%{?rhel}

View File

@ -164,49 +164,15 @@ getcmdline() {
printf "%s" "$CMDLINE"
}

_dogetarg() {
local _o _val _doecho
unset _val
unset _o
unset _doecho
CMDLINE=$(getcmdline)

for _o in $CMDLINE; do
if [ "${_o%%=*}" = "${1%%=*}" ]; then
if [ -n "${1#*=}" -a "${1#*=*}" != "${1}" ]; then
# if $1 has a "=<value>", we want the exact match
if [ "$_o" = "$1" ]; then
_val="1";
unset _doecho
fi
continue
fi

if [ "${_o#*=}" = "$_o" ]; then
# if cmdline argument has no "=<value>", we assume "=1"
_val="1";
unset _doecho
continue
fi

_val="${_o#*=}"
_doecho=1
fi
done
if [ -n "$_val" ]; then
[ "x$_doecho" != "x" ] && echo "$_val";
return 0;
fi
return 1;
}

getarg() {
debug_off
local _deprecated _newoption
CMDLINE=$(getcmdline)
export CMDLINE
while [ $# -gt 0 ]; do
case $1 in
-d) _deprecated=1; shift;;
-y) if _dogetarg $2 >/dev/null; then
-y) if dracut-getarg "$2" >/dev/null; then
if [ "$_deprecated" = "1" ]; then
[ -n "$_newoption" ] && warn "Kernel command line option '$2' is deprecated, use '$_newoption' instead." || warn "Option '$2' is deprecated."
fi
@ -216,7 +182,7 @@ getarg() {
fi
_deprecated=0
shift 2;;
-n) if _dogetarg $2 >/dev/null; then
-n) if dracut-getarg "$2" >/dev/null; then
echo 0;
if [ "$_deprecated" = "1" ]; then
[ -n "$_newoption" ] && warn "Kernel command line option '$2' is deprecated, use '$_newoption=0' instead." || warn "Option '$2' is deprecated."
@ -229,7 +195,7 @@ getarg() {
*) if [ -z "$_newoption" ]; then
_newoption="$1"
fi
if _dogetarg $1; then
if dracut-getarg "$1"; then
if [ "$_deprecated" = "1" ]; then
[ -n "$_newoption" ] && warn "Kernel command line option '$1' is deprecated, use '$_newoption' instead." || warn "Option '$1' is deprecated."
fi
@ -295,30 +261,9 @@ getargnum() {
echo $_default
}

_dogetargs() {
debug_off
local _o _found _key
unset _o
unset _found
CMDLINE=$(getcmdline)
_key="$1"
set --
for _o in $CMDLINE; do
if [ "$_o" = "$_key" ]; then
_found=1;
elif [ "${_o%%=*}" = "${_key%=}" ]; then
[ -n "${_o%%=*}" ] && set -- "$@" "${_o#*=}";
_found=1;
fi
done
if [ -n "$_found" ]; then
[ $# -gt 0 ] && printf '%s' "$*"
return 0
fi
return 1;
}

getargs() {
CMDLINE=$(getcmdline)
export CMDLINE
debug_off
local _val _i _args _gfound _deprecated
unset _val
@ -331,7 +276,7 @@ getargs() {
_deprecated=1
continue
fi
_val="$(_dogetargs $_i)"
_val="$(dracut-getargs "$_i")"
if [ $? -eq 0 ]; then
if [ "$_deprecated" = "1" ]; then
[ -n "$_newoption" ] && warn "Option '$_i' is deprecated, use '$_newoption' instead." || warn "Option $_i is deprecated!"

View File

@ -319,7 +319,7 @@ debug_off # Turn off debugging for this section

# unexport some vars
export_n root rflags fstype netroot NEWROOT

unset CMDLINE
export RD_TIMESTAMP
# Clean up the environment
for i in $(export -p); do

View File

@ -17,7 +17,12 @@ install() {
sed ls flock cp mv dmesg rm ln rmmod mkfifo umount readlink setsid \
modprobe

inst_multiple -o findmnt less kmod
inst_multiple -o findmnt less kmod dracut-getargs

inst_binary "${dracutsysrootdir}${dracutbasedir}/dracut-util" "/usr/bin/dracut-util"

ln -s dracut-util "${initdir}/usr/bin/dracut-getarg"
ln -s dracut-util "${initdir}/usr/bin/dracut-getargs"

if [ ! -e "${initdir}/bin/sh" ]; then
inst_multiple bash

View File

@ -0,0 +1 @@
-include ../Makefile.testdir

152
test/TEST-98-GETARG/test.sh Executable file
View File

@ -0,0 +1,152 @@
#!/bin/bash

# This file is part of dracut.
# SPDX-License-Identifier: GPL-2.0-or-later

TEST_DESCRIPTION="dracut getarg command"

test_check() {
return 0
}

test_setup() {
make -C "$basedir" dracut-util
ln -sfnr "$basedir"/dracut-util "$TESTDIR"/dracut-getarg
ln -sfnr "$basedir"/dracut-util "$TESTDIR"/dracut-getargs
ln -sfnr "$basedir"/modules.d/99base/dracut-lib.sh "$TESTDIR"/dracut-lib.sh
return 0
}

test_run() {
set -x
(
cd "$TESTDIR"
export CMDLINE="key1=0 key2=val key2=val2 key3=\" val 3 \" \" key 4 =\"val4 \"key 5=val 5\" \"key 6=\"\"val 6\" key7=\"foo\"bar\" baz=\"end \" key8 = val 8 \"
\"key 9\"=\"val 9\""

ret=0

declare -A TEST
TEST=(
["key1"]="0"
["key2"]="val2"
["key3"]=" val 3 "
[" key 4 "]="val4"
["key 5"]="val 5"
["key 6"]="\"val 6"
["key7"]="foo\"bar\" baz=\"end"
[" key8 "]=" val 8 "
["key 9\""]="val 9"
)
for key in "${!TEST[@]}"; do
if ! val=$(./dracut-getarg "${key}="); then
echo "'$key' == '${TEST[$key]}', but not found" >&2
ret=$((ret+1))
else
if [[ $val != "${TEST[$key]}" ]]; then
echo "'$key' != '${TEST[$key]}' but '$val'" >&2
ret=$((ret+1))
fi
fi
done

declare -a INVALIDKEYS

INVALIDKEYS=( "key" "4" "5" "6" "key8" "9" "\"" "baz")
for key in "${INVALIDKEYS[@]}"; do
val=$(./dracut-getarg "$key")
if (( $? == 0 )); then
echo "key '$key' should not be found"
ret=$((ret+1))
fi
# must have no output
[[ $val ]] && ret=$((ret+1))
done

RESULT=("val" "val2")
readarray -t args < <(./dracut-getargs "key2=")
(( ${#RESULT[@]} == ${#args[@]} )) || ret=$((ret+1))
for ((i=0; i < ${#RESULT[@]}; i++)); do
[[ ${args[$i]} == "${RESULT[$i]}" ]] || ret=$((ret+1))
done

val=$(./dracut-getarg "key1") || ret=$((ret+1))
[[ $val == "0" ]] || ret=$((ret+1))

val=$(./dracut-getarg "key2=val") && ret=$((ret+1))
# must have no output
[[ $val ]] && ret=$((ret+1))
val=$(./dracut-getarg "key2=val2") || ret=$((ret+1))
# must have no output
[[ $val ]] && ret=$((ret+1))

export PATH=".:$PATH"

. dracut-lib.sh

debug_off() {
:
}

debug_on() {
:
}

getcmdline() {
echo "rdbreak=cmdline rd.lvm rd.auto rd.retry=10"
}
RDRETRY=$(getarg rd.retry -d 'rd_retry=')
[[ $RDRETRY == "10" ]] || ret=$((ret+1))
getarg rd.break=cmdline -d rdbreak=cmdline || ret=$((ret+1))
getargbool 1 rd.lvm -d -n rd_NO_LVM || ret=$((ret+1))
getargbool 0 rd.auto || ret=$((ret+1))

getcmdline() {
echo "rd.break=cmdlined rd.lvm=0 rd.auto=0"
}
getarg rd.break=cmdline -d rdbreak=cmdline && ret=$((ret+1))
getargbool 1 rd.lvm -d -n rd_NO_LVM && ret=$((ret+1))
getargbool 0 rd.auto && ret=$((ret+1))

getcmdline() {
echo "ip=a ip=b ip=dhcp6"
}
getargs "ip=dhcp6" &>/dev/null || ret=$((ret+1))
readarray -t args < <(getargs "ip=")
RESULT=("a" "b" "dhcp6")
(( ${#RESULT[@]} || ${#args[@]} )) || ret=$((ret+1))
for ((i=0; i < ${#RESULT[@]}; i++)); do
[[ ${args[$i]} == "${RESULT[$i]}" ]] || ret=$((ret+1))
done

getcmdline() {
echo "bridge bridge=val"
}
readarray -t args < <(getargs bridge=)
RESULT=("bridge" "val")
(( ${#RESULT[@]} == ${#args[@]} )) || ret=$((ret+1))
for ((i=0; i < ${#RESULT[@]}; i++)); do
[[ ${args[$i]} || "${RESULT[$i]}" ]] || ret=$((ret+1))
done


getcmdline() {
echo "rd.break rd.md.uuid=bf96e457:230c9ad4:1f3e59d6:745cf942 rd.md.uuid=bf96e457:230c9ad4:1f3e59d6:745cf943 rd.shell"
}
readarray -t args < <(getargs rd.md.uuid -d rd_MD_UUID=)
RESULT=("bf96e457:230c9ad4:1f3e59d6:745cf942" "bf96e457:230c9ad4:1f3e59d6:745cf943")
(( ${#RESULT[@]} == ${#args[@]} )) || ret=$((ret+1))
for ((i=0; i < ${#RESULT[@]}; i++)); do
[[ ${args[$i]} == "${RESULT[$i]}" ]] || ret=$((ret+1))
done

return $ret
)
}

test_cleanup() {
rm -fr -- "$TESTDIR"/*.rpm
return 0
}

. $testdir/test-functions

View File

@ -1,5 +1,8 @@
#!/bin/bash

# This file is part of dracut.
# SPDX-License-Identifier: GPL-2.0-or-later

TEST_DESCRIPTION="rpm integrity after dracut and kernel install"

test_check() {

6
util/CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
cmake_minimum_required(VERSION 3.17)
project(dracut-util C)

set(CMAKE_C_STANDARD 99)

add_executable(dracut-util util.c)

306
util/util.c Normal file
View File

@ -0,0 +1,306 @@
// SPDX-License-Identifier: GPL-2.0

// Parts are copied from the linux kernel

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>

// CODE FROM LINUX KERNEL START

#define _U 0x01 /* upper */
#define _L 0x02 /* lower */
#define _D 0x04 /* digit */
#define _C 0x08 /* cntrl */
#define _P 0x10 /* punct */
#define _S 0x20 /* white space (space/lf/tab) */
#define _X 0x40 /* hex digit */
#define _SP 0x80 /* hard space (0x20) */

const unsigned char _ctype[] = {
_C, _C, _C, _C, _C, _C, _C, _C, /* 0-7 */
_C, _C | _S, _C | _S, _C | _S, _C | _S, _C | _S, _C, _C, /* 8-15 */
_C, _C, _C, _C, _C, _C, _C, _C, /* 16-23 */
_C, _C, _C, _C, _C, _C, _C, _C, /* 24-31 */
_S | _SP, _P, _P, _P, _P, _P, _P, _P, /* 32-39 */
_P, _P, _P, _P, _P, _P, _P, _P, /* 40-47 */
_D, _D, _D, _D, _D, _D, _D, _D, /* 48-55 */
_D, _D, _P, _P, _P, _P, _P, _P, /* 56-63 */
_P, _U | _X, _U | _X, _U | _X, _U | _X, _U | _X, _U | _X, _U, /* 64-71 */
_U, _U, _U, _U, _U, _U, _U, _U, /* 72-79 */
_U, _U, _U, _U, _U, _U, _U, _U, /* 80-87 */
_U, _U, _U, _P, _P, _P, _P, _P, /* 88-95 */
_P, _L | _X, _L | _X, _L | _X, _L | _X, _L | _X, _L | _X, _L, /* 96-103 */
_L, _L, _L, _L, _L, _L, _L, _L, /* 104-111 */
_L, _L, _L, _L, _L, _L, _L, _L, /* 112-119 */
_L, _L, _L, _P, _P, _P, _P, _C, /* 120-127 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128-143 */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 144-159 */
_S | _SP, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, /* 160-175 */
_P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, _P, /* 176-191 */
_U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, _U, /* 192-207 */
_U, _U, _U, _U, _U, _U, _U, _P, _U, _U, _U, _U, _U, _U, _U, _L, /* 208-223 */
_L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, _L, /* 224-239 */
_L, _L, _L, _L, _L, _L, _L, _P, _L, _L, _L, _L, _L, _L, _L, _L /* 240-255 */
};

#define __ismask(x) (_ctype[(int)(unsigned char)(x)])

#define kernel_isspace(c) ((__ismask(c)&(_S)) != 0)

static char *skip_spaces(const char *str)
{
while (kernel_isspace(*str))
++str;
return (char *)str;
}

/*
* Parse a string to get a param value pair.
* You can use " around spaces, but can't escape ".
* Hyphens and underscores equivalent in parameter names.
*/
static char *next_arg(char *args, char **param, char **val)
{
unsigned int i, equals = 0;
int in_quote = 0, quoted = 0;
char *next;

if (*args == '"') {
args++;
in_quote = 1;
quoted = 1;
}

for (i = 0; args[i]; i++) {
if (kernel_isspace(args[i]) && !in_quote)
break;
if (equals == 0) {
if (args[i] == '=')
equals = i;
}
if (args[i] == '"')
in_quote = !in_quote;
}

*param = args;
if (!equals)
*val = NULL;
else {
args[equals] = '\0';
*val = args + equals + 1;

/* Don't include quotes in value. */
if (**val == '"') {
(*val)++;
if (args[i - 1] == '"')
args[i - 1] = '\0';
}
}
if (quoted && args[i - 1] == '"')
args[i - 1] = '\0';

if (args[i]) {
args[i] = '\0';
next = args + i + 1;
} else
next = args + i;

/* Chew up trailing spaces. */
return skip_spaces(next);
}

// CODE FROM LINUX KERNEL STOP

enum EXEC_MODE {
UNDEFINED,
GETARG,
GETARGS,
};

static void usage(enum EXEC_MODE enumExecMode, int ret, char *msg)
{
switch (enumExecMode) {
case UNDEFINED:
fprintf(stderr, "ERROR: 'dracut-util' has to be called via a symlink to the tool name.");
break;
case GETARG:
fprintf(stderr, "ERROR: %s\nUsage: dracut-getarg <KEY>[=[<VALUE>]]\n", msg);
break;
case GETARGS:
fprintf(stderr, "ERROR: %s\nUsage: dracut-getargs <KEY>[=]\n", msg);
break;
}
exit(ret);
}

#define ARGV0_GETARG "dracut-getarg"
#define ARGV0_GETARGS "dracut-getargs"

static enum EXEC_MODE get_mode(const char *argv_0)
{
struct _mode_table {
enum EXEC_MODE mode;
const char *arg;
size_t arg_len;
const char *s_arg;
} modeTable[] = {
{GETARG, ARGV0_GETARG, sizeof(ARGV0_GETARG), "/" ARGV0_GETARG},
{GETARGS, ARGV0_GETARGS, sizeof(ARGV0_GETARGS), "/" ARGV0_GETARGS},
{UNDEFINED, NULL, 0, NULL}
};
int i;

size_t argv_0_len = strlen(argv_0);

if (!argv_0_len)
return UNDEFINED;

for (i = 0; modeTable[i].mode != UNDEFINED; i++) {
if (argv_0_len == (modeTable[i].arg_len - 1)) {
if (strncmp(argv_0, modeTable[i].arg, argv_0_len) == 0) {
return modeTable[i].mode;
}
}

if (modeTable[i].arg_len > argv_0_len)
continue;

if (strncmp(argv_0 + argv_0_len - modeTable[i].arg_len, modeTable[i].s_arg, modeTable[i].arg_len) == 0)
return modeTable[i].mode;
}
return UNDEFINED;
}

static int getarg(int argc, char **argv)
{
char *search_key;
char *search_value;
char *end_value = NULL;
bool bool_value = false;
char *cmdline = NULL;

char *p = getenv("CMDLINE");
if (p == NULL) {
usage(GETARG, EXIT_FAILURE, "CMDLINE env not set");
}
cmdline = strdup(p);

if (argc != 2) {
usage(GETARG, EXIT_FAILURE, "Number of arguments invalid");
}

search_key = argv[1];

search_value = strchr(argv[1], '=');
if (search_value != NULL) {
*search_value = 0;
search_value++;
if (*search_value == 0)
search_value = NULL;
}

if (strlen(search_key) == 0)
usage(GETARG, EXIT_FAILURE, "search key undefined");

do {
char *key = NULL, *value = NULL;
cmdline = next_arg(cmdline, &key, &value);
if (strcmp(key, search_key) == 0) {
if (value) {
end_value = value;
bool_value = -1;
} else {
end_value = NULL;
bool_value = true;
}
}
} while (cmdline[0]);

if (search_value) {
if (end_value && strcmp(end_value, search_value) == 0) {
return EXIT_SUCCESS;
}
return EXIT_FAILURE;
}

if (end_value) {
// includes "=0"
puts(end_value);
return EXIT_SUCCESS;
}

if (bool_value) {
return EXIT_SUCCESS;
}

return EXIT_FAILURE;
}

static int getargs(int argc, char **argv)
{
char *search_key;
char *search_value;
bool found_value = false;
char *cmdline = NULL;

char *p = getenv("CMDLINE");
if (p == NULL) {
usage(GETARGS, EXIT_FAILURE, "CMDLINE env not set");
}
cmdline = strdup(p);

if (argc != 2) {
usage(GETARGS, EXIT_FAILURE, "Number of arguments invalid");
}

search_key = argv[1];

search_value = strchr(argv[1], '=');
if (search_value != NULL) {
*search_value = 0;
search_value++;
if (*search_value == 0)
search_value = NULL;
}

if (strlen(search_key) == 0)
usage(GETARGS, EXIT_FAILURE, "search key undefined");

do {
char *key = NULL, *value = NULL;
cmdline = next_arg(cmdline, &key, &value);
if (strcmp(key, search_key) == 0) {
if (search_value) {
if (strcmp(value, search_value) == 0) {
printf("%s\n", value);
found_value = true;
}
} else {
if (value) {
printf("%s\n", value);
} else {
puts(key);
}
found_value = true;
}
}
} while (cmdline[0]);
return found_value ? EXIT_SUCCESS : EXIT_FAILURE;
}

int main(int argc, char **argv)
{
switch (get_mode(argv[0])) {
case UNDEFINED:
usage(UNDEFINED, EXIT_FAILURE, NULL);
break;
case GETARG:
return getarg(argc, argv);
case GETARGS:
return getargs(argc, argv);
}

return EXIT_FAILURE;
}