diff --git a/Makefile.am b/Makefile.am index f3aa4fe8f7..c02d135aa4 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1623,29 +1623,34 @@ install-win-bundle-thirdparty: if test -n "$$ARCH" ; then export ARCH ; fi ; \ DESTDIR='$(DESTDIR)' ; export DESTDIR ; \ ( cd '$(DESTDIR)' || exit ; \ - GREP=$(GREP)' EGREP='$(EGREP)' \ + GREP='$(GREP)' EGREP='$(EGREP)' \ DESTDIR="" '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ + BD="`basename \"$$D\"`" ; \ + if test -e './$(bindir)/'"$$BD" && diff './$(bindir)/'"$$BD" "$$D" 2>/dev/null >/dev/null ; then \ + echo " DLL->bin $$D : skip (already installed there)" 2>&1 ; \ + continue ; \ + fi ; \ echo " DLL->bin $$D" 2>&1 ; \ cp -pf "$$D" './$(bindir)/' ; \ done ; \ ) || exit ; \ ( if test x"$(bindir)" = x"$(sbindir)" ; then exit 0 ; fi ; \ cd '$(DESTDIR)/$(sbindir)' || exit ; \ - GREP=$(GREP)' EGREP='$(EGREP)' \ + GREP='$(GREP)' EGREP='$(EGREP)' \ '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->sbin $$D" 2>&1 ; \ + echo " DLL->sbin $$D (symlink from bindir)" 2>&1 ; \ ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit ; \ ( if test x"$(driverexecdir)" = x"$(bindir)" ; then exit 0 ; fi ; \ if test x"$(driverexecdir)" = x"$(sbindir)" ; then exit 0 ; fi ; \ cd '$(DESTDIR)/$(driverexecdir)' || exit ; \ - GREP=$(GREP)' EGREP='$(EGREP)' \ + GREP='$(GREP)' EGREP='$(EGREP)' \ '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->drv $$D" 2>&1 ; \ + echo " DLL->drv $$D (symlink from bindir)" 2>&1 ; \ ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit ; \ @@ -1654,10 +1659,10 @@ install-win-bundle-thirdparty: if test x"$(cgiexecdir)" = x"$(sbindir)" ; then exit 0 ; fi ; \ if test x"$(driverexecdir)" = x"$(cgiexecdir)" ; then exit 0 ; fi ; \ cd '$(DESTDIR)/$(cgiexecdir)' || exit ; \ - GREP=$(GREP)' EGREP='$(EGREP)' \ + GREP='$(GREP)' EGREP='$(EGREP)' \ '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->cgi $$D" 2>&1 ; \ + echo " DLL->cgi $$D (symlink from bindir)" 2>&1 ; \ ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit ; \ @@ -1666,10 +1671,10 @@ install-win-bundle-thirdparty: if test x"$(libexecdir)" = x"$(driverexecdir)" ; then exit 0 ; fi ; \ if test x"$(libexecdir)" = x"$(cgiexecdir)" ; then exit 0 ; fi ; \ cd '$(DESTDIR)/$(libexecdir)' || exit ; \ - GREP=$(GREP)' EGREP='$(EGREP)' \ + GREP='$(GREP)' EGREP='$(EGREP)' \ '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->libexec $$D" 2>&1 ; \ + echo " DLL->libexec $$D (symlink from bindir)" 2>&1 ; \ ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit diff --git a/NEWS.adoc b/NEWS.adoc index b71decb216..2f2df4cfaa 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -83,6 +83,9 @@ https://github.com/networkupstools/nut/milestone/12 * Revised detection of (relative) paths to program and configuration files near the currently running program in NUT for Windows builds. [issues #3207, #3063, #3065, #3219] + * Fixed thread-safety of IP address printout in `libupsclient` method + `upscli_tryconnect()` (practical bug seen in `nut-scanner` parallel scans). + [issue #3234] - `asem`, `bestfortress`, `bestuferrups`, `bicker_ser`, `everups`, `metasys`, `masterguard`, `mge-utalk`, `oneac`, `phoenixcontact_modbus`, `pijuice`, @@ -98,6 +101,10 @@ https://github.com/networkupstools/nut/milestone/12 - Introduced a new NUT driver named `meanwell_ntu` which provides support for the Mean Well NTU series hybrid inverter and UPS units. [PR #3206] + - Introduced a new NUT driver named `must_ep2000pro` which provides support + for the MUST EP2000Pro compatible, line-interactive UPS models which talk + serial-protocol modbus protocol. [PR #3228] + - `nhs_ser` driver updates: * Driver source code includes a table to map baud rates (defined by the OS as C macros) to numbers and strings more useful to the driver program. @@ -355,6 +362,21 @@ several `FSD` notifications into one executed action. [PR #3097] system conforming to FHS-3.0 standard where that location is absent or is a symlink, while `/run` exists and is a true directory. [#3099] + - Augmented `nut.exe` launcher with `status` and `restart` actions, and + general ability to debug-log the activities during service management + operations. (Re-)registration of the "Network UPS Tools" service should + now populate a nice description of it. The `-U` (uninstall) action should + now also try to stop the service first. [PR #3235] + + - Fixed `LISTEN *` handling for `upsd.exe` in NUT for Windows builds. [PR #3237] + + - Revised WIN32 `WSAStartup()` and registration of `atexit(WSACleanup)` to + only be done once per program (and cleanups to be always registered); this + impacts the C `libupsclient` and C++ `libnutclient` libraries (and so most + NUT clients and tools which use the former, and `nutconf` which uses the + latter), the `apcupsd` driver, `upsd` data server, and `nut-scanner` tool. + [PR #3237] + - Revised CI and deliverable scripts, and Makefile recipes, to not use the verbatim `grep -E` (loudly preferred by newer systems, but may be absent on older ones) after all, nor use `egrep` (loudly disliked by diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 1059cb1ffe..9ca1e7b572 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -63,6 +63,12 @@ Changes from 2.8.4 to 2.8.5 usually override `--with-pidpath` anyway to provide a dedicated location for NUT `root`-owned daemon PID files. [#3099] +- The autoconf managed macro for `--with-port` option was renamed from plain + `PORT` to less ambiguous `NUT_PORT` to avoid potential conflicts with any + third-party code. If anyone did use this macro in their custom builds or + integrations, some renaming (or stubbed definitions to the effect of + `PORT=NUT_PORT`) may be needed. [#3238] + - In drivers using the common `main.c` and `main.h` framework, introduced a `void upsdrv_tweak_prognames(void)` required method (may be no-op) to optionally tweak `prognames[]` entries now that there is certain support diff --git a/clients/nutclient.cpp b/clients/nutclient.cpp index 2d86e5343e..7aaf1d673a 100644 --- a/clients/nutclient.cpp +++ b/clients/nutclient.cpp @@ -285,8 +285,14 @@ void Socket::connect(const std::string& host, uint16_t port) HANDLE event = NULL; unsigned long argp; - WSADATA WSAdata; - WSAStartup(2,&WSAdata); + /* Required ritual before calling any socket functions */ + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } #endif /* WIN32 */ _sock = INVALID_SOCKET; @@ -794,7 +800,7 @@ bool Client::hasFeature(const Feature& feature) TcpClient::TcpClient(): Client(), _host("localhost"), -_port(3493), +_port(NUT_PORT), _timeout(0), _socket(new internal::Socket) { diff --git a/clients/nutclient.h b/clients/nutclient.h index 19dbdcfde4..291eebf13f 100644 --- a/clients/nutclient.h +++ b/clients/nutclient.h @@ -33,7 +33,15 @@ /* See include/common.h for details behind this */ #ifndef NUT_UNUSED_VARIABLE -#define NUT_UNUSED_VARIABLE(x) (void)(x) +# define NUT_UNUSED_VARIABLE(x) (void)(x) +#endif + +/* Should be defined via autoconf in include/config.h - + * if that is included earlier by the program code. + * If not - got a fallback here: + */ +#ifndef NUT_PORT +# define NUT_PORT 3493 #endif namespace nut @@ -389,7 +397,7 @@ class TcpClient : public Client * \param host Server host name. * \param port Server port. */ - TcpClient(const std::string& host, uint16_t port = 3493); + TcpClient(const std::string& host, uint16_t port = NUT_PORT); ~TcpClient() override; /** @@ -397,7 +405,7 @@ class TcpClient : public Client * \param host Server host name. * \param port Server port. */ - void connect(const std::string& host, uint16_t port = 3493); + void connect(const std::string& host, uint16_t port = NUT_PORT); /** * Connect to the server. diff --git a/clients/upsclient.c b/clients/upsclient.c index 36263e8b06..b72d1c36b7 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -1019,9 +1019,16 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags HANDLE event = NULL; unsigned long argp; - WSADATA WSAdata; - WSAStartup(2,&WSAdata); + /* Required ritual before calling any socket functions */ + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } #endif /* WIN32 */ + if (!ups) { return -1; } @@ -1164,9 +1171,11 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags ups->upserror == UPSCLI_ERR_CONNFAILURE && ups->syserrno == ETIMEDOUT ) { - const char *addrstr = inet_ntopAI(ai); + const char *addrstr = xinet_ntopAI(ai); upslogx(LOG_WARNING, "%s: Connection to host timed out: '%s'", __func__, (addrstr && *addrstr) ? addrstr : NUT_STRARG(host)); + if (addrstr) + free((char*)addrstr); break; } continue; @@ -1694,7 +1703,7 @@ int upscli_splitname(const char *buf, char **upsname, char **hostname, uint16_t return -1; } - *port = PORT; + *port = NUT_PORT; return 0; } @@ -1750,7 +1759,7 @@ int upscli_splitaddr(const char *buf, char **hostname, uint16_t *port) /* no port specified, use default */ if (((s = strtok_r(NULL, "\0", &last)) == NULL) || (*s != ':')) { - *port = PORT; + *port = NUT_PORT; return 0; } } else { @@ -1763,7 +1772,7 @@ int upscli_splitaddr(const char *buf, char **hostname, uint16_t *port) /* no port specified, use default */ if (s == NULL) { - *port = PORT; + *port = NUT_PORT; return 0; } } diff --git a/common/common.c b/common/common.c index b4bec05728..10eb7ac73e 100644 --- a/common/common.c +++ b/common/common.c @@ -978,22 +978,8 @@ char * getprocname(pid_t pid) /* Fall through to try /proc etc. if available */ } else { - LPVOID WinBuf; - DWORD WinErr = GetLastError(); - FormatMessage( - FORMAT_MESSAGE_MAX_WIDTH_MASK | - FORMAT_MESSAGE_ALLOCATE_BUFFER | - FORMAT_MESSAGE_FROM_SYSTEM | - FORMAT_MESSAGE_IGNORE_INSERTS, - NULL, - WinErr, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), - (LPTSTR) &WinBuf, - 0, NULL ); - - upsdebugx(3, "%s: failed to get WIN32 process info: %s", - __func__, (char *)WinBuf); - LocalFree(WinBuf); + upsdebug_with_errno(3, + "%s: failed to get WIN32 process info", __func__); } } #endif /* WIN32 */ @@ -1764,15 +1750,18 @@ char * getfullpath(char * relative_path) if (!len_PREFIX) { len_PREFIX = strlen(PREFIX); - upsdebugx(6, "%s: PREFIX (len=%" PRIuSIZE"):\t'%s'", __func__, len_PREFIX, PREFIX); + upsdebugx(6, "%s: built-in PREFIX (len=%" PRIuSIZE"):\t'%s'", __func__, len_PREFIX, PREFIX); } if (GetModuleFileName(NULL, buf, sizeof(buf)) == 0) { + upsdebug_with_errno(3, + "%s: failed to get WIN32 process info " + "with GetModuleFileName() of current program", __func__); return NULL; } - upsdebugx(5, "%s: relative_path:\t'%s'", __func__, NUT_STRARG(relative_path)); - upsdebugx(5, "%s: GetModuleFileName:\t'%s'", __func__, buf); + upsdebugx(5, "%s: passed relative_path:\t'%s'", __func__, NUT_STRARG(relative_path)); + upsdebugx(5, "%s: GetModuleFileName (buf):\t'%s'", __func__, buf); /* remove trailing executable name and its preceeding slash */ last_slash = strrchr(buf, '\\'); @@ -1836,7 +1825,7 @@ char * getfullpath(char * relative_path) */ int depth = 0; - for (last_slash = prefix_in_buf + len_PREFIX; last_slash; last_slash++) { + for (last_slash = prefix_in_buf + len_PREFIX; *last_slash; last_slash++) { if ( (*last_slash == '/' || *last_slash == '\\') && (*(last_slash+1) != '\0') && (*(last_slash+1) != '\\') @@ -5348,66 +5337,209 @@ int match_regex_hex(const regex_t *preg, const int n) } #endif /* HAVE_LIBREGEX */ -/* NOT THREAD SAFE! +/* NOT THREAD-SAFE WHERE MARKED! * Helpers to convert one IP address to string from different structure types - * Return pointer to internal buffer, or NULL and errno upon errors */ -const char *inet_ntopSS(struct sockaddr_storage *s) -{ - static char str[40]; + * Return pointer to provided (or internal, or allocated) buffer, or NULL and + * errno upon errors. + * WARNING: Do not fence this compilation with NUT_WANT_INET_NTOP_XX macro as + * used in header (although maybe consider moving it to another libcommon*?) + */ +/* https://stackoverflow.com/a/29147085/4715872 + * obviously INET6_ADDRSTRLEN is expected to be larger + * than INET_ADDRSTRLEN, but this may be required in case + * if for some unexpected reason IPv6 is not supported, and + * INET6_ADDRSTRLEN is defined as 0 + * but this is not very likely and I am aware of no cases of + * this in practice (editor) + */ +#define INETADDRBUF_SIZE (MAX(40, MAX(INET6_ADDRSTRLEN, INET_ADDRSTRLEN) + 1)) + +const char *inet_ntopSS(struct sockaddr_storage *s, char *addrstr, size_t addrstrsz) +{ if (!s) { errno = EINVAL; return NULL; } +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif switch (s->ss_family) { case AF_INET: - return inet_ntop (AF_INET, &(((struct sockaddr_in *)s)->sin_addr), str, 16); + return inet_ntop (AF_INET, &(((struct sockaddr_in *)s)->sin_addr), addrstr, MIN(16, addrstrsz)); case AF_INET6: - return inet_ntop (AF_INET6, &(((struct sockaddr_in6 *)s)->sin6_addr), str, 40); + return inet_ntop (AF_INET6, &(((struct sockaddr_in6 *)s)->sin6_addr), addrstr, MIN(40, addrstrsz)); default: errno = EAFNOSUPPORT; return NULL; } +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif +} + +const char *inet_ntopSS_thread_unsafe(struct sockaddr_storage *s) +{ +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + static char addrstr[INETADDRBUF_SIZE]; +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + return inet_ntopSS(s, addrstr, sizeof(addrstr)); } -const char *inet_ntopAI(struct addrinfo *ai) +const char *xinet_ntopSS(struct sockaddr_storage *s) +{ +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + char *addrstr = xcalloc(INETADDRBUF_SIZE, sizeof(char*)); + const char *ret; + + if (!addrstr) + return NULL; + ret = inet_ntopSS(s, addrstr, INETADDRBUF_SIZE); +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + if (ret) /* non-null pointer to our buffer */ + return addrstr; + + /* if error */ + free(addrstr); + return NULL; +} + +const char *inet_ntopAI(struct addrinfo *ai, char *addrstr, size_t addrstrsz) { /* Note: below we manipulate copies of ai - cannot cast into * specific structure type pointers right away because: * error: cast from 'struct sockaddr *' to 'struct sockaddr_storage *' * increases required alignment from 2 to 8 */ - /* https://stackoverflow.com/a/29147085/4715872 - * obviously INET6_ADDRSTRLEN is expected to be larger - * than INET_ADDRSTRLEN, but this may be required in case - * if for some unexpected reason IPv6 is not supported, and - * INET6_ADDRSTRLEN is defined as 0 - * but this is not very likely and I am aware of no cases of - * this in practice (editor) - */ - static char addrstr[(INET6_ADDRSTRLEN > INET_ADDRSTRLEN ? INET6_ADDRSTRLEN : INET_ADDRSTRLEN) + 1]; - if (!ai || !ai->ai_addr) { errno = EINVAL; return NULL; } addrstr[0] = '\0'; +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif switch (ai->ai_family) { case AF_INET: { struct sockaddr_in addr_in; memcpy(&addr_in, ai->ai_addr, sizeof(addr_in)); - return inet_ntop(AF_INET, &(addr_in.sin_addr), addrstr, INET_ADDRSTRLEN); + return inet_ntop(AF_INET, &(addr_in.sin_addr), addrstr, MIN(INET_ADDRSTRLEN, addrstrsz)); } case AF_INET6: { struct sockaddr_in6 addr_in6; memcpy(&addr_in6, ai->ai_addr, sizeof(addr_in6)); - return inet_ntop(AF_INET6, &(addr_in6.sin6_addr), addrstr, INET6_ADDRSTRLEN); + return inet_ntop(AF_INET6, &(addr_in6.sin6_addr), addrstr, MIN(INET6_ADDRSTRLEN, addrstrsz)); } default: errno = EAFNOSUPPORT; return NULL; } +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif +} + +const char *inet_ntopAI_thread_unsafe(struct addrinfo *ai) +{ +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + static char addrstr[INETADDRBUF_SIZE]; +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + return inet_ntopAI(ai, addrstr, sizeof(addrstr)); +} + +const char *xinet_ntopAI(struct addrinfo *ai) +{ +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + char *addrstr = xcalloc(INETADDRBUF_SIZE, sizeof(char*)); + const char *ret; + + if (!addrstr) + return NULL; + ret = inet_ntopAI(ai, addrstr, INETADDRBUF_SIZE); +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + if (ret) /* non-null pointer to our buffer */ + return addrstr; + + /* if error */ + free(addrstr); + return NULL; } diff --git a/configure.ac b/configure.ac index 899c28a1fa..c4c3c6e02c 100644 --- a/configure.ac +++ b/configure.ac @@ -4734,11 +4734,11 @@ AC_MSG_CHECKING(network port number) NUT_ARG_WITH([port], [PORTNUM], [port for network communications], [3493]) AS_IF([(test 0 -lt "${nut_with_port}" && test 65536 -gt "${nut_with_port}") 2>/dev/null], - [PORT="${nut_with_port}"], + [NUT_PORT="${nut_with_port}"], [AC_MSG_ERROR(invalid option --with(out)-port - see docs/configure.txt)] ) -AC_MSG_RESULT(${PORT}) -AC_DEFINE_UNQUOTED(PORT, ${PORT}, [Port for network communications]) +AC_MSG_RESULT(${NUT_PORT}) +AC_DEFINE_UNQUOTED(NUT_PORT, ${NUT_PORT}, [Port for NUT network communications]) dnl --------------------------------------------------------------------- AC_MSG_CHECKING(facility for syslog) @@ -6748,7 +6748,7 @@ AC_SUBST(LDFLAGS_NUT_RPATH) AC_SUBST(LDFLAGS_NUT_RPATH_CXX) AC_SUBST(DRVPATH) AC_SUBST(SBINDIR) -AC_SUBST(PORT) +AC_SUBST(NUT_PORT) AC_SUBST(RUN_AS_USER) AC_SUBST(RUN_AS_GROUP) AC_SUBST(SUN_LIBUSB) diff --git a/data/driver.list.in b/data/driver.list.in index e885b0fb29..b1b2cfa203 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -963,6 +963,8 @@ "Minibox" "ups" "5" "openUPS Intelligent UPS" "USB port" "usbhid-ups" +"Must" "ups" "4" "EP2000Pro" "" "must_ep2000pro" + "Mustek" "ups" "2" "Powermust" "400VA Plus" "blazer_ser" "Mustek" "ups" "2" "Powermust" "600VA Plus" "blazer_ser" "Mustek" "ups" "2" "Powermust" "800VA Pro" "blazer_ser" diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 0a2f1f916a..8ca0d5259a 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -1362,6 +1362,7 @@ SRC_MODBUS_PAGES = \ generic_modbus.txt \ huawei-ups2000.txt \ socomec_jbus.txt \ + must_ep2000pro.txt \ adelsystem_cbi.txt \ apc_modbus.txt @@ -1370,6 +1371,7 @@ INST_MAN_MODBUS_PAGES = \ generic_modbus.$(MAN_SECTION_CMD_SYS) \ huawei-ups2000.$(MAN_SECTION_CMD_SYS) \ socomec_jbus.$(MAN_SECTION_CMD_SYS) \ + must_ep2000pro.$(MAN_SECTION_CMD_SYS) \ adelsystem_cbi.$(MAN_SECTION_CMD_SYS) \ apc_modbus.$(MAN_SECTION_CMD_SYS) @@ -1390,6 +1392,7 @@ INST_HTML_MODBUS_MANS = \ generic_modbus.html \ huawei-ups2000.html \ socomec_jbus.html \ + must_ep2000pro.html \ adelsystem_cbi.html \ apc_modbus.html DIST_ALL_HTML_PAGES += $(INST_HTML_MODBUS_MANS) diff --git a/docs/man/must_ep2000pro.txt b/docs/man/must_ep2000pro.txt new file mode 100644 index 0000000000..49240978a0 --- /dev/null +++ b/docs/man/must_ep2000pro.txt @@ -0,0 +1,124 @@ +MUST_EP2000Pro(8) +================= + +NAME +---- + +must_ep2000pro - Driver for MUST EP2000Pro UPS with +RS-232 serial Modbus connection. + +SYNOPSIS +-------- + +*must_ep2000pro* -h + +*must_ep2000pro* -a 'DEVICE_NAME' ['OPTIONS'] + +NOTE: This man page only documents the hardware-specific features of the +'must_ep2000pro' driver. For information about the core driver, see +linkman:nutupsdrv[8]. + +SUPPORTED HARDWARE +------------------ + +This driver supports MUST EP2000Pro compatible, line-interactive +UPS with the following characteristics: + +1. 12V or 24V external battery + +2. Connection: RS-232 or USB (via RS232-to-USB converter dongle) + +Some models that may be covered by this driver: + +* Original EP200Pro +* Energy Smart.2 +* Luxeon UPS-500ZR +* Bineos EP20-1000 PRO +* any other work with original program from MUST + +Currently, it has only been tested on the following model: + +* Energy Smart.2 600W + +Generally, these device do not serve any identification data, so +autodetection is not possible. Only one documented piece of info is +"software version" with unknown meaning. + +The `must_ep2000pro` driver uses the libmodbus project, for Modbus +implementation. + +INSTALLATION +------------ + +This driver should be built by default if libmodbus and development headers +are available. You can force the `configure` script to build it with the +following arguments: + + :; ./configure --with-serial=yes --with-modbus=yes + +You also need to give proper (R/W) permissions on the local serial device +file to allow the NUT driver run-time user to access it. This may need +additional setup for start-up scripting, udev or upower rules, to apply +the rights on every boot -- especially if your device nodes are tracked +by a virtual filesystem. + +For example, a USB-to-serial converter can be identified as `/dev/ttyACM0` +or `/dev/ttyUSB0` on Linux, or `/dev/ttyU0` on FreeBSD (note the capital "U"). +A built-in serial port can be identified as `/dev/ttyS0` on Linux or one of +`/dev/cua*` names on FreeBSD. + +EXTRA ARGUMENTS +--------------- + +This driver also supports the following optional settings: + +*lowbatt*='num':: +Set the value of `battery.charge.low` to 'num'. Device itself has only switch-off +on low battery voltage. + +INSTANT COMMANDS +---------------- + +This driver supports some instant commands (see linkman:upscmd[8]): + +*beeper.enable*:: +Enable the UPS beeper + +*beeper.disable*:: +Disable the UPS beeper + +*shutdown.return*:: +Turn off the load and wait for the power to return + +KNOWN PROBLEMS +-------------- + +If you run the *shutdown.return* command with mains present it does nothing. + +If you run the *shutdown.return* command without mains present it immediately +powers off the device. + +There is no setting in device documentation to specify a shutdown delay. + +On development side: to write even a single register, you must use the +"write multiple registers" command. + +AUTHOR +------ + +Mikhail Mironov + +SEE ALSO +-------- + +The core driver: +~~~~~~~~~~~~~~~~ + +linkman:nutupsdrv[8] + +Internet resources: +~~~~~~~~~~~~~~~~~~~ + +* The NUT (Network UPS Tools) home page: https://www.networkupstools.org/ +* libmodbus home page: http://libmodbus.org +* Simple data reader for similar device: https://github.com/reverieline/ep2000pro diff --git a/docs/man/nut.exe.txt b/docs/man/nut.exe.txt index b5c0b28b51..ed4e233bc3 100644 --- a/docs/man/nut.exe.txt +++ b/docs/man/nut.exe.txt @@ -30,23 +30,25 @@ Windows system as applicable. Beside launching or stopping a set of the NUT programs in certain cases, this helper program also allows to register (or un-register) itself as a Windows service. To actually manage the service from command line you can -execute the Windows `net` command, e.g.: +execute the Windows `net` or `sc` commands, e.g.: ---- net stop "Network UPS Tools" net start "Network UPS Tools" +sc query "Network UPS Tools" ---- You can also execute `nut start` to automatically register the service (if not yet registered) and start it, and `nut stop` to stop the service -(if registered and running). +(if registered and running), and `nut status` to check on it (if registered). Note that for a Windows machine to act as a NUT data server for further clients, you may have to add Windows Firewall rules to allow incoming connections (by default to port `3493/tcp`), e.g. using PowerShell to elevate (alternately right-click a "Command Prompt" shortcut and select "Run as administrator"), and execute `netsh` to actually configure the -needed "Advanced Firewall" rule: +needed "Advanced Firewall" rule (align the path to binary with your +installation): ---- REM Elevate to administrator status then run netsh to add firewall rule. @@ -90,10 +92,10 @@ The non-trivial debug level would be passed down to launched NUT programs. Primarily useful for troubleshooting with the non-service mode. *-I*:: -Install as a Windows service called "Network UPS Tools". +Install as a Windows service called "Network UPS Tools". Does not start it. *-U*:: -Uninstall the Windows service. +(Try to stop and) Uninstall the Windows service called "Network UPS Tools". *-N*:: Run once in non-service mode (for troubleshooting). @@ -105,6 +107,13 @@ and try to start this service. *stop*:: Try to stop a Windows service called "Network UPS Tools". +*restart*:: +Try to stop a Windows service called "Network UPS Tools" (do not fail if +we can not), then try to install it (if not yet done) and start it. + +*status*:: +Try to query status of a Windows service called "Network UPS Tools". + DIAGNOSTICS ----------- @@ -116,7 +125,13 @@ initialization while not running in a service context. Most of normal logging from *nut.exe* goes to the Windows Event Log. Launched NUT programs may emit messages of their own; their fate when no -console is attached is questionable. +console is attached is questionable. The service may be cheerfully "running" +(as long as `MODE=...` is set in `nut.conf`), but specific daemons under it +might refuse to start. For example, it may be troublesome to see (without +perusing the Windows Event Log, Applications section) whether they are missing +some configuration file or entry. You may have to launch respective daemons +manually to check what they complain about, or use *nut.exe -N* mode, +all with raised NUT debug verbosity settings. SEE ALSO -------- diff --git a/docs/nut.dict b/docs/nut.dict index 35788586df..b57ca2c10a 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3652 utf-8 +personal_ws-1.1 en 3659 utf-8 AAC AAS ABI @@ -132,6 +132,7 @@ Berge Berzonis Bieringer BigServers +Bineos BlaXwan BlackOut BladeUPS @@ -691,6 +692,7 @@ LowBatt Loyer Lua Luca +Luxeon Lygre Lynge MANPATH @@ -765,6 +767,7 @@ MinGW MiniCOL MiniGuard Minislot +Mironov Moar Modbus ModemManager @@ -1480,6 +1483,8 @@ WIP WIPO WMNut WS +WSACleanup +WSAStartup WSDIR WSE WSL @@ -1636,6 +1641,7 @@ aspell ast async atcl +atexit ats aug augeas @@ -3080,6 +3086,7 @@ safenet salicru sbin sbindir +sc scanopts scd sched diff --git a/drivers/Makefile.am b/drivers/Makefile.am index a1eb4a7315..0ccd36f2e7 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -98,7 +98,7 @@ SERIAL_USB_DRIVERLIST = \ nutdrv_qx NEONXML_DRIVERLIST = netxml-ups MACOSX_DRIVERLIST = macosx-ups -MODBUS_DRIVERLIST = phoenixcontact_modbus generic_modbus huawei-ups2000 socomec_jbus adelsystem_cbi apc_modbus +MODBUS_DRIVERLIST = phoenixcontact_modbus generic_modbus huawei-ups2000 socomec_jbus adelsystem_cbi apc_modbus must_ep2000pro LINUX_I2C_DRIVERLIST = asem pijuice hwmon_ina219 POWERMAN_DRIVERLIST = powerman-pdu IPMI_DRIVERLIST = nut-ipmipsu @@ -416,6 +416,11 @@ huawei_ups2000_LDADD = $(LDADD_DRIVERS_SERIAL) $(LIBMODBUS_LIBS) socomec_jbus_SOURCES = socomec_jbus.c socomec_jbus_LDADD = $(LDADD_DRIVERS_SERIAL) $(LIBMODBUS_LIBS) +# MUST EP2000Pro driver +# (this is a Modbus driver) +must_ep2000pro_SOURCES = must_ep2000pro.c +must_ep2000pro_LDADD = $(LDADD_DRIVERS_SERIAL) $(LIBMODBUS_LIBS) + # Linux I2C drivers asem_LDADD = $(LDADD_DRIVERS) $(LIBI2C_LIBS) asem_SOURCES = asem.c diff --git a/drivers/apcupsd-ups.c b/drivers/apcupsd-ups.c index 0a0bd4cae4..fd57823637 100644 --- a/drivers/apcupsd-ups.c +++ b/drivers/apcupsd-ups.c @@ -396,9 +396,14 @@ void upsdrv_initups(void) int v; #ifdef WIN32 - WSADATA WSAdata; - WSAStartup(2,&WSAdata); - atexit((void(*)(void))WSACleanup); + /* Required ritual before calling any socket functions */ + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } #endif /* WIN32 */ /* NOTE: in case of errors below we set "port" to 0, @@ -484,7 +489,7 @@ void upsdrv_initups(void) upslogx(LOG_INFO, "Will poll apcupsd at IPv%s address %s port %" PRIu16, (host->ai_family == AF_INET ? "4" : (host->ai_family == AF_INET6 ? "6" : "?")), - NUT_STRARG(inet_ntopAI(host)), port); + NUT_STRARG(inet_ntopAI_thread_unsafe(host)), port); } else if(res) { freeaddrinfo(res); } diff --git a/drivers/must_ep2000pro.c b/drivers/must_ep2000pro.c new file mode 100644 index 0000000000..1abc093e47 --- /dev/null +++ b/drivers/must_ep2000pro.c @@ -0,0 +1,567 @@ +/* must_ep2000pro.c - Driver for MUST EP2000 Pro UPS + * + * Copyright (C) + * 2025 Mikhail Mironov + * + * Based on documentation received from manafacturer by email + * + * 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 + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "main.h" +#include + +#if !(defined NUT_MODBUS_LINKTYPE_STR) +# define NUT_MODBUS_LINKTYPE_STR "unknown" +#endif + +#define DRIVER_NAME "MUST EP2000Pro driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" +#define DRIVER_VERSION "0.01" + +#ifndef STRFY0 +# define STRFY0(x) #x +#endif +#ifndef STRFY +# define STRFY(x) STRFY0(x) +#endif + +#define CHECK_BIT(var,pos) ((var) & (1<<(pos))) +#define BAUD_RATE 9600 +#define PARITY 'N' +#define DATA_BIT 8 +#define STOP_BIT 1 +#define MODBUS_SLAVE_ID 10 + +#define BATTERY_LOW_DEFAULT 15 +#define BATTERY_LOW_NAME "lowbatt" + +/* Variables */ +static modbus_t *modbus_ctx = NULL; +static uint16_t battery_low = BATTERY_LOW_DEFAULT; + +static int mb_read(int addr, int nb, uint16_t * dest); +static int mb_write(int addr, const uint16_t value); + +/* driver description structure */ +upsdrv_info_t upsdrv_info = { + DRIVER_NAME, + DRIVER_VERSION, + "Mikhail Mironov \n", + DRV_BETA, + {NULL} +}; + +/* fault description struct */ +struct fault { + int code; /* fault code */ + char *desc; /* description */ +}; +typedef struct fault fault_t; + +static fault_t fault_list [] = { + /* From documentation */ + {2, "[Inverter over temperature]"}, + {3, "[Battery voltage is too high]"}, + {4, "[Battery voltage is too low]"}, + {5, "[Output short circuited]"}, + {6, "[Inverter output voltage is high]"}, + {7, "[Overload]"}, + {11, "[Main relay failed]"}, + {41, "[Inverter grid voltage is low]"}, + {42, "[Inverter grid voltage is high]"}, + {43, "[Inverter grid under frequency]"}, + {44, "[Inverter grid over frequency]"}, + {45, "[AVR failed]"}, + {51, "[Inverter over current protection error]"}, + {58, "[Inverter output voltage is too low]"}, + + /* Found in internet */ + {1, "[Fan is locked when inverter is off]"}, + {8, "[Inverter bus voltage is too high]"}, + {9, "[Bus soft start failed]"}, + {21, "[Inverter output voltage sensor error]"}, + {22, "[Inverter grid voltage sensor error]"}, + {23, "[Inverter output current sensor error]"}, + {24, "[Inverter grid current sensor error]"}, + {25, "[Inverter load current sensor error]"}, + {26, "[Inverter grid over current error]"}, + {27, "[Inverter radiator over temperature]"}, + {31, "[Solar charger battery voltage class error]"}, + {32, "[Solar charger current sensor error]"}, + {33, "[Solar charger current is uncontrollable]"}, + {52, "[Inverter bus voltage is too low]"}, + {53, "[Inverter soft start failed]"}, + {54, "[Over DC voltage in AC output]"}, + {56, "[Battery connection is open]"}, + {57, "[Inverter control current sensor error]"}, + {61, "[Fan is locked when inverter is on]"}, + {62, "[Fan2 is locked when inverter is on]"}, + {63, "[Battery is over-charged]"}, + {64, "[Low battery]"}, + {67, "[Overload]"}, + {70, "[Output power derating]"}, + {72, "[Solar charger stops due to low battery]"}, + {73, "[Solar charger stops due to high PV voltage]"}, + {74, "[Solar charger stops due to over load]"}, + {75, "[Solar charger over temperature]"}, + {76, "[PV charger communication error]"}, + {77, "[Parameter error]"}, + + /* End of list */ + {0, NULL} +}; + +enum state { + INIT=1, + SELF_CHECK, + BACKUP, + LINE, + STOP, + POWER_OFF, + GRID_CHG, + SOFT_START, +}; +typedef enum state state_t; + +/** + * Device register map: + * + * RO Block + * 30000 Type of machine (Reserved) + * 30001 Software version + * 30002 Work state (See state enum) + * 30003 BatClass, V (12, 24) + * 30004 Rated power, W + * 30005 Grid voltage, 0.1V + * 30006 Grid frequency, 0.1Hz + * 30007 Output Voltage, 0.1V + * 30008 Output Frequency, 0.1Hz + * 30009 Load current, 0.1A + * 30010 Load Power, W + * 30011 Reserved + * 30012 Load percent, % + * 30013 LoadState (0: LOAD_NORMAL, 1: LOAD_ALARM, 2: OVER_LOAD) + * 30014 Battery Voltage, 0.1V + * 30015 Battery current, 0.1A + * 30016 Reserved + * 30017 Battery SOC, % + * 30018 Transformer Temp, ℃ + * 30019 AVR State (0: AVR_BYPASS, 1: AVR_STEPDWON, 2: AVR_BOOST, 4: AVR_EBOOST) + * 30020 Buzzer State (0: BUZZ_OFF, 1: BUZZ_BLEW, 2: BUZZ_ALARM) + * 30021 System fault ID (See fault_list) + * 30022 System alarm ID (Bit field, see below) + * 30023 Charge Stage (0: CC, 1: CV, 2: FV) + * 30024 Charge Flag (1: charge, 0: no charge) + * 30025 Main SW (1: on, 0: off) + * 30026 DelayType (0: standard, 1: long delay, 2: integration) + * + * Alarm ID: + * Bit 0 + * Bit 1 Battery voltage is too Low + * Bit 2 Over load + * Bit 3 Battery voltage is too high + * Bit 4 Parameter error + * Bit 5-15 Reserved + * + * RW Block: + * 31000 Grid frequency type (0: 50Hz, 1: 60Hz) + * 31001 Grid voltage Type, V (220, 230) + * 31002 Shutdown voltage, 0.1V (12V: 100-120; 24V: x2) + * 31003 Absorption charge voltage, 0.1V (12V: 138-145; 24V: x2) + * 31004 Float Charge Voltage, 0.1V (12V: 135-145; 24V: x2) + * 31005 Bulk Current, A (5 10 15 20 25 30; Max current depends on Rated power and BatClass) + * 31006 Buzzer (1: silence, 0: normal) + * 31007 Enable grid charge (0: enable, 1: disable) + * 31009 Enable backlight (1: enable, 0: disable) + * 31016 Utility power on (0: enable, 1: disable) + * 31017 Enable over load recover (1: enable, 0: disable) + * + * Write only block. 0 - ignore, 1 - action + * 32000 Restore factory settings + * 32001 Remote reset + * 32002 Remote shutdown + */ + +static int instcmd(const char *cmdname, const char *extra); +static int setvar(const char *varname, const char *value); + +void upsdrv_initinfo(void) +{ + uint16_t base_reg[5]; + uint16_t conf_reg[4]; + int r; + int tmp; + + upsdebugx(2, "upsdrv_initinfo"); + + /* check battery low from arguments */ + if (testvar(BATTERY_LOW_NAME)) { + tmp = atoi(getval(BATTERY_LOW_NAME)); + if (tmp < 1 || tmp > 99) { + fatalx(EXIT_FAILURE, "Given value '%d' of " BATTERY_LOW_NAME " is out of range (1-99)", tmp); + } + battery_low = (uint16_t) tmp; + } + + dstate_setinfo("battery.charge.low", "%u", battery_low); + + dstate_setinfo("ups.mfr", "MUST"); + dstate_setinfo("ups.model", "EP2000Pro"); + + upsdebugx(2, "initial read"); + + r = mb_read(30000, 5, base_reg); + if (r == -1) { + fatalx(EXIT_FAILURE, "failed to read base registers from UPS: %s", modbus_strerror(errno)); + } + r = mb_read(31000, 4, conf_reg); + if (r == -1) { + fatalx(EXIT_FAILURE, "failed to read config registers from UPS: %s", modbus_strerror(errno)); + } + + dstate_setinfo("ups.firmware", "%u", base_reg[1]); /*30001 Software version*/ + dstate_setinfo("battery.voltage.nominal", "%u", base_reg[3]); /*30003 BatClass*/ + dstate_setinfo("ups.power.nominal", "%u", base_reg[4]); /*30004 Rated power*/ + dstate_setinfo("output.frequency.nominal", "%d", conf_reg[0] ? 60 : 50); /*31000 Grid frequency type*/ + dstate_setinfo("output.voltage.nominal", "%u", conf_reg[1]); /*31001 Grid voltage Type*/ + dstate_setinfo("battery.voltage.low", "%.1f", conf_reg[2] * 0.1); /*31002 Shutdown voltage*/ + dstate_setinfo("battery.voltage.high", "%.1f", conf_reg[3] * 0.1); /*31003 Absorption charge voltage*/ + + dstate_addcmd("shutdown.return"); + dstate_addcmd("beeper.disable"); + dstate_addcmd("beeper.enable"); + + upsh.instcmd = instcmd; + + dstate_setflags("output.frequency.nominal", ST_FLAG_RW); + dstate_setflags("output.voltage.nominal", ST_FLAG_RW); + dstate_setflags("battery.voltage.low", ST_FLAG_RW); + dstate_setflags("battery.voltage.high", ST_FLAG_RW); + + dstate_addenum("output.frequency.nominal", "50"); + dstate_addenum("output.frequency.nominal", "60"); + dstate_addenum("output.voltage.nominal", "220"); + dstate_addenum("output.voltage.nominal", "230"); + + upsh.setvar = setvar; +} + +void upsdrv_updateinfo(void) +{ + uint16_t base_reg[25]; + uint16_t conf_reg[7]; + int r; + state_t state; + fault_t *fault_cur; + char *fault_desc; + + upsdebugx(2, "upsdrv_updateinfo"); + + r = mb_read(30000, 25, base_reg); + if (r == -1) { + dstate_datastale(); + return; + } + r = mb_read(31000, 7, conf_reg); + if (r == -1) { + dstate_datastale(); + return; + } + + if (base_reg[2] < INIT || base_reg[2] > SOFT_START) { + upslogx(LOG_WARNING, "Unexpected UPS state: %u", base_reg[2]); + dstate_datastale(); + return; + } else { + state = (state_t) base_reg[2]; + } + + status_init(); + alarm_init(); + + dstate_setinfo("input.voltage", "%.1f", base_reg[5] * 0.1); /*30005 Grid voltage*/ + dstate_setinfo("input.frequency", "%.1f", base_reg[6] * 0.1); /*30006 Grid frequency*/ + dstate_setinfo("output.voltage", "%.1f", base_reg[7] * 0.1); /*30007 Output Voltage*/ + dstate_setinfo("output.frequency", "%.1f", base_reg[8] * 0.1); /*30008 Output Frequency*/ + dstate_setinfo("output.current", "%.1f", base_reg[9] * 0.1); /*30009 Load current*/ + dstate_setinfo("ups.power", "%u", base_reg[10]); /*30010 Load Power*/ + dstate_setinfo("ups.load", "%u", base_reg[12]); /*30012 Load percent*/ + dstate_setinfo("battery.voltage", "%.1f", base_reg[14] * 0.1); /*30014 Battery Voltage*/ + dstate_setinfo("battery.current", "%.1f", base_reg[15] * 0.1); /*30015 Battery current*/ + dstate_setinfo("battery.charge", "%u", base_reg[17]); /*30017 Battery SOC*/ + dstate_setinfo("ups.temperature", "%u", base_reg[18]); /*30018 Transformer Temp*/ + dstate_setinfo("battery.voltage.low", "%.1f", conf_reg[2] * 0.1); /*31002 Shutdown voltage*/ + dstate_setinfo("battery.voltage.high", "%.1f", conf_reg[3] * 0.1); /*31003 Absorption charge voltage*/ + dstate_setinfo("ups.beeper.status", conf_reg[6] ? "disabled" : "enabled"); /*31006 Buzzer*/ + + upsdebugx(2, "Float Charge Voltage %.1f", conf_reg[4] * 0.1); /*31004 Float Charge Voltage*/ + upsdebugx(2, "Bulk Current %u", conf_reg[5]); /*31005 Bulk Current*/ + + if (base_reg[19] != 0) { + if (base_reg[19] == 1) { + status_set("TRIM"); + } else { + status_set("BOOST"); + } + } + + if (base_reg[13] != 0) { + status_set("OVER"); + if (base_reg[13] > 1) { + alarm_set("[OVERLOAD]"); + } + } + + if (state == BACKUP) { + status_set("OB"); + } else if (state == LINE) { + status_set("OL"); + } else if (state == STOP || state == POWER_OFF) { + status_set("OFF"); + } + + if (base_reg[17] <= battery_low) {/*30017 Battery SOC*/ + status_set("LB"); + } + + upsdebugx(2, "Is Charge: %s", base_reg[24] ? "yes" : "no"); /*30024 Charge Flag*/ + upsdebugx(2, "Charger mode: %s", base_reg[23] == 0 ? "CC" : base_reg[23] == 1 ? "CV" : "FV"); /*30023 Charge Stage*/ + + if (state == BACKUP) { + dstate_setinfo("battery.charger.status", "discharging"); + } else if (!base_reg[24]) {/*30024 Charge Flag*/ + dstate_setinfo("battery.charger.status", "resting"); + } else if (base_reg[23] < 2) {/*30023 Charge Stage*/ + dstate_setinfo("battery.charger.status", "charging"); + } else { + dstate_setinfo("battery.charger.status", "floating"); + } + + if (base_reg[21]) { + fault_desc = "[Generic fault]"; + for (fault_cur = fault_list; fault_list->desc; fault_cur++) { + if (fault_cur->code == base_reg[21]) { + fault_desc = fault_cur->desc; + break; + } + } + alarm_set(fault_desc); + } + + if (CHECK_BIT(base_reg[22], 1)) {/*Battery voltage is too Low*/ + status_set("LB"); + } + if (CHECK_BIT(base_reg[22], 2)) {/*Over load*/ + status_set("OVER"); + } + if (CHECK_BIT(base_reg[22], 3)) {/*Battery voltage is too high*/ + status_set("HB"); + } + if (CHECK_BIT(base_reg[22], 4)) {/*Parameter error*/ + alarm_set("[Parameter error]"); + } + + alarm_commit(); + status_commit(); + dstate_dataok(); + + return; +} + +void upsdrv_shutdown(void) +{ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + int ret = -1; + + upsdebugx(2, "upsdrv_shutdown"); + + ret = do_loop_shutdown_commands("shutdown.return", NULL); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(ret == STAT_INSTCMD_HANDLED ? EF_EXIT_SUCCESS : EF_EXIT_FAILURE); +} + +static int set_beeper(int enabled) +{ + return mb_write(31006, enabled ? 0 : 1); /*31006 Buzzer (1: silence, 0: normal)*/ +} + +static int instcmd(const char *cmdname, const char *extra) +{ + int r; + + /* May be used in logging below, but not as a command argument */ + NUT_UNUSED_VARIABLE(extra); + upsdebug_INSTCMD_STARTING(cmdname, extra); + + if (!strcasecmp(cmdname, "beeper.enable")) { + r = set_beeper(1); + return r != -1 ? STAT_INSTCMD_HANDLED : STAT_INSTCMD_FAILED; + } else if (!strcasecmp(cmdname, "beeper.disable")) { + r = set_beeper(0); + return r != -1 ? STAT_INSTCMD_HANDLED : STAT_INSTCMD_FAILED; + } else if (!strcasecmp(cmdname, "shutdown.return")) { + r = mb_write(32002, 1); /*32002 Remote shutdown*/ + return r != -1 ? STAT_INSTCMD_HANDLED : STAT_INSTCMD_FAILED; + } + + upslog_INSTCMD_UNKNOWN(cmdname, extra); + return STAT_INSTCMD_UNKNOWN; +} + +static int set_dvar(const char *varname, const char *value, int addr, double low, double high) { + const char *rate; + int r; + double val; + + val = strtod(value, NULL); + + rate = dstate_getinfo("battery.voltage.nominal"); + if (rate != NULL && strcmp(rate, "12") != 0) { + low *= 2; + high *= 2; + } + + if (val < low || val > high) { + upslogx(LOG_WARNING, "%s %s out of range: %.1f-%.1f", varname, value, low, high); + return STAT_SET_FAILED; + } else { + r = mb_write(addr, (uint16_t) (val * 10)); + if (r == -1) { + return STAT_SET_FAILED; + } else { + dstate_setinfo(varname, "%.1f", val); + return STAT_SET_HANDLED; + } + } +} + +static int set_ivar(const char *varname, int val, int addr, uint16_t setval, int val1, int val2) { + int r; + + if (val != val1 && val != val2) { + upslogx(LOG_WARNING, "%s %d out list of valid values: %d, %d", varname, val, val1, val2); + return STAT_SET_FAILED; + } else { + r = mb_write(addr, setval); + if (r == -1) { + return STAT_SET_FAILED; + } else { + dstate_setinfo(varname, "%d", val); + return STAT_SET_HANDLED; + } + } +} + +static int setvar(const char *varname, const char *value) +{ + int i; + + upsdebug_SET_STARTING(varname, value); + + if (!strcasecmp(varname, "battery.voltage.low")) { + return set_dvar("battery.voltage.low", value, 31002, 10, 12); + } else if (!strcasecmp(varname, "battery.voltage.high")) { + return set_dvar("battery.voltage.high", value, 31003, 13.8, 14.5); + } else if (!strcasecmp(varname, "output.frequency.nominal")) { + i = atoi(value); + return set_ivar("output.frequency.nominal", i, 31000, i == 50 ? 0 : 1, 50, 60); + } else if (!strcasecmp(varname, "output.voltage.nominal")) { + i = atoi(value); + return set_ivar("output.voltage.nominal", i, 31000, (uint16_t) i, 220, 230); + } + + upslog_SET_UNKNOWN(varname, value); + return STAT_SET_UNKNOWN; +} + +void upsdrv_help(void) +{ +} + +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + +/* list flags and values that you want to receive via -x */ +void upsdrv_makevartable(void) +{ + addvar(VAR_VALUE, BATTERY_LOW_NAME, "Low battery level (default " STRFY(BATTERY_LOW_DEFAULT) ")"); +} + +void upsdrv_initups(void) +{ + int r; + upsdebugx(2, "upsdrv_initups"); + + modbus_ctx = modbus_new_rtu(device_path, BAUD_RATE, PARITY, DATA_BIT, STOP_BIT); + if (modbus_ctx == NULL) + fatalx(EXIT_FAILURE, "Unable to create the libmodbus context"); + + r = modbus_set_slave(modbus_ctx, MODBUS_SLAVE_ID); /* slave ID */ + if (r < 0) { + modbus_free(modbus_ctx); + fatalx(EXIT_FAILURE, "Invalid modbus slave ID %d",MODBUS_SLAVE_ID); + } + + if (modbus_connect(modbus_ctx) == -1) { + modbus_free(modbus_ctx); + fatalx(EXIT_FAILURE, "modbus_connect: unable to connect: %s", modbus_strerror(errno)); + } + +} + +void upsdrv_cleanup(void) +{ + if (modbus_ctx != NULL) { + modbus_close(modbus_ctx); + modbus_free(modbus_ctx); + } +} + +/* Modbus Read Holding Registers */ +static int mb_read(int addr, int nb, uint16_t * dest) +{ + int r, i; + + /* zero out the thing, because we might have reused it */ + for (i=0; i= (q)) ? (p) : (q)) +#endif +#ifndef MIN +# define MIN(p,q) (((p) <= (q)) ? (p) : (q)) +#endif + /* POSIX requires these, and most but not all systems use same * magical numbers for the file descriptors... yep, not all do! */ @@ -138,7 +145,7 @@ extern "C" { * including pipes for driver-upsd communications: */ # define TYPE_FD int # define ERROR_FD (-1) -# define VALID_FD(a) (a>=0) +# define VALID_FD(a) ((a)>=0) /* Type of what NUT serial/SHUT methods juggle: */ # define TYPE_FD_SER TYPE_FD @@ -158,15 +165,20 @@ extern "C" { */ # define TYPE_FD HANDLE # define ERROR_FD (INVALID_HANDLE_VALUE) -# define VALID_FD(a) (a!=INVALID_HANDLE_VALUE) +# define VALID_FD(a) ((a)!=INVALID_HANDLE_VALUE) # ifndef INVALID_SOCKET -# define INVALID_SOCKET -1 +# define INVALID_SOCKET ((SOCKET)(-1)) # endif +/* Bitness-dependent "pointer-sized unsigned integer" (usually 32 or 64 bits) */ # define TYPE_FD_SOCK SOCKET # define ERROR_FD_SOCK INVALID_SOCKET -# define VALID_FD_SOCK(a) (a!=INVALID_SOCKET) +/* Valid range for SOCKET is 0..(INVALID_SOCKET-1) and there is no special + * check for "-1" (may be or not be coincidental by casting and/or definition + * in existing headers) nor generally negative values, as in Unix socket API. + */ +# define VALID_FD_SOCK(a) ((a)!=INVALID_SOCKET) typedef struct serial_handler_s { HANDLE handle; @@ -181,7 +193,7 @@ typedef struct serial_handler_s { # define TYPE_FD_SER serial_handler_t * # define ERROR_FD_SER (NULL) -# define VALID_FD_SER(a) (a!=NULL) +# define VALID_FD_SER(a) ((a)!=NULL) /* difftime returns erroneous value so we use this macro */ # undef difftime @@ -451,11 +463,17 @@ const char * rootpidpath(void); void check_unix_socket_filename(const char *fn); #ifdef NUT_WANT_INET_NTOP_XX -/* NOT THREAD SAFE! - * Helpers to convert one IP address to string from different structure types - * Return pointer to internal buffer, or NULL and errno upon errors */ -const char *inet_ntopSS(struct sockaddr_storage *s); -const char *inet_ntopAI(struct addrinfo *ai); +/* Helpers to convert one IP address to string from different structure types + * Return pointer to internal buffer (in NOT THREAD SAFE! methods named as such) + * or caller-provided buffer or an allocated buffer that caller must free (in + * the "x" methods), or NULL and errno upon errors */ +const char *inet_ntopSS(struct sockaddr_storage *s, char *addrstr, size_t addrstrsz); +const char *inet_ntopSS_thread_unsafe(struct sockaddr_storage *s); +const char *xinet_ntopSS(struct sockaddr_storage *s); + +const char *inet_ntopAI(struct addrinfo *ai, char *addrstr, size_t addrstrsz); +const char *inet_ntopAI_thread_unsafe(struct addrinfo *ai); +const char *xinet_ntopAI(struct addrinfo *ai); #endif /* NUT_WANT_INET_NTOP_XX */ /* Provide integration for systemd inhibitor interface (where available, diff --git a/scripts/Windows/wininit.c b/scripts/Windows/wininit.c index e78e411939..004a37694a 100644 --- a/scripts/Windows/wininit.c +++ b/scripts/Windows/wininit.c @@ -3,7 +3,7 @@ Copyright (C) 2010 Frederic Bohe - 2021-2024 Jim Klimov + 2021-2025 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 @@ -40,6 +40,10 @@ typedef struct conn_s { static DWORD upsd_pid = 0; static DWORD upsmon_pid = 0; +static DWORD upsdrvctl_pid = 0; +static HANDLE upsd_handle = INVALID_HANDLE_VALUE; +static HANDLE upsmon_handle = INVALID_HANDLE_VALUE; +static HANDLE upsdrvctl_handle = INVALID_HANDLE_VALUE; static BOOL service_flag = TRUE; HANDLE svc_stop = NULL; static SERVICE_STATUS SvcStatus; @@ -81,7 +85,6 @@ static void print_event(DWORD priority, const char * fmt, ...) NULL); /* no binary data */ DeregisterEventSource(EventSource); - } if (buf) @@ -89,7 +92,7 @@ static void print_event(DWORD priority, const char * fmt, ...) } /* returns PID of the newly created process or 0 on failure */ -static DWORD create_process(char * command) +static DWORD create_process(char * command, HANDLE *pHandle) { STARTUPINFO StartupInfo; PROCESS_INFORMATION ProcessInformation; @@ -100,6 +103,7 @@ static DWORD create_process(char * command) StartupInfo.cb = sizeof(StartupInfo); memset(&ProcessInformation, 0, sizeof(ProcessInformation)); + upsdebugx(2, "%s: %s", __func__, NUT_STRARG(command)); res = CreateProcess( NULL, command, @@ -115,10 +119,15 @@ static DWORD create_process(char * command) LastError = GetLastError(); if (res == 0) { + upsdebug_with_errno(1, "%s: failed to create process '%s'", __func__, NUT_STRARG(command)); print_event(LOG_ERR, "Can't create process %s : %d", command, LastError); return 0; } + upsdebugx(3, "%s: %s returned PID: %" PRIiMAX, __func__, + NUT_STRARG(command), (intmax_t)(ProcessInformation.dwProcessId)); + if (pHandle) + *pHandle = ProcessInformation.hProcess; return ProcessInformation.dwProcessId; } @@ -156,7 +165,9 @@ static DWORD run_drivers(void) path, makearg_debug()); } free(path); - return create_process(command); + + upsdrvctl_pid = create_process(command, &upsdrvctl_handle); + return upsdrvctl_pid; } /* return PID of created process or 0 on failure */ @@ -173,7 +184,7 @@ static DWORD stop_drivers(void) path, makearg_debug()); } free(path); - return create_process(command); + return create_process(command, NULL); } /* return PID of created process or 0 on failure */ @@ -188,11 +199,13 @@ static void run_upsd(void) snprintfcat(command, sizeof(command), " %s", makearg_debug()); } free(path); - upsd_pid = create_process(command); + upsd_pid = create_process(command, &upsd_handle); } static void stop_upsd(void) { + upsdebugx(3, "%s: using pipe '%s' to stop PID: %" PRIiMAX, __func__, + NUT_STRARG(UPSD_PIPE_NAME), (intmax_t)(upsd_pid)); if (sendsignal(UPSD_PIPE_NAME, COMMAND_STOP, 0)) { print_event(LOG_ERR, "Error stopping upsd (%d)", GetLastError()); } @@ -210,11 +223,13 @@ static void run_upsmon(void) snprintfcat(command, sizeof(command), " %s", makearg_debug()); } free(path); - upsmon_pid = create_process(command); + upsmon_pid = create_process(command, &upsmon_handle); } static void stop_upsmon(void) { + upsdebugx(3, "%s: using pipe '%s' to stop PID: %" PRIiMAX, __func__, + NUT_STRARG(UPSMON_PIPE_NAME), (intmax_t)(upsmon_pid)); if (sendsignal(UPSMON_PIPE_NAME, COMMAND_STOP, 0)) { print_event(LOG_ERR, "Error stopping upsmon (%d)", GetLastError()); } @@ -244,6 +259,7 @@ static DWORD test_powerdownflag(void) StartupInfo.cb = sizeof(StartupInfo); memset(&ProcessInformation, 0, sizeof(ProcessInformation)); + upsdebugx(2, "%s: launch %s", __func__, command); res = CreateProcess( NULL, command, @@ -259,6 +275,7 @@ static DWORD test_powerdownflag(void) LastError = GetLastError(); if (res == 0) { + upsdebug_with_errno(1, "%s: failed to create process '%s'", __func__, command); print_event(LOG_ERR, "Can't create process %s : %d", command, LastError); return 1; } @@ -267,6 +284,7 @@ static DWORD test_powerdownflag(void) res = GetExitCodeProcess(ProcessInformation.hProcess, &status); if (res != 0) { if (status != STILL_ACTIVE) { + upsdebugx(1, "%s: powerdownflag check returned: %d", __func__, status); return status; } } @@ -274,6 +292,7 @@ static DWORD test_powerdownflag(void) i--; } + upsdebugx(1, "%s: powerdownflag check timed out", __func__); return 1; } @@ -290,7 +309,7 @@ static DWORD shutdown_ups(void) path, makearg_debug()); } free(path); - return create_process(command); + return create_process(command, NULL); } /* return 0 on failure */ @@ -310,11 +329,13 @@ static int parse_nutconf(BOOL start_flag) return 0; } + upsdebugx(1, "%s: parsing config file: %s", __func__, fn); while (fgets(buf, sizeof(buf), nutf) != NULL) { if (buf[0] != '#') { if (strstr(buf, "standalone") != NULL || strstr(buf, "netserver") != NULL ) { + upsdebugx(2, "%s: saw 'standalone' or 'netserver' on a non-comment line, assuming this is MODE", __func__); if (start_flag == NUT_START) { print_event(LOG_INFO, "Starting drivers"); run_drivers(); @@ -346,6 +367,7 @@ static int parse_nutconf(BOOL start_flag) } } if (strstr(buf, "netclient") != NULL) { + upsdebugx(2, "%s: saw 'netclient' on a non-comment line, assuming this is MODE", __func__); if (start_flag == NUT_START) { print_event(LOG_INFO, "Starting upsmon (client only)"); run_upsmon(); @@ -376,6 +398,7 @@ static int SvcInstall(const char * SvcName, const char * args) TCHAR Path[NUT_PATH_MAX]; if (!GetModuleFileName(NULL, Path, NUT_PATH_MAX)) { + upsdebug_with_errno(1, "%s: Could not GetModuleFileName() of current program", __func__); printf("Cannot install service (%d)\n", (int)GetLastError()); return EXIT_FAILURE; } @@ -390,6 +413,7 @@ static int SvcInstall(const char * SvcName, const char * args) SC_MANAGER_ALL_ACCESS); /* full access rights */ if (NULL == SCManager) { + upsdebug_with_errno(1, "%s: Could not OpenSCManager()", __func__); upslogx(LOG_ERR, "OpenSCManager failed (%d)\n", (int)GetLastError()); return EXIT_FAILURE; } @@ -410,6 +434,7 @@ static int SvcInstall(const char * SvcName, const char * args) NULL); /* no password */ if (Service == NULL) { + upsdebug_with_errno(1, "%s: Could not CreateService()", __func__); upslogx(LOG_ERR, "CreateService failed (%d)\n", (int)GetLastError()); CloseServiceHandle(SCManager); return EXIT_FAILURE; @@ -418,6 +443,20 @@ static int SvcInstall(const char * SvcName, const char * args) upslogx(LOG_INFO, "Service installed successfully\n"); } +#ifdef SERVICE_CONFIG_DESCRIPTION + { /* scope */ + SERVICE_DESCRIPTIONA descr; + descr.lpDescription = "The Network UPS Tools (NUT) project " + "provides monitoring and management of " + "Uninterruptible Power Sources (UPS) and similar hardware, " + "allowing for multiple systems fed by an UPS to be " + "safely shut down in case of a power outage. " + "For more details please see " NUT_WEBSITE_BASE; + + ChangeServiceConfig2A(Service, SERVICE_CONFIG_DESCRIPTION, &descr); + } +#endif + CloseServiceHandle(Service); CloseServiceHandle(SCManager); @@ -440,7 +479,8 @@ static int SvcExists(const char * SvcName) SC_MANAGER_ALL_ACCESS); /* full access rights */ if (NULL == SCManager) { - upsdebugx(1, "OpenSCManager failed (%d)\n", (int)GetLastError()); + upsdebug_with_errno(1, "%s: Could not OpenSCManager()", __func__); + upslogx(LOG_ERR, "OpenSCManager failed (%d)\n", (int)GetLastError()); return -2; } @@ -450,8 +490,9 @@ static int SvcExists(const char * SvcName) DELETE); /* need delete access */ if (Service == NULL) { - upsdebugx(1, "OpenService failed (%d) for \"%s\"\n", - (int)GetLastError(), SvcName); + upsdebug_with_errno(1, "%s: OpenService failed for '%s'", + __func__, SvcName); + upslogx(LOG_ERR, "OpenService failed (%d)\n", (int)GetLastError()); CloseServiceHandle(SCManager); return -1; } @@ -459,7 +500,7 @@ static int SvcExists(const char * SvcName) CloseServiceHandle(Service); CloseServiceHandle(SCManager); - upsdebugx(1, "Service \"%s\" seems to exist", SvcName); + upsdebugx(1, "%s: Service '%s' seems to exist", __func__, SvcName); return 1; } @@ -474,6 +515,7 @@ static int SvcUninstall(const char * SvcName) SC_MANAGER_ALL_ACCESS); /* full access rights */ if (NULL == SCManager) { + upsdebug_with_errno(1, "%s: Could not OpenSCManager()", __func__); upslogx(LOG_ERR, "OpenSCManager failed (%d)\n", (int)GetLastError()); return EXIT_FAILURE; } @@ -484,16 +526,19 @@ static int SvcUninstall(const char * SvcName) DELETE); /* need delete access */ if (Service == NULL) { + upsdebug_with_errno(1, "%s: OpenService failed for '%s'", + __func__, SvcName); upslogx(LOG_ERR, "OpenService failed (%d)\n", (int)GetLastError()); CloseServiceHandle(SCManager); return EXIT_FAILURE; } if (!DeleteService(Service)) { + upsdebug_with_errno(1, "%s: Could not DeleteService()", __func__); upslogx(LOG_ERR, "DeleteService failed (%d)\n", (int)GetLastError()); } else { - upslogx(LOG_ERR, "Service deleted successfully\n"); + upslogx(LOG_INFO, "Service deleted successfully\n"); } CloseServiceHandle(Service); @@ -502,6 +547,9 @@ static int SvcUninstall(const char * SvcName) return EXIT_SUCCESS; } +/* Report the status of the service to the Windows SCM + * FIXME: Combine with common.c SMF/systemd/... notifier? + */ static void ReportSvcStatus( DWORD CurrentState, DWORD Win32ExitCode, @@ -513,6 +561,9 @@ static void ReportSvcStatus( SvcStatus.dwWin32ExitCode = Win32ExitCode; SvcStatus.dwWaitHint = WaitHint; + /* FIXME: Find a way to stringify? */ + upsdebugx(2, "%s: reporting transition to status %d", __func__, CurrentState); + if (CurrentState == SERVICE_START_PENDING) SvcStatus.dwControlsAccepted = 0; else SvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN; @@ -536,9 +587,11 @@ static void WINAPI SvcCtrlHandler(DWORD Ctrl) { case SERVICE_CONTROL_STOP: case SERVICE_CONTROL_SHUTDOWN: + upsdebugx(1, "%s: report SERVICE_STOP_PENDING", __func__); ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0); /* Signal the service to stop */ + upsdebugx(1, "%s: report actual service stop/shutdown type...", __func__); SetEvent(svc_stop); ReportSvcStatus(SvcStatus.dwCurrentState, NO_ERROR, 0); @@ -560,6 +613,8 @@ static void SvcStart(char * SvcName) SvcCtrlHandler); if (!SvcStatusHandle) { + upsdebug_with_errno(1, "%s: RegisterServiceCtrlHandler failed for '%s'", + __func__, SvcName); upslogx(LOG_ERR, "RegisterServiceCtrlHandler\n"); return; } @@ -568,6 +623,7 @@ static void SvcStart(char * SvcName) SvcStatus.dwServiceSpecificExitCode = 0; /* Report initial status to the SCM */ + upsdebugx(1, "%s: report SERVICE_START_PENDING", __func__); ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000); } @@ -580,9 +636,12 @@ static void SvcReady(void) NULL); /* no name */ if (svc_stop == NULL) { + upsdebugx(1, "%s: report SERVICE_STOPPED", __func__); ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0); return; } + + upsdebugx(1, "%s: report SERVICE_RUNNING", __func__); ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0); } @@ -590,6 +649,7 @@ static void close_all(void) { pipe_conn_t *conn; + upsdebugx(3, "%s: closing all connections (if any)", __func__); for (conn = pipe_connhead; conn; conn = conn->next) { pipe_disconnect(conn); } @@ -599,8 +659,8 @@ static void close_all(void) static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) { DWORD ret; - HANDLE handles[MAXIMUM_WAIT_OBJECTS]; - int maxhandle = 0; + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; /* 64 per current WinAPI sheaders */ + size_t maxhandle = 0; pipe_conn_t *conn; DWORD priority; char *buf; @@ -609,6 +669,7 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) NUT_UNUSED_VARIABLE(argv); if (service_flag) { + upsdebugx(3, "%s: starting service...", __func__); SvcStart(SVCNAME); } @@ -620,6 +681,7 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) print_event(LOG_INFO, "Starting"); /* pipe for event log proxy */ + upsdebugx(3, "%s: calling pipe_create()...", __func__); pipe_create(EVENTLOG_PIPE_NAME); /* parse nut.conf and start relevant processes */ @@ -641,11 +703,16 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) /* Wait on the read IO of each connections */ for (conn = pipe_connhead; conn; conn = conn->next) { + /* Leave one or two for "new event" below */ + if (maxhandle + 1 + (service_flag ? 1 : 0) >= sizeof(handles)) { + upsdebugx(1, "%s: skipping handle: too many connected already", __func__); + continue; + } handles[maxhandle] = conn->overlapped.hEvent; maxhandle++; } - /* Add the new pipe connected event */ + /* Add the "new pipe connected" event */ handles[maxhandle] = pipe_connection_overlapped.hEvent; maxhandle++; @@ -659,6 +726,7 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) if (ret == WAIT_FAILED) { print_event(LOG_ERR, "Wait failed"); + upsdebug_with_errno(1, "%s: WaitForMultipleObjects", __func__); return; } @@ -697,6 +765,61 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) } } } + + /* Check on each daemon: Was it supposed to run? Does it still? */ + if (upsdrvctl_pid) { + DWORD status = 0; + BOOL res = FALSE; + + res = GetExitCodeProcess(upsdrvctl_handle, &status); + if (res != 0) { + if (status != STILL_ACTIVE) { + upslog_with_errno(LOG_WARNING, "%s: GetExitCodeProcess(upsdrvctl): daemon died, restarting", __func__); + run_drivers(); + Sleep(5000); + } else { + upsdebugx(2, "%s: upsdrvctl is still running as PID %" PRIuMAX, __func__, (uintmax_t)upsdrvctl_pid); + } + } else { + upslog_with_errno(LOG_ERR, "%s: GetExitCodeProcess(upsdrvctl)", __func__); + } + } + + if (upsd_pid) { + DWORD status = 0; + BOOL res = FALSE; + + res = GetExitCodeProcess(upsd_handle, &status); + if (res != 0) { + if (status != STILL_ACTIVE) { + upslog_with_errno(LOG_WARNING, "%s: GetExitCodeProcess(upsd): daemon died, restarting", __func__); + run_upsd(); + Sleep(5000); + } else { + upsdebugx(2, "%s: upsd is still running as PID %" PRIuMAX, __func__, (uintmax_t)upsd_pid); + } + } else { + upslog_with_errno(LOG_ERR, "%s: GetExitCodeProcess(upsd)", __func__); + } + } + + if (upsmon_pid) { + DWORD status = 0; + BOOL res = FALSE; + + res = GetExitCodeProcess(upsmon_handle, &status); + if (res != 0) { + if (status != STILL_ACTIVE) { + upslog_with_errno(LOG_WARNING, "%s: GetExitCodeProcess(upsmon): daemon died, restarting", __func__); + run_upsmon(); + Sleep(5000); + } else { + upsdebugx(2, "%s: upsmon is still running as PID %" PRIuMAX, __func__, (uintmax_t)upsmon_pid); + } + } else { + upslog_with_errno(LOG_ERR, "%s: GetExitCodeProcess(upsmon)", __func__); + } + } } } @@ -708,9 +831,11 @@ static void help(const char *arg_progname) printf("including shutdown and power-off handling (where supported). All together they rely\n"); printf("on nut.conf and other files in %s\n", confpath()); - printf("\nUsage: %s {start | stop}\n\n", arg_progname); + printf("\nUsage: %s {start | stop | status}\n\n", arg_progname); printf(" start Install as a service (%s) if not yet done, then `net start` it\n", SVCNAME); printf(" stop If the service (%s) is installed, command it to `net stop`\n", SVCNAME); + printf(" restart 'stop' (do not fail), then 'start' (register '%s' as needed)\n", SVCNAME); + printf(" status Query system for current status of service '%s'\n", SVCNAME); printf("Note you may have to run this in an elevated privilege command shell, or use `runas`\n"); printf("\nUsage: %s [OPTION]\n\n", arg_progname); @@ -734,37 +859,6 @@ int main(int argc, char **argv) int i, default_opterr = opterr; const char *progname = xbasename(argc > 0 ? argv[0] : "nut.exe"); - if (argc > 1) { - if (!strcmp(argv[1], "/?")) { - help(progname); - return EXIT_SUCCESS; - } - - if (!strcmp(argv[1], "stop")) { - int ret; - if (SvcExists(SVCNAME) < 0) - fprintf(stderr, "WARNING: Can not access service \"%s\"", SVCNAME); - - ret = system("net stop \"" SVCNAME "\""); - if (ret == 0) - return EXIT_SUCCESS; - fatalx(EXIT_FAILURE, "FAILED stopping %s: %i", SVCNAME, ret); - } - - if (!strcmp(argv[1], "start")) { - int ret; - if (SvcExists(SVCNAME) < 0) { - fprintf(stderr, "WARNING: Can not access service \"%s\", registering first", SVCNAME); - SvcInstall(SVCNAME, NULL); - } - - ret = system("net start \"" SVCNAME "\""); - if (ret == 0) - return EXIT_SUCCESS; - fatalx(EXIT_FAILURE, "FAILED starting %s: %i", SVCNAME, ret); - } - } - /* TODO: Do not warn about unknown args - pass them to SvcMain() * Currently neutered because that method ignores argc/argv de-facto. * opterr = 0; @@ -774,6 +868,14 @@ int main(int argc, char **argv) case 'I': return SvcInstall(SVCNAME, NULL); case 'U': + { /* scoping */ + int ret = 0; + upsdebugx(1, "exec: net stop \"" SVCNAME "\""); + ret = system("net stop \"" SVCNAME "\""); + upsdebugx(1, "exec: returned: %d", ret); + if (ret != 0) + upsdebugx(0, "FAILED stopping %s: %i", SVCNAME, ret); + } return SvcUninstall(SVCNAME); case 'N': service_flag = FALSE; @@ -797,12 +899,91 @@ int main(int argc, char **argv) * chars to '?' so we can not log what exactly was wrong. */ upsdebugx(1, "%s: unknown option ignored " - "(maybe SvcMain would use it later)", - progname); + "(maybe SvcMain would use it later): '%c'", + progname, i); break; } } + /* Of short opts before this, primarily we might expect debug to be bumped */ + if (argc > 1 && optind < argc) { + if (!strcmp(argv[optind], "/?")) { + help(progname); + return EXIT_SUCCESS; + } + + if (!strcmp(argv[optind], "stop")) { + int ret; + if (SvcExists(SVCNAME) < 0) + fprintf(stderr, "WARNING: Can not access service \"%s\"\n", SVCNAME); + + upsdebugx(1, "exec: net stop \"" SVCNAME "\""); + ret = system("net stop \"" SVCNAME "\""); + upsdebugx(1, "exec: returned: %d", ret); + if (ret == 0) + return EXIT_SUCCESS; + fatalx(EXIT_FAILURE, "FAILED stopping %s: %i", SVCNAME, ret); + } + + if (!strcmp(argv[optind], "start")) { + int ret; + if (SvcExists(SVCNAME) < 0) { + fprintf(stderr, "WARNING: Can not access service \"%s\", registering first\n", SVCNAME); + SvcInstall(SVCNAME, NULL); + } + + upsdebugx(1, "exec: net start \"" SVCNAME "\""); + ret = system("net start \"" SVCNAME "\""); + upsdebugx(1, "exec: returned: %d", ret); + if (ret == 0) + return EXIT_SUCCESS; + fatalx(EXIT_FAILURE, "FAILED starting %s: %i", SVCNAME, ret); + } + + if (!strcmp(argv[optind], "restart")) { + int ret; + if (SvcExists(SVCNAME) < 0) + fprintf(stderr, "WARNING: Can not access service \"%s\"\n", SVCNAME); + + upsdebugx(1, "exec: net stop \"" SVCNAME "\""); + ret = system("net stop \"" SVCNAME "\""); + upsdebugx(1, "exec: returned: %d", ret); + if (ret != 0) + fprintf(stderr, "WARNING: FAILED stopping %s: %i", SVCNAME, ret); + + if (SvcExists(SVCNAME) < 0) { + fprintf(stderr, "WARNING: Can not access service \"%s\", registering first\n", SVCNAME); + SvcInstall(SVCNAME, NULL); + } + + /* FIXME: Query service status if it is stopping */ + Sleep(15000); + + upsdebugx(1, "exec: net start \"" SVCNAME "\""); + ret = system("net start \"" SVCNAME "\""); + upsdebugx(1, "exec: returned: %d", ret); + if (ret == 0) + return EXIT_SUCCESS; + fatalx(EXIT_FAILURE, "FAILED starting %s: %i", SVCNAME, ret); + } + + if (!strcmp(argv[optind], "status")) { + int ret; + if (SvcExists(SVCNAME) < 0) { + fprintf(stderr, "WARNING: Can not access service \"%s\"\n", SVCNAME); + } + + upsdebugx(1, "exec: sc query \"" SVCNAME "\""); + ret = system("sc query \"" SVCNAME "\""); + upsdebugx(1, "exec: returned: %d", ret); + if (ret == 0) + return EXIT_SUCCESS; + fatalx(EXIT_FAILURE, "FAILED querying %s: %i", SVCNAME, ret); + } + + fatalx(EXIT_FAILURE, "Unknown option: %s", argv[optind]); + } + optind = 0; opterr = default_opterr; diff --git a/scripts/obs/nut.spec b/scripts/obs/nut.spec index a90024f046..173f318e16 100644 --- a/scripts/obs/nut.spec +++ b/scripts/obs/nut.spec @@ -493,9 +493,9 @@ sh autogen.sh ### via Make now ### (cd tools; python nut-snmpinfo.py) make %{?_smp_mflags} -PORT=$(sed -n 's/#define PORT //p' config.log) -if test "$PORT" = 3493 ; then - PORT=nut +NUT_PORT=$(sed -n 's/#define NUT_PORT //p' config.log) +if test "$NUT_PORT" = 3493 ; then + NUT_PORT=nut fi %check diff --git a/server/conf.c b/server/conf.c index 3026c0b2cd..18f7bc89f2 100644 --- a/server/conf.c +++ b/server/conf.c @@ -318,7 +318,7 @@ static int parse_upsd_conf_args(size_t numargs, char **arg) /* LISTEN
[] */ if (!strcmp(arg[0], "LISTEN")) { if (numargs < 3) - listen_add(arg[1], string_const(PORT)); + listen_add(arg[1], string_const(NUT_PORT)); else listen_add(arg[1], arg[2]); return 1; diff --git a/server/upsd.c b/server/upsd.c index 7e34f27622..f835b66922 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -234,6 +234,10 @@ void listen_add(const char *addr, const char *port) server->addr = xstrdup(addr); server->port = xstrdup(port); server->sock_fd = ERROR_FD_SOCK; +#ifdef WIN32 + /* field defined as a HANDLE not TYPE_FD, so stick with that for initializer */ + server->Event = INVALID_HANDLE_VALUE; +#endif server->next = NULL; if (firstaddr) { @@ -264,15 +268,18 @@ static void stype_free(stype_t *server) /* create a listening socket for tcp connections */ static void setuptcp(stype_t *server) { -#ifdef WIN32 - WSADATA WSAdata; -#endif /* WIN32 */ struct addrinfo hints, *res, *ai; int v = 0, one = 1; #ifdef WIN32 - WSAStartup(2,&WSAdata); - atexit((void(*)(void))WSACleanup); + /* Required ritual before calling any socket functions */ + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } #endif /* WIN32 */ if (VALID_FD_SOCK(server->sock_fd)) { @@ -388,6 +395,9 @@ static void setuptcp(stype_t *server) server->addr = serverAnyV4->addr; server->port = serverAnyV4->port; server->sock_fd = serverAnyV4->sock_fd; +#ifdef WIN32 + server->Event = serverAnyV4->Event; +#endif /* ...and keep whatever server->next there was */ /* Free the ghost, all needed info was relocated */ @@ -412,6 +422,9 @@ static void setuptcp(stype_t *server) server->addr = serverAnyV6->addr; server->port = serverAnyV6->port; server->sock_fd = serverAnyV6->sock_fd; +#ifdef WIN32 + server->Event = serverAnyV6->Event; +#endif /* ...and keep whatever server->next there was */ /* Free the ghost, all needed info was relocated */ @@ -425,6 +438,7 @@ static void setuptcp(stype_t *server) } serverAnyV6 = NULL; + /* Splitting "LISTEN * [port]" was a special case, closed */ return; } @@ -493,13 +507,15 @@ static void setuptcp(stype_t *server) } if (ai->ai_next) { - const char *ipaddr = inet_ntopAI(ai); + const char *ipaddr = xinet_ntopAI(ai); upslogx(LOG_WARNING, "setuptcp: bound to %s%s%s but there seem to be " "further (ignored) addresses resolved for this name", server->addr, ipaddr == NULL ? "" : " as ", - ipaddr == NULL ? "" : ipaddr); + ipaddr == NULL ? "" : NUT_STRARG(ipaddr)); + if (ipaddr) + free((char*)ipaddr); } server->sock_fd = sock_fd; @@ -507,13 +523,22 @@ static void setuptcp(stype_t *server) } #ifdef WIN32 + if (VALID_FD_SOCK(server->sock_fd)) { server->Event = CreateEvent(NULL, /* Security */ - FALSE, /* auto-reset */ - FALSE, /* initial state */ - NULL); /* no name */ - - /* Associate socket event to the socket via its Event object */ - WSAEventSelect( server->sock_fd, server->Event, FD_ACCEPT ); + FALSE, /* auto-reset */ + FALSE, /* initial state */ + NULL); /* no name */ + + /* TOTHINK: Should failures here be fatal? */ + if (server->Event) { + /* Associate socket event to the socket via its Event object */ + if (WSAEventSelect(server->sock_fd, server->Event, FD_ACCEPT)) { + upsdebug_with_errno(3, "setuptcp: WSAEventSelect"); + } + } else { + upsdebug_with_errno(3, "setuptcp: CreateEvent"); + } + } #endif /* WIN32 */ freeaddrinfo(res); @@ -524,6 +549,19 @@ static void setuptcp(stype_t *server) upslogx(LOG_ERR, "not listening on %s port %s", server->addr, server->port); } else { upslogx(LOG_INFO, "listening on %s port %s", server->addr, server->port); +#ifndef WIN32 + upsdebugx(1, "%s: SERVER listener [%s:%s] on FD %" PRIuMAX, + __func__, server->addr, server->port, + (uintmax_t)server->sock_fd); +#else + upsdebugx(1, "%s: SERVER listener [%s:%s] on FD %" PRIuMAX + ", event handler %p%s", + __func__, server->addr, server->port, + (uintmax_t)server->sock_fd, + server->Event, + (server->Event == INVALID_HANDLE_VALUE ? " (INVALID_HANDLE_VALUE)" : "") + ); +#endif } return; @@ -824,7 +862,7 @@ static void client_connect(stype_t *server) time(&client->last_heard); - client->addr = xstrdup(inet_ntopSS(&csock)); + client->addr = (char*)xinet_ntopSS(&csock); client->tracking = 0; @@ -922,19 +960,24 @@ void server_load(void) listenersValidLocalhostName = 0, listenersValidLocalhostName6 = 0, listenersValidLocalhostIPv4 = 0, - listenersValidLocalhostIPv6 = 0; + listenersValidLocalhostIPv6 = 0, + listenersTotalAny = 0, listenersValidAny = 0, + listenersAnyIPv4 = 0, + listenersAnyIPv6 = 0, + listenersValidAnyIPv4 = 0, + listenersValidAnyIPv6 = 0; /* default behaviour if no LISTEN address has been specified */ if (!firstaddr) { /* Note: default opt_af==AF_UNSPEC so not constrained to only one protocol */ if (opt_af != AF_INET) { upsdebugx(1, "%s: No LISTEN configuration provided, will try IPv6 localhost", __func__); - listen_add("::1", string_const(PORT)); + listen_add("::1", string_const(NUT_PORT)); } if (opt_af != AF_INET6) { upsdebugx(1, "%s: No LISTEN configuration provided, will try IPv4 localhost", __func__); - listen_add("127.0.0.1", string_const(PORT)); + listen_add("127.0.0.1", string_const(NUT_PORT)); } } @@ -986,13 +1029,31 @@ void server_load(void) listenersValidLocalhost++; } } + + if (!strcmp(server->addr, "0.0.0.0")) { + listenersAnyIPv4++; + listenersTotalAny++; + if (VALID_FD_SOCK(server->sock_fd)) { + listenersValidAnyIPv4++; + listenersValidAny++; + } + } + + if (!strcmp(server->addr, "::0")) { + listenersAnyIPv6++; + listenersTotalAny++; + if (VALID_FD_SOCK(server->sock_fd)) { + listenersValidAnyIPv6++; + listenersValidAny++; + } + } } upsdebugx(1, "%s: tried to set up %" PRIuSIZE " listening sockets, succeeded with %" PRIuSIZE, __func__, listenersTotal, listenersValid); upsdebugx(3, "%s: ...of those related to localhost: " - "overall: %" PRIuSIZE " tried, %" PRIuSIZE " succeeded; " + "overall: %" PRIuSIZE "(T)ried/%" PRIuSIZE "(S)ucceeded; " "by name: %" PRIuSIZE "T/%" PRIuSIZE "S; " "by name(6): %" PRIuSIZE "T/%" PRIuSIZE "S; " "by IPv4 addr: %" PRIuSIZE "T/%" PRIuSIZE "S; " @@ -1004,6 +1065,15 @@ void server_load(void) listenersLocalhostIPv4, listenersValidLocalhostIPv4, listenersLocalhostIPv6, listenersValidLocalhostIPv6 ); + upsdebugx(3, "%s: ...of those related to ANY: " + "overall: %" PRIuSIZE "(T)ried/%" PRIuSIZE "(S)ucceeded; " + "by IPv4 addr: %" PRIuSIZE "T/%" PRIuSIZE "S; " + "by IPv6 addr: %" PRIuSIZE "T/%" PRIuSIZE "S", + __func__, + listenersTotalAny, listenersValidAny, + listenersAnyIPv4, listenersValidAnyIPv4, + listenersAnyIPv6, listenersValidAnyIPv6 + ); /* check if we have at least 1 valid LISTEN interface */ if (!listenersValid) { @@ -1148,11 +1218,14 @@ static void upsd_cleanup(void) static void poll_reload(void) { -#ifndef WIN32 long ret; size_t maxalloc; +#ifndef WIN32 ret = sysconf(_SC_OPEN_MAX); +#else /* WIN32 */ + ret = (long)MAXIMUM_WAIT_OBJECTS; +#endif /* WIN32 */ if ((intmax_t)ret < (intmax_t)maxconn) { fatalx(EXIT_FAILURE, @@ -1167,8 +1240,12 @@ static void poll_reload(void) "The server won't start until this problem is resolved.\n", (intmax_t)maxconn); } +#ifndef WIN32 /* How many items can we stuff into the array? */ maxalloc = SIZE_MAX / sizeof(void *); +#else /* WIN32 */ + maxalloc = MAXIMUM_WAIT_OBJECTS; +#endif /* WIN32 */ if ((uintmax_t)maxalloc < (uintmax_t)maxconn) { fatalx(EXIT_FAILURE, "You requested %" PRIdMAX " as maximum number of connections, but we can only allocate %" PRIuSIZE ".\n" @@ -1176,12 +1253,11 @@ static void poll_reload(void) } /* The checks above effectively limit that maxconn is in size_t range */ + upsdebugx(1, "%s: (p)re-allocate %" PRIuMAX + " entries for polling FDs and handlers", + __func__, (uintmax_t)maxconn); fds = xrealloc(fds, (size_t)maxconn * sizeof(*fds)); handler = xrealloc(handler, (size_t)maxconn * sizeof(*handler)); -#else /* WIN32 */ - fds = xrealloc(fds, (size_t)MAXIMUM_WAIT_OBJECTS * sizeof(*fds)); - handler = xrealloc(handler, (size_t)MAXIMUM_WAIT_OBJECTS * sizeof(*handler)); -#endif /* WIN32 */ } /* instant command and setvar status tracking */ @@ -1422,6 +1498,8 @@ static void mainloop(void) pipe_conn_t * conn; #endif /* WIN32 */ + size_t nfds_wanted = 0, /* Connections we looked at (some may be invalid) */ + nfds_considered = 0; /* Connections we wanted to poll (but might be over maxconn limit) */ nfds_t nfds = 0; upstype_t *ups; nut_ctype_t *client, *cnext; @@ -1445,22 +1523,24 @@ static void mainloop(void) #ifndef WIN32 /* scan through driver sockets */ - for (ups = firstups; ups && (nfds < maxconn); ups = ups->next) { + for (ups = firstups; ups; ups = ups->next) { + nfds_considered++; /* see if we need to (re)connect to the socket */ if (INVALID_FD(ups->sock_fd)) { - upsdebugx(1, "%s: UPS [%s] is not currently connected, " + upsdebugx(1, "%s: UPS [%s] driver is not currently connected, " "trying to reconnect", __func__, ups->name); ups->sock_fd = sstate_connect(ups); if (INVALID_FD(ups->sock_fd)) { - upsdebugx(1, "%s: UPS [%s] is still not connected (FD %d)", + upsdebugx(1, "%s: UPS [%s] driver is still not connected (FD %d)", __func__, ups->name, ups->sock_fd); + continue; } else { - upsdebugx(1, "%s: UPS [%s] is now connected as FD %d", + upsdebugx(1, "%s: UPS [%s] driver is now connected as FD %d", __func__, ups->name, ups->sock_fd); + /* fall through to handle it right away */ } - continue; } /* throw some warnings if it's not feeding us data any more */ @@ -1470,6 +1550,22 @@ static void mainloop(void) ups_data_ok(ups); } + nfds_wanted++; + /* Note: not a SOCKET (type), as far as WinAPI is concerned, + * so here also checking for Unix-style file descriptor as is: */ + if (INVALID_FD(ups->sock_fd)) { + upsdebugx(5, "%s: skip DRIVER [%s, FD %d]: socket not bound", __func__, ups->name, ups->sock_fd); + continue; + } + + if (nfds >= maxconn) { + /* ignore devices that we are unable to handle */ + upsdebugx(5, "%s: skip DRIVER [%s, FD %d]: too many handled already", __func__, ups->name, ups->sock_fd); + continue; + } + + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for DRIVER [%s, FD %d]", + __func__, (uintmax_t)nfds, ups->name, ups->sock_fd); fds[nfds].fd = ups->sock_fd; fds[nfds].events = POLLIN; @@ -1481,21 +1577,32 @@ static void mainloop(void) /* scan through client sockets */ for (client = firstclient; client; client = cnext) { - cnext = client->next; + nfds_considered++; if (difftime(now, client->last_heard) > 60) { /* shed clients after 1 minute of inactivity */ /* FIXME: create an upsd.conf parameter (CLIENT_INACTIVITY_DELAY) */ + upsdebugx(5, "%s: skip CLIENT [%s => %s, FD %d]: inactive too long", __func__, client->addr, client->loginups, client->sock_fd); client_disconnect(client); continue; } + /* Do this after disconnect attempt, so zombies do not pile up: */ + if (INVALID_FD_SOCK(client->sock_fd)) { + upsdebugx(5, "%s: skip CLIENT [%s => %s, FD %d]: socket not bound", __func__, client->addr, client->loginups, client->sock_fd); + continue; + } + + nfds_wanted++; if (nfds >= maxconn) { /* ignore clients that we are unable to handle */ + upsdebugx(5, "%s: skip CLIENT [%s => %s, FD %d]: too many handled already", __func__, client->addr, client->loginups, client->sock_fd); continue; } + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for CLIENT [%s => %s, FD %d]", + __func__, (uintmax_t)nfds, client->addr, client->loginups, client->sock_fd); fds[nfds].fd = client->sock_fd; fds[nfds].events = POLLIN; @@ -1506,12 +1613,23 @@ static void mainloop(void) } /* scan through server sockets */ - for (server = firstaddr; server && (nfds < maxconn); server = server->next) { + for (server = firstaddr; server; server = server->next) { + nfds_considered++; + + if (INVALID_FD_SOCK(server->sock_fd)) { + upsdebugx(5, "%s: skip invalid SERVER listener [%s:%s, FD %d]: socket not bound", __func__, server->addr, server->port, server->sock_fd); + continue; + } - if (server->sock_fd < 0) { + nfds_wanted++; + if (nfds >= maxconn) { + /* ignore clients that we are unable to handle */ + upsdebugx(5, "%s: skip SERVER listener [%s:%s, FD %d]: too many handled already", __func__, server->addr, server->port, server->sock_fd); continue; } + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for SERVER listener [%s:%s, FD %d]", + __func__, (uintmax_t)nfds, server->addr, server->port, server->sock_fd); fds[nfds].fd = server->sock_fd; fds[nfds].events = POLLIN; @@ -1521,7 +1639,19 @@ static void mainloop(void) nfds++; } - upsdebugx(2, "%s: polling %" PRIdMAX " filedescriptors", __func__, (intmax_t)nfds); + upsdebugx(2, "%s: polling %" PRIdMAX " filedescriptors; some stats: " + "considered %" PRIdMAX " connections, " + "wanted to actually poll %" PRIdMAX + " and was constrained by maxconn=%" PRIdMAX, + __func__, (intmax_t)nfds, (intmax_t)nfds_considered, + (intmax_t)nfds_wanted, (intmax_t)maxconn); + + if (nfds_wanted != nfds || nfds_wanted >= maxconn) { + upslogx(LOG_ERR, "upsd polling %" PRIdMAX " filedescriptors," + " but wanted to poll %" PRIdMAX + " and was constrained by maxconn=%" PRIdMAX, + (intmax_t)nfds, (intmax_t)nfds_wanted, (intmax_t)maxconn); + } ret = poll(fds, nfds, 2000); @@ -1532,6 +1662,7 @@ static void mainloop(void) if (ret < 0) { upslog_with_errno(LOG_ERR, "%s", __func__); + usleep(100000); /* 0.1 sec */ return; } @@ -1634,22 +1765,24 @@ static void mainloop(void) } #else /* WIN32 */ /* scan through driver sockets */ - for (ups = firstups; ups && (nfds < maxconn); ups = ups->next) { + for (ups = firstups; ups; ups = ups->next) { + nfds_considered++; /* see if we need to (re)connect to the socket */ if (INVALID_FD(ups->sock_fd)) { - upsdebugx(1, "%s: UPS [%s] is not currently connected, " + upsdebugx(1, "%s: UPS [%s] driver is not currently connected, " "trying to reconnect", __func__, ups->name); ups->sock_fd = sstate_connect(ups); if (INVALID_FD(ups->sock_fd)) { - upsdebugx(1, "%s: UPS [%s] is still not connected (FD %d)", + upsdebugx(1, "%s: UPS [%s] driver is still not connected (FD %d)", __func__, ups->name, ups->sock_fd); + continue; } else { - upsdebugx(1, "%s: UPS [%s] is now connected as FD %d", + upsdebugx(1, "%s: UPS [%s] driver is now connected as FD %d", __func__, ups->name, ups->sock_fd); + /* fall through to handle it right away */ } - continue; } /* throw some warnings if it's not feeding us data any more */ @@ -1659,33 +1792,66 @@ static void mainloop(void) ups_data_ok(ups); } - /* FIXME: Is the conditional needed? We got here... */ - if (VALID_FD(ups->sock_fd)) { - fds[nfds] = ups->read_overlapped.hEvent; + /* Note: not a SOCKET (type) but a HANDLE, as far as WinAPI is concerned: */ + if (INVALID_FD(ups->sock_fd)) { + upsdebugx(5, "%s: skip DRIVER [%s, handle %p]: socket not bound", __func__, ups->name, ups->sock_fd); + continue; + } - handler[nfds].type = DRIVER; - handler[nfds].data = ups; + if (INVALID_FD(ups->read_overlapped.hEvent)) { + upsdebugx(5, "%s: skip DRIVER [%s, handle %p: event loop not bound", __func__, ups->name, ups->sock_fd); + continue; + } - nfds++; + nfds_wanted++; + if (nfds >= maxconn) { + /* ignore devices that we are unable to handle */ + upsdebugx(5, "%s: skip DRIVER [%s, handle %p]: too many handled already", __func__, ups->name, ups->sock_fd); + continue; } + + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for DRIVER [%s, handle %p]", + __func__, (uintmax_t)nfds, ups->name, ups->sock_fd); + fds[nfds] = ups->read_overlapped.hEvent; + + handler[nfds].type = DRIVER; + handler[nfds].data = ups; + + nfds++; } /* scan through client sockets */ for (client = firstclient; client; client = cnext) { - cnext = client->next; + nfds_considered++; if (difftime(now, client->last_heard) > 60) { /* shed clients after 1 minute of inactivity */ + upsdebugx(5, "%s: skip CLIENT [%s => %s, FD %" PRIuMAX "]: inactive too long", __func__, client->addr, client->loginups, (uintmax_t)client->sock_fd); client_disconnect(client); continue; } + /* Do this after disconnect attempt, so zombies do not pile up: */ + if (INVALID_FD_SOCK(client->sock_fd)) { + upsdebugx(5, "%s: skip CLIENT [%s => %s, FD %" PRIuMAX "]: socket not bound", __func__, client->addr, client->loginups, (uintmax_t)client->sock_fd); + continue; + } + + if (INVALID_FD(client->Event)) { + upsdebugx(5, "%s: skip CLIENT [%s => %s, FD %" PRIuMAX "]: event loop not bound", __func__, client->addr, client->loginups, (uintmax_t)client->sock_fd); + continue; + } + + nfds_wanted++; if (nfds >= maxconn) { /* ignore clients that we are unable to handle */ + upsdebugx(5, "%s: skip CLIENT [%s => %s, FD %" PRIuMAX "]: too many handled already", __func__, client->addr, client->loginups, (uintmax_t)client->sock_fd); continue; } + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for CLIENT [%s => %s, FD %" PRIuMAX "]", + __func__, (uintmax_t)nfds, client->addr, client->loginups, (uintmax_t)client->sock_fd); fds[nfds] = client->Event; handler[nfds].type = CLIENT; @@ -1695,12 +1861,28 @@ static void mainloop(void) } /* scan through server sockets */ - for (server = firstaddr; server && (nfds < maxconn); server = server->next) { + for (server = firstaddr; server; server = server->next) { + nfds_considered++; if (INVALID_FD_SOCK(server->sock_fd)) { + upsdebugx(5, "%s: skip invalid SERVER listener [%s:%s, FD %" PRIuMAX "]: socket not bound", __func__, server->addr, server->port, (uintmax_t)server->sock_fd); + continue; + } + + if (INVALID_FD(server->Event)) { + upsdebugx(5, "%s: skip invalid SERVER listener [%s:%s, FD %" PRIuMAX "]: event loop not bound", __func__, server->addr, server->port, (uintmax_t)server->sock_fd); + continue; + } + + nfds_wanted++; + if (nfds >= maxconn) { + /* ignore listeners that we are unable to handle */ + upsdebugx(5, "%s: skip SERVER listener [%s:%s, FD %" PRIuMAX "]: too many handled already", __func__, server->addr, server->port, (uintmax_t)server->sock_fd); continue; } + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for SERVER listener [%s:%s, FD %" PRIuMAX "]", + __func__, (uintmax_t)nfds, server->addr, server->port, (uintmax_t)server->sock_fd); fds[nfds] = server->Event; handler[nfds].type = SERVER; @@ -1711,18 +1893,63 @@ static void mainloop(void) /* Wait on the read IO on named pipe */ for (conn = pipe_connhead; conn; conn = conn->next) { + nfds_considered++; + + /* FIXME: derive name from conn->handle to report it. + * See GetFileInformationByHandleEx() in API + */ + if (INVALID_FD(conn->overlapped.hEvent)) { + upsdebugx(5, "%s: skip invalid NAMED PIPE listener: event loop not bound", __func__); + continue; + } + + nfds_wanted++; + if (nfds >= maxconn) { + /* ignore listeners that we are unable to handle */ + upsdebugx(5, "%s: skip NAMED PIPE listener: too many handled already", __func__); + continue; + } + + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for NAMED PIPE", + __func__, (uintmax_t)nfds); fds[nfds] = conn->overlapped.hEvent; handler[nfds].type = NAMED_PIPE; handler[nfds].data = (void *)conn; nfds++; } - /* Add the new named pipe connected event */ - fds[nfds] = pipe_connection_overlapped.hEvent; - handler[nfds].type = NAMED_PIPE; - handler[nfds].data = NULL; - nfds++; - upsdebugx(2, "%s: wait for %d filedescriptors", __func__, nfds); + /* Add the "new named pipe connected" event */ + nfds_considered++; + if (INVALID_FD(pipe_connection_overlapped.hEvent)) { + upsdebugx(5, "%s: skip invalid handler for new NAMED PIPE connections: event loop not bound", __func__); + } else { + nfds_wanted++; + if (nfds >= maxconn) { + /* ignore listeners that we are unable to handle */ + upsdebugx(5, "%s: skip handler for new NAMED PIPE connections: too many handled already", __func__); + } else { + upsdebugx(4, "%s: adding FD handler #%" PRIuMAX " for new NAMED PIPE connection", + __func__, (uintmax_t)nfds); + fds[nfds] = pipe_connection_overlapped.hEvent; + handler[nfds].type = NAMED_PIPE; + handler[nfds].data = NULL; + nfds++; + } + } + + upsdebugx(2, "%s: wait for %" PRIdMAX " filedescriptors; some stats: " + "considered %" PRIdMAX " connections, " + "wanted to actually poll %" PRIdMAX + " and was constrained by maxconn=%" PRIdMAX, + __func__, (intmax_t)nfds, (intmax_t)nfds_considered, + (intmax_t)nfds_wanted, (intmax_t)maxconn); + + if (nfds_wanted != nfds || nfds_wanted >= maxconn) { + upslogx(LOG_ERR, "upsd polling %" PRIuMAX " filedescriptors," + " but wanted to poll %" PRIuMAX + " and was constrained by maxconn=%" PRIuMAX, + (uintmax_t)nfds, (uintmax_t)nfds_wanted, (uintmax_t)maxconn); + } /* https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects */ ret = WaitForMultipleObjects(nfds,fds,FALSE,2000); @@ -1739,6 +1966,7 @@ static void mainloop(void) err = err; /* remove compile time warning */ upslog_with_errno(LOG_ERR, "%s", __func__); upsdebugx(2, "%s: wait failed: code 0x%" PRIx64, __func__, err); + Sleep(100); /* 0.1 sec */ return; } @@ -1771,6 +1999,7 @@ static void mainloop(void) if (ret >= nfds) { /* Array indexes are [0..nfds-1] */ upsdebugx(2, "%s: unexpected response to query about data available: %" PRIu64, __func__, ret); + Sleep(100); /* 0.1 sec */ return; } @@ -2214,7 +2443,8 @@ int main(int argc, char **argv) /* FIXME: Check for overflows (and int size of nfds_t vs. long) - see get_max_pid_t() for example */ maxconn = (nfds_t)sysconf(_SC_OPEN_MAX); #else /* WIN32 */ - maxconn = 64; /*FIXME NUT_WIN32_INCOMPLETE : arbitrary value, need adjustement */ + /* hard-coded 64 (from ddk/wdm.h or winnt.h) */ + maxconn = MAXIMUM_WAIT_OBJECTS; #endif /* WIN32 */ /* handle upsd.conf */ diff --git a/tests/NIT/Makefile.am b/tests/NIT/Makefile.am index 40af57864f..8332bac658 100644 --- a/tests/NIT/Makefile.am +++ b/tests/NIT/Makefile.am @@ -23,7 +23,10 @@ endif check-NIT: $(abs_srcdir)/nit.sh GREP="$(GREP)"; EGREP="$(EGREP)"; export GREP; export EGREP; \ BUILTIN_RUN_AS_USER='$(RUN_AS_USER)' BUILTIN_RUN_AS_GROUP='$(RUN_AS_GROUP)' \ - "$(abs_srcdir)/nit.sh" + abs_srcdir='$(abs_srcdir)' abs_builddir='$(abs_builddir)' \ + abs_top_srcdir='$(abs_top_srcdir)' abs_top_builddir='$(abs_top_builddir)' \ + EXEEXT='$(EXEEXT)' \ + "$(abs_srcdir)/nit.sh" # Make sure pre-requisites for NIT are fresh as we iterate check-NIT-devel: $(abs_srcdir)/nit.sh @@ -43,6 +46,9 @@ check-NIT-sandbox: $(abs_srcdir)/nit.sh LANG=C LC_ALL=C TZ=UTC \ NUT_PORT=$(NUT_PORT) NIT_CASE="$(NIT_CASE)" NUT_FOREGROUND_WITH_PID=true \ BUILTIN_RUN_AS_USER='$(RUN_AS_USER)' BUILTIN_RUN_AS_GROUP='$(RUN_AS_GROUP)' \ + abs_srcdir='$(abs_srcdir)' abs_builddir='$(abs_builddir)' \ + abs_top_srcdir='$(abs_top_srcdir)' abs_top_builddir='$(abs_top_builddir)' \ + EXEEXT='$(EXEEXT)' \ "$(abs_srcdir)/nit.sh" check-NIT-sandbox-devel: $(abs_srcdir)/nit.sh diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 49dbe83814..0db5378c85 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -31,6 +31,13 @@ # Common sandbox run for testing goes from NUT root build directory like: # DEBUG_SLEEP=600 NUT_PORT=12345 NIT_CASE=testcase_sandbox_start_drivers_after_upsd NUT_FOREGROUND_WITH_PID=true make check-NIT & # +# NOTE: For systems where executables need an extension, like Windows, +# you may require an EXEEXT variable to be exported (e.g. by a Makefile); +# also on Windows the DLL shared libraries may have to be present in same +# directory as the executable "module" (or in the current working directory). +# Ability to run in cross-builds (e.g. NUT for Windows on Linux) may depend +# on binary support deployed in the run-time system ( Wine, WSL... on Linux). +# # Design note: written with dumbed-down POSIX shell syntax, to # properly work in whatever different OSes have (bash, dash, # ksh, busybox sh...) @@ -219,6 +226,53 @@ die() { # By default, keep stdout hidden but report the errors: [ -n "$RUNCMD_QUIET_OUT" ] || RUNCMD_QUIET_OUT=true [ -n "$RUNCMD_QUIET_ERR" ] || RUNCMD_QUIET_ERR=false +execcmd() { + # Help set up EXEEXT and logging, but allow use for backgrounded runs. + # WARNING: uses `exec` and overrides NUT_DEBUG_LEVEL, so must be + # called as sub-shelled (by pipe, amperesand, backticks, etc.)! + # Do not "fix" this method to round parentheses, because the way + # this is works just right for remembering CHILDPID="$!" and later + # killing off the daemons. + log_debug "execcmd: asked for: $@" + CMDPROG="" + case "$1" in + upsc|*/upsc|upsc"${EXEEXT-}"|*/upsc"${EXEEXT-}") + if [ -n "${NUT_DEBUG_LEVEL_UPSC-}" ]; then + NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSC}" + fi + ;; + nut-scanner|*/nut-scanner|nut-scanner"${EXEEXT-}"|*/nut-scanner"${EXEEXT-}") + if [ -n "${NUT_DEBUG_LEVEL_NUT_SCANNER-}" ]; then + NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_NUT_SCANNER}" + fi + ;; + esac + + case "$1" in + *"${EXEEXT-}") CMDPROG="$1" ;; + *) if [ -x "$1" ] || (command -v "$1") >/dev/null 2>/dev/null ; then + CMDPROG="$1" + else + if [ x"${EXEEXT-}" = x ] ; then + log_warn "Did not find '$1' via 'command -v', the call below may fail" + CMDPROG="$1" + else + if [ -x "$1${EXEEXT-}" ] || (command -v "$1${EXEEXT-}") >/dev/null 2>/dev/null ; then + CMDPROG="$1${EXEEXT}" + else + log_warn "Did not find '$1' nor '$1${EXEEXT-}' via 'command -v', the call below may fail" + CMDPROG="$1" + fi + fi + fi + ;; + esac + shift + + log_debug "execcmd: running: ${CMDPROG} $@" + exec "${CMDPROG}" "$@" +} + runcmd() { # Re-uses a couple of files in test scratch area NUT_STATEPATH # to store the stderr and stdout of the launched program. @@ -232,21 +286,7 @@ runcmd() { CMDOUT="" CMDERR="" - # FIXME: Consider EXEEXT? - case "$0" in - upsc|*/upsc) - if [ -n "${NUT_DEBUG_LEVEL_UPSC-}" ]; then - NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSC}" - fi - ;; - nut-scanner|*/nut-scanner) - if [ -n "${NUT_DEBUG_LEVEL_NUT_SCANNER-}" ]; then - NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_NUT_SCANNER}" - fi - ;; - esac - - "$@" > "${NUT_STATEPATH}/runcmd.out" 2>"${NUT_STATEPATH}/runcmd.err" || CMDRES=$? + (execcmd "$@" > "${NUT_STATEPATH}/runcmd.out" 2>"${NUT_STATEPATH}/runcmd.err") || CMDRES=$? NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" CMDOUT="`cat \"${NUT_STATEPATH}/runcmd.out\"`" CMDERR="`cat \"${NUT_STATEPATH}/runcmd.err\"`" @@ -262,25 +302,89 @@ runcmd() { # from the source codebase are where the script resides, e.g. # the $(srcdir) from the Makefile. If we are not in the source # tree, tests would use binaries in PATH (e.g. packaged install). -BUILDDIR="`pwd`" +# Note that assumptions about relative paths below may NOT FIT +# out-of-tree builds, like NUT for Windows under Linux, so we'd +# better trust envvars from `make`, if exported and available to us! +BUILDDIR="" +log_debug "Current working directory: '`pwd`'" +if [ x"${abs_builddir-}" != x ] ; then + BUILDDIR="${abs_builddir-}" + log_debug "Trying BUILDDIR='${BUILDDIR}' from make vars" +else + if [ x"${builddir-}" != x ] ; then + log_debug "Trying BUILDDIR='${builddir}' from make vars" + BUILDDIR="`cd \"${builddir}\" && pwd`" + fi +fi +if [ x"${BUILDDIR}" = x ] || [ ! -d "${BUILDDIR}" ] ; then + BUILDDIR="`pwd`" + log_info "Guessing BUILDDIR='${BUILDDIR}' from script location..." +else + log_info "Using BUILDDIR='${BUILDDIR}' from make vars" +fi + TOP_BUILDDIR="" -case "${BUILDDIR}" in - */tests/NIT) - TOP_BUILDDIR="`cd \"${BUILDDIR}\"/../.. && pwd`" ;; - *) log_info "Current directory '${BUILDDIR}' is not a .../tests/NIT" ;; -esac +if [ x"${abs_top_builddir-}" != x ] ; then + TOP_BUILDDIR="${abs_top_builddir}" + log_debug "Trying TOP_BUILDDIR='${TOP_BUILDDIR}' from make vars" +else + if [ x"${top_builddir-}" != x ] ; then + log_debug "Trying TOP_BUILDDIR='${top_builddir}' from make vars" + TOP_BUILDDIR="`cd \"${top_builddir}\" && pwd`" + fi +fi +if [ x"${TOP_BUILDDIR}" = x ] || [ ! -d "${TOP_BUILDDIR}" ] ; then + case "${BUILDDIR}" in + */tests/NIT) + TOP_BUILDDIR="`cd \"${BUILDDIR}\"/../.. && pwd`" ;; + *) log_info "Current directory '${BUILDDIR}' is not a .../tests/NIT" ;; + esac + log_info "Guessing TOP_BUILDDIR='${TOP_BUILDDIR}' from script location and/or BUILDDIR value..." +else + log_info "Using TOP_BUILDDIR='${TOP_BUILDDIR}' from make vars" +fi if test ! -w "${BUILDDIR}" ; then log_error "BUILDDIR='${BUILDDIR}' is not writeable, tests may fail below" fi -SRCDIR="`dirname \"$0\"`" -SRCDIR="`cd \"$SRCDIR\" && pwd`" +SRCDIR="" +if [ x"${abs_srcdir-}" != x ] ; then + SRCDIR="${abs_srcdir}" + log_debug "Trying SRCDIR='${SRCDIR}' from make vars" +else + if [ x"${srcdir-}" != x ] ; then + log_debug "Trying SRCDIR='${srcdir}' from make vars" + SRCDIR="`cd \"${srcdir}\" && pwd`" + fi +fi +if [ x"${SRCDIR}" = x ] || [ ! -d "${SRCDIR}" ] ; then + SRCDIR="`dirname \"$0\"`" + SRCDIR="`cd \"$SRCDIR\" && pwd`" + log_info "Guessing SRCDIR='${SRCDIR}' from script location..." +else + log_info "Using SRCDIR='${SRCDIR}' from make vars" +fi + TOP_SRCDIR="" -case "${SRCDIR}" in - */tests/NIT) - TOP_SRCDIR="`cd \"${SRCDIR}\"/../.. && pwd`" ;; - *) log_info "Script source directory '${SRCDIR}' is not a .../tests/NIT" ;; -esac +if [ x"${abs_top_srcdir-}" != x ] ; then + TOP_SRCDIR="${abs_top_srcdir}" + log_debug "Trying TOP_SRCDIR='${TOP_SRCDIR}' from make vars" +else + if [ x"${top_srcdir-}" != x ] ; then + log_debug "Trying TOP_SRCDIR='${top_srcdir}' from make vars" + TOP_SRCDIR="`cd \"${top_srcdir}\" && pwd`" + fi +fi +if [ x"${TOP_SRCDIR}" = x ] || [ ! -d "${TOP_SRCDIR}" ] ; then + case "${SRCDIR}" in + */tests/NIT) + TOP_SRCDIR="`cd \"${SRCDIR}\"/../.. && pwd`" ;; + *) log_info "Script source directory '${SRCDIR}' is not a .../tests/NIT" ;; + esac + log_info "Guessing TOP_SRCDIR='${TOP_SRCDIR}' from script location and/or SRCDIR value..." +else + log_info "Using TOP_SRCDIR='${TOP_SRCDIR}' from make vars" +fi # Make these paths known to e.g. upsmon/upssched and handler scripts they call export BUILDDIR TOP_BUILDDIR SRCDIR TOP_SRCDIR @@ -322,8 +426,9 @@ else LD_LIBRARY_PATH_CLIENT="${LD_LIBRARY_PATH_ORIG}" fi +log_info "Locating NUT programs to test:" for PROG in upsd upsc dummy-ups upsmon upslog upssched ; do - (command -v ${PROG}) || die "Useless setup: ${PROG} not found in PATH: ${PATH}" + (command -v ${PROG}) || (command -v ${PROG}${EXEEXT-}) || die "Useless setup: ${PROG} not found in PATH: ${PATH}" done PID_UPSD="" @@ -986,7 +1091,7 @@ testcase_upsd_no_configs_at_all() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} ${ARG_USER} + (execcmd upsd ${ARG_FG} ${ARG_USER}) if [ "$?" = 0 ]; then log_error "[testcase_upsd_no_configs_at_all] upsd should fail without configs" FAILED="`expr $FAILED + 1`" @@ -1005,7 +1110,7 @@ testcase_upsd_no_configs_driver_file() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} ${ARG_USER} + (execcmd upsd ${ARG_FG} ${ARG_USER}) if [ "$?" = 0 ]; then log_error "[testcase_upsd_no_configs_driver_file] upsd should fail without driver config file" FAILED="`expr $FAILED + 1`" @@ -1025,7 +1130,7 @@ testcase_upsd_no_configs_in_driver_file() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} ${ARG_USER} + (execcmd upsd ${ARG_FG} ${ARG_USER}) if [ "$?" = 0 ]; then log_error "[testcase_upsd_no_configs_in_driver_file] upsd should fail without drivers defined in config file" FAILED="`expr $FAILED + 1`" @@ -1047,7 +1152,7 @@ upsd_start_loop() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} ${ARG_USER} & + execcmd upsd ${ARG_FG} ${ARG_USER} & PID_UPSD="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_debug "[${TESTCASE}] Tried to start UPSD as PID $PID_UPSD" @@ -1081,7 +1186,7 @@ upsd_start_loop() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} ${ARG_USER} & + execcmd upsd ${ARG_FG} ${ARG_USER} & PID_UPSD="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_warn "[${TESTCASE}] Tried to start UPSD again, now as PID $PID_UPSD" @@ -1149,6 +1254,28 @@ testcase_upsd_allow_no_device() { log_info "[testcase_upsd_allow_no_device] OK, empty response as expected" PASSED="`expr $PASSED + 1`" fi + + log_separator + log_info "[testcase_upsd_allow_no_device] Query JSON listing from UPSD by UPSC (no devices configured yet) to test that UPSD responds to UPSC" + if runcmd upsc -j -l localhost:$NUT_PORT && test x"${CMDOUT}" != x ; then + log_debug "[testcase_upsd_allow_no_device] got a JSON reply:" "$CMDOUT" + JSTRIP="`echo \"${CMDOUT}\" | tr -d ' ' | tr -d '\n' | tr -d '\r'`" + if test x"${JSTRIP}" = x'[]' ; then + log_info "[testcase_upsd_allow_no_device] OK, empty-list JSON response as expected" + PASSED="`expr $PASSED + 1`" + else + log_error "[testcase_upsd_allow_no_device] got a reply for upsc JSON listing for empty but running server, but it was not expected (not an empty list):" "$CMDOUT" "$CMDERR" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_upsd_allow_no_device" + res_testcase_upsd_allow_no_device=1 + fi + else + log_error "[testcase_upsd_allow_no_device] did not get a reply for upsc JSON listing for empty but running server:" "$CMDOUT" "$CMDERR" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_upsd_allow_no_device" + res_testcase_upsd_allow_no_device=1 + fi + log_separator else log_error "[testcase_upsd_allow_no_device] upsd was expected to be running although no devices are defined; is ups.conf populated?" ls -la "$NUT_CONFPATH/" || true @@ -1232,17 +1359,17 @@ sandbox_start_drivers() { if [ -n "${NUT_DEBUG_LEVEL_DRIVERS-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_DRIVERS}" fi - #upsdrvctl ${ARG_FG} ${ARG_USER} start dummy & - dummy-ups -a dummy ${ARG_USER} ${ARG_FG} & + #execcmd upsdrvctl ${ARG_FG} ${ARG_USER} start dummy & + execcmd dummy-ups -a dummy ${ARG_USER} ${ARG_FG} & PID_DUMMYUPS="$!" log_debug "Tried to start dummy-ups driver for 'dummy' as PID $PID_DUMMYUPS" if [ x"${TOP_SRCDIR}" != x ]; then - dummy-ups -a UPS1 ${ARG_USER} ${ARG_FG} & + execcmd dummy-ups -a UPS1 ${ARG_USER} ${ARG_FG} & PID_DUMMYUPS1="$!" log_debug "Tried to start dummy-ups driver for 'UPS1' as PID $PID_DUMMYUPS1" - dummy-ups -a UPS2 ${ARG_USER} ${ARG_FG} & + execcmd dummy-ups -a UPS2 ${ARG_USER} ${ARG_FG} & PID_DUMMYUPS2="$!" log_debug "Tried to start dummy-ups driver for 'UPS2' as PID $PID_DUMMYUPS2" fi @@ -1281,6 +1408,18 @@ UPS2" EXPECTED_UPSLIST="`echo \"$EXPECTED_UPSLIST\" | tr -d '\r'`" fi + EXPECTED_UPSLIST_JSON='[ + "dummy"' + if [ x"${TOP_SRCDIR}" != x ]; then + EXPECTED_UPSLIST_JSON="${EXPECTED_UPSLIST_JSON},"' + "UPS1", + "UPS2"' + fi + EXPECTED_UPSLIST_JSON="${EXPECTED_UPSLIST_JSON}"' +]' + # For windows runners (strip CR if any): + EXPECTED_UPSLIST_JSON="`echo \"$EXPECTED_UPSLIST_JSON\" | tr -d '\r'`" + log_info "[testcase_sandbox_start_upsd_alone] Query listing from UPSD by UPSC (driver not running yet)" res_testcase_sandbox_start_upsd_alone=0 runcmd upsc -l localhost:$NUT_PORT || die "[testcase_sandbox_start_upsd_alone] upsd does not respond on port ${NUT_PORT} ($?): $CMDOUT" @@ -1297,6 +1436,19 @@ UPS2" PASSED="`expr $PASSED + 1`" fi + runcmd upsc -j -l localhost:$NUT_PORT || die "[testcase_sandbox_start_upsd_alone] upsd does not respond on port ${NUT_PORT} or JSON listing failed ($?): $CMDOUT" + if [ x"${TOP_SRCDIR}" != x ]; then + CMDOUT="`echo \"$CMDOUT\" | tr -d '\r'`" + fi + if [ x"$CMDOUT" != x"$EXPECTED_UPSLIST_JSON" ] ; then + log_error "[testcase_sandbox_start_upsd_alone] got this reply for upsc JSON listing when '$EXPECTED_UPSLIST' was expected: '$CMDOUT'" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_start_upsd_alone" + res_testcase_sandbox_start_upsd_alone=1 + else + PASSED="`expr $PASSED + 1`" + fi + log_info "[testcase_sandbox_start_upsd_alone] Query driver state from UPSD by UPSC (driver not running yet)" runcmd upsc dummy@localhost:$NUT_PORT && { log_error "upsc was supposed to answer with error exit code: $CMDOUT" @@ -1314,6 +1466,30 @@ UPS2" res_testcase_sandbox_start_upsd_alone=1 fi + log_info "[testcase_sandbox_start_upsd_alone] Query driver state from UPSD by UPSC (driver not running yet) in JSON mode" + runcmd upsc -j dummy@localhost:$NUT_PORT && { + log_error "upsc was supposed to answer with error exit code: $CMDOUT" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_start_upsd_alone" + res_testcase_sandbox_start_upsd_alone=1 + } + log_debug "[testcase_sandbox_start_upsd_alone] got a JSON reply:" "$CMDOUT" + EXPECTED_UPSDATA_JSON='{ + "error": "Driver not connected" +}' + # For windows runners (strip CR if any): + EXPECTED_UPSDATA_JSON="`echo \"$EXPECTED_UPSDATA_JSON\" | tr -d '\r'`" + CMDOUT="`echo \"$CMDOUT\" | tr -d '\r'`" + # Note: avoid exact matching for stderr, because it can have Init SSL messages etc. + if echo "$CMDERR" | ${GREP} 'Error: Driver not connected' >/dev/null && test x"${CMDOUT}" = x"${EXPECTED_UPSDATA_JSON}"; then + PASSED="`expr $PASSED + 1`" + else + log_error "[testcase_sandbox_start_upsd_alone] got some other reply for upsc JSON query when 'Error: Driver not connected' was expected on stderr and similar in JSON object: '$CMDOUT'" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_start_upsd_alone" + res_testcase_sandbox_start_upsd_alone=1 + fi + if [ "$res_testcase_sandbox_start_upsd_alone" = 0 ]; then log_info "[testcase_sandbox_start_upsd_alone] PASSED: got just the failures expected for data server alone (driver not running yet)" else @@ -1323,6 +1499,17 @@ UPS2" return $res_testcase_sandbox_start_upsd_alone } +EXPECTED_UPSWAIT_JSON='{ + "ups.status": "WAIT" +}' +EXPECTED_UPSWAIT_JSON2='{ + "driver.state": "updateinfo", + "ups.status": "WAIT" +}' +# For windows runners (strip CR if any): +EXPECTED_UPSWAIT_JSON="`echo \"$EXPECTED_UPSDATA_JSON\" | tr -d '\r'`" +EXPECTED_UPSWAIT_JSON2="`echo \"$EXPECTED_UPSDATA_JSON2\" | tr -d '\r'`" + testcase_sandbox_start_upsd_after_drivers() { # Historically this is a fallback from testcase_sandbox_start_drivers_after_upsd log_info "[testcase_sandbox_start_upsd_after_drivers] Test starting UPSD after drivers" @@ -1334,7 +1521,7 @@ testcase_sandbox_start_upsd_after_drivers() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} ${ARG_USER} & + execcmd upsd ${ARG_FG} ${ARG_USER} & PID_UPSD="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_debug "[testcase_sandbox_start_upsd_after_drivers] Tried to start UPSD as PID $PID_UPSD" @@ -1345,13 +1532,24 @@ testcase_sandbox_start_upsd_after_drivers() { sleep 5 COUNTDOWN=90 + GOT_REPLY=false while [ "$COUNTDOWN" -gt 0 ]; do # For query errors or known wait, keep looping runcmd upsc dummy@localhost:$NUT_PORT \ && case "$CMDOUT" in *"ups.status: WAIT"*) ;; - *) log_info "Got output:" ; echo "$CMDOUT" ; break ;; + *) log_info "[testcase_sandbox_start_upsd_after_drivers] Got output:" ; echo "$CMDOUT" ; GOT_REPLY=true ;; esac + + runcmd upsc -j dummy@localhost:$NUT_PORT \ + && case "$CMDOUT" in + "${EXPECTED_UPSWAIT_JSON}") ;; + "${EXPECTED_UPSWAIT_JSON2}") ;; + *) log_info "[testcase_sandbox_start_upsd_after_drivers] Got JSON output:" ; echo "$CMDOUT" ; GOT_REPLY=true ;; + esac + + if $GOT_REPLY ; then break ; fi + sleep 1 COUNTDOWN="`expr $COUNTDOWN - 1`" done @@ -1380,6 +1578,7 @@ testcase_sandbox_start_drivers_after_upsd() { # 40+(drv)/50+(upsd) sec a DUMPALL is processed (regular 30-sec loop?) - # so tightly near a minute until we have sturdy replies. COUNTDOWN=90 + GOT_REPLY=false while [ "$COUNTDOWN" -gt 0 ]; do # For query errors or known wait, keep looping. May get: # driver.state: updateinfo @@ -1387,8 +1586,18 @@ testcase_sandbox_start_drivers_after_upsd() { runcmd upsc dummy@localhost:$NUT_PORT \ && case "$CMDOUT" in *"ups.status: WAIT"*) ;; - *) log_info "[testcase_sandbox_start_drivers_after_upsd] Got output:" ; echo "$CMDOUT" ; break ;; + *) log_info "[testcase_sandbox_start_drivers_after_upsd] Got output:" ; echo "$CMDOUT" ; GOT_REPLY=true ;; + esac + + runcmd upsc -j dummy@localhost:$NUT_PORT \ + && case "$CMDOUT" in + "${EXPECTED_UPSWAIT_JSON}") ;; + "${EXPECTED_UPSWAIT_JSON2}") ;; + *) log_info "[testcase_sandbox_start_drivers_after_upsd] Got JSON output:" ; echo "$CMDOUT" ; GOT_REPLY=true ;; esac + + if $GOT_REPLY ; then break ; fi + sleep 1 COUNTDOWN="`expr $COUNTDOWN - 1`" done @@ -1407,15 +1616,28 @@ testcase_sandbox_start_drivers_after_upsd() { log_info "[testcase_sandbox_start_drivers_after_upsd] Wait for dummy UPSes with larger data sets to initialize" for U in UPS1 UPS2 ; do COUNTDOWN=90 - # TODO: Convert to runcmd()? - OUT="" - while [ x"$OUT" = x"ups.status: WAIT" ] ; do - OUT="`upsc $U@localhost:$NUT_PORT ups.status`" || break - [ x"$OUT" = x"ups.status: WAIT" ] || { log_info "[testcase_sandbox_start_drivers_after_upsd] Got output:"; echo "$OUT"; break; } + GOT_REPLY=false + + while [ "$COUNTDOWN" -gt 0 ]; do + runcmd upsc $U@localhost:$NUT_PORT ups.status \ + && case "$CMDOUT" in + WAIT) ;; + *) log_info "[testcase_sandbox_start_drivers_after_upsd] Got output for $U:" ; echo "$CMDOUT" ; GOT_REPLY=true ;; + esac + + runcmd upsc -j $U@localhost:$NUT_PORT ups.status \ + && case "$CMDOUT" in + '"WAIT"') ;; # JSON string is a valid document too + *) log_info "[testcase_sandbox_start_drivers_after_upsd] Got JSON output for $U:" ; echo "$CMDOUT" ; GOT_REPLY=true ;; + esac + + if $GOT_REPLY ; then break ; fi + sleep 1 COUNTDOWN="`expr $COUNTDOWN - 1`" + # Systemic error, e.g. could not create socket file? - [ "$COUNTDOWN" -lt 1 ] && die "[testcase_sandbox_start_drivers_after_upsd] Dummy driver did not start or respond in time" + [ "$COUNTDOWN" -lt 1 ] && die "[testcase_sandbox_start_drivers_after_upsd] Dummy driver for $U did not start or respond in time" done if [ "$COUNTDOWN" -le 88 ] ; then log_warn "[testcase_sandbox_start_drivers_after_upsd] Had to wait a few retries for the $U driver to connect" @@ -1437,6 +1659,22 @@ testcase_sandbox_upsc_query_model() { PASSED="`expr $PASSED + 1`" log_info "[testcase_sandbox_upsc_query_model] PASSED: got expected model from dummy device: $CMDOUT" fi + + log_info "[testcase_sandbox_upsc_query_model] Query model from dummy device in JSON" + runcmd upsc -j dummy@localhost:$NUT_PORT device.model || die "[testcase_sandbox_upsc_query_model] upsd does not respond on port ${NUT_PORT} or can not get JSON output ($?): $CMDOUT" + log_debug "[testcase_sandbox_upsc_query_model] got a JSON reply:" "$CMDOUT" + EXPECTED_UPSDATA_JSON='"Dummy UPS"' + # For windows runners (strip CR if any): + EXPECTED_UPSDATA_JSON="`echo \"$EXPECTED_UPSDATA_JSON\" | tr -d '\r'`" + CMDOUT="`echo \"$CMDOUT\" | tr -d '\r'`" + if [ x"$CMDOUT" != x"${EXPECTED_UPSDATA_JSON}" ] ; then + log_error "[testcase_sandbox_upsc_query_model] got this reply for upsc JSON query when 'device.model: Dummy UPS' was expected: $CMDOUT" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_upsc_query_model" + else + PASSED="`expr $PASSED + 1`" + log_info "[testcase_sandbox_upsc_query_model] PASSED: got expected model from dummy device in JSON: $CMDOUT" + fi } testcase_sandbox_upsc_query_bogus() { @@ -1455,6 +1693,31 @@ testcase_sandbox_upsc_query_bogus() { FAILED="`expr $FAILED + 1`" FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_upsc_query_bogus" fi + + log_info "[testcase_sandbox_upsc_query_bogus] Query driver state from UPSD by UPSC for bogus info in JSON" + runcmd upsc -j dummy@localhost:$NUT_PORT ups.bogus.value && { + log_error "[testcase_sandbox_upsc_query_bogus] upsc was supposed to answer with error exit code: $CMDOUT" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_upsc_query_bogus" + } + log_debug "[testcase_sandbox_upsc_query_bogus] got a JSON reply:" "$CMDOUT" + EXPECTED_UPSDATA_JSON='{"error": "Variable not supported by UPS"}' + EXPECTED_UPSDATA_JSON2='{ + "error": "Variable not supported by UPS" +}' + # For windows runners (strip CR if any): + EXPECTED_UPSDATA_JSON="`echo \"$EXPECTED_UPSDATA_JSON\" | tr -d '\r'`" + EXPECTED_UPSDATA_JSON2="`echo \"$EXPECTED_UPSDATA_JSON2\" | tr -d '\r'`" + CMDOUT="`echo \"$CMDOUT\" | tr -d '\r'`" + # Note: avoid exact matching for stderr, because it can have Init SSL messages etc. + if echo "$CMDERR" | ${GREP} 'Error: Variable not supported by UPS' >/dev/null && test x"${CMDOUT}" = x"${EXPECTED_UPSDATA_JSON}" -o x"${CMDOUT}" = x"${EXPECTED_UPSDATA_JSON2}" ; then + PASSED="`expr $PASSED + 1`" + log_info "[testcase_sandbox_upsc_query_bogus] PASSED: got expected reply to bogus query in JSON" + else + log_error "[testcase_sandbox_upsc_query_bogus] got some other reply for upsc JSON query when 'Error: Variable not supported by UPS' was expected on stderr: stderr:'$CMDERR' / stdout:'$CMDOUT'" + FAILED="`expr $FAILED + 1`" + FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_upsc_query_bogus" + fi } testcase_sandbox_upsc_query_timer() { @@ -1471,13 +1734,13 @@ testcase_sandbox_upsc_query_timer() { if [ -n "${NUT_DEBUG_LEVEL_UPSLOG-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSLOG}" fi - upslog -F -i 1 -d 30 -m "dummy@localhost:${NUT_PORT},${NUT_STATEPATH}/upslog-dummy.log" & + execcmd upslog -F -i 1 -d 30 -m "dummy@localhost:${NUT_PORT},${NUT_STATEPATH}/upslog-dummy.log" & PID_UPSLOG="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" # TODO: Any need to convert to runcmd()? - OUT1="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT1" ; sleep 3 - OUT2="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT2" + OUT1="`execcmd upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT1" ; sleep 3 + OUT2="`execcmd upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT2" OUT3="" OUT4="" OUT5="" @@ -1486,13 +1749,13 @@ testcase_sandbox_upsc_query_timer() { # (pollfreq) after reading the file before wrapping around if [ x"$OUT1" = x"$OUT2" ]; then sleep 3 - OUT3="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT3" + OUT3="`execcmd upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT3" if [ x"$OUT2" = x"$OUT3" ]; then sleep 3 - OUT4="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT4" + OUT4="`execcmd upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT4" if [ x"$OUT3" = x"$OUT4" ]; then sleep 8 - OUT5="`upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT4" + OUT5="`execcmd upsc dummy@localhost:$NUT_PORT ups.status`" || die "[testcase_sandbox_upsc_query_timer] upsd does not respond on port ${NUT_PORT} ($?): $OUT4" fi fi fi @@ -1859,7 +2122,7 @@ upsmon_start_loop() { # but the sample script honours NUT_DEBUG_LEVEL_UPSSCHED if set NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSMON}" fi - upsmon ${ARG_FG} ${ARG_USER} & + execcmd upsmon ${ARG_FG} ${ARG_USER} & PID_UPSMON="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_debug "[${TESTCASE}] Tried to start UPSMON as PID $PID_UPSMON" diff --git a/tests/cpputest-client.cpp b/tests/cpputest-client.cpp index 1c4c98610b..02b588d693 100644 --- a/tests/cpputest-client.cpp +++ b/tests/cpputest-client.cpp @@ -87,11 +87,11 @@ class NutActiveClientTest : public CppUnit::TestFixture private: /* Fed by caller via envvars: */ - uint16_t NUT_PORT = 0; - std::string NUT_USER = ""; - std::string NUT_PASS = ""; - std::string NUT_PRIMARY_DEVICE = ""; - std::string NUT_SETVAR_DEVICE = ""; + uint16_t env_NUT_PORT = 0; + std::string env_NUT_USER = ""; + std::string env_NUT_PASS = ""; + std::string env_NUT_PRIMARY_DEVICE = ""; + std::string env_NUT_SETVAR_DEVICE = ""; public: void setUp() override; @@ -125,7 +125,7 @@ strarr stringvector_to_strarr(const std::vector& strset); void NutActiveClientTest::setUp() { - /* NUT_PORT etc. are provided by external test suite driver */ + /* NUT_PORT etc. env vars are provided by external test suite driver */ char * s; s = std::getenv("NUT_PORT"); @@ -134,29 +134,29 @@ void NutActiveClientTest::setUp() if (l < 1 || l > 65535) { throw std::runtime_error("NUT_PORT specified by caller is out of range"); } - NUT_PORT = static_cast(l); + env_NUT_PORT = static_cast(l); } else { throw std::runtime_error("NUT_PORT not specified by caller, NIT should call this test"); } s = std::getenv("NUT_USER"); if (s) { - NUT_USER = s; + env_NUT_USER = s; } // else stays empty s = std::getenv("NUT_PASS"); if (s) { - NUT_PASS = s; + env_NUT_PASS = s; } // else stays empty s = std::getenv("NUT_PRIMARY_DEVICE"); if (s) { - NUT_PRIMARY_DEVICE = s; + env_NUT_PRIMARY_DEVICE = s; } // else stays empty s = std::getenv("NUT_SETVAR_DEVICE"); if (s) { - NUT_SETVAR_DEVICE = s; + env_NUT_SETVAR_DEVICE = s; } // else stays empty } @@ -165,7 +165,7 @@ void NutActiveClientTest::tearDown() } void NutActiveClientTest::test_query_ver() { - nut::TcpClient c("localhost", NUT_PORT); + nut::TcpClient c("localhost", env_NUT_PORT); std::string s; std::cerr << "[D] C++ NUT Client lib test running against Data Server at: " @@ -219,7 +219,7 @@ void NutActiveClientTest::test_query_ver() { } void NutActiveClientTest::test_list_ups() { - nut::TcpClient c("localhost", NUT_PORT); + nut::TcpClient c("localhost", env_NUT_PORT); std::set devs; bool noException = true; @@ -251,12 +251,12 @@ void NutActiveClientTest::test_list_ups() { } void NutActiveClientTest::test_list_ups_clients() { - nut::TcpClient c("localhost", NUT_PORT); + nut::TcpClient c("localhost", env_NUT_PORT); std::map> deviceClients; bool noException = true; try { - c.authenticate(NUT_USER, NUT_PASS); + c.authenticate(env_NUT_USER, env_NUT_PASS); std::cerr << "[D] Authenticated without exceptions" << std::endl; /* Note: no high hopes here, credentials are checked by server * when running critical commands, not at auth request itself */ @@ -268,7 +268,7 @@ void NutActiveClientTest::test_list_ups_clients() { } try { - c.deviceLogin(NUT_PRIMARY_DEVICE); + c.deviceLogin(env_NUT_PRIMARY_DEVICE); } catch(nut::NutException& ex) { @@ -313,15 +313,15 @@ void NutActiveClientTest::test_list_ups_clients() { } void NutActiveClientTest::test_auth_user() { - if (NUT_USER.empty()) { + if (env_NUT_USER.empty()) { std::cerr << "[D] SKIPPING test_auth_user()" << std::endl; return; } - nut::TcpClient c("localhost", NUT_PORT); + nut::TcpClient c("localhost", env_NUT_PORT); bool noException = true; try { - c.authenticate(NUT_USER, NUT_PASS); + c.authenticate(env_NUT_USER, env_NUT_PASS); std::cerr << "[D] Authenticated without exceptions" << std::endl; /* Note: no high hopes here, credentials are checked by server * when running critical commands, not at auth request itself */ @@ -332,22 +332,22 @@ void NutActiveClientTest::test_auth_user() { noException = false; } - if (!NUT_SETVAR_DEVICE.empty()) { + if (!env_NUT_SETVAR_DEVICE.empty()) { try { TrackingResult tres; TrackingID tid; int i; std::string nutVar = "ups.status"; /* Has a risk of flip-flop with NIT dummy setup */ - std::string s1 = c.getDeviceVariableValue(NUT_SETVAR_DEVICE, nutVar)[0]; + std::string s1 = c.getDeviceVariableValue(env_NUT_SETVAR_DEVICE, nutVar)[0]; std::string sTest = s1 + "-test"; - std::cerr << "[D] Got initial device '" << NUT_SETVAR_DEVICE + std::cerr << "[D] Got initial device '" << env_NUT_SETVAR_DEVICE << "' variable '" << nutVar << "' value: " << s1 << std::endl; CPPUNIT_ASSERT_MESSAGE( "Did not expect empty value here", !s1.empty()); - tid = c.setDeviceVariable(NUT_SETVAR_DEVICE, nutVar, sTest); + tid = c.setDeviceVariable(env_NUT_SETVAR_DEVICE, nutVar, sTest); while ( (tres = c.getTrackingResult(tid)) == PENDING) { usleep(100); } @@ -366,7 +366,7 @@ void NutActiveClientTest::test_auth_user() { */ std::string s2; for (i = 0; i < 100 ; i++) { - s2 = c.getDeviceVariableValue(NUT_SETVAR_DEVICE, nutVar)[0]; + s2 = c.getDeviceVariableValue(env_NUT_SETVAR_DEVICE, nutVar)[0]; if (s2 == sTest) break; usleep(100000); @@ -376,7 +376,7 @@ void NutActiveClientTest::test_auth_user() { << std::endl; /* Fix it back */ - tid = c.setDeviceVariable(NUT_SETVAR_DEVICE, nutVar, s1); + tid = c.setDeviceVariable(env_NUT_SETVAR_DEVICE, nutVar, s1); while ( (tres = c.getTrackingResult(tid)) == PENDING) { usleep(100); } @@ -387,7 +387,7 @@ void NutActiveClientTest::test_auth_user() { } std::string s3; for (i = 0; i < 100 ; i++) { - s3 = c.getDeviceVariableValue(NUT_SETVAR_DEVICE, nutVar)[0]; + s3 = c.getDeviceVariableValue(env_NUT_SETVAR_DEVICE, nutVar)[0]; if (s3 == s1) break; usleep(100000); @@ -434,15 +434,15 @@ void NutActiveClientTest::test_auth_user() { } void NutActiveClientTest::test_auth_primary() { - if (NUT_USER.empty() || NUT_PRIMARY_DEVICE.empty()) { + if (env_NUT_USER.empty() || env_NUT_PRIMARY_DEVICE.empty()) { std::cerr << "[D] SKIPPING test_auth_primary()" << std::endl; return; } - nut::TcpClient c("localhost", NUT_PORT); + nut::TcpClient c("localhost", env_NUT_PORT); bool noException = true; try { - c.authenticate(NUT_USER, NUT_PASS); + c.authenticate(env_NUT_USER, env_NUT_PASS); std::cerr << "[D] Authenticated without exceptions" << std::endl; } catch(nut::NutException& ex) @@ -453,31 +453,31 @@ void NutActiveClientTest::test_auth_primary() { } try { - Device d = c.getDevice(NUT_PRIMARY_DEVICE); + Device d = c.getDevice(env_NUT_PRIMARY_DEVICE); bool gotPrimary = false; bool gotMaster = false; try { - c.deviceMaster(NUT_PRIMARY_DEVICE); + c.deviceMaster(env_NUT_PRIMARY_DEVICE); gotMaster = true; std::cerr << "[D] Elevated as MASTER without exceptions" << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Could not elevate as MASTER for " - << "NUT_PRIMARY_DEVICE " << NUT_PRIMARY_DEVICE << ": " + << "NUT_PRIMARY_DEVICE " << env_NUT_PRIMARY_DEVICE << ": " << ex.what() << std::endl; } try { - c.devicePrimary(NUT_PRIMARY_DEVICE); + c.devicePrimary(env_NUT_PRIMARY_DEVICE); gotPrimary = true; std::cerr << "[D] Elevated as PRIMARY without exceptions" << std::endl; } catch(nut::NutException& ex) { std::cerr << "[D] Could not elevate as PRIMARY for " - << "NUT_PRIMARY_DEVICE " << NUT_PRIMARY_DEVICE << ": " + << "NUT_PRIMARY_DEVICE " << env_NUT_PRIMARY_DEVICE << ": " << ex.what() << std::endl; } @@ -486,7 +486,7 @@ void NutActiveClientTest::test_auth_primary() { } catch(nut::NutException& ex) { - std::cerr << "[D] NUT_PRIMARY_DEVICE " << NUT_PRIMARY_DEVICE + std::cerr << "[D] NUT_PRIMARY_DEVICE " << env_NUT_PRIMARY_DEVICE << " not found on Data Server: " << ex.what() << std::endl; noException = false; diff --git a/tools/nut-scanner/nutscan-init.c b/tools/nut-scanner/nutscan-init.c index 1cfc236345..a577bb7496 100644 --- a/tools/nut-scanner/nutscan-init.c +++ b/tools/nut-scanner/nutscan-init.c @@ -152,9 +152,13 @@ void nutscan_init(void) #ifdef WIN32 /* Required ritual before calling any socket functions */ - WSADATA WSAdata; - WSAStartup(2,&WSAdata); - atexit((void(*)(void))WSACleanup); + static WSADATA WSAdata; + static int WSA_Started = 0; + if (!WSA_Started) { + WSAStartup(2, &WSAdata); + atexit((void(*)(void))WSACleanup); + WSA_Started = 1; + } #endif /* WIN32 */ /* Optional filter to not walk things twice */ diff --git a/tools/nut-scanner/scan_avahi.c b/tools/nut-scanner/scan_avahi.c index 16e5261261..4760368c15 100644 --- a/tools/nut-scanner/scan_avahi.c +++ b/tools/nut-scanner/scan_avahi.c @@ -304,7 +304,7 @@ static void update_device(const char * host_name, const char *ip, uint16_t port, nutscan_add_option_to_device(dev, "desc", "IPv6"); } - if (port != PORT) { + if (port != NUT_PORT) { /* +5+1+1+1 is for : - port number (max 65535 so 5 characters), - '@' and ':' characters @@ -359,7 +359,7 @@ static void update_device(const char * host_name, const char *ip, uint16_t port, if (proto == AVAHI_PROTO_INET6) { nutscan_add_option_to_device(dev, "desc", "IPv6"); } - if (port != PORT) { + if (port != NUT_PORT) { /*+1+1 is for ':' character and terminating 0 */ /*buf is the string containing the port number*/ buf_size = strlen(host_name) + strlen(buf) + 1 + 1; diff --git a/tools/nut-scanner/scan_nut.c b/tools/nut-scanner/scan_nut.c index 0065f85072..9321d96a62 100644 --- a/tools/nut-scanner/scan_nut.c +++ b/tools/nut-scanner/scan_nut.c @@ -238,7 +238,7 @@ static void * list_nut_devices_thready(void * arg) * around the host name: */ buf_size = strlen(answer[1]) + strlen(hostname) + 1 + 1 + 1 + 1; - if (port != PORT) { + if (port != NUT_PORT) { /* colon and up to 5 digits */ buf_size += 6; } @@ -254,7 +254,7 @@ static void * list_nut_devices_thready(void * arg) if (*hostname == '[') hostname_colon = NULL; - if (port != PORT) { + if (port != NUT_PORT) { if (hostname_colon) { snprintf(dev->port, buf_size, "%s@[%s]:%" PRIu16, answer[1], hostname, port);