From 9f40a8f1089a576d2719443914c228f7d2e2cce4 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 11 Sep 2024 15:17:51 +0200 Subject: [PATCH 1/4] drivers/usb-common.c: fix C style of comments [#2604] Signed-off-by: Jim Klimov --- drivers/usb-common.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/drivers/usb-common.c b/drivers/usb-common.c index 8256c95069..81074f6fc9 100644 --- a/drivers/usb-common.c +++ b/drivers/usb-common.c @@ -433,7 +433,7 @@ 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; @@ -480,13 +480,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'; From 938e80e2b62ba2d5ab75512b294000f5f0219584 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 11 Sep 2024 15:18:28 +0200 Subject: [PATCH 2/4] tools/nut-scanner/scan_usb.c: port nut_usb_get_string() from usb-common.c [#2604, #2615] Signed-off-by: Jim Klimov --- NEWS.adoc | 4 +- drivers/usb-common.c | 10 ++- tools/nut-scanner/scan_usb.c | 150 ++++++++++++++++++++++++++++++++--- 3 files changed, 151 insertions(+), 13 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index f4d8929bfc..2808324767 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -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 diff --git a/drivers/usb-common.c b/drivers/usb-common.c index 81074f6fc9..42721a6afa 100644 --- a/drivers/usb-common.c +++ b/drivers/usb-common.c @@ -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, @@ -439,7 +442,10 @@ static int nut_usb_get_string_descriptor( 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, diff --git a/tools/nut-scanner/scan_usb.c b/tools/nut-scanner/scan_usb.c index 6750a00074..3642602b2c 100644 --- a/tools/nut-scanner/scan_usb.c +++ b/tools/nut-scanner/scan_usb.c @@ -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 @@ -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" @@ -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"); @@ -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); @@ -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) { @@ -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, ' ')); @@ -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, ' ')); @@ -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, ' ')); From 2532b05b093e1f1a049df23d1de37e86d2027bf3 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 11 Sep 2024 16:47:07 +0200 Subject: [PATCH 3/4] m4/ax_realpath_lib.m4: fix indentation Signed-off-by: Jim Klimov --- m4/ax_realpath_lib.m4 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/m4/ax_realpath_lib.m4 b/m4/ax_realpath_lib.m4 index 274517bfd1..62b0e11216 100644 --- a/m4/ax_realpath_lib.m4 +++ b/m4/ax_realpath_lib.m4 @@ -168,13 +168,12 @@ AC_DEFUN([AX_REALPATH_LIB], 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" + ]) ]) From 9e16b0e2177270bf0b07555ff33315535bb01a04 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Wed, 11 Sep 2024 17:04:48 +0200 Subject: [PATCH 4/4] m4/ax_realpath_lib.m4: recognize "GNU ld script" files which redirect to a real library Signed-off-by: Jim Klimov --- m4/ax_realpath_lib.m4 | 48 ++++++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/m4/ax_realpath_lib.m4 b/m4/ax_realpath_lib.m4 index 62b0e11216..d57de269ff 100644 --- a/m4/ax_realpath_lib.m4 +++ b/m4/ax_realpath_lib.m4 @@ -156,16 +156,44 @@ 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}" ],[