agama/live/live-root/usr/lib/dracut/modules.d/99hcn/parse-hcn.sh
2026-08-17 14:18:55 +01:00

666 lines
25 KiB
Bash
Executable file

#!/bin/sh
# HCN (Hybrid Cloud Network) dracut module
# Discovers HCN configurations from device-tree and sets up bonding
#
# Workflow:
# 1. Check if HCN is enabled (rd.hcn=1, rd.hcn.ip, or rd.hcn.route), exit early if not
# 2. Discover HCN devices from device-tree (/proc/device-tree)
# - Scans PCI devices (SR-IOV ethernet adapters)
# - Scans vdevices (VNIC and Virtual Ethernet)
# 3. Build MAPPINGS: bond names -> slave devices with MACs and modes
# 4. Process rd.hcn.ip= and rd.hcn.route= parameters and replace slave references with bonds
# 5. Carry over the remaining network options of the real kernel command line
# (nameserver=, rd.peerdns=, rd.net.dhcp.*)
# 6. Generate NetworkManager connections to a temporary directory via nm-initrd-generator
# 7. Fix up generated connections to use correct bond masters and naming
# 8. Copy connections to /etc/NetworkManager/system-connections for persistence
#
# This script runs from hcn-init-initrd.service, once udev has discovered the
# devices. Much earlier, hcn-cmdline.sh has already told NetworkManager and the
# other dracut modules to leave the network alone by writing an "ip=hcn" marker
# to /etc/cmdline.d, so nothing configures the bond ports behind our back.
#
# Usage:
# rd.hcn=1 - Create bond connections only (no IP configuration)
# rd.hcn.ip=<config> - Create bonds with HCN-specific IP configuration
# rd.hcn.route=<config> - Create bonds with HCN-specific route configuration
command -v getargs >/dev/null || . /lib/dracut-lib.sh
# Helper to read 4 bytes from device-tree and return hex string
xdump4() {
hexdump -n 4 -ve '/1 "%02x"' "$1"
}
# Helper to get MAC address from device-tree
get_mac() {
local dev=$1
if [ -f "$dev/local-mac-address" ]; then
hexdump -ve '/1 "%02x:"' "$dev/local-mac-address" | sed 's/:$//'
fi
}
# Number of 3 second waits left for the devices to show up in sysfs after a
# migration, 3 minutes in total.
#
# The budget is global and not per device on purpose: the devices appear in
# parallel, so waiting for them one by one would multiply the time spent here
# by the number of devices and hcn-init-initrd.service, which allows 5 minutes
# for the whole run, would kill the script in the middle of it.
HCN_WAIT_RETRIES=60
# Function to discover HCN mapping for a device-tree node
#
# On success it sets HCN_MAPPING to "bondname devname mac mode" and returns 0.
# The result is handed over in a variable instead of being printed because the
# function logs its progress with info(), and under systemd info() writes to
# stdout, which would end up mixed into the mapping (see carry_over_cmdline).
get_dev_hcn() {
local dev=$1
local hcnid devname mode mac ofpath
HCN_MAPPING=""
hcnid=$(xdump4 "$dev/ibm,hcn-id")
[ -z "$hcnid" ] && return 1
mode=$(tr -d '\0' <"$dev/ibm,hcn-mode" 2>/dev/null)
ofpath=${dev#/proc/device-tree}
# Wait for device to appear in sysfs. This might take time after migration.
# Every device is looked up at least once, even with the shared wait budget
# already spent by the previous ones. devname is reset on each try because
# ofpathname can resolve a name for a device that is not in sysfs yet, and
# such a name must not be taken as a valid result once the waiting is over.
while :; do
devname=$(ofpathname -l "$ofpath" 2>/dev/null)
if [ -n "$devname" ] && [ -e "/sys/class/net/$devname" ]; then
info "parse-hcn: device $devname ready for $ofpath"
mac=$(get_mac "$dev")
break
fi
devname=""
[ "${HCN_WAIT_RETRIES:-0}" -gt 0 ] || break
# The tries are short so that a device that is already there is not waited
# for longer than needed, but only one out of five is logged, the messages
# also go to the console
if [ $((HCN_WAIT_RETRIES % 5)) -eq 0 ]; then
info "parse-hcn: waiting for device for $ofpath ($HCN_WAIT_RETRIES tries left)"
fi
sleep 3
HCN_WAIT_RETRIES=$((HCN_WAIT_RETRIES - 1))
done
if [ -z "$devname" ]; then
warn "parse-hcn: could not resolve device name for $dev"
return 1
fi
# The bond mapping: bondname devname mac mode
HCN_MAPPING="bond$hcnid $devname ${mac:-none} ${mode:-none}"
return 0
}
# Get a value from a keyfile (INI format)
# Usage: gkeyfile_get <file> <section> <key>
gkeyfile_get() {
awk -v sec="$2" -v key="$3" '
$0 ~ "^[[:space:]]*\\[.*\\][[:space:]]*$" {
current_sec = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_sec)
current_sec = substr(current_sec, 2, length(current_sec) - 2)
in_sec = (current_sec == sec)
}
in_sec {
idx = index($0, "=")
if (idx > 0) {
k = substr($0, 1, idx - 1)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", k)
if (k == key) {
val = substr($0, idx + 1)
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", val)
print val
exit
}
}
}
' "$1"
}
# Check if a key exists in a keyfile (INI format)
# Usage: gkeyfile_has <file> <section> <key>
gkeyfile_has() {
awk -v sec="$2" -v key="$3" '
$0 ~ "^[[:space:]]*\\[.*\\][[:space:]]*$" {
current_sec = $0
gsub(/^[[:space:]]+|[[:space:]]+$/, "", current_sec)
current_sec = substr(current_sec, 2, length(current_sec) - 2)
in_sec = (current_sec == sec)
}
in_sec {
idx = index($0, "=")
if (idx > 0) {
k = substr($0, 1, idx - 1)
gsub(/^[[:space:]]+|[[:space:]]+$/, "", k)
if (k == key) {
found = 1
exit
}
}
}
END { exit !found }
' "$1"
}
# Set a value in a keyfile (INI format)
# Usage: gkeyfile_set <file> <section> <key> <val>
gkeyfile_set() {
if gkeyfile_has "$1" "$2" "$3"; then
sed -i "/^\[$2\]/,/^\[/ s|^\([[:space:]]*$3[[:space:]]*=[[:space:]]*\).*|\1$4|" "$1"
else
sed -i "/^\[$2\]/a $3=$4" "$1"
fi
}
# Parse a NetworkManager connection file and extract key fields
# Returns: id|uuid|ifname|master|controller|mac (pipe-separated)
parse_nm_connection() {
awk -F'=' '
/^[[:space:]]*id[[:space:]]*=/ {
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
id = $2
}
/^[[:space:]]*uuid[[:space:]]*=/ {
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
uuid = $2
}
/^[[:space:]]*interface-name[[:space:]]*=/ {
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
ifname = $2
}
/^[[:space:]]*master[[:space:]]*=/ {
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
master = $2
}
/^[[:space:]]*controller[[:space:]]*=/ {
gsub(/^[[:space:]]+|[[:space:]]+$|"/, "", $2)
controller = $2
}
/^[[:space:]]*mac-address[[:space:]]*=/ {
gsub(/^[[:space:]]+|[[:space:]]+$|:|"/, "", $2)
mac = tolower($2)
}
END {
print id "|" uuid "|" ifname "|" master "|" controller "|" mac
}
' "$1"
}
# Function to fix up NetworkManager connection files
#
# IMPORTANT: This function should ONLY be called when rd.hcn=1 is set
#
# nm-initrd-generator creates connections with:
# - Interface names as connection IDs (e.g., "eth0")
# - UUIDs as master/controller references instead of bond names
# - Generic interface-name settings
#
# We need to transform these to:
# - Bond-based connection IDs (e.g., "bond0-eth0")
# - Bond names as master/controller references (e.g., "bond0")
# - Correct slave-type/port-type set to 'bond'
# - Match slaves to correct bonds based on MAPPINGS
#
# This function only modifies connections that are related to HCN bonds
# (either bond interfaces themselves or slaves found in MAPPINGS)
fixup_nm_connections() {
local conn_dir="${HCN_RUNTIME_CONN_DIR:-$NM_RUNTIME_CONN_DIR}"
[ -d "$conn_dir" ] || return 0
local con id uuid ifname master controller mac uuid_map map
local found_master found_ifname mapping_info new_id
# First pass: build UUID to ID mapping for resolution
for con in "$conn_dir"/*.nmconnection; do
[ -e "$con" ] || continue
IFS='|' read -r id uuid ifname master controller mac <<EOF
$(parse_nm_connection "$con")
EOF
[ -n "$uuid" ] && [ -n "$id" ] && uuid_map="$uuid_map $uuid:$id"
done
# Second pass: fixup files (only those related to HCN bonds)
for con in "$conn_dir"/*.nmconnection; do
[ -e "$con" ] || continue
# Start from scratch, otherwise a connection that is in no mapping and
# whose controller is no HCN bond would inherit the values of the previous
# iteration and be treated as HCN-related
found_master="" found_ifname=""
# Extract connection details
IFS='|' read -r id uuid ifname master controller mac <<EOF
$(parse_nm_connection "$con")
EOF
# Guard: Ensure we have at least id and uuid
if [ -z "$id" ] || [ -z "$uuid" ]; then
info "parse-hcn: skipping invalid connection file $con"
continue
fi
# Resolve UUIDs to names for master/controller
for map in $uuid_map; do
if [ "$master" = "${map%:*}" ]; then
master=${map#*:}
gkeyfile_set "$con" connection master "$master"
elif [ "$controller" = "${map%:*}" ]; then
controller=${map#*:}
gkeyfile_set "$con" connection controller "$controller"
fi
done
# Fallback if interface-name is missing
[ -z "$ifname" ] && ifname="$id"
# Search MAPPINGS for this connection to find the correct master
# Only connections in MAPPINGS or with HCN bond masters will be modified
mapping_info=$(echo "$MAPPINGS" | awk -v iface="$ifname" -v mac="$mac" '
{
for (i=1; i<=NF; i+=4) {
m_bond = $(i)
m_iface = $(i+1)
m_mac = $(i+2)
gsub(/:/, "", m_mac)
m_mac = tolower(m_mac)
if ((iface != "" && m_iface == iface) || (mac != "" && m_mac == mac)) {
print m_bond, m_iface
exit
}
}
}
')
if [ -n "$mapping_info" ]; then
found_master=${mapping_info% *}
found_ifname=${mapping_info#* }
elif strstr " $BOND_LIST " " ${master:-$controller} "; then
found_master=${master:-$controller}
found_ifname=$ifname
fi
# Only modify connections that are HCN-related
if [ -n "$found_master" ]; then
info "parse-hcn: fixing up connection $id (HCN-related)"
# Ensure controller/master and port-type/slave-type are correct
if gkeyfile_has "$con" connection controller; then
gkeyfile_set "$con" connection controller "$found_master"
elif gkeyfile_has "$con" connection master; then
gkeyfile_set "$con" connection master "$found_master"
else
gkeyfile_set "$con" connection controller "$found_master"
fi
if gkeyfile_has "$con" connection slave-type; then
gkeyfile_set "$con" connection slave-type bond
else
gkeyfile_set "$con" connection port-type bond
fi
# Rename connection ID and file to match expectations
new_id="$found_master-$found_ifname"
if [ "$id" != "$new_id" ]; then
info "parse-hcn: renaming connection $id to $new_id"
gkeyfile_set "$con" connection id "$new_id"
mv "$con" "$conn_dir/$new_id.nmconnection"
fi
fi
done
}
# Carry over the rest of the network command line
#
# nm-initrd-generator is called with a command line built from scratch, so any
# network option the user really passed is invisible to it unless it is copied
# over here. The generator run NetworkManager does on its own is no substitute:
# the "ip=hcn" marker keeps that run from producing any connection, so
# everything it would have parsed into one is lost.
#
# Options that end up in a global configuration file rather than in a
# connection (rd.net.dns, rd.net.dns-backend, rd.net.dns-resolve-mode,
# rd.net.timeout.carrier) are left out on purpose, the NetworkManager run
# already wrote them to /run/NetworkManager/conf.d.
#
# Only the options that name no device are copied. The ones that do
# (rd.net.dhcp.client-id=, bootdev=, rd.ethtool=, and the vlan=, bridge= and
# team= stacked devices) would need the port reference rewritten to its bond,
# and the generator creates a full connection for any device named in them,
# which this module would then persist to /etc.
#
# Appends the options to NEW_ARGS. The result is not printed on purpose: the
# function logs what it copies with info(), and info() writes to stdout when
# DRACUT_SYSTEMD is set, so the output of a function using it cannot be
# captured with a command substitution without getting the log messages mixed
# into the value.
#
# Only options taking a value can be carried over this way, because getargs()
# prints nothing for an option given as a bare flag. All the ones below do take
# one, rd.peerdns is used as rd.peerdns=0. Keep that in mind before adding a
# boolean option here, it would need getargbool() instead.
carry_over_cmdline() {
local opt val
for opt in nameserver rd.peerdns rd.net.timeout.dhcp rd.net.dhcp.retry \
rd.net.dhcp.vendor-class rd.net.dhcp.dscp; do
for val in $(getargs "$opt"); do
info "parse-hcn: keeping $opt=$val"
NEW_ARGS="$NEW_ARGS $opt=$val"
done
done
}
# --- Main Execution ---
info "parse-hcn: starting"
MAPPINGS=""
if [ -d /proc/device-tree ]; then
# Scan PCI devices (SR-IOV)
for dev in /proc/device-tree/pci*/ethernet*; do
[ -e "$dev/ibm,hcn-id" ] || continue
info "parse-hcn: checking PCI device $dev"
if get_dev_hcn "$dev"; then
MAPPINGS="$MAPPINGS $HCN_MAPPING"
fi
done
# Scan vdevices (VNIC and Virtual Ethernet)
for dev in /proc/device-tree/vdevice/vnic* /proc/device-tree/vdevice/l-lan*; do
[ -e "$dev/ibm,hcn-id" ] || continue
info "parse-hcn: checking vdevice $dev"
if get_dev_hcn "$dev"; then
MAPPINGS="$MAPPINGS $HCN_MAPPING"
fi
done
fi
if [ -z "$MAPPINGS" ]; then
info "parse-hcn: no HCN devices found"
exit 0
fi
info "parse-hcn: discovered mappings: $MAPPINGS"
# Command line processing
NM_CONF_DIR="/etc/NetworkManager"
NM_CONF_CONN_DIR="$NM_CONF_DIR/system-connections"
NM_RUNTIME_DIR="/run/NetworkManager"
NM_RUNTIME_CONN_DIR="$NM_RUNTIME_DIR/system-connections"
HCN_RUNTIME_DIR="/run/hcn"
HCN_RUNTIME_CONN_DIR="$HCN_RUNTIME_DIR/system-connections"
HCN_RUNTIME_CONF_DIR="$HCN_RUNTIME_DIR/conf.d"
NEW_ARGS=""
# Extract unique bond names from discovered mappings
BOND_NAMES=$(echo "$MAPPINGS" | awk '{for(i=1;i<=NF;i+=4) if (!seen[$i]++) print $i}')
# Space separated copy of BOND_NAMES. BOND_NAMES is newline separated, which
# does not work for the " $list " substring checks done with strstr. The echo
# is what collapses the newlines, so it is not the useless one it looks like.
# shellcheck disable=SC2086,SC2116
BOND_LIST=$(echo $BOND_NAMES)
# First discovered bond. Only unqualified *static* configs (a fixed address
# with no interface field) fall back to this bond, because a fixed address can
# belong to a single bond. Method-only configs (dhcp, auto6, ...) carry no
# per-host address and instead fan out to every discovered bond (see below).
FIRST_BOND=${BOND_LIST%% *}
for BONDNAME in $BOND_NAMES; do
SLAVES="" SLAVE_NAMES="" SLAVE_MACS="" PRIMARY=""
OTHER_SLAVE_NAMES="" OTHER_SLAVE_MACS=""
# Extract slaves for this bond and other bonds from MAPPINGS
set -- $MAPPINGS
while [ $# -ge 4 ]; do
if [ "$1" = "$BONDNAME" ]; then
SLAVE_NAMES="$SLAVE_NAMES $2"
[ "$3" != "none" ] && SLAVE_MACS="$SLAVE_MACS $3"
[ "$4" = "primary" ] && PRIMARY="$2"
SLAVES="${SLAVES:+$SLAVES,}$2"
else
OTHER_SLAVE_NAMES="$OTHER_SLAVE_NAMES $2"
[ "$3" != "none" ] && OTHER_SLAVE_MACS="$OTHER_SLAVE_MACS $3"
fi
shift 4
done
info "parse-hcn: configuring bond $BONDNAME with slaves:$SLAVE_NAMES"
# Add bond definition to NEW_ARGS
BOND_OPTS="mode=1,miimon=100,fail_over_mac=2${PRIMARY:+,primary=$PRIMARY}"
NEW_ARGS="$NEW_ARGS bond=$BONDNAME:$SLAVES:$BOND_OPTS"
# Process rd.hcn.ip= - replace slave names/MACs with bond name
for HCN_IP in $(getargs rd.hcn.ip); do
# Lowercased copy for case-insensitive MAC matching: MACs in MAPPINGS are
# lowercase, but may be supplied in uppercase on the kernel command line.
HCN_IP_LC=$(echo "$HCN_IP" | tr 'A-Z' 'a-z')
matched=0
for slave in $SLAVE_NAMES $SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
# MACs (which contain ':') are compared case-insensitively; interface
# names are compared as-is to preserve their case.
hcn_cmp=$HCN_IP
case "$slave" in *:*) hcn_cmp=$HCN_IP_LC ;; esac
# Check if HCN_IP contains the slave (as a whole word/field)
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
matched=1
# Replace slave with bond name (handle boundaries)
current_hcn_ip=$(echo "$hcn_cmp" | sed -E "s#^($slave|$slave_dash)([: ]|$)#$BONDNAME\2#; s#([: ])($slave|$slave_dash)([: ]|$)#\1$BONDNAME\3#g")
NEW_ARGS="$NEW_ARGS ip=$current_hcn_ip"
break
fi
done
# Check if this IP is targeted at another bond's slave
matched_other=0
for slave in $OTHER_SLAVE_NAMES $OTHER_SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
hcn_cmp=$HCN_IP
case "$slave" in *:*) hcn_cmp=$HCN_IP_LC ;; esac
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
matched_other=1
break
fi
done
# No slave matched and no bond name present - apply the unqualified config.
# Method-only configs (dhcp, auto6, ...) fan out to every bond; unqualified
# static addresses fall back to the first bond only (see the *) branch).
has_bond_ip=0
case "$HCN_IP" in
*:bond[0-9]*) has_bond_ip=1 ;;
esac
if [ $matched -eq 0 ] && [ $matched_other -eq 0 ] && [ $has_bond_ip -eq 0 ]; then
case "$HCN_IP" in
dhcp | on | any | single-dhcp | dhcp6 | auto6 | ibft | either6 | link6)
# Method-only config (no fixed address): each bond obtains its own
# configuration independently, so apply it to every discovered bond.
info "parse-hcn: applying $HCN_IP to $BONDNAME"
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:$HCN_IP"
;;
*:dhcp | *:on | *:any | *:dhcp6 | *:auto6 | *:link6)
# Format: rd.hcn.ip=<interface>:{dhcp|on|any|dhcp6|auto6|link6}[:[<mtu>]]
# Method-only config with optional MTU: also applies to every bond.
# Extract optional MTU field (after the method, could be followed by more colons)
temp_ip=${HCN_IP}
# Count colons to determine if there are extra fields
colons=0
while [ "${temp_ip#*:}" != "$temp_ip" ]; do
colons=$((colons + 1))
temp_ip=${temp_ip#*:}
done
# Format: interface:method[:mtu]
# We need at least 1 colon (interface:method), optionally 2 for MTU
if [ "$colons" -ge 1 ]; then
info "parse-hcn: applying DHCP/auto config with optional MTU to $BONDNAME"
NEW_ARGS="$NEW_ARGS ip=$BONDNAME:${HCN_IP#*:}"
fi
;;
*)
# Static IP configuration: a fixed address can belong to only one bond,
# so an unqualified static config falls back to the first bond only.
[ "$BONDNAME" = "$FIRST_BOND" ] || continue
# Count colons to determine format
# Standard format: rd.hcn.ip=<client-IP>:[<peer>]:<gateway-IP>:<netmask>:<client_hostname>:<interface>:{none|off|dhcp|on|any|dhcp6|auto6|ibft}[:[<dns1>][:<dns2>]]
# Minimum fields: client-IP:gateway:netmask:hostname:interface:method (5 colons)
# Extended: adds :dns1 and/or :dns2 (up to 7 colons)
temp_ip=${HCN_IP}
colons=0
while [ "${temp_ip#*:}" != "$temp_ip" ]; do
colons=$((colons + 1))
temp_ip=${temp_ip#*:}
done
if [ "$colons" -ge 5 ]; then
# Already has interface field, just need to replace it with bond name
# Extract fields: we need to replace the 6th field (interface) with BONDNAME
# Preserve any DNS fields that may follow
# shellcheck disable=SC2034
IFS=':' read -r f1 f2 f3 f4 f5 f6 f7 f8 f9 <<EOF
$HCN_IP
EOF
# Reconstruct with bond name as interface (f6 position)
# Note: f7 and beyond might be method:dns1:dns2 or just method
if [ -n "$f9" ]; then
# Has dns2 field
info "parse-hcn: applying static IP config with DNS1 and DNS2 to $BONDNAME"
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME:$f7:$f8:$f9"
elif [ -n "$f8" ]; then
# Has dns1 field
info "parse-hcn: applying static IP config with DNS1 to $BONDNAME"
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME:$f7:$f8"
elif [ -n "$f7" ]; then
# Standard format with method
info "parse-hcn: applying static IP config to $BONDNAME"
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME:$f7"
else
# Fallback - just interface name
NEW_ARGS="$NEW_ARGS ip=$f1:$f2:$f3:$f4:$f5:$BONDNAME"
fi
elif [ "$colons" -lt 5 ]; then
# Pad with colons to reach standard ip= format (legacy behavior)
case $((5 - colons)) in
1) suffix=":" ;;
2) suffix="::" ;;
3) suffix=":::" ;;
4) suffix="::::" ;;
5) suffix=":::::" ;;
*) suffix="" ;;
esac
info "parse-hcn: applying static IP config to $BONDNAME (padded)"
NEW_ARGS="$NEW_ARGS ip=$HCN_IP${suffix}$BONDNAME:none"
fi
;;
esac
fi
done
# Process rd.hcn.route= - replace slave names/MACs with bond name
for HCN_ROUTE in $(getargs rd.hcn.route); do
# Lowercased copy for case-insensitive MAC matching (see rd.hcn.ip above).
HCN_ROUTE_LC=$(echo "$HCN_ROUTE" | tr 'A-Z' 'a-z')
matched=0
for slave in $SLAVE_NAMES $SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
hcn_cmp=$HCN_ROUTE
case "$slave" in *:*) hcn_cmp=$HCN_ROUTE_LC ;; esac
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
matched=1
current_hcn_route=$(echo "$hcn_cmp" | sed -E "s#^($slave|$slave_dash)([: ]|$)#$BONDNAME\2#; s#([: ])($slave|$slave_dash)([: ]|$)#\1$BONDNAME\3#g")
NEW_ARGS="$NEW_ARGS rd.route=$current_hcn_route"
break
fi
done
# Check if this route is targeted at another bond's slave
matched_other=0
for slave in $OTHER_SLAVE_NAMES $OTHER_SLAVE_MACS; do
slave_dash=$(str_replace "$slave" ":" "-")
hcn_cmp=$HCN_ROUTE
case "$slave" in *:*) hcn_cmp=$HCN_ROUTE_LC ;; esac
if strstr ":$hcn_cmp:" ":$slave:" || strstr ":$hcn_cmp:" ":$slave_dash:"; then
matched_other=1
break
fi
done
# No slave matched and no bond name present - apply to first bond
has_bond_route=0
case "$HCN_ROUTE" in
*:bond[0-9]*) has_bond_route=1 ;;
esac
if [ $matched -eq 0 ] && [ $matched_other -eq 0 ] && [ $has_bond_route -eq 0 ] && [ "$BONDNAME" = "$FIRST_BOND" ]; then
temp_route=${HCN_ROUTE}
colons=0
while [ "${temp_route#*:}" != "$temp_route" ]; do
colons=$((colons + 1))
temp_route=${temp_route#*:}
done
if [ "$colons" -le 1 ]; then
info "parse-hcn: applying route to $BONDNAME"
NEW_ARGS="$NEW_ARGS rd.route=$HCN_ROUTE$([ "$colons" -eq 0 ] && echo "::" || echo ":")$BONDNAME"
fi
fi
done
done
carry_over_cmdline
# Write new configuration and update NetworkManager
if [ -n "$NEW_ARGS" ]; then
# Create runtime directories to store HCN connections and the generator
# configuration files
mkdir -p "$HCN_RUNTIME_CONN_DIR" "$HCN_RUNTIME_CONF_DIR"
# Write new cmdline options
info "parse-hcn: new cmdline arguments: $NEW_ARGS"
echo "$NEW_ARGS" > "$HCN_RUNTIME_DIR"/cmdline
# Find and execute nm-initrd-generator
generator_found=0
for gen in /usr/lib/nm-initrd-generator /usr/libexec/nm-initrd-generator; do
[ -x "$gen" ] || continue
generator_found=1
info "parse-hcn: calling $gen"
rm -f "$HCN_RUNTIME_CONN_DIR"/*
# --run-config-dir points to a scratch directory on purpose. The generator
# always writes 15-carrier-timeout.conf, and with the default directory
# this run would overwrite the one NetworkManager generated from the real
# command line, resetting a user supplied rd.net.timeout.carrier.
# shellcheck disable=SC2086
if "$gen" -c "$HCN_RUNTIME_CONN_DIR" -r "$HCN_RUNTIME_CONF_DIR" -- $NEW_ARGS; then
if [ "$(ls -A "$HCN_RUNTIME_CONN_DIR")" ]; then
fixup_nm_connections
# Persist these new HCN connections to /etc
mkdir -p "$NM_CONF_CONN_DIR"
cp -rf "$HCN_RUNTIME_CONN_DIR"/* "$NM_CONF_CONN_DIR"
mkdir -p "$NM_RUNTIME_DIR"/initrd
: > "$NM_RUNTIME_DIR"/initrd/neednet
echo '[ -f /tmp/nm.done ]' > "$hookdir"/initqueue/finished/nm.sh
else
warn "parse-hcn: nm-initrd-generator did not generate any connections"
fi
else
warn "parse-hcn: nm-initrd-generator failed"
fi
break
done
if [ $generator_found -eq 0 ]; then
warn "parse-hcn: nm-initrd-generator not found"
fi
fi