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 }