github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/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.ResolvedContent = []gadget.ResolvedContent{ 242 { 243 VolumeContent: &gadget.VolumeContent{ 244 UnresolvedSource: "grubx64.efi", 245 Target: "EFI/boot/grubx64.efi", 246 }, 247 ResolvedSource: filepath.Join(s.gadgetRoot, "grubx64.efi"), 248 }, 249 } 250 obs := &mockWriteObserver{ 251 c: c, 252 observeErr: tc.observeErr, 253 expectedStruct: &m.LaidOutStructure, 254 } 255 err := install.WriteContent(&m, s.gadgetRoot, obs) 256 if tc.err == "" { 257 c.Assert(err, IsNil) 258 } else { 259 c.Assert(err, ErrorMatches, tc.err) 260 } 261 262 if err == nil { 263 // the target file system is mounted on a directory named after the structure index 264 content, err := ioutil.ReadFile(filepath.Join(mockMountpoint, "2", "EFI/boot/grubx64.efi")) 265 c.Assert(err, IsNil) 266 c.Check(string(content), Equals, "grubx64.efi content") 267 c.Assert(obs.content, DeepEquals, map[string][]*mockContentChange{ 268 filepath.Join(mockMountpoint, "2"): { 269 { 270 path: "EFI/boot/grubx64.efi", 271 change: &gadget.ContentChange{After: filepath.Join(s.gadgetRoot, "grubx64.efi")}, 272 }, 273 }, 274 }) 275 } 276 } 277 } 278 279 func (s *contentTestSuite) TestWriteRawContent(c *C) { 280 mockNode := filepath.Join(s.dir, "mock-node") 281 err := ioutil.WriteFile(mockNode, nil, 0644) 282 c.Assert(err, IsNil) 283 284 // copy existing mock 285 m := mockOnDiskStructureBiosBoot 286 m.Node = mockNode 287 m.LaidOutContent = []gadget.LaidOutContent{ 288 { 289 VolumeContent: &gadget.VolumeContent{ 290 Image: "pc-core.img", 291 }, 292 StartOffset: 2, 293 Size: quantity.Size(len("pc-core.img content")), 294 }, 295 } 296 297 err = install.WriteContent(&m, s.gadgetRoot, nil) 298 c.Assert(err, IsNil) 299 300 content, err := ioutil.ReadFile(m.Node) 301 c.Assert(err, IsNil) 302 // note the 2 zero byte start offset 303 c.Check(string(content), Equals, "\x00\x00pc-core.img content") 304 } 305 306 func (s *contentTestSuite) TestMountFilesystem(c *C) { 307 dirs.SetRootDir(c.MkDir()) 308 defer dirs.SetRootDir("") 309 310 // mounting will only happen for devices with a label 311 mockOnDiskStructureBiosBoot.Label = "bios-boot" 312 defer func() { mockOnDiskStructureBiosBoot.Label = "" }() 313 314 err := install.MountFilesystem(&mockOnDiskStructureBiosBoot, boot.InitramfsRunMntDir) 315 c.Assert(err, ErrorMatches, "cannot mount a partition with no filesystem") 316 317 // mount a filesystem... 318 err = install.MountFilesystem(&mockOnDiskStructureSystemSeed, boot.InitramfsRunMntDir) 319 c.Assert(err, IsNil) 320 321 // ...and check if it was mounted at the right mount point 322 c.Check(s.mockMountCalls, HasLen, 1) 323 c.Check(s.mockMountCalls, DeepEquals, []struct{ source, target, fstype string }{ 324 {"/dev/node2", boot.InitramfsUbuntuSeedDir, "vfat"}, 325 }) 326 327 // now try to mount a filesystem with no label 328 mockOnDiskStructureSystemSeed.Label = "" 329 defer func() { mockOnDiskStructureSystemSeed.Label = "ubuntu-seed" }() 330 331 err = install.MountFilesystem(&mockOnDiskStructureSystemSeed, boot.InitramfsRunMntDir) 332 c.Assert(err, ErrorMatches, "cannot mount a filesystem with no label") 333 }