Merge lp:~mvo/snappy/snappy-improved-developer-mode2 into lp:~snappy-dev/snappy/snappy-moved-to-github

Proposed by Michael Vogt
Status: Merged
Approved by: Michael Vogt
Approved revision: 514
Merged at revision: 516
Proposed branch: lp:~mvo/snappy/snappy-improved-developer-mode2
Merge into: lp:~snappy-dev/snappy/snappy-moved-to-github
Prerequisite: lp:~mvo/snappy/snappy-improve-developer-mode-detection
Diff against target: 1280 lines (+505/-391)
12 files modified
partition/assets.go (+87/-0)
partition/assets_test.go (+36/-0)
partition/bootloader_grub.go (+1/-1)
partition/bootloader_grub_test.go (+3/-3)
partition/bootloader_uboot.go (+9/-7)
partition/bootloader_uboot_test.go (+9/-10)
partition/dirs.go (+41/-0)
partition/mount.go (+153/-0)
partition/mount_test.go (+64/-0)
partition/partition.go (+35/-280)
partition/partition_test.go (+48/-90)
partition/utils.go (+19/-0)
To merge this branch: bzr merge lp:~mvo/snappy/snappy-improved-developer-mode2
Reviewer Review Type Date Requested Status
Michael Vogt (community) Approve
John Lenton (community) Approve
Review via email: mp+261692@code.launchpad.net

Commit message

Refactor partition/ and split code into more files.

Description of the change

A bit of refactoring in the partition/ system, mostly moving stuff around. The goal is to eventually decouple bootloader and partition a bit more. The branch is rather large, sorry for that. Please let me know if I should split it up further.

To post a comment you must log in.
Revision history for this message
John Lenton (chipaca) :
Revision history for this message
John Lenton (chipaca) wrote :

I'm pretending you removed the stray comment :-) feel free to top-approve once that's done. Also, commit message.

review: Approve
Revision history for this message
Michael Vogt (mvo) wrote :

Thanks! I fixed the mentioned issues and it should be good to go now.

Revision history for this message
Snappy Tarmac (snappydevtarmac) wrote :

Attempt to merge into lp:snappy failed due to conflicts:

text conflict in partition/bootloader_uboot_test.go

514. By Michael Vogt

merged lp:snappy and resolved conflicts

