gitee.com/mysnapcore/mysnapd@v0.1.0/interfaces/builtin/shared_memory_test.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2021 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 "fmt" 24 "os" 25 "path/filepath" 26 "strings" 27 28 . "gopkg.in/check.v1" 29 30 "gitee.com/mysnapcore/mysnapd/dirs" 31 "gitee.com/mysnapcore/mysnapd/interfaces" 32 "gitee.com/mysnapcore/mysnapd/interfaces/apparmor" 33 "gitee.com/mysnapcore/mysnapd/interfaces/builtin" 34 "gitee.com/mysnapcore/mysnapd/interfaces/mount" 35 "gitee.com/mysnapcore/mysnapd/osutil" 36 "gitee.com/mysnapcore/mysnapd/snap" 37 "gitee.com/mysnapcore/mysnapd/testutil" 38 ) 39 40 type SharedMemoryInterfaceSuite struct { 41 testutil.BaseTest 42 43 iface interfaces.Interface 44 slotInfo *snap.SlotInfo 45 slot *interfaces.ConnectedSlot 46 plugInfo *snap.PlugInfo 47 plug *interfaces.ConnectedPlug 48 wildcardPlugInfo *snap.PlugInfo 49 wildcardPlug *interfaces.ConnectedPlug 50 wildcardSlotInfo *snap.SlotInfo 51 wildcardSlot *interfaces.ConnectedSlot 52 privatePlugInfo *snap.PlugInfo 53 privatePlug *interfaces.ConnectedPlug 54 privateSlotInfo *snap.SlotInfo 55 privateSlot *interfaces.ConnectedSlot 56 } 57 58 var _ = Suite(&SharedMemoryInterfaceSuite{ 59 iface: builtin.MustInterface("shared-memory"), 60 }) 61 62 const sharedMemoryConsumerYaml = `name: consumer 63 version: 0 64 plugs: 65 shmem: 66 interface: shared-memory 67 shared-memory: foo 68 private: false 69 shmem-wildcard: 70 interface: shared-memory 71 shared-memory: foo-wildcard 72 private: false 73 shmem-private: 74 interface: shared-memory 75 private: true 76 apps: 77 app: 78 plugs: [shmem] 79 ` 80 81 const sharedMemoryProviderYaml = `name: provider 82 version: 0 83 slots: 84 shmem: 85 interface: shared-memory 86 shared-memory: foo 87 write: [ bar ] 88 read: [ bar-ro ] 89 private: false 90 shmem-wildcard: 91 interface: shared-memory 92 shared-memory: foo-wildcard 93 write: [ bar* ] 94 read: [ bar-ro* ] 95 private: false 96 apps: 97 app: 98 slots: [shmem] 99 ` 100 101 const sharedMemoryCoreYaml = `name: core 102 version: 0 103 type: os 104 slots: 105 shared-memory: 106 interface: shared-memory 107 apps: 108 app: 109 ` 110 111 func (s *SharedMemoryInterfaceSuite) SetUpTest(c *C) { 112 s.BaseTest.SetUpTest(c) 113 114 s.plug, s.plugInfo = MockConnectedPlug(c, sharedMemoryConsumerYaml, nil, "shmem") 115 s.slot, s.slotInfo = MockConnectedSlot(c, sharedMemoryProviderYaml, nil, "shmem") 116 117 s.wildcardPlug, s.wildcardPlugInfo = MockConnectedPlug(c, sharedMemoryConsumerYaml, nil, "shmem-wildcard") 118 s.wildcardSlot, s.wildcardSlotInfo = MockConnectedSlot(c, sharedMemoryProviderYaml, nil, "shmem-wildcard") 119 120 s.privatePlug, s.privatePlugInfo = MockConnectedPlug(c, sharedMemoryConsumerYaml, nil, "shmem-private") 121 s.privateSlot, s.privateSlotInfo = MockConnectedSlot(c, sharedMemoryCoreYaml, nil, "shared-memory") 122 } 123 124 func (s *SharedMemoryInterfaceSuite) TestName(c *C) { 125 c.Assert(s.iface.Name(), Equals, "shared-memory") 126 } 127 128 func (s *SharedMemoryInterfaceSuite) TestSanitizePlug(c *C) { 129 c.Check(interfaces.BeforePreparePlug(s.iface, s.plugInfo), IsNil) 130 c.Check(interfaces.BeforeConnectPlug(s.iface, s.plug), IsNil) 131 132 c.Check(interfaces.BeforePreparePlug(s.iface, s.wildcardPlugInfo), IsNil) 133 c.Check(interfaces.BeforeConnectPlug(s.iface, s.wildcardPlug), IsNil) 134 } 135 136 func (s *SharedMemoryInterfaceSuite) TestSanitizePlugUnhappy(c *C) { 137 var sharedMemoryYaml = `name: consumer 138 version: 0 139 plugs: 140 shmem: 141 interface: shared-memory 142 %s 143 apps: 144 app: 145 plugs: [shmem] 146 ` 147 data := []struct { 148 plugYaml string 149 expectedError string 150 }{ 151 { 152 "shared-memory: [one two]", 153 `shared-memory "shared-memory" attribute must be a string, not \[one two\]`, 154 }, 155 { 156 "private: hello", 157 `shared-memory "private" attribute must be a bool, not hello`, 158 }, 159 { 160 "private: true\n shared-memory: foo", 161 `shared-memory "shared-memory" attribute must not be set together with "private: true"`, 162 }, 163 } 164 165 for _, testData := range data { 166 snapYaml := fmt.Sprintf(sharedMemoryYaml, testData.plugYaml) 167 _, plug := MockConnectedPlug(c, snapYaml, nil, "shmem") 168 err := interfaces.BeforePreparePlug(s.iface, plug) 169 c.Check(err, ErrorMatches, testData.expectedError, Commentf("yaml: %s", testData.plugYaml)) 170 } 171 } 172 173 func (s *SharedMemoryInterfaceSuite) TestPlugPrivateAttribute(c *C) { 174 const snapYaml = `name: consumer 175 version: 0 176 plugs: 177 shmem: 178 interface: shared-memory 179 private: true 180 apps: 181 app: 182 plugs: [shmem] 183 ` 184 _, plug := MockConnectedPlug(c, snapYaml, nil, "shmem") 185 err := interfaces.BeforePreparePlug(s.iface, plug) 186 c.Assert(err, IsNil) 187 c.Check(plug.Attrs["private"], Equals, true) 188 c.Check(plug.Attrs["shared-memory"], Equals, nil) 189 } 190 191 func (s *SharedMemoryInterfaceSuite) TestPlugPrivateConflictsWithNonPrivate(c *C) { 192 const snapYaml1 = `name: consumer 193 version: 0 194 plugs: 195 shmem: 196 interface: shared-memory 197 shmem-private: 198 interface: shared-memory 199 private: true 200 ` 201 _, plug := MockConnectedPlug(c, snapYaml1, nil, "shmem-private") 202 err := interfaces.BeforePreparePlug(s.iface, plug) 203 c.Check(err, ErrorMatches, `shared-memory plug with "private: true" set cannot be used with other shared-memory plugs`) 204 205 const snapYaml2 = `name: consumer 206 version: 0 207 plugs: 208 shmem-private: 209 interface: shared-memory 210 private: true 211 slots: 212 shmem: 213 interface: shared-memory 214 ` 215 _, plug = MockConnectedPlug(c, snapYaml2, nil, "shmem-private") 216 err = interfaces.BeforePreparePlug(s.iface, plug) 217 c.Check(err, ErrorMatches, `shared-memory plug with \"private: true\" set cannot be used with shared-memory slots`) 218 } 219 220 func (s *SharedMemoryInterfaceSuite) TestPlugShmAttribute(c *C) { 221 var plugYamlTemplate = `name: consumer 222 version: 0 223 plugs: 224 shmem: 225 interface: shared-memory 226 %s 227 apps: 228 app: 229 plugs: [shmem] 230 ` 231 232 data := []struct { 233 plugYaml string 234 expectedName string 235 }{ 236 { 237 "", // missing "shared-memory" attribute 238 "shmem", // use the name of the plug 239 }, 240 { 241 "shared-memory: shmemFoo", 242 "shmemFoo", 243 }, 244 } 245 246 for _, testData := range data { 247 snapYaml := fmt.Sprintf(plugYamlTemplate, testData.plugYaml) 248 _, plug := MockConnectedPlug(c, snapYaml, nil, "shmem") 249 err := interfaces.BeforePreparePlug(s.iface, plug) 250 c.Assert(err, IsNil) 251 c.Check(plug.Attrs["private"], Equals, false, 252 Commentf(`yaml: %q`, testData.plugYaml)) 253 c.Check(plug.Attrs["shared-memory"], Equals, testData.expectedName, 254 Commentf(`yaml: %q`, testData.plugYaml)) 255 } 256 } 257 258 func (s *SharedMemoryInterfaceSuite) TestSanitizeSlot(c *C) { 259 c.Assert(interfaces.BeforePrepareSlot(s.iface, s.slotInfo), IsNil) 260 c.Assert(interfaces.BeforePrepareSlot(s.iface, s.wildcardSlotInfo), IsNil) 261 } 262 263 func (s *SharedMemoryInterfaceSuite) TestSanitizeSlotUnhappy(c *C) { 264 var sharedMemoryYaml = `name: provider 265 version: 0 266 slots: 267 shmem: 268 interface: shared-memory 269 %s 270 apps: 271 app: 272 slots: [shmem] 273 ` 274 data := []struct { 275 slotYaml string 276 expectedError string 277 }{ 278 { 279 "shared-memory: 12", 280 `shared-memory "shared-memory" attribute must be a string, not 12`, 281 }, 282 { 283 "", // missing "write" attribute 284 `shared memory interface requires at least a valid "read" or "write" attribute`, 285 }, 286 { 287 "write: a string", 288 `shared-memory "write" attribute must be a list of strings, not "a string"`, 289 }, 290 { 291 "read: [Mixed, 12, False, list]", 292 `shared-memory "read" attribute must be a list of strings, not "\[Mixed 12 false list\]"`, 293 }, 294 { 295 `read: ["ok", "trailing-space "]`, 296 `shared-memory interface path has leading or trailing spaces: "trailing-space "`, 297 }, 298 { 299 `write: [" leading-space"]`, 300 `shared-memory interface path has leading or trailing spaces: " leading-space"`, 301 }, 302 { 303 `write: [""]`, 304 `shared-memory interface path is empty`, 305 }, 306 { 307 `write: [mem**]`, 308 `shared-memory interface path is invalid: "mem\*\*" contains \*\* which is unsupported.*`, 309 }, 310 { 311 `read: [..]`, 312 `shared-memory interface path is not clean: ".."`, 313 }, 314 { 315 `write: [/dev/shm/bar]`, 316 `shared-memory interface path should not contain '/': "/dev/shm/bar"`, 317 }, 318 { 319 `write: [mem/../etc]`, 320 `shared-memory interface path should not contain '/': "mem/../etc"`, 321 }, 322 { 323 "write: [valid]\n read: [../invalid]", 324 `shared-memory interface path should not contain '/': "../invalid"`, 325 }, 326 { 327 "read: [valid]\n write: [../invalid]", 328 `shared-memory interface path should not contain '/': "../invalid"`, 329 }, 330 } 331 332 for _, testData := range data { 333 snapYaml := fmt.Sprintf(sharedMemoryYaml, testData.slotYaml) 334 _, slot := MockConnectedSlot(c, snapYaml, nil, "shmem") 335 err := interfaces.BeforePrepareSlot(s.iface, slot) 336 c.Check(err, ErrorMatches, testData.expectedError, Commentf("yaml: %s", testData.slotYaml)) 337 } 338 } 339 340 func (s *SharedMemoryInterfaceSuite) TestSlotShmAttribute(c *C) { 341 var slotYamlTemplate = `name: consumer 342 version: 0 343 slots: 344 shmem: 345 interface: shared-memory 346 write: [foo] 347 %s 348 apps: 349 app: 350 slots: [shmem] 351 ` 352 353 data := []struct { 354 slotYaml string 355 expectedName string 356 }{ 357 { 358 "", // missing "shared-memory" attribute 359 "shmem", // use the name of the slot 360 }, 361 { 362 "shared-memory: shmemBar", 363 "shmemBar", 364 }, 365 } 366 367 for _, testData := range data { 368 snapYaml := fmt.Sprintf(slotYamlTemplate, testData.slotYaml) 369 _, slot := MockConnectedSlot(c, snapYaml, nil, "shmem") 370 err := interfaces.BeforePrepareSlot(s.iface, slot) 371 c.Assert(err, IsNil) 372 c.Check(slot.Attrs["shared-memory"], Equals, testData.expectedName, 373 Commentf(`yaml: %q`, testData.slotYaml)) 374 } 375 } 376 377 func (s *SharedMemoryInterfaceSuite) TestStaticInfo(c *C) { 378 si := interfaces.StaticInfoOf(s.iface) 379 c.Check(si.ImplicitOnCore, Equals, true) 380 c.Check(si.ImplicitOnClassic, Equals, true) 381 c.Check(si.Summary, Equals, `allows two snaps to use predefined shared memory objects`) 382 c.Check(si.BaseDeclarationSlots, testutil.Contains, "shared-memory") 383 } 384 385 func (s *SharedMemoryInterfaceSuite) TestAppArmorSpec(c *C) { 386 spec := &apparmor.Specification{} 387 388 c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) 389 plugSnippet := spec.SnippetForTag("snap.consumer.app") 390 391 c.Assert(spec.AddConnectedSlot(s.iface, s.plug, s.slot), IsNil) 392 slotSnippet := spec.SnippetForTag("snap.provider.app") 393 394 c.Assert(spec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.provider.app"}) 395 396 c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar" mrwlk,`) 397 c.Check(plugSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" r,`) 398 399 // Slot has read-write permissions to all paths 400 c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar" mrwlk,`) 401 c.Check(slotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro" mrwlk,`) 402 403 wildcardSpec := &apparmor.Specification{} 404 c.Assert(wildcardSpec.AddConnectedPlug(s.iface, s.wildcardPlug, s.wildcardSlot), IsNil) 405 wildcardPlugSnippet := wildcardSpec.SnippetForTag("snap.consumer.app") 406 407 c.Assert(wildcardSpec.AddConnectedSlot(s.iface, s.wildcardPlug, s.wildcardSlot), IsNil) 408 wildcardSlotSnippet := wildcardSpec.SnippetForTag("snap.provider.app") 409 410 c.Assert(wildcardSpec.SecurityTags(), DeepEquals, []string{"snap.consumer.app", "snap.provider.app"}) 411 412 c.Check(wildcardPlugSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" mrwlk,`) 413 c.Check(wildcardPlugSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro*" r,`) 414 415 // Slot has read-write permissions to all paths 416 c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar*" mrwlk,`) 417 c.Check(wildcardSlotSnippet, testutil.Contains, `"/{dev,run}/shm/bar-ro*" mrwlk,`) 418 419 spec = &apparmor.Specification{} 420 c.Assert(spec.AddConnectedPlug(s.iface, s.privatePlug, s.privateSlot), IsNil) 421 privatePlugSnippet := spec.SnippetForTag("snap.consumer.app") 422 privateUpdateNS := spec.UpdateNS() 423 424 c.Assert(spec.AddConnectedSlot(s.iface, s.privatePlug, s.privateSlot), IsNil) 425 privateSlotSnippet := spec.SnippetForTag("snap.core.app") 426 427 c.Check(privatePlugSnippet, testutil.Contains, `"/dev/shm/*" mrwlkix`) 428 c.Check(privateSlotSnippet, Equals, "") 429 c.Check(strings.Join(privateUpdateNS, ""), Equals, ` # Private /dev/shm 430 /dev/ r, 431 /dev/shm/{,**} rw, 432 mount options=(bind, rw) /dev/shm/snap.consumer/ -> /dev/shm/, 433 umount /dev/shm/,`) 434 } 435 436 func (s *SharedMemoryInterfaceSuite) TestMountSpec(c *C) { 437 tmpdir := c.MkDir() 438 dirs.SetRootDir(tmpdir) 439 defer dirs.SetRootDir("/") 440 c.Assert(os.MkdirAll(filepath.Join(tmpdir, "/dev/shm"), 0777), IsNil) 441 442 // No mount entries for non-private shared-memory plugs 443 spec := &mount.Specification{} 444 c.Assert(spec.AddConnectedPlug(s.iface, s.plug, s.slot), IsNil) 445 c.Check(spec.MountEntries(), HasLen, 0) 446 447 spec = &mount.Specification{} 448 c.Assert(spec.AddConnectedPlug(s.iface, s.privatePlug, s.privateSlot), IsNil) 449 mounts := []osutil.MountEntry{ 450 { 451 Name: filepath.Join(tmpdir, "/dev/shm/snap.consumer"), 452 Dir: "/dev/shm", 453 Options: []string{"bind", "rw"}, 454 }, 455 } 456 c.Check(spec.MountEntries(), DeepEquals, mounts) 457 458 // Cannot set up mount entries if /dev/shm is a symlink 459 c.Assert(os.Remove(filepath.Join(tmpdir, "/dev/shm")), IsNil) 460 c.Assert(os.Symlink("/run/shm", filepath.Join(tmpdir, "/dev/shm")), IsNil) 461 spec = &mount.Specification{} 462 err := spec.AddConnectedPlug(s.iface, s.privatePlug, s.privateSlot) 463 c.Check(err, ErrorMatches, `shared-memory plug with "private: true" cannot be connected if ".*/dev/shm" is a symlink`) 464 } 465 466 func (s *SharedMemoryInterfaceSuite) TestAutoConnect(c *C) { 467 c.Assert(s.iface.AutoConnect(s.plugInfo, s.slotInfo), Equals, true) 468 } 469 470 func (s *SharedMemoryInterfaceSuite) TestInterfaces(c *C) { 471 c.Check(builtin.Interfaces(), testutil.DeepContains, s.iface) 472 }