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