Merge lp:~mvo/snappy/snappy-all-snap2 into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Michael Vogt
Status: Work in progress
Proposed branch: lp:~mvo/snappy/snappy-all-snap2
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Diff against target: 5273 lines (+682/-3895)
35 files modified
bootloader/bootloader.go (+65/-229)
bootloader/bootloader_grub.go (+4/-51)
bootloader/bootloader_grub_test.go (+47/-122)
bootloader/bootloader_test.go (+94/-381)
bootloader/bootloader_uboot.go (+25/-216)
bootloader/bootloader_uboot_test.go (+47/-285)
bootloader/utils.go (+63/-0)
bootloader/utils_test.go (+47/-0)
cmd/snappy/cmd_booted.go (+2/-8)
gen-coverage.sh (+2/-2)
partition/assets.go (+0/-87)
partition/assets_test.go (+0/-36)
partition/dirs.go (+0/-41)
partition/mount.go (+0/-153)
partition/mount_test.go (+0/-64)
partition/partition.go (+0/-622)
partition/utils.go (+0/-82)
partition/utils_test.go (+0/-47)
pkg/types.go (+2/-0)
snappy/click.go (+2/-1)
snappy/dirs.go (+4/-0)
snappy/install.go (+2/-2)
snappy/install_test.go (+0/-92)
snappy/kernel.go (+103/-0)
snappy/oem.go (+11/-0)
snappy/os.go (+68/-0)
snappy/parts.go (+4/-8)
snappy/purge.go (+1/-1)
snappy/purge_test.go (+1/-1)
snappy/snapp.go (+80/-15)
snappy/snapp_test.go (+8/-11)
snappy/systemimage.go (+0/-518)
snappy/systemimage_native.go (+0/-211)
snappy/systemimage_native_test.go (+0/-123)
snappy/systemimage_test.go (+0/-486)
To merge this branch: bzr merge lp:~mvo/snappy/snappy-all-snap2
Reviewer Review Type Date Requested Status
Snappy Developers Pending
Review via email: mp+269922@code.launchpad.net

Description of the change

This moves the os/kernel into snaps.

To post a comment you must log in.
lp:~mvo/snappy/snappy-all-snap2 updated
667. By Michael Vogt

fix activate in the kernel

668. By Michael Vogt

re-add utils.go

Unmerged revisions

668. By Michael Vogt

re-add utils.go

667. By Michael Vogt

fix activate in the kernel

666. By Michael Vogt

add {Oem,OsKernel}Snap types

665. By Michael Vogt

re-add test hook function

664. By Michael Vogt

rename partition to bootloader

663. By Michael Vogt

add tests for the new code

662. By Michael Vogt

rename PartitionTestSuite->BootloaderTestSuite

661. By Michael Vogt

unset snappy_trial_boot

660. By Michael Vogt

move partition.go bits into bootloader as we do not care about partitioning at this level anymore

659. By Michael Vogt

