From 501d82f79675a6bf9b37e8250152515863a80236 Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Fri, 5 Mar 2021 16:07:10 +0100 Subject: [PATCH] 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. --- .github/workflows/fedora-32.yml | 1 + .github/workflows/fedora-33.yml | 1 + .github/workflows/fedora-latest.yml | 1 + Makefile | 20 +- dracut.spec | 4 +- modules.d/99base/dracut-lib.sh | 71 +------ modules.d/99base/init.sh | 2 +- modules.d/99base/module-setup.sh | 7 +- test/TEST-98-GETARG/Makefile | 1 + test/TEST-98-GETARG/test.sh | 152 ++++++++++++++ test/TEST-99-RPM/test.sh | 3 + util/CMakeLists.txt | 6 + util/util.c | 306 ++++++++++++++++++++++++++++ 13 files changed, 504 insertions(+), 71 deletions(-) create mode 100644 test/TEST-98-GETARG/Makefile create mode 100755 test/TEST-98-GETARG/test.sh create mode 100644 util/CMakeLists.txt create mode 100644 util/util.c diff --git a/.github/workflows/fedora-32.yml b/.github/workflows/fedora-32.yml index 5c1de80cd9..ec04bbd7b1 100644 --- a/.github/workflows/fedora-32.yml +++ b/.github/workflows/fedora-32.yml @@ -36,6 +36,7 @@ jobs: "36", "40", "41", + "98", ] fail-fast: false steps: diff --git a/.github/workflows/fedora-33.yml b/.github/workflows/fedora-33.yml index 6ad9e4ad41..a13c7ce902 100644 --- a/.github/workflows/fedora-33.yml +++ b/.github/workflows/fedora-33.yml @@ -37,6 +37,7 @@ jobs: "36", "40", "41", + "98", ] fail-fast: false steps: diff --git a/.github/workflows/fedora-latest.yml b/.github/workflows/fedora-latest.yml index 026bec0f46..28806cd53a 100644 --- a/.github/workflows/fedora-latest.yml +++ b/.github/workflows/fedora-latest.yml @@ -36,6 +36,7 @@ jobs: "36", "40", "41", + "98", ] fail-fast: false steps: diff --git a/Makefile b/Makefile index 1aa4d4697b..c7859ff477 100644 --- a/Makefile +++ b/Makefile @@ -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 diff --git a/dracut.spec b/dracut.spec index dfb9608fdd..1ca7bde0e0 100644 --- a/dracut.spec +++ b/dracut.spec @@ -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} diff --git a/modules.d/99base/dracut-lib.sh b/modules.d/99base/dracut-lib.sh index 315a91eb8f..c0b3e4aac9 100755 --- a/modules.d/99base/dracut-lib.sh +++ b/modules.d/99base/dracut-lib.sh @@ -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 "=", 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 "=", 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!" diff --git a/modules.d/99base/init.sh b/modules.d/99base/init.sh index c5ea777400..4f2857625e 100755 --- a/modules.d/99base/init.sh +++ b/modules.d/99base/init.sh @@ -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 diff --git a/modules.d/99base/module-setup.sh b/modules.d/99base/module-setup.sh index bf037bb677..76ab51f208 100755 --- a/modules.d/99base/module-setup.sh +++ b/modules.d/99base/module-setup.sh @@ -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 diff --git a/test/TEST-98-GETARG/Makefile b/test/TEST-98-GETARG/Makefile new file mode 100644 index 0000000000..2dcab8164b --- /dev/null +++ b/test/TEST-98-GETARG/Makefile @@ -0,0 +1 @@ +-include ../Makefile.testdir diff --git a/test/TEST-98-GETARG/test.sh b/test/TEST-98-GETARG/test.sh new file mode 100755 index 0000000000..a87a5f6ea3 --- /dev/null +++ b/test/TEST-98-GETARG/test.sh @@ -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 diff --git a/test/TEST-99-RPM/test.sh b/test/TEST-99-RPM/test.sh index e6d96816a0..244bde1acc 100755 --- a/test/TEST-99-RPM/test.sh +++ b/test/TEST-99-RPM/test.sh @@ -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() { diff --git a/util/CMakeLists.txt b/util/CMakeLists.txt new file mode 100644 index 0000000000..ab380d2d94 --- /dev/null +++ b/util/CMakeLists.txt @@ -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) diff --git a/util/util.c b/util/util.c new file mode 100644 index 0000000000..8ae06d6c41 --- /dev/null +++ b/util/util.c @@ -0,0 +1,306 @@ +// SPDX-License-Identifier: GPL-2.0 + +// Parts are copied from the linux kernel + +#include +#include +#include +#include + +// 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 [=[]]\n", msg); + break; + case GETARGS: + fprintf(stderr, "ERROR: %s\nUsage: dracut-getargs [=]\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; +}