Grub and snappy updates


This week, the snappy powered Ubuntu Core landed some interesting changes with regards to how it handles grub based systems.

History

The original implementation was based on traditional Ubuntu systems, where a bunch of files that any debian package could setup influenced the resulting grub.cfg that resulted after running update-grub. This produced a grub.cfg that was really hard to manage or debug, and what is most important, out of our control. This also differed greatly from our u-boot story where any gadget could override the boot setup so it bootstraps as needed.

We don’t want to own the logic for this, but only provide the guidelines for the necessary primitives for proper rollback at that level to work.

These steps also make our bootloader story look and feel more similar between each other where soon we may be able to just not care about it as the logic will be driven as if it were a black box.

Rolling it out

Even though this worked targeted the development release, also known as the rolling one, we trie to make sure all systems would transition to this model transparently. Given the model though, it isn’t a one step solution as we need to be able to update to systems which do not run update-grub and rollback to systems that do. We also need to update from a sytem that has this new snappy logic to one that doesn’t. This was solved with a very grub.cfg slick grub.cfg delivered through the gadget packages (still named oem in current implementations), in contrast, similar to the u-boot and uEnv.txt mechanics.

On a running system, these would be the steps taken:

  • Device is running a version that runs update-grub.
  • oem package update is delivered through autopilot containing the new grub.cfg.
  • The os is updated bringing in some new snappy logic.
  • The next os update would be driven by the new snappy logic which would sync grub.cfg from the oem package into the bootloader area. This new snappy would not run update-grub. The system would boot from the legacy kernel paths as if it were a delta update no new kernel would be delivered.
  • Updates would rinse and repeat, when there’s a new kernel provided in an update, the bootloader a and b locations would be used to store that kernel, grub.cfg already has logic to boot from those locations so the change is transparent.
  • On the next update, kernel asset file syncing would take place and populate the other label (a or b).

This is the development release so we shouldn’t worry to much about breaking, but why do so if it can be avoided ;-)

Outcome

The resulting code base is much simpler, there are less headaches and we don’t need to maintain or understand huge grub script snippets. Just for kicks, this is the grub.cfg we use:

set default=0
set timeout=3

insmod part_gpt
insmod ext2

if [ -s $prefix/grubenv ]; then
  load_env
fi

if [ -z "$snappy_mode" ]; then
    set snappy_mode=regular
    save_env snappy_mode
fi
if [ -z "$snappy_ab" ]; then
    set snappy_ab=a
    save_env snappy_ab
fi

if [ "$snappy_mode" = "try" ]; then
    if [ "$snappy_trial_boot" = "1" ]; then
        # Previous boot failed to unset snappy_trial_boot, so toggle
        # rootfs.
        if [ "$snappy_ab" = "a" ]; then
            set snappy_ab=b
        else
            set snappy_ab=a
        fi
        save_env snappy_ab
    else
        # Trial mode so set the snappy_trial_boot (which snappy is
        # expected to unset).
        #
        # Note: don't use the standard recordfail variable since that forces
        # the menu to be displayed and sets an infinite timeout if set.
        set snappy_trial_boot=1
        save_env snappy_trial_boot
    fi
fi

set label="system-$snappy_ab"
set cmdline="root=LABEL=$label ro init=/lib/systemd/systemd console=ttyS0 console=tty1 panic=-1"

menuentry "$label" {
    if [ -e "$prefix/$snappy_ab/vmlinuz" ]; then
        linux $prefix/$snappy_ab/vmlinuz $cmdline
        initrd $prefix/$snappy_ab/initrd.img
    else
        # old-style kernel-in-os-partition
        search --no-floppy --set --label "$label"
        linux /vmlinuz $cmdline
        initrd /initrd.img
    fi
}

and this was the grub.cfg that was auto generated:

#
# DO NOT EDIT THIS FILE
#
# It is automatically generated by grub-mkconfig using templates
# from /etc/grub.d and settings from /etc/default/grub
#

### BEGIN /etc/grub.d/00_header ###
if [ -s $prefix/grubenv ]; then
  set have_grubenv=true
  load_env
fi
if [ "${next_entry}" ] ; then
   set default="${next_entry}"
   set next_entry=
   save_env next_entry
   set boot_once=true
else
   set default="0"
fi

if [ x"${feature_menuentry_id}" = xy ]; then
  menuentry_id_option="--id"
else
  menuentry_id_option=""
fi

