]> git.ipfire.org Git - thirdparty/dracut.git/blobdiff - dracut-functions.sh
test: increase test VM memory from 512M to 1024M to avoid OOM killer
[thirdparty/dracut.git] / dracut-functions.sh
index a35dc016fd0b54bead241e0ed77359fd08ce7b68..8afbbd801ed428225511a31141da7901b0038e39 100755 (executable)
@@ -22,21 +22,28 @@ export LC_MESSAGES=C
 # is_func <command>
 # Check whether $1 is a function.
 is_func() {
-    [[ "$(type -t "$1")" = "function" ]]
+    [[ "$(type -t "$1")" == "function" ]]
 }
 
 # Generic substring function.  If $2 is in $1, return 0.
-strstr() { [[ $1 = *"$2"* ]]; }
+strstr() { [[ $1 == *"$2"* ]]; }
 # Generic glob matching function. If glob pattern $2 matches anywhere in $1, OK
-strglobin() { [[ $1 = *$2* ]]; }
+strglobin() { [[ $1 == *$2* ]]; }
 # Generic glob matching function. If glob pattern $2 matches all of $1, OK
 # shellcheck disable=SC2053
-strglob() { [[ $1 = $2 ]]; }
+strglob() { [[ $1 == $2 ]]; }
 # returns OK if $1 contains literal string $2 at the beginning, and isn't empty
 str_starts() { [ "${1#"$2"*}" != "$1" ]; }
 # returns OK if $1 contains literal string $2 at the end, and isn't empty
 str_ends() { [ "${1%*"$2"}" != "$1" ]; }
 
