monitor.go (6359B)
1 //go:build linux && cgo 2 // +build linux,cgo 3 4 package udev 5 6 /* 7 #cgo LDFLAGS: -ludev 8 #include <libudev.h> 9 #include <linux/types.h> 10 #include <stdlib.h> 11 #include <linux/kdev_t.h> 12 */ 13 import "C" 14 import ( 15 "context" 16 "errors" 17 "fmt" 18 "syscall" 19 20 "golang.org/x/sys/unix" 21 ) 22 23 // Monitor is an opaque object handling an event source 24 type Monitor struct { 25 ptr *C.struct_udev_monitor 26 u *Udev 27 } 28 29 const ( 30 maxEpollEvents = 32 31 epollTimeout = 1000 32 ) 33 34 // Lock the udev context 35 func (m *Monitor) lock() { 36 m.u.m.Lock() 37 } 38 39 // Unlock the udev context 40 func (m *Monitor) unlock() { 41 m.u.m.Unlock() 42 } 43 44 // Unref the monitor 45 func monitorUnref(m *Monitor) { 46 C.udev_monitor_unref(m.ptr) 47 } 48 49 // SetReceiveBufferSize sets the size of the kernel socket buffer. 50 // This call needs the appropriate privileges to succeed. 51 func (m *Monitor) SetReceiveBufferSize(size int) (err error) { 52 m.lock() 53 defer m.unlock() 54 if C.udev_monitor_set_receive_buffer_size(m.ptr, (C.int)(size)) != 0 { 55 err = errors.New("udev: udev_monitor_set_receive_buffer_size failed") 56 } 57 return 58 } 59 60 // FilterAddMatchSubsystem adds a filter matching the device against a subsystem. 61 // This filter is efficiently executed inside the kernel, and libudev subscribers will usually not be woken up for devices which do not match. 62 // The filter must be installed before the monitor is switched to listening mode with the DeviceChan function. 63 func (m *Monitor) FilterAddMatchSubsystem(subsystem string) (err error) { 64 m.lock() 65 defer m.unlock() 66 s := C.CString(subsystem) 67 defer freeCharPtr(s) 68 if C.udev_monitor_filter_add_match_subsystem_devtype(m.ptr, s, nil) != 0 { 69 err = errors.New("udev: udev_monitor_filter_add_match_subsystem_devtype failed") 70 } 71 return 72 } 73 74 // FilterAddMatchSubsystemDevtype adds a filter matching the device against a subsystem and device type. 75 // This filter is efficiently executed inside the kernel, and libudev subscribers will usually not be woken up for devices which do not match. 76 // The filter must be installed before the monitor is switched to listening mode with the DeviceChan function. 77 func (m *Monitor) FilterAddMatchSubsystemDevtype(subsystem, devtype string) (err error) { 78 m.lock() 79 defer m.unlock() 80 s, d := C.CString(subsystem), C.CString(devtype) 81 defer freeCharPtr(s) 82 defer freeCharPtr(d) 83 if C.udev_monitor_filter_add_match_subsystem_devtype(m.ptr, s, d) != 0 { 84 err = errors.New("udev: udev_monitor_filter_add_match_subsystem_devtype failed") 85 } 86 return 87 } 88 89 // FilterAddMatchTag adds a filter matching the device against a tag. 90 // This filter is efficiently executed inside the kernel, and libudev subscribers will usually not be woken up for devices which do not match. 91 // The filter must be installed before the monitor is switched to listening mode. 92 func (m *Monitor) FilterAddMatchTag(tag string) (err error) { 93 m.lock() 94 defer m.unlock() 95 t := C.CString(tag) 96 defer freeCharPtr(t) 97 if C.udev_monitor_filter_add_match_tag(m.ptr, t) != 0 { 98 err = errors.New("udev: udev_monitor_filter_add_match_tag failed") 99 } 100 return 101 } 102 103 // FilterUpdate updates the installed socket filter. 104 // This is only needed, if the filter was removed or changed. 105 func (m *Monitor) FilterUpdate() (err error) { 106 m.lock() 107 defer m.unlock() 108 if C.udev_monitor_filter_update(m.ptr) != 0 { 109 err = errors.New("udev: udev_monitor_filter_update failed") 110 } 111 return 112 } 113 114 // FilterRemove removes all filter from the Monitor. 115 func (m *Monitor) FilterRemove() (err error) { 116 m.lock() 117 defer m.unlock() 118 if C.udev_monitor_filter_remove(m.ptr) != 0 { 119 err = errors.New("udev: udev_monitor_filter_remove failed") 120 } 121 return 122 } 123 124 // receiveDevice is a helper function receiving a device while the Mutex is locked 125 func (m *Monitor) receiveDevice() (d *Device) { 126 m.lock() 127 defer m.unlock() 128 return m.u.newDevice(C.udev_monitor_receive_device(m.ptr)) 129 } 130 131 // DeviceChan binds the udev_monitor socket to the event source and spawns a 132 // goroutine. The goroutine efficiently waits on the monitor socket using epoll. 133 // Data is received from the udev monitor socket and a new Device is created 134 // with the data received. Pointers to the device are sent on the returned 135 // channel. The function takes a context as argument, which when done will stop 136 // the goroutine and close the device channel. Only socket connections with 137 // uid=0 are accepted. 138 func (m *Monitor) DeviceChan(ctx context.Context) (<-chan *Device, <-chan error, error) { 139 140 var event unix.EpollEvent 141 var events [maxEpollEvents]unix.EpollEvent 142 143 // Lock the context 144 m.lock() 145 defer m.unlock() 146 147 // Enable receiving 148 if C.udev_monitor_enable_receiving(m.ptr) != 0 { 149 return nil, nil, errors.New("udev: udev_monitor_enable_receiving failed") 150 } 151 152 // Set the fd to non-blocking 153 fd := C.udev_monitor_get_fd(m.ptr) 154 if e := unix.SetNonblock(int(fd), true); e != nil { 155 return nil, nil, errors.New("udev: unix.SetNonblock failed") 156 } 157 158 // Create an epoll fd 159 epfd, e := unix.EpollCreate1(0) 160 if e != nil { 161 return nil, nil, errors.New("udev: unix.EpollCreate1 failed") 162 } 163 164 // Add the fd to the epoll fd 165 event.Events = unix.EPOLLIN | unix.EPOLLET 166 event.Fd = int32(fd) 167 if e = unix.EpollCtl(epfd, unix.EPOLL_CTL_ADD, int(fd), &event); e != nil { 168 return nil, nil, errors.New("udev: unix.EpollCtl failed") 169 } 170 171 // Create the device and error channels 172 ch := make(chan *Device) 173 errorChannel := make(chan error) 174 175 // Create goroutine to epoll the fd 176 go func(fd int32) { 177 // Close the epoll fd when goroutine exits 178 defer unix.Close(epfd) 179 // Close the channel when goroutine exits 180 defer close(ch) 181 defer close(errorChannel) 182 // Loop forever 183 for { 184 // Poll the file descriptor 185 nevents, e := unix.EpollWait(epfd, events[:], epollTimeout) 186 // Ignore the EINTR error case since cancelation is performed with the 187 // context's Done() channel 188 errno, isErrno := e.(syscall.Errno) 189 if (e != nil && !isErrno) || (isErrno && errno != syscall.EINTR) { 190 errorChannel <- fmt.Errorf("Error during EpollWait: %s", errno.Error()) 191 return 192 } 193 // Check for done signal 194 select { 195 case <-ctx.Done(): 196 return 197 default: 198 } 199 // Process events 200 for ev := 0; ev < nevents; ev++ { 201 if events[ev].Fd == fd { 202 if (events[ev].Events & unix.EPOLLIN) != 0 { 203 for d := m.receiveDevice(); d != nil; d = m.receiveDevice() { 204 ch <- d 205 } 206 } 207 } 208 } 209 } 210 }(int32(fd)) 211 212 return ch, errorChannel, nil 213 }