diff --git a/NEWS.adoc b/NEWS.adoc index 814bc79ff6..c45c3ee4ba 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -114,6 +114,10 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. seems to have confused the MGE SHUT (Serial HID UPS Transfer) driver support [#2022] + - An issue was identified which could cause `libupsclient` parser of device + and host names to crash upon bad inputs (e.g. poorly resolved environment + variables in scripts). Now it should fail more gracefully [#2052] + - New `configure --enable-inplace-runtime` option should set default values for `--sysconfdir`, `--with-user` and `--with-group` options to match an existing NUT deployment -- for users who are trying if a custom build @@ -233,6 +237,11 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. of all USB drivers using these options to include the same description [#1766] + - Added a "busport" USB matching option (if supported by the hardware, OS and + libusb on the particular deployment, it should allow to specify physical + port numbers on an USB hub, rather than logical "device" enumeration values, + and in turn -- this should be less volatile across reboots etc.) [#2043] + - Added an `allow_duplicates` flag for common USB matching options which may help monitor several related no-name devices (although without knowing reliably which one is which... better than nothing) [#1756] diff --git a/clients/upsclient.c b/clients/upsclient.c index 6a823fa57c..cd30fedf67 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -1161,7 +1161,7 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags pconf_init(&ups->pc_ctx, NULL); - ups->host = strdup(host); + ups->host = xstrdup(host); if (!ups->host) { ups->upserror = UPSCLI_ERR_NOMEM; @@ -1618,15 +1618,32 @@ int upscli_splitname(const char *buf, char **upsname, char **hostname, uint16_t s = strchr(tmp, '@'); - if ((*upsname = strdup(strtok_r(tmp, "@", &last))) == NULL) { - fprintf(stderr, "upscli_splitname: strdup failed\n"); + /* someone passed a "@hostname" string? */ + if (s == tmp) { + fprintf(stderr, "upscli_splitname: got empty upsname string\n"); return -1; } + if ((*upsname = xstrdup(strtok_r(tmp, "@", &last))) == NULL) { + fprintf(stderr, "upscli_splitname: xstrdup failed\n"); + return -1; + } + + /* someone passed a "@hostname" string (take two)? */ + if (!**upsname) { + fprintf(stderr, "upscli_splitname: got empty upsname string\n"); + return -1; + } + + /* + fprintf(stderr, "upscli_splitname3: got buf='%s', tmp='%s', upsname='%s', possible hostname:port='%s'\n", + NUT_STRARG(buf), NUT_STRARG(tmp), NUT_STRARG(*upsname), NUT_STRARG((s ? s+1 : s))); + */ + /* only a upsname is specified, fill in defaults */ if (s == NULL) { - if ((*hostname = strdup("localhost")) == NULL) { - fprintf(stderr, "upscli_splitname: strdup failed\n"); + if ((*hostname = xstrdup("localhost")) == NULL) { + fprintf(stderr, "upscli_splitname: xstrdup failed\n"); return -1; } @@ -1634,6 +1651,12 @@ int upscli_splitname(const char *buf, char **upsname, char **hostname, uint16_t return 0; } + /* someone passed a "upsname@" string? */ + if (!(*(s+1))) { + fprintf(stderr, "upscli_splitname: got the @ separator and then an empty hostname[:port] string\n"); + return -1; + } + return upscli_splitaddr(s+1, hostname, port); } @@ -1659,8 +1682,8 @@ int upscli_splitaddr(const char *buf, char **hostname, uint16_t *port) return -1; } - if ((*hostname = strdup(strtok_r(tmp+1, "]", &last))) == NULL) { - fprintf(stderr, "upscli_splitaddr: strdup failed\n"); + if ((*hostname = xstrdup(strtok_r(tmp+1, "]", &last))) == NULL) { + fprintf(stderr, "upscli_splitaddr: xstrdup failed\n"); return -1; } @@ -1672,8 +1695,8 @@ int upscli_splitaddr(const char *buf, char **hostname, uint16_t *port) } else { s = strchr(tmp, ':'); - if ((*hostname = strdup(strtok_r(tmp, ":", &last))) == NULL) { - fprintf(stderr, "upscli_splitaddr: strdup failed\n"); + if ((*hostname = xstrdup(strtok_r(tmp, ":", &last))) == NULL) { + fprintf(stderr, "upscli_splitaddr: xstrdup failed\n"); return -1; } diff --git a/common/common.c b/common/common.c index 0864f28b23..f96584db60 100644 --- a/common/common.c +++ b/common/common.c @@ -1738,7 +1738,14 @@ void *xrealloc(void *ptr, size_t size) char *xstrdup(const char *string) { - char *p = strdup(string); + char *p; + + if (string == NULL) { + upsdebugx(1, "%s: got null input", __func__); + return NULL; + } + + p = strdup(string); if (p == NULL) fatal_with_errno(EXIT_FAILURE, "%s", oom_msg); diff --git a/docs/man/nut_usb_addvars.txt b/docs/man/nut_usb_addvars.txt index c9a9c5d1d9..b96b252d8d 100644 --- a/docs/man/nut_usb_addvars.txt +++ b/docs/man/nut_usb_addvars.txt @@ -56,18 +56,30 @@ Examples: Select a UPS on a specific USB bus or group of buses. The argument is a regular expression that must match the bus name where the UPS is -connected (e.g. `bus="002"` or `bus="00[2-3]"`) as seen in -`/proc/bus/usb/devices` or *lsusb(8)*; including leading zeroes. +connected (e.g. `bus="002"` or `bus="00[2-3]"`) as seen on Linux in +`/sys/bus/usb/devices` or *lsusb(8)*; including leading zeroes. *device =* 'regex':: Select a UPS on a specific USB device or group of devices. The argument is a regular expression that must match the device name where the UPS is -connected (e.g. `device="001"` or `device="00[1-2]"`) as seen in -`/proc/bus/usb/devices` or *lsusb(8)*; including leading zeroes. -Note that device numbers are not guaranteed by the OS to be stable across +connected (e.g. `device="001"` or `device="00[1-2]"`) as seen on Linux +in `/sys/bus/usb/devices` or *lsusb(8)*; including leading zeroes. ++ +NOTE: device numbers are not guaranteed by the OS to be stable across re-boots or device re-plugging. +*busport =* 'regex':: + +If supported by the hardware, OS and libusb on the particular deployment, +this option should allow to specify physical port numbers on an USB hub, +rather than logical `device` enumeration values, and in turn -- this should +be less volatile across reboots or re-plugging. ++ +NOTE: this option is not practically supported by some NUT builds +(it should be ignored with a warning then), and not by all systems +that NUT can run on. + *allow_duplicates*:: If you have several UPS devices which may not be uniquely identified by diff --git a/docs/nut.dict b/docs/nut.dict index 9a2bc36a82..9e477f30c7 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3224 utf-8 +personal_ws-1.1 en 3225 utf-8 AAS ABI ACFAIL @@ -1641,6 +1641,7 @@ bugfixes buildbots builddir bullseye +busport busybox bv bypassvolts diff --git a/drivers/blazer_usb.c b/drivers/blazer_usb.c index 6cb3337ad7..2bc9c28258 100644 --- a/drivers/blazer_usb.c +++ b/drivers/blazer_usb.c @@ -37,7 +37,7 @@ #endif #define DRIVER_NAME "Megatec/Q1 protocol USB driver" -#define DRIVER_VERSION "0.16" +#define DRIVER_VERSION "0.17" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -597,7 +597,7 @@ void upsdrv_initups(void) #ifndef TESTING int ret, langid; char tbuf[255]; /* Some devices choke on size > 255 */ - char *regex_array[7]; + char *regex_array[USBMATCHER_REGEXP_ARRAY_LIMIT]; char *subdrv = getval("subdriver"); warn_if_bad_usb_port_filename(device_path); @@ -609,6 +609,13 @@ void upsdrv_initups(void) regex_array[4] = getval("serial"); regex_array[5] = getval("bus"); regex_array[6] = getval("device"); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + regex_array[7] = getval("busport"); +# else + if (getval("busport")) { + upslogx(LOG_WARNING, "\"busport\" is configured for the device, but is not actually handled by current build combination of NUT and libusb (ignored)"); + } +# endif /* check for language ID workaround (#1) */ if (getval("langid_fix")) { @@ -725,5 +732,8 @@ void upsdrv_cleanup(void) free(usbdevice.Serial); free(usbdevice.Bus); free(usbdevice.Device); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(usbdevice.BusPort); +# endif #endif /* TESTING */ } diff --git a/drivers/libshut.c b/drivers/libshut.c index 79acd0ade8..3601c0c344 100644 --- a/drivers/libshut.c +++ b/drivers/libshut.c @@ -43,7 +43,7 @@ #include "common.h" /* for xmalloc, upsdebugx prototypes */ #define SHUT_DRIVER_NAME "SHUT communication driver" -#define SHUT_DRIVER_VERSION "0.87" +#define SHUT_DRIVER_VERSION "0.88" /* communication driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -468,14 +468,26 @@ static int libshut_open( free(curDevice->Serial); free(curDevice->Bus); free(curDevice->Device); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(curDevice->BusPort); +#endif memset(curDevice, '\0', sizeof(*curDevice)); curDevice->VendorID = dev_descriptor->idVendor; curDevice->ProductID = dev_descriptor->idProduct; curDevice->Bus = strdup("serial"); curDevice->Device = strdup(arg_device_path); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + curDevice->BusPort = (char *)malloc(4); + if (curDevice->BusPort == NULL) { + fatal_with_errno(EXIT_FAILURE, "Out of memory"); + } + upsdebugx(2, "%s: NOTE: BusPort is always zero with libshut", __func__); + sprintf(curDevice->BusPort, "%03d", 0); +#endif + curDevice->bcdDevice = dev_descriptor->bcdDevice; - curDevice->Vendor = strdup("Eaton"); + curDevice->Vendor = NULL; if (dev_descriptor->iManufacturer) { ret = shut_get_string_simple(*arg_upsfd, dev_descriptor->iManufacturer, string, MAX_STRING_SIZE); @@ -483,6 +495,9 @@ static int libshut_open( curDevice->Vendor = strdup(string); } } + if (curDevice->Vendor == NULL) { + curDevice->Vendor = strdup("Eaton"); + } /* ensure iProduct retrieval */ if (dev_descriptor->iProduct) { @@ -514,6 +529,9 @@ static int libshut_open( upsdebugx(2, "- Product: %s", curDevice->Product); upsdebugx(2, "- Serial Number: %s", curDevice->Serial); upsdebugx(2, "- Bus: %s", curDevice->Bus); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + upsdebugx(2, "- Bus Port: %s", curDevice->BusPort ? curDevice->BusPort : "unknown"); +#endif upsdebugx(2, "- Device: %s", curDevice->Device ? curDevice->Device : "unknown"); upsdebugx(2, "- Device release number: %04x", curDevice->bcdDevice); upsdebugx(2, "Device matches"); diff --git a/drivers/libshut.h b/drivers/libshut.h index 84da1af1ed..caa0780ce8 100644 --- a/drivers/libshut.h +++ b/drivers/libshut.h @@ -116,7 +116,7 @@ typedef int usb_ctrl_timeout_msec; /* in milliseconds */ /*! * SHUTDevice_t: Describe a SHUT device. This structure contains exactly - * the 5 pieces of information by which a SHUT device identifies + * the 5 or more pieces of information by which a SHUT device identifies * itself, so it serves as a kind of "fingerprint" of the device. This * information must be matched exactly when reopening a device, and * therefore must not be "improved" or updated by a client @@ -132,6 +132,9 @@ typedef struct SHUTDevice_s { char* Bus; /*!< Bus name, e.g. "003" */ uint16_t bcdDevice; /*!< Device release number */ char *Device; /*!< Device name on the bus, e.g. "001" */ +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + char *BusPort; /*!< Port name, e.g. "001" */ +#endif } SHUTDevice_t; /*! diff --git a/drivers/libusb0.c b/drivers/libusb0.c index b07a363c0c..86072bbba7 100644 --- a/drivers/libusb0.c +++ b/drivers/libusb0.c @@ -37,7 +37,7 @@ #endif #define USB_DRIVER_NAME "USB communication driver (libusb 0.1)" -#define USB_DRIVER_VERSION "0.44" +#define USB_DRIVER_VERSION "0.45" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -76,6 +76,11 @@ void nut_usb_addvars(void) addvar(VAR_VALUE, "bus", "Regular expression to match USB bus name"); addvar(VAR_VALUE, "device", "Regular expression to match USB device name"); + /* Not supported by libusb0, but let's not crash config + * parsing on unknown keywords due to such nuances! :) */ + addvar(VAR_VALUE, "busport", "Regular expression to match USB bus port name" + " (tolerated but ignored in this build)" + ); /* Warning: this feature is inherently non-deterministic! * If you only care to know that at least one of your no-name UPSes is online, @@ -287,6 +292,9 @@ static int libusb_open(usb_dev_handle **udevp, free(curDevice->Serial); free(curDevice->Bus); free(curDevice->Device); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(curDevice->BusPort); +#endif memset(curDevice, '\0', sizeof(*curDevice)); /* Keep the list of items in sync with those matched by @@ -298,6 +306,15 @@ static int libusb_open(usb_dev_handle **udevp, curDevice->Device = xstrdup(dev->filename); curDevice->bcdDevice = dev->descriptor.bcdDevice; +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + curDevice->BusPort = (char *)malloc(4); + if (curDevice->BusPort == NULL) { + fatal_with_errno(EXIT_FAILURE, "Out of memory"); + } + upsdebugx(2, "%s: NOTE: BusPort is always zero with libusb0", __func__); + sprintf(curDevice->BusPort, "%03d", 0); +#endif + if (dev->descriptor.iManufacturer) { retries = MAX_RETRY; while (retries > 0) { @@ -347,6 +364,9 @@ static int libusb_open(usb_dev_handle **udevp, upsdebugx(2, "- Serial Number: %s", curDevice->Serial ? curDevice->Serial : "unknown"); upsdebugx(2, "- Bus: %s", curDevice->Bus ? curDevice->Bus : "unknown"); upsdebugx(2, "- Device: %s", curDevice->Device ? curDevice->Device : "unknown"); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + upsdebugx(2, "- Bus Port: %s", curDevice->BusPort ? curDevice->BusPort : "unknown"); +#endif upsdebugx(2, "- Device release number: %04x", curDevice->bcdDevice); /* FIXME: extend to Eaton OEMs (HP, IBM, ...) */ diff --git a/drivers/libusb1.c b/drivers/libusb1.c index 522d8a183d..84d5ebdbc3 100644 --- a/drivers/libusb1.c +++ b/drivers/libusb1.c @@ -33,7 +33,7 @@ #include "nut_stdint.h" #define USB_DRIVER_NAME "USB communication driver (libusb 1.0)" -#define USB_DRIVER_VERSION "0.45" +#define USB_DRIVER_VERSION "0.46" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -66,6 +66,15 @@ void nut_usb_addvars(void) addvar(VAR_VALUE, "bus", "Regular expression to match USB bus name"); addvar(VAR_VALUE, "device", "Regular expression to match USB device name"); + addvar(VAR_VALUE, "busport", "Regular expression to match USB bus port name" +#if (!defined WITH_USB_BUSPORT) || (!WITH_USB_BUSPORT) + /* Not supported by this version of libusb1, + * but let's not crash config parsing on + * unknown keywords due to such nuances! :) + */ + " (tolerated but ignored in this build)" +#endif + ); /* Warning: this feature is inherently non-deterministic! * If you only care to know that at least one of your no-name UPSes is online, @@ -163,6 +172,9 @@ static int nut_libusb_open(libusb_device_handle **udevp, const struct libusb_interface_descriptor *if_desc; libusb_device_handle *udev; uint8_t bus_num, device_addr; +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + uint8_t bus_port; +#endif int ret, res; unsigned char buf[20]; const unsigned char *p; @@ -237,6 +249,9 @@ static int nut_libusb_open(libusb_device_handle **udevp, free(curDevice->Serial); free(curDevice->Bus); free(curDevice->Device); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(curDevice->BusPort); +#endif memset(curDevice, '\0', sizeof(*curDevice)); /* Keep the list of items in sync with those matched by @@ -278,6 +293,21 @@ static int nut_libusb_open(libusb_device_handle **udevp, } } +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + bus_port = libusb_get_port_number(device); + curDevice->BusPort = (char *)malloc(4); + if (curDevice->BusPort == NULL) { + libusb_free_device_list(devlist, 1); + fatal_with_errno(EXIT_FAILURE, "Out of memory"); + } + if (bus_port > 0) { + sprintf(curDevice->BusPort, "%03d", bus_port); + } else { + upsdebugx(1, "%s: invalid libusb bus number %i", + __func__, bus_port); + } +#endif + curDevice->VendorID = dev_desc.idVendor; curDevice->ProductID = dev_desc.idProduct; curDevice->bcdDevice = dev_desc.bcdDevice; @@ -342,6 +372,9 @@ static int nut_libusb_open(libusb_device_handle **udevp, upsdebugx(2, "- Product: %s", curDevice->Product ? curDevice->Product : "unknown"); upsdebugx(2, "- Serial Number: %s", curDevice->Serial ? curDevice->Serial : "unknown"); upsdebugx(2, "- Bus: %s", curDevice->Bus ? curDevice->Bus : "unknown"); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + upsdebugx(2, "- Bus Port: %s", curDevice->BusPort ? curDevice->BusPort : "unknown"); +#endif upsdebugx(2, "- Device: %s", curDevice->Device ? curDevice->Device : "unknown"); upsdebugx(2, "- Device release number: %04x", curDevice->bcdDevice); diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index 83ab443a87..f394ce5213 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -57,7 +57,7 @@ #define DRIVER_NAME "Generic Q* Serial driver" #endif /* QX_USB */ -#define DRIVER_VERSION "0.35" +#define DRIVER_VERSION "0.36" #ifdef QX_SERIAL #include "serial.h" @@ -3016,6 +3016,9 @@ void upsdrv_initups(void) getval("serial") || getval("bus") || getval("langid_fix") +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + || getval("busport") +#endif ) { /* USB */ is_usb = 1; @@ -3107,10 +3110,10 @@ void upsdrv_initups(void) warn_if_bad_usb_port_filename(device_path); - #ifndef TESTING +# ifndef TESTING int ret, langid; char tbuf[255]; /* Some devices choke on size > 255 */ - char *regex_array[7]; + char *regex_array[USBMATCHER_REGEXP_ARRAY_LIMIT]; char *subdrv = getval("subdriver"); @@ -3121,6 +3124,13 @@ void upsdrv_initups(void) regex_array[4] = getval("serial"); regex_array[5] = getval("bus"); regex_array[6] = getval("device"); +# if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + regex_array[7] = getval("busport"); +# else + if (getval("busport")) { + upslogx(LOG_WARNING, "\"busport\" is configured for the device, but is not actually handled by current build combination of NUT and libusb (ignored)"); + } +# endif /* Check for language ID workaround (#1) */ if (getval("langid_fix")) { @@ -3236,11 +3246,11 @@ void upsdrv_initups(void) } } - #endif /* TESTING */ +# endif /* TESTING */ - #ifdef QX_SERIAL +# ifdef QX_SERIAL } /* is_usb */ - #endif /* QX_SERIAL */ +# endif /* QX_SERIAL */ #endif /* QX_USB */ @@ -3260,23 +3270,22 @@ void upsdrv_cleanup(void) #ifndef TESTING -#ifdef QX_SERIAL +# ifdef QX_SERIAL - #ifdef QX_USB +# ifdef QX_USB if (!is_usb) { - #endif /* QX_USB */ +# endif /* QX_USB */ ser_set_dtr(upsfd, 0); ser_close(upsfd, device_path); - #ifdef QX_USB +# ifdef QX_USB } else { /* is_usb */ - #endif /* QX_USB */ +# endif /* QX_USB */ -#endif /* QX_SERIAL */ - -#ifdef QX_USB +# endif /* QX_SERIAL */ +# ifdef QX_USB usb->close_dev(udev); USBFreeExactMatcher(reopen_matcher); USBFreeRegexMatcher(regex_matcher); @@ -3285,12 +3294,15 @@ void upsdrv_cleanup(void) free(usbdevice.Serial); free(usbdevice.Bus); free(usbdevice.Device); +# if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(usbdevice.BusPort); +# endif - #ifdef QX_SERIAL +# ifdef QX_SERIAL } /* is_usb */ - #endif /* QX_SERIAL */ +# endif /* QX_SERIAL */ -#endif /* QX_USB */ +# endif /* QX_USB */ #endif /* TESTING */ diff --git a/drivers/riello_usb.c b/drivers/riello_usb.c index b0c6a51b5a..1a7fe7d4f3 100644 --- a/drivers/riello_usb.c +++ b/drivers/riello_usb.c @@ -34,7 +34,7 @@ #include "riello.h" #define DRIVER_NAME "Riello USB driver" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.10" #define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */ #define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */ @@ -851,7 +851,7 @@ void upsdrv_initups(void) }; int ret; - char *regex_array[7]; + char *regex_array[USBMATCHER_REGEXP_ARRAY_LIMIT]; char *subdrv = getval("subdriver"); @@ -864,6 +864,13 @@ void upsdrv_initups(void) regex_array[4] = getval("serial"); regex_array[5] = getval("bus"); regex_array[6] = getval("device"); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + regex_array[7] = getval("busport"); +#else + if (getval("busport")) { + upslogx(LOG_WARNING, "\"busport\" is configured for the device, but is not actually handled by current build combination of NUT and libusb (ignored)"); + } +#endif /* pick up the subdriver name if set explicitly */ if (subdrv) { @@ -1233,4 +1240,7 @@ void upsdrv_cleanup(void) free(usbdevice.Serial); free(usbdevice.Bus); free(usbdevice.Device); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(usbdevice.BusPort); +#endif } diff --git a/drivers/tripplite_usb.c b/drivers/tripplite_usb.c index 84508f8f2e..858b93dcd0 100644 --- a/drivers/tripplite_usb.c +++ b/drivers/tripplite_usb.c @@ -1568,7 +1568,7 @@ void upsdrv_makevartable(void) */ void upsdrv_initups(void) { - char *regex_array[7]; + char *regex_array[USBMATCHER_REGEXP_ARRAY_LIMIT]; char *value; int r; @@ -1582,6 +1582,9 @@ void upsdrv_initups(void) regex_array[4] = getval("serial"); /* probably won't see this */ regex_array[5] = getval("bus"); regex_array[6] = getval("device"); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + regex_array[7] = getval("busport"); +#endif r = USBNewRegexMatcher(®ex_matcher, regex_array, REG_ICASE | REG_EXTENDED); if (r==-1) { @@ -1656,4 +1659,7 @@ void upsdrv_cleanup(void) free(curDevice.Serial); free(curDevice.Bus); free(curDevice.Device); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(curDevice.BusPort); +#endif } diff --git a/drivers/usb-common.c b/drivers/usb-common.c index a45a621168..d3bc96748b 100644 --- a/drivers/usb-common.c +++ b/drivers/usb-common.c @@ -119,6 +119,15 @@ static int match_function_exact(USBDevice_t *hd, void *privdata) return 0; } #endif +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + #ifdef DEBUG_EXACT_MATCH_BUSPORT + if (strcmp_null(hd->BusPort, data->BusPort) != 0) { + upsdebugx(2, "%s: failed match of %s: %s != %s", + __func__, "BusPort", hd->BusPort, data->BusPort); + return 0; + } + #endif +#endif #ifdef DEBUG_EXACT_MATCH_DEVICE if (strcmp_null(hd->Device, data->Device) != 0) { upsdebugx(2, "%s: failed match of %s: %s != %s", @@ -300,7 +309,7 @@ static int match_regex_hex(regex_t *preg, int n) /* private data type: hold a set of compiled regular expressions. */ typedef struct regex_matcher_data_s { - regex_t *regex[7]; + regex_t *regex[USBMATCHER_REGEXP_ARRAY_LIMIT]; } regex_matcher_data_t; /* private callback function for regex matches */ @@ -390,6 +399,19 @@ static int match_function_regex(USBDevice_t *hd, void *privdata) __func__, "Device", hd->Device); return r; } + +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + r = match_regex(data->regex[7], hd->BusPort); + if (r != 1) { +/* + upsdebugx(2, "%s: failed match of %s: %s !~ %s", + __func__, "Device", hd->Device, data->regex[6]); +*/ + upsdebugx(2, "%s: failed match of %s: %s", + __func__, "Bus Port", hd->BusPort); + return r; + } +#endif return 1; } @@ -426,7 +448,7 @@ int USBNewRegexMatcher(USBDeviceMatcher_t **matcher, char **regex, int cflags) m->privdata = (void *)data; m->next = NULL; - for (i=0; i<7; i++) { + for (i=0; i < USBMATCHER_REGEXP_ARRAY_LIMIT; i++) { r = compile_regex(&data->regex[i], regex[i], cflags); if (r == -2) { r = i+1; @@ -453,7 +475,7 @@ void USBFreeRegexMatcher(USBDeviceMatcher_t *matcher) data = (regex_matcher_data_t *)matcher->privdata; - for (i = 0; i < 7; i++) { + for (i = 0; i < USBMATCHER_REGEXP_ARRAY_LIMIT; i++) { if (!data->regex[i]) { continue; } diff --git a/drivers/usb-common.h b/drivers/usb-common.h index a3513adf0d..d1de6d23d5 100644 --- a/drivers/usb-common.h +++ b/drivers/usb-common.h @@ -460,6 +460,11 @@ /* USB standard timeout [ms] */ #define USB_TIMEOUT 5000 +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) +# define USBMATCHER_REGEXP_ARRAY_LIMIT 8 +#else +# define USBMATCHER_REGEXP_ARRAY_LIMIT 7 +#endif /*! * USBDevice_t: Describe a USB device. This structure contains exactly @@ -482,6 +487,9 @@ typedef struct USBDevice_s { char *Bus; /*!< Bus name, e.g. "003" */ uint16_t bcdDevice; /*!< Device release number */ char *Device; /*!< Device name on the bus, e.g. "001" */ +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + char *BusPort; /*!< Port name, e.g. "001" */ +#endif } USBDevice_t; /*! diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 2074970865..36748c66fb 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -28,7 +28,7 @@ */ #define DRIVER_NAME "Generic HID driver" -#define DRIVER_VERSION "0.50" +#define DRIVER_VERSION "0.51" #define HU_VAR_WAITBEFORERECONNECT "waitbeforereconnect" @@ -118,7 +118,11 @@ static subdriver_t *subdriver = NULL; /* Global vars */ static HIDDevice_t *hd = NULL; -static HIDDevice_t curDevice = { 0x0000, 0x0000, NULL, NULL, NULL, NULL, 0, NULL }; +static HIDDevice_t curDevice = { 0x0000, 0x0000, NULL, NULL, NULL, NULL, 0, NULL +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + , NULL +#endif +}; static HIDDeviceMatcher_t *subdriver_matcher = NULL; #if !((defined SHUT_MODE) && SHUT_MODE) static HIDDeviceMatcher_t *exact_matcher = NULL; @@ -1023,7 +1027,7 @@ void upsdrv_initups(void) subdriver_matcher = device_path; #else /* !SHUT_MODE => USB */ - char *regex_array[7]; + char *regex_array[USBMATCHER_REGEXP_ARRAY_LIMIT]; upsdebugx(1, "upsdrv_initups (non-SHUT)..."); @@ -1054,6 +1058,13 @@ void upsdrv_initups(void) regex_array[4] = getval("serial"); regex_array[5] = getval("bus"); regex_array[6] = getval("device"); +#if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + regex_array[7] = getval("busport"); +#else + if (getval("busport")) { + upslogx(LOG_WARNING, "\"busport\" is configured for the device, but is not actually handled by current build combination of NUT and libusb (ignored)"); + } +#endif ret = USBNewRegexMatcher(®ex_matcher, regex_array, REG_ICASE | REG_EXTENDED); switch(ret) @@ -1177,6 +1188,9 @@ void upsdrv_cleanup(void) free(curDevice.Serial); free(curDevice.Bus); free(curDevice.Device); +# if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + free(curDevice.BusPort); +# endif #endif /* !SHUT_MODE => USB */ } diff --git a/m4/nut_check_libusb.m4 b/m4/nut_check_libusb.m4 index 98b0ad0c59..5b042b0790 100644 --- a/m4/nut_check_libusb.m4 +++ b/m4/nut_check_libusb.m4 @@ -286,6 +286,7 @@ if test -z "${nut_have_libusb_seen}"; then nut_have_libusb=no fi + nut_with_usb_busport=no AS_IF([test "${nut_have_libusb}" = "yes"], [ dnl ---------------------------------------------------------------------- dnl additional USB-related checks @@ -318,12 +319,27 @@ if test -z "${nut_have_libusb_seen}"; then ] ) + dnl AC_MSG_CHECKING([for libusb bus port support]) + dnl Per https://github.com/networkupstools/nut/issues/2043#issuecomment-1721856494 : + dnl #if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000102) + 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]) + AS_IF([test x"${nut_with_usb_busport}" = xyes], [ + AC_DEFINE(WITH_USB_BUSPORT, 1, + [Define to 1 for libusb versions where we can support "busport" USB matching value.]) + ], [ + AC_DEFINE(WITH_USB_BUSPORT, 0, + [Define to 1 for libusb versions where we can support "busport" USB matching value.]) + ]) + AS_IF([test "${nut_have_libusb}" = "yes"], [ LIBUSB_CFLAGS="${CFLAGS}" LIBUSB_LIBS="${LIBS}" diff --git a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in index b237c03423..2d2c126dbb 100755 --- a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in +++ b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in @@ -490,7 +490,7 @@ upsconf_getDriverMedia() { printf '%s\n%s\n' "$CURR_DRV" "usb" ; return ;; /dev/*) # See drivers/nutdrv_qx.c :: upsdrv_initups() for a list - if [ -n "`upsconf_getValue "$1" 'subdriver' 'vendorid' 'productid' 'vendor' 'product' 'serial' 'bus' 'langid_fix'`" ] \ + if [ -n "`upsconf_getValue "$1" 'subdriver' 'vendorid' 'productid' 'vendor' 'product' 'serial' 'bus' 'busport' 'langid_fix'`" ] \ ; then printf '%s\n%s\n' "$CURR_DRV" "usb" ; return else diff --git a/tools/nut-scanner/scan_usb.c b/tools/nut-scanner/scan_usb.c index 2a8c116bfe..a56c54cf8a 100644 --- a/tools/nut-scanner/scan_usb.c +++ b/tools/nut-scanner/scan_usb.c @@ -54,6 +54,7 @@ static int (*nut_usb_get_string_simple)(libusb_device_handle *dev, int index, 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)(libusb_device *dev, struct libusb_device_descriptor *desc); #else /* => WITH_LIBUSB_0_1 */ @@ -157,6 +158,20 @@ int nutscan_load_usb_library(const char *libname_path) goto err; } + /* This method may be absent in some libusb versions, and we should + * tolerate that! In run-time driver code see also blocks fenced by: + * #if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) + */ + *(void **) (&nut_usb_get_port_number) = lt_dlsym(dl_handle, + "libusb_get_port_number"); + if ((dl_error = lt_dlerror()) != NULL) { + fprintf(stderr, + "While loading USB library (%s), failed to find libusb_get_port_number() : %s. " + "The \"busport\" USB matching option will be disabled.\n", + libname_path, dl_error); + nut_usb_get_port_number = NULL; + } + *(void **) (&nut_usb_get_device_descriptor) = lt_dlsym(dl_handle, "libusb_get_device_descriptor"); if ((dl_error = lt_dlerror()) != NULL) { @@ -251,7 +266,8 @@ nutscan_device_t * nutscan_scan_usb() /* device_port physical meaning: connection port on that bus; * different consumers plugged into same socket should have * the same port value. However in practice such functionality - * depends on platform and HW involved. + * depends on platform and HW involved and may mean logical + * enumeration results. * In libusb1 API: first libusb_get_port_numbers() earlier known * as libusb_get_port_path() for physical port number on the bus, see * https://libusb.sourceforge.io/api-1.0/group__libusb__dev.html#ga14879a0ea7daccdcddb68852d86c00c4 @@ -267,6 +283,12 @@ nutscan_device_t * nutscan_scan_usb() libusb_device *dev; libusb_device **devlist; uint8_t bus_num; + /* Sort of like device_port above, but different (should be + * more closely about physical port number than logical device + * enumeration results). Uses libusb_get_port_number() where + * available in libusb (and hoping the OS and HW honour it). + */ + char *bus_port = NULL; #else /* => WITH_LIBUSB_0_1 */ struct usb_device *dev; struct usb_bus *bus; @@ -339,6 +361,22 @@ nutscan_device_t * nutscan_scan_usb() } } + if (nut_usb_get_port_number != NULL) { + bus_port = (char *)malloc(4); + if (bus_port == NULL) { + (*nut_usb_free_device_list)(devlist, 1); + (*nut_usb_exit)(NULL); + fatal_with_errno(EXIT_FAILURE, "Out of memory"); + } else { + uint8_t port_num = (*nut_usb_get_port_number)(dev); + if (port_num > 0) { + snprintf(bus_port, 4, "%03d", port_num); + } else { + snprintf(bus_port, 4, ".*"); + } + } + } + bcdDevice = dev_desc.bcdDevice; #else /* => WITH_LIBUSB_0_1 */ # ifndef WIN32 @@ -367,8 +405,8 @@ nutscan_device_t * nutscan_scan_usb() ret = (*nut_usb_open)(dev, &udev); if (!udev || ret != LIBUSB_SUCCESS) { fprintf(stderr, "Failed to open device " - "bus '%s' device/port '%s', skipping: %s\n", - busname, device_port, + "bus '%s' device/port '%s' bus/port '%s', skipping: %s\n", + busname, device_port, bus_port, (*nut_usb_strerror)(ret)); /* Note: closing is not applicable @@ -379,6 +417,10 @@ nutscan_device_t * nutscan_scan_usb() free(busname); free(device_port); + if (bus_port != NULL) { + free(bus_port); + bus_port = NULL; + } continue; } @@ -405,6 +447,10 @@ nutscan_device_t * nutscan_scan_usb() #if WITH_LIBUSB_1_0 free(busname); free(device_port); + if (bus_port != NULL) { + free(bus_port); + bus_port = NULL; + } (*nut_usb_free_device_list)(devlist, 1); (*nut_usb_exit)(NULL); #endif /* WITH_LIBUSB_1_0 */ @@ -425,6 +471,10 @@ nutscan_device_t * nutscan_scan_usb() #if WITH_LIBUSB_1_0 free(busname); free(device_port); + if (bus_port != NULL) { + free(bus_port); + bus_port = NULL; + } (*nut_usb_free_device_list)(devlist, 1); (*nut_usb_exit)(NULL); #endif /* WITH_LIBUSB_1_0 */ @@ -446,6 +496,10 @@ nutscan_device_t * nutscan_scan_usb() #if WITH_LIBUSB_1_0 free(busname); free(device_port); + if (bus_port != NULL) { + free(bus_port); + bus_port = NULL; + } (*nut_usb_free_device_list)(devlist, 1); (*nut_usb_exit)(NULL); #endif /* WITH_LIBUSB_1_0 */ @@ -466,6 +520,10 @@ nutscan_device_t * nutscan_scan_usb() #if WITH_LIBUSB_1_0 free(busname); free(device_port); + if (bus_port != NULL) { + free(bus_port); + bus_port = NULL; + } (*nut_usb_free_device_list)(devlist, 1); (*nut_usb_exit)(NULL); #endif /* WITH_LIBUSB_1_0 */ @@ -520,6 +578,16 @@ nutscan_device_t * nutscan_scan_usb() "device", device_port); +#if WITH_LIBUSB_1_0 + if (bus_port) { + nutscan_add_option_to_device(nut_dev, + "busport", + bus_port); + free(bus_port); + bus_port = NULL; + } +#endif /* WITH_LIBUSB_1_0 */ + /* Not currently matched by drivers, hence commented for now: */ sprintf(string, "%04X", bcdDevice); nutscan_add_option_to_device(nut_dev, @@ -540,6 +608,10 @@ nutscan_device_t * nutscan_scan_usb() #else /* not WITH_LIBUSB_0_1 */ free(busname); free(device_port); + if (bus_port != NULL) { + free(bus_port); + bus_port = NULL; + } } (*nut_usb_free_device_list)(devlist, 1);