+trim() {
+    local var="$*"
+    var="${var#"${var%%[![:space:]]*}"}" # remove leading whitespace characters
+    var="${var%"${var##*[![:space:]]}"}" # remove trailing whitespace characters
+    printf "%s" "$var"
+}
+
 # find a binary.  If we were not passed the full path directly,
 # search in the usual places to find the binary.
 find_binary() {
@@ -46,7 +53,7 @@ find_binary() {
     local p
     [[ -z ${1##/*} ]] || _delim="/"
 
-    if [[ "$1" == *.so* ]]; then
+    if [[ $1 == *.so* ]]; then
         # shellcheck disable=SC2154
         for l in $libdirs; do
             _path="${l}${_delim}${1}"
@@ -61,7 +68,7 @@ find_binary() {
             return 0
         fi
     fi
-    if [[ "$1" == */* ]]; then
+    if [[ $1 == */* ]]; then
         _path="${_delim}${1}"
         if [[ -L ${dracutsysrootdir}${_path} ]] || [[ -x ${dracutsysrootdir}${_path} ]]; then
             printf "%s\n" "${_path}"
@@ -76,7 +83,7 @@ find_binary() {
         fi
     done
 
-    [[ -n "$dracutsysrootdir" ]] && return 1
+    [[ -n $dracutsysrootdir ]] && return 1
     type -P "${1##*/}"
 }
 
@@ -90,10 +97,10 @@ ldconfig_paths() {
 # $3 = version b
 vercmp() {
     local _n1
-    read -a _n1 <<< "${1//./ }"
+    read -r -a _n1 <<< "${1//./ }"
     local _op=$2
     local _n2
-    read -a _n2 <<< "${3//./ }"
+    read -r -a _n2 <<< "${3//./ }"
     local _i _res
 
     for ((_i = 0; ; _i++)); do
@@ -144,10 +151,11 @@ print_vars() {
 # $ normalize_path ///test/test//
 # /test/test
 normalize_path() {
+    # shellcheck disable=SC2064
+    trap "$(shopt -p extglob)" RETURN
     shopt -q -s extglob
-    set -- "${1//+(\/)//}"
-    shopt -q -u extglob
-    printf "%s\n" "${1%/}"
+    local p=${1//+(\/)//}
+    printf "%s\n" "${p%/}"
 }
 
 # convert_abs_rel <from> <to>
@@ -163,24 +171,24 @@ convert_abs_rel() {
     set -- "$(normalize_path "$1")" "$(normalize_path "$2")"
 
     # corner case #1 - self looping link
-    [[ "$1" == "$2" ]] && {
+    [[ $1 == "$2" ]] && {
         printf "%s\n" "${1##*/}"
         return
     }
 
     # corner case #2 - own dir link
-    [[ "${1%/*}" == "$2" ]] && {
+    [[ ${1%/*} == "$2" ]] && {
         printf ".\n"
         return
     }
 
-    read -d '/' -r -a __current <<< "$1"
-    read -d '/' -a __absolute <<< "$2"
+    IFS=/ read -r -a __current <<< "$1"
+    IFS=/ read -r -a __absolute <<< "$2"
 
     __abssize=${#__absolute[@]}
     __cursize=${#__current[@]}
 
-    while [[ "${__absolute[__level]}" == "${__current[__level]}" ]]; do
+    while [[ ${__absolute[__level]} == "${__current[__level]}" ]]; do
         ((__level++))
         if ((__level > __abssize || __level > __cursize)); then
             break
@@ -213,8 +221,8 @@ get_fs_env() {
     [[ $1 ]] || return
     unset ID_FS_TYPE
     ID_FS_TYPE=$(blkid -u filesystem -o export -- "$1" \
-        | while read line || [ -n "$line" ]; do
-            if [[ "$line" == TYPE\=* ]]; then
+        | while read -r line || [ -n "$line" ]; do
+            if [[ $line == "TYPE="* ]]; then
                 printf "%s" "${line#TYPE=}"
                 exit 0
             fi
@@ -233,8 +241,20 @@ get_fs_env() {
 # 8:2
 get_maj_min() {
     local _majmin
-    _majmin="$(stat -L -c '%t:%T' "$1" 2> /dev/null)"
-    printf "%s" "$((0x${_majmin%:*})):$((0x${_majmin#*:}))"
+    local _out
+
+    if [[ $get_maj_min_cache_file ]]; then
+        _out="$(grep -m1 -oE "^$1 \S+$" "$get_maj_min_cache_file" | awk '{print $NF}')"
+    fi
+
+    if ! [[ "$_out" ]]; then
+        _majmin="$(stat -L -c '%t:%T' "$1" 2> /dev/null)"
+        _out="$(printf "%s" "$((0x${_majmin%:*})):$((0x${_majmin#*:}))")"
+        if [[ $get_maj_min_cache_file ]]; then
+            echo "$1 $_out" >> "$get_maj_min_cache_file"
+        fi
+    fi
+    echo -n "$_out"
 }
 
 # get_devpath_block <device>
@@ -244,8 +264,8 @@ get_devpath_block() {
     _majmin=$(get_maj_min "$1")
 
     for _i in /sys/block/*/dev /sys/block/*/*/dev; do
-        [[ -e "$_i" ]] || continue
-        if [[ "$_majmin" == "$(< "$_i")" ]]; then
+        [[ -e $_i ]] || continue
+        if [[ $_majmin == "$(< "$_i")" ]]; then
             printf "%s" "${_i%/dev}"
             return 0
         fi
@@ -260,7 +280,7 @@ get_persistent_dev() {
     _dev=$(get_maj_min "$1")
     [ -z "$_dev" ] && return
 
-    if [[ -n "$persistent_policy" ]]; then
+    if [[ -n $persistent_policy ]]; then
         _pol="/dev/disk/${persistent_policy}/*"
     else
         _pol=
@@ -275,7 +295,7 @@ get_persistent_dev() {
         /dev/disk/by-partlabel/* \
         /dev/disk/by-id/* \
         /dev/disk/by-path/*; do
-        [[ -e "$i" ]] || continue
+        [[ -e $i ]] || continue
         [[ $i == /dev/mapper/control ]] && continue
         [[ $i == /dev/mapper/mpath* ]] && continue
         _tmp=$(get_maj_min "$i")
@@ -349,10 +369,10 @@ find_block_device() {
     if [[ $use_fstab != yes ]]; then
         [[ -d $_find_mpt/. ]]
         findmnt -e -v -n -o 'MAJ:MIN,SOURCE' --target "$_find_mpt" | {
-            while read _majmin _dev || [ -n "$_dev" ]; do
+            while read -r _majmin _dev || [ -n "$_dev" ]; do
                 if [[ -b $_dev ]]; then
                     if ! [[ $_majmin ]] || [[ $_majmin == 0:* ]]; then
-                        _majmin=$(get_maj_min $_dev)
+                        _majmin=$(get_maj_min "$_dev")
                     fi
                     if [[ $_majmin ]]; then
                         printf "%s\n" "$_majmin"
@@ -361,7 +381,7 @@ find_block_device() {
                     fi
                     return 0
                 fi
-                if [[ $_dev = *:* ]]; then
+                if [[ $_dev == *:* ]]; then
                     printf "%s\n" "$_dev"
                     return 0
                 fi
@@ -370,15 +390,16 @@ find_block_device() {
         } && return 0
     fi
     # fall back to /etc/fstab
+    [[ ! -f "$dracutsysrootdir"/etc/fstab ]] && return 1
 
     findmnt -e --fstab -v -n -o 'MAJ:MIN,SOURCE' --target "$_find_mpt" | {
-        while read _majmin _dev || [ -n "$_dev" ]; do
+        while read -r _majmin _dev || [ -n "$_dev" ]; do
             if ! [[ $_dev ]]; then
                 _dev="$_majmin"
                 unset _majmin
             fi
             if [[ -b $_dev ]]; then
-                [[ $_majmin ]] || _majmin=$(get_maj_min $_dev)
+                [[ $_majmin ]] || _majmin=$(get_maj_min "$_dev")
                 if [[ $_majmin ]]; then
                     printf "%s\n" "$_majmin"
                 else
@@ -386,7 +407,7 @@ find_block_device() {
                 fi
                 return 0
             fi
-            if [[ $_dev = *:* ]]; then
+            if [[ $_dev == *:* ]]; then
                 printf "%s\n" "$_dev"
                 return 0
             fi
@@ -410,9 +431,9 @@ find_mp_fstype() {
 
     if [[ $use_fstab != yes ]]; then
         findmnt -e -v -n -o 'FSTYPE' --target "$1" | {
-            while read _fs || [ -n "$_fs" ]; do
+            while read -r _fs || [ -n "$_fs" ]; do
                 [[ $_fs ]] || continue
-                [[ $_fs = "autofs" ]] && continue
+                [[ $_fs == "autofs" ]] && continue
                 printf "%s" "$_fs"
                 return 0
             done
@@ -420,10 +441,12 @@ find_mp_fstype() {
         } && return 0
     fi
 
+    [[ ! -f "$dracutsysrootdir"/etc/fstab ]] && return 1
+
     findmnt --fstab -e -v -n -o 'FSTYPE' --target "$1" | {
-        while read _fs || [ -n "$_fs" ]; do
+        while read -r _fs || [ -n "$_fs" ]; do
             [[ $_fs ]] || continue
-            [[ $_fs = "autofs" ]] && continue
+            [[ $_fs == "autofs" ]] && continue
             printf "%s" "$_fs"
             return 0
         done
@@ -444,15 +467,15 @@ find_mp_fstype() {
 find_dev_fstype() {
     local _find_dev _fs
     _find_dev="$1"
-    if ! [[ "$_find_dev" = /dev* ]]; then
+    if ! [[ $_find_dev == /dev* ]]; then
         [[ -b "/dev/block/$_find_dev" ]] && _find_dev="/dev/block/$_find_dev"
     fi
 
     if [[ $use_fstab != yes ]]; then
         findmnt -e -v -n -o 'FSTYPE' --source "$_find_dev" | {
-            while read _fs || [ -n "$_fs" ]; do
+            while read -r _fs || [ -n "$_fs" ]; do
                 [[ $_fs ]] || continue
-                [[ $_fs = "autofs" ]] && continue
+                [[ $_fs == "autofs" ]] && continue
                 printf "%s" "$_fs"
                 return 0
             done
@@ -460,10 +483,12 @@ find_dev_fstype() {
         } && return 0
     fi
 
+    [[ ! -f "$dracutsysrootdir"/etc/fstab ]] && return 1
+
     findmnt --fstab -e -v -n -o 'FSTYPE' --source "$_find_dev" | {
-        while read _fs || [ -n "$_fs" ]; do
+        while read -r _fs || [ -n "$_fs" ]; do
             [[ $_fs ]] || continue
-            [[ $_fs = "autofs" ]] && continue
+            [[ $_fs == "autofs" ]] && continue
             printf "%s" "$_fs"
             return 0
         done
@@ -486,6 +511,8 @@ find_mp_fsopts() {
         findmnt -e -v -n -o 'OPTIONS' --target "$1" 2> /dev/null && return 0
     fi
 
+    [[ ! -f "$dracutsysrootdir"/etc/fstab ]] && return 1
+
     findmnt --fstab -e -v -n -o 'OPTIONS' --target "$1"
 }
 
@@ -501,7 +528,7 @@ find_mp_fsopts() {
 find_dev_fsopts() {
     local _find_dev
     _find_dev="$1"
-    if ! [[ "$_find_dev" = /dev* ]]; then
+    if ! [[ $_find_dev == /dev* ]]; then
         [[ -b "/dev/block/$_find_dev" ]] && _find_dev="/dev/block/$_find_dev"
     fi
 
@@ -509,6 +536,8 @@ find_dev_fsopts() {
         findmnt -e -v -n -o 'OPTIONS' --source "$_find_dev" 2> /dev/null && return 0
     fi
 
+    [[ ! -f "$dracutsysrootdir"/etc/fstab ]] && return 1
+
     findmnt --fstab -e -v -n -o 'OPTIONS' --source "$_find_dev"
 }
 
@@ -542,15 +571,15 @@ host_fs_all() {
 check_block_and_slaves() {
     local _x
     [[ -b /dev/block/$2 ]] || return 1 # Not a block device? So sorry.
-    if ! lvm_internal_dev $2; then "$1" $2 && return; fi
+    if ! lvm_internal_dev "$2"; then "$1" "$2" && return; fi
     check_vol_slaves "$@" && return 0
     if [[ -f /sys/dev/block/$2/../dev ]] && [[ /sys/dev/block/$2/../subsystem -ef /sys/class/block ]]; then
-        check_block_and_slaves $1 $(< "/sys/dev/block/$2/../dev") && return 0
+        check_block_and_slaves "$1" "$(< "/sys/dev/block/$2/../dev")" && return 0
     fi
-    for _x in /sys/dev/block/$2/slaves/*; do
+    for _x in /sys/dev/block/"$2"/slaves/*; do
         [[ -f $_x/dev ]] || continue
         [[ $_x/subsystem -ef /sys/class/block ]] || continue
-        check_block_and_slaves $1 $(< "$_x/dev") && return 0
+        check_block_and_slaves "$1" "$(< "$_x/dev")" && return 0
     done
     return 1
 }
@@ -558,17 +587,17 @@ check_block_and_slaves() {
 check_block_and_slaves_all() {
     local _x _ret=1
     [[ -b /dev/block/$2 ]] || return 1 # Not a block device? So sorry.
-    if ! lvm_internal_dev $2 && "$1" $2; then
+    if ! lvm_internal_dev "$2" && "$1" "$2"; then
         _ret=0
     fi
     check_vol_slaves_all "$@" && return 0
     if [[ -f /sys/dev/block/$2/../dev ]] && [[ /sys/dev/block/$2/../subsystem -ef /sys/class/block ]]; then
-        check_block_and_slaves_all $1 $(< "/sys/dev/block/$2/../dev") && _ret=0
+        check_block_and_slaves_all "$1" "$(< "/sys/dev/block/$2/../dev")" && _ret=0
     fi
-    for _x in /sys/dev/block/$2/slaves/*; do
+    for _x in /sys/dev/block/"$2"/slaves/*; do
         [[ -f $_x/dev ]] || continue
         [[ $_x/subsystem -ef /sys/class/block ]] || continue
-        check_block_and_slaves_all $1 $(< "$_x/dev") && _ret=0
+        check_block_and_slaves_all "$1" "$(< "$_x/dev")" && _ret=0
     done
     return $_ret
 }
@@ -583,8 +612,8 @@ for_each_host_dev_and_slaves_all() {
     [[ "${host_devs[*]}" ]] || return 2
 
     for _dev in "${host_devs[@]}"; do
-        [[ -b "$_dev" ]] || continue
-        if check_block_and_slaves_all $_func $(get_maj_min $_dev); then
+        [[ -b $_dev ]] || continue
+        if check_block_and_slaves_all "$_func" "$(get_maj_min "$_dev")"; then
             _ret=0
         fi
     done
@@ -598,12 +627,33 @@ for_each_host_dev_and_slaves() {
     [[ "${host_devs[*]}" ]] || return 2
 
     for _dev in "${host_devs[@]}"; do
-        [[ -b "$_dev" ]] || continue
-        check_block_and_slaves $_func $(get_maj_min $_dev) && return 0
+        [[ -b $_dev ]] || continue
+        check_block_and_slaves "$_func" "$(get_maj_min "$_dev")" && return 0
     done
     return 1
 }
 
+# /sys/dev/block/major:minor is symbol link to real hardware device
+# go downstream $(realpath /sys/dev/block/major:minor) to detect driver
+get_blockdev_drv_through_sys() {
+    local _block_mods=""
+    local _path
+
+    _path=$(realpath "$1")
+    while true; do
+        if [[ -L "$_path"/driver/module ]]; then
+            _mod=$(realpath "$_path"/driver/module)
+            _mod=$(basename "$_mod")
+            _block_mods="$_block_mods $_mod"
+        fi
+        _path=$(dirname "$_path")
+        if [[ $_path == '/sys/devices' ]] || [[ $_path == '/' ]]; then
+            break
+        fi
+    done
+    echo "$_block_mods"
+}
+
 # ugly workaround for the lvm design
 # There is no volume group device,
 # so, there are no slave devices for volume groups.
@@ -611,40 +661,37 @@ for_each_host_dev_and_slaves() {
 # but you cannot create the logical volume without the volume group.
 # And the volume group might be bigger than the devices the LV needs.
 check_vol_slaves() {
-    local _lv _vg _pv _dm _majmin
+    local _vg _pv _dm _majmin
     _majmin="$2"
-    _lv="/dev/block/$_majmin"
     _dm=/sys/dev/block/$_majmin/dm
-    [[ -f $_dm/uuid && $(< $_dm/uuid) =~ LVM-* ]] || return 1
-    _vg=$(dmsetup splitname --noheadings -o vg_name $(< "$_dm/name"))
+    [[ -f $_dm/uuid && $(< "$_dm"/uuid) =~ LVM-* ]] || return 1
+    _vg=$(dmsetup splitname --noheadings -o vg_name "$(< "$_dm/name")")
     # strip space
     _vg="${_vg//[[:space:]]/}"
     if [[ $_vg ]]; then
         for _pv in $(lvm vgs --noheadings -o pv_name "$_vg" 2> /dev/null); do
-            check_block_and_slaves $1 $(get_maj_min $_pv) && return 0
+            check_block_and_slaves "$1" "$(get_maj_min "$_pv")" && return 0
         done
     fi
     return 1
 }
 
 check_vol_slaves_all() {
-    local _lv _vg _pv _majmin
+    local _vg _pv _majmin
     _majmin="$2"
-    _lv="/dev/block/$_majmin"
     _dm="/sys/dev/block/$_majmin/dm"
-    [[ -f $_dm/uuid && $(< $_dm/uuid) =~ LVM-* ]] || return 1
-    _vg=$(dmsetup splitname --noheadings -o vg_name $(< "$_dm/name"))
+    [[ -f $_dm/uuid && $(< "$_dm"/uuid) =~ LVM-* ]] || return 1
+    _vg=$(dmsetup splitname --noheadings -o vg_name "$(< "$_dm/name")")
     # strip space
     _vg="${_vg//[[:space:]]/}"
     if [[ $_vg ]]; then
         # when filter/global_filter is set, lvm may be failed
-        lvm lvs --noheadings -o vg_name $_vg 2> /dev/null 1> /dev/null
-        if [ $? -ne 0 ]; then
+        if ! lvm lvs --noheadings -o vg_name "$_vg" 2> /dev/null 1> /dev/null; then
             return 1
         fi
 
         for _pv in $(lvm vgs --noheadings -o pv_name "$_vg" 2> /dev/null); do
-            check_block_and_slaves_all $1 $(get_maj_min $_pv)
+            check_block_and_slaves_all "$1" "$(get_maj_min "$_pv")"
         done
         return 0
     fi
@@ -659,12 +706,13 @@ fs_get_option() {
     local _option=$2
     local OLDIFS="$IFS"
     IFS=,
+    # shellcheck disable=SC2086
     set -- $_fsopts
     IFS="$OLDIFS"
     while [ $# -gt 0 ]; do
         case $1 in
             $_option=*)
-                echo ${1#${_option}=}
+                echo "${1#"${_option}"=}"
                 break
                 ;;
         esac
@@ -690,7 +738,7 @@ check_kernel_config() {
 # 0 if the kernel module is either built-in or available
 # 1 if the kernel module is not enabled
 check_kernel_module() {
-    modprobe -S $kernel --dry-run $1 &> /dev/null || return 1
+    modprobe -d "$dracutsysrootdir" -S "$kernel" --dry-run "$1" &> /dev/null || return 1
 }
 
 # get_cpu_vendor
@@ -707,20 +755,23 @@ get_cpu_vendor() {
 # get_host_ucode
 # Get the hosts' ucode file based on the /proc/cpuinfo
 get_ucode_file() {
-    local family=$(grep -E "cpu family" /proc/cpuinfo | head -1 | sed s/.*:\ //)
-    local model=$(grep -E "model" /proc/cpuinfo | grep -v name | head -1 | sed s/.*:\ //)
-    local stepping=$(grep -E "stepping" /proc/cpuinfo | head -1 | sed s/.*:\ //)
+    local family
+    local model
+    local stepping
+    family=$(grep -E "cpu family" /proc/cpuinfo | head -1 | sed "s/.*:\ //")
+    model=$(grep -E "model" /proc/cpuinfo | grep -v name | head -1 | sed "s/.*:\ //")
+    stepping=$(grep -E "stepping" /proc/cpuinfo | head -1 | sed "s/.*:\ //")
 
     if [[ "$(get_cpu_vendor)" == "AMD" ]]; then
         if [[ $family -ge 21 ]]; then
-            printf "microcode_amd_fam%xh.bin" $family
+            printf "microcode_amd_fam%xh.bin" "$family"
         else
             printf "microcode_amd.bin"
         fi
     fi
     if [[ "$(get_cpu_vendor)" == "Intel" ]]; then
         # The /proc/cpuinfo are in decimal.
-        printf "%02x-%02x-%02x" ${family} ${model} ${stepping}
+        printf "%02x-%02x-%02x" "${family}" "${model}" "${stepping}"
     fi
 }
 
@@ -728,9 +779,9 @@ get_ucode_file() {
 # If it is an LVM device, touch only devices which have /dev/VG/LV symlink.
 lvm_internal_dev() {
     local dev_dm_dir=/sys/dev/block/$1/dm
-    [[ ! -f $dev_dm_dir/uuid || $(< $dev_dm_dir/uuid) != LVM-* ]] && return 1 # Not an LVM device
+    [[ ! -f $dev_dm_dir/uuid || $(< "$dev_dm_dir"/uuid) != LVM-* ]] && return 1 # Not an LVM device
     local DM_VG_NAME DM_LV_NAME DM_LV_LAYER
-    eval $(dmsetup splitname --nameprefixes --noheadings --rows "$(< $dev_dm_dir/name)" 2> /dev/null)
+    eval "$(dmsetup splitname --nameprefixes --noheadings --rows "$(< "$dev_dm_dir"/name)" 2> /dev/null)"
     [[ ${DM_VG_NAME} ]] && [[ ${DM_LV_NAME} ]] || return 0 # Better skip this!
     [[ ${DM_LV_LAYER} ]] || [[ ! -L /dev/${DM_VG_NAME}/${DM_LV_NAME} ]]
 }
@@ -738,21 +789,47 @@ lvm_internal_dev() {
 btrfs_devs() {
     local _mp="$1"
     btrfs device usage "$_mp" \
-        | while read _dev _rest; do
+        | while read -r _dev _; do
             str_starts "$_dev" "/" || continue
             _dev=${_dev%,}
             printf -- "%s\n" "$_dev"
         done
 }
 
+zfs_devs() {
+    local _mp="$1"
+    zpool list -H -v -P "${_mp%%/*}" | awk -F$'\t' '$2 ~ /^\// {print $2}' \
+        | while read -r _dev; do
+            realpath "${_dev}"
+        done
+}
+
 iface_for_remote_addr() {
+    # shellcheck disable=SC2046
     set -- $(ip -o route get to "$1")
-    echo $3
+    while [ $# -gt 0 ]; do
+        case $1 in
+            dev)
+                echo "$2"
+                return
+                ;;
+        esac
+        shift
+    done
 }
 
 local_addr_for_remote_addr() {
+    # shellcheck disable=SC2046
     set -- $(ip -o route get to "$1")
-    echo $5
+    while [ $# -gt 0 ]; do
+        case $1 in
+            src)
+                echo "$2"
+                return
+                ;;
+        esac
+        shift
+    done
 }
 
 peer_for_addr() {
@@ -809,7 +886,7 @@ is_unbracketed_ipv6_address() {
 # remote address can be reached
 ip_params_for_remote_addr() {
     local remote_addr=$1
-    local ifname local_addr peer netmask= gateway ifmac
+    local ifname local_addr peer netmask gateway ifmac
 
     [[ $remote_addr ]] || return 1
     ifname=$(iface_for_remote_addr "$remote_addr")
@@ -869,7 +946,7 @@ block_is_nbd() {
 }
 
 # block_is_iscsi <maj:min>
-# Check whether $1 is an nbd device
+# Check whether $1 is an iSCSI device
 block_is_iscsi() {
     local _dir
     local _dev=$1
@@ -893,7 +970,7 @@ block_is_fcoe() {
     until [[ -d "$_dir/sys" ]]; do
         _dir="$_dir/.."
         if [[ -d "$_dir/subsystem" ]]; then
-            subsystem=$(basename $(readlink $_dir/subsystem))
+            subsystem=$(basename "$(readlink "$_dir"/subsystem)")
             [[ $subsystem == "fcoe" ]] && return 0
         fi
     done
@@ -906,7 +983,102 @@ block_is_netdevice() {
     block_is_nbd "$1" || block_is_iscsi "$1" || block_is_fcoe "$1"
 }
 
+# convert the driver name given by udevadm to the corresponding kernel module name
+get_module_name() {
+    local dev_driver
+    while read -r dev_driver; do
+        case "$dev_driver" in
+            mmcblk)
+                echo "mmc_block"
+                ;;
+            *)
+                echo "$dev_driver"
+                ;;
+        esac
+    done
+}
+
 # get the corresponding kernel modules of a /sys/class/*/* or/dev/* device
 get_dev_module() {
-    udevadm info -a "$1" | sed -n 's/\s*DRIVERS=="\(\S\+\)"/\1/p'
+    local dev_attr_walk
+    local dev_drivers
+    local dev_paths
+    dev_attr_walk=$(udevadm info -a "$1")
+    dev_drivers=$(echo "$dev_attr_walk" \
+        | sed -n 's/\s*DRIVERS=="\(\S\+\)"/\1/p' \
+        | get_module_name)
+
+    # also return modalias info from sysfs paths parsed by udevadm
+    dev_paths=$(echo "$dev_attr_walk" | sed -n 's/.*\(\/devices\/.*\)'\'':/\1/p')
+    local dev_path
+    for dev_path in $dev_paths; do
+        local modalias_file="/sys$dev_path/modalias"
+        if [ -e "$modalias_file" ]; then
+            dev_drivers="$(printf "%s\n%s" "$dev_drivers" "$(cat "$modalias_file")")"
+        fi
+    done
+
+    # if no kernel modules found and device is in a virtual subsystem, follow symlinks
+    if [[ -z $dev_drivers && $(udevadm info -q path "$1") == "/devices/virtual"* ]]; then
+        local dev_vkernel
+        local dev_vsubsystem
+        local dev_vpath
+        dev_vkernel=$(echo "$dev_attr_walk" | sed -n 's/\s*KERNELS=="\(\S\+\)"/\1/p' | tail -1)
+        dev_vsubsystem=$(echo "$dev_attr_walk" | sed -n 's/\s*SUBSYSTEMS=="\(\S\+\)"/\1/p' | tail -1)
+        dev_vpath="/sys/devices/virtual/$dev_vsubsystem/$dev_vkernel"
+        if [[ -n $dev_vkernel && -n $dev_vsubsystem && -d $dev_vpath ]]; then
+            local dev_links
+            local dev_link
+            dev_links=$(find "$dev_vpath" -maxdepth 1 -type l ! -name "subsystem" -exec readlink {} \;)
+            for dev_link in $dev_links; do
+                [[ -n $dev_drivers && ${dev_drivers: -1} != $'\n' ]] && dev_drivers+=$'\n'
+                dev_drivers+=$(udevadm info -a "$dev_vpath/$dev_link" \
+                    | sed -n 's/\s*DRIVERS=="\(\S\+\)"/\1/p' \
+                    | get_module_name \
+                    | grep -v -e pcieport)
+            done
+        fi
+    fi
+    echo "$dev_drivers"
+}
+
+# Check if file is in PE format
+pe_file_format() {
+    if [[ $# -eq 1 ]]; then
+        local magic
+        magic=$(objdump -p "$1" \
+            | gawk '{if ($1 == "Magic"){print strtonum("0x"$2)}}')
+        magic=$(printf "0x%x" "$magic")
+        # 0x10b (PE32), 0x20b (PE32+)
+        [[ $magic == 0x20b || $magic == 0x10b ]] && return 0
+    fi
+    return 1
+}
+
+# Get specific data from the PE header
+pe_get_header_data() {
+    local data_header
+    [[ $# -ne "2" ]] && return 1
+    [[ $(pe_file_format "$1") -eq 1 ]] && return 1
+    data_header=$(objdump -p "$1" \
+        | awk -v data="$2" '{if ($1 == data){print $2}}')
+    echo "$data_header"
+}
+
+# Get the SectionAlignment data from the PE header
+pe_get_section_align() {
+    local align_hex
+    [[ $# -ne "1" ]] && return 1
+    align_hex=$(pe_get_header_data "$1" "SectionAlignment")
+    [[ $? -eq 1 ]] && return 1
+    echo "$((16#$align_hex))"
+}
+
+# Get the ImageBase data from the PE header
+pe_get_image_base() {
+    local base_image
+    [[ $# -ne "1" ]] && return 1
+    base_image=$(pe_get_header_data "$1" "ImageBase")
+    [[ $? -eq 1 ]] && return 1
+    echo "$((16#$base_image))"
 }