diff --git a/NEWS.adoc b/NEWS.adoc index a8be705969..c4ad725faf 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -401,6 +401,10 @@ relocated into new `shutdown.default` INSTCMD definitions. [#2670] by the `configure` script during build, but can override it with a `NUT_PIDPATH` environment variable in certain use-cases (such as tests). [#2407] + * allow drivers to set `STATEPATH` via `ups.conf` to match `upsd` + custom configuration ability; the data server would prefer the value + from `ups.conf` over the one in `upsd.conf`, if both are present. + Note that `NUT_STATEPATH` environment variable trumps both. [issue #694] * introduced a check for daemons working with PID files to double-check that if they can resolve the program name of a running process with this identifier, that such name matches the current program (avoid diff --git a/UPGRADING.adoc b/UPGRADING.adoc index c0b3c7a7cb..b3d5924cd9 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -110,6 +110,11 @@ Changes from 2.8.2 to 2.8.3 a Git workspace, not tarball archives) may have to set it to `false` when calling `make` for NUT. [#2510] +- Drivers should now be able to set `STATEPATH` via `ups.conf` to match `upsd` + custom configuration ability; in fact, the data server would prefer the + value from `ups.conf` over the one in `upsd.conf`, if both are present. + Note that `NUT_STATEPATH` environment variable trumps both. [issue #694] + - NUT products like `nut-scanner`, which dynamically load shared libraries at run-time without persistent pre-linking, should now know the library file names that were present during build (likely encumbered with version diff --git a/common/nutconf.cpp b/common/nutconf.cpp index 3f1eef9aef..776672a1c8 100644 --- a/common/nutconf.cpp +++ b/common/nutconf.cpp @@ -927,7 +927,7 @@ bool GenericConfiguration::writeTo(NutStream & ostream) const } -bool GenericConfiguration::get(const std::string & section, const std::string & entry, ConfigParamList & params) const +bool GenericConfiguration::get(const std::string & section, const std::string & entry, ConfigParamList & params, bool caseSensitive) const { // Get section SectionMap::const_iterator section_iter = sections.find(section); @@ -938,9 +938,22 @@ bool GenericConfiguration::get(const std::string & section, const std::string & const GenericConfigSection::EntryMap & entries = section_iter->second.entries; GenericConfigSection::EntryMap::const_iterator entry_iter = entries.find(entry); - if (entry_iter == entries.end()) + if (entry_iter == entries.end()) { + if (caseSensitive) + return false; + + // Another pass, maybe slower and inefficient, for case-insensitive matching + // We are already at one end of the entries, so scroll back to beginning + GenericConfigSection::EntryMap::const_iterator entry_begin = entries.begin(); + for (; entry_iter != entry_begin; entry_iter--) { + if (!(::strcasecmp(entry_iter->first.c_str(), entry.c_str()))) + goto found; + } + return false; + } +found: // Provide parameters values params = entry_iter->second.values; @@ -1025,13 +1038,13 @@ void GenericConfiguration::removeSection(const std::string & section) } -std::string GenericConfiguration::getStr(const std::string & section, const std::string & entry) const +std::string GenericConfiguration::getStr(const std::string & section, const std::string & entry, bool caseSensitive) const { std::string str; ConfigParamList params; - if (!get(section, entry, params)) + if (!get(section, entry, params, caseSensitive)) return str; if (params.empty()) diff --git a/conf/ups.conf.sample b/conf/ups.conf.sample index 6a7a72c8fd..b4c1da0921 100644 --- a/conf/ups.conf.sample +++ b/conf/ups.conf.sample @@ -67,6 +67,15 @@ # # driverpath: OPTIONAL. Used for custom setups. See man page for details. # +# statepath: OPTIONAL. Used for custom setups. Tell drivers to place their +# state sockets for communication with 'upsd' in 'path' rather +# than the default location that was compiled into the program. +# Note that the drivers must use the same path as 'upsd', so the +# data server would prefer this setting from `ups.conf` global +# section, if present, over its own in `upsd.conf`. +# Environment variable NUT_STATEPATH set by caller can override +# this setting. +# # nowait: OPTIONAL. Tell upsdrvctl to not wait at all for the driver(s) # to execute the requested command. Fire and forget. # diff --git a/conf/upsd.conf.sample b/conf/upsd.conf.sample index 8fe3d1333d..9497b1c63b 100644 --- a/conf/upsd.conf.sample +++ b/conf/upsd.conf.sample @@ -72,6 +72,9 @@ # # Tell upsd to look for the driver state sockets in 'path' rather # than the default that was compiled into the program. +# Note that the drivers must use the same path, so `upsd` would prefer the +# same-named setting from `ups.conf` global section, if present, over its own. +# Environment variable NUT_STATEPATH set by caller can override this setting. # ======================================================================= # LISTEN [] diff --git a/configure.ac b/configure.ac index c6672cba23..ffcfd9d480 100644 --- a/configure.ac +++ b/configure.ac @@ -5544,6 +5544,7 @@ AC_CONFIG_FILES([ scripts/python/module/setup.py scripts/upsdrvsvcctl/Makefile scripts/systemd/Makefile + scripts/systemd/nut.target scripts/systemd/nut-common-tmpfiles.conf scripts/systemd/nut-driver@.service scripts/systemd/nut-monitor.service diff --git a/docs/man/ups.conf.txt b/docs/man/ups.conf.txt index cf2020603a..d0c6424f95 100644 --- a/docs/man/ups.conf.txt +++ b/docs/man/ups.conf.txt @@ -68,6 +68,19 @@ Optional. Path name of the directory in which the UPS driver executables reside. If you don't specify this, the programs look in a built-in default directory, which is often /usr/local/ups/bin. +*statepath*:: + +Optional. Path name of the directory in which the UPS drivers should place +their state sockets for local communication with `upsd` data server, rather +than the default location that was compiled into the program, which is often +/var/state/ups. ++ +Note that the drivers must use the same path as `upsd`, so the data server +would prefer this setting from `ups.conf` global section, if present, over +its own in `upsd.conf`. ++ +Environment variable `NUT_STATEPATH` set by caller can override this setting. + *maxstartdelay*:: Optional. Same as the UPS field of the same name, but this is the diff --git a/docs/man/upsd.conf.txt b/docs/man/upsd.conf.txt index 4e8a9c855c..c1c1c26164 100644 --- a/docs/man/upsd.conf.txt +++ b/docs/man/upsd.conf.txt @@ -78,6 +78,12 @@ used by init-scripts and service unit method scripts. Tell upsd to look for the driver state sockets in 'path' rather than the default that was compiled into the program. ++ +Note that the drivers must use the same path, so `upsd` would prefer the +same-named setting from `ups.conf` global section, if present, over its own. ++ +Environment variable `NUT_STATEPATH` set by caller (e.g. init script +or service method) can override this setting. "LISTEN 'interface' 'port'":: diff --git a/drivers/main.c b/drivers/main.c index 23716c3385..24e0c7838c 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -1500,6 +1500,23 @@ static void do_global_args(const char *var, const char *val) return; } + /* Most ups.conf vars are lower-case, but this one + * is shared with upsd which uses upper-case. */ + if (!strcasecmp(var, "STATEPATH")) { + /* Is there a higher-priority envvar? */ + char *s = getenv("NUT_STATEPATH"); + if (s) { + if (strcmp(s, val)) { + upslogx(LOG_WARNING, "Environment variable NUT_STATEPATH='%s' overrides setting STATEPATH='%s'", s, val); + } /* else be quiet */ + } else { + /* Just let any further consumers of dflt_statepath() + * know the desired value with minimal codebase impact + */ + setenv("NUT_STATEPATH", val, 1); + } + } + /* unrecognized */ } diff --git a/include/nutconf.hpp b/include/nutconf.hpp index a3bdf7fdaf..dd1c141513 100644 --- a/include/nutconf.hpp +++ b/include/nutconf.hpp @@ -810,24 +810,26 @@ class GenericConfiguration : public BaseConfiguration, public Serialisable * \param[in] section Section name * \param[in] entry Entry name * \param[out] params Configuration parameters + * \param[in] caseSensitive Use case-sensitive entry name matching? (default: true) * * \retval true if the entry was found * \retval false otherwise */ - bool get(const std::string & section, const std::string & entry, ConfigParamList & params) const; + bool get(const std::string & section, const std::string & entry, ConfigParamList & params, bool caseSensitive = true) const; /** * \brief Global scope configuration parameters getter * * \param[in] entry Entry name * \param[out] params Configuration parameters + * \param[in] caseSensitive Use case-sensitive entry name matching? (default: true) * * \retval true if the entry was found * \retval false otherwise */ - inline bool get(const std::string & entry, ConfigParamList & params) const + inline bool get(const std::string & entry, ConfigParamList & params, bool caseSensitive = true) const { - return get("", entry, params); + return get("", entry, params, caseSensitive); } /** @@ -926,12 +928,34 @@ class GenericConfiguration : public BaseConfiguration, public Serialisable * * \param section Section name * \param entry Entry name + * \param caseSensitive Use case-sensitive entry name matching? (default: true) * * \return Configuration parameter as string */ std::string getStr( const std::string & section, - const std::string & entry) const; + const std::string & entry, + bool caseSensitive = true) const; + + /** + * \brief Configuration string getter + * + * Empty string is returned if the section or entry doesn't exist. + * + * \param section Section name + * \param entry Entry name (as C char array or string literal) + * \param caseSensitive Use case-sensitive entry name matching? (default: true) + * + * \return Configuration parameter as string + */ + std::string getStr( + const std::string & section, + const char * entry, + bool caseSensitive = true) const + { + std::string sEntry{entry}; + return getStr(section, sEntry, caseSensitive); + } /** * \brief Global scope configuration string getter @@ -939,12 +963,15 @@ class GenericConfiguration : public BaseConfiguration, public Serialisable * Empty string is returned if the entry doesn't exist. * * \param entry Entry name + * \param caseSensitive Use case-sensitive entry name matching? (default: true) * * \return Configuration parameter as string */ - inline std::string getStr(const std::string & entry) const + inline std::string getStr( + const std::string & entry, + bool caseSensitive = true) const { - return getStr("", entry); + return getStr("", entry, caseSensitive); } /** @@ -1666,6 +1693,7 @@ class UpsConfiguration : public GenericConfiguration inline std::string getChroot() const { return getStr("chroot"); } inline std::string getDriverPath() const { return getStr("driverpath"); } + inline std::string getStatePath() const { return getStr("statepath", false); } // NOTE: accept it case-insensitively inline std::string getGroup() const { return getStr("group"); } inline std::string getSynchronous() const { return getStr("synchronous"); } inline std::string getUser() const { return getStr("user"); } @@ -1682,6 +1710,7 @@ class UpsConfiguration : public GenericConfiguration inline void setChroot(const std::string & path) { setStr("chroot", path); } inline void setDriverPath(const std::string & path) { setStr("driverpath", path); } + inline void setStatePath(const std::string & path) { setStr("statepath", path); } inline void setGroup(const std::string & group) { setStr("group", group); } inline void setSynchronous(const std::string & val) { setStr("synchronous", val); } inline void setUser(const std::string & user) { setStr("user", user); } diff --git a/scripts/augeas/nutupsconf.aug.tpl b/scripts/augeas/nutupsconf.aug.tpl index 804ce9e17e..b193c4e84f 100644 --- a/scripts/augeas/nutupsconf.aug.tpl +++ b/scripts/augeas/nutupsconf.aug.tpl @@ -42,6 +42,8 @@ let ups_global = "chroot" | "user" | "group" | "debug_min" + | "STATEPATH" + | "statepath" (* This expression did involve a lot of courtship around the parser *) let ups_fields_re = /(default|override)\.[^:=#\r\t\n \/]+/ @@ -57,6 +59,7 @@ let ups_fields = "driver" | "user" | "group" | "debug_min" + | "LIBUSB_DEBUG" @SPECIFIC_DRV_VARS@ let ups_entry = IniFile.indented_entry (ups_global|ups_fields|ups_fields_re) ups_sep ups_comment diff --git a/scripts/systemd/.gitignore b/scripts/systemd/.gitignore index 560c249b43..e56937b1e7 100644 --- a/scripts/systemd/.gitignore +++ b/scripts/systemd/.gitignore @@ -1,6 +1,7 @@ # Note: nut-common-tmpfiles.conf.in is also generated, by configure script /nut-common-tmpfiles.conf.in /nut-common-tmpfiles.conf +/nut.target /nut-driver.service /nut-driver@.service /nut-monitor.service diff --git a/scripts/systemd/Makefile.am b/scripts/systemd/Makefile.am index e1a5fa5231..577456cb29 100644 --- a/scripts/systemd/Makefile.am +++ b/scripts/systemd/Makefile.am @@ -24,7 +24,7 @@ endif !WITH_LIBSYSTEMD_INHIBITOR systemdtmpfiles_DATA = \ nut-common-tmpfiles.conf -EXTRA_DIST += nut-driver.target nut.target nut-sleep.service +EXTRA_DIST += nut-driver.target nut-sleep.service systemdshutdown_SCRIPTS = nutshutdown @@ -35,7 +35,7 @@ sbin_SCRIPTS = ../upsdrvsvcctl/upsdrvsvcctl else !HAVE_SYSTEMD EXTRA_DIST += \ nut-driver@.service.in nut-monitor.service.in nut-sleep.service \ - nut-server.service.in nutshutdown.in nut-driver.target nut.target \ + nut-server.service.in nutshutdown.in nut-driver.target nut.target.in \ nut-driver-enumerator.path.in nut-driver-enumerator.service.in \ nut-driver-enumerator-daemon-activator.path.in \ nut-driver-enumerator-daemon-activator.service.in \ diff --git a/scripts/systemd/nut.target b/scripts/systemd/nut.target.in similarity index 100% rename from scripts/systemd/nut.target rename to scripts/systemd/nut.target.in diff --git a/server/conf.c b/server/conf.c index 3fa8393258..a40b09bcac 100644 --- a/server/conf.c +++ b/server/conf.c @@ -247,7 +247,7 @@ static int parse_upsd_conf_args(size_t numargs, char **arg) if (sp && strcmp(sp, arg[1])) { /* Only warn if the two strings are not equal */ upslogx(LOG_WARNING, - "Ignoring STATEPATH='%s' from configuration file, " + "Ignoring STATEPATH='%s' from upsd.conf configuration file, " "in favor of NUT_STATEPATH='%s' environment variable", NUT_STRARG(arg[1]), NUT_STRARG(sp)); } @@ -443,8 +443,26 @@ void do_upsconf_args(char *upsname, char *var, char *val) { ups_t *temp; - /* no "global" stuff for us */ + /* almost no "global" stuff for us */ if (!upsname) { + + /* STATEPATH (may be lower-case) */ + if (!strcasecmp(var, "STATEPATH")) { + const char *sp = getenv("NUT_STATEPATH"); + if (sp && strcmp(sp, val)) { + /* Only warn if the two strings are not equal */ + upslogx(LOG_WARNING, + "Ignoring STATEPATH='%s' from ups.conf configuration file, " + "in favor of NUT_STATEPATH='%s' environment variable", + NUT_STRARG(val), NUT_STRARG(sp)); + } + free(statepath); + statepath = xstrdup(sp ? sp : val); + /* This setting source keeps priority + * to best match up with the drivers */ + setenv("NUT_STATEPATH", statepath, 1); + } + return; }