Merge lp:~mvo/snappy/snappy-improved-developer-mode2 into lp:~snappy-dev/snappy/snappy-moved-to-github
- snappy-improved-developer-mode2
- Merge into 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 |
Related bugs: |
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
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/
- 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 | +} |
I'm pretending you removed the stray comment :-) feel free to top-approve once that's done. Also, commit message.