//go:build linux package usb import ( "fmt" "sync" "golang.org/x/sys/unix" ) // Externally supplied file descriptors, keyed by bus ID. // // Android is the reason this exists. Apps there cannot open /dev/bus/usb: // access goes through the framework, which shows a permission dialog and // returns an already-open descriptor. A small Java shim obtains it and passes // it to this process, which then drives the device through the same usbdevfs // ioctls as anywhere else — the kernel interface is identical, only the way // the descriptor is obtained differs. var ( adoptedMu sync.Mutex adoptedFDs = make(map[string]int) ) // AdoptDeviceFD registers an already-open usbdevfs file descriptor for a bus // ID. The next OpenDevice for that bus ID takes it instead of opening a path. // // Ownership transfers: the descriptor is closed when the resulting handle is // closed, or by ReleaseAdoptedFDs if it is never claimed. func AdoptDeviceFD(busID string, fd int) error { if busID == "" { return fmt.Errorf("bus ID is required") } if fd < 0 { return fmt.Errorf("invalid file descriptor %d", fd) } // Reject a descriptor that is not actually usable, so the failure is // reported here rather than as a confusing ioctl error much later. if _, err := unix.FcntlInt(uintptr(fd), unix.F_GETFD, 0); err != nil { return fmt.Errorf("file descriptor %d is not open: %w", fd, err) } adoptedMu.Lock() defer adoptedMu.Unlock() if old, exists := adoptedFDs[busID]; exists && old != fd { unix.Close(old) } adoptedFDs[busID] = fd return nil } // takeAdoptedFD removes and returns a registered descriptor, if any. func takeAdoptedFD(busID string) (int, bool) { adoptedMu.Lock() defer adoptedMu.Unlock() fd, ok := adoptedFDs[busID] if ok { delete(adoptedFDs, busID) } return fd, ok } // HasAdoptedFD reports whether a descriptor is registered for a bus ID. func HasAdoptedFD(busID string) bool { adoptedMu.Lock() defer adoptedMu.Unlock() _, ok := adoptedFDs[busID] return ok } // ReleaseAdoptedFDs closes every registered descriptor that was never claimed. func ReleaseAdoptedFDs() { adoptedMu.Lock() defer adoptedMu.Unlock() for busID, fd := range adoptedFDs { unix.Close(fd) delete(adoptedFDs, busID) } }