Skip to content

Commit

Permalink
Merge pull request #2626 from jimklimov/issue-2615
Browse files Browse the repository at this point in the history
Port and use a variant of `nut_usb_get_string()` for `nut-scanner`
  • Loading branch information
jimklimov authored Sep 11, 2024
2 parents abfc752 + 9e16b0e commit 7cb95f4
Show file tree
Hide file tree
Showing 4 changed files with 199 additions and 34 deletions.
4 changes: 3 additions & 1 deletion NEWS.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -166,9 +166,11 @@ https://github.com/networkupstools/nut/milestone/11
* Currently this was tested to fix certain device discovery with `usbhid-ups`;
should also apply out of the box to same discovery logic in `blazer_usb`,
`nutdrv_qx`, `riello_usb` and `tripplite_usb` drivers.
* Also applied to `nut-scanner` and `libnutscan`. [issue #2615]
* More work may be needed for other USB-capable drivers (`richcomm_usb`,
`nutdrv_atcl_usb`) and for general code to collect string readings and
other data points, and for `nut-scanner`.
other data points, and to configure the fallback locale or choose one
if several are served by the device. [issues #2613, #2614, #2615]
- Introduced a new driver concept for interaction with OS-reported hardware
monitoring readings. Currently instantiated as `hwmon_ina219` specifically
Expand Down
18 changes: 12 additions & 6 deletions drivers/usb-common.c
Original file line number Diff line number Diff line change
Expand Up @@ -416,7 +416,10 @@ void warn_if_bad_usb_port_filename(const char *fn) {
*/
#define MAX_STRING_DESC_TRIES 3

/* API neutral, handles retries */
/* API neutral, handles retries.
* Note for future development: a variant of this code is adapted into
* tools/nut-scanner/scan_usb.c - please keep in sync if changing here.
*/
static int nut_usb_get_string_descriptor(
usb_dev_handle *udev,
int StringIdx,
Expand All @@ -433,13 +436,16 @@ static int nut_usb_get_string_descriptor(
break;
} else if (tries) {
upsdebugx(1, "%s: string descriptor %d request failed, retrying...", __func__, StringIdx);
usleep(50000); /* 50 ms, might help in some cases */
usleep(50000); /* 50 ms, might help in some cases */
}
}
return ret;
}

/* API neutral, assumes en_US if langid descriptor is broken */
/* API neutral, assumes en_US if langid descriptor is broken.
* Note for future development: a variant of this code is adapted into
* tools/nut-scanner/scan_usb.c - please keep in sync if changing here.
*/
int nut_usb_get_string(
usb_dev_handle *udev,
int StringIdx,
Expand Down Expand Up @@ -480,13 +486,13 @@ int nut_usb_get_string(

/* translate simple UTF-16LE to 8-bit */
len = ret < (int)buflen ? ret : (int)buflen;
len = len / 2 - 1; // 16-bit characters, without header
len = len < (int)buflen - 1 ? len : (int)buflen - 1; // reserve for null terminator
len = len / 2 - 1; /* 16-bit characters, without header */
len = len < (int)buflen - 1 ? len : (int)buflen - 1; /* reserve for null terminator */
for (i = 0; i < len; i++) {
if (buffer[2 + i * 2 + 1] == 0)
buf[i] = buffer[2 + i * 2];
else
buf[i] = '?'; // not decoded
buf[i] = '?'; /* not decoded */
}
buf[i] = '\0';

Expand Down
61 changes: 44 additions & 17 deletions m4/ax_realpath_lib.m4
Original file line number Diff line number Diff line change
Expand Up @@ -156,25 +156,52 @@ AC_DEFUN([AX_REALPATH_LIB],
AS_IF([test -n "${myLIBPATH}" && test -s "${myLIBPATH}"], [
AC_MSG_RESULT([initially '${myLIBPATH}'])
dnl # Resolving the directory location is a nice bonus
dnl # (usually the paths are relative to toolkit and ugly,
dnl # though maybe arguably portable with regard to symlinks).
dnl # The primary goal is to resolve the actual library file
dnl # name like "libnetsnmp.so.1.2.3", so we can preferentially
dnl # try to dlopen() it on a system with a packaged footprint
dnl # that does not serve short (developer-friendly) links like
dnl # "libnetsnmp.so".
myLIBPATH_REAL="${myLIBPATH}"
AX_REALPATH([${myLIBPATH}], [myLIBPATH_REAL])
AC_MSG_CHECKING([whether the file is a "GNU ld script" and not a binary])
AS_IF([LANG=C LC_ALL=C file "${myLIBPATH}" | grep -Ei '(ascii|text)' && grep -w GROUP "${myLIBPATH}" >/dev/null], [
# dnl e.g. # cat /usr/lib/x86_64-linux-gnu/libusb.so
# dnl /* GNU ld script. */
# dnl GROUP ( /lib/x86_64-linux-gnu/libusb-0.1.so.4.4.4 )
# dnl Note that spaces around parentheses vary, more keywords
# dnl may be present in a group (e.g. AS_NEEDED), and comment
# dnl strings are inconsistent (useless to match by).
AC_MSG_RESULT([yes, iterate further])
myLIBPATH_LDSCRIPT="`grep -w GROUP "${myLIBPATH}" | sed 's,^.*GROUP *( *\(/@<:@^ @:>@*\.so@<:@^ @:>@*\)@<:@^0-9a-zA-Z_.-@:>@.*$,\1,'`"
AS_IF([test -n "${myLIBPATH_LDSCRIPT}" && test -s "${myLIBPATH_LDSCRIPT}"], [
AC_MSG_NOTICE([will dig into ${myLIBPATH_LDSCRIPT}])
dnl # See detailed comments just below
myLIBPATH_REAL="${myLIBPATH_LDSCRIPT}"
AX_REALPATH([${myLIBPATH_LDSCRIPT}], [myLIBPATH_REAL])
], [
AC_MSG_NOTICE([could not determine a further path name, will use what we have])
dnl # See detailed comments just below
myLIBPATH_REAL="${myLIBPATH}"
AX_REALPATH([${myLIBPATH}], [myLIBPATH_REAL])
])
],[
AC_MSG_RESULT([no, seems like a normal binary])
dnl # Resolving the directory location is a nice bonus
dnl # (usually the paths are relative to toolkit and ugly,
dnl # though maybe arguably portable with regard to symlinks).
dnl # The primary goal is to resolve the actual library file
dnl # name like "libnetsnmp.so.1.2.3", so we can preferentially
dnl # try to dlopen() it on a system with a packaged footprint
dnl # that does not serve short (developer-friendly) links like
dnl # "libnetsnmp.so".
myLIBPATH_REAL="${myLIBPATH}"
AX_REALPATH([${myLIBPATH}], [myLIBPATH_REAL])
])
AC_MSG_RESULT(${myLIBPATH_REAL})
$2="${myLIBPATH_REAL}"
],[
],[
AC_MSG_RESULT([not found])
$2="$3"
])
],
[AC_MSG_WARN([Compiler not detected as GCC/CLANG-compatible, skipping REALPATH_LIB($1)])
$2="$3"
]
)
])
],
[AC_MSG_WARN([Compiler not detected as GCC/CLANG-compatible, skipping REALPATH_LIB($1)])
$2="$3"
])
])
150 changes: 140 additions & 10 deletions tools/nut-scanner/scan_usb.c
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,15 @@ static const char *dl_error = NULL;
static char *dl_saved_libname = NULL;

static int (*nut_usb_close)(libusb_device_handle *dev);
static int (*nut_usb_get_string_simple)(libusb_device_handle *dev, int index,
static int (*nut_usb_control_transfer)(libusb_device_handle *dev,
uint8_t request_type, uint8_t bRequest, uint16_t wValue, uint16_t wIndex,
unsigned char *data, uint16_t wLength, unsigned int timeout);
static int (*nut_usb_get_string_with_langid)(libusb_device_handle *dev, int index, int langid,
char *buf, size_t buflen);
/* Fallback implem if the above is not a library symbol */
static int nut_usb_get_string_with_langid_control_transfer(
libusb_device_handle *dev, int index, int langid,
char *buf, size_t buflen);


/* Compatibility layer between libusb 0.1 and 1.0 */
#if WITH_LIBUSB_1_0
Expand All @@ -64,6 +70,9 @@ static int (*nut_usb_get_string_simple)(libusb_device_handle *dev, int index,
static uint8_t (*nut_usb_get_port_number)(libusb_device *dev);
static int (*nut_usb_get_device_descriptor)(libusb_device *dev,
struct libusb_device_descriptor *desc);
# define USB_DT_STRING LIBUSB_DT_STRING
# define USB_ENDPOINT_IN LIBUSB_ENDPOINT_IN
# define USB_REQ_GET_DESCRIPTOR LIBUSB_REQUEST_GET_DESCRIPTOR
#else /* => WITH_LIBUSB_0_1 */
# define USB_INIT_SYMBOL "usb_init"
# define USB_OPEN_SYMBOL "usb_open"
Expand Down Expand Up @@ -196,11 +205,20 @@ int nutscan_load_usb_library(const char *libname_path)
goto err;
}

*(void **) (&nut_usb_get_string_simple) = lt_dlsym(dl_handle,
"libusb_get_string_descriptor_ascii");
*(void **) (&nut_usb_control_transfer) = lt_dlsym(dl_handle,
"libusb_control_transfer");
if ((dl_error = lt_dlerror()) != NULL) {
goto err;
}

*(void **) (&nut_usb_get_string_with_langid) = lt_dlsym(dl_handle,
"libusb_get_string_descriptor");
if ((dl_error = lt_dlerror()) != NULL) {
/* This one may be only defined in a header as an inline method;
* then we are adapting it via nut_usb_control_transfer().
*/
nut_usb_get_string_with_langid = NULL;
}
#else /* for libusb 0.1 */
*(void **) (&nut_usb_find_busses) = lt_dlsym(dl_handle,
"usb_find_busses");
Expand Down Expand Up @@ -228,12 +246,23 @@ int nutscan_load_usb_library(const char *libname_path)
goto err;
}

