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 8256c95069..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, @@ -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, @@ -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'; diff --git a/m4/ax_realpath_lib.m4 b/m4/ax_realpath_lib.m4 index 274517bfd1..d57de269ff 100644 --- a/m4/ax_realpath_lib.m4 +++ b/m4/ax_realpath_lib.m4 @@ -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" + ]) ]) 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, ' '));