usb-ks

USB Killswitch
git clone git://git.laack.co/usb-ks.git
Log | Files | Refs | README | LICENSE

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 }