#!/bin/sh case $1 in prereqs) exit 0 ;; esac set -e # set -x total_mem=$(free | awk '/^Mem:/{print $2}') tmpfs_size=$(( $total_mem / 20 * 17 )) free_mem_thres=$(( $total_mem / 20 * 18 )) tmp_mnt='/mnt/ramdisk-convfs' root_mnt='/mnt/root-convfs' root_dev='' flash_dev='' block_flash='' aboot_flag='' backup_file='' prev_os='' sonic_fast_reboot=false in_kdump=false # Wait until get the fullpath of flash device, e.g., /dev/sda wait_get_flash_dev() { local try_rounds=30 while [ $try_rounds -gt 0 ]; do for dev in $(ls /sys/block); do local is_mmc=$(echo "$dev" | grep 'mmcblk.*boot.*' | cat) if [ -n "$is_mmc" ]; then continue fi local devid="$(realpath "/sys/block/$dev/device")/" local is_device=$(echo "$devid" | grep '^/sys/devices/' | cat) local is_flash=$(echo "$devid" | grep "$block_flash" | cat) if [ -n "$is_device" -a -n "$is_flash" ]; then flash_dev="/dev/$dev" return 0 fi done sleep 1 try_rounds=$(( $try_rounds - 1 )) done return 1 } # Wait for root_dev to be ready wait_for_root_dev() { local try_rounds=30 while [ $try_rounds -gt 0 ]; do if blkid | sed 's/"//g' | grep -q "$root_dev"; then return 0 fi if [ -e "$root_dev" ]; then return 0 fi sleep 1 try_rounds=$(( $try_rounds - 1 )) done return 1 } # Alway run cleanup before exit cleanup() { if grep -q "$root_mnt" /proc/mounts; then umount "$root_mnt" fi if grep -q "$tmp_mnt" /proc/mounts; then umount "$tmp_mnt" fi [ -e "$root_mnt" ] && rmdir "$root_mnt" [ -e "$tmp_mnt" ] && rmdir "$tmp_mnt" } trap cleanup EXIT notification() { cat << EOF A failure happend in modifying the root file system which stopped the upgrade. Manual interventions are needed to fix the issue. Note that: 1) files in the old root file system may have been lost and the old partition table may have been corrupted; 2) The files in the old root file system were copied to $tmp_mnt; 3) The old partition table was dumped to the file $tmp_mnt/$backup_file by sfdisk; 4) Quitting the current shell will lose all files mentioned above permanently. EOF } run_cmd() { if ! eval "$1"; then echo "$2" notification sh exit 1 fi } fixup_flash_permissions() { # properly set flash permissions for others # this allows the sonic admin user to have read access on the flash local flash_mnt="$1" chmod o+rx "$flash_mnt" # remove all the filesystem acls from the flash setfacl -Rb "$flash_mnt" } # Update ROOT device referring to the path under block_flash # This is for the occasional name swap between /dev/sda and /dev/sdb update_root() { # Check that root=/dev/*, ignoring any cases like root=UUID=* [ "${ROOT#/dev}" = "${ROOT}" ] && return 0 # Replace the beginning chars of ROOT by the ones of flash_dev with same index {% raw %} prefix_length="${#flash_dev}" {% endraw %} part_id="${ROOT:$prefix_length}" ROOT="$flash_dev$part_id" echo "ROOT=$ROOT" > /conf/param.conf } # Extract kernel parameters set -- $(cat /proc/cmdline) for x in "$@"; do case "$x" in block_flash=*) block_flash="${x#block_flash=}" ;; Aboot=*) aboot_flag="${x#Aboot=}" ;; loop=*) x1="${x#loop=}" image_dir="${x1%/*}" ;; docker_inram=*) docker_inram="${x#docker_inram=}" ;; prev_os=*) prev_os="${x#prev_os=}" ;; SONIC_BOOT_TYPE=warm*|SONIC_BOOT_TYPE=fast*) sonic_fast_reboot=true ;; systemd.unit=kdump-tools.service) in_kdump=true ;; esac done # Check aboot [ -z "$aboot_flag" ] && exit 0 # Check kdump [ "$in_kdump" = true ] && exit 0 # Skip this script for warm-reboot/fast-reboot from sonic [ "$sonic_fast_reboot" = true ] && [ "$prev_os" != eos ] && exit 0 # Get flash dev name if [ -z "$block_flash" ]; then echo "Error: flash device info is not provided" exit 1 fi if ! wait_get_flash_dev; then echo "Error: flash device is not found" exit 1 fi # If root=/dev/*, update ROOT to the device under block_flash update_root root_dev="$ROOT" if [ -z "$root_dev" ]; then echo "Error: root device name is not provided" exit 1 fi if ! wait_for_root_dev; then echo "Error: timeout in waiting for $root_dev" exit 1 fi # exit when the root is ext4 if ! blkid | grep "$root_dev.*vfat" -q; then mkdir -p "$root_mnt" mount -t ext4 "$root_dev" "$root_mnt" fixup_flash_permissions "$root_mnt" umount "$root_mnt" rmdir "$root_mnt" exit 0 fi # Check memory size for tmpfs free_mem=$(free | awk '/^Mem:/{print $4}') if [ "$free_mem" -lt "$free_mem_thres" ]; then echo "Error: memory is not enough" exit 1 fi # Backup partition table mkdir -p "$root_mnt" mount "$root_dev" "$root_mnt" backup_file=backup.$(date +%Y-%m-%d.%H-%M-%S) sfdisk -d "$flash_dev" > "$root_mnt/$backup_file" # Check total size of files in root total_file_size=$(du -s "$root_mnt" | awk '{print $1}') if [ "$total_file_size" -gt "$tmpfs_size" ]; then echo "Error: total file size is too large" exit 1 fi # Create tmpfs, and copy files to tmpfs mkdir -p "$tmp_mnt" mount -t tmpfs -o size="${tmpfs_size}k" tmpfs "$tmp_mnt" cp -a "$root_mnt/." "$tmp_mnt/" umount "$root_mnt" #### Lines below will modify the root file system, so any failure will be trapped to shell for manual interventions. if [ $(echo -n "$root_dev" | tail -c 1) == "1" ]; then # Create a new partition table (content in flash_dev will be deleted) err_msg="Error: Failed to zero out first MB" cmd="dd if=/dev/zero of=$flash_dev bs=512 count=2048" run_cmd "$cmd" "$err_msg" err_msg="Error: repartitioning $flash_dev failed" cmd="echo '2048' | sfdisk $flash_dev || (sleep 3; blockdev --rereadpt $flash_dev && fdisk -l $flash_dev | grep -q ${root_dev}.*Linux)" run_cmd "$cmd" "$err_msg" fi sleep 2 err_msg="Error: timeout in waiting for $root_dev after repartition" cmd="wait_for_root_dev" run_cmd "$cmd" "$err_msg" err_msg="Error: formatting to ext4 failed" cmd="/usr/local/sbin/mke2fs -t ext4 -F -O '^huge_file,^metadata_csum' $root_dev" run_cmd "$cmd" "$err_msg" err_msg="Error: mounting $root_dev to $root_mnt failed" cmd="mount -t ext4 $root_dev $root_mnt" run_cmd "$cmd" "$err_msg" if [ x"$docker_inram" != x"on" ]; then err_msg="Error: extract docker directory" cmd="[ -f $tmp_mnt/$image_dir/{{ FILESYSTEM_DOCKERFS }} ] && rm -rf $root_mnt/$image_dir/{{ DOCKERFS_DIR }} && mkdir -p $root_mnt/$image_dir/{{ DOCKERFS_DIR }} && tar xzf $tmp_mnt/$image_dir/{{ FILESYSTEM_DOCKERFS }} -C $root_mnt/$image_dir/{{ DOCKERFS_DIR }} && rm -f $tmp_mnt/$image_dir/{{ FILESYSTEM_DOCKERFS }}" run_cmd "$cmd" "$err_msg" fi err_msg="Error: copying files form $tmp_mnt to $root_mnt failed" cmd="cp -a $tmp_mnt/. $root_mnt/" run_cmd "$cmd" "$err_msg" fixup_flash_permissions "$root_mnt"