github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/interfaces/builtin/raw_volume_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2019 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 builtin_test 21 22 import ( 23 . "gopkg.in/check.v1" 24 "strings" 25 26 "github.com/snapcore/snapd/interfaces" 27 "github.com/snapcore/snapd/interfaces/apparmor" 28 "github.com/snapcore/snapd/interfaces/builtin" 29 "github.com/snapcore/snapd/interfaces/udev" 30 "github.com/snapcore/snapd/snap" 31 "github.com/snapcore/snapd/snap/snaptest" 32 "github.com/snapcore/snapd/testutil" 33 ) 34 35 type rawVolumeInterfaceSuite struct { 36 testutil.BaseTest 37 iface interfaces.Interface 38 39 // OS snap 40 testSlot1Info *snap.SlotInfo 41 testSlot2Info *snap.SlotInfo 42 testSlot3Info *snap.SlotInfo 43 44 // Gadget snap 45 testUDev1 *interfaces.ConnectedSlot 46 testUDev1Info *snap.SlotInfo 47 testUDev2 *interfaces.ConnectedSlot 48 testUDev2Info *snap.SlotInfo 49 testUDev3 *interfaces.ConnectedSlot 50 testUDev3Info *snap.SlotInfo 51 52 testUDevBadValue1 *interfaces.ConnectedSlot 53 testUDevBadValue1Info *snap.SlotInfo 54 55 // Consuming snap 56 testPlugPart1 *interfaces.ConnectedPlug 57 testPlugPart1Info *snap.PlugInfo 58 testPlugPart2 *interfaces.ConnectedPlug 59 testPlugPart2Info *snap.PlugInfo 60 testPlugPart3 *interfaces.ConnectedPlug 61 testPlugPart3Info *snap.PlugInfo 62 } 63 64 var _ = Suite(&rawVolumeInterfaceSuite{ 65 iface: builtin.MustInterface("raw-volume"), 66 }) 67 68 const rawVolumeConsumerYaml = `name: consumer 69 version: 0 70 apps: 71 app: 72 plugs: [raw-volume] 73 ` 74 75 const rawVolumeCoreYaml = `name: core 76 version: 0 77 type: os 78 slots: 79 raw-volume: 80 ` 81 82 func (s *rawVolumeInterfaceSuite) SetUpTest(c *C) { 83 // Mock for OS snap 84 osSnapInfo := snaptest.MockInfo(c, ` 85 name: core 86 version: 0 87 type: os 88 slots: 89 test-part-1: 90 interface: raw-volume 91 path: /dev/vda1 92 test-part-2: 93 interface: raw-volume 94 path: /dev/mmcblk0p1 95 test-part-3: 96 interface: raw-volume 97 path: /dev/i2o/hda1 98 `, nil) 99 s.testSlot1Info = osSnapInfo.Slots["test-part-1"] 100 s.testSlot2Info = osSnapInfo.Slots["test-part-2"] 101 s.testSlot3Info = osSnapInfo.Slots["test-part-3"] 102 103 // Mock for Gadget snap 104 gadgetSnapInfo := snaptest.MockInfo(c, ` 105 name: some-device 106 version: 0 107 type: gadget 108 slots: 109 test-udev-1: 110 interface: raw-volume 111 path: /dev/vda1 112 test-udev-2: 113 interface: raw-volume 114 path: /dev/mmcblk0p1 115 test-udev-3: 116 interface: raw-volume 117 path: /dev/i2o/hda1 118 test-udev-bad-value-1: 119 interface: raw-volume 120 path: /dev/vda0 121 `, nil) 122 s.testUDev1Info = gadgetSnapInfo.Slots["test-udev-1"] 123 s.testUDev1 = interfaces.NewConnectedSlot(s.testUDev1Info, nil, nil) 124 s.testUDev2Info = gadgetSnapInfo.Slots["test-udev-2"] 125 s.testUDev2 = interfaces.NewConnectedSlot(s.testUDev2Info, nil, nil) 126 s.testUDev3Info = gadgetSnapInfo.Slots["test-udev-3"] 127 s.testUDev3 = interfaces.NewConnectedSlot(s.testUDev3Info, nil, nil) 128 s.testUDevBadValue1Info = gadgetSnapInfo.Slots["test-udev-bad-value-1"] 129 s.testUDevBadValue1 = interfaces.NewConnectedSlot(s.testUDevBadValue1Info, nil, nil) 130 131 // Mock for consumer snaps 132 consumingSnapInfo := snaptest.MockInfo(c, ` 133 name: client-snap 134 version: 0 135 plugs: 136 plug-for-part-1: 137 interface: raw-volume 138 plug-for-part-2: 139 interface: raw-volume 140 plug-for-part-3: 141 interface: raw-volume 142 apps: 143 app-accessing-1-part: 144 command: foo 145 plugs: 146 - plug-for-part-1 147 app-accessing-2-part: 148 command: foo 149 plugs: 150 - plug-for-part-2 151 app-accessing-3-part: 152 command: foo 153 plugs: 154 - plug-for-part-3 155 `, nil) 156 s.testPlugPart1Info = consumingSnapInfo.Plugs["plug-for-part-1"] 157 s.testPlugPart1 = interfaces.NewConnectedPlug(s.testPlugPart1Info, nil, nil) 158 s.testPlugPart2Info = consumingSnapInfo.Plugs["plug-for-part-2"] 159 s.testPlugPart2 = interfaces.NewConnectedPlug(s.testPlugPart2Info, nil, nil) 160 s.testPlugPart3Info = consumingSnapInfo.Plugs["plug-for-part-3"] 161 s.testPlugPart3 = interfaces.NewConnectedPlug(s.testPlugPart3Info, nil, nil) 162 } 163 164 func (s *rawVolumeInterfaceSuite) TestName(c *C) { 165 c.Assert(s.iface.Name(), Equals, "raw-volume") 166 } 167 168 func (s *rawVolumeInterfaceSuite) TestSanitizeCoreSnapSlot(c *C) { 169 c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testSlot1Info), IsNil) 170 c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testSlot2Info), IsNil) 171 c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testSlot3Info), IsNil) 172 } 173 174 func (s *rawVolumeInterfaceSuite) TestSanitizeGadgetSnapSlot(c *C) { 175 c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDev1Info), IsNil) 176 } 177 178 func (s *rawVolumeInterfaceSuite) TestSanitizeBadGadgetSnapSlot(c *C) { 179 c.Assert(interfaces.BeforePrepareSlot(s.iface, s.testUDevBadValue1Info), ErrorMatches, `slot "some-device:test-udev-bad-value-1" path attribute must be a valid device node`) 180 } 181 182 func (s *rawVolumeInterfaceSuite) TestSanitizeSlotHappy(c *C) { 183 const mockSnapYaml = `name: raw-volume-slot-snap 184 type: gadget 185 version: 1.0 186 slots: 187 raw-volume: 188 path: $t 189 ` 190 191 var testCases = []struct { 192 input string 193 }{ 194 {`/dev/hda1`}, 195 {`/dev/hda63`}, 196 {`/dev/hdb42`}, 197 {`/dev/hdt63`}, 198 {`/dev/sda1`}, 199 {`/dev/sda15`}, 200 {`/dev/sdb8`}, 201 {`/dev/sdc14`}, 202 {`/dev/sdde10`}, 203 {`/dev/sdiv15`}, 204 {`/dev/i2o/hda1`}, 205 {`/dev/i2o/hda15`}, 206 {`/dev/i2o/hdb8`}, 207 {`/dev/i2o/hdc10`}, 208 {`/dev/i2o/hdde10`}, 209 {`/dev/i2o/hddx15`}, 210 {`/dev/mmcblk0p1`}, 211 {`/dev/mmcblk0p63`}, 212 {`/dev/mmcblk12p42`}, 213 {`/dev/mmcblk999p63`}, 214 {`/dev/nvme0p1`}, 215 {`/dev/nvme0p63`}, 216 {`/dev/nvme12p42`}, 217 {`/dev/nvme99p63`}, 218 {`/dev/nvme0n1p1`}, 219 {`/dev/nvme0n1p63`}, 220 {`/dev/nvme12n34p42`}, 221 {`/dev/nvme99n63p63`}, 222 {`/dev/vda1`}, 223 {`/dev/vda63`}, 224 {`/dev/vdb42`}, 225 {`/dev/vdz63`}, 226 } 227 228 for _, t := range testCases { 229 yml := strings.Replace(mockSnapYaml, "$t", t.input, -1) 230 info := snaptest.MockInfo(c, yml, nil) 231 slot := info.Slots["raw-volume"] 232 233 c.Check(interfaces.BeforePrepareSlot(s.iface, slot), IsNil, Commentf("unexpected error for %q", t.input)) 234 } 235 } 236 237 func (s *rawVolumeInterfaceSuite) TestSanitizeSlotUnhappy(c *C) { 238 const mockSnapYaml = `name: raw-volume-slot-snap 239 type: gadget 240 version: 1.0 241 slots: 242 raw-volume: 243 path: $t 244 ` 245 246 var testCases = []struct { 247 input string 248 }{ 249 {`/dev/hda0`}, 250 {`/dev/hdt64`}, 251 {`/dev/hdu1`}, 252 {`/dev/sda0`}, 253 {`/dev/sdiv16`}, 254 {`/dev/sdiw1`}, 255 {`/dev/i2o/hda0`}, 256 {`/dev/i20/hddx16`}, 257 {`/dev/i2o/hddy1`}, 258 {`/dev/mmcblk0p0`}, 259 {`/dev/mmcblk999p64`}, 260 {`/dev/mmcblk1000p1`}, 261 {`/dev/nvme0p0`}, 262 {`/dev/nvme99p64`}, 263 {`/dev/nvme100p1`}, 264 {`/dev/nvme0n0p1`}, 265 {`/dev/nvme99n64p1`}, 266 {`/dev/nvme100n1p1`}, 267 {`/dev/vda0`}, 268 {`/dev/vdz64`}, 269 {`/dev/vdaa1`}, 270 } 271 272 for _, t := range testCases { 273 yml := strings.Replace(mockSnapYaml, "$t", t.input, -1) 274 info := snaptest.MockInfo(c, yml, nil) 275 slot := info.Slots["raw-volume"] 276 277 c.Check(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `slot "raw-volume-slot-snap:raw-volume" path attribute must be a valid device node`, Commentf("unexpected error for %q", t.input)) 278 } 279 } 280 281 func (s *rawVolumeInterfaceSuite) TestSanitizeSlotUnclean(c *C) { 282 const mockSnapYaml = `name: raw-volume-slot-snap 283 type: gadget 284 version: 1.0 285 slots: 286 raw-volume: 287 path: $t 288 ` 289 290 var testCases = []struct { 291 input string 292 }{ 293 {`/dev/hda1/.`}, 294 {`/dev/i2o/`}, 295 {`/dev/./././mmcblk0p1////`}, 296 } 297 298 for _, t := range testCases { 299 yml := strings.Replace(mockSnapYaml, "$t", t.input, -1) 300 info := snaptest.MockInfo(c, yml, nil) 301 slot := info.Slots["raw-volume"] 302 c.Check(interfaces.BeforePrepareSlot(s.iface, slot), ErrorMatches, `cannot use slot "raw-volume-slot-snap:raw-volume" path ".*": try ".*"`, Commentf("unexpected error for %q", t.input)) 303 } 304 } 305 306 func (s *rawVolumeInterfaceSuite) TestUDevSpec(c *C) { 307 spec := &udev.Specification{} 308 c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart1, s.testUDev1), IsNil) 309 c.Assert(spec.Snippets(), HasLen, 2) 310 c.Assert(spec.Snippets()[0], Equals, `# raw-volume 311 KERNEL=="vda1", TAG+="snap_client-snap_app-accessing-1-part"`) 312 c.Assert(spec.Snippets(), testutil.Contains, `TAG=="snap_client-snap_app-accessing-1-part", RUN+="/usr/lib/snapd/snap-device-helper $env{ACTION} snap_client-snap_app-accessing-1-part $devpath $major:$minor"`) 313 314 spec = &udev.Specification{} 315 c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart2, s.testUDev2), IsNil) 316 c.Assert(spec.Snippets(), HasLen, 2) 317 c.Assert(spec.Snippets()[0], Equals, `# raw-volume 318 KERNEL=="mmcblk0p1", TAG+="snap_client-snap_app-accessing-2-part"`) 319 320 spec = &udev.Specification{} 321 c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart3, s.testUDev3), IsNil) 322 c.Assert(spec.Snippets(), HasLen, 2) 323 c.Assert(spec.Snippets()[0], Equals, `# raw-volume 324 KERNEL=="i2o/hda1", TAG+="snap_client-snap_app-accessing-3-part"`) 325 } 326 327 func (s *rawVolumeInterfaceSuite) TestAppArmorSpec(c *C) { 328 spec := &apparmor.Specification{} 329 c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart1, s.testUDev1), IsNil) 330 c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-1-part"}) 331 c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-1-part"), testutil.Contains, `/dev/vda1 rw,`) 332 c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-1-part"), testutil.Contains, `capability sys_admin,`) 333 334 spec = &apparmor.Specification{} 335 c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart2, s.testUDev2), IsNil) 336 c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-2-part"}) 337 c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-2-part"), testutil.Contains, `/dev/mmcblk0p1 rw,`) 338 c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-2-part"), testutil.Contains, `capability sys_admin,`) 339 340 spec = &apparmor.Specification{} 341 c.Assert(spec.AddConnectedPlug(s.iface, s.testPlugPart3, s.testUDev3), IsNil) 342 c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.client-snap.app-accessing-3-part"}) 343 c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-3-part"), testutil.Contains, `/dev/i2o/hda1 rw,`) 344 c.Assert(spec.SnippetForTag("snap.client-snap.app-accessing-3-part"), testutil.Contains, `capability sys_admin,`) 345 } 346 347 func (s *rawVolumeInterfaceSuite) TestStaticInfo(c *C) { 348 si := interfaces.StaticInfoOf(s.iface) 349 c.Assert(si.ImplicitOnCore, Equals, false) 350 c.Assert(si.ImplicitOnClassic, Equals, false) 351 c.Assert(si.Summary, Equals, `allows read/write access to specific disk partition`) 352 c.Assert(si.BaseDeclarationSlots, testutil.Contains, "raw-volume") 353 } 354 355 func (s *rawVolumeInterfaceSuite) TestAutoConnect(c *C) { 356 c.Check(s.iface.AutoConnect(nil, nil), Equals, true) 357 } 358 359 func (s *rawVolumeInterfaceSuite) TestInterfaces(c *C) { 360 c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) 361 }