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 }