github.com/Lephar/snapd@v0.0.0-20210825215435-c7fba9cef4d2/interfaces/repo.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2016 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 interfaces 21 22 import ( 23 "fmt" 24 "sort" 25 "strings" 26 "sync" 27 28 "github.com/snapcore/snapd/interfaces/hotplug" 29 "github.com/snapcore/snapd/interfaces/utils" 30 "github.com/snapcore/snapd/snap" 31 ) 32 33 // Repository stores all known snappy plugs and slots and ifaces. 34 type Repository struct { 35 // Protects the internals from concurrent access. 36 m sync.Mutex 37 ifaces map[string]Interface 38 // subset of ifaces that implement HotplugDeviceAdded method 39 hotplugIfaces map[string]Interface 40 // Indexed by [snapName][plugName] 41 plugs map[string]map[string]*snap.PlugInfo 42 slots map[string]map[string]*snap.SlotInfo 43 // given a slot and a plug, are they connected? 44 slotPlugs map[*snap.SlotInfo]map[*snap.PlugInfo]*Connection 45 // given a plug and a slot, are they connected? 46 plugSlots map[*snap.PlugInfo]map[*snap.SlotInfo]*Connection 47 backends []SecurityBackend 48 } 49 50 // NewRepository creates an empty plug repository. 51 func NewRepository() *Repository { 52 repo := &Repository{ 53 ifaces: make(map[string]Interface), 54 hotplugIfaces: make(map[string]Interface), 55 plugs: make(map[string]map[string]*snap.PlugInfo), 56 slots: make(map[string]map[string]*snap.SlotInfo), 57 slotPlugs: make(map[*snap.SlotInfo]map[*snap.PlugInfo]*Connection), 58 plugSlots: make(map[*snap.PlugInfo]map[*snap.SlotInfo]*Connection), 59 } 60 61 return repo 62 } 63 64 // Interface returns an interface with a given name. 65 func (r *Repository) Interface(interfaceName string) Interface { 66 r.m.Lock() 67 defer r.m.Unlock() 68 69 return r.ifaces[interfaceName] 70 } 71 72 // AddInterface adds the provided interface to the repository. 73 func (r *Repository) AddInterface(i Interface) error { 74 r.m.Lock() 75 defer r.m.Unlock() 76 77 interfaceName := i.Name() 78 if err := snap.ValidateInterfaceName(interfaceName); err != nil { 79 return err 80 } 81 if _, ok := r.ifaces[interfaceName]; ok { 82 return fmt.Errorf("cannot add interface: %q, interface name is in use", interfaceName) 83 } 84 r.ifaces[interfaceName] = i 85 86 if _, ok := i.(hotplug.Definer); ok { 87 r.hotplugIfaces[interfaceName] = i 88 } 89 90 return nil 91 } 92 93 // AllInterfaces returns all the interfaces added to the repository, ordered by name. 94 func (r *Repository) AllInterfaces() []Interface { 95 r.m.Lock() 96 defer r.m.Unlock() 97 98 ifaces := make([]Interface, 0, len(r.ifaces)) 99 for _, iface := range r.ifaces { 100 ifaces = append(ifaces, iface) 101 } 102 sort.Sort(byInterfaceName(ifaces)) 103 return ifaces 104 } 105 106 // AllHotplugInterfaces returns all interfaces that handle hotplug events. 107 func (r *Repository) AllHotplugInterfaces() map[string]Interface { 108 r.m.Lock() 109 defer r.m.Unlock() 110 111 ifaces := make(map[string]Interface) 112 for _, iface := range r.hotplugIfaces { 113 ifaces[iface.Name()] = iface 114 } 115 return ifaces 116 } 117 118 // InfoOptions describes options for Info. 119 // 120 // Names: return just this subset if non-empty. 121 // Doc: return documentation. 122 // Plugs: return information about plugs. 123 // Slots: return information about slots. 124 // Connected: only consider interfaces with at least one connection. 125 type InfoOptions struct { 126 Names []string 127 Doc bool 128 Plugs bool 129 Slots bool 130 Connected bool 131 } 132 133 func (r *Repository) interfaceInfo(iface Interface, opts *InfoOptions) *Info { 134 // NOTE: InfoOptions.Connected is handled by Info 135 si := StaticInfoOf(iface) 136 ifaceName := iface.Name() 137 ii := &Info{ 138 Name: ifaceName, 139 Summary: si.Summary, 140 } 141 if opts != nil && opts.Doc { 142 // Collect documentation URL 143 ii.DocURL = si.DocURL 144 } 145 if opts != nil && opts.Plugs { 146 // Collect all plugs of this interface type. 147 for _, snapName := range sortedSnapNamesWithPlugs(r.plugs) { 148 for _, plugName := range sortedPlugNames(r.plugs[snapName]) { 149 plugInfo := r.plugs[snapName][plugName] 150 if plugInfo.Interface == ifaceName { 151 ii.Plugs = append(ii.Plugs, plugInfo) 152 } 153 } 154 } 155 } 156 if opts != nil && opts.Slots { 157 // Collect all slots of this interface type. 158 for _, snapName := range sortedSnapNamesWithSlots(r.slots) { 159 for _, slotName := range sortedSlotNames(r.slots[snapName]) { 160 slotInfo := r.slots[snapName][slotName] 161 if slotInfo.Interface == ifaceName { 162 ii.Slots = append(ii.Slots, slotInfo) 163 } 164 } 165 } 166 } 167 return ii 168 } 169 170 // Info returns information about interfaces in the system. 171 // 172 // If names is empty then all interfaces are considered. Query options decide 173 // which data to return but can also skip interfaces without connections. See 174 // the documentation of InfoOptions for details. 175 func (r *Repository) Info(opts *InfoOptions) []*Info { 176 r.m.Lock() 177 defer r.m.Unlock() 178 179 // If necessary compute the set of interfaces with any connections. 180 var connected map[string]bool 181 if opts != nil && opts.Connected { 182 connected = make(map[string]bool) 183 for _, plugMap := range r.slotPlugs { 184 for plug, conn := range plugMap { 185 if conn != nil { 186 connected[plug.Interface] = true 187 } 188 } 189 } 190 for _, slotMap := range r.plugSlots { 191 for slot, conn := range slotMap { 192 if conn != nil { 193 connected[slot.Interface] = true 194 } 195 } 196 } 197 } 198 199 // If weren't asked about specific interfaces then query every interface. 200 var names []string 201 if opts == nil || len(opts.Names) == 0 { 202 for _, iface := range r.ifaces { 203 name := iface.Name() 204 if connected == nil || connected[name] { 205 // Optionally filter out interfaces without connections. 206 names = append(names, name) 207 } 208 } 209 } else { 210 names = make([]string, len(opts.Names)) 211 copy(names, opts.Names) 212 } 213 sort.Strings(names) 214 215 // Query each interface we are interested in. 216 infos := make([]*Info, 0, len(names)) 217 for _, name := range names { 218 if iface, ok := r.ifaces[name]; ok { 219 if connected == nil || connected[name] { 220 infos = append(infos, r.interfaceInfo(iface, opts)) 221 } 222 } 223 } 224 return infos 225 } 226 227 // AddBackend adds the provided security backend to the repository. 228 func (r *Repository) AddBackend(backend SecurityBackend) error { 229 r.m.Lock() 230 defer r.m.Unlock() 231 232 name := backend.Name() 233 for _, other := range r.backends { 234 if other.Name() == name { 235 return fmt.Errorf("cannot add backend %q, security system name is in use", name) 236 } 237 } 238 r.backends = append(r.backends, backend) 239 return nil 240 } 241 242 // AllPlugs returns all plugs of the given interface. 243 // If interfaceName is the empty string, all plugs are returned. 244 func (r *Repository) AllPlugs(interfaceName string) []*snap.PlugInfo { 245 r.m.Lock() 246 defer r.m.Unlock() 247 248 var result []*snap.PlugInfo 249 for _, plugsForSnap := range r.plugs { 250 for _, plug := range plugsForSnap { 251 if interfaceName == "" || plug.Interface == interfaceName { 252 result = append(result, plug) 253 } 254 } 255 } 256 sort.Sort(byPlugSnapAndName(result)) 257 return result 258 } 259 260 // Plugs returns the plugs offered by the named snap. 261 func (r *Repository) Plugs(snapName string) []*snap.PlugInfo { 262 r.m.Lock() 263 defer r.m.Unlock() 264 265 var result []*snap.PlugInfo 266 for _, plug := range r.plugs[snapName] { 267 result = append(result, plug) 268 } 269 sort.Sort(byPlugSnapAndName(result)) 270 return result 271 } 272 273 // Plug returns the specified plug from the named snap. 274 func (r *Repository) Plug(snapName, plugName string) *snap.PlugInfo { 275 r.m.Lock() 276 defer r.m.Unlock() 277 278 return r.plugs[snapName][plugName] 279 } 280 281 // Connection returns the specified Connection object or an error. 282 func (r *Repository) Connection(connRef *ConnRef) (*Connection, error) { 283 // Ensure that such plug exists 284 plug := r.plugs[connRef.PlugRef.Snap][connRef.PlugRef.Name] 285 if plug == nil { 286 return nil, &NoPlugOrSlotError{ 287 message: fmt.Sprintf("snap %q has no plug named %q", 288 connRef.PlugRef.Snap, connRef.PlugRef.Name)} 289 } 290 // Ensure that such slot exists 291 slot := r.slots[connRef.SlotRef.Snap][connRef.SlotRef.Name] 292 if slot == nil { 293 return nil, &NoPlugOrSlotError{ 294 message: fmt.Sprintf("snap %q has no slot named %q", 295 connRef.SlotRef.Snap, connRef.SlotRef.Name)} 296 } 297 // Ensure that slot and plug are connected 298 conn, ok := r.slotPlugs[slot][plug] 299 if !ok { 300 return nil, &NotConnectedError{ 301 message: fmt.Sprintf("no connection from %s:%s to %s:%s", 302 connRef.PlugRef.Snap, connRef.PlugRef.Name, 303 connRef.SlotRef.Snap, connRef.SlotRef.Name)} 304 } 305 306 return conn, nil 307 } 308 309 // AddPlug adds a plug to the repository. 310 // Plug names must be valid snap names, as defined by ValidateName. 311 // Plug name must be unique within a particular snap. 312 func (r *Repository) AddPlug(plug *snap.PlugInfo) error { 313 r.m.Lock() 314 defer r.m.Unlock() 315 316 snapName := plug.Snap.InstanceName() 317 318 // Reject snaps with invalid names 319 if err := snap.ValidateInstanceName(snapName); err != nil { 320 return err 321 } 322 // Reject plugs with invalid names 323 if err := snap.ValidatePlugName(plug.Name); err != nil { 324 return err 325 } 326 i := r.ifaces[plug.Interface] 327 if i == nil { 328 return fmt.Errorf("cannot add plug, interface %q is not known", plug.Interface) 329 } 330 if _, ok := r.plugs[snapName][plug.Name]; ok { 331 return fmt.Errorf("snap %q has plugs conflicting on name %q", snapName, plug.Name) 332 } 333 if _, ok := r.slots[snapName][plug.Name]; ok { 334 return fmt.Errorf("snap %q has plug and slot conflicting on name %q", snapName, plug.Name) 335 } 336 if r.plugs[snapName] == nil { 337 r.plugs[snapName] = make(map[string]*snap.PlugInfo) 338 } 339 r.plugs[snapName][plug.Name] = plug 340 return nil 341 } 342 343 // RemovePlug removes the named plug provided by a given snap. 344 // The removed plug must exist and must not be used anywhere. 345 func (r *Repository) RemovePlug(snapName, plugName string) error { 346 r.m.Lock() 347 defer r.m.Unlock() 348 349 // Ensure that such plug exists 350 plug := r.plugs[snapName][plugName] 351 if plug == nil { 352 return fmt.Errorf("cannot remove plug %q from snap %q, no such plug", plugName, snapName) 353 } 354 // Ensure that the plug is not used by any slot 355 if len(r.plugSlots[plug]) > 0 { 356 return fmt.Errorf("cannot remove plug %q from snap %q, it is still connected", plugName, snapName) 357 } 358 delete(r.plugs[snapName], plugName) 359 if len(r.plugs[snapName]) == 0 { 360 delete(r.plugs, snapName) 361 } 362 return nil 363 } 364 365 // AllSlots returns all slots of the given interface. 366 // If interfaceName is the empty string, all slots are returned. 367 func (r *Repository) AllSlots(interfaceName string) []*snap.SlotInfo { 368 r.m.Lock() 369 defer r.m.Unlock() 370 371 var result []*snap.SlotInfo 372 for _, slotsForSnap := range r.slots { 373 for _, slot := range slotsForSnap { 374 if interfaceName == "" || slot.Interface == interfaceName { 375 result = append(result, slot) 376 } 377 } 378 } 379 sort.Sort(bySlotSnapAndName(result)) 380 return result 381 } 382 383 // Slots returns the slots offered by the named snap. 384 func (r *Repository) Slots(snapName string) []*snap.SlotInfo { 385 r.m.Lock() 386 defer r.m.Unlock() 387 388 var result []*snap.SlotInfo 389 for _, slot := range r.slots[snapName] { 390 result = append(result, slot) 391 } 392 sort.Sort(bySlotSnapAndName(result)) 393 return result 394 } 395 396 // Slot returns the specified slot from the named snap. 397 func (r *Repository) Slot(snapName, slotName string) *snap.SlotInfo { 398 r.m.Lock() 399 defer r.m.Unlock() 400 401 return r.slots[snapName][slotName] 402 } 403 404 // AddSlot adds a new slot to the repository. 405 // Adding a slot with invalid name returns an error. 406 // Adding a slot that has the same name and snap name as another slot returns an error. 407 func (r *Repository) AddSlot(slot *snap.SlotInfo) error { 408 r.m.Lock() 409 defer r.m.Unlock() 410 411 snapName := slot.Snap.InstanceName() 412 413 // Reject snaps with invalid names 414 if err := snap.ValidateInstanceName(snapName); err != nil { 415 return err 416 } 417 // Reject slots with invalid names 418 if err := snap.ValidateSlotName(slot.Name); err != nil { 419 return err 420 } 421 // TODO: ensure that apps are correct 422 i := r.ifaces[slot.Interface] 423 if i == nil { 424 return fmt.Errorf("cannot add slot, interface %q is not known", slot.Interface) 425 } 426 if _, ok := r.slots[snapName][slot.Name]; ok { 427 return fmt.Errorf("snap %q has slots conflicting on name %q", snapName, slot.Name) 428 } 429 if _, ok := r.plugs[snapName][slot.Name]; ok { 430 return fmt.Errorf("snap %q has plug and slot conflicting on name %q", snapName, slot.Name) 431 } 432 if r.slots[snapName] == nil { 433 r.slots[snapName] = make(map[string]*snap.SlotInfo) 434 } 435 r.slots[snapName][slot.Name] = slot 436 return nil 437 } 438 439 // RemoveSlot removes a named slot from the given snap. 440 // Removing a slot that doesn't exist returns an error. 441 // Removing a slot that is connected to a plug returns an error. 442 func (r *Repository) RemoveSlot(snapName, slotName string) error { 443 r.m.Lock() 444 defer r.m.Unlock() 445 446 // Ensure that such slot exists 447 slot := r.slots[snapName][slotName] 448 if slot == nil { 449 return fmt.Errorf("cannot remove slot %q from snap %q, no such slot", slotName, snapName) 450 } 451 // Ensure that the slot is not using any plugs 452 if len(r.slotPlugs[slot]) > 0 { 453 return fmt.Errorf("cannot remove slot %q from snap %q, it is still connected", slotName, snapName) 454 } 455 delete(r.slots[snapName], slotName) 456 if len(r.slots[snapName]) == 0 { 457 delete(r.slots, snapName) 458 } 459 return nil 460 } 461 462 // ResolveConnect resolves potentially missing plug or slot names and returns a 463 // fully populated connection reference. 464 func (r *Repository) ResolveConnect(plugSnapName, plugName, slotSnapName, slotName string) (*ConnRef, error) { 465 r.m.Lock() 466 defer r.m.Unlock() 467 468 if plugSnapName == "" { 469 return nil, fmt.Errorf("cannot resolve connection, plug snap name is empty") 470 } 471 if plugName == "" { 472 return nil, fmt.Errorf("cannot resolve connection, plug name is empty") 473 } 474 // Ensure that such plug exists 475 plug := r.plugs[plugSnapName][plugName] 476 if plug == nil { 477 return nil, &NoPlugOrSlotError{ 478 message: fmt.Sprintf("snap %q has no plug named %q", 479 plugSnapName, plugName), 480 } 481 } 482 483 if slotSnapName == "" { 484 // Use the core snap if the slot-side snap name is empty 485 switch { 486 case r.slots["snapd"] != nil: 487 slotSnapName = "snapd" 488 case r.slots["core"] != nil: 489 slotSnapName = "core" 490 case r.slots["ubuntu-core"] != nil: 491 slotSnapName = "ubuntu-core" 492 default: 493 // XXX: perhaps this should not be an error and instead it should 494 // silently assume "core" now? 495 return nil, fmt.Errorf("cannot resolve connection, slot snap name is empty") 496 } 497 } 498 if slotName == "" { 499 // Find the unambiguous slot that satisfies plug requirements 500 var candidates []string 501 for candidateSlotName, candidateSlot := range r.slots[slotSnapName] { 502 // TODO: use some smarter matching (e.g. against $attrs) 503 if candidateSlot.Interface == plug.Interface { 504 candidates = append(candidates, candidateSlotName) 505 } 506 } 507 switch len(candidates) { 508 case 0: 509 return nil, fmt.Errorf("snap %q has no %q interface slots", slotSnapName, plug.Interface) 510 case 1: 511 slotName = candidates[0] 512 default: 513 sort.Strings(candidates) 514 return nil, fmt.Errorf("snap %q has multiple %q interface slots: %s", slotSnapName, plug.Interface, strings.Join(candidates, ", ")) 515 } 516 } 517 518 // Ensure that such slot exists 519 slot := r.slots[slotSnapName][slotName] 520 if slot == nil { 521 return nil, &NoPlugOrSlotError{ 522 message: fmt.Sprintf("snap %q has no slot named %q", slotSnapName, slotName), 523 } 524 } 525 // Ensure that plug and slot are compatible 526 if slot.Interface != plug.Interface { 527 return nil, fmt.Errorf("cannot connect %s:%s (%q interface) to %s:%s (%q interface)", 528 plugSnapName, plugName, plug.Interface, slotSnapName, slotName, slot.Interface) 529 } 530 return NewConnRef(plug, slot), nil 531 } 532 533 // slotValidator can be implemented by Interfaces that need to validate the slot before the security is lifted. 534 type slotValidator interface { 535 BeforeConnectSlot(slot *ConnectedSlot) error 536 } 537 538 // plugValidator can be implemented by Interfaces that need to validate the plug before the security is lifted. 539 type plugValidator interface { 540 BeforeConnectPlug(plug *ConnectedPlug) error 541 } 542 543 type PolicyFunc func(*ConnectedPlug, *ConnectedSlot) (bool, error) 544 545 // Connect establishes a connection between a plug and a slot. 546 // The plug and the slot must have the same interface. 547 // When connections are reloaded policyCheck is null (we don't check policy again). 548 func (r *Repository) Connect(ref *ConnRef, plugStaticAttrs, plugDynamicAttrs, slotStaticAttrs, slotDynamicAttrs map[string]interface{}, policyCheck PolicyFunc) (*Connection, error) { 549 r.m.Lock() 550 defer r.m.Unlock() 551 552 plugSnapName := ref.PlugRef.Snap 553 plugName := ref.PlugRef.Name 554 slotSnapName := ref.SlotRef.Snap 555 slotName := ref.SlotRef.Name 556 557 // Ensure that such plug exists 558 plug := r.plugs[plugSnapName][plugName] 559 if plug == nil { 560 return nil, &NoPlugOrSlotError{ 561 message: fmt.Sprintf("cannot connect plug %q from snap %q: no such plug", 562 plugName, plugSnapName)} 563 } 564 // Ensure that such slot exists 565 slot := r.slots[slotSnapName][slotName] 566 if slot == nil { 567 return nil, &NoPlugOrSlotError{ 568 message: fmt.Sprintf("cannot connect slot %q from snap %q: no such slot", 569 slotName, slotSnapName)} 570 } 571 // Ensure that plug and slot are compatible 572 if slot.Interface != plug.Interface { 573 return nil, fmt.Errorf(`cannot connect plug "%s:%s" (interface %q) to "%s:%s" (interface %q)`, 574 plugSnapName, plugName, plug.Interface, slotSnapName, slotName, slot.Interface) 575 } 576 577 iface, ok := r.ifaces[plug.Interface] 578 if !ok { 579 return nil, fmt.Errorf("internal error: unknown interface %q", plug.Interface) 580 } 581 582 cplug := NewConnectedPlug(plug, plugStaticAttrs, plugDynamicAttrs) 583 cslot := NewConnectedSlot(slot, slotStaticAttrs, slotDynamicAttrs) 584 585 // policyCheck is null when reloading connections 586 if policyCheck != nil { 587 if i, ok := iface.(plugValidator); ok { 588 if err := i.BeforeConnectPlug(cplug); err != nil { 589 return nil, fmt.Errorf("cannot connect plug %q of snap %q: %s", plug.Name, plug.Snap.InstanceName(), err) 590 } 591 } 592 if i, ok := iface.(slotValidator); ok { 593 if err := i.BeforeConnectSlot(cslot); err != nil { 594 return nil, fmt.Errorf("cannot connect slot %q of snap %q: %s", slot.Name, slot.Snap.InstanceName(), err) 595 } 596 } 597 598 // autoconnect policy checker returns false to indicate disallowed auto-connection, but it's not an error. 599 ok, err := policyCheck(cplug, cslot) 600 if err != nil || !ok { 601 return nil, err 602 } 603 } 604 605 // Connect the plug 606 if r.slotPlugs[slot] == nil { 607 r.slotPlugs[slot] = make(map[*snap.PlugInfo]*Connection) 608 } 609 if r.plugSlots[plug] == nil { 610 r.plugSlots[plug] = make(map[*snap.SlotInfo]*Connection) 611 } 612 613 conn := &Connection{Plug: cplug, Slot: cslot} 614 r.slotPlugs[slot][plug] = conn 615 r.plugSlots[plug][slot] = conn 616 return conn, nil 617 } 618 619 // NotConnectedError is returned by Disconnect() if the requested connection does 620 // not exist. 621 type NotConnectedError struct { 622 message string 623 } 624 625 func (e *NotConnectedError) Error() string { 626 return e.message 627 } 628 629 // NoPlugOrSlotError is returned by Disconnect() if either the plug or slot does 630 // no exist. 631 type NoPlugOrSlotError struct { 632 message string 633 } 634 635 func (e *NoPlugOrSlotError) Error() string { 636 return e.message 637 } 638 639 // Disconnect disconnects the named plug from the slot of the given snap. 640 // 641 // Disconnect() finds a specific slot and a specific plug and disconnects that 642 // plug from that slot. It is an error if plug or slot cannot be found or if 643 // the connect does not exist. 644 func (r *Repository) Disconnect(plugSnapName, plugName, slotSnapName, slotName string) error { 645 r.m.Lock() 646 defer r.m.Unlock() 647 648 // Sanity check 649 if plugSnapName == "" { 650 return fmt.Errorf("cannot disconnect, plug snap name is empty") 651 } 652 if plugName == "" { 653 return fmt.Errorf("cannot disconnect, plug name is empty") 654 } 655 if slotSnapName == "" { 656 return fmt.Errorf("cannot disconnect, slot snap name is empty") 657 } 658 if slotName == "" { 659 return fmt.Errorf("cannot disconnect, slot name is empty") 660 } 661 662 // Ensure that such plug exists 663 plug := r.plugs[plugSnapName][plugName] 664 if plug == nil { 665 return &NoPlugOrSlotError{ 666 message: fmt.Sprintf("snap %q has no plug named %q", 667 plugSnapName, plugName), 668 } 669 } 670 // Ensure that such slot exists 671 slot := r.slots[slotSnapName][slotName] 672 if slot == nil { 673 return &NoPlugOrSlotError{ 674 message: fmt.Sprintf("snap %q has no slot named %q", 675 slotSnapName, slotName), 676 } 677 } 678 // Ensure that slot and plug are connected 679 if r.slotPlugs[slot][plug] == nil { 680 return &NotConnectedError{ 681 message: fmt.Sprintf("cannot disconnect %s:%s from %s:%s, it is not connected", 682 plugSnapName, plugName, slotSnapName, slotName), 683 } 684 } 685 r.disconnect(plug, slot) 686 return nil 687 } 688 689 // Connected returns references for all connections that are currently 690 // established with the provided plug or slot. 691 func (r *Repository) Connected(snapName, plugOrSlotName string) ([]*ConnRef, error) { 692 r.m.Lock() 693 defer r.m.Unlock() 694 695 return r.connected(snapName, plugOrSlotName) 696 } 697 698 func (r *Repository) connected(snapName, plugOrSlotName string) ([]*ConnRef, error) { 699 if snapName == "" { 700 snapName, _ = r.guessSystemSnapName() 701 if snapName == "" { 702 return nil, fmt.Errorf("internal error: cannot obtain core snap name while computing connections") 703 } 704 } 705 var conns []*ConnRef 706 if plugOrSlotName == "" { 707 return nil, fmt.Errorf("plug or slot name is empty") 708 } 709 // Check if plugOrSlotName actually maps to anything 710 if r.plugs[snapName][plugOrSlotName] == nil && r.slots[snapName][plugOrSlotName] == nil { 711 return nil, &NoPlugOrSlotError{ 712 message: fmt.Sprintf("snap %q has no plug or slot named %q", 713 snapName, plugOrSlotName)} 714 } 715 // Collect all the relevant connections 716 717 if plug, ok := r.plugs[snapName][plugOrSlotName]; ok { 718 for slotInfo := range r.plugSlots[plug] { 719 connRef := NewConnRef(plug, slotInfo) 720 conns = append(conns, connRef) 721 } 722 } 723 724 if slot, ok := r.slots[snapName][plugOrSlotName]; ok { 725 for plugInfo := range r.slotPlugs[slot] { 726 connRef := NewConnRef(plugInfo, slot) 727 conns = append(conns, connRef) 728 } 729 } 730 731 return conns, nil 732 } 733 734 // ConnectionsForHotplugKey returns all hotplug connections for given interface name and hotplug key. 735 func (r *Repository) ConnectionsForHotplugKey(ifaceName string, hotplugKey snap.HotplugKey) ([]*ConnRef, error) { 736 r.m.Lock() 737 defer r.m.Unlock() 738 739 snapName, err := r.guessSystemSnapName() 740 if err != nil { 741 return nil, err 742 } 743 var conns []*ConnRef 744 for _, slotInfo := range r.slots[snapName] { 745 if slotInfo.Interface == ifaceName && slotInfo.HotplugKey == hotplugKey { 746 for plugInfo := range r.slotPlugs[slotInfo] { 747 connRef := NewConnRef(plugInfo, slotInfo) 748 conns = append(conns, connRef) 749 } 750 } 751 } 752 753 return conns, nil 754 } 755 756 // SlotForHotplugKey returns a hotplug slot for given interface name and hotplug key or nil 757 // if there is no slot. 758 func (r *Repository) SlotForHotplugKey(ifaceName string, hotplugKey snap.HotplugKey) (*snap.SlotInfo, error) { 759 r.m.Lock() 760 defer r.m.Unlock() 761 762 snapName, err := r.guessSystemSnapName() 763 if err != nil { 764 return nil, err 765 } 766 767 for _, slotInfo := range r.slots[snapName] { 768 if slotInfo.Interface == ifaceName && slotInfo.HotplugKey == hotplugKey { 769 return slotInfo, nil 770 } 771 } 772 return nil, nil 773 } 774 775 // UpdateHotplugSlotAttrs updates static attributes of hotplug slot associated with given hotplugkey, and returns the resulting 776 // slot. Slots can only be updated if not connected to any plug. 777 func (r *Repository) UpdateHotplugSlotAttrs(ifaceName string, hotplugKey snap.HotplugKey, staticAttrs map[string]interface{}) (*snap.SlotInfo, error) { 778 r.m.Lock() 779 defer r.m.Unlock() 780 781 snapName, err := r.guessSystemSnapName() 782 if err != nil { 783 return nil, err 784 } 785 786 for _, slotInfo := range r.slots[snapName] { 787 if slotInfo.Interface == ifaceName && slotInfo.HotplugKey == hotplugKey { 788 if len(r.slotPlugs[slotInfo]) > 0 { 789 // slots should be updated when disconnected, and reconnected back after updating. 790 return nil, fmt.Errorf("internal error: cannot update slot %s while connected", slotInfo.Name) 791 } 792 slotInfo.Attrs = utils.CopyAttributes(staticAttrs) 793 return slotInfo, nil 794 } 795 } 796 797 return nil, fmt.Errorf("cannot find hotplug slot for interface %s and hotplug key %q", ifaceName, hotplugKey) 798 } 799 800 func (r *Repository) Connections(snapName string) ([]*ConnRef, error) { 801 r.m.Lock() 802 defer r.m.Unlock() 803 804 if snapName == "" { 805 snapName, _ = r.guessSystemSnapName() 806 if snapName == "" { 807 return nil, fmt.Errorf("internal error: cannot obtain core snap name while computing connections") 808 } 809 } 810 811 var conns []*ConnRef 812 for _, plugInfo := range r.plugs[snapName] { 813 for slotInfo := range r.plugSlots[plugInfo] { 814 connRef := NewConnRef(plugInfo, slotInfo) 815 conns = append(conns, connRef) 816 } 817 } 818 for _, slotInfo := range r.slots[snapName] { 819 for plugInfo := range r.slotPlugs[slotInfo] { 820 // self-connection, ignore here as we got it already in the plugs loop above 821 if plugInfo.Snap == slotInfo.Snap { 822 continue 823 } 824 connRef := NewConnRef(plugInfo, slotInfo) 825 conns = append(conns, connRef) 826 } 827 } 828 829 return conns, nil 830 } 831 832 // guessSystemSnapName returns the name of the system snap if one exists 833 func (r *Repository) guessSystemSnapName() (string, error) { 834 switch { 835 case r.slots["snapd"] != nil: 836 return "snapd", nil 837 case r.slots["core"] != nil: 838 return "core", nil 839 case r.slots["ubuntu-core"] != nil: 840 return "ubuntu-core", nil 841 default: 842 return "", fmt.Errorf("cannot guess the name of the core snap") 843 } 844 } 845 846 // DisconnectAll disconnects all provided connection references. 847 func (r *Repository) DisconnectAll(conns []*ConnRef) { 848 r.m.Lock() 849 defer r.m.Unlock() 850 851 for _, conn := range conns { 852 plug := r.plugs[conn.PlugRef.Snap][conn.PlugRef.Name] 853 slot := r.slots[conn.SlotRef.Snap][conn.SlotRef.Name] 854 if plug != nil && slot != nil { 855 r.disconnect(plug, slot) 856 } 857 } 858 } 859 860 // disconnect disconnects a plug from a slot. 861 func (r *Repository) disconnect(plug *snap.PlugInfo, slot *snap.SlotInfo) { 862 delete(r.slotPlugs[slot], plug) 863 if len(r.slotPlugs[slot]) == 0 { 864 delete(r.slotPlugs, slot) 865 } 866 delete(r.plugSlots[plug], slot) 867 if len(r.plugSlots[plug]) == 0 { 868 delete(r.plugSlots, plug) 869 } 870 } 871 872 // Backends returns all the security backends. 873 // The order is the same as the order in which they were inserted. 874 func (r *Repository) Backends() []SecurityBackend { 875 r.m.Lock() 876 defer r.m.Unlock() 877 878 result := make([]SecurityBackend, len(r.backends)) 879 copy(result, r.backends) 880 return result 881 } 882 883 // Interfaces returns object holding a lists of all the plugs and slots and their connections. 884 func (r *Repository) Interfaces() *Interfaces { 885 r.m.Lock() 886 defer r.m.Unlock() 887 888 ifaces := &Interfaces{} 889 890 // Copy and flatten plugs and slots 891 for _, plugs := range r.plugs { 892 for _, plugInfo := range plugs { 893 ifaces.Plugs = append(ifaces.Plugs, plugInfo) 894 } 895 } 896 for _, slots := range r.slots { 897 for _, slotInfo := range slots { 898 ifaces.Slots = append(ifaces.Slots, slotInfo) 899 } 900 } 901 902 for plug, slots := range r.plugSlots { 903 for slot := range slots { 904 ifaces.Connections = append(ifaces.Connections, NewConnRef(plug, slot)) 905 } 906 } 907 908 sort.Sort(byPlugSnapAndName(ifaces.Plugs)) 909 sort.Sort(bySlotSnapAndName(ifaces.Slots)) 910 sort.Sort(byConnRef(ifaces.Connections)) 911 return ifaces 912 } 913 914 // SnapSpecification returns the specification of a given snap in a given security system. 915 func (r *Repository) SnapSpecification(securitySystem SecuritySystem, snapName string) (Specification, error) { 916 r.m.Lock() 917 defer r.m.Unlock() 918 919 var backend SecurityBackend 920 for _, b := range r.backends { 921 if b.Name() == securitySystem { 922 backend = b 923 break 924 } 925 } 926 if backend == nil { 927 return nil, fmt.Errorf("cannot handle interfaces of snap %q, security system %q is not known", snapName, securitySystem) 928 } 929 930 spec := backend.NewSpecification() 931 932 // slot side 933 for _, slotInfo := range r.slots[snapName] { 934 iface := r.ifaces[slotInfo.Interface] 935 if err := spec.AddPermanentSlot(iface, slotInfo); err != nil { 936 return nil, err 937 } 938 for _, conn := range r.slotPlugs[slotInfo] { 939 if err := spec.AddConnectedSlot(iface, conn.Plug, conn.Slot); err != nil { 940 return nil, err 941 } 942 } 943 } 944 // plug side 945 for _, plugInfo := range r.plugs[snapName] { 946 iface := r.ifaces[plugInfo.Interface] 947 if err := spec.AddPermanentPlug(iface, plugInfo); err != nil { 948 return nil, err 949 } 950 for _, conn := range r.plugSlots[plugInfo] { 951 if err := spec.AddConnectedPlug(iface, conn.Plug, conn.Slot); err != nil { 952 return nil, err 953 } 954 } 955 } 956 return spec, nil 957 } 958 959 // AddSnap adds plugs and slots declared by the given snap to the repository. 960 // 961 // This function can be used to implement snap install or, when used along with 962 // RemoveSnap, snap upgrade. 963 // 964 // AddSnap doesn't change existing plugs/slots. The caller is responsible for 965 // ensuring that the snap is not present in the repository in any way prior to 966 // calling this function. If this constraint is violated then no changes are 967 // made and an error is returned. 968 // 969 // Each added plug/slot is validated according to the corresponding interface. 970 // Unknown interfaces and plugs/slots that don't validate are not added. 971 // Information about those failures are returned to the caller. 972 func (r *Repository) AddSnap(snapInfo *snap.Info) error { 973 if snapInfo.Broken != "" { 974 return fmt.Errorf("snap is broken: %s", snapInfo.Broken) 975 } 976 err := snap.Validate(snapInfo) 977 if err != nil { 978 return err 979 } 980 981 r.m.Lock() 982 defer r.m.Unlock() 983 984 snapName := snapInfo.InstanceName() 985 986 if r.plugs[snapName] != nil || r.slots[snapName] != nil { 987 return fmt.Errorf("cannot register interfaces for snap %q more than once", snapName) 988 } 989 990 for plugName, plugInfo := range snapInfo.Plugs { 991 if _, ok := r.ifaces[plugInfo.Interface]; !ok { 992 continue 993 } 994 if r.plugs[snapName] == nil { 995 r.plugs[snapName] = make(map[string]*snap.PlugInfo) 996 } 997 r.plugs[snapName][plugName] = plugInfo 998 } 999 1000 for slotName, slotInfo := range snapInfo.Slots { 1001 if _, ok := r.ifaces[slotInfo.Interface]; !ok { 1002 continue 1003 } 1004 if r.slots[snapName] == nil { 1005 r.slots[snapName] = make(map[string]*snap.SlotInfo) 1006 } 1007 r.slots[snapName][slotName] = slotInfo 1008 } 1009 return nil 1010 } 1011 1012 // RemoveSnap removes all the plugs and slots associated with a given snap. 1013 // 1014 // This function can be used to implement snap removal or, when used along with 1015 // AddSnap, snap upgrade. 1016 // 1017 // RemoveSnap does not remove connections. The caller is responsible for 1018 // ensuring that connections are broken before calling this method. If this 1019 // constraint is violated then no changes are made and an error is returned. 1020 func (r *Repository) RemoveSnap(snapName string) error { 1021 r.m.Lock() 1022 defer r.m.Unlock() 1023 1024 for plugName, plug := range r.plugs[snapName] { 1025 if len(r.plugSlots[plug]) > 0 { 1026 return fmt.Errorf("cannot remove connected plug %s.%s", snapName, plugName) 1027 } 1028 } 1029 for slotName, slot := range r.slots[snapName] { 1030 if len(r.slotPlugs[slot]) > 0 { 1031 return fmt.Errorf("cannot remove connected slot %s.%s", snapName, slotName) 1032 } 1033 } 1034 1035 for _, plug := range r.plugs[snapName] { 1036 delete(r.plugSlots, plug) 1037 } 1038 delete(r.plugs, snapName) 1039 for _, slot := range r.slots[snapName] { 1040 delete(r.slotPlugs, slot) 1041 } 1042 delete(r.slots, snapName) 1043 1044 return nil 1045 } 1046 1047 // DisconnectSnap disconnects all the connections to and from a given snap. 1048 // 1049 // The return value is a list of names that were affected. 1050 func (r *Repository) DisconnectSnap(snapName string) ([]string, error) { 1051 r.m.Lock() 1052 defer r.m.Unlock() 1053 1054 seen := make(map[*snap.Info]bool) 1055 1056 for _, plug := range r.plugs[snapName] { 1057 for slot := range r.plugSlots[plug] { 1058 r.disconnect(plug, slot) 1059 seen[plug.Snap] = true 1060 seen[slot.Snap] = true 1061 } 1062 } 1063 1064 for _, slot := range r.slots[snapName] { 1065 for plug := range r.slotPlugs[slot] { 1066 r.disconnect(plug, slot) 1067 seen[plug.Snap] = true 1068 seen[slot.Snap] = true 1069 } 1070 } 1071 1072 result := make([]string, 0, len(seen)) 1073 for info := range seen { 1074 result = append(result, info.InstanceName()) 1075 } 1076 sort.Strings(result) 1077 return result, nil 1078 } 1079 1080 // SideArity conveys the arity constraints for an allowed auto-connection. 1081 // ATM only slots-per-plug might have an interesting non-default 1082 // value. 1083 // See: https://forum.snapcraft.io/t/plug-slot-declaration-rules-greedy-plugs/12438 1084 type SideArity interface { 1085 SlotsPerPlugAny() bool 1086 // TODO: consider PlugsPerSlot* 1087 } 1088 1089 // AutoConnectCandidateSlots finds and returns viable auto-connection candidates 1090 // for a given plug. 1091 func (r *Repository) AutoConnectCandidateSlots(plugSnapName, plugName string, policyCheck func(*ConnectedPlug, *ConnectedSlot) (bool, SideArity, error)) ([]*snap.SlotInfo, []SideArity) { 1092 r.m.Lock() 1093 defer r.m.Unlock() 1094 1095 plugInfo := r.plugs[plugSnapName][plugName] 1096 if plugInfo == nil { 1097 return nil, nil 1098 } 1099 1100 var candidates []*snap.SlotInfo 1101 var arities []SideArity 1102 for _, slotsForSnap := range r.slots { 1103 for _, slotInfo := range slotsForSnap { 1104 if slotInfo.Interface != plugInfo.Interface { 1105 continue 1106 } 1107 iface := slotInfo.Interface 1108 1109 // declaration based checks disallow 1110 ok, arity, err := policyCheck(NewConnectedPlug(plugInfo, nil, nil), NewConnectedSlot(slotInfo, nil, nil)) 1111 if !ok || err != nil { 1112 continue 1113 } 1114 1115 if r.ifaces[iface].AutoConnect(plugInfo, slotInfo) { 1116 candidates = append(candidates, slotInfo) 1117 arities = append(arities, arity) 1118 } 1119 } 1120 } 1121 return candidates, arities 1122 } 1123 1124 // AutoConnectCandidatePlugs finds and returns viable auto-connection candidates 1125 // for a given slot. 1126 func (r *Repository) AutoConnectCandidatePlugs(slotSnapName, slotName string, policyCheck func(*ConnectedPlug, *ConnectedSlot) (bool, SideArity, error)) []*snap.PlugInfo { 1127 r.m.Lock() 1128 defer r.m.Unlock() 1129 1130 slotInfo := r.slots[slotSnapName][slotName] 1131 if slotInfo == nil { 1132 return nil 1133 } 1134 1135 var candidates []*snap.PlugInfo 1136 for _, plugsForSnap := range r.plugs { 1137 for _, plugInfo := range plugsForSnap { 1138 if slotInfo.Interface != plugInfo.Interface { 1139 continue 1140 } 1141 iface := slotInfo.Interface 1142 1143 // declaration based checks disallow 1144 ok, _, err := policyCheck(NewConnectedPlug(plugInfo, nil, nil), NewConnectedSlot(slotInfo, nil, nil)) 1145 if !ok || err != nil { 1146 continue 1147 } 1148 1149 if r.ifaces[iface].AutoConnect(plugInfo, slotInfo) { 1150 candidates = append(candidates, plugInfo) 1151 } 1152 } 1153 } 1154 return candidates 1155 }