kill partition.IsNextBootOther

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== renamed directory 'partition' => 'bootloader'
=== modified file 'bootloader/bootloader.go'
--- partition/bootloader.go 2015-07-16 11:07:30 +0000
+++ bootloader/bootloader.go 2015-09-03 08:14:56 +0000
@@ -17,70 +17,40 @@
17 *17 *
18 */18 */
1919
20package partition20package bootloader
2121
22import (22import (
23 "fmt"23 "errors"
24 "os"24 "strings"
25 "path/filepath"25)
2626
27 "launchpad.net/snappy/helpers"27var (
28 // ErrBootloader is returned if the bootloader can not be determined
29 ErrBootloader = errors.New("Unable to determine bootloader")
28)30)
2931
30const (32const (
31 // bootloader variable used to denote which rootfs to boot from
32 bootloaderRootfsVar = "snappy_ab"
33
34 // bootloader variable used to determine if boot was successful.33 // bootloader variable used to determine if boot was successful.
35 // Set to value of either bootloaderBootmodeTry (when attempting34 // Set to value of either bootloaderBootmodeTry (when attempting
36 // to boot a new rootfs) or bootloaderBootmodeSuccess (to denote35 // to boot a new rootfs) or bootloaderBootmodeSuccess (to denote
37 // that the boot of the new rootfs was successful).36 // that the boot of the new rootfs was successful).
38 bootloaderBootmodeVar = "snappy_mode"37 bootloaderBootmodeVar = "snappy_mode"
3938
40 bootloaderTrialBootVar = "snappy_trial_boot"
41
42 // Initial and final values39 // Initial and final values
43 bootloaderBootmodeTry = "try"40 bootloaderBootmodeTry = "try"
44 bootloaderBootmodeSuccess = "regular"41 bootloaderBootmodeSuccess = "regular"
4542
46 // textual description in hardware.yaml for AB systems43 // set by the bootloader itself to "1" and it will be cleared
47 bootloaderSystemAB = "system-AB"44 // as the last action of the boot
45 bootloaderTrialBootVar = "snappy_trial_boot"
48)46)
4947
50type bootloaderName string
51
52type bootLoader interface {48type bootLoader interface {
53 // Name of the bootloader
54 Name() bootloaderName
55
56 // Switch bootloader configuration so that the "other" root
57 // filesystem partition will be used on next boot.
58 ToggleRootFS(otherRootfs string) error
59
60 // Hook function called before system-image starts downloading
61 // and applying archives that allows files to be copied between
62 // partitions.
63 SyncBootFiles(bootAssets map[string]string) error
64
65 // Install any hardware-specific files that system-image
66 // downloaded.
67 HandleAssets() error
68
69 // Return the value of the specified bootloader variable49 // Return the value of the specified bootloader variable
70 GetBootVar(name string) (string, error)50 GetBootVar(name string) (string, error)
7151
72 // Return the 1-character name corresponding to the52 // Set the value of the specified bootloader variable
73 // rootfs that will be used on _next_ boot.53 SetBootVar(key, value string) error
74 //
75 // XXX: Note the distinction between this method and
76 // GetOtherRootFSName(): the latter corresponds to the other
77 // partition, whereas the value returned by this method is
78 // queried directly from the bootloader.
79 GetNextBootRootFSName() (string, error)
80
81 // Update the bootloader configuration to mark the
82 // currently-booted rootfs as having booted successfully.
83 MarkCurrentBootSuccessful(currentRootfs string) error
8454
85 // BootDir returns the (writable) bootloader-specific boot55 // BootDir returns the (writable) bootloader-specific boot
86 // directory.56 // directory.
@@ -88,16 +58,16 @@
88}58}
8959
90// Factory method that returns a new bootloader for the given partition60// Factory method that returns a new bootloader for the given partition
91var bootloader = bootloaderImpl61var new = bootloaderImpl
9262
93func bootloaderImpl(p *Partition) (bootLoader, error) {63func bootloaderImpl() (bootLoader, error) {
94 // try uboot64 // try uboot
95 if uboot := newUboot(p); uboot != nil {65 if uboot := newUboot(); uboot != nil {
96 return uboot, nil66 return uboot, nil
97 }67 }
9868
99 // no, try grub69 // no, try grub
100 if grub := newGrub(p); grub != nil {70 if grub := newGrub(); grub != nil {
101 return grub, nil71 return grub, nil
102 }72 }
10373
@@ -105,196 +75,62 @@
105 return nil, ErrBootloader75 return nil, ErrBootloader
106}76}
10777
108type bootloaderType struct {78// MarkBootSuccessful marks the boot as successful
109 partition *Partition79func MarkBootSuccessful() (err error) {
11080 bootloader, err := new()
111 // each rootfs partition has a corresponding u-boot directory named81 if err != nil {
112 // from the last character of the partition name ('a' or 'b').82 return err
113 currentRootfs string83 }
114 otherRootfs string84
11585 // FIXME: these vars should be applied all in a single run
116 // full path to rootfs-specific assets on boot partition86 for _, k := range []string{"snappy_os", "snappy_kernel"} {
117 currentBootPath string87 value, err := bootloader.GetBootVar(k)
118 otherBootPath string
119
120 // FIXME: this should /boot if possible
121 // the dir that the bootloader lives in (e.g. /boot/uboot)
122 bootloaderDir string
123}
124
125func newBootLoader(partition *Partition, bootloaderDir string) *bootloaderType {
126 // FIXME: is this the right thing to do? i.e. what should we do
127 // on a single partition system?
128 if partition.otherRootPartition() == nil {
129 return nil
130 }
131
132 // full label of the system {system-a,system-b}
133 currentLabel := partition.rootPartition().name
134 otherLabel := partition.otherRootPartition().name
135
136 // single letter description of the rootfs {a,b}
137 currentRootfs := string(currentLabel[len(currentLabel)-1])
138 otherRootfs := string(otherLabel[len(otherLabel)-1])
139
140 return &bootloaderType{
141 partition: partition,
142
143 currentRootfs: currentRootfs,
144 otherRootfs: otherRootfs,
145
146 // the paths that the kernel/initramfs are loaded, e.g.
147 // /boot/uboot/a
148 currentBootPath: filepath.Join(bootloaderDir, currentRootfs),
149 otherBootPath: filepath.Join(bootloaderDir, otherRootfs),
150
151 // the base bootloader dir, e.g. /boot/uboot or /boot/grub
152 bootloaderDir: bootloaderDir,
153 }
154}
155
156// FIXME:
157// - populate kernel if missing
158func (b *bootloaderType) SyncBootFiles(bootAssets map[string]string) (err error) {
159 for src, dst := range bootAssets {
160 if err := helpers.CopyIfDifferent(src, filepath.Join(b.bootloaderDir, dst)); err != nil {
161 return err
162 }
163 }
164
165 srcDir := b.currentBootPath
166 destDir := b.otherBootPath
167
168 // ensure they exist
169 for _, dir := range []string{srcDir, destDir} {
170 if err := os.MkdirAll(dir, 0755); err != nil {
171 return err
172 }
173
174 }
175 return helpers.RSyncWithDelete(srcDir, destDir)
176}
177
178// FIXME:
179// - if this fails it will never be re-tried because the "other" patition
180// is updated to revision-N in /etc/system-image/channel.ini
181// so the system only downloads from revision-N onwards even though the
182// complete update was not applied (i.e. kernel missing)
183func (b *bootloaderType) HandleAssets() (err error) {
184 // check if we have anything, if there is no hardware yaml, there is nothing
185 // to process.
186 hardware, err := readHardwareSpec()
187 if err == ErrNoHardwareYaml {
188 return nil
189 } else if err != nil {
190 return err
191 }
192 // ensure to remove the file if there are no errors
193 defer func() {
194 if err == nil {
195 os.Remove(hardwareSpecFile)
196 }
197 }()
198
199 /*
200 // validate bootloader
201 if hardware.Bootloader != b.Name() {
202 return fmt.Errorf(
203 "bootloader is of type %s but hardware spec requires %s",
204 b.Name(),
205 hardware.Bootloader)
206 }
207 */
208
209 // validate partition layout
210 if b.partition.dualRootPartitions() && hardware.PartitionLayout != bootloaderSystemAB {
211 return fmt.Errorf("hardware spec requires dual root partitions")
212 }
213
214 // ensure we have the destdir
215 destDir := b.otherBootPath
216 if err := os.MkdirAll(destDir, dirMode); err != nil {
217 return err
218 }
219
220 // install kernel+initrd
221 for _, file := range []string{hardware.Kernel, hardware.Initrd} {
222
223 if file == "" {
224 continue
225 }
226
227 // expand path
228 path := filepath.Join(cacheDir, file)
229
230 if !helpers.FileExists(path) {
231 return fmt.Errorf("can not find file %s", path)
232 }
233
234 // ensure we remove the dir later
235 defer func() {
236 if err == nil {
237 os.RemoveAll(filepath.Dir(path))
238 }
239 }()
240
241 if err := runCommand("/bin/cp", path, destDir); err != nil {
242 return err
243 }
244 }
245
246 // TODO: look at the OEM package for dtb changes too once that is
247 // fully speced
248
249 // install .dtb files
250 dtbSrcDir := filepath.Join(cacheDir, hardware.DtbDir)
251 // ensure there is a DtbDir specified
252 if hardware.DtbDir != "" && helpers.FileExists(dtbSrcDir) {
253 // ensure we cleanup the source dir
254 defer func() {
255 if err == nil {
256 os.RemoveAll(dtbSrcDir)
257 }
258 }()
259
260 dtbDestDir := filepath.Join(destDir, "dtbs")
261 if err := os.MkdirAll(dtbDestDir, dirMode); err != nil {
262 return err
263 }
264
265 files, err := filepath.Glob(filepath.Join(dtbSrcDir, "*"))
266 if err != nil {88 if err != nil {
267 return err89 return err
268 }90 }
26991
270 for _, file := range files {92 // FIXME: a bit ugly
271 if err := runCommand("/bin/cp", file, dtbDestDir); err != nil {93 newKey := strings.Replace(k, "snappy_", "snappy_good_", -1)
272 return err94 if err := bootloader.SetBootVar(newKey, value); err != nil {
273 }
274 }
275 }
276
277 if helpers.FileExists(flashAssetsDir) {
278 // FIXME: we don't currently do anything with the
279 // MLO + uImage files since they are not specified in
280 // the hardware spec. So for now, just remove them.
281
282 if err := os.RemoveAll(flashAssetsDir); err != nil {
283 return err95 return err
284 }96 }
285 }97
28698 }
287 return err99
100 if err := bootloader.SetBootVar("snappy_mode", "regular"); err != nil {
101 return err
102 }
103
104 return bootloader.SetBootVar("snappy_trial_boot", "0")
288}105}
289106
290// BootloaderDir returns the full path to the (mounted and writable)107// Dir returns the full path to the (mounted and writable)
291// bootloader-specific boot directory.108// bootloader-specific boot directory.
292func BootloaderDir() string {109func Dir() string {
293 if helpers.FileExists(bootloaderUbootDir) {110 bootloader, err := new()
294 return bootloaderUbootDir111 if err != nil {
295 } else if helpers.FileExists(bootloaderGrubDir) {112 return ""
296 return bootloaderGrubDir113 }
297 }114
298115 return bootloader.BootDir()
299 return ""116}
117
118// SetBootVar sets a boot variable
119func SetBootVar(key, value string) error {
120 bootloader, err := new()
121 if err != nil {
122 return err
123 }
124
125 return bootloader.SetBootVar(key, value)
126}
127
128// GetBootVar sets a boot variable
129func GetBootVar(key string) (string, error) {
130 bootloader, err := new()
131 if err != nil {
132 return "", err
133 }
134
135 return bootloader.GetBootVar(key)
300}136}
301137
=== modified file 'bootloader/bootloader_grub.go'
--- partition/bootloader_grub.go 2015-07-23 11:54:14 +0000
+++ bootloader/bootloader_grub.go 2015-09-03 08:14:56 +0000
@@ -17,7 +17,7 @@
17 *17 *
18 */18 */
1919
20package partition20package bootloader
2121
22import (22import (
23 "fmt"23 "fmt"
@@ -45,45 +45,15 @@
45)45)
4646
47type grub struct {47type grub struct {
48 bootloaderType
49}48}
5049
51const bootloaderNameGrub bootloaderName = "grub"
52
53// newGrub create a new Grub bootloader object50// newGrub create a new Grub bootloader object
54func newGrub(partition *Partition) bootLoader {51func newGrub() bootLoader {
55 if !helpers.FileExists(bootloaderGrubConfigFile) {52 if !helpers.FileExists(bootloaderGrubConfigFile) {
56 return nil53 return nil
57 }54 }
5855
59 b := newBootLoader(partition, bootloaderGrubDir)56 return &grub{}
60 if b == nil {
61 return nil
62 }
63 g := grub{bootloaderType: *b}
64
65 return &g
66}
67
68func (g *grub) Name() bootloaderName {
69 return bootloaderNameGrub
70}
71
72// ToggleRootFS make the Grub bootloader switch rootfs's.
73//
74// Approach:
75//
76// Update the grub configuration.
77func (g *grub) ToggleRootFS(otherRootfs string) (err error) {
78
79 if err := g.setBootVar(bootloaderBootmodeVar, bootloaderBootmodeTry); err != nil {
80 return err
81 }
82
83 // Record the partition that will be used for next boot. This
84 // isn't necessary for correct operation under grub, but allows
85 // us to query the next boot device easily.
86 return g.setBootVar(bootloaderRootfsVar, otherRootfs)
87}57}
8858
89func (g *grub) GetBootVar(name string) (value string, err error) {59func (g *grub) GetBootVar(name string) (value string, err error) {
@@ -103,7 +73,7 @@
103 return cfg.Get("", name)73 return cfg.Get("", name)
104}74}
10575
106func (g *grub) setBootVar(name, value string) (err error) {76func (g *grub) SetBootVar(name, value string) (err error) {
107 // note that strings are not quoted since because77 // note that strings are not quoted since because
108 // RunCommand() does not use a shell and thus adding quotes78 // RunCommand() does not use a shell and thus adding quotes
109 // stores them in the environment file (which is not desirable)79 // stores them in the environment file (which is not desirable)
@@ -111,23 +81,6 @@
111 return runCommand(bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", arg)81 return runCommand(bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", arg)
112}82}
11383
114func (g *grub) GetNextBootRootFSName() (label string, err error) {
115 return g.GetBootVar(bootloaderRootfsVar)
116}
117
118func (g *grub) MarkCurrentBootSuccessful(currentRootfs string) (err error) {
119 // Clear the variable set on boot to denote a good boot.
120 if err := g.setBootVar(bootloaderTrialBootVar, "0"); err != nil {
121 return err
122 }
123
124 if err := g.setBootVar(bootloaderRootfsVar, currentRootfs); err != nil {
125 return err
126 }
127
128 return g.setBootVar(bootloaderBootmodeVar, bootloaderBootmodeSuccess)
129}
130
131func (g *grub) BootDir() string {84func (g *grub) BootDir() string {
132 return bootloaderGrubDir85 return bootloaderGrubDir
133}86}
13487
=== modified file 'bootloader/bootloader_grub_test.go'
--- partition/bootloader_grub_test.go 2015-07-23 11:54:14 +0000
+++ bootloader/bootloader_grub_test.go 2015-09-03 08:14:56 +0000
@@ -17,15 +17,13 @@
17 *17 *
18 */18 */
1919
20package partition20package bootloader
2121
22import (22import (
23 "fmt"23 "fmt"
24 "io/ioutil"24 "io/ioutil"
25 "os"25 "os"
26 "path/filepath"26 "strings"
27
28 "launchpad.net/snappy/helpers"
2927
30 . "gopkg.in/check.v1"28 . "gopkg.in/check.v1"
31)29)
@@ -35,7 +33,21 @@
35 c.Assert(err, IsNil)33 c.Assert(err, IsNil)
36}34}
3735
38func (s *PartitionTestSuite) makeFakeGrubEnv(c *C) {36type singleCommand []string
37
38var allCommands = []singleCommand{}
39
40func mockRunCommandWithCapture(args ...string) (err error) {
41 allCommands = append(allCommands, args)
42 return nil
43}
44
45func mockGrubEditenvList(cmd ...string) (string, error) {
46 mockGrubEditenvOutput := fmt.Sprintf("%s=regular", bootloaderBootmodeVar)
47 return mockGrubEditenvOutput, nil
48}
49
50func (s *BootloaderTestSuite) makeFakeGrubEnv(c *C) {
39 // create bootloader51 // create bootloader
40 err := os.MkdirAll(bootloaderGrubDir, 0755)52 err := os.MkdirAll(bootloaderGrubDir, 0755)
41 c.Assert(err, IsNil)53 c.Assert(err, IsNil)
@@ -48,135 +60,48 @@
48 runCommand = mockRunCommandWithCapture60 runCommand = mockRunCommandWithCapture
49}61}
5062
51func (s *PartitionTestSuite) TestNewGrubNoGrubReturnsNil(c *C) {63func (s *BootloaderTestSuite) TestNewGrubNoGrubReturnsNil(c *C) {
52 bootloaderGrubConfigFile = "no-such-dir"64 bootloaderGrubConfigFile = "no-such-dir"
5365
54 partition := New()66 g := newGrub()
55 g := newGrub(partition)
56 c.Assert(g, IsNil)67 c.Assert(g, IsNil)
57}68}
5869
59func (s *PartitionTestSuite) TestNewGrub(c *C) {70func (s *BootloaderTestSuite) TestNewGrub(c *C) {
60 s.makeFakeGrubEnv(c)71 s.makeFakeGrubEnv(c)
6172
62 partition := New()73 g := newGrub()
63 g := newGrub(partition)74 c.Assert(g, NotNil)
64 c.Assert(g, NotNil)75}
65 c.Assert(g.Name(), Equals, bootloaderNameGrub)76
66}77func (s *BootloaderTestSuite) TestGetBootVer(c *C) {
67
68type singleCommand []string
69
70var allCommands = []singleCommand{}
71
72func mockRunCommandWithCapture(args ...string) (err error) {
73 allCommands = append(allCommands, args)
74 return nil
75}
76
77func (s *PartitionTestSuite) TestToggleRootFS(c *C) {
78 s.makeFakeGrubEnv(c)
79 allCommands = []singleCommand{}
80
81 partition := New()
82 g := newGrub(partition)
83 c.Assert(g, NotNil)
84 err := g.ToggleRootFS("b")
85 c.Assert(err, IsNil)
86
87 // this is always called
88 mp := singleCommand{"/bin/mountpoint", mountTarget}
89 c.Assert(allCommands[0], DeepEquals, mp)
90
91 expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_mode=try"}
92 c.Assert(allCommands[1], DeepEquals, expectedGrubSet)
93
94 // the https://developer.ubuntu.com/en/snappy/porting guide says
95 // we always use the short names
96 expectedGrubSet = singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_ab=b"}
97 c.Assert(allCommands[2], DeepEquals, expectedGrubSet)
98
99 c.Assert(len(allCommands), Equals, 3)
100}
101
102func mockGrubEditenvList(cmd ...string) (string, error) {
103 mockGrubEditenvOutput := fmt.Sprintf("%s=regular", bootloaderBootmodeVar)
104 return mockGrubEditenvOutput, nil
105}
106
107func (s *PartitionTestSuite) TestGetBootVer(c *C) {
108 s.makeFakeGrubEnv(c)78 s.makeFakeGrubEnv(c)
109 runCommandWithStdout = mockGrubEditenvList79 runCommandWithStdout = mockGrubEditenvList
11080
111 partition := New()81 g := newGrub()
112 g := newGrub(partition)
11382
114 v, err := g.GetBootVar(bootloaderBootmodeVar)83 v, err := g.GetBootVar(bootloaderBootmodeVar)
115 c.Assert(err, IsNil)84 c.Assert(err, IsNil)
116 c.Assert(v, Equals, "regular")85 c.Assert(v, Equals, "regular")
117}86}
11887
119func (s *PartitionTestSuite) TestGetBootloaderWithGrub(c *C) {88func (s *BootloaderTestSuite) TestSetBootVer(c *C) {
120 s.makeFakeGrubEnv(c)89 s.makeFakeGrubEnv(c)
121 p := New()90 runCommandWithStdout = mockGrubEditenvList
122 bootloader, err := bootloader(p)91
123 c.Assert(err, IsNil)92 g := newGrub()
124 c.Assert(bootloader.Name(), Equals, bootloaderNameGrub)93
125}94 err := g.SetBootVar("snappy_os", "123")
12695 c.Assert(err, IsNil)
127func (s *PartitionTestSuite) TestGrubMarkCurrentBootSuccessful(c *C) {96
128 s.makeFakeGrubEnv(c)97 expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_os=123"}
129 allCommands = []singleCommand{}98 c.Assert(allCommands[0], DeepEquals, expectedGrubSet)
13099 c.Assert(allCommands, HasLen, 1)
131 partition := New()100}
132 g := newGrub(partition)101
133 c.Assert(g, NotNil)102func (s *BootloaderTestSuite) TestGrubBootdir(c *C) {
134 err := g.MarkCurrentBootSuccessful("a")103 s.makeFakeGrubEnv(c)
135 c.Assert(err, IsNil)104
136105 g := newGrub()
137 // this is always called106 c.Assert(strings.HasSuffix(g.BootDir(), "/boot/grub"), Equals, true)
138 mp := singleCommand{"/bin/mountpoint", mountTarget}
139 c.Assert(allCommands[0], DeepEquals, mp)
140
141 expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_trial_boot=0"}
142
143 c.Assert(allCommands[1], DeepEquals, expectedGrubSet)
144
145 expectedGrubSet2 := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_ab=a"}
146
147 c.Assert(allCommands[2], DeepEquals, expectedGrubSet2)
148
149 expectedGrubSet3 := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_mode=regular"}
150
151 c.Assert(allCommands[3], DeepEquals, expectedGrubSet3)
152
153}
154
155func (s *PartitionTestSuite) TestSyncBootFilesWithAssets(c *C) {
156 err := os.MkdirAll(bootloaderGrubDir, 0755)
157 c.Assert(err, IsNil)
158
159 runCommand = mockRunCommand
160 b := grub{
161 bootloaderType{
162 currentBootPath: c.MkDir(),
163 otherBootPath: c.MkDir(),
164 bootloaderDir: c.MkDir(),
165 },
166 }
167
168 bootfile := filepath.Join(c.MkDir(), "bootfile")
169 err = ioutil.WriteFile(bootfile, []byte(bootfile), 0644)
170 c.Assert(err, IsNil)
171
172 bootassets := map[string]string{
173 bootfile: filepath.Base(bootfile),
174 }
175
176 err = b.SyncBootFiles(bootassets)
177 c.Assert(err, IsNil)
178
179 dst := filepath.Join(b.bootloaderDir, bootassets[bootfile])
180 c.Check(helpers.FileExists(dst), Equals, true)
181 c.Check(helpers.FilesAreEqual(bootfile, dst), Equals, true)
182}107}
183108
=== renamed file 'partition/partition_test.go' => 'bootloader/bootloader_test.go'
--- partition/partition_test.go 2015-07-16 11:07:30 +0000
+++ bootloader/bootloader_test.go 2015-09-03 08:14:56 +0000
@@ -17,14 +17,11 @@
17 *17 *
18 */18 */
1919
20package partition20package bootloader
2121
22import (22import (
23 "errors"
24 "io/ioutil"
25 "os"23 "os"
26 "path/filepath"24 "path/filepath"
27 "strings"
28 "testing"25 "testing"
2926
30 . "gopkg.in/check.v1"27 . "gopkg.in/check.v1"
@@ -34,22 +31,14 @@
34func Test(t *testing.T) { TestingT(t) }31func Test(t *testing.T) { TestingT(t) }
3532
36// partition specific testsuite33// partition specific testsuite
37type PartitionTestSuite struct {34type BootloaderTestSuite struct {
38 tempdir string35 tempdir string
39}36}
4037
41var _ = Suite(&PartitionTestSuite{})38var _ = Suite(&BootloaderTestSuite{})
4239
43func mockRunCommand(args ...string) (err error) {40func (s *BootloaderTestSuite) SetUpTest(c *C) {
44 return err
45}
46
47func (s *PartitionTestSuite) SetUpTest(c *C) {
48 s.tempdir = c.MkDir()41 s.tempdir = c.MkDir()
49 runLsblk = mockRunLsblkDualSnappy
50
51 // custom mount target
52 mountTarget = c.MkDir()
5342
54 // setup fake paths for grub43 // setup fake paths for grub
55 bootloaderGrubDir = filepath.Join(s.tempdir, "boot", "grub")44 bootloaderGrubDir = filepath.Join(s.tempdir, "boot", "grub")
@@ -58,23 +47,15 @@
5847
59 // and uboot48 // and uboot
60 bootloaderUbootDir = filepath.Join(s.tempdir, "boot", "uboot")49 bootloaderUbootDir = filepath.Join(s.tempdir, "boot", "uboot")
61 bootloaderUbootConfigFile = filepath.Join(bootloaderUbootDir, "uEnv.txt")
62 bootloaderUbootEnvFile = filepath.Join(bootloaderUbootDir, "uEnv.txt")
63 bootloaderUbootFwEnvFile = filepath.Join(bootloaderUbootDir, "uboot.env")50 bootloaderUbootFwEnvFile = filepath.Join(bootloaderUbootDir, "uboot.env")
64 bootloaderUbootStampFile = filepath.Join(bootloaderUbootDir, "snappy-stamp.txt")
65
66 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
67}51}
6852
69func (s *PartitionTestSuite) TearDownTest(c *C) {53func (s *BootloaderTestSuite) TearDownTest(c *C) {
70 os.RemoveAll(s.tempdir)54 os.RemoveAll(s.tempdir)
7155
72 // always restore what we might have mocked away56 // always restore what we might have mocked away
73 runCommand = runCommandImpl57 runCommand = runCommandImpl
74 bootloader = bootloaderImpl58 new = bootloaderImpl
75 cacheDir = cacheDirReal
76 hardwareSpecFile = hardwareSpecFileReal
77 mountTarget = mountTargetReal
7859
79 // grub vars60 // grub vars
80 bootloaderGrubConfigFile = bootloaderGrubConfigFileReal61 bootloaderGrubConfigFile = bootloaderGrubConfigFileReal
@@ -82,369 +63,101 @@
8263
83 // uboot vars64 // uboot vars
84 bootloaderUbootDir = bootloaderUbootDirReal65 bootloaderUbootDir = bootloaderUbootDirReal
85 bootloaderUbootConfigFile = bootloaderUbootConfigFileReal
86 bootloaderUbootEnvFile = bootloaderUbootEnvFileReal
87 bootloaderUbootStampFile = bootloaderUbootStampFileReal
88
89 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
90}
91
92func makeHardwareYaml(c *C, hardwareYaml string) (outPath string) {
93 tmp, err := ioutil.TempFile(c.MkDir(), "hw-")
94 c.Assert(err, IsNil)
95 defer tmp.Close()
96
97 if hardwareYaml == "" {
98 hardwareYaml = `
99kernel: assets/vmlinuz
100initrd: assets/initrd.img
101dtbs: assets/dtbs
102partition-layout: system-AB
103bootloader: u-boot
104`
105 }
106 _, err = tmp.Write([]byte(hardwareYaml))
107 c.Assert(err, IsNil)
108
109 return tmp.Name()
110}
111
112func mockRunLsblkDualSnappy() (output []string, err error) {
113 dualData := `
114NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
115NAME="sda1" LABEL="" PKNAME="sda" MOUNTPOINT=""
116NAME="sda2" LABEL="system-boot" PKNAME="sda" MOUNTPOINT="/boot/efi"
117NAME="sda3" LABEL="system-a" PKNAME="sda" MOUNTPOINT="/"
118NAME="sda4" LABEL="system-b" PKNAME="sda" MOUNTPOINT=""
119NAME="sda5" LABEL="writable" PKNAME="sda" MOUNTPOINT="/writable"
120NAME="sr0" LABEL="" PKNAME="" MOUNTPOINT=""
121`
122 return strings.Split(dualData, "\n"), err
123}
124
125func (s *PartitionTestSuite) TestSnappyDualRoot(c *C) {
126 p := New()
127 c.Assert(p.dualRootPartitions(), Equals, true)
128 c.Assert(p.singleRootPartition(), Equals, false)
129
130 rootPartitions := p.rootPartitions()
131 c.Assert(rootPartitions[0].name, Equals, "system-a")
132 c.Assert(rootPartitions[0].device, Equals, "/dev/sda3")
133 c.Assert(rootPartitions[0].parentName, Equals, "/dev/sda")
134 c.Assert(rootPartitions[1].name, Equals, "system-b")
135 c.Assert(rootPartitions[1].device, Equals, "/dev/sda4")
136 c.Assert(rootPartitions[1].parentName, Equals, "/dev/sda")
137
138 wp := p.writablePartition()
139 c.Assert(wp.name, Equals, "writable")
140 c.Assert(wp.device, Equals, "/dev/sda5")
141 c.Assert(wp.parentName, Equals, "/dev/sda")
142
143 boot := p.bootPartition()
144 c.Assert(boot.name, Equals, "system-boot")
145 c.Assert(boot.device, Equals, "/dev/sda2")
146 c.Assert(boot.parentName, Equals, "/dev/sda")
147
148 root := p.rootPartition()
149 c.Assert(root.name, Equals, "system-a")
150 c.Assert(root.device, Equals, "/dev/sda3")
151 c.Assert(root.parentName, Equals, "/dev/sda")
152
153 other := p.otherRootPartition()
154 c.Assert(other.name, Equals, "system-b")
155 c.Assert(other.device, Equals, "/dev/sda4")
156 c.Assert(other.parentName, Equals, "/dev/sda")
157}
158
159func (s *PartitionTestSuite) TestRunWithOtherDualParitionRO(c *C) {
160 p := New()
161 reportedRoot := ""
162 err := p.RunWithOther(RO, func(otherRoot string) (err error) {
163 reportedRoot = otherRoot
164 return nil
165 })
166 c.Assert(err, IsNil)
167 c.Assert(reportedRoot, Equals, mountTarget)
168}
169
170func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) {
171 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
172
173 runCommand = mockRunCommand
174
175 p := New()
176 err := p.RunWithOther(RW, func(otherRoot string) (err error) {
177 return errors.New("canary")
178 })
179
180 // ensure we actually got the right error
181 c.Assert(err, NotNil)
182 c.Assert(err.Error(), Equals, "canary")
183
184 // ensure cleanup happend
185
186 // FIXME: mounts are global
187 expected := mountEntry{
188 source: "/dev/sda4",
189 target: mountTarget,
190 options: "",
191 bindMount: false,
192 }
193
194 // At program exit, "other" should still be mounted
195 c.Assert(mounts, DeepEquals, mountEntryArray{expected})
196
197 undoMounts(false)
198
199 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
200}
201
202func (s *PartitionTestSuite) TestRunWithOtherSingleParitionRO(c *C) {
203 runLsblk = mockRunLsblkSingleRootSnappy
204 p := New()
205 err := p.RunWithOther(RO, func(otherRoot string) (err error) {
206 return nil
207 })
208 c.Assert(err, Equals, ErrNoDualPartition)
209}
210
211func mockRunLsblkSingleRootSnappy() (output []string, err error) {
212 dualData := `
213NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
214NAME="sda1" LABEL="" PKNAME="sda" MOUNTPOINT=""
215NAME="sda2" LABEL="system-boot" PKNAME="sda" MOUNTPOINT=""
216NAME="sda3" LABEL="system-a" PKNAME="sda" MOUNTPOINT="/"
217NAME="sda5" LABEL="writable" PKNAME="sda" MOUNTPOINT="/writable"
218`
219 return strings.Split(dualData, "\n"), err
220}
221func (s *PartitionTestSuite) TestSnappySingleRoot(c *C) {
222 runLsblk = mockRunLsblkSingleRootSnappy
223
224 p := New()
225 c.Assert(p.dualRootPartitions(), Equals, false)
226 c.Assert(p.singleRootPartition(), Equals, true)
227
228 root := p.rootPartition()
229 c.Assert(root.name, Equals, "system-a")
230 c.Assert(root.device, Equals, "/dev/sda3")
231 c.Assert(root.parentName, Equals, "/dev/sda")
232
233 other := p.otherRootPartition()
234 c.Assert(other, IsNil)
235
236 rootPartitions := p.rootPartitions()
237 c.Assert(&rootPartitions[0], DeepEquals, root)
238}
239
240func (s *PartitionTestSuite) TestMountUnmountTracking(c *C) {
241 runCommand = mockRunCommand
242
243 p := New()
244 c.Assert(p, NotNil)
245
246 p.mountOtherRootfs(false)
247 expected := mountEntry{
248 source: "/dev/sda4",
249 target: mountTarget,
250 options: "",
251 bindMount: false,
252 }
253
254 c.Assert(mounts, DeepEquals, mountEntryArray{expected})
255
256 p.unmountOtherRootfs()
257 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
258}
259
260func (s *PartitionTestSuite) TestUnmountRequiredFilesystems(c *C) {
261 runCommand = mockRunCommand
262 s.makeFakeGrubEnv(c)
263
264 p := New()
265 c.Assert(c, NotNil)
266
267 p.bindmountRequiredFilesystems()
268 c.Assert(mounts, DeepEquals, mountEntryArray{
269 mountEntry{source: "/dev", target: mountTarget + "/dev",
270 options: "bind", bindMount: true},
271
272 mountEntry{source: "/proc", target: mountTarget + "/proc",
273 options: "bind", bindMount: true},
274
275 mountEntry{source: "/sys", target: mountTarget + "/sys",
276 options: "bind", bindMount: true},
277 mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
278 options: "bind", bindMount: true},
279
280 // Required to allow grub inside the chroot to access
281 // the "current" rootfs outside the chroot (used
282 // to generate the grub menuitems).
283 mountEntry{source: "/",
284 target: mountTarget + mountTarget,
285 options: "bind,ro", bindMount: true},
286 })
287 p.unmountRequiredFilesystems()
288 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
289}
290
291func (s *PartitionTestSuite) TestUndoMounts(c *C) {
292 runCommand = mockRunCommand
293
294 p := New()
295 c.Assert(c, NotNil)
296
297 err := p.remountOther(RW)
298 c.Assert(err, IsNil)
299
300 p.bindmountRequiredFilesystems()
301 c.Assert(mounts, DeepEquals, mountEntryArray{
302
303 mountEntry{source: "/dev/sda4", target: mountTarget,
304 options: "", bindMount: false},
305
306 mountEntry{source: "/dev", target: mountTarget + "/dev",
307 options: "bind", bindMount: true},
308
309 mountEntry{source: "/proc", target: mountTarget + "/proc",
310 options: "bind", bindMount: true},
311
312 mountEntry{source: "/sys", target: mountTarget + "/sys",
313 options: "bind", bindMount: true},
314
315 mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
316 options: "bind", bindMount: true},
317
318 mountEntry{source: "/",
319 target: mountTarget + mountTarget,
320 options: "bind,ro", bindMount: true},
321 })
322
323 // should leave non-bind mounts
324 undoMounts(true)
325
326 c.Assert(mounts, DeepEquals, mountEntryArray{
327 mountEntry{
328 source: "/dev/sda4",
329 target: mountTarget,
330 options: "",
331 bindMount: false,
332 },
333 })
334
335 // should unmount everything
336 undoMounts(false)
337
338 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
339}
340
341func mockRunLsblkNoSnappy() (output []string, err error) {
342 dualData := `
343NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
344NAME="sda1" LABEL="meep" PKNAME="sda" MOUNTPOINT="/"
345NAME="sr0" LABEL="" PKNAME="" MOUNTPOINT=""
346`
347 return strings.Split(dualData, "\n"), err
348}
349
350func (s *PartitionTestSuite) TestSnappyNoSnappyPartitions(c *C) {
351 runLsblk = mockRunLsblkNoSnappy
352
353 p := New()
354 err := p.getPartitionDetails()
355 c.Assert(err, Equals, ErrPartitionDetection)
356
357 c.Assert(p.dualRootPartitions(), Equals, false)
358 c.Assert(p.singleRootPartition(), Equals, false)
359
360 c.Assert(p.rootPartition(), IsNil)
361 c.Assert(p.bootPartition(), IsNil)
362 c.Assert(p.writablePartition(), IsNil)
363 c.Assert(p.otherRootPartition(), IsNil)
364}66}
36567
366// mock bootloader for the tests68// mock bootloader for the tests
367type mockBootloader struct {69type mockBootloader struct {
368 ToggleRootFSCalled bool70 bootvars map[string]string
369 HandleAssetsCalled bool
370 MarkCurrentBootSuccessfulCalled bool
371 SyncBootFilesCalled bool
372}71}
37372
374func (b *mockBootloader) Name() bootloaderName {
375 return "mocky"
376}
377func (b *mockBootloader) ToggleRootFS(otherRootfs string) error {
378 b.ToggleRootFSCalled = true
379 return nil
380}
381func (b *mockBootloader) SyncBootFiles(bootAssets map[string]string) error {
382 b.SyncBootFilesCalled = true
383 return nil
384}
385func (b *mockBootloader) HandleAssets() error {
386 b.HandleAssetsCalled = true
387 return nil
388}
389func (b *mockBootloader) GetBootVar(name string) (string, error) {73func (b *mockBootloader) GetBootVar(name string) (string, error) {
390 return "", nil74 return b.bootvars[name], nil
391}75}
392func (b *mockBootloader) GetNextBootRootFSName() (string, error) {76func (b *mockBootloader) SetBootVar(key, value string) error {
393 return "", nil77 b.bootvars[key] = value
394}
395func (b *mockBootloader) MarkCurrentBootSuccessful(currentRootfs string) error {
396 b.MarkCurrentBootSuccessfulCalled = true
397 return nil78 return nil
398}79}
399func (b *mockBootloader) BootDir() string {80func (b *mockBootloader) BootDir() string {
400 return ""81 return "/boot/mock"
401}82}
40283
403func (s *PartitionTestSuite) TestToggleBootloaderRootfs(c *C) {84func mockRunCommand(args ...string) (err error) {
404 runCommand = mockRunCommand85 return err
405 b := &mockBootloader{}86}
406 bootloader = func(p *Partition) (bootLoader, error) {87
407 return b, nil88func (s *BootloaderTestSuite) TestMarkBootSuccessful(c *C) {
408 }89 runCommand = mockRunCommand
40990 b := &mockBootloader{
410 p := New()91 bootvars: map[string]string{
411 c.Assert(c, NotNil)92 "snappy_os": "os1",
41293 "snappy_kernel": "k1",
413 err := p.toggleBootloaderRootfs()94 },
414 c.Assert(err, IsNil)95 }
415 c.Assert(b.ToggleRootFSCalled, Equals, true)96 new = func() (bootLoader, error) {
416 c.Assert(b.HandleAssetsCalled, Equals, true)97 return b, nil
41798 }
418 p.unmountOtherRootfs()99
419 c.Assert(mounts, DeepEquals, mountEntryArray(nil))100 err := MarkBootSuccessful()
420}101 c.Assert(err, IsNil)
421102 c.Assert(b.bootvars["snappy_good_os"], Equals, "os1")
422func (s *PartitionTestSuite) TestMarkBootSuccessful(c *C) {103 c.Assert(b.bootvars["snappy_good_kernel"], Equals, "k1")
423 runCommand = mockRunCommand104 c.Assert(b.bootvars["snappy_mode"], Equals, "regular")
424 b := &mockBootloader{}105 c.Assert(b.bootvars["snappy_trial_boot"], Equals, "0")
425 bootloader = func(p *Partition) (bootLoader, error) {106}
426 return b, nil107
427 }108func (s *BootloaderTestSuite) TestSetBootVar(c *C) {
428109 runCommand = mockRunCommand
429 p := New()110 b := &mockBootloader{
430 c.Assert(c, NotNil)111 bootvars: map[string]string{},
431112 }
432 err := p.MarkBootSuccessful()113 new = func() (bootLoader, error) {
433 c.Assert(err, IsNil)114 return b, nil
434 c.Assert(b.MarkCurrentBootSuccessfulCalled, Equals, true)115 }
435}116
436117 err := SetBootVar("foo", "bar")
437func (s *PartitionTestSuite) TestSyncBootFiles(c *C) {118 c.Assert(err, IsNil)
438 runCommand = mockRunCommand119 c.Assert(b.bootvars["foo"], Equals, "bar")
439 b := &mockBootloader{}120}
440 bootloader = func(p *Partition) (bootLoader, error) {121
441 return b, nil122func (s *BootloaderTestSuite) TestGetBootVar(c *C) {
442 }123 runCommand = mockRunCommand
443124 b := &mockBootloader{
444 p := New()125 bootvars: map[string]string{"baz": "foobar"},
445 c.Assert(c, NotNil)126 }
446127 new = func() (bootLoader, error) {
447 err := p.SyncBootloaderFiles(nil)128 return b, nil
448 c.Assert(err, IsNil)129 }
449 c.Assert(b.SyncBootFilesCalled, Equals, true)130
131 v, err := GetBootVar("baz")
132 c.Assert(err, IsNil)
133 c.Assert(v, Equals, "foobar")
134}
135
136func (s *BootloaderTestSuite) TestGetBootDir(c *C) {
137 runCommand = mockRunCommand
138 b := &mockBootloader{
139 bootvars: map[string]string{"baz": "foobar"},
140 }
141 new = func() (bootLoader, error) {
142 return b, nil
143 }
144
145 d := Dir()
146 c.Assert(d, Equals, "/boot/mock")
147}
148
149func (s *BootloaderTestSuite) TestBootloaderIsGrub(c *C) {
150 s.makeFakeGrubEnv(c)
151
152 g, err := new()
153 c.Assert(err, IsNil)
154 c.Assert(g, FitsTypeOf, &grub{})
155}
156
157func (s *BootloaderTestSuite) TestBootloaderIsUboot(c *C) {
158 s.makeFakeUbootEnv(c)
159
160 u, err := new()
161 c.Assert(err, IsNil)
162 c.Assert(u, FitsTypeOf, &uboot{})
450}163}
451164
=== modified file 'bootloader/bootloader_uboot.go'
--- partition/bootloader_uboot.go 2015-07-24 12:00:01 +0000
+++ bootloader/bootloader_uboot.go 2015-09-03 08:14:56 +0000
@@ -17,252 +17,61 @@
17 *17 *
18 */18 */
1919
20package partition20package bootloader
2121
22import (22import (
23 "bufio"
24 "bytes"
25 "fmt"
26 "os"
27 "strings"
28
29 "launchpad.net/snappy/helpers"23 "launchpad.net/snappy/helpers"
3024
31 "github.com/mvo5/goconfigparser"
32 "github.com/mvo5/uboot-go/uenv"25 "github.com/mvo5/uboot-go/uenv"
33)26)
3427
35const (28const (
36 bootloaderUbootDirReal = "/boot/uboot"29 bootloaderUbootDirReal = "/boot/uboot"
37 bootloaderUbootConfigFileReal = "/boot/uboot/uEnv.txt"
38
39 // File created by u-boot itself when
40 // bootloaderBootmodeTry == "try" which the
41 // successfully booted system must remove to flag to u-boot that
42 // this partition is "good".
43 bootloaderUbootStampFileReal = "/boot/uboot/snappy-stamp.txt"
44
45 // DEPRECATED:
46 bootloaderUbootEnvFileReal = "/boot/uboot/snappy-system.txt"
47
48 // the real uboot env
49 bootloaderUbootFwEnvFileReal = "/boot/uboot/uboot.env"30 bootloaderUbootFwEnvFileReal = "/boot/uboot/uboot.env"
50)31)
5132
52// var to make it testable33// var to make it testable
53var (34var (
54 bootloaderUbootDir = bootloaderUbootDirReal35 bootloaderUbootDir = bootloaderUbootDirReal
55 bootloaderUbootConfigFile = bootloaderUbootConfigFileReal36 bootloaderUbootFwEnvFile = bootloaderUbootFwEnvFileReal
56 bootloaderUbootStampFile = bootloaderUbootStampFileReal
57 bootloaderUbootEnvFile = bootloaderUbootEnvFileReal
58 bootloaderUbootFwEnvFile = bootloaderUbootFwEnvFileReal
59
60 atomicWriteFile = helpers.AtomicWriteFile
61)37)
6238
63const bootloaderNameUboot bootloaderName = "u-boot"
64
65type uboot struct {39type uboot struct {
66 bootloaderType40}
67}
68
69// Stores a Name and a Value to be added as a name=value pair in a file.
70// TODO convert to map
71type configFileChange struct {
72 Name string
73 Value string
74}
75
76var setBootVar = func(name, value string) error { return nil }
77var getBootVar = func(name string) (string, error) { return "", nil }
7841
79// newUboot create a new Uboot bootloader object42// newUboot create a new Uboot bootloader object
80func newUboot(partition *Partition) bootLoader {43func newUboot() bootLoader {
81 if !helpers.FileExists(bootloaderUbootConfigFile) {44 if !helpers.FileExists(bootloaderUbootFwEnvFile) {
82 return nil45 return nil
83 }46 }
8447
85 b := newBootLoader(partition, bootloaderUbootDir)48 return &uboot{}
86 if b == nil {49}
87 return nil50
88 }51func (u *uboot) GetBootVar(name string) (value string, err error) {
89 u := uboot{bootloaderType: *b}52 env, err := uenv.Open(bootloaderUbootFwEnvFile)
9053 if err != nil {
91 if helpers.FileExists(bootloaderUbootFwEnvFile) {54 return "", err
92 setBootVar = setBootVarFwEnv55 }
93 getBootVar = getBootVarFwEnv56
94 } else {57 return env.Get(name), nil
95 setBootVar = setBootVarLegacy58}
96 getBootVar = getBootVarLegacy59
97 }60func (u *uboot) SetBootVar(key, value string) error {
98
99 return &u
100}
101
102func (u *uboot) Name() bootloaderName {
103 return bootloaderNameUboot
104}
105
106func (u *uboot) ToggleRootFS(otherRootfs string) (err error) {
107 if err := setBootVar(bootloaderRootfsVar, string(otherRootfs)); err != nil {
108 return err
109 }
110
111 return setBootVar(bootloaderBootmodeVar, bootloaderBootmodeTry)
112}
113
114func getBootVarLegacy(name string) (value string, err error) {
115 cfg := goconfigparser.New()
116 cfg.AllowNoSectionHeader = true
117 if err := cfg.ReadFile(bootloaderUbootEnvFile); err != nil {
118 return "", nil
119 }
120
121 return cfg.Get("", name)
122}
123
124func setBootVarLegacy(name, value string) error {
125 curVal, err := getBootVarLegacy(name)
126 if err == nil && curVal == value {
127 return nil
128 }
129
130 changes := []configFileChange{
131 configFileChange{
132 Name: name,
133 Value: value,
134 },
135 }
136
137 return modifyNameValueFile(bootloaderUbootEnvFile, changes)
138}
139
140func setBootVarFwEnv(name, value string) error {
141 env, err := uenv.Open(bootloaderUbootFwEnvFile)61 env, err := uenv.Open(bootloaderUbootFwEnvFile)
142 if err != nil {62 if err != nil {
143 return err63 return err
144 }64 }
14565
146 // already set, nothing to do66 // already set, nothing to do
147 if env.Get(name) == value {67 if env.Get(key) == value {
148 return nil68 return nil
149 }69 }
15070
151 env.Set(name, value)71 env.Set(key, value)
152 return env.Save()72 return env.Save()
153}73}
15474
155func getBootVarFwEnv(name string) (string, error) {
156 env, err := uenv.Open(bootloaderUbootFwEnvFile)
157 if err != nil {
158 return "", err
159 }
160
161 return env.Get(name), nil
162}
163
164func (u *uboot) GetBootVar(name string) (value string, err error) {
165 return getBootVar(name)
166}
167
168func (u *uboot) GetNextBootRootFSName() (label string, err error) {
169 value, err := u.GetBootVar(bootloaderRootfsVar)
170 if err != nil {
171 // should never happen
172 return "", err
173 }
174
175 return value, nil
176}
177
178// FIXME: this is super similar to grub now, refactor to extract the
179// common code
180func (u *uboot) MarkCurrentBootSuccessful(currentRootfs string) error {
181 // Clear the variable set on boot to denote a good boot.
182 if err := setBootVar(bootloaderTrialBootVar, "0"); err != nil {
183 return err
184 }
185
186 if err := setBootVar(bootloaderRootfsVar, currentRootfs); err != nil {
187 return err
188 }
189
190 if err := setBootVar(bootloaderBootmodeVar, bootloaderBootmodeSuccess); err != nil {
191 return err
192 }
193
194 // legacy support, does not error if the file is not there
195 return os.RemoveAll(bootloaderUbootStampFile)
196}
197
198func (u *uboot) BootDir() string {75func (u *uboot) BootDir() string {
199 return bootloaderUbootDir76 return bootloaderUbootDir
200}77}
201
202// Rewrite the specified file, applying the specified set of changes.
203// Lines not in the changes slice are left alone.
204// If the original file does not contain any of the name entries (from
205// the corresponding configFileChange objects), those entries are
206// appended to the file.
207//
208// FIXME: put into utils package
209// FIXME: improve logic
210func modifyNameValueFile(path string, changes []configFileChange) error {
211 var updated []configFileChange
212
213 // we won't write to a file if we don't need to.
214 updateNeeded := false
215
216 file, err := os.Open(path)
217 if err != nil {
218 return err
219 }
220 defer file.Close()
221
222 buf := bytes.NewBuffer(nil)
223 scanner := bufio.NewScanner(file)
224 for scanner.Scan() {
225 line := scanner.Text()
226 for _, change := range changes {
227 if strings.HasPrefix(line, fmt.Sprintf("%s=", change.Name)) {
228 value := strings.SplitN(line, "=", 2)[1]
229 // updated is used later to see if you had the originally requested
230 // value.
231 updated = append(updated, change)
232 if value != change.Value {
233 line = fmt.Sprintf("%s=%s", change.Name, change.Value)
234 updateNeeded = true
235 }
236 }
237 }
238 if _, err := fmt.Fprintln(buf, line); err != nil {
239 return err
240 }
241 }
242
243 for _, change := range changes {
244 got := false
245 for _, update := range updated {
246 if update.Name == change.Name {
247 got = true
248 break
249 }
250 }
251
252 if !got {
253 updateNeeded = true
254
255 // name/value pair did not exist in original
256 // file, so append
257 if _, err := fmt.Fprintf(buf, "%s=%s\n", change.Name, change.Value); err != nil {
258 return err
259 }
260 }
261 }
262
263 if updateNeeded {
264 return atomicWriteFile(path, buf.Bytes(), 0644)
265 }
266
267 return nil
268}
26978
=== modified file 'bootloader/bootloader_uboot_test.go'
--- partition/bootloader_uboot_test.go 2015-07-24 11:58:04 +0000
+++ bootloader/bootloader_uboot_test.go 2015-09-03 08:14:56 +0000
@@ -17,17 +17,14 @@
17 *17 *
18 */18 */
1919
20package partition20package bootloader
2121
22import (22import (
23 "io/ioutil"
24 "os"23 "os"
25 "path/filepath"
26 "strings"24 "strings"
27 "time"25 "time"
2826
29 . "gopkg.in/check.v1"27 . "gopkg.in/check.v1"
30 "launchpad.net/snappy/helpers"
3128
32 "github.com/mvo5/uboot-go/uenv"29 "github.com/mvo5/uboot-go/uenv"
33)30)
@@ -66,319 +63,84 @@
66snappy_boot=if test "${snappy_mode}" = "try"; then if test -e mmc ${bootpart} ${snappy_stamp}; then if test "${snappy_ab}" = "a"; then setenv snappy_ab "b"; else setenv snappy_ab "a"; fi; else fatwrite mmc ${mmcdev}:${mmcpart} 0x0 ${snappy_stamp} 0; fi; fi; run loadfiles; setenv mmcroot /dev/disk/by-label/system-${snappy_ab} ${snappy_cmdline}; run mmcargs; bootz ${loadaddr} ${initrd_addr}:${initrd_size} ${fdtaddr}63snappy_boot=if test "${snappy_mode}" = "try"; then if test -e mmc ${bootpart} ${snappy_stamp}; then if test "${snappy_ab}" = "a"; then setenv snappy_ab "b"; else setenv snappy_ab "a"; fi; else fatwrite mmc ${mmcdev}:${mmcpart} 0x0 ${snappy_stamp} 0; fi; fi; run loadfiles; setenv mmcroot /dev/disk/by-label/system-${snappy_ab} ${snappy_cmdline}; run mmcargs; bootz ${loadaddr} ${initrd_addr}:${initrd_size} ${fdtaddr}
67`64`
6865
69func (s *PartitionTestSuite) makeFakeUbootEnv(c *C) {66func (s *BootloaderTestSuite) makeFakeUbootEnv(c *C) {
70 err := os.MkdirAll(bootloaderUbootDir, 0755)67 err := os.MkdirAll(bootloaderUbootDir, 0755)
71 c.Assert(err, IsNil)68 c.Assert(err, IsNil)
7269
73 // this file just needs to exist70 env, err := uenv.Create(bootloaderUbootFwEnvFile, 4096)
74 err = ioutil.WriteFile(bootloaderUbootConfigFile, []byte(""), 0644)
75 c.Assert(err, IsNil)71 c.Assert(err, IsNil)
7672 env.Set("snappy_ab", "a")
77 // this file needs specific data73 env.Set("snappy_mode", "regular")
78 err = ioutil.WriteFile(bootloaderUbootEnvFile, []byte(fakeUbootEnvData), 0644)74 err = env.Save()
79 c.Assert(err, IsNil)75 c.Assert(err, IsNil)
80}76}
8177
82func (s *PartitionTestSuite) TestNewUbootNoUbootReturnsNil(c *C) {78func (s *BootloaderTestSuite) TestNewUbootNoUbootReturnsNil(c *C) {
83 partition := New()79 u := newUboot()
84 u := newUboot(partition)
85 c.Assert(u, IsNil)80 c.Assert(u, IsNil)
86}81}
8782
88func (s *PartitionTestSuite) TestNewUboot(c *C) {83func (s *BootloaderTestSuite) TestNewUboot(c *C) {
89 s.makeFakeUbootEnv(c)84 s.makeFakeUbootEnv(c)
9085
91 partition := New()86 u := newUboot()
92 u := newUboot(partition)87 c.Assert(u, NotNil)
93 c.Assert(u, NotNil)88}
94 c.Assert(u.Name(), Equals, bootloaderNameUboot)89
95}90func (s *BootloaderTestSuite) TestUbootGetEnvVar(c *C) {
9691 s.makeFakeUbootEnv(c)
97func (s *PartitionTestSuite) TestUbootGetBootVar(c *C) {92
98 s.makeFakeUbootEnv(c)93 u := newUboot()
99
100 partition := New()
101 u := newUboot(partition)
102
103 nextBoot, err := u.GetBootVar(bootloaderRootfsVar)
104 c.Assert(err, IsNil)
105 // the https://developer.ubuntu.com/en/snappy/porting guide says
106 // we always use the short names
107 c.Assert(nextBoot, Equals, "a")
108
109 // ensure that nextBootIsOther works too
110 c.Assert(partition.IsNextBootOther(), Equals, false)
111}
112
113func (s *PartitionTestSuite) TestUbootToggleRootFS(c *C) {
114 s.makeFakeUbootEnv(c)
115
116 partition := New()
117 u := newUboot(partition)
118 c.Assert(u, NotNil)
119
120 err := u.ToggleRootFS("b")
121 c.Assert(err, IsNil)
122
123 nextBoot, err := u.GetBootVar(bootloaderRootfsVar)
124 c.Assert(err, IsNil)
125 c.Assert(nextBoot, Equals, "b")
126
127 // ensure that nextBootIsOther works too
128 c.Assert(partition.IsNextBootOther(), Equals, true)
129}
130
131func (s *PartitionTestSuite) TestUbootGetEnvVar(c *C) {
132 s.makeFakeUbootEnv(c)
133
134 partition := New()
135 u := newUboot(partition)
136 c.Assert(u, NotNil)94 c.Assert(u, NotNil)
13795
138 v, err := u.GetBootVar(bootloaderBootmodeVar)96 v, err := u.GetBootVar(bootloaderBootmodeVar)
139 c.Assert(err, IsNil)97 c.Assert(err, IsNil)
140 c.Assert(v, Equals, "regular")98 c.Assert(v, Equals, "regular")
14199
142 v, err = u.GetBootVar(bootloaderRootfsVar)100 v, err = u.GetBootVar("snappy_ab")
143 c.Assert(err, IsNil)101 c.Assert(err, IsNil)
144 c.Assert(v, Equals, "a")102 c.Assert(v, Equals, "a")
145}103}
146104
147func (s *PartitionTestSuite) TestGetBootloaderWithUboot(c *C) {105func (s *BootloaderTestSuite) TestUbootSetEnvVar(c *C) {
148 s.makeFakeUbootEnv(c)106 s.makeFakeUbootEnv(c)
149 p := New()107
150 bootloader, err := bootloader(p)108 u := newUboot()
151 c.Assert(err, IsNil)109 c.Assert(u, NotNil)
152 c.Assert(bootloader.Name(), Equals, bootloaderNameUboot)110
153}111 err := u.SetBootVar("snappy_os", "123")
154112 c.Assert(err, IsNil)
155func makeMockAssetsDir(c *C) {113
156 for _, f := range []string{"assets/vmlinuz", "assets/initrd.img", "assets/dtbs/foo.dtb", "assets/dtbs/bar.dtb"} {114 v, err := u.GetBootVar("snappy_os")
157 p := filepath.Join(cacheDir, f)115 c.Assert(err, IsNil)
158 os.MkdirAll(filepath.Dir(p), 0755)116 c.Assert(v, Equals, "123")
159 err := ioutil.WriteFile(p, []byte(f), 0644)117}
160 c.Assert(err, IsNil)118
161 }119func (s *BootloaderTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
162}120 s.makeFakeUbootEnv(c)
163
164func (s *PartitionTestSuite) TestHandleAssets(c *C) {
165 s.makeFakeUbootEnv(c)
166 p := New()
167 bootloader, err := bootloader(p)
168 c.Assert(err, IsNil)
169
170 // mock the hardwareYaml and the cacheDir
171 hardwareSpecFile = makeHardwareYaml(c, "")
172 cacheDir = c.MkDir()
173
174 // create mock assets/
175 makeMockAssetsDir(c)
176
177 // run the handle assets code
178 err = bootloader.HandleAssets()
179 c.Assert(err, IsNil)
180
181 // ensure the files are where we expect them
182 otherBootPath := bootloader.(*uboot).otherBootPath
183 for _, f := range []string{"vmlinuz", "initrd.img", "dtbs/foo.dtb", "dtbs/bar.dtb"} {
184 content, err := ioutil.ReadFile(filepath.Join(otherBootPath, f))
185 c.Assert(err, IsNil)
186 // match content
187 c.Assert(strings.HasSuffix(string(content), f), Equals, true)
188 }
189
190 // ensure nothing left behind
191 c.Assert(helpers.FileExists(filepath.Join(cacheDir, "assets")), Equals, false)
192 c.Assert(helpers.FileExists(hardwareSpecFile), Equals, false)
193}
194
195func (s *PartitionTestSuite) TestHandleAssetsVerifyBootloader(c *C) {
196 s.makeFakeUbootEnv(c)
197 p := New()
198 bootloader, err := bootloader(p)
199 c.Assert(err, IsNil)
200
201 // mock the hardwareYaml and the cacheDir
202 hardwareSpecFile = makeHardwareYaml(c, "bootloader: grub")
203 cacheDir = c.MkDir()
204
205 err = bootloader.HandleAssets()
206 c.Assert(err, NotNil)
207}
208
209func (s *PartitionTestSuite) TestHandleAssetsFailVerifyPartitionLayout(c *C) {
210 s.makeFakeUbootEnv(c)
211 p := New()
212 bootloader, err := bootloader(p)
213 c.Assert(err, IsNil)
214
215 // mock the hardwareYaml and the cacheDir
216 hardwareSpecFile = makeHardwareYaml(c, `
217bootloader: u-boot
218partition-layout: inplace
219`)
220 err = bootloader.HandleAssets()
221 c.Assert(err, NotNil)
222}
223
224func (s *PartitionTestSuite) TestHandleAssetsNoHardwareYaml(c *C) {
225 s.makeFakeUbootEnv(c)
226 hardwareSpecFile = filepath.Join(c.MkDir(), "non-existent.yaml")
227
228 p := New()
229 bootloader, err := bootloader(p)
230 c.Assert(err, IsNil)
231
232 c.Assert(bootloader.HandleAssets(), IsNil)
233}
234
235func (s *PartitionTestSuite) TestHandleAssetsBadHardwareYaml(c *C) {
236 s.makeFakeUbootEnv(c)
237 p := New()
238 bootloader, err := bootloader(p)
239 c.Assert(err, IsNil)
240
241 hardwareSpecFile = makeHardwareYaml(c, `
242bootloader u-boot
243`)
244
245 c.Assert(bootloader.HandleAssets(), NotNil)
246}
247
248func (s *PartitionTestSuite) TestUbootMarkCurrentBootSuccessful(c *C) {
249 s.makeFakeUbootEnv(c)
250
251 // To simulate what uboot does for a "try" mode boot, create a
252 // stamp file. uboot will expect this file to be removed by
253 // "snappy booted" if the system boots successfully. If this
254 // file exists when uboot starts, it will know that the previous
255 // boot failed, and will therefore toggle to the other rootfs.
256 err := ioutil.WriteFile(bootloaderUbootStampFile, []byte(""), 0640)
257 c.Assert(err, IsNil)
258 c.Assert(helpers.FileExists(bootloaderUbootStampFile), Equals, true)
259
260 partition := New()
261 u := newUboot(partition)
262 c.Assert(u, NotNil)
263
264 // enter "try" mode so that we check to ensure that snappy
265 // correctly modifies the snappy_mode variable from "try" to
266 // "regular" to denote a good boot.
267 err = u.ToggleRootFS("b")
268 c.Assert(err, IsNil)
269
270 c.Assert(helpers.FileExists(bootloaderUbootEnvFile), Equals, true)
271 bytes, err := ioutil.ReadFile(bootloaderUbootEnvFile)
272 c.Assert(err, IsNil)
273 c.Assert(strings.Contains(string(bytes), "snappy_mode=try"), Equals, true)
274 c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, false)
275 c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true)
276
277 err = u.MarkCurrentBootSuccessful("b")
278 c.Assert(err, IsNil)
279
280 c.Assert(helpers.FileExists(bootloaderUbootStampFile), Equals, false)
281 c.Assert(helpers.FileExists(bootloaderUbootEnvFile), Equals, true)
282
283 bytes, err = ioutil.ReadFile(bootloaderUbootEnvFile)
284 c.Assert(err, IsNil)
285 c.Assert(strings.Contains(string(bytes), "snappy_mode=try"), Equals, false)
286 c.Assert(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, true)
287 c.Assert(strings.Contains(string(bytes), "snappy_ab=b"), Equals, true)
288}
289
290func (s *PartitionTestSuite) TestNoWriteNotNeeded(c *C) {
291 s.makeFakeUbootEnv(c)
292
293 atomiCall := false
294 atomicWriteFile = func(a string, b []byte, c os.FileMode) error {
295 atomiCall = true
296 return helpers.AtomicWriteFile(a, b, c)
297 }
298
299 partition := New()
300 u := newUboot(partition)
301 c.Assert(u, NotNil)
302
303 c.Check(u.MarkCurrentBootSuccessful("a"), IsNil)
304 c.Assert(atomiCall, Equals, false)
305}
306
307func (s *PartitionTestSuite) TestWriteDueToMissingValues(c *C) {
308 s.makeFakeUbootEnv(c)
309
310 // this file needs specific data
311 c.Assert(ioutil.WriteFile(bootloaderUbootEnvFile, []byte(""), 0644), IsNil)
312
313 atomiCall := false
314 atomicWriteFile = func(a string, b []byte, c os.FileMode) error {
315 atomiCall = true
316 return helpers.AtomicWriteFile(a, b, c)
317 }
318
319 partition := New()
320 u := newUboot(partition)
321 c.Assert(u, NotNil)
322
323 c.Check(u.MarkCurrentBootSuccessful("a"), IsNil)
324 c.Assert(atomiCall, Equals, true)
325
326 bytes, err := ioutil.ReadFile(bootloaderUbootEnvFile)
327 c.Assert(err, IsNil)
328 c.Check(strings.Contains(string(bytes), "snappy_mode=try"), Equals, false)
329 c.Check(strings.Contains(string(bytes), "snappy_mode=regular"), Equals, true)
330 c.Check(strings.Contains(string(bytes), "snappy_ab=a"), Equals, true)
331}
332
333func (s *PartitionTestSuite) TestUbootMarkCurrentBootSuccessfulFwEnv(c *C) {
334 s.makeFakeUbootEnv(c)
335
336 env, err := uenv.Create(bootloaderUbootFwEnvFile, 4096)
337 c.Assert(err, IsNil)
338 env.Set("snappy_ab", "b")
339 env.Set("snappy_mode", "try")
340 env.Set("snappy_trial_boot", "1")
341 err = env.Save()
342 c.Assert(err, IsNil)
343
344 partition := New()
345 u := newUboot(partition)
346 c.Assert(u, NotNil)
347
348 err = u.MarkCurrentBootSuccessful("b")
349 c.Assert(err, IsNil)
350
351 env, err = uenv.Open(bootloaderUbootFwEnvFile)
352 c.Assert(err, IsNil)
353 c.Assert(env.String(), Equals, "snappy_ab=b\nsnappy_mode=regular\nsnappy_trial_boot=0\n")
354}
355
356func (s *PartitionTestSuite) TestUbootSetEnvNoUselessWrites(c *C) {
357 s.makeFakeUbootEnv(c)
358
359 env, err := uenv.Create(bootloaderUbootFwEnvFile, 4096)
360 c.Assert(err, IsNil)
361 env.Set("snappy_ab", "b")
362 env.Set("snappy_mode", "regular")
363 err = env.Save()
364 c.Assert(err, IsNil)
365121
366 st, err := os.Stat(bootloaderUbootFwEnvFile)122 st, err := os.Stat(bootloaderUbootFwEnvFile)
367 c.Assert(err, IsNil)123 c.Assert(err, IsNil)
368 time.Sleep(100 * time.Millisecond)124 time.Sleep(100 * time.Millisecond)
369125
370 partition := New()126 u := newUboot()
371 u := newUboot(partition)
372 c.Assert(u, NotNil)127 c.Assert(u, NotNil)
373128
374 err = setBootVar(bootloaderRootfsVar, "b")129 err = u.SetBootVar("snappy_ab", "a")
375 c.Assert(err, IsNil)130 c.Assert(err, IsNil)
376131
377 env, err = uenv.Open(bootloaderUbootFwEnvFile)132 env, err := uenv.Open(bootloaderUbootFwEnvFile)
378 c.Assert(err, IsNil)133 c.Assert(err, IsNil)
379 c.Assert(env.String(), Equals, "snappy_ab=b\nsnappy_mode=regular\n")134 c.Assert(env.String(), Equals, "snappy_ab=a\nsnappy_mode=regular\n")
380135
381 st2, err := os.Stat(bootloaderUbootFwEnvFile)136 st2, err := os.Stat(bootloaderUbootFwEnvFile)
382 c.Assert(err, IsNil)137 c.Assert(err, IsNil)
383 c.Assert(st.ModTime(), Equals, st2.ModTime())138 c.Assert(st.ModTime(), Equals, st2.ModTime())
384}139}
140
141func (s *BootloaderTestSuite) TestUbootBootdir(c *C) {
142 s.makeFakeUbootEnv(c)
143
144 u := newUboot()
145 c.Assert(strings.HasSuffix(u.BootDir(), "/boot/uboot"), Equals, true)
146}
385147
=== added file 'bootloader/utils.go'
--- bootloader/utils.go 1970-01-01 00:00:00 +0000
+++ bootloader/utils.go 2015-09-03 08:14:56 +0000
@@ -0,0 +1,63 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package bootloader
21
22import (
23 "errors"
24 "fmt"
25 "os/exec"
26 "strings"
27)
28
29// FIXME: would it make sense to differenciate between launch errors and
30// exit code? (i.e. something like (returnCode, error) ?)
31func runCommandImpl(args ...string) (err error) {
32 if len(args) == 0 {
33 return errors.New("no command specified")
34 }
35
36 if out, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil {
37 cmdline := strings.Join(args, " ")
38 return fmt.Errorf("Failed to run command '%s': %s (%s)",
39 cmdline, out, err)
40 }
41 return nil
42}
43
44// Run the command specified by args
45// This is a var instead of a function to making mocking in the tests easier
46var runCommand = runCommandImpl
47
48// Run command specified by args and return the output
49func runCommandWithStdoutImpl(args ...string) (output string, err error) {
50 if len(args) == 0 {
51 return "", errors.New("no command specified")
52 }
53
54 bytes, err := exec.Command(args[0], args[1:]...).Output()
55 if err != nil {
56 return "", err
57 }
58
59 return string(bytes), err
60}
61
62// This is a var instead of a function to making mocking in the tests easier
63var runCommandWithStdout = runCommandWithStdoutImpl
064
=== added file 'bootloader/utils_test.go'
--- bootloader/utils_test.go 1970-01-01 00:00:00 +0000
+++ bootloader/utils_test.go 2015-09-03 08:14:56 +0000
@@ -0,0 +1,47 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package bootloader
21
22import (
23 . "gopkg.in/check.v1"
24)
25
26type UtilsTestSuite struct {
27}
28
29var _ = Suite(&UtilsTestSuite{})
30
31func (s *UtilsTestSuite) SetUpTest(c *C) {
32}
33
34func (s *UtilsTestSuite) TestRunCommand(c *C) {
35 err := runCommandImpl("false")
36 c.Assert(err, NotNil)
37
38 err = runCommandImpl("no-such-command")
39 c.Assert(err, NotNil)
40}
41
42func (s *UtilsTestSuite) TestRunCommandWithStdout(c *C) {
43 runCommandWithStdout = runCommandWithStdoutImpl
44 output, err := runCommandWithStdout("sh", "-c", "printf 'foo\nbar'")
45 c.Assert(err, IsNil)
46 c.Assert(output, DeepEquals, "foo\nbar")
47}
048
=== modified file 'cmd/snappy/cmd_booted.go'
--- cmd/snappy/cmd_booted.go 2015-06-15 07:38:57 +0000
+++ cmd/snappy/cmd_booted.go 2015-09-03 08:14:56 +0000
@@ -20,9 +20,8 @@
20package main20package main
2121
22import (22import (
23 "launchpad.net/snappy/bootloader"
23 "launchpad.net/snappy/logger"24 "launchpad.net/snappy/logger"
24 "launchpad.net/snappy/pkg"
25 "launchpad.net/snappy/snappy"
26)25)
2726
28type cmdBooted struct {27type cmdBooted struct {
@@ -43,10 +42,5 @@
43}42}
4443
45func (x *cmdBooted) doBooted() error {44func (x *cmdBooted) doBooted() error {
46 parts, err := snappy.ActiveSnapsByType(pkg.TypeCore)45 return bootloader.MarkBootSuccessful()
47 if err != nil {
48 return err
49 }
50
51 return parts[0].(*snappy.SystemImagePart).MarkBootSuccessful()
52}46}
5347
=== modified file 'gen-coverage.sh'
--- gen-coverage.sh 2015-06-09 13:02:49 +0000
+++ gen-coverage.sh 2015-09-03 08:14:56 +0000
@@ -10,8 +10,8 @@
1010
11(cd snappy &&11(cd snappy &&
12 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-snappy.html)12 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-snappy.html)
13(cd partition &&13(cd bootloader &&
14 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-partition.html)14 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-bootloader.html)
15(cd logger &&15(cd logger &&
16 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-logger.html)16 $GOPATH/bin/gocov test | $GOPATH/bin/gocov-html > $OUTPUTDIR/cov-logger.html)
17(cd helpers &&17(cd helpers &&
1818
=== removed file 'partition/assets.go'
--- partition/assets.go 2015-06-11 13:08:19 +0000
+++ partition/assets.go 1970-01-01 00:00:00 +0000
@@ -1,87 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package partition
21
22import (
23 "errors"
24 "io/ioutil"
25 "os"
26 "path/filepath"
27
28 "gopkg.in/yaml.v2"
29)
30
31// Representation of the yaml in the hardwareSpecFile
32type hardwareSpecType struct {
33 Kernel string `yaml:"kernel"`
34 Initrd string `yaml:"initrd"`
35 DtbDir string `yaml:"dtbs"`
36 PartitionLayout string `yaml:"partition-layout"`
37 Bootloader bootloaderName `yaml:"bootloader"`
38}
39
40var (
41 // ErrNoHardwareYaml is returned when no hardware yaml is found in
42 // the update, this means that there is nothing to process with regards
43 // to device parts.
44 ErrNoHardwareYaml = errors.New("no hardware.yaml")
45
46 // Declarative specification of the type of system which specifies such
47 // details as:
48 //
49 // - the location of initrd+kernel within the system-image archive.
50 // - the location of hardware-specific .dtb files within the
51 // system-image archive.
52 // - the type of bootloader that should be used for this system.
53 // - expected system partition layout (single or dual rootfs's).
54 hardwareSpecFileReal = filepath.Join(cacheDir, "hardware.yaml")
55
56 // useful to override in the tests
57 hardwareSpecFile = hardwareSpecFileReal
58
59 // Directory that _may_ get automatically created on unpack that
60 // contains updated hardware-specific boot assets (such as initrd,
61 // kernel)
62 assetsDir = filepath.Join(cacheDir, "assets")
63
64 // Directory that _may_ get automatically created on unpack that
65 // contains updated hardware-specific assets that require flashing
66 // to the disk (such as uBoot, MLO)
67 flashAssetsDir = filepath.Join(cacheDir, "flashtool-assets")
68)
69
70func readHardwareSpec() (*hardwareSpecType, error) {
71 var h hardwareSpecType
72
73 data, err := ioutil.ReadFile(hardwareSpecFile)
74 // if hardware.yaml does not exist it just means that there was no
75 // device part in the update.
76 if os.IsNotExist(err) {
77 return nil, ErrNoHardwareYaml
78 } else if err != nil {
79 return nil, err
80 }
81
82 if err := yaml.Unmarshal([]byte(data), &h); err != nil {
83 return nil, err
84 }
85
86 return &h, nil
87}
880
=== removed file 'partition/assets_test.go'
--- partition/assets_test.go 2015-06-11 07:06:21 +0000
+++ partition/assets_test.go 1970-01-01 00:00:00 +0000
@@ -1,36 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package partition
21
22import (
23 . "gopkg.in/check.v1"
24)
25
26func (s *PartitionTestSuite) TestHardwareSpec(c *C) {
27
28 hardwareSpecFile = makeHardwareYaml(c, "")
29 hw, err := readHardwareSpec()
30 c.Assert(err, IsNil)
31 c.Assert(hw.Kernel, Equals, "assets/vmlinuz")
32 c.Assert(hw.Initrd, Equals, "assets/initrd.img")
33 c.Assert(hw.DtbDir, Equals, "assets/dtbs")
34 c.Assert(hw.PartitionLayout, Equals, bootloaderSystemAB)
35 c.Assert(hw.Bootloader, Equals, bootloaderNameUboot)
36}
370
=== removed file 'partition/dirs.go'
--- partition/dirs.go 2015-06-11 08:05:14 +0000
+++ partition/dirs.go 1970-01-01 00:00:00 +0000
@@ -1,41 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package partition
21
22import (
23 "path/filepath"
24)
25
26// The full path to the cache directory, which is used as a
27// scratch pad, for downloading new images to and bind mounting the
28// rootfs.
29const cacheDirReal = "/writable/cache"
30
31var (
32 // useful for overwriting in the tests
33 cacheDir = cacheDirReal
34
35 // Directory to mount writable root filesystem below the cache
36 // diretory.
37 mountTargetReal = filepath.Join(cacheDir, "system")
38
39 // useful to override in tests
40 mountTarget = mountTargetReal
41)
420
=== removed file 'partition/mount.go'
--- partition/mount.go 2015-06-11 13:08:19 +0000
+++ partition/mount.go 1970-01-01 00:00:00 +0000
@@ -1,153 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package partition
21
22import (
23 "fmt"
24 "sort"
25)
26
27// MountOption represents how the partition should be mounted, currently
28// RO (read-only) and RW (read-write) are supported
29type MountOption int
30
31const (
32 // RO mounts the partition read-only
33 RO MountOption = iota
34 // RW mounts the partition read-only
35 RW
36)
37
38// mountEntry represents a mount this package has created.
39type mountEntry struct {
40 source string
41 target string
42
43 options string
44
45 // true if target refers to a bind mount. We could derive this
46 // from options, but this field saves the effort.
47 bindMount bool
48}
49
50// mountEntryArray represents an array of mountEntry objects.
51type mountEntryArray []mountEntry
52
53// current mounts that this package has created.
54var mounts mountEntryArray
55
56// Len is part of the sort interface, required to allow sort to work
57// with an array of Mount objects.
58func (mounts mountEntryArray) Len() int {
59 return len(mounts)
60}
61
62// Less is part of the sort interface, required to allow sort to work
63// with an array of Mount objects.
64func (mounts mountEntryArray) Less(i, j int) bool {
65 return mounts[i].target < mounts[j].target
66}
67
68// Swap is part of the sort interface, required to allow sort to work
69// with an array of Mount objects.
70func (mounts mountEntryArray) Swap(i, j int) {
71 mounts[i], mounts[j] = mounts[j], mounts[i]
72}
73
74// removeMountByTarget removes the Mount specified by the target from
75// the global mounts array.
76func removeMountByTarget(mnts mountEntryArray, target string) (results mountEntryArray) {
77
78 for _, m := range mnts {
79 if m.target != target {
80 results = append(results, m)
81 }
82 }
83
84 return results
85}
86
87// undoMounts unmounts all mounts this package has mounted optionally
88// only unmounting bind mounts and leaving all remaining mounts.
89func undoMounts(bindMountsOnly bool) error {
90
91 mountsCopy := make(mountEntryArray, len(mounts), cap(mounts))
92 copy(mountsCopy, mounts)
93
94 // reverse sort to ensure unmounts are handled in the correct
95 // order.
96 sort.Sort(sort.Reverse(mountsCopy))
97
98 // Iterate backwards since we want a reverse-sorted list of
99 // mounts to ensure we can unmount in order.
100 for _, mount := range mountsCopy {
101 if bindMountsOnly && !mount.bindMount {
102 continue
103 }
104
105 if err := unmountAndRemoveFromGlobalMountList(mount.target); err != nil {
106 return err
107 }
108 }
109
110 return nil
111}
112
113// FIXME: use syscall.Mount() here
114func mount(source, target, options string) (err error) {
115 var args []string
116
117 args = append(args, "/bin/mount")
118 if options != "" {
119 args = append(args, fmt.Sprintf("-o%s", options))
120 }
121
122 args = append(args, source)
123 args = append(args, target)
124
125 return runCommand(args...)
126}
127
128// Mount the given directory and add it to the global mounts slice
129func mountAndAddToGlobalMountList(m mountEntry) (err error) {
130
131 err = mount(m.source, m.target, m.options)
132 if err == nil {
133 mounts = append(mounts, m)
134 }
135
136 return err
137}
138
139// Unmount the given directory and remove it from the global "mounts" slice
140func unmountAndRemoveFromGlobalMountList(target string) (err error) {
141 err = runCommand("/bin/umount", target)
142 if err != nil {
143 return err
144
145 }
146
147 results := removeMountByTarget(mounts, target)
148
149 // Update global
150 mounts = results
151
152 return nil
153}
1540
=== removed file 'partition/mount_test.go'
--- partition/mount_test.go 2015-06-11 08:15:22 +0000
+++ partition/mount_test.go 1970-01-01 00:00:00 +0000
@@ -1,64 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package partition
21
22import (
23 . "gopkg.in/check.v1"
24)
25
26func (s *PartitionTestSuite) TestMountEntryArray(c *C) {
27 mea := mountEntryArray{}
28
29 c.Assert(mea.Len(), Equals, 0)
30
31 me := mountEntry{source: "/dev",
32 target: "/dev",
33 options: "bind",
34 bindMount: true}
35
36 mea = append(mea, me)
37 c.Assert(mea.Len(), Equals, 1)
38
39 me = mountEntry{source: "/foo",
40 target: "/foo",
41 options: "",
42 bindMount: false}
43
44 mea = append(mea, me)
45 c.Assert(mea.Len(), Equals, 2)
46
47 c.Assert(mea.Less(0, 1), Equals, true)
48 c.Assert(mea.Less(1, 0), Equals, false)
49
50 mea.Swap(0, 1)
51 c.Assert(mea.Less(0, 1), Equals, false)
52 c.Assert(mea.Less(1, 0), Equals, true)
53
54 results := removeMountByTarget(mea, "invalid")
55
56 // No change expected
57 c.Assert(results, DeepEquals, mea)
58
59 results = removeMountByTarget(mea, "/dev")
60
61 c.Assert(len(results), Equals, 1)
62 c.Assert(results[0], Equals, mountEntry{source: "/foo",
63 target: "/foo", options: "", bindMount: false})
64}
650
=== removed file 'partition/partition.go'
--- partition/partition.go 2015-07-15 07:53:43 +0000
+++ partition/partition.go 1970-01-01 00:00:00 +0000
@@ -1,622 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20// Package partition manipulate snappy disk partitions
21package partition
22
23import (
24 "errors"
25 "fmt"
26 "os"
27 "os/signal"
28 "path/filepath"
29 "regexp"
30 "strings"
31 "sync"
32 "syscall"
33
34 "launchpad.net/snappy/logger"
35)
36
37const (
38 // Name of writable user data partition label as created by
39 // ubuntu-device-flash(1).
40 writablePartitionLabel = "writable"
41
42 // Name of primary root filesystem partition label as created by
43 // ubuntu-device-flash(1).
44 rootfsAlabel = "system-a"
45
46 // Name of primary root filesystem partition label as created by
47 // ubuntu-device-flash(1). Note that this partition will
48 // only be present if this is an A/B upgrade system.
49 rootfsBlabel = "system-b"
50
51 // name of boot partition label as created by ubuntu-device-flash(1).
52 bootPartitionLabel = "system-boot"
53
54 // File creation mode used when any directories are created
55 dirMode = 0750
56)
57
58var (
59 // ErrBootloader is returned if the bootloader can not be determined
60 ErrBootloader = errors.New("Unable to determine bootloader")
61
62 // ErrPartitionDetection is returned if the partition type can not
63 // be detected
64 ErrPartitionDetection = errors.New("Failed to detect system type")
65
66 // ErrNoDualPartition is returned if you try to use a dual
67 // partition feature on a single partition
68 ErrNoDualPartition = errors.New("No dual partition")
69)
70
71// Interface provides the interface to interact with a partition
72type Interface interface {
73 ToggleNextBoot() error
74
75 MarkBootSuccessful() error
76 // FIXME: could we make SyncBootloaderFiles part of ToogleBootloader
77 // to expose even less implementation details?
78 SyncBootloaderFiles(bootAssets map[string]string) error
79 IsNextBootOther() bool
80
81 // run the function f with the otherRoot mounted
82 RunWithOther(rw MountOption, f func(otherRoot string) (err error)) (err error)
83}
84
85// Partition is the type to interact with the partition
86type Partition struct {
87 // all partitions
88 partitions []blockDevice
89
90 // just root partitions
91 roots []string
92}
93
94type blockDevice struct {
95 // label for partition
96 name string
97
98 // the last char of the partition label
99 shortName string
100
101 // full path to device on which partition exists
102 // (for example "/dev/sda3")
103 device string
104
105 // full path to disk device (for example "/dev/sda")
106 parentName string
107
108 // mountpoint (or nil if not mounted)
109 mountpoint string
110}
111
112var once sync.Once
113
114func init() {
115 once.Do(setupSignalHandler)
116}
117
118func signalHandler(sig os.Signal) {
119 err := undoMounts(false)
120 if err != nil {
121 logger.Noticef("Failed to unmount: %v", err)
122 }
123}
124
125func setupSignalHandler() {
126 ch := make(chan os.Signal, 1)
127
128 // add the signals we care about
129 signal.Notify(ch, os.Interrupt)
130 signal.Notify(ch, syscall.SIGTERM)
131
132 go func() {
133 // block waiting for a signal
134 sig := <-ch
135
136 // handle it
137 signalHandler(sig)
138 os.Exit(1)
139 }()
140}
141
142// Returns a list of root filesystem partition labels
143func rootPartitionLabels() []string {
144 return []string{rootfsAlabel, rootfsBlabel}
145}
146
147// Returns a list of all recognised partition labels
148func allPartitionLabels() []string {
149 var labels []string
150
151 labels = rootPartitionLabels()
152 labels = append(labels, bootPartitionLabel)
153 labels = append(labels, writablePartitionLabel)
154
155 return labels
156}
157
158var runLsblk = func() (out []string, err error) {
159 output, err := runCommandWithStdout(
160 "/bin/lsblk",
161 "--ascii",
162 "--output=NAME,LABEL,PKNAME,MOUNTPOINT",
163 "--pairs")
164 if err != nil {
165 return out, err
166 }
167
168 return strings.Split(output, "\n"), nil
169}
170
171// Determine details of the recognised disk partitions
172// available on the system via lsblk
173func loadPartitionDetails() (partitions []blockDevice, err error) {
174 recognised := allPartitionLabels()
175
176 lines, err := runLsblk()
177 if err != nil {
178 return partitions, err
179 }
180 pattern := regexp.MustCompile(`(?:[^\s"]|"(?:[^"])*")+`)
181
182 for _, line := range lines {
183 fields := make(map[string]string)
184
185 // split the line into 'NAME="quoted value"' fields
186 matches := pattern.FindAllString(line, -1)
187
188 for _, match := range matches {
189 tmp := strings.Split(match, "=")
190 name := tmp[0]
191
192 // remove quotes
193 value := strings.Trim(tmp[1], "\"")
194
195 // store
196 fields[name] = value
197 }
198
199 // Look for expected partition labels
200 name, ok := fields["LABEL"]
201 if !ok {
202 continue
203 }
204
205 if name == "" || name == "\"\"" {
206 continue
207 }
208
209 pos := stringInSlice(recognised, name)
210 if pos < 0 {
211 // ignore unrecognised partitions
212 continue
213 }
214
215 // reconstruct full path to disk partition device
216 device := fmt.Sprintf("/dev/%s", fields["NAME"])
217
218 // FIXME: we should have a way to mock the "/dev" dir
219 // or we skip this test lsblk never returns non-existing
220 // devices
221 /*
222 if err := FileExists(device); err != nil {
223 continue
224 }
225 */
226 // reconstruct full path to entire disk device
227 disk := fmt.Sprintf("/dev/%s", fields["PKNAME"])
228
229 // FIXME: we should have a way to mock the "/dev" dir
230 // or we skip this test lsblk never returns non-existing
231 // files
232 /*
233 if err := FileExists(disk); err != nil {
234 continue
235 }
236 */
237 shortName := string(name[len(name)-1])
238 bd := blockDevice{
239 name: fields["LABEL"],
240 shortName: shortName,
241 device: device,
242 mountpoint: fields["MOUNTPOINT"],
243 parentName: disk,
244 }
245
246 partitions = append(partitions, bd)
247 }
248
249 return partitions, nil
250}
251
252// New creates a new partition type
253func New() *Partition {
254 p := new(Partition)
255
256 p.getPartitionDetails()
257
258 return p
259}
260
261// RunWithOther mount the other rootfs partition, execute the
262// specified function and unmount "other" before returning. If "other"
263// is mounted read-write, /proc, /sys and /dev will also be
264// bind-mounted at the time the specified function is called.
265func (p *Partition) RunWithOther(option MountOption, f func(otherRoot string) (err error)) (err error) {
266 dual := p.dualRootPartitions()
267
268 if !dual {
269 return ErrNoDualPartition
270 }
271
272 if option == RW {
273 if err := p.remountOther(RW); err != nil {
274 return err
275 }
276
277 defer func() {
278 // we can't reuse err here as this will override
279 // the error value we got from calling "f()"
280 derr := p.remountOther(RO)
281 if derr != nil && err == nil {
282 err = derr
283 }
284 }()
285
286 if err := p.bindmountRequiredFilesystems(); err != nil {
287 return err
288 }
289
290 defer func() {
291 // we can't reuse err here as this will override
292 // the error value we got from calling "f()"
293 derr := p.unmountRequiredFilesystems()
294 if derr != nil && err == nil {
295 err = derr
296 }
297 }()
298 }
299
300 err = f(mountTarget)
301 return err
302}
303
304// SyncBootloaderFiles syncs the bootloader files
305//
306// We need this code solve the following scenario:
307//
308// 1. start with: /boot/a/k1, /boot/b/k1
309// 2. upgrade with new kernel k2: /boot/a/k1, /boot/b/k2
310// 3. reboot into b, running with /boot/b/k2
311// 4. new update without a changed kernel, system-a updated
312//
313// 6. reboot to system-a with /boot/a/k1 (WRONG!)
314// But it should be system-a with /boot/a/k2 it was just not part of
315// the s-i delta as we already got it. So as step (5) above we do the
316// SyncBootloaderFiles that copies /boot/b/k2 -> /boot/a/
317// (and that is ok because we know /boot/b/k2 works)
318//
319func (p *Partition) SyncBootloaderFiles(bootAssets map[string]string) (err error) {
320 bootloader, err := bootloader(p)
321 if err != nil {
322 return err
323 }
324
325 return bootloader.SyncBootFiles(bootAssets)
326}
327
328// ToggleNextBoot toggles the roofs that should be used on the next boot
329func (p *Partition) ToggleNextBoot() (err error) {
330 if p.dualRootPartitions() {
331 return p.toggleBootloaderRootfs()
332 }
333 return err
334}
335
336// MarkBootSuccessful marks the boot as successful
337func (p *Partition) MarkBootSuccessful() (err error) {
338 bootloader, err := bootloader(p)
339 if err != nil {
340 return err
341 }
342
343 currentRootfs := p.rootPartition().shortName
344 return bootloader.MarkCurrentBootSuccessful(currentRootfs)
345}
346
347// IsNextBootOther return true if the next boot will use the other rootfs
348// partition.
349func (p *Partition) IsNextBootOther() bool {
350 bootloader, err := bootloader(p)
351 if err != nil {
352 return false
353 }
354
355 value, err := bootloader.GetBootVar(bootloaderBootmodeVar)
356 if err != nil {
357 return false
358 }
359
360 if value != bootloaderBootmodeTry {
361 return false
362 }
363
364 fsname, err := bootloader.GetNextBootRootFSName()
365 if err != nil {
366 return false
367 }
368
369 otherRootfs := p.otherRootPartition().shortName
370 if fsname == otherRootfs {
371 return true
372 }
373
374 return false
375}
376
377func (p *Partition) getPartitionDetails() (err error) {
378 p.partitions, err = loadPartitionDetails()
379 if err != nil {
380 return err
381 }
382
383 if !p.dualRootPartitions() && !p.singleRootPartition() {
384 return ErrPartitionDetection
385 }
386
387 if p.dualRootPartitions() {
388 // XXX: this will soon be handled automatically at boot by
389 // initramfs-tools-ubuntu-core.
390 return p.ensureOtherMountedRO()
391 }
392
393 return err
394}
395
396// Return array of blockDevices representing available root partitions
397func (p *Partition) rootPartitions() (roots []blockDevice) {
398 for _, part := range p.partitions {
399 pos := stringInSlice(rootPartitionLabels(), part.name)
400 if pos >= 0 {
401 roots = append(roots, part)
402 }
403 }
404
405 return roots
406}
407
408// Return true if system has dual root partitions configured in the
409// expected manner for a snappy system.
410func (p *Partition) dualRootPartitions() bool {
411 return len(p.rootPartitions()) == 2
412}
413
414// Return true if system has a single root partition configured in the
415// expected manner for a snappy system.
416func (p *Partition) singleRootPartition() bool {
417 return len(p.rootPartitions()) == 1
418}
419
420// Return pointer to blockDevice representing writable partition
421func (p *Partition) writablePartition() (result *blockDevice) {
422 for _, part := range p.partitions {
423 if part.name == writablePartitionLabel {
424 return &part
425 }
426 }
427
428 return nil
429}
430
431// Return pointer to blockDevice representing boot partition (if any)
432func (p *Partition) bootPartition() (result *blockDevice) {
433 for _, part := range p.partitions {
434 if part.name == bootPartitionLabel {
435 return &part
436 }
437 }
438
439 return nil
440}
441
442// Return pointer to blockDevice representing currently mounted root
443// filesystem
444func (p *Partition) rootPartition() (result *blockDevice) {
445 for _, part := range p.rootPartitions() {
446 if part.mountpoint == "/" {
447 return &part
448 }
449 }
450
451 return nil
452}
453
454// Return pointer to blockDevice representing the "other" root
455// filesystem (which is not currently mounted)
456func (p *Partition) otherRootPartition() (result *blockDevice) {
457 for _, part := range p.rootPartitions() {
458 if part.mountpoint != "/" {
459 return &part
460 }
461 }
462
463 return nil
464}
465
466// Mount the "other" root filesystem
467func (p *Partition) mountOtherRootfs(readOnly bool) (err error) {
468 var other *blockDevice
469
470 if err := os.MkdirAll(mountTarget, dirMode); err != nil {
471 return err
472 }
473
474 other = p.otherRootPartition()
475
476 m := mountEntry{source: other.device, target: mountTarget}
477
478 if readOnly {
479 m.options = "ro"
480 err = mountAndAddToGlobalMountList(m)
481 } else {
482 err = fsck(m.source)
483 if err != nil {
484 return err
485 }
486 err = mountAndAddToGlobalMountList(m)
487 }
488
489 return err
490}
491
492// Create a read-only bindmount of the currently-mounted rootfs at the
493// specified mountpoint location (which must already exist).
494func (p *Partition) bindmountThisRootfsRO(target string) (err error) {
495 return mountAndAddToGlobalMountList(mountEntry{source: "/",
496 target: target,
497 options: "bind,ro",
498 bindMount: true})
499}
500
501// Ensure the other partition is mounted read-only.
502func (p *Partition) ensureOtherMountedRO() (err error) {
503 if err = runCommand("/bin/mountpoint", mountTarget); err == nil {
504 // already mounted
505 return err
506 }
507
508 return p.mountOtherRootfs(true)
509}
510
511// Remount the already-mounted other partition. Whether the mount
512// should become writable is specified by the writable argument.
513//
514// XXX: Note that in the case where writable=true, this isn't a simple
515// toggle - if the partition is already mounted read-only, it needs to
516// be unmounted, fsck(8)'d, then (re-)mounted read-write.
517func (p *Partition) remountOther(option MountOption) (err error) {
518 other := p.otherRootPartition()
519
520 if option == RW {
521 // r/o -> r/w: initially r/o, so no need to fsck before
522 // switching to r/w.
523 err = p.unmountOtherRootfs()
524 if err != nil {
525 return err
526 }
527
528 err = fsck(other.device)
529 if err != nil {
530 return err
531 }
532
533 return mountAndAddToGlobalMountList(mountEntry{
534 source: other.device,
535 target: mountTarget})
536 }
537 // r/w -> r/o: no fsck required.
538 return mount(other.device, mountTarget, "remount,ro")
539}
540
541func (p *Partition) unmountOtherRootfs() (err error) {
542 return unmountAndRemoveFromGlobalMountList(mountTarget)
543}
544
545// The bootloader requires a few filesystems to be mounted when
546// run from within a chroot.
547func (p *Partition) bindmountRequiredFilesystems() (err error) {
548
549 // we always requires these
550 requiredChrootMounts := []string{"/dev", "/proc", "/sys"}
551
552 // if there is a boot partition we also bind-mount it
553 boot := p.bootPartition()
554 if boot != nil && boot.mountpoint != "" {
555 requiredChrootMounts = append(requiredChrootMounts, boot.mountpoint)
556 }
557
558 for _, fs := range requiredChrootMounts {
559 target := filepath.Join(mountTarget, fs)
560
561 err := mountAndAddToGlobalMountList(mountEntry{source: fs,
562 target: target,
563 options: "bind",
564 bindMount: true})
565 if err != nil {
566 return err
567 }
568 }
569
570 // Grub also requires access to both rootfs's when run from
571 // within a chroot (to allow it to create menu entries for
572 // both), so bindmount the real rootfs.
573 targetInChroot := filepath.Join(mountTarget, mountTarget)
574
575 // FIXME: we should really remove this after the unmount
576
577 if err = os.MkdirAll(targetInChroot, dirMode); err != nil {
578 return err
579 }
580
581 return p.bindmountThisRootfsRO(targetInChroot)
582}
583
584// Undo the effects of BindmountRequiredFilesystems()
585func (p *Partition) unmountRequiredFilesystems() (err error) {
586 if err = undoMounts(true); err != nil {
587 return err
588 }
589
590 return nil
591}
592
593func (p *Partition) toggleBootloaderRootfs() (err error) {
594
595 if !p.dualRootPartitions() {
596 return errors.New("System is not dual root")
597 }
598
599 bootloader, err := bootloader(p)
600 if err != nil {
601 return err
602 }
603
604 // ensure we have updated kernels etc
605 if err := bootloader.HandleAssets(); err != nil {
606 return err
607 }
608
609 otherRootfs := p.otherRootPartition().shortName
610 return bootloader.ToggleRootFS(otherRootfs)
611}
612
613// BootloaderDir returns the full path to the (mounted and writable)
614// bootloader-specific boot directory.
615func (p *Partition) BootloaderDir() string {
616 bootloader, err := bootloader(p)
617 if err != nil {
618 return ""
619 }
620
621 return bootloader.BootDir()
622}
6230
=== removed file 'partition/utils.go'
--- partition/utils.go 2015-07-08 10:21:02 +0000
+++ partition/utils.go 1970-01-01 00:00:00 +0000
@@ -1,82 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package partition
21
22import (
23 "errors"
24 "fmt"
25 "os/exec"
26 "strings"
27)
28
29// FIXME: would it make sense to differenciate between launch errors and
30// exit code? (i.e. something like (returnCode, error) ?)
31func runCommandImpl(args ...string) (err error) {
32 if len(args) == 0 {
33 return errors.New("no command specified")
34 }
35
36 if out, err := exec.Command(args[0], args[1:]...).CombinedOutput(); err != nil {
37 cmdline := strings.Join(args, " ")
38 return fmt.Errorf("Failed to run command '%s': %s (%s)",
39 cmdline, out, err)
40 }
41 return nil
42}
43
44// Run the command specified by args
45// This is a var instead of a function to making mocking in the tests easier
46var runCommand = runCommandImpl
47
48// Run command specified by args and return the output
49func runCommandWithStdoutImpl(args ...string) (output string, err error) {
50 if len(args) == 0 {
51 return "", errors.New("no command specified")
52 }
53
54 bytes, err := exec.Command(args[0], args[1:]...).Output()
55 if err != nil {
56 return "", err
57 }
58
59 return string(bytes), err
60}
61
62// This is a var instead of a function to making mocking in the tests easier
63var runCommandWithStdout = runCommandWithStdoutImpl
64
65// Run fsck(8) on specified device.
66func fsck(device string) (err error) {
67 return runCommand(
68 "/sbin/fsck",
69 "-M", // Paranoia - don't fsck if already mounted
70 "-av", device)
71}
72
73// Returns the position of the string in the given slice or -1 if its not found
74func stringInSlice(slice []string, value string) int {
75 for i, s := range slice {
76 if s == value {
77 return i
78 }
79 }
80
81 return -1
82}
830
=== removed file 'partition/utils_test.go'
--- partition/utils_test.go 2015-06-02 20:46:07 +0000
+++ partition/utils_test.go 1970-01-01 00:00:00 +0000
@@ -1,47 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package partition
21
22import (
23 . "gopkg.in/check.v1"
24)
25
26type UtilsTestSuite struct {
27}
28
29var _ = Suite(&UtilsTestSuite{})
30
31func (s *UtilsTestSuite) SetUpTest(c *C) {
32}
33
34func (s *UtilsTestSuite) TestRunCommand(c *C) {
35 err := runCommandImpl("false")
36 c.Assert(err, NotNil)
37
38 err = runCommandImpl("no-such-command")
39 c.Assert(err, NotNil)
40}
41
42func (s *UtilsTestSuite) TestRunCommandWithStdout(c *C) {
43 runCommandWithStdout = runCommandWithStdoutImpl
44 output, err := runCommandWithStdout("sh", "-c", "printf 'foo\nbar'")
45 c.Assert(err, IsNil)
46 c.Assert(output, DeepEquals, "foo\nbar")
47}
480
=== modified file 'pkg/types.go'
--- pkg/types.go 2015-05-19 19:34:13 +0000
+++ pkg/types.go 2015-09-03 08:14:56 +0000
@@ -34,6 +34,8 @@
34 TypeCore Type = "core"34 TypeCore Type = "core"
35 TypeFramework Type = "framework"35 TypeFramework Type = "framework"
36 TypeOem Type = "oem"36 TypeOem Type = "oem"
37 TypeOS Type = "os"
38 TypeKernel Type = "kernel"
37)39)
3840
39// MarshalJSON returns *m as the JSON encoding of m.41// MarshalJSON returns *m as the JSON encoding of m.
4042
=== modified file 'snappy/click.go'
--- snappy/click.go 2015-08-19 15:21:16 +0000
+++ snappy/click.go 2015-09-03 08:14:56 +0000
@@ -718,7 +718,8 @@
718 if err != nil {718 if err != nil {
719 return "", err719 return "", err
720 }720 }
721 defer part.deb.Close()721 // FIXME: NewSnapPartFromSnapFile is ugly that it needs this close
722 defer part.debClose()
722723
723 return part.Install(inter, flags)724 return part.Install(inter, flags)
724}725}
725726
=== modified file 'snappy/dirs.go'
--- snappy/dirs.go 2015-06-30 12:36:00 +0000
+++ snappy/dirs.go 2015-09-03 08:14:56 +0000
@@ -27,6 +27,8 @@
2727
28 snapAppsDir string28 snapAppsDir string
29 snapOemDir string29 snapOemDir string
30 snapOsDir string
31 snapKernelDir string
30 snapDataDir string32 snapDataDir string
31 snapDataHomeGlob string33 snapDataHomeGlob string
32 snapAppArmorDir string34 snapAppArmorDir string
@@ -53,6 +55,8 @@
5355
54 snapAppsDir = filepath.Join(rootdir, "/apps")56 snapAppsDir = filepath.Join(rootdir, "/apps")
55 snapOemDir = filepath.Join(rootdir, "/oem")57 snapOemDir = filepath.Join(rootdir, "/oem")
58 snapOsDir = filepath.Join(rootdir, "/os")
59 snapKernelDir = filepath.Join(rootdir, "/kernel")
56 snapDataDir = filepath.Join(rootdir, "/var/lib/apps")60 snapDataDir = filepath.Join(rootdir, "/var/lib/apps")
57 snapDataHomeGlob = filepath.Join(rootdir, "/home/*/apps/")61 snapDataHomeGlob = filepath.Join(rootdir, "/home/*/apps/")
58 snapAppArmorDir = filepath.Join(rootdir, "/var/lib/apparmor/clicks")62 snapAppArmorDir = filepath.Join(rootdir, "/var/lib/apparmor/clicks")
5963
=== modified file 'snappy/install.go'
--- snappy/install.go 2015-06-11 09:38:18 +0000
+++ snappy/install.go 2015-09-03 08:14:56 +0000
@@ -24,8 +24,8 @@
24 "os"24 "os"
25 "sort"25 "sort"
2626
27 "launchpad.net/snappy/bootloader"
27 "launchpad.net/snappy/logger"28 "launchpad.net/snappy/logger"
28 "launchpad.net/snappy/partition"
29 "launchpad.net/snappy/progress"29 "launchpad.net/snappy/progress"
30 "launchpad.net/snappy/provisioning"30 "launchpad.net/snappy/provisioning"
31)31)
@@ -97,7 +97,7 @@
97 // FIXME: this is terrible, we really need a single97 // FIXME: this is terrible, we really need a single
98 // bootloader dir like /boot or /boot/loader98 // bootloader dir like /boot or /boot/loader
99 // instead of having to query the partition code99 // instead of having to query the partition code
100 if provisioning.InDeveloperMode(partition.BootloaderDir()) {100 if provisioning.InDeveloperMode(bootloader.Dir()) {
101 flags |= AllowUnauthenticated101 flags |= AllowUnauthenticated
102 }102 }
103103
104104
=== modified file 'snappy/install_test.go'
--- snappy/install_test.go 2015-06-29 20:21:38 +0000
+++ snappy/install_test.go 2015-09-03 08:14:56 +0000
@@ -30,7 +30,6 @@
30 "path/filepath"30 "path/filepath"
3131
32 . "gopkg.in/check.v1"32 . "gopkg.in/check.v1"
33 "launchpad.net/snappy/partition"
34 "launchpad.net/snappy/progress"33 "launchpad.net/snappy/progress"
35)34)
3635
@@ -172,94 +171,3 @@
172 _, err = Install("hello-app.potato", 0, ag)171 _, err = Install("hello-app.potato", 0, ag)
173 c.Assert(err, ErrorMatches, ".*"+ErrPackageNameAlreadyInstalled.Error())172 c.Assert(err, ErrorMatches, ".*"+ErrPackageNameAlreadyInstalled.Error())
174}173}
175
176func (s *SnapTestSuite) TestUpdate(c *C) {
177 snapPackagev1 := makeTestSnapPackage(c, "name: foo\nversion: 1\nvendor: foo")
178 name, err := Install(snapPackagev1, AllowUnauthenticated|DoInstallGC, &progress.NullProgress{})
179 c.Assert(err, IsNil)
180 c.Assert(name, Equals, "foo")
181
182 snapPackagev2 := makeTestSnapPackage(c, "name: foo\nversion: 2\nvendor: foo")
183
184 snapR, err := os.Open(snapPackagev2)
185 c.Assert(err, IsNil)
186 defer snapR.Close()
187
188 // details
189 var dlURL, iconURL string
190 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
191 switch r.URL.Path {
192 case "/details/foo":
193 io.WriteString(w, `{
194"package_name": "foo",
195"version": "2",
196"origin": "sideload",
197"anon_download_url": "`+dlURL+`",
198"icon_url": "`+iconURL+`"
199}`)
200 case "/dl":
201 snapR.Seek(0, 0)
202 io.Copy(w, snapR)
203 case "/icon":
204 fmt.Fprintf(w, "")
205 default:
206 panic("unexpected url path: " + r.URL.Path)
207 }
208 }))
209 c.Assert(mockServer, NotNil)
210 defer mockServer.Close()
211
212 dlURL = mockServer.URL + "/dl"
213 iconURL = mockServer.URL + "/icon"
214
215 storeDetailsURI, err = url.Parse(mockServer.URL + "/details/")
216 c.Assert(err, IsNil)
217
218 // bulk
219 mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
220 io.WriteString(w, `[{
221 "package_name": "foo",
222 "version": "2",
223 "origin": "sideload",
224 "anon_download_url": "`+dlURL+`",
225 "icon_url": "`+iconURL+`"
226}]`)
227 }))
228
229 storeBulkURI, err = url.Parse(mockServer.URL)
230 c.Assert(err, IsNil)
231
232 c.Assert(mockServer, NotNil)
233 defer mockServer.Close()
234
235 // system image
236 newPartition = func() (p partition.Interface) {
237 return new(MockPartition)
238 }
239 defer func() { newPartition = newPartitionImpl }()
240
241 tempdir := c.MkDir()
242 systemImageRoot = tempdir
243
244 makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, systemImageChannelConfig), "1")
245 // setup fake /other partition
246 makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, "other", systemImageChannelConfig), "2")
247
248 siServer := runMockSystemImageWebServer()
249 defer siServer.Close()
250
251 mockServer = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
252 io.WriteString(w, fmt.Sprintf(mockSystemImageIndexJSONTemplate, "1"))
253 }))
254 c.Assert(mockServer, NotNil)
255 defer mockServer.Close()
256
257 systemImageServer = mockServer.URL
258
259 // the test
260 updates, err := Update(0, &progress.NullProgress{})
261 c.Assert(err, IsNil)
262 c.Assert(updates, HasLen, 1)
263 c.Check(updates[0].Name(), Equals, "foo")
264 c.Check(updates[0].Version(), Equals, "2")
265}
266174
=== added file 'snappy/kernel.go'
--- snappy/kernel.go 1970-01-01 00:00:00 +0000
+++ snappy/kernel.go 2015-09-03 08:14:56 +0000
@@ -0,0 +1,103 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package snappy
21
22import (
23 "os"
24 "path/filepath"
25
26 "launchpad.net/snappy/bootloader"
27 "launchpad.net/snappy/helpers"
28)
29
30type KernelSnap struct {
31 SnapPart
32}
33
34func (s *KernelSnap) NeedsReboot() bool {
35 snappyKernel, _ := bootloader.GetBootVar("snappy_kernel")
36 if !s.IsActive() && snappyKernel == s.Version() {
37 return true
38 }
39
40 return false
41}
42
43/*
44func (s *KernelSnap) dir() string {
45 fullName := s.SnapPart.m.qualifiedName(s.origin)
46 return filepath.Join(snapKernelDir, fullName, s.m.Version)
47}
48*/
49
50func (s *KernelSnap) activate(inhibitHooks bool, inter interacter) error {
51 if err := s.SnapPart.activate(inhibitHooks, inter); err != nil {
52 return err
53 }
54
55 if err := bootloader.SetBootVar("snappy_kernel", s.Version()); err != nil {
56 return err
57 }
58
59 return bootloader.SetBootVar("snappy_mode", "try")
60}
61
62func unpackKernel(s *SnapPart) error {
63 // FIXME: do we need the globalRootDir here?
64 if err := s.deb.Unpack(s.basedir); err != nil {
65 return err
66 }
67
68 // FIXME: create special unpack that diverts kernel/initrd/dtbs
69 // to the right place, this will currently copy the files
70 // (duplicate data)
71 bootdir := bootloader.Dir()
72 if err := os.MkdirAll(filepath.Join(bootdir, s.Version()), 0755); err != nil {
73 return err
74 }
75 if s.m.Kernel != "" {
76 src := filepath.Join(s.basedir, s.m.Kernel)
77 dst := filepath.Join(bootdir, s.Version(), normalizeKernelInitrdName(s.m.Kernel))
78 if err := helpers.CopyFile(src, dst, helpers.CopyFlagDefault); err != nil {
79 return err
80 }
81 }
82 if s.m.Initrd != "" {
83 src := filepath.Join(s.basedir, s.m.Initrd)
84 dst := filepath.Join(bootdir, s.Version(), normalizeKernelInitrdName(s.m.Initrd))
85 if err := helpers.CopyFile(src, dst, helpers.CopyFlagDefault); err != nil {
86 return err
87 }
88 }
89 if s.m.Dtbs != "" {
90 src := filepath.Join(s.basedir, s.m.Dtbs)
91 dst := filepath.Join(bootdir, s.Version(), s.m.Dtbs)
92 // FIXME: a simple "cp -a" is what we need here
93 if err := helpers.RSyncWithDelete(src, dst); err != nil {
94 return err
95 }
96 }
97
98 if err := bootloader.SetBootVar("snappy_kernel", s.Version()); err != nil {
99 return err
100 }
101
102 return bootloader.SetBootVar("snappy_mode", "trial")
103}
0104
=== modified file 'snappy/oem.go'
--- snappy/oem.go 2015-07-01 14:48:33 +0000
+++ snappy/oem.go 2015-09-03 08:14:56 +0000
@@ -291,3 +291,14 @@
291291
292 return nil292 return nil
293}293}
294
295type OemSnap struct {
296 SnapPart
297}
298
299/*
300func (s *OemSnap) dir() string {
301 fullName := s.SnapPart.m.qualifiedName(s.origin)
302 return filepath.Join(snapOemDir, fullName, s.m.Version)
303}
304*/
294305
=== added file 'snappy/os.go'
--- snappy/os.go 1970-01-01 00:00:00 +0000
+++ snappy/os.go 2015-09-03 08:14:56 +0000
@@ -0,0 +1,68 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package snappy
21
22import (
23 "launchpad.net/snappy/bootloader"
24)
25
26type OsSnap struct {
27 SnapPart
28}
29
30func (s *OsSnap) NeedsReboot() bool {
31 snappyOS, _ := bootloader.GetBootVar("snappy_os")
32 if !s.IsActive() && snappyOS == s.Version() {
33 return true
34 }
35
36 return false
37}
38
39/*
40func (s *OsSnap) dir() string {
41 fullName := s.SnapPart.m.qualifiedName(s.origin)
42 return filepath.Join(snapOsDir, fullName, s.m.Version)
43}
44*/
45
46func (s *OsSnap) activate(inhibitHooks bool, inter interacter) error {
47 if err := s.SnapPart.activate(inhibitHooks, inter); err != nil {
48 return err
49 }
50 if err := bootloader.SetBootVar("snappy_os", s.Version()); err != nil {
51 return err
52 }
53
54 return bootloader.SetBootVar("snappy_mode", "try")
55}
56
57func unpackOS(s *SnapPart) error {
58 // FIXME: do we need the globalRootDir here?
59 if err := s.deb.Unpack(s.basedir); err != nil {
60 return err
61 }
62
63 if err := bootloader.SetBootVar("snappy_os", s.Version()); err != nil {
64 return err
65 }
66
67 return bootloader.SetBootVar("snappy_mode", "trial")
68}
069
=== modified file 'snappy/parts.go'
--- snappy/parts.go 2015-07-23 11:05:37 +0000
+++ snappy/parts.go 2015-09-03 08:14:56 +0000
@@ -134,14 +134,10 @@
134 m := new(MetaRepository)134 m := new(MetaRepository)
135 m.all = []Repository{}135 m.all = []Repository{}
136136
137 if repo := NewSystemImageRepository(); repo != nil {137 for _, d := range []string{snapAppsDir, snapOemDir, snapOsDir, snapKernelDir} {
138 m.all = append(m.all, repo)138 if repo := NewLocalSnapRepository(d); repo != nil {
139 }139 m.all = append(m.all, repo)
140 if repo := NewLocalSnapRepository(snapAppsDir); repo != nil {140 }
141 m.all = append(m.all, repo)
142 }
143 if repo := NewLocalSnapRepository(snapOemDir); repo != nil {
144 m.all = append(m.all, repo)
145 }141 }
146142
147 return m143 return m
148144
=== modified file 'snappy/purge.go'
--- snappy/purge.go 2015-05-29 12:08:46 +0000
+++ snappy/purge.go 2015-09-03 08:14:56 +0000
@@ -47,7 +47,7 @@
4747
48 purgeActive := flags&DoPurgeActive != 048 purgeActive := flags&DoPurgeActive != 0
4949
50 var active []*SnapPart50 var active []Snap
5151
52 for _, datadir := range datadirs {52 for _, datadir := range datadirs {
53 yamlPath := filepath.Join(snapAppsDir, datadir.QualifiedName(), datadir.Version, "meta", "package.yaml")53 yamlPath := filepath.Join(snapAppsDir, datadir.QualifiedName(), datadir.Version, "meta", "package.yaml")
5454
=== modified file 'snappy/purge_test.go'
--- snappy/purge_test.go 2015-06-09 19:11:54 +0000
+++ snappy/purge_test.go 2015-09-03 08:14:56 +0000
@@ -57,7 +57,7 @@
57 c.Check(inter.notified, HasLen, 0)57 c.Check(inter.notified, HasLen, 0)
58}58}
5959
60func (s *purgeSuite) mkpkg(c *C, args ...string) (dataDir string, part *SnapPart) {60func (s *purgeSuite) mkpkg(c *C, args ...string) (dataDir string, part Snap) {
61 version := "1.10"61 version := "1.10"
62 extra := ""62 extra := ""
63 switch len(args) {63 switch len(args) {
6464
=== modified file 'snappy/snapp.go'
--- snappy/snapp.go 2015-08-19 15:21:16 +0000
+++ snappy/snapp.go 2015-09-03 08:14:56 +0000
@@ -38,6 +38,7 @@
3838
39 "gopkg.in/yaml.v2"39 "gopkg.in/yaml.v2"
4040
41 "launchpad.net/snappy/bootloader"
41 "launchpad.net/snappy/clickdeb"42 "launchpad.net/snappy/clickdeb"
42 "launchpad.net/snappy/helpers"43 "launchpad.net/snappy/helpers"
43 "launchpad.net/snappy/logger"44 "launchpad.net/snappy/logger"
@@ -198,6 +199,16 @@
198 return nil199 return nil
199}200}
200201
202type Snap interface {
203 Part
204 activate(inhibitHooks bool, inter interacter) error
205 deactivate(inhibitHooks bool, inter interacter) error
206 remove(inter interacter) error
207 dir() string
208 ServiceYamls() []ServiceYaml
209 debClose() error
210}
211
201// TODO split into payloads per package type composing the common212// TODO split into payloads per package type composing the common
202// elements for all snaps.213// elements for all snaps.
203type packageYaml struct {214type packageYaml struct {
@@ -228,6 +239,11 @@
228239
229 ExplicitLicenseAgreement bool `yaml:"explicit-license-agreement,omitempty"`240 ExplicitLicenseAgreement bool `yaml:"explicit-license-agreement,omitempty"`
230 LicenseVersion string `yaml:"license-version,omitempty"`241 LicenseVersion string `yaml:"license-version,omitempty"`
242
243 // FIXME: move into a special kernel struct
244 Kernel string `yaml:"kernel,omitempty"`
245 Initrd string `yaml:"initrd,omitempty"`
246 Dtbs string `yaml:"dtbs,omitempty"`
231}247}
232248
233type remoteSnap struct {249type remoteSnap struct {
@@ -362,6 +378,12 @@
362 return m.Name + "." + origin378 return m.Name + "." + origin
363}379}
364380
381// noramlizeAssetName transforms like "vmlinuz-4.1.0" -> "vmlinuz"
382func normalizeKernelInitrdName(name string) string {
383 name = filepath.Base(name)
384 return strings.SplitN(name, "-", 2)[0]
385}
386
365func (m *packageYaml) checkForNameClashes() error {387func (m *packageYaml) checkForNameClashes() error {
366 d := make(map[string]struct{})388 d := make(map[string]struct{})
367 for _, bin := range m.Binaries {389 for _, bin := range m.Binaries {
@@ -528,7 +550,7 @@
528}550}
529551
530// NewInstalledSnapPart returns a new SnapPart from the given yamlPath552// NewInstalledSnapPart returns a new SnapPart from the given yamlPath
531func NewInstalledSnapPart(yamlPath, origin string) (*SnapPart, error) {553func NewInstalledSnapPart(yamlPath, origin string) (Snap, error) {
532 m, err := parsePackageYamlFile(yamlPath)554 m, err := parsePackageYamlFile(yamlPath)
533 if err != nil {555 if err != nil {
534 return nil, err556 return nil, err
@@ -546,7 +568,7 @@
546// NewSnapPartFromSnapFile loads a snap from the given (clickdeb) snap file.568// NewSnapPartFromSnapFile loads a snap from the given (clickdeb) snap file.
547// Caller should call Close on the clickdeb.569// Caller should call Close on the clickdeb.
548// TODO: expose that Close.570// TODO: expose that Close.
549func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (*SnapPart, error) {571func NewSnapPartFromSnapFile(snapFile string, origin string, unauthOk bool) (Snap, error) {
550 if err := clickdeb.Verify(snapFile, unauthOk); err != nil {572 if err := clickdeb.Verify(snapFile, unauthOk); err != nil {
551 return nil, err573 return nil, err
552 }574 }
@@ -566,10 +588,15 @@
566 return nil, err588 return nil, err
567 }589 }
568590
591 // FIMXE: move all into {App,Kernel,Os}Snap.dir()
569 targetDir := snapAppsDir592 targetDir := snapAppsDir
570 // the "oem" parts are special593 switch m.Type {
571 if m.Type == pkg.TypeOem {594 case pkg.TypeOem:
572 targetDir = snapOemDir595 targetDir = snapOemDir
596 case pkg.TypeOS:
597 targetDir = snapOsDir
598 case pkg.TypeKernel:
599 targetDir = snapKernelDir
573 }600 }
574601
575 if origin == sideloadedOrigin {602 if origin == sideloadedOrigin {
@@ -579,12 +606,22 @@
579 fullName := m.qualifiedName(origin)606 fullName := m.qualifiedName(origin)
580 instDir := filepath.Join(targetDir, fullName, m.Version)607 instDir := filepath.Join(targetDir, fullName, m.Version)
581608
582 return &SnapPart{609 baseSnap := &SnapPart{
583 basedir: instDir,610 basedir: instDir,
584 origin: origin,611 origin: origin,
585 m: m,612 m: m,
586 deb: d,613 deb: d,
587 }, nil614 }
615 switch m.Type {
616 case pkg.TypeOem:
617 return &OemSnap{SnapPart: *baseSnap}, nil
618 case pkg.TypeOS:
619 return &OsSnap{SnapPart: *baseSnap}, nil
620 case pkg.TypeKernel:
621 return &KernelSnap{SnapPart: *baseSnap}, nil
622 }
623
624 return baseSnap, nil
588}625}
589626
590// NewSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath627// NewSnapPartFromYaml returns a new SnapPart from the given *packageYaml at yamlPath
@@ -650,6 +687,10 @@
650 return part, nil687 return part, nil
651}688}
652689
690func (s *SnapPart) dir() string {
691 return s.basedir
692}
693
653// Type returns the type of the SnapPart (app, oem, ...)694// Type returns the type of the SnapPart (app, oem, ...)
654func (s *SnapPart) Type() pkg.Type {695func (s *SnapPart) Type() pkg.Type {
655 if s.m.Type != "" {696 if s.m.Type != "" {
@@ -665,6 +706,11 @@
665 return s.m.Name706 return s.m.Name
666}707}
667708
709// FIXME: super ugly
710func (s *SnapPart) debClose() error {
711 return s.deb.Close()
712}
713
668// Version returns the version714// Version returns the version
669func (s *SnapPart) Version() string {715func (s *SnapPart) Version() string {
670 if s.basedir != "" {716 if s.basedir != "" {
@@ -802,7 +848,7 @@
802 fullName := QualifiedName(s)848 fullName := QualifiedName(s)
803 dataDir := filepath.Join(snapDataDir, fullName, s.Version())849 dataDir := filepath.Join(snapDataDir, fullName, s.Version())
804850
805 var oldPart *SnapPart851 var oldPart Snap
806 if currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current")); currentActiveDir != "" {852 if currentActiveDir, _ := filepath.EvalSymlinks(filepath.Join(s.basedir, "..", "current")); currentActiveDir != "" {
807 oldPart, err = NewInstalledSnapPart(filepath.Join(currentActiveDir, "meta", "package.yaml"), s.origin)853 oldPart, err = NewInstalledSnapPart(filepath.Join(currentActiveDir, "meta", "package.yaml"), s.origin)
808 if err != nil {854 if err != nil {
@@ -824,10 +870,21 @@
824 }870 }
825 }()871 }()
826872
827 // we need to call the external helper so that we can reliable drop873 // FIXME: so ugly, what we need is
828 // privs874 // "dynamic dispatch of overridden methods" which go makes hard
829 if err := s.deb.UnpackWithDropPrivs(s.basedir, globalRootDir); err != nil {875 switch s.Type() {
830 return "", err876 case pkg.TypeOS:
877 if err := unpackOS(s); err != nil {
878 return "", err
879 }
880 case pkg.TypeKernel:
881 if err := unpackKernel(s); err != nil {
882 return "", err
883 }
884 default:
885 if err := s.deb.UnpackWithDropPrivs(s.basedir, globalRootDir); err != nil {
886 return "", err
887 }
831 }888 }
832889
833 // legacy, the hooks (e.g. apparmor) need this. Once we converted890 // legacy, the hooks (e.g. apparmor) need this. Once we converted
@@ -1128,6 +1185,14 @@
11281185
1129// NeedsReboot returns true if the snap becomes active on the next reboot1186// NeedsReboot returns true if the snap becomes active on the next reboot
1130func (s *SnapPart) NeedsReboot() bool {1187func (s *SnapPart) NeedsReboot() bool {
1188 // FIXME: needs to become SnapKernel
1189 if s.m.Type == pkg.TypeKernel {
1190 snappyKernel, _ := bootloader.GetBootVar("snappy_kernel")
1191 if !s.IsActive() && snappyKernel == s.Version() {
1192 return true
1193 }
1194 }
1195
1131 return false1196 return false
1132}1197}
11331198
@@ -1266,10 +1331,10 @@
1266}1331}
12671332
1268// RefreshDependentsSecurity refreshes the security policies of dependent snaps1333// RefreshDependentsSecurity refreshes the security policies of dependent snaps
1269func (s *SnapPart) RefreshDependentsSecurity(oldPart *SnapPart, inter interacter) (err error) {1334func (s *SnapPart) RefreshDependentsSecurity(oldPart Snap, inter interacter) (err error) {
1270 oldBaseDir := ""1335 oldBaseDir := ""
1271 if oldPart != nil {1336 if oldPart != nil {
1272 oldBaseDir = oldPart.basedir1337 oldBaseDir = oldPart.(*SnapPart).basedir
1273 }1338 }
1274 upPol, upTpl := policy.AppArmorDelta(oldBaseDir, s.basedir, s.Name()+"_")1339 upPol, upTpl := policy.AppArmorDelta(oldBaseDir, s.basedir, s.Name()+"_")
12751340
@@ -1878,7 +1943,7 @@
1878// The returned environment contains additional SNAP_* variables that1943// The returned environment contains additional SNAP_* variables that
1879// are required when calling a meta/hook/ script and that will override1944// are required when calling a meta/hook/ script and that will override
1880// any already existing SNAP_* variables in os.Environment()1945// any already existing SNAP_* variables in os.Environment()
1881func makeSnapHookEnv(part *SnapPart) (env []string) {1946func makeSnapHookEnv(part Snap) (env []string) {
1882 desc := struct {1947 desc := struct {
1883 AppName string1948 AppName string
1884 AppArch string1949 AppArch string
@@ -1889,7 +1954,7 @@
1889 }{1954 }{
1890 part.Name(),1955 part.Name(),
1891 helpers.UbuntuArchitecture(),1956 helpers.UbuntuArchitecture(),
1892 part.basedir,1957 part.dir(),
1893 part.Version(),1958 part.Version(),
1894 QualifiedName(part),1959 QualifiedName(part),
1895 part.Origin(),1960 part.Origin(),
18961961
=== modified file 'snappy/snapp_test.go'
--- snappy/snapp_test.go 2015-07-23 11:05:37 +0000
+++ snappy/snapp_test.go 2015-09-03 08:14:56 +0000
@@ -29,10 +29,10 @@
29 "os"29 "os"
30 "path/filepath"30 "path/filepath"
31 "strings"31 "strings"
32 "testing"
3233
33 "launchpad.net/snappy/clickdeb"34 "launchpad.net/snappy/clickdeb"
34 "launchpad.net/snappy/helpers"35 "launchpad.net/snappy/helpers"
35 "launchpad.net/snappy/partition"
36 "launchpad.net/snappy/pkg"36 "launchpad.net/snappy/pkg"
37 "launchpad.net/snappy/policy"37 "launchpad.net/snappy/policy"
38 "launchpad.net/snappy/release"38 "launchpad.net/snappy/release"
@@ -41,6 +41,9 @@
41 . "gopkg.in/check.v1"41 . "gopkg.in/check.v1"
42)42)
4343
44// Hook up check.v1 into the "go test" runner
45func Test(t *testing.T) { TestingT(t) }
46
44type SnapTestSuite struct {47type SnapTestSuite struct {
45 tempdir string48 tempdir string
46 clickhook string49 clickhook string
@@ -54,9 +57,6 @@
54 aaClickHookCmd = "/bin/true"57 aaClickHookCmd = "/bin/true"
55 s.secbase = policy.SecBase58 s.secbase = policy.SecBase
56 s.tempdir = c.MkDir()59 s.tempdir = c.MkDir()
57 newPartition = func() (p partition.Interface) {
58 return new(MockPartition)
59 }
6060
61 SetRootDir(s.tempdir)61 SetRootDir(s.tempdir)
62 policy.SecBase = filepath.Join(s.tempdir, "security")62 policy.SecBase = filepath.Join(s.tempdir, "security")
@@ -96,9 +96,6 @@
96 c.Assert(err, IsNil)96 c.Assert(err, IsNil)
9797
98 runScFilterGen = mockRunScFilterGen98 runScFilterGen = mockRunScFilterGen
99
100 // ensure we do not look at the system
101 systemImageRoot = s.tempdir
102}99}
103100
104func (s *SnapTestSuite) TearDownTest(c *C) {101func (s *SnapTestSuite) TearDownTest(c *C) {
@@ -153,11 +150,11 @@
153 c.Assert(services[0].Name, Equals, "svc1")150 c.Assert(services[0].Name, Equals, "svc1")
154151
155 // ensure we get valid Date()152 // ensure we get valid Date()
156 st, err := os.Stat(snap.basedir)153 st, err := os.Stat(snap.dir())
157 c.Assert(err, IsNil)154 c.Assert(err, IsNil)
158 c.Assert(snap.Date(), Equals, st.ModTime())155 c.Assert(snap.Date(), Equals, st.ModTime())
159156
160 c.Assert(snap.basedir, Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))157 c.Assert(snap.dir(), Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
161 c.Assert(snap.InstalledSize(), Not(Equals), -1)158 c.Assert(snap.InstalledSize(), Not(Equals), -1)
162}159}
163160
@@ -827,11 +824,11 @@
827 c.Assert(services[1].Description, Equals, "Service #2")824 c.Assert(services[1].Description, Equals, "Service #2")
828825
829 // ensure we get valid Date()826 // ensure we get valid Date()
830 st, err := os.Stat(snap.basedir)827 st, err := os.Stat(snap.dir())
831 c.Assert(err, IsNil)828 c.Assert(err, IsNil)
832 c.Assert(snap.Date(), Equals, st.ModTime())829 c.Assert(snap.Date(), Equals, st.ModTime())
833830
834 c.Assert(snap.basedir, Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))831 c.Assert(snap.dir(), Equals, filepath.Join(s.tempdir, "apps", helloAppComposedName, "1.10"))
835 c.Assert(snap.InstalledSize(), Not(Equals), -1)832 c.Assert(snap.InstalledSize(), Not(Equals), -1)
836}833}
837834
838835
=== removed file 'snappy/systemimage.go'
--- snappy/systemimage.go 2015-07-24 12:03:30 +0000
+++ snappy/systemimage.go 1970-01-01 00:00:00 +0000
@@ -1,518 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package snappy
21
22import (
23 "crypto/sha512"
24 "encoding/hex"
25 "fmt"
26 "os"
27 "path/filepath"
28 "strings"
29 "time"
30
31 "github.com/mvo5/goconfigparser"
32
33 "launchpad.net/snappy/coreconfig"
34 "launchpad.net/snappy/helpers"
35 "launchpad.net/snappy/logger"
36 "launchpad.net/snappy/partition"
37 "launchpad.net/snappy/pkg"
38 "launchpad.net/snappy/progress"
39 "launchpad.net/snappy/provisioning"
40)
41
42const (
43 systemImagePartName = "ubuntu-core"
44 systemImagePartOrigin = "ubuntu"
45 systemImagePartVendor = "Canonical Ltd."
46
47 // location of the channel config on the filesystem.
48 //
49 // This file specifies the s-i version installed on the rootfs
50 // and hence s-i updates this file on every update applied to
51 // the rootfs (by unpacking file "version-$version.tar.xz").
52 systemImageChannelConfig = "/etc/system-image/config.d/01_channel.ini"
53
54 // location of the client config.
55 //
56 // The full path to this file needs to be passed to
57 // systemImageCli when querying a different rootfs.
58 systemImageClientConfig = "/etc/system-image/config.d/00_default.ini"
59)
60
61var (
62 // the system-image-cli binary
63 systemImageCli = "system-image-cli"
64)
65
66// This is the root directory of the filesystem. Its only useful to
67// change when writing tests
68var systemImageRoot = "/"
69
70// will replace newPartition() to return a mockPartition
71var newPartition = newPartitionImpl
72
73func newPartitionImpl() (p partition.Interface) {
74 return partition.New()
75}
76
77// SystemImagePart represents a "core" snap that is managed via the SystemImage
78// client
79type SystemImagePart struct {
80 version string
81 versionDetails string
82 channelName string
83 lastUpdate time.Time
84
85 isInstalled bool
86 isActive bool
87
88 updateSize int64
89
90 partition partition.Interface
91}
92
93// Type returns pkg.TypeCore for this snap
94func (s *SystemImagePart) Type() pkg.Type {
95 return pkg.TypeCore
96}
97
98// Name returns the name
99func (s *SystemImagePart) Name() string {
100 return systemImagePartName
101}
102
103// Origin returns the origin ("ubuntu")
104func (s *SystemImagePart) Origin() string {
105 return systemImagePartOrigin
106}
107
108// Vendor returns the vendor ("Canonical Ltd.")
109func (s *SystemImagePart) Vendor() string {
110 return systemImagePartVendor
111}
112
113// Version returns the version
114func (s *SystemImagePart) Version() string {
115 return s.version
116}
117
118// Description returns the description
119func (s *SystemImagePart) Description() string {
120 return "ubuntu-core description"
121}
122
123// Hash returns the hash
124func (s *SystemImagePart) Hash() string {
125 hasher := sha512.New()
126 hasher.Write([]byte(s.versionDetails))
127 hexdigest := hex.EncodeToString(hasher.Sum(nil))
128
129 return hexdigest
130}
131
132// IsActive returns true if the snap is active
133func (s *SystemImagePart) IsActive() bool {
134 return s.isActive
135}
136
137// IsInstalled returns true if the snap is installed
138func (s *SystemImagePart) IsInstalled() bool {
139 return s.isInstalled
140}
141
142// InstalledSize returns the size of the installed snap
143func (s *SystemImagePart) InstalledSize() int64 {
144 return -1
145}
146
147// DownloadSize returns the dowload size
148func (s *SystemImagePart) DownloadSize() int64 {
149 return s.updateSize
150}
151
152// Date returns the last update date
153func (s *SystemImagePart) Date() time.Time {
154 return s.lastUpdate
155}
156
157// SetActive sets the snap active
158func (s *SystemImagePart) SetActive(pb progress.Meter) (err error) {
159 isNextBootOther := s.partition.IsNextBootOther()
160 // active and no switch scheduled -> nothing to do
161 if s.IsActive() && !isNextBootOther {
162 return nil
163 }
164 // not currently active but switch scheduled already -> nothing to do
165 if !s.IsActive() && isNextBootOther {
166 return nil
167 }
168
169 return s.partition.ToggleNextBoot()
170}
171
172// override in tests
173var bootloaderDir = bootloaderDirImpl
174
175func bootloaderDirImpl() string {
176 return partition.BootloaderDir()
177}
178
179// Install installs the snap
180func (s *SystemImagePart) Install(pb progress.Meter, flags InstallFlags) (name string, err error) {
181 if provisioning.IsSideLoaded(bootloaderDir()) {
182 return "", ErrSideLoaded
183 }
184
185 if pb != nil {
186 // ensure the progress finishes when we are done
187 defer func() {
188 pb.Finished()
189 }()
190 }
191
192 // Ensure there is always a kernel + initrd to boot with, even
193 // if the update does not provide new versions.
194 if s.needsBootAssetSync() {
195 if pb != nil {
196 pb.Notify("Syncing boot files")
197 }
198 err = s.partition.SyncBootloaderFiles(bootAssetFilePaths())
199 if err != nil {
200 return "", err
201 }
202 }
203
204 // find out what config file to use, the other partition may be
205 // empty so we need to fallback to the current one if it is
206 configFile := systemImageClientConfig
207 err = s.partition.RunWithOther(partition.RO, func(otherRoot string) (err error) {
208 // XXX: Note that systemImageDownloadUpdate() requires
209 // the s-i _client_ config file whereas otherIsEmpty()
210 // checks the s-i _channel_ config file.
211 otherConfigFile := filepath.Join(systemImageRoot, otherRoot, systemImageClientConfig)
212 if !otherIsEmpty(otherRoot) && helpers.FileExists(otherConfigFile) {
213 configFile = otherConfigFile
214 }
215
216 // NOTE: we need to pass the config dir here
217 configDir := filepath.Dir(configFile)
218 return systemImageDownloadUpdate(configDir, pb)
219 })
220 if err != nil {
221 return "", err
222 }
223
224 // Check that the final system state is as expected.
225 if err = s.verifyUpgradeWasApplied(); err != nil {
226 return "", err
227 }
228
229 // XXX: ToggleNextBoot() calls handleAssets() (but not SyncBootloader
230 // files :/) - handleAssets() may copy kernel/initramfs to the
231 // sync mounted /boot/uboot, so its very slow, tell the user
232 // at least that something is going on
233 if pb != nil {
234 pb.Notify("Updating boot files")
235 }
236 if err = s.partition.ToggleNextBoot(); err != nil {
237 return "", err
238 }
239 return systemImagePartName, nil
240}
241
242// Ensure the expected version update was applied to the expected partition.
243func (s *SystemImagePart) verifyUpgradeWasApplied() error {
244 // The upgrade has now been applied, so check that the expected
245 // update was applied by comparing "self" (which is the newest
246 // system-image revision with that installed on the other
247 // partition.
248
249 // Determine the latest installed part.
250 latestPart := makeOtherPart(s.partition)
251 if latestPart == nil {
252 // If there is no other part, this system must be a
253 // single rootfs one, so re-query current to find the
254 // latest installed part.
255 latestPart = makeCurrentPart(s.partition)
256 }
257
258 if latestPart == nil {
259 return &ErrUpgradeVerificationFailed{
260 msg: "could not find latest installed partition",
261 }
262 }
263
264 if s.version != latestPart.Version() {
265 return &ErrUpgradeVerificationFailed{
266 msg: fmt.Sprintf("found %q but expected %q", latestPart.Version(), s.version),
267 }
268 }
269
270 return nil
271}
272
273// Uninstall can not be used for "core" snaps
274func (s *SystemImagePart) Uninstall(progress.Meter) error {
275 return ErrPackageNotRemovable
276}
277
278// Config is used to to configure the snap
279func (s *SystemImagePart) Config(configuration []byte) (newConfig string, err error) {
280 if cfg := string(configuration); cfg != "" {
281 return coreconfig.Set(cfg)
282 }
283
284 return coreconfig.Get()
285}
286
287// NeedsReboot returns true if the snap becomes active on the next reboot
288func (s *SystemImagePart) NeedsReboot() bool {
289
290 if !s.IsActive() && s.partition.IsNextBootOther() {
291 return true
292 }
293
294 return false
295}
296
297// MarkBootSuccessful marks the *currently* booted rootfs as "good"
298// (it booted :)
299// Note: Not part of the Part interface.
300func (s *SystemImagePart) MarkBootSuccessful() (err error) {
301
302 return s.partition.MarkBootSuccessful()
303}
304
305// Channel returns the system-image-server channel used
306func (s *SystemImagePart) Channel() string {
307 return s.channelName
308}
309
310// Icon returns the icon path
311func (s *SystemImagePart) Icon() string {
312 return ""
313}
314
315// Frameworks returns the list of frameworks needed by the snap
316func (s *SystemImagePart) Frameworks() ([]string, error) {
317 // system image parts can't depend on frameworks.
318 return nil, nil
319}
320
321// SystemImageRepository is the type used for the system-image-server
322type SystemImageRepository struct {
323 partition partition.Interface
324}
325
326// NewSystemImageRepository returns a new SystemImageRepository
327func NewSystemImageRepository() *SystemImageRepository {
328 return &SystemImageRepository{partition: newPartition()}
329}
330
331func makePartFromSystemImageConfigFile(p partition.Interface, channelIniPath string, isActive bool) (part Part, err error) {
332 cfg := goconfigparser.New()
333 f, err := os.Open(channelIniPath)
334 if err != nil {
335 return nil, err
336 }
337 defer f.Close()
338 err = cfg.Read(f)
339 if err != nil {
340 logger.Noticef("Can not parse config %q: %v", channelIniPath, err)
341 return nil, err
342 }
343 st, err := os.Stat(channelIniPath)
344 if err != nil {
345 logger.Noticef("Can not stat %q: %v", channelIniPath, err)
346 return nil, err
347 }
348
349 currentBuildNumber, err := cfg.Get("service", "build_number")
350 versionDetails, err := cfg.Get("service", "version_detail")
351 channelName, err := cfg.Get("service", "channel")
352 return &SystemImagePart{
353 isActive: isActive,
354 isInstalled: true,
355 version: currentBuildNumber,
356 versionDetails: versionDetails,
357 channelName: channelName,
358 lastUpdate: st.ModTime(),
359 partition: p}, err
360}
361
362// Returns the part associated with the current rootfs
363func makeCurrentPart(p partition.Interface) Part {
364 configFile := filepath.Join(systemImageRoot, systemImageChannelConfig)
365 part, err := makePartFromSystemImageConfigFile(p, configFile, true)
366 if err != nil {
367 return nil
368 }
369 return part
370}
371
372// otherIsEmpty returns true if the rootfs path specified should be
373// considered empty.
374//
375// Note that the rootfs _may_ not actually be strictly empty, but it
376// must be considered empty if it is incomplete.
377//
378// This function encapsulates the heuristics to determine if the rootfs
379// is complete.
380func otherIsEmpty(root string) bool {
381 configFile := filepath.Join(systemImageRoot, root, systemImageChannelConfig)
382
383 st, err := os.Stat(configFile)
384
385 if err == nil && st.Size() > 0 {
386 // the channel config file exists and has a "reasonable"
387 // size. The upgrade therefore completed successfully,
388 // so consider the rootfs complete.
389 return false
390 }
391
392 // The upgrader pre-creates the s-i channel config file as a
393 // zero-sized file when "other" is first provisioned.
394 //
395 // So if this file either does not exist, or has a size of zero
396 // (indicating the upgrader failed to complete the s-i unpack on
397 // a previous boot [which would have made configFile >0 bytes]),
398 // the other partition is considered empty.
399
400 return true
401}
402
403// Returns the part associated with the other rootfs (if any)
404func makeOtherPart(p partition.Interface) Part {
405 var part Part
406 err := p.RunWithOther(partition.RO, func(otherRoot string) (err error) {
407 if otherIsEmpty(otherRoot) {
408 return nil
409 }
410
411 configFile := filepath.Join(systemImageRoot, otherRoot, systemImageChannelConfig)
412 part, err = makePartFromSystemImageConfigFile(p, configFile, false)
413 if err != nil {
414 logger.Noticef("Can not make system-image part for %q: %v", configFile, err)
415 }
416 return err
417 })
418 if err == partition.ErrNoDualPartition {
419 return nil
420 }
421 return part
422}
423
424// Description describes the repository
425func (s *SystemImageRepository) Description() string {
426 return "SystemImageRepository"
427}
428
429// Search searches the SystemImageRepository for the given terms
430func (s *SystemImageRepository) Search(terms string) (versions []Part, err error) {
431 if strings.Contains(terms, systemImagePartName) {
432 part := makeCurrentPart(s.partition)
433 versions = append(versions, part)
434 }
435 return versions, err
436}
437
438// Details returns details for the given snap
439func (s *SystemImageRepository) Details(snapName string) (versions []Part, err error) {
440 if snapName == systemImagePartName {
441 part := makeCurrentPart(s.partition)
442 versions = append(versions, part)
443 }
444 return versions, err
445}
446
447// Updates returns the available updates
448func (s *SystemImageRepository) Updates() (parts []Part, err error) {
449 configFile := filepath.Join(systemImageRoot, systemImageChannelConfig)
450 updateStatus, err := systemImageClientCheckForUpdates(configFile)
451
452 current := makeCurrentPart(s.partition)
453 // no VersionCompare here because the channel provides a "order" and
454 // that may go backwards when switching channels(?)
455 if current.Version() != updateStatus.targetVersion {
456 parts = append(parts, &SystemImagePart{
457 version: updateStatus.targetVersion,
458 versionDetails: updateStatus.targetVersionDetails,
459 lastUpdate: updateStatus.lastUpdate,
460 updateSize: updateStatus.updateSize,
461 channelName: current.(*SystemImagePart).channelName,
462 partition: s.partition})
463 }
464
465 return parts, err
466}
467
468// Installed returns the installed snaps from this repository
469func (s *SystemImageRepository) Installed() (parts []Part, err error) {
470 // current partition
471 curr := makeCurrentPart(s.partition)
472 if curr != nil {
473 parts = append(parts, curr)
474 }
475
476 // other partition
477 other := makeOtherPart(s.partition)
478 if other != nil {
479 parts = append(parts, other)
480 }
481
482 return parts, err
483}
484
485// needsSync determines if syncing boot assets is required
486func (s *SystemImagePart) needsBootAssetSync() bool {
487 // current partition
488 curr := makeCurrentPart(s.partition)
489 if curr == nil {
490 // this should never ever happen
491 panic("current part does not exist")
492 }
493
494 // other partition
495 other := makeOtherPart(s.partition)
496 if other == nil {
497 return true
498 }
499
500 // the idea here is that a channel change on the other
501 // partition always triggers a full image download so
502 // there is no need for syncing the assets (because the
503 // kernel is included in the full image already)
504 //
505 // FIXME: its not entirely clear if this is true, there
506 // is no mechanism to switch channels right now and all
507 // the tests always switch both partitions
508 if curr.Channel() != other.Channel() {
509 return false
510 }
511
512 // if the other version is already a higher version number
513 // than the current one it means all the kernel updates
514 // has happend already and we do not need to sync the
515 // bootloader files, see:
516 // https://bugs.launchpad.net/snappy/+bug/1474125
517 return VersionCompare(curr.Version(), other.Version()) > 0
518}
5190
=== removed file 'snappy/systemimage_native.go'
--- snappy/systemimage_native.go 2015-07-02 20:57:18 +0000
+++ snappy/systemimage_native.go 1970-01-01 00:00:00 +0000
@@ -1,211 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package snappy
21
22import (
23 "bufio"
24 "encoding/json"
25 "fmt"
26 "io"
27 "io/ioutil"
28 "net/http"
29 "os"
30 "os/exec"
31 "path"
32 "strings"
33 "time"
34
35 "github.com/mvo5/goconfigparser"
36
37 "launchpad.net/snappy/helpers"
38 "launchpad.net/snappy/progress"
39)
40
41var systemImageServer = "https://system-image.ubuntu.com/"
42
43type updateStatus struct {
44 targetVersion string
45 targetVersionDetails string
46 updateSize int64
47 lastUpdate time.Time
48}
49
50type channelImage struct {
51 Descripton string `json:"description,omitempty"`
52 Type string `json:"type, omitempty"`
53 Version int `json:"version, omitempty"`
54 VersionDetails string `json:"version_detail, omitempty"`
55 Files []channelImageFiles `json:"files"`
56}
57
58type channelImageFiles struct {
59 Size int64 `json:"size"`
60}
61
62type channelImageGlobal struct {
63 GeneratedAt string `json:"generated_at"`
64}
65
66type channelJSON struct {
67 Global channelImageGlobal `json:"global"`
68 Images []channelImage `json:"images"`
69}
70
71func systemImageClientCheckForUpdates(configFile string) (us updateStatus, err error) {
72 cfg := goconfigparser.New()
73 if err := cfg.ReadFile(configFile); err != nil {
74 return us, err
75 }
76 channel, _ := cfg.Get("service", "channel")
77 device, _ := cfg.Get("service", "device")
78
79 indexURL := systemImageServer + "/" + path.Join(channel, device, "index.json")
80
81 resp, err := http.Get(indexURL)
82 if err != nil {
83 return us, err
84 }
85 defer resp.Body.Close()
86
87 if resp.StatusCode != 200 {
88 return us, fmt.Errorf("systemImageDbusProxy: unexpected http statusCode %v for %s", resp.StatusCode, indexURL)
89 }
90
91 // and decode json
92 var channelData channelJSON
93 dec := json.NewDecoder(resp.Body)
94 if err := dec.Decode(&channelData); err != nil {
95 return us, err
96 }
97
98 // global property
99 us.lastUpdate, _ = time.Parse("Mon Jan 2 15:04:05 MST 2006", channelData.Global.GeneratedAt)
100
101 // FIXME: find latest image of type "full" here
102 latestImage := channelData.Images[len(channelData.Images)-1]
103 us.targetVersion = fmt.Sprintf("%d", latestImage.Version)
104 us.targetVersionDetails = latestImage.VersionDetails
105
106 // FIXME: this is not accurate right now as it does not take
107 // the deltas into account
108 for _, f := range latestImage.Files {
109 us.updateSize += f.Size
110 }
111
112 return us, nil
113}
114
115type genericJSON struct {
116 Type string `json:"type, omitempty"`
117 Message string `json:"msg, omitempty"`
118 Now float64 `json:"now, omitempty"`
119 Total float64 `json:"total, omitempty"`
120}
121
122func parseSIProgress(pb progress.Meter, stdout io.Reader) error {
123 if pb == nil {
124 pb = &progress.NullProgress{}
125 }
126
127 scanner := bufio.NewScanner(stdout)
128 // s-i is funny, total changes during the runs
129 total := 0.0
130 pb.Start("ubuntu-core", 100)
131
132 for scanner.Scan() {
133 if os.Getenv("SNAPPY_DEBUG") != "" {
134 fmt.Println(scanner.Text())
135 }
136
137 jsonStream := strings.NewReader(scanner.Text())
138 dec := json.NewDecoder(jsonStream)
139 var genericData genericJSON
140 if err := dec.Decode(&genericData); err != nil {
141 // we ignore invalid json here and continue
142 // the parsing if s-i-cli or ubuntu-core-upgrader
143 // output something unexpected (like stray debug
144 // output or whatnot)
145 continue
146 }
147
148 switch {
149 case genericData.Type == "spinner":
150 pb.Spin(genericData.Message)
151 case genericData.Type == "error":
152 return fmt.Errorf("error from %s: %s", systemImageCli, genericData.Message)
153 case genericData.Type == "progress":
154 if total != genericData.Total {
155 total = genericData.Total
156 pb.SetTotal(total)
157 }
158 pb.Set(genericData.Now)
159 }
160 }
161 // ugly: avoid Spin() artifacts
162 pb.Notify("\nApply done")
163
164 if err := scanner.Err(); err != nil {
165 return err
166 }
167
168 return nil
169}
170
171func systemImageDownloadUpdate(configDir string, pb progress.Meter) (err error) {
172 cmd := exec.Command(systemImageCli, "--progress", "json", "-C", configDir)
173
174 // collect progress over stdout pipe if we want progress
175 var stdout io.Reader
176 stdout, err = cmd.StdoutPipe()
177 if err != nil {
178 return err
179 }
180
181 // collect error message (traceback etc in a separate goroutine)
182 stderr, err := cmd.StderrPipe()
183 if err != nil {
184 return err
185 }
186 stderrCh := make(chan []byte)
187 go func() {
188 stderrContent, _ := ioutil.ReadAll(stderr)
189 stderrCh <- stderrContent
190 }()
191
192 // run it
193 if err := cmd.Start(); err != nil {
194 return err
195 }
196
197 // and parse progress synchronously
198 if err := parseSIProgress(pb, stdout); err != nil {
199 return err
200 }
201
202 // we need to read all of stderr *before* calling cmd.Wait() to avoid
203 // a race, see docs for "os/exec:func (*Cmd) StdoutPipe"
204 stderrContent := <-stderrCh
205 if err := cmd.Wait(); err != nil {
206 retCode, _ := helpers.ExitCode(err)
207 return fmt.Errorf("%s failed with return code %v: %s", systemImageCli, retCode, string(stderrContent))
208 }
209
210 return err
211}
2120
=== removed file 'snappy/systemimage_native_test.go'
--- snappy/systemimage_native_test.go 2015-06-02 20:46:07 +0000
+++ snappy/systemimage_native_test.go 1970-01-01 00:00:00 +0000
@@ -1,123 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package snappy
21
22import (
23 "fmt"
24 "io"
25 "net/http"
26 "net/http/httptest"
27 "path/filepath"
28 "time"
29
30 . "gopkg.in/check.v1"
31)
32
33/* acquired via:
34 curl https://system-image.ubuntu.com/ubuntu-core/devel/generic_armhf/index.json
35*/
36const mockSystemImageIndexJSONTemplate = `{
37 "global": {
38 "generated_at": "Thu Feb 19 18:26:23 UTC 2015"
39 },
40 "images": [
41 {
42 "description": ",version=1",
43 "files": [
44 {
45 "checksum": "3869be55a95db880862fe3dc3f5d643101736f674e9970a2099a883bd2bd2367",
46 "order": 0,
47 "path": "/pool/ubuntu-3f8ef532557aaec57264565b9948734bfe01f2a39886c816b0f69ae19e25f903.tar.xz",
48 "signature": "/pool/ubuntu-3f8ef532557aaec57264565b9948734bfe01f2a39886c816b0f69ae19e25f903.tar.xz.asc",
49 "size": 71081252
50 },
51 {
52 "checksum": "f1a5ae7b4264e045ad58710a427168c02732e29130e5619dd2a82bbc8781b7c0",
53 "order": 1,
54 "path": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz",
55 "signature": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz.asc",
56 "size": 52589508
57 },
58 {
59 "checksum": "a84fb49b505a797e1ddd6811d8cf79b5fe2a021198cd221e022318026dfb5417",
60 "order": 2,
61 "path": "/ubuntu-core/devel/generic_armhf/version-1.tar.xz",
62 "signature": "/ubuntu-core/devel/generic_armhf/version-1.tar.xz.asc",
63 "size": 328
64 }
65 ],
66 "type": "full",
67 "version": 1,
68 "version_detail": ",version=1"
69 },
70 {
71 "description": ",version=2",
72 "files": [
73 {
74 "checksum": "8be1d2c82a6d785089de91febf70aa7aa790c23fae4f2e02c5dc4dfc343d005a",
75 "order": 0,
76 "path": "/pool/ubuntu-0e6f7a24f941a2fbe27c922b5158da9ab177afaa851c28c002a22f4166f3ec01.tar.xz",
77 "signature": "/pool/ubuntu-0e6f7a24f941a2fbe27c922b5158da9ab177afaa851c28c002a22f4166f3ec01.tar.xz.asc",
78 "size": 70576648
79 },
80 {
81 "checksum": "f1a5ae7b4264e045ad58710a427168c02732e29130e5619dd2a82bbc8781b7c0",
82 "order": 1,
83 "path": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz",
84 "signature": "/pool/device-bf0a49e75a3deb99855f186906f17d7668e2dcc3a0b38f5feade3e6f7c75e9b8.tar.xz.asc",
85 "size": 52589508
86 },
87 {
88 "checksum": "242f0198b4bd0b63c943e7ff7eb46889753c725cb5b127a7bb76600bcecee544",
89 "order": 2,
90 "path": "/ubuntu-core/devel/generic_armhf/version-2.tar.xz",
91 "signature": "/ubuntu-core/devel/generic_armhf/version-2.tar.xz.asc",
92 "size": 332
93 }
94 ],
95 "type": "full",
96 "version": %s,
97 "version_detail": ",version=2"
98 }
99 ]
100}`
101
102var mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
103
104func runMockSystemImageWebServer() *httptest.Server {
105 mockServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
106 io.WriteString(w, mockSystemImageIndexJSON)
107 }))
108 if mockServer == nil {
109 return nil
110 }
111 systemImageServer = mockServer.URL
112 return mockServer
113}
114
115func (s *SITestSuite) TestCheckForUpdates(c *C) {
116 mockConfigFile := filepath.Join(c.MkDir(), "channel.init")
117 makeFakeSystemImageChannelConfig(c, mockConfigFile, "1")
118 updateStatus, err := systemImageClientCheckForUpdates(mockConfigFile)
119 c.Assert(err, IsNil)
120 c.Assert(updateStatus.targetVersion, Equals, "2")
121 c.Assert(updateStatus.targetVersionDetails, Equals, ",version=2")
122 c.Assert(updateStatus.lastUpdate, Equals, time.Date(2015, 02, 19, 18, 26, 23, 0, time.UTC))
123}
1240
=== removed file 'snappy/systemimage_test.go'
--- snappy/systemimage_test.go 2015-07-15 07:53:43 +0000
+++ snappy/systemimage_test.go 1970-01-01 00:00:00 +0000
@@ -1,486 +0,0 @@
1// -*- Mode: Go; indent-tabs-mode: t -*-
2
3/*
4 * Copyright (C) 2014-2015 Canonical Ltd
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3 as
8 * published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 */
19
20package snappy
21
22import (
23 "fmt"
24 "io/ioutil"
25 "net/http/httptest"
26 "os"
27 "path/filepath"
28 "strings"
29 "testing"
30
31 "launchpad.net/snappy/partition"
32 "launchpad.net/snappy/provisioning"
33
34 . "gopkg.in/check.v1"
35)
36
37// Hook up check.v1 into the "go test" runner
38func Test(t *testing.T) { TestingT(t) }
39
40type SITestSuite struct {
41 systemImage *SystemImageRepository
42 mockSystemImageWebServer *httptest.Server
43}
44
45var _ = Suite(&SITestSuite{})
46
47func (s *SITestSuite) SetUpTest(c *C) {
48 newPartition = func() (p partition.Interface) {
49 return new(MockPartition)
50 }
51
52 s.systemImage = NewSystemImageRepository()
53 c.Assert(s, NotNil)
54 // setup alternative root for system image
55 tempdir := c.MkDir()
56 systemImageRoot = tempdir
57
58 makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, systemImageChannelConfig), "1")
59 // setup fake /other partition
60 makeFakeSystemImageChannelConfig(c, filepath.Join(tempdir, "other", systemImageChannelConfig), "0")
61
62 // run test webserver instead of talking to the real one
63 //
64 // The mock webserver versions "1" and "2"
65 s.mockSystemImageWebServer = runMockSystemImageWebServer()
66 c.Assert(s.mockSystemImageWebServer, NotNil)
67
68 // create mock system-image-cli
69 systemImageCli = makeMockSystemImageCli(c, tempdir)
70}
71
72func (s *SITestSuite) TearDownTest(c *C) {
73 s.mockSystemImageWebServer.Close()
74 systemImageRoot = "/"
75 bootloaderDir = bootloaderDirImpl
76}
77
78func makeMockSystemImageCli(c *C, tempdir string) string {
79 s := `#!/bin/sh
80
81printf '{"type": "progress", "now": 20, "total":100}\n'
82printf '{"type": "progress", "now": 40, "total":100}\n'
83printf '{"type": "progress", "now": 60, "total":100}\n'
84printf '{"type": "progress", "now": 80, "total":100}\n'
85printf '{"type": "progress", "now": 100, "total":100}\n'
86printf '{"type": "spinner", "msg": "Applying"}\n'
87`
88 mockScript := filepath.Join(tempdir, "system-image-cli")
89 err := ioutil.WriteFile(mockScript, []byte(s), 0755)
90 c.Assert(err, IsNil)
91
92 return mockScript
93}
94
95func makeFakeSystemImageChannelConfig(c *C, cfgPath, buildNumber string) {
96 os.MkdirAll(filepath.Dir(cfgPath), 0775)
97 f, err := os.OpenFile(cfgPath, os.O_CREATE|os.O_RDWR, 0664)
98 c.Assert(err, IsNil)
99 defer f.Close()
100 f.Write([]byte(fmt.Sprintf(`
101[service]
102base: system-image.ubuntu.com
103http_port: 80
104https_port: 443
105channel: ubuntu-core/devel-proposed
106device: generic_amd64
107build_number: %s
108version_detail: ubuntu=20141206,raw-device=20141206,version=77
109`, buildNumber)))
110}
111
112func (s *SITestSuite) TestTestInstalled(c *C) {
113 // whats installed
114 parts, err := s.systemImage.Installed()
115 c.Assert(err, IsNil)
116 // we have one active and one inactive
117 c.Assert(parts, HasLen, 2)
118 c.Assert(parts[0].Name(), Equals, systemImagePartName)
119 c.Assert(parts[0].Origin(), Equals, systemImagePartOrigin)
120 c.Assert(parts[0].Vendor(), Equals, systemImagePartVendor)
121 c.Assert(parts[0].Version(), Equals, "1")
122 c.Assert(parts[0].Hash(), Equals, "e09c13f68fccef3b2fe0f5c8ff5c61acf2173b170b1f2a3646487147690b0970ef6f2c555d7bcb072035f29ee4ea66a6df7f6bb320d358d3a7d78a0c37a8a549")
123 c.Assert(parts[0].IsActive(), Equals, true)
124 c.Assert(parts[0].Channel(), Equals, "ubuntu-core/devel-proposed")
125
126 // second partition is not active and has a different version
127 c.Assert(parts[1].IsActive(), Equals, false)
128 c.Assert(parts[1].Version(), Equals, "0")
129}
130
131func (s *SITestSuite) TestUpdateNoUpdate(c *C) {
132 mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "1")
133 parts, err := s.systemImage.Updates()
134 c.Assert(err, IsNil)
135 c.Assert(parts, HasLen, 0)
136}
137
138func (s *SITestSuite) TestUpdateHasUpdate(c *C) {
139 // add a update
140 mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
141 parts, err := s.systemImage.Updates()
142 c.Assert(err, IsNil)
143 c.Assert(parts, HasLen, 1)
144 c.Assert(parts[0].Name(), Equals, "ubuntu-core")
145 c.Assert(parts[0].Version(), Equals, "2")
146 c.Assert(parts[0].DownloadSize(), Equals, int64(123166488))
147}
148
149type MockPartition struct {
150 toggleNextBootCalled bool
151 markBootSuccessfulCalled bool
152 syncBootloaderFilesCalled bool
153}
154
155func (p *MockPartition) ToggleNextBoot() error {
156 p.toggleNextBootCalled = true
157 return nil
158}
159
160func (p *MockPartition) MarkBootSuccessful() error {
161 p.markBootSuccessfulCalled = true
162 return nil
163}
164func (p *MockPartition) SyncBootloaderFiles(map[string]string) error {
165 p.syncBootloaderFilesCalled = true
166 return nil
167}
168func (p *MockPartition) IsNextBootOther() bool {
169 return false
170}
171
172func (p *MockPartition) RunWithOther(option partition.MountOption, f func(otherRoot string) (err error)) (err error) {
173 return f("/other")
174}
175
176// used by GetBootLoaderDir(), used to test sideload logic.
177var tempBootDir string
178
179func (p *MockPartition) BootloaderDir() string {
180 return tempBootDir
181}
182
183func (s *SITestSuite) TestSystemImagePartInstallUpdatesPartition(c *C) {
184 // FIXME: ideally we would change the version to "2" as a side-effect
185 // of calling sp.Install() we need to update it because the
186 // sp.Install() will verify that it got applied
187 makeFakeSystemImageChannelConfig(c, filepath.Join(systemImageRoot, "other", systemImageChannelConfig), "2")
188
189 // add a update
190 mockSystemImageIndexJSON = fmt.Sprintf(mockSystemImageIndexJSONTemplate, "2")
191 parts, err := s.systemImage.Updates()
192 c.Assert(err, IsNil)
193
194 sp := parts[0].(*SystemImagePart)
195 mockPartition := MockPartition{}
196 sp.partition = &mockPartition
197
198 pb := &MockProgressMeter{}
199 // do the install
200 _, err = sp.Install(pb, 0)
201 c.Assert(err, IsNil)
202 c.Assert(mockPartition.toggleNextBootCalled, Equals, true)
203 c.Assert(pb.total, Equals, 100.0)
204 c.Assert(pb.spin, Equals, true)
205 c.Assert(pb.spinMsg, Equals, "Applying")
206 c.Assert(pb.finished, Equals, true)
207 c.Assert(pb.progress, DeepEquals, []float64{20.0, 40.0, 60.0, 80.0, 100.0})
208}
209
210func (s *SITestSuite) TestSystemImagePartInstallUpdatesBroken(c *C) {
211 // fake a broken upgrade
212 scriptContent := `#!/bin/sh
213printf '{"type": "error", "msg": "some error msg"}\n'
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches