Skip to content

Commit

Permalink
introduce a HidError which also contains the errno value (currently n…
Browse files Browse the repository at this point in the history
…ot 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 c56b520
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 22 deletions.
45 changes: 43 additions & 2 deletions hid.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,54 @@
// 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"
"syscall"
)

type HidError struct {
s string
errno int
}

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

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

// 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 {
return e.errno == int(syscall.EINTR)
}

// ErrDeviceClosed is returned for operations where the device closed before or
// during the execution.
Expand Down
51 changes: 31 additions & 20 deletions hid_enabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ package hid
#elif OS_WINDOWS
#include "hidapi/windows/hid.c"
#endif
#include <errno.h>
static int _get_errno(void) { return errno; }

Check failure on line 52 in hid_enabled.go

View workflow job for this annotation

GitHub Actions / Build on windows-latest

error: conflicting types for '_get_errno'; have 'int(void)'
*/
import "C"

import (
"errors"
"runtime"
"sync"
"unsafe"
Expand Down Expand Up @@ -163,7 +165,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 +242,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 +293,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 +306,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 +337,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 +351,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 +382,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 +424,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 +467,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 +509,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 +543,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 +599,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 c56b520

Please sign in to comment.