diff --git a/NEWS.adoc b/NEWS.adoc index 8275b373a8..705415dcb3 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -58,10 +58,11 @@ https://github.com/networkupstools/nut/milestone/13 attempts on timeout errors, simplifying error recovery. [PR #3414] * Increased default TCP response timeout to 2000 ms. [PR #3418] + - `dummy-ups` driver updates: - * Added `authconf` driver parameter for repeater mode to control - authentication configuration discovery. It accepts `default`, `none`, - or a specific authconf file path. [issue #3329] + * Added `authconf` driver parameter for repeater mode to control + authentication configuration discovery. It accepts `default`, `none`, + or a specific authconf file path. [issue #3329] - `nhs_ser` driver updates: * Modernized serial communication and added validated settings for baud @@ -171,6 +172,22 @@ https://github.com/networkupstools/nut/milestone/13 chunks populated only partially with bytes we intended. These buffers are now more diligently pre-zeroed as their siblings were in other code paths, to avoid sending host stack garbage to a device. [#3529] + * Introduced a `reconnect_max_tries` setting for NUT drivers, so they can + be configured to exit after the specified number of reconnection attempts + (so that the OS service management facility can completely recycle the + driver program). Multiple drivers were updated to use the new facility + to consistently report `reconnect_trying()` states instead of what they + did before, including: `adelsystem_cbi`, `apc_modbus`, `apcsmart`, + `bcmxcp_usb`, `blazer_usb`, `generic_modbus`, `nhs_ser`, `nutdrv_qx`, + `powerman-pdu`, `powervar_cx_usb`, `richcomm_usb`, `riello_usb`, + `tripplite_usb`, `usbhid-ups` (it is possible that for now some of them + would log the situation twice, with old methods and the new, and that + some would abort the program if reconnection attempt fails). [PR #3541] + * `belkin`, `bestfortress`, `genericups`, `powerpanel`, `tripplite` drivers + introduced attempts to reconnect in case of failure, instead of just + exiting right away or remaining in data stale mode indefinitely, also + using `reconnect_trying()` for consistent reporting. They now track + serial port file descriptor validity a bit more diligently. [PR #3541] - NUT client libraries: * Complete support for actions documented in `docs/net-protocol.txt` diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 44b0ff39e4..b0146c60ed 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -74,6 +74,11 @@ The new `-A filename` option defaults to trying to use a `nutauth.conf` file reading one ('none' as the legacy default). See the updated manual pages for more details. [issues #3329, #3411] +- The `powervar_cx_usb`, `tripplite_usb` drivers used a built-in limit on + reconnection attempts after which they exited ('60' and '10' respectively). + This was revised to follow the new common setting `reconnect_max_tries`, + which defaults to trying indefinitely now. [#3541] + Changes from 2.8.4 to 2.8.5 --------------------------- diff --git a/conf/ups.conf.sample b/conf/ups.conf.sample index bbf508751e..42e7e95bff 100644 --- a/conf/ups.conf.sample +++ b/conf/ups.conf.sample @@ -112,6 +112,25 @@ maxretry = 3 # # The default is 75 seconds. # +# reconnect_max_tries: OPTIONAL. This can be set as a global variable above +# your first UPS definition and it can also be set in a +# UPS section. If a driver loses connection to the device, +# try reconnecting for the specified number of iterations and +# exit (so some OS service management facility or an alerted +# sysadmin can restart the driver program "from scratch"). +# A zero value (unreasonable but possible) means to exit as +# soon as the device connection was lost. +# A negative value (default) means to try reconnecting +# indefinitely (e.g. data cable fell out, or the data path +# to a networked UPS was lost for a significant time). +# +# reconnect_report_freq: OPTIONAL. This can be set as a global variable +# above your first UPS definition and it can also be set in a +# UPS section, and used in conjunction with reconnect_max_tries +# to report progress (and on-going lack of success) every N +# attempts, to avoid flooding the logs. A non-positive value +# disables such updates completely (default). +# # debug_min: OPTIONAL. Specify a minimum debug level for all driver daemons # and for the upsdrvctl tool (when specified at global level), # or for this driver daemon (when specified in a driver section), diff --git a/docs/man/ups.conf.txt b/docs/man/ups.conf.txt index 9211df7121..c98ace5255 100644 --- a/docs/man/ups.conf.txt +++ b/docs/man/ups.conf.txt @@ -100,6 +100,27 @@ its own in `upsd.conf`. + Environment variable `NUT_STATEPATH` set by caller can override this setting. +*reconnect_max_tries*:: + +Optional. If a driver loses connection to the device, try reconnecting for +the specified number of iterations and exit (so some OS service management +facility or an alerted sysadmin can restart the driver program "from scratch"). ++ +A zero value (unreasonable but possible) means to exit as soon as the device +connection was lost. ++ +A negative value (default) means to try reconnecting indefinitely (e.g. data +cable fell out, or the data path to a networked UPS was lost for a significant +time). + +*reconnect_report_freq*:: + +Optional. This can be used in conjunction with `reconnect_max_tries` to +report progress (and on-going lack of success) every `N` attempts, to avoid +flooding the logs. ++ +A non-positive value disables such updates completely (default). + *maxstartdelay*:: Optional. Same as the UPS field of the same name, but this is the @@ -340,6 +361,16 @@ They would not be actively killed by `upsdrvctl` after this timeout expires. + The default is 75 seconds. +*reconnect_max_tries*:: +Optional. This can be set as a global variable above your first UPS +definition and it can also be set in a UPS section. See explanation +above, in the global section. + +*reconnect_report_freq*:: +Optional. This can be set as a global variable above your first UPS +definition and it can also be set in a UPS section. See explanation +above, in the global section. + *maxretry*:: Optional. This can be set as a global variable above your first UPS definition and it can also be set in a UPS section. See explanation diff --git a/drivers/adelsystem_cbi.c b/drivers/adelsystem_cbi.c index 56ec7985a9..719a0b1fc9 100644 --- a/drivers/adelsystem_cbi.c +++ b/drivers/adelsystem_cbi.c @@ -34,7 +34,7 @@ #endif #define DRIVER_NAME "NUT ADELSYSTEM DC-UPS CB/CBI driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.09" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ @@ -1330,7 +1330,7 @@ void modbus_reconnect(void) int rval; upsdebugx(1, "modbus_reconnect, trying to reconnect to modbus server"); - dstate_setinfo("driver.state", "reconnect.trying"); + reconnect_trying(RECONNECT_TRYING); /* clear current modbus context */ modbus_close(mbctx); @@ -1391,5 +1391,5 @@ void modbus_reconnect(void) /* #elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval) // some un-castable type in fields */ #endif /* NUT_MODBUS_TIMEOUT_ARG_* */ - dstate_setinfo("driver.state", "quiet"); + reconnect_trying(RECONNECT_SUCCESS); } diff --git a/drivers/apc_modbus.c b/drivers/apc_modbus.c index 42ef2cffb8..cc9bc5000c 100644 --- a/drivers/apc_modbus.c +++ b/drivers/apc_modbus.c @@ -43,7 +43,7 @@ #endif #define DRIVER_NAME "NUT APC Modbus driver " DRIVER_NAME_NUT_MODBUS_HAS_USB_WITH_STR " USB support (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.20" +#define DRIVER_VERSION "0.21" #if defined NUT_MODBUS_HAS_USB @@ -955,7 +955,7 @@ static void _apc_modbus_create_reopen_matcher(void) static int _apc_modbus_reopen(void) { - dstate_setinfo("driver.state", "reconnect.trying"); + reconnect_trying(RECONNECT_TRYING); if (modbus_connect(modbus_ctx) < 0) { upslogx(LOG_ERR, "%s: Unable to connect Modbus: %s", __func__, modbus_strerror(errno)); @@ -975,9 +975,10 @@ static int _apc_modbus_reopen(void) is_open = 1; - dstate_setinfo("driver.state", "reconnect.updateinfo"); + reconnect_trying(RECONNECT_UPDATEINFO); _apc_modbus_read_inventory(); - dstate_setinfo("driver.state", "quiet"); + + reconnect_trying(RECONNECT_SUCCESS); return 1; } diff --git a/drivers/apcsmart.c b/drivers/apcsmart.c index a0f7f8c48f..72287d8e98 100644 --- a/drivers/apcsmart.c +++ b/drivers/apcsmart.c @@ -38,7 +38,7 @@ #include "apcsmart_tabs.h" #define DRIVER_NAME "APC Smart protocol driver" -#define DRIVER_VERSION "3.39" +#define DRIVER_VERSION "3.40" #ifdef WIN32 # ifndef ECANCELED @@ -477,7 +477,7 @@ static ssize_t apc_read_i(char *buf, size_t buflen, int flags, const char *fn, u fatalx (EXIT_FAILURE, "Error: apc_read_i called with buflen too large"); } - if (INVALID_FD(upsfd)) + if (INVALID_FD_SER(upsfd)) return 0; if (flags & SER_D0) { @@ -605,7 +605,7 @@ static ssize_t apc_write_i(unsigned char code, const char *fn, unsigned int ln) ssize_t ret; errno = 0; - if (INVALID_FD(upsfd)) + if (INVALID_FD_SER(upsfd)) return 0; ret = ser_send_char(upsfd, code); @@ -2301,6 +2301,13 @@ void upsdrv_initups(void) } upsfd = extrafd = ser_open(device_path); + + if (INVALID_FD_SER(upsfd)) { + upslogx(LOG_WARNING, "%s: failed to open %s", + __func__, device_path); + /* \todo: Deal with the failure */ + } + apc_ser_set(); /* fill length values */ @@ -2312,7 +2319,7 @@ void upsdrv_cleanup(void) { char temp[APC_LBUF]; - if (INVALID_FD(upsfd)) + if (INVALID_FD_SER(upsfd)) return; apc_flush(0); @@ -2320,6 +2327,7 @@ void upsdrv_cleanup(void) apc_write(APC_GODUMB); apc_read(temp, sizeof(temp), SER_TO); ser_close(upsfd, device_path); + upsfd = ERROR_FD_SER; } void upsdrv_initinfo(void) @@ -2394,8 +2402,9 @@ void upsdrv_updateinfo(void) /* become aggressive after a few tries */ if (!(last_worked % 60)) { - upslogx(LOG_WARNING, "Trying to reconnect to the UPS"); - dstate_setinfo("driver.state", "reconnect.trying"); + upsdebugx(1, "%s: Trying to reconnect to the UPS", __func__); + + reconnect_trying(RECONNECT_TRYING); upsdebugx(1, "%s: call upsdrv_cleanup", __func__); /* dstate_setinfo("driver.state", "cleanup.upsdrv"); */ @@ -2412,12 +2421,18 @@ void upsdrv_updateinfo(void) /* dstate_setinfo("driver.state", "init.info"); */ upsdrv_initinfo(); + if (INVALID_FD_SER(upsfd)) { + upsdebugx(1, "%s: upsfd remains invalid", __func__); + dstate_datastale(); + return; + } + + reconnect_trying(RECONNECT_UPDATEINFO); upsdebugx(1, "%s: call upsdrv_updateinfo", __func__); - dstate_setinfo("driver.state", "reconnect.updateinfo"); /* dstate_setinfo("driver.state", "init.updateinfo"); */ upsdrv_updateinfo(); - dstate_setinfo("driver.state", "init.quiet"); + reconnect_trying(RECONNECT_SUCCESS); } upsdebugx(1, "%s: nudging UPS with 'Y', iteration #%d ...", diff --git a/drivers/bcmxcp_usb.c b/drivers/bcmxcp_usb.c index 8ab3c32d38..6cf0b061dd 100644 --- a/drivers/bcmxcp_usb.c +++ b/drivers/bcmxcp_usb.c @@ -11,7 +11,7 @@ #include #define SUBDRIVER_NAME "USB communication subdriver" -#define SUBDRIVER_VERSION "0.28" +#define SUBDRIVER_VERSION "0.29" /* communication driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -385,7 +385,7 @@ void upsdrv_cleanup(void) void upsdrv_reconnect(void) { - dstate_setinfo("driver.state", "reconnect.trying"); + reconnect_trying(RECONNECT_TRYING); upsdebugx(4, "=================================================="); upsdebugx(4, "= device has been disconnected, try to reconnect ="); @@ -396,7 +396,12 @@ void upsdrv_reconnect(void) upsdrv_initups(); - dstate_setinfo("driver.state", "quiet"); + if (upsdev) { + reconnect_trying(RECONNECT_SUCCESS); + /* dstate_dataok() is called in bcmxcp.c::upsdrv_updateinfo() */ + } else { + dstate_datastale(); + } } /* USB functions */ diff --git a/drivers/belkin.c b/drivers/belkin.c index 24211a93b9..edcad87dbb 100644 --- a/drivers/belkin.c +++ b/drivers/belkin.c @@ -29,10 +29,13 @@ #include "nut_stdint.h" #define DRIVER_NAME "Belkin Smart protocol driver" -#define DRIVER_VERSION "0.30" +#define DRIVER_VERSION "0.31" static ssize_t init_communication(void); static ssize_t get_belkin_reply(char *buf); +static int reconnect_ups(void); +void upsdrv_initups(void); +void upsdrv_cleanup(void); /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -223,9 +226,17 @@ void upsdrv_updateinfo(void) if (retry < MAXTRIES) { upsdebugx(1, "Communications with UPS lost: status read failed!"); retry++; - } else { /* too many retries */ - upslogx(LOG_WARNING, "Communications with UPS lost: status read failed!"); + return; + } + + if (may_log_reconnect_trying(0)) + upslogx(LOG_WARNING, "Communications with UPS lost: status read failed; attempting reconnect"); + + if (!reconnect_ups()) { dstate_datastale(); + } else { + /* Do not extra-log below */ + retry = 0; } return; } @@ -499,6 +510,13 @@ void upsdrv_makevartable(void) void upsdrv_initups(void) { upsfd = ser_open(device_path); + + if (INVALID_FD_SER(upsfd)) { + upslogx(LOG_WARNING, "%s: failed to open %s", + __func__, device_path); + /* \todo: Deal with the failure */ + } + ser_set_speed(upsfd, device_path, B2400); /* set DTR to low and RTS to high */ @@ -510,17 +528,24 @@ void upsdrv_initups(void) ser_flush_io(upsfd); } -void upsdrv_initinfo(void) +static int init_driver_state(int fatal_on_failure) { ssize_t res; char temp[SMALLBUF], st[SMALLBUF]; res = init_communication(); if (res < 0) { - fatalx(EXIT_FAILURE, - "Unable to detect an Belkin Smart protocol UPS on port %s\n" - "Check the cabling, port name or model name and try again", device_path - ); + if (fatal_on_failure) { + fatalx(EXIT_FAILURE, + "Unable to detect an Belkin Smart protocol UPS on port %s\n" + "Check the cabling, port name or model name and try again", device_path + ); + } + + if (may_log_reconnect_trying(1)) + upslogx(LOG_WARNING, "Unable to re-establish communication with the Belkin UPS on port %s", device_path); + + return 0; } dstate_setinfo("ups.mfr", "BELKIN"); @@ -564,9 +589,39 @@ void upsdrv_initinfo(void) dstate_addcmd("test.battery.stop"); upsh.instcmd = instcmd; + return 1; +} + +void upsdrv_initinfo(void) +{ + if (!init_driver_state(1)) { + return; + } +} + +static int reconnect_ups(void) +{ + reconnect_trying(RECONNECT_TRYING); + + upsdrv_cleanup(); + upsdrv_initups(); + + if (INVALID_FD_SER(upsfd) || !init_driver_state(0)) { + dstate_datastale(); + return 0; + } + + /* TOTHINK: Any data refresh and reconnect_trying(RECONNECT_UPDATEINFO) here? */ + reconnect_trying(RECONNECT_SUCCESS); + return 1; } void upsdrv_cleanup(void) { - ser_close(upsfd, device_path); + upsdebugx(1, "%s: begin", __func__); + if (VALID_FD_SER(upsfd)) { + ser_close(upsfd, device_path); + upsfd = ERROR_FD_SER; /* invalidate the closed upsfd */ + } + upsdebugx(1, "%s: end", __func__); } diff --git a/drivers/bestfortress.c b/drivers/bestfortress.c index 713e99e672..959a629afd 100644 --- a/drivers/bestfortress.c +++ b/drivers/bestfortress.c @@ -34,8 +34,8 @@ #define inline __inline #endif -#define DRIVER_NAME "Best Fortress UPS driver" -#define DRIVER_VERSION "0.15" +#define DRIVER_NAME "Best Fortress UPS driver" +#define DRIVER_VERSION "0.16" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -73,6 +73,8 @@ static const char *shutdown_delay = "20"; static int instcmd (const char *cmdname, const char *extra); static int upsdrv_setvar (const char *varname, const char *val); +static int reconnect_ups(void); +void upsdrv_cleanup(void); /* Rated maximum VA output as configured by the user. */ static int maxload = 0; @@ -361,7 +363,9 @@ void upsdrv_updateinfo(void) /* \todo: Analyze/fix code and rewrite message. */ upsdebugx(2, "%s: pointer to data not initialized after processing", __func__); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } return; } @@ -624,7 +628,7 @@ void upsdrv_initups(void) } upsfd = ser_open(device_path); - if (INVALID_FD(upsfd)) { + if (INVALID_FD_SER(upsfd)) { upslogx(LOG_WARNING, "%s: failed to open %s", __func__, device_path); /* \todo: Deal with the failure */ @@ -639,7 +643,28 @@ void upsdrv_initups(void) upsdebugx(1, "%s: end", __func__); } +static int reconnect_ups(void) +{ + reconnect_trying(RECONNECT_TRYING); + + upsdrv_cleanup(); + upsdrv_initups(); + if (INVALID_FD_SER(upsfd)) + return 0; + + reconnect_trying(RECONNECT_UPDATEINFO); + upsdrv_initinfo(); + + reconnect_trying(RECONNECT_SUCCESS); + return 1; +} + void upsdrv_cleanup(void) { - upsdebugx(1, "%s: begin/end", __func__); + upsdebugx(1, "%s: begin", __func__); + if (VALID_FD_SER(upsfd)) { + ser_close(upsfd, device_path); + upsfd = ERROR_FD_SER; /* invalidate the closed upsfd */ + } + upsdebugx(1, "%s: end", __func__); } diff --git a/drivers/blazer_usb.c b/drivers/blazer_usb.c index 6ab18246e1..9790294c2a 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.25" +#define DRIVER_VERSION "0.26" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -483,21 +483,27 @@ ssize_t blazer_command(const char *cmd, char *buf, size_t buflen) { #ifndef TESTING ssize_t ret; + int reconnecting = (udev == NULL); - if (udev == NULL) { - dstate_setinfo("driver.state", "reconnect.trying"); + if (reconnecting) { + reconnect_trying(RECONNECT_TRYING); ret = usb->open_dev(&udev, &usbdevice, reopen_matcher, NULL); if (ret < 1) { + /* dstate_datastale/dstate_dataok managed in blazer.c::upsdrv_updateinfo() */ return ret; } - dstate_setinfo("driver.state", "reconnect.updateinfo"); + reconnect_trying(RECONNECT_UPDATEINFO); } ret = (*subdriver_command)(cmd, buf, buflen); if (ret >= 0) { + /* clean read: forget any overflow streak */ + if (reconnecting) { + reconnect_trying(RECONNECT_SUCCESS); + } return ret; } @@ -540,6 +546,8 @@ ssize_t blazer_command(const char *cmd, char *buf, size_t buflen) case LIBUSB_ERROR_NOT_FOUND: /* No such file or directory */ fallthrough_case_reconnect: /* Uh oh, got to reconnect! */ + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); usb->close_dev(udev); udev = NULL; @@ -558,6 +566,14 @@ ssize_t blazer_command(const char *cmd, char *buf, size_t buflen) break; } + if (reconnecting) { + /* Success after updateinfo in the bulk of this method body */ + upsdebugx(1, "%s: libusb returned %" PRIiSIZE + " which was not classified as a known error, assuming reconnection succeeded", + __func__, ret); + reconnect_trying(RECONNECT_SUCCESS); + } + return ret; #else /* if TESTING: */ const struct { diff --git a/drivers/dstate.c b/drivers/dstate.c index afa229af46..fada57675b 100644 --- a/drivers/dstate.c +++ b/drivers/dstate.c @@ -1394,7 +1394,7 @@ static int sock_read(conn_t *conn) /* Special case for signals */ if (!strncmp(conn->buf, COMMAND_STOP, sizeof(COMMAND_STOP))) { - set_exit_flag(1); + set_exit_flag(EF_EXIT_SUCCESS); return 1; } #endif /* WIN32 */ diff --git a/drivers/generic_modbus.c b/drivers/generic_modbus.c index 29ac2ce5db..bee0235a66 100644 --- a/drivers/generic_modbus.c +++ b/drivers/generic_modbus.c @@ -31,7 +31,7 @@ #endif #define DRIVER_NAME "NUT Generic Modbus driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.10" +#define DRIVER_VERSION "0.11" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ @@ -1063,7 +1063,7 @@ void modbus_reconnect(void) int rval; upsdebugx(2, "modbus_reconnect, trying to reconnect to modbus server"); - dstate_setinfo("driver.state", "reconnect.trying"); + reconnect_trying(RECONNECT_TRYING); /* clear current modbus context */ modbus_close(mbctx); @@ -1124,5 +1124,5 @@ void modbus_reconnect(void) /* #elif (defined NUT_MODBUS_TIMEOUT_ARG_timeval) // some un-castable type in fields */ #endif /* NUT_MODBUS_TIMEOUT_ARG_* */ - dstate_setinfo("driver.state", "quiet"); + reconnect_trying(RECONNECT_SUCCESS); } diff --git a/drivers/genericups.c b/drivers/genericups.c index dfeb17d420..98661eeccd 100644 --- a/drivers/genericups.c +++ b/drivers/genericups.c @@ -31,7 +31,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Generic contact-closure UPS driver" -#define DRIVER_VERSION "1.42" +#define DRIVER_VERSION "1.43" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -180,6 +180,22 @@ static void parse_input_signals(const char *value, int *line, int *val) upsdebugx(4, "%s: exit", __func__); } +static int reconnect_ups(void) +{ + reconnect_trying(RECONNECT_TRYING); + + upsdrv_cleanup(); + upsdrv_initups(); + if (INVALID_FD_SER(upsfd)) + return 0; + + reconnect_trying(RECONNECT_UPDATEINFO); + upsdrv_initinfo(); + + reconnect_trying(RECONNECT_SUCCESS); + return 1; +} + void upsdrv_initinfo(void) { char *v; @@ -231,7 +247,8 @@ void upsdrv_updateinfo(void) if (ret != 0) { upslog_with_errno(LOG_INFO, "ioctl failed"); ser_comm_fail("Status read failed"); - dstate_datastale(); + if (!reconnect_ups()) + dstate_datastale(); return; } @@ -437,6 +454,12 @@ void upsdrv_initups(void) upsfd = ser_open(device_path); + if (INVALID_FD_SER(upsfd)) { + upslogx(LOG_WARNING, "%s: failed to open %s", + __func__, device_path); + /* \todo: Deal with the failure */ + } + if (tcgetattr(upsfd, &tio)) { fatal_with_errno(EXIT_FAILURE, "tcgetattr"); } @@ -475,5 +498,10 @@ void upsdrv_initups(void) void upsdrv_cleanup(void) { - ser_close(upsfd, device_path); + upsdebugx(1, "%s: begin", __func__); + if (VALID_FD_SER(upsfd)) { + ser_close(upsfd, device_path); + upsfd = ERROR_FD_SER; /* invalidate the closed upsfd */ + } + upsdebugx(1, "%s: end", __func__); } diff --git a/drivers/main.c b/drivers/main.c index cdff467242..4f741fc308 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -101,7 +101,8 @@ vartab_t *vartab_h = NULL; */ time_t poll_interval = 2; static char *chroot_path = NULL, *user = NULL, *group = NULL; -static int user_from_cmdline = 0, group_from_cmdline = 0; +static int user_from_cmdline = 0, group_from_cmdline = 0, + reconnect_max_tries = -1, reconnect_count = 0, reconnect_report_freq = -1; /* signal handling */ int exit_flag = 0; @@ -1397,6 +1398,27 @@ static int main_arg(char *var, char *val) return 1; /* handled */ } + /* Allow per-driver overrides of the global setting + * and allow to reload this, why not. */ + if (!strcmp(var, "reconnect_max_tries")) { + int intval = -1; + if ( str_to_int (val, &intval, 10) ) { + reconnect_max_tries = intval; + reconnect_count = 0; + } else { + upslogx(LOG_INFO, "WARNING : Invalid reconnect_max_tries value found in ups.conf global settings"); + } + } + + if (!strcmp(var, "reconnect_report_freq")) { + int intval = -1; + if ( str_to_int (val, &intval, 10) ) { + reconnect_report_freq = intval; + } else { + upslogx(LOG_INFO, "WARNING : Invalid reconnect_report_freq value found in ups.conf global settings"); + } + } + /* only for upsdrvctl - ignored here */ if (!strcmp(var, "sdorder")) return 1; /* handled */ @@ -1571,6 +1593,29 @@ static void do_global_args(const char *var, const char *val) return; } + if (!strcmp(var, "reconnect_max_tries")) { + int intval = -1; + if ( str_to_int (val, &intval, 10) ) { + reconnect_max_tries = intval; + reconnect_count = 0; + } else { + upslogx(LOG_INFO, "WARNING : Invalid reconnect_max_tries value found in ups.conf global settings"); + } + + return; + } + + if (!strcmp(var, "reconnect_report_freq")) { + int intval = -1; + if ( str_to_int (val, &intval, 10) ) { + reconnect_report_freq = intval; + } else { + upslogx(LOG_INFO, "WARNING : Invalid reconnect_report_freq value found in ups.conf global settings"); + } + + return; + } + /* Allow to specify its minimal debugging level for all drivers - * admins can set more with command-line args, but can't set * less without changing config. Should help debug of services. @@ -2041,11 +2086,11 @@ static void exit_cleanup(void) void set_exit_flag(int sig) { - switch (exit_flag) { - case -2: + switch (sig) { + case EF_EXIT_SUCCESS: upsdebugx(1, "%s: raising exit flag due to programmatic abort: EXIT_SUCCESS", __func__); break; - case -1: + case EF_EXIT_FAILURE: upsdebugx(1, "%s: raising exit flag due to programmatic abort: EXIT_FAILURE", __func__); break; default: @@ -2169,6 +2214,158 @@ void setup_signals(void) } #endif /* !WIN32*/ +/* Return 1 if driver can now report custom reconnection start/progress details */ +int may_log_reconnect_trying(int throttle_hit) { + if (reconnect_count > 0) { + /* Accounting reconnections already, no care about throttling - be quiet */ + if (!throttle_hit || reconnect_report_freq < 1) + return 0; + + /* At that point in throttling where we may talk */ + if ((reconnect_count % reconnect_report_freq) == 0) + return 1; + + /* Throttling - be quiet */ + return 0; + } + + /* Not counting yet, would be a first report */ + return 1; +} + +/* Called by a driver to either enter/continue a reconnection loop + * (trying=1), almost done (trying=2), or to end it (trying=0). + * Return how many attempts remain before driver exits (-1 if it won't). + */ +int reconnect_trying(reconnect_state_t trying) { + /* TODO: Reconcile log-throttling with e.g. USB precedent in richcomm_usb.c, usbhid-ups.c et al. */ + /* TODO: Raise/clear dstate_datastale()/dstate_dataok()? After how many iterations? */ + switch (trying) { + case RECONNECT_SUCCESS: + if (reconnect_count > 0) { + upsdebugx(1, "%s: Driver reconnected " + "to the device [%s] after %d attempts", + __func__, upsname, reconnect_count); + upslogx(LOG_NOTICE, "Communications with UPS [%s] re-established", upsname); + } + reconnect_count = 0; + dstate_setinfo("driver.state", "quiet"); + return -1; + + case RECONNECT_UPDATEINFO: + if (reconnect_count > 0) { + upsdebugx(1, "%s: Driver has technically reconnected " + "to the device [%s] after %d attempts " + "and is now re-evaluating device data", + __func__, upsname, reconnect_count); + } else { + upsdebugx(1, "%s: BOGUS: Driver reported that it " + "reconnected to the device [%s] and is now " + "re-evaluating device data, but it never " + "started a reconnection counter", + __func__, upsname); + } + dstate_setinfo("driver.state", "reconnect.updateinfo"); + + /* Not expecting a restart. Not finished, so not clearing the + * counter either, though -- the update might fail and we would + * again try to reconnect, logically continuing the current loop. */ + if (reconnect_max_tries > 0 && reconnect_max_tries > reconnect_count) { + return reconnect_max_tries - reconnect_count + 1; + } + + return -1; + + case RECONNECT_TRYING: + if (reconnect_max_tries == 0) { + upslogx(LOG_WARNING, "Driver lost connection " + "to the device [%s] and will exit immediately", + upsname); + set_exit_flag(EF_EXIT_FAILURE); + return 0; + } + + dstate_setinfo("driver.state", "reconnect.trying"); + + if (reconnect_count == 0) { + if (reconnect_max_tries < 0) { + upslogx(LOG_WARNING, "Driver reconnecting " + "to the device [%s], will try " + "indefinitely", + upsname); + } else { + upslogx(LOG_WARNING, "Driver reconnecting " + "to the device [%s], will try for " + "max %d attempts, then will exit", + upsname, reconnect_max_tries); + } + } else if (reconnect_report_freq > 0 && (reconnect_count % reconnect_report_freq) == 0) { + upslogx(LOG_WARNING, "Driver still reconnecting " + "to the device [%s] for %d attempts", + upsname, reconnect_count); + } + + if (reconnect_count < INT_MAX) { + reconnect_count++; + } else { + upsdebugx(1, "%s: reconnect counter overflowed", __func__); + if (reconnect_max_tries > 0) { + upslogx(LOG_WARNING, "Driver lost connection " + "to the device [%s] and reconnect " + "counter overflowed, will exit immediately", + upsname); + set_exit_flag(EF_EXIT_FAILURE); + return 0; + } + } + + if (reconnect_max_tries > 0) { + if (reconnect_max_tries < reconnect_count) { + upslogx(LOG_WARNING, "Driver lost connection " + "to the device [%s] and tried to " + "reconnect for %d times, " + "now will exit as configured", + upsname, reconnect_count); + set_exit_flag(EF_EXIT_FAILURE); + return 0; + } + return reconnect_max_tries - reconnect_count + 1; + } + + return -1; + + /* Some warnings settings require all enum values to be covered + * and complain if a default is then present "superfluously", + * others require that a default always exists. Quiesce that. + */ +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif + /* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + default: + /* any `tries not in [0, 1, 2]`, must not occur */ + upsdebugx(1, "%s: Invalid reconnect status value: %d", __func__, (int)trying); + return -1; +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + } +} + /* This source file is used in some unit tests to mock realistic driver * behavior - using a production driver skeleton, but their own main(). * It is called from main-stub.c in shared-mode builds. @@ -3301,6 +3498,11 @@ int main(int argc, char **argv) gettimeofday(&timeout, NULL); timeout.tv_sec += poll_interval; + if (reconnect_count > 0) { + dstate_setinfo("driver.reconnect_count", "%d", reconnect_count); + dstate_setinfo("driver.reconnect_max_tries", "%d", reconnect_max_tries); + } + /* Drivers can now choose to track changes of current battery * charge vs. its previous value to e.g. report "CHRG" status. * TODO: Eventually provide a common `runtimecal` fallback to all? @@ -3326,7 +3528,7 @@ int main(int argc, char **argv) if (update_count == dump_data) { dstate_setinfo("driver.state", "dumping"); dstate_dump(); - exit_flag = 1; + set_exit_flag(EF_EXIT_SUCCESS); } else update_count++; @@ -3348,6 +3550,6 @@ int main(int argc, char **argv) upsnotify(NOTIFY_STATE_STOPPING, "Signal %d: exiting", exit_flag); } - exit(exit_flag == -1 ? EXIT_FAILURE : EXIT_SUCCESS); + exit(exit_flag == EF_EXIT_FAILURE ? EXIT_FAILURE : EXIT_SUCCESS); } #endif /* DRIVERS_MAIN_WITHOUT_MAIN */ diff --git a/drivers/main.h b/drivers/main.h index 5c56ca0069..6c3d9b31ba 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -112,6 +112,39 @@ typedef struct vartab_s { void addvar(int vartype, const char *name, const char *desc); void addvar_reloadable(int vartype, const char *name, const char *desc); +typedef enum reconnect_state { + RECONNECT_SUCCESS = 0, + RECONNECT_TRYING, + RECONNECT_UPDATEINFO +} reconnect_state_t; +/** Called by a driver to either: + * - enter/continue a reconnection loop (trying=RECONNECT_TRYING=1, and with + * certain configuration of `reconnect_max_tries>=0` can `set_exit_flag()`), + * - signal impending success (trying=RECONNECT_UPDATEINFO=2), or + * - end it due to successful reconnection (trying=RECONNECT_SUCCESS=0). + * + * Sets the `driver.state` to "reconnect.trying" (1) or "quiet" (0) + * respectively; if some drivers re-evaluate complete device data + * after reconnection has technically succeeded but before becoming + * generally available again, they are welcome to set `driver.state` + * to "reconnect.updateinfo" (2). + * + * Returns how many attempts remain before driver exits (-1 if it won't, + * 0 if exiting now). + */ +int reconnect_trying(reconnect_state_t trying); + +/** Return 1 if driver can now report custom reconnection start/progress + * details, with common information to be logged by reconnect_trying(), + * otherwise 0. If `throttle_hit!=0`, check if `reconnect_report_freq>0` + * and `reconnect_count%reconnect_report_freq == 0` to allow a progress + * report to be logged. + * + * This method should be called before reconnect_trying(RECONNECT_TRYING) + * bumps the counters involved. + */ +int may_log_reconnect_trying(int throttle_hit); + /* Several helpers for driver configuration reloading follow: * * testval_reloadable() checks if we are currently reloading (or initially * loading) the configuration, and if strings oldval==newval or not, diff --git a/drivers/nhs_ser.c b/drivers/nhs_ser.c index 49fde674d9..79e7d9e027 100644 --- a/drivers/nhs_ser.c +++ b/drivers/nhs_ser.c @@ -4,7 +4,7 @@ * * Copyright (C) 2024 Lucas Willian Bocchi * Initial Release (as nhs-nut.c) - * Copyright (C) 2024 - 2025 Jim Klimov + * Copyright (C) 2024 - 2026 Jim Klimov * Codebase adjusted to NUT standards * * This program is free software; you can redistribute it and/or modify @@ -1923,7 +1923,7 @@ static unsigned int get_numbat(void) { * restored after a communication failure. */ static TYPE_FD_SER reconnect_ups_if_needed(void) { - /* retries to open port */ + /* retries to open port until we declare "data stale" loudly */ static unsigned int retries = 0; /* If comms failed earlier, try to resuscitate */ @@ -1932,7 +1932,7 @@ static TYPE_FD_SER reconnect_ups_if_needed(void) { __func__, porta); /* Uh oh, got to reconnect! */ - dstate_setinfo("driver.state", "reconnect.trying"); + reconnect_trying(RECONNECT_TRYING); /* Close any surviving handle and mark it invalid before reopening. */ close_serial_port(); @@ -1942,19 +1942,21 @@ static TYPE_FD_SER reconnect_ups_if_needed(void) { serial_fd = openfd(porta, baudrate); retries++; /* Try above at least once per main cycle */ - if (retries >= MAXTRIES) + if (retries >= MAXTRIES) { + upsdebugx(1, "%s: serial port reopen failed", __func__); break; + } usleep(checktime); } if (VALID_FD_SER(serial_fd)) { - if (retries > MAXTRIES) { + if (retries > MAXTRIES && may_log_reconnect_trying(1)) { upslogx(LOG_NOTICE, "Communications with UPS re-established"); } retries = 0; - dstate_setinfo("driver.state", "quiet"); + reconnect_trying(RECONNECT_SUCCESS); } else { - if (retries == MAXTRIES) { + if (retries == MAXTRIES && may_log_reconnect_trying(1)) { upslogx(LOG_WARNING, "Communications with UPS lost: port reopen failed!"); } dstate_datastale(); @@ -2602,7 +2604,7 @@ void upsdrv_shutdown(void) { /* replace with a proper shutdown function */ upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + set_exit_flag(EF_EXIT_FAILURE); upsdebugx(1, "Driver shutdown"); } diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index ad58464c02..6b32ece307 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -58,7 +58,7 @@ # define DRIVER_NAME "Generic Q* Serial driver" #endif /* QX_USB */ -#define DRIVER_VERSION "0.52" +#define DRIVER_VERSION "0.53" #ifdef QX_SERIAL # include "serial.h" @@ -3908,6 +3908,7 @@ static ssize_t qx_command(const char *cmd, size_t cmdlen, char *buf, size_t bufl /* Persists across calls; only consecutive overflows accumulate (any clean * read zeroes it, see the switch on `ret` below). */ static int overflow_tries = 0; + int reconnecting = (udev == NULL); # endif #endif @@ -3927,8 +3928,8 @@ static ssize_t qx_command(const char *cmd, size_t cmdlen, char *buf, size_t bufl if (is_usb) { # endif /* QX_SERIAL (&& QX_USB)*/ - if (udev == NULL) { - dstate_setinfo("driver.state", "reconnect.trying"); + if (reconnecting) { + reconnect_trying(RECONNECT_TRYING); ret = usb->open_dev(&udev, &usbdevice, reopen_matcher, NULL); @@ -3936,13 +3937,16 @@ static ssize_t qx_command(const char *cmd, size_t cmdlen, char *buf, size_t bufl return ret; } - dstate_setinfo("driver.state", "reconnect.updateinfo"); + reconnect_trying(RECONNECT_UPDATEINFO); } ret = (*subdriver_command)(cmd, cmdlen, buf, buflen); if (ret >= 0) { overflow_tries = 0; /* clean read: forget any overflow streak */ + if (reconnecting) { + reconnect_trying(RECONNECT_SUCCESS); + } return ret; } @@ -3999,6 +4003,8 @@ static ssize_t qx_command(const char *cmd, size_t cmdlen, char *buf, size_t bufl case LIBUSB_ERROR_NOT_FOUND: /* No such file or directory */ fallthrough_case_reconnect: /* Uh oh, got to reconnect! */ + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); usb->close_dev(udev); udev = NULL; @@ -4031,6 +4037,14 @@ static ssize_t qx_command(const char *cmd, size_t cmdlen, char *buf, size_t bufl break; } + if (reconnecting) { + /* Success after updateinfo in the bulk of this method body */ + upsdebugx(1, "%s: libusb returned %" PRIiSIZE + " which was not classified as a known error, assuming reconnection succeeded", + __func__, ret); + reconnect_trying(RECONNECT_SUCCESS); + } + # ifdef QX_SERIAL /* Communication: serial */ } else { /* !is_usb */ @@ -4045,6 +4059,7 @@ static ssize_t qx_command(const char *cmd, size_t cmdlen, char *buf, size_t bufl ret = ser_send_buf(upsfd, cmd, cmdlen); if (ret <= 0) { + /* TOTHINK: Is any special reconnect logic/tracking needed? */ upsdebugx(3, "send: %s (%" PRIiSIZE ")", ret ? strerror(errno) : "timeout", ret); return ret; diff --git a/drivers/powerman-pdu.c b/drivers/powerman-pdu.c index fdbb58fc3b..8e684c15e8 100644 --- a/drivers/powerman-pdu.c +++ b/drivers/powerman-pdu.c @@ -23,7 +23,7 @@ #include /* pm_err_t and other beasts */ #define DRIVER_NAME "Powerman PDU client driver" -#define DRIVER_VERSION "0.17" +#define DRIVER_VERSION "0.18" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -218,7 +218,7 @@ static int reconnect_ups(void) { pm_err_t rv; - dstate_setinfo("driver.state", "reconnect.trying"); + reconnect_trying(RECONNECT_TRYING); upsdebugx(4, "==================================================="); upsdebugx(4, "= connection lost with Powerman, try to reconnect ="); @@ -228,10 +228,11 @@ static int reconnect_ups(void) pm_disconnect(pm); /* Connect to the PowerMan daemon */ - if ((rv = pm_connect(device_path, NULL, &pm, 0)) != PM_ESUCCESS) + if ((rv = pm_connect(device_path, NULL, &pm, 0)) != PM_ESUCCESS) { + dstate_datastale(); return 0; - else { - dstate_setinfo("driver.state", "quiet"); + } else { + reconnect_trying(RECONNECT_SUCCESS); upsdebugx(4, "connection restored with Powerman"); return 1; } diff --git a/drivers/powerpanel.c b/drivers/powerpanel.c index f90cb6f9b5..8a00fde576 100644 --- a/drivers/powerpanel.c +++ b/drivers/powerpanel.c @@ -36,7 +36,7 @@ static subdriver_t *subdriver[] = { }; #define DRIVER_NAME "CyberPower text/binary protocol UPS driver" -#define DRIVER_VERSION "0.31" +#define DRIVER_VERSION "0.32" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -50,6 +50,22 @@ upsdrv_info_t upsdrv_info = { }; /* FIXME: add a sub version for binary and text subdrivers? */ +static int reconnect_ups(void) +{ + reconnect_trying(RECONNECT_TRYING); + + upsdrv_cleanup(); + upsdrv_initups(); + if (INVALID_FD_SER(upsfd)) + return 0; + + reconnect_trying(RECONNECT_UPDATEINFO); + upsdrv_initinfo(); + + reconnect_trying(RECONNECT_SUCCESS); + return 1; +} + void upsdrv_initinfo(void) { char *s; @@ -84,12 +100,18 @@ void upsdrv_updateinfo(void) if (subdriver[mode]->updateinfo() < 0) { ser_comm_fail("Status read failed!"); + /* First retry a few times by just re-issuing queries, maybe + * there was some line noise; if that fails - try reconnecting. + */ if (retry < 3) { retry++; } else { - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } } + /* Even if reconnected, go on to another updateinfo() cycle */ return; } @@ -156,6 +178,12 @@ void upsdrv_initups(void) version = getval("protocol"); upsfd = ser_open(device_path); + if (INVALID_FD_SER(upsfd)) { + upslogx(LOG_WARNING, "%s: failed to open %s", + __func__, device_path); + /* \todo: Deal with the failure */ + } + ser_set_rts(upsfd, 0); /* @@ -204,6 +232,11 @@ void upsdrv_makevartable(void) void upsdrv_cleanup(void) { - ser_set_dtr(upsfd, 0); - ser_close(upsfd, device_path); + upsdebugx(1, "%s: begin", __func__); + if (VALID_FD_SER(upsfd)) { + ser_set_dtr(upsfd, 0); + ser_close(upsfd, device_path); + upsfd = ERROR_FD_SER; /* invalidate the closed upsfd */ + } + upsdebugx(1, "%s: end", __func__); } diff --git a/drivers/powervar_cx_usb.c b/drivers/powervar_cx_usb.c index 982fc9566b..b76de31b49 100644 --- a/drivers/powervar_cx_usb.c +++ b/drivers/powervar_cx_usb.c @@ -45,11 +45,10 @@ #include "powervar_cx.h" /* Common driver variables and functions */ #define DRIVER_NAME "Powervar-CUSSP UPS driver (USB)" -#define DRIVER_VERSION "1.01" +#define DRIVER_VERSION "1.02" /* USB comm stuff here */ #define USB_RESPONSE_SIZE 8 -#define MAX_CNCT_ATTMPTS 60 /* x calls to upsdrv_updateinfo */ /* Powervar */ #define POWERVAR_VENDORID 0x4234 @@ -396,47 +395,44 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { int ret; - static int CnctAttempts = 0; upsdebugx(3, "In upsdrv_updateinfo"); if (ReconnectFlag) { - dstate_setinfo("driver.state", "reconnect.trying"); - upslogx(LOG_WARNING, "USB device may be detached."); - upslogx(LOG_NOTICE, "USB reconnect attempt: %d.", ++CnctAttempts); - upsdebugx(4, "USB reconnect attempt: %d", CnctAttempts); + int maylog = may_log_reconnect_trying(1); + + if (maylog) + upslogx(LOG_WARNING, "USB device may be detached."); + + reconnect_trying(RECONNECT_TRYING); hd = NULL; ret = comm_driver->open_dev(&udev, &curDevice, reopen_matcher, match_by_something); if (ret < 1) { - if (CnctAttempts >= MAX_CNCT_ATTMPTS) - { - upsdebugx(4, "Exceeded max reconnect attemtps."); - fatalx(EXIT_FAILURE, "Exceeded max reconnect attempts."); - } - else - { - upslogx(LOG_INFO, "USB reconnect attempt %d failed.", CnctAttempts); + if (maylog) { + upslogx(LOG_INFO, "USB reconnect attempt failed."); upslogx(LOG_INFO, "Will try another reconnect in a bit."); - dstate_datastale(); - return; } + dstate_datastale(); + return; } upsdebugx(4, "USB device reconnected!"); ReconnectFlag = 0; /* Show good for now */ - CnctAttempts = 0; hd = &curDevice; - upslogx(LOG_NOTICE, "USB reconnect successful"); - dstate_setinfo("driver.state", "reconnect.updateinfo"); + if (maylog) + upslogx(LOG_NOTICE, "USB reconnect successful"); + + reconnect_trying(RECONNECT_UPDATEINFO); upsdrv_initinfo(); - dstate_setinfo("driver.state", "quiet"); + reconnect_trying(RECONNECT_SUCCESS); + /* dstate_dataok() managed in powervar_cx.c::PvarCommon_UpdateInfo() */ } PvarCommon_Updateinfo (); diff --git a/drivers/richcomm_usb.c b/drivers/richcomm_usb.c index c25adfa7ae..8fe1203973 100644 --- a/drivers/richcomm_usb.c +++ b/drivers/richcomm_usb.c @@ -30,7 +30,7 @@ /* driver version */ #define DRIVER_NAME "Richcomm dry-contact to USB driver" -#define DRIVER_VERSION "0.19" +#define DRIVER_VERSION "0.20" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -185,6 +185,7 @@ static int query_ups(char *reply) static void usb_comm_fail(const char *fmt, ...) { + /* FIXME [#3541]: Clean up driver custom tracking and MAX tolerance */ int ret; char why[SMALLBUF]; va_list ap; @@ -240,6 +241,7 @@ static void usb_comm_fail(const char *fmt, ...) static void usb_comm_good(void) { + /* FIXME [#3541]: Clean up driver custom tracking and MAX tolerance */ if (comm_failures == 0) { return; } @@ -797,15 +799,20 @@ void upsdrv_updateinfo(void) { char reply[REPLY_PACKETSIZE]; int ret, online, battery_normal; + int reconnecting = (udev == NULL); + + if (reconnecting) { + reconnect_trying(RECONNECT_TRYING); - if (!udev) { - dstate_setinfo("driver.state", "reconnect.trying"); ret = usb_device_open(&udev, &usbdevice, reopen_matcher, &driver_callback); + /* FIXIME: Other drivers have <1 check here */ if (ret < 0) { + dstate_datastale(); return; } - dstate_setinfo("driver.state", "reconnect.updateinfo"); + + reconnect_trying(RECONNECT_UPDATEINFO); } memset(reply, 0, sizeof(reply)); @@ -816,6 +823,8 @@ void upsdrv_updateinfo(void) usb_comm_fail("Query to UPS failed"); dstate_datastale(); + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); usb_device_close(udev); udev = NULL; @@ -826,6 +835,10 @@ void upsdrv_updateinfo(void) usb_comm_good(); dstate_dataok(); + if (reconnecting) { + reconnect_trying(RECONNECT_SUCCESS); + } + /* * 3rd bit of 4th byte indicates whether the UPS is on line (1) * or on battery (0) diff --git a/drivers/riello_usb.c b/drivers/riello_usb.c index 176a3b6a81..ffa7bb36b6 100644 --- a/drivers/riello_usb.c +++ b/drivers/riello_usb.c @@ -9,7 +9,7 @@ * Copyright (C) 2012 - Elio Parisi * Copyright (C) 2016 Eaton * Copyright (C) 2022-2024 "amikot" - * Copyright (C) 2022-2025 Jim Klimov + * Copyright (C) 2022-2026 Jim Klimov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -363,7 +363,7 @@ static int riello_command(uint8_t *cmd, uint8_t *buf, uint16_t length, uint16_t int ret; if (udev == NULL) { - dstate_setinfo("driver.state", "reconnect.trying"); + reconnect_trying(RECONNECT_TRYING); ret = usb->open_dev(&udev, &usbdevice, reopen_matcher, &driver_callback); @@ -371,9 +371,9 @@ static int riello_command(uint8_t *cmd, uint8_t *buf, uint16_t length, uint16_t if (ret < 0) return ret; - dstate_setinfo("driver.state", "reconnect.updateinfo"); + reconnect_trying(RECONNECT_UPDATEINFO); upsdrv_initinfo(); /* reconnect usb cable */ - dstate_setinfo("driver.state", "quiet"); + reconnect_trying(RECONNECT_SUCCESS); } ret = (*subdriver_command)(cmd, buf, length, buflen); @@ -423,6 +423,8 @@ static int riello_command(uint8_t *cmd, uint8_t *buf, uint16_t length, uint16_t case LIBUSB_ERROR_NOT_FOUND: /* No such file or directory */ fallthrough_case_reconnect: /* Uh oh, got to reconnect! */ + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); usb->close_dev(udev); udev = NULL; diff --git a/drivers/tripplite.c b/drivers/tripplite.c index d8404a3f15..6c8b8e9719 100644 --- a/drivers/tripplite.c +++ b/drivers/tripplite.c @@ -117,7 +117,7 @@ #include #define DRIVER_NAME "Tripp-Lite SmartUPS driver" -#define DRIVER_VERSION "0.99" +#define DRIVER_VERSION "0.100" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -135,6 +135,8 @@ static unsigned int offdelay = DEFAULT_OFFDELAY; static unsigned int startdelay = DEFAULT_STARTDELAY; static unsigned int bootdelay = DEFAULT_BOOTDELAY; +static int reconnect_ups(void); + static long hex2d(char *start, unsigned int len) { char buf[32]; @@ -405,6 +407,22 @@ void upsdrv_shutdown(void) set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); } +static int reconnect_ups(void) +{ + reconnect_trying(RECONNECT_TRYING); + + upsdrv_cleanup(); + upsdrv_initups(); + if (INVALID_FD_SER(upsfd)) + return 0; + + reconnect_trying(RECONNECT_UPDATEINFO); + upsdrv_initinfo(); + + reconnect_trying(RECONNECT_SUCCESS); + return 1; +} + void upsdrv_updateinfo(void) { static int numfails; @@ -420,7 +438,10 @@ void upsdrv_updateinfo(void) ++numfails; if (numfails > MAXTRIES) { ser_comm_fail("Data command failed: [%" PRIiSIZE "] bytes != 21 bytes.", len); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -441,7 +462,10 @@ void upsdrv_updateinfo(void) if (numfails > MAXTRIES) { ser_comm_fail("Data out of bounds: [%0ld,%3d,%3ld,%02.2f]", volt, temp, load, freq); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -452,7 +476,10 @@ void upsdrv_updateinfo(void) ++numfails; if (numfails > MAXTRIES) { ser_comm_fail("Battery voltage out of bounds: [%02.1f]", bv); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -463,7 +490,10 @@ void upsdrv_updateinfo(void) ++numfails; if (numfails > MAXTRIES) { ser_comm_fail("InVoltMax out of bounds: [%ld]", vmax); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -474,7 +504,10 @@ void upsdrv_updateinfo(void) ++numfails; if (numfails > MAXTRIES) { ser_comm_fail("InVoltMin out of bounds: [%ld]", vmin); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -486,7 +519,10 @@ void upsdrv_updateinfo(void) ++numfails; if (numfails > MAXTRIES) { ser_comm_fail("Self test is out of range: [%ld]", stest); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -494,7 +530,10 @@ void upsdrv_updateinfo(void) ++numfails; if (numfails > MAXTRIES) { ser_comm_fail("Self test returned non-numeric data."); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -502,7 +541,10 @@ void upsdrv_updateinfo(void) ++numfails; if (numfails > MAXTRIES) { ser_comm_fail("Self test out of bounds: [%ld]", stest); - dstate_datastale(); + if (!reconnect_ups()) { + dstate_datastale(); + } + numfails = 0; } return; } @@ -644,6 +686,13 @@ void upsdrv_initups(void) char *val; upsfd = ser_open(device_path); + + if (INVALID_FD_SER(upsfd)) { + upslogx(LOG_WARNING, "%s: failed to open %s", + __func__, device_path); + /* \todo: Deal with the failure */ + } + ser_set_speed(upsfd, device_path, B2400); if ((val = getval("offdelay"))) { @@ -665,6 +714,10 @@ void upsdrv_initups(void) void upsdrv_cleanup(void) { - ser_close(upsfd, device_path); + upsdebugx(1, "%s: begin", __func__); + if (VALID_FD_SER(upsfd)) { + ser_close(upsfd, device_path); + upsfd = ERROR_FD_SER; /* invalidate the closed upsfd */ + } + upsdebugx(1, "%s: end", __func__); } - diff --git a/drivers/tripplite_usb.c b/drivers/tripplite_usb.c index c5711f35a6..7fd6575484 100644 --- a/drivers/tripplite_usb.c +++ b/drivers/tripplite_usb.c @@ -137,7 +137,7 @@ #include "usb-common.h" #define DRIVER_NAME "Tripp Lite OMNIVS / SMARTPRO driver" -#define DRIVER_VERSION "0.45" +#define DRIVER_VERSION "0.46" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -279,8 +279,6 @@ static int is_smart_protocol(void) #define MAX_RECV_TRIES 10 #define RECV_WAIT_MSEC 1000 /*!< was 100 for OMNIVS; SMARTPRO units need longer */ -#define MAX_RECONNECT_TRIES 10 - #define DEFAULT_OFFDELAY 64 /*!< seconds (max 0xFF) */ #define DEFAULT_STARTDELAY 60 /*!< seconds (max 0xFFFFFF) */ #define DEFAULT_BOOTDELAY 64 /*!< seconds (max 0xFF) */ @@ -386,27 +384,29 @@ int match_by_unitid(usb_dev_handle *argudev, USBDevice_t *arghd, usb_ctrl_charbu */ static int reconnect_ups(void) { - int ret; + int ret, maylog; if (hd != NULL) { return 1; } - dstate_setinfo("driver.state", "reconnect.trying"); + maylog = may_log_reconnect_trying(1); + reconnect_trying(RECONNECT_TRYING); - upsdebugx(2, "=================================================="); - upsdebugx(2, "= device has been disconnected, try to reconnect ="); - upsdebugx(2, "=================================================="); + upsdebugx(4, "=================================================="); + upsdebugx(4, "= device has been disconnected, try to reconnect ="); + upsdebugx(4, "=================================================="); ret = comm_driver->open_dev(&udev, &curDevice, reopen_matcher, match_by_unitid); if (ret < 1) { - upslogx(LOG_INFO, "Reconnecting to UPS failed; will retry later..."); + if (maylog) + upslogx(LOG_INFO, "Reconnecting to UPS failed; will retry later..."); dstate_datastale(); return 0; } hd = &curDevice; - dstate_setinfo("driver.state", "quiet"); + reconnect_trying(RECONNECT_SUCCESS); return ret; } @@ -677,8 +677,6 @@ void upsdrv_initinfo(void); */ static void usb_comm_fail(int res, const char *msg) { - static int try_num = 0; - switch(res) { case LIBUSB_ERROR_BUSY: upslogx(LOG_WARNING, @@ -689,25 +687,18 @@ static void usb_comm_fail(int res, const char *msg) #endif default: - dstate_setinfo("driver.state", "reconnect.trying"); upslogx(LOG_WARNING, "%s: Device detached? (error %d: %s)", msg, res, nut_usb_strerror(res)); - upslogx(LOG_NOTICE, "Reconnect attempt #%d", ++try_num); hd = NULL; reconnect_ups(); if(hd) { upslogx(LOG_NOTICE, "Successfully reconnected"); - try_num = 0; - dstate_setinfo("driver.state", "reconnect.updateinfo"); + reconnect_trying(RECONNECT_UPDATEINFO); upsdrv_initinfo(); - dstate_setinfo("driver.state", "quiet"); - } else { - if(try_num > MAX_RECONNECT_TRIES) { - fatalx(EXIT_FAILURE, "Too many unsuccessful reconnection attempts"); - } + reconnect_trying(RECONNECT_SUCCESS); } break; } diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 8c18f0401a..13a46ffe9f 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -29,7 +29,7 @@ */ #define DRIVER_NAME "Generic HID driver" -#define DRIVER_VERSION "0.72" +#define DRIVER_VERSION "0.73" #define HU_VAR_WAITBEFORERECONNECT "waitbeforereconnect" @@ -1380,7 +1380,7 @@ void upsdrv_updateinfo(void) { hid_info_t *item; HIDData_t *event[MAX_EVENT_NUM], *found_data; - int i, evtCount; + int i, evtCount, reconnecting = (hd == NULL); double value; time_t now; @@ -1389,14 +1389,17 @@ void upsdrv_updateinfo(void) time(&now); /* check for device availability to set datastale! */ - if (hd == NULL) { + if (reconnecting) { /* don't flood reconnection attempts */ + int maylog; if (now < (lastpoll + poll_interval)) { return; } + maylog = may_log_reconnect_trying(1); + reconnect_trying(RECONNECT_TRYING); upsdebugx(1, "Got to reconnect!"); - if (use_interrupt_pipe == TRUE && interrupt_pipe_EIO_count > 0) { + if (use_interrupt_pipe == TRUE && interrupt_pipe_EIO_count > 0 && maylog) { upsdebugx(0, "Reconnecting. If you saw \"nut_libusb_get_interrupt: Input/Output Error\" " "or similar message in the log above, try setting \"pollonly\" flag in \"ups.conf\" " "options section for this driver!"); @@ -1412,11 +1415,15 @@ void upsdrv_updateinfo(void) interrupt_pipe_EIO_count = 0; interrupt_pipe_no_events_count = 0; + reconnect_trying(RECONNECT_UPDATEINFO); if (hid_ups_walk(HU_WALKMODE_INIT) == FALSE) { hd = NULL; return; } + + /* Claim ultimate RECONNECT_SUCCESS further below */ } + #ifdef DEBUG interval(); #endif @@ -1451,11 +1458,15 @@ void upsdrv_updateinfo(void) case LIBUSB_ERROR_NO_MEM: /* Insufficient memory */ fallthrough_reconnect: /* Uh oh, got to reconnect! */ + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); hd = NULL; return; case LIBUSB_ERROR_IO: /* I/O error */ /* Uh oh, got to reconnect, with a special suggestion! */ + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); interrupt_pipe_EIO_count++; hd = NULL; @@ -1508,10 +1519,12 @@ void upsdrv_updateinfo(void) ups_infoval_set(item, value); } + #ifdef DEBUG upsdebugx(1, "took %.3f seconds handling interrupt reports...", interval()); #endif + /* clear status buffer before beginning */ status_init(); buzzmode_init(); @@ -1544,10 +1557,15 @@ void upsdrv_updateinfo(void) status_commit(); dstate_dataok(); + #ifdef DEBUG upsdebugx(1, "took %.3f seconds handling feature reports...", interval()); #endif + + if (reconnecting) { + reconnect_trying(RECONNECT_SUCCESS); + } } void upsdrv_initinfo(void) @@ -2408,6 +2426,8 @@ static bool_t hid_ups_walk(walkmode_t mode) case LIBUSB_ERROR_NO_MEM: /* Insufficient memory */ fallthrough_reconnect: /* Uh oh, got to reconnect! */ + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); hd = NULL; return FALSE; @@ -2519,6 +2539,8 @@ static bool_t hid_ups_walk(walkmode_t mode) && (mode == HU_WALKMODE_QUICK_UPDATE || mode == HU_WALKMODE_FULL_UPDATE) ) { upsdebugx(1, "Got zero successful data polls - device may be disconnected"); + /* Not accounting just yet with reconnect_trying(RECONNECT_TRYING), + * to avoid off-by-one counter errors */ dstate_setinfo("driver.state", "reconnect.trying"); hd = NULL; return FALSE; @@ -2533,6 +2555,8 @@ static int reconnect_ups(void) char *val; int wait_before_reconnect = 0; + /* NOTE: Not calling reconnect_trying() counters/loggers here, + * as our caller upsdrv_updateinfo() does it */ dstate_setinfo("driver.state", "reconnect.trying"); /* Init time to wait before trying to reconnect (seconds) */ @@ -2564,6 +2588,10 @@ static int reconnect_ups(void) ret = comm_driver->open_dev(&udev, &curDevice, subdriver_matcher, NULL); upsdebugx(4, "Opening comm_driver returns ret=%i", ret); if (ret > 0) { + /* NOTE: Not calling reconnect_trying() counters/loggers here, + * as our caller upsdrv_updateinfo() does it. Notably, it will + * "RECONNECT_UPDATEINFO" just after this success before handing + * off the data to ultimate consumers. */ upsdebugx(0, "Device has been reconnected"); dstate_setinfo("driver.state", "quiet"); return 1;