github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/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  }