#!/bin/sh # Copyright (C) 2016 Arista Networks, Inc. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # Aboot stage 0 boot script # # This boot0 script can be used in different scenario # - Installation and boot from Aboot (manual or reboot on a new image) # - EOS to SONiC fast-reboot (installation and kexec in EOS) # - SONiC to SONiC fast-reboot (installation and kexec in SONiC) # # Use it this way: # - swipath=path/to/swi install=true boot0 # - swipath=path/to/swi install=true force=true boot0 # - swipath=path/to/swi kexec=true boot0 # # The logic defaults to the first scenario but providing extra environment variable # will affect the behavior of the script. # The list of variables is maintained here # - KERNEL : relative path to the kernel to execute # - INITRD : relative path to the initrd to load # - CMDLINE : place to find the default kernel cmdline to use for the platform # - VERBOSE : setting it to 1 will enable debug traces # # By default the boot0 script will behave for an Aboot based behavior. # Options can be provided to only run some features of this script. # # Extra kernel parameters can be provided at runtime by the user by adding them # into the kernel-params file. # # Secureboot of SONiC SWI images is also supported. # In such cases, there will only be a partial install on the flash. # SONiC will be mostly booting in RAM as a live operating system. # All templated variables should be declared here image_name="image-%%IMAGE_VERSION%%" dockerfs="{{ FILESYSTEM_DOCKERFS }}" boot_image="{{ ABOOT_BOOT_IMAGE }}" installer_image="sonic.swi" docker_dir="{{ DOCKERFS_DIR }}" do_not_clean="do-not-clean" kernel_params="kernel-params" aboot_machine="arista_unknown" info() { printf "%04.2f: $@\n" "$(cut -f1 -d' ' /proc/uptime)"; } err() { info "Error: $@"; } warn() { info "Warning: $@"; } if [ $# -ne 0 ]; then echo "usage: $0 (see code)" exit 1 fi # extract mount point from the swi path, e.g., /mnt/flash/sonic.swi --> /mnt/flash if [ -z "$target_path" ]; then if [ -z "$swipath" ]; then err "target_path= is required when swipath= is not provided" exit 1 fi target_path=$(df "$swipath" | tail -1 | tr -s " " | cut -d ' ' -f6) fi image_path="$target_path/$image_name" hook_path="$image_path/platform/hooks" data_path="$image_path/platform/data" boot_image_path="$image_path/$boot_image" installer_image_path="$image_path/$installer_image" boot_config="$target_path/boot-config" cmdline_allowlist='crashkernel' # for backward compatibility with the sonic_upgrade= behavior install="${install:-${sonic_upgrade:-}}" is_secureboot_enabled() { if $in_aboot; then if [ -x /bin/securebootctl ] && securebootctl sb -display | grep -q "Secure Boot enabled"; then return 0 fi return 1 else if grep -q aboot.secureboot /proc/cmdline; then return 0 fi # FIXME: EOS is not handled here return 1 fi } clean_flash() { ## Remove all the other unnecssary files except swi file, boot-config for f in $(ls -A $target_path); do if [ $f != "${swipath##*/}" ] && [ $f != "boot-config" ] && [ $f != "preserve-installer" ] && [ $f != "$kernel_params" ] && [ $f != "aquota.user" ] && [ $f != "old_config" ] && [ $f != "minigraph.xml" ] && [ $f != "snmp.yml" ] && [ $f != "acl.json" ] then rm -rf "$target_path/$f" fi done } in_array() { local value="$1" shift for other in $@; do if [ "$value" = "$other" ]; then return 0 fi done return 1 } update_boot_config() { local key="$1" local value="$2" if grep "$key" "$boot_config" 2>&1 > /dev/null; then sed -i "s#^$key=.*\$#$key=$value#" "$boot_config" else echo "$key=$value" >> "$boot_config" fi } get_boot_config() { local key="$1" sed -n "s#^$key=##p" "$boot_config" || : } update_next_boot() { local default="$(get_boot_config SWI_DEFAULT)" if [ -z "$default" ]; then warn "boot-config has no variable SWI_DEFAULT" else info "Next reboot will use $default" update_boot_config SWI "$default" fi } get_sorted_hooks() { echo $(find "$1" -name '[0-9][0-9]-*' -type f) } run_hooks() { if [ -d "$hook_path/$1" ]; then for hook in $(get_sorted_hooks "$hook_path/$1"); do if [ ! -z "$hook" ]; then info "Running hook $(basename $hook)" . "$hook" fi done fi } get_uuid_for() { local dev="$1" if type lsblk 2>&1 > /dev/null; then lsblk "$dev" -n --output UUID elif type blkid 2>&1 > /dev/null; then blkid | grep "^$dev" | sed -n 's/^.* UUID="//p' | sed 's/".*$//' fi } cmdline_append() { cat >> /tmp/append } cmdline_clear() { echo -n > /tmp/append } cmdline_add() { echo "$@" >> /tmp/append } cmdline_has() { grep -q "$1" /tmp/append } cmdline_echo() { # echo trims and remove whitespace duplicates echo $(cat /tmp/append | tr '\n' ' ') } cmdline_get() { # extract last matching value for key sed -nr "s/.*$1=([^ ]+).*/\1/p" /tmp/append | tail -n 1 } find_first_kernel_under() { local path="$1" find "$path" -name 'vmlinuz-*' -type f | head -n 1 } find_first_initrd_under() { local path="$1" find "$path" -name 'initrd.img-*' -type f | head -n 1 } get_tmpfs() { local tmpfs="${1:-$(mktemp -d)}" mkdir -p "$tmpfs" if ! $in_aboot && ! mount | grep -q ' /tmp type tmpfs'; then # mount a real tmpfs on /tmp/xxx if /tmp is not one already. mount -t tmpfs "$(dirname $tmpfs)" "$tmpfs" fi echo "$tmpfs" } clean_tmpfs() { local tmpfs="$1" if mount | grep -q "$tmpfs"; then umount "$tmpfs" || : else rm -rf "$tmpfs" fi } move_swi_to_tmpfs() { local oldswi="$1" local tmpfs="$(get_tmpfs "$swi_tmpfs")" local newswi="$tmpfs/$(basename "$oldswi")" mv "$oldswi" "$newswi" echo "$newswi" } cleanup_swi_tmpfs() { rm -f "$swipath" clean_tmpfs "$(dirname "$swipath")" } mount_relpath() { local abspath="$1" local mountpath="$(df "$abspath" | sed -nr '/\//s/^.* ([^ ]+)$/\1/p')" echo "$abspath" | sed "s#^$mountpath/##" } zip_file_offset() { local zipfile="$1" local filename="$2" if $in_aboot; then unzip -qqf "$zipfile" "$filename" else local hexoffset="$(zipdetails "$zipfile" | sed -En "/Filename +'$filename'/,/^$/p" | sed -En 's/^([^ ]+) PAYLOAD$/\1/p')" echo "$((0x$hexoffset))" fi } SWI_ALREADY_INSTALLED=0 SWI_NOT_INSTALLED=1 SWI_VERSION_MISMATCH=2 SWI_OTHER_VARIANT_INSTALLED=3 is_swi_installed() { local swi_path="$1" local swi_version="$(unzip -qp "$swi_path" .imagehash)" local local_version="$(cat $image_path/.imagehash 2>/dev/null || :)" if [ -z "$local_version" ]; then # no image installed for this version return $SWI_NOT_INSTALLED fi if [ "$swi_version" != "$local_version" ]; then warn "Installed image has a different version than $swipath" return $SWI_VERSION_MISMATCH fi if $secureboot; then if [ -s "$installer_image_path" ]; then # secureboot image already installed return $SWI_ALREADY_INSTALLED else # regular image of the same version already installed return $SWI_OTHER_VARIANT_INSTALLED fi else if [ -s "$boot_image_path" ]; then # regular image already installed return $SWI_ALREADY_INSTALLED else # secureboot image of the same version already installed return $SWI_OTHER_VARIANT_INSTALLED fi fi } extract_image() { info "Moving swi to a tmpfs" ## Avoid problematic flash usage spike on older systems, also improves I/O swipath="$(move_swi_to_tmpfs "$swipath")" info "Extracting swi content" ## Unzip the image except boot0 and dockerfs archive unzip -oq "$swipath" -x boot0 "$dockerfs" -d "$image_path" ## detect rootfs type rootfs_type=`grep " $target_path " /proc/mounts | cut -d' ' -f3` info "Extracting $dockerfs from swi" ## Unpacking dockerfs delayed ## 1. when disk is vfat as it does not support symbolic link ## 2. when disk is small, expand it into ramfs during initrd if [ "$rootfs_type" != "vfat" ] && ! cmdline_has docker_inram=on; then mkdir -p "$image_path/$docker_dir" if [ -n "$install" ]; then TAR_EXTRA_OPTION="--numeric-owner --warning=no-timestamp" fi ## extract docker archive unzip -oqp "$swipath" "$dockerfs" | tar xzf - -C "$image_path/$docker_dir" $TAR_EXTRA_OPTION else ## save dockerfs archive in the image directory unzip -oq "$swipath" "$dockerfs" -d "$image_path" info "Unpacking $dockerfs delayed to initrd because $target_path is $rootfs_type or docker_inram is on" fi ## remove installer since it's not needed anymore if $preserve_installer; then info "Preserving installer under $installer_image_path" mv "$swipath" "$installer_image_path" chmod a+r "$installer_image_path" else info "Remove installer" cleanup_swi_tmpfs "$(basename "$swipath")" fi ## use new reduced-size boot swi local swi_boot_path="flash:$image_name/$boot_image" update_boot_config SWI "$swi_boot_path" update_boot_config SWI_DEFAULT "$swi_boot_path" ## sync disk operations sync } extract_image_secureboot() { info "Extracting necessary swi content" unzip -oq "$swipath" platform/firsttime .imagehash -d "$image_path" info "Installing image as $installer_image_path" mv "$swipath" "$installer_image_path" chmod a+r "$installer_image_path" swipath="$installer_image_path" local swi_boot_path="flash:$image_name/$installer_image" update_boot_config SWI "$swi_boot_path" update_boot_config SWI_DEFAULT "$swi_boot_path" sync } prepare_image_secureboot() { local boot_tmpfs="$(get_tmpfs)" info "Extracting boot content in tmpfs" unzip -oq "$swipath" 'boot/*' -d "$boot_tmpfs" info "Generating machine.conf and cmdline" write_secureboot_configs sync # override environment variables preventing external tamper on kernel execution CMDLINE="$(cmdline_echo) $ENV_EXTRA_CMDLINE" KERNEL="$(find_first_kernel_under "$boot_tmpfs")" INITRD="$(find_first_initrd_under "$boot_tmpfs")" } write_machine_config() { ## Detect SKU and create a hardware description file aboot_version=$(cmdline_get Aboot | sed 's/^.*norcal.-//') if [ -x /bin/sysinit ]; then aboot_build_date=$(stat -c %y /bin/sysinit | sed 's/ /T/') else aboot_build_date="unknown" fi cat < ${target_path}/machine.conf aboot_version=$aboot_version aboot_vendor=arista aboot_platform=x86_64-$aboot_machine aboot_machine=$aboot_machine aboot_arch=x86_64 aboot_build_date=$aboot_build_date EOF chmod a+r "${target_path}/machine.conf" } read_system_eeprom() { if [ -x /bin/readprefdl ] && [ -f /tmp/.system-prefdl ]; then readprefdl -f /tmp/.system-prefdl -d > $target_path/.system-prefdl elif [ -f /etc/prefdl ]; then cp /etc/prefdl $target_path/.system-prefdl chmod a+r $target_path/.system-prefdl fi } write_platform_specific_cmdline() { local platform="$(cmdline_get platform)" local sid="$(cmdline_get sid | sed 's/Ssd$//')" # set varlog size to 100MB local varlog_size=100 # sonic_mode is set to fixed by default. sonic_mode="fixed" supervisor_mode="supervisor" linecard_mode="linecard" # detect the size of the flash partition from name in Aboot/EOS/SONiC local flash_size=$(($(df "$target_path" | tail -1 | tr -s ' ' | cut -f2 -d' ') / 1000)) if [ "$platform" = "raven" ]; then # Assuming sid=Cloverdale aboot_machine=arista_7050_qx32 flash_size=2000 cmdline_add modprobe.blacklist=radeon,sp5100_tco cmdline_add acpi=off fi if [ "$platform" = "crow" ]; then # Assuming sid=Clearlake aboot_machine=arista_7050_qx32s flash_size=3700 cmdline_add modprobe.blacklist=radeon,sp5100_tco fi if [ "$sid" = "Upperlake" ] || [ "$sid" = "UpperlakeES" ]; then aboot_machine=arista_7060_cx32s flash_size=3700 fi if [ "$sid" = "UpperlakePlus" ]; then aboot_machine=arista_7060cx2_32s flash_size=3700 fi if [ "$sid" = "Gardena" ] || [ "$sid" = "GardenaE" ]; then aboot_machine=arista_7260cx3_64 flash_size=28000 fi if [ "$sid" = "Alhambra" ]; then aboot_machine=arista_7170_64c flash_size=28000 cmdline_add hugepages=128 fi if [ "$sid" = "Mineral" ]; then aboot_machine=arista_7170_32c flash_size=28000 cmdline_add hugepages=128 fi if [ "$sid" = "MineralD" ]; then aboot_machine=arista_7170_32cd flash_size=28000 cmdline_add hugepages=128 fi if [ "$sid" = "Lodoga" ]; then aboot_machine=arista_7050cx3_32s flash_size=3700 fi if [ "$sid" = "BlackhawkO" ]; then aboot_machine=arista_7060px4_32 flash_size=28000 fi if [ "$sid" = "BlackhawkDD" ]; then aboot_machine=arista_7060dx4_32 flash_size=28000 fi if [ "$sid" = "Smartsville" ]; then aboot_machine=arista_7280cr3_32p4 flash_size=7382 fi if [ "$sid" = "SmartsvilleBK" ]; then aboot_machine=arista_7280cr3k_32p4 flash_size=7382 fi if [ "$sid" = "SmartsvilleDD" ]; then aboot_machine=arista_7280cr3_32d4 flash_size=7382 fi if [ "$sid" = "SmartsvilleDDBK" ]; then aboot_machine=arista_7280cr3k_32d4 flash_size=7382 fi if [ "$sid" = "Clearwater2" ]; then aboot_machine=arista_7800r3_48cq2_lc sonic_mode="$linecard_mode" fi if [ "$sid" = "Clearwater2Ms" ]; then aboot_machine=arista_7800r3_48cqm2_lc sonic_mode="$linecard_mode" fi if [ "$sid" = "OtterLake" ]; then aboot_machine=arista_7800_sup flash_size=30000 sonic_mode=$supervisor_mode fi if in_array "$platform" "rook" "magpie" "woodpecker" "sprucefish"; then cmdline_add tsc=reliable cmdline_add pcie_ports=native cmdline_add rhash_entries=1 cmdline_add usb-storage.delay_use=0 cmdline_add reassign_prefmem fi if in_array "$platform" "rook" "sprucefish"; then cmdline_add iommu=on cmdline_add intel_iommu=on read_system_eeprom fi if in_array "$platform" "crow" "magpie"; then cmdline_add amd_iommu=off cmdline_add modprobe.blacklist=snd_hda_intel,hdaudio read_system_eeprom fi if in_array "$platform" "woodpecker"; then cmdline_add modprobe.blacklist=snd_hda_intel,hdaudio read_system_eeprom fi if in_array "$platform" "lorikeet" "hedgehog"; then read_system_eeprom fi if [ $flash_size -ge 28000 ]; then varlog_size=4096 elif [ $flash_size -ge 3700 ]; then varlog_size=400 elif [ $flash_size -le 2000 ]; then # enable docker_inram for switches with less than 2G of flash cmdline_add docker_inram=on cmdline_add logs_inram=on fi cmdline_add "varlog_size=$varlog_size" cmdline_add "sonic.mode=$sonic_mode" } write_image_specific_cmdline() { # security cmdline_add security=apparmor cmdline_add apparmor=1 # fs configuration cmdline_add rw # disable deterministic interface naming cmdline_add net.ifnames=0 # verbosity cmdline_add quiet # Start showing systemd information from the first failing unit if any. # systemd.show_status=false or quiet can be used to silence systemd entierly cmdline_add systemd.show_status=auto # Pass the MAC address to the new kernel as a command line parameter. This makes it # possible to restore the MAC address in the new kernel without requiring driver modifications. if [ -f /sys/class/net/ma1/address ]; then cmdline_add "hwaddr_ma1=$(cat /sys/class/net/ma1/address)" elif [ -f /sys/class/net/eth0/address ]; then cmdline_add "hwaddr_ma1=$(cat /sys/class/net/eth0/address)" else err "Management port not found." fi # Obtain root partition uuid local mountstr="$(mount | grep " $target_path ")" local rootdev="$(echo $mountstr | cut -f1 -d' ')" local rootfstype="$(echo $mountstr | cut -f5 -d' ')" local rootuuid="$(get_uuid_for $rootdev)" if [ -z "$rootuuid" ] || [ "$rootfstype" = "vfat" ] ; then cmdline_add "root=$rootdev" else cmdline_add "root=UUID=$rootuuid" fi } write_default_cmdline() { local delimiter="cmdline-aboot-end" cmdline_clear if $in_aboot; then # generate the default kernel parameters for the platform cat /etc/cmdline | sed "/^\(${bootconfigvars// /\|}\|crashkernel\|loglevel\|ignore_loglevel\)\(\$\|=\)/d;/^\$/d" | cmdline_append elif grep -q "$delimiter" /proc/cmdline; then # we are on a recent sonic image using delimiter. extracting the part of the # cmdline coming from aboot is trivial. cat /proc/cmdline | sed -E "s/^(.*) $delimiter .*$/\1/" | tr ' ' '\n' | cmdline_append else # we are either on SONiC or EOS and the commandline doesn't have a delimiter # for the Aboot part. Take an educated guess at a right delimiter. # Subject to breakage if EOS or SONiC cmdline change. cat /proc/cmdline | sed -E 's/^(.*) rw .*$/\1/' | tr ' ' '\n' | cmdline_append fi cmdline_add "$delimiter" } write_common_configs() { write_default_cmdline write_platform_specific_cmdline write_image_specific_cmdline write_machine_config } write_secureboot_configs() { write_common_configs cmdline_add "loop=$(mount_relpath "$installer_image_path")" cmdline_add loopfstype=squashfs cmdline_add "loopoffset=$(zip_file_offset "$installer_image_path" fs.squashfs)" cmdline_add docker_inram=on cmdline_add secure_boot_enable=y cmdline_add aboot.secureboot=enabled # setting panic= has the side effect of disabling the initrd shell on error cmdline_add panic=0 } write_regular_configs() { write_common_configs cmdline_add "loop=$image_name/fs.squashfs" cmdline_add loopfstype=squashfs # use extra parameters from kernel-params hook if the file exists if [ -f "$target_path/$kernel_params" ]; then cat "$target_path/$kernel_params" | cmdline_append fi # FIXME: sonic sometimes adds extra kernel parameters from user space # this is unsafe but some will be kept as part of the regular boot if [ -f "$image_path/kernel-cmdline" ]; then cat "$image_path/kernel-cmdline" | tr ' ' '\n' | grep -E "$cmdline_allowlist" | cmdline_append fi # FIXME: legacy configuration files used by fast-reboot and eos2sonic # these should be deprecated over time. cmdline_echo > "$image_path/kernel-cmdline" cmdline_echo | sed 's/ cmdline-aboot-end.*$//' > "$target_path/kernel-params-base" } run_kexec() { local cmdline="${CMDLINE:-$(cmdline_echo) $ENV_EXTRA_CMDLINE}" local kernel="${KERNEL:-$(find_first_kernel_under "$image_path/boot")}" local initrd="${INITRD:-$(find_first_initrd_under "$image_path/boot")}" if $verbose; then # show systemd showdown sequence when verbose is set cmdline="$(echo "$cmdline" | sed 's/systemd.show_status=auto/systemd.show_status=true/')" fi if $debug; then # enable initrd debug as well as kernel verbose output cmdline="$(echo "$cmdline" | sed 's/ quiet//')" cmdline="$cmdline debug loglevel=7 log_buf_len=8M printk.devmsg=on" fi sync kexec --load --initrd="$initrd" --append="$cmdline" "$kernel" ( [ -z "$testonly" ] && [ -z "$loadonly" ] ) || exit 0 info "Kexecing..." kexec --exec } secureboot_install() { if [ -e "$image_path" ]; then warn "Image folder $image_path already exist (likely regular install)" fi mkdir -p "$image_path" info "Installing image as $installer_image_path" extract_image_secureboot } regular_install() { if [ -e "$image_path" ]; then warn "Image folder $image_path already exist (likely secureboot install)" fi mkdir -p $image_path info "Generating boot-config, machine.conf and cmdline" write_regular_configs "$image_path" info "Installing image under $image_path" extract_image run_hooks post-install } secureboot_boot() { # boot material is extracted and generated in RAM. # SONiC starts as a live OS. info "Preparing image for secureboot" prepare_image_secureboot update_next_boot run_kexec } regular_boot() { # boot uses the image installed on the flash run_hooks pre-kexec write_regular_configs "$image_path" update_next_boot run_kexec } # In Aboot no option will be provided therefore these are the default values to use in_aboot=true do_clean=true do_install=true do_kexec=true # prevent the flash from being cleaned if the do-not-clean file exists if [ -f "$target_path/$do_not_clean" ]; then do_clean=false fi # Parse the cmdline options (used from EOS or from SONiC) if [ ! -z "$install" ]; then # install from SONiC or EOS in_aboot=false do_clean=false do_kexec=false elif [ ! -z "$kexec" ]; then # kexec from SONiC or EOS in_aboot=false do_install=false do_clean=false fi # Make sure boot-config exists to avoid noise touch "$boot_config" # Verbosity can be defined by the caller, default to false otherwise verbose=${verbose:-false} debug=${debug:-false} if [ -f "$target_path/verbose-boot" ] || [ "$(get_boot_config VERBOSE)" = "1" ] || ! $in_aboot; then verbose=true fi if [ -f "$target_path/debug-boot" ] || [ "$(get_boot_config DEBUG)" = "1" ]; then verbose=true debug=true fi # behavioral configuration for secureboot # can be overidden by passing secureboot=true via env if [ -z "$secureboot" ]; then if is_secureboot_enabled; then secureboot=true else secureboot=false fi fi # preserve original installer during regular install when set preserve_installer=false if [ -f "$target_path/preserve-installer" ] || [ "$(get_boot_config PRESERVE_INSTALLER)" = "1" ]; then preserve_installer=true fi # enable shell debug mode to get the most verbosity if $verbose; then set -x fi # install the image if newer if $do_install; then if ! unzip -ql "$swipath" 2>&1 > /dev/null; then err "The swipath= environment variable does not point to a valid SWI" exit 1 fi swi_installed=0 is_swi_installed "$swipath" || swi_installed=$? if [ "$swi_installed" -ne $SWI_ALREADY_INSTALLED ] || [ -n "$force" ]; then if [ "$swi_installed" -eq $SWI_VERSION_MISMATCH ] || [ -n "$force" ]; then warn "Removing existing installation folder $image_path" rm -rf $image_path fi if [ "$swi_installed" -ne $SWI_OTHER_VARIANT_INSTALLED ] && $do_clean; then info "Cleaning flash content $target_path" clean_flash fi if $secureboot; then secureboot_install else regular_install fi else info "Using previously installed image" fi fi # chainloading using kexec if $do_kexec; then if $secureboot; then secureboot_boot else regular_boot fi fi