Merge lp:~sergiusens/snappy/noUpdateGrub into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Sergio Schvezov on 2015-06-08
Status: Merged
Approved by: John Lenton on 2015-07-03
Approved revision: 499
Merged at revision: 556
Proposed branch: lp:~sergiusens/snappy/noUpdateGrub
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 1314 lines (+368/-643)
13 files modified
debian/ubuntu-snappy.install (+0/-1)
etc/grub.d/09_snappy (+0/-441)
helpers/helpers.go (+11/-0)
helpers/helpers_test.go (+38/-0)
partition/bootloader.go (+196/-8)
partition/bootloader_grub.go (+13/-31)
partition/bootloader_grub_test.go (+35/-7)
partition/bootloader_uboot.go (+6/-119)
partition/partition.go (+19/-21)
partition/partition_test.go (+2/-12)
snappy/oem.go (+46/-1)
snappy/systemimage.go (+1/-1)
snappy/systemimage_test.go (+1/-1)
To merge this branch: bzr merge lp:~sergiusens/snappy/noUpdateGrub
Reviewer Review Type Date Requested Status
John Lenton 2015-06-08 Approve on 2015-06-11
Michael Vogt 2015-06-19 Pending
Review via email: mp+261381@code.launchpad.net

Commit Message

Don't run update-grub, instead use grub.cfg from the oem package.

Description of the Change

this can land alone

To post a comment you must log in.
Michael Vogt (mvo) wrote :

Yay, I like how this looks. Please merge lp:~mvo/snappy/noUpdateGrub for more that can be removed if we go to a static grub.cfg.

lp:~sergiusens/snappy/noUpdateGrub updated on 2015-06-08
488. By Sergio Schvezov on 2015-06-08

Merging mvo's neat stuff

John Lenton (chipaca) wrote :

LGTM. Probably needs conflict resolution though.

review: Approve
Michael Vogt (mvo) wrote :

I don't think we can merge this just yet, we need a new grub.cfg first if I'm not missing something.

lp:~sergiusens/snappy/noUpdateGrub updated on 2015-07-01
489. By Sergio Schvezov on 2015-06-18

Merging mvo's nice additions

490. By Sergio Schvezov on 2015-06-19

merge trunk

491. By Sergio Schvezov on 2015-07-01

Merging trunk

492. By Sergio Schvezov on 2015-07-01

fix bootloader dir and support file syncing from the oem package

493. By Sergio Schvezov on 2015-07-01

tests for copy file if different

494. By Sergio Schvezov on 2015-07-01

Test boot asset copy

495. By Sergio Schvezov on 2015-07-01

Remove spurious print

John Lenton (chipaca) :
lp:~sergiusens/snappy/noUpdateGrub updated on 2015-07-03
496. By Sergio Schvezov on 2015-07-01

merge trunk

497. By Sergio Schvezov on 2015-07-01

Using helpers.CopyFile

498. By Sergio Schvezov on 2015-07-02

Revert system-image-cli change

499. By Sergio Schvezov on 2015-07-03

