diff --git a/.gitignore b/.gitignore index de8f6c43df..79412200cf 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ Makefile Makefile.in tags +.all.*-generated.timestamp ## Parent directory only /aclocal.m4 diff --git a/Makefile.am b/Makefile.am index 1622c034ed..8db3c2ba47 100644 --- a/Makefile.am +++ b/Makefile.am @@ -94,22 +94,46 @@ SUBDIRS_ALL_LIBS_LOCAL = \ #all all-recursive all-am-local all-local: all-fanout-maybe all-recursive: all-fanout-maybe -check-recursive install-recursive: generated-headers-with-a-touch -all check install: all-fanout-cleanup +# Make sure automake-defined check/install goals begin with our hack for +# touch-files for generated headers (so each depending sub-directory does +# not re-evaluate those rules in whole); note that these goals may end up +# also calling all-fanout-maybe (as they depend on "all"): +check-recursive install-recursive: generated-headers-with-a-touch +# NOTE: Beside dependency above, also called from some code paths in +# the all-fanout-maybe implementation: generated-headers-with-a-touch: @dotMAKE@ - $(MAKE) $(AM_MAKEFLAGS) NUT_VERSION_H_GENERATED=false nut_version.h - $(MAKE) $(AM_MAKEFLAGS) touch-include-all-nut_version-generated.timestamp - $(MAKE) $(AM_MAKEFLAGS) libupsclient-version.h - $(MAKE) $(AM_MAKEFLAGS) touch-clients-all-libupsclient_version-generated.timestamp + +$(MAKE) $(AM_MAKEFLAGS) NUT_VERSION_H_GENERATED=false nut_version.h + +$(MAKE) $(AM_MAKEFLAGS) touch-include-all-nut_version-generated.timestamp + +$(MAKE) $(AM_MAKEFLAGS) libupsclient-version.h + +$(MAKE) $(AM_MAKEFLAGS) touch-clients-all-libupsclient_version-generated.timestamp + +$(MAKE) $(AM_MAKEFLAGS) NUT_LINKMAN_GENERATED=false prep-linkman-generated + +$(MAKE) $(AM_MAKEFLAGS) touch-docs-man-all-linkman-generated-generated.timestamp + +# After completing the automake-defined goals, clean up: +all: all-fanout-cleanup +check: check-fanout-cleanup +install: install-fanout-cleanup # Run as part of "all", but after the autotools-standard "all-recursive" # where we quiesce nut_version.h regeneration attempts for each subdir -all-fanout-cleanup: all-recursive +# which automake recipes iterate; similarly for "check" and "install": +cleanup-touchfiles-for-generated-headers: @rm -f include/.all.nut_version-generated.timestamp \ + docs/man/.all.nut_linkman-generated.timestamp \ clients/.all.libupsclient_version-generated.timestamp +all-fanout-cleanup: all-recursive @dotMAKE@ + +@$(MAKE) $(AM_MAKEFLAGS) cleanup-touchfiles-for-generated-headers + +check-fanout-cleanup: check-recursive @dotMAKE@ + +@$(MAKE) $(AM_MAKEFLAGS) cleanup-touchfiles-for-generated-headers + +install-fanout-cleanup: install-recursive @dotMAKE@ + +@$(MAKE) $(AM_MAKEFLAGS) cleanup-touchfiles-for-generated-headers + +# Called from generated-headers-with-a-touch: touch-include-all-nut_version-generated.timestamp: @[ -s include/nut_version.h ] @touch -r include/nut_version.h -d '-10 seconds' include/.all.nut_version-generated.timestamp && exit ; \ @@ -122,6 +146,16 @@ touch-clients-all-libupsclient_version-generated.timestamp: touch -d '1970-01-01' clients/.all.libupsclient_version-generated.timestamp && exit ; \ touch clients/.all.libupsclient_version-generated.timestamp +touch-docs-man-all-linkman-generated-generated.timestamp: + @[ -s docs/man/linkman-driver-names.txt ] && [ -s docs/man/linkman-drivertool-names.txt ] + @if test -n "`find docs/man/linkman-driver-names.txt -newer docs/man/linkman-drivertool-names.txt`" ; then \ + touch -r docs/man/linkman-drivertool-names.txt -d '-10 seconds' docs/man/.all.nut_linkman-generated.timestamp && exit ; \ + else \ + touch -r docs/man/linkman-driver-names.txt -d '-10 seconds' docs/man/.all.nut_linkman-generated.timestamp && exit ; \ + fi ; \ + touch -d '1970-01-01' docs/man/.all.nut_linkman-generated.timestamp && exit ; \ + touch docs/man/.all.nut_linkman-generated.timestamp + # Verbosity for fanout rule tracing; 0/1 (or "default" that may auto-set # to 0 or 1 in some rules below) SUBDIR_MAKE_VERBOSE = default @@ -134,7 +168,7 @@ all-fanout-maybe: @dotMAKE@ clients/.all.libupsclient_version-generated.timestamp +@if [ x"$(NUT_MAKE_SKIP_FANOUT)" = xtrue ] ; then \ if [ x"$(SUBDIR_MAKE_VERBOSE)" != x0 ] ; then \ - echo " SUBDIR-MAKE $@: skip optimization for parallel make - NUT_MAKE_SKIP_FANOUT is set" ; \ + echo " SUBDIR-MAKE SKIP $@: skip optimization for parallel make - NUT_MAKE_SKIP_FANOUT is set" ; \ fi ; \ $(MAKE) $(AM_MAKEFLAGS) generated-headers-with-a-touch || exit ; \ exit 0 ; \ @@ -142,15 +176,15 @@ all-fanout-maybe: @dotMAKE@ case "-$(MAKEFLAGS) $(AM_MAKEFLAGS)" in \ *-j|*-j" "*|*-{j,l}{0,1,2,3,4,5,6,7,8,9}*|*-[jl][0123456789]*|*{-l,--jobs,--load-average,--max-load}" "{-,0,1,2,3,4,5,6,7,8,9}*|*--jobserver*|*--jobs" "[0123456789]*|*--load-average" "[0123456789]*|*--max-load" "[0123456789]*) \ if [ x"$(SUBDIR_MAKE_VERBOSE)" != x0 ] ; then \ - echo " SUBDIR-MAKE $@: implement optimization for parallel make as 'make all-fanout-subdirs'" ; \ + echo " SUBDIR-MAKE LAUNCH $@: implement optimization for parallel make as 'make all-fanout-subdirs'" ; \ fi ; \ $(MAKE) $(AM_MAKEFLAGS) all-fanout-subdirs || exit ; \ if [ x"$(SUBDIR_MAKE_VERBOSE)" != x0 ] ; then \ - echo " SUBDIR-MAKE $@: optimization for parallel make as 'make all-fanout-subdirs' finished; now automake rules can follow up with default implementation of 'all-recursive' and/or 'all' (may try to regenerate some files again; should keep existing results though)" ; \ + echo " SUBDIR-MAKE FINISH $@: optimization for parallel make as 'make all-fanout-subdirs' finished; now automake rules can follow up with default implementation of 'all-recursive' and/or 'all' (may try to regenerate some files again; should keep existing results though)" ; \ fi ;; \ *) \ if [ x"$(SUBDIR_MAKE_VERBOSE)" != x0 ] ; then \ - echo " SUBDIR-MAKE $@: skip optimization for parallel make - we seem to run sequentially now, seen MAKEFLAGS='$(MAKEFLAGS)' AM_MAKEFLAGS='$(AM_MAKEFLAGS)'" ; \ + echo " SUBDIR-MAKE SKIP $@: skip optimization for parallel make - we seem to run sequentially now, seen MAKEFLAGS='$(MAKEFLAGS)' AM_MAKEFLAGS='$(AM_MAKEFLAGS)'" ; \ fi ; \ $(MAKE) $(AM_MAKEFLAGS) generated-headers-with-a-touch || exit ; \ ;; \ @@ -234,12 +268,12 @@ SUBDIR_TGT_RULE = ( \ [ x"$${TGT-}" != x ] || TGT="`echo '$@' | awk -F/ '{print $$1}'`" ; \ [ x"$${DIR-}" != x ] || DIR="`echo '$@' | sed 's,^[^/]*/,,'`" ; \ if [ x"$(SUBDIR_MAKE_VERBOSE)" != x0 ] ; then \ - echo " SUBDIR-MAKE STARTING $@: 'make $${SUBDIR_TGT_MAKEFLAGS-} $$TGT' in $$DIR ..." ; \ + echo " SUBDIR-MAKE STARTING $@: 'make $${SUBDIR_TGT_MAKEFLAGS-} $$TGT' in $$DIR ..." ; \ fi ; \ cd "$(abs_builddir)/$${DIR}" && \ $(MAKE) $(AM_MAKEFLAGS) $${SUBDIR_TGT_MAKEFLAGS-} "$${TGT}" || { RES=$$?; echo " SUBDIR-MAKE FAILURE: 'make $$TGT' in $$DIR" >&2 ; exit $$RES ; } ; \ if [ x"$(SUBDIR_MAKE_VERBOSE)" != x0 ] ; then \ - echo " SUBDIR-MAKE SUCCESS $@: 'make $${SUBDIR_TGT_MAKEFLAGS-} $$TGT' in $$DIR" ; \ + echo " SUBDIR-MAKE SUCCESS $@: 'make $${SUBDIR_TGT_MAKEFLAGS-} $$TGT' in $$DIR" ; \ fi ; \ ) @@ -343,16 +377,30 @@ all/include: all-libs-local/include @dotMAKE@ +@NUT_VERSION_H_GENERATED=true; export NUT_VERSION_H_GENERATED; \ $(SUBDIR_TGT_RULE) -prep-src-docs/docs/man: @dotMAKE@ - +@SUBDIR_TGT_MAKEFLAGS='MAINTAINER_DOCS_PREP_MAN_DELAY=3'; export SUBDIR_TGT_MAKEFLAGS; \ +# Quickly bail out if somehow recursed into this goal from a dependency +# (looking at all/docs with its several sub-make calls): +prep-linkman-generated/docs/man: @dotMAKE@ + +@if [ x"$(NUT_LINKMAN_GENERATED)" = xtrue ] || [ x"$${NUT_LINKMAN_GENERATED}" = xtrue ] ; then exit 0 ; fi ; \ + SUBDIR_TGT_MAKEFLAGS='NUT_LINKMAN_GENERATED=false'; export SUBDIR_TGT_MAKEFLAGS; \ + NUT_LINKMAN_GENERATED=false; export NUT_LINKMAN_GENERATED; \ + $(SUBDIR_TGT_RULE) || exit ; \ + $(MAKE) $(AM_MAKEFLAGS) touch-docs-man-all-linkman-generated-generated.timestamp + +prep-src-docs/docs/man: prep-linkman-generated/docs/man @dotMAKE@ + +@SUBDIR_TGT_MAKEFLAGS='MAINTAINER_DOCS_PREP_MAN_DELAY=3 NUT_LINKMAN_GENERATED=true'; export SUBDIR_TGT_MAKEFLAGS; \ + NUT_LINKMAN_GENERATED=true; export NUT_LINKMAN_GENERATED; \ $(SUBDIR_TGT_RULE) -prep-src-docs/docs: @dotMAKE@ - +@DOCS_NO_MAN=true; export DOCS_NO_MAN; \ +prep-src-docs/docs: prep-linkman-generated/docs/man @dotMAKE@ + +@SUBDIR_TGT_MAKEFLAGS='NUT_LINKMAN_GENERATED=true'; export SUBDIR_TGT_MAKEFLAGS; \ + DOCS_NO_MAN=true; export DOCS_NO_MAN; \ + NUT_LINKMAN_GENERATED=true; export NUT_LINKMAN_GENERATED; \ $(SUBDIR_TGT_RULE) all/docs/man: prep-src-docs/docs/man @dotMAKE@ - +@$(SUBDIR_TGT_RULE) + +@SUBDIR_TGT_MAKEFLAGS='NUT_LINKMAN_GENERATED=true'; export SUBDIR_TGT_MAKEFLAGS; \ + NUT_LINKMAN_GENERATED=true; export NUT_LINKMAN_GENERATED; \ + $(SUBDIR_TGT_RULE) # Note: we optionally sort of depend on ChangeLog.adoc so it is pre-made and # pre-processed for html/pdf renders (if any are requested), so they surely @@ -361,7 +409,8 @@ all/docs/man: prep-src-docs/docs/man @dotMAKE@ # types are enabled. MAINTAINER_ASCIIDOCS_CHANGELOG_DELAY = 0 all/docs: prep-src-docs/docs/man @dotMAKE@ - +@case "@DOC_BUILD_LIST@" in \ + +@NUT_LINKMAN_GENERATED=true; export NUT_LINKMAN_GENERATED; \ + case "@DOC_BUILD_LIST@" in \ *pdf*|*html-single*|*html-chunked*) \ echo " DOC-CHANGELOG-ASCIIDOC Pre-generate ChangeLog artifacts before the bulk of $@ ..." ; \ MAINTAINER_ASCIIDOCS_CHANGELOG_DELAY="$(MAINTAINER_ASCIIDOCS_CHANGELOG_DELAY)" \ @@ -371,11 +420,14 @@ all/docs: prep-src-docs/docs/man @dotMAKE@ echo " DOC-CHANGELOG-ASCIIDOC Pre-generate ChangeLog artifacts before the bulk of $@ : SUCCESS" ;; \ *) ;; \ esac - +@$(MAKE) $(AM_MAKEFLAGS) prep-src-docs/docs - +@DOCS_NO_MAN=true; export DOCS_NO_MAN; $(SUBDIR_TGT_RULE) + +@NUT_LINKMAN_GENERATED=true; export NUT_LINKMAN_GENERATED; \ + $(MAKE) $(AM_MAKEFLAGS) prep-src-docs/docs + +@NUT_LINKMAN_GENERATED=true; export NUT_LINKMAN_GENERATED; \ + DOCS_NO_MAN=true; export DOCS_NO_MAN; $(SUBDIR_TGT_RULE) all-recursive/docs: all/docs all/docs/man @dotMAKE@ - +@$(SUBDIR_TGT_RULE) + +@NUT_LINKMAN_GENERATED=true; export NUT_LINKMAN_GENERATED; \ + $(SUBDIR_TGT_RULE) # Dependencies below are dictated by who needs whose library from another dir # (generated by a sub-make there, so we pre-emptively ensure it exists to avoid @@ -761,7 +813,7 @@ spellcheck spellcheck-interactive: @dotMAKE@ if [ x"$(NUT_MAKE_SKIP_FANOUT)" = xtrue ] ; then \ RES=0 ; \ if [ x"$(SUBDIR_MAKE_VERBOSE)" != x0 ] ; then \ - echo " SUBDIR-MAKE $@: skip optimization for parallel make - NUT_MAKE_SKIP_FANOUT is set" ; \ + echo " SUBDIR-MAKE SKIP $@: skip optimization for parallel make - NUT_MAKE_SKIP_FANOUT is set" ; \ fi ; \ (cd $(builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) -k -s $(abs_top_builddir)/docs/.prep-src-docs) || RES=$$? ; \ (cd $(builddir)/docs/man && $(MAKE) $(AM_MAKEFLAGS) -k -s $(abs_top_builddir)/docs/man/.prep-src-docs) || RES=$$? ; \ @@ -781,8 +833,9 @@ spellcheck spellcheck-interactive: @dotMAKE@ fi ; \ export SUBDIR_MAKE_VERBOSE ; \ ( $(MAKE) $(AM_MAKEFLAGS) SPELLCHECK_TGT='$@' SUBDIR_MAKE_VERBOSE="$${SUBDIR_MAKE_VERBOSE}" -k -s $(SPELLCHECK_DIRS) && exit ; \ - echo "WARNING: FAILED fanned-out attempt in $@, retrying with NUT_MAKE_SKIP_FANOUT" >&2 ; \ - $(MAKE) $(AM_MAKEFLAGS) NUT_MAKE_SKIP_FANOUT=true SPELLCHECK_TGT='$@' -k -s $(SPELLCHECK_DIRS) ) || exit ; \ + echo "WARNING: FAILED fanned-out attempt in $@, retrying with NUT_MAKE_SKIP_FANOUT" >&2 ; \ + $(MAKE) $(AM_MAKEFLAGS) NUT_MAKE_SKIP_FANOUT=true SPELLCHECK_TGT='$@' -k -s $(SPELLCHECK_DIRS) \ + ) || exit ; \ if [ x'$@' = xspellcheck-interactive ] ; then \ echo "SUCCESS: $@: follow up with spellcheck-quick to revise and update timestamps"; \ $(MAKE) $(AM_MAKEFLAGS) spellcheck-quick ; \ @@ -1087,6 +1140,10 @@ $(abs_top_builddir)/ChangeLog: tools/gitlog2changelog.py dummy-stamp ChangeLog.adoc: ChangeLog @dotMAKE@ +cd $(abs_top_builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) ../ChangeLog.adoc +prep-linkman-generated: @dotMAKE@ + @rm -f docs/man/.all.nut_linkman-generated.timestamp + +cd $(abs_top_builddir)/docs/man && $(MAKE) $(AM_MAKEFLAGS) prep-linkman-generated + nut_version.h include/nut_version.h: @dotMAKE@ @rm -f include/.all.nut_version-generated.timestamp +cd $(abs_top_builddir)/include && $(MAKE) $(AM_MAKEFLAGS) nut_version.h diff --git a/NEWS.adoc b/NEWS.adoc index e308d6fc79..2b470bbd24 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -86,6 +86,10 @@ https://github.com/networkupstools/nut/milestone/12 not-requested (e.g. do not start `upsd` in `MODE=netclient`), even though the unit is nominally enabled; this should make packaging and providing service pre-sets more simple. [issue #837, PR #3233] ++ +In a way, this should also fix fallout of a change delivered in NUT v2.8.4 +release, where `upsmon` could have started with `MODE=none` in `nut.conf`, +but the `nutshutdown` script would bail out quickly and quietly. [PR #3008] * Fixed thread-safety of IP address printout in `libupsclient` method `upscli_tryconnect()` (practical bug seen in `nut-scanner` parallel scans). [issue #3234] @@ -327,7 +331,7 @@ https://github.com/networkupstools/nut/milestone/12 - `upsd` data server updates: * Sometimes "Data for UPS [X] is stale" and "UPS [X] data is no longer - stale" messages were logged in the same second, especially no busy + stale" messages were logged in the same second, especially on busy systems. Now we allow one more second on top of `MAXAGE` setting to declare the device dead, just in case fractional/whole second rounding comes into play and breaks things. [issue #661] @@ -337,7 +341,10 @@ https://github.com/networkupstools/nut/milestone/12 operating system allows, by only waiting for that amount of Unix sockets or Windows `HANDLE`'s at a time, and moving on to another chunk. The system-provided value can be further limited by `NUT_SYSMAXCONN_LIMIT` - environment variable (e.g. in tests). [#3302] + environment variable (e.g. in tests). For the other side of the coin, the + NIT script now supports an optional `DUMMY_UPS_SWARM_COUNT` environment + variable which can specify the amount of additional drivers to spawn for + the sake of stress-testing, and `UPSLOG_SWARM_COUNT` for clients. [#3302] * Extended processing of `CERTREQUEST` setting to handle numeric or specific string values, to match both ways of reading ambiguous documentation. Added `configure --with-ssl-client-validation` toggle to expose the @@ -502,6 +509,8 @@ several `FSD` notifications into one executed action. [PR #3097] * Added an option to (primarily) `--disable-threading` for systems with detected but broken `libpthread` support, or to test alternate code paths during development or in CI. [#3300] + * Adjusted C++ header search path on Termux when `-isystem` is involved, + as noted with NSS builds. [issues #1599, #1711, PR #3353] - Recipes, CI and helper script updates not classified above: * Fixed CI recipes for PyPI publication of PyNUT(Client) module to also diff --git a/clients/Makefile.am b/clients/Makefile.am index 36a59ec8b4..4af2e01732 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -16,9 +16,9 @@ CLEANFILES = # optionally includes "common.h" with the NUT build setup - and this option # was never triggered in fact, not until pushed through command line like this: AM_CXXFLAGS = -DHAVE_NUTCOMMON=1 -I$(top_builddir)/include -I$(top_srcdir)/include -if WITH_SSL - AM_CXXFLAGS += $(LIBSSL_CFLAGS) -endif WITH_SSL +if WITH_SSL_CXX + AM_CXXFLAGS += $(LIBSSL_CXXFLAGS) +endif WITH_SSL_CXX # Make sure out-of-dir dependencies exist (especially when dev-building parts): $(top_builddir)/include/nut_version.h \ @@ -309,9 +309,9 @@ if HAVE_WINDOWS # Many versions of MingW seem to fail to build non-static DLL without this libnutclient_la_LDFLAGS += -no-undefined endif HAVE_WINDOWS -if WITH_SSL +if WITH_SSL_CXX libnutclient_la_LIBADD += $(LIBSSL_LDFLAGS_RPATH) $(LIBSSL_LIBS) -endif WITH_SSL +endif WITH_SSL_CXX else !HAVE_CXX11 EXTRA_DIST += nutclient.h nutclient.cpp endif !HAVE_CXX11 diff --git a/clients/nutclient.cpp b/clients/nutclient.cpp index 9df97b0b9d..77e396ee15 100644 --- a/clients/nutclient.cpp +++ b/clients/nutclient.cpp @@ -47,14 +47,16 @@ #include #include -#ifdef WITH_NSS -# include -# include -# include -# include -# include -# include -#endif /* WITH_NSS */ +#ifdef WITH_SSL_CXX +# ifdef WITH_NSS +# include +# include +# include +# include +# include +# include +# endif /* WITH_NSS */ +#endif /* WITH_SSL_CXX */ /* Windows/Linux Socket compatibility layer: */ /* Thanks to Benjamin Roux (http://broux.developpez.com/articles/c/sockets/) */ @@ -258,11 +260,13 @@ class Socket private: SOCKET _sock; -#ifdef WITH_OPENSSL +#ifdef WITH_SSL_CXX +# ifdef WITH_OPENSSL SSL* _ssl; static SSL_CTX* _ssl_ctx; -#elif defined(WITH_NSS) +# elif defined(WITH_NSS) PRFileDesc* _ssl; +# endif #endif bool _debugConnect; struct timeval _tv; @@ -271,7 +275,8 @@ class Socket uint16_t _port; int _ssl_configured; int _force_ssl; /* Always known, so even non-SSL builds can fail if security is required */ -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +#ifdef WITH_SSL_CXX +# if defined(WITH_OPENSSL) || defined(WITH_NSS) int _certverify; /* OpenSSL specific */ std::string _ca_path; @@ -284,14 +289,14 @@ class Socket std::string _certstore_prefix; std::string _certident_name; std::string _certhost_name; -#endif +# endif -#if defined(WITH_OPENSSL) +# if defined(WITH_OPENSSL) /* Callbacks, syntax dictated by OpenSSL */ static int openssl_password_callback(char *buf, int size, int rwflag, void *userdata); /* pem_passwd_cb, 1.1.0+ */ -#endif +# endif -#if defined(WITH_NSS) +# if defined(WITH_NSS) /* Callbacks, syntax dictated by NSS */ static char *nss_password_callback(PK11SlotInfo *slot, PRBool retry, void *arg); static SECStatus AuthCertificate(CERTCertDBHandle *arg, PRFileDesc *fd, @@ -303,10 +308,12 @@ class Socket CERTDistNames *caNames, CERTCertificate **pRetCert, SECKEYPrivateKey **pRetKey); static void HandshakeCallback(PRFileDesc *fd, void *arg); -#endif +# endif +#endif /* WITH_SSL_CXX */ }; -#ifdef WITH_OPENSSL +#ifdef WITH_SSL_CXX +# ifdef WITH_OPENSSL SSL_CTX* Socket::_ssl_ctx = nullptr; /*static*/ int Socket::openssl_password_callback(char *buf, int size, int rwflag, void *userdata) /* pem_passwd_cb, 1.1.0+ */ @@ -322,9 +329,9 @@ SSL_CTX* Socket::_ssl_ctx = nullptr; buf[size - 1] = '\0'; return static_cast(strlen(buf)); } -#endif +# endif /* WITH_OPENSSL */ -#ifdef WITH_NSS +# ifdef WITH_NSS static void nss_error(const char* funcname) { char buffer[256]; @@ -423,20 +430,25 @@ static void nss_error(const char* funcname) std::cerr << "SSL handshake done successfully with server " << sock->_host << std::endl; } } -#endif /* WITH_NSS */ +# endif /* WITH_NSS */ +#endif /* WITH_SSL_CXX */ Socket::Socket(): _sock(INVALID_SOCKET), -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +#ifdef WITH_SSL_CXX +# if defined(WITH_OPENSSL) || defined(WITH_NSS) _ssl(nullptr), +# endif #endif _debugConnect(false), _tv(), _port(NUT_PORT), _force_ssl(0) -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +#ifdef WITH_SSL_CXX +# if defined(WITH_OPENSSL) || defined(WITH_NSS) ,_certverify(-1) -#endif +# endif +#endif /* WITH_SSL_CXX */ { _tv.tv_sec = -1; _tv.tv_usec = 0; @@ -761,17 +773,20 @@ void Socket::connect(const std::string& host, uint16_t port) void Socket::disconnect() { -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +#ifdef WITH_SSL_CXX +# if defined(WITH_OPENSSL) || defined(WITH_NSS) if (_ssl) { -# ifdef WITH_OPENSSL +# ifdef WITH_OPENSSL SSL_shutdown(_ssl); SSL_free(_ssl); -# elif defined(WITH_NSS) +# elif defined(WITH_NSS) PR_Close(_ssl); -# endif +# endif _ssl = nullptr; } -#endif +# endif +#endif /* WITH_SSL_CXX */ + if(_sock != INVALID_SOCKET) { ::closesocket(_sock); @@ -782,7 +797,7 @@ void Socket::disconnect() bool Socket::isSSL()const { -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +#if defined (WITH_SSL_CXX) && (defined(WITH_OPENSSL) || defined(WITH_NSS)) return _ssl != nullptr; #else return false; @@ -793,7 +808,7 @@ void Socket::setSSLConfig_OpenSSL(bool force_ssl, int certverify, const std::str { _force_ssl = force_ssl; -#if defined(WITH_OPENSSL) +#if defined(WITH_SSL_CXX) && defined(WITH_OPENSSL) _certverify = certverify; /* These need to be saved at least to handle callbacks * (to see if errors are fatal or ignorable) @@ -821,7 +836,7 @@ void Socket::setSSLConfig_NSS(bool force_ssl, int certverify, const std::string& { _force_ssl = force_ssl; -#if defined(WITH_NSS) +#if defined(WITH_SSL_CXX) && defined(WITH_NSS) _certverify = certverify; /* These need to be saved at least to handle NSS callbacks * (to see if errors are fatal or ignorable) @@ -851,7 +866,8 @@ void Socket::startTLS() throw nut::NotConnectedException(); } -#ifdef WITH_OPENSSL +#ifdef WITH_SSL_CXX +# ifdef WITH_OPENSSL if (!(_ssl_configured & UPSCLI_SSL_CAPS_OPENSSL)) { if (_debugConnect) std::cerr << "[D2] Socket::startTLS(): Not configured for OpenSSL" << @@ -863,7 +879,7 @@ void Socket::startTLS() } return; } -#elif defined(WITH_NSS) +# elif defined(WITH_NSS) if (!(_ssl_configured & UPSCLI_SSL_CAPS_NSS)) { if (_debugConnect) std::cerr << "[D2] Socket::startTLS(): Not configured for NSS" << @@ -875,9 +891,9 @@ void Socket::startTLS() } return; } -#endif /* WITH_OPENSSL || WITH_NSS */ +# endif /* WITH_OPENSSL || WITH_NSS */ -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +# if defined(WITH_OPENSSL) || defined(WITH_NSS) write("STARTTLS"); std::string res = read(); if (res.substr(0, 11) != "OK STARTTLS") { @@ -887,17 +903,17 @@ void Socket::startTLS() } return; } -#endif /* WITH_OPENSSL || WITH_NSS */ +# endif /* WITH_OPENSSL || WITH_NSS */ -#ifdef WITH_OPENSSL +# ifdef WITH_OPENSSL if (!_ssl_ctx) { -# if OPENSSL_VERSION_NUMBER < 0x10100000L +# if OPENSSL_VERSION_NUMBER < 0x10100000L SSL_load_error_strings(); SSL_library_init(); _ssl_ctx = SSL_CTX_new(SSLv23_client_method()); -# else +# else _ssl_ctx = SSL_CTX_new(TLS_client_method()); -# endif +# endif if (!_ssl_ctx) { throw nut::SSLException_OpenSSL("Cannot create SSL context"); } @@ -916,9 +932,9 @@ void Socket::startTLS() throw nut::SSLException_OpenSSL("Failed to load client certificate file"); } if (!_key_pass.empty()) { -# if OPENSSL_VERSION_NUMBER < 0x10100000L +# if OPENSSL_VERSION_NUMBER < 0x10100000L throw nut::SSLException_OpenSSL("Private key password support not implemented for OpenSSL < 1.1 yet"); -# else +# else /* OpenSSL 1.1.0+ * https://docs.openssl.org/3.5/man3/SSL_CTX_set_default_passwd_cb/#return-values */ @@ -926,7 +942,7 @@ void Socket::startTLS() SSL_CTX_set_default_passwd_cb(_ssl_ctx, openssl_password_callback); /* 2. Set the userdata to the password string */ SSL_CTX_set_default_passwd_cb_userdata(_ssl_ctx, const_cast(static_cast(_key_pass.c_str()))); -# endif +# endif } if (SSL_CTX_use_PrivateKey_file(_ssl_ctx, _key_file.empty() ? _cert_file.c_str() : _key_file.c_str(), SSL_FILETYPE_PEM) != 1) { throw nut::SSLException_OpenSSL("Failed to load client private key file"); @@ -948,7 +964,7 @@ void Socket::startTLS() throw nut::SSLException_OpenSSL(std::string("SSL connection failed: ") + errbuf); } -#elif defined(WITH_NSS) +# elif defined(WITH_NSS) /* NSS implementation following upsclient.c logic */ static bool nss_initialized = false; @@ -1025,6 +1041,7 @@ void Socket::startTLS() disconnect(); throw nut::SSLException_NSS("Handshake failed"); } +# endif /* WITH_NSS */ #else if (_debugConnect) std::cerr << "[D2] Socket::startTLS(): SSL support not compiled in" << @@ -1034,7 +1051,7 @@ void Socket::startTLS() disconnect(); throw nut::SSLException("SSL support not compiled in"); } -#endif +#endif /* WITH_SSL_CXX */ } bool Socket::isConnected()const @@ -1061,7 +1078,7 @@ size_t Socket::read(void* buf, size_t sz) } ssize_t res; -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +#if defined(WITH_SSL_CXX) && (defined(WITH_OPENSSL) || defined(WITH_NSS)) if (_ssl) { # ifdef WITH_OPENSSL res = SSL_read(_ssl, buf, static_cast(sz)); @@ -1102,7 +1119,7 @@ size_t Socket::write(const void* buf, size_t sz) } ssize_t res; -#if defined(WITH_OPENSSL) || defined(WITH_NSS) +#if defined(WITH_SSL_CXX) && (defined(WITH_OPENSSL) || defined(WITH_NSS)) if (_ssl) { # ifdef WITH_OPENSSL res = SSL_write(_ssl, buf, static_cast(sz)); @@ -1320,14 +1337,14 @@ TcpClient::~TcpClient() { int ret = UPSCLI_SSL_CAPS_NONE; -#ifdef WITH_SSL +#ifdef WITH_SSL_CXX # ifdef WITH_OPENSSL ret |= UPSCLI_SSL_CAPS_OPENSSL; # endif # ifdef WITH_NSS ret |= UPSCLI_SSL_CAPS_NSS; # endif -#endif /* WITH_SSL */ +#endif /* WITH_SSL_CXX */ return ret; } diff --git a/clients/nutclient.h b/clients/nutclient.h index abb22c866b..20c6d7a2d1 100644 --- a/clients/nutclient.h +++ b/clients/nutclient.h @@ -29,13 +29,15 @@ /* Begin of C++ nutclient library declaration */ #ifdef __cplusplus -#ifdef WITH_OPENSSL +#ifdef WITH_SSL_CXX +# ifdef WITH_OPENSSL # include # include -#elif defined(WITH_NSS) /* not WITH_OPENSSL */ +# elif defined(WITH_NSS) /* not WITH_OPENSSL */ # include # include -#endif /* WITH_OPENSSL | WITH_NSS */ +# endif /* WITH_OPENSSL | WITH_NSS */ +#endif /* WITH_SSL_CXX */ #include #include diff --git a/common/common.c b/common/common.c index aec564876c..35da0c6f99 100644 --- a/common/common.c +++ b/common/common.c @@ -1,7 +1,7 @@ /* common.c - common useful functions Copyright (C) 2000 Russell Kroll - Copyright (C) 2021-2025 Jim Klimov + Copyright (C) 2021-2026 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -47,6 +47,9 @@ # include #endif +static const char * getmyprocname(void); +static const char * getmyprocbasename(void); + #if (defined WITH_LIBSYSTEMD_INHIBITOR) && (defined WITH_LIBSYSTEMD && WITH_LIBSYSTEMD) && (defined WITH_LIBSYSTEMD_INHIBITOR && WITH_LIBSYSTEMD_INHIBITOR) && !(defined(WITHOUT_LIBSYSTEMD) && (WITHOUT_LIBSYSTEMD)) # ifdef HAVE_SYSTEMD_SD_BUS_H # include @@ -760,7 +763,7 @@ void background(void) NUT_WIN32_INCOMPLETE_MAYBE_NOT_APPLICABLE(); #endif /* WIN32 */ - upslogx(LOG_INFO, "Startup successful"); + upslogx(LOG_INFO, "Startup successful: %s", getmyprocbasename()); } /* do this here to keep pwd/grp stuff out of the main files */ @@ -3961,6 +3964,14 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) gettimeofday(&now, NULL); upslog_start = now; + +#ifdef WIN32 + /* Ensure line buffering for sane logs on Windows console + * especially when many threads/daemons write there. */ + setvbuf(stderr, NULL, _IOLBF, BUFSIZ); + /* Also stdout (some messages go there) for good measure: */ + setvbuf(stdout, NULL, _IOLBF, BUFSIZ); +#endif } if (xbit_test(upslog_flags, UPSLOG_STDERR) || xbit_test(upslog_flags, UPSLOG_STDOUT)) { diff --git a/conf/ups.conf.sample b/conf/ups.conf.sample index 4d04554001..bbf508751e 100644 --- a/conf/ups.conf.sample +++ b/conf/ups.conf.sample @@ -131,7 +131,9 @@ maxretry = 3 # user, group: OPTIONAL. Overrides the compiled-in (also global-section, # when used in driver section) default unprivileged user/group # name for NUT device driver. Impacts access rights used for -# the socket file access (group) and communication ports (user). +# the socket file access (group) and communication ports (user +# and its default group; you may want to add that user in the +# operating system to `dialout` group to access serial ports). # # synchronous: OPTIONAL. The driver work by default in asynchronous # mode (like *no*) with fallback to synchronous if sending diff --git a/configure.ac b/configure.ac index b29c5d5566..92ed45dc07 100644 --- a/configure.ac +++ b/configure.ac @@ -7063,24 +7063,30 @@ AC_MSG_RESULT([C: ${nut_with_debuginfo_C}; C++: ${nut_with_debuginfo_CXX}]) dnl Only in the end, do you understand... AC_SUBST(CPPUNIT_NUT_CXXFLAGS) -AS_IF([test "${have_cxx11}" = yes && test x"${TERMUX__PREFIX-}" != x && test -d "${TERMUX__PREFIX-}"], [ - dnl There is a bit of trouble with third-party dependencies whose pkgconf - dnl files or our own code add `-isystem` entries (to avoid warnings about - dnl sloppy code, among other things): this can circumvent (clang++) search - dnl for C++ headers. Simple `-I/path/name` is okay in this regard. - dnl Such a problem for NUT was only seen/reported on Termux so far. - dnl https://github.com/termux/termux-packages/issues/23578 - dnl https://github.com/termux/termux-packages/pull/23579 - AC_MSG_CHECKING(if C++11 support in current compiler needs help in Termux) +dnl We can use OpenSSL or NSS in C++ libnutclient builds, and their CFLAGS on +dnl various platforms can pull in options which can confuse C++ preprocessing, +dnl such as -isystem settings which preclude looking for standard headers (due +dnl to `#include_next` involved in C++ headers ignoring certain directories to +dnl avoid loops). + +dnl There is a bit of trouble with third-party dependencies whose pkgconf +dnl files or our own code add `-isystem` entries (to avoid warnings about +dnl sloppy code, among other things): this can circumvent (clang++) search +dnl for C++ headers. Simple `-I/path/name` is okay in this regard. +dnl Such a problem for NUT was only seen/reported on Termux and MSYS2 so far. + +LIBSSL_CXXFLAGS="${LIBSSL_CFLAGS}" +nut_with_ssl_cxx=no +AS_IF([test "${have_cxx11}" = yes && test -n "${LIBSSL_CXXFLAGS}"], [ + AC_MSG_CHECKING([if C++ support in current compiler needs flag tweaks to build with SSL libs]) my_CFLAGS="${CFLAGS}" my_CPPFLAGS="${CPPFLAGS}" my_CXXFLAGS="${CXXFLAGS}" - - dnl # set the proper header include order - first package includes, then prefix includes - dnl # -isystem${TERMUX_PREFIX}/include/c++/v1 is needed here for on-device building to work correctly - dnl NOTE: two underscores when building, at least on android directly - my_FIXX="-isystem${TERMUX__PREFIX}/include/c++/v1 -isystem${TERMUX__PREFIX}/include" + my_FIXX="" + my_INCLUDEDIR_C="" + my_INCLUDEDIR_CXX="" + my_FIXED=false AC_LANG_PUSH([C++]) @@ -7089,16 +7095,100 @@ AS_IF([test "${have_cxx11}" = yes && test x"${TERMUX__PREFIX-}" != x && test -d CXXFLAGS="${my_CXXFLAGS} ${LIBSSL_CFLAGS}" AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[${CPLUSPLUS_DECL}]], [[${CPLUSPLUS_MAIN}]])], - [AC_MSG_RESULT([LIBSSL flags work out of the box])], - [CXXFLAGS="${my_CXXFLAGS} ${my_FIXX} ${LIBSSL_CFLAGS}" - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[${CPLUSPLUS_DECL}]], [[${CPLUSPLUS_MAIN}]])], - [AC_MSG_RESULT([yes, -isystem preference helps for LIBSSL fkags]) - LIBSSL_CFLAGS="${my_FIXX} ${LIBSSL_CFLAGS}" - ], - [AC_MSG_RESULT([LIBSSL flags just won't work]) - have_cxx11=no + [AC_MSG_RESULT([LIBSSL flags work out of the box]) + my_FIXED=true], + [AC_MSG_RESULT([This system needs help. We have a few platform-dependent ideas ready...]) + AS_IF([test x"${TERMUX__PREFIX-}" != x && test -d "${TERMUX__PREFIX-}/include"], + [AC_MSG_CHECKING([whether tweaks for Termux via TERMUX__PREFIX would help]) + dnl https://github.com/termux/termux-packages/issues/23578 + dnl https://github.com/termux/termux-packages/pull/23579 : + dnl # set the proper header include order - first package includes, then prefix includes + dnl # -isystem${TERMUX_PREFIX}/include/c++/v1 is needed here for on-device building to work correctly + dnl NOTE: two underscores when building, at least on android directly + my_INCLUDEDIR_C="${TERMUX__PREFIX}/include" + ], [ + AS_IF([test x"${MSYS_HOME-}" != x && test -d "${MSYS_HOME-}/../include"], + [AC_MSG_CHECKING([whether tweaks for MSYS2 via MSYS_HOME would help]) + dnl my_INCLUDEDIR_C="${MSYS_HOME}/../include" + my_INCLUDEDIR_C="`printf '%s' \"${MSYS_HOME}/../include\" | tr '\\' '/'`"], + [AS_IF([test x"${MSYSTEM_PREFIX-}" != x && test -d "${MSYSTEM_PREFIX-}/include"], + [AC_MSG_CHECKING([whether tweaks for MSYS2 via MSYSTEM_PREFIX would help]) + my_INCLUDEDIR_C="${MSYSTEM_PREFIX-}/include" + ]) + ]) + ]) + + AS_IF([test -n "$my_INCLUDEDIR_C"], [ + AS_IF([test "${CLANGCC}" = "yes" && test -d "${my_INCLUDEDIR_C}/c++/v1"], + [my_INCLUDEDIR_CXX="${my_INCLUDEDIR_C}/c++/v1"], + [AS_IF([test "${GCC}" = "yes" && test -d "${my_INCLUDEDIR_C}/c++/${CXX_VERSION_NUMBER}"], + [my_INCLUDEDIR_CXX="${my_INCLUDEDIR_C}/c++/${CXX_VERSION_NUMBER}"] + )] + ) + ]) + + AS_IF([test -n "$my_INCLUDEDIR_CXX"], [ + my_FIXX="-isystem${my_INCLUDEDIR_CXX} -isystem${my_INCLUDEDIR_C}" + CXXFLAGS="${my_CXXFLAGS} ${my_FIXX} ${LIBSSL_CFLAGS}" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[${CPLUSPLUS_DECL}]], [[${CPLUSPLUS_MAIN}]])], + [AC_MSG_RESULT([yes, tweaking -isystem preference helps for LIBSSL flags]) + LIBSSL_CXXFLAGS="${my_FIXX} ${LIBSSL_CFLAGS}" + my_FIXED=true], + [AC_MSG_RESULT([no, tweaking -isystem preference did not help]) + AS_IF([test x"${nut_enable_configure_debug}" = xyes], [ + AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) Tried LIBSSL_CXXFLAGS="${my_FIXX-} ${LIBSSL_CFLAGS}"]) + ]) + ]) + ],[AC_MSG_RESULT([no, could not determine directories to tweak -isystem preference])] + ) + + AS_IF([test "$my_FIXED" = false], [ + AS_IF([test x"${nut_enable_configure_debug}" = xyes], [ + AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) Tried my_INCLUDEDIR_C="${my_INCLUDEDIR_C-}"]) + AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) Tried my_INCLUDEDIR_CXX="${my_INCLUDEDIR_CXX-}"]) + AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) CXX_VERSION_NUMBER="${CXX_VERSION_NUMBER}"]) ]) - ]) + + AS_CASE([${LIBSSL_CXXFLAGS}], + [*-isystem*], + [AC_MSG_CHECKING([whether removing -isystem from LIBSSL_CXXFLAGS would help]) + my_FIXX="" + my_SKIP=false + for TOKEN in $LIBSSL_CXXFLAGS ; do + AS_CASE([${TOKEN}], + [-isystem], [my_SKIP=true], + [-isystem*], [my_SKIP=false], + [AS_IF([test "${my_SKIP}" = true], + [my_SKIP=false], + [my_FIXX="$my_FIXX $TOKEN"] + )] + ) + done + unset TOKEN + unset my_SKIP + + CXXFLAGS="${my_CXXFLAGS} ${my_FIXX}" + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[${CPLUSPLUS_DECL}]], [[${CPLUSPLUS_MAIN}]])], + [AC_MSG_RESULT([yes, removing -isystem helps for LIBSSL flags]) + LIBSSL_CXXFLAGS="${my_FIXX}" + my_FIXED=true], + [AC_MSG_RESULT([no, removing -isystem from LIBSSL_CXXFLAGS did not help]) + AS_IF([test x"${nut_enable_configure_debug}" = xyes], [ + AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) Tried LIBSSL_CXXFLAGS="${my_FIXX-}"]) + ]) + ] + ) + ] + ) + ]) + + AS_IF([test "$my_FIXED" = true], + [nut_with_ssl_cxx=yes], + [AC_MSG_RESULT([LIBSSL flags just won't work with C++]) + dnl We disable just SSL support in the C++ library + ]) + ] + ) CFLAGS="${my_CFLAGS}" CPPFLAGS="${my_CPPFLAGS}" @@ -7106,9 +7196,16 @@ AS_IF([test "${have_cxx11}" = yes && test x"${TERMUX__PREFIX-}" != x && test -d AC_LANG_POP([C++]) unset CPLUSPLUS_MAIN unset CPLUSPLUS_DECL - umset my_FIXX + unset my_FIXX + unset my_FIXED + unset my_INCLUDEDIR_C + unset my_INCLUDEDIR_CXX ]) +AC_SUBST(LIBSSL_CXXFLAGS) +NUT_REPORT_FEATURE([enable SSL support in C++ client library], [${nut_with_ssl_cxx}], [${nut_ssl_lib}], + [WITH_SSL_CXX], [Define to enable SSL in libnutclient]) + AC_DEFINE_UNQUOTED([EXEEXT], ["${EXEEXT}"], [Platform-specific extension for binary program files (may be empty where not required)]) AC_SUBST(EXEEXT) diff --git a/docs/FAQ.txt b/docs/FAQ.txt index 9d6baa5e8d..34f131e037 100644 --- a/docs/FAQ.txt +++ b/docs/FAQ.txt @@ -74,6 +74,38 @@ linkman:ups.conf[5]. Just define it before any of your `[sections]`: port = /dev/ttyS0 ---- +Alternately, you can specify a `user=` in the driver section. + +NOTE: The driver program runs with the group account(s) associated with +that user account in the system, and sets its group ID to the primary +group of that account. For serial port access, you may want to add +your 'ups' or 'nut' role account to the 'dialout' group on most Linux +and Unix systems. The `group=` setting in `ups.conf` is for something +else: sharing access to socket files used for communications with +the `upsd` data server, which may run as another different user account. + +*Answer 3* + +Some other programs that you start may steal the port, so the NUT driver +loses a connection (or can never establish one). Revise your device +file system node permissions for corresponding USB or serial ports, +and group membership for NUT driver daemons and other programs in your +system. USB and serial ports might change ownership as the logged-in +console user sessions (physically attached virtual terminals) are +switched on some OS distributions. + +Investigate `udev` or similar framework to assign access permissions +to certain devices on your system, and if you find an offending program +that enumerates all ports thus "stealing" them from NUT and other +consumers, find a way to configure it with a specific port list that +it should use, or to run as a user account that has no access to the +ports you want dedicated to NUT. + +One user report dealt with WINE emulator used for modem programs, so +it was deliberately picking serial port devices and was running as a +member of the `dialout` group. That was an interesting way to shoot +oneself in the foot... + == upsc, upsstats, and the other clients say 'access denied'. The device communication port (serial, USB or network) permissions are fine, so what gives? In this case, "access denied" means the access to linkman:upsd[8], not the diff --git a/docs/Makefile.am b/docs/Makefile.am index ee725f8c58..9a9c735abc 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -811,14 +811,24 @@ qa-guide.pdf: docinfo-since-v2.8.3.xml qa-guide-docinfo.xml @$(GENERATE_PDF) # Used below for spellcheck and for .prep-src-docs -SPELLCHECK_SRC_DEFAULT = $(ALL_TXT_SRC) \ +# List most-frequently edited files first, to hit typos there sooner when +# developing. Some files from sub-directories are spell-checked here and +# not in their own makefiles, because they are included as chapters in +# some larger documents anchored here. +# Note that in builds with enabled parallel fanout mode (even if done +# sequentially, e.g. without NUT_MAKE_SKIP_FANOUT=true explicitly), +# files located in the current directory are processed first anyway, +# as one (possibly parallelized) sub-make call. +SPELLCHECK_SRC_DEFAULT = \ + ../NEWS.adoc ../UPGRADING.adoc \ asciidoc-vars.conf \ - ../ci_build.adoc ../README.adoc ../NEWS.adoc \ - ../INSTALL.nut.adoc ../UPGRADING.adoc \ + ../ci_build.adoc ../README.adoc \ + ../INSTALL.nut.adoc \ ../TODO.adoc ../scripts/ufw/README.adoc \ ../scripts/augeas/README.adoc ../lib/README.adoc \ ../tools/nut-scanner/README.adoc \ - ../AUTHORS ../COPYING ../LICENSE-GPL2 ../LICENSE-GPL3 ../LICENSE-DCO + ../AUTHORS ../COPYING ../LICENSE-GPL2 ../LICENSE-GPL3 ../LICENSE-DCO \ + $(ALL_TXT_SRC) if HAVE_ASPELL # Non-interactively spell check all documentation source files. @@ -987,6 +997,8 @@ spellcheck: @dotMAKE@ $(ASPELL) --help || true; \ (command -v dpkg) && ( dpkg -l | $(GREP) -i aspell ) || true ; \ echo "ASPELL automatic execution line is : ( sed 's,^\(.*\)$$, \1,' < docfile.txt | $(ASPELL) -a $(ASPELL_NUT_TEXMODE_ARGS) $(ASPELL_NUT_COMMON_ARGS) | $(EGREP) -b -v '$(ASPELL_OUT_NOTERRORS)' )" ; \ + echo "SPELLCHECK_SRCDIR: $(SPELLCHECK_SRCDIR)" ; \ + echo "SPELLCHECK_SRC: $(SPELLCHECK_SRC)" ; \ echo "ASPELL proceeding to spellchecking job..."; \ else true; fi +@FAILED="" ; LANG=C; LC_ALL=C; export LANG; export LC_ALL; \ @@ -999,11 +1011,18 @@ spellcheck: @dotMAKE@ || FAILED="$$FAILED $(SPELLCHECK_SRCDIR)/$$docsrc"; \ done ; \ else \ + SPELLCHECK_NOEXT_DOCS_FIRST="`for docsrc in $(SPELLCHECK_SRC); do case \"$${docsrc}\" in ../NEWS.adoc|../UPGRADING.adoc) printf '%s ' \"$${docsrc}\" ;; *) ;; esac ; done`" ; \ SPELLCHECK_AUTO_TGT="`for docsrc in $(SPELLCHECK_SRC); do case \"$${docsrc}\" in */*) ;; *.adoc|*.txt|*.in|*.conf|*.sample) printf '%s ' \"$${docsrc}-spellchecked-auto\" ;; esac ; done`" ; \ - SPELLCHECK_NOEXT_DOCS="`for docsrc in $(SPELLCHECK_SRC); do case \"$${docsrc}\" in */*) printf '%s ' \"$${docsrc}\" ;; *.adoc|*.txt|*.in|*.conf|*.sample) ;; *) printf '%s ' \"$${docsrc}\" ;; esac ; done`" ; \ + SPELLCHECK_NOEXT_DOCS="`for docsrc in $(SPELLCHECK_SRC); do case \"$${docsrc}\" in ../NEWS.adoc|../UPGRADING.adoc) ;; */*) printf '%s ' \"$${docsrc}\" ;; *.adoc|*.txt|*.in|*.conf|*.sample) ;; *) printf '%s ' \"$${docsrc}\" ;; esac ; done`" ; \ if test "$(SPELLCHECK_ENV_DEBUG)" != no ; then \ - echo "ASPELL MAKEFILE DEBUG: from `pwd`: SPELLCHECK_NOEXT_DOCS='$${SPELLCHECK_NOEXT_DOCS}' SPELLCHECK_AUTO_TGT='$${SPELLCHECK_AUTO_TGT}'" ; \ + echo "ASPELL MAKEFILE DEBUG: from `pwd`: SPELLCHECK_NOEXT_DOCS_FIRST='$${SPELLCHECK_NOEXT_DOCS_FIRST}' SPELLCHECK_AUTO_TGT='$${SPELLCHECK_AUTO_TGT}' SPELLCHECK_NOEXT_DOCS='$${SPELLCHECK_NOEXT_DOCS}'" ; \ else true ; fi ; \ + if [ x"$${SPELLCHECK_NOEXT_DOCS_FIRST}" != x ] ; then \ + for docsrc in $${SPELLCHECK_NOEXT_DOCS_FIRST} ; do \ + $(MAKE) $(AM_MAKEFLAGS) -k -s -f "$(abs_top_builddir)/docs/Makefile" SPELLCHECK_SRC="" SPELLCHECK_SRC_ONE="$${docsrc}" SPELLCHECK_BUILDDIR="$(SPELLCHECK_BUILDDIR)" SPELLCHECK_SRCDIR="$(SPELLCHECK_SRCDIR)" VPATH="$(SPELLCHECK_SRCDIR):$(SPELLCHECK_BUILDDIR):$(VPATH)" "$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked" \ + || FAILED="$$FAILED $(SPELLCHECK_SRCDIR)/$$docsrc"; \ + done ; \ + fi ; \ if [ x"$${SPELLCHECK_AUTO_TGT}" != x ] ; then \ $(MAKE) $(AM_MAKEFLAGS) -k -s -f "$(abs_top_builddir)/docs/Makefile" SPELLCHECK_SRC="" SPELLCHECK_BUILDDIR="$(SPELLCHECK_BUILDDIR)" SPELLCHECK_SRCDIR="$(SPELLCHECK_SRCDIR)" VPATH="$(SPELLCHECK_SRCDIR):$(SPELLCHECK_BUILDDIR):$(VPATH)" $${SPELLCHECK_AUTO_TGT} ; \ FAILED="`for docsrc in $(SPELLCHECK_SRC); do if [ -f \"$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked-auto.failed\" ] ; then printf '%s ' \"$(SPELLCHECK_SRCDIR)/$${docsrc}\" ; rm -f \"$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked-auto.failed\" ; fi ; done`" ; \ diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 2b31b160ca..278aaec68e 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -1704,15 +1704,43 @@ LINKMAN_INCLUDE_CONSUMERS = index.txt upsd.txt nutupsdrv.txt nut.txt # TOTHINK: Parse sources for `include::` hits dynamically to build (parts of) this regex? LINKMAN_EXCLUDE_NONDRIVER = '^(nutupsdrv|blazer-common|nut_usb_addvars|networked_hostnames)\.txt$$' -CLEANFILES = linkman-*.txt.tmp* +CLEANFILES = linkman-*.txt.tmp* .all.nut_*-generated.timestamp # Note we can have a pre-built file from the tarball, sort of useful when # e.g. no man page building tools are locally available (we might not be # able to render a new instance though). At least do not let '$@' confuse # us into overwriting its instance in srcdir (if differs from builddir). + +# Shared shell snippet to quickly bail out from (re-)building these files +# in a parallelized build/check sequence: +LINKMAN_CHECK_GENERATED = { \ + if [ -s '$@' ] ; then \ + if [ x"$(NUT_LINKMAN_GENERATED)" = xtrue ] || [ x"$${NUT_LINKMAN_GENERATED}" = xtrue ] ; then \ + if [ x"$(MAINTAINER_GENERATE_HEADER_DEBUG)" = xyes ] ; then \ + echo "=== SKIP (include) $@ (NUT_LINKMAN_GENERATED makevar=$(NUT_LINKMAN_GENERATED) shellvar=$${NUT_LINKMAN_GENERATED})" >&2; \ + fi ; \ + exit 0 ; \ + fi ; \ + if test -n "`find '$@' -newer '.all.nut_linkman-generated.timestamp' 2>/dev/null`" \ + && [ x"$(NUT_LINKMAN_GENERATED)" != xfalse ] \ + && [ x"$${NUT_LINKMAN_GENERATED}" != xfalse ] \ + ; then \ + if [ x"$(MAINTAINER_GENERATE_HEADER_DEBUG)" = xyes ] ; then \ + echo "=== SKIP (include) $@ (.all.nut_linkman-generated.timestamp was made in this larger run and is older than the generated file)" >&2; \ + fi ; \ + exit 0 ; \ + fi ; \ + fi; \ + if [ x"$(MAINTAINER_GENERATE_HEADER_DEBUG)" = xyes ] ; then \ + echo " GENERATE-LINKMAN $@ (NUT_LINKMAN_GENERATED makevar=$(NUT_LINKMAN_GENERATED) shellvar=$${NUT_LINKMAN_GENERATED})"; \ + else \ + echo " GENERATE-LINKMAN $@"; \ + fi ; \ +} + linkman-driver-names.txt: Makefile - @echo " GENERATE-LINKMAN $@" - @(LC_ALL=C; LANG=C; export LC_ALL LANG; \ + @$(LINKMAN_CHECK_GENERATED) ; \ + (LC_ALL=C; LANG=C; export LC_ALL LANG; \ for F in $(LINKMAN_PAGES_DRIVERS) ; do echo "$$F" ; done \ | $(EGREP) -v $(LINKMAN_EXCLUDE_NONDRIVER) \ | sort -n | uniq \ @@ -1733,8 +1761,8 @@ linkman-driver-names.txt: Makefile fi linkman-drivertool-names.txt: Makefile - @echo " GENERATE-LINKMAN $@" - @(LC_ALL=C; LANG=C; export LC_ALL LANG; \ + @$(LINKMAN_CHECK_GENERATED) ; \ + (LC_ALL=C; LANG=C; export LC_ALL LANG; \ for F in $(LINKMAN_PAGES_DRIVERTOOLS) ; do echo "$$F" ; done \ | sort -n | uniq \ | sed 's,^\(.*\)\.txt$$,- linkman:\1[$(MAN_SECTION_CMD_SYS)],' ; \ @@ -2140,6 +2168,9 @@ spellcheck spellcheck-interactive spellcheck-sortdict: @dotMAKE@ PREP_SRC = $(LINKMAN_INCLUDE_GENERATED) $(SRC_ALL_PAGES) asciidoc.conf ASCIIDOC_LINKMANEXT_SECTION_REWRITE = @ASCIIDOC_LINKMANEXT_SECTION_REWRITE@ +# Allow this to be called before top-level parallel or recursive build fanout: +prep-linkman-generated: $(LINKMAN_INCLUDE_GENERATED) Makefile + # NOTE: Some "make" implementations prefix a relative or absent path to # the filenames in PREP_SRC, others (e.g. Sun make) prepend the absolute # path to locate the sources, so we end up with bogus trees under docs/. diff --git a/docs/nut.dict b/docs/nut.dict index e830687f99..19e02ba4e0 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3705 utf-8 +personal_ws-1.1 en 3706 utf-8 AAC AAS ABI @@ -2409,6 +2409,7 @@ isbmex ish iso isolator +isystem iter ivp ivtscd diff --git a/drivers/upsdrvctl.c b/drivers/upsdrvctl.c index 8b1ee9a9db..dfa95f2b75 100644 --- a/drivers/upsdrvctl.c +++ b/drivers/upsdrvctl.c @@ -878,11 +878,26 @@ static void forkexec(char *const argv[], const ups_t *ups) /* Use the local maxstartdelay, if available */ if (ups->maxstartdelay != -1) { - if (ups->maxstartdelay >= 0) + if (ups->maxstartdelay >= 0) { + upsdebugx(2, "%s[POSIX]: will wait for %u seconds " + "to check that driver survived this long " + "(per device configuration section)", + __func__, (unsigned int)ups->maxstartdelay); alarm((unsigned int)ups->maxstartdelay); + } } else { /* Otherwise, use the global (or default) value */ - if (maxstartdelay >= 0) + if (maxstartdelay >= 0) { + upsdebugx(2, "%s[POSIX]: will wait for %u seconds " + "to check that driver survived this long " + "(per global configuration section)", + __func__, (unsigned int)maxstartdelay); alarm((unsigned int)maxstartdelay); + } else { + upsdebugx(2, "%s[POSIX]: will NOT wait " + "to check that driver survived this long " + "(not required by global nor by device " + "configuration sections)", __func__); + } } waitret = waitpid(pid, &wstat, 0); @@ -946,7 +961,7 @@ static void forkexec(char *const argv[], const ups_t *ups) char commandline[LARGEBUF]; STARTUPINFO StartupInfo; PROCESS_INFORMATION ProcessInformation; - int i = 1; + int i = 1, waited = 0; memset(&StartupInfo, 0, sizeof(STARTUPINFO)); @@ -997,8 +1012,38 @@ static void forkexec(char *const argv[], const ups_t *ups) * Unlike under Linux, Windows spawn drivers directly. If the driver is alive, all is OK. * An optimization can probably be implemented to prevent waiting so much time when all is OK. */ - res = WaitForSingleObject(ProcessInformation.hProcess, - (ups->maxstartdelay!=-1?ups->maxstartdelay:maxstartdelay)*1000); + + /* Use the local maxstartdelay, if available */ + if (ups->maxstartdelay != -1) { + if (ups->maxstartdelay >= 0) { + upsdebugx(2, "%s[WIN32]: will wait for %u seconds " + "to check that driver survived this long " + "(per device configuration section)", + __func__, (unsigned int)ups->maxstartdelay); + res = WaitForSingleObject(ProcessInformation.hProcess, + ((unsigned int)ups->maxstartdelay) * 1000); + waited = 1; + } + } else { /* Otherwise, use the global (or default) value */ + if (maxstartdelay >= 0) { + upsdebugx(2, "%s[WIN32]: will wait for %u seconds " + "to check that driver survived this long " + "(per global configuration section)", + __func__, (unsigned int)maxstartdelay); + res = WaitForSingleObject(ProcessInformation.hProcess, + ((unsigned int)maxstartdelay) * 1000); + waited = 1; + } + } + + if (!waited) { + upsdebugx(2, "%s[WIN32]: will NOT wait " + "to check that driver survived this long " + "(not required by global nor by device " + "configuration sections)", __func__); + res = WaitForSingleObject(ProcessInformation.hProcess, + 0); + } if (res != WAIT_TIMEOUT) { GetExitCodeProcess( ProcessInformation.hProcess, &exit_code ); @@ -1370,12 +1415,13 @@ static void start_driver(const ups_t *ups) int cur_exec_error = exec_error; int cur_exec_timeout = exec_timeout; - upsdebugx(2, "%i remaining attempts", drv_maxretry); + upsdebugx(2, "%s: %i remaining attempts", __func__, drv_maxretry); debugcmdline(2, "exec: ", argv); drv_maxretry--; if (!testmode) { forkexec(argv, ups); + upsdebugx(3, "%s: forkexec() finished", __func__); } /* driver command succeeded */ @@ -1387,8 +1433,11 @@ static void start_driver(const ups_t *ups) else { /* otherwise, retry if still needed */ if (drv_maxretry > 0) - if (drv_retrydelay >= 0) + if (drv_retrydelay >= 0) { + upsdebugx(3, "%s: retrying after %u seconds", + __func__, (unsigned int)drv_retrydelay); sleep ((unsigned int)drv_retrydelay); + } } } } diff --git a/m4/nut_compiler_family.m4 b/m4/nut_compiler_family.m4 index f1719c8ae3..692907b193 100644 --- a/m4/nut_compiler_family.m4 +++ b/m4/nut_compiler_family.m4 @@ -89,6 +89,12 @@ if test -z "${nut_compiler_family_seen}"; then AS_IF([test "x$CC_VERSION" = x], [CC_VERSION="`echo \"${CC_VERSION_FULL}\" | head -1`"]) AS_IF([test "x$CXX_VERSION" = x], [CXX_VERSION="`echo \"${CXX_VERSION_FULL}\" | head -1`"]) AS_IF([test "x$CPP_VERSION" = x], [CPP_VERSION="`echo \"${CPP_VERSION_FULL}\" | head -1`"]) + + dnl Starting with number like "6.0.0" or "7.5.0-il-0" is fair game, + dnl but a "gcc-4.4.4-il-4" (starting with "gcc") is not + CC_VERSION_NUMBER="`echo \"${CC_VERSION}\" | sed -e 's,^.* \(@<:@0-9@:>@@<:@0-9@:>@*\.@<:@0-9@:>@@<:@^ ),@:>@*\).*$,\1,' -e 's, .*$,,' | ${EGREP} '^@<:@0-9@:>@' | head -1`" + CXX_VERSION_NUMBER="`echo \"${CXX_VERSION}\" | sed -e 's,^.* \(@<:@0-9@:>@@<:@0-9@:>@*\.@<:@0-9@:>@@<:@^ ),@:>@*\).*$,\1,' -e 's, .*$,,' | ${EGREP} '^@<:@0-9@:>@' | head -1`" + CPP_VERSION_NUMBER="`echo \"${CPP_VERSION}\" | sed -e 's,^.* \(@<:@0-9@:>@@<:@0-9@:>@*\.@<:@0-9@:>@@<:@^ ),@:>@*\).*$,\1,' -e 's, .*$,,' | ${EGREP} '^@<:@0-9@:>@' | head -1`" fi ]) diff --git a/server/upsd.c b/server/upsd.c index b843f1b5e1..14b4ffec47 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -1713,34 +1713,38 @@ static void mainloop(void) */ size_t last_chunk = nfds % sysmaxconn, chunk, chunks = nfds / sysmaxconn + (last_chunk ? 1 : 0); - int poll_TO = 2000 / chunks, tmpret; + int poll_TO, poll_TO_chunk = 2000 / chunks, tmpret; - if (poll_TO < 10) - poll_TO = 10; - - upsdebugx(4, "%s: chunked filedescriptor polling via %" PRIuSIZE - " chunks, last one sized %" PRIuSIZE - ", with timeout of %d msec per chunk", - __func__, chunks, last_chunk, poll_TO); + if (poll_TO_chunk < 10) + poll_TO_chunk = 10; ret = 0; - for (chunk = 0; chunk < chunks; chunk++) { - upsdebugx(5, - "%s: chunked filedescriptor polling #%" PRIuSIZE - " of %" PRIuSIZE " chunks, with %d hits so far", - __func__, chunk, chunks, ret); - tmpret = poll(&fds[chunk * sysmaxconn], - (last_chunk && chunk == chunks - 1 ? last_chunk : sysmaxconn), - poll_TO); - if (tmpret < 0) { - upsdebug_with_errno(2, - "%s: failed during chunked polling, handled %" PRIuSIZE - " of %" PRIuSIZE " chunks so far, with %d hits", + /* First run a quick check if anyone is already waiting + * (especially in non-first chunks), then a loop with waits */ + for (poll_TO = 0; poll_TO <= poll_TO_chunk && ret == 0; poll_TO += poll_TO_chunk) { + upsdebugx(4, "%s: chunked filedescriptor polling via %" PRIuSIZE + " chunks, last one sized %" PRIuSIZE + ", with timeout of %d msec per chunk", + __func__, chunks, last_chunk, poll_TO); + + for (chunk = 0; chunk < chunks; chunk++) { + upsdebugx(5, + "%s: chunked filedescriptor polling #%" PRIuSIZE + " of %" PRIuSIZE " chunks, with %d hits so far", __func__, chunk, chunks, ret); - ret = tmpret; - break; + tmpret = poll(&fds[chunk * sysmaxconn], + (last_chunk && chunk == chunks - 1 ? last_chunk : sysmaxconn), + poll_TO); + if (tmpret < 0) { + upsdebug_with_errno(2, + "%s: failed during chunked polling, handled %" PRIuSIZE + " of %" PRIuSIZE " chunks so far, with %d hits", + __func__, chunk, chunks, ret); + ret = tmpret; + break; + } + ret += tmpret; } - ret += tmpret; } } @@ -2070,29 +2074,36 @@ static void mainloop(void) */ size_t last_chunk = nfds % sysmaxconn, chunks = nfds / sysmaxconn + (last_chunk ? 1 : 0); - DWORD poll_TO = 2000 / chunks, tmpret; + DWORD poll_TO, poll_TO_chunk = 2000 / chunks, tmpret; - if (poll_TO < 10) - poll_TO = 10; - - upsdebugx(4, "%s: chunked filedescriptor polling via %" PRIuSIZE - " chunks, last one sized %" PRIuSIZE - ", with timeout of %" PRIi64 " msec per chunk", - __func__, chunks, last_chunk, poll_TO); + if (poll_TO_chunk < 10) + poll_TO_chunk = 10; ret = WAIT_TIMEOUT; - for (chunk = 0; chunk < chunks; chunk++) { - upsdebugx(5, - "%s: chunked filedescriptor polling #%" PRIuSIZE - " of %" PRIuSIZE " chunks, with %" PRIu64 " hits so far", - __func__, chunk, chunks, ret); - tmpret = WaitForMultipleObjects( - (last_chunk && chunk == chunks - 1 ? last_chunk : sysmaxconn), - &fds[chunk * sysmaxconn], - FALSE, poll_TO); - if (tmpret != WAIT_TIMEOUT) { - ret = tmpret; - break; + /* First run a quick check if anyone is already waiting + * (especially in non-first chunks), then a loop with waits */ + for (poll_TO = 0; poll_TO <= poll_TO_chunk && ret == WAIT_TIMEOUT; poll_TO += poll_TO_chunk) { + upsdebugx(4, "%s: chunked filedescriptor polling via %" PRIuSIZE + " chunks, last one sized %" PRIuSIZE + ", with timeout of %" PRIi64 " msec per chunk", + __func__, chunks, last_chunk, poll_TO); + + for (chunk = 0; chunk < chunks; chunk++) { + upsdebugx(5, + "%s: chunked filedescriptor polling #%" PRIuSIZE + " of %" PRIuSIZE " chunks", + __func__, chunk, chunks); + tmpret = WaitForMultipleObjects( + (last_chunk && chunk == chunks - 1 ? last_chunk : sysmaxconn), + &fds[chunk * sysmaxconn], + FALSE, poll_TO); + if (tmpret != WAIT_TIMEOUT) { + /* NOTE: Actual offset depends on this value + * being in ABANDONED or OBJECT range, further + * shifted for array lookup by N chunks */ + ret = tmpret; + break; + } } } } @@ -2122,18 +2133,19 @@ static void mainloop(void) #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE # pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" #endif + /* https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects */ if (ret >= WAIT_ABANDONED_0 && ret <= WAIT_ABANDONED_0 + (nfds < sysmaxconn ? nfds : sysmaxconn) - 1) { /* One abandoned mutex object that satisfied the wait? */ - ret = ret - WAIT_ABANDONED_0; - upsdebugx(5, "%s: got abandoned FD array item: %" PRIu64, __func__, nfds, ret); + ret = ret - WAIT_ABANDONED_0 + chunk * sysmaxconn; + upsdebugx(5, "%s: got abandoned FD array item: %" PRIu64 " of %" PRIu64, __func__, ret, nfds - 1); /* FIXME: Should this be handled somehow? Cleanup? Abort?.. */ } else - if (ret >= WAIT_OBJECT_0 && ret <= WAIT_OBJECT_0 + nfds - 1) { + if (ret >= WAIT_OBJECT_0 && ret <= WAIT_OBJECT_0 + (nfds < sysmaxconn ? nfds : sysmaxconn) - 1) { /* Which one handle was triggered this time? */ /* Note: WAIT_OBJECT_0 may be currently defined as 0, * but docs insist on checking and shifting the range */ ret = ret - WAIT_OBJECT_0 + chunk * sysmaxconn; - upsdebugx(5, "%s: got event on FD array item: %" PRIu64, __func__, nfds, ret); + upsdebugx(5, "%s: got event on FD array item: %" PRIu64 " of %" PRIu64, __func__, ret, nfds - 1); } #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) ) # pragma GCC diagnostic pop diff --git a/tests/Makefile.am b/tests/Makefile.am index 078321ff3c..2bdab40017 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -23,9 +23,9 @@ CLEANFILES = *.trs *.log AM_CFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -I$(top_srcdir)/drivers AM_CXXFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include -if WITH_SSL - AM_CXXFLAGS += $(LIBSSL_CFLAGS) -endif WITH_SSL +if WITH_SSL_CXX + AM_CXXFLAGS += $(LIBSSL_CXXFLAGS) +endif WITH_SSL_CXX # Compiler flags for cppunit tests CPPUNIT_NUT_CXXFLAGS = @CPPUNIT_NUT_CXXFLAGS@ @@ -217,20 +217,20 @@ if WITH_LIBNUTCONF cppunittest_SOURCES += $(CPPUNITTESTSRC_NUTCONF) cppunittest_LDADD += $(top_builddir)/common/libnutconf.la endif WITH_LIBNUTCONF -if WITH_SSL +if WITH_SSL_CXX # Not really used here, but transient dependency from libnutclient # At least on NetBSD consumers should know directly... cppunittest_LDADD += $(LIBSSL_LIBS) -endif WITH_SSL +endif WITH_SSL_CXX cppnit_CXXFLAGS = $(AM_CXXFLAGS) $(CPPUNIT_CFLAGS) $(CPPUNIT_CXXFLAGS) $(CPPUNIT_NUT_CXXFLAGS) $(CXXFLAGS) cppnit_LDFLAGS = $(CPPUNIT_LDFLAGS) $(CPPUNIT_LIBS) cppnit_LDADD = $(top_builddir)/clients/libnutclientstub.la cppnit_LDADD += $(top_builddir)/clients/libnutclient.la cppnit_SOURCES = $(CPPCLIENTTESTSRC) $(CPPUNITTESTERSRC) -if WITH_SSL +if WITH_SSL_CXX cppnit_LDADD += $(LIBSSL_LIBS) -endif WITH_SSL +endif WITH_SSL_CXX else !HAVE_CPPUNIT diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 44f64ea9bb..68d6e9a422 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -111,6 +111,10 @@ if [ x"${NUT_FOREGROUND_WITH_PID-}" = xtrue ] ; then ARG_FG="-FF" ; fi TABCHAR="`printf '\t'`" +# Special case to launch a lot of drivers and stress-test the select() loops etc. +[ -n "${DUMMY_UPS_SWARM_COUNT}" ] && [ "${DUMMY_UPS_SWARM_COUNT}" -gt 0 ] || DUMMY_UPS_SWARM_COUNT=0 +[ -n "${UPSLOG_SWARM_COUNT}" ] && [ "${UPSLOG_SWARM_COUNT}" -gt 0 ] || UPSLOG_SWARM_COUNT=0 + log_separator() { echo "" >&2 echo "================================" >&2 @@ -447,6 +451,8 @@ PID_UPSSCHED="" PID_DUMMYUPS="" PID_DUMMYUPS1="" PID_DUMMYUPS2="" +PIDS_DUMMYUPS_SWARM="" +PIDS_UPSLOG_SWARM="" WITH_SSL_CLIENT="`upsmon -Dh 2>&1 | grep 'Using NUT libupsclient library'`" || WITH_SSL_CLIENT="none" # NOTE: Currently OpenSSL/NSS builds and codepaths are exclusive of each other! @@ -779,10 +785,10 @@ stop_daemons() { PID_UPSSCHED_NOW="`head -1 \"$NUT_PIDPATH/upssched.pid\"`" fi - if [ -n "$PID_UPSD$PID_UPSMON$PID_DUMMYUPS$PID_DUMMYUPS1$PID_DUMMYUPS2$PID_UPSSCHED$PID_UPSSCHED_NOW" ] ; then + if [ -n "$PID_UPSD$PID_UPSMON$PID_DUMMYUPS$PID_DUMMYUPS1$PID_DUMMYUPS2$PIDS_DUMMYUPS_SWARM$PIDS_UPSLOG_SWARM$PID_UPSSCHED$PID_UPSSCHED_NOW" ] ; then log_info "Stopping test daemons" - kill -15 $PID_UPSD $PID_UPSMON $PID_DUMMYUPS $PID_DUMMYUPS1 $PID_DUMMYUPS2 $PID_UPSSCHED $PID_UPSSCHED_NOW 2>/dev/null || return 0 - wait $PID_UPSD $PID_UPSMON $PID_DUMMYUPS $PID_DUMMYUPS1 $PID_DUMMYUPS2 $PID_UPSSCHED $PID_UPSSCHED_NOW || true + kill -15 $PID_UPSD $PID_UPSMON $PID_DUMMYUPS $PID_DUMMYUPS1 $PID_DUMMYUPS2 $PIDS_DUMMYUPS_SWARM $PIDS_UPSLOG_SWARM $PID_UPSSCHED $PID_UPSSCHED_NOW 2>/dev/null || return 0 + wait $PID_UPSD $PID_UPSMON $PID_DUMMYUPS $PID_DUMMYUPS1 $PID_DUMMYUPS2 $PIDS_DUMMYUPS_SWARM $PIDS_UPSLOG_SWARM $PID_UPSSCHED $PID_UPSSCHED_NOW || true fi PID_UPSD="" @@ -791,6 +797,8 @@ stop_daemons() { PID_DUMMYUPS="" PID_DUMMYUPS1="" PID_DUMMYUPS2="" + PIDS_DUMMYUPS_SWARM="" + PIDS_UPSLOG_SWARM="" unset PID_UPSSCHED_NOW } @@ -1171,6 +1179,11 @@ EOF if [ -n "${NUT_DEBUG_MIN-}" ] ; then echo "DEBUG_MIN ${NUT_DEBUG_MIN}" >> "$NUT_CONFPATH/upsd.conf" || exit fi + + if [ "$DUMMY_UPS_SWARM_COUNT" -gt 5 ] || [ "$UPSLOG_SWARM_COUNT" -gt 5 ] ; then + # Enable select-group looping (especially on Windows with sysmaxconn=64): + echo "MAXCONN `expr $DUMMY_UPS_SWARM_COUNT + $UPSLOG_SWARM_COUNT + 30`" >> "$NUT_CONFPATH/upsd.conf" || exit + fi } generatecfg_upsd_nodev() { @@ -1470,7 +1483,9 @@ EOF generatecfg_ups_trivial() { # Populate the configs for the run - ( echo 'maxretry = 3' > "$NUT_CONFPATH/ups.conf" || exit + ( # Hints primarily for upsdrvctl: + echo 'maxretry = 3' > "$NUT_CONFPATH/ups.conf" || exit + echo 'maxstartdelay = 1' >> "$NUT_CONFPATH/ups.conf" || exit if [ x"${ABS_TOP_BUILDDIR}" != x ]; then # NOTE: Windows backslashes are pre-escaped in the configure-generated value case "${ABS_TOP_BUILDDIR}" in @@ -1556,8 +1571,38 @@ EOF mv -f "$F.bak" "$F" ${EGREP} '^ups.status:' "$F" >/dev/null || { echo "ups.status: OL BOOST" >> "$F"; } done - fi + if [ "$DUMMY_UPS_SWARM_COUNT" -gt 0 ] ; then + log_info "Adding a swarm of ${DUMMY_UPS_SWARM_COUNT} drivers" + for N in `seq 1 $DUMMY_UPS_SWARM_COUNT` ; do + case "`expr $N % 3`" in + 0) cat << EOF +[UPSwarm$N] + driver = dummy-ups + desc = "Example event sequence" + port = evolution500.seq +EOF + ;; + 1) cat << EOF +[UPSwarm$N] + driver = dummy-ups + desc = "Example ePDU data dump" + port = epdu-managed.dev + mode = dummy-once +EOF + ;; + 2) cat << EOF +[UPSwarm$N] + driver = dummy-ups + desc = "Example ePDU data dump (loop)" + port = epdu-managed.dev + mode = dummy-loop +EOF + ;; + esac + done >> "$NUT_CONFPATH/ups.conf" + fi + fi } ##################################################### @@ -1567,6 +1612,21 @@ isPidAlive() { [ -d "/proc/$1" ] || kill -0 "$1" 2>/dev/null } +arePidsAlive() { + _DEAD="" + for _PID in "$@" ; do + isPidAlive "$_PID" || _DEAD="${_DEAD} $_PID" + done + unset _PID + if [ -n "${_DEAD}" ]; then + log_error "[arePidsAlive] Some are dead:${_DEAD}" + unset _DEAD + return 1 + fi + unset _DEAD + return 0 +} + FAILED=0 FAILED_FUNCS="" PASSED=0 @@ -1833,7 +1893,7 @@ sandbox_start_upsd() { sandbox_start_drivers() { if isPidAlive "$PID_DUMMYUPS" \ - && { [ x"${TOP_SRCDIR}" != x ] && isPidAlive "$PID_DUMMYUPS1" && isPidAlive "$PID_DUMMYUPS2" \ + && { [ x"${TOP_SRCDIR}" != x ] && arePidsAlive "$PID_DUMMYUPS1" "$PID_DUMMYUPS2" $PIDS_DUMMYUPS_SWARM \ || [ x"${TOP_SRCDIR}" = x ] ; } \ ; then # All drivers expected for this environment are already running @@ -1860,6 +1920,15 @@ sandbox_start_drivers() { 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" + + if [ "$DUMMY_UPS_SWARM_COUNT" -gt 0 ] ; then + log_info "Starting a swarm of ${DUMMY_UPS_SWARM_COUNT} drivers" + for N in `seq 1 $DUMMY_UPS_SWARM_COUNT` ; do + execcmd dummy-ups -a UPSwarm$N ${ARG_USER} ${ARG_FG} & + PIDS_DUMMYUPS_SWARM="$PIDS_DUMMYUPS_SWARM $!" + log_debug "Tried to start dummy-ups driver for 'UPSwarm$N' as PID $!" + done + fi fi NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" @@ -1870,7 +1939,7 @@ sandbox_start_drivers() { fi if isPidAlive "$PID_DUMMYUPS" \ - && { [ x"${TOP_SRCDIR}" != x ] && isPidAlive "$PID_DUMMYUPS1" && isPidAlive "$PID_DUMMYUPS2" \ + && { [ x"${TOP_SRCDIR}" != x ] && arePidsAlive "$PID_DUMMYUPS1" "$PID_DUMMYUPS2" $PIDS_DUMMYUPS_SWARM \ || [ x"${TOP_SRCDIR}" = x ] ; } \ ; then # All drivers expected for this environment are already running @@ -1892,6 +1961,12 @@ testcase_sandbox_start_upsd_alone() { EXPECTED_UPSLIST="$EXPECTED_UPSLIST UPS1 UPS2" + if [ "$DUMMY_UPS_SWARM_COUNT" -gt 0 ] ; then + for N in `seq 1 $DUMMY_UPS_SWARM_COUNT` ; do + EXPECTED_UPSLIST="$EXPECTED_UPSLIST +UPSwarm$N" + done + fi # For windows runners (strip CR if any): EXPECTED_UPSLIST="`echo \"$EXPECTED_UPSLIST\" | tr -d '\r'`" fi @@ -1902,6 +1977,18 @@ UPS2" EXPECTED_UPSLIST_JSON="${EXPECTED_UPSLIST_JSON},"' "UPS1", "UPS2"' + if [ "$DUMMY_UPS_SWARM_COUNT" -gt 0 ] ; then + EXPECTED_UPSLIST_JSON="$EXPECTED_UPSLIST_JSON," + if [ "$DUMMY_UPS_SWARM_COUNT" -gt 1 ] ; then + DUMMY_UPS_SWARM_COUNT_1="`expr $DUMMY_UPS_SWARM_COUNT - 1`" + for N in `seq 1 $DUMMY_UPS_SWARM_COUNT_1` ; do + EXPECTED_UPSLIST_JSON="$EXPECTED_UPSLIST_JSON + \"UPSwarm$N\"," + done + fi + EXPECTED_UPSLIST_JSON="$EXPECTED_UPSLIST_JSON + \"UPSwarm${DUMMY_UPS_SWARM_COUNT}\"" + fi fi EXPECTED_UPSLIST_JSON="${EXPECTED_UPSLIST_JSON}"' ]' @@ -2226,6 +2313,16 @@ testcase_sandbox_upsc_query_timer() { PID_UPSLOG="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" + # No timeout, no kill - keep them running if requested (trap exit): + if [ "$UPSLOG_SWARM_COUNT" -gt 0 ] ; then + log_info "Starting a swarm of ${UPSLOG_SWARM_COUNT} clients" + for N in `seq 1 $UPSLOG_SWARM_COUNT` ; do + execcmd upslog -F -i 1 -N -m "*@localhost:${NUT_PORT},${NUT_STATEPATH}/upslog-dummy-$N.log" & + PIDS_UPSLOG_SWARM="$PIDS_UPSLOG_SWARM $!" + log_debug "Tried to start upslog as PID $!" + done + fi + # TODO: Any need to convert to runcmd()? 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" @@ -2652,7 +2749,7 @@ testcase_sandbox_nutscanner_list() { if [ x"${TOP_SRCDIR}" = x ]; then PORTS_WANT=1 else - PORTS_WANT=3 + PORTS_WANT="`expr 3 + $DUMMY_UPS_SWARM_COUNT`" fi PORTS_SEEN="`echo \"$CMDOUT\" | ${EGREP} -c 'port *='`" diff --git a/tests/cpputest-client.cpp b/tests/cpputest-client.cpp index c22859da8f..3fef89413d 100644 --- a/tests/cpputest-client.cpp +++ b/tests/cpputest-client.cpp @@ -181,7 +181,11 @@ void NutActiveClientTest::setUp() s = std::getenv("NUT_FORCESSL"); if (s && (std::string(s) == "1" || std::string(s) == "true" || std::string(s) == "yes")) { +#ifdef WITH_SSL_CXX env_NUT_FORCESSL = true; +#else + std::cerr << "[D] Not built with WITH_SSL_CXX, ignoring NUT_FORCESSL=true" << std::endl; +#endif } s = std::getenv("NUT_CERTVERIFY"); @@ -245,6 +249,9 @@ void NutActiveClientTest::setupClientSSL(nut::TcpClient &c) || !env_NUT_CERTFILE.empty() || !env_NUT_KEYFILE.empty() ) { +#ifndef WITH_SSL_CXX + try { +#endif c.setSSLConfig(SSLConfig_OpenSSL( env_NUT_FORCESSL, env_NUT_CERTVERIFY, @@ -254,6 +261,13 @@ void NutActiveClientTest::setupClientSSL(nut::TcpClient &c) env_NUT_KEYFILE, env_NUT_KEYPASS )); +#ifndef WITH_SSL_CXX + } + catch(nut::SSLException& ex) + { + std::cerr << "[D] Not built with WITH_SSL_CXX and reasonably failed to setSSLConfig(OpenSSL): " << ex.what() << std::endl; + } +#endif } if (env_NUT_CERTVERIFY != -1 @@ -263,6 +277,9 @@ void NutActiveClientTest::setupClientSSL(nut::TcpClient &c) || !env_NUT_CERTHOST_NAME.empty() || !env_NUT_CERTIDENT_NAME.empty() ) { +#ifndef WITH_SSL_CXX + try { +#endif c.setSSLConfig(SSLConfig_NSS( env_NUT_FORCESSL, env_NUT_CERTVERIFY, @@ -272,6 +289,13 @@ void NutActiveClientTest::setupClientSSL(nut::TcpClient &c) env_NUT_CERTHOST_NAME, env_NUT_CERTIDENT_NAME )); +#ifndef WITH_SSL_CXX + } + catch(nut::SSLException& ex) + { + std::cerr << "[D] Not built with WITH_SSL_CXX and reasonably failed to setSSLConfig(NSS): " << ex.what() << std::endl; + } +#endif } std::cerr << "[D] C++ NUT Client lib enabled SSL options:"