github.com/rigado/snapd@v2.42.5-go-mod+incompatible/overlord/ifacestate/udevmonitor/udevmon.go (about) 1 // -*- Mode: Go; indent-tabs-mode: t -*- 2 3 /* 4 * Copyright (C) 2018 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 udevmonitor 21 22 import ( 23 "fmt" 24 25 "gopkg.in/tomb.v2" 26 27 "github.com/snapcore/snapd/interfaces/hotplug" 28 "github.com/snapcore/snapd/logger" 29 "github.com/snapcore/snapd/osutil/udev/netlink" 30 ) 31 32 type Interface interface { 33 Connect() error 34 Disconnect() error 35 Run() error 36 Stop() error 37 } 38 39 type DeviceAddedFunc func(device *hotplug.HotplugDeviceInfo) 40 type DeviceRemovedFunc func(device *hotplug.HotplugDeviceInfo) 41 type EnumerationDoneFunc func() 42 43 // Monitor monitors kernel uevents making it possible to find hotpluggable devices. 44 type Monitor struct { 45 tomb tomb.Tomb 46 deviceAdded DeviceAddedFunc 47 deviceRemoved DeviceRemovedFunc 48 enumerationDone func() 49 netlinkConn *netlink.UEventConn 50 // channels used by netlink connection and monitor 51 monitorStop chan struct{} 52 netlinkErrors chan error 53 netlinkEvents chan netlink.UEvent 54 55 // seen keeps track of all observed devices to know when to 56 // ignore a spurious event (in case it happens, e.g. when 57 // device gets reported by both the enumeration and monitor on 58 // startup). the keys are based on device paths which are 59 // guaranteed to be unique and stable till device gets 60 // removed. the lookup is not persisted and gets populated 61 // and updated in response to enumeration and hotplug events. 62 seen map[string]bool 63 } 64 65 func New(added DeviceAddedFunc, removed DeviceRemovedFunc, enumerationDone EnumerationDoneFunc) Interface { 66 m := &Monitor{ 67 deviceAdded: added, 68 deviceRemoved: removed, 69 enumerationDone: enumerationDone, 70 netlinkConn: &netlink.UEventConn{}, 71 seen: make(map[string]bool), 72 } 73 74 m.netlinkEvents = make(chan netlink.UEvent) 75 m.netlinkErrors = make(chan error) 76 77 return m 78 } 79 80 func (m *Monitor) EventsChannel() chan netlink.UEvent { 81 return m.netlinkEvents 82 } 83 84 func (m *Monitor) Connect() error { 85 if m.netlinkConn == nil || m.netlinkConn.Fd != 0 { 86 // this cannot happen in real code but may happen in tests 87 return fmt.Errorf("cannot connect: already connected") 88 } 89 90 if err := m.netlinkConn.Connect(netlink.UdevEvent); err != nil { 91 return fmt.Errorf("cannot start udev monitor: %s", err) 92 } 93 94 var filter netlink.Matcher 95 // TODO: extend with other criteria based on the hotplug interfaces 96 filter = &netlink.RuleDefinitions{ 97 Rules: []netlink.RuleDefinition{ 98 {Env: map[string]string{"SUBSYSTEM": "tty"}}, 99 {Env: map[string]string{"SUBSYSTEM": "net"}}, 100 {Env: map[string]string{"SUBSYSTEM": "usb"}}}} 101 102 m.monitorStop = m.netlinkConn.Monitor(m.netlinkEvents, m.netlinkErrors, filter) 103 104 return nil 105 } 106 107 func (m *Monitor) Disconnect() error { 108 select { 109 case m.monitorStop <- struct{}{}: 110 default: 111 } 112 return m.netlinkConn.Close() 113 } 114 115 // Run enumerates existing USB devices and starts a new goroutine that 116 // handles hotplug events (devices added or removed). It returns immediately. 117 // The goroutine must be stopped by calling Stop() method. 118 func (m *Monitor) Run() error { 119 // Gather devices from udevadm info output (enumeration on startup). 120 devices, parseErrors, err := hotplug.EnumerateExistingDevices() 121 if err != nil { 122 return fmt.Errorf("cannot enumerate existing devices: %s", err) 123 } 124 m.tomb.Go(func() error { 125 for _, perr := range parseErrors { 126 logger.Noticef("udev enumeration error: %s", perr) 127 } 128 for _, dev := range devices { 129 devPath := dev.DevicePath() 130 if m.seen[devPath] { 131 continue 132 } 133 m.seen[devPath] = true 134 if m.deviceAdded != nil { 135 m.deviceAdded(dev) 136 } 137 } 138 if m.enumerationDone != nil { 139 m.enumerationDone() 140 } 141 142 // Process hotplug events reported by udev monitor. 143 for { 144 select { 145 case err := <-m.netlinkErrors: 146 logger.Noticef("udev event error: %s", err) 147 case ev := <-m.netlinkEvents: 148 m.udevEvent(&ev) 149 case <-m.tomb.Dying(): 150 return m.Disconnect() 151 } 152 } 153 }) 154 155 return nil 156 } 157 158 func (m *Monitor) Stop() error { 159 m.tomb.Kill(nil) 160 err := m.tomb.Wait() 161 m.netlinkConn = nil 162 return err 163 } 164 165 func (m *Monitor) udevEvent(ev *netlink.UEvent) { 166 switch ev.Action { 167 case netlink.ADD: 168 m.addDevice(ev.KObj, ev.Env) 169 case netlink.REMOVE: 170 m.removeDevice(ev.KObj, ev.Env) 171 default: 172 } 173 } 174 175 func (m *Monitor) addDevice(kobj string, env map[string]string) { 176 dev, err := hotplug.NewHotplugDeviceInfo(env) 177 if err != nil { 178 return 179 } 180 devPath := dev.DevicePath() 181 if m.seen[devPath] { 182 return 183 } 184 m.seen[devPath] = true 185 if m.deviceAdded != nil { 186 m.deviceAdded(dev) 187 } 188 } 189 190 func (m *Monitor) removeDevice(kobj string, env map[string]string) { 191 dev, err := hotplug.NewHotplugDeviceInfo(env) 192 if err != nil { 193 return 194 } 195 devPath := dev.DevicePath() 196 if !m.seen[devPath] { 197 logger.Debugf("udev monitor observed remove event for unknown device %s", dev) 198 return 199 } 200 delete(m.seen, devPath) 201 if m.deviceRemoved != nil { 202 m.deviceRemoved(dev) 203 } 204 }