diff --git a/NEWS.adoc b/NEWS.adoc index 7bec29669c..17bbb19206 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -309,6 +309,11 @@ https://github.com/networkupstools/nut/milestone/11 as "OTHER" notification type (whenever the set of such tokens appears or changes) or "NOTOTHER" when they disappear. [#415] + - upslog: + * Added support for limiting the loop count. Using in NIT (NUT Integration + Test) suite for double profit (checking the tool and fallback in NIT). + * Added systemd and SMF service integration. [#1803] + - More systemd integration: * Introduced a `nut-sleep.service` unit which stops `nut.target` when a system sleep was requested, and starts it when the sleep is finished. diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 47c4a554b3..dfcfaca94a 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -70,6 +70,10 @@ Changes from 2.8.2 to 2.8.3 For fallback with older systemd, a `nut-sleep.service` is provided now. [#1070, #2596, #2597] +- Added systemd and SMF service integration for `upslog` as a `nut-logger` + service (disabled by default, needs a `upslog.conf` file to deliver the + `UPSLOG_ARGS=...` setting for actual monitoring and logging). [#1803] + - Handling of per-UPS `ALARM` state was introduced to `upsmon`, allowing it to optionally treat it as a factor in deciding that the device is in a "critical" state (polled more often, assumed dead if communications are diff --git a/clients/Makefile.am b/clients/Makefile.am index 5af70c54a3..1b59ce990f 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -79,6 +79,7 @@ upsc_SOURCES = upsc.c upsclient.h upscmd_SOURCES = upscmd.c upsclient.h upsrw_SOURCES = upsrw.c upsclient.h upslog_SOURCES = upslog.c upsclient.h upslog.h +upslog_LDADD = $(LDADD_FULL) upsmon_SOURCES = upsmon.c upsmon.h upsclient.h upsmon_LDADD = $(LDADD_FULL) if HAVE_WINDOWS_SOCKETS diff --git a/clients/upslog.c b/clients/upslog.c index 4b88cd9421..ac53e8d5ea 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -39,12 +39,14 @@ #include "timehead.h" #include "nut_stdint.h" #include "upslog.h" +#include "str.h" #ifdef WIN32 #include "wincompat.h" #endif static int reopen_flag = 0, exit_flag = 0; + static size_t max_loops = 0; static char *upsname; static UPSCONN_t *ups; @@ -78,7 +80,8 @@ static void reopen_log(void) { for (monhost_ups_current = monhost_ups_anchor; monhost_ups_current != NULL; - monhost_ups_current = monhost_ups_current->next) { + monhost_ups_current = monhost_ups_current->next + ) { if (monhost_ups_current->logfile == stdout) { upslogx(LOG_INFO, "logging to stdout"); return; @@ -153,6 +156,7 @@ static void help(const char *prog) printf(" -f - Log format. See below for details.\n"); printf(" - Use -f \"\" so your shell doesn't break it up.\n"); printf(" -i - Time between updates, in seconds\n"); + printf(" -d - Exit after specified amount of updates\n"); printf(" -l - Log file name, or - for stdout (foreground by default)\n"); printf(" -F - stay foregrounded even if logging into a file\n"); printf(" -B - stay backgrounded even if logging to stdout\n"); @@ -434,7 +438,7 @@ static void run_flist(struct monhost_ups *monhost_ups_print) int main(int argc, char **argv) { int interval = 30, i, foreground = -1; - size_t monhost_len = 0; + size_t monhost_len = 0, loop_count = 0; const char *prog = xbasename(argv[0]); time_t now, nextpoll = 0; const char *user = NULL; @@ -446,7 +450,7 @@ int main(int argc, char **argv) print_banner_once(prog, 0); - while ((i = getopt(argc, argv, "+hs:l:i:f:u:Vp:FBm:")) != -1) { + while ((i = getopt(argc, argv, "+hs:l:i:d:f:u:Vp:FBm:")) != -1) { switch(i) { case 'h': help(prog); @@ -501,6 +505,31 @@ int main(int argc, char **argv) interval = atoi(optarg); break; + case 'd': + { /* scoping */ + unsigned long ul = 0; + if (str_to_ulong(optarg, &ul, 10)) { +#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 push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + if (ul < SIZE_MAX) + max_loops = (size_t)ul; + else + upslogx(LOG_ERR, "Invalid max loops"); +#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 +#endif + } else + upslogx(LOG_ERR, "Invalid max loops"); + } + break; + case 'f': logformat = optarg; break; @@ -594,7 +623,8 @@ int main(int argc, char **argv) for (monhost_ups_current = monhost_ups_anchor; monhost_ups_current != NULL; - monhost_ups_current = monhost_ups_current->next) { + monhost_ups_current = monhost_ups_current->next + ) { printf("logging status of %s to %s (%is intervals)\n", monhost_ups_current->monhost, monhost_ups_current->logfn, interval); if (upscli_splitname(monhost_ups_current->monhost, &(monhost_ups_current->upsname), &(monhost_ups_current->hostname), &(monhost_ups_current->port)) != 0) { @@ -668,7 +698,8 @@ int main(int argc, char **argv) for (monhost_ups_current = monhost_ups_anchor; monhost_ups_current != NULL; - monhost_ups_current = monhost_ups_current->next) { + monhost_ups_current = monhost_ups_current->next + ) { ups = monhost_ups_current->ups; /* XXX Not ideal */ upsname = monhost_ups_current->upsname; /* XXX Not ideal */ /* reconnect if necessary */ @@ -683,6 +714,14 @@ int main(int argc, char **argv) upscli_disconnect(ups); } } + + if (max_loops > 0) { + loop_count++; + if (loop_count >= max_loops || loop_count > (SIZE_MAX - 1)) { + upslogx(LOG_INFO, "%" PRIuSIZE " loops have elapsed", max_loops); + exit_flag = 1; + } + } } upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); @@ -690,7 +729,8 @@ int main(int argc, char **argv) for (monhost_ups_current = monhost_ups_anchor; monhost_ups_current != NULL; - monhost_ups_current = monhost_ups_current->next) { + monhost_ups_current = monhost_ups_current->next + ) { if (monhost_ups_current->logfile != stdout) fclose(monhost_ups_current->logfile); diff --git a/docs/man/upslog.txt b/docs/man/upslog.txt index 478af0f9ac..e9bfcbb8cc 100644 --- a/docs/man/upslog.txt +++ b/docs/man/upslog.txt @@ -71,6 +71,11 @@ Wait this many seconds between polls. This defaults to 30 seconds. If you require tighter timing, you should write your own logger using the linkman:upsclient[3] library. +*-d* 'count':: + +Exit after specified amount of updates. Default is 0 for infinite loop +(until interrupted otherwise). + *-l* 'logfile':: Store the results in this file. diff --git a/scripts/systemd/nut-logger.service.in b/scripts/systemd/nut-logger.service.in index 914566fc0d..13d27cfbbf 100644 --- a/scripts/systemd/nut-logger.service.in +++ b/scripts/systemd/nut-logger.service.in @@ -23,7 +23,8 @@ Wants=nut-server.service # Requires=network-online.target # After=network-online.target PartOf=nut.target -Before=nut.target ### not enabled by default, but ordered if enabled by user +### not enabled by default, but ordered if enabled by user: +Before=nut.target Documentation=man:upslog(8) Documentation=@NUT_WEBSITE_BASE@/docs/man/upslog.html @@ -40,10 +41,12 @@ EnvironmentFile=-@CONFPATH@/nut.conf EnvironmentFile=@CONFPATH@/upslog.conf SyslogIdentifier=%N ExecStartPre=-@SYSTEMD_TMPFILES_PROGRAM@ --create @systemdtmpfilesdir@/nut-common-tmpfiles.conf -ExecStartPre=/bin/test -n "${UPSLOG_ARGS-}" -ExecStart=@SBINDIR@/upslog @SYSTEMD_DAEMON_ARGS_UPSLOG@ $UPSLOG_ARGS +ExecStartPre=/bin/test -n "${UPSLOG_ARGS}" +ExecStart=@BINDIR@/upslog @SYSTEMD_DAEMON_ARGS_UPSLOG@ $UPSLOG_ARGS # NOTE: SIGHUP is supported to re-open the log file, -# which is the default systemd ReloadSignal +# which is the default systemd ReloadSignal (only +# sent by systemd 253 and newer) +ExecReload=/bin/kill -HUP $MAINPID PIDFile=@PIDPATH@/upslog.pid # With this program, the PID file always exists and "kill -TERM" in particular # can be used from command-line or some legacy scripts, it causes a clean and diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 2da6495092..befec52803 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -34,7 +34,7 @@ # ksh, busybox sh...) # # Copyright -# 2022-2024 Jim Klimov +# 2022-2025 Jim Klimov # # License: GPLv2+ @@ -356,6 +356,11 @@ if [ "`id -u`" = 0 ]; then fi stop_daemons() { + if [ -n "$PID_UPSMON" ] ; then + log_info "Stopping test daemons: upsmon via command" + upsmon -c stop + fi + if [ -n "$PID_UPSD$PID_UPSMON$PID_DUMMYUPS$PID_DUMMYUPS1$PID_DUMMYUPS2" ] ; then log_info "Stopping test daemons" kill -15 $PID_UPSD $PID_UPSMON $PID_DUMMYUPS $PID_DUMMYUPS1 $PID_DUMMYUPS2 2>/dev/null || return 0 @@ -1189,7 +1194,17 @@ testcase_sandbox_upsc_query_bogus() { testcase_sandbox_upsc_query_timer() { log_separator log_info "[testcase_sandbox_upsc_query_timer] Test that dummy-ups TIMER action changes the reported state" - # Driver is set up to flip ups.status every 5 sec, so check every 3 + + # Driver is set up to flip ups.status every 5 sec, so check every 3 sec + # with upsc, but we can follow with upslog more intensively. New process + # launches can lag a lot on very busy SUTs; hopefully still-running ones + # are more responsive in this regard. + log_info "Starting upslog daemon" + rm -f "${NUT_STATEPATH}/upslog-dummy.log" || true + # Start as foregrounded always, so we have a PID to kill easily: + upslog -F -i 1 -d 30 -m "dummy@localhost:${NUT_PORT},${NUT_STATEPATH}/upslog-dummy.log" & + PID_UPSLOG="$!" + # 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" @@ -1211,7 +1226,15 @@ testcase_sandbox_upsc_query_timer() { fi fi fi - if echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OB" && echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OL" ; then + + log_info "Stopping upslog daemon" + kill -15 $PID_UPSLOG 2>/dev/null || true + wait $PID_UPSLOG || true + + if (grep " [OB] " "${NUT_STATEPATH}/upslog-dummy.log" && grep " [OL] " "${NUT_STATEPATH}/upslog-dummy.log") \ + || (grep " \[OB\] " "${NUT_STATEPATH}/upslog-dummy.log" && grep " \[OL\] " "${NUT_STATEPATH}/upslog-dummy.log") \ + || (echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OB" && echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OL") \ + ; then log_info "[testcase_sandbox_upsc_query_timer] PASSED: ups.status flips over time" PASSED="`expr $PASSED + 1`" else @@ -1219,6 +1242,8 @@ testcase_sandbox_upsc_query_timer() { FAILED="`expr $FAILED + 1`" FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_upsc_query_timer" fi + + #rm -f "${NUT_STATEPATH}/upslog-dummy.log" || true } isTestablePython() {