Skip to content

Commit

Permalink
introduce a HidError which also contains the errno/LastError value (c…
Browse files Browse the repository at this point in the history
…urrently not exported), not only the error text

this is mainly to detect if a systemcall was interrupted by a signal
which may happen since go1.14 due to preemtible goroutines.
at least the hidraw backend is affected as it uses syscalls to
read/wrtie data.
  • Loading branch information
bearsh committed Dec 2, 2023
1 parent 79a0545 commit 7ea0ddf
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 22 deletions.
49 changes: 47 additions & 2 deletions hid.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,58 @@
// hid - Gopher Interface Devices (USB HID)
// Copyright (c) 2017 Péter Szilágyi. All rights reserved.
// 2023 Martin Gysel
//
// This file is released under the 3-clause BSD license. Note however that Linux
// support depends on libusb, released under GNU LGPL 2.1 or later.
// support may depends on libusb, released under GNU LGPL 2.1 or later.

// Package hid provides an interface for USB HID devices.
package hid

import "errors"
import (
"errors"
"fmt"
"runtime"
"syscall"
)

type HidError struct {
s string
code int
}

func newHidError(text string, code int) error {
return &HidError{s: text, code: code}
}

func (e *HidError) Error() string {
if e.code == 0 {
return e.s
}
return fmt.Sprintf("%s (%d)", e.s, e.code)
}

// InterruptedSystemCall returns true if a blocking operation was
// interrupted by a system call.
//
// Since go1.14, goroutines are now asynchronously preemptible:
// A consequence of the implementation of preemption is that on Unix systems,
// including Linux and macOS systems, programs built with Go 1.14 will receive
// more signals than programs built with earlier releases. This means that
// programs that use packages like syscall or golang.org/x/sys/unix will
// see more slow system calls fail with EINTR errors.
//
// The same is true for programs using cgo for system calls.
// Unfortunately we have no control over the underlying hidapi library, but
// we can, on error, read out errno and check for EINTR errors (at least on
// supported platforms). For convenience, this function has been added to quickly
// check if the underlying systemcall got interrupted by a signal.
// AFAIK this only happens on linux with the hidraw backend.
func (e *HidError) InterruptedSystemCall() bool {
if runtime.GOOS != "windows" {
return e.code == int(syscall.EINTR)
}
return false
}

// ErrDeviceClosed is returned for operations where the device closed before or
// during the execution.
Expand Down
56 changes: 36 additions & 20 deletions hid_enabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,18 @@ package hid
#elif OS_WINDOWS
#include "hidapi/windows/hid.c"
#endif
static int get_errno(void) {
#ifdef OS_WINDOWS
return (int)GetLastError();
#else
return errno;
#endif
}
*/
import "C"

import (
"errors"
"runtime"
"sync"
"unsafe"
Expand Down Expand Up @@ -163,7 +170,7 @@ func OpenByPath(p string) (*Device, error) {

info := C.hid_get_device_info(device)
if info == nil {
return nil, errors.New("hidapi: failed to query device info")
return nil, newHidError("hidapi: failed to query device info", (int)(C.get_errno()))
}

dev := &Device{
Expand Down Expand Up @@ -240,13 +247,14 @@ func (dev *Device) Write(b []byte) (int, error) {
if device == nil {
return 0, ErrDeviceClosed
}

// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return 0, errors.New("hidapi: unknown failure")
return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return 0, errors.New("hidapi: " + failure)
return 0, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}
return written, nil
}
Expand Down Expand Up @@ -290,10 +298,10 @@ func (dev *Device) SendFeatureReport(b []byte) (int, error) {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return 0, errors.New("hidapi: unknown failure")
return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return 0, errors.New("hidapi: " + failure)
return 0, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}
return written, nil
}
Expand All @@ -303,6 +311,10 @@ func (dev *Device) SendFeatureReport(b []byte) (int, error) {
// Input reports are returned to the host through the INTERRUPT IN
// endpoint. The first byte will contain the Report number if the
// device uses numbered reports.
//
// A blocking system call may be interrupted by the go runtime. This
// can be checked by casting the error to an HidError and calling
// the InterruptedSystemCall() method.
func (dev *Device) Read(b []byte) (int, error) {
// Abort if nothing to read
if len(b) == 0 {
Expand Down Expand Up @@ -330,10 +342,10 @@ func (dev *Device) Read(b []byte) (int, error) {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return 0, errors.New("hidapi: unknown failure")
return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return 0, errors.New("hidapi: " + failure)
return 0, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}
return read, nil
}
Expand All @@ -344,6 +356,10 @@ func (dev *Device) Read(b []byte) (int, error) {
// Input reports are returned to the host through the INTERRUPT IN
// endpoint. The first byte will contain the Report number if the
// device uses numbered reports.
//
// A blocking system call may be interrupted by the go runtime. This
// can be checked by casting the error to an HidError and calling
// the InterruptedSystemCall() method.
func (dev *Device) ReadTimeout(b []byte, timeout int) (int, error) {
// Abort if nothing to read
if len(b) == 0 {
Expand Down Expand Up @@ -371,10 +387,10 @@ func (dev *Device) ReadTimeout(b []byte, timeout int) (int, error) {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return 0, errors.New("hidapi: unknown failure")
return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return 0, errors.New("hidapi: " + failure)
return 0, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}
return read, nil
}
Expand Down Expand Up @@ -413,10 +429,10 @@ func (dev *Device) GetFeatureReport(b []byte) (int, error) {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return 0, errors.New("hidapi: unknown failure")
return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return 0, errors.New("hidapi: " + failure)
return 0, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}

return read, nil
Expand Down Expand Up @@ -456,10 +472,10 @@ func (dev *Device) GetInputReport(b []byte) (int, error) {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return 0, errors.New("hidapi: unknown failure")
return 0, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return 0, errors.New("hidapi: " + failure)
return 0, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}

return read, nil
Expand Down Expand Up @@ -498,10 +514,10 @@ func (dev *Device) SetNonblocking(b bool) error {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return errors.New("hidapi: unknown failure")
return newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return errors.New("hidapi: " + failure)
return newHidError("hidapi: "+failure, (int)(C.get_errno()))
}

return nil
Expand Down Expand Up @@ -532,10 +548,10 @@ func (dev *Device) GetDeviceInfo() (*DeviceInfo, error) {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return nil, errors.New("hidapi: unknown failure")
return nil, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return nil, errors.New("hidapi: " + failure)
return nil, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}

info := &DeviceInfo{
Expand Down Expand Up @@ -588,10 +604,10 @@ func (dev *Device) GetReportDescriptor() ([]byte, error) {
// Device not closed, some other error occurred
message := C.hid_error(device)
if message == nil {
return nil, errors.New("hidapi: unknown failure")
return nil, newHidError("hidapi: unknown failure", (int)(C.get_errno()))
}
failure, _ := wcharTToString(message)
return nil, errors.New("hidapi: " + failure)
return nil, newHidError("hidapi: "+failure, (int)(C.get_errno()))
}

return b[:read], nil
Expand Down

0 comments on commit 7ea0ddf

Please sign in to comment.