github.com/rigado/snapd@v2.42.5-go-mod+incompatible/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  	"sync"
    24  	"time"
    25  
    26  	"github.com/snapcore/snapd/interfaces"
    27  	"github.com/snapcore/snapd/interfaces/backends"
    28  	"github.com/snapcore/snapd/logger"
    29  	"github.com/snapcore/snapd/overlord/hookstate"
    30  	"github.com/snapcore/snapd/overlord/ifacestate/ifacerepo"
    31  	"github.com/snapcore/snapd/overlord/ifacestate/udevmonitor"
    32  	"github.com/snapcore/snapd/overlord/state"
    33  	"github.com/snapcore/snapd/snap"
    34  	"github.com/snapcore/snapd/timings"
    35  )
    36  
    37  type deviceData struct {
    38  	ifaceName  string
    39  	hotplugKey snap.HotplugKey
    40  }
    41  
    42  // InterfaceManager is responsible for the maintenance of interfaces in
    43  // the system state.  It maintains interface connections, and also observes
    44  // installed snaps to track the current set of available plugs and slots.
    45  type InterfaceManager struct {
    46  	state *state.State
    47  	repo  *interfaces.Repository
    48  
    49  	udevMonMu           sync.Mutex
    50  	udevMon             udevmonitor.Interface
    51  	udevRetryTimeout    time.Time
    52  	udevMonitorDisabled bool
    53  	// indexed by interface name and device key. Reset to nil when enumeration is done.
    54  	enumeratedDeviceKeys map[string]map[snap.HotplugKey]bool
    55  	enumerationDone      bool
    56  	// maps sysfs path -> [(interface name, device key)...]
    57  	hotplugDevicePaths map[string][]deviceData
    58  
    59  	// extras
    60  	extraInterfaces []interfaces.Interface
    61  	extraBackends   []interfaces.SecurityBackend
    62  }
    63  
    64  // Manager returns a new InterfaceManager.
    65  // Extra interfaces can be provided for testing.
    66  func Manager(s *state.State, hookManager *hookstate.HookManager, runner *state.TaskRunner, extraInterfaces []interfaces.Interface, extraBackends []interfaces.SecurityBackend) (*InterfaceManager, error) {
    67  	delayedCrossMgrInit()
    68  
    69  	// NOTE: hookManager is nil only when testing.
    70  	if hookManager != nil {
    71  		setupHooks(hookManager)
    72  	}
    73  
    74  	// Leave udevRetryTimeout at the default value, so that udev is initialized on first Ensure run.
    75  	m := &InterfaceManager{
    76  		state: s,
    77  		repo:  interfaces.NewRepository(),
    78  		// note: enumeratedDeviceKeys is reset to nil when enumeration is done
    79  		enumeratedDeviceKeys: make(map[string]map[snap.HotplugKey]bool),
    80  		hotplugDevicePaths:   make(map[string][]deviceData),
    81  		// extras
    82  		extraInterfaces: extraInterfaces,
    83  		extraBackends:   extraBackends,
    84  	}
    85  
    86  	taskKinds := map[string]bool{}
    87  	addHandler := func(kind string, do, undo state.HandlerFunc) {
    88  		taskKinds[kind] = true
    89  		runner.AddHandler(kind, do, undo)
    90  	}
    91  
    92  	addHandler("connect", m.doConnect, m.undoConnect)
    93  	addHandler("disconnect", m.doDisconnect, m.undoDisconnect)
    94  	addHandler("setup-profiles", m.doSetupProfiles, m.undoSetupProfiles)
    95  	addHandler("remove-profiles", m.doRemoveProfiles, m.doSetupProfiles)
    96  	addHandler("discard-conns", m.doDiscardConns, m.undoDiscardConns)
    97  	addHandler("auto-connect", m.doAutoConnect, m.undoAutoConnect)
    98  	addHandler("gadget-connect", m.doGadgetConnect, nil)
    99  	addHandler("auto-disconnect", m.doAutoDisconnect, nil)
   100  	addHandler("hotplug-add-slot", m.doHotplugAddSlot, nil)
   101  	addHandler("hotplug-connect", m.doHotplugConnect, nil)
   102  	addHandler("hotplug-update-slot", m.doHotplugUpdateSlot, nil)
   103  	addHandler("hotplug-remove-slot", m.doHotplugRemoveSlot, nil)
   104  	addHandler("hotplug-disconnect", m.doHotplugDisconnect, nil)
   105  
   106  	// don't block on hotplug-seq-wait task
   107  	runner.AddHandler("hotplug-seq-wait", m.doHotplugSeqWait, nil)
   108  
   109  	// helper for ubuntu-core -> core
   110  	addHandler("transition-ubuntu-core", m.doTransitionUbuntuCore, m.undoTransitionUbuntuCore)
   111  
   112  	// interface tasks might touch more than the immediate task target snap, serialize them
   113  	runner.AddBlocked(func(t *state.Task, running []*state.Task) bool {
   114  		if !taskKinds[t.Kind()] {
   115  			return false
   116  		}
   117  
   118  		for _, t := range running {
   119  			if taskKinds[t.Kind()] {
   120  				return true
   121  			}
   122  		}
   123  
   124  		return false
   125  	})
   126  
   127  	return m, nil
   128  }
   129  
   130  // StartUp implements StateStarterUp.Startup.
   131  func (m *InterfaceManager) StartUp() error {
   132  	s := m.state
   133  	perfTimings := timings.New(map[string]string{"startup": "ifacemgr"})
   134  
   135  	s.Lock()
   136  	defer s.Unlock()
   137  
   138  	snaps, err := snapsWithSecurityProfiles(m.state)
   139  	if err != nil {
   140  		return err
   141  	}
   142  	// Before deciding about adding implicit slots to any snap we need to scan
   143  	// the set of snaps we know about. If any of those is "snapd" then for the
   144  	// duration of this process always add implicit slots to snapd and not to
   145  	// any other type: os snap and use a mapper to use names core-snapd-system
   146  	// on state, in memory and in API responses, respectively.
   147  	m.selectInterfaceMapper(snaps)
   148  
   149  	if err := m.addInterfaces(m.extraInterfaces); err != nil {
   150  		return err
   151  	}
   152  	if err := m.addBackends(m.extraBackends); err != nil {
   153  		return err
   154  	}
   155  	if err := m.addSnaps(snaps); err != nil {
   156  		return err
   157  	}
   158  	if err := m.renameCorePlugConnection(); err != nil {
   159  		return err
   160  	}
   161  	if err := removeStaleConnections(m.state); err != nil {
   162  		return err
   163  	}
   164  	if _, err := m.reloadConnections(""); err != nil {
   165  		return err
   166  	}
   167  	if profilesNeedRegeneration() {
   168  		if err := m.regenerateAllSecurityProfiles(perfTimings); err != nil {
   169  			return err
   170  		}
   171  	}
   172  
   173  	ifacerepo.Replace(s, m.repo)
   174  
   175  	perfTimings.Save(s)
   176  
   177  	return nil
   178  }
   179  
   180  // Ensure implements StateManager.Ensure.
   181  func (m *InterfaceManager) Ensure() error {
   182  	if m.udevMonitorDisabled {
   183  		return nil
   184  	}
   185  	m.udevMonMu.Lock()
   186  	udevMon := m.udevMon
   187  	m.udevMonMu.Unlock()
   188  	if udevMon != nil {
   189  		return nil
   190  	}
   191  
   192  	// don't initialize udev monitor until we have a system snap so that we
   193  	// can attach hotplug interfaces to it.
   194  	if !checkSystemSnapIsPresent(m.state) {
   195  		return nil
   196  	}
   197  
   198  	// retry udev monitor initialization every 5 minutes
   199  	now := time.Now()
   200  	if now.After(m.udevRetryTimeout) {
   201  		err := m.initUDevMonitor()
   202  		if err != nil {
   203  			m.udevRetryTimeout = now.Add(udevInitRetryTimeout)
   204  		}
   205  		return err
   206  	}
   207  	return nil
   208  }
   209  
   210  // Stop implements StateStopper. It stops the udev monitor,
   211  // if running.
   212  func (m *InterfaceManager) Stop() {
   213  	m.udevMonMu.Lock()
   214  	udevMon := m.udevMon
   215  	m.udevMonMu.Unlock()
   216  	if udevMon == nil {
   217  		return
   218  	}
   219  	if err := udevMon.Stop(); err != nil {
   220  		logger.Noticef("Cannot stop udev monitor: %s", err)
   221  	}
   222  	m.udevMonMu.Lock()
   223  	defer m.udevMonMu.Unlock()
   224  	m.udevMon = nil
   225  }
   226  
   227  // Repository returns the interface repository used internally by the manager.
   228  //
   229  // This method has two use-cases:
   230  // - it is needed for setting up state in daemon tests
   231  // - it is needed to return the set of known interfaces in the daemon api
   232  //
   233  // In the second case it is only informational and repository has internal
   234  // locks to ensure consistency.
   235  func (m *InterfaceManager) Repository() *interfaces.Repository {
   236  	return m.repo
   237  }
   238  
   239  type ConnectionState struct {
   240  	// Auto indicates whether the connection was established automatically
   241  	Auto bool
   242  	// ByGadget indicates whether the connection was trigged by the gadget
   243  	ByGadget bool
   244  	// Interface name of the connection
   245  	Interface string
   246  	// Undesired indicates whether the connection, otherwise established
   247  	// automatically, was explicitly disconnected
   248  	Undesired        bool
   249  	StaticPlugAttrs  map[string]interface{}
   250  	DynamicPlugAttrs map[string]interface{}
   251  	StaticSlotAttrs  map[string]interface{}
   252  	DynamicSlotAttrs map[string]interface{}
   253  	HotplugGone      bool
   254  }
   255  
   256  // ConnectionStates return the state of connections tracked by the manager
   257  func (m *InterfaceManager) ConnectionStates() (connStateByRef map[string]ConnectionState, err error) {
   258  	m.state.Lock()
   259  	defer m.state.Unlock()
   260  	states, err := getConns(m.state)
   261  	if err != nil {
   262  		return nil, err
   263  	}
   264  
   265  	connStateByRef = make(map[string]ConnectionState, len(states))
   266  	for cref, cstate := range states {
   267  		connStateByRef[cref] = ConnectionState{
   268  			Auto:             cstate.Auto,
   269  			ByGadget:         cstate.ByGadget,
   270  			Interface:        cstate.Interface,
   271  			Undesired:        cstate.Undesired,
   272  			StaticPlugAttrs:  cstate.StaticPlugAttrs,
   273  			DynamicPlugAttrs: cstate.DynamicPlugAttrs,
   274  			StaticSlotAttrs:  cstate.StaticSlotAttrs,
   275  			DynamicSlotAttrs: cstate.DynamicSlotAttrs,
   276  			HotplugGone:      cstate.HotplugGone,
   277  		}
   278  	}
   279  	return connStateByRef, nil
   280  }
   281  
   282  // DisableUDevMonitor disables the instantiation of udev monitor, but has no effect
   283  // if udev is already created; it should be called after creating InterfaceManager, before
   284  // first Ensure.
   285  // This method is meant for tests only.
   286  func (m *InterfaceManager) DisableUDevMonitor() {
   287  	if m.udevMon != nil {
   288  		logger.Noticef("UDev Monitor already created, cannot be disabled")
   289  		return
   290  	}
   291  	m.udevMonitorDisabled = true
   292  }
   293  
   294  var (
   295  	udevInitRetryTimeout = time.Minute * 5
   296  	createUDevMonitor    = udevmonitor.New
   297  )
   298  
   299  func (m *InterfaceManager) initUDevMonitor() error {
   300  	mon := createUDevMonitor(m.hotplugDeviceAdded, m.hotplugDeviceRemoved, m.hotplugEnumerationDone)
   301  	if err := mon.Connect(); err != nil {
   302  		return err
   303  	}
   304  	if err := mon.Run(); err != nil {
   305  		mon.Disconnect()
   306  		return err
   307  	}
   308  
   309  	m.udevMonMu.Lock()
   310  	defer m.udevMonMu.Unlock()
   311  	m.udevMon = mon
   312  	return nil
   313  }
   314  
   315  // MockSecurityBackends mocks the list of security backends that are used for setting up security.
   316  //
   317  // This function is public because it is referenced in the daemon
   318  func MockSecurityBackends(be []interfaces.SecurityBackend) func() {
   319  	old := backends.All
   320  	backends.All = be
   321  	return func() { backends.All = old }
   322  }
   323  
   324  // MockObservedDevicePath adds the given device to the map of observed devices.
   325  // This function is used for tests only.
   326  func (m *InterfaceManager) MockObservedDevicePath(devPath, ifaceName string, hotplugKey snap.HotplugKey) func() {
   327  	old := m.hotplugDevicePaths
   328  	m.hotplugDevicePaths[devPath] = append(m.hotplugDevicePaths[devPath], deviceData{hotplugKey: hotplugKey, ifaceName: ifaceName})
   329  	return func() { m.hotplugDevicePaths = old }
   330  }