Revision history for this message
Michael Vogt (mvo) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'partition/assets.go'
2--- partition/assets.go 1970-01-01 00:00:00 +0000
3+++ partition/assets.go 2015-06-24 13:28:39 +0000
4@@ -0,0 +1,87 @@
5+// -*- Mode: Go; indent-tabs-mode: t -*-
6+
7+/*
8+ * Copyright (C) 2014-2015 Canonical Ltd
9+ *
10+ * This program is free software: you can redistribute it and/or modify
11+ * it under the terms of the GNU General Public License version 3 as
12+ * published by the Free Software Foundation.
13+ *
14+ * This program is distributed in the hope that it will be useful,
15+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
16+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17+ * GNU General Public License for more details.
18+ *
19+ * You should have received a copy of the GNU General Public License
20+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
21+ *
22+ */
23+
24+package partition
25+
26+import (
27+ "errors"
28+ "io/ioutil"
29+ "os"
30+ "path/filepath"
31+
32+ "gopkg.in/yaml.v2"
33+)
34+
35+// Representation of the yaml in the hardwareSpecFile
36+type hardwareSpecType struct {
37+ Kernel string `yaml:"kernel"`
38+ Initrd string `yaml:"initrd"`
39+ DtbDir string `yaml:"dtbs"`
40+ PartitionLayout string `yaml:"partition-layout"`
41+ Bootloader bootloaderName `yaml:"bootloader"`
42+}
43+
44+var (
45+ // ErrNoHardwareYaml is returned when no hardware yaml is found in
46+ // the update, this means that there is nothing to process with regards
47+ // to device parts.
48+ ErrNoHardwareYaml = errors.New("no hardware.yaml")
49+
50+ // Declarative specification of the type of system which specifies such
51+ // details as:
52+ //
53+ // - the location of initrd+kernel within the system-image archive.
54+ // - the location of hardware-specific .dtb files within the
55+ // system-image archive.
56+ // - the type of bootloader that should be used for this system.
57+ // - expected system partition layout (single or dual rootfs's).
58+ hardwareSpecFileReal = filepath.Join(cacheDir, "hardware.yaml")
59+
60+ // useful to override in the tests
61+ hardwareSpecFile = hardwareSpecFileReal
62+
63+ // Directory that _may_ get automatically created on unpack that
64+ // contains updated hardware-specific boot assets (such as initrd,
65+ // kernel)
66+ assetsDir = filepath.Join(cacheDir, "assets")
67+
68+ // Directory that _may_ get automatically created on unpack that
69+ // contains updated hardware-specific assets that require flashing
70+ // to the disk (such as uBoot, MLO)
71+ flashAssetsDir = filepath.Join(cacheDir, "flashtool-assets")
72+)
73+
74+func readHardwareSpec() (*hardwareSpecType, error) {
75+ var h hardwareSpecType
76+
77+ data, err := ioutil.ReadFile(hardwareSpecFile)
78+ // if hardware.yaml does not exist it just means that there was no
79+ // device part in the update.
80+ if os.IsNotExist(err) {
81+ return nil, ErrNoHardwareYaml
82+ } else if err != nil {
83+ return nil, err
84+ }
85+
86+ if err := yaml.Unmarshal([]byte(data), &h); err != nil {
87+ return nil, err
88+ }
89+
90+ return &h, nil
91+}
92
93=== added file 'partition/assets_test.go'
94--- partition/assets_test.go 1970-01-01 00:00:00 +0000
95+++ partition/assets_test.go 2015-06-24 13:28:39 +0000
96@@ -0,0 +1,36 @@
97+// -*- Mode: Go; indent-tabs-mode: t -*-
98+
99+/*
100+ * Copyright (C) 2014-2015 Canonical Ltd
101+ *
102+ * This program is free software: you can redistribute it and/or modify
103+ * it under the terms of the GNU General Public License version 3 as
104+ * published by the Free Software Foundation.
105+ *
106+ * This program is distributed in the hope that it will be useful,
107+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
108+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
109+ * GNU General Public License for more details.
110+ *
111+ * You should have received a copy of the GNU General Public License
112+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
113+ *
114+ */
115+
116+package partition
117+
118+import (
119+ . "gopkg.in/check.v1"
120+)
121+
122+func (s *PartitionTestSuite) TestHardwareSpec(c *C) {
123+
124+ hardwareSpecFile = makeHardwareYaml(c, "")
125+ hw, err := readHardwareSpec()
126+ c.Assert(err, IsNil)
127+ c.Assert(hw.Kernel, Equals, "assets/vmlinuz")
128+ c.Assert(hw.Initrd, Equals, "assets/initrd.img")
129+ c.Assert(hw.DtbDir, Equals, "assets/dtbs")
130+ c.Assert(hw.PartitionLayout, Equals, bootloaderSystemAB)
131+ c.Assert(hw.Bootloader, Equals, bootloaderNameUboot)
132+}
133
134=== modified file 'partition/bootloader_grub.go'
135--- partition/bootloader_grub.go 2015-06-09 17:22:59 +0000
136+++ partition/bootloader_grub.go 2015-06-24 13:28:39 +0000
137@@ -80,7 +80,7 @@
138 func (g *grub) ToggleRootFS() (err error) {
139
140 // create the grub config
141- if err := runInChroot(g.partition.MountTarget(), bootloaderGrubUpdateCmd); err != nil {
142+ if err := runInChroot(mountTarget, bootloaderGrubUpdateCmd); err != nil {
143 return err
144 }
145
146
147=== modified file 'partition/bootloader_grub_test.go'
148--- partition/bootloader_grub_test.go 2015-06-09 17:43:20 +0000
149+++ partition/bootloader_grub_test.go 2015-06-24 13:28:39 +0000
150@@ -92,10 +92,10 @@
151 c.Assert(err, IsNil)
152
153 // this is always called
154- mp := singleCommand{"/bin/mountpoint", "/writable/cache/system"}
155+ mp := singleCommand{"/bin/mountpoint", mountTarget}
156 c.Assert(allCommands[0], DeepEquals, mp)
157
158- expectedGrubUpdate := singleCommand{"/usr/sbin/chroot", "/writable/cache/system", bootloaderGrubUpdateCmd}
159+ expectedGrubUpdate := singleCommand{"/usr/sbin/chroot", mountTarget, bootloaderGrubUpdateCmd}
160 c.Assert(allCommands[1], DeepEquals, expectedGrubUpdate)
161
162 expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "set", "snappy_mode=try"}
163@@ -145,7 +145,7 @@
164 c.Assert(err, IsNil)
165
166 // this is always called
167- mp := singleCommand{"/bin/mountpoint", "/writable/cache/system"}
168+ mp := singleCommand{"/bin/mountpoint", mountTarget}
169 c.Assert(allCommands[0], DeepEquals, mp)
170
171 expectedGrubSet := singleCommand{bootloaderGrubEnvCmd, bootloaderGrubEnvFile, "unset", "snappy_trial_boot"}
172
173=== modified file 'partition/bootloader_uboot.go'
174--- partition/bootloader_uboot.go 2015-06-09 17:22:59 +0000
175+++ partition/bootloader_uboot.go 2015-06-24 13:28:39 +0000
176@@ -227,14 +227,18 @@
177 func (u *uboot) HandleAssets() (err error) {
178 // check if we have anything, if there is no hardware yaml, there is nothing
179 // to process.
180- hardware, err := u.partition.hardwareSpec()
181+ hardware, err := readHardwareSpec()
182 if err == ErrNoHardwareYaml {
183 return nil
184 } else if err != nil {
185 return err
186 }
187- // ensure to remove the file once we are done
188- defer os.Remove(u.partition.hardwareSpecFile)
189+ // ensure to remove the file once we are done (and all was good)
190+ defer func() {
191+ if err == nil {
192+ os.Remove(hardwareSpecFile)
193+ }
194+ }()
195
196 // validate bootloader
197 if hardware.Bootloader != u.Name() {
198@@ -263,7 +267,7 @@
199 }
200
201 // expand path
202- path := filepath.Join(u.partition.cacheDir(), file)
203+ path := filepath.Join(cacheDir, file)
204
205 if !helpers.FileExists(path) {
206 return fmt.Errorf("can not find file %s", path)
207@@ -281,7 +285,7 @@
208 // fully speced
209
210 // install .dtb files
211- dtbSrcDir := filepath.Join(u.partition.cacheDir(), hardware.DtbDir)
212+ dtbSrcDir := filepath.Join(cacheDir, hardware.DtbDir)
213 if helpers.FileExists(dtbSrcDir) {
214 // ensure we cleanup the source dir
215 defer os.RemoveAll(dtbSrcDir)
216@@ -303,8 +307,6 @@
217 }
218 }
219
220- flashAssetsDir := u.partition.flashAssetsDir()
221-
222 if helpers.FileExists(flashAssetsDir) {
223 // FIXME: we don't currently do anything with the
224 // MLO + uImage files since they are not specified in
225
226=== modified file 'partition/bootloader_uboot_test.go'
227--- partition/bootloader_uboot_test.go 2015-06-12 05:23:40 +0000
228+++ partition/bootloader_uboot_test.go 2015-06-24 13:28:39 +0000
229@@ -158,7 +158,7 @@
230
231 func makeMockAssetsDir(c *C) {
232 for _, f := range []string{"assets/vmlinuz", "assets/initrd.img", "assets/dtbs/foo.dtb", "assets/dtbs/bar.dtb"} {
233- p := filepath.Join(defaultCacheDir, f)
234+ p := filepath.Join(cacheDir, f)
235 os.MkdirAll(filepath.Dir(p), 0755)
236 err := ioutil.WriteFile(p, []byte(f), 0644)
237 c.Assert(err, IsNil)
238@@ -172,8 +172,8 @@
239 c.Assert(err, IsNil)
240
241 // mock the hardwareYaml and the cacheDir
242- p.hardwareSpecFile = makeHardwareYaml(c, "")
243- defaultCacheDir = c.MkDir()
244+ hardwareSpecFile = makeHardwareYaml(c, "")
245+ cacheDir = c.MkDir()
246
247 // create mock assets/
248 makeMockAssetsDir(c)
249@@ -192,8 +192,8 @@
250 }
251
252 // ensure nothing left behind
253- c.Assert(helpers.FileExists(filepath.Join(defaultCacheDir, "assets")), Equals, false)
254- c.Assert(helpers.FileExists(p.hardwareSpecFile), Equals, false)
255+ c.Assert(helpers.FileExists(filepath.Join(cacheDir, "assets")), Equals, false)
256+ c.Assert(helpers.FileExists(hardwareSpecFile), Equals, false)
257 }
258
259 func (s *PartitionTestSuite) TestHandleAssetsVerifyBootloader(c *C) {
260@@ -203,7 +203,8 @@
261 c.Assert(err, IsNil)
262
263 // mock the hardwareYaml and the cacheDir
264- p.hardwareSpecFile = makeHardwareYaml(c, "bootloader: grub")
265+ hardwareSpecFile = makeHardwareYaml(c, "bootloader: grub")
266+ cacheDir = c.MkDir()
267
268 err = bootloader.HandleAssets()
269 c.Assert(err, NotNil)
270@@ -216,18 +217,16 @@
271 c.Assert(err, IsNil)
272
273 // mock the hardwareYaml and the cacheDir
274- p.hardwareSpecFile = makeHardwareYaml(c, `
275+ hardwareSpecFile = makeHardwareYaml(c, `
276 bootloader: u-boot
277 partition-layout: inplace
278 `)
279-
280 err = bootloader.HandleAssets()
281 c.Assert(err, NotNil)
282 }
283
284 func (s *PartitionTestSuite) TestHandleAssetsNoHardwareYaml(c *C) {
285 s.makeFakeUbootEnv(c)
286- defaultCacheDir = c.MkDir()
287
288 p := New()
289 bootloader, err := bootloader(p)
290@@ -242,7 +241,7 @@
291 bootloader, err := bootloader(p)
292 c.Assert(err, IsNil)
293
294- p.hardwareSpecFile = makeHardwareYaml(c, `
295+ hardwareSpecFile = makeHardwareYaml(c, `
296 bootloader u-boot
297 `)
298
299
300=== added file 'partition/dirs.go'
301--- partition/dirs.go 1970-01-01 00:00:00 +0000
302+++ partition/dirs.go 2015-06-24 13:28:39 +0000
303@@ -0,0 +1,41 @@
304+// -*- Mode: Go; indent-tabs-mode: t -*-
305+
306+/*
307+ * Copyright (C) 2014-2015 Canonical Ltd
308+ *
309+ * This program is free software: you can redistribute it and/or modify
310+ * it under the terms of the GNU General Public License version 3 as
311+ * published by the Free Software Foundation.
312+ *
313+ * This program is distributed in the hope that it will be useful,
314+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
315+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
316+ * GNU General Public License for more details.
317+ *
318+ * You should have received a copy of the GNU General Public License
319+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
320+ *
321+ */
322+
323+package partition
324+
325+import (
326+ "path/filepath"
327+)
328+
329+// The full path to the cache directory, which is used as a
330+// scratch pad, for downloading new images to and bind mounting the
331+// rootfs.
332+const cacheDirReal = "/writable/cache"
333+
334+var (
335+ // useful for overwriting in the tests
336+ cacheDir = cacheDirReal
337+
338+ // Directory to mount writable root filesystem below the cache
339+ // diretory.
340+ mountTargetReal = filepath.Join(cacheDir, "system")
341+
342+ // useful to override in tests
343+ mountTarget = mountTargetReal
344+)
345
346=== added file 'partition/mount.go'
347--- partition/mount.go 1970-01-01 00:00:00 +0000
348+++ partition/mount.go 2015-06-24 13:28:39 +0000
349@@ -0,0 +1,153 @@
350+// -*- Mode: Go; indent-tabs-mode: t -*-
351+
352+/*
353+ * Copyright (C) 2014-2015 Canonical Ltd
354+ *
355+ * This program is free software: you can redistribute it and/or modify
356+ * it under the terms of the GNU General Public License version 3 as
357+ * published by the Free Software Foundation.
358+ *
359+ * This program is distributed in the hope that it will be useful,
360+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
361+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
362+ * GNU General Public License for more details.
363+ *
364+ * You should have received a copy of the GNU General Public License
365+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
366+ *
367+ */
368+
369+package partition
370+
371+import (
372+ "fmt"
373+ "sort"
374+)
375+
376+// MountOption represents how the partition should be mounted, currently
377+// RO (read-only) and RW (read-write) are supported
378+type MountOption int
379+
380+const (
381+ // RO mounts the partition read-only
382+ RO MountOption = iota
383+ // RW mounts the partition read-only
384+ RW
385+)
386+
387+// mountEntry represents a mount this package has created.
388+type mountEntry struct {
389+ source string
390+ target string
391+
392+ options string
393+
394+ // true if target refers to a bind mount. We could derive this
395+ // from options, but this field saves the effort.
396+ bindMount bool
397+}
398+
399+// mountEntryArray represents an array of mountEntry objects.
400+type mountEntryArray []mountEntry
401+
402+// current mounts that this package has created.
403+var mounts mountEntryArray
404+
405+// Len is part of the sort interface, required to allow sort to work
406+// with an array of Mount objects.
407+func (mounts mountEntryArray) Len() int {
408+ return len(mounts)
409+}
410+
411+// Less is part of the sort interface, required to allow sort to work
412+// with an array of Mount objects.
413+func (mounts mountEntryArray) Less(i, j int) bool {
414+ return mounts[i].target < mounts[j].target
415+}
416+
417+// Swap is part of the sort interface, required to allow sort to work
418+// with an array of Mount objects.
419+func (mounts mountEntryArray) Swap(i, j int) {
420+ mounts[i], mounts[j] = mounts[j], mounts[i]
421+}
422+
423+// removeMountByTarget removes the Mount specified by the target from
424+// the global mounts array.
425+func removeMountByTarget(mnts mountEntryArray, target string) (results mountEntryArray) {
426+
427+ for _, m := range mnts {
428+ if m.target != target {
429+ results = append(results, m)
430+ }
431+ }
432+
433+ return results
434+}
435+
436+// undoMounts unmounts all mounts this package has mounted optionally
437+// only unmounting bind mounts and leaving all remaining mounts.
438+func undoMounts(bindMountsOnly bool) error {
439+
440+ mountsCopy := make(mountEntryArray, len(mounts), cap(mounts))
441+ copy(mountsCopy, mounts)
442+
443+ // reverse sort to ensure unmounts are handled in the correct
444+ // order.
445+ sort.Sort(sort.Reverse(mountsCopy))
446+
447+ // Iterate backwards since we want a reverse-sorted list of
448+ // mounts to ensure we can unmount in order.
449+ for _, mount := range mountsCopy {
450+ if bindMountsOnly && !mount.bindMount {
451+ continue
452+ }
453+
454+ if err := unmountAndRemoveFromGlobalMountList(mount.target); err != nil {
455+ return err
456+ }
457+ }
458+
459+ return nil
460+}
461+
462+// FIXME: use syscall.Mount() here
463+func mount(source, target, options string) (err error) {
464+ var args []string
465+
466+ args = append(args, "/bin/mount")
467+ if options != "" {
468+ args = append(args, fmt.Sprintf("-o%s", options))
469+ }
470+
471+ args = append(args, source)
472+ args = append(args, target)
473+
474+ return runCommand(args...)
475+}
476+
477+// Mount the given directory and add it to the global mounts slice
478+func mountAndAddToGlobalMountList(m mountEntry) (err error) {
479+
480+ err = mount(m.source, m.target, m.options)
481+ if err == nil {
482+ mounts = append(mounts, m)
483+ }
484+
485+ return err
486+}
487+
488+// Unmount the given directory and remove it from the global "mounts" slice
489+func unmountAndRemoveFromGlobalMountList(target string) (err error) {
490+ err = runCommand("/bin/umount", target)
491+ if err != nil {
492+ return err
493+
494+ }
495+
496+ results := removeMountByTarget(mounts, target)
497+
498+ // Update global
499+ mounts = results
500+
501+ return nil
502+}
503
504=== added file 'partition/mount_test.go'
505--- partition/mount_test.go 1970-01-01 00:00:00 +0000
506+++ partition/mount_test.go 2015-06-24 13:28:39 +0000
507@@ -0,0 +1,64 @@
508+// -*- Mode: Go; indent-tabs-mode: t -*-
509+
510+/*
511+ * Copyright (C) 2014-2015 Canonical Ltd
512+ *
513+ * This program is free software: you can redistribute it and/or modify
514+ * it under the terms of the GNU General Public License version 3 as
515+ * published by the Free Software Foundation.
516+ *
517+ * This program is distributed in the hope that it will be useful,
518+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
519+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
520+ * GNU General Public License for more details.
521+ *
522+ * You should have received a copy of the GNU General Public License
523+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
524+ *
525+ */
526+
527+package partition
528+
529+import (
530+ . "gopkg.in/check.v1"
531+)
532+
533+func (s *PartitionTestSuite) TestMountEntryArray(c *C) {
534+ mea := mountEntryArray{}
535+
536+ c.Assert(mea.Len(), Equals, 0)
537+
538+ me := mountEntry{source: "/dev",
539+ target: "/dev",
540+ options: "bind",
541+ bindMount: true}
542+
543+ mea = append(mea, me)
544+ c.Assert(mea.Len(), Equals, 1)
545+
546+ me = mountEntry{source: "/foo",
547+ target: "/foo",
548+ options: "",
549+ bindMount: false}
550+
551+ mea = append(mea, me)
552+ c.Assert(mea.Len(), Equals, 2)
553+
554+ c.Assert(mea.Less(0, 1), Equals, true)
555+ c.Assert(mea.Less(1, 0), Equals, false)
556+
557+ mea.Swap(0, 1)
558+ c.Assert(mea.Less(0, 1), Equals, false)
559+ c.Assert(mea.Less(1, 0), Equals, true)
560+
561+ results := removeMountByTarget(mea, "invalid")
562+
563+ // No change expected
564+ c.Assert(results, DeepEquals, mea)
565+
566+ results = removeMountByTarget(mea, "/dev")
567+
568+ c.Assert(len(results), Equals, 1)
569+ c.Assert(results[0], Equals, mountEntry{source: "/foo",
570+ target: "/foo", options: "", bindMount: false})
571+}
572
573=== modified file 'partition/partition.go'
574--- partition/partition.go 2015-06-09 17:43:20 +0000
575+++ partition/partition.go 2015-06-24 13:28:39 +0000
576@@ -23,50 +23,37 @@
577 import (
578 "errors"
579 "fmt"
580- "io/ioutil"
581 "os"
582 "os/signal"
583 "path/filepath"
584 "regexp"
585- "sort"
586 "strings"
587+ "sync"
588 "syscall"
589
590- "gopkg.in/yaml.v2"
591-
592 "launchpad.net/snappy/logger"
593 )
594
595-var signalHandlerRegistered = false
596-
597-// Name of writable user data partition label as created by
598-// ubuntu-device-flash(1).
599-const writablePartitionLabel = "writable"
600-
601-// Name of primary root filesystem partition label as created by
602-// ubuntu-device-flash(1).
603-const rootfsAlabel = "system-a"
604-
605-// Name of primary root filesystem partition label as created by
606-// ubuntu-device-flash(1). Note that this partition will
607-// only be present if this is an A/B upgrade system.
608-const rootfsBlabel = "system-b"
609-
610-// name of boot partition label as created by ubuntu-device-flash(1).
611-const bootPartitionLabel = "system-boot"
612-
613-// its useful to override this in tests
614-const realDefaultCacheDir = "/writable/cache"
615-
616-// FIXME: Should query system-image-cli (see bug LP:#1380574).
617-var defaultCacheDir = realDefaultCacheDir
618-
619-// Directory to mount writable root filesystem below the cache
620-// diretory.
621-const mountTarget = "system"
622-
623-// File creation mode used when any directories are created
624-const dirMode = 0750
625+const (
626+ // Name of writable user data partition label as created by
627+ // ubuntu-device-flash(1).
628+ writablePartitionLabel = "writable"
629+
630+ // Name of primary root filesystem partition label as created by
631+ // ubuntu-device-flash(1).
632+ rootfsAlabel = "system-a"
633+
634+ // Name of primary root filesystem partition label as created by
635+ // ubuntu-device-flash(1). Note that this partition will
636+ // only be present if this is an A/B upgrade system.
637+ rootfsBlabel = "system-b"
638+
639+ // name of boot partition label as created by ubuntu-device-flash(1).
640+ bootPartitionLabel = "system-boot"
641+
642+ // File creation mode used when any directories are created
643+ dirMode = 0750
644+)
645
646 var (
647 // ErrBootloader is returned if the bootloader can not be determined
648@@ -79,41 +66,6 @@
649 // ErrNoDualPartition is returned if you try to use a dual
650 // partition feature on a single partition
651 ErrNoDualPartition = errors.New("No dual partition")
652-
653- // ErrNoHardwareYaml is returned when no hardware yaml is found in
654- // the update, this means that there is nothing to process with regards
655- // to device parts.
656- ErrNoHardwareYaml = errors.New("no hardware.yaml")
657-)
658-
659-// Declarative specification of the type of system which specifies such
660-// details as:
661-//
662-// - the location of initrd+kernel within the system-image archive.
663-// - the location of hardware-specific .dtb files within the
664-// system-image archive.
665-// - the type of bootloader that should be used for this system.
666-// - expected system partition layout (single or dual rootfs's).
667-const hardwareSpecFile = "hardware.yaml"
668-
669-// Directory that _may_ get automatically created on unpack that
670-// contains updated hardware-specific boot assets (such as initrd, kernel)
671-const assetsDir = "assets"
672-
673-// Directory that _may_ get automatically created on unpack that
674-// contains updated hardware-specific assets that require flashing
675-// to the disk (such as uBoot, MLO)
676-const flashAssetsDir = "flashtool-assets"
677-
678-// MountOption represents how the partition should be mounted, currently
679-// RO (read-only) and RW (read-write) are supported
680-type MountOption int
681-
682-const (
683- // RO mounts the partition read-only
684- RO MountOption = iota
685- // RW mounts the partition read-only
686- RW
687 )
688
689 // Interface provides the interface to interact with a partition
690@@ -134,24 +86,6 @@
691 BootloaderDir() string
692 }
693
694-// mountEntry represents a mount this package has created.
695-type mountEntry struct {
696- source string
697- target string
698-
699- options string
700-
701- // true if target refers to a bind mount. We could derive this
702- // from options, but this field saves the effort.
703- bindMount bool
704-}
705-
706-// mountEntryArray represents an array of mountEntry objects.
707-type mountEntryArray []mountEntry
708-
709-// current mounts that this package has created.
710-var mounts mountEntryArray
711-
712 // Partition is the type to interact with the partition
713 type Partition struct {
714 // all partitions
715@@ -159,8 +93,6 @@
716
717 // just root partitions
718 roots []string
719-
720- hardwareSpecFile string
721 }
722
723 type blockDevice struct {
724@@ -178,77 +110,10 @@
725 mountpoint string
726 }
727
728-// Representation of HARDWARE_SPEC_FILE
729-type hardwareSpecType struct {
730- Kernel string `yaml:"kernel"`
731- Initrd string `yaml:"initrd"`
732- DtbDir string `yaml:"dtbs"`
733- PartitionLayout string `yaml:"partition-layout"`
734- Bootloader bootloaderName `yaml:"bootloader"`
735-}
736-
737-// Len is part of the sort interface, required to allow sort to work
738-// with an array of Mount objects.
739-func (mounts mountEntryArray) Len() int {
740- return len(mounts)
741-}
742-
743-// Less is part of the sort interface, required to allow sort to work
744-// with an array of Mount objects.
745-func (mounts mountEntryArray) Less(i, j int) bool {
746- return mounts[i].target < mounts[j].target
747-}
748-
749-// Swap is part of the sort interface, required to allow sort to work
750-// with an array of Mount objects.
751-func (mounts mountEntryArray) Swap(i, j int) {
752- mounts[i], mounts[j] = mounts[j], mounts[i]
753-}
754-
755-// removeMountByTarget removes the Mount specified by the target from
756-// the global mounts array.
757-func removeMountByTarget(mnts mountEntryArray, target string) (results mountEntryArray) {
758-
759- for _, m := range mnts {
760- if m.target != target {
761- results = append(results, m)
762- }
763- }
764-
765- return results
766-}
767+var once sync.Once
768
769 func init() {
770- if !signalHandlerRegistered {
771- setupSignalHandler()
772- signalHandlerRegistered = true
773- }
774-}
775-
776-// undoMounts unmounts all mounts this package has mounted optionally
777-// only unmounting bind mounts and leaving all remaining mounts.
778-func undoMounts(bindMountsOnly bool) error {
779-
780- mountsCopy := make(mountEntryArray, len(mounts), cap(mounts))
781- copy(mountsCopy, mounts)
782-
783- // reverse sort to ensure unmounts are handled in the correct
784- // order.
785- sort.Sort(sort.Reverse(mountsCopy))
786-
787- // Iterate backwards since we want a reverse-sorted list of
788- // mounts to ensure we can unmount in order.
789- for _, mount := range mountsCopy {
790- if bindMountsOnly && !mount.bindMount {
791- continue
792- }
793-
794- if err := unmountAndRemoveFromGlobalMountList(mount.target); err != nil {
795- return err
796- }
797- }
798-
799- return nil
800+ once.Do(setupSignalHandler)
801 }
802
803 func signalHandler(sig os.Signal) {
804@@ -291,66 +156,6 @@
805 return labels
806 }
807
808-func mount(source, target, options string) (err error) {
809- var args []string
810-
811- args = append(args, "/bin/mount")
812- if options != "" {
813- args = append(args, fmt.Sprintf("-o%s", options))
814- }
815-
816- args = append(args, source)
817- args = append(args, target)
818-
819- return runCommand(args...)
820-}
821-
822-// Mount the given directory and add it to the global mounts slice
823-func mountAndAddToGlobalMountList(m mountEntry) (err error) {
824-
825- err = mount(m.source, m.target, m.options)
826- if err == nil {
827- mounts = append(mounts, m)
828- }
829-
830- return err
831-}
832-
833-// Unmount the given directory and remove it from the global "mounts" slice
834-func unmountAndRemoveFromGlobalMountList(target string) (err error) {
835- err = runCommand("/bin/umount", target)
836- if err != nil {
837- return err
838-
839- }
840-
841- results := removeMountByTarget(mounts, target)
842-
843- // Update global
844- mounts = results
845-
846- return nil
847-}
848-
849-// Run fsck(8) on specified device.
850-func fsck(device string) (err error) {
851- return runCommand(
852- "/sbin/fsck",
853- "-M", // Paranoia - don't fsck if already mounted
854- "-av", device)
855-}
856-
857-// Returns the position of the string in the given slice or -1 if its not found
858-func stringInSlice(slice []string, value string) int {
859- for i, s := range slice {
860- if s == value {
861- return i
862- }
863- }
864-
865- return -1
866-}
867-
868 var runLsblk = func() (out []string, err error) {
869 output, err := runCommandWithStdout(
870 "/bin/lsblk",
871@@ -443,20 +248,11 @@
872 return partitions, nil
873 }
874
875-var makeDirectory = func(path string, mode os.FileMode) error {
876- return os.MkdirAll(path, mode)
877-}
878-
879-func (p *Partition) makeMountPoint() (err error) {
880- return makeDirectory(p.MountTarget(), dirMode)
881-}
882-
883 // New creates a new partition type
884 func New() *Partition {
885 p := new(Partition)
886
887 p.getPartitionDetails()
888- p.hardwareSpecFile = filepath.Join(p.cacheDir(), hardwareSpecFile)
889
890 return p
891 }
892@@ -500,7 +296,7 @@
893 }()
894 }
895
896- err = f(p.MountTarget())
897+ err = f(mountTarget)
898 return err
899 }
900
901@@ -542,47 +338,6 @@
902 return isNextBootOther(bootloader)
903 }
904
905-// Returns the full path to the cache directory, which is used as a
906-// scratch pad, for downloading new images to and bind mounting the
907-// rootfs.
908-func (p *Partition) cacheDir() string {
909- return defaultCacheDir
910-}
911-
912-func (p *Partition) hardwareSpec() (hardwareSpecType, error) {
913- h := hardwareSpecType{}
914-
915- data, err := ioutil.ReadFile(p.hardwareSpecFile)
916- // if hardware.yaml does not exist it just means that there was no
917- // device part in the update.
918- if os.IsNotExist(err) {
919- return h, ErrNoHardwareYaml
920- } else if err != nil {
921- return h, err
922- }
923-
924- if err := yaml.Unmarshal([]byte(data), &h); err != nil {
925- return h, err
926- }
927-
928- return h, nil
929-}
930-
931-// Return full path to the main assets directory
932-func (p *Partition) assetsDir() string {
933- return filepath.Join(p.cacheDir(), assetsDir)
934-}
935-
936-// Return the full path to the hardware-specific flash assets directory.
937-func (p *Partition) flashAssetsDir() string {
938- return filepath.Join(p.cacheDir(), flashAssetsDir)
939-}
940-
941-// MountTarget gets the full path to the mount target directory
942-func (p *Partition) MountTarget() string {
943- return filepath.Join(p.cacheDir(), mountTarget)
944-}
945-
946 func (p *Partition) getPartitionDetails() (err error) {
947 p.partitions, err = loadPartitionDetails()
948 if err != nil {
949@@ -676,11 +431,13 @@
950 func (p *Partition) mountOtherRootfs(readOnly bool) (err error) {
951 var other *blockDevice
952
953- p.makeMountPoint()
954+ if err := os.MkdirAll(mountTarget, dirMode); err != nil {
955+ return err
956+ }
957
958 other = p.otherRootPartition()
959
960- m := mountEntry{source: other.device, target: p.MountTarget()}
961+ m := mountEntry{source: other.device, target: mountTarget}
962
963 if readOnly {
964 m.options = "ro"
965@@ -707,9 +464,7 @@
966
967 // Ensure the other partition is mounted read-only.
968 func (p *Partition) ensureOtherMountedRO() (err error) {
969- mountpoint := p.MountTarget()
970-
971- if err = runCommand("/bin/mountpoint", mountpoint); err == nil {
972+ if err = runCommand("/bin/mountpoint", mountTarget); err == nil {
973 // already mounted
974 return err
975 }
976@@ -741,14 +496,14 @@
977
978 return mountAndAddToGlobalMountList(mountEntry{
979 source: other.device,
980- target: p.MountTarget()})
981+ target: mountTarget})
982 }
983 // r/w -> r/o: no fsck required.
984- return mount(other.device, p.MountTarget(), "remount,ro")
985+ return mount(other.device, mountTarget, "remount,ro")
986 }
987
988 func (p *Partition) unmountOtherRootfs() (err error) {
989- return unmountAndRemoveFromGlobalMountList(p.MountTarget())
990+ return unmountAndRemoveFromGlobalMountList(mountTarget)
991 }
992
993 // The bootloader requires a few filesystems to be mounted when
994@@ -773,7 +528,7 @@
995 }
996
997 for _, fs := range requiredChrootMounts {
998- target := filepath.Join(p.MountTarget(), fs)
999+ target := filepath.Join(mountTarget, fs)
1000
1001 err := mountAndAddToGlobalMountList(mountEntry{source: fs,
1002 target: target,
1003@@ -787,11 +542,11 @@
1004 // Grub also requires access to both rootfs's when run from
1005 // within a chroot (to allow it to create menu entries for
1006 // both), so bindmount the real rootfs.
1007- targetInChroot := filepath.Join(p.MountTarget(), p.MountTarget())
1008+ targetInChroot := filepath.Join(mountTarget, mountTarget)
1009
1010 // FIXME: we should really remove this after the unmount
1011
1012- if err = makeDirectory(targetInChroot, dirMode); err != nil {
1013+ if err = os.MkdirAll(targetInChroot, dirMode); err != nil {
1014 return err
1015 }
1016
1017
1018=== modified file 'partition/partition_test.go'
1019--- partition/partition_test.go 2015-06-09 17:43:20 +0000
1020+++ partition/partition_test.go 2015-06-24 13:28:39 +0000
1021@@ -44,14 +44,13 @@
1022 return err
1023 }
1024
1025-func mockMakeDirectory(path string, mode os.FileMode) error {
1026- return nil
1027-}
1028-
1029 func (s *PartitionTestSuite) SetUpTest(c *C) {
1030 s.tempdir = c.MkDir()
1031 runLsblk = mockRunLsblkDualSnappy
1032
1033+ // custom mount target
1034+ mountTarget = c.MkDir()
1035+
1036 // setup fake paths for grub
1037 bootloaderGrubDir = filepath.Join(s.tempdir, "boot", "grub")
1038 bootloaderGrubConfigFile = filepath.Join(bootloaderGrubDir, "grub.cfg")
1039@@ -72,8 +71,10 @@
1040
1041 // always restore what we might have mocked away
1042 runCommand = runCommandImpl
1043- defaultCacheDir = realDefaultCacheDir
1044 bootloader = bootloaderImpl
1045+ cacheDir = cacheDirReal
1046+ hardwareSpecFile = hardwareSpecFileReal
1047+ mountTarget = mountTargetReal
1048
1049 // grub vars
1050 bootloaderGrubConfigFile = bootloaderGrubConfigFileReal
1051@@ -109,20 +110,6 @@
1052 return tmp.Name()
1053 }
1054
1055-func (s *PartitionTestSuite) TestHardwareSpec(c *C) {
1056- p := New()
1057- c.Assert(p, NotNil)
1058-
1059- p.hardwareSpecFile = makeHardwareYaml(c, "")
1060- hw, err := p.hardwareSpec()
1061- c.Assert(err, IsNil)
1062- c.Assert(hw.Kernel, Equals, "assets/vmlinuz")
1063- c.Assert(hw.Initrd, Equals, "assets/initrd.img")
1064- c.Assert(hw.DtbDir, Equals, "assets/dtbs")
1065- c.Assert(hw.PartitionLayout, Equals, bootloaderSystemAB)
1066- c.Assert(hw.Bootloader, Equals, bootloaderNameUboot)
1067-}
1068-
1069 func mockRunLsblkDualSnappy() (output []string, err error) {
1070 dualData := `
1071 NAME="sda" LABEL="" PKNAME="" MOUNTPOINT=""
1072@@ -136,46 +123,6 @@
1073 return strings.Split(dualData, "\n"), err
1074 }
1075
1076-func (s *PartitionTestSuite) TestMountEntryArray(c *C) {
1077- mea := mountEntryArray{}
1078-
1079- c.Assert(mea.Len(), Equals, 0)
1080-
1081- me := mountEntry{source: "/dev",
1082- target: "/dev",
1083- options: "bind",
1084- bindMount: true}
1085-
1086- mea = append(mea, me)
1087- c.Assert(mea.Len(), Equals, 1)
1088-
1089- me = mountEntry{source: "/foo",
1090- target: "/foo",
1091- options: "",
1092- bindMount: false}
1093-
1094- mea = append(mea, me)
1095- c.Assert(mea.Len(), Equals, 2)
1096-
1097- c.Assert(mea.Less(0, 1), Equals, true)
1098- c.Assert(mea.Less(1, 0), Equals, false)
1099-
1100- mea.Swap(0, 1)
1101- c.Assert(mea.Less(0, 1), Equals, false)
1102- c.Assert(mea.Less(1, 0), Equals, true)
1103-
1104- results := removeMountByTarget(mea, "invalid")
1105-
1106- // No change expected
1107- c.Assert(results, DeepEquals, mea)
1108-
1109- results = removeMountByTarget(mea, "/dev")
1110-
1111- c.Assert(len(results), Equals, 1)
1112- c.Assert(results[0], Equals, mountEntry{source: "/foo",
1113- target: "/foo", options: "", bindMount: false})
1114-}
1115-
1116 func (s *PartitionTestSuite) TestSnappyDualRoot(c *C) {
1117 p := New()
1118 c.Assert(p.dualRootPartitions(), Equals, true)
1119@@ -218,14 +165,13 @@
1120 return nil
1121 })
1122 c.Assert(err, IsNil)
1123- c.Assert(reportedRoot, Equals, (&Partition{}).MountTarget())
1124+ c.Assert(reportedRoot, Equals, mountTarget)
1125 }
1126
1127 func (s *PartitionTestSuite) TestRunWithOtherDualParitionRWFuncErr(c *C) {
1128 c.Assert(mounts, DeepEquals, mountEntryArray(nil))
1129
1130 runCommand = mockRunCommand
1131- makeDirectory = mockMakeDirectory
1132
1133 p := New()
1134 err := p.RunWithOther(RW, func(otherRoot string) (err error) {
1135@@ -239,8 +185,12 @@
1136 // ensure cleanup happend
1137
1138 // FIXME: mounts are global
1139- expected := mountEntry{source: "/dev/sda4",
1140- target: "/writable/cache/system", options: "", bindMount: false}
1141+ expected := mountEntry{
1142+ source: "/dev/sda4",
1143+ target: mountTarget,
1144+ options: "",
1145+ bindMount: false,
1146+ }
1147
1148 // At program exit, "other" should still be mounted
1149 c.Assert(mounts, DeepEquals, mountEntryArray{expected})
1150@@ -295,8 +245,12 @@
1151 c.Assert(p, NotNil)
1152
1153 p.mountOtherRootfs(false)
1154- expected := mountEntry{source: "/dev/sda4",
1155- target: "/writable/cache/system", options: "", bindMount: false}
1156+ expected := mountEntry{
1157+ source: "/dev/sda4",
1158+ target: mountTarget,
1159+ options: "",
1160+ bindMount: false,
1161+ }
1162
1163 c.Assert(mounts, DeepEquals, mountEntryArray{expected})
1164
1165@@ -313,26 +267,26 @@
1166
1167 p.bindmountRequiredFilesystems()
1168 c.Assert(mounts, DeepEquals, mountEntryArray{
1169- mountEntry{source: "/dev", target: p.MountTarget() + "/dev",
1170- options: "bind", bindMount: true},
1171-
1172- mountEntry{source: "/proc", target: p.MountTarget() + "/proc",
1173- options: "bind", bindMount: true},
1174-
1175- mountEntry{source: "/sys", target: p.MountTarget() + "/sys",
1176- options: "bind", bindMount: true},
1177- mountEntry{source: "/boot/efi", target: p.MountTarget() + "/boot/efi",
1178+ mountEntry{source: "/dev", target: mountTarget + "/dev",
1179+ options: "bind", bindMount: true},
1180+
1181+ mountEntry{source: "/proc", target: mountTarget + "/proc",
1182+ options: "bind", bindMount: true},
1183+
1184+ mountEntry{source: "/sys", target: mountTarget + "/sys",
1185+ options: "bind", bindMount: true},
1186+ mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
1187 options: "bind", bindMount: true},
1188
1189 // this comes from the grub bootloader via AdditionalBindMounts
1190- mountEntry{source: "/boot/grub", target: p.MountTarget() + "/boot/grub",
1191+ mountEntry{source: "/boot/grub", target: mountTarget + "/boot/grub",
1192 options: "bind", bindMount: true},
1193
1194 // Required to allow grub inside the chroot to access
1195 // the "current" rootfs outside the chroot (used
1196 // to generate the grub menuitems).
1197 mountEntry{source: "/",
1198- target: p.MountTarget() + p.MountTarget(),
1199+ target: mountTarget + mountTarget,
1200 options: "bind,ro", bindMount: true},
1201 })
1202 p.unmountRequiredFilesystems()
1203@@ -351,23 +305,23 @@
1204 p.bindmountRequiredFilesystems()
1205 c.Assert(mounts, DeepEquals, mountEntryArray{
1206
1207- mountEntry{source: "/dev/sda4", target: "/writable/cache/system",
1208+ mountEntry{source: "/dev/sda4", target: mountTarget,
1209 options: "", bindMount: false},
1210
1211- mountEntry{source: "/dev", target: p.MountTarget() + "/dev",
1212- options: "bind", bindMount: true},
1213-
1214- mountEntry{source: "/proc", target: p.MountTarget() + "/proc",
1215- options: "bind", bindMount: true},
1216-
1217- mountEntry{source: "/sys", target: p.MountTarget() + "/sys",
1218- options: "bind", bindMount: true},
1219-
1220- mountEntry{source: "/boot/efi", target: p.MountTarget() + "/boot/efi",
1221+ mountEntry{source: "/dev", target: mountTarget + "/dev",
1222+ options: "bind", bindMount: true},
1223+
1224+ mountEntry{source: "/proc", target: mountTarget + "/proc",
1225+ options: "bind", bindMount: true},
1226+
1227+ mountEntry{source: "/sys", target: mountTarget + "/sys",
1228+ options: "bind", bindMount: true},
1229+
1230+ mountEntry{source: "/boot/efi", target: mountTarget + "/boot/efi",
1231 options: "bind", bindMount: true},
1232
1233 mountEntry{source: "/",
1234- target: p.MountTarget() + p.MountTarget(),
1235+ target: mountTarget + mountTarget,
1236 options: "bind,ro", bindMount: true},
1237 })
1238
1239@@ -375,8 +329,12 @@
1240 undoMounts(true)
1241
1242 c.Assert(mounts, DeepEquals, mountEntryArray{
1243- mountEntry{source: "/dev/sda4", target: "/writable/cache/system",
1244- options: "", bindMount: false},
1245+ mountEntry{
1246+ source: "/dev/sda4",
1247+ target: mountTarget,
1248+ options: "",
1249+ bindMount: false,
1250+ },
1251 })
1252
1253 // should unmount everything
1254
1255=== modified file 'partition/utils.go'
1256--- partition/utils.go 2015-05-19 14:09:19 +0000
1257+++ partition/utils.go 2015-06-24 13:28:39 +0000
1258@@ -69,3 +69,22 @@
1259
1260 // This is a var instead of a function to making mocking in the tests easier
1261 var runCommandWithStdout = runCommandWithStdoutImpl
1262+
1263+// Run fsck(8) on specified device.
1264+func fsck(device string) (err error) {
1265+ return runCommand(
1266+ "/sbin/fsck",
1267+ "-M", // Paranoia - don't fsck if already mounted
1268+ "-av", device)
1269+}
1270+
1271+// Returns the position of the string in the given slice or -1 if its not found
1272+func stringInSlice(slice []string, value string) int {
1273+ for i, s := range slice {
1274+ if s == value {
1275+ return i
1276+ }
1277+ }
1278+
1279+ return -1
1280+}

Subscribers

People subscribed via source and target branches