diff --git a/NEWS b/NEWS index a640603a71..e3dfa9b151 100644 --- a/NEWS +++ b/NEWS @@ -155,30 +155,30 @@ https://github.com/networkupstools/nut/milestone/8 this variable unless troubleshooting, although other systems rely on it) [#805] - - the nut-scanner program was updated to fall back to loading unresolved + - The nut-scanner program was updated to fall back to loading unresolved library filenames, hoping that `lt_dlopen()` implementation on the current platform would find library files better [#805] - - detection of `libltdl` in `configure` script updated with fallback code to + - Detection of `libltdl` in `configure` script updated with fallback code to find it on systems that deliver the library to `/usr/local/lib` (e.g. on FreeBSD) [#1577] - - an explicit `configure --with-nut-scanner` toggle was added, specifically + - An explicit `configure --with-nut-scanner` toggle was added, specifically so that build environments requesting `--with-all` but lack `libltdl` would abort and require either to install the dependency or explicitly forfeit the tool (some distro packages missed it quietly in the past) [#1560] - - existing openssl-1.1.0 support added for NUT v2.8.0 release was tested to + - Existing openssl-1.1.0 support added for NUT v2.8.0 release was tested to be sufficient without deprecation warnings for builds against openssl-3.0.x (but no real-time testing was done yet) [#1547] - upslog: Added support for logging multiple devices with one call to the program [#1604] - - some fixes applied to Solaris/illumos packaging and SMF service support + - Some fixes applied to Solaris/illumos packaging and SMF service support [#1554, #1564] - - some fixes for builds on older OSes with less functional default system + - Some fixes for builds on older OSes with less functional default system shell interpreters - now `autogen.sh` supports a `CONFIG_SHELL` envvar to inject its value into generated `configure` script [#1736] * Note that you may have to install additional tools (possibly from @@ -222,6 +222,13 @@ https://github.com/networkupstools/nut/milestone/8 encouraged to pick optimal location for their distributions (which remains mounted at least read-only late in shutdown) [#529] + - Extended Linux systemd support with optional notifications about daemon + state (READY, RELOADING, STOPPING) and watchdog keep-alive messages [#1590] + + - Extended Linux systemd units with aliases named after the daemons: + `nut-server.service` as `upsd.service`, and `nut-monitor.service` as + `upsmon.service` (so simple `systemctl reload upsd` can work) [#1777] + - Further revision of public headers delivered by NUT was done, particularly to address lack of common data types (`size_t`, `ssize_t`, `uint16_t`, `time_t` etc.) in third-party client code that earlier sufficed to only diff --git a/UPGRADING b/UPGRADING index f3c363258a..027a552fc3 100644 --- a/UPGRADING +++ b/UPGRADING @@ -47,7 +47,7 @@ Changes from 2.8.0 to 2.8.1 serial drivers in respective Makefile and configure script options - this may impact packaging decisions on some distributions going forward [#1446] -- an explicit `configure --with-nut-scanner` toggle was added, specifically +- An explicit `configure --with-nut-scanner` toggle was added, specifically so that build environments requesting `--with-all` but lacking `libltdl` would abort and require the packager either to install the dependency or explicitly forfeit building the tool (some distro packages missed it @@ -66,6 +66,13 @@ Changes from 2.8.0 to 2.8.1 * Several other issues have been fixed related to this file and its content, including #1030, #1037, #1117 and #1712 +- Extended Linux systemd support with optional notifications about daemon + state (READY, RELOADING, STOPPING) and watchdog keep-alive messages. + Note that `WatchdogSec=` values are currently NOT pre-set into systemd + unit file templates provided by NUT, this is an exercise for end-users + based on sizing of their deployments and performance of monitoring station + [#1590, #1777] + - snmp-ups: some subdrivers (addressed using the driver parameter `mibs`) were renamed: `pw` is now `eaton_pw_nm2`, and `pxgx_ups` is `eaton_pxg_ups` [#1715] diff --git a/clients/upslog.c b/clients/upslog.c index 24f96dcad9..191aedf390 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -618,7 +618,11 @@ int main(int argc, char **argv) compile_format(); + upsnotify(NOTIFY_STATE_READY_WITH_PID, NULL); + while (exit_flag == 0) { + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + time(&now); if (nextpoll > now) { @@ -631,10 +635,12 @@ int main(int argc, char **argv) } if (reopen_flag) { + upsnotify(NOTIFY_STATE_RELOADING, NULL); upslogx(LOG_INFO, "Signal %d: reopening log file", reopen_flag); reopen_log(); reopen_flag = 0; + upsnotify(NOTIFY_STATE_READY, NULL); } for (monhost_ups_current = monhost_ups_anchor; @@ -657,6 +663,8 @@ int main(int argc, char **argv) } upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); + upsnotify(NOTIFY_STATE_STOPPING, "Signal %d: exiting", exit_flag); + for (monhost_ups_current = monhost_ups_anchor; monhost_ups_current != NULL; monhost_ups_current = monhost_ups_current->next) { diff --git a/clients/upsmon.c b/clients/upsmon.c index f0767a5aa1..aac051e1b6 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -621,6 +621,8 @@ static void doshutdown(void) static void doshutdown(void) { + upsnotify(NOTIFY_STATE_STOPPING, "Executing automatic power-fail shutdown"); + /* this should probably go away at some point */ upslogx(LOG_CRIT, "Executing automatic power-fail shutdown"); wall("Executing automatic power-fail shutdown\n"); @@ -2580,6 +2582,7 @@ int main(int argc, char *argv[]) } if (upscli_init(certverify, certpath, certname, certpasswd) < 0) { + upsnotify(NOTIFY_STATE_STOPPING, "Failed upscli_init()"); exit(EXIT_FAILURE); } @@ -2590,15 +2593,22 @@ int main(int argc, char *argv[]) closelog(); open_syslog(prog); + upsnotify(NOTIFY_STATE_READY_WITH_PID, NULL); + while (exit_flag == 0) { utype_t *ups; + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + /* check flags from signal handlers */ if (userfsd) forceshutdown(); - if (reload_flag) + if (reload_flag) { + upsnotify(NOTIFY_STATE_RELOADING, NULL); reload_conf(); + upsnotify(NOTIFY_STATE_READY, NULL); + } for (ups = firstups; ups != NULL; ups = ups->next) pollups(ups); @@ -2676,6 +2686,7 @@ int main(int argc, char *argv[]) } upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); + upsnotify(NOTIFY_STATE_STOPPING, "Signal %d: exiting", exit_flag); upsmon_cleanup(); exit(EXIT_SUCCESS); diff --git a/common/Makefile.am b/common/Makefile.am index 9e64f050d1..4fbae5f41c 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -73,6 +73,22 @@ endif libcommon_la_LIBADD = libparseconf.la @LTLIBOBJS@ @NETLIBS@ libcommonclient_la_LIBADD = libparseconf.la @LTLIBOBJS@ @NETLIBS@ +libcommon_la_CFLAGS = $(AM_CFLAGS) +libcommonclient_la_CFLAGS = $(AM_CFLAGS) + +# Did the user request, and build env support, tighter integration with +# libsystemd methods such as sd_notify()? +if WITH_LIBSYSTEMD + libcommon_la_CFLAGS += $(LIBSYSTEMD_CFLAGS) + libcommon_la_LIBADD += $(LIBSYSTEMD_LIBS) + +# A typical client should not need this, +# but just in case (and to simplify linking)... +# libcommonclient_la_CFLAGS += $(LIBSYSTEMD_CFLAGS) +# libcommonclient_la_LIBADD += $(LIBSYSTEMD_LIBS) + libcommonclient_la_CFLAGS += -DWITHOUT_LIBSYSTEMD=1 +endif + MAINTAINERCLEANFILES = Makefile.in .dirstamp # NOTE: Do not clean ".deps" in SUBDIRS of the main project, diff --git a/common/common.c b/common/common.c index 4efdfe4451..88469790b3 100644 --- a/common/common.c +++ b/common/common.c @@ -36,6 +36,22 @@ # include #endif +#ifdef WITH_LIBSYSTEMD +# include +/* upsnotify() debug-logs its reports; a watchdog ping is something we + * try to send often so report it just once (whether enabled or not) */ +static int upsnotify_reported_watchdog_systemd = 0; +/* Similarly for only reporting once if the notification subsystem is disabled */ +static int upsnotify_reported_disabled_systemd = 0; +# ifndef DEBUG_SYSTEMD_WATCHDOG +/* Define this to 1 for lots of spam at debug level 6, and ignoring WATCHDOG_PID + * so trying to post reports anyway if WATCHDOG_USEC is valid */ +# define DEBUG_SYSTEMD_WATCHDOG 0 +# endif +#endif +/* Similarly for only reporting once if the notification subsystem is not built-in */ +static int upsnotify_reported_disabled_notech = 0; + /* the reason we define UPS_VERSION as a static string, rather than a macro, is to make dependency tracking easier (only common.o depends on nut_version_macro.h), and also to prevent all sources from @@ -438,9 +454,15 @@ int sendsignalpid(pid_t pid, int sig) pid_t parsepid(const char *buf) { pid_t pid = -1; + intmax_t _pid; + + if (!buf) { + upsdebugx(6, "%s: called with NULL input", __func__); + return pid; + } /* assuming 10 digits for a long */ - intmax_t _pid = strtol(buf, (char **)NULL, 10); + _pid = strtol(buf, (char **)NULL, 10); if (_pid <= get_max_pid_t()) { pid = (pid_t)_pid; } else { @@ -591,6 +613,330 @@ const char *xbasename(const char *file) return p + 1; } +/* Send (daemon) state-change notifications to an + * external service management framework such as systemd + */ +int upsnotify(upsnotify_state_t state, const char *fmt, ...) +{ + int ret = -127; + va_list va; + char buf[LARGEBUF]; + char msgbuf[LARGEBUF]; + size_t msglen = 0; + + /* Prepare the message (if any) as a string */ + msgbuf[0] = '\0'; + if (fmt) { + va_start(va, fmt); +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL +#pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_SECURITY +#pragma GCC diagnostic ignored "-Wformat-security" +#endif + /* generic message... */ + ret = vsnprintf(msgbuf, sizeof(msgbuf), fmt, va); +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL +#pragma GCC diagnostic pop +#endif + va_end(va); + + if ((ret < 0) || (ret >= (int) sizeof(msgbuf))) { + syslog(LOG_WARNING, + "%s (%s:%d): vsnprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(msgbuf), ret); + } else { + msglen = strlen(msgbuf); + } + /* Reset for actual notification processing below */ + ret = -127; + } + +#if defined(WITH_LIBSYSTEMD) && (WITH_LIBSYSTEMD) +# if defined(WITHOUT_LIBSYSTEMD) && (WITHOUT_LIBSYSTEMD) + NUT_UNUSED_VARIABLE(buf); + NUT_UNUSED_VARIABLE(msglen); + if (!upsnotify_reported_disabled_systemd) + upsdebugx(6, "%s: notify about state %i with libsystemd: " + "skipped for libcommonclient build, " + "will not spam more about it", __func__, state); + upsnotify_reported_disabled_systemd = 1; +# else + if (!getenv("NOTIFY_SOCKET")) { + if (!upsnotify_reported_disabled_systemd) + upsdebugx(6, "%s: notify about state %i with libsystemd: " + "was requested, but not running as a service unit now, " + "will not spam more about it", + __func__, state); + upsnotify_reported_disabled_systemd = 1; + } else { +# ifdef HAVE_SD_NOTIFY + +# if ! DEBUG_SYSTEMD_WATCHDOG + if (state != NOTIFY_STATE_WATCHDOG || !upsnotify_reported_watchdog_systemd) +# endif + upsdebugx(6, "%s: notify about state %i with libsystemd: use sd_notify()", __func__, state); + + /* https://www.freedesktop.org/software/systemd/man/sd_notify.html */ + if (msglen) { + ret = snprintf(buf, sizeof(buf), "STATUS=%s", msgbuf); + if ((ret < 0) || (ret >= (int) sizeof(buf))) { + syslog(LOG_WARNING, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(buf), ret); + msglen = 0; + } else { + msglen = (size_t)ret; + } + } + + switch (state) { + case NOTIFY_STATE_READY: + ret = snprintf(buf + msglen, sizeof(buf) - msglen, + "%sREADY=1", + msglen ? "\n" : ""); + break; + + case NOTIFY_STATE_READY_WITH_PID: + if (1) { /* scoping */ + char pidbuf[SMALLBUF]; + if (snprintf(pidbuf, sizeof(pidbuf), "%lu", (unsigned long) getpid())) { + ret = snprintf(buf + msglen, sizeof(buf) - msglen, + "%sREADY=1\n" + "MAINPID=%s", + msglen ? "\n" : "", + pidbuf); + upsdebugx(6, "%s: notifying systemd about MAINPID=%s", + __func__, pidbuf); + /* https://github.com/systemd/systemd/issues/25961 + * Reset the WATCHDOG_PID so we know this is the + * process we want to post pings from! + */ + unsetenv("WATCHDOG_PID"); + setenv("WATCHDOG_PID", pidbuf, 1); + } else { + upsdebugx(6, "%s: NOT notifying systemd about MAINPID, " + "got an error stringifying it; processing as " + "plain NOTIFY_STATE_READY", + __func__); + ret = snprintf(buf + msglen, sizeof(buf) - msglen, + "%sREADY=1", + msglen ? "\n" : ""); + /* TODO: Maybe revise/drop this tweak if + * loggers other than systemd are used: */ + state = NOTIFY_STATE_READY; + } + } + break; + + case NOTIFY_STATE_RELOADING: + ret = snprintf(buf + msglen, sizeof(buf) - msglen, "%s%s", + msglen ? "\n" : "", + "RELOADING=1"); + break; + + case NOTIFY_STATE_STOPPING: + ret = snprintf(buf + msglen, sizeof(buf) - msglen, "%s%s", + msglen ? "\n" : "", + "STOPPING=1"); + break; + + case NOTIFY_STATE_STATUS: + /* Only send a text message per "fmt" */ + if (!msglen) { + upsdebugx(6, "%s: failed to notify about status: none provided", __func__); + ret = -1; + } else { + ret = (int)msglen; + } + break; + + case NOTIFY_STATE_WATCHDOG: + /* Ping the framework that we are still alive */ + if (1) { /* scoping */ + int postit = 0; + +# ifdef HAVE_SD_WATCHDOG_ENABLED + uint64_t to = 0; + postit = sd_watchdog_enabled(0, &to); + + if (postit < 0) { +# if ! DEBUG_SYSTEMD_WATCHDOG + if (!upsnotify_reported_watchdog_systemd) +# endif + upsdebugx(6, "%s: sd_enabled_watchdog query failed: %s", + __func__, strerror(postit)); + } else { +# if ! DEBUG_SYSTEMD_WATCHDOG + if (!upsnotify_reported_watchdog_systemd || postit > 0) +# else + if (postit > 0) +# endif + upsdebugx(6, "%s: sd_enabled_watchdog query returned: %d " + "(%" PRIu64 "msec remain)", + __func__, postit, to); + } +# endif + + if (postit < 1) { + char *s = getenv("WATCHDOG_USEC"); +# if ! DEBUG_SYSTEMD_WATCHDOG + if (!upsnotify_reported_watchdog_systemd) +# endif + upsdebugx(6, "%s: WATCHDOG_USEC=%s", __func__, s); + if (s && *s) { + long l = strtol(s, (char **)NULL, 10); + if (l > 0) { + pid_t wdpid = parsepid(getenv("WATCHDOG_PID")); + if (wdpid == (pid_t)-1 || wdpid == getpid()) { +# if ! DEBUG_SYSTEMD_WATCHDOG + if (!upsnotify_reported_watchdog_systemd) +# endif + upsdebugx(6, "%s: can post: WATCHDOG_PID=%li", + __func__, (long)wdpid); + postit = 1; + } else { +# if ! DEBUG_SYSTEMD_WATCHDOG + if (!upsnotify_reported_watchdog_systemd) +# endif + upsdebugx(6, "%s: watchdog is configured, " + "but not for this process: " + "WATCHDOG_PID=%li", + __func__, (long)wdpid); +# if DEBUG_SYSTEMD_WATCHDOG + /* Just try to post - at worst, systemd + * NotifyAccess will prohibit the message. + * The envvar simply helps child processes + * know they should not spam the watchdog + * handler (usually only MAINPID should): + * https://github.com/systemd/systemd/issues/25961#issuecomment-1373947907 + */ + postit = 1; +# else + postit = 0; +# endif + } + } + } + } + + if (postit > 0) { + ret = snprintf(buf + msglen, sizeof(buf) - msglen, "%s%s", + msglen ? "\n" : "", + "WATCHDOG=1"); + } else if (postit == 0) { +# if ! DEBUG_SYSTEMD_WATCHDOG + if (!upsnotify_reported_watchdog_systemd) +# endif + upsdebugx(6, "%s: failed to tickle the watchdog: not enabled for this unit", __func__); + ret = -126; + } + } + break; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + /* All enum cases defined as of the time of coding + * have been covered above. Handle later definitions, + * memory corruptions and buggy inputs below... + */ + default: + if (!msglen) { + upsdebugx(6, "%s: unknown state and no status message provided", __func__); + ret = -1; + } else { + upsdebugx(6, "%s: unknown state but have a status message provided", __func__); + ret = (int)msglen; + } +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + } + + if ((ret < 0) || (ret >= (int) sizeof(buf))) { + /* Refusal to send the watchdog ping is not an error to report */ + if ( !(ret == -126 && (state == NOTIFY_STATE_WATCHDOG)) ) { + syslog(LOG_WARNING, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(buf), ret); + } + ret = -1; + } else { + upsdebugx(6, "%s: posting sd_notify: %s", __func__, buf); + msglen = (size_t)ret; + ret = sd_notify(0, buf); + if (ret > 0 && state == NOTIFY_STATE_READY_WITH_PID) { + /* Usually we begin the main loop just after this + * and post a watchdog message but systemd did not + * yet prepare to handle us */ + upsdebugx(6, "%s: wait for NOTIFY_STATE_READY_WITH_PID to be handled by systemd", __func__); +# ifdef HAVE_SD_NOTIFY_BARRIER + sd_notify_barrier(0, UINT64_MAX); +# else + usleep(3 * 1000000); +# endif + } + } + +# else /* not HAVE_SD_NOTIFY: */ + /* FIXME: Try to fork and call systemd-notify helper program */ + upsdebugx(6, "%s: notify about state %i with libsystemd: lacking sd_notify()", __func__, state); + ret = -127; +# endif /* HAVE_SD_NOTIFY */ + } +# endif /* if not WITHOUT_LIBSYSTEMD (explicit avoid) */ +#else /* not WITH_LIBSYSTEMD */ + NUT_UNUSED_VARIABLE(buf); + NUT_UNUSED_VARIABLE(msglen); +#endif /* WITH_LIBSYSTEMD */ + + if (ret < 0 +#if defined(WITH_LIBSYSTEMD) && (WITH_LIBSYSTEMD) && !(defined(WITHOUT_LIBSYSTEMD) && (WITHOUT_LIBSYSTEMD)) && (defined(HAVE_SD_NOTIFY) && HAVE_SD_NOTIFY) +# if ! DEBUG_SYSTEMD_WATCHDOG + && (!upsnotify_reported_watchdog_systemd || (state != NOTIFY_STATE_WATCHDOG)) +# endif +#endif + ) { + if (ret == -127) { + if (!upsnotify_reported_disabled_notech) + upsdebugx(6, "%s: failed to notify about state %i: no notification tech defined, will not spam more about it", __func__, state); + upsnotify_reported_disabled_notech = 1; + } else { + upsdebugx(6, "%s: failed to notify about state %i", __func__, state); + } + } + +#if defined(WITH_LIBSYSTEMD) && (WITH_LIBSYSTEMD) +# if ! DEBUG_SYSTEMD_WATCHDOG + if (state == NOTIFY_STATE_WATCHDOG && !upsnotify_reported_watchdog_systemd) { + upsdebugx(6, "%s: logged the systemd watchdog situation once, will not spam more about it", __func__); + upsnotify_reported_watchdog_systemd = 1; + } +# endif +#endif + + return ret; +} + static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) { int ret; diff --git a/configure.ac b/configure.ac index 689366c220..0bd0f1af6d 100644 --- a/configure.ac +++ b/configure.ac @@ -1397,6 +1397,7 @@ dnl ---------------------------------------------------------------------- dnl checks related to --with_linux_i2c dnl Check for i2c header on Linux, used for ASEM UPS driver NUT_ARG_WITH([linux_i2c], [build and install i2c drivers], [auto]) +LIBI2C_LIBS="" if test "${nut_with_linux_i2c}" != no; then case ${target_os} in linux* ) @@ -1429,6 +1430,8 @@ if test "${nut_with_linux_i2c}" != no; then dnl "compromised" by lack of respective binary library, so dnl even if we have the right headers, ultimate link fails. dnl Note: here we keep the verdict from above, or make it worse. + LIBS_SAVED="$LIBS" + LIBS="" AC_SEARCH_LIBS([i2c_smbus_read_byte, i2c_smbus_access, i2c_smbus_read_byte_data, i2c_smbus_write_byte_datai2c_smbus_write_byte_data, i2c_smbus_read_word_data, i2c_smbus_write_word_data, i2c_smbus_read_block_data], [i2c], [nut_with_linux_i2c="yes"], @@ -1436,6 +1439,8 @@ if test "${nut_with_linux_i2c}" != no; then [AC_MSG_ERROR(i2c was required but can not be fulfilled for this build)], [nut_with_linux_i2c="no"]) ]) + LIBI2C_LIBS="$LIBS" + LIBS="$LIBS_SAVED" ;; * ) if test "${nut_with_linux_i2c}" = yes; then @@ -2596,6 +2601,7 @@ fi AC_MSG_RESULT([${solarisinit}]) AM_CONDITIONAL(WITH_SOLARIS_INIT, test x"$solarisinit" = x"yes") + dnl Note: Currently there is no reliable automatic detection - dnl users have to ask they want systemd units installed, or dnl risk auto-detection like seen below. @@ -2629,15 +2635,16 @@ else have_systemd="no" AC_MSG_RESULT(no) fi -dnl Note: we may want tighter integration, e.g. systemd-notify support at -dnl a later point. That would be a further option/flag. This one is a very -dnl basic yes/no toggle. -NUT_REPORT_FEATURE([consider systemd support], [${have_systemd}], [], - [HAVE_SYSTEMD], [Define to consider systemd support]) +dnl Note: we may want tighter integration, e.g. systemd-notify support +dnl configured as a further option/flag (see --with-systemd below). +dnl This one is a very basic yes/no toggle for unit file delivery. +NUT_REPORT_FEATURE([consider basic systemd support], [${have_systemd}], [], + [HAVE_SYSTEMD], [Define to consider basic systemd support (provide units and configuration files)]) dnl This option is only provided so that make distcheck can override it, dnl otherwise we ask pkg-config whenever --with-systemdsystemunitdir is dnl given + AC_MSG_CHECKING(whether to install systemd shutdown files) AC_ARG_WITH([systemdshutdowndir], AS_HELP_STRING([--with-systemdshutdowndir=DIR], [Directory for systemd shutdown scripts (auto)]), @@ -2670,6 +2677,7 @@ else AC_MSG_RESULT(no) fi + dnl Note: if (systemd-)tmpfiles tech is present, it can be useful even for dnl daemons starting not as systemd units, to pre-create /var/run/nut etc. AC_MSG_CHECKING([whether to install systemd tmpfiles files]) @@ -2701,6 +2709,72 @@ else AC_MSG_RESULT(no) fi + +dnl Note: we may want binaries with sd_notify and similar features regardless +dnl of building and delivering unit files (which may be crafted separately). +dnl TODO: although end-user deployments (for custom builds) may be lacking +dnl libsystemd development files, they might have a `systemd-notify` program +dnl intended to help scripted service units. Consider making use of that then. +AC_MSG_CHECKING(whether to build binaries with tighter systemd integration support) +NUT_ARG_WITH([libsystemd], [build binaries with tighter systemd integration (notifications etc)], [auto]) +NUT_CHECK_LIBSYSTEMD + +AS_IF([test x"${nut_with_libsystemd}" = xyes && test x"${nut_have_libsystemd}" != xyes], + [AC_MSG_ERROR([--with-libsystemd was requested, but the library was not found or usable])]) +AS_CASE(["${nut_with_libsystemd}"], + [yes|no], [have_libsystemd="${nut_with_libsystemd}"], + [AS_IF([test x"${nut_have_libsystemd}" = xyes], + [with_libsystemd="yes"], + [with_libsystemd="no"]) + ]) +AC_MSG_RESULT(${with_libsystemd}) + +AS_IF([test x"${with_libsystemd}" = xyes], [ + dnl Built with sd_notify support + dnl Note: `upsd -FF` both runs without forking and leaves a PID file, as + dnl needed for `upsd -c reload` in legacy scripts and old habits to work: + SYSTEMD_DAEMON_ARGS_UPSD="-FF" + SYSTEMD_DAEMON_TYPE_UPSD="notify" + SYSTEMD_DAEMON_ARGS_UPSMON="-F" + SYSTEMD_DAEMON_TYPE_UPSMON="notify" + SYSTEMD_DAEMON_ARGS_DRIVER="" + SYSTEMD_DAEMON_TYPE_DRIVER="notify" + dnl Calling shell, upsdrvctl, driver, and then it forks... ugh! + dnl https://github.com/systemd/systemd/issues/25961 + dnl FIXME: if NotifyAccess=cgroup appears, use it (consult SYSTEMD_VERSION) + SYSTEMD_DAEMON_NOTIFYACCESS_DRIVER="NotifyAccess=all" + dnl Similar for UPSMON with its two processes: + SYSTEMD_DAEMON_NOTIFYACCESS_UPSMON="NotifyAccess=all" + dnl UPSD is started directly by systemd and do not fork: + SYSTEMD_DAEMON_NOTIFYACCESS_UPSD="NotifyAccess=main" + dnl Note: at this time we do not pre-define watchdog settings, + dnl to avoid breaking something by a poorly hardcoded guess. + dnl This is something end-users should do for their deployment, + dnl especially for drivers + SYSTEMD_DAEMON_WATCHDOG_DRIVER="#WatchdogSec=240s" + SYSTEMD_DAEMON_WATCHDOG_UPSD="#WatchdogSec=240s" + SYSTEMD_DAEMON_WATCHDOG_UPSMON="#WatchdogSec=240s" + ], [ + dnl "Usual" daemons that happen to be spawned by systemd + SYSTEMD_DAEMON_ARGS_UPSD="-F" + SYSTEMD_DAEMON_TYPE_UPSD="simple" + SYSTEMD_DAEMON_ARGS_UPSMON="-F" + SYSTEMD_DAEMON_TYPE_UPSMON="simple" + SYSTEMD_DAEMON_ARGS_DRIVER="" + SYSTEMD_DAEMON_TYPE_DRIVER="forking" + SYSTEMD_DAEMON_NOTIFYACCESS_DRIVER="" + SYSTEMD_DAEMON_NOTIFYACCESS_UPSD="" + SYSTEMD_DAEMON_NOTIFYACCESS_UPSMON="" + dnl Watchdog should not be configured for not-notifying units, right? + SYSTEMD_DAEMON_WATCHDOG_DRIVER="#WatchdogSec=240s" + SYSTEMD_DAEMON_WATCHDOG_UPSD="#WatchdogSec=240s" + SYSTEMD_DAEMON_WATCHDOG_UPSMON="#WatchdogSec=240s" + ]) + +NUT_REPORT_FEATURE([build with tighter systemd support], [${with_libsystemd}], [], + [WITH_LIBSYSTEMD], [Define to build with tighter systemd support (sd_notify etc)]) + + dnl dnl Tests for CppUnit availability and usability (will be built if we can, dnl and if valgrind is enabled for this configuration - reported below). @@ -3321,12 +3395,27 @@ AC_SUBST(LIBMODBUS_CFLAGS) AC_SUBST(LIBMODBUS_LIBS) AC_SUBST(LIBIPMI_CFLAGS) AC_SUBST(LIBIPMI_LIBS) +AC_SUBST(LIBI2C_LIBS) AC_SUBST(DOC_BUILD_LIST) AC_SUBST(DOC_CHECK_LIST) AC_SUBST(LIBWRAP_CFLAGS) AC_SUBST(LIBWRAP_LIBS) AC_SUBST(LIBLTDL_CFLAGS) AC_SUBST(LIBLTDL_LIBS) +AC_SUBST(LIBSYSTEMD_CFLAGS) +AC_SUBST(LIBSYSTEMD_LIBS) +AC_SUBST(SYSTEMD_DAEMON_ARGS_UPSD) +AC_SUBST(SYSTEMD_DAEMON_TYPE_UPSD) +AC_SUBST(SYSTEMD_DAEMON_ARGS_UPSMON) +AC_SUBST(SYSTEMD_DAEMON_TYPE_UPSMON) +AC_SUBST(SYSTEMD_DAEMON_ARGS_DRIVER) +AC_SUBST(SYSTEMD_DAEMON_TYPE_DRIVER) +AC_SUBST(SYSTEMD_DAEMON_NOTIFYACCESS_DRIVER) +AC_SUBST(SYSTEMD_DAEMON_NOTIFYACCESS_UPSD) +AC_SUBST(SYSTEMD_DAEMON_NOTIFYACCESS_UPSMON) +AC_SUBST(SYSTEMD_DAEMON_WATCHDOG_UPSD) +AC_SUBST(SYSTEMD_DAEMON_WATCHDOG_UPSMON) +AC_SUBST(SYSTEMD_DAEMON_WATCHDOG_DRIVER) AC_SUBST(DRIVER_BUILD_LIST) AC_SUBST(DRIVER_MAN_LIST) AC_SUBST(DRIVER_MAN_LIST_PAGES) diff --git a/docs/FAQ.txt b/docs/FAQ.txt index d8e24baceb..2666478d97 100644 --- a/docs/FAQ.txt +++ b/docs/FAQ.txt @@ -550,6 +550,10 @@ of `nut-server.service` by default starts `upsd -F` and does not save a PID file; if your workflow requires to use plain `upsd -c reload`, you should customize the unit (with a drop-in file) to start `upsd -FF`. +NUT releases after 2.8.0 define aliases for these units, so if your Linux +distribution uses NUT-provided unit definitions, `systemctl reload upsd` +may also work. + == I just bought a new WhizBang UPS that has a USB connector. How do I monitor it? There are several driver to support USB models. diff --git a/docs/config-notes.txt b/docs/config-notes.txt index 2e0ab75e02..5f9f7a236d 100644 --- a/docs/config-notes.txt +++ b/docs/config-notes.txt @@ -602,6 +602,10 @@ generally forgoes saving of PID files, so `upsd -c ` would not work. If your workflow requires to manage these daemons beside the OS provided framework, you can customize it to start `upsd -FF` and save the PID file. +NUT releases after 2.8.0 define aliases for these units, so if your Linux +distribution uses NUT-provided unit definitions, `systemctl reload upsd` +may also work. + NOTE: If you want to make reloading work later, see the entry in the link:FAQ.html[FAQ] about starting `upsd` as a different user. diff --git a/docs/config-prereqs.txt b/docs/config-prereqs.txt index 8a4fc62027..3b40f4736f 100644 --- a/docs/config-prereqs.txt +++ b/docs/config-prereqs.txt @@ -151,6 +151,10 @@ metadata about recently published package revisions: :; apt-get install \ libgd-dev +# Optionally for sd_notify integration: +:; apt-get install \ + libsystemd-dev + # NOTE: Some older Debian-like distributions, could ship "libcrypto-dev" # and/or "openssl-dev" instead of "libssl-dev" by its modern name :; apt-get install \ @@ -254,7 +258,7 @@ drivers in distro packaging of NUT. Resolution and doc PRs are welcome. ------ :; yum install \ ccache time \ - file systemd-devel \ + file \ git python perl curl \ make autoconf automake libtool-ltdl-devel libtool \ valgrind \ @@ -282,6 +286,10 @@ drivers in distro packaging of NUT. Resolution and doc PRs are welcome. :; yum install \ gd-devel +# Optionally for sd_notify integration: +:; yum install \ + systemd-devel + # NOTE: "libusbx" is the CentOS way of naming "libusb-1.0" # vs. the older "libusb" as the package with "libusb-0.1" :; yum install \ @@ -358,6 +366,10 @@ Install tools and prerequisites for NUT: :; pacman -S --needed \ gd +# Optionally for sd_notify integration: +:; pacman -S --needed \ + systemd + :; pacman -S --needed \ cppunit \ openssl nss \ diff --git a/docs/configure.txt b/docs/configure.txt index 488e7225eb..822fca1990 100644 --- a/docs/configure.txt +++ b/docs/configure.txt @@ -537,6 +537,18 @@ Use `--with-systemdtmpfilesdir` to detect the settings using pkg-config. Use `--with-systemdtmpfilesdir=no` to disable this feature altogether. + --with-libsystemd=(auto|yes|no) + --with-libsystemd-includes=CFLAGS + --with-libsystemd-libs=LDFLAGS + +If the build system provides `libsystemd` headers, NUT binaries can be +built with tighter integration to this service management framework. +In this case NUT daemons (`upsd`, `upsmon`, `upslog` and drivers) would +report their life-cycle milestones (`READY`, `RELOADING`, `STOPPING`) and +support the watchdog reports (if enabled in their respective units by +end-user -- not done by default since the numbers depends on monitoring +system performance). Default: "auto" (integration enabled if detected). + --with-augeas-lenses-dir=PATH Where to install Augeas configuration-management lenses. diff --git a/docs/man/upsd.txt b/docs/man/upsd.txt index ffdcbcc802..1debe7f9a5 100644 --- a/docs/man/upsd.txt +++ b/docs/man/upsd.txt @@ -88,9 +88,18 @@ RELOADING upsd can reload its configuration files without shutting down the process if you send it a SIGHUP or start it again with `-c reload`. This only works if the background process is able to read those files, and if the daemon did -save a PID file when it started (e.g. service instances wrapped by systemd -or SMF do not save them by default -- use respective `reload`/`refresh` -framework actions instead). +save a PID file when it started. + +[NOTE] +====== +Service instances wrapped by systemd or SMF might not save them by default -- +use respective `reload`/`refresh` framework actions instead then), e.g. +`systemctl reload nut-server` + +NUT releases after 2.8.0 define aliases for these units, so if your Linux +distribution uses NUT-provided unit definitions, `systemctl reload upsd` +may also work. +====== If you think that upsd can't reload, check your syslog for error messages. If it's complaining about not being able to read the files, then you need diff --git a/docs/nut.dict b/docs/nut.dict index bd1fd31a52..0f89a7b016 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3067 utf-8 +personal_ws-1.1 en 3069 utf-8 AAS ABI ACFAIL @@ -1341,6 +1341,7 @@ WS WSE WTU Waldie +WatchdogSec Werror Wextra WhizBang @@ -2133,6 +2134,7 @@ libregex libs libsnmp libssl +libsystemd libtool libupsclient libusb diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 31fcd7120b..393845be13 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -15,6 +15,7 @@ LDADD_DRIVERS_SERIAL = libdummy_serial.la $(LDADD_DRIVERS) $(SERLIBS) # most targets are serial drivers, so make this the default LDADD = $(LDADD_DRIVERS_SERIAL) + # Avoid per-target CFLAGS, because this will prevent re-use of object # files. In any case, CFLAGS are only -I options, so there is no harm, # but only add them if we really use the target. @@ -292,9 +293,9 @@ socomec_jbus_SOURCES = socomec_jbus.c socomec_jbus_LDADD = $(LDADD_DRIVERS_SERIAL) $(LIBMODBUS_LIBS) # Linux I2C drivers -asem_LDADD = $(LDADD_DRIVERS) +asem_LDADD = $(LDADD_DRIVERS) $(LIBI2C_LIBS) asem_SOURCES = asem.c -pijuice_LDADD = $(LDADD_DRIVERS) +pijuice_LDADD = $(LDADD_DRIVERS) $(LIBI2C_LIBS) pijuice_SOURCES = pijuice.c # nutdrv_qx USB/Serial diff --git a/drivers/main.c b/drivers/main.c index 8f7dce69cd..6640b13e20 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -633,6 +633,10 @@ static void vartab_free(void) static void exit_cleanup(void) { + if (!dump_data) { + upsnotify(NOTIFY_STATE_STOPPING, "exit_cleanup()"); + } + free(chroot_path); free(device_path); free(user); @@ -1147,10 +1151,18 @@ int main(int argc, char **argv) writepid(pidfn); /* PID changes when backgrounding */ } + if (!dump_data) { + upsnotify(NOTIFY_STATE_READY_WITH_PID, NULL); + } + while (!exit_flag) { struct timeval timeout; + if (!dump_data) { + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + } + gettimeofday(&timeout, NULL); timeout.tv_sec += poll_interval; @@ -1175,8 +1187,10 @@ int main(int argc, char **argv) /* if we get here, the exit flag was set by a signal handler */ /* however, avoid to "pollute" data dump output! */ - if (!dump_data) + if (!dump_data) { upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); + upsnotify(NOTIFY_STATE_STOPPING, "Signal %d: exiting", exit_flag); + } exit(EXIT_SUCCESS); } diff --git a/include/common.h b/include/common.h index 6e22e3159d..b2e61abc2d 100644 --- a/include/common.h +++ b/include/common.h @@ -245,6 +245,23 @@ const char * altpidpath(void); /* Die with a standard message if socket filename is too long */ void check_unix_socket_filename(const char *fn); +/* Send (daemon) state-change notifications to an + * external service management framework such as systemd. + * State types below are initially loosely modeled after + * https://www.freedesktop.org/software/systemd/man/sd_notify.html + */ +typedef enum eupsnotify_state { + NOTIFY_STATE_READY = 1, + NOTIFY_STATE_READY_WITH_PID, + NOTIFY_STATE_RELOADING, + NOTIFY_STATE_STOPPING, + NOTIFY_STATE_STATUS, /* Send a text message per "fmt" below */ + NOTIFY_STATE_WATCHDOG /* Ping the framework that we are still alive */ +} upsnotify_state_t; +/* Note: here fmt may be null, then the STATUS message would not be sent/added */ +int upsnotify(upsnotify_state_t state, const char *fmt, ...) + __attribute__ ((__format__ (__printf__, 2, 3))); + /* upslog*() messages are sent to syslog always; * their life after that is out of NUT's control */ void upslog_with_errno(int priority, const char *fmt, ...) diff --git a/m4/nut_check_libsystemd.m4 b/m4/nut_check_libsystemd.m4 new file mode 100644 index 0000000000..155add4423 --- /dev/null +++ b/m4/nut_check_libsystemd.m4 @@ -0,0 +1,90 @@ +dnl Check for LIBSYSTEMD compiler flags. On success, set nut_have_libsystemd="yes" +dnl and set LIBSYSTEMD_CFLAGS and LIBSYSTEMD_LIBS. On failure, set +dnl nut_have_libsystemd="no". This macro can be run multiple times, but will +dnl do the checking only once. + +AC_DEFUN([NUT_CHECK_LIBSYSTEMD], +[ +if test -z "${nut_have_libsystemd_seen}"; then + nut_have_libsystemd_seen=yes + NUT_CHECK_PKGCONFIG + + dnl save CFLAGS and LIBS + CFLAGS_ORIG="${CFLAGS}" + LIBS_ORIG="${LIBS}" + + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [dnl See which version of the systemd library (if any) is installed + dnl FIXME : Support detection of cflags/ldflags below by legacy + dnl discovery if pkgconfig is not there + AC_MSG_CHECKING(for libsystemd version via pkg-config) + SYSTEMD_VERSION="`$PKG_CONFIG --silence-errors --modversion libsystemd 2>/dev/null`" + if test "$?" != "0" -o -z "${SYSTEMD_VERSION}"; then + SYSTEMD_VERSION="none" + fi + AC_MSG_RESULT(${SYSTEMD_VERSION} found) + ], + [SYSTEMD_VERSION="none" + AC_MSG_NOTICE([can not check libsystemd settings via pkg-config]) + ] + ) + + AC_MSG_CHECKING(for libsystemd cflags) + AC_ARG_WITH(libsystemd-includes, + AS_HELP_STRING([@<:@--with-libsystemd-includes=CFLAGS@:>@], [include flags for the systemd library]), + [ + case "${withval}" in + yes|no) + AC_MSG_ERROR(invalid option --with(out)-libsystemd-includes - see docs/configure.txt) + ;; + *) + CFLAGS="${withval}" + ;; + esac + ], [ + dnl Not specifying a default include path here, + dnl headers are referenced by relative directory + dnl and these should be in OS location usually. + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [CFLAGS="`$PKG_CONFIG --silence-errors --cflags libsystemd 2>/dev/null`" || CFLAGS=""], + [CFLAGS=""] + )] + ) + AC_MSG_RESULT([${CFLAGS}]) + + AC_MSG_CHECKING(for libsystemd ldflags) + AC_ARG_WITH(libsystemd-libs, + AS_HELP_STRING([@<:@--with-libsystemd-libs=LIBS@:>@], [linker flags for the systemd library]), + [ + case "${withval}" in + yes|no) + AC_MSG_ERROR(invalid option --with(out)-libsystemd-libs - see docs/configure.txt) + ;; + *) + LIBS="${withval}" + ;; + esac + ], [ + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [LIBS="`$PKG_CONFIG --silence-errors --libs libsystemd 2>/dev/null`" || LIBS="-lsystemd"], + [LIBS="-lsystemd"] + )] + ) + AC_MSG_RESULT([${LIBS}]) + + dnl check if libsystemd is usable + AC_CHECK_HEADERS(systemd/sd-daemon.h, [nut_have_libsystemd=yes], [nut_have_libsystemd=no], [AC_INCLUDES_DEFAULT]) + AC_CHECK_FUNCS(sd_notify, [], [nut_have_libsystemd=no]) + + AS_IF([test x"${nut_have_libsystemd}" = x"yes"], [ + dnl Check for additional feature support in library (optional) + AC_CHECK_FUNCS(sd_booted sd_watchdog_enabled sd_notify_barrier) + LIBSYSTEMD_CFLAGS="${CFLAGS}" + LIBSYSTEMD_LIBS="${LIBS}" + ]) + + dnl restore original CFLAGS and LIBS + CFLAGS="${CFLAGS_ORIG}" + LIBS="${LIBS_ORIG}" +fi +]) diff --git a/scripts/systemd/nut-driver@.service.in b/scripts/systemd/nut-driver@.service.in index f3e08a4c23..413750813a 100644 --- a/scripts/systemd/nut-driver@.service.in +++ b/scripts/systemd/nut-driver@.service.in @@ -41,7 +41,7 @@ PartOf=nut-driver.target Environment=NUT_IGNORE_NOWAIT=true EnvironmentFile=-@CONFPATH@/nut.conf SyslogIdentifier=%N -ExecStart=/bin/sh -c 'NUTDEV="`@NUT_LIBEXECDIR@/nut-driver-enumerator.sh --get-device-for-service %i`" && [ -n "$NUTDEV" ] || { echo "FATAL: Could not find a NUT device section for service unit %i" >&2 ; exit 1 ; } ; @SBINDIR@/upsdrvctl start "$NUTDEV"' +ExecStart=/bin/sh -c 'NUTDEV="`@NUT_LIBEXECDIR@/nut-driver-enumerator.sh --get-device-for-service %i`" && [ -n "$NUTDEV" ] || { echo "FATAL: Could not find a NUT device section for service unit %i" >&2 ; exit 1 ; } ; @SBINDIR@/upsdrvctl @SYSTEMD_DAEMON_ARGS_DRIVER@ start "$NUTDEV"' ExecStop=/bin/sh -c 'NUTDEV="`@NUT_LIBEXECDIR@/nut-driver-enumerator.sh --get-device-for-service %i`" && [ -n "$NUTDEV" ] || { echo "FATAL: Could not find a NUT device section for service unit %i" >&2 ; exit 1 ; } ; @SBINDIR@/upsdrvctl stop "$NUTDEV"' # Restart really always, do not stop trying: StartLimitInterval=0 @@ -53,7 +53,15 @@ Restart=always # devices (so getting a chicken-and-egg problem for driver-upsd-driver # orderly series of initializations). More details in NUT issue #779. RestartSec=15s -Type=forking +Type=@SYSTEMD_DAEMON_TYPE_DRIVER@ +@SYSTEMD_DAEMON_NOTIFYACCESS_DRIVER@ +# Note: if you set up with systemd notification support, you can take +# advantage of watchdog mechanism. Timeouts involved are deployment +# dependent (how many devices you monitor, how stressed are they and +# the monitoring system, protocol used -- e.g. SNMP walks can take long), +# so distributions should not pre-define this (at least not to a small +# value): +@SYSTEMD_DAEMON_WATCHDOG_DRIVER@ # Note: If you customize the "maxstartdelay" in ups.conf or in your # NUT compilation defaults, so it exceeds the default systemd unit # startup timeout (typically 90 sec), then make sure to set a slightly diff --git a/scripts/systemd/nut-monitor.service.in b/scripts/systemd/nut-monitor.service.in index 4ee216e160..5b22148ae8 100644 --- a/scripts/systemd/nut-monitor.service.in +++ b/scripts/systemd/nut-monitor.service.in @@ -1,5 +1,6 @@ [Unit] Description=Network UPS Tools - power device monitor and shutdown controller +# Note: do not mistake this historic misnomer for "NUT-Monitor" Python GUI client After=local-fs.target network.target nut-server.service # Note: We do not specify Requires nut-server.service because # the `upsd` daemon(s) may be running on a different machine @@ -18,9 +19,18 @@ PartOf=nut.target [Service] EnvironmentFile=-@CONFPATH@/nut.conf SyslogIdentifier=%N -ExecStart=@SBINDIR@/upsmon -F +ExecStart=@SBINDIR@/upsmon @SYSTEMD_DAEMON_ARGS_UPSMON@ ExecReload=@SBINDIR@/upsmon -c reload PIDFile=@PIDPATH@/upsmon.pid +# If "-FF" or background-daemon mode is used, so that PID file exists +# and "upsmon -c stop" in particular can be used from command-line or +# legacy scripts, it causes a clean and intentional exit of the daemon. +# Then systemd should not revive it - hence restart it only on failure: +Restart=on-failure +Type=@SYSTEMD_DAEMON_TYPE_UPSMON@ +@SYSTEMD_DAEMON_WATCHDOG_UPSMON@ +@SYSTEMD_DAEMON_NOTIFYACCESS_UPSMON@ [Install] WantedBy=nut.target +Alias=upsmon.service diff --git a/scripts/systemd/nut-server.service.in b/scripts/systemd/nut-server.service.in index 5edc2d5fa4..0a377c23ab 100644 --- a/scripts/systemd/nut-server.service.in +++ b/scripts/systemd/nut-server.service.in @@ -23,8 +23,19 @@ SyslogIdentifier=%N # Note: foreground mode "-F" by default skips writing a PID file (and # needs default Type=simple); we can use "-FF" here to create the file # anyway, so that old "upsd -c reload" works rather than systemd action: -ExecStart=@SBINDIR@/upsd -F +ExecStart=@SBINDIR@/upsd @SYSTEMD_DAEMON_ARGS_UPSD@ ExecReload=@SBINDIR@/upsd -c reload -P $MAINPID +# No tracking for PIDFile path and service attribute here (it might not +# even exist). +# If "-FF" or background-daemon mode is used, so that PID file exists +# and "upsd -c stop" in particular can be used from command-line or +# legacy scripts, it causes a clean and intentional exit of the daemon. +# Then systemd should not revive it - hence restart it only on failure: +Restart=on-failure +Type=@SYSTEMD_DAEMON_TYPE_UPSD@ +@SYSTEMD_DAEMON_WATCHDOG_UPSD@ +@SYSTEMD_DAEMON_NOTIFYACCESS_UPSD@ [Install] WantedBy=nut.target +Alias=upsd.service diff --git a/server/upsd.c b/server/upsd.c index 138688bf47..1a87b55d61 100644 --- a/server/upsd.c +++ b/server/upsd.c @@ -1083,12 +1083,16 @@ static void mainloop(void) stype_t *server; time_t now; + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + time(&now); if (reload_flag) { + upsnotify(NOTIFY_STATE_RELOADING, NULL); conf_reload(); poll_reload(); reload_flag = 0; + upsnotify(NOTIFY_STATE_READY, NULL); } /* cleanup instcmd/setvar status tracking entries if needed */ @@ -1914,12 +1918,16 @@ int main(int argc, char **argv) /* initialize SSL (keyfile must be readable by nut user) */ ssl_init(); + upsnotify(NOTIFY_STATE_READY_WITH_PID, NULL); + while (!exit_flag) { + /* Note: mainloop() calls upsnotify(NOTIFY_STATE_WATCHDOG, NULL); */ mainloop(); } - ssl_cleanup(); - upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); + upsnotify(NOTIFY_STATE_STOPPING, "Signal %d: exiting", exit_flag); + + ssl_cleanup(); return EXIT_SUCCESS; }