*(void **) (&nut_usb_get_string_simple) = lt_dlsym(dl_handle,
"usb_get_string_simple");
*(void **) (&nut_usb_control_transfer) = lt_dlsym(dl_handle,
"usb_control_msg");
if ((dl_error = lt_dlerror()) != NULL) {
goto err;
}
#endif /* WITH_LIBUSB_1_0 */

*(void **) (&nut_usb_get_string_with_langid) = lt_dlsym(dl_handle,
"usb_get_string");
if ((dl_error = lt_dlerror()) != NULL) {
/* See comment above */
nut_usb_get_string_with_langid = NULL;
}
#endif /* not WITH_LIBUSB_1_0 => for libusb 0.1 */

if (nut_usb_get_string_with_langid == NULL) {
nut_usb_get_string_with_langid = nut_usb_get_string_with_langid_control_transfer;
}

if (dl_saved_libname)
free(dl_saved_libname);
Expand Down Expand Up @@ -273,6 +302,107 @@ static char* is_usb_device_supported(usb_device_id_t *usb_device_id_list,
return NULL;
}

/* This one may be only defined in a header as an inline method;
* then we are adapting it via nut_usb_control_transfer() per
* https://github.com/libusb/libusb-compat-0.1/blob/eaed7b8f11badaf07a91e07538f6e8842f59eaab/libusb/libusb-dload.h#L165-L171
*/
static int nut_usb_get_string_with_langid_control_transfer(
libusb_device_handle *dev, int index, int langid,
char *buf, size_t buflen)
{
return (*nut_usb_control_transfer)(
dev, USB_ENDPOINT_IN, USB_REQ_GET_DESCRIPTOR,
(USB_DT_STRING << 8) + index, langid,
(unsigned char *)buf, buflen, 1000);
}

