Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port and use a variant of nut_usb_get_string() for nut-scanner #2626

Merged
merged 4 commits into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading