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 }