From b7a14b3c8062fcbb0d4fa3c872cfd754ae0f968b Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 17 Sep 2023 22:07:30 +0200 Subject: [PATCH 01/16] m4/nut_check_libregex.m4: move detection from m4/nut_check_libusb.m4; adapt configure.ac and */Makefile.am accordingly Signed-off-by: Jim Klimov --- configure.ac | 11 +++-- drivers/Makefile.am | 1 + m4/nut_check_libregex.m4 | 100 +++++++++++++++++++++++++++++++++++++++ m4/nut_check_libusb.m4 | 7 ++- 4 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 m4/nut_check_libregex.m4 diff --git a/configure.ac b/configure.ac index b2619d2c90..5f22c4d02a 100644 --- a/configure.ac +++ b/configure.ac @@ -193,13 +193,17 @@ if test ! -d "${auglensdir}"; then fi fi -LIBREGEX_LIBS='' +dnl ### NUT_CHECK_LIBREGEX ### Detect below as part of libusb etc. +dnl ### LIBREGEX_LIBS='' dnl Disable Hotplug, DevD and udev support on Windows dnl (useful when cross-compiling) case "${target_os}" in *mingw* ) - dnl TODO: Actually detect it? See also nut_check_libusb.m4 for same. - LIBREGEX_LIBS='-lregex' + dnl TODO: Actually detect it? See also nut_check_libregex.m4 for same. + dnl Here we assumed from practice that it is available... + dnl ### if test x"${LIBREGEX_LIBS}" = x ; then + dnl ### LIBREGEX_LIBS='-lregex' + dnl ### fi hotplugdir='' udevdir='' devddir='' @@ -1522,6 +1526,7 @@ dnl These checks are performed unconditionally, even if the corresponding dnl --with-* options are not given. This is because we cannot predict dnl what will be in the --with-drivers argument. +NUT_CHECK_LIBREGEX NUT_ARG_WITH([usb], [build and install USB drivers, optionally require build with specified version of libusb library and API: (auto|libusb-0.1|libusb-1.0)], [auto]) nut_usb_lib="" NUT_CHECK_LIBUSB diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 3d945abd2f..747a97a9ed 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -124,6 +124,7 @@ upsdrvctl_LDADD = $(LDADD_COMMON) libdummy_upsdrvquery.la # serial drivers: all of them use standard LDADD and CFLAGS al175_SOURCES = al175.c apcsmart_SOURCES = apcsmart.c apcsmart_tabs.c +apcsmart_CFLAGS = $(LIBREGEX_CFLAGS) apcsmart_LDADD = $(LDADD) $(LIBREGEX_LIBS) apcsmart_old_SOURCES = apcsmart-old.c bcmxcp_SOURCES = bcmxcp.c bcmxcp_ser.c diff --git a/m4/nut_check_libregex.m4 b/m4/nut_check_libregex.m4 new file mode 100644 index 0000000000..6a3a4c8f02 --- /dev/null +++ b/m4/nut_check_libregex.m4 @@ -0,0 +1,100 @@ +dnl Check for LIBREGEX (and, if found, fill 'nut_usb_lib' with its +dnl approximate version) and its compiler flags. On success, set +dnl nut_have_libusb="yes" and set LIBREGEX_CFLAGS and LIBREGEX_LIBS. On failure, set +dnl nut_have_libusb="no". This macro can be run multiple times, but will +dnl do the checking only once. + +AC_DEFUN([NUT_CHECK_LIBREGEX], +[ +if test -z "${nut_have_libregex_seen}"; then + nut_have_libregex_seen=yes + NUT_CHECK_PKGCONFIG + + dnl save CFLAGS and LIBS + CFLAGS_ORIG="${CFLAGS}" + LIBS_ORIG="${LIBS}" + CFLAGS="" + LIBS="" + + dnl Actually did not see it in any systems' pkg-config info... + dnl Part of standard footprint? + LIBREGEX_MODULE="" + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [AC_MSG_CHECKING(for libregex version via pkg-config) + LIBREGEX_VERSION="`$PKG_CONFIG --silence-errors --modversion regex 2>/dev/null`" + if test "$?" != "0" -o -z "${LIBREGEX_VERSION}"; then + LIBREGEX_VERSION="`$PKG_CONFIG --silence-errors --modversion libregex 2>/dev/null`" + if test "$?" != "0" -o -z "${LIBREGEX_VERSION}"; then + LIBREGEX_VERSION="none" + else + LIBREGEX_MODULE="libregex" + fi + else + LIBREGEX_MODULE="regex" + fi + AC_MSG_RESULT(${LIBREGEX_VERSION} found) + ], + [LIBREGEX_VERSION="none" + AC_MSG_NOTICE([can not check libregex settings via pkg-config]) + ] + ) + + AS_IF([test x"$LIBREGEX_VERSION" != xnone && test x"$LIBREGEX_MODULE" != x], + [CFLAGS="`$PKG_CONFIG --silence-errors --cflags "${LIBREGEX_MODULE}" 2>/dev/null`" + LIBS="`$PKG_CONFIG --silence-errors --libs "${LIBREGEX_MODULE}" 2>/dev/null`" + REQUIRES="${LIBREGEX_MODULE}" + ], + [CFLAGS="" + LIBS="" + REQUIRES="" + ] + ) + + dnl Check if libregex is usable + AC_LANG_PUSH([C]) + dnl # With USB we can match desired devices by regex + dnl # (and currently have no other use for the library); + dnl # however we may have some general regex helper + dnl # methods built into libcommon (may become useful + dnl # elsewhere) - so need to know if we may and should. + dnl # Maybe already involved in NUT for Windows builds... + nut_have_regex=no + AC_CHECK_HEADER([regex.h], + [nut_have_regex=yes + AC_DEFINE([HAVE_REGEX_H], [1], + [Define to 1 if you have .])]) + AC_CHECK_DECLS([regexec, regcomp], [nut_have_regex=yes], [], +[#ifdef HAVE_REGEX_H +# include +#endif +]) + + AC_SEARCH_LIBS([regcomp, regexec], [], [nut_have_regex=yes], [ + AS_IF([test x"$LIBS" = x], [ + AC_SEARCH_LIBS([regcomp, regexec], [regex], [ + LIBS="-lregex" + nut_have_regex=yes + ]) + ]) + ]) + + AS_IF([test x"${nut_have_regex}" = xyes], [ + LIBREGEX_CFLAGS="${CFLAGS}" + LIBREGEX_LIBS="${LIBS}" + AC_DEFINE(HAVE_LIBREGEX, 1, + [Define to 1 for build where we can support general regex matching.]) + ], [ + LIBREGEX_CFLAGS="" + LIBREGEX_LIBS="" + AC_DEFINE(HAVE_LIBREGEX, 0, + [Define to 1 for build where we can support general regex matching.]) + ]) + AM_CONDITIONAL(HAVE_LIBREGEX, test x"${nut_have_regex}" = xyes) + + AC_LANG_POP([C]) + + dnl restore original CFLAGS and LIBS + CFLAGS="${CFLAGS_ORIG}" + LIBS="${LIBS_ORIG}" +fi +]) diff --git a/m4/nut_check_libusb.m4 b/m4/nut_check_libusb.m4 index 5b042b0790..186e7c6e2f 100644 --- a/m4/nut_check_libusb.m4 +++ b/m4/nut_check_libusb.m4 @@ -14,6 +14,9 @@ if test -z "${nut_have_libusb_seen}"; then nut_have_libusb_seen=yes NUT_CHECK_PKGCONFIG + dnl Our USB matching relies on regex abilities + NUT_CHECK_LIBREGEX + dnl save CFLAGS and LIBS CFLAGS_ORIG="${CFLAGS}" LIBS_ORIG="${LIBS}" @@ -325,10 +328,6 @@ if test -z "${nut_have_libusb_seen}"; then dnl DEFINE WITH_USB_BUSPORT dnl #endif AC_CHECK_FUNCS(libusb_get_port_number, [nut_with_usb_busport=yes]) - - dnl # With USB we can match desired devices by regex; - dnl # and currently have no other use for the library: - AC_SEARCH_LIBS(regcomp, regex) ]) AC_LANG_POP([C]) From 0897c9f2d9b323864e978fd837a54f724e4f1dbd Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 17 Sep 2023 22:08:53 +0200 Subject: [PATCH 02/16] common.{c,h}: move here general REGEX helper methods from drivers/usb-common.{c,h} [#1369] Signed-off-by: Jim Klimov --- common/Makefile.am | 8 +++ common/common.c | 106 ++++++++++++++++++++++++++++++++++++ drivers/usb-common.c | 126 ------------------------------------------- include/common.h | 40 ++++++++++++++ 4 files changed, 154 insertions(+), 126 deletions(-) diff --git a/common/Makefile.am b/common/Makefile.am index e30efd6428..a6914d6698 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -76,6 +76,14 @@ libcommonclient_la_LIBADD = libparseconf.la @LTLIBOBJS@ @NETLIBS@ libcommon_la_CFLAGS = $(AM_CFLAGS) libcommonclient_la_CFLAGS = $(AM_CFLAGS) +if HAVE_LIBREGEX + libcommon_la_CFLAGS += $(LIBREGEX_CFLAGS) + libcommon_la_LIBADD += $(LIBREGEX_LIBS) + + libcommonclient_la_CFLAGS += $(LIBREGEX_CFLAGS) + libcommonclient_la_LIBADD += $(LIBREGEX_LIBS) +endif + # Did the user request, and build env support, tighter integration with # libsystemd methods such as sd_notify()? if WITH_LIBSYSTEMD diff --git a/common/common.c b/common/common.c index d91a9d368f..16228665a5 100644 --- a/common/common.c +++ b/common/common.c @@ -2126,3 +2126,109 @@ void set_close_on_exec(int fd) { # endif #endif } + +/**** REGEX helper methods ****/ + +int strcmp_null(const char *s1, const char *s2) +{ + if (s1 == NULL && s2 == NULL) { + return 0; + } + + if (s1 == NULL) { + return -1; + } + + if (s2 == NULL) { + return 1; + } + + return strcmp(s1, s2); +} + +#if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) +int compile_regex(regex_t **compiled, const char *regex, const int cflags) +{ + int r; + regex_t *preg; + + if (regex == NULL) { + *compiled = NULL; + return 0; + } + + preg = malloc(sizeof(*preg)); + if (!preg) { + return -1; + } + + r = regcomp(preg, regex, cflags); + if (r) { + free(preg); + return -2; + } + + *compiled = preg; + + return 0; +} + +int match_regex(const regex_t *preg, const char *str) +{ + int r; + size_t len = 0; + char *string; + regmatch_t match; + + if (!preg) { + return 1; + } + + if (!str) { + string = xstrdup(""); + } else { + /* skip leading whitespace */ + for (len = 0; len < strlen(str); len++) { + + if (!strchr(" \t\n", str[len])) { + break; + } + } + + string = xstrdup(str+len); + + /* skip trailing whitespace */ + for (len = strlen(string); len > 0; len--) { + + if (!strchr(" \t\n", string[len-1])) { + break; + } + } + + string[len] = '\0'; + } + + /* test the regular expression */ + r = regexec(preg, string, 1, &match, 0); + free(string); + if (r) { + return 0; + } + + /* check that the match is the entire string */ + if ((match.rm_so != 0) || (match.rm_eo != (int)len)) { + return 0; + } + + return 1; +} + +int match_regex_hex(const regex_t *preg, const int n) +{ + char buf[10]; + + snprintf(buf, sizeof(buf), "%04x", n); + + return match_regex(preg, buf); +} +#endif /* HAVE_LIBREGEX */ diff --git a/drivers/usb-common.c b/drivers/usb-common.c index d3bc96748b..ccdb104a59 100644 --- a/drivers/usb-common.c +++ b/drivers/usb-common.c @@ -54,27 +54,6 @@ int is_usb_device_supported(usb_device_id_t *usb_device_id_list, USBDevice_t *de /* ---------------------------------------------------------------------- */ /* matchers */ -/* helper function: version of strcmp that tolerates NULL - * pointers. NULL is considered to come before all other strings - * alphabetically. - */ -static int strcmp_null(char *s1, char *s2) -{ - if (s1 == NULL && s2 == NULL) { - return 0; - } - - if (s1 == NULL) { - return -1; - } - - if (s2 == NULL) { - return 1; - } - - return strcmp(s1, s2); -} - /* private callback function for exact matches */ static int match_function_exact(USBDevice_t *hd, void *privdata) @@ -202,111 +181,6 @@ void USBFreeExactMatcher(USBDeviceMatcher_t *matcher) free(matcher); } -/* Private function for compiling a regular expression. On success, - * store the compiled regular expression (or NULL) in *compiled, and - * return 0. On error with errno set, return -1. If the supplied - * regular expression is unparseable, return -2 (an error message can - * then be retrieved with regerror(3)). Note that *compiled will be an - * allocated value, and must be freed with regfree(), then free(), see - * regex(3). As a special case, if regex==NULL, then set - * *compiled=NULL (regular expression NULL is intended to match - * anything). - */ -static int compile_regex(regex_t **compiled, char *regex, int cflags) -{ - int r; - regex_t *preg; - - if (regex == NULL) { - *compiled = NULL; - return 0; - } - - preg = malloc(sizeof(*preg)); - if (!preg) { - return -1; - } - - r = regcomp(preg, regex, cflags); - if (r) { - free(preg); - return -2; - } - - *compiled = preg; - - return 0; -} - -/* Private function for regular expression matching. Check if the - * entire string str (minus any initial and trailing whitespace) - * matches the compiled regular expression preg. Return 1 if it - * matches, 0 if not. Return -1 on error with errno set. Special - * cases: if preg==NULL, it matches everything (no contraint). If - * str==NULL, then it is treated as "". - */ -static int match_regex(regex_t *preg, char *str) -{ - int r; - size_t len = 0; - char *string; - regmatch_t match; - - if (!preg) { - return 1; - } - - if (!str) { - string = xstrdup(""); - } else { - /* skip leading whitespace */ - for (len = 0; len < strlen(str); len++) { - - if (!strchr(" \t\n", str[len])) { - break; - } - } - - string = xstrdup(str+len); - - /* skip trailing whitespace */ - for (len = strlen(string); len > 0; len--) { - - if (!strchr(" \t\n", string[len-1])) { - break; - } - } - - string[len] = '\0'; - } - - /* test the regular expression */ - r = regexec(preg, string, 1, &match, 0); - free(string); - if (r) { - return 0; - } - - /* check that the match is the entire string */ - if ((match.rm_so != 0) || (match.rm_eo != (int)len)) { - return 0; - } - - return 1; -} - -/* Private function, similar to match_regex, but the argument being - * matched is a (hexadecimal) number, rather than a string. It is - * converted to a 4-digit hexadecimal string. */ -static int match_regex_hex(regex_t *preg, int n) -{ - char buf[10]; - - snprintf(buf, sizeof(buf), "%04x", n); - - return match_regex(preg, buf); -} - /* private data type: hold a set of compiled regular expressions. */ typedef struct regex_matcher_data_s { regex_t *regex[USBMATCHER_REGEXP_ARRAY_LIMIT]; diff --git a/include/common.h b/include/common.h index 3d0052f8fd..31a10a8fc4 100644 --- a/include/common.h +++ b/include/common.h @@ -85,6 +85,10 @@ #include "proto.h" #include "str.h" +#if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) +# include +#endif + #ifdef __cplusplus /* *INDENT-OFF* */ extern "C" { @@ -338,6 +342,42 @@ void *xcalloc(size_t number, size_t size); void *xrealloc(void *ptr, size_t size); char *xstrdup(const char *string); +/**** REGEX helper methods ****/ + +/* helper function: version of strcmp that tolerates NULL + * pointers. NULL is considered to come before all other strings + * alphabetically. + */ +int strcmp_null(const char *s1, const char *s2); + +#if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) +/* Helper function for compiling a regular expression. On success, + * store the compiled regular expression (or NULL) in *compiled, and + * return 0. On error with errno set, return -1. If the supplied + * regular expression is unparseable, return -2 (an error message can + * then be retrieved with regerror(3)). Note that *compiled will be an + * allocated value, and must be freed with regfree(), then free(), see + * regex(3). As a special case, if regex==NULL, then set + * *compiled=NULL (regular expression NULL is intended to match + * anything). + */ +int compile_regex(regex_t **compiled, const char *regex, const int cflags); + +/* Helper function for regular expression matching. Check if the + * entire string str (minus any initial and trailing whitespace) + * matches the compiled regular expression preg. Return 1 if it + * matches, 0 if not. Return -1 on error with errno set. Special + * cases: if preg==NULL, it matches everything (no contraint). If + * str==NULL, then it is treated as "". + */ +int match_regex(const regex_t *preg, const char *str); + +/* Helper function, similar to match_regex, but the argument being + * matched is a (hexadecimal) number, rather than a string. It is + * converted to a 4-digit hexadecimal string. */ +int match_regex_hex(const regex_t *preg, const int n); +#endif /* HAVE_LIBREGEX */ + /* Note: different method signatures instead of TYPE_FD_SER due to "const" */ #ifndef WIN32 ssize_t select_read(const int fd, void *buf, const size_t buflen, const time_t d_sec, const suseconds_t d_usec); From e029089ecf784fbb8012d18ed2e3cc4d3fb700ac Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 17 Sep 2023 22:38:17 +0200 Subject: [PATCH 03/16] clients/Makefile.am: build clients against libcommonclient.la (fewer link and run-time deps pulled), not libcommon.la Signed-off-by: Jim Klimov --- clients/Makefile.am | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clients/Makefile.am b/clients/Makefile.am index 8a2dd61991..d38a6a5bf2 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -12,8 +12,9 @@ $(top_builddir)/common/libcommonclient.la \ $(top_builddir)/common/libparseconf.la: dummy @cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F) -# by default, link programs in this directory with libcommon.a -LDADD = $(top_builddir)/common/libcommon.la libupsclient.la $(NETLIBS) +# by default, link programs in this directory with +# the more compact libcommonclient.a bundle +LDADD = $(top_builddir)/common/libcommonclient.la libupsclient.la $(NETLIBS) if WITH_SSL LDADD += $(LIBSSL_LIBS) endif From 91eda53a4f466e1df9090e9e3011a5a5138f3488 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 17 Sep 2023 22:11:20 +0200 Subject: [PATCH 04/16] drivers/usbhid-ups.c: implement match_function_subdriver_name() for "subdriver" matching option; update docs about it [#1369] Signed-off-by: Jim Klimov --- NEWS.adoc | 5 +++ docs/man/usbhid-ups.txt | 12 ++++++ drivers/usbhid-ups.c | 87 +++++++++++++++++++++++++++++++++++++---- 3 files changed, 97 insertions(+), 7 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 18609d78d9..b9e7b4495c 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -152,6 +152,11 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. * extended default ranges for max battery voltage when guessing [#1279] - usbhid-ups updates: + * added support for `subdriver` configuration option, to select the + USB HID subdriver for the device manually where automatic match + does not suffice (e.g. new devices for which no `vendorid`/`productid` + pair was built into any driver, or for different-capability devices + with same interface chips, notably "phoenixtec/liebert" and "mge") [#1369] * cps-hid subdriver now applies same report descriptor fixing logic to devices with ProductID 0x0601 as done earlier for 0x0501, to get the correct output voltage data [#1497] diff --git a/docs/man/usbhid-ups.txt b/docs/man/usbhid-ups.txt index 9b86737991..098844abaa 100644 --- a/docs/man/usbhid-ups.txt +++ b/docs/man/usbhid-ups.txt @@ -54,6 +54,18 @@ This driver also supports the following optional settings: include::nut_usb_addvars.txt[] +*subdriver*='regex':: +Select the USB HID subdriver for the device manually, where automatic match +by device attributes alone does not suffice (e.g. new devices for which no +`vendorid`/`productid` pair was built into any driver -- but common USB HID +support is anticipated, or for different-capability devices with same +interface chips, notably "phoenixtec/liebert" and "mge"). ++ +NOTE: this option first checks for exact matches to subdriver identification +strings, such as `"TrippLite HID 0.85"` (which are prone to bit-rot), and if +there was no exact match -- retries with a case-insensitive extended regular +expression. + *offdelay*='num':: Set the timer before the UPS is turned off after the kill power command is sent (via the *-k* switch). diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 36748c66fb..beb32d6e45 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -28,16 +28,17 @@ */ #define DRIVER_NAME "Generic HID driver" -#define DRIVER_VERSION "0.51" +#define DRIVER_VERSION "0.52" #define HU_VAR_WAITBEFORERECONNECT "waitbeforereconnect" -#include "main.h" +#include "main.h" /* Must be first, includes "config.h" */ #include "nut_stdint.h" #include "libhid.h" #include "usbhid-ups.h" #include "hidparser.h" #include "hidtypes.h" +#include "common.h" #ifdef WIN32 #include "wincompat.h" #endif @@ -531,6 +532,67 @@ info_lkp_t kelvin_celsius_conversion[] = { { 0, NULL, kelvin_celsius_conversion_fun, NULL } }; +static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { + char *subdrv = getval("subdriver"); + + /* Pick up the subdriver name if set explicitly */ + if (subdrv) { + int res, i, flag_HAVE_LIBREGEX = 0; +#if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) + regex_t *regex_ptr = NULL; + flag_HAVE_LIBREGEX = 1; +#endif + + upsdebugx(2, + "%s: matching a subdriver by explicit " + "name%s: '%s'...", + __func__, + flag_HAVE_LIBREGEX ? "/regex" : "", + subdrv); + + /* First try exact match for strings like "TrippLite HID 0.85" + * Not likely to hit (due to versions etc.), but worth a try :) + */ + for (i=0; subdriver_list[i] != NULL; i++) { + if (strcmp_null(subdrv, subdriver_list[i]->name) == 0) { + return subdriver_list[i]; + } + } + +#if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) + /* Then try a case-insensitive regex like "tripplite" */ + res = compile_regex(®ex_ptr, subdrv, REG_ICASE | REG_EXTENDED); + if (res == 0 && regex_ptr != NULL) { + for (i=0; subdriver_list[i] != NULL; i++) { + res = match_regex(regex_ptr, subdriver_list[i]->name); + if (res == 1) { + free(regex_ptr); + return subdriver_list[i]; + } + } + } + + if (regex_ptr) + free(regex_ptr); +#endif /* HAVE_LIBREGEX */ + + if (fatal_mismatch) { + fatalx(EXIT_FAILURE, + "Configuration requested subdriver '%s' but none matched", + subdrv); + } else { + upslogx(LOG_WARNING, + "Configuration requested subdriver '%s' but none matched; " + "will try USB matching by other fields", + subdrv); + } + } + + /* No match (and non-fatal mismatch mode), or no + * "subdriver" was specified in configuration */ + return NULL; +} + /*! * subdriver matcher: only useful for USB mode * as SHUT is only supported by MGE UPS SYSTEMS units @@ -538,9 +600,14 @@ info_lkp_t kelvin_celsius_conversion[] = { #if !((defined SHUT_MODE) && SHUT_MODE) static int match_function_subdriver(HIDDevice_t *d, void *privdata) { - int i; + int i; NUT_UNUSED_VARIABLE(privdata); + if (match_function_subdriver_name(1)) { + /* This driver can handle this device. Guessing so... */ + return 1; + } + upsdebugx(2, "%s (non-SHUT mode): matching a device...", __func__); for (i=0; subdriver_list[i] != NULL; i++) { @@ -809,6 +876,8 @@ void upsdrv_makevartable(void) "Set to disable fix-ups for broken USB encoding, etc. which we apply by default on certain vendors/products"); #if !((defined SHUT_MODE) && SHUT_MODE) + addvar(VAR_VALUE, "subdriver", "Explicit USB HID subdriver selection"); + /* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */ nut_usb_addvars(); @@ -1294,13 +1363,17 @@ static int callback( } /* select the subdriver for this device */ - for (i=0; subdriver_list[i] != NULL; i++) { - if (subdriver_list[i]->claim(hd)) { - break; + subdriver = match_function_subdriver_name(0); + if (!subdriver) { + for (i=0; subdriver_list[i] != NULL; i++) { + if (subdriver_list[i]->claim(hd)) { + break; + } } + + subdriver = subdriver_list[i]; } - subdriver = subdriver_list[i]; if (!subdriver) { upsdebugx(1, "Manufacturer not supported!"); return 0; From 9289fbc751439105b42e815b78f98261be0fc38d Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 17 Sep 2023 23:27:28 +0200 Subject: [PATCH 05/16] drivers/usbhid-ups.c: match_function_subdriver_name(): automate use of regex "somename.*" [#1369] Signed-off-by: Jim Klimov --- drivers/usbhid-ups.c | 37 +++++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index beb32d6e45..8ec5bfa15d 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -538,6 +538,7 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { /* Pick up the subdriver name if set explicitly */ if (subdrv) { int res, i, flag_HAVE_LIBREGEX = 0; + size_t len; #if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) regex_t *regex_ptr = NULL; flag_HAVE_LIBREGEX = 1; @@ -560,7 +561,9 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { } #if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) - /* Then try a case-insensitive regex like "tripplite" */ + /* Then try a case-insensitive regex like "tripplite.*" + * if so provided by caller */ + upsdebugx(2, "%s: retry matching by regex 'as is'", __func__); res = compile_regex(®ex_ptr, subdrv, REG_ICASE | REG_EXTENDED); if (res == 0 && regex_ptr != NULL) { for (i=0; subdriver_list[i] != NULL; i++) { @@ -572,8 +575,38 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { } } - if (regex_ptr) + if (regex_ptr) { free(regex_ptr); + regex_ptr = NULL; + } + + /* Then try a case-insensitive regex like "tripplite.*" + * with automatically added ".*" */ + len = strlen(subdrv); + if ( + (len < 3 || (subdrv[len-2] != '.' && subdrv[len-1] != '*')) + && len < (LARGEBUF-3) + ) { + char buf[LARGEBUF]; + upsdebugx(2, "%s: retry matching by regex with added '.*'", __func__); + snprintf(buf, sizeof(buf), "%s.*", subdrv); + res = compile_regex(®ex_ptr, buf, REG_ICASE | REG_EXTENDED); + if (res == 0 && regex_ptr != NULL) { + for (i=0; subdriver_list[i] != NULL; i++) { + res = match_regex(regex_ptr, subdriver_list[i]->name); + if (res == 1) { + free(regex_ptr); + return subdriver_list[i]; + } + } + } + + if (regex_ptr) { + free(regex_ptr); + regex_ptr = NULL; + } + } + #endif /* HAVE_LIBREGEX */ if (fatal_mismatch) { From e1b0320413949bf556bf4effa5ec0dd8be0206b8 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 17 Sep 2023 23:30:35 +0200 Subject: [PATCH 06/16] drivers/usbhid-ups.c: match_function_subdriver_name(): report the name of found USB HID subdriver [#1369] Signed-off-by: Jim Klimov --- drivers/usbhid-ups.c | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 8ec5bfa15d..17600bb8eb 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -534,6 +534,7 @@ info_lkp_t kelvin_celsius_conversion[] = { static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { char *subdrv = getval("subdriver"); + subdriver_t *info = NULL; /* Pick up the subdriver name if set explicitly */ if (subdrv) { @@ -556,7 +557,8 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { */ for (i=0; subdriver_list[i] != NULL; i++) { if (strcmp_null(subdrv, subdriver_list[i]->name) == 0) { - return subdriver_list[i]; + info = subdriver_list[i]; + goto found; } } @@ -570,7 +572,8 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { res = match_regex(regex_ptr, subdriver_list[i]->name); if (res == 1) { free(regex_ptr); - return subdriver_list[i]; + info = subdriver_list[i]; + goto found; } } } @@ -596,7 +599,8 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { res = match_regex(regex_ptr, subdriver_list[i]->name); if (res == 1) { free(regex_ptr); - return subdriver_list[i]; + info = subdriver_list[i]; + goto found; } } } @@ -624,6 +628,10 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { /* No match (and non-fatal mismatch mode), or no * "subdriver" was specified in configuration */ return NULL; + +found: + upsdebugx(2, "%s: found a match: %s", __func__, info->name); + return info; } /*! From 44b64e1d0a587693ff2e8b03fc0f18db8fb3e18a Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Sun, 17 Sep 2023 23:52:06 +0200 Subject: [PATCH 07/16] drivers/usbhid-ups.c: match_function_subdriver_name(): require (or recommend) vendorid/productid [#1369] Signed-off-by: Jim Klimov --- drivers/usbhid-ups.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 17600bb8eb..9bb9119942 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -631,6 +631,20 @@ static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { found: upsdebugx(2, "%s: found a match: %s", __func__, info->name); + if (!getval("vendorid") || !getval("productid")) { + if (fatal_mismatch) { + fatalx(EXIT_FAILURE, + "When specifying a subdriver, " + "'vendorid' and 'productid' " + "are mandatory."); + } else { + upslogx(LOG_WARNING, + "When specifying a subdriver, " + "'vendorid' and 'productid' " + "are highly recommended."); + } + } + return info; } From 98ecf015d8d8354fc8079ee186905e21462fd362 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 12:54:25 +0000 Subject: [PATCH 08/16] configure.ac: hush down some more clang "-Wextra" or "-Weverything" warnings that we can not really address [#2055, #823] Signed-off-by: Jim Klimov --- configure.ac | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/configure.ac b/configure.ac index 5f22c4d02a..f7353f286d 100644 --- a/configure.ac +++ b/configure.ac @@ -4073,6 +4073,14 @@ dnl # -Wno-padded -- NSPR and NSS headers get to issue lots of that dnl # -Wno-c++98-compat-pedantic -Wno-c++98-compat -- our C++ code uses nullptr dnl # as requested by newer linters, and C++98 does not. We require C++11 dnl # or newer anyway, and skip building C++ library and test otherwise. +dnl # -Wno-fuse-ld-path -- not much in our control what recipes the autotools +dnl # on the build host generate... this tries to avoid failures due to: +dnl # clang-13: error: '-fuse-ld=' taking a path is deprecated. +dnl # Use '--ld-path=' instead [-Werror,-Wfuse-ld-path] +dnl # -Wno-unsafe-buffer-usage -- clang-16 introduced a check too smart for +dnl # its own good. It detects use of pointer aritmetics as arrays are +dnl # walked, which is indeed potentially dangerous. And also is nearly +dnl # unavoidable in C (at least not without major rewrites of the world). dnl ### Special exclusion picks for clang-medium (same as hard, plus...): dnl # -Wno-float-conversion -Wno-double-promotion -Wno-implicit-float-conversion dnl # -- reduce noise due to floating-point literals like "3.14" being a C @@ -4103,12 +4111,12 @@ AS_CASE(["${nut_enable_warnings}"], CXXFLAGS="${CXXFLAGS} -Wall" ], [clang-hard], [ - CFLAGS="${CFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -pedantic" - CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat" + CFLAGS="${CFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -pedantic -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" + CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" ], [clang-medium], [ - CFLAGS="${CFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -pedantic -Wno-float-conversion -Wno-double-promotion -Wno-implicit-float-conversion -Wno-conversion -Wno-incompatible-pointer-types-discards-qualifiers" - CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat" + CFLAGS="${CFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -pedantic -Wno-fuse-ld-path -Wno-unsafe-buffer-usage -Wno-float-conversion -Wno-double-promotion -Wno-implicit-float-conversion -Wno-conversion -Wno-incompatible-pointer-types-discards-qualifiers" + CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" ], [clang-minimal], [ CFLAGS="${CFLAGS} -ferror-limit=0 -Wall -Wextra -Wno-documentation" From 697ca394114794654bcf2134aed2aba6410fa64a Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 13:23:12 +0000 Subject: [PATCH 09/16] tools/nut-scanner/nutscan-display.c: report trailing blanks in serial numbers Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-display.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tools/nut-scanner/nutscan-display.c b/tools/nut-scanner/nutscan-display.c index 9cfa2716ec..c031889a82 100644 --- a/tools/nut-scanner/nutscan-display.c +++ b/tools/nut-scanner/nutscan-display.c @@ -294,7 +294,7 @@ void nutscan_display_sanity_check_serial(nutscan_device_t * device) goto exit; } - /* Now look for red flags in the map */ + /* Now look for red flags in the map (key=sernum, val=device(s)) */ /* FIXME: Weed out special chars to avoid breaking comment-line markup? * Thinking of ASCII control codes < 32 including CR/LF, and codes 128+... */ for (i = 0; i < count; i++) { @@ -308,6 +308,13 @@ void nutscan_display_sanity_check_serial(nutscan_device_t * device) continue; } + j = strlen(entry->key); + if (j > 0 && (entry->key[j-1] == '\t' || entry->key[j-1] == ' ')) { + printf("\n# WARNING: trailing blank space in \"serial\" " + "value \"%s\" reported in device configuration(s): %s", + entry->key, entry->val); + } + /* All chars in "serial" are same (zero, space, etc.) */ for (j = 0; entry->key[j] != '\0' && entry->key[j] == entry->key[0]; j++); if (j > 0 && entry->key[j] == '\0') { From fb6dcec2fc1a7b41d7bf3b4035e5b887252fe941 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 13:23:49 +0000 Subject: [PATCH 10/16] tools/nut-scanner/nutscan-display.c: do not drop out after the first (non-NULL) report, let all complaints be known Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-display.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/tools/nut-scanner/nutscan-display.c b/tools/nut-scanner/nutscan-display.c index c031889a82..5c0f92f5b3 100644 --- a/tools/nut-scanner/nutscan-display.c +++ b/tools/nut-scanner/nutscan-display.c @@ -322,7 +322,6 @@ void nutscan_display_sanity_check_serial(nutscan_device_t * device) "with %" PRIuSIZE " copies of '%c' (0x%02X) " "reported in some devices: %s\n", j, entry->key[0], entry->key[0], entry->val); - continue; } /* Duplicates (maybe same device, maybe not) */ @@ -334,7 +333,6 @@ void nutscan_display_sanity_check_serial(nutscan_device_t * device) "likely a vendor bug if reported by same driver " "for many devices): %s\n", entry->key, entry->val); - continue; } } From 68ef74fd1a7bbb116dfecd5cf5e8b10f8ad500dc Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 13:24:21 +0000 Subject: [PATCH 11/16] tools/nut-scanner/nutscan-display.c: fix report for "same serial in different devices" Signed-off-by: Jim Klimov --- tools/nut-scanner/nutscan-display.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/nut-scanner/nutscan-display.c b/tools/nut-scanner/nutscan-display.c index 5c0f92f5b3..6539c166a4 100644 --- a/tools/nut-scanner/nutscan-display.c +++ b/tools/nut-scanner/nutscan-display.c @@ -324,9 +324,9 @@ void nutscan_display_sanity_check_serial(nutscan_device_t * device) j, entry->key[0], entry->key[0], entry->val); } - /* Duplicates (maybe same device, maybe not) */ + /* Duplicates (maybe same device, maybe not) - see if val has a ',' */ for (j = 0; entry->val[j] != '\0' && entry->val[j] != ','; j++); - if (j > 0 && entry->key[j] != '\0') { + if (j > 0 && entry->val[j] != '\0') { printf("\n# WARNING: same \"serial\" value \"%s\" " "reported in several device configurations " "(maybe okay if multiple drivers for same device, " From 5cb6049e73792f38c912abdf83dcad0445b3e375 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 17:20:51 +0200 Subject: [PATCH 12/16] Update Makefile.am Steamline use of LIBREGEX_CFLAGS via AM_CFLAGS, same as others. Signed-off-by: Jim Klimov --- drivers/Makefile.am | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 747a97a9ed..1a576f944b 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -38,6 +38,9 @@ endif if WITH_MODBUS AM_CFLAGS += $(LIBMODBUS_CFLAGS) endif +if HAVE_LIBREGEX + AM_CFLAGS += $(LIBREGEX_CFLAGS) +endif NUTSW_DRIVERLIST = dummy-ups clone clone-outlet apcupsd-ups skel SERIAL_DRIVERLIST = al175 bcmxcp belkin belkinunv bestfcom \ @@ -124,7 +127,6 @@ upsdrvctl_LDADD = $(LDADD_COMMON) libdummy_upsdrvquery.la # serial drivers: all of them use standard LDADD and CFLAGS al175_SOURCES = al175.c apcsmart_SOURCES = apcsmart.c apcsmart_tabs.c -apcsmart_CFLAGS = $(LIBREGEX_CFLAGS) apcsmart_LDADD = $(LDADD) $(LIBREGEX_LIBS) apcsmart_old_SOURCES = apcsmart-old.c bcmxcp_SOURCES = bcmxcp.c bcmxcp_ser.c From 28289ab8eab57ff95559d7ef027bf166af879653 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 19:20:18 +0000 Subject: [PATCH 13/16] m4/nut_check_libregex.m4: avoid using cached reply for the absent library name, when checking for -lregex Signed-off-by: Jim Klimov --- m4/nut_check_libregex.m4 | 2 ++ 1 file changed, 2 insertions(+) diff --git a/m4/nut_check_libregex.m4 b/m4/nut_check_libregex.m4 index 6a3a4c8f02..86c2bc1552 100644 --- a/m4/nut_check_libregex.m4 +++ b/m4/nut_check_libregex.m4 @@ -71,6 +71,8 @@ if test -z "${nut_have_libregex_seen}"; then AC_SEARCH_LIBS([regcomp, regexec], [], [nut_have_regex=yes], [ AS_IF([test x"$LIBS" = x], [ + dnl Avoid using cached reply for the absent library name + unset ac_cv_search_regcomp__regexec || true AC_SEARCH_LIBS([regcomp, regexec], [regex], [ LIBS="-lregex" nut_have_regex=yes From 93113806905d096f6affd88da8d49244ded50a30 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 22:07:22 +0200 Subject: [PATCH 14/16] docs/man/nutdrv_qx.txt, docs/man/usbhid-ups.txt: update man page notes about subdriver settings [#1369] Signed-off-by: Jim Klimov --- docs/man/nutdrv_qx.txt | 7 ++++++- docs/man/usbhid-ups.txt | 3 +++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/man/nutdrv_qx.txt b/docs/man/nutdrv_qx.txt index 71b11f1cd3..83f2a136d3 100644 --- a/docs/man/nutdrv_qx.txt +++ b/docs/man/nutdrv_qx.txt @@ -348,7 +348,12 @@ include::nut_usb_addvars.txt[] *subdriver =* 'string':: Select a serial-over-USB subdriver to use. You have a choice between *cypress*, *fabula*, *fuji*, *hunnox*, *ippon*, *krauler*, *phoenix*, *phoenixtec*, *sgs*, *snr*, *armac* and *ablerex*. -When using this option, it is mandatory to also specify the *vendorid* and *productid*. ++ +Run the driver program with the `--help` option to see the exact list of +`subdriver` values it would currently recognize. ++ +NOTE: When using this option, it is mandatory to also specify the *vendorid* +and *productid* matching parameters. *langid_fix =* 'value':: Apply the language ID workaround to the *krauler* subdriver. diff --git a/docs/man/usbhid-ups.txt b/docs/man/usbhid-ups.txt index 098844abaa..ac7c4aa43c 100644 --- a/docs/man/usbhid-ups.txt +++ b/docs/man/usbhid-ups.txt @@ -65,6 +65,9 @@ NOTE: this option first checks for exact matches to subdriver identification strings, such as `"TrippLite HID 0.85"` (which are prone to bit-rot), and if there was no exact match -- retries with a case-insensitive extended regular expression. ++ +NOTE: When using this option, it is mandatory to also specify the *vendorid* +and *productid* matching parameters. *offdelay*='num':: Set the timer before the UPS is turned off after the kill power command is From 166b762aa173844a87604dea20e18f485eb7b33c Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 22:09:23 +0200 Subject: [PATCH 15/16] drivers/usbhid-ups.c, docs/man/usbhid-ups.txt: list available "subdriver" setting values in command-line usage request (--help) [#1369] Signed-off-by: Jim Klimov --- docs/man/usbhid-ups.txt | 3 +++ drivers/usbhid-ups.c | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/docs/man/usbhid-ups.txt b/docs/man/usbhid-ups.txt index ac7c4aa43c..0a916fbc4f 100644 --- a/docs/man/usbhid-ups.txt +++ b/docs/man/usbhid-ups.txt @@ -61,6 +61,9 @@ by device attributes alone does not suffice (e.g. new devices for which no support is anticipated, or for different-capability devices with same interface chips, notably "phoenixtec/liebert" and "mge"). + +Run the driver program with the `--help` option to see the exact list of +`subdriver` values it would currently recognize. ++ NOTE: this option first checks for exact matches to subdriver identification strings, such as `"TrippLite HID 0.85"` (which are prone to bit-rot), and if there was no exact match -- retries with a case-insensitive extended regular diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 9bb9119942..800abe3bbe 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -893,7 +893,19 @@ void upsdrv_shutdown(void) void upsdrv_help(void) { - /* FIXME: to be completed */ + size_t i; + printf("\nAcceptable values for 'subdriver' via -x or ups.conf " + "in this driver (exact names here, case-insensitive " + "sub-strings may be used, as well as regular expressions): "); + + for (i = 0; subdriver_list[i] != NULL; i++) { + if (i>0) + printf(", "); + printf("\"%s\"", subdriver_list[i]->name); + } + printf("\n\n"); + + printf("Read The Fine Manual ('man 8 usbhid-ups')\n"); } void upsdrv_makevartable(void) From e8e7dc5e641a31298101a89dc9dd85b002001e32 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 18 Sep 2023 22:09:59 +0200 Subject: [PATCH 16/16] drivers/nutdrv_qx.c, docs/man/nutdrv_qx.txt: list available "protocol" setting values in command-line usage request (--help) [#1369] Signed-off-by: Jim Klimov --- docs/man/nutdrv_qx.txt | 3 +++ drivers/nutdrv_qx.c | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 34 insertions(+), 5 deletions(-) diff --git a/docs/man/nutdrv_qx.txt b/docs/man/nutdrv_qx.txt index 83f2a136d3..0298e03434 100644 --- a/docs/man/nutdrv_qx.txt +++ b/docs/man/nutdrv_qx.txt @@ -64,6 +64,9 @@ If you set stayoff in linkman:ups.conf[5] when FSD arises the UPS will call a *s Skip autodetection of the protocol to use and only use the one specified. Supported values: 'bestups', 'hunnox', 'masterguard', 'mecer', 'megatec', 'megatec/old', 'mustek', 'q1', 'voltronic', 'voltronic-qs', 'voltronic-qs-hex' and 'zinto'. + +Run the driver program with the `--help` option to see the exact list of +`protocol` values it would currently recognize. ++ Note that if you end up using the 'q1' protocol, you may want to give a try to the 'mecer', 'megatec' and 'zinto' ones setting the <> (only one, or both). *pollfreq =* 'num':: diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index 056130ef50..8f5b01d222 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -39,6 +39,7 @@ */ #include "config.h" +#include #include "main.h" #include "attribute.h" #include "nut_float.h" @@ -2771,20 +2772,45 @@ void upsdrv_shutdown(void) void upsdrv_help(void) { -#ifdef QX_USB - #ifndef TESTING +#ifndef TESTING size_t i; +# ifdef QX_USB + /* Subdrivers have special SOMETHING_command() handling and + * are listed in usbsubdriver[] array (just above in this + * source file). + */ printf("\nAcceptable values for 'subdriver' via -x or ups.conf in this driver: "); - for (i = 0; usbsubdriver[i].name != NULL; i++) { if (i>0) printf(", "); printf("%s", usbsubdriver[i].name); } printf("\n\n"); - #endif -#endif +# endif /* QX_USB*/ + + /* Protocols are the first token from "name" field in + * subdriver_t instances in files like nutdrv_qx_mecer.c + */ + printf("\nAcceptable values for 'protocol' via -x or ups.conf in this driver: "); + for (i = 0; subdriver_list[i] != NULL; i++) { + char subdrv_name[SMALLBUF], *p; + + /* Get rid of subdriver version */ + snprintf(subdrv_name, sizeof(subdrv_name), "%.*s", + (int)strcspn(subdriver_list[i]->name, " "), + subdriver_list[i]->name); + + /* lowercase the (ASCII) string */ + for (p = subdrv_name; *p; ++p) + *p = tolower(*p); + + if (i>0) + printf(", "); + printf("%s", subdrv_name); + } + printf("\n\n"); +#endif /* TESTING */ printf("Read The Fine Manual ('man 8 nutdrv_qx')\n"); }