Trunk merge

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/ubuntu-snappy.install'
2--- debian/ubuntu-snappy.install 2015-04-17 17:07:45 +0000
3+++ debian/ubuntu-snappy.install 2015-07-03 12:26:50 +0000
4@@ -1,4 +1,3 @@
5 debian/*.service /lib/systemd/system/
6 debian/*.target /lib/systemd/system/
7-etc
8 debian/*.timer /lib/systemd/system/
9
10=== removed directory 'etc'
11=== removed directory 'etc/grub.d'
12=== removed file 'etc/grub.d/09_snappy'
13--- etc/grub.d/09_snappy 2015-06-08 19:15:52 +0000
14+++ etc/grub.d/09_snappy 1970-01-01 00:00:00 +0000
15@@ -1,441 +0,0 @@
16-#!/bin/sh
17-#---------------------------------------------------------------------
18-# Summary: Grub bootloader logic for Ubuntu Snappy systems.
19-# Description: This is a heavily modified "10_linux" grub snippet that
20-# deals with Snappy dual-rootfs systems.
21-#
22-# XXX: Note that this script is called from within a chroot environment
23-# on snappy systems!
24-#
25-#---------------------------------------------------------------------
26-
27-set -e
28-
29-prefix="/usr"
30-exec_prefix="/usr"
31-datarootdir="/usr/share"
32-
33-# Utility functions
34-. "${datarootdir}/grub/grub-mkconfig_lib"
35-
36-# Globals
37-machine=`uname -m`
38-
39-SNAPPY_OS="Ubuntu Core Snappy"
40-SNAPPY_TYPE=simple
41-
42-#---------------------------------------------------------------------
43-
44-# Display message and exit
45-die()
46-{
47- msg="$*"
48- echo "ERROR: $msg" >&2
49- exit 1
50-}
51-
52-# Create a grub menu entry by writing a menuentry to stdout.
53-linux_entry_ext()
54-{
55- local name="$1"
56- local os="$2"
57- local version="$3"
58- local type="$4"
59- local args="$5"
60- local device="$6"
61- local kernel="$7"
62- local initrd="$8"
63-
64- local boot_device_id=
65- local prepare_root_cache=
66- local prepare_boot_cache=
67-
68- if [ -z "$boot_device_id" ]; then
69- boot_device_id="$(grub_get_device_id "${device}")"
70- fi
71-
72- echo "menuentry '$name' ${CLASS} \$menuentry_id_option 'gnulinux-simple-$boot_device_id' {" | sed "s/^/$submenu_indentation/"
73-
74- if [ x$type != xrecovery ] ; then
75- save_default_entry | grub_add_tab
76- fi
77-
78- # Use ELILO's generic "efifb" when it's known to be available.
79- # FIXME: We need an interface to select vesafb in case efifb can't be used.
80- if [ "x$GRUB_GFXPAYLOAD_LINUX" = x ]; then
81- echo " load_video" | sed "s/^/$submenu_indentation/"
82- else
83- if [ "x$GRUB_GFXPAYLOAD_LINUX" != xtext ]; then
84- echo " load_video" | sed "s/^/$submenu_indentation/"
85- fi
86- fi
87- if ([ "$ubuntu_recovery" = 0 ] || [ x$type != xrecovery ]) && \
88- ([ "x$GRUB_GFXPAYLOAD_LINUX" != x ] || [ "$gfxpayload_dynamic" = 1 ]); then
89- echo " gfxmode \$linux_gfx_mode" | sed "s/^/$submenu_indentation/"
90- fi
91-
92- echo " insmod gzio" | sed "s/^/$submenu_indentation/"
93- echo " if [ x\$grub_platform = xxen ]; then insmod xzio; insmod lzopio; fi" | sed "s/^/$submenu_indentation/"
94-
95- # device may be a label (LABEL=name), so convert back to full path
96- label_name=$(echo "$device"|sed 's/^LABEL=//g')
97- if [ "$device" = "$label_name" ]
98- then
99- device_path="$device"
100- else
101- # found a label
102- device_path=$(get_partition_from_label "$label_name")
103- fi
104-
105- if [ x$dirname = x/ ]; then
106- if [ -z "${prepare_root_cache}" ]; then
107-
108- prepare_root_cache="$(prepare_grub_to_access_device ${device_path} | grub_add_tab)"
109- fi
110- printf '%s\n' "${prepare_root_cache}" | sed "s/^/$submenu_indentation/"
111- else
112- if [ -z "${prepare_boot_cache}" ]; then
113- prepare_boot_cache="$(prepare_grub_to_access_device ${device_path} | grub_add_tab)"
114- fi
115- printf '%s\n' "${prepare_boot_cache}" | sed "s/^/$submenu_indentation/"
116- fi
117-
118- if [ x"$quiet_boot" = x0 ] || [ x"$type" != xsimple ]; then
119- message="$(gettext_printf "Loading Linux %s ..." ${version})"
120- sed "s/^/$submenu_indentation/" << EOF
121- echo '$(echo "$message" | grub_quote)'
122-EOF
123- fi
124-
125- sed "s/^/$submenu_indentation/" << EOF
126- linux ${kernel} root=${device} ro ${args}
127-EOF
128-
129- if test -n "${initrd}"; then
130- # TRANSLATORS: ramdisk isn't identifier. Should be translated.
131- if [ x"$quiet_boot" = x0 ] || [ x"$type" != xsimple ]; then
132- message="$(gettext_printf "Loading initial ramdisk ...")"
133- sed "s/^/$submenu_indentation/" << EOF
134- echo '$(echo "$message" | grub_quote)'
135-EOF
136- fi
137- sed "s/^/$submenu_indentation/" << EOF
138- initrd ${initrd}
139-EOF
140- fi
141-
142- sed "s/^/$submenu_indentation/" << EOF
143-}
144-EOF
145-}
146-
147-# Returns a list of the currently available kernels.
148-# $1: If set, look for kernel below "$1/boot/".
149-get_kernels()
150-{
151- local prefix_dir="$1"
152- local list
153-
154- case "x$machine" in
155- xi?86 | xx86_64)
156- list=`for i in $prefix_dir/boot/vmlinuz-* \
157- $prefix_dir/vmlinuz-* \
158- $prefix_dir/boot/kernel-* ; do
159- if grub_file_is_not_garbage "$i" ; then echo -n "$i " ; fi
160- done` ;;
161- *)
162- list=`for i in $prefix_dir/boot/vmlinuz-* \
163- $prefix_dir/boot/vmlinux-* \
164- $prefix_dir/vmlinuz-* \
165- $prefix_dir/vmlinux-* \
166- $prefix_dir/boot/kernel-* ; do
167- if grub_file_is_not_garbage "$i" ; then echo -n "$i " ; fi
168- done` ;;
169- esac
170- echo "$list"
171-}
172-
173-# Composes and returns a kernel cmd line
174-get_cmdline() {
175- channel_ini="/etc/system-image/channel.ini"
176- grep -q 'device: azure_amd64' "$channel_ini" && azure_cmdline="rootdelay=300"
177-
178- echo "${GRUB_CMDLINE_LINUX} ${GRUB_CMDLINE_LINUX_DEFAULT} $azure_cmdline"
179-}
180-
181-# Returns the path to the initrd for the kernel specified by $1.
182-# $1: kernel version.
183-# $2: directory to look in.
184-get_initrd()
185-{
186- local version="$1"
187- local dir="$2"
188-
189- local alt_version=`echo $version | sed -e "s,\.old$,,g"`
190- local initrd=
191- local i=
192-
193- for i in "initrd.img-${version}" "initrd-${version}.img" "initrd-${version}.gz" \
194- "initrd-${version}" "initramfs-${version}.img" \
195- "initrd.img-${alt_version}" "initrd-${alt_version}.img" \
196- "initrd-${alt_version}" "initramfs-${alt_version}.img" \
197- "initramfs-genkernel-${version}" \
198- "initramfs-genkernel-${alt_version}" \
199- "initramfs-genkernel-${GENKERNEL_ARCH}-${version}" \
200- "initramfs-genkernel-${GENKERNEL_ARCH}-${alt_version}"; do
201- if test -e "${dir}/${i}" ; then
202- initrd="${dir}/${i}"
203- break
204- fi
205- done
206- echo "$initrd"
207-}
208-
209-# Determine full path to disk partition given a filesystem label.
210-get_partition_from_label()
211-{
212- local label="$1"
213- local part=
214- local path=
215-
216- [ -n "$label" ] || grub_warn "need FS label"
217-
218- part=$(find /dev -name "$label"|tail -1)
219- [ -z "$part" ] && return
220- path=$(readlink -f "$part")
221- [ -n "$path" ] && echo "$path"
222-}
223-
224-# Return the partition label for the given partition device.
225-# $1: full path to partition device.
226-get_label_from_device()
227-{
228- local root="$1"
229-
230- local label=
231- local std_label=
232- local label_rootfs=
233-
234- for std_label in system-a system-b; do
235- label_rootfs=$(findfs "PARTLABEL=$std_label" 2>/dev/null || :)
236- if [ "$label_rootfs" = "$root" ]; then
237- label="$std_label"
238- break
239- fi
240- done
241-
242- echo "$label"
243-}
244-
245-# Return the full path to the device corresponding to the given
246-# partition label.
247-#
248-# $1: partition label.
249-get_device_from_label()
250-{
251- local label="$1"
252- local device=
253-
254- device=$(findfs "PARTLABEL=$label" 2>/dev/null || :)
255- echo "$device"
256-}
257-
258-# Given a rootfs label, return the rootfs label corresponding to the
259-# "other" rootfs partition.
260-get_other_rootfs_label()
261-{
262- local label="$1"
263-
264- if [ "$label" = "system-a" ]; then
265- echo "system-b"
266- else
267- echo "system-a"
268- fi
269-}
270-
271-# Given a mountpoint, return the corresponding device path
272-# $1: mountpoint.
273-get_device_from_mountpoint()
274-{
275- local mountpoint="$1"
276- local device=
277-
278- # XXX: Parse mount output rather than looking in /proc/mounts to
279- # avoid seeing the mounts outside the chroot.
280- device=$(/bin/mount | grep "^/dev/.* on ${mountpoint}[[:space:]]" 2>/dev/null |\
281- awk '{print $1}' || :)
282-
283- echo "$device"
284-}
285-
286-# Convert a partition label name to a menuentry name
287-make_name()
288-{
289- local label="$1"
290-
291- echo "$SNAPPY_OS $label rootfs" | grub_quote
292-}
293-
294-# Arrange for a grub menuentry to be created for the given partition.
295-#
296-# $1: full path to rootfs partition device
297-# $2: partition label associated with $1
298-# $3: mountpoint of $1.
299-handle_menu_entry()
300-{
301- local rootfs_device="$1"
302- local label="$2"
303- local mountpoint="$3"
304-
305- local name=
306- local device=
307- local mount_prefix=
308- local list=
309- local linux=
310- local basename=
311- local dirname=
312- local rel_dirname=
313- local version=
314- local initrd=
315- local cmdline=
316-
317- # boot by label
318- device="LABEL=$label"
319-
320- name=$(make_name "$label")
321-
322- # avoid double-leading slashes and the subsequent need to call
323- # 'readlink -f'.
324- if [ "$mountpoint" = "/" ]; then
325- mount_prefix=""
326- else
327- mount_prefix="$mountpoint"
328- fi
329- list=$(get_kernels "$mount_prefix")
330-
331- linux=$(version_find_latest $list)
332- basename=$(basename "$linux")
333- dirname=$(dirname "$linux")
334- rel_dirname=$(make_system_path_relative_to_its_root "$dirname")
335- version=$(echo "$basename" | sed -e "s,^[^0-9]*-,,g")
336-
337- initrd=$(get_initrd "$version" "$dirname")
338-
339- cmdline=$(get_cmdline)
340-
341- # convert the path to the mounted "other" rootfs back to a
342- # a standard one by removing the mountpoint prefix.
343- if [ "$mountpoint" != "/" ]; then
344- linux=$(echo "$linux" | sed "s!^${mountpoint}!!g")
345- initrd=$(echo "$initrd" | sed "s!^${mountpoint}!!g")
346- fi
347-
348- # Create menu entries for the 2 snappy rootfs's.
349- linux_entry_ext "$name" "$SNAPPY_OS" "$version" \
350- "$SNAPPY_TYPE" "$cmdline" "$device" \
351- "$linux" "$initrd"
352-}
353-
354-#---------------------------------------------------------------------
355-# main
356-
357-case "$machine" in
358- i?86) GENKERNEL_ARCH="x86" ;;
359- mips|mips64) GENKERNEL_ARCH="mips" ;;
360- mipsel|mips64el) GENKERNEL_ARCH="mipsel" ;;
361- arm*) GENKERNEL_ARCH="arm" ;;
362- *) GENKERNEL_ARCH="$machine" ;;
363-esac
364-
365-# Determine which partition label is being used for the current root
366-# directory. This is slightly convoluted but required since this code
367-# runs within a chroot environment (where lsblk does not work).
368-#
369-# XXX: Note that since this code is run from within a chroot (where the
370-# "other" rootfs is mounted), it might appear that the logic is
371-# inverted. However, the code below simply
372-this_mountpoint="/"
373-this_root=$(get_device_from_mountpoint "$this_mountpoint")
374-[ -z "$this_root" ] && {
375- die "cannot determine rootfs for $this_mountpoint"
376-}
377-
378-this_label=$(get_label_from_device "$this_root")
379-[ -z "$this_label" ] && {
380- die "cannot determine partition label for rootfs $this_root"
381-}
382-
383-handle_menu_entry "$this_root" "$this_label" "$this_mountpoint"
384-
385-other_mountpoint="/writable/cache/system"
386-
387-# When this script is run on a real snappy system, even if there is only
388-# a single rootfs provisioned, the other rootfs partition is expected to
389-# be formatted and mounted.
390-#
391-# However this script is also run at provisioning time where
392-# $other_mountpoint will not be a mountpoint. Therefore in the provisioning
393-# scenario, only a single menuentry will be generated if only a single
394-# rootfs is provisioned.
395-if $(mountpoint -q "$other_mountpoint"); then
396- other_label=$(get_other_rootfs_label "$this_label")
397-
398- other_root=$(get_device_from_label "$other_label")
399- [ -z "$other_root" ] && {
400- die "cannot determine rootfs"
401- }
402-
403- handle_menu_entry "$other_root" "$other_label" "$other_mountpoint"
404-fi
405-
406-# Toggle rootfs if previous boot failed.
407-#
408-# Since grub sets snappy_trial_boot, if it is _already_ set when grub starts
409-# and we're in try mode, the previous boot must have failed to unset it,
410-# so toggle the rootfs.
411-sed "s/^/$submenu_indentation/" << EOF
412- # set defaults
413- if [ -z "\$snappy_mode" ]; then
414- set snappy_mode=regular
415- save_env snappy_mode
416- fi
417- if [ -z "\$snappy_ab" ]; then
418- set snappy_ab=a
419- save_env snappy_ab
420- fi
421-
422- if [ "\$snappy_mode" = "try" ]; then
423- if [ "\$snappy_trial_boot" = "1" ]; then
424- # Previous boot failed to unset snappy_trial_boot, so toggle
425- # rootfs.
426- if [ "\$snappy_ab" = "a" ]; then
427- set default="$(make_name system-b)"
428- set snappy_ab=b
429- else
430- set snappy_ab=a
431- set default="$(make_name system-a)"
432- fi
433- save_env snappy_ab
434- else
435- # Trial mode so set the snappy_trial_boot (which snappy is
436- # expected to unset).
437- #
438- # Note: don't use the standard recordfail variable since that forces
439- # the menu to be displayed and sets an infinite timeout if set.
440- set snappy_trial_boot=1
441- save_env snappy_trial_boot
442-
443- if [ "\$snappy_ab" = "a" ]; then
444- set default="$(make_name system-a)"
445- else
446- set default="$(make_name system-b)"
447- fi
448- fi
449- else
450- if [ "\$snappy_ab" = "a" ]; then
451- set default="$(make_name system-a)"
452- else
453- set default="$(make_name system-b)"
454- fi
455- fi
456-EOF
457
458=== modified file 'helpers/helpers.go'
459--- helpers/helpers.go 2015-06-09 19:11:54 +0000
460+++ helpers/helpers.go 2015-07-03 12:26:50 +0000
461@@ -498,3 +498,14 @@
462
463 return err
464 }
465+
466+// CopyIfDifferent copies src to dst only if dst is different that src
467+func CopyIfDifferent(src, dst string) error {
468+ if !FilesAreEqual(src, dst) {
469+ if err := CopyFile(src, dst, CopyFlagSync|CopyFlagOverwrite); err != nil {
470+ return err
471+ }
472+ }
473+
474+ return nil
475+}
476
477=== modified file 'helpers/helpers_test.go'
478--- helpers/helpers_test.go 2015-06-09 19:11:54 +0000
479+++ helpers/helpers_test.go 2015-07-03 12:26:50 +0000
480@@ -445,3 +445,41 @@
481 c.Check(err, NotNil)
482 c.Check(err, ErrorMatches, ".*permission denied.*")
483 }
484+
485+func (ts *HTestSuite) TestCopyIfDifferent(c *C) {
486+ srcDir := c.MkDir()
487+ dstDir := c.MkDir()
488+
489+ // new file
490+ src := filepath.Join(srcDir, "bop")
491+ dst := filepath.Join(dstDir, "bob")
492+ err := ioutil.WriteFile(src, []byte(nil), 0644)
493+ c.Assert(err, IsNil)
494+
495+ err = CopyIfDifferent(src, dst)
496+ c.Assert(err, IsNil)
497+ c.Check(FilesAreEqual(dst, src), Equals, true)
498+
499+ // updated file
500+ src = filepath.Join(srcDir, "bip")
501+ dst = filepath.Join(dstDir, "bib")
502+ err = ioutil.WriteFile(src, []byte("123"), 0644)
503+ c.Assert(err, IsNil)
504+ err = ioutil.WriteFile(dst, []byte(nil), 0644)
505+ c.Assert(err, IsNil)
506+
507+ err = CopyIfDifferent(src, dst)
508+ c.Assert(err, IsNil)
509+ c.Check(FilesAreEqual(dst, src), Equals, true)
510+}
511+
512+func (ts *HTestSuite) TestCopyIfDifferentErrorsOnNoSrc(c *C) {
513+ srcDir := c.MkDir()
514+ dstDir := c.MkDir()
515+
516+ src := filepath.Join(srcDir, "mop")
517+ dst := filepath.Join(dstDir, "mop")
518+
519+ err := CopyIfDifferent(src, dst)
520+ c.Assert(err, NotNil)
521+}
522
523=== modified file 'partition/bootloader.go'
524--- partition/bootloader.go 2015-06-11 09:38:18 +0000
525+++ partition/bootloader.go 2015-07-03 12:26:50 +0000
526@@ -19,6 +19,14 @@
527
528 package partition
529
530+import (
531+ "fmt"
532+ "os"
533+ "path/filepath"
534+
535+ "launchpad.net/snappy/helpers"
536+)
537+
538 const (
539 // bootloader variable used to denote which rootfs to boot from
540 bootloaderRootfsVar = "snappy_ab"
541@@ -50,7 +58,7 @@
542 // Hook function called before system-image starts downloading
543 // and applying archives that allows files to be copied between
544 // partitions.
545- SyncBootFiles() error
546+ SyncBootFiles(bootAssets map[string]string) error
547
548 // Install any hardware-specific files that system-image
549 // downloaded.
550@@ -72,9 +80,6 @@
551 // currently-booted rootfs as having booted successfully.
552 MarkCurrentBootSuccessful(currentRootfs string) error
553
554- // Return the additional required chroot bind mounts for this bootloader
555- AdditionalBindMounts() []string
556-
557 // BootDir returns the (writable) bootloader-specific boot
558 // directory.
559 BootDir() string
560@@ -98,13 +103,196 @@
561 return nil, ErrBootloader
562 }
563
564+type bootloaderType struct {
565+ partition *Partition
566+
567+ // each rootfs partition has a corresponding u-boot directory named
568+ // from the last character of the partition name ('a' or 'b').
569+ currentRootfs string
570+ otherRootfs string
571+
572+ // full path to rootfs-specific assets on boot partition
573+ currentBootPath string
574+ otherBootPath string
575+
576+ // FIXME: this should /boot if possible
577+ // the dir that the bootloader lives in (e.g. /boot/uboot)
578+ bootloaderDir string
579+}
580+
581+func newBootLoader(partition *Partition, bootloaderDir string) *bootloaderType {
582+ // FIXME: is this the right thing to do? i.e. what should we do
583+ // on a single partition system?
584+ if partition.otherRootPartition() == nil {
585+ return nil
586+ }
587+
588+ // full label of the system {system-a,system-b}
589+ currentLabel := partition.rootPartition().name
590+ otherLabel := partition.otherRootPartition().name
591+
592+ // single letter description of the rootfs {a,b}
593+ currentRootfs := string(currentLabel[len(currentLabel)-1])
594+ otherRootfs := string(otherLabel[len(otherLabel)-1])
595+
596+ return &bootloaderType{
597+ partition: partition,
598+
599+ currentRootfs: currentRootfs,
600+ otherRootfs: otherRootfs,
601+
602+ // the paths that the kernel/initramfs are loaded, e.g.
603+ // /boot/uboot/a
604+ currentBootPath: filepath.Join(bootloaderDir, currentRootfs),
605+ otherBootPath: filepath.Join(bootloaderDir, otherRootfs),
606+
607+ // the base bootloader dir, e.g. /boot/uboot or /boot/grub
608+ bootloaderDir: bootloaderDir,
609+ }
610+}
611+
612+// FIXME:
613+// - populate kernel if missing
614+func (b *bootloaderType) SyncBootFiles(bootAssets map[string]string) (err error) {
615+ for src, dst := range bootAssets {
616+ if err := helpers.CopyIfDifferent(src, filepath.Join(b.bootloaderDir, dst)); err != nil {
617+ return err
618+ }
619+ }
620+
621+ srcDir := b.currentBootPath
622+ destDir := b.otherBootPath
623+
624+ // ensure they exist
625+ for _, dir := range []string{srcDir, destDir} {
626+ if err := os.MkdirAll(dir, 0755); err != nil {
627+ return err
628+ }
629+
630+ }
631+ return helpers.RSyncWithDelete(srcDir, destDir)
632+}
633+
634+// FIXME:
635+// - if this fails it will never be re-tried because the "other" patition
636+// is updated to revision-N in /etc/system-image/channel.ini
637+// so the system only downloads from revision-N onwards even though the
638+// complete update was not applied (i.e. kernel missing)
639+func (b *bootloaderType) HandleAssets() (err error) {
640+ // check if we have anything, if there is no hardware yaml, there is nothing
641+ // to process.
642+ hardware, err := readHardwareSpec()
643+ if err == ErrNoHardwareYaml {
644+ return nil
645+ } else if err != nil {
646+ return err
647+ }
648+ // ensure to remove the file if there are no errors
649+ defer func() {
650+ if err == nil {
651+ os.Remove(hardwareSpecFile)
652+ }
653+ }()
654+
655+ /*
656+ // validate bootloader
657+ if hardware.Bootloader != b.Name() {
658+ return fmt.Errorf(
659+ "bootloader is of type %s but hardware spec requires %s",
660+ b.Name(),
661+ hardware.Bootloader)
662+ }
663+ */
664+
665+ // validate partition layout
666+ if b.partition.dualRootPartitions() && hardware.PartitionLayout != bootloaderSystemAB {
667+ return fmt.Errorf("hardware spec requires dual root partitions")
668+ }
669+
670+ // ensure we have the destdir
671+ destDir := b.otherBootPath
672+ if err := os.MkdirAll(destDir, dirMode); err != nil {
673+ return err
674+ }
675+
676+ // install kernel+initrd
677+ for _, file := range []string{hardware.Kernel, hardware.Initrd} {
678+
679+ if file == "" {
680+ continue
681+ }
682+
683+ // expand path
684+ path := filepath.Join(cacheDir, file)
685+
686+ if !helpers.FileExists(path) {
687+ return fmt.Errorf("can not find file %s", path)
688+ }
689+
690+ // ensure we remove the dir later
691+ defer func() {
692+ if err == nil {
693+ os.RemoveAll(filepath.Dir(path))
694+ }
695+ }()
696+
697+ if err := runCommand("/bin/cp", path, destDir); err != nil {
698+ return err
699+ }
700+ }
701+
702+ // TODO: look at the OEM package for dtb changes too once that is
703+ // fully speced
704+
705+ // install .dtb files
706+ dtbSrcDir := filepath.Join(cacheDir, hardware.DtbDir)
707+ // ensure there is a DtbDir specified
708+ if hardware.DtbDir != "" && helpers.FileExists(dtbSrcDir) {
709+ // ensure we cleanup the source dir
710+ defer func() {
711+ if err == nil {
712+ os.RemoveAll(dtbSrcDir)
713+ }
714+ }()
715+
716+ dtbDestDir := filepath.Join(destDir, "dtbs")
717+ if err := os.MkdirAll(dtbDestDir, dirMode); err != nil {
718+ return err
719+ }
720+
721+ files, err := filepath.Glob(filepath.Join(dtbSrcDir, "*"))
722+ if err != nil {
723+ return err
724+ }
725+
726+ for _, file := range files {
727+ if err := runCommand("/bin/cp", file, dtbDestDir); err != nil {
728+ return err
729+ }
730+ }
731+ }
732+
733+ if helpers.FileExists(flashAssetsDir) {
734+ // FIXME: we don't currently do anything with the
735+ // MLO + uImage files since they are not specified in
736+ // the hardware spec. So for now, just remove them.
737+
738+ if err := os.RemoveAll(flashAssetsDir); err != nil {
739+ return err
740+ }
741+ }
742+
743+ return err
744+}
745+
746 // BootloaderDir returns the full path to the (mounted and writable)
747 // bootloader-specific boot directory.
748 func BootloaderDir() string {
749- b, err := bootloader(nil)
750- if err != nil {
751- return ""
752+ if helpers.FileExists(bootloaderUbootDir) {
753+ return bootloaderUbootDir
754+ } else if helpers.FileExists(bootloaderGrubDir) {
755+ return bootloaderGrubDir
756 }
757
758- return b.BootDir()
759+ return ""
760 }
761
762=== modified file 'partition/bootloader_grub.go'
763--- partition/bootloader_grub.go 2015-06-11 09:16:26 +0000
764+++ partition/bootloader_grub.go 2015-07-03 12:26:50 +0000
765@@ -33,7 +33,6 @@
766 bootloaderGrubEnvFileReal = "/boot/grub/grubenv"
767
768 bootloaderGrubEnvCmdReal = "/usr/bin/grub-editenv"
769- bootloaderGrubUpdateCmdReal = "/usr/sbin/update-grub"
770 bootloaderGrubTrialBootVarReal = "snappy_trial_boot"
771 )
772
773@@ -44,22 +43,28 @@
774 bootloaderGrubTrialBootVar = bootloaderGrubTrialBootVarReal
775 bootloaderGrubEnvFile = bootloaderGrubEnvFileReal
776
777- bootloaderGrubEnvCmd = bootloaderGrubEnvCmdReal
778- bootloaderGrubUpdateCmd = bootloaderGrubUpdateCmdReal
779+ bootloaderGrubEnvCmd = bootloaderGrubEnvCmdReal
780 )
781
782 type grub struct {
783+ bootloaderType
784 }
785
786 const bootloaderNameGrub bootloaderName = "grub"
787
788 // newGrub create a new Grub bootloader object
789 func newGrub(partition *Partition) bootLoader {
790- if !helpers.FileExists(bootloaderGrubConfigFile) || !helpers.FileExists(bootloaderGrubUpdateCmd) {
791- return nil
792- }
793-
794- return &grub{}
795+ if !helpers.FileExists(bootloaderGrubConfigFile) {
796+ return nil
797+ }
798+
799+ b := newBootLoader(partition, bootloaderGrubDir)
800+ if b == nil {
801+ return nil
802+ }
803+ g := grub{bootloaderType: *b}
804+
805+ return &g
806 }
807
808 func (g *grub) Name() bootloaderName {
809@@ -73,11 +78,6 @@
810 // Update the grub configuration.
811 func (g *grub) ToggleRootFS(otherRootfs string) (err error) {
812
813- // create the grub config
814- if err := runInChroot(mountTarget, bootloaderGrubUpdateCmd); err != nil {
815- return err
816- }
817-
818 if err := g.setBootVar(bootloaderBootmodeVar, bootloaderBootmodeTry); err != nil {
819 return err
820 }
821@@ -135,24 +135,6 @@
822 return g.setBootVar(bootloaderBootmodeVar, bootloaderBootmodeSuccess)
823 }
824
825-func (g *grub) SyncBootFiles() (err error) {
826- // NOP
827- return nil
828-}
829-
830-func (g *grub) HandleAssets() (err error) {
831-
832- // NOP - since grub is used on generic hardware, it doesn't
833- // need to make use of hardware-specific assets
834- return nil
835-}
836-
837-func (g *grub) AdditionalBindMounts() []string {
838- // grub needs this in addition to "system-boot" as its the
839- // well known location for its configuration
840- return []string{"/boot/grub"}
841-}
842-
843 func (g *grub) BootDir() string {
844 return bootloaderGrubDir
845 }
846
847=== modified file 'partition/bootloader_grub_test.go'
848--- partition/bootloader_grub_test.go 2015-06-11 09:16:26 +0000
849+++ partition/bootloader_grub_test.go 2015-07-03 12:26:50 +0000
850@@ -23,6 +23,9 @@
851 "fmt"
852 "io/ioutil"
853 "os"
854+ "path/filepath"
855+
856+ "launchpad.net/snappy/helpers"
857
858 . "gopkg.in/check.v1"
859 )
860@@ -40,7 +43,6 @@
861 // these files just needs to exist
862 mockGrubFile(c, bootloaderGrubConfigFile, 0644)
863 mockGrubFile(c, bootloaderGrubEnvFile, 0644)
864- mockGrubFile(c, bootloaderGrubUpdateCmd, 0755)
865
866 // do not run commands for real
867 runCommand = mockRunCommandWithCapture
868@@ -86,18 +88,15 @@
869 mp := singleCommand{"/bin/mountpoint", mountTarget}
870 c.Assert(allCommands[0], DeepEquals, mp)
871
872- expectedGrubUpdate := singleCommand{"/usr/sbin/chroot", mountTarget, bootloaderGrubUpdateCmd}
873- c.Assert(allCommands[1], DeepEquals, expectedGrubUpdate)
874-
875 expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_mode=try"}
876- c.Assert(allCommands[2], DeepEquals, expectedGrubSet)
877+ c.Assert(allCommands[1], DeepEquals, expectedGrubSet)
878
879 // the https://developer.ubuntu.com/en/snappy/porting guide says
880 // we always use the short names
881 expectedGrubSet = singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_ab=b"}
882- c.Assert(allCommands[3], DeepEquals, expectedGrubSet)
883+ c.Assert(allCommands[2], DeepEquals, expectedGrubSet)
884
885- c.Assert(len(allCommands), Equals, 4)
886+ c.Assert(len(allCommands), Equals, 3)
887 }
888
889 func mockGrubEditenvList(cmd ...string) (string, error) {
890@@ -152,3 +151,32 @@
891 c.Assert(allCommands[3], DeepEquals, expectedGrubSet3)
892
893 }
894+
895+func (s *PartitionTestSuite) TestSyncBootFilesWithAssets(c *C) {
896+ err := os.MkdirAll(bootloaderGrubDir, 0755)
897+ c.Assert(err, IsNil)
898+
899+ runCommand = mockRunCommand
900+ b := grub{
901+ bootloaderType{
902+ currentBootPath: c.MkDir(),
903+ otherBootPath: c.MkDir(),
904+ bootloaderDir: c.MkDir(),
905+ },
906+ }
907+
908+ bootfile := filepath.Join(c.MkDir(), "bootfile")
909+ err = ioutil.WriteFile(bootfile, []byte(bootfile), 0644)
910+ c.Assert(err, IsNil)
911+
912+ bootassets := map[string]string{
913+ bootfile: filepath.Base(bootfile),
914+ }
915+
916+ err = b.SyncBootFiles(bootassets)
917+ c.Assert(err, IsNil)
918+
919+ dst := filepath.Join(b.bootloaderDir, bootassets[bootfile])
920+ c.Check(helpers.FileExists(dst), Equals, true)
921+ c.Check(helpers.FilesAreEqual(bootfile, dst), Equals, true)
922+}
923
924=== modified file 'partition/bootloader_uboot.go'
925--- partition/bootloader_uboot.go 2015-06-11 09:16:26 +0000
926+++ partition/bootloader_uboot.go 2015-07-03 12:26:50 +0000
927@@ -58,9 +58,7 @@
928 const bootloaderNameUboot bootloaderName = "u-boot"
929
930 type uboot struct {
931- // full path to rootfs-specific assets on boot partition
932- currentBootPath string
933- otherBootPath string
934+ bootloaderType
935 }
936
937 // Stores a Name and a Value to be added as a name=value pair in a file.
938@@ -76,13 +74,11 @@
939 return nil
940 }
941
942- u := uboot{}
943-
944- currentRootfs := partition.rootPartition().shortName
945- u.currentBootPath = filepath.Join(bootloaderUbootDir, currentRootfs)
946-
947- otherRootfs := partition.otherRootPartition().shortName
948- u.otherBootPath = filepath.Join(bootloaderUbootDir, otherRootfs)
949+ b := newBootLoader(partition, bootloaderUbootDir)
950+ if b == nil {
951+ return nil
952+ }
953+ u := uboot{bootloaderType: *b}
954
955 return &u
956 }
957@@ -207,110 +203,6 @@
958 return os.RemoveAll(bootloaderUbootStampFile)
959 }
960
961-func (u *uboot) SyncBootFiles() (err error) {
962- srcDir := u.currentBootPath
963- destDir := u.otherBootPath
964-
965- return helpers.RSyncWithDelete(srcDir, destDir)
966-}
967-
968-func (u *uboot) HandleAssets() (err error) {
969- // check if we have anything, if there is no hardware yaml, there is nothing
970- // to process.
971- hardware, err := readHardwareSpec()
972- if err == ErrNoHardwareYaml {
973- return nil
974- } else if err != nil {
975- return err
976- }
977- // ensure to remove the file once we are done (and all was good)
978- defer func() {
979- if err == nil {
980- os.Remove(hardwareSpecFile)
981- }
982- }()
983-
984- // validate bootloader
985- if hardware.Bootloader != u.Name() {
986- return fmt.Errorf(
987- "bootloader is of type %s but hardware spec requires %s",
988- u.Name(),
989- hardware.Bootloader)
990- }
991-
992- // validate partition layout, we ONLY support bootloaderSystemAB
993- // currently
994- if hardware.PartitionLayout != bootloaderSystemAB {
995- return fmt.Errorf("hardware spec requires dual root partitions")
996- }
997-
998- // ensure we have the destdir
999- destDir := u.otherBootPath
1000- if err := os.MkdirAll(destDir, dirMode); err != nil {
1001- return err
1002- }
1003-
1004- // install kernel+initrd
1005- for _, file := range []string{hardware.Kernel, hardware.Initrd} {
1006-
1007- if file == "" {
1008- continue
1009- }
1010-
1011- // expand path
1012- path := filepath.Join(cacheDir, file)
1013-
1014- if !helpers.FileExists(path) {
1015- return fmt.Errorf("can not find file %s", path)
1016- }
1017-
1018- // ensure we remove the dir later
1019- defer os.RemoveAll(filepath.Dir(path))
1020-
1021- if err := runCommand("/bin/cp", path, destDir); err != nil {
1022- return err
1023- }
1024- }
1025-
1026- // TODO: look at the OEM package for dtb changes too once that is
1027- // fully speced
1028-
1029- // install .dtb files
1030- dtbSrcDir := filepath.Join(cacheDir, hardware.DtbDir)
1031- if helpers.FileExists(dtbSrcDir) {
1032- // ensure we cleanup the source dir
1033- defer os.RemoveAll(dtbSrcDir)
1034-
1035- dtbDestDir := filepath.Join(destDir, "dtbs")
1036- if err := os.MkdirAll(dtbDestDir, dirMode); err != nil {
1037- return err
1038- }
1039-
1040- files, err := filepath.Glob(filepath.Join(dtbSrcDir, "*"))
1041- if err != nil {
1042- return err
1043- }
1044-
1045- for _, file := range files {
1046- if err := runCommand("/bin/cp", file, dtbDestDir); err != nil {
1047- return err
1048- }
1049- }
1050- }
1051-
1052- if helpers.FileExists(flashAssetsDir) {
1053- // FIXME: we don't currently do anything with the
1054- // MLO + uImage files since they are not specified in
1055- // the hardware spec. So for now, just remove them.
1056-
1057- if err := os.RemoveAll(flashAssetsDir); err != nil {
1058- return err
1059- }
1060- }
1061-
1062- return err
1063-}
1064-
1065 // Write lines to file atomically. File does not have to preexist.
1066 // FIXME: put into utils package
1067 func atomicFileUpdateImpl(file string, lines []string) (err error) {
1068@@ -399,11 +291,6 @@
1069 return nil
1070 }
1071
1072-func (u *uboot) AdditionalBindMounts() []string {
1073- // nothing additional to system-boot required on uboot
1074- return []string{}
1075-}
1076-
1077 func (u *uboot) BootDir() string {
1078 return bootloaderUbootDir
1079 }
1080
1081=== modified file 'partition/partition.go'
1082--- partition/partition.go 2015-06-29 23:15:00 +0000
1083+++ partition/partition.go 2015-07-03 12:26:50 +0000
1084@@ -75,7 +75,7 @@
1085 MarkBootSuccessful() error
1086 // FIXME: could we make SyncBootloaderFiles part of ToogleBootloader
1087 // to expose even less implementation details?
1088- SyncBootloaderFiles() error
1089+ SyncBootloaderFiles(bootAssets map[string]string) error
1090 IsNextBootOther() bool
1091
1092 // run the function f with the otherRoot mounted
1093@@ -303,12 +303,13 @@
1094
1095 // SyncBootloaderFiles syncs the bootloader files
1096 // FIXME: can we unexport this?
1097-func (p *Partition) SyncBootloaderFiles() (err error) {
1098+func (p *Partition) SyncBootloaderFiles(bootAssets map[string]string) (err error) {
1099 bootloader, err := bootloader(p)
1100 if err != nil {
1101 return err
1102 }
1103- return bootloader.SyncBootFiles()
1104+
1105+ return bootloader.SyncBootFiles(bootAssets)
1106 }
1107
1108 // ToggleNextBoot toggles the roofs that should be used on the next boot
1109@@ -541,14 +542,6 @@
1110 requiredChrootMounts = append(requiredChrootMounts, boot.mountpoint)
1111 }
1112
1113- // add additional bootloader mounts, this is required for grub
1114- bootloader, err := bootloader(p)
1115- if err == nil && bootloader != nil {
1116- for _, mount := range bootloader.AdditionalBindMounts() {
1117- requiredChrootMounts = append(requiredChrootMounts, mount)
1118- }
1119- }
1120-
1121 for _, fs := range requiredChrootMounts {
1122 target := filepath.Join(mountTarget, fs)
1123
1124@@ -595,17 +588,22 @@
1125 return err
1126 }
1127
1128- // XXX: first toggle roofs and then handle assets? that seems
1129- // wrong given that handleAssets may fails and we will
1130- // knowingly boot into a broken system
1131- err = p.RunWithOther(RW, func(otherRoot string) (err error) {
1132- otherRootfs := p.otherRootPartition().shortName
1133- return bootloader.ToggleRootFS(otherRootfs)
1134- })
1135-
1136+ // ensure we have updated kernels etc
1137+ if err := bootloader.HandleAssets(); err != nil {
1138+ return err
1139+ }
1140+
1141+ otherRootfs := p.otherRootPartition().shortName
1142+ return bootloader.ToggleRootFS(otherRootfs)
1143+}
1144+
1145+// BootloaderDir returns the full path to the (mounted and writable)
1146+// bootloader-specific boot directory.
1147+func (p *Partition) BootloaderDir() string {
1148+ bootloader, err := bootloader(p)
1149 if err != nil {
1150- return err
1151+ return ""
1152 }
1153
1154- return bootloader.HandleAssets()
1155+ return bootloader.BootDir()
1156 }
1157
1158=== modified file 'partition/partition_test.go'
1159--- partition/partition_test.go 2015-06-11 09:16:26 +0000
1160+++ partition/partition_test.go 2015-07-03 12:26:50 +0000
1161@@ -55,7 +55,6 @@
1162 bootloaderGrubDir = filepath.Join(s.tempdir, "boot", "grub")
1163 bootloaderGrubConfigFile = filepath.Join(bootloaderGrubDir, "grub.cfg")
1164 bootloaderGrubEnvFile = filepath.Join(bootloaderGrubDir, "grubenv")
1165- bootloaderGrubUpdateCmd = filepath.Join(s.tempdir, "update-grub")
1166
1167 // and uboot
1168 bootloaderUbootDir = filepath.Join(s.tempdir, "boot", "uboot")
1169@@ -79,7 +78,6 @@
1170 // grub vars
1171 bootloaderGrubConfigFile = bootloaderGrubConfigFileReal
1172 bootloaderGrubEnvFile = bootloaderGrubEnvFileReal
1173- bootloaderGrubUpdateCmd = bootloaderGrubUpdateCmdReal
1174
1175 // uboot vars
1176 bootloaderUbootDir = bootloaderUbootDirReal
1177@@ -278,10 +276,6 @@
1178 mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
1179 options: "bind", bindMount: true},
1180
1181- // this comes from the grub bootloader via AdditionalBindMounts
1182- mountEntry{source: "/boot/grub", target: mountTarget + "/boot/grub",
1183- options: "bind", bindMount: true},
1184-
1185 // Required to allow grub inside the chroot to access
1186 // the "current" rootfs outside the chroot (used
1187 // to generate the grub menuitems).
1188@@ -383,7 +377,7 @@
1189 b.ToggleRootFSCalled = true
1190 return nil
1191 }
1192-func (b *mockBootloader) SyncBootFiles() error {
1193+func (b *mockBootloader) SyncBootFiles(bootAssets map[string]string) error {
1194 b.SyncBootFilesCalled = true
1195 return nil
1196 }
1197@@ -401,10 +395,6 @@
1198 b.MarkCurrentBootSuccessfulCalled = true
1199 return nil
1200 }
1201-func (b *mockBootloader) AdditionalBindMounts() []string {
1202- return nil
1203-}
1204-
1205 func (b *mockBootloader) BootDir() string {
1206 return ""
1207 }
1208@@ -453,7 +443,7 @@
1209 p := New()
1210 c.Assert(c, NotNil)
1211
1212- err := p.SyncBootloaderFiles()
1213+ err := p.SyncBootloaderFiles(nil)
1214 c.Assert(err, IsNil)
1215 c.Assert(b.SyncBootFilesCalled, Equals, true)
1216 }
1217
1218=== modified file 'snappy/oem.go'
1219--- snappy/oem.go 2015-05-28 11:58:30 +0000
1220+++ snappy/oem.go 2015-07-03 12:26:50 +0000
1221@@ -40,7 +40,8 @@
1222 type OEM struct {
1223 Store Store `yaml:"store,omitempty"`
1224 Hardware struct {
1225- Assign []HardwareAssign `yaml:"assign,omitempty"`
1226+ Assign []HardwareAssign `yaml:"assign,omitempty"`
1227+ BootAssets *BootAssets `yaml:"boot-assets,omitempty"`
1228 } `yaml:"hardware,omitempty"`
1229 Software Software `yaml:"software,omitempty"`
1230 }
1231@@ -55,6 +56,28 @@
1232 BuiltIn []string `yaml:"built-in,omitempty"`
1233 }
1234
1235+// BootAssets represent all the artifacts required for booting a system
1236+// that are particular to the board.
1237+type BootAssets struct {
1238+ Files []BootAssetFiles `yaml:"files,omitempty"`
1239+ RawFiles []BootAssetRawFiles `yaml:"raw-files,omitempty"`
1240+}
1241+
1242+// BootAssetRawFiles represent all the artifacts required for booting a system
1243+// that are particular to the board and require copying to specific sectors of
1244+// the disk
1245+type BootAssetRawFiles struct {
1246+ Path string `yaml:"path"`
1247+ Offset string `yaml:"offset"`
1248+}
1249+
1250+// BootAssetFiles represent all the files required for booting a system
1251+// that are particular to the board
1252+type BootAssetFiles struct {
1253+ Path string `yaml:"path"`
1254+ Target string `yaml:"target,omitempty"`
1255+}
1256+
1257 // HardwareAssign describes the hardware a app can use
1258 type HardwareAssign struct {
1259 PartID string `yaml:"part-id,omitempty"`
1260@@ -111,6 +134,28 @@
1261 return nil, errors.New("no oem snap")
1262 }
1263
1264+func bootAssetFilePaths() map[string]string {
1265+ oem, err := getOem()
1266+ if err != nil {
1267+ return nil
1268+ }
1269+
1270+ fileList := make(map[string]string)
1271+ oemPath := filepath.Join(snapOemDir, oem.Name, oem.Version)
1272+
1273+ for _, asset := range oem.OEM.Hardware.BootAssets.Files {
1274+ orig := filepath.Join(oemPath, asset.Path)
1275+
1276+ if asset.Target == "" {
1277+ fileList[orig] = filepath.Base(orig)
1278+ } else {
1279+ fileList[orig] = asset.Target
1280+ }
1281+ }
1282+
1283+ return fileList
1284+}
1285+
1286 // StoreID returns the store id setup by the oem package or an empty string
1287 func StoreID() string {
1288 oem, err := getOem()
1289
1290=== modified file 'snappy/systemimage.go'
1291--- snappy/systemimage.go 2015-06-30 10:34:01 +0000
1292+++ snappy/systemimage.go 2015-07-03 12:26:50 +0000
1293@@ -194,7 +194,7 @@
1294 if pb != nil {
1295 pb.Notify("Syncing boot files")
1296 }
1297- err = s.partition.SyncBootloaderFiles()
1298+ err = s.partition.SyncBootloaderFiles(bootAssetFilePaths())
1299 if err != nil {
1300 return "", err
1301 }
1302
1303=== modified file 'snappy/systemimage_test.go'
1304--- snappy/systemimage_test.go 2015-06-30 10:34:01 +0000
1305+++ snappy/systemimage_test.go 2015-07-03 12:26:50 +0000
1306@@ -161,7 +161,7 @@
1307 p.markBootSuccessfulCalled = true
1308 return nil
1309 }
1310-func (p *MockPartition) SyncBootloaderFiles() error {
1311+func (p *MockPartition) SyncBootloaderFiles(map[string]string) error {
1312 p.syncBootloaderFilesCalled = true
1313 return nil
1314 }

Subscribers

People subscribed via source and target branches