From eb561768961e6a43165b849570532c68ad588b7b Mon Sep 17 00:00:00 2001 From: nmbro Date: Mon, 19 Oct 2026 02:31:32 +0200 Subject: [PATCH 1/8] build: accept libusb-1.0 from 1.0.9 on configure required libusb_strerror() (libusb 1.0.16+, see #509) to use the libusb-1.0 API, so e.g. the libusb1 1.0.9 package of CentOS 6 was rejected. Building against it showed only one other method missing: libusb_get_version(). * configure: accept libusb_error_name() (1.0.9+) when libusb_strerror() is missing, and check for libusb_get_version(). * usb-common.h: map libusb_strerror() to libusb_error_name() when the former is missing; messages then show the error code name. * libusb1.c: without libusb_get_version(), report the libusb version in driver.version.usb as unknown. * nut-scanner: when the libusb loaded at run time lacks libusb_strerror(), use libusb_error_name(); treat a missing libusb_get_version() as an unknown (old) version instead of failing to load the library. Mirror struct libusb_version for builds against headers that lack it. Tested by building against the headers and libraries from the CentOS 6.10 RPMs (host GCC), and by running nut-scanner built against libusb 1.0.27 with the 1.0.9 library and vice versa. Not tested with devices. Refs: #3781 Co-Authored-By: Claude Opus 5.5 Signed-off-by: nmbro --- NEWS.adoc | 8 ++++++++ drivers/libusb1.c | 13 ++++++++++--- drivers/usb-common.h | 8 ++++++++ m4/nut_check_libusb.m4 | 23 +++++++++++++++++++---- tools/nut-scanner/scan_usb.c | 36 ++++++++++++++++++++++++++++++++++-- 5 files changed, 79 insertions(+), 9 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 67077f8612..ab3f093f17 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -1071,6 +1071,14 @@ https://github.com/networkupstools/nut/milestone/13 under `lib64` (e.g. on CentOS 7) and in directories named by just the major and minor version (e.g. `aspell-0.60` for `aspell` 0.60.6.1). [issue #2068] + * USB support can now be built against libusb-1.0 releases from 1.0.9 + on, such as the `libusb1` 1.0.9 package of CentOS 6. Before 1.0.16 + there is no `libusb_strerror()`, so error messages show the name of + the libusb error code (from `libusb_error_name()`) instead of its + description; before 1.0.10 or so there is no `libusb_get_version()`, + so `driver.version.usb` says the version is unknown. `nut-scanner` + falls back the same way when the libusb it loads at run time lacks + these methods. [issue #509, issue #3781] Release notes for NUT 2.8.5 - what's new since 2.8.4 diff --git a/drivers/libusb1.c b/drivers/libusb1.c index 65266c2682..f1f68afe11 100644 --- a/drivers/libusb1.c +++ b/drivers/libusb1.c @@ -149,7 +149,9 @@ static void nut_libusb_close(libusb_device_handle *udev); */ void nut_usb_addvars(void) { +#if (defined HAVE_LIBUSB_GET_VERSION) && HAVE_LIBUSB_GET_VERSION const struct libusb_version *v = libusb_get_version(); +#endif /* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */ addvar(VAR_VALUE, "vendor", "Regular expression to match UPS Manufacturer string"); @@ -189,11 +191,16 @@ void nut_usb_addvars(void) addvar(VAR_VALUE, "usb_hid_ep_in", "Deeper tuning of USB communications for complex devices"); addvar(VAR_VALUE, "usb_hid_ep_out", "Deeper tuning of USB communications for complex devices"); -#ifdef LIBUSB_API_VERSION +#if (defined HAVE_LIBUSB_GET_VERSION) && HAVE_LIBUSB_GET_VERSION +# ifdef LIBUSB_API_VERSION dstate_setinfo("driver.version.usb", "libusb-%u.%u.%u (API: 0x%08X)", v->major, v->minor, v->micro, (unsigned int)LIBUSB_API_VERSION); -#else /* no LIBUSB_API_VERSION */ +# else /* no LIBUSB_API_VERSION */ dstate_setinfo("driver.version.usb", "libusb-%u.%u.%u", v->major, v->minor, v->micro); -#endif /* LIBUSB_API_VERSION */ +# endif /* LIBUSB_API_VERSION */ +#else /* no libusb_get_version() */ + /* Added after libusb 1.0.9, the oldest release configure accepts */ + dstate_setinfo("driver.version.usb", "libusb-1.0 (version unknown, 1.0.9 or older)"); +#endif /* HAVE_LIBUSB_GET_VERSION */ upsdebugx(1, "Using USB implementation: %s", dstate_getinfo("driver.version.usb")); } diff --git a/drivers/usb-common.h b/drivers/usb-common.h index 35dae8b146..c81f1750f1 100644 --- a/drivers/usb-common.h +++ b/drivers/usb-common.h @@ -95,6 +95,14 @@ #if WITH_LIBUSB_1_0 # include + /* libusb_strerror() appeared in libusb 1.0.16; configure accepts older + * releases from 1.0.9 on, which only have libusb_error_name(): it names + * the error code (e.g. "LIBUSB_ERROR_TIMEOUT") instead of describing it. + * See https://github.com/networkupstools/nut/issues/509 */ +# if !(defined HAVE_LIBUSB_STRERROR) || !HAVE_LIBUSB_STRERROR +# define libusb_strerror(e) libusb_error_name((int)(e)) +# endif + /* Simply remap libusb functions/structures from 0.1 to 1.0 */ /* Structures */ diff --git a/m4/nut_check_libusb.m4 b/m4/nut_check_libusb.m4 index 26f92185f1..8c0767d6aa 100644 --- a/m4/nut_check_libusb.m4 +++ b/m4/nut_check_libusb.m4 @@ -226,11 +226,22 @@ AC_DEFUN([NUT_CHECK_LIBUSB], dnl libusb 1.0: libusb_set_auto_detach_kernel_driver AC_CHECK_HEADERS(libusb.h, [nut_cv_have_libusb=yes], [nut_cv_have_libusb=no], [AC_INCLUDES_DEFAULT]) AC_CHECK_FUNCS(libusb_init, [], [nut_cv_have_libusb=no]) - AC_CHECK_FUNCS(libusb_strerror, [], [nut_cv_have_libusb=no; nut_cv_have_libusb_strerror=no]) + dnl libusb_strerror() appeared in 1.0.16; older releases + dnl from 1.0.9 on have libusb_error_name() which we can + dnl use instead (see usb-common.h): + AC_CHECK_FUNCS(libusb_strerror, [], [nut_cv_have_libusb_strerror=no]) AS_IF([test "${nut_cv_have_libusb_strerror}" = "no"], [ - AC_MSG_WARN([libusb_strerror() not found; install libusbx to use libusb 1.0 API. See https://github.com/networkupstools/nut/issues/509]) + AC_CHECK_FUNCS(libusb_error_name, [ + AC_MSG_NOTICE([libusb_strerror() not found (libusb older than 1.0.16), error messages will show libusb_error_name() codes instead]) + ], [ + nut_cv_have_libusb=no + AC_MSG_WARN([neither libusb_strerror() nor libusb_error_name() found; libusb 1.0.9 or newer is needed to use libusb 1.0 API. See https://github.com/networkupstools/nut/issues/509]) + ]) ]) AS_IF([test "${nut_cv_have_libusb}" = "yes"], [ + dnl Absent before libusb 1.0.10 or so, not fatal: + AC_CHECK_FUNCS(libusb_get_version) + dnl This function is fairly old, but check for it anyway: AC_CHECK_FUNCS(libusb_kernel_driver_active) dnl Check for libusb "force driver unbind" availability @@ -533,10 +544,14 @@ AC_DEFUN([NUT_CHECK_LIBUSB], dnl libusb 1.0 detected via pkg-config: AC_CHECK_HEADERS(libusb.h, [], [nut_have_libusb=no], [AC_INCLUDES_DEFAULT]) AC_CHECK_FUNCS(libusb_init, [], [nut_have_libusb=no]) - AC_CHECK_FUNCS(libusb_strerror, [], [nut_have_libusb=no; nut_have_libusb_strerror=no]) + AC_CHECK_FUNCS(libusb_strerror, [], [nut_have_libusb_strerror=no]) AS_IF([test "${nut_have_libusb_strerror}" = "no"], [ - AC_MSG_WARN([libusb_strerror() not found; install libusbx to use libusb 1.0 API. See https://github.com/networkupstools/nut/issues/509]) + AC_CHECK_FUNCS(libusb_error_name, [], [ + nut_have_libusb=no + AC_MSG_WARN([neither libusb_strerror() nor libusb_error_name() found; libusb 1.0.9 or newer is needed to use libusb 1.0 API. See https://github.com/networkupstools/nut/issues/509]) + ]) ]) + AC_CHECK_FUNCS(libusb_get_version) ], [ dnl libusb 0.1, or missing pkg-config: AC_CHECK_HEADERS(usb.h, [], [ diff --git a/tools/nut-scanner/scan_usb.c b/tools/nut-scanner/scan_usb.c index c8489b2512..b594f02816 100644 --- a/tools/nut-scanner/scan_usb.c +++ b/tools/nut-scanner/scan_usb.c @@ -57,6 +57,18 @@ static int nut_usb_get_string_with_langid_control_transfer( /* Compatibility layer between libusb 0.1 and 1.0 */ #if WITH_LIBUSB_1_0 +# if !(defined HAVE_LIBUSB_GET_VERSION) || !HAVE_LIBUSB_GET_VERSION + /* libusb.h of 1.0.9 and older lacks this; mirror its (stable ABI) + * layout so that a newer libusb found at run time can still be asked */ + struct libusb_version { + const uint16_t major; + const uint16_t minor; + const uint16_t micro; + const uint16_t nano; + const char *rc; + const char *describe; + }; +# endif /* !HAVE_LIBUSB_GET_VERSION */ # define USB_INIT_SYMBOL "libusb_init" # define USB_OPEN_SYMBOL "libusb_open" # define USB_CLOSE_SYMBOL "libusb_close" @@ -160,7 +172,21 @@ int nutscan_load_usb_library(const char *libname_path) *(void **) (&nut_usb_strerror) = lt_dlsym(dl_handle, symbol = USB_STRERROR_SYMBOL); if ((dl_error = lt_dlerror()) != NULL) { +#if WITH_LIBUSB_1_0 + /* libusb_strerror() appeared in libusb 1.0.16; releases from + * 1.0.9 on have libusb_error_name() with a compatible signature, + * which names the error code instead of describing it. */ + upsdebugx(1, "%s: While loading USB library (%s), " + "failed to find %s() : %s. Trying libusb_error_name() instead.", + __func__, libname_path, USB_STRERROR_SYMBOL, dl_error); + *(void **) (&nut_usb_strerror) = lt_dlsym(dl_handle, + symbol = "libusb_error_name"); + if ((dl_error = lt_dlerror()) != NULL) { + goto err; + } +#else goto err; +#endif /* WITH_LIBUSB_1_0 */ } #if WITH_LIBUSB_1_0 @@ -170,10 +196,15 @@ int nutscan_load_usb_library(const char *libname_path) goto err; } + /* Absent in libusb 1.0.9 and older, tolerate that */ *(void **) (&nut_usb_get_version) = lt_dlsym(dl_handle, symbol = "libusb_get_version"); if ((dl_error = lt_dlerror()) != NULL) { - goto err; + upsdebugx(1, "%s: While loading USB library (%s), " + "failed to find libusb_get_version() : %s. " + "The libusb version is unknown, probably 1.0.9 or older.", + __func__, libname_path, dl_error); + nut_usb_get_version = NULL; } *(void **) (&nut_usb_get_device_list) = lt_dlsym(dl_handle, @@ -516,7 +547,8 @@ nutscan_device_t * nutscan_scan_usb(nutscan_usb_t * scanopts) * libusb_exit() should not be called unless libusb_init() * succeeded for same context. */ - const struct libusb_version *ver = (*nut_usb_get_version)(); + /* No libusb_get_version() means a libusb older than 1.0.24 */ + const struct libusb_version *ver = nut_usb_get_version ? (*nut_usb_get_version)() : NULL; if (ver && (ver->major > 1 || (ver->major == 1 && ver->minor >= 24))) (*nut_usb_exit)(NULL); From 2031bd54ef924f73a3a5604bfecdef0e6d57f68a Mon Sep 17 00:00:00 2001 From: nmbro Date: Mon, 19 Oct 2026 02:31:57 +0200 Subject: [PATCH 2/8] build: fall back to libusb-0.1 if libusb-1.0 is too old With --with-usb=auto (or yes), configure picks libusb-1.0 whenever pkg-config knows it. If that libusb-1.0 then failed the usability checks, the libusb-0.1 checks were never tried and configure silently built no USB drivers, even with a usable libusb-0.1 installed. When the libusb-1.0 picked by preference does not link with libusb_init() and libusb_error_name() (libusb 1.0.9+), and a libusb-0.1 was also detected, switch to libusb-0.1 before computing the flags and say so in a notice. An explicit --with-usb=libusb-1.0, or custom --with-usb-includes/--with-usb-libs, still fails as before. Tested with the CentOS 6.0 libusb1 1.0.3 + libusb 0.1.12 RPM contents (falls back, builds all drivers), 1.0.9 + 0.1.12 (keeps 1.0.9), and the host libusb 1.0.27 (unchanged), including a cached configure re-run. Closes: #3781 Co-Authored-By: Claude Opus 5.5 Signed-off-by: nmbro --- NEWS.adoc | 4 ++++ m4/nut_check_libusb.m4 | 45 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index ab3f093f17..ffbb333dce 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -1079,6 +1079,10 @@ https://github.com/networkupstools/nut/milestone/13 so `driver.version.usb` says the version is unknown. `nut-scanner` falls back the same way when the libusb it loads at run time lacks these methods. [issue #509, issue #3781] + * With `--with-usb=auto` (or `yes`), the `configure` script now falls + back to an also available libusb-0.1 when the preferred libusb-1.0 is + too old for NUT, instead of building without USB drivers. An explicit + `--with-usb=libusb-1.0` still fails in that case. [issue #3781] Release notes for NUT 2.8.5 - what's new since 2.8.4 diff --git a/m4/nut_check_libusb.m4 b/m4/nut_check_libusb.m4 index 8c0767d6aa..0ad62d8bfb 100644 --- a/m4/nut_check_libusb.m4 +++ b/m4/nut_check_libusb.m4 @@ -156,6 +156,45 @@ AC_DEFUN([NUT_CHECK_LIBUSB], ) AC_MSG_RESULT([${nut_cv_LIBUSB_VERSION} ${nut_cv_usb_lib}]) + dnl A libusb-1.0 picked by preference (not explicitly requested) + dnl may be too old for NUT: libusb_error_name() (1.0.9+) is the + dnl minimum, standing in for libusb_strerror() (1.0.16+, see + dnl https://github.com/networkupstools/nut/issues/509) e.g. with + dnl the libusb1-1.0.9 of CentOS 6. If a libusb-0.1 is also available, + dnl fall back to it rather than end up with no USB drivers, see + dnl https://github.com/networkupstools/nut/issues/3781 + dnl Custom cflags/ldflags are taken as an explicit choice too. + AS_IF([test x"${nut_cv_usb_lib}" = x"(libusb-1.0)" \ + && test x"${nut_with_usb_includes}" = xauto \ + && test x"${nut_with_usb_libs}" = xauto \ + && test x"${nut_cv_LIBUSB_0_1_VERSION}" != xnone -o x"${nut_cv_LIBUSB_CONFIG_VERSION}" != xnone + ], [ + AS_CASE(["${nut_cv_with_usb}"], + [auto|yes], [ + AC_MSG_CHECKING([whether libusb-1.0 ${nut_cv_LIBUSB_1_0_VERSION} provides libusb_init() and libusb_error_name()]) + CFLAGS="${CFLAGS_ORIG} `$PKG_CONFIG --silence-errors --cflags libusb-1.0 2>/dev/null`" + LIBS="${LIBS_ORIG} `$PKG_CONFIG --silence-errors --libs libusb-1.0 2>/dev/null`" + AC_LANG_PUSH([C]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include ]], + [[libusb_init(NULL); (void)libusb_error_name(LIBUSB_SUCCESS);]])], + [AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no]) + AS_IF([test x"${nut_cv_LIBUSB_0_1_VERSION}" != xnone], + [nut_cv_LIBUSB_VERSION="${nut_cv_LIBUSB_0_1_VERSION}" + nut_cv_usb_lib="(libusb-0.1)" + ], + [nut_cv_LIBUSB_VERSION="${nut_cv_LIBUSB_CONFIG_VERSION}" + nut_cv_usb_lib="(libusb-0.1-config)" + ]) + AC_MSG_NOTICE([libusb-1.0 ${nut_cv_LIBUSB_1_0_VERSION} is not usable by NUT (libusb 1.0.9+ needed), falling back to ${nut_cv_LIBUSB_VERSION} ${nut_cv_usb_lib}]) + ]) + AC_LANG_POP([C]) + CFLAGS="" + LIBS="" + ] + ) + ]) + AS_IF([test x"${nut_cv_LIBUSB_1_0_VERSION}" != xnone && test x"${nut_cv_usb_lib}" != x"(libusb-1.0)" ], [AC_MSG_NOTICE([libusb-1.0 support was detected, but another was chosen ${nut_cv_usb_lib}])] ) @@ -209,10 +248,8 @@ AC_DEFUN([NUT_CHECK_LIBUSB], ) AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) - dnl TODO: Consult chosen nut_cv_usb_lib value and/or nut_cv_with_usb argument - dnl (with "auto" we may use a 0.1 if present and working while a 1.0 is - dnl present but useless) - dnl Check if libusb is usable + dnl Check if libusb is usable (a 1.0 too old for NUT was already + dnl swapped for an available 0.1 above, when not chosen explicitly) CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" LIBS="${LIBS_ORIG} ${depLIBS}" nut_cv_have_libusb_strerror="" From f0859637d5486e238fc98dd49acf40c1ab708b17 Mon Sep 17 00:00:00 2001 From: nmbro Date: Mon, 19 Oct 2026 22:12:34 +0200 Subject: [PATCH 3/8] scripts/Windows: update wdi-simple for WinUSB The USB driver installer for Windows still installed the libusb-win32 libusb0.sys driver, which libusb-1.0 cannot use without libusbK.dll. Install WinUSB by default instead, with libusbK as an option. Also fix bugs in it: it looped until a 0xFFFF/0xFFFF entry, but the generated nutscan-usb.h table now ends with {0, 0}, so it read past the end; it could return an uninitialized value; it passed an extra printf argument; it never parsed its options or freed the device list. Composite devices are now matched on their first interface. Rewrite the README for current libwdi (the old SourceForge wiki and ddk_build.cmd are gone, and nutscan-usb.h now needs NUT's config.h), and point the WiX installer text at Zadig/WinUSB. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017MHaw4FzL87GMq4cHFMVzt Signed-off-by: nmbro --- NEWS.adoc | 5 + docs/nut.dict | 13 +- scripts/Windows/DriverInstaller/README.adoc | 55 ++++- scripts/Windows/DriverInstaller/wdi-simple.c | 230 ++++++++++++------ .../Windows/Installer/NUT-Installer.xml.in | 4 +- 5 files changed, 228 insertions(+), 79 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index ffbb333dce..f08c5e7840 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -1083,6 +1083,11 @@ https://github.com/networkupstools/nut/milestone/13 back to an also available libusb-0.1 when the preferred libusb-1.0 is too old for NUT, instead of building without USB drivers. An explicit `--with-usb=libusb-1.0` still fails in that case. [issue #3781] + * The `wdi-simple` USB driver installer for Windows now installs the + WinUSB driver (or libusbK, if chosen) instead of `libusb0.sys`, + accepts command-line options, and no longer reads past the end of + the generated NUT USB device table. Its build instructions were + updated for current libwdi. [issue #1507] Release notes for NUT 2.8.5 - what's new since 2.8.4 diff --git a/docs/nut.dict b/docs/nut.dict index b8ad7835e8..c80333f437 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3916 utf-8 +personal_ws-1.1 en 3927 utf-8 AAC AAS ABI @@ -1586,6 +1586,7 @@ WALKMODE WARNFATAL WARNOPT WCH +WDK WELI WH WHAD @@ -2665,6 +2666,8 @@ libtool libupsclient libusb libusb's +libusbK +libusbk libusbugen libusbx libvirt @@ -2699,6 +2702,7 @@ lm lmodbus ln lnetsnmp +lntdll loadPercentage localcalculation localhost @@ -2713,6 +2717,7 @@ login logins logout logrotate +lole lookup loopback lowbatt @@ -2721,6 +2726,7 @@ lr lregex ls lsd +lsetupapi lst lsusb lt @@ -2729,6 +2735,7 @@ lu lua lv lvo +lwdi lws lxc lxcbr @@ -3217,6 +3224,7 @@ reconnection recurses recursing recv +redistributable redistributors reentrancy refactored @@ -3394,6 +3402,7 @@ slaveaddr slaveid slavesync slibtool +sln sm smartslot smartups @@ -3820,6 +3829,7 @@ waitbeforereconnect wakeup wc wdi +wdkdir webserver wf wget @@ -3832,6 +3842,7 @@ wininit winnutclient winpthreads winsymlinks +winusb withGiven withdoc wix diff --git a/scripts/Windows/DriverInstaller/README.adoc b/scripts/Windows/DriverInstaller/README.adoc index 4e60a1ff93..568b9631e7 100644 --- a/scripts/Windows/DriverInstaller/README.adoc +++ b/scripts/Windows/DriverInstaller/README.adoc @@ -1,11 +1,52 @@ -To easily compile `wdi-simple.exe`, unzip a copy of `libwdi` on your disk. +NUT USB driver installer for Windows +==================================== -Then set it up to be able to build following -http://sourceforge.net/apps/mediawiki/libwdi/index.php?title=Install +On Windows, NUT drivers talk to USB devices through libusb-1.0, which can +only open a device that is bound to a suitable kernel driver. Most UPS +devices are bound to the Windows HID driver by default, so a different +driver must be installed for them first. WinUSB is preferred: it is part +of Windows, and libusb-1.0 supports it natively. -NOTE: Modern development may be at https://github.com/pbatard/libwdi +`wdi-simple.c` is a small console program based on an example from +https://github.com/pbatard/libwdi[libwdi]. It looks for plugged-in USB +devices whose vendor and product IDs are known to NUT (from the generated +`tools/nut-scanner/nutscan-usb.h`) and installs a driver for each: -Then copy `wdi-simple.c` and `nutscan-usb.h` into the "examples" -subdirectory of libwdi directory. +- `winusb` (default, recommended) +- `libusbk` (needs `libusbK.dll` next to the NUT programs) -Finally run `ddk_build.cmd` from the root of the `libwdi` directory. +NUT for Windows is built with libusb-1.0 only; the libusb-win32 +`libusb0.sys` driver used by older NUT releases is no longer supported. + +Run `wdi-simple --help` for the options. It must be run with +administrator rights. Note that libwdi (and Zadig) currently support +Windows 7 and newer. + +The same can be done by hand with https://zadig.akeo.ie/[Zadig]: select +the UPS (use "Options / List All Devices" if needed), choose `WinUSB` +and click "Replace Driver". To revert, uninstall the device in Device +Manager (with "delete the driver" checked) and re-plug it. + +Building +-------- + +1. Build libwdi, either with Visual Studio (`libwdi.sln`) or with MinGW + (including cross-builds with `--host=...`), following + https://github.com/pbatard/libwdi/wiki/Install. To embed WinUSB, it + needs the WDK redistributable components (`--with-wdkdir=...`). + +2. Configure and build NUT for Windows (e.g. with `build-mingw-nut.sh` + in the parent directory), so that `config.h` and + `tools/nut-scanner/nutscan-usb.h` are generated. + +3. Compile `wdi-simple.c` against both, for example with MinGW: ++ +---- +:; x86_64-w64-mingw32-gcc -o wdi-simple.exe \ + -I"$LIBWDI_SRC/libwdi" \ + -I"$NUT_BUILD/include" -I"$NUT_SRC/include" \ + -I"$NUT_SRC/tools/nut-scanner" \ + $(pkg-config --cflags libusb-1.0) \ + wdi-simple.c \ + -static -L"$LIBWDI_SRC/libwdi/.libs" -lwdi -lsetupapi -lole32 -lntdll +---- diff --git a/scripts/Windows/DriverInstaller/wdi-simple.c b/scripts/Windows/DriverInstaller/wdi-simple.c index 31d50b5e67..2bad5bf4bb 100644 --- a/scripts/Windows/DriverInstaller/wdi-simple.c +++ b/scripts/Windows/DriverInstaller/wdi-simple.c @@ -19,6 +19,7 @@ */ #include +#include #include #include #ifdef _MSC_VER @@ -29,37 +30,126 @@ #include "libwdi.h" #include "nutscan-usb.h" -#define oprintf(...) do {if (!opt_silent) printf(__VA_ARGS__);} while(0) - #define DESC "NUT USB driver" #define INF_NAME "usb_device.inf" #define DEFAULT_DIR "usb_driver" +static int opt_silent = 0; + +static void oprintf(const char *fmt, ...) +{ + va_list ap; + + if (opt_silent) { + return; + } + + va_start(ap, fmt); + vprintf(fmt, ap); + va_end(ap); +} + +static void usage(void) +{ + printf("Usage: wdi-simple [options]\n\n"); + printf("Install a USB driver for the UPS devices known to NUT,\n"); + printf("so that NUT drivers can open them.\n\n"); + printf("-t, --type driver to install: winusb (default) or libusbk\n"); + printf("-d, --dest set the extraction directory (default: %s)\n", DEFAULT_DIR); + printf("-x, --extract extract driver files only, do not install\n"); + printf("-s, --silent silent mode, do not wait for user input\n"); + printf("-l, --log set libwdi log level (0=debug, 4=none)\n"); + printf("-h, --help display this help\n"); +} + +/* Is this device (interface) one that a NUT driver would open? */ +static int is_known_ups(const struct wdi_device_info *ldev) +{ + size_t i; + + /* NUT drivers use the first interface of composite devices */ + if (ldev->is_composite && ldev->mi != 0) { + return 0; + } + + /* The generated table ends with an all-zero entry */ + for (i = 0; usb_device_table[i].vendorID != 0 || usb_device_table[i].productID != 0; i++) { + if (ldev->vid == usb_device_table[i].vendorID + && ldev->pid == usb_device_table[i].productID + ) { + return 1; + } + } + + return 0; +} int __cdecl main(int argc, char** argv) { char desc[128] = DESC; - struct wdi_device_info *ldev, *ldev_start, dev = {NULL, 0, 0, false, 0, DESC, NULL, NULL, NULL}; - struct wdi_options_create_list ocl = { 0 }; - struct wdi_options_prepare_driver opd = { 0 }; - struct wdi_options_install_driver oid = { 0 }; + struct wdi_device_info *ldev, *ldev_start = NULL, dev; + struct wdi_options_create_list ocl; + struct wdi_options_prepare_driver opd; + struct wdi_options_install_driver oid; + static struct option long_options[] = { + {"type", required_argument, 0, 't'}, + {"dest", required_argument, 0, 'd'}, + {"extract", no_argument, 0, 'x'}, + {"silent", no_argument, 0, 's'}, + {"log", required_argument, 0, 'l'}, + {"help", no_argument, 0, 'h'}, + {0, 0, 0, 0} + }; int c, r; - int opt_silent = 0, opt_extract = 0, log_level = WDI_LOG_LEVEL_WARNING; -/* - int opt_silent = 0, opt_extract = 0, log_level = WDI_LOG_LEVEL_DEBUG; -*/ + int opt_extract = 0, log_level = WDI_LOG_LEVEL_WARNING; + int found = 0; char *inf_name = INF_NAME; char *ext_dir = DEFAULT_DIR; - bool matching_device_found; - int index = 0; - ocl.list_all = true; - ocl.list_hubs = true; - ocl.trim_whitespaces = true; - opd.driver_type = WDI_LIBUSB0; -/* + memset(&ocl, 0, sizeof(ocl)); + memset(&opd, 0, sizeof(opd)); + memset(&oid, 0, sizeof(oid)); + + ocl.list_all = TRUE; + ocl.list_hubs = TRUE; + ocl.trim_whitespaces = TRUE; + /* libusb-1.0 (used by NUT on Windows) works best with WinUSB */ opd.driver_type = WDI_WINUSB; -*/ + + while ((c = getopt_long(argc, argv, "d:hl:st:x", long_options, NULL)) != -1) { + switch (c) { + case 'd': + ext_dir = optarg; + break; + case 'h': + usage(); + return 0; + case 'l': + log_level = (int)strtol(optarg, NULL, 0); + break; + case 's': + opt_silent = 1; + log_level = WDI_LOG_LEVEL_NONE; + break; + case 't': + if (!strcmp(optarg, "winusb")) { + opd.driver_type = WDI_WINUSB; + } else if (!strcmp(optarg, "libusbk")) { + opd.driver_type = WDI_LIBUSBK; + } else { + fprintf(stderr, "Unknown driver type: %s\n", optarg); + usage(); + return 1; + } + break; + case 'x': + opt_extract = 1; + break; + default: + usage(); + return 1; + } + } wdi_set_log_level(log_level); @@ -67,62 +157,64 @@ int __cdecl main(int argc, char** argv) oprintf("-------------------------\n\n"); oprintf("Searching for known UPS...\n"); - /* Try to match against a plugged device */ - matching_device_found = false; - if (wdi_create_list(&ldev, &ocl) == WDI_SUCCESS) { - r = WDI_SUCCESS; - ldev_start = ldev; - while( usb_device_table[index].vendorID != 0xFFFF || usb_device_table[index].productID != 0xFFFF) { - dev.next = NULL; - dev.vid = usb_device_table[index].vendorID; - dev.pid = usb_device_table[index].productID; - dev.is_composite = false; - dev.mi = 0; + r = wdi_create_list(&ldev_start, &ocl); + if (r != WDI_SUCCESS && r != WDI_ERROR_NO_DEVICE) { + oprintf("Could not list USB devices: %s\n", wdi_strerror(r)); + ldev_start = NULL; + } + + for (ldev = ldev_start; ldev != NULL; ldev = ldev->next) { + if (!is_known_ups(ldev)) { + continue; + } + found++; + + /* Work on a copy: the list owns its strings */ + memcpy(&dev, ldev, sizeof(dev)); + dev.next = NULL; + if (dev.desc == NULL) { dev.desc = desc; - dev.driver = NULL; - dev.device_id = NULL; - dev.hardware_id = NULL; -/* - oprintf("NUT device : vid : %0X - pid : %0X\n",dev.vid, dev.pid); -*/ + } - for (ldev = ldev_start; (ldev != NULL) && (r == WDI_SUCCESS); ldev = ldev->next) { -/* - oprintf("trying vid : %0X - pid : %0X\n",ldev->vid, ldev->pid); -*/ - if ( (ldev->vid == dev.vid) && (ldev->pid == dev.pid) && (ldev->mi == dev.mi) ) { - oprintf("Found UPS : vendor ID = %0X - Product ID = %0X\n",ldev->vid, ldev->pid, ldev->mi); - dev.hardware_id = ldev->hardware_id; - dev.device_id = ldev->device_id; - matching_device_found = true; - - oprintf("Extracting driver files...\n"); - r = wdi_prepare_driver(&dev, ext_dir, inf_name, &opd); - oprintf(" %s\n", wdi_strerror(r)); - if ((r != WDI_SUCCESS) || (opt_extract)) - return r; - - oprintf(" %s: ", dev.hardware_id); - fflush(stdout); - oprintf("Installing driver\n"); - r = wdi_install_driver(&dev, ext_dir, inf_name, &oid); - oprintf("%s\n", wdi_strerror(r)); - if( r == WDI_SUCCESS ) { - oprintf("You should now unplug and re-plug your device to finish driver's installation.\nHit enter when it's done.\n"); - } - else { - oprintf("An error occured while installing driver.\nTry installing libUSB manually.\nHit enter to continue\n"); - } - getc(stdin); - } - } - index++; + oprintf("Found UPS: vendor ID = %04X - product ID = %04X (%s)\n", + dev.vid, dev.pid, dev.desc); + + oprintf("Extracting driver files...\n"); + r = wdi_prepare_driver(&dev, ext_dir, inf_name, &opd); + oprintf(" %s\n", wdi_strerror(r)); + if (r != WDI_SUCCESS || opt_extract) { + break; } + + oprintf("Installing driver for %s...\n", + dev.hardware_id ? dev.hardware_id : "device"); + r = wdi_install_driver(&dev, ext_dir, inf_name, &oid); + oprintf(" %s\n", wdi_strerror(r)); + if (r != WDI_SUCCESS) { + break; + } + } + + if (ldev_start != NULL) { + wdi_destroy_list(ldev_start); + } + + if (!found) { + oprintf("No known UPS device found.\n" + "Check that the UPS is plugged in, or install a driver\n" + "for it manually (e.g. WinUSB with Zadig).\n"); + r = WDI_ERROR_NOT_FOUND; + } else if (r != WDI_SUCCESS) { + oprintf("An error occurred while installing the driver.\n" + "Try installing a driver for the UPS manually\n" + "(e.g. WinUSB with Zadig).\n"); + } else if (!opt_extract) { + oprintf("You should now unplug and re-plug your device\n" + "to finish the driver installation.\n"); } - /* No plugged USB device matches */ - if (!matching_device_found) { - oprintf("No known UPS device found.\nTry installing libUSB manually.\nHit enter to continue\n"); + if (!opt_silent) { + oprintf("Hit enter to continue\n"); getc(stdin); } diff --git a/scripts/Windows/Installer/NUT-Installer.xml.in b/scripts/Windows/Installer/NUT-Installer.xml.in index 04beb98298..50169e41a8 100644 --- a/scripts/Windows/Installer/NUT-Installer.xml.in +++ b/scripts/Windows/Installer/NUT-Installer.xml.in @@ -23,9 +23,9 @@ If the automatic USB driver installation fails, you can try to install a driver manually. For this go to : -https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/ +https://zadig.akeo.ie/ -After installling libusb-win32, run libUSB's Inf Wizard and choose your device. +Run Zadig, select your UPS, choose WinUSB and click "Replace Driver". Click Next to continue [Wizard]. From 5f121cbcbbb0589d32915b0f519be95ac85883d7 Mon Sep 17 00:00:00 2001 From: nmbro Date: Mon, 19 Oct 2026 22:12:49 +0200 Subject: [PATCH 4/8] build: remove libusb-0.1 support for Windows libusb-0.1 (libusb-win32 or libusb-compat) brings nothing to Windows builds that libusb-1.0 with the WinUSB driver does not, and lacks the async and threading support some drivers (e.g. apcmicrolink) need. libusb-1.0 builds were tested with apcmicrolink and nut-scanner against a real UPS on Windows 10. configure now refuses --with-usb=libusb-0.1 for mingw targets, ignores an auto-detected libusb-0.1 there, and no longer looks for the libusb-win32 lusb0_usb.h header or in /mingw64/lib. libusb0.c errors out on WIN32 and loses its Windows-only code paths; blazer_usb.c no longer skips LIBUSB_ERROR_OVERFLOW on Windows (that was a libusb-win32 limitation). Docs and AppVeyor drop libusb-win32 and libusb-compat. Closes: #1507 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017MHaw4FzL87GMq4cHFMVzt Signed-off-by: nmbro --- .github/scripts/appveyor/02-install.bat | 2 +- INSTALL.nut.adoc | 19 ++++--- NEWS.adoc | 6 ++ UPGRADING.adoc | 7 +++ docs/config-prereqs.txt | 17 ++---- drivers/blazer_usb.c | 10 +--- drivers/libusb0.c | 68 +++------------------- drivers/libusb1.c | 6 +- drivers/usb-common.h | 29 +++++----- m4/nut_check_libusb.m4 | 75 ++++++++----------------- scripts/Windows/README.adoc | 12 +--- tools/nut-usbinfo.pl | 6 +- 12 files changed, 83 insertions(+), 174 deletions(-) diff --git a/.github/scripts/appveyor/02-install.bat b/.github/scripts/appveyor/02-install.bat index 6914e4715a..3bb6df46ad 100644 --- a/.github/scripts/appveyor/02-install.bat +++ b/.github/scripts/appveyor/02-install.bat @@ -31,7 +31,7 @@ REM Without the ecosystem update above, the overhead of this step remained REM similar. It did however use a smaller population (via deps) and older REM versions of packages. REM Prerequisites for NUT per https://github.com/networkupstools/nut/blob/master/docs/config-prereqs.txt : -C:\msys64\usr\bin\bash -lc "date -u; pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain autoconf-wrapper automake-wrapper libtool mingw-w64-x86_64-libltdl gcc ccache mingw-w64-x86_64-ccache git aspell aspell-en python mingw-w64-x86_64-python-pygments mingw-w64-x86_64-winpthreads-git mingw-w64-x86_64-libusb mingw-w64-x86_64-libusb-compat-git mingw-w64-x86_64-neon libneon-devel mingw-w64-x86_64-libgd mingw-w64-x86_64-cppunit mingw-w64-x86_64-nss mingw-w64-x86_64-openssl" +C:\msys64\usr\bin\bash -lc "date -u; pacman --noconfirm -S --needed base-devel mingw-w64-x86_64-toolchain autoconf-wrapper automake-wrapper libtool mingw-w64-x86_64-libltdl gcc ccache mingw-w64-x86_64-ccache git aspell aspell-en python mingw-w64-x86_64-python-pygments mingw-w64-x86_64-winpthreads-git mingw-w64-x86_64-libusb mingw-w64-x86_64-neon libneon-devel mingw-w64-x86_64-libgd mingw-w64-x86_64-cppunit mingw-w64-x86_64-nss mingw-w64-x86_64-openssl" if errorlevel 1 exit /b %ERRORLEVEL% REM SKIP mingw-w64-x86_64-libmodbus-git : we custom-build one with USB support REM SKIP for now NUT-Monitor prereqs (runtime Python would require somilar modules; need to fix localization builds like "fr.po"): gettext mingw-w64-x86_64-python-pyqt6 diff --git a/INSTALL.nut.adoc b/INSTALL.nut.adoc index c546d49136..d72d78e0bd 100644 --- a/INSTALL.nut.adoc +++ b/INSTALL.nut.adoc @@ -968,14 +968,17 @@ Newer Windows releases should include the Windows Installer natively. Run `NUT-Installer.msi` and follow the wizard indications. If you plan to use an UPS which is locally connected to an USB port, -you have to install -link:https://sourceforge.net/projects/libusb-win32/files/[libUSB-win32] -on your system. Then you must install your device via libusb's "Inf Wizard". - -NOTE: If you intend to build from source, relevant sources may be available at -https://github.com/mcuee/libusb-win32 and keep in mind that it is a variant of -libusb-0.1. Current NUT supports libusb-1.0 as well, and that project should -have Windows support out of the box (but it was not explored for NUT yet). +you have to bind it to the WinUSB driver, so that NUT (built with +libusb-1.0) can open it. Most UPS devices are bound to the Windows HID +driver by default. You can use the `wdi-simple` program shipped with NUT +(see `scripts/Windows/DriverInstaller` in the sources), or +link:https://zadig.akeo.ie/[Zadig]: select the UPS, choose `WinUSB` and +click "Replace Driver". Both currently support Windows 7 and newer. + +NOTE: Older NUT for Windows builds used libusb-0.1 via +https://github.com/mcuee/libusb-win32[libusb-win32] and its `libusb0.sys` +driver, installed with libusb's "Inf Wizard". Current NUT for Windows is +built with libusb-1.0 only, and does not support that driver. If you have selected default directory, all configuration files are located in `C:\Program Files\NUT\ups\etc` diff --git a/NEWS.adoc b/NEWS.adoc index f08c5e7840..3e87bbce07 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -1083,6 +1083,12 @@ https://github.com/networkupstools/nut/milestone/13 back to an also available libusb-0.1 when the preferred libusb-1.0 is too old for NUT, instead of building without USB drivers. An explicit `--with-usb=libusb-1.0` still fails in that case. [issue #3781] + * Windows (MinGW/MSYS2) builds of NUT now support only libusb-1.0 for + USB drivers, used with the WinUSB driver. Support for libusb-0.1 + (libusb-win32 with its `libusb0.sys` driver, or libusb-compat) was + removed from Windows builds: `configure --with-usb=libusb-0.1` now + fails there, and an auto-detected libusb-0.1 is not used. Tested + with `apcmicrolink` and `nut-scanner` on Windows 10. [issue #1507] * The `wdi-simple` USB driver installer for Windows now installs the WinUSB driver (or libusbK, if chosen) instead of `libusb0.sys`, accepts command-line options, and no longer reads past the end of diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 15fa29dece..98b9b9dd59 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -33,6 +33,13 @@ Changes from 2.8.5 to 2.8.6 - PLANNED: Keep track of any further API clean-up? +- Windows builds of NUT now support only libusb-1.0; libusb-0.1 + (libusb-win32 or libusb-compat) is no longer supported there, and + `--with-usb=libusb-0.1` fails. Packagers should depend on libusb-1.0 + instead. USB devices previously bound to the libusb-win32 + `libusb0.sys` driver must be re-bound to the WinUSB driver (e.g. with + the updated `wdi-simple` or with Zadig). [issue #1507] + - The Perl `UPS::Nut` module now decodes NUT quoting and backslash escapes in query/list values and `nutauth.conf`, including include paths. Remove manual decoding of these returned values and pass literal credentials diff --git a/docs/config-prereqs.txt b/docs/config-prereqs.txt index 9d49a4799b..a3737b65a6 100644 --- a/docs/config-prereqs.txt +++ b/docs/config-prereqs.txt @@ -2066,23 +2066,14 @@ export PATH mingw-w64-x86_64-winpthreads-git \ mingw-w64-clang-x86_64-winpthreads-git -# Note that MSYS2 includes libusb-1.0 "natively" -# The NUT codebase adjustments for Windows might at this moment expect older -# ecosystem via https://github.com/mcuee/libusb-win32 -- subject to fix then. +# Note that MSYS2 includes libusb-1.0 "natively"; it is the only libusb +# supported by NUT on Windows (libusb-win32 and libusb-compat, i.e. the +# libusb-0.1 API, are not). The UPS must be bound to the WinUSB driver +# (e.g. with Zadig) for libusb-1.0 to open it. :; pacman -S --needed \ mingw-w64-x86_64-libusb \ mingw-w64-clang-x86_64-libusb -# Seems that the older libusb-win32 (libusb-0.1) is also available as: -:; pacman -S --needed \ - mingw-w64-x86_64-libusb-win32 \ - mingw-w64-clang-x86_64-libusb-win32 - -# Alternately there is libusb-compat (libusb-1.0 codebase exposing the older -# libusb-0.1 API) which SHOULD NOT be installed along with the real libusb-0.1: -# :; pacman -S --needed mingw-w64-x86_64-libusb-compat-git \ -# mingw-w64-clang-x86_64-libusb-compat-git - # This also pulls *-devel of several other projects: :; pacman -S --needed \ mingw-w64-x86_64-neon libneon-devel \ diff --git a/drivers/blazer_usb.c b/drivers/blazer_usb.c index 21d740ea14..02b35a279d 100644 --- a/drivers/blazer_usb.c +++ b/drivers/blazer_usb.c @@ -37,7 +37,7 @@ #endif /* WIN32 */ #define DRIVER_NAME "Megatec/Q1 protocol USB driver" -#define DRIVER_VERSION "0.27" +#define DRIVER_VERSION "0.28" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -595,14 +595,10 @@ ssize_t blazer_command(const char *cmd, char *buf, size_t buflen) break; case LIBUSB_ERROR_TIMEOUT: /* Connection timed out */ -/* libusb-win32 does not know EPROTO and EOVERFLOW, - * it only returns EIO for any IO errors */ -#ifndef WIN32 case LIBUSB_ERROR_OVERFLOW: /* Value too large for defined data type */ -# if EPROTO && WITH_LIBUSB_0_1 +#if EPROTO && WITH_LIBUSB_0_1 case -EPROTO: /* Protocol error */ -# endif -#endif /* !WIN32 */ +#endif default: break; } diff --git a/drivers/libusb0.c b/drivers/libusb0.c index b87838beaa..4c6581e42e 100644 --- a/drivers/libusb0.c +++ b/drivers/libusb0.c @@ -31,6 +31,10 @@ #include "config.h" /* for HAVE_USB_DETACH_KERNEL_DRIVER_NP flag */ +#if (defined WIN32) || (defined _WIN32) +# error "libusb-0.1 is not supported for Windows builds, use libusb-1.0 (see https://github.com/networkupstools/nut/issues/1507)" +#endif + #if (defined ENABLE_SHARED_PRIVATE_LIBS) && ENABLE_SHARED_PRIVATE_LIBS # if (defined BUILD_FOR_SHARED_PRIVATE_LIBS) && BUILD_FOR_SHARED_PRIVATE_LIBS # define suggest_NDE_conflict LIBNUTPRIVATE_suggest_NDE_conflict @@ -42,13 +46,10 @@ #include "common.h" /* for xmalloc, upsdebugx prototypes */ #include "usb-common.h" #include "nut_libusb.h" -#ifdef WIN32 -# include "wincompat.h" -#endif /* WIN32 */ #include "strcasestr-static.h" #define USB_DRIVER_NAME "USB communication driver (libusb 0.1)" -#define USB_DRIVER_VERSION "0.54" +#define USB_DRIVER_VERSION "0.55" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -290,15 +291,7 @@ static int nut_libusb_open(usb_dev_handle **udevp, usb_find_busses(); usb_find_devices(); -#ifdef WIN32 - busses = usb_get_busses(); -#else /* !WIN32 */ - /* libusb built-in; not sure why original NUT for WIN32 - * code differed or if it is actually better? Or why - * this was not tackled in a few other files for USB?.. - */ busses = usb_busses; -#endif /* !WIN32 */ #ifndef __linux__ /* SUN_LIBUSB (confirmed to work on Solaris and FreeBSD) */ /* Causes a double free corruption in linux if device is detached! */ @@ -466,9 +459,6 @@ static int nut_libusb_open(usb_dev_handle **udevp, /* this method requires at least libusb 0.1.8: * it force device claiming by unbinding * attached driver... From libhid */ -#ifdef WIN32 - usb_set_configuration(udev, 1); -#endif /* WIN32 */ retries = 3; while ((ret = usb_claim_interface(udev, usb_subdriver.hid_rep_index)) < 0) { upsdebugx(2, "failed to claim USB device: %s", @@ -663,23 +653,9 @@ static int nut_libusb_open(usb_dev_handle **udevp, if (res < rdlen) { -#ifndef WIN32 upsdebugx(2, "Warning: report descriptor too short " "(expected %" PRI_NUT_USB_CTRL_CHARBUFSIZE ", got %d)", rdlen, res); -#else /* WIN32 */ - /* https://github.com/networkupstools/nut/issues/1690#issuecomment-1455206002 */ - upsdebugx(0, "Warning: report descriptor too short " - "(expected %" PRI_NUT_USB_CTRL_CHARBUFSIZE - ", got %d)", rdlen, res); - upsdebugx(0, "Please check your Windows Device Manager: " - "perhaps the UPS was recognized by default OS\n" - "driver such as HID UPS Battery (hidbatt.sys, " - "hidusb.sys or similar). It could have been\n" - "\"restored\" by Windows Update. You can try " - "https://zadig.akeo.ie/ to handle it with\n" - "either WinUSB, libusb0.sys or libusbK.sys."); -#endif /* WIN32 */ rdlen = res; /* correct rdlen if necessary */ } @@ -762,16 +738,6 @@ static int nut_libusb_open(usb_dev_handle **udevp, "https://github.com/networkupstools/nut/wiki/%%22Insufficient-permissions%%22-when-starting-USB-drivers"); } -#ifdef WIN32 - upsdebugx(0, "Please check your Windows Device Manager: " - "perhaps the UPS was recognized by default OS\n" - "driver such as HID UPS Battery (hidbatt.sys, " - "hidusb.sys or similar). It could have been\n" - "\"restored\" by Windows Update. You can try " - "https://zadig.akeo.ie/ to handle it with\n" - "either WinUSB, libusb0.sys or libusbK.sys."); -#endif /* WIN32 */ - return -1; } @@ -803,16 +769,12 @@ static int nut_libusb_strerror(const int ret, const char *desc) upsdebugx(2, "%s: Connection timed out", desc); return 0; -/* libusb-win32 does not know EPROTO and EOVERFLOW, - * it only returns EIO for any IO errors */ -#ifndef WIN32 case -EOVERFLOW: /* Value too large for defined data type */ -# ifdef EPROTO +#ifdef EPROTO case -EPROTO: /* Protocol error */ -# endif +#endif upsdebugx(2, "%s: %s", desc, usb_strerror()); return 0; -#endif /* !WIN32 */ case 0: /** TOTHINK: Should this (probably LIBUSB_SUCCESS) be quiet? */ default: /* Undetermined, log only */ @@ -850,10 +812,6 @@ static int nut_libusb_get_report( usb_subdriver.hid_rep_index, raw_buf, ReportSize, USB_TIMEOUT); -#ifdef WIN32 - errno = -ret; -#endif /* WIN32 */ - /* Ignore "protocol stall" (for unsupported request) on control endpoint */ if (ret == -EPIPE) { return 0; @@ -885,10 +843,6 @@ static int nut_libusb_set_report( usb_subdriver.hid_rep_index, raw_buf, ReportSize, USB_TIMEOUT); -#ifdef WIN32 - errno = -ret; -#endif /* WIN32 */ - /* Ignore "protocol stall" (for unsupported request) on control endpoint */ if (ret == -EPIPE) { return 0; @@ -922,10 +876,6 @@ static int nut_libusb_get_string( ret = nut_usb_get_string(udev, StringIdx, buf, (size_t)buflen); -#ifdef WIN32 - errno = -ret; -#endif /* WIN32 */ - /** 0 can be seen as an empty string, or as a success for * logging below - also tends to happen */ if (ret == 0) { @@ -960,10 +910,6 @@ static int nut_libusb_get_interrupt( /* Interrupt EP is USB_ENDPOINT_IN with offset defined in hid_ep_in, which is 0 by default, unless overridden in subdriver. */ ret = usb_interrupt_read(udev, USB_ENDPOINT_IN + usb_subdriver.hid_ep_in, (char *)buf, bufsize, timeout); -#ifdef WIN32 - errno = -ret; -#endif /* WIN32 */ - /* Clear stall condition */ if (ret == -EPIPE) { ret = usb_clear_halt(udev, 0x81); diff --git a/drivers/libusb1.c b/drivers/libusb1.c index f1f68afe11..72c997808f 100644 --- a/drivers/libusb1.c +++ b/drivers/libusb1.c @@ -47,7 +47,7 @@ #endif #define USB_DRIVER_NAME "USB communication driver (libusb 1.0)" -#define USB_DRIVER_VERSION "0.55" +#define USB_DRIVER_VERSION "0.56" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -866,7 +866,7 @@ static int nut_libusb_open(libusb_device_handle **udevp, "hidusb.sys or similar). It could have been\n" "\"restored\" by Windows Update. You can try " "https://zadig.akeo.ie/ to handle it with\n" - "either WinUSB, libusb0.sys or libusbK.sys."); + "WinUSB (preferred) or libusbK.sys."); #endif /* WIN32 */ rdlen = res; /* correct rdlen if necessary */ } @@ -967,7 +967,7 @@ static int nut_libusb_open(libusb_device_handle **udevp, "hidusb.sys or similar). It could have been\n" "\"restored\" by Windows Update. You can try " "https://zadig.akeo.ie/ to handle it with\n" - "either WinUSB, libusb0.sys or libusbK.sys."); + "WinUSB (preferred) or libusbK.sys."); #endif /* WIN32 */ return -1; diff --git a/drivers/usb-common.h b/drivers/usb-common.h index c81f1750f1..51b21d697d 100644 --- a/drivers/usb-common.h +++ b/drivers/usb-common.h @@ -20,15 +20,20 @@ * Original SGML for that seems to be in source tarball such as * http://deb.debian.org/debian/pool/main/libu/libusb/libusb_0.1.12.orig.tar.gz * - * Related (but currently not directly used) projects include: - * - libusb-win32 port based on libusb-0.1 API (bug-fix-only mode, - * new projects should use libusb Windows backend): - * https://sourceforge.net/p/libusb-win32/wiki/Documentation/ - * - (Currently not in NUT codebase scope, but might help...) - * > A compatibility layer allowing applications written for - * > libusb-0.1 to work with libusb-1.0. libusb-compat-0.1 - * > attempts to look, feel, smell and walk like libusb-0.1. - * Mostly. Details (and known differences) documented at: + * Related projects: + * - On Windows, NUT uses libusb-1.0 only, whose Windows backend needs + * the device to be bound to a supported kernel driver: + * https://github.com/libusb/libusb/wiki/Windows + * - WinUSB (preferred, part of Windows): + * https://learn.microsoft.com/en-us/windows-hardware/drivers/usbcon/winusb-installation + * - libusbK (needs libusbK.dll): + * https://github.com/mcuee/libusbk + * Such a driver can be installed with libwdi (as NUT's wdi-simple + * does) or Zadig: + * https://github.com/pbatard/libwdi + * - libusb-compat-0.1 provides the libusb-0.1 API on top of libusb-1.0, + * and can stand in for libusb-0.1 on non-Windows platforms. Details + * (and known differences) are documented at: * https://github.com/libusb/libusb-compat-0.1 * * Also note that at least currently this does not deal with non-libusb @@ -402,11 +407,7 @@ # ifdef HAVE_USB_H # include # else -# ifdef HAVE_LUSB0_USB_H -# include -# else -# error "configure script error: Neither HAVE_USB_H nor HAVE_LUSB0_USB_H is set for the WITH_LIBUSB_0_1 build" -# endif +# error "configure script error: HAVE_USB_H is not set for the WITH_LIBUSB_0_1 build" # endif /* Structures */ /* See detailed comments above, in libusb-1.0 definitions diff --git a/m4/nut_check_libusb.m4 b/m4/nut_check_libusb.m4 index 0ad62d8bfb..ed9b909852 100644 --- a/m4/nut_check_libusb.m4 +++ b/m4/nut_check_libusb.m4 @@ -85,28 +85,9 @@ AC_DEFUN([NUT_CHECK_LIBUSB], AS_IF([test x"${nut_cv_LIBUSB_1_0_VERSION}" != xnone], [ nut_cv_LIBUSB_VERSION="${nut_cv_LIBUSB_1_0_VERSION}" nut_cv_usb_lib="(libusb-1.0)" - dnl ...except on Windows, where we support libusb-0.1(-compat) - dnl better so far (allow manual specification though, to let - dnl someone finally develop the on-par support), see also + dnl On Windows (mingw/MSYS2 native and cross builds) only + dnl libusb-1.0 is supported (with the WinUSB driver), see dnl https://github.com/networkupstools/nut/issues/1507 - dnl Note this may upset detection of libmodbus RTU USB support. - AS_IF([test x"${nut_cv_LIBUSB_0_1_VERSION}" != xnone], [ - AS_CASE(["${target_os}"], - [*mingw*], [ - AS_IF([test x"$build" = x"$host"], [ - AS_IF([test "${nut_with_modbus_and_usb}" = "yes"], [ - AC_MSG_NOTICE(["Normally" mingw/MSYS2 native builds prefer libusb-0.1(-compat) over libusb-1.0 if both are available, but you requested --with-modbus+usb so preferring libusb-1.0 in this build]) - ], [ - AC_MSG_NOTICE([mingw/MSYS2 native builds prefer libusb-0.1(-compat) over libusb-1.0 if both are available until https://github.com/networkupstools/nut/issues/1507 is solved]) - nut_cv_LIBUSB_VERSION="${nut_cv_LIBUSB_0_1_VERSION}" - nut_cv_usb_lib="(libusb-0.1)" - ]) - ],[ - AC_MSG_NOTICE([mingw cross-builds prefer libusb-1.0 over libusb-0.1(-compat) if both are available]) - ]) - ] - ) - ]) ], [ AS_IF([test x"${nut_cv_LIBUSB_0_1_VERSION}" != xnone], [ nut_cv_LIBUSB_VERSION="${nut_cv_LIBUSB_0_1_VERSION}" @@ -138,6 +119,9 @@ AC_DEFUN([NUT_CHECK_LIBUSB], nut_cv_usb_lib="(libusb-1.0)" ], [libusb-0.1|0.1], [ + AS_CASE(["${target_os}"], + [*mingw*], [AC_MSG_ERROR([option --with-usb=${nut_cv_with_usb} is not supported for Windows builds, please use libusb-1.0 (with the WinUSB driver), see https://github.com/networkupstools/nut/issues/1507])] + ) AS_IF([test x"${nut_cv_LIBUSB_0_1_VERSION}" = xnone \ && test x"${nut_cv_LIBUSB_CONFIG_VERSION}" = xnone], [AC_MSG_ERROR([option --with-usb=${nut_cv_with_usb} was required, but this library version was not detected]) @@ -296,33 +280,13 @@ AC_DEFUN([NUT_CHECK_LIBUSB], ]) ], [ dnl libusb 0.1, or missing pkg-config : - AC_CHECK_HEADERS(usb.h, [nut_cv_have_libusb=yes], [ - nut_cv_have_libusb=no - dnl Per https://sourceforge.net/projects/libusb-win32/files/libusb-win32-releases/1.2.6.0/ - dnl this project (used among alternatives in MSYS2/MinGW builds) - dnl uses a different include filename to avoid conflict with - dnl a WDK header: - AS_CASE(["${target_os}"], - [*mingw*], [ - AC_MSG_NOTICE([try alternate header name for mingw builds with libusb-win32]) - AC_CHECK_HEADERS(lusb0_usb.h, [ - nut_cv_usb_lib="(libusb-0.1)" - nut_cv_have_libusb=yes - ], [], [AC_INCLUDES_DEFAULT]) - ]) - ], - [AC_INCLUDES_DEFAULT]) + AC_CHECK_HEADERS(usb.h, [nut_cv_have_libusb=yes], [nut_cv_have_libusb=no], [AC_INCLUDES_DEFAULT]) AC_CHECK_FUNCS(usb_init, [], [ dnl Some systems may just have libusb in their standard dnl paths, but not the pkg-config or libusb-config data AS_IF([test "${nut_cv_have_libusb}" = "yes" && test "$LIBUSB_VERSION" = "none" && test -z "$LIBS" -o x"$LIBS" = x"-lusb" ], [ AC_MSG_CHECKING([if libusb is just present in path]) depLIBS="-L/usr/lib -L/usr/local/lib -lusb" - dnl TODO: Detect bitness for trying /mingw32 or /usr/$ARCH as well? - dnl This currently caters to mingw-w64-x86_64-libusb-win32 of MSYS2: - AS_CASE(["${target_os}"], - [*mingw*], [depLIBS="-L/mingw64/lib $depLIBS"] - ) unset ac_cv_func_usb_init || true LIBS="${LIBS_ORIG} ${depLIBS}" AC_CHECK_FUNCS(usb_init, [], [ @@ -405,6 +369,22 @@ AC_DEFUN([NUT_CHECK_LIBUSB], ]) AC_LANG_POP([C]) + dnl libusb-0.1 (libusb-win32 or libusb-compat) is not supported + dnl for Windows builds, see https://github.com/networkupstools/nut/issues/1507 + AS_IF([test "${nut_cv_have_libusb}" = "yes"], [ + AS_CASE(["${target_os}"], + [*mingw*], [ + AS_CASE(["${nut_cv_usb_lib}"], + ["(libusb-0.1)"|"(libusb-0.1-config)"], [ + AC_MSG_WARN([libusb-0.1 ${nut_cv_usb_lib} is not supported for Windows builds, please use libusb-1.0 (with the WinUSB driver), see https://github.com/networkupstools/nut/issues/1507]) + nut_cv_have_libusb=no + nut_cv_usb_lib="" + ] + ) + ] + ) + ]) + nut_cv_LIBUSB_CFLAGS="" nut_cv_LIBUSB_LIBS="" AS_IF([test "${nut_cv_have_libusb}" = "yes"], [ @@ -591,16 +571,7 @@ AC_DEFUN([NUT_CHECK_LIBUSB], AC_CHECK_FUNCS(libusb_get_version) ], [ dnl libusb 0.1, or missing pkg-config: - AC_CHECK_HEADERS(usb.h, [], [ - nut_cv_have_libusb=no - AS_CASE(["${target_os}"], - [*mingw*], [ - AC_MSG_NOTICE([try alternate header name for mingw builds with libusb-win32]) - AC_CHECK_HEADERS(lusb0_usb.h, [], [], [AC_INCLUDES_DEFAULT]) - ]) - ], - [AC_INCLUDES_DEFAULT] - ) + AC_CHECK_HEADERS(usb.h, [], [nut_cv_have_libusb=no], [AC_INCLUDES_DEFAULT]) dnl No elaborate re-detection like that done above. dnl Just set the vars about the method with LIBS already chosen. diff --git a/scripts/Windows/README.adoc b/scripts/Windows/README.adoc index 087d304a4f..dad19de3bf 100644 --- a/scripts/Windows/README.adoc +++ b/scripts/Windows/README.adoc @@ -305,16 +305,8 @@ libusb :; make :; sudo make install -* libusb-compat-0.1 (API translation layer for older codebase, uses libusb-1.0) - - :; ( cd "$DLDIR" && wget -c https://github.com/libusb/libusb-compat-0.1/archive/refs/heads/master.zip -O libusb-compat-0.1-master.zip ) - :; cd "$WSDIR" - :; unzip "$DLDIR"/libusb-compat-0.1-master.zip - :; cd libusb-compat-0.1-master - :; ./bootstrap.sh - :; ./configure --prefix="$PREFIX" $HOST_FLAG - :; make - :; sudo make install +NOTE: Only libusb-1.0 is supported for Windows builds of NUT; +libusb-0.1 (libusb-win32 or libusb-compat-0.1) is not. zlib diff --git a/tools/nut-usbinfo.pl b/tools/nut-usbinfo.pl index 87f517ad19..263d25ec9d 100755 --- a/tools/nut-usbinfo.pl +++ b/tools/nut-usbinfo.pl @@ -174,11 +174,7 @@ sub gen_usb_files print $outDevScanner "# ifdef HAVE_USB_H\n"; print $outDevScanner "# include \n"; print $outDevScanner "# else\n"; - print $outDevScanner "# ifdef HAVE_LUSB0_USB_H\n"; - print $outDevScanner "# include \n"; - print $outDevScanner "# else\n"; - print $outDevScanner "# error \"configure script error: Neither HAVE_USB_H nor HAVE_LUSB0_USB_H is set for the WITH_LIBUSB_0_1 build\"\n"; - print $outDevScanner "# endif\n"; + print $outDevScanner "# error \"configure script error: HAVE_USB_H is not set for the WITH_LIBUSB_0_1 build\"\n"; print $outDevScanner "# endif\n"; print $outDevScanner " /* simple remap to avoid bloating structures */\n"; print $outDevScanner " typedef usb_dev_handle libusb_device_handle;\n"; From 90c84cc183165b0f5118ad94b23ff38a1ea53c75 Mon Sep 17 00:00:00 2001 From: nmbro Date: Wed, 21 Oct 2026 00:11:10 +0200 Subject: [PATCH 5/8] fix(wincompat): drop libusb-win32 ETIMEDOUT fallback Value 116 came from libusb-win32 error.h; with libusb-0.1 support removed for Windows it only risks clashing with MinGW errno.h (138). Signed-off-by: nmbro --- include/wincompat.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/include/wincompat.h b/include/wincompat.h index dc90168945..e1a03ba6ef 100644 --- a/include/wincompat.h +++ b/include/wincompat.h @@ -46,14 +46,6 @@ /* These use-cases need to be revised, maybe hushed later: */ #define NUT_WIN32_INCOMPLETE_MAYBE_NOT_APPLICABLE() NUT_WIN32_INCOMPLETE_DETAILED("may be not applicable to the platform") -/* This value is defined in the error.h file of the libusb-win32 sources - * FIXME: Should only be relevant for builds WITH_LIBUSB_0_1 - #ifdef it so? - * Conflicts with e.g. /msys64/mingw64/include/errno.h which defines it as 138 - */ -#ifndef ETIMEDOUT -# define ETIMEDOUT 116 -#endif - #define random() rand() typedef const char * uid_t; struct passwd { From 9c266a9ecd9897bf34664a7e667204e3fdf39f83 Mon Sep 17 00:00:00 2001 From: nmbro Date: Wed, 21 Oct 2026 00:11:10 +0200 Subject: [PATCH 6/8] docs(install): document WinUSB/Zadig setup for USB devices Add a Windows USB section; mark the historic MSI notes as libusb-win32 era and point to the WiX installer. Refs #1485 Signed-off-by: nmbro --- INSTALL.nut.adoc | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/INSTALL.nut.adoc b/INSTALL.nut.adoc index d72d78e0bd..c472f1fe9b 100644 --- a/INSTALL.nut.adoc +++ b/INSTALL.nut.adoc @@ -948,6 +948,18 @@ Archives with binaries built by recent iterations of continuous integration jobs should be available for exploration on the respective CI platforms. ====== +[[Windows_USB]] +USB-connected devices +^^^^^^^^^^^^^^^^^^^^^ + +If you plan to use an UPS which is locally connected to an USB port, +you have to bind it to the WinUSB driver, so that NUT (built with +libusb-1.0) can open it. Most UPS devices are bound to the Windows HID +driver by default. You can use the `wdi-simple` program shipped with NUT +(see `scripts/Windows/DriverInstaller` in the sources), or +link:https://zadig.akeo.ie/[Zadig]: select the UPS, choose `WinUSB` and +click "Replace Driver". Both currently support Windows 7 and newer. + Historical MSI installer ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -967,18 +979,16 @@ Newer Windows releases should include the Windows Installer natively. Run `NUT-Installer.msi` and follow the wizard indications. -If you plan to use an UPS which is locally connected to an USB port, -you have to bind it to the WinUSB driver, so that NUT (built with -libusb-1.0) can open it. Most UPS devices are bound to the Windows HID -driver by default. You can use the `wdi-simple` program shipped with NUT -(see `scripts/Windows/DriverInstaller` in the sources), or -link:https://zadig.akeo.ie/[Zadig]: select the UPS, choose `WinUSB` and -click "Replace Driver". Both currently support Windows 7 and newer. +That historical package was built with libusb-0.1: to use a locally +connected USB UPS with it, you would install +https://github.com/mcuee/libusb-win32[libusb-win32] and bind the device +to its `libusb0.sys` driver with libusb's "Inf Wizard". Current NUT for +Windows is built with libusb-1.0 only, and does not support that driver +(see <> above instead). -NOTE: Older NUT for Windows builds used libusb-0.1 via -https://github.com/mcuee/libusb-win32[libusb-win32] and its `libusb0.sys` -driver, installed with libusb's "Inf Wizard". Current NUT for Windows is -built with libusb-1.0 only, and does not support that driver. +NOTE: A new installer based on the WiX Toolset is being worked on, see +link:scripts/Windows/Installer/README.adoc[scripts/Windows/Installer/README.adoc] +and https://github.com/networkupstools/nut/issues/1485 for details. If you have selected default directory, all configuration files are located in `C:\Program Files\NUT\ups\etc` From 93965aa79aa68bd332b121dc8b130c43c38bb912 Mon Sep 17 00:00:00 2001 From: nmbro Date: Wed, 21 Oct 2026 01:12:40 +0200 Subject: [PATCH 7/8] fix(nut-scanner): call libusb-1.0 methods with LIBUSB_CALL On 32-bit Windows, libusb-1.0 exports its methods as __stdcall (LIBUSB_CALL is WINAPI there). nut-scanner called them through default (cdecl) function pointers, which misaligned the stack: "nut-scanner -U" crashed in libusb_get_device_descriptor(). Declare the pointers (and the fallback string-descriptor method assigned to one of them) with LIBUSB_CALL for libusb-1.0 builds. It expands to nothing on other platforms, and 64-bit Windows has a single calling convention, so only 32-bit Windows changes. Signed-off-by: nmbro --- NEWS.adoc | 5 +++++ tools/nut-scanner/scan_usb.c | 41 ++++++++++++++++++++++-------------- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 3e87bbce07..47721f56f8 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -82,6 +82,11 @@ https://github.com/networkupstools/nut/milestone/13 result was stored as the availability of the XML/HTTP scan. So the `-M` scan could be disabled despite a working `libneon`, or offered without one (impacts NUT v2.8.3 through v2.8.5). [PR #3657] + - `nut-scanner` crashed in USB scans in 32-bit Windows builds: it + called the dynamically loaded libusb-1.0 methods as default C + functions, but there they use the calling convention of Windows + API methods (`LIBUSB_CALL`), so the stack got misaligned (impacts + all NUT 2.8.x until now). * USB drivers: fixed an out-of-bounds read of the candidate HID report descriptor lengths, which could cause a segmentation fault (regression added in NUT v2.8.5 release). [PR #3550] diff --git a/tools/nut-scanner/scan_usb.c b/tools/nut-scanner/scan_usb.c index b594f02816..28e109d01f 100644 --- a/tools/nut-scanner/scan_usb.c +++ b/tools/nut-scanner/scan_usb.c @@ -42,16 +42,25 @@ static lt_dlhandle dl_handle = NULL; 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_control_transfer)( +/* libusb-1.0 methods use the LIBUSB_CALL calling convention: WINAPI (i.e. + * __stdcall) on Windows, where calling them via pointers to default (cdecl) + * functions breaks the stack in 32-bit builds; libusb-0.1 uses the default */ +#if WITH_LIBUSB_1_0 && (defined LIBUSB_CALL) +# define NUT_USB_CALL LIBUSB_CALL +#else +# define NUT_USB_CALL +#endif + +static int (NUT_USB_CALL *nut_usb_close)(libusb_device_handle *dev); +static int (NUT_USB_CALL *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)( +static int (NUT_USB_CALL *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( +static int NUT_USB_CALL nut_usb_get_string_with_langid_control_transfer( libusb_device_handle *dev, int index, int langid, char *buf, size_t buflen); @@ -73,19 +82,19 @@ static int nut_usb_get_string_with_langid_control_transfer( # define USB_OPEN_SYMBOL "libusb_open" # define USB_CLOSE_SYMBOL "libusb_close" # define USB_STRERROR_SYMBOL "libusb_strerror" - static int (*nut_usb_open)(libusb_device *dev, libusb_device_handle **handle); - static int (*nut_usb_init)(libusb_context **ctx); - static void (*nut_usb_exit)(libusb_context *ctx); - static char * (*nut_usb_strerror)(enum libusb_error errcode); - static ssize_t (*nut_usb_get_device_list)(libusb_context *ctx, libusb_device ***list); - static void (*nut_usb_free_device_list)(libusb_device **list, int unref_devices); - static uint8_t (*nut_usb_get_bus_number)(libusb_device *dev); - static uint8_t (*nut_usb_get_device_address)(libusb_device *dev); - static uint8_t (*nut_usb_get_port_number)(libusb_device *dev); - static int (*nut_usb_get_device_descriptor)( + static int (NUT_USB_CALL *nut_usb_open)(libusb_device *dev, libusb_device_handle **handle); + static int (NUT_USB_CALL *nut_usb_init)(libusb_context **ctx); + static void (NUT_USB_CALL *nut_usb_exit)(libusb_context *ctx); + static char * (NUT_USB_CALL *nut_usb_strerror)(enum libusb_error errcode); + static ssize_t (NUT_USB_CALL *nut_usb_get_device_list)(libusb_context *ctx, libusb_device ***list); + static void (NUT_USB_CALL *nut_usb_free_device_list)(libusb_device **list, int unref_devices); + static uint8_t (NUT_USB_CALL *nut_usb_get_bus_number)(libusb_device *dev); + static uint8_t (NUT_USB_CALL *nut_usb_get_device_address)(libusb_device *dev); + static uint8_t (NUT_USB_CALL *nut_usb_get_port_number)(libusb_device *dev); + static int (NUT_USB_CALL *nut_usb_get_device_descriptor)( libusb_device *dev, struct libusb_device_descriptor *desc); - static const struct libusb_version * (*nut_usb_get_version)(void); + static const struct libusb_version * (NUT_USB_CALL *nut_usb_get_version)(void); # define USB_DT_STRING LIBUSB_DT_STRING # define USB_ENDPOINT_IN LIBUSB_ENDPOINT_IN # define USB_REQ_GET_DESCRIPTOR LIBUSB_REQUEST_GET_DESCRIPTOR @@ -364,7 +373,7 @@ static char* is_usb_device_supported( * 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( +static int NUT_USB_CALL nut_usb_get_string_with_langid_control_transfer( libusb_device_handle *dev, int index, int langid, char *buf, size_t buflen) { From 40b62497d59796cff1379ed233df843eb2683150 Mon Sep 17 00:00:00 2001 From: nmbro Date: Wed, 21 Oct 2026 18:28:28 +0200 Subject: [PATCH 8/8] NEWS.adoc: refer to PR #3782 for its libusb entries The libusb-1.0.9 and libusb-0.1 fallback entries come from the commits of PR #3782, which this branch builds on; use the same references there and here, so either PR merges cleanly after the other. Co-Authored-By: Claude Opus 5.5 Signed-off-by: nmbro --- NEWS.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index 4e33b5fa9a..ca7e5c8eb5 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -1129,12 +1129,12 @@ https://github.com/networkupstools/nut/milestone/13 description; before 1.0.10 or so there is no `libusb_get_version()`, so `driver.version.usb` says the version is unknown. `nut-scanner` falls back the same way when the libusb it loads at run time lacks - these methods. [issues #509, #3781, PR #3791] + these methods. [issues #509, #3781, PR #3782] * With `--with-usb=auto` (or `yes`), the `configure` script now falls back to an also available libusb-0.1 when the preferred libusb-1.0 is too old for NUT, instead of building without USB drivers. An explicit `--with-usb=libusb-1.0` still fails in that case. - [issue #3781, PR #3791] + [issue #3781, PR #3782] * Windows (MinGW/MSYS2) builds of NUT now support only libusb-1.0 for USB drivers, used with the WinUSB driver. Support for libusb-0.1 (libusb-win32 with its `libusb0.sys` driver, or libusb-compat) was