/* Replicated from usb-common.c with consideration for nut-scanner's
* use of method pointers instead of direct linking. API neutral,
* handles retries. Retries were originally introduced for "Tripp Lite"
* devices, see https://github.com/networkupstools/nut/issues/414
*/
#define MAX_STRING_DESC_TRIES 3

static int nut_usb_get_string_descriptor(
libusb_device_handle *udev,
int StringIdx,
int langid,
char *buf,
size_t buflen)
{
int ret = -1;
int tries = MAX_STRING_DESC_TRIES;

while (tries--) {
ret = (*nut_usb_get_string_with_langid)(
udev, StringIdx,
langid, buf, buflen);
if (ret >= 0) {
break;
} else if (tries) {
upsdebugx(1, "%s: string descriptor %d request failed, retrying...", __func__, StringIdx);
usleep(50000); /* 50 ms, might help in some cases */
}
}
return ret;
}

/* Replicated from usb-common.c with consideration for nut-scanner's
* use of method pointers instead of direct linking. API neutral,
* assumes en_US if langid descriptor is broken */
static int nut_usb_get_string(
libusb_device_handle *udev,
int StringIdx,
char *buf,
size_t buflen)
{
int ret;
char buffer[255];
int langid;
int len;
int i;

if (!udev || StringIdx < 1 || StringIdx > 255) {
return -1;
}

/* request langid descriptor */
ret = nut_usb_get_string_descriptor(udev, 0, 0, buffer, 4);
if (ret < 0)
return ret;

if (ret == 4 && buffer[0] >= 4 && buffer[1] == USB_DT_STRING) {
langid = buffer[2] | (buffer[3] << 8);
} else {
upsdebugx(1, "%s: Broken language identifier, assuming en_US", __func__);
langid = 0x0409;
}

/* retrieve string in preferred language */
ret = nut_usb_get_string_descriptor(udev, StringIdx, langid, buffer, sizeof(buffer));
if (ret < 0) {
#ifdef WIN32
/* only for libusb0 ? */
errno = -ret;
#endif
return ret;
}

/* translate simple UTF-16LE to 8-bit */
len = ret < (int)buflen ? ret : (int)buflen;
len = len / 2 - 1; /* 16-bit characters, without header */
len = len < (int)buflen - 1 ? len : (int)buflen - 1; /* reserve for null terminator */
for (i = 0; i < len; i++) {
if (buffer[2 + i * 2 + 1] == 0)
buf[i] = buffer[2 + i * 2];
else
buf[i] = '?'; /* not decoded */
}
buf[i] = '\0';

return len;
}

/* return NULL if error */
nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts)
{
Expand Down Expand Up @@ -496,7 +626,7 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts)

/* get serial number */
if (iSerialNumber) {
ret = (*nut_usb_get_string_simple)(udev,
ret = nut_usb_get_string(udev,
iSerialNumber, string, sizeof(string));
if (ret > 0) {
serialnumber = strdup(str_rtrim(string, ' '));
Expand All @@ -521,7 +651,7 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts)

/* get product name */
if (iProduct) {
ret = (*nut_usb_get_string_simple)(udev,
ret = nut_usb_get_string(udev,
iProduct, string, sizeof(string));
if (ret > 0) {
device_name = strdup(str_rtrim(string, ' '));
Expand All @@ -547,7 +677,7 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts)

/* get vendor name */
if (iManufacturer) {
ret = (*nut_usb_get_string_simple)(udev,
ret = nut_usb_get_string(udev,
iManufacturer, string, sizeof(string));
if (ret > 0) {
vendor_name = strdup(str_rtrim(string, ' '));
Expand Down

0 comments on commit 7cb95f4

Please sign in to comment.