github.com/kubiko/snapd@v0.0.0-20201013125620-d4f3094d9ddf/gadget/install/content_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019-2020 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 install_test 21 22 import ( 23 "errors" 24 "io/ioutil" 25 "os" 26 "path/filepath" 27 28 . "gopkg.in/check.v1" 29 30 "github.com/snapcore/snapd/boot" 31 "github.com/snapcore/snapd/dirs" 32 "github.com/snapcore/snapd/gadget" 33 "github.com/snapcore/snapd/gadget/install" 34 "github.com/snapcore/snapd/testutil" 35 ) 36 37 type contentTestSuite struct { 38 testutil.BaseTest 39 40 dir string 41 42 gadgetRoot string 43 44 mockMountPoint string 45 mockMountCalls []struct{ source, target, fstype string } 46 mockUnmountCalls []string 47 48 mockMountErr error 49 } 50 51 var _ = Suite(&contentTestSuite{}) 52 53 func (s *contentTestSuite) SetUpTest(c *C) { 54 s.BaseTest.SetUpTest(c) 55 56 s.dir = c.MkDir() 57 58 s.mockMountErr = nil 59 s.mockMountCalls = nil 60 s.mockUnmountCalls = nil 61 62 s.gadgetRoot = c.MkDir() 63 err := makeMockGadget(s.gadgetRoot, gadgetContent) 64 c.Assert(err, IsNil) 65 66 s.mockMountPoint = c.MkDir() 67 restore := install.MockContentMountpoint(s.mockMountPoint) 68 s.AddCleanup(restore) 69 70 restore = install.MockSysMount(func(source, target, fstype string, flags uintptr, data string) error { 71 s.mockMountCalls = append(s.mockMountCalls, struct{ source, target, fstype string }{source, target, fstype}) 72 return s.mockMountErr 73 }) 74 s.AddCleanup(restore) 75 76 restore = install.MockSysUnmount(func(target string, flags int) error { 77 s.mockUnmountCalls = append(s.mockUnmountCalls, target) 78 return nil 79 }) 80 s.AddCleanup(restore) 81 } 82 83 var mockOnDiskStructureBiosBoot = gadget.OnDiskStructure{ 84 Node: "/dev/node1", 85 LaidOutStructure: gadget.LaidOutStructure{ 86 VolumeStructure: &gadget.VolumeStructure{ 87 Name: "BIOS Boot", 88 Size: 1 * 1024 * 1024, 89 Type: "DA,21686148-6449-6E6F-744E-656564454649", 90 Content: []gadget.VolumeContent{ 91 { 92 Image: "pc-core.img", 93 }, 94 }, 95 }, 96 StartOffset: 0, 97 Index: 1, 98 }, 99 } 100 101 var mockOnDiskStructureSystemSeed = gadget.OnDiskStructure{ 102 Node: "/dev/node2", 103 LaidOutStructure: gadget.LaidOutStructure{ 104 VolumeStructure: &gadget.VolumeStructure{ 105 Name: "Recovery", 106 Size: 1258291200, 107 Type: "EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B", 108 Role: "system-seed", 109 Label: "ubuntu-seed", 110 Filesystem: "vfat", 111 Content: []gadget.VolumeContent{ 112 { 113 Source: "grubx64.efi", 114 Target: "EFI/boot/grubx64.efi", 115 }, 116 }, 117 }, 118 StartOffset: 2097152, 119 Index: 2, 120 }, 121 } 122 123 func makeMockGadget(gadgetRoot, gadgetContent string) error { 124 if err := os.MkdirAll(filepath.Join(gadgetRoot, "meta"), 0755); err != nil { 125 return err 126 } 127 if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "meta", "gadget.yaml"), []byte(gadgetContent), 0644); err != nil { 128 return err 129 } 130 if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "pc-boot.img"), []byte("pc-boot.img content"), 0644); err != nil { 131 return err 132 } 133 if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "pc-core.img"), []byte("pc-core.img content"), 0644); err != nil { 134 return err 135 } 136 if err := ioutil.WriteFile(filepath.Join(gadgetRoot, "grubx64.efi"), []byte("grubx64.efi content"), 0644); err != nil { 137 return err 138 } 139 140 return nil 141 } 142 143 const gadgetContent = `volumes: 144 pc: 145 bootloader: grub 146 structure: 147 - name: mbr 148 type: mbr 149 size: 440 150 content: 151 - image: pc-boot.img 152 - name: BIOS Boot 153 type: DA,21686148-6449-6E6F-744E-656564454649 154 size: 1M 155 offset: 1M 156 offset-write: mbr+92 157 content: 158 - image: pc-core.img 159 - name: Recovery 160 role: system-seed 161 filesystem: vfat 162 # UEFI will boot the ESP partition by default first 163 type: EF,C12A7328-F81F-11D2-BA4B-00A0C93EC93B 164 size: 1200M 165 content: 166 - source: grubx64.efi 167 target: EFI/boot/grubx64.efi 168 - name: Writable 169 role: system-data 170 filesystem: ext4 171 type: 83,0FC63DAF-8483-4772-8E79-3D69D8477DE4 172 size: 1200M 173 ` 174 175 type mockContentChange struct { 176 path string 177 change *gadget.ContentChange 178 } 179 180 type mockWriteObserver struct { 181 content map[string][]*mockContentChange 182 observeErr error 183 expectedStruct *gadget.LaidOutStructure 184 c *C 185 } 186 187 func (m *mockWriteObserver) Observe(op gadget.ContentOperation, sourceStruct *gadget.LaidOutStructure, 188 targetRootDir, relativeTargetPath string, data *gadget.ContentChange) (gadget.ContentChangeAction, error) { 189 if m.content == nil { 190 m.content = make(map[string][]*mockContentChange) 191 } 192 m.content[targetRootDir] = append(m.content[targetRootDir], 193 &mockContentChange{path: relativeTargetPath, change: data}) 194 m.c.Assert(sourceStruct, NotNil) 195 m.c.Check(sourceStruct, DeepEquals, m.expectedStruct) 196 return gadget.ChangeApply, m.observeErr 197 } 198 199 func (s *contentTestSuite) TestWriteFilesystemContent(c *C) { 200 for _, tc := range []struct { 201 mountErr error 202 unmountErr error 203 observeErr error 204 err string 205 }{ 206 { 207 mountErr: nil, 208 unmountErr: nil, 209 err: "", 210 }, { 211 mountErr: errors.New("mount error"), 212 unmountErr: nil, 213 err: "cannot mount filesystem .*: mount error", 214 }, { 215 mountErr: nil, 216 unmountErr: errors.New("unmount error"), 217 err: "unmount error", 218 }, { 219 observeErr: errors.New("observe error"), 220 err: "cannot create filesystem image: cannot write filesystem content of source:grubx64.efi: cannot observe file write: observe error", 221 }, 222 } { 223 mockMountpoint := c.MkDir() 224 225 restore := install.MockContentMountpoint(mockMountpoint) 226 defer restore() 227 228 restore = install.MockSysMount(func(source, target, fstype string, flags uintptr, data string) error { 229 return tc.mountErr 230 }) 231 defer restore() 232 233 restore = install.MockSysUnmount(func(target string, flags int) error { 234 return tc.unmountErr 235 }) 236 defer restore() 237 238 // copy existing mock 239 m := mockOnDiskStructureSystemSeed 240 m.LaidOutContent = []gadget.LaidOutContent{ 241 { 242 VolumeContent: &gadget.VolumeContent{ 243 Source: "grubx64.efi", 244 Target: "EFI/boot/grubx64.efi", 245 }, 246 }, 247 } 248 obs := &mockWriteObserver{ 249 c: c, 250 observeErr: tc.observeErr, 251 expectedStruct: &m.LaidOutStructure, 252 } 253 err := install.WriteContent(&m, s.gadgetRoot, obs) 254 if tc.err == "" { 255 c.Assert(err, IsNil) 256 } else { 257 c.Assert(err, ErrorMatches, tc.err) 258 } 259 260 if err == nil { 261 // the target file system is mounted on a directory named after the structure index 262 content, err := ioutil.ReadFile(filepath.Join(mockMountpoint, "2", "EFI/boot/grubx64.efi")) 263 c.Assert(err, IsNil) 264 c.Check(string(content), Equals, "grubx64.efi content") 265 c.Assert(obs.content, DeepEquals, map[string][]*mockContentChange{ 266 filepath.Join(mockMountpoint, "2"): { 267 { 268 path: "EFI/boot/grubx64.efi", 269 change: &gadget.ContentChange{After: filepath.Join(s.gadgetRoot, "grubx64.efi")}, 270 }, 271 }, 272 }) 273 } 274 } 275 } 276 277 func (s *contentTestSuite) TestWriteRawContent(c *C) { 278 mockNode := filepath.Join(s.dir, "mock-node") 279 err := ioutil.WriteFile(mockNode, nil, 0644) 280 c.Assert(err, IsNil) 281 282 // copy existing mock 283 m := mockOnDiskStructureBiosBoot 284 m.Node = mockNode 285 m.LaidOutContent = []gadget.LaidOutContent{ 286 { 287 VolumeContent: &gadget.VolumeContent{ 288 Image: "pc-core.img", 289 }, 290 StartOffset: 2, 291 Size: gadget.Size(len("pc-core.img content")), 292 }, 293 } 294 295 err = install.WriteContent(&m, s.gadgetRoot, nil) 296 c.Assert(err, IsNil) 297 298 content, err := ioutil.ReadFile(m.Node) 299 c.Assert(err, IsNil) 300 // note the 2 zero byte start offset 301 c.Check(string(content), Equals, "\x00\x00pc-core.img content") 302 } 303 304 func (s *contentTestSuite) TestMountFilesystem(c *C) { 305 dirs.SetRootDir(c.MkDir()) 306 defer dirs.SetRootDir("") 307 308 // mounting will only happen for devices with a label 309 mockOnDiskStructureBiosBoot.Label = "bios-boot" 310 defer func() { mockOnDiskStructureBiosBoot.Label = "" }() 311 312 err := install.MountFilesystem(&mockOnDiskStructureBiosBoot, boot.InitramfsRunMntDir) 313 c.Assert(err, ErrorMatches, "cannot mount a partition with no filesystem") 314 315 // mount a filesystem... 316 err = install.MountFilesystem(&mockOnDiskStructureSystemSeed, boot.InitramfsRunMntDir) 317 c.Assert(err, IsNil) 318 319 // ...and check if it was mounted at the right mount point 320 c.Check(s.mockMountCalls, HasLen, 1) 321 c.Check(s.mockMountCalls, DeepEquals, []struct{ source, target, fstype string }{ 322 {"/dev/node2", boot.InitramfsUbuntuSeedDir, "vfat"}, 323 }) 324 325 // now try to mount a filesystem with no label 326 mockOnDiskStructureSystemSeed.Label = "" 327 defer func() { mockOnDiskStructureSystemSeed.Label = "ubuntu-seed" }() 328 329 err = install.MountFilesystem(&mockOnDiskStructureSystemSeed, boot.InitramfsRunMntDir) 330 c.Assert(err, ErrorMatches, "cannot mount a filesystem with no label") 331 }