diff --git a/ci_build.sh b/ci_build.sh index 3658a61555..f930c181a3 100755 --- a/ci_build.sh +++ b/ci_build.sh @@ -293,6 +293,19 @@ for L in $NODE_LABELS ; do "NUT_BUILD_CAPS=cppunit"|"NUT_BUILD_CAPS=cppunit=yes") [ -n "$CANBUILD_CPPUNIT_TESTS" ] || CANBUILD_CPPUNIT_TESTS=yes ;; + # Currently we don't build --with-nutconf" by default, unless desired + # explicitly by developers' local workflow (or NUT CI farm recipes), + # that codebase needs some clean-up first... This should cover both + # the tool and the cppunit tests for it (if active per above). + "NUT_BUILD_CAPS=nutconf=no") + [ -n "$CANBUILD_NUTCONF" ] || CANBUILD_NUTCONF=no ;; + "NUT_BUILD_CAPS=nutconf=no-gcc") + [ -n "$CANBUILD_NUTCONF" ] || CANBUILD_NUTCONF=no-gcc ;; + "NUT_BUILD_CAPS=nutconf=no-clang") + [ -n "$CANBUILD_NUTCONF" ] || CANBUILD_NUTCONF=no-clang ;; + "NUT_BUILD_CAPS=nutconf"|"NUT_BUILD_CAPS=nutconf=yes") + [ -n "$CANBUILD_NUTCONF" ] || CANBUILD_NUTCONF=yes ;; + # Some (QEMU) builders have issues running valgrind as a tool "NUT_BUILD_CAPS=valgrind=no") [ -n "$CANBUILD_VALGRIND_TESTS" ] || CANBUILD_VALGRIND_TESTS=no ;; @@ -1060,6 +1073,21 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp CONFIG_OPTS+=("--enable-cppunit=no") fi + if [ -z "${CANBUILD_NUTCONF-}" ] \ + || ( [ "${CANBUILD_NUTCONF-}" = "no-gcc" ] && [ "$COMPILER_FAMILY" = "GCC" ] ) \ + || ( [ "${CANBUILD_NUTCONF-}" = "no-clang" ] && [ "$COMPILER_FAMILY" = "CLANG" ] ) \ + ; then + CANBUILD_NUTCONF=no + fi + + if [ "${CANBUILD_NUTCONF-}" = yes ] ; then + echo "WARNING: Build agent says it can build nutconf, enabling the experimental feature" >&2 + CONFIG_OPTS+=("--with-nutconf=yes") + elif [ "${CANBUILD_NUTCONF-}" = no ] ; then + echo "WARNING: Build agent says it can not build nutconf (or did not say anything), disabling the feature" >&2 + CONFIG_OPTS+=("--with-nutconf=no") + fi + if [ "${CANBUILD_VALGRIND_TESTS-}" = no ] ; then echo "WARNING: Build agent says it has a broken valgrind, adding configure option to skip tests with it" >&2 CONFIG_OPTS+=("--with-valgrind=no") diff --git a/common/Makefile.am b/common/Makefile.am index 603fd3873f..7620ead426 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -9,13 +9,22 @@ @NUT_AM_MAKE_CAN_EXPORT@export CCACHE_PATH=@CCACHE_PATH@ @NUT_AM_MAKE_CAN_EXPORT@export PATH=@PATH_DURING_CONFIGURE@ -AM_CFLAGS = -I$(top_srcdir)/include +AM_CFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include +AM_CXXFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include AM_LDFLAGS = -no-undefined EXTRA_DIST = noinst_LTLIBRARIES = libparseconf.la libcommon.la libcommonclient.la +if WITH_NUTCONF +# We define the recipe below in any case, but only activate it by default +# if the build configuration tells us to: +noinst_LTLIBRARIES += libnutconf.la +endif + libparseconf_la_SOURCES = parseconf.c +libnutconf_la_SOURCES = nutconf.cpp nutstream.cpp nutwriter.cpp nutipc.cpp + # do not hard depend on '../include/nut_version.h', since it blocks # 'dist', and is only required for actual build, in which case # BUILT_SOURCES (in ../include) will ensure nut_version.h will diff --git a/common/nutconf.cpp b/common/nutconf.cpp new file mode 100644 index 0000000000..6f6b884aad --- /dev/null +++ b/common/nutconf.cpp @@ -0,0 +1,1602 @@ +/* + nutconf.cpp - configuration API + + Copyright (C) + 2012 Emilien Kia + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "nutconf.hpp" +#include "nutwriter.hpp" + +#include +#include +#include + +#include +#include +#include + + +namespace nut { + +/* Trivial implementations out of class declaration to avoid + * error: 'ClassName' has no out-of-line virtual method definitions; its vtable + * will be emitted in every translation unit [-Werror,-Wweak-vtables] + */ +Serialisable::~Serialisable() {} +BaseConfiguration::~BaseConfiguration() {} +GenericConfiguration::~GenericConfiguration() {} +UpsConfiguration::~UpsConfiguration() {} +NutParser::~NutParser() {} + +// +// Tool functions +// + +/** + * Parse a specified type from a string and set it as Settable if success. + */ +template +Settable StringToSettableNumber(const std::string & src) +{ + std::stringstream ss(src); + T result; + if(ss >> result) + { + return Settable(result); + } + else + { + return Settable(); + } +} + + +// +// NutParser +// + +NutParser::NutParser(const char* buffer, unsigned int options) : +_options(options), +_buffer(buffer), +_pos(0) { +} + +NutParser::NutParser(const std::string& buffer, unsigned int options) : +_options(options), +_buffer(buffer), +_pos(0) { +} + +void NutParser::setOptions(unsigned int options, bool set) +{ + if(set) + { + _options |= options; + } + else + { + _options &= ~options; + } +} + +char NutParser::get() { + if (_pos >= _buffer.size()) + return 0; + else + return _buffer[_pos++]; +} + +char NutParser::peek() { + return _buffer[_pos]; +} + +size_t NutParser::getPos()const { + return _pos; +} + +void NutParser::setPos(size_t pos) { + _pos = pos; +} + +char NutParser::charAt(size_t pos)const { + return _buffer[pos]; +} + +void NutParser::pushPos() { + _stack.push_back(_pos); +} + +size_t NutParser::popPos() { + size_t pos = _stack.back(); + _stack.pop_back(); + return pos; +} + +void NutParser::rewind() { + _pos = popPos(); +} + +void NutParser::back() { + if (_pos > 0) + --_pos; +} + +/* Parse a string source for a CHARS and return its size if found or 0, if not. + * CHARS ::= CHAR+ + * CHAR ::= __ASCIICHAR__ - ( __SPACES__ | '\\' | '\"' | '#' ) + * | '\\' ( __SPACES__ | '\\' | '\"' | '#' ) + * TODO: accept "\t", "\s", "\r", "\n" ?? + */ +std::string NutParser::parseCHARS() { + bool escaped = false; // Is char escaped ? + std::string res; // Stored string + + pushPos(); + + for (char c = get(); c != 0 /*EOF*/; c = get()) { + if (escaped) { + if (isspace(c) || c == '\\' || c == '"' || c == '#') { + res += c; + } else { + /* WTF ??? */ + } + escaped = false; + } else { + if (c == '\\') { + escaped = true; + } else if (isgraph(c) /*&& c != '\\'*/ && c != '"' && c != '#') { + res += c; + } else { + back(); + break; + } + } + } + + popPos(); + return res; +} + +/* Parse a string source for a STRCHARS and return its size if found or 0, if not. + * STRCHARS ::= STRCHAR+ + * STRCHAR ::= __ASCIICHAR__ - ( '\\' | '\"') + * | '\\' ( '\\' | '\"' ) + * TODO: accept "\t", "\s", "\r", "\n" ?? + */ +std::string NutParser::parseSTRCHARS() { + bool escaped = false; // Is char escaped ? + std::string str; // Stored string + + pushPos(); + + for (char c = get(); c != 0 /*EOF*/; c = get()) { + if (escaped) { + if (isspace(c) || c == '\\' || c == '"') { + str += c; + } else { + /* WTF ??? */ + } + escaped = false; + } else { + if (c == '\\') { + escaped = true; + } else if (isprint(c) && c != '\\' && c != '"') { + str += c; + } else { + back(); + break; + } + } + } + + popPos(); + return str; +} + +/** Parse a string source for getting the next token, ignoring spaces. + * \return Token type. + */ +NutParser::Token NutParser::parseToken() { + + /** Lexical parsing machine state enumeration.*/ + typedef enum { + LEXPARSING_STATE_DEFAULT, + LEXPARSING_STATE_QUOTED_STRING, + LEXPARSING_STATE_STRING, + LEXPARSING_STATE_COMMENT + } LEXPARSING_STATE_e; + LEXPARSING_STATE_e state = LEXPARSING_STATE_DEFAULT; + + Token token; + bool escaped = false; + + pushPos(); + + for (char c = get(); c != 0 /*EOF*/; c = get()) { + switch (state) { + case LEXPARSING_STATE_DEFAULT: /* Wait for a non-space char */ + { + if (c == ' ' || c == '\t') { + /* Space : do nothing */ + } else if (c == '[') { + token = Token(Token::TOKEN_BRACKET_OPEN, c); + popPos(); + return token; + } else if (c == ']') { + token = Token(Token::TOKEN_BRACKET_CLOSE, c); + popPos(); + return token; + } else if (c == ':' && !hasOptions(OPTION_IGNORE_COLON)) { + token = Token(Token::TOKEN_COLON, c); + popPos(); + return token; + } else if (c == '=') { + token = Token(Token::TOKEN_EQUAL, c); + popPos(); + return token; + } else if (c == '\r' || c == '\n') { + token = Token(Token::TOKEN_EOL, c); + popPos(); + return token; + } else if (c == '#') { + token.type = Token::TOKEN_COMMENT; + state = LEXPARSING_STATE_COMMENT; + } else if (c == '"') { + /* Begin of QUOTED STRING */ + token.type = Token::TOKEN_QUOTED_STRING; + state = LEXPARSING_STATE_QUOTED_STRING; + } else if (c == '\\') { + /* Begin of STRING with escape */ + token.type = Token::TOKEN_STRING; + state = LEXPARSING_STATE_STRING; + escaped = true; + } else if (isgraph(c)) { + /* Begin of STRING */ + token.type = Token::TOKEN_STRING; + state = LEXPARSING_STATE_STRING; + token.str += c; + } else { + rewind(); + return Token(Token::TOKEN_UNKNOWN); + } + break; + } + case LEXPARSING_STATE_QUOTED_STRING: + { + if (c == '"') { + if (escaped) { + escaped = false; + token.str += '"'; + } else { + popPos(); + return token; + } + } else if (c == '\\') { + if (escaped) { + escaped = false; + token.str += '\\'; + } else { + escaped = true; + } + } else if (c == ' ' || c == '\t' || isgraph(c)) { + token.str += c; + } else if (c == '\r' || c == '\n') /* EOL */{ + /* WTF ? consider it as correct ? */ + back(); + popPos(); + return token; + } else if (c == 0) /* EOF */ { + popPos(); + return token; + } else /* Bad character ?? */ { + /* WTF ? Keep, Ignore ? */ + } + /* TODO What about other escaped character ? */ + break; + } + case LEXPARSING_STATE_STRING: + { + if (c == ' ' || c == '\t' || c == '"' || c == '#' || c == '[' || c == ']' + || (c == ':' && !hasOptions(OPTION_IGNORE_COLON)) + || c == '=' + ) { + if (escaped) { + escaped = false; + token.str += c; + } else { + back(); + popPos(); + return token; + } + } else if (c == '\\') { + if (escaped) { + escaped = false; + token.str += c; + } else { + escaped = true; + } + } else if (c == '\r' || c == '\n') /* EOL */{ + back(); + popPos(); + return token; + } else if (c == 0) /* EOF */ { + popPos(); + return token; + }else if (isgraph(c)) { + token.str += c; + } else /* Bad character ?? */ { + /* WTF ? Keep, Ignore ? */ + } + /* TODO What about escaped character ? */ + break; + } + case LEXPARSING_STATE_COMMENT: + { + if (c == '\r' || c == '\n') { + return token; + } else { + token.str += c; + } + break; + } + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#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 "-Wcovered-switch-default" +#endif + default: + /* Must not occur. */ + break; +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) +# pragma GCC diagnostic pop +#endif + } + } + popPos(); + return token; +} + +std::list NutParser::parseLine() { + std::list res; + + while (true) { + NutParser::Token token = parseToken(); + + switch (token.type) { + case Token::TOKEN_STRING: + case Token::TOKEN_QUOTED_STRING: + case Token::TOKEN_BRACKET_OPEN: + case Token::TOKEN_BRACKET_CLOSE: + case Token::TOKEN_EQUAL: + case Token::TOKEN_COLON: + res.push_back(token); + break; + case Token::TOKEN_COMMENT: + res.push_back(token); + // Should return (EOL)Token::TOKEN_COMMENT: + return res; + case Token::TOKEN_UNKNOWN: + case Token::TOKEN_NONE: + case Token::TOKEN_EOL: + return res; + } + } +} + +// +// NutConfigParser +// + +NutConfigParser::NutConfigParser(const char* buffer, unsigned int options) : +NutParser(buffer, options) { +} + +NutConfigParser::NutConfigParser(const std::string& buffer, unsigned int options) : +NutParser(buffer, options) { +} + +void NutConfigParser::parseConfig(BaseConfiguration* config) { + NUT_UNUSED_VARIABLE(config); + parseConfig(); +} + +void NutConfigParser::parseConfig() { + onParseBegin(); + + enum ConfigParserState { + CPS_DEFAULT, + CPS_SECTION_OPENED, + CPS_SECTION_HAVE_NAME, + CPS_SECTION_CLOSED, + CPS_DIRECTIVE_HAVE_NAME, + CPS_DIRECTIVE_VALUES + } state = CPS_DEFAULT; + + Token tok; + std::string name; + std::list values; + char sep = 0; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#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 "-Wcovered-switch-default" +#endif + while (1) { + tok = parseToken(); + if (!tok) + break; + switch (state) { + case CPS_DEFAULT: + switch (tok.type) { + case Token::TOKEN_COMMENT: + onParseComment(tok.str); + /* Clean and return to default */ + break; + case Token::TOKEN_BRACKET_OPEN: + state = CPS_SECTION_OPENED; + break; + case Token::TOKEN_STRING: + case Token::TOKEN_QUOTED_STRING: + name = tok.str; + state = CPS_DIRECTIVE_HAVE_NAME; + break; + + case Token::TOKEN_UNKNOWN: + case Token::TOKEN_NONE: + case Token::TOKEN_BRACKET_CLOSE: + case Token::TOKEN_EQUAL: + case Token::TOKEN_COLON: + case Token::TOKEN_EOL: + default: + /* WTF ? */ + break; + } + break; + case CPS_SECTION_OPENED: + switch (tok.type) { + case Token::TOKEN_STRING: + case Token::TOKEN_QUOTED_STRING: + /* Should occur ! */ + name = tok.str; + state = CPS_SECTION_HAVE_NAME; + break; + case Token::TOKEN_BRACKET_CLOSE: + /* Empty section name */ + state = CPS_SECTION_CLOSED; + break; + case Token::TOKEN_COMMENT: + /* Lack of closing bracket !!! */ + onParseSectionName(name, tok.str); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + case Token::TOKEN_EOL: + /* Lack of closing bracket !!! */ + onParseSectionName(name); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + + case Token::TOKEN_UNKNOWN: + case Token::TOKEN_NONE: + case Token::TOKEN_BRACKET_OPEN: + case Token::TOKEN_EQUAL: + case Token::TOKEN_COLON: + default: + /* WTF ? */ + break; + } + break; + case CPS_SECTION_HAVE_NAME: + switch (tok.type) { + case Token::TOKEN_BRACKET_CLOSE: + /* Must occur ! */ + state = CPS_SECTION_CLOSED; + break; + case Token::TOKEN_COMMENT: + /* Lack of closing bracket !!! */ + onParseSectionName(name, tok.str); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + case Token::TOKEN_EOL: + /* Lack of closing bracket !!! */ + onParseSectionName(name); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + + case Token::TOKEN_QUOTED_STRING: + case Token::TOKEN_BRACKET_OPEN: + case Token::TOKEN_COLON: + case Token::TOKEN_EQUAL: + case Token::TOKEN_UNKNOWN: + case Token::TOKEN_NONE: + case Token::TOKEN_STRING: + default: + /* WTF ? */ + break; + } + break; + case CPS_SECTION_CLOSED: + switch (tok.type) { + case Token::TOKEN_COMMENT: + /* Could occur ! */ + onParseSectionName(name, tok.str); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + case Token::TOKEN_EOL: + /* Could occur ! */ + onParseSectionName(name); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + + case Token::TOKEN_QUOTED_STRING: + case Token::TOKEN_BRACKET_OPEN: + case Token::TOKEN_BRACKET_CLOSE: + case Token::TOKEN_UNKNOWN: + case Token::TOKEN_NONE: + case Token::TOKEN_STRING: + case Token::TOKEN_COLON: + case Token::TOKEN_EQUAL: + default: + /* WTF ? */ + break; + } + break; + case CPS_DIRECTIVE_HAVE_NAME: + switch (tok.type) { + case Token::TOKEN_COMMENT: + /* Could occur ! */ + onParseDirective(name, 0, std::list (), tok.str); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + case Token::TOKEN_EOL: + /* Could occur ! */ + onParseDirective(name); + /* Clean and return to default */ + name.clear(); + state = CPS_DEFAULT; + break; + case Token::TOKEN_COLON: + case Token::TOKEN_EQUAL: + /* Could occur ! */ + sep = tok.str[0]; + state = CPS_DIRECTIVE_VALUES; + break; + case Token::TOKEN_STRING: + case Token::TOKEN_QUOTED_STRING: + /* Could occur ! */ + values.push_back(tok.str); + state = CPS_DIRECTIVE_VALUES; + break; + + case Token::TOKEN_UNKNOWN: + case Token::TOKEN_NONE: + case Token::TOKEN_BRACKET_OPEN: + case Token::TOKEN_BRACKET_CLOSE: + default: + /* WTF ? */ + break; + } + break; + case CPS_DIRECTIVE_VALUES: + switch (tok.type) { + case Token::TOKEN_COMMENT: + /* Could occur ! */ + onParseDirective(name, sep, values, tok.str); + /* Clean and return to default */ + name.clear(); + values.clear(); + sep = 0; + state = CPS_DEFAULT; + break; + case Token::TOKEN_EOL: + /* Could occur ! */ + onParseDirective(name, sep, values); + /* Clean and return to default */ + name.clear(); + values.clear(); + sep = 0; + state = CPS_DEFAULT; + break; + case Token::TOKEN_STRING: + case Token::TOKEN_QUOTED_STRING: + /* Could occur ! */ + values.push_back(tok.str); + state = CPS_DIRECTIVE_VALUES; + break; + + case Token::TOKEN_UNKNOWN: + case Token::TOKEN_NONE: + case Token::TOKEN_BRACKET_OPEN: + case Token::TOKEN_BRACKET_CLOSE: + case Token::TOKEN_EQUAL: + case Token::TOKEN_COLON: + default: + /* WTF ? */ + break; + } + break; + } + } + + switch(state) + { + case CPS_SECTION_OPENED: + case CPS_SECTION_HAVE_NAME: + /* Lack of closing bracket !!! */ + onParseSectionName(name); + break; + case CPS_SECTION_CLOSED: + /* Could occur ! */ + onParseSectionName(name); + break; + case CPS_DIRECTIVE_HAVE_NAME: + /* Could occur ! */ + onParseDirective(name); + break; + case CPS_DIRECTIVE_VALUES: + /* Could occur ! */ + onParseDirective(name, sep, values); + break; + case CPS_DEFAULT: + /* TOTHINK: no-op? */ + break; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) +# pragma GCC diagnostic pop +#endif + + onParseEnd(); +} + + + +// +// DefaultConfigParser +// + +DefaultConfigParser::DefaultConfigParser(const char* buffer) : +NutConfigParser(buffer) { +} + +DefaultConfigParser::DefaultConfigParser(const std::string& buffer) : +NutConfigParser(buffer) { +} + +void DefaultConfigParser::onParseBegin() { + // Start with empty section (i.e. global one) + _section.clear(); +} + +void DefaultConfigParser::onParseComment(const std::string& /*comment*/) { + // Comments are ignored for now +} + +void DefaultConfigParser::onParseSectionName(const std::string& sectionName, const std::string& /*comment*/) { + // Comments are ignored for now + + // Process current section. + if (!_section.empty()) { + onParseSection(_section); + _section.clear(); + } + + // Start a new section + _section.name = sectionName; +} + +void DefaultConfigParser::onParseDirective(const std::string& directiveName, char /*sep*/, const ConfigParamList& values, const std::string& /*comment*/) { + // Comments are ignored for now + // Separator has no specific semantic in this context + + // Save values + _section.entries[directiveName].name = directiveName; + _section.entries[directiveName].values = values; + + // TODO Can probably be optimized. +} + +void DefaultConfigParser::onParseEnd() { + // Process current (last) section + if (!_section.empty()) { + onParseSection(_section); + _section.clear(); + } +} + + +// +// GenericConfigSection +// + +bool GenericConfigSection::empty()const { + return name.empty() && entries.empty(); +} + +void GenericConfigSection::clear() { + name.clear(); + entries.clear(); +} + +// +// GenericConfigParser +// + +GenericConfigParser::GenericConfigParser(const char* buffer): +DefaultConfigParser(buffer), +_config(nullptr) +{ +} + +GenericConfigParser::GenericConfigParser(const std::string& buffer): +DefaultConfigParser(buffer), +_config(nullptr) +{ +} + +void GenericConfigParser::parseConfig(BaseConfiguration* config) +{ + if(config!=nullptr) + { + _config = config; + NutConfigParser::parseConfig(); + _config = nullptr; + } +} + + +void GenericConfigParser::onParseSection(const GenericConfigSection& section) +{ + if(_config!=nullptr) + { + _config->setGenericConfigSection(section); + } +} + +// +// GenericConfiguration +// + +void GenericConfiguration::setGenericConfigSection(const GenericConfigSection& section) +{ + sections[section.name] = section; +} + + +void GenericConfiguration::parseFromString(const std::string& str) +{ + GenericConfigParser parser(str); + parser.parseConfig(this); +} + + +bool GenericConfiguration::parseFrom(NutStream & istream) +{ + // TODO: The parser is highly inefficient, it should use NutStream, directly + std::string str; + + if (NutStream::NUTS_OK != istream.getString(str)) + return false; + + parseFromString(str); + + return true; +} + + +bool GenericConfiguration::writeTo(NutStream & ostream) const +{ + GenericConfigWriter writer(ostream); + + return NutWriter::NUTW_OK == writer.writeConfig(*this); +} + + +bool GenericConfiguration::get(const std::string & section, const std::string & entry, ConfigParamList & params) const +{ + // Get section + SectionMap::const_iterator section_iter = sections.find(section); + if (section_iter == sections.end()) + return false; + + // Get entry + const GenericConfigSection::EntryMap & entries = section_iter->second.entries; + + GenericConfigSection::EntryMap::const_iterator entry_iter = entries.find(entry); + if (entry_iter == entries.end()) + return false; + + // Provide parameters values + params = entry_iter->second.values; + + return true; +} + + +void GenericConfiguration::set(const std::string & section, const std::string & entry, const ConfigParamList & params) +{ + // Get section + SectionMap::iterator section_iter = sections.lower_bound(section); + if (sections.end() == section_iter || section_iter->first != section) { + section_iter = sections.insert(section_iter, + std::pair(section, GenericConfigSection())); + + section_iter->second.name = section; + } + + // Get entry + GenericConfigSection::EntryMap & entries = section_iter->second.entries; + + GenericConfigSection::EntryMap::iterator entry_iter = entries.lower_bound(entry); + if (entries.end() == entry_iter || entry_iter->first != entry) { + entry_iter = entries.insert(entry_iter, + std::pair(entry, GenericConfigSectionEntry())); + + entry_iter->second.name = entry; + } + + // Set parameters values + entry_iter->second.values = params; +} + + +void GenericConfiguration::add(const std::string & section, const std::string & entry, const ConfigParamList & params) +{ + // No job is another job well done + if (params.empty()) + return; + + // Note that the implementation is quite naive and inefficient. + // However, efficiency isn't our aim at the moment. + + // Get current parameters + ConfigParamList current_params; + + get(section, entry, current_params); + + // Add the provided parameters + current_params.insert(current_params.end(), params.begin(), params.end()); + + set(section, entry, current_params); +} + + +void GenericConfiguration::remove(const std::string & section, const std::string & entry) +{ + // Get section + SectionMap::iterator section_iter = sections.find(section); + if (sections.end() == section_iter) + return; + + // Get entry + GenericConfigSection::EntryMap & entries = section_iter->second.entries; + + GenericConfigSection::EntryMap::iterator entry_iter = entries.find(entry); + if (entries.end() == entry_iter) + return; + + entries.erase(entry_iter); +} + + +void GenericConfiguration::removeSection(const std::string & section) +{ + // Get section + SectionMap::iterator section_iter = sections.find(section); + if (sections.end() == section_iter) + return; + + sections.erase(section_iter); +} + + +std::string GenericConfiguration::getStr(const std::string & section, const std::string & entry) const +{ + std::string str; + + ConfigParamList params; + + if (!get(section, entry, params)) + return str; + + if (params.empty()) + return str; + + return params.front(); +} + + +void GenericConfiguration::setStr( + const std::string & section, + const std::string & entry, + const std::string & value) +{ + ConfigParamList param; + + param.push_back(value); + + set(section, entry, param); +} + + +long long int GenericConfiguration::getInt(const std::string & section, const std::string & entry, long long int val) const +{ + ConfigParamList params; + + if (!get(section, entry, params)) + return val; + + if (params.empty()) + return val; + + // TBD: What if there are multiple values? + std::stringstream val_str(params.front()); + + val_str >> val; + + return val; +} + + +void GenericConfiguration::setInt(const std::string & section, const std::string & entry, long long int val) +{ + std::stringstream val_str; + val_str << val; + + set(section, entry, ConfigParamList(1, val_str.str())); +} + + +bool GenericConfiguration::str2bool(const std::string & str) +{ + if ("true" == str) return true; + if ("on" == str) return true; + if ("1" == str) return true; + if ("yes" == str) return true; + if ("ok" == str) return true; + + return false; +} + + +const std::string & GenericConfiguration::bool2str(bool val) +{ + static const std::string b0("off"); + static const std::string b1("on"); + + return val ? b1 : b0; +} + + +// +// UpsmonConfiguration +// + +UpsmonConfiguration::UpsmonConfiguration() +{ +} + +void UpsmonConfiguration::parseFromString(const std::string& str) +{ + UpsmonConfigParser parser(str); + parser.parseUpsmonConfig(this); +} + +UpsmonConfiguration::NotifyFlag UpsmonConfiguration::NotifyFlagFromString(const std::string& str) +{ + if(str=="SYSLOG") + return NOTIFY_SYSLOG; + else if(str=="WALL") + return NOTIFY_WALL; + else if(str=="EXEC") + return NOTIFY_EXEC; + else if(str=="IGNORE") + return NOTIFY_IGNORE; + else + return NOTIFY_IGNORE; +} + +UpsmonConfiguration::NotifyType UpsmonConfiguration::NotifyTypeFromString(const std::string& str) +{ + if(str=="ONLINE") + return NOTIFY_ONLINE; + else if(str=="ONBATT") + return NOTIFY_ONBATT; + else if(str=="LOWBATT") + return NOTIFY_LOWBATT; + else if(str=="FSD") + return NOTIFY_FSD; + else if(str=="COMMOK") + return NOTIFY_COMMOK; + else if(str=="COMMBAD") + return NOTIFY_COMMBAD; + else if(str=="SHUTDOWN") + return NOTIFY_SHUTDOWN; + else if(str=="REPLBATT") + return NOTIFY_REPLBATT; + else if(str=="NOCOMM") + return NOTIFY_NOCOMM; + else if(str=="NOPARENT") + return NOTIFY_NOPARENT; + else + return NOTIFY_TYPE_MAX; +} + + +bool UpsmonConfiguration::parseFrom(NutStream & istream) +{ + // TODO: The parser is highly inefficient, it should use NutStream, directly + std::string str; + + if (NutStream::NUTS_OK != istream.getString(str)) + return false; + + parseFromString(str); + + return true; +} + + +bool UpsmonConfiguration::writeTo(NutStream & ostream) const +{ + UpsmonConfigWriter writer(ostream); + + return NutWriter::NUTW_OK == writer.writeConfig(*this); +} + + +// +// UpsmonConfigParser +// + +UpsmonConfigParser::UpsmonConfigParser(const char* buffer): +NutConfigParser(buffer) +{ +} + +UpsmonConfigParser::UpsmonConfigParser(const std::string& buffer): +NutConfigParser(buffer) +{ +} + +void UpsmonConfigParser::parseUpsmonConfig(UpsmonConfiguration* config) +{ + if(config!=nullptr) + { + _config = config; + NutConfigParser::parseConfig(); + _config = nullptr; + } +} + +void UpsmonConfigParser::onParseBegin() +{ + // Do nothing +} + +void UpsmonConfigParser::onParseComment(const std::string& /*comment*/) +{ + // Comments are ignored for now +} + +void UpsmonConfigParser::onParseSectionName(const std::string& /*sectionName*/, const std::string& /*comment*/) +{ + // There must not be sections in upsmon.conf. + // Ignore it + // TODO Add error reporting ? +} + + +void UpsmonConfigParser::onParseDirective(const std::string& directiveName, char /*sep*/, const ConfigParamList& values, const std::string& /*comment*/) +{ + // NOTE: separators are always ignored + + if(_config) + { + if(directiveName == "RUN_AS_USER") + { + if(values.size()>0) + { + _config->runAsUser = values.front(); + } + } + else if(directiveName == "MONITOR") + { + if (values.size() == 5 || values.size() == 6) + { + UpsmonConfiguration::Monitor monitor; + ConfigParamList::const_iterator it = values.begin(); + std::stringstream system(*it++); + std::string word; + /* + * Why didn't the original author just receive the words + * into their target strings?.. e.g.: + * std::getline(system, monitor.upsname, '@'); + * std::getline(system, monitor.hostname); + */ + monitor.upsname = (static_cast(std::getline(system, word, '@')), word); + monitor.hostname = (static_cast(std::getline(system, word)), word); + monitor.port = (values.size() == 6 ? *StringToSettableNumber(*it++) : 0u); + monitor.powerValue = StringToSettableNumber(*it++); + monitor.username = *it++; + monitor.password = *it++; + monitor.isMaster = (*it) == "master"; + _config->monitors.push_back(monitor); + } + } + else if(directiveName == "MINSUPPLIES") + { + if(values.size()>0) + { + _config->minSupplies = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "SHUTDOWNCMD") + { + if(values.size()>0) + { + _config->shutdownCmd = values.front(); + } + } + else if(directiveName == "NOTIFYCMD") + { + if(values.size()>0) + { + _config->notifyCmd = values.front(); + } + } + else if(directiveName == "POLLFREQ") + { + if(values.size()>0) + { + _config->poolFreq = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "POLLFREQALERT") + { + if(values.size()>0) + { + _config->poolFreqAlert = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "HOSTSYNC") + { + if(values.size()>0) + { + _config->hotSync = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "DEADTIME") + { + if(values.size()>0) + { + _config->deadTime = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "POWERDOWNFLAG") + { + if(values.size()>0) + { + _config->powerDownFlag = values.front(); + } + } + else if(directiveName == "NOTIFYMSG") + { + if(values.size()==2) + { + UpsmonConfiguration::NotifyType type = UpsmonConfiguration::NotifyTypeFromString(values.front()); + if(type!=UpsmonConfiguration::NOTIFY_TYPE_MAX) + { + _config->notifyMessages[static_cast(type)] = *(++values.begin()); + } + } + } + else if(directiveName == "NOTIFYFLAG") + { + if(values.size()==2) + { + UpsmonConfiguration::NotifyType type = UpsmonConfiguration::NotifyTypeFromString(values.front()); + if(type!=UpsmonConfiguration::NOTIFY_TYPE_MAX) + { + unsigned int flags = 0; + std::string word; + std::stringstream stream(*(++values.begin())); + while( std::getline(stream, word, '+') ) + { + flags |= UpsmonConfiguration::NotifyFlagFromString(word); + } + _config->notifyFlags[static_cast(type)] = flags; + } + } + } + else if(directiveName == "RBWARNTIME") + { + if(values.size()>0) + { + _config->rbWarnTime = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "NOCOMMWARNTIME") + { + if(values.size()>0) + { + _config->noCommWarnTime = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "FINALDELAY") + { + if(values.size()>0) + { + _config->finalDelay = StringToSettableNumber(values.front()); + } + } + else + { + // TODO WTF with unknown commands ? + } + } +} + +void UpsmonConfigParser::onParseEnd() +{ + // Do nothing +} + +// +// NutConfiguration +// + +NutConfiguration::NutConfiguration(): + mode(MODE_UNKNOWN) +{ +} + +void NutConfiguration::parseFromString(const std::string& str) +{ + NutConfConfigParser parser(str); + parser.parseNutConfConfig(this); +} + +NutConfiguration::NutMode NutConfiguration::NutModeFromString(const std::string& str) +{ + if(str == "none") + return MODE_NONE; + else if(str == "standalone") + return MODE_STANDALONE; + else if(str == "netserver") + return MODE_NETSERVER; + else if(str == "netclient") + return MODE_NETCLIENT; + else if(str == "controlled") + return MODE_CONTROLLED; + else if(str == "manual") + return MODE_MANUAL; + else + return MODE_UNKNOWN; +} + + +bool NutConfiguration::parseFrom(NutStream & istream) +{ + // TODO: The parser is highly inefficient, it should use NutStream, directly + std::string str; + + if (NutStream::NUTS_OK != istream.getString(str)) + return false; + + parseFromString(str); + + return true; +} + + +bool NutConfiguration::writeTo(NutStream & ostream) const +{ + NutConfConfigWriter writer(ostream); + + return NutWriter::NUTW_OK == writer.writeConfig(*this); +} + + +// +// NutConfConfigParser +// + +NutConfConfigParser::NutConfConfigParser(const char* buffer): +NutConfigParser(buffer) +{ +} + +NutConfConfigParser::NutConfConfigParser(const std::string& buffer): +NutConfigParser(buffer) +{ +} + +void NutConfConfigParser::parseNutConfConfig(NutConfiguration* config) +{ + if(config!=nullptr) + { + _config = config; + NutConfigParser::parseConfig(); + _config = nullptr; + } +} + +void NutConfConfigParser::onParseBegin() +{ + // Do nothing +} + +void NutConfConfigParser::onParseComment(const std::string& /*comment*/) +{ + // Comments are ignored for now +} + +void NutConfConfigParser::onParseSectionName(const std::string& /*sectionName*/, const std::string& /*comment*/) +{ + // There must not be sections in nutconf.conf. + // Ignore it + // TODO Add error reporting ? +} + +void NutConfConfigParser::onParseDirective(const std::string& directiveName, char /*sep*/, const ConfigParamList& values, const std::string& /*comment*/) +{ + // Comments are ignored for now + // NOTE: although sep must be '=', sep is not verified. + if(_config && directiveName=="MODE" && values.size()==1) + { + std::string val = values.front(); + NutConfiguration::NutMode mode = NutConfiguration::NutModeFromString(val); + if(mode != NutConfiguration::MODE_UNKNOWN) + _config->mode = mode; + } + else + { + // TODO WTF with errors ? + } +} + +void NutConfConfigParser::onParseEnd() +{ + // Do nothing +} + + +// +// UpsdConfiguration +// + +UpsdConfiguration::UpsdConfiguration() +{ +} + +void UpsdConfiguration::parseFromString(const std::string& str) +{ + UpsdConfigParser parser(str); + parser.parseUpsdConfig(this); +} + + +bool UpsdConfiguration::parseFrom(NutStream & istream) +{ + // TODO: The parser is highly inefficient, it should use NutStream, directly + std::string str; + + if (NutStream::NUTS_OK != istream.getString(str)) + return false; + + parseFromString(str); + + return true; +} + + +bool UpsdConfiguration::writeTo(NutStream & ostream) const +{ + UpsdConfigWriter writer(ostream); + + return NutWriter::NUTW_OK == writer.writeConfig(*this); +} + + +// +// UpsdConfigParser +// + +UpsdConfigParser::UpsdConfigParser(const char* buffer): +NutConfigParser(buffer, NutParser::OPTION_IGNORE_COLON) +{ +} + +UpsdConfigParser::UpsdConfigParser(const std::string& buffer): +NutConfigParser(buffer, NutParser::OPTION_IGNORE_COLON) +{ +} + +void UpsdConfigParser::parseUpsdConfig(UpsdConfiguration* config) +{ + if(config!=nullptr) + { + _config = config; + NutConfigParser::parseConfig(); + _config = nullptr; + } +} + +void UpsdConfigParser::onParseBegin() +{ + // Do nothing +} + +void UpsdConfigParser::onParseComment(const std::string& comment) +{ + // Comments are ignored for now + NUT_UNUSED_VARIABLE(comment); +} + +void UpsdConfigParser::onParseSectionName(const std::string& sectionName, const std::string& comment) +{ + // There must not be sections in upsd.conf. + // Ignore it + // TODO Add error reporting ? + NUT_UNUSED_VARIABLE(sectionName); + NUT_UNUSED_VARIABLE(comment); +} + +void UpsdConfigParser::onParseDirective(const std::string& directiveName, char sep, const ConfigParamList& values, const std::string& comment) +{ + // NOTE: separators are always ignored + NUT_UNUSED_VARIABLE(sep); + NUT_UNUSED_VARIABLE(comment); + + if(_config) + { + if(directiveName == "MAXAGE") + { + if(values.size()>0) + { + _config->maxAge = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "STATEPATH") + { + if(values.size()>0) + { + _config->statePath = values.front(); + } + } + else if(directiveName == "MAXCONN") + { + if(values.size()>0) + { + _config->maxConn = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "CERTFILE") + { + if(values.size()>0) + { + _config->certFile = values.front(); + } + } + else if(directiveName == "LISTEN") + { + if(values.size()==1 || values.size()==2) + { + UpsdConfiguration::Listen listen; + listen.address = values.front(); + if(values.size()==2) + { + listen.port = StringToSettableNumber(*(++values.begin())); + } + _config->listens.push_back(listen); + } + } + else + { + // TODO WTF with unknown commands ? + } + } +} + +void UpsdConfigParser::onParseEnd() +{ + // Do nothing +} + + +// +// UpsdUsersConfiguration +// + +UpsdUsersConfiguration::upsmon_mode_t UpsdUsersConfiguration::getUpsmonMode() const +{ + std::string mode_str = getStr("upsmon", "upsmon"); + + if ("master" == mode_str) + return UPSMON_MASTER; + + if ("slave" == mode_str) + return UPSMON_SLAVE; + + return UPSMON_UNDEF; +} + + +void UpsdUsersConfiguration::setUpsmonMode(upsmon_mode_t mode) +{ + assert(UPSMON_UNDEF != mode); + + setStr("upsmon", "upsmon", (UPSMON_MASTER == mode ? "master" : "slave")); +} + + +bool UpsdUsersConfiguration::parseFrom(NutStream & istream) +{ + // TODO: The parser is highly inefficient, it should use NutStream, directly + std::string str; + + if (NutStream::NUTS_OK != istream.getString(str)) + return false; + + parseFromString(str); + + return true; +} + + +bool UpsdUsersConfiguration::writeTo(NutStream & ostream) const +{ + UpsdUsersConfigWriter writer(ostream); + + return NutWriter::NUTW_OK == writer.writeConfig(*this); +} + +} /* namespace nut */ diff --git a/common/nutipc.cpp b/common/nutipc.cpp new file mode 100644 index 0000000000..cf33e02309 --- /dev/null +++ b/common/nutipc.cpp @@ -0,0 +1,275 @@ +/* + nutipc.cpp - NUT IPC + + Copyright (C) 2012 Eaton + + Author: Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "nutipc.hpp" +#include "nutstream.hpp" +#include "config.h" + +#include + + +namespace nut { + +/* Trivial implementations out of class declaration to avoid + * error: 'ClassName' has no out-of-line virtual method definitions; its vtable + * will be emitted in every translation unit [-Werror,-Wweak-vtables] + */ +Process::Main::~Main() {} +Signal::Handler::~Handler() {} + +pid_t Process::getPID() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + return getpid(); +} + + +pid_t Process::getPPID() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + return getppid(); +} + + +/** + * \brief Command line segmentation + * + * The function parses the \c command and chops off (and return) + * the first command line word (i.e. does segmentation based + * on white spaces, unless quoted). + * White spaces are removed from the returned words. + * + * \param[in,out] command Command line + * + * \return Command line word + */ +static std::string getCmdLineWord(std::string & command) { + size_t len = 0; + + // Remove initial whitespace + while (len < command.size()) { + if (' ' != command[len] && '\t' != command[len]) + break; + + ++len; + } + + command.erase(0, len); + + // Seek word end + bool bslsh = false; + char quote = 0; + + for (len = 0; len < command.size(); ++len) { + char ch = command[len]; + + // White space (may be inside quotes) + if (' ' == ch || '\t' == ch) { + if (!quote) + break; + } + + // Backspace (second one cancels the first) + else if ('\\' == ch) { + bslsh = bslsh ? false : true; + } + + // Double quote (may be escaped or nested) + else if ('"' == ch) { + if (!bslsh) { + if (!quote) + quote = '"'; + + // Final double quote + else if ('"' == quote) + quote = 0; + } + } + + // Single quote (can't be escaped) + else if ('\'' == ch) { + if (!quote) + quote = '\''; + + else if ('\'' == quote) + quote = 0; + } + + // Cancel backslash + if ('\\' != ch) + bslsh = false; + } + + // Extract the word + std::string word = command.substr(0, len); + + command.erase(0, len); + + return word; +} + + +Process::Executor::Executor(const std::string & command) { + std::string cmd(command); + + m_bin = getCmdLineWord(cmd); + + for (;;) { + std::string arg = getCmdLineWord(cmd); + + if (arg.empty()) + break; + + m_args.push_back(arg); + } +} + + +int Process::Executor::operator () () +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif +{ + const char ** args_c_str = new const char *[m_args.size() + 2]; + + const char * bin_c_str = m_bin.c_str(); + + args_c_str[0] = bin_c_str; + + Arguments::const_iterator arg = m_args.begin(); + + size_t i = 1; + + for (; arg != m_args.end(); ++arg, ++i) { + args_c_str[i] = (*arg).c_str(); + } + + args_c_str[i] = nullptr; + + int status = ::execvp(bin_c_str, const_cast(args_c_str)); + + // Upon successful execution, the execvp function never returns + // (since the process context is replaced, completely) + + delete[] args_c_str; + + std::stringstream e; + + e << "Failed to execute binary " << m_bin << ": " << status; + + throw std::runtime_error(e.str()); +} + + +int sigPipeWriteCmd(int fh, void * cmd, size_t cmd_size) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif +{ + char * cmd_bytes = reinterpret_cast(cmd); + + do { + ssize_t written = ::write(fh, cmd_bytes, cmd_size); + + if (written < 0) + return errno; + + cmd_bytes += written; + cmd_size -= static_cast(written); + + } while (cmd_size); + + return 0; +} + + +int Signal::send(Signal::enum_t signame, pid_t pid) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif +{ + int sig = static_cast(signame); + + int status = ::kill(pid, sig); + + if (0 == status) + return 0; + + if (EINVAL != errno) + return errno; + + std::stringstream e; + + e << "Can't send invalid signal " << sig; + + throw std::logic_error(e.str()); +} + + +int Signal::send(Signal::enum_t signame, const std::string & pid_file) { + NutFile file(pid_file, NutFile::READ_ONLY); + + std::string pid_str; + + NutStream::status_t read_st = file.getString(pid_str); + + if (NutStream::NUTS_OK != read_st) { + std::stringstream e; + + e << "Failed to read PID from " << pid_file << ": " << read_st; + + throw std::runtime_error(e.str()); + } + + std::stringstream pid_conv(pid_str); + + pid_t pid; + + if (!(pid_conv >> pid)) { + std::stringstream e; + + e << "Failed to convert contents of " << pid_file << " to PID"; + + throw std::runtime_error(e.str()); + } + + return send(signame, pid); +} + + +int NutSignal::send(NutSignal::enum_t signame, const std::string & process) { + std::string pid_file; + + // TBD: What's ALTPIDPATH and shouldn't we also consider it? + pid_file += PIDPATH; + pid_file += '/'; + pid_file += process; + pid_file += ".pid"; + + return Signal::send(signame, pid_file); +} + +} // end of namespace nut diff --git a/common/nutstream.cpp b/common/nutstream.cpp new file mode 100644 index 0000000000..8e861ac08c --- /dev/null +++ b/common/nutstream.cpp @@ -0,0 +1,930 @@ +/* + nutstream.cpp - NUT stream + + Copyright (C) + 2012 Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "nutstream.hpp" + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +#include +#include +} + + +namespace nut { + +/* Trivial implementation out of class declaration to avoid + * error: 'ClassName' has no out-of-line virtual method definitions; its vtable + * will be emitted in every translation unit [-Werror,-Wweak-vtables] + */ +NutStream::~NutStream() {} + +NutStream::status_t NutMemory::getChar(char & ch) { + if (m_pos == m_impl.size()) + return NUTS_EOF; + + if (m_pos > m_impl.size()) + return NUTS_ERROR; + + ch = m_impl.at(m_pos); + + return NUTS_OK; +} + + +void NutMemory::readChar() { + if (m_pos < m_impl.size()) + ++m_pos; +} + + +NutStream::status_t NutMemory::getString(std::string & str) { + str = m_impl.substr(m_pos); + + m_pos = m_impl.size(); + + return NUTS_OK; +} + + +NutStream::status_t NutMemory::putChar(char ch) { + m_impl += ch; + + return NUTS_OK; +} + + +NutStream::status_t NutMemory::putString(const std::string & str) { + m_impl += str; + + return NUTS_OK; +} + + +NutStream::status_t NutMemory::putData(const std::string & data) { + return putString(data); +} + + +const std::string NutFile::m_tmp_dir("/var/tmp"); + + +NutFile::NutFile(anonymous_t): + m_impl(nullptr), + m_current_ch('\0'), + m_current_ch_valid(false) +{ + m_impl = ::tmpfile(); + + if (nullptr == m_impl) { + int err_code = errno; + + std::stringstream e; + e << "Failed to create temporary file: " << err_code << ": " << ::strerror(err_code); + + throw std::runtime_error(e.str()); + } +} + + +bool NutFile::exists(int & err_code, std::string & err_msg) const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + struct stat info; + + int status = ::stat(m_name.c_str(), &info); + + if (!status) + return true; + + err_code = errno; + err_msg = std::string(::strerror(err_code)); + + return false; +} + + +bool NutFile::open(access_t mode, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + static const char *read_only = "r"; + static const char *write_only = "w"; + static const char *read_write = "r+"; + static const char *read_write_clear = "w+"; + static const char *append_only = "a"; + static const char *read_append = "a+"; + + const char *mode_str = nullptr; + + switch (mode) { + case READ_ONLY: + mode_str = read_only; + break; + case WRITE_ONLY: + mode_str = write_only; + break; + case READ_WRITE: + mode_str = read_write; + break; + case READ_WRITE_CLEAR: + mode_str = read_write_clear; + break; + case READ_APPEND: + mode_str = read_append; + break; + case APPEND_ONLY: + mode_str = append_only; + break; + } + + assert(nullptr != mode_str); + + m_impl = ::fopen(m_name.c_str(), mode_str); + + if (nullptr != m_impl) + return true; + + err_code = errno; + err_msg = std::string(::strerror(err_code)); + + return false; +} + + +bool NutFile::close(int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + err_code = ::fclose(m_impl); + + if (0 != err_code) { + err_msg = std::string(::strerror(err_code)); + + return false; + } + + m_impl = nullptr; + + return true; +} + + +bool NutFile::remove(int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + err_code = ::unlink(m_name.c_str()); + + if (0 != err_code) { + err_code = errno; + + err_msg = std::string(::strerror(err_code)); + + return false; + } + + return true; +} + + +NutFile::NutFile(const std::string & name, access_t mode): + m_name(name), + m_impl(nullptr), + m_current_ch('\0'), + m_current_ch_valid(false) +{ + openx(mode); +} + + +std::string NutFile::tmpName() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif +{ + // Note: in many systems' implementations this claims a warning like: + // the use of `tempnam' is dangerous, better use `mkstemp' + // or + // These functions are deprecated because more secure versions + // are available; see tmpnam_s, _wtmpnam_s. + // but it seems the alternatives are different for various platforms + // and so a replacement is not quite portable (stack of ifdef's?), per + // https://stackoverflow.com/questions/3299881/tmpnam-warning-saying-it-is-dangerous + char *tmp_name = ::tempnam(m_tmp_dir.c_str(), nullptr); + + if (nullptr == tmp_name) + throw std::runtime_error( + "Failed to create temporary file name"); + + std::string tmp_name_str(tmp_name); + + ::free(tmp_name); + + return tmp_name_str; +} + + +NutFile::NutFile(access_t mode): + m_name(tmpName()), + m_impl(nullptr), + m_current_ch('\0'), + m_current_ch_valid(false) +{ + openx(mode); +} + + +/** + * \brief C fgetc wrapper + * + * \param[in] file File + * \param[out] ch Character + * + * \retval NUTS_OK on success + * \retval NUTS_EOF on end-of-file + * \retval NUTS_ERROR on read error + */ +inline static NutStream::status_t fgetcWrapper(FILE * file, char & ch) { + assert(nullptr != file); + + errno = 0; + + int c = ::fgetc(file); + + if (EOF == c) { + if (0 == errno) + return NutStream::NUTS_EOF; + + return NutStream::NUTS_ERROR; + } + + ch = static_cast(c); + + return NutStream::NUTS_OK; +} + + +NutStream::status_t NutFile::getChar(char & ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + if (m_current_ch_valid) { + ch = m_current_ch; + + return NUTS_OK; + } + + if (nullptr == m_impl) + return NUTS_ERROR; + + status_t status = fgetcWrapper(m_impl, ch); + + if (NUTS_OK != status) + return status; + + // Cache the character for future reference + m_current_ch = ch; + m_current_ch_valid = true; + + return NUTS_OK; +} + + +void NutFile::readChar() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + m_current_ch_valid = false; +} + + +NutStream::status_t NutFile::getString(std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + if (m_current_ch_valid) + str += m_current_ch; + + m_current_ch_valid = false; + + if (nullptr == m_impl) + return NUTS_ERROR; + + // Note that ::fgetc is used instead of ::fgets + // That's because of \0 char. support + for (;;) { + char ch; + + status_t status = fgetcWrapper(m_impl, ch); + + if (NUTS_ERROR == status) + return status; + + if (NUTS_EOF == status) + return NUTS_OK; + + str += ch; + } +} + + +NutStream::status_t NutFile::putChar(char ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + int c; + + if (nullptr == m_impl) + return NUTS_ERROR; + + c = ::fputc(static_cast(ch), m_impl); + + return EOF == c ? NUTS_ERROR : NUTS_OK; +} + + +NutStream::status_t NutFile::putString(const std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + int c; + + if (nullptr == m_impl) + return NUTS_ERROR; + + c = ::fputs(str.c_str(), m_impl); + + return EOF == c ? NUTS_ERROR : NUTS_OK; +} + + +NutStream::status_t NutFile::putData(const std::string & data) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + // Unfortunately, C FILE interface doesn't have non C-string + // put function (i.e. function for raw data output with size specifier + for (size_t i = 0; i < data.size(); ++i) { + status_t st = putChar(data.at(i)); + + if (NUTS_ERROR == st) + return NUTS_ERROR; + } + + return NUTS_OK; +} + + +NutFile::~NutFile() { + if (nullptr != m_impl) + closex(); +} + + +void NutSocket::Address::init_unix(Address & addr, const std::string & path) { + struct sockaddr_un * un_addr = reinterpret_cast(::malloc(sizeof(struct sockaddr_un))); + + if (nullptr == un_addr) + throw std::bad_alloc(); + + un_addr->sun_family = AF_UNIX; + + assert(sizeof(un_addr->sun_path) / sizeof(char) > path.size()); + + for (size_t i = 0; i < path.size(); ++i) + un_addr->sun_path[i] = path.at(i); + + un_addr->sun_path[path.size()] = '\0'; + + addr.m_sock_addr = reinterpret_cast(un_addr); + addr.m_length = sizeof(*un_addr); +} + + +void NutSocket::Address::init_ipv4(Address & addr, const std::vector & qb, uint16_t port) { + assert(4 == qb.size()); + + uint32_t packed_qb = 0; + + struct sockaddr_in * in4_addr = reinterpret_cast(::malloc(sizeof(struct sockaddr_in))); + + if (nullptr == in4_addr) + throw std::bad_alloc(); + + packed_qb = static_cast(qb.at(0)); + packed_qb |= static_cast(qb.at(1)) << 8; + packed_qb |= static_cast(qb.at(2)) << 16; + packed_qb |= static_cast(qb.at(3)) << 24; + + in4_addr->sin_family = AF_INET; + in4_addr->sin_port = htons(port); + in4_addr->sin_addr.s_addr = packed_qb; + + addr.m_sock_addr = reinterpret_cast(in4_addr); + addr.m_length = sizeof(*in4_addr); +} + + +void NutSocket::Address::init_ipv6(Address & addr, const std::vector & hb, uint16_t port) { + assert(16 == hb.size()); + + struct sockaddr_in6 * in6_addr = reinterpret_cast(::malloc(sizeof(struct sockaddr_in6))); + + if (nullptr == in6_addr) + throw std::bad_alloc(); + + in6_addr->sin6_family = AF_INET6; + in6_addr->sin6_port = htons(port); + in6_addr->sin6_flowinfo = 0; // TODO: check that + in6_addr->sin6_scope_id = 0; // TODO: check that + + for (size_t i = 0; i < 16; ++i) + in6_addr->sin6_addr.s6_addr[i] = hb.at(i); + + addr.m_sock_addr = reinterpret_cast(in6_addr); + addr.m_length = sizeof(*in6_addr); +} + + +NutSocket::Address::Address( + unsigned char msb, + unsigned char msb2, + unsigned char lsb2, + unsigned char lsb, + uint16_t port) +{ + std::vector qb; + + qb.reserve(4); + qb.push_back(msb); + qb.push_back(msb2); + qb.push_back(lsb2); + qb.push_back(lsb); + + init_ipv4(*this, qb, port); +} + + +NutSocket::Address::Address(const std::vector & bytes, uint16_t port) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif +{ + switch (bytes.size()) { + case 4: + init_ipv4(*this, bytes, port); + break; + + case 16: + init_ipv6(*this, bytes, port); + break; + + default: { + std::stringstream e; + e << "Unsupported IP address size: " << bytes.size(); + + throw std::logic_error(e.str()); + } + } +} + + +NutSocket::Address::Address(const Address & orig): m_sock_addr(nullptr), m_length(orig.m_length) { + void * copy = ::malloc(m_length); + + if (nullptr == copy) + throw std::bad_alloc(); + + ::memcpy(copy, orig.m_sock_addr, m_length); + + m_sock_addr = reinterpret_cast(copy); +} + + +/** + * \brief Format IPv4 address + * + * \param packed 4 bytes in network byte order + * + * \return IPv4 address string + */ +static std::string formatIPv4addr(uint32_t packed) { + std::stringstream ss; + + ss << (packed & 0x000000ff) << "."; + ss << (packed >> 8 & 0x000000ff) << "."; + ss << (packed >> 16 & 0x000000ff) << "."; + ss << (packed >> 24 & 0x000000ff); + + return ss.str(); +} + + +/** + * \brief Format IPv6 address + * + * \param bytes 16 bytes in network byte order + * + * \return IPv6 address string + */ +static std::string formatIPv6addr(unsigned char const bytes[16]) { + // Check for special form addresses + bool zero_at_0_9 = true; + bool zero_at_0_14 = false; + + for (size_t i = 0; zero_at_0_9 && i < 10; ++i) + zero_at_0_9 = 0 == bytes[i]; + + if (zero_at_0_9) { + zero_at_0_14 = true; + + for (size_t i = 10; zero_at_0_14 && i < 15; ++i) + zero_at_0_14 = 0 == bytes[i]; + } + + // Loopback + if (zero_at_0_14 && 1 == bytes[15]) + return "::1"; + + std::stringstream ss; + + // IPv4 mapped on IPv6 address + if (zero_at_0_9 && 0xff == bytes[10] && 0xff == bytes[11]) { + ss << "::FFFF:"; + ss << bytes[12] << '.' << bytes[13] << '.'; + ss << bytes[14] << '.' << bytes[15]; + + return ss.str(); + } + + // Standard form + // TODO: ommition (REVIEW: omission?) of lengthy zero word strings + ss << std::uppercase << std::hex << std::setfill('0'); + + for (size_t i = 0; ; ) { + uint16_t w = static_cast(static_cast(bytes[2 * i]) << 8) | static_cast(bytes[2 * i + 1]); + + ss << std::setw(4) << w; + + if (8 == ++i) + break; + + ss << ':'; + } + + return ss.str(); +} + + +std::string NutSocket::Address::str() const { + assert(nullptr != m_sock_addr); + + sa_family_t family = m_sock_addr->sa_family; + + std::stringstream ss; + ss << "nut::NutSocket::Address(family: " << family; + + switch (family) { + case AF_UNIX: { + struct sockaddr_un * addr = reinterpret_cast(m_sock_addr); + + ss << " (UNIX domain socket), file: " << addr->sun_path; + + break; + } + + case AF_INET: { + struct sockaddr_in * addr = reinterpret_cast(m_sock_addr); + + ss << " (IPv4 address), " << formatIPv4addr(addr->sin_addr.s_addr) << ":" << addr->sin_port; + + break; + } + + case AF_INET6: { + struct sockaddr_in6 * addr = reinterpret_cast(m_sock_addr); + + ss << " (IPv6 address), " << formatIPv6addr(addr->sin6_addr.s6_addr) << ":" << addr->sin6_port; + + break; + } + + default: { + std::stringstream e; + e << "NOT IMPLEMENTED: Socket address family " << family << " unsupported"; + + throw std::logic_error(e.str()); + } + } + + ss << ")"; + + return ss.str(); +} + + +NutSocket::Address::~Address() { + ::free(m_sock_addr); +} + + +bool NutSocket::accept( + NutSocket & sock, + const NutSocket & listen_sock, + int & err_code, + std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif +{ + assert(-1 == sock.m_impl); + + struct sockaddr sock_addr; + socklen_t sock_addr_size = sizeof(sock_addr); + + sock.m_impl = ::accept(listen_sock.m_impl, &sock_addr, &sock_addr_size); + + if (-1 != sock.m_impl) + return true; + + err_code = errno; + err_msg = std::string(::strerror(err_code)); + + // The following reasons of unsuccessful termination are non-exceptional + switch (err_code) { + case EAGAIN: // Non-blocking listen socket, no conn. pending + case ECONNABORTED: // Connection has been aborted + case EINTR: // Interrupted by a signal + case EMFILE: // Open file descriptors per-process limit was reached + case ENFILE: // Open file descriptors per-system limit was reached + case EPROTO: // Protocol error + return false; + } + + std::stringstream e; + e << "Failed to accept connection: " << err_code << ": " << err_msg; + + throw std::logic_error(e.str()); +} + + +NutSocket::NutSocket(domain_t dom, type_t type, proto_t proto): + m_impl(-1), + m_current_ch('\0'), + m_current_ch_valid(false) +{ + int cdom = static_cast(dom); + int ctype = static_cast(type); + int cproto = static_cast(proto); + + m_impl = ::socket(cdom, ctype, cproto); + + if (-1 == m_impl) { + int erno = errno; + + std::stringstream e; + e << "Failed to create socket domain: "; + e << cdom << ", type: " << ctype << ", proto: " << cproto; + e << ": " << erno << ": " << ::strerror(erno); + + throw std::runtime_error(e.str()); + } +} + + +bool NutSocket::bind(const Address & addr, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + err_code = ::bind(m_impl, addr.m_sock_addr, addr.m_length); + + if (0 == err_code) + return true; + + err_code = errno; + err_msg = std::string(::strerror(err_code)); + + return false; +} + + +bool NutSocket::listen(int backlog, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + err_code = ::listen(m_impl, backlog); + + if (0 == err_code) + return true; + + err_code = errno; + err_msg = std::string(::strerror(err_code)); + + return false; +} + + +bool NutSocket::connect(const Address & addr, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + err_code = ::connect(m_impl, addr.m_sock_addr, addr.m_length); + + if (0 == err_code) + return true; + + err_code = errno; + err_msg = std::string(::strerror(err_code)); + + return false; +} + + +bool NutSocket::close(int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + err_code = ::close(m_impl); + + if (0 == err_code) { + m_impl = -1; + + return true; + } + + err_code = errno; + err_msg = std::string(::strerror(err_code)); + + return false; +} + + +NutSocket::~NutSocket() { + if (-1 != m_impl) + closex(); +} + + +NutStream::status_t NutSocket::getChar(char & ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + if (m_current_ch_valid) { + ch = m_current_ch; + + return NUTS_OK; + } + + // TBD: Perhaps we should buffer more bytes at once + // However, buffering is already done in kernel space, + // so unless we need greater reading efficiency, char-by-char + // reading should be sufficient + + ssize_t read_cnt = ::read(m_impl, &ch, 1); + + if (1 == read_cnt) { + m_current_ch = ch; + m_current_ch_valid = true; + + return NUTS_OK; + } + + if (0 == read_cnt) + return NUTS_EOF; + + assert(-1 == read_cnt); + + // TODO: At least logging of the error (errno), if not propagation + + return NUTS_ERROR; +} + + +void NutSocket::readChar() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + m_current_ch_valid = false; +} + + +NutStream::status_t NutSocket::getString(std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + if (m_current_ch_valid) + str += m_current_ch; + + m_current_ch_valid = false; + + char buffer[512]; + + for (;;) { + ssize_t read_cnt = ::read(m_impl, buffer, sizeof(buffer) / sizeof(buffer[0])); + + if (read_cnt < 0) + return NUTS_ERROR; + + if (0 == read_cnt) + return NUTS_OK; + + str.append(buffer, static_cast(read_cnt)); + } +} + + +NutStream::status_t NutSocket::putChar(char ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + ssize_t write_cnt = ::write(m_impl, &ch, 1); + + if (1 == write_cnt) + return NUTS_OK; + + assert(-1 == write_cnt); + + // TODO: At least logging of the error (errno), if not propagation + + return NUTS_ERROR; +} + + +NutStream::status_t NutSocket::putString(const std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif +{ + size_t str_len = str.size(); + + // Avoid the costly system call unless necessary + if (0 == str_len) + return NUTS_OK; + + ssize_t write_cnt = ::write(m_impl, str.data(), str_len); + + // TODO: Under certain circumstances, less than the whole + // string might be written + // Review the code if async. I/O is supported (in which case + // the function shall have to implement the blocking using + // select/poll/epoll on its own (probably select for portability) + + assert(write_cnt > 0); + + if (static_cast(write_cnt) == str_len) + return NUTS_OK; + + // TODO: At least logging of the error (errno), if not propagation + + return NUTS_ERROR; +} + +} // end of namespace nut diff --git a/common/nutwriter.cpp b/common/nutwriter.cpp new file mode 100644 index 0000000000..51afed5820 --- /dev/null +++ b/common/nutwriter.cpp @@ -0,0 +1,715 @@ +/* + nutwriter.cpp - NUT writer + + Copyright (C) + 2012 Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "nutwriter.hpp" + +#include +#include +#include +#include +#include +#include + + +/** + * \brief NUT configuration directive generator + * + * The macro is used to simplify generation of + * NUT config. directives. + * + * IMPORTANT NOTE: + * In case of writing error, the macro causes immediate + * return from the calling function (propagating the writing status). + * + * \param name Directive name + * \param arg_t Directive argument implementation type + * \param arg Directive argument + * \param quote_arg Boolean flag; check to quote the argument + */ +#define CONFIG_DIRECTIVEX(name, arg_t, arg, quote_arg) \ + do { \ + if ((arg).set()) { \ + const arg_t & arg_val = (arg); \ + std::stringstream ss; \ + ss << name << ' '; \ + if (quote_arg) \ + ss << '"'; \ + ss << arg_val; \ + if (quote_arg) \ + ss << '"'; \ + status_t status = writeDirective(ss.str()); \ + if (NUTW_OK != status) \ + return status; \ + } \ + } while (0) + + +namespace nut { + + +/* Trivial implementations out of class declaration to avoid + * error: 'ClassName' has no out-of-line virtual method definitions; its vtable + * will be emitted in every translation unit [-Werror,-Wweak-vtables] + */ +NutConfigWriter::~NutConfigWriter() {} +NutConfConfigWriter::~NutConfConfigWriter() {} +UpsmonConfigWriter::~UpsmonConfigWriter() {} +UpsdConfigWriter::~UpsdConfigWriter() {} + +// End-of-Line separators (arch. dependent) + +/** UNIX style EoL */ +static const std::string LF("\n"); + +// TODO: Make a compile-time selection +#if (0) +// M$ Windows EoL +static const std::string CRLF("\r\n"); + +// Apple MAC EoL +static const std::string CR("\r"); +#endif // end of #if (0) + + +const std::string & NutWriter::eol(LF); + +const std::string GenericConfigWriter::s_default_section_entry_indent("\t"); +const std::string GenericConfigWriter::s_default_section_entry_separator(" = "); + + +NutWriter::status_t NutWriter::writeEachLine(const std::string & str, const std::string & pref) { + for (size_t pos = 0; pos < str.size(); ) { + // Prefix every line + status_t status = write(pref); + + if (NUTW_OK != status) + return status; + + // Write up to the next EoL (or till the end) + size_t eol_pos = str.find(eol, pos); + + if (str.npos == eol_pos) + return write(str.substr(pos) + eol); + + eol_pos += eol.size(); + + status = write(str.substr(pos, eol_pos)); + + if (NUTW_OK != status) + return status; + + // Update position + pos = eol_pos; + } + + return NUTW_OK; +} + + +NutWriter::status_t SectionlessConfigWriter::writeDirective(const std::string & str) { + return write(str + eol); +} + + +NutWriter::status_t SectionlessConfigWriter::writeComment(const std::string & str) { + return writeEachLine(str, "# "); +} + + +NutWriter::status_t SectionlessConfigWriter::writeSectionName(const std::string & name) { + std::string e("INTERNAL ERROR: Attempt to write section name "); + e += name + " to a section-less configuration file"; + + throw std::logic_error(e); +} + + +NutWriter::status_t NutConfConfigWriter::writeConfig(const NutConfiguration & config) { + status_t status; + + // Mode + // TBD: How should I serialize an unknown mode? + if (config.mode.set()) { + std::string mode_str; + + NutConfiguration::NutMode mode = config.mode; + + switch (mode) { + case NutConfiguration::MODE_UNKNOWN: + // BEWARE! Intentional fall-through to MODE_NONE branch + + case NutConfiguration::MODE_NONE: + mode_str = "none"; + break; + + case NutConfiguration::MODE_STANDALONE: + mode_str = "standalone"; + break; + + case NutConfiguration::MODE_NETSERVER: + mode_str = "netserver"; + break; + + case NutConfiguration::MODE_NETCLIENT: + mode_str = "netclient"; + break; + + case NutConfiguration::MODE_CONTROLLED: + mode_str = "controlled"; + break; + + case NutConfiguration::MODE_MANUAL: + mode_str = "manual"; + break; + } + + status = writeDirective("MODE=" + mode_str); + + if (NUTW_OK != status) + return status; + } + + return NUTW_OK; +} + + +/** Notify types & flags strings */ +struct NotifyFlagsStrings { + // TBD: Shouldn't this mapping be shared with the parser? + // This is an obvious redundancy... + + /** Notify type strings list */ + typedef const char * TypeStrings[UpsmonConfiguration::NOTIFY_TYPE_MAX]; + + /** Notify flag strings map */ + typedef std::map FlagStrings; + + /** Notify type strings */ + static const TypeStrings type_str; + + /** Notify flag strings */ + static const FlagStrings flag_str; + + /** + * \brief Initialize notify flag strings + * + * \return Notify flag strings map + */ + static FlagStrings initFlagStrings() { + FlagStrings str; + + str[UpsmonConfiguration::NOTIFY_IGNORE] = "IGNORE"; + str[UpsmonConfiguration::NOTIFY_SYSLOG] = "SYSLOG"; + str[UpsmonConfiguration::NOTIFY_WALL] = "WALL"; + str[UpsmonConfiguration::NOTIFY_EXEC] = "EXEC"; + + return str; + } + +}; // end of struct NotifyFlagsStrings + + +const NotifyFlagsStrings::TypeStrings NotifyFlagsStrings::type_str = { + "ONLINE", // NOTIFY_ONLINE + "ONBATT", // NOTIFY_ONBATT + "LOWBATT", // NOTIFY_LOWBATT + "FSD\t", // NOTIFY_FSD (including padding) + "COMMOK", // NOTIFY_COMMOK + "COMMBAD", // NOTIFY_COMMBAD + "SHUTDOWN", // NOTIFY_SHUTDOWN + "REPLBATT", // NOTIFY_REPLBATT + "NOCOMM", // NOTIFY_NOCOMM + "NOPARENT", // NOTIFY_NOPARENT +}; + + +const NotifyFlagsStrings::FlagStrings NotifyFlagsStrings::flag_str = + NotifyFlagsStrings::initFlagStrings(); + + +/** + * \brief upsmon notify flags serializer + * + * \param type Notification type + * \param flags Notification flags + * + * \return NOTIFYFLAG directive string + */ +static std::string serializeNotifyFlags(UpsmonConfiguration::NotifyType type, unsigned int flags) { + static const NotifyFlagsStrings::FlagStrings::const_iterator ignore_str_iter = + NotifyFlagsStrings::flag_str.find(UpsmonConfiguration::NOTIFY_IGNORE); + + static const std::string ignore_str(ignore_str_iter->second); + + assert(type < UpsmonConfiguration::NOTIFY_TYPE_MAX); + + std::string directive("NOTIFYFLAG "); + + directive += NotifyFlagsStrings::type_str[type]; + + char separator = '\t'; + + // The IGNORE flag is actually no-flag case + if (UpsmonConfiguration::NOTIFY_IGNORE == flags) { + directive += separator; + directive += ignore_str; + + return directive; + } + + NotifyFlagsStrings::FlagStrings::const_iterator fl_iter = + NotifyFlagsStrings::flag_str.begin(); + + for (; fl_iter != NotifyFlagsStrings::flag_str.end(); ++fl_iter) { + if (fl_iter->first & flags) { + directive += separator; + directive += fl_iter->second; + + separator = '+'; + } + } + + return directive; +} + + +/** + * \brief upsmon notify messages serializer + * + * \param type Notification type + * \param msg Notification message + * + * \return NOTIFYMSG directive string + */ +static std::string serializeNotifyMessage(UpsmonConfiguration::NotifyType type, const std::string & msg) { + assert(type < UpsmonConfiguration::NOTIFY_TYPE_MAX); + + std::string directive("NOTIFYMSG "); + + directive += NotifyFlagsStrings::type_str[type]; + directive += '\t'; + directive += '"' + msg + '"'; + + return directive; +} + + +/** + * \brief Get notify type successor + * + * TBD: Should be in nutconf.hpp + * + * \param type Notify type + * + * \return Notify type successor + */ +inline static UpsmonConfiguration::NotifyType nextNotifyType(UpsmonConfiguration::NotifyType type) { + assert(type < UpsmonConfiguration::NOTIFY_TYPE_MAX); + + int type_ord = static_cast(type); + + return static_cast(type_ord + 1); +} + + +/** + * \brief Notify type pre-increment + * + * TBD: Should be in nutconf.hpp + * + * \param[in,out] type Notify type + * + * \return \c type successor + */ +inline static UpsmonConfiguration::NotifyType operator ++(UpsmonConfiguration::NotifyType & type) { + return type = nextNotifyType(type); +} + + +/** + * \brief Notify type post-increment + * + * TBD: Should be in nutconf.hpp + * + * \param[in,out] type Notify type + * + * \return \c type + */ +/* // CURRENTLY UNUSED +inline static UpsmonConfiguration::NotifyType operator ++(UpsmonConfiguration::NotifyType & type, int) { + UpsmonConfiguration::NotifyType type_copy = type; + + type = nextNotifyType(type); + + return type_copy; +} +*/ + +/** + * \brief UPS monitor definition serializer + * + * \param monitor Monitor + * + * \return Monitor config. directive + */ +static std::string serializeMonitor(const UpsmonConfiguration::Monitor & monitor) { + std::stringstream directive; + + directive << "MONITOR "; + + // System + directive << monitor.upsname << '@' << monitor.hostname; + + if (monitor.port) + directive << ':' << monitor.port; + + directive << ' '; + + // Power value + directive << monitor.powerValue << ' '; + + // Username & password + directive << monitor.username << ' ' << monitor.password << ' '; + + // Master/slave + directive << (monitor.isMaster ? "master" : "slave"); + + return directive.str(); +} + + +NutWriter::status_t UpsmonConfigWriter::writeConfig(const UpsmonConfiguration & config) { + /** + * \brief upsmon directive generator + * + * The macro is locally used to simplify generation of + * upsmon config. directives (except those with enumerated + * arguments). + * + * NOTE that the macro may cause return from the function + * (upon writing error). + * See \ref CONFIG_DIRECTIVEX for more information. + * + * \param name Directive name + * \param arg_t Directive argument implementation type + * \param arg Directive argument + * \param quote_arg Boolean flag; check to quote the argument + */ + #define UPSMON_DIRECTIVEX(name, arg_t, arg, quote_arg) \ + CONFIG_DIRECTIVEX(name, arg_t, arg, quote_arg) + + UPSMON_DIRECTIVEX("RUN_AS_USER", std::string, config.runAsUser, false); + UPSMON_DIRECTIVEX("SHUTDOWNCMD", std::string, config.shutdownCmd, true); + UPSMON_DIRECTIVEX("NOTIFYCMD", std::string, config.notifyCmd, true); + UPSMON_DIRECTIVEX("POWERDOWNFLAG", std::string, config.powerDownFlag, false); + UPSMON_DIRECTIVEX("MINSUPPLIES", unsigned int, config.minSupplies, false); + UPSMON_DIRECTIVEX("POLLFREQ", unsigned int, config.poolFreq, false); + UPSMON_DIRECTIVEX("POLLFREQALERT", unsigned int, config.poolFreqAlert, false); + UPSMON_DIRECTIVEX("HOSTSYNC", unsigned int, config.hotSync, false); + UPSMON_DIRECTIVEX("DEADTIME", unsigned int, config.deadTime, false); + UPSMON_DIRECTIVEX("RBWARNTIME", unsigned int, config.rbWarnTime, false); + UPSMON_DIRECTIVEX("NOCOMMWARNTIME", unsigned int, config.noCommWarnTime, false); + UPSMON_DIRECTIVEX("FINALDELAY", unsigned int, config.finalDelay, false); + + #undef UPSMON_DIRECTIVEX + + UpsmonConfiguration::NotifyType type; + + // Notify flags + type = UpsmonConfiguration::NOTIFY_ONLINE; + + for (; type < UpsmonConfiguration::NOTIFY_TYPE_MAX; ++type) { + if (config.notifyFlags[type].set()) { + std::string directive = serializeNotifyFlags(type, config.notifyFlags[type]); + + status_t status = writeDirective(directive); + + if (NUTW_OK != status) + return status; + } + } + + // Notify messages + type = UpsmonConfiguration::NOTIFY_ONLINE; + + for (; type < UpsmonConfiguration::NOTIFY_TYPE_MAX; ++type) { + if (config.notifyMessages[type].set()) { + std::string directive = serializeNotifyMessage(type, config.notifyMessages[type]); + + status_t status = writeDirective(directive); + + if (NUTW_OK != status) + return status; + } + } + + // Monitors + std::list::const_iterator mon_iter = config.monitors.begin(); + + for (; mon_iter != config.monitors.end(); ++mon_iter) { + std::string directive = serializeMonitor(*mon_iter); + + status_t status = writeDirective(directive); + + if (NUTW_OK != status) + return status; + } + + return NUTW_OK; +} + + +/** + * \brief upsd listen address serializer + * + * \param address Listen address + * + * \return Serialized listen address + */ +static std::string serializeUpsdListenAddress(const UpsdConfiguration::Listen & address) { + std::stringstream directive; + + directive << "LISTEN " << address.address; + + if (address.port.set()) + directive << ' ' << static_cast(address.port); + + return directive.str(); +} + + +NutWriter::status_t UpsdConfigWriter::writeConfig(const UpsdConfiguration & config) { + /** + * \brief upsd directive generator + * + * The macro is locally used to simplify generation of + * upsd config. directives (except the listen addresses). + * + * NOTE that the macro may cause return from the function + * (upon writing error). + * See \ref CONFIG_DIRECTIVEX for more information. + * + * \param name Directive name + * \param arg_t Directive argument implementation type + * \param arg Directive argument + */ + #define UPSD_DIRECTIVEX(name, arg_t, arg) \ + CONFIG_DIRECTIVEX(name, arg_t, arg, false) + + UPSD_DIRECTIVEX("MAXAGE", unsigned int, config.maxAge); + UPSD_DIRECTIVEX("MAXCONN", unsigned int, config.maxConn); + UPSD_DIRECTIVEX("STATEPATH", std::string, config.statePath); + UPSD_DIRECTIVEX("CERTFILE", std::string, config.certFile); + + #undef UPSD_DIRECTIVEX + + // Listen addresses + std::list::const_iterator la_iter = config.listens.begin(); + + for (; la_iter != config.listens.end(); ++la_iter) { + std::string directive = serializeUpsdListenAddress(*la_iter); + + status_t status = writeDirective(directive); + + if (NUTW_OK != status) + return status; + } + + return NUTW_OK; +} + + +NutWriter::status_t DefaultConfigWriter::writeComment(const std::string & str) { + return writeEachLine(str, "# "); +} + + +NutWriter::status_t DefaultConfigWriter::writeSectionName(const std::string & name) { + std::string section_line("["); + section_line += name + "]" + eol; + + return write(section_line); +} + + +NutWriter::status_t DefaultConfigWriter::writeDirective(const std::string & str) { + return write(str + eol); +} + + +/** + * \brief Value quoting and escaping + * + * The function checks whether the value string contains + * any spaces and/or '=' characters. + * If so, the result is double-quoted and all inner double + * quotes shall be escaped using backslash. + * + * \param val Value string + * + * \return Value string ready for serialization + */ +static std::string encodeValue(const std::string & val) { + // Check the string for spaces and '=' + bool quote = false; + + for (size_t i = 0; i < val.size() && !quote; ++i) { + char ch = val[i]; + + quote = ' ' == ch || '=' == ch || ':' == ch; + } + + if (!quote) + return val; + + // Quote value and escape inner quotes + std::string qval; + + qval += '"'; + + for (size_t i = 0; i < val.size(); ++i) { + char ch = val[i]; + + if ('"' == ch) + qval += '\\'; + + qval += ch; + } + + qval += '"'; + + return qval; +} + + +NutWriter::status_t GenericConfigWriter::writeSectionEntry( + const GenericConfigSectionEntry & entry, + const std::string & indent, + const std::string & kv_sep) +{ + ConfigParamList::const_iterator value_iter = entry.values.begin(); + + for (; value_iter != entry.values.end(); ++value_iter) { + std::string value = encodeValue(*value_iter); + + status_t status = writeDirective(indent + entry.name + kv_sep + value); + + if (NUTW_OK != status) + return status; + } + + return NUTW_OK; +} + + +NutWriter::status_t GenericConfigWriter::writeSection(const GenericConfigSection & section) { + status_t status; + + // Note that global scope definitions are in section + // with an empty name + // The section name won't be written and the assignments + // won't be indented + std::string indent; + + if (!section.name.empty()) { + status = writeSectionName(section.name); + + if (NUTW_OK != status) + return status; + + indent += "\t"; + } + + // Write section name/value pairs + GenericConfigSection::EntryMap::const_iterator entry_iter = section.entries.begin(); + + for (; entry_iter != section.entries.end(); ++entry_iter) { + status = writeSectionEntry(entry_iter->second, indent); + + if (NUTW_OK != status) + return status; + } + + return NUTW_OK; +} + + +NutWriter::status_t GenericConfigWriter::writeConfig(const GenericConfiguration & config) { + // Write sections + // Note that lexicographic ordering places the global + // (i.e. empty-name) section as the first one + GenericConfiguration::SectionMap::const_iterator section_iter = config.sections.begin(); + + for (; section_iter != config.sections.end(); ++section_iter) { + status_t status = writeSection(section_iter->second); + + if (NUTW_OK != status) + return status; + + // TBD: Write one empty line as section separator + status = write(eol); + + if (NUTW_OK != status) + return status; + } + + return NUTW_OK; +} + + +NutWriter::status_t UpsdUsersConfigWriter::writeSection(const GenericConfigSection & section) { + static const std::string upsmon_entry_separator(" "); + + status_t status; + + // upsmon section requires special handling because of the upsmon (master|slave) directive + if ("upsmon" != section.name) + return GenericConfigWriter::writeSection(section); + + status = writeSectionName(section.name); + + if (NUTW_OK != status) + return status; + + // Write section name/value pairs + GenericConfigSection::EntryMap::const_iterator entry_iter = section.entries.begin(); + + for (; entry_iter != section.entries.end(); ++entry_iter) { + // Special case of upsmon parameter + if ("upsmon" == entry_iter->second.name) { + status = writeSectionEntry(entry_iter->second, + s_default_section_entry_indent, + upsmon_entry_separator); + } + + // Standard entry serialization + else { + status = writeSectionEntry(entry_iter->second); + } + + if (NUTW_OK != status) + return status; + } + + return NUTW_OK; +} + +} // end of namespace nut diff --git a/configure.ac b/configure.ac index e97a3fe631..69c4a4f604 100644 --- a/configure.ac +++ b/configure.ac @@ -1609,6 +1609,12 @@ dnl they are listed near the top by "./configure --help"; however, dnl note that options with further investigation methods are listed dnl a bit below to be grouped with their additional with/enable help. +dnl While the contribution is being absorbed into the NUT codebase, +dnl we don't want the CI farm to build it by default and so block NUT +dnl packaging. This toggle may be removed or switched to "yes" later. +dnl Until then, also this is not activated by --with-all flag. +NUT_ARG_WITH([nutconf], [build and install the nutconf tool (experimental, has compiler/coverage warnings)], [no]) + NUT_ARG_WITH([dev], [build and install the development files], [no]) dnl Activate WITH_UNMAPPED_DATA_POINTS for troubleshooting and evolution? @@ -2811,6 +2817,13 @@ NUT_REPORT_FEATURE([build and install the development files], [${nut_with_dev}], [WITH_DEV], [Define to enable development files support]) dnl ---------------------------------------------------------------------- +AM_CONDITIONAL(WITH_NUTCONF, test "${nut_with_nutconf}" = "yes") +NUT_REPORT_FEATURE([build and install the nutconf tool (experimental, has compiler/coverage warnings)], + [${nut_with_nutconf}], [], + [WITH_NUTCONF], [Define to enable nutconf tool support]) + +dnl ---------------------------------------------------------------------- + dnl checks related to MS Windows support (MingW) AC_CHECK_TOOL([WINDMC], [windmc], [none]) @@ -4300,6 +4313,9 @@ dnl # -Wno-padded -- NSPR and NSS headers get to issue lots of that dnl # -Wno-c++98-compat-pedantic -Wno-c++98-compat -- our C++ code uses nullptr dnl # as requested by newer linters, and C++98 does not. We require C++11 dnl # or newer anyway, and skip building C++ library and test otherwise. +dnl # -Wno-exit-time-destructors -- "(static) const something" items would be +dnl # de-allocated en-masse when the program exits, not GC'ed at run-time. +dnl # Oh well... dnl # -Wno-fuse-ld-path -- not much in our control what recipes the autotools dnl # on the build host generate... this tries to avoid failures due to: dnl # clang-13: error: '-fuse-ld=' taking a path is deprecated. @@ -4309,6 +4325,9 @@ dnl # its own good. It detects use of pointer aritmetics as arrays are dnl # walked, which is indeed potentially dangerous. And also is nearly dnl # unavoidable in C (at least not without major rewrites of the world). dnl ### Special exclusion picks for clang-medium (same as hard, plus...): +dnl # -Wno-global-constructors -- using "const something" out of method context +dnl # potentially impacts start-up time and may be prone to race conditions +dnl # (for non-trivial interconnected objects), better be re-architected. dnl # -Wno-float-conversion -Wno-double-promotion -Wno-implicit-float-conversion dnl # -- reduce noise due to floating-point literals like "3.14" being a C dnl # double type (a "3.14f" literal is a C float) cast implicitly into @@ -4346,11 +4365,11 @@ AS_CASE(["${nut_enable_warnings}"], ], [clang-hard], [ CFLAGS="${CFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -pedantic -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" - CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" + CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-exit-time-destructors -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" ], [clang-medium], [ CFLAGS="${CFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -pedantic -Wno-fuse-ld-path -Wno-unsafe-buffer-usage -Wno-float-conversion -Wno-double-promotion -Wno-implicit-float-conversion -Wno-conversion -Wno-incompatible-pointer-types-discards-qualifiers -Wno-incompatible-function-pointer-types-strict" - CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" + CXXFLAGS="${CXXFLAGS} -ferror-limit=0 -Wno-system-headers -Wall -Wextra -Weverything -Wno-disabled-macro-expansion -Wno-unused-macros -Wno-reserved-id-macro -Wno-padded -Wno-documentation -Wno-cast-qual -Wno-c++98-compat-pedantic -Wno-c++98-compat -Wno-exit-time-destructors -Wno-global-constructors -Wno-fuse-ld-path -Wno-unsafe-buffer-usage" ], [clang-minimal], [ CFLAGS="${CFLAGS} -ferror-limit=0 -Wall -Wextra -Wno-documentation" @@ -4545,6 +4564,10 @@ ABS_TOP_BUILDDIR="`cd "${TOP_BUILDDIR}" && pwd`" || AC_MSG_ERROR([Can not detect ABS_TOP_SRCDIR="`cd "${abs_srcdir}" && pwd`" || AC_MSG_ERROR([Can not detect ABS_TOP_SRCDIR]) AM_CONDITIONAL([BUILDING_IN_TREE], [test "${ABS_TOP_BUILDDIR}" = "${ABS_TOP_SRCDIR}"]) +dnl Use these at best for tests (e.g. nutconf), not production code: +AC_DEFINE_UNQUOTED([ABS_TOP_SRCDIR], ["${ABS_TOP_SRCDIR}"], [NUT source directory when the build was configured]) +AC_DEFINE_UNQUOTED([ABS_TOP_BUILDDIR], ["${ABS_TOP_BUILDDIR}"], [NUT build directory when the build was configured]) + AC_MSG_CHECKING([whether to customize ${TOP_BUILDDIR}/scripts/systemd/nut-common-tmpfiles.conf.in for this system]) dnl TOTHINK: Some distributions make the directories below owned dnl by "root:${RUN_AS_GROUP}" with 77x permissions. Is it safer?.. @@ -4713,6 +4736,7 @@ AC_CONFIG_FILES([ server/Makefile tools/Makefile tools/nut-scanner/Makefile + tools/nutconf/Makefile tests/Makefile tests/NIT/Makefile Makefile diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 532ea1a42c..de22f78916 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -98,15 +98,15 @@ HTML_CLIENT_MANS = \ upsrw.html \ upssched.html -SRC_TOOL_PAGES = nut-scanner.txt nut-recorder.txt +SRC_TOOL_PAGES = nut-scanner.txt nut-recorder.txt nutconf.txt if WITH_MANS -MAN_TOOL_PAGES = nut-scanner.8 nut-recorder.8 +MAN_TOOL_PAGES = nut-scanner.8 nut-recorder.8 nutconf.8 endif WITH_MANS man8_MANS += $(MAN_TOOL_PAGES) -HTML_TOOL_MANS = nut-scanner.html nut-recorder.html +HTML_TOOL_MANS = nut-scanner.html nut-recorder.html nutconf.html # CGI (--with-cgi) related manpages SRC_CGI_PAGES = \ diff --git a/docs/man/nutconf.txt b/docs/man/nutconf.txt new file mode 100644 index 0000000000..2485f94aaa --- /dev/null +++ b/docs/man/nutconf.txt @@ -0,0 +1,257 @@ +NUTCONF(8) +========== + + +NAME +---- +nutconf - NUT configuration tool + +SYNOPSIS +-------- +*nutconf* --help + +*nutconf* ['OPTIONS'] + +DESCRIPTION +----------- + +*nutconf* tool is used to create and manipulate NUT configuration files. +It also supports device scanning (to suggest configuration of devices). + +INSTALLATION +------------ + +The scanning feature depends on the very same compile time and run time +dependencies as the *nut-scanner*. + +OPTIONS +------- + +*-h* | *-help* | *--help*:: +Display the help text. + +*-v* | *--verbose*:: +Increase output verbosity (may be used multiple times). + +*--is-configured*:: +Checks whether NUT was configured, before. + +*--system*:: +System configuration directory shall be used. + +*--local* 'directory':: +Sets alternative configuration directory. + +*--get-mode*:: +Prints current NUT configuration mode + +*--set-mode* 'mode':: +Sets NUT configuration mode. + + :: +Known modes are: + +- standalone +- netserver +- netclient +- controlled +- manual +- none + +CONFIGURATION ENTRY SET/ADD OPTIONS +----------------------------------- + +These options mostly have 2 forms: '--set-...' or '--add-...'. + +The difference is that the set options discard previous settings +while the add options keep them. + +Note that such options may be specified multiple times for one run +(to enable setting multiple entries at once). + +*--set-monitor* | *--add-monitor* '':: +Sets/adds a NUT monitor. + + :: +Arguments: +'' '[:]' '' '' '' '(\"master\"|\"slave\")' + +*--set-listen* | *--add-listen* '
' '[]':: +Sets/adds 'upsd' daemon listen address + +*--set-device* | *--add-device* '':: +Sets/adds a device (typically a UPS). + + :: +Arguments: +'' '' '' '[=]*' + + :: +The attribute/value pairs follow device configuration syntax. +Devices may have very different configuration attributes depending on the driver. +Exhaustive description of them is beyond this man page and may be found in NUT documentation. + +*--set-notifyflags* | *--add-notifyflags* '' '+':: +Sets/adds notification flags for the notification type. + + :: +Notification types are: + +- 'ONLINE' (mains is present) +- 'ONBATT' (mains is gone) +- 'LOWBATT' (remaining battery capacity is low) +- 'FSD' (shutdown was forced) +- 'COMMOK' (communication with device established) +- 'COMMBAD' (lost communication with device) +- 'SHUTDOWN' (system is going down, now) +- 'REPLBATT' (UPS battery needs replacing) +- 'NOCOMM' (device is unavailable) +- 'NOPARENT' (upsmon parent process died, shutdown is impossible) + + :: +Notification flags: + +- 'SYSLOG' (use syslogd to log the notification) +- 'WALL' (push a message to users' terminals) +- 'EXEC' (execute a command) +- 'IGNORE' (don't act) + +*--set-notifymsg* '' '':: +Sets message for the specified notification type. + +*--set-shutdowncmd* '':: +Sets command used to shut the system down. + +*--set-user* | *--add-user* '':: +Sets/adds NUT user. + + :: +Arguments: + +- '' (specifies user name). +For 'upsmon' user, it has a special form of +'upsmon=(master|slave)' which specifies the monitoring mode. +- 'password=' sets password for the user +- 'actions=' sets actions ('SET', 'FSD' are supported) +- 'instcmds=' sets instant commands allowed for the user +(may be used multiple times) + +SCANNING OPTIONS +---------------- + +Availability of each scanning option depends on availability of +various 3rd-party libraries both at compile time and run time. + +Issue the tool with the *--help* option to check which of the +*--scan-...* options are actually supported. + +All timeouts are in microseconds. + +*--scan-snmp* '' '' '[=]*':: +Scans for SNMP devices on IP addresses from the specified range. + + :: +Known attributes are: + +- 'timeout' device scan timeout +- 'community' SNMP community (default: *public*) +- 'sec-level' security level (SNMPv3); one of *noAuthNoPriv* *authNoPriv*, *authPriv* +- 'sec-name' security name (SNMPv3); mandatory companion of *sec-level* +- 'auth-password' authentication password (SNMPv3); mandatory for *authNoPriv* and *authPriv* +- 'priv-password' privacy password (SNMPv3); mandatory for *authPriv* +- 'auth-protocol' authentication protocol (SNMPv3): *MD5* or *SHA*, *MD5* is the default +- 'priv-protocol' priv. protocol (SNMPv3): *DES* or *AES*, *DES* is the default +- 'peer-name' peer name + +*--scan-usb*:: +Scans the USB bus for known devices + +*--scan-xml-http* '[]':: +Scans for XML/HTTP devices on the network. + +*--scan-nut* '' '' '' '[]':: +Scans for NUT (pseudo-)devices on the network. + +*--scan-avahi* '[]':: +Scans for Avahi devices. + +*--scan-ipmi* '' '' '[=]'*:: +Scans for IPMI devices on IP addresses from the specified range. + + :: +Known attributes are: + +- 'username' username (mandatory for IPMI/LAN) +- 'password' user password (mandatory for IPMI/LAN) +- 'auth-type' authentication type (see below) +- 'cipher-suite-id' cipher suite ID (see below) +- 'K-g-BMC-key' optional second key (???) +- 'priv-level' priv. level +- 'workaround-flags' +- 'version' (1.5 or 2.0) + + :: +Authentication types: + :: +Specifies the IPMI 1.5 authentication type to use (NONE, STRAIGHT_PASSWORD_KEY, MD2, and MD5) with the remote host (default=MD5). +This forces connection through the 'lan' IPMI interface , thus in IPMI 1.5 mode. + +- 'none' (authentication is disabled) +- 'MD2' +- 'MD5' (default) +- 'plain-password' (no ciphering used for password sending) +- 'OEM' +- 'RMCPplus' + + :: +Cipher suite IDs: + :: +Specifies the IPMI 2.0 cipher suite ID to use. The Cipher Suite ID identifies a set of authentication, integrity, and +confidentiality algorithms to use for IPMI 2.0 communication. The authentication algorithm identifies the algorithm +to use for session setup, the integrity algorithm identifies the algorithm to use for session packet signatures, and the +confidentiality algorithm identifies the algorithm to use for payload encryption (default=3). + :: +The following cipher suite ids are currently supported (Authentication; Integrity; Confidentiality): + +- '0': None; None; None +- '1': HMAC-SHA1; None; None +- '2': HMAC-SHA1; HMAC-SHA1-96; None +- '3': HMAC-SHA1; HMAC-SHA1-96; AES-CBC-128 +- '6': HMAC-MD5; None; None +- '7': HMAC-MD5; HMAC-MD5-128; None +- '8': HMAC-MD5; HMAC-MD5-128; AES-CBC-128 +- '11': HMAC-MD5; MD5-128; None +- '12': HMAC-MD5; MD5-128; AES-CBC-128 +- '15': HMAC-SHA256; None; None +- '16': HMAC-SHA256; HMAC_SHA256_128; None +- '17': HMAC-SHA256; HMAC_SHA256_128; AES-CBC-128 + +*--scan-serial* ''*:: +Scans for serial devices (of supported types) on the specified +serial ports. + +EXAMPLES +-------- + +To set alternative directory for configuration files: + +*nutconf --local ~/test/nut/etc* + +To add another user (keeping the existing ones): + +*nutconf --add-user bart password=qwerty* + +To scan USB devices and serial devices (on the 1st two ports): + +*nutconf --scan-usb --scan-serial /dev/ttyS1 /dev/ttyS2* + +SEE ALSO +-------- + +linkman:ups.conf[5] +linkman:nut-scanner[8] + +INTERNET RESOURCES +------------------ + +The NUT (Network UPS Tools) home page: http://www.networkupstools.org/ diff --git a/docs/nut.dict b/docs/nut.dict index c07d6af523..cb4fe6a929 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3443 utf-8 +personal_ws-1.1 en 3453 utf-8 AAC AAS ABI @@ -101,6 +101,7 @@ BCD BCHARGE BCM BD +BMC BNT BOH BP @@ -823,6 +824,7 @@ NQA NTP NUT's NUTClient +NUTCONF NUTSRC NUTServer NVA @@ -1085,6 +1087,7 @@ RETPCT REXX RK RMCARD +RMCPplus RMXL RNF RNG @@ -1666,6 +1669,7 @@ backported backports backupspro badpassword +bart baseurl bashrc batchable @@ -2630,7 +2634,9 @@ noro noscanlangid nosuid notAfter +notifyflags notifyme +notifymsg notifytype notransferoids novendor @@ -2647,6 +2653,7 @@ numlogins numq nutclient nutclientmem +nutconf nutdev nutdevN nutdrv @@ -2804,6 +2811,7 @@ prereqs pretentiousVariableNamingSchemes prgshut printf +priv privPassword privProtocol problemMatcher @@ -2979,6 +2987,7 @@ sgs sha shm shutdownArguments +shutdowncmd shutdowndebounce shutdowndelay shutdownpolarity @@ -3116,6 +3125,7 @@ syscalls sysconfdir sysconfig syslog +syslogd systemctl systemd systemdshutdowndir diff --git a/include/Makefile.am b/include/Makefile.am index 7def0626d9..90c2dbc54b 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -11,6 +11,7 @@ dist_noinst_HEADERS = attribute.h common.h extstate.h proto.h \ state.h str.h timehead.h upsconf.h nut_float.h nut_stdint.h nut_platform.h \ + nutstream.hpp nutwriter.hpp nutipc.hpp nutconf.hpp \ wincompat.h # Optionally deliverable as part of NUT public API: diff --git a/include/nutconf.hpp b/include/nutconf.hpp new file mode 100644 index 0000000000..086db17e23 --- /dev/null +++ b/include/nutconf.hpp @@ -0,0 +1,1133 @@ +/* + nutconf.hpp - Nut configuration file manipulation API + + Copyright (C) + 2012 Emilien Kia + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef NUTCONF_H_SEEN +#define NUTCONF_H_SEEN 1 + +#include "nutstream.hpp" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* See include/common.h for details behind this */ +#ifndef NUT_UNUSED_VARIABLE +#define NUT_UNUSED_VARIABLE(x) (void)(x) +#endif + +#ifdef __cplusplus + +namespace nut +{ + +class NutParser; +class NutConfigParser; +class DefaultConfigParser; +class GenericConfigParser; + + +/** + * Helper to specify if a configuration variable is set or not. + * In addition of its value. + */ +template +class Settable +{ +protected: + Type _value; + bool _set; +public: + Settable():_set(false){} + Settable(const Settable& val):_value(val._value), _set(val._set){} + Settable(const Type& val):_value(val), _set(true){} + + /* Avoid implicit copy/move operator declarations */ + Settable(Settable&&) = default; + Settable& operator=(const Settable&) = default; + Settable& operator=(Settable&&) = default; + + bool set()const{return _set;} + void clear(){_set = false;} + + operator const Type&()const{return _value;} + operator Type&(){return _value;} + + const Type& operator *()const{return _value;} + Type& operator *(){return _value;} + + Settable& operator=(const Type& val){_value = val; _set = true; return *this;} + + bool operator==(const Settable& val)const + { + if(!set() && !val.set()) + return false; + else + return (set() && val.set() && _value==val._value); + } + + bool operator==(const Type& val)const + { + if(!set()) + return false; + else + return _value == val; + } + +}; + + +/** + * \brief Serialisable interface + * + * Classes that implement this interface provide way to serialize + * and deserialize instances to/from streams. + */ +class Serialisable +{ +protected: + + /** Formal constructor */ + Serialisable() {} + +public: + + /** + * \brief Deserializer + * + * \param istream Input stream + * + * \retval true in case of success + * \retval false in case of read error + */ + virtual bool parseFrom(NutStream & istream) = 0; + + /** + * \brief Serializer + * + * \param ostream Output stream + * + * \retval true in case of success + * \retval false in case of write error + */ + virtual bool writeTo(NutStream & ostream) const = 0; + + /** Destructor */ + virtual ~Serialisable(); + +}; // end of class Serialisable + + +/** + * NUT config parser. + */ +class NutParser +{ +public: + enum ParsingOption + { + OPTION_DEFAULT = 0, + /** Colon character is considered as string character and not as specific token. + Useful for IPv6 addresses */ + OPTION_IGNORE_COLON = 1 + }; + + NutParser(const char* buffer = nullptr, unsigned int options = OPTION_DEFAULT); + NutParser(const std::string& buffer, unsigned int options = OPTION_DEFAULT); + + virtual ~NutParser(); + + /** Parsing configuration functions + * \{ */ + void setOptions(unsigned int options){_options = options;} + unsigned int getOptions()const{return _options;} + void setOptions(unsigned int options, bool set = true); + void unsetOptions(unsigned int options){setOptions(options, false);} + bool hasOptions(unsigned int options)const{return (_options&options) == options;} + /** \} */ + + struct Token + { + enum TokenType { + TOKEN_UNKNOWN = -1, + TOKEN_NONE = 0, + TOKEN_STRING = 1, + TOKEN_QUOTED_STRING, + TOKEN_COMMENT, + TOKEN_BRACKET_OPEN, + TOKEN_BRACKET_CLOSE, + TOKEN_EQUAL, + TOKEN_COLON, + TOKEN_EOL + } type; + std::string str; + + Token():type(TOKEN_NONE),str(){} + Token(TokenType type_arg, const std::string& str_arg=""):type(type_arg),str(str_arg){} + Token(TokenType type_arg, char c):type(type_arg),str(1, c){} + Token(const Token& tok):type(tok.type),str(tok.str){} + + /* Avoid implicit copy/move operator declarations */ + Token(Token&&) = default; + Token& operator=(const Token&) = default; + Token& operator=(Token&&) = default; + + bool is(TokenType type_arg)const{return this->type==type_arg;} + + bool operator==(const Token& tok)const{return tok.type==type && tok.str==str;} + + operator bool()const{return type!=TOKEN_UNKNOWN && type!=TOKEN_NONE;} + }; + + /** Parsing functions + * \{ */ + std::string parseCHARS(); + std::string parseSTRCHARS(); + Token parseToken(); + std::list parseLine(); + /** \} */ + +#ifndef UNITEST_MODE +protected: +#endif /* UNITEST_MODE */ + size_t getPos()const; + void setPos(size_t pos); + char charAt(size_t pos)const; + + void pushPos(); + size_t popPos(); + void rewind(); + + void back(); + + char get(); + char peek(); + +private: + unsigned int _options; + + std::string _buffer; + size_t _pos; + std::vector _stack; +}; + + +typedef std::list ConfigParamList; + +struct GenericConfigSectionEntry +{ + std::string name; + ConfigParamList values; + // std::string comment; + +}; + +struct GenericConfigSection +{ + /** Section entries map */ + typedef std::map EntryMap; + + std::string name; + // std::string comment; + EntryMap entries; + + const GenericConfigSectionEntry& operator [] (const std::string& varname)const{return entries.find(varname)->second;} + GenericConfigSectionEntry& operator [] (const std::string& varname){return entries[varname];} + + bool empty()const; + void clear(); +}; + +class BaseConfiguration +{ + friend class GenericConfigParser; +public: + virtual ~BaseConfiguration(); +protected: + virtual void setGenericConfigSection(const GenericConfigSection& section) = 0; +}; + +class NutConfigParser : public NutParser +{ +public: + virtual void parseConfig(); + + /* Declared for cleaner overrides; arg ignored in current class */ + virtual void parseConfig(BaseConfiguration* config); + +protected: + NutConfigParser(const char* buffer = nullptr, unsigned int options = OPTION_DEFAULT); + NutConfigParser(const std::string& buffer, unsigned int options = OPTION_DEFAULT); + + virtual void onParseBegin()=0; + virtual void onParseComment(const std::string& comment)=0; + virtual void onParseSectionName(const std::string& sectionName, const std::string& comment = "")=0; + virtual void onParseDirective(const std::string& directiveName, char sep = 0, const ConfigParamList& values = ConfigParamList(), const std::string& comment = "")=0; + virtual void onParseEnd()=0; +}; + +class DefaultConfigParser : public NutConfigParser +{ +public: + DefaultConfigParser(const char* buffer = nullptr); + DefaultConfigParser(const std::string& buffer); + +protected: + virtual void onParseSection(const GenericConfigSection& section)=0; + + virtual void onParseBegin() override; + virtual void onParseComment(const std::string& comment) override; + virtual void onParseSectionName(const std::string& sectionName, const std::string& comment = "") override; + virtual void onParseDirective(const std::string& directiveName, char sep = 0, const ConfigParamList& values = ConfigParamList(), const std::string& comment = "") override; + virtual void onParseEnd() override; + + GenericConfigSection _section; ///> Currently parsed section +}; + + +class GenericConfigParser : public DefaultConfigParser +{ +public: + GenericConfigParser(const char* buffer = nullptr); + GenericConfigParser(const std::string& buffer); + + virtual void parseConfig(BaseConfiguration* config) override; + +protected: + virtual void onParseSection(const GenericConfigSection& section) override; + + BaseConfiguration* _config; +}; + + +class GenericConfiguration : public BaseConfiguration, public Serialisable +{ +public: + /** Sections map */ + typedef std::map SectionMap; + + GenericConfiguration(){} + + virtual ~GenericConfiguration() override; + + void parseFromString(const std::string& str); + + /** Serialisable interface implementation \{ */ + bool parseFrom(NutStream & istream) override; + bool writeTo(NutStream & ostream) const override; + /** \} */ + + // FIXME Let be public or set it as protected with public accessors ? + SectionMap sections; + + const GenericConfigSection& operator[](const std::string& secname)const{return sections.find(secname)->second;} + GenericConfigSection& operator[](const std::string& secname){return sections[secname];} + + +protected: + virtual void setGenericConfigSection(const GenericConfigSection& section) override; + + /** + * \brief Configuration parameters getter + * + * \param[in] section Section name + * \param[in] entry Entry name + * \param[out] params Configuration parameters + * + * \retval true if the entry was found + * \retval false otherwise + */ + bool get(const std::string & section, const std::string & entry, ConfigParamList & params) const; + + /** + * \brief Global scope configuration parameters getter + * + * \param[in] entry Entry name + * \param[out] params Configuration parameters + * + * \retval true if the entry was found + * \retval false otherwise + */ + inline bool get(const std::string & entry, ConfigParamList & params) const + { + return get("", entry, params); + } + + /** + * \brief Configuration parameters setter + * + * The section and entry are created unless they already exist. + * + * \param[in] section Section name + * \param[in] entry Entry name + * \param[in] params Configuration parameters + */ + void set(const std::string & section, const std::string & entry, const ConfigParamList & params); + + /** + * \brief Global scope configuration parameters setter + * + * The entry is created unless it already exists. + * + * \param[in] entry Entry name + * \param[in] params Configuration parameters + */ + inline void set(const std::string & entry, const ConfigParamList & params) + { + set("", entry, params); + } + + /** + * \brief Add configuration parameters + * + * The section and entry are created unless they already exist. + * Current parameters are kept, the provided are added to the list end. + * + * \param[in] section Section name + * \param[in] entry Entry name + * \param[in] params Configuration parameters + */ + void add(const std::string & section, const std::string & entry, const ConfigParamList & params); + + /** + * \brief Add global scope configuration parameters + * + * The entry is created unless they already exists. + * Current parameters are kept, the provided are added to the list end. + * + * \param[in] entry Entry name + * \param[in] params Configuration parameters + */ + inline void add(const std::string & entry, const ConfigParamList & params) + { + add("", entry, params); + } + + /** + * \brief Configuration parameters removal + * + * Removes the entry, only. + * Does nothing if the section or the entry don't exist. + * + * \param section Section name + * \param entry Entry name + */ + void remove(const std::string & section, const std::string & entry); + + /** + * \brief Global scope configuration parameters removal + * + * Removes the entry, only. + * Does nothing if the entry don't exist. + * + * \param entry Entry name + */ + inline void remove(const std::string & entry) + { + remove("", entry); + } + + /** + * \brief Configuration section removal + * + * Removes entire section (if exists). + * + * \param section Section name + */ + void removeSection(const std::string & section); + + /** Global scope configuration removal */ + inline void removeGlobal() + { + removeSection(""); + } + + /** + * \brief Configuration string getter + * + * Empty string is returned if the section or entry doesn't exist. + * + * \param section Section name + * \param entry Entry name + * + * \return Configuration parameter as string + */ + std::string getStr( + const std::string & section, + const std::string & entry) const; + + /** + * \brief Global scope configuration string getter + * + * Empty string is returned if the entry doesn't exist. + * + * \param entry Entry name + * + * \return Configuration parameter as string + */ + inline std::string getStr(const std::string & entry) const + { + return getStr("", entry); + } + + /** + * \brief Configuration string setter + * + * \param section Section name + * \param entry Entry name + * \param value Parameter value + */ + void setStr( + const std::string & section, + const std::string & entry, + const std::string & value); + + /** + * \brief Global scope configuration string setter + * + * \param entry Entry name + * \param value Parameter value + */ + inline void setStr( + const std::string & entry, + const std::string & value) + { + setStr("", entry, value); + } + + /** + * \brief Configuration number getter + * + * \param section Section name + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as number (or the default if not defined) + */ + long long int getInt( + const std::string & section, + const std::string & entry, + long long int val = 0) const; + + /** + * \brief Global scope configuration number getter + * + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as number (or the default if not defined) + */ + inline long long int getInt(const std::string & entry, long long int val = 0) const + { + return getInt("", entry, val); + } + + /** + * \brief Configuration number setter + * + * \param section Section name + * \param entry Entry name + * \param val Default value + */ + void setInt( + const std::string & section, + const std::string & entry, + long long int val); + + /** + * \brief Global scope configuration number setter + * + * \param entry Entry name + * \param val Default value + */ + inline void setInt( + const std::string & entry, + long long int val) + { + setInt("", entry, val); + } + + /** + * \brief Cast numeric type with range check + * + * Throws an exception on cast error. + * + * \param number Number + * \param min Minimum + * \param max Maximum + * + * \return \c number which was cast to target type + */ + template + static T range_cast(long long int number, long long int min, long long int max) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::range_error) +#endif + { + if (number < min) + { + std::stringstream e; + e << "Failed to range-cast " << number << " (underflows " << min << ')'; + + throw std::range_error(e.str()); + } + + if (number > max) + { + std::stringstream e; + e << "Failed to range-cast " << number << " (overflows " << max << ')'; + + throw std::range_error(e.str()); + } + + return static_cast(number); + } + + /** + * \brief Resolve string as Boolean value + * + * \param str String + * + * \retval true IFF the string expresses a known true value + * \retval false otherwise + */ + static bool str2bool(const std::string & str); + + /** + * \brief Convert Boolean value to string + * + * \param val Boolean value + * + * \return \c val as string + */ + static const std::string & bool2str(bool val); + +}; // end of class GenericConfiguration + + + +class UpsmonConfiguration : public Serialisable +{ +public: + UpsmonConfiguration(); + void parseFromString(const std::string& str); + + Settable runAsUser, shutdownCmd, notifyCmd, powerDownFlag; + Settable minSupplies, poolFreq, poolFreqAlert, hotSync; + Settable deadTime, rbWarnTime, noCommWarnTime, finalDelay; + + enum NotifyFlag { + NOTIFY_IGNORE = 0, + NOTIFY_SYSLOG = 1, + NOTIFY_WALL = 1 << 1, + NOTIFY_EXEC = 1 << 2 + }; + + enum NotifyType { + NOTIFY_ONLINE, + NOTIFY_ONBATT, + NOTIFY_LOWBATT, + NOTIFY_FSD, + NOTIFY_COMMOK, + NOTIFY_COMMBAD, + NOTIFY_SHUTDOWN, + NOTIFY_REPLBATT, + NOTIFY_NOCOMM, + NOTIFY_NOPARENT, + NOTIFY_TYPE_MAX + }; + + static NotifyFlag NotifyFlagFromString(const std::string& str); + static NotifyType NotifyTypeFromString(const std::string& str); + + Settable notifyFlags[NOTIFY_TYPE_MAX]; + Settable notifyMessages[NOTIFY_TYPE_MAX]; + + struct Monitor { + std::string upsname, hostname; + uint16_t port; + unsigned int powerValue; + std::string username, password; + bool isMaster; + }; + + std::list monitors; + + /** Serialisable interface implementation \{ */ + bool parseFrom(NutStream & istream) override; + bool writeTo(NutStream & ostream) const override; + /** \} */ + +}; // end of class UpsmonConfiguration + + + +class UpsmonConfigParser : public NutConfigParser +{ +public: + UpsmonConfigParser(const char* buffer = nullptr); + UpsmonConfigParser(const std::string& buffer); + + void parseUpsmonConfig(UpsmonConfiguration* config); +protected: + virtual void onParseBegin() override; + virtual void onParseComment(const std::string& comment) override; + virtual void onParseSectionName(const std::string& sectionName, const std::string& comment = "") override; + virtual void onParseDirective(const std::string& directiveName, char sep = 0, const ConfigParamList& values = ConfigParamList(), const std::string& comment = "") override; + virtual void onParseEnd() override; + + UpsmonConfiguration* _config; +}; + + +class NutConfiguration: public Serialisable +{ +public: + NutConfiguration(); + void parseFromString(const std::string& str); + + enum NutMode { + MODE_UNKNOWN = -1, + MODE_NONE = 0, + MODE_STANDALONE, + MODE_NETSERVER, + MODE_NETCLIENT, + MODE_CONTROLLED, + MODE_MANUAL, + }; + + Settable mode; + + static NutMode NutModeFromString(const std::string& str); + + /** Serialisable interface implementation \{ */ + bool parseFrom(NutStream & istream) override; + bool writeTo(NutStream & ostream) const override; + /** \} */ +}; + + +class NutConfConfigParser : public NutConfigParser +{ +public: + NutConfConfigParser(const char* buffer = nullptr); + NutConfConfigParser(const std::string& buffer); + + void parseNutConfConfig(NutConfiguration* config); +protected: + virtual void onParseBegin() override; + virtual void onParseComment(const std::string& comment) override; + virtual void onParseSectionName(const std::string& sectionName, const std::string& comment = "") override; + virtual void onParseDirective(const std::string& directiveName, char sep = 0, const ConfigParamList& values = ConfigParamList(), const std::string& comment = "") override; + virtual void onParseEnd() override; + + NutConfiguration* _config; +}; + + +class UpsdConfiguration : public Serialisable +{ +public: + UpsdConfiguration(); + void parseFromString(const std::string& str); + + Settable maxAge, maxConn; + Settable statePath, certFile; + + struct Listen + { + std::string address; + Settable port; + + inline bool operator==(const Listen& listen)const + { + return address == listen.address && port == listen.port; + } + }; + std::list listens; + + /** Serialisable interface implementation \{ */ + bool parseFrom(NutStream & istream) override; + bool writeTo(NutStream & ostream) const override; + /** \} */ +}; + + + + +class UpsdConfigParser : public NutConfigParser +{ +public: + UpsdConfigParser(const char* buffer = nullptr); + UpsdConfigParser(const std::string& buffer); + + void parseUpsdConfig(UpsdConfiguration* config); +protected: + virtual void onParseBegin() override; + virtual void onParseComment(const std::string& comment) override; + virtual void onParseSectionName(const std::string& sectionName, const std::string& comment = "") override; + virtual void onParseDirective(const std::string& directiveName, char sep = 0, const ConfigParamList& values = ConfigParamList(), const std::string& comment = "") override; + virtual void onParseEnd() override; + + UpsdConfiguration* _config; +}; + + +/** UPS configuration */ +class UpsConfiguration : public GenericConfiguration +{ +public: + /** Global configuration attributes getters and setters \{ */ + + inline std::string getChroot() const { return getStr("chroot"); } + inline std::string getDriverPath() const { return getStr("driverpath"); } + inline std::string getUser() const { return getStr("user"); } + + inline long long int getMaxStartDelay() const { return getInt("maxstartdelay"); } + inline long long int getPollInterval() const { return getInt("pollinterval", 5); } // TODO: check the default + + inline void setChroot(const std::string & path) { setStr("chroot", path); } + inline void setDriverPath(const std::string & path) { setStr("driverpath", path); } + inline void setUser(const std::string & user) { setStr("user", user); } + + inline void setMaxStartDelay(long long int delay) { setInt("maxstartdelay", delay); } + inline void setPollInterval(long long int interval) { setInt("pollinterval", interval); } + + /** \} */ + + /** Generic = getter */ + inline std::string getKey(const std::string & ups, const std::string & key) const { return getStr(ups, key); } + + /** Generic = setter */ + inline void setKey(const std::string & ups, const std::string & key, const std::string & val) { + setStr(ups, key, val); + } + + /** UPS-specific configuration attributes getters and setters \{ */ + inline std::string getDriver(const std::string & ups) const { return getStr(ups, "driver"); } + inline std::string getDescription(const std::string & ups) const { return getStr(ups, "desc"); } + inline std::string getCP(const std::string & ups) const { return getStr(ups, "CP"); } + inline std::string getCS(const std::string & ups) const { return getStr(ups, "CS"); } + inline std::string getID(const std::string & ups) const { return getStr(ups, "ID"); } + inline std::string getLB(const std::string & ups) const { return getStr(ups, "LB"); } + inline std::string getLowBatt(const std::string & ups) const { return getStr(ups, "LowBatt"); } + inline std::string getOL(const std::string & ups) const { return getStr(ups, "OL"); } + inline std::string getSD(const std::string & ups) const { return getStr(ups, "SD"); } + inline std::string getAuthPassword(const std::string & ups) const { return getStr(ups, "authPassword"); } + inline std::string getAuthProtocol(const std::string & ups) const { return getStr(ups, "authProtocol"); } + inline std::string getAuthType(const std::string & ups) const { return getStr(ups, "authtype"); } + inline std::string getAWD(const std::string & ups) const { return getStr(ups, "awd"); } + inline std::string getBatText(const std::string & ups) const { return getStr(ups, "battext"); } + inline std::string getBus(const std::string & ups) const { return getStr(ups, "bus"); } + inline std::string getCommunity(const std::string & ups) const { return getStr(ups, "community"); } + inline std::string getFRUID(const std::string & ups) const { return getStr(ups, "fruid"); } + inline std::string getLoadStatus(const std::string & ups) const { return getStr(ups, "load.status"); } + inline std::string getLogin(const std::string & ups) const { return getStr(ups, "login"); } + inline std::string getLowbatt(const std::string & ups) const { return getStr(ups, "lowbatt"); } + inline std::string getManufacturer(const std::string & ups) const { return getStr(ups, "manufacturer"); } + inline std::string getMethodOfFlowControl(const std::string & ups) const { return getStr(ups, "methodOfFlowControl"); } + inline std::string getMIBs(const std::string & ups) const { return getStr(ups, "mibs"); } + inline std::string getModel(const std::string & ups) const { return getStr(ups, "model"); } + inline std::string getModelName(const std::string & ups) const { return getStr(ups, "modelname"); } + inline std::string getNotification(const std::string & ups) const { return getStr(ups, "notification"); } + inline std::string getOldMAC(const std::string & ups) const { return getStr(ups, "oldmac"); } + inline std::string getPassword(const std::string & ups) const { return getStr(ups, "password"); } + inline std::string getPort(const std::string & ups) const { return getStr(ups, "port"); } + inline std::string getPrefix(const std::string & ups) const { return getStr(ups, "prefix"); } + inline std::string getPrivPassword(const std::string & ups) const { return getStr(ups, "privPassword"); } + inline std::string getPrivProtocol(const std::string & ups) const { return getStr(ups, "privProtocol"); } + inline std::string getProduct(const std::string & ups) const { return getStr(ups, "product"); } + inline std::string getProductID(const std::string & ups) const { return getStr(ups, "productid"); } + inline std::string getProtocol(const std::string & ups) const { return getStr(ups, "protocol"); } + inline std::string getRuntimeCal(const std::string & ups) const { return getStr(ups, "runtimecal"); } + inline std::string getSDType(const std::string & ups) const { return getStr(ups, "sdtype"); } + inline std::string getSecLevel(const std::string & ups) const { return getStr(ups, "secLevel"); } + inline std::string getSecName(const std::string & ups) const { return getStr(ups, "secName"); } + inline std::string getSensorID(const std::string & ups) const { return getStr(ups, "sensorid"); } + inline std::string getSerial(const std::string & ups) const { return getStr(ups, "serial"); } + inline std::string getSerialNumber(const std::string & ups) const { return getStr(ups, "serialnumber"); } + inline std::string getShutdownArguments(const std::string & ups) const { return getStr(ups, "shutdownArguments"); } + inline std::string getSNMPversion(const std::string & ups) const { return getStr(ups, "snmp_version"); } + inline std::string getSubdriver(const std::string & ups) const { return getStr(ups, "subdriver"); } + inline std::string getType(const std::string & ups) const { return getStr(ups, "type"); } + inline std::string getUPStype(const std::string & ups) const { return getStr(ups, "upstype"); } + inline std::string getUSD(const std::string & ups) const { return getStr(ups, "usd"); } + inline std::string getUsername(const std::string & ups) const { return getStr(ups, "username"); } + inline std::string getValidationSequence(const std::string & ups) const { return getStr(ups, "validationSequence"); } + inline std::string getVendor(const std::string & ups) const { return getStr(ups, "vendor"); } + inline std::string getVendorID(const std::string & ups) const { return getStr(ups, "vendorid"); } + inline std::string getWUGrace(const std::string & ups) const { return getStr(ups, "wugrace"); } + + + inline long long int getSDOrder(const std::string & ups) const { return getInt(ups, "sdorder"); } // TODO: Is that a number? + inline long long int getMaxStartDelay(const std::string & ups) const { return getInt(ups, "maxstartdelay"); } + inline long long int getAdvOrder(const std::string & ups) const { return getInt(ups, "advorder"); } // CHECKME + inline long long int getBatteryPercentage(const std::string & ups) const { return getInt(ups, "batteryPercentage"); } // CHECKME + inline long long int getOffDelay(const std::string & ups) const { return getInt(ups, "OffDelay"); } // CHECKME + inline long long int getOnDelay(const std::string & ups) const { return getInt(ups, "OnDelay"); } // CHECKME + inline long long int getBattVoltMult(const std::string & ups) const { return getInt(ups, "battvoltmult"); } // CHECKME + inline long long int getBaudRate(const std::string & ups) const { return getInt(ups, "baud_rate"); } // CHECKME + inline long long int getBaudrate(const std::string & ups) const { return getInt(ups, "baudrate"); } // CHECKME + inline long long int getCablePower(const std::string & ups) const { return getInt(ups, "cablepower"); } // CHECKME + inline long long int getChargeTime(const std::string & ups) const { return getInt(ups, "chargetime"); } // CHECKME + inline long long int getDaysOff(const std::string & ups) const { return getInt(ups, "daysoff"); } // CHECKME + inline long long int getDaySweek(const std::string & ups) const { return getInt(ups, "daysweek"); } // CHECKME + inline long long int getFrequency(const std::string & ups) const { return getInt(ups, "frequency"); } // CHECKME + inline long long int getHourOff(const std::string & ups) const { return getInt(ups, "houroff"); } // CHECKME + inline long long int getHourOn(const std::string & ups) const { return getInt(ups, "houron"); } // CHECKME + inline long long int getIdleLoad(const std::string & ups) const { return getInt(ups, "idleload"); } // CHECKME + inline long long int getInputTimeout(const std::string & ups) const { return getInt(ups, "input_timeout"); } // CHECKME + inline long long int getLineVoltage(const std::string & ups) const { return getInt(ups, "linevoltage"); } // CHECKME + inline long long int getLoadpercentage(const std::string & ups) const { return getInt(ups, "loadPercentage"); } // CHECKME + inline long long int getMaxLoad(const std::string & ups) const { return getInt(ups, "max_load"); } // CHECKME + inline long long int getMFR(const std::string & ups) const { return getInt(ups, "mfr"); } // CHECKME + inline long long int getMinCharge(const std::string & ups) const { return getInt(ups, "mincharge"); } // CHECKME + inline long long int getMinRuntime(const std::string & ups) const { return getInt(ups, "minruntime"); } // CHECKME + inline long long int getNomBattVolt(const std::string & ups) const { return getInt(ups, "nombattvolt"); } // CHECKME + inline long long int getNumOfBytesFromUPS(const std::string & ups) const { return getInt(ups, "numOfBytesFromUPS"); } // CHECKME + inline long long int getOffdelay(const std::string & ups) const { return getInt(ups, "offdelay"); } // CHECKME + inline long long int getOndelay(const std::string & ups) const { return getInt(ups, "ondelay"); } // CHECKME + inline long long int getOutputPace(const std::string & ups) const { return getInt(ups, "output_pace"); } // CHECKME + inline long long int getPollFreq(const std::string & ups) const { return getInt(ups, "pollfreq"); } // CHECKME + inline long long int getPowerUp(const std::string & ups) const { return getInt(ups, "powerup"); } // CHECKME + inline long long int getPrgShut(const std::string & ups) const { return getInt(ups, "prgshut"); } // CHECKME + inline long long int getRebootDelay(const std::string & ups) const { return getInt(ups, "rebootdelay"); } // CHECKME + inline long long int getSDtime(const std::string & ups) const { return getInt(ups, "sdtime"); } // CHECKME + inline long long int getShutdownDelay(const std::string & ups) const { return getInt(ups, "shutdown_delay"); } // CHECKME + inline long long int getStartDelay(const std::string & ups) const { return getInt(ups, "startdelay"); } // CHECKME + inline long long int getTestTime(const std::string & ups) const { return getInt(ups, "testtime"); } // CHECKME + inline long long int getTimeout(const std::string & ups) const { return getInt(ups, "timeout"); } // CHECKME + inline long long int getUPSdelayShutdown(const std::string & ups) const { return getInt(ups, "ups.delay.shutdown"); } // CHECKME + inline long long int getUPSdelayStart(const std::string & ups) const { return getInt(ups, "ups.delay.start"); } // CHECKME + inline long long int getVoltage(const std::string & ups) const { return getInt(ups, "voltage"); } // CHECKME + inline long long int getWait(const std::string & ups) const { return getInt(ups, "wait"); } // CHECKME + + inline bool getNolock(const std::string & ups) const { return str2bool(getStr(ups, "nolock")); } + inline bool getCable(const std::string & ups) const { return str2bool(getStr(ups, "cable")); } + inline bool getDumbTerm(const std::string & ups) const { return str2bool(getStr(ups, "dumbterm")); } + inline bool getExplore(const std::string & ups) const { return str2bool(getStr(ups, "explore")); } + inline bool getFakeLowBatt(const std::string & ups) const { return str2bool(getStr(ups, "fake_lowbatt")); } + inline bool getFlash(const std::string & ups) const { return str2bool(getStr(ups, "flash")); } + inline bool getFullUpdate(const std::string & ups) const { return str2bool(getStr(ups, "full_update")); } + inline bool getLangIDfix(const std::string & ups) const { return str2bool(getStr(ups, "langid_fix")); } + inline bool getLoadOff(const std::string & ups) const { return str2bool(getStr(ups, "load.off")); } + inline bool getLoadOn(const std::string & ups) const { return str2bool(getStr(ups, "load.on")); } + inline bool getNoHang(const std::string & ups) const { return str2bool(getStr(ups, "nohang")); } + inline bool getNoRating(const std::string & ups) const { return str2bool(getStr(ups, "norating")); } + inline bool getNoTransferOIDs(const std::string & ups) const { return str2bool(getStr(ups, "notransferoids")); } + inline bool getNoVendor(const std::string & ups) const { return str2bool(getStr(ups, "novendor")); } + inline bool getNoWarnNoImp(const std::string & ups) const { return str2bool(getStr(ups, "nowarn_noimp")); } + inline bool getPollOnly(const std::string & ups) const { return str2bool(getStr(ups, "pollonly")); } + inline bool getSilent(const std::string & ups) const { return str2bool(getStr(ups, "silent")); } + inline bool getStatusOnly(const std::string & ups) const { return str2bool(getStr(ups, "status_only")); } + inline bool getSubscribe(const std::string & ups) const { return str2bool(getStr(ups, "subscribe")); } + inline bool getUseCRLF(const std::string & ups) const { return str2bool(getStr(ups, "use_crlf")); } + inline bool getUsePreLF(const std::string & ups) const { return str2bool(getStr(ups, "use_pre_lf")); } + + + inline void setDriver(const std::string & ups, const std::string & driver) { setStr(ups, "driver", driver); } + inline void setDescription(const std::string & ups, const std::string & desc) { setStr(ups, "desc", desc); } + inline void setLowBatt(const std::string & ups, const std::string & lowbatt) { setStr(ups, "LowBatt", lowbatt); } + inline void setOL(const std::string & ups, const std::string & ol) { setStr(ups, "OL", ol); } + inline void setSD(const std::string & ups, const std::string & sd) { setStr(ups, "SD", sd); } + inline void setAuthPassword(const std::string & ups, const std::string & auth_passwd) { setStr(ups, "authPassword", auth_passwd); } + inline void setAuthProtocol(const std::string & ups, const std::string & auth_proto) { setStr(ups, "authProtocol", auth_proto); } + inline void setAuthType(const std::string & ups, const std::string & authtype) { setStr(ups, "authtype", authtype); } + inline void setAWD(const std::string & ups, const std::string & awd) { setStr(ups, "awd", awd); } + inline void setBatText(const std::string & ups, const std::string & battext) { setStr(ups, "battext", battext); } + inline void setBus(const std::string & ups, const std::string & bus) { setStr(ups, "bus", bus); } + inline void setCommunity(const std::string & ups, const std::string & community) { setStr(ups, "community", community); } + inline void setFRUID(const std::string & ups, const std::string & fruid) { setStr(ups, "fruid", fruid); } + inline void setLoadStatus(const std::string & ups, const std::string & load_status) { setStr(ups, "load.status", load_status); } + inline void setLogin(const std::string & ups, const std::string & login) { setStr(ups, "login", login); } + inline void setLowbatt(const std::string & ups, const std::string & lowbatt) { setStr(ups, "lowbatt", lowbatt); } + inline void setManufacturer(const std::string & ups, const std::string & manufacturer) { setStr(ups, "manufacturer", manufacturer); } + inline void setMethodOfFlowControl(const std::string & ups, const std::string & method) { setStr(ups, "methodOfFlowControl", method); } + inline void setMIBs(const std::string & ups, const std::string & mibs) { setStr(ups, "mibs", mibs); } + inline void setModel(const std::string & ups, const std::string & model) { setStr(ups, "model", model); } + inline void setModelName(const std::string & ups, const std::string & modelname) { setStr(ups, "modelname", modelname); } + inline void setNotification(const std::string & ups, const std::string & notification) { setStr(ups, "notification", notification); } + inline void setOldMAC(const std::string & ups, const std::string & oldmac) { setStr(ups, "oldmac", oldmac); } + inline void setPassword(const std::string & ups, const std::string & password) { setStr(ups, "password", password); } + inline void setPort(const std::string & ups, const std::string & port) { setStr(ups, "port", port); } + inline void setPrefix(const std::string & ups, const std::string & prefix) { setStr(ups, "prefix", prefix); } + inline void setPrivPassword(const std::string & ups, const std::string & priv_passwd) { setStr(ups, "privPassword", priv_passwd); } + inline void setPrivProtocol(const std::string & ups, const std::string & priv_proto) { setStr(ups, "privProtocol", priv_proto); } + inline void setProduct(const std::string & ups, const std::string & product) { setStr(ups, "product", product); } + inline void setProductID(const std::string & ups, const std::string & productid) { setStr(ups, "productid", productid); } + inline void setProtocol(const std::string & ups, const std::string & protocol) { setStr(ups, "protocol", protocol); } + inline void setRuntimeCal(const std::string & ups, const std::string & runtimecal) { setStr(ups, "runtimecal", runtimecal); } + inline void setSDtype(const std::string & ups, const std::string & sdtype) { setStr(ups, "sdtype", sdtype); } + inline void setSecLevel(const std::string & ups, const std::string & sec_level) { setStr(ups, "secLevel", sec_level); } + inline void setSecName(const std::string & ups, const std::string & sec_name) { setStr(ups, "secName", sec_name); } + inline void setSensorID(const std::string & ups, const std::string & sensorid) { setStr(ups, "sensorid", sensorid); } + inline void setSerial(const std::string & ups, const std::string & serial) { setStr(ups, "serial", serial); } + inline void setSerialNumber(const std::string & ups, const std::string & serialnumber) { setStr(ups, "serialnumber", serialnumber); } + inline void setShutdownArguments(const std::string & ups, const std::string & sd_args) { setStr(ups, "shutdownArguments", sd_args); } + inline void setSNMPversion(const std::string & ups, const std::string & snmp_version) { setStr(ups, "snmp_version", snmp_version); } + inline void setSubdriver(const std::string & ups, const std::string & subdriver) { setStr(ups, "subdriver", subdriver); } + inline void setType(const std::string & ups, const std::string & type) { setStr(ups, "type", type); } + inline void setUPStype(const std::string & ups, const std::string & upstype) { setStr(ups, "upstype", upstype); } + inline void setUSD(const std::string & ups, const std::string & usd) { setStr(ups, "usd", usd); } + inline void setUsername(const std::string & ups, const std::string & username) { setStr(ups, "username", username); } + inline void setValidationSequence(const std::string & ups, const std::string & valid_seq) { setStr(ups, "validationSequence", valid_seq); } + inline void setVendor(const std::string & ups, const std::string & vendor) { setStr(ups, "vendor", vendor); } + inline void setVendorID(const std::string & ups, const std::string & vendorid) { setStr(ups, "vendorid", vendorid); } + inline void setWUGrace(const std::string & ups, const std::string & wugrace) { setStr(ups, "wugrace", wugrace); } + + inline void setSDOrder(const std::string & ups, long long int ord) { setInt(ups, "sdorder", ord); } + inline void setMaxStartDelay(const std::string & ups, long long int delay) { setInt(ups, "maxstartdelay", delay); } + inline void setADVorder(const std::string & ups, long long int advorder) { setInt(ups, "advorder", advorder); } // CHECKME + inline void setBatteryPercentage(const std::string & ups, long long int batt) { setInt(ups, "batteryPercentage", batt); } // CHECKME + inline void setOffDelay(const std::string & ups, long long int offdelay) { setInt(ups, "OffDelay", offdelay); } // CHECKME + inline void setOnDelay(const std::string & ups, long long int ondelay) { setInt(ups, "OnDelay", ondelay); } // CHECKME + inline void setBattVoltMult(const std::string & ups, long long int mult) { setInt(ups, "battvoltmult", mult); } // CHECKME + inline void setBaudRate(const std::string & ups, long long int baud_rate) { setInt(ups, "baud_rate", baud_rate); } // CHECKME + inline void setBaudrate(const std::string & ups, long long int baudrate) { setInt(ups, "baudrate", baudrate); } // CHECKME + inline void setCablePower(const std::string & ups, long long int cablepower) { setInt(ups, "cablepower", cablepower); } // CHECKME + inline void setChargeTime(const std::string & ups, long long int chargetime) { setInt(ups, "chargetime", chargetime); } // CHECKME + inline void setDaysOff(const std::string & ups, long long int daysoff) { setInt(ups, "daysoff", daysoff); } // CHECKME + inline void setDaysWeek(const std::string & ups, long long int daysweek) { setInt(ups, "daysweek", daysweek); } // CHECKME + inline void setFrequency(const std::string & ups, long long int frequency) { setInt(ups, "frequency", frequency); } // CHECKME + inline void setHourOff(const std::string & ups, long long int houroff) { setInt(ups, "houroff", houroff); } // CHECKME + inline void setHourOn(const std::string & ups, long long int houron) { setInt(ups, "houron", houron); } // CHECKME + inline void setIdleLoad(const std::string & ups, long long int idleload) { setInt(ups, "idleload", idleload); } // CHECKME + inline void setInputTimeout(const std::string & ups, long long int timeout) { setInt(ups, "input_timeout", timeout); } // CHECKME + inline void setLineVoltage(const std::string & ups, long long int linevoltage) { setInt(ups, "linevoltage", linevoltage); } // CHECKME + inline void setLoadpercentage(const std::string & ups, long long int load) { setInt(ups, "loadPercentage", load); } // CHECKME + inline void setMaxLoad(const std::string & ups, long long int max_load) { setInt(ups, "max_load", max_load); } // CHECKME + inline void setMFR(const std::string & ups, long long int mfr) { setInt(ups, "mfr", mfr); } // CHECKME + inline void setMinCharge(const std::string & ups, long long int mincharge) { setInt(ups, "mincharge", mincharge); } // CHECKME + inline void setMinRuntime(const std::string & ups, long long int minruntime) { setInt(ups, "minruntime", minruntime); } // CHECKME + inline void setNomBattVolt(const std::string & ups, long long int nombattvolt) { setInt(ups, "nombattvolt", nombattvolt); } // CHECKME + inline void setNumOfBytesFromUPS(const std::string & ups, long long int bytes) { setInt(ups, "numOfBytesFromUPS", bytes); } // CHECKME + inline void setOffdelay(const std::string & ups, long long int offdelay) { setInt(ups, "offdelay", offdelay); } // CHECKME + inline void setOndelay(const std::string & ups, long long int ondelay) { setInt(ups, "ondelay", ondelay); } // CHECKME + inline void setOutputPace(const std::string & ups, long long int output_pace) { setInt(ups, "output_pace", output_pace); } // CHECKME + inline void setPollFreq(const std::string & ups, long long int pollfreq) { setInt(ups, "pollfreq", pollfreq); } // CHECKME + inline void setPowerUp(const std::string & ups, long long int powerup) { setInt(ups, "powerup", powerup); } // CHECKME + inline void setPrgShut(const std::string & ups, long long int prgshut) { setInt(ups, "prgshut", prgshut); } // CHECKME + inline void setRebootDelay(const std::string & ups, long long int delay) { setInt(ups, "rebootdelay", delay); } // CHECKME + inline void setSDtime(const std::string & ups, long long int sdtime) { setInt(ups, "sdtime", sdtime); } // CHECKME + inline void setShutdownDelay(const std::string & ups, long long int delay) { setInt(ups, "shutdown_delay", delay); } // CHECKME + inline void setStartDelay(const std::string & ups, long long int delay) { setInt(ups, "startdelay", delay); } // CHECKME + inline void setTestTime(const std::string & ups, long long int testtime) { setInt(ups, "testtime", testtime); } // CHECKME + inline void setTimeout(const std::string & ups, long long int timeout) { setInt(ups, "timeout", timeout); } // CHECKME + inline void setUPSdelayShutdown(const std::string & ups, long long int delay) { setInt(ups, "ups.delay.shutdown", delay); } // CHECKME + inline void setUPSdelayStart(const std::string & ups, long long int delay) { setInt(ups, "ups.delay.start", delay); } // CHECKME + inline void setVoltage(const std::string & ups, long long int voltage) { setInt(ups, "voltage", voltage); } // CHECKME + inline void setWait(const std::string & ups, long long int wait) { setInt(ups, "wait", wait); } // CHECKME + + inline void setNolock(const std::string & ups, bool set = true) { setStr(ups, "nolock", bool2str(set)); } + inline void setCable(const std::string & ups, bool set = true) { setStr(ups, "cable", bool2str(set)); } + inline void setDumbTerm(const std::string & ups, bool set = true) { setStr(ups, "dumbterm", bool2str(set)); } + inline void setExplore(const std::string & ups, bool set = true) { setStr(ups, "explore", bool2str(set)); } + inline void setFakeLowBatt(const std::string & ups, bool set = true) { setStr(ups, "fake_lowbatt", bool2str(set)); } + inline void setFlash(const std::string & ups, bool set = true) { setStr(ups, "flash", bool2str(set)); } + inline void setFullUpdate(const std::string & ups, bool set = true) { setStr(ups, "full_update", bool2str(set)); } + inline void setLangIDfix(const std::string & ups, bool set = true) { setStr(ups, "langid_fix", bool2str(set)); } + inline void setLoadOff(const std::string & ups, bool set = true) { setStr(ups, "load.off", bool2str(set)); } + inline void setLoadOn(const std::string & ups, bool set = true) { setStr(ups, "load.on", bool2str(set)); } + inline void setNoHang(const std::string & ups, bool set = true) { setStr(ups, "nohang", bool2str(set)); } + inline void setNoRating(const std::string & ups, bool set = true) { setStr(ups, "norating", bool2str(set)); } + inline void setNoTransferOIDs(const std::string & ups, bool set = true) { setStr(ups, "notransferoids", bool2str(set)); } + inline void setNoVendor(const std::string & ups, bool set = true) { setStr(ups, "novendor", bool2str(set)); } + inline void setNoWarnNoImp(const std::string & ups, bool set = true) { setStr(ups, "nowarn_noimp", bool2str(set)); } + inline void setPollOnly(const std::string & ups, bool set = true) { setStr(ups, "pollonly", bool2str(set)); } + inline void setSilent(const std::string & ups, bool set = true) { setStr(ups, "silent", bool2str(set)); } + inline void setStatusOnly(const std::string & ups, bool set = true) { setStr(ups, "status_only", bool2str(set)); } + inline void setSubscribe(const std::string & ups, bool set = true) { setStr(ups, "subscribe", bool2str(set)); } + inline void setUseCRLF(const std::string & ups, bool set = true) { setStr(ups, "use_crlf", bool2str(set)); } + inline void setUsePreLF(const std::string & ups, bool set = true) { setStr(ups, "use_pre_lf", bool2str(set)); } + + /** \} */ + + virtual ~UpsConfiguration() override; +}; // end of class UpsConfiguration + + +/** upsd users configuration */ +class UpsdUsersConfiguration : public GenericConfiguration +{ +public: + /** upsmon mode */ + typedef enum { + UPSMON_UNDEF = 0, /**< Unknown mode */ + UPSMON_MASTER, /**< Master mode */ + UPSMON_SLAVE, /**< Slave mode */ + } upsmon_mode_t; + + /** User-specific configuration attributes getters and setters \{ */ + + inline std::string getPassword(const std::string & user) const { return getStr(user, "password"); } + + inline ConfigParamList getActions(const std::string & user) const + { + ConfigParamList actions; + get(user, "actions", actions); + return actions; + } + + inline ConfigParamList getInstantCommands(const std::string & user) const + { + ConfigParamList cmds; + get(user, "instcmds", cmds); + return cmds; + } + + upsmon_mode_t getUpsmonMode() const; + + inline void setPassword(const std::string & user, const std::string & passwd) { setStr(user, "password", passwd); } + + inline void setActions(const std::string & user, const ConfigParamList & actions) { set(user, "actions", actions); } + inline void setInstantCommands(const std::string & user, const ConfigParamList & cmds) { set(user, "instcmds", cmds); } + + inline void addActions(const std::string & user, const ConfigParamList & actions) { add(user, "actions", actions); } + inline void addInstantCommands(const std::string & user, const ConfigParamList & cmds) { add(user, "instcmds", cmds); } + + /** + * \brief upsmon mode setter + * + * Note that the UPSMON_UNDEF mode isn't allowed as parameter + * (logically, if you set something, it shall be defined...) + * + * \param mode Mode + */ + void setUpsmonMode(upsmon_mode_t mode); + + /** \} */ + + /** Serialisable interface implementation overload \{ */ + bool parseFrom(NutStream & istream) override; + bool writeTo(NutStream & ostream) const override; + /** \} */ + +}; // end of class UpsdUsersConfiguration + +} /* namespace nut */ +#endif /* __cplusplus */ +#endif /* NUTCONF_H_SEEN */ diff --git a/include/nutipc.hpp b/include/nutipc.hpp new file mode 100644 index 0000000000..c3f339bb25 --- /dev/null +++ b/include/nutipc.hpp @@ -0,0 +1,800 @@ +/* + nutipc.hpp - NUT IPC + + Copyright (C) 2012 Eaton + + Author: Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef NUT_NUTIPC_HPP +#define NUT_NUTIPC_HPP + +#include +#include +#include + +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + +/* See include/common.h for details behind this */ +#ifndef NUT_UNUSED_VARIABLE +#define NUT_UNUSED_VARIABLE(x) (void)(x) +#endif + +namespace nut { + +/** + * Process-related information + */ +class Process { + private: + + /** The type yields no instances */ + Process() {} + + public: + + /** Get current process ID */ + static pid_t getPID() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** Get parent process ID */ + static pid_t getPPID() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * Process main routine functor prototype + */ + class Main { + protected: + + /** Formal constructor */ + Main() {} + virtual ~Main(); + + public: + + /* Avoid implicit copy/move operator declarations */ + Main(Main&&) = default; + Main& operator=(const Main&) = default; + //Main& operator=(Main&&) = default; + + /** Routine */ + virtual int operator () () = 0; + + }; // end of class Main + + /** + * Child process + */ + template + class Child { + private: + + pid_t m_pid; /**< Child PID */ + bool m_exited; /**< Exited flag */ + int m_exit_code; /**< Exit code */ + + public: + + /** + * \brief Constructor of child process + * + * The constructor calls \c ::fork to create another child process. + * The child executes \ref m_main functor instance operator \c (). + * When the functor's \c () operator returns, the returned value + * shall be used as the child exit code (and the child will exit). + * + * \param main Child process main routine + */ + Child(M main) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + ; + + /** Child PID */ + inline pid_t getPID() const { return m_pid; } + + /** + * \brief Wait for child process to exit + * + * The method blocks as long as the child runs. + * It returns the child's exit code. + * It throws an exception if executed twice + * (or on other illogical usage). + * + * \return Child process exit code + */ + int wait() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif + ; + + /** + * \brief Child exit code getter + * + * \return Child exit code + */ + inline int exitCode() { + return wait(); + } + + /** + * \brief Destructor + * + * The destructor shall wait for the child process + * (unless already exited). + */ + ~Child() { + wait(); + } + + }; // end of class Child + + /** + * External command executor + */ + class Executor: public Main { + public: + + /** Command line arguments list */ + typedef std::list Arguments; + + private: + + std::string m_bin; + Arguments m_args; + + public: + + /** + * \brief Constructor + * + * The binary path may be omitted; the implementation shall perform + * the actions shell would do to search for the binary (i.e. check \c PATH + * environment variable); + * + * Note that even option switches are command line arguments; + * e.g. "tail -n 20" command has 2 arguments: "-n" and "20". + * + * \brief bin Binary to be executed + * \brief args Command-line arguments to the binary + */ + Executor(const std::string & bin, const Arguments & args): + m_bin(bin), m_args(args) {} + + /** + * \brief Constructor + * + * This constructor form splits the command string specified + * to the binary and its cmd-line arguments for the caller (by spaces). + * + * Note however, that the command must be a binary execution; if you want + * to run a shell command, you must execute the shell, explicitly; e.g: + * "/bin/sh -c ''" shall probably be what you want. + * + * \param command Command to be executed + */ + Executor(const std::string & command); + + /** Execution of the binary */ + int operator () () +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + override; + }; // end of class Executor + + /** + * External command execution + */ + class Execution: public Child { + public: + + /** + * Constructor + * + * The binary path may be omitted; the implementation shall perform + * the actions shell would do to search for the binary (i.e. check \c PATH + * environment variable); + * + * \brief binary Binary to be executed + * \brief arguments Command-line arguments to the binary + */ + Execution(const std::string & binary, const Executor::Arguments & arguments): + Child(Executor(binary, arguments)) {} + + /** + * Constructor + * + * This form of the constructor splits the command string specified + * to the binary and its cmd-line arguments for the caller (by spaces). + * + * Note however, that the command must be a binary execution; if you want + * to run a shell command, you must execute the shell, explicitly; e.g: + * "/bin/sh -c ''" shall probably be what you want. + * + * \param command Command to be executed + */ + Execution(const std::string & command): Child(Executor(command)) {} + + }; // end of class Execution + + /** + * \brief Execute command and wait for exit code + * + * \param binary Binary to be executed + * \param arguments Command-line arguments to the binary + * + * \return Exit code + */ + static inline int execute(const std::string & binary, const Executor::Arguments & arguments) { + Execution child(binary, arguments); + + return child.wait(); + } + + /** + * \brief Execute command and wait for exit code + * + * \param command Command to be executed + * + * \return Exit code + */ + static inline int execute(const std::string & command) { + Execution child(command); + + return child.wait(); + } + +}; // end of class Process + + +template +Process::Child::Child(M main) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + : + m_pid(0), + m_exited(false), + m_exit_code(0) +{ + m_pid = ::fork(); + + if (!m_pid) + ::exit(main()); +} + + +template +int Process::Child::wait() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif +{ + if (m_exited) + return m_exit_code; + + pid_t wpid = ::waitpid(m_pid, &m_exit_code, 0); + + if (-1 == wpid) { + int erno = errno; + + std::stringstream e; + + e << "Failed to wait for process " << m_pid << ": "; + e << erno << ": " << strerror(erno); + + throw std::logic_error(e.str()); + } + + m_exited = true; + m_exit_code = WEXITSTATUS(m_exit_code); + + return m_exit_code; +} + + +/** + * POSIX signal + * + * For portability reasons, only mostly common subset of POSIX.1-2001 signals are supported. + */ +class Signal { + public: + + /** Signals */ + typedef enum { + HUP = SIGHUP, /** Hangup */ + INT = SIGINT, /** Interrupt */ + QUIT = SIGQUIT, /** Quit */ + ILL = SIGILL, /** Illegal Instruction */ + TRAP = SIGTRAP, /** Trace/breakpoint trap */ + ABORT = SIGABRT, /** Abort */ + BUS = SIGBUS, /** Bus error (bad memory access) */ + FPE = SIGFPE, /** Floating point exception */ + KILL = SIGKILL, /** Kill (unmaskable) */ + SEGV = SIGSEGV, /** Invalid memory reference */ + PIPE = SIGPIPE, /** Broken pipe */ + ALARM = SIGALRM, /** Alarm */ + TERM = SIGTERM, /** Termination */ + USER1 = SIGUSR1, /** User-defined signal 1 */ + USER2 = SIGUSR2, /** User-defined signal 2 */ + CHILD = SIGCHLD, /** Child stopped or terminated */ + CONT = SIGCONT, /** Continue if stopped */ + STOP = SIGSTOP, /** Stop process (unmaskable) */ + TSTOP = SIGTSTP, /** Stop typed at TTY */ + TTYIN = SIGTTIN, /** TTY input for background process */ + TTYOUT = SIGTTOU, /** TTY output for background process */ + PROF = SIGPROF, /** Profiling timer expired */ + SYS = SIGSYS, /** Bad argument to routine */ + URG = SIGURG, /** Urgent condition on socket */ + VTALRM = SIGVTALRM, /** Virtual alarm clock */ + XCPU = SIGXCPU, /** CPU time limit exceeded */ + XFSZ = SIGXFSZ, /** File size limit exceeded */ + } enum_t; // end of typedef enum + + /** Signal list */ + typedef std::list List; + + /** + * \brief Signal handler + * + * Signal handler interface. + */ + class Handler { + protected: + + /** Formal constructor */ + Handler() {} + + public: + + /** + * \brief Signal handler routine + * + * \param signal Signal + */ + virtual void operator () (enum_t signal) = 0; + + /** Formal destructor */ + virtual ~Handler(); + + }; // end of class Handler + + private: + + /** Formal constructor */ + Signal() {} + + public: + + /** Signal handler thread handle */ + template + class HandlerThread { + friend class Signal; + + private: + + /** Control commands */ + typedef enum { + HT_QUIT = 0, /**< Shutdown the thread */ + HT_SIGNAL = 1, /**< Signal obtained */ + } command_t; + + /** Communication pipe */ + static int s_comm_pipe[2]; + + /** POSIX thread */ + pthread_t m_impl; + + /** + * \brief Signal handler thread main routine + * + * The function synchronously read commands from the communication pipe. + * It processes control commands on its own (e.g. the quit command). + * It passes all signals to signal handler instance of \ref H + * Which must implement the \ref Signal::Handler interface. + * The handler is instantiated in scope of the routine. + * It closes the communication pipe read end in reaction to + * \ref HT_QUIT command. + * + * \param comm_pipe_read_end Communication pipe read end + * + * \retval N/A (the function never returns) + */ + static void * main(void * comm_pipe_read_end); + + /** + * \brief Signal handler routine + * + * The actual signal handler routine executed by the OS when the process + * obtains signal to be handled. + * The function simply writes the signal number to the signal handler + * thread communication pipe (as parameter of the \ref HT_SIGNAL command). + * The signal handling itself (whatever necessary) shall be done + * by the dedicated thread (to avoid possible re-entrance issues). + * + * Note that \c ::write is required to be an async-signal-safe function by + * POSIX.1-2004; also note that up to \c PIPE_BUF bytes are written atomically + * as required by IEEE Std 1003.1, 2004 Edition,\c PIPE_BUF being typically + * hundreds of bytes at least (POSIX requires 512B, Linux provides whole 4KiB + * page). + * + * \param signal Signal + */ + static void signalNotifier(int signal); + + public: + + /** + * \brief Constructor + * + * At most one thread per handler instance may be created. + * This limitation is both due sanity reasons (it wouldn't + * make much sense to handle the same signal by multiple threads) + * and because of the signal handler routine only has access + * to one communication pipe write end (the static member). + * This is actually the only technical reason of having the class + * template (so that every instance has its own static comm. queue). + * However, for different handler classes, multiple handling threads + * may be created (if it makes sense) since these will be different + * template instances and therefore will use different static + * communication pipes. + * If more than 1 instance creation is attempted, an exception is thrown. + * + * \param siglist List of signals that shall be handled by the thread + */ + HandlerThread(const Signal::List & siglist) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error, std::runtime_error) +#endif + ; + + /** + * \brief Terminate the thread + * + * The method sends the signal handler thread the \ref QUIT signal. + * It blocks until the thread is joined. + * Closes the communication pipe write end. + */ + void quit() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + ; + + /** + * \brief Destructor + * + * Forces the signal handler thread termination (unless already down). + */ + ~HandlerThread() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + ; + + }; // end of HandlerThread + + /** + * \brief Send signal to a process + * + * An exception is thrown if the signal isn't implemented. + * + * \param signame Signal name + * \param pid Process ID + * + * \retval 0 in case of success + * \retval EPERM if the process doesn't have permission to send the signal + * \retval ESRCH if the process (group) identified doesn't exist + */ + static int send(enum_t signame, pid_t pid) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif + ; + + /** + * \brief Send signal to a process identified via PID file + * + * An exception is thrown if the signal isn't implemented + * or PID file read fails. + * + * \param signame Signal name + * \param pid_file File containing process PID + * + * \retval 0 in case of success + * \retval EPERM if the process doesn't have permission to send the signal + * \retval ESRCH if the process (group) identified doesn't exist + */ + static int send(enum_t signame, const std::string & pid_file); + +}; // end of class Signal + + +/** Initialization of the communication pipes */ +template +int Signal::HandlerThread::s_comm_pipe[2] = { -1, -1 }; + + +template +void * Signal::HandlerThread::main(void * comm_pipe_read_end) { + int rfd = *(reinterpret_cast(comm_pipe_read_end)); + + H handler; + + for (;;) { + fd_set rfds; + FD_ZERO(&rfds); + FD_SET(rfd, &rfds); + + // Poll on signal pipe + // Note that direct blocking read could be also used; + // however, select allows timeout specification + // which might come handy... + int fdno = ::select(FD_SETSIZE, &rfds, nullptr, nullptr, nullptr); + + // TBD: Die or recover on error? + if (-1 == fdno) { + std::stringstream e; + + e << "Poll on communication pipe read end "; + e << rfd << " failed: " << errno; + + throw std::runtime_error(e.str()); + } + + assert(1 == fdno); + assert(FD_ISSET(rfd, &rfds)); + + // Read command + int word; + + ssize_t read_out = ::read(rfd, &word, sizeof(word)); + + // TBD: again, how should we treat read error? + if (-1 == read_out) { + std::stringstream e; + + e << "Failed to read command from the command pipe: "; + e << errno; + + throw std::runtime_error(e.str()); + } + + assert(sizeof(word) == read_out); + + command_t command = static_cast(word); + + switch (command) { + case HT_QUIT: + // Close comm. pipe read end + ::close(rfd); + + // Terminate thread + ::pthread_exit(nullptr); + + case HT_SIGNAL: + // Read signal number + read_out = ::read(rfd, &word, sizeof(word)); + + // TBD: again, how should we treat read error? + if (-1 == read_out) { + std::stringstream e; + + e << "Failed to read signal number "; + e << "from the command pipe: " << errno; + + throw std::runtime_error(e.str()); + } + + assert(sizeof(word) == read_out); + + Signal::enum_t sig = static_cast(word); + + // Handle signal + handler(sig); + } + } + + // Pro-forma exception + throw std::logic_error("INTERNAL ERROR: Unreachable code reached"); +} + + +/** + * \brief Write command to command pipe + * + * \param fh Pipe writing end (file handle) + * \param cmd Command + * \param cmd_size Command size + * + * \retval 0 on success + * \retval errno on error + */ +int sigPipeWriteCmd(int fh, void * cmd, size_t cmd_size) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + ; + + +template +void Signal::HandlerThread::signalNotifier(int signal) { + int sig[2] = { + static_cast(Signal::HandlerThread::HT_SIGNAL), + }; + + sig[1] = signal; + + // TBD: The return value is silently ignored. + // Either the write should've succeeded or the handling + // thread is already coming down... + sigPipeWriteCmd(s_comm_pipe[1], sig, sizeof(sig)); +} + + +template +Signal::HandlerThread::HandlerThread(const Signal::List & siglist) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error, std::runtime_error) +#endif +{ + // At most one instance per process allowed + if (-1 != s_comm_pipe[1]) + throw std::logic_error( + "Attempt to start a duplicate of signal handling thread detected"); + + // Create communication pipe + if (::pipe(s_comm_pipe)) { + std::stringstream e; + + e << "Failed to create communication pipe: " << errno; + + throw std::runtime_error(e.str()); + } + + // Start the thread + int status = ::pthread_create(&m_impl, nullptr, &main, s_comm_pipe); + + if (status) { + std::stringstream e; + + e << "Failed to start the thread: " << status; + + throw std::runtime_error(e.str()); + } + + // Register signals + Signal::List::const_iterator sig = siglist.begin(); + + for (; sig != siglist.end(); ++sig) { + struct sigaction action; + + ::memset(&action, 0, sizeof(action)); + ::sigemptyset(&action.sa_mask); + + action.sa_handler = &signalNotifier; + + int signo = static_cast(*sig); + + // TBD: We might want to save the old handlers... + status = ::sigaction(signo, &action, nullptr); + + if (status) { + std::stringstream e; + + e << "Failed to register signal handler for signal "; + e << signo << ": " << errno; + + throw std::runtime_error(e.str()); + } + } +} + + +template +void Signal::HandlerThread::quit() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif +{ + static int quit = static_cast(Signal::HandlerThread::HT_QUIT); + + sigPipeWriteCmd(s_comm_pipe[1], &quit, sizeof(quit)); + + int status = ::pthread_join(m_impl, nullptr); + + if (status) { + std::stringstream e; + + e << "Failed to joint signal handling thread: " << status; + + throw std::runtime_error(e.str()); + } + + if (::close(s_comm_pipe[1])) { + std::stringstream e; + + e << "Failed to close communication pipe: " << errno; + + throw std::runtime_error(e.str()); + } + + s_comm_pipe[1] = -1; +} + + +template +Signal::HandlerThread::~HandlerThread() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif +{ + // Stop the thread unless already stopped + if (-1 != s_comm_pipe[1]) + quit(); +} + + +/** NUT-specific signal handling */ +class NutSignal: public Signal { + public: + + /** + * \brief Send signal to a NUT process + * + * The function assembles process-specific PID file name and path + * and calls \ref Signal::send. + * + * An exception is thrown if the signal isn't implemented + * or PID file read fails. + * + * \param signame Signal name + * \param process File containing process PID + * + * \retval 0 in case of success + * \retval EPERM if the process doesn't have permission to send the signal + * \retval ESRCH if the process (group) identified doesn't exist + */ + static int send(enum_t signame, const std::string & process); + +}; // end of class NutSignal + +} // end of namespace nut + +#endif /* end of #ifndef NUT_NUTIPC_H */ diff --git a/include/nutstream.hpp b/include/nutstream.hpp new file mode 100644 index 0000000000..f5f5ca86c8 --- /dev/null +++ b/include/nutstream.hpp @@ -0,0 +1,1181 @@ +/* + nutstream.hpp - NUT stream + + Copyright (C) + 2012 Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef nut_nutstream_h +#define nut_nutstream_h + +#ifdef __cplusplus + +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +} + +/* See include/common.h for details behind this */ +#ifndef NUT_UNUSED_VARIABLE +#define NUT_UNUSED_VARIABLE(x) (void)(x) +#endif + +namespace nut +{ + +/** + * \brief Data stream interface + * + * The interface provides character-based streamed I/O. + */ +class NutStream { + public: + + /** Data store status */ + typedef enum { + NUTS_OK = 0, /** Operation successful */ + NUTS_EOF, /** End of stream reached */ + NUTS_ERROR, /** Error occurred */ + } status_t; + + protected: + + /** Formal constructor */ + NutStream() {} + + public: + + /** + * \brief Get one character from current position + * + * The method provides character from current position + * in the data store. + * The position is not shifted (see \ref readChar for + * current character consumption). + * + * The operation is synchronous (the call blocks if necessary). + * + * \param[out] ch Character + * + * \retval NUTS_OK on success, + * \retval NUTS_EOF on end of stream, + * \retval NUTS_ERROR on read error + */ + virtual status_t getChar(char & ch) = 0; + + /** + * \brief Consume current character + * + * The method shifts position in the stream. + * It shall never block (if the position gets + * past-the-end of currently available data, + * subsequent call to \ref getChar will block). + */ + virtual void readChar() = 0; + + /** + * \brief Read characters from the stream till EoF + * + * The method may be used to synchronously read + * whole (rest of) stream. + * Note that implementation may block flow. + * + * \retval NUTS_OK on success, + * \retval NUTS_ERROR on read error + */ + virtual status_t getString(std::string & str) = 0; + + /** + * \brief Put one character to the stream end + * + * \param[in] ch Character + * + * \retval NUTS_OK on success, + * \retval NUTS_ERROR on write error + */ + virtual status_t putChar(char ch) = 0; + + /** + * \brief Put string to the stream end + * + * \param[in] str String + * + * \retval NUTS_OK on success, + * \retval NUTS_ERROR on write error + */ + virtual status_t putString(const std::string & str) = 0; + + /** + * \brief Put data to the stream end + * + * The difference between \ref putString and this method + * is that it is able to serialize also data containing + * null characters. + * + * \param[in] data Data + * + * \retval NUTS_OK on success, + * \retval NUTS_ERROR on write error + */ + virtual status_t putData(const std::string & data) = 0; + + /** Formal destructor */ + virtual ~NutStream(); + +}; // end of class NutStream + + +/** Memory stream */ +class NutMemory: public NutStream { + private: + + /** Implementation */ + std::string m_impl; + + /** Position in implementation */ + size_t m_pos; + + public: + + /** Constructor */ + NutMemory(): m_pos(0) {} + + /** + * \brief Constructor + * + * \param str Init value + */ + NutMemory(const std::string & str): m_impl(str), m_pos(0) {} + + // NutStream interface implementation + status_t getChar(char & ch) override; + void readChar() override; + status_t getString(std::string & str) override; + status_t putChar(char ch) override; + status_t putString(const std::string & str) override; + status_t putData(const std::string & data) override; + +}; // end of class NutMemory + + +/** File stream */ +class NutFile: public NutStream { + public: + + /** Access mode */ + typedef enum { + /** Read-only, initial pos. is at the beginning */ + READ_ONLY = 0, + + /** Write-only, with creation, clears the file if exists */ + WRITE_ONLY, + + /** Read-write, initial pos. is at the beginning */ + READ_WRITE, + + /** As previous, but with creation, clears the file if exists */ + READ_WRITE_CLEAR, + + /** Read & write to end, with creation, init. pos: beginning/end for r/w */ + READ_APPEND, + + /** Write only, with creation, initial position is at the end */ + APPEND_ONLY, + } access_t; + + /** Unnamed temp. file constructor flag */ + typedef enum { + ANONYMOUS, /** Anonymous temp. file flag */ + } anonymous_t; + + private: + + /** Temporary files directory */ + static const std::string m_tmp_dir; + + /** File name */ + const std::string m_name; + + /** Implementation */ + FILE *m_impl; + + /** Current character cache */ + char m_current_ch; + + /** Current character cache status */ + bool m_current_ch_valid; + + /** + * \brief Generate temporary file name + * + * Throws an exception on file name generation error. + * + * \return Temporary file name + */ + std::string tmpName() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + ; + + public: + + /** Constructor + * + * \param[in] name File name + */ + NutFile(const std::string & name): + m_name(name), + m_impl(nullptr), + m_current_ch('\0'), + m_current_ch_valid(false) {} + + /** + * \brief Temporary file constructor (with open) + * + * Anonymous temp. file (with automatic destruction) will be created. + */ + NutFile(anonymous_t); + + /** + * \brief File name getter + * + * \return File name + */ + inline const std::string & name() const { + return m_name; + } + + /** + * \brief Check whether file exists + * + * \param[out] err_code Error code + * \param[out] err_msg Error message + * + * \retval true IFF the file exists + * \retval false otherwise + */ + bool exists(int & err_code, std::string & err_msg) const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Check whether file exists + * + * \retval true IFF the file exists + * \retval false otherwise + */ + inline bool exists() const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + int ec; + std::string em; + + return exists(ec, em); + } + + /** + * \brief Check whether file exists (or throw exception) + * + * \retval true IFF the file exists + * \retval false otherwise + */ + inline bool existsx() const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (exists(ec, em)) + return true; + + if (ENOENT == ec || ENOTDIR == ec) + return false; + + std::stringstream e; + e << "Failed to check file " << m_name << " existence: " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + /** + * \brief Open file + * + * \param[in] mode File open mode + * \param[out] err_code Error code + * \param[out] err-msg Error message + * + * \retval true if open succeeded + * \retval false if open failed + */ + bool open(access_t mode, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Open file + * + * \param[in] mode File open mode (read-only by default) + * + * \retval true if open succeeded + * \retval false if open failed + */ + inline bool open(access_t mode = READ_ONLY) { + int ec; + std::string em; + + return open(mode, ec, em); + } + + /** + * \brief Open file (or throw exception) + * + * \param[in] mode File open mode (read-only by default) + * + * \retval true if open succeeded + * \retval false if open failed + */ + inline void openx(access_t mode = READ_ONLY) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (open(mode, ec, em)) + return; + + std::stringstream e; + e << "Failed to open file " << m_name << ": " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + /** + * \brief Close file + * + * \param[out] err_code Error code + * \param[out] err_msg Error message + * + * \retval true if close succeeded + * \retval false if close failed + */ + bool close(int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Close file + * + * \retval true if close succeeded + * \retval false if close failed + */ + inline bool close() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + int ec; + std::string em; + + return close(ec, em); + } + + /** Close file (or throw exception) */ + inline void closex() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (close(ec, em)) + return; + + std::stringstream e; + e << "Failed to close file " << m_name << ": " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + /** + * \brief Remove file + * + * \param[out] err_code Error code + * \param[out] err-msg Error message + * + * \retval true if \c unlink succeeded + * \retval false if \c unlink failed + */ + bool remove(int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Remove file + * + * \retval true if \c unlink succeeded + * \retval false if \c unlink failed + */ + inline bool remove() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + int ec; + std::string em; + + return remove(ec, em); + } + + /** Remove file (or throw exception) */ + inline void removex() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (remove(ec, em)) + return; + + std::stringstream e; + e << "Failed to remove file " << m_name << ": " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + /** + * \brief Constructor (with open) + * + * Opens the file, throws exception on open error. + * + * \param[in] name File name + * \param[in] mode File open mode + */ + NutFile(const std::string & name, access_t mode); + + /** + * \brief Temporary file constructor (with open) + * + * Opens file in \ref m_tmp_dir. + * Throws exception on open error. + * Note that for temporary files, non-creation modes + * don't make sense (and will result in throwing an exception). + * + * \param[in] name File name + * \param[in] mode File open mode (r/w by default) + */ + NutFile(access_t mode = READ_WRITE_CLEAR); + + // NutStream interface implementation + status_t getChar(char & ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + void readChar() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + status_t getString(std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + status_t putChar(char ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + status_t putString(const std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + status_t putData(const std::string & data) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + /** Destructor (closes the file) */ + ~NutFile() override; + + private: + + /** + * \brief Copy constructor + * + * TODO: Copying is forbidden (for now). + * If required, it may be enabled, using fdup. + * + * \param orig Original file + */ + NutFile(const NutFile & orig) +#if (defined __cplusplus) && (__cplusplus >= 201100) + __attribute__((noreturn)) +#endif + { + NUT_UNUSED_VARIABLE(orig); + throw std::logic_error("NOT IMPLEMENTED"); + } + + /** + * \brief Assignment + * + * TODO: Assignment is forbidden (for now). + * See copy constructor for implementation notes + * (and don't forget to destroy left value, properly). + * + * \param rval Right value + */ + NutFile & operator = (const NutFile & rval) +#if (defined __cplusplus) && (__cplusplus >= 201100) + __attribute__((noreturn)) +#endif + { + NUT_UNUSED_VARIABLE(rval); + throw std::logic_error("NOT IMPLEMENTED"); + } + +}; // end of class NutFile + + +/** Socket stream */ +class NutSocket: public NutStream { + public: + + /** Socket domain */ + typedef enum { + NUTSOCKD_UNIX = AF_UNIX, /** Unix */ + NUTSOCKD_INETv4 = AF_INET, /** IPv4 */ + NUTSOCKD_INETv6 = AF_INET6, /** IPv6 */ + } domain_t; + + /** Socket type */ + typedef enum { + NUTSOCKT_STREAM = SOCK_STREAM, /** Stream */ + NUTSOCKT_DGRAM = SOCK_DGRAM, /** Datagram */ + } type_t; + + /** Socket protocol */ + typedef enum { + NUTSOCKP_IMPLICIT = 0, /** Implicit protocol for chosen type */ + } proto_t; + + /** Socket address */ + class Address { + friend class NutSocket; + + private: + + /** Implementation */ + struct sockaddr *m_sock_addr; + + /** Length */ + socklen_t m_length; + + /** + * \brief Invalid address constructor + * + * Invalid address may be produced e.g. by failed DNS resolving. + */ + Address(): m_sock_addr(nullptr), m_length(0) {} + + /** + * \brief Initialize UNIX socket address + * + * \param addr UNIX socket address + * \param path Pathname + */ + static void init_unix(Address & addr, const std::string & path); + + /** + * \brief Initialize IPv4 address + * + * \param addr IPv4 address + * \param qb Byte quadruplet (MSB is at index 0) + * \param port Port number + */ + static void init_ipv4(Address & addr, const std::vector & qb, uint16_t port); + + /** + * \brief Initialize IPv6 address + * + * \param addr IPv6 address + * \param hb 16 bytes of the address (MSB is at index 0) + * \param port Port number + */ + static void init_ipv6(Address & addr, const std::vector & hb, uint16_t port); + + public: + + /** + * \brief Check address validity + * + * \retval true if the address is valid + * \retval false otherwise + */ + inline bool valid() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + return nullptr != m_sock_addr; + } + + /** + * \brief UNIX socket address constructor + * + * \param path Pathname + */ + Address(const std::string & path) { + init_unix(*this, path); + } + + /** + * \brief IPv4 address constructor + * + * \param msb Most significant byte + * \param msb2 Second most significant byte + * \param lsb2 Second least significant byte + * \param lsb Least significant byte + * \param port Port number + */ + Address(unsigned char msb, + unsigned char msb2, + unsigned char lsb2, + unsigned char lsb, + uint16_t port); + + /** + * \brief IP address constructor + * + * Creates either IPv4 or IPv6 address (depending on + * how many bytes are provided via the \c bytes argument). + * Throws an exception if the byte-count is invalid. + * + * \param bytes 4 or 16 address bytes (MSB is at index 0) + * \param port Port number + */ + Address(const std::vector & bytes, uint16_t port) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif + ; + + /** + * \brief Copy constructor + * + * \param orig Original address + */ + Address(const Address & orig); + + /** + * \brief Stringifisation + * + * \return String representation of the address + */ + std::string str() const; + + /** Stringification */ + inline operator std::string() const { + return str(); + } + + /** Destructor */ + ~Address(); + + }; // end of class Address + + /** Flag for socket constructor of accepted connection */ + typedef enum { + ACCEPT /** Accept flag */ + } accept_flag_t; + + private: + + /** Socket implementation */ + int m_impl; + + /** Current character cache */ + char m_current_ch; + + /** Current character cache status */ + bool m_current_ch_valid; + + /** + * \brief Accept client connection on a listen socket + * + * \param[out] sock Socket + * \param[in] listen_sock Listen socket + * \param[out] err_code Error code + * \param[out] err_msg Error message + * + * \retval true on success + * \retval false otherwise + */ + static bool accept( + NutSocket & sock, + const NutSocket & listen_sock, + int & err_code, + std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif + ; + + /** + * \brief Accept client connection on a listen socket + * + * \param[out] sock Socket + * \param[in] listen_sock Listen socket + * + * \retval true on success + * \retval false otherwise + */ + inline static bool accept( + NutSocket & sock, + const NutSocket & listen_sock) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif + { + int ec; + std::string em; + + return accept(sock, listen_sock, ec, em); + } + + /** + * \brief Accept client connection (or throw exception) + * + * \param[out] sock Socket + * \param[in] listen_sock Listen socket + */ + inline static void acceptx( + NutSocket & sock, + const NutSocket & listen_sock) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error, std::runtime_error) +#endif + { + int ec; + std::string em; + + if (accept(sock, listen_sock, ec, em)) + return; + + std::stringstream e; + e << "Failed to accept connection: " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + public: + + /** + * \brief Socket valid check + * + * \retval true if the socket is initialized + * \retval false otherwise + */ + inline bool valid() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + return -1 != m_impl; + } + + /** + * \brief Constructor + * + * \param dom Socket domain + * \param type Socket type + * \param proto Socket protocol + */ + NutSocket( + domain_t dom = NUTSOCKD_INETv4, + type_t type = NUTSOCKT_STREAM, + proto_t proto = NUTSOCKP_IMPLICIT); + + /** + * \brief Accepted client socket constructor + * + * Accepts connection on a listen socket. + * If the argument isn't a listen socket, exception is thrown. + * The call will block until either there's an incoming connection + * or the listening socket is closed or there's an error. + * + * \param listen_sock Listening socket + */ + NutSocket(accept_flag_t, const NutSocket & listen_sock, int & err_code, std::string & err_msg): + m_impl(-1), + m_current_ch('\0'), + m_current_ch_valid(false) + { + accept(*this, listen_sock, err_code, err_msg); + } + + /** + * \brief Accepted client socket constructor + * + * Accepts connection on a listen socket. + * + * \param listen_sock Listening socket + */ + NutSocket(accept_flag_t, const NutSocket & listen_sock): + m_impl(-1), + m_current_ch('\0'), + m_current_ch_valid(false) + { + accept(*this, listen_sock); + } + + /** + * \brief Bind socket to an address + * + * \param[in] addr Socket address + * \param[out] err_code Error code + * \param[out] err_msg Error message + * + * \retval true on success + * \retval false on error + */ + bool bind(const Address & addr, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Bind socket to an address + * + * \param addr Socket address + * + * \retval true on success + * \retval false on error + */ + inline bool bind(const Address & addr) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + int ec; + std::string em; + + return bind(addr, ec, em); + } + + /** + * \brief Bind socket to an address (or throw exception) + * + * \param addr Socket address + */ + inline void bindx(const Address & addr) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (bind(addr, ec, em)) + return; + + std::stringstream e; + e << "Failed to bind socket to address " << addr.str() << ": " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + /** + * \brief Listen on a socket + * + * The function sets TCP listen socket. + * + * \param[in] backlog Limit of pending connections + * \param[out] err_code Error code + * \param[out] err_msg Error message + * + * \retval true on success + * \retval false on error + */ + bool listen(int backlog, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Listen on socket + * + * \param[in] backlog Limit of pending connections + * + * \retval true on success + * \retval false on error + */ + inline bool listen(int backlog) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + int ec; + std::string em; + + return listen(backlog, ec, em); + } + + /** + * \brief Listen on socket (or throw an exception) + * + * \param[in] backlog Limit of pending connections + */ + inline void listenx(int backlog) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (listen(backlog, ec, em)) + return; + + std::stringstream e; + e << "Failed to listen on socket: " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + /** + * \brief Connect to a listen socket + * + * \param[in] addr Remote address + * \param[out] err_code Error code + * \param[out] err_msg Error message + * + * \retval true on success + * \retval false on error + */ + bool connect(const Address & addr, int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Connect to a listen socket + * + * \param[in] addr Remote address + * + * \retval true on success + * \retval false on error + */ + inline bool connect(const Address & addr) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + int ec; + std::string em; + + return connect(addr, ec, em); + } + + /** + * \brief Connect to a listen socket (or throw an exception) + * + * \param[in] addr Remote address + */ + inline void connectx(const Address & addr) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (connect(addr, ec, em)) + return; + + std::stringstream e; + e << "Failed to connect socket: " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + /** + * \brief Close socket + * + * \param[out] err_code Error code + * \param[out] err-msg Error message + * + * \retval true if close succeeded + * \retval false if close failed + */ + bool close(int & err_code, std::string & err_msg) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + ; + + /** + * \brief Close socket + * + * \retval true if close succeeded + * \retval false if close failed + */ + inline bool close() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + { + int ec; + std::string em; + + return close(ec, em); + } + + /** Close socket (or throw exception) */ + inline void closex() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::runtime_error) +#endif + { + int ec; + std::string em; + + if (close(ec, em)) + return; + + std::stringstream e; + e << "Failed to close socket " << m_impl << ": " << ec << ": " << em; + + throw std::runtime_error(e.str()); + } + + // NutStream interface implementation + status_t getChar(char & ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + void readChar() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + status_t getString(std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + status_t putChar(char ch) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + status_t putString(const std::string & str) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw() +#endif + override; + + + inline status_t putData(const std::string & data) override { + return putString(data); // no difference on sockets + } + + /** Destructor (closes socket if necessary) */ + ~NutSocket() override; + + private: + + /** + * \brief Copy constructor + * + * TODO: Copying is forbidden (for now). + * If required, it may be enabled, using dup. + * + * \param orig Original file + */ + NutSocket(const NutSocket & orig) +#if (defined __cplusplus) && (__cplusplus >= 201100) + __attribute__((noreturn)) +#endif + { + NUT_UNUSED_VARIABLE(orig); + throw std::logic_error("NOT IMPLEMENTED"); + } + + /** + * \brief Assignment + * + * TODO: Assignment is forbidden (for now). + * See copy constructor for implementation notes + * (and don't forget to destroy left value, properly). + * + * \param rval Right value + */ + NutSocket & operator = (const NutSocket & rval) { + NUT_UNUSED_VARIABLE(rval); + throw std::logic_error("NOT IMPLEMENTED"); + } + +}; // end of class NutSocket + +} // end of namespace nut + +#endif /* __cplusplus */ + +#endif /* end of #ifndef nut_nutstream_h */ diff --git a/include/nutwriter.hpp b/include/nutwriter.hpp new file mode 100644 index 0000000000..b04c44acbb --- /dev/null +++ b/include/nutwriter.hpp @@ -0,0 +1,410 @@ +/* + nutwriter.hpp - NUT writer + + Copyright (C) + 2012 Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#ifndef nut_nutwriter_h +#define nut_nutwriter_h + +#ifdef __cplusplus + +#include "nutstream.hpp" +#include "nutconf.hpp" + +#include + + +namespace nut +{ + +/** + * \brief NUT stream writer + */ +class NutWriter { + public: + + /** NUT writer status */ + typedef enum { + NUTW_OK = 0, /** Writing successful */ + NUTW_ERROR, /** Writing failed */ + } status_t; // end of typedef enum */ + + protected: + + /** EoL separator */ + static const std::string & eol; + + /** Output stream (by reference) */ + NutStream & m_output_stream; + + public: + + /** + * \brief Constructor + * + * Creates the writer. + * The \c ostream parameter provides the writer reference + * to an existing output stream; note that the stream + * must exist throughout whole the writer's life. + * + * TBD: + * The stream might actually be passed either by value + * (\c NutStream implementations would have to support + * copying, though, which is not implemented at the moment) + * or using reference counting mechanism (smart pointers etc). + * The latter is perhaps a better choice (if the stream existence + * dependency is a considerable issue). + * + * \param ostream Output stream + */ + NutWriter(NutStream & ostream): m_output_stream(ostream) {} + + /** + * \brief Write to output stream + * + * The method writes the provided string to the output stream. + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + inline status_t write(const std::string & str) { + NutStream::status_t status = m_output_stream.putString(str); + + return NutStream::NUTS_OK == status ? NUTW_OK : NUTW_ERROR; + } + + /** + * \brief Write to output stream + * + * The method writes the provided string to the output stream. + * An exception is thrown on error. + */ + inline void writex(const std::string & str) { + NutStream::status_t status = m_output_stream.putString(str); + + if (NutStream::NUTS_OK != status) { + std::stringstream e; + e << "Failed to write to output stream: " << status; + + throw std::runtime_error(e.str()); + } + } + + protected: + + /** + * \brief Write (prefixed) lines + * + * The method splits string to lines (by EoL) and prefix them + * with specified string upon writing. + * + * \param str String (multi-line) + * \param pref Prefix + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + status_t writeEachLine(const std::string & str, const std::string & pref); + +}; // end of class NutWriter + + +/** + * \brief NUT configuration writer interface + */ +class NutConfigWriter: public NutWriter { + protected: + + /** Formal constructor */ + NutConfigWriter(NutStream & ostream): NutWriter(ostream) {} + + public: + + /** + * \brief Write comment + * + * \param str Comment string + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + virtual status_t writeComment(const std::string & str) = 0; + + /** + * \brief Write section name + * + * \param name Section name + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + virtual status_t writeSectionName(const std::string & name) = 0; + + /** + * \brief Write directive + * + * \param str Directive string + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + virtual status_t writeDirective(const std::string & str) = 0; + + /** Virtual destructor */ + virtual ~NutConfigWriter(); + +}; // end of class NutConfigWriter + + +/** + * \brief NUT section-less configuration writer specialization + * + * Partial implementation of \ref NutConfigWriter for section-less + * configuration files. + */ +class SectionlessConfigWriter: public NutConfigWriter { + protected: + + /** + * \brief Constructor + * + * \param ostream Output stream + */ + SectionlessConfigWriter(NutStream & ostream): NutConfigWriter(ostream) {} + + public: + + // Partial \ref NutConfigWriter interface implementation + status_t writeDirective(const std::string & str) override; + status_t writeComment(const std::string & str) override; + + private: + + // Section name writing is forbidden (no sections) + status_t writeSectionName(const std::string & name) override; + +}; // end of class SectionlessConfigWriter + + +/** + * \brief \c nut.conf configuration file serializer + */ +class NutConfConfigWriter: public SectionlessConfigWriter { + public: + + /** + * \brief Constructor + * + * \param ostream Output stream + */ + NutConfConfigWriter(NutStream & ostream): SectionlessConfigWriter(ostream) {} + + /** + * \brief Serialize configuration container + * + * \param config Configuration + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + status_t writeConfig(const NutConfiguration & config); + + /* Ensure an out-of-line method to avoid "weak-vtables" warning */ + virtual ~NutConfConfigWriter() override; +}; // end of class NutConfConfigWriter + + +/** + * \brief \c upsmon.conf configuration file serializer + */ +class UpsmonConfigWriter: public SectionlessConfigWriter { + public: + + /** + * \brief Constructor + * + * \param ostream Output stream + */ + UpsmonConfigWriter(NutStream & ostream): SectionlessConfigWriter(ostream) {} + + /** + * \brief Serialize configuration container + * + * \param config Configuration + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + status_t writeConfig(const UpsmonConfiguration & config); + + /* Ensure an out-of-line method to avoid "weak-vtables" warning */ + virtual ~UpsmonConfigWriter() override; +}; // end of class UpsmonConfigWriter + + +/** + * \brief \c upsd.conf configuration file serializer + */ +class UpsdConfigWriter: public SectionlessConfigWriter { + public: + + /** + * \brief Constructor + * + * \param ostream Output stream + */ + UpsdConfigWriter(NutStream & ostream): SectionlessConfigWriter(ostream) {} + + /** + * \brief Serialize configuration container + * + * \param config Configuration + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + status_t writeConfig(const UpsdConfiguration & config); + + /* Ensure an out-of-line method to avoid "weak-vtables" warning */ + virtual ~UpsdConfigWriter() override; +}; // end of class UpsdConfigWriter + + +/** + * \brief NUT default configuration writer ancestor + * + * Implements the \ref NutConfigWriter interface + * and adds \c writeSection prototype to be implemented + * by descendants. + */ +class DefaultConfigWriter: public NutConfigWriter { + protected: + + /** + * \brief Constructor + * + * \param ostream Output stream + */ + DefaultConfigWriter(NutStream & ostream): NutConfigWriter(ostream) {} + + public: + + // \ref NutConfigWriter interface implementation + status_t writeComment(const std::string & str) override; + status_t writeSectionName(const std::string & name) override; + status_t writeDirective(const std::string & str) override; + + /** + * \brief Write configuration section + * + * Serialize generic configuration section. + * + * \param section Configuration section + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + virtual status_t writeSection(const GenericConfigSection & section) = 0; + +}; // end of class DefaultConfigWriter + + +/** + * \brief NUT generic configuration writer + * + * Base configuration file serializer. + * Implements the \ref DefaultConfigWriter \c writeSection method + * and adds \c writeConfig routine for configuration file serialization. + */ +class GenericConfigWriter: public DefaultConfigWriter { + protected: + + /** Default indentation of the key/value pair in section entry */ + static const std::string s_default_section_entry_indent; + + /** Default separator of the key/value pair in section entry */ + static const std::string s_default_section_entry_separator; + + /** + * \brief Section entry serializer + * + * \param entry Section entry + * \param indent Indentation + * \param kv_sep Key/value separator + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + status_t writeSectionEntry( + const GenericConfigSectionEntry & entry, + const std::string & indent = s_default_section_entry_indent, + const std::string & kv_sep = s_default_section_entry_separator); + + public: + + /** + * \brief Constructor + * + * \param ostream Output stream + */ + GenericConfigWriter(NutStream & ostream): DefaultConfigWriter(ostream) {} + + // Section serializer implementation + status_t writeSection(const GenericConfigSection & section) override; + + /** + * \brief Base configuration serializer + * + * \param config Base configuration + * + * \retval NUTW_OK on success + * \retval NUTW_ERROR otherwise + */ + status_t writeConfig(const GenericConfiguration & config); + +}; // end of class GenericConfigWriter + + +/** + * \brief NUT upsd.users configuration file writer + * + * upsd.users configuration file serializer. + * Overloads the generic section serializer because of the upsmon section, + * which contains an anomalous upsmon (master|slave) directive. + */ +class UpsdUsersConfigWriter: public GenericConfigWriter { + public: + + /** + * \brief Constructor + * + * \param ostream Output stream + */ + UpsdUsersConfigWriter(NutStream & ostream): GenericConfigWriter(ostream) {} + + // Section serializer overload + status_t writeSection(const GenericConfigSection & section) override; + +}; // end of class UpsdUsersConfigWriter + +} // end of namespace nut + +#endif /* __cplusplus */ + +#endif /* end of #ifndef nut_nutwriter_h */ diff --git a/include/proto.h b/include/proto.h index 8c7ef1ae00..f88dd61f1c 100644 --- a/include/proto.h +++ b/include/proto.h @@ -64,6 +64,12 @@ extern "C" { # endif #endif +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + #if !defined (HAVE_SNPRINTF) || defined (__Lynx__) int snprintf (char *str, size_t count, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 3, 4))); diff --git a/tests/Makefile.am b/tests/Makefile.am index 8ddb86af43..152c650ef6 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -45,9 +45,9 @@ nutlogtest-nofail.sh: nutlogtest$(EXEEXT) @chmod +x $@ # NOTE: Keep the line above empty! -else +else !REQUIRE_NUT_STRARG TESTS += nutlogtest$(EXEEXT) -endif +endif !REQUIRE_NUT_STRARG TESTS += nuttimetest nuttimetest_SOURCES = nuttimetest.c @@ -69,9 +69,9 @@ nodist_getvaluetest_SOURCES = hidparser.c # Pull the right include path for chosen libusb version: getvaluetest_CFLAGS = $(AM_CFLAGS) $(LIBUSB_CFLAGS) getvaluetest_LDADD = $(top_builddir)/common/libcommon.la -else +else !WITH_USB EXTRA_DIST += getvaluetest.c hidparser.c -endif +endif !WITH_USB if WITH_GPIO TESTS += gpiotest @@ -88,21 +88,24 @@ gpiotest_SOURCES = generic_gpio_utest.c generic_gpio_liblocal.c nodist_gpiotest_SOURCES = generic_gpio_libgpiod.c generic_gpio_common.c gpiotest_LDADD = $(top_builddir)/drivers/libdummy_mockdrv.la gpiotest_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/tests -DDRIVERS_MAIN_WITHOUT_MAIN=1 -else +else !WITH_GPIO EXTRA_DIST += generic_gpio_utest.c generic_gpio_liblocal.c -endif +endif !WITH_GPIO CLEANFILES += generic_gpio_libgpiod.c generic_gpio_common.c EXTRA_DIST += generic_gpio_utest.h generic_gpio_test.txt # Make sure out-of-dir dependencies exist (especially when dev-building parts): $(top_builddir)/drivers/libdummy_mockdrv.la \ +$(top_builddir)/common/libnutconf.la \ $(top_builddir)/common/libcommon.la: dummy @cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F) ### Optional tests which can not be built everywhere # List of src files for CppUnit tests CPPUNITTESTSRC = example.cpp nutclienttest.cpp +# These are an optional part of cppunittest, if building WITH_NUTCONF +CPPUNITTESTSRC_NUTCONF = nutconf.cpp nutstream_ut.cpp nutconf_ut.cpp nutipc_ut.cpp # The test driver which orchestrates running those tests above CPPUNITTESTERSRC = cpputest.cpp @@ -123,13 +126,22 @@ check_PROGRAMS += cppnit if WITH_VALGRIND check-local: $(check_PROGRAMS) RES=0; for P in $^ ; do $(VALGRIND) ./$$P || { RES=$$? ; echo "FAILED: $(VALGRIND) ./$$P" >&2; }; done; exit $$RES -endif +endif WITH_VALGRIND cppunittest_CXXFLAGS = $(AM_CXXFLAGS) $(CPPUNIT_CFLAGS) $(CPPUNIT_CXXFLAGS) $(CPPUNIT_NUT_CXXFLAGS) $(CXXFLAGS) +###cppunittest_CXXFLAGS += -I$(top_srcdir)/include -DTOP_SRCDIR="\"$(top_srcdir)\"" cppunittest_LDFLAGS = $(CPPUNIT_LDFLAGS) $(CPPUNIT_LIBS) -cppunittest_LDADD = $(top_builddir)/clients/libnutclient.la $(top_builddir)/clients/libnutclientstub.la +cppunittest_LDADD = $(top_builddir)/clients/libnutclient.la +cppunittest_LDADD += $(top_builddir)/clients/libnutclientstub.la cppunittest_SOURCES = $(CPPUNITTESTSRC) $(CPPUNITTESTERSRC) +# Currently nutconf and related codebase causes woes for static analysis +# so we do not build it unless explicitly asked to. +if WITH_NUTCONF +cppunittest_SOURCES += $(CPPUNITTESTSRC_NUTCONF) +cppunittest_LDADD += $(top_builddir)/common/libnutconf.la +endif WITH_NUTCONF + cppnit_CXXFLAGS = $(AM_CXXFLAGS) $(CPPUNIT_CFLAGS) $(CPPUNIT_CXXFLAGS) $(CPPUNIT_NUT_CXXFLAGS) $(CXXFLAGS) cppnit_LDFLAGS = $(CPPUNIT_LDFLAGS) $(CPPUNIT_LIBS) cppnit_LDADD = $(top_builddir)/clients/libnutclient.la $(top_builddir)/clients/libnutclientstub.la @@ -144,7 +156,7 @@ $(top_builddir)/clients/libnutclientstub.la: dummy else !HAVE_CPPUNIT # Just redistribute test source into tarball if not building tests -EXTRA_DIST += $(CPPUNITTESTSRC) $(CPPCLIENTTESTSRC) $(CPPUNITTESTERSRC) +EXTRA_DIST += $(CPPUNITTESTSRC) $(CPPCLIENTTESTSRC) $(CPPUNITTESTERSRC) $(CPPUNITTESTSRC_NUTCONF) cppnit: @echo "SKIP: $@ not implemented without C++11 and CPPUNIT enabled" >&2 ; exit 1 @@ -154,7 +166,7 @@ endif !HAVE_CPPUNIT else !HAVE_CXX11 # Just redistribute test source into tarball if not building C++ at all -EXTRA_DIST += $(CPPUNITTESTSRC) $(CPPCLIENTTESTSRC) $(CPPUNITTESTERSRC) +EXTRA_DIST += $(CPPUNITTESTSRC) $(CPPCLIENTTESTSRC) $(CPPUNITTESTERSRC) $(CPPUNITTESTSRC_NUTCONF) cppnit: @echo "SKIP: $@ not implemented without C++11 and CPPUNIT enabled" >&2 ; exit 1 diff --git a/tests/nutconf.cpp b/tests/nutconf.cpp new file mode 100644 index 0000000000..4f4e3edb46 --- /dev/null +++ b/tests/nutconf.cpp @@ -0,0 +1,343 @@ +/* + tests/nutconf.cpp - based on CppUnit unit test example + + Copyright (C) + 2012 Emilien Kia + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include + +// Define to de-activate protection of parsing tool members: +#define UNITEST_MODE 1 + +#include "nutconf.hpp" +using namespace nut; + +#include +#include +using namespace std; + +class NutConfTest : public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE( NutConfTest ); + CPPUNIT_TEST( testOptions ); + CPPUNIT_TEST( testParseCHARS ); + CPPUNIT_TEST( testParseSTRCHARS ); + CPPUNIT_TEST( testPasreToken ); + CPPUNIT_TEST( testPasreTokenWithoutColon ); + CPPUNIT_TEST( testGenericConfigParser ); + CPPUNIT_TEST( testUpsmonConfigParser ); + CPPUNIT_TEST( testNutConfConfigParser ); + CPPUNIT_TEST( testUpsdConfigParser ); + CPPUNIT_TEST_SUITE_END(); + +public: + void setUp() override; + void tearDown() override; + + void testOptions(); + void testParseCHARS(); + void testParseSTRCHARS(); + void testPasreToken(); + void testPasreTokenWithoutColon(); + + void testGenericConfigParser(); + void testUpsmonConfigParser(); + void testNutConfConfigParser(); + void testUpsdConfigParser(); +}; + +// Registers the fixture into the 'registry' +CPPUNIT_TEST_SUITE_REGISTRATION( NutConfTest ); + + +void NutConfTest::setUp() +{ +} + + +void NutConfTest::tearDown() +{ +} + +void NutConfTest::testOptions() +{ + { + NutParser parse("Bonjour monde!", NutParser::OPTION_DEFAULT); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Has parsing options", 0u, parse.getOptions()); + CPPUNIT_ASSERT_MESSAGE("Has OPTION_IGNORE_COLON parsing option", !parse.hasOptions(NutParser::OPTION_IGNORE_COLON)); + } + + { + NutParser parse("Bonjour monde!", NutParser::OPTION_IGNORE_COLON); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Has bad parsing options", static_cast(NutParser::OPTION_IGNORE_COLON), parse.getOptions()); + CPPUNIT_ASSERT_MESSAGE("Has not OPTION_IGNORE_COLON parsing option", parse.hasOptions(NutParser::OPTION_IGNORE_COLON)); + } + +} + +void NutConfTest::testParseCHARS() +{ + { + NutParser parse("Bonjour monde!"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find first string 'Bonjour'", string("Bonjour"), parse.parseCHARS()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot get a character ''", ' ', parse.get()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find second string 'monde!'", string("monde!"), parse.parseCHARS()); + } + + { + NutParser parse("To\\ to"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find escaped string 'To to'", string("To to"), parse.parseCHARS()); + } + + { + NutParser parse("To\"to"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find escaped string 'To'", string("To"), parse.parseCHARS()); + } + + { + NutParser parse("To\\\"to"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find escaped string 'To\"to'", string("To\"to"), parse.parseCHARS()); + } + +} + + +void NutConfTest::testParseSTRCHARS() +{ + { + NutParser parse("Bonjour\"monde!\""); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find first string 'Bonjour'", string("Bonjour"), parse.parseSTRCHARS()); + parse.get(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find second string 'monde!'", string("monde!"), parse.parseSTRCHARS()); + } + + { + NutParser parse("To to"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find spaced string 'To tue de l’appareil qui se serait malencontreuo'", string("To to"), parse.parseSTRCHARS()); + } + + { + NutParser parse("To\\\"to"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find quoted-escaped string 'To\"to'", string("To\"to"), parse.parseSTRCHARS()); + } +} + +void NutConfTest::testPasreToken() +{ + static const char* src = + "Bonjour monde\n" + "[ceci]# Plouf\n" + "\n" + "titi = \"tata toto\"\n" + "NOTIFYFLAG LOWBATT SYSLOG+WALL\n" + "::1" + ; + NutParser parse(src); + +// NutConfigParser::Token tok = parse.parseToken(); +// std::cout << "token = " << tok.type << " - " << tok.str << std::endl; + + CPPUNIT_ASSERT_MESSAGE("Cannot find 1st token 'Bonjour'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "Bonjour")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 2nd token 'monde'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "monde")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 3th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 4rd token '['", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_BRACKET_OPEN, "[")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 5th token 'ceci'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "ceci")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 6th token ']'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_BRACKET_CLOSE, "]")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 7th token ' Plouf'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_COMMENT, " Plouf")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 8th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 9th token 'titi'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "titi")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 10th token '='", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EQUAL, "=")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 11th token 'tata toto'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_QUOTED_STRING, "tata toto")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 12th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 13th token 'NOTIFYFLAG'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "NOTIFYFLAG")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 14th token 'LOWBATT'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "LOWBATT")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 15th token 'SYSLOG+WALL'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "SYSLOG+WALL")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 16th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 17th token ':'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_COLON, ":")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 18th token ':'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_COLON, ":")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 19th token '1'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "1")); + +} + +void NutConfTest::testPasreTokenWithoutColon() +{ + static const char* src = + "Bonjour monde\n" + "[ceci]# Plouf\n" + "\n" + "titi = \"tata toto\"\n" + "NOTIFYFLAG LOWBATT SYSLOG+WALL\n" + "::1" + ; + NutParser parse(src, NutParser::OPTION_IGNORE_COLON); + +// NutConfigParser::Token tok = parse.parseToken(); +// std::cout << "token = " << tok.type << " - " << tok.str << std::endl; + + CPPUNIT_ASSERT_MESSAGE("Cannot find 1st token 'Bonjour'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "Bonjour")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 2nd token 'monde'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "monde")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 3th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 4rd token '['", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_BRACKET_OPEN, "[")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 5th token 'ceci'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "ceci")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 6th token ']'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_BRACKET_CLOSE, "]")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 7th token ' Plouf'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_COMMENT, " Plouf")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 8th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 9th token 'titi'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "titi")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 10th token '='", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EQUAL, "=")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 11th token 'tata toto'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_QUOTED_STRING, "tata toto")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 12th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 13th token 'NOTIFYFLAG'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "NOTIFYFLAG")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 14th token 'LOWBATT'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "LOWBATT")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 15th token 'SYSLOG+WALL'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "SYSLOG+WALL")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 16th token '\n'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_EOL, "\n")); + CPPUNIT_ASSERT_MESSAGE("Cannot find 17th token '::1'", parse.parseToken() == NutParser::Token(NutParser::Token::TOKEN_STRING, "::1")); + +} + +void NutConfTest::testGenericConfigParser() +{ + static const char* src = + "glovar1 = toto\n" + "glovar2 = \"truc bidule\"\n" + "\n" + "[section1] # One section\n" + "var1 = \"one value\"\n" + " \n" + "var2\n" + "\n" + "[section2]\n" + "var1 = other value\n" + "var toto"; + + GenericConfiguration conf; + conf.parseFromString(src); + + CPPUNIT_ASSERT_MESSAGE("Cannot find a global section", conf.sections.find("") != conf.sections.end() ); + CPPUNIT_ASSERT_MESSAGE("Cannot find global section's glovar1 variable", conf.sections[""]["glovar1"].values.front() == "toto" ); + CPPUNIT_ASSERT_MESSAGE("Cannot find global section's glovar2 variable", conf.sections[""]["glovar2"].values.front() == "truc bidule" ); + + CPPUNIT_ASSERT_MESSAGE("Cannot find section1", conf.sections.find("section1") != conf.sections.end() ); + CPPUNIT_ASSERT_MESSAGE("Cannot find section1's var1 variable", conf.sections["section1"]["var1"].values.front() == "one value" ); + CPPUNIT_ASSERT_MESSAGE("Cannot find section1's var2 variable", conf.sections["section1"]["var2"].values.size() == 0 ); + + CPPUNIT_ASSERT_MESSAGE("Cannot find section2", conf.sections.find("section2") != conf.sections.end() ); + CPPUNIT_ASSERT_MESSAGE("Cannot find section2's var1 variable", conf.sections["section2"]["var1"].values.front() == "other" ); + CPPUNIT_ASSERT_MESSAGE("Cannot find section2's var1 variable", *(++(conf.sections["section2"]["var1"].values.begin())) == "value" ); + CPPUNIT_ASSERT_MESSAGE("Cannot find section2's var variable", conf.sections["section2"]["var"].values.front() == "toto" ); + +} + +void NutConfTest::testUpsmonConfigParser() +{ + static const char* src = + "RUN_AS_USER nutmon\n" + "MONITOR myups@bigserver 1 monmaster blah master\n" + "MONITOR su700@server.example.com 1 upsmon secretpass slave\n" + "MONITOR myups@localhost 1 upsmon pass master\n" + "MINSUPPLIES 1\n" + "\n" + "# MINSUPPLIES 25\n" + "SHUTDOWNCMD \"/sbin/shutdown -h +0\"\n" + "NOTIFYCMD /usr/local/ups/bin/notifyme\n" + "POLLFREQ 30\n" + "POLLFREQALERT 5\n" + "HOSTSYNC 15\n" + "DEADTIME 15\n" + "POWERDOWNFLAG /etc/killpower\n" + "NOTIFYMSG ONLINE \"UPS %s on line power\"\n" + "NOTIFYFLAG LOWBATT SYSLOG+WALL\n" + "RBWARNTIME 43200\n" + "NOCOMMWARNTIME 300\n" + "FINALDELAY 5" + ; + + UpsmonConfiguration conf; + conf.parseFromString(src); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find RUN_AS_USER 'nutmon'", string("nutmon"), *conf.runAsUser); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find MINSUPPLIES 1", 1u, *conf.minSupplies); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find SHUTDOWNCMD '/sbin/shutdown -h +0'", string("/sbin/shutdown -h +0"), *conf.shutdownCmd); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find NOTIFYCMD '/usr/local/ups/bin/notifyme'", string("/usr/local/ups/bin/notifyme"), *conf.notifyCmd); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find POWERDOWNFLAG '/etc/killpower'", string("/etc/killpower"), *conf.powerDownFlag); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find POLLFREQ 30", 30u, *conf.poolFreq); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find POLLFREQALERT 5", 5u, *conf.poolFreqAlert); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find HOSTSYNC 15", 15u, *conf.hotSync); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find DEADTIME 15", 15u, *conf.deadTime); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find RBWARNTIME 43200", 43200u, *conf.rbWarnTime); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find NOCOMMWARNTIME 300", 300u, *conf.noCommWarnTime); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find FINALDELAY 5", 5u, *conf.finalDelay); + + CPPUNIT_ASSERT_MESSAGE("Find a NOTIFYFLAG ONLINE", !conf.notifyFlags[nut::UpsmonConfiguration::NOTIFY_ONLINE].set()); + CPPUNIT_ASSERT_MESSAGE("Cannot find a NOTIFYFLAG LOWBATT", conf.notifyFlags[nut::UpsmonConfiguration::NOTIFY_LOWBATT].set()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find a NOTIFYFLAG LOWBATT SYSLOG+WALL", 3u, static_cast(conf.notifyFlags[nut::UpsmonConfiguration::NOTIFY_LOWBATT])); + + + CPPUNIT_ASSERT_MESSAGE("Find a NOTIFYMSG LOWBATT", !conf.notifyMessages[nut::UpsmonConfiguration::NOTIFY_LOWBATT].set()); + CPPUNIT_ASSERT_MESSAGE("Cannot find a NOTIFYMSG ONLINE", conf.notifyMessages[nut::UpsmonConfiguration::NOTIFY_ONLINE].set()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find a NOTIFYMSG ONLINE \"UPS %s on line power\"", string("UPS %s on line power"), *conf.notifyMessages[nut::UpsmonConfiguration::NOTIFY_ONLINE]); +} + + +void NutConfTest::testNutConfConfigParser() +{ + static const char* src = + "\n\nMODE=standalone\n"; + + NutConfiguration conf; + conf.parseFromString(src); + + CPPUNIT_ASSERT_MESSAGE("Cannot find a MODE", conf.mode.set()); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find a MODE=standalone", nut::NutConfiguration::MODE_STANDALONE, *conf.mode); +} + +void NutConfTest::testUpsdConfigParser() +{ + static const char* src = + "MAXAGE 15\n" + "STATEPATH /var/run/nut\n" + "LISTEN 127.0.0.1 3493\n" + "LISTEN ::1 3493\n" + "MAXCONN 1024\n" + "CERTFILE /home/toto/cert.file" + ; + + UpsdConfiguration conf; + conf.parseFromString(src); + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find MAXAGE 15", 15u, *conf.maxAge); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find MAXCONN 1024", 1024u, *conf.maxConn); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find STATEPATH /var/run/nut", string("/var/run/nut"), *conf.statePath); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Cannot find CERTFILE /home/toto/cert.file", string("/home/toto/cert.file"), *conf.certFile); + + // Find Listen 127.0.0.1 3493 + { + typedef std::list ListenList; + UpsdConfiguration::Listen listen = {"127.0.0.1", 3493}; + ListenList::const_iterator it = find(conf.listens.begin(), conf.listens.end(), listen); + CPPUNIT_ASSERT_MESSAGE("LISTEN 127.0.0.1 3493", it != conf.listens.end()); + } + + // Find Listen ::1 3493 + { + typedef std::list ListenList; + UpsdConfiguration::Listen listen = {"::1", 3493}; + ListenList::const_iterator it = find(conf.listens.begin(), conf.listens.end(), listen); + CPPUNIT_ASSERT_MESSAGE("LISTEN ::1 3493", it != conf.listens.end()); + } + +} diff --git a/tests/nutconf_ut.cpp b/tests/nutconf_ut.cpp new file mode 100644 index 0000000000..b5b29d0474 --- /dev/null +++ b/tests/nutconf_ut.cpp @@ -0,0 +1,241 @@ +/* + NUT configuration unit test + + Copyright (C) + 2012 Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +#include "config.h" + +#include "nutstream.hpp" +#include "nutconf.hpp" +#include "nutwriter.hpp" + +#include + +/** + * \brief NUT configuration unit test + */ +class NutConfigUnitTest: public CppUnit::TestFixture { + private: + + CPPUNIT_TEST_SUITE(NutConfigUnitTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + /** + * \brief Load configuration from file + * + * \param config Configuration object + * \param file_name Configuration file name + */ + void load(nut::Serialisable * config, const std::string & file_name); + + /** + * \brief Check configuration serialization contents + * + * \param config Configuration object + * \param content Expected serialization + */ + void check(const nut::Serialisable * config, const std::string & content); + + /** nut.conf test */ + void testNutConfiguration(); + + /** upsmon.conf test */ + void testUpsmonConfiguration(); + + /** upsd.conf test */ + void testUpsdConfiguration(); + + /** ups.conf test */ + void testUpsConfiguration(); + + /** upsd.users test */ + void testUpsdUsersConfiguration(); + + public: + + inline void setUp() override {} + inline void tearDown() override {} + + inline void test() { + testNutConfiguration(); + testUpsmonConfiguration(); + testUpsdConfiguration(); + testUpsConfiguration(); + testUpsdUsersConfiguration(); + } + + virtual ~NutConfigUnitTest() override; +}; // end of class NutConfigUnitTest + +// Register the test suite +CPPUNIT_TEST_SUITE_REGISTRATION(NutConfigUnitTest); + + +void NutConfigUnitTest::load(nut::Serialisable * config, const std::string & file_name) { + nut::NutFile file(file_name, nut::NutFile::READ_ONLY); + + CPPUNIT_ASSERT(config->parseFrom(file)); +} + + +void NutConfigUnitTest::check(const nut::Serialisable * config, const std::string & content) { + nut::NutMemory mem; + + CPPUNIT_ASSERT(config->writeTo(mem)); + + std::string str; + + nut::NutStream::status_t status = mem.getString(str); + + CPPUNIT_ASSERT(nut::NutStream::NUTS_OK == status); + + if (content != str) { + std::cerr << "--- expected ---" << std::endl << content << "--- end ---" << std::endl; + std::cerr << "--- serialized ---" << std::endl << str << "--- end ---" << std::endl; + + CPPUNIT_ASSERT_MESSAGE("Configuration serialization check failed", 0); + } +} + + +void NutConfigUnitTest::testNutConfiguration() { + nut::NutConfiguration config; + + load(static_cast(&config), ABS_TOP_SRCDIR "/conf/nut.conf.sample"); + + config.mode = nut::NutConfiguration::MODE_STANDALONE; + + check(static_cast(&config), + "MODE=standalone\n" + ); +} + + +void NutConfigUnitTest::testUpsmonConfiguration() { + nut::UpsmonConfiguration config; + + // Note: this file gets generated from a .in template + load(static_cast(&config), ABS_TOP_BUILDDIR "/conf/upsmon.conf.sample"); + + config.shutdownCmd = "/sbin/shutdown -h +2 'System shutdown in 2 minutes!'"; + config.powerDownFlag = "/run/nut/killpower"; + config.poolFreqAlert = 10; + config.deadTime = 30; + + check(static_cast(&config), + "SHUTDOWNCMD \"/sbin/shutdown -h +2 'System shutdown in 2 minutes!'\"\n" + "POWERDOWNFLAG /run/nut/killpower\n" + "MINSUPPLIES 1\n" + "POLLFREQ 5\n" + "POLLFREQALERT 10\n" + "HOSTSYNC 15\n" + "DEADTIME 30\n" + "RBWARNTIME 43200\n" + "NOCOMMWARNTIME 300\n" + "FINALDELAY 5\n" + ); +} + + +void NutConfigUnitTest::testUpsdConfiguration() { + nut::UpsdConfiguration config; + + load(static_cast(&config), ABS_TOP_SRCDIR "/conf/upsd.conf.sample"); + + config.maxAge = 15; + config.statePath = "/var/run/nut"; + config.maxConn = 1024; + config.certFile = "/usr/share/ssl-cert/ssleay.cnf"; + + nut::UpsdConfiguration::Listen listen; + + listen.address = "127.0.0.1"; + listen.port = 3493; + + config.listens.push_back(listen); + + listen.address = "::1"; + + config.listens.push_back(listen); + + check(static_cast(&config), + "MAXAGE 15\n" + "MAXCONN 1024\n" + "STATEPATH /var/run/nut\n" + "CERTFILE /usr/share/ssl-cert/ssleay.cnf\n" + "LISTEN 127.0.0.1 3493\n" + "LISTEN ::1 3493\n" + ); +} + + +void NutConfigUnitTest::testUpsConfiguration() { + nut::UpsConfiguration config; + + load(static_cast(&config), ABS_TOP_SRCDIR "/conf/ups.conf.sample"); + + static const std::string my_ups("powerpal"); + + config.setDriver(my_ups, "blazer_ser"); + config.setPort(my_ups, "/dev/ttyS0"); + config.setDescription(my_ups, "Web server"); + + // Note: "maxretry = 3" comes from current ups.conf.sample non-comment lines + check(static_cast(&config), + "maxretry = 3\n\n" + "[powerpal]\n" + "\tdesc = \"Web server\"\n" + "\tdriver = blazer_ser\n" + "\tport = /dev/ttyS0\n" + "\n" + ); +} + + +void NutConfigUnitTest::testUpsdUsersConfiguration() { + nut::UpsdUsersConfiguration config; + + load(static_cast(&config), ABS_TOP_SRCDIR "/conf/upsd.users.sample"); + + config.setPassword("upsmon", "ytrewq"); + config.setUpsmonMode(nut::UpsdUsersConfiguration::UPSMON_MASTER); + + config.setPassword("admin", "qwerty=ui"); + config.setActions("admin", nut::ConfigParamList(1, "SET")); + config.setInstantCommands("admin", nut::ConfigParamList(1, "ALL")); + + check(static_cast(&config), + "[admin]\n" + "\tactions = SET\n" + "\tinstcmds = ALL\n" + "\tpassword = \"qwerty=ui\"\n" + "\n" + "[upsmon]\n" + "\tpassword = ytrewq\n" + "\tupsmon master\n" + "\n" + ); +} + +// Implement out of class declaration to avoid +// error: 'SomeClass' has no out-of-line virtual method +// definitions; its vtable will be emitted in every translation unit +// [-Werror,-Wweak-vtables] +NutConfigUnitTest::~NutConfigUnitTest() {} diff --git a/tests/nutipc_ut.cpp b/tests/nutipc_ut.cpp new file mode 100644 index 0000000000..273e0a2eec --- /dev/null +++ b/tests/nutipc_ut.cpp @@ -0,0 +1,200 @@ +/* + NUT IPC unit test + + Copyright (C) 2012 + + \author Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "nutipc.hpp" +#include "nutstream.hpp" +#include "config.h" + +#include + +#include + +extern "C" { +#include +#include +#include +} + + +/** + * \brief NUT IPC module unit test + */ +class NutIPCUnitTest: public CppUnit::TestFixture { + private: + + CPPUNIT_TEST_SUITE(NutIPCUnitTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + /** External command execution test */ + void testExec(); + + /** + * \brief Test signal handler + * + * \param signal Signal caught + */ + static void testSignalHandler(int signal); + + /** Signal sending test */ + void testSignalSend(); + + /** Signal receiving test */ + void testSignalRecv(); + + public: + + inline void setUp() override {} + inline void tearDown() override {} + + inline void test() { + testExec(); + testSignalSend(); + testSignalRecv(); + } + + virtual ~NutIPCUnitTest() override; +}; // end of class NutIPCUnitTest + +// Register the test suite +CPPUNIT_TEST_SUITE_REGISTRATION(NutIPCUnitTest); + + +void NutIPCUnitTest::testExec() { + static const std::string bin = "/bin/sh"; + + nut::Process::Executor::Arguments args; + + args.push_back("-c"); + args.push_back("exit 123"); + + nut::Process::Execution child(bin, args); + + CPPUNIT_ASSERT(123 == child.wait()); + + CPPUNIT_ASSERT(0 == nut::Process::execute("test 'Hello world' == 'Hello world'")); +} + + +/** Last signal caught */ +static int signal_caught = 0; + +void NutIPCUnitTest::testSignalHandler(int signal) { + signal_caught = signal; +} + +void NutIPCUnitTest::testSignalSend() { + struct sigaction action; + + pid_t my_pid = nut::Process::getPID(); + + // Set SIGUSR1 signal handler + ::memset(&action, 0, sizeof(action)); + ::sigemptyset(&action.sa_mask); + + action.sa_handler = &testSignalHandler; + + CPPUNIT_ASSERT(0 == ::sigaction(static_cast(nut::Signal::USER1), &action, nullptr)); + + // Send signal directly + CPPUNIT_ASSERT(0 == nut::Signal::send(nut::Signal::USER1, my_pid)); + + CPPUNIT_ASSERT(static_cast(nut::Signal::USER1) == signal_caught); + + signal_caught = 0; + + // Save PID to a PIDfile + static const std::string pid_file_name("/tmp/foobar.pid"); + + std::stringstream my_pid_ss; + + my_pid_ss << my_pid; + + nut::NutFile pid_file(pid_file_name, nut::NutFile::WRITE_ONLY); + + pid_file.putString(my_pid_ss.str()); + + pid_file.closex(); + + // Send signal to process via the PIDfile + CPPUNIT_ASSERT(0 == nut::Signal::send(nut::Signal::USER1, pid_file_name)); + + CPPUNIT_ASSERT(static_cast(nut::Signal::USER1) == signal_caught); + + pid_file.removex(); + + signal_caught = 0; +} + + +/** Caught signal list */ +static nut::Signal::List caught_signals; + +/** Signal handler routine */ +class TestSignalHandler: public nut::Signal::Handler { + public: + + void operator () (nut::Signal::enum_t signal) override { + caught_signals.push_back(signal); + } + + virtual ~TestSignalHandler() override; +}; // end of class TestSignalHandler + +void NutIPCUnitTest::testSignalRecv() { + // Create signal handler thread + nut::Signal::List signals; + + signals.push_back(nut::Signal::USER1); + signals.push_back(nut::Signal::USER2); + + nut::Signal::HandlerThread sig_handler(signals); + + pid_t my_pid = nut::Process::getPID(); + + CPPUNIT_ASSERT(0 == nut::Signal::send(nut::Signal::USER2, my_pid)); + CPPUNIT_ASSERT(0 == nut::Signal::send(nut::Signal::USER1, my_pid)); + CPPUNIT_ASSERT(0 == nut::Signal::send(nut::Signal::USER1, my_pid)); + + // Let the sig. handler thread finish... + ::sleep(1); + + CPPUNIT_ASSERT(caught_signals.size() == 3); + + CPPUNIT_ASSERT(caught_signals.front() == nut::Signal::USER2); + + caught_signals.pop_front(); + + CPPUNIT_ASSERT(caught_signals.front() == nut::Signal::USER1); + + caught_signals.pop_front(); + + CPPUNIT_ASSERT(caught_signals.front() == nut::Signal::USER1); +} + +// Implement out of class declaration to avoid +// error: 'SomeClass' has no out-of-line virtual method +// definitions; its vtable will be emitted in every translation unit +// [-Werror,-Wweak-vtables] +TestSignalHandler::~TestSignalHandler() {} +NutIPCUnitTest::~NutIPCUnitTest() {} diff --git a/tests/nutstream_ut.cpp b/tests/nutstream_ut.cpp new file mode 100644 index 0000000000..29f507e68b --- /dev/null +++ b/tests/nutstream_ut.cpp @@ -0,0 +1,339 @@ +/* + NUT stream unit test + + Copyright (C) + 2012 Vaclav Krpec + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + + +#include "nutstream.hpp" + +#include + +#include +#include +#include + +extern "C" { +#include +#include +#include +#include +#include +} + + +/** Test data */ +static const std::string test_data( + "And the mother of Jesus said unto the Lord, ""They have no more wine.""\n" + "And Jesus said unto the servants, ""Fill six water pots with water.""\n" + "And they did so.\n" + "And when the steward of the feast did taste of the water from the pots, it had become wine.\n" + "And he knew not whence it had come.\n" + "But the servants did know, and they applauded loudly in the kitchen.\n" + "And they said unto the Lord, ""How the Hell did you do that!?""\n" + "And inquired of him ""Do you do children’s parties?""\n" + "And the Lord said ""No.""\n" + ); + + +/** + * \brief Read and check test data from a stream + * + * \param stream Input stream + * + * \retval true in case of success + * \retval false in case of failure + */ +static bool readTestData(nut::NutStream * stream) { + assert(nullptr != stream); + + // Read characters from the stream + for (size_t pos = 0, iter = 0; ; ++iter) { + char ch; + + nut::NutStream::status_t status = stream->getChar(ch); + + if (nut::NutStream::NUTS_ERROR == status) + return false; + + if (nut::NutStream::NUTS_EOF == status) + break; + + if (nut::NutStream::NUTS_OK != status) + return false; + + if (ch != test_data.at(pos)) + return false; + + // Every other character shall be checked twice + if (0 == iter % 8) + continue; + + // Consume current character + stream->readChar(); + + ++pos; + } + + return true; +} + + +/** + * \brief Write test data to a stream + * + * \param stream Output stream + * + * \retval true in case of success + * \retval false in case of failure + */ +static bool writeTestData(nut::NutStream * stream) { + assert(nullptr != stream); + + size_t pivot = static_cast(0.5 * static_cast(test_data.size())); + + // Write characters to the stream + for (size_t i = 0; i < pivot; ++i) { + char ch = test_data.at(i); + + nut::NutStream::status_t status = stream->putChar(ch); + + if (nut::NutStream::NUTS_OK != status) + return false; + } + + // Write string to the stream + const std::string str = test_data.substr(pivot); + + nut::NutStream::status_t status = stream->putString(str); + + CPPUNIT_ASSERT(nut::NutStream::NUTS_OK == status); + + return true; +} + + +/** + * \brief NUT stream unit test suite (abstract) + */ +class NutStreamUnitTest: public CppUnit::TestFixture { + protected: + + /** + * \brief Read test data from stream + * + * \c CPPUNIT_ASSERT macro is used to resolve error. + * + * \param stream Input stream + */ + inline void readx(nut::NutStream * stream) { + CPPUNIT_ASSERT(readTestData(stream)); + } + + /** + * \brief Write test data to stream + * + * \c CPPUNIT_ASSERT macro is used to resolve error. + * + * \param stream Output stream + */ + inline void writex(nut::NutStream * stream) { + CPPUNIT_ASSERT(writeTestData(stream)); + } + + virtual ~NutStreamUnitTest() override; +}; // end of class NutStreamUnitTest + + +/** + * \brief NUT memory stream unit test suite + */ +class NutMemoryUnitTest: public NutStreamUnitTest { + private: + + CPPUNIT_TEST_SUITE(NutMemoryUnitTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + public: + + inline void setUp() override {} + inline void tearDown() override {} + + virtual void test(); + +}; // end of class NutMemoryUnitTest + + +void NutMemoryUnitTest::test() { + nut::NutMemory input_mstream(test_data); + nut::NutMemory output_mstream; + + readx(&input_mstream); + writex(&output_mstream); + readx(&output_mstream); +} + + +/** + * \brief NUT file stream unit test suite + */ +class NutFileUnitTest: public NutStreamUnitTest { + private: + + CPPUNIT_TEST_SUITE(NutFileUnitTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + public: + + inline void setUp() override {} + inline void tearDown() override {} + + virtual void test(); + +}; // end of class NutFileUnitTest + + +void NutFileUnitTest::test() { + nut::NutFile fstream(nut::NutFile::ANONYMOUS); + + writex(&fstream); + readx(&fstream); +} + + +/** + * \brief NUT socket stream unit test suite + */ +class NutSocketUnitTest: public NutStreamUnitTest { + private: + + /** NUT socket stream unit test: writer */ + class Writer { + private: + + /** Remote listen address */ + nut::NutSocket::Address m_remote_address; + + public: + + /** + * \brief Constructor + * + * \param addr Remote address + */ + Writer(const nut::NutSocket::Address & addr): + m_remote_address(addr) + {} + + /** + * \brief Writer routine + * + * Writer shall write contents of the test data + * to its connection socket. + * + * \retval true in case of success + * \retval false otherwise + */ + bool run(); + + }; // end of class Writer + + /** TCP listen address IPv4 */ + static const nut::NutSocket::Address m_listen_address; + + CPPUNIT_TEST_SUITE(NutSocketUnitTest); + CPPUNIT_TEST(test); + CPPUNIT_TEST_SUITE_END(); + + public: + + inline void setUp() override {} + inline void tearDown() override {} + + virtual void test(); + +}; // end of class NutSocketUnitTest + + +/* Randomize to try avoiding collisions in parallel testing */ +const nut::NutSocket::Address NutSocketUnitTest::m_listen_address(127, 0, 0, 1, 10000 + (::random() % 40000)); + + +bool NutSocketUnitTest::Writer::run() { + nut::NutSocket conn_sock; + + if (!conn_sock.connect(m_remote_address)) + return false; + + if (!writeTestData(&conn_sock)) + return false; + + if (!conn_sock.close()) + return false; + + return true; +} + + +void NutSocketUnitTest::test() { + // Fork writer + pid_t writer_pid = ::fork(); + + if (!writer_pid) { + // Wait for listen socket + ::sleep(1); + + // Run writer + CPPUNIT_ASSERT(Writer(m_listen_address).run()); + + return; + } + + // Listen + nut::NutSocket listen_sock; + + CPPUNIT_ASSERT(listen_sock.bind(m_listen_address)); + CPPUNIT_ASSERT(listen_sock.listen(10)); + + // Accept connection + nut::NutSocket conn_sock(nut::NutSocket::ACCEPT, listen_sock); + + // Read the test data + readx(&conn_sock); + + // Wait for writer + int writer_exit; + pid_t wpid = ::waitpid(writer_pid, &writer_exit, 0); + + CPPUNIT_ASSERT(wpid == writer_pid); + CPPUNIT_ASSERT(0 == writer_exit); +} + + +// Register the test suite +CPPUNIT_TEST_SUITE_REGISTRATION(NutMemoryUnitTest); +CPPUNIT_TEST_SUITE_REGISTRATION(NutFileUnitTest); +CPPUNIT_TEST_SUITE_REGISTRATION(NutSocketUnitTest); + +// Implement out of class declaration to avoid +// error: 'SomeClass' has no out-of-line virtual method +// definitions; its vtable will be emitted in every translation unit +// [-Werror,-Wweak-vtables] +NutStreamUnitTest::~NutStreamUnitTest() {} diff --git a/tools/Makefile.am b/tools/Makefile.am index bc52174d4c..97cdccf0f1 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -20,7 +20,7 @@ # to have nutscan-{usb,snmp}.h built before going into the nut-scanner # sub-directory. For good measure we also call this from nut-scanner's # make, to handle developer workflow (editing the *.c sources this uses). -SUBDIRS = . nut-scanner +SUBDIRS = . nut-scanner nutconf PYTHON = @PYTHON@ diff --git a/tools/nut-scanner/nut-scan.h b/tools/nut-scanner/nut-scan.h index 817e651b6e..3bba701369 100644 --- a/tools/nut-scanner/nut-scan.h +++ b/tools/nut-scanner/nut-scan.h @@ -128,10 +128,10 @@ typedef struct nutscan_ipmi { #define IPMI_AUTHENTICATION_TYPE_STRAIGHT_PASSWORD_KEY 0x04 #define IPMI_AUTHENTICATION_TYPE_OEM_PROP 0x05 #define IPMI_AUTHENTICATION_TYPE_RMCPPLUS 0x06 -#endif +#endif /* IPMI_AUTHENTICATION_TYPE_NONE */ #ifndef IPMI_PRIVILEGE_LEVEL_ADMIN #define IPMI_PRIVILEGE_LEVEL_ADMIN 0x04 -#endif +#endif /* IPMI_PRIVILEGE_LEVEL_ADMIN */ #define IPMI_1_5 1 #define IPMI_2_0 0 diff --git a/tools/nut-scanner/nut-scanner.c b/tools/nut-scanner/nut-scanner.c index 04b9c722fd..500b3d9d59 100644 --- a/tools/nut-scanner/nut-scanner.c +++ b/tools/nut-scanner/nut-scanner.c @@ -226,7 +226,6 @@ static void * run_eaton_serial(void *arg) dev[TYPE_EATON_SERIAL] = nutscan_scan_eaton_serial(serial_ports); return NULL; } - #endif /* HAVE_PTHREAD */ static void show_usage(void) diff --git a/tools/nutconf/.gitignore b/tools/nutconf/.gitignore new file mode 100644 index 0000000000..95548aa108 --- /dev/null +++ b/tools/nutconf/.gitignore @@ -0,0 +1,2 @@ +nutconf + diff --git a/tools/nutconf/Makefile.am b/tools/nutconf/Makefile.am new file mode 100644 index 0000000000..f5bbbd5e95 --- /dev/null +++ b/tools/nutconf/Makefile.am @@ -0,0 +1,32 @@ +# Network UPS Tools: NUT configuration tool + +all: $(bin_PROGRAMS) + +bin_PROGRAMS = + +# Currently nutconf and related codebase causes woes for static analysis +# so we do not build it unless explicitly asked to. +if WITH_NUTCONF +bin_PROGRAMS += nutconf +nutconf_SOURCES = nutconf.cpp +nutconf_CXXFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include +nutconf_LDADD = $(top_builddir)/common/libcommon.la \ + $(top_builddir)/common/libnutconf.la + +# Add support for device scanning +if WITH_LIBLTDL +nutconf_CXXFLAGS += -DWITH_NUTSCANNER -I$(top_srcdir)/tools/nut-scanner +nutconf_LDADD += $(top_builddir)/tools/nut-scanner/libnutscan.la +endif WITH_LIBLTDL + +endif WITH_NUTCONF + +# Make sure out-of-dir dependencies exist (especially when dev-building parts): +$(top_builddir)/common/libcommon.la \ +$(top_builddir)/common/libnutconf.la \ +$(top_builddir)/tools/nut-scanner/libnutscan.la: dummy + @cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F) + +dummy: + +MAINTAINERCLEANFILES = Makefile.in .dirstamp diff --git a/tools/nutconf/nutconf.cpp b/tools/nutconf/nutconf.cpp new file mode 100644 index 0000000000..15d37ac4a4 --- /dev/null +++ b/tools/nutconf/nutconf.cpp @@ -0,0 +1,3212 @@ +/* + * Copyright (C) 2013 - EATON + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +/*! \file nutconf.cpp + \brief NUT configuration tool + \author Frederic Bohe + \author Vaclav Krpec +*/ + +#include "config.h" +#include "nutconf.hpp" +#include "nutstream.hpp" + +extern "C" { +#if (defined WITH_NUTSCANNER) +#include "nut-scan.h" +#include "nutscan-init.h" +#include "nutscan-device.h" +#endif // defined WITH_NUTSCANNER +} + +#include +#include +#include +#include +#include +#include +#include + + +class Usage { + private: + + /** Usage text */ + static const char * s_text[]; + + /** Private constructor (no instances) */ + Usage() {} + + public: + + /** Print usage */ + static void print(const std::string & bin); + +}; // end of class usage + + +const char * Usage::s_text[] = { + " -h -help", + " --help Display this help and exit", + " --autoconfigure Perform automatic configuration", + " --is-configured Checks whether NUT is configured", + " --local Sets configuration directory", + " --system Sets configuration directory to " CONFPATH " (default)", + " --get-mode Gets NUT mode (see below)", + " --set-mode Sets NUT mode (see below)", + " --set-monitor Configures one monitor (see below)", + " All existing entries are removed; however, it may be", + " specified multiple times to set multiple entries", + " --add-monitor Same as --set-monitor, but keeps existing entries", + " The two options are mutually exclusive", + " --set-listen [] Configures one listen address for the NUT daemon", + " All existing entries are removed; however, it may be", + " specified multiple times to set multiple entries", + " --add-listen [] Same as --set-listen, but keeps existing entries", + " The two options are mutually exclusive", + " --set-device Configures one UPS device (see below)", + " All existing devices are removed; however, it may be", + " specified multiple times to set multiple devices", + " --add-device Same as --set-device, but keeps existing devices", + " The two options are mutually exclusive", + " --set-notifyflags + Configures notify flags for notification type", + " See below for the types and supported flags", + " Existing flags are replaced", + " --add-notifyflags + Same as --set-notifyflags, but keeps existing flags", + " --set-notifymsg Configures notification message for the type", + " --set-notifycmd Configures notification command", + " --set-shutdowncmd Configures shutdown command", + " --set-minsupplies Configures minimum of required power supplies", + " --set-powerdownflag Configures powerdown flag file", + " --set-user Configures one user (see below)", + " All existing users are removed; however, it may be", + " specified multiple times to set multiple users", + " --add-user Same as --set-user, but keeps existing users", + " The two options are mutually exclusive", + " -v", + " --verbose Increase verbosity of output one level", + " May be specified multiple times", +#if (defined WITH_NUTSCANNER) +#if (defined WITH_SNMP) + " --scan-snmp Scan SNMP devices (see below)", + " May be specified multiple times", +#endif // defined WITH_SNMP +#if (defined WITH_USB) + " --scan-usb Scan USB devices", +#endif // defined WITH_USB +#if (defined WITH_NEON) + " --scan-xml-http [] Scan XML/HTTP devices (optional timeout in us)", +#endif // defined WITH_NEON + " --scan-nut Scan NUT devices (see below for the specs)", + " May be specified multiple times", +#if (defined WITH_AVAHI) + " --scan-avahi [] Scan Avahi devices (optional timeout in us)", +#endif // defined WITH_AVAHI +#if (defined WITH_IPMI) + " --scan-ipmi Scan IPMI devices (see below)", + " May be specified multiple times", +#endif // defined WITH_IPMI + " --scan-serial * Scan for serial devices on specified ports", +#endif // defined WITH_NUTSCANNER + "", + "NUT modes: standalone, netserver, netclient, controlled, manual, none", + "Monitor is specified by the following sequence:", + " [:] (\"master\"|\"slave\")", + "UPS device is specified by the following sequence:", + " [=]*", + "Notification types:", + " ONLINE, ONBATT, LOWBATT, FSD, COMMOK, COMMBAD, SHUTDOWN, REPLBATT, NOCOMM, NOPARENT", + "Notification flags:", + " SYSLOG, WALL, EXEC, IGNORE", + "User specification:", + "The very 1st argument is the username (but see the upsmon exception, below).", + "Next arguments use scheme of key/value pairs in form =", + "Known keys are:", + " password, actions (from {SET,FSD}), instcmds (accepted multiple times)", + "Specially, for the upsmon user, the 1st argument takes form of", + " upsmon={master|slave}", +#if (defined WITH_NUTSCANNER) +#if (defined WITH_SNMP) + "SNMP device scan specification:", + " [=]*", + "Known attributes are:", + " timeout (in us), community, sec-level, sec-name, auth-password, priv-password,", + " auth-protocol, priv-protocol, peer-name", +#endif // defined WITH_SNMP + "NUT device scan specification:", + " []", +#if (defined WITH_IPMI) + "IMPI device scan specification:", + " [=]*", + "Known attributes are:", + " username, password, auth-type, cipher-suite-id, K-g-BMC-key, priv-level,", + " workaround-flags, version", +#endif // defined WITH_IPMI +#endif // defined WITH_NUTSCANNER + "", +}; + + +void Usage::print(const std::string & bin) { + std::cerr + << "Usage: " << bin << " [OPTIONS]" << std::endl + << std::endl + << "OPTIONS:" << std::endl; + + for (size_t i = 0; i < sizeof(s_text) / sizeof(char *); ++i) { + std::cerr << s_text[i] << std::endl; + } +} + + +/** Command line options */ +class Options { + public: + + /** Options list */ + typedef std::list List; + + /** Option arguments list */ + typedef std::list Arguments; + + protected: + + /** Options map */ + typedef std::multimap Map; + + private: + + /** Option type */ + typedef enum { + singleDash, /**< Single-dash prefixed option */ + doubleDash, /**< Double-dash prefixed option */ + } type_t; + + /** Arguments of the last option processed (\c nullptr means bin. args) */ + Arguments * m_last; + + /** Binary arguments */ + Arguments m_args; + + /** Single-dashed options */ + Map m_single; + + /** Double-dashed options */ + Map m_double; + + /** + * \brief Add option + * + * \param type Option type + * \param opt Option + */ + void add(type_t type, const std::string & opt); + + /** + * \brief Add argument to the last option + * + * \param arg Argument + */ + inline void addArg(const std::string & arg) { + Arguments * args = nullptr != m_last ? m_last : &m_args; + + args->push_back(arg); + } + + /** + * \brief Count options + * + * \param map Option map + * \param opt Option + * + * \return Options count + */ + size_t count(const Map & map, const std::string & opt) const; + + /** + * \brief Get option arguments + * + * \param[in] map Option map + * \param[in] opt Option + * \param[out] args Option arguments + * \param[in] order Option order (1st by default) + * + * \retval true IFF the option was specified on the command line + * \retval false otherwise + */ + bool get(const Map & map, const std::string & opt, Arguments & args, size_t order = 0) const; + + /** + * \brief Get options list + * + * \param[in] map Option map + * \param[out] list Option list + * + * \return List of options + */ + void strings(const Map & map, List & list) const; + + /** + * \brief Dump options (for debugging reasons) + * + * \param stream Output stream + */ + void dump(std::ostream & stream) const; + + public: + + /** + * \brief Constructor (from \c main routine arguments) + * + * \param argv Argument list + * \param argc Argument count + */ + Options(char * const argv[], int argc); + + /** + * \brief Count single-dashed options + * + * \param opt Option + * + * \return Options count + */ + inline size_t countSingle(const std::string & opt) const { + return count(m_single, opt); + } + + /** + * \brief Count double-dashed options + * + * \param opt Option + * + * \return Options count + */ + inline size_t countDouble(const std::string & opt) const { + return count(m_double, opt); + } + + /** + * \brief Count options (single or double dashed) + * + * \param opt Option + * + * \return Options count + */ + inline bool count(const std::string & opt) const { + return countSingle(opt) + countDouble(opt); + } + + /** + * \brief Check single-dashed option existence + * + * \param opt Option + * + * \retval true IFF the option was specified on the command line + * \retval false otherwise + */ + inline bool existsSingle(const std::string & opt) const { + return countSingle(opt) > 0; + } + + /** + * \brief Check double-dashed option existence + * + * \param opt Option + * + * \retval true IFF the option was specified on the command line + * \retval false otherwise + */ + inline bool existsDouble(const std::string & opt) const { + return countDouble(opt) > 0; + } + + /** + * \brief Check option existence (single or double dashed) + * + * \param opt Option + * + * \retval true IFF the option was specified on the command line + * \retval false otherwise + */ + inline bool exists(const std::string & opt) const { + return existsSingle(opt) || existsDouble(opt); + } + + /** + * \brief Get single-dashed option arguments + * + * \param[in] opt Option + * \param[out] args Option arguments + * \param[in] order Option order (1st by default) + * + * \retval true IFF the option was specified on the command line + * \retval false otherwise + */ + inline bool getSingle(const std::string & opt, Arguments & args, size_t order = 0) const { + return get(m_single, opt, args, order); + } + + /** + * \brief Get double-dashed option arguments + * + * \param[in] opt Option + * \param[out] args Option arguments + * \param[in] order Option order (1st by default) + * + * \retval true IFF the option was specified on the command line + * \retval false otherwise + */ + inline bool getDouble(const std::string & opt, Arguments & args, size_t order = 0) const { + return get(m_double, opt, args, order); + } + + /** + * \brief Get binary arguments + * + * \return Arguments of the binary itself + */ + inline const Arguments & get() const { return m_args; } + + /** + * \brief Get single-dashed options list + * + * \return List of single-dashed options + */ + inline List stringsSingle() const { + List list; + + strings(m_single, list); + + return list; + } + + /** + * \brief Get double-dashed options list + * + * \return List of double-dashed options + */ + inline List stringsDouble() const { + List list; + + strings(m_double, list); + + return list; + } + + /** + * \brief Get all options list + * + * \return List of single or double-dashed options + */ + inline List strings() const; + +}; // end of class Options + + +void Options::add(Options::type_t type, const std::string & opt) { + Map * map; + + switch (type) { + case singleDash: + map = &m_single; + + break; + + case doubleDash: + map = &m_double; + + break; + } + + Map::iterator entry = map->insert(Map::value_type(opt, Arguments())); + + m_last = &entry->second; +} + + +size_t Options::count(const Options::Map & map, const std::string & opt) const { + size_t cnt = 0; + + Map::const_iterator entry = map.find(opt); + + for (; entry != map.end() && entry->first == opt; ++entry) + ++cnt; + + return cnt; +} + + +bool Options::get(const Options::Map & map, const std::string & opt, Arguments & args, size_t order) const { + Map::const_iterator entry = map.find(opt); + + if (map.end() == entry) + return false; + + for (; order; --order) { + Map::const_iterator next = entry; + + ++next; + + if (map.end() == next || next->first != opt) + return false; + + entry = next; + } + + args = entry->second; + + return true; +} + + +void Options::strings(const Map & map, List & list) const { + for (Map::const_iterator opt = map.begin(); opt != map.end(); ++opt) + list.push_back(opt->first); +} + + +void Options::dump(std::ostream & stream) const { + stream << "----- Options dump begin -----" << std::endl; + + Map::const_iterator opt; + + Arguments::const_iterator arg; + + for (opt = m_single.begin(); opt != m_single.end(); ++opt) { + stream << '-' << opt->first << ' '; + + for (arg = opt->second.begin(); arg != opt->second.end(); ++arg) + stream << *arg << ' '; + + stream << std::endl; + } + + for (opt = m_double.begin(); opt != m_double.end(); ++opt) { + stream << "--" << opt->first << ' '; + + for (arg = opt->second.begin(); arg != opt->second.end(); ++arg) + stream << *arg << ' '; + + stream << std::endl; + } + + stream << "-- "; + + for (arg = m_args.begin(); arg != m_args.end(); ++arg) + stream << *arg; + + stream << std::endl << "----- Options dump end -----" << std::endl; +} + + +Options::Options(char * const argv[], int argc): m_last(nullptr) { + for (int i = 1; i < argc; ++i) { + const std::string arg(argv[i]); + + // Empty string is the current option argument, too + // '-' alone is also an option argument + // (like stdout placeholder etc) + if (arg.empty() || '-' != arg[0] || 1 == arg.size()) + addArg(arg); + + // Single-dashed option + else if ('-' != arg[1]) + add(singleDash, arg.substr(1)); + + // "--" alone is valid as it means that what follows + // belongs to the binary ("empty" option arguments) + else if (2 == arg.size()) + m_last = nullptr; + + // Double-dashed option + else if ('-' != arg[2]) + add(doubleDash, arg.substr(2)); + + // "---" prefix means an option argument + else + addArg(arg); + } + + // Options debugging + //dump(std::cerr); +} + + +Options::List Options::strings() const { + List list = stringsSingle(); + + strings(m_double, list); + + return list; +} + + +#if (defined WITH_NUTSCANNER) + +/** NUT scanner wrapper */ +class NutScanner { + public: + + /** Device info */ + class Device { + friend class NutScanner; + + public: + + /** Device type */ + typedef nutscan_device_type_t type_t; + + /** Device options */ + typedef std::map options_t; + + public: + + const std::string type; /**< Type */ + const std::string driver; /**< Driver */ + const std::string port; /**< Port */ + const options_t options; /**< Options */ + + private: + + /** + * \brief Create options + * + * \param opt libnutscan device options + * + * \return Device option list + */ + static options_t createOptions(nutscan_options_t * opt); + + /** Constructor */ + Device(nutscan_device_t * dev); + + }; // end of class Device + + /** Device list */ + typedef std::list devices_t; + + /** SNMP attributes */ + struct SNMPAttributes { + std::string community; /**< Community */ + std::string sec_level; /**< Sec. level */ + std::string sec_name; /**< Sec. name */ + std::string auth_passwd; /**< Authentication password */ + std::string priv_passwd; /**< Priv. password */ + std::string auth_proto; /**< Authentication protocol */ + std::string priv_proto; /**< Priv. protocol */ + std::string peer_name; /**< Peer name */ + }; // end of class SNMPAttributes + + /** IMPI attributes */ + struct IPMIAttributes { + std::string username; /**< Username */ + std::string passwd; /**< Password */ + int auth_type; /**< Authentication type */ + int cipher_suite_id; /**< Cipher suite ID */ + std::string K_g_BMC_key; /**< Optional 2nd key */ + int priv_level; /**< Priviledge level */ + unsigned wa_flags; /**< Workaround flags */ + int version; /**< IPMI protocol version */ + + /** Constructor */ + IPMIAttributes(): + auth_type(IPMI_AUTHENTICATION_TYPE_MD5), + cipher_suite_id(3), + priv_level(IPMI_PRIVILEGE_LEVEL_ADMIN), + wa_flags(0), + version(IPMI_1_5) + {} + + }; // end of struct IMPIAttributes + + private: + + /** NUT scanner initialization/finalization */ + struct InitFinal { + /** Initialization */ + InitFinal() { nutscan_init(); } + + /** Finalization */ + ~InitFinal() { nutscan_free(); } + + }; // end of struct InitFinal + + /** Initializer / finalizer */ + static InitFinal s_init_final; + + /** + * \brief Transform nut-scan provided devices into list of device info + * + * The nut-scan provided device list is destroyed. + * + * \param dev_list nut-scan provided device list + * + * \return Device info list + */ + static devices_t dev2list(nutscan_device_t * dev_list); + + /** Instantiation forbidden */ + NutScanner() {} + + public: + + /** + * \brief Scan for SNMP devices + * + * \param start_ip Address range left border + * \param stop_ip Address range right border + * \param us_timeout Device scan timeout + * \param attrs SNMP attributes + * + * \return Device list + */ + static devices_t devicesSNMP( + const std::string & start_ip, + const std::string & stop_ip, + useconds_t us_timeout, + const SNMPAttributes & attrs); + + /** + * \brief Scan for USB devices + * + * \return Device list + */ + inline static devices_t devicesUSB() { + // FIXME: Since NUT v2.8.2 nutscan_scan_usb accepts + // a `nutscan_usb_t * scanopts` to tweak what values + // it reports -- make use of it in this class. + // A nullptr value causes safe defaults to be used, + // as decided by the library. + nutscan_device_t * dev = ::nutscan_scan_usb(nullptr); + + return dev2list(dev); + } + + /** + * \brief Scan for XML/HTTP devices (broadcast) + * + * \param us_timeout Scan timeout + * + * \return Device list + */ + inline static devices_t devicesXMLHTTP(useconds_t us_timeout) { + nutscan_xml_t xml_sec; + nutscan_device_t * dev; + + ::memset(&xml_sec, 0, sizeof(xml_sec)); + /* Set the default values for XML HTTP (run_xml()) */ + xml_sec.port_http = 80; + xml_sec.port_udp = 4679; + xml_sec.usec_timeout = us_timeout; + xml_sec.peername = nullptr; + dev = ::nutscan_scan_xml_http_range(nullptr, nullptr, us_timeout, &xml_sec); + + return dev2list(dev); + } + + /** + * \brief Scan for NUT (pseudo-)devices + * + * \param start_ip Address range left border + * \param stop_ip Address range right border + * \param port Port + * \param us_timeout Device scan timeout + * + * \return Device list + */ + inline static devices_t devicesNUT( + const std::string & start_ip, + const std::string & stop_ip, + const std::string & port, + useconds_t us_timeout) + { + nutscan_device_t * dev = ::nutscan_scan_nut( + start_ip.c_str(), stop_ip.c_str(), port.c_str(), us_timeout); + + return dev2list(dev); + } + + /** + * \brief Scan for Avahi devices + * + * \param us_timeout Scan timeout + * + * \return Device list + */ + inline static devices_t devicesAvahi(useconds_t us_timeout) { + nutscan_device_t * dev = ::nutscan_scan_avahi(us_timeout); + + return dev2list(dev); + } + + /** + * \brief Scan for IPMI devices + * + * \return Device list + */ + static devices_t devicesIPMI( + const std::string & start_ip, + const std::string & stop_ip, + const IPMIAttributes & attrs); + + /** + * \brief Scan for Eaton serial devices + * + * \param ports List of serial ports + * + * \return Device list + */ + static devices_t devicesEatonSerial(const std::list & ports); + +}; // end of class NutScanner + + +NutScanner::InitFinal NutScanner::s_init_final; + + +NutScanner::Device::options_t NutScanner::Device::createOptions(nutscan_options_t * opt) { + options_t options; + + // Create options + for (; nullptr != opt; opt = opt->next) { + assert(nullptr != opt->option); + + options.insert( + options_t::value_type(opt->option, + nullptr != opt->value ? opt->value : "")); + } + + return options; +} + + +NutScanner::Device::Device(nutscan_device_t * dev): + type(nutscan_device_type_string(dev->type)), + driver(nullptr != dev->driver ? dev->driver : ""), + port(nullptr != dev->port ? dev->port : ""), + options(createOptions(dev->opt)) +{} + + +NutScanner::devices_t NutScanner::dev2list(nutscan_device_t * dev_list) { + devices_t list; + + nutscan_device_t * dev = dev_list; + + for (; dev != nullptr; dev = dev->next) { + // Skip devices of type NONE + // TBD: This happens with the serial scan on an invalid device + // Should be fixed in libnutscan I think + if (TYPE_NONE == dev->type) + continue; + + list.push_back(Device(dev)); + } + + ::nutscan_free_device(dev_list); + + return list; +} + + +NutScanner::devices_t NutScanner::devicesSNMP( + const std::string & start_ip, + const std::string & stop_ip, + useconds_t us_timeout, + const SNMPAttributes & attrs) +{ + nutscan_snmp_t snmp_attrs; + + ::memset(&snmp_attrs, 0, sizeof(snmp_attrs)); + + // TBD: const casting is necessery + // Shouldn't the nutscan_snmp_t items be constant? + + if (!attrs.community.empty()) + snmp_attrs.community = const_cast(attrs.community.c_str()); + + if (!attrs.sec_level.empty()) + snmp_attrs.secLevel = const_cast(attrs.sec_level.c_str()); + + if (!attrs.sec_name.empty()) + snmp_attrs.secName = const_cast(attrs.sec_name.c_str()); + + if (!attrs.auth_passwd.empty()) + snmp_attrs.authPassword = const_cast(attrs.auth_passwd.c_str()); + + if (!attrs.priv_passwd.empty()) + snmp_attrs.privPassword = const_cast(attrs.priv_passwd.c_str()); + + if (!attrs.auth_proto.empty()) + snmp_attrs.authProtocol = const_cast(attrs.auth_proto.c_str()); + + if (!attrs.priv_proto.empty()) + snmp_attrs.privProtocol = const_cast(attrs.priv_proto.c_str()); + + if (!attrs.peer_name.empty()) + snmp_attrs.peername = const_cast(attrs.peer_name.c_str()); + + nutscan_device_t * dev = ::nutscan_scan_snmp( + start_ip.c_str(), stop_ip.c_str(), us_timeout, &snmp_attrs); + + return dev2list(dev); +} + + +NutScanner::devices_t NutScanner::devicesIPMI( + const std::string & start_ip, + const std::string & stop_ip, + const IPMIAttributes & attrs) +{ + nutscan_ipmi_t ipmi_attrs; + + ::memset(&ipmi_attrs, 0, sizeof(ipmi_attrs)); + + // TBD: const casting is necessary + // Shouldn't the nutscan_ipmi_t C-string items be constant? + + if (!attrs.username.empty()) + ipmi_attrs.username = const_cast(attrs.username.c_str()); + + if (!attrs.passwd.empty()) + ipmi_attrs.password = const_cast(attrs.passwd.c_str()); + + ipmi_attrs.authentication_type = attrs.auth_type; + ipmi_attrs.cipher_suite_id = attrs.cipher_suite_id; + + if (!attrs.K_g_BMC_key.empty()) + ipmi_attrs.K_g_BMC_key = const_cast(attrs.K_g_BMC_key.c_str()); + + ipmi_attrs.privilege_level = attrs.priv_level; + ipmi_attrs.workaround_flags = attrs.wa_flags; + ipmi_attrs.ipmi_version = attrs.version; + + nutscan_device_t * dev = ::nutscan_scan_ipmi( + start_ip.c_str(), stop_ip.c_str(), &ipmi_attrs); + + return dev2list(dev); +} + + +NutScanner::devices_t NutScanner::devicesEatonSerial(const std::list & ports) { + std::string port_list; + + std::list::const_iterator port = ports.begin(); + + while (port != ports.end()) { + port_list += *port; + + ++port; + + if (port == ports.end()) + break; + + port_list += ' '; + } + + nutscan_device_t * dev = ::nutscan_scan_eaton_serial(port_list.c_str()); + + return dev2list(dev); +} + +#endif // defined WITH_NUTSCANNER + + +/** nutconf tool specific options */ +class NutConfOptions: public Options { + public: + + /** + * \brief Option mode (getter/setter) + * + * The mode is typically used for options that act as a value (info) + * getter when specified without arguments while if given arguments, + * it sets the value (info). + */ + typedef enum { + NOT_SPECIFIED, /**< Option not specified on command line */ + GETTER, /**< Option is a getter */ + SETTER, /**< Option is a setter */ + } mode_t; + + /** Listen address specification */ + typedef std::pair ListenAddrSpec; + + /** Device specification */ + struct DeviceSpec { + /** Device settings map */ + typedef std::map Map; + + std::string id; /**< Device identifier */ + Map settings; /**< Device settings */ + }; // end of struct DeviceSpec + + /** Notify flags specification */ + typedef std::pair > NotifyFlagsSpec; + + /** Notify flags specifications */ + typedef std::map NotifyFlagsSpecs; + + /** Notify messages specifications */ + typedef std::map NotifyMsgSpecs; + + /** User specification */ + struct UserSpec { + std::string name; /**< Username */ + std::string passwd; /**< Password */ + std::list actions; /**< Actions */ + std::list instcmds; /**< Instant commands */ + }; // end of struct UserSpec + + /** upsmon user specification */ + struct UpsmonUserSpec: public UserSpec { + std::string mode; /**< upsmon mode (master/slave) */ + }; // end of struct UpsmonUserSpec + + /** User specification list */ + typedef std::list UserSpecs; + + private: + + /** Unknown options */ + List m_unknown; + + /** Option specification errors */ + std::list m_errors; + + /** + * \brief Option mode getter (including arguments for setters) + * + * \param[in] opt Option + * \param[out] args Option arguments + * \param[in] order Option order (1st by default) + * + * \retval NOT_SPECIFIED if the option was not specified on the command line + * \retval GETTER if the option has no arguments + * \retval SETTER otherwise (option specified with arguments) + */ + mode_t optMode(const std::string & opt, Arguments & args, size_t order = 0) const; + + + /** + * \brief Option mode getter + * + * \param opt Option + * \param order Option order (1st by default) + * + * \retval NOT_SPECIFIED if the option was not specified on the command line + * \retval GETTER if the option has no arguments + * \retval SETTER otherwise (option specified with arguments) + */ + mode_t optMode(const std::string & opt, size_t order = 0) const { + Arguments args; + + return optMode(opt, args, order); + } + + public: + + /** Options are valid */ + bool valid; + + /** --autoconfigure */ + bool autoconfigure; + + /** --is-configured */ + bool is_configured; + + /** -- local argument */ + std::string local; + + /** --system */ + bool system; + + /** --get-mode */ + bool get_mode; + + /** --set-mode argument */ + std::string mode; + + /** --{add|set}-monitor arguments (all the monitors) */ + std::vector monitors; + + /** Set monitor options count */ + size_t set_monitor_cnt; + + /** Add monitor options count */ + size_t add_monitor_cnt; + + /** --{add|set}-listen arguments (all the addresses) */ + std::vector listen_addrs; + + /** Set listen address options count */ + size_t set_listen_cnt; + + /** Add listen address options count */ + size_t add_listen_cnt; + + /** Device specifications */ + std::vector devices; + + /** Set devices options count */ + size_t set_device_cnt; + + /** Add devices options count */ + size_t add_device_cnt; + + /** Notify flags specifications */ + NotifyFlagsSpecs notify_flags; + + /** Set notify flags options count */ + size_t set_notify_flags_cnt; + + /** Add notify flags options count */ + size_t add_notify_flags_cnt; + + /** Notify messages specifications */ + NotifyMsgSpecs notify_msgs; + + /** Set notify message options count */ + size_t set_notify_msg_cnt; + + /** Notify command */ + std::string notify_cmd; + + /** Shutdown command */ + std::string shutdown_cmd; + + /** Min. supplies */ + std::string min_supplies; + + /** Powerdown flag */ + std::string powerdown_flag; + + /** Users specifications */ + UserSpecs users; + + /** Set user options count */ + size_t set_user_cnt; + + /** Add user options count */ + size_t add_user_cnt; + + /** Verbosity level */ + unsigned int verbose; + + /** Scan NUT devices options count */ + size_t scan_nut_cnt; + + /** Scan USB devices */ + bool scan_usb; + + /** Scan xml_http_devices */ + bool scan_xml_http; + + /** Scan Avahi devices */ + bool scan_avahi; + + /** Scan IPMI devices */ + size_t scan_ipmi_cnt; + + /** Scan SNMP devices options count */ + size_t scan_snmp_cnt; + + /** Scan Eaton serial devices */ + bool scan_serial; + + /** Constructor */ + NutConfOptions(char * const argv[], int argc); + + /** Destructor */ + ~NutConfOptions(); + + /** + * \brief Report invalid options to STDERR + * + * BEWARE: throws an exception if options are valid. + * Check that using the \ref valid flag. + */ + void reportInvalid() const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif + ; + + /** + * \brief Get NUT mode + * + * \return NUT mode (if --mode option was specified with an argument) + */ + inline std::string getMode() const { + Arguments args; + + if (SETTER != optMode("mode", args)) + return ""; + + assert(!args.empty()); + + return args.front(); + } + + /** + * \brief Get monitor definition + * + * \param[out] ups UPS name + * \param[out] host_port Host (possibly including port specification) + * \param[out] pwr_val Power value + * \param[out] user Username + * \param[out] passwd User password + * \param[out] mode Monitor mode + * \param[in] which Monitor order (1st by default) + */ + void getMonitor( + std::string & ups, + std::string & host_port, + std::string & pwr_val, + std::string & user, + std::string & passwd, + std::string & mode, + size_t which = 0) const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::range_error) +#endif + ; + + private: + + /** + * \brief Check --mode argument validity + * + * \param mode Mode argument + * + * \retval true IFF the mode is set correctly + * \retval false otherwise + */ + static bool checkMode(const std::string & mode); + + /** + * \brief Add another user specification + * + * The method is responsible for parsing --{set|add}-user option arguments + * and storing the user specification in \ref users list. + * + * \param args User specification (raw) + */ + void addUser(const Arguments & args); + +}; // end of class NutConfOptions + + +NutConfOptions::mode_t NutConfOptions::optMode(const std::string & opt, Arguments & args, size_t order) const { + if (!getDouble(opt, args, order)) + return NOT_SPECIFIED; + + if (args.empty()) + return GETTER; + + return SETTER; +} + + +NutConfOptions::NutConfOptions(char * const argv[], int argc): + Options(argv, argc), + valid(true), + autoconfigure(false), + is_configured(false), + system(false), + get_mode(false), + set_monitor_cnt(0), + add_monitor_cnt(0), + set_listen_cnt(0), + add_listen_cnt(0), + set_device_cnt(0), + add_device_cnt(0), + set_notify_flags_cnt(0), + add_notify_flags_cnt(0), + set_notify_msg_cnt(0), + set_user_cnt(0), + add_user_cnt(0), + verbose(0), + scan_nut_cnt(0), + scan_usb(false), + scan_xml_http(false), + scan_avahi(false), + scan_ipmi_cnt(0), + scan_snmp_cnt(0), + scan_serial(false) +{ + static const std::string sDash("-"); + static const std::string dDash("--"); + + // Specify single-dashed options + List list = stringsSingle(); + + for (List::const_iterator opt = list.begin(); opt != list.end(); ++opt) { + // Known options + if ("v" == *opt) { + ++verbose; + } + + // Unknown option + else { + m_unknown.push_back(sDash + *opt); + } + } + + // Specify double-dashed options + list = stringsDouble(); + + for (List::const_iterator opt = list.begin(); opt != list.end(); ++opt) { + // Known options + if ("autoconfigure" == *opt) { + if (autoconfigure) + m_errors.push_back("--autoconfigure option specified more than once"); + else + autoconfigure = true; + } + else if ("is-configured" == *opt) { + if (is_configured) + m_errors.push_back("--is-configured option specified more than once"); + else + is_configured = true; + } + else if ("local" == *opt) { + Arguments args; + + if (!local.empty()) + m_errors.push_back("--local option specified more than once"); + + else if (NutConfOptions::SETTER != optMode("local", args)) + m_errors.push_back("--local option requires an argument"); + + else if (args.size() > 1) + m_errors.push_back("Only one directory may be specified with the --local option"); + + else + local = args.front(); + } + else if ("system" == *opt) { + if (system) + m_errors.push_back("--system option specified more than once"); + else + system = true; + } + else if ("get-mode" == *opt) { + if (get_mode) + m_errors.push_back("--get-mode option specified more than once"); + else + get_mode = true; + } + else if ("set-mode" == *opt) { + Arguments args; + + if (!mode.empty()) + m_errors.push_back("--set-mode option specified more than once"); + + else if (NutConfOptions::SETTER != optMode(*opt, args)) + m_errors.push_back("--set-mode option requires an argument"); + + else if (args.size() > 1) + m_errors.push_back("Only one argument allowed for the --set-mode option"); + + else if (args.size() == 1 && !checkMode(args.front())) + m_errors.push_back("Unknown NUT mode: \"" + args.front() + "\""); + + else + mode = args.front(); + } + else if ("set-monitor" == *opt || "add-monitor" == *opt) { + size_t * cnt = ('s' == (*opt)[0] ? &set_monitor_cnt : &add_monitor_cnt); + + Arguments args; + + if (NutConfOptions::SETTER != optMode(*opt, args, *cnt)) + m_errors.push_back("--" + *opt + " option requires arguments"); + + else if (args.size() != 6) + m_errors.push_back("--" + *opt + " option requires exactly 6 arguments"); + + else + for (Arguments::const_iterator arg = args.begin(); arg != args.end(); ++arg) + monitors.push_back(*arg); + + ++*cnt; + } + else if ("set-listen" == *opt || "add-listen" == *opt) { + size_t * cnt = ('s' == (*opt)[0] ? &set_listen_cnt : &add_listen_cnt); + + Arguments args; + + if (NutConfOptions::SETTER != optMode(*opt, args, *cnt)) + m_errors.push_back("--" + *opt + " option requires arguments"); + + else if (args.size() < 1 || args.size() > 2) + m_errors.push_back("--" + *opt + " option requires 1 or 2 arguments"); + + else { + ListenAddrSpec addr_port(args.front(), args.size() > 1 ? args.back() : ""); + + listen_addrs.push_back(addr_port); + } + + ++*cnt; + } + else if ("set-device" == *opt || "add-device" == *opt) { + size_t * cnt = ('s' == (*opt)[0] ? &set_device_cnt : &add_device_cnt); + + Arguments args; + + if (NutConfOptions::SETTER != optMode(*opt, args, *cnt)) + m_errors.push_back("--" + *opt + " option requires arguments"); + + else if (args.size() < 3) + m_errors.push_back("--" + *opt + " option requires at least 3 arguments"); + + else { + DeviceSpec dev; + + Arguments::const_iterator arg = args.begin(); + + assert(args.size() >= 3); + + dev.id = *arg++; + + dev.settings["driver"] = *arg++; + dev.settings["port"] = *arg++; + + for (; arg != args.end(); ++arg) { + size_t eq_pos = (*arg).find('='); + + if (std::string::npos == eq_pos) + m_errors.push_back("--" + *opt + + " option extra argument '" + + *arg + "' is illegal"); + + else + dev.settings[(*arg).substr(0, eq_pos)] = + (*arg).substr(eq_pos + 1); + } + + devices.push_back(dev); + } + + ++*cnt; + } + else if ("set-notifyflags" == *opt || "add-notifyflags" == *opt) { + bool set = 's' == (*opt)[0]; + size_t * cnt = (set ? &set_notify_flags_cnt : &add_notify_flags_cnt); + + Arguments args; + + if (NutConfOptions::SETTER != optMode(*opt, args, *cnt)) + m_errors.push_back("--" + *opt + " option requires arguments"); + + else if (args.size() < 2) + m_errors.push_back("--" + *opt + " option requires at least 2 arguments"); + + else { + Arguments::const_iterator arg = args.begin(); + + const std::string & type = *arg++; + + NotifyFlagsSpecs::iterator flags = notify_flags.lower_bound(type); + + if (flags != notify_flags.end() && flags->first == type) + m_errors.push_back( + "--" + *opt + " option: conflicting specifications " + + "for notification type " + type); + + else { + flags = notify_flags.insert(flags, + NotifyFlagsSpecs::value_type( + type, + NotifyFlagsSpec(set, std::list()))); + + for (; arg != args.end(); ++arg) + flags->second.second.push_back(*arg); + } + } + + ++*cnt; + } + else if ("set-notifymsg" == *opt) { + Arguments args; + + if (NutConfOptions::SETTER != optMode(*opt, args, set_notify_msg_cnt)) + m_errors.push_back("--" + *opt + " option requires arguments"); + + else if (args.size() != 2) { + m_errors.push_back("--" + *opt + " option requires 2 arguments"); + m_errors.push_back(" (perhaps you need to quote the message?)"); + } + + else { + notify_msgs[args.front()] = args.back(); + } + + ++set_notify_msg_cnt; + } + else if ("set-notifycmd" == *opt) { + Arguments args; + + if (!notify_cmd.empty()) + m_errors.push_back("--set-notifycmd option specified more than once"); + + else if (NutConfOptions::SETTER != optMode("set-notifycmd", args)) + m_errors.push_back("--set-notifycmd option requires an argument"); + + else if (args.size() > 1) { + m_errors.push_back("Too many arguments for the --set-notifycmd option"); + m_errors.push_back(" (perhaps you need to quote the command?)"); + } + + else + notify_cmd = args.front(); + } + else if ("set-shutdowncmd" == *opt) { + Arguments args; + + if (!shutdown_cmd.empty()) + m_errors.push_back("--set-shutdowncmd option specified more than once"); + + else if (NutConfOptions::SETTER != optMode("set-shutdowncmd", args)) + m_errors.push_back("--set-shutdowncmd option requires an argument"); + + else if (args.size() > 1) { + m_errors.push_back("Too many arguments for the --set-shutdowncmd option"); + m_errors.push_back(" (perhaps you need to quote the command?)"); + } + + else + shutdown_cmd = args.front(); + } + else if ("set-minsupplies" == *opt) { + Arguments args; + + if (!min_supplies.empty()) + m_errors.push_back("--set-minsupplies option specified more than once"); + + else if (NutConfOptions::SETTER != optMode("set-minsupplies", args)) + m_errors.push_back("--set-minsupplies option requires an argument"); + + else if (args.size() > 1) { + m_errors.push_back("Too many arguments for the --set-minsupplies option"); + } + + else + min_supplies = args.front(); + } + else if ("set-powerdownflag" == *opt) { + Arguments args; + + if (!powerdown_flag.empty()) + m_errors.push_back("--set-powerdownflag option specified more than once"); + + else if (NutConfOptions::SETTER != optMode("set-powerdownflag", args)) + m_errors.push_back("--set-powerdownflag option requires an argument"); + + else if (args.size() > 1) { + m_errors.push_back("Too many arguments for the --set-powerdownflag option"); + } + + else + powerdown_flag = args.front(); + } + else if ("set-user" == *opt || "add-user" == *opt) { + size_t * cnt = ('s' == (*opt)[0] ? &set_user_cnt : &add_user_cnt); + + Arguments args; + + if (NutConfOptions::SETTER != optMode(*opt, args, *cnt)) + m_errors.push_back("--" + *opt + " option requires arguments"); + + else + addUser(args); + + ++*cnt; + } + else if ("verbose" == *opt) { + ++verbose; + } + +#if (defined WITH_NUTSCANNER) + +#if (defined WITH_USB) + else if ("scan-usb" == *opt) { + if (scan_usb) + m_errors.push_back("--scan-usb option specified more than once"); + else + scan_usb = true; + } +#endif // defined WITH_USB + else if ("scan-nut" == *opt) { + Arguments args; + + getDouble(*opt, args, scan_nut_cnt); + + if (args.size() < 3) + m_errors.push_back("--scan-nut option requires at least 3 arguments"); + + else if (args.size() > 4) + m_errors.push_back("--scan-nut option requires at most 4 arguments"); + + ++scan_nut_cnt; + } +#if (defined WITH_NEON) + else if ("scan-xml-http" == *opt) { + if (scan_xml_http) + m_errors.push_back("--scan-xml-http option specified more than once"); + else { + Arguments args; + + getDouble(*opt, args); + + if (args.size() > 1) + m_errors.push_back("--scan-xml-http option accepts only one argument"); + + scan_xml_http = true; + } + } +#endif // defined WITH_NEON +#if (defined WITH_AVAHI) + else if ("scan-avahi" == *opt) { + if (scan_avahi) + m_errors.push_back("--scan-avahi option specified more than once"); + else { + Arguments args; + + getDouble(*opt, args); + + if (args.size() > 1) + m_errors.push_back("--scan-avahi option accepts only one argument"); + + scan_avahi = true; + } + } +#endif // defined WITH_AVAHI +#if (defined WITH_IPMI) + else if ("scan-ipmi" == *opt) { + Arguments args; + + getDouble(*opt, args, scan_ipmi_cnt); + + if (args.size() < 2) + m_errors.push_back("--scan-ipmi option requires at least 2 arguments"); + + ++scan_ipmi_cnt; + } +#endif // defined WITH_IPMI +#if (defined WITH_SNMP) + else if ("scan-snmp" == *opt) { + Arguments args; + + getDouble(*opt, args, scan_snmp_cnt); + + if (args.size() < 2) + m_errors.push_back("--scan-snmp option requires at least 2 arguments"); + + ++scan_snmp_cnt; + } +#endif // defined WITH_SNMP + else if ("scan-serial" == *opt) { + if (scan_serial) + m_errors.push_back("--scan-serial option specified more than once"); + else + scan_serial = true; + } + +#endif // defined WITH_NUTSCANNER) + + // Unknown option + else { + m_unknown.push_back(dDash + *opt); + } + } + + // Options are valid IFF we know all of them + // and there are no direct binary arguments + valid = m_unknown.empty() && m_errors.empty() && get().empty(); + + // --set-monitor and --add-monitor are mutually exclusive + if (set_monitor_cnt > 0 && add_monitor_cnt > 0) { + m_errors.push_back("--set-monitor and --add-monitor options can't both be specified"); + + valid = false; + } + + // --set-listen and --add-listen are mutually exclusive + if (set_listen_cnt > 0 && add_listen_cnt > 0) { + m_errors.push_back("--set-listen and --add-listen options can't both be specified"); + + valid = false; + } + + // --set-device and --add-device are mutually exclusive + if (set_device_cnt > 0 && add_device_cnt > 0) { + m_errors.push_back("--set-device and --add-device options can't both be specified"); + + valid = false; + } + + // --set-user and --add-user are mutually exclusive + if (set_user_cnt > 0 && add_user_cnt > 0) { + m_errors.push_back("--set-user and --add-user options can't both be specified"); + + valid = false; + } +} + + +NutConfOptions::~NutConfOptions() { + UserSpecs::iterator user = users.begin(); + + for (; user != users.end(); ++user) { + delete *user; + *user = nullptr; + } +} + + +void NutConfOptions::reportInvalid() const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::logic_error) +#endif +{ + if (valid) + throw std::logic_error("No invalid options to report"); + + List::const_iterator unknown_opt = m_unknown.begin(); + + for (; unknown_opt != m_unknown.end(); ++unknown_opt) { + std::cerr << "Unknown option: " << *unknown_opt << std::endl; + } + + std::list::const_iterator error = m_errors.begin(); + + for (; error != m_errors.end(); ++error) { + std::cerr << "Option error: " << *error << std::endl; + } + + // No direct arguments expected + const Arguments & args = get(); + + Arguments::const_iterator arg = args.begin(); + + for (; arg != args.end(); ++arg) { + std::cerr << "Unexpected argument: " << *arg << std::endl; + } +} + + +bool NutConfOptions::checkMode(const std::string & mode) { + if ("standalone" == mode) return true; + if ("netserver" == mode) return true; + if ("netclient" == mode) return true; + if ("controlled" == mode) return true; + if ("manual" == mode) return true; + if ("none" == mode) return true; + + return false; +} + + +/** + * \brief Automatically destroyed dynamic object pointer + * + * The template class is useful in situation where you need to + * create a dynamic object, process its attributes and automatically + * destroy it in case of error simply by leaving the scope + * (i.e. not having to worry about calling \c delete by hand). + */ +template +class autodelete_ptr { + private: + + T * m_impl; + + /** Cleanup */ + inline void cleanup() { + if (nullptr != m_impl) + delete m_impl; + + m_impl = nullptr; + } + + public: + + /** Constructor (unset) */ + autodelete_ptr(): m_impl(nullptr) {} + + /** Constructor */ + autodelete_ptr(T * ptr): m_impl(ptr) {} + + /** Setter */ + inline autodelete_ptr & operator = (T * ptr) { + cleanup(); + + m_impl = ptr; + + return *this; + } + + /** Pointer accessor */ + inline operator T * () const { return m_impl; } + + /** Pointer accessor */ + inline T * operator -> () const { return m_impl; } + + /** Pointer overtake (invalidates the object) */ + inline T * give() { + T * ptr = m_impl; + + m_impl = nullptr; + + return ptr; + } + + /** Destructor */ + ~autodelete_ptr() { + cleanup(); + } + + private: + + /** Copying is forbidden */ + autodelete_ptr(const autodelete_ptr & orig) { + NUT_UNUSED_VARIABLE(orig); + } + + /** Assignment is forbidden */ + autodelete_ptr & operator = (const autodelete_ptr & orig) { + NUT_UNUSED_VARIABLE(orig); + } + +}; // end of template class autodelete_ptr + + +void NutConfOptions::addUser(const Options::Arguments & args) { + assert(args.size() > 0); + + Arguments::const_iterator arg_iter = args.begin(); + + // Create new user specification + autodelete_ptr user; + + const std::string & name = *arg_iter; + + // upsmon user (apparently) + // Note that we use pragmatic do ... while (0) loop to enable break + do if (name.size() >= 6 && name.substr(0, 6) == "upsmon") { + if (6 == name.size()) { + m_errors.push_back("upsmon user specification requires monitor mode"); + + return; + } + + // Fall-through to ordinary user specification + if ('=' != name[6]) + break; + + UpsmonUserSpec * upsmon_user = new UpsmonUserSpec; + + upsmon_user->name = "upsmon"; + upsmon_user->mode = name.substr(7); + + user = upsmon_user; + + } while (0); // end of pragmatic do ... while (0) loop + + // Ordinary user + if (nullptr == user) { + user = new UserSpec; + + user->name = name; + } + + assert(nullptr != user); + + // Set user attributes + bool errors = false; + + for (++arg_iter; arg_iter != args.end(); ++arg_iter) { + const std::string & arg = *arg_iter; + + size_t eq_pos = arg.find('='); + + if (std::string::npos == eq_pos) { + m_errors.push_back("Illegal user attribute specification: \"" + arg + '"'); + + errors = true; + + continue; + } + + const std::string attr = arg.substr(0, eq_pos); + const std::string val = arg.substr(eq_pos + 1); + + if ("password" == attr) + user->passwd = val; + + else if ("actions" == attr) + user->actions.push_back(val); + + else if ("instcmds" == attr) + user->instcmds.push_back(val); + + else { + m_errors.push_back("Unknown user attribute: \"" + attr + '"'); + + errors = true; + } + } + + if (errors) + return; + + // Store user specification + users.push_back(user.give()); +} + + +void NutConfOptions::getMonitor( + std::string & ups, + std::string & host_port, + std::string & pwr_val, + std::string & user, + std::string & passwd, + std::string & mode_arg, + size_t which) const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::range_error) +#endif +{ + if (which >= monitors.size() / 6) + throw std::range_error("INTERNAL ERROR: monitors index overflow"); + + size_t base_idx = 6 * which; + + assert(monitors.size() >= base_idx + 6); + + ups = monitors[base_idx]; + host_port = monitors[base_idx + 1]; + pwr_val = monitors[base_idx + 2]; + user = monitors[base_idx + 3]; + passwd = monitors[base_idx + 4]; + mode_arg = monitors[base_idx + 5]; +} + + +/** + * \brief Sources configuration object from file (if exists) + * + * If the file doesn't exist, the conf. object is unchanged + * (and the result is indicated by the return value). + * If the file exists, but can't be parsed, an error is reported + * and the execution is terminated. + * + * \param config Configuration object + * \param file_name File name + * + * \retval true if the configuration file was sourced + * \retval false if the file doesn't exist + */ +static bool source(nut::Serialisable * config, const std::string & file_name) { + nut::NutFile file(file_name); + + if (!file.exists()) + return false; + + file.openx(); + + bool parsed_ok = config->parseFrom(file); + + file.closex(); + + if (parsed_ok) + return true; + + std::cerr << "Error: Failed to parse " << file_name << std::endl; + + ::exit(1); +} + + +/** + * \brief Store configuration object to file + * + * If the file exists, it's rewritten. + * + * \param config Configuration object + * \param file_name File name + */ +static void store(nut::Serialisable * config, const std::string & file_name) { + nut::NutFile file(file_name, nut::NutFile::WRITE_ONLY); + + bool written_ok = config->writeTo(file); + + file.closex(); + + if (written_ok) + return; + + std::cerr << "Error: Failed to write " << file_name << std::endl; + + ::exit(1); +} + + +/** + * \brief Check whether NUT was configured + * + * \param etc Configuration directory + * + * \retval true IFF nut.conf exists and MODE != none + * \retval false otherwise + */ +static bool isConfigured(const std::string & etc) { + nut::NutFile nut_conf_file(etc + "/nut.conf"); + + if (!nut_conf_file.exists()) + return false; + + nut_conf_file.openx(); + + nut::NutConfiguration nut_conf; + + nut_conf.parseFrom(nut_conf_file); + + return + nut::NutConfiguration::MODE_UNKNOWN != nut_conf.mode && + nut::NutConfiguration::MODE_NONE != nut_conf.mode; +} + + +/** + * \brief Transform monitor specification + * + * Transform monitor specification from cmd. line to monitor configuration. + * + * \param i Monitor index + * \param options nutconf options + * + * \return Monitor configuration + */ +static nut::UpsmonConfiguration::Monitor monitor( + size_t i, + const NutConfOptions & options) +{ + nut::UpsmonConfiguration::Monitor monitor; + + std::string host_port, pwr_val, mode; + + options.getMonitor( + monitor.upsname, host_port, pwr_val, + monitor.username, monitor.password, mode, + i); + + // Parse host[:port] + unsigned short port = 0; + + size_t colon_idx = host_port.rfind(':'); + + if (std::string::npos != colon_idx) { + std::stringstream ss(host_port.substr(colon_idx + 1)); + + if ((ss >> port).fail()) { + std::cerr + << "Error: failed to parse host specification \"" + << host_port << '"' << std::endl; + + ::exit(1); + } + } + + // Parse power value + unsigned int power_value; + + std::stringstream ss(pwr_val); + + if ((ss >> power_value).fail()) { + std::cerr + << "Error: failed to parse power value \"" + << pwr_val << '"' << std::endl; + + ::exit(1); + } + + monitor.hostname = host_port.substr(0, colon_idx); + monitor.port = port; + monitor.powerValue = power_value; + monitor.isMaster = "master" == mode; + + return monitor; +} + + +/** + * \brief NUT mode getter + * + * \param etc Configuration directory + * + * \return NUT mode (as string) + */ +static std::string getMode(const std::string & etc) { + std::string nut_conf_file(etc + "/nut.conf"); + + nut::NutConfiguration nut_conf; + + // Source previous configuration + source(&nut_conf, nut_conf_file); + + nut::NutConfiguration::NutMode mode = nut_conf.mode; + + switch (mode) { + case nut::NutConfiguration::MODE_UNKNOWN: return "unknown"; + case nut::NutConfiguration::MODE_NONE: return "none"; + case nut::NutConfiguration::MODE_STANDALONE: return "standalone"; + case nut::NutConfiguration::MODE_NETSERVER: return "netserver"; + case nut::NutConfiguration::MODE_NETCLIENT: return "netclient"; + case nut::NutConfiguration::MODE_CONTROLLED: return "controlled"; + case nut::NutConfiguration::MODE_MANUAL: return "manual"; + } + + std::stringstream e; + + e << "INTERNAL ERROR: Unknown NUT mode: " << mode; + + throw std::logic_error(e.str()); +} + + +/** + * \brief NUT mode setter + * + * \param mode Mode + * \param etc Configuration directory + */ +static void setMode(const std::string & mode, const std::string & etc) { + std::string nut_conf_file(etc + "/nut.conf"); + + nut::NutConfiguration nut_conf; + + // Source previous configuration (if any) + source(&nut_conf, nut_conf_file); + + // Set mode + nut_conf.mode = nut::NutConfiguration::NutModeFromString(mode); + + // Store configuration + store(&nut_conf, nut_conf_file); +} + + +/** + * \brief Set monitors in upsmon.conf + * + * \param monitors Monitor list + * \param etc Configuration directory + * \param keep_ex Keep existing entries (discard by default) + */ +static void setMonitors( + const std::list & monitors, + const std::string & etc, bool keep_ex = false) +{ + std::string upsmon_conf_file(etc + "/upsmon.conf"); + + nut::UpsmonConfiguration upsmon_conf; + + // Source previous configuration (if any) + source(&upsmon_conf, upsmon_conf_file); + + // Remove existing monitors (unless we want to keep them) + if (!keep_ex) + upsmon_conf.monitors.clear(); + + // Add monitors to the current ones (if any) + std::list::const_iterator + monitor = monitors.begin(); + + for (; monitor != monitors.end(); ++monitor) + upsmon_conf.monitors.push_back(*monitor); + + // Store configuration + store(&upsmon_conf, upsmon_conf_file); +} + + +/** + * \brief Transform listen address specification + * + * Transform listen address specification from cmd. line to listen address configuration. + * + * \param i Listen address index + * \param options nutconf options + * + * \return Listen address configuration + */ +static nut::UpsdConfiguration::Listen listenAddr( + size_t i, + const NutConfOptions & options) +{ + nut::UpsdConfiguration::Listen listen_addr; + + const NutConfOptions::ListenAddrSpec & addr_spec = options.listen_addrs[i]; + + listen_addr.address = addr_spec.first; + + // Parse port + if (!addr_spec.second.empty()) { + unsigned short port = 0; + + std::stringstream ss(addr_spec.second); + + if ((ss >> port).fail()) { + std::cerr + << "Error: failed to parse port specification \"" + << addr_spec.second << '"' << std::endl; + + ::exit(1); + } + + listen_addr.port = port; + } + + return listen_addr; +} + + +/** + * \brief Set listen addresses in upsd.conf + * + * \param listen_addrs Address list + * \param etc Configuration directory + * \param keep_ex Keep existing entries (discard by default) + */ +static void setListenAddrs( + const std::list & listen_addrs, + const std::string & etc, bool keep_ex = false) +{ + std::string upsd_conf_file(etc + "/upsd.conf"); + + nut::UpsdConfiguration upsd_conf; + + // Source previous configuration (if any) + source(&upsd_conf, upsd_conf_file); + + // Remove existing listen addresses (unless we want to keep them) + if (!keep_ex) + upsd_conf.listens.clear(); + + // Add listen addresses to the current ones (if any) + std::list::const_iterator + listen = listen_addrs.begin(); + + for (; listen != listen_addrs.end(); ++listen) + upsd_conf.listens.push_back(*listen); + + // Store configuration + store(&upsd_conf, upsd_conf_file); +} + + +/** + * \brief Set devices in ups.conf + * + * \param devices Device list + * \param etc Configuration directory + * \param keep_ex Keep existing entries (discard by default) + */ +static void setDevices( + const std::vector & devices, + const std::string & etc, bool keep_ex = false) +{ + std::string ups_conf_file(etc + "/ups.conf"); + + nut::UpsConfiguration ups_conf; + + // Source previous configuration (if any) + source(&ups_conf, ups_conf_file); + + // Remove existing devices (unless we want to keep them) + if (!keep_ex) { + nut::UpsConfiguration::SectionMap::iterator + ups = ups_conf.sections.begin(); + + for (; ups != ups_conf.sections.end(); ++ups) { + // Keep global section + if (ups->first.empty()) + continue; + + ups_conf.sections.erase(ups); + } + } + + // Add devices to the current ones (if any) + std::vector::const_iterator + dev = devices.begin(); + + for (; dev != devices.end(); ++dev) { + const std::string & id = (*dev).id; + + NutConfOptions::DeviceSpec::Map::const_iterator + setting = (*dev).settings.begin(); + + for (; setting != (*dev).settings.end(); ++setting) + ups_conf.setKey(id, setting->first, setting->second); + } + + // Store configuration + store(&ups_conf, ups_conf_file); +} + + +/** + * \brief Set notify flags in upsmon.conf + * + * \param flags Notify flags specifications + * \param etc Configuration directory + */ +static void setNotifyFlags( + const NutConfOptions::NotifyFlagsSpecs & flags, + const std::string & etc) +{ + std::string upsmon_conf_file(etc + "/upsmon.conf"); + + nut::UpsmonConfiguration upsmon_conf; + + // Source previous configuration (if any) + source(&upsmon_conf, upsmon_conf_file); + + NutConfOptions::NotifyFlagsSpecs::const_iterator specs = flags.begin(); + + for (; specs != flags.end(); ++specs) { + // Resolve notification type + nut::UpsmonConfiguration::NotifyType type = + nut::UpsmonConfiguration::NotifyTypeFromString(specs->first); + + if (nut::UpsmonConfiguration::NOTIFY_TYPE_MAX == type) { + std::cerr + << "Error: failed to parse notification type specification \"" + << specs->first << '"' << std::endl; + + ::exit(1); + } + + nut::Settable & sum = + upsmon_conf.notifyFlags[type]; + + // Clear current flags (unless we want to keep them) + if (specs->second.first || !sum.set()) + sum = nut::UpsmonConfiguration::NOTIFY_IGNORE; + + // Assemble flags + std::list::const_iterator spec = specs->second.second.begin(); + + for (; spec != specs->second.second.end(); ++spec) { + nut::UpsmonConfiguration::NotifyFlag flag = + nut::UpsmonConfiguration::NotifyFlagFromString(*spec); + + sum |= static_cast(flag); + } + } + + // Store configuration + store(&upsmon_conf, upsmon_conf_file); +} + + +/** + * \brief Set notify messages in upsmon.conf + * + * \param msgs Notify messages specifications + * \param etc Configuration directory + */ +static void setNotifyMsgs( + const NutConfOptions::NotifyMsgSpecs & msgs, + const std::string & etc) +{ + std::string upsmon_conf_file(etc + "/upsmon.conf"); + + nut::UpsmonConfiguration upsmon_conf; + + // Source previous configuration (if any) + source(&upsmon_conf, upsmon_conf_file); + + NutConfOptions::NotifyMsgSpecs::const_iterator spec = msgs.begin(); + + for (; spec != msgs.end(); ++spec) { + // Resolve notification type + nut::UpsmonConfiguration::NotifyType type = + nut::UpsmonConfiguration::NotifyTypeFromString(spec->first); + + if (nut::UpsmonConfiguration::NOTIFY_TYPE_MAX == type) { + std::cerr + << "Error: failed to parse notification type specification \"" + << spec->first << '"' << std::endl; + + ::exit(1); + } + + // Set message + upsmon_conf.notifyMessages[type] = spec->second; + } + + // Store configuration + store(&upsmon_conf, upsmon_conf_file); +} + + +/** + * \brief Set notify command in upsmon.conf + * + * \param cmd Notify command + * \param etc Configuration directory + */ +static void setNotifyCmd(const std::string & cmd, const std::string & etc) +{ + std::string upsmon_conf_file(etc + "/upsmon.conf"); + + nut::UpsmonConfiguration upsmon_conf; + + // Source previous configuration (if any) + source(&upsmon_conf, upsmon_conf_file); + + upsmon_conf.notifyCmd = cmd; + + // Store configuration + store(&upsmon_conf, upsmon_conf_file); +} + + +/** + * \brief Set shutdown command in upsmon.conf + * + * \param cmd Shutdown command + * \param etc Configuration directory + */ +static void setShutdownCmd(const std::string & cmd, const std::string & etc) +{ + std::string upsmon_conf_file(etc + "/upsmon.conf"); + + nut::UpsmonConfiguration upsmon_conf; + + // Source previous configuration (if any) + source(&upsmon_conf, upsmon_conf_file); + + upsmon_conf.shutdownCmd = cmd; + + // Store configuration + store(&upsmon_conf, upsmon_conf_file); +} + + +/** + * \brief Set minimum of power supplies in upsmon.conf + * + * \param min_supplies Minimum of power supplies + * \param etc Configuration directory + */ +static void setMinSupplies(const std::string & min_supplies, const std::string & etc) { + std::string upsmon_conf_file(etc + "/upsmon.conf"); + + nut::UpsmonConfiguration upsmon_conf; + + // Source previous configuration (if any) + source(&upsmon_conf, upsmon_conf_file); + + unsigned int min; + + std::stringstream ss(min_supplies); + + if ((ss >> min).fail()) { + std::cerr + << "Error: invalid min. power supplies specification: \"" + << min_supplies << '"' << std::endl; + + ::exit(1); + } + + upsmon_conf.minSupplies = min; + + // Store configuration + store(&upsmon_conf, upsmon_conf_file); +} + + +/** + * \brief Set powerdown flag file in upsmon.conf + * + * \param powerdown_flag Powerdown flag file + * \param etc Configuration directory + */ +static void setPowerdownFlag(const std::string & powerdown_flag, const std::string & etc) { + std::string upsmon_conf_file(etc + "/upsmon.conf"); + + nut::UpsmonConfiguration upsmon_conf; + + // Source previous configuration (if any) + source(&upsmon_conf, upsmon_conf_file); + + upsmon_conf.powerDownFlag = powerdown_flag; + + // Store configuration + store(&upsmon_conf, upsmon_conf_file); +} + + +/** + * \brief Set users in upsd.users + * + * \param users User list + * \param etc Configuration directory + * \param keep_ex Keep existing entries (discard by default) + */ +static void setUsers( + const NutConfOptions::UserSpecs & users, + const std::string & etc, bool keep_ex = false) +{ + std::string upsd_users_file(etc + "/upsd.users"); + + nut::UpsdUsersConfiguration upsd_users; + + // Source previous configuration (if any) + source(&upsd_users, upsd_users_file); + + // Remove existing users (unless we want to keep them) + if (!keep_ex) { + nut::UpsdUsersConfiguration::SectionMap::iterator + user = upsd_users.sections.begin(); + + for (; user != upsd_users.sections.end(); ++user) { + // Keep global section + if (user->first.empty()) + continue; + + upsd_users.sections.erase(user); + } + } + + // Set/add users and/or their attributes + NutConfOptions::UserSpecs::const_iterator + user_iter = users.begin(); + + for (; user_iter != users.end(); ++user_iter) { + const NutConfOptions::UserSpec * user = *user_iter; + + const std::string & username = user->name; + + // Set password + if (!user->passwd.empty()) + upsd_users.setPassword(username, user->passwd); + + // Set actions + std::list::const_iterator action = user->actions.begin(); + + for (; action != user->actions.end(); ++action) + upsd_users.addActions(username, nut::ConfigParamList(1, *action)); + + // Set instant commands + std::list::const_iterator cmd = user->instcmds.begin(); + + for (; cmd != user->instcmds.end(); ++cmd) + upsd_users.addInstantCommands(username, nut::ConfigParamList(1, *cmd)); + + // upsmon user-specific settings + if ("upsmon" == username) { + const NutConfOptions::UpsmonUserSpec * upsmon_user = + static_cast(user); + + // Set upsmon mode + nut::UpsdUsersConfiguration::upsmon_mode_t mode = + nut::UpsdUsersConfiguration::UPSMON_UNDEF; + + if ("master" == upsmon_user->mode) + mode = nut::UpsdUsersConfiguration::UPSMON_MASTER; + + else if ("slave" == upsmon_user->mode) + mode = nut::UpsdUsersConfiguration::UPSMON_SLAVE; + + else { + std::cerr + << "Error: Invalid upsmon mode specification: \"" + << upsmon_user->mode << '"' << std::endl; + + ::exit(1); + } + + upsd_users.setUpsmonMode(mode); + } + } + + // Store configuration + store(&upsd_users, upsd_users_file); +} + + +#if (defined WITH_NUTSCANNER) + +/** + * \brief Print devices info + * + * \param devices Device list + * \param verbose Verbosity level + */ +static void printDevicesInfo(const NutScanner::devices_t & devices, unsigned int verbose = 0) { + NutScanner::devices_t::const_iterator dev_iter = devices.begin(); + + nut::GenericConfiguration devices_conf; + + unsigned int dev_no = 1; + + for (; dev_iter != devices.end(); ++dev_iter, ++dev_no) { + const NutScanner::Device & dev = *dev_iter; + + // Print just plain list + if (verbose == 0) + std::cout + << dev.type << ' ' + << dev.driver << ' ' + << dev.port << std::endl; + + // Assemble full info + else { + std::stringstream name; + + name << "device_type_" << dev.type << "_no_"; + + name.width(3); + name.fill('0'); + + name << dev_no; + + nut::GenericConfigSection & device_conf = devices_conf[name.str()]; + + device_conf.name = name.str(); + + // Set driver + nut::GenericConfigSectionEntry & driver = device_conf["driver"]; + + driver.name = "driver"; + driver.values.push_back(dev.driver); + + // Set port + nut::GenericConfigSectionEntry & port = device_conf["port"]; + + port.name = "port"; + port.values.push_back(dev.port); + + // Set options + NutScanner::Device::options_t::const_iterator + opt = dev.options.begin(); + + for (; opt != dev.options.end(); ++opt) { + nut::GenericConfigSectionEntry & option = device_conf[opt->first]; + + option.name = opt->first; + option.values.push_back(opt->second); + } + } + } + + // Print full info + if (0 != verbose) { + nut::NutMemory info; + + devices_conf.writeTo(info); + + std::string info_str; + + assert(nut::NutStream::NUTS_OK == info.getString(info_str)); + + std::cout << info_str; + } +} + + +/** + * \brief Scan for SNMP devices + * + * \param options Options + */ +static void scanSNMPdevices(const NutConfOptions & options) { + for (size_t i = 0; ; ++i) { + NutConfOptions::Arguments args; + + bool ok = options.getDouble("scan-snmp", args, i); + + if (!ok) break; + + // Sanity checks + assert(args.size() >= 2); + + NutConfOptions::Arguments::const_iterator arg = args.begin(); + + const std::string & start_ip = *arg++; + const std::string & stop_ip = *arg++; + + // TBD: where should we get the default? + useconds_t us_timeout = 1000000; + + NutScanner::SNMPAttributes attrs; + + // Parse = pairs + bool errors = false; + + for (; arg != args.end(); ++arg) { + size_t eq_pos = (*arg).find('='); + + if (std::string::npos == eq_pos) { + std::cerr + << "Error: Invalid SNMP attribute specification: \"" + << *arg << '"' << std::endl; + + errors = true; + + continue; + } + + std::string attr = (*arg).substr(0, eq_pos); + std::string val = (*arg).substr(eq_pos + 1); + + if ("timeout" == attr) { + std::stringstream ss(val); + + if ((ss >> us_timeout).fail()) { + std::cerr + << "Error: Invalid SNMP timeout specification: \"" + << val << '"' << std::endl; + + errors = true; + + continue; + } + } + else if ("community" == attr) { + attrs.community = val; + } + else if ("sec-level" == attr) { + attrs.sec_level = val; + } + else if ("sec-name" == attr) { + attrs.sec_name = val; + } + else if ("auth-password" == attr) { + attrs.auth_passwd = val; + } + else if ("priv-password" == attr) { + attrs.priv_passwd = val; + } + else if ("auth-protocol" == attr) { + attrs.auth_proto = val; + } + else if ("priv-protocol" == attr) { + attrs.priv_proto = val; + } + else if ("peer-name" == attr) { + attrs.peer_name = val; + } + + else { + std::cerr + << "Error: Unknown SNMP attribute: \"" + << attr << '"' << std::endl; + + errors = true; + } + } + + if (errors) continue; + + NutScanner::devices_t devices = NutScanner::devicesSNMP( + start_ip, stop_ip, us_timeout, attrs); + + printDevicesInfo(devices, options.verbose); + } +} + + +/** + * \brief Scan for USB devices + * + * \param options Options + */ +static void scanUSBdevices(const NutConfOptions & options) { + NutScanner::devices_t devices = NutScanner::devicesUSB(); + + printDevicesInfo(devices, options.verbose); +} + + +/** + * \brief Scan for NUT devices + * + * \param options Options + */ +static void scanNUTdevices(const NutConfOptions & options) { + for (size_t i = 0; ; ++i) { + NutConfOptions::Arguments args; + + bool ok = options.getDouble("scan-nut", args, i); + + if (!ok) break; + + // Sanity checks + assert(args.size() >= 3); + + NutConfOptions::Arguments::const_iterator arg = args.begin(); + + const std::string & start_ip = *arg++; + const std::string & stop_ip = *arg++; + const std::string & port = *arg++; + + // TBD: where should we get the default? + useconds_t us_timeout = 1000000; + + if (arg != args.end()) { + std::stringstream ss(*arg); + + ss >> us_timeout; + } + + NutScanner::devices_t devices = NutScanner::devicesNUT( + start_ip, stop_ip, port, us_timeout); + + printDevicesInfo(devices, options.verbose); + } +} + + +/** + * \brief Scan for XML/HTTP devices + * + * \param options Options + */ +static void scanXMLHTTPdevices(const NutConfOptions & options) { + NutConfOptions::Arguments args; + + bool ok = options.getDouble("scan-xml-http", args); + + // Sanity checks + assert(ok); + + // TBD: where should we get the default? + useconds_t us_timeout = 1000000; + + if (!args.empty()) { + std::stringstream ss(args.front()); + + ss >> us_timeout; + } + + NutScanner::devices_t devices = NutScanner::devicesXMLHTTP(us_timeout); + + printDevicesInfo(devices, options.verbose); +} + + +/** + * \brief Scan for Avahi devices + * + * \param options Options + */ +static void scanAvahiDevices(const NutConfOptions & options) { + NutConfOptions::Arguments args; + + bool ok = options.getDouble("scan-avahi", args); + + // Sanity checks + assert(ok); + + // TBD: where should we get the default? + useconds_t us_timeout = 1000000; + + if (!args.empty()) { + std::stringstream ss(args.front()); + + ss >> us_timeout; + } + + NutScanner::devices_t devices = NutScanner::devicesAvahi(us_timeout); + + printDevicesInfo(devices, options.verbose); +} + + +/** + * \brief Scan for IPMI devices + * + * \param options Options + */ +static void scanIPMIdevices(const NutConfOptions & options) { + for (size_t i = 0; ; ++i) { + NutConfOptions::Arguments args; + + bool ok = options.getDouble("scan-ipmi", args, i); + + if (!ok) break; + + // Sanity checks + assert(args.size() >= 2); + + NutConfOptions::Arguments::const_iterator arg = args.begin(); + + const std::string & start_ip = *arg++; + const std::string & stop_ip = *arg++; + + NutScanner::IPMIAttributes attrs; + + // Parse = pairs + bool errors = false; + + for (; arg != args.end(); ++arg) { + size_t eq_pos = (*arg).find('='); + + if (std::string::npos == eq_pos) { + std::cerr + << "Error: Invalid IPMI attribute specification: \"" + << *arg << '"' << std::endl; + + errors = true; + + continue; + } + + std::string attr = (*arg).substr(0, eq_pos); + std::string val = (*arg).substr(eq_pos + 1); + + if ("username" == attr) { + attrs.username = val; + } + else if ("password" == attr) { + attrs.passwd = val; + } + else if ("auth-type" == attr) { + if ("none" == val) + attrs.auth_type = IPMI_AUTHENTICATION_TYPE_NONE; + else if ("MD2" == val) + attrs.auth_type = IPMI_AUTHENTICATION_TYPE_MD2; + else if ("MD5" == val) + attrs.auth_type = IPMI_AUTHENTICATION_TYPE_MD5; + else if ("plain-password" == val) + attrs.auth_type = IPMI_AUTHENTICATION_TYPE_STRAIGHT_PASSWORD_KEY; + else if ("OEM" == val) + attrs.auth_type = IPMI_AUTHENTICATION_TYPE_OEM_PROP; + else if ("RMCPplus" == val) + attrs.auth_type = IPMI_AUTHENTICATION_TYPE_RMCPPLUS; + else { + std::cerr + << "Error: Invalid IPMI auth. type: \"" + << val << '"' << std::endl; + + errors = true; + + continue; + } + } + else if ("cipher-suite-id" == attr) { + std::stringstream ss(val); + + if ((ss >> attrs.cipher_suite_id).fail()) { + std::cerr + << "Error: Invalid IPMI cipher suite ID: \"" + << val << '"' << std::endl; + + errors = true; + + continue; + } + } + else if ("K-g-BMC-key" == attr) { + attrs.K_g_BMC_key = val; + } + else if ("priv-level" == attr) { + std::stringstream ss(val); + + if ((ss >> attrs.priv_level).fail()) { + std::cerr + << "Error: Invalid IPMI priv. level: \"" + << val << '"' << std::endl; + + errors = true; + + continue; + } + } + else if ("workaround-flags" == attr) { + std::stringstream ss(val); + + if ((ss >> attrs.wa_flags).fail()) { + std::cerr + << "Error: Invalid IPMI workaround flags: \"" + << val << '"' << std::endl; + + errors = true; + + continue; + } + } + + else if ("version" == attr) { + if ("1.5" == val) + attrs.version = IPMI_1_5; + else if ("2.0" == val) + attrs.version = IPMI_2_0; + else { + std::cerr + << "Error: Unsupported IPMI version " + << val << std::endl; + + errors = true; + + continue; + } + } + + else { + std::cerr + << "Error: Unknown IPMI attribute: \"" + << attr << '"' << std::endl; + + errors = true; + } + } + + if (errors) continue; + + NutScanner::devices_t devices = NutScanner::devicesIPMI( + start_ip, stop_ip, attrs); + + printDevicesInfo(devices, options.verbose); + } +} + + +/** + * \brief Scan for serial devices devices + * + * \param options Options + */ +static void scanSerialDevices(const NutConfOptions & options) { + NutConfOptions::Arguments args; + + bool ok = options.getDouble("scan-serial", args); + + // Sanity checks + assert(ok); + + NutScanner::devices_t devices = NutScanner::devicesEatonSerial(args); + + printDevicesInfo(devices, options.verbose); +} + +#endif // defined WITH_NUTSCANNER + + +/** + * \brief Main routine (exceptions unsafe) + * + * \param argc Argument count + * \param argv Arguments + * + * \return 0 always (exits on error) + */ +static int mainx(int argc, char * const argv[]) { + // Get options + NutConfOptions options(argv, argc); + + // Usage + if (options.exists("help") || options.existsSingle("h")) { + Usage::print(argv[0]); + + ::exit(0); + } + + // Check that command-line options validity + if (!options.valid) { + options.reportInvalid(); + + Usage::print(argv[0]); + + ::exit(1); + } + + // Set configuration directory + std::string etc(CONFPATH); + + if (!options.local.empty()) { + etc = options.local; + } + + // Check configuration directory availability + nut::NutFile etc_dir(etc); + + if (!etc_dir.exists()) { + std::cerr << "Error: Configuration directory " << etc << " isn't available" << std::endl; + + ::exit(1); + } + + // --is-configured query + if (options.is_configured) { + bool is_configured = isConfigured(etc); + + std::cout << (is_configured ? "true" : "false") << std::endl; + + ::exit(is_configured ? 0 : 1); + } + + // --get-mode + if (options.get_mode) { + std::cout << getMode(etc) << std::endl; + } + + // --set-mode + if (!options.mode.empty()) { + setMode(options.mode, etc); + } + + // Monitors were set + if (!options.monitors.empty()) { + std::list monitors; + + for (size_t n = options.monitors.size() / 6, i = 0; i < n; ++i) { + monitors.push_back(monitor(i, options)); + } + + setMonitors(monitors, etc, options.add_monitor_cnt > 0); + } + + // Listen addresses were set + if (!options.listen_addrs.empty()) { + std::list listen_addrs; + + for (size_t i = 0; i < options.listen_addrs.size(); ++i) { + listen_addrs.push_back(listenAddr(i, options)); + } + + setListenAddrs(listen_addrs, etc, options.add_listen_cnt > 0); + } + + // Devices were set + if (!options.devices.empty()) { + setDevices(options.devices, etc, options.add_device_cnt > 0); + } + + // Notify flags were set + if (!options.notify_flags.empty()) { + setNotifyFlags(options.notify_flags, etc); + } + + // Notify messages were set + if (!options.notify_msgs.empty()) { + setNotifyMsgs(options.notify_msgs, etc); + } + + // Notify command was set + if (!options.notify_cmd.empty()) { + setNotifyCmd(options.notify_cmd, etc); + } + + // Shutdown command was set + if (!options.shutdown_cmd.empty()) { + setShutdownCmd(options.shutdown_cmd, etc); + } + + // Min. of power supplies was set + if (!options.min_supplies.empty()) { + setMinSupplies(options.min_supplies, etc); + } + + // Powerdown flag file was set + if (!options.powerdown_flag.empty()) { + setPowerdownFlag(options.powerdown_flag, etc); + } + + // Users were set + if (!options.users.empty()) { + setUsers(options.users, etc, options.add_user_cnt > 0); + } + +#if (defined WITH_NUTSCANNER) + + // SNMP devices scan + if (options.scan_snmp_cnt) { + scanSNMPdevices(options); + } + + // USB devices scan + if (options.scan_usb) { + scanUSBdevices(options); + } + + // NUT devices scan + if (options.scan_nut_cnt) { + scanNUTdevices(options); + } + + // XML/HTTP devices scan + if (options.scan_xml_http) { + scanXMLHTTPdevices(options); + } + + // Avahi devices scan + if (options.scan_avahi) { + scanAvahiDevices(options); + } + + // IPMI devices scan + if (options.scan_ipmi_cnt) { + scanIPMIdevices(options); + } + + // Serial devices scan + if (options.scan_serial) { + scanSerialDevices(options); + } + +#endif // defined WITH_NUTSCANNER + + return 0; +} + + +/** + * \brief Main routine exception-safe wrapper + * + * Exceptions should never leak... + * + * \param argc Argument count + * \param argv Arguments + */ +int main(int argc, char * const argv[]) { + try { + return mainx(argc, argv); + } + catch (const std::exception & e) { + std::cerr + << "Error: " << e.what() << std::endl; + } + catch (...) { + std::cerr + << "INTERNAL ERROR: exception of unknown origin caught" << std::endl + << "Please issue a bug report to nut-upsdev@lists.alioth.debian.org" + << std::endl; + } + + ::exit(128); +}