#!/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 a kernel-params file. image_name="image-%%IMAGE_VERSION%%" dockerfs="{{ FILESYSTEM_DOCKERFS }}" 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: $@"; } # 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" cmdline_base="$target_path/kernel-params-base" cmdline_image="$image_path/kernel-cmdline" boot_config="$target_path/boot-config" swi_tmpfs="/tmp/tmp-swi" bootconfigvars="KERNEL INITRD CONSOLESPEED PASSWORD NETDEV NETAUTO NETIP NETMASK NETGW NETDOMAIN NETDNS NETHW memtest" flash_re=" /mnt/flash| /host" # for backward compatibility with the sonic_upgrade= behavior install="${install:-${sonic_upgrade:-}}" parse_environment_config() { for n in ${bootconfigvars}; do eval v="\$$n" if [ "$v" ]; then echo "$n=$v" fi done } 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 != "$kernel_params" ] && [ $f != "$cmdline_base" ] && [ $f != "aquota.user" ] && [ $f != "old_config" ] && [ $f != "minigraph.xml" ] && [ $f != "snmp.yml" ] && [ $f != "acl.json" ] then rm -rf "$target_path/$f" fi done } 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 update_boot_config SWI "$default" fi } move_swi_to_tmpfs() { local oldswi="$1" local newswi="$swi_tmpfs/$(basename $oldswi)" mkdir -p "$swi_tmpfs" if ! $in_aboot && ! mount | grep -q ' /tmp type tmpfs'; then # mount a real tmpfs on /tmp/tmp-swi if /tmp is not one already. mount -t tmpfs tmp-swi "$swi_tmpfs" fi mv "$oldswi" "$newswi" echo "$newswi" } cleanup_swi_tmpfs() { rm -f "$swipath" if mount | grep -q "$swi_tmpfs"; then umount "$swi_tmpfs" || : fi } extract_image() { mkdir -p "$image_path" 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" -a x"$docker_inram" != x"on" ]; then mkdir -p "$image_path/{{ DOCKERFS_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/{{ DOCKERFS_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 info "Remove installer" cleanup_swi_tmpfs ## use new reduced-size boot swi local swi_boot_path="flash:$image_name/{{ ABOOT_BOOT_IMAGE }}" update_boot_config SWI "$swi_boot_path" update_boot_config SWI_DEFAULT "$swi_boot_path" ## sync disk operations sync } write_machine_config() { ## Detect SKU and create a hardware description file aboot_version=$(grep ^Aboot "$cmdline_base" | sed 's/^.*norcal.-//' | tail -n 1) 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" } in_array() { local value="$1" shift for other in $@; do if [ "$value" = "$other" ]; then return 0 fi done return 1 } read_system_eeprom() { if [ -x /bin/readprefdl ]; 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 } platform_specific() { local platform="$(sed -nr 's/.*platform=([^ ]+).*/\1/p' "$cmdline_base")" local sid="$(sed -nr 's/.*sid=([^ ]+).*/\1/p' "$cmdline_base" | sed 's/Ssd$//')" # set varlog size to 100MB local varlog_size=100 # detect the size of the flash partition from name in Aboot/EOS/SONiC local flash_size=$(($(df | grep -E "$flash_re" | tr -s ' ' | cut -f2 -d' ') / 1000)) if [ "$platform" = "raven" ]; then # Assuming sid=Cloverdale aboot_machine=arista_7050_qx32 flash_size=2000 docker_inram=on echo "modprobe.blacklist=radeon,sp5100_tco acpi=off docker_inram=on" >>/tmp/append fi if [ "$platform" = "crow" ]; then # Assuming sid=Clearlake aboot_machine=arista_7050_qx32s flash_size=3700 echo "modprobe.blacklist=radeon,sp5100_tco" >>/tmp/append 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 echo "hugepages=128" >> /tmp/append fi if [ "$sid" = "Mineral" ]; then aboot_machine=arista_7170_32c flash_size=28000 echo "hugepages=128" >> /tmp/append fi if [ "$sid" = "MineralD" ]; then aboot_machine=arista_7170_32cd flash_size=28000 echo "hugepages=128" >> /tmp/append 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 in_array "$platform" "rook" "magpie" "woodpecker"; then echo "tsc=reliable pcie_ports=native" >>/tmp/append echo "rhash_entries=1 usb-storage.delay_use=0" >>/tmp/append echo "reassign_prefmem" >> /tmp/append fi if in_array "$platform" "rook"; then echo "iommu=on intel_iommu=on" >>/tmp/append read_system_eeprom fi if in_array "$platform" "crow" "magpie"; then echo "amd_iommu=off modprobe.blacklist=snd_hda_intel,hdaudio" >> /tmp/append read_system_eeprom fi if in_array "$platform" "woodpecker"; then echo "modprobe.blacklist=snd_hda_intel,hdaudio" >> /tmp/append read_system_eeprom fi if [ $flash_size -ge 28000 ]; then varlog_size=4096 elif [ $flash_size -ge 3700 ]; then varlog_size=400 fi echo "varlog_size=$varlog_size" >>/tmp/append # disable deterministic interface naming echo "net.ifnames=0" >>/tmp/append } 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" | grep -Eo '[^"]+' fi } write_boot_configs() { if $in_aboot; then # generate the default kernel parameters for the platform echo "$append" > $cmdline_base cat /etc/cmdline | sed "/^\(${bootconfigvars// /\|}\|crashkernel\|loglevel\|ignore_loglevel\)\(\$\|=\)/d;/^\$/d" >> $cmdline_base parse_environment_config >> $cmdline_base elif [ ! -f "$cmdline_base" ]; then # some systems were started with other versions of this script and therefore # do not have the $cmdline_base file. we assume that we are on Sonic or EOS. cat /proc/cmdline | sed -E 's/^(.*) rw .*$/\1/' | tr ' ' '\n' > $cmdline_base fi cp $cmdline_base /tmp/append platform_specific echo "rw loop=$image_name/fs.squashfs loopfstype=squashfs apparmor=1 security=apparmor quiet" >> /tmp/append # 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 echo "hwaddr_ma1=$(cat /sys/class/net/ma1/address)" >> /tmp/append elif [ -f /sys/class/net/eth0/address ]; then echo "hwaddr_ma1=$(cat /sys/class/net/eth0/address)" >> /tmp/append else err "Management port not found." fi # use extra parameters from kernel-params hook if the file exists if [ -f "$target_path/$kernel_params" ]; then cat "$target_path/$kernel_params" >> /tmp/append fi # setting root partition if not overridden by kernel-params if ! grep -q "root=" /tmp/append; then rootdev="$(mount | grep -E "$flash_re" | cut -f1 -d' ')" rootfstype="$(mount | grep -E "$flash_re" | cut -f5 -d' ')" rootuuid="$(get_uuid_for $rootdev)" if [ -z "$rootuuid" ] || [ "$rootfstype" = "vfat" ] ; then echo "root=$rootdev" >> /tmp/append else echo "root=UUID=$rootuuid" >> /tmp/append fi fi mkdir -p "$image_path" cat /tmp/append > $cmdline_image [ -s ${target_path}/machine.conf ] || write_machine_config sync } run_kexec() { local cmdline="$(cat $cmdline_image | tr '\n' ' ') $ENV_EXTRA_CMDLINE" local kernel="${KERNEL:-$(find $image_path/boot -name 'vmlinuz-*' -type f | head -n 1)}" local initrd="${INITRD:-$(find $image_path/boot -name 'initrd.img-*' -type f | head -n 1)}" if $verbose; then # show systemd showdown sequence when verbose is set cmdline="$cmdline systemd.show_status=true" else # 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="$cmdline systemd.show_status=auto" fi kexec --load --initrd="$initrd" --append="$cmdline" "$kernel" [ -z "$testonly" ] || exit 0 info "Kexecing..." kexec --exec } 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 } # 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 elif [ $# -ne 0 ]; then echo "usage: $0 (see code)" exit 1 fi # Verbosity can be defined by the caller, default to false otherwise verbose=${verbose:-false} if [ -f "$target_path/verbose-boot" ] || [ "$(get_boot_config VERBOSE)" = "1" ] || ! $in_aboot; then verbose=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 # we expect the swi to install to be a non empty file if [ ! -s "$swipath" ]; then err "The swipath= environment variable does not point to a valid SWI" exit 1 fi # check the hash file in the image, and determine to install or just skip GIT_REVISION=$(unzip -p "$swipath" .imagehash) LOCAL_IMAGEHASH=$(cat $image_path/.imagehash 2>/dev/null || true) if [ "$GIT_REVISION" != "$LOCAL_IMAGEHASH" ] || [ ! -z "$force" ]; then if $do_clean; then info "Cleaning flash content $target_path" clean_flash fi info "Generating boot-config, machine.conf and cmdline" write_boot_configs info "Installing image under $image_path" extract_image run_hooks post-install else info "Using previously installed image" fi fi # chainloading using kexec if $do_kexec; then run_hooks pre-kexec update_next_boot run_kexec fi