export menuentry_id_option

if [ "${prev_saved_entry}" ]; then
  set saved_entry="${prev_saved_entry}"
  save_env saved_entry
  set prev_saved_entry=
  save_env prev_saved_entry
  set boot_once=true
fi

function savedefault {
  if [ -z "${boot_once}" ]; then
    saved_entry="${chosen}"
    save_env saved_entry
  fi
}
function recordfail {
  set recordfail=1
  if [ -n "${have_grubenv}" ]; then if [ -z "${boot_once}" ]; then save_env recordfail; fi; fi
}
function load_video {
  if [ x$feature_all_video_module = xy ]; then
    insmod all_video
  else
    insmod efi_gop
    insmod efi_uga
    insmod ieee1275_fb
    insmod vbe
    insmod vga
    insmod video_bochs
    insmod video_cirrus
  fi
}

terminal_input console
terminal_output console
if [ "${recordfail}" = 1 ] ; then
  set timeout=0
else
  if [ x$feature_timeout_style = xy ] ; then
    set timeout_style=hidden
    set timeout=0
  # Fallback hidden-timeout code in case the timeout_style feature is
  # unavailable.
  elif sleep --interruptible 0 ; then
    set timeout=0
  fi
fi
### END /etc/grub.d/00_header ###

### BEGIN /etc/grub.d/05_debian_theme ###
set menu_color_normal=white/black
set menu_color_highlight=black/light-gray
### END /etc/grub.d/05_debian_theme ###

### BEGIN /etc/grub.d/09_snappy ###
menuentry 'Ubuntu Core Snappy system-b rootfs'  $menuentry_id_option 'gnulinux-simple-LABEL=system-b' {
	load_video
	insmod gzio
	if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
	insmod part_gpt
	insmod ext2
	set root='hd0,gpt4'
	if [ x$feature_platform_search_hint = xy ]; then
	  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4  4ff468ee-953f-45df-a751-e6232a1c8ef7
	else
	  search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
	fi
	linux /boot/vmlinuz-3.19.0-22-generic root=LABEL=system-b ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
	initrd /boot/initrd.img-3.19.0-22-generic
}
menuentry 'Ubuntu Core Snappy system-a rootfs'  $menuentry_id_option 'gnulinux-simple-LABEL=system-a' {
	load_video
	insmod gzio
	if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
	insmod part_gpt
	insmod ext2
	set root='hd0,gpt3'
	if [ x$feature_platform_search_hint = xy ]; then
	  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt3 --hint-efi=hd0,gpt3 --hint-baremetal=ahci0,gpt3  47ea9cad-ec4f-4290-85e8-eee7dac19f1c
	else
	  search --no-floppy --fs-uuid --set=root 47ea9cad-ec4f-4290-85e8-eee7dac19f1c
	fi
	linux /boot/vmlinuz-3.19.0-22-generic root=LABEL=system-a ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
	initrd /boot/initrd.img-3.19.0-22-generic
}
    # set defaults
    if [ -z "$snappy_mode" ]; then
        set snappy_mode=regular
        save_env snappy_mode
    fi
    if [ -z "$snappy_ab" ]; then
        set snappy_ab=a
        save_env snappy_ab
    fi

    if [ "$snappy_mode" = "try" ]; then
        if [ "$snappy_trial_boot" = "1" ]; then
            # Previous boot failed to unset snappy_trial_boot, so toggle
            # rootfs.
            if [ "$snappy_ab" = "a" ]; then
                set default="Ubuntu Core Snappy system-b rootfs"
                set snappy_ab=b
            else
                set snappy_ab=a
                set default="Ubuntu Core Snappy system-a rootfs"
            fi
            save_env snappy_ab
        else
            # Trial mode so set the snappy_trial_boot (which snappy is
            # expected to unset).
            #
            # Note: don't use the standard recordfail variable since that forces
            # the menu to be displayed and sets an infinite timeout if set.
            set snappy_trial_boot=1
            save_env snappy_trial_boot

            if [ "$snappy_ab" = "a" ]; then
                set default="Ubuntu Core Snappy system-a rootfs"
            else
                set default="Ubuntu Core Snappy system-b rootfs"
            fi
        fi
    else
        if [ "$snappy_ab" = "a" ]; then
            set default="Ubuntu Core Snappy system-a rootfs"
        else
            set default="Ubuntu Core Snappy system-b rootfs"
        fi
    fi
