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