github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/overlord/ifacestate/ifacemgr.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016-2017 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 ifacestate
    21  
    22  import (
    23  	"fmt"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/snapcore/snapd/interfaces"
    28  	"github.com/snapcore/snapd/interfaces/backends"
    29  	"github.com/snapcore/snapd/logger"
    30  	"github.com/snapcore/snapd/overlord/hookstate"
    31  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    32  	"github.com/snapcore/snapd/overlord/ifacestate/udevmonitor"
    33  	"github.com/snapcore/snapd/overlord/snapstate"
    34  	"github.com/snapcore/snapd/overlord/state"
    35  	"github.com/snapcore/snapd/snap"
    36  	"github.com/snapcore/snapd/snapdenv"
    37  	"github.com/snapcore/snapd/timings"
    38  )
    39  
    40  type deviceData struct {
    41  	ifaceName  string
    42  	hotplugKey snap.HotplugKey
    43  }
    44  
    45  // InterfaceManager is responsible for the maintenance of interfaces in
    46  // the system state.  It maintains interface connections, and also observes
    47  // installed snaps to track the current set of available plugs and slots.
    48  type InterfaceManager struct {
    49  	state *state.State
    50  	repo  *interfaces.Repository
    51  
    52  	udevMonMu           sync.Mutex
    53  	udevMon             udevmonitor.Interface
    54  	udevRetryTimeout    time.Time
    55  	udevMonitorDisabled bool
    56  	// indexed by interface name and device key. Reset to nil when enumeration is done.
    57  	enumeratedDeviceKeys map[string]map[snap.HotplugKey]bool
    58  	enumerationDone      bool
    59  	// maps sysfs path -> [(interface name, device key)...]
    60  	hotplugDevicePaths map[string][]deviceData
    61  
    62  	// extras
    63  	extraInterfaces []interfaces.Interface
    64  	extraBackends   []interfaces.SecurityBackend
    65  
    66  	preseed bool
    67  }
    68  
    69  // Manager returns a new InterfaceManager.
    70  // Extra interfaces can be provided for testing.
    71  func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.TaskRunner, extraInterfaces []interfaces.Interface, extraBackends []interfaces.SecurityBackend) (*InterfaceManager, error) {
    72  	delayedCrossMgrInit()
    73  
    74  	// NOTE: hookManager is nil only when testing.
    75  	if hookManager != nil {
    76  		setupHooks(hookManager)
    77  	}
    78  
    79  	// Leave udevRetryTimeout at the default value, so that udev is initialized on first Ensure run.
    80  	m := &InterfaceManager{
    81  		state: s,
    82  		repo:  interfaces.NewRepository(),
    83  		// note: enumeratedDeviceKeys is reset to nil when enumeration is done
    84  		enumeratedDeviceKeys: make(map[string]map[snap.HotplugKey]bool),
    85  		hotplugDevicePaths:   make(map[string][]deviceData),
    86  		// extras
    87  		extraInterfaces: extraInterfaces,
    88  		extraBackends:   extraBackends,
    89  		preseed:         snapdenv.Preseeding(),
    90  	}
    91  
    92  	taskKinds := map[string]bool{}
    93  	addHandler := func(kind string, do, undo state.HandlerFunc) {
    94  		taskKinds[kind] = true
    95  		runner.AddHandler(kind, do, undo)
    96  	}
    97  
    98  	addHandler("connect", m.doConnect, m.undoConnect)
    99  	addHandler("disconnect", m.doDisconnect, m.undoDisconnect)
   100  	addHandler("setup-profiles", m.doSetupProfiles, m.undoSetupProfiles)
   101  	addHandler("remove-profiles", m.doRemoveProfiles, m.doSetupProfiles)
   102  	addHandler("discard-conns", m.doDiscardConns, m.undoDiscardConns)
   103  	addHandler("auto-connect", m.doAutoConnect, m.undoAutoConnect)
   104  	addHandler("auto-disconnect", m.doAutoDisconnect, nil)
   105  	addHandler("hotplug-add-slot", m.doHotplugAddSlot, nil)
   106  	addHandler("hotplug-connect", m.doHotplugConnect, nil)
   107  	addHandler("hotplug-update-slot", m.doHotplugUpdateSlot, nil)
   108  	addHandler("hotplug-remove-slot", m.doHotplugRemoveSlot, nil)
   109  	addHandler("hotplug-disconnect", m.doHotplugDisconnect, nil)
   110  
   111  	// don't block on hotplug-seq-wait task
   112  	runner.AddHandler("hotplug-seq-wait", m.doHotplugSeqWait, nil)
   113  
   114  	// helper for ubuntu-core -> core
   115  	addHandler("transition-ubuntu-core", m.doTransitionUbuntuCore, m.undoTransitionUbuntuCore)
   116  
   117  	// interface tasks might touch more than the immediate task target snap, serialize them
   118  	runner.AddBlocked(func(t *state.Task, running []*state.Task) bool {
   119  		if !taskKinds[t.Kind()] {
   120  			return false
   121  		}
   122  
   123  		for _, t := range running {
   124  			if taskKinds[t.Kind()] {
   125  				return true
   126  			}
   127  		}
   128  
   129  		return false
   130  	})
   131  
   132  	return m, nil
   133  }
   134  
   135  // StartUp implements StateStarterUp.Startup.
   136  func (m *InterfaceManager) StartUp() error {
   137  	s := m.state
   138  	perfTimings := timings.New(map[string]string{"startup": "ifacemgr"})
   139  
   140  	s.Lock()
   141  	defer s.Unlock()
   142  
   143  	snaps, err := snapsWithSecurityProfiles(m.state)
   144  	if err != nil {
   145  		return err
   146  	}
   147  	// Before deciding about adding implicit slots to any snap we need to scan
   148  	// the set of snaps we know about. If any of those is "snapd" then for the
   149  	// duration of this process always add implicit slots to snapd and not to
   150  	// any other type: os snap and use a mapper to use names core-snapd-system
   151  	// on state, in memory and in API responses, respectively.
   152  	m.selectInterfaceMapper(snaps)
   153  
   154  	if err := m.addInterfaces(m.extraInterfaces); err != nil {
   155  		return err
   156  	}
   157  	if err := m.addBackends(m.extraBackends); err != nil {
   158  		return err
   159  	}
   160  	if err := m.addSnaps(snaps); err != nil {
   161  		return err
   162  	}
   163  	if err := m.renameCorePlugConnection(); err != nil {
   164  		return err
   165  	}
   166  	if err := removeStaleConnections(m.state); err != nil {
   167  		return err
   168  	}
   169  	if _, err := m.reloadConnections(""); err != nil {
   170  		return err
   171  	}
   172  	if profilesNeedRegeneration() {
   173  		if err := m.regenerateAllSecurityProfiles(perfTimings); err != nil {
   174  			return err
   175  		}
   176  	}
   177  
   178  	ifacerepo.Replace(s, m.repo)
   179  
   180  	// wire late profile removal support into snapstate
   181  	snapstate.SecurityProfilesRemoveLate = m.discardSecurityProfilesLate
   182  
   183  	perfTimings.Save(s)
   184  
   185  	return nil
   186  }
   187  
   188  // Ensure implements StateManager.Ensure.
   189  func (m *InterfaceManager) Ensure() error {
   190  	// do not worry about udev monitor in preseeding mode
   191  	if m.preseed {
   192  		return nil
   193  	}
   194  
   195  	if m.udevMonitorDisabled {
   196  		return nil
   197  	}
   198  	m.udevMonMu.Lock()
   199  	udevMon := m.udevMon
   200  	m.udevMonMu.Unlock()
   201  	if udevMon != nil {
   202  		return nil
   203  	}
   204  
   205  	// don't initialize udev monitor until we have a system snap so that we
   206  	// can attach hotplug interfaces to it.
   207  	if !checkSystemSnapIsPresent(m.state) {
   208  		return nil
   209  	}
   210  
   211  	// retry udev monitor initialization every 5 minutes
   212  	now := time.Now()
   213  	if now.After(m.udevRetryTimeout) {
   214  		err := m.initUDevMonitor()
   215  		if err != nil {
   216  			m.udevRetryTimeout = now.Add(udevInitRetryTimeout)
   217  		}
   218  		return err
   219  	}
   220  	return nil
   221  }
   222  
   223  // Stop implements StateStopper. It stops the udev monitor,
   224  // if running.
   225  func (m *InterfaceManager) Stop() {
   226  	m.udevMonMu.Lock()
   227  	udevMon := m.udevMon
   228  	m.udevMonMu.Unlock()
   229  	if udevMon == nil {
   230  		return
   231  	}
   232  	if err := udevMon.Stop(); err != nil {
   233  		logger.Noticef("Cannot stop udev monitor: %s", err)
   234  	}
   235  	m.udevMonMu.Lock()
   236  	defer m.udevMonMu.Unlock()
   237  	m.udevMon = nil
   238  }
   239  
   240  // Repository returns the interface repository used internally by the manager.
   241  //
   242  // This method has two use-cases:
   243  // - it is needed for setting up state in daemon tests
   244  // - it is needed to return the set of known interfaces in the daemon api
   245  //
   246  // In the second case it is only informational and repository has internal
   247  // locks to ensure consistency.
   248  func (m *InterfaceManager) Repository() *interfaces.Repository {
   249  	return m.repo
   250  }
   251  
   252  type ConnectionState struct {
   253  	// Auto indicates whether the connection was established automatically
   254  	Auto bool
   255  	// ByGadget indicates whether the connection was trigged by the gadget
   256  	ByGadget bool
   257  	// Interface name of the connection
   258  	Interface string
   259  	// Undesired indicates whether the connection, otherwise established
   260  	// automatically, was explicitly disconnected
   261  	Undesired        bool
   262  	StaticPlugAttrs  map[string]interface{}
   263  	DynamicPlugAttrs map[string]interface{}
   264  	StaticSlotAttrs  map[string]interface{}
   265  	DynamicSlotAttrs map[string]interface{}
   266  	HotplugGone      bool
   267  }
   268  
   269  // ConnectionStates return the state of connections stored in the state.
   270  // Note that this includes inactive connections (i.e. referring to non-
   271  // existing plug/slots), so this map must be cross-referenced with current
   272  // snap info if needed.
   273  // The state must be locked by the caller.
   274  func ConnectionStates(st *state.State) (connStateByRef map[string]ConnectionState, err error) {
   275  	states, err := getConns(st)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  
   280  	connStateByRef = make(map[string]ConnectionState, len(states))
   281  	for cref, cstate := range states {
   282  		connStateByRef[cref] = ConnectionState{
   283  			Auto:             cstate.Auto,
   284  			ByGadget:         cstate.ByGadget,
   285  			Interface:        cstate.Interface,
   286  			Undesired:        cstate.Undesired,
   287  			StaticPlugAttrs:  cstate.StaticPlugAttrs,
   288  			DynamicPlugAttrs: cstate.DynamicPlugAttrs,
   289  			StaticSlotAttrs:  cstate.StaticSlotAttrs,
   290  			DynamicSlotAttrs: cstate.DynamicSlotAttrs,
   291  			HotplugGone:      cstate.HotplugGone,
   292  		}
   293  	}
   294  	return connStateByRef, nil
   295  }
   296  
   297  // ConnectionStates return the state of connections tracked by the manager
   298  func (m *InterfaceManager) ConnectionStates() (connStateByRef map[string]ConnectionState, err error) {
   299  	m.state.Lock()
   300  	defer m.state.Unlock()
   301  
   302  	return ConnectionStates(m.state)
   303  }
   304  
   305  // ResolveDisconnect resolves potentially missing plug or slot names and
   306  // returns a list of fully populated connection references that can be
   307  // disconnected.
   308  //
   309  // It can be used in two different ways:
   310  // 1: snap disconnect <snap>:<plug> <snap>:<slot>
   311  // 2: snap disconnect <snap>:<plug or slot>
   312  //
   313  // In the first case the referenced plug and slot must be connected.  In the
   314  // second case any matching connection are returned but it is not an error if
   315  // there are no connections.
   316  //
   317  // In both cases the snap name can be omitted to implicitly refer to the core
   318  // snap. If there's no core snap it is simply assumed to be called "core" to
   319  // provide consistent error messages.
   320  func (m *InterfaceManager) ResolveDisconnect(plugSnapName, plugName, slotSnapName, slotName string, forget bool) ([]*interfaces.ConnRef, error) {
   321  	var connected func(plugSn, plug, slotSn, slot string) (bool, error)
   322  	var connectedPlugOrSlot func(snapName, plugOrSlotName string) ([]*interfaces.ConnRef, error)
   323  
   324  	if forget {
   325  		conns, err := getConns(m.state)
   326  		if err != nil {
   327  			return nil, err
   328  		}
   329  		connected = func(plugSn, plug, slotSn, slot string) (bool, error) {
   330  			cref := interfaces.ConnRef{
   331  				PlugRef: interfaces.PlugRef{Snap: plugSn, Name: plug},
   332  				SlotRef: interfaces.SlotRef{Snap: slotSn, Name: slot},
   333  			}
   334  			_, ok := conns[cref.ID()]
   335  			return ok, nil
   336  		}
   337  
   338  		connectedPlugOrSlot = func(snapName, plugOrSlotName string) ([]*interfaces.ConnRef, error) {
   339  			var refs []*interfaces.ConnRef
   340  			for connID := range conns {
   341  				cref, err := interfaces.ParseConnRef(connID)
   342  				if err != nil {
   343  					return nil, err
   344  				}
   345  				if cref.PlugRef.Snap == snapName && cref.PlugRef.Name == plugOrSlotName {
   346  					refs = append(refs, cref)
   347  				}
   348  				if cref.SlotRef.Snap == snapName && cref.SlotRef.Name == plugOrSlotName {
   349  					refs = append(refs, cref)
   350  				}
   351  			}
   352  			return refs, nil
   353  		}
   354  	} else {
   355  		connected = func(plugSn, plug, slotSn, slot string) (bool, error) {
   356  			_, err := m.repo.Connection(&interfaces.ConnRef{
   357  				PlugRef: interfaces.PlugRef{Snap: plugSn, Name: plug},
   358  				SlotRef: interfaces.SlotRef{Snap: slotSn, Name: slot},
   359  			})
   360  			if _, notConnected := err.(*interfaces.NotConnectedError); notConnected {
   361  				return false, nil
   362  			}
   363  			if err != nil {
   364  				return false, err
   365  			}
   366  			return true, nil
   367  		}
   368  
   369  		connectedPlugOrSlot = func(snapName, plugOrSlotName string) ([]*interfaces.ConnRef, error) {
   370  			return m.repo.Connected(snapName, plugOrSlotName)
   371  		}
   372  	}
   373  
   374  	coreSnapName := SystemSnapName()
   375  
   376  	// There are two allowed forms (see snap disconnect --help)
   377  	switch {
   378  	// 1: <snap>:<plug> <snap>:<slot>
   379  	// Return exactly one plug/slot or an error if it doesn't exist.
   380  	case plugName != "" && slotName != "":
   381  		// The snap name can be omitted to implicitly refer to the core snap.
   382  		if plugSnapName == "" {
   383  			plugSnapName = coreSnapName
   384  		}
   385  		// The snap name can be omitted to implicitly refer to the core snap.
   386  		if slotSnapName == "" {
   387  			slotSnapName = coreSnapName
   388  		}
   389  		// Ensure that slot and plug are connected
   390  		isConnected, err := connected(plugSnapName, plugName, slotSnapName, slotName)
   391  		if err != nil {
   392  			return nil, err
   393  		}
   394  		if !isConnected {
   395  			if forget {
   396  				return nil, fmt.Errorf("cannot forget connection %s:%s from %s:%s, it was not connected",
   397  					plugSnapName, plugName, slotSnapName, slotName)
   398  			}
   399  			return nil, fmt.Errorf("cannot disconnect %s:%s from %s:%s, it is not connected",
   400  				plugSnapName, plugName, slotSnapName, slotName)
   401  		}
   402  		return []*interfaces.ConnRef{
   403  			{
   404  				PlugRef: interfaces.PlugRef{Snap: plugSnapName, Name: plugName},
   405  				SlotRef: interfaces.SlotRef{Snap: slotSnapName, Name: slotName},
   406  			}}, nil
   407  	// 2: <snap>:<plug or slot> (through 1st pair)
   408  	// Return a list of connections involving specified plug or slot.
   409  	case plugName != "" && slotName == "" && slotSnapName == "":
   410  		// The snap name can be omitted to implicitly refer to the core snap.
   411  		if plugSnapName == "" {
   412  			plugSnapName = coreSnapName
   413  		}
   414  		return connectedPlugOrSlot(plugSnapName, plugName)
   415  	// 2: <snap>:<plug or slot> (through 2nd pair)
   416  	// Return a list of connections involving specified plug or slot.
   417  	case plugSnapName == "" && plugName == "" && slotName != "":
   418  		// The snap name can be omitted to implicitly refer to the core snap.
   419  		if slotSnapName == "" {
   420  			slotSnapName = coreSnapName
   421  		}
   422  		return connectedPlugOrSlot(slotSnapName, slotName)
   423  	default:
   424  		return nil, fmt.Errorf("allowed forms are <snap>:<plug> <snap>:<slot> or <snap>:<plug or slot>")
   425  	}
   426  }
   427  
   428  // DisableUDevMonitor disables the instantiation of udev monitor, but has no effect
   429  // if udev is already created; it should be called after creating InterfaceManager, before
   430  // first Ensure.
   431  // This method is meant for tests only.
   432  func (m *InterfaceManager) DisableUDevMonitor() {
   433  	if m.udevMon != nil {
   434  		logger.Noticef("UDev Monitor already created, cannot be disabled")
   435  		return
   436  	}
   437  	m.udevMonitorDisabled = true
   438  }
   439  
   440  var (
   441  	udevInitRetryTimeout = time.Minute * 5
   442  	createUDevMonitor    = udevmonitor.New
   443  )
   444  
   445  func (m *InterfaceManager) initUDevMonitor() error {
   446  	mon := createUDevMonitor(m.hotplugDeviceAdded, m.hotplugDeviceRemoved, m.hotplugEnumerationDone)
   447  	if err := mon.Connect(); err != nil {
   448  		return err
   449  	}
   450  	if err := mon.Run(); err != nil {
   451  		return err
   452  	}
   453  
   454  	m.udevMonMu.Lock()
   455  	defer m.udevMonMu.Unlock()
   456  	m.udevMon = mon
   457  	return nil
   458  }
   459  
   460  // MockSecurityBackends mocks the list of security backends that are used for setting up security.
   461  //
   462  // This function is public because it is referenced in the daemon
   463  func MockSecurityBackends(be []interfaces.SecurityBackend) func() {
   464  	old := backends.All
   465  	backends.All = be
   466  	return func() { backends.All = old }
   467  }
   468  
   469  // MockObservedDevicePath adds the given device to the map of observed devices.
   470  // This function is used for tests only.
   471  func (m *InterfaceManager) MockObservedDevicePath(devPath, ifaceName string, hotplugKey snap.HotplugKey) func() {
   472  	old := m.hotplugDevicePaths
   473  	m.hotplugDevicePaths[devPath] = append(m.hotplugDevicePaths[devPath], deviceData{hotplugKey: hotplugKey, ifaceName: ifaceName})
   474  	return func() { m.hotplugDevicePaths = old }
   475  }