### END /etc/grub.d/09_snappy ###

### BEGIN /etc/grub.d/10_linux ###
function gfxmode {
	set gfxpayload="${1}"
	if [ "${1}" = "keep" ]; then
		set vt_handoff=vt.handoff=7
	else
		set vt_handoff=
	fi
}
if [ "${recordfail}" != 1 ]; then
  if [ -e ${prefix}/gfxblacklist.txt ]; then
    if hwmatch ${prefix}/gfxblacklist.txt 3; then
      if [ ${match} = 0 ]; then
        set linux_gfx_mode=keep
      else
        set linux_gfx_mode=text
      fi
    else
      set linux_gfx_mode=text
    fi
  else
    set linux_gfx_mode=keep
  fi
else
  set linux_gfx_mode=text
fi
export linux_gfx_mode
menuentry 'Ubuntu' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-simple-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
	recordfail
	load_video
	gfxmode $linux_gfx_mode
	insmod gzio
	if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
	insmod part_gpt
	insmod ext2
	set root='hd0,gpt4'
	if [ x$feature_platform_search_hint = xy ]; then
	  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4  4ff468ee-953f-45df-a751-e6232a1c8ef7
	else
	  search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
	fi
	linux	/boot/vmlinuz-3.19.0-22-generic root=/dev/sda4 ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
	initrd	/boot/initrd.img-3.19.0-22-generic
}
submenu 'Advanced options for Ubuntu' $menuentry_id_option 'gnulinux-advanced-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
	menuentry 'Ubuntu, with Linux 3.19.0-22-generic' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-3.19.0-22-generic-advanced-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
		recordfail
		load_video
		gfxmode $linux_gfx_mode
		insmod gzio
		if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
		insmod part_gpt
		insmod ext2
		set root='hd0,gpt4'
		if [ x$feature_platform_search_hint = xy ]; then
		  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4  4ff468ee-953f-45df-a751-e6232a1c8ef7
		else
		  search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
		fi
		echo	'Loading Linux 3.19.0-22-generic ...'
		linux	/boot/vmlinuz-3.19.0-22-generic root=/dev/sda4 ro init=/lib/systemd/systemd console=tty1 console=ttyS0 panic=-1
		echo	'Loading initial ramdisk ...'
		initrd	/boot/initrd.img-3.19.0-22-generic
	}
	menuentry 'Ubuntu, with Linux 3.19.0-22-generic (recovery mode)' --class ubuntu --class gnu-linux --class gnu --class os $menuentry_id_option 'gnulinux-3.19.0-22-generic-recovery-4ff468ee-953f-45df-a751-e6232a1c8ef7' {
		recordfail
		load_video
		insmod gzio
		if [ x$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi
		insmod part_gpt
		insmod ext2
		set root='hd0,gpt4'
		if [ x$feature_platform_search_hint = xy ]; then
		  search --no-floppy --fs-uuid --set=root --hint-bios=hd0,gpt4 --hint-efi=hd0,gpt4 --hint-baremetal=ahci0,gpt4  4ff468ee-953f-45df-a751-e6232a1c8ef7
		else
		  search --no-floppy --fs-uuid --set=root 4ff468ee-953f-45df-a751-e6232a1c8ef7
		fi
		echo	'Loading Linux 3.19.0-22-generic ...'
		linux	/boot/vmlinuz-3.19.0-22-generic root=/dev/sda4 ro single nomodeset init=/lib/systemd/systemd
		echo	'Loading initial ramdisk ...'
		initrd	/boot/initrd.img-3.19.0-22-generic
	}
}

### END /etc/grub.d/10_linux ###

### BEGIN /etc/grub.d/20_linux_xen ###

### END /etc/grub.d/20_linux_xen ###

### BEGIN /etc/grub.d/30_os-prober ###
### END /etc/grub.d/30_os-prober ###

### BEGIN /etc/grub.d/30_uefi-firmware ###
### END /etc/grub.d/30_uefi-firmware ###

### BEGIN /etc/grub.d/40_custom ###
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.
### END /etc/grub.d/40_custom ###

### BEGIN /etc/grub.d/41_custom ###
if [ -f  ${config_directory}/custom.cfg ]; then
  source ${config_directory}/custom.cfg
elif [ -z "${config_directory}" -a -f  $prefix/custom.cfg ]; then
  source $prefix/custom.cfg;
fi
### END /etc/grub.d/41_custom ###
comments powered by Disqus