This refactors parse_cmdline into a few chunks of code, mainly to separate the work of parsing from the handling of the actual parsed parameters. By default, parse_cmdline_item is used as the callback for handling the parameters, but this could be overriden by other code sourcing init_functions if desirable. Our test harness passes more tests, but we leave behind some expected failures to describe the cases where it still fails. Fortunately I've not yet been able to find any cases which warrant --expect-parse-fail. --- init_functions | 175 ++++++++++++++++++++++++++++++++---------------- test/test_parse_cmdline | 16 ++--- 2 files changed, 125 insertions(+), 66 deletions(-) diff --git a/init_functions b/init_functions index 05f4ab0..d5a584e 100644 --- a/init_functions +++ b/init_functions @@ -114,74 +114,133 @@ set_log_option() { done } -parse_cmdline() { - local _w _quoted _lhs _rhs _cmdline - read -r _cmdline - for _w in $_cmdline; do - if [ -z "$_quoted" ]; then - case $_w in - # ignore everything after a # in the commandline - \#*) break ;; - # special cases - rw|ro) rwopt=$_w ;; - fstype) rootfstype=$_w ;; - fsck.mode=*) - case ${_w#*=} in - force) - forcefsck=y - ;; - skip) - fastboot=y - ;; - *) - err "unknown fsck.mode parameter: '${_w#*=}'" - ;; - esac +startswith() { + local word=$1 prefix=$2 + + case $word in + $prefix*) + return 0 + ;; + esac + + return 1 +} + +endswith() { + local word=$1 suffix=$2 + + case $word in + *$suffix) + return 0 + ;; + esac + + return 1 +} + +parse_cmdline_item() { + local key=$1 value=$2 + + case $key in + rw|ro) + rwopt=$key + ;; + fstype) + # The kernel understands 'rootfstype', but mkinitcpio has (without + # documentation) supported 'fstype' instead. Ensure we support both + # for backwards compat, but make fstype legacy. + rootfstype=$value + ;; + fsck.mode) + case $value in + force) + forcefsck=y ;; - rd.*) - case ${_w#rd.} in - debug) - rd_debug=y - ;; - log) - rd_logmask=$(( _rdlog_kmsg | _rdlog_cons )) - ;; - log=*) - set_log_option "${_w#rd.log=}" - ;; - esac + skip) + fastboot=y ;; - # abide by shell variable naming rules - [[:alpha:]_]*=*) - _rhs=${_w#*=} - _lhs=${_w%%=*} - _lhs=${_lhs//[-.]/_} - if [ '"' = "${_rhs:0:1}" ]; then - if [ '"' = "${_rhs:$((${#_rhs}-1))}" ]; then - _rhs="${_rhs:1:$((${#_rhs}-2))}" - else - _rhs=${_rhs:1} - _quoted=1 - continue - fi + *) + err "unknown fsck.mode parameter: '$value'" + ;; + esac + ;; + rd.debug) + rd_debug=y + ;; + rd.log) + if [ -n "$value" ]; then + set_log_option "$value" + else + rd_logmask=$(( _rdlog_kmsg | _rdlog_cons )) + fi + ;; + [![:alpha:]_]*|[[:alpha:]_]*[![:alnum:]_]*) + # invalid shell variable, ignore it + ;; + *) + # valid shell variable + eval "$key"='${value:-y}' + ;; + esac +} + +process_cmdline_param() { + local item_callback=$1 key=$2 value=$3 + + # maybe unquote the value + if startswith "$value" "[\"']" && endswith "$value" "${value:0:1}"; then + value=${value#?} value=${value%?} + fi + + "$item_callback" "$key" "$value" +} + +parse_cmdline() { + local item_callback=${1:-parse_cmdline_item} + local cmdline word quoted key value + + set -f + read -r cmdline + set -- $cmdline + set +f + + for word; do + if [ -n "$quoted" ]; then + value="$value $word" + else + case $word in + *=*) + key=${word%%=*} + value=${word#*=} + + if startswith "$value" "[\"']"; then + quoted=${value:0:1} fi - eval $_lhs=\$_rhs ;; - [[:alpha:]_]*) - _lhs=${_w//[-.]/_} - eval $_lhs=y + '#'*) + break + ;; + *) + key=$word ;; esac - else - if [ '"' = "${_w:$((${#_w}-1))}" ]; then - _rhs="$_rhs ${_w%\"}" - unset _quoted - eval $_lhs=\$_rhs + fi + + if [ -n "$quoted" ]; then + if endswith "$value" "$quoted"; then + unset quoted else - _rhs="$_rhs $_w" + continue fi fi + + process_cmdline_param "$item_callback" "$key" "$value" + unset key value done + + if [ -n "$key" ]; then + process_cmdline_param "$item_callback" "$key" "$value" + fi } fsck_device() { diff --git a/test/test_parse_cmdline b/test/test_parse_cmdline index 9afd9cb..ff855e4 100755 --- a/test/test_parse_cmdline +++ b/test/test_parse_cmdline @@ -130,19 +130,19 @@ test_parse 'foo="bar baz"' \ 'foo' 'bar baz' # single quoting -test_parse --expect-fail "foo='bar'" \ +test_parse "foo='bar'" \ 'foo' 'bar' -test_parse --expect-parse-fail "foo='bar baz'" \ +test_parse "foo='bar baz'" \ 'foo' 'bar baz' # dangling quotes -test_parse --expect-fail 'foo="bar' \ +test_parse 'foo="bar' \ 'foo' '"bar' test_parse 'foo=bar"' \ 'foo' 'bar"' # nested quotes -test_parse --expect-parse-fail "foo='\"bar baz\"' herp='\"de\"rp'" \ +test_parse "foo='\"bar baz\"' herp='\"de\"rp'" \ 'foo' '"bar baz"' \ 'herp' '"de"rp' @@ -174,9 +174,9 @@ test_parse 'foo="bar #baz" parse=this' \ # shell metachars test_parse 'foo=*' \ 'foo' '\*' -test_parse --expect-fail 'Make*' \ +test_parse 'Make*' \ 'Makefile' '' -test_parse --expect-fail '[Makefile]*' \ +test_parse '[Makefile]*' \ 'Makefile' '' \ 'init' '' \ 'functions' '' @@ -184,7 +184,7 @@ test_parse --expect-fail '[Makefile]*' \ # invalid names test_parse 'in-valid=name' test_parse '6foo=bar' -test_parse --expect-parse-fail '"gar bage"' \ +test_parse '"gar bage"' \ 'gar' '' \ 'bage' '' @@ -197,7 +197,7 @@ test_parse 'ro' \ 'ro' '' \ 'rw' '' \ 'rwopt' 'ro' -test_parse --expect-fail 'fstype=btrfs' \ +test_parse 'fstype=btrfs' \ 'rootfstype' 'btrfs' test_parse 'fsck.mode=force' \ 'forcefsck' 'y' \ -- 2.8.3