diff --git a/.circleci/config.yml b/.circleci/config.yml index c6a2acb950..08bc03f609 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -17,13 +17,17 @@ jobs: parameters: XCODE_VER: type: string - default: "12.5.1" + # https://circleci.com/docs/using-macos/#supported-xcode-versions-silicon + default: "13.4.1" CC: type: string default: "" # e.g. "clang" CXX: type: string default: "" # e.g. "clang++" + CPP: + type: string + default: "" # e.g. "clang -E" CC_STDVER: type: string default: "" # e.g. "-std=gnu17" @@ -39,15 +43,27 @@ jobs: BREW_MORE: type: string default: "" # e.g. "avahi" for all-driver tests + #HOMEBREW_PREFIX: + # Detected below at run-time + # See https://docs.brew.sh/Installation : + # * /opt/homebrew for Apple Silicon, + # * /usr/local for macOS Intel and + # * /home/linuxbrew/.linuxbrew for Linux + # TODO: Find a way to select one of the values based on the platform? + #type: string + #default: "/opt/homebrew" # since July 2024 CircleCI only serves Apple Silicon instances + #default: "/usr/local" # was when CircleCI builders had x86 environment: CC: << parameters.CC >> CXX: << parameters.CXX >> + CPP: << parameters.CPP >> CC_STDVER: << parameters.CC_STDVER >> CXX_STDVER: << parameters.CXX_STDVER >> BUILD_TYPE: << parameters.BUILD_TYPE >> CI_BUILDDIR: << parameters.CI_BUILDDIR >> BREW_MORE: << parameters.BREW_MORE >> + #HOMEBREW_PREFIX: << parameters.HOMEBREW_PREFIX >> # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor @@ -65,25 +81,63 @@ jobs: # Note: MacOS default /bin/bash 3.x is too old for ci_build.sh # Brew brings /usr/local/bin/bash 5.x as of this writing - # TODO: Are Binutils needed? + # We also reinstall/relink openssl@3 because asciidoc deps pull + # in openssl@1 and MAYBE they conflict (cause lack of exposed + # symlinks). - run: name: "homebrew" command: |- - HOMEBREW_NO_AUTO_UPDATE=1; export HOMEBREW_NO_AUTO_UPDATE; - brew install ccache bash libtool pkg-config gd libusb neon net-snmp openssl $BREW_MORE #binutils + #HOMEBREW_NO_AUTO_UPDATE=1; export HOMEBREW_NO_AUTO_UPDATE; + brew install ccache bash libtool binutils autoconf automake git m4 \ + pkg-config aspell asciidoc docbook-xsl cppunit gd \ + libusb neon net-snmp \ + nss openssl \ + libmodbus freeipmi powerman $BREW_MORE + #brew reinstall openssl@3 + if ps -ef | grep -v grep | grep sshd ; then + brew install mc + ifconfig -a || true + fi -# - run: -# name: "homebrew-libtool" -# command: |- -# #find /usr /opt /lib* -name '*ltdl*' -ls 2>/dev/null || true -# brew unlink libtool && brew link libtool -# #find /usr /opt /lib* -name '*ltdl*' -ls 2>/dev/null || true + # https://github.com/Homebrew/legacy-homebrew/issues/15488 + - run: + name: "homebrew-relink" + command: |- + #find /usr /opt /lib* -name '*ltdl*' -ls 2>/dev/null || true + brew unlink libtool && brew link libtool + #find /usr /opt /lib* -name '*ltdl*' -ls 2>/dev/null || true + brew info openssl + #brew info openssl@3 + #brew info openssl@1.1 + #command -v pkg-config || true + #pkg-config --list-all || true + #HOMEBREW_PREFIX="`brew config | grep HOMEBREW_PREFIX: | awk '{print $2}'`" + #ls -la "${HOMEBREW_PREFIX}/lib/pkgconfig" || true + #ls -la "${HOMEBREW_PREFIX}/include/openssl" || true + #ls -la "${HOMEBREW_PREFIX}/include/openssl"/* || true + #find "${HOMEBREW_PREFIX}"/Cellar/openssl* -ls || true + pkg-config --libs --cflags nss + pkg-config --libs --cflags openssl || { + brew unlink openssl && brew link --force openssl + pkg-config --libs --cflags openssl ; } + brew unlink net-snmp && brew link --force net-snmp - restore_cache: keys: - ccache-{{ .Branch }}-{{ arch }}-{{ .Environment.CIRCLE_JOB }} - ccache-master-{{ arch }}-{{ .Environment.CIRCLE_JOB }} + # Fail fast if we had a problem with prerequisites - this + # situation would likely impact all build branches anyway. + # Requires a Personal API token in the building account: + # https://support.circleci.com/hc/en-us/articles/360052058811-Exit-Build-Early-if-Any-Test-Fails + - run: + name: Fail Fast + when: on_fail + command: |- + echo "Canceling workflow as a step resulted in failure" + curl -X POST --header "Content-Type: application/json" "https://circleci.com/api/v2/workflow/${CIRCLE_WORKFLOW_ID}/cancel?circle-token=${CIRCLE_TOKEN}" + # - run: # name: "check shell" # command: /usr/bin/env bash --version || true; command -v bash || true @@ -99,14 +153,16 @@ jobs: # uses of sem_init() and sem_destroy() in nut-scanner.c) # NOTE: CANBUILD_NIT_TESTS=yes to check if single-executor environments # do not have a problem with it. + # NOTE: python3.11 is available but broken on some of the workers + # (no homebrew dirs in search path) - run: name: "ci_build" command: |- - CI_CCACHE_SYMLINKDIR="/usr/local/opt/ccache/libexec" \ + HOMEBREW_PREFIX="`brew config | grep HOMEBREW_PREFIX: | awk '{print $2}'`" \ CANBUILD_NIT_TESTS=yes \ - CFLAGS="$CC_STDVER -Wno-poison-system-directories -Wno-deprecated-declarations" \ - CXXFLAGS="$CXX_STDVER -Wno-poison-system-directories" \ - LDFLAGS="-L/usr/local/lib" \ + CFLAGS="$CC_STDVER" \ + CXXFLAGS="$CXX_STDVER" \ + PYTHON=python3.12 \ ./ci_build.sh - run: @@ -125,6 +181,13 @@ jobs: - ~/.ccache key: ccache-{{ .Branch }}-{{ arch }}-{{ .Environment.CIRCLE_JOB }} + - store_artifacts: + path: config.log + + - store_artifacts: + path: config.nut_report_feature.log + + # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: @@ -132,37 +195,58 @@ workflows: jobs: # Note: while "ccache" lists hordes of symlinks to gcc-XXX versions, # in practice these toolkits are not installed (by default) + # or actually identify as clang ### This scenario is a subset of fightwarn-all below (modulo C standard), ### so disabled to not waste time from free CircleCI allowance limit: # - osx-xcode: -# name: "gnu17-clang-xcode12_5_1-default-all-errors" -# XCODE_VER: "12.5.1" +# name: "gnu17-clang-xcode13_4_1-default-all-errors" +# XCODE_VER: "13.4.1" # CC: "clang" # CXX: "clang++" # CC_STDVER: "-std=gnu17" # CXX_STDVER: "-std=gnu++17" +# - osx-xcode: +# name: "gnu11-gcc-xcode13_4_1-out-of-tree" +# CC: "gcc" +# CXX: "g++" +# CC_STDVER: "-std=gnu11" +# CXX_STDVER: "-std=gnu++11" +# # Try an out-of-tree build: +# CI_BUILDDIR: "obj" + +# - osx-xcode: +# name: "c99-cxx11-gcc-xcode13_4_1-default-distcheck" +# CC: "gcc" +# CXX: "g++" +# CC_STDVER: "-std=c99" +# CXX_STDVER: "-std=c++11" +# # Try usual and distchecked build: +# BUILD_TYPE: "default" + - osx-xcode: - name: "gnu11-gcc-xcode12_5_1-out-of-tree" - CC: "gcc" - CXX: "g++" + name: "gnu11-clang-xcode13_4_1-out-of-tree" + CC: "clang" + CXX: "clang++" + CPP: "clang -E" CC_STDVER: "-std=gnu11" CXX_STDVER: "-std=gnu++11" # Try an out-of-tree build: CI_BUILDDIR: "obj" - osx-xcode: - name: "c99-cxx11-gcc-xcode12_5_1-default-distcheck" - CC: "gcc" - CXX: "g++" + name: "c99-cxx11-clang-xcode13_4_1-default-distcheck" + CC: "clang" + CXX: "clang++" + CPP: "clang -E" CC_STDVER: "-std=c99" CXX_STDVER: "-std=c++11" # Try usual and distchecked build: BUILD_TYPE: "default" - osx-xcode: - name: "stdDefault-xcode12_5_1-fightwarn-all" + name: "stdDefault-xcode13_4_1-fightwarn-all" # Run "default-all-errors" with both compiler families, # using their default C/C++ standard for current release: BUILD_TYPE: "fightwarn-all" @@ -170,8 +254,8 @@ workflows: ### This does not work due to missing dependencies built for MacOS in homebrew: ### TODO? Evaluate other packagers (MacPorts, fink...)? # - osx-xcode: -# name: "c17-clang-xcode12_5_1-alldrv" -# XCODE_VER: "12.5.1" +# name: "c17-clang-xcode13_4_1-alldrv" +# XCODE_VER: "13.4.1" # CC: "clang" # CXX: "clang++" # CC_STDVER: "-std=c17" diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 0000000000..28eaa5d8a1 --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,4 @@ +# For interpreted languages: +paths: + - scripts/python/module + - scripts/python/app diff --git a/.github/workflows/PyNUTClient.yml b/.github/workflows/PyNUTClient.yml index 018562485b..e4a4834180 100644 --- a/.github/workflows/PyNUTClient.yml +++ b/.github/workflows/PyNUTClient.yml @@ -47,7 +47,7 @@ jobs: { TAG_NAME="$(git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]' --exclude '*-signed' --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*')" \ || TAG_NAME="$(git describe --tags --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*' --exclude '*Windows*' --exclude '*IPM*')" ; } \ && test -n "${TAG_NAME}" \ - || TAG_NAME="2.8.1-`TZ=UTC date +%s`" ; + || TAG_NAME="2.8.2-`TZ=UTC date +%s`" ; fi >&2 ; TAG_NAME="$(echo "$TAG_NAME" | sed -e 's/^v\([0-9]\)/\1/' -e 's,^.*/,,' -e 's/^v//' -e 's/-g.*$//' -e 's/-/./g')" ; echo "TAG_NAME=$TAG_NAME" >> $GITHUB_OUTPUT diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2438bd0555..edb55bd86f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -23,7 +23,7 @@ on: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} permissions: actions: read contents: read @@ -32,10 +32,22 @@ jobs: strategy: fail-fast: false matrix: + # https://docs.github.com/en/actions/using-jobs/using-a-matrix-for-your-jobs language: [ 'cpp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support - # TODO: Want to find Python sources to test like with LGTM, see https://github.com/networkupstools/nut/issues/1726 + os: ['ubuntu-latest'] + # TOTHINK: windows-latest, macos-latest + compiler: ['CC=gcc CXX=g++', 'CC=clang CXX=clang++'] + NUT_SSL_VARIANTS: ['no', 'nss', 'openssl'] + NUT_USB_VARIANTS: ['no', 'libusb-1.0', 'libusb-0.1'] + include: + # Add cell(s) to the matrix, beside the combinatorics made above + - language: 'python' + os: 'ubuntu-latest' + compiler: 'PYTHON=python3' + NUT_SSL_VARIANTS: 'no' + NUT_USB_VARIANTS: 'no' steps: - name: Checkout repository @@ -52,12 +64,40 @@ jobs: # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # queries: security-extended,security-and-quality + config-file: ./.github/codeql/codeql-config.yml + # https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + - if: matrix.language == 'cpp' && matrix.os == 'ubuntu-latest' + name: NUT CI Prerequisite packages (Ubuntu) + run: | + sudo apt update + case x"${{matrix.compiler}}" in x*clang*) sudo apt install clang ;; x*) sudo apt install gcc g++ ;; esac + sudo apt install libltdl-dev libtool libtool-bin cppcheck ccache libgd-dev libcppunit-dev libsystemd-dev libssl-dev libnss3-dev augeas-tools libaugeas-dev augeas-lenses libusb-dev libusb-1.0-0-dev libmodbus-dev libsnmp-dev libpowerman0-dev libfreeipmi-dev libipmimonitoring-dev libavahi-common-dev libavahi-core-dev libavahi-client-dev libgpiod-dev libneon27-dev libi2c-dev i2c-tools lm-sensors # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v3 + #- name: Autobuild + # uses: github/codeql-action/autobuild@v3 + + #- if: matrix.language == 'cpp' + # name: NUT CI Build (default-all-errors matrix) + # run: | + # BUILD_TYPE=default-all-errors BUILD_SSL_ONCE=true DO_DISTCHECK=no CI_SKIP_CHECK=true CANBUILD_DOCS_ALL=no ${{ matrix.compiler }} NUT_SSL_VARIANTS=${{ matrix.NUT_SSL_VARIANTS }} NUT_USB_VARIANTS=${{ matrix.NUT_USB_VARIANTS }} ./ci_build.sh + + #- if: matrix.language == 'cpp' + # name: NUT CI Build (fightwarn-all) + # run: | + # BUILD_TYPE=fightwarn-all ./ci_build.sh + + # TOTHINK: Can we prepare the working area once (apt, autogen => containers?) + # and then spread it out for builds and analyses? + # Can ccache be used across builds? + - if: matrix.language == 'cpp' + name: NUT CI Build + run: | + ./autogen.sh + ./configure --enable-warnings --enable-Werror --enable-Wcolor --with-all=auto --with-dev --without-docs ${{matrix.compiler}} --with-ssl=${{matrix.NUT_SSL_VARIANTS}} --with-usb=${{matrix.NUT_USB_VARIANTS}} + make -s -j 8 # ℹī¸ Command-line programs to run using the OS shell. # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun diff --git a/.gitignore b/.gitignore index bc2e70a1a3..fd04ce4e58 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,7 @@ /_install_pkgprotodir/ Makefile Makefile.in + ## Parent directory only /aclocal.m4 /ar-lib @@ -22,6 +23,7 @@ Makefile.in /ChangeLog.adoc /config.guess /config.log +/config.log.inplace-outer /config.status /config.sub /config.h @@ -43,16 +45,23 @@ Makefile.in /README /TODO /UPGRADING +/VERSION_DEFAULT +/VERSION_FORCED +/VERSION_FORCED_SEMVER /install-sh /libtool /ltmain.sh /missing /test-driver +*-contentchecked *-spellchecked *-prepped +*.usage-report *.adoc-parsed *.adoc*.tmp +*.adoc*.tmp.* *.txt*.tmp +*.txt*.tmp.* /cppcheck*.xml /.ci*.txt* /.ci*.log diff --git a/COPYING b/COPYING index 94adc8c31f..40882612c2 100644 --- a/COPYING +++ b/COPYING @@ -3,7 +3,14 @@ or (at your option) any later version. See "LICENSE-GPL2" in the root of this distribution. - The files in the scripts/python/ directory are released under GNU General + The scripts/python/module/nut_telnetlib.py file is copied for fall-back + purposes from Python 3.10, and released under its original license (the + PSF License Version 2). It was only modified in the comment section to + describe the copy, purpose and provenance per section 3 of the license. + According to https://github.com/python/cpython/blob/3.10/LICENSE most of + the licenses Python was provided under over the years are GPL-compatible. + + Other files in the scripts/python/ directory are released under GNU General Public License (GPL) version 3, or (at your option) any later version. See "LICENSE-GPL3" in the root of this distribution. @@ -11,6 +18,11 @@ license as Perl itself. That is to say either GPL version 1 or (at your option) any later version, or the "Artistic License". + The scripts/installer contents were donated to the Network UPS Tools project + by Eaton in 2022, including the license change from the one originally used + with their software companion to terms of the open-source project (that is, + GPL version 2 or newer). + Several fallback implementations for methods absent from standard library of an end-user's current build platform are derived from source code available under the two-clause BSD license (common/strptime.c, common/strnlen.c) diff --git a/Jenkinsfile-dynamatrix b/Jenkinsfile-dynamatrix index 11e14f15bf..e55a526ddd 100644 --- a/Jenkinsfile-dynamatrix +++ b/Jenkinsfile-dynamatrix @@ -25,7 +25,8 @@ import org.nut.dynamatrix.*; def dynacfgPipeline = [:] // NOTE: These can be further disabled or active in different combo specs - // below based on branch names + // below based on branch names. Also note that the values are somewhat + // "inversed" -- e.g. that "disabledSomething = false" means "enable it". dynacfgPipeline.disableSlowBuildAutotools = false dynacfgPipeline.disableSlowBuildCIBuild = false dynacfgPipeline.disableSlowBuildCIBuildExperimental = false @@ -254,7 +255,7 @@ import org.nut.dynamatrix.*; // Imported from jenkins-dynamatrix JSL vars/autotools.groovy: // a workaround for the cases of curiously missing MAKE envvar... - dynacfgPipeline.buildPhases['distcheck'] = """( if [ x"\${MAKE-}" = x ]; then echo "WARNING: MAKE is somehow unset, defaulting!" >&2; MAKE=make; fi; eval \${CONFIG_ENVVARS} time \${MAKE} \${MAKE_OPTS} distcheck DISTCHECK_FLAGS="\${CONFIG_OPTS}" )""" + dynacfgPipeline.buildPhases['distcheck'] = """( if [ x"\${MAKE-}" = x ]; then echo "WARNING: MAKE is somehow unset, defaulting!" >&2; MAKE=make; fi; eval \${CONFIG_ENVVARS} time \${MAKE} \${MAKE_OPTS} distcheck DISTCHECK_FLAGS=\${CONFIG_OPTS:+\\"\$CONFIG_OPTS\\"} )""" // Note: shellcheck/spellcheck/... require autotools currently // or need to be redefined with respective BUILD_TYPE @@ -285,7 +286,10 @@ set | sort -n """ } withEnvOptional(dynacfgPipeline.defaultTools) { - unstashCleanSrc(dynacfgPipeline.stashnameSrc) + stage('Unstash sources') { + unstashCleanSrc(dynacfgPipeline.stashnameSrc) + } + buildMatrixCellCI(dynacfgPipeline, dsbcClone, stageNameClone) //buildMatrixCellCI(dynacfgPipeline, dsbc, stageName) } @@ -315,7 +319,10 @@ set | sort -n """ } withEnvOptional(dynacfgPipeline.defaultTools) { - unstashCleanSrc(dynacfgPipeline.stashnameSrc) + stage('Unstash sources') { + unstashCleanSrc(dynacfgPipeline.stashnameSrc) + } + def dynacfgPipeline_ciBuild = dynacfgPipeline.clone() dynacfgPipeline_ciBuild.buildSystem = 'ci_build.sh' dynacfgPipeline_ciBuild.buildPhases = [:] @@ -421,6 +428,64 @@ set | sort -n """ //'bodyParStages': {} ] // one slowBuild filter configuration, autotools-Wall + ,[name: 'Default autotools driven build with default configuration, bitness and warning levels on each NUT CI farm platform (but with fatal warnings as of gnu99/gnu++11, must pass where enabled)', + disabled: dynacfgPipeline.disableSlowBuildAutotools, + //branchRegexSource: ~/^(PR-.+|fightwarn.*)$/, + //branchRegexTarget: dynacfgPipeline.branchStableRegex, + //branchRegexTarget: ~/fightwarn/, + //appliesToChangedFilesRegex: dynacfgPipeline.appliesToChangedFilesRegex_C, + 'getParStages': { def dynamatrix, Closure body -> + return dynamatrix.generateBuild([ + //commonLabelExpr: dynacfgBase.commonLabelExpr, + //defaultDynamatrixConfig: dynacfgBase.defaultDynamatrixConfig, + requiredNodelabels: [], + excludedNodelabels: [], + + dynamatrixAxesVirtualLabelsMap: [ + //'BITS': [32, 64], + // 'CSTDVERSION': ['03', '2a'], + //'CSTDVERSION_${KEY}': [ ['c': '03', 'cxx': '03'], ['c': '99', 'cxx': '98'], ['c': '17', 'cxx': '2a'], 'ansi' ], + //'CSTDVERSION_${KEY}': [ ['c': '03', 'cxx': '03'], ['c': '99', 'cxx': '98'], ['c': '17', 'cxx': '2a'] ], + 'CSTDVERSION_${KEY}': [ ['c': '99', 'cxx': '11'] ], + 'CSTDVARIANT': ['gnu'] + ], + dynamatrixAxesCommonEnv: [ + // One set of several simultaneously exported envvars! + // CONFIG_OPTS are picked up by our dynamatrix configuration + // and substituted into shell "as is" for normal builds + // (so splitting into many tokens), or quoted as a single + // token DISTCHECK_FLAGS in its stage (split by make later). + ['LANG=C','LC_ALL=C','TZ=UTC', + 'CONFIG_OPTS=--with-all=auto --with-docs=auto --with-ssl=auto --enable-Werror --enable-warnings --disable-Wcolor --enable-silent-rules' + ] + ], + + mergeMode: [ 'dynamatrixAxesVirtualLabelsMap': 'replace', 'excludeCombos': 'merge' ], + allowedFailure: [ + dynacfgPipeline.axisCombos_WINDOWS, + dynacfgPipeline.axisCombos_STRICT_C + ], + runAllowedFailure: true, + //dynamatrixAxesLabels: ['OS_FAMILY', 'OS_DISTRO', '${COMPILER}VER', 'ARCH${ARCH_BITS}'], + //dynamatrixAxesLabels: [~/^OS/, '${COMPILER}VER', 'ARCH${ARCH_BITS}'], + dynamatrixAxesLabels: [~/^OS_DISTRO/, 'COMPILER'], + excludeCombos: dynacfgPipeline.excludeCombos_DEFAULT_STRICT_C + + [dynacfgPipeline.axisCombos_WINDOWS_CROSS] + + [[~/OS_DISTRO=openbsd-6\./, ~/COMPILER=GCC/]] + // Here we picked just OSes and compilers (gcc or clang), + // so exclude systems which have e.g. gcc-4.2.1 which claims + // type range comparison warnings despite pragma fencing. + // gcc-4.8.x on CentOS 7 and Ubuntu 14.04 looks already okay. + + [[~/OS_DISTRO=macos/]] + // MacOS (at least agents prepared with HomeBrew packages) + // requires a few pkg-config and CFLAGS pre-sets which are + // done in ci_build.sh and defeat the purpose of this stage. + // So it is easier and more honest to just skip it. + ], body) + }, // getParStages + //'bodyParStages': {} + ] // one slowBuild filter configuration, autotools-everywhere + ,[name: 'Various non-docs distchecked target builds with main and ~newest supported C/C++ revisions (must pass on all platforms)', disabled: dynacfgPipeline.disableSlowBuildCIBuild, //branchRegexSource: ~/^(PR-.+|fightwarn.*)$/, @@ -478,8 +543,8 @@ set | sort -n """ // BUILD_TYPE=default-tgt:distcheck-light + NO_PKG_CONFIG=true ? ], dynamatrixAxesCommonEnv: [ - ['LANG=C','LC_ALL=C','TZ=UTC' - //,'BUILD_WARNFATAL=yes','BUILD_WARNOPT=hard' + ['LANG=C','LC_ALL=C','TZ=UTC','BUILD_WARNFATAL=yes' + //,'BUILD_WARNOPT=hard' ] ], allowedFailure: [ @@ -527,8 +592,8 @@ set | sort -n """ 'BUILD_TYPE': ['default-tgt:cppcheck'] ], dynamatrixAxesCommonEnv: [ - ['LANG=C','LC_ALL=C','TZ=UTC', 'DO_CLEAN_CHECK=no' - //,'BUILD_WARNFATAL=yes','BUILD_WARNOPT=hard' + ['LANG=C','LC_ALL=C','TZ=UTC', 'DO_CLEAN_CHECK=no', 'BUILD_WARNFATAL=yes' + //,'BUILD_WARNOPT=hard' ] ], allowedFailure: [ @@ -685,7 +750,8 @@ set | sort -n """ ], dynamatrixAxesCommonEnv: [ ['LANG=C','LC_ALL=C','TZ=UTC', - 'BUILD_WARNFATAL=yes','BUILD_WARNOPT=minimal' + 'BUILD_WARNFATAL=yes' + //,'BUILD_WARNOPT=medium' ] ], allowedFailure: [ @@ -726,9 +792,14 @@ set | sort -n """ dynamatrixAxesCommonEnv: [ ['LANG=C','LC_ALL=C','TZ=UTC', // Build in a subdirectory to check that out-of-dir - // builds are healthy too + // builds are healthy too. + // NOTE: It would be useful to also have a recipe to build + // "completely out-of-tree", in a different filesystem (to + // make sure we do not rely on hard-links, relative paths, + // etc.) 'CI_BUILDDIR=obj', - 'BUILD_WARNFATAL=yes','BUILD_WARNOPT=minimal' + 'BUILD_WARNFATAL=yes' + //,'BUILD_WARNOPT=minimal' ] ], allowedFailure: [ @@ -767,7 +838,8 @@ set | sort -n """ ], dynamatrixAxesCommonEnv: [ ['LANG=C','LC_ALL=C','TZ=UTC', - 'BUILD_WARNFATAL=yes','BUILD_WARNOPT=minimal' + 'BUILD_WARNFATAL=yes' + //,'BUILD_WARNOPT=minimal' ] ], allowedFailure: [ @@ -1213,7 +1285,7 @@ set | sort -n """ 'bodyParStages': dynacfgPipeline.slowBuildDefaultBody_ci_build ] // one slowBuild filter configuration - ,[name: 'Strict C and GNU standard builds on cross-Windows platforms (Linux+mingw), without distcheck and docs (allowed to fail)', + ,[name: (dynacfgPipeline.disableStrictCIBuild_CrossWindows ? '' : 'Strict C and ') + 'GNU standard builds on cross-Windows platforms (Linux+mingw), without distcheck and docs (allowed to fail)', disabled: dynacfgPipeline.disableSlowBuildCIBuild, //branchRegexSource: ~/^(PR-.+|.*fightwarn.*|.*Windows.*)$/, //branchRegexTarget: ~/fightwarn|Windows-.*/, diff --git a/Makefile.am b/Makefile.am index bac4f8eb2b..2a108bdda8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -3,11 +3,21 @@ # Export certain values for ccache which NUT ci_build.sh can customize, # to facilitate developer iteration re-runs of "make" later. # At least GNU and BSD make implementations are okay with this syntax. -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_DIR=@CCACHE_DIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_PATH=@CCACHE_PATH@ -@NUT_AM_MAKE_CAN_EXPORT@export PATH=@PATH_DURING_CONFIGURE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_NAMESPACE@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_BASEDIR@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_DIR@export CCACHE_DIR=@CCACHE_DIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export CCACHE_PATH=@CCACHE_PATH@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export PATH=@PATH_DURING_CONFIGURE@ + +# First target often defines default behavior: all +# We follow up with another pass to `make doc` because our wild recipes +# sometimes preclude generating all of them on the first pass (FIXME!) +# missing e.g. PDF and HTML which then pop up in `make check` footprint, +# or misses a .prep-src-docs stage needed to pattern-make man page files +# with some "make" implementations... +all all-am-local all-local: all-recursive + +@$(MAKE) $(AM_MAKEFLAGS) doc + +@$(MAKE) $(AM_MAKEFLAGS) doc # include directory for aclocal ACLOCAL_AMFLAGS = -I m4 @@ -60,6 +70,7 @@ endif DISTCHECK_FLAGS = --with-all --with-ssl --with-doc=auto --with-pynut=app --with-nut_monitor=force DISTCHECK_LIGHT_FLAGS = --with-all=auto --with-ssl=auto --with-doc=auto --with-pynut=app --with-nut_monitor=force +DISTCHECK_LIGHT_MAN_FLAGS = --with-all=auto --with-ssl=auto --with-doc=man --with-pynut=app --with-nut_monitor=force DISTCHECK_VALGRIND_FLAGS = --with-all=auto --with-ssl=auto --with-doc=skip --with-valgrind CXXFLAGS='$(CXXFLAGS) -g' CFLAGS='$(CFLAGS) -g' --with-pynut=app --with-nut_monitor=force # Note: this rule uses envvar DISTCHECK_FLAGS expanded at run-time @@ -73,12 +84,20 @@ DISTCHECK_CONFIGURE_FLAGS = ${DISTCHECK_FLAGS} \ --with-devd-dir='$${prefix}/etc/devd' \ --with-pynut=app --with-nut_monitor=force +# Note: trickery with prefix below is needed to expand it from +# DISTCHECK_CONFIGURE_FLAGS defaults defined above in a manner +# that is meaningful for sub-make program (gets stripped away +# otherwise and breaks custom distchecks). distcheck-light: - +$(MAKE) $(AM_MAKEFLAGS) DISTCHECK_FLAGS="$(DISTCHECK_LIGHT_FLAGS)" distcheck + +prefix='$${prefix}'; $(MAKE) $(AM_MAKEFLAGS) DISTCHECK_CONFIGURE_FLAGS="$(DISTCHECK_CONFIGURE_FLAGS)" DISTCHECK_FLAGS="$(DISTCHECK_LIGHT_FLAGS)" distcheck + +distcheck-light-man: + +prefix='$${prefix}'; $(MAKE) $(AM_MAKEFLAGS) DISTCHECK_CONFIGURE_FLAGS="$(DISTCHECK_CONFIGURE_FLAGS)" DISTCHECK_FLAGS="$(DISTCHECK_LIGHT_MAN_FLAGS)" distcheck # Make a distcheck (and check in particular) with enabled valgrind and debug info memcheck distcheck-valgrind: - +$(MAKE) $(AM_MAKEFLAGS) DISTCHECK_FLAGS="$(DISTCHECK_VALGRIND_FLAGS)" distcheck + @echo "See also scripts/valgrind in NUT sources for a helper tool" + +prefix='$${prefix}'; $(MAKE) $(AM_MAKEFLAGS) DISTCHECK_CONFIGURE_FLAGS="$(DISTCHECK_CONFIGURE_FLAGS)" DISTCHECK_FLAGS="$(DISTCHECK_VALGRIND_FLAGS)" distcheck # workaround the dist generated files that are also part of the distribution # Note that distcleancheck is disabled for now, while waiting for a proper @@ -92,7 +111,7 @@ distcleancheck: realclean: maintainer-clean # Files made by our targets: -CLEANFILES = *-spellchecked *.adoc-parsed cppcheck*.xml +CLEANFILES = *-spellchecked *.adoc-parsed cppcheck*.xml config.log.inplace-outer DISTCLEANFILES = ChangeLog # Most of the files generated by custom rules in the configure script @@ -141,7 +160,7 @@ distclean-local: spellcheck spellcheck-interactive: +@RES=0; \ (cd $(builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) -s $(abs_top_builddir)/docs/.prep-src-docs) || RES=$$? ; \ - (cd $(builddir)/docs/man && $(MAKE) $(AM_MAKEFLAGS) -s $(abs_top_builddir)/docs/.prep-src-docs) || RES=$$? ; \ + (cd $(builddir)/docs/man && $(MAKE) $(AM_MAKEFLAGS) -s $(abs_top_builddir)/docs/man/.prep-src-docs) || RES=$$? ; \ (cd $(builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) -s $@) || RES=$$? ; \ (cd $(builddir)/docs/man && $(MAKE) $(AM_MAKEFLAGS) -s $@) || RES=$$? ; \ (cd $(builddir)/conf && $(MAKE) $(AM_MAKEFLAGS) -s $@) || RES=$$? ; \ @@ -165,13 +184,15 @@ spellcheck spellcheck-interactive: # such as PDF generators, so it should only be called at developer's # discretion, choice and risk. The "check-man" targets covers source # texts, man pages and HTML rendering of man pages, as enabled by tools. -doc spellcheck-sortdict \ +doc spellcheck-sortdict spellcheck-report-dict-usage \ all-docs check-docs \ -man all-man man-man check-man man-html all-html: - +cd $(builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) $@ +man all-man man-man check-man html-man all-html: + +cd $(abs_top_builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) -s $(abs_top_builddir)/docs/.prep-src-docs + +cd $(abs_top_builddir)/docs/man && $(MAKE) $(AM_MAKEFLAGS) -s $(abs_top_builddir)/docs/man/.prep-src-docs + +cd $(abs_top_builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) $@ INSTALL.nut UPGRADING NEWS README: - +cd $(builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) ../$(@F).adoc-parsed && cp -f ../$(@F).adoc-parsed ../$(@F) + +cd $(abs_top_builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) ../$(@F).adoc-parsed && cp -f ../$(@F).adoc-parsed ../$(@F) # Workarounds for https://github.com/github/markup/issues/1095 # require direct definition of our attributes in each source @@ -226,9 +247,29 @@ maintainer-asciidocs: fi; \ ) -check-NIT check-NIT-devel: +check-NIT check-NIT-devel check-NIT-sandbox check-NIT-sandbox-devel: +cd $(builddir)/tests/NIT && $(MAKE) $(AM_MAKEFLAGS) $@ +VERSION_DEFAULT: dummy-stamp + @abs_top_srcdir='$(abs_top_srcdir)' ; \ + abs_top_builddir='$(abs_top_builddir)' ; \ + export abs_top_srcdir ; export abs_top_builddir ; \ + NUT_VERSION_QUERY=UPDATE_FILE '$(abs_top_srcdir)/tools/gitlog2version.sh' + +CLEANFILES += VERSION_DEFAULT.tmp +EXTRA_DIST += VERSION_DEFAULT + +# Best-effort delivery for (overly?) customized distros, e.g. via +# echo NUT_VERSION_FORCED_SEMVER=1.1.1 > VERSION_FORCED_SEMVER +dist-hook: + for D in "$(abs_top_srcdir)" "$(abs_top_builddir)" ; do \ + for F in VERSION_FORCED VERSION_FORCED_SEMVER ; do \ + if [ -s "$$D/$$F" ] ; then \ + cat "$$D/$$F" > "$(top_distdir)/$$F" || true ; \ + fi ; \ + done ; \ + done + # This target adds syntax-checking for committed shell script files, # to avoid surprises and delays in finding fatal typos after packaging ### @@ -243,7 +284,7 @@ check-NIT check-NIT-devel: ### check-scripts-syntax: @echo 'NOTE: modern bash complains about scripts using backticks (warning not error), which we ignore in NUT codebase for portability reasons: `...` obsolete, use $$(...)' - @RUNBASH=bash; if [ -x /bin/bash ]; then RUNBASH=/bin/bash ; else if [ -x /usr/bin/env ] ; then RUNBASH="/usr/bin/env bash"; fi; fi ; \ + @RUNBASH=bash; if [ -x /bin/bash ] && /bin/bash -c 'echo $${BASH_VERSION}' | grep -E '^[456789]\.' ; then RUNBASH=/bin/bash ; else if [ -x /usr/bin/env ] ; then RUNBASH="/usr/bin/env bash"; fi; fi ; \ for F in `git ls-files || find . -type f` ; do \ case "`file "$$F"`" in \ *"Bourne-Again shell script"*) ( set -x ; $$RUNBASH -n "$$F" ; ) ;; \ @@ -321,14 +362,62 @@ GITLOG_START_POINT=v2.6.0 # the current dir, and defaults to generate a "ChangeLog" in the current dir. # The script itself is generated from a template, so resides in builddir. dummy-stamp: -ChangeLog: tools/gitlog2changelog.py dummy-stamp - cd $(abs_top_srcdir) && \ +ChangeLog: dummy-stamp + +@$(MAKE) $(AM_MAKEFLAGS) $(abs_top_builddir)/ChangeLog + +if WITH_PDF_NONASCII_TITLES +WITH_PDF_NONASCII_TITLES_ENVVAR = WITH_PDF_NONASCII_TITLES=yes +else +WITH_PDF_NONASCII_TITLES_ENVVAR = WITH_PDF_NONASCII_TITLES=no +endif + +# Be sure to not confuse with a DIST'ed file (and so try to overwrite it); +# do however avoid re-generating it if already made on a previous pass and +# the Git HEAD pointer (branch) or its actual "index" or "object" database +# did not change since then - meaning the local developer or CI did not +# modify the metadata (subsequent generation of the huge PDF/HTML files +# can cost dearly). +# Note there's a bit more fuss about Git internals which NUT should not +# really care about encapsulation-wise (detection of NUT_GITDIR location +# which may reside elsewhere, e.g. with local repo clones with reference +# repo configuration, or submodules). But this is a Git-crawling target +# anyway, and in the worst case (Git's design changes) we would spend a +# bit of time researching the FS in vain, and go on to re-generate the +# ChangeLog when maybe we should not have - oh well. +# WARNING: The CHANGELOG_REQUIRE_GROUP_BY_DATE_AUTHOR=true mode here is +# default to allow for prettier documentation, but it can require too much +# memory for weaker build systems. Set it to false when calling make there. +CHANGELOG_REQUIRE_GROUP_BY_DATE_AUTHOR_ENVVAR = true +$(abs_top_builddir)/ChangeLog: tools/gitlog2changelog.py dummy-stamp + @cd $(abs_top_srcdir) && \ if test -e .git ; then \ - CHANGELOG_FILE="$@" $(abs_top_builddir)/tools/gitlog2changelog.py $(GITLOG_START_POINT) || \ - { printf "gitlog2changelog.py failed to generate the ChangeLog.\n\nNOTE: See https://github.com/networkupstools/nut/commits/master for change history.\n\n" > "$@" ; } ; \ + NUT_GITDIR=".git" ; if test -r "$${NUT_GITDIR}" -a ! -d "$${NUT_GITDIR}" ; then GD="`grep -E '^gitdir:' "$${NUT_GITDIR}" | sed 's/^gitdir: *//'`" && test -n "$$GD" -a -d "$$GD" && NUT_GITDIR="$$GD" ; fi ; \ + if test -s "$@" -a -d "$${NUT_GITDIR}" && test -z "`find "$${NUT_GITDIR}" -newer "$@" 2>/dev/null`" ; then \ + echo " DOC-CHANGELOG-GENERATE $@ : SKIP (keep existing)" ; \ + echo "Using still-valid ChangeLog file generated earlier from same revision of Git source metadata in '$${NUT_GITDIR}'" >&2 ; \ + else \ + echo " DOC-CHANGELOG-GENERATE $@" ; \ + CHANGELOG_FILE="$@" $(WITH_PDF_NONASCII_TITLES_ENVVAR) \ + CHANGELOG_REQUIRE_GROUP_BY_DATE_AUTHOR="$(CHANGELOG_REQUIRE_GROUP_BY_DATE_AUTHOR_ENVVAR)" \ + $(abs_top_builddir)/tools/gitlog2changelog.py $(GITLOG_START_POINT) || { \ + echo " DOC-CHANGELOG-GENERATE $@ : FAILED (non-fatal)" >&2 ; \ + printf "gitlog2changelog.py failed to generate the ChangeLog.\n\nNOTE: See https://github.com/networkupstools/nut/commits/master for change history.\n\n" > "$@" ; \ + } ; \ + fi ; \ else \ - if ! test -s "$@" ; then \ - printf "Failed to generate the ChangeLog.\n\nNOTE: See https://github.com/networkupstools/nut/commits/master for change history.\n\n" > "$@" ; \ + if test x"$(abs_top_srcdir)" != x"$(abs_top_builddir)" -a -s ./ChangeLog ; then \ + echo " DOC-CHANGELOG-GENERATE $@ : SKIP (keep existing)" ; \ + echo "Using distributed ChangeLog file from sources" >&2 ; \ + rm -f "$@" || true ; \ + cat ./ChangeLog > "$@" ; \ + else \ + if test -s "$@" ; then \ + echo " DOC-CHANGELOG-GENERATE $@ : SKIP (keep existing)" ; \ + echo "Using distributed ChangeLog file from sources" >&2 ; \ + else \ + echo " DOC-CHANGELOG-GENERATE $@ : FAILED (non-fatal)" >&2 ; \ + printf "Failed to generate the ChangeLog.\n\nNOTE: See https://github.com/networkupstools/nut/commits/master for change history.\n\n" > "$@" ; \ + fi ; \ fi ; \ fi @@ -393,7 +482,7 @@ install-dirs: @echo "Warning: 'make install-dirs' is deprecated." @echo "Use 'make installdirs' instead." @echo $(WARN) - +$(MAKE) installdirs + +$(MAKE) $(AM_MAKEFLAGS) installdirs cgi build-cgi install-cgi install-cgi-dir install-cgi-bin \ install-cgi-man install-cgi-conf install-cgi-html: @echo "Error: 'make $@' no longer exists." @@ -460,7 +549,8 @@ if HAVE_WINDOWS # Then hardlink libraries for sbin... (alternative: all bins in one dir) # TOTHINK: Are there more dirs to consider? So far we cover bindir, sbindir and # driverexecdir (e.g. some Linux distros place drivers to /lib/nut while tools -# and daemons are in /usr/bin and /usr/sbin), and cgiexecdir; anything else?.. +# and daemons are in /usr/bin and /usr/sbin), and cgiexecdir, and occasional +# helpers like "sockdebug.exe" in libexecdir; anything else?.. # Note we hold existance of cgiexecdir as optional, but the name is expected to # be defined. Other dirs are "just assumed" to exist (that we are not packaging # some NUT build without drivers/tools/daemons). Subject to change if needed. @@ -486,7 +576,7 @@ install-win-bundle-thirdparty: ( cd '$(DESTDIR)' || exit ; \ DESTDIR="" '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->bin $$D" 2>&1 ; \ + echo " DLL->bin $$D" 2>&1 ; \ cp -pf "$$D" './$(bindir)/' ; \ done ; \ ) || exit ; \ @@ -494,7 +584,7 @@ install-win-bundle-thirdparty: cd '$(DESTDIR)/$(sbindir)' || exit ; \ '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->sbin $$D" 2>&1 ; \ + echo " DLL->sbin $$D" 2>&1 ; \ ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ done ; \ ) || exit ; \ @@ -503,7 +593,7 @@ install-win-bundle-thirdparty: cd '$(DESTDIR)/$(driverexecdir)' || exit ; \ '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->drv $$D" 2>&1 ; \ + echo " DLL->drv $$D" 2>&1 ; \ ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ done ; \ ) || exit ; \ @@ -514,7 +604,18 @@ install-win-bundle-thirdparty: cd '$(DESTDIR)/$(cgiexecdir)' || exit ; \ '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ - echo " DLL->cgi $$D" 2>&1 ; \ + echo " DLL->cgi $$D" 2>&1 ; \ + ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ + done ; \ + ) || exit ; \ + ( if test x"$(libexecdir)" = x"$(bindir)" ; then exit 0 ; fi ; \ + if test x"$(libexecdir)" = x"$(sbindir)" ; then exit 0 ; fi ; \ + if test x"$(libexecdir)" = x"$(driverexecdir)" ; then exit 0 ; fi ; \ + if test x"$(libexecdir)" = x"$(cgiexecdir)" ; then exit 0 ; fi ; \ + cd '$(DESTDIR)/$(libexecdir)' || exit ; \ + '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ + | while read D ; do \ + echo " DLL->libexec $$D" 2>&1 ; \ ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ done ; \ ) || exit @@ -523,9 +624,10 @@ install-win-bundle-thirdparty: relsbindir="`echo './$(sbindir)/' | sed 's,//*,/,g'`" ; \ reldriverexecdir="`echo './$(driverexecdir)/' | sed 's,//*,/,g'`" ; \ relcgiexecdir="`echo './$(cgiexecdir)/' | sed 's,//*,/,g'`" ; \ + rellibexecdir="`echo './$(libexecdir)/' | sed 's,//*,/,g'`" ; \ cd '$(DESTDIR)' || exit ; \ find . -type f | grep -Ei '\.(exe|dll)$$' \ - | grep -vE "^($${relbindir}|$${relsbindir}|$${reldriverexecdir}|$${relcgiexecdir})" \ + | grep -vE "^($${relbindir}|$${relsbindir}|$${reldriverexecdir}|$${relcgiexecdir}|$${rellibexecdir})" \ | ( RES=0 ; while IFS= read LINE ; do echo "$$LINE" ; RES=1; done; exit $$RES ) else diff --git a/NEWS.adoc b/NEWS.adoc index 10319c9cd2..f09a5c36c8 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -40,8 +40,337 @@ https://github.com/networkupstools/nut/milestone/11 - (expected) Bug fixes for fallout possible due to "fightwarn" effort in 2.8.0 -PLANNED: Release notes for NUT 2.8.2 - what's new since 2.8.1 -------------------------------------------------------------- + - Fix fallout of development in NUT v2.8.0 and/or v2.8.1 and/or v2.8.2: + * Move of `NUT_DEBUG_LEVEL` and "-D" CLI option handling to start of + driver programs for issue #2259 in NUT v2.8.2 release misfired with + regard to data-dump mode (it no longer caused foreground by default). + [#2408] + * The `nut-driver-enumerator.sh` improvements misfired in v2.8.2 release + with an overlooked bit of shell syntax, and caused `nut-driver@upsname` + instances to not auto-restart when `ups.conf` is edited. [#682, #2410] + * Addition of "NUT Simulated devices" support to `nut-scanner` in v2.8.2 + broke detection of (in-)ability to find and query "Old NUT" servers via + `libupsclient.so` (internal flag got always enabled). [#2246] + * A fix for `upsmon` v2.8.1 setting of `OFFDURATION` [PR #2108, issue #2104, + revisiting PR #2055, issue #2044] was overly zealous and impacted also + the `OB` state in cases where communications to the data server were + severed and `DEADTIME` setting was not honored. [PR #2462, issue #2454] + * Using `drivername -c reload` (e.g. facilitated by `nut-driver-enumerator` + script and service when editing `ups.conf`) led to disconnected Unix + sockets and a tight polling loop that hogged CPU. While the underlying + bug is ancient, it took recent development to hit it as a practical + regression. [issue #1904, issue #2484] + * Fallback `localtime_r()` and `gmtime_r()` for some platform builds where + a `*_s()` variant was available was not handled correctly. [PR #2583] + * A recently introduced `allow_killpower` did not actually work as an + `ups.conf` flag (only as a protocol command). [issue #2605, PR #2606] + + - development iterations of NUT should now identify with not only the semantic + version of a preceding release, but with git-derived information about the + amount of iterations that followed (if available): the three-number "semver" + would be seen on release snapshots, while other builds would expose the + added components: one with the amount of commits on the main development + trunk since the preceding release which are ancestors of the built code + base, and in case of feature development branches -- another component + with the amount of commits unique to this branch (which are not part of + the development trunk). This allows to produce more relevant (monotonously + growing) version identifiers for packages and similar artifacts, with more + meaningful upgrades via development snapshots, eventually. A copy of the + current version information would be embedded into "dist" archives as a + `VERSION_DEFAULT` file. [#1949] + + - drivers, `upsd`, `upsmon`: reduce "scary noise" about failure to `fopen()` + the PID file (which most of the time means that no previous instance of + the daemon was running to potentially conflict with), especially useless + since in recent NUT releases the verdicts from `sendsignal*()` methods + are analyzed and lead to layman worded situation reports in these programs. + [issue #1782, PR #2384] + + - drivers started with the `-FF` command-line option (e.g. wrapped into the + systemd units to stay "foregrounded" *and* save a PID file anyway) should + now also handle an existing PID file to interact with the earlier instance + of the driver program, if still running (e.g. started manually). [#2384] + + - Extended instant commands for driver reloading with a `driver.exit` + command for a protocol equivalent of sending a `SIGTERM`, e.g. when + a newer instance of the driver program tries to start. [#1903, #2392] + + - A new `NUT_QUIET_INIT_BANNER` envvar (presence or "true" value) can now + prevent the tool name and NUT version banner from being unilaterally + printed out when NUT programs start. [issues #1789 vs. #316; #2573] + + - The `upsdrvctl` should now warn if executed on systems where NUT was + built with support for service management frameworks like systemd or SMF, + so nut-driver service units prepared by `nut-driver-enumerator` would + conflict with manually-executed driver programs. This warning can be + hushed by exporting a `NUT_QUIET_INIT_NDE_WARNING` environment variable + with any value. + + - Extended `upsdrvctl` with a `list` operation (or `-l` option) to report + manageable device configuration names (possible `` arguments to + `start`, `stop` etc. operations), or to confirm a single name that it + is known, and a `status` operation for more information. [#2567] + + - riello_ser updates: + * added `localcalculation` option to compute `battery.runtime` and + `battery.charge` if the device provides bogus values [issue #2390, + following in the footsteps of #1692, #1685 done for `riello_usb`] + (similar to `runtimecal` in some other drivers, may be refactored + to that configuration and logic model in later NUT releases) + + - apcsmart updates: + * Revised code to use `strncpy()` and avoid potential overflows that are + possible with `strcpy()` used before. [PR #2564] + * Lost communications led to a logging flood, should not anymore. + In fact, the driver should try fully reconnecting upon getting into + a prolonged data stale condition. [issue #704, PR #2564] + + - added Visench C1K (using serial port converter with USB ID `1a86:7523`) + as known supported by `nutdrv_qx` (Megatec protocol) since at least + NUT v2.7.4 release [#2395] + + - bicker_ser: added new driver for Bicker 12/24Vdc UPS via RS-232 serial + communication protocol, which supports any UPS shipped with the PSZ-1053 + extension module. [PR #2448] + + - usbhid-ups updates: + * Support of the `onlinedischarge_log_throttle_hovercharge` in the NUT + v2.8.2 release was found to be incomplete. [#2423, follow-up to #2215] + * Added support for `lbrb_log_delay_sec=N` setting to delay propagation of + `LB` or `LB+RB` state (buggy with APC BXnnnnMI devices circa 2023-2024). + This may work better with flags like `onlinedischarge_calibration` and + `lbrb_log_delay_without_calibrating` for some devices. [#2347] + * General suggestion from `possibly_supported()` message method for devices + with VendorID=`0x06da` (Phoenixtec), seen in some models supported by + MGE HID or Liebert HID, updated to suggest trying `nutdrv_qx`. [#334] + * MGE HID list of `mge_model_names[]` was extended for Eaton 9E, 5PX and 5SC + series (largely guessing, feedback and PRs for adaptation to actual + string values reported by devices via USB are welcome), so these devices + would now report `battery.voltage` and `battery.voltage.nominal`. [#2380] + * `powercom-hid` subdriver sent UPS shutdown commands in wrong byte order, + at least for devices currently in the field. A toggle was added to set + the old behavior (if some devices do need it), while a fix is applied + by default: `powercom_sdcmd_byte_order_fallback`. [PR #2480] + * `cps-hid` subdriver now supports more variables, as available on e.g. + CP1350EPFCLCD model. [PR #2540] + * USB parameters (per `usb_communication_subdriver_t`) are now set back to + their default values during enumeration after probing each subdriver. + Having an unrelated device connected with a VID:PID matching the + `arduino-hid` subdriver prevented use of an actual `usb-hid` device due to + changes made to this struct during probe. [#2611] + + - USB drivers could log `(nut_)libusb_get_string: Success` due to either + reading an empty string or getting a success code `0` from libusb. + This difference should now be better logged, and not into syslog. [#2399] + + - USB drivers can benefit from a new `nut_usb_get_string()` method which + can do a fallback `en_US` query for devices which report a broken "langid" + language identifier value. This notably manifested in inability to query + device Manufacturer, Model and Serial Number values with some buggy device + firmware or hardware. [PR #2604, issues #1925, #414] + * Currently this was tested to fix certain device discovery with `usbhid-ups`; + should also apply out of the box to same discovery logic in `blazer_usb`, + `nutdrv_qx`, `riello_usb` and `tripplite_usb` drivers. + * Also applied to `nut-scanner` and `libnutscan`. [issue #2615] + * More work may be needed for other USB-capable drivers (`richcomm_usb`, + `nutdrv_atcl_usb`) and for general code to collect string readings and + other data points, and to configure the fallback locale or choose one + if several are served by the device. [issues #2613, #2614, #2615] + + - USB drivers should now be more likely to succeed with iterative detection + of an UPS interface on a composite USB device or when looking at devices + with non-default interface/endpoint/config numbers. [PR #2611] + + - Introduced a new driver concept for interaction with OS-reported hardware + monitoring readings. Currently instantiated as `hwmon_ina219` specifically + made for Texas Instruments INA219 chip as exposed in the Linux "hwmon" + subsystem of its "sysfs" interface (and talking I2C under the hood), this + approach seems to have good potential to expand into covering more devices + and perhaps platforms. [#2430] + + - upsmon: + * it was realized that the `POWERDOWNFLAG` must be explicitly set in the + configuration file, there is no built-in default in the binary program + (the settings facilitated by the `configure` script during build "only" + impact the `upsmon.conf.sample`, init-scripts and similar files generated + from templates). [issue #321, PR #2383] + * added an `OBLBDURATION` (seconds) setting to optionally delay raising + the alarm for immediate shutdown in critical situation. [#321] + + - More systemd integration: + * Introduced a `nut-sleep.service` unit which stops `nut.target` when a + system sleep was requested, and starts it when the sleep is finished. + This helps avoid NUT shutting down a woken-up system just because its + power state was critical before the sleep (called as a `SHUTDOWNCMD` + implementation by the end-user), and a next-read timestamp was not seen + (deemed to be a stale UPS, meaning lost communications during critical + state, so must go down ASAP). While not as elegant as native systemd + "inhibitor interface" support, this approach does work. [#1833, #1070] + * Introduced support for the "inhibitor interface" as well (should be + available on systems with systemd version 183 or newer) for a better + handling of the time jump specifically in the `upsmon` client via new + `Inhibit()` method in `common.c`. [#1070] + * As an extension of the logic introduced above, hopefully now `upsmon` + would behave better in face of any significant and unexpected clock + jumps (on POSIX builds so far), even if they are not suspend/hibernate + events (or they were but we could not have an inhibit lock). Now they + should be handled similar (avoid stale UPS data and rash decisions) + for summer/winter time change on non-UTC deployments, a debugger + suspending the `upsmon` process, etc. [#2597] + + - gamatronic driver revised for safer memory operations; this was reported + to have fixed a Segmentation Fault seen in earlier NUT releases with + some of the devices supported by this driver. [#2427] + + - upsd: + * `upsd_cleanup()` is now traced, to more easily see that the daemon is + exiting (and/or start-up has aborted due to configuration or run-time + issues). Warning about "world readable" files clarified. [#2417] + + - nut-scanner: + * the tool relies on dynamic loading of shared objects (library files) + orchestrated at run-time rather than pre-compiled, to avoid excessively + huge package footprints. This however relies on knowing (or sufficiently + safely guessing) the library file names to use, and short `libname.so` + is not ubiquitously available. With the new `m4` macro `AX_REALPATH_LIB` + we can store and try to use the file name which was present on the build + system, while we search for a suitable library. [#2431] ++ +NOTE: A different but functionally equivalent trick is done for `libupsclient` +during a NUT build. + * fixed support for IPv6 addresses (passed in square brackets) for both + `-s` start/`-e` end command-line options, and for `-m cidr/mask` option. + [issue #2512, PR #2518] + * newly added support to scan several IP addresses (single or ranges) + with the same call, by repeating command-line options; also `-m auto{,4,6}` + can be specified (once) to select IP (all, IPv4, IPv6) address ranges of + configured local network interfaces. + An `/ADDRLEN` suffix can be added to the option, to filter out discovered + subnets with too many bits available for the host address part (avoiding + millions of scans in the extreme cases). + [issue #2244, issue #2511, PR #2509, PR #2513, PR #2517] + * implemented parallel scanning for IPMI bus, otherwise default scan for + all supported buses with `-m auto` takes unbearably long. [#2523] + * bumped version of `libnutscan` to 2.6.0, it now includes a few more + methods and symbols from `libcommon`. [issue #2244, PR #2509] + * do not actively suggest `vendor(id)`, `product(id)`, and `serial` options + for `bcmxcp_usb`, `richcomm_usb` and `nutdrv_atcl_usb` drivers for now + [#1763, #1764, #1768, #2580] + + - common code: + * introduced a `NUT_DEBUG_SYSLOG` environment variable to tweak activation + of syslog message emission (and related detachment of `stderr` when + backgrounding), primarily useful for NIT and perhaps systemd. Most + methods relied on logging bits being set, so this change aims to be + minimally invasive to impact setting of those bits (or not) in the + first place. [#2394] + * `root`-owned daemons now use not the hard-coded `PIDPATH` value set + by the `configure` script during build, but can override it with a + `NUT_PIDPATH` environment variable in certain use-cases (such as + tests). [#2407] + * introduced a check for daemons working with PID files to double-check + that if they can resolve the program name of a running process with + this identifier, that such name matches the current program (avoid + failures to start NUT daemons if PID files are on persistent storage, + and some unrelated program got that PID after a reboot). This might + introduce regressions for heavily customized NUT builds (e.g. those + embedded in NAS or similar devices) where binary file names differ + significantly from a `progname` string defined in the respective NUT + source file, so a boolean `NUT_IGNORE_CHECKPROCNAME` environment + variable support was added to optionally disable this verification. + Also the NUT daemons should request to double-check against their + run-time process name (if it can be detected). [issue #2463] + * introduced `m4` macros to check during `configure` phase for the + platform, and a `nut_bool.h` header with `nut_bool_t` type to use + during build, to avoid the numerous definitions of Boolean types + and values (or macros) in the NUT codebase. [issue #1176, issue #31] + * custom `distcheck-something` targets did not inherit `DISTCHECK_FLAGS` + properly. [#2541] + * added `status_get()` in NUT driver state API, to check if a status + token string had been set recently, and to avoid duplicate settings. + [PR #2565] + * local socket/pipe protocol introduced a `LOGOUT` command for cleaner + disconnection handling. [#2572] + * codebase adapted to the liking of `clang-18` and newer revisions of + `gcc-13`+ whose static analyzers on NUT CI farm complained about some + imperfections after adding newer OS revisions to the population of + build agents. [#2585, #2588] + + - updated `docs/nut-names.txt` with items defined by 42ITy NUT fork. [#2339] + + - various recipe, documentation and source files were revised to address + respective warnings issued by the new generations of analysis tools. + [#823, #2437, link:https://github.com/networkupstools/nut-website/issues/52[nut-website issue #52]] + + - added `scripts/valgrind` with a helper script and suppression file to + ignore common third-party problems. [#2511] + + - revised `nut.exe` (the NUT for Windows wrapper for all-in-one service) + to be more helpful with command-line use (report that it failed to start + as a service, have a help message, pass debug verbosity to launched NUT + programs...) and add a man page for it. [issue #2432, PR #2446] + + - the `PyNUTClient` module should no longer rely on presence of a `telnetlib` + module in the build or execution environment (deprecated in Python 3.11, + removed since Python 3.13). [#2183] + + - enabled installation of built single-file PDF and HTML (including man page + renditions) under the configured `docdir`. It seems previously they were + only built (if requested) but not installed via `make`, unlike the common + man pages which are delivered automatically. [#2445] ++ + NOTE: The `html-chunked` documents are currently still not installed. + + - added support to `./configure --with-doc=man=dist-auto` to use distributed + manual page files if present; only fall back to build them if we can. [#2473] + + - added a `make distcheck-light-man` recipe to require verification that the + manual page files can be built using the prepared "tarball" archive. [#2473] + + - revised the documentation building recipes, with the goal to avoid building + the `ChangeLog` products and their intermediate files more than once (but + still react to `git` metadata changes during development), and to sanity + check the resulting final document (currently only for `html-single` mode). + As part of this, the `CHANGELOG_REQUIRE_GROUP_BY_DATE_AUTHOR` setting was + added (for `make` calls and used by `tools/gitlog2changelog.py.in` script), + and it defaults to `true` allowing for better ordered documents at the cost + of some memory during document generation. [#2510] + + - added a `common/Makefile.am` build product for a new internal library + `libcommonstr.la` which allows a smaller selection of helper methods + for tools like `nut-scanner` which do not need the full `libcommon.la` + nor `libcommonclient.la`. [#2478, #2491] + + - added a `drivers/Makefile.am` build product for a new internal library + `libserial-nutscan.la` to simplify `tools/nut-scanner/Makefile.am` recipes. + [#2490] + + - build of `snmp-ups` and `netxml-ups` drivers now explicitly brings linker + dependency on chosen SSL libraries. [#2479] + + - brought keyword dictionaries of `nutconf` and `augeas` NUT configuration + file parsers up to date; restored automated checks for `augeas` lenses. + [issue #657, issue #2294] ++ + NOTE: Some known issues remain with augeas lens definitions so currently + they should be able to parse common simple use-cases but not certain types + of more complex configurations (e.g. some line patterns that involve too + many double-quote characters) which are valid for NUT proper. [#657] + + - Cross-builds using only a host implementation of `pkg-config` program + should now ignore host `*.pc` files and avoid confusion. + + - NUT CI farm build recipes, documentation and some `m4`/`configure.ac` + sources updated to handle a much larger build scope on MacOS. Also + migrated the builders to Apple Silicon from x86 (deprecated by CircleCI). + [#2502] + + - Introduced a simple experiment to expose NUT client readings as filesystem + objects via FUSE, in `scripts/fuse/execfuse-nut` now. [#2591] + + +Release notes for NUT 2.8.2 - what's new since 2.8.1 +---------------------------------------------------- https://github.com/networkupstools/nut/milestone/10 @@ -87,6 +416,10 @@ https://github.com/networkupstools/nut/milestone/10 `configure` script option `--with-debuginfo`. Note that default autoconf behavior usually embeds moderate optimizations and debug information on its own. [PR #2310] + * A fix applied among clean-ups between NUT v2.7.4 and v2.8.0 releases + backfired for `usbhid-ups` subdriver `belkin-hid` which in practice + relied on the broken older behavior; more details in its entry below. + [PR #2371] - nut-usbinfo.pl, nut-scanner and libnutscan: * Library API version for `libnutscan` was bumped from 2.2.0 to 2.5.0 @@ -146,6 +479,13 @@ https://github.com/networkupstools/nut/milestone/10 * Driver programs with debug tracing support via `-D` CLI option and/or the `NUT_DEBUG_LEVEL` environment variable now check those earlier in their life-time, so that initialization routine can be debugged. [#2259] + * Multiple USB-capable drivers got options to customize `usb_config_index` + `usb_hid_rep_index`, `usb_hid_desc_index`, `usb_hid_ep_in` and + `usb_hid_ep_out` hardware connection settings via `ups.conf` options. + This is treated as experimental, not all code paths may be actually + using such values from `struct usb_communication_subdriver_t` rather + than hard-coded defaults. Discovery of correct values is up to the + user at the moment (using `lsusb`, internet search, luck...) [#2149] - nut-driver-enumerator (NDE) service/script: * The optional daemon mode (primarily useful for systems which monitor @@ -173,6 +513,11 @@ https://github.com/networkupstools/nut/milestone/10 useful as an UPS driver, not just a controller developer sandbox. [#2188] * `cps-hid` subdriver now supports devices branded as Cyber Energy and built by cooperation with Cyber Power Systems. [#2312] + * `belkin-hid` subdriver now supports Liebert PSI5 devices which have a + different numeric reading scale than earlier handled models. [issue #2271, + PR #2272, PR #2369] Generally the wrong-scale processing was addressed, + including a regression in NUT v2.8.0 which led to zero values + in voltage data points which NUT v2.7.4 reported well [#2371] * The `onlinedischarge` configuration flag name was too ambiguous and got deprecated (will be supported but no longer promoted by documentation), introducing `onlinedischarge_onbattery` as the meaningful alias. [#2213] @@ -187,16 +532,25 @@ https://github.com/networkupstools/nut/milestone/10 is not reported by the device (should be frequent by default, in case the UPS-reported state combination does reflect a bad power condition). + - nutdrv_qx driver: + * Fixed handling of `battery_voltage_reports_one_pack` configuration flag + introduced in NUT v2.8.1. [originally by PR #1279; fixed by PR #2324, + issue #2325] + - Various code and documentation fixes for NSS crypto support. [#2274, #2268] - Laid foundations for the SmartNUT effort (aiming to integrate drivers with some other backends than the networked NUT data server process). - - Eaton contributed recipes and scripts used to create the IPP for Unix bundle - (UPS software companion), delivering NUT packages with an interactive + - Eaton contributed recipes and scripts used to create the IPP for Unix + bundle (aka Eaton IPSS Unix or UPP), a freely available value-added + packaging of NUT distributed as the UPS software companion for OSes + where their more complex UPS monitoring/management tools had not been + ported. This allows for delivery of NUT packages with an interactive installer and some system integration scripts (events, notifications, - status, shutdown daemon...) -- provided "as is" at the moment, and may - later serve as foundation or inspiration for new NUT features. [#2288] + status, shutdown daemon...), and was contributed to the NUT upstream + project by Eaton -- provided "as is" at the moment, and may later serve + as foundation or inspiration for new NUT features. [#2288] - nutconf (C++ library and tool to read and manage NUT configuration files) was started in the open by Eaton employees and used in the IPP installer, @@ -205,6 +559,12 @@ https://github.com/networkupstools/nut/milestone/10 known deficiencies in Windows platform support, as well as some un-awareness about configuration key words which appeared in NUT since 2013. [#2290] + - The `tools/gitlog2changelog.py.in` script was revised, in particular to + convert section titles (with contributor names coming from Git metadata) + into plain ASCII character set, for `dblatex` versions which do not allow + diacritics and other kinds of non-trivial characters in sections. This can + cause successful builds of `ChangeLog.pdf` file on more platforms, but at + expense of a semi-cosmetic difference in those names. [PR #2360, PR #2366] Release notes for NUT 2.8.1 - what's new since 2.8.0 ---------------------------------------------------- @@ -352,6 +712,12 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. * added `onlinedischarge_calibration` option for UPSes that report `OL+DISCHRG` when they are in calibration mode [#2104] + - riello_usb updates: + * added `localcalculation` option to compute `battery.runtime` and + `battery.charge` if the device provides bogus values [#1692, #1685] + (similar to `runtimecal` in some other drivers, may be refactored + to that configuration and logic model in later NUT releases) + - powercom driver should now try harder to refresh data from device [#356] - tripplite_usb driver now supports configuration of `upsid` to match the @@ -1424,6 +1790,8 @@ NOTE: There was no public NUT 2.7.0 release. - usbhid-ups: final fix for APC Back UPS ES. APC Back UPS ES devices have buggy firmware, and this version does not cause a regression. The max_report variable should be set automatically based on the USB identification values. + * UPDATE: known as `maxreport` flag for `usbhid-ups` driver, and as a + `max_report_size` setting in code, as of NUT v2.8.2 release. - nut-scanner: fix crash @@ -1484,6 +1852,7 @@ Release notes for NUT 2.6.5 - what's new since 2.6.4 - mge-shut driver has been replaced by a new implementation (newmge-shut). In case of issue with this new version, users can revert to oldmge-shut. + UPDATE: oldmge-shut was dropped between 2.7.4 and 2.8.0 releases. - First NUT virtualization package: NUT now supports integration with VMware ESXI 5.0, through a native VIB package. This is, for the time diff --git a/README.adoc b/README.adoc index 2121366665..9bbbca12d7 100644 --- a/README.adoc +++ b/README.adoc @@ -217,6 +217,10 @@ which the larger potential sponsors consider when choosing how to help FOSS projects. Keeping the lights shining in such a large non-regression build matrix is a big undertaking! +ifndef::pdf_format[] +image:https://api.star-history.com/svg?repos=networkupstools/nut&type=Date[link="https://star-history.com/#networkupstools/nut&Date" alt="NUT GitHub Star History Chart"] +endif::pdf_format[] + See <> for an overview of the shared effort. ===== @@ -825,8 +829,9 @@ endif::env-github[] preview tarballs with binaries). | image:images/ci/DO_Powered_by_Badge_blue_140pxW.png[alt="DigitalOcean logo",width="140",height="29",link="https://www.digitalocean.com/?refcode=d2fbf2b9e082&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge"] -| The link:https://www.digitalocean.com/?refcode=d2fbf2b9e082&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge[DigitalOcean] droplets allow us to host - NUT CI farm build agents, and eventually re-house the Jenkins controller too. +| The link:https://www.digitalocean.com/?refcode=d2fbf2b9e082&utm_campaign=Referral_Invite&utm_medium=Referral_Program&utm_source=badge[DigitalOcean] + droplets allow us to host NUT CI farm Jenkins controller and the build agents + for multiple operating systems. | image:images/ci/gandi-ar21.png[alt="Gandi.Net logo",width="120",height="60",link="https://www.gandi.net/"] | link:https://www.gandi.net/[Gandi.Net] took up the costs of NUT DNS hosting. diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 2ede05ee5a..98c4e492fc 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -26,6 +26,110 @@ Changes from 2.8.2 to 2.8.3 - PLANNED: Keep track of any further API clean-up? +- NUT development snapshots can now have more version components than the + standard semantic versioning triplet, optionally adding the amount of + commits on the development trunk since previous release, and the amount + of commits on a feature branch that are unique to it. Release artifacts + that have zeroes in both positions would have them stripped and still + have the standard "semver" exposed, but the development snapshots can + now be more reasonably upgraded with automated tooling. A copy of the + current version information would be embedded into "dist" archives as + a `VERSION_DEFAULT` file, so it can be used without git. Certain distros + can benefit from a `VERSION_FORCED` file or a `NUT_VERSION_FORCED` + environment variable exported from their build system, e.g. via + `echo NUT_VERSION_FORCED=1.1.1 > VERSION_FORCED`. Unfortunately, some + appliances tag all software the same with their firmware version; + if this is required, a (NUT_)VERSION_FORCED_SEMVER envvar or file can + help identify the actual NUT release version triplet used on the box. + Please use it, it immensely helps with community troubleshooting! + [issue #1949] + +- `upsmon` should now integrate natively with systemd-driven OS sleep events + (built with systemd version 221 or newer "inhibitor interface"), so various + hacks previously packaged into `/usr/lib/systemd/system-sleep/` scripts or + units requiring/conflicting with the `sleep.target` may be obsolete. + For fallback with older systemd, a `nut-sleep.service` is provided now. + [#1070, #2596, #2597] + +- `usbhid-ups` subdriver `PowerCOM HID` subdriver sent UPS `shutdown` and + `stayoff` commands in wrong byte order, at least for devices currently + in the field. Driver now sends the commands in a way that satisfies new + devices; just in case a flag toggle `powercom_sdcmd_byte_order_fallback` + was added to set the old behavior (if some devices do need it). [PR #2480] + +- Added support for `lbrb_log_delay_sec=N` setting to delay propagation of + `LB` or `LB+RB` state (buggy with APC BXnnnnMI devices/firmwares issued + circa 2023-2024 which flood the logs with spurious LOWBATT and REPLACEBATT + events). This may work better for some devices when combined with flags + like `onlinedischarge_calibration` and `lbrb_log_delay_without_calibrating`. + [#2347] + +- Enabled installation of built PDF and HTML (including man page renditions) + files under the configured `docdir`. It seems previously they were only + built (if requested) but not installed via `make`, unlike the common man + pages which are delivered automatically. Packaging recipes can likely + be simplified now. [#2445] + +- A `NUT_DEBUG_SYSLOG` environment variable was introduced to tweak activation + of syslog message emission (and related detachment of `stderr` when daemons + are backgrounding), which can be useful for systemd service units. It can be + set via `nut.conf` file for all standard consumers, or patched/dropped-in to + systemd unit definitions specifically (less recommended, but may be easier + to package). The positive effect would be avoiding duplicate logging as both + `syslog` and `stderr` ending up in the same journal. [#2394] + +- A `CHANGELOG_REQUIRE_GROUP_BY_DATE_AUTHOR` setting was added (for `make` + calls and used by `tools/gitlog2changelog.py.in` script), and it defaults + to `true` allowing for better ordered documents at the cost of some memory + during document generation. Resource-constrained builders (working from + a Git workspace, not tarball archives) may have to set it to `false` when + calling `make` for NUT. [#2510] + +- NUT products like `nut-scanner`, which dynamically load shared libraries + at run-time without persistent pre-linking, should now know the library + file names that were present during build (likely encumbered with version + suffixes), and prefer them over plain `libname.so` patterns used previously + (which on some platforms are only delivered by development packages as + symlinks). Packaging recipes can likely be simplified now: some distros + certainly did patch NUT source to similar effect). [#2431] + +- Numerous changes to `nut-scanner` and symbols that its `libnutscan.so` + delivers have caused a library version bump. New methods have been added + and one structure (`nutscan_ipmi_t`) updated in a (hopefully) backwards + compatible manner. [PR #2523, issue #2244 and numerous PRs for it] + +- Internal API change for `sendsignalpid()` and `sendsignalfn()` methods, + which can impact NUT forks which build using `libcommon.la` and similar + libraries. Added new last argument with `const char *progname` (may be + `NULL`) to check that we are signalling an expected program name when we + work with a PID. With the same effort, NUT programs which deal with PID + files to send signals (`upsd`, `upsmon`, drivers and `upsdrvctl`) would + now default to a safety precaution -- checking that the running process + with that PID has the expected program name (on platforms where we can + determine one). This might introduce regressions for heavily customized + NUT builds (e.g. embedded in NAS or similar devices) whose binary file + names differ significantly from a `progname` defined in the respective + NUT source file, so a boolean `NUT_IGNORE_CHECKPROCNAME` environment + variable support was added to optionally disable this verification. + Also the NUT daemons should request to double-check against their + run-time process name (if it can be detected). [issue #2463] + +- More environment variable support was added to NUT programs, primarily + aimed at wrappers such as init scripts and service unit definitions, + allowing to tweak what (and whether) they write into debug traces, and + so "make noise" or "bring invaluable insights" to logs or terminal; + they can generally be used for services and init scripts via `nut.conf`: + * See `NUT_IGNORE_CHECKPROCNAME` and `NUT_DEBUG_SYSLOG` above. [#1915] + * A `NUT_QUIET_INIT_BANNER` envvar (presence or "true" value) prevents + tool name and NUT version banner from being printed out when programs + start. [issues #1789 vs. #316] + +- A `configure` script option to `--enable-NUT_STRARG-always` was added + to enable the `NUT_STRARG` macro (to handle `NULL` string printing) + even if system libraries seem to safely support this behavior natively. + This should primarily help against overly zealous static analysis tools + in recent compiler generations. [#2585] + Changes from 2.8.1 to 2.8.2 --------------------------- @@ -44,6 +148,13 @@ Changes from 2.8.1 to 2.8.2 appear as comments, or enabled by specifying the `-U` command-line option several times. [#2221] +- The `tools/gitlog2changelog.py.in` script was revised, in particular to + convert section titles (with contributor names) into plain ASCII character + set, for `dblatex` versions which do not allow diacritics and other kinds + of non-trivial characters in sections. A number of other projects seem to + use the NUT version of the script, and are encouraged to look at related + changes in `configure.ac` and `Makefile.am` recipes. [PR #2360, PR #2366] + Changes from 2.8.0 to 2.8.1 --------------------------- @@ -414,17 +525,20 @@ Changes from 2.6.4 to 2.6.5 --------------------------- - users are encouraged to update to NUT 2.6.5, to fix a regression in -upssched. + upssched. + - mge-shut driver has been replaced by a new implementation (newmge-shut). -In case of issue with this new version, users can revert to oldmge-shut. + In case of issue with this new version, users can revert to oldmge-shut. + UPDATE: oldmge-shut was dropped between 2.7.4 and 2.8.0 releases. Changes from 2.6.3 to 2.6.4 --------------------------- - users are encouraged to update to NUT 2.6.4, to fix upsd vulnerability -(CVE-2012-2944: upsd can be remotely crashed). + (CVE-2012-2944: upsd can be remotely crashed). + - users of the bestups driver are encouraged to switch to blazer_ser, -since bestups will soon be deprecated. + since bestups will soon be deprecated. Changes from 2.6.2 to 2.6.3 --------------------------- @@ -435,7 +549,7 @@ Changes from 2.6.1 to 2.6.2 --------------------------- - apcsmart driver has been replaced by a new implementation. In case of issue -with this new version, users can revert to apcsmart-old. + with this new version, users can revert to apcsmart-old. Changes from 2.6.0 to 2.6.1 --------------------------- @@ -446,9 +560,10 @@ Changes from 2.4.3 to 2.6.0 --------------------------- - users of the megatec and megatec_usb drivers must respectively switch to -blazer_ser and blazer_usb. + blazer_ser and blazer_usb. + - users of the liebertgxt2 driver are advised that the driver name has changed -to liebert-esp2. + to liebert-esp2. Changes from 2.4.2 to 2.4.3 --------------------------- @@ -459,11 +574,12 @@ Changes from 2.4.1 to 2.4.2 --------------------------- - The default subdriver for the blazer_usb driver USB id 06da:0003 has changed. -If you use such a device and it is no longer working with this driver, override -the 'subdriver' default in 'ups.conf' (see man 8 blazer). + If you use such a device and it is no longer working with this driver, + override the 'subdriver' default in 'ups.conf' (see man 8 blazer). + - NUT ACL and the allowfrom mechanism has been replaced in 2.4.0 by the LISTEN -directive and tcp-wrappers respectively. This information was missing below, so -a double note has been added. + directive and tcp-wrappers respectively. This information was missing below, + so a double note has been added. Changes from 2.4.0 to 2.4.1 --------------------------- @@ -474,157 +590,175 @@ Changes from 2.2.2 to 2.4.0 --------------------------- - The nut.conf file has been introduced to standardize startup configuration -across the various systems. + across the various systems. + - The cpsups and nitram drivers have been replaced by the powerpanel driver, -and removed from the tree. The cyberpower driver may suffer the same in the -future. + and removed from the tree. The cyberpower driver may suffer the same in the + future. + - The al175 and energizerups drivers have been removed from the tree, since -these were tagged broken for a long time. + these were tagged broken for a long time. + - Developers of external client application using libupsclient must rename -their "UPSCONN" client structure to "UPSCONN_t". + their "UPSCONN" client structure to "UPSCONN_t". + - The upsd server will now disconnect clients that remain silent for more than -60 seconds. + 60 seconds. + - The files under scripts/python/client are distributed under GPL 3+, whereas -the rest of the files are distributed under GPL 2+. Refer to COPYING for more -information. + the rest of the files are distributed under GPL 2+. Refer to COPYING for more + information. + - The generated udev rules file has been renamed with dash only, no underscore -anymore (ie 52-nut-usbups.rules instead of 52_nut-usbups.rules) + anymore (i.e. 52-nut-usbups.rules instead of 52_nut-usbups.rules) Changes from 2.2.1 to 2.2.2 --------------------------- - The configure option "--with-lib" has been replaced by "--with-dev". -This enable the additional build and distribution of the static -version of libupsclient, along with the pkg-config helper and manual -pages. The default configure option is to distribute only the shared -version of libupsclient. This can be overridden by using the -"--disable-shared" configure option (distribute static only binaries). + This enable the additional build and distribution of the static + version of libupsclient, along with the pkg-config helper and manual + pages. The default configure option is to distribute only the shared + version of libupsclient. This can be overridden by using the + "--disable-shared" configure option (distribute static only binaries). + - The UPS poweroff handling of the usbhid-ups driver has been reworked. -Though regression is not expected, users of this driver are -encouraged to test this feature by calling "upsmon -c fsd" and -report any issue on the NUT mailing lists. + Though regression is not expected, users of this driver are + encouraged to test this feature by calling "upsmon -c fsd" and + report any issue on the NUT mailing lists. Changes from 2.2.0 to 2.2.1 --------------------------- - nothing that affects upgraded systems. -(The below message is repeated due to previous omission) + (The below message is repeated due to previous omission) + - Developers of external client application using libupsclient are -encouraged to rename their "UPSCONN" client structure to "UPSCONN_t" -since the former will disappear by the release of NUT 2.4. + encouraged to rename their "UPSCONN" client structure to "UPSCONN_t" + since the former will disappear by the release of NUT 2.4. Changes from 2.0.5 to 2.2.0 --------------------------- - users of the newhidups driver are advised that the driver name has changed -to usbhid-ups. + to usbhid-ups. + - users of the hidups driver must switch to usbhid-ups. + - users of the following drivers (powermust, blazer, fentonups, mustek, -esupssmart, ippon, sms) must switch to megatec, which replaces -all these drivers. Please refer to doc/megatec.txt for details. + esupssmart, ippon, sms) must switch to megatec, which replaces + all these drivers. Please refer to doc/megatec.txt for details. + - users of the mge-shut driver are encouraged to test newmge-shut, which -is an alternate driver scheduled to replace mge-shut, + is an alternate driver scheduled to replace mge-shut, + - users of the cpsups driver are encouraged to switch to powerpanel which -is scheduled to replace cpsups, + is scheduled to replace cpsups, + - packagers will have to rework the whole nut packaging due to the -major changes in the build system (completely modified, and now using -automake). Refer to packaging/debian/ for an example of migration. + major changes in the build system (completely modified, and now using + automake). Refer to packaging/debian/ for an example of migration. + - specifying '-a ' is now mandatory when starting a driver manually, -ie not using upsdrvctl. + i.e. not using upsdrvctl. + - Developers of external client application using libupsclient are -encouraged to rename the "UPSCONN" client structure to "UPSCONN_t" -since the former will disappear by the release of NUT 2.4. + encouraged to rename the "UPSCONN" client structure to "UPSCONN_t" + since the former will disappear by the release of NUT 2.4. Changes from 2.0.4 to 2.0.5 --------------------------- - users of the newhidups driver: the driver is now more strict about -refusing to connect to unknown devices. If your device was -previously supported, but fails to be recognized now, add -'productid=XXXX' to ups.conf. Please report the device to the NUT -developer's mailing list. + refusing to connect to unknown devices. If your device was + previously supported, but fails to be recognized now, add + 'productid=XXXX' to ups.conf. Please report the device to the NUT + developer's mailing list. Changes from 2.0.3 to 2.0.4 --------------------------- - nothing that affects upgraded systems. + - users of the following drivers (powermust, blazer, fentonups, mustek, -esupssmart, ippon, sms, masterguard) are encouraged to switch to megatec, -which should replace all these drivers by nut 2.2. For more information, -please refer to doc/megatec.txt + esupssmart, ippon, sms, masterguard) are encouraged to switch to megatec, + which should replace all these drivers by nut 2.2. For more information, + please refer to doc/megatec.txt Changes from 2.0.2 to 2.0.3 --------------------------- - nothing that affects upgraded systems. + - hidups users are encouraged to switch to newhidups, as hidups will be -removed by nut 2.2. + removed by nut 2.2. Changes from 2.0.1 to 2.0.2 --------------------------- - The newhidups driver, which is the long run USB support approach, -needs hotplug files installed to setup the right permissions on -device file to operate. Check newhidups manual page for more information. + needs hotplug files installed to setup the right permissions on + device file to operate. Check newhidups manual page for more information. Changes from 2.0.0 to 2.0.1 --------------------------- - The cyberpower1100 driver is now called cpsups since it supports -more than just one model. If you use this driver, be sure to remove -the old binary and update your ups.conf 'driver=' setting with the -new name. + more than just one model. If you use this driver, be sure to remove + the old binary and update your ups.conf 'driver=' setting with the + new name. - The upsstats.html template page has been changed slightly to reflect -better HTML compliance, so you may want to update your installed copy -accordingly. If you've customized your file, don't just copy the new -one over it, or your changes will be lost! + better HTML compliance, so you may want to update your installed copy + accordingly. If you've customized your file, don't just copy the new + one over it, or your changes will be lost! Changes from 1.4.0 to 2.0.0 --------------------------- - The sample config files are no longer installed by default. If you -want to install them, use 'make install-conf' for the main programs, -and 'make install-cgi-conf' for the CGI programs. + want to install them, use 'make install-conf' for the main programs, + and 'make install-cgi-conf' for the CGI programs. - ACCESS is no longer supported in upsd.conf. Use ACCEPT and REJECT. -Old way: + * Old way: ++ ACCESS grant all adminbox ACCESS grant all webserver ACCESS deny all all -New way: - + * New way: ++ ACCEPT adminbox ACCEPT webserver REJECT all -Note that ACCEPT and REJECT can take multiple arguments, so this -will also work: - + * Note that ACCEPT and REJECT can take multiple arguments, so this + will also work: ++ ACCEPT adminbox webserver REJECT all - The drivers no longer support sddelay in ups.conf or -d on the -command line. If you need a delay after calling 'upsdrvctl -shutdown', add a call to sleep in your shutdown script. + command line. If you need a delay after calling 'upsdrvctl + shutdown', add a call to sleep in your shutdown script. - The templates used by upsstats have changed considerably to reflect -the new variable names. If you use upsstats, you will need to -install new copies or edit your existing files to use the new names. + the new variable names. If you use upsstats, you will need to + install new copies or edit your existing files to use the new names. - Nobody needed UDP mode, so it has been removed. The only users -seemed to be a few people like me with ancient asapm-ups binaries. -If you really want to run asapm-ups again, bug me for the new patch -which makes it work with upsclient. + seemed to be a few people like me with ancient asapm-ups binaries. + If you really want to run asapm-ups again, bug me for the new patch + which makes it work with upsclient. - 'make install-misc' is now 'make install-lib'. The misc directory -has been gone for a long time, and the target was ambiguous. + has been gone for a long time, and the target was ambiguous. - The newapc driver has been renamed to apcsmart. If you previously -used newapc, make sure you delete the old binary and fix your -ups.conf. Otherwise, you may run the old driver from 1.4. + used newapc, make sure you delete the old binary and fix your + ups.conf. Otherwise, you may run the old driver from 1.4. File trimmed here on changes from 1.2.2 to 1.4.0 ------------------------------------------------ diff --git a/appveyor.yml b/appveyor.yml index ba5101aa18..2c0d712164 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,7 +6,7 @@ # https://www.appveyor.com/docs/build-configuration/ # https://www.appveyor.com/docs/windows-images-software/ -version: 2.8.1.{build}-{branch} +version: 2.8.2.{build}-{branch} # base image image: Visual Studio 2022 diff --git a/autogen.sh b/autogen.sh index afcf3d647b..f59d93e886 100755 --- a/autogen.sh +++ b/autogen.sh @@ -24,6 +24,8 @@ else DEBUG=false fi +NUT_VERSION_QUERY=UPDATE_FILE "`dirname $0`"/tools/gitlog2version.sh + if [ -n "${PYTHON-}" ] ; then # May be a name/path of binary, or one with args - check both (command -v "$PYTHON") \ @@ -165,6 +167,18 @@ fi >&2 [ -f NEWS ] || { echo "Please see NEWS.adoc for actual contents" > NEWS; } [ -f README ] || { echo "Please see README.adoc for actual contents" > README; } +# Try to serve a fresh one at least when we remake from scratch like this +# Note to not do it forcefully during `configure` or rebuild +rm -f include/nut_version.h || true + +echo "----------------------------------------------------------------------" +echo "Please note that on some systems the routine below can complain that " +echo " > configure.ac: warning: AC_INIT: not a literal: m4_esyscmd_s(...)" +echo "but still does the right thing about PACKAGE_VERSION and PACKAGE_URL settings." +echo "Check if your distro provides an 'autoconf-archive' package anf if it helps." +echo "Please post an issue in NUT bug tracker with platform details if it does not." +echo "----------------------------------------------------------------------" + echo "Calling autoreconf..." AUTOTOOL_RES=0 if $DEBUG ; then @@ -178,16 +192,19 @@ fi [ "$AUTOTOOL_RES" = 0 ] && [ -s configure ] && [ -x configure ] \ || { cat << EOF +---------------------------------------------------------------------- FAILED: did not generate an executable configure script! # Note: on some systems "autoreconf", "automake" et al are dispatcher # scripts, and need you to explicitly say which version you want, e.g. # export AUTOCONF_VERSION=2.65 AUTOMAKE_VERSION=1.13 # If you get issues with AC_DISABLE_STATIC make sure you have libtool. +# # If it complains about "too few" or "excess" "arguments to builtin ifdef", # check the configure.ac line it refers to and un-comment (or comment away) # the third argument for AM_SILENT_RULES check, or comment away the whole # "ifdef" block if your autotools still would not grok it. +---------------------------------------------------------------------- EOF exit 1 } >&2 @@ -216,13 +233,20 @@ else CONFIG_SHELL="`head -1 configure | sed 's,^#!,,'`" fi -# NOTE: Unquoted, may be multi-token +# NOTE: Unquoted CONFIG_SHELL, may be multi-token $CONFIG_SHELL -n configure 2>/dev/null >/dev/null \ -|| { echo "FAILED: configure script did not pass shell interpreter syntax checks with $CONFIG_SHELL" >&2 +|| { + echo "----------------------------------------------------------------------" >&2 + echo "FAILED: configure script did not pass shell interpreter syntax checks with $CONFIG_SHELL" >&2 echo "NOTE: If you are using an older OS release, try executing the script with" >&2 echo "a more functional shell implementation (dtksh, bash, dash...)" >&2 echo "You can re-run this script with a CONFIG_SHELL in environment" >&2 + echo "----------------------------------------------------------------------" >&2 exit 1 } -echo "The generated configure script passed shell interpreter syntax checks" +echo "----------------------------------------------------------------------" +echo "SUCCESS: The generated configure script passed shell interpreter syntax checks" +echo "Please proceed by running './configure --with-many-desired-options'" +echo "For details check './configure --help' or docs/configure.txt in NUT sources" +echo "----------------------------------------------------------------------" diff --git a/ci_build.sh b/ci_build.sh index 83e395e447..12cec3a01b 100755 --- a/ci_build.sh +++ b/ci_build.sh @@ -55,7 +55,9 @@ case "$BUILD_TYPE" in fightwarn-gcc) CC="gcc" CXX="g++" - CPP="cpp" + # Avoid "cpp" directly as it may be too "traditional" + #CPP="cpp" + CPP="gcc -E" BUILD_TYPE=fightwarn ;; fightwarn-clang) @@ -191,16 +193,22 @@ fi # (note: a "-" value requests to NOT use a CI_CCACHE_SYMLINKDIR; # ccache may still be used via prefixing if the tool is found in # the PATH, unless you export CI_CCACHE_USE=no also): -if [ -z "${CI_CCACHE_SYMLINKDIR-}" ] ; then - for D in \ +propose_CI_CCACHE_SYMLINKDIR() { + echo \ "/usr/lib/ccache" \ "/mingw64/lib/ccache/bin" \ "/mingw32/lib/ccache/bin" \ "/usr/lib64/ccache" \ "/usr/libexec/ccache" \ "/usr/lib/ccache/bin" \ - "/usr/local/lib/ccache" \ - ; do + "/usr/local/lib/ccache" + + if [ -n "${HOMEBREW_PREFIX-}" ]; then + echo "${HOMEBREW_PREFIX}/opt/ccache/libexec" + fi +} +if [ -z "${CI_CCACHE_SYMLINKDIR-}" ] ; then + for D in `propose_CI_CCACHE_SYMLINKDIR` ; do if [ -d "$D" ] ; then if ( ls -la "$D" | grep -e ' -> .*ccache' >/dev/null) \ || ( test -n "`find "$D" -maxdepth 1 -type f -exec grep -li ccache '{}' \;`" ) \ @@ -462,7 +470,7 @@ if [ -z "${CANBUILD_LIBGD_CGI-}" ]; then # NUT CI farm with Jenkins can build it; Travis could not [[ "$CI_OS_NAME" = "freebsd" ]] && CANBUILD_LIBGD_CGI=yes \ - || [[ "$TRAVIS_OS_NAME" = "freebsd" ]] && CANBUILD_LIBGD_CGI=no + || { [[ "$TRAVIS_OS_NAME" = "freebsd" ]] && CANBUILD_LIBGD_CGI=no ; } # See also below for some compiler-dependent decisions fi @@ -540,8 +548,10 @@ configure_nut() { echo "=== CC='$CC' CXX='$CXX' CPP='$CPP'" [ -z "${CI_SHELL_IS_FLAKY-}" ] || echo "=== CI_SHELL_IS_FLAKY='$CI_SHELL_IS_FLAKY'" $CI_TIME $CONFIGURE_SCRIPT "${CONFIG_OPTS[@]}" \ + && echo "$0: configure phase complete (0)" >&2 \ && return 0 \ || { RES_CFG=$? + echo "$0: configure phase complete ($RES_CFG)" >&2 echo "FAILED ($RES_CFG) to configure nut, will dump config.log in a second to help troubleshoot CI" >&2 echo " (or press Ctrl+C to abort now if running interactively)" >&2 sleep 5 @@ -573,6 +583,12 @@ build_to_only_catch_errors_target() { # Sub-shells to avoid crashing with "unhandled" faults in "set -e" mode: ( echo "`date`: Starting the parallel build attempt (quietly to build what we can) for '$@' ..."; \ + if [ -n "$PARMAKE_FLAGS" ]; then + echo "For parallel builds, '$PARMAKE_FLAGS' options would be used" + fi + if [ -n "$MAKEFLAGS" ]; then + echo "Generally, MAKEFLAGS='$MAKEFLAGS' options would be passed" + fi ( case "${CI_PARMAKE_VERBOSITY}" in silent) # Note: stderr would still expose errors and warnings (needed for @@ -585,7 +601,12 @@ build_to_only_catch_errors_target() { default) $CI_TIME $MAKE -k $PARMAKE_FLAGS "$@" ;; esac ) && echo "`date`: SUCCESS" ; ) || \ - ( echo "`date`: Starting the sequential build attempt (to list remaining files with errors considered fatal for this build configuration) for '$@'..."; \ + ( RET=$? + if [ "$CI_FAILFAST" = true ]; then + echo "===== Aborting after parallel build attempt failure for '$*' because CI_FAILFAST=$CI_FAILFAST" >&2 + exit $RET + fi + echo "`date`: Starting the sequential build attempt (to list remaining files with errors considered fatal for this build configuration) for '$@'..."; \ $CI_TIME $MAKE $MAKE_FLAGS_VERBOSE "$@" -k ) || return $? return 0 } @@ -646,7 +667,9 @@ check_gitignore() { # Shell-glob filename pattern for points of interest to git status # and git diff; note that filenames starting with a dot should be # reported by `git status -- '*'` and not hidden. - [ -n "${FILE_GLOB-}" ] || FILE_GLOB='*' + [ -n "${FILE_GLOB-}" ] || FILE_GLOB="'*'" + # Always filter these names away: + FILE_GLOB_EXCLUDE="':!.ci*.log*' ':!VERSION_DEFAULT' ':!VERSION_FORCED*'" [ -n "${GIT_ARGS-}" ] || GIT_ARGS='' # e.g. GIT_ARGS="--ignored" # Display contents of the diff? # (Helps copy-paste from CI logs to source to amend quickly) @@ -661,12 +684,11 @@ check_gitignore() { # One invocation should report to log if there was any discrepancy # to report in the first place (GITOUT may be empty without error): - GITOUT="`git status $GIT_ARGS -s -- "${FILE_GLOB}"`" \ + GITOUT="`git status $GIT_ARGS -s -- ${FILE_GLOB} ${FILE_GLOB_EXCLUDE}`" \ || { echo "WARNING: Could not query git repo while in `pwd`" >&2 ; GITOUT=""; } if [ -n "${GITOUT-}" ] ; then echo "$GITOUT" \ - | grep -E -v '^.. \.ci.*\.log.*' \ | grep -E "${FILE_REGEX}" else echo "Got no output and no errors querying git repo while in `pwd`: seems clean" >&2 @@ -674,12 +696,12 @@ check_gitignore() { echo "===" # Another invocation checks that there was nothing to complain about: - if [ -n "`git status $GIT_ARGS -s "${FILE_GLOB}" | grep -E -v '^.. \.ci.*\.log.*' | grep -E "^.. ${FILE_REGEX}"`" ] \ + if [ -n "`git status $GIT_ARGS -s ${FILE_GLOB} ${FILE_GLOB_EXCLUDE} | grep -E "^.. ${FILE_REGEX}"`" ] \ && [ "$CI_REQUIRE_GOOD_GITIGNORE" != false ] \ ; then echo "FATAL: There are changes in $FILE_DESCR files listed above - tracked sources should be updated in the PR (even if generated - not all builders can do so), and build products should be added to a .gitignore file, everything made should be cleaned and no tracked files should be removed! You can 'export CI_REQUIRE_GOOD_GITIGNORE=false' if appropriate." >&2 if [ "$GIT_DIFF_SHOW" = true ]; then - PAGER=cat git diff -- "${FILE_GLOB}" || true + PAGER=cat git diff -- ${FILE_GLOB} ${FILE_GLOB_EXCLUDE} || true fi echo "===" return 1 @@ -687,6 +709,45 @@ check_gitignore() { return 0 } +consider_cleanup_shortcut() { + # Note: modern auto(re)conf requires pkg-config to generate the configure + # script, so to stage the situation of building without one (as if on an + # older system) we have to remove it when we already have the script. + # This matches the use-case of distro-building from release tarballs that + # include all needed pre-generated files to rely less on OS facilities. + DO_REGENERATE=false + if [ x"${CI_REGENERATE}" = xtrue ] ; then + echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because CI_REGENERATE='${CI_REGENERATE}'" + DO_REGENERATE=true + fi + + if [ -s Makefile ]; then + if [ -n "`find "${SCRIPTDIR}" -name configure.ac -newer "${CI_BUILDDIR}"/configure`" ] \ + || [ -n "`find "${SCRIPTDIR}" -name '*.m4' -newer "${CI_BUILDDIR}"/configure`" ] \ + || [ -n "`find "${SCRIPTDIR}" -name Makefile.am -newer "${CI_BUILDDIR}"/Makefile`" ] \ + || [ -n "`find "${SCRIPTDIR}" -name Makefile.in -newer "${CI_BUILDDIR}"/Makefile`" ] \ + || [ -n "`find "${SCRIPTDIR}" -name Makefile.am -newer "${CI_BUILDDIR}"/Makefile.in`" ] \ + ; then + # Avoid reconfiguring just for the sake of distclean + echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because recipes changed" + DO_REGENERATE=true + fi + fi + + # When itertating configure.ac or m4 sources, we can end up with an + # existing but useless scropt file - nuke it and restart from scratch! + if [ -s "${CI_BUILDDIR}"/configure ] ; then + if ! sh -n "${CI_BUILDDIR}"/configure 2>/dev/null ; then + echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because current configure script syntax is broken" + DO_REGENERATE=true + fi + fi + + if $DO_REGENERATE ; then + rm -f "${CI_BUILDDIR}"/Makefile "${CI_BUILDDIR}"/configure "${CI_BUILDDIR}"/include/config.h "${CI_BUILDDIR}"/include/config.h.in "${CI_BUILDDIR}"'/include/config.h.in~' + fi +} + can_clean_check() { if [ "${DO_CLEAN_CHECK-}" = "no" ] ; then # NOTE: Not handling here particular DO_MAINTAINER_CLEAN_CHECK or DO_DIST_CLEAN_CHECK @@ -956,13 +1017,13 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp # Note: can be a multi-token name like "clang -E" or just not a full pathname ( [ -x "$CPP" ] || $CPP --help >/dev/null 2>/dev/null ) && export CPP else - if is_gnucc "cpp" ; then - CPP=cpp && export CPP - else - case "$COMPILER_FAMILY" in - CLANG*|GCC*) CPP="$CC -E" && export CPP ;; - esac - fi + # Avoid "cpp" directly as it may be too "traditional" + case "$COMPILER_FAMILY" in + CLANG*|GCC*) CPP="$CC -E" && export CPP ;; + *) if is_gnucc "cpp" ; then + CPP=cpp && export CPP + fi ;; + esac fi if [ -z "${CANBUILD_LIBGD_CGI-}" ]; then @@ -987,14 +1048,6 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp fi fi - # Note: Potentially there can be spaces in entries for multiple - # *FLAGS here; this should be okay as long as entry expands to - # one token when calling shell (may not be the case for distcheck) - CONFIG_OPTS+=("CFLAGS=-I${BUILD_PREFIX}/include ${CFLAGS}") - CONFIG_OPTS+=("CPPFLAGS=-I${BUILD_PREFIX}/include ${CPPFLAGS}") - CONFIG_OPTS+=("CXXFLAGS=-I${BUILD_PREFIX}/include ${CXXFLAGS}") - CONFIG_OPTS+=("LDFLAGS=-L${BUILD_PREFIX}/lib ${LDFLAGS}") - DEFAULT_PKG_CONFIG_PATH="${BUILD_PREFIX}/lib/pkgconfig" SYSPKG_CONFIG_PATH="" # Let the OS guess... usually case "`echo "$CI_OS_NAME" | tr 'A-Z' 'a-z'`" in @@ -1018,6 +1071,63 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp ;; esac ;; + *darwin*|*macos*|*osx*) + # Architecture-dependent base dir, e.g. + # * /usr/local on macos x86 + # * /opt/homebrew on macos Apple Silicon + if [ -n "${HOMEBREW_PREFIX-}" -a -d "${HOMEBREW_PREFIX-}" ]; then + echo "Homebrew: export general pkg-config location and C/C++/LD flags for the platform" + SYS_PKG_CONFIG_PATH="${HOMEBREW_PREFIX}/lib/pkgconfig" + CFLAGS="${CFLAGS-} -Wno-poison-system-directories -Wno-deprecated-declarations -isystem ${HOMEBREW_PREFIX}/include -I${HOMEBREW_PREFIX}/include" + #CPPFLAGS="${CPPFLAGS-} -Wno-poison-system-directories -Wno-deprecated-declarations -isystem ${HOMEBREW_PREFIX}/include -I${HOMEBREW_PREFIX}/include" + CXXFLAGS="${CXXFLAGS-} -Wno-poison-system-directories -isystem ${HOMEBREW_PREFIX}/include -I${HOMEBREW_PREFIX}/include" + LDFLAGS="${LDFLAGS-} -L${HOMEBREW_PREFIX}/lib" + + # Net-SNMP "clashes" with system-provided tools (but no header/lib) + # so explicit args are needed + checkFSobj="${HOMEBREW_PREFIX}/opt/net-snmp/lib/pkgconfig" + if [ -d "$checkFSobj" -a ! -e "${HOMEBREW_PREFIX}/lib/pkgconfig/netsnmp.pc" ] ; then + echo "Homebrew: export pkg-config location for Net-SNMP" + SYS_PKG_CONFIG_PATH="$SYS_PKG_CONFIG_PATH:$checkFSobj" + #echo "Homebrew: export flags for Net-SNMP" + #CONFIG_OPTS+=("--with-snmp-includes=-isystem ${HOMEBREW_PREFIX}/opt/net-snmp/include -I${HOMEBREW_PREFIX}/opt/net-snmp/include") + #CONFIG_OPTS+=("--with-snmp-libs=-L${HOMEBREW_PREFIX}/opt/net-snmp/lib") + fi + + if [ -d "${HOMEBREW_PREFIX}/opt/net-snmp/include" -a -d "${HOMEBREW_PREFIX}/include/openssl" ]; then + # TODO? Check netsnmp.pc for Libs.private with + # -L/opt/homebrew/opt/openssl@1.1/lib + # or + # -L/usr/local/opt/openssl@3/lib + # among other options to derive the exact version + # it wants, and serve that include path here + echo "Homebrew: export configure options for Net-SNMP with default OpenSSL headers (too intimate on Homebrew)" + CONFIG_OPTS+=("--with-snmp-includes=-isystem ${HOMEBREW_PREFIX}/opt/net-snmp/include -I${HOMEBREW_PREFIX}/opt/net-snmp/include -isystem ${HOMEBREW_PREFIX}/include -I${HOMEBREW_PREFIX}/include") + CONFIG_OPTS+=("--with-snmp-libs=-L${HOMEBREW_PREFIX}/opt/net-snmp/lib -lnetsnmp") + fi + + # A bit hackish to check this outside `configure`, but... + if [ -s "${HOMEBREW_PREFIX-}/include/ltdl.h" ] ; then + echo "Homebrew: export flags for LibLTDL" + # The m4 script clear default CFLAGS/LIBS so benefit from new ones + CONFIG_OPTS+=("--with-libltdl-includes=-isystem ${HOMEBREW_PREFIX}/include -I${HOMEBREW_PREFIX}/include") + CONFIG_OPTS+=("--with-libltdl-libs=-L${HOMEBREW_PREFIX}/lib -lltdl") + fi + + if [ -z "${XML_CATALOG_FILES-}" ] ; then + checkFSobj="${HOMEBREW_PREFIX}/etc/xml/catalog" + if [ -e "$checkFSobj" ] ; then + echo "Homebrew: export XML_CATALOG_FILES='$checkFSobj' for asciidoc et al" + XML_CATALOG_FILES="$checkFSobj" + export XML_CATALOG_FILES + fi + fi + else + echo "WARNING: It seems you are building on MacOS, but HOMEBREW_PREFIX is not set or valid." + echo 'If you do use this build system, try running eval "$(brew shellenv)"' + echo "in your terminal or shell profile, it can help with auto-detection of some features!" + fi + ;; esac if [ -n "$SYS_PKG_CONFIG_PATH" ] ; then if [ -n "$PKG_CONFIG_PATH" ] ; then @@ -1032,6 +1142,14 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp CONFIG_OPTS+=("PKG_CONFIG_PATH=${DEFAULT_PKG_CONFIG_PATH}") fi + # Note: Potentially there can be spaces in entries for multiple + # *FLAGS here; this should be okay as long as entry expands to + # one token when calling shell (may not be the case for distcheck) + CONFIG_OPTS+=("CFLAGS=-I${BUILD_PREFIX}/include ${CFLAGS}") + CONFIG_OPTS+=("CPPFLAGS=-I${BUILD_PREFIX}/include ${CPPFLAGS}") + CONFIG_OPTS+=("CXXFLAGS=-I${BUILD_PREFIX}/include ${CXXFLAGS}") + CONFIG_OPTS+=("LDFLAGS=-L${BUILD_PREFIX}/lib ${LDFLAGS}") + CONFIG_OPTS+=("--enable-keep_nut_report_feature") CONFIG_OPTS+=("--prefix=${BUILD_PREFIX}") CONFIG_OPTS+=("--sysconfdir=${BUILD_PREFIX}/etc/nut") @@ -1215,7 +1333,11 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp CONFIG_OPTS+=("--with-cgi=auto") fi else - CONFIG_OPTS+=("--with-cgi=auto") + if [ "${CANBUILD_LIBGD_CGI-}" = "no" ] ; then + CONFIG_OPTS+=("--without-cgi") + else + CONFIG_OPTS+=("--with-cgi=auto") + fi fi ;; "default-alldrv:no-distcheck") @@ -1359,38 +1481,13 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp CONFIG_OPTS+=("--with-debuginfo=${BUILD_DEBUGINFO}") fi - # Note: modern auto(re)conf requires pkg-config to generate the configure - # script, so to stage the situation of building without one (as if on an - # older system) we have to remove it when we already have the script. - # This matches the use-case of distro-building from release tarballs that - # include all needed pre-generated files to rely less on OS facilities. - if [ -s Makefile ]; then - if [ -n "`find "${SCRIPTDIR}" -name configure.ac -newer "${CI_BUILDDIR}"/configure`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name '*.m4' -newer "${CI_BUILDDIR}"/configure`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name Makefile.am -newer "${CI_BUILDDIR}"/Makefile`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name Makefile.in -newer "${CI_BUILDDIR}"/Makefile`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name Makefile.am -newer "${CI_BUILDDIR}"/Makefile.in`" ] \ - ; then - # Avoid reconfiguring for the sake of distclean - echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because recipes changed" - rm -f "${CI_BUILDDIR}"/Makefile "${CI_BUILDDIR}"/configure - fi - fi - - # When itertating configure.ac or m4 sources, we can end up with an - # existing but useless scropt file - nuke it and restart from scratch! - if [ -s "${CI_BUILDDIR}"/configure ] ; then - if ! sh -n "${CI_BUILDDIR}"/configure 2>/dev/null ; then - echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because current configure script syntax is broken" - rm -f "${CI_BUILDDIR}"/Makefile "${CI_BUILDDIR}"/configure - fi - fi + consider_cleanup_shortcut if [ -s Makefile ]; then # Let initial clean-up be at default verbosity # Handle Ctrl+C with helpful suggestions: - trap 'echo "!!! If clean-up looped remaking the configure script for maintainer-clean, try to:"; echo " rm -f Makefile configure ; $0 $SCRIPT_ARGS"' 2 + trap 'echo "!!! If clean-up looped remaking the configure script for maintainer-clean, try to:"; echo " rm -f Makefile configure include/config.h* ; $0 $SCRIPT_ARGS"' 2 echo "=== Starting initial clean-up (from old build products)" case "$MAKE_FLAGS $MAKE_FLAGS_CLEAN" in @@ -1430,7 +1527,11 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-sp # e.g. distcheck-light, distcheck-valgrind, cppcheck, maybe # others later, as defined in Makefile.am: BUILD_TGT="`echo "$BUILD_TYPE" | sed 's,^default-tgt:,,'`" - echo "`date`: Starting the sequential build attempt for singular target $BUILD_TGT..." + if [ -n "${PARMAKE_FLAGS}" ]; then + echo "`date`: Starting the parallel build attempt for singular target $BUILD_TGT..." + else + echo "`date`: Starting the sequential build attempt for singular target $BUILD_TGT..." + fi # Note: Makefile.am already sets some default DISTCHECK_CONFIGURE_FLAGS # that include DISTCHECK_FLAGS if provided @@ -1981,27 +2082,8 @@ bindings) fi cd "${SCRIPTDIR}" - if [ -s Makefile ]; then - if [ -n "`find "${SCRIPTDIR}" -name configure.ac -newer "${CI_BUILDDIR}"/configure`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name '*.m4' -newer "${CI_BUILDDIR}"/configure`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name Makefile.am -newer "${CI_BUILDDIR}"/Makefile`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name Makefile.in -newer "${CI_BUILDDIR}"/Makefile`" ] \ - || [ -n "`find "${SCRIPTDIR}" -name Makefile.am -newer "${CI_BUILDDIR}"/Makefile.in`" ] \ - ; then - # Avoid reconfiguring for the sake of distclean - echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because recipes changed" - rm -f "${CI_BUILDDIR}"/Makefile "${CI_BUILDDIR}"/configure - fi - fi - # When itertating configure.ac or m4 sources, we can end up with an - # existing but useless scropt file - nuke it and restart from scratch! - if [ -s "${CI_BUILDDIR}"/configure ] ; then - if ! sh -n "${CI_BUILDDIR}"/configure 2>/dev/null ; then - echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because current configure script syntax is broken" - rm -f "${CI_BUILDDIR}"/Makefile "${CI_BUILDDIR}"/configure - fi - fi + consider_cleanup_shortcut if [ -s Makefile ]; then # Help developers debug: @@ -2042,11 +2124,28 @@ bindings) CONFIG_OPTS+=("--with-debuginfo=auto") fi - ${CONFIGURE_SCRIPT} "${CONFIG_OPTS[@]}" + if [ -n "${PYTHON-}" ]; then + # WARNING: Watch out for whitespaces, not handled here! + CONFIG_OPTS+=("--with-python=${PYTHON}") + fi + + RES_CFG=0 + ${CONFIGURE_SCRIPT} "${CONFIG_OPTS[@]}" \ + || RES_CFG=$? + echo "$0: configure phase complete ($RES_CFG)" >&2 + [ x"$RES_CFG" = x0 ] || exit $RES_CFG # NOTE: Currently parallel builds are expected to succeed (as far # as recipes are concerned), and the builds without a BUILD_TYPE # are aimed at developer iterations so not tweaking verbosity. + echo "Configuration finished, starting make" >&2 + if [ -n "$PARMAKE_FLAGS" ]; then + echo "For parallel builds, '$PARMAKE_FLAGS' options would be used" >&2 + fi + if [ -n "$MAKEFLAGS" ]; then + echo "Generally, MAKEFLAGS='$MAKEFLAGS' options would be passed" >&2 + fi + #$MAKE all || \ $MAKE $PARMAKE_FLAGS all || exit if [ "${CI_SKIP_CHECK}" != true ] ; then $MAKE check || exit ; fi diff --git a/clients/.gitignore b/clients/.gitignore index 26183d14df..c422f84a7d 100644 --- a/clients/.gitignore +++ b/clients/.gitignore @@ -1,3 +1,4 @@ +/libupsclient-version.h /upsimage.cgi /upsset.cgi /upsstats.cgi diff --git a/clients/Makefile.am b/clients/Makefile.am index b873046cc0..bb5957984b 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -3,13 +3,14 @@ # Export certain values for ccache which NUT ci_build.sh can customize, # to facilitate developer iteration re-runs of "make" later. # At least GNU and BSD make implementations are okay with this syntax. -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_DIR=@CCACHE_DIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_PATH=@CCACHE_PATH@ -@NUT_AM_MAKE_CAN_EXPORT@export PATH=@PATH_DURING_CONFIGURE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_NAMESPACE@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_BASEDIR@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_DIR@export CCACHE_DIR=@CCACHE_DIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export CCACHE_PATH=@CCACHE_PATH@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export PATH=@PATH_DURING_CONFIGURE@ EXTRA_DIST = +CLEANFILES = # nutclient.cpp for some legacy reason (maybe initial detached development?) # optionally includes "common.h" with the NUT build setup - and this option @@ -117,6 +118,31 @@ if HAVE_WINDOWS libupsclient_la_LDFLAGS += -no-undefined endif +# ./clients/libupsclient.la samples (partial!) on... +# Linux: +# # The name that we can dlopen(3). +# dlname='libupsclient.so.6' +# # Names of this library. +# library_names='libupsclient.so.6.0.1 libupsclient.so.6 libupsclient.so' +# # Directory that this library needs to be installed in: +# libdir='/usr/local/ups/lib' +# WIN32: +# dlname='libupsclient-6.dll' +# library_names='libupsclient.dll.a' +# libdir='//lib' +CLEANFILES += libupsclient-version.h +libupsclient-version.h: libupsclient.la + @dlname_filter() { sed -e 's/^[^=]*=//' -e 's/^"\(.*\)"$$/\1/' -e 's/^'"'"'\(.*\)'"'"'$$/\1/' ; }; \ + SOFILE_LIBUPSCLIENT="`grep -E '^dlname' '$?' | dlname_filter`" \ + || SOFILE_LIBUPSCLIENT="" ; \ + if [ x"$${SOFILE_LIBUPSCLIENT-}" = x ] ; then \ + printf "#ifdef SOFILE_LIBUPSCLIENT\n# undef SOFILE_LIBUPSCLIENT\n#endif\n\n" ; \ + printf "#ifdef SOPATH_LIBUPSCLIENT\n# undef SOPATH_LIBUPSCLIENT\n#endif\n\n" ; \ + else \ + printf "#ifndef SOFILE_LIBUPSCLIENT\n# define SOFILE_LIBUPSCLIENT \"%s\"\n#endif\n\n" "$${SOFILE_LIBUPSCLIENT}" ; \ + printf "#ifndef SOPATH_LIBUPSCLIENT\n# define SOPATH_LIBUPSCLIENT \"%s/%s\"\n#endif\n\n" "@LIBDIR@" "$${SOFILE_LIBUPSCLIENT}" ; \ + fi > "$@" + if HAVE_CXX11 # libnutclient version information and build libnutclient_la_SOURCES = nutclient.h nutclient.cpp diff --git a/clients/nutclient.cpp b/clients/nutclient.cpp index 63ec2ae903..d873e387c0 100644 --- a/clients/nutclient.cpp +++ b/clients/nutclient.cpp @@ -1367,6 +1367,31 @@ std::vector TcpClient::explode(const std::string& str, size_t begin } state = QUOTED_STRING; break; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } } diff --git a/clients/nutclient.h b/clients/nutclient.h index 8047e26ced..19dbdcfde4 100644 --- a/clients/nutclient.h +++ b/clients/nutclient.h @@ -18,7 +18,7 @@ */ #ifndef NUTCLIENT_HPP_SEEN -#define NUTCLIENT_HPP_SEEN +#define NUTCLIENT_HPP_SEEN 1 /* Begin of C++ nutclient library declaration */ #ifdef __cplusplus diff --git a/clients/nutclientmem.h b/clients/nutclientmem.h index 6363262681..8584a9ca45 100644 --- a/clients/nutclientmem.h +++ b/clients/nutclientmem.h @@ -18,7 +18,7 @@ */ #ifndef NUTCLIENTMEM_HPP_SEEN -#define NUTCLIENTMEM_HPP_SEEN +#define NUTCLIENTMEM_HPP_SEEN 1 /* Begin of C++ nutclient library declaration */ #ifdef __cplusplus @@ -117,4 +117,4 @@ NUTCLIENT_MEM_t nutclient_mem_create_client(); #endif /* __cplusplus */ /* End of C nutclient library declaration */ -#endif /* NUTCLIENTMOCK_HPP_SEEN */ +#endif /* NUTCLIENTMEM_HPP_SEEN */ diff --git a/clients/upsc.c b/clients/upsc.c index 71715f4731..d99d18085a 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -2,6 +2,7 @@ Copyright (C) 1999 Russell Kroll Copyright (C) 2012 Arnaud Quette + Copyright (C) 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -35,15 +36,14 @@ static UPSCONN_t *ups = NULL; static void usage(const char *prog) { - printf("Network UPS Tools upsc %s\n\n", UPS_VERSION); + print_banner_once(prog, 2); + printf("NUT read-only client program to display UPS variables.\n"); - printf("usage: %s -l | -L [[:port]]\n", prog); + printf("\nusage: %s -l | -L [[:port]]\n", prog); printf(" %s []\n", prog); printf(" %s -c \n", prog); - printf("\nDemo program to display UPS variables.\n\n"); - - printf("First form (lists UPSes):\n"); + printf("\nFirst form (lists UPSes):\n"); printf(" -l - lists each UPS on , one per line.\n"); printf(" -L - lists each UPS followed by its description (from ups.conf).\n"); printf(" Default hostname: localhost\n"); @@ -217,10 +217,22 @@ static void clean_exit(void) int main(int argc, char **argv) { - int i; + int i = 0; uint16_t port; int varlist = 0, clientlist = 0, verbose = 0; const char *prog = xbasename(argv[0]); + char *s = NULL; + + /* NOTE: Caller must `export NUT_DEBUG_LEVEL` to see debugs for upsc + * and NUT methods called from it. This line aims to just initialize + * the subsystem, and set initial timestamp. Debugging the client is + * primarily of use to developers, so is not exposed via `-D` args. + */ + s = getenv("NUT_DEBUG_LEVEL"); + if (s && str_to_int(s, &i, 10) && i > 0) { + nut_debug_level = i; + } + upsdebugx(1, "Starting NUT client: %s", prog); while ((i = getopt(argc, argv, "+hlLcV")) != -1) { @@ -238,12 +250,11 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); - - fatalx(EXIT_SUCCESS, "Network UPS Tools upsc %s", UPS_VERSION); -#ifndef HAVE___ATTRIBUTE__NORETURN - exit(EXIT_SUCCESS); /* Should not get here in practice, but compiler is afraid we can fall through */ -#endif + exit(EXIT_SUCCESS); case 'h': default: @@ -267,6 +278,8 @@ int main(int argc, char **argv) fatalx(EXIT_FAILURE, "Error: invalid UPS definition.\nRequired format: upsname[@hostname[:port]]"); } } + upsdebugx(1, "upsname='%s' hostname='%s' port='%" PRIu16 "'", + NUT_STRARG(upsname), NUT_STRARG(hostname), port); ups = xmalloc(sizeof(*ups)); @@ -275,18 +288,22 @@ int main(int argc, char **argv) } if (varlist) { + upsdebugx(1, "Calling list_upses()"); list_upses(verbose); exit(EXIT_SUCCESS); } if (clientlist) { + upsdebugx(1, "Calling list_clients()"); list_clients(upsname); exit(EXIT_SUCCESS); } if (argc > 1) { + upsdebugx(1, "Calling printvar(%s)", argv[1]); printvar(argv[1]); } else { + upsdebugx(1, "Calling list_vars()"); list_vars(); } diff --git a/clients/upsclient.c b/clients/upsclient.c index 74290cd26f..d6a3d6ff59 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -3,6 +3,7 @@ Copyright (C) 2002 Russell Kroll 2008 Arjen de Korte + 2020 - 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -61,25 +62,25 @@ /* WA for Solaris/i386 bug: non-blocking connect sets errno to ENOENT */ #if (defined NUT_PLATFORM_SOLARIS) - #define SOLARIS_i386_NBCONNECT_ENOENT(status) ( (!strcmp("i386", CPU_TYPE)) ? (ENOENT == (status)) : 0 ) +# define SOLARIS_i386_NBCONNECT_ENOENT(status) ( (!strcmp("i386", CPU_TYPE)) ? (ENOENT == (status)) : 0 ) #else - #define SOLARIS_i386_NBCONNECT_ENOENT(status) (0) +# define SOLARIS_i386_NBCONNECT_ENOENT(status) (0) #endif /* end of Solaris/i386 WA for non-blocking connect */ /* WA for AIX bug: non-blocking connect sets errno to 0 */ #if (defined NUT_PLATFORM_AIX) - #define AIX_NBCONNECT_0(status) (0 == (status)) +# define AIX_NBCONNECT_0(status) (0 == (status)) #else - #define AIX_NBCONNECT_0(status) (0) +# define AIX_NBCONNECT_0(status) (0) #endif /* end of AIX WA for non-blocking connect */ #ifdef WITH_NSS - #include - #include - #include - #include - #include - #include +# include +# include +# include +# include +# include +# include #endif /* WITH_NSS */ #define UPSCLIENT_MAGIC 0x19980308 @@ -619,6 +620,9 @@ const char *upscli_strerror(UPSCONN_t *ups) upscli_errlist[ups->upserror].str, ups->pc_ctx.errmsg); return ups->errbuf; + + default: + break; } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL @@ -1024,6 +1028,7 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags ups->fd = -1; if (!host) { + upslogx(LOG_WARNING, "%s: Host not specified", __func__); ups->upserror = UPSCLI_ERR_NOSUCHHOST; return -1; } @@ -1049,6 +1054,7 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags case EAI_AGAIN: continue; case EAI_NONAME: + upslogx(LOG_WARNING, "%s: Host not found: '%s'", __func__, NUT_STRARG(host)); ups->upserror = UPSCLI_ERR_NOSUCHHOST; return -1; case EAI_MEMORY: @@ -1057,6 +1063,8 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags case EAI_SYSTEM: ups->syserrno = errno; break; + default: + break; } ups->upserror = UPSCLI_ERR_UNKNOWN; @@ -1207,7 +1215,7 @@ int upscli_tryconnect(UPSCONN_t *ups, const char *host, uint16_t port, int flags } else if (tryssl && ret == 0) { if (certverify != 0) { upslogx(LOG_NOTICE, "Can not connect to NUT server %s in SSL and " - "certificate is needed, disconnect", host); + "certificate is needed, disconnect", host); upscli_disconnect(ups); return -1; } @@ -1686,7 +1694,21 @@ int upscli_splitaddr(const char *buf, char **hostname, uint16_t *port) return -1; } + s = strchr(tmp, '@'); + + /* someone passed a "@hostname" string? */ + if (s) { + fprintf(stderr, "upscli_splitaddr: wrong call? " + "Got upsname@hostname[:port] string where " + "only hostname[:port] was expected: %s\n", buf); + /* let it pass, but probably fail later */ + } + if (*tmp == '[') { + /* NOTE: Brackets are required for colon-separated IPv6 + * addresses, to differentiate from a port number. For + * example, `[1234:5678]:3493` would seem right. + */ if (strchr(tmp, ']') == NULL) { fprintf(stderr, "upscli_splitaddr: missing closing bracket in [domain literal]\n"); return -1; diff --git a/clients/upsclient.h b/clients/upsclient.h index c79be96cfd..c359905882 100644 --- a/clients/upsclient.h +++ b/clients/upsclient.h @@ -18,27 +18,27 @@ */ #ifndef UPSCLIENT_H_SEEN -#define UPSCLIENT_H_SEEN +#define UPSCLIENT_H_SEEN 1 #ifdef WITH_OPENSSL - #include - #include +# include +# include #elif defined(WITH_NSS) /* WITH_OPENSSL */ - #include - #include +# include +# include #endif /* WITH_OPENSSL | WITH_NSS */ /* Not including nut_stdint.h because this is part of end-user API */ #if defined HAVE_INTTYPES_H - #include +# include #endif #if defined HAVE_STDINT_H - #include +# include #endif #if defined HAVE_LIMITS_H - #include +# include #endif /* Not including NUT timehead.h because this is part of end-user API */ @@ -53,6 +53,10 @@ # endif #endif +#if defined HAVE_SYS_TYPES_H +# include +#endif + #ifdef __cplusplus /* *INDENT-OFF* */ extern "C" { diff --git a/clients/upscmd.c b/clients/upscmd.c index 4d71ebce2f..868009bf53 100644 --- a/clients/upscmd.c +++ b/clients/upscmd.c @@ -47,11 +47,12 @@ struct list_t { static void usage(const char *prog) { - printf("Network UPS Tools upscmd %s\n\n", UPS_VERSION); - printf("usage: %s [-h]\n", prog); + print_banner_once(prog, 2); + printf("NUT administration client program to initiate instant commands on UPS hardware.\n"); + + printf("\nusage: %s [-h]\n", prog); printf(" %s [-l ]\n", prog); printf(" %s [-u ] [-p ] [-w] [-t ] []\n\n", prog); - printf("Administration program to initiate instant commands on UPS hardware.\n"); printf("\n"); printf(" -h display this help text\n"); printf(" -V display the version of this software\n"); @@ -312,12 +313,11 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); - - fatalx(EXIT_SUCCESS, "Network UPS Tools upscmd %s", UPS_VERSION); -#ifndef HAVE___ATTRIBUTE__NORETURN - exit(EXIT_SUCCESS); /* Should not get here in practice, but compiler is afraid we can fall through */ -#endif + exit(EXIT_SUCCESS); case 'h': default: diff --git a/clients/upslog.c b/clients/upslog.c index 502f6299e9..f1d36b9fcc 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -144,7 +144,8 @@ static void help(const char *prog) static void help(const char *prog) { - printf("UPS status logger.\n"); + print_banner_once(prog, 2); + printf("NUT read-only client program - UPS status logger.\n"); printf("\nusage: %s [OPTIONS]\n", prog); printf("\n"); @@ -439,7 +440,7 @@ int main(int argc, char **argv) logformat = DEFAULT_LOGFORMAT; user = RUN_AS_USER; - printf("Network UPS Tools %s %s\n", prog, UPS_VERSION); + print_banner_once(prog, 0); while ((i = getopt(argc, argv, "+hs:l:i:f:u:Vp:FBm:")) != -1) { switch(i) { @@ -505,6 +506,9 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); exit(EXIT_SUCCESS); @@ -519,6 +523,12 @@ int main(int argc, char **argv) case 'B': foreground = 0; break; + + default: + fatalx(EXIT_FAILURE, + "Error: unknown option -%c. Try -h for help.", + (char)i); + } } diff --git a/clients/upsmon.c b/clients/upsmon.c index 1b4af7cfb4..ed9a630722 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -3,7 +3,7 @@ Copyright (C) 1998 Russell Kroll 2012 Arnaud Quette - 2020-2023 Jim Klimov + 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,12 +24,12 @@ #include #ifndef WIN32 -#include -#include -#include -#include +# include +# include +# include +# include #else -#include +# include #endif #include "nut_stdint.h" @@ -39,7 +39,7 @@ #include "timehead.h" #ifdef HAVE_STDARG_H -#include +# include #endif static char *shutdowncmd = NULL, *notifycmd = NULL; @@ -86,6 +86,20 @@ static int pollfail_log_throttle_max = -1; */ static int offdurationtime = 30; + /* Normally we raise alarms for immediate shutdown for consumers + * of an UPS known to be on battery (OB) and achieving the low + * battery status (LB), if that is their last remaining power + * source to satisfy their MINSUPPLIES setting. + * In some cases, users may want to delay raising the alarm + * (using the OBLBDURATION option) at their discretion and risk + * of an ungraceful shutdown. + * UPS state polling frequency will be the minimum of this delay + * and POLLFREQALERT (as normally applied for "OB" devices) when + * this situation is in place. + * Non-positive values are currently treated as 0 (immediate FSD). + */ +static int oblbdurationtime = 0; + /* secondary hosts are given 15 sec by default to logout from upsd */ static int hostsync = 15; @@ -138,7 +152,13 @@ static sigset_t nut_upsmon_sigmask; #ifdef HAVE_SYSTEMD # define SERVICE_UNIT_NAME "nut-monitor.service" -#endif +#endif /* HAVE_SYSTEMD */ + +/* If we successfully use Inhibit() to be notified about + * the OS going to sleep, this is the messenger variable: + */ +static TYPE_FD sleep_inhibitor_fd = ERROR_FD; +static int sleep_inhibitor_status = -2; /* Users can pass a -D[...] option to enable debugging. * For the service tracing purposes, also the upsmon.conf @@ -192,7 +212,7 @@ static void wall(const char *text) fprintf(wf, "%s\n", text); pclose(wf); #else - #define MESSAGE_CMD "message.exe" +# define MESSAGE_CMD "message.exe" char * command; /* first +1 is for the space between message and text @@ -325,6 +345,8 @@ static void notify(const char *notice, int flags, const char *ntype, } } + upsdebugx(6, "%s (child): exiting after notifications", __func__); + exit(EXIT_SUCCESS); #else async_notify_t * data; @@ -379,6 +401,10 @@ static void do_notify(const utype_t *ups, int ntype) #endif notify(msg, notifylist[i].flags, notifylist[i].name, upsname); + + upsdebugx(3, "%s: ntype 0x%04x (%s) finished", + __func__, ntype, notifylist[i].name); + return; } } @@ -1062,6 +1088,9 @@ static int is_ups_critical(utype_t *ups) if (flag_isset(ups->status, ST_FSD)) return 1; + /* Used in a number of clauses below */ + time(&now); + if (ups->commstate == 0) { if (flag_isset(ups->status, ST_CAL)) { upslogx(LOG_WARNING, @@ -1090,11 +1119,17 @@ static int is_ups_critical(utype_t *ups) } if (ups->linestate == 0) { - upslogx(LOG_WARNING, + /* Just a message for post-mortem troubleshooting: + * no flag flips, no return values issued just here + * (note the message is likely to appear on every + * cycle when the communications are down, to help + * track when this was the case; no log throttling). + */ + upsdebugx(1, "UPS [%s] was last known to be not fully online " - "and currently is not communicating, assuming dead", + "and currently is not communicating, just so you " + "know (waiting for DEADTIME to elapse)", ups->sys); - return 1; } } @@ -1111,8 +1146,23 @@ static int is_ups_critical(utype_t *ups) /* not OB or not LB = not critical yet */ if ((!flag_isset(ups->status, ST_ONBATT)) || (!flag_isset(ups->status, ST_LOWBATT)) - ) + ) { + if (ups->oblbsince > 0) { + upslogx(LOG_WARNING, + "%s: seems that UPS [%s] returned from OB+LB " + "state now after spending %" PRIiMAX + " seconds in it", + __func__, ups->upsname, + (intmax_t)(now - ups->oblbsince)); + ups->oblbsince = 0; + + if (flag_isset(ups->status, ST_ONBATT)) + sleepval = pollfreqalert; + else + sleepval = pollfreq; + } return 0; + } /* must be OB+LB now */ @@ -1129,17 +1179,90 @@ static int is_ups_critical(utype_t *ups) return 0; } - /* if we're a primary, declare it critical so we set FSD on it */ - if (flag_isset(ups->status, ST_PRIMARY)) - return 1; + if (oblbdurationtime > 0) { + /* User configured this upsmon instance to delay + * issuing or processing FSD alerts */ + if (ups->oblbsince < 1 && now > 0) { + /* We would bump polling frequency if needed, + * to check the UPS state as soon as needed + * to see if this requested OB+LB duration + * has elapsed (every 1 sec in extreme case). + * Using GCD algorithm inspired by + * https://www.programiz.com/c-programming/examples/hcf-gcd + */ + int n1 = (pollfreqalert > 0) ? pollfreqalert : 1; + int n2 = (oblbdurationtime > 0) ? oblbdurationtime : 1; + + /* Find GCD: */ + while (n1 != n2) { + if (n1 > n2) + n1 -= n2; + else + n2 -= n1; + } + sleepval = (n1 > 0) ? n1 : 1; - /* must be a secondary now */ + upslogx(LOG_WARNING, + "%s: seems that UPS [%s] is OB+LB now, " + "but OBLBDURATION=%d - not declaring " + "a critical state just now, but will " + "bump the polling rate to %d (from " + "POLLFREQALERT=%d) and wait to see " + "if this situation dissipates soon", + __func__, ups->upsname, + oblbdurationtime, + sleepval, + pollfreqalert); + + if (!flag_isset(ups->status, ST_PRIMARY) + && hostsync > oblbdurationtime + ) { + upslogx(LOG_WARNING, + "%s: HOSTSYNC=%d exceeds OBLBDURATION " + "and would be effectively ignored on " + "this secondary upsmon client system", + __func__, hostsync); + } - /* FSD isn't set, so the primary hasn't seen it yet */ + ups->oblbsince = now; - time(&now); + return 0; + } - /* give the primary up to HOSTSYNC seconds before shutting down */ + /* Already looping in OB LB duration? */ + if (now > 0 && (now - ups->oblbsince < oblbdurationtime)) { + upslogx(LOG_WARNING, + "%s: seems that UPS [%s] is still " + "OB+LB for %" PRIiMAX " sec out of %d delay", + __func__, ups->upsname, + (intmax_t)(now - ups->oblbsince), + oblbdurationtime); + return 0; + } + + upslogx(LOG_WARNING, + "%s: seems that UPS [%s] is still " + "OB+LB for %" PRIiMAX " sec out of %d delay - " + "proceeding with shutdown alerts", + __func__, ups->upsname, + (intmax_t)(now - ups->oblbsince), + oblbdurationtime); + + /* Timer expired, fall through to raise FSD on this primary + * or handle it on the secondary */ + } + + /* if we're a primary, declare it critical so we set FSD on it */ + if (flag_isset(ups->status, ST_PRIMARY)) { + return 1; + } + + /* We must be a secondary now, in OB+LB situation, and + * FSD isn't set - so the primary hasn't seen it yet. + * Give the primary up to HOSTSYNC seconds to report it + * before commencing to shut down this secondary system + * anyway. + */ if ((now - ups->lastnoncrit) > hostsync) { upslogx(LOG_WARNING, "Giving up on the primary for UPS [%s] " "after %d sec since last comms", @@ -1147,7 +1270,8 @@ static int is_ups_critical(utype_t *ups) return 1; } - /* there's still time left, maybe OB+LB will go away next time we look? */ + /* secondary: there's still time left, maybe OB+LB will go away next + * time we look? */ return 0; } @@ -1492,22 +1616,28 @@ static void addups(int reloading, const char *sys, const char *pvs, else firstups = tmp; - if (tmp->pv) - upslogx(LOG_INFO, "UPS: %s (%s) (power value %d)", tmp->sys, - flag_isset(tmp->status, ST_PRIMARY) ? "primary" : "secondary", - tmp->pv); - else - upslogx(LOG_INFO, "UPS: %s (monitoring only)", tmp->sys); + /* Negative debug value may be set by help() to be really quiet */ + if (nut_debug_level > -1) { + if (tmp->pv) + upslogx(LOG_INFO, "UPS: %s (%s) (power value %d)", tmp->sys, + flag_isset(tmp->status, ST_PRIMARY) ? "primary" : "secondary", + tmp->pv); + else + upslogx(LOG_INFO, "UPS: %s (monitoring only)", tmp->sys); + } tmp->upsname = tmp->hostname = NULL; if (upscli_splitname(tmp->sys, &tmp->upsname, &tmp->hostname, - &tmp->port) != 0) { - upslogx(LOG_ERR, "Error: unable to split UPS name [%s]", - tmp->sys); + &tmp->port) != 0 + ) { + if (nut_debug_level > -1) { + upslogx(LOG_ERR, "Error: unable to split UPS name [%s]", + tmp->sys); + } } - if (!tmp->upsname) + if (!tmp->upsname && nut_debug_level > -1) upslogx(LOG_WARNING, "Warning: UPS [%s]: no upsname set!", tmp->sys); } @@ -1587,8 +1717,10 @@ static void checkmode(char *cfgentry, char *oldvalue, char *newvalue, if (use_pipe == 0) return; - /* it's ok if we're not reloading yet */ - if (reloading == 0) + /* it's ok if we're not reloading yet + * or "almost-fake" loading to show help() + */ + if (reloading < 1) return; /* also nothing to do if it didn't change */ @@ -1652,7 +1784,7 @@ static int parse_conf_arg(size_t numargs, char **arg) powerdownflag = filter_path(arg[1]); #endif - if (!reload_flag) + if (reload_flag == 0) upslogx(LOG_INFO, "Using power down flag file %s", arg[1]); @@ -1705,6 +1837,14 @@ static int parse_conf_arg(size_t numargs, char **arg) return 1; } + /* OBLBDURATION */ + if (!strcmp(arg[0], "OBLBDURATION")) { + oblbdurationtime = atoi(arg[1]); + if (oblbdurationtime < 1) + oblbdurationtime = 0; + return 1; + } + /* HOSTSYNC */ if (!strcmp(arg[0], "HOSTSYNC")) { hostsync = atoi(arg[1]); @@ -1854,6 +1994,10 @@ static void loadconfig(void) PCONF_CTX_t ctx; int numerrors = 0; + upsdebugx(1, "%s: %s %s", __func__, + (reload_flag == 1) ? "Reloading" : "Loading", + configfile); + pconf_init(&ctx, upsmon_err); if (!pconf_file_begin(&ctx, configfile)) { @@ -1864,6 +2008,12 @@ static void loadconfig(void) return; } + if (reload_flag == -1) { + /* For help() */ + upsdebugx(1, "Load failed: %s", ctx.errmsg); + return; + } + fatalx(EXIT_FAILURE, "%s", ctx.errmsg); } @@ -1930,11 +2080,14 @@ static void loadconfig(void) nut_debug_level_global); nut_debug_level = nut_debug_level_global; } else { - /* DEBUG_MIN is absent or commented-away in ups.conf */ - upslogx(LOG_INFO, - "Applying debug level %d from " - "original command line arguments", - nut_debug_level_args); + /* DEBUG_MIN is absent or commented-away in ups.conf + * Negative value may be set by help() to be really quiet + */ + if (nut_debug_level_args > -1) + upslogx(LOG_INFO, + "Applying debug level %d from " + "original command line arguments", + nut_debug_level_args); nut_debug_level = nut_debug_level_args; } @@ -1953,6 +2106,16 @@ static void loadconfig(void) } pconf_finish(&ctx); + + /* TOTHINK: Should this warning be limited to non-WIN32 builds? */ + if (!powerdownflag) { + upslogx(LOG_WARNING, "No POWERDOWNFLAG value was configured in %s!", + configfile); + upslogx(LOG_INFO, + "POWERDOWNFLAG should be a path to file that is normally " + "writeable for root user, and remains at least readable " + "late in shutdown after all unmounting completes."); + } } #ifndef WIN32 @@ -2399,8 +2562,9 @@ static void clear_pdflag(void) ret = pdflag_status(); if (ret == -1) { - upslogx(LOG_ERR, "POWERDOWNFLAG (%s) does not contain" - "the upsmon magic string - disabling!", powerdownflag); + upslogx(LOG_ERR, "POWERDOWNFLAG (%s) does not contain " + "the upsmon magic string (not a NUT file) - " + "disabling!", powerdownflag); powerdownflag = NULL; return; } @@ -2445,9 +2609,34 @@ static void help(const char *arg_progname) static void help(const char *arg_progname) { - printf("Monitors UPS servers and may initiate shutdown if necessary.\n\n"); + int old_debug_level = nut_debug_level; + int old_debug_level_args = nut_debug_level_args; + int old_debug_level_global = nut_debug_level_global; + + /* Try to get POWERDOWNFLAG? */ + if (!powerdownflag) { + /* Avoid fatalx() on bad/absent configs */ + reload_flag = -1; + + /* Hush messages normally emitted by loadconfig() */ + nut_debug_level = -2; + nut_debug_level_args = -2; + nut_debug_level_global = -2; - printf("usage: %s [OPTIONS]\n\n", arg_progname); + loadconfig(); + + nut_debug_level = old_debug_level; + nut_debug_level_args = old_debug_level_args; + nut_debug_level_global = old_debug_level_global; + + /* Separate from logs emitted by loadconfig() */ + /* printf("\n"); */ + } + + print_banner_once(arg_progname, 2); + printf("NUT client which monitors UPS servers and may initiate shutdown if necessary.\n"); + + printf("\nusage: %s [OPTIONS]\n\n", arg_progname); printf(" -c send command to running process\n"); printf(" commands:\n"); printf(" - fsd: shutdown all primary-mode UPSes (use with caution)\n"); @@ -2461,7 +2650,8 @@ static void help(const char *arg_progname) printf(" -B stay backgrounded even if debugging is bumped\n"); printf(" -V display the version of this software\n"); printf(" -h display this help\n"); - printf(" -K checks POWERDOWNFLAG, sets exit code to 0 if set\n"); + printf(" -K checks POWERDOWNFLAG (%s), sets exit code to 0 if set\n", + powerdownflag ? powerdownflag : "***NOT CONFIGURED***"); printf(" -p always run privileged (disable privileged parent)\n"); printf(" -u run child as user (ignored when using -p)\n"); printf(" -4 IPv4 only\n"); @@ -2693,6 +2883,40 @@ static void check_parent(void) upslogx(LOG_ALERT, "Parent died - shutdown impossible"); } +static void init_Inhibitor(const char *prog) +{ + static int sleep_inhibitor_fail_reported = 0; + static int sleep_status_ability_reported = 0; + + if (INVALID_FD(sleep_inhibitor_fd)) { + /* See https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.login1.html + * and https://systemd.io/INHIBITOR_LOCKS/ documentation about option values. + */ + sleep_inhibitor_fd = Inhibit("sleep", prog, "Careful handling of OS sleep with regard to power device monitoring", "delay"); + if (VALID_FD(sleep_inhibitor_fd)) { + upslogx(LOG_INFO, "%s: initialized OS integration for sleep inhibitor", prog); + sleep_inhibitor_fail_reported = 0; + } else { + if (!sleep_inhibitor_fail_reported) { + upslogx(LOG_WARNING, "%s: failed to initialize OS integration for sleep inhibitor", prog); + } + sleep_inhibitor_fail_reported = 1; + } + + if (!sleep_status_ability_reported && isPreparingForSleepSupported()) { + /* At this point isSupported may be -1 (unknown yet), + * make a query to be sure */ + sleep_inhibitor_status = isPreparingForSleep(); + if (isPreparingForSleepSupported()) { + upslogx(LOG_INFO, "%s: initialized OS integration for sleep/wake monitoring", prog); + } else { + upslogx(LOG_INFO, "%s: failed to initialize OS integration for sleep/wake monitoring", prog); + } + sleep_status_ability_reported = 1; + } + } +} + int main(int argc, char *argv[]) { const char *prog = xbasename(argv[0]); @@ -2725,7 +2949,7 @@ int main(int argc, char *argv[]) } #endif - printf("Network UPS Tools %s %s\n", prog, UPS_VERSION); + print_banner_once(prog, 0); /* if no configuration file is specified on the command line, use default */ configfile = xmalloc(SMALLBUF); @@ -2787,7 +3011,9 @@ int main(int argc, char *argv[]) run_as_user = xstrdup(optarg); break; case 'V': - /* just show the optional CONFIG_FLAGS banner */ + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); exit(EXIT_SUCCESS); case '4': @@ -2831,20 +3057,22 @@ int main(int argc, char *argv[]) * for probing whether a competing older instance of this program * is running (error if it is). */ + /* Hush the fopen(pidfile) message but let "real errors" be seen */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_KILL_SIG0PING - 1; #ifndef WIN32 /* If cmd == 0 we are starting and check if a previous instance * is running by sending signal '0' (i.e. 'kill 0' equivalent) */ if (oldpid < 0) { - cmdret = sendsignal(prog, cmd); + cmdret = sendsignal(prog, cmd, 1); } else { - cmdret = sendsignalpid(oldpid, cmd); + cmdret = sendsignalpid(oldpid, cmd, prog, 1); } #else /* WIN32 */ if (cmd) { /* Command the running daemon, it should be there */ - cmdret = sendsignal(UPSMON_PIPE_NAME, cmd); + cmdret = sendsignal(UPSMON_PIPE_NAME, cmd, 1); } else { /* Starting new daemon, check for competition */ mutex = CreateMutex(NULL, TRUE, UPSMON_PIPE_NAME); @@ -2890,8 +3118,9 @@ int main(int argc, char *argv[]) */ upslogx(LOG_WARNING, "Could not %s PID file " "to see if previous upsmon instance is " - "already running!", - (cmdret == -3 ? "find" : "parse")); + "already running or not!%s", + (cmdret == -3 ? "find" : "parse"), + (checking_flag ? " This is okay during OS shutdown, which is when checking POWERDOWNFLAG makes most sense." : "")); break; case -1: @@ -2933,22 +3162,32 @@ int main(int argc, char *argv[]) (oldpid < 0 ? " or add '-P $PID' argument" : "")); break; } -# else +# else /* not HAVE_SYSTEMD */ if (oldpid < 0) { upslogx(LOG_NOTICE, "Try to add '-P $PID' argument"); } -# endif +# endif /* not HAVE_SYSTEMD */ #endif /* not WIN32 */ } exit((cmdret == 0) ? EXIT_SUCCESS : EXIT_FAILURE); } + /* Restore the signal errors verbosity */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT; + argc -= optind; argv += optind; open_syslog(prog); + if (checking_flag) { + /* Do not normally report the UPSes we would monitor, etc. + * from loadconfig() for just checking the killpower flag */ + if (nut_debug_level == 0) + nut_debug_level = -2; + } + loadconfig(); /* CLI debug level can not be smaller than debug_min specified @@ -3021,6 +3260,14 @@ int main(int argc, char *argv[]) while (exit_flag == 0) { utype_t *ups; + time_t start, end, now; +#ifndef WIN32 + time_t prev; +#endif + double dt = 0; + + if (isInhibitSupported()) + init_Inhibitor(prog); upsnotify(NOTIFY_STATE_WATCHDOG, NULL); @@ -3034,8 +3281,108 @@ int main(int argc, char *argv[]) upsnotify(NOTIFY_STATE_READY, NULL); } - for (ups = firstups; ups != NULL; ups = ups->next) + if (isPreparingForSleepSupported()) { + if (sleep_inhibitor_status < 0) { + /* We may be coming fresh from an aborted loop cycle + * with *its* finding of pending or finished OS sleep; + * do not lose that knowledge by just reading "same as + * before" (-1) with another query here! */ + sleep_inhibitor_status = isPreparingForSleep(); + } else if (!isInhibitSupported()) { + /* We can monitor OS sleep/wake changes, but could not + * get an inhibition lock to sync this behavior nicely + * (e.g. due to lacking permissions to get such lock). + * Our information may be obsolete (e.g. we saw the + * beginning of OS sleep, but by the time we got + * to this line - we have already woken up) */ + int new_sleep_inhibitor_status = isPreparingForSleep(); + if (new_sleep_inhibitor_status >= 0 && sleep_inhibitor_status != new_sleep_inhibitor_status) { + upsdebugx(2, "We had previously learned sleep_inhibitor_status=%d " + "in a hastily aborted older loop cycle, but by the time " + "we got to handle it - the current value is %d", + sleep_inhibitor_status, new_sleep_inhibitor_status); + sleep_inhibitor_status = new_sleep_inhibitor_status; + } + } + upsdebugx(5, "sleep_inhibitor_status=%d", sleep_inhibitor_status); + } + if (sleep_inhibitor_status == 1) { + /* Preparing for sleep */ + do_notify(NULL, NOTIFY_SUSPEND_STARTING); + upslogx(LOG_INFO, "%s: Processing OS going to sleep", prog); + Uninhibit(&sleep_inhibitor_fd); + + upsnotify(NOTIFY_STATE_RELOADING, NULL); + upsdebugx(0, "%s: Dozing off until system tells us otherwise; send a SIGHUP or SIGTERM to break the loop manually", prog); + while ((sleep_inhibitor_status = isPreparingForSleep()) == -1 && !reload_flag && !exit_flag) { + usleep(1000000); + } + if (nut_debug_level == 0) { + upsdebugx(0, "%s: Dozing off finished", prog); + } else { + upsdebugx(0, "%s: Dozing off finished: sleep_inhibitor_status=%d reload_flag=%d exit_flag=%d", + prog, sleep_inhibitor_status, reload_flag, exit_flag); + } + + /*... when sleep_inhibitor_status has changed (to 0 after waking up?), + * go to switch/case below. Or after a user signal, skip on... */ + upsnotify(NOTIFY_STATE_READY, NULL); + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + + if (exit_flag) + break; + + if (reload_flag && sleep_inhibitor_status != 0) { + upslogx(LOG_WARNING, "%s: OS was last known to be preparing for sleep, and we were " + "interrupted by a reload signal. If the sleep does happen later, the system might " + "shut down after wake-up if UPS info is stale after a known critical power state!", + prog); + upsnotify(NOTIFY_STATE_RELOADING, NULL); + reload_conf(); + upsnotify(NOTIFY_STATE_READY, NULL); + } + upsdebugx(5, "sleep_inhibitor_status=%d (after dozing off)", sleep_inhibitor_status); + } /*... else go to switch/case below */ + + switch (sleep_inhibitor_status) { + case 0: /* Waking up */ + do_notify(NULL, NOTIFY_SUSPEND_FINISHED); + upslogx(LOG_INFO, "%s: Processing OS wake-up after sleep", prog); + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + + upsnotify(NOTIFY_STATE_RELOADING, NULL); + init_Inhibitor(prog); + + time(&now); + for (ups = firstups; ups != NULL; ups = ups->next) { + ups->status = 0; + ups->lastpoll = now; + } + + set_reload_flag(1); + reload_conf(); + + upsnotify(NOTIFY_STATE_READY, NULL); + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + + break; + + case 1: /* Handled above */ + case -1: /* Same as before */ + case -2: /* !isPreparingForSleepSupported() */ + default: /* Odd... */ + break; + } + /* Reset the value, regardless of support */ + sleep_inhibitor_status = -2; + + for (ups = firstups; ups != NULL; ups = ups->next) { + if (isPreparingForSleepSupported() && (sleep_inhibitor_status = isPreparingForSleep()) >= 0) { + upsdebugx(2, "Aborting UPS polling sub-loop because OS is preparing for sleep or just woke up"); + goto end_loop_cycle; + } pollups(ups); + } recalc(); @@ -3047,12 +3394,47 @@ int main(int argc, char *argv[]) /* reap children that have exited */ waitpid(-1, NULL, WNOHANG); - sleep(sleepval); + time(&start); + if (isPreparingForSleepSupported()) { + sleep_inhibitor_status = -2; + now = start; + + while (sleep_inhibitor_status < 0 && dt < sleepval) { + prev = now; + sleep(1); + sleep_inhibitor_status = isPreparingForSleep(); + time(&now); + dt = difftime(now, start); + upsdebugx(7, "start=%" PRIiMAX " now=%" PRIiMAX " dt=%g sleepval=%u sleep_inhibitor_status=%d", + (intmax_t)start, (intmax_t)now, dt, sleepval, sleep_inhibitor_status); + if (dt > (sleepval + 5) || difftime(now, prev) > 5) { + upsdebugx(2, "It seems we have slept without warning or the system clock was changed"); + if (sleep_inhibitor_status < 0) + sleep_inhibitor_status = 0; /* behave as woken up */ + } else if (dt < 0) { + upsdebugx(2, "It seems the system clock was changed into the past"); + sleep_inhibitor_status = 0; /* behave as woken up */ + } + } + + if (sleep_inhibitor_status >= 0) { + upsdebugx(2, "Aborting polling delay between main loop cycles because OS is preparing for sleep or just woke up"); + goto end_loop_cycle; + } + } else { + /* sleep tight */ + sleep(sleepval); + } + time(&end); #else maxhandle = 0; - memset(&handles,0,sizeof(handles)); + memset(&handles, 0, sizeof(handles)); /* Wait on the read IO of each connections */ + /* TODO: Windows suspend/hibernate tracking might fit here + * if we add a file handle to watch for... something, and + * so wake up quickly to skip the up-to-sleepval delay. + */ for (conn = pipe_connhead; conn; conn = conn->next) { handles[maxhandle] = conn->overlapped.hEvent; maxhandle++; @@ -3061,7 +3443,9 @@ int main(int argc, char *argv[]) handles[maxhandle] = pipe_connection_overlapped.hEvent; maxhandle++; - ret = WaitForMultipleObjects(maxhandle,handles,FALSE,sleepval*1000); + time(&start); + ret = WaitForMultipleObjects(maxhandle, handles, FALSE, sleepval*1000); + time(&end); if (ret == WAIT_FAILED) { upslogx(LOG_ERR, "Wait failed"); @@ -3073,8 +3457,8 @@ int main(int argc, char *argv[]) } /* Retrieve the signaled connection */ - for(conn = pipe_connhead; conn != NULL; conn = conn->next) { - if( conn->overlapped.hEvent == handles[ret-WAIT_OBJECT_0]) { + for (conn = pipe_connhead; conn != NULL; conn = conn->next) { + if (conn->overlapped.hEvent == handles[ret-WAIT_OBJECT_0]) { break; } } @@ -3084,8 +3468,8 @@ int main(int argc, char *argv[]) } /* one of the read event handle has been signaled */ else { - if( conn != NULL) { - if ( pipe_ready(conn) ) { + if (conn != NULL) { + if (pipe_ready(conn)) { if (!strncmp(conn->buf, SIGCMD_FSD, sizeof(SIGCMD_FSD))) { user_fsd(1); } @@ -3099,7 +3483,7 @@ int main(int argc, char *argv[]) } else { - upslogx(LOG_ERR,"Unknown signal"); + upslogx(LOG_ERR, "Unknown signal"); } pipe_disconnect(conn); @@ -3107,11 +3491,29 @@ int main(int argc, char *argv[]) } } #endif + + /* General-purpose handling of time jumps for OSes/run-times + * without NUT direct support for suspend/inhibit */ + dt = difftime(end, start); + if (dt > (sleepval + 5)) { + upsdebugx(2, "It seems we have slept without warning or the system clock was changed"); + if (sleep_inhibitor_status < 0) + sleep_inhibitor_status = 0; /* behave as woken up */ + } else if (dt < 0) { + upsdebugx(2, "It seems the system clock was changed into the past"); + if (sleep_inhibitor_status < 0) + sleep_inhibitor_status = 0; /* behave as woken up */ + } + +end_loop_cycle: + /* No-op to avoid a warning about label at end of compound statement */ + (void)1; } upslogx(LOG_INFO, "Signal %d: exiting", exit_flag); upsnotify(NOTIFY_STATE_STOPPING, "Signal %d: exiting", exit_flag); upsmon_cleanup(); + upsdebugx(1, "Finally exiting due to signal %d", exit_flag); exit(EXIT_SUCCESS); } diff --git a/clients/upsmon.h b/clients/upsmon.h index 767e146709..383ae2becd 100644 --- a/clients/upsmon.h +++ b/clients/upsmon.h @@ -1,6 +1,9 @@ /* upsmon.h - headers and other useful things for upsmon.h - Copyright (C) 2000 Russell Kroll + Copyright (C) + 2000 Russell Kroll + 2012 Arnaud Quette + 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -79,6 +82,7 @@ typedef struct { time_t lastncwarn; /* time of last NOCOMM warning */ time_t offsince; /* time of recent entry into OFF state */ + time_t oblbsince; /* time of recent entry into OB LB state (normally this causes immediate shutdown alert, unless we are configured to delay it) */ void *next; } utype_t; @@ -88,20 +92,23 @@ typedef struct { #define NOTIFY_ONLINE 0 /* UPS went on-line */ #define NOTIFY_ONBATT 1 /* UPS went on battery */ #define NOTIFY_LOWBATT 2 /* UPS went to low battery */ -#define NOTIFY_FSD 3 /* Primary upsmon set FSD flag */ +#define NOTIFY_FSD 3 /* Primary upsmon set FSD flag */ #define NOTIFY_COMMOK 4 /* Communication established */ #define NOTIFY_COMMBAD 5 /* Communication lost */ #define NOTIFY_SHUTDOWN 6 /* System shutdown in progress */ #define NOTIFY_REPLBATT 7 /* UPS battery needs to be replaced */ #define NOTIFY_NOCOMM 8 /* UPS hasn't been contacted in a while */ #define NOTIFY_NOPARENT 9 /* privileged parent process died */ -#define NOTIFY_CAL 10 /* UPS is performing calibration */ -#define NOTIFY_NOTCAL 11 /* UPS is performing calibration */ +#define NOTIFY_CAL 10 /* UPS is performing calibration */ +#define NOTIFY_NOTCAL 11 /* UPS is performing calibration */ #define NOTIFY_OFF 12 /* UPS is administratively OFF or asleep*/ #define NOTIFY_NOTOFF 13 /* UPS is not anymore administratively OFF or asleep*/ #define NOTIFY_BYPASS 14 /* UPS is administratively on bypass */ #define NOTIFY_NOTBYPASS 15 /* UPS is not anymore administratively on bypass */ +#define NOTIFY_SUSPEND_STARTING 30 /* OS is entering sleep/suspend/hibernate slumber mode, and we know it */ +#define NOTIFY_SUSPEND_FINISHED 31 /* OS just finished sleep/suspend/hibernate slumber mode, and we know it */ + /* notify flag values */ #define NOTIFY_IGNORE (1 << 0) /* don't do anything */ @@ -125,7 +132,7 @@ typedef struct { static struct { int type; const char *name; - char *msg; /* NULL until overridden */ + char *msg; /* NULL until overridden */ const char *stockmsg; int flags; } notifylist[] = @@ -133,7 +140,7 @@ static struct { { NOTIFY_ONLINE, "ONLINE", NULL, "UPS %s on line power", NOTIFY_DEFAULT }, { NOTIFY_ONBATT, "ONBATT", NULL, "UPS %s on battery", NOTIFY_DEFAULT }, { NOTIFY_LOWBATT, "LOWBATT", NULL, "UPS %s battery is low", NOTIFY_DEFAULT }, - { NOTIFY_FSD, "FSD", NULL, "UPS %s: forced shutdown in progress", NOTIFY_DEFAULT }, + { NOTIFY_FSD, "FSD", NULL, "UPS %s: forced shutdown in progress", NOTIFY_DEFAULT }, { NOTIFY_COMMOK, "COMMOK", NULL, "Communications with UPS %s established", NOTIFY_DEFAULT }, { NOTIFY_COMMBAD, "COMMBAD", NULL, "Communications with UPS %s lost", NOTIFY_DEFAULT }, { NOTIFY_SHUTDOWN, "SHUTDOWN", NULL, "Auto logout and shutdown proceeding", NOTIFY_DEFAULT }, @@ -146,6 +153,10 @@ static struct { { NOTIFY_NOTOFF, "NOTOFF", NULL, "UPS %s: no longer administratively OFF or asleep", NOTIFY_DEFAULT }, { NOTIFY_BYPASS, "BYPASS", NULL, "UPS %s: on bypass (powered, not protecting)", NOTIFY_DEFAULT }, { NOTIFY_NOTBYPASS,"NOTBYPASS",NULL, "UPS %s: no longer on bypass", NOTIFY_DEFAULT }, + + { NOTIFY_SUSPEND_STARTING, "SUSPEND_STARTING", NULL, "OS is entering sleep/suspend/hibernate mode", NOTIFY_DEFAULT }, + { NOTIFY_SUSPEND_FINISHED, "SUSPEND_FINISHED", NULL, "OS just finished sleep/suspend/hibernate mode, de-activating obsolete UPS readings to avoid an unfortunate shutdown", NOTIFY_DEFAULT }, + { 0, NULL, NULL, NULL, 0 } }; diff --git a/clients/upsrw.c b/clients/upsrw.c index 2086d6de43..710d39d51e 100644 --- a/clients/upsrw.c +++ b/clients/upsrw.c @@ -47,10 +47,11 @@ struct list_t { static void usage(const char *prog) { - printf("Network UPS Tools %s %s\n\n", prog, UPS_VERSION); - printf("usage: %s [-h]\n", prog); + print_banner_once(prog, 2); + printf("NUT administration client program to set variables within UPS hardware.\n"); + + printf("\nusage: %s [-h]\n", prog); printf(" %s [-s ] [-u ] [-p ] [-w] [-t ] \n\n", prog); - printf("Demo program to set variables within UPS hardware.\n"); printf("\n"); printf(" -h display this help text\n"); printf(" -V display the version of this software\n"); @@ -668,7 +669,9 @@ int main(int argc, char **argv) tracking_enabled = 1; break; case 'V': - printf("Network UPS Tools %s %s\n", prog, UPS_VERSION); + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(prog, 1); nut_report_config_flags(); exit(EXIT_SUCCESS); case 'h': diff --git a/clients/upssched.c b/clients/upssched.c index 9a8d00dd46..32e5b060be 100644 --- a/clients/upssched.c +++ b/clients/upssched.c @@ -43,17 +43,17 @@ #include #ifndef WIN32 -#include -#include -#include -#include -#include -#include -#include +# include +# include +# include +# include +# include +# include +# include #else -#include "wincompat.h" -#include -#include +# include "wincompat.h" +# include +# include #endif #include "upssched.h" @@ -75,7 +75,7 @@ static const char *upsname, *notify_type; #ifdef WIN32 static OVERLAPPED connect_overlapped; -#define BUF_LEN 512 +# define BUF_LEN 512 #endif #define PARENT_STARTED -2 @@ -286,6 +286,9 @@ static void us_serialize(int op) ret = read(pipefd[0], &ch, 1); close(pipefd[0]); break; + + default: + break; } } #endif @@ -361,7 +364,7 @@ static TYPE_FD open_sock(void) /* Wait for a connection */ ConnectNamedPipe(fd,&connect_overlapped); -#endif +#endif /* WIN32 */ return fd; } @@ -482,7 +485,7 @@ static int send_to_one(conn_t *conn, const char *fmt, ...) return 0; /* failed */ } -#endif +#endif /* WIN32 */ return 1; /* OK */ } @@ -495,9 +498,9 @@ static TYPE_FD conn_add(TYPE_FD sockfd) int ret; conn_t *tmp, *last; struct sockaddr_un saddr; -#if defined(__hpux) && !defined(_XOPEN_SOURCE_EXTENDED) +# if defined(__hpux) && !defined(_XOPEN_SOURCE_EXTENDED) int salen; -#else +# else socklen_t salen; #endif @@ -625,7 +628,7 @@ static TYPE_FD conn_add(TYPE_FD sockfd) upsdebugx(3, "new connection on handle %p", acc); pconf_init(&conn->ctx, NULL); -#endif +#endif /* WIN32 */ return acc; } @@ -753,7 +756,8 @@ static int sock_read(conn_t *conn) /* Restart async read */ memset(conn->buf,0,sizeof(conn->buf)); ReadFile(conn->fd,conn->buf,1,NULL,&(conn->read_overlapped)); -#endif +#endif /* WIN32 */ + ret = pconf_char(&conn->ctx, ch); if (ret == 0) /* nothing to parse yet */ @@ -815,7 +819,7 @@ static void start_daemon(TYPE_FD lockfd) /* child */ /* make fds 0-2 (typically) point somewhere defined */ -#ifdef HAVE_DUP2 +# ifdef HAVE_DUP2 /* system can close (if needed) and (re-)open a specific FD number */ if (1) { /* scoping */ TYPE_FD devnull = open("/dev/null", O_RDWR); @@ -836,8 +840,8 @@ static void start_daemon(TYPE_FD lockfd) close(devnull); } -#else -# ifdef HAVE_DUP +# else /* not HAVE_DUP2 */ +# ifdef HAVE_DUP /* opportunistically duplicate to the "lowest-available" FD number */ close(STDIN_FILENO); if (open("/dev/null", O_RDWR) != STDIN_FILENO) @@ -854,7 +858,7 @@ static void start_daemon(TYPE_FD lockfd) if (dup(STDIN_FILENO) != STDERR_FILENO) fatal_with_errno(EXIT_FAILURE, "dup /dev/null as STDERR"); } -# else +# else /* not HAVE_DUP */ close(STDIN_FILENO); if (open("/dev/null", O_RDWR) != STDIN_FILENO) fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDIN"); @@ -870,8 +874,8 @@ static void start_daemon(TYPE_FD lockfd) if (open("/dev/null", O_RDWR) != STDERR_FILENO) fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDERR"); } -# endif -#endif +# endif /* not HAVE_DUP */ +# endif /* not HAVE_DUP2 */ pipefd = open_sock(); @@ -1048,7 +1052,7 @@ static void start_daemon(TYPE_FD lockfd) checktimers(); } -#endif +#endif /* WIN32 */ } /* --- 'client' functions --- */ @@ -1100,7 +1104,7 @@ static TYPE_FD try_connect(void) if (VALID_FD(pipefd)) return pipefd; -#endif +#endif /* WIN32 */ return ERROR_FD; } @@ -1289,7 +1293,7 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) CloseHandle(pipefd); continue; } -#endif +#endif /* WIN32 */ if (!strncmp(buf, "OK", 2)) return; /* success */ @@ -1530,6 +1534,11 @@ int main(int argc, char **argv) /* just show the optional CONFIG_FLAGS banner */ nut_report_config_flags(); exit(EXIT_SUCCESS); + + default: + fatalx(EXIT_FAILURE, + "Error: unknown option -%c. Try -h for help.", + (char)i); } } diff --git a/common/Makefile.am b/common/Makefile.am index 569ad9c6ec..13bf15c15c 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -3,11 +3,11 @@ # Export certain values for ccache which NUT ci_build.sh can customize, # to facilitate developer iteration re-runs of "make" later. # At least GNU and BSD make implementations are okay with this syntax. -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_DIR=@CCACHE_DIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_PATH=@CCACHE_PATH@ -@NUT_AM_MAKE_CAN_EXPORT@export PATH=@PATH_DURING_CONFIGURE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_NAMESPACE@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_BASEDIR@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_DIR@export CCACHE_DIR=@CCACHE_DIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export CCACHE_PATH=@CCACHE_PATH@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export PATH=@PATH_DURING_CONFIGURE@ AM_CFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include AM_CXXFLAGS = -I$(top_builddir)/include -I$(top_srcdir)/include @@ -45,13 +45,27 @@ endif !BUILDING_IN_TREE $(top_builddir)/include/nut_version.h: +@cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F) +# FIXME: If we maintain some of those helper libs as subsets of the others +# (strictly), maybe build the lowest common denominator only and link the +# bigger scopes with it (rinse and repeat)? libcommon_la_SOURCES = state.c str.c upsconf.c libcommonclient_la_SOURCES = state.c str.c + +# several other Makefiles include the two helpers common.c str.c (and +# perhaps some other string-related code), so make them a library too; +# note that LTLIBOBJS pulls in snprintf.c contents too. +noinst_LTLIBRARIES += libcommonstr.la +libcommonstr_la_SOURCES = str.c +libcommonstr_la_CFLAGS = $(AM_CFLAGS) -DWITHOUT_LIBSYSTEMD=1 +libcommonstr_la_LIBADD = @LTLIBOBJS@ @BSDKVMPROCLIBS@ + if BUILDING_IN_TREE libcommon_la_SOURCES += common.c + libcommonstr_la_SOURCES += common.c libcommonclient_la_SOURCES += common.c else !BUILDING_IN_TREE nodist_libcommon_la_SOURCES = common.c + nodist_libcommonstr_la_SOURCES = common.c nodist_libcommonclient_la_SOURCES = common.c CLEANFILES += $(top_builddir)/common/common.c BUILT_SOURCES = common.c @@ -62,6 +76,7 @@ if HAVE_STRPTIME else !HAVE_STRPTIME # fall back to NetBSD implem libcommon_la_SOURCES += strptime.c + libcommonstr_la_SOURCES += strptime.c libcommonclient_la_SOURCES += strptime.c endif !HAVE_STRPTIME @@ -70,6 +85,7 @@ if HAVE_STRNLEN else !HAVE_STRNLEN # fall back to FreeBSD implem libcommon_la_SOURCES += strnlen.c + libcommonstr_la_SOURCES += strnlen.c libcommonclient_la_SOURCES += strnlen.c endif !HAVE_STRNLEN @@ -78,9 +94,19 @@ if HAVE_STRSEP else !HAVE_STRSEP # fall back to simple implem libcommon_la_SOURCES += strsep.c + libcommonstr_la_SOURCES += strsep.c libcommonclient_la_SOURCES += strsep.c endif !HAVE_STRSEP +if WANT_TIMEGM_FALLBACK + # fall back to simple implem + libcommon_la_SOURCES += timegm_fallback.c + libcommonstr_la_SOURCES += timegm_fallback.c + libcommonclient_la_SOURCES += timegm_fallback.c +else !WANT_TIMEGM_FALLBACK + EXTRA_DIST += timegm_fallback.c +endif !WANT_TIMEGM_FALLBACK + if HAVE_WINDOWS libnutwincompat_la_SOURCES = wincompat.c $(top_srcdir)/include/wincompat.h libnutwincompat_la_LDFLAGS = @@ -97,8 +123,8 @@ endif HAVE_WINDOWS # ensure inclusion of local implementation of missing systems functions # using LTLIBOBJS. Refer to configure.in/.ac -> AC_REPLACE_FUNCS -libcommon_la_LIBADD = libparseconf.la @LTLIBOBJS@ @NETLIBS@ -libcommonclient_la_LIBADD = libparseconf.la @LTLIBOBJS@ @NETLIBS@ +libcommon_la_LIBADD = libparseconf.la @LTLIBOBJS@ @NETLIBS@ @BSDKVMPROCLIBS@ +libcommonclient_la_LIBADD = libparseconf.la @LTLIBOBJS@ @NETLIBS@ @BSDKVMPROCLIBS@ libcommon_la_CFLAGS = $(AM_CFLAGS) libcommonclient_la_CFLAGS = $(AM_CFLAGS) @@ -112,6 +138,9 @@ if HAVE_LIBREGEX libcommon_la_CFLAGS += $(LIBREGEX_CFLAGS) libcommon_la_LIBADD += $(LIBREGEX_LIBS) + libcommonstr_la_CFLAGS += $(LIBREGEX_CFLAGS) + libcommonstr_la_LIBADD += $(LIBREGEX_LIBS) + libcommonclient_la_CFLAGS += $(LIBREGEX_CFLAGS) libcommonclient_la_LIBADD += $(LIBREGEX_LIBS) endif HAVE_LIBREGEX diff --git a/common/common.c b/common/common.c index 8e20c9607c..b126f79fa4 100644 --- a/common/common.c +++ b/common/common.c @@ -1,7 +1,7 @@ /* common.c - common useful functions Copyright (C) 2000 Russell Kroll - Copyright (C) 2021-2022 Jim Klimov + Copyright (C) 2021-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -23,13 +23,19 @@ #include #ifndef WIN32 -#include -#include -#include -#include -#include +# include +# include +# include +# include +# include #else -#include +# include +# include +# include +#endif + +#ifdef HAVE_UNISTD_H +# include /* readlink */ #endif #include @@ -37,6 +43,385 @@ # include #endif +#if (defined WITH_LIBSYSTEMD_INHIBITOR) && (defined WITH_LIBSYSTEMD && WITH_LIBSYSTEMD) && (defined WITH_LIBSYSTEMD_INHIBITOR && WITH_LIBSYSTEMD_INHIBITOR) && !(defined(WITHOUT_LIBSYSTEMD) && (WITHOUT_LIBSYSTEMD)) +# ifdef HAVE_SYSTEMD_SD_BUS_H +# include +# endif +/* Code below is inspired by https://systemd.io/INHIBITOR_LOCKS/ docs, and + * https://github.com/systemd/systemd/issues/34004 discussion which pointed + * to https://github.com/systemd/systemd/blob/main/src/login/inhibit.c tool + * and https://github.com/systemd/systemd/blob/main/src/basic/errno-util.h etc. + * and https://www.freedesktop.org/software/systemd/man/latest/sd_bus_call_method.html + */ +static int RET_NERRNO(int ret) { + if (ret < 0) { + if (errno > 0) + return -EINVAL; + return -errno; + } + + return ret; +} + +/* FIXME: Pedantically speaking, the attribute is assumed supported by GCC + * and CLANG; practically - not sure if we have platforms with sufficiently + * new libsystemd (its headers and example code also use this) and older or + * different compilers. This can be addressed a bit more clumsily directly, + * but we only want to do so if needed in real life. */ +#define _cleanup_(f) __attribute__((cleanup(f))) + +/* The "bus_login_mgr" definition per + * https://github.com/systemd/systemd/blob/4cf7a676af9a79ff418227d8ff488dfca6f243ab/src/shared/bus-locator.c#L24 */ +#define SDBUS_DEST "org.freedesktop.login1" +#define SDBUS_PATH "/org/freedesktop/login1" +#define SDBUS_IFACE "org.freedesktop.login1.Manager" + +static /*_cleanup_(sd_bus_flush_close_unrefp)*/ sd_bus *systemd_bus = NULL; +static int isSupported_Inhibit = -1, isSupported_Inhibit_errno = 0; +static int isSupported_PreparingForSleep = -1, isSupported_PreparingForSleep_errno = 0; + +static void close_sdbus_once(void) { + /* Per https://manpages.debian.org/testing/libsystemd-dev/sd_bus_flush_close_unrefp.3.en.html + * these end-of-life methods do not tell us if we succeeded or failed + * closing the bus connection in any manner, so we here also do not. + */ + + if (!systemd_bus) { + errno = 0; + return; + } + + upsdebugx(1, "%s: trying", __func__); + errno = 0; + sd_bus_flush_close_unrefp(&systemd_bus); + systemd_bus = NULL; +} + +static int open_sdbus_once(const char *caller) { + static int openedOnce = 0, faultReported = 0; + int r = 1; + + errno = 0; + if (systemd_bus) + return r; + +# if defined HAVE_SD_BUS_OPEN_SYSTEM_WITH_DESCRIPTION && HAVE_SD_BUS_OPEN_SYSTEM_WITH_DESCRIPTION + r = sd_bus_open_system_with_description(&systemd_bus, "Bus connection for Network UPS Tools sleep/suspend/hibernate handling"); +# else + r = sd_bus_open_system(&systemd_bus); +# endif + if (r < 0 || !systemd_bus) { + if (r >= 0) { + if (!faultReported) + upsdebugx(1, "%s: Failed to acquire bus for %s(): " + "got null pointer and %d exit-code; setting EINVAL", + __func__, NUT_STRARG(caller), r); + r = -EINVAL; + } else { + if (!faultReported) + upsdebugx(1, "%s: Failed to acquire bus for %s() (%d): %s", + __func__, NUT_STRARG(caller), r, strerror(-r)); + } + faultReported = 1; + } else { + upsdebugx(1, "%s: succeeded for %s", __func__, NUT_STRARG(caller)); + faultReported = 0; + } + + if (systemd_bus && !openedOnce) { + openedOnce = 1; + atexit(close_sdbus_once); + } + + if (systemd_bus) { +# if !(defined HAVE_SD_BUS_OPEN_SYSTEM_WITH_DESCRIPTION && HAVE_SD_BUS_OPEN_SYSTEM_WITH_DESCRIPTION) +# if defined HAVE_SD_BUS_SET_DESCRIPTION && HAVE_SD_BUS_SET_DESCRIPTION + if (sd_bus_set_description(systemd_bus, "Bus connection for Network UPS Tools sleep/suspend/hibernate handling") < 0) + upsdebugx(1, "%s: failed to sd_bus_set_description(), oh well", __func__); +# endif +# endif + + /* second arg for (bool)arg_ask_password - 0 for the non-interactive daemon */ + sd_bus_set_allow_interactive_authorization(systemd_bus, 0); + } + + return r; +} + +static int would_reopen_sdbus(int r) { + if (r >= 0) + return 0; + + switch (-r) { + /* Rule out issues that would not clear themselves (e.g. not stale connections) */ + case ENOENT: + case EPERM: + case EACCES: + return 0; + } + + return 1; +} + +static int reopen_sdbus_once(int r, const char *caller, const char *purpose) +{ + if (r >= 0) + return r; + + switch (-r) { + /* Rule out issues that would not clear themselves (e.g. not stale connections) */ + case ENOENT: + case EPERM: + case EACCES: + break; + + /* An "Invalid request descriptor" might fit this bill */ + default: + upsdebugx(1, "%s for %s() for %s failed (%d) once, will retry D-Bus connection: %s", + __func__, caller, purpose, r, strerror(-r)); + + close_sdbus_once(); + r = open_sdbus_once(caller); + if (r < 0) { + /* Errors, if any, reported above */ + return r; + } + break; + } + + return r; +} + +int isInhibitSupported(void) +{ + return isSupported_Inhibit; +} + +int isPreparingForSleepSupported(void) +{ + return isSupported_PreparingForSleep; +} + +TYPE_FD Inhibit(const char *arg_what, const char *arg_who, const char *arg_why, const char *arg_mode) +{ + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_(sd_bus_message_unrefp) sd_bus_message *reply = NULL; + int r; + TYPE_FD fd = ERROR_FD; + + if (isSupported_Inhibit == 0) { + /* Already determined that we can not use it, e.g. due to perms */ + errno = isSupported_Inhibit_errno; + return -errno; + } + + /* Not found in public headers: + bool arg_ask_password = true; + (void) polkit_agent_open_if_enabled(BUS_TRANSPORT_LOCAL, arg_ask_password); + */ + + r = open_sdbus_once(__func__); + if (r < 0) { + /* Errors, if any, reported above */ + return r; + } + + r = sd_bus_call_method(systemd_bus, SDBUS_DEST, SDBUS_PATH, SDBUS_IFACE, "Inhibit", &error, &reply, "ssss", arg_what, arg_who, arg_why, arg_mode); + if (r < 0) { + if (would_reopen_sdbus(r)) { + if ((r = reopen_sdbus_once(r, __func__, "sd_bus_call_method()")) < 0) + return r; + + r = sd_bus_call_method(systemd_bus, SDBUS_DEST, SDBUS_PATH, SDBUS_IFACE, "Inhibit", &error, &reply, "ssss", arg_what, arg_who, arg_why, arg_mode); + } else { + /* Permissions for the privileged operation... did it ever succeed? */ + if (isSupported_Inhibit < 0) { + upsdebugx(1, "%s: %s() failed seemingly due to permissions, marking %s as not supported", + __func__, "sd_bus_call_method", "Inhibit"); + isSupported_Inhibit = 0; + isSupported_Inhibit_errno = r; + } + } + + if (r < 0) { + upsdebugx(1, "%s: %s() failed (%d): %s", + __func__, "sd_bus_call_method", r, strerror(-r)); + if (error.message && *(error.message)) + upsdebugx(2, "%s: details from libsystemd: %s", + __func__, error.message); + return r; + } else { + upsdebugx(1, "%s: reconnection to D-Bus helped with %s()", + __func__, "sd_bus_call_method"); + } + } + + r = sd_bus_message_read_basic(reply, SD_BUS_TYPE_UNIX_FD, &fd); + if (r < 0) { + upsdebugx(1, "%s: %s() failed (%d): %s", + __func__, "sd_bus_message_read_basic", r, strerror(-r)); + if (isSupported_Inhibit < 0 && !would_reopen_sdbus(r)) { + upsdebugx(1, "%s: %s() failed seemingly due to permissions, marking %s as not supported", + __func__, "sd_bus_message_read_basic", "Inhibit"); + isSupported_Inhibit = 0; + isSupported_Inhibit_errno = r; + } + return r; + } + + /* Data query succeeded, so it is supported */ + isSupported_Inhibit = 1; + + /* NOTE: F_DUPFD_CLOEXEC is in POSIX.1-2008 (Linux 2.6.24); seek out + * an alternative sequence of options if needed on older systems */ + r = RET_NERRNO(fcntl(fd, F_DUPFD_CLOEXEC, 3)); + if (r < 0) { + upsdebugx(1, "%s: fcntl() failed (%d): %s", + __func__, r, strerror(-r)); + return fd; + } + + return r; +} + +void Uninhibit(TYPE_FD *fd_ptr) +{ + if (!fd_ptr) + return; + if (INVALID_FD(*fd_ptr)) + return; + + /* Closing the socket allows systemd to proceed (we un-inhibit our lock on system + * life-cycle handling). After waking up, we should Inhibit() anew, if needed. + */ + close(*fd_ptr); + *fd_ptr = ERROR_FD; +} + +int isPreparingForSleep(void) +{ + static int32_t prev = -1; + int32_t val = 0; /* 4-byte int expected for SD_BUS_TYPE_BOOLEAN aka 'b' */ + int r; + _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + + if (isSupported_PreparingForSleep == 0) { + /* Already determined that we can not use it, e.g. due to perms */ + errno = isSupported_PreparingForSleep_errno; + return -errno; + } + + r = open_sdbus_once(__func__); + if (r < 0) { + /* Errors, if any, reported above */ + return r; + } + + /* @org.freedesktop.DBus.Property.EmitsChangedSignal("false") + * readonly b PreparingForSleep = ...; + * https://www.freedesktop.org/software/systemd/man/latest/org.freedesktop.login1.html + * https://www.freedesktop.org/software/systemd/man/latest/sd_bus_set_property.html + * https://www.freedesktop.org/software/systemd/man/latest/sd_bus_message_append.html (data types) + */ + r = sd_bus_get_property_trivial(systemd_bus, SDBUS_DEST, SDBUS_PATH, SDBUS_IFACE, "PreparingForSleep", &error, SD_BUS_TYPE_BOOLEAN, &val); + if (r < 0) { + if (would_reopen_sdbus(r)) { + if ((r = reopen_sdbus_once(r, __func__, "sd_bus_get_property_trivial()")) < 0) + return r; + + r = sd_bus_get_property_trivial(systemd_bus, SDBUS_DEST, SDBUS_PATH, SDBUS_IFACE, "PreparingForSleep", &error, 'b', &val); + } else { + if (isSupported_PreparingForSleep < 0) { + upsdebugx(1, "%s: %s() failed seemingly due to permissions, marking %s as not supported", + __func__, "sd_bus_get_property_trivial", "PreparingForSleep"); + isSupported_PreparingForSleep = 0; + isSupported_PreparingForSleep_errno = r; + } + } + + if (r < 0) { + upsdebugx(1, "%s: %s() failed (%d): %s", + __func__, "sd_bus_get_property_trivial", r, strerror(-r)); + if (error.message && *(error.message)) + upsdebugx(2, "%s: details from libsystemd: %s", + __func__, error.message); + return r; + } else { + upsdebugx(1, "%s: reconnection to D-Bus helped with %s()", + __func__, "sd_bus_get_property_trivial"); + } + } + + /* Data query succeeded, so it is supported */ + isSupported_PreparingForSleep = 1; + + if (val == prev) { + /* Unchanged */ + return -1; + } + + /* First run and not immediately going to sleep, assume unchanged (no-op for upsmon et al) */ + if (prev < 0 && !val) { + prev = val; + return -1; + } + + /* 0 or 1 */ + prev = val; + return val; +} + +#else /* not WITH_LIBSYSTEMD_INHIBITOR */ + +int isInhibitSupported(void) +{ + return 0; +} + +int isPreparingForSleepSupported(void) +{ + return 0; +} + +TYPE_FD Inhibit(const char *arg_what, const char *arg_who, const char *arg_why, const char *arg_mode) +{ + static int reported = 0; + NUT_UNUSED_VARIABLE(arg_what); + NUT_UNUSED_VARIABLE(arg_who); + NUT_UNUSED_VARIABLE(arg_why); + NUT_UNUSED_VARIABLE(arg_mode); + + if (!reported) { + upsdebugx(6, "%s: Not implemented on this platform", __func__); + reported = 1; + } + return ERROR_FD; +} + +int isPreparingForSleep(void) +{ + static int reported = 0; + + if (!reported) { + upsdebugx(6, "%s: Not implemented on this platform", __func__); + reported = 1; + } + return -1; +} + +void Uninhibit(TYPE_FD *fd_ptr) +{ + static int reported = 0; + NUT_UNUSED_VARIABLE(fd_ptr); + + if (!reported) { + upsdebugx(6, "%s: Not implemented on this platform", __func__); + reported = 1; + } +} + +#endif /* not WITH_LIBSYSTEMD_INHIBITOR */ + #ifdef WITH_LIBSYSTEMD # include /* upsnotify() debug-logs its reports; a watchdog ping is something we @@ -48,15 +433,15 @@ static int upsnotify_reported_disabled_systemd = 0; /* Define this to 1 for lots of spam at debug level 6, and ignoring WATCHDOG_PID * so trying to post reports anyway if WATCHDOG_USEC is valid */ # define DEBUG_SYSTEMD_WATCHDOG 0 -# endif -#endif +# endif /* DEBUG_SYSTEMD_WATCHDOG */ +#endif /* WITH_LIBSYSTEMD */ /* Similarly for only reporting once if the notification subsystem is not built-in */ static int upsnotify_reported_disabled_notech = 0; static int upsnotify_report_verbosity = -1; /* the reason we define UPS_VERSION as a static string, rather than a macro, is to make dependency tracking easier (only common.o depends - on nut_version_macro.h), and also to prevent all sources from + on nut_version.h), and also to prevent all sources from having to be recompiled each time the version changes (they only need to be re-linked). */ #include "nut_version.h" @@ -79,6 +464,17 @@ const char *UPS_VERSION = NUT_VERSION_MACRO; #include #include #include + +#if defined(HAVE_LIB_BSD_KVM_PROC) && HAVE_LIB_BSD_KVM_PROC +# include +# include +# include +#endif + +#if defined(HAVE_LIB_ILLUMOS_PROC) && HAVE_LIB_ILLUMOS_PROC +# include +#endif + pid_t get_max_pid_t(void) { #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE @@ -108,6 +504,23 @@ pid_t get_max_pid_t(void) #endif } + /* Normally sendsignalfn(), sendsignalpid() and related methods call + * upslogx() to report issues such as failed fopen() of PID file, + * failed parse of its contents, inability to send a signal (absent + * process or some other issue like permissions). + * Some of these low-level reports look noisy and scary to users, + * others are a bit confusing ("PID file not found... is it bad or + * good, what do I do with that knowledge?") so several consuming + * programs actually parse the returned codes to formulate their + * own messages like "No earlier instance of this daemon was found + * running" and users benefit even less from low-level reports. + * This variable and its values are a bit of internal detail between + * certain NUT programs to hush the low-level reports when they are + * not being otherwise debugged (e.g. nut_debug_level < 1). + * Default value allows all those messages to appear. + */ + int nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT; + int nut_debug_level = 0; int nut_log_level = 0; static int upslog_flags = UPSLOG_STDERR; @@ -129,11 +542,124 @@ static int xbit_test(int val, int flag) return ((val & flag) == flag); } +int syslog_is_disabled(void) +{ + static int value = -1; + + if (value < 0) { + char *s = getenv("NUT_DEBUG_SYSLOG"); + /* Not set or not disabled by the setting: default is enabled (inversed per method name) */ + value = 0; + if (s) { + if (!strcmp(s, "stderr")) { + value = 1; + } else if (!strcmp(s, "none") || !strcmp(s, "false")) { + value = 2; + } else if (!strcmp(s, "syslog") || !strcmp(s, "true") || !strcmp(s, "default")) { + /* Just reserve a value to quietly do the default */ + value = 0; + } else { + upsdebugx(0, "%s: unknown NUT_DEBUG_SYSLOG='%s' value, ignored (assuming enabled)", + __func__, s); + } + } + } + + return value; +} + +int banner_is_disabled(void) +{ + static int value = -1; + + if (value < 0) { + char *s = getenv("NUT_QUIET_INIT_BANNER"); + /* Envvar present and empty or true-ish means NUT tool name+version + * banners disabled by the setting: default is enabled (inversed per + * method name) */ + value = 0; + if (s) { + if (*s == '\0' || !strcasecmp(s, "true") || strcmp(s, "1")) { + value = 1; + } + } + } + + return value; +} + +const char *describe_NUT_VERSION_once(void) +{ + static char buf[LARGEBUF]; + static const char *printed = NULL; + + if (printed) + return printed; + + memset(buf, 0, sizeof(buf)); + +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#endif + /* NOTE: Some compilers deduce that macro-based decisions about + * NUT_VERSION_IS_RELEASE make one of codepaths unreachable in + * a particular build. So we pragmatically handwave this away. + */ + if (1 < snprintf(buf, sizeof(buf), + "%s %s%s%s", + NUT_VERSION_MACRO, + NUT_VERSION_IS_RELEASE ? "release" : "(development iteration after ", + NUT_VERSION_IS_RELEASE ? "" : NUT_VERSION_SEMVER_MACRO, + NUT_VERSION_IS_RELEASE ? "" : ")" + )) { + printed = buf; + } else { + upslogx(LOG_WARNING, "%s: failed to report detailed NUT version", __func__); + printed = UPS_VERSION; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic pop +#endif + + return printed; +} + +int print_banner_once(const char *prog, int even_if_disabled) +{ + static int printed = 0; + static int ret = -1; + + if (printed) + return ret; + + if (!banner_is_disabled() || even_if_disabled) { + ret = printf("Network UPS Tools %s %s%s\n", + prog, describe_NUT_VERSION_once(), + even_if_disabled == 2 ? "\n" : ""); + fflush(stdout); + if (ret > 0) + printed = 1; + } + + return ret; +} + /* enable writing upslog_with_errno() and upslogx() type messages to the syslog */ void syslogbit_set(void) { - xbit_set(&upslog_flags, UPSLOG_SYSLOG); + if (!syslog_is_disabled()) + xbit_set(&upslog_flags, UPSLOG_SYSLOG); } /* get the syslog ready for us */ @@ -142,12 +668,15 @@ void open_syslog(const char *progname) #ifndef WIN32 int opt; + if (syslog_is_disabled()) + return; + opt = LOG_PID; /* we need this to grab /dev/log before chroot */ -#ifdef LOG_NDELAY +# ifdef LOG_NDELAY opt |= LOG_NDELAY; -#endif +# endif /* LOG_NDELAY */ openlog(progname, opt, LOG_FACILITY); @@ -180,31 +709,43 @@ void open_syslog(const char *progname) break; default: fatalx(EXIT_FAILURE, "Invalid log level threshold"); -#else +# else case 0: break; default: upslogx(LOG_INFO, "Changing log level threshold not possible"); break; -#endif +# endif /* HAVE_SETLOGMASK && HAVE_DECL_LOG_UPTO */ } #else EventLogName = progname; -#endif +#endif /* WIND32 */ } /* close ttys and become a daemon */ void background(void) { + /* Normally we enable SYSLOG and disable STDERR, + * unless NUT_DEBUG_SYSLOG envvar interferes as + * interpreted in syslog_is_disabled() method: */ + int syslog_disabled = syslog_is_disabled(), + stderr_disabled = (syslog_disabled == 0 || syslog_disabled == 2); + #ifndef WIN32 int pid; if ((pid = fork()) < 0) fatal_with_errno(EXIT_FAILURE, "Unable to enter background"); +#endif - xbit_set(&upslog_flags, UPSLOG_SYSLOG); - xbit_clear(&upslog_flags, UPSLOG_STDERR); + if (!syslog_disabled) + /* not disabled: NUT_DEBUG_SYSLOG is unset or invalid */ + xbit_set(&upslog_flags, UPSLOG_SYSLOG); + if (stderr_disabled) + /* NUT_DEBUG_SYSLOG="none" or unset/invalid */ + xbit_clear(&upslog_flags, UPSLOG_STDERR); +#ifndef WIN32 if (pid != 0) { /* parent */ /* these are typically fds 0-2: */ @@ -217,7 +758,7 @@ void background(void) /* child */ /* make fds 0-2 (typically) point somewhere defined */ -#ifdef HAVE_DUP2 +# ifdef HAVE_DUP2 /* system can close (if needed) and (re-)open a specific FD number */ if (1) { /* scoping */ TYPE_FD devnull = open("/dev/null", O_RDWR); @@ -228,13 +769,15 @@ void background(void) fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDIN"); if (dup2(devnull, STDOUT_FILENO) != STDOUT_FILENO) fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDOUT"); - if (dup2(devnull, STDERR_FILENO) != STDERR_FILENO) - fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDERR"); + if (stderr_disabled) { + if (dup2(devnull, STDERR_FILENO) != STDERR_FILENO) + fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDERR"); + } close(devnull); } -#else -# ifdef HAVE_DUP +# else +# ifdef HAVE_DUP /* opportunistically duplicate to the "lowest-available" FD number */ close(STDIN_FILENO); if (open("/dev/null", O_RDWR) != STDIN_FILENO) @@ -244,10 +787,12 @@ void background(void) if (dup(STDIN_FILENO) != STDOUT_FILENO) fatal_with_errno(EXIT_FAILURE, "dup /dev/null as STDOUT"); - close(STDERR_FILENO); - if (dup(STDIN_FILENO) != STDERR_FILENO) - fatal_with_errno(EXIT_FAILURE, "dup /dev/null as STDERR"); -# else + if (stderr_disabled) { + close(STDERR_FILENO); + if (dup(STDIN_FILENO) != STDERR_FILENO) + fatal_with_errno(EXIT_FAILURE, "dup /dev/null as STDERR"); + } +# else close(STDIN_FILENO); if (open("/dev/null", O_RDWR) != STDIN_FILENO) fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDIN"); @@ -256,20 +801,18 @@ void background(void) if (open("/dev/null", O_RDWR) != STDOUT_FILENO) fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDOUT"); - close(STDERR_FILENO); - if (open("/dev/null", O_RDWR) != STDERR_FILENO) - fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDERR"); + if (stderr_disabled) { + close(STDERR_FILENO); + if (open("/dev/null", O_RDWR) != STDERR_FILENO) + fatal_with_errno(EXIT_FAILURE, "re-open /dev/null as STDERR"); + } +# endif # endif -#endif -#ifdef HAVE_SETSID +# ifdef HAVE_SETSID setsid(); /* make a new session to dodge signals */ -#endif - -#else /* WIN32 */ - xbit_set(&upslog_flags, UPSLOG_SYSLOG); - xbit_clear(&upslog_flags, UPSLOG_STDERR); -#endif +# endif +#endif /* not WIN32 */ upslogx(LOG_INFO, "Startup successful"); } @@ -321,77 +864,749 @@ struct passwd *get_user_pwent(const char *name) #endif } -/* change to the user defined in the struct */ -void become_user(struct passwd *pw) +/* change to the user defined in the struct */ +void become_user(struct passwd *pw) +{ +#ifndef WIN32 + /* if we can't switch users, then don't even try */ + intmax_t initial_uid = getuid(); + intmax_t initial_euid = geteuid(); + + if (!pw) { + upsdebugx(1, "Can not become_user(), skipped"); + return; + } + + if ((initial_euid != 0) && (initial_uid != 0)) { + intmax_t initial_gid = getgid(); + if (initial_euid == (intmax_t)pw->pw_uid + || initial_uid == (intmax_t)pw->pw_uid + ) { + upsdebugx(1, "No need to become_user(%s): " + "already UID=%jd GID=%jd", + pw->pw_name, initial_uid, initial_gid); + } else { + upsdebugx(1, "Can not become_user(%s): " + "not root initially, " + "remaining UID=%jd GID=%jd", + pw->pw_name, initial_uid, initial_gid); + } + return; + } + + if (initial_uid == 0) + if (seteuid(0)) + fatal_with_errno(EXIT_FAILURE, "getuid gave 0, but seteuid(0) failed"); + + if (initgroups(pw->pw_name, pw->pw_gid) == -1) + fatal_with_errno(EXIT_FAILURE, "initgroups"); + + if (setgid(pw->pw_gid) == -1) + fatal_with_errno(EXIT_FAILURE, "setgid"); + + if (setuid(pw->pw_uid) == -1) + fatal_with_errno(EXIT_FAILURE, "setuid"); + + upsdebugx(1, "Succeeded to become_user(%s): now UID=%jd GID=%jd", + pw->pw_name, (intmax_t)getuid(), (intmax_t)getgid()); +#else + upsdebugx(1, "Can not become_user(%s): not implemented on this platform", + pw ? pw->pw_name : ""); +#endif +} + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) ) +# pragma GCC diagnostic push +#endif +#if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif +#if (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) +# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif +/* drop down into a directory and throw away pointers to the old path */ +void chroot_start(const char *path) +{ + if (chdir(path)) + fatal_with_errno(EXIT_FAILURE, "chdir(%s)", path); + +#ifndef WIN32 + if (chroot(path)) + fatal_with_errno(EXIT_FAILURE, "chroot(%s)", path); + +#else + upsdebugx(1, "Can not chroot into %s: not implemented on this platform", path); +#endif + + if (chdir("/")) + fatal_with_errno(EXIT_FAILURE, "chdir(/)"); + +#ifndef WIN32 + upsdebugx(1, "chrooted into %s", path); +#endif +} + +char * getprocname(pid_t pid) +{ + /* Try to identify process (program) name for the given PID, + * return NULL if we can not for any reason (does not run, + * no rights, do not know how to get it on current OS, etc.) + * If the returned value is not NULL, caller should free() it. + * Some implementation pieces borrowed from + * https://man7.org/linux/man-pages/man2/readlink.2.html and + * https://github.com/openbsd/src/blob/master/bin/ps/ps.c + * NOTE: Very much platform-dependent! + */ + char *procname = NULL; + size_t procnamelen = 0; +#ifdef UNIX_PATH_MAX + char pathname[UNIX_PATH_MAX]; +#else + char pathname[PATH_MAX]; +#endif + struct stat st; + +#ifdef WIN32 + /* Try Windows API calls, then fall through to /proc emulation in MinGW/MSYS2 + * https://stackoverflow.com/questions/1591342/c-how-to-determine-if-a-windows-process-is-running + * http://cppip.blogspot.com/2013/01/check-if-process-is-running.html + */ + upsdebugx(5, "%s: begin to query WIN32 process info", __func__); + HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, (DWORD)pid); + if (process) { + DWORD ret = GetModuleFileNameExA( + process, /* hProcess */ + NULL, /* hModule */ + (LPSTR)pathname, + (DWORD)(sizeof(pathname)) + ); + CloseHandle(process); + pathname[sizeof(pathname) - 1] = '\0'; + + if (ret) { + /* length of the string copied to the buffer */ + procnamelen = strlen(pathname); + + upsdebugx(3, "%s: try to parse the name from WIN32 process info", + __func__); + if (ret != procnamelen) { + upsdebugx(3, "%s: length mismatch getting WIN32 process info: %" + PRIuMAX " vs. " PRIuSIZE, + __func__, (uintmax_t)ret, procnamelen); + } + + if ((procname = (char*)calloc(procnamelen + 1, sizeof(char)))) { + if (snprintf(procname, procnamelen + 1, "%s", pathname) < 1) { + upsdebug_with_errno(3, "%s: failed to snprintf procname: WIN32-like", __func__); + } else { + goto finish; + } + } else { + upsdebug_with_errno(3, "%s: failed to allocate the procname " + "string to store token from WIN32 size %" PRIuSIZE, + __func__, procnamelen); + } + + /* Fall through to try /proc etc. if available */ + } else { + LPVOID WinBuf; + DWORD WinErr = GetLastError(); + FormatMessage( + FORMAT_MESSAGE_MAX_WIDTH_MASK | + FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, + WinErr, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + (LPTSTR) &WinBuf, + 0, NULL ); + + upsdebugx(3, "%s: failed to get WIN32 process info: %s", + __func__, (char *)WinBuf); + LocalFree(WinBuf); + } + } +#endif + + if (stat("/proc", &st) == 0 && ((st.st_mode & S_IFMT) == S_IFDIR)) { + upsdebugx(3, "%s: /proc is an accessible directory, investigating", __func__); + +#if (defined HAVE_READLINK) && HAVE_READLINK + /* Linux-like */ + if (snprintf(pathname, sizeof(pathname), "/proc/%" PRIuMAX "/exe", (uintmax_t)pid) < 10) { + upsdebug_with_errno(3, "%s: failed to snprintf pathname: Linux-like", __func__); + goto finish; + } + + if (lstat(pathname, &st) == 0) { + goto process_stat_symlink; + } + + /* FreeBSD-like */ + if (snprintf(pathname, sizeof(pathname), "/proc/%" PRIuMAX "/file", (uintmax_t)pid) < 10) { + upsdebug_with_errno(3, "%s: failed to snprintf pathname: FreeBSD-like", __func__); + goto finish; + } + + if (lstat(pathname, &st) == 0) { + goto process_stat_symlink; + } + + goto process_parse_file; + +process_stat_symlink: + upsdebugx(3, "%s: located symlink for PID %" PRIuMAX " at: %s", + __func__, (uintmax_t)pid, pathname); + /* Some magic symlinks under (for example) /proc and /sys + * report 'st_size' as zero. In that case, take PATH_MAX + * or equivalent as a "good enough" estimate. */ + if (st.st_size) { + /* Add one for ending '\0' */ + procnamelen = st.st_size + 1; + } else { + procnamelen = sizeof(pathname); + } + + /* Not xcalloc() here, not too fatal if we fail */ + procname = (char*)calloc(procnamelen, sizeof(char)); + if (procname) { + int nbytes = readlink(pathname, procname, procnamelen); + if (nbytes < 0) { + upsdebug_with_errno(1, "%s: failed to readlink() from %s", + __func__, pathname); + free(procname); + procname = NULL; + goto process_parse_file; + } +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + if ((unsigned int)nbytes > SIZE_MAX || procnamelen <= (size_t)nbytes) { +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) ) +# pragma GCC diagnostic pop +#endif + upsdebugx(1, "%s: failed to readlink() from %s: may have been truncated", + __func__, pathname); + free(procname); + procname = NULL; + goto process_parse_file; + } + + /* Got a useful reply */ + procname[nbytes] = '\0'; + goto finish; + } else { + upsdebug_with_errno(3, "%s: failed to allocate the procname string " + "to readlink() size %" PRIuSIZE, __func__, procnamelen); + goto finish; + } +#else + upsdebugx(3, "%s: this platform does not have readlink(), skipping this method", __func__); + goto process_parse_file; +#endif /* HAVE_READLINK */ + +process_parse_file: + upsdebugx(5, "%s: try to parse some files under /proc", __func__); + + /* Check /proc/NNN/cmdline (may start with a '-' to ignore, for + * a title string like "-bash" where programs edit their argv[0] + * (Linux-like OSes at least). Inspired by + * https://gist.github.com/evanslai/30c6d588a80222f665f10b4577dadd61 + */ + if (snprintf(pathname, sizeof(pathname), "/proc/%" PRIuMAX "/cmdline", (uintmax_t)pid) < 10) { + upsdebug_with_errno(3, "%s: failed to snprintf pathname: Linux-like", __func__); + goto finish; + } + + if (stat(pathname, &st) == 0) { + FILE* fp = fopen(pathname, "r"); + if (fp) { + char buf[sizeof(pathname)]; + if (fgets(buf, sizeof(buf), fp) != NULL) { + /* check the first token in the file, the program name */ + char* first = strtok(buf, " "); + + fclose(fp); + if (first) { + if (*first == '-') + first++; + + /* Not xcalloc() here, not too fatal if we fail */ + if ((procnamelen = strlen(first))) { + upsdebugx(3, "%s: try to parse some files under /proc: processing %s", + __func__, pathname); + if ((procname = (char*)calloc(procnamelen + 1, sizeof(char)))) { + if (snprintf(procname, procnamelen + 1, "%s", first) < 1) { + upsdebug_with_errno(3, "%s: failed to snprintf procname: Linux-like", __func__); + } + } else { + upsdebug_with_errno(3, "%s: failed to allocate the procname " + "string to store token from 'cmdline' size %" PRIuSIZE, + __func__, procnamelen); + } + + goto finish; + } + } + } else { + fclose(fp); + } + } + } + + /* Check /proc/NNN/stat (second token, in parentheses, may be truncated) + * see e.g. https://stackoverflow.com/a/12675103/4715872 */ + if (snprintf(pathname, sizeof(pathname), "/proc/%" PRIuMAX "/stat", (uintmax_t)pid) < 10) { + upsdebug_with_errno(3, "%s: failed to snprintf pathname: Linux-like", __func__); + goto finish; + } + + if (stat(pathname, &st) == 0) { + FILE* fp = fopen(pathname, "r"); + if (fp) { + long spid; + char sstate; + char buf[sizeof(pathname)]; + + memset (buf, 0, sizeof(buf)); + if ( (fscanf(fp, "%ld (%[^)]) %c", &spid, buf, &sstate)) == 3 ) { + /* Some names can be pretty titles like "init(Ubuntu)" + * or "Relay(223)". Or truncated like "docker-desktop-". + * Tokenize by "(" " " and extract the first token to + * address the former "problem", not too much we can + * do about the latter except for keeping NUT program + * names concise. + */ + char* first = strtok(buf, "( "); + + fclose(fp); + if (first) { + /* Not xcalloc() here, not too fatal if we fail */ + if ((procnamelen = strlen(first))) { + upsdebugx(3, "%s: try to parse some files under /proc: processing %s " + "(WARNING: may be truncated)", + __func__, pathname); + if ((procname = (char*)calloc(procnamelen + 1, sizeof(char)))) { + if (snprintf(procname, procnamelen + 1, "%s", first) < 1) { + upsdebug_with_errno(3, "%s: failed to snprintf procname: Linux-like", __func__); + } + } else { + upsdebug_with_errno(3, "%s: failed to allocate the procname " + "string to store token from 'stat' size %" PRIuSIZE, + __func__, procnamelen); + } + + goto finish; + } + } + } else { + fclose(fp); + } + } + } + +#if defined(HAVE_LIB_ILLUMOS_PROC) && HAVE_LIB_ILLUMOS_PROC + /* Solaris/illumos: parse binary structure at /proc/NNN/psinfo */ + if (snprintf(pathname, sizeof(pathname), "/proc/%" PRIuMAX "/psinfo", (uintmax_t)pid) < 10) { + upsdebug_with_errno(3, "%s: failed to snprintf pathname: Solaris/illumos-like", __func__); + goto finish; + } + + if (stat(pathname, &st) == 0) { + FILE* fp = fopen(pathname, "r"); + if (!fp) { + upsdebug_with_errno(3, "%s: try to parse '%s':" + "fopen() returned NULL", __func__, pathname); + } else { + psinfo_t info; /* process information from /proc */ + size_t r; + + memset (&info, 0, sizeof(info)); + r = fread((char *)&info, sizeof (info), 1, fp); + if (r != 1) { + upsdebug_with_errno(3, "%s: try to parse '%s': " + "unexpected read size: got %" PRIuSIZE + " record(s) from file of size %" PRIuMAX + " vs. 1 piece of %" PRIuSIZE " struct size", + __func__, pathname, r, + (uintmax_t)st.st_size, sizeof (info)); + fclose(fp); + } else { + fclose(fp); + + /* Not xcalloc() here, not too fatal if we fail */ + if ((procnamelen = strlen(info.pr_fname))) { + upsdebugx(3, "%s: try to parse some files under /proc: processing %s", + __func__, pathname); + if ((procname = (char*)calloc(procnamelen + 1, sizeof(char)))) { + if (snprintf(procname, procnamelen + 1, "%s", info.pr_fname) < 1) { + upsdebug_with_errno(3, "%s: failed to snprintf pathname: Solaris/illumos-like", __func__); + } + } else { + upsdebug_with_errno(3, "%s: failed to allocate the procname " + "string to store token from 'psinfo' size %" PRIuSIZE, + __func__, procnamelen); + } + + goto finish; + } + } + } + } +#endif + } else { + upsdebug_with_errno(3, "%s: /proc is not a directory or not accessible", __func__); + } + +#if defined(HAVE_LIB_BSD_KVM_PROC) && HAVE_LIB_BSD_KVM_PROC + /* OpenBSD, maybe other BSD: no /proc; use API call, see ps.c link above and + * https://kaashif.co.uk/2015/06/18/how-to-get-a-list-of-processes-on-openbsd-in-c/ + */ + if (!procname) { + char errbuf[_POSIX2_LINE_MAX]; + kvm_t *kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + + upsdebugx(3, "%s: try to parse BSD KVM process info snapsnot", __func__); + if (!kd) { + upsdebugx(3, "%s: try to parse BSD KVM process info snapsnot: " + "kvm_openfiles() returned NULL", __func__); + } else { + int nentries = 0; + struct kinfo_proc *kp = kvm_getprocs(kd, KERN_PROC_PID, pid, sizeof(*kp), &nentries); + + if (!kp) { + upsdebugx(3, "%s: try to parse BSD KVM process info snapsnot: " + "kvm_getprocs() returned NULL", __func__); + } else { + int i; + if (nentries != 1) + upsdebugx(3, "%s: expected to get 1 reply from BSD kvm_getprocs but got %d", + __func__, nentries); + for (i = 0; i < nentries; i++) { + upsdebugx(5, "%s: processing reply #%d from BSD" + " kvm_getprocs: pid=%" PRIuMAX " name='%s'", + __func__, i, (uintmax_t)kp[i].p_pid, kp[i].p_comm); + if ((uintmax_t)(kp[i].p_pid) == (uintmax_t)pid) { + /* Not xcalloc() here, not too fatal if we fail */ + if ((procnamelen = strlen(kp[i].p_comm))) { + if ((procname = (char*)calloc(procnamelen + 1, sizeof(char)))) { + if (snprintf(procname, procnamelen + 1, "%s", kp[i].p_comm) < 1) { + upsdebug_with_errno(3, "%s: failed to snprintf procname: BSD-like", __func__); + } + } else { + upsdebug_with_errno(3, "%s: failed to allocate the procname " + "string to store token from BSD KVM process info " + "snapsnot size %" PRIuSIZE, + __func__, procnamelen); + } + + goto finish; + } + } + } + } + } + } +#endif /* HAVE_LIB_BSD_KVM_PROC */ + + goto finish; + +finish: + if (procname) { + procnamelen = strlen(procname); + if (procnamelen == 0) { + free(procname); + procname = NULL; + } else { + upsdebugx(1, "%s: determined process name for PID %" PRIuMAX ": %s", + __func__, (uintmax_t)pid, procname); + } + } + + if (!procname) { + upsdebugx(1, "%s: failed to determine process name for PID %" PRIuMAX, + __func__, (uintmax_t)pid); + } + + return procname; +} +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_BESIDEFUNC) && (!defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP_INSIDEFUNC) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS_BESIDEFUNC) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE_BESIDEFUNC) ) +# pragma GCC diagnostic pop +#endif + +size_t parseprogbasename(char *buf, size_t buflen, const char *progname, size_t *pprogbasenamelen, size_t *pprogbasenamedot) +{ + size_t i, + progbasenamelen = 0, + progbasenamedot = 0; + + if (pprogbasenamelen) + *pprogbasenamelen = 0; + + if (pprogbasenamedot) + *pprogbasenamedot = 0; + + if (!buf || !progname || !buflen || progname[0] == '\0') + return 0; + + for (i = 0; i < buflen && progname[i] != '\0'; i++) { + if (progname[i] == '/' +#ifdef WIN32 + || progname[i] == '\\' +#endif + ) { + progbasenamelen = 0; + progbasenamedot = 0; + continue; + } + + if (progname[i] == '.') + progbasenamedot = progbasenamelen; + + buf[progbasenamelen++] = progname[i]; + } + buf[progbasenamelen] = '\0'; + buf[buflen - 1] = '\0'; + + if (pprogbasenamelen) + *pprogbasenamelen = progbasenamelen; + + if (pprogbasenamedot) + *pprogbasenamedot = progbasenamedot; + + return progbasenamelen; +} + +int checkprocname_ignored(const char *caller) { -#ifndef WIN32 - /* if we can't switch users, then don't even try */ - intmax_t initial_uid = getuid(); - intmax_t initial_euid = geteuid(); + char *s = NULL; - if (!pw) { - upsdebugx(1, "Can not become_user(), skipped"); - return; + if ((s = getenv("NUT_IGNORE_CHECKPROCNAME"))) { + /* FIXME: Make server/conf.c::parse_boolean() reusable */ + if ( (!strcasecmp(s, "true")) || (!strcasecmp(s, "on")) || (!strcasecmp(s, "yes")) || (!strcasecmp(s, "1"))) { + upsdebugx(1, "%s for %s: skipping because caller set NUT_IGNORE_CHECKPROCNAME", __func__, NUT_STRARG(caller)); + return 1; + } } - if ((initial_euid != 0) && (initial_uid != 0)) { - intmax_t initial_gid = getgid(); - if (initial_euid == (intmax_t)pw->pw_uid - || initial_uid == (intmax_t)pw->pw_uid + return 0; +} + +int compareprocname(pid_t pid, const char *procname, const char *progname) +{ + /* Given the binary path name of (presumably) a running process, + * check if it matches the assumed name of the current program. + * The "pid" value is used in log reporting. + * Returns: + * -3 Skipped because NUT_IGNORE_CHECKPROCNAME is set + * -2 Could not parse a program name (ok to proceed, + * risky - but matches legacy behavior) + * -1 Could not identify a program name (ok to proceed, + * risky - but matches legacy behavior) + * 0 Process name identified, does not seem to match + * 1+ Process name identified, and seems to match with + * varying precision + * Generally speaking, if (compareprocname(...)) then ok to proceed + */ + + int ret = -127; + size_t procbasenamelen = 0, progbasenamelen = 0; + /* Track where the last dot is in the basename; 0 means none */ + size_t procbasenamedot = 0, progbasenamedot = 0; +#ifdef UNIX_PATH_MAX + char procbasename[UNIX_PATH_MAX], progbasename[UNIX_PATH_MAX]; +#else + char procbasename[PATH_MAX], progbasename[PATH_MAX]; +#endif + + if (checkprocname_ignored(__func__)) { + ret = -3; + goto finish; + } + + if (!procname || !progname) { + ret = -1; + goto finish; + } + + /* First quickly try for an exact hit (possible dir names included) */ + if (!strcmp(procname, progname)) { + ret = 1; + goto finish; + } + + /* Parse the basenames apart */ + if (!parseprogbasename(progbasename, sizeof(progbasename), progname, &progbasenamelen, &progbasenamedot) + || !parseprogbasename(procbasename, sizeof(procbasename), procname, &procbasenamelen, &procbasenamedot) + ) { + ret = -2; + goto finish; + } + + /* First quickly try for an exact hit of base names */ + if (progbasenamelen == procbasenamelen && progbasenamedot == procbasenamedot && !strcmp(procbasename, progbasename)) { + ret = 2; + goto finish; + } + + /* Check for executable program filename extensions and/or case-insensitive + * matching on some platforms */ +#ifdef WIN32 + if (!strcasecmp(procname, progname)) { + ret = 3; + goto finish; + } + + if (!strcasecmp(procbasename, progbasename)) { + ret = 4; + goto finish; + } + + if (progbasenamedot == procbasenamedot || !progbasenamedot || !procbasenamedot) { + /* Same base name before ext, maybe different casing or absence of ext in one of them */ + size_t dot = progbasenamedot ? progbasenamedot : procbasenamedot; + + if (!strncasecmp(progbasename, procbasename, dot - 1) && + ( (progbasenamedot && !strcasecmp(progbasename + progbasenamedot, ".exe")) + || (procbasenamedot && !strcasecmp(procbasename + procbasenamedot, ".exe")) ) ) { - upsdebugx(1, "No need to become_user(%s): " - "already UID=%jd GID=%jd", - pw->pw_name, initial_uid, initial_gid); - } else { - upsdebugx(1, "Can not become_user(%s): " - "not root initially, " - "remaining UID=%jd GID=%jd", - pw->pw_name, initial_uid, initial_gid); + ret = 5; + goto finish; } - return; } +#endif - if (initial_uid == 0) - if (seteuid(0)) - fatal_with_errno(EXIT_FAILURE, "getuid gave 0, but seteuid(0) failed"); + /* TOTHINK: Developer builds wrapped with libtool may be prefixed + * by "lt-" in the filename. Should we re-enter (or wrap around) + * this search with a set of variants with/without the prefix on + * both sides?.. + */ - if (initgroups(pw->pw_name, pw->pw_gid) == -1) - fatal_with_errno(EXIT_FAILURE, "initgroups"); + /* Nothing above has matched */ + ret = 0; + +finish: + switch (ret) { + case 5: + upsdebugx(1, + "%s: case-insensitive base name hit with " + "an executable program extension involved for " + "PID %" PRIuMAX " of '%s'=>'%s' and checked " + "'%s'=>'%s'", + __func__, (uintmax_t)pid, + procname, procbasename, + progname, progbasename); + break; - if (setgid(pw->pw_gid) == -1) - fatal_with_errno(EXIT_FAILURE, "setgid"); + case 4: + upsdebugx(1, + "%s: case-insensitive base name hit for PID %" + PRIuMAX " of '%s'=>'%s' and checked '%s'=>'%s'", + __func__, (uintmax_t)pid, + procname, procbasename, + progname, progbasename); + break; - if (setuid(pw->pw_uid) == -1) - fatal_with_errno(EXIT_FAILURE, "setuid"); + case 3: + upsdebugx(1, + "%s: case-insensitive full name hit for PID %" + PRIuMAX " of '%s' and checked '%s'", + __func__, (uintmax_t)pid, procname, progname); + break; - upsdebugx(1, "Succeeded to become_user(%s): now UID=%jd GID=%jd", - pw->pw_name, (intmax_t)getuid(), (intmax_t)getgid()); -#else - upsdebugx(1, "Can not become_user(%s): not implemented on this platform", - pw ? pw->pw_name : ""); -#endif + case 2: + upsdebugx(1, + "%s: case-sensitive base name hit for PID %" + PRIuMAX " of '%s'=>'%s' and checked '%s'=>'%s'", + __func__, (uintmax_t)pid, + procname, procbasename, + progname, progbasename); + break; + + case 1: + upsdebugx(1, + "%s: exact case-sensitive full name hit for PID %" + PRIuMAX " of '%s' and checked '%s'", + __func__, (uintmax_t)pid, procname, progname); + break; + + case 0: + upsdebugx(1, + "%s: did not find any match of program names " + "for PID %" PRIuMAX " of '%s'=>'%s' and checked " + "'%s'=>'%s'", + __func__, (uintmax_t)pid, + procname, procbasename, + progname, progbasename); + break; + + case -1: + /* failed to getprocname(), logged above in it */ + break; + + case -2: + upsdebugx(1, + "%s: failed to parse base names of the programs", + __func__); + break; + + case -3: + /* skipped due to envvar, logged above */ + break; + + default: + upsdebugx(1, + "%s: unexpected result looking for process name " + "of PID %" PRIuMAX ": %d", + __func__, (uintmax_t)pid, ret); + ret = -127; + break; + } + + return ret; } -/* drop down into a directory and throw away pointers to the old path */ -void chroot_start(const char *path) +int checkprocname(pid_t pid, const char *progname) { - if (chdir(path)) - fatal_with_errno(EXIT_FAILURE, "chdir(%s)", path); + /* If we can determine the binary path name of the specified "pid", + * check if it matches the assumed name of the current program. + * Returns: same as compareprocname() + * Generally speaking, if (checkprocname(...)) then ok to proceed + */ + char *procname = NULL; + int ret = 0; -#ifndef WIN32 - if (chroot(path)) - fatal_with_errno(EXIT_FAILURE, "chroot(%s)", path); + /* Quick skip before drilling into getprocname() */ + if (checkprocname_ignored(__func__)) { + ret = -3; + goto finish; + } -#else - upsdebugx(1, "Can not chroot into %s: not implemented on this platform", path); -#endif + if (!progname) { + ret = -1; + goto finish; + } - if (chdir("/")) - fatal_with_errno(EXIT_FAILURE, "chdir(/)"); + procname = getprocname(pid); + if (!procname) { + ret = -1; + goto finish; + } -#ifndef WIN32 - upsdebugx(1, "chrooted into %s", path); -#endif + ret = compareprocname(pid, procname, progname); + +finish: + if (procname) + free(procname); + + return ret; } #ifdef WIN32 @@ -430,7 +1645,7 @@ void writepid(const char *name) if (*name == '/') snprintf(fn, sizeof(fn), "%s", name); else - snprintf(fn, sizeof(fn), "%s/%s.pid", PIDPATH, name); + snprintf(fn, sizeof(fn), "%s/%s.pid", rootpidpath(), name); mask = umask(022); pidf = fopen(fn, "w"); @@ -453,23 +1668,161 @@ void writepid(const char *name) /* send sig to pid, returns -1 for error, or * zero for a successfully sent signal */ -int sendsignalpid(pid_t pid, int sig) +int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_progname) { #ifndef WIN32 - int ret; + int ret, cpn1 = -10, cpn2 = -10; + char *current_progname = NULL, *procname = NULL; + /* TOTHINK: What about containers where a NUT daemon *is* the only process + * and is the PID=1 of the container (recycle if dead)? */ if (pid < 2 || pid > get_max_pid_t()) { - upslogx(LOG_NOTICE, - "Ignoring invalid pid number %" PRIdMAX, - (intmax_t) pid); + if (nut_debug_level > 0 || nut_sendsignal_debug_level > 0) + upslogx(LOG_NOTICE, + "Ignoring invalid pid number %" PRIdMAX, + (intmax_t) pid); + return -1; + } + + ret = 0; + if (!checkprocname_ignored(__func__)) + procname = getprocname(pid); + + if (procname && progname) { + /* Check against some expected (often built-in) name */ + if (!(cpn1 = compareprocname(pid, procname, progname))) { + /* Did not match expected (often built-in) name */ + ret = -1; + } else { + if (cpn1 > 0) { + /* Matched expected name, ok to proceed */ + ret = 1; + } + /* ...else could not determine name of PID; think later */ + } + } + /* if (cpn1 == -3) => NUT_IGNORE_CHECKPROCNAME=true */ + /* if (cpn1 == -1) => could not determine name of PID... retry just in case? */ + if (procname && ret <= 0 && check_current_progname && cpn1 != -3) { + /* NOTE: This could be optimized a bit by pre-finding the procname + * of "pid" and re-using it, but this is not a hot enough code path + * to bother much. + */ + current_progname = getprocname(getpid()); + if (current_progname && (cpn2 = compareprocname(pid, procname, current_progname))) { + if (cpn2 > 0) { + /* Matched current process as asked, ok to proceed */ + ret = 2; + } + /* ...else could not determine name of PID; think later */ + } else { + if (current_progname) { + /* Did not match current process name */ + ret = -2; + } /* else just did not determine current process + * name, so did not actually check either + * // ret = -3; + */ + } + } + + /* if ret == 0, ok to proceed - not asked for any sanity checks; + * if ret > 0, ok to proceed - we had some definitive match above; + * if ret < 0, NOT OK to proceed - we had some definitive fault above + */ + if (ret < 0) { + upsdebugx(1, + "%s: ran at least one check, and all such checks " + "found a process name for PID %" PRIuMAX " and " + "failed to match: " + "found procname='%s', " + "expected progname='%s' (res=%d%s), " + "current progname='%s' (res=%d%s)", + __func__, (uintmax_t)pid, + NUT_STRARG(procname), + NUT_STRARG(progname), cpn1, + (cpn1 == -10 ? ": did not check" : ""), + NUT_STRARG(current_progname), cpn2, + (cpn2 == -10 ? ": did not check" : "")); + + if (nut_debug_level > 0 || nut_sendsignal_debug_level > 1) { + switch (ret) { + case -1: + upslogx(LOG_ERR, "Tried to signal PID %" PRIuMAX + " which exists but is not of" + " expected program '%s'; not asked" + " to cross-check current PID's name", + (uintmax_t)pid, progname); + break; + + /* Maybe we tried both data sources, maybe just current_progname */ + case -2: + /*case -3:*/ + if (progname && current_progname) { + /* Tried both, downgraded verdict further */ + upslogx(LOG_ERR, "Tried to signal PID %" PRIuMAX + " which exists but is not of expected" + " program '%s' nor current '%s'", + (uintmax_t)pid, progname, current_progname); + } else if (current_progname) { + /* Not asked for progname==NULL */ + upslogx(LOG_ERR, "Tried to signal PID %" PRIuMAX + " which exists but is not of" + " current program '%s'", + (uintmax_t)pid, current_progname); + } else if (progname) { + upslogx(LOG_ERR, "Tried to signal PID %" PRIuMAX + " which exists but is not of" + " expected program '%s'; could not" + " cross-check current PID's name", + (uintmax_t)pid, progname); + } else { + /* Both NULL; one not asked, another not detected; + * should not actually get here (wannabe `ret==-3`) + */ + upslogx(LOG_ERR, "Tried to signal PID %" PRIuMAX + " but could not cross-check current PID's" + " name: did not expect to get here", + (uintmax_t)pid); + } + break; + + default: + break; + } + } + + if (current_progname) { + free(current_progname); + current_progname = NULL; + } + + if (procname) { + free(procname); + procname = NULL; + } + + /* Logged or not, sanity-check was requested and failed */ return -1; } - /* see if this is going to work first - does the process exist? */ + if (current_progname) { + free(current_progname); + current_progname = NULL; + } + + if (procname) { + free(procname); + procname = NULL; + } + + /* see if this is going to work first - does the process exist, + * and do we have permissions to signal it? */ ret = kill(pid, 0); if (ret < 0) { - perror("kill"); + if (nut_debug_level > 0 || nut_sendsignal_debug_level >= NUT_SENDSIGNAL_DEBUG_LEVEL_KILL_SIG0PING) + perror("kill"); return -1; } @@ -478,7 +1831,8 @@ int sendsignalpid(pid_t pid, int sig) ret = kill(pid, sig); if (ret < 0) { - perror("kill"); + if (nut_debug_level > 0 || nut_sendsignal_debug_level > 1) + perror("kill"); return -1; } } @@ -487,6 +1841,9 @@ int sendsignalpid(pid_t pid, int sig) #else NUT_UNUSED_VARIABLE(pid); NUT_UNUSED_VARIABLE(sig); + NUT_UNUSED_VARIABLE(progname); + NUT_UNUSED_VARIABLE(check_current_progname); + /* Windows builds use named pipes, not signals per se */ upslogx(LOG_ERR, "%s: not implemented for Win32 and " "should not have been called directly!", @@ -503,8 +1860,10 @@ pid_t parsepid(const char *buf) pid_t pid = -1; intmax_t _pid; + errno = 0; if (!buf) { upsdebugx(6, "%s: called with NULL input", __func__); + errno = EINVAL; return pid; } @@ -513,36 +1872,48 @@ pid_t parsepid(const char *buf) if (_pid <= get_max_pid_t()) { pid = (pid_t)_pid; } else { - upslogx(LOG_NOTICE, "Received a pid number too big for a pid_t: %" PRIdMAX, _pid); + errno = ERANGE; + + if (nut_debug_level > 0 || nut_sendsignal_debug_level > 0) + upslogx(LOG_NOTICE, + "Received a pid number too big for a pid_t: %" + PRIdMAX, _pid); } return pid; } -/* open pidfn, get the pid, then send it sig +/* open pidfn, get the pid; * returns negative codes for errors, or - * zero for a successfully sent signal + * zero for a successfully discovered value */ -#ifndef WIN32 -int sendsignalfn(const char *pidfn, int sig) +pid_t parsepidfile(const char *pidfn) { char buf[SMALLBUF]; FILE *pidf; pid_t pid = -1; - int ret = -1; pidf = fopen(pidfn, "r"); if (!pidf) { - upslog_with_errno(LOG_NOTICE, "fopen %s", pidfn); + /* This one happens quite often when a daemon starts + * for the first time and no opponent PID file exists, + * so the cut-off verbosity is higher. + */ + if (nut_debug_level > 0 || + nut_sendsignal_debug_level >= NUT_SENDSIGNAL_DEBUG_LEVEL_FOPEN_PIDFILE) + upslog_with_errno(LOG_NOTICE, "fopen %s", pidfn); return -3; } if (fgets(buf, sizeof(buf), pidf) == NULL) { - upslogx(LOG_NOTICE, "Failed to read pid from %s", pidfn); + if (nut_debug_level > 0 || nut_sendsignal_debug_level > 2) + upslogx(LOG_NOTICE, "Failed to read pid from %s", pidfn); fclose(pidf); return -2; } - /* TOTHINK: Original code only closed pidf before + + /* TOTHINK: Original sendsignalfn code (which this + * was extracted from) only closed pidf before * exiting the method, on error or "normally". * Why not here? Do we want an (exclusive?) hold * on it while being active in the method? @@ -551,20 +1922,36 @@ int sendsignalfn(const char *pidfn, int sig) /* this method actively reports errors, if any */ pid = parsepid(buf); + fclose(pidf); + + return pid; +} + +/* open pidfn, get the pid, then send it sig + * returns negative codes for errors, or + * zero for a successfully sent signal + */ +#ifndef WIN32 +int sendsignalfn(const char *pidfn, int sig, const char *progname, int check_current_progname) +{ + int ret = -1; + pid_t pid = parsepidfile(pidfn); + if (pid >= 0) { /* this method actively reports errors, if any */ - ret = sendsignalpid(pid, sig); + ret = sendsignalpid(pid, sig, progname, check_current_progname); } - fclose(pidf); return ret; } #else /* => WIN32 */ -int sendsignalfn(const char *pidfn, const char * sig) +int sendsignalfn(const char *pidfn, const char * sig, const char *progname_ignored, int check_current_progname_ignored) { BOOL ret; + NUT_UNUSED_VARIABLE(progname_ignored); + NUT_UNUSED_VARIABLE(check_current_progname_ignored); ret = send_to_named_pipe(pidfn, sig); @@ -627,18 +2014,21 @@ int snprintfcat(char *dst, size_t size, const char *fmt, ...) /* lazy way to send a signal if the program uses the PIDPATH */ #ifndef WIN32 -int sendsignal(const char *progname, int sig) +int sendsignal(const char *progname, int sig, int check_current_progname) { char fn[SMALLBUF]; - snprintf(fn, sizeof(fn), "%s/%s.pid", PIDPATH, progname); + snprintf(fn, sizeof(fn), "%s/%s.pid", rootpidpath(), progname); - return sendsignalfn(fn, sig); + return sendsignalfn(fn, sig, progname, check_current_progname); } #else -int sendsignal(const char *progname, const char * sig) +int sendsignal(const char *progname, const char * sig, int check_current_progname) { - return sendsignalfn(progname, sig); + /* progname is used as the pipe name for WIN32 + * check_current_progname is de-facto ignored + */ + return sendsignalfn(progname, sig, NULL, check_current_progname); } #endif @@ -846,9 +2236,15 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) va_end(va); if ((ret < 0) || (ret >= (int) sizeof(msgbuf))) { - syslog(LOG_WARNING, - "%s (%s:%d): vsnprintf needed more than %" PRIuSIZE " bytes: %d", - __func__, __FILE__, __LINE__, sizeof(msgbuf), ret); + if (syslog_is_disabled()) { + fprintf(stderr, + "%s (%s:%d): vsnprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(msgbuf), ret); + } else { + syslog(LOG_WARNING, + "%s (%s:%d): vsnprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(msgbuf), ret); + } } else { msglen = strlen(msgbuf); } @@ -866,7 +2262,7 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) "skipped for libcommonclient build, " "will not spam more about it", __func__, state); upsnotify_reported_disabled_systemd = 1; -# else +# else /* not WITHOUT_LIBSYSTEMD */ if (!getenv("NOTIFY_SOCKET")) { if (!upsnotify_reported_disabled_systemd) upsdebugx(upsnotify_report_verbosity, @@ -884,9 +2280,15 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) usec_t monots = timespec_load(&monoclock_ts); ret = snprintf(monoclock_str + 1, sizeof(monoclock_str) - 1, "MONOTONIC_USEC=%" PRI_USEC, monots); if ((ret < 0) || (ret >= (int) sizeof(monoclock_str) - 1)) { - syslog(LOG_WARNING, - "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", - __func__, __FILE__, __LINE__, sizeof(monoclock_str), ret); + if (syslog_is_disabled()) { + fprintf(stderr, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(monoclock_str), ret); + } else { + syslog(LOG_WARNING, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(monoclock_str), ret); + } msglen = 0; } else { monoclock_str[0] = '\n'; @@ -903,9 +2305,15 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) if (msglen) { ret = snprintf(buf, sizeof(buf), "STATUS=%s", msgbuf); if ((ret < 0) || (ret >= (int) sizeof(buf))) { - syslog(LOG_WARNING, - "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", - __func__, __FILE__, __LINE__, sizeof(buf), ret); + if (syslog_is_disabled()) { + fprintf(stderr, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(buf), ret); + } else { + syslog(LOG_WARNING, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(buf), ret); + } msglen = 0; } else { msglen = (size_t)ret; @@ -1002,7 +2410,7 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) "(%" PRIu64 "msec remain)", __func__, postit, to); } -# endif +# endif /* HAVE_SD_WATCHDOG_ENABLED */ if (postit < 1) { char *s = getenv("WATCHDOG_USEC"); @@ -1098,9 +2506,15 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) if ((ret < 0) || (ret >= (int) sizeof(buf))) { /* Refusal to send the watchdog ping is not an error to report */ if ( !(ret == -126 && (state == NOTIFY_STATE_WATCHDOG)) ) { - syslog(LOG_WARNING, - "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", - __func__, __FILE__, __LINE__, sizeof(buf), ret); + if (syslog_is_disabled()) { + fprintf(stderr, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(buf), ret); + } else { + syslog(LOG_WARNING, + "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", + __func__, __FILE__, __LINE__, sizeof(buf), ret); + } } ret = -1; } else { @@ -1175,7 +2589,9 @@ void nut_report_config_flags(void) * Depending on amount of configuration tunables involved by a particular * build of NUT, the string can be quite long (over 1KB). */ +#if 0 const char *acinit_ver = NULL; +#endif /* Pass these as variables to avoid warning about never reaching one * of compiled codepaths: */ const char *compiler_ver = CC_VERSION; @@ -1185,6 +2601,7 @@ void nut_report_config_flags(void) if (nut_debug_level < 1) return; +#if 0 /* Only report git revision if NUT_VERSION_MACRO in nut_version.h aka * UPS_VERSION here is remarkably different from PACKAGE_VERSION from * configure.ac AC_INIT() -- which may be e.g. "2.8.0.1" although some @@ -1202,7 +2619,14 @@ void nut_report_config_flags(void) * especially embedders, tend to place their product IDs here), * or if PACKAGE_VERSION *is NOT* a substring of it: */ acinit_ver = PACKAGE_VERSION; +/* + // Triplet that was printed below: + (acinit_ver ? " (release/snapshot of " : ""), + (acinit_ver ? acinit_ver : ""), + (acinit_ver ? ")" : ""), +*/ } +#endif /* NOTE: If changing wording here, keep in sync with configure.ac logic * looking for CONFIG_FLAGS_DEPLOYED via "configured with flags:" string! @@ -1220,13 +2644,10 @@ void nut_report_config_flags(void) } if (xbit_test(upslog_flags, UPSLOG_STDERR)) { - fprintf(stderr, "%4.0f.%06ld\t[D1] Network UPS Tools version %s%s%s%s%s%s%s %s%s\n", + fprintf(stderr, "%4.0f.%06ld\t[D1] Network UPS Tools version %s%s%s%s %s%s\n", difftime(now.tv_sec, upslog_start.tv_sec), (long)(now.tv_usec - upslog_start.tv_usec), - UPS_VERSION, - (acinit_ver ? " (release/snapshot of " : ""), - (acinit_ver ? acinit_ver : ""), - (acinit_ver ? ")" : ""), + describe_NUT_VERSION_once(), (compiler_ver && *compiler_ver != '\0' ? " built with " : ""), (compiler_ver && *compiler_ver != '\0' ? compiler_ver : ""), (compiler_ver && *compiler_ver != '\0' ? " and" : ""), @@ -1240,18 +2661,16 @@ void nut_report_config_flags(void) /* NOTE: May be ignored or truncated by receiver if that syslog server * (and/or OS sender) does not accept messages of such length */ - if (xbit_test(upslog_flags, UPSLOG_SYSLOG)) - syslog(LOG_DEBUG, "Network UPS Tools version %s%s%s%s%s%s%s %s%s", - UPS_VERSION, - (acinit_ver ? " (release/snapshot of " : ""), - (acinit_ver ? acinit_ver : ""), - (acinit_ver ? ")" : ""), + if (xbit_test(upslog_flags, UPSLOG_SYSLOG)) { + syslog(LOG_DEBUG, "Network UPS Tools version %s%s%s%s %s%s", + describe_NUT_VERSION_once(), (compiler_ver && *compiler_ver != '\0' ? " built with " : ""), (compiler_ver && *compiler_ver != '\0' ? compiler_ver : ""), (compiler_ver && *compiler_ver != '\0' ? " and" : ""), (config_flags && *config_flags != '\0' ? "configured with flags: " : "configured all by default guesswork"), (config_flags && *config_flags != '\0' ? config_flags : "") ); + } } static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) @@ -1335,10 +2754,17 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) /* Arbitrary limit, gotta stop somewhere */ if (bufsize > LARGEBUF * 64) { vupslog_too_long: - syslog(LOG_WARNING, "vupslog: vsnprintf needed " - "more than %" PRIuSIZE " bytes; logged " - "output can be truncated", - bufsize); + if (syslog_is_disabled()) { + fprintf(stderr, "vupslog: vsnprintf needed " + "more than %" PRIuSIZE " bytes; logged " + "output can be truncated", + bufsize); + } else { + syslog(LOG_WARNING, "vupslog: vsnprintf needed " + "more than %" PRIuSIZE " bytes; logged " + "output can be truncated", + bufsize); + } break; } } while(1); @@ -1378,6 +2804,7 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) * so mark the starting point whenever we first try to log */ if (upslog_start.tv_sec == 0) { struct timeval now; + gettimeofday(&now, NULL); upslog_start = now; } @@ -1415,7 +2842,13 @@ static void vupslog(int priority, const char *fmt, va_list va, int use_strerror) /* Return the default path for the directory containing configuration files */ const char * confpath(void) { - const char *path = getenv("NUT_CONFPATH"); + static const char *path = NULL; + + /* Cached by earlier calls? */ + if (path) + return path; + + path = getenv("NUT_CONFPATH"); #ifdef WIN32 if (path == NULL) { @@ -1426,13 +2859,22 @@ const char * confpath(void) /* We assume, here and elsewhere, that * at least CONFPATH is always defined */ - return (path != NULL && *path != '\0') ? path : CONFPATH; + if (path == NULL || *path == '\0') + path = CONFPATH; + + return path; } /* Return the default path for the directory containing state files */ const char * dflt_statepath(void) { - const char *path = getenv("NUT_STATEPATH"); + static const char *path = NULL; + + /* Cached by earlier calls? */ + if (path) + return path; + + path = getenv("NUT_STATEPATH"); #ifdef WIN32 if (path == NULL) { @@ -1443,7 +2885,10 @@ const char * dflt_statepath(void) /* We assume, here and elsewhere, that * at least STATEPATH is always defined */ - return (path != NULL && *path != '\0') ? path : STATEPATH; + if (path == NULL || *path == '\0') + path = STATEPATH; + + return path; } /* Return the alternate path for pid files, for processes running as non-root @@ -1454,7 +2899,11 @@ const char * dflt_statepath(void) */ const char * altpidpath(void) { - const char * path; + static const char *path = NULL; + + /* Cached by earlier calls? */ + if (path) + return path; path = getenv("NUT_ALTPIDPATH"); if ( (path == NULL) || (*path == '\0') ) { @@ -1472,11 +2921,43 @@ const char * altpidpath(void) return path; #ifdef ALTPIDPATH - return ALTPIDPATH; + path = ALTPIDPATH; #else /* With WIN32 in the loop, this may be more than a fallback to STATEPATH: */ - return dflt_statepath(); + path = dflt_statepath(); +#endif + + return path; +} + +/* Return the main path for pid files, for processes running as root, such + * as upsmon. Typically this is the built-in PIDPATH (from configure script) + * but certain use-cases such as the test suite can override it with the + * NUT_PIDPATH environment variable. + */ +const char * rootpidpath(void) +{ + static const char *path = NULL; + + /* Cached by earlier calls? */ + if (path) + return path; + + path = getenv("NUT_PIDPATH"); + +#ifdef WIN32 + if (path == NULL) { + /* fall back to built-in pathname relative to binary/workdir */ + path = getfullpath(PATH_ETC); + } #endif + + /* We assume, here and elsewhere, that + * at least PIDPATH is always defined */ + if (path == NULL || *path == '\0') + path = PIDPATH; + + return path; } /* Die with a standard message if socket filename is too long */ @@ -1587,8 +3068,13 @@ void s_upsdebug_with_errno(int level, const char *fmt, ...) ret = snprintf(fmt2, sizeof(fmt2), "[D%d] %s", level, fmt); } if ((ret < 0) || (ret >= (int) sizeof(fmt2))) { - syslog(LOG_WARNING, "upsdebug_with_errno: snprintf needed more than %d bytes", - LARGEBUF); + if (syslog_is_disabled()) { + fprintf(stderr, "upsdebug_with_errno: snprintf needed more than %d bytes", + LARGEBUF); + } else { + syslog(LOG_WARNING, "upsdebug_with_errno: snprintf needed more than %d bytes", + LARGEBUF); + } } else { fmt = (const char *)fmt2; } @@ -1637,8 +3123,13 @@ void s_upsdebugx(int level, const char *fmt, ...) } if ((ret < 0) || (ret >= (int) sizeof(fmt2))) { - syslog(LOG_WARNING, "upsdebugx: snprintf needed more than %d bytes", - LARGEBUF); + if (syslog_is_disabled()) { + fprintf(stderr, "upsdebugx: snprintf needed more than %d bytes", + LARGEBUF); + } else { + syslog(LOG_WARNING, "upsdebugx: snprintf needed more than %d bytes", + LARGEBUF); + } } else { fmt = (const char *)fmt2; } @@ -1765,10 +3256,25 @@ void s_upsdebug_ascii(int level, const char *msg, const void *buf, size_t len) static void vfatal(const char *fmt, va_list va, int use_strerror) { + /* Normally we enable SYSLOG and disable STDERR, + * unless NUT_DEBUG_SYSLOG envvar interferes as + * interpreted in syslog_is_disabled() method: */ + int syslog_disabled = syslog_is_disabled(), + stderr_disabled = (syslog_disabled == 0 || syslog_disabled == 2); + if (xbit_test(upslog_flags, UPSLOG_STDERR_ON_FATAL)) xbit_set(&upslog_flags, UPSLOG_STDERR); - if (xbit_test(upslog_flags, UPSLOG_SYSLOG_ON_FATAL)) - xbit_set(&upslog_flags, UPSLOG_SYSLOG); + if (xbit_test(upslog_flags, UPSLOG_SYSLOG_ON_FATAL)) { + if (syslog_disabled) { + /* FIXME: Corner case... env asked for stderr + * instead of syslog - should we care about + * UPSLOG_STDERR_ON_FATAL being not set? */ + if (!stderr_disabled) + xbit_set(&upslog_flags, UPSLOG_STDERR); + } else { + xbit_set(&upslog_flags, UPSLOG_SYSLOG); + } + } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic push @@ -2145,6 +3651,9 @@ void nut_prepare_search_paths(void) { dupe = 1; #if HAVE_DECL_REALPATH free((char *)dirname); + /* Have some valid value, for kicks (likely + * to be ignored in the code path below) */ + dirname = search_paths_builtin[i]; #endif break; } @@ -2155,10 +3664,17 @@ void nut_prepare_search_paths(void) { "existing unique directory: %s", __func__, count_filtered, dirname); #if !HAVE_DECL_REALPATH + /* Make a copy of table entry, else we have + * a dynamic result of realpath() made above. + */ dirname = (const char *)xstrdup(dirname); #endif filtered_search_paths[count_filtered++] = dirname; - } + } /* else: dirname was freed above (for realpath) + * or is a reference to the table entry; no need + * to free() it either way */ + + closedir(dp); } /* If we mangled this before, forget the old result: */ @@ -2248,27 +3764,36 @@ static char * get_libname_in_dir(const char* base_libname, size_t base_libname_l */ DIR *dp; struct dirent *dirp; - char *libname_path = NULL; + char *libname_path = NULL, *libname_alias = NULL; char current_test_path[LARGEBUF]; + upsdebugx(3, "%s('%s', %" PRIuSIZE ", '%s', %i): Entering method...", + __func__, base_libname, base_libname_length, dirname, index); + memset(current_test_path, 0, LARGEBUF); if ((dp = opendir(dirname)) == NULL) { if (index >= 0) { - upsdebugx(5,"NOT looking for lib %s in unreachable directory #%d : %s", - base_libname, index, dirname); + upsdebugx(5, "%s: NOT looking for lib %s in " + "unreachable directory #%d : %s", + __func__, base_libname, index, dirname); } else { - upsdebugx(5,"NOT looking for lib %s in unreachable directory : %s", - base_libname, dirname); + upsdebugx(5, "%s: NOT looking for lib %s in " + "unreachable directory : %s", + __func__, base_libname, dirname); } return NULL; } if (index >= 0) { - upsdebugx(2,"Looking for lib %s in directory #%d : %s", base_libname, index, dirname); + upsdebugx(4, "%s: Looking for lib %s in directory #%d : %s", + __func__, base_libname, index, dirname); } else { - upsdebugx(2,"Looking for lib %s in directory : %s", base_libname, dirname); + upsdebugx(4, "%s: Looking for lib %s in directory : %s", + __func__, base_libname, dirname); } + + /* TODO: Try a quick stat() first? */ while ((dirp = readdir(dp)) != NULL) { #if !HAVE_DECL_REALPATH @@ -2276,11 +3801,18 @@ static char * get_libname_in_dir(const char* base_libname, size_t base_libname_l #endif int compres; - upsdebugx(5,"Comparing lib %s with dirpath entry %s", base_libname, dirp->d_name); + upsdebugx(5, "%s: Comparing lib %s with dirpath entry %s", + __func__, base_libname, dirp->d_name); compres = strncmp(dirp->d_name, base_libname, base_libname_length); - if (compres == 0 - && dirp->d_name[base_libname_length] == '\0' /* avoid "*.dll.a" etc. */ - ) { + if (compres == 0) { + /* avoid "*.dll.a", ".so.1.2.3" etc. */ + if (dirp->d_name[base_libname_length] != '\0') { + if (!libname_alias) { + libname_alias = xstrdup(dirp->d_name); + } + continue; + } + snprintf(current_test_path, LARGEBUF, "%s/%s", dirname, dirp->d_name); #if HAVE_DECL_REALPATH libname_path = realpath(current_test_path, NULL); @@ -2299,7 +3831,8 @@ static char * get_libname_in_dir(const char* base_libname, size_t base_libname_l for (p = current_test_path; *p != '\0' && (p - current_test_path) < LARGEBUF; p++) { if (*p == '/') *p = '\\'; } - upsdebugx(3, "%s: WIN32: re-checking with %s", __func__, current_test_path); + upsdebugx(4, "%s: WIN32: re-checking with %s", + __func__, current_test_path); if (stat(current_test_path, &st) == 0) { if (st.st_size > 0) { libname_path = xstrdup(current_test_path); @@ -2312,7 +3845,8 @@ static char * get_libname_in_dir(const char* base_libname, size_t base_libname_l * original dir, and no threading at this moment, to be safe!) * https://stackoverflow.com/a/66096983/4715872 */ - upsdebugx(3, "%s: WIN32: re-checking with %s", __func__, current_test_path + 2); + upsdebugx(4, "%s: WIN32: re-checking with %s", + __func__, current_test_path + 2); if (stat(current_test_path + 2, &st) == 0) { if (st.st_size > 0) { libname_path = xstrdup(current_test_path + 2); @@ -2322,9 +3856,9 @@ static char * get_libname_in_dir(const char* base_libname, size_t base_libname_l # endif /* WIN32 */ #endif /* HAVE_DECL_REALPATH */ - upsdebugx(2,"Candidate path for lib %s is %s (realpath %s)", + upsdebugx(2, "Candidate path for lib %s is %s (realpath %s)", base_libname, current_test_path, - (libname_path!=NULL)?libname_path:"NULL"); + NUT_STRARG(libname_path)); if (libname_path != NULL) break; } @@ -2332,6 +3866,17 @@ static char * get_libname_in_dir(const char* base_libname, size_t base_libname_l closedir(dp); + if (libname_alias) { + if (!libname_path) { + upsdebugx(1, "Got no strong candidate path for lib %s in %s" + ", but saw seemingly related names (are you missing" + " a symbolic link, perhaps?) e.g.: %s", + base_libname, dirname, libname_alias); + } + + free(libname_alias); + } + return libname_path; } @@ -2345,11 +3890,18 @@ static char * get_libname_in_pathset(const char* base_libname, size_t base_libna char *onedir = NULL; char* pathset_tmp; + upsdebugx(3, "%s('%s', %" PRIuSIZE ", '%s', %i): Entering method...", + __func__, base_libname, base_libname_length, + NUT_STRARG(pathset), + counter ? *counter : -1); + if (!pathset || *pathset == '\0') return NULL; /* First call to tokenization passes the string, others pass NULL */ pathset_tmp = xstrdup(pathset); + upsdebugx(4, "%s: Looking for lib %s in a colon-separated path set", + __func__, base_libname); while (NULL != (onedir = strtok( (onedir ? NULL : pathset_tmp), ":" ))) { libname_path = get_libname_in_dir(base_libname, base_libname_length, onedir, (*counter)++); if (libname_path != NULL) @@ -2362,6 +3914,8 @@ static char * get_libname_in_pathset(const char* base_libname, size_t base_libna pathset_tmp = xstrdup(pathset); if (!libname_path) { onedir = NULL; /* probably is NULL already, but better ensure this */ + upsdebugx(4, "%s: WIN32: Looking for lib %s in a semicolon-separated path set", + __func__, base_libname); while (NULL != (onedir = strtok( (onedir ? NULL : pathset_tmp), ";" ))) { libname_path = get_libname_in_dir(base_libname, base_libname_length, onedir, (*counter)++); if (libname_path != NULL) @@ -2381,29 +3935,77 @@ char * get_libname(const char* base_libname) int index = 0, counter = 0; char *libname_path = NULL; size_t base_libname_length = strlen(base_libname); + struct stat st; + + upsdebugx(3, "%s('%s'): Entering method...", __func__, base_libname); + + /* First, check for an exact hit by absolute/relative path + * if `base_libname` includes path separator character(s) */ + if (xbasename(base_libname) != base_libname) { + upsdebugx(4, "%s: Looking for lib %s by exact hit...", + __func__, base_libname); +#if HAVE_DECL_REALPATH + /* allocates the buffer we free() later */ + libname_path = realpath(base_libname, NULL); + if (libname_path != NULL) { + if (stat(libname_path, &st) == 0) { + if (st.st_size > 0) { + upsdebugx(2, "Looking for lib %s, found by exact hit", + base_libname); + goto found; + } + } + + /* else: does not actually exist */ + free(libname_path); + libname_path = NULL; + } +#endif /* HAVE_DECL_REALPATH */ + + /* Just check if candidate name is (points to?) valid file */ + if (stat(base_libname, &st) == 0) { + if (st.st_size > 0) { + libname_path = xstrdup(base_libname); + upsdebugx(2, "Looking for lib %s, found by exact hit", + base_libname); + goto found; + } + } + } /* Normally these envvars should not be set, but if the user insists, * we should prefer the override... */ #ifdef BUILD_64 + upsdebugx(4, "%s: Looking for lib %s by path-set LD_LIBRARY_PATH_64...", + __func__, base_libname); libname_path = get_libname_in_pathset(base_libname, base_libname_length, getenv("LD_LIBRARY_PATH_64"), &counter); if (libname_path != NULL) { - upsdebugx(2, "Looking for lib %s, found in LD_LIBRARY_PATH_64", base_libname); + upsdebugx(2, "Looking for lib %s, found in LD_LIBRARY_PATH_64", + base_libname); goto found; } #else + upsdebugx(4, "%s: Looking for lib %s by path-set LD_LIBRARY_PATH_32...", + __func__, base_libname); libname_path = get_libname_in_pathset(base_libname, base_libname_length, getenv("LD_LIBRARY_PATH_32"), &counter); if (libname_path != NULL) { - upsdebugx(2, "Looking for lib %s, found in LD_LIBRARY_PATH_32", base_libname); + upsdebugx(2, "Looking for lib %s, found in LD_LIBRARY_PATH_32", + base_libname); goto found; } #endif + upsdebugx(4, "%s: Looking for lib %s by path-set LD_LIBRARY_PATH...", + __func__, base_libname); libname_path = get_libname_in_pathset(base_libname, base_libname_length, getenv("LD_LIBRARY_PATH"), &counter); if (libname_path != NULL) { - upsdebugx(2, "Looking for lib %s, found in LD_LIBRARY_PATH", base_libname); + upsdebugx(2, "Looking for lib %s, found in LD_LIBRARY_PATH", + base_libname); goto found; } + upsdebugx(4, "%s: Looking for lib %s by search_paths[]...", + __func__, base_libname); for (index = 0 ; (search_paths[index] != NULL) && (libname_path == NULL) ; index++) { libname_path = get_libname_in_dir(base_libname, base_libname_length, search_paths[index], counter++); @@ -2418,17 +4020,23 @@ char * get_libname(const char* base_libname) if (!libname_path) { /* First check near the EXE (if executing it from another * working directory) */ + upsdebugx(4, "%s: WIN32: Looking for lib %s near EXE...", + __func__, base_libname); libname_path = get_libname_in_dir(base_libname, base_libname_length, getfullpath(NULL), counter++); } # ifdef PATH_LIB if (!libname_path) { + upsdebugx(4, "%s: WIN32: Looking for lib %s via PATH_LIB...", + __func__, base_libname); libname_path = get_libname_in_dir(base_libname, base_libname_length, getfullpath(PATH_LIB), counter++); } # endif if (!libname_path) { /* Resolve "lib" dir near the one with current executable ("bin" or "sbin") */ + upsdebugx(4, "%s: WIN32: Looking for lib %s in a 'lib' dir near EXE...", + __func__, base_libname); libname_path = get_libname_in_dir(base_libname, base_libname_length, getfullpath("/../lib"), counter++); } #endif /* WIN32 so far */ @@ -2437,13 +4045,15 @@ char * get_libname(const char* base_libname) /* Windows-specific: DLLs can be provided by common "PATH" envvar, * at lowest search priority though (after EXE dir, system, etc.) */ if (!libname_path) { - upsdebugx(2, "Looking for lib %s in PATH", base_libname); + upsdebugx(4, "%s: WIN32: Looking for lib %s in PATH", + __func__, base_libname); libname_path = get_libname_in_pathset(base_libname, base_libname_length, getenv("PATH"), &counter); } #endif /* WIN32 */ found: - upsdebugx(1,"Looking for lib %s, found %s", base_libname, (libname_path!=NULL)?libname_path:"NULL"); + upsdebugx(1, "Looking for lib %s, found %s", + base_libname, NUT_STRARG(libname_path)); return libname_path; } diff --git a/common/nutconf.cpp b/common/nutconf.cpp index 33adb3c47c..35c4ffd5e6 100644 --- a/common/nutconf.cpp +++ b/common/nutconf.cpp @@ -3,6 +3,7 @@ Copyright (C) 2012 Emilien Kia + 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -55,15 +56,30 @@ NutParser::~NutParser() {} template Settable StringToSettableNumber(const std::string & src) { - std::stringstream ss(src); - T result; - if(ss >> result) - { - return Settable(result); - } - else - { - return Settable(); + if (typeid(T) == typeid(bool)) { + static const Settable b0(false); + static const Settable b1(true); + + // See also: GenericConfiguration::str2bool() + // FIXME: Can these two methods be merged? + if ("true" == src) return b1; + if ("on" == src) return b1; + if ("1" == src) return b1; + if ("yes" == src) return b1; + if ("ok" == src) return b1; + + return b0; + } else { + std::stringstream ss(src); + T result; + if(ss >> result) + { + return Settable(result); + } + else + { + return Settable(); + } } } @@ -96,44 +112,53 @@ void NutParser::setOptions(unsigned int options, bool set) } } -char NutParser::get() { +char NutParser::get() +{ if (_pos >= _buffer.size()) return 0; else return _buffer[_pos++]; } -char NutParser::peek() { +char NutParser::peek() +{ return _buffer[_pos]; } -size_t NutParser::getPos()const { +size_t NutParser::getPos()const +{ return _pos; } -void NutParser::setPos(size_t pos) { +void NutParser::setPos(size_t pos) +{ _pos = pos; } -char NutParser::charAt(size_t pos)const { +char NutParser::charAt(size_t pos)const +{ return _buffer[pos]; } -void NutParser::pushPos() { +void NutParser::pushPos() +{ _stack.push_back(_pos); } -size_t NutParser::popPos() { +size_t NutParser::popPos() +{ size_t pos = _stack.back(); _stack.pop_back(); return pos; } -void NutParser::rewind() { +void NutParser::rewind() +{ _pos = popPos(); } -void NutParser::back() { +void NutParser::back() +{ if (_pos > 0) --_pos; } @@ -144,7 +169,8 @@ void NutParser::back() { * | '\\' ( __SPACES__ | '\\' | '\"' | '#' ) * TODO: accept "\t", "\s", "\r", "\n" ?? */ -std::string NutParser::parseCHARS() { +std::string NutParser::parseCHARS() +{ bool escaped = false; // Is char escaped ? std::string res; // Stored string @@ -180,7 +206,8 @@ std::string NutParser::parseCHARS() { * | '\\' ( '\\' | '\"' ) * TODO: accept "\t", "\s", "\r", "\n" ?? */ -std::string NutParser::parseSTRCHARS() { +std::string NutParser::parseSTRCHARS() +{ bool escaped = false; // Is char escaped ? std::string str; // Stored string @@ -213,7 +240,8 @@ std::string NutParser::parseSTRCHARS() { /** Parse a string source for getting the next token, ignoring spaces. * \return Token type. */ -NutParser::Token NutParser::parseToken() { +NutParser::Token NutParser::parseToken() +{ /** Lexical parsing machine state enumeration.*/ typedef enum { @@ -387,7 +415,8 @@ NutParser::Token NutParser::parseToken() { return token; } -std::list NutParser::parseLine() { +std::list NutParser::parseLine() +{ std::list res; while (true) { @@ -410,6 +439,30 @@ std::list NutParser::parseLine() { case Token::TOKEN_NONE: case Token::TOKEN_EOL: return res; +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } } } @@ -419,19 +472,23 @@ std::list NutParser::parseLine() { // NutConfigParser::NutConfigParser(const char* buffer, unsigned int options) : -NutParser(buffer, options) { +NutParser(buffer, options) +{ } NutConfigParser::NutConfigParser(const std::string& buffer, unsigned int options) : -NutParser(buffer, options) { +NutParser(buffer, options) +{ } -void NutConfigParser::parseConfig(BaseConfiguration* config) { +void NutConfigParser::parseConfig(BaseConfiguration* config) +{ NUT_UNUSED_VARIABLE(config); parseConfig(); } -void NutConfigParser::parseConfig() { +void NutConfigParser::parseConfig() +{ onParseBegin(); enum ConfigParserState { @@ -670,6 +727,9 @@ void NutConfigParser::parseConfig() { break; } break; + + default: + break; } } @@ -695,6 +755,8 @@ void NutConfigParser::parseConfig() { case CPS_DEFAULT: /* TOTHINK: no-op? */ break; + default: + break; } #ifdef __clang__ #pragma clang diagnostic pop @@ -713,23 +775,31 @@ void NutConfigParser::parseConfig() { // DefaultConfigParser::DefaultConfigParser(const char* buffer) : -NutConfigParser(buffer) { +NutConfigParser(buffer) +{ } DefaultConfigParser::DefaultConfigParser(const std::string& buffer) : -NutConfigParser(buffer) { +NutConfigParser(buffer) +{ } -void DefaultConfigParser::onParseBegin() { +void DefaultConfigParser::onParseBegin() +{ // Start with empty section (i.e. global one) _section.clear(); } -void DefaultConfigParser::onParseComment(const std::string& /*comment*/) { +void DefaultConfigParser::onParseComment( + const std::string& /*comment*/) +{ // Comments are ignored for now } -void DefaultConfigParser::onParseSectionName(const std::string& sectionName, const std::string& /*comment*/) { +void DefaultConfigParser::onParseSectionName( + const std::string& sectionName, + const std::string& /*comment*/) +{ // Comments are ignored for now // Process current section. @@ -742,7 +812,12 @@ void DefaultConfigParser::onParseSectionName(const std::string& sectionName, con _section.name = sectionName; } -void DefaultConfigParser::onParseDirective(const std::string& directiveName, char /*sep*/, const ConfigParamList& values, const std::string& /*comment*/) { +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 @@ -753,7 +828,8 @@ void DefaultConfigParser::onParseDirective(const std::string& directiveName, cha // TODO Can probably be optimized. } -void DefaultConfigParser::onParseEnd() { +void DefaultConfigParser::onParseEnd() +{ // Process current (last) section if (!_section.empty()) { onParseSection(_section); @@ -766,11 +842,13 @@ void DefaultConfigParser::onParseEnd() { // GenericConfigSection // -bool GenericConfigSection::empty()const { +bool GenericConfigSection::empty()const +{ return name.empty() && entries.empty(); } -void GenericConfigSection::clear() { +void GenericConfigSection::clear() +{ name.clear(); entries.clear(); } @@ -976,7 +1054,135 @@ void GenericConfiguration::setStr( } -long long int GenericConfiguration::getInt(const std::string & section, const std::string & entry, long long int val) const +bool GenericConfiguration::getBool( + const std::string & section, + const std::string & entry, + bool val) const +{ + std::string s = getStr(section, entry); + if (s.empty()) + return val; + return str2bool(s); +} + + +bool GenericConfiguration::getFlag( + const std::string & section, + const std::string & entry) const +{ + ConfigParamList params; + + if (!get(section, entry, params)) + return false; + + // Flag - if exists then "true" + return true; +} + + +void GenericConfiguration::setFlag( + const std::string & section, + const std::string & entry, + bool val) +{ + ConfigParamList param; + + if (val) { + param.push_back("true"); + set(section, entry, param); + } else { + remove(section, entry); + } +} + + +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())); +} + + +long long int GenericConfiguration::getIntHex( + 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::string s = params.front(); + size_t foundPos = s.rfind("0x", 0); + if (foundPos == std::string::npos || foundPos != 0) { + // Add the prefix for hex conversion + s = "0x" + s; + } + std::stringstream val_str; + val_str << std::hex << s; + + // Output into int type + val_str >> std::hex >> val; + + return val; +} + + +void GenericConfiguration::setIntHex( + const std::string & section, + const std::string & entry, + long long int val) +{ + std::stringstream val_str; + + // Note NUT v2.8.1 introduced these as "hexnum" values, + // but the strtoul() underneath knows to strip "0x" for + // base16 conversions - so can we write them either way: + + // https://stackoverflow.com/a/61060765/4715872 + // << std::showbase for "0x" in saved printouts + val_str << std::nouppercase << std::hex << val; + + set(section, entry, ConfigParamList(1, val_str.str())); +} + + +double GenericConfiguration::getDouble( + const std::string & section, + const std::string & entry, + double val) const { ConfigParamList params; @@ -995,7 +1201,10 @@ long long int GenericConfiguration::getInt(const std::string & section, const st } -void GenericConfiguration::setInt(const std::string & section, const std::string & entry, long long int val) +void GenericConfiguration::setDouble( + const std::string & section, + const std::string & entry, + double val) { std::stringstream val_str; val_str << val; @@ -1004,11 +1213,31 @@ void GenericConfiguration::setInt(const std::string & section, const std::string } +nut::BoolInt GenericConfiguration::getBoolInt( + const std::string & section, + const std::string & entry, + nut::BoolInt val) const +{ + ConfigParamList params; + + if (!get(section, entry, params)) + return val; + + if (params.empty()) + return val; + + // TBD: What if there are multiple values? + nut::BoolInt bi(params.front()); + + return bi; +} + + bool GenericConfiguration::str2bool(const std::string & str) { if ("true" == str) return true; if ("on" == str) return true; - if ("1" == str) return true; + if ("1" == str) return true; if ("yes" == str) return true; if ("ok" == str) return true; @@ -1075,6 +1304,22 @@ UpsmonConfiguration::NotifyType UpsmonConfiguration::NotifyTypeFromString(const return NOTIFY_NOCOMM; else if(str=="NOPARENT") return NOTIFY_NOPARENT; + else if(str=="CAL") + return NOTIFY_CAL; + else if(str=="NOTCAL") + return NOTIFY_NOTCAL; + else if(str=="OFF") + return NOTIFY_OFF; + else if(str=="NOTOFF") + return NOTIFY_NOTOFF; + else if(str=="BYPASS") + return NOTIFY_BYPASS; + else if(str=="NOTBYPASS") + return NOTIFY_NOTBYPASS; + else if(str=="SUSPEND_STARTING") + return NOTIFY_SUSPEND_STARTING; + else if(str=="SUSPEND_FINISHED") + return NOTIFY_SUSPEND_FINISHED; else return NOTIFY_TYPE_MAX; } @@ -1150,7 +1395,16 @@ void UpsmonConfigParser::onParseDirective(const std::string& directiveName, char if(_config) { - if(directiveName == "RUN_AS_USER") + if(!(::strcasecmp(directiveName.c_str(), "DEBUG_MIN"))) + { + // NOTE: We allow DEBUG_MIN in any casing as it can be copy-pasted + // between different configs and they use different historic casing + if(values.size()>0) + { + _config->debugMin = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "RUN_AS_USER") { if(values.size()>0) { @@ -1177,7 +1431,7 @@ void UpsmonConfigParser::onParseDirective(const std::string& directiveName, char monitor.powerValue = StringToSettableNumber(*it++); monitor.username = *it++; monitor.password = *it++; - monitor.isMaster = (*it) == "master"; + monitor.isPrimary = (*it) == "primary"; // master for NUT v2.7.4 and older _config->monitors.push_back(monitor); } } @@ -1206,21 +1460,98 @@ void UpsmonConfigParser::onParseDirective(const std::string& directiveName, char { if(values.size()>0) { - _config->poolFreq = StringToSettableNumber(values.front()); + _config->pollFreq = StringToSettableNumber(values.front()); } } else if(directiveName == "POLLFREQALERT") { if(values.size()>0) { - _config->poolFreqAlert = StringToSettableNumber(values.front()); + _config->pollFreqAlert = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "POLLFAIL_LOG_THROTTLE_MAX") + { + if(values.size()>0) + { + _config->pollFailLogThrottleMax = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "OFFDURATION") + { + if(values.size()>0) + { + _config->offDuration = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "OBLBDURATION") + { + if(values.size()>0) + { + _config->oblbDuration = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "SHUTDOWNEXIT") + { + if(values.size()>0) + { + nut::BoolInt bi; + bi << values.front(); + _config->shutdownExit = bi; + } + } + else if(directiveName == "CERTPATH") + { + if(values.size()>0) + { + _config->certPath = values.front(); + } + } + else if(directiveName == "CERTIDENT") + { + if(values.size()==2) + { + _config->certIdent.certName = values.front(); + _config->certIdent.certDbPass = (*(++values.begin())); + } + } + else if(directiveName == "CERTHOST") + { + if(values.size()==4) + { + nut::CertHost certHost; + ConfigParamList::const_iterator it = values.begin(); + certHost.host = *it++; + certHost.certName = *it++; + certHost.certVerify = *it++; + certHost.forceSsl = *it++; + + _config->certHosts.push_back(certHost); + } + } + else if(directiveName == "CERTVERIFY") + { + if(values.size()>0) + { + nut::BoolInt bi; + bi << values.front(); + _config->certVerify = bi; + } + } + else if(directiveName == "FORCESSL") + { + if(values.size()>0) + { + nut::BoolInt bi; + bi << values.front(); + _config->forceSsl = bi; } } else if(directiveName == "HOSTSYNC") { if(values.size()>0) { - _config->hotSync = StringToSettableNumber(values.front()); + _config->hostSync = StringToSettableNumber(values.front()); } } else if(directiveName == "DEADTIME") @@ -1400,16 +1731,70 @@ void NutConfConfigParser::onParseDirective(const std::string& directiveName, cha { // 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 + if(_config) { - // TODO WTF with errors ? + if(directiveName == "MODE") + { + if (values.size()==1) { + std::string val = values.front(); + NutConfiguration::NutMode mode = NutConfiguration::NutModeFromString(val); + if(mode != NutConfiguration::MODE_UNKNOWN) + _config->mode = mode; + } + } + else if(directiveName == "ALLOW_NO_DEVICE") + { + if(values.size()>0) + { + _config->allowNoDevice = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "ALLOW_NOT_ALL_LISTENERS") + { + if(values.size()>0) + { + _config->allowNotAllListeners = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "UPSD_OPTIONS") + { + if(values.size()>0) + { + _config->upsdOptions = values.front(); + } + } + else if(directiveName == "UPSMON_OPTIONS") + { + if(values.size()>0) + { + _config->upsmonOptions = values.front(); + } + } + else if(directiveName == "POWEROFF_WAIT") + { + if(values.size()>0) + { + _config->poweroffWait = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "POWEROFF_QUIET") + { + if(values.size()>0) + { + _config->poweroffQuiet = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "NUT_DEBUG_LEVEL") + { + if(values.size()>0) + { + _config->debugLevel = StringToSettableNumber(values.front()); + } + } + else + { + // TODO WTF with errors ? + } } } @@ -1508,7 +1893,16 @@ void UpsdConfigParser::onParseDirective(const std::string& directiveName, char s if(_config) { - if(directiveName == "MAXAGE") + if(!(::strcasecmp(directiveName.c_str(), "DEBUG_MIN"))) + { + // NOTE: We allow DEBUG_MIN in any casing as it can be copy-pasted + // between different configs and they use different historic casing + if(values.size()>0) + { + _config->debugMin = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "MAXAGE") { if(values.size()>0) { @@ -1529,6 +1923,34 @@ void UpsdConfigParser::onParseDirective(const std::string& directiveName, char s _config->maxConn = StringToSettableNumber(values.front()); } } + else if(directiveName == "TRACKINGDELAY") + { + if(values.size()>0) + { + _config->trackingDelay = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "ALLOW_NO_DEVICE") + { + if(values.size()>0) + { + _config->allowNoDevice = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "ALLOW_NOT_ALL_LISTENERS") + { + if(values.size()>0) + { + _config->allowNotAllListeners = StringToSettableNumber(values.front()); + } + } + else if(directiveName == "DISABLE_WEAK_SSL") + { + if(values.size()>0) + { + _config->allowNotAllListeners = StringToSettableNumber(values.front()); + } + } else if(directiveName == "CERTFILE") { if(values.size()>0) @@ -1536,6 +1958,28 @@ void UpsdConfigParser::onParseDirective(const std::string& directiveName, char s _config->certFile = values.front(); } } + else if(directiveName == "CERTPATH") + { + if(values.size()>0) + { + _config->certPath = values.front(); + } + } + else if(directiveName == "CERTIDENT") + { + if(values.size()==2) + { + _config->certIdent.certName = values.front(); + _config->certIdent.certDbPass = (*(++values.begin())); + } + } + else if(directiveName == "CERTREQUEST") + { + if(values.size()>0) + { + _config->certRequestLevel = StringToSettableNumber(values.front()); + } + } else if(directiveName == "LISTEN") { if(values.size()==1 || values.size()==2) @@ -1570,11 +2014,11 @@ UpsdUsersConfiguration::upsmon_mode_t UpsdUsersConfiguration::getUpsmonMode() co { std::string mode_str = getStr("upsmon", "upsmon"); - if ("master" == mode_str) - return UPSMON_MASTER; + if ("primary" == mode_str || "master" == mode_str) + return UPSMON_PRIMARY; - if ("slave" == mode_str) - return UPSMON_SLAVE; + if ("secondary" == mode_str || "slave" == mode_str) + return UPSMON_SECONDARY; return UPSMON_UNDEF; } @@ -1584,7 +2028,8 @@ void UpsdUsersConfiguration::setUpsmonMode(upsmon_mode_t mode) { assert(UPSMON_UNDEF != mode); - setStr("upsmon", "upsmon", (UPSMON_MASTER == mode ? "master" : "slave")); + setStr("upsmon", "upsmon", (UPSMON_PRIMARY == mode ? "primary" : "secondary")); + /* NUT v2.7.4 and older: setStr("upsmon", "upsmon", (UPSMON_PRIMARY == mode ? "master" : "slave")); */ } diff --git a/common/nutipc.cpp b/common/nutipc.cpp index eaa2829f8e..d8bf131271 100644 --- a/common/nutipc.cpp +++ b/common/nutipc.cpp @@ -5,6 +5,10 @@ Author: Vaclav Krpec + Copyright (C) 2024 NUT Community + + Author: Jim Klimov + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or @@ -35,6 +39,7 @@ # define HAVE_LOCALTIME_R 111 #endif +#include "common.h" #include "nutipc.hpp" #include "nutstream.hpp" @@ -293,8 +298,9 @@ int Signal::send(Signal::enum_t signame, const std::string & pid_file) { 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; + // FIXME: What about ALTPIDPATH (for non-root daemons) + // and shouldn't we also consider it (e.g. try/catch)? + pid_file += rootpidpath(); pid_file += '/'; pid_file += process; pid_file += ".pid"; diff --git a/common/nutstream.cpp b/common/nutstream.cpp index 555cc44f15..4a0092d2f1 100644 --- a/common/nutstream.cpp +++ b/common/nutstream.cpp @@ -3,6 +3,7 @@ Copyright (C) 2012 Vaclav Krpec + 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -299,7 +300,23 @@ static const char* getTmpDirPath() { return "/tmp"; } +/* Pedantic builds complain about the static variable below... + * It is assumed safe to ignore since it is a std::string with + * no complex teardown at program exit. + */ +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic push +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS +# pragma GCC diagnostic ignored "-Wglobal-constructors" +# endif +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS +# pragma GCC diagnostic ignored "-Wexit-time-destructors" +# endif +#endif const std::string NutFile::m_tmp_dir(getTmpDirPath()); +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS || defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_GLOBAL_CONSTRUCTORS) +#pragma GCC diagnostic pop +#endif NutFile::NutFile(anonymous_t): m_name(""), @@ -415,6 +432,31 @@ const char * NutFile::strAccessMode(access_t mode) case APPEND_ONLY: mode_str = append_only; break; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } assert(nullptr != mode_str); @@ -1032,6 +1074,8 @@ bool NutSocket::accept( case ENFILE: // Open file descriptors per-system limit was reached case EPROTO: // Protocol error return false; + default: + break; } std::stringstream e; diff --git a/common/nutwriter.cpp b/common/nutwriter.cpp index 8cc232bcdc..b3db8d69a6 100644 --- a/common/nutwriter.cpp +++ b/common/nutwriter.cpp @@ -3,6 +3,7 @@ Copyright (C) 2012 Vaclav Krpec + 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -29,7 +30,7 @@ #include #include #include - +#include /** * \brief NUT configuration directive generator @@ -64,6 +65,50 @@ } while (0) +/** + * \brief Shell (envvar) configuration directive generator + * + * The macro is used to simplify generation of + * nut.conf file 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 + */ +// NOTE: Due to this being a macro applied to any argument type, +// implementation for e.g. bool handling jumps through hoops like +// stringification and back. FIXME: working optimization welcome. +#define SHELL_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 << '\''; \ + if (typeid(arg_val) == typeid(bool&)) { \ + std::stringstream ssb; \ + ssb << arg_val; \ + std::string sb = ssb.str(); \ + if ("1" == sb) { ss << "true"; } \ + else if ("0" == sb) { ss << "false"; } \ + else { ss << arg_val; } \ + } else \ + ss << arg_val; \ + if (quote_arg) \ + ss << '\''; \ + status_t status = writeDirective(ss.str()); \ + if (NUTW_OK != status) \ + return status; \ + } \ + } while (0) + + namespace nut { @@ -71,10 +116,18 @@ namespace nut { * 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() {} +NutConfigWriter::~NutConfigWriter() {} // generic interface/base class +// Flat-config classes: +NutConfConfigWriter::~NutConfConfigWriter() {} // nut.conf, shell format +UpsmonConfigWriter::~UpsmonConfigWriter() {} // upsmon.conf +UpsdConfigWriter::~UpsdConfigWriter() {} // upsd.conf +// Structured-config classes R/W is handled via GenericConfiguration: +// UpsConfiguration: ups.conf +// UpsdUsersConfiguration: upsd.users +// Not handled currently: +// xxx: upssched.conf +// xxx: upsset.conf +// xxx: hosts.conf // End-of-Line separators (arch. dependent) @@ -97,6 +150,62 @@ const std::string GenericConfigWriter::s_default_section_entry_indent("\t"); const std::string GenericConfigWriter::s_default_section_entry_separator(" = "); +/** + * \brief NSS certificate identity serializer + * + * \param ident Certificate identity object + * + * \return Serialized certificate identity + */ +static std::string serializeCertIdent(const nut::CertIdent & ident) { + std::stringstream directive; + const std::string & val1 = (ident.certName), val2 = (ident.certDbPass); + + directive << "CERTIDENT \"" << val1 << "\" \"" << val2 << "\""; + + return directive.str(); +} + + +/** + * \brief NSS certificate-protected host info serializer + * + * \param certHost Certificate-protected host info object + * + * \return Serialized certificate-protected host info + */ +static std::string serializeCertHost(const nut::CertHost & certHost) { + std::stringstream directive; + const std::string & val1 = (certHost.host), val2 = (certHost.certName); + + directive << "CERTHOST \"" << val1 << "\" \"" << val2 << "\""; + + // Spec says to write these as 0/1 integers + nut::BoolInt bi; + int i; + // NOTE: After copy-assignments below (which inherit original strictness), + // need to add relaxed mode for 0/1 as false/true handling: + //bi.bool01 = true; + + // Avoid static analysis concerns that the internal _value + // "may be used uninitialized in this function" (ETOOSMART): + bi = false; + + // Assumed to be set() - exception otherwise + bi = certHost.certVerify; + bi.bool01 = true; + i = bi; + directive << " " << i; + + bi = certHost.forceSsl; + bi.bool01 = true; + i = bi; + directive << " " << i; + + return directive.str(); +} + + NutWriter::status_t NutWriter::writeEachLine(const std::string & str, const std::string & pref) { for (size_t pos = 0; pos < str.size(); ) { // Prefix every line @@ -145,11 +254,10 @@ NutWriter::status_t SectionlessConfigWriter::writeSectionName(const std::string NutWriter::status_t NutConfConfigWriter::writeConfig(const NutConfiguration & config) { - status_t status; - // Mode // TBD: How should I serialize an unknown mode? if (config.mode.set()) { + status_t status; std::string mode_str; NutConfiguration::NutMode mode = config.mode; @@ -181,6 +289,31 @@ NutWriter::status_t NutConfConfigWriter::writeConfig(const NutConfiguration & co case NutConfiguration::MODE_MANUAL: mode_str = "manual"; break; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } status = writeDirective("MODE=" + mode_str); @@ -189,6 +322,31 @@ NutWriter::status_t NutConfConfigWriter::writeConfig(const NutConfiguration & co return status; } +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +#endif + SHELL_CONFIG_DIRECTIVEX("ALLOW_NO_DEVICE", bool, config.allowNoDevice, false); + SHELL_CONFIG_DIRECTIVEX("ALLOW_NOT_ALL_LISTENERS", bool, config.allowNotAllListeners, false); + SHELL_CONFIG_DIRECTIVEX("UPSD_OPTIONS", std::string, config.upsdOptions, true); + SHELL_CONFIG_DIRECTIVEX("UPSMON_OPTIONS", std::string, config.upsmonOptions, true); + SHELL_CONFIG_DIRECTIVEX("POWEROFF_WAIT", unsigned int, config.poweroffWait, false); + SHELL_CONFIG_DIRECTIVEX("POWEROFF_QUIET", bool, config.poweroffQuiet, false); + SHELL_CONFIG_DIRECTIVEX("NUT_DEBUG_LEVEL", int, config.debugLevel, false); +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) +# pragma GCC diagnostic pop +#endif + return NUTW_OK; } @@ -240,6 +398,14 @@ const NotifyFlagsStrings::TypeStrings NotifyFlagsStrings::type_str = { "REPLBATT", // NOTIFY_REPLBATT "NOCOMM", // NOTIFY_NOCOMM "NOPARENT", // NOTIFY_NOPARENT + "CAL\t", // NOTIFY_CAL (including padding) + "NOTCAL", // NOTIFY_NOTCAL + "OFF\t", // NOTIFY_OFF (including padding) + "NOTOFF", // NOTIFY_NOTOFF + "BYPASS", // NOTIFY_BYPASS + "NOTBYPASS", // NOTIFY_NOTBYPASS + "SUSPEND_STARTING", // NOTIFY_SUSPEND_STARTING + "SUSPEND_FINISHED", // NOTIFY_SUSPEND_FINISHED }; @@ -391,8 +557,9 @@ static std::string serializeMonitor(const UpsmonConfiguration::Monitor & monitor // Username & password directive << monitor.username << ' ' << monitor.password << ' '; - // Master/slave - directive << (monitor.isMaster ? "master" : "slave"); + // Primary/secondary (legacy master/slave) + directive << (monitor.isPrimary ? "primary" : "secondary"); + /* NUT v2.7.4 and older: directive << (monitor.isPrimary ? "master" : "slave");*/ return directive.str(); } @@ -415,7 +582,7 @@ NutWriter::status_t UpsmonConfigWriter::writeConfig(const UpsmonConfiguration & * \param arg Directive argument * \param quote_arg Boolean flag; check to quote the argument */ - #define UPSMON_DIRECTIVEX(name, arg_t, arg, quote_arg) \ +# define UPSMON_DIRECTIVEX(name, arg_t, arg, quote_arg) \ CONFIG_DIRECTIVEX(name, arg_t, arg, quote_arg) /* The "false" arg in macro below evaluates to `if (false) ...` after @@ -431,14 +598,55 @@ NutWriter::status_t UpsmonConfigWriter::writeConfig(const UpsmonConfiguration & # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wunreachable-code" #endif - UPSMON_DIRECTIVEX("RUN_AS_USER", std::string, config.runAsUser, false); + UPSMON_DIRECTIVEX("DEBUG_MIN", int, config.debugMin, false); + UPSMON_DIRECTIVEX("RUN_AS_USER", std::string, config.runAsUser, true); 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("POWERDOWNFLAG", std::string, config.powerDownFlag, true); 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("POLLFREQ", unsigned int, config.pollFreq, false); + UPSMON_DIRECTIVEX("POLLFREQALERT", unsigned int, config.pollFreqAlert, false); + UPSMON_DIRECTIVEX("POLLFAIL_LOG_THROTTLE_MAX", int, config.pollFailLogThrottleMax, false); + UPSMON_DIRECTIVEX("OFFDURATION", int, config.offDuration, false); + UPSMON_DIRECTIVEX("OBLBDURATION", int, config.oblbDuration, false); + UPSMON_DIRECTIVEX("SHUTDOWNEXIT", nut::BoolInt, config.shutdownExit, false); + + UPSMON_DIRECTIVEX("CERTPATH", std::string, config.certPath, true); + + // Spec says to write these as 0/1 integers + // and the macro requires Settable<> + // Mumbo-jumbo below for guaranteed casting to int + nut::BoolInt bi, bi2; + Settable bis; + int i; + // NOTE: After copy-assignments below (which inherit original strictness), + // need to add relaxed mode for 0/1 as false/true handling: + // bi.bool01 = true; + bi2.bool01 = false; // strict mode for 0/1 as int handling + // Avoid static analysis concerns that the internal _value + // "may be used uninitialized in this function" (ETOOSMART): + bi = false; + bi2 = false; + + if (config.certVerify.set()) { + bi = config.certVerify; + bi.bool01 = true; + i = bi; + bi2 = i; + bis = bi2; + UPSMON_DIRECTIVEX("CERTVERIFY", nut::BoolInt, bis, false); + } + + if (config.forceSsl.set()) { + bi = config.forceSsl; + bi.bool01 = true; + i = bi; + bi2 = i; + bis = bi2; + UPSMON_DIRECTIVEX("FORCESSL", nut::BoolInt, bis, false); + } + + UPSMON_DIRECTIVEX("HOSTSYNC", unsigned int, config.hostSync, 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); @@ -450,7 +658,29 @@ NutWriter::status_t UpsmonConfigWriter::writeConfig(const UpsmonConfiguration & # pragma GCC diagnostic pop #endif - #undef UPSMON_DIRECTIVEX +# undef UPSMON_DIRECTIVEX + + // Certificate identity + if (config.certIdent.set()) { + std::string directive = serializeCertIdent(config.certIdent); + + status_t status = writeDirective(directive); + + if (NUTW_OK != status) + return status; + } + + // Remote host(s) protected by specific certificates on their listeners + std::list::const_iterator la_iter = config.certHosts.begin(); + + for (; la_iter != config.certHosts.end(); ++la_iter) { + std::string directive = serializeCertHost(*la_iter); + + status_t status = writeDirective(directive); + + if (NUTW_OK != status) + return status; + } UpsmonConfiguration::NotifyType type; @@ -532,7 +762,7 @@ NutWriter::status_t UpsdConfigWriter::writeConfig(const UpsdConfiguration & conf * \param arg_t Directive argument implementation type * \param arg Directive argument */ - #define UPSD_DIRECTIVEX(name, arg_t, arg) \ +# define UPSD_DIRECTIVEX(name, arg_t, arg) \ CONFIG_DIRECTIVEX(name, arg_t, arg, false) /* The "false" arg in macro below evaluates to `if (false) ...` after @@ -548,10 +778,17 @@ NutWriter::status_t UpsdConfigWriter::writeConfig(const UpsdConfiguration & conf # pragma clang diagnostic push # pragma clang diagnostic ignored "-Wunreachable-code" #endif - 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); + UPSD_DIRECTIVEX("DEBUG_MIN", int, config.debugMin); + UPSD_DIRECTIVEX("MAXAGE", unsigned int, config.maxAge); + UPSD_DIRECTIVEX("MAXCONN", unsigned int, config.maxConn); + UPSD_DIRECTIVEX("TRACKINGDELAY", unsigned int, config.trackingDelay); + UPSD_DIRECTIVEX("ALLOW_NO_DEVICE", bool, config.allowNoDevice); + UPSD_DIRECTIVEX("ALLOW_NOT_ALL_LISTENERS", bool, config.allowNotAllListeners); + UPSD_DIRECTIVEX("DISABLE_WEAK_SSL", bool, config.disableWeakSsl); + CONFIG_DIRECTIVEX("STATEPATH", std::string, config.statePath, true); + CONFIG_DIRECTIVEX("CERTFILE", std::string, config.certFile, true); + CONFIG_DIRECTIVEX("CERTPATH", std::string, config.certPath, true); + UPSD_DIRECTIVEX("CERTREQUEST", unsigned int, config.certRequestLevel); #ifdef __clang__ # pragma clang diagnostic pop #endif @@ -559,7 +796,17 @@ NutWriter::status_t UpsdConfigWriter::writeConfig(const UpsdConfiguration & conf # pragma GCC diagnostic pop #endif - #undef UPSD_DIRECTIVEX +# undef UPSD_DIRECTIVEX + + // Certificate identity + if (config.certIdent.set()) { + std::string directive = serializeCertIdent(config.certIdent); + + status_t status = writeDirective(directive); + + if (NUTW_OK != status) + return status; + } // Listen addresses std::list::const_iterator la_iter = config.listens.begin(); diff --git a/common/parseconf.c b/common/parseconf.c index f7d6eec61b..41e376146f 100644 --- a/common/parseconf.c +++ b/common/parseconf.c @@ -485,6 +485,9 @@ static void parse_char(PCONF_CTX_t *ctx) case STATE_COLLECTLITERAL: ctx->state = collectliteral(ctx); break; + + default: + break; } /* switch */ } diff --git a/common/state.c b/common/state.c index 6eaf18ab6c..51eb8db6ff 100644 --- a/common/state.c +++ b/common/state.c @@ -4,6 +4,7 @@ 2003 Russell Kroll 2008 Arjen de Korte 2012 Arnaud Quette + 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -295,6 +296,7 @@ int state_setinfo(st_tree_t **nptr, const char *var, const char *val) /* changes should be ignored */ if (node->flags & ST_FLAG_IMMUTABLE) { + upsdebugx(6, "%s: not changing immutable variable [%s]", __func__, var); return 0; /* no change */ } @@ -357,8 +359,8 @@ int state_addenum(st_tree_t *root, const char *var, const char *val) sttmp = state_tree_find(root, var); if (!sttmp) { - upslogx(LOG_ERR, "state_addenum: base variable (%s) " - "does not exist", var); + upslogx(LOG_ERR, "%s: base variable (%s) " + "does not exist", __func__, var); return 0; /* failed */ } @@ -400,8 +402,8 @@ int state_addrange(st_tree_t *root, const char *var, const int min, const int ma /* sanity check */ if (min > max) { - upslogx(LOG_ERR, "state_addrange: min is superior to max! (%i, %i)", - min, max); + upslogx(LOG_ERR, "%s: min is superior to max! (%i, %i)", + __func__, min, max); return 0; } @@ -409,8 +411,8 @@ int state_addrange(st_tree_t *root, const char *var, const int min, const int ma sttmp = state_tree_find(root, var); if (!sttmp) { - upslogx(LOG_ERR, "state_addrange: base variable (%s) " - "does not exist", var); + upslogx(LOG_ERR, "%s: base variable (%s) " + "does not exist", __func__, var); return 0; /* failed */ } @@ -427,8 +429,8 @@ int state_setaux(st_tree_t *root, const char *var, const char *auxs) sttmp = state_tree_find(root, var); if (!sttmp) { - upslogx(LOG_ERR, "state_addenum: base variable (%s) " - "does not exist", var); + upslogx(LOG_ERR, "%s: base variable (%s) " + "does not exist", __func__, var); return -1; /* failed */ } @@ -524,8 +526,8 @@ void state_setflags(st_tree_t *root, const char *var, size_t numflags, char **fl sttmp = state_tree_find(root, var); if (!sttmp) { - upslogx(LOG_ERR, "state_setflags: base variable (%s) " - "does not exist", var); + upslogx(LOG_ERR, "%s: base variable (%s) " + "does not exist", __func__, var); return; } @@ -549,7 +551,7 @@ void state_setflags(st_tree_t *root, const char *var, size_t numflags, char **fl continue; } - upsdebugx(2, "Unrecognized flag [%s]", flag[i]); + upsdebugx(2, "%s: Unrecognized flag [%s]", __func__, flag[i]); } } diff --git a/common/str.c b/common/str.c index 295f450804..df9f0734e8 100644 --- a/common/str.c +++ b/common/str.c @@ -627,3 +627,30 @@ int str_ends_with(const char *s, const char *suff) { return (slen >= sufflen) && (!memcmp(s + slen - sufflen, suff, sufflen)); } + +#ifndef HAVE_STRTOF +# include +# include +float strtof(const char *nptr, char **endptr) +{ + double d; + int i; + + if (!nptr || !*nptr) { + errno = EINVAL; + return 0; + } + + i = sscanf(nptr, "%f", &d); + if (i < 1) { + errno = EINVAL; + return 0; + } + + if (endptr) { + *endptr = (char*)nptr + i; + } + + return (float)d; +} +#endif diff --git a/common/timegm_fallback.c b/common/timegm_fallback.c new file mode 100644 index 0000000000..5c8a9bfc34 --- /dev/null +++ b/common/timegm_fallback.c @@ -0,0 +1,49 @@ +/* Fallback timegm() for systems that lack one. + * Algorithm: http://howardhinnant.github.io/date_algorithms.html + * https://stackoverflow.com/a/58037981/4715872 + */ + +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif + +static int days_from_epoch_1970(int y, int m, int d) +{ + y -= m <= 2; + int era = y / 400; + int yoe = y - era * 400; // [0, 399] + int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1; // [0, 365] + int doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; // [0, 146096] + return era * 146097 + doe - 719468; +} + +/* It does not modify broken-down time */ +time_t timegm_fallback(struct tm const* t) +{ + int year = t->tm_year + 1900; + int month = t->tm_mon; // 0-11 + int days_since_epoch_1970; + + if (month > 11) + { + year += month / 12; + month %= 12; + } + else if (month < 0) + { + int years_diff = (11 - month) / 12; + year -= years_diff; + month += 12 * years_diff; + } + days_since_epoch_1970 = days_from_epoch_1970(year, month + 1, t->tm_mday); + + return 60 * (60 * (24L * days_since_epoch_1970 + t->tm_hour) + t->tm_min) + t->tm_sec; +} + diff --git a/conf/Makefile.am b/conf/Makefile.am index 873d288324..fd384eebbc 100644 --- a/conf/Makefile.am +++ b/conf/Makefile.am @@ -22,7 +22,7 @@ SPELLCHECK_SRC = $(dist_sysconf_DATA) \ # NOTE: Due to portability, we do not use a GNU percent-wildcard extension. # We also have to export some variables that may be tainted by relative # paths when parsing the other makefile (e.g. MKDIR_P that may be defined -# via expanded $(top_builddir)/install_sh): +# via expanded $(top_builddir)/install-sh): #%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) # +$(MAKE) -s -f $(top_builddir)/docs/Makefile $(AM_MAKEFLAGS) MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ diff --git a/conf/hosts.conf.sample b/conf/hosts.conf.sample index 24beb82956..37e561e78b 100644 --- a/conf/hosts.conf.sample +++ b/conf/hosts.conf.sample @@ -3,6 +3,9 @@ # This file is used to control the CGI programs. If you have not # installed them, you may safely ignore or delete this file. # +# NOTE: Contents of this file should be pure ASCII (character codes +# not in range would be ignored with a warning message). +# # ----------------------------------------------------------------------- # # upsstats will use the list of MONITOR entries when displaying the diff --git a/conf/nut.conf.sample b/conf/nut.conf.sample index 71d5465d92..b58730480f 100644 --- a/conf/nut.conf.sample +++ b/conf/nut.conf.sample @@ -9,12 +9,19 @@ # # IMPORTANT NOTES: # This file is intended to be sourced by standard POSIX shell scripts -# (so there is no guaranteed `export VAR=VAL` syntax) and additionally -# by systemd on Linux (no guaranteed expansion of variables). +# (so there is no guaranteed `export VAR=VAL` syntax, while you may need +# to `export VAR` when sourcing it into init-scripts, for propagation to +# NUT programs eventually), and additionally by systemd on Linux (with no +# guaranteed expansion of variables -- only verbatim assignments). +# # You MUST NOT use spaces around the equal sign! +# # Practical support for this file and its settings currently varies between # various OS packages and NUT sample scripts, but should converge over time. # +# Contents of this file should be pure ASCII (character codes not in range +# would be ignored with a warning message). +# # See also: `man nut.conf` (usually in Manual pages Section 5, # for Configuration files) # @@ -86,3 +93,69 @@ MODE=none # via logs or console captures. # Set to `true` to avoid that trove of information, if you consider it noise. #POWEROFF_QUIET=true + +# The optional 'NUT_DEBUG_LEVEL' setting controls the default debugging message +# verbosity passed to NUT daemons. As an environment variable, its priority sits +# between that of 'DEBUG_MIN' setting of a driver and the command-line options. +#NUT_DEBUG_LEVEL=0 +#export NUT_DEBUG_LEVEL + +# Optionally add current process ID to tags with debug-level identifiers. +# This may be useful when many NUT daemons write to the same console or log +# file, such as in containers/plugins for Home Assistant, storage appliances... +#NUT_DEBUG_PID=true +#export NUT_DEBUG_PID + +# Normally NUT can (attempt to) use the syslog or Event Log (WIN32), but the +# environment variable 'NUT_DEBUG_SYSLOG' allows to bypass it, and perhaps keep +# the daemons logging to stderr (useful e.g. in NUT Integration Test suite to +# not pollute the OS logs, or in systemd where stderr and syslog both go into +# the same journal). Recognized values: +# `stderr` Disabled and background() keeps stderr attached +# `none` Disabled and background() detaches stderr as usual +# `default`/unset/other Not disabled +#NUT_DEBUG_SYSLOG=stderr +#export NUT_DEBUG_SYSLOG + +# Normally NUT can (attempt to) verify that the program file name matches the +# name associated with a running process, when using PID files to send signals. +# The `NUT_IGNORE_CHECKPROCNAME` boolean toggle allows to quickly skip such +# verification, in case it causes problems (e.g. NUT programs were renamed and +# do not match built-in expectations). This environment variable can also be +# optionally set in init-scripts or service methods for `upsd`, `upsmon` and +# NUT drivers/`upsdrvctl`. +#NUT_IGNORE_CHECKPROCNAME=true +#export NUT_IGNORE_CHECKPROCNAME + +# Optional flag to prevent daemons which can notify service management frameworks +# (such as systemd) about passing their lifecycle milestones, to not report +# loudly if they could NOT do so (e.g. running on a system without a framework, +# or misconfigured so they could not report and the OS could eventually restart +# the false-positively identified "unresponsive" service. +# Currently such reports, done by default, help troubleshoot service start-up +# and highlight that NUT sources (or package build) did not take advantage of +# tighter OS service management framework integration (if one exists, so that +# developers could focus on adding that). Reasons to set this flag could include +# platforms without such a framework and not expecting one, although nagging +# your favourite OS or contributing development to make it better is also a way. +#NUT_QUIET_INIT_UPSNOTIFY=true +#export NUT_QUIET_INIT_UPSNOTIFY + +############################################################################## +# Variables that can be helpful more for tool scripting than service daemons +############################################################################## + +# Optionally prevent `libupsclient` consumers (notoriously `upsc`, maybe +# also `dummy-ups` driver or `nut-scanner` tool) from reporting whether +# they have initialized SSL support -- or, loudly, failed to initialize +# as it was not configured on this system. +#NUT_QUIET_INIT_SSL=true +#export NUT_QUIET_INIT_SSL + +# Optionally suppress NUT tool name and version banner (normally shown in most +# NUT programs unilaterally, before processing any CLI options and possibly +# failing due to that). +# NOT recommended for services due to adverse troubleshooting impact, but may +# be helpful in shell profiles or scripts which process NUT tool outputs. +#NUT_QUIET_INIT_BANNER=true +#export NUT_QUIET_INIT_BANNER diff --git a/conf/ups.conf.sample b/conf/ups.conf.sample index 3a37f82ad4..2eff376ca0 100644 --- a/conf/ups.conf.sample +++ b/conf/ups.conf.sample @@ -9,6 +9,9 @@ # # --- # +# NOTE: Contents of this file should be pure ASCII (character codes +# not in range would be ignored with a warning message). +# # This is where you configure all the UPSes that this system will be # monitoring directly. These are usually attached to serial ports, but # USB devices and SNMP devices are also supported. @@ -172,6 +175,15 @@ maxretry = 3 # so it is not called by default. Yet others can be composite # devices which use a non-zero interface to represent the UPS. # +# usb_config_index=hexnum, usb_hid_rep_index=hexnum, +# usb_hid_desc_index=hexnum, usb_hid_ep_in=hexnum, usb_hid_ep_out=hexnum: +# OPTIONAL. Force use of specific interface, endpoint, descriptor +# index etc. numbers, rather than defaulting to 0 (rarely other +# values in certain drivers for some devices known to use non-zero +# numbers). As a rule of thumb for `usb_hid_desc_index` discovery, +# you can see larger `wDescriptorLength` values (roughly 600+ bytes) +# in reports of `lsusb` or similar tools. +# # default.: OPTIONAL. Set a default value for which is # used in case the UPS doesn't provide a value, but which will be # overwritten if a value is available from the UPS, e.g.: diff --git a/conf/upsd.conf.sample b/conf/upsd.conf.sample index dfbdf9e5ba..8fe3d1333d 100644 --- a/conf/upsd.conf.sample +++ b/conf/upsd.conf.sample @@ -7,6 +7,9 @@ # Each entry below provides usage and default value. # # For more information, refer to upsd.conf manual page. +# +# NOTE: Contents of this file should be pure ASCII (character codes +# not in range would be ignored with a warning message). # ======================================================================= # MAXAGE diff --git a/conf/upsd.users.sample b/conf/upsd.users.sample index 60c42892bf..5fd4e2443c 100644 --- a/conf/upsd.users.sample +++ b/conf/upsd.users.sample @@ -4,6 +4,9 @@ # Users are defined here, are given passwords, and their privileges are # controlled here too. Since this file will contain passwords, keep it # secure, with only enough permissions for upsd to read it. +# +# NOTE: Contents of this file should be pure ASCII (character codes +# not in range would be ignored with a warning message). # -------------------------------------------------------------------------- diff --git a/conf/upsmon.conf.sample.in b/conf/upsmon.conf.sample.in index bcda24a549..8f5a69b0e2 100644 --- a/conf/upsmon.conf.sample.in +++ b/conf/upsmon.conf.sample.in @@ -1,6 +1,14 @@ # Network UPS Tools: example upsmon configuration # # This file contains passwords, so keep it secure. +# +# NOTE: Contents of this file should be pure ASCII (character codes +# not in range would be ignored with a warning message). +# +# A minimal configuration should include at least one MONITOR instruction, +# MINSUPPLIES (may be 0 if this system is only monitoring other NUT servers), +# and a POWERDOWNFLAG if this machine is a "primary" system connected to +# the UPS and drives its late-shutdown power-off command in an emergency. # -------------------------------------------------------------------------- # RUN_AS_USER @@ -292,8 +300,13 @@ DEADTIME 15 # For Windows setup use something like: # POWERDOWNFLAG "C:\\killpower" # -# For Unix/Linux systems the legacy default is: -# POWERDOWNFLAG /etc/killpower +# For Unix/Linux systems the legacy common value is: +# POWERDOWNFLAG /etc/killpower +# but nowadays you may want it in a temporary filesystem (e.g. under /run) +# +# WARNING: The 'upsmon' binary program does not have a built-in default, +# so this setting MUST be specified in the configuration, in order for the +# late shutdown integration to work on the particular primary-mode system! POWERDOWNFLAG "@POWERDOWNFLAG@" # -------------------------------------------------------------------------- @@ -320,9 +333,14 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # NOTIFYMSG BYPASS "UPS %s: on bypass (powered, not protecting)" # NOTIFYMSG NOTBYPASS "UPS %s: no longer on bypass" # -# Note that %s is replaced with the identifier of the UPS in question. +# A few messages not directly related to UPS events are also available: +# +# NOTIFYMSG SUSPEND_STARTING "OS is entering sleep/suspend/hibernate mode" +# NOTIFYMSG SUSPEND_FINISHED "OS just finished sleep/suspend/hibernate mode, de-activating obsolete UPS readings to avoid an unfortunate shutdown" # -# Possible values for : +# Note that %s (where used) is replaced with the identifier of the UPS in question. +# +# Meanings of some possible values for : # # ONLINE : UPS is back online # ONBATT : UPS is on battery @@ -362,6 +380,9 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # NOTIFYFLAG BYPASS SYSLOG+WALL # NOTIFYFLAG NOTBYPASS SYSLOG+WALL # +# NOTIFYFLAG SUSPEND_STARTING SYSLOG+WALL +# NOTIFYFLAG SUSPEND_FINISHED SYSLOG+WALL +# # Possible values for the flags: # # SYSLOG - Write the message in the syslog @@ -390,6 +411,29 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" OFFDURATION 30 +# -------------------------------------------------------------------------- +# OBLBDURATION - put "OB LB" state into effect if it persists for this many +# seconds +# +# NUT normally raises alarms for immediate shutdown (FSD) for consumers of an +# UPS known to be on battery (OB) and achieving the low battery status (LB), +# if that is their last remaining power source to satisfy their MINSUPPLIES +# setting. In some special cases, users may want to delay raising the alarm +# (using the OBLBDURATION option) at their discretion and risk of an ungraceful +# shutdown. +# +# A non-positive value makes the FSD effect of detected "OB LB" state immediate. +# Built-in default value is 0 (seconds). +# +# NOTE: If both `OBLBDURATION` and `HOSTSYNC` options are set on the same +# (secondary) `upsmon` client system, and `HOSTSYNC` is shorter, it would be +# effectively ignored: `upsmon` would wait for up to `OBLBDURATION` seconds +# for the "OB LB" state to clear, and then the secondary client logic would +# fall through to immediate shutdown. If the primary system issues an FSD on +# this UPS, that would take an even higher-priority effect as soon as seen. +# +#OBLBDURATION 0 + # -------------------------------------------------------------------------- # RBWARNTIME - replace battery warning time in seconds # diff --git a/conf/upssched.conf.sample.in b/conf/upssched.conf.sample.in index 541dea8234..79543cd714 100644 --- a/conf/upssched.conf.sample.in +++ b/conf/upssched.conf.sample.in @@ -1,5 +1,8 @@ # Network UPS Tools - upssched.conf sample file # +# NOTE: Contents of this file should be pure ASCII (character codes +# not in range would be ignored with a warning message). +# # ============================================================================ # # CMDSCRIPT diff --git a/conf/upsset.conf.sample b/conf/upsset.conf.sample index 3bb4850573..9825f787a0 100644 --- a/conf/upsset.conf.sample +++ b/conf/upsset.conf.sample @@ -5,6 +5,9 @@ # the upsset.cgi program from running until you have assured it that you # have secured your web server's CGI directory. # +# NOTE: Contents of this file should be pure ASCII (character codes +# not in range would be ignored with a warning message). +# # By default, your web server will probably let anyone access upsset.cgi # once it is installed. This means that anyone could attempt to crack # upsd logins since they would appear to be coming from your web server, diff --git a/configure.ac b/configure.ac index 640c715840..fedaafbb64 100644 --- a/configure.ac +++ b/configure.ac @@ -2,10 +2,31 @@ dnl +------------------------------------------------------------------+ dnl | Network UPS Tools: configure.ac | dnl +------------------------------------------------------------------+ +dnl # Support for older autoconf without m4_esyscmd_s +m4_ifndef([m4_chomp_all], [m4_define([m4_chomp_all], [m4_format([[%.*s]], m4_bregexp(m4_translit([[$1]], [/], [/ ]), [/*$]), [$1])])]) +m4_ifndef([m4_esyscmd_s], [m4_define([m4_esyscmd_s], [m4_chomp_all(m4_esyscmd([$1]))])]) + dnl NUT version number is defined here, with a Git suffixed macro like dnl NUT_VERSION_MACRO "2.7.4-2838-gdfc3ac08" -dnl in include/nut_version.h (generated by make) -AC_INIT([nut],[2.8.1.1],[https://github.com/networkupstools/nut/issues]) +dnl defined separately in include/nut_version.h (generated by make) +dnl ...or not defined, for quicker rebuilds, depending on settings. +dnl Old hard-coded approach (mangled a bit): +dnl AC INIT([nut],[2.8.2.1],[https://github.com/networkupstools/nut/issues]) +dnl Note: srcdir is only set after AC INIT has completed, but $0 points +dnl to the generated configure script which is part of dist tarball and +dnl should be at source root too. +dnl Note: this gets evaluated (script called) a lot of times during autoconf +dnl but ends up as static strings in the generated configure script. It may +dnl be beneficial to have a `version.m4` generated by `autogen.sh` and/or +dnl shipped in a tarball, allowing for a singular include into configure.ac +dnl See https://github.com/networkupstools/nut/issues/1949 for details. +dnl Oddly, autoconf-2.69 on CentOS 7 both complains that AC INIT argument +dnl is not a literal, and does contain expected values in the generated +dnl script and include/config.h. A "pure" m4 solution would be quieter. +AC_INIT([nut], + m4_esyscmd_s([NUT_VERSION_QUERY=VER50 "`dirname "$0"`/tools/gitlog2version.sh" 2>/dev/null]), + [https://github.com/networkupstools/nut/issues],[nut], + m4_esyscmd_s([NUT_VERSION_QUERY=URL "`dirname "$0"`/tools/gitlog2version.sh" 2>/dev/null])) dnl See docs/maintainer-guide.txt about releases - updating the version dnl above is a small part of the consistent ritual! @@ -15,12 +36,25 @@ dnl the PDF documentation revision history via docs/docinfo.xml.in). dnl If the NUT codebase in this workspace is being developed and rebuilt dnl without reconfiguration, detailed version in the binaries will differ dnl (that one comes from the NUT_VERSION_MACRO from nut_version.h file). -(command -v git >/dev/null 2>/dev/null) \ -&& NUT_SOURCE_GITREV="`(git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]' --exclude '*-signed' --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*' 2>/dev/null || git describe --tags --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*' --exclude '*Windows*' --exclude '*IPM*' 2>/dev/null ) | sed -e 's/^v\([0-9]\)/\1/' -e 's,^.*/,,'`" \ -|| NUT_SOURCE_GITREV="" +dnl # Example: NUT_SOURCE_GITREV='2.8.2.695.1-696-g0e00f0777' +NUT_SOURCE_GITREV="`NUT_VERSION_QUERY=DESC50 "${srcdir}/tools/gitlog2version.sh" 2>/dev/null`" + +dnl A true/false (literally) response that can be used for prettier messages +dnl emitted by NUT code. +NUT_SOURCE_GITREV_IS_RELEASE="`NUT_VERSION_QUERY=IS_RELEASE "${srcdir}/tools/gitlog2version.sh" 2>/dev/null`" +AS_IF([$NUT_SOURCE_GITREV_IS_RELEASE], [NUT_SOURCE_GITREV_DEVREL="release"], [NUT_SOURCE_GITREV_DEVREL="development iteration"]) + +dnl Semantic version of most-recent NUT release this code is derived from. +dnl It may equal the NUT_SOURCE_GITREV and NUT_SOURCE_GITREV_NUMERIC, but +dnl this situation is only expected if NUT_SOURCE_GITREV_IS_RELEASE==true. +NUT_SOURCE_GITREV_SEMVER="`NUT_VERSION_QUERY=SEMVER "${srcdir}/tools/gitlog2version.sh" 2>/dev/null`" dnl Gitrev-based build identifier which can be used for e.g. PyPI uploads: -NUT_SOURCE_GITREV_NUMERIC="`echo "${NUT_SOURCE_GITREV}" | sed -e 's/^v//' -e 's/-g.*$//' -e 's/-/./g'`" +dnl # Example: NUT_SOURCE_GITREV_NUMERIC='2.8.2.695.1.696' +dnl # NUT_SOURCE_GITREV_NUMERIC="`echo "${NUT_SOURCE_GITREV}" | sed -e 's/^v//' -e 's/-g.*$//' -e 's/-/./g'`" +dnl # Without the commit-count since tag (dash-separated part): +dnl # Example: NUT_SOURCE_GITREV_NUMERIC='2.8.2.695.1' +NUT_SOURCE_GITREV_NUMERIC="`echo "${NUT_SOURCE_GITREV}" | sed -e 's/^v//' -e 's/-g.*$//' -e 's/-@<:@0-9@:>@*$//'`" dnl Note: except for experiments, do not pass this into config.h - use dnl the NUT_VERSION_MACRO from nut_version.h instead. Developers may @@ -81,7 +115,10 @@ AC_CONFIG_SRCDIR(server/upsd.c) AC_CONFIG_MACRO_DIR([m4]) AS_IF([test x"${NUT_SOURCE_GITREV}" = x], [echo "Network UPS Tools version ${PACKAGE_VERSION}"], - [echo "Network UPS Tools version ${PACKAGE_VERSION} (${NUT_SOURCE_GITREV})"]) + [AS_IF([test x"${NUT_SOURCE_GITREV}" = x"${PACKAGE_VERSION}"], + [echo "Network UPS Tools version ${PACKAGE_VERSION} ${NUT_SOURCE_GITREV_DEVREL}"], + [echo "Network UPS Tools version ${PACKAGE_VERSION} (${NUT_SOURCE_GITREV}) ${NUT_SOURCE_GITREV_DEVREL}"]) + ]) AC_CANONICAL_TARGET NUT_CHECK_OS NUT_STASH_WARNINGS @@ -198,6 +235,8 @@ dnl # not configure time. dnl AC_DEFINE_UNQUOTED(UPS_VERSION, "${PACKAGE_VERSION}", [NUT version]) dnl However, automatically define the tree version (mostly for AC_SUBST) +dnl FIXME: This just picks first 3 chars, assuming single-digit components +dnl separated by a dot: TREE_VERSION="`echo ${PACKAGE_VERSION} | awk '{ print substr($0,1,3) }'`" AC_DEFINE_UNQUOTED(TREE_VERSION, "${TREE_VERSION}", [NUT tree version]) @@ -234,12 +273,30 @@ PIDPATH="/var/run" dnl Honour new LFS recommendations if applied on the build system: AS_IF([test -d "/run"], [PIDPATH="/run"]) +AC_CHECK_PROGS([GETENT], [getent], []) +AC_CHECK_PROGS([ID], [id], []) + +PROBE_OS_USER="false" +PROBE_OS_GROUP="false" +AS_IF([test x"${GETENT}" != x], [ + PROBE_OS_USER="${GETENT} passwd " + PROBE_OS_GROUP="${GETENT} group " + ],[ + AS_IF([test x"${ID}" != x], [ + PROBE_OS_USER="${ID} -u " + PROBE_OS_GROUP="${ID} -g " + ],[ + AC_MSG_WARN([Can not check existence of user and group accounts on this system]) + ]) + ] +) + dnl Defaults for respective configure options below dnl Note these defaults may change further below depending on OS and dnl certain other configure options (e.g. "in-place replacement") RUN_AS_USER="nobody" RUN_AS_GROUP="nobody" -AS_IF([test -n "`getent group nogroup`" && ! test -n "`getent group "${RUN_AS_GROUP}"`"], +AS_IF([test -n "`${PROBE_OS_GROUP} nogroup`" && ! test -n "`${PROBE_OS_GROUP} "${RUN_AS_GROUP}"`"], [RUN_AS_GROUP="nogroup"] ) @@ -252,14 +309,37 @@ AS_CASE([${target_os}], cgiexecdir='${exec_prefix}/cgi-bin' driverexecdir='${exec_prefix}/bin' +dnl Note: htmldir per se is originally for nut-cgi resources htmldir='${prefix}/html' +htmldocdir='${docdir}/html-doc' +htmlmandir='${docdir}/html-man' pkgconfigdir='${libdir}/pkgconfig' -auglensdir='/usr/share/augeas/lenses/dist' -if test ! -d "${auglensdir}"; then - auglensdir='/usr/share/augeas/lenses' - if test ! -d "${auglensdir}"; then - auglensdir='' - fi + +dnl Detection of augeas lens dirs is a bit troublesome, since +dnl they (if present) reside in location not controlled by NUT. +dnl Try detecting based on build parameters, or fall back to +dnl hard-coded default location (may break e.g. distcheck). +dnl Note it would not work too well for "/usr/local/ups" :) +auglensdir='${datarootdir}/augeas/lenses/dist' +conftemp="${auglensdir}" +eval conftemp=\"${conftemp}\" +eval conftemp=\"${conftemp}\" + +if test ! -d "${conftemp}"; then + auglensdir='${datarootdir}/augeas/lenses' + conftemp="${auglensdir}" + eval conftemp=\"${conftemp}\" + eval conftemp=\"${conftemp}\" + + if test ! -d "${conftemp}"; then + auglensdir='/usr/share/augeas/lenses/dist' + if test ! -d "${auglensdir}"; then + auglensdir='/usr/share/augeas/lenses' + if test ! -d "${auglensdir}"; then + auglensdir='' + fi + fi + fi fi dnl ### NUT_CHECK_LIBREGEX ### Detect below as part of libusb etc. @@ -519,7 +599,7 @@ AS_IF([test x"$nut_enable_inplace_runtime" = xyes -a x"${NUT_VERSION_DEPLOYED-}" [for F in "${udevdir}/rules.d"/*-nut-*.rules ; do if test -s "$F" ; then nut_inplace_group="`grep GROUP= "$F" | head -1 | sed 's,^.* GROUP="*\(.*\)"*.*$,\1,'`" \ - && test -n "`getent group "${nut_inplace_group}"`" \ + && test -n "`${PROBE_OS_GROUP} "${nut_inplace_group}"`" \ && AC_MSG_RESULT([Got from ${F}]) \ && break \ || nut_inplace_group="" @@ -528,8 +608,8 @@ AS_IF([test x"$nut_enable_inplace_runtime" = xyes -a x"${NUT_VERSION_DEPLOYED-}" ]) AS_IF([test -z "${nut_inplace_group}"], - [AS_IF([test -n "`getent group nut`"], [nut_inplace_group="nut"], - [AS_IF([test -n "`getent group ups`"], [nut_inplace_group="ups"])])]) + [AS_IF([test -n "`${PROBE_OS_GROUP} nut`"], [nut_inplace_group="nut"], + [AS_IF([test -n "`${PROBE_OS_GROUP} ups`"], [nut_inplace_group="ups"])])]) AS_IF([test -n "${nut_inplace_group}"], [ AC_MSG_RESULT([${nut_inplace_group}]) @@ -553,7 +633,7 @@ AS_IF([test x"$nut_enable_inplace_runtime" = xyes -a x"${NUT_VERSION_DEPLOYED-}" `grep -E '^ *##* *RUN_AS_USER' "${CONFPATH}/upsmon.conf" | awk '{print $3}'`\ ; do \ AS_IF([test -z "${nut_inplace_user}"], [ - test -n "`getent passwd "${nut_inplace_user}"`" \ + test -n "`${PROBE_OS_USER} "${nut_inplace_user}"`" \ && AC_MSG_RESULT([Got from ${CONFPATH}/upsmon.conf]) \ || nut_inplace_user=""]) done @@ -566,7 +646,7 @@ AS_IF([test x"$nut_enable_inplace_runtime" = xyes -a x"${NUT_VERSION_DEPLOYED-}" `grep -E '^ *##* *RUN_AS_USER' "${CONFPATH}/upsmon.conf.sample" | awk '{print $3}'`\ ; do \ AS_IF([test -z "${nut_inplace_user}"], [ - test -n "`getent passwd "${nut_inplace_user}"`" \ + test -n "`${PROBE_OS_USER} "${nut_inplace_user}"`" \ && AC_MSG_RESULT([Got from ${CONFPATH}/upsmon.conf]) \ || nut_inplace_user=""]) done @@ -584,15 +664,15 @@ AS_IF([test x"$nut_enable_inplace_runtime" = xyes -a x"${NUT_VERSION_DEPLOYED-}" ; do AS_IF([test -z "${nut_inplace_user}" && test -s "$F"], [nut_inplace_user="`grep -E '^ *nutuser=' "$F" | sed 's,^ *nutuser=,,'`" \ - && test -n "`getent passwd "${nut_inplace_user}"`" \ + && test -n "`${PROBE_OS_USER} "${nut_inplace_user}"`" \ && AC_MSG_RESULT([Got from $F]) \ || nut_inplace_user="" ]) done AS_IF([test -z "${nut_inplace_user}"], - [AS_IF([test -n "`getent passwd nut`"], [nut_inplace_user="nut"], - [AS_IF([test -n "`getent passwd ups`"], [nut_inplace_user="ups"])])]) + [AS_IF([test -n "`${PROBE_OS_USER} nut`"], [nut_inplace_user="nut"], + [AS_IF([test -n "`${PROBE_OS_USER} ups`"], [nut_inplace_user="ups"])])]) AS_IF([test -n "${nut_inplace_user}"], [ AC_MSG_RESULT([${nut_inplace_user}]) @@ -617,6 +697,10 @@ AS_IF([test x"$nut_enable_inplace_runtime" = xyes -a x"${NUT_VERSION_DEPLOYED-}" test -n "${CONFIG_CXXFLAGS-}" || CONFIG_CXXFLAGS=" " export CONFIG_CFLAGS export CONFIG_CXXFLAGS + + AC_MSG_NOTICE([Moving config.log of the original invocation to config.log.inplace-outer just before exec...]) + mv -f config.log config.log.inplace-outer || true + eval exec "$0" $CONFIG_FLAGS_DEPLOYED $CONFIG_FLAGS --disable-inplace-runtime ],[ AC_MSG_NOTICE([No CONFIG_FLAGS were reported or discovered from existing NUT deployment (if any); restarting script for a clean run]) @@ -627,6 +711,10 @@ AS_IF([test x"$nut_enable_inplace_runtime" = xyes -a x"${NUT_VERSION_DEPLOYED-}" test -n "${CONFIG_CXXFLAGS-}" || CONFIG_CXXFLAGS=" " export CONFIG_CFLAGS export CONFIG_CXXFLAGS + + AC_MSG_NOTICE([Moving config.log of the original invocation to config.log.inplace-outer just before exec...]) + mv -f config.log config.log.inplace-outer || true + AC_MSG_NOTICE([exec "$0" $CONFIG_FLAGS --disable-inplace-runtime]) eval exec "$0" $CONFIG_FLAGS --disable-inplace-runtime ]) @@ -655,7 +743,10 @@ NUT_ARG_ENABLE([keep_nut_report_feature], AS_IF([test x"${NUT_SOURCE_GITREV}" = x], [NUT_REPORT([configured version], [${PACKAGE_VERSION}])], - [NUT_REPORT([configured version], [${PACKAGE_VERSION} (${NUT_SOURCE_GITREV})])]) + [AS_IF([test x"${NUT_SOURCE_GITREV}" = x"${PACKAGE_VERSION}"], + [NUT_REPORT([configured version], [${PACKAGE_VERSION} ${NUT_SOURCE_GITREV_DEVREL}])], + [NUT_REPORT([configured version], [${PACKAGE_VERSION} (${NUT_SOURCE_GITREV}) ${NUT_SOURCE_GITREV_DEVREL}])]) + ]) dnl Note: the compiler/pragma/attr methods below are custom for NUT codebase: NUT_COMPILER_FAMILY @@ -677,17 +768,34 @@ AX_C_PRAGMAS AX_C___ATTRIBUTE__ AX_C_PRINTF_STRING_NULL +dnl Check if the system provides a boolean type and how it is spelled +NUT_CHECK_BOOL + dnl All current systems provide time.h; it need not be checked for. dnl Not all systems provide sys/time.h, but those that do, all allow dnl you to include it and time.h simultaneously. dnl NUT codebase provides the include/timehead.h to wrap these nuances. -AC_CHECK_HEADERS_ONCE([sys/time.h time.h sys/types.h sys/socket.h netdb.h]) +AC_CHECK_HEADERS_ONCE([sys/time.h time.h sys/types.h]) dnl ###obsolete### AC_HEADER_TIME AS_IF([test "$ac_cv_header_sys_time_h" = yes], [AC_DEFINE([TIME_WITH_SYS_TIME],[1],[Define to 1 if you can safely include both and . This macro is deemed obsolete by autotools.]) ], []) +CODE_TIMEINCL=" +#ifdef TIME_WITH_SYS_TIME +# include +# include +#else +# ifdef HAVE_SYS_TIME_H +# include +# else +# include +# endif +#endif +" + +AC_CHECK_HEADERS_ONCE([fcntl.h sys/stat.h sys/socket.h netdb.h]) AC_CHECK_FUNCS(flock lockf fcvt fcvtl dup dup2 abs_val abs) AC_CHECK_HEADER([float.h], @@ -761,9 +869,30 @@ SEMLIBS="" AC_CHECK_HEADER([semaphore.h], [AC_DEFINE([HAVE_SEMAPHORE_H], [1], [Define to 1 if you have .]) - AC_MSG_CHECKING([for sem_t, sem_init() and sem_destroy()]) + AC_LANG_PUSH([C]) - AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ + myLIBS="${LIBS}" + LIBS="" + SEMLIBS_LRT="" + + dnl Solaris 8 builds complain about indirect dependency involved: + dnl sem_init nut_scanner-nut-scanner.o + dnl (symbol belongs to implicit dependency /usr/lib/librt.so.1) + AC_SEARCH_LIBS([sem_init], [pthread], [], [ + unset ac_cv_search_sem_init + AC_SEARCH_LIBS([sem_init], [pthread], [SEMLIBS_LRT=" -lrt"], [], [-lrt])]) + AC_SEARCH_LIBS([sem_open], [pthread], [], [ + unset ac_cv_search_sem_open + AC_SEARCH_LIBS([sem_open], [pthread], [SEMLIBS_LRT=" -lrt"], [], [-lrt])]) + + AS_CASE([${ac_cv_search_sem_init}], [no*], [], [SEMLIBS="${ac_cv_search_sem_init}"]) + AS_CASE([${ac_cv_search_sem_open}], [no*], [], ["${SEMLIBS}"], [], [SEMLIBS="${ac_cv_search_sem_open}"]) + SEMLIBS="${SEMLIBS}${SEMLIBS_LRT}" + + LIBS="${SEMLIBS}" + + AC_MSG_CHECKING([for sem_t, sem_init() and sem_destroy()]) + AX_RUN_OR_LINK_IFELSE([AC_LANG_PROGRAM([ #include ], [sem_t semaphore; @@ -773,18 +902,38 @@ sem_destroy(&semaphore); * normally check for non-zero meaning to look in errno */ ] )], - [AC_DEFINE([HAVE_SEMAPHORE], [1], - [Define to 1 if you have with usable sem_t sem_init() and sem_destroy().]) + [AC_DEFINE([HAVE_SEMAPHORE_UNNAMED], [1], + [Define to 1 if you have with usable sem_t, sem_init() and sem_destroy() for unnamed semaphores.]) AC_MSG_RESULT([ok]) + ], + [AC_MSG_RESULT([no])] + ) - dnl Solaris 8 builds complain about indirect dependency involved: - dnl sem_init nut_scanner-nut-scanner.o - dnl (symbol belongs to implicit dependency /usr/lib/librt.so.1) - - AC_CHECK_LIB(rt, sem_init, SEMLIBS="-lrt") + AC_MSG_CHECKING([for sem_t, sem_open() and sem_close()]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([ +#include +#ifdef HAVE_FCNTL_H +# include /* For O_* constants */ +#endif +#ifdef SYS_STAT_H +# include /* For mode constants */ +#endif +], +[sem_t *semaphore = sem_open("/s", O_CREAT, 0644, 4); +if (semaphore != SEM_FAILED) + sem_close(semaphore); +/* Do not care about actual return value in this test, + * normally check for non-zero meaning to look in errno */ +] + )], + [AC_DEFINE([HAVE_SEMAPHORE_NAMED], [1], + [Define to 1 if you have with usable sem_t, sem_open() and sem_close() for named semaphores.]) + AC_MSG_RESULT([ok]) ], [AC_MSG_RESULT([no])] ) + + LIBS="${myLIBS}" AC_LANG_POP([C]) ] ) @@ -813,29 +962,44 @@ dnl# [], [AC_MSG_WARN([Required C library routine not found; try adding __EXTEN dnl These appear with CFLAGS="-std=c99 -D_POSIX_C_SOURCE=200112L": dnl# Methods below currently do not cause build errors for C99+ modes so are dnl# not tested as thoroughly as those further below: -AC_CHECK_FUNCS(strtok_r fileno sigemptyset sigaction, - [], [AC_MSG_WARN([Required C library routine not found; try adding -D_POSIX_C_SOURCE=200112L])]) +AC_CHECK_FUNCS(strtof strtok_r fileno sigemptyset sigaction, + [], [AC_MSG_WARN([Required C library routine not found by linker; try adding -D_POSIX_C_SOURCE=200112L])]) dnl For these we have a fallback implementation via the other, dnl if at least one is available, so initial check is quiet. dnl This typically pops up in POSIX vs. Windows builds: -AC_CHECK_FUNCS(localtime_r localtime_s gmtime_r gmtime_s, - [], []) +dnl Reminder: the former checks for declarations in headers, +dnl the latter checks if known libraries suffice for linker. +dnl Might need AC_CHECK_LIBS as well to populate the list with +dnl known variants? +AC_CHECK_DECLS([localtime_r, localtime_s, gmtime_r, gmtime_s, timegm, _mkgmtime], [], [], [$CODE_TIMEINCL]) +AC_CHECK_FUNCS(localtime_r localtime_s gmtime_r gmtime_s timegm _mkgmtime, [], []) AC_MSG_CHECKING([for at least one gmtime implementation]) -AS_IF([test x"${ac_cv_func_gmtime_s}-${ac_cv_func_gmtime_r}" = "xno-no"], [ +AS_IF([test x"${ac_cv_func_gmtime_s}-${ac_cv_func_gmtime_r}" = "xno-no" && test x"${ac_cv_have_decl_gmtime_s}-${ac_cv_have_decl_gmtime_r}" = "xno-no"], [ AC_MSG_RESULT([no]) - AC_MSG_WARN([Required C library routine gmtime_s nor gmtime_r was not found; try adding -D_POSIX_C_SOURCE=200112L]) + AC_MSG_WARN([Required C library routine gmtime_s nor gmtime_r was not found by linker nor in headers; try adding -D_POSIX_C_SOURCE=200112L and/or -D_POSIX_THREAD_SAFE_FUNCTIONS=200112L]) ],[ AC_MSG_RESULT([yes]) ]) AC_MSG_CHECKING([for at least one localtime implementation]) -AS_IF([test x"${ac_cv_func_localtime_s}-${ac_cv_func_localtime_r}" = "xno-no"], [ +AS_IF([test x"${ac_cv_func_localtime_s}-${ac_cv_func_localtime_r}" = "xno-no" && test x"${ac_cv_have_decl_localtime_s}-${ac_cv_have_decl_localtime_r}" = "xno-no"], [ + AC_MSG_RESULT([no]) + AC_MSG_WARN([Required C library routine localtime_s nor localtime_r was not found by linker nor in headers; try adding -D_POSIX_C_SOURCE=200112L and/or -D_POSIX_THREAD_SAFE_FUNCTIONS=200112L]) + ],[ + AC_MSG_RESULT([yes]) + ]) + +AC_MSG_CHECKING([for at least one timegm implementation]) +AS_IF([test x"${ac_cv_func_timegm}-${ac_cv_func__mkgmtime}" = "xno-no" && test x"${ac_cv_have_decl_timegm}-${ac_cv_have_decl__mkgmtime}" = "xno-no"], [ AC_MSG_RESULT([no]) - AC_MSG_WARN([Required C library routine localtime_s nor localtime_r was not found; try adding -D_POSIX_C_SOURCE=200112L]) + AC_MSG_WARN([Required C library routine timegm nor _mkgmtime was not found by linker nor in headers]) + AC_DEFINE_UNQUOTED([WANT_TIMEGM_FALLBACK], [1], [Defined if we want to use timegm_fallback()]) + AM_CONDITIONAL([WANT_TIMEGM_FALLBACK], [true]) ],[ AC_MSG_RESULT([yes]) + AM_CONDITIONAL([WANT_TIMEGM_FALLBACK], [false]) ]) AC_LANG_PUSH([C]) @@ -936,16 +1100,7 @@ AC_CACHE_CHECK([for strptime(s1,s2,tm)], [ac_cv_func_strptime], [AX_RUN_OR_LINK_IFELSE( [AC_LANG_PROGRAM([$CODE_STRINGINCL -#ifdef TIME_WITH_SYS_TIME -# include -# include -#else -# ifdef HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif +$CODE_TIMEINCL ], [struct tm tm; char *date = "12/30/1999"; @@ -965,16 +1120,7 @@ AC_CACHE_CHECK([for clock_gettime(CLOCK_MONOTONIC,ts)], [ac_cv_func_clock_gettime], [AX_RUN_OR_LINK_IFELSE( [AC_LANG_PROGRAM([$CODE_STRINGINCL -#ifdef TIME_WITH_SYS_TIME -# include -# include -#else -# ifdef HAVE_SYS_TIME_H -# include -# else -# include -# endif -#endif +$CODE_TIMEINCL ], [struct timespec monoclock_ts; int got_monoclock = clock_gettime(CLOCK_MONOTONIC, &monoclock_ts); @@ -1036,6 +1182,12 @@ AC_CHECK_HEADER([sys/select.h], [AC_DEFINE([HAVE_SYS_SELECT_H], [1], [Define to 1 if you have .])]) +AC_CHECK_HEADER([unistd.h], + [AC_DEFINE([HAVE_UNISTD_H], [1], + [Define to 1 if you have .])]) + +AC_CHECK_FUNCS(readlink) + AC_CACHE_CHECK([for suseconds_t], [ac_cv_type_suseconds_t], [AC_COMPILE_IFELSE( @@ -1094,6 +1246,68 @@ AS_IF([test x"${ac_cv_func_usleep}" = xyes], [AC_MSG_WARN([Required C library routine usleep not found; try adding -D_POSIX_C_SOURCE=200112L])] ) +dnl OpenBSD (at least) methods to query process info, per +dnl https://github.com/openbsd/src/blob/master/bin/ps/ps.c +dnl https://kaashif.co.uk/2015/06/18/how-to-get-a-list-of-processes-on-openbsd-in-c/ +BSDKVMPROCLIBS="" +myLIBS="$LIBS" +LIBS="$LIBS -lkvm" +AC_CACHE_CHECK([for BSD KVM process info libs], + [ac_cv_lib_bsd_kvm_proc], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ +#include +#include +#include +#include +#include + ]], + [[ + char errbuf[_POSIX2_LINE_MAX]; + kvm_t *kernel = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, errbuf); + int nentries = 0; + struct kinfo_proc *kinfo = kvm_getprocs(kernel, KERN_PROC_ALL, 0, sizeof(struct kinfo_proc), &nentries); + int i; + for (i = 0; i < nentries; ++i) { + printf("%s\n", kinfo[i].p_comm); + } +/* autoconf adds ";return 0;" */ +/* we hope the code above fails if type is not defined or range is not sufficient */ +]])], + [ac_cv_lib_bsd_kvm_proc=yes + BSDKVMPROCLIBS="-lkvm" + ], [ac_cv_lib_bsd_kvm_proc=no] + )]) +LIBS="$myLIBS" + +AS_IF([test x"${ac_cv_lib_bsd_kvm_proc}" = xyes], + [AC_DEFINE([HAVE_LIB_BSD_KVM_PROC], 1, [defined if we have libs, includes and methods for BSD KVM process info])] + ) + +dnl https://github.com/illumos/illumos-gate/blob/master/usr/src/uts/common/sys/procfs.h#L318 +dnl https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/ps/ps.c +AC_CACHE_CHECK([for Solaris/illumos process info libs], + [ac_cv_lib_illumos_proc], + [AC_LINK_IFELSE( + [AC_LANG_PROGRAM([[ +#include +#include + ]], + [[ + psinfo_t info; + printf("%s", info.pr_fname) +/* autoconf adds ";return 0;" */ +/* we hope the code above fails if type is not defined or range is not sufficient */ +]])], + [ac_cv_lib_illumos_proc=yes + ], [ac_cv_lib_illumos_proc=no] + )]) +LIBS="$myLIBS" + +AS_IF([test x"${ac_cv_lib_illumos_proc}" = xyes], + [AC_DEFINE([HAVE_LIB_ILLUMOS_PROC], 1, [defined if we have libs, includes and methods for Solaris/illumos process info])] + ) + AC_LANG_POP([C]) dnl These routines' arg types differ in strict C standard mode @@ -1108,13 +1322,36 @@ AC_MSG_CHECKING([whether ln -sr works]) dnl We need to relative-symlink some files. Or hardlink. Or copy... LN_S_R="cp -pR" if test "$as_ln_s" = "ln -s" ; then - LN_S_R="ln" + _abs_srcdir="`cd "${srcdir}" && pwd`" || _abs_srcdir="" + _abs_builddir="`pwd`" + dnl AC_MSG_NOTICE([srcdir='${srcdir}' _abs_srcdir='${_abs_srcdir}' _abs_builddir='${_abs_builddir}']) + if test x"${_abs_srcdir}" = x"${_abs_builddir}" ; then + LN_S_R="ln" + else + dnl NOTE: Here we check equality of the file systems by the + dnl devices they are mounted from (first column in df output): + _fs_srcdir="`df "${_abs_srcdir}" | tail -1 | awk '{print $1}'`" || fs_srcdir="XXXs" + _fs_builddir="`df "${_abs_builddir}" | tail -1 | awk '{print $1}'`" || fs_builddir="XXXb" + dnl AC_MSG_NOTICE([_fs_srcdir='${_fs_srcdir}' _fs_builddir='${_fs_builddir}']) + if test x"${_fs_srcdir}" = x"${_fs_builddir}" ; then + LN_S_R="ln" + else + dnl Source and build areas are in different filesystems, + dnl can not hardlink - keep copying approach in place + AC_MSG_NOTICE([Source and build areas are in different filesystems, or we could not detect this for sure - avoiding hardlinks]) + fi + unset _fs_srcdir _fs_builddir + fi + unset _abs_srcdir _abs_builddir + + dnl Explore GNU ln (or compatible) with relative symlink support DIR1="$(mktemp -d "dir1.XXXXXXX")" && \ DIR2="$(mktemp -d "dir2.XXXXXXX")" && \ touch "${DIR1}/a" && \ $as_ln_s -r "${DIR1}/a" "${DIR2}/b" && \ ls -la "${DIR2}/b" | grep '\.\./' > /dev/null && \ LN_S_R="$as_ln_s -r" + rm -rf "${DIR1}" "${DIR2}" fi AC_SUBST([LN_S_R], [${LN_S_R}]) @@ -1352,8 +1589,144 @@ AS_IF([test x"${ac_cv_struct_pollfd}" = xyes], ] ) +NETLIBS_GETADDRS="" +dnl For `nut-scanner -m auto` modes, see also: +dnl https://stackoverflow.com/a/41151132/4715872 +dnl https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersaddresses (since ~Windows Vista) +dnl https://learn.microsoft.com/en-us/windows/win32/api/iphlpapi/nf-iphlpapi-getadaptersinfo (before Windows XP; not recommended later) +dnl Must check in global context, to have it not-defined where appropriate too +NUT_CHECK_HEADER_IPHLPAPI +AC_CHECK_HEADERS_ONCE([ifaddrs.h netinet/in.h net/if.h]) +AC_CHECK_FUNCS([getifaddrs], [], [ + AS_CASE([${target_os}], + [*mingw*], [ + dnl Check for GetAdaptersAddresses / GetAdaptersInfo + AS_IF([test x"${nut_cv_header_iphlpapi_h}" = xyes], [ + + myIPHLPAPI_TEST_HEADERS=' +#if HAVE_WINDOWS_H +# undef inline +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif +# include +# if HAVE_WINSOCK2_H +# include +# endif +# if HAVE_IPHLPAPI_H +# include +# endif +#endif +#include +' + + myIPHLPAPI_TEST_GAA=' +/* ULONG GetAdaptersAddresses(ULONG af, ULONG flags, void* rsvd, PIP_ADAPTER_ADDRESSES addrs, PULONG sizeptr); */ +IP_ADAPTER_ADDRESSES buf@<:@8@:>@; +ULONG bufsz = sizeof(buf); +printf("%ld ", GetAdaptersAddresses(AF_UNSPEC, GAA_FLAG_SKIP_MULTICAST, NULL, buf, &bufsz)); +printf("%ld ", GetAdaptersAddresses(AF_INET, GAA_FLAG_SKIP_DNS_SERVER, NULL, buf, &bufsz)); +printf("%ld ", GetAdaptersAddresses(AF_INET6, GAA_FLAG_SKIP_ANYCAST, NULL, buf, &bufsz)) +/* autoconf adds ";return 0;" */ +' + + AC_CACHE_CHECK([for GetAdaptersAddresses() with IPv4 and IPv6 support], + [ac_cv_func_GetAdaptersAddresses], + [AC_LANG_PUSH([C]) + dnl e.g. add "-lws2_32" for mingw builds, maybe "-liphlpapi" + dnl the NETLIBS are set by NUT_CHECK_SOCKETLIB above + SAVED_LIBS="$LIBS" + LIBS="$LIBS $NETLIBS" + AX_RUN_OR_LINK_IFELSE( + [AC_LANG_PROGRAM( + [${myIPHLPAPI_TEST_HEADERS}], + [${myIPHLPAPI_TEST_GAA}])], + [ac_cv_func_GetAdaptersAddresses=yes + ], [ + NETLIBS_GETADDRS="-liphlpapi" + LIBS="$LIBS $NETLIBS $NETLIBS_GETADDRS" + AX_RUN_OR_LINK_IFELSE( + [AC_LANG_PROGRAM( + [${myIPHLPAPI_TEST_HEADERS}], + [${myIPHLPAPI_TEST_GAA}])], + [ + ac_cv_func_GetAdaptersAddresses=yes + ], [ + ac_cv_func_GetAdaptersAddresses=no + NETLIBS_GETADDRS="" + ] + ) + ] + ) + AC_LANG_POP([C]) + LIBS="$SAVED_LIBS" + ]) + AS_IF([test x"${ac_cv_func_GetAdaptersAddresses}" = xyes], + [AC_DEFINE([HAVE_GETADAPTERSADDRESSES], 1, [defined if system has the GetAdaptersAddresses() method])], + [dnl AC_MSG_WARN([WIN32 library routine GetAdaptersAddresses() not found]) + AS_CASE([${target_os}], + [*mingw*], [AC_MSG_WARN([Windows antivirus might block this test])] + ) + ] + ) + + myIPHLPAPI_TEST_GAI=' +/* ULONG GetAdaptersInfo(PIP_ADAPTER_INFO addrs, PULONG sizeptr); */ +IP_ADAPTER_INFO buf@<:@8@:>@; +ULONG bufsz = sizeof(buf); +printf("%ld ", GetAdaptersInfo(buf, &bufsz)) +/* autoconf adds ";return 0;" */ +' + + AC_CACHE_CHECK([for GetAdaptersInfo() with IPv4 only support], + [ac_cv_func_GetAdaptersInfo], + [AC_LANG_PUSH([C]) + dnl e.g. add "-lws2_32" for mingw builds + dnl the NETLIBS are set by NUT_CHECK_SOCKETLIB above + SAVED_LIBS="$LIBS" + LIBS="$LIBS $NETLIBS $NETLIBS_GETADDRS" + AX_RUN_OR_LINK_IFELSE( + [AC_LANG_PROGRAM( + [${myIPHLPAPI_TEST_HEADERS}], + [${myIPHLPAPI_TEST_GAI}])], + [ac_cv_func_GetAdaptersInfo=yes + ], [ + NETLIBS_GETADDRS="-liphlpapi" + LIBS="$LIBS $NETLIBS $NETLIBS_GETADDRS" + AX_RUN_OR_LINK_IFELSE( + [AC_LANG_PROGRAM( + [${myIPHLPAPI_TEST_HEADERS}], + [${myIPHLPAPI_TEST_GAI}])], + [ + ac_cv_func_GetAdaptersInfo=yes + ], [ + ac_cv_func_GetAdaptersInfo=no + NETLIBS_GETADDRS="" + ] + ) + ] + ) + AC_LANG_POP([C]) + LIBS="$SAVED_LIBS" + ]) + AS_IF([test x"${ac_cv_func_GetAdaptersInfo}" = xyes], + [AC_DEFINE([HAVE_GETADAPTERSINFO], 1, [defined if system has the GetAdaptersInfo() method])], + [dnl AC_MSG_WARN([WIN32 library routine GetAdaptersInfo() not found]) + AS_CASE([${target_os}], + [*mingw*], [AC_MSG_WARN([Windows antivirus might block this test])] + ) + ] + ) + + ]) + ] + )] +) +AC_SUBST([NETLIBS_GETADDRS]) + + dnl ---------------------------------------------------------------------- -dnl Check for python binary program names per language version +dnl Check for Python binary program names per language version dnl to embed into scripts and Make rules NUT_CHECK_PYTHON_DEFAULT @@ -1699,6 +2072,59 @@ NUT_ARG_WITH([debuginfo], [no]) dnl To help find warning/error details in a wall of text, see --enable-Wcolor handled above +dnl ---------------------------------------------------------------------- +dnl Check for with-ssl, and --with-nss or --with-openssl which can be used +dnl by NUT as well as its networking-capable dependencies (net-snmp, etc.) +dnl Only one can be enabled at a time, with a preference for OpenSSL +dnl if both are available + +nut_ssl_lib="" + +NUT_ARG_WITH([ssl], [enable SSL support (either NSS or OpenSSL)], [auto]) +NUT_ARG_WITH([nss], [enable SSL support using Mozilla NSS], [auto]) +NUT_ARG_WITH([openssl], [enable SSL support using OpenSSL], [auto]) + +dnl ${nut_with_ssl}: any value except "yes" or "no" is treated as "auto". +if test "${nut_with_ssl}" != "no"; then + dnl check if either NSS or OpenSSL was explicitly requested + if test "${nut_with_nss}" = "yes"; then + NUT_CHECK_LIBNSS + if test "${nut_have_libnss}" != "yes"; then + AC_MSG_ERROR([Mozilla NSS not found (required for SSL support)]) + fi + elif test "${nut_with_openssl}" = "yes"; then + NUT_CHECK_LIBOPENSSL + if test "${nut_have_openssl}" != "yes"; then + AC_MSG_ERROR([OpenSSL not found (required for SSL support)]) + fi + else + dnl Prefer OpenSSL over NSS otherwise + NUT_CHECK_LIBOPENSSL + if test "${nut_have_openssl}" != "yes"; then + NUT_CHECK_LIBNSS + if test "${nut_have_libnss}" != "yes"; then + dnl Only abort if SSL has been explicitly requested by the user + if test "${nut_with_ssl}" = "yes"; then + AC_MSG_ERROR([Neither Mozilla NSS nor OpenSSL was found, but one is needed for the requested SSL support.]) + else + AC_MSG_WARN([Neither Mozilla NSS nor OpenSSL was found (required for SSL support)]) + fi + nut_with_ssl="no" + else + nut_with_nss="${nut_have_libnss}" + fi + else + nut_with_openssl="${nut_have_openssl}" + fi + fi +fi + +AM_CONDITIONAL(WITH_NSS, test "${nut_with_nss}" = "yes") +AM_CONDITIONAL(WITH_OPENSSL, test "${nut_with_openssl}" = "yes") + +NUT_REPORT_FEATURE([enable SSL support], [${nut_with_ssl}], [${nut_ssl_lib}], + [WITH_SSL], [Define to enable SSL]) + dnl ---------------------------------------------------------------------- dnl Check for presence and compiler flags of various libraries @@ -2033,12 +2459,11 @@ if test "${nut_with_linux_i2c}" != no; then [AC_DEFINE([HAVE_LINUX_SMBUS_H], [1], [Define to 1 if you have .])] ) + nut_have_linux_i2c="no" AC_CHECK_DECLS( [i2c_smbus_access, i2c_smbus_read_byte_data, i2c_smbus_write_byte_data, i2c_smbus_read_word_data, i2c_smbus_write_word_data, i2c_smbus_read_block_data], - [], dnl # nut_with_linux_i2c="yes" - [AS_IF([test "${nut_with_linux_i2c}" = "yes"], - [AC_MSG_ERROR(i2c was required but can not be fulfilled for this build)], - [nut_with_linux_i2c="no"])], + [nut_have_linux_i2c="yes"], + [], [#include #ifdef HAVE_LINUX_I2C_DEV_H #include @@ -2054,13 +2479,26 @@ if test "${nut_with_linux_i2c}" != no; then dnl Note: here we keep the verdict from above, or make it worse. LIBS_SAVED="$LIBS" LIBS="" - AC_SEARCH_LIBS([i2c_smbus_read_byte, i2c_smbus_access, i2c_smbus_read_byte_data, i2c_smbus_write_byte_datai2c_smbus_write_byte_data, i2c_smbus_read_word_data, i2c_smbus_write_word_data, i2c_smbus_read_block_data], - [i2c], + AS_IF([test "${nut_have_linux_i2c}" = yes], [ + nut_have_linux_i2c="no" + AC_SEARCH_LIBS(i2c_smbus_read_byte, i2c, [ + AC_SEARCH_LIBS(i2c_smbus_access, i2c, [ + AC_SEARCH_LIBS(i2c_smbus_read_byte_data, i2c, [ + AC_SEARCH_LIBS(i2c_smbus_write_byte_data, i2c, [ + AC_SEARCH_LIBS(i2c_smbus_read_word_data, i2c, [ + AC_SEARCH_LIBS(i2c_smbus_write_word_data, i2c, [ + AC_SEARCH_LIBS(i2c_smbus_read_block_data, i2c, [ + [nut_have_linux_i2c="yes"] + ])])])])])])])]) + + dnl # Note: *with* (desire) is not "no" in this big if-clause + AS_IF([test "${nut_have_linux_i2c}" = yes], [nut_with_linux_i2c="yes"], [AS_IF([test "${nut_with_linux_i2c}" = "yes"], [AC_MSG_ERROR(i2c was required but can not be fulfilled for this build)], [nut_with_linux_i2c="no"]) ]) + LIBI2C_LIBS="$LIBS" LIBS="$LIBS_SAVED" ;; @@ -2080,58 +2518,6 @@ NUT_REPORT_FEATURE( [Define to enable I2C support] ) -dnl ---------------------------------------------------------------------- -dnl Check for with-ssl, and --with-nss or --with-openssl -dnl Only one can be enabled at a time, with a preference for OpenSSL -dnl if both are available - -nut_ssl_lib="" - -NUT_ARG_WITH([ssl], [enable SSL support (either NSS or OpenSSL)], [auto]) -NUT_ARG_WITH([nss], [enable SSL support using Mozilla NSS], [auto]) -NUT_ARG_WITH([openssl], [enable SSL support using OpenSSL], [auto]) - -dnl ${nut_with_ssl}: any value except "yes" or "no" is treated as "auto". -if test "${nut_with_ssl}" != "no"; then - dnl check if either NSS or OpenSSL was explicitly requested - if test "${nut_with_nss}" = "yes"; then - NUT_CHECK_LIBNSS - if test "${nut_have_libnss}" != "yes"; then - AC_MSG_ERROR([Mozilla NSS not found (required for SSL support)]) - fi - elif test "${nut_with_openssl}" = "yes"; then - NUT_CHECK_LIBOPENSSL - if test "${nut_have_openssl}" != "yes"; then - AC_MSG_ERROR([OpenSSL not found (required for SSL support)]) - fi - else - dnl Prefer OpenSSL over NSS otherwise - NUT_CHECK_LIBOPENSSL - if test "${nut_have_openssl}" != "yes"; then - NUT_CHECK_LIBNSS - if test "${nut_have_libnss}" != "yes"; then - dnl Only abort if SSL has been explicitly requested by the user - if test "${nut_with_ssl}" = "yes"; then - AC_MSG_ERROR([Neither Mozilla NSS nor OpenSSL was found, but one is needed for the requested SSL support.]) - else - AC_MSG_WARN([Neither Mozilla NSS nor OpenSSL was found (required for SSL support)]) - fi - nut_with_ssl="no" - else - nut_with_nss="${nut_have_libnss}" - fi - else - nut_with_openssl="${nut_have_openssl}" - fi - fi -fi - -AM_CONDITIONAL(WITH_NSS, test "${nut_with_nss}" = "yes") -AM_CONDITIONAL(WITH_OPENSSL, test "${nut_with_openssl}" = "yes") - -NUT_REPORT_FEATURE([enable SSL support], [${nut_with_ssl}], [${nut_ssl_lib}], - [WITH_SSL], [Define to enable SSL]) - dnl ---------------------------------------------------------------------- dnl Check for --with-wrap @@ -2191,6 +2577,8 @@ fi NUT_REPORT_FEATURE([enable libltdl (Libtool dlopen abstraction) support], [${nut_with_libltdl}], [], [WITH_LIBLTDL], [Define to enable libltdl (Libtool dlopen abstraction) support]) +dnl Explicitly report if we are building nut-scanner or not +dnl since it requires libltdl if test x"${nut_with_libltdl}" = x"no" && test x"${nut_with_nut_scanner}" = x"yes"; then AC_MSG_ERROR([libltdl support was disabled or not found, but --with-nut-scanner was requested and requires it]) fi @@ -2228,29 +2616,37 @@ NUT_REPORT_FEATURE([build CGI programs], [${nut_with_cgi}], [], dnl ---------------------------------------------------------------------- dnl checks related to --with-pynut and --with-nut_monitor -dnl ${nut_with_nut_monitor}: TODO: arg values to request python 2 gtk2, -dnl python 3 qt5, or both -AC_MSG_CHECKING([if we can and should install NUT-Monitor desktop application]) +dnl The PYTHON*_REPORT vars also serve as flags that we have certain usable +dnl Python interpreter versions to care about below, so we only test for +dnl their existence once (in NUT_CHECK*PYTHON* m4 macros). + +dnl ${nut_with_nut_monitor}: TODO: arg values to request Python 2 gtk2, +dnl Python 3 qt5, or both +AC_MSG_CHECKING([if we want install NUT-Monitor desktop application]) +AC_MSG_RESULT([${nut_with_nut_monitor}]) nut_with_nut_monitor_py2gtk2="" nut_with_nut_monitor_py3qt5="" nut_with_nut_monitor_desktop="" dnl TODO: Add a way to define this path? will have app/ maybe module/ inside... nut_with_nut_monitor_dir="${datarootdir}/nut-monitor" + PYTHON_FAILED_TEST_DETAILS="" +dnl ### AC_MSG_NOTICE([nut_with_nut_monitor-1: ${nut_with_nut_monitor}]) if test x"${nut_with_nut_monitor}" != xno ; then dnl While we might just install for "yes" request, in hopes user would - dnl get their python ecosystem in place later, we need some criteria to + dnl get their Python ecosystem in place later, we need some criteria to dnl avoid installing it always :) Also, need to substitute the shebang. if test -z "${PYTHON}${PYTHON2}${PYTHON3}" ; then case "${nut_with_nut_monitor}" in "auto") nut_with_nut_monitor="no" - PYTHON_FAILED_TEST_DETAILS="No python 2/3 interpreter was found" + PYTHON_FAILED_TEST_DETAILS="No Python 2/3 interpreter was found" ;; - "yes") AC_MSG_ERROR([No python 2/3 interpreter was found, required for NUT-Monitor desktop application]) + "yes") AC_MSG_ERROR([No Python 2/3 interpreter was found, required for NUT-Monitor desktop application]) ;; esac fi fi +dnl ### AC_MSG_NOTICE([nut_with_nut_monitor-2: ${nut_with_nut_monitor}]) if test x"${nut_with_nut_monitor}" != xno ; then dnl Note: no double-quoting for use, the command string may be multi-token @@ -2258,53 +2654,73 @@ if test x"${nut_with_nut_monitor}" != xno ; then dnl for "config.log" details since... forever? Still, hardcoded numbers... PYTHON2_TEST_MODULES="re,glob,codecs,gtk,gtk.glade,gobject,ConfigParser" PYTHON3_TEST_MODULES="re,glob,codecs,PyQt5.uic,configparser" - if test -n "${PYTHON2}" \ - && (command -v ${PYTHON2} || which ${PYTHON2}) >/dev/null 2>/dev/null \ - ; then + if test -n "${PYTHON2_VERSION_INFO_REPORT}" ; then + AC_MSG_CHECKING([if we have Python2 prerequisites for NUT-Monitor desktop application]) if ${PYTHON2} -c "import ${PYTHON2_TEST_MODULES}" 1>&5 2>&5 \ ; then nut_with_nut_monitor_py2gtk2="yes" + AC_MSG_RESULT([yes]) else + AC_MSG_RESULT([no]) PYTHON_FAILED_TEST_DETAILS="Missing some or all of these Python2 modules: '${PYTHON2_TEST_MODULES}'" fi fi - if test -n "${PYTHON3}" \ - && (command -v ${PYTHON3} || which ${PYTHON3}) >/dev/null 2>/dev/null \ - ; then + if test -n "${PYTHON3_VERSION_INFO_REPORT}" ; then + AC_MSG_CHECKING([if we have Python3 prerequisites for NUT-Monitor desktop application]) if ${PYTHON3} -c "import ${PYTHON3_TEST_MODULES}" 1>&5 2>&5 \ ; then nut_with_nut_monitor_py3qt5="yes" + AC_MSG_RESULT([yes]) else - PYTHON_FAILED_TEST_DETAILS="Missing some or all of these Python3 modules: '${PYTHON3_TEST_MODULES}'" + AC_MSG_RESULT([no]) + if test -n "${PYTHON_FAILED_TEST_DETAILS}" ; then + PYTHON_FAILED_TEST_DETAILS="${PYTHON_FAILED_TEST_DETAILS} and some or all of these Python3 modules: '${PYTHON3_TEST_MODULES}'" + else + PYTHON_FAILED_TEST_DETAILS="Missing some or all of these Python3 modules: '${PYTHON3_TEST_MODULES}'" + fi fi fi dnl Fall back to default interpreter if test -z "${nut_with_nut_monitor_py2gtk2}${nut_with_nut_monitor_py3qt5}" \ - && test -n "${PYTHON}" \ - && (command -v ${PYTHON} || which ${PYTHON2}) >/dev/null 2>/dev/null \ + && test -n "${PYTHON_VERSION_INFO_REPORT}" \ + && test x"${PYTHON_VERSION_INFO_REPORT}" != x"${PYTHON3_VERSION_INFO_REPORT}" \ + && test x"${PYTHON_VERSION_INFO_REPORT}" != x"${PYTHON2_VERSION_INFO_REPORT}" \ ; then + AC_MSG_CHECKING([if we have Python3 prerequisites for NUT-Monitor desktop application in default Python]) if ${PYTHON} -c "import ${PYTHON3_TEST_MODULES}" 1>&5 2>&5 \ ; then nut_with_nut_monitor_py3qt5="yes" + AC_MSG_RESULT([yes]) else - PYTHON_FAILED_TEST_DETAILS="Missing some or all of these Python3 modules: '${PYTHON3_TEST_MODULES}'" + AC_MSG_RESULT([no]) + if test -n "${PYTHON_FAILED_TEST_DETAILS}" ; then + PYTHON_FAILED_TEST_DETAILS="${PYTHON_FAILED_TEST_DETAILS} and some or all of these Python3 modules in default Python: '${PYTHON3_TEST_MODULES}'" + else + PYTHON_FAILED_TEST_DETAILS="Missing some or all of these Python3 modules in default Python: '${PYTHON3_TEST_MODULES}'" + fi fi + AC_MSG_CHECKING([if we have Python2 prerequisites for NUT-Monitor desktop application in default Python]) if ${PYTHON} -c "import ${PYTHON2_TEST_MODULES}" 1>&5 2>&5 \ ; then nut_with_nut_monitor_py2gtk2="yes" PYTHON_FAILED_TEST_DETAILS="" + AC_MSG_RESULT([yes]) else + AC_MSG_RESULT([no]) if test -n "${PYTHON_FAILED_TEST_DETAILS}" ; then - PYTHON_FAILED_TEST_DETAILS="${PYTHON_FAILED_TEST_DETAILS} and some or all of these Python2 modules: '${PYTHON2_TEST_MODULES}'" + PYTHON_FAILED_TEST_DETAILS="${PYTHON_FAILED_TEST_DETAILS} and some or all of these Python2 modules in default Python: '${PYTHON2_TEST_MODULES}'" else - PYTHON_FAILED_TEST_DETAILS="Missing some or all of these Python2 modules: '${PYTHON2_TEST_MODULES}'" + PYTHON_FAILED_TEST_DETAILS="Missing some or all of these Python2 modules in default Python: '${PYTHON2_TEST_MODULES}'" fi fi fi + dnl ### AC_MSG_NOTICE([nut_with_nut_monitor-3: ${nut_with_nut_monitor}]) + dnl ### AC_MSG_NOTICE([nut_with_nut_monitor_py2gtk2: ${nut_with_nut_monitor_py2gtk2}]) + dnl ### AC_MSG_NOTICE([nut_with_nut_monitor_py3qt5: ${nut_with_nut_monitor_py3qt5}]) dnl Can we satisfy any NUT-Monitor installation request? if test -n "${nut_with_nut_monitor_py2gtk2}${nut_with_nut_monitor_py3qt5}" ; then case "${nut_with_nut_monitor}" in @@ -2314,11 +2730,14 @@ if test x"${nut_with_nut_monitor}" != xno ; then case "${nut_with_nut_monitor}" in "auto") nut_with_nut_monitor="no" ;; "yes") - AC_MSG_ERROR([No python 2/3 interpreter with needed modules was found, as required for NUT-Monitor desktop application: ${PYTHON_FAILED_TEST_DETAILS}]) + AC_MSG_ERROR([No Python 2/3 interpreter with needed modules was found, as required for NUT-Monitor desktop application: ${PYTHON_FAILED_TEST_DETAILS}]) ;; esac fi fi + +AC_MSG_CHECKING([if we can and should install NUT-Monitor desktop application]) +dnl ### AC_MSG_NOTICE([nut_with_nut_monitor-4: ${nut_with_nut_monitor}]) case "${nut_with_nut_monitor}" in "no") if test -n "${PYTHON_FAILED_TEST_DETAILS}" ; then AC_MSG_RESULT([${nut_with_nut_monitor}: ${PYTHON_FAILED_TEST_DETAILS}]) @@ -2329,6 +2748,7 @@ case "${nut_with_nut_monitor}" in *) AC_MSG_RESULT([${nut_with_nut_monitor}]) ;; esac +dnl ### AC_MSG_NOTICE([nut_with_nut_monitor-5: ${nut_with_nut_monitor}]) if test x"${nut_with_nut_monitor}" != xno ; then if (command -v desktop-file-install || which desktop-file-install) >/dev/null 2>/dev/null ; then case "${nut_with_nut_monitor}" in @@ -2343,45 +2763,99 @@ if test x"${nut_with_nut_monitor}" != xno ; then esac fi fi +dnl ### AC_MSG_NOTICE([nut_with_nut_monitor-6: ${nut_with_nut_monitor}]) -dnl ${nut_with_pynut}: TODO: arg values to request python 2, 3 or both -AC_MSG_CHECKING([if we can and should install PyNUT module]) -nut_with_pynut_py="" -nut_with_pynut_py2="" -nut_with_pynut_py3="" +dnl Check if we can use distributed or fallback telnetlib module for PyNUTClient +nut_have_telnetlib_py="" +nut_have_telnetlib_py2="" +nut_have_telnetlib_py3="" if test x"${nut_with_pynut}" != xno \ -a -n "${PYTHON}${PYTHON2}${PYTHON3}" \ ; then - if test -n "${PYTHON2}" \ - && (command -v ${PYTHON2} || which ${PYTHON2}) >/dev/null 2>/dev/null \ - ; then + if test -n "${PYTHON2_VERSION_INFO_REPORT}" ; then + AC_MSG_CHECKING([if we can use stock Python2 telnetlib module provided with interpreter ${PYTHON2}]) if ${PYTHON2} -c "import telnetlib" \ ; then - nut_with_pynut_py2="yes" + nut_have_telnetlib_py2="yes" + else + nut_have_telnetlib_py2="no" fi + AC_MSG_RESULT([${nut_have_telnetlib_py2}]) fi - if test -n "${PYTHON3}" \ - && (command -v ${PYTHON3} || which ${PYTHON3}) >/dev/null 2>/dev/null \ - ; then + if test -n "${PYTHON3_VERSION_INFO_REPORT}" ; then + AC_MSG_CHECKING([if we can use stock Python3 telnetlib module for PyNUTClient provided with interpreter ${PYTHON3} (note for warnings from Python 3.11 and beyond: we have a fallback nut_telnetlib module just in case)]) if ${PYTHON3} -c "import telnetlib" \ ; then - nut_with_pynut_py3="yes" + nut_have_telnetlib_py3="yes" + else + nut_have_telnetlib_py3="no" + fi + AC_MSG_RESULT([${nut_have_telnetlib_py3}]) + + if test x"${nut_have_telnetlib_py3}" = x"no" ; then + dnl We have a stashed copy from Python 3.10, so + dnl this line essentially checks for presence of + dnl a usable interpreter implementation compatible + dnl with Python 3.x syntax. + AC_MSG_CHECKING([if we can use fallback Python3 nut_telnetlib module for PyNUTClient]) + if (cd "${srcdir}"/scripts/python/module && ${PYTHON3} -c "import nut_telnetlib as telnetlib") \ + ; then + nut_have_telnetlib_py3="yes" + fi + AC_MSG_RESULT([${nut_have_telnetlib_py3}]) fi fi dnl Test same-ness of pythons with sys.version also? - if test -n "${PYTHON}" \ - && (command -v ${PYTHON} || which ${PYTHON}) >/dev/null 2>/dev/null \ - && test "${PYTHON}" != "${PYTHON2}" -a "${PYTHON}" != "${PYTHON3}" \ + if test -n "${PYTHON_VERSION_INFO_REPORT}" \ + && test x"${PYTHON_VERSION_INFO_REPORT}" != x"${PYTHON3_VERSION_INFO_REPORT}" \ + && test x"${PYTHON_VERSION_INFO_REPORT}" != x"${PYTHON2_VERSION_INFO_REPORT}" \ ; then + AC_MSG_CHECKING([if we can use stock Python telnetlib module for PyNUTClient provided with interpreter ${PYTHON} (note for warnings from Python 3.11 and beyond: we have a fallback nut_telnetlib module just in case)]) if ${PYTHON} -c "import telnetlib" \ ; then - nut_with_pynut_py="yes" + nut_have_telnetlib_py="yes" + else + nut_have_telnetlib_py="no" + fi + AC_MSG_RESULT([${nut_have_telnetlib_py}]) + + if test x"${nut_have_telnetlib_py}" = x"no" ; then + dnl See comments above + AC_MSG_CHECKING([if we can use fallback Python nut_telnetlib module for PyNUTClient]) + if (cd "${srcdir}"/scripts/python/module && ${PYTHON} -c "import nut_telnetlib as telnetlib") \ + ; then + nut_have_telnetlib_py="yes" + fi + AC_MSG_RESULT([${nut_have_telnetlib_py}]) fi fi fi +dnl ${nut_with_pynut}: TODO: arg values to request Python 2, 3 or both +dnl Note that per block above, nut_have_telnetlib_py* values are definitive +dnl if checked, or empty if skipped (no such Python, not nut_with_pynut, etc.) +AC_MSG_CHECKING([if we can and should install PyNUT module]) +nut_with_pynut_py="" +nut_with_pynut_py2="" +nut_with_pynut_py3="" +if test x"${nut_with_pynut}" != xno \ + -a -n "${PYTHON}${PYTHON2}${PYTHON3}" \ +; then + if test x"${nut_have_telnetlib_py2}" = x"yes" ; then + nut_with_pynut_py2="yes" + fi + + if test x"${nut_have_telnetlib_py3}" = x"yes" ; then + nut_with_pynut_py3="yes" + fi + + if test x"${nut_have_telnetlib_py}" = x"yes" ; then + nut_with_pynut_py="yes" + fi +fi + if test -z "${nut_with_pynut_py}${nut_with_pynut_py2}${nut_with_pynut_py3}" ; then dnl Not all prereqs are available... case "${nut_with_pynut}" in @@ -2420,7 +2894,7 @@ if test x"${nut_with_pynut}" != xno ; then if test "${nut_with_nut_monitor}" = yes -o "${nut_with_nut_monitor}" = force ; then nut_with_pynut="app" else - AC_MSG_ERROR([python interpreter and/or its site-packages location not found, but required for PyNUT]) + AC_MSG_ERROR([Python interpreter and/or its site-packages location not found, but required for PyNUT]) fi ;; esac @@ -2471,6 +2945,18 @@ AC_SUBST([nut_with_pynut_py], [${nut_with_pynut_py}]) AC_SUBST([nut_with_pynut_py2], [${nut_with_pynut_py2}]) AC_SUBST([nut_with_pynut_py3], [${nut_with_pynut_py3}]) +dnl MacOS Darwin has a problem with script shebangs, and tends to run anything +dnl with shell. On the upside, it has no limit on length or amount of tokens +dnl in the shebang line. +dnl https://github.com/NixOS/nixpkgs/issues/65351/ +AS_CASE([${target_os}], + [*darwin*], [ + AS_IF([test -n "${PYTHON}" -a x"${PYTHON}" != xno], [ PYTHON=" /usr/bin/env ${PYTHON}"]) + AS_IF([test -n "${PYTHON2}" -a x"${PYTHON2}" != xno], [PYTHON2=" /usr/bin/env ${PYTHON2}"]) + AS_IF([test -n "${PYTHON3}" -a x"${PYTHON3}" != xno], [PYTHON3=" /usr/bin/env ${PYTHON3}"]) + ] +) + AS_IF([test "${nut_with_nut_monitor}" != no -o "${nut_with_pynut}" != no], [ NUT_REPORT([use default Python interpreter], [${PYTHON}]) NUT_REPORT([use specific Python2 interpreter], [${PYTHON2}]) @@ -2586,6 +3072,9 @@ case "${nut_with_doc}" in skip|all=skip) nut_doc_build_list="man=skip html-single=skip html-chunked=skip pdf=skip" ;; + dist-auto) # Experimental, currently only for MANs: prefer disted files if present, auto otherwise + nut_doc_build_list="man=dist-auto html-single=dist-auto html-chunked=dist-auto pdf=dist-auto" + ;; no|all=no) nut_doc_build_list="" ;; @@ -2626,7 +3115,7 @@ for nut_doc_build_target in $nut_doc_build_list; do ;; esac case "${nut_doc_build_target_flag}" in - yes|no|auto|skip) ;; + yes|no|auto|skip|dist-auto) ;; "") nut_doc_build_target_flag="yes" ;; *) rm -rf "${DOCTESTDIR}" AC_MSG_ERROR([Invalid documentation format option: ${nut_doc_build_target}]) ;; @@ -2693,6 +3182,7 @@ dnl not fail if we have no tools to generate it (so add to SKIP list). pdf*) AC_MSG_CHECKING([if dblatex version can build ${nut_doc_build_target_base} (minimum required ${DBLATEX_MIN_VERSION})]) can_build_doc_pdf=no + can_build_doc_pdf_nonascii_titles=no AX_COMPARE_VERSION([${DBLATEX_VERSION}], [ge], [${DBLATEX_MIN_VERSION}], [ ( cd "$DOCTESTDIR" && ${A2X} --format=pdf --destination-dir=. "${abs_srcdir}"/docs/asciidoc.txt && test -s asciidoc.pdf ) && can_build_doc_pdf=yes rm -f "${DOCTESTDIR}"/asciidoc.pdf @@ -2700,6 +3190,10 @@ dnl not fail if we have no tools to generate it (so add to SKIP list). if test "${can_build_doc_pdf}" = yes ; then AC_MSG_RESULT(yes) DOC_BUILD_LIST="${DOC_BUILD_LIST} ${nut_doc_build_target_base}" + AC_MSG_CHECKING([if dblatex can process non-ASCII section titles for PDF]) + ( cd "$DOCTESTDIR" && sed -e 's/^Intro/'"`printf '\303\215'`"'ntro/' -e 's/Works in Progress/Works '"`printf '\303\255'`"'n Progress/' < "${abs_srcdir}"/docs/asciidoc.txt > asciidoc.tmp.txt && ${A2X} --format=pdf --destination-dir=. asciidoc.tmp.txt && test -s asciidoc.tmp.pdf ) && can_build_doc_pdf_nonascii_titles=yes + rm -f "${DOCTESTDIR}"/asciidoc.tmp.pdf + AC_MSG_RESULT(${can_build_doc_pdf_nonascii_titles}) else AC_MSG_RESULT(no) if test "${nut_doc_build_target_flag}" = "yes" ; then @@ -2712,31 +3206,48 @@ dnl not fail if we have no tools to generate it (so add to SKIP list). ;; man*) + dnl Experimental support for --with-doc=man=dist-auto to prefer pre-disted docs if available, below AC_MSG_CHECKING([if we can build ${nut_doc_build_target_base}]) can_build_doc_man=no + have_disted_doc_man=no + want_disted_doc_man=no + if test -s "${abs_srcdir}"/docs/man/snmp-ups.8 ; then + dnl Test that groff files exist (building from distributed tarball, not git repo) + have_disted_doc_man=yes + fi + if test x"${nut_doc_build_target}" = x"man=dist-auto" || test "${nut_doc_build_target_flag}" = "dist-auto"; then + want_disted_doc_man=yes + fi if test "${nut_have_asciidoc}" = yes ; then ( cd "$DOCTESTDIR" && ${A2X} --format manpage --destination-dir=. --xsltproc-opts="--nonet" "${abs_srcdir}"/docs/man/snmp-ups.txt && test -s snmp-ups.8 ) && can_build_doc_man=yes rm -f "${DOCTESTDIR}"/snmp-ups.8 fi - if test "${can_build_doc_man}" = yes ; then - AC_MSG_RESULT(yes) - DOC_BUILD_LIST="${DOC_BUILD_LIST} ${nut_doc_build_target_base}" + if test "${want_disted_doc_man}" = yes && test "${have_disted_doc_man}" = yes ; then + AC_MSG_NOTICE([Requested, and can, install pre-built distributed copies of ${nut_doc_build_target_base} documentation]) + DOC_SKIPBUILD_LIST="${DOC_SKIPBUILD_LIST} ${nut_doc_build_target_base}" + DOC_INSTALL_DISTED_MANS="yes" + dnl Avoid rebuilding existing build products due to their timestamp dependencies: + touch -r "${abs_srcdir}"/docs/man/Makefile.am "${abs_srcdir}"/docs/man/*.{1,2,3,4,5,6,7,8,9}* "${abs_srcdir}"/docs/man/*.{txt,xml,html,pdf} || true else - AC_MSG_RESULT(no) - if test "${nut_doc_build_target_flag}" = "yes" ; then - DOC_CANNOTBUILD_LIST="${DOC_CANNOTBUILD_LIST} ${nut_doc_build_target_base}" - AC_MSG_WARN([Unable to build ${nut_doc_build_target_base} documentation which you requested]) + if test "${can_build_doc_man}" = yes ; then + AC_MSG_RESULT(yes) + DOC_BUILD_LIST="${DOC_BUILD_LIST} ${nut_doc_build_target_base}" else - DOC_SKIPBUILD_LIST="${DOC_SKIPBUILD_LIST} ${nut_doc_build_target_base}" - if test "${nut_doc_build_target_flag}" = "auto" ; then -dnl Test that groff files exist (building from distributed tarball, not git repo) - if test -s "${abs_srcdir}"/docs/man/snmp-ups.8 ; then - AC_MSG_WARN([Unable to build ${nut_doc_build_target_base} documentation, but can install pre-built distributed copies]) - DOC_INSTALL_DISTED_MANS="yes" - else - AC_MSG_WARN([Unable to build ${nut_doc_build_target_base} documentation, and unable to install pre-built distributed copies because they are absent]) - fi - fi # Other variants include "no", "skip"... + AC_MSG_RESULT(no) + if test "${nut_doc_build_target_flag}" = "yes" ; then + DOC_CANNOTBUILD_LIST="${DOC_CANNOTBUILD_LIST} ${nut_doc_build_target_base}" + AC_MSG_WARN([Unable to build ${nut_doc_build_target_base} documentation which you requested]) + else + DOC_SKIPBUILD_LIST="${DOC_SKIPBUILD_LIST} ${nut_doc_build_target_base}" + if test "${nut_doc_build_target_flag}" = "auto" || test "${nut_doc_build_target_flag}" = "dist-auto" ; then + if test "${have_disted_doc_man}" = yes ; then + AC_MSG_WARN([Unable to build ${nut_doc_build_target_base} documentation, but can install pre-built distributed copies]) + DOC_INSTALL_DISTED_MANS="yes" + else + AC_MSG_WARN([Unable to build ${nut_doc_build_target_base} documentation, and unable to install pre-built distributed copies because they are absent]) + fi + fi # Other variants include "no", "skip"... + fi fi fi ;; @@ -2775,6 +3286,8 @@ no) ;; esac +AM_CONDITIONAL(WITH_PDF_NONASCII_TITLES, [test x"$can_build_doc_pdf_nonascii_titles" = xyes]) + NUT_REPORT_FEATURE([build specific documentation format(s)], [${nut_with_doc}], [${DOC_BUILD_LIST}], [WITH_DOCS], [Define to enable overall documentation generation]) @@ -2782,9 +3295,9 @@ NUT_REPORT_FEATURE([build specific documentation format(s)], [${nut_with_doc}], # for "make check" in "docs/" here... DOC_CHECK_LIST="" if test "${nut_with_doc}" = yes ; then - for V in $DOC_BUILD_LIST ; do - DOC_CHECK_LIST="$DOC_CHECK_LIST check-$V" - done + for V in $DOC_BUILD_LIST ; do + DOC_CHECK_LIST="$DOC_CHECK_LIST check-$V" + done fi WITH_MANS=no @@ -2804,6 +3317,39 @@ AM_CONDITIONAL(WITH_MANS, test "${WITH_MANS}" = "yes") AM_CONDITIONAL(SKIP_MANS, test "${SKIP_MANS}" = "yes") AM_CONDITIONAL(DOC_INSTALL_DISTED_MANS, test "${DOC_INSTALL_DISTED_MANS}" = "yes") +WITH_HTML_SINGLE=no +SKIP_HTML_SINGLE=no +if echo "${DOC_BUILD_LIST}" | grep -w "html-single" >/dev/null ; then + WITH_HTML_SINGLE=yes +fi +if echo "${DOC_SKIPBUILD_LIST}" | grep -w "html-single" >/dev/null ; then + SKIP_HTML_SINGLE=yes +fi +AM_CONDITIONAL(WITH_HTML_SINGLE, test "${WITH_HTML_SINGLE}" = "yes") +AM_CONDITIONAL(SKIP_HTML_SINGLE, test "${SKIP_HTML_SINGLE}" = "yes") + +WITH_HTML_CHUNKED=no +SKIP_HTML_CHUNKED=no +if echo "${DOC_BUILD_LIST}" | grep -w "html-chunked" >/dev/null ; then + WITH_HTML_CHUNKED=yes +fi +if echo "${DOC_SKIPBUILD_LIST}" | grep -w "html-chunked" >/dev/null ; then + SKIP_HTML_CHUNKED=yes +fi +AM_CONDITIONAL(WITH_HTML_CHUNKED, test "${WITH_HTML_CHUNKED}" = "yes") +AM_CONDITIONAL(SKIP_HTML_CHUNKED, test "${SKIP_HTML_CHUNKED}" = "yes") + +WITH_PDFS=no +SKIP_PDFS=no +if echo "${DOC_BUILD_LIST}" | grep -w "pdf" >/dev/null ; then + WITH_PDFS=yes +fi +if echo "${DOC_SKIPBUILD_LIST}" | grep -w "pdf" >/dev/null ; then + SKIP_PDFS=yes +fi +AM_CONDITIONAL(WITH_PDFS, test "${WITH_PDFS}" = "yes") +AM_CONDITIONAL(SKIP_PDFS, test "${SKIP_PDFS}" = "yes") + dnl ---------------------------------------------------------------------- dnl checks related to --with-dev @@ -3276,8 +3822,8 @@ if test x"$solarissmf" = xauto ; then fi fi AC_MSG_RESULT([${solarissmf}]) -AM_CONDITIONAL(WITH_SOLARIS_SMF, test x"$solarissmf" = x"yes") -NUT_REPORT([consider basic SMF support], [${solarissmf}]) +NUT_REPORT_FEATURE([consider basic SMF support], [${solarissmf}], [], + [WITH_SOLARIS_SMF], [Define to consider basic SMF support (provide units and configuration files)]) AC_MSG_CHECKING(whether to install Solaris SVR4 (legacy) init-script files) @@ -3547,6 +4093,10 @@ AS_IF([test x"${with_libsystemd}" = xyes && test x"${SYSTEMD_SUPPORTS_DAEMON_TYP NUT_REPORT_FEATURE([build with tighter systemd support], [${with_libsystemd}], [], [WITH_LIBSYSTEMD], [Define to build with tighter systemd support (sd_notify etc)]) +nut_with_libsystemd_inhibitor=0 +AS_IF([test x"${with_libsystemd}" = xyes && test x"${nut_have_libsystemd_inhibitor}" = xyes], [nut_with_libsystemd_inhibitor=1]) +AC_DEFINE_UNQUOTED(WITH_LIBSYSTEMD_INHIBITOR, [${nut_with_libsystemd_inhibitor}], [Define as 1 if we can use systemd inhibitor interface here]) +AM_CONDITIONAL([WITH_LIBSYSTEMD_INHIBITOR], [test x"${nut_with_libsystemd_inhibitor}" = x1]) dnl dnl Tests for CppUnit availability and usability (will be built if we can, @@ -3769,6 +4319,12 @@ else fi AM_CONDITIONAL(WITH_AUGLENS, test -n "${auglensdir}") +if test -n "${auglensdir}"; then + auglenstestsdir="${auglensdir}/tests" +else + auglenstestsdir='' +fi + AC_PATH_PROGS([AUGPARSE], [augparse], [none]) AM_CONDITIONAL([HAVE_AUGPARSE], [test "x${AUGPARSE}" != "xnone"]) AC_MSG_CHECKING([whether to enable Augeas configuration-management lenses tests]) @@ -3778,6 +4334,7 @@ else AC_MSG_RESULT(no) fi +dnl ---------------------------------------------------------------------- AC_MSG_CHECKING(whether to install hotplug rules) AC_ARG_WITH(hotplug-dir, @@ -4202,6 +4759,8 @@ AC_SUBST(TREE_VERSION) AC_SUBST(NUT_NETVERSION) AC_SUBST(FORCE_NUT_VERSION) AC_SUBST(NUT_SOURCE_GITREV) +AC_SUBST(NUT_SOURCE_GITREV_IS_RELEASE) +AC_SUBST(NUT_SOURCE_GITREV_SEMVER) AC_SUBST(NUT_SOURCE_GITREV_NUMERIC) AC_SUBST(LIBSSL_CFLAGS) AC_SUBST(LIBSSL_LIBS) @@ -4251,6 +4810,7 @@ AC_SUBST(DRIVER_BUILD_LIST) AC_SUBST(DRIVER_MAN_LIST) AC_SUBST(DRIVER_MAN_LIST_PAGES) AC_SUBST(DRIVER_INSTALL_TARGET) +AC_SUBST(BSDKVMPROCLIBS) AC_SUBST(NETLIBS) AC_SUBST(SERLIBS) AC_SUBST(SEMLIBS) @@ -4278,11 +4838,14 @@ AC_SUBST(devddir) AC_SUBST(driverexecdir) AC_SUBST(freebsdquirksdir) AC_SUBST(htmldir) +AC_SUBST(htmldocdir) +AC_SUBST(htmlmandir) AC_SUBST(pkgconfigdir) AC_SUBST(systemdsystemunitdir) AC_SUBST(systemdshutdowndir) AC_SUBST(systemdtmpfilesdir) AC_SUBST(auglensdir) +AC_SUBST(auglenstestsdir) AC_SUBST(hotplugdir) AC_SUBST(udevdir) @@ -4641,19 +5204,23 @@ dnl When building ON Windows (mingw/MSYS2, cygwin, etc.) fudge these dnl path strings back to what native OS methods would recognize. AS_CASE([${target_os}], [*mingw*], [ + AC_MSG_NOTICE([Will try to resolve Windows paths to ABS_TOP_SRCDIR and ABS_TOP_BUILDDIR with cygpath or mingw/msys pwd -W tool (would fail if not a native build)]) + dnl Cygwin path resolver AC_CHECK_TOOL([CYGPATH], [cygpath], [none]) - AS_IF([test "x${CYGPATH}" != "xnone"], [ + AS_IF([test "x${CYGPATH}" != "xnone" && test x"`${CYGPATH}`" != x], [ tmp="`${CYGPATH} -m "${ABS_TOP_BUILDDIR}" | sed -e 's,/,\\\\\\\\,g'`" && test -n "$tmp" && test -d "$tmp" && ABS_TOP_BUILDDIR="$tmp" tmp="`${CYGPATH} -m "${ABS_TOP_SRCDIR}" | sed 's,/,\\\\\\\\,g'`" && test -n "$tmp" && test -d "$tmp" && ABS_TOP_SRCDIR="$tmp" ],[ dnl MSYS pwd with -W option to resolve - AC_CHECK_TOOL([PWD], [pwd], [none]) - AS_IF([test "x${PWD}" != "xnone"], [ - tmp="`(cd "${ABS_TOP_BUILDDIR}" && ${PWD} -W) | sed 's,/,\\\\\\\\,g'`" && test -n "$tmp" && test -d "$tmp" && ABS_TOP_BUILDDIR="$tmp" - tmp="`(cd "${ABS_TOP_SRCDIR}" && ${PWD} -W) | sed 's,/,\\\\\\\\,g'`" && test -n "$tmp" && test -d "$tmp" && ABS_TOP_SRCDIR="$tmp" + AC_CHECK_TOOL([PWDTOOL], [pwd], [none]) + AS_IF([test "x${PWDTOOL}" != "xnone" && test x"`${PWDTOOL} -W`" != x], [ + tmp="`(cd "${ABS_TOP_BUILDDIR}" && ${PWDTOOL} -W) | sed 's,/,\\\\\\\\,g'`" && test -n "$tmp" && test -d "$tmp" && ABS_TOP_BUILDDIR="$tmp" + tmp="`(cd "${ABS_TOP_SRCDIR}" && ${PWDTOOL} -W) | sed 's,/,\\\\\\\\,g'`" && test -n "$tmp" && test -d "$tmp" && ABS_TOP_SRCDIR="$tmp" ]) ]) + + AC_MSG_NOTICE([FWIW, assuming ABS_TOP_SRCDIR="$ABS_TOP_SRCDIR" and ABS_TOP_BUILDDIR="$ABS_TOP_BUILDDIR"]) ]) dnl Use these at best for tests (e.g. nutconf), not production code: @@ -4755,6 +5322,19 @@ AC_ARG_VAR(CCACHE_BASEDIR) AC_ARG_VAR(CCACHE_DIR) AC_ARG_VAR(CCACHE_PATH) +dnl Some versions of ccache take poorly to an exported empty CCACHE_DIR etc. +dnl Avoid exporting them if not set at the configure time (assuming ci_build.sh +dnl integration or user's shell profile sets them persistently) +AS_IF([test x"${CCACHE_NAMESPACE-}" = x], [NUT_AM_EXPORT_CCACHE_NAMESPACE="#"], [NUT_AM_EXPORT_CCACHE_NAMESPACE=""]) +AC_SUBST(NUT_AM_EXPORT_CCACHE_NAMESPACE) +AS_IF([test x"${CCACHE_BASEDIR-}" = x], [NUT_AM_EXPORT_CCACHE_BASEDIR="#"], [NUT_AM_EXPORT_CCACHE_BASEDIR=""]) +AC_SUBST(NUT_AM_EXPORT_CCACHE_BASEDIR) +AS_IF([test x"${CCACHE_DIR-}" = x], [NUT_AM_EXPORT_CCACHE_DIR="#"], [NUT_AM_EXPORT_CCACHE_DIR=""]) +AC_SUBST(NUT_AM_EXPORT_CCACHE_DIR) + +dnl Application of PATH_DURING_CONFIGURE is also fenced by NUT_AM_EXPORT_CCACHE_PATH: +AS_IF([test x"${CCACHE_PATH-}" = x], [NUT_AM_EXPORT_CCACHE_PATH="#"], [NUT_AM_EXPORT_CCACHE_PATH=""]) +AC_SUBST(NUT_AM_EXPORT_CCACHE_PATH) PATH_DURING_CONFIGURE="$PATH" AC_SUBST(PATH_DURING_CONFIGURE) @@ -4789,7 +5369,16 @@ AS_CASE(["${CONFIG_CXXFLAGS}"], AS_CASE(["${nut_with_debuginfo_C}"], [yes], [ AS_IF([test x"${CLANGCC}" = x"yes" -o x"${GCC}" = x"yes"], [ - CFLAGS="${CFLAGS} -O0 -g3 -gdwarf-2" + dnl Where we can enable debug, minimize the optimizations. + dnl On some platforms LIBNETSNMP_CFLAGS or some such defines + dnl _FORTIFY_SOURCE=N which in turn "requires compiling with + dnl optimization", so we can not disable it in CFLAGS applied + dnl in the end of each and every build command line (highest + dnl priority). However, CPPFLAGS are before (making these + dnl lines an implementation-dependent hack)... + dnl AS_IF([set | grep -E 'FORTIFY_SOURCE'], [], [CFLAGS="${CFLAGS} -O0"]) + CPPFLAGS="${CPPFLAGS} -O0" + CFLAGS="${CFLAGS} -g3 -gdwarf-2" ],[nut_with_debuginfo_C="Unknown C compiler, not adding options"] )], dnl # [no]: By default we do not add debug info @@ -4802,7 +5391,9 @@ AS_CASE(["${nut_with_debuginfo_C}"], AS_CASE(["${nut_with_debuginfo_CXX}"], [yes], [ AS_IF([test "x$CLANGXX" = xyes -o "x$GXX" = xyes], [ - dnl Where we can enable debug, minimize the optimizations + dnl Where we can enable debug, minimize the optimizations. + dnl NOTE: Concerns for -O0 behavior commented above are about C code, + dnl not seen (yet) with C++. CXXFLAGS="${CXXFLAGS} -O0 -g3 -gdwarf-2" dnl Use same settings for CPPUNIT tests (they bump their own by default) CPPUNIT_NUT_CXXFLAGS="" diff --git a/data/Makefile.am b/data/Makefile.am index 5fba2e442b..29c440bac2 100644 --- a/data/Makefile.am +++ b/data/Makefile.am @@ -9,7 +9,7 @@ EXTRA_DIST = evolution500.seq epdu-managed.dev # NOTE: Due to portability, we do not use a GNU percent-wildcard extension. # We also have to export some variables that may be tainted by relative # paths when parsing the other makefile (e.g. MKDIR_P that may be defined -# via expanded $(top_builddir)/install_sh): +# via expanded $(top_builddir)/install-sh): #%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) # +$(MAKE) -s -f $(top_builddir)/docs/Makefile $(AM_MAKEFLAGS) MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ diff --git a/data/cmdvartab b/data/cmdvartab index 9a1d9ab3a9..38d2b356dd 100644 --- a/data/cmdvartab +++ b/data/cmdvartab @@ -194,6 +194,7 @@ VARDESC driver.version.usb "USB library version" # VARDESC driver.parameter.[[:alpha:]]+ "Driver parameter: " # VARDESC driver.flag.[[:alpha:]]+ "Driver flag: " +CMDDESC driver.exit "Tell the driver daemon to just exit its program (so the caller or service management framework can restart it with new options)" CMDDESC driver.killpower "Tell the driver daemon to initiate UPS shutdown; should be unlocked with driver.flag.allow_killpower option or variable setting" CMDDESC driver.reload "Reload running driver configuration from the file system (only works for changes in some options)" CMDDESC driver.reload-or-error "Reload running driver configuration from the file system (only works for changes in some options); return an error if something changed and could not be applied live (so the caller can restart it with new options)" diff --git a/data/driver.list.in b/data/driver.list.in index 32b795cb2e..7ed01d0987 100644 --- a/data/driver.list.in +++ b/data/driver.list.in @@ -34,6 +34,8 @@ # # Duplicate text in the last field will be cooked out during the conversion # to HTML with ROWSPAN magic. They must be an exact match for this to work. +# +# The entry may be followed by comments. Be sure to not use double-quotes there! "Ablerex" "ups" "2" "625L" "USB" "blazer_usb" "Ablerex" "ups" "2" "Hope Office 400/600" "" "blazer_ser" @@ -77,6 +79,7 @@ "APC" "ups" "3" "Back-UPS BF500" "USB" "usbhid-ups" "APC" "ups" "3" "BACK-UPS XS LCD" "USB" "usbhid-ups" "APC" "ups" "3" "Back-UPS XS 1000M (Back-UPS Pro 1000, Model BX1000M)" "USB" "usbhid-ups" # https://github.com/networkupstools/nut/issues/139 +"APC" "ups" "3" "Back-UPS BX****MI Series (may need tweaks since 2023)" "USB" "usbhid-ups lbrb_log_delay_sec=N lbrb_log_delay_without_calibrating onlinedischarge_calibration" # https://github.com/networkupstools/nut/issues/2347 "APC" "ups" "3" "SMC2200BI-BR" "USB" "usbhid-ups" # https://github.com/networkupstools/nut/issues/557 "APC" "ups" "3" "Smart-UPS (USB)" "USB" "usbhid-ups" "APC" "ups" "3" "Smart-UPS 750 (SMT750I, USB)" "USB" "usbhid-ups" @@ -194,6 +197,11 @@ "Best Power" "ups" "1" "Micro-Ferrups" "" "bestuferrups" "Best Power" "ups" "1" "Fortress/Ferrups" "f-command support" "bestfcom" +"Bicker" "ups" "3" "UPSIC-1205" "" "bicker_ser" +"Bicker" "ups" "3" "UPSIC-2403" "" "bicker_ser" +"Bicker" "ups" "3" "DC2412-UPS" "" "bicker_ser" +"Bicker" "ups" "3" "DC2412-UPS-LD" "" "bicker_ser" + "Borri" "ups" "2" "B400-010-B/B400-020-B/B400-030-B/B400-010-C/B400-020-C/B400-030-C" "USB" "blazer_usb" "Borri" "ups" "2" "B400-R010-B/B400-R020-B/B400-R030-B/B400-R010-C/B400-R020-C/B400-R030-C" "USB" "blazer_usb" "Borri" "ups" "2" "B500-060-B/B500-100-B/B500-060-C/B500-100-C" "USB" "blazer_usb" @@ -221,7 +229,7 @@ "Crown" "ups" "2" "CMU-SP1200IEC" "USB" "nutdrv_qx port=auto vendorid=0001 productid=0000 protocol=hunnox langid_fix=0x0409 novendor noscanlangid" # https://github.com/networkupstools/nut/pull/638 caveats at https://github.com/networkupstools/nut/issues/1014 -"Cyber Energy" "ups" "3" "Models with USB ID 0483:A430" "USB" "usbhid-ups" # https://alioth-lists.debian.net/pipermail/nut-upsdev/2024-February/007966.html +"Cyber Energy" "ups" "3" "Models with USB" "USB" "usbhid-ups" # https://alioth-lists.debian.net/pipermail/nut-upsdev/2024-February/007966.html https://alioth-lists.debian.net/pipermail/nut-upsdev/2024-June/008002.html "Cyber Power Systems" "ups" "1" "550SL" "" "genericups upstype=7" "Cyber Power Systems" "ups" "1" "725SL" "" "genericups upstype=7" @@ -246,7 +254,8 @@ "Cyber Power Systems" "ups" "3" "CP1350AVRLCD" "USB" "usbhid-ups" "Cyber Power Systems" "ups" "3" "CP1500AVRLCD" "USB" "usbhid-ups" "Cyber Power Systems" "ups" "3" "CP850PFCLCD" "USB" "usbhid-ups" # https://github.com/networkupstools/nut/issues/605 -"Cyber Power Systems" "ups" "3" "CP1350PFCLCD" "USB" "usbhid-ups" # https://alioth-lists.debian.net/pipermail/nut-upsuser/2023-October/013441.html +"Cyber Power Systems" "ups" "3" "CP1350PFCLCD" "USB" "usbhid-ups" # https://alioth-lists.debian.net/pipermail/nut-upsuser/2023-October/013441.html +"Cyber Power Systems" "ups" "3" "CP1350EPFCLCD" "USB" "usbhid-ups" "Cyber Power Systems" "ups" "3" "CP1500PFCLCD" "USB" "usbhid-ups" # https://www.cyberpowersystems.com/product/ups/cp1500pfclcd/ https://github.com/networkupstools/nut/issues/520 "Cyber Power Systems" "ups" "3" "CPJ500" "USB" "usbhid-ups" # https://www.cyberpower.com/jp/ja/product/sku/cpj500#downloads https://github.com/networkupstools/nut/issues/1403 "Cyber Power Systems" "ups" "3" "CP900AVR" "USB" "usbhid-ups" @@ -354,6 +363,7 @@ "Eaton" "ups" "5" "5S" "USB port" "usbhid-ups" "Eaton" "ups" "5" "5SC" "USB port" "usbhid-ups" "Eaton" "ups" "5" "5P" "USB port" "usbhid-ups" +"Eaton" "ups" "5" "9E" "USB port" "usbhid-ups" "Eaton" "ups" "5" "9SX" "USB port" "usbhid-ups" "Eaton" "ups" "5" "9PX" "USB port" "usbhid-ups" "Eaton" "ups" "5" "9PX Split Phase 6/8/10 kVA" "USB port" "usbhid-ups" @@ -609,7 +619,7 @@ "Legrand" "ups" "2" "Keor Line RT" "Serial" "nutdrv_qx" "Legrand" "ups" "2" "Keor Line RT" "USB" "nutdrv_qx" "Legrand" "ups" "2" "Keor LP" "Serial" "nutdrv_qx" -"Legrand" "ups" "2" "Keor Multiplug" "USB" "nutdrv_qx" # NOTE: As of 2023 reports, the "NEW Keor Multiplug" is not supported (also by older vendor SW) +"Legrand" "ups" "2" "Keor Multiplug" "USB" "nutdrv_qx" # NOTE: As of 2023 reports, the 'NEW Keor Multiplug' is not supported (also by older vendor SW) "Legrand" "ups" "2" "Keor S" "Serial" "nutdrv_qx" "Legrand" "ups" "2" "Keor S" "USB" "nutdrv_qx" "Legrand" "ups" "3" "Keor PDU" "USB" "usbhid-ups" @@ -1111,8 +1121,8 @@ "Riello" "ups" "3" "(various)" "Netman Plus 101 SNMP Box" "snmp-ups" "Riello" "ups" "3" "(various)" "Netman Plus 102 SNMP Card" "snmp-ups" "Riello" "ups" "3" "(various)" "Netman Plus 202 SNMP Card" "snmp-ups" -"Riello" "ups" "3" "(various)" "Netman 204 SNMP Card" "snmp-ups" # Note: in https://github.com/networkupstools/nut/issues/1878 submitted not as "Netman Plus", clarification requested -"Riello" "ups" "3" "(various)" "Netman 208 SNMP Card" "snmp-ups" # Note: in https://github.com/networkupstools/nut/issues/1878 submitted not as "Netman Plus", clarification requested +"Riello" "ups" "3" "(various)" "Netman 204 SNMP Card" "snmp-ups" # Note: in https://github.com/networkupstools/nut/issues/1878 submitted not as 'Netman Plus', clarification requested +"Riello" "ups" "3" "(various)" "Netman 208 SNMP Card" "snmp-ups" # Note: in https://github.com/networkupstools/nut/issues/1878 submitted not as 'Netman Plus', clarification requested "Rocketfish" "ups" "3" "RF-1000VA / RF-1025VA" "USB" "usbhid-ups" @@ -1201,6 +1211,8 @@ "Tecnoware" "ups" "2" "UPS ERA LCD 0.65" "USB" "blazer_usb langid_fix=0x409" "Tecnoware" "ups" "2" "UPS ERA PLUS 1100" "USB" "blazer_usb" # https://www.tecnoware.com/Prodotti/FGCERAPL1100/ups-era-plus-1100.aspx https://github.com/networkupstools/nut/issues/747 +"Texas Instruments" "ups" "5" "INA219" "hwmon" "hwmon_ina219" + "Tripp Lite" "ups" "1" "(various)" "Lan 2.2 interface - black 73-0844 cable" "genericups upstype=5" "Tripp Lite" "ups" "3" "1500 LCD" "USB" "usbhid-ups" "Tripp Lite" "ups" "3" "AVR550U" "USB (protocol 2010)" "usbhid-ups" # http://www.tripplite.com/en/products/model.cfm?txtSeriesID=930&txtModelID=3090 @@ -1382,6 +1394,8 @@ "Viewsonic" "ups" "1" "PowerES" "420E" "optiups" +"Visench" "ups" "1" "C1K" "(/dev/ttyCH341USB0 with USB ID 1a86:7523)" "nutdrv_qx" # https://github.com/networkupstools/nut/issues/2395 : Visench C1K; mfr/model are strings of `2`'s; USB ID identifies as QinHeng Electronics CH340 serial converter chip + "Vivaldi" "ups" "1" "EA200 LED" "USB" "richcomm_usb" "Voltronic Power" "ups" "2" "Apex 1KVA" "Serial" "nutdrv_qx" diff --git a/data/html/Makefile.am b/data/html/Makefile.am index 19436e12da..1327c724b9 100644 --- a/data/html/Makefile.am +++ b/data/html/Makefile.am @@ -12,7 +12,7 @@ SPELLCHECK_SRC = README.adoc # NOTE: Due to portability, we do not use a GNU percent-wildcard extension. # We also have to export some variables that may be tainted by relative # paths when parsing the other makefile (e.g. MKDIR_P that may be defined -# via expanded $(top_builddir)/install_sh): +# via expanded $(top_builddir)/install-sh): #%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) # +$(MAKE) -s -f $(top_builddir)/docs/Makefile $(AM_MAKEFLAGS) MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ diff --git a/docs/FAQ.txt b/docs/FAQ.txt index a04d1d29f2..806bb41e46 100644 --- a/docs/FAQ.txt +++ b/docs/FAQ.txt @@ -281,6 +281,13 @@ with 'upsdrvsvcctl resync' and then manage those with commands like 'upsdrvsvcctl stop' and 'upsdrvsvcctl start' (note that on other systems this tool may be not pre-installed via packaging). +In fact, service instances prepared by the `nut-driver-enumerator` script +and service based on contents of your `ups.conf` file and automatically +maintained by the respective framework can conflict with manual execution +of drivers, so `upsdrvctl` would emit a warning in NUT builds with that +capability (can be silenced by exporting a `NUT_QUIET_INIT_NDE_WARNING` +environment variable with any value). + *Answer 2* Some USB UPS devices have unreliable USB to serial interfaces. @@ -314,7 +321,8 @@ remembers the 'last state' when the UPS restarts. One solution is to change your shutdown scripts so you never reach that point. You *want* the system to die without reaching the part where the kernel tells it to shut down. A possible script -might look like this: +might look like this (see `scripts/systemd/nutshutdown` in NUT +sources for a more streamlined and modern variant): ------ # other shutdown stuff here (mount -o remount,ro ...) diff --git a/docs/Makefile.am b/docs/Makefile.am index 33f0ff5d77..d8b18a382b 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -1,9 +1,37 @@ # Network UPS Tools: main docs +# FIXME: There is a lot of shell-scripting below which gets processed by +# whatever system shell there is. While bash handles expressions like +# VAL="`cmd "some params"`" +# in a way that "some params" are a single token passed to "cmd" as an +# argument, and the stdout of such command execution is collected into a +# single-token string as "VAL" (even if with white-spaces). Some other +# shells, e.g. "ksh" seems to actively dislike unbalanced double-quotes +# inside the backticks (e.g. matched in some grep/sed regexes below), +# although generally the approach works for such "VAL" assignments too. +# Note that the newer "$(...)" syntax is not portable, older shells +# have no idea about it, and it is cumbersome with `make` substitution. +# Keep a lookout with multi-platform NUT CI jobs, and try to use single +# quotes where possible (e.g. where pre-expanded `make` variables are +# involved - so shell should not process them again anyway). + MAINTAINERCLEANFILES = Makefile.in .dirstamp EXTRA_DIST = -all: doc +# Note: "doc" ensures the `configure`-specified list of documents we actually +# want, while the default generated "all: all-am" target historically causes +# some but not all of these targets to get built (e.g. ChangeLog html/pdf is +# usually not made there). Post-processing "doc" as part of "all" helps +# ensure that we do not rebuild stuff in vain during parallel builds (where +# "all-am" and "doc" would be unordered parallel goals of the "all" target) +# while getting those further goals achieved eventually in the default build. +# Crucially, this allows to make sure "ChangeLog(.adoc*)" files have been +# generated once (can take a looong while), settled into place, and only then +# we revisit them for html/pdf rendering (another long while) without randomly +# confusing the system with new timestamps and needless regenerations later on. +all: + @echo " DOC-FOLLOW-UP Basic 'make $@' in `pwd` is done, following up with 'make doc' to ensure complex document types" + +@$(MAKE) $(AM_MAKEFLAGS) doc # Is "egrep == grep -E" always valid? (maybe all a job for configure.ac) #EGREP = egrep @@ -88,6 +116,7 @@ ALL_TXT_SRC = nut-names.txt daisychain.txt \ release-notes.txt ChangeLog.txt solaris-usb.txt ASPELL_FILTER_PATH = @ASPELL_FILTER_PATH@ +# NOTE: This can be set by caller such as nut-website builder: NUT_SPELL_DICT = nut.dict EXTRA_DIST += $(ALL_TXT_SRC) $(SHARED_DEPS) $(IMAGE_FILES) \ $(IMAGE_LOGO_FILES) $(IMAGE_LOGO_FILES_JENKINS_NUT) $(CABLES_IMAGES) $(NUT_SPELL_DICT) \ @@ -121,7 +150,7 @@ ASCIIDOC_PDF = user-manual.pdf \ FAQ.pdf SUBDIRS = man cables -SUFFIXES = .txt .html .pdf -spellchecked .txt-prepped +SUFFIXES = .txt .html .pdf .txt-spellchecked .txt-prepped # This list is defined by configure script choices and options: CHECK_LOCAL_TARGETS = @DOC_CHECK_LIST@ @@ -131,6 +160,7 @@ endif WITH_SPELLCHECK check-local: $(CHECK_LOCAL_TARGETS) # Make sure sources are there for out-of-tree builds: +all-local all-am-local \ @DOC_BUILD_LIST@ $(ASCIIDOC_PDF) $(ASCIIDOC_HTML_SINGLE) $(ASCIIDOC_HTML_CHUNKED): $(abs_top_builddir)/docs/.prep-src-docs # This list is defined by configure script choices and options: @@ -144,11 +174,36 @@ all-docs: docs check-docs: check-pdf check-html-single check-html-chunked check-man +# Not called by default, but handy for maintainers to check which words +# in the custom dictionary are used or not by the current NUT codebase. +# Note that historically many words were added to facilitate rendition +# of the nut-website (long ago splintered from main nut repository), +# but since recently it has a way to track its own additions to the +# dictionary file. This code should help populate it as well, and keep +# only relevant entries in the appropriate corner of the sources. +# Note this can take 5-10 minutes! +spellcheck-report-dict-usage: $(NUT_SPELL_DICT).usage-report + pdf: $(ASCIIDOC_PDF) # also build the HTML manpages with these targets html-single: $(ASCIIDOC_HTML_SINGLE) html-chunked: $(ASCIIDOC_HTML_CHUNKED) +# htmldocdir and pdfdir are set by autoconf/automake +htmldoc_DATA = +if WITH_HTML_SINGLE +htmldoc_DATA += $(ASCIIDOC_HTML_SINGLE) +endif +# FIXME: Install tools refuse to work with directories in this context +# and html-chunked documentation has a separate tree per document. +# Maybe an "(un)install-data-local" or "install-data-hook" for this? +#if WITH_HTML_CHUNKED +#htmldoc_DATA += $(ASCIIDOC_HTML_CHUNKED) +#endif +if WITH_PDFS +pdf_DATA = $(ASCIIDOC_PDFS) +endif + # the "for" loops might better use $^ but it might be not portable check-pdf: $(ASCIIDOC_PDF) @FAILED=""; LANG=C; LC_ALL=C; export LANG; export LC_ALL; \ @@ -158,13 +213,104 @@ check-pdf: $(ASCIIDOC_PDF) echo "FAILED PDF sanity check for:$$FAILED" >&2 ; file $$FAILED >&2 ; exit 1; \ fi; echo "PASSED PDF sanity check"; exit 0 +# Regarding ChangeLog check: sometimes asciidoc gives up early +# (we have megabytes of text and thousands of sections here). +# In some cases, the intermediate XML is broken and a2x=>xmllint +# notices it. In others, it is truncated at just the right place +# structurally and leads to a short HTML with only part of the +# expected contents. We should no longer have several processes +# trying to create the files involved (or rather do so atomically +# and rename into final path, in case we still have competition +# here); earlier when several generators appended to the same +# file we could have several copies overlaid, with one of the +# document's copies starting mid-sentence of another. +# The two expected mentions are in the table of contents and +# in the eventual section. Checking for first/second entries, +# and not exactly two mentions, should allow to catch the case +# of overlapping documents. Checking for the last entry allows +# to catch incomplete parses, where asciidoc gave up early. +# NOTE: Technically it may be more than two, if the author and +# date were used several times in the original ChangeLog file +# (either with different e-mails, or if different author's work +# is interleaved during the day, e.g. many PRs merged, and no +# CHANGELOG_REQUIRE_GROUP_BY_DATE_AUTHOR=true setting was in place. +# NOTE: No dependencies, avoids (re-)generation and log messages. +ChangeLog.html-contentchecked: + @FAILED=""; \ + if [ -s '$(top_builddir)/ChangeLog' ] && [ -s ChangeLog.html ] ; then \ + SECOND_ENTRY="`grep -E '^[0-9]' '$(top_builddir)/ChangeLog' | head -2 | tail -1 | sed 's/ *[\"<].*//'`" || SECOND_ENTRY="" ; \ + FIRST_ENTRY="`grep -E '^[0-9]' '$(top_builddir)/ChangeLog' | head -1 | sed 's/ *[\"<].*//'`" || FIRST_ENTRY="" ; \ + LAST_ENTRY="`grep -E '^[0-9]' '$(top_builddir)/ChangeLog' | tail -1 | sed 's/ *[\"<].*//'`" || LAST_ENTRY="" ; \ + if [ -n "$${FIRST_ENTRY}" ] ; then \ + O="`grep -cE "^$${FIRST_ENTRY}" '$(top_builddir)/ChangeLog'`" ; \ + N="`grep -cE "title.*$${FIRST_ENTRY}" 'ChangeLog.html'`" ; \ + MIN="`expr $${O} + 1`" && [ "$${MIN}" -gt 0 ] 2>/dev/null || MIN=1 ; \ + MAX="`expr $${O} + $${O}`" && [ "$${MAX}" -gt 2 ] 2>/dev/null || MAX=2 ; \ + if [ "$${N}" -lt "$${MIN}" ] || [ "$${N}" -gt "$${MAX}" ]; then \ + echo "FAILED ChangeLog.html check: does not contain expected first entry the right amount of times (huge doc, must have got aborted mid-way): $${FIRST_ENTRY} (seen $${N} times, expected between $${MIN} and $${MAX})" >&2 ; \ + if [ -z "$$FAILED" ] ; then \ + echo "Expected size over 3MB (for common builds):" >&2 ; \ + ls -la "ChangeLog.html" '$(top_builddir)/ChangeLog'* >&2 ; \ + FAILED="ChangeLog.html" ; \ + fi ; \ + fi ; \ + fi; \ + if [ -n "$${SECOND_ENTRY}" ] ; then \ + O="`grep -cE "^$${SECOND_ENTRY}" '$(top_builddir)/ChangeLog'`" ; \ + N="`grep -cE "title.*$${SECOND_ENTRY}" 'ChangeLog.html'`" ; \ + MIN="`expr $${O} + 1`" && [ "$${MIN}" -gt 0 ] 2>/dev/null || MIN=1 ; \ + MAX="`expr $${O} + $${O}`" && [ "$${MAX}" -gt 2 ] 2>/dev/null || MAX=2 ; \ + if [ "$${N}" -lt "$${MIN}" ] || [ "$${N}" -gt "$${MAX}" ]; then \ + echo "FAILED ChangeLog.html check: does not contain expected second entry the right amount of times (huge doc, must have got aborted mid-way): $${SECOND_ENTRY} (seen $${N} times, expected between $${MIN} and $${MAX})" >&2 ; \ + if [ -z "$$FAILED" ] ; then \ + echo "Expected size over 3MB (for common builds):" >&2 ; \ + ls -la "ChangeLog.html" '$(top_builddir)/ChangeLog'* >&2 ; \ + FAILED="ChangeLog.html" ; \ + fi ; \ + fi ; \ + fi; \ + if [ -n "$${LAST_ENTRY}" ] ; then \ + O="`grep -cE "^$${LAST_ENTRY}" '$(top_builddir)/ChangeLog'`" ; \ + N="`grep -cE "title.*$${LAST_ENTRY}" 'ChangeLog.html'`" ; \ + MIN="`expr $${O} + 1`" && [ "$${MIN}" -gt 0 ] 2>/dev/null || MIN=1 ; \ + MAX="`expr $${O} + $${O}`" && [ "$${MAX}" -gt 2 ] 2>/dev/null || MAX=2 ; \ + if [ "$${N}" -lt "$${MIN}" ] || [ "$${N}" -gt "$${MAX}" ]; then \ + echo "FAILED ChangeLog.html check: does not contain expected last entry the right amount of times (huge doc, must have got aborted mid-way): $${LAST_ENTRY} (seen $${N} times, expected between $${MIN} and $${MAX})" >&2 ; \ + if [ -z "$$FAILED" ] ; then \ + echo "Expected size over 3MB (for common builds):" >&2 ; \ + ls -la "ChangeLog.html" '$(top_builddir)/ChangeLog'* >&2 ; \ + FAILED="ChangeLog.html" ; \ + fi ; \ + fi ; \ + fi; \ + if [ x"$$FAILED" = x ] ; then \ + echo "PASSED $@" >&2 ; \ + exit 0 ; \ + fi ; \ + if [ x"$$FAILED" != x ] && [ -s '$(top_builddir)/ChangeLog.adoc' ] \ + && [ "`head -1 $(top_builddir)/ChangeLog.adoc`" = "=== Failed to generate the ChangeLog" ] \ + ; then \ + FAILED="" ; \ + fi; \ + fi; \ + if [ x"$$FAILED" = x ] ; then \ + echo "SKIPPED $@ because input files were not available" >&2 ; \ + exit 0 ; \ + fi ; \ + exit 1 + check-html-single: $(ASCIIDOC_HTML_SINGLE) - @FAILED=""; LANG=C; LC_ALL=C; export LANG; export LC_ALL; \ - for F in $(ASCIIDOC_HTML_SINGLE) ; do \ + +@FAILED=""; LANG=C; LC_ALL=C; export LANG; export LC_ALL; \ + for F in $(ASCIIDOC_HTML_SINGLE) ; do \ test -s "$$F" && { file "$$F" | $(EGREP) -i '(XML|HTML.*document)' > /dev/null ; } || FAILED="$$FAILED $$F" ; \ - done; if test -n "$$FAILED" ; then \ + case "$$F" in \ + *ChangeLog*) if [ -s '$(top_builddir)/ChangeLog' ] ; then \ + $(MAKE) $(AM_MAKEFLAGS) "`basename "$$F"`"-contentchecked || FAILED="$$FAILED $$F" ; \ + fi ;; \ + esac ; \ + done; if test -n "$$FAILED" ; then \ echo "FAILED HTML-single sanity check for:$$FAILED" >&2 ; file $$FAILED >&2 ; exit 1; \ - fi; echo "PASSED HTML-single sanity check"; exit 0 + fi; echo "PASSED HTML-single sanity check"; exit 0 check-html-chunked: $(ASCIIDOC_HTML_CHUNKED) @FAILED=""; LANG=C; LC_ALL=C; export LANG; export LC_ALL; \ @@ -185,12 +331,12 @@ check-html-chunked: $(ASCIIDOC_HTML_CHUNKED) # chosen during configure script execution. The "all-man" and "all-html" # rules build everything documented. check-man all-man man-man all-html html-man: - +cd $(top_builddir)/docs/man/ && $(MAKE) $(AM_MAKEFLAGS) -f Makefile $@ + +cd $(abs_top_builddir)/docs/man/ && $(MAKE) $(AM_MAKEFLAGS) -f Makefile $@ man: - +cd $(top_builddir)/docs/man/ && $(MAKE) $(AM_MAKEFLAGS) -f Makefile all + +cd $(abs_top_builddir)/docs/man/ && $(MAKE) $(AM_MAKEFLAGS) -f Makefile all -CLEANFILES = *.xml *.html *.pdf *-spellchecked docbook-xsl.css docinfo.xml.in.tmp +CLEANFILES = *.xml *.html *.pdf *-spellchecked *-contentchecked docbook-xsl.css docinfo.xml.in.tmp CLEANFILES += $(top_builddir)/INSTALL.nut $(top_builddir)/UPGRADING $(top_builddir)/NEWS $(top_builddir)/ChangeLog.adoc $(top_builddir)/README CLEANFILES += $(top_builddir)/*.adoc-parsed *.adoc-parsed @@ -218,21 +364,38 @@ DOCBUILD_FILTER_GITHUB_LINKS = { \ # The $< is okay here, it is used in a suffix rule below DOCBUILD_CONVERT_GITHUB_LINKS = { \ echo " DOC-ASCIIDOC-GITHUB-LINKS Parsing GitHub link patterns $< => $@"; \ - cat "$<" | $(DOCBUILD_FILTER_GITHUB_LINKS) > "$@.tmp" \ - && mv -f "$@.tmp" "$@" \ + cat "$<" | $(DOCBUILD_FILTER_GITHUB_LINKS) > "$@.tmp.$$$$" \ + && mv -f "$@.tmp.$$$$" "$@" \ ; } .adoc.adoc-parsed: @$(DOCBUILD_CONVERT_GITHUB_LINKS) -$(top_builddir)/ChangeLog: - +@echo " DOC-CHANGELOG-GENERATE $@" \ +$(top_builddir)/ChangeLog.adoc-parsed: $(top_builddir)/ChangeLog.adoc + +dummy: +$(top_builddir)/ChangeLog: dummy + @+echo " DOC-CHANGELOG-GENERATE-WRAPPER $@ : call parent Makefile to decide if (re-)generation is needed" \ && cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) $(@F) # BSD Make dislikes the path resolution here and does not always populate "$<" # (and claims why: "Using $< in a non-suffix rule context is a GNUmake idiom"), # but it has a "$?" for "list of dependencies that are newer than the target". # For more details see https://man.freebsd.org/cgi/man.cgi +if WITH_PDF_NONASCII_TITLES +A2X_ASCII_IDS = +else !WITH_PDF_NONASCII_TITLES +A2X_ASCII_IDS = ":ascii-ids:\n" +endif !WITH_PDF_NONASCII_TITLES + +# Probably due to the web of makefiles and an overwhelmed job server in some +# implementations, during parallel builds we can end up scheduling several +# threads creating this asciidoc (and adoc-parsed later). This step only +# costs a few seconds, however the updated timestamp may cause new HTML/PDF +# builds which cost a lot more. Below we try a few ways to detect a build +# already running and bail out early if the file exists. Otherwise we bite +# the bullet and spend a few seconds, and then re-check if another thread +# did exist and finished first. $(top_builddir)/ChangeLog.adoc: $(top_builddir)/ChangeLog @INPUT="$?"; \ test -n "$${INPUT}" || INPUT="$$(top_builddir)/ChangeLog" ; \ @@ -240,11 +403,13 @@ $(top_builddir)/ChangeLog.adoc: $(top_builddir)/ChangeLog || { \ MSG="FAILED to resolve input or output filename with this make implementation, or input was not generated!"; \ echo " DOC-CHANGELOG-ASCIIDOC SKIP: $${MSG}" >&2; \ - test -n "$@" && echo "$${MSG}" > "$@" ; \ + test -n "$@" && { printf '=== Failed to generate the ChangeLog\n\n%s\n\nNOTE: See https://github.com/networkupstools/nut/commits/master for change history.\n\n' "$${MSG}" > "$@" ; } ; \ exit ; \ } ; \ + W=10 ; while [ "$${W}" -gt 0 ] && find '$@.tmp.'* '$@' -newer "$${INPUT}" 2>/dev/null >/dev/null ; do sleep 1 ; W="`expr $$W - 1`"; done ; touch "$@.tmp.$$$$"; \ + if [ x"`find '$@' -newer "$${INPUT}" 2>/dev/null`" != x ] ; then echo " DOC-CHANGELOG-ASCIIDOC $${INPUT} => $@ : SKIP (keep existing)"; rm -f "$@.tmp.$$$$"; exit 0 ; fi ; \ echo " DOC-CHANGELOG-ASCIIDOC $${INPUT} => $@" \ - && printf "ifdef::txt[]\n== Very detailed Change Log\nendif::txt[]\n\n" > "$@.tmp" \ + && printf "ifdef::txt[]\n== Very detailed Change Log\n"$(A2X_ASCII_IDS)"endif::txt[]\n\n" > "$@.tmp.$$$$" \ && TABCHAR="`printf '\t'`" \ && $(SED) \ -e 's,^\([0-9a-zA-Z]\),=== \1,' \ @@ -255,8 +420,9 @@ $(top_builddir)/ChangeLog.adoc: $(top_builddir)/ChangeLog -e 's,^\([ '"$${TABCHAR}"'].*\)\([~|^]\),\1\\\2,g' \ -e 's,\[\[\([^]]*\)\]\],[\1],g' \ -e 's,^\(\s\s*\)\([0-9]\),\1{empty}\2,g' \ - < "$${INPUT}" >> "$@.tmp" \ - && mv -f "$@.tmp" "$@" + < "$${INPUT}" >> "$@.tmp.$$$$" \ + && if [ x"`find '$@' -newer "$${INPUT}" 2>/dev/null`" != x ] ; then echo " DOC-CHANGELOG-ASCIIDOC $${INPUT} => $@ : SKIP (keep recently born competitor)"; rm -f "$@.tmp.$$$$"; \ + else mv -f "$@.tmp.$$$$" "$@" ; fi # Add other directory deps (not for local EXTRA_DIST) and generated contents FULL_USER_MANUAL_DEPS = $(USER_MANUAL_DEPS) $(SHARED_DEPS) \ @@ -303,7 +469,7 @@ A2X_COMMON_OPTS = $(ASCIIDOC_VERBOSE) \ # As of version 8.6.9 it lies, and the argument is required for our distcheck # (and does affect PDF builds, as found during work on collision-avoidance - # true with at least asciidoc/a2x versions 9.0.0rc2). -# For more details see issues https://github.com/asciidoc/asciidoc/issues/44 +# For more details see issues https://web.archive.org/web/20201207082352/https://github.com/asciidoc/asciidoc/issues/44 # and https://github.com/networkupstools/nut/pull/281 (in short, attempts # to "fix" this warning broke NUT build). If this is to be retried later, see # https://github.com/networkupstools/nut/pull/281/commits/fe17861c4ea12679b3ebfefa8a6d692d79d99f2d @@ -374,18 +540,33 @@ DOCBUILD_END = { \ *.html: common.xsl xhtml.xsl .txt-prepped.html: - @A2X_OUTDIR="tmp/html-single.$(@F).$$$$" ; \ + +@A2X_OUTDIR="tmp/html-single.$(@F).$$$$" ; \ echo " DOC-HTML Generating $@"; \ $(DOCBUILD_BEGIN) ; RES=0; \ - $(A2X) $(A2X_COMMON_OPTS) --attribute=xhtml11_format --format=xhtml --xsl-file=$(srcdir)/xhtml.xsl "$(&2; \ + rm -f "$@"; \ + A2X_OUTDIR="tmp/html-single.$(@F).$$$$-retry" ; \ + $(DOCBUILD_BEGIN) ; RES=0; rm -f "`basename '$(@F)'`"-contentchecked || true ; \ + $(A2X) $(A2X_COMMON_OPTS) --attribute=xhtml11_format --format=xhtml --xsl-file=$(srcdir)/xhtml.xsl "$(&2 ; exit 0; fi; \ @@ -485,12 +670,12 @@ $(SPELLCHECK_BUILDDIR)/$(SPELLCHECK_SRC_ONE)-spellchecked: $(SPELLCHECK_SRCDIR)/ /*) ;; \ */*) if [ x"$${REPORT_SRCDIR}" = x ] ; then \ echo EMPTY >$(SPELLCHECK_RECIPE_DEBUG_STREAM) ; \ - REPORT_SRCDIR="`dirname "$(SPELLCHECK_SRC_ONE)"`"; \ + REPORT_SRCDIR="`dirname '$(SPELLCHECK_SRC_ONE)'`"; \ else \ - echo "APPEND: SPELLCHECK_SRCDIR='$(SPELLCHECK_SRCDIR)' SPELLCHECK_SRC_ONE='$(SPELLCHECK_SRC_ONE)' dirname='`dirname "$(SPELLCHECK_SRC_ONE)"`'" >$(SPELLCHECK_RECIPE_DEBUG_STREAM) ; \ - REPORT_SRCDIR="$${REPORT_SRCDIR}/`dirname "$(SPELLCHECK_SRC_ONE)"`"; \ + echo "APPEND: SPELLCHECK_SRCDIR='$(SPELLCHECK_SRCDIR)' SPELLCHECK_SRC_ONE='$(SPELLCHECK_SRC_ONE)' dirname='`dirname '$(SPELLCHECK_SRC_ONE)'`'" >$(SPELLCHECK_RECIPE_DEBUG_STREAM) ; \ + REPORT_SRCDIR="$${REPORT_SRCDIR}/`dirname '$(SPELLCHECK_SRC_ONE)'`"; \ fi ; \ - REPORT_SRC_ONE="`basename "$(SPELLCHECK_SRC_ONE)"`"; \ + REPORT_SRC_ONE="`basename '$(SPELLCHECK_SRC_ONE)'`"; \ ;; \ *) ;; \ esac; \ @@ -505,8 +690,16 @@ $(SPELLCHECK_BUILDDIR)/$(SPELLCHECK_SRC_ONE)-spellchecked: $(SPELLCHECK_SRCDIR)/ */) ;; \ *) REPORT_SRCDIR="$${REPORT_SRCDIR}/" ;; \ esac ; \ - echo " ASPELL Spell checking on $${REPORT_PREFIX}$${REPORT_SRCDIR}$${REPORT_SRC_ONE}"; \ - OUT="`(sed 's,^\(.*\)$$, \1,' | $(ASPELL) -a $(ASPELL_NUT_TEXMODE_ARGS) $(ASPELL_NUT_COMMON_ARGS) 2>&1) < "$(SPELLCHECK_SRCDIR)/$(SPELLCHECK_SRC_ONE)"`" \ + if [ x"$(SPELLCHECK_INTERACTIVE)" = xtrue ] ; then \ + echo " ASPELL Spell checking (interactively) on $${REPORT_PREFIX}$${REPORT_SRCDIR}$${REPORT_SRC_ONE}"; \ + LANG=$(ASPELL_ENV_LANG) LC_ALL=$(ASPELL_ENV_LANG) $(ASPELL) check $(ASPELL_NUT_COMMON_ARGS) '$(SPELLCHECK_SRCDIR)/$(SPELLCHECK_SRC_ONE)' || exit ; \ + if [ -s $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting ] && [ -s $(abs_srcdir)/$(NUT_SPELL_DICT) ] && diff $(abs_srcdir)/$(NUT_SPELL_DICT) $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting >/dev/null ; then \ + touch -r $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting $(abs_srcdir)/$(NUT_SPELL_DICT) ; \ + fi ; \ + else \ + echo " ASPELL Spell checking on $${REPORT_PREFIX}$${REPORT_SRCDIR}$${REPORT_SRC_ONE}"; \ + fi ; \ + OUT="`(sed 's,^\(.*\)$$, \1,' | $(ASPELL) -a $(ASPELL_NUT_TEXMODE_ARGS) $(ASPELL_NUT_COMMON_ARGS) 2>&1) < '$(SPELLCHECK_SRCDIR)/$(SPELLCHECK_SRC_ONE)'`" \ && { if test -n "$$OUT" ; then OUT="`echo "$$OUT" | $(EGREP) -b -v '$(ASPELL_OUT_NOTERRORS)' `" ; fi; \ test -z "$$OUT" ; } \ || { RES=$$? ; \ @@ -536,7 +729,7 @@ spellcheck: done ; \ if test -n "$$FAILED" ; then \ echo "=====================================================================" ; \ - echo "FAILED automatic spellcheck for the following sources (relative to `pwd`): $$FAILED" ; \ + echo "FAILED automatic spellcheck for the following sources (relative to `pwd`) using custom dictionary file '$(NUT_SPELL_DICT)': $$FAILED" ; \ echo "=====================================================================" ; \ echo "Please 'cd $(abs_top_builddir) && make spellcheck-interactive'"; \ echo "to either fix document sources or update the dictionary of accepted"; \ @@ -563,7 +756,7 @@ spellcheck-sortdict: $(abs_builddir)/$(NUT_SPELL_DICT).sorted $(abs_builddir)/$(NUT_SPELL_DICT).sorted: $(abs_srcdir)/$(NUT_SPELL_DICT) @cp -pf $(abs_srcdir)/$(NUT_SPELL_DICT) $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting @LANG=$(ASPELL_ENV_LANG); LC_ALL=$(ASPELL_ENV_LANG); export LANG; export LC_ALL; ( \ - WORDLIST="`tail -n +2 < "$?" | sort | uniq`"; \ + WORDLIST="`tail -n +2 < '$(?)' | sort | uniq`"; \ WORDCOUNT="`echo "$$WORDLIST" | wc -l`"; \ head -1 < "$?" | while read P L C E ; do echo "$$P $$L $$WORDCOUNT $$E"; break; done ; \ echo "$$WORDLIST"; \ @@ -576,34 +769,86 @@ $(abs_builddir)/$(NUT_SPELL_DICT).sorted: $(abs_srcdir)/$(NUT_SPELL_DICT) DISTCLEANFILES = $(NUT_SPELL_DICT).bak-pre-sorting .$(NUT_SPELL_DICT).sorted $(NUT_SPELL_DICT).sorted +# NOTE: In "make SPELLCHECK_INTERACTIVE=true ${docsrc}-spellchecked", +# after an interactive "aspell check" we follow-up by a run of usual +# non-interactive spell-checker to verify that the developer actually +# has fixed all of the files that the tool had concerns about, and +# that the touch-file is updated if the file is okay (to speed up +# any future re-runs). We also must update all relevant *-spellchecked +# touch-files after "make spellcheck-sortdict" which updates "nut.dict" +# file which is a prerequisite for docs checks. spellcheck-interactive: - @FAILED="" ; for docsrc in $(SPELLCHECK_SRC); do \ - echo "Spell checking on $(SPELLCHECK_SRCDIR)/$$docsrc"; \ - LANG=$(ASPELL_ENV_LANG) LC_ALL=$(ASPELL_ENV_LANG) $(ASPELL) check $(ASPELL_NUT_COMMON_ARGS) $(SPELLCHECK_SRCDIR)/$$docsrc || \ - FAILED="$$FAILED $(SPELLCHECK_SRCDIR)/$$docsrc"; \ - done ; \ - if test -n "$$FAILED" ; then \ - echo "FAILED interactive spellcheck for the following sources (relative to `pwd`): $$FAILED" >&2 ; \ + @cp -pf $(abs_srcdir)/$(NUT_SPELL_DICT) $(abs_builddir)/$(NUT_SPELL_DICT).bak-pre-sorting + +@FAILED="" ; for docsrc in $(SPELLCHECK_SRC); do \ + if test "$(SPELLCHECK_ENV_DEBUG)" != no ; then \ + echo "ASPELL (INTERACTIVE) MAKEFILE DEBUG: Will see from `pwd` if '$(SPELLCHECK_SRCDIR)/$${docsrc}-spellchecked' is up to date" >&2; \ + else true ; fi ; \ + $(MAKE) $(AM_MAKEFLAGS) -s -f "$(abs_top_builddir)/docs/Makefile" SPELLCHECK_INTERACTIVE="true" SPELLCHECK_SRC="" SPELLCHECK_SRC_ONE="$${docsrc}" SPELLCHECK_BUILDDIR="$(SPELLCHECK_BUILDDIR)" SPELLCHECK_SRCDIR="$(SPELLCHECK_SRCDIR)" "$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked" \ + || FAILED="$$FAILED $(SPELLCHECK_SRCDIR)/$$docsrc"; \ + done ; \ + if test -n "$$FAILED" ; then \ + echo "FAILED interactive spellcheck for the following sources (relative to `pwd`) using custom dictionary file '$(NUT_SPELL_DICT)': $$FAILED" >&2 ; \ exit 1; \ - fi ; exit 0 - +$(MAKE) $(AM_MAKEFLAGS) spellcheck-sortdict + fi ; \ + $(MAKE) $(AM_MAKEFLAGS) spellcheck-sortdict || exit ; \ + for docsrc in $(SPELLCHECK_SRC); do \ + if test -e "$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked" ; then \ + touch "$(SPELLCHECK_BUILDDIR)/$${docsrc}-spellchecked" ; \ + fi ; \ + done @echo "------------------------------------------------------------------------"; \ echo "Custom dictionary file $(NUT_SPELL_DICT) may have been updated now."; \ - echo "Use 'git add -p docs/$(NUT_SPELL_DICT) && git checkout -- docs/$(NUT_SPELL_DICT) && make spellcheck-sortdict && git add -p docs/$(NUT_SPELL_DICT)'"; \ + echo "Use e.g. 'git add -p docs/$(NUT_SPELL_DICT) && git checkout -- docs/$(NUT_SPELL_DICT) && make spellcheck-sortdict && git add -p docs/$(NUT_SPELL_DICT)'"; \ echo "to review changes (please DO NOT REMOVE LINES that aspell chose to drop,"; \ echo "because other systems might not know these words in their system dictionaries)"; \ echo "------------------------------------------------------------------------" else !HAVE_ASPELL -# This rule woulf probably just fail; normally with no ASPELL there are no callers for it +# This rule would probably just fail; normally with no ASPELL there are no callers for it */*-spellchecked *-spellchecked: Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) - @echo " SKIP-ASPELL $@ : Documentation spell check not available since 'aspell' was not found." >&2 + @echo " SKIP-ASPELL $@ : Documentation spell check not available since 'aspell' was not found (or missing its English dictionary)." >&2 spellcheck: - @echo "Documentation spell check not available since 'aspell' was not found." + @echo "Documentation spell check not available since 'aspell' was not found (or missing its English dictionary)." spellcheck-interactive: - @echo "Documentation spell check not available since 'aspell' was not found." + @echo "Documentation spell check not available since 'aspell' was not found (or missing its English dictionary)." endif !HAVE_ASPELL +# Note that NUT_SPELL_DICT may be an include snippet without the header line. +# To exclude files like `docs/nut.dict` or `nut-website.dict(.addons)` from +# the usage lookups, we assume that a `*.dict*` pattern fits any used names. +# Entries prefixed with '+++' mean something used in NUT sources in context +# that aspell is likely to treat as a word (standalone or surrounded by certain +# chars); otherwise in entries prefixed with '---' we print hit counts and +# contents (if any, ending with '^^^') for the character pattern across the +# whole Git-tracked codebase (case-insensitively for good measure). +# Note this can take 5-10 minutes! +# TOTHINK: Constrain to (caller-specified or default) SPELLCHECK_SRC? +$(NUT_SPELL_DICT).usage-report: $(NUT_SPELL_DICT) + @echo "Preparing $@"; \ + LANG=C; LC_ALL=C; export LANG; export LC_ALL; \ + grep -v -E '^personal_ws' < $? \ + | while read W ; do ( \ + cd "$(abs_top_srcdir)" || exit ; \ + git grep -q "$$W" -- ':!*.dict*' || git grep -qE "[0-9_,./\ -]$$W[0-9_,./\ -]" -- ':!*.dict*' ) \ + && echo "+++ $$W" \ + || ( \ + HITS_CS="`git grep "$$W" -- ':!*.dict*'`" || true; \ + HITS_CI="`git grep -i "$$W" -- ':!*.dict*'`" || true; \ + if [ -n "$$HITS_CS" ] ; then HITC_CS="`echo "$$HITS_CS" | wc -l`" ; else HITC_CS=0; fi; \ + if [ -n "$$HITS_CI" ] ; then HITC_CI="`echo "$$HITS_CI" | wc -l`" ; else HITC_CI=0; fi; \ + printf '%s (%d case-sensitive/%d case-insensitive)\n' "--- $$W" "$$HITC_CS" "$$HITC_CI"; \ + if [ "$$HITC_CS" != 0 ] ; then echo "$$HITS_CS" ; echo "^^^"; else \ + if [ "$$HITC_CI" != 0 ] ; then echo "$$HITS_CI" ; echo "^^^"; fi; \ + fi; \ + ); \ + done > "$@.tmp.$$$$" \ + && test -f "$@.tmp.$$$$" \ + && mv -f "$@.tmp.$$$$" "$@" + @echo "Reporting words from $? possibly not used in current inspected code base revision under $(abs_top_srcdir)" >&2 ; \ + grep -E '^--- ' < "$@" | grep '(0 ' || echo "SUCCESS: None found" + +CLEANFILES += $(NUT_SPELL_DICT).usage-report.tmp +MAINTAINERCLEANFILES += $(NUT_SPELL_DICT).usage-report # When building out-of-tree, be sure to have all asciidoc resources # under the same dir structure (tool limitation) @@ -621,7 +866,7 @@ $(abs_top_builddir)/docs/.prep-src-docs: $(PREP_SRC) COUNT=0; \ for F in $(PREP_SRC) ; do \ case "$$F" in \ - /*) F="`echo "$$F" | sed "s#^$(abs_top_srcdir)/*#./#"`"; \ + /*) F="`echo "$$F" | sed 's#^$(abs_top_srcdir)/*#./#'`"; \ if test x"$${linkroot}" = x"$(abs_builddir)" ; then \ linkroot="$(abs_top_builddir)" ; \ cd "$(abs_top_builddir)" ; \ @@ -647,7 +892,7 @@ $(abs_top_builddir)/docs/.prep-src-docs: $(PREP_SRC) linksrcroot="$(abs_srcdir)" ; \ for F in `echo $(PREP_SRC) | tr ' ' '\n' | sort -n | uniq` ; do \ case "$$F" in \ - /*) F="`echo "$$F" | sed "s#^$(abs_top_srcdir)/*#./#"`"; \ + /*) F="`echo "$$F" | sed 's#^$(abs_top_srcdir)/*#./#'`"; \ if test x"$${linkroot}" = x"$(abs_builddir)" ; then \ linkroot="$(abs_top_builddir)" ; \ linksrcroot="$(abs_top_srcdir)" ; \ @@ -658,7 +903,7 @@ $(abs_top_builddir)/docs/.prep-src-docs: $(PREP_SRC) D="`dirname "$$F"`" ; \ $(MKDIR_P) "$${linkroot}/$$D" || { rm -f "$@.working" ; exit 1 ; } ; \ if ! test -s "$${linkroot}/$$F" && test -s "$${linksrcroot}/$$F" ; then \ - echo " LN '$${linksrcroot}/$$F' => '$${linkroot}/$$F' (PWD = '`pwd`')" >&2 ; \ + echo " LN '$${linksrcroot}/$$F' => '$${linkroot}/$$F' (PWD = '`pwd`')" ; \ ln -fs "$${linksrcroot}/$$F" "$${linkroot}/$$F" || { rm -f "$@.working" ; exit 1 ; } ; \ COUNT="`expr $$COUNT + 1`" ; \ fi ; \ @@ -676,7 +921,7 @@ clean-local: $(AM_V_at)rm -rf *.chunked *.bak tmp $(AM_V_at)for F in $(PREP_SRC) ; do \ case "$$F" in \ - /*) F="`echo "$$F" | sed "s#^$(abs_top_srcdir)/*#./#"`"; cd "$(abs_top_builddir)" ;; \ + /*) F="`echo "$$F" | sed 's#^$(abs_top_srcdir)/*#./#'`"; cd "$(abs_top_builddir)" ;; \ esac ; \ if test x"$(abs_srcdir)" != x"$(abs_builddir)" ; then \ if test -L "$$F" || test -h "$$F" ; then \ diff --git a/docs/acknowledgements.txt b/docs/acknowledgements.txt index 77d0fd30ab..875589cc75 100644 --- a/docs/acknowledgements.txt +++ b/docs/acknowledgements.txt @@ -71,7 +71,9 @@ UPS manufacturers [[Eaton]] * link:http://powerquality.eaton.com[Eaton], has been the main NUT supporter in -the past, between 2007 and 2011, continuing MGE UPS SYSTEMS efforts. +the past, between 2007 and 2011, continuing MGE UPS SYSTEMS efforts. In 2022, +the Eaton NUT-based companion software bundle sources developed (pre-?)2013-2018 +were contributed and re-licensed to become part of NUT. As such, Eaton has been: - providing extensive technical documents (Eaton protocols library), - providing units to developers of NUT and related projects, diff --git a/docs/config-notes.txt b/docs/config-notes.txt index 6451b1c9fb..6fc72d7ddc 100644 --- a/docs/config-notes.txt +++ b/docs/config-notes.txt @@ -25,7 +25,7 @@ Generalities All configuration files within this package are parsed with a common state machine, which means they all can use a number of extras described here. -First, most of the programs use an uppercase word to declare a +First, most of the programs use an upper-case word to declare a configuration directive. This may be something like MONITOR, NOTIFYCMD, or ACCESS. The case does matter here. "monitor" won't be recognized. diff --git a/docs/config-prereqs.txt b/docs/config-prereqs.txt index 600f822952..f52fea02f9 100644 --- a/docs/config-prereqs.txt +++ b/docs/config-prereqs.txt @@ -21,9 +21,27 @@ Some of the below are alternatives, e.g. compiler toolkits (gcc vs. clang) or SSL implementations (OpenSSL vs Mozilla NSS) -- no problem installing both, at a disk space cost. -NOTE: Some NUT branches may need additional or different software versions +[NOTE] +====== +Some NUT branches may need additional or different software versions that are not yet included into `master` branch dependencies, e.g. the DMF -(Dynamic Mapping Files) sub-project needs LUA 5.1. +(Dynamic Mapping Files) sub-project needs LUA 5.1 for build and run-time, +and some Python modules for build, e.g. using OS packaging or custom call +to `pip install pycparser`. + +In case your system still provides a Python 2.x environment (and for some +reason you want to use it instead of Python 3.x), but does not anymore +provide a `pip` nor `pycparser` packages for it, you may need to use an +external bootstrap first, e.g.: + +------ +# Fetch get-pip.py for python 2.7 +:; curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py +:; python2 get-pip.py +:; python2 -m pip --version +:; python2 -m pip install pycparser +------ +====== More packages and/or system setup may be needed to actually run NUT with all features enabled; chapters below concern just with building it. @@ -115,6 +133,20 @@ actual package source versions on both Debian 8 "Jessie" and Debian 11 "Buster"). ====== +[NOTE] +.FUN NOTE +====== +For development on the road (or a native ARM build) you can use the +link:https://termux.dev/en/[Termux] project on Android. It provides +a sufficiently Debian-like operating environment for all intents and +purposes, but you may have to use their `pkg` wrapper instead of `apt` +tooling directly. + +You would need at least a couple of gigabytes available on the internal +phone storage though, especially if using `ccache` or setting up cross +builds. +====== + Debian-like package installations commonly start with an update of metadata about recently published package revisions: @@ -123,7 +155,7 @@ metadata about recently published package revisions: :; apt-get install \ ccache time \ - git python perl curl \ + git perl curl \ make autoconf automake libltdl-dev libtool \ valgrind \ cppcheck \ @@ -134,6 +166,10 @@ metadata about recently published package revisions: :; apt-get install \ libtool-bin +# See comments below, python version and package naming depends on distro +:; apt-get install \ + python + # NOTE: For python, you may eventually have to specify a variant like this # (numbers depending on default or additional packages of your distro): # :; apt-get install python2 python2.7 python-is-python2 @@ -320,13 +356,17 @@ drivers in distro packaging of NUT. Resolution and doc PRs are welcome. :; yum install \ ccache time \ file \ - git python perl curl \ + git perl curl \ make autoconf automake libtool-ltdl-devel libtool \ valgrind \ cppcheck \ pkgconfig \ gcc gcc-c++ clang +# See comments below, python version and package naming depends on distro +:; yum install \ + python + # NOTE: For python, you may eventually have to specify a variant like this # (numbers depending on default or additional packages of your distro): # :; yum install python-2.7.5 @@ -670,13 +710,17 @@ below. ------ :; pkg install \ - git python perl5 curl \ + git perl5 curl \ gmake autoconf automake autotools libltdl libtool \ valgrind \ cppcheck \ pkgconf \ gcc clang +# See comments below, python version and package naming depends on distro +:; pkg install \ + python + # NOTE: For python, you may eventually have to specify a variant like this # (numbers depending on default or additional packages of your distro): # :; pkg install python2 python27 @@ -699,7 +743,7 @@ below. :; pkg install \ cppunit \ - openssl nss \ + nss \ augeas \ libmodbus \ neon \ @@ -708,6 +752,15 @@ below. freeipmi \ avahi +# NOTE: At least on FreeBSD 12, system-provided crypto exists and is used +# by libnetsnmp, libneon, etc. - but is not marked as a package. Conversely, +# the openssl-1.1.1k (as of this writing) can be installed as a package into +# /usr/local/lib and then causes linking conflicts. The core system-provided +# build of openssl does include headers and is useful for NUT build "as is". +# ONLY INSTALL THIS PACKAGE IF REQUIRED (may get problems to rectify later): +:; test -e /lib/libcrypto.so -a -e /usr/lib/libssl.so || \ + pkg install openssl + :; pkg install \ lua51 @@ -789,18 +842,22 @@ default site. :; pkg_add sudo bash mc wget rsync :; pkg_add \ - git python curl \ + git curl \ gmake autoconf automake libltdl libtool \ valgrind \ cppcheck \ pkgconf \ gcc clang +# See comments below, python version and package naming depends on distro +:; pkg_add \ + python + # NOTE: For python, you may eventually have to specify a variant like this # (numbers depending on default or additional packages of your distro): -# :; pkg_add python-2.7.15p0 +# :; pkg_add python-2.7.15p0 py-pip # and/or: -# :; pkg_add python-3.6.6p1 +# :; pkg_add python-3.6.6p1 py3-pip # although you might succeed specifying shorter names and the packager # will offer a list of matching variants (as it does for "python" above). # NOTE: "perl" is not currently a package, but seemingly part of base OS. @@ -825,16 +882,23 @@ default site. openssl nss \ augeas \ libusb1 \ - neon \ net-snmp \ avahi +# For netxml-ups driver the library should suffice; however for nut-scanner +# you may currently require to add a `libneon.so` symlink (the package seems +# to only deliver a numbered SO library name), e.g.: +:; pkg_add neon && \ + if ! test -s /usr/local/lib/libneon.so ; then + ln -s "`cd /usr/local/lib && ls -1 libneon.so.* | sort -n | tail -1`" /usr/local/lib/libneon.so + fi + # Select a LUA-5.1 (or possibly 5.2?) version :; pkg_add \ lua :; pkg_add \ - dash ksh93 + bash dash ksh93 :; pkg_add \ argp-standalone \ @@ -922,12 +986,16 @@ you can work around by `make MKDIRPROG="mkdir -p" install -j 8` for example. ------ :; pkgin install \ - git python27 python39 perl curl \ + git perl curl \ make gmake autoconf automake libltdl libtool \ cppcheck \ pkgconf \ gcc7 clang +# See comments below, python version and package naming depends on distro +:; pkgin install \ + python27 python39 + ;; ( cd /usr/pkg/bin && ( ln -fs python2.7 python2 ; ln -fs python3.9 python3 ) ) # You can find a list of what is (pre-)installed with: # :; pkgin list | grep -Ei 'perl|python' @@ -1307,7 +1375,20 @@ for compilers, and Homebrew community packaging for dependencies (including `bash` since the system one is too old for `ci_build.sh` script syntax). See `.travis.yml` and `.circleci/config.yml` for practical details of the -setup. +setup, and https://brew.sh if you want to install it on your MacOS system +(note that its default packaged locations known as `HOMEBREW_PREFIX` differ +depending on architecture -- see https://docs.brew.sh/Installation for more +details; find via `brew config | grep HOMEBREW_PREFIX: | awk '{print $2}'`). + +NOTE: The quickest pre-configuration for `ci_build.sh` integration with this +non-default build system would be to add this line into your shell profile: +---- +eval "$(brew shellenv)" +---- + +NOTE: Homebrew is not the only build/packaging system available for MacOS, +so NUT scripts do not make any assumptions nor try to find a build system +they were not told about (via `HOMEBREW_PREFIX` in this case). Currently known dependencies for basic build include: ---- @@ -1315,18 +1396,49 @@ Currently known dependencies for basic build include: :; HOMEBREW_NO_AUTO_UPDATE=1; export HOMEBREW_NO_AUTO_UPDATE # Required: -:; brew install ccache bash libtool pkg-config gd libusb neon net-snmp openssl +:; brew install ccache bash libtool binutils autoconf automake git m4 \ + pkg-config aspell asciidoc docbook-xsl cppunit gd \ + libusb neon net-snmp \ + nss openssl \ + libmodbus freeipmi powerman ---- +NOTE: for `asciidoc`/`a2x` to work, you should `export XML_CATALOG_FILES` with +the location of packaged resources (`${HOMEBREW_PREFIX}/etc/xml/catalog`). +On one test system, man page builds spewed warnings like +`:1: SyntaxWarning: invalid escape sequence '\S'` +but seemed to produce reasonable results otherwise. + Note that ccache is installed in a different location than expected by default in the `ci_build.sh` script, so if your system allows to add the symbolic link -to `/usr/local/opt/ccache/libexec` as `/usr/lib/ccache` -- please do so as the -easiest way out. Alternatively, to prepare building sessions with `ci_build.sh` -you can: +to `/opt/homebrew/opt/ccache/libexec` (`/usr/local/opt/ccache/libexec` on x86) +as `/usr/lib/ccache` -- please do so as the easiest way out. + +Alternatively, to prepare building sessions with `ci_build.sh` you can: ---- -:; export CI_CCACHE_SYMLINKDIR="/usr/local/opt/ccache/libexec" +:; export CI_CCACHE_SYMLINKDIR="/opt/homebrew/opt/ccache/libexec" + +### ...or for x86 builders: +#:; export CI_CCACHE_SYMLINKDIR="/usr/local/opt/ccache/libexec" ---- +NOTE: For Jenkins agents, also need to `brew install --cask temurin@21` +for JRE/JDK 21. Java 17 or 21 (an LTS) is required to run Jenkins agents +after summer 2024. + +Note that you would have to create symbolic links to version-numbered names +of compilers, e.g. `clang-14` and `clang++-14` in both `/usr/local/bin` +(pointing to `/bin/clang(++)` and in the `ccache` location prepared above +(pointing to `../bin/ccache`), and repeat that in locations prepared by +XCode installation such as `/Library/Developer/CommandLineTools/usr/bin/` +and `/usr/local/Homebrew/Library/Homebrew/shims/mac/super/` just as +`ln -s clang{,-14} ; ln -s clang++{,-14}`. Apparently `clang` is the +only compiler available; various names of `gcc*` are links to the same +binaries. + +WARNING: Take care to *NOT* symlink a `clang-cpp(-14)` which is not a +name recognized by XCode dispatcher program, so requests to it freeze. + Windows builds ~~~~~~~~~~~~~~ diff --git a/docs/configure.txt b/docs/configure.txt index 623e5ec1cc..04655da4da 100644 --- a/docs/configure.txt +++ b/docs/configure.txt @@ -255,10 +255,17 @@ Other values understood for this option are listed below: * A `--with-doc=no` quietly skips generation of all types of documentation, including manpages. -* `--with-doc=skip` is used to configure some of the `make distcheck*` +* A `--with-doc=skip` is used to configure some of the `make distcheck*` scenarios to re-use man page files built and distributed by the main build and not waste time on re-generation of those. +* A `--with-doc=dist-auto` allows to use pre-distributed MAN pages if present + (should be in "tarball" release archives; should not be among Git-tracked + sources; may be left over from earlier builds in same workspace), or build + those if we can (the `auto` part). Practically this is implemented in detail + only for `--with-doc=man=dist-auto`, as we do not dist HTML and PDF products; + it is a placeholder for those to simplify the generic configuration calls. + Multiple documentation format values can be specified, separated with comma. Each such value can be suffixed with `=yes` to require building of this one documentation format (abort configuration if tools are missing), `=auto` to @@ -430,6 +437,16 @@ which generated them from data walks, but developers did not rename yet to NUT mappings conforming to `docs/nut-names.txt` standards. Production driver builds must not include any non-standard names. + --enable-NUT_STRARG-always (default: auto) + +Enable the `NUT_STRARG` macro (to handle `NULL` string printing) even +if the system libraries seem to safely support this behavior natively? +This flag should primarily help against overly zealous static analysis +tools in recent compiler generations. The `auto` value enables the flag +for certain compiler versions known to complain about some of our use +cases -- even though those seem to be false positives. + + I want it all! ~~~~~~~~~~~~~~ diff --git a/docs/developers.txt b/docs/developers.txt index b989da0424..ccddee5be2 100644 --- a/docs/developers.txt +++ b/docs/developers.txt @@ -531,6 +531,15 @@ Some supporting maintenance and development is doable with IntelliJ IDEA, making some things easier to do than with a simple Notepad, but it does not handle C/C++ development as such. +Take note that some IDEs can store their project data in the source root +directory of a project (such as NUT codebase). While `.gitignore` rules +can take care of not adding your local configuration into the SCM, these +locations can be wiped by a careless `git clean -fdX`. You are advised +to explore configuring your IDE to store project configurations outside +the source codebase location, or to track such directories as `nbproject` +or `nb-cache` as a separate Git repository (not necessarily a submodule +of NUT nor really diligently tracked) to avoid such surprises. + IDE notes on Windows ~~~~~~~~~~~~~~~~~~~~ @@ -969,6 +978,34 @@ offsets divisible by 4 or 8 (consistently for the whole table): #define SOMETHING_WITH_A_VERY_LONG_NAME 255 /* flag comment */ -------------------------------------------------------------------------------- +While at it, we encourage indentation of nested preprocessor macros +and pragmas, by adding a single space character for each inner level, +as well as commenting the `#else` and `#endif` parts (especially if +they are far away from their opening `#if`/`#ifdef`/`#ifndef` statement) +to help visual navigation in the source code base. Please take care to +keep the hash `#` character of the preprocessor lines in the left-most +column, since some implementations of `cpp` parser used for analysis +default to "traditional" (pre-C89) syntax shared with other languages, +and then ignore lines which do not start with the hash character (or +worse, ignore only some of them but not others). + +-------------------------------------------------------------------------------- +#ifdef WITH_SSL +# ifdef WITH_NSS + /* some code for NSS */ +# endif /* WITH_NSS */ +# ifdef WITH_OPENSSL +# ifndef WIN32 + /* some code for OpenSSL on POSIX systems */ +# else /* not WIN32 */ + /* some code for OpenSSL on Windows */ +# endif /* not WIN32 */ +# endif /* WITH_OPENSSL */ +#else /* not WITH_SSL */ + /* report that crypto support is not built */ +#endif /* WITH_SSL */ +-------------------------------------------------------------------------------- + If you write something that uses leading spaces, you may get away with it in a driver that's relatively secluded. However, if we have to work on that code, expect it to get reformatted according to the above. @@ -1043,6 +1080,10 @@ To display in a rough example: } ------------------------------------------------------------------------------- +All this having been said, we do detect and use the support for pragmas +to quiesce the complaints about such situations, but limit their use to +processing of certain third-party header files. + Miscellaneous coding style tools -------------------------------- @@ -1171,6 +1212,9 @@ suspected area and then exit cleanly. valgrind will tell you if you've done anything dodgy like freeing regions twice, reading uninitialized memory, or if you've leaked memory anywhere. +See also `scripts/valgrind` in NUT sources for a helper tool and resource +files to suppress common third-party problems. + For more information, refer to the link:http://valgrind.kde.org[Valgrind] project. diff --git a/docs/docinfo.xml.in b/docs/docinfo.xml.in index 20284705e6..ea0c5fcee0 100644 --- a/docs/docinfo.xml.in +++ b/docs/docinfo.xml.in @@ -15,6 +15,17 @@ with a focus on those changes which impacted documentation. --> + + 2.8.2 + 2024-04-01 + JK + + Some changes to docs and recipes, libnutscan API and functionality. + Added nutconf (library and tool). Fixed some regressions and added + improvements for certain new device series. + + + 2.8.1 2023-10-31 diff --git a/docs/documentation.txt b/docs/documentation.txt index ddc2d1f425..27abb1b76b 100644 --- a/docs/documentation.txt +++ b/docs/documentation.txt @@ -23,7 +23,7 @@ ifndef::website[] - link:https://www.networkupstools.org/ddl/index.html#_supported_devices[Devices Dumps Library (DDL)]: Provides information on how devices are supported; see also link:https://www.networkupstools.org/stable-hcl.html[the HCL] - link:../solaris-usb.html[Notes on NUT monitoring of USB devices in Solaris and related operating systems] endif::website[] -- link:https://github.com/networkupstools/ConfigExamples/releases/latest[NUT Configuration Examples] book maintained by Roger Price +- link:https://github.com/networkupstools/ConfigExamples/releases/latest/download/ConfigExamples.pdf[NUT Configuration Examples] book maintained by Roger Price - link:https://github.com/networkupstools/nut/wiki[NUT GitHub Wiki] Developer Documentation @@ -96,7 +96,11 @@ These are general information about UPS, PDU, ATS, PSU and SCD: These are writeups by users of the software. -- link:http://rogerprice.org/NUT.html[NUT Setup with openSUSE] '(Roger Price)' +- link:http://rogerprice.org/NUT[NUT Configuration Examples and helper scripts] + '(Roger Price)' (sources replicated in NUT GitHub organization as + link:https://github.com/networkupstools/ConfigExamples[ConfigExamples], + link:https://github.com/networkupstools/TLS-UPSmon[TLS-UPSmon], + and link:https://github.com/networkupstools/TLS-Shims[TLS-Shims]) - link:http://www.dimat.unina2.it/LCS/MonitoraggioUpsNutUbuntu10-eng.htm[Deploying NUT on an Ubuntu 10.04 cluster] '(Stefano Angelone)' - link:http://blog.shadypixel.com/monitoring-a-ups-with-nut-on-debian-or-ubuntu-linux[Monitoring a UPS with nut on Debian or Ubuntu Linux] '(Avery Fay)' - link:http://linux.developpez.com/cours/upsusb/[Installation et gestion d'un UPS USB en rÊseau sous linux] '(Olivier Van Hoof, french)' diff --git a/docs/download.txt b/docs/download.txt index d4d2ea6110..984e3b7bfa 100644 --- a/docs/download.txt +++ b/docs/download.txt @@ -146,7 +146,7 @@ link:https://github.com/networkupstools/nut/wiki/Links-to-distribution-packaging * link:https://cgit.freebsd.org/ports/tree/sysutils/nut-devel[FreeBSD package recipe (devel)], link:https://cgit.freebsd.org/ports/tree/sysutils/nut[FreeBSD package recipe] and link:http://www.FreeBSD.org/cgi/ports.cgi?query=^nut-&stype=name[FreeBSD package overview] - * link:cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/sysutils/ups-nut/[NetBSD recipe] and link:http://pkgsrc.se/sysutils/ups-nut[NetBSD package overview] + * link:http://cvsweb.netbsd.org/bsdweb.cgi/pkgsrc/sysutils/ups-nut/[NetBSD recipe] and link:https://pkgsrc.se/sysutils/ups-nut[NetBSD package overview] * link:http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/sysutils/nut/[OpenBSD recipe] * link:https://github.com/freenas/iocage-ports/tree/master/sysutils/nut[FreeNAS iocage-ports recipe], link:http://doc.freenas.org/9.3/freenas_services.html#ups[FreeNAS 9.3 docs on UPS integration] diff --git a/docs/maintainer-guide.txt b/docs/maintainer-guide.txt index 51c7561ab4..07af013a5c 100644 --- a/docs/maintainer-guide.txt +++ b/docs/maintainer-guide.txt @@ -6,6 +6,10 @@ ____________________ Introduction ============ +////////////////////////////////////////////////////////////////////////////// +NOTE: This file is currently not delivered in tarballs nor spellchecked. +////////////////////////////////////////////////////////////////////////////// + ... Mailing lists administration @@ -88,7 +92,7 @@ MAINTAINER SANDBOX (to be completed and pushed) * clean up "in-development" bits from files, e.g.: ** TODO etc. referring planned future in the `NEWS.adoc` and `UPDATING.adoc` - files + files - except for the upcoming release ** comment away the top-most (auto-resolved) NUT version and build date in `docs/docinfo.xml.in` -- DO NOT add (or at least commit) an entry for the actual fixed release version and date just yet @@ -104,20 +108,31 @@ MAINTAINER SANDBOX (to be completed and pushed) any recent changes to drivers (including `main.c` and other major impact from common code) and sub-drivers (`*-hid.c` for `usbhid-ups`, `*-mib.c` for `snmp-ups`, `nutdrv_qx_*` etc.) have been reflected in bumps to their - `DRIVER_VERSION` or equivalen macros + `DRIVER_VERSION` or equivalent macros ** ideally maintained during development, as features are getting merged for community testing and future development baseline in the master branch +** this is the good time to remove the `PLANNED` status from the upcoming + release info section title * NOTE that the `ChangeLog` file is currently not tracked in SCM * update this document: `docs/maintainer-guide.txt` as it inevitably requires * commit these finishing touches * bump the release identification: +** update the fallback `NUT_DEFAULT_VERSION` in `tools/gitlog2version.sh` to + (ex: `2.8.0`), and provide the `VERSION_FORCED` + and `VERSION_FORCED_SEMVER` files (to same effect but more explicitly and + visibly) to be added to the `dist` archive tarball file +*** Experiment if still needed: update version to + (ex: 2.8.0) in `configure.ac`, or if the script bump above + git tagging + would suffice: +---- +:; git commit -sm 'configure.ac: update AC_INIT for NUT v2.8.0 release` +---- ** add an entry in `docs/docinfo.xml.in` for the actual fixed release version and date ** revise `.github/workflows/PyNUTClient.yml` for fallback `TAG_NAME` naming ** revise `appveyor.yml` for branch naming ** revise `scripts/Windows/build-mingw-nut.sh` for fallback value of `VER_OPT` -** update version to (ex: 2.8.0) in `configure.ac` ** commit with a relevant release message, e.g.: ---- :; git commit -sm 'Update versions for release of NUT v2.8.0' @@ -140,10 +155,11 @@ MAINTAINER SANDBOX (to be completed and pushed) make -j 8 spellcheck && \ make -j 8 distcheck ---- -* create a GPG-signed tag v (ex: v2.8.0): +* create an annotated GPG-signed tag v (ex: `v2.8.0`): ---- -:; git tag -sm 'Release NUT v2.8.0' +:; git tag -asm 'Release NUT v2.8.0' v2.8.0 ---- +** in case of second thoughts, `git tag -d v2.8.0` and retry later ** try to avoid adding signed tags later (ex. v2.8.0-signed) to avoid the mess in GitHub release URLs (or do amend that post-factum), for more details see e.g. https://github.com/networkupstools/nut/issues/1971 @@ -156,9 +172,17 @@ MAINTAINER SANDBOX (to be completed and pushed) then) to store the source tarball, checksum and signature files * post-release update of the "in-development" codebase: -** maybe update nut/configure.ac version to .1 (ex: 2.8.0.1) +** update the fallback `NUT_DEFAULT_VERSION` in `tools/gitlog2version.sh` to + .1 (ex: `2.8.0.1`); remove the `VERSION_FORCED` + and `VERSION_FORCED_SEMVER` files +*** maybe update nut/configure.ac version to .1 + (ex: `2.8.0.1`): +---- +:; git commit -sm 'configure.ac: bump AC_INIT to development version 2.8.0.1' +---- ** `git revert` the commit which cleaned up "in-development" bits above -** Possibly resolve relevant merge conflicts for the changed context +** Possibly resolve relevant merge conflicts for the changed context, + e.g. the changed "PLANNED" status of the now-issued release info * push commits and tag @@ -172,15 +196,24 @@ MAINTAINER SANDBOX (to be completed and pushed) NOTE: for `nut` submodule be sure to refer to the tagged commit, not to the subsequent "in-development" codebase. ** in `source` submodule add a copy of tarball, checksum and hash files for - download + download: `make dist{,-hash,-sig}` +*** for a `new-X.Y.Z.txt` file use the `NEWS.adoc` with a stripped title + (see existing files) ** `git tag` the website release -** generate and publish a "historic" sub-site snapshot (currently manually) +** generate and publish a "historic" sub-site snapshot (currently manually, + check comments in nut-website's `ci_build.sh` script and/or its README file) ** update `nut` submodule to current commit ("in-development" codebase), and the `historic/index.txt` to refer to the snapshot for reference (users of specific-version packages) ** generate and publish the usual website revision (by CI or manually) +** Note that it may be needed to manually copy the new tarball-related files, + `new-X.Y.Z.txt`, change log, and maintainer keys (if updated), into the + rendered website repository, even if using the CI scripted publication. -* check that the website renders properly +* check that the website renders properly (a few minutes after publication) +** re-check the tarball signature according to current `security.txt` document + on an empty VM/container/chroot/user-home setup, to make sure that the GPG + instructions fit the reality (needed maintainer keys are published, etc.) * draft and publish a GitHub release based on the signed tag ** attach the same copy of tarball, checksum and hash files as for nut-website @@ -195,4 +228,13 @@ MAINTAINER SANDBOX (to be completed and pushed) * announce on mailing list, IRC, etc. +* update https://en.wikipedia.org/wiki/Network_UPS_Tools + +* create a GitHub issue label for the new release, e.g. at + https://github.com/networkupstools/nut/labels?q=impact : +---- +impacts-release-2.8.0 +Issues reported against NUT release 2.8.0 (maybe vanilla or with minor packaging tweaks) +---- + ////////////////////////////////////////////////////////////////////////////// diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 2931bc5252..a7b5d3781a 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -22,6 +22,8 @@ EGREP = grep -E all: +all-am-local all-local: $(abs_top_builddir)/docs/man/.prep-src-docs + # Base configuration and client manpages, always installed SRC_CONF_PAGES = \ nut.conf.txt \ @@ -85,6 +87,13 @@ MAN_CLIENT_PAGES = \ upssched.8 endif WITH_MANS +if HAVE_WINDOWS +SRC_CLIENT_PAGES += nut.exe.txt +if WITH_MANS +MAN_CLIENT_PAGES += nut.exe.8 +endif WITH_MANS +endif HAVE_WINDOWS + man8_MANS = $(MAN_CLIENT_PAGES) HTML_CLIENT_MANS = \ @@ -100,6 +109,10 @@ HTML_CLIENT_MANS = \ upsrw.html \ upssched.html +if HAVE_WINDOWS +HTML_CLIENT_MANS += nut.exe.html +endif HAVE_WINDOWS + SRC_TOOL_PAGES = nut-scanner.txt nut-recorder.txt nutconf.txt if WITH_MANS @@ -185,6 +198,12 @@ SRC_DEV_PAGES = \ nutscan_display_ups_conf_with_sanity_check.txt \ nutscan_display_ups_conf.txt \ nutscan_display_parsable.txt \ + nutscan_init_ip_ranges.txt \ + nutscan_free_ip_ranges.txt \ + nutscan_stringify_ip_ranges.txt \ + nutscan_add_ip_range.txt \ + nutscan_ip_ranges_iter_init.txt \ + nutscan_ip_ranges_iter_inc.txt \ nutscan_cidr_to_ip.txt \ nutscan_new_device.txt \ nutscan_free_device.txt \ @@ -303,6 +322,12 @@ MAN3_DEV_PAGES = \ nutscan_display_ups_conf_with_sanity_check.3 \ nutscan_display_ups_conf.3 \ nutscan_display_parsable.3 \ + nutscan_init_ip_ranges.3 \ + nutscan_free_ip_ranges.3 \ + nutscan_stringify_ip_ranges.3 \ + nutscan_add_ip_range.3 \ + nutscan_ip_ranges_iter_init.3 \ + nutscan_ip_ranges_iter_inc.3 \ nutscan_cidr_to_ip.3 \ nutscan_new_device.3 \ nutscan_free_device.3 \ @@ -312,13 +337,25 @@ MAN3_DEV_PAGES = \ nutscan_get_serial_ports_list.3 \ nutscan_init.3 +# Alias page for one text describing two commands: upscli_readline_timeout.3: upscli_readline.3 touch $@ upscli_sendline_timeout.3: upscli_sendline.3 touch $@ -# Alias page for one text describing two commands: +nutscan_scan_ip_range_snmp.3: nutscan_scan_snmp.3 + touch $@ + +nutscan_scan_ip_range_xml_http.3: nutscan_scan_xml_http_range.3 + touch $@ + +nutscan_scan_ip_range_nut.3: nutscan_scan_nut.3 + touch $@ + +nutscan_scan_ip_range_ipmi.3: nutscan_scan_ipmi.3 + touch $@ + nutscan_add_commented_option_to_device.3: nutscan_add_option_to_device.3 touch $@ @@ -378,6 +415,12 @@ HTML_DEV_MANS = \ nutscan_display_ups_conf_with_sanity_check.html \ nutscan_display_ups_conf.html \ nutscan_display_parsable.html \ + nutscan_init_ip_ranges.html \ + nutscan_free_ip_ranges.html \ + nutscan_stringify_ip_ranges.html \ + nutscan_add_ip_range.html \ + nutscan_ip_ranges_iter_init.html \ + nutscan_ip_ranges_iter_inc.html \ nutscan_cidr_to_ip.html \ nutscan_new_device.html \ nutscan_free_device.html \ @@ -392,8 +435,32 @@ HTML_DEV_MANS = \ # Can't make this work on all make implementations at once, so disabled for now # Anyway it would be the same man-like page for several functions HTML_DEV_MANS_FICTION = \ + upscli_readline_timeout.html \ + upscli_sendline_timeout.html \ + nutscan_scan_ip_range_snmp.html \ + nutscan_scan_ip_range_xml_http.html \ + nutscan_scan_ip_range_nut.html \ + nutscan_scan_ip_range_ipmi.html \ nutscan_add_commented_option_to_device.html +upscli_readline_timeout.html: upscli_readline.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + +upscli_sendline_timeout.html: upscli_sendline.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + +nutscan_scan_ip_range_snmp.html: nutscan_scan_snmp.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + +nutscan_scan_ip_range_xml_http.html: nutscan_scan_xml_http_range.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + +nutscan_scan_ip_range_nut.html: nutscan_scan_nut.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + +nutscan_scan_ip_range_ipmi.html: nutscan_scan_ipmi.html + test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + nutscan_add_commented_option_to_device.html: nutscan_add_option_to_device.html test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ @@ -417,9 +484,11 @@ SRC_SERIAL_PAGES = \ bestuferrups.txt \ bestups.txt \ bestfcom.txt \ + bicker_ser.txt \ blazer-common.txt \ blazer_ser.txt \ - clone.txt \ + clone.txt \ + clone-outlet.txt \ dummy-ups.txt \ etapro.txt \ everups.txt \ @@ -464,8 +533,10 @@ MAN_SERIAL_PAGES = \ bestuferrups.8 \ bestups.8 \ bestfcom.8 \ + bicker_ser.8 \ blazer_ser.8 \ - clone.8 \ + clone.8 \ + clone-outlet.8 \ dummy-ups.8 \ etapro.8 \ everups.8 \ @@ -513,8 +584,10 @@ HTML_SERIAL_MANS = \ bestuferrups.html \ bestups.html \ bestfcom.html \ + bicker_ser.html \ blazer_ser.html \ - clone.html \ + clone.html \ + clone-outlet.html \ dummy-ups.html \ etapro.html \ everups.html \ @@ -709,17 +782,17 @@ HTML_MODBUS_MANS = phoenixcontact_modbus.html \ endif ! SOME_DRIVERS # (--with-linux_i2c) -SRC_LINUX_I2C_PAGES = asem.txt pijuice.txt +SRC_LINUX_I2C_PAGES = asem.txt pijuice.txt hwmon_ina219.txt if ! SOME_DRIVERS if WITH_MANS -MAN_LINUX_I2C_PAGES = asem.8 pijuice.8 +MAN_LINUX_I2C_PAGES = asem.8 pijuice.8 hwmon_ina219.8 endif WITH_MANS if WITH_LINUX_I2C man8_MANS += $(MAN_LINUX_I2C_PAGES) endif WITH_LINUX_I2C -HTML_LINUX_I2C_MANS = asem.html pijuice.html +HTML_LINUX_I2C_MANS = asem.html pijuice.html hwmon_ina219.html endif ! SOME_DRIVERS # (--with-gpio) @@ -832,6 +905,16 @@ HTML_MANS = \ $(HTML_LINUX_I2C_MANS) \ $(HTML_GPIO_MANS) +# htmlmandir is set by autoconf/automake +htmlman_DATA = +if WITH_HTML_SINGLE +htmlman_DATA += $(HTML_MANS) +else !WITH_HTML_SINGLE +if WITH_HTML_CHUNKED +htmlman_DATA += $(HTML_MANS) +endif WITH_HTML_CHUNKED +endif !WITH_HTML_SINGLE + # Note: target documents, except nutupsdrv.txt itself, start the # list of drivers with `- linkman:nutupsdrv[8]` entry # To regenerate these files, do `make distclean` first @@ -950,7 +1033,7 @@ CLEANFILES = *-spellchecked .prep-src-docs *-prepped SUFFIXES = .txt-prepped .txt .html .1 .3 .5 .8 -# For builds with allowed installation of prebuild man pages, check that +# For builds with allowed installation of prebuilt man pages, check that # they exist in sources (make would pull them automatically as a fallback # from failed lookup in build products). For builds that require rebuild # of man pages, abort with error if build product is missing. @@ -1123,7 +1206,7 @@ endif !HAVE_ASCIIDOC # NOTE: Due to portability, we do not use a GNU percent-wildcard extension. # We also have to export some variables that may be tainted by relative # paths when parsing the other makefile (e.g. MKDIR_P that may be defined -# via expanded $(top_builddir)/install_sh): +# via expanded $(top_builddir)/install-sh): #%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) # +$(MAKE) -s -f $(top_builddir)/docs/Makefile $(AM_MAKEFLAGS) MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ diff --git a/docs/man/apc_modbus.txt b/docs/man/apc_modbus.txt index 07cac400f6..4deb74b4e1 100644 --- a/docs/man/apc_modbus.txt +++ b/docs/man/apc_modbus.txt @@ -83,6 +83,66 @@ Set the Modbus slave id. The default slave id is 1. Set the Modbus response timeout. The default timeout is set by libmodbus. It can be good to set a higher timeout on TCP connections with high latency. +BUGS +---- + +This driver relies on advanced features of `libmodbus` to talk Modbus protocol +over USB specifically (Serial and TCP are part of common library codebase). +At the time of this writing, the common library project is just expecting a +merge of the pull request with this ability. + +For the time being, if your OS distribution does not ship the required feature +set, you may have to build your own `libmodbus` and subsequently (re-)build NUT +against this library, as detailed in the NUT GitHub Wiki at +https://github.com/networkupstools/nut/wiki/APC-UPS-with-Modbus-protocol + +The short sequence may be like follows: +------ +cd ~/ +git clone -b rtu_usb https://github.com/networkupstools/libmodbus +cd libmodbus +./autogen.sh +./configure --with-libusb --prefix=/path/to/prefix +make install +------ + +[NOTE] +====== +* you may need to `make && sudo make install` if you want to place this library + files' variant into a system path (like `--prefix=/usr/local/ups` to match + NUT defaults -- this activity would need privilege elevation via `sudo`), + and not into your home directory or some `/tmp` location. +* conversely, you may want to + `./configure --with-libusb --enable-static --disable-shared --prefix=/path/to/prefix` + and only build and install a static `libmodbus.a` (can well be installed into + `/tmp` or similarly short-lived location), so that the customized Modbus+USB + logic gets built directly into `apc_modbus` binary program and there would be + no potential run-time conflict with a dynamic library file available elsewhere + in the system. +====== + +------ +cd ~/ +git clone https://github.com/networkupstools/nut +cd nut +./autogen.sh +./configure --with-drivers=apc_modbus --with-usb --with-modbus \ + --with-modbus-includes=-I/path/to/prefix/include/modbus \ + --with-modbus-libs="-L/path/to/prefix/lib -lmodbus" +make +------ + +[NOTE] +====== +* Other NUT `configure` options may be needed for proper behavior, such as +`--prefix`, `--with-sysconfdir`, `--with-user` and `--with-group` to match +your packaged or otherwise preceding NUT installation. +====== + +The `./configure --enable-inplace-runtime` may be a good start to inherit +build configuration from an existing NUT deployment, as further detailed at +https://github.com/networkupstools/nut/wiki/Building-NUT-for-in%E2%80%90place-upgrades-or-non%E2%80%90disruptive-tests + AUTHORS ------- diff --git a/docs/man/bicker_ser.txt b/docs/man/bicker_ser.txt new file mode 100644 index 0000000000..bebecceef7 --- /dev/null +++ b/docs/man/bicker_ser.txt @@ -0,0 +1,123 @@ +BICKER_SER(8) +============= + +NAME +---- + +bicker_ser - Driver for Bicker DC UPS via serial port connections + +SYNOPSIS +-------- + +*bicker_ser* -h + +*bicker_ser* -a 'UPS_NAME' ['OPTIONS'] + +NOTE: This man page only documents the hardware-specific features of the +*bicker_ser* driver. For information about the core driver, see +linkman:nutupsdrv[8]. + +SUPPORTED HARDWARE +------------------ + +*bicker_ser* supports all Bicker UPSes shipped with the PSZ-1053 extension +module such as UPSIC-1205, UPSIC-2403, DC2412-UPS and DC2412-UPS-LD. + +CABLING +------- + +The needed cable is a standard pin-to-pin serial cable with pins 2, 3 and 5 +(on DB9 connector) connected. + +EXTRA ARGUMENTS +--------------- + +This driver supports no extra arguments from linkman:ups.conf[5]. + +VARIABLES +--------- + +Depending on the type of your UPS unit, some of the following variables may +be changed with linkman:upsrw[8]. If the driver can't read a variable from the +UPS, it will not be made available. Whenever not explicitly stated, any variable +can be disabled, in which case the action it performs will not be executed. To +disable a variable, set it to an empty value. + +*ups.delay.shutdown* (in seconds, default disabled):: +If activated and the UPS is in battery mode and the set time has expired, the +output will be disabled, and the UPS and energy storage will be disconnected. + +*ups.delay.start* (in seconds, default disabled):: +If activated and a restart condition switches the UPS output off and on again, +the set time is the delay between switching on and off. The time should cause a +defined off time so that capacities in the application can be discharged. + +*battery.charge.restart* (in percent, default disabled):: +If activated and the UPS is off or restarts, the UPS output will not be released +until the energy storage device has the set charge state. The energy storage +device is charged in the meantime. + +*battery.charge.low* (in percent, default `20`):: +If activated and the UPS is in battery mode and the battery level drops below +the set value, a shutdown command via relay event is signaled. + +*experimental.output.current.low* (in mA, default `200`):: +If activated and the UPS is in battery mode and the current drops below the set +value, the output of the UPS will shut down and disconnect the energy storage to +prevent self-discharge. + +*experimental.ups.delay.shutdown.signal* (in seconds, default disabled):: +If activated and the UPS is in battery mode and the set time has elapsed, a +shutdown command via relay event is signaled. + +*experimental.ups.delay.shutdown.signal.masked* (in seconds, default disabled):: +If activated and the UPS is in battery mode and the signal at the IN-1 input is +high and the set time has expired, a shutdown command via relay event is +signaled. + +*experimental.battery.charge.low.empty* (in percent, default `20`):: +This parameter stores the threshold value for the "Battery Empty" signal. +Currently this setting is only valid for relay signaling. Cannot be disabled. + +*experimental.ups.relay.mode* (default `0x01`):: +This parameter controls the behavior of the relay in case of different events. +Cannot be disabled. ++ +Available relay modes: +[horizontal] +`0x01`::: On power fail (normally closed) +`0x02`::: On power fail (normally opened) +`0x03`::: Shutdown impulse (1 second) +`0x04`::: Battery low signal (normally closed) +`0x05`::: Battery defect signal (normally closed) + +INSTANT COMMANDS +---------------- + +*shutdown.return*:: +Turn off the load and return when power is back. + +KNOWN ISSUES AND BUGS +--------------------- + +*ups.delay.shutdown is not honored*:: +Although that delay is properly set when sending the shutdown command, it seems +some UPS ignore it and use a fixed 2 seconds delay instead. + +AUTHOR +------ + +Nicola Fontana + +SEE ALSO +-------- + +The core driver: +~~~~~~~~~~~~~~~~ + +linkman:nutupsdrv[8] + +Internet resources: +~~~~~~~~~~~~~~~~~~~ + +The NUT (Network UPS Tools) home page: https://www.networkupstools.org/ diff --git a/docs/man/clone-outlet.txt b/docs/man/clone-outlet.txt new file mode 100644 index 0000000000..b26d778f34 --- /dev/null +++ b/docs/man/clone-outlet.txt @@ -0,0 +1,197 @@ +CLONE-OUTLET(8) +=============== + +NAME +---- + +clone-outlet - clone an UPS, treating its outlet as if it were an UPS (monitoring only) + +SYNOPSIS +-------- + +*clone-outlet* -h + +*clone-outlet* -a 'UPS_NAME' ['OPTIONS'] + +NOTE: This man page only documents the specific features of the +clone-outlet driver. For information about the core driver, see +linkman:nutupsdrv[8]. + +DESCRIPTION +----------- + +This driver, which sits on top of another driver socket, allows users to group +clients to a particular outlet of a device and deal with this output as if it +were a normal UPS. Unlike the linkman:clone[8] driver or linkman:dummy-ups[8] +in repeater mode, this driver represents a read-only device for monitoring and +shutdowns (it does not accept setting any values or sending instant commands +during run time). + +Unlike linkman:dummy-ups[8], this driver does not require a running `upsd` +data server nor use the networked NUT protocol to talk to the "real" driver +(which may be remote in case of `dummy-ups` repeater mode). + +This driver does not create a completely new virtual device, but replaces or +extends some of the original readings reported by the "real" driver using +information from the specified outlet, and relays all other readings as they +were. + +Remote clients like `upsmon` can `MONITOR` the device entry presented by the +data server with this driver (and the "real" driver) running and published. + +A larger deployment with one or more lower-priority devices collected on a +manageable outlet of an UPS or ePDU would likely see several drivers set +up on the system actually capable of interactions with the UPS and running +the NUT data server linkman:upsd[8] (and likely powered by another outlet): + +* a "real" driver talking to the UPS; +* a `clone` driver talking to the "real" driver and issuing outlet power-off + (or power-cycle) based on relatively high thresholds for remaining battery + charge and/or runtime of the actual UPS (or explicit instant commands), + with such operations first setting the respective timers for the outlet + on the "real" driver, and the "FSD" flag among states of the virtual UPS + status; +* possibly a `clone-outlet` driver which is read-only and interprets the + outlet timer values as triggers for "FSD" or "OFF" flags reported among + states of the virtual UPS status. + +With this approach, the lower-priority systems collected on such outlet +would run the NUT linkman:upsmon[8] client to `MONITOR` the virtual UPS +presented by the read-only `clone-outlet` driver and shut down as soon as +the "FSD" flag is raised (fairly early, based on charge and/or runtime +thresholds configured for that driver) allowing the higher-priority devices +(likely including the NUT server) to enjoy a longer on-battery life. + +The `clone` driver responsible for outlet power state changes would not +normally be monitored directly (e.g. to avoid unfortunate direct shutdown +requests from those clients), although it can be (instead of `clone-outlet`) +in sufficiently trusted networks. + +EXTRA ARGUMENTS +--------------- + +This driver supports the following settings: + +*port*='drivername-devicename':: +Required. The standard NUT driver `port` setting, here it provides the name +of the local Unix socket (or named Windows pipe) for connection to the "real" +driver. + +*prefix*='outlet.N':: +Required. Specify the outlet prefix as known on the original driver. +The subset of data points reported by the "real" UPS driver for the actual device +on this prefix would be reported as data points of the virtual UPS maintained by +this driver. + +IMPLEMENTATION +-------------- + +The port specification in the linkman:ups.conf[5] should reference the +local driver socket (or Windows named pipe) that the "real" UPS driver +is using. For example: + +------ + [realups] + driver = usbhid-ups + port = auto + + [clone-outlet-1] + driver = clone-outlet + port = usbhid-ups-realups + prefix = outlet.1 + desc = "Outlet 1 of the Real UPS" + [...] +------ + +The driver internally interprets "real" driver's information about shutdown +delay and shutdown timer, whole UPS status and this outlet's status, and +relays other data points as they were. + +If the outlet supports and reports a delayed power-off, the virtual UPS would +issue an FSD via `ups.status` for its clients to shut down safely. + +In more detail: + +Given the (required) `prefix` value such as `outlet.1`, the driver would +specifically keep track of `.status`, `.delay.shutdown`, +and `.timer.shutdown` data points reported by the "real" driver. +Numeric values of the `*.shutdown` readings would be noted by this driver, +and the boolean outlet status ("off" or otherwise) will be remembered. +These values will also be re-published by this driver "as is". + +The `ups.status` from the "real" driver would be remembered, but not +re-published by this driver immediately. Instead, it would be published +during regular "update info" loop cycles either: + +* with the "OFF" state added (if `.status` indicates the outlet + is "off"), +* or with the "FSD" state prepended (if `.timer.shutdown` is + non-negative and does not exceed the `.delay.shutdown` value), +* or "as is" if the outlet power state is "not critical". + + +IMPORTANT +--------- + +Unlike a real UPS, you should *not* configure a upsmon primary mode for this +driver. When a upsmon primary sees the OB LB flags and tells the upsd server +it is OK to initiate the shutdown sequence, the server will latch the FSD +status and it will not be possible to restart the systems connected without +restarting the upsd server. + +This will be a problem if the power returns after the clone UPS initiated +the shutdown sequence on it's outlet, but returns before the real UPS begins +shutting down. The solution is in the clone driver, that will insert the +FSD flag if needed without the help of a upsmon primary. + +CAVEATS +------- + +The clone UPS will follow the status on the real UPS driver. You can only +make the clone UPS shutdown earlier than the real UPS driver, not later. +If the real UPS driver initiates a shutdown, the clone-outlet UPS driver +will immediately follow. + +Be aware that the commands to shutdown/restart an outlet on the real UPS +drivers are not affected, so if you tell the real UPS driver to shutdown +the outlet of the clone UPS driver immediately, your clients will lose +power without warning. A delayed outlet power-off should propagate as FSD, +and the delay should be sufficiently long to allow for client shutdowns. + +If you use service management frameworks like systemd or SMF to manage the +dependencies between driver instances and other units, then you may have +to set up special dependencies (e.g. with systemd "drop-in" snippet files) +to queue your `clone` drivers to start after the "real" device drivers. + +////////////////////////////////////// +TODO later: declare the driver as "optional", see +https://github.com/networkupstools/nut/issues/1389 +////////////////////////////////////// + +AUTHOR +------ + +Arjen de Korte + +SEE ALSO +-------- + +linkman:upscmd[1], +linkman:upsrw[1], +linkman:ups.conf[5], +linkman:clone[8], +linkman:nutupsdrv[8] + +Dummy driver: +~~~~~~~~~~~~~ + +The "repeater" mode of 'dummy-ups' driver is in some ways similar to the +'clone' and 'clone-outlet' drivers, by relaying information from a locally +or remotely running "real" device driver (and NUT data server). + +linkman:dummy-ups[8] + +Internet Resources: +~~~~~~~~~~~~~~~~~~~ + +The NUT (Network UPS Tools) home page: https://www.networkupstools.org/ diff --git a/docs/man/clone.txt b/docs/man/clone.txt index d5ee2e4a8c..bd27128057 100644 --- a/docs/man/clone.txt +++ b/docs/man/clone.txt @@ -4,7 +4,7 @@ CLONE(8) NAME ---- -clone - UPS driver clone +clone - clone an UPS, treating its outlet as if it were an UPS (with shutdown INSTCMD support) SYNOPSIS -------- @@ -22,13 +22,61 @@ DESCRIPTION This driver, which sits on top of another driver socket, allows users to group clients to a particular outlet of a device and deal with this output as if it -was a normal UPS. +were a normal UPS. Unlike the linkman:clone-outlet[8] driver, this driver +represents a manageable device for that can be used both for monitoring and +for client computer and UPS/ePDU outlet shutdowns (it supports sending +relevant instant commands during run time). + +Unlike linkman:dummy-ups[8], this driver does not require a running `upsd` +data server nor use the networked NUT protocol to talk to the "real" driver +(which may be remote in case of `dummy-ups` repeater mode). + +This driver does not create a completely new virtual device, but replaces or +extends some of the original readings reported by the "real" driver using +information from the specified outlet, and relays all other readings as they +were. + +Remote clients like `upsmon` can `MONITOR` the device entry presented by the +data server with this driver (and the "real" driver) running and published. + +A larger deployment with one or more lower-priority devices collected on a +manageable outlet of an UPS or ePDU would likely see several drivers set +up on the system actually capable of interactions with the UPS and running +the NUT data server linkman:upsd[8] (and likely powered by another outlet): + +* a "real" driver talking to the UPS; +* a `clone` driver talking to the "real" driver and issuing outlet power-off + (or power-cycle) based on relatively high thresholds for remaining battery + charge and/or runtime of the actual UPS (or explicit instant commands), + with such operations first setting the respective timers for the outlet + on the "real" driver, and the "FSD" flag among states of the virtual UPS + status; +* possibly a `clone-outlet` driver which is read-only and interprets the + outlet timer values as triggers for "FSD" or "OFF" flags reported among + states of the virtual UPS status. + +With this approach, the lower-priority systems collected on such outlet +would run the NUT linkman:upsmon[8] client to `MONITOR` the virtual UPS +presented by the read-only `clone-outlet` driver and shut down as soon as +the "FSD" flag is raised (fairly early, based on charge and/or runtime +thresholds configured for that driver) allowing the higher-priority devices +(likely including the NUT server) to enjoy a longer on-battery life. + +The `clone` driver responsible for outlet power state changes would not +normally be monitored directly (e.g. to avoid unfortunate direct shutdown +requests from those clients), although it can be (instead of `clone-outlet`) +in sufficiently trusted networks. EXTRA ARGUMENTS --------------- This driver supports the following settings: +*port*='drivername-devicename':: +Required. The standard NUT driver `port` setting, here it provides the name +of the local Unix socket (or named Windows pipe) for connection to the "real" +driver. + *load.off*='command':: Recommended. Set the command on the "real" UPS driver that will be used to switch off the outlet. You need both *load.off* and *load.on* in @@ -67,8 +115,9 @@ Set the remaining battery runtime when the clone UPS switches to LB IMPLEMENTATION -------------- -The port specification in the linkman:ups.conf[5] reference the driver -socket that the "real" UPS driver is using. For example: +The port specification in the linkman:ups.conf[5] should reference the +local driver socket (or Windows named pipe) that the "real" UPS driver +is using. For example: ------ [realups] @@ -81,9 +130,44 @@ socket that the "real" UPS driver is using. For example: load.on = outlet.1.load.on load.off = outlet.1.load.off load.status = outlet.1.status + desc = "Outlet 1 of the Real UPS" [...] ------ +This driver supports instant commands to initiate a forced shutdown for +`upsmon` or similar clients which `MONITOR` this virtual UPS device, if +the outlet status is currently 'on' and no other shutdown was initiated +yet (setting the virtual UPS shutdown delay timer to `offdelay` and +issuing an FSD via `ups.status`): + +* `shutdown.return` -- power the outlet back on after `ondelay`; +* `shutdown.stayoff` -- keep the outlet 'off'. + +Such commands are propagated to the "real" driver using the NUT socket +protocol (using command names specified in the `load.off` and `load.on` +driver configuration options), if the shutdown or start timers are set +at the moment, or if the "real" device is not "online" and its known +battery charge or runtime are below the configured "low" thresholds. + +The outlet status is determined using the name specified by the +`load.status` driver option if set, or is just assumed by latest +completed shutdown/start operation (using unknown outlet number). + +The driver does not support a common NUT device shutdown operation as +such (`clone -k` just prints an error and bails out). + +This driver also supports setting certain NUT variables at run-time: + +* `battery.charge.low` -- see `mincharge` in driver options; +* `battery.runtime.low` -- see `minruntime` in driver options. + +Compared to the "real" driver's readings, this driver also adds +(or overrides) the following data points: `ups.delay.shutdown`, +`ups.delay.start`, `ups.timer.shutdown` and `ups.timer.start`. +It keeps track of "real" driver's values of `battery.charge` and +`battery.runtime` (actual current readings) to decide on automated +outlet shutdown later on. + IMPORTANT --------- @@ -132,14 +216,15 @@ SEE ALSO linkman:upscmd[1], linkman:upsrw[1], linkman:ups.conf[5], +linkman:clone-outlet[8], linkman:nutupsdrv[8] Dummy driver: ~~~~~~~~~~~~~ The "repeater" mode of 'dummy-ups' driver is in some ways similar to the -'clone' driver, by relaying information from a locally or remotely running -"real" device driver (and NUT data server). +'clone' and 'clone-outlet' drivers, by relaying information from a locally +or remotely running "real" device driver (and NUT data server). linkman:dummy-ups[8] diff --git a/docs/man/dummy-ups.txt b/docs/man/dummy-ups.txt index b45de7d85d..d51f42e56e 100644 --- a/docs/man/dummy-ups.txt +++ b/docs/man/dummy-ups.txt @@ -282,11 +282,16 @@ Clone driver: ~~~~~~~~~~~~~ The "repeater" mode of 'dummy-ups' driver is in some ways similar to the -'clone' driver, which sits on top of another driver socket, and allows -users to group clients to a particular outlet of a device and deal with -this output as if it were a normal UPS. - -linkman:clone[8] +'clone' and 'clone-outlet' drivers, which sit on top of another driver +socket (or named Windows pipe) locally, and allow users to group clients +to a particular outlet of a device and deal with this output as if it +were a normal UPS. Notably, in this mode the 'dummy-ups' driver is a +client to the networked NUT protocol and can relay information of local +or remotely served devices, and requires a running NUT data server 'upsd' +to represent the "real" device for this to work. + +linkman:clone[8], +linkman:clone-outlet[8] Internet Resources: ~~~~~~~~~~~~~~~~~~~ diff --git a/docs/man/hosts.conf.txt b/docs/man/hosts.conf.txt index 1bf23da3c2..9c82cd016c 100644 --- a/docs/man/hosts.conf.txt +++ b/docs/man/hosts.conf.txt @@ -14,6 +14,12 @@ linkman:upsimage.cgi[8]) use this file to determine if they are allowed to talk to a host. This keeps random visitors from using your web server to annoy others by creating outgoing connections. +IMPORTANT NOTES +--------------- + +* Contents of this file should be pure ASCII (character codes + not in range would be ignored with a warning message). + DIRECTIVES ---------- diff --git a/docs/man/huawei-ups2000.txt b/docs/man/huawei-ups2000.txt index fadd11c39c..0cbe179a9f 100644 --- a/docs/man/huawei-ups2000.txt +++ b/docs/man/huawei-ups2000.txt @@ -31,9 +31,13 @@ on Linux 5.12 and newer kernels, but there are exceptions, read the section *Cabling* carefully). The UPS2000 series has two variants: UPS2000-A with a tower chassis, -and UPS2000-G with a rack-mount chassis. Both should be equally supported, -but more testers are needed. -Currently, it has been tested on the following models. +and UPS2000-G with a rack-mount chassis. Within these two variants, +there are also two sub-variants: a standard runtime model powered by +an internal battery pack denoted by an "S" suffix, and a long runtime +model powered by an external battery pack denoted by an "L" suffix. + +All of these models should be equally supported, but more testers are +needed. Currently, it has been tested on the following models. * UPS2000-A-1KTTS (firmware: UPS2000A, V2R1C1SPC40, P1.0-D1.0) * UPS2000-A-1KTTS (firmware: UPS2000A, V2R1C1SPC50, P1.0-D1.0) @@ -42,6 +46,7 @@ Currently, it has been tested on the following models. * UPS2000-G-1KRTS (firmware: UPS2000G, V2R1C1SPC50, P1.0-D1.0) * UPS2000-G-3KRTS (firmware: UPS2000A, V2R1C1SPC40, P1.0-D1.0) * UPS2000-G-3KRTS (firmware: UPS2000G, V2R1C1SPC50, P1.0-D1.0) +* UPS2000-G-3KRTL (firmware: UPS2000A, V2R1C1SPC40, P1.0-D1.0) If your model is not in the list, we encourage you to report successful or unsuccessful results to the bug tracker or the mailing list. diff --git a/docs/man/hwmon_ina219.txt b/docs/man/hwmon_ina219.txt new file mode 100644 index 0000000000..9cf8394186 --- /dev/null +++ b/docs/man/hwmon_ina219.txt @@ -0,0 +1,141 @@ +HWMON_INA219(8) +=============== + +NAME +---- + +hwmon_ina219 - driver for UPS based on INA219 + +SYNOPSIS +-------- + +*hwmon_ina219* -h + +*hwmon_ina219* -a 'UPS_NAME' ['OPTIONS'] + +NOTE: This man page only documents the specific features of the *hwmon_ina219* +driver. For information about the core driver, see linkman:nutupsdrv[8]. + +The driver implements reading of current and voltage from INA219 by using hwmon +sysfs API of the Linux Kernel. There is no other UPS-like logic in there. Based +on the measurements of the battery voltage and charging current, the driver +makes assumptions of the current state of the system. + +SUPPORTED HARDWARE +------------------ + +The *hwmon_ina219* driver is based on setup with Raspberry PI Compute Module 4 +and its baseboard Waveshare CM4-POE-UPS-BASE. + +EXTRA ARGUMENTS +--------------- + +The required parameter for this driver: + +*port*='hwmon-dir':: +Path to appropriate /sys/hwmon/hwmonX or 'auto' to detect automatically. + +Optional parameters: + +*default.battery.charge.low*='low-battery-threshold':: +Threshold for low battery state (in percent). + +*default.battery.voltage.nominal*='voltage-value':: +Nominal voltage (V) value of utilized batteries, used to derive their low +and high watermark settings (see below). Default: 3.6. ++ +Known pre-sets include: `3.6`, `3.7`, `3.8`, `3.85`. + +*default.battery.voltage.low*='voltage-value':: +Low voltage (V) value of used batteries. Practically, it denotes depleted +batteries. If not given, it is derived from the *battery.voltage.nominal*. + +*default.battery.voltage.high*='voltage-value':: +High voltage (V) value of used batteries. Practically, it denotes fully +charged batteries. If not given, it is derived from the +*battery.voltage.nominal*. + +INSTALLATION +------------ + +This driver is specific to the Linux hwmon API. + +When using with the Waveshare CM4-POE-UPS-BASE baseboard, there are few steps +to be done to enable access to the INA219 circuit: + +. edit boot/config.txt: ++ +---- + dtparam=i2c_vc=on + dtoverlay=i2c-ina219 +---- + +. create a new device tree overlay file i2c-ina219.dts: ++ +---- +/dts-v1/; +/plugin/; + +/ { + compatible = "brcm,bcm2835"; + + fragment@0 { + target = <&i2c_csi_dsi>; + __overlay__ { + #address-cells = <1>; + #size-cells = <0>; + + ina219@43 { + status = "okay"; + compatible = "ti,ina219"; + reg = <0x43>; + shunt-resistor = <100000>; // R100 + }; + }; + }; +}; +---- + +. convert i2c-ina219.dts to dtbo and place it into /boot/overlays: ++ +---- +$ dtc -@ -I dts -O dtb -o /boot/overlays/i2c-ina219.dtbo i2c-ina219.dts +---- + +. configure hwmon_ina219 UPS driver for NUT (ups.conf): ++ +---- +[ina219] +driver = hwmon_ina219 +port = auto +---- + +KNOWN ISSUES AND BUGS +--------------------- + +The driver shutdown function is not implemented. + +AUTHORS +------- + +Andrew Anderson + +SEE ALSO +-------- + +The core driver: +~~~~~~~~~~~~~~~~ + +linkman:nutupsdrv[8] + +Internet resources: +~~~~~~~~~~~~~~~~~~~ + +* Initial pull requests adding this driver: +** https://github.com/networkupstools/nut/pull/2430 +** https://github.com/networkupstools/nut/issues/2378 + +* Baseboard with INA219: https://www.waveshare.com/wiki/CM4-POE-UPS-BASE +* TI INA219: https://www.ti.com/lit/ds/symlink/ina219.pdf + +* The NUT (Network UPS Tools) home page: https://www.networkupstools.org/ diff --git a/docs/man/nut-scanner.txt b/docs/man/nut-scanner.txt index df635160f3..075589c152 100644 --- a/docs/man/nut-scanner.txt +++ b/docs/man/nut-scanner.txt @@ -78,38 +78,66 @@ an 'end IP'. See specific SNMP OPTIONS for community and security settings. *-M* | *--xml_scan*:: Scan XML/HTTP devices. Can broadcast a network message on the current network -interfaces to retrieve XML/HTTP capable devices. No IP required in this mode. +interface(s) to retrieve XML/HTTP capable devices. No IP required in this mode. +If IP address ranges are specified, they would be scanned instead of a broadcast. *-O* | *--oldnut_scan*:: Scan NUT devices (i.e. upsd daemon) on IP ranging from 'start IP' to 'end IP'. *-n* | *--nut_simulation_scan*:: -Scan NUT simulated devices (.dev files in $CONFPATH). +Scan NUT simulated devices (`.dev` files in `$NUT_CONFPATH`). *-A* | *--avahi_scan*:: -Scan NUT servers using Avahi request on the current network interfaces. -No IP required. +Scan NUT servers using Avahi request on the current network interface(s). +No IP address options are required or used. *-I* | *--ipmi_scan*:: Scan NUT compatible power supplies available via IPMI on the current host, -or over the network. +or over the network if IP address ranges are specified. *-E* | *--eaton_serial* 'serial ports':: Scan Eaton devices (XCP and SHUT) available via serial bus on the current host. This option must be requested explicitly, even for a complete scan. 'serial ports' can be expressed in various forms: - ++ - 'auto' to scan all serial ports. - a single character indicating a port number ('0' (zero) for /dev/ttyS0 and -/dev/ttyUSB0 on Linux, '1' for COM1 on Windows, 'a' for /dev/ttya on Solaris...) + /dev/ttyUSB0 on Linux, '1' for COM1 on Windows, 'a' for /dev/ttya on Solaris...) - a range of N characters, hyphen separated, describing the range of -ports using 'X-Y', where X and Y are characters referring to the port number. + ports using 'X-Y', where X and Y are characters referring to the port number. - a single port name. - a list of ports name, coma separated, like '/dev/ttyS1,/dev/ttyS4'. NETWORK OPTIONS --------------- +NOTE: The networked buses (such as SNMP, NetXML, IPMI and "Old NUT") allow to +specify several IP (IPv4 or IPv6) address ranges, down to individual single +IP addresses. Normally a new range is specified by a set of one `-s` and one +`-e` options following each other (in any order). Lone or consecutive `-s` or +`-e` options present on the command line would translate to single-IP queries. +Also a `-m` option squashed between two `-s` and `-e` options would be a new +range, turning those two into single-IP queries. This feature does not by +itself recombine "neighboring" addresses into one range, nor even check for +duplicate or overlapping specifications. ++ +Also note that some buses require IP address(es) to scan, and others have a +different behavior when exactly no addresses are specified (it is not currently +possible to mix the two behaviors in one invocation of the `nut-scanner` tool). ++ +A single-address range may be a host name which would be resolved into one IP +address by the system resolver. A CIDR using a host name and netmask length +would be resolved into an IP address and subjected to the mask application, +to query hosts "near" the named one. ++ +Finally note that currently even if multi-threaded support is available, each +range specification is a separate fan-out of queries constrained by the timeout. +Requests to scan many single IP addresses will take a while to complete, much +longer than if they were a single range. This will be hopefully fixed in later +releases. + +NOTE: Colon-separated IPv6 addresses must be passed in square brackets. + *-t* | *--timeout* 'timeout':: Set the network timeout in seconds. Default timeout is 5 seconds. @@ -124,7 +152,20 @@ If this parameter is omitted, only the 'start IP' is scanned. If 'end IP' is less than 'start IP', both parameters are internally permuted. *-m* | *--mask_cidr* 'IP address/mask':: -Set a range of IP using CIDR notation. +Set a range of IP addresses by using CIDR notation. ++ +A special form `-m auto` allows `nut-scanner` to detect local IP address(es) +and scan corresponding subnet(s) on supported platforms, and `-m auto4` or +`-m auto6` limits the selected addresses to IPv4 and IPv6 respectively. Only +the first "auto*" request would be honoured, others ignored with a warning. ++ +An `/ADDRLEN` suffix can be added to the option, to filter out discovered +subnets with too many bits available for the host address part (avoiding +millions of scans in the extreme cases). For example, if your IPv4 LAN's +network range is `10.2.3.0/24`, its address part is `(32-24)=8`. Note that +while this is applied to IPv6 networks also, their typical `/64` subnets +are not likely to have a NUT/SNMP/NetXML/... server *that* close nearby +(in addressing terms), for a tight filter to find them. Default is `8`. NUT DEVICE OPTION ----------------- @@ -220,8 +261,8 @@ MISCELLANEOUS OPTIONS Display NUT version. *-a* | *--available*:: -Display available bus that can be scanned, depending on how the nut-scanner -binary program has been compiled. (OLDNUT, USB, SNMP, XML, AVAHI, IPMI). +Display available buses that can be scanned, depending on how the nut-scanner +binary program has been compiled. (e.g. OLDNUT, USB, SNMP, XML, AVAHI, IPMI). *-q* | *--quiet*:: Display only scan result. No information on currently scanned bus is displayed. @@ -229,6 +270,12 @@ Display only scan result. No information on currently scanned bus is displayed. *-D* | *--nut_debug_level*:: Raise the debugging level. Use this multiple times to see more details. +NOTE: The level of debugging needed depends both on nut-scanner and the +problem you're trying to diagnose. Therefore, first explain the problem you +have with `nut-scanner` to a developer/maintainer, before sending them debugging +output. More often than not, if you just pick a level, the output may be +either too limited or too verbose to be of any use. + EXAMPLES -------- diff --git a/docs/man/nut.conf.txt b/docs/man/nut.conf.txt index 2df3552645..80c8939153 100644 --- a/docs/man/nut.conf.txt +++ b/docs/man/nut.conf.txt @@ -20,13 +20,31 @@ Blank lines are ignored. Lines with a hash ('#') character at the 1st position of the line are ignored, too. They can be used to add comments. -IMPORTANT NOTE --------------- +IMPORTANT NOTES +--------------- -This file is intended to be sourced by shell scripts. -You MUST NOT use spaces around the equal sign! +* This file is intended to be sourced by shell scripts as well as by + service management frameworks like systemd on Linux: + - There is no guaranteed `export VAR=VAL` syntax + - No guaranteed expansion of variables like `VAR1="$VAR2-something"` -- + only verbatim assignments + - You may need to `export VAR` when sourcing it into init-scripts + or other scripts, for eventual propagation of certain settings + to NUT programs. Not-exported variables can only be consumed by + the script which "sourced" the file (and may choose to `export` + them independently). + +* You MUST NOT use spaces around the equal sign! + +* Practical support for this file and its settings currently varies + between different OS packages and NUT sample scripts, but should + converge over time. + +* Contents of this file should be pure ASCII (character codes + not in range would be ignored with a warning message). Refer to the EXAMPLE section for illustrations. +---------------- DIRECTIVES ---------- @@ -112,6 +130,59 @@ integration scripts or service units would emit messages about their activity troubleshooting via logs or console captures. Set to `true` to avoid that trove of information, if you consider it noise. +*NUT_DEBUG_LEVEL*:: +Optional, defaults to `0`. This setting controls the default debugging message +verbosity passed to NUT daemons. As an environment variable, its priority sits +between that of 'DEBUG_MIN' setting of a driver and the command-line options. + +*NUT_DEBUG_PID*:: +Optionally add current process ID to tags with debug-level identifiers. +This may be useful when many NUT daemons write to the same console or log +file, such as in containers/plugins for Home Assistant, storage appliances... + +*NUT_DEBUG_SYSLOG*:: +Optional, unset by default. +Normally NUT can (attempt to) use the syslog or Event Log (WIN32), but the +environment variable 'NUT_DEBUG_SYSLOG' allows to bypass it, and perhaps keep +the daemons logging to stderr (useful e.g. in NUT Integration Test suite to +not pollute the OS logs, or in systemd where stderr and syslog both go into +the same journal). Recognized values: ++ +[options="header"] +|=========================================================================== +| Value | Description +| `stderr` | Disabled and background() keeps stderr attached +| `none` | Disabled and background() detaches stderr as usual +| `default` | Not disabled +| unset/other | Not disabled +|=========================================================================== + +*NUT_IGNORE_CHECKPROCNAME*:: +Optional, defaults to `false`. Normally NUT can (attempt to) verify that +the program file name matches the name associated with a running process, +when using PID files to send signals. ++ +The `NUT_IGNORE_CHECKPROCNAME` boolean toggle allows to quickly skip such +verification, in case it causes problems (e.g. NUT programs were renamed +and do not match built-in expectations). ++ +This environment variable can also be optionally set in init-scripts or +service methods for `upsd`, `upsmon` and NUT drivers/`upsdrvctl`. + +*NUT_QUIET_INIT_UPSNOTIFY*:: +Optional flag to prevent daemons which can notify service management frameworks +(such as systemd) about passing their lifecycle milestones, to not report +loudly if they could NOT do so (e.g. running on a system without a framework, +or misconfigured so they could not report and the OS could eventually restart +the false-positively identified "unresponsive" service. ++ +Currently such reports, done by default, help troubleshoot service start-up +and highlight that NUT sources (or package build) did not take advantage of +tighter OS service management framework integration (if one exists, so that +developers could focus on adding that). Reasons to set this flag could include +platforms without such a framework and not expecting one, although nagging +your favourite OS or contributing development to make it better is also a way. + EXAMPLE ------- diff --git a/docs/man/nut.exe.txt b/docs/man/nut.exe.txt new file mode 100644 index 0000000000..c176a89d33 --- /dev/null +++ b/docs/man/nut.exe.txt @@ -0,0 +1,130 @@ +NUT.EXE(8) +========== + +NAME +---- + +nut.exe - NUT for Windows wrapper for all-in-one service + +SYNOPSIS +-------- + +*nut.exe* {-h | /?} + +*nut.exe* ['OPTIONS'] + +*nut.exe* (as a service implementation) + +DESCRIPTION +----------- + +*nut.exe* wraps NUT programs to start and stop as a Windows service. + +Depending on 'nut.conf' setting of 'MODE', it would manage the bundle of +driver(s), 'upsd' data server and 'upsmon' client, as well as attempt an +UPS shutdown command in case of FSD handling, or for mere 'netclient' systems +it would run just the 'upsmon' client to monitor remote UPS device(s) and +initiate the OS shut down on the local Windows system as applicable. + +Beside launching or stopping a set of the NUT programs in certain cases, +this helper program also allows to register (or un-register) itself as a +Windows service. To actually manage the service from command line you can +execute the Windows `net` command, e.g.: + +---- +net stop "Network UPS Tools" +net start "Network UPS Tools" +---- + +You can also execute `nut start` to automatically register the service +(if not yet registered) and start it, and `nut stop` to stop the service +(if registered and running). + +Note that for a Windows machine to act as a NUT data server for further +clients, you may have to add Windows Firewall rules to allow incoming +connections (by default to port `3493/tcp`), e.g. using PowerShell to +elevate (alternately right-click a "Command Prompt" shortcut and select +"Run as administrator"), and execute `netsh` to actually configure the +needed "Advanced Firewall" rule: + +---- +REM Elevate to administrator status then run netsh to add firewall rule. +REM Recommended to adapt "LocalIP" to expected listeners of this server, +REM and "RemoteIP" to your single or comma-separated subnet(s) in CIDR +REM notation, specific client IP address(es), or ranges of address(es) +REM (dash-separated, as IP1-IP2). + +REM The following goes as one long command line: + +powershell.exe -Command "Start-Process netsh.exe -ArgumentList + \"advfirewall firewall add rule name=NUT-upsd-data-server + dir=in action=allow localip=ANY remoteip=ANY + program=%ProgramFiles%\NUT\sbin\upsd.exe + localport=3493 protocol=tcp\" -Verb RunAs" +---- + +Keep in mind that by default NUT `upsd` only listens on `localhost`, so +you would need to add some `LISTEN` directives in `upsd.conf` as well. + +OPTIONS +------- + +*nut.exe* is currently launched with no arguments when it is intended to +run as the implementation of a registered Windows service; it would error +out otherwise. + +*/?*:: +Display the help text and exit. + +*-h*:: +Display the help text and exit. + +*-V*:: +Display NUT version and exit. + +*-D*:: +Raise the debug level. Use this multiple times for additional details. +The non-trivial debug level would be passed down to launched NUT programs. +Primarily useful for troubleshooting with the non-service mode. + +*-I*:: +Install as a Windows service called "Network UPS Tools". + +*-U*:: +Uninstall the Windows service. + +*-N*:: +Run once in non-service mode (for troubleshooting). + +*start*:: +Install as a Windows service called "Network UPS Tools" (if not yet done), +and try to start this service. + +*stop*:: +Try to stop a Windows service called "Network UPS Tools". + +DIAGNOSTICS +----------- + +*nut.exe* should not interact with console message buffers (stdout, stderr) +much, except when explicitly asked to (e.g. displaying help and NUT version, +running with verbose debug mode) or when exiting after an attempted service +initialization while not running in a service context. + +Most of normal logging from *nut.exe* goes to the Windows Event Log. + +Launched NUT programs may emit messages of their own; their fate when no +console is attached is questionable. + +SEE ALSO +-------- + +linkman:nut.conf[5], linkman:ups.conf[5], linkman:nutupsdrv[8], +linkman:upsd[8], linkman:upsd.conf[5], linkman:upsd.users[5], +linkman:upsmon[8], linkman:upsmon.conf[5] + +Internet resources: +~~~~~~~~~~~~~~~~~~~ + +The NUT (Network UPS Tools) home page: https://www.networkupstools.org/ + diff --git a/docs/man/nut_usb_addvars.txt b/docs/man/nut_usb_addvars.txt index 10479e7697..0ef343fead 100644 --- a/docs/man/nut_usb_addvars.txt +++ b/docs/man/nut_usb_addvars.txt @@ -109,3 +109,34 @@ If you must really know *which* one, it will not! Force redundant call to `usb_set_altinterface()`, especially if needed for devices serving multiple USB roles where the UPS is not represented by the interface number `0` (default). + +*usb_config_index*:: +*usb_hid_rep_index*:: +*usb_hid_desc_index*:: +*usb_hid_ep_in*:: +*usb_hid_ep_out*:: + +Force use of specific interface, endpoint, descriptor index etc. numbers, +rather than defaulting to 0 (rarely other values in certain drivers for +some devices known to use non-zero numbers). Specified as a hexadecimal +number. ++ +As a rule of thumb for `usb_hid_desc_index` discovery, you can see larger +`wDescriptorLength` values (roughly 600+ bytes) in reports of `lsusb` or +similar tools. + +[NOTE] +====== +Run-time troubleshooting of USB-capable NUT drivers can involve not only +raising the common NUT debug verbosity (e.g. using the `DEBUG_MIN` setting +in linkman:ups.conf[5] or protocol commands to change the `driver.debug` +value), but may also benefit from LibUSB specific debugging. + +For the latter, currently you would have to export the environment variable +`LIBUSB_DEBUG` before starting a NUT driver (may be set and "exported" via +linkman:nut.conf[5]), to a numeric value such as `4` ("All messages are +emitted"). For more details, including the currently supported values, see: + +* https://libusb.sourceforge.io/api-1.0/ +* https://libusb.sourceforge.io/api-1.0/group__libusb__lib.html +====== diff --git a/docs/man/nutconf.txt b/docs/man/nutconf.txt index 2485f94aaa..3866a9716a 100644 --- a/docs/man/nutconf.txt +++ b/docs/man/nutconf.txt @@ -107,6 +107,14 @@ Notification types are: - 'REPLBATT' (UPS battery needs replacing) - 'NOCOMM' (device is unavailable) - 'NOPARENT' (upsmon parent process died, shutdown is impossible) +- 'CAL' (calibration in progress) +- 'NOTCAL' (calibration finished) +- 'OFF' (UPS is administratively OFF or asleep, should wake up on command) +- 'NOTOFF' (UPS is no longer administratively OFF or asleep) +- 'BYPASS' (on bypass = powered, not protecting) +- 'NOTBYPASS' (no longer on bypass) +- 'SUSPEND_STARTING' (OS is entering sleep/suspend/hibernate mode) +- 'SUSPEND_FINISHED' (OS just finished sleep/suspend/hibernate mode) :: Notification flags: diff --git a/docs/man/nutscan.txt b/docs/man/nutscan.txt index bfef09f52b..7f3390d026 100644 --- a/docs/man/nutscan.txt +++ b/docs/man/nutscan.txt @@ -68,6 +68,9 @@ linkman:nutscan_display_parsable[3], linkman:nutscan_display_ups_conf[3], linkman:nutscan_new_device[3], linkman:nutscan_free_device[3], linkman:nutscan_add_device_to_device[3], linkman:nutscan_add_option_to_device[3], +linkman:nutscan_init_ip_ranges[3], +linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_add_ip_range[3], linkman:nutscan_cidr_to_ip[3] Internet resources: diff --git a/docs/man/nutscan_add_ip_range.txt b/docs/man/nutscan_add_ip_range.txt new file mode 100644 index 0000000000..8270c6cc69 --- /dev/null +++ b/docs/man/nutscan_add_ip_range.txt @@ -0,0 +1,77 @@ +NUTSCAN_ADD_IP_RANGE(3) +======================= + +NAME +---- + +nutscan_add_ip_range - Add an entry with IP address range (starting +and ending addresses) to a `nutscan_ip_range_list_t` structure. + +SYNOPSIS +-------- + + #include + + /* One IP address range: */ + typedef struct nutscan_ip_range_s { + char * start_ip; + char * end_ip; + struct nutscan_ip_range_s * next; + } nutscan_ip_range_t; + + /* List of IP address ranges and helper data: */ + typedef struct nutscan_ip_range_list_s { + nutscan_ip_range_t * ip_ranges; /* Actual linked list of entries, first entry */ + nutscan_ip_range_t * ip_ranges_last; /* Pointer to end of list for quicker additions */ + size_t ip_ranges_count; /* Counter of added entries */ + } nutscan_ip_range_list_t; + + + size_t nutscan_add_ip_range( + nutscan_ip_range_list_t *irl, + char * start_ip, + char * end_ip); + +DESCRIPTION +----------- + +The *nutscan_add_ip_range()* function can create and add a `nutscan_ip_range_t` +entry based on provided inputs to the specified `nutscan_ip_range_list_t` +structure. The resulting amount of entries in the structure is returned, +or 0 in case of non-fatal errors. + +This function skips work if: + +* the structure pointer is `NULL` (0 is returned); +* neither `start_ip` nor `end_ip` were provided, i.e. they have `NULL` values + (current list length from the structure is returned); +* failed to allocate the entry (fatal). + +If only one of `start_ip` or `end_ip` values was provided (not `NULL`), a +single-address range is created with both addresses set to the same pointer +value. + +The structure should be initialized before use by `nutscan_init_ip_ranges()`. + +The caller must free the contents of the structure after completing its use +by calling `nutscan_free_ip_ranges()` (after which the structure can be +re-used for a new list), and explicitly `free()` the structure object itself if +it was allocated dynamically (e.g. by calling `nutscan_init_ip_ranges(NULL)`). + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +Currently there are no checks for duplicate or overlapping entries, so the +same IP addresses and whole IP address ranges can be added to the list (and +would eventually be scanned) many times. + +SEE ALSO +-------- + +linkman:nutscan_init_ip_ranges[3], linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_stringify_ip_ranges[3], +linkman:nutscan_cidr_to_ip[3], +linkman:nutscan_ip_ranges_iter_init[3], +linkman:nutscan_ip_ranges_iter_inc[3] diff --git a/docs/man/nutscan_free_ip_ranges.txt b/docs/man/nutscan_free_ip_ranges.txt new file mode 100644 index 0000000000..567e89dcfe --- /dev/null +++ b/docs/man/nutscan_free_ip_ranges.txt @@ -0,0 +1,43 @@ +NUTSCAN_FREE_IP_RANGES(3) +========================= + +NAME +---- + +nutscan_free_ip_ranges - Free contents of a `nutscan_ip_range_list_t` +structure populated (and optionally created) by `nutscan_init_ip_ranges()` +and, more importantly, filled by a series of `nutscan_add_ip_range()` calls. + +SYNOPSIS +-------- + + #include + + void nutscan_free_ip_ranges(nutscan_ip_range_list_t *irl); + +DESCRIPTION +----------- + +The *nutscan_free_ip_ranges()* function can free a `nutscan_ip_range_list_t` +structure. Doing so, it frees the whole linked list of `nutscan_ip_range_t` +entries, and zeroes out helper properties. + +The structure itself is not freed (as it can be a statically allocated +variable on the stack), and can be re-used for a new list if needed. + +The caller must free the structure object if it was allocated dynamically +(e.g. by calling `nutscan_init_ip_ranges(NULL)`). + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +SEE ALSO +-------- + +linkman:nutscan_init_ip_ranges[3], linkman:nutscan_add_ip_range[3], +linkman:nutscan_stringify_ip_ranges[3], +linkman:nutscan_cidr_to_ip[3], +linkman:nutscan_ip_ranges_iter_init[3], +linkman:nutscan_ip_ranges_iter_inc[3] diff --git a/docs/man/nutscan_init_ip_ranges.txt b/docs/man/nutscan_init_ip_ranges.txt new file mode 100644 index 0000000000..73094aaefc --- /dev/null +++ b/docs/man/nutscan_init_ip_ranges.txt @@ -0,0 +1,44 @@ +NUTSCAN_INIT_IP_RANGES(3) +========================= + +NAME +---- + +nutscan_init_ip_ranges - Initialize contents of a `nutscan_ip_range_list_t` +structure (and optionally create one in the first place). + +SYNOPSIS +-------- + + #include + + nutscan_ip_range_list_t * nutscan_init_ip_ranges(nutscan_ip_range_list_t *irl); + +DESCRIPTION +----------- + +The *nutscan_init_ip_ranges()* function can prepare a `nutscan_ip_range_list_t` +structure by zeroing out its fields. If the argument is `NULL`, the structure +is dynamically allocated. Either way, a pointer to it is returned. + +A structure passed by caller is not assumed to have any valid contents to free, +as it may have garbage from stack after allocation. + +The caller must free the contents of the structure after completing its use +by calling `nutscan_free_ip_ranges` (after which the structure can be re-used), +and explicitly `free()` the structure object itself if it was allocated +dynamically (e.g. by calling `nutscan_init_ip_ranges(NULL)`). + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +SEE ALSO +-------- + +linkman:nutscan_free_ip_ranges[3], linkman:nutscan_add_ip_range[3], +linkman:nutscan_stringify_ip_ranges[3], +linkman:nutscan_cidr_to_ip[3], +linkman:nutscan_ip_ranges_iter_init[3], +linkman:nutscan_ip_ranges_iter_inc[3] diff --git a/docs/man/nutscan_ip_ranges_iter_inc.txt b/docs/man/nutscan_ip_ranges_iter_inc.txt new file mode 100644 index 0000000000..abbd12aa0c --- /dev/null +++ b/docs/man/nutscan_ip_ranges_iter_inc.txt @@ -0,0 +1,44 @@ +NUTSCAN_IP_RANGES_ITER_INC(3) +============================= + +NAME +---- + +nutscan_ip_ranges_iter_inc - Proceed with iteration of an IP address range +using a `nutscan_ip_range_list_iter_t` structure. + +SYNOPSIS +-------- + + #include + + char * nutscan_ip_ranges_iter_inc(nutscan_ip_range_list_iter_t *irliter); + +DESCRIPTION +----------- + +The *nutscan_ip_ranges_iter_inc()* function can prepare an iterator from +the specified `nutscan_ip_range_list_t` structure. + +This function skips work if: + +* the structure pointer is `NULL` (`NULL` is returned); +* the structure pointer's `ip_range` list is `NULL` (`NULL` is returned), +* the structure pointer's `ip_range_iter` pointer is `NULL` (`NULL` is + returned). + +Returns the next IP address from the currently iterated registered IP +address range, or switches iteration to the next range if no addresses +remained in the current one. + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +SEE ALSO +-------- + +linkman:nutscan_init_ip_ranges[3], linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_cidr_to_ip[3], +linkman:nutscan_ip_ranges_iter_init[3] diff --git a/docs/man/nutscan_ip_ranges_iter_init.txt b/docs/man/nutscan_ip_ranges_iter_init.txt new file mode 100644 index 0000000000..4c871c8e9b --- /dev/null +++ b/docs/man/nutscan_ip_ranges_iter_init.txt @@ -0,0 +1,48 @@ +NUTSCAN_IP_RANGES_ITER_INIT(3) +============================== + +NAME +---- + +nutscan_ip_ranges_iter_init - Begin iteration of an IP address range using +a `nutscan_ip_range_list_iter_t` structure. + +SYNOPSIS +-------- + + #include + + char * nutscan_ip_ranges_iter_init( + nutscan_ip_range_list_iter_t *irliter, + const nutscan_ip_range_list_t *irl); + +DESCRIPTION +----------- + +The *nutscan_ip_ranges_iter_init()* function can prepare an iterator from +the specified `nutscan_ip_range_list_t` structure, saving it into the +caller-provided `nutscan_ip_range_list_iter_t` helper object. + +Different iterators may be created to walk the same `nutscan_ip_range_list_t` +list from different scans independently, but the list and its contents should +not be freed while anyone references it. + +This function skips work if: + +* the structure pointer is `NULL` (`NULL` is returned); +* the structure pointer's `ip_range` list is `NULL` (`NULL` is returned). + +Returns the first IP address from the first registered IP address range. +Subsequent addresses can be returned by `nutscan_ip_ranges_iter_inc()`. + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +SEE ALSO +-------- + +linkman:nutscan_init_ip_ranges[3], linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_cidr_to_ip[3], +linkman:nutscan_ip_ranges_iter_inc[3] diff --git a/docs/man/nutscan_scan_ipmi.txt b/docs/man/nutscan_scan_ipmi.txt index a1fddcfce8..9d43210f68 100644 --- a/docs/man/nutscan_scan_ipmi.txt +++ b/docs/man/nutscan_scan_ipmi.txt @@ -12,22 +12,29 @@ SYNOPSIS #include nutscan_device_t * nutscan_scan_ipmi( - const char * startIP, - const char * stopIP, - nutscan_ipmi_t * sec); + const char * startIP, + const char * stopIP, + nutscan_ipmi_t * sec); + + nutscan_device_t * nutscan_scan_ip_range_ipmi( + nutscan_ip_range_list_t * irl, + nutscan_ipmi_t * sec); DESCRIPTION ----------- -The *nutscan_scan_ipmi()* function tries to detect IPMI manageable devices. +The *nutscan_scan_ipmi()* and *nutscan_scan_ip_range_ipmi()* functions +try to detect IPMI manageable devices. -If 'start_ip' is NULL, the function searches for a local PSU. +If 'start_ip' for the former or 'irl' for the latter are NULL, the +respective function searches for a local PSU. Otherwise, it searches for remote hosts that would serve IPMI protocols, and would try to authenticate using the data in 'sec' structure. -It issues a NUT request on every IP ranging from 'startIP' to 'stopIP', -where 'stopIP' is optional. Those IP arguments may be either IPv4 or IPv6 -addresses or host names. +The former issues an IPMI request on every IP ranging from 'startIP' to +'stopIP', where 'stopIP' is optional; the latter can walk several IP +address ranges represented by a `nutscan_ip_range_list_t` structure. +Those IP arguments may be either IPv4 or IPv6 addresses or host names. You MUST call linkman:nutscan_init[3] before using this function. @@ -57,5 +64,7 @@ linkman:nutscan_display_ups_conf_with_sanity_check[3], linkman:nutscan_display_ups_conf[3], linkman:nutscan_display_parsable[3], linkman:nutscan_new_device[3], linkman:nutscan_free_device[3], linkman:nutscan_add_option_to_device[3], -linkman:nutscan_add_device_to_device[3], linkman:nutscan_scan_eaton_serial[3], +linkman:nutscan_init_ip_ranges[3], +linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_add_ip_range[3], linkman:nutscan_cidr_to_ip[3] diff --git a/docs/man/nutscan_scan_nut.txt b/docs/man/nutscan_scan_nut.txt index 08c6520c88..1263a6be23 100644 --- a/docs/man/nutscan_scan_nut.txt +++ b/docs/man/nutscan_scan_nut.txt @@ -18,13 +18,21 @@ SYNOPSIS const char * port, useconds_t usec_timeout); + nutscan_device_t * nutscan_scan_ip_range_nut( + nutscan_ip_range_list_t * irl, + const char * port, + useconds_t usec_timeout); + DESCRIPTION ----------- -The *nutscan_scan_nut()* function try to detect available NUT services -and their associated devices. It issues a NUT request on every IP ranging -from 'startIP' to 'stopIP'. 'startIP' is mandatory, 'stopIP' is optional. -Those IP arguments may be either IPv4 or IPv6 addresses or host names. +The *nutscan_scan_nut()* and *nutscan_scan_ip_range_nut()* functions +try to detect available NUT services and their associated devices. +The former issues a NUT request on every IP ranging from 'startIP' +to 'stopIP' (where 'startIP' is mandatory, 'stopIP' is optional); +the latter can walk several IP address ranges represented by a +`nutscan_ip_range_list_t` structure. Those IP arguments may be +either IPv4 or IPv6 addresses or host names. You MUST call linkman:nutscan_init[3] before using this function. @@ -54,4 +62,7 @@ linkman:nutscan_display_ups_conf[3], linkman:nutscan_display_parsable[3], linkman:nutscan_new_device[3], linkman:nutscan_free_device[3], linkman:nutscan_add_option_to_device[3], linkman:nutscan_add_device_to_device[3], linkman:nutscan_scan_eaton_serial[3], +linkman:nutscan_init_ip_ranges[3], +linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_add_ip_range[3], linkman:nutscan_cidr_to_ip[3] diff --git a/docs/man/nutscan_scan_snmp.txt b/docs/man/nutscan_scan_snmp.txt index 3ae72ccfd1..f89aebdde1 100644 --- a/docs/man/nutscan_scan_snmp.txt +++ b/docs/man/nutscan_scan_snmp.txt @@ -18,12 +18,19 @@ SYNOPSIS useconds_t timeout, nutscan_snmp_t * sec); + nutscan_device_t * nutscan_scan_ip_range_snmp( + nutscan_ip_range_list_t * irl, + useconds_t usec_timeout, + nutscan_snmp_t * sec); + DESCRIPTION ----------- -The *nutscan_scan_snmp()* function try to detect NUT compatible SNMP -devices. It tries SNMP queries on every IP ranging from 'start_ip' to -'stop_ip'. Those IP arguments may be either IPv4 or IPv6 addresses or +The *nutscan_scan_snmp()* and *nutscan_scan_ip_range_snmp()* functions +try to detect NUT compatible SNMP devices. The former tries SNMP queries +on every IP ranging from 'start_ip' to 'stop_ip'; the latter can walk +several IP address ranges represented by a `nutscan_ip_range_list_t` +structure. Those IP arguments may be either IPv4 or IPv6 addresses or host names. You MUST call linkman:nutscan_init[3] before using this function. @@ -90,4 +97,7 @@ linkman:nutscan_display_ups_conf[3], linkman:nutscan_display_parsable[3], linkman:nutscan_new_device[3], linkman:nutscan_free_device[3], linkman:nutscan_add_option_to_device[3], linkman:nutscan_add_device_to_device[3], linkman:nutscan_scan_eaton_serial[3], +linkman:nutscan_init_ip_ranges[3], +linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_add_ip_range[3], linkman:nutscan_cidr_to_ip[3] diff --git a/docs/man/nutscan_scan_xml_http_range.txt b/docs/man/nutscan_scan_xml_http_range.txt index 70e0a86b23..1c9b667c81 100644 --- a/docs/man/nutscan_scan_xml_http_range.txt +++ b/docs/man/nutscan_scan_xml_http_range.txt @@ -18,16 +18,24 @@ SYNOPSIS useconds_t usec_timeout, nutscan_xml_t * sec) + nutscan_device_t * nutscan_scan_ip_range_xml_http( + nutscan_ip_range_list_t * irl, + useconds_t usec_timeout, + nutscan_xml_t * sec) + DESCRIPTION ----------- -The *nutscan_scan_xml_http_range()* function tries to detect NUT compatible -XML/HTTP devices. +The *nutscan_scan_xml_http_range()* and *nutscan_scan_ip_range_xml_http()* +functions try to detect NUT compatible XML/HTTP devices. -If 'start_ip' is NULL, the function does this by issuing a broadcast message -on currently configured network interfaces. +If 'start_ip' for the former or 'irl' for the latter are NULL, the +respective function does this by issuing a broadcast message on all +currently configured network interfaces. -Otherwise, it queries every IP ranging from 'start_ip' to 'stop_ip'. +Otherwise, the former queries every IP ranging from 'start_ip' to 'stop_ip', +where 'stopIP' is optional; the latter can walk several IP address ranges +represented by a `nutscan_ip_range_list_t` structure. Those IP arguments may be either IPv4 or IPv6 addresses or host names. It waits up to 'usec_timeout' microseconds for a response from potential @@ -55,4 +63,8 @@ linkman:nutscan_display_ups_conf_with_sanity_check[3], linkman:nutscan_display_ups_conf[3], linkman:nutscan_display_parsable[3], linkman:nutscan_new_device[3], linkman:nutscan_free_device[3], linkman:nutscan_add_option_to_device[3], -linkman:nutscan_add_device_to_device[3], linkman:nutscan_scan_eaton_serial[3] +linkman:nutscan_add_device_to_device[3], linkman:nutscan_scan_eaton_serial[3], +linkman:nutscan_init_ip_ranges[3], +linkman:nutscan_free_ip_ranges[3], +linkman:nutscan_add_ip_range[3], +linkman:nutscan_cidr_to_ip[3] diff --git a/docs/man/nutscan_stringify_ip_ranges.txt b/docs/man/nutscan_stringify_ip_ranges.txt new file mode 100644 index 0000000000..cc14793f10 --- /dev/null +++ b/docs/man/nutscan_stringify_ip_ranges.txt @@ -0,0 +1,39 @@ +NUTSCAN_STRINGIFY_IP_RANGES(3) +============================== + +NAME +---- + +nutscan_stringify_ip_ranges - Collect contents of a `nutscan_ip_range_list_t` +structure into a string buffer that can be further printed into logs. + +SYNOPSIS +-------- + + #include + + const char * nutscan_stringify_ip_ranges(nutscan_ip_range_list_t *irl); + +DESCRIPTION +----------- + +The *nutscan_stringify_ip_ranges()* function can walk a `nutscan_ip_range_list_t` +structure to report its contents: count of list items, and a comma-separated +listing with each item as a single token (if `start_ip==end_ip` in that range) +or a range as `start_ip .. end_ip`. + +Returns a pointer to internal statically allocated buffer which would be +overwritten by subsequent calls, but does not have to be freed by caller. + +NOTES +----- + +Technically, the function is currently defined in 'nutscan-ip.h' file. + +SEE ALSO +-------- + +linkman:nutscan_free_ip_ranges[3], linkman:nutscan_add_ip_range[3], +linkman:nutscan_cidr_to_ip[3], +linkman:nutscan_ip_ranges_iter_init[3], +linkman:nutscan_ip_ranges_iter_inc[3] diff --git a/docs/man/nutupsdrv.txt b/docs/man/nutupsdrv.txt index e1cc17722a..6d6fe6564a 100644 --- a/docs/man/nutupsdrv.txt +++ b/docs/man/nutupsdrv.txt @@ -114,6 +114,13 @@ are: which can not be applied "on the fly" (may fail for critical changes like run-time user/group accounts) ///////// + *exit*;; tell the currently running driver instance to just exit + (so an external caller like the new driver instance, or + the systemd or SMF frameworks would start another copy) + +With recent NUT releases, such commands can be sent using the Unix socket +for driver-server interaction. As a fallback, like older releases, signals +can be sent to the old driver instance's PID (where possible). *-P* 'pid':: Send the command signal above using specified PID number, rather than diff --git a/docs/man/riello_ser.txt b/docs/man/riello_ser.txt index a9c684f51f..12ebb179ad 100644 --- a/docs/man/riello_ser.txt +++ b/docs/man/riello_ser.txt @@ -4,7 +4,7 @@ RIELLO_SER(8) NAME ---- -riello_ser - Driver for Riello UPS Protocol UPS equipment +riello_ser - Driver for Riello UPS Protocol UPS equipment via serial port connections SYNOPSIS -------- @@ -25,6 +25,34 @@ UPS GPSER and SENTR protocols. Older Riello UPS products are not supported. +EXTRA ARGUMENTS +--------------- + +You may need to tweak some settings, depending on the make and model of your +UPS (see linkman:ups.conf[5]): + +*localcalculation*:: +When enabled, driver will calculate values of `battery.runtime` and +`battery.charge` "locally" in the driver. This is for some Riello models +which provide incorrect values in hardware readings, or none at all. +This "local calculation" is done according to nominal battery capacity, +nominal battery voltage, actual battery charge, maximum and actual UPS +load. ++ +You may want to also configure 'default.battery.voltage.low' and +'default.battery.voltage.high' in case the built-in default range +(from 10.7V to 12.9V) does not match your hardware, or give a shot +to 'default.battery.voltage.nominal' (e.g. '24') if your device does +not serve that either. ++ +NOTE: Lead (PbAc) battery charge graph is not linear, so guesstimated +charge value may not be perfectly accurate. However it should be good +enough to determine battery actual status and roughly estimate the time +it can still power the system. ++ +WARNING: This keyword may be deprecated in future releases of the driver, +in favor of `runtimecal` and other settings which it requires (as seen in +linkman:nutdrv_qx[8], linkman:blazer_ser[8] and linkman:blazer_usb[8] drivers). AUTHOR ------ @@ -34,8 +62,13 @@ Massimo Zampieri SEE ALSO -------- +Related drivers +~~~~~~~~~~~~~~~ + +linkman:riello_usb[8] + The core driver -~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~ linkman:nutupsdrv[8] diff --git a/docs/man/riello_usb.txt b/docs/man/riello_usb.txt index 737aa499f7..088a437a5e 100644 --- a/docs/man/riello_usb.txt +++ b/docs/man/riello_usb.txt @@ -29,6 +29,33 @@ EXTRA ARGUMENTS include::nut_usb_addvars.txt[] +You may need to tweak some settings, depending on the make and model of your +UPS (see linkman:ups.conf[5]): + +*localcalculation*:: +When enabled, driver will calculate values of `battery.runtime` and +`battery.charge` "locally" in the driver. This is for some Riello models +(iPlug and iDialog series) which provide incorrect values in hardware +readings, or none at all. +This "local calculation" is done according to nominal battery capacity, +nominal battery voltage, actual battery charge, maximum and actual UPS +load. ++ +You may want to also configure 'default.battery.voltage.low' and +'default.battery.voltage.high' in case the built-in default range +(from 10.7V to 12.9V) does not match your hardware, or give a shot +to 'default.battery.voltage.nominal' (e.g. '24') if your device does +not serve that either. ++ +NOTE: Lead (PbAc) battery charge graph is not linear, so guesstimated +charge value may not be perfectly accurate. However it should be good +enough to determine battery actual status and roughly estimate the time +it can still power the system. ++ +WARNING: This keyword may be deprecated in future releases of the driver, +in favor of `runtimecal` and other settings which it requires (as seen in +linkman:nutdrv_qx[8], linkman:blazer_ser[8] and linkman:blazer_usb[8] drivers). + AUTHOR ------ @@ -37,8 +64,13 @@ Massimo Zampieri SEE ALSO -------- +Related drivers +~~~~~~~~~~~~~~~ + +linkman:riello_ser[8] + The core driver -~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~ linkman:nutupsdrv[8] diff --git a/docs/man/ups.conf.txt b/docs/man/ups.conf.txt index aa98feff1e..28c102ca11 100644 --- a/docs/man/ups.conf.txt +++ b/docs/man/ups.conf.txt @@ -48,6 +48,11 @@ Here is another example, when connecting a serial UPS on Windows: port = "\\\\.\\COM10" desc = "UPS on a Windows machine" +IMPORTANT NOTES +--------------- + +* Contents of this file should be pure ASCII (character codes + not in range would be ignored with a warning message). GLOBAL DIRECTIVES ----------------- @@ -272,6 +277,21 @@ certain UPSes from working on Mac OS X. If your UPS requires explicitly setting the alternate interface, include this flag, and email the nut-upsdev list with details about your UPS and operating system. +*usb_config_index*:: +*usb_hid_rep_index*:: +*usb_hid_desc_index*:: +*usb_hid_ep_in*:: +*usb_hid_ep_out*:: + +Optional. Force use of specific interface, endpoint, descriptor index etc. +numbers, rather than defaulting to 0 (rarely other values in certain drivers +for some devices known to use non-zero numbers). Specified as a hexadecimal +number. ++ +As a rule of thumb for `usb_hid_desc_index` discovery, you can see larger +`wDescriptorLength` values (roughly 600+ bytes) in reports of `lsusb` or +similar tools. + *default.*:: Optional. Set a default value for which is used in case the UPS diff --git a/docs/man/upsd.conf.txt b/docs/man/upsd.conf.txt index 5597f483c5..4e8a9c855c 100644 --- a/docs/man/upsd.conf.txt +++ b/docs/man/upsd.conf.txt @@ -14,6 +14,12 @@ miscellaneous configuration values. This file contains details on access controls, so keep it secure. Ideally, only the upsd process should be able to read it. +IMPORTANT NOTES +--------------- + +* Contents of this file should be pure ASCII (character codes + not in range would be ignored with a warning message). + CONFIGURATION DIRECTIVES ------------------------ diff --git a/docs/man/upsdrvctl.txt b/docs/man/upsdrvctl.txt index 446ed2d411..d68b459e82 100644 --- a/docs/man/upsdrvctl.txt +++ b/docs/man/upsdrvctl.txt @@ -11,7 +11,9 @@ SYNOPSIS *upsdrvctl* -h -*upsdrvctl* ['OPTIONS'] {start | stop | shutdown} ['ups'] +*upsdrvctl* ['OPTIONS'] {start | stop | shutdown | status} ['ups'] + +*upsdrvctl* ['OPTIONS'] {list | -l} ['ups'] *upsdrvctl* ['OPTIONS'] -c COMMAND ['ups'] @@ -25,8 +27,14 @@ whenever possible. When used properly, upsdrvctl lets you maintain identical startup scripts across multiple systems with different UPS configurations. -Note: For operating systems with service management frameworks, such as +NOTE: For operating systems with service management frameworks, such as Solaris SMF or Linux systemd, the *upsdrvsvcctl* may be a better choice. +In fact, service instances prepared by linkman:nut-driver-enumerator[8] +based on contents of your linkman:ups.conf[5] file and automatically +maintained by the respective framework can conflict with manual execution +of drivers, so this tool would emit a warning in NUT builds with that +capability (can be silenced by exporting a `NUT_QUIET_INIT_NDE_WARNING` +environment variable with any value). OPTIONS ------- @@ -81,6 +89,9 @@ this mode (not saved by default when staying in foreground). Drivers will run in the background, regardless of debugging settings, as set by *-D* and passed-through by *-d* options. +*-l*:: +Alias for `list` command. + COMMANDS -------- @@ -118,6 +129,68 @@ NOTE: refer to linkman:ups.conf[5] for using the *nowait* parameter. It can be overridden by `NUT_IGNORE_NOWAIT` environment variable (e.g. used to work around certain issues with systemd otherwise). +*list*:: +Without a further argument, report all currently known device configuration +names to `stdout`, one per line. With an argument, also try to report that +name, but exit with an error code if that name is not known. + +NOTE: The tool would exit with an error if `ups.conf` file is not found, +readable, or does not define any device sections (whose names are reported +here and managed in other commands). + +NOTE: The tool name and NUT version banner line is also printed to `stdout` +before any other processing. This can be suppressed by `NUT_QUIET_INIT_BANNER` +environment variable (exported by caller and empty or "true"): + + :; NUT_QUIET_INIT_BANNER=true upsdrvctl list + dummy + UPS1 + UPS2 + +*status*:: +Similar to `list`, but reports more information -- also the driver name, the +PID if it is running, and result of a signal probe to check it is responding. +The `NUT_QUIET_INIT_BANNER` suppression can be helpful for scripted parsing. +If there is anything to print (at least one device is known), the first line +of status report would be the heading with column names: + + :; NUT_QUIET_INIT_BANNER=true upsdrvctl status + UPSNAME UPSDRV RUNNING PF_PID S_RESPONSIVE S_PID S_STATUS + dummy dummy-ups N/A -3 NOT_RESPONSIVE N/A + eco650 usbhid-ups RUNNING 3559207 RESPONSIVE 3559207 "OL" + UPS2 dummy-ups RUNNING 31455 RESPONSIVE 31455 "OL BOOST" + +Values are TAB-separated, but UPSNAME and UPSDRV may be padded by spaces +on the right and on the left respectively. Any complex string values would +be encased in double-quotes. + +Fields reported (`PF_*` = according to PID file, if any; `S_*` = via socket +protocol): + + *UPSNAME*;; driver section configuration name + *UPSDRV*;; driver program name per `ups.conf` + *RUNNING*;; `RUNNING` if `PF_PID` or `S_PID` is valid, + `STOPPED` if at least one PID value was parsed but + none was found running with a correct program name; + `N/A` if no PID file/socket reply or failed to parse. + First the PID file is consulted, but it may be absent + either due to command-line parameters of daemons, or + due to platform (WIN32). If no PID value was found and + confirmed this way, we fall back to checking the PID + reported via protocol (if available and different). + *PF_PID*;; PID of driver according to PID file (if any), or some + negative values upon errors (as defined in `common.c`) + including an absent PID file, invalid contents, or + unsupported platform for this mechanism (e.g. WIN32) + *S_RESPONSIVE*;; `RESPONSIVE` if `PING`/`PONG` during socket protocol + session setup succeeded; `NOT_RESPONSIVE` otherwise + *S_PID*;; PID of driver according to `GETPID` active query, + or `N/A` if the query failed + *S_STATUS*;; Quoted value of `ups.status` variable + +This mode does not discover drivers that are not in `ups.conf` (e.g. started +manually for experiments with many `-x` CLI options). + *-c* 'command':: Send 'command' to the background process as a signal. Valid commands are: @@ -142,6 +215,9 @@ are: which can not be applied "on the fly" (may fail for critical changes like run-time user/group accounts) ///////// + *exit*;; tell the currently running driver instance to just exit + (so an external caller like the new driver instance, or + the systemd or SMF frameworks would start another copy) If the `upsdrvctl` was launched to remain in memory and manage NUT driver processes, it can receive supported signals and pass them to those drivers. @@ -174,7 +250,8 @@ background. SEE ALSO -------- -linkman:upsdrvsvcctl[8], linkman:nutupsdrv[8], linkman:upsd[8], linkman:ups.conf[5] +linkman:upsdrvsvcctl[8], linkman:nut-driver-enumerator[8], +linkman:nutupsdrv[8], linkman:upsd[8], linkman:ups.conf[5] Internet resources: ~~~~~~~~~~~~~~~~~~~ diff --git a/docs/man/upsdrvsvcctl.txt b/docs/man/upsdrvsvcctl.txt index e6e00de697..475d005462 100644 --- a/docs/man/upsdrvsvcctl.txt +++ b/docs/man/upsdrvsvcctl.txt @@ -11,7 +11,7 @@ SYNOPSIS *upsdrvsvcctl* -h -*upsdrvsvcctl* ['OPTIONS'] {start | stop } ['ups'] +*upsdrvsvcctl* ['OPTIONS'] {start | stop | status} ['ups'] DESCRIPTION ----------- @@ -61,7 +61,13 @@ OPTIONS ------- *-h*:: -Display the help text. +Display the help text, including the built-in version of the script. + +*-V*:: +Display the version of NUT binaries (calling *upsdrvctl -V*), which +normally should not differ much from the built-in version of the script +shown in help. But with custom builds everything is possible, so it may +be useful to know. *-t*:: Enable testing mode. Testing mode makes upsdrvsvcctl display the actions @@ -127,6 +133,14 @@ by using the 'maxretry' and 'retrydelay' options - see linkman:ups.conf[5]. *stop*:: Stop the UPS driver(s). +*status*:: +Query run-time status of all configured devices (or one specified device). +Currently defers work to linkman:upsdrvctl[8], to list known device +configurations and their driver daemon details (PID, responsiveness, +`ups.status`) and to linkman:nut-driver-enumerator[8] to map device +names to service unit instances to report their names and states in +the service management framework. + *upsdrvsvcctl* also supports further operations for troubleshooting the mapping of NUT driver section names to the service instance names (which may differ due to limitations of various systems). diff --git a/docs/man/upsmon.conf.txt b/docs/man/upsmon.conf.txt index a50edb86f7..d7e8b0a4fa 100644 --- a/docs/man/upsmon.conf.txt +++ b/docs/man/upsmon.conf.txt @@ -14,9 +14,20 @@ will monitor and to tell it how to shut down the system when necessary. It will contain passwords, so keep it secure. Ideally, only the upsmon process should be able to read it. +A minimal configuration should include at least one `MONITOR` instruction, +`MINSUPPLIES` (may be 0 if this system is only monitoring other NUT servers), +and a `POWERDOWNFLAG` if this machine is a "primary" system connected to +the UPS and drives its late-shutdown power-off command in an emergency. + Additionally, other optional configuration values can be set in this file. +IMPORTANT NOTES +--------------- + +* Contents of this file should be pure ASCII (character codes + not in range would be ignored with a warning message). + CONFIGURATION DIRECTIVES ------------------------ @@ -264,6 +275,11 @@ BYPASS;; UPS on bypass (powered, not protecting) NOTBYPASS;; UPS no longer on bypass +SUSPEND_STARTING;; OS is entering sleep/suspend/hibernate mode + +SUSPEND_FINISHED;; OS just finished sleep/suspend/hibernate mode, +de-activating obsolete UPS readings to avoid an unfortunate shutdown + *NOTIFYFLAG* 'type' 'flag'[+'flag']...:: By default, upsmon sends walls global messages to all logged in users) @@ -319,14 +335,27 @@ also apply here. *POWERDOWNFLAG* 'filename':: upsmon creates this file when running in primary mode when the UPS needs -to be powered off. You should check for this file in your shutdown -scripts and call `upsdrvctl shutdown` if it exists. +to be powered off. You should check for this file in your late shutdown +scripts and call `upsdrvctl shutdown` if it exists; note that `upsmon -K` +may be called for this effect, if NUT configuration files remain readable +at that point (file systems mostly unmounted or changed to read-only). ++ +Historically it was often `/etc/killpower` but nowadays you may want it +in a temporary filesystem (e.g. under `/run` or `/run/nut` location). ++ +Note that double backslashes must be used for Windows paths, e.g. +`C:\\Temp\\killpower` (modern Windows may also accept forward slashes +like `C:/Temp/killpower` but YMMV). + This is done to forcibly reset the secondary systems, so they don't get stuck at the "halted" stage even if the power returns during the shutdown process. This usually does not work well on contact-closure UPSes that use the genericups driver. + +WARNING: The `upsmon` binary program does not have a built-in default, +so this setting MUST be specified in the configuration, in order for the +late shutdown integration to work on the particular primary-mode system! ++ See the config-notes.txt file in the docs subdirectory for more information. Refer to the section: [[UPS_shutdown]] "Configuring automatic shutdowns for low battery events", @@ -353,6 +382,26 @@ NOTE: so far we support the device reporting an "OFF" state which usually means completely un-powering the load; a bug-tracker issue was logged to design similar support for just some manageable outlets or outlet groups. +*OBLBDURATION* 'seconds':: + +NUT normally raises alarms for immediate shutdown (FSD) for consumers of an +UPS known to be on battery ("OB") and achieving the low battery status ("LB"), +if that is their last remaining power source to satisfy their `MINSUPPLIES` +setting. In some special cases, users may want to delay raising the alarm +(using the `OBLBDURATION` option) at their discretion and risk of an ungraceful +shutdown. ++ +A positive value puts "OB LB" state into effect only if it persists for this +many seconds. A non-positive value makes the FSD effect of detected "OB LB" +state immediate. Built-in default value is 0 (seconds). ++ +NOTE: If both `OBLBDURATION` and `HOSTSYNC` options are set on the same +(secondary) `upsmon` client system, and `HOSTSYNC` is shorter, it would be +effectively ignored: `upsmon` would wait for up to `OBLBDURATION` seconds +for the "OB LB" state to clear, and then the secondary client logic would +fall through to immediate shutdown. If the primary system issues an FSD on +this UPS, that would take an even higher-priority effect as soon as seen. + *RBWARNTIME* 'seconds':: When a UPS says that it needs to have its battery replaced, upsmon will diff --git a/docs/man/upsmon.txt b/docs/man/upsmon.txt index afe4ad6692..ce18f6228e 100644 --- a/docs/man/upsmon.txt +++ b/docs/man/upsmon.txt @@ -178,6 +178,13 @@ UPS on bypass (powered, not protecting). *NOTBYPASS*:: UPS no longer on bypass. +*SUSPEND_STARTING*:: +OS is entering sleep/suspend/hibernate mode. + +*SUSPEND_FINISHED*:: +OS just finished sleep/suspend/hibernate mode, de-activating +obsolete UPS readings to avoid an unfortunate shutdown. + NOTIFY COMMAND -------------- diff --git a/docs/man/upssched.conf.txt b/docs/man/upssched.conf.txt index f6ea00e457..d6566c6b32 100644 --- a/docs/man/upssched.conf.txt +++ b/docs/man/upssched.conf.txt @@ -12,6 +12,12 @@ DESCRIPTION This file controls the operations of linkman:upssched[8], the timer-based helper program for linkman:upsmon[8]. +IMPORTANT NOTES +--------------- + +* Contents of this file should be pure ASCII (character codes + not in range would be ignored with a warning message). + CONFIGURATION DIRECTIVES ------------------------ diff --git a/docs/man/upsset.conf.txt b/docs/man/upsset.conf.txt index cfb422928d..a9170cde2c 100644 --- a/docs/man/upsset.conf.txt +++ b/docs/man/upsset.conf.txt @@ -13,6 +13,12 @@ This file only does one job--it lets you convince linkman:upsset.cgi[8] that your system's CGI directory is secure. The program will not run until this file has been properly defined. +IMPORTANT NOTES +--------------- + +* Contents of this file should be pure ASCII (character codes + not in range would be ignored with a warning message). + SECURITY REQUIREMENTS --------------------- diff --git a/docs/man/usbhid-ups.txt b/docs/man/usbhid-ups.txt index 323fe53deb..12f9703218 100644 --- a/docs/man/usbhid-ups.txt +++ b/docs/man/usbhid-ups.txt @@ -160,6 +160,20 @@ not forcing it to be fully charged all the time. As long as the current value of `battery.charge` remains at or above this threshold percentage (default 100), the `OL+DISCHRG` message logging is not triggered by variations of the charge. +*lbrb_log_delay_sec*='num':: +Set to delay status-setting (and log messages) about device entering `LB` or +`LB+RB` state. ++ +Some APC BXnnnnMI device models or firmware versions (reportedly 2023-2024) +frequently report low battery, replace battery, and all ok within a couple +of seconds, sometimes but not always preceded by OL+DISCHRG (presumably +calibration). This setting lets the driver ignore short-lived states and +only pay attention if they persist longer than this setting (and the device +power state is `OL`). + +*lbrb_log_delay_without_calibrating*:: +Set to apply `lbrb_log_delay_sec` even if device is not calibrating. + *disable_fix_report_desc*:: Set to disable fix-ups for broken USB encoding, etc. which we apply by default on certain models (vendors/products) which were reported as not following the @@ -174,6 +188,15 @@ It is also always possible that NUT fix-ups cause issues on some devices, whether due to NUT bugs or because the vendor protocol implementation is broken in more than one place. +*powercom_sdcmd_byte_order_fallback*:: +Original `PowerCOM HID` subdriver code (until version 0.7) sent UPS `shutdown` +and `stayoff` commands in a wrong byte order, than what is needed by actual +devices seen in the field in 2024. The byte order is fixed to satisfy new +devices by default since version 0.71. Just in case there are different +firmwares out there with opposite behaviors, we provide this toggle to use +old behavior in a particular deployment. Maybe it was just a bug and nobody +needs this fall-back... + *explore*:: With this option, the driver will connect to any device, including ones that are not yet supported. This must always be combined with the diff --git a/docs/net-protocol.txt b/docs/net-protocol.txt index 222180f106..577e29abaa 100644 --- a/docs/net-protocol.txt +++ b/docs/net-protocol.txt @@ -142,7 +142,7 @@ Response: - 'ENUM': an enumerated type, which supports a few specific values - 'STRING:n': this is a string of maximum length n - 'RANGE': this is an numeric, either integer or float, comprised in the -range (see LIST RANGE) + range (see LIST RANGE) - 'NUMBER': this is a simple numeric value, either integer or float ENUM, STRING and RANGE are usually associated with RW, but not always. @@ -153,7 +153,8 @@ float. Note that float values are expressed using decimal (base 10) english-based representation, so using a dot, in non-scientific notation. So hexadecimal, exponents, and comma for thousands separator are forbidden. -For example: "1200.20" is valid, while "1,200.20" and "1200,20" are invalid. +For example: "1200.20" is valid, while "1,200.20" and "1200,20" and "1.2e4" +are invalid. This replaces the old "VARTYPE" command. diff --git a/docs/new-drivers.txt b/docs/new-drivers.txt index 8503a38287..b425a32b56 100644 --- a/docs/new-drivers.txt +++ b/docs/new-drivers.txt @@ -214,7 +214,11 @@ UPS status flags like on line (OL) and on battery (OB) live in ups.status. Don't manipulate this by hand. There are functions which will do this for you. - status_init() -- before doing anything else + status_init() -- before doing anything else (clear internal buffers, + etc.) + + status_get(val) -- optionally check if a status word had been set + since the most-recent status_init() status_set(val) -- add a status word (OB, OL, etc) diff --git a/docs/nut-names.txt b/docs/nut-names.txt index 2c48c1959b..2149fccdbc 100644 --- a/docs/nut-names.txt +++ b/docs/nut-names.txt @@ -273,6 +273,9 @@ input: Incoming line/power information | input.current.low.critical | Low critical threshold (A) | 2 | input.current.high.warning | High warning threshold (A) | 10 | input.current.high.critical | High critical threshold (A) | 12 +| input.feed.color | Color of the input feed + (opaque string) | 3831236 +| input.feed.desc | Description of the input feed | Feed A | input.frequency | Input line frequency (Hz) | 60.00 | input.frequency.nominal | Nominal input line frequency (Hz) | 60 @@ -543,16 +546,28 @@ above in the Time and Date format chapter. ambient: Conditions from external probe equipment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -NOTE: multiple sensors can be exposed using the indexed notation. -'ambient.*', without index or using '0', relates to the embedded sensor. -For example: 'ambient.temperature' represent the embedded sensor temperature. -Other sensors (external, communication card, ...) can use indexes -from '1' to 'n'. For example: 'ambient.1.temperature' for the first external -sensor temperature. +NOTE: *n* stands for the sensors index. A special case is "ambient.0" which is +equivalent to "ambient" (without index), and represents the default sensor of +the device. This is not to be confused with the device embedded sensor, which +is published as 'ups.temperature'. The most important data is "ambient.count", +used to iterate over the whole set of outlets. +For more information, refer to the NUT sensors management notes chapter +of the user manual. [options="header"] |================================================================================== | Name | Description | Example value +| ambient.count | Total number of sensors | 2 +| ambient.n.name | Ambient sensor name | sensor 1 +| ambient.n.id | Ambient sensor identifier + (opaque string) | 80f09325-2838-5637-b62a-cef9cbe2747 +| ambient.n.address | Ambient sensor address + (opaque string) | 1 +| ambient.n.parent.serial | Ambient sensor parent serial number + (opaque string) | U603E34000 +| ambient.n.mfr | Ambient sensor manufacturer | EATON +| ambient.n.model | Ambient sensor model | EMPDT1H1C2 +| ambient.n.firmware | Ambient sensor firmware | 01.03.0011 | ambient.n.present | Ambient sensor presence | yes | ambient.n.temperature | Ambient temperature (degrees C) | 25.40 @@ -600,8 +615,16 @@ sensor temperature. seen (percent) | 13 | ambient.n.contacts.x.status | State of the dry contact sensor x | open +| ambient.n.contacts.x.config | Configuration of the dry + contact sensor x | normal-open +| ambient.n.contacts.x.name | Name of the dry contact + sensor x | smoke-detector1 |================================================================================== +NOTE: +- ambient.n.contacts.x.status may either be the raw status ('open' or 'closed'), +or may relate to ambient.n.contacts.x.config. In this case, the value can be +'active' or 'inactive'. outlet: Smart outlet management ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -685,8 +708,9 @@ outlet collection apply to outlet.group, especially for the indexing 'n' and "outlet.group.count". Most of the data published for outlets also apply to outlet.group, -including: id, name (similar as outlet "desc"), status, current and -voltage (including status, alarm and thresholds). +including: id, name (similar as outlet "desc"), color, status, current and +voltage (including status, alarm and thresholds). Other actions and settings +also apply ({delay,timer}.{shutdown,start}) Some specific data to outlet groups exists: @@ -700,6 +724,8 @@ Some specific data to outlet groups exists: | outlet.group.n.phase | Electrical phase to which the physical outlet group (Gang) is connected to | L1 +| outlet.group.n.input | Input to which an outlet group + is connected | 1 |================================================================================= Example: diff --git a/docs/nut.dict b/docs/nut.dict index 96b530f9c3..46a395154f 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3468 utf-8 +personal_ws-1.1 en 3215 utf-8 AAC AAS ABI @@ -8,10 +8,10 @@ ACK ACM ACPI ACPresent -ACrms ADDR ADDRCONFIG ADDRINFO +ADDRLEN ADELSYSTEM ADK ADKK @@ -38,8 +38,6 @@ ATT ATTRS ATX ATs -AUTOCOMMIT -AUTOPUSH AVL AVR AVRLCD @@ -50,17 +48,11 @@ ActivePower AdMiN Affero Agrain -AlarmStatus -AlarmsHelp Albanese -Alcatel Alexey AllowOverride Alm -AlmCPol -AlmEnbl Amplon -Ampère Andreas Andreassen Andrzej @@ -69,6 +61,7 @@ Antonino Apodaca AppData AppVeyor +ArgumentList Arjen Arkadiusz Armin @@ -80,12 +73,8 @@ Asium Ates AudibleAlarmControl AuthConfig -AutoFrq -AutoMsg -AutoRst Autobook Autoconfigure -Autostartup Avocent Axel Axxium @@ -96,7 +85,6 @@ BATTTEMP BATTV BATTVOLT BBBB -BBHyst BCD BCHARGE BCM @@ -108,37 +96,30 @@ BP BQ BRD BTEST -BTIntervl BTS -BTTime BTV BUFRD BUZ +BXnnnnMI BYP BZ BZOFF BZON BackPro BackUPS -BadPW Bads Baggesen Bakos Balker Bartek -BattTstFail BatteryA BatteryB -BaudRt BaulÊ BayTech -BeepTone Belkin's Benedikt Berge Berzonis -BestPort -BiWeekly Bieringer BigServers BlaXwan @@ -164,7 +145,6 @@ CA's CABAC CAs CBI -CBLimit CCC CCCC CDC @@ -176,6 +156,7 @@ CERTIDENT CERTREQUEST CERTVERIF CEST +CHECKPROCNAME CHOST CHRG CL @@ -185,6 +166,7 @@ CLOCAL CMDDESC CMDSCRIPT CN +COLSPAN COMLI COMMBAD COMMFAULT @@ -200,10 +182,8 @@ CPPFLAGS CPUs CRC CREAD -CROSSTALK CSN CSS -CSUM CTB CUDA CVE @@ -215,12 +195,11 @@ Casar CentOS Centralion ChangeLog -ChargdV Chatziathanassiou CheckUPS CheckUPSAvailable Checksum -Chiou +Christoph Chu Cichowski CircleCI @@ -229,14 +208,12 @@ CodingStyle Collver Colombier Comfo -ConITm -ConVTm +CommandLineTools ConfigVoltage ConnectUPS Corbelli Corbolante Coutadeur -CrestF Ctrl Cuvellard Cyber @@ -286,15 +263,15 @@ DTE DTrace DUMPALL DUMPDONE +DUMPSTATUS +DUMPVALUE DWAKE DWITH DX Daniele Dashjr DataRoom -Dbnc Ddtrace -DeepTstFail Defensor DeltaUPSv DesktopFileName @@ -304,7 +281,6 @@ DeviceLogin DeviceLogout Dgtk Dharm -DiSplay Diehl Dietze DigitalOcean @@ -332,6 +308,7 @@ EL ELCD EMI EMP +EMPDT ENDFOR ENV EOC @@ -361,13 +338,8 @@ Elio Elizarov Eltek Emilien -EmmmmDnnnnn Energia EnergySaving -EpbrashcDoegfl -Eqls -EqlzInvl -EqlzTm Erikson Eriksson Evgeny @@ -393,7 +365,6 @@ FREHn FRELn FRU FSP -FTS FTTx FTUPS FV @@ -402,7 +373,6 @@ Faber Fabio Fabrice Fairstone -FaltSens Farkas Feldman Ferrups @@ -411,13 +381,13 @@ Fideltronik Filipozzi Fiskars FlossMetrics +Fontana Forza Fosshost Frama FreeBSD FreeDesktop FreeIPMI -FreqSens Frolov FullLoad Fuß @@ -425,13 +395,12 @@ GC GCCVER GES GETADDRINFO +GETPID GID -GKrellM GND GNUmakefile GObject GPIO -GPIOCHIP GPL GPSER GRs @@ -439,7 +408,6 @@ GTK GUESSTIMATION GUID GUIs -GWD GXE GXT Gabeler @@ -449,7 +417,6 @@ Gandi Gaspar Gathman Gembe -GenTestFail Gert GetRWVars GetUPSCommands @@ -458,23 +425,17 @@ GetUPSNames GetUPSVars Ghali Giese -Gilles GitHub GitHub's -Gnd Gnomovision GnuTLS Goebl -Golang Gomes Goncalves Gordeev Gough -Grafana Grafenthal Gtec -GuardEnd -GuardSt GuideBook Guillen HB @@ -495,13 +456,13 @@ HOSTLINK HOSTSYNC HOWTO HPE -HPS HPUX HREF HUPCL HV HVT HW +HWMON Hajduch Hanno Harnhammar @@ -509,14 +470,10 @@ Havard Heavner Hessenflow HiBox -HiFreq -HighBatt Hirschler Hlavinka Holger -HomeKit Homebrew -Homebridge Hoogervorst Hough Hrusecky @@ -525,7 +482,6 @@ Hurd HÃĨvard IANA IC -ID's IDE IDEN IDEs @@ -546,7 +502,6 @@ INTL INV INVOLT IPAR -IPBX IPC IPM IPP @@ -565,12 +520,7 @@ Innova Integrators IntelCC IntelliJ -InvCDly -InvCPol -InvMin Invensys -InverterV -Invter IoT Ioannou JAWAN @@ -585,7 +535,6 @@ JW Jageson Jarosch Jasuny -JavaScript Javadoc Javascript Jenkinsfile @@ -594,9 +543,9 @@ Jong Joon Jumpered KNutClient -KNutSetting KOLFF KRT +KRTL KRTS KTTS Kain @@ -607,7 +556,6 @@ Kazutoshi Kebo Keor Kersey -KeyClic Kia Kierdelewicz Kirill @@ -622,7 +570,6 @@ Krpec Kubernetes Kx LASTXFER -LBT LCDRM LCDRT LCDRTXL @@ -654,9 +601,9 @@ LOADPCT LOCKFN LOCKNAME LOTRANS +LTS LUA LVM -LVT LYONN Lacerda Lallement @@ -671,20 +618,15 @@ LibLTDL LibUSB LineA LineB -LineSens Lintian ListClients Lite's +LocalIP LogMax LogMin LowBatt -LowFreq -LowRntm -LowRuntime Loyer -Ltr Luca -Lucent Lygre Lynge MANPATH @@ -693,11 +635,11 @@ MAXCONN MAXLINEV MAXPARMAKES MBATTCHG -MCOL MCU MDigest MEC MEGATAEC +MERCHANTABILITY MH MIBs MIMode @@ -707,13 +649,11 @@ MINTIMEL MKDIRPROG MLH MMM -MMMMMMMMMMMMMMM MNU MONITORed MOXA MPSU MQ -MQTT MSI MSII MSIII @@ -733,15 +673,11 @@ Malkiewicz Mandriva Marcio Marek -MariaDB Martinezgarza Martín Marzouk Massimo Matthijs -MaxACVI -MaxACVO -MaxDCV MaxLinear McKinnon Megaline @@ -756,9 +692,6 @@ Micropower Microsol Mikiewicz Milkov -MinACVI -MinACVO -MinDCV MinGW MiniCOL MiniGuard @@ -767,8 +700,6 @@ Moar Modbus ModemManager Modris -MonAMI -MonUPS Monett Morioka Morozov @@ -778,10 +709,8 @@ Mozilla Msg MultiLink Multiplug -Munin MyCompany MyPasSw -MySQL MyState NAK NAS @@ -795,16 +724,10 @@ NHS NMC NMCs NMS -NNNNN -NNNNNNN -NNNNNNNN -NNNNNNNNNN -NNNNNNNNNNNNNNNNNNN NOAUTH NOBROADCAST NOCOMM NOCOMMWARNTIME -NOCONF NOGET NOMBATTV NOMINV @@ -826,8 +749,6 @@ NTP NUT's NUTCONF NUTClient -NUTSRC -NUTServer NVA NX Nadav @@ -841,6 +762,7 @@ NetInvent NetPro NetServer NetUps +NetXML Netman NetworkUPSTools Neus @@ -848,21 +770,16 @@ Niels Niklas Niro Nobreaks -NodeJS Nom -NomDCV -NomVIn -NomVOut NotePad Novell -NrLoBatt NuGet NutException Nxx OAH +OBLBDURATION OBSOLETION OC -ODH OEM OEM'ed OFFDURATION @@ -875,8 +792,6 @@ ONF ONV OOM OSABI -OSC -OSF OSs OUTDIR OUTPUTV @@ -884,7 +799,6 @@ OUTVOLT OV Oden OffTime -OffTmDays Ohloh Oldworld Olli @@ -894,7 +808,6 @@ OmniOS OmniSmart OnLine OnTime -OnTmDays OneAC OpenBSD OpenIndiana @@ -904,7 +817,6 @@ OpenSolaris OpenSource OpenUPS Opengear -Opengear's Opensource Opti OptiUPS @@ -913,7 +825,6 @@ Orvaldi Orzechowski OutletSystem OutputOverload -OvrLds PBT PBTn PBTnn @@ -927,18 +838,15 @@ PEX PFCLCD PGFn PGP -PGRn PHP PHVn PINGs -PINN PIPEFN PLD PLL PLVn POLLFAIL POLLFREQALERT -POMode POSIX POWERDOWNFLAG POWEREX @@ -949,7 +857,6 @@ PPD PPDn PPDnnn PPP -PPPPPPPPPP PR PR'ed PROGRA @@ -958,17 +865,17 @@ PROTVER PRs PSA PSD +PSF PSFn PSGPSER -PSKxn PSSENTR PSUs PSW PSX +PSZ PThreads PULS PV -PWLv PWR PXG PYTHONPATH @@ -985,7 +892,6 @@ Petri Petter Pezzini Phasak -PhaseWin PhoenixContact PhoenixTec Phoenixtec @@ -1000,11 +906,9 @@ Potrans Poush PowerAlert PowerCOM -PowerChute PowerCom PowerES PowerKinetics -PowerMIB PowerMac PowerMan PowerManagerII @@ -1016,12 +920,12 @@ PowerPS PowerPal PowerPanel PowerShare +PowerShell PowerShield PowerSure PowerTech PowerTrust PowerVS -PowerVault PowerWalker PowerWare Powerchute @@ -1032,15 +936,13 @@ Prachi Prereqs PresentStatus Priv -Procomm ProductID Progra +ProgramFiles Proxmox Prynych Pulizzi -PwrOut PyDOC -PyGTK PyNUT PyNUTClient PyNUTError @@ -1068,25 +970,24 @@ QPD QQ QQQ QRI -QSKTn -QSKn QVFW QWS QinHeng Quette -RAIDiator RBWARNTIME RDLCK RDNT RDWR README +REALPATH REDi REFREPO +REPLACEBATT REPLBATT REQSSL -RESPIN RETPCT REXX +RISC RK RMCARD RMCPplus @@ -1108,15 +1009,13 @@ RWD Rackmount Radek RatedVA -RatedWatts -ReadyNAS RealSmart RedHat Redhat -RefNom Regados Reinholdtsen Remi +RemoteIP Remoting Rene RenÊ @@ -1128,17 +1027,15 @@ Rickard Ridgway Riihikallio Rik -RntmK Rocketfish Rodrigues Rodríguez Rouben Rozman Rucelf +RunAs RunUPSCommand -RuntimeWarning RxD -RxHs Ryabov SAI SASU @@ -1149,7 +1046,6 @@ SDA SDE SDFLAG SDR -SDRnnnnn SDT SELFTEST SELinux @@ -1161,7 +1057,6 @@ SETFL SETINFOs SETLK SFE -SFTWTMS SG SGI SHA @@ -1178,8 +1073,6 @@ SIGTERM SIGURG SIGUSR SIGWINCH -SKOFFn -SKONn SKP SKU SL @@ -1197,7 +1090,6 @@ SOCKADDR SOCKLEN SOFF SOLA -SOLA's SOMECO SOURCEMODE SOV @@ -1216,6 +1108,7 @@ STI STIME STO STP +STRARG SUNWlibusbugen SUNWugen SUNWusb @@ -1250,16 +1143,11 @@ SetRWVar Shara ShareAlike Shaul -ShdnDbnc -ShdnDly -ShdnPol ShutDown -Shutdn Sibbald Sicon Sidorov SigLevel -Signetic Silvino Sinline Sistem @@ -1267,7 +1155,6 @@ Sistemas SlackPack Slackware SmartBoost -SmartCell SmartNUT SmartOnline SmartPro @@ -1294,6 +1181,7 @@ Stanislav StatePath Stefano Stimits +StrangeOS SuSE Suatoni Sublicensing @@ -1304,8 +1192,7 @@ Sy Sycon Symmetra Symmetras -Synology -SysHrs +SyntaxWarning Sysgration SyslogIdentifier SystemIO @@ -1319,6 +1206,7 @@ TCIOFLUSH TCSANOW TEMPC TEMPF +TESTPASS TGS TIMELEFT TIOCM @@ -1337,19 +1225,13 @@ TTT TXF TXG TXV -TXVxx -TapSwDly -TapSwPh Tchakhmakhtchian TcpClient Technic Technorama Tecnoware Tefft -TeleCom Telecom -Televideo -TeraStation Teurlings Thanos Thecus @@ -1357,12 +1239,10 @@ Theodor Thierry Tigra Tnn -ToddGreenfield Tomek Toolset TopGuard Toth -Traceback TrackingID TrackingResult Tripp @@ -1370,7 +1250,6 @@ TrippLite Tru Tx TxD -TxHS UB UBD UBR @@ -1392,6 +1271,7 @@ UPS's UPSCONN UPSDESC UPSHOST +UPSIC UPSIMAGEPATH UPSLC UPSNOTIFY @@ -1406,7 +1286,6 @@ UPScode UPSes UPSilon UPSmart -UPSmon UPSonic UPSs UPStation @@ -1416,7 +1295,6 @@ USBDEVFS USBDevice USERADDed USV -USVs UTC UTalk UUU @@ -1433,7 +1311,6 @@ V'ger VALIGN VARDESC VARTYPE -VAout VER VERFW VFI @@ -1458,6 +1335,7 @@ Viewpower Viewsonic Viktor VirCIO +Visench Vout Vultech VÃĄclav @@ -1478,6 +1356,7 @@ WTU Waldie WantedBy WatchdogSec +Waveshare WebFreak Werror Weverything @@ -1490,11 +1369,11 @@ WinNUT WinPower Wireshark Wl -WordFmt Wrede XAU XC XCP +XCode XLA XOFF XON @@ -1509,17 +1388,13 @@ XXXX XXXXXXXXXXXX XYZ Xfer -XferDly -XfmrRes Xlinker Xpert Xups -Xymon YQ YV YY YYYY -YYYYMMDD YZ Ygor Yifeng @@ -1528,7 +1403,6 @@ Yukai Yunto ZFS ZProject -ZZZZZZZZ Zaika Zampieri Zawadzki @@ -1537,17 +1411,10 @@ abandonware abcd ablerex abuild -acVoltsIn -acVoltsout -acampsiOut -acampsiout accessmode acl acm acpi -acquisited -acvoltsin -acvoltsout acx adb addcmd @@ -1563,20 +1430,16 @@ adm admin's adminbox adoc +advfirewall advorder ae aec af aggregator ai +aix al ala -alarmcenables -alarmconpolarity -alarmlog -alarmshelp -alarmstatus -alarmtest alertset alioth alist @@ -1590,7 +1453,6 @@ altroots amd anded antivirus -aod aon ap apc @@ -1637,21 +1499,17 @@ authPassword authPriv authProtocol authType -autoadjust autoboot autoconf autodetect autodetected autodetection -autofrequency autogen automagically automake -automessage autopoint autoreconf autorestart -autosaving autoscan autostart autotools @@ -1673,7 +1531,6 @@ backport backported backports backupspro -badpassword bart baseurl bashrc @@ -1685,18 +1542,15 @@ battext battlow battnumb battpacks -batttestinterval -batttesttime battvoltmult battvolts baudrate baytech baz -bbh bcdDevice +bcm bcmxcp bd -beepertone belkin belkinunv bestfcom @@ -1718,24 +1572,21 @@ bitmapped bitmask bitness bitnesses -bitwise bn +bool boolean -boostvoolts bootable bp br brazil +brcm brotli bsd -bsv bt bti btn btnG btt -buckboosthyst -buckvolts buf buflen bugfix @@ -1747,20 +1598,19 @@ bullseye busport busybox bv -bypassvolts -byv cStandard cablepower calloc cb +cbe cbi cbl -cblimit ccache cd cdc cdf cee +cef centos cerr certfile @@ -1769,14 +1619,6 @@ certpasswd certpath certutil certverify -cfacaod -cfacaon -cfacvid -cfacvin -cfacvod -cfacvon -cfdcvd -cfdcvn cfg cfgadm cflag @@ -1786,8 +1628,6 @@ cgi cgipath cgroup cgroupsv -chargedvbattery -chargermode chargetime charset checksum @@ -1798,7 +1638,6 @@ chkconfig chmod chown chr -christoph chroot chrooted chrooting @@ -1810,10 +1649,6 @@ ci cidr cis clav -clearalarms -clearhistory -clearlogs -clearpassword clepple clicky cls @@ -1830,10 +1665,8 @@ codebase codepath coldstarts collectd -colspan command's commandlen -commentsMap committer compat compilerPath @@ -1845,14 +1678,8 @@ configurationProvider configureaz configureaza confpath -consolecontrol const -constantitime -constantvtime -constatus -contdisplay contrib -contstatus copyrightable coreutils cout @@ -1878,6 +1705,7 @@ crw crypto csh cshdelay +csi css cstdint ctime @@ -1886,6 +1714,7 @@ cts ctypes cua cuaa +curr customizations cvt cwd @@ -1905,8 +1734,6 @@ datagrams dataok datasheet datastale -dayofweek -dbgsym dblatex dcd dcn @@ -1944,9 +1771,7 @@ devscan dfl dhcp dialout -difftool diffutils -dipsw dir disassembly discardable @@ -1964,7 +1789,7 @@ dnf dnl dnsmasq docbook -dockapp +docdir docs dod domxml @@ -1982,13 +1807,20 @@ drvpath drwxr drwxrwx ds +dsi dsr dsssl dstate dt +dtb +dtbo +dtc dtds dtksh +dtoverlay +dtparam dtr +dts du dumbterm dummycons @@ -2001,7 +1833,6 @@ ePDU ePDUs eaton ec -echoback eco edb editorconfig @@ -2028,14 +1859,7 @@ envvar envvars ep epdu -epodebounce -epodelay -epop -epopolarity eq -equalizeinterval -equalizetime -equalizevolts errno esac esupssmart @@ -2049,10 +1873,10 @@ everyone's everything's evilhack exe +execfuse executables executeCommand execve -extendedhistory extern externalConsole extradata @@ -2060,11 +1884,11 @@ fabula facto fallthrough fatalx -faultsensitivity fc fcb fcntl fd +fdX fds fe featureReport @@ -2073,33 +1897,26 @@ fentonups fffdddxxx ffff fi -fieldset fightwarn -figlineint -figoffline -figonline filename filenames filesystem filesystems firewalling firmwares -flts fmt fno fontconfig -footnoteref +fopen forceshutdown forcessl formatconfig -formatparam formatstring fosshost fp freebsd freeipmi freetype -freqsensitivity frob frontends fs @@ -2108,7 +1925,6 @@ fsr fstab ftdi fuji -fullload func gamatronic gandi @@ -2148,6 +1964,7 @@ gitk gitlab gmail gmake +gmtime gnuplot gnutls google @@ -2162,8 +1979,6 @@ graphviz groupadd groupname gtk -guardpend -guardpstart guesstimate guesstimation guez @@ -2177,6 +1992,7 @@ hasFeature hb hcd hcl +hexnum hg hh hibernate's @@ -2185,8 +2001,6 @@ hidparser hidraw hidtypes hidups -highbattery -highfrequency homebrew hoster hostname @@ -2196,6 +2010,7 @@ hotplug hotplugging hovercharge hpe +hpux href htaccess html @@ -2206,15 +2021,18 @@ https huawei hunnox hwdb +hwmon +hwmonX hypervisor hypervisors iBox +iDialog iDowell iManufacturer +iPlug iSerial iUSB ib -ibattery icd iconv icp @@ -2222,13 +2040,10 @@ icu idProduct idVendor ident -identifymessage idleload -idm ie ietf ifdef -ifeval ifndef ignoreFailures ignorelb @@ -2240,6 +2055,7 @@ im imagesdir img imv +ina includePath includedir inductor @@ -2260,7 +2076,6 @@ innotech inode inplace installable -installcheck installpkg installurl instcmd @@ -2277,12 +2092,7 @@ interprocess interruptonly interruptsize intltool -invcontdelay -invcontpolarity inverter -inverterlog -inverterminutes -invertervolts io ioLogik ioLogikE @@ -2300,11 +2110,14 @@ ipp ippon ipv ipxe +irl +irliter isDefault isbmex ish iso isolator +iter ivtscd jNUT jNut @@ -2317,17 +2130,13 @@ jimklimov journalctl jpeg jpg -jpgraph jre json -jumboinfo -jumbotron kVA kadets kaminski kde keychain -keyclick keygen keyout keyring @@ -2344,8 +2153,8 @@ kvm labcd lan langid -largp lasaine +lbrb ld ldd le @@ -2356,6 +2165,8 @@ libaugeas libavahi libc libcommon +libcommonclient +libcommonstr libcppunit libcrypto libcurl @@ -2363,6 +2174,7 @@ libdir libdummy libexec libexecdir +libexpat libfreeipmi libgd libgpgme @@ -2376,6 +2188,7 @@ libipmimonitoring libltdl liblzma libmodbus +libname libneon libnetsnmp libnss @@ -2391,6 +2204,7 @@ libpowerman libre libregex libs +libserial libsnmp libssl libsystemd @@ -2412,7 +2226,6 @@ licensors liebert liebertgxt lifecycle -linesensitivity linevoltage linkdoc linksingledoc @@ -2420,34 +2233,31 @@ linux linuxdoc lipo listDeviceClients -listdef littleguy lk lldb llvm lm +lmodbus ln lnetsnmp loadPercentage +localcalculation localhost +localip +localport localtime lockf -lockwashers logfacility logfile login logins logout logrotate -longterm lookup loopback lowbatt lowbattery -lowfrequency -lowruntime -lowvoltsout -lposix lr lregex lsd @@ -2463,7 +2273,6 @@ lxc lxcbr lxccontainer lxcfs -lxml lxyz lz mA @@ -2483,17 +2292,13 @@ manpage manpages masterguard matcher -maxacvi -maxacvo maxd -maxdcv maxlength maxreport maxretry maxstartdelay maxva maxvalue -maxvi maxvo mc mcedit @@ -2502,8 +2307,6 @@ mdadm mecer megatec memset -merchantability -mergetool metadata metasys methodOfFlowControl @@ -2519,18 +2322,13 @@ microdowell microlink microsol middleware -minacvi -minacvo mincharge -mindcv mingw minicol minruntime mins -minutalk minva minvalue -minvi minvo mips mirrorlist @@ -2539,7 +2337,6 @@ misconfigured mkdir mmZ mmap -mmddyyyy mn mockdrv modbus @@ -2548,12 +2345,10 @@ modprobe monmaster monofasico monpasswd -monslave monuser morbo mortem mozilla -mqtt msec msg msgfmt @@ -2582,6 +2377,9 @@ mysecurityname myups myupsname nLogic +nMONITOR +nPOWERDOWNFLAG +nSHUTDOWNCMD nabcd nameserver namespace @@ -2590,15 +2388,15 @@ nanosleep nashkaminski natively nb +nbproject nbr -nbsp nd -ndcv nds -nearlowbattery netcat netclient +netmask netserver +netsh netsnmp netvision networkupstools @@ -2624,7 +2422,6 @@ nobody's nobt nodev nodownload -nodtk noexec noflag nogroup @@ -2632,11 +2429,7 @@ nohang noimp noinst nolock -nomacvoltsin -nomacvoltsout nombattvolt -nomdcvolts -nomfrequency noncommercially noout norating @@ -2654,7 +2447,6 @@ nowait nowarn np nss -ntUPSd nuget num numOfBytesFromUPS @@ -2669,6 +2461,7 @@ nutdevN nutdrv nutmon nutscan +nutshutdown nutsrv nutupsdrv nutvalue @@ -2676,12 +2469,8 @@ nvi nvo nwfilter odette -odt offdelay offsite -offt -offtimedays -oftd oids ok okhlybov @@ -2694,15 +2483,11 @@ omnios onbatt onbattery onbattwarn -onclick ondelay oneac online onlinedischarge ont -ontd -ontimedays -ontiniedays onwards ooce openSUSE @@ -2711,7 +2496,6 @@ openipmi openjdk openlog openmp -opensolaris openssh openssl optimizations @@ -2719,7 +2503,6 @@ optiups oq os ostream -otherprotocols otheruser outliers ovmf @@ -2728,14 +2511,13 @@ pacman pacstrap parallelized param -paramkeywords parsable parseconf +parsers passname passphrase passthrough passwd -passwordlevel pathname pathnames pbzip @@ -2754,7 +2536,6 @@ pfSense pfexec pfy ph -phasewindow phoenixcontact phoenixtec picocom @@ -2772,12 +2553,10 @@ pkgconfig pkgin plaintext plugin -plugnplay pluma pmset pmu png -pnp pollable pollfreq pollinterval @@ -2797,12 +2576,9 @@ powermand powermust powernet poweroff -powerofftime -poweronmode -powerontime -powerouts powerpal powerpanel +powershell powerup powervalue powerware @@ -2831,6 +2607,7 @@ probu proc productid prog +progname prtconf ps psu @@ -2840,9 +2617,8 @@ pts pty pulizzi pw -pwl +pwd pwmib -pwro px pxW pxg @@ -2855,9 +2631,6 @@ pynut qDEB qa qemu -qfs -qgs -qpi qs queequeg quiesce @@ -2865,11 +2638,7 @@ qx qx's qxflags rD -rackmount -raquo raritan -ratedva -ratedwatts rb rcctl readline @@ -2881,34 +2650,31 @@ rebased rebasing rebootdelay rebranded -receivexhs reconnection recv redistributors reentrancy refactored refactoring -referencenominal regex regtype relatime releasekeyring relicensing -remoting +remoteip renderer renderers +repindex repo reportId reposurgeon repotec req resetter -resistive resolv resync ret retrydelay -revdate revnumber rex rexx @@ -2934,13 +2700,13 @@ rqt rsa rsync rts +rtu ru rubygem runlevel runnable runtime runtimecal -runtimek runtimes rva rw @@ -2957,7 +2723,7 @@ screenshot screenshots scriptname sd -sdb +sdcmd sddelay sdk sdl @@ -2973,9 +2739,13 @@ securityLevel securityName sed selftest +semver sendback sendline sendmail +sendsignal +sendsignalfn +sendsignalpid sequentialized ser seria @@ -2988,7 +2758,6 @@ setflags setgid setinfo setpci -setpoint setq setuid setupCommands @@ -2998,21 +2767,18 @@ sfr sgml sgs sha +shellenv shm shutdownArguments shutdowncmd -shutdowndebounce shutdowndelay -shutdownpolarity shutdowntime -shutup si siemens sig sigaction sigmask signedness -simplejson simu sio sitesearch @@ -3020,10 +2786,10 @@ sitop sizeof ske skel +sl slackpkg slaveid slavesync -slewrate slibtool sm smartups @@ -3032,7 +2798,6 @@ sms sn snailmail snmp -snmpagent snmpv snmpwalk snprintf @@ -3042,8 +2807,11 @@ socat sockdebug socketname socomec +solari solaris +solcmn solibs +solint solis somename somepass @@ -3069,7 +2837,6 @@ stan startIP startdelay startup -statefilepath statepath stayoff stderr @@ -3091,7 +2858,9 @@ strcpy strdup strerror strftime +stringify strlen +strncpy strnlen strptime struct @@ -3113,10 +2882,12 @@ sublicense sublicenses submodule submodules +subnet +subnets subtree sudo suid -superset +suppressions suseconds sv svc @@ -3137,6 +2908,7 @@ sysV syscalls sysconfdir sysconfig +sysfs syslog syslogd systemctl @@ -3144,17 +2916,11 @@ systemd systemdshutdowndir systemdsystemunitdir systemdtmpfilesdir -systemhours -systemmode systemtest sysutils sysvinit -tabledef tagname -tapswitchdelay -tapswitchphase targetArchitecture -tbody tcflush tcgetattr tcl @@ -3164,10 +2930,9 @@ tcsetattr tcsh td tdriver -teknologist +telnetlib tempmax tempmin -terminal's termios testime testtime @@ -3178,11 +2943,9 @@ tgz th timeframe timehead -timeline timername timestamp timeticks -tiocm tios tmp tmpfiles @@ -3194,7 +2957,6 @@ toolset topFrame topbot tport -transmitxhs tripplite tripplitesu troff @@ -3253,12 +3015,10 @@ uninstall uninterruptible uniq unistd -unitidentify unix unmapped unmounts unpowered -unshutup unstash updateinfo upexia @@ -3279,7 +3039,6 @@ upscode upscommon upsct upsd -upsd's upsdebug upsdebugx upsdev @@ -3299,7 +3058,6 @@ upslog upslogx upsmon upsmon's -upsmonuser upsname upsonbatt upspass @@ -3323,7 +3081,6 @@ usbhid usbif usbinfo usbmisc -usbscan usbups usbus usd @@ -3359,7 +3116,6 @@ varname varvalue vbatt vc -vectronic vendorid ver verifySourceSig @@ -3377,20 +3133,14 @@ virtualization virtualized vivo vo -vod voltronic -von vscode wDescriptorLength waitbeforereconnect wakeup wc -wchar wdi -webNUT -webnut webserver -websitelayout wf wget whitespace @@ -3403,7 +3153,6 @@ winpthreads wix wmNUT wmnut -wordformat workflow workspace workspaceFolder @@ -3425,11 +3174,9 @@ xcalloc xcode xd xe -xferdelay xff xfff xffff -xfmrresistance xh xhci xhtml diff --git a/docs/sock-protocol.txt b/docs/sock-protocol.txt index bd440cbc18..4442dc2a28 100644 --- a/docs/sock-protocol.txt +++ b/docs/sock-protocol.txt @@ -108,10 +108,14 @@ SETFLAGS Note that this command takes a variable number of arguments, as multiple flags are supported. Also note that they are not crammed together in -"", since "RW STRING" would mean something completely different. +"" quotes, since "RW STRING" would mean something completely different. This also replaces any previous flags for a given variable. +Currently supported flags include `RW`, `STRING` and `NUMBER` +(detailed in the NUT Network Protocol documentation); unrecognized values +are quietly ignored. + ADDCMD ~~~~~~ @@ -126,6 +130,19 @@ DELCMD DELCMD load.on +PID +~~~ + + PID + + PID 12345 + + PID "StrangeOS process identifier" + +Response to `GETPID` query, where we serve platform-specific process +identifier. On POSIX and many other platforms this would be a numeric +value, but most generally it should be treated as an opaque string. + DUMPDONE ~~~~~~~~ @@ -145,6 +162,14 @@ This is sent in response to a PING from the server. It is only used as a sanity check to make sure that the driver has not gotten stuck somewhere. +OK +~~ + + OK Goodbye + +This is sent in response to a LOGOUT from the server (or more likely from +a sibling driver or `upsdrvctl` program). + DATAOK ~~~~~~ @@ -236,6 +261,14 @@ NOTE: TRACKING was set to ON on upsd. In this case, driver will later return the execution status, using TRACKING. +GETPID +~~~~~~ + +The server (or sibling driver instances, or `upsdrvctl` tool) can use +this to request the platform-specific process identifier of the driver +process. On POSIX and many other platforms this would be a numeric value, +but most generally it should be treated as an opaque string. + DUMPALL ~~~~~~~ @@ -250,6 +283,27 @@ The server can tell when it has a full copy of the data by waiting for DUMPDONE. That special response from the driver is sent once the entire set has been transmitted. +DUMPVALUE +~~~~~~~~~ + + DUMPVALUE + + DUMPVALUE driver.version + +Only request the value of specified variable name (and its additional +metadata in other lines), same as when `DUMPALL` iterates all such names. + +The NUT data server or other socket-protocol client should parse the +response line by line, looking for the SETINFO line to get the value; +if a DUMPDONE is seen first, the value was not available in the driver. + +DUMPSTATUS +~~~~~~~~~~ + + DUMPSTATUS + +Effectively an alias to `DUMPVALUE ups.status`. + NOBROADCAST ~~~~~~~~~~~ @@ -266,6 +320,17 @@ numeric argument. Note that initial default is to receive everything, so this command may be useful for connections that disabled broadcasts at some point. +LOGOUT +~~~~~~ + +Primarily used by communications between driver processes and/or `upsdrvctl`, +this command allows clients to gracefully close connection to the NUT driver +which acts as the server on the socket/pipe, avoiding noisy logs about sudden +disconnection. + + LOGOUT + OK Goodbye + Design notes ------------ diff --git a/drivers/Makefile.am b/drivers/Makefile.am index 3eede11ff7..53ff78a3c8 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -3,11 +3,11 @@ # Export certain values for ccache which NUT ci_build.sh can customize, # to facilitate developer iteration re-runs of "make" later. # At least GNU and BSD make implementations are okay with this syntax. -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_DIR=@CCACHE_DIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_PATH=@CCACHE_PATH@ -@NUT_AM_MAKE_CAN_EXPORT@export PATH=@PATH_DURING_CONFIGURE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_NAMESPACE@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_BASEDIR@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_DIR@export CCACHE_DIR=@CCACHE_DIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export CCACHE_PATH=@CCACHE_PATH@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export PATH=@PATH_DURING_CONFIGURE@ # Make sure out-of-dir dependencies exist (especially when dev-building parts): $(top_builddir)/common/libcommon.la \ @@ -57,7 +57,7 @@ SERIAL_DRIVERLIST = al175 bcmxcp belkin belkinunv bestfcom \ gamatronic genericups isbmex liebert liebert-esp2 masterguard metasys \ mge-utalk microdowell microsol-apc mge-shut oneac optiups powercom rhino \ safenet nutdrv_siemens-sitop solis tripplite tripplitesu upscode2 victronups powerpanel \ - blazer_ser ivtscd apcsmart apcsmart-old riello_ser sms_ser + blazer_ser ivtscd apcsmart apcsmart-old riello_ser sms_ser bicker_ser SNMP_DRIVERLIST = snmp-ups USB_LIBUSB_DRIVERLIST = usbhid-ups bcmxcp_usb tripplite_usb \ blazer_usb richcomm_usb riello_usb \ @@ -68,7 +68,7 @@ SERIAL_USB_DRIVERLIST = \ NEONXML_DRIVERLIST = netxml-ups MACOSX_DRIVERLIST = macosx-ups MODBUS_DRIVERLIST = phoenixcontact_modbus generic_modbus huawei-ups2000 socomec_jbus adelsystem_cbi apc_modbus invt_modbus -LINUX_I2C_DRIVERLIST = asem pijuice +LINUX_I2C_DRIVERLIST = asem pijuice hwmon_ina219 POWERMAN_DRIVERLIST = powerman-pdu IPMI_DRIVERLIST = nut-ipmipsu GPIO_DRIVERLIST = generic_gpio_libgpiod @@ -186,6 +186,8 @@ riello_ser_SOURCES = riello.c riello_ser.c riello_ser_LDADD = $(LDADD) -lm sms_ser_SOURCES = sms_ser.c sms_ser_LDADD = $(LDADD) -lm +bicker_ser_SOURCES = bicker_ser.c +bicker_ser_LDADD = $(LDADD) -lm # non-serial drivers: these use custom LDADD and/or CFLAGS @@ -273,9 +275,23 @@ snmp_ups_CFLAGS = $(AM_CFLAGS) snmp_ups_CFLAGS += $(LIBNETSNMP_CFLAGS) snmp_ups_LDADD = $(LDADD_DRIVERS) $(LIBNETSNMP_LIBS) -lm +if WITH_SSL +if !WITH_OPENSSL + snmp_ups_CFLAGS += -UNETSNMP_USE_OPENSSL +endif !WITH_OPENSSL + snmp_ups_CFLAGS += $(LIBSSL_CFLAGS) + snmp_ups_LDADD += $(LIBSSL_LIBS) +endif WITH_SSL + # NEON XML/HTTP netxml_ups_SOURCES = netxml-ups.c mge-xml.c netxml_ups_LDADD = $(LDADD_DRIVERS) $(LIBNEON_LIBS) +netxml_ups_CFLAGS = $(AM_CFLAGS) + +if WITH_SSL + netxml_ups_CFLAGS += $(LIBSSL_CFLAGS) + netxml_ups_LDADD += $(LIBSSL_LIBS) +endif # Powerman powerman_pdu_SOURCES = powerman-pdu.c @@ -334,6 +350,8 @@ asem_LDADD = $(LDADD_DRIVERS) $(LIBI2C_LIBS) asem_SOURCES = asem.c pijuice_LDADD = $(LDADD_DRIVERS) $(LIBI2C_LIBS) pijuice_SOURCES = pijuice.c +hwmon_ina219_LDADD = $(LDADD_DRIVERS) +hwmon_ina219_SOURCES = hwmon_ina219.c # GPIO drivers generic_gpio_libgpiod_SOURCES = generic_gpio_common.c generic_gpio_libgpiod.c @@ -365,7 +383,8 @@ nutdrv_qx_SOURCES += $(NUTDRV_QX_SUBDRIVERS) # tracking (which is automatic), but to ensure these files are # distributed by "make dist". -dist_noinst_HEADERS = apc-mib.h apc-iem-mib.h apc-hid.h arduino-hid.h baytech-mib.h bcmxcp.h bcmxcp_ser.h \ +dist_noinst_HEADERS = \ + apc_modbus.h apc-mib.h apc-iem-mib.h apc-hid.h arduino-hid.h baytech-mib.h bcmxcp.h bcmxcp_ser.h \ bcmxcp_io.h belkin.h belkin-hid.h bestpower-mib.h blazer.h cps-hid.h dstate.h \ dummy-ups.h explore-hid.h gamatronic.h genericups.h \ generic_gpio_common.h generic_gpio_libgpiod.h \ @@ -394,6 +413,7 @@ dist_noinst_HEADERS = apc-mib.h apc-iem-mib.h apc-hid.h arduino-hid.h baytech-mi # for object files. This library is used during the build process, # and is not meant to be installed. EXTRA_LTLIBRARIES = libdummy.la libdummy_serial.la libdummy_upsdrvquery.la + libdummy_la_SOURCES = main.c dstate.c libdummy_la_LDFLAGS = -no-undefined -static libdummy_serial_la_SOURCES = serial.c @@ -409,6 +429,14 @@ libdummy_mockdrv_la_SOURCES = main.c dstate.c libdummy_mockdrv_la_CFLAGS = $(AM_CFLAGS) -DDRIVERS_MAIN_WITHOUT_MAIN=1 libdummy_mockdrv_la_LDFLAGS = -static $(top_builddir)/common/libcommon.la $(top_builddir)/common/libparseconf.la +# Also define a library with serial-port UPS routines needed for nut-scanner +noinst_LTLIBRARIES = libserial-nutscan.la + +libserial_nutscan_la_SOURCES = serial.c bcmxcp_ser.c +libserial_nutscan_la_LDFLAGS = +libserial_nutscan_la_LIBADD = $(SERLIBS) +libserial_nutscan_la_CFLAGS = $(AM_CFLAGS) -I$(top_srcdir)/clients -I$(top_srcdir)/include -I$(top_srcdir)/drivers + dummy: CLEANFILES = $(EXTRA_LTLIBRARIES) $(EXTRA_PROGRAMS) diff --git a/drivers/adelsystem_cbi.c b/drivers/adelsystem_cbi.c index 3f2135b932..6c4d7df174 100644 --- a/drivers/adelsystem_cbi.c +++ b/drivers/adelsystem_cbi.c @@ -30,7 +30,7 @@ #include #define DRIVER_NAME "NUT ADELSYSTEM DC-UPS CB/CBI driver" -#define DRIVER_VERSION "0.02" +#define DRIVER_VERSION "0.03" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ diff --git a/drivers/al175.c b/drivers/al175.c index 6ed311a8dc..fe01763492 100644 --- a/drivers/al175.c +++ b/drivers/al175.c @@ -52,7 +52,7 @@ typedef uint8_t byte_t; #define DRIVER_NAME "Eltek AL175/COMLI driver" -#define DRIVER_VERSION "0.14" +#define DRIVER_VERSION "0.15" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/apc-mib.c b/drivers/apc-mib.c index 1c6c575ac6..78924e31c3 100644 --- a/drivers/apc-mib.c +++ b/drivers/apc-mib.c @@ -164,9 +164,9 @@ static info_lkp_t apcc_transfer_reasons[] = { #define APCC_REBOOT_DO 2 #define APCC_REBOOT_GRACEFUL 3 #if 0 /* not used. */ - #define APCC_OID_SLEEP ".1.3.6.1.4.1.318.1.1.1.6.2.3" - #define APCC_SLEEP_ON "2" - #define APCC_SLEEP_GRACEFUL "3" +# define APCC_OID_SLEEP ".1.3.6.1.4.1.318.1.1.1.6.2.3" +# define APCC_SLEEP_ON "2" +# define APCC_SLEEP_GRACEFUL "3" #endif diff --git a/drivers/apc_modbus.c b/drivers/apc_modbus.c index 7e437e2fce..4f7cc9fd02 100644 --- a/drivers/apc_modbus.c +++ b/drivers/apc_modbus.c @@ -17,13 +17,16 @@ */ #include "main.h" + #if defined NUT_MODBUS_HAS_USB -#include "nut_libusb.h" -#include "libhid.h" -#include "hidparser.h" +# include "nut_libusb.h" +# include "libhid.h" +# include "hidparser.h" #endif /* defined NUT_MODBUS_HAS_USB */ + #include "timehead.h" #include "nut_stdint.h" +#include "apc_modbus.h" #include #include @@ -31,7 +34,7 @@ #include #define DRIVER_NAME "NUT APC Modbus driver" -#define DRIVER_VERSION "0.01" +#define DRIVER_VERSION "0.10" #if defined NUT_MODBUS_HAS_USB @@ -89,23 +92,30 @@ upsdrv_info_t upsdrv_info = { }; typedef enum { - APC_MODBUS_VALUE_TYPE_INT = 0, - APC_MODBUS_VALUE_TYPE_UINT, - APC_MODBUS_VALUE_TYPE_STRING + APC_VT_INT = 0, + APC_VT_UINT, + APC_VT_STRING } apc_modbus_value_types; -static const apc_modbus_value_types apc_modbus_value_types_max = APC_MODBUS_VALUE_TYPE_STRING; +typedef enum { + APC_VF_NONE = 0, + APC_VF_RW = (1 << 0) +} apc_modbus_value_flags; + +static const apc_modbus_value_types apc_modbus_value_types_max = APC_VT_STRING; + +typedef union { + int64_t int_value; + uint64_t uint_value; + char *string_value; +} apc_modbus_value_data_t; typedef struct { apc_modbus_value_types type; const char *format; int scale; void *variable_ptr; - union { - int64_t int_value; - uint64_t uint_value; - char *string_value; - } data; + apc_modbus_value_data_t data; } apc_modbus_value_t; typedef struct { @@ -178,6 +188,37 @@ static int _apc_modbus_to_string(const uint16_t *value, const size_t value_len, return 1; } +static int _apc_modbus_from_string(const char *value, uint16_t *output, size_t output_len) +{ + size_t value_len, vi, oi; + uint16_t tmp; + + if (value == NULL || output == NULL) { + /* Invalid parameters */ + return 0; + } + + value_len = strlen(value); + + if (value_len > (output_len * sizeof(uint16_t))) { + /* Output buffer too small */ + return 0; + } + + for (vi = 0, oi = 0; vi < value_len && oi < output_len; vi += 2, oi++) { + tmp = value[vi] << 8; + if (vi + 1 < value_len) + tmp |= value[vi + 1]; + output[oi] = tmp; + } + + for (; oi < output_len; oi++) { + output[oi] = 0; + } + + return 1; +} + /* Converts a Modbus integer to a uint64_t. * See MPAO-98KJ7F_EN section 1.3.1 Numbers. */ static int _apc_modbus_to_uint64(const uint16_t *value, const size_t value_len, uint64_t *output) @@ -197,6 +238,51 @@ static int _apc_modbus_to_uint64(const uint16_t *value, const size_t value_len, return 1; } +static int _apc_modbus_from_uint64(uint64_t value, uint16_t *output, size_t output_len) +{ + ssize_t oi; + size_t bits; + + if (output == NULL) { + /* Invalid parameters */ + return 0; + } + + bits = output_len * sizeof(uint16_t) * 8; + if (bits < 64) { + if (value > ((1ULL << bits) - 1)) { + /* Overflow */ + return 0; + } + } + + for (oi = output_len - 1; oi >= 0; oi--) { + output[oi] = (uint16_t)(value & 0xFFFF); + value >>= 16; + } + + return 1; +} + +static int _apc_modbus_from_uint64_string(const char *value, uint16_t *output, size_t output_len) +{ + uint64_t value_uint; + char *endptr; + + if (value == NULL || output == NULL) { + /* Invalid parameters */ + return 0; + } + + errno = 0; + value_uint = strtoull(value, &endptr, 0); + if (endptr == value || *endptr != '\0' || errno > 0) { + return 0; + } + + return _apc_modbus_from_uint64(value_uint, output, output_len); +} + static int _apc_modbus_to_int64(const uint16_t *value, const size_t value_len, int64_t *output) { size_t shiftval; @@ -212,6 +298,50 @@ static int _apc_modbus_to_int64(const uint16_t *value, const size_t value_len, i return 1; } +static int _apc_modbus_from_int64(int64_t value, uint16_t *output, size_t output_len) +{ + ssize_t oi; + size_t bits; + + if (output == NULL) { + /* Invalid parameters */ + return 0; + } + + bits = output_len * sizeof(uint16_t) * 8; + if (value > ((1LL << (bits - 1)) - 1) || + value < -(1LL << (bits - 1))) { + /* Overflow */ + return 0; + } + + for (oi = output_len - 1; oi >= 0; oi--) { + output[oi] = (uint16_t)(value & 0xFFFF); + value >>= 16; + } + + return 1; +} + +static int _apc_modbus_from_int64_string(const char *value, uint16_t *output, size_t output_len) +{ + int64_t value_int; + char *endptr; + + if (value == NULL || output == NULL) { + /* Invalid parameters */ + return 0; + } + + errno = 0; + value_int = strtoll(value, &endptr, 0); + if (endptr == value || *endptr != '\0' || errno > 0) { + return 0; + } + + return _apc_modbus_from_int64(value_int, output, output_len); +} + static int _apc_modbus_to_double(const apc_modbus_value_t *value, double *output) { int factor; @@ -225,14 +355,38 @@ static int _apc_modbus_to_double(const apc_modbus_value_t *value, double *output assert(value->type <= apc_modbus_value_types_max); switch (value->type) { - case APC_MODBUS_VALUE_TYPE_INT: + case APC_VT_INT: *output = (double)value->data.int_value / factor; break; - case APC_MODBUS_VALUE_TYPE_UINT: + case APC_VT_UINT: *output = (double)value->data.uint_value / factor; break; - case APC_MODBUS_VALUE_TYPE_STRING: + case APC_VT_STRING: return 0; +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } return 1; @@ -332,7 +486,7 @@ static int _apc_modbus_voltage_to_nut(const apc_modbus_value_t *value, char *out return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_UINT) { + if (value->type != APC_VT_UINT) { return 0; } @@ -360,7 +514,7 @@ static int _apc_modbus_efficiency_to_nut(const apc_modbus_value_t *value, char * return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_INT) { + if (value->type != APC_VT_INT) { return 0; } @@ -413,7 +567,7 @@ static int _apc_modbus_status_change_cause_to_nut(const apc_modbus_value_t *valu return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_UINT) { + if (value->type != APC_VT_UINT) { return 0; } @@ -526,35 +680,150 @@ static int _apc_modbus_status_change_cause_to_nut(const apc_modbus_value_t *valu static apc_modbus_converter_t _apc_modbus_status_change_cause_conversion = { _apc_modbus_status_change_cause_to_nut, NULL }; +static int _apc_modbus_string_join(const char *values[], size_t values_len, const char *separator, char *output, size_t output_len) +{ + size_t i; + size_t output_idx; + int res; + + if (values == NULL || values_len == 0 || separator == NULL || output == NULL || output_len == 0) { + /* Invalid parameters */ + return 0; + } + + output_idx = 0; + + for (i = 0; i < values_len && output_idx < output_len; i++) { + if (values[i] == NULL) + continue; + + if (i == 0) { + res = snprintf(output + output_idx, output_len - output_idx, "%s", values[i]); + } else { + res = snprintf(output + output_idx, output_len - output_idx, "%s%s", separator, values[i]); + } + + if (res < 0 || (size_t)res >= output_len) { + return 0; + } + + output_idx += res; + } + + return 1; +} + +static int _apc_modbus_battery_test_status_to_nut(const apc_modbus_value_t *value, char *output, size_t output_len) +{ + const char *result, *source, *modifier; + const char *values[3]; + + if (value == NULL || output == NULL || output_len == 0) { + /* Invalid parameters */ + return 0; + } + + if (value->type != APC_VT_UINT) { + return 0; + } + + result = NULL; + if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PENDING)) { + result = "Pending"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_INPROGRESS)) { + result = "InProgress"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PASSED)) { + result = "Passed"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_FAILED)) { + result = "Failed"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_REFUSED)) { + result = "Refused"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_ABORTED)) { + result = "Aborted"; + } + + source = NULL; + if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_PROTOCOL)) { + source = "Source: Protocol"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_LOCALUI)) { + source = "Source: LocalUI"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_INTERNAL)) { + source = "Source: Internal"; + } + + modifier = NULL; + if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INVALIDSTATE)) { + modifier = "Modifier: InvalidState"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INTERNALFAULT)) { + modifier = "Modifier: InternalFault"; + } else if ((value->data.uint_value & APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_STATEOFCHARGENOTACCEPTABLE)) { + modifier = "Modifier: StateOfChargeNotAcceptable"; + } + + values[0] = result; + values[1] = source; + values[2] = modifier; + return _apc_modbus_string_join(values, SIZEOF_ARRAY(values), ", ", output, output_len); +} + +static apc_modbus_converter_t _apc_modbus_battery_test_status_conversion = { _apc_modbus_battery_test_status_to_nut, NULL }; + +static const time_t apc_date_start_offset = 946684800; /* 2000-01-01 00:00 */ + static int _apc_modbus_date_to_nut(const apc_modbus_value_t *value, char *output, size_t output_len) { - struct tm *tm_info; + struct tm tm_info; time_t time_stamp; - const time_t start_offset = 946684800; /* 2000-01-01 00:00 */ if (value == NULL || output == NULL || output_len == 0) { /* Invalid parameters */ return 0; } - if (value->type != APC_MODBUS_VALUE_TYPE_UINT) { + if (value->type != APC_VT_UINT) { return 0; } - time_stamp = ((int64_t)value->data.uint_value * 86400) + start_offset; - tm_info = gmtime(&time_stamp); - strftime(output, output_len, "%Y-%m-%d", tm_info); + time_stamp = ((int64_t)value->data.uint_value * 86400) + apc_date_start_offset; + gmtime_r(&time_stamp, &tm_info); + strftime(output, output_len, "%Y-%m-%d", &tm_info); return 1; } -static apc_modbus_converter_t _apc_modbus_date_conversion = { _apc_modbus_date_to_nut, NULL }; +static int _apc_modbus_date_from_nut(const char *value, uint16_t *output, size_t output_len) +{ + struct tm tm_struct; + time_t epoch_time; + uint64_t uint_value; + + if (value == NULL || output == NULL || output_len == 0) { + /* Invalid parameters */ + return 0; + } + + memset(&tm_struct, 0, sizeof(tm_struct)); + if (strptime(value, "%Y-%m-%d", &tm_struct) == NULL) { + return 0; + } + + if ((epoch_time = timegm(&tm_struct)) == -1) { + return 0; + } + + uint_value = (epoch_time - apc_date_start_offset) / 86400; + + return _apc_modbus_from_uint64(uint_value, output, output_len); +} + +static apc_modbus_converter_t _apc_modbus_date_conversion = { _apc_modbus_date_to_nut, _apc_modbus_date_from_nut }; typedef struct { const char *nut_variable_name; size_t modbus_addr; size_t modbus_len; /* Number of uint16_t registers */ apc_modbus_value_types value_type; + apc_modbus_value_flags value_flags; apc_modbus_converter_t *value_converter; const char *value_format; int value_scale; @@ -563,50 +832,74 @@ typedef struct { /* Values that only need to be updated once on startup */ static apc_modbus_register_t apc_modbus_register_map_inventory[] = { - { "ups.firmware", 516, 8, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, - { "ups.model", 532, 16, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, /* also device.model, filled automatically */ - { "ups.serial", 564, 8, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, /* also device.serial, filled automatically */ - { "ups.power.nominal", 588, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.0f", 0, &power_nominal }, - { "ups.realpower.nominal", 589, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.0f", 0, &realpower_nominal }, - { "ups.mfr.date", 591, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_date_conversion, NULL, 0, NULL }, - { "battery.date", 595, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_date_conversion, NULL, 0, NULL }, - { "ups.id", 596, 8, APC_MODBUS_VALUE_TYPE_STRING, NULL, "%s", 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "ups.firmware", 516, 8, APC_VT_STRING, 0, NULL, "%s", 0, NULL }, + { "ups.model", 532, 16, APC_VT_STRING, 0, NULL, "%s", 0, NULL }, /* also device.model, filled automatically */ + { "ups.serial", 564, 8, APC_VT_STRING, 0, NULL, "%s", 0, NULL }, /* also device.serial, filled automatically */ + { "ups.power.nominal", 588, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.0f", 0, &power_nominal }, + { "ups.realpower.nominal", 589, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.0f", 0, &realpower_nominal }, + { "ups.mfr.date", 591, 1, APC_VT_UINT, 0, &_apc_modbus_date_conversion, NULL, 0, NULL }, + { "battery.date", 595, 1, APC_VT_UINT, APC_VF_RW, &_apc_modbus_date_conversion, NULL, 0, NULL }, + { "ups.id", 596, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.0.name", 604, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.1.name", 612, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.2.name", 620, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { "outlet.group.3.name", 628, 8, APC_VT_STRING, APC_VF_RW, NULL, "%s", 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } }; static apc_modbus_register_t apc_modbus_register_map_status[] = { - { "input.transfer.reason", 2, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_status_change_cause_conversion, NULL, 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "input.transfer.reason", 2, 1, APC_VT_UINT, 0, &_apc_modbus_status_change_cause_conversion, NULL, 0, NULL }, + { "ups.test.result", 23, 1, APC_VT_UINT, 0, &_apc_modbus_battery_test_status_conversion, NULL, 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } }; static apc_modbus_register_t apc_modbus_register_map_dynamic[] = { - { "battery.runtime", 128, 2, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "battery.charge", 130, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 9, NULL }, - { "battery.voltage", 131, 1, APC_MODBUS_VALUE_TYPE_INT, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, - { "battery.date.maintenance", 133, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_date_conversion, NULL, 0, NULL }, - { "battery.temperature", 135, 1, APC_MODBUS_VALUE_TYPE_INT, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, - { "ups.load", 136, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 8, NULL }, - { "ups.realpower", 136, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_power_conversion, "%.2f", 8, &realpower_nominal }, - { "ups.power", 138, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_power_conversion, "%.2f", 8, &power_nominal }, - { "output.current", 140, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, - { "output.voltage", 142, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 6, NULL }, - { "output.frequency", 144, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, - { "experimental.output.energy", 145, 2, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "input.voltage", 151, 1, APC_MODBUS_VALUE_TYPE_UINT, &_apc_modbus_voltage_conversion, "%.2f", 6, NULL }, - { "ups.efficiency", 154, 1, APC_MODBUS_VALUE_TYPE_INT, &_apc_modbus_efficiency_conversion, "%.1f", 7, NULL }, - { "ups.timer.shutdown", 155, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.timer.start", 156, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.timer.reboot", 157, 2, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "battery.runtime", 128, 2, APC_VT_UINT, 0, NULL, "%u", 0, NULL }, + { "battery.charge", 130, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 9, NULL }, + { "battery.voltage", 131, 1, APC_VT_INT, 0, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, + { "battery.date.maintenance", 133, 1, APC_VT_UINT, 0, &_apc_modbus_date_conversion, NULL, 0, NULL }, + { "battery.temperature", 135, 1, APC_VT_INT, 0, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, + { "ups.load", 136, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 8, NULL }, + { "ups.realpower", 136, 1, APC_VT_UINT, 0, &_apc_modbus_power_conversion, "%.2f", 8, &realpower_nominal }, + { "ups.power", 138, 1, APC_VT_UINT, 0, &_apc_modbus_power_conversion, "%.2f", 8, &power_nominal }, + { "output.current", 140, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 5, NULL }, + { "output.voltage", 142, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 6, NULL }, + { "output.frequency", 144, 1, APC_VT_UINT, 0, &_apc_modbus_double_conversion, "%.2f", 7, NULL }, + { "experimental.output.energy", 145, 2, APC_VT_UINT, 0, NULL, "%u", 0, NULL }, + { "input.voltage", 151, 1, APC_VT_UINT, 0, &_apc_modbus_voltage_conversion, "%.2f", 6, NULL }, + { "ups.efficiency", 154, 1, APC_VT_INT, 0, &_apc_modbus_efficiency_conversion, "%.1f", 7, NULL }, + { "ups.timer.shutdown", 155, 1, APC_VT_INT, 0, NULL, "%d", 0, NULL }, + { "ups.timer.start", 156, 1, APC_VT_INT, 0, NULL, "%d", 0, NULL }, + { "ups.timer.reboot", 157, 2, APC_VT_INT, 0, NULL, "%d", 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } }; static apc_modbus_register_t apc_modbus_register_map_static[] = { - { "input.transfer.high", 1026, 1, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "input.transfer.low", 1027, 1, APC_MODBUS_VALUE_TYPE_UINT, NULL, "%u", 0, NULL }, - { "ups.delay.shutdown", 1029, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.delay.start", 1030, 1, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { "ups.delay.reboot", 1031, 2, APC_MODBUS_VALUE_TYPE_INT, NULL, "%d", 0, NULL }, - { NULL, 0, 0, 0, NULL, NULL, 0.0f, NULL } + { "input.transfer.high", 1026, 1, APC_VT_UINT, APC_VF_RW, NULL, "%u", 0, NULL }, + { "input.transfer.low", 1027, 1, APC_VT_UINT, APC_VF_RW, NULL, "%u", 0, NULL }, + { "ups.delay.shutdown", 1029, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "ups.delay.start", 1030, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "ups.delay.reboot", 1031, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.0.delay.shutdown", 1029, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.0.delay.start", 1030, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.0.delay.reboot", 1031, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.1.delay.shutdown", 1034, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.1.delay.start", 1035, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.1.delay.reboot", 1036, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.2.delay.shutdown", 1039, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.2.delay.start", 1040, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.2.delay.reboot", 1041, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.3.delay.shutdown", 1044, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.3.delay.start", 1045, 1, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { "outlet.group.3.delay.reboot", 1046, 2, APC_VT_INT, APC_VF_RW, NULL, "%d", 0, NULL }, + { NULL, 0, 0, 0, 0, NULL, NULL, 0.0f, NULL } +}; + +static apc_modbus_register_t* apc_modbus_register_maps[] = { + apc_modbus_register_map_inventory, + apc_modbus_register_map_status, + apc_modbus_register_map_dynamic, + apc_modbus_register_map_static }; static void _apc_modbus_close(int free_modbus) @@ -757,6 +1050,7 @@ static int _apc_modbus_update_value(apc_modbus_register_t *regs_info, const uint { apc_modbus_value_t value; char strbuf[33], nutvbuf[128]; + int dstate_flags; if (regs_info == NULL || regs == NULL || regs_len == 0) { /* Invalid parameters */ @@ -770,19 +1064,44 @@ static int _apc_modbus_update_value(apc_modbus_register_t *regs_info, const uint assert(regs_info->value_type <= apc_modbus_value_types_max); switch (regs_info->value_type) { - case APC_MODBUS_VALUE_TYPE_STRING: + case APC_VT_STRING: _apc_modbus_to_string(regs, regs_info->modbus_len, strbuf, sizeof(strbuf)); value.data.string_value = strbuf; break; - case APC_MODBUS_VALUE_TYPE_INT: + case APC_VT_INT: _apc_modbus_to_int64(regs, regs_info->modbus_len, &value.data.int_value); break; - case APC_MODBUS_VALUE_TYPE_UINT: + case APC_VT_UINT: _apc_modbus_to_uint64(regs, regs_info->modbus_len, &value.data.uint_value); break; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } - if (regs_info->value_converter != NULL) { + if (regs_info->value_converter != NULL && regs_info->value_converter->apc_to_nut != NULL) { /* If we have a converter, use it and set the value as a string */ if (!regs_info->value_converter->apc_to_nut(&value, nutvbuf, sizeof(nutvbuf))) { upslogx(LOG_ERR, "%s: Failed to convert register %" PRIuSIZE ":%" PRIuSIZE, __func__, @@ -799,21 +1118,58 @@ static int _apc_modbus_update_value(apc_modbus_register_t *regs_info, const uint #endif assert(regs_info->value_type <= apc_modbus_value_types_max); switch (regs_info->value_type) { - case APC_MODBUS_VALUE_TYPE_STRING: + case APC_VT_STRING: dstate_setinfo(regs_info->nut_variable_name, regs_info->value_format, value.data.string_value); break; - case APC_MODBUS_VALUE_TYPE_INT: + case APC_VT_INT: dstate_setinfo(regs_info->nut_variable_name, regs_info->value_format, value.data.int_value); break; - case APC_MODBUS_VALUE_TYPE_UINT: + case APC_VT_UINT: dstate_setinfo(regs_info->nut_variable_name, regs_info->value_format, value.data.uint_value); break; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop #endif } + dstate_flags = 0; + if (regs_info->value_type == APC_VT_STRING) { + dstate_flags |= ST_FLAG_STRING; + } + if ((regs_info->value_flags & APC_VF_RW)) { + dstate_flags |= ST_FLAG_RW; + } + dstate_setflags(regs_info->nut_variable_name, dstate_flags); + if (regs_info->value_type == APC_VT_STRING) { + dstate_setaux(regs_info->nut_variable_name, regs_info->modbus_len * sizeof(uint16_t)); + } + return 1; } @@ -837,11 +1193,38 @@ static int _apc_modbus_process_registers(apc_modbus_register_t* values, const ui static int _apc_modbus_read_inventory(void) { - uint16_t regbuf[88]; + uint16_t regbuf[120]; + int start_addr; + uint16_t sog_relay_config; + int outlet_group_count; /* Inventory Information */ - if (_apc_modbus_read_registers(modbus_ctx, 516, 88, regbuf)) { - _apc_modbus_process_registers(apc_modbus_register_map_inventory, regbuf, 88, 516); + start_addr = apc_modbus_register_map_inventory[0].modbus_addr; + if (_apc_modbus_read_registers(modbus_ctx, start_addr, SIZEOF_ARRAY(regbuf), regbuf)) { + sog_relay_config = regbuf[APC_MODBUS_SOGRELAYCONFIGSETTING_BF_REG - start_addr]; + + outlet_group_count = 0; + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_MOG_PRESENT)) { + outlet_group_count++; + } + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_0_PRESENT)) { + outlet_group_count++; + } + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_1_PRESENT)) { + outlet_group_count++; + } + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_2_PRESENT)) { + outlet_group_count++; + } + /* Documentation says there is a bit for SOG3, but everything else does not have it */ + if ((sog_relay_config & APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_3_PRESENT)) { + upslogx(LOG_WARNING, "%s: SOG3 present, but we don't know how to use it", __func__); + outlet_group_count++; + } + + dstate_setinfo("outlet.group.count", "%d", outlet_group_count); + + _apc_modbus_process_registers(apc_modbus_register_map_inventory, regbuf, SIZEOF_ARRAY(regbuf), start_addr); } else { return 0; } @@ -849,8 +1232,218 @@ static int _apc_modbus_read_inventory(void) return 1; } +static int _apc_modbus_setvar(const char *nut_varname, const char *str_value) +{ + size_t mi, i; + int addr, nb, r; + apc_modbus_register_t *apc_map = NULL, *apc_value = NULL; + uint16_t reg_value[16]; + + for (mi = 0; mi < SIZEOF_ARRAY(apc_modbus_register_maps) && apc_value == NULL; mi++) { + apc_map = apc_modbus_register_maps[mi]; + + for (i = 0; apc_map[i].nut_variable_name; i++) { + if (!strcasecmp(nut_varname, apc_map[i].nut_variable_name)) { + apc_value = &apc_map[i]; + break; + } + } + } + + if (!apc_map || !apc_value) { + upslogx(LOG_WARNING, "%s: [%s] is unknown", __func__, nut_varname); + return STAT_SET_UNKNOWN; + } + + if (!(apc_value->value_flags & APC_VF_RW)) { + upslogx(LOG_WARNING, "%s: [%s] is not writable", __func__, nut_varname); + return STAT_SET_INVALID; + } + + assert(apc_value->modbus_len < SIZEOF_ARRAY(reg_value)); + + if (apc_value->value_converter && apc_value->value_converter->nut_to_apc) { + if (!apc_value->value_converter->nut_to_apc(str_value, reg_value, apc_value->modbus_len)) { + upslogx(LOG_WARNING, "%s: [%s] failed to convert value", __func__, nut_varname); + return STAT_SET_CONVERSION_FAILED; + } + } else { + assert(apc_value->value_type <= apc_modbus_value_types_max); + switch (apc_value->value_type) { + case APC_VT_STRING: + r = _apc_modbus_from_string(str_value, reg_value, apc_value->modbus_len); + break; + case APC_VT_INT: + r = _apc_modbus_from_int64_string(str_value, reg_value, apc_value->modbus_len); + break; + case APC_VT_UINT: + r = _apc_modbus_from_uint64_string(str_value, reg_value, apc_value->modbus_len); + break; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif + } + + if (!r) { + upslogx(LOG_WARNING, "%s: [%s] failed to convert value", __func__, nut_varname); + return STAT_SET_CONVERSION_FAILED; + } + } + + addr = apc_value->modbus_addr; + nb = apc_value->modbus_len; + if (modbus_write_registers(modbus_ctx, addr, nb, reg_value) < 0) { + upslogx(LOG_ERR, "%s: Write of %d:%d failed: %s (%s)", __func__, addr, addr + nb, modbus_strerror(errno), device_path); + _apc_modbus_handle_error(modbus_ctx); + return STAT_SET_FAILED; + } + + /* There seem to be some communication problems if we don't wait after writing. + * Maybe there is some register we need to poll for write completion? + */ + usleep(100000); + + upslogx(LOG_INFO, "SET %s='%s'", nut_varname, str_value); + + if (_apc_modbus_read_registers(modbus_ctx, addr, nb, reg_value)) { + _apc_modbus_process_registers(apc_map, reg_value, nb, addr); + } + + return STAT_SET_HANDLED; +} + +typedef struct { + const char *nut_command_name; + size_t modbus_addr; + size_t modbus_len; /* Number of uint16_t registers */ + uint64_t value; +} apc_modbus_command_t; + +static apc_modbus_command_t apc_modbus_command_map[] = { + { "test.battery.start", APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_REG, 1, APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_START }, + { "test.battery.stop", APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_REG, 1, APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_ABORT }, + { "test.panel.start", APC_MODBUS_USERINTERFACECOMMAND_BF_REG, 1, APC_MODBUS_USERINTERFACECOMMAND_BF_SHORT_TEST }, + { "calibrate.start", APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_REG, 1, APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_START }, + { "calibrate.stop", APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_REG, 1, APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_ABORT }, + { "bypass.start", APC_MODBUS_UPSCOMMAND_BF_REG, 2, APC_MODBUS_UPSCOMMAND_BF_OUTPUT_INTO_BYPASS }, + { "bypass.stop", APC_MODBUS_UPSCOMMAND_BF_REG, 2, APC_MODBUS_UPSCOMMAND_BF_OUTPUT_OUT_OF_BYPASS }, + { "beeper.mute", APC_MODBUS_USERINTERFACECOMMAND_BF_REG, 1, APC_MODBUS_USERINTERFACECOMMAND_BF_MUTE_ALL_ACTIVE_AUDIBLE_ALARMS }, + { "load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "load.off.delay", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "load.on.delay", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_ON_DELAY }, + { "shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "shutdown.stayoff", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "shutdown.reboot", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "shutdown.reboot.graceful", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.0.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.0.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "outlet.0.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "outlet.0.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP }, + { "outlet.1.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.1.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 }, + { "outlet.1.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 }, + { "outlet.1.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 }, + { "outlet.2.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.2.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 }, + { "outlet.2.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 }, + { "outlet.2.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 }, + { "outlet.3.shutdown.return", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 | APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY }, + { "outlet.3.load.off", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 }, + { "outlet.3.load.on", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 }, + { "outlet.3.load.cycle", APC_MODBUS_OUTLETCOMMAND_BF_REG, 2, + APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 }, + { NULL, 0, 0, 0 } +}; + +static int _apc_modbus_instcmd(const char *nut_cmdname, const char *extra) +{ + size_t i; + int addr, nb; + apc_modbus_command_t *apc_command = NULL; + uint16_t value[4]; /* Max 64-bit */ + + NUT_UNUSED_VARIABLE(extra); + + for (i = 0; apc_modbus_command_map[i].nut_command_name; i++) { + if (!strcasecmp(nut_cmdname, apc_modbus_command_map[i].nut_command_name)) { + apc_command = &apc_modbus_command_map[i]; + break; + } + } + + if (!apc_command) { + upslogx(LOG_WARNING, "%s: [%s] is unknown", __func__, nut_cmdname); + return STAT_INSTCMD_UNKNOWN; + } + + assert(apc_command->modbus_len <= SIZEOF_ARRAY(value)); + + if (!_apc_modbus_from_uint64(apc_command->value, value, apc_command->modbus_len)) { + upslogx(LOG_WARNING, "%s: [%s] failed to convert value", __func__, nut_cmdname); + return STAT_INSTCMD_CONVERSION_FAILED; + } + + addr = apc_command->modbus_addr; + nb = apc_command->modbus_len; + if (modbus_write_registers(modbus_ctx, addr, nb, value) < 0) { + upslogx(LOG_ERR, "%s: Write of %d:%d failed: %s (%s)", __func__, addr, addr + nb, modbus_strerror(errno), device_path); + _apc_modbus_handle_error(modbus_ctx); + return STAT_INSTCMD_FAILED; + } + + return STAT_INSTCMD_HANDLED; +} + void upsdrv_initinfo(void) { + size_t i; + if (!_apc_modbus_read_inventory()) { fatalx(EXIT_FAILURE, "Can't read inventory information from the UPS"); } @@ -858,6 +1451,14 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.mfr", "American Power Conversion"); /* also device.mfr, filled automatically */ dstate_setinfo("device.type", "ups"); + + for (i = 0; apc_modbus_command_map[i].nut_command_name; i++) { + dstate_addcmd(apc_modbus_command_map[i].nut_command_name); + } + + upsh.setvar = _apc_modbus_setvar; + upsh.instcmd = _apc_modbus_instcmd; + } void upsdrv_updateinfo(void) @@ -950,8 +1551,8 @@ void upsdrv_updateinfo(void) } /* Static Data */ - if (_apc_modbus_read_registers(modbus_ctx, 1026, 7, regbuf)) { - _apc_modbus_process_registers(apc_modbus_register_map_static, regbuf, 7, 1026); + if (_apc_modbus_read_registers(modbus_ctx, 1026, 22, regbuf)) { + _apc_modbus_process_registers(apc_modbus_register_map_static, regbuf, 22, 1026); } else { dstate_datastale(); return; @@ -964,9 +1565,7 @@ void upsdrv_updateinfo(void) void upsdrv_shutdown(void) { - /* TODO: replace with a proper shutdown function */ - upslogx(LOG_ERR, "shutdown not supported"); - set_exit_flag(-1); + modbus_write_register(modbus_ctx, APC_MODBUS_OUTLETCOMMAND_BF_REG, APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN | APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP); } void upsdrv_help(void) @@ -1341,6 +1940,9 @@ void upsdrv_initups(void) } #if defined NUT_MODBUS_HAS_USB + /* This creates an exact matcher after the first connection so that on + * reconnect we are more likely to match the exact device we connected to + * the first time. */ _apc_modbus_create_reopen_matcher(); #endif /* defined NUT_MODBUS_HAS_USB */ diff --git a/drivers/apc_modbus.h b/drivers/apc_modbus.h new file mode 100644 index 0000000000..d175eb4974 --- /dev/null +++ b/drivers/apc_modbus.h @@ -0,0 +1,91 @@ +/* apc_modbus.h - Driver for APC Modbus UPS + * Copyright Š 2023 Axel Gembe + * + * 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 APC_MODBUS_H +#define APC_MODBUS_H + +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PENDING (1 << 0) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_INPROGRESS (1 << 1) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_PASSED (1 << 2) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_FAILED (1 << 3) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_REFUSED (1 << 4) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_ABORTED (1 << 5) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_PROTOCOL (1 << 6) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_LOCALUI (1 << 7) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_SOURCE_INTERNAL (1 << 8) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INVALIDSTATE (1 << 9) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_INTERNALFAULT (1 << 10) +#define APC_MODBUS_REPLACEBATTERYTESTSTATUS_BF_MOD_STATEOFCHARGENOTACCEPTABLE (1 << 11) + +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_REG 590 +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_MOG_PRESENT (1 << 0) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_0_PRESENT (1 << 1) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_1_PRESENT (1 << 2) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_2_PRESENT (1 << 3) +#define APC_MODBUS_SOGRELAYCONFIGSETTING_BF_SOG_3_PRESENT (1 << 4) + +#define APC_MODBUS_UPSCOMMAND_BF_REG 1536 +/* 0 - 2 are reserved */ +#define APC_MODBUS_UPSCOMMAND_BF_RESTORE_FACTORY_SETTINGS (1 << 3) +#define APC_MODBUS_UPSCOMMAND_BF_OUTPUT_INTO_BYPASS (1 << 4) +#define APC_MODBUS_UPSCOMMAND_BF_OUTPUT_OUT_OF_BYPASS (1 << 5) +/* 6 - 8 are reserved */ +#define APC_MODBUS_UPSCOMMAND_BF_CLEAR_FAULTS (1 << 9) +/* 10 - 12 are reserved */ +#define APC_MODBUS_UPSCOMMAND_BF_RESET_STRINGS (1 << 13) +#define APC_MODBUS_UPSCOMMAND_BF_RESET_LOGS (1 << 14) + +#define APC_MODBUS_OUTLETCOMMAND_BF_REG 1538 +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_CANCEL (1 << 0) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_ON (1 << 1) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_OFF (1 << 2) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_SHUTDOWN (1 << 3) +#define APC_MODBUS_OUTLETCOMMAND_BF_CMD_OUTPUT_REBOOT (1 << 4) +#define APC_MODBUS_OUTLETCOMMAND_BF_MOD_COLD_BOOT_ALLOWED (1 << 5) +#define APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_ON_DELAY (1 << 6) +#define APC_MODBUS_OUTLETCOMMAND_BF_MOD_USE_OFF_DELAY (1 << 7) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_MAIN_OUTLET_GROUP (1 << 8) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_0 (1 << 9) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_1 (1 << 10) +#define APC_MODBUS_OUTLETCOMMAND_BF_TARGET_SWITCHED_OUTLET_GROUP_2 (1 << 11) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_USB_PORT (1 << 12) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_LOCAL_USER (1 << 13) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_RJ45_PORT (1 << 14) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_SMART_SLOT_1 (1 << 15) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_SMART_SLOT_2 (1 << 16) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_INTERNAL_NETWORK_1 (1 << 17) +#define APC_MODBUS_OUTLETCOMMAND_BF_SOURCE_INTERNAL_NETWORK_2 (1 << 18) + +#define APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_REG 1541 +#define APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_START (1 << 0) +#define APC_MODBUS_REPLACEBATTERYTESTCOMMAND_BF_ABORT (1 << 1) + +#define APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_REG 1542 +#define APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_START (1 << 0) +#define APC_MODBUS_RUNTIMECALIBRATIONCOMMAND_BF_ABORT (1 << 1) + +#define APC_MODBUS_USERINTERFACECOMMAND_BF_REG 1543 +#define APC_MODBUS_USERINTERFACECOMMAND_BF_SHORT_TEST (1 << 0) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_CONTINUOUS_TEST (1 << 1) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_MUTE_ALL_ACTIVE_AUDIBLE_ALARMS (1 << 2) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_CANCEL_MUTE (1 << 3) +/* 4 is reserved */ +#define APC_MODBUS_USERINTERFACECOMMAND_BF_ACKNOWLEDGE_BATTERY_ALARMS (1 << 5) +#define APC_MODBUS_USERINTERFACECOMMAND_BF_ACKNOWLEDGE_SITE_WIRING_ALARM (1 << 6) + +#endif /* APC_MODBUS_H */ diff --git a/drivers/apcsmart-old.c b/drivers/apcsmart-old.c index 9771e0e52a..5a56c99106 100644 --- a/drivers/apcsmart-old.c +++ b/drivers/apcsmart-old.c @@ -118,6 +118,9 @@ static const char *convert_data(apc_vartab_t *cmd_entry, const char *upsval) case 'S': return "simulated power failure or UPS test"; default: return upsval; } + + default: + break; } upslogx(LOG_NOTICE, "Unable to handle conversion of %s", cmd_entry->name); diff --git a/drivers/apcsmart.c b/drivers/apcsmart.c index 97b488bc46..54c04dcda7 100644 --- a/drivers/apcsmart.c +++ b/drivers/apcsmart.c @@ -4,6 +4,7 @@ * Copyright (C) 1999 Russell Kroll * (C) 2000 Nigel Metheringham * (C) 2011+ Michal Soltys + * (C) 2024 Jim Klimov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -36,7 +37,7 @@ #include "apcsmart_tabs.h" #define DRIVER_NAME "APC Smart protocol driver" -#define DRIVER_VERSION "3.32" +#define DRIVER_VERSION "3.34" #ifdef WIN32 # ifndef ECANCELED @@ -87,7 +88,7 @@ static int (*sdlist[])(const void *) = { /* * note: both lookup functions MUST be used after variable detection is * completed - that is after deprecate_vars() call; the general reason for this - * is 1:n and n:1 nut <-> apc mappings, which are not determined prior to the + * is 1:n and n:1 NUT <-> APC mappings, which are not determined prior to the * detection */ static apc_vartab_t *vt_lookup_char(char cmdchar) @@ -148,6 +149,8 @@ static const char *convert_data(apc_vartab_t *vt, const char *upsval) static char temp[APC_LBUF]; long tval; + memset(temp, '\0', sizeof(temp)); + /* this should never happen */ if (strlen(upsval) >= sizeof(temp)) { upslogx(LOG_CRIT, "%s: the length of [%s] is too big", __func__, vt->name); @@ -166,7 +169,8 @@ static const char *convert_data(apc_vartab_t *vt, const char *upsval) case APC_F_SECONDS: case APC_F_LEAVE: /* no conversion for any of these */ - strcpy(temp, upsval); + strncpy(temp, upsval, sizeof(temp) - 1); + temp[sizeof(temp) - 1] = '\0'; return temp; case APC_F_HOURS: @@ -193,21 +197,26 @@ static const char *convert_data(apc_vartab_t *vt, const char *upsval) case 'O': return "no transfers yet since turnon"; case 'S': return "simulated power failure or UPS test"; default: - strcpy(temp, upsval); - return temp; + strncpy(temp, upsval, sizeof(temp) - 1); + temp[sizeof(temp) - 1] = '\0'; + return temp; } + + default: + break; } /* this should never happen */ upslogx(LOG_CRIT, "%s: unable to convert [%s]", __func__, vt->name); - strcpy(temp, upsval); + strncpy(temp, upsval, sizeof(temp) - 1); + temp[sizeof(temp) - 1] = '\0'; return temp; } /* report differences if tcsetattr != tcgetattr, return otherwise */ /* - * Aix compatible names + * AIX compatible names */ #if defined(VWERSE) && !defined(VWERASE) #define VWERASE VWERSE @@ -276,7 +285,7 @@ static void apc_ser_diff(struct termios *tioset, struct termios *tioget) upslogx(LOG_NOTICE, "%s: device reports different attributes than requested", device_path); /* - * According to the manual the most common problem is mis-matched + * According to the manual the most common problem is mismatched * combinations of input and output baud rates. If the combination is * not supported then neither are changed. This should not be a * problem here since we set them both to the same extremely common @@ -284,12 +293,18 @@ static void apc_ser_diff(struct termios *tioset, struct termios *tioget) */ for (i = 0; i < SIZEOF_ARRAY(tio); i++) { - upsdebugx(1, "tc%cetattr(): gfmt1:cflag=%x:iflag=%x:lflag=%x:oflag=%x:", dir[i], - (unsigned int) tio[i]->c_cflag, (unsigned int) tio[i]->c_iflag, - (unsigned int) tio[i]->c_lflag, (unsigned int) tio[i]->c_oflag); + upsdebugx(1, + "%s: tc%cetattr(): gfmt1:cflag=%x:iflag=%x:lflag=%x:oflag=%x:", + __func__, dir[i], + (unsigned int) tio[i]->c_cflag, (unsigned int) tio[i]->c_iflag, + (unsigned int) tio[i]->c_lflag, (unsigned int) tio[i]->c_oflag); for (cp = cchars1; cp->name; ++cp) - upsdebugx(1, "\t%s=%x:", cp->name, tio[i]->c_cc[cp->sub]); - upsdebugx(1, "\tispeed=%d:ospeed=%d", (int) cfgetispeed(tio[i]), (int) cfgetospeed(tio[i])); + upsdebugx(1, "%s: \t%s=%x:", + __func__, cp->name, tio[i]->c_cc[cp->sub]); + upsdebugx(1, "%s: \tispeed=%d:ospeed=%d", + __func__, + (int) cfgetispeed(tio[i]), + (int) cfgetospeed(tio[i])); } } @@ -441,7 +456,7 @@ static void alert_handler(char ch) } /* - * we need a tiny bit different processing due to '*' and canonical mode; the + * we need a tiny bit different processing due to "*" and canonical mode; the * function is subtly different from generic ser_get_line_alert() */ #define apc_read(b, l, f) apc_read_i(b, l, f, __func__, __LINE__) @@ -506,10 +521,10 @@ static ssize_t apc_read_i(char *buf, size_t buflen, int flags, const char *fn, u ser_comm_fail("serial port read timeout: %u(%s)", ln, fn); return ret; } - /* ok, timeout is acceptable */ + /* ok, timeout is acceptable... */ if (ret == 0 && (flags & SER_TO)) { /* - * but it doesn't imply ser_comm_good + * ...but it doesn't imply ser_comm_good * * for example we might be in comm_fail condition, * trying to "nudge" the UPS with some command @@ -535,17 +550,18 @@ static ssize_t apc_read_i(char *buf, size_t buflen, int flags, const char *fn, u return (ssize_t)count; } /* - * '*' is set as a secondary EOL; convert to 'OK' only as a - * reply to shutdown command in sdok(); otherwise next - * select_read() will continue normally + * "*" is set as a secondary EOL; convert to "OK" only + * as a reply to shutdown command in sdok(); otherwise + * next select_read() will continue normally */ if ((flags & SER_HA) && temp[i] == '*') { /* - * a bit paranoid, but remember '*' is not real EOL; - * there could be some firmware in existence, that - * would send both string: 'OK\n' and alert: '*'. - * Just in case, try to flush the input with small 1 sec. - * timeout + * a bit paranoid, but remember that "*" is not + * real EOL; there could be some firmware in + * existence, which would send both string: + * "OK\n" and alert: "*". + * Just in case, try to flush the input with a + * small 1 sec timeout. */ memset(buf, '\0', buflen); errno = 0; @@ -589,9 +605,9 @@ static ssize_t apc_write_i(unsigned char code, const char *fn, unsigned int ln) ret = ser_send_char(upsfd, code); /* - * Formally any write() sould never return 0, if the count != 0. For + * Formally any write() should never return 0, if the count != 0. For * the sake of handling any obscure nonsense, we consider such return - * as a failure - thus <= condition; either way, LE is pretty hard + * as a failure - thus <= condition; either way, LE is a pretty hard * condition hardly ever happening; */ if (ret <= 0) @@ -603,7 +619,7 @@ static ssize_t apc_write_i(unsigned char code, const char *fn, unsigned int ln) /* * We have to watch out for NA, here; * This is generally safe, as otherwise we will just timeout. The reason we do - * it, is that under certain conditions an ups might respond with NA for + * it, is that under certain conditions an UPS might respond with NA for * something it would normally handle (e.g. calling @ while being in powered * off or hibernated state. If we keep sending the "arguments" after getting * NA, they will be interpreted as commands, which is quite a bug :) @@ -677,7 +693,7 @@ static void apc_flush(int flags) } } -/* apc specific wrappers around set/del info - to handle "packed" variables */ +/* APC specific wrappers around set/del info - to handle "packed" variables */ static void apc_dstate_delinfo(apc_vartab_t *vt, int skip) { char *name, *nidx; @@ -694,7 +710,9 @@ static void apc_dstate_delinfo(apc_vartab_t *vt, int skip) return; } - strcpy(name, vt->name); + memset(name, '\0', vt->nlen0); + strncpy(name, vt->name, vt->nlen0 - 1); + name[vt->nlen0 - 1] = '\0'; nidx = strstr(name,".0.") + 1; for (c = skip; c < vt->cnt; c++) { @@ -710,6 +728,7 @@ static void apc_dstate_setinfo(apc_vartab_t *vt, const char *upsval) { char *name, *nidx; char *temp, *vidx[APC_PACK_MAX], *com, *curr; + size_t upsvallen = strlen(upsval); int c; /* standard not packed var */ @@ -723,18 +742,23 @@ static void apc_dstate_setinfo(apc_vartab_t *vt, const char *upsval) return; } - if ( !(temp = xmalloc(sizeof(char) * (strlen(upsval) + 1))) ) { + if ( !(temp = xmalloc(sizeof(char) * (upsvallen + 2))) ) { + /* +2 seems like an overkill, but helps hush compiler warnings */ upslogx(LOG_ERR, "apc_dstate_setinfo() failed to allocate buffer"); free(name); return; } /* we have to set proper name for dstate_setinfo() calls */ - strcpy(name, vt->name); + memset(name, '\0', vt->nlen0); + strncpy(name, vt->name, vt->nlen0 - 1); + name[vt->nlen0 - 1] = '\0'; nidx = strstr(name,".0.") + 1; /* split the value string */ - strcpy(temp, upsval); + memset(temp, '\0', upsvallen + 2); + strncpy(temp, upsval, upsvallen + 1); + temp[upsvallen] = '\0'; curr = temp; c = 0; do { @@ -905,10 +929,10 @@ static int var_verify(apc_vartab_t *vt) } /* - * This function iterates over vartab, deprecating nut<->apc 1:n and n:1 + * This function iterates over vartab, deprecating NUT<->APC 1:n and n:1 * variables. We prefer earliest present variable. All the other ones must be * marked as not present (which implies deprecation). - * This pass is requried after completion of all protocol_verify() and/or + * This pass is required after completion of all protocol_verify() and/or * legacy_verify() calls. */ static void deprecate_vars(void) @@ -978,7 +1002,7 @@ static void apc_getcaps(int qco) /* * note - apc_read() needs larger timeout grace (not a problem w.r.t. - * to nut's timing, as it's done only during setup) and different + * to NUT's timing, as it's done only during setup) and different * ignore set due to certain characters like '#' being received */ ret = apc_read(temp, sizeof(temp), SER_CC|SER_TO); @@ -1001,7 +1025,8 @@ static void apc_getcaps(int qco) if (temp[0] != '#') { upslogx(LOG_WARNING, "unknown capability start char [%c] !", temp[0]); - upsdebugx(1, "please report this caps string: %s", temp); + upsdebugx(1, "%s: please report this caps string: %s", + __func__, temp); return; } @@ -1060,7 +1085,8 @@ static void apc_getcaps(int qco) /* mark this as writable */ if (valid) { - upsdebugx(1, "%s [%s(%c)] - capability supported", vt->name, prtchr(cmd), loc); + upsdebugx(1, "%s: %s [%s(%c)] - capability supported", + __func__, vt->name, prtchr(cmd), loc); dstate_setflags(vt->name, ST_FLAG_RW); @@ -1118,7 +1144,7 @@ static void protocol_verify(unsigned char cmd) return; /* - * loop necessary for apc:nut 1:n cases (e.g. T -> device.uptime, + * loop necessary for APC:NUT 1:n cases (e.g. T -> device.uptime, * ambient.0.temperature) */ found = 0; @@ -1134,7 +1160,7 @@ static void protocol_verify(unsigned char cmd) /* * see if it's a command - * loop necessary for apc:nut 1:n cases (e.g. D -> calibrate.start, + * loop necessary for APC:NUT 1:n cases (e.g. D -> calibrate.start, * calibrate.stop) */ found = 0; @@ -1162,7 +1188,7 @@ static void oldapcsetup(void) { /* * note: battery.date and ups.id make little sense here, as - * that would imply writability and this is an *old* apc psu + * that would imply writability and this is an *old* APC UPS */ legacy_verify("ups.temperature"); legacy_verify("ups.load"); @@ -1183,26 +1209,27 @@ static void oldapcsetup(void) if (vt_lookup_name("output.current")) dstate_setinfo("ups.model", "Matrix-UPS"); else { - /* really old models don't support ups.model (apc: 0x01) */ + /* really old models don't support ups.model (APC: 0x01) */ if (!vt_lookup_name("ups.model")) /* force the model name */ dstate_setinfo("ups.model", "Smart-UPS"); } /* - * If we have come down this path then we dont do capabilities and + * If we have come down this path then we don't do capabilities and * other shiny features. */ } -/* some hardware is a special case - hotwire the list of cmdchars */ +/* some hardware is a special case - hot-wire the list of cmdchars */ static int firmware_table_lookup(void) { ssize_t ret; unsigned int i, j; char buf[APC_LBUF]; - upsdebugx(1, "attempting firmware lookup using [%s]", prtchr(APC_FW_OLD)); + upsdebugx(1, "%s: attempting firmware lookup using [%s]", + __func__, prtchr(APC_FW_OLD)); apc_flush(0); if (apc_write(APC_FW_OLD) != 1) @@ -1215,7 +1242,8 @@ static int firmware_table_lookup(void) * firmware version, we attempt that only if 'V' doesn't work. */ if (!ret || !strcmp(buf, "NA")) { - upsdebugx(1, "attempting firmware lookup using [%s]", prtchr(APC_FW_NEW)); + upsdebugx(1, "%s: attempting firmware lookup using [%s]", + __func__, prtchr(APC_FW_NEW)); if (apc_write(APC_FW_NEW) != 1) return 0; @@ -1223,12 +1251,12 @@ static int firmware_table_lookup(void) return 0; } - upsdebugx(1, "detected firmware version: %s", buf); + upsdebugx(1, "%s: detected firmware version: %s", __func__, buf); /* this will be reworked if we get a lot of these things */ if (!strcmp(buf, "451.2.I")) { /* quirk_capability_overflow */ - upsdebugx(1, "WARN: quirky firmware !"); + upsdebugx(1, "%s: WARN: quirky firmware !", __func__); return 2; } @@ -1238,13 +1266,16 @@ static int firmware_table_lookup(void) * through 'b'; see: * http://article.gmane.org/gmane.comp.monitoring.nut.user/7762 */ - strcpy(buf, "set\1"); + memset(buf, '\0', sizeof(buf)); + strncpy(buf, "set\1", sizeof(buf) - 1); + buf[sizeof(buf) - 1] = '\0'; } for (i = 0; apc_compattab[i].firmware != NULL; i++) { if (!strcmp(apc_compattab[i].firmware, buf)) { - upsdebugx(1, "matched firmware: %s", apc_compattab[i].firmware); + upsdebugx(1, "%s: matched firmware: %s", + __func__, apc_compattab[i].firmware); /* magic ? */ if (strspn(apc_compattab[i].firmware, "05")) { @@ -1254,7 +1285,9 @@ static int firmware_table_lookup(void) } /* matched - run the cmdchars from the table */ - upsdebugx(1, "parsing out supported cmds and vars"); + upsdebugx(1, + "%s: parsing out supported cmds and vars", + __func__); for (j = 0; j < strlen(apc_compattab[i].cmdchars); j++) protocol_verify((const unsigned char)(apc_compattab[i].cmdchars[j])); deprecate_vars(); @@ -1282,7 +1315,8 @@ static int getbaseinfo(void) /* found compat */ return 1; - upsdebugx(1, "attempting var/cmdset lookup using [%s]", prtchr(APC_CMDSET)); + upsdebugx(1, "%s: attempting var/cmdset lookup using [%s]", + __func__, prtchr(APC_CMDSET)); /* * Initially we ask the UPS what commands it takes. If this fails we are * going to need an alternate strategy - we can deal with that if it @@ -1302,7 +1336,7 @@ static int getbaseinfo(void) return 1; } - upsdebugx(1, "parsing out supported cmds/vars"); + upsdebugx(1, "%s: parsing out supported cmds/vars", __func__); /* * returned set is verified for validity above, so just extract * what's interesting for us @@ -1321,7 +1355,7 @@ static int getbaseinfo(void) /* if capabilities are supported, add them here */ if (strchr(cmds, APC_CAPS)) { - upsdebugx(1, "parsing out caps"); + upsdebugx(1, "%s: parsing out caps", __func__); apc_getcaps(qco); } return 1; @@ -1345,7 +1379,7 @@ static int do_cal(int start) /* if we can't check the current calibration status, bail out */ if ((ret < 1) || (!strcmp(temp, "NA"))) { - upslogx(LOG_WARNING, "%s", "runtime calibration state undeterminable"); + upslogx(LOG_WARNING, "%s", "runtime calibration state can not be determined"); return STAT_INSTCMD_HANDLED; /* FUTURE: failure */ } @@ -1429,7 +1463,7 @@ static int smartmode(void) /* * get the UPS talking to us in smart mode * note: this is weird overkill, but possibly excused due to some obscure - * hardware/firmware combinations; simpler version commmented out above, for + * hardware/firmware combinations; simpler version commented out above, for * now let's keep minimally adjusted old one */ static int smartmode(int cnt) @@ -1444,7 +1478,7 @@ static int smartmode(int cnt) if (apc_write(APC_GOSMART) != 1) return 0; - /* timeout here is intented */ + /* timeout here is intended */ ret = apc_read(temp, sizeof(temp), SER_TO|SER_D1); if (ret > 0 && !strcmp(temp, "SM")) return 1; /* success */ @@ -1463,8 +1497,8 @@ static int smartmode(int cnt) } /* - * all shutdown commands should respond with 'OK' or '*' - * apc_read() handles conversion to 'OK' so we care only about that one + * all shutdown commands should respond with "OK" or "*" + * apc_read() handles conversion to "OK" so we care only about that one * ign allows for timeout without assuming an error */ static int sdok(int ign) @@ -1473,7 +1507,7 @@ static int sdok(int ign) char temp[APC_SBUF]; /* - * older upses on failed commands might just timeout, we cut down + * older UPSes on failed commands might just timeout, we cut down * timeout grace though * furthermore, command 'Z' will not reply with anything */ @@ -1519,7 +1553,9 @@ static int sdcmd_CS(const void *foo) if ((val = getval("cshdelay"))) cshd = (strtod(val, NULL) * 1000000); - upsdebugx(1, "%s: issuing CS 'hack' [%s+%s] with %2.1f sec delay", __func__, prtchr(APC_CMD_SIMPWF), prtchr(APC_CMD_SOFTDOWN), (double)cshd / 1000000); + upsdebugx(1, "%s: issuing CS 'hack' [%s+%s] with %2.1f sec delay", + __func__, prtchr(APC_CMD_SIMPWF), prtchr(APC_CMD_SOFTDOWN), + (double)cshd / 1000000); if (ups_status & APC_STAT_OL) { apc_flush(0); upsdebugx(1, "%s: issuing [%s]", __func__, prtchr(APC_CMD_SIMPWF)); @@ -1550,6 +1586,8 @@ static int sdcmd_AT(const void *str) const char *awd = str; char temp[APC_SBUF], *ptr; + memset(temp, '\0', sizeof(temp)); + if (!awd) awd = "000"; @@ -1561,10 +1599,11 @@ static int sdcmd_AT(const void *str) for (i = cnt; i < padto ; i++) { *ptr++ = '0'; } - strcpy(ptr, awd); + strncpy(ptr, awd, sizeof(temp) - (ptr - temp) - 1); + temp[sizeof(temp) - 1] = '\0'; - upsdebugx(1, "%s: issuing [%s] with %ld minutes of additional wakeup delay", - __func__, prtchr(APC_CMD_GRACEDOWN), strtol(awd, NULL, 10)*6); + upsdebugx(1, "%s: issuing [%s] with %ld minutes of additional wake-up delay", + __func__, prtchr(APC_CMD_GRACEDOWN), strtol(awd, NULL, 10)*6); apc_flush(0); ret = apc_write_long(temp); @@ -1586,7 +1625,7 @@ static int sdcmd_AT(const void *str) /* * "tricky" part - we tried @nn variation and it (unsurprisingly) * failed; we have to abort the sequence with something bogus to have - * the clean state; newer upses will respond with 'NO', older will be + * the clean state; newer UPSes will respond with "NO", older will be * silent (YMMV); */ apc_write(APC_GOSMART); @@ -1626,7 +1665,7 @@ static int sdcmd_Z(const void *foo) return STAT_INSTCMD_FAILED; } - /* note: ups will not reply anything after this command */ + /* note: UPS will not reply anything after this command */ return sdok(1); } @@ -1777,7 +1816,7 @@ static int setvar_enum(apc_vartab_t *vt, const char *val) ptr = convert_data(vt, orig); - /* suppress redundant changes - easier on the eeprom */ + /* suppress redundant changes - easier on the EEPROM */ if (!strcmp(ptr, val)) { upslogx(LOG_INFO, "%s: ignoring SET %s='%s' (unchanged value)", __func__, vt->name, val); @@ -1851,6 +1890,8 @@ static int setvar_string(apc_vartab_t *vt, const char *val) return STAT_SET_FAILED; } + memset(temp, '\0', sizeof(temp)); + apc_flush(SER_AA); if (apc_write((const unsigned char)vt->cmd) != 1) return STAT_SET_FAILED; @@ -1860,7 +1901,7 @@ static int setvar_string(apc_vartab_t *vt, const char *val) if ((ret < 1) || (!strcmp(temp, "NA"))) return STAT_SET_FAILED; - /* suppress redundant changes - easier on the eeprom */ + /* suppress redundant changes - easier on the EEPROM */ if (!strcmp(temp, val)) { upslogx(LOG_INFO, "%s: ignoring SET %s='%s' (unchanged value)", __func__, vt->name, val); @@ -1870,7 +1911,8 @@ static int setvar_string(apc_vartab_t *vt, const char *val) /* length sanitized above */ temp[0] = APC_NEXTVAL; - strcpy(temp + 1, val); + strncpy(temp + 1, val, sizeof(temp) - 1); + temp[sizeof(temp) - 1] = '\0'; ptr = temp + strlen(temp); for (i = strlen(val); i < APC_STRLEN; i++) *ptr++ = '\015'; /* pad with CRs */ @@ -1933,7 +1975,7 @@ static int do_loadon(void) return STAT_INSTCMD_FAILED; /* - * ups will not reply anything after this command, but might + * UPS will not reply anything after this command, but might * generate brief OVER condition (which will be corrected on * the next status update) */ @@ -1942,7 +1984,7 @@ static int do_loadon(void) return STAT_INSTCMD_HANDLED; } -/* actually send the instcmd's char to the ups */ +/* actually send the instcmd's char to the UPS */ static int do_cmd(const apc_cmdtab_t *ct) { ssize_t ret; @@ -2085,7 +2127,7 @@ void upsdrv_makevartable(void) { addvar(VAR_VALUE, "ttymode", "tty discipline selection"); addvar(VAR_VALUE, "cable", "alternate cable (940-0095B) selection"); - addvar(VAR_VALUE, "awd", "hard hibernate's additional wakeup delay"); + addvar(VAR_VALUE, "awd", "hard hibernate's additional wake-up delay"); addvar(VAR_VALUE, "sdtype", "simple shutdown method"); addvar(VAR_VALUE, "advorder", "advanced shutdown control"); addvar(VAR_VALUE, "cshdelay", "CS hack delay"); @@ -2105,7 +2147,7 @@ void upsdrv_initups(void) char *val; apc_vartab_t *ptr; - /* sanitize awd (additional waekup delay of '@' command) */ + /* sanitize awd (additional wake-up delay of '@' command) */ if ((val = getval("awd")) && !rexhlp(APC_AWDFMT, val)) { fatalx(EXIT_FAILURE, "invalid value (%s) for option 'awd'", val); } @@ -2164,7 +2206,7 @@ void upsdrv_initinfo(void) ); } - /* manufacturer ID - hardcoded in this particular module */ + /* manufacturer ID - hard-coded in this particular module */ dstate_setinfo("ups.mfr", "APC"); if (!(pmod = dstate_getinfo("ups.model"))) @@ -2172,7 +2214,8 @@ void upsdrv_initinfo(void) if (!(pser = dstate_getinfo("ups.serial"))) pser = "unknown serial"; - upsdebugx(1, "detected %s [%s] on %s", pmod, pser, device_path); + upsdebugx(1, "%s: detected %s [%s] on %s", + __func__, pmod, pser, device_path); setuphandlers(); /* @@ -2189,22 +2232,70 @@ void upsdrv_updateinfo(void) int all; time_t now; - /* try to wake up a dead ups once in awhile */ + /* try to wake up a dead UPS once in awhile */ if (dstate_is_stale()) { + char temp[LARGEBUF]; + size_t count = 0; + if (!last_worked) upsdebugx(1, "%s: %s", __func__, "comm lost"); /* reset this so a full update runs when the UPS returns */ last_full = 0; + /* Flush the buffer in case it helps, + * or sleep 1 sec if buffer is empty */ + while (select_read(upsfd, temp, sizeof(temp), 1, 0) > 0) { + count++; + } + errno = 0; + + if (!count) { + /* Do not flood the device (and our logs, below) + * with retry attempts */ + usleep(1000000); + } + if (++last_worked < 10) return; /* become aggressive after a few tries */ - upsdebugx(1, "%s: nudging ups with 'Y', iteration #%d ...", __func__, last_worked); - if (!smartmode(1)) + if (!(last_worked % 60)) { + upslogx(LOG_WARNING, "Trying to reconnect to the UPS"); + dstate_setinfo("driver.state", "reconnect.trying"); + + upsdebugx(1, "%s: call upsdrv_cleanup", __func__); + /* dstate_setinfo("driver.state", "cleanup.upsdrv"); */ + upsdrv_cleanup(); + + upsdebugx(1, "%s: sleep 5 sec", __func__); + usleep(5000000); + + upsdebugx(1, "%s: call upsdrv_initups", __func__); + /* dstate_setinfo("driver.state", "init.device"); */ + upsdrv_initups(); + + upsdebugx(1, "%s: call upsdrv_initinfo", __func__); + /* dstate_setinfo("driver.state", "init.info"); */ + upsdrv_initinfo(); + + upsdebugx(1, "%s: call upsdrv_updateinfo", __func__); + dstate_setinfo("driver.state", "reconnect.updateinfo"); + /* dstate_setinfo("driver.state", "init.updateinfo"); */ + upsdrv_updateinfo(); + + dstate_setinfo("driver.state", "init.quiet"); + } + + upsdebugx(1, "%s: nudging UPS with 'Y', iteration #%d ...", + __func__, last_worked); + if (!smartmode(1)) { return; + } + if (last_worked > 10) + upslogx(LOG_WARNING, "%s: recovered the connection", __func__); + upsdebugx(1, "%s: recovered the connection", __func__); last_worked = 0; } diff --git a/drivers/apcupsd-ups.c b/drivers/apcupsd-ups.c index 82bc3af631..9f4b71bc20 100644 --- a/drivers/apcupsd-ups.c +++ b/drivers/apcupsd-ups.c @@ -57,7 +57,7 @@ typedef struct pollfd { #include "nut_stdint.h" #define DRIVER_NAME "apcupsd network client UPS driver" -#define DRIVER_VERSION "0.71" +#define DRIVER_VERSION "0.72" #define POLL_INTERVAL_MIN 10 diff --git a/drivers/arduino-hid.c b/drivers/arduino-hid.c index 59206cbfd0..c63fff7573 100644 --- a/drivers/arduino-hid.c +++ b/drivers/arduino-hid.c @@ -151,18 +151,24 @@ static int arduino_claim(HIDDevice_t *hd) case POSSIBLY_SUPPORTED: /* by default, reject, unless the productid option is given */ if (getval("productid")) { - usb->hid_ep_in=4; - usb->hid_ep_out=5; - usb->hid_rep_index = 2; + if (!getval("usb_hid_ep_in")) + usb->hid_ep_in=4; + if (!getval("usb_hid_ep_out")) + usb->hid_ep_out=5; + if (!getval("usb_hid_rep_index")) + usb->hid_rep_index = 2; return 1; } possibly_supported("Arduino", hd); return 0; case SUPPORTED: - usb->hid_ep_in=4; - usb->hid_ep_out=5; - usb->hid_rep_index = 2; + if (!getval("usb_hid_ep_in")) + usb->hid_ep_in=4; + if (!getval("usb_hid_ep_out")) + usb->hid_ep_out=5; + if (!getval("usb_hid_rep_index")) + usb->hid_rep_index = 2; return 1; case NOT_SUPPORTED: diff --git a/drivers/asem.c b/drivers/asem.c index d35a54746a..82b4746627 100644 --- a/drivers/asem.c +++ b/drivers/asem.c @@ -67,7 +67,7 @@ #endif #define DRIVER_NAME "ASEM" -#define DRIVER_VERSION "0.12" +#define DRIVER_VERSION "0.13" /* Valid on ASEM PB1300 UPS */ #define BQ2060_ADDRESS 0x0B diff --git a/drivers/bcmxcp.c b/drivers/bcmxcp.c index fd6676ff9a..63822ae876 100644 --- a/drivers/bcmxcp.c +++ b/drivers/bcmxcp.c @@ -110,15 +110,13 @@ TODO List: #include "main.h" -#include /* For ldexp() */ -#include /*for FLT_MAX */ - -#include "nut_stdint.h" /* for uint8_t, uint16_t, uint32_t, ... */ +#include "nut_float.h" /* For ldexp(), FLT_MAX */ +#include "nut_stdint.h" /* for uint8_t, uint16_t, uint32_t, ... */ #include "bcmxcp_io.h" #include "bcmxcp.h" -#define DRIVER_NAME "BCMXCP UPS driver" -#define DRIVER_VERSION "0.33" +#define DRIVER_NAME "BCMXCP UPS driver" +#define DRIVER_VERSION "0.35" #define MAX_NUT_NAME_LENGTH 128 #define NUT_OUTLET_POSITION 7 @@ -1160,6 +1158,8 @@ void init_ext_vars(void) dstate_setaux("battery.packs", 1); break; + default: + break; } } } @@ -2098,6 +2098,8 @@ static int instcmd(const char *cmdname, const char *extra) cbuf[2] = 0x2; break; /*mute beeper*/ } + default: + break; } cbuf[3] = 0x0; /*padding*/ @@ -2216,6 +2218,10 @@ void upsdrv_help(void) /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { + /* NOTE: The USB variant of this driver currently does not + * involve nut_usb_addvars() method like others do. When + * fixing, see also tools/nut-scanner/scan_usb.c "exceptions". + */ addvar(VAR_VALUE, "shutdown_delay", "Specify shutdown delay (seconds)"); addvar(VAR_VALUE, "baud_rate", "Specify communication speed (ex: 9600)"); } diff --git a/drivers/belkin-hid.c b/drivers/belkin-hid.c index 686dc92612..c096282d3a 100644 --- a/drivers/belkin-hid.c +++ b/drivers/belkin-hid.c @@ -3,7 +3,9 @@ * Copyright (C) * 2003 - 2008 Arnaud Quette * 2005 Peter Selinger - * 2011, 2014 Charles Lepple + * 2011, 2014 Charles Lepple + * 2024 James R. Parks + * 2024 Jim Klimov * * Sponsored by MGE UPS SYSTEMS * @@ -24,14 +26,13 @@ * */ -#include "main.h" /* for getval() */ +#include "main.h" /* for getval() */ #include "usbhid-ups.h" #include "belkin-hid.h" #include "usb-common.h" +#include "nut_float.h" /* For fabs() */ -#include /* for fabs() */ - -#define BELKIN_HID_VERSION "Belkin/Liebert HID 0.18" +#define BELKIN_HID_VERSION "Belkin/Liebert HID 0.22" /* Belkin */ #define BELKIN_VENDORID 0x050d @@ -68,9 +69,11 @@ static usb_device_id_t belkin_usb_device_table[] = { { USB_DEVICE(BELKIN_VENDORID, 0x1100), NULL }, /* Liebert GXT4 UPS */ - { USB_DEVICE(LIEBERT_VENDORID, 0x0004), NULL }, + { USB_DEVICE(LIEBERT_VENDORID, 0x0000), NULL }, /* Liebert PowerSure PSA UPS */ { USB_DEVICE(LIEBERT_VENDORID, 0x0001), NULL }, + /* Liebert PowerSure PST UPS */ + { USB_DEVICE(LIEBERT_VENDORID, 0x0002), NULL }, /* Liebert PowerSure PSI 1440 */ { USB_DEVICE(LIEBERT_VENDORID, 0x0004), NULL }, /* Liebert GXT3 */ @@ -86,31 +89,39 @@ static const char *liebert_charging_fun(double value); static const char *liebert_lowbatt_fun(double value); static const char *liebert_replacebatt_fun(double value); static const char *liebert_shutdownimm_fun(double value); + +/* These lookup functions also cover the 1e-7 factor which seems to + * be due to a broken report descriptor in certain Liebert units. + * Exposed for unit testing - not "static" */ static const char *liebert_config_voltage_fun(double value); static const char *liebert_line_voltage_fun(double value); +static double liebert_config_voltage_mult = 1.0; +static double liebert_line_voltage_mult = 1.0; +static char liebert_conversion_buf[10]; + static info_lkp_t liebert_online_info[] = { { 0, NULL, liebert_online_fun, NULL } }; static info_lkp_t liebert_discharging_info[] = { - { 0, NULL, liebert_discharging_fun, NULL } + { 0, NULL, liebert_discharging_fun, NULL } }; static info_lkp_t liebert_charging_info[] = { - { 0, NULL, liebert_charging_fun, NULL } + { 0, NULL, liebert_charging_fun, NULL } }; static info_lkp_t liebert_lowbatt_info[] = { - { 0, NULL, liebert_lowbatt_fun, NULL } + { 0, NULL, liebert_lowbatt_fun, NULL } }; static info_lkp_t liebert_replacebatt_info[] = { - { 0, NULL, liebert_replacebatt_fun, NULL } + { 0, NULL, liebert_replacebatt_fun, NULL } }; static info_lkp_t liebert_shutdownimm_info[] = { - { 0, NULL, liebert_shutdownimm_fun, NULL } + { 0, NULL, liebert_shutdownimm_fun, NULL } }; static info_lkp_t liebert_config_voltage_info[] = { @@ -121,13 +132,6 @@ static info_lkp_t liebert_line_voltage_info[] = { { 0, NULL, liebert_line_voltage_fun, NULL }, }; -static double liebert_config_voltage_mult = 1.0; -static double liebert_line_voltage_mult = 1.0; -static char liebert_conversion_buf[10]; - -/* These lookup functions also cover the 1e-7 factor which seems to be due to a - * broken report descriptor in certain Liebert units. - */ static const char *liebert_online_fun(double value) { return value ? "online" : "!online"; @@ -164,8 +168,13 @@ static const char *liebert_shutdownimm_fun(double value) */ static const char *liebert_config_voltage_fun(double value) { - if( value < 1 ) { - if( fabs(value - 1e-7) < 1e-9 ) { + /* Does not fire with devices seen diring investigation for + * https://github.com/networkupstools/nut/issues/2370 + * as the ones seen serve nominal "config" values as integers + * (e.g. "230" for line and "24" for battery). + */ + if (value < 1) { + if (fabs(value - 1e-7) < 1e-9) { liebert_config_voltage_mult = 1e8; liebert_line_voltage_mult = 1e7; /* stomp this in case input voltage was low */ upsdebugx(2, "ConfigVoltage = %g -> assuming correction factor = %g", @@ -182,9 +191,33 @@ static const char *liebert_config_voltage_fun(double value) static const char *liebert_line_voltage_fun(double value) { - if( value < 1 ) { - if( fabs(value - 1e-7) < 1e-9 ) { + /* Keep large readings like "230" or "24" as is */ + if (value < 1) { + int picked_scale = 0; + /* NOTE: Start with tiniest scale first */ + + /* Practical use-case for mult=1e7: + * 1.39e-06 => 13.9 + * 2.201e-05 => 220.1 + * NOTE: The clause below is in fact broken for this use-case, + * but was present in sources for ages (worked wrongly with an + * integer-oriented abs() so collapsed into "if (0 < 1e-9) {")! + * if (fabs(value - 1e-7) < 1e-9) { + */ + if (fabs(value - 1e-5) < 4*1e-5) { liebert_line_voltage_mult = 1e7; + picked_scale = 1; + } else + /* Practical use-case for mult=1e5: + * 0.000273 => 27.3 + * 0.001212 => 121.2 + */ + if (fabs(value - 1e-3) < 4*1e-3) { + liebert_line_voltage_mult = 1e5; + picked_scale = 1; + } + + if (picked_scale) { upsdebugx(2, "Input/OutputVoltage = %g -> assuming correction factor = %g", value, liebert_line_voltage_mult); } else { @@ -480,6 +513,17 @@ static hid_info_t belkin_hid2nut[] = { { "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%s", 0, liebert_line_voltage_info }, { "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%s", HU_FLAG_STATIC, liebert_config_voltage_info }, { "ups.load", 0, 0, "UPS.Output.PercentLoad", NULL, "%.0f", 0, NULL }, + /* Liebert PSI5 */ + { "input.voltage.nominal", 0, 0, "UPS.Flow.ConfigVoltage", NULL, "%.0f", 0, NULL }, + { "input.frequency", 0, 0, "UPS.PowerConverter.Input.Frequency", NULL, "%s", 0, divide_by_100_conversion }, + { "input.voltage", 0, 0, "UPS.PowerConverter.Input.Voltage", NULL, "%s", 0, liebert_line_voltage_info }, + { "output.voltage.nominal", 0, 0, "UPS.Flow.ConfigVoltage", NULL, "%.0f", 0, NULL }, + { "output.frequency", 0, 0, "UPS.PowerConverter.Output.Frequency", NULL, "%s", 0, divide_by_100_conversion }, + { "output.voltage", 0, 0, "UPS.PowerConverter.Output.Voltage", NULL, "%s", 0, liebert_line_voltage_info }, + { "ups.load", 0, 0, "UPS.OutletSystem.Outlet.PercentLoad", NULL, "%.0f", 0, NULL }, + { "battery.voltage", 0, 0, "UPS.BatterySystem.Battery.Voltage", NULL, "%s", 0, liebert_line_voltage_info }, + { "battery.voltage.nominal", 0, 0, "UPS.BatterySystem.Battery.ConfigVoltage", NULL, "%.0f", 0, NULL }, + { "battery.capacity", 0, 0, "UPS.Flow.ConfigApparentPower", NULL, "%.0f", 0, NULL }, /* status */ { "BOOL", 0, 0, "UPS.PowerSummary.Discharging", NULL, NULL, HU_FLAG_QUICK_POLL, liebert_discharging_info }, /* might not need to be liebert_* version */ { "BOOL", 0, 0, "UPS.PowerSummary.Charging", NULL, NULL, HU_FLAG_QUICK_POLL, liebert_charging_info }, @@ -621,6 +665,9 @@ static int belkin_claim(HIDDevice_t *hd) } possibly_supported("Liebert", hd); return 0; + + default: + break; } return 0; diff --git a/drivers/belkin.c b/drivers/belkin.c index 34a3f39b24..8b339d2260 100644 --- a/drivers/belkin.c +++ b/drivers/belkin.c @@ -29,7 +29,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Belkin Smart protocol driver" -#define DRIVER_VERSION "0.26" +#define DRIVER_VERSION "0.27" static ssize_t init_communication(void); static ssize_t get_belkin_reply(char *buf); diff --git a/drivers/belkinunv.c b/drivers/belkinunv.c index 83826a8b7f..0c82865321 100644 --- a/drivers/belkinunv.c +++ b/drivers/belkinunv.c @@ -94,7 +94,7 @@ #include "serial.h" #define DRIVER_NAME "Belkin 'Universal UPS' driver" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/bestfcom.c b/drivers/bestfcom.c index 38a996dac1..5402114652 100644 --- a/drivers/bestfcom.c +++ b/drivers/bestfcom.c @@ -45,7 +45,7 @@ #include "serial.h" #define DRIVER_NAME "Best Ferrups/Fortress driver" -#define DRIVER_VERSION "0.14" +#define DRIVER_VERSION "0.15" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/bestfortress.c b/drivers/bestfortress.c index d86d1b4daa..8c650abb60 100644 --- a/drivers/bestfortress.c +++ b/drivers/bestfortress.c @@ -35,7 +35,7 @@ #endif #define DRIVER_NAME "Best Fortress UPS driver" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/bestuferrups.c b/drivers/bestuferrups.c index c248b13ba7..1585d6a903 100644 --- a/drivers/bestuferrups.c +++ b/drivers/bestuferrups.c @@ -33,7 +33,7 @@ #include "serial.h" #define DRIVER_NAME "Best Ferrups Series ME/RE/MD driver" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/bestups.c b/drivers/bestups.c index d388658e12..b34e804c5b 100644 --- a/drivers/bestups.c +++ b/drivers/bestups.c @@ -29,7 +29,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Best UPS driver" -#define DRIVER_VERSION "1.08" +#define DRIVER_VERSION "1.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -209,6 +209,9 @@ static void ups_ident(void) case 5: highvolt = atof(ptr); break; + + default: + break; } ptr = strtok(NULL, ","); diff --git a/drivers/bicker_ser.c b/drivers/bicker_ser.c new file mode 100644 index 0000000000..a5c2c4db49 --- /dev/null +++ b/drivers/bicker_ser.c @@ -0,0 +1,925 @@ +/* + * bicker_ser.c: support for Bicker SuperCapacitors DC UPSes + * + * Copyright (C) 2024 - Nicola Fontana + * + * 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 + */ + +/* The protocol is reported in many Bicker's manuals but (according to + * Bicker itself) the best source is the UPS Gen Software's user manual: + * + * https://www.bicker.de/media/pdf/ff/cc/fe/en_user_manual_ups-gen2-configuration-software.pdf + * + * Basically, this is a binary protocol without checksums: + * + * 1 byte 1 byte 1 byte 1 byte 0..252 bytes 1 byte + * +-------+-------+-------+-------+--- - - - ---+-------+ + * | SOH | Size | Index | CMD | Data | EOT | + * +-------+-------+-------+-------+--- - - - ---+-------+ + * | HEADER | + * + * where: + * - `SOH` is the start signal (0x01) + * - `Size` is the length (in bytes) of the header and the data field + * - `Index` is the command index: see AVAILABLE COMMANDS + * - `CMD` is the command code to execute: see AVAILABLE COMMANDS + * - `Data` is the (optional) argument of the command + * - `EOT` is the end signal (0x04) + * + * The same format is used for incoming and outcoming packets. The data + * returned in the `Data` field is always in little-endian order. + * + * AVAILABLE COMMANDS + * ------------------ + * + * - Index = 0x01 (GENERIC) + * - CMD = 0x40 (status flags) + * - CMD = 0x41 (input voltage) + * - CMD = 0x42 (input current) + * - CMD = 0x43 (output voltage) + * - CMD = 0x44 (output current) + * - CMD = 0x45 (battery voltage) + * - CMD = 0x46 (battery current) + * - CMD = 0x47 (battery state of charge) + * - CMD = 0x48 (battery state of health) + * - CMD = 0x49 (battery cycles) + * - CMD = 0x4A (battery temperature) + * - CMD = 0x60 (manufacturer) + * - CMD = 0x61 (serial number) + * - CMD = 0x62 (device name) + * - CMD = 0x63 (firmware version) + * - CMD = 0x64 (battery pack) + * - CMD = 0x65 (firmware core version) + * - CMD = 0x66 (CPU temperature) + * - CMD = 0x67 (hardware revision) + * - CMD = 0x21 (UPS output) + * - CMD = 0x2F (shutdown flag) + * - CMD = 0x7A (reset parameter settings) + * + * - Index = 0x07 (PARAMETER) + * - CMD = 0x00 (get/set dummy entry: do not use!) + * - CMD = 0x01 (get/set load sensor) + * - CMD = 0x02 (get/set maximum backup time) + * - CMD = 0x03 (get/set os shutdown by timer) + * - CMD = 0x04 (get/set restart delay timer) + * - CMD = 0x05 (get/set minimum capacity to start) + * - CMD = 0x06 (get/set maximum backup time by in-1) + * - CMD = 0x07 (get/set os shutdown by soc) + * - CMD = 0x08 (get/set battery soc low threshold) + * - CMD = 0x09 (get/set relay event configuration) + * - CMD = 0x0A (get/set RS232 port configuration: place holder!) + * + * - Index = 0x03 (COMMANDS GOT FROM UPSIC MANUAL) + * - CMD = 0x1B (GetChargeStatusRegister) + * - CMD = 0x1C (GetMonitorStatusRegister) + * - CMD = 0x1E (GetCapacity) + * - CMD = 0x1F (GetEsr) + * - CMD = 0x20 (GetVCap1Voltage) + * - CMD = 0x21 (GetVCap2Voltage) + * - CMD = 0x22 (GetVCap3Voltage) + * - CMD = 0x23 (GetVCap4Voltage) + * - CMD = 0x25 (GetInputVoltage) + * - CMD = 0x26 (GetCapStackVoltage) + * - CMD = 0x27 (GetOutputVoltage) + * - CMD = 0x28 (GetInputCurrent) + * - CMD = 0x29 (GetChargeCurrent) + * - CMD = 0x31 (StartCapEsrMeasurement) + * - CMD = 0x32 (SetTimeToShutdown) + */ + +#include "config.h" +#include "main.h" +#include "attribute.h" +#include "nut_stdint.h" + +#include "serial.h" + +#define DRIVER_NAME "Bicker serial protocol" +#define DRIVER_VERSION "0.02" + +#define BICKER_SOH 0x01 +#define BICKER_EOT 0x04 +#define BICKER_TIMEOUT 1 +#define BICKER_DELAY 20 +#define BICKER_RETRIES 3 +#define BICKER_MAXID 0x0A /* Max parameter ID */ +#define BICKER_MAXVAL 0xFFFF /* Max parameter value */ + +/* Protocol lengths */ +#define BICKER_HEADER 3 +#define BICKER_MAXDATA (255 - BICKER_HEADER) +#define BICKER_PACKET(datalen) (1 + BICKER_HEADER + (datalen) + 1) + +#define TOUINT(ch) ((unsigned)(uint8_t)(ch)) +#define LOWBYTE(w) ((uint8_t)((uint16_t)(w) & 0x00FF)) +#define HIGHBYTE(w) ((uint8_t)(((uint16_t)(w) & 0xFF00) >> 8)) +#define WORDLH(l,h) ((uint16_t)((l) + ((h) << 8))) + +upsdrv_info_t upsdrv_info = { + DRIVER_NAME, + DRIVER_VERSION, + "Nicola Fontana ", + DRV_EXPERIMENTAL, + { NULL } +}; + +typedef struct { + uint8_t id; + uint16_t min; + uint16_t max; + uint16_t std; + uint8_t enabled; + uint16_t value; +} BickerParameter; + +typedef struct { + uint8_t bicker_id; + const char *nut_name; + const char *description; +} BickerMapping; + +static const BickerMapping bicker_mappings[] = { + /* Official variables present in docs/nut-names.txt */ + { 0x02, "ups.delay.shutdown", + "Interval to wait after shutdown with delay command (seconds)" }, + { 0x04, "ups.delay.start", + "Interval to wait before restarting the load (seconds)" }, + { 0x05, "battery.charge.restart", + "Minimum battery level for UPS restart after power-off" }, + { 0x07, "battery.charge.low", + "Remaining battery level when UPS switches to LB (percent)" }, + + /* Unofficial variables under the "experimental" namespace */ + { 0x01, "experimental.output.current.low", + "Current threshold under which the power will be cut (mA)" }, + { 0x03, "experimental.ups.delay.shutdown.signal", + "Interval to wait before sending the shutdown signal (seconds)" }, + { 0x06, "experimental.ups.delay.shutdown.signal.masked", + "Interval to wait with IN1 high before sending the shutdown signal (seconds)" }, + { 0x08, "experimental.battery.charge.low.empty", + "Battery level threshold for the empty signal (percent)" }, + { 0x09, "experimental.ups.relay.mode", + "Behavior of the relay" }, +}; + +/** + * Parameter id validation. + * @param id Id of the parameter + * @param context Description of the calling code for the log message + * @return 1 on valid id, 0 on errors. + * + * The id is valid if within the 0x01..BICKER_MAXID range (inclusive). + */ +static int bicker_valid_id(uint8_t id, const char *context) +{ + if (id < 1 || id > BICKER_MAXID) { + upslogx(LOG_ERR, "%s: parameter id 0x%02X is out of range (0x01..0x%02X)", + context, (unsigned)id, (unsigned)BICKER_MAXID); + return 0; + } + return 1; +} + +/** + * Send a packet. + * @param idx Command index + * @param cmd Command + * @param data Source data or NULL for no data field + * @param datalen Size of the source data field or 0 + * @return `datalen` on success or -1 on errors. + */ +static ssize_t bicker_send(uint8_t idx, uint8_t cmd, const void *data, size_t datalen) +{ + uint8_t buf[BICKER_PACKET(BICKER_MAXDATA)]; + size_t buflen; + ssize_t ret; + + if (data != NULL) { + if (datalen > BICKER_MAXDATA) { + upslogx(LOG_ERR, + "Data size exceeded: %" PRIuSIZE " > %d", + datalen, BICKER_MAXDATA); + return -1; + } + memcpy(&buf[1 + BICKER_HEADER], data, datalen); + } else { + datalen = 0; + } + + ser_flush_io(upsfd); + + buflen = BICKER_PACKET(datalen); + buf[0] = BICKER_SOH; + buf[1] = BICKER_HEADER + datalen; + buf[2] = idx; + buf[3] = cmd; + buf[buflen - 1] = BICKER_EOT; + + ret = ser_send_buf(upsfd, buf, buflen); + if (ret < 0) { + upslog_with_errno(LOG_WARNING, "ser_send_buf failed"); + return -1; + } else if ((size_t) ret != buflen) { + upslogx(LOG_WARNING, "ser_send_buf returned %" + PRIiSIZE " instead of %" PRIuSIZE, + ret, buflen); + return -1; + } + + upsdebug_hex(3, "bicker_send", buf, buflen); + return datalen; +} + +/** + * Receive a packet with a data field of unknown size. + * @param idx Command index + * @param cmd Command + * @param data Destination buffer or NULL to discard the data field + * @return The size of the data field on success or -1 on errors. + * + * The data field is stored directly in the destination buffer. `data`, + * if not NULL, must have at least BICKER_MAXDATA bytes. + */ +static ssize_t bicker_receive(uint8_t idx, uint8_t cmd, void *data) +{ + ssize_t ret; + size_t buflen, datalen; + uint8_t buf[BICKER_PACKET(BICKER_MAXDATA)]; + + /* Read first two bytes (SOH + size) */ + ret = ser_get_buf_len(upsfd, buf, 2, BICKER_TIMEOUT, 0); + if (ret < 0) { + upslog_with_errno(LOG_WARNING, "Initial ser_get_buf_len failed"); + return -1; + } else if (ret < 2) { + upslogx(LOG_WARNING, "Timeout waiting for response packet"); + return -1; + } else if (buf[0] != BICKER_SOH) { + upslogx(LOG_WARNING, "Received 0x%02X instead of SOH (0x%02X)", + (unsigned)buf[0], (unsigned)BICKER_SOH); + return -1; + } + + /* buf[1] (the size field) is BICKER_HEADER + data length, so */ + datalen = buf[1] - BICKER_HEADER; + + /* Read the rest of the packet */ + buflen = BICKER_PACKET(datalen); + ret = ser_get_buf_len(upsfd, buf + 2, buflen - 2, BICKER_TIMEOUT, 0); + if (ret < 0) { + upslog_with_errno(LOG_WARNING, "ser_get_buf_len failed"); + return -1; + } + + upsdebug_hex(3, "bicker_receive", buf, ret + 2); + + if ((size_t)ret < buflen - 2) { + upslogx(LOG_WARNING, "Timeout waiting for the end of the packet"); + return -1; + } else if (buf[buflen - 1] != BICKER_EOT) { + upslogx(LOG_WARNING, "Received 0x%02X instead of EOT (0x%02X)", + (unsigned)buf[buflen - 1], (unsigned)BICKER_EOT); + return -1; + } else if (idx != 0xEE && buf[2] == 0xEE) { + /* I found experimentally that, when the syntax is + * formally correct but a feature is not supported, + * the device returns 0x01 0x03 0xEE 0x07 0x04. */ + upsdebugx(2, "Command is not supported"); + return -1; + } else if (buf[2] != idx) { + upslogx(LOG_WARNING, "Indexes do not match: sent 0x%02X, received 0x%02X", + (unsigned)idx, (unsigned)buf[2]); + return -1; + } else if (buf[3] != cmd) { + upslogx(LOG_WARNING, "Commands do not match: sent 0x%02X, received 0x%02X", + (unsigned)cmd, (unsigned)buf[3]); + return -1; + } + + if (data != NULL) { + memcpy(data, &buf[1 + BICKER_HEADER], datalen); + } + + return datalen; +} + +/** + * Receive a packet with a data field of known size. + * @param idx Command index + * @param cmd Command + * @param dst Destination buffer or NULL to discard the data field + * @param datalen The expected size of the data field + * @return `datalen` on success or -1 on errors. + * + * `dst`, if not NULL, must have at least `datalen` bytes. If the data + * is not exactly `datalen` bytes, an error is thrown. + */ +static ssize_t bicker_receive_known(uint8_t idx, uint8_t cmd, void *dst, size_t datalen) +{ + ssize_t ret; + uint8_t data[BICKER_MAXDATA]; + + ret = bicker_receive(idx, cmd, data); + if (ret < 0) { + return ret; + } + + if (datalen != (size_t)ret) { + upslogx(LOG_ERR, "Data size does not match: expected %" + PRIuSIZE " but got %" PRIiSIZE " bytes", + datalen, ret); + return -1; + } + + if (dst != NULL) { + memcpy(dst, data, datalen); + } + + return datalen; +} + +/** + * Receive the response of a set/get parameter command. + * @param id Id of the parameter + * @param dst Where to store the response or NULL to discard + * @return The size of the data field on success or -1 on errors. + */ +static ssize_t bicker_receive_parameter(uint8_t id, BickerParameter *dst) +{ + ssize_t ret; + uint8_t data[10]; + BickerParameter parameter; + + if (!bicker_valid_id(id, "bicker_receive_parameter")) { + return -1; + } + + ret = bicker_receive_known(0x07, id, data, sizeof(data)); + if (ret < 0) { + return ret; + } + + /* The returned `data` is in the format: + * [AA] [bbBB] [ccCC] [ddDD] [EE] [ffFF] + * where: + * [AA] = parameter id (Byte) + * [BBbb] = minimum value (UInt16) + * [CCcc] = maximum value (UInt16) + * [DDdd] = standard value (UInt16) + * [EE] = enabled (Bool) + * [FFff] = value (UInt16) + */ + parameter.id = data[0]; + parameter.min = WORDLH(data[1], data[2]); + parameter.max = WORDLH(data[3], data[4]); + parameter.std = WORDLH(data[5], data[6]); + parameter.enabled = data[7]; + parameter.value = WORDLH(data[8], data[9]); + + upsdebugx(3, "Parameter %u = %u (%s, min = %u, max = %u, std = %u)", + (unsigned)parameter.id, (unsigned)parameter.value, + parameter.enabled ? "enabled" : "disabled", + (unsigned)parameter.min, (unsigned)parameter.max, + (unsigned)parameter.std); + + if (dst != NULL) { + memcpy(dst, ¶meter, sizeof(parameter)); + } + + return ret; +} + +/** + * Execute a command that returns an uint8_t value. + * @param idx Command index + * @param cmd Command + * @param dst Destination for the value + * @return The size of the data field on success or -1 on errors. + */ +static ssize_t bicker_read_uint8(uint8_t idx, uint8_t cmd, uint8_t *dst) +{ + ssize_t ret; + + ret = bicker_send(idx, cmd, NULL, 0); + if (ret < 0) { + return ret; + } + + return bicker_receive_known(idx, cmd, dst, 1); +} + +/** + * Execute a command that returns an uint16_t value. + * @param idx Command index + * @param cmd Command + * @param dst Destination for the value or NULL to discard + * @return The size of the data field on success or -1 on errors. + */ +static ssize_t bicker_read_uint16(uint8_t idx, uint8_t cmd, uint16_t *dst) +{ + ssize_t ret; + uint8_t data[2]; + + ret = bicker_send(idx, cmd, NULL, 0); + if (ret < 0) { + return ret; + } + + ret = bicker_receive_known(idx, cmd, data, 2); + if (ret < 0) { + return ret; + } + + if (dst != NULL) { + *dst = WORDLH(data[0], data[1]); + } + + return ret; +} + +/** + * Execute a command that returns an int16_t value. + * @param idx Command index + * @param cmd Command + * @param dst Destination for the value or NULL to discard + * @return The size of the data field on success or -1 on errors. + */ +static ssize_t bicker_read_int16(uint8_t idx, uint8_t cmd, int16_t *dst) +{ + return bicker_read_uint16(idx, cmd, (uint16_t *) dst); +} + +/** + * Execute a command that returns a string. + * @param idx Command index + * @param cmd Command + * @param dst Destination for the string or NULL to discard + * @return The size of the data field on success or -1 on errors. + * + * `dst`, if not NULL, must have at least BICKER_MAXDATA+1 bytes, the + * additional byte needed to accomodate the ending '\0'. + */ +static ssize_t bicker_read_string(uint8_t idx, uint8_t cmd, char *dst) +{ + ssize_t ret; + + ret = bicker_send(idx, cmd, NULL, 0); + if (ret < 0) { + return ret; + } + + ret = bicker_receive(idx, cmd, dst); + if (ret < 0) { + return ret; + } + + dst[ret] = '\0'; + return ret; +} + +/** + * Create a read-write Bicker parameter. + * @param parameter Source information + * @param mapping How that parameter is mapped to NUT + */ +static void bicker_new(const BickerParameter *parameter, const BickerMapping *mapping) +{ + const char *varname; + + varname = mapping->nut_name; + if (parameter->enabled) { + dstate_setinfo(varname, "%u", (unsigned)parameter->value); + } else { + /* dstate_setinfo(varname, "") triggers a GCC warning */ + dstate_setinfo(varname, "%s", ""); + } + + /* Using ST_FLAG_STRING so an empty string can be used + * to identify a disabled parameter */ + dstate_setflags(varname, ST_FLAG_RW | ST_FLAG_STRING); + + /* Just tested it: setting a range does not hinder setting + * an empty string with `dstate_setinfo(varname, "")` */ + if (parameter->min == BICKER_MAXVAL) { + /* The device here is likely corrupt: + * apply a standard range to try using it anyway */ + upslogx(LOG_WARNING, "Parameter %s is corrupt", varname); + dstate_addrange(varname, 0, BICKER_MAXVAL); + } else { + dstate_addrange(varname, parameter->min, parameter->max); + } + + /* Maximum value for an uint16_t is 65535, i.e. 5 digits */ + dstate_setaux(varname, 5); +} + +/** + * Get a Bicker parameter. + * @param id Id of the parameter + * @param dst Where to store the response or NULL to discard + * @return The size of the data field on success or -1 on errors. + */ +static ssize_t bicker_get(uint8_t id, BickerParameter *dst) +{ + ssize_t ret; + + if (!bicker_valid_id(id, "bicker_get")) { + return -1; + } + + ret = bicker_send(0x07, id, NULL, 0); + if (ret < 0) { + return ret; + } + + return bicker_receive_parameter(id, dst); +} + +/** + * Set a Bicker parameter. + * @param id Id of the parameter + * @param enabled 0 to disable, 1 to enable + * @param value What to set in the value field + * @param dst Where to store the response or NULL to discard + * @return The size of the data field on success or -1 on errors. + */ +static ssize_t bicker_set(uint8_t id, uint8_t enabled, uint16_t value, BickerParameter *dst) +{ + ssize_t ret; + uint8_t data[3]; + + if (!bicker_valid_id(id, "bicker_set")) { + return -1; + } else if (enabled > 1) { + upslogx(LOG_ERR, "bicker_set(0x%02X, %d, %u): enabled must be 0 or 1", + (unsigned)id, enabled, (unsigned)value); + return -1; + } + + /* Format of `data` is "[EE] [ffFF]" + * where: + * [EE] = enabled (Bool) + * [FFff] = value (UInt16) + */ + data[0] = enabled; + data[1] = LOWBYTE(value); + data[2] = HIGHBYTE(value); + ret = bicker_send(0x07, id, data, 3); + if (ret < 0) { + return ret; + } + + return bicker_receive_parameter(id, dst); +} + +/** + * Write to a Bicker parameter. + * @param id Id of the parameter + * @param val A string with the value to write + * @param dst Where to store the response or NULL to discard + * @return The size of the data field on success or -1 on errors. + * + * This function is similar to bicker_set() but accepts string values. + * If `val` is NULL or empty, the underlying Bicker parameter is + * disabled and reset to its standard value. + */ +static int bicker_write(uint8_t id, const char *val, BickerParameter *dst) +{ + ssize_t ret; + BickerParameter parameter; + uint8_t enabled; + uint16_t value; + + if (val == NULL || val[0] == '\0') { + ret = bicker_get(id, ¶meter); + if (ret < 0) { + return ret; + } + enabled = 0; + value = parameter.std; + } else { + enabled = 1; + value = atoi(val); + } + + ret = bicker_set(id, enabled, value, ¶meter); + if (ret < 0) { + return ret; + } + + if (dst != NULL) { + memcpy(dst, ¶meter, sizeof(parameter)); + } + + return ret; +} + +/* For some reason the `seconds` delay (at least on my UPSIC-2403D) + * is not honored: the shutdown is always delayed by 2 seconds. This + * fixed delay seems to be independent from the state of the UPS (on + * line or on battery) and from the DIP switches setting. + * + * As response I get the same command with `0xE0` in the data field. + */ +static ssize_t bicker_delayed_shutdown(uint8_t seconds) +{ + ssize_t ret; + uint8_t response; + + ret = bicker_send(0x03, 0x32, &seconds, 1); + if (ret < 0) { + return ret; + } + + ret = bicker_receive_known(0x03, 0x32, &response, 1); + if (ret >= 0) { + upslogx(LOG_INFO, "Shutting down in %d seconds: response = 0x%02X", + seconds, (unsigned)response); + } + + return ret; +} + +static ssize_t bicker_shutdown(void) +{ + const char *str; + int delay; + + str = dstate_getinfo("ups.delay.shutdown"); + delay = str != NULL ? atoi(str) : 0; + if (delay > 255) { + upslogx(LOG_WARNING, "Shutdown delay too big: %d > 255", + delay); + delay = 255; + } + + return bicker_delayed_shutdown(delay); +} + +static int bicker_instcmd(const char *cmdname, const char *extra) +{ + NUT_UNUSED_VARIABLE(extra); + + if (!strcasecmp(cmdname, "shutdown.return")) { + bicker_shutdown(); + } + + upslogx(LOG_NOTICE, "instcmd: unknown command [%s]", cmdname); + return STAT_INSTCMD_UNKNOWN; +} + +static int bicker_setvar(const char *varname, const char *val) +{ + const BickerMapping *mapping; + unsigned i; + BickerParameter parameter; + + /* This should not be needed because when `bicker_write()` is + * successful the `parameter` struct is populated but gcc seems + * not to be smart enough to realize that and errors out with + * "error: ‘parameter...’ may be used uninitialized in this function" + */ + parameter.id = 0; + parameter.min = 0; + parameter.max = BICKER_MAXVAL; + parameter.std = 0; + parameter.enabled = 0; + parameter.value = 0; + + /* Handle mapped parameters */ + for (i = 0; i < SIZEOF_ARRAY(bicker_mappings); ++i) { + mapping = &bicker_mappings[i]; + if (!strcasecmp(varname, mapping->nut_name)) { + if (bicker_write(mapping->bicker_id, val, ¶meter) < 0) { + return STAT_SET_FAILED; + } + + if (parameter.enabled) { + dstate_setinfo(varname, "%u", + (unsigned)parameter.value); + } else { + /* Disabled parameters are removed from NUT */ + dstate_delinfo(varname); + } + return STAT_SET_HANDLED; + } + } + + upslogx(LOG_NOTICE, "setvar: unknown variable [%s]", varname); + return STAT_SET_UNKNOWN; +} + +void upsdrv_initinfo(void) +{ + char string[BICKER_MAXDATA + 1]; + + dstate_setinfo("device.type", "ups"); + + if (bicker_read_string(0x01, 0x60, string) >= 0) { + dstate_setinfo("device.mfr", "%s", string); + } + + if (bicker_read_string(0x01, 0x61, string) >= 0) { + dstate_setinfo("device.serial", "%s", string); + } + + if (bicker_read_string(0x01, 0x62, string) >= 0) { + dstate_setinfo("device.model", "%s", string); + } + + dstate_addcmd("shutdown.return"); + + upsh.instcmd = bicker_instcmd; + upsh.setvar = bicker_setvar; +} + +void upsdrv_updateinfo(void) +{ + uint8_t u8; + uint16_t u16; + int16_t i16; + ssize_t ret; + + ret = bicker_read_uint16(0x01, 0x41, &u16); + if (ret < 0) { + dstate_datastale(); + return; + } + dstate_setinfo("input.voltage", "%.1f", (double) u16 / 1000); + + ret = bicker_read_uint16(0x01, 0x42, &u16); + if (ret < 0) { + dstate_datastale(); + return; + } + dstate_setinfo("input.current", "%.3f", (double) u16 / 1000); + + ret = bicker_read_uint16(0x01, 0x43, &u16); + if (ret < 0) { + dstate_datastale(); + return; + } + dstate_setinfo("output.voltage", "%.3f", (double) u16 / 1000); + + ret = bicker_read_uint16(0x01, 0x44, &u16); + if (ret < 0) { + dstate_datastale(); + return; + } + dstate_setinfo("output.current", "%.3f", (double) u16 / 1000); + + /* This is a supercap UPS so, in this context, + * the "battery" is the supercap stack */ + ret = bicker_read_uint16(0x01, 0x45, &u16); + if (ret < 0) { + dstate_datastale(); + return; + } + dstate_setinfo("battery.voltage", "%.3f", (double) u16 / 1000); + + ret = bicker_read_int16(0x01, 0x46, &i16); + if (ret < 0) { + dstate_datastale(); + return; + } + dstate_setinfo("battery.current", "%.3f", (double) i16 / 1000); + + /* Not implemented for all energy packs: failure acceptable */ + if (bicker_read_uint16(0x01, 0x4A, &u16) >= 0) { + dstate_setinfo("battery.temperature", "%.1f", (double) u16 - 273.16); + } + + /* Not implemented for all energy packs: failure acceptable */ + if (bicker_read_uint8(0x01, 0x48, &u8) >= 0) { + dstate_setinfo("battery.status", "%d%%", u8); + } + + ret = bicker_read_uint8(0x01, 0x47, &u8); + if (ret < 0) { + dstate_datastale(); + return; + } + dstate_setinfo("battery.charge", "%d", u8); + + status_init(); + + /* In `u8` we already have the battery charge */ + if (u8 < atoi(dstate_getinfo("battery.charge.low"))) { + status_set("LB"); + } + + /* StatusFlags() returns an 8 bit register: + * 0. Charging + * 1. Discharging + * 2. Power present + * 3. Battery present + * 4. Shutdown received + * 5. Overcurrent + * 6. --- + * 7. --- + */ + ret = bicker_read_uint8(0x01, 0x40, &u8); + if (ret < 0) { + dstate_datastale(); + return; + } + + if ((u8 & 0x01) > 0) { + status_set("CHRG"); + } + if ((u8 & 0x02) > 0) { + status_set("DISCHRG"); + } + dstate_setinfo("battery.charger.status", + (u8 & 0x01) > 0 ? "charging" : + (u8 & 0x02) > 0 ? "discharging" : + "resting"); + + status_set((u8 & 0x04) > 0 ? "OL" : "OB"); + if ((u8 & 0x20) > 0) { + status_set("OVER"); + } + + status_commit(); + + dstate_dataok(); +} + +void upsdrv_shutdown(void) +{ + int retry; + + for (retry = 1; retry <= BICKER_RETRIES; retry++) { + if (bicker_shutdown() > 0) { + set_exit_flag(-2); /* EXIT_SUCCESS */ + return; + } + } + + upslogx(LOG_ERR, "Shutdown failed!"); + set_exit_flag(-1); +} + +void upsdrv_help(void) +{ +} + +void upsdrv_makevartable(void) +{ +} + +void upsdrv_initups(void) +{ + char string[BICKER_MAXDATA + 1]; + BickerParameter parameter; + const BickerMapping *mapping; + unsigned i; + + upsfd = ser_open(device_path); + ser_set_speed(upsfd, device_path, B38400); + ser_set_dtr(upsfd, 1); + + if (bicker_read_string(0x01, 0x63, string) >= 0) { + dstate_setinfo("ups.firmware", "%s", string); + } + + if (bicker_read_string(0x01, 0x64, string) >= 0) { + dstate_setinfo("battery.type", "%s", string); + } + + /* Not implemented on all UPSes */ + if (bicker_read_string(0x01, 0x65, string) >= 0) { + dstate_setinfo("ups.firmware.aux", "%s", string); + } + + /* Initialize mapped parameters */ + for (i = 0; i < SIZEOF_ARRAY(bicker_mappings); ++i) { + mapping = &bicker_mappings[i]; + if (bicker_get(mapping->bicker_id, ¶meter) >= 0) { + bicker_new(¶meter, mapping); + } + } + + /* Ensure "battery.charge.low" variable is defined */ + if (dstate_getinfo("battery.charge.low") == NULL) { + dstate_setinfo("battery.charge.low", "%d", 20); + } +} + +void upsdrv_cleanup(void) +{ + ser_close(upsfd, device_path); +} diff --git a/drivers/blazer.c b/drivers/blazer.c index 230cd9a89e..af224fb5bb 100644 --- a/drivers/blazer.c +++ b/drivers/blazer.c @@ -554,7 +554,7 @@ void blazer_makevartable(void) addvar(VAR_FLAG, "norating", "Skip reading rating information from UPS"); addvar(VAR_FLAG, "novendor", "Skip reading vendor information from UPS"); - addvar(VAR_FLAG, "protocol", "Preselect communication protocol (skip autodetection)"); + addvar(VAR_VALUE, "protocol", "Preselect communication protocol (skip autodetection)"); } diff --git a/drivers/blazer_ser.c b/drivers/blazer_ser.c index f0cd90e4e8..5f830e3e6a 100644 --- a/drivers/blazer_ser.c +++ b/drivers/blazer_ser.c @@ -31,7 +31,7 @@ #include "blazer.h" #define DRIVER_NAME "Megatec/Q1 protocol serial driver" -#define DRIVER_VERSION "1.60" +#define DRIVER_VERSION "1.62" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/blazer_usb.c b/drivers/blazer_usb.c index 27c5ceecbf..fb0f75a2d5 100644 --- a/drivers/blazer_usb.c +++ b/drivers/blazer_usb.c @@ -37,7 +37,7 @@ #endif #define DRIVER_NAME "Megatec/Q1 protocol USB driver" -#define DRIVER_VERSION "0.17" +#define DRIVER_VERSION "0.20" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -138,6 +138,9 @@ static int phoenix_command(const char *cmd, char *buf, size_t buflen) case LIBUSB_ERROR_TIMEOUT: /** Operation or Connection timed out */ break; + + default: + break; } if (ret < 0) { diff --git a/drivers/clone-outlet.c b/drivers/clone-outlet.c index 62f3cd186d..791a9648c9 100644 --- a/drivers/clone-outlet.c +++ b/drivers/clone-outlet.c @@ -1,7 +1,9 @@ /* -* clone-outlet.c: clone outlet UPS driver +* clone-outlet.c: clone an UPS, treating its outlet as if it were an UPS +* (monitoring only) * * Copyright (C) 2009 - Arjen de Korte +* Copyright (C) 2024 - Jim Klimov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,8 +30,8 @@ #include #endif -#define DRIVER_NAME "clone outlet UPS Driver" -#define DRIVER_VERSION "0.03" +#define DRIVER_NAME "Clone outlet UPS driver" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -67,20 +69,20 @@ static struct { static int dumpdone = 0; static PCONF_CTX_t sock_ctx; -static time_t last_heard = 0, last_ping = 0; +static time_t last_poll = 0, last_heard = 0, last_ping = 0; -#ifdef WIN32 -static char read_buf[SMALLBUF]; -static OVERLAPPED read_overlapped; -#else +#ifndef WIN32 /* TODO: Why not built in WIN32? */ static time_t last_connfail = 0; +#else +static char read_buf[SMALLBUF]; +static OVERLAPPED read_overlapped; #endif static int parse_args(size_t numargs, char **arg) { if (numargs < 1) { - return 0; + goto skip_out; } if (!strcasecmp(arg[0], "PONG")) { @@ -105,7 +107,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 2) { - return 0; + goto skip_out; } /* DELINFO */ @@ -115,7 +117,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 3) { - return 0; + goto skip_out; } /* SETINFO */ @@ -147,6 +149,24 @@ static int parse_args(size_t numargs, char **arg) return 1; } +skip_out: + if (nut_debug_level > 0) { + char buf[LARGEBUF]; + size_t i; + int len = -1; + + memset(buf, 0, sizeof(buf)); + for (i = 0; i < numargs; i++) { + len = snprintfcat(buf, sizeof(buf), "[%s] ", arg[i]); + } + if (len > 0) { + buf[len - 1] = '\0'; + } + + upsdebugx(3, "%s: ignored protocol line with %" PRIuSIZE " keyword(s): %s", + __func__, numargs, numargs < 1 ? "" : buf); + } + return 0; } @@ -154,11 +174,11 @@ static int parse_args(size_t numargs, char **arg) static TYPE_FD sstate_connect(void) { TYPE_FD fd; + const char *dumpcmd = "DUMPALL\n"; #ifndef WIN32 ssize_t ret; int len; - const char *dumpcmd = "DUMPALL\n"; struct sockaddr_un sa; memset(&sa, '\0', sizeof(sa)); @@ -234,7 +254,6 @@ static TYPE_FD sstate_connect(void) /* continued below... */ #else /* WIN32 */ char pipename[SMALLBUF]; - const char *dumpcmd = "DUMPALL\n"; BOOL result = FALSE; snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\%s/%s", dflt_statepath(), device_path); @@ -324,12 +343,12 @@ static int sstate_sendline(const char *buf) DWORD bytesWritten = 0; BOOL result = FALSE; - result = WriteFile (upsfd,buf,strlen(buf),&bytesWritten,NULL); + result = WriteFile (upsfd, buf, strlen(buf), &bytesWritten, NULL); - if( result == 0 ) { + if (result == 0) { ret = 0; } - else { + else { ret = (int)bytesWritten; } #endif @@ -449,6 +468,18 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { + time_t now = time(NULL); + double d; + + /* Throttle tight loops to avoid CPU burn, e.g. when the socket to driver + * is not in fact connected, so a select() somewhere is not waiting much */ + if (last_poll > 0 && (d = difftime(now, last_poll)) < 1.0) { + upsdebugx(5, "%s: too little time (%g sec) has passed since last cycle, throttling", + __func__, d); + usleep(500000); + now = time(NULL); + } + if (sstate_dead(15)) { sstate_disconnect(); extrafd = upsfd = sstate_connect(); @@ -474,6 +505,8 @@ void upsdrv_updateinfo(void) upsdebugx(3, "%s: power state not critical", getval("prefix")); dstate_setinfo("ups.status", "%s", ups.status); + + last_poll = now; } diff --git a/drivers/clone.c b/drivers/clone.c index 9f963b09a6..b15819eb78 100644 --- a/drivers/clone.c +++ b/drivers/clone.c @@ -1,7 +1,9 @@ /* -* clone.c: UPS driver clone +* clone.c: clone an UPS, treating its outlet as if it were an UPS +* (with shutdown INSTCMD support) * * Copyright (C) 2009 - Arjen de Korte +* Copyright (C) 2024 - Jim Klimov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -31,7 +33,7 @@ #endif #define DRIVER_NAME "Clone UPS driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -65,13 +67,14 @@ static int dumpdone = 0, online = 1, outlet = 1; static long offdelay = 120, ondelay = 30; static PCONF_CTX_t sock_ctx; -static time_t last_poll = 0, last_heard = 0, - last_ping = 0; +static time_t last_poll = 0, last_heard = 0, last_ping = 0; + #ifndef WIN32 -static time_t last_connfail = 0; +/* TODO: Why not built in WIN32? */ +static time_t last_connfail = 0; #else -static char read_buf[SMALLBUF]; -static OVERLAPPED read_overlapped; +static char read_buf[SMALLBUF]; +static OVERLAPPED read_overlapped; #endif static int instcmd(const char *cmdname, const char *extra); @@ -80,7 +83,7 @@ static int instcmd(const char *cmdname, const char *extra); static int parse_args(size_t numargs, char **arg) { if (numargs < 1) { - return 0; + goto skip_out; } if (!strcasecmp(arg[0], "PONG")) { @@ -105,7 +108,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 2) { - return 0; + goto skip_out; } /* DELINFO */ @@ -115,7 +118,7 @@ static int parse_args(size_t numargs, char **arg) } if (numargs < 3) { - return 0; + goto skip_out; } /* SETINFO */ @@ -161,6 +164,24 @@ static int parse_args(size_t numargs, char **arg) return 1; } +skip_out: + if (nut_debug_level > 0) { + char buf[LARGEBUF]; + size_t i; + int len = -1; + + memset(buf, 0, sizeof(buf)); + for (i = 0; i < numargs; i++) { + len = snprintfcat(buf, sizeof(buf), "[%s] ", arg[i]); + } + if (len > 0) { + buf[len - 1] = '\0'; + } + + upsdebugx(3, "%s: ignored protocol line with %" PRIuSIZE " keyword(s): %s", + __func__, numargs, numargs < 1 ? "" : buf); + } + return 0; } @@ -168,11 +189,11 @@ static int parse_args(size_t numargs, char **arg) static TYPE_FD sstate_connect(void) { TYPE_FD fd; + const char *dumpcmd = "DUMPALL\n"; #ifndef WIN32 ssize_t ret; int len; - const char *dumpcmd = "DUMPALL\n"; struct sockaddr_un sa; memset(&sa, '\0', sizeof(sa)); @@ -248,7 +269,6 @@ static TYPE_FD sstate_connect(void) /* continued below... */ #else /* WIN32 */ char pipename[SMALLBUF]; - const char *dumpcmd = "DUMPALL\n"; BOOL result = FALSE; snprintf(pipename, sizeof(pipename), "\\\\.\\pipe\\%s/%s", dflt_statepath(), device_path); @@ -338,7 +358,7 @@ static int sstate_sendline(const char *buf) DWORD bytesWritten = 0; BOOL result = FALSE; - result = WriteFile (upsfd,buf,strlen(buf),&bytesWritten,NULL); + result = WriteFile (upsfd, buf, strlen(buf), &bytesWritten, NULL); if (result == 0) { ret = 0; @@ -373,13 +393,13 @@ static int sstate_readline(void) if (ret < 0) { switch(errno) { - case EINTR: - case EAGAIN: - return 0; + case EINTR: + case EAGAIN: + return 0; - default: - upslog_with_errno(LOG_WARNING, "Read from UPS [%s] failed", device_path); - return -1; + default: + upslog_with_errno(LOG_WARNING, "Read from UPS [%s] failed", device_path); + return -1; } } #else @@ -398,19 +418,19 @@ static int sstate_readline(void) switch (pconf_char(&sock_ctx, buf[i])) { - case 1: - if (parse_args(sock_ctx.numargs, sock_ctx.arglist)) { - time(&last_heard); - } - continue; - - case 0: - continue; /* haven't gotten a line yet */ - - default: - /* parse error */ - upslogx(LOG_NOTICE, "Parse error on sock: %s", sock_ctx.errmsg); - return -1; + case 1: + if (parse_args(sock_ctx.numargs, sock_ctx.arglist)) { + time(&last_heard); + } + continue; + + case 0: + continue; /* haven't gotten a line yet */ + + default: + /* parse error */ + upslogx(LOG_NOTICE, "Parse error on sock: %s", sock_ctx.errmsg); + return -1; } } @@ -556,6 +576,16 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { time_t now = time(NULL); + double d; + + /* Throttle tight loops to avoid CPU burn, e.g. when the socket to driver + * is not in fact connected, so a select() somewhere is not waiting much */ + if (last_poll > 0 && (d = difftime(now, last_poll)) < 1.0) { + upsdebugx(5, "%s: too little time (%g sec) has passed since last cycle, throttling", + __func__, d); + usleep(500000); + now = time(NULL); + } if (sstate_dead(15)) { sstate_disconnect(); diff --git a/drivers/cps-hid.c b/drivers/cps-hid.c index cbe714e787..651807a454 100644 --- a/drivers/cps-hid.c +++ b/drivers/cps-hid.c @@ -4,6 +4,7 @@ * 2003 - 2008 Arnaud Quette * 2005 - 2006 Peter Selinger * 2020 - 2024 Jim Klimov + * 2024 Alejandro GonzÃĄlez * * Note: this subdriver was initially generated as a "stub" by the * gen-usbhid-subdriver script. It must be customized. @@ -31,7 +32,7 @@ #include "cps-hid.h" #include "usb-common.h" -#define CPS_HID_VERSION "CyberPower HID 0.80" +#define CPS_HID_VERSION "CyberPower HID 0.81" /* Cyber Power Systems */ #define CPS_VENDORID 0x0764 @@ -77,7 +78,7 @@ static usb_device_id_t cps_usb_device_table[] = { { USB_DEVICE(CPS_VENDORID, 0x0005), NULL }, /* Dynex DX-800U?, CP1200AVR/BC1200D, CP825AVR-G, CP1000AVRLCD, CP1000PFCLCD, CP1500C, CP550HG, etc. */ { USB_DEVICE(CPS_VENDORID, 0x0501), &cps_battery_scale }, - /* OR2200LCDRM2U, OR700LCDRM1U, PR6000LCDRTXL5U */ + /* OR2200LCDRM2U, OR700LCDRM1U, PR6000LCDRTXL5U, CP1350EPFCLCD */ { USB_DEVICE(CPS_VENDORID, 0x0601), NULL }, /* Cyber Energy branded devices by CPS */ @@ -154,12 +155,36 @@ static info_lkp_t cps_battcharge[] = { { 0, NULL, &cps_battcharge_fun, NULL } }; +static const char *cps_battstatus_fun(double value) +{ + static char buf[8]; + + /* assumes `UPS.PowerSummary.FullChargeCapacity` is in %, which should be for */ + /* UPSes that conform to the USB HID spec, given how we read `battery.charge` */ + snprintf(buf, sizeof(buf), "%.0f%%", value); + + return buf; +} + +static info_lkp_t cps_battstatus[] = { + { 0, NULL, &cps_battstatus_fun, NULL } +}; + +static info_lkp_t cps_sensitivity_info[] = { + { 0, "low", NULL, NULL }, + { 1, "normal", NULL, NULL }, + { 2, "high", NULL, NULL }, + { 0, NULL, NULL, NULL } +}; + /* --------------------------------------------------------------- */ /* Vendor-specific usage table */ /* --------------------------------------------------------------- */ /* CPS usage table */ static usage_lkp_t cps_usage_lkp[] = { + { "CPSFirmwareVersion", 0xff0100d0 }, + { "CPSInputSensitivity", 0xff010043 }, { NULL, 0x0 } }; @@ -180,7 +205,6 @@ static hid_info_t cps_hid2nut[] = { { "unmapped.ups.powersummary.designcapacity", 0, 0, "UPS.PowerSummary.DesignCapacity", NULL, "%.0f", 0, NULL }, { "unmapped.ups.powersummary.capacitygranularity1", 0, 0, "UPS.PowerSummary.CapacityGranularity1", NULL, "%.0f", 0, NULL }, { "unmapped.ups.powersummary.capacitygranularity2", 0, 0, "UPS.PowerSummary.CapacityGranularity2", NULL, "%.0f", 0, NULL }, - { "unmapped.ups.powersummary.fullchargecapacity", 0, 0, "UPS.PowerSummary.FullChargeCapacity", NULL, "%.0f", 0, NULL }, #endif /* if WITH_UNMAPPED_DATA_POINTS */ /* Battery page */ @@ -194,17 +218,22 @@ static hid_info_t cps_hid2nut[] = { { "battery.runtime.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.PowerSummary.RemainingTimeLimit", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL }, { "battery.voltage.nominal", 0, 0, "UPS.PowerSummary.ConfigVoltage", NULL, "%.0f", 0, NULL }, { "battery.voltage", 0, 0, "UPS.PowerSummary.Voltage", NULL, "%s", 0, cps_battvolt }, + { "battery.status", 0, 0, "UPS.PowerSummary.FullChargeCapacity", NULL, "%s", 0, cps_battstatus }, /* UPS page */ { "ups.load", 0, 0, "UPS.Output.PercentLoad", NULL, "%.0f", 0, NULL }, { "ups.beeper.status", 0, 0, "UPS.PowerSummary.AudibleAlarmControl", NULL, "%s", 0, beeper_info }, { "ups.test.result", 0, 0, "UPS.Output.Test", NULL, "%s", 0, test_read_info }, + { "ups.power", 0, 0, "UPS.Output.ApparentPower", NULL, "%.0f", 0, NULL }, + { "ups.power.nominal", 0, 0, "UPS.Output.ConfigApparentPower", NULL, "%.0f", 0, NULL }, + { "ups.realpower", 0, 0, "UPS.Output.ActivePower", NULL, "%.0f", 0, NULL }, { "ups.realpower.nominal", 0, 0, "UPS.Output.ConfigActivePower", NULL, "%.0f", 0, NULL }, { "ups.delay.start", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.DelayBeforeStartup", NULL, DEFAULT_ONDELAY, HU_FLAG_ABSENT, NULL}, { "ups.delay.shutdown", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.DelayBeforeShutdown", NULL, DEFAULT_OFFDELAY, HU_FLAG_ABSENT, NULL}, { "ups.timer.start", 0, 0, "UPS.Output.DelayBeforeStartup", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, { "ups.timer.shutdown", 0, 0, "UPS.Output.DelayBeforeShutdown", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, { "ups.timer.reboot", 0, 0, "UPS.Output.DelayBeforeReboot", NULL, "%.0f", HU_FLAG_QUICK_POLL, NULL}, + { "ups.firmware", 0, 0, "UPS.PowerSummary.CPSFirmwareVersion", NULL, "%s", HU_FLAG_STATIC, stringid_conversion }, /* Special case: ups.status & ups.alarm */ { "BOOL", 0, 0, "UPS.PowerSummary.PresentStatus.ACPresent", NULL, NULL, HU_FLAG_QUICK_POLL, online_info }, @@ -222,6 +251,10 @@ static hid_info_t cps_hid2nut[] = { { "input.voltage", 0, 0, "UPS.Input.Voltage", NULL, "%.1f", 0, NULL }, { "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL }, { "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Input.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL }, + /* used by CP1350EPFCLCD */ + { "input.transfer.low", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.LowVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL }, + { "input.transfer.high", ST_FLAG_RW | ST_FLAG_STRING, 10, "UPS.Output.HighVoltageTransfer", NULL, "%.0f", HU_FLAG_SEMI_STATIC, NULL }, + { "input.sensitivity", ST_FLAG_RW | ST_FLAG_STRING, 0, "UPS.Output.CPSInputSensitivity", NULL, "%s", HU_FLAG_SEMI_STATIC | HU_FLAG_ENUM, cps_sensitivity_info }, /* Output page */ { "output.frequency", 0, 0, "UPS.Output.Frequency", NULL, "%.1f", 0, NULL }, diff --git a/drivers/dstate.c b/drivers/dstate.c index 43b48719d5..f6edaf616a 100644 --- a/drivers/dstate.c +++ b/drivers/dstate.c @@ -3,7 +3,8 @@ Copyright (C) 2003 Russell Kroll 2008 Arjen de Korte - 2012 - 2017 Arnaud Quette + 2012-2017 Arnaud Quette + 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -24,15 +25,15 @@ #include #ifndef WIN32 -#include -#include -#include -#include -#include -#include +# include +# include +# include +# include +# include +# include #else -#include -#include "wincompat.h" +# include +# include "wincompat.h" #endif #include "common.h" @@ -105,6 +106,9 @@ static void sock_fail(const char *fn) printf(" - rm %s\n\n", dflt_statepath()); printf(" - mkdir %s\n", dflt_statepath()); break; + + default: + break; } /* @@ -202,6 +206,7 @@ static TYPE_FD sock_open(const char *fn) static void sock_disconnect(conn_t *conn) { #ifndef WIN32 + upsdebugx(3, "%s: disconnecting socket %d", __func__, (int)conn->fd); close(conn->fd); #else /* FIXME not sure if this is the right way to close a connection */ @@ -209,11 +214,14 @@ static void sock_disconnect(conn_t *conn) CloseHandle(conn->read_overlapped.hEvent); conn->read_overlapped.hEvent = INVALID_HANDLE_VALUE; } + upsdebugx(3, "%s: disconnecting named pipe handle %p", __func__, conn->fd); DisconnectNamedPipe(conn->fd); #endif + upsdebugx(5, "%s: finishing parsing context", __func__); pconf_finish(&conn->ctx); + upsdebugx(5, "%s: relinking the chain of connections", __func__); if (conn->prev) { conn->prev->next = conn->next; } else { @@ -226,6 +234,7 @@ static void sock_disconnect(conn_t *conn) /* conntail = conn->prev; */ } + upsdebugx(5, "%s: freeing the conn object", __func__); free(conn); } @@ -281,7 +290,7 @@ static void send_to_all(const char *fmt, ...) result = WriteFile (conn->fd, buf, buflen, &bytesWritten, NULL); if( result == 0 ) { - upsdebugx(2, "write failed on handle %p, disconnecting", conn->fd); + upsdebugx(2, "%s: write failed on handle %p, disconnecting", __func__, conn->fd); sock_disconnect(conn); continue; } @@ -300,7 +309,7 @@ static void send_to_all(const char *fmt, ...) "handle %p failed (ret=%" PRIiSIZE "), disconnecting: %s", __func__, buflen, conn->fd, ret, strerror(errno)); #endif - upsdebugx(6, "failed write: %s", buf); + upsdebugx(6, "%s: failed write: %s", __func__, buf); sock_disconnect(conn); @@ -426,7 +435,7 @@ static int send_to_one(conn_t *conn, const char *fmt, ...) "handle %p failed (ret=%" PRIiSIZE "), disconnecting: %s", __func__, buflen, conn->fd, ret, strerror(errno)); #endif - upsdebugx(6, "failed write: %s", buf); + upsdebugx(6, "%s: failed write: %s", __func__, buf); sock_disconnect(conn); /* TOTHINK: Maybe fallback elsewhere in other cases? */ @@ -474,7 +483,7 @@ static void sock_connect(TYPE_FD sock) fd = accept(sock, (struct sockaddr *) &sa, &salen); if (INVALID_FD(fd)) { - upslog_with_errno(LOG_ERR, "accept on unix fd failed"); + upslog_with_errno(LOG_ERR, "%s: accept on unix fd failed", __func__); return; } @@ -490,7 +499,7 @@ static void sock_connect(TYPE_FD sock) ret = fcntl(fd, F_GETFL, 0); if (ret < 0) { - upslog_with_errno(LOG_ERR, "fcntl get on unix fd failed"); + upslog_with_errno(LOG_ERR, "%s: fcntl get on unix fd failed", __func__); close(fd); return; } @@ -498,7 +507,7 @@ static void sock_connect(TYPE_FD sock) ret = fcntl(fd, F_SETFL, ret | O_NDELAY); if (ret < 0) { - upslog_with_errno(LOG_ERR, "fcntl set O_NDELAY on unix fd failed"); + upslog_with_errno(LOG_ERR, "%s: fcntl set O_NDELAY on unix fd failed", __func__); close(fd); return; } @@ -507,7 +516,7 @@ static void sock_connect(TYPE_FD sock) upsdebugx(0, "%s: keeping default synchronous mode", __func__); } - conn = xcalloc(1, sizeof(*conn)); + conn = (conn_t *)xcalloc(1, sizeof(*conn)); conn->fd = fd; #else /* WIN32 */ @@ -569,6 +578,8 @@ static void sock_connect(TYPE_FD sock) #endif conn->nobroadcast = 0; + conn->readzero = 0; + conn->closing = 0; pconf_init(&conn->ctx, NULL); if (connhead) { @@ -579,31 +590,18 @@ static void sock_connect(TYPE_FD sock) connhead = conn; #ifndef WIN32 - upsdebugx(3, "new connection on fd %d", fd); + upsdebugx(3, "%s: new connection on fd %d", __func__, fd); #else - upsdebugx(3, "new connection on handle %p", sock); + upsdebugx(3, "%s: new connection on handle %p", __func__, sock); #endif } -static int st_tree_dump_conn(st_tree_t *node, conn_t *conn) +static int st_tree_dump_conn_one_node(st_tree_t *node, conn_t *conn) { - int ret; enum_t *etmp; range_t *rtmp; - if (!node) { - return 1; /* not an error */ - } - - if (node->left) { - ret = st_tree_dump_conn(node->left, conn); - - if (!ret) { - return 0; /* write failed in the child */ - } - } - if (!send_to_one(conn, "SETINFO %s \"%s\"\n", node->var, node->val)) { return 0; /* write failed, bail out */ } @@ -651,6 +649,28 @@ static int st_tree_dump_conn(st_tree_t *node, conn_t *conn) } } + return 1; /* everything's OK here ... */ +} + +static int st_tree_dump_conn(st_tree_t *node, conn_t *conn) +{ + int ret; + + if (!node) { + return 1; /* not an error */ + } + + if (node->left) { + ret = st_tree_dump_conn(node->left, conn); + + if (!ret) { + return 0; /* write failed in the child */ + } + } + + if (!st_tree_dump_conn_one_node(node, conn)) + return 0; /* one of writes failed, bail out */ + if (node->right) { return st_tree_dump_conn(node->right, conn); } @@ -683,26 +703,60 @@ static int sock_arg(conn_t *conn, size_t numarg, char **arg) char *sockfn = pipename; /* Just for the report below; not a global var in WIN32 builds */ #endif - upsdebugx(6, "Driver on %s is now handling %s with %" PRIuSIZE " args", - sockfn, numarg ? arg[0] : "", numarg); + upsdebugx(6, "%s: Driver on %s is now handling %s with %" PRIuSIZE " args", + __func__, sockfn, numarg ? arg[0] : "", numarg); if (numarg < 1) { return 0; } - if (!strcasecmp(arg[0], "DUMPALL")) { + if (!strcasecmp(arg[0], "LOGOUT")) { + send_to_one(conn, "OK Goodbye\n"); +#ifndef WIN32 + upsdebugx(2, "%s: received LOGOUT on socket %d, will be disconnecting", __func__, (int)conn->fd); +#else + upsdebugx(2, "%s: received LOGOUT on handle %p, will be disconnecting", __func__, conn->fd); +#endif + /* Let the system flush the reply somehow (or the other + * side to just see it) before we drop the pipe */ + usleep(1000000); + /* err on the safe side, and actually close/free conn separately */ + conn->closing = 1; + upsdebugx(4, "%s: LOGOUT processing finished", __func__); + return 2; + } + + if (!strcasecmp(arg[0], "GETPID")) { + send_to_one(conn, "PID %" PRIiMAX "\n", (intmax_t)getpid()); + return 1; + } + if (!strcasecmp(arg[0], "DUMPALL") || !strcasecmp(arg[0], "DUMPSTATUS") || (!strcasecmp(arg[0], "DUMPVALUE") && numarg > 1)) { /* first thing: the staleness flag (see also below) */ if ((stale == 1) && !send_to_one(conn, "DATASTALE\n")) { return 1; } - if (!st_tree_dump_conn(dtree_root, conn)) { - return 1; - } + if (!strcasecmp(arg[0], "DUMPALL")) { + if (!st_tree_dump_conn(dtree_root, conn)) { + return 1; + } - if (!cmd_dump_conn(conn)) { - return 1; + if (!cmd_dump_conn(conn)) { + return 1; + } + } else { + /* A cheaper version of the dump */ + char *varname = (!strcasecmp(arg[0], "DUMPSTATUS") ? "ups.status" : (numarg > 1 ? arg[1] : NULL)); + st_tree_t *sttmp = (varname ? state_tree_find(dtree_root, varname) : NULL); + + if (!sttmp) { + upsdebugx(1, "%s: %s was requested but currently no %s is known", + __func__, arg[0], NUT_STRARG(varname)); + } else { + if (!st_tree_dump_conn_one_node(sttmp, conn)) + return 1; + } } if ((stale == 0) && !send_to_one(conn, "DATAOK\n")) { @@ -872,6 +926,7 @@ static int sock_arg(conn_t *conn, size_t numarg, char **arg) static void sock_read(conn_t *conn) { ssize_t ret, i; + int ret_arg = -1; #ifndef WIN32 char buf[SMALLBUF]; @@ -890,6 +945,39 @@ static void sock_read(conn_t *conn) return; } } + + if (ret == 0) { + int flags = fcntl(conn->fd, F_GETFL), is_closed = 0; + upsdebugx(2, "%s: read() returned 0; flags=%04X O_NDELAY=%04X", __func__, flags, O_NDELAY); + if (flags & O_NDELAY || O_NDELAY == 0) { + /* O_NDELAY with zero bytes means nothing to read but + * since read() follows a successful select() with + * ready file descriptor, ret shouldn't be 0. + * This may also mean that the counterpart has exited + * and the file descriptor should be reaped. + * e.g. a `driver -c reload -a testups` fires its + * message over Unix socket and disconnects. + */ + is_closed = 1; + } else { + /* assume we will soon have data waiting in the buffer */ + conn->readzero++; + upsdebugx(1, "%s: got zero-sized reads %d times in a row", __func__, conn->readzero); + if (conn->readzero > DSTATE_CONN_READZERO_THROTTLE_MAX) { + is_closed = 2; + } else { + usleep(DSTATE_CONN_READZERO_THROTTLE_USEC); + } + } + + if (is_closed) { + upsdebugx(1, "%s: it seems the other side has closed the connection", __func__); + sock_disconnect(conn); + return; + } + } else { + conn->readzero = 0; + } #else char *buf = conn->buf; DWORD bytesRead; @@ -917,7 +1005,8 @@ static void sock_read(conn_t *conn) continue; case 1: /* try to use it, and complain about unknown commands */ - if (!sock_arg(conn, conn->ctx.numargs, conn->ctx.arglist)) { + ret_arg = sock_arg(conn, conn->ctx.numargs, conn->ctx.arglist); + if (!ret_arg) { size_t arg; upslogx(LOG_INFO, "Unknown command on socket: "); @@ -925,7 +1014,13 @@ static void sock_read(conn_t *conn) for (arg = 0; arg < conn->ctx.numargs && arg < INT_MAX; arg++) { upslogx(LOG_INFO, "arg %d: %s", (int)arg, conn->ctx.arglist[arg]); } + } else if (ret_arg == 2) { + /* closed by LOGOUT processing, conn is free()'d */ + if (i < ret) + upsdebugx(1, "%s: returning early, socket may be not valid anymore", __func__); + return; } + continue; default: /* nothing parsed */ @@ -1002,9 +1097,9 @@ char * dstate_init(const char *prog, const char *devname) sockfd = sock_open(sockname); #ifndef WIN32 - upsdebugx(2, "dstate_init: sock %s open on fd %d", sockname, sockfd); + upsdebugx(2, "%s: sock %s open on fd %d", __func__, sockname, sockfd); #else - upsdebugx(2, "dstate_init: sock %s open on handle %p", sockname, sockfd); + upsdebugx(2, "%s: sock %s open on handle %p", __func__, sockname, sockfd); #endif /* NOTE: Caller must free this string */ @@ -1016,13 +1111,12 @@ int dstate_poll_fds(struct timeval timeout, TYPE_FD arg_extrafd) { int maxfd = 0; /* Unidiomatic use vs. "sockfd" below, which is "int" on non-WIN32 */ int overrun = 0; - conn_t *conn; + conn_t *conn, *cnext; struct timeval now; #ifndef WIN32 int ret; fd_set rfds; - conn_t *cnext; FD_ZERO(&rfds); FD_SET(sockfd, &rfds); @@ -1077,7 +1171,7 @@ int dstate_poll_fds(struct timeval timeout, TYPE_FD arg_extrafd) break; default: - upslog_with_errno(LOG_ERR, "select unix sockets failed"); + upslog_with_errno(LOG_ERR, "%s: select unix sockets failed", __func__); } return overrun; @@ -1095,6 +1189,14 @@ int dstate_poll_fds(struct timeval timeout, TYPE_FD arg_extrafd) } } + for (conn = connhead; conn; conn = cnext) { + cnext = conn->next; + + if (conn->closing) { + sock_disconnect(conn); + } + } + /* tell the caller if that fd woke up */ if (VALID_FD(arg_extrafd) && (FD_ISSET(arg_extrafd, &rfds))) { return 1; @@ -1154,7 +1256,7 @@ int dstate_poll_fds(struct timeval timeout, TYPE_FD arg_extrafd) } if (ret == WAIT_FAILED) { - upslog_with_errno(LOG_ERR, "waitfor failed"); + upslog_with_errno(LOG_ERR, "%s: waitfor failed", __func__); return overrun; } @@ -1176,6 +1278,14 @@ int dstate_poll_fds(struct timeval timeout, TYPE_FD arg_extrafd) } } + for (conn = connhead; conn; conn = cnext) { + cnext = conn->next; + + if (conn->closing) { + sock_disconnect(conn); + } + } + /* tell the caller if that fd woke up */ /* if (VALID_FD(arg_extrafd) && (ret == arg_extrafd)) { @@ -1355,7 +1465,7 @@ void dstate_setaux(const char *var, long aux) sttmp = state_tree_find(dtree_root, var); if (!sttmp) { - upslogx(LOG_ERR, "dstate_setaux: base variable (%s) does not exist", var); + upslogx(LOG_ERR, "%s: base variable (%s) does not exist", __func__, var); return; } @@ -1510,6 +1620,36 @@ void status_init(void) memset(status_buf, 0, sizeof(status_buf)); } +/* check if a status element has been set, return 0 if not, 1 if yes + * (considering a whole-word token in temporary status_buf) */ +int status_get(const char *buf) +{ + char *s = NULL; + size_t offset = 0, buflen = 0; + + if (!buf || !*buf || !*status_buf) + return 0; + + s = strstr(status_buf, buf); + buflen = strlen(buf); + + /* not found */ + if (!s) + return 0; + + offset = status_buf - s; + if (offset == 0 || status_buf[offset - 1] == ' ') { + /* We have hit the start of token */ + if (s[buflen] == '\0' || s[buflen] == ' ') { + /* And we have hit the end of token */ + return 1; + } + } + + /* buf was a substring of some other token */ + return 0; +} + /* add a status element */ void status_set(const char *buf) { @@ -1518,6 +1658,11 @@ void status_set(const char *buf) return; } + if (status_get(buf)) { + upsdebugx(2, "%s: status was already set: %s", __func__, buf); + return; + } + /* separate with a space if multiple elements are present */ if (strlen(status_buf) > 0) { snprintfcat(status_buf, sizeof(status_buf), " %s", buf); @@ -1675,9 +1820,15 @@ void alarm_set(const char *buf) # pragma GCC diagnostic pop #endif -/* write the status_buf into the info array */ +/* write the status_buf into the info array for "ups.alarm" */ void alarm_commit(void) { + /* Note this is a bit different from `device_alarm_commit(0);` + * because here we also increase AND zero out the alarm count. + * alarm_active = 0; device_alarm_commit(0); + * would be equivalent, but too intimate for later maintenance. + */ + if (strlen(alarm_buf) > 0) { dstate_setinfo("ups.alarm", "%s", alarm_buf); alarm_active = 1; @@ -1694,6 +1845,8 @@ void device_alarm_init(void) } /* same as above, but writes to "device.X.ups.alarm" or "ups.alarm" */ +/* Note that 20 chars below just allow for a 2-digit "X" */ +/* FIXME? Shouldn't this be changed to be a LARGEBUF aka sizeof(alarm_buf) ? */ void device_alarm_commit(const int device_number) { char info_name[20]; diff --git a/drivers/dstate.h b/drivers/dstate.h index 2fa754defa..bb578f61aa 100644 --- a/drivers/dstate.h +++ b/drivers/dstate.h @@ -3,6 +3,7 @@ Copyright (C) 2003 Russell Kroll 2012-2017 Arnaud Quette + 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -52,8 +53,16 @@ typedef struct conn_s { struct conn_s *prev; struct conn_s *next; int nobroadcast; /* connections can request to ignore send_to_all() updates */ + int readzero; /* how many times in a row we had zero bytes read; see DSTATE_CONN_READZERO_THROTTLE_USEC and DSTATE_CONN_READZERO_THROTTLE_MAX */ + int closing; /* raised during LOGOUT processing, to close the socket when time is right */ } conn_t; +/* sleep after read()ing zero bytes */ +#define DSTATE_CONN_READZERO_THROTTLE_USEC 500 + +/* close socket after read()ing zero bytes this many times in a row */ +#define DSTATE_CONN_READZERO_THROTTLE_MAX 5 + #include "main.h" /* for set_exit_flag(); uses conn_t itself */ extern struct ups_handler upsh; @@ -92,6 +101,10 @@ int dstate_is_stale(void); /* clean out the temp space for a new pass */ void status_init(void); +/* check if a status element has been set, return 0 if not, 1 if yes + * (considering a whole-word token in temporary status_buf) */ +int status_get(const char *buf); + /* add a status element */ void status_set(const char *buf); diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 7c3a435f38..939bf16b1b 100644 --- a/drivers/dummy-ups.c +++ b/drivers/dummy-ups.c @@ -48,7 +48,7 @@ #include "dummy-ups.h" #define DRIVER_NAME "Device simulation and repeater driver" -#define DRIVER_VERSION "0.18" +#define DRIVER_VERSION "0.19" /* driver description structure */ upsdrv_info_t upsdrv_info = diff --git a/drivers/etapro.c b/drivers/etapro.c index 2e5fa812ef..6e4f593fe8 100644 --- a/drivers/etapro.c +++ b/drivers/etapro.c @@ -55,7 +55,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "ETA PRO driver" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -106,6 +106,8 @@ etapro_get_response(const char *resp_type) case 'T': dstate_setinfo("ups.mfr.date", "%s", cp + 2); return 0; + default: + break; } /* Handle all other responses as hexadecimal numbers. */ val = 0; diff --git a/drivers/everups.c b/drivers/everups.c index dc032b0fec..6a3b09d57f 100644 --- a/drivers/everups.c +++ b/drivers/everups.c @@ -21,7 +21,7 @@ #include "serial.h" #define DRIVER_NAME "Ever UPS driver (serial)" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/gamatronic.c b/drivers/gamatronic.c index bf0c6ad333..9c1b6fa6eb 100644 --- a/drivers/gamatronic.c +++ b/drivers/gamatronic.c @@ -33,7 +33,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Gamatronic UPS driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.06" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -53,13 +53,23 @@ upsdrv_info_t upsdrv_info = { #define SER_WAIT_SEC 1 /* allow 3.0 sec for ser_get calls */ #define SER_WAIT_USEC 0 +/* Reasons for this number are lost in history; protocol docs at + * https://networkupstools.org/protocols/sec-protocol.html and + * https://networkupstools.org/protocols/us9003.html specify 128 + * bytes as the max data length in a message; +5 chars for meta + * data and +1 for `\0` termination yields maybe 134 as the limit. + */ +/* FIXME: Some methods get a buffer and assume its length. + * This should normally be a parameter! */ +#define GAMATRONIC_BUF_LEN 140 + static int sec_upsrecv (char *buf) { char lenbuf[4]; int ret; - ser_get_line(upsfd, buf, 140, ENDCHAR, IGNCHARS,SER_WAIT_SEC, SER_WAIT_USEC); - if (buf[0] == SEC_MSG_STARTCHAR) { + ser_get_line(upsfd, buf, GAMATRONIC_BUF_LEN, ENDCHAR, IGNCHARS, SER_WAIT_SEC, SER_WAIT_USEC); + if (buf[0] == SEC_MSG_STARTCHAR) { switch (buf[1]) { case SEC_NAK: return(-1); @@ -69,11 +79,24 @@ static int sec_upsrecv (char *buf) strncpy(lenbuf, buf+2, 3); lenbuf[3] = '\0'; ret = atoi(lenbuf); + if (ret > GAMATRONIC_BUF_LEN) { + upslogx(1, "%s: got a longer response message " + "than expected for protocol: %d (%s) > %d", + __func__, ret, lenbuf, GAMATRONIC_BUF_LEN); + ret = GAMATRONIC_BUF_LEN; + } if (ret > 0) { - strcpy(buf,buf+5); + /* Note: ser_get_line() returns a + * safely zero-terminated string */ + memmove(buf, buf+5, (GAMATRONIC_BUF_LEN - 5)); return(ret); } - else return (-2); + + /* else (ret <= 0) : */ + upslogx(1, "%s: invalid response message length: %s", + __func__, lenbuf); + return (-2); + default: return(-2); } @@ -84,7 +107,7 @@ static int sec_upsrecv (char *buf) static ssize_t sec_cmd(const char mode, const char *command, char *msgbuf, ssize_t *buflen) { - char msg[140]; + char msg[GAMATRONIC_BUF_LEN]; ssize_t ret; memset(msg, 0, sizeof(msg)); @@ -201,7 +224,7 @@ static void update_pseudovars( void ) static void sec_poll ( int pollflag ) { ssize_t msglen; int f, q; - char retbuf[140], *n, *r; + char retbuf[GAMATRONIC_BUF_LEN], *n, *r; for (q=0; q #include diff --git a/drivers/generic_gpio_libgpiod.c b/drivers/generic_gpio_libgpiod.c index dded983b85..ab88271cfc 100644 --- a/drivers/generic_gpio_libgpiod.c +++ b/drivers/generic_gpio_libgpiod.c @@ -27,7 +27,7 @@ #include "generic_gpio_libgpiod.h" #define DRIVER_NAME "GPIO UPS driver" -#define DRIVER_VERSION "1.01" +#define DRIVER_VERSION "1.02" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/generic_gpio_libgpiod.h b/drivers/generic_gpio_libgpiod.h index 66f13b4511..77deacefb8 100644 --- a/drivers/generic_gpio_libgpiod.h +++ b/drivers/generic_gpio_libgpiod.h @@ -21,7 +21,7 @@ */ #ifndef GENERIC_GPIO_LIBGPIOD_H_SEEN -#define GENERIC_GPIO_LIBGPIOD_H_SEEN +#define GENERIC_GPIO_LIBGPIOD_H_SEEN 1 #include diff --git a/drivers/generic_modbus.c b/drivers/generic_modbus.c index b242c5a6a5..c5a9fd5090 100644 --- a/drivers/generic_modbus.c +++ b/drivers/generic_modbus.c @@ -27,7 +27,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "NUT Generic Modbus driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.05" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ diff --git a/drivers/genericups.c b/drivers/genericups.c index acd2e3f6d6..fe9f1dbc9b 100644 --- a/drivers/genericups.c +++ b/drivers/genericups.c @@ -31,7 +31,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Generic contact-closure UPS driver" -#define DRIVER_VERSION "1.39" +#define DRIVER_VERSION "1.40" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/hidparser.c b/drivers/hidparser.c index fd52fb3208..a7fb05ec4c 100644 --- a/drivers/hidparser.c +++ b/drivers/hidparser.c @@ -350,6 +350,9 @@ static int HIDParse(HIDParser_t *pParser, HIDData_t *pData) /* can't handle long items, but should at least skip them */ pParser->Pos += (uint8_t)(pParser->Value & 0xff); break; + + default: + break; } } /* while ((Found < 0) && (pParser->Pos < pParser->ReportDescSize)) */ diff --git a/drivers/hidparser.h b/drivers/hidparser.h index 07b9f267aa..6d82927bd5 100644 --- a/drivers/hidparser.h +++ b/drivers/hidparser.h @@ -23,7 +23,7 @@ * -------------------------------------------------------------------------- */ #ifndef NUT_HID_PARSER_H_SEEN -#define NUT_HID_PARSER_H_SEEN +#define NUT_HID_PARSER_H_SEEN 1 #ifdef __cplusplus @@ -32,7 +32,14 @@ extern "C" { /* *INDENT-ON* */ #endif /* __cplusplus */ -#include "config.h" +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif + #include "hidtypes.h" /* Include "usb-common.h" or "libshut.h" as appropriate, to define the diff --git a/drivers/huawei-ups2000.c b/drivers/huawei-ups2000.c index 9ebf86ff28..403eb1bdcd 100644 --- a/drivers/huawei-ups2000.c +++ b/drivers/huawei-ups2000.c @@ -51,7 +51,7 @@ #include "timehead.h" /* fallback gmtime_r() variants if needed (e.g. some WIN32) */ #define DRIVER_NAME "NUT Huawei UPS2000 (1kVA-3kVA) RS-232 Modbus driver" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.07" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 1 @@ -1035,6 +1035,9 @@ static int ups2000_update_alarm(void) case ALARM_CLEAR_DEPENDING: upslogx(loglevel, "This alarm is auto or manual cleared " "depending on the specific problem."); + break; + default: + break; } ups2000_alarm[i].active = 1; @@ -1858,7 +1861,7 @@ static time_t time_seek(time_t t, int seconds) if (!t) fatalx(EXIT_FAILURE, "time_seek() failed!"); - if (!gmtime_r(&t, &time_tm)) + if (gmtime_r(&t, &time_tm) == NULL) fatalx(EXIT_FAILURE, "time_seek() failed!"); time_tm.tm_sec += seconds; diff --git a/drivers/hwmon_ina219.c b/drivers/hwmon_ina219.c new file mode 100644 index 0000000000..988208a2a1 --- /dev/null +++ b/drivers/hwmon_ina219.c @@ -0,0 +1,476 @@ +/* hwmon.c Driver for INA219 hwmon-based power monitors. + + Copyright (C) 2024 Jan Viktorin + + 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 "main.h" +#include "nut_float.h" +#include "nut_stdint.h" + +#include +#include +#include +#include + +#ifndef STRFY0 +# define STRFY0(x) #x +#endif +#ifndef STRFY +# define STRFY(x) STRFY0(x) +#endif + +#define SYSFS_HWMON_DIR "/sys/class/hwmon" +#define BATTERY_CHARGE_LOW 15 +#define DRIVER_NAME "hwmon-INA219 UPS driver" +#define DRIVER_VERSION "0.01" + +upsdrv_info_t upsdrv_info = { + DRIVER_NAME, + DRIVER_VERSION, + "Jan Viktorin ", + DRV_EXPERIMENTAL, + { NULL }, +}; + +/** + * @brief Path usually pointing to /sys/class/hwmon/hwmonX. + */ +static char ina219_base_path[PATH_MAX]; + +/** + * @brief Threshold for detection of LB status. + */ +static unsigned int battery_charge_low = BATTERY_CHARGE_LOW; + +/** + * @brief Battery voltage (mV) when it is considered depleted. + */ +static unsigned int battery_voltage_min; + +/** + * @brief Battery voltage (mV) when it is considered fully charged. + */ +static unsigned int battery_voltage_max; + +/** + * @brief Voltage value as recently read from in1_input (mV). + * @see https://docs.kernel.org/hwmon/ina2xx.html + */ +static int voltage = 0; + +/** + * @brief Current value as recently read from curr1_input (mA). + * @see https://docs.kernel.org/hwmon/ina2xx.html + */ +static int current = 0; + +static int file_contains(const char *path, const char *text) +{ + FILE *f; + char buf[128]; + size_t len; + + if ((f = fopen(path, "r")) == NULL) { + upslog_with_errno(LOG_ERR, "unable to open %s", path); + return 0; + } + + len = fread(buf, 1, sizeof(buf), f); + fclose(f); + upsdebugx(4, "read %" PRIuSIZE " bytes", len); + + if (strlen(text) != len) + return 0; + + if (memcmp(buf, text, len)) + return 0; + + return 1; +} + +static int file_read_number(const char *path, int *value) +{ + char buf[128]; + ssize_t ret; + int fd; + + if ((fd = open(path, O_RDONLY)) < 0) + return -errno; + + if ((ret = pread(fd, buf, sizeof(buf), 0)) < 0) { + const int _e = -errno; + close(fd); + return _e; + } + else { + close(fd); + + buf[ret] = '\0'; + *value = atoi(buf); + return 0; + } +} + +static int detect_ina219(const char *ina219_dir) +{ + char namepath[PATH_MAX]; + + upsdebugx(3, "checking %s", ina219_dir); + + snprintf(namepath, sizeof(namepath), "%s/name", ina219_dir); + + if (!file_contains(namepath, "ina219\n")) + return -ENODEV; + + upsdebugx(3, "detected ina219 at %s", ina219_dir); + return 0; +} + +static int scan_hwmon_ina219(const char *sysfs_hwmon_dir) +{ + DIR *sysfs; + struct dirent *entry; + int ret; + + if (strcmp(device_path, "auto")) { + if ((ret = detect_ina219(device_path)) < 0) { + fatal_with_errno(EXIT_FAILURE, + "not a valid hwmon ina219 dir: '%s'\n", device_path); + } + + snprintf(ina219_base_path, sizeof(ina219_base_path), "%s", device_path); + return 0; + } + + upslogx(LOG_NOTICE, "scanning %s for ina219", sysfs_hwmon_dir); + + if ((sysfs = opendir(sysfs_hwmon_dir)) == NULL) { + const int _e = -errno; + upslog_with_errno(LOG_ERR, "unable to open %s", sysfs_hwmon_dir); + return _e; + } + + while ((entry = readdir(sysfs)) != NULL) { + char hwmon_dir[PATH_MAX]; + + if (entry->d_type != DT_DIR && entry->d_type != DT_LNK) { + upsdebugx(3, "path %s/%s is not directory/symlink", sysfs_hwmon_dir, + entry->d_name); + continue; + } + + if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, "..")) { + upsdebugx(3, "skipping path %s/%s", sysfs_hwmon_dir, entry->d_name); + continue; + } + + snprintf(hwmon_dir, sizeof(hwmon_dir), "%s/%s", + sysfs_hwmon_dir, entry->d_name); + if (detect_ina219(hwmon_dir) < 0) { + /* Log this one only if really troubleshooting, + * this is quite an anticipated and noisy situation, + * to disregard most of the directories in sysfs :) + */ + upsdebugx(6, "skipping path %s: not the expected subsystem", hwmon_dir); + continue; + } + + snprintf(ina219_base_path, sizeof(ina219_base_path), "%s", hwmon_dir); + closedir(sysfs); + return 0; + } + + closedir(sysfs); + return -ENODEV; +} + +static int update_intvar( + const char *base_path, + const char *name, + int *value) +{ + char path[PATH_MAX]; + int ret; + + if (snprintf(path, sizeof(path), "%s/%s", base_path, name) >= PATH_MAX) { + errno = ENAMETOOLONG; + upslog_with_errno(LOG_ERR, "snprintf(%s/%s) has failed", base_path, name); + return -ENAMETOOLONG; + } + + if ((ret = file_read_number(path, value)) < 0) { + errno = -ret; + upslog_with_errno(LOG_ERR, "file_read_number(%s) has failed", path); + return ret; + } + + return 0; +} + +static int update_voltage(void) +{ + return update_intvar(ina219_base_path, "in1_input", &voltage); +} + +static int update_current(void) +{ + return update_intvar(ina219_base_path, "curr1_input", ¤t); +} + +void upsdrv_makevartable(void) +{ + addvar(VAR_VALUE, "sysfs_dir", + "Path to sysfs dir of hwmon if port=auto (" SYSFS_HWMON_DIR ")"); +} + +static int parse_voltage(const char *s, double *v) +{ + errno = 0; + *v = strtod(s, NULL); + + if (errno) + return -errno; + + if (!isnormal(*v) || !isfinite(*v)) + return -EDOM; + + return 0; +} + +static void battery_voltage_params_init(void) +{ + /* Note: with this device, we do not really expect these values + * to be reported by hardware or change over time, so this is + * only parsed (from built-in or user-provided defaults) into + * C variables once. + */ + const char *d_min = dstate_getinfo("battery.voltage.low"); + const char *d_max = dstate_getinfo("battery.voltage.high"); + double volt_nominal; + int ret; + + if (!d_min || !d_max) { + const char *d_nom; + + d_nom = dstate_getinfo("battery.voltage.nominal"); + upsdebugx(4, "battery.voltage.nominal = '%s'\n", d_nom); + + if (d_nom == NULL) { + volt_nominal = 3.6; + } + else { + if ((ret = parse_voltage(d_nom, &volt_nominal)) < 0) { + errno = -ret; + fatal_with_errno(EXIT_FAILURE, + "battery.voltage.nominal is invalid: '%s'\n", d_nom); + } + } + + upslogx(LOG_NOTICE, + "guess battery params from battery.voltage.nominal = %.1lf V\n", + volt_nominal); + + if (d_equal(volt_nominal, 3.6)) { + battery_voltage_min = 3000; + battery_voltage_max = 4250; + } + else if (d_equal(volt_nominal, 3.7)) { + battery_voltage_min = 3000; + battery_voltage_max = 4250; + } + else if (d_equal(volt_nominal, 3.8)) { + battery_voltage_min = 3000; + battery_voltage_max = 4350; + } + else if (d_equal(volt_nominal, 3.85)) { + battery_voltage_min = 3000; + battery_voltage_max = 4400; + } + else { + fatalx(EXIT_FAILURE, "unsupported battery.voltage.nominal: %lf\n", + volt_nominal); + } + } + else { + double tmp; + + if ((ret = parse_voltage(d_min, &tmp)) < 0) { + errno = -ret; + fatal_with_errno(EXIT_FAILURE, + "invalid battery.voltage.low: '%s'\n", d_min); + } + else { + battery_voltage_min = (unsigned int) (tmp * 1000.0); + } + + if ((ret = parse_voltage(d_max, &tmp)) < 0) { + errno = -ret; + fatal_with_errno(EXIT_FAILURE, + "invalid battery.voltage.high: '%s'\n", d_max); + } + else { + battery_voltage_max = (unsigned int) (tmp * 1000.0); + } + } + + upslogx(LOG_NOTICE, "battery.voltage.low = %u mV\n", battery_voltage_min); + upslogx(LOG_NOTICE, "battery.voltage.high = %u mV\n", battery_voltage_max); +} + +static int parse_charge(const char *s, unsigned int *v) +{ + long tmp; + + errno = 0; + tmp = strtol(s, NULL, 0); + + if (errno) + return -errno; + + if (tmp < 1 || tmp > 100) + return -ERANGE; + + *v = (int) tmp; + return 0; +} + +static void battery_charge_params_init(void) +{ + const char *v_lb = dstate_getinfo("battery.charge.low"); + int ret; + + if (v_lb) { + if ((ret = parse_charge(v_lb, &battery_charge_low)) < 0) { + errno = -ret; + fatal_with_errno(EXIT_FAILURE, + "battery.charge.low is invalid: '%s'", v_lb); + } + } + + upslogx(LOG_NOTICE, "battery.charge.low = %u\n", battery_charge_low); +} + +void upsdrv_initups(void) +{ + const char *hwmon_dir = SYSFS_HWMON_DIR; + int ret; + + if (getval("sysfs_dir")) + hwmon_dir = getval("sysfs_dir"); + + if ((ret = scan_hwmon_ina219(hwmon_dir)) < 0) { + errno = -ret; + fatal_with_errno(EXIT_FAILURE, "scan_hwmon_ina219(%s) has failed", + hwmon_dir); + } + + battery_voltage_params_init(); + battery_charge_params_init(); +} + +void upsdrv_initinfo(void) +{ + dstate_setinfo("ups.mfr", "%s", "Texas Instruments"); + dstate_setinfo("ups.model", "%s", "INA219"); + dstate_setinfo("ups.type", "%s", "ups"); + dstate_setinfo("device.mfr", "%s", "Texas Instruments"); + dstate_setinfo("device.model", "%s", "INA219"); + dstate_setinfo("device.type", "%s", "ups"); + dstate_setinfo("device.description", "%s", + "Bidirectional Current/Power Monitor With I2C Interface"); + + dstate_setinfo("battery.charge.low", "%d", battery_charge_low); +} + +static unsigned int battery_charge_compute(void) +{ + const double divisor = (battery_voltage_max - battery_voltage_min) / 100.0; + double charge; + + if (voltage < 0) + return 0; + + if (((unsigned int) voltage) > battery_voltage_min) + charge = voltage - battery_voltage_min; + else + charge = 0; + + charge /= divisor; + charge = charge > 100 ? 100 : charge; + + return (unsigned int) charge; +} + +void upsdrv_updateinfo(void) +{ + unsigned int charge = 0; + int stale = 0; + + if (update_voltage() < 0) + stale = 1; + + upsdebugx(3, "Battery voltage: %.3fV", voltage / 1000.0); + + if (update_current() < 0) + stale = 1; + + upsdebugx(3, "Battery current: %.3fA", current / 1000.0); + + if (stale) { + dstate_datastale(); + return; + } + + status_init(); + + charge = battery_charge_compute(); + + status_set(current <= 0 ? "OL" : "OB"); + + if (current < 0) + status_set("CHRG"); + else if (current > 0) + status_set("DISCHRG"); + + if (charge <= battery_charge_low) + status_set("LB"); + + dstate_setinfo("battery.voltage", "%.3f", voltage / 1000.0); + dstate_setinfo("battery.current", "%.3f", current / 1000.0); + dstate_setinfo("battery.charge", "%d", charge); + + if (charge <= battery_charge_low && current > 0) + dstate_setinfo("battery.runtime", "%d", 60); // 1 minute + + status_commit(); + dstate_dataok(); +} + +void upsdrv_shutdown(void) +{ + upslogx(LOG_ERR, "shutdown not supported"); + set_exit_flag(-1); +} + +void upsdrv_help(void) +{ + /* No special options in this driver (vars/flags are auto-documented) */ +} + +void upsdrv_cleanup(void) +{ +} diff --git a/drivers/isbmex.c b/drivers/isbmex.c index 923b46f7ee..eff64b12f1 100644 --- a/drivers/isbmex.c +++ b/drivers/isbmex.c @@ -22,12 +22,12 @@ #include "main.h" #include "serial.h" +#include "nut_float.h" /* For sqrt() */ -#include /* for sqrt */ #include #define DRIVER_NAME "ISBMEX UPS driver" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/ivtscd.c b/drivers/ivtscd.c index 3a07c72281..318e09c241 100644 --- a/drivers/ivtscd.c +++ b/drivers/ivtscd.c @@ -25,7 +25,7 @@ #include "attribute.h" #define DRIVER_NAME "IVT Solar Controller driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.05" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/libhid.c b/drivers/libhid.c index 74b9991973..5ba17b1f0f 100644 --- a/drivers/libhid.c +++ b/drivers/libhid.c @@ -1,6 +1,6 @@ /*! * @file libhid.c - * @brief HID Library - User API (Generic HID Access using MGE HIDParser) + * @brief NUT HID Library - User API (Generic HID Access using MGE HIDParser) * * @author Copyright (C) 2003 - 2007 * Arnaud Quette && @@ -39,7 +39,7 @@ #ifdef HAVE_STRINGS_H # include #endif -/* #include */ +/* #include "nut_float.h" */ #include "libhid.h" #include "hidparser.h" #include "common.h" /* for xmalloc, upsdebugx prototypes */ @@ -47,10 +47,10 @@ /* Communication layers and drivers (USB and MGE SHUT) */ #if (defined SHUT_MODE) && SHUT_MODE - #include "libshut.h" +# include "libshut.h" communication_subdriver_t *comm_driver = &shut_subdriver; #else /* !SHUT_MODE => USB */ - #include "nut_libusb.h" +# include "nut_libusb.h" communication_subdriver_t *comm_driver = &usb_subdriver; #endif /* SHUT_MODE / USB */ @@ -239,7 +239,7 @@ static int refresh_report_buffer(reportbuf_t *rbuf, hid_dev_handle_t udev, HIDDa r = (size_t)ret; if (rbuf->len[id] != r) { - /* e.g. if maxreportsize flag was set */ + /* e.g. if max_report_size flag was set */ upsdebugx(2, "%s: expected %" PRIuSIZE " bytes, but got %" PRIuSIZE " instead", __func__, rbuf->len[id], r); diff --git a/drivers/libhid.h b/drivers/libhid.h index 613fda9aae..4a916a6fb4 100644 --- a/drivers/libhid.h +++ b/drivers/libhid.h @@ -1,6 +1,6 @@ /*! * @file libhid.h - * @brief HID Library - User API + * @brief NUT HID Library - User API * * @author Copyright (C) 2003 - 2007 * Arnaud Quette && @@ -27,9 +27,15 @@ * -------------------------------------------------------------------------- */ #ifndef NUT_LIBHID_H_SEEN -#define NUT_LIBHID_H_SEEN - -#include "config.h" +#define NUT_LIBHID_H_SEEN 1 + +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif #include #include "nut_stdint.h" @@ -38,19 +44,19 @@ #include "timehead.h" #if (defined SHUT_MODE) && SHUT_MODE - #include "libshut.h" +# include "libshut.h" typedef SHUTDevice_t HIDDevice_t; typedef char HIDDeviceMatcher_t; typedef usb_dev_handle hid_dev_handle_t; typedef shut_communication_subdriver_t communication_subdriver_t; - #define HID_DEV_HANDLE_CLOSED (hid_dev_handle_t)(ERROR_FD_SER) +# define HID_DEV_HANDLE_CLOSED (hid_dev_handle_t)(ERROR_FD_SER) #else /* !SHUT_MODE => USB */ - #include "nut_libusb.h" /* includes usb-common.h */ +# include "nut_libusb.h" /* includes usb-common.h */ typedef USBDevice_t HIDDevice_t; typedef USBDeviceMatcher_t HIDDeviceMatcher_t; typedef usb_dev_handle * hid_dev_handle_t; typedef usb_communication_subdriver_t communication_subdriver_t; - #define HID_DEV_HANDLE_CLOSED (hid_dev_handle_t)(NULL) +# define HID_DEV_HANDLE_CLOSED (hid_dev_handle_t)(NULL) #endif /* SHUT_MODE / USB */ /* use explicit booleans */ diff --git a/drivers/libshut.c b/drivers/libshut.c index 3601c0c344..7a9f78bd34 100644 --- a/drivers/libshut.c +++ b/drivers/libshut.c @@ -43,7 +43,7 @@ #include "common.h" /* for xmalloc, upsdebugx prototypes */ #define SHUT_DRIVER_NAME "SHUT communication driver" -#define SHUT_DRIVER_VERSION "0.88" +#define SHUT_DRIVER_VERSION "0.89" /* communication driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -376,15 +376,10 @@ static int libshut_open( usb_ctrl_charbuf rdbuf, usb_ctrl_charbufsize rdlen)) { int ret, res; - /* Below we cast this buffer as sometimes containing entried of type - * "struct device_descriptor_s" or "struct my_hid_descriptor". - * Currently both of these are sized "2", and I don't see a way - * to require a "max()" of such sizes to align for generally. - */ usb_ctrl_char buf[20] __attribute__((aligned(4))); char string[MAX_STRING_SIZE]; - struct my_hid_descriptor *desc; - struct device_descriptor_s *dev_descriptor; + struct my_hid_descriptor desc_buf, *desc = &desc_buf; + struct device_descriptor_s dev_descriptor_buf, *dev_descriptor = &dev_descriptor_buf; /* report descriptor */ usb_ctrl_char rdbuf[MAX_REPORT_SIZE]; @@ -394,6 +389,50 @@ static int libshut_open( * version is at index 1 (in which case, bcdDevice == 0x0202) */ usb_ctrl_descindex hid_desc_index = 0; +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-type-limit-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#pragma clang diagnostic ignored "-Wtautological-compare" +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + static int usb_hid_number_opts_parsed = 0; + if (!usb_hid_number_opts_parsed) { + const char *s; + unsigned short us = 0; + if ((s = getval("usb_hid_desc_index"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_DESCINDEX_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_desc_index", __func__); + } + hid_desc_index = (usb_ctrl_descindex)us; + } + usb_hid_number_opts_parsed = 1; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE) ) +# pragma GCC diagnostic pop +#endif + if (!arg_device_path) { fatalx(EXIT_FAILURE, "%s: arg_device_path=null", __func__); } @@ -426,21 +465,7 @@ static int libshut_open( } /* Get DEVICE descriptor */ -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wcast-align" -#endif - dev_descriptor = (struct device_descriptor_s *)buf; -#ifdef __clang__ -# pragma clang diagnostic pop -#endif -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + memcpy(dev_descriptor, buf, sizeof(struct device_descriptor_s)); res = shut_get_descriptor(*arg_upsfd, USB_DT_DEVICE, 0, buf, USB_DT_DEVICE_SIZE); /* res = shut_control_msg(devp, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, (USB_DT_DEVICE << 8) + 0, 0, buf, 0x9, SHUT_TIMEOUT); */ @@ -537,26 +562,13 @@ static int libshut_open( upsdebugx(2, "Device matches"); if ((curDevice->VendorID == 0x463) && (curDevice->bcdDevice == 0x0202)) { - upsdebugx(1, "Eaton device v2.02. Using full report descriptor"); + upsdebugx(1, "Eaton device v2.02. Using full report descriptor"); + if (!getval("usb_hid_desc_index")) hid_desc_index = 1; } /* Get HID descriptor */ -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wcast-align" -#endif -#ifdef __clang__ -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wcast-align" -#endif - desc = (struct my_hid_descriptor *)buf; -#ifdef __clang__ -# pragma clang diagnostic pop -#endif -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN) -# pragma GCC diagnostic pop -#endif + memcpy(desc, buf, sizeof(struct my_hid_descriptor)); res = shut_get_descriptor(*arg_upsfd, USB_DT_HID, hid_desc_index, buf, 0x9); /* res = shut_control_msg(devp, USB_ENDPOINT_IN+1, USB_REQ_GET_DESCRIPTOR, (USB_DT_HID << 8) + 0, 0, buf, 0x9, SHUT_TIMEOUT); */ @@ -1271,7 +1283,7 @@ static int shut_control_msg( __func__, data_size); return -1; } - if (data_size > 0x0F) { + if (data_size > 0x0F || data_size > sizeof(shut_pkt) - 2) { upsdebugx(1, "%s: WARNING: data_size %" PRI_NUT_USB_CTRL_CHARBUFSIZE " may be too large for SHUT packet?", __func__, data_size); @@ -1281,7 +1293,7 @@ static int shut_control_msg( } shut_pkt[1] = (unsigned char)(data_size<<4) + (unsigned char)data_size; if ( (requesttype == REQUEST_TYPE_SET_REPORT) && (remaining_size < 8) ) - memcpy(&shut_pkt[2], bytes, data_size); /* we need to send ctrl.data */ + memcpy(&shut_pkt[2], bytes, data_size > sizeof(shut_pkt) - 2 ? sizeof(shut_pkt) - 2 : data_size); /* we need to send ctrl.data */ else memcpy(&shut_pkt[2], &ctrl, 8); shut_pkt[(data_size+3) - 1] = shut_checksum(&shut_pkt[2], (unsigned char)data_size); diff --git a/drivers/libusb0.c b/drivers/libusb0.c index c713450ba0..1f86740848 100644 --- a/drivers/libusb0.c +++ b/drivers/libusb0.c @@ -5,6 +5,7 @@ * @author Copyright (C) * 2003 - 2007 Arnaud Quette * 2005 - 2007 Peter Selinger + * 2021 - 2024 Jim Klimov * * This program is sponsored by MGE UPS SYSTEMS - opensource.mgeups.com * @@ -37,7 +38,7 @@ #endif #define USB_DRIVER_NAME "USB communication driver (libusb 0.1)" -#define USB_DRIVER_VERSION "0.45" +#define USB_DRIVER_VERSION "0.48" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -49,7 +50,6 @@ upsdrv_info_t comm_upsdrv_info = { }; #define MAX_REPORT_SIZE 0x1800 -#define MAX_RETRY 3 #if (!HAVE_STRCASESTR) && (HAVE_STRSTR && HAVE_STRLWR && HAVE_STRDUP) /* Only used in this file of all NUT codebase, so not in str.{c,h} @@ -59,7 +59,7 @@ upsdrv_info_t comm_upsdrv_info = { static char *strcasestr(const char *haystack, const char *needle); #endif -static void libusb_close(usb_dev_handle *udev); +static void nut_libusb_close(usb_dev_handle *udev); /*! Add USB-related driver variables with addvar() and dstate_setinfo(). * This removes some code duplication across the USB drivers. @@ -94,6 +94,12 @@ void nut_usb_addvars(void) addvar(VAR_VALUE, "usb_set_altinterface", "Force redundant call to usb_set_altinterface() (value=bAlternateSetting; default=0)"); + addvar(VAR_VALUE, "usb_config_index", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_rep_index", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_desc_index", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_ep_in", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_ep_out", "Deeper tuning of USB communications for complex devices"); + dstate_setinfo("driver.version.usb", "libusb-0.1 (or compat)"); upsdebugx(1, "Using USB implementation: %s", dstate_getinfo("driver.version.usb")); @@ -148,7 +154,7 @@ static inline int matches(USBDeviceMatcher_t *matcher, USBDevice_t *device) { * devices from working on Mac OS X (presumably the OS is already setting * altinterface to 0). */ -static int nut_usb_set_altinterface(usb_dev_handle *udev) +static int nut_libusb_set_altinterface(usb_dev_handle *udev) { int altinterface = 0, ret = 0; char *alt_string, *endp = NULL; @@ -180,6 +186,20 @@ static int nut_usb_set_altinterface(usb_dev_handle *udev) return ret; } +static void nut_libusb_subdriver_defaults(usb_communication_subdriver_t *subdriver) +{ + if (!getval("usb_config_index")) + subdriver->usb_config_index = LIBUSB_DEFAULT_CONF_INDEX; + if (!getval("usb_hid_rep_index")) + subdriver->hid_rep_index = LIBUSB_DEFAULT_INTERFACE; + if (!getval("usb_hid_desc_index")) + subdriver->hid_desc_index = LIBUSB_DEFAULT_DESC_INDEX; + if (!getval("usb_hid_ep_in")) + subdriver->hid_ep_in = LIBUSB_DEFAULT_HID_EP_IN; + if (!getval("usb_hid_ep_out")) + subdriver->hid_ep_out = LIBUSB_DEFAULT_HID_EP_OUT; +} + #define usb_control_msg typesafe_control_msg /* On success, fill in the curDevice structure and return the report @@ -190,13 +210,15 @@ static int nut_usb_set_altinterface(usb_dev_handle *udev) * is accepted, or < 1 if not. If it isn't accepted, the next device * (if any) will be tried, until there are no more devices left. */ -static int libusb_open(usb_dev_handle **udevp, +static int nut_libusb_open(usb_dev_handle **udevp, USBDevice_t *curDevice, USBDeviceMatcher_t *matcher, int (*callback)(usb_dev_handle *udev, USBDevice_t *hd, usb_ctrl_charbuf rdbuf, usb_ctrl_charbufsize rdlen) ) { +#ifdef HAVE_USB_DETACH_KERNEL_DRIVER_NP int retries; +#endif usb_ctrl_charbufsize rdlen1, rdlen2; /* report descriptor length, method 1+2 */ USBDeviceMatcher_t *m; struct usb_device *dev; @@ -219,6 +241,76 @@ static int libusb_open(usb_dev_handle **udevp, struct usb_bus *busses; + static int usb_hid_number_opts_parsed = 0; + if (!usb_hid_number_opts_parsed) { + const char *s; + unsigned short us = 0; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-type-limit-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#pragma clang diagnostic ignored "-Wtautological-compare" +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + if ((s = getval("usb_config_index"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_CFGINDEX_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_config_index", __func__); + } + usb_subdriver.usb_config_index = (usb_ctrl_cfgindex)us; + } + if ((s = getval("usb_hid_rep_index"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_REPINDEX_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_rep_index", __func__); + } + usb_subdriver.hid_rep_index = (usb_ctrl_repindex)us; + } + if ((s = getval("usb_hid_desc_index"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_DESCINDEX_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_desc_index", __func__); + } + usb_subdriver.hid_desc_index = (usb_ctrl_descindex)us; + } + if ((s = getval("usb_hid_ep_in"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_ENDPOINT_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_ep_in", __func__); + } + usb_subdriver.hid_ep_in = (usb_ctrl_endpoint)us; + } + if ((s = getval("usb_hid_ep_out"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_ENDPOINT_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_ep_out", __func__); + } + usb_subdriver.hid_ep_out = (usb_ctrl_endpoint)us; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE) ) +# pragma GCC diagnostic pop +#endif + + usb_hid_number_opts_parsed = 1; + } + /* libusb base init */ usb_init(); usb_find_busses(); @@ -236,7 +328,7 @@ static int libusb_open(usb_dev_handle **udevp, #ifndef __linux__ /* SUN_LIBUSB (confirmed to work on Solaris and FreeBSD) */ /* Causes a double free corruption in linux if device is detached! */ - libusb_close(*udevp); + nut_libusb_close(*udevp); #endif upsdebugx(3, "usb_busses=%p", (void*)usb_busses); @@ -319,44 +411,32 @@ static int libusb_open(usb_dev_handle **udevp, #endif if (dev->descriptor.iManufacturer) { - retries = MAX_RETRY; - while (retries > 0) { - ret = usb_get_string_simple(udev, dev->descriptor.iManufacturer, - string, sizeof(string)); - if (ret > 0) { - curDevice->Vendor = xstrdup(string); - break; - } - retries--; - upsdebugx(1, "%s get iManufacturer failed, retrying...", __func__); + ret = nut_usb_get_string(udev, dev->descriptor.iManufacturer, + string, sizeof(string)); + if (ret > 0) { + curDevice->Vendor = xstrdup(string); + } else { + upsdebugx(1, "%s: get Manufacturer string failed", __func__); } } if (dev->descriptor.iProduct) { - retries = MAX_RETRY; - while (retries > 0) { - ret = usb_get_string_simple(udev, dev->descriptor.iProduct, - string, sizeof(string)); - if (ret > 0) { - curDevice->Product = xstrdup(string); - break; - } - retries--; - upsdebugx(1, "%s get iProduct failed, retrying...", __func__); + ret = nut_usb_get_string(udev, dev->descriptor.iProduct, + string, sizeof(string)); + if (ret > 0) { + curDevice->Product = xstrdup(string); + } else { + upsdebugx(1, "%s: get Product string failed", __func__); } } if (dev->descriptor.iSerialNumber) { - retries = MAX_RETRY; - while (retries > 0) { - ret = usb_get_string_simple(udev, dev->descriptor.iSerialNumber, - string, sizeof(string)); - if (ret > 0) { - curDevice->Serial = xstrdup(string); - break; - } - retries--; - upsdebugx(1, "%s get iSerialNumber failed, retrying...", __func__); + ret = nut_usb_get_string(udev, dev->descriptor.iSerialNumber, + string, sizeof(string)); + if (ret > 0) { + curDevice->Serial = xstrdup(string); + } else { + upsdebugx(1, "%s: get Serial Number string failed", __func__); } } @@ -374,7 +454,8 @@ static int libusb_open(usb_dev_handle **udevp, /* FIXME: extend to Eaton OEMs (HP, IBM, ...) */ if ((curDevice->VendorID == 0x463) && (curDevice->bcdDevice == 0x0202)) { - usb_subdriver.hid_desc_index = 1; + if (!getval("usb_hid_desc_index")) + usb_subdriver.hid_desc_index = 1; } upsdebugx(2, "Trying to match device"); @@ -411,11 +492,10 @@ static int libusb_open(usb_dev_handle **udevp, /* this method requires at least libusb 0.1.8: * it force device claiming by unbinding * attached driver... From libhid */ - retries = MAX_RETRY; #ifdef WIN32 usb_set_configuration(udev, 1); #endif - + retries = 3; while ((ret = usb_claim_interface(udev, usb_subdriver.hid_rep_index)) < 0) { upsdebugx(2, "failed to claim USB device: %s", usb_strerror()); @@ -462,8 +542,12 @@ static int libusb_open(usb_dev_handle **udevp, #endif /* if_claimed = 1; */ - nut_usb_set_altinterface(udev); + nut_libusb_set_altinterface(udev); + /* Did the driver provide a callback method for any further + * device acceptance checks (e.g. when same ID is supported + * by several sub-drivers, differing by vendor/model strings)? + */ if (!callback) { return 1; } @@ -618,6 +702,18 @@ static int libusb_open(usb_dev_handle **udevp, "Report descriptor retrieved (Reportlen = %" PRI_NUT_USB_CTRL_CHARBUFSIZE ")", rdlen); upsdebugx(2, "Found HID device"); + + upsdebugx(3, "Using default, detected or customized USB HID numbers: " + "usb_config_index=%d usb_hid_rep_index=%d " + "usb_hid_desc_index=%d " + "usb_hid_ep_in=%d usb_hid_ep_out=%d", + usb_subdriver.usb_config_index, + usb_subdriver.hid_rep_index, + usb_subdriver.hid_desc_index, + usb_subdriver.hid_ep_in, + usb_subdriver.hid_ep_out + ); + fflush(stdout); return rdlen; @@ -629,6 +725,8 @@ static int libusb_open(usb_dev_handle **udevp, /* if (if_claimed) usb_release_interface(udev, 0); */ usb_close(udev); + /* reset any parameters modified by unmatched drivers back to defaults */ + nut_libusb_subdriver_defaults(&usb_subdriver); } } @@ -657,7 +755,7 @@ static int libusb_open(usb_dev_handle **udevp, * Error handler for usb_get/set_* functions. Return value > 0 success, * 0 unknown or temporary failure (ignored), < 0 permanent failure (reconnect) */ -static int libusb_strerror(const int ret, const char *desc) +static int nut_libusb_strerror(const int ret, const char *desc) { if (ret > 0) { return ret; @@ -692,6 +790,7 @@ static int libusb_strerror(const int ret, const char *desc) return 0; #endif /* WIN32 */ + case 0: /** TOTHINK: Should this (probably LIBUSB_SUCCESS) be quiet? */ default: /* Undetermined, log only */ upslogx(LOG_DEBUG, "%s: %s", desc, usb_strerror()); return 0; @@ -703,10 +802,10 @@ static int libusb_strerror(const int ret, const char *desc) */ /* Expected evaluated types for the API: - * static int libusb_get_report(usb_dev_handle *udev, + * static int nut_libusb_get_report(usb_dev_handle *udev, * int ReportId, unsigned char *raw_buf, int ReportSize) */ -static int libusb_get_report( +static int nut_libusb_get_report( usb_dev_handle *udev, usb_ctrl_repindex ReportId, usb_ctrl_charbuf raw_buf, @@ -714,7 +813,7 @@ static int libusb_get_report( { int ret; - upsdebugx(4, "Entering libusb_get_report"); + upsdebugx(4, "Entering nut_libusb_get_report"); if (!udev) { return 0; @@ -736,14 +835,14 @@ static int libusb_get_report( return 0; } - return libusb_strerror(ret, __func__); + return nut_libusb_strerror(ret, __func__); } /* Expected evaluated types for the API: - * static int libusb_set_report(usb_dev_handle *udev, + * static int nut_libusb_set_report(usb_dev_handle *udev, * int ReportId, unsigned char *raw_buf, int ReportSize) */ -static int libusb_set_report( +static int nut_libusb_set_report( usb_dev_handle *udev, usb_ctrl_repindex ReportId, usb_ctrl_charbuf raw_buf, @@ -771,14 +870,14 @@ static int libusb_set_report( return 0; } - return libusb_strerror(ret, __func__); + return nut_libusb_strerror(ret, __func__); } /* Expected evaluated types for the API: - * static int libusb_get_string(usb_dev_handle *udev, - * int StringIdx, char *buf, size_t buflen) + * static int nut_libusb_get_string(usb_dev_handle *udev, + * int StringIdx, char *buf, int buflen) */ -static int libusb_get_string( +static int nut_libusb_get_string( usb_dev_handle *udev, usb_ctrl_strindex StringIdx, char *buf, @@ -786,46 +885,43 @@ static int libusb_get_string( { int ret; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) ) -# pragma GCC diagnostic push -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS -# pragma GCC diagnostic ignored "-Wtype-limits" -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE -# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE -# pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare" -#endif /* - * usb.h:int usb_get_string_simple(usb_dev_handle *dev, int index, + * usb.h:int nut_usb_get_string(usb_dev_handle *dev, int index, * usb.h- char *buf, size_t buflen); */ if (!udev - || StringIdx < 0 || (uintmax_t)StringIdx > INT_MAX - || buflen < 0 || (uintmax_t)buflen > (uintmax_t)SIZE_MAX + || StringIdx < 1 || StringIdx > 255 + || buflen < 1 ) { -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) ) -# pragma GCC diagnostic pop -#endif return -1; } - ret = usb_get_string_simple(udev, StringIdx, buf, (size_t)buflen); + ret = nut_usb_get_string(udev, StringIdx, buf, (size_t)buflen); #ifdef WIN32 errno = -ret; #endif - return libusb_strerror(ret, __func__); + /** 0 can be seen as an empty string, or as a success for + * logging below - also tends to happen */ + if (ret == 0) { + size_t len = strlen(buf); + upsdebugx(2, "%s: nut_usb_get_string() returned " + "0 (might be just success code), " + "actual buf length is %" PRIuSIZE, __func__, len); + /* if (len) */ + return len; + /* else may log "nut_libusb_get_string: Success" and return 0 below */ + } + + return nut_libusb_strerror(ret, __func__); } /* Expected evaluated types for the API: - * static int libusb_get_interrupt(usb_dev_handle *udev, + * static int nut_libusb_get_interrupt(usb_dev_handle *udev, * unsigned char *buf, int bufsize, int timeout) */ -static int libusb_get_interrupt( +static int nut_libusb_get_interrupt( usb_dev_handle *udev, usb_ctrl_charbuf buf, usb_ctrl_charbufsize bufsize, @@ -849,10 +945,10 @@ static int libusb_get_interrupt( ret = usb_clear_halt(udev, 0x81); } - return libusb_strerror(ret, __func__); + return nut_libusb_strerror(ret, __func__); } -static void libusb_close(usb_dev_handle *udev) +static void nut_libusb_close(usb_dev_handle *udev) { if (!udev) { return; @@ -899,12 +995,12 @@ static char *strcasestr(const char *haystack, const char *needle) { usb_communication_subdriver_t usb_subdriver = { USB_DRIVER_NAME, USB_DRIVER_VERSION, - libusb_open, - libusb_close, - libusb_get_report, - libusb_set_report, - libusb_get_string, - libusb_get_interrupt, + nut_libusb_open, + nut_libusb_close, + nut_libusb_get_report, + nut_libusb_set_report, + nut_libusb_get_string, + nut_libusb_get_interrupt, LIBUSB_DEFAULT_CONF_INDEX, LIBUSB_DEFAULT_INTERFACE, LIBUSB_DEFAULT_DESC_INDEX, diff --git a/drivers/libusb1.c b/drivers/libusb1.c index c84a28218a..4eac05ca12 100644 --- a/drivers/libusb1.c +++ b/drivers/libusb1.c @@ -4,7 +4,7 @@ * * @author Copyright (C) 2016 Eaton * Copyright (C) 2016 Arnaud Quette - * Copyright (C) 2021 Jim Klimov + * Copyright (C) 2021-2024 Jim Klimov * * The logic of this file is ripped from mge-shut driver (also from * Arnaud Quette), which is a "HID over serial link" UPS driver for @@ -33,7 +33,7 @@ #include "nut_stdint.h" #define USB_DRIVER_NAME "USB communication driver (libusb 1.0)" -#define USB_DRIVER_VERSION "0.46" +#define USB_DRIVER_VERSION "0.49" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -45,7 +45,6 @@ upsdrv_info_t comm_upsdrv_info = { }; #define MAX_REPORT_SIZE 0x1800 -#define MAX_RETRY 3 static void nut_libusb_close(libusb_device_handle *udev); @@ -88,6 +87,12 @@ void nut_usb_addvars(void) addvar(VAR_VALUE, "usb_set_altinterface", "Force redundant call to usb_set_altinterface() (value=bAlternateSetting; default=0)"); + addvar(VAR_VALUE, "usb_config_index", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_rep_index", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_desc_index", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_ep_in", "Deeper tuning of USB communications for complex devices"); + addvar(VAR_VALUE, "usb_hid_ep_out", "Deeper tuning of USB communications for complex devices"); + #ifdef LIBUSB_API_VERSION dstate_setinfo("driver.version.usb", "libusb-%u.%u.%u (API: 0x%x)", v->major, v->minor, v->micro, LIBUSB_API_VERSION); #else /* no LIBUSB_API_VERSION */ @@ -114,7 +119,7 @@ static inline int matches(USBDeviceMatcher_t *matcher, USBDevice_t *device) { * devices from working on Mac OS X (presumably the OS is already setting * altinterface to 0). */ -static int nut_usb_set_altinterface(libusb_device_handle *udev) +static int nut_libusb_set_altinterface(libusb_device_handle *udev) { int altinterface = 0, ret = 0; char *alt_string, *endp = NULL; @@ -147,6 +152,20 @@ static int nut_usb_set_altinterface(libusb_device_handle *udev) return ret; } +static void nut_libusb_subdriver_defaults(usb_communication_subdriver_t *subdriver) +{ + if (!getval("usb_config_index")) + subdriver->usb_config_index = LIBUSB_DEFAULT_CONF_INDEX; + if (!getval("usb_hid_rep_index")) + subdriver->hid_rep_index = LIBUSB_DEFAULT_INTERFACE; + if (!getval("usb_hid_desc_index")) + subdriver->hid_desc_index = LIBUSB_DEFAULT_DESC_INDEX; + if (!getval("usb_hid_ep_in")) + subdriver->hid_ep_in = LIBUSB_DEFAULT_HID_EP_IN; + if (!getval("usb_hid_ep_out")) + subdriver->hid_ep_out = LIBUSB_DEFAULT_HID_EP_OUT; +} + /* On success, fill in the curDevice structure and return the report * descriptor length. On failure, return -1. * Note: When callback is not NULL, the report descriptor will be @@ -161,7 +180,9 @@ static int nut_libusb_open(libusb_device_handle **udevp, USBDevice_t *hd, usb_ctrl_charbuf rdbuf, usb_ctrl_charbufsize rdlen) ) { +#if (defined HAVE_LIBUSB_DETACH_KERNEL_DRIVER) || (defined HAVE_LIBUSB_DETACH_KERNEL_DRIVER_NP) int retries; +#endif /* libusb-1.0 usb_ctrl_charbufsize is uint16_t and we * want the rdlen vars signed - so taking a wider type */ int32_t rdlen1, rdlen2; /* report descriptor length, method 1+2 */ @@ -189,6 +210,76 @@ static int nut_libusb_open(libusb_device_handle **udevp, unsigned char rdbuf[MAX_REPORT_SIZE]; int32_t rdlen; + static int usb_hid_number_opts_parsed = 0; + if (!usb_hid_number_opts_parsed) { + const char *s; + unsigned short us = 0; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS +# pragma GCC diagnostic ignored "-Wtype-limits" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE +# pragma GCC diagnostic ignored "-Wtautological-type-limit-compare" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +#pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunreachable-code" +#pragma clang diagnostic ignored "-Wtautological-compare" +#pragma clang diagnostic ignored "-Wtautological-constant-out-of-range-compare" +#endif + if ((s = getval("usb_config_index"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_CFGINDEX_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_config_index", __func__); + } + usb_subdriver.usb_config_index = (usb_ctrl_cfgindex)us; + } + if ((s = getval("usb_hid_rep_index"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_REPINDEX_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_rep_index", __func__); + } + usb_subdriver.hid_rep_index = (usb_ctrl_repindex)us; + } + if ((s = getval("usb_hid_desc_index"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_DESCINDEX_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_desc_index", __func__); + } + usb_subdriver.hid_desc_index = (usb_ctrl_descindex)us; + } + if ((s = getval("usb_hid_ep_in"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_ENDPOINT_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_ep_in", __func__); + } + usb_subdriver.hid_ep_in = (usb_ctrl_endpoint)us; + } + if ((s = getval("usb_hid_ep_out"))) { + if (!str_to_ushort(s, &us, 16) || (us > USB_CTRL_ENDPOINT_MAX)) { + fatalx(EXIT_FAILURE, "%s: could not parse usb_hid_ep_out", __func__); + } + usb_subdriver.hid_ep_out = (usb_ctrl_endpoint)us; + } +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_TYPE_LIMIT_COMPARE) ) +# pragma GCC diagnostic pop +#endif + + usb_hid_number_opts_parsed = 1; + } + /* libusb base init */ if (libusb_init(NULL) < 0) { libusb_exit(NULL); @@ -314,56 +405,44 @@ static int nut_libusb_open(libusb_device_handle **udevp, curDevice->bcdDevice = dev_desc.bcdDevice; if (dev_desc.iManufacturer) { - retries = MAX_RETRY; - while (retries > 0) { - ret = libusb_get_string_descriptor_ascii(udev, dev_desc.iManufacturer, - (unsigned char*)string, sizeof(string)); - if (ret > 0) { - curDevice->Vendor = strdup(string); - if (curDevice->Vendor == NULL) { - libusb_free_device_list(devlist, 1); - fatal_with_errno(EXIT_FAILURE, "Out of memory"); - } - break; + ret = nut_usb_get_string(udev, dev_desc.iManufacturer, + string, sizeof(string)); + if (ret > 0) { + curDevice->Vendor = strdup(string); + if (curDevice->Vendor == NULL) { + libusb_free_device_list(devlist, 1); + fatal_with_errno(EXIT_FAILURE, "Out of memory"); } - retries--; - upsdebugx(1, "%s get iManufacturer failed, retrying...", __func__); + } else { + upsdebugx(1, "%s: get Manufacturer string failed", __func__); } } if (dev_desc.iProduct) { - retries = MAX_RETRY; - while (retries > 0) { - ret = libusb_get_string_descriptor_ascii(udev, dev_desc.iProduct, - (unsigned char*)string, sizeof(string)); - if (ret > 0) { - curDevice->Product = strdup(string); - if (curDevice->Product == NULL) { - libusb_free_device_list(devlist, 1); - fatal_with_errno(EXIT_FAILURE, "Out of memory"); - } - break; + ret = nut_usb_get_string(udev, dev_desc.iProduct, + string, sizeof(string)); + if (ret > 0) { + curDevice->Product = strdup(string); + if (curDevice->Product == NULL) { + libusb_free_device_list(devlist, 1); + fatal_with_errno(EXIT_FAILURE, "Out of memory"); } - retries--; - upsdebugx(1, "%s get iProduct failed, retrying...", __func__); + } else { + upsdebugx(1, "%s: get Product string failed", __func__); } } if (dev_desc.iSerialNumber) { - retries = MAX_RETRY; - while (retries > 0) { - ret = libusb_get_string_descriptor_ascii(udev, dev_desc.iSerialNumber, - (unsigned char*)string, sizeof(string)); - if (ret > 0) { - curDevice->Serial = strdup(string); - if (curDevice->Serial == NULL) { - libusb_free_device_list(devlist, 1); - fatal_with_errno(EXIT_FAILURE, "Out of memory"); - } - break; + ret = nut_usb_get_string(udev, dev_desc.iSerialNumber, + string, sizeof(string)); + if (ret > 0) { + curDevice->Serial = strdup(string); + if (curDevice->Serial == NULL) { + libusb_free_device_list(devlist, 1); + fatal_with_errno(EXIT_FAILURE, "Out of memory"); } - retries--; - upsdebugx(1, "%s get iSerialNumber failed, retrying...", __func__); + } else { + upsdebugx(1, "%s: get Serial Number string failed", __func__); } } @@ -381,6 +460,7 @@ static int nut_libusb_open(libusb_device_handle **udevp, /* FIXME: extend to Eaton OEMs (HP, IBM, ...) */ if ((curDevice->VendorID == 0x463) && (curDevice->bcdDevice == 0x0202)) { + if (!getval("usb_hid_desc_index")) usb_subdriver.hid_desc_index = 1; } @@ -455,12 +535,12 @@ static int nut_libusb_open(libusb_device_handle **udevp, #if (defined HAVE_LIBUSB_DETACH_KERNEL_DRIVER) || (defined HAVE_LIBUSB_DETACH_KERNEL_DRIVER_NP) /* Then, try the explicit detach method. * This function is available on FreeBSD 10.1-10.3 */ - retries = MAX_RETRY; #ifdef WIN32 /* TODO: Align with libusb1 - initially from Windows branch made against libusb0 */ libusb_set_configuration(udev, 1); #endif + retries = 3; while ((ret = libusb_claim_interface(udev, usb_subdriver.hid_rep_index)) != LIBUSB_SUCCESS) { upsdebugx(2, "failed to claim USB device: %s", libusb_strerror((enum libusb_error)ret)); @@ -522,8 +602,12 @@ static int nut_libusb_open(libusb_device_handle **udevp, upsdebugx(2, "Claimed interface %d successfully", usb_subdriver.hid_rep_index); - nut_usb_set_altinterface(udev); + nut_libusb_set_altinterface(udev); + /* Did the driver provide a callback method for any further + * device acceptance checks (e.g. when same ID is supported + * by several sub-drivers, differing by vendor/model strings)? + */ if (!callback) { libusb_free_config_descriptor(conf_desc); libusb_free_device_list(devlist, 1); @@ -702,6 +786,18 @@ static int nut_libusb_open(libusb_device_handle **udevp, upsdebugx(2, "Report descriptor retrieved (Reportlen = %d)", rdlen); upsdebugx(2, "Found HID device"); + + upsdebugx(3, "Using default, detected or customized USB HID numbers: " + "usb_config_index=%d usb_hid_rep_index=%d " + "usb_hid_desc_index=%d " + "usb_hid_ep_in=%d usb_hid_ep_out=%d", + usb_subdriver.usb_config_index, + usb_subdriver.hid_rep_index, + usb_subdriver.hid_desc_index, + usb_subdriver.hid_ep_in, + usb_subdriver.hid_ep_out + ); + fflush(stdout); libusb_free_device_list(devlist, 1); @@ -713,6 +809,8 @@ static int nut_libusb_open(libusb_device_handle **udevp, /* if (if_claimed) libusb_release_interface(udev, usb_subdriver.hid_rep_index); */ libusb_close(udev); + /* reset any parameters modified by unmatched drivers back to defaults */ + nut_libusb_subdriver_defaults(&usb_subdriver); } *udevp = NULL; @@ -783,6 +881,7 @@ static int nut_libusb_strerror(const int ret, const char *desc) return 0; #endif /* WIN32 */ + case LIBUSB_SUCCESS: /** TOTHINK: Should this be quiet? */ case LIBUSB_ERROR_OTHER: /** Other error */ default: /** Undetermined, log only */ upslogx(LOG_DEBUG, "%s: %s", desc, libusb_strerror((enum libusb_error)ret)); @@ -898,7 +997,7 @@ static int nut_libusb_set_report( /* Expected evaluated types for the API: * static int nut_libusb_get_string(libusb_device_handle *udev, - * int StringIdx, char *buf, int buflen) + * uint8_t StringIdx, unsigned char *buf, uint16_t buflen) */ static int nut_libusb_get_string( libusb_device_handle *udev, @@ -908,30 +1007,24 @@ static int nut_libusb_get_string( { int ret; -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) ) -# pragma GCC diagnostic push -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS -# pragma GCC diagnostic ignored "-Wtype-limits" -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE -# pragma GCC diagnostic ignored "-Wtautological-constant-out-of-range-compare" -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE -# pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare" -#endif - if (!udev - || StringIdx < 0 || (uintmax_t)StringIdx > UINT8_MAX - || buflen < 0 || (uintmax_t)buflen > (uintmax_t)INT_MAX - ) { -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TYPE_LIMITS) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_CONSTANT_OUT_OF_RANGE_COMPARE) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE) ) -# pragma GCC diagnostic pop -#endif + if (!udev || StringIdx < 1 || buflen < 1) { return -1; } - ret = libusb_get_string_descriptor_ascii(udev, (uint8_t)StringIdx, - (unsigned char*)buf, (int)buflen); + ret = nut_usb_get_string(udev, (uint8_t)StringIdx, + buf, (int)buflen); + + /** 0 can be seen as an empty string, or as LIBUSB_SUCCESS for + * logging below - also tends to happen */ + if (ret == 0) { + size_t len = strlen(buf); + upsdebugx(2, "%s: nut_usb_get_string() returned " + "0 (might be just LIBUSB_SUCCESS code), " + "actual buf length is %" PRIuSIZE, __func__, len); + /* if (len) */ + return len; + /* else may log "nut_libusb_get_string: Success" and return 0 below */ + } return nut_libusb_strerror(ret, __func__); } diff --git a/drivers/liebert-esp2.c b/drivers/liebert-esp2.c index ef27cc871a..10b67b789e 100644 --- a/drivers/liebert-esp2.c +++ b/drivers/liebert-esp2.c @@ -28,7 +28,7 @@ #define IsBitSet(val, bit) ((val) & (1 << (bit))) #define DRIVER_NAME "Liebert ESP-II serial UPS driver" -#define DRIVER_VERSION "0.06" +#define DRIVER_VERSION "0.07" #define UPS_SHUTDOWN_DELAY 12 /* it means UPS will be shutdown 120 sec */ #define SHUTDOWN_CMD_LEN 8 diff --git a/drivers/liebert.c b/drivers/liebert.c index 1c21e6e0da..0c1bc176cc 100644 --- a/drivers/liebert.c +++ b/drivers/liebert.c @@ -27,7 +27,7 @@ #include "attribute.h" #define DRIVER_NAME "Liebert MultiLink UPS driver" -#define DRIVER_VERSION "1.04" +#define DRIVER_VERSION "1.05" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/macosx-ups.c b/drivers/macosx-ups.c index 1740320c29..b35585ef93 100644 --- a/drivers/macosx-ups.c +++ b/drivers/macosx-ups.c @@ -29,7 +29,7 @@ #include "IOKit/ps/IOPSKeys.h" #define DRIVER_NAME "Mac OS X UPS meta-driver" -#define DRIVER_VERSION "1.40" +#define DRIVER_VERSION "1.41" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/main.c b/drivers/main.c index 8eb7b08b5c..5c4753a116 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -4,7 +4,7 @@ 1999 Russell Kroll 2005 - 2017 Arnaud Quette 2017 Eaton (author: Emilien Kia ) - 2017 - 2022 Jim Klimov + 2017 - 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -139,7 +139,9 @@ void upsdrv_banner (void) { int i; - printf("Network UPS Tools - %s %s (%s)\n", upsdrv_info.name, upsdrv_info.version, UPS_VERSION); + printf("Network UPS Tools driver %s - %s %s\n", + describe_NUT_VERSION_once(), + upsdrv_info.name, upsdrv_info.version); /* process sub driver(s) information */ for (i = 0; upsdrv_info.subdrv_info[i]; i++) { @@ -155,6 +157,8 @@ void upsdrv_banner (void) printf("%s %s\n", upsdrv_info.subdrv_info[i]->name, upsdrv_info.subdrv_info[i]->version); } + + fflush(stdout); } #ifndef DRIVERS_MAIN_WITHOUT_MAIN @@ -178,6 +182,11 @@ static void help_msg(void) { vartab_t *tmp; + if (banner_is_disabled()) { + /* Was not printed at start of main() */ + upsdrv_banner(); + } + nut_report_config_flags(); printf("\nusage: %s (-a |-s ) [OPTIONS]\n", progname); @@ -227,6 +236,9 @@ static void help_msg(void) printf(" - reload-or-exit: re-read configuration files (exit the old\n"); printf(" driver instance if needed, so an external caller like the\n"); printf(" systemd or SMF frameworks would start another copy)\n"); + printf(" - exit: tell the currently running driver instance to just exit\n"); + printf(" (so an external caller like the new driver instance, or the\n"); + printf(" systemd or SMF frameworks would start another copy)\n"); /* NOTE for FIXME above: PID-signalling is non-WIN32-only for us */ printf(" -P - send the signal above to specified PID (bypassing PID file)\n"); # endif /* WIN32 */ @@ -264,7 +276,9 @@ void dparam_setinfo(const char *var, const char *val) { char vtmp[SMALLBUF]; - /* store these in dstate for debugging and other help */ + /* store these in dstate for debugging and other help + * note these are not immutable since we can reload + */ if (val) { snprintf(vtmp, sizeof(vtmp), "driver.parameter.%s", var); dstate_setinfo(vtmp, "%s", val); @@ -299,6 +313,10 @@ void storeval(const char *var, char *val) /* NOTE: No regard for VAR_SENSITIVE here */ dstate_setinfo(var+9, "%s", val); dstate_setflags(var+9, ST_FLAG_IMMUTABLE); + + /* note these are not immutable since we can reload + * although the effect of this is questionable (FIXME) + */ dparam_setinfo(var, val); return; } @@ -364,6 +382,11 @@ void storeval(const char *var, char *val) || !strcmp(var, "device") || !strcmp(var, "busport") || !strcmp(var, "usb_set_altinterface") + || !strcmp(var, "usb_config_index") + || !strcmp(var, "usb_hid_rep_index") + || !strcmp(var, "usb_hid_desc_index") + || !strcmp(var, "usb_hid_ep_in") + || !strcmp(var, "usb_hid_ep_out") || !strcmp(var, "allow_duplicates") || !strcmp(var, "langid_fix") || !strcmp(var, "noscanlangid") @@ -532,6 +555,9 @@ int testvar_reloadable(const char *var, const char *val, int vartype) case 0: /* value may not be (re-)applied, but it may not have been required */ break; + + default: + break; } upsdebugx(6, "%s: verdict for (re)loading var=%s value: %d", @@ -625,6 +651,9 @@ int testval_reloadable(const char *var, const char *oldval, const char *newval, case 0: /* value may not be (re-)applied, but it may not have been required */ break; + + default: + break; } upsdebugx(6, "%s: verdict for (re)loading var=%s value: %d", @@ -741,6 +770,11 @@ int main_instcmd(const char *cmdname, const char *extra, conn_t *conn) { } } + if (!strcmp(cmdname, "driver.exit")) { + set_reload_flag(SIGCMD_EXIT); + return STAT_INSTCMD_HANDLED; + } + #ifndef WIN32 /* TODO: Equivalent for WIN32 - see SIGCMD_RELOAD in upd and upsmon */ if (!strcmp(cmdname, "driver.reload")) { @@ -1544,6 +1578,15 @@ static void set_reload_flag( break; #endif + case SIGCMD_EXIT: /* Not even a signal, but a socket protocol action, + * and not a reload either - just applied here for consistency */ +/* + reload_flag = 15; + break; +*/ + set_exit_flag(-2); + return; + case SIGCMD_RELOAD: /* SIGHUP */ case SIGCMD_RELOAD_OR_ERROR: /* Not even a signal, but a socket protocol action */ default: @@ -1557,10 +1600,13 @@ static void set_reload_flag( if (sig && !strcmp(sig, SIGCMD_RELOAD_OR_ERROR)) { /* reload what we can, log what needs a restart so skipped */ reload_flag = 1; + } else if (sig && !strcmp(sig, SIGCMD_EXIT)) { + set_exit_flag(-2); + return; } else { /* non-fatal reload as a fallback */ reload_flag = 1; - } + } upsdebugx(1, "%s: raising reload flag due to command %s => reload_flag=%d", __func__, sig, reload_flag); @@ -1658,6 +1704,11 @@ int main(int argc, char **argv) nut_debug_level++; nut_debug_level_args++; break; + case 'd': + dump_data = atoi(optarg); + break; + default: + break; } } /* Reset the index, read argv[1] next time (loop below) @@ -1730,7 +1781,9 @@ int main(int argc, char **argv) open_syslog(progname); - upsdrv_banner(); + if (!banner_is_disabled()) { + upsdrv_banner(); + } if (upsdrv_info.status == DRV_EXPERIMENTAL) { printf("Warning: This is an experimental driver.\n"); @@ -1778,7 +1831,7 @@ int main(int argc, char **argv) /* Processed above */ break; case 'd': - dump_data = atoi(optarg); + /* Processed above */ break; case 'i': { /* scope */ int ipv = atoi(optarg); @@ -1806,6 +1859,10 @@ int main(int argc, char **argv) if (!strncmp(optarg, "reload-or-error", strlen(optarg))) { cmd = SIGCMD_RELOAD_OR_ERROR; } + else + if (!strncmp(optarg, "exit", strlen(optarg))) { + cmd = SIGCMD_EXIT; + } #ifndef WIN32 else if (!strncmp(optarg, "reload", strlen(optarg))) { @@ -1828,9 +1885,14 @@ int main(int argc, char **argv) "Error: unknown argument to option -%c. Try -h for help.", i); } #ifndef WIN32 - upsdebugx(1, "Will send signal %d (%s) for command '%s' " - "to already-running driver %s-%s (if any) and exit", - cmd, strsignal(cmd), optarg, progname, upsname); + if (cmd > 0) + upsdebugx(1, "Will send signal %d (%s) for command '%s' " + "to already-running driver %s-%s (if any) and exit", + cmd, strsignal(cmd), optarg, progname, upsname); + else + upsdebugx(1, "Will send request for command '%s' (internal code %d) " + "to already-running driver %s-%s (if any) and exit", + optarg, cmd, progname, upsname); #else upsdebugx(1, "Will send request '%s' for command '%s' " "to already-running driver %s-%s (if any) and exit", @@ -1887,7 +1949,20 @@ int main(int argc, char **argv) group_from_cmdline = 1; break; case 'V': - /* already printed the banner for program name */ + /* Avoid the verbose message about + * driver daemon state integration + * with a service management framework + * like systemd, as not too relevant + * to program version reporting here + * (only seen with non-zero debug) */ + setenv("NUT_QUIET_INIT_UPSNOTIFY", "yes", 0); + + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + if (banner_is_disabled()) { + /* Was not printed at start of main() */ + upsdrv_banner(); + } nut_report_config_flags(); exit(EXIT_SUCCESS); case 'x': @@ -1898,7 +1973,8 @@ int main(int argc, char **argv) exit(EXIT_SUCCESS); default: fatalx(EXIT_FAILURE, - "Error: unknown option -%c. Try -h for help.", i); + "Error: unknown option -%c. Try -h for help.", + (char)i); } } @@ -2001,40 +2077,181 @@ int main(int argc, char **argv) /* Handle reload-or-error over socket protocol with * the running older driver instance */ #ifndef WIN32 - if (cmd == SIGCMD_RELOAD_OR_ERROR) + if (cmd == SIGCMD_RELOAD_OR_ERROR || cmd == SIGCMD_EXIT) #else - if (cmd && !strcmp(cmd, SIGCMD_RELOAD_OR_ERROR)) + if (cmd && (!strcmp(cmd, SIGCMD_RELOAD_OR_ERROR) || !strcmp(cmd, SIGCMD_EXIT))) #endif /* WIN32 */ { /* Not a signal, but a socket protocol action */ ssize_t cmdret = -1; - char buf[LARGEBUF]; + char buf[LARGEBUF], cmdbuf[LARGEBUF]; struct timeval tv; + char *cmdname = NULL; + +#ifndef WIN32 + if (cmd == SIGCMD_RELOAD_OR_ERROR) +#else + if (!strcmp(cmd, SIGCMD_RELOAD_OR_ERROR)) +#endif + cmdname = "reload-or-error"; + else +#ifndef WIN32 + if (cmd == SIGCMD_EXIT) +#else + if (!strcmp(cmd, SIGCMD_EXIT)) +#endif + cmdname = "exit"; + +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW || defined HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION) +# pragma GCC diagnostic push +# ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW +# pragma GCC diagnostic ignored "-Wformat-overflow" +# endif +# ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION +# pragma GCC diagnostic ignored "-Wformat-truncation" +# endif +#endif + /* Some compilers do detect a chance of cmdname=NULL with + * NUT builds on systems where libc does not care and prints + * the right thing anyway (so NUT_STRARG macro is trivial). + * In this weird case gotta silence the static checks. + */ + upsdebugx(1, "Signalling UPS [%s]: driver.%s", + upsname, NUT_STRARG(cmdname)); +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_FORMAT_OVERFLOW || defined HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_TRUNCATION) +# pragma GCC diagnostic pop +#endif + + if (!cmdname) + fatalx(EXIT_FAILURE, "Command not recognized"); /* Post the query and wait for reply */ /* FIXME: coordinate with pollfreq? */ tv.tv_sec = 15; tv.tv_usec = 0; + snprintf(cmdbuf, sizeof(cmdbuf), "INSTCMD driver.%s\n", cmdname); cmdret = upsdrvquery_oneshot(progname, upsname, - "INSTCMD driver.reload-or-error\n", - buf, sizeof(buf), &tv); + cmdbuf, buf, sizeof(buf), &tv); if (cmdret < 0) { upslog_with_errno(LOG_ERR, "Socket dialog with the other driver instance"); } else { /* TODO: handle buf reply contents */ - upslogx(LOG_INFO, "Request to reload-or-error returned code %" PRIiSIZE, cmdret); + upslogx(LOG_INFO, "Request for driver to %s returned code %" PRIiSIZE, + cmdname, cmdret); } /* exit((cmdret == 0) ? EXIT_SUCCESS : EXIT_FAILURE); */ exit(((cmdret < 0) || (((uintmax_t)cmdret) > ((uintmax_t)INT_MAX))) ? 255 : (int)cmdret); } + /* If we would be starting as a driver (not to command a sibling), + * any earlier instances should be turned off - to release access + * to hardware connections and to generally avoid any confusion. + * Further below we would try to use a PID file (if at all used + * and still present) to terminate an earlier instance, but first + * we would try to use the Unix socket protocol to tell that + * earlier instance to exit cleanly. After all, this socket file + * should exist for the driver to talk to the NUT data server... + */ + + /* Hush the fopen(pidfile) message but let "real errors" be seen */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_KILL_SIG0PING - 1; + + if (!cmd && (!do_forceshutdown)) { + ssize_t cmdret = -1; + char buf[LARGEBUF]; + struct timeval tv; + + upsdebugx(1, "Signalling UPS [%s]: driver.exit (quietly, no fuss if no driver is running or responding)", upsname); + + /* Post the query and wait for reply */ + /* FIXME: coordinate with pollfreq? */ + tv.tv_sec = 15; + tv.tv_usec = 0; + + /* Hush the messages about initial connection failure, but + * let "real errors" from started communication be seen. + * It is okay if no driver instance is running at this + * point, but if it is running but not communicating - + * that is another story. + */ + nut_upsdrvquery_debug_level = NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT - 1; + cmdret = upsdrvquery_oneshot(progname, upsname, + "INSTCMD driver.exit\n", + buf, sizeof(buf), &tv); + + upsdebugx(1, "Request for other driver to exit returned code %" PRIiSIZE, + cmdret); + if (cmdret < 0) { + /* Failed to communicate, assume no other instance runs */ + upsdebug_with_errno(1, "Socket dialog with the other driver instance " + "(may be absent) failed"); + } else { + /* NOTE: Successful dialog does not mean the other + * driver instance has stopped (just that it responded + * "yes, sir!" - actual wind-down can take some time. + */ + upslogx(LOG_WARNING, "Duplicate driver instance detected (local %s exists)! " + "Asked the other driver nicely to self-terminate!", +#ifndef WIN32 + "Unix socket" +#else + "pipe" +#endif + ); + + for (i = 10; i > 0; i--) { + if (exit_flag) + fatalx(EXIT_FAILURE, "Got a break signal ourselves during attempt to terminate other driver"); + + /* Allow driver some time to quit, and + * retry until it does not respond anymore */ + sleep(5); + + if (exit_flag) + fatalx(EXIT_FAILURE, "Got a break signal ourselves during attempt to terminate other driver"); + + tv.tv_sec = 3; + tv.tv_usec = 0; + cmdret = upsdrvquery_oneshot(progname, upsname, + "INSTCMD driver.exit\n", + buf, sizeof(buf), &tv); + upsdebugx(1, "Subsequent request for other driver to exit returned code %" + PRIiSIZE, cmdret); + + if (cmdret < 0) + break; + } + + if (i < 1) { + upslogx(LOG_WARNING, "Duplicate driver instance did not respond to termination requests! " + "Is it stuck or from an older NUT release? " + "Will retry via PID file and signals, if available."); + /* NOTE: We would try via PID in any case, + * but as we report a fault here - let the + * user know that not all is lost right now :) + */ + + /* Restore the signal errors verbosity, so that + * e.g. follow-up fopen() issues can be seen - + * we did probably encounter a sibling driver + * instance after all, so can talk about it. + */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT; + } + } + + /* Restore the socket protocol errors verbosity */ + nut_upsdrvquery_debug_level = NUT_UPSDRVQUERY_DEBUG_LEVEL_DEFAULT; + } + #ifndef WIN32 /* Setup PID file to receive signals to communicate with this driver - * instance once backgrounded, and to stop a competing older instance. - * Or to send it a signal deliberately. + * instance once backgrounded (or staying foregrounded with `-FF`), + * and to stop a competing older instance. Or to send it a signal + * deliberately. */ - if (cmd || ((foreground == 0) && (!do_forceshutdown))) { + if (cmd || ((foreground == 0 || foreground == 2) && (!do_forceshutdown))) { char pidfnbuf[SMALLBUF]; snprintf(pidfnbuf, sizeof(pidfnbuf), "%s/%s-%s.pid", altpidpath(), progname, upsname); @@ -2043,9 +2260,9 @@ int main(int argc, char **argv) int cmdret = -1; /* Send a signal to older copy of the driver, if any */ if (oldpid < 0) { - cmdret = sendsignalfn(pidfnbuf, cmd); + cmdret = sendsignalfn(pidfnbuf, cmd, progname, 1); } else { - cmdret = sendsignalpid(oldpid, cmd); + cmdret = sendsignalpid(oldpid, cmd, progname, 1); } switch (cmdret) { @@ -2061,7 +2278,7 @@ int main(int argc, char **argv) */ upslogx(LOG_WARNING, "Could not %s PID file '%s' " "to see if previous driver instance is " - "already running!", + "already running or not!", (cmdret == -3 ? "find" : "parse"), pidfnbuf); break; @@ -2072,21 +2289,21 @@ int main(int argc, char **argv) /* if cmd was nontrivial - speak up below, else be quiet */ upsdebugx(1, "Just failed to send signal, no daemon was running"); break; - } + } /* switch (cmdret) */ - /* We were signalling a daemon, successfully or not - exit now... - * Modulo the possibility of a "reload-or-something" where we - * effectively terminate the old driver and start a new one due - * to configuration changes that were not reloadable. Such mode - * is not implemented currently. - */ - if (cmdret != 0) { - /* sendsignal*() above might have logged more details - * for troubleshooting, e.g. about lack of PID file + /* We were signalling a daemon, successfully or not - exit now... + * Modulo the possibility of a "reload-or-something" where we + * effectively terminate the old driver and start a new one due + * to configuration changes that were not reloadable. Such mode + * is not implemented currently. */ - upslogx(LOG_NOTICE, "Failed to signal the currently running daemon (if any)"); + if (cmdret != 0) { + /* sendsignal*() above might have logged more details + * for troubleshooting, e.g. about lack of PID file + */ + upslogx(LOG_NOTICE, "Failed to signal the currently running daemon (if any)"); # ifdef HAVE_SYSTEMD - switch (cmd) { + switch (cmd) { case SIGCMD_RELOAD: upslogx(LOG_NOTICE, "Try something like " "'systemctl reload nut-driver@%s.service'%s", @@ -2111,7 +2328,7 @@ int main(int argc, char **argv) upsname, (oldpid < 0 ? " or add '-P $PID' argument" : "")); break; - } + } /* switch (cmd) */ /* ... or edit nut-server.service locally to start `upsd -FF` * and so save the PID file for ability to manage the daemon * beside the service framework, possibly confusing things... @@ -2121,40 +2338,47 @@ int main(int argc, char **argv) upslogx(LOG_NOTICE, "Try to add '-P $PID' argument"); } # endif /* HAVE_SYSTEMD */ - } + } /* if (cmdret != 0) */ exit((cmdret == 0) ? EXIT_SUCCESS : EXIT_FAILURE); - } + } /* if (cmd) */ /* Try to prevent that driver is started multiple times. If a PID file */ /* already exists, send a TERM signal to the process and try if it goes */ /* away. If not, retry a couple of times. */ for (i = 0; i < 3; i++) { struct stat st; + int sigret; - if (stat(pidfnbuf, &st) != 0) { - /* PID file not found */ + if ((sigret = stat(pidfnbuf, &st)) != 0) { + upsdebugx(1, "PID file %s not found; stat() returned %d (errno=%d): %s", + pidfnbuf, sigret, errno, strerror(errno)); break; } upslogx(LOG_WARNING, "Duplicate driver instance detected (PID file %s exists)! Terminating other driver!", pidfnbuf); - if (sendsignalfn(pidfnbuf, SIGTERM) != 0) { - /* Can't send signal to PID, assume invalid file */ + if ((sigret = sendsignalfn(pidfnbuf, SIGTERM, progname, 1) != 0)) { + upsdebugx(1, "Can't send signal to PID, assume invalid PID file %s; " + "sendsignalfn() returned %d (errno=%d): %s", + pidfnbuf, sigret, errno, strerror(errno)); break; } - /* Allow driver some time to quit */ + upsdebugx(1, "Signal sent without errors, allow the other driver instance some time to quit"); sleep(5); + + if (exit_flag) + fatalx(EXIT_FAILURE, "Got a break signal during attempt to terminate other driver"); } if (i > 0) { struct stat st; if (stat(pidfnbuf, &st) == 0) { upslogx(LOG_WARNING, "Duplicate driver instance is still alive (PID file %s exists) after several termination attempts! Killing other driver!", pidfnbuf); - if (sendsignalfn(pidfnbuf, SIGKILL) == 0) { + if (sendsignalfn(pidfnbuf, SIGKILL, progname, 1) == 0) { sleep(5); - if (sendsignalfn(pidfnbuf, 0) == 0) { + if (sendsignalfn(pidfnbuf, 0, progname, 1) == 0) { upslogx(LOG_WARNING, "Duplicate driver instance is still alive (could signal the process)"); /* TODO: Should we writepid() below in this case? * Or if driver init fails, restore the old content @@ -2198,7 +2422,7 @@ int main(int argc, char **argv) upslogx(LOG_WARNING, "Duplicate driver instance detected! Terminating other driver!"); for(i=0;i<10;i++) { DWORD res; - sendsignal(name, COMMAND_STOP); + sendsignal(name, COMMAND_STOP, 1); if(mutex != NULL ) { res = WaitForSingleObject(mutex,1000); if(res==WAIT_OBJECT_0) { @@ -2219,6 +2443,9 @@ int main(int argc, char **argv) } #endif /* WIN32 */ + /* Restore the signal errors verbosity */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT; + /* clear out callback handler data */ memset(&upsh, '\0', sizeof(upsh)); @@ -2445,6 +2672,7 @@ int main(int argc, char **argv) snprintf(pidfnbuf, sizeof(pidfnbuf), "%s/%s-%s.pid", altpidpath(), progname, upsname); pidfn = xstrdup(pidfnbuf); } + /* Probably was saved above already, but better safe than sorry */ upslogx(LOG_WARNING, "Running as foreground process, but saving a PID file anyway"); writepid(pidfn); break; @@ -2453,7 +2681,10 @@ int main(int argc, char **argv) upslogx(LOG_WARNING, "Running as foreground process, not saving a PID file"); } - dstate_setinfo("driver.flag.allow_killpower", "0"); + /* May already be set by parsed configuration flag, only set default if not: */ + if (dstate_getinfo("driver.flag.allow_killpower") == NULL) + dstate_setinfo("driver.flag.allow_killpower", "0"); + dstate_setflags("driver.flag.allow_killpower", ST_FLAG_RW | ST_FLAG_NUMBER); dstate_addcmd("driver.killpower"); diff --git a/drivers/main.h b/drivers/main.h index 1658ae3491..5276c98ece 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -1,5 +1,5 @@ #ifndef NUT_MAIN_H_SEEN -#define NUT_MAIN_H_SEEN +#define NUT_MAIN_H_SEEN 1 #include "common.h" #include "upsconf.h" @@ -131,6 +131,7 @@ void setup_signals(void); #ifndef WIN32 # define SIGCMD_RELOAD SIGHUP /* not a signal, so negative; relies on socket protocol */ +# define SIGCMD_EXIT -SIGTERM # define SIGCMD_RELOAD_OR_ERROR -SIGCMD_RELOAD # define SIGCMD_RELOAD_OR_EXIT SIGUSR1 /* // FIXME: Implement this self-recycling in drivers (keeping the PID): @@ -153,6 +154,7 @@ void setup_signals(void); # endif #else /* FIXME: handle WIN32 builds for other signals too */ +# define SIGCMD_EXIT "driver.exit" # define SIGCMD_RELOAD_OR_ERROR "driver.reload-or-error" #endif /* WIN32 */ diff --git a/drivers/masterguard.c b/drivers/masterguard.c index 37cae77651..f5bbb397ce 100644 --- a/drivers/masterguard.c +++ b/drivers/masterguard.c @@ -29,7 +29,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "MASTERGUARD UPS driver" -#define DRIVER_VERSION "0.26" +#define DRIVER_VERSION "0.27" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -164,7 +164,7 @@ static void parseFlags( char *flags ) ********************************************************************/ static void query1( char *buf ) { - #define WORDMAXLEN 255 +# define WORDMAXLEN 255 char value[WORDMAXLEN]; char word[WORDMAXLEN]; char *newPOS; @@ -238,7 +238,7 @@ static void query1( char *buf ) ********************************************************************/ static void query3( char *buf ) { - #define WORDMAXLEN 255 +# define WORDMAXLEN 255 char value[WORDMAXLEN]; char word[WORDMAXLEN]; char *newPOS; diff --git a/drivers/metasys.c b/drivers/metasys.c index e5003b116f..cae06a174b 100644 --- a/drivers/metasys.c +++ b/drivers/metasys.c @@ -28,7 +28,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Metasystem UPS driver" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/mge-hid.c b/drivers/mge-hid.c index 81c3a1298b..b174ac4d6b 100644 --- a/drivers/mge-hid.c +++ b/drivers/mge-hid.c @@ -2,7 +2,7 @@ * * Copyright (C) * 2003 - 2015 Arnaud Quette - * 2015 - 2016 Eaton / Arnaud Quette + * 2015 - 2024 Eaton / Arnaud Quette * * Sponsored by MGE UPS SYSTEMS * @@ -50,7 +50,7 @@ # endif #endif -#define MGE_HID_VERSION "MGE HID 1.46" +#define MGE_HID_VERSION "MGE HID 1.48" /* (prev. MGE Office Protection Systems, prev. MGE UPS SYSTEMS) */ /* Eaton */ @@ -63,18 +63,18 @@ #define POWERWARE_VENDORID 0x0592 /* Hewlett Packard */ -#define HP_VENDORID 0x03f0 +#define HP_VENDORID 0x03f0 /* AEG */ -#define AEG_VENDORID 0x2b2d +#define AEG_VENDORID 0x2b2d /* Note that normally this VID is handled by Liebert/Phoenixtec HID mapping, * here it is just for for AEG PROTECT NAS devices: */ /* Phoenixtec Power Co., Ltd */ -#define PHOENIXTEC 0x06da +#define PHOENIXTEC 0x06da /* IBM */ -#define IBM_VENDORID 0x04b3 +#define IBM_VENDORID 0x04b3 #if !((defined SHUT_MODE) && SHUT_MODE) #include "usb-common.h" @@ -131,7 +131,8 @@ typedef enum { MGE_PULSAR_M_2200, MGE_PULSAR_M_3000, MGE_PULSAR_M_3000_XL, - EATON_5P = 0x500 /* Eaton 5P / 5PX series */ + EATON_5P = 0x500, /* Eaton 5P / 5PX / 5SC series */ + EATON_9E = 0x900 /* Eaton 9E entry-level series */ } models_type_t; /* Default to line-interactive or online (ie, not offline). @@ -207,14 +208,14 @@ static long round (double value) static long round (LDOUBLE value) # endif { - long intpart; + long intpart; - intpart = (long)value; - value = value - intpart; - if (value >= 0.5) - intpart++; + intpart = (long)value; + value = value - intpart; + if (value >= 0.5) + intpart++; - return intpart; + return intpart; } #endif /* HAVE_DECL_ROUND */ @@ -489,6 +490,7 @@ static const char *mge_battery_voltage_nominal_fun(double value) case MGE_PULSAR_M: case EATON_5P: + case EATON_9E: /* Presumably per https://github.com/networkupstools/nut/issues/1925#issuecomment-1562342854 */ break; default: @@ -514,6 +516,7 @@ static const char *mge_battery_voltage_fun(double value) case MGE_EVOLUTION: case MGE_PULSAR_M: case EATON_5P: + case EATON_9E: /* Presumably per https://github.com/networkupstools/nut/issues/1925#issuecomment-1562342854 */ break; default: @@ -1123,6 +1126,55 @@ static models_name_t mge_model_names [] = { "Eaton 5P", "1150", EATON_5P, "5P 1150" }, { "Eaton 5P", "1550", EATON_5P, "5P 1550" }, + /* Eaton 5PX, names assumed per VA numbers in + * https://www.eaton.com/gb/en-gb/catalog/backup-power-ups-surge-it-power-distribution/eaton-5px-ups-emea.html#tab-2 + * and a user report in https://github.com/networkupstools/nut/issues/2380 + * Fixes for actual product/model names reported via USB are welcome + */ + { "Eaton 5PX", "1500", EATON_5P, NULL }, + { "Eaton 5PX", "2200", EATON_5P, NULL }, + { "Eaton 5PX", "3000", EATON_5P, NULL }, + + /* Eaton 5SC, names assumed per VA numbers in + * https://www.eaton.com/gb/en-gb/site-search.html.searchTerm$5sc.tabs$all.html + * and a user report in https://github.com/networkupstools/nut/issues/2380 + * Fixes for actual product/model names reported via USB are welcome + */ + { "Eaton 5SC", "500", EATON_5P, NULL }, + { "Eaton 5SC", "750", EATON_5P, NULL }, + { "Eaton 5SC", "1000", EATON_5P, NULL }, + { "Eaton 5SC", "1500", EATON_5P, NULL }, + { "Eaton 5SC", "2200", EATON_5P, NULL }, + { "Eaton 5SC", "3000", EATON_5P, NULL }, + + /* Eaton 9E entry-level series per discussions in + * https://github.com/networkupstools/nut/issues/1925 + * https://github.com/networkupstools/nut/issues/2380 + * https://github.com/networkupstools/nut/issues/2492 + */ + { "Eaton 9E", "1000", EATON_9E, "9E1000" }, + { "Eaton 9E", "1000i", EATON_9E, "9E1000i" }, + { "Eaton 9E", "1000iau", EATON_9E, "9E1000iau" }, + { "Eaton 9E", "1000ir", EATON_9E, "9E1000ir" }, + { "Eaton 9E", "2000", EATON_9E, "9E2000" }, + { "Eaton 9E", "2000i", EATON_9E, "9E2000i" }, + { "Eaton 9E", "2000iau", EATON_9E, "9E2000iau" }, + { "Eaton 9E", "2000ir", EATON_9E, "9E2000ir" }, + { "Eaton 9E", "3000", EATON_9E, "9E3000" }, + { "Eaton 9E", "3000i", EATON_9E, "9E3000i" }, + { "Eaton 9E", "3000iau", EATON_9E, "9E3000iau" }, + { "Eaton 9E", "3000ir", EATON_9E, "9E3000ir" }, + { "Eaton 9E", "3000ixl", EATON_9E, "9E3000ixl" }, + { "Eaton 9E", "3000ixlau", EATON_9E, "9E3000ixlau" }, + + /* https://github.com/networkupstools/nut/issues/1925#issuecomment-1609262963 + * if we failed to get iManufacturer, iProduct and iSerialNumber but saw + * the UPS.Flow.[4].ConfigApparentPower (the "2000" or "3000" part here) + */ + { "unknown", "1000", EATON_9E, "9E1000i (presumed)" }, + { "unknown", "2000", EATON_9E, "9E2000i (presumed)" }, + { "unknown", "3000", EATON_9E, "9E3000i (presumed)" }, + /* Pulsar M models */ { "PULSAR M", "2200", MGE_PULSAR_M_2200, NULL }, { "PULSAR M", "3000", MGE_PULSAR_M_3000, NULL }, @@ -1648,6 +1700,9 @@ static int mge_claim(HIDDevice_t *hd) { /* Let liebert-hid grab this */ return 0; + + default: + break; } return 1; diff --git a/drivers/mge-utalk.c b/drivers/mge-utalk.c index 9e34954199..dca2a14fe6 100644 --- a/drivers/mge-utalk.c +++ b/drivers/mge-utalk.c @@ -69,7 +69,7 @@ /* --------------------------------------------------------------- */ #define DRIVER_NAME "MGE UPS SYSTEMS/U-Talk driver" -#define DRIVER_VERSION "0.95" +#define DRIVER_VERSION "0.96" /* driver description structure */ diff --git a/drivers/mge-utalk.h b/drivers/mge-utalk.h index 67b1135533..91efb054f1 100644 --- a/drivers/mge-utalk.h +++ b/drivers/mge-utalk.h @@ -173,10 +173,10 @@ static const double multiplier[4][8] = { /* use explicit booleans */ #ifdef FALSE - #undef FALSE +# undef FALSE #endif /* FALSE */ #ifdef TRUE - #undef TRUE +# undef TRUE #endif /* TRUE */ typedef enum ebool { FALSE=0, TRUE } bool_t; diff --git a/drivers/mge-xml.c b/drivers/mge-xml.c index 42eab857c7..b7ec2c59c0 100644 --- a/drivers/mge-xml.c +++ b/drivers/mge-xml.c @@ -510,6 +510,8 @@ static const char *mge_beeper_info(const char *arg_val) return "enabled"; case 3: return "muted"; + default: + break; } return NULL; } @@ -528,6 +530,8 @@ static const char *mge_upstype_conversion(const char *arg_val) return "online - parallel with hot standy"; case 5: return "online - hot standby redundancy"; + default: + break; } return NULL; } @@ -542,6 +546,8 @@ static const char *mge_sensitivity_info(const char *arg_val) return "high"; case 2: return "low"; + default: + break; } return NULL; } @@ -566,6 +572,8 @@ static const char *mge_test_result_info(const char *arg_val) return "no test initiated"; case 7: return "test scheduled"; + default: + break; } return NULL; } @@ -1416,6 +1424,10 @@ static int mge_xml_startelm_cb(void *userdata, int parent, const char *nspace, c state = XC_BROADCAST; break; } + break; + + default: + break; } upsdebugx(3, "%s: name <%s> (parent = %d, state = %d)", __func__, name, parent, state); @@ -1445,6 +1457,9 @@ static int mge_xml_cdata_cb(void *userdata, int state, const char *cdata, size_t case GO_OBJECT: snprintfcat(val, sizeof(val), "%.*s", (int)len, cdata); break; + + default: + break; } return 0; @@ -1517,6 +1532,9 @@ static int mge_xml_endelm_cb(void *userdata, int state, const char *nspace, cons &inited_phaseinfo_out, &num_outphases, 0); break; + + default: + break; } return 0; diff --git a/drivers/microdowell.c b/drivers/microdowell.c index 9b2637ea81..0d491b1c7e 100644 --- a/drivers/microdowell.c +++ b/drivers/microdowell.c @@ -44,7 +44,7 @@ #define MAX_SHUTDOWN_DELAY_LEN 5 #define DRIVER_NAME "MICRODOWELL UPS driver" -#define DRIVER_VERSION "0.03" +#define DRIVER_VERSION "0.04" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -213,7 +213,7 @@ static void SendCmdToSerial(unsigned char *Buff, size_t Len) static unsigned char * CmdSerial(unsigned char *OutBuffer, size_t Len, unsigned char *RetBuffer) { - #define TMP_BUFF_LEN 1024 +# define TMP_BUFF_LEN 1024 unsigned char InpBuff[TMP_BUFF_LEN+1] ; unsigned char TmpBuff[3] ; int i, ErrCode ; diff --git a/drivers/microsol-apc.c b/drivers/microsol-apc.c index 329263703f..c8979b67b1 100644 --- a/drivers/microsol-apc.c +++ b/drivers/microsol-apc.c @@ -35,7 +35,7 @@ #include "microsol-apc.h" #define DRIVER_NAME "APC Back-UPS BR series UPS driver" -#define DRIVER_VERSION "0.70" +#define DRIVER_VERSION "0.71" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/netxml-ups.c b/drivers/netxml-ups.c index 7ebc30432f..cfdc31632c 100644 --- a/drivers/netxml-ups.c +++ b/drivers/netxml-ups.c @@ -42,7 +42,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "network XML UPS" -#define DRIVER_VERSION "0.45" +#define DRIVER_VERSION "0.46" /** *_OBJECT query multi-part body boundary */ #define FORM_POST_BOUNDARY "NUT-NETXML-UPS-OBJECTS" @@ -1149,13 +1149,36 @@ static void object_entry_destroy(object_query_t *handle, object_entry_t *entry) switch (handle->type) { case SET_OBJECT_REQUEST: set_object_req_destroy(&entry->payld.req); - break; case SET_OBJECT_RESPONSE: set_object_resp_destroy(&entry->payld.resp); + break; +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } /* Destroy entry */ @@ -1809,13 +1832,36 @@ static object_query_t *set_object(object_query_t *req) { switch (req->mode) { case RAW_POST: resp = set_object_raw(req); - break; case FORM_POST: resp = set_object_form(req); + break; +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic push +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT +# pragma GCC diagnostic ignored "-Wcovered-switch-default" +#endif +#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE +# pragma GCC diagnostic ignored "-Wunreachable-code" +#endif +/* Older CLANG (e.g. clang-3.4) seems to not support the GCC pragmas above */ +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunreachable-code" +# pragma clang diagnostic ignored "-Wcovered-switch-default" +#endif + 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) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) +# pragma GCC diagnostic pop +#endif } return resp; diff --git a/drivers/nut-ipmipsu.c b/drivers/nut-ipmipsu.c index 1ed5ea72ad..95845285a3 100644 --- a/drivers/nut-ipmipsu.c +++ b/drivers/nut-ipmipsu.c @@ -27,7 +27,7 @@ #include "nut-ipmi.h" #define DRIVER_NAME "IPMI PSU driver" -#define DRIVER_VERSION "0.32" +#define DRIVER_VERSION "0.33" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -183,7 +183,8 @@ void upsdrv_help(void) /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { - /* FIXME: need more params. + /* FIXME: need more params. */ +/* addvar(VAR_VALUE, "username", "Remote server username"); addvar(VAR_VALUE, "password", "Remote server password"); addvar(VAR_VALUE, "authtype", @@ -192,7 +193,9 @@ void upsdrv_makevartable(void) "Type of the device to match ('psu' for \"Power Supply\")"); addvar(VAR_VALUE, "serial", "Serial number to match a specific device"); - addvar(VAR_VALUE, "fruid", "FRU identifier to match a specific device"); */ + addvar(VAR_VALUE, "fruid", "FRU identifier to match a specific device"); + addvar(VAR_VALUE, "sensorid", "Sensor identifier to match a specific device"); +*/ } void upsdrv_initups(void) diff --git a/drivers/nut-libfreeipmi.c b/drivers/nut-libfreeipmi.c index cdf3d59fb4..02f87b090c 100644 --- a/drivers/nut-libfreeipmi.c +++ b/drivers/nut-libfreeipmi.c @@ -48,7 +48,7 @@ #include #include #if HAVE_FREEIPMI_MONITORING -#include +# include #endif #include "nut-ipmi.h" #include "nut_stdint.h" @@ -69,41 +69,41 @@ static ipmi_monitoring_ctx_t mon_ctx = NULL; #ifdef HAVE_FREEIPMI_11X_12X static ipmi_sdr_ctx_t sdr_ctx = NULL; static ipmi_fru_ctx_t fru_ctx = NULL; - #define SDR_PARSE_CTX sdr_ctx - #define NUT_IPMI_SDR_CACHE_DEFAULTS IPMI_SDR_CACHE_CREATE_FLAGS_DEFAULT +# define SDR_PARSE_CTX sdr_ctx +# define NUT_IPMI_SDR_CACHE_DEFAULTS IPMI_SDR_CACHE_CREATE_FLAGS_DEFAULT #else static ipmi_sdr_cache_ctx_t sdr_ctx = NULL; static ipmi_sdr_parse_ctx_t sdr_parse_ctx = NULL; - #define SDR_PARSE_CTX sdr_parse_ctx +# define SDR_PARSE_CTX sdr_parse_ctx static ipmi_fru_parse_ctx_t fru_ctx = NULL; /* Functions remapping */ - #define ipmi_sdr_ctx_create ipmi_sdr_cache_ctx_create - #define ipmi_sdr_ctx_destroy ipmi_sdr_cache_ctx_destroy - #define ipmi_sdr_ctx_errnum ipmi_sdr_cache_ctx_errnum - #define ipmi_sdr_ctx_errormsg ipmi_sdr_cache_ctx_errormsg - #define ipmi_fru_ctx_create ipmi_fru_parse_ctx_create - #define ipmi_fru_ctx_destroy ipmi_fru_parse_ctx_destroy - #define ipmi_fru_ctx_set_flags ipmi_fru_parse_ctx_set_flags - #define ipmi_fru_ctx_strerror ipmi_fru_parse_ctx_strerror - #define ipmi_fru_ctx_errnum ipmi_fru_parse_ctx_errnum - #define ipmi_fru_open_device_id ipmi_fru_parse_open_device_id - #define ipmi_fru_close_device_id ipmi_fru_parse_close_device_id - #define ipmi_fru_ctx_errormsg ipmi_fru_parse_ctx_errormsg - #define ipmi_fru_read_data_area ipmi_fru_parse_read_data_area - #define ipmi_fru_next ipmi_fru_parse_next - #define ipmi_fru_type_length_field_to_string ipmi_fru_parse_type_length_field_to_string - #define ipmi_fru_multirecord_power_supply_information ipmi_fru_parse_multirecord_power_supply_information - #define ipmi_fru_board_info_area ipmi_fru_parse_board_info_area - #define ipmi_fru_field_t ipmi_fru_parse_field_t +# define ipmi_sdr_ctx_create ipmi_sdr_cache_ctx_create +# define ipmi_sdr_ctx_destroy ipmi_sdr_cache_ctx_destroy +# define ipmi_sdr_ctx_errnum ipmi_sdr_cache_ctx_errnum +# define ipmi_sdr_ctx_errormsg ipmi_sdr_cache_ctx_errormsg +# define ipmi_fru_ctx_create ipmi_fru_parse_ctx_create +# define ipmi_fru_ctx_destroy ipmi_fru_parse_ctx_destroy +# define ipmi_fru_ctx_set_flags ipmi_fru_parse_ctx_set_flags +# define ipmi_fru_ctx_strerror ipmi_fru_parse_ctx_strerror +# define ipmi_fru_ctx_errnum ipmi_fru_parse_ctx_errnum +# define ipmi_fru_open_device_id ipmi_fru_parse_open_device_id +# define ipmi_fru_close_device_id ipmi_fru_parse_close_device_id +# define ipmi_fru_ctx_errormsg ipmi_fru_parse_ctx_errormsg +# define ipmi_fru_read_data_area ipmi_fru_parse_read_data_area +# define ipmi_fru_next ipmi_fru_parse_next +# define ipmi_fru_type_length_field_to_string ipmi_fru_parse_type_length_field_to_string +# define ipmi_fru_multirecord_power_supply_information ipmi_fru_parse_multirecord_power_supply_information +# define ipmi_fru_board_info_area ipmi_fru_parse_board_info_area +# define ipmi_fru_field_t ipmi_fru_parse_field_t /* Constants */ - #define IPMI_SDR_MAX_RECORD_LENGTH IPMI_SDR_CACHE_MAX_SDR_RECORD_LENGTH - #define IPMI_SDR_ERR_CACHE_READ_CACHE_DOES_NOT_EXIST IPMI_SDR_CACHE_ERR_CACHE_READ_CACHE_DOES_NOT_EXIST - #define IPMI_FRU_AREA_SIZE_MAX IPMI_FRU_PARSE_AREA_SIZE_MAX - #define IPMI_FRU_FLAGS_SKIP_CHECKSUM_CHECKS IPMI_FRU_PARSE_FLAGS_SKIP_CHECKSUM_CHECKS - #define IPMI_FRU_AREA_TYPE_BOARD_INFO_AREA IPMI_FRU_PARSE_AREA_TYPE_BOARD_INFO_AREA - #define IPMI_FRU_AREA_TYPE_MULTIRECORD_POWER_SUPPLY_INFORMATION IPMI_FRU_PARSE_AREA_TYPE_MULTIRECORD_POWER_SUPPLY_INFORMATION - #define IPMI_FRU_AREA_STRING_MAX IPMI_FRU_PARSE_AREA_STRING_MAX - #define NUT_IPMI_SDR_CACHE_DEFAULTS IPMI_SDR_CACHE_CREATE_FLAGS_DEFAULT, IPMI_SDR_CACHE_VALIDATION_FLAGS_DEFAULT +# define IPMI_SDR_MAX_RECORD_LENGTH IPMI_SDR_CACHE_MAX_SDR_RECORD_LENGTH +# define IPMI_SDR_ERR_CACHE_READ_CACHE_DOES_NOT_EXIST IPMI_SDR_CACHE_ERR_CACHE_READ_CACHE_DOES_NOT_EXIST +# define IPMI_FRU_AREA_SIZE_MAX IPMI_FRU_PARSE_AREA_SIZE_MAX +# define IPMI_FRU_FLAGS_SKIP_CHECKSUM_CHECKS IPMI_FRU_PARSE_FLAGS_SKIP_CHECKSUM_CHECKS +# define IPMI_FRU_AREA_TYPE_BOARD_INFO_AREA IPMI_FRU_PARSE_AREA_TYPE_BOARD_INFO_AREA +# define IPMI_FRU_AREA_TYPE_MULTIRECORD_POWER_SUPPLY_INFORMATION IPMI_FRU_PARSE_AREA_TYPE_MULTIRECORD_POWER_SUPPLY_INFORMATION +# define IPMI_FRU_AREA_STRING_MAX IPMI_FRU_PARSE_AREA_STRING_MAX +# define NUT_IPMI_SDR_CACHE_DEFAULTS IPMI_SDR_CACHE_CREATE_FLAGS_DEFAULT, IPMI_SDR_CACHE_VALIDATION_FLAGS_DEFAULT #endif /* HAVE_FREEIPMI_11X_12X */ /* FIXME: freeipmi auto selects a cache based on the hostname you are @@ -310,22 +310,23 @@ static const char* libfreeipmi_getfield (uint8_t language_code, if (strbuflen) return strbuf; - return NULL; + return NULL; } /* Get voltage value from the IPMI voltage code */ static float libfreeipmi_get_voltage (uint8_t voltage_code) { - if (voltage_code == IPMI_FRU_VOLTAGE_12V) - return 12; - else if (voltage_code == IPMI_FRU_VOLTAGE_MINUS12V) - return -12; - else if (voltage_code == IPMI_FRU_VOLTAGE_5V) - return 5; - else if (voltage_code == IPMI_FRU_VOLTAGE_3_3V) - return 3.3; - else - return 0; + /* FIXME: switch/case? */ + if (voltage_code == IPMI_FRU_VOLTAGE_12V) + return 12; + else if (voltage_code == IPMI_FRU_VOLTAGE_MINUS12V) + return -12; + else if (voltage_code == IPMI_FRU_VOLTAGE_5V) + return 5; + else if (voltage_code == IPMI_FRU_VOLTAGE_3_3V) + return 3.3; + else + return 0; } /* Cleanup IPMI contexts */ @@ -363,6 +364,11 @@ static int libfreeipmi_get_psu_info (const void *areabuf, uint8_t area_length, IPMIDevice_t *ipmi_dev) { + /* FIXME: libfreeipmi headers currently define the 4 voltage_range + * values as "unsigned int". It seems earlier it was "int", but now + * the compiler complains when it is in place - so changed to unsigned. + * The proper fix might be to detect and influence this in configure. */ + /* FIXME: directly use ipmi_dev fields */ unsigned int overall_capacity; input_voltage_range_t low_end_input_voltage_range_1; @@ -1063,6 +1069,8 @@ int nut_ipmi_get_sensors_status(IPMIDevice_t *ipmi_dev) str_count++; } break; + default: + break; } } @@ -1086,6 +1094,8 @@ int nut_ipmi_get_sensors_status(IPMIDevice_t *ipmi_dev) status_set("OFF"); retval = 0; break; + default: + break; } status_commit(); diff --git a/drivers/nutdrv_atcl_usb.c b/drivers/nutdrv_atcl_usb.c index c8d1162e2e..5c52057a33 100644 --- a/drivers/nutdrv_atcl_usb.c +++ b/drivers/nutdrv_atcl_usb.c @@ -28,7 +28,7 @@ /* driver version */ #define DRIVER_NAME "'ATCL FOR UPS' USB driver" -#define DRIVER_VERSION "1.17" +#define DRIVER_VERSION "1.18" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -440,6 +440,9 @@ static int usb_device_open(usb_dev_handle **handlep, USBDevice_t *device, USBDev case -2: upsdebugx(4, "matcher: unspecified error"); goto next_device; + + default: + break; } } #ifdef HAVE_LIBUSB_SET_AUTO_DETACH_KERNEL_DRIVER @@ -686,6 +689,7 @@ void upsdrv_makevartable(void) { /* NOTE: This driver uses a very custom device matching method, * so does not involve nut_usb_addvars() method like others do. + * When fixing, see also tools/nut-scanner/scan_usb.c "exceptions". */ addvar(VAR_VALUE, "vendor", "USB vendor string (or NULL if none)"); } diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index 177a659d22..3bc1d8675a 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -47,22 +47,22 @@ /* note: QX_USB/QX_SERIAL set through Makefile */ #ifdef QX_USB - #include "nut_libusb.h" /* also includes "usb-common.h" */ +# include "nut_libusb.h" /* also includes "usb-common.h" */ - #ifdef QX_SERIAL - #define DRIVER_NAME "Generic Q* USB/Serial driver" - #else - #define DRIVER_NAME "Generic Q* USB driver" - #endif /* QX_SERIAL */ +# ifdef QX_SERIAL +# define DRIVER_NAME "Generic Q* USB/Serial driver" +# else +# define DRIVER_NAME "Generic Q* USB driver" +# endif /* QX_SERIAL */ #else - #define DRIVER_NAME "Generic Q* Serial driver" +# define DRIVER_NAME "Generic Q* Serial driver" #endif /* QX_USB */ -#define DRIVER_VERSION "0.36" +#define DRIVER_VERSION "0.37" #ifdef QX_SERIAL - #include "serial.h" - #define SER_WAIT_SEC 1 /* 3 seconds for Best UPS */ +# include "serial.h" +# define SER_WAIT_SEC 1 /* 3 seconds for Best UPS */ #endif /* QX_SERIAL */ #include "nutdrv_qx.h" @@ -370,8 +370,7 @@ static void qx_initbattery(void) batt_packs_known = 1; } - val = getval("battery_voltage_reports_one_pack"); - if (val) { + if (testvar("battery_voltage_reports_one_pack")) { battery_voltage_reports_one_pack = 1; /* If we already have a battery.voltage reading from the device, * it is not yet "adjusted" to consider the multiplication for @@ -758,6 +757,9 @@ static int phoenix_command(const char *cmd, char *buf, size_t buflen) case LIBUSB_ERROR_TIMEOUT: /* Connection timed out */ break; + + default: + break; } if (ret < 0) { @@ -2257,7 +2259,7 @@ static qx_usb_device_id_t qx_usb_id[] = { { USB_DEVICE(0x0001, 0x0000), "ATCL FOR UPS", "ATCL FOR UPS", &fuji_subdriver }, /* Fuji UPSes */ { USB_DEVICE(0x0001, 0x0000), NULL, NULL, &krauler_subdriver }, /* Krauler UP-M500VA */ { USB_DEVICE(0x0001, 0x0000), NULL, "MEC0003", &snr_subdriver }, /* SNR-UPS-LID-XXXX UPSes */ - { USB_DEVICE(0x0925, 0x1234), NULL, NULL, &armac_subdriver }, /* Armac UPS and maybe other richcomm-like or using old PowerManagerII software */ + { USB_DEVICE(0x0925, 0x1234), NULL, NULL, &armac_subdriver }, /* Armac UPS and maybe other richcomm-like or using old PowerManagerII software */ /* End of list */ { -1, -1, NULL, NULL, NULL } }; @@ -2872,7 +2874,7 @@ void upsdrv_shutdown(void) } #ifdef QX_USB - #ifndef TESTING +# ifndef TESTING static const struct { const char *name; int (*command)(const char *cmd, char *buf, size_t buflen); @@ -2891,7 +2893,7 @@ void upsdrv_shutdown(void) { "armac", &armac_command }, { NULL, NULL } }; - #endif +# endif #endif @@ -3194,13 +3196,13 @@ void upsdrv_initups(void) /* Serial */ #ifdef QX_SERIAL - #ifdef QX_USB +# ifdef QX_USB if (!is_usb) { - #else +# else { /* scoping */ - #endif /* QX_USB */ +# endif /* QX_USB */ - #ifndef TESTING +# ifndef TESTING const struct { const char *val; @@ -3261,13 +3263,13 @@ void upsdrv_initups(void) /* Allow some time to settle for the cablepower */ usleep(100000); - #endif /* TESTING */ +# endif /* TESTING */ - #ifdef QX_USB +# ifdef QX_USB } else { /* is_usb */ - #else +# else } /* end of scoping */ - #endif /* QX_USB */ +# endif /* QX_USB */ #endif /* QX_SERIAL */ @@ -3530,7 +3532,7 @@ static ssize_t qx_command(const char *cmd, char *buf, size_t buflen) # endif #endif - #if WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */ +# if WITH_LIBUSB_0_1 /* limit to libusb 0.1 implementation */ case -EPERM: /* Operation not permitted */ fatal_with_errno(EXIT_FAILURE, "Permissions problem"); #ifndef HAVE___ATTRIBUTE__NORETURN @@ -3543,7 +3545,7 @@ static ssize_t qx_command(const char *cmd, char *buf, size_t buflen) # pragma GCC diagnostic pop # endif #endif - #endif /* WITH_LIBUSB_0_1 */ +# endif /* WITH_LIBUSB_0_1 */ case LIBUSB_ERROR_PIPE: /* Broken pipe */ if (usb_clear_halt(udev, 0x81) == 0) { diff --git a/drivers/nutdrv_qx.h b/drivers/nutdrv_qx.h index 0c5f20688a..faffe717cc 100644 --- a/drivers/nutdrv_qx.h +++ b/drivers/nutdrv_qx.h @@ -26,11 +26,18 @@ #ifndef NUTDRV_QX_H #define NUTDRV_QX_H +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif + #include #include #include #include -#include "config.h" /* For testing purposes */ /*#define TESTING*/ diff --git a/drivers/nutdrv_qx_masterguard.c b/drivers/nutdrv_qx_masterguard.c index 85a0cae0f6..f7e155b6a2 100644 --- a/drivers/nutdrv_qx_masterguard.c +++ b/drivers/nutdrv_qx_masterguard.c @@ -619,6 +619,8 @@ static int masterguard_setvar(item_t *item, char *value, const size_t valuelen) case 's': snprintf(value, valuelen, item->command, s); break; + default: + break; } #ifdef HAVE_PRAGMAS_FOR_GCC_DIAGNOSTIC_IGNORED_FORMAT_NONLITERAL #pragma GCC diagnostic pop diff --git a/drivers/nutdrv_siemens_sitop.c b/drivers/nutdrv_siemens_sitop.c index 1978f04022..f45bbc6dd4 100644 --- a/drivers/nutdrv_siemens_sitop.c +++ b/drivers/nutdrv_siemens_sitop.c @@ -56,7 +56,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Siemens SITOP UPS500 series driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.05" #define RX_BUFFER_SIZE 100 diff --git a/drivers/oneac.c b/drivers/oneac.c index 0bb30d1f47..dcbac8e744 100644 --- a/drivers/oneac.c +++ b/drivers/oneac.c @@ -48,7 +48,7 @@ int setcmd(const char* varname, const char* setvalue); int instcmd(const char *cmdname, const char *extra); #define DRIVER_NAME "Oneac EG/ON/OZ/OB UPS driver" -#define DRIVER_VERSION "0.82" +#define DRIVER_VERSION "0.83" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/openups-hid.c b/drivers/openups-hid.c index 0a9eda9ad0..2e99c5440e 100644 --- a/drivers/openups-hid.c +++ b/drivers/openups-hid.c @@ -63,6 +63,8 @@ static void *get_voltage_multiplier(USBDevice_t *device) ccharge_scale = 0.1; /* unverified */ cdischarge_scale = 0.1; /* unverified */ break; + default: + break; } upsdebugx(1, "vin_scale = %g; vout_scale = %g\n", vin_scale, vout_scale); diff --git a/drivers/optiups.c b/drivers/optiups.c index b3fe2634f2..9f4344d53c 100644 --- a/drivers/optiups.c +++ b/drivers/optiups.c @@ -28,7 +28,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Opti-UPS driver" -#define DRIVER_VERSION "1.04" +#define DRIVER_VERSION "1.05" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/phoenixcontact_modbus.c b/drivers/phoenixcontact_modbus.c index 733989ef68..ab447e19af 100644 --- a/drivers/phoenixcontact_modbus.c +++ b/drivers/phoenixcontact_modbus.c @@ -24,7 +24,7 @@ #include #define DRIVER_NAME "NUT PhoenixContact Modbus driver" -#define DRIVER_VERSION "0.03" +#define DRIVER_VERSION "0.04" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 192 diff --git a/drivers/pijuice.c b/drivers/pijuice.c index 3e43bfdf89..09499f099a 100644 --- a/drivers/pijuice.c +++ b/drivers/pijuice.c @@ -22,6 +22,9 @@ #include #include "nut_stdint.h" +#define DRIVER_NAME "PiJuice UPS driver" +#define DRIVER_VERSION "0.12" + /* * Linux I2C userland is a bit of a mess until distros refresh to * the i2c-tools 4.x release that profides i2c/smbus.h for userspace @@ -217,9 +220,6 @@ static inline __u8* i2c_smbus_read_i2c_block_data(int file, __u8 command, __u8 l #define HIGH_BATTERY_THRESHOLD 75.0 #define NOMINAL_BATTERY_VOLTAGE 4.18 -#define DRIVER_NAME "PiJuice UPS driver" -#define DRIVER_VERSION "0.11" - static uint8_t i2c_address = 0x14; static uint8_t shutdown_delay = 30; diff --git a/drivers/powercom-hid.c b/drivers/powercom-hid.c index 5f838e1bc7..ebadc68841 100644 --- a/drivers/powercom-hid.c +++ b/drivers/powercom-hid.c @@ -4,7 +4,7 @@ * 2003 - 2009 Arnaud Quette * 2005 - 2006 Peter Selinger * 2008 - 2009 Arjen de Korte - * 2020 - 2022 Jim Klimov + * 2020 - 2024 Jim Klimov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -26,7 +26,7 @@ #include "powercom-hid.h" #include "usb-common.h" -#define POWERCOM_HID_VERSION "PowerCOM HID 0.7" +#define POWERCOM_HID_VERSION "PowerCOM HID 0.71" /* FIXME: experimental flag to be put in upsdrv_info */ /* PowerCOM */ @@ -54,6 +54,14 @@ static usb_device_id_t powercom_usb_device_table[] = { static char powercom_scratch_buf[32]; +/* Original subdriver code until version 0.7 sent shutdown commands + * in a wrong byte order than is needed by devices seen in the field + * in 2024. Just in case there are different firmwares, we provide + * a toggle to use old behavior. Maybe it was just a bug and nobody + * needs this fall-back... + */ +static char powercom_sdcmd_byte_order_fallback = 0; + static const char *powercom_startup_fun(double value) { uint16_t i = value; @@ -93,7 +101,14 @@ static const char *powercom_shutdown_fun(double value) { uint16_t i = value; - snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (i & 0x00FF) + (i >> 8)); + if (powercom_sdcmd_byte_order_fallback) { + /* Legacy behavior */ + snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (i & 0x00FF) + (i >> 8)); + } else { + /* New default */ + snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (i >> 8) + (i & 0x00FF)); + } + upsdebugx(3, "%s: value = %.0f, buf = %s", __func__, value, powercom_scratch_buf); return powercom_scratch_buf; @@ -113,8 +128,16 @@ static double powercom_shutdown_nuf(const char *value) val = (uint16_t)iv; val = val ? val : 1; /* 0 sets the maximum delay */ - command = ((uint16_t)((val % 60) << 8)) + (uint16_t)(val / 60); - command |= 0x4000; /* AC RESTART NORMAL ENABLE */ + if (powercom_sdcmd_byte_order_fallback) { + /* Legacy behavior */ + command = ((uint16_t)((val % 60) << 8)) + (uint16_t)(val / 60); + command |= 0x4000; /* AC RESTART NORMAL ENABLE */ + } else { + /* New default */ + command = ((uint16_t)((val / 60) << 8)) + (uint16_t)(val % 60); + command |= 0x0040; /* AC RESTART NORMAL ENABLE */ + } + upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command); return command; @@ -138,8 +161,16 @@ static double powercom_stayoff_nuf(const char *value) val = (uint16_t)iv; val = val ? val : 1; /* 0 sets the maximum delay */ - command = ((uint16_t)((val % 60) << 8)) + (uint16_t)(val / 60); - command |= 0x8000; /* AC RESTART NORMAL DISABLE */ + if (powercom_sdcmd_byte_order_fallback) { + /* Legacy behavior */ + command = ((uint16_t)((val % 60) << 8)) + (uint16_t)(val / 60); + command |= 0x8000; /* AC RESTART NORMAL DISABLE */ + } else { + /* New default */ + command = ((uint16_t)((val / 60) << 8)) + (uint16_t)(val % 60); + command |= 0x0080; /* AC RESTART NORMAL DISABLE */ + } + upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command); return command; @@ -541,8 +572,9 @@ static int powercom_claim(HIDDevice_t *hd) } /* by default, reject, unless the productid option is given */ if (getval("productid")) { - return 1; + goto accept; } + /* report and reject */ possibly_supported("PowerCOM", hd); return 0; @@ -551,12 +583,17 @@ static int powercom_claim(HIDDevice_t *hd) interrupt_only = 1; interrupt_size = 8; } - return 1; + goto accept; case NOT_SUPPORTED: default: return 0; } + +accept: + powercom_sdcmd_byte_order_fallback = testvar("powercom_sdcmd_byte_order_fallback"); + + return 1; } subdriver_t powercom_subdriver = { diff --git a/drivers/powercom.c b/drivers/powercom.c index 8b22f74c84..d31e730b7c 100644 --- a/drivers/powercom.c +++ b/drivers/powercom.c @@ -83,10 +83,10 @@ #include "main.h" #include "serial.h" #include "powercom.h" -#include "math.h" +#include "nut_float.h" #define DRIVER_NAME "PowerCom protocol UPS driver" -#define DRIVER_VERSION "0.21" +#define DRIVER_VERSION "0.23" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -669,6 +669,7 @@ static float load_level(void) case 1000: case 1500: case 2000: return raw_data[UPS_LOAD]*110.0/load1000i[voltage]; + default: break; } } } else if (!strcmp(types[type].name, "KIN")) { diff --git a/drivers/powerman-pdu.c b/drivers/powerman-pdu.c index 316950a47c..277bd4d347 100644 --- a/drivers/powerman-pdu.c +++ b/drivers/powerman-pdu.c @@ -23,7 +23,7 @@ #include /* pm_err_t and other beasts */ #define DRIVER_NAME "Powerman PDU client driver" -#define DRIVER_VERSION "0.13" +#define DRIVER_VERSION "0.14" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/powerp-bin.c b/drivers/powerp-bin.c index 0c7da014a0..3c6d74f00b 100644 --- a/drivers/powerp-bin.c +++ b/drivers/powerp-bin.c @@ -32,10 +32,9 @@ #include "powerp-bin.h" #include "nut_stdint.h" +#include "nut_float.h" -#include - -#define POWERPANEL_BIN_VERSION "Powerpanel-Binary 0.5" +#define POWERPANEL_BIN_VERSION "Powerpanel-Binary 0.60" typedef struct { unsigned char start; diff --git a/drivers/powerp-txt.c b/drivers/powerp-txt.c index 0390a7ca64..d13e8899a0 100644 --- a/drivers/powerp-txt.c +++ b/drivers/powerp-txt.c @@ -36,7 +36,7 @@ #include -#define POWERPANEL_TEXT_VERSION "Powerpanel-Text 0.6" +#define POWERPANEL_TEXT_VERSION "Powerpanel-Text 0.60" typedef struct { float i_volt; diff --git a/drivers/powerpanel.c b/drivers/powerpanel.c index 9ec775784e..5600ad4f7c 100644 --- a/drivers/powerpanel.c +++ b/drivers/powerpanel.c @@ -36,7 +36,7 @@ static subdriver_t *subdriver[] = { }; #define DRIVER_NAME "CyberPower text/binary protocol UPS driver" -#define DRIVER_VERSION "0.29" +#define DRIVER_VERSION "0.30" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/powervar-hid.c b/drivers/powervar-hid.c index a37978aa48..4821ca88c2 100644 --- a/drivers/powervar-hid.c +++ b/drivers/powervar-hid.c @@ -119,14 +119,16 @@ static int powervar_claim(HIDDevice_t *hd) case POSSIBLY_SUPPORTED: /* by default, reject, unless the productid option is given */ if (getval("productid")) { - usb->hid_rep_index = 1; + if (!getval("usb_hid_rep_index")) + usb->hid_rep_index = 1; return 1; } possibly_supported("Powervar", hd); return 0; case SUPPORTED: - usb->hid_rep_index = 1; + if (!getval("usb_hid_rep_index")) + usb->hid_rep_index = 1; return 1; case NOT_SUPPORTED: diff --git a/drivers/rhino.c b/drivers/rhino.c index 6ceab04158..f312fab798 100644 --- a/drivers/rhino.c +++ b/drivers/rhino.c @@ -38,7 +38,7 @@ #include "timehead.h" #define DRIVER_NAME "Microsol Rhino UPS driver" -#define DRIVER_VERSION "0.53" +#define DRIVER_VERSION "0.54" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -464,6 +464,9 @@ CommReceive(const unsigned char *bufptr, ssize_t size) break; } + default: + break; + } Waiting = 0; diff --git a/drivers/richcomm_usb.c b/drivers/richcomm_usb.c index 053144654b..766d3bba60 100644 --- a/drivers/richcomm_usb.c +++ b/drivers/richcomm_usb.c @@ -30,7 +30,7 @@ /* driver version */ #define DRIVER_NAME "Richcomm dry-contact to USB driver" -#define DRIVER_VERSION "0.12" +#define DRIVER_VERSION "0.13" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -490,6 +490,9 @@ static int usb_device_open(usb_dev_handle **handlep, USBDevice_t *device, USBDev case -2: upsdebugx(4, "matcher: unspecified error"); goto next_device; + + default: + break; } } #ifdef HAVE_LIBUSB_SET_AUTO_DETACH_KERNEL_DRIVER @@ -743,6 +746,7 @@ void upsdrv_makevartable(void) { /* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */ /* TODO: Uncomment while addressing https://github.com/networkupstools/nut/issues/1768 - nut_usb_addvars(); + * When fixing, see also tools/nut-scanner/scan_usb.c "exceptions". + * nut_usb_addvars(); */ } diff --git a/drivers/riello.c b/drivers/riello.c index 28c992bfde..dc3a09796f 100644 --- a/drivers/riello.c +++ b/drivers/riello.c @@ -86,6 +86,8 @@ uint16_t riello_calc_CRC(uint8_t type, uint8_t *buff, uint16_t size, uint8_t che CRC_Word += buff[i]; } break; + default: + break; } return(CRC_Word); } @@ -126,6 +128,8 @@ uint8_t riello_test_crc(uint8_t type, uint8_t *buff, uint16_t size, uint8_t chec if (suma != CRC_Word) return(1); break; + default: + break; } return(0); } @@ -944,6 +948,8 @@ uint8_t riello_header(uint8_t type, uint8_t a, uint8_t* length) if ((buf_ptr_length==0) && (LAST_DATA[5]>0x20) && (LAST_DATA[4]==0x2)) return(1); break; + default: + break; } return(0); } @@ -963,6 +969,8 @@ uint8_t riello_tail(uint8_t type, uint8_t length) if (LAST_DATA[5] == 0x03) return(1); break; + default: + break; } return(0); } @@ -974,6 +982,8 @@ uint8_t riello_test_nak(uint8_t type, uint8_t* buffer) if (buffer[3] == 0x15) return(1); break; + default: + break; } return(0); } diff --git a/drivers/riello_ser.c b/drivers/riello_ser.c index 6748fca83a..f15c785ef5 100644 --- a/drivers/riello_ser.c +++ b/drivers/riello_ser.c @@ -48,10 +48,10 @@ #include "riello.h" #define DRIVER_NAME "Riello serial driver" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.11" -#define DEFAULT_OFFDELAY 5 -#define DEFAULT_BOOTDELAY 5 +#define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */ +#define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */ /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -76,6 +76,15 @@ static unsigned int bootdelay = DEFAULT_BOOTDELAY; static TRielloData DevData; +/* Flag for estimation of battery.runtime and battery.charge */ +static int localcalculation = 0; +static int localcalculation_logged = 0; +/* NOTE: Do not change these default, they refer to battery.voltage.nominal=12.0 + * and used in related maths later */ +static double batt_volt_nom = 12.0; +static double batt_volt_low = 10.4; +static double batt_volt_high = 13.0; + /********************************************************************** * char_read (char *bytes, size_t size, int read_timeout) * @@ -729,6 +738,7 @@ static int start_ups_comm(void) void upsdrv_initinfo(void) { int ret; + const char *valN = NULL, *valL = NULL, *valH = NULL; ret = start_ups_comm(); @@ -739,6 +749,15 @@ void upsdrv_initinfo(void) else upsdebugx(2, "Communication with UPS established"); + if (testvar("localcalculation")) { + localcalculation = 1; + upsdebugx(1, "Will guesstimate battery charge and runtime " + "instead of trusting device readings (if any); " + "consider also setting default.battery.voltage.low " + "and default.battery.voltage.high for this device"); + } + dstate_setinfo("driver.parameter.localcalculation", "%d", localcalculation); + if (typeRielloProtocol == DEV_RIELLOGPSER) riello_parse_gi(&bufIn[0], &DevData); else @@ -770,13 +789,33 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.serial", "%s", (unsigned char*) DevData.Identification); dstate_setinfo("ups.firmware", "%s", (unsigned char*) DevData.Version); + /* Is it set by user default/override configuration? + * NOTE: "valN" is also used for a check just below. + */ + valN = dstate_getinfo("battery.voltage.nominal"); + if (valN) { + batt_volt_nom = strtod(valN, NULL); + upsdebugx(1, "Using battery.voltage.nominal=%.1f " + "likely coming from user configuration", + batt_volt_nom); + } + if (typeRielloProtocol == DEV_RIELLOGPSER) { if (get_ups_nominal() == 0) { dstate_setinfo("ups.realpower.nominal", "%u", DevData.NomPowerKW); dstate_setinfo("ups.power.nominal", "%u", DevData.NomPowerKVA); dstate_setinfo("output.voltage.nominal", "%u", DevData.NominalUout); dstate_setinfo("output.frequency.nominal", "%.1f", DevData.NomFout/10.0); - dstate_setinfo("battery.voltage.nominal", "%u", DevData.NomUbat); + + /* Is it set by user default/override configuration (see just above)? */ + if (valN) { + upsdebugx(1, "...instead of battery.voltage.nominal=%u " + "reported by the device", DevData.NomUbat); + } else { + dstate_setinfo("battery.voltage.nominal", "%u", DevData.NomUbat); + batt_volt_nom = (double)DevData.NomUbat; + } + dstate_setinfo("battery.capacity", "%u", DevData.NomBatCap); } } @@ -786,11 +825,94 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.power.nominal", "%u", DevData.NomPowerKVA); dstate_setinfo("output.voltage.nominal", "%u", DevData.NominalUout); dstate_setinfo("output.frequency.nominal", "%.1f", DevData.NomFout/10.0); - dstate_setinfo("battery.voltage.nominal", "%u", DevData.NomUbat); + + /* Is it set by user default/override configuration (see just above)? */ + if (valN) { + upsdebugx(1, "...instead of battery.voltage.nominal=%u " + "reported by the device", DevData.NomUbat); + } else { + dstate_setinfo("battery.voltage.nominal", "%u", DevData.NomUbat); + batt_volt_nom = (double)DevData.NomUbat; + } + dstate_setinfo("battery.capacity", "%u", DevData.NomBatCap); + } else { + /* TOTHINK: Check the momentary reading of battery.voltage + * or would it be too confusing (especially if it is above + * 12V and might correspond to a discharged UPS when the + * driver starts up after an outage?) + * NOTE: DevData.Ubat would be scaled by 10! + */ + if (!valN) { + /* The nominal was not already set by user configuration... */ + upsdebugx(1, "Using built-in default battery.voltage.nominal=%.1f", + batt_volt_nom); + dstate_setinfo("battery.voltage.nominal", "%.1f", batt_volt_nom); + } + } + } + + /* We have a nominal voltage by now - either from user configuration + * or from the device itself (or initial defaults for 12V). Do we have + * any low/high range from HW/FW or defaults from ups.conf? */ + valL = dstate_getinfo("battery.voltage.low"); + valH = dstate_getinfo("battery.voltage.high"); + + { /* scoping */ + /* Pick a suitable low/high range (or keep built-in default). + * The factor may be a count of battery packs in the UPS. + */ + int times12 = batt_volt_nom / 12; + if (times12 > 1) { + /* Scale up the range for 24V (X=2) etc. */ + upsdebugx(3, "%s: Using %i times the voltage range of 12V PbAc battery", + __func__, times12); + batt_volt_low *= times12; + batt_volt_high *= times12; + } + } + + if (!valL && !valH) { + /* Both not set (NULL) => pick by nominal (X times 12V above). */ + upsdebugx(3, "Neither battery.voltage.low=%.1f " + "nor battery.voltage.high=%.1f is set via " + "driver configuration or by device; keeping " + "at built-in default value (aligned " + "with battery.voltage.nominal=%.1f)", + batt_volt_low, batt_volt_high, batt_volt_nom); + } else { + if (valL) { + batt_volt_low = strtod(valL, NULL); + upsdebugx(2, "%s: Using battery.voltage.low=%.1f from device or settings", + __func__, batt_volt_low); + } + + if (valH) { + batt_volt_high = strtod(valH, NULL); + upsdebugx(2, "%s: Using battery.voltage.high=%.1f from device or settings", + __func__, batt_volt_high); + } + + /* If just one of those is set, then what? */ + if (valL || valH) { + upsdebugx(1, "WARNING: Only one of battery.voltage.low=%.1f " + "or battery.voltage.high=%.1f is set via " + "driver configuration; keeping the other " + "at built-in default value (aligned " + "with battery.voltage.nominal=%.1f)", + batt_volt_low, batt_volt_high, batt_volt_nom); + } else { + upsdebugx(1, "Both of battery.voltage.low=%.1f " + "or battery.voltage.high=%.1f are set via " + "driver configuration; not aligning " + "with battery.voltage.nominal=%.1f", + batt_volt_low, batt_volt_high, batt_volt_nom); } } + /* Whatever the origin, make the values known via dstate */ + dstate_setinfo("battery.voltage.low", "%.1f", batt_volt_low); + dstate_setinfo("battery.voltage.high", "%.1f", batt_volt_high); /* commands ----------------------------------------------- */ dstate_addcmd("load.off"); @@ -822,6 +944,12 @@ void upsdrv_updateinfo(void) uint8_t getextendedOK; static int countlost = 0; int stat; + int battcharge; + float battruntime; + float upsloadfactor; +#ifdef RIELLO_DYNAMIC_BATTVOLT_INFO + const char *val = NULL; +#endif upsdebugx(1, "countlost %d",countlost); @@ -834,10 +962,13 @@ void upsdrv_updateinfo(void) } } - if (typeRielloProtocol == DEV_RIELLOGPSER) + if (typeRielloProtocol == DEV_RIELLOGPSER) { stat = get_ups_status(); - else + upsdebugx(1, "get_ups_status() %d", stat ); + } else { stat = get_ups_sentr(); + upsdebugx(1, "get_ups_sentr() %d", stat ); + } if (stat < 0) { if (countlost < COUNTLOST) @@ -862,13 +993,75 @@ void upsdrv_updateinfo(void) dstate_setinfo("output.frequency", "%.2f", DevData.Fout/10.0); dstate_setinfo("battery.voltage", "%.1f", DevData.Ubat/10.0); - if ((DevData.BatCap < 0xFFFF) && (DevData.BatTime < 0xFFFF)) { - dstate_setinfo("battery.charge", "%u", DevData.BatCap); - dstate_setinfo("battery.runtime", "%u", DevData.BatTime*60); +#ifdef RIELLO_DYNAMIC_BATTVOLT_INFO + /* Can be set via default.* or override.* driver options + * if not served by the device HW/FW */ + val = dstate_getinfo("battery.voltage.low"); + if (val) { + batt_volt_low = strtod(val, NULL); + } + + val = dstate_getinfo("battery.voltage.high"); + if (val) { + batt_volt_high = strtod(val, NULL); } +#endif + + if (localcalculation) { + /* NOTE: at this time "localcalculation" is a configuration toggle. + * Maybe later it can be replaced by a common "runtimecal" setting. */ + /* Considered "Ubat" physical range here (e.g. 10.7V to 12.9V) is + * seen as "107" or "129" integers in the DevData properties: */ + uint16_t Ubat_low = batt_volt_low * 10; /* e.g. 107 */ + uint16_t Ubat_high = batt_volt_high * 10; /* e.g. 129 */ + static int batt_volt_logged = 0; + + if (!batt_volt_logged) { + upsdebugx(0, "\nUsing battery.voltage.low=%.1f and " + "battery.voltage.high=%.1f for \"localcalculation\" " + "guesstimates of battery.charge and battery.runtime", + batt_volt_low, batt_volt_high); + batt_volt_logged = 1; + } - if (DevData.Tsystem < 0xFF) + battcharge = ((DevData.Ubat <= Ubat_high) && (DevData.Ubat >= Ubat_low)) + ? (((DevData.Ubat - Ubat_low)*100) / (Ubat_high - Ubat_low)) + : ((DevData.Ubat < Ubat_low) ? 0 : 100); + battruntime = (DevData.NomBatCap * DevData.NomUbat * 3600.0/DevData.NomPowerKW) * (battcharge/100.0); + upsloadfactor = (DevData.Pout1 > 0) ? (DevData.Pout1/100.0) : 1; + + dstate_setinfo("battery.charge", "%u", battcharge); + dstate_setinfo("battery.runtime", "%.0f", battruntime/upsloadfactor); + } + else { + if (!localcalculation_logged) { + upsdebugx(0, "\nIf you don't see values for battery.charge and " + "battery.runtime or values are incorrect," + "try setting \"localcalculation\" flag in \"ups.conf\" " + "options section for this driver!\n"); + localcalculation_logged = 1; + } + if ((DevData.BatCap < 0xFFFF) && (DevData.BatTime < 0xFFFF)) { + /* Use values reported by the driver unless they are marked + * invalid/unknown by HW/FW (all bits in the word are set). + */ + dstate_setinfo("battery.charge", "%u", DevData.BatCap); + dstate_setinfo("battery.runtime", "%u", DevData.BatTime*60); + } + } + + if (DevData.Tsystem == 255) { + /* Use values reported by the driver unless they are marked + * invalid/unknown by HW/FW (all bits in the word are set). + */ + /*dstate_setinfo("ups.temperature", "%u", 0);*/ + upsdebugx(4, "Reported temperature value is 0xFF, " + "probably meaning \"-1\" for error or " + "missing sensor - ignored"); + } + else if (DevData.Tsystem < 0xFF) { dstate_setinfo("ups.temperature", "%u", DevData.Tsystem); + } if (input_monophase) { dstate_setinfo("input.voltage", "%u", DevData.Uinp1); @@ -1032,6 +1225,8 @@ void upsdrv_makevartable(void) /* allow '-x foo=' */ /* addvar(VAR_VALUE, "foo", "Override foo setting"); */ + + addvar(VAR_FLAG, "localcalculation", "Calculate battery charge and runtime locally"); } void upsdrv_initups(void) diff --git a/drivers/riello_usb.c b/drivers/riello_usb.c index e965fd7830..fdc7de8246 100644 --- a/drivers/riello_usb.c +++ b/drivers/riello_usb.c @@ -8,6 +8,8 @@ * * Copyright (C) 2012 - Elio Parisi * Copyright (C) 2016 Eaton + * Copyright (C) 2022-2024 "amikot" + * Copyright (C) 2022-2024 Jim Klimov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -34,7 +36,7 @@ #include "riello.h" #define DRIVER_NAME "Riello USB driver" -#define DRIVER_VERSION "0.11" +#define DRIVER_VERSION "0.13" #define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */ #define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */ @@ -68,6 +70,15 @@ static USBDevice_t usbdevice; static USBDeviceMatcher_t *reopen_matcher = NULL; static USBDeviceMatcher_t *regex_matcher = NULL; +/* Flag for estimation of battery.runtime and battery.charge */ +static int localcalculation = 0; +static int localcalculation_logged = 0; +/* NOTE: Do not change these default, they refer to battery.voltage.nominal=12.0 + * and used in related maths later */ +static double batt_volt_nom = 12.0; +static double batt_volt_low = 10.4; +static double batt_volt_high = 13.0; + static int (*subdriver_command)(uint8_t *cmd, uint8_t *buf, uint16_t length, uint16_t buflen) = NULL; static void ussleep(useconds_t usec) @@ -838,6 +849,8 @@ void upsdrv_makevartable(void) { /* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */ nut_usb_addvars(); + + addvar(VAR_FLAG, "localcalculation", "Calculate battery charge and runtime locally"); } void upsdrv_initups(void) @@ -940,6 +953,7 @@ void upsdrv_initups(void) void upsdrv_initinfo(void) { int ret; + const char *valN = NULL, *valL = NULL, *valH = NULL; ret = start_ups_comm(); @@ -950,6 +964,15 @@ void upsdrv_initinfo(void) else upsdebugx(2, "Communication with UPS established"); + if (testvar("localcalculation")) { + localcalculation = 1; + upsdebugx(1, "Will guesstimate battery charge and runtime " + "instead of trusting device readings (if any); " + "consider also setting default.battery.voltage.low " + "and default.battery.voltage.high for this device"); + } + dstate_setinfo("driver.parameter.localcalculation", "%d", localcalculation); + riello_parse_gi(&bufIn[0], &DevData); gpser_error_control = DevData.Identif_bytes[4]-0x30; @@ -978,15 +1001,110 @@ void upsdrv_initinfo(void) dstate_setinfo("ups.serial", "%s", (unsigned char*) DevData.Identification); dstate_setinfo("ups.firmware", "%s", (unsigned char*) DevData.Version); + /* Is it set by user default/override configuration? + * NOTE: "valN" is also used for a check just below. + */ + valN = dstate_getinfo("battery.voltage.nominal"); + if (valN) { + batt_volt_nom = strtod(valN, NULL); + upsdebugx(1, "Using battery.voltage.nominal=%.1f " + "likely coming from user configuration", + batt_volt_nom); + } + if (get_ups_nominal() == 0) { dstate_setinfo("ups.realpower.nominal", "%u", DevData.NomPowerKW); dstate_setinfo("ups.power.nominal", "%u", DevData.NomPowerKVA); dstate_setinfo("output.voltage.nominal", "%u", DevData.NominalUout); dstate_setinfo("output.frequency.nominal", "%.1f", DevData.NomFout/10.0); - dstate_setinfo("battery.voltage.nominal", "%u", DevData.NomUbat); + + /* Is it set by user default/override configuration (see just above)? */ + if (valN) { + upsdebugx(1, "...instead of battery.voltage.nominal=%u " + "reported by the device", DevData.NomUbat); + } else { + dstate_setinfo("battery.voltage.nominal", "%u", DevData.NomUbat); + batt_volt_nom = (double)DevData.NomUbat; + } + dstate_setinfo("battery.capacity", "%u", DevData.NomBatCap); + } else { + /* TOTHINK: Check the momentary reading of battery.voltage + * or would it be too confusing (especially if it is above + * 12V and might correspond to a discharged UPS when the + * driver starts up after an outage?) + * NOTE: DevData.Ubat would be scaled by 10! + */ + if (!valN) { + /* The nominal was not already set by user configuration... */ + upsdebugx(1, "Using built-in default battery.voltage.nominal=%.1f", + batt_volt_nom); + dstate_setinfo("battery.voltage.nominal", "%.1f", batt_volt_nom); + } + } + + /* We have a nominal voltage by now - either from user configuration + * or from the device itself (or initial defaults for 12V). Do we have + * any low/high range from HW/FW or defaults from ups.conf? */ + valL = dstate_getinfo("battery.voltage.low"); + valH = dstate_getinfo("battery.voltage.high"); + + { /* scoping */ + /* Pick a suitable low/high range (or keep built-in default). + * The factor may be a count of battery packs in the UPS. + */ + int times12 = batt_volt_nom / 12; + if (times12 > 1) { + /* Scale up the range for 24V (X=2) etc. */ + upsdebugx(3, "%s: Using %i times the voltage range of 12V PbAc battery", + __func__, times12); + batt_volt_low *= times12; + batt_volt_high *= times12; + } } + if (!valL && !valH) { + /* Both not set (NULL) => pick by nominal (X times 12V above). */ + upsdebugx(3, "Neither battery.voltage.low=%.1f " + "nor battery.voltage.high=%.1f is set via " + "driver configuration or by device; keeping " + "at built-in default value (aligned " + "with battery.voltage.nominal=%.1f)", + batt_volt_low, batt_volt_high, batt_volt_nom); + } else { + if (valL) { + batt_volt_low = strtod(valL, NULL); + upsdebugx(2, "%s: Using battery.voltage.low=%.1f from device or settings", + __func__, batt_volt_low); + } + + if (valH) { + batt_volt_high = strtod(valH, NULL); + upsdebugx(2, "%s: Using battery.voltage.high=%.1f from device or settings", + __func__, batt_volt_high); + } + + /* If just one of those is set, then what? */ + if (valL || valH) { + upsdebugx(1, "WARNING: Only one of battery.voltage.low=%.1f " + "or battery.voltage.high=%.1f is set via " + "driver configuration; keeping the other " + "at built-in default value (aligned " + "with battery.voltage.nominal=%.1f)", + batt_volt_low, batt_volt_high, batt_volt_nom); + } else { + upsdebugx(1, "Both of battery.voltage.low=%.1f " + "or battery.voltage.high=%.1f are set via " + "driver configuration; not aligning " + "with battery.voltage.nominal=%.1f", + batt_volt_low, batt_volt_high, batt_volt_nom); + } + } + + /* Whatever the origin, make the values known via dstate */ + dstate_setinfo("battery.voltage.low", "%.1f", batt_volt_low); + dstate_setinfo("battery.voltage.high", "%.1f", batt_volt_high); + /* commands ----------------------------------------------- */ dstate_addcmd("load.off"); dstate_addcmd("load.on"); @@ -1056,6 +1174,12 @@ void upsdrv_updateinfo(void) uint8_t getextendedOK; static int countlost = 0; int stat; + int battcharge; + float battruntime; + float upsloadfactor; +#ifdef RIELLO_DYNAMIC_BATTVOLT_INFO + const char *val = NULL; +#endif upsdebugx(1, "countlost %d",countlost); @@ -1090,14 +1214,76 @@ void upsdrv_updateinfo(void) dstate_setinfo("input.bypass.frequency", "%.2f", DevData.Fbypass/10.0); dstate_setinfo("output.frequency", "%.2f", DevData.Fout/10.0); dstate_setinfo("battery.voltage", "%.1f", DevData.Ubat/10.0); - if ((DevData.BatCap < 0xFFFF) && (DevData.BatTime < 0xFFFF)) { - dstate_setinfo("battery.charge", "%u", DevData.BatCap); - dstate_setinfo("battery.runtime", "%u", DevData.BatTime*60); + +#ifdef RIELLO_DYNAMIC_BATTVOLT_INFO + /* Can be set via default.* or override.* driver options + * if not served by the device HW/FW */ + val = dstate_getinfo("battery.voltage.low"); + if (val) { + batt_volt_low = strtod(val, NULL); } - if (DevData.Tsystem < 0xFF) - dstate_setinfo("ups.temperature", "%u", DevData.Tsystem); + val = dstate_getinfo("battery.voltage.high"); + if (val) { + batt_volt_high = strtod(val, NULL); + } +#endif + + if (localcalculation) { + /* NOTE: at this time "localcalculation" is a configuration toggle. + * Maybe later it can be replaced by a common "runtimecal" setting. */ + /* Considered "Ubat" physical range here (e.g. 10.7V to 12.9V) is + * seen as "107" or "129" integers in the DevData properties: */ + uint16_t Ubat_low = batt_volt_low * 10; /* e.g. 107 */ + uint16_t Ubat_high = batt_volt_high * 10; /* e.g. 129 */ + static int batt_volt_logged = 0; + + if (!batt_volt_logged) { + upsdebugx(0, "\nUsing battery.voltage.low=%.1f and " + "battery.voltage.high=%.1f for \"localcalculation\" " + "guesstimates of battery.charge and battery.runtime", + batt_volt_low, batt_volt_high); + batt_volt_logged = 1; + } + + battcharge = ((DevData.Ubat <= Ubat_high) && (DevData.Ubat >= Ubat_low)) + ? (((DevData.Ubat - Ubat_low)*100) / (Ubat_high - Ubat_low)) + : ((DevData.Ubat < Ubat_low) ? 0 : 100); + battruntime = (DevData.NomBatCap * DevData.NomUbat * 3600.0/DevData.NomPowerKW) * (battcharge/100.0); + upsloadfactor = (DevData.Pout1 > 0) ? (DevData.Pout1/100.0) : 1; + dstate_setinfo("battery.charge", "%u", battcharge); + dstate_setinfo("battery.runtime", "%.0f", battruntime/upsloadfactor); + } + else { + if (!localcalculation_logged) { + upsdebugx(0, "\nIf you don't see values for battery.charge and " + "battery.runtime or values are incorrect," + "try setting \"localcalculation\" flag in \"ups.conf\" " + "options section for this driver!\n"); + localcalculation_logged = 1; + } + if ((DevData.BatCap < 0xFFFF) && (DevData.BatTime < 0xFFFF)) { + /* Use values reported by the driver unless they are marked + * invalid/unknown by HW/FW (all bits in the word are set). + */ + dstate_setinfo("battery.charge", "%u", DevData.BatCap); + dstate_setinfo("battery.runtime", "%u", DevData.BatTime*60); + } + } + + if (DevData.Tsystem == 255) { + /* Use values reported by the driver unless they are marked + * invalid/unknown by HW/FW (all bits in the word are set). + */ + /*dstate_setinfo("ups.temperature", "%u", 0);*/ + upsdebugx(4, "Reported temperature value is 0xFF, " + "probably meaning \"-1\" for error or " + "missing sensor - ignored"); + } + else if (DevData.Tsystem < 0xFF) { + dstate_setinfo("ups.temperature", "%u", DevData.Tsystem); + } if (input_monophase) { dstate_setinfo("input.voltage", "%u", DevData.Uinp1); diff --git a/drivers/safenet.c b/drivers/safenet.c index 68365463fa..351187419e 100644 --- a/drivers/safenet.c +++ b/drivers/safenet.c @@ -41,7 +41,7 @@ #include "safenet.h" #define DRIVER_NAME "Generic SafeNet UPS driver" -#define DRIVER_VERSION "1.80" +#define DRIVER_VERSION "1.81" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/serial.h b/drivers/serial.h index 7987cb385d..cd8aa0fbe4 100644 --- a/drivers/serial.h +++ b/drivers/serial.h @@ -1,7 +1,13 @@ #ifndef SERIAL_H_SEEN #define SERIAL_H_SEEN 1 -#include "config.h" /* should be first */ +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif #include "attribute.h" diff --git a/drivers/skel.c b/drivers/skel.c index b5033672bb..9b45023832 100644 --- a/drivers/skel.c +++ b/drivers/skel.c @@ -22,7 +22,7 @@ /* #define IGNCHARS "" */ #define DRIVER_NAME "Skeleton UPS driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.05" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/sms_ser.c b/drivers/sms_ser.c index 02100634c9..7b0f840896 100644 --- a/drivers/sms_ser.c +++ b/drivers/sms_ser.c @@ -31,7 +31,7 @@ #define ENDCHAR '\r' #define DRIVER_NAME "SMS Brazil UPS driver" -#define DRIVER_VERSION "1.00" +#define DRIVER_VERSION "1.01" #define QUERY_SIZE 7 #define BUFFER_SIZE 18 diff --git a/drivers/snmp-ups.c b/drivers/snmp-ups.c index 9e7e04ca3a..d6e2f6a89b 100644 --- a/drivers/snmp-ups.c +++ b/drivers/snmp-ups.c @@ -174,7 +174,7 @@ static const char *mibname; static const char *mibvers; #define DRIVER_NAME "Generic SNMP UPS driver" -#define DRIVER_VERSION "1.30" +#define DRIVER_VERSION "1.31" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -259,8 +259,10 @@ void upsdrv_initinfo(void) && !(su_info_p->flags & SU_OUTLET_GROUP)) { /* first check that this OID actually exists */ + /* FIXME: daisychain commands support! */ su_addcmd(su_info_p); + /* if (nut_snmp_get(su_info_p->OID) != NULL) { dstate_addcmd(su_info_p->info_type); @@ -610,6 +612,7 @@ void upsdrv_initups(void) } printf("\nOverall this driver has loaded %d MIB-to-NUT mapping tables\n", i); exit(EXIT_SUCCESS); + /* fatalx(EXIT_FAILURE, "Marking the exit code as failure since the driver is not started now"); */ } /* init SNMP library, etc... */ @@ -1144,7 +1147,6 @@ static struct snmp_pdu **nut_snmp_walk(const char *OID, int max_iteration) break; } - if (!((status == STAT_SUCCESS) && (response->errstat == SNMP_ERR_NOERROR))) { if (mibname == NULL) { /* We are probing for proper mib - ignore errors */ @@ -2921,8 +2923,9 @@ bool_t daisychain_init(void) if (devices_count == -1) { #endif /* WITH_SNMP_LKP_FUN */ - if (nut_snmp_get_int(su_info_p->OID, &devices_count) == TRUE) + if (nut_snmp_get_int(su_info_p->OID, &devices_count) == TRUE) { upsdebugx(1, "There are %ld device(s) present", devices_count); + } else { upsdebugx(1, "Error: can't get the number of device(s) present!"); @@ -3312,7 +3315,7 @@ bool_t snmp_ups_walk(int mode) #ifdef COUNT_ITERATIONS /* check stale elements only on each PN_STALE_RETRY iteration. */ - if ((su_info_p->flags & SU_FLAG_STALE) && + if ((su_info_p->flags & SU_FLAG_STALE) && (iterations % SU_STALE_RETRY) != 0) continue; #endif diff --git a/drivers/snmp-ups.h b/drivers/snmp-ups.h index 83a3a25b98..c069d7bb3d 100644 --- a/drivers/snmp-ups.h +++ b/drivers/snmp-ups.h @@ -52,6 +52,14 @@ #ifndef SNMP_UPS_H #define SNMP_UPS_H +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif + #include "nut_stdint.h" /* uint32_t */ /* FIXME: still needed? @@ -76,6 +84,10 @@ #undef PACKAGE_TARNAME #endif +#ifdef PACKAGE_URL +#undef PACKAGE_URL +#endif + #ifdef HAVE_DMALLOC_H #undef HAVE_DMALLOC_H #endif @@ -89,9 +101,27 @@ # endif #endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNUSED_PARAMETER) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wunused-parameter" +#endif +#ifdef __clang__ +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wunused-parameter" +#endif +/* These tend to have inlined API implementations as empty braces, + * causing warnings about unused parameters. + */ #include #include +#ifdef __clang__ +# pragma clang diagnostic pop +#endif +#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNUSED_PARAMETER) +# pragma GCC diagnostic pop +#endif + #ifndef ONE_SEC /* This macro name disappeared from net-snmp sources and headers * after v5.9 tag, and was replaced by explicit expression below: */ diff --git a/drivers/socomec_jbus.c b/drivers/socomec_jbus.c index dc9e260ce0..76534512be 100644 --- a/drivers/socomec_jbus.c +++ b/drivers/socomec_jbus.c @@ -31,7 +31,7 @@ #include #define DRIVER_NAME "Socomec jbus driver" -#define DRIVER_VERSION "0.07" +#define DRIVER_VERSION "0.08" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 1 diff --git a/drivers/solis.c b/drivers/solis.c index ee0f0a456c..11120580bb 100644 --- a/drivers/solis.c +++ b/drivers/solis.c @@ -48,7 +48,7 @@ #include "timehead.h" #define DRIVER_NAME "Microsol Solis UPS driver" -#define DRIVER_VERSION "0.69" +#define DRIVER_VERSION "0.70" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -799,8 +799,10 @@ static void get_base_info(void) { Model = "Solis 3.0"; break; case 16: - Model = "Microsol Back-Ups BZ1200-BR"; - break; + Model = "Microsol Back-Ups BZ1200-BR"; + break; + default: + break; } /* if( isprogram ) */ diff --git a/drivers/tripplite.c b/drivers/tripplite.c index f353c38a7e..cfb4d2230f 100644 --- a/drivers/tripplite.c +++ b/drivers/tripplite.c @@ -113,11 +113,11 @@ #include "serial.h" #include "tripplite.h" #include "nut_stdint.h" -#include +#include "nut_float.h" #include #define DRIVER_NAME "Tripp-Lite SmartUPS driver" -#define DRIVER_VERSION "0.94" +#define DRIVER_VERSION "0.96" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/tripplite_usb.c b/drivers/tripplite_usb.c index 5dbc22f0c0..d441e783a4 100644 --- a/drivers/tripplite_usb.c +++ b/drivers/tripplite_usb.c @@ -132,12 +132,12 @@ #include "main.h" #include "nut_libusb.h" -#include +#include "nut_float.h" #include #include "usb-common.h" #define DRIVER_NAME "Tripp Lite OMNIVS / SMARTPRO driver" -#define DRIVER_VERSION "0.35" +#define DRIVER_VERSION "0.38" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1493,6 +1493,8 @@ void upsdrv_updateinfo(void) case '0': dstate_setinfo("input.frequency.nominal", "%d", 50); break; + default: + break; } } diff --git a/drivers/tripplitesu.c b/drivers/tripplitesu.c index 82d98eee88..fb6f42076a 100644 --- a/drivers/tripplitesu.c +++ b/drivers/tripplitesu.c @@ -126,7 +126,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "Tripp Lite SmartOnline driver" -#define DRIVER_VERSION "0.07" +#define DRIVER_VERSION "0.08" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/upscode2.c b/drivers/upscode2.c index 5dac03bf20..ee88ba74f8 100644 --- a/drivers/upscode2.c +++ b/drivers/upscode2.c @@ -43,7 +43,7 @@ #include "nut_float.h" #define DRIVER_NAME "UPScode II UPS driver" -#define DRIVER_VERSION "0.91" +#define DRIVER_VERSION "0.92" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/upsdrvctl.c b/drivers/upsdrvctl.c index 1c8b6fd52c..e0662e3088 100644 --- a/drivers/upsdrvctl.c +++ b/drivers/upsdrvctl.c @@ -1,6 +1,9 @@ /* upsdrvctl.c - UPS driver controller - Copyright (C) 2001 Russell Kroll + Copyright (C) + 2001 Russell Kroll + 2005 - 2017 Arnaud Quette + 2017 - 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -188,37 +191,56 @@ static void signal_driver_cmd(const ups_t *ups, ) { #ifndef WIN32 +/* TODO: implement WIN32: https://github.com/networkupstools/nut/issues/1916 + * Currently the codepath is not implemented below + */ char pidfn[SMALLBUF]; #endif int ret; #ifndef WIN32 - if (cmd == SIGCMD_RELOAD_OR_ERROR) + if (cmd == SIGCMD_RELOAD_OR_ERROR || cmd == SIGCMD_EXIT) #else - if (cmd && !strcmp(cmd, SIGCMD_RELOAD_OR_ERROR)) + if (cmd && (!strcmp(cmd, SIGCMD_RELOAD_OR_ERROR) || !strcmp(cmd, SIGCMD_EXIT))) #endif { /* not a signal, use socket protocol */ - char buf[LARGEBUF]; + char buf[LARGEBUF], cmdbuf[LARGEBUF]; struct timeval tv; + char *cmdname = NULL; - upsdebugx(1, "Signalling UPS [%s]: %s", - ups->upsname, "driver.reload-or-error"); +#ifndef WIN32 + if (cmd == SIGCMD_RELOAD_OR_ERROR) +#else + if (!strcmp(cmd, SIGCMD_RELOAD_OR_ERROR)) +#endif + cmdname = "reload-or-error"; + else +#ifndef WIN32 + if (cmd == SIGCMD_EXIT) +#else + if (!strcmp(cmd, SIGCMD_EXIT)) +#endif + cmdname = "exit"; + + upsdebugx(1, "Signalling UPS [%s]: driver.%s", + ups->upsname, NUT_STRARG(cmdname)); - if (testmode) + if (testmode || !cmdname) return; /* Post the query and wait for reply */ /* FIXME: coordinate with pollfreq? */ tv.tv_sec = 15; tv.tv_usec = 0; + snprintf(cmdbuf, sizeof(cmdbuf), "INSTCMD driver.%s\n", cmdname); ret = upsdrvquery_oneshot(ups->driver, ups->upsname, - "INSTCMD driver.reload-or-error\n", - buf, sizeof(buf), &tv); + cmdbuf, buf, sizeof(buf), &tv); if (ret < 0) { goto socket_error; } else { - upslogx(LOG_INFO, "Request to reload-or-error returned code %d", ret); + upslogx(LOG_INFO, "Request for driver to %s returned code %d", + cmdname, ret); if (ret != STAT_INSTCMD_HANDLED) exec_error++; /* TODO: Propagate "ret" to caller, eventually CLI exit-code? */ @@ -233,7 +255,7 @@ static void signal_driver_cmd(const ups_t *ups, } #ifndef WIN32 -/* TODO: implement WIN32 */ +/* TODO: implement WIN32: https://github.com/networkupstools/nut/issues/1916 */ /* handle generally signalling the UPS */ /* Real signals */ #ifndef WIN32 @@ -281,11 +303,13 @@ static void signal_driver_cmd(const ups_t *ups, if (testmode) return; + /* Hush the fopen(pidfile) message but let "real errors" be seen */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_KILL_SIG0PING - 1; #ifndef WIN32 if (ups->pid == -1) { - ret = sendsignalfn(pidfn, cmd); + ret = sendsignalfn(pidfn, cmd, ups->driver, 0); } else { - ret = sendsignalpid(ups->pid, cmd); + ret = sendsignalpid(ups->pid, cmd, ups->driver, 0); /* reap zombie if this child died */ if (waitpid(ups->pid, NULL, WNOHANG) == ups->pid) { upslog_with_errno(LOG_WARNING, @@ -294,14 +318,16 @@ static void signal_driver_cmd(const ups_t *ups, } } #else - ret = sendsignal(pidfn, cmd); + ret = sendsignal(pidfn, cmd, 0); #endif + /* Restore the signal errors verbosity */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT; if (ret < 0) { upslog_with_errno(LOG_ERR, "Signalling %s failed: %d", pidfn, ret); exec_error++; } -#endif /* WIN32 */ +#endif /* WIN32: https://github.com/networkupstools/nut/issues/1916 */ } /* handle generally signalling the UPS with recently raised signal */ @@ -353,60 +379,63 @@ static void stop_driver(const ups_t *ups) if (testmode) return; + /* Hush the fopen(pidfile) message but let "real errors" be seen */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_KILL_SIG0PING - 1; + #ifndef WIN32 if (ups->pid == -1) { - ret = sendsignalfn(pidfn, SIGTERM); + ret = sendsignalfn(pidfn, SIGTERM, ups->driver, 0); } else { - ret = sendsignalpid(ups->pid, SIGTERM); + ret = sendsignalpid(ups->pid, SIGTERM, ups->driver, 0); /* reap zombie if this child died */ if (waitpid(ups->pid, NULL, WNOHANG) == ups->pid) { - return; + goto clean_return; } } #else - ret = sendsignal(pidfn, COMMAND_STOP); + ret = sendsignal(pidfn, COMMAND_STOP, 0); #endif if (ret < 0) { #ifndef WIN32 upsdebugx(2, "SIGTERM to %s failed, retrying with SIGKILL", pidfn); if (ups->pid == -1) { - ret = sendsignalfn(pidfn, SIGKILL); + ret = sendsignalfn(pidfn, SIGKILL, ups->driver, 0); } else { - ret = sendsignalpid(ups->pid, SIGKILL); + ret = sendsignalpid(ups->pid, SIGKILL, ups->driver, 0); /* reap zombie if this child died */ if (waitpid(ups->pid, NULL, WNOHANG) == ups->pid) { - return; + goto clean_return; } } #else upsdebugx(2, "Stopping %s failed, retrying again", pidfn); - ret = sendsignal(pidfn, COMMAND_STOP); + ret = sendsignal(pidfn, COMMAND_STOP, 0); #endif if (ret < 0) { upslog_with_errno(LOG_ERR, "Stopping %s failed", pidfn); exec_error++; - return; + goto clean_return; } } for (i = 0; i < 5 ; i++) { #ifndef WIN32 if (ups->pid == -1) { - ret = sendsignalfn(pidfn, 0); + ret = sendsignalfn(pidfn, 0, ups->driver, 0); } else { /* reap zombie if this child died */ if (waitpid(ups->pid, NULL, WNOHANG) == ups->pid) { - return; + goto clean_return; } - ret = sendsignalpid(ups->pid, 0); + ret = sendsignalpid(ups->pid, 0, ups->driver, 0); } #else - ret = sendsignalfn(pidfn, 0); + ret = sendsignalfn(pidfn, 0, ups->driver, 0); #endif if (ret != 0) { upsdebugx(2, "Sending signal to %s failed, driver is finally down or wrongly owned", pidfn); - return; + goto clean_return; } sleep(1); } @@ -414,32 +443,32 @@ static void stop_driver(const ups_t *ups) #ifndef WIN32 upslog_with_errno(LOG_ERR, "Stopping %s failed, retrying harder", pidfn); if (ups->pid == -1) { - ret = sendsignalfn(pidfn, SIGKILL); + ret = sendsignalfn(pidfn, SIGKILL, ups->driver, 0); } else { /* reap zombie if this child died */ if (waitpid(ups->pid, NULL, WNOHANG) == ups->pid) { - return; + goto clean_return; } - ret = sendsignalpid(ups->pid, SIGKILL); + ret = sendsignalpid(ups->pid, SIGKILL, ups->driver, 0); } #else upslog_with_errno(LOG_ERR, "Stopping %s failed, retrying again", pidfn); - ret = sendsignal(pidfn, COMMAND_STOP); + ret = sendsignal(pidfn, COMMAND_STOP, 0); #endif if (ret == 0) { for (i = 0; i < 5 ; i++) { #ifndef WIN32 if (ups->pid == -1) { - ret = sendsignalfn(pidfn, 0); + ret = sendsignalfn(pidfn, 0, ups->driver, 0); } else { /* reap zombie if this child died */ if (waitpid(ups->pid, NULL, WNOHANG) == ups->pid) { - return; + goto clean_return; } - ret = sendsignalpid(ups->pid, 0); + ret = sendsignalpid(ups->pid, 0, ups->driver, 0); } #else - ret = sendsignalfn(pidfn, 0); + ret = sendsignalfn(pidfn, 0, ups->driver, 0); #endif if (ret != 0) { upsdebugx(2, "Sending signal to %s failed, driver is finally down or wrongly owned", pidfn); @@ -449,7 +478,7 @@ static void stop_driver(const ups_t *ups) if (ups->pid == -1) { unlink(pidfn); } - return; + goto clean_return; } sleep(1); } @@ -457,6 +486,10 @@ static void stop_driver(const ups_t *ups) upslog_with_errno(LOG_ERR, "Stopping %s failed", pidfn); exec_error++; + +clean_return: + /* Restore the signal errors verbosity */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT; } void set_exit_flag(const int sig) @@ -492,7 +525,7 @@ static void reset_signal_flag(void) } #ifndef WIN32 -/* TODO: Equivalent for WIN32 - see SIGCMD_RELOAD in upd and upsmon */ +/* TODO: Equivalent for WIN32 - see SIGCMD_RELOAD in upsd and upsmon */ static void set_reload_flag(const #ifndef WIN32 int @@ -516,6 +549,10 @@ static void set_reload_flag(const break; # endif + case SIGCMD_EXIT: /* Not even a signal, but a socket protocol action */ + reload_flag = 15; + break; + case SIGCMD_RELOAD: /* SIGHUP */ case SIGCMD_RELOAD_OR_ERROR: /* Not even a signal, but a socket protocol action */ default: @@ -768,6 +805,221 @@ static void forkexec(char *const argv[], const ups_t *ups) #endif } +static void list_driver(const ups_t *ups) +{ + /* Just a short report: one config section name per line */ + printf("%s\n", ups->upsname); +} + +static void status_driver(const ups_t *ups) +{ + /* TODO: Options (global static) for details of configuration like + * the driver name, serial, etc. or even current life-cycle status + * (e.g. valid PID existence, data query via socket protocol...) + */ + static int headerShown = 0; +#ifndef WIN32 + char pidfn[SMALLBUF]; + int cmdret = -1; +#endif + char bufPid[LARGEBUF], *pidStrFromSocket = NULL, + bufStatus[LARGEBUF], *statusStrFromSocket = NULL; + int pidAlive = -1, + qretPing = -1, qretPid = -1, qretStatus = -1, + nudl = nut_upsdrvquery_debug_level, + nsdl = nut_sendsignal_debug_level; + pid_t pidFromFile = -1, pidFromSocket = -1; + struct timeval tv; + udq_pipe_conn_t *conn; + + if (!ups) { + upsdebugx(1, "%s: skip due to ups==null", __func__); + return; + } + + /* Hush the fopen(pidfile) message but let "real errors" be seen */ + nut_sendsignal_debug_level = NUT_SENDSIGNAL_DEBUG_LEVEL_KILL_SIG0PING - 1; +#ifndef WIN32 + snprintf(pidfn, sizeof(pidfn), "%s/%s-%s.pid", altpidpath(), ups->driver, ups->upsname); + pidFromFile = parsepidfile(pidfn); + if (pidFromFile >= 0) { /* this method actively reports errors, if any */ + cmdret = sendsignalpid(pidFromFile, 0, ups->driver, 1); + /* returns zero for a successfully sent signal */ + if (cmdret == 0) + pidAlive = 1; + } + upsdebugx(4, "%s: pidfn=%s pidFromFile=%" PRIiMAX " cmdret=%d pidAlive=%d", + __func__, pidfn, (intmax_t)pidFromFile, cmdret, pidAlive); +#else +/* // FIXME: We actually have no probing signals over pipe so far, + // and sending 0 (NULL here) is proven unsafe + // Instead we will try below with PID learned from pipe + snprintf(pidfn, sizeof(pidfn), "%s-%s", ups->driver, ups->upsname); + cmdret = sendsignal(pidfn, COMMAND_RELOAD, 1); + upsdebugx(4, "%s: pipe pidfn=%s cmdret=%d", __func__, pidfn, cmdret); + */ +#endif + + /* Hush the fopen(socketfile) in upsdrvquery_connect_drvname_upsname() */ + nut_upsdrvquery_debug_level = 0; + conn = upsdrvquery_connect_drvname_upsname(ups->driver, ups->upsname); + nut_upsdrvquery_debug_level = nudl; + + if (conn && VALID_FD(conn->sockfd)) { + upsdebugx(3, "%s: connected", __func__); + /* Post the query and wait for reply */ + /* FIXME: coordinate with pollfreq? */ + tv.tv_sec = 3; + tv.tv_usec = 0; + + memset(bufPid, 0, sizeof(bufPid)); + + /* Involves a PING/PONG check, and more; + * returns -1 on error */ + upsdebugx(3, "%s: upsdrvquery_prepare", __func__); + qretPing = upsdrvquery_prepare(conn, tv); + + if (qretPing >= 0) { + qretPing = STAT_INSTCMD_HANDLED; + + /* No TRACKING in queries below */ + memset(bufPid, 0, sizeof(bufPid)); + + upsdebugx(3, "%s: upsdrvquery_write GETPID", __func__); + if (upsdrvquery_write(conn, "GETPID\n") >= 0 + && (qretPid = upsdrvquery_read_timeout(conn, tv)) >= 1 + && (!strncmp(conn->buf, "PID ", 4)) + ) { + size_t l; + upsdebugx(4, "%s: upsdrvquery_read GETPID", __func__); + snprintf(bufPid, sizeof(bufPid), "%s", conn->buf + 4); + upsdebugx(4, "%s: upsdrvquery_read GETPID 2", __func__); + l = strlen(bufPid); + pidStrFromSocket = bufPid; + if (bufPid[l - 1] == '\n') + bufPid[l - 1] = '\0'; + qretPid = STAT_INSTCMD_HANDLED; + pidFromSocket = parsepid(pidStrFromSocket); + if (errno != 0) + pidFromSocket = -1; + } else { + /* query failed or returned not a PID */ + qretPid = -1; + } + } + + if (qretPid == STAT_INSTCMD_HANDLED) { + memset(bufStatus, 0, sizeof(bufStatus)); +#ifdef WIN32 + /* Allow a new read to happen later */ + conn->newread = 1; +#endif + + upsdebugx(3, "%s: upsdrvquery_write DUMPSTATUS", __func__); + if (upsdrvquery_write(conn, "DUMPSTATUS\n") >= 0 + && (qretStatus = upsdrvquery_read_timeout(conn, tv)) >= 1 + ) { + char *buf; + + upsdebugx(4, "%s: upsdrvquery_read DUMPSTATUS", __func__); + /* save before strtok mangles it */ + snprintf(bufStatus, sizeof(bufStatus), "%s", conn->buf); + + upsdebugx(4, "%s: upsdrvquery_read DUMPSTATUS 2", __func__); + for (buf = strtok(bufStatus, "\n"); buf; buf = strtok(NULL, "\n")) { + if (statusStrFromSocket) { + /* New loop, NUL byte was set if needed */ + break; + } + + if (!strncmp(buf, "SETINFO ups.status ", 19)) { + statusStrFromSocket = buf + 19; + qretStatus = STAT_INSTCMD_HANDLED; + /* continue to loop, to clear '\n' to '\0' */ + } + + if (!strncmp(buf, "DUMPDONE\n", 9)) { + /* New loop, NUL byte was set if needed */ + break; + } + } + } else { + /* query failed or did not return SETINFO ups.status ... */ + qretStatus = -1; + } + } + } else { + upsdebugx(3, "%s: not connected", __func__); + } + + upsdebugx(3, "%s: close socket", __func__); + upsdrvquery_close(conn); + if (conn) { + upsdebugx(4, "%s: free socket", __func__); + free(conn); + conn = NULL; + } + + if (pidFromFile < 0 && pidFromSocket >= 0) { + upsdebugx(3, "%s: PID was not available from a file, but was " + "from Socket Protocol; check if it is alive instead", + __func__); + + pidAlive = checkprocname(pidFromSocket, ups->driver); + upsdebugx(4, "%s: pidFromSocket=%" PRIiMAX " pidAlive=%d", + __func__, (intmax_t)pidFromSocket, pidAlive); + } else if (pidAlive < 0 && pidFromSocket >= 0 && (pidFromFile != pidFromSocket)) { + upsdebugx(3, "%s: PID value was available from a file, but was " + "not found running or valid; another one is available " + "from Socket Protocol; check if it is alive instead", + __func__); + + pidAlive = checkprocname(pidFromSocket, ups->driver); + upsdebugx(4, "%s: pidFromSocket=%" PRIiMAX " pidAlive=%d", + __func__, (intmax_t)pidFromSocket, pidAlive); + } + + /* Complete any cached (error) writes before the next lines, + * more so on WIN32 */ + fflush(stderr); + fflush(stdout); + usleep(1000); + + if (!headerShown) { + printf("%-11s\t%11s\t%s\t%s\t%s\t%s\t%s\n", + "UPSNAME", + "UPSDRV", + "RUNNING", + "PF_PID", + "S_RESPONSIVE", + "S_PID", + "S_STATUS" + ); + headerShown = 1; + } + + upsdebugx(2, "%s: raw values: pidAlive=%d " + "pidFromFile=%" PRIiMAX " pidFromSocket=%" PRIiMAX " " + "qretPing=%d qretPid=%d qretStatus=%d", + __func__, pidAlive, + (intmax_t)(pidFromFile), (intmax_t)(pidFromSocket), + qretPing, qretPid, qretStatus + ); + + printf("%-11s\t%11s\t%s\t%" PRIiMAX "\t%s\t%s\t%s\n", + ups->upsname, ups->driver, + ((pidFromFile < 0 && pidFromSocket < 0) || (pidAlive < 0) + ? "N/A" : (pidAlive > 0 ? "RUNNING" : "STOPPED")), + (intmax_t)(pidFromFile), + ((qretPing == STAT_INSTCMD_HANDLED) ? "RESPONSIVE" : "NOT_RESPONSIVE"), + (pidStrFromSocket ? pidStrFromSocket : "N/A"), + ((qretStatus == STAT_INSTCMD_HANDLED) ? NUT_STRARG(statusStrFromSocket) : "") + ); + fflush(stdout); + + nut_sendsignal_debug_level = nsdl; +} + static void start_driver(const ups_t *ups) { char *argv[10]; @@ -920,8 +1172,11 @@ static void help(const char *progname) static void help(const char *arg_progname) { - printf("Starts and stops UPS drivers via ups.conf.\n\n"); - printf("usage: %s [OPTIONS] (start | stop | shutdown) []\n\n", arg_progname); + print_banner_once(arg_progname, 2); + printf("UPS driver controller: Starts and stops UPS drivers via ups.conf.\n\n"); + + printf("usage: %s [OPTIONS] (start | stop | shutdown | status) []\n\n", arg_progname); + printf("usage: %s [OPTIONS] (list | -l) []\n\n", arg_progname); printf("usage: %s [OPTIONS] -c []\n\n", arg_progname); printf("Common options:\n"); @@ -935,11 +1190,15 @@ static void help(const char *arg_progname) printf(" -FF driver stays foregrounded and still saves the PID file\n"); printf(" -B driver(s) stay backgrounded even if debugging is bumped\n"); - printf("Signalling a running driver:\n"); + printf("\nListing known driver(s):\n"); + printf(" -l | list list all device driver confgurations that can be managed\n"); + printf(" -l | list only try to list the specified device driver confgurations (error if unresolved)\n"); + + printf("\nSignalling a running driver:\n"); printf(" -c send via signal to running driver(s)\n"); printf(" supported commands:\n"); #ifndef WIN32 -/* FIXME: port event loop from upsd/upsmon to allow messaging fellow drivers in WIN32 builds */ +/* FIXME: port event loop from upsd/upsmon to allow messaging fellow drivers in WIN32 builds: https://github.com/networkupstools/nut/issues/1916 */ printf(" - data-dump: if the driver still has STDOUT attached (maybe\n"); printf(" to log), dump its currently collected information there\n"); printf(" - reload: re-read configuration files, ignoring changed\n"); @@ -952,7 +1211,7 @@ static void help(const char *arg_progname) printf(" based on that count, so the caller can decide the fate of\n"); printf(" the currently running driver instance\n"); #ifndef WIN32 -/* FIXME: port event loop from upsd/upsmon to allow messaging fellow drivers in WIN32 builds */ +/* FIXME: port event loop from upsd/upsmon to allow messaging fellow drivers in WIN32 builds: https://github.com/networkupstools/nut/issues/1916 */ # ifdef SIGCMD_RELOAD_OR_RESTART printf(" - reload-or-restart: re-read configuration files (close the\n"); printf(" old driver instance device connection if needed, and have\n"); @@ -962,14 +1221,21 @@ static void help(const char *arg_progname) printf(" driver instance if needed, so an external caller like the\n"); printf(" systemd or SMF frameworks would start another copy)\n"); #endif /* WIN32 */ + printf(" - exit: tell the currently running driver instance to just exit\n"); + printf(" (so an external caller like the new driver instance, or the\n"); + printf(" systemd or SMF frameworks would start another copy)\n"); - printf("Driver life cycle options:\n"); + printf("\nDriver life cycle options:\n"); printf(" start start all UPS drivers in ups.conf\n"); - printf(" start only start driver for UPS \n"); + printf(" start only start driver for UPS \n"); printf(" stop stop all UPS drivers in ups.conf\n"); printf(" stop only stop driver for UPS \n"); printf(" shutdown shutdown all UPS drivers in ups.conf\n"); printf(" shutdown only shutdown UPS \n"); + printf(" status query status for all UPS drivers in ups.conf\n"); + printf(" status only query status for driver for UPS \n"); + printf(" Fields: UPSNAME UPSDRV RUNNING PF_PID S_RESPONSIVE S_PID S_STATUS\n"); + printf(" (PF_* = according to PID file, if any; S_* = via socket protocol)\n"); exit(EXIT_SUCCESS); } @@ -1037,16 +1303,27 @@ static void send_one_driver(void (*command_func)(const ups_t *), const char *arg /* walk UPS table and send command to all UPSes according to sdorder */ static void send_all_drivers(void (*command_func)(const ups_t *)) { - ups_t *ups; + ups_t *ups = upstable; int i; - if (!upstable) + if (!ups) fatalx(EXIT_FAILURE, "Error: no UPS definitions found in ups.conf"); exec_error = 0; exec_timeout = 0; + + if (command_func == &list_driver || command_func == &status_driver) { + while (ups) { + command_func(ups); + ups = ups->next; + } + + fflush(stdout); + return; + } + if (command_func != &shutdown_driver) { - ups = upstable; + /* e.g. start_driver or stop_driver */ /* Only warn when relevant - got more than one device to start */ if (command_func == &start_driver @@ -1080,8 +1357,6 @@ static void send_all_drivers(void (*command_func)(const ups_t *)) /* Orderly processing of shutdowns */ for (i = 0; i <= maxsdorder; i++) { - ups = upstable; - while (ups) { if (ups->sdorder == i) command_func(ups); @@ -1133,13 +1408,15 @@ static void exit_cleanup(void) int main(int argc, char **argv) { int i, lastarg = 0; - char *prog; - - printf("Network UPS Tools - UPS driver controller %s\n", - UPS_VERSION); + char *prog, *command_name = NULL, progdesc[LARGEBUF]; prog = argv[0]; - while ((i = getopt(argc, argv, "+htu:r:DdFBVc:")) != -1) { + + /* Historically special banner*/ + snprintf(progdesc, sizeof(progdesc), "%s - UPS driver controller", xbasename(prog)); + print_banner_once(progdesc, 0); + + while ((i = getopt(argc, argv, "+htu:r:DdFBVc:l")) != -1) { switch(i) { case 'r': pt_root = optarg; @@ -1154,6 +1431,10 @@ int main(int argc, char **argv) break; case 'V': + /* just show the version and optional + * CONFIG_FLAGS banner if available */ + print_banner_once(progdesc, 1); + nut_report_config_flags(); exit(EXIT_SUCCESS); case 'D': @@ -1184,12 +1465,17 @@ int main(int argc, char **argv) "sent with option -%c. Try -h for help.", i); } command = &signal_driver; + command_name = "signal"; if (!strncmp(optarg, "reload-or-error", strlen(optarg))) { signal_flag = SIGCMD_RELOAD_OR_ERROR; } + else + if (!strncmp(optarg, "exit", strlen(optarg))) { + signal_flag = SIGCMD_EXIT; + } #ifndef WIN32 -/* FIXME: port event loop from upsd/upsmon to allow messaging fellow drivers in WIN32 builds */ +/* FIXME: port event loop from upsd/upsmon to allow messaging fellow drivers in WIN32 builds: https://github.com/networkupstools/nut/issues/1916 */ else if (!strncmp(optarg, "dump", strlen(optarg))) { signal_flag = SIGCMD_DATA_DUMP; @@ -1208,7 +1494,7 @@ int main(int argc, char **argv) if (!strncmp(optarg, "reload-or-exit", strlen(optarg))) { signal_flag = SIGCMD_RELOAD_OR_EXIT; } -#endif /* WIN32 */ +#endif /* WIN32: https://github.com/networkupstools/nut/issues/1916 */ /* bad command given */ if (!signal_flag) { @@ -1218,17 +1504,27 @@ int main(int argc, char **argv) pt_cmd = optarg; #ifndef WIN32 - upsdebugx(1, "Will send signal %d (%s) for command '%s' " - "to already-running driver (if any) and exit", - signal_flag, strsignal(signal_flag), optarg); + if (signal_flag > 0) + upsdebugx(1, "Will send signal %d (%s) for command '%s' " + "to already-running driver (if any) and exit", + signal_flag, strsignal(signal_flag), optarg); + else + upsdebugx(1, "Will send request for command '%s' (internal code %d) " + "to already-running driver (if any) and exit", + optarg, signal_flag); #else upsdebugx(1, "Will send request '%s' for command '%s' " "to already-running driver (if any) and exit", signal_flag, optarg); #endif /* WIN32 */ break; + case 'l': + command = &list_driver; + command_name = "list"; + break; case 'h': default: + /* not progdesc, shows details of its own */ help(prog); } } @@ -1259,28 +1555,28 @@ int main(int argc, char **argv) nut_debug_level = 2; } - if (nut_debug_level_passthrough == 0) { - upsdebugx(2, "\n" - "If you're not a NUT core developer, chances are that you're told to enable debugging\n" - "to see why a driver isn't working for you. We're sorry for the confusion, but this is\n" - "the 'upsdrvctl' wrapper, not the driver you're interested in.\n\n" - "Below you'll find one or more lines starting with 'exec:' followed by an absolute\n" - "path to the driver binary and some command line option. This is what the driver\n" - "starts and you need to copy and paste that line and append the debug flags to that\n" - "line (less the 'exec:' prefix).\n\n" - "Alternately, provide an additional '-d' (lower-case) parameter to 'upsdrvctl' to\n" - "pass its current debug level to the launched driver, and '-B' keeps it backgrounded.\n"); - } - + /* Note: argv is incremented above, so [0] is currently the next + * CLI keyword after options */ if (!command) { if (!strcmp(argv[0], "start")) { command = &start_driver; + command_name = argv[0]; } else if (!strcmp(argv[0], "stop")) { command = &stop_driver; + command_name = argv[0]; } else if (!strcmp(argv[0], "shutdown")) { command = &shutdown_driver; + command_name = argv[0]; + } else + if (!strcmp(argv[0], "list")) { + command = &list_driver; + command_name = argv[0]; + } else + if (!strcmp(argv[0], "status")) { + command = &status_driver; + command_name = argv[0]; } lastarg = 1; } @@ -1288,6 +1584,19 @@ int main(int argc, char **argv) if (!command) fatalx(EXIT_FAILURE, "Error: unrecognized command [%s]", argv[0]); + if (nut_debug_level_passthrough == 0 && (command == &start_driver || command == &shutdown_driver)) { + upsdebugx(2, "\n" + "If you're not a NUT core developer, chances are that you're told to enable debugging\n" + "to see why a driver isn't working for you. We're sorry for the confusion, but this is\n" + "the 'upsdrvctl' wrapper, not the driver you're interested in.\n\n" + "Below you'll find one or more lines starting with 'exec:' followed by an absolute\n" + "path to the driver binary and some command line option. This is what the driver\n" + "starts and you need to copy and paste that line and append the debug flags to that\n" + "line (less the 'exec:' prefix).\n\n" + "Alternately, provide an additional '-d' (lower-case) parameter to 'upsdrvctl' to\n" + "pass its current debug level to the launched driver, and '-B' keeps it backgrounded.\n"); + } + #ifndef WIN32 driverpath = xstrdup(DRVPATH); /* set default */ #else @@ -1308,19 +1617,39 @@ int main(int argc, char **argv) } upsdebugx(1, "upsdrvctl commanding all drivers (%d found): %s", - upscount, (pt_cmd ? pt_cmd : NUT_STRARG(argv[lastarg]))); + upscount, (pt_cmd ? pt_cmd : NUT_STRARG(command_name))); send_all_drivers(command); } else if (argc == (lastarg + 1)) { upscount = 1; upsdebugx(1, "upsdrvctl commanding one driver (%s): %s", - argv[lastarg], (pt_cmd ? pt_cmd : NUT_STRARG(argv[lastarg - 1]))); + argv[lastarg], (pt_cmd ? pt_cmd : NUT_STRARG(command_name))); send_one_driver(command, argv[lastarg]); } else { fatalx(EXIT_FAILURE, "Error: extra arguments left on command line\n" "(common options should be before a command and UPS name)"); } +#if (defined(WITH_SOLARIS_SMF) && WITH_SOLARIS_SMF) || (defined(HAVE_SYSTEMD) && HAVE_SYSTEMD) + if (!getenv("NUT_QUIET_INIT_NDE_WARNING") + && (command == &start_driver || command == &stop_driver || command == &shutdown_driver) + ) { +# if (defined(WITH_SOLARIS_SMF) && WITH_SOLARIS_SMF) + char *fwk = "SMF"; +# else +# if (defined(HAVE_SYSTEMD) && HAVE_SYSTEMD) + char *fwk = "systemd"; +# endif +# endif + upslogx(LOG_WARNING, "WARNING: %s was called directly on a system with %s support.\n" + " Please consider using 'upsdrvsvcctl' instead, to avoid conflicts with\n" + " nut-driver service instances prepared by 'nut-driver-enumerator'!", + prog, fwk); + upsdebugx(1, "For more details see https://github.com/networkupstools/nut/wiki/nut%%E2%%80%%90driver%%E2%%80%%90enumerator-(NDE)"); + upsdebugx(1, "To silence this warning export NUT_QUIET_INIT_NDE_WARNING with any value"); + } +#endif + /* Note that the numeric value here is not precise (it reflects * the number of "timeouts" which grows with amount of drivers * and retries. Below we re-check each driver to convert the diff --git a/drivers/upsdrvquery.c b/drivers/upsdrvquery.c index 0936631ab5..31bb8e94e1 100644 --- a/drivers/upsdrvquery.c +++ b/drivers/upsdrvquery.c @@ -2,7 +2,7 @@ tracked until a response arrives, returning that line and closing a connection - Copyright (C) 2023 Jim Klimov + Copyright (C) 2023-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -39,6 +39,19 @@ #include "upsdrvquery.h" #include "nut_stdint.h" +/* Normally the upsdrvquery*() methods call upslogx() to report issues + * such as failed fopen() of Unix socket file, or a dialog timeout or + * different error. + * In a few cases we call these methods opportunistically, and so if + * they fail - we do not care enough to raise a lot of "scary noise"; + * the caller can take care of logging as/if needed. + * This variable and its values are a bit of internal detail between + * certain NUT programs to hush the low-level reports when they are + * not being otherwise debugged (e.g. nut_debug_level < 1). + * Default value allows all those messages to appear. + */ +int nut_upsdrvquery_debug_level = NUT_UPSDRVQUERY_DEBUG_LEVEL_DEFAULT; + udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { udq_pipe_conn_t *conn = (udq_pipe_conn_t*)xcalloc(1, sizeof(udq_pipe_conn_t)); @@ -54,13 +67,15 @@ udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { conn->sockfd = socket(AF_UNIX, SOCK_STREAM, 0); if (conn->sockfd < 0) { - upslog_with_errno(LOG_ERR, "open socket"); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "open socket"); free(conn); return NULL; } if (connect(conn->sockfd, (struct sockaddr *) &sa, sizeof(sa)) < 0) { - upslog_with_errno(LOG_ERR, "connect to driver socket at %s", sockfn); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "connect to driver socket at %s", sockfn); close(conn->sockfd); free(conn); return NULL; @@ -68,14 +83,16 @@ udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { ret = fcntl(conn->sockfd, F_GETFL, 0); if (ret < 0) { - upslog_with_errno(LOG_ERR, "fcntl get on driver socket %s failed", sockfn); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "fcntl get on driver socket %s failed", sockfn); close(conn->sockfd); free(conn); return NULL; } if (fcntl(conn->sockfd, F_SETFL, ret | O_NDELAY) < 0) { - upslog_with_errno(LOG_ERR, "fcntl set O_NDELAY on driver socket %s failed", sockfn); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "fcntl set O_NDELAY on driver socket %s failed", sockfn); close(conn->sockfd); free(conn); return NULL; @@ -84,7 +101,8 @@ udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { BOOL result = WaitNamedPipe(sockfn, NMPWAIT_USE_DEFAULT_WAIT); if (result == FALSE) { - upslog_with_errno(LOG_ERR, "WaitNamedPipe : %d\n", GetLastError()); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "WaitNamedPipe : %d\n", GetLastError()); return NULL; } @@ -99,7 +117,8 @@ udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { NULL); /* no template file */ if (conn->sockfd == INVALID_HANDLE_VALUE) { - upslog_with_errno(LOG_ERR, "CreateFile : %d\n", GetLastError()); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "CreateFile : %d\n", GetLastError()); free(conn); return NULL; } @@ -114,7 +133,8 @@ udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { ); if (conn->overlapped.hEvent == NULL) { - upslogx(LOG_ERR, "Can't create event for reading event log"); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslogx(LOG_ERR, "Can't create event for reading event log"); free(conn); return NULL; } @@ -138,27 +158,49 @@ udq_pipe_conn_t *upsdrvquery_connect(const char *sockfn) { } udq_pipe_conn_t *upsdrvquery_connect_drvname_upsname(const char *drvname, const char *upsname) { - char pidfn[SMALLBUF]; + char sockname[SMALLBUF]; #ifndef WIN32 struct stat fs; - snprintf(pidfn, sizeof(pidfn), "%s/%s-%s", + snprintf(sockname, sizeof(sockname), "%s/%s-%s", dflt_statepath(), drvname, upsname); - check_unix_socket_filename(pidfn); - if (stat(pidfn, &fs)) { - upslog_with_errno(LOG_ERR, "Can't open %s", pidfn); + check_unix_socket_filename(sockname); + if (stat(sockname, &fs)) { + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "Can't open %s", sockname); return NULL; } #else - snprintf(pidfn, sizeof(pidfn), "\\\\.\\pipe\\%s-%s", drvname, upsname); + snprintf(sockname, sizeof(sockname), "\\\\.\\pipe\\%s-%s", drvname, upsname); #endif /* WIN32 */ - return upsdrvquery_connect(pidfn); + return upsdrvquery_connect(sockname); } void upsdrvquery_close(udq_pipe_conn_t *conn) { +#ifdef WIN32 + int loggedOut = 0; +#endif /* WIN32 */ + if (!conn) return; + if (VALID_FD(conn->sockfd)) { + int nudl = nut_upsdrvquery_debug_level; + ssize_t ret; + upsdebugx(5, "%s: closing driver socket, try to say goodbye", __func__); + ret = upsdrvquery_write(conn, "LOGOUT\n"); + if (7 <= ret) { + upsdebugx(5, "%s: okay", __func__); +#ifdef WIN32 + loggedOut = 1; +#endif /* WIN32 */ + usleep(1000000); + } else { + upsdebugx(5, "%s: must have been closed on the other side", __func__); + } + nut_upsdrvquery_debug_level = nudl; + } + #ifndef WIN32 if (VALID_FD(conn->sockfd)) close(conn->sockfd); @@ -169,10 +211,11 @@ void upsdrvquery_close(udq_pipe_conn_t *conn) { memset(&(conn->overlapped), 0, sizeof(conn->overlapped)); if (VALID_FD(conn->sockfd)) { - if (DisconnectNamedPipe(conn->sockfd) == 0) { - upslogx(LOG_ERR, - "DisconnectNamedPipe error : %d", - (int)GetLastError()); + if (DisconnectNamedPipe(conn->sockfd) == 0 && !loggedOut) { + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslogx(LOG_ERR, + "DisconnectNamedPipe error : %d", + (int)GetLastError()); } CloseHandle(conn->sockfd); } @@ -196,7 +239,8 @@ ssize_t upsdrvquery_read_timeout(udq_pipe_conn_t *conn, struct timeval tv) { #endif if (!conn || INVALID_FD(conn->sockfd)) { - upslog_with_errno(LOG_ERR, "socket not initialized"); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "socket not initialized"); return -1; } @@ -205,7 +249,8 @@ ssize_t upsdrvquery_read_timeout(udq_pipe_conn_t *conn, struct timeval tv) { FD_SET(conn->sockfd, &rfds); if (select(conn->sockfd + 1, &rfds, NULL, NULL, &tv) < 0) { - upslog_with_errno(LOG_ERR, "select with socket"); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_DIALOG) + upslog_with_errno(LOG_ERR, "select with socket"); /* upsdrvquery_close(conn); */ return -1; } @@ -219,7 +264,8 @@ ssize_t upsdrvquery_read_timeout(udq_pipe_conn_t *conn, struct timeval tv) { ret = read(conn->sockfd, conn->buf, sizeof(conn->buf)); #else /* - upslog_with_errno(LOG_ERR, "Support for this platform is not currently implemented"); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level > 0) + upslog_with_errno(LOG_ERR, "Support for this platform is not currently implemented"); return -1; */ @@ -333,7 +379,8 @@ ssize_t upsdrvquery_write(udq_pipe_conn_t *conn, const char *buf) { upsdebugx(5, "%s: write to driver socket: %s", __func__, buf); if (!conn || INVALID_FD(conn->sockfd)) { - upslog_with_errno(LOG_ERR, "socket not initialized"); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT) + upslog_with_errno(LOG_ERR, "socket not initialized"); return -1; } @@ -341,7 +388,8 @@ ssize_t upsdrvquery_write(udq_pipe_conn_t *conn, const char *buf) { ret = write(conn->sockfd, buf, buflen); if (ret < 0 || ret != (int)buflen) { - upslog_with_errno(LOG_ERR, "Write to socket %d failed", conn->sockfd); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_DIALOG) + upslog_with_errno(LOG_ERR, "Write to socket %d failed", conn->sockfd); goto socket_error; } @@ -349,7 +397,8 @@ ssize_t upsdrvquery_write(udq_pipe_conn_t *conn, const char *buf) { #else result = WriteFile(conn->sockfd, buf, buflen, &bytesWritten, NULL); if (result == 0 || bytesWritten != (DWORD)buflen) { - upslog_with_errno(LOG_ERR, "Write to handle %p failed", conn->sockfd); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_DIALOG) + upslog_with_errno(LOG_ERR, "Write to handle %p failed", conn->sockfd); goto socket_error; } @@ -433,7 +482,8 @@ ssize_t upsdrvquery_prepare(udq_pipe_conn_t *conn, struct timeval tv) { if (upsdrvquery_read_timeout(conn, tv) < 1) goto socket_error; if (strcmp(conn->buf, "ON")) { - upslog_with_errno(LOG_ERR, "Driver does not have TRACKING support enabled"); + if (nut_debug_level > 0 || nut_upsdrvquery_debug_level >= NUT_UPSDRVQUERY_DEBUG_LEVEL_DIALOG) + upslog_with_errno(LOG_ERR, "Driver does not have TRACKING support enabled"); goto socket_error; } */ diff --git a/drivers/upsdrvquery.h b/drivers/upsdrvquery.h index 6a7b879d6b..e3854940b2 100644 --- a/drivers/upsdrvquery.h +++ b/drivers/upsdrvquery.h @@ -2,7 +2,7 @@ tracked until a response arrives, returning that line and closing a connection - Copyright (C) 2023 Jim Klimov + Copyright (C) 2023-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ */ #ifndef NUT_UPSDRVQUERY_H_SEEN -#define NUT_UPSDRVQUERY_H_SEEN +#define NUT_UPSDRVQUERY_H_SEEN 1 #include "common.h" /* TYPE_FD etc. */ #include "timehead.h" @@ -48,4 +48,11 @@ ssize_t upsdrvquery_request(udq_pipe_conn_t *conn, struct timeval tv, const char /* if buf != NULL, last reply is copied there */ ssize_t upsdrvquery_oneshot(const char *drvname, const char *upsname, const char *query, char *buf, const size_t bufsz, struct timeval *tv); +/* Internal toggle for some NUT programs that deal with Unix socket chatter. + * For a detailed rationale comment see upsdrvquery.c */ +extern int nut_upsdrvquery_debug_level; +#define NUT_UPSDRVQUERY_DEBUG_LEVEL_DEFAULT 6 +#define NUT_UPSDRVQUERY_DEBUG_LEVEL_CONNECT 5 +#define NUT_UPSDRVQUERY_DEBUG_LEVEL_DIALOG 4 + #endif /* NUT_UPSDRVQUERY_H_SEEN */ diff --git a/drivers/upshandler.h b/drivers/upshandler.h index fea10bc20c..78e6ffef0a 100644 --- a/drivers/upshandler.h +++ b/drivers/upshandler.h @@ -22,18 +22,20 @@ /* return values for instcmd */ enum { - STAT_INSTCMD_HANDLED = 0, /* completed successfully */ - STAT_INSTCMD_UNKNOWN, /* unspecified error */ - STAT_INSTCMD_INVALID, /* invalid command */ - STAT_INSTCMD_FAILED /* command failed */ + STAT_INSTCMD_HANDLED = 0, /* completed successfully */ + STAT_INSTCMD_UNKNOWN, /* unspecified error */ + STAT_INSTCMD_INVALID, /* invalid command */ + STAT_INSTCMD_FAILED, /* command failed */ + STAT_INSTCMD_CONVERSION_FAILED /* could not convert value */ }; /* return values for setvar */ enum { - STAT_SET_HANDLED = 0, /* completed successfully */ - STAT_SET_UNKNOWN, /* unspecified error */ - STAT_SET_INVALID, /* not writeable */ - STAT_SET_FAILED /* writing failed */ + STAT_SET_HANDLED = 0, /* completed successfully */ + STAT_SET_UNKNOWN, /* unspecified error */ + STAT_SET_INVALID, /* not writeable */ + STAT_SET_FAILED, /* writing failed */ + STAT_SET_CONVERSION_FAILED /* could not convert value from string */ }; /* structure for funcs that get called by msg parse routine */ diff --git a/drivers/usb-common.c b/drivers/usb-common.c index 6c10860ef8..42721a6afa 100644 --- a/drivers/usb-common.c +++ b/drivers/usb-common.c @@ -100,13 +100,13 @@ static int match_function_exact(USBDevice_t *hd, void *privdata) } #endif #if (defined WITH_USB_BUSPORT) && (WITH_USB_BUSPORT) - #ifdef DEBUG_EXACT_MATCH_BUSPORT +# ifdef DEBUG_EXACT_MATCH_BUSPORT if (strcmp_null(hd->BusPort, data->BusPort) != 0) { upsdebugx(2, "%s: failed match of %s: %s != %s", __func__, "BusPort", hd->BusPort, data->BusPort); return 0; } - #endif +# endif #endif #ifdef DEBUG_EXACT_MATCH_DEVICE if (strcmp_null(hd->Device, data->Device) != 0) { @@ -153,6 +153,13 @@ int USBNewExactMatcher(USBDeviceMatcher_t **matcher, USBDevice_t *hd) #ifdef DEBUG_EXACT_MATCH_DEVICE data->Device = hd->Device ? strdup(hd->Device) : NULL; #endif + + /* NOTE: Callers must pre-initialize to NULL! */ + if (matcher && *matcher) { + free(*matcher); + *matcher = NULL; + } + *matcher = m; return 0; @@ -334,6 +341,12 @@ int USBNewRegexMatcher(USBDeviceMatcher_t **matcher, char **regex, int cflags) } } + /* NOTE: Callers must pre-initialize to NULL! */ + if (matcher && *matcher) { + free(*matcher); + *matcher = NULL; + } + *matcher = m; return 0; @@ -397,3 +410,91 @@ void warn_if_bad_usb_port_filename(const char *fn) { __func__, fn); return; } + +/* Retries were introduced for "Tripp Lite" devices, see + * https://github.com/networkupstools/nut/issues/414 + */ +#define MAX_STRING_DESC_TRIES 3 + +/* API neutral, handles retries. + * Note for future development: a variant of this code is adapted into + * tools/nut-scanner/scan_usb.c - please keep in sync if changing here. + */ +static int nut_usb_get_string_descriptor( + usb_dev_handle *udev, + int StringIdx, + int langid, + char *buf, + size_t buflen) +{ + int ret = -1; + int tries = MAX_STRING_DESC_TRIES; + + while (tries--) { + ret = usb_get_string(udev, (usb_ctrl_strindex)StringIdx, langid, (usb_ctrl_charbuf)buf, buflen); + if (ret >= 0) { + break; + } else if (tries) { + upsdebugx(1, "%s: string descriptor %d request failed, retrying...", __func__, StringIdx); + usleep(50000); /* 50 ms, might help in some cases */ + } + } + return ret; +} + +/* API neutral, assumes en_US if langid descriptor is broken. + * Note for future development: a variant of this code is adapted into + * tools/nut-scanner/scan_usb.c - please keep in sync if changing here. + */ +int nut_usb_get_string( + usb_dev_handle *udev, + int StringIdx, + char *buf, + size_t buflen) +{ + int ret; + char buffer[255]; + int langid; + int len; + int i; + + if (!udev || StringIdx < 1 || StringIdx > 255) { + return -1; + } + + /* request langid descriptor */ + ret = nut_usb_get_string_descriptor(udev, 0, 0, buffer, 4); + if (ret < 0) + return ret; + + if (ret == 4 && buffer[0] >= 4 && buffer[1] == USB_DT_STRING) { + langid = buffer[2] | (buffer[3] << 8); + } else { + upsdebugx(1, "%s: Broken language identifier, assuming en_US", __func__); + langid = 0x0409; + } + + /* retrieve string in preferred language */ + ret = nut_usb_get_string_descriptor(udev, StringIdx, langid, buffer, sizeof(buffer)); + if (ret < 0) { +#ifdef WIN32 + /* only for libusb0 ? */ + errno = -ret; +#endif + return ret; + } + + /* translate simple UTF-16LE to 8-bit */ + len = ret < (int)buflen ? ret : (int)buflen; + len = len / 2 - 1; /* 16-bit characters, without header */ + len = len < (int)buflen - 1 ? len : (int)buflen - 1; /* reserve for null terminator */ + for (i = 0; i < len; i++) { + if (buffer[2 + i * 2 + 1] == 0) + buf[i] = buffer[2 + i * 2]; + else + buf[i] = '?'; /* not decoded */ + } + buf[i] = '\0'; + + return len; +} diff --git a/drivers/usb-common.h b/drivers/usb-common.h index 4f151ee8ac..b71bed8a06 100644 --- a/drivers/usb-common.h +++ b/drivers/usb-common.h @@ -57,7 +57,13 @@ #ifndef NUT_USB_COMMON_H #define NUT_USB_COMMON_H -#include "config.h" /* be sure to know all about the system config */ +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif /* Note: usb-common.h (this file) is included by nut_libusb.h, * so not looping the includes ;) @@ -107,79 +113,79 @@ * any use-case. */ typedef uint8_t usb_ctrl_requesttype; - #define USB_CTRL_REQUESTTYPE_MIN 0 - #define USB_CTRL_REQUESTTYPE_MAX UINT8_MAX +# define USB_CTRL_REQUESTTYPE_MIN 0 +# define USB_CTRL_REQUESTTYPE_MAX UINT8_MAX typedef uint8_t usb_ctrl_request; - #define USB_CTRL_REQUEST_MIN 0 - #define USB_CTRL_REQUEST_MAX UINT8_MAX +# define USB_CTRL_REQUEST_MIN 0 +# define USB_CTRL_REQUEST_MAX UINT8_MAX typedef unsigned char usb_ctrl_endpoint; - #define USB_CTRL_ENDPOINT_MIN 0 - #define USB_CTRL_ENDPOINT_MAX UCHAR_MAX +# define USB_CTRL_ENDPOINT_MIN 0 +# define USB_CTRL_ENDPOINT_MAX UCHAR_MAX typedef uint16_t usb_ctrl_msgvalue; - #define USB_CTRL_MSGVALUE_MIN 0 - #define USB_CTRL_MSGVALUE_MAX UINT16_MAX +# define USB_CTRL_MSGVALUE_MIN 0 +# define USB_CTRL_MSGVALUE_MAX UINT16_MAX typedef uint8_t usb_ctrl_cfgindex; - #define USB_CTRL_CFGINDEX_MIN 0 - #define USB_CTRL_CFGINDEX_MAX UINT8_MAX +# define USB_CTRL_CFGINDEX_MIN 0 +# define USB_CTRL_CFGINDEX_MAX UINT8_MAX typedef uint16_t usb_ctrl_repindex; - #define USB_CTRL_REPINDEX_MIN 0 - #define USB_CTRL_REPINDEX_MAX UINT16_MAX +# define USB_CTRL_REPINDEX_MIN 0 +# define USB_CTRL_REPINDEX_MAX UINT16_MAX typedef uint8_t usb_ctrl_strindex; - #define USB_CTRL_STRINDEX_MIN 0 - #define USB_CTRL_STRINDEX_MAX UINT8_MAX +# define USB_CTRL_STRINDEX_MIN 0 +# define USB_CTRL_STRINDEX_MAX UINT8_MAX typedef uint8_t usb_ctrl_descindex; - #define USB_CTRL_DESCINDEX_MIN 0 - #define USB_CTRL_DESCINDEX_MAX UINT8_MAX +# define USB_CTRL_DESCINDEX_MIN 0 +# define USB_CTRL_DESCINDEX_MAX UINT8_MAX typedef unsigned char* usb_ctrl_charbuf; typedef unsigned char usb_ctrl_char; - #define USB_CTRL_CHAR_MIN 0 - #define USB_CTRL_CHAR_MAX UCHAR_MAX +# define USB_CTRL_CHAR_MIN 0 +# define USB_CTRL_CHAR_MAX UCHAR_MAX /* Here MIN/MAX should not matter much, type mostly used for casting */ typedef uint16_t usb_ctrl_charbufsize; - #define USB_CTRL_CHARBUFSIZE_MIN 0 - #define USB_CTRL_CHARBUFSIZE_MAX UINT16_MAX - #define PRI_NUT_USB_CTRL_CHARBUFSIZE PRIu16 +# define USB_CTRL_CHARBUFSIZE_MIN 0 +# define USB_CTRL_CHARBUFSIZE_MAX UINT16_MAX +# define PRI_NUT_USB_CTRL_CHARBUFSIZE PRIu16 typedef unsigned int usb_ctrl_timeout_msec; /* in milliseconds */ /* Note: there does not seem to be a standard type * for milliseconds, like there is an useconds_t */ - #define USB_CTRL_TIMEOUTMSEC_MIN 0 - #define USB_CTRL_TIMEOUTMSEC_MAX UINT_MAX +# define USB_CTRL_TIMEOUTMSEC_MIN 0 +# define USB_CTRL_TIMEOUTMSEC_MAX UINT_MAX /* defines */ - #define USB_CLASS_PER_INTERFACE LIBUSB_CLASS_PER_INTERFACE - #define USB_DT_STRING LIBUSB_DT_STRING - #define USB_ENDPOINT_IN LIBUSB_ENDPOINT_IN - #define USB_ENDPOINT_OUT LIBUSB_ENDPOINT_OUT - #define USB_RECIP_ENDPOINT LIBUSB_RECIPIENT_ENDPOINT - #define USB_RECIP_INTERFACE LIBUSB_RECIPIENT_INTERFACE - #define USB_REQ_SET_DESCRIPTOR LIBUSB_REQUEST_SET_DESCRIPTOR - #define USB_TYPE_CLASS LIBUSB_REQUEST_TYPE_CLASS - #define USB_TYPE_VENDOR LIBUSB_REQUEST_TYPE_VENDOR +# define USB_CLASS_PER_INTERFACE LIBUSB_CLASS_PER_INTERFACE +# define USB_DT_STRING LIBUSB_DT_STRING +# define USB_ENDPOINT_IN LIBUSB_ENDPOINT_IN +# define USB_ENDPOINT_OUT LIBUSB_ENDPOINT_OUT +# define USB_RECIP_ENDPOINT LIBUSB_RECIPIENT_ENDPOINT +# define USB_RECIP_INTERFACE LIBUSB_RECIPIENT_INTERFACE +# define USB_REQ_SET_DESCRIPTOR LIBUSB_REQUEST_SET_DESCRIPTOR +# define USB_TYPE_CLASS LIBUSB_REQUEST_TYPE_CLASS +# define USB_TYPE_VENDOR LIBUSB_REQUEST_TYPE_VENDOR /* Codebase updated to use LIBUSB_* tokens: - #define ERROR_ACCESS LIBUSB_ERROR_ACCESS - #define ERROR_BUSY LIBUSB_ERROR_BUSY - #define ERROR_IO LIBUSB_ERROR_IO - #define ERROR_NO_DEVICE LIBUSB_ERROR_NO_DEVICE - #define ERROR_NOT_FOUND LIBUSB_ERROR_NOT_FOUND - #define ERROR_OVERFLOW LIBUSB_ERROR_OVERFLOW - #define ERROR_PIPE LIBUSB_ERROR_PIPE - #define ERROR_TIMEOUT LIBUSB_ERROR_TIMEOUT - #define ERROR_NO_MEM LIBUSB_ERROR_NO_MEM - #define ERROR_INVALID_PARAM LIBUSB_ERROR_INVALID_PARAM - #define ERROR_INTERRUPTED LIBUSB_ERROR_INTERRUPTED - #define ERROR_NOT_SUPPORTED LIBUSB_ERROR_NOT_SUPPORTED - #define ERROR_OTHER LIBUSB_ERROR_OTHER +# define ERROR_ACCESS LIBUSB_ERROR_ACCESS +# define ERROR_BUSY LIBUSB_ERROR_BUSY +# define ERROR_IO LIBUSB_ERROR_IO +# define ERROR_NO_DEVICE LIBUSB_ERROR_NO_DEVICE +# define ERROR_NOT_FOUND LIBUSB_ERROR_NOT_FOUND +# define ERROR_OVERFLOW LIBUSB_ERROR_OVERFLOW +# define ERROR_PIPE LIBUSB_ERROR_PIPE +# define ERROR_TIMEOUT LIBUSB_ERROR_TIMEOUT +# define ERROR_NO_MEM LIBUSB_ERROR_NO_MEM +# define ERROR_INVALID_PARAM LIBUSB_ERROR_INVALID_PARAM +# define ERROR_INTERRUPTED LIBUSB_ERROR_INTERRUPTED +# define ERROR_NOT_SUPPORTED LIBUSB_ERROR_NOT_SUPPORTED +# define ERROR_OTHER LIBUSB_ERROR_OTHER */ /* Functions, including range-checks to convert data types of the two APIs. @@ -367,16 +373,16 @@ #endif /* Functions for which simple mappings seem to suffice (no build warnings emitted): */ - #define usb_claim_interface libusb_claim_interface - #define usb_clear_halt libusb_clear_halt - #define usb_close libusb_close - #define usb_set_configuration libusb_set_configuration - #define usb_release_interface libusb_release_interface - #define usb_reset libusb_reset_device +# define usb_claim_interface libusb_claim_interface +# define usb_clear_halt libusb_clear_halt +# define usb_close libusb_close +# define usb_set_configuration libusb_set_configuration +# define usb_release_interface libusb_release_interface +# define usb_reset libusb_reset_device /* FIXME: some original libusb1.c code cast the (int) argument * as (enum libusb_error) - should we force that in the macro? */ - #define nut_usb_strerror(a) libusb_strerror(a) +# define nut_usb_strerror(a) libusb_strerror(a) #endif /* WITH_LIBUSB_1_0 */ /* Note: Checked above that in practice we handle some one libusb API */ @@ -400,70 +406,70 @@ /* no typedef for usb_dev_handle - part of libusb-0.1 API names */ typedef int usb_ctrl_requesttype; - #define USB_CTRL_REQUESTTYPE_MIN INT_MIN - #define USB_CTRL_REQUESTTYPE_MAX INT_MAX +# define USB_CTRL_REQUESTTYPE_MIN INT_MIN +# define USB_CTRL_REQUESTTYPE_MAX INT_MAX typedef int usb_ctrl_request; - #define USB_CTRL_REQUEST_MIN INT_MIN - #define USB_CTRL_REQUEST_MAX INT_MAX +# define USB_CTRL_REQUEST_MIN INT_MIN +# define USB_CTRL_REQUEST_MAX INT_MAX typedef int usb_ctrl_endpoint; - #define USB_CTRL_ENDPOINT_MIN INT_MIN - #define USB_CTRL_ENDPOINT_MAX INT_MAX +# define USB_CTRL_ENDPOINT_MIN INT_MIN +# define USB_CTRL_ENDPOINT_MAX INT_MAX typedef int usb_ctrl_msgvalue; - #define USB_CTRL_MSGVALUE_MIN INT_MIN - #define USB_CTRL_MSGVALUE_MAX INT_MAX +# define USB_CTRL_MSGVALUE_MIN INT_MIN +# define USB_CTRL_MSGVALUE_MAX INT_MAX typedef uint8_t usb_ctrl_cfgindex; - #define USB_CTRL_CFGINDEX_MIN 0 - #define USB_CTRL_CFGINDEX_MAX UINT8_MAX +# define USB_CTRL_CFGINDEX_MIN 0 +# define USB_CTRL_CFGINDEX_MAX UINT8_MAX typedef int usb_ctrl_repindex; - #define USB_CTRL_REPINDEX_MIN INT_MIN - #define USB_CTRL_REPINDEX_MAX INT_MAX +# define USB_CTRL_REPINDEX_MIN INT_MIN +# define USB_CTRL_REPINDEX_MAX INT_MAX typedef int usb_ctrl_strindex; - #define USB_CTRL_STRINDEX_MIN INT_MIN - #define USB_CTRL_STRINDEX_MAX INT_MAX +# define USB_CTRL_STRINDEX_MIN INT_MIN +# define USB_CTRL_STRINDEX_MAX INT_MAX typedef int usb_ctrl_descindex; - #define USB_CTRL_DESCINDEX_MIN INT_MIN - #define USB_CTRL_DESCINDEX_MAX INT_MAX +# define USB_CTRL_DESCINDEX_MIN INT_MIN +# define USB_CTRL_DESCINDEX_MAX INT_MAX /* Here MIN/MAX should not matter much, type mostly used for casting */ typedef char* usb_ctrl_charbuf; typedef char usb_ctrl_char; - #define USB_CTRL_CHAR_MIN CHAR_MIN - #define USB_CTRL_CHAR_MAX CHAR_MAX +# define USB_CTRL_CHAR_MIN CHAR_MIN +# define USB_CTRL_CHAR_MAX CHAR_MAX typedef int usb_ctrl_charbufsize; - #define USB_CTRL_CHARBUFSIZE_MIN INT_MIN - #define USB_CTRL_CHARBUFSIZE_MAX INT_MAX +# define USB_CTRL_CHARBUFSIZE_MIN INT_MIN +# define USB_CTRL_CHARBUFSIZE_MAX INT_MAX /* There is no PRIi :) So we define directly by spec */ - #define PRI_NUT_USB_CTRL_CHARBUFSIZE "i" +# define PRI_NUT_USB_CTRL_CHARBUFSIZE "i" typedef int usb_ctrl_timeout_msec; /* in milliseconds */ - #define USB_CTRL_TIMEOUTMSEC_MIN INT_MIN - #define USB_CTRL_TIMEOUTMSEC_MAX INT_MAX +# define USB_CTRL_TIMEOUTMSEC_MIN INT_MIN +# define USB_CTRL_TIMEOUTMSEC_MAX INT_MAX /* defines */ - #define LIBUSB_ERROR_ACCESS -EACCES - #define LIBUSB_ERROR_BUSY -EBUSY - #define LIBUSB_ERROR_IO -EIO - #define LIBUSB_ERROR_NO_DEVICE -ENODEV - #define LIBUSB_ERROR_NOT_FOUND -ENOENT - #define LIBUSB_ERROR_OVERFLOW -EOVERFLOW - #define LIBUSB_ERROR_PIPE -EPIPE - #define LIBUSB_ERROR_TIMEOUT -ETIMEDOUT - #define LIBUSB_ERROR_NO_MEM -ENOMEM - #define LIBUSB_ERROR_INVALID_PARAM -EINVAL - #define LIBUSB_ERROR_INTERRUPTED -EINTR - #define LIBUSB_ERROR_NOT_SUPPORTED -ENOSYS - #define LIBUSB_ERROR_OTHER -ERANGE +# define LIBUSB_ERROR_ACCESS -EACCES +# define LIBUSB_ERROR_BUSY -EBUSY +# define LIBUSB_ERROR_IO -EIO +# define LIBUSB_ERROR_NO_DEVICE -ENODEV +# define LIBUSB_ERROR_NOT_FOUND -ENOENT +# define LIBUSB_ERROR_OVERFLOW -EOVERFLOW +# define LIBUSB_ERROR_PIPE -EPIPE +# define LIBUSB_ERROR_TIMEOUT -ETIMEDOUT +# define LIBUSB_ERROR_NO_MEM -ENOMEM +# define LIBUSB_ERROR_INVALID_PARAM -EINVAL +# define LIBUSB_ERROR_INTERRUPTED -EINTR +# define LIBUSB_ERROR_NOT_SUPPORTED -ENOSYS +# define LIBUSB_ERROR_OTHER -ERANGE /* Functions for which simple mappings seem to suffice (no build warnings emitted): */ - #define nut_usb_strerror(a) usb_strerror() +# define nut_usb_strerror(a) usb_strerror() #endif /* WITH_LIBUSB_0_1 */ /* USB standard timeout [ms] */ @@ -554,4 +560,9 @@ void nut_usb_addvars(void); * here, to use in several USB-capable drivers. */ void warn_if_bad_usb_port_filename(const char *fn); +/* Retrieves string descriptor from device, decoded to 8-bit string, + * applies retries on failures, and assumes en_US in case the + * langid descriptor is invalid. */ +int nut_usb_get_string(usb_dev_handle *udev, int StringIdx, char *buf, size_t buflen); + #endif /* NUT_USB_COMMON_H */ diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index 4775d38fe8..b5968d0a02 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -6,6 +6,7 @@ * 2005-2006 Peter Selinger * 2007-2009 Arjen de Korte * 2016 Eaton / Arnaud Quette + * 2020-2024 Jim Klimov * * This program was sponsored by MGE UPS SYSTEMS, and now Eaton * @@ -28,7 +29,7 @@ */ #define DRIVER_NAME "Generic HID driver" -#define DRIVER_VERSION "0.52" +#define DRIVER_VERSION "0.57" #define HU_VAR_WAITBEFORERECONNECT "waitbeforereconnect" @@ -48,21 +49,21 @@ #if !((defined SHUT_MODE) && SHUT_MODE) /* explore stub goes first, others alphabetically */ - #include "explore-hid.h" - #include "apc-hid.h" - #include "arduino-hid.h" - #include "belkin-hid.h" - #include "cps-hid.h" - #include "delta_ups-hid.h" - #include "ever-hid.h" - #include "idowell-hid.h" - #include "legrand-hid.h" - #include "liebert-hid.h" - #include "openups-hid.h" - #include "powercom-hid.h" - #include "powervar-hid.h" - #include "salicru-hid.h" - #include "tripplite-hid.h" +# include "explore-hid.h" +# include "apc-hid.h" +# include "arduino-hid.h" +# include "belkin-hid.h" +# include "cps-hid.h" +# include "delta_ups-hid.h" +# include "ever-hid.h" +# include "idowell-hid.h" +# include "legrand-hid.h" +# include "liebert-hid.h" +# include "openups-hid.h" +# include "powercom-hid.h" +# include "powervar-hid.h" +# include "salicru-hid.h" +# include "tripplite-hid.h" #endif /* !SHUT_MODE => USB */ /* Reference list of available subdrivers */ @@ -141,6 +142,15 @@ static size_t interrupt_pipe_EIO_count = 0; /* How many times we had I/O errors static time_t lastpoll; /* Timestamp the last polling */ hid_dev_handle_t udev = HID_DEV_HANDLE_CLOSED; +/** + * Track when calibration started, whether known from UPS status flags + * or interpreted from OL&DISCHRG combo on some devices (see below). + * The last_calibration_start is reset to 0 when the status becomes + * inactive, and last_calibration_finish is incremented every time. + */ +static time_t last_calibration_start = 0; +static time_t last_calibration_finish = 0; + /** * CyberPower UT series sometime need a bit of help deciding their online status. * This quirk is to enable the special handling of OL & DISCHRG at the same time @@ -195,6 +205,34 @@ static int onlinedischarge_log_throttle_charge = -1; */ static int onlinedischarge_log_throttle_hovercharge = 100; +/** + * Per https://github.com/networkupstools/nut/issues/2347 some + * APC BXnnnnMI devices made (flashed?) in 2023-2024 irregularly + * but frequently spew a series of state changes: + * * (maybe OL+DISCHRG), + * * LB, + * * RB, + * * + * within a couple of seconds. If this tunable is positive, we + * would only report the device states on the bus if they persist + * that long (or more), only then assuming they reflect a real + * problematic state and not some internal calibration. + */ +static int lbrb_log_delay_sec = 0; +/** + * By default we only act on (lbrb_log_delay_sec>0) when the device + * is in calibration mode of whatever nature (directly reported or + * assumed from other flag combos). With this flag we do not check + * for calibration and only look at LB + RB timestamps. + */ +static int lbrb_log_delay_without_calibrating = 0; +/** + * When did we last enter the situation? (if more than lbrb_log_delay_sec + * ago, then set the device status and emit the message) + */ +static time_t last_lb_start = 0; +static time_t last_rb_start = 0; + /* support functions */ static hid_info_t *find_nut_info(const char *varname); static hid_info_t *find_hid_info(const HIDData_t *hiddata); @@ -553,6 +591,22 @@ info_lkp_t divide_by_10_conversion[] = { { 0, NULL, divide_by_10_conversion_fun, NULL } }; +/* returns statically allocated string - must not use it again before + done with result! */ +static const char *divide_by_100_conversion_fun(double value) +{ + static char buf[20]; + + snprintf(buf, sizeof(buf), "%0.1f", value * 0.01); + + return buf; +} + +/* FIXME? Do we need an inverse "nuf()" here? */ +info_lkp_t divide_by_100_conversion[] = { + { 0, NULL, divide_by_100_conversion_fun, NULL } +}; + /* returns statically allocated string - must not use it again before done with result! */ static const char *kelvin_celsius_conversion_fun(double value) @@ -997,10 +1051,19 @@ void upsdrv_makevartable(void) addvar(VAR_VALUE, "onlinedischarge_log_throttle_hovercharge", "Set to throttle log messages about discharging while online (only if battery.charge is under this value)"); - + + addvar(VAR_VALUE, "lbrb_log_delay_sec", + "Set to delay status-setting (and log messages) about device in LB or LB+RB state"); + + addvar(VAR_FLAG, "lbrb_log_delay_without_calibrating", + "Set to apply lbrb_log_delay_sec even if device is not calibrating"); + addvar(VAR_FLAG, "disable_fix_report_desc", "Set to disable fix-ups for broken USB encoding, etc. which we apply by default on certain vendors/products"); + addvar(VAR_FLAG, "powercom_sdcmd_byte_order_fallback", + "Set to use legacy byte order for Powercom HID shutdown commands. Either it was wrong forever, or some older devices/firmwares had it the other way around"); + #if !((defined SHUT_MODE) && SHUT_MODE) addvar(VAR_VALUE, "subdriver", "Explicit USB HID subdriver selection"); @@ -1047,9 +1110,9 @@ void upsdrv_updateinfo(void) upsdebugx(1, "Got to reconnect!"); if (use_interrupt_pipe == TRUE && interrupt_pipe_EIO_count > 0) { - upsdebugx(0, "\nReconnecting. If you saw \"nut_libusb_get_interrupt: Input/Output Error\" " + upsdebugx(0, "Reconnecting. If you saw \"nut_libusb_get_interrupt: Input/Output Error\" " "or similar message in the log above, try setting \"pollonly\" flag in \"ups.conf\" " - "options section for this driver!\n"); + "options section for this driver!"); } if (!reconnect_ups()) { @@ -1332,6 +1395,105 @@ void upsdrv_initups(void) } } + val = getval("onlinedischarge_log_throttle_hovercharge"); + if (val) { + int ipv = atoi(val); + if (ipv < 1 || ipv > 100) { + onlinedischarge_log_throttle_hovercharge = 100; + upslogx(LOG_WARNING, + "Warning: invalid value for " + "onlinedischarge_log_throttle_hovercharge: %s, " + "defaulting to %d", + val, onlinedischarge_log_throttle_hovercharge); + } else { + onlinedischarge_log_throttle_hovercharge = ipv; + } + } + + val = getval("lbrb_log_delay_sec"); + if (val) { + int ipv = atoi(val); + if ((ipv == 0 && strcmp("0", val)) || (ipv < 0)) { + lbrb_log_delay_sec = 3; + upslogx(LOG_WARNING, + "Warning: invalid value for " + "lbrb_log_delay_sec: %s, " + "defaulting to %d", + val, lbrb_log_delay_sec); + } else { + lbrb_log_delay_sec = ipv; + } + } else { + /* Activate APC BXnnnMI/BXnnnnMI tweaks, for details see + * https://github.com/networkupstools/nut/issues/2347 + */ + size_t productLen = hd->Product ? strlen(hd->Product) : 0; + + /* FIXME: Consider also ups.mfr.date as 2023 or newer? + * Eventually up to some year this gets fixed? + */ + if (hd->Vendor + && productLen > 6 /* BXnnnMI at least */ + && (!strcmp(hd->Vendor, "APC") || !strcmp(hd->Vendor, "American Power Conversion")) + && (strstr(hd->Product, " BX") || strstr(hd->Product, "BX") == hd->Product) + && (hd->Product[productLen - 2] == 'M' && hd->Product[productLen - 1] == 'I') + ) { + int got_lbrb_log_delay_without_calibrating = testvar("lbrb_log_delay_without_calibrating") ? 1 : 0, + got_onlinedischarge_calibration = testvar("onlinedischarge_calibration") ? 1 : 0, + got_onlinedischarge_log_throttle_sec = testvar("onlinedischarge_log_throttle_sec") ? 1 : 0; + + lbrb_log_delay_sec = 3; + + upslogx(LOG_INFO, "Defaulting lbrb_log_delay_sec=%d " + "for %s model %s%s%s%s%s%s%s%s%s%s", + lbrb_log_delay_sec, + hd->Vendor, hd->Product, + + !got_lbrb_log_delay_without_calibrating + || !got_onlinedischarge_calibration + || !got_onlinedischarge_log_throttle_sec + ? "; consider also setting the " : "", + + !got_lbrb_log_delay_without_calibrating + ? "lbrb_log_delay_without_calibrating " : "", + + !got_lbrb_log_delay_without_calibrating + && (!got_onlinedischarge_calibration + || !got_onlinedischarge_log_throttle_sec) + ? "and/or " : "", + + !got_onlinedischarge_calibration + ? "onlinedischarge_calibration " : "", + + (!got_lbrb_log_delay_without_calibrating + || !got_onlinedischarge_calibration ) + && !got_onlinedischarge_log_throttle_sec + ? "and/or " : "", + + !got_onlinedischarge_log_throttle_sec + ? "onlinedischarge_log_throttle_sec " : "", + + !got_lbrb_log_delay_without_calibrating + || !got_onlinedischarge_calibration + || !got_onlinedischarge_log_throttle_sec + ? "flag" : "", + + 2 > ( got_lbrb_log_delay_without_calibrating + + got_onlinedischarge_calibration + + got_onlinedischarge_log_throttle_sec) + ? "(s)" : "", + + !got_lbrb_log_delay_without_calibrating + || !got_onlinedischarge_calibration + || !got_onlinedischarge_log_throttle_sec + ? " in your configuration" : ""); + } + } + + if (testvar("lbrb_log_delay_without_calibrating")) { + lbrb_log_delay_without_calibrating = 1; + } + if (testvar("disable_fix_report_desc")) { disable_fix_report_desc = 1; } @@ -1426,6 +1588,11 @@ void possibly_supported(const char *mfr, HIDDevice_t *arghd) "'-x productid=%04x' option. Please report your results to the NUT user's\n" "mailing list .\n", mfr, arghd->VendorID, arghd->ProductID, arghd->ProductID); + + if (arghd->VendorID == 0x06da) { + upsdebugx(0, +"Please note that this Vendor ID is also known in devices supported by nutdrv_qx"); + } } /* Update ups_status to remember this status item. Interpretation is @@ -1927,10 +2094,49 @@ unsigned ups_status_get(void) return ups_status; } +/** Helper to both status_set("CAL") and track last_calibration_start timestamp */ +static void status_set_CAL(void) +{ + /* Note: dstate tokens can only be set, not cleared; a + * dstate_init() wipes the whole internal buffer though. */ + int wasSet = status_get("CAL"); + time_t now; + + time(&now); + + /* A few sanity checks */ + if (wasSet) { + if (!last_calibration_start) { + upsdebugx(2, "%s: status was already set but not time-stamped: CAL", __func__); + } else { + upsdebugx(2, "%s: status was already set %f sec ago : CAL", + __func__, difftime(now, last_calibration_start)); + } + } else { + if (last_calibration_finish) { + upsdebugx(2, "%s: starting a new calibration, last one finished %f sec ago", + __func__, difftime(now, last_calibration_finish)); + } else { + upsdebugx(2, "%s: starting a new calibration, first in this driver's lifetime", + __func__); + } + } + + if (!last_calibration_start) { + last_calibration_start = now; + } + + if (!wasSet) { + status_set("CAL"); /* calibration */ + } +} + /* Convert the local status information to NUT format and set NUT status. */ static void ups_status_set(void) { + int isCalibrating = 0; + if (ups_status & STATUS(VRANGE)) { dstate_setinfo("input.transfer.reason", "input voltage out of range"); } else if (ups_status & STATUS(FRANGE)) { @@ -1946,7 +2152,7 @@ static void ups_status_set(void) * raise FSD urgently. So we first let upsmon know it is just a drill. */ if (ups_status & STATUS(CALIB)) { - status_set("CAL"); /* calibration */ + status_set_CAL(); /* calibration */ } if ((!(ups_status & STATUS(DISCHRG))) && ( @@ -1954,8 +2160,8 @@ static void ups_status_set(void) || onlinedischarge_log_throttle_charge != -1 )) { upsdebugx(1, - "%s: seems that UPS [%s] was in OL+DISCHRG state, " - "but no longer is now.", + "%s: seems that UPS [%s] was in OL+DISCHRG state " + "previously, but no is longer discharging now.", __func__, upsname); onlinedischarge_log_throttle_timestamp = 0; onlinedischarge_log_throttle_charge = -1; @@ -1969,7 +2175,7 @@ static void ups_status_set(void) /* if online but discharging */ if (onlinedischarge_calibration) { /* if we treat OL+DISCHRG as calibrating */ - status_set("CAL"); /* calibration */ + status_set_CAL(); /* calibration */ } if (onlinedischarge_onbattery) { @@ -1995,23 +2201,38 @@ static void ups_status_set(void) /* First disable, then enable if OK for noise*/ do_logmsg = 0; - /* Time or not, did the charge change since last log? */ + /* Time or not, did the charge change since last log? + * Reminder: "onlinedischarge_log_throttle_charge" is + * the last-reported charge in OL+DISCHRG situation, + * as the battery remainder trickles down and we only + * report the changes (throttling the message stream). + * The "onlinedischarge_log_throttle_hovercharge" lets + * us ignore sufficiently high battery charges, where + * the user configuration (or defaults at 100%) tell + * us this is just about the battery not accepting the + * external power *all* the time so its charge "hovers" + * (typically between 90%-100%) to benefit the chemical + * process back-end of the battery and its life time. + */ if ((s = dstate_getinfo("battery.charge"))) { - /* NOTE: "0" may mean a conversion error: */ + /* NOTE: exact "0" may mean a conversion error: */ current_charge = atoi(s); if (current_charge > 0 && current_charge != onlinedischarge_log_throttle_charge ) { /* Charge has changed, but is it * now low enough to worry? */ - if (onlinedischarge_log_throttle_hovercharge - < onlinedischarge_log_throttle_charge + if (current_charge + < onlinedischarge_log_throttle_hovercharge ) { upsdebugx(3, "%s: current " "battery.charge=%d is under " - "onlinedischarge_log_throttle_charge=%d", + "onlinedischarge_log_throttle_hovercharge=%d " + "(previous onlinedischarge_log_throttle_charge=%d): %s", __func__, current_charge, - onlinedischarge_log_throttle_charge); + onlinedischarge_log_throttle_hovercharge, + onlinedischarge_log_throttle_charge, + (current_charge > onlinedischarge_log_throttle_charge ? "charging" : "draining")); do_logmsg = 1; } else { /* All seems OK, don't spam log @@ -2020,9 +2241,12 @@ static void ups_status_set(void) upsdebugx(5, "%s: current " "battery.charge=%d " "is okay compared to " - "onlinedischarge_log_throttle_charge=%d", + "onlinedischarge_log_throttle_hovercharge=%d " + "(previous onlinedischarge_log_throttle_charge=%d): %s", __func__, current_charge, - onlinedischarge_log_throttle_charge); + onlinedischarge_log_throttle_hovercharge, + onlinedischarge_log_throttle_charge, + (current_charge > onlinedischarge_log_throttle_charge ? "charging" : "draining")); } } } else { @@ -2057,6 +2281,7 @@ static void ups_status_set(void) } if (do_logmsg) { + /* If OL+DISCHRG, and not-throttled against log spam */ char msg_charge[LARGEBUF]; msg_charge[0] = '\0'; @@ -2075,9 +2300,10 @@ static void ups_status_set(void) } else { snprintf(msg_charge, sizeof(msg_charge), "Battery charge changed from %d to %d " - "since last such report. ", + "since last such report (%s). ", onlinedischarge_log_throttle_charge, - current_charge); + current_charge, + (current_charge > onlinedischarge_log_throttle_charge ? "charging" : "draining")); } onlinedischarge_log_throttle_charge = current_charge; } @@ -2093,6 +2319,8 @@ static void ups_status_set(void) status_set("OL"); } + isCalibrating = status_get("CAL"); + if ((ups_status & STATUS(DISCHRG)) && !(ups_status & STATUS(DEPLETED))) { status_set("DISCHRG"); /* discharging */ @@ -2102,13 +2330,58 @@ static void ups_status_set(void) status_set("CHRG"); /* charging */ } if (ups_status & (STATUS(LOWBATT) | STATUS(TIMELIMITEXP) | STATUS(SHUTDOWNIMM))) { - status_set("LB"); /* low battery */ + if (lbrb_log_delay_sec < 1 + || (!isCalibrating && !lbrb_log_delay_without_calibrating) + || !(ups_status & STATUS(ONLINE)) /* assume actual power failure, do not delay */ + ) { + /* Quick and easy decision */ + status_set("LB"); /* low battery */ + } else { + time_t now; + time(&now); + + if (!last_lb_start) { + last_lb_start = now; + } else { + if (difftime(now, last_lb_start) > lbrb_log_delay_sec) { + /* Patience expired */ + status_set("LB"); /* low battery */ + } else { + upsdebugx(2, "%s: throttling LB status due to lbrb_log_delay_sec", __func__); + } + } + } + } else { + last_lb_start = 0; } if (ups_status & STATUS(OVERLOAD)) { status_set("OVER"); /* overload */ } if (ups_status & STATUS(REPLACEBATT)) { - status_set("RB"); /* replace batt */ + if (lbrb_log_delay_sec < 1 + || (!isCalibrating && !lbrb_log_delay_without_calibrating) + || !last_lb_start /* Calibration ended (not LB anymore) */ + || !(ups_status & STATUS(ONLINE)) /* assume actual power failure, do not delay */ + ) { + /* Quick and easy decision */ + status_set("RB"); /* replace batt */ + } else { + time_t now; + time(&now); + + if (!last_rb_start) { + last_rb_start = now; + } else { + if (difftime(now, last_rb_start) > lbrb_log_delay_sec) { + /* Patience expired */ + status_set("RB"); /* replace batt */ + } else { + upsdebugx(2, "%s: throttling RB status due to lbrb_log_delay_sec", __func__); + } + } + } + } else { + last_rb_start = 0; } if (ups_status & STATUS(TRIM)) { status_set("TRIM"); /* SmartTrim */ @@ -2122,6 +2395,15 @@ static void ups_status_set(void) if (ups_status & STATUS(OFF)) { status_set("OFF"); /* ups is off */ } + + if (!isCalibrating) { + if (last_calibration_start) { + time(&last_calibration_finish); + upsdebugx(2, "%s: calibration is no longer in place, took %f sec", + __func__, difftime(last_calibration_finish, last_calibration_start)); + } + last_calibration_start = 0; + } } /* find info element definition in info array diff --git a/drivers/usbhid-ups.h b/drivers/usbhid-ups.h index 2e8a3fcedd..f6f67fea72 100644 --- a/drivers/usbhid-ups.h +++ b/drivers/usbhid-ups.h @@ -26,11 +26,19 @@ #ifndef USBHID_UPS_H #define USBHID_UPS_H +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif + #include #include #include #include -#include "config.h" + #include "libhid.h" extern hid_dev_handle_t udev; @@ -114,6 +122,7 @@ extern info_lkp_t date_conversion[]; extern info_lkp_t hex_conversion[]; extern info_lkp_t stringid_conversion[]; extern info_lkp_t divide_by_10_conversion[]; +extern info_lkp_t divide_by_100_conversion[]; extern info_lkp_t kelvin_celsius_conversion[]; /* ---------------------------------------------------------------------- */ diff --git a/drivers/victronups.c b/drivers/victronups.c index 49901f43bf..ad11b4b3cb 100644 --- a/drivers/victronups.c +++ b/drivers/victronups.c @@ -32,7 +32,7 @@ #include "serial.h" #define DRIVER_NAME "GE/IMV/Victron UPS driver" -#define DRIVER_VERSION "0.22" +#define DRIVER_VERSION "0.23" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/include/Makefile.am b/include/Makefile.am index 90c2dbc54b..cb13105ac5 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -3,15 +3,17 @@ # Export certain values for ccache which NUT ci_build.sh can customize, # to facilitate developer iteration re-runs of "make" later. # At least GNU and BSD make implementations are okay with this syntax. -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_DIR=@CCACHE_DIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_PATH=@CCACHE_PATH@ -@NUT_AM_MAKE_CAN_EXPORT@export PATH=@PATH_DURING_CONFIGURE@ - -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 \ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_NAMESPACE@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_BASEDIR@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_DIR@export CCACHE_DIR=@CCACHE_DIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export CCACHE_PATH=@CCACHE_PATH@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export PATH=@PATH_DURING_CONFIGURE@ + +dist_noinst_HEADERS = \ + attribute.h common.h extstate.h proto.h \ + state.h str.h timehead.h upsconf.h \ + nut_bool.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: @@ -30,7 +32,9 @@ MAINTAINERCLEANFILES = Makefile.in .dirstamp # (for builds not made from the tagged commit in a Git workspace) nut_version.h: @FORCE_NUT_VERSION@ - @GITREV="`git describe --tags --match 'v[0-9]*.[0-9]*.[0-9]' --exclude '*-signed' --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*' 2>/dev/null | sed -e 's/^v\([0-9]\)/\1/' -e 's,^.*/,,'`" || GITREV=""; \ + @GITREV="`$(top_srcdir)/tools/gitlog2version.sh`" || GITREV=""; \ + GITREV_IS_RELEASE="`NUT_VERSION_QUERY=IS_RELEASE $(top_srcdir)/tools/gitlog2version.sh 2>/dev/null`" || GITREV_IS_RELEASE="false"; \ + GITREV_SEMVER="`NUT_VERSION_QUERY=SEMVER $(top_srcdir)/tools/gitlog2version.sh 2>/dev/null`" || GITREV_SEMVER=""; \ { echo '/* Autogenerated file. Do not change. */' ; \ echo '/* This file was generated by "make". */' ; \ if [ -z "$$GITREV" ]; then \ @@ -43,9 +47,18 @@ nut_version.h: @FORCE_NUT_VERSION@ echo ' * building the newest tagged commit itself - then just the tag).'; \ echo ' */' ; \ fi ; \ + if [ -z "$$GITREV_SEMVER" ]; then \ + GITREV_SEMVER="@NUT_SOURCE_GITREV_SEMVER@"; \ + fi ; \ echo "#define NUT_VERSION_MACRO \"$$NUT_VERSION\"" ; \ + echo "#define NUT_VERSION_SEMVER_MACRO \"$$GITREV_SEMVER\"" ; \ + if $$GITREV_IS_RELEASE ; then \ + echo "#define NUT_VERSION_IS_RELEASE 1" ; \ + else \ + echo "#define NUT_VERSION_IS_RELEASE 0" ; \ + fi ; \ } > "$@.tmp" ; \ - echo "NUT_VERSION: \"$$NUT_VERSION\"" + echo "NUT_VERSION: \"$$NUT_VERSION\" NUT_VERSION_IS_RELEASE:$$GITREV_IS_RELEASE NUT_VERSION_SEMVER: \"$$GITREV_SEMVER\"" -test -f "$@" || cp "$@.tmp" "$@" -cmp -s "$@.tmp" "$@" || cp "$@.tmp" "$@" -rm -f "$@.tmp" diff --git a/include/common.h b/include/common.h index 407b8425aa..1a1c3a8188 100644 --- a/include/common.h +++ b/include/common.h @@ -1,6 +1,7 @@ /* common.h - prototypes for the common useful functions Copyright (C) 2000 Russell Kroll + Copyright (C) 2021-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -20,7 +21,13 @@ #ifndef NUT_COMMON_H_SEEN #define NUT_COMMON_H_SEEN 1 -#include "config.h" /* must be the first header */ +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif #ifdef WIN32 # ifndef __POSIX_VISIBLE @@ -193,6 +200,42 @@ extern const char *UPS_VERSION; /** @brief Default timeout (in seconds) for retrieving the result of a `TRACKING`-enabled operation (e.g. `INSTCMD`, `SET VAR`). */ #define DEFAULT_TRACKING_TIMEOUT 10 +/* Returns a pointer to static internal char[] buffer with current value + * of NUT_VERSION_MACRO (aka char* UPS_VERSION) and its layman description + * (e.g. a "release" or "development iteration after" a certain semantically + * versioned release). Returns UPS_VERSION if failed to construct a better + * description. Either way, should not be free()'d by caller and does not + * have an end-of-line char of its own. */ +const char *describe_NUT_VERSION_once(void); + +/* Based on NUT_QUIET_INIT_BANNER envvar (present and empty or "true") + * hide the NUT tool name+version banners; show them by default */ +int banner_is_disabled(void); + +/* Some NUT programs have historically printed their banner at start-up + * always, and so did not print one in help()/usage() or handling `-V` + * like others did. Now that we have NUT_QUIET_INIT_BANNER, we need a + * way to print that banner (regardless of the flag in some cases). + * The "even_if_disabled" should be 0 for initial banner of those + * programs (so the envvar would hide it), 1 -V case and 2 in -h case + * (for a blank line after). As before, the banner is printed to stdout. + * Returns the result of printf() involved. Remembers to not print again + * if the earlier printf() was successful. + */ +int print_banner_once(const char *prog, int even_if_disabled); + +/* Normally we can (attempt to) use the syslog or Event Log (WIN32), + * but environment variable NUT_DEBUG_SYSLOG allows to bypass it, and + * perhaps keep daemons logging to stderr (e.g. in NUT Integration Test + * suite to not pollute the OS logs, or in systemd where stderr and + * syslog both go into the same journal). Returns: + * 0 Not disabled (NUT_DEBUG_SYSLOG not set to a value below; unset or "default" + * values are handled quietly, but others emit a warning) + * 1 Disabled and background() keeps stderr attached (NUT_DEBUG_SYSLOG="stderr") + * 2 Disabled and background() detaches stderr as usual (NUT_DEBUG_SYSLOG="none") + */ +int syslog_is_disabled(void); + /* get the syslog ready for us */ void open_syslog(const char *progname); @@ -208,6 +251,46 @@ void become_user(struct passwd *pw); /* drop down into a directory and throw away pointers to the old path */ void chroot_start(const char *path); +/* Try to identify process (program) name for the given PID, + * return NULL if we can not for any reason (does not run, + * no rights, do not know how to get it on current OS, etc.) + * If the returned value is not NULL, caller should free() it. + * Some implementation pieces borrowed from + * https://man7.org/linux/man-pages/man2/readlink.2.html and + * https://github.com/openbsd/src/blob/master/bin/ps/ps.c + * NOTE: Very much platform-dependent! */ +char * getprocname(pid_t pid); + +/* Determine the base name of specified progname (may be full path) + * and the location of the last "." dot character in it for extension + * (caller's len and dot populated only if pointers are not NULL). + */ +size_t parseprogbasename(char *buf, size_t buflen, const char *progname, size_t *pprogbasenamelen, size_t *pprogbasenamedot); + +/* If we can determine the binary path name of the specified "pid", + * check if it matches the assumed name of the current program. + * Returns: + * -3 Skipped because NUT_IGNORE_CHECKPROCNAME is set + * -2 Could not parse a program name (ok to proceed, + * risky - but matches legacy behavior) + * -1 Could not identify a program name (ok to proceed, + * risky - but matches legacy behavior) + * 0 Process name identified, does not seem to match + * 1+ Process name identified, and seems to match with + * varying precision + * Generally speaking, if (checkprocname(...)) then ok to proceed + */ +int checkprocname(pid_t pid, const char *progname); +/* compareprocname() does the bulk of work for checkprocname() + * and returns same values. The "pid" argument is used for logging. + * Generally speaking, if (compareprocname(...)) then ok to proceed + */ +int compareprocname(pid_t pid, const char *procname, const char *progname); +/* Helper for the above methods and some others. If it returns true (1), + * work about PID-name comparison should be quickly skipped. + */ +int checkprocname_ignored(const char *caller); + /* write a pid file - is a full pathname *or* just the program name */ void writepid(const char *name); @@ -215,11 +298,11 @@ void writepid(const char *name); * a few sanity checks; returns -1 on error */ pid_t parsepid(const char *buf); -/* send a signal to another running process */ +/* send a signal to another running NUT process */ #ifndef WIN32 -int sendsignal(const char *progname, int sig); +int sendsignal(const char *progname, int sig, int check_current_progname); #else -int sendsignal(const char *progname, const char * sig); +int sendsignal(const char *progname, const char * sig, int check_current_progname); #endif int snprintfcat(char *dst, size_t size, const char *fmt, ...) @@ -230,20 +313,34 @@ pid_t get_max_pid_t(void); /* send sig to pid after some sanity checks, returns * -1 for error, or zero for a successfully sent signal */ -int sendsignalpid(pid_t pid, int sig); +int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_progname); + +/* open and get the pid + * returns zero or more for successfully retrieved value, + * negative for errors: + * -3 PID file not found + * -2 PID file not parsable + */ +pid_t parsepidfile(const char *pidfn); -/* open , get the pid, then send it +#ifndef WIN32 +/* use parsepidfile() to get the pid, then send it * returns zero for successfully sent signal, * negative for errors: * -3 PID file not found * -2 PID file not parsable * -1 Error sending signal + * + * if executable process with that pid has suitable progname + * (specified or that of the current process, depending on args: + * most daemons request to check_current_progname for their other + * process instancees, but upsdrvctl which manages differently + * named driver programs does not request it) */ -#ifndef WIN32 -/* open , get the pid, then send it */ -int sendsignalfn(const char *pidfn, int sig); +int sendsignalfn(const char *pidfn, int sig, const char *progname, int check_current_progname); #else -int sendsignalfn(const char *pidfn, const char * sig); +/* No progname here - communications via named pipe */ +int sendsignalfn(const char *pidfn, const char * sig, const char *progname_ignored, int check_current_progname_ignored); #endif const char *xbasename(const char *file); @@ -258,12 +355,40 @@ const char * confpath(void); /* Return the default path for the directory containing state files */ const char * dflt_statepath(void); -/* Return the alternate path for pid files */ +/* Return the alternate path for pid files (non-root daemons) */ const char * altpidpath(void); +/* Return the main path for pid files (root daemons like upsmon) */ +const char * rootpidpath(void); + /* Die with a standard message if socket filename is too long */ void check_unix_socket_filename(const char *fn); +/* Provide integration for systemd inhibitor interface (where available, + * dummy code otherwise) implementing the pseudo-code example from + * https://systemd.io/INHIBITOR_LOCKS/ + * TODO: Potentially extensible to other frameworks with similar concepts?.. + */ +TYPE_FD Inhibit(const char *arg_what, const char *arg_who, const char *arg_why, const char *arg_mode); +/* Let the service management framework proceed with its sleep mode */ +void Uninhibit(TYPE_FD *fd_ptr); +/* Check once if the system plans to sleep or is waking up: + * -1 Same reply as before, whatever it was + * 0 (false) Just after the sleep, at least as a bus signal + * 1 (true) Before the sleep - we must process and un-block it + */ +int isPreparingForSleep(void); + +/* A couple of methods to reflect built-in (absent) or run-time (it depends) + * support for monitoring that the OS goes to sleep and wakes up, and if we + * can "inhibit" that going to sleep in order to do some house-keeping first. + * -1 = do not know yet + * 0 = not supported, do not bother asking in daemon loops + * 1 = seems supported + */ +int isInhibitSupported(void); +int isPreparingForSleepSupported(void); + /* Send (daemon) state-change notifications to an * external service management framework such as systemd. * State types below are initially loosely modeled after @@ -341,6 +466,13 @@ void nut_report_config_flags(void); void upsdebugx_report_search_paths(int level, int report_search_paths_builtin); void nut_prepare_search_paths(void); +/* Internal toggle for some NUT programs that deal with signal sending. + * For a detailed rationale comment see common.c */ +extern int nut_sendsignal_debug_level; +#define NUT_SENDSIGNAL_DEBUG_LEVEL_DEFAULT 6 +#define NUT_SENDSIGNAL_DEBUG_LEVEL_FOPEN_PIDFILE 5 +#define NUT_SENDSIGNAL_DEBUG_LEVEL_KILL_SIG0PING 4 + extern int nut_debug_level; extern int nut_log_level; diff --git a/include/nut_bool.h b/include/nut_bool.h new file mode 100644 index 0000000000..0fa4b41828 --- /dev/null +++ b/include/nut_bool.h @@ -0,0 +1,99 @@ +/* + * nut_bool.h - Network UPS Tools boolean type definitions + * which should ensure a "nut_bool_t" name with + * lower-case values "true" and "false" + * + * Inspired by earlier efforts and numerous definitions in NUT codebase. + * Copyright (C) 2024 Jim Klimov + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * 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_BOOL_H_SEEN +#define NUT_BOOL_H_SEEN 1 + +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif + +/* See also https://en.cppreference.com/w/cpp/header/cstdbool for more + * info about what should be available where per standard approach. */ +#ifdef __cplusplus +# if defined HAVE_CSTDBOOL_H || defined HAVE_CSTDBOOL +# include +# else +# ifdef HAVE_STDBOOL_H +# include +# endif +# endif +#else +/* plain C */ +# ifdef HAVE_STDBOOL_H +# include +# endif +#endif + +/* Is the goal achieved by the system headers or compiler itself, + * so we can just alias to existing type and its values? */ +#if defined __bool_true_false_are_defined && __bool_true_false_are_defined + typedef bool nut_bool_t; +#elif defined FOUND__BOOL_TYPE && defined HAVE__BOOL_VALUE_LOWERCASE + typedef FOUND__BOOL_TYPE nut_bool_t; +#elif defined FOUND_BOOL_TYPE && defined HAVE_BOOL_VALUE_LOWERCASE + typedef FOUND_BOOL_TYPE nut_bool_t; +#elif defined FOUND_BOOLEAN_TYPE && defined HAVE_BOOLEAN_VALUE_LOWERCASE + typedef FOUND_BOOLEAN_TYPE nut_bool_t; +#elif defined FOUND_BOOL_T_TYPE && defined HAVE_BOOL_T_VALUE_LOWERCASE + typedef FOUND_BOOL_T_TYPE nut_bool_t; +#else + /* Need a new type; can we use an enum with lower-case values? */ +# if (defined true && defined false) || defined HAVE__BOOL_VALUE_LOWERCASE || defined HAVE_BOOL_VALUE_LOWERCASE || defined HAVE_BOOLEAN_VALUE_LOWERCASE || defined HAVE_BOOL_T_VALUE_LOWERCASE + /* Lower-case true/false are known */ +# if defined FOUND__BOOL_TYPE + /* Got a C99 built-in mandated by the standard */ + typedef FOUND__BOOL_TYPE nut_bool_t; +# else + typedef int nut_bool_t; +# endif +# elif defined FOUND__BOOL_VALUE_TRUE && defined FOUND__BOOL_VALUE_FALSE + typedef enum nut_bool_enum { false = FOUND__BOOL_VALUE_FALSE, true = FOUND__BOOL_VALUE_TRUE } nut_bool_t; +# elif defined FOUND_BOOL_VALUE_TRUE && defined FOUND_BOOL_VALUE_FALSE + typedef enum nut_bool_enum { false = FOUND_BOOL_VALUE_FALSE, true = FOUND_BOOL_VALUE_TRUE } nut_bool_t; +# elif defined FOUND_BOOLEAN_VALUE_TRUE && defined FOUND_BOOLEAN_VALUE_FALSE + typedef enum nut_bool_enum { false = FOUND_BOOLEAN_VALUE_FALSE, true = FOUND_BOOLEAN_VALUE_TRUE } nut_bool_t; +# elif defined FOUND_BOOL_T_VALUE_TRUE && defined FOUND_BOOL_T_VALUE_FALSE + typedef enum nut_bool_enum { false = FOUND_BOOL_T_VALUE_FALSE, true = FOUND_BOOL_T_VALUE_TRUE } nut_bool_t; +# else + typedef enum nut_bool_enum { false = 0, true = 1 } nut_bool_t; +# endif +#endif + +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif + +#endif /* NUT_BOOL_H_SEEN */ diff --git a/include/nut_float.h b/include/nut_float.h index 290edb4909..fe790e6e99 100644 --- a/include/nut_float.h +++ b/include/nut_float.h @@ -22,7 +22,13 @@ #ifndef NUT_FLOAT_H_SEEN #define NUT_FLOAT_H_SEEN 1 -#include "config.h" +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif #if defined HAVE_FLOAT_H # include @@ -51,4 +57,9 @@ #define d_equal(x, y) ( fabs((double)(x) - (double)(y)) <= DBL_EPSILON ) #define ld_equal(x, y) ( fabsl((long double)(x) - (long double)(y)) <= LDBL_EPSILON ) +#ifndef HAVE_STRTOF +/* Use fallback from libcommon */ +float strtof(const char *nptr, char **endptr); +#endif + #endif /* NUT_FLOAT_H_SEEN */ diff --git a/include/nut_platform.h b/include/nut_platform.h index ba640c5a7c..90237d2600 100644 --- a/include/nut_platform.h +++ b/include/nut_platform.h @@ -27,26 +27,31 @@ /* Apple Mac OS X, iOS and Darwin */ #if (defined __APPLE__ && defined __MACH__) /** Apple OS based on Mach ukernel */ - #define NUT_PLATFORM_APPLE_MACH +# define NUT_PLATFORM_APPLE_MACH - #include + /* https://stackoverflow.com/a/2339910/4715872 */ +# ifndef SOEXT +# define SOEXT ".dylib" +# endif - #if (defined TARGET_OS_EMBEDDED) +# include + +# if (defined TARGET_OS_EMBEDDED) /** iOS (implies \ref NUT_PLATFORM_APPLE_MACH) */ - #define NUT_PLATFORM_APPLE_IOS - #endif - #if (defined TARGET_IPHONE_SIMULATOR) +# define NUT_PLATFORM_APPLE_IOS +# endif +# if (defined TARGET_IPHONE_SIMULATOR) /** iOS simulator (implies \ref NUT_PLATFORM_APPLE_MACH) */ - #define NUT_PLATFORM_APPLE_IOS_SIMULATOR - #endif - #if (defined TARGET_OS_IPHONE) +# define NUT_PLATFORM_APPLE_IOS_SIMULATOR +# endif +# if (defined TARGET_OS_IPHONE) /** iPhone (implies \ref NUT_PLATFORM_APPLE_MACH) */ - #define NUT_PLATFORM_APPLE_IPHONE - #endif - #if (defined TARGET_OS_MAC) +# define NUT_PLATFORM_APPLE_IPHONE +# endif +# if (defined TARGET_OS_MAC) /** Mac OS X (implies \ref NUT_PLATFORM_APPLE_MACH) */ - #define NUT_PLATFORM_APPLE_OSX - #endif +# define NUT_PLATFORM_APPLE_OSX +# endif #endif /* @@ -56,69 +61,94 @@ * Remove if no longer necessary */ #if (defined _AIX && !defined __unix__) - #define __unix__ +# define __unix__ #endif /* Microsoft Windows */ #if (defined _WIN32 || defined _WIN64) /** Windows */ - #define NUT_PLATFORM_MS_WINDOWS +# define NUT_PLATFORM_MS_WINDOWS + +# ifndef SOEXT +# define SOEXT ".dll" +# endif - #if (defined NTDDI_WIN8 && NTDDI_VERSION >= NTDDI_WIN8) +# if (defined NTDDI_WIN8 && NTDDI_VERSION >= NTDDI_WIN8) /** Windows 8 */ - #define NUT_PLATFORM_MS_WINDOWS8 - #endif +# define NUT_PLATFORM_MS_WINDOWS8 +# endif /* UNIX */ /* Note that Apple OSX doesn't define __unix__ nor __unix; are they ashamed or something? */ #elif (defined __unix__ || defined __unix || defined NUT_PLATFORM_APPLE_MACH) - #include - #include +# include +# include /** UNIX */ - #define NUT_PLATFORM_UNIX +# define NUT_PLATFORM_UNIX - #if (defined _POSIX_VERSION) +# if (defined _POSIX_VERSION) /** POSIX (implies \ref NUT_PLATFORM_UNIX), expands to POSIX version */ - #define NUT_PLATFORM_POSIX _POSIX_VERSION - #endif +# define NUT_PLATFORM_POSIX _POSIX_VERSION +# endif - #if (defined __linux__) +# if (defined __linux__) /** Linux (implies \ref NUT_PLATFORM_UNIX) */ - #define NUT_PLATFORM_LINUX - #endif - #if (defined __sun && defined __SVR4) +# define NUT_PLATFORM_LINUX +# endif +# if (defined __sun && defined __SVR4) /** Solaris (implies \ref NUT_PLATFORM_UNIX) */ - #define NUT_PLATFORM_SOLARIS - #endif - #if (defined __hpux) +# define NUT_PLATFORM_SOLARIS +# endif +# if (defined __hpux) /** Hewlett-Packard HP-UX (implies \ref NUT_PLATFORM_UNIX) */ - #define NUT_PLATFORM_HPUX - #endif - #if (defined _AIX) +# define NUT_PLATFORM_HPUX + + /* Note: depending on CPU arch and OS version, library file + * name patterns here could have been "*.so" as well. + * E.g. per + * https://community.hpe.com/t5/operating-system-hp-ux/so-and-sl-files/td-p/3780528 + * *.sl are used in PA-RISC (11.11) + * *.so shared libraries are used in HP-UX 11.20 and upwards. + * Integrity (Itanium-based) HPUX can use *.sl as well, + * but it is not recommended, see ld(1) under -lx: + * https://web.archive.org/web/20090925153446/http://docs.hp.com/en/B2355-60103/ld.1.html + */ + /* FIXME: May want to detect better the CPU or OS version + * to decide the SOEXT here*/ +# ifndef SOEXT +# define SOEXT ".sl" +# endif +# endif +# if (defined _AIX) /** AIX (implies \ref NUT_PLATFORM_UNIX) */ - #define NUT_PLATFORM_AIX - #endif +# define NUT_PLATFORM_AIX +# endif /* Note that BSD is defined in sys/param.h */ - #if (defined BSD) +# if (defined BSD) /** BSD (implies \ref NUT_PLATFORM_UNIX) */ - #define NUT_PLATFORM_BSD +# define NUT_PLATFORM_BSD - #if (defined __DragonFly__) +# if (defined __DragonFly__) /** DragonFly (implies \ref NUT_PLATFORM_UNIX, \ref NUT_PLATFORM_BSD) */ - #define NUT_PLATFORM_DRAGONFLY - #elif (defined __FreeBSD__) +# define NUT_PLATFORM_DRAGONFLY +# elif (defined __FreeBSD__) /** FreeBSD (implies \ref NUT_PLATFORM_UNIX, \ref NUT_PLATFORM_BSD) */ - #define NUT_PLATFORM_FREEBSD - #elif (defined __OpenBSD__) +# define NUT_PLATFORM_FREEBSD +# elif (defined __OpenBSD__) /** OpenBSD (implies \ref NUT_PLATFORM_UNIX, \ref NUT_PLATFORM_BSD) */ - #define NUT_PLATFORM_OPENBSD - #elif (defined __NetBSD__) +# define NUT_PLATFORM_OPENBSD +# elif (defined __NetBSD__) /** NetBSD (implies \ref NUT_PLATFORM_UNIX, \ref NUT_PLATFORM_BSD) */ - #define NUT_PLATFORM_NETBSD - #endif - #endif +# define NUT_PLATFORM_NETBSD +# endif +# endif +#endif + +/* not WIN32, not MACOS, not HPUX... */ +#ifndef SOEXT +# define SOEXT ".so" #endif #endif /* NUT_PLATFORM_H_SEEN */ diff --git a/include/nut_stdint.h b/include/nut_stdint.h index 5f173191ae..9d64a061b4 100644 --- a/include/nut_stdint.h +++ b/include/nut_stdint.h @@ -21,7 +21,13 @@ #ifndef NUT_STDINT_H_SEEN #define NUT_STDINT_H_SEEN 1 -#include "config.h" +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif #ifndef __STDC_LIMIT_MACROS #define __STDC_LIMIT_MACROS 1 diff --git a/include/nutconf.hpp b/include/nutconf.hpp index 086db17e23..02c50f271d 100644 --- a/include/nutconf.hpp +++ b/include/nutconf.hpp @@ -3,6 +3,7 @@ Copyright (C) 2012 Emilien Kia + 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -34,6 +35,7 @@ #include #include #include +#include /* See include/common.h for details behind this */ #ifndef NUT_UNUSED_VARIABLE @@ -61,6 +63,14 @@ class Settable protected: Type _value; bool _set; + std::string errMsg_ENOTSET()const { + static const std::string msg = + "Can not retrieve a Settable value of " + "an instance that was not assigned yet " + "(or was last known cleared)"; + return msg; + } + public: Settable():_set(false){} Settable(const Settable& val):_value(val._value), _set(val._set){} @@ -74,11 +84,43 @@ class Settable bool set()const{return _set;} void clear(){_set = false;} - operator const Type&()const{return _value;} - operator Type&(){return _value;} + operator const Type&()const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (!set()) + throw std::invalid_argument(errMsg_ENOTSET()); + return _value; + } + operator Type&() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (!set()) + throw std::invalid_argument(errMsg_ENOTSET()); + return _value; + } - const Type& operator *()const{return _value;} - Type& operator *(){return _value;} + const Type& operator *()const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (!set()) + throw std::invalid_argument(errMsg_ENOTSET()); + return _value; + } + Type& operator *() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (!set()) + throw std::invalid_argument(errMsg_ENOTSET()); + return _value; + } Settable& operator=(const Type& val){_value = val; _set = true; return *this;} @@ -142,6 +184,417 @@ class Serialisable }; // end of class Serialisable +/** + * \brief Mix a boolean (yes/no) or wider integer range + */ +class BoolInt +{ +private: + Settable b; + Settable i; + +public: + /** If set, its value specifies if we want i==0/1 to mean false/true + * in value-equality and type-cast operators for int and bool types. + * NOTE: If true, assignment from "0" and "1" strings sets the int + * not bool stored value representation! + * NOTE: Survives the clear() call which applies to stored value, + * but would be reset by assignment from another BoolInt object. + */ + Settable bool01; + + /** Leave all contents un-set */ + BoolInt() {} + + BoolInt(const bool val) { + *this = val; + } + BoolInt(const bool val, bool newBool01) { + this->bool01 = newBool01; + *this = val; + } + + BoolInt(const int val) { + *this = val; + } + BoolInt(const int val, bool newBool01) { + this->bool01 = newBool01; + *this = val; + } + + BoolInt(const char* val) { + *this = val; + } + BoolInt(const char* val, bool newBool01) { + this->bool01 = newBool01; + *this = val; + } + + BoolInt(const std::string &val) { + *this = val; + } + BoolInt(const std::string &val, bool newBool01) { + this->bool01 = newBool01; + *this = val; + } + + BoolInt(const BoolInt& other) { + *this = other; + } + BoolInt(const BoolInt& other, bool newBool01) { + this->bool01 = newBool01; + *this = other; + this->bool01 = newBool01; + } + + inline void clear() + { + i.clear(); + b.clear(); + } + + inline BoolInt& operator=(const BoolInt& other) + { + clear(); + bool01.clear(); + + if (other.b.set()) b = other.b; + if (other.i.set()) i = other.i; + + if (other.bool01.set()) + bool01 = other.bool01; + + return *this; + } + + inline BoolInt& operator=(BoolInt&& other) + { + clear(); + bool01.clear(); + + if (other.b.set()) b = other.b; + if (other.i.set()) i = other.i; + + if (other.bool01.set()) + bool01 = other.bool01; + + return *this; + } + + inline BoolInt& operator=(int val) + { + clear(); + i = val; + return *this; + } + + inline BoolInt& operator=(bool val) + { + clear(); + b = val; + return *this; + } + + inline BoolInt& operator=(const char* s) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (!s) + throw std::invalid_argument( + "BoolInt value from string is is not supported"); + + std::string src(s); + return (*this = src); + } + + inline BoolInt& operator=(std::string src) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + static const Settable b0(false); + static const Settable b1(true); + + // NOTE: Not a pointer, is not null at least + if (src.empty()) + throw std::invalid_argument( + "BoolInt value from string is is not supported"); + + clear(); + + if ("false" == src) { b = b0; return *this; } + if ("off" == src) { b = b0; return *this; } + if ("0" == src) { + if (bool01.set() && bool01 == false) { + i = 0; + } else { + b = b0; + } + return *this; + } + if ("no" == src) { b = b0; return *this; } + + if ("true" == src) { b = b1; return *this; } + if ("on" == src) { b = b1; return *this; } + if ("1" == src) { + if (bool01.set() && bool01 == false) { + i = 1; + } else { + b = b1; + } + return *this; + } + if ("yes" == src) { b = b1; return *this; } + if ("ok" == src) { b = b1; return *this; } + + std::stringstream ss(src); + int result; + if (ss >> result && ss.rdbuf()->in_avail() == 0) { + // Conversion succeeded and all chars were read + // (e.g. not a decimal number) + i = result; +#ifdef DEBUG + std::cerr << "BoolInt assigned from '" << src + << "': got int '" << result << "'" + << " stream empty? " << ss.rdbuf()->in_avail() + << std::endl; +#endif + return *this; + } + + throw std::invalid_argument("BoolInt value from '" + src + + "' string not understood as bool nor int"); + } + + inline BoolInt& operator<<(bool other) + { + *this = other; + return *this; + } + + inline BoolInt& operator<<(int other) + { + *this = other; + return *this; + } + + inline BoolInt& operator<<(std::string other) + { + *this = other; + return *this; + } + + inline BoolInt& operator<<(const char* other) + { + *this = other; + return *this; + } + + inline bool operator==(const BoolInt& other)const + { + // Either direct values are set and then equal; optionally + // else numeric values of int and bool are cross-equal. + if (b.set() && other.b.set()) return (b == other.b); + if (i.set() && other.i.set()) return (i == other.i); + + if ((bool01.set() && bool01 == true) + || (!bool01.set() && other.bool01.set() && other.bool01 == true) + ) { + // false if at least one object has neither i nor b + // values "set()", or if their numeric values do not + // match up as 0 or 1 exactly vs. boolean values. + if (i.set() && other.b.set()) + return ( (other.b && i == 1) || (!other.b && i == 0) ); + if (b.set() && other.i.set()) + return ( (b && other.i == 1) || (!b && other.i == 0) ); + } + + return false; + } + + inline bool operator==(const bool other)const + { + if (b.set()) return (b == other); + if (bool01.set() && bool01 == true) { + if (i.set()) + return ((other && i == 1) || (!other && i == 0)); + } + return false; + } + + inline bool operator==(const int other)const + { + if (i.set()) return (i == other); + if (bool01.set() && bool01 == true) { + if (b.set()) + return ((b && other == 1) || (!b && other == 0)); + } + return false; + } + + inline bool operator==(const std::string other)const + { + BoolInt tmp; + if (bool01.set()) + tmp.bool01 = bool01; + tmp = other; + return (*this == tmp); + } + + inline bool operator==(const char* s)const + { + if (!s) + return false; + + std::string src(s); + return (*this == src); + } + + inline bool set()const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (i.set() && b.set()) + throw std::invalid_argument( + "BoolInt value somehow got both bool and int values set"); + + return (i.set() || b.set()); + } + + operator int() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (i.set()) return i; + if (bool01.set() && bool01 == true) { + if (b.set()) { + /** Cause use of operator to avoid warnings like + * "may be used uninitialized in this function" + */ + if (b == true) return 1; + return 0; + } + } else { + throw std::invalid_argument( + "BoolInt value not set to int"); + } + + throw std::invalid_argument( + "BoolInt value not set, neither to bool nor to int"); + } + + operator bool() +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (b.set()) return b; + if (bool01.set() && bool01 == true) { + if (i.set()) { + if (i == 0) return false; + if (i == 1) return true; + } + } else { + throw std::invalid_argument( + "BoolInt value not set to bool"); + } + + throw std::invalid_argument( + "BoolInt value not set, neither to bool nor to int"); + } + + inline std::string toString()const +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + if (b.set()) { + if (b == true) return "yes"; + return "no"; + } + + if (i.set()) { + if (bool01.set() && bool01 == true) { + if (i == 0) return "no"; + if (i == 1) return "yes"; + } + + std::ostringstream ss; + ss << i; + return ss.str(); + } + + throw std::invalid_argument( + "BoolInt value not set, neither to bool nor to int"); + } + + // FIXME: `std::string s = bi;` just won't work + // but we can use `s = bi.toString()` or `cout << bi` + operator std::string()const { + return this->toString(); + } + + operator std::string&()const { + return *(new std::string(this->operator std::string())); + } +}; + +std::ostream& operator << (std::ostream &os, const BoolInt &bi); +inline std::ostream& operator << (std::ostream &os, const BoolInt &bi) { + return (os << bi.toString()); +} + + +/** + * \brief Certificate Identification structure for NUT + * + * Contains a certificate name and database password + */ +struct CertIdent +{ + Settable certName, certDbPass; + + inline bool operator==(const CertIdent& ident)const + { + return certName == ident.certName && certDbPass == ident.certDbPass; + } + + inline bool set()const + { + return certName.set() && certDbPass.set(); + } +}; + + +/** + * \brief Certificate protected host structure for NUT + * + * Contains a host name, certificate name and option flags + */ +struct CertHost +{ + Settable host, certName; + nut::BoolInt certVerify, forceSsl; + + inline bool operator==(const CertHost& other)const + { + return certName == other.certName + && host == other.host + && certVerify == other.certVerify + && forceSsl == other.forceSsl; + } + + inline bool set()const + { + return certName.set() && host.set() + && certVerify.set() && forceSsl.set(); + } +}; + + /** * NUT config parser. */ @@ -519,6 +972,175 @@ class GenericConfiguration : public BaseConfiguration, public Serialisable setStr("", entry, value); } + /** + * \brief Configuration flag getter + * + * False is returned if the section or entry doesn't exist. + * If a flag exists in configuration (any value is ignored), + * it is effectively True. + * + * \param section Section name + * \param entry Entry name + * + * \return Configuration parameter as boolean + */ + bool getFlag( + const std::string & section, + const std::string & entry) const; + + /** + * \brief Global scope configuration flag getter + * + * False is returned if the entry doesn't exist. + * If a flag exists in configuration (any value is ignored), + * it is effectively True. + * + * \param entry Entry name + * + * \return Configuration parameter as boolean + */ + inline bool getFlag(const std::string & entry) const + { + return getFlag("", entry); + } + + /** + * \brief Configuration flag setter (mentioned == true) + * + * Note: to unset a flag, we just use remove() method. + * + * \param section Section name + * \param entry Entry name + */ + void setFlag( + const std::string & section, + const std::string & entry, + bool val = true); + + /** + * \brief Global scope configuration flag setter (mentioned == true) + * + * Note: to unset a flag, we just use remove() method. + * + * \param entry Entry name + */ + inline void setFlag( + const std::string & entry, + bool val = true) + { + setFlag("", entry, val); + } + + /** + * \brief Configuration boolean option getter + * + * Value depends on original string representation of a setting. + * + * \param section Section name + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as boolean (or the default if not defined) + */ + bool getBool( + const std::string & section, + const std::string & entry, + bool val = false) const; + + /** + * \brief Configuration boolean option getter + * + * Value depends on original string representation of a setting. + * + * \param section Section name + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as boolean (or the default if not defined) + */ + // Avoid error: implicit conversion turns string literal + // into bool: 'const char[7]' to 'bool' + bool getBool( + const std::string & section, + const char * entry, + bool val = false) const + { + return getBool(section, std::string(entry), val); + } + + /** + * \brief Global scope configuration boolean option getter + * + * Value depends on original string representation of a setting. + * + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as boolean (or the default if not defined) + */ + inline bool getBool(const std::string & entry, bool val = false) const + { + return getBool("", entry, val); + } + + /** + * \brief Configuration boolean option setter + * + * \param section Section name + * \param entry Entry name + * \param val Default value + */ + inline void setBool( + const std::string & section, + const std::string & entry, + bool val = true) + { + setStr(section, entry, bool2str(val)); + } + + /** + * \brief Global scope configuration boolean option setter + * + * \param entry Entry name + * \param val Default value + */ + inline void setBool( + const std::string & entry, + bool val = true) + { + setBool("", entry, val); + } + + /** + * \brief Configuration boolean option setter from a string + * + * \param section Section name + * \param entry Entry name + * \param val Default value + */ + inline void setBool( + const std::string & section, + const std::string & entry, + const std::string & val = "true") + { + // Normalize: + bool b = str2bool(val); + setStr(section, entry, bool2str(b)); + } + + /** + * \brief Global scope configuration boolean option setter from a string + * + * \param entry Entry name + * \param val Default value + */ + inline void setBool( + const std::string & entry, + const std::string & val = "true") + { + setBool("", entry, val); + } + /** * \brief Configuration number getter * @@ -571,6 +1193,110 @@ class GenericConfiguration : public BaseConfiguration, public Serialisable setInt("", entry, val); } + /** + * \brief Configuration number getter (hex value even if without leading "0x") + * + * \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 getIntHex( + const std::string & section, + const std::string & entry, + long long int val = 0) const; + + /** + * \brief Global scope configuration number getter (hex value even if without leading "0x") + * + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as number (or the default if not defined) + */ + inline long long int getIntHex(const std::string & entry, long long int val = 0) const + { + return getIntHex("", entry, val); + } + + /** + * \brief Configuration number setter (hex value even if without leading "0x") + * + * \param section Section name + * \param entry Entry name + * \param val Default value + */ + void setIntHex( + const std::string & section, + const std::string & entry, + long long int val); + + /** + * \brief Global scope configuration number setter (hex value even if without leading "0x") + * + * \param entry Entry name + * \param val Default value + */ + inline void setIntHex( + const std::string & entry, + long long int val) + { + setIntHex("", entry, val); + } + + /** + * \brief Configuration floating-point 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) + */ + double getDouble( + const std::string & section, + const std::string & entry, + double val = 0.0) const; + + /** + * \brief Global scope configuration floating-point number getter + * + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as number (or the default if not defined) + */ + inline double getDouble(const std::string & entry, double val = 0.0) const + { + return getDouble("", entry, val); + } + + /** + * \brief Configuration floating-point number setter + * + * \param section Section name + * \param entry Entry name + * \param val Default value + */ + void setDouble( + const std::string & section, + const std::string & entry, + double val); + + /** + * \brief Global scope configuration floating-point number setter + * + * \param entry Entry name + * \param val Default value + */ + inline void setDouble( + const std::string & entry, + double val) + { + setDouble("", entry, val); + } + /** * \brief Cast numeric type with range check * @@ -607,13 +1333,84 @@ class GenericConfiguration : public BaseConfiguration, public Serialisable return static_cast(number); } + /** + * \brief Configuration mixed boolean/int option getter + * + * Value depends on original string representation of a setting. + * + * \param section Section name + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as BoolInt type for original + * values which have a boolean or integer-numeric meaning + * (or the default if not defined) + */ + nut::BoolInt getBoolInt( + const std::string & section, + const std::string & entry, + nut::BoolInt val = false) const; + + /** + * \brief Global scope configuration mixed boolean/int option getter + * + * Value depends on original string representation of a setting. + * + * \param entry Entry name + * \param val Default value + * + * \return Configuration parameter as BoolInt type for original + * values which have a boolean or integer-numeric meaning + * (or the default if not defined) + */ + inline nut::BoolInt getBoolInt(const std::string & entry, nut::BoolInt val = false) const + { + return getBoolInt("", entry, val); + } + + /** + * \brief Configuration mixed boolean/int option setter + * + * Input value types are auto-converted through BoolInt + * type for sanity checks (e.g. throw exceptions for + * invalid string contents) and are stored as strings + * internally. + * + * \param section Section name + * \param entry Entry name + * \param val Default value + */ + inline void setBoolInt( + const std::string & section, + const std::string & entry, + nut::BoolInt val = true) +#if (defined __cplusplus) && (__cplusplus < 201100) + throw(std::invalid_argument) +#endif + { + setStr(section, entry, val); + } + + /** + * \brief Global scope configuration mixed boolean/int option setter + * + * \param entry Entry name + * \param val Default value + */ + inline void setBoolInt( + const std::string & entry, + nut::BoolInt val = true) + { + setBoolInt("", entry, val); + } + /** * \brief Resolve string as Boolean value * * \param str String * * \retval true IFF the string expresses a known true value - * \retval false otherwise + * \retval false otherwise (no errors emitted for bogus inputs) */ static bool str2bool(const std::string & str); @@ -636,8 +1433,17 @@ class UpsmonConfiguration : public Serialisable UpsmonConfiguration(); void parseFromString(const std::string& str); + Settable debugMin, pollFailLogThrottleMax; + Settable offDuration, oblbDuration; Settable runAsUser, shutdownCmd, notifyCmd, powerDownFlag; - Settable minSupplies, poolFreq, poolFreqAlert, hotSync; + /* yes|no (boolean) or a delay */ + Settable shutdownExit; + /* practically boolean, but in 0|1 written form (bool01 fiddling) */ + Settable certVerify, forceSsl; + Settable certPath; + CertIdent certIdent; + std::list certHosts; + Settable minSupplies, pollFreq, pollFreqAlert, hostSync; Settable deadTime, rbWarnTime, noCommWarnTime, finalDelay; enum NotifyFlag { @@ -648,7 +1454,7 @@ class UpsmonConfiguration : public Serialisable }; enum NotifyType { - NOTIFY_ONLINE, + NOTIFY_ONLINE = 0, NOTIFY_ONBATT, NOTIFY_LOWBATT, NOTIFY_FSD, @@ -658,6 +1464,16 @@ class UpsmonConfiguration : public Serialisable NOTIFY_REPLBATT, NOTIFY_NOCOMM, NOTIFY_NOPARENT, + NOTIFY_CAL, + NOTIFY_NOTCAL, + NOTIFY_OFF, + NOTIFY_NOTOFF, + NOTIFY_BYPASS, + NOTIFY_NOTBYPASS, + + NOTIFY_SUSPEND_STARTING = 30, + NOTIFY_SUSPEND_FINISHED, + NOTIFY_TYPE_MAX }; @@ -672,7 +1488,7 @@ class UpsmonConfiguration : public Serialisable uint16_t port; unsigned int powerValue; std::string username, password; - bool isMaster; + bool isPrimary; }; std::list monitors; @@ -722,6 +1538,11 @@ class NutConfiguration: public Serialisable Settable mode; + Settable allowNoDevice, allowNotAllListeners, poweroffQuiet; + Settable upsdOptions, upsmonOptions; + Settable poweroffWait; + Settable debugLevel; + static NutMode NutModeFromString(const std::string& str); /** Serialisable interface implementation \{ */ @@ -755,8 +1576,10 @@ class UpsdConfiguration : public Serialisable UpsdConfiguration(); void parseFromString(const std::string& str); - Settable maxAge, maxConn; - Settable statePath, certFile; + Settable debugMin; + Settable maxAge, maxConn, trackingDelay, certRequestLevel; + Settable statePath, certFile, certPath; + Settable allowNoDevice, allowNotAllListeners, disableWeakSsl; struct Listen { @@ -770,6 +1593,8 @@ class UpsdConfiguration : public Serialisable }; std::list listens; + CertIdent certIdent; + /** Serialisable interface implementation \{ */ bool parseFrom(NutStream & istream) override; bool writeTo(NutStream & ostream) const override; @@ -800,22 +1625,66 @@ class UpsdConfigParser : public NutConfigParser /** UPS configuration */ class UpsConfiguration : public GenericConfiguration { + /* Note: key words for ups.conf are well collected from sources + * by augeas lens preparation scripts. Maintainers of this class + * can consume that information like this to see key words in + * context of their use in sources and documentation (omit the + * `continue` in grep of `nutconf.hpp` to see details of ALL + * keywords and not just those not yet covered by this class + * (e.g. to verify handling as str/int/bool/flag... types): + +:; ( cd scripts/augeas && python ./gen-nutupsconf-aug.py.in ) + +:; grep -E '[=|] "' scripts/augeas/nutupsconf.aug.in | awk '{print $NF}' | tr -d '"' \ + | while read O ; do echo "=== $O :" ; \ + grep -w '"'"$O"'"' ./include/nutconf.hpp && continue ; \ + { cd ./docs/man/ && grep -A10 -w "$O" *.txt || echo '!!! UNDOCUMENTED !!!' ; } ; \ + echo "-----"; { cd ./drivers && grep -A10 -w '"'"$O"'"' *.{c,h} || echo '!!! NOT USED IN CODE !!!' ; } ; \ + echo "-----"; echo "" ; done | less + + * Arrange found new keywords into two columns (first would be + * "C names" camel-cased and expanded as deemed fit) and generate + * lines for code blocks below as e.g.: +:; while read C O ; do \ + printf '\tinline std::string get%-45sconst { return getStr(ups, "%s"); }\n' \ + "$C"'(const std::string & ups)' "$O"; \ + printf '\tinline void set%-75s{ setStr(ups, "%-22s val); }\n' \ + "$C"'(const std::string & ups, const std::string & val)' "$O"'",' ; \ + done < nutupsconf-newnames.bool | sort + + */ + 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 getGroup() const { return getStr("group"); } + inline std::string getSynchronous() const { return getStr("synchronous"); } inline std::string getUser() const { return getStr("user"); } + // Flag - if exists then "true" + inline bool getNoWait() const { return getFlag("nowait"); } + + inline long long int getDebugMin() const { return getInt("debug_min"); } + inline long long int getMaxRetry() const { return getInt("maxretry"); } inline long long int getMaxStartDelay() const { return getInt("maxstartdelay"); } - inline long long int getPollInterval() const { return getInt("pollinterval", 5); } // TODO: check the default + inline long long int getPollInterval() const { return getInt("pollinterval", 5); } // TODO: check the default + inline long long int getRetryDelay() const { return getInt("retrydelay"); } inline void setChroot(const std::string & path) { setStr("chroot", path); } inline void setDriverPath(const std::string & path) { setStr("driverpath", path); } + inline void setGroup(const std::string & group) { setStr("group", group); } + inline void setSynchronous(const std::string & val) { setStr("synchronous", val); } inline void setUser(const std::string & user) { setStr("user", user); } + inline void setNoWait(bool val = true) { setFlag("nowait", val); } + + inline void setDebugMin(long long int num) { setInt("debug_min", num); } + inline void setMaxRetry(long long int num) { setInt("maxretry", num); } inline void setMaxStartDelay(long long int delay) { setInt("maxstartdelay", delay); } inline void setPollInterval(long long int interval) { setInt("pollinterval", interval); } + inline void setRetryDelay(long long int delay) { setInt("retrydelay", delay); } /** \} */ @@ -827,16 +1696,45 @@ class UpsConfiguration : public GenericConfiguration setStr(ups, key, val); } + /** PUZZLE: What to do about "default.*" and "override.*" + * settings that apply to anything (vars!) that follows? + * Maybe nest the UpsConfiguration objects, and so query + * e.g. upscfg.default.getBatteryNominalVoltage() ? + * Or just keep that info in ConfigParamList per section + * and wrap free-style queries? */ + inline std::string getDefaultStr(const std::string & ups, const std::string & key) const { return getStr(ups, "default." + key); } + inline long long int getDefaultInt(const std::string & ups, const std::string & key) const { return getInt(ups, "default." + key); } + inline long long int getDefaultIntHex(const std::string & ups, const std::string & key) const { return getIntHex(ups, "default." + key); } + inline bool getDefaultFlag(const std::string & ups, const std::string & key) const { return getFlag(ups, "default." + key); } + inline bool getDefaultBool(const std::string & ups, const std::string & key) const { return getBool(ups, "default." + key); } + inline nut::BoolInt getDefaultBoolInt(const std::string & ups, const std::string & key) const { return getBoolInt(ups, "default." + key); } + inline double getDefaultDouble(const std::string & ups, const std::string & key) const { return getDouble(ups, "default." + key); } + + inline std::string getOverrideStr(const std::string & ups, const std::string & key) const { return getStr(ups, "override." + key); } + inline long long int getOverrideInt(const std::string & ups, const std::string & key) const { return getInt(ups, "override." + key); } + inline long long int getOverrideIntHex(const std::string & ups, const std::string & key) const { return getIntHex(ups, "override." + key); } + inline bool getOverrideFlag(const std::string & ups, const std::string & key) const { return getFlag(ups, "override." + key); } + inline bool getOverrideBool(const std::string & ups, const std::string & key) const { return getBool(ups, "override." + key); } + inline nut::BoolInt getOverrideBoolInt(const std::string & ups, const std::string & key) const { return getBoolInt(ups, "override." + key); } + inline double getOverrideDouble(const std::string & ups, const std::string & key) const { return getDouble(ups, "override." + key); } + + inline void setDefaultStr(const std::string & ups, const std::string & key, const std::string & val) { setStr(ups, "default." + key, val); } + inline void setDefaultInt(const std::string & ups, const std::string & key, long long int val) { setInt(ups, "default." + key, val); } + inline void setDefaultIntHex(const std::string & ups, const std::string & key, long long int val) { setIntHex(ups, "default." + key, val); } + inline void setDefaultFlag(const std::string & ups, const std::string & key, bool val = true) { setFlag(ups, "default." + key, val); } + inline void setDefaultBool(const std::string & ups, const std::string & key, bool val) { setBool(ups, "default." + key, val); } + inline void setDefaultBoolInt(const std::string & ups, const std::string & key, nut::BoolInt val) { setBoolInt(ups, "default." + key, val); } + inline void setDefaultDouble(const std::string & ups, const std::string & key, double val) { setDouble(ups, "default." + key, val); } + + inline void setOverrideStr(const std::string & ups, const std::string & key, const std::string & val) { setStr(ups, "override." + key, val); } + inline void setOverrideInt(const std::string & ups, const std::string & key, long long int val) { setInt(ups, "override." + key, val); } + inline void setOverrideIntHex(const std::string & ups, const std::string & key, long long int val) { setIntHex(ups, "override." + key, val); } + inline void setOverrideFlag(const std::string & ups, const std::string & key, bool val = true) { setFlag(ups, "override." + key, val); } + inline void setOverrideBool(const std::string & ups, const std::string & key, bool val) { setBool(ups, "override." + key, val); } + inline void setOverrideBoolInt(const std::string & ups, const std::string & key, nut::BoolInt val) { setBoolInt(ups, "override." + key, val); } + inline void setOverrideDouble(const std::string & ups, const std::string & key, double val) { setDouble(ups, "override." + 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"); } @@ -844,17 +1742,33 @@ class UpsConfiguration : public GenericConfiguration 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 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 getFRUID(const std::string & ups) const { return getStr(ups, "fruid"); } + inline std::string getGenericGPIO_Rules(const std::string & ups) const { return getStr(ups, "rules"); } + inline std::string getGenericUPS_BYPASS(const std::string & ups) const { return getStr(ups, "BYPASS"); } + inline std::string getGenericUPS_CP(const std::string & ups) const { return getStr(ups, "CP"); } + inline std::string getGenericUPS_LB(const std::string & ups) const { return getStr(ups, "LB"); } + inline std::string getGenericUPS_OL(const std::string & ups) const { return getStr(ups, "OL"); } + inline std::string getGenericUPS_RB(const std::string & ups) const { return getStr(ups, "RB"); } + inline std::string getGenericUPS_SD(const std::string & ups) const { return getStr(ups, "SD"); } + inline std::string getGroup(const std::string & ups) const { return getStr(ups, "group"); } + inline std::string getID(const std::string & ups) const { return getStr(ups, "ID"); } 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 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 getModbus_DeviceMfr(const std::string & ups) const { return getStr(ups, "device_mfr"); } + inline std::string getModbus_DeviceModel(const std::string & ups) const { return getStr(ups, "device_model"); } + inline std::string getModbus_Parity(const std::string & ups) const { return getStr(ups, "parity"); } + inline std::string getModbus_PortType(const std::string & ups) const { return getStr(ups, "porttype"); } + inline std::string getModbus_SerParity(const std::string & ups) const { return getStr(ups, "ser_parity"); } 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"); } @@ -873,87 +1787,200 @@ class UpsConfiguration : public GenericConfiguration 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 getSynchronous(const std::string & ups) const { return getStr(ups, "synchronous"); } + inline std::string getTtyMode(const std::string & ups) const { return getStr(ups, "ttymode"); } 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 getUpsId(const std::string & ups) const { return getStr(ups, "upsid"); } + inline std::string getUsbBusPort(const std::string & ups) const { return getStr(ups, "busport"); } + inline std::string getUsbDevice(const std::string & ups) const { return getStr(ups, "device"); } inline std::string getUSD(const std::string & ups) const { return getStr(ups, "usd"); } + inline std::string getUser(const std::string & ups) const { return getStr(ups, "user"); } 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 getWorkRangeType(const std::string & ups) const { return getStr(ups, "work_range_type"); } inline std::string getWUGrace(const std::string & ups) const { return getStr(ups, "wugrace"); } + // Items below are "unused" - mostly set in + // drivers/nutdrv_qx_masterguard.c + inline std::string getFault1(const std::string & ups) const { return getStr(ups, "fault_1"); } + inline std::string getFault2(const std::string & ups) const { return getStr(ups, "fault_2"); } + inline std::string getFault3(const std::string & ups) const { return getStr(ups, "fault_3"); } + inline std::string getFault4(const std::string & ups) const { return getStr(ups, "fault_4"); } + inline std::string getFault5(const std::string & ups) const { return getStr(ups, "fault_5"); } + inline std::string getInputFaultVoltage(const std::string & ups) const { return getStr(ups, "input_fault_voltage"); } + inline std::string getNominalCellVoltage(const std::string & ups) const { return getStr(ups, "nominal_cell_voltage"); } + inline std::string getNumberOfBatteryCells(const std::string & ups)const { return getStr(ups, "number_of_battery_cells"); } + inline std::string getOutputVoltages(const std::string & ups) const { return getStr(ups, "output_voltages"); } + inline std::string getRechargeTime(const std::string & ups) const { return getStr(ups, "recharge_time"); } + inline std::string getRuntimeFull(const std::string & ups) const { return getStr(ups, "runtime_full"); } + inline std::string getRuntimeHalf(const std::string & ups) const { return getStr(ups, "runtime_half"); } + inline std::string getSeries(const std::string & ups) const { return getStr(ups, "series"); } + + // Items below are essentially booleans (expected values + // are "enabled/disabled") -- refactoring planned per + // https://github.com/networkupstools/nut/issues/2421 + inline std::string getAdvancedEcoMode(const std::string & ups) const { return getStr(ups, "advanced_eco_mode"); } + inline std::string getAlarmControl(const std::string & ups) const { return getStr(ups, "alarm_control"); } + inline std::string getBatteryAlarm(const std::string & ups) const { return getStr(ups, "battery_alarm"); } + inline std::string getBatteryOpenStatusCheck(const std::string & ups)const { return getStr(ups, "battery_open_status_check"); } + inline std::string getBypassAlarm(const std::string & ups) const { return getStr(ups, "bypass_alarm"); } + inline std::string getBypassForbidding(const std::string & ups) const { return getStr(ups, "bypass_forbidding"); } + inline std::string getBypassWhenOff(const std::string & ups) const { return getStr(ups, "bypass_when_off"); } + inline std::string getConstantPhaseAngle(const std::string & ups) const { return getStr(ups, "constant_phase_angle"); } + inline std::string getConverterMode(const std::string & ups) const { return getStr(ups, "converter_mode"); } + inline std::string getEcoMode(const std::string & ups) const { return getStr(ups, "eco_mode"); } + inline std::string getLimitedRuntimeOnBattery(const std::string & ups)const { return getStr(ups, "limited_runtime_on_battery"); } + inline std::string getSiteFaultDetection(const std::string & ups) const { return getStr(ups, "site_fault_detection"); } + + inline long long int getAdvOrder(const std::string & ups) const { return getInt(ups, "advorder"); } // CHECKME + inline long long int getAsem_HB(const std::string & ups) const { return getInt(ups, "hb"); } + inline long long int getAsem_LB(const std::string & ups) const { return getInt(ups, "lb"); } + inline long long int getBatteryNumber(const std::string & ups) const { return getInt(ups, "battery_number"); } + inline long long int getBatteryPercentage(const std::string & ups) const { return getInt(ups, "batteryPercentage"); } // 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 getDebugMin(const std::string & ups) const { return getInt(ups, "debug_min"); } + 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 getI2C_address(const std::string & ups) const { return getInt(ups, "i2c_address"); } + 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 getInterruptSize(const std::string & ups) const { return getInt(ups, "interruptsize"); } + 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 getMaxPollsWithoutData(const std::string & ups) const { return getInt(ups, "max_polls_without_data"); } + inline long long int getMaxStartDelay(const std::string & ups) const { return getInt(ups, "maxstartdelay"); } + 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 getModbus_ByteTimeoutSec(const std::string & ups) const { return getInt(ups, "mod_byte_to_s"); } + inline long long int getModbus_ByteTimeoutUsec(const std::string & ups) const { return getInt(ups, "mod_byte_to_us"); } + inline long long int getModbus_CHRG_addr(const std::string & ups) const { return getInt(ups, "CHRG_addr"); } + inline long long int getModbus_CHRG_noro(const std::string & ups) const { return getInt(ups, "CHRG_noro"); } + inline long long int getModbus_CHRG_regtype(const std::string & ups) const { return getInt(ups, "CHRG_regtype"); } + inline long long int getModbus_DISCHRG_addr(const std::string & ups) const { return getInt(ups, "DISCHRG_addr"); } + inline long long int getModbus_DISCHRG_noro(const std::string & ups) const { return getInt(ups, "DISCHRG_noro"); } + inline long long int getModbus_DISCHRG_regtype(const std::string & ups) const { return getInt(ups, "DISCHRG_regtype"); } + inline long long int getModbus_DataBits(const std::string & ups) const { return getInt(ups, "databits"); } + inline long long int getModbus_DeviceSlaveId(const std::string & ups) const { return getInt(ups, "dev_slave_id"); } + inline long long int getModbus_FSD_addr(const std::string & ups) const { return getInt(ups, "FSD_addr"); } + inline long long int getModbus_FSD_noro(const std::string & ups) const { return getInt(ups, "FSD_noro"); } + inline long long int getModbus_FSD_pulse_duration(const std::string & ups) const { return getInt(ups, "FSD_pulse_duration"); } + inline long long int getModbus_FSD_regtype(const std::string & ups) const { return getInt(ups, "FSD_regtype"); } + inline long long int getModbus_HB_addr(const std::string & ups) const { return getInt(ups, "HB_addr"); } + inline long long int getModbus_HB_noro(const std::string & ups) const { return getInt(ups, "HB_noro"); } + inline long long int getModbus_HB_regtype(const std::string & ups) const { return getInt(ups, "HB_regtype"); } + inline long long int getModbus_LB_addr(const std::string & ups) const { return getInt(ups, "LB_addr"); } + inline long long int getModbus_LB_noro(const std::string & ups) const { return getInt(ups, "LB_noro"); } + inline long long int getModbus_LB_regtype(const std::string & ups) const { return getInt(ups, "LB_regtype"); } + inline long long int getModbus_OB_addr(const std::string & ups) const { return getInt(ups, "OB_addr"); } + inline long long int getModbus_OB_noro(const std::string & ups) const { return getInt(ups, "OB_noro"); } + inline long long int getModbus_OB_regtype(const std::string & ups) const { return getInt(ups, "OB_regtype"); } + inline long long int getModbus_OL_addr(const std::string & ups) const { return getInt(ups, "OL_addr"); } + inline long long int getModbus_OL_noro(const std::string & ups) const { return getInt(ups, "OL_noro"); } + inline long long int getModbus_OL_regtype(const std::string & ups) const { return getInt(ups, "OL_regtype"); } + inline long long int getModbus_RB_addr(const std::string & ups) const { return getInt(ups, "RB_addr"); } + inline long long int getModbus_RB_noro(const std::string & ups) const { return getInt(ups, "RB_noro"); } + inline long long int getModbus_RB_regtype(const std::string & ups) const { return getInt(ups, "RB_regtype"); } + inline long long int getModbus_ResponseTimeoutMsec(const std::string & ups)const { return getInt(ups, "response_timeout_ms"); } + inline long long int getModbus_ResponseTimeoutSec(const std::string & ups) const { return getInt(ups, "mod_resp_to_s"); } + inline long long int getModbus_ResponseTimeoutUsec(const std::string & ups)const { return getInt(ups, "mod_resp_to_us"); } + inline long long int getModbus_RioSlaveId(const std::string & ups) const { return getInt(ups, "rio_slave_id"); } + inline long long int getModbus_SerBaudRate(const std::string & ups) const { return getInt(ups, "ser_baud_rate"); } + inline long long int getModbus_SerDataBit(const std::string & ups) const { return getInt(ups, "ser_data_bit"); } + inline long long int getModbus_SerStopBit(const std::string & ups) const { return getInt(ups, "ser_stop_bit"); } + inline long long int getModbus_SlaveId(const std::string & ups) const { return getInt(ups, "slaveid"); } + inline long long int getModbus_StopBits(const std::string & ups) const { return getInt(ups, "stopbits"); } + 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 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 getOndelay(const std::string & ups) const { return getInt(ups, "ondelay"); } // CHECKME + inline long long int getOnlineDischargeLogThrottleHovercharge(const std::string & ups)const { return getInt(ups, "onlinedischarge_log_throttle_hovercharge"); } + inline long long int getOnlineDischargeLogThrottleSec(const std::string & ups)const { return getInt(ups, "onlinedischarge_log_throttle_sec"); } + inline long long int getOutputPace(const std::string & ups) const { return getInt(ups, "output_pace"); } // CHECKME + inline long long int getOutputPhaseAngle(const std::string & ups) const { return getInt(ups, "output_phase_angle"); } + inline long long int getPinsShutdownMode(const std::string & ups) const { return getInt(ups, "pins_shutdown_mode"); } + 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 getSDOrder(const std::string & ups) const { return getInt(ups, "sdorder"); } // TODO: Is that a number? + inline long long int getSDtime(const std::string & ups) const { return getInt(ups, "sdtime"); } // CHECKME + inline long long int getSemistaticFreq(const std::string & ups) const { return getInt(ups, "semistaticfreq"); } + inline long long int getShutdownDelay(const std::string & ups) const { return getInt(ups, "shutdown_delay"); } // CHECKME + inline long long int getShutdownDuration(const std::string & ups) const { return getInt(ups, "shutdown_duration"); } + inline long long int getShutdownTimer(const std::string & ups) const { return getInt(ups, "shutdown_timer"); } + inline long long int getSlaveAddress(const std::string & ups) const { return getInt(ups, "slave_address"); } + inline long long int getSnmpRetries(const std::string & ups) const { return getInt(ups, "snmp_retries"); } + inline long long int getSnmpTimeout(const std::string & ups) const { return getInt(ups, "snmp_timeout"); } + 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 getWaitBeforeReconnect(const std::string & ups) const { return getInt(ups, "waitbeforereconnect"); } + + /** belkinunv: both a flag (wait for AC power) and value (also wait for charge level) */ + inline long long int getWait(const std::string & ups) const { return getInt(ups, "wait"); } + + /** May be a flag or a number; 0 is among valid values (default -1 for unset) */ + inline long long int getUsbSetAltInterface(const std::string & ups) const { return getInt(ups, "usb_set_altinterface", -1); } // CHECKME + + // NUT specifies these as "hexnum" values (optionally with prefixed 0x but hex anyway) + inline long long int getUsbConfigIndex(const std::string & ups) const { return getIntHex(ups, "usb_config_index"); } // CHECKME + inline long long int getUsbHidDescIndex(const std::string & ups) const { return getIntHex(ups, "usb_hid_desc_index"); } // CHECKME + inline long long int getUsbHidRepIndex(const std::string & ups) const { return getIntHex(ups, "usb_hid_rep_index"); } // CHECKME + inline long long int getUsbHidEndpointIn(const std::string & ups) const { return getIntHex(ups, "usb_hid_ep_in"); } // CHECKME + inline long long int getUsbHidEndpointOut(const std::string & ups) const { return getIntHex(ups, "usb_hid_ep_out"); } // CHECKME + + inline double getBatteryMax(const std::string & ups) const { return getDouble(ups, "battery_max"); } + inline double getBatteryMin(const std::string & ups) const { return getDouble(ups, "battery_min"); } + inline double getCSHackDelay(const std::string & ups) const { return getDouble(ups, "cshdelay"); } + inline double getMaxBypassFreq(const std::string & ups) const { return getDouble(ups, "max_bypass_freq"); } + inline double getMaxBypassVolt(const std::string & ups) const { return getDouble(ups, "max_bypass_volt"); } + inline double getMinBypassFreq(const std::string & ups) const { return getDouble(ups, "min_bypass_freq"); } + inline double getMinBypassVolt(const std::string & ups) const { return getDouble(ups, "min_bypass_volt"); } + + // Flag - if exists then "true" + inline bool getCancelShutdown(const std::string & ups) const { return getFlag(ups, "CS"); } + inline bool getDumbTerm(const std::string & ups) const { return getFlag(ups, "dumbterm"); } + inline bool getExplore(const std::string & ups) const { return getFlag(ups, "explore"); } + inline bool getFakeLowBatt(const std::string & ups) const { return getFlag(ups, "fake_lowbatt"); } + inline bool getFlash(const std::string & ups) const { return getFlag(ups, "flash"); } + inline bool getIgnoreLB(const std::string & ups) const { return getFlag(ups, "ignorelb"); } + inline bool getNoHang(const std::string & ups) const { return getFlag(ups, "nohang"); } + inline bool getNoRating(const std::string & ups) const { return getFlag(ups, "norating"); } + inline bool getNoTransferOIDs(const std::string & ups) const { return getFlag(ups, "notransferoids"); } + inline bool getNoVendor(const std::string & ups) const { return getFlag(ups, "novendor"); } + inline bool getNoWarnNoImp(const std::string & ups) const { return getFlag(ups, "nowarn_noimp"); } + inline bool getOldMAC(const std::string & ups) const { return getFlag(ups, "oldmac"); } + inline bool getPollOnly(const std::string & ups) const { return getFlag(ups, "pollonly"); } + inline bool getSilent(const std::string & ups) const { return getFlag(ups, "silent"); } + inline bool getStatusOnly(const std::string & ups) const { return getFlag(ups, "status_only"); } + inline bool getSubscribe(const std::string & ups) const { return getFlag(ups, "subscribe"); } + inline bool getUseCRLF(const std::string & ups) const { return getFlag(ups, "use_crlf"); } + inline bool getUsePreLF(const std::string & ups) const { return getFlag(ups, "use_pre_lf"); } + + inline bool getNolock(const std::string & ups) const { return getBool(ups, "nolock"); } + inline bool getCable(const std::string & ups) const { return getBool(ups, "cable"); } + inline bool getFullUpdate(const std::string & ups) const { return getBool(ups, "full_update"); } + inline bool getLangIDfix(const std::string & ups) const { return getBool(ups, "langid_fix"); } + inline bool getLoadOff(const std::string & ups) const { return getBool(ups, "load.off"); } + inline bool getLoadOn(const std::string & ups) const { return getBool(ups, "load.on"); } - 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); } @@ -961,17 +1988,32 @@ class UpsConfiguration : public GenericConfiguration 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 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 setFRUID(const std::string & ups, const std::string & fruid) { setStr(ups, "fruid", fruid); } + inline void setGenericGPIO_Rules(const std::string & ups, const std::string & val) { setStr(ups, "rules", val); } + inline void setGenericUPS_BYPASS(const std::string & ups, const std::string & bypass) { setStr(ups, "BYPASS", bypass); } + inline void setGenericUPS_CP(const std::string & ups, const std::string & cp) { setStr(ups, "CP", cp); } + inline void setGenericUPS_LB(const std::string & ups, const std::string & lb) { setStr(ups, "LB", lb); } + inline void setGenericUPS_OL(const std::string & ups, const std::string & ol) { setStr(ups, "OL", ol); } + inline void setGenericUPS_RB(const std::string & ups, const std::string & rb) { setStr(ups, "RB", rb); } + inline void setGenericUPS_SD(const std::string & ups, const std::string & sd) { setStr(ups, "SD", sd); } + inline void setGroup(const std::string & ups, const std::string & group) { setStr(ups, "group", group); } 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 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 setModbus_DeviceMfr(const std::string & ups, const std::string & val) { setStr(ups, "device_mfr", val); } + inline void setModbus_DeviceModel(const std::string & ups, const std::string & val) { setStr(ups, "device_model", val); } + inline void setModbus_Parity(const std::string & ups, const std::string & val) { setStr(ups, "parity", val); } + inline void setModbus_PortType(const std::string & ups, const std::string & val) { setStr(ups, "porttype", val); } + inline void setModbus_SerParity(const std::string & ups, const std::string & val) { setStr(ups, "ser_parity", val); } 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); } @@ -990,79 +2032,199 @@ class UpsConfiguration : public GenericConfiguration 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 setSynchronous(const std::string & ups, const std::string & synchronous) { setStr(ups, "synchronous", synchronous); } + inline void setTtyMode(const std::string & ups, const std::string & val) { setStr(ups, "ttymode", val); } 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 setUpsId(const std::string & ups, const std::string & val) { setStr(ups, "upsid", val); } + inline void setUsbBusPort(const std::string & ups, const std::string & val) { setStr(ups, "busport", val); } + inline void setUsbDevice(const std::string & ups, const std::string & val) { setStr(ups, "device", val); } 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 setUser(const std::string & ups, const std::string & user) { setStr(ups, "user", user); } 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 setWorkRangeType(const std::string & ups, const std::string & val) { setStr(ups, "work_range_type", val); } 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 setADVorder(const std::string & ups, long long int advorder) { setInt(ups, "advorder", advorder); } // CHECKME + inline void setAsem_HB(const std::string & ups, long long int val) { setInt(ups, "hb", val); } + inline void setAsem_LB(const std::string & ups, long long int val) { setInt(ups, "lb", val); } + inline void setBatteryNumber(const std::string & ups, long long int val) { setInt(ups, "battery_number", val); } + inline void setBatteryPercentage(const std::string & ups, long long int batt) { setInt(ups, "batteryPercentage", batt); } // 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 setDebugMin(const std::string & ups, long long int val) { setInt(ups, "debug_min", val); } + 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 setI2C_address(const std::string & ups, long long int val) { setInt(ups, "i2c_address", val); } + 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 setInterruptSize(const std::string & ups, long long int val) { setInt(ups, "interruptsize", val); } + 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 setMaxPollsWithoutData(const std::string & ups, long long int val) { setInt(ups, "max_polls_without_data", val); } + inline void setMaxStartDelay(const std::string & ups, long long int delay) { setInt(ups, "maxstartdelay", delay); } + 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 setModbus_ByteTimeoutSec(const std::string & ups, long long int val) { setInt(ups, "mod_byte_to_s", val); } + inline void setModbus_ByteTimeoutUsec(const std::string & ups, long long int val) { setInt(ups, "mod_byte_to_us", val); } + inline void setModbus_CHRG_addr(const std::string & ups, long long int val) { setInt(ups, "CHRG_addr", val); } + inline void setModbus_CHRG_noro(const std::string & ups, long long int val) { setInt(ups, "CHRG_noro", val); } + inline void setModbus_CHRG_regtype(const std::string & ups, long long int val) { setInt(ups, "CHRG_regtype", val); } + inline void setModbus_DISCHRG_addr(const std::string & ups, long long int val) { setInt(ups, "DISCHRG_addr", val); } + inline void setModbus_DISCHRG_noro(const std::string & ups, long long int val) { setInt(ups, "DISCHRG_noro", val); } + inline void setModbus_DISCHRG_regtype(const std::string & ups, long long int val) { setInt(ups, "DISCHRG_regtype", val); } + inline void setModbus_DataBits(const std::string & ups, long long int val) { setInt(ups, "databits", val); } + inline void setModbus_DeviceSlaveId(const std::string & ups, long long int val) { setInt(ups, "dev_slave_id", val); } + inline void setModbus_FSD_addr(const std::string & ups, long long int val) { setInt(ups, "FSD_addr", val); } + inline void setModbus_FSD_noro(const std::string & ups, long long int val) { setInt(ups, "FSD_noro", val); } + inline void setModbus_FSD_pulse_duration(const std::string & ups, long long int val) { setInt(ups, "FSD_pulse_duration", val); } + inline void setModbus_FSD_regtype(const std::string & ups, long long int val) { setInt(ups, "FSD_regtype", val); } + inline void setModbus_HB_addr(const std::string & ups, long long int val) { setInt(ups, "HB_addr", val); } + inline void setModbus_HB_noro(const std::string & ups, long long int val) { setInt(ups, "HB_noro", val); } + inline void setModbus_HB_regtype(const std::string & ups, long long int val) { setInt(ups, "HB_regtype", val); } + inline void setModbus_LB_addr(const std::string & ups, long long int val) { setInt(ups, "LB_addr", val); } + inline void setModbus_LB_noro(const std::string & ups, long long int val) { setInt(ups, "LB_noro", val); } + inline void setModbus_LB_regtype(const std::string & ups, long long int val) { setInt(ups, "LB_regtype", val); } + inline void setModbus_OB_addr(const std::string & ups, long long int val) { setInt(ups, "OB_addr", val); } + inline void setModbus_OB_noro(const std::string & ups, long long int val) { setInt(ups, "OB_noro", val); } + inline void setModbus_OB_regtype(const std::string & ups, long long int val) { setInt(ups, "OB_regtype", val); } + inline void setModbus_OL_addr(const std::string & ups, long long int val) { setInt(ups, "OL_addr", val); } + inline void setModbus_OL_noro(const std::string & ups, long long int val) { setInt(ups, "OL_noro", val); } + inline void setModbus_OL_regtype(const std::string & ups, long long int val) { setInt(ups, "OL_regtype", val); } + inline void setModbus_RB_addr(const std::string & ups, long long int val) { setInt(ups, "RB_addr", val); } + inline void setModbus_RB_noro(const std::string & ups, long long int val) { setInt(ups, "RB_noro", val); } + inline void setModbus_RB_regtype(const std::string & ups, long long int val) { setInt(ups, "RB_regtype", val); } + inline void setModbus_ResponseTimeoutMsec(const std::string & ups, long long int val) { setInt(ups, "response_timeout_ms", val); } + inline void setModbus_ResponseTimeoutSec(const std::string & ups, long long int val) { setInt(ups, "mod_resp_to_s", val); } + inline void setModbus_ResponseTimeoutUsec(const std::string & ups, long long int val) { setInt(ups, "mod_resp_to_us", val); } + inline void setModbus_RioSlaveId(const std::string & ups, long long int val) { setInt(ups, "rio_slave_id", val); } + inline void setModbus_SerBaudRate(const std::string & ups, long long int val) { setInt(ups, "ser_baud_rate", val); } + inline void setModbus_SerDataBit(const std::string & ups, long long int val) { setInt(ups, "ser_data_bit", val); } + inline void setModbus_SerStopBit(const std::string & ups, long long int val) { setInt(ups, "ser_stop_bit", val); } + inline void setModbus_SlaveId(const std::string & ups, long long int val) { setInt(ups, "slaveid", val); } + inline void setModbus_StopBits(const std::string & ups, long long int val) { setInt(ups, "stopbits", val); } + 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 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 setOndelay(const std::string & ups, long long int ondelay) { setInt(ups, "ondelay", ondelay); } // CHECKME + inline void setOnlineDischargeLogThrottleHovercharge(const std::string & ups, long long int val) { setInt(ups, "onlinedischarge_log_throttle_hovercharge", val); } + inline void setOnlineDischargeLogThrottleSec(const std::string & ups, long long int val) { setInt(ups, "onlinedischarge_log_throttle_sec", val); } + inline void setOutputPace(const std::string & ups, long long int output_pace) { setInt(ups, "output_pace", output_pace); } // CHECKME + inline void setOutputPhaseAngle(const std::string & ups, long long int val) { setInt(ups, "output_phase_angle", val); } + inline void setPinsShutdownMode(const std::string & ups, long long int val) { setInt(ups, "pins_shutdown_mode", val); } + 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 setSDOrder(const std::string & ups, long long int ord) { setInt(ups, "sdorder", ord); } + inline void setSemistaticFreq(const std::string & ups, long long int val) { setInt(ups, "semistaticfreq", val); } + inline void setShutdownDelay(const std::string & ups, long long int delay) { setInt(ups, "shutdown_delay", delay); } // CHECKME + inline void setShutdownDuration(const std::string & ups, long long int val) { setInt(ups, "shutdown_duration", val); } + inline void setShutdownTimer(const std::string & ups, long long int val) { setInt(ups, "shutdown_timer", val); } + inline void setSlaveAddress(const std::string & ups, long long int val) { setInt(ups, "slave_address", val); } + inline void setSnmpRetries(const std::string & ups, long long int val) { setInt(ups, "snmp_retries", val); } + inline void setSnmpTimeout(const std::string & ups, long long int val) { setInt(ups, "snmp_timeout", val); } + 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 setWaitBeforeReconnect(const std::string & ups, long long int val) { setInt(ups, "waitbeforereconnect", val); } + + // Items below are "unused" - mostly set in + // drivers/nutdrv_qx_masterguard.c + inline void setFault1(const std::string & ups, const std::string & val) { setStr(ups, "fault_1", val); } + inline void setFault2(const std::string & ups, const std::string & val) { setStr(ups, "fault_2", val); } + inline void setFault3(const std::string & ups, const std::string & val) { setStr(ups, "fault_3", val); } + inline void setFault4(const std::string & ups, const std::string & val) { setStr(ups, "fault_4", val); } + inline void setFault5(const std::string & ups, const std::string & val) { setStr(ups, "fault_5", val); } + inline void setInputFaultVoltage(const std::string & ups, const std::string & val) { setStr(ups, "input_fault_voltage", val); } + inline void setNominalCellVoltage(const std::string & ups, const std::string & val) { setStr(ups, "nominal_cell_voltage",val); } + inline void setNumberOfBatteryCells(const std::string & ups, const std::string & val) { setStr(ups, "number_of_battery_cells", val); } + inline void setOutputVoltages(const std::string & ups, const std::string & val) { setStr(ups, "output_voltages", val); } + inline void setRechargeTime(const std::string & ups, const std::string & val) { setStr(ups, "recharge_time", val); } + inline void setRuntimeFull(const std::string & ups, const std::string & val) { setStr(ups, "runtime_full", val); } + inline void setRuntimeHalf(const std::string & ups, const std::string & val) { setStr(ups, "runtime_half", val); } + inline void setSeries(const std::string & ups, const std::string & val) { setStr(ups, "series", val); } + + // Items below are essentially booleans (expected values + // are "enabled/disabled") -- refactoring planned per + // https://github.com/networkupstools/nut/issues/2421 + inline void setAdvancedEcoMode(const std::string & ups, const std::string & val) { setStr(ups, "advanced_eco_mode", val); } + inline void setAlarmControl(const std::string & ups, const std::string & val) { setStr(ups, "alarm_control", val); } + inline void setBatteryAlarm(const std::string & ups, const std::string & val) { setStr(ups, "battery_alarm", val); } + inline void setBatteryOpenStatusCheck(const std::string & ups, const std::string & val) { setStr(ups, "battery_open_status_check", val); } + inline void setBypassAlarm(const std::string & ups, const std::string & val) { setStr(ups, "bypass_alarm", val); } + inline void setBypassForbidding(const std::string & ups, const std::string & val) { setStr(ups, "bypass_forbidding", val); } + inline void setBypassWhenOff(const std::string & ups, const std::string & val) { setStr(ups, "bypass_when_off", val); } + inline void setConstantPhaseAngle(const std::string & ups, const std::string & val) { setStr(ups, "constant_phase_angle",val); } + inline void setConverterMode(const std::string & ups, const std::string & val) { setStr(ups, "converter_mode", val); } + inline void setEcoMode(const std::string & ups, const std::string & val) { setStr(ups, "eco_mode", val); } + inline void setLimitedRuntimeOnBattery(const std::string & ups, const std::string & val) { setStr(ups, "limited_runtime_on_battery", val); } + inline void setSiteFaultDetection(const std::string & ups, const std::string & val) { setStr(ups, "site_fault_detection",val); } + + /** belkinunv: both a flag (wait for AC power) and value (also wait for charge level) */ 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)); } + /** May be a flag or a number; 0 is among valid values (default -1 for unset) */ + inline void setUsbSetAltInterface(const std::string & ups, long long int val = 0) { if (val >= 0) { setInt(ups, "usb_set_altinterface", val); } else { remove(ups, "usb_set_altinterface"); } } // CHECKME + + // NUT specifies these as "hexnum" values (optionally with prefixed 0x but hex anyway) + inline void setUsbConfigIndex(const std::string & ups, long long int val) { setIntHex(ups, "usb_config_index", val); } // CHECKME + inline void setUsbHidDescIndex(const std::string & ups, long long int val) { setIntHex(ups, "usb_hid_desc_index", val); } // CHECKME + inline void setUsbHidRepIndex(const std::string & ups, long long int val) { setIntHex(ups, "usb_hid_rep_index", val); } // CHECKME + inline void setUsbHidEndpointIn(const std::string & ups, long long int val) { setIntHex(ups, "usb_hid_ep_in", val); } // CHECKME + inline void setUsbHidEndpointOut(const std::string & ups, long long int val) { setIntHex(ups, "usb_hid_ep_out", val); } // CHECKME + + inline void setBatteryMax(const std::string & ups, double val) { setDouble(ups, "battery_max", val); } + inline void setBatteryMin(const std::string & ups, double val) { setDouble(ups, "battery_min", val); } + inline void setCSHackDelay(const std::string & ups, double val) { setDouble(ups, "cshdelay", val); } + inline void setMaxBypassFreq(const std::string & ups, double val) { setDouble(ups, "max_bypass_freq", val); } + inline void setMaxBypassVolt(const std::string & ups, double val) { setDouble(ups, "max_bypass_volt", val); } + inline void setMinBypassFreq(const std::string & ups, double val) { setDouble(ups, "min_bypass_freq", val); } + inline void setMinBypassVolt(const std::string & ups, double val) { setDouble(ups, "min_bypass_volt", val); } + + // Flag - if exists then "true"; remove() to "unset" => "false" + inline void setCancelShutdown(const std::string & ups, bool set = true) { setFlag(ups, "CS", set); } + inline void setDumbTerm(const std::string & ups, bool set = true) { setFlag(ups, "dumbterm", set); } + inline void setExplore(const std::string & ups, bool set = true) { setFlag(ups, "explore", set); } + inline void setFakeLowBatt(const std::string & ups, bool set = true) { setFlag(ups, "fake_lowbatt", set); } + inline void setFlash(const std::string & ups, bool set = true) { setFlag(ups, "flash", set); } + inline void setIgnoreLB(const std::string & ups, bool set = true) { setFlag(ups, "ignorelb", set); } + inline void setNoHang(const std::string & ups, bool set = true) { setFlag(ups, "nohang", set); } + inline void setNoRating(const std::string & ups, bool set = true) { setFlag(ups, "norating", set); } + inline void setNoTransferOIDs(const std::string & ups, bool set = true) { setFlag(ups, "notransferoids", set); } + inline void setNoVendor(const std::string & ups, bool set = true) { setFlag(ups, "novendor", set); } + inline void setNoWarnNoImp(const std::string & ups, bool set = true) { setFlag(ups, "nowarn_noimp", set); } + inline void setOldMAC(const std::string & ups, bool set = true) { setFlag(ups, "oldmac", set); } + inline void setPollOnly(const std::string & ups, bool set = true) { setFlag(ups, "pollonly", set); } + inline void setSilent(const std::string & ups, bool set = true) { setFlag(ups, "silent", set); } + inline void setStatusOnly(const std::string & ups, bool set = true) { setFlag(ups, "status_only", set); } // aka OPTI_MINPOLL + inline void setSubscribe(const std::string & ups, bool set = true) { setFlag(ups, "subscribe", set); } + inline void setUseCRLF(const std::string & ups, bool set = true) { setFlag(ups, "use_crlf", set); } + inline void setUsePreLF(const std::string & ups, bool set = true) { setFlag(ups, "use_pre_lf", set); } + + inline void setNolock(const std::string & ups, bool set = true) { setBool(ups, "nolock", set); } + inline void setCable(const std::string & ups, bool set = true) { setBool(ups, "cable", set); } + inline void setFullUpdate(const std::string & ups, bool set = true) { setBool(ups, "full_update", set); } + inline void setLangIDfix(const std::string & ups, bool set = true) { setBool(ups, "langid_fix", set); } + inline void setLoadOff(const std::string & ups, bool set = true) { setBool(ups, "load.off", set); } + inline void setLoadOn(const std::string & ups, bool set = true) { setBool(ups, "load.on", set); } /** \} */ @@ -1077,14 +2239,16 @@ class UpsdUsersConfiguration : public GenericConfiguration /** upsmon mode */ typedef enum { UPSMON_UNDEF = 0, /**< Unknown mode */ - UPSMON_MASTER, /**< Master mode */ - UPSMON_SLAVE, /**< Slave mode */ + UPSMON_PRIMARY, /**< Primary (legacy "Master") mode */ + UPSMON_SECONDARY, /**< Secondary (legacy "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"); } + /** Currently valid actions include "SET" and "FSD", + * but the method does not constrain the values */ inline ConfigParamList getActions(const std::string & user) const { ConfigParamList actions; @@ -1092,6 +2256,8 @@ class UpsdUsersConfiguration : public GenericConfiguration return actions; } + /** Valid commands are "ALL" or a list of specific commands + * supported by the device (NUT driver dependent) */ inline ConfigParamList getInstantCommands(const std::string & user) const { ConfigParamList cmds; @@ -1117,6 +2283,9 @@ class UpsdUsersConfiguration : public GenericConfiguration * * \param mode Mode */ + /* TOTHINK: Do we need a writer (other method, optional parameter + * to this one) for obsolete wordings of the upsmon mode? + * Note: reader in the getter accepts both old and new values. */ void setUpsmonMode(upsmon_mode_t mode); /** \} */ diff --git a/include/nutipc.hpp b/include/nutipc.hpp index d668363b3c..56618d6200 100644 --- a/include/nutipc.hpp +++ b/include/nutipc.hpp @@ -5,6 +5,10 @@ Author: Vaclav Krpec + Copyright (C) 2024 NUT Community + + Author: Jim Klimov + This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or @@ -810,7 +814,11 @@ void Signal::HandlerThread::quit() template -Signal::HandlerThread::~HandlerThread() +Signal::HandlerThread::~HandlerThread +#if (defined __clang__) + +#endif +() #if (defined __cplusplus) && (__cplusplus < 201100) throw(std::runtime_error) #endif diff --git a/include/nutstream.hpp b/include/nutstream.hpp index 13c9cf76ed..68352942cc 100644 --- a/include/nutstream.hpp +++ b/include/nutstream.hpp @@ -3,6 +3,7 @@ Copyright (C) 2012 Vaclav Krpec + 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/include/nutwriter.hpp b/include/nutwriter.hpp index b04c44acbb..6d88ca6c09 100644 --- a/include/nutwriter.hpp +++ b/include/nutwriter.hpp @@ -3,6 +3,7 @@ Copyright (C) 2012 Vaclav Krpec + 2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -126,7 +127,7 @@ class NutWriter { /** - * \brief NUT configuration writer interface + * \brief NUT configuration writer interface (generic) */ class NutConfigWriter: public NutWriter { protected: diff --git a/include/parseconf.h b/include/parseconf.h index 828a1e09b3..3c8c8851da 100644 --- a/include/parseconf.h +++ b/include/parseconf.h @@ -24,15 +24,15 @@ /* Not including nut_stdint.h because this is part of end-user API */ #if defined HAVE_INTTYPES_H - #include +# include #endif #if defined HAVE_STDINT_H - #include +# include #endif #if defined HAVE_LIMITS_H - #include +# include #endif #ifdef __cplusplus diff --git a/include/proto.h b/include/proto.h index f88dd61f1c..68c251a118 100644 --- a/include/proto.h +++ b/include/proto.h @@ -24,7 +24,13 @@ #ifndef NUT_PROTO_H_SEEN #define NUT_PROTO_H_SEEN 1 -#include "config.h" +/* "config.h" is generated by autotools and lacks a header guard, so + * we use an unambiguously named macro we know we must have, as one. + * It must be the first header: be sure to know all about system config. + */ +#ifndef NUT_NETVERSION +# include "config.h" +#endif #include "attribute.h" diff --git a/include/state.h b/include/state.h index 60dc3433fe..9857e4d584 100644 --- a/include/state.h +++ b/include/state.h @@ -32,6 +32,8 @@ extern "C" { #define ST_SOCK_BUF_LEN 512 +#include "timehead.h" + #if defined(HAVE_CLOCK_GETTIME) && defined(HAVE_CLOCK_MONOTONIC) && HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC typedef struct timespec st_tree_timespec_t; #else diff --git a/include/str.h b/include/str.h index d7aac249f6..44cd3a667d 100644 --- a/include/str.h +++ b/include/str.h @@ -32,7 +32,7 @@ extern "C" { /* Some compilers and/or C libraries do not handle printf("%s", NULL) correctly */ #ifndef NUT_STRARG # if (defined REQUIRE_NUT_STRARG) && (REQUIRE_NUT_STRARG == 0) -# define NUT_STRARG(x) x +# define NUT_STRARG(x) (x) # else /* Is required, or not defined => err on safe side */ # define NUT_STRARG(x) (x?x:"(null)") diff --git a/include/timehead.h b/include/timehead.h index 7a5d989a7d..0eeea4bf9b 100644 --- a/include/timehead.h +++ b/include/timehead.h @@ -1,8 +1,8 @@ /* timehead.h - from the autoconf docs: sanely include the right time headers everywhere - Copyright (C) 2001 Russell Kroll - 2005 Arnaud Quette - 2020 Jim Klimov + Copyright (C) 2001 Russell Kroll + 2005 Arnaud Quette + 2020-2024 Jim Klimov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -44,10 +44,12 @@ extern "C" { char * strptime(const char *buf, const char *fmt, struct tm *tm); #endif -#ifndef HAVE_LOCALTIME_R -# ifdef HAVE_LOCALTIME_S -/* A bit of a silly trick, but should help on MSYS2 builds it seems */ -# define localtime_r(timer, buf) localtime_s(timer, buf) +#if !(defined HAVE_LOCALTIME_R && HAVE_LOCALTIME_R) && !(defined HAVE_DECL_LOCALTIME_R && HAVE_DECL_LOCALTIME_R) +# if (defined HAVE_LOCALTIME_S && HAVE_LOCALTIME_S) || (defined HAVE_DECL_LOCALTIME_S && HAVE_DECL_LOCALTIME_S) +/* A bit of a silly trick, but should help on MSYS2 builds it seems + * errno_t localtime_s(struct tm *_Tm, const time_t *_Time) + */ +# define localtime_r(timer, buf) (localtime_s(buf, timer) ? NULL : buf) # else # include /* memcpy */ static inline struct tm *localtime_r( const time_t *timer, struct tm *buf ) { @@ -59,9 +61,10 @@ static inline struct tm *localtime_r( const time_t *timer, struct tm *buf ) { # endif #endif -#ifndef HAVE_GMTIME_R -# ifdef HAVE_GMTIME_S -# define gmtime_r(timer, buf) gmtime_s(timer, buf) +#if !(defined HAVE_GMTIME_R && HAVE_GMTIME_R) && !(defined HAVE_DECL_GMTIME_R && HAVE_DECL_GMTIME_R) +# if (defined HAVE_GMTIME_S && HAVE_GMTIME_S) || (defined HAVE_DECL_GMTIME_S && HAVE_DECL_GMTIME_S) +/* See comment above */ +# define gmtime_r(timer, buf) (gmtime_s(buf, timer) ? NULL : buf) # else # include /* memcpy */ static inline struct tm *gmtime_r( const time_t *timer, struct tm *buf ) { @@ -73,6 +76,19 @@ static inline struct tm *gmtime_r( const time_t *timer, struct tm *buf ) { # endif #endif +#if !(defined HAVE_TIMEGM && HAVE_TIMEGM) && !(defined HAVE_DECL_TIMEGM && HAVE_DECL_TIMEGM) +# if (defined HAVE__MKGMTIME && HAVE__MKGMTIME) || (defined HAVE_DECL__MKGMTIME && HAVE_DECL__MKGMTIME) +# define timegm(tm) _mkgmtime(tm) +# else +# ifdef WANT_TIMEGM_FALLBACK + /* use an implementation from fallbacks in NUT codebase */ +# define timegm(tm) timegm_fallback(tm) +# else +# error "No fallback implementation for timegm" +# endif +# endif +#endif + #ifdef __cplusplus /* *INDENT-OFF* */ } diff --git a/indent.sh b/indent.sh index 0aaf571f32..67cfd72352 100755 --- a/indent.sh +++ b/indent.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # Filter NUT C source file style to conform to recommendations of # https://www.networkupstools.org/docs/developer-guide.chunked/ar01s03.html#_coding_style diff --git a/lib/Makefile.am b/lib/Makefile.am index 7d272d8de6..63a0427104 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -3,11 +3,11 @@ # Export certain values for ccache which NUT ci_build.sh can customize, # to facilitate developer iteration re-runs of "make" later. # At least GNU and BSD make implementations are okay with this syntax. -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_DIR=@CCACHE_DIR@ -@NUT_AM_MAKE_CAN_EXPORT@export CCACHE_PATH=@CCACHE_PATH@ -@NUT_AM_MAKE_CAN_EXPORT@export PATH=@PATH_DURING_CONFIGURE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_NAMESPACE@export CCACHE_NAMESPACE=@CCACHE_NAMESPACE@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_BASEDIR@export CCACHE_BASEDIR=@CCACHE_BASEDIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_DIR@export CCACHE_DIR=@CCACHE_DIR@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export CCACHE_PATH=@CCACHE_PATH@ +@NUT_AM_MAKE_CAN_EXPORT@@NUT_AM_EXPORT_CCACHE_PATH@export PATH=@PATH_DURING_CONFIGURE@ EXTRA_DIST = README.adoc diff --git a/m4/ax_c_pragmas.m4 b/m4/ax_c_pragmas.m4 index f2cf78f676..af33afb6a3 100644 --- a/m4/ax_c_pragmas.m4 +++ b/m4/ax_c_pragmas.m4 @@ -146,6 +146,33 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" ]) dnl Special pragma support testing for clang dnl Test common pragmas for GCC (and compatible) compilers + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wpedantic"], + [ax_cv__pragma__gcc__diags_ignored_pedantic], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[void func(void) { +#pragma GCC diagnostic ignored "-Wpedantic" +} +]], [])], + [ax_cv__pragma__gcc__diags_ignored_pedantic=yes], + [ax_cv__pragma__gcc__diags_ignored_pedantic=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_pedantic" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_PEDANTIC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wpedantic"]) + ]) + + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wpedantic" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_pedantic_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wpedantic"]], [])], + [ax_cv__pragma__gcc__diags_ignored_pedantic_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_pedantic_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_pedantic_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_PEDANTIC_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wpedantic" (outside functions)]) + ]) + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wunused-function"], [ax_cv__pragma__gcc__diags_ignored_unused_function], [AC_COMPILE_IFELSE( @@ -161,6 +188,21 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNUSED_FUNCTION], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wunused-function"]) ]) + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wunused-parameter"], + [ax_cv__pragma__gcc__diags_ignored_unused_parameter], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[void func(void) { +#pragma GCC diagnostic ignored "-Wunused-parameter" +} +]], [])], + [ax_cv__pragma__gcc__diags_ignored_unused_parameter=yes], + [ax_cv__pragma__gcc__diags_ignored_unused_parameter=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_unused_parameter" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNUSED_PARAMETER], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wunused-parameter"]) + ]) + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wdeprecated-declarations"], [ax_cv__pragma__gcc__diags_ignored_deprecated_declarations], [AC_COMPILE_IFELSE( @@ -662,6 +704,33 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXTRA_SEMI_STMT_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wextra-semi-stmt" (outside functions)]) ]) + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Waddress"], + [ax_cv__pragma__gcc__diags_ignored_address], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[void func(void) { +#pragma GCC diagnostic ignored "-Waddress" +} +]], [])], + [ax_cv__pragma__gcc__diags_ignored_address=yes], + [ax_cv__pragma__gcc__diags_ignored_address=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_address" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Waddress"]) + ]) + + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Waddress" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_address_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Waddress"]], [])], + [ax_cv__pragma__gcc__diags_ignored_address_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_address_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_address_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ADDRESS_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Waddress" (outside functions)]) + ]) + AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wcast-align"], [ax_cv__pragma__gcc__diags_ignored_cast_align], [AC_COMPILE_IFELSE( @@ -689,6 +758,7 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_CAST_ALIGN_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wcast-align" (outside functions)]) ]) + dnl https://reviews.llvm.org/D134831 AC_CACHE_CHECK([for pragma GCC diagnostic ignored "-Wcast-function-type-strict"], [ax_cv__pragma__gcc__diags_ignored_cast_function_type_strict], [AC_COMPILE_IFELSE( @@ -883,6 +953,90 @@ dnl ### [CFLAGS="${CFLAGS_SAVED} -Werror=pragmas -Werror=unknown-warning" AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXIT_TIME_DESTRUCTORS_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wexit-time-destructors" (outside functions)]) ]) + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wsuggest-override" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_suggest_override_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wsuggest-override"]], [])], + [ax_cv__pragma__gcc__diags_ignored_suggest_override_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_suggest_override_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_suggest_override_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_SUGGEST_OVERRIDE_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wsuggest-override" (outside functions)]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wsuggest-destructor-override" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_suggest_destructor_override_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wsuggest-destructor-override"]], [])], + [ax_cv__pragma__gcc__diags_ignored_suggest_destructor_override_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_suggest_destructor_override_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_suggest_destructor_override_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_SUGGEST_DESTRUCTOR_OVERRIDE_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wsuggest-destructor-override" (outside functions)]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wweak-vtables" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_weak_vtables_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wweak-vtables"]], [])], + [ax_cv__pragma__gcc__diags_ignored_weak_vtables_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_weak_vtables_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_weak_vtables_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_WEAK_VTABLES_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wweak-vtables" (outside functions)]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wdeprecated-dynamic-exception-spec" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_deprecated_dynamic_exception_spec_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wdeprecated-dynamic-exception-spec"]], [])], + [ax_cv__pragma__gcc__diags_ignored_deprecated_dynamic_exception_spec_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_deprecated_dynamic_exception_spec_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_deprecated_dynamic_exception_spec_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_DEPRECATED_DYNAMIC_EXCEPTION_SPEC_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wdeprecated-dynamic-exception-spec" (outside functions)]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wextra-semi" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_extra_semi_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wextra-semi"]], [])], + [ax_cv__pragma__gcc__diags_ignored_extra_semi_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_extra_semi_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_extra_semi_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXTRA_SEMI_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wextra-semi" (outside functions)]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wold-style-cast" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_old_style_cast_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wold-style-cast"]], [])], + [ax_cv__pragma__gcc__diags_ignored_old_style_cast_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_old_style_cast_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_old_style_cast_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_OLD_STYLE_CAST_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wold-style-cast" (outside functions)]) + ]) + + AC_CACHE_CHECK([for C++ pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" (outside functions)], + [ax_cv__pragma__gcc__diags_ignored_zero_as_null_pointer_constant_besidefunc], + [AC_COMPILE_IFELSE( + [AC_LANG_PROGRAM([[#pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant"]], [])], + [ax_cv__pragma__gcc__diags_ignored_zero_as_null_pointer_constant_besidefunc=yes], + [ax_cv__pragma__gcc__diags_ignored_zero_as_null_pointer_constant_besidefunc=no] + )] + ) + AS_IF([test "$ax_cv__pragma__gcc__diags_ignored_zero_as_null_pointer_constant_besidefunc" = "yes"],[ + AC_DEFINE([HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_ZERO_AS_NULL_POINTER_CONSTANT_BESIDEFUNC], 1, [define if your compiler has #pragma GCC diagnostic ignored "-Wzero-as-null-pointer-constant" (outside functions)]) + ]) + AC_LANG_POP([C++]) dnl # Meta-macros for simpler use-cases where we pick @@ -1040,7 +1194,34 @@ return 0; ) unset myWARN_CFLAGS - AS_IF([test "$ax_cv__printf_string_null" = "yes"],[ + NUT_ARG_ENABLE([NUT_STRARG-always], + [Enable NUT_STRARG macro to handle NULL string printing even if system libraries seem to support it natively], + [auto]) + + dnl gcc-13.2.0 and gcc-13.3.0 were seen to complain about + dnl alleged formatting string overflow (seems like a false + dnl positive in that case). Require the full macro there + dnl by default. + dnl AC_MSG_NOTICE([CC_VERSION='$CC_VERSION']) + AS_IF([test x"$nut_enable_NUT_STRARG_always" = xauto], [ + nut_enable_NUT_STRARG_always=no + AS_IF([test "${CLANGCC}" = "yes"], [ + true dnl no-op at the moment +dnl AS_CASE(["$CC_VERSION"], +dnl [*" "18.*], [nut_enable_NUT_STRARG_always=yes] +dnl ) + ],[ + AS_IF([test "${GCC}" = "yes"], [ + AS_CASE(["$CC_VERSION"], + [*" "13.*], [nut_enable_NUT_STRARG_always=yes] + ) + ]) + ]) + AS_IF([test x"$nut_enable_NUT_STRARG_always" = xyes], + [AC_MSG_NOTICE([Automatically enabled NUT_STRARG-always due to compiler version used])]) + ]) + + AS_IF([test "$ax_cv__printf_string_null" = "yes" && test x"$nut_enable_NUT_STRARG_always" != xyes],[ AM_CONDITIONAL([REQUIRE_NUT_STRARG], [false]) AC_DEFINE([REQUIRE_NUT_STRARG], [0], [Define to 0 if your libc can printf("%s", NULL) sanely, or to 1 if your libc requires workarounds to print NULL values.]) diff --git a/m4/ax_realpath_lib.m4 b/m4/ax_realpath_lib.m4 new file mode 100644 index 0000000000..d57de269ff --- /dev/null +++ b/m4/ax_realpath_lib.m4 @@ -0,0 +1,207 @@ +dnl Resolve real path (if we can) to a library file that would be used +dnl by gcc/clang compatible toolkit. Relies on AX_REALPATH macro and +dnl may benefit from a REALPATH envvar pointing to a command-line tool +dnl (otherwise m4/shell implementation gets used), and NUT_COMPILER_FAMILY. +dnl Copyright (C) 2024 by Jim Klimov +dnl Licensed under the terms of GPLv2 or newer. + +dnl Calling script is welcome to pre-detect external REALPATH implementation, +dnl otherwise shell implementation would be used (hopefully capable enough): +dnl AC_CHECK_PROGS([REALPATH], [realpath], []) + +AC_DEFUN([AX_REALPATH_LIB_PREREQ], +[ +if test -z "${nut_ax_realpath_lib_prereq_seen}"; then + nut_ax_realpath_lib_prereq_seen=yes + + AC_REQUIRE([NUT_COMPILER_FAMILY])dnl + AS_CASE(["${target_os}"], + [*mingw*], [ + AS_IF([test x"${DLLTOOL-}" = x], + [AC_CHECK_TOOLS([DLLTOOL], [dlltool dlltool.exe], [false])]) + AS_IF([test x"${OBJDUMP-}" = x], + [AC_CHECK_TOOLS([OBJDUMP], [objdump objdump.exe], [false])]) + dnl # Assuming mingw (sourceware) x86_64-w64-mingw32-ld or similar + AS_IF([test x"${LD-}" = x], + [AC_CHECK_TOOLS([LD], [ld ld.exe], [false])]) + ], + [*darwin*], [ + dnl # Can be a non-GNU ld + AS_IF([test x"${LD-}" = x], + [AC_CHECK_TOOLS([LD], [ld], [false])]) + ] + ) +fi +]) + +AC_DEFUN([AX_REALPATH_LIB], +[ + dnl # resolve libname - #1 value (not quoted by caller) if we can; + dnl # save into varname #2. + dnl # In case of problems return #3 (optional, defaults to empty). + AC_REQUIRE([AX_REALPATH_LIB_PREREQ])dnl + + AS_IF([test x"$1" = x], [AC_MSG_ERROR([Bad call to REALPATH_LIB macro (arg1)])]) + AS_IF([test x"$2" = x], [AC_MSG_ERROR([Bad call to REALPATH_LIB macro (arg2)])]) + + AS_IF([test "x$GCC" = xyes -o "x$CLANGCC" = xyes], [ + myLIBNAME="$1" + AS_CASE(["${myLIBNAME}"], + [-l*], [myLIBNAME="`echo "$myLIBNAME" | sed 's/^-l/lib/'`"] + ) + + dnl # Primarily we care to know dynamically linked (shared object) + dnl # files, so inject the extension to the presumed base name + AS_CASE(["${myLIBNAME}"], + [*.so*|*.a|*.o|*.lo|*.la|*.dll|*.dll.a|*.lib|*.dylib|*.sl], [], + [ + AS_CASE(["${target_os}"], + [*mingw*], [myLIBNAME="${myLIBNAME}.dll"], + [*darwin*], [myLIBNAME="${myLIBNAME}.dylib"], + [*hpux*|*hp?ux*], [ + dnl # See detailed comments in nut_platform.h + myLIBNAME="${myLIBNAME}.sl" + ],[myLIBNAME="${myLIBNAME}.so"]) + ] + ) + + AS_CASE(["${myLIBNAME}"], + [lib*], [ + dnl Alas, the portable solution with sed is to avoid + dnl parentheses and pipe chars, got too many different + dnl ways to escape them in the wild + myLIBNAME_LD="`echo "$myLIBNAME" | sed -e 's/^lib/-l/' -e 's/\.dll$//' -e 's/\.dll\.a$//' -e 's/\.a$//' -e 's/\.o$//' -e 's/\.la$//' -e 's/\.lo$//' -e 's/\.lib$//' -e 's/\.dylib$//' -e 's/\.so\..*$//' -e 's/\.so//' -e 's/\.sl\..*$//' -e 's/\.sl//'`" + ], [myLIBNAME_LD="-l$myLIBNAME"] dnl best-effort... + ) + + AC_MSG_CHECKING([for real path to $1 (re-parsed as ${myLIBNAME} likely file name / ${myLIBNAME_LD} linker arg)]) + myLIBPATH="" + AS_CASE(["${target_os}"], + [*mingw*], [ + AS_IF([test x"${LD}" != x -a x"${LD}" != xfalse -a x"${DLLTOOL}" != x -a x"${DLLTOOL}" != xfalse], [ + dnl Expected output (Linux mingw cross-build) ends like: + dnl ================================================== + dnl x86_64-w64-mingw32-ld: mode i386pep + dnl attempt to open /usr/x86_64-w64-mingw32/lib/libnetsnmp.dll.a succeeded + dnl /usr/x86_64-w64-mingw32/lib/libnetsnmp.dll.a + dnl ...which we then resolve with dlltool into a libnetsnmp-40.dll + dnl and finally find one in /usr/x86_64-w64-mingw32/bin/libnetsnmp-40.dll + dnl (note the version number embedded into base name). + { myLIBPATH="`$LD --verbose -Bdynamic ${myLIBNAME_LD} | grep -wi dll | tail -1`" \ + && test -n "${myLIBPATH}" && test -s "${myLIBPATH}" ; } \ + || { myLIBPATH="`$LD $LDFLAGS $LIBS --verbose -Bdynamic ${myLIBNAME_LD} | grep -wi dll | tail -1`" \ + && test -n "${myLIBPATH}" && test -s "${myLIBPATH}" ; } \ + || myLIBPATH="" + + dnl Resolve dynamic library "internal" name from its stub, if needed + AS_CASE(["${myLIBPATH}"], + [*.dll.a], [ + myLIBPATH="`$DLLTOOL -I "${myLIBPATH}"`" || myLIBPATH="" + ] + ) + + dnl Internal name may be just that, with no path info + AS_CASE(["${myLIBPATH}"], + [*/*], [], + [ + for D in \ + "/usr/${target}/bin" \ + "/usr/${target}/lib" \ + "${MSYSTEM_PREFIX}/bin" \ + "${MSYSTEM_PREFIX}/lib" \ + "${MINGW_PREFIX}/bin" \ + "${MINGW_PREFIX}/lib" \ + `${CC} --print-search-dirs 2>/dev/null | grep libraries: | sed 's,^libraries: *=/,/,'` \ + ; do + if test -s "$D/${myLIBPATH}" 2>/dev/null ; then + myLIBPATH="$D/${myLIBPATH}" + break + fi + done + unset D + ] + ) + ]) + ], [ dnl # POSIX/MacOS builds + { myLIBPATH="`${CC} --print-file-name="$myLIBNAME"`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || { myLIBPATH="`${CC} $CFLAGS --print-file-name="$myLIBNAME"`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || { myLIBPATH="`${CC} $CFLAGS $LDFLAGS $LIBS --print-file-name="$myLIBNAME"`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || myLIBPATH="" + ] + ) + + AS_IF([test -z "${myLIBPATH}" && test x"${LD}" != x -a x"${LD}" != xfalse], [ + AS_CASE(["${target_os}"], + [*darwin*], [ + dnl Try MacOS-style LD as fallback; expecting strings like + dnl ld: warning: /usr/local/lib/libneon.dylib, ignoring unexpected dylib file + my_uname_m="`uname -m`" + { myLIBPATH="`${LD} -dynamic -r -arch "${target_cpu}" -search_dylibs_first "${myLIBNAME_LD}" 2>&1 | grep -w dylib | sed 's/^@<:@^\/@:>@*\(\/.*\.dylib\),.*$/\1/'`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || { myLIBPATH="`${LD} $LDFLAGS -dynamic -r -arch "${target_cpu}" -search_dylibs_first "${myLIBNAME_LD}" 2>&1 | grep -w dylib | sed 's/^@<:@^\/@:>@*\(\/.*\.dylib\),.*$/\1/'`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || { myLIBPATH="`${LD} $LDFLAGS $LIBS -dynamic -r -arch "${target_cpu}" -search_dylibs_first "${myLIBNAME_LD}" 2>&1 | grep -w dylib | sed 's/^@<:@^\/@:>@*\(\/.*\.dylib\),.*$/\1/'`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || if test x"${target_cpu}" != x"${my_uname_m}" ; then + { myLIBPATH="`${LD} -dynamic -r -arch "${my_uname_m}" -search_dylibs_first "${myLIBNAME_LD}" 2>&1 | grep -w dylib | sed 's/^@<:@^\/@:>@*\(\/.*\.dylib\),.*$/\1/'`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || { myLIBPATH="`${LD} $LDFLAGS -dynamic -r -arch "${my_uname_m}" -search_dylibs_first "${myLIBNAME_LD}" 2>&1 | grep -w dylib | sed 's/^@<:@^\/@:>@*\(\/.*\.dylib\),.*$/\1/'`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || { myLIBPATH="`${LD} $LDFLAGS $LIBS -dynamic -r -arch "${my_uname_m}" -search_dylibs_first "${myLIBNAME_LD}" 2>&1 | grep -w dylib | sed 's/^@<:@^\/@:>@*\(\/.*\.dylib\),.*$/\1/'`" && test -n "$myLIBPATH" && test -s "$myLIBPATH" ; } \ + || myLIBPATH="" + else + myLIBPATH="" + fi + rm -f a.out 2>/dev/null || true + unset my_uname_m + ] + ) + ]) + + AS_IF([test -n "${myLIBPATH}" && test -s "${myLIBPATH}"], [ + AC_MSG_RESULT([initially '${myLIBPATH}']) + + AC_MSG_CHECKING([whether the file is a "GNU ld script" and not a binary]) + AS_IF([LANG=C LC_ALL=C file "${myLIBPATH}" | grep -Ei '(ascii|text)' && grep -w GROUP "${myLIBPATH}" >/dev/null], [ + # dnl e.g. # cat /usr/lib/x86_64-linux-gnu/libusb.so + # dnl /* GNU ld script. */ + # dnl GROUP ( /lib/x86_64-linux-gnu/libusb-0.1.so.4.4.4 ) + # dnl Note that spaces around parentheses vary, more keywords + # dnl may be present in a group (e.g. AS_NEEDED), and comment + # dnl strings are inconsistent (useless to match by). + AC_MSG_RESULT([yes, iterate further]) + myLIBPATH_LDSCRIPT="`grep -w GROUP "${myLIBPATH}" | sed 's,^.*GROUP *( *\(/@<:@^ @:>@*\.so@<:@^ @:>@*\)@<:@^0-9a-zA-Z_.-@:>@.*$,\1,'`" + AS_IF([test -n "${myLIBPATH_LDSCRIPT}" && test -s "${myLIBPATH_LDSCRIPT}"], [ + AC_MSG_NOTICE([will dig into ${myLIBPATH_LDSCRIPT}]) + + dnl # See detailed comments just below + myLIBPATH_REAL="${myLIBPATH_LDSCRIPT}" + AX_REALPATH([${myLIBPATH_LDSCRIPT}], [myLIBPATH_REAL]) + ], [ + AC_MSG_NOTICE([could not determine a further path name, will use what we have]) + + dnl # See detailed comments just below + myLIBPATH_REAL="${myLIBPATH}" + AX_REALPATH([${myLIBPATH}], [myLIBPATH_REAL]) + ]) + ],[ + AC_MSG_RESULT([no, seems like a normal binary]) + + dnl # Resolving the directory location is a nice bonus + dnl # (usually the paths are relative to toolkit and ugly, + dnl # though maybe arguably portable with regard to symlinks). + dnl # The primary goal is to resolve the actual library file + dnl # name like "libnetsnmp.so.1.2.3", so we can preferentially + dnl # try to dlopen() it on a system with a packaged footprint + dnl # that does not serve short (developer-friendly) links like + dnl # "libnetsnmp.so". + myLIBPATH_REAL="${myLIBPATH}" + AX_REALPATH([${myLIBPATH}], [myLIBPATH_REAL]) + ]) + + AC_MSG_RESULT(${myLIBPATH_REAL}) + $2="${myLIBPATH_REAL}" + ],[ + AC_MSG_RESULT([not found]) + $2="$3" + ]) + ], + [AC_MSG_WARN([Compiler not detected as GCC/CLANG-compatible, skipping REALPATH_LIB($1)]) + $2="$3" + ]) +]) diff --git a/m4/nut_check_aspell.m4 b/m4/nut_check_aspell.m4 index dbccd27878..4515821add 100644 --- a/m4/nut_check_aspell.m4 +++ b/m4/nut_check_aspell.m4 @@ -114,7 +114,7 @@ if test -z "${nut_have_aspell_seen}"; then AS_IF([test -n "$ASPELL_FILTER_TEX_PATH" -a -d "$ASPELL_FILTER_TEX_PATH"], [ASPELL_NUT_TEXMODE_ARGS="--filter-path='${ASPELL_FILTER_TEX_PATH}' ${ASPELL_NUT_TEXMODE_ARGS}"]) ASPELL_NUT_COMMON_ARGS="-d en -a" dnl Using "eval" to handle quotes, in case of funny paths - out0="`LANG=C; LC_ALL=C; export LANG; export LC_ALL; exec -- 2>&1; set -x; echo test | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS}`"; res0=$? + out0="`LANG=C; LC_ALL=C; export LANG; export LC_ALL; ( set -x; echo test | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS} ) 2>&1`"; res0=$? AS_IF([test x"$res0" != x0], [ AC_MSG_NOTICE([FAILED CMD: ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS}]) AC_MSG_NOTICE([aspell result ($res0) and output: $out0]) @@ -126,7 +126,7 @@ if test -z "${nut_have_aspell_seen}"; then AC_MSG_RESULT(no) AC_MSG_CHECKING([if detected aspell configuration works with built-in paths (tweaked one finds wrong binary modules)]) ASPELL_NUT_TEXMODE_ARGS="-t" - out0="`LANG=C; LC_ALL=C; export LANG; export LC_ALL; exec -- 2>&1; set -x; echo test | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS}`"; res0=$? + out0="`LANG=C; LC_ALL=C; export LANG; export LC_ALL; ( set -x; echo test | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS} ) 2>&1`"; res0=$? AS_IF([test x"$res0" = x0], [ASPELL_FILTER_TEX_PATH=""], [ AC_MSG_NOTICE([FAILED CMD: ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS}]) AC_MSG_NOTICE([aspell result ($res0) and output: $out0]) diff --git a/m4/nut_check_bool.m4 b/m4/nut_check_bool.m4 new file mode 100644 index 0000000000..f77d345fbc --- /dev/null +++ b/m4/nut_check_bool.m4 @@ -0,0 +1,378 @@ +dnl # For the sake of portability, check if the system offers a "bool" or a +dnl # "bool_t" type, and "true"/"false" values for it, and try to determine +dnl # how exactly these are spelled and implemented. For consumers in the +dnl # codebase, this plays along with "includes/nut_bool.h" header. +dnl # Ideally the just exists and provides reasonable types and +dnl # values (all lower-case), per the standard, which we would just alias +dnl # as "nut_bool_t" and use. +dnl # See also https://en.cppreference.com/w/cpp/header/cstdbool for more +dnl # info about what should be available where per standard approach. + +dnl # Copyright (C) 2024 by Jim Klimov + +AC_DEFUN([NUT_CHECK_BOOL], +[ +if test -z "${nut_check_bool_seen}"; then + nut_check_bool_seen="yes" + + AC_MSG_NOTICE([Checking below whether bool types are provided by system headers and how]) + + AC_LANG_PUSH([C++]) + AC_CHECK_HEADERS_ONCE([cstdbool]) + AC_LANG_POP([C++]) + + AC_LANG_PUSH([C]) + + dnl # Check for existing definition of boolean type (should be stdbool.h, but...) + AC_CHECK_HEADERS_ONCE([stdbool.h]) + + myINCLUDE_STDBOOL=" +#ifdef HAVE_STDBOOL_H +# include +#endif +" + + dnl # Below we would check for C99 built-in "_Bool", and for "bool", + dnl # "boolean" and/or "bool_t" type names with various spellings + dnl # that may come from headers like + + FOUND__BOOL_TYPE="" + FOUND__BOOL_IMPLEM_STR="" + dnl # _bool + HAVE__BOOL_TYPE_LOWERCASE=false + dnl # _BOOL + HAVE__BOOL_TYPE_UPPERCASE=false + dnl # _Bool + HAVE__BOOL_TYPE_CAMELCASE=false + + FOUND_BOOL_TYPE="" + FOUND_BOOL_IMPLEM_STR="" + dnl # bool + HAVE_BOOL_TYPE_LOWERCASE=false + dnl # BOOL + HAVE_BOOL_TYPE_UPPERCASE=false + dnl # Bool + HAVE_BOOL_TYPE_CAMELCASE=false + + FOUND_BOOLEAN_TYPE="" + FOUND_BOOLEAN_IMPLEM_STR="" + dnl # boolean + HAVE_BOOLEAN_TYPE_LOWERCASE=false + dnl # BOOLEAN + HAVE_BOOLEAN_TYPE_UPPERCASE=false + dnl # Boolean + HAVE_BOOLEAN_TYPE_CAMELCASE=false + + FOUND_BOOL_T_TYPE="" + FOUND_BOOL_T_IMPLEM_STR="" + dnl # bool_t + HAVE_BOOL_T_TYPE_LOWERCASE=false + dnl # BOOL_T + HAVE_BOOL_T_TYPE_UPPERCASE=false + dnl # Bool_t? + HAVE_BOOL_T_TYPE_CAMELCASE=false + + FOUND__BOOL_VALUE_TRUE="" + FOUND__BOOL_VALUE_FALSE="" + dnl # true/false + HAVE__BOOL_VALUE_LOWERCASE=false + dnl # TRUE/FALSE + HAVE__BOOL_VALUE_UPPERCASE=false + dnl # True/False + HAVE__BOOL_VALUE_CAMELCASE=false + + FOUND_BOOL_VALUE_TRUE="" + FOUND_BOOL_VALUE_FALSE="" + HAVE_BOOL_VALUE_LOWERCASE=false + HAVE_BOOL_VALUE_UPPERCASE=false + HAVE_BOOL_VALUE_CAMELCASE=false + + FOUND_BOOLEAN_VALUE_TRUE="" + FOUND_BOOLEAN_VALUE_FALSE="" + HAVE_BOOLEAN_VALUE_LOWERCASE=false + HAVE_BOOLEAN_VALUE_UPPERCASE=false + HAVE_BOOLEAN_VALUE_CAMELCASE=false + + FOUND_BOOL_T_VALUE_TRUE="" + FOUND_BOOL_T_VALUE_FALSE="" + HAVE_BOOL_T_VALUE_LOWERCASE=false + HAVE_BOOL_T_VALUE_UPPERCASE=false + HAVE_BOOL_T_VALUE_CAMELCASE=false + + HAVE__BOOL_IMPLEM_MACRO=false + HAVE__BOOL_IMPLEM_INT=false + HAVE__BOOL_IMPLEM_ENUM=false + + HAVE_BOOL_IMPLEM_MACRO=false + HAVE_BOOL_IMPLEM_INT=false + HAVE_BOOL_IMPLEM_ENUM=false + + HAVE_BOOLEAN_IMPLEM_MACRO=false + HAVE_BOOLEAN_IMPLEM_INT=false + HAVE_BOOLEAN_IMPLEM_ENUM=false + + HAVE_BOOL_T_IMPLEM_MACRO=false + HAVE_BOOL_T_IMPLEM_INT=false + HAVE_BOOL_T_IMPLEM_ENUM=false + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [_bool b])], [HAVE__BOOL_TYPE_LOWERCASE=true; FOUND__BOOL_TYPE="_bool"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [_BOOL b])], [HAVE__BOOL_TYPE_UPPERCASE=true; FOUND__BOOL_TYPE="_BOOL"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [_Bool b])], [HAVE__BOOL_TYPE_CAMELCASE=true; FOUND__BOOL_TYPE="_Bool"]) + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [bool b])], [HAVE_BOOL_TYPE_LOWERCASE=true; FOUND_BOOL_TYPE="bool"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [BOOL b])], [HAVE_BOOL_TYPE_UPPERCASE=true; FOUND_BOOL_TYPE="BOOL"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [Bool b])], [HAVE_BOOL_TYPE_CAMELCASE=true; FOUND_BOOL_TYPE="Bool"]) + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [boolean b])], [HAVE_BOOLEAN_TYPE_LOWERCASE=true; FOUND_BOOLEAN_TYPE="boolean"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [BOOLEAN b])], [HAVE_BOOLEAN_TYPE_UPPERCASE=true; FOUND_BOOLEAN_TYPE="BOOLEAN"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [Boolean b])], [HAVE_BOOLEAN_TYPE_CAMELCASE=true; FOUND_BOOLEAN_TYPE="Boolean"]) + + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [bool_t b])], [HAVE_BOOL_T_TYPE_LOWERCASE=true; FOUND_BOOL_T_TYPE="bool_t"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [BOOL_T b])], [HAVE_BOOL_T_TYPE_UPPERCASE=true; FOUND_BOOL_T_TYPE="BOOL_T"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [Bool_t b])], [HAVE_BOOL_T_TYPE_CAMELCASE=true; FOUND_BOOL_T_TYPE="Bool_t"]) + + AS_IF([test x"${FOUND__BOOL_TYPE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND__BOOL_TYPE} bT = true, bF = false])], [HAVE__BOOL_VALUE_LOWERCASE=true; FOUND__BOOL_VALUE_TRUE="true"; FOUND__BOOL_VALUE_FALSE="false"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND__BOOL_TYPE} bT = TRUE, bF = FALSE])], [HAVE__BOOL_VALUE_UPPERCASE=true; FOUND__BOOL_VALUE_TRUE="TRUE"; FOUND__BOOL_VALUE_FALSE="FALSE"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND__BOOL_TYPE} bT = True, bF = False])], [HAVE__BOOL_VALUE_CAMELCASE=true; FOUND__BOOL_VALUE_TRUE="True"; FOUND__BOOL_VALUE_FALSE="False"]) + ]) + + AS_IF([test x"${FOUND_BOOL_TYPE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOL_TYPE} bT = true, bF = false])], [HAVE_BOOL_VALUE_LOWERCASE=true; FOUND_BOOL_VALUE_TRUE="true"; FOUND_BOOL_VALUE_FALSE="false"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOL_TYPE} bT = TRUE, bF = FALSE])], [HAVE_BOOL_VALUE_UPPERCASE=true; FOUND_BOOL_VALUE_TRUE="TRUE"; FOUND_BOOL_VALUE_FALSE="FALSE"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOL_TYPE} bT = True, bF = False])], [HAVE_BOOL_VALUE_CAMELCASE=true; FOUND_BOOL_VALUE_TRUE="True"; FOUND_BOOL_VALUE_FALSE="False"]) + ]) + + AS_IF([test x"${FOUND_BOOLEAN_TYPE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOLEAN_TYPE} bT = true, bF = false])], [HAVE_BOOLEAN_VALUE_LOWERCASE=true; FOUND_BOOLEAN_VALUE_TRUE="true"; FOUND_BOOL_VALUE_FALSE="false"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOLEAN_TYPE} bT = TRUE, bF = FALSE])], [HAVE_BOOLEAN_VALUE_UPPERCASE=true; FOUND_BOOLEAN_VALUE_TRUE="TRUE"; FOUND_BOOL_VALUE_FALSE="FALSE"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOLEAN_TYPE} bT = True, bF = False])], [HAVE_BOOLEAN_VALUE_CAMELCASE=true; FOUND_BOOLEAN_VALUE_TRUE="True"; FOUND_BOOL_VALUE_FALSE="False"]) + ]) + + AS_IF([test x"${FOUND_BOOL_T_TYPE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOL_T_TYPE} b = true, bF = false])], [HAVE_BOOL_T_VALUE_LOWERCASE=true; FOUND_BOOL_T_VALUE_TRUE="true"; FOUND_BOOL_T_VALUE_FALSE="false"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOL_T_TYPE} b = TRUE, bF = FALSE])], [HAVE_BOOL_T_VALUE_UPPERCASE=true; FOUND_BOOL_T_VALUE_TRUE="TRUE"; FOUND_BOOL_T_VALUE_FALSE="FALSE"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [${FOUND_BOOL_T_TYPE} b = True, bF = False])], [HAVE_BOOL_T_VALUE_CAMELCASE=true; FOUND_BOOL_T_VALUE_TRUE="True"; FOUND_BOOL_T_VALUE_FALSE="False"]) + ]) + + dnl # FIXME: Some more diligent checks for signed/unsigned int/char/... + dnl # type details? e.g. via sizeof, assignment to negatives, etc. + AS_IF([test x"${FOUND__BOOL_TYPE}" = x && test x"${FOUND_BOOL_TYPE}" = x && test x"${FOUND_BOOLEAN_TYPE}" = x && test x"${FOUND_BOOL_T_TYPE}" = x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [int bT = true, bF = false])], [FOUND_BOOL_TYPE="int"; HAVE_BOOL_VALUE_LOWERCASE=true; FOUND_BOOL_VALUE_TRUE="true"; FOUND_BOOL_VALUE_FALSE="false"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [int bT = TRUE, bF = FALSE])], [FOUND_BOOL_TYPE="int"; HAVE_BOOL_VALUE_UPPERCASE=true; FOUND_BOOL_VALUE_TRUE="TRUE"; FOUND_BOOL_VALUE_FALSE="FALSE"]) + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [int bT = True, bF = False])], [FOUND_BOOL_TYPE="int"; HAVE_BOOL_VALUE_CAMELCASE=true; FOUND_BOOL_VALUE_TRUE="True"; FOUND_BOOL_VALUE_FALSE="False"]) + ]) + + dnl # Assume there are only 3 implementation options we can discern here + + dnl #################################################################### + + AS_IF([test x"${FOUND__BOOL_TYPE}" != x && test x"${FOUND__BOOL_VALUE_TRUE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +#ifndef ${FOUND__BOOL_VALUE_TRUE} +#error "${FOUND__BOOL_VALUE_TRUE} is not a macro +#endif +#ifndef ${FOUND__BOOL_VALUE_FALSE} +#error "${FOUND__BOOL_VALUE_FALSE} is not a macro +#endif + ])], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +${FOUND__BOOL_TYPE} bT = ${FOUND__BOOL_VALUE_TRUE}; +bT = 42 + ])], [HAVE__BOOL_IMPLEM_INT=true; FOUND__BOOL_IMPLEM="number"], [HAVE__BOOL_IMPLEM_ENUM=true; FOUND__BOOL_IMPLEM="enum"]) + ], [HAVE__BOOL_IMPLEM_MACRO=true; FOUND__BOOL_IMPLEM="macro"]) + + dnl # Final check + AX_RUN_OR_LINK_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +${FOUND__BOOL_TYPE} b = ${FOUND__BOOL_VALUE_TRUE}; +int ret = 0; + +if (!(!b == ${FOUND__BOOL_VALUE_FALSE})) ret = 1; +if (!(b != ${FOUND__BOOL_VALUE_FALSE})) ret = 2; +if (!(!b != ${FOUND__BOOL_VALUE_TRUE})) ret = 3; +if (!b) ret = 4; +if (ret) + return ret +/* no ";return 0;" here - autoconf adds one */ + ])], + [ dnl # All tests passed, remember this + AC_MSG_NOTICE([Detected a semantically usable "_Bool"-like type name ${FOUND__BOOL_TYPE} implemented as ${FOUND__BOOL_IMPLEM} with boolean values '${FOUND__BOOL_VALUE_TRUE}' and '${FOUND__BOOL_VALUE_FALSE}']) + AC_DEFINE_UNQUOTED([FOUND__BOOL_TYPE], [${FOUND__BOOL_TYPE}], [C spelling of _Bool type]) + AC_DEFINE_UNQUOTED([FOUND__BOOL_IMPLEM_STR], ["${FOUND__BOOL_IMPLEM}"], [String spelling of _Bool type implementation]) + AC_DEFINE_UNQUOTED([FOUND__BOOL_VALUE_TRUE], [${FOUND__BOOL_VALUE_TRUE}], [C spelling of _Bool type true value]) + AC_DEFINE_UNQUOTED([FOUND__BOOL_VALUE_FALSE], [${FOUND__BOOL_VALUE_FALSE}], [C spelling of _Bool type false value]) + + AS_IF([${HAVE__BOOL_TYPE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_TYPE_LOWERCASE], [1], [Name of ${FOUND__BOOL_TYPE} is defined as lower-case token])]) + AS_IF([${HAVE__BOOL_TYPE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_TYPE_UPPERCASE], [1], [Name of ${FOUND__BOOL_TYPE} is defined as upper-case token])]) + AS_IF([${HAVE__BOOL_TYPE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_TYPE_CAMELCASE], [1], [Name of ${FOUND__BOOL_TYPE} is defined as camel-case token])]) + + AS_IF([${HAVE__BOOL_VALUE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_VALUE_LOWERCASE], [1], [Boolean values of ${FOUND__BOOL_TYPE} are defined as lower-case tokens])]) + AS_IF([${HAVE__BOOL_VALUE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_VALUE_UPPERCASE], [1], [Boolean values of ${FOUND__BOOL_TYPE} are defined as upper-case tokens])]) + AS_IF([${HAVE__BOOL_VALUE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_VALUE_CAMELCASE], [1], [Boolean values of ${FOUND__BOOL_TYPE} are defined as camel-case tokens])]) + + AS_IF([${HAVE__BOOL_IMPLEM_INT}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_IMPLEM_INT], [1], [Boolean type ${FOUND__BOOL_TYPE} is defined as a general-purpose number type (int)])]) + AS_IF([${HAVE__BOOL_IMPLEM_ENUM}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_IMPLEM_ENUM], [1], [Boolean type ${FOUND__BOOL_TYPE} is defined as an enum with specific values allowed])]) + AS_IF([${HAVE__BOOL_IMPLEM_MACRO}], [AC_DEFINE_UNQUOTED([HAVE__BOOL_IMPLEM_MACRO], [1], [Boolean values of ${FOUND__BOOL_TYPE} are defined as a preprocessor macro (type/implem is questionable)])]) + ], + [AC_MSG_NOTICE([Detected a "_Bool"-like type name ${FOUND__BOOL_TYPE}, but it was not semantically usable])]) + ], [AC_MSG_NOTICE([A "_Bool"-like type name or its useful values were not detected])]) + + dnl #################################################################### + + AS_IF([test x"${FOUND_BOOL_TYPE}" != x && test x"${FOUND_BOOL_VALUE_TRUE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +#ifndef ${FOUND_BOOL_VALUE_TRUE} +#error "${FOUND_BOOL_VALUE_TRUE} is not a macro +#endif +#ifndef ${FOUND_BOOL_VALUE_FALSE} +#error "${FOUND_BOOL_VALUE_FALSE} is not a macro +#endif + ])], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +${FOUND_BOOL_TYPE} bT = ${FOUND_BOOL_VALUE_TRUE}; +bT = 42 + ])], [HAVE_BOOL_IMPLEM_INT=true; FOUND_BOOL_IMPLEM="number"], [HAVE_BOOL_IMPLEM_ENUM=true; FOUND_BOOL_IMPLEM="enum"]) + ], [HAVE_BOOL_IMPLEM_MACRO=true; FOUND_BOOL_IMPLEM="macro"]) + + dnl # Final check + AX_RUN_OR_LINK_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +${FOUND_BOOL_TYPE} b = ${FOUND_BOOL_VALUE_TRUE}; +int ret = 0; + +if (!(!b == ${FOUND_BOOL_VALUE_FALSE})) ret = 1; +if (!(b != ${FOUND_BOOL_VALUE_FALSE})) ret = 2; +if (!(!b != ${FOUND_BOOL_VALUE_TRUE})) ret = 3; +if (!b) ret = 4; +if (ret) + return ret +/* no ";return 0;" here - autoconf adds one */ + ])], + [ dnl # All tests passed, remember this + AC_MSG_NOTICE([Detected a semantically usable "bool"-like type name ${FOUND_BOOL_TYPE} implemented as ${FOUND_BOOL_IMPLEM} with boolean values '${FOUND_BOOL_VALUE_TRUE}' and '${FOUND_BOOL_VALUE_FALSE}']) + AC_DEFINE_UNQUOTED([FOUND_BOOL_TYPE], [${FOUND_BOOL_TYPE}], [C spelling of bool type]) + AC_DEFINE_UNQUOTED([FOUND_BOOL_IMPLEM_STR], ["${FOUND_BOOL_IMPLEM}"], [String spelling of bool type implementation]) + AC_DEFINE_UNQUOTED([FOUND_BOOL_VALUE_TRUE], [${FOUND_BOOL_VALUE_TRUE}], [C spelling of bool type true value]) + AC_DEFINE_UNQUOTED([FOUND_BOOL_VALUE_FALSE], [${FOUND_BOOL_VALUE_FALSE}], [C spelling of bool type false value]) + + AS_IF([${HAVE_BOOL_TYPE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_TYPE_LOWERCASE], [1], [Name of ${FOUND_BOOL_TYPE} is defined as lower-case token])]) + AS_IF([${HAVE_BOOL_TYPE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_TYPE_UPPERCASE], [1], [Name of ${FOUND_BOOL_TYPE} is defined as upper-case token])]) + AS_IF([${HAVE_BOOL_TYPE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_TYPE_CAMELCASE], [1], [Name of ${FOUND_BOOL_TYPE} is defined as camel-case token])]) + + AS_IF([${HAVE_BOOL_VALUE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_VALUE_LOWERCASE], [1], [Boolean values of ${FOUND_BOOL_TYPE} are defined as lower-case tokens])]) + AS_IF([${HAVE_BOOL_VALUE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_VALUE_UPPERCASE], [1], [Boolean values of ${FOUND_BOOL_TYPE} are defined as upper-case tokens])]) + AS_IF([${HAVE_BOOL_VALUE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_VALUE_CAMELCASE], [1], [Boolean values of ${FOUND_BOOL_TYPE} are defined as camel-case tokens])]) + + AS_IF([${HAVE_BOOL_IMPLEM_INT}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_IMPLEM_INT], [1], [Boolean type ${FOUND_BOOL_TYPE} is defined as a general-purpose number type (int)])]) + AS_IF([${HAVE_BOOL_IMPLEM_ENUM}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_IMPLEM_ENUM], [1], [Boolean type ${FOUND_BOOL_TYPE} is defined as an enum with specific values allowed])]) + AS_IF([${HAVE_BOOL_IMPLEM_MACRO}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_IMPLEM_MACRO], [1], [Boolean values of ${FOUND_BOOL_TYPE} are defined as a preprocessor macro (type/implem is questionable)])]) + ], + [AC_MSG_NOTICE([Detected a "bool"-like type name ${FOUND_BOOL_TYPE}, but it was not semantically usable])]) + ], [AC_MSG_NOTICE([A "bool"-like type name or its useful values were not detected])]) + + dnl #################################################################### + + AS_IF([test x"${FOUND_BOOLEAN_TYPE}" != x && test x"${FOUND_BOOLEAN_VALUE_TRUE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +#ifndef ${FOUND_BOOLEAN_VALUE_TRUE} +#error "${FOUND_BOOLEAN_VALUE_TRUE} is not a macro +#endif +#ifndef ${FOUND_BOOLEAN_VALUE_FALSE} +#error "${FOUND_BOOLEAN_VALUE_FALSE} is not a macro +#endif + ])], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +${FOUND_BOOLEAN_TYPE} bT = ${FOUND_BOOLEAN_VALUE_TRUE}; +bT = 42 + ])], [HAVE_BOOLEAN_IMPLEM_INT=true; FOUND_BOOLEAN_IMPLEM="number"], [HAVE_BOOLEAN_IMPLEM_ENUM=true; FOUND_BOOLEAN_IMPLEM="enum"]) + ], [HAVE_BOOLEAN_IMPLEM_MACRO=true; FOUND_BOOLEAN_IMPLEM="macro"]) + + dnl # Final check + AX_RUN_OR_LINK_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +${FOUND_BOOLEAN_TYPE} b = ${FOUND_BOOLEAN_VALUE_TRUE}; +int ret = 0; + +if (!(!b == ${FOUND_BOOLEAN_VALUE_FALSE})) ret = 1; +if (!(b != ${FOUND_BOOLEAN_VALUE_FALSE})) ret = 2; +if (!(!b != ${FOUND_BOOLEAN_VALUE_TRUE})) ret = 3; +if (!b) ret = 4; +if (ret) + return ret +/* no ";return 0;" here - autoconf adds one */ + ])], + [ dnl # All tests passed, remember this + AC_MSG_NOTICE([Detected a semantically usable "boolean"-like type name ${FOUND_BOOLEAN_TYPE} implemented as ${FOUND_BOOLEAN_IMPLEM} with boolean values '${FOUND_BOOLEAN_VALUE_TRUE}' and '${FOUND_BOOL_VALUE_FALSE}']) + AC_DEFINE_UNQUOTED([FOUND_BOOLEAN_TYPE], [${FOUND_BOOLEAN_TYPE}], [C spelling of boolean type]) + AC_DEFINE_UNQUOTED([FOUND_BOOLEAN_IMPLEM_STR], ["${FOUND_BOOLEAN_IMPLEM}"], [String spelling of boolean type implementation]) + AC_DEFINE_UNQUOTED([FOUND_BOOLEAN_VALUE_TRUE], [${FOUND_BOOLEAN_VALUE_TRUE}], [C spelling of boolean type true value]) + AC_DEFINE_UNQUOTED([FOUND_BOOLEAN_VALUE_FALSE], [${FOUND_BOOLEAN_VALUE_FALSE}], [C spelling of boolean type false value]) + + AS_IF([${HAVE_BOOLEAN_TYPE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_TYPE_LOWERCASE], [1], [Name of ${FOUND_BOOLEAN_TYPE} is defined as lower-case token])]) + AS_IF([${HAVE_BOOLEAN_TYPE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_TYPE_UPPERCASE], [1], [Name of ${FOUND_BOOLEAN_TYPE} is defined as upper-case token])]) + AS_IF([${HAVE_BOOLEAN_TYPE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_TYPE_CAMELCASE], [1], [Name of ${FOUND_BOOLEAN_TYPE} is defined as camel-case token])]) + + AS_IF([${HAVE_BOOLEAN_VALUE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_VALUE_LOWERCASE], [1], [Boolean values of ${FOUND_BOOLEAN_TYPE} are defined as lower-case tokens])]) + AS_IF([${HAVE_BOOLEAN_VALUE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_VALUE_UPPERCASE], [1], [Boolean values of ${FOUND_BOOLEAN_TYPE} are defined as upper-case tokens])]) + AS_IF([${HAVE_BOOLEAN_VALUE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_VALUE_CAMELCASE], [1], [Boolean values of ${FOUND_BOOLEAN_TYPE} are defined as camel-case tokens])]) + + AS_IF([${HAVE_BOOLEAN_IMPLEM_INT}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_IMPLEM_INT], [1], [Boolean type ${FOUND_BOOLEAN_TYPE} is defined as a general-purpose number type (int)])]) + AS_IF([${HAVE_BOOLEAN_IMPLEM_ENUM}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_IMPLEM_ENUM], [1], [Boolean type ${FOUND_BOOLEAN_TYPE} is defined as an enum with specific values allowed])]) + AS_IF([${HAVE_BOOLEAN_IMPLEM_MACRO}], [AC_DEFINE_UNQUOTED([HAVE_BOOLEAN_IMPLEM_MACRO], [1], [Boolean values of ${FOUND_BOOLEAN_TYPE} are defined as a preprocessor macro (type/implem is questionable)])]) + ], + [AC_MSG_NOTICE([Detected a "boolean"-like type name ${FOUND_BOOLEAN_TYPE}, but it was not semantically usable])]) + ], [AC_MSG_NOTICE([A "boolean"-like type name or its useful values were not detected])]) + + dnl #################################################################### + + AS_IF([test x"${FOUND_BOOL_T_TYPE}" != x && test x"${FOUND_BOOL_T_VALUE_TRUE}" != x], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +#ifndef ${FOUND_BOOL_T_VALUE_TRUE} +#error "${FOUND_BOOL_T_VALUE_TRUE} is not a macro +#endif + ])], [ + AC_COMPILE_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_T_STDBOOL}], [ +${FOUND_BOOL_T_TYPE} bT = ${FOUND_BOOL_T_VALUE_TRUE}; +bT = 42 + ])], [HAVE_BOOL_T_IMPLEM_INT=true; FOUND_BOOL_T_IMPLEM="number"], [HAVE_BOOL_T_IMPLEM_ENUM=true; FOUND_BOOL_T_IMPLEM="enum"]) + ], [HAVE_BOOL_T_IMPLEM_MACRO=true; FOUND_BOOL_T_IMPLEM="macro"]) + + dnl # Final check + AX_RUN_OR_LINK_IFELSE([AC_LANG_PROGRAM([${myINCLUDE_STDBOOL}], [ +${FOUND_BOOL_T_TYPE} b = ${FOUND_BOOL_T_VALUE_TRUE}; +int ret = 0; + +if (!(!b == ${FOUND_BOOL_T_VALUE_FALSE})) ret = 1; +if (!(b != ${FOUND_BOOL_T_VALUE_FALSE})) ret = 2; +if (!(!b != ${FOUND_BOOL_T_VALUE_TRUE})) ret = 3; +if (!b) ret = 4; +if (ret) + return ret +/* no ";return 0;" here - autoconf adds one */ + ])], + [ dnl # All tests passed, remember this + AC_MSG_NOTICE([Detected a semantically usable "bool_t"-like type name ${FOUND_BOOL_T_TYPE} implemented as ${FOUND_BOOL_T_IMPLEM} with boolean values '${FOUND_BOOL_T_VALUE_TRUE}' and '${FOUND_BOOL_T_VALUE_FALSE}']) + AC_DEFINE_UNQUOTED([FOUND_BOOL_T_TYPE], [${FOUND_BOOL_T_TYPE}], [C spelling of bool_t type]) + AC_DEFINE_UNQUOTED([FOUND_BOOL_T_IMPLEM_STR], ["${FOUND_BOOL_T_IMPLEM}"], [String spelling of bool_t type implementation]) + AC_DEFINE_UNQUOTED([FOUND_BOOL_T_VALUE_TRUE], [${FOUND_BOOL_T_VALUE_TRUE}], [C spelling of bool_t type true value]) + AC_DEFINE_UNQUOTED([FOUND_BOOL_T_VALUE_FALSE], [${FOUND_BOOL_T_VALUE_FALSE}], [C spelling of bool_t type false value]) + + AS_IF([${HAVE_BOOL_T_TYPE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_TYPE_LOWERCASE], [1], [Name of ${FOUND_BOOL_T_TYPE} is defined as lower-case token])]) + AS_IF([${HAVE_BOOL_T_TYPE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_TYPE_UPPERCASE], [1], [Name of ${FOUND_BOOL_T_TYPE} is defined as upper-case token])]) + AS_IF([${HAVE_BOOL_T_TYPE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_TYPE_CAMELCASE], [1], [Name of ${FOUND_BOOL_T_TYPE} is defined as camel-case token])]) + + AS_IF([${HAVE_BOOL_T_VALUE_LOWERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_VALUE_LOWERCASE], [1], [Boolean values of ${FOUND_BOOL_T_TYPE} are defined as lower-case tokens])]) + AS_IF([${HAVE_BOOL_T_VALUE_UPPERCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_VALUE_UPPERCASE], [1], [Boolean values of ${FOUND_BOOL_T_TYPE} are defined as upper-case tokens])]) + AS_IF([${HAVE_BOOL_T_VALUE_CAMELCASE}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_VALUE_CAMELCASE], [1], [Boolean values of ${FOUND_BOOL_T_TYPE} are defined as camel-case tokens])]) + + AS_IF([${HAVE_BOOL_T_IMPLEM_INT}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_IMPLEM_INT], [1], [Boolean type ${FOUND_BOOL_T_TYPE} is defined as a general-purpose number type (int)])]) + AS_IF([${HAVE_BOOL_T_IMPLEM_ENUM}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_IMPLEM_ENUM], [1], [Boolean type ${FOUND_BOOL_T_TYPE} is defined as an enum with specific values allowed])]) + AS_IF([${HAVE_BOOL_T_IMPLEM_MACRO}], [AC_DEFINE_UNQUOTED([HAVE_BOOL_T_IMPLEM_MACRO], [1], [Boolean values of ${FOUND_BOOL_T_TYPE} are defined as a preprocessor macro (type/implem is questionable)])]) + ], + [AC_MSG_NOTICE([Detected a "bool_t"-like type name ${FOUND_BOOL_T_TYPE}, but it was not semantically usable])]) + ], [AC_MSG_NOTICE([A "bool_t"-like type name or its useful values were not detected])]) + + dnl #################################################################### + + AC_LANG_POP([C]) + +fi +]) diff --git a/m4/nut_check_headers_windows.m4 b/m4/nut_check_headers_windows.m4 index ce0286d045..4d52a15961 100644 --- a/m4/nut_check_headers_windows.m4 +++ b/m4/nut_check_headers_windows.m4 @@ -208,3 +208,41 @@ AC_DEFUN([NUT_CHECK_HEADER_WS2TCPIP], [ esac AM_CONDITIONAL(HAVE_WS2TCPIP_H, test "x$nut_cv_header_ws2tcpip_h" = xyes) ]) + +dnl NUT_CHECK_HEADER_IPHLPAPI +dnl ------------------------------------------------- +dnl Check for compilable and valid iphlpapi.h header + +AC_DEFUN([NUT_CHECK_HEADER_IPHLPAPI], [ + AC_REQUIRE([NUT_CHECK_HEADER_WINSOCK2])dnl + AC_CACHE_CHECK([for iphlpapi.h], [nut_cv_header_iphlpapi_h], [ + AC_LANG_PUSH([C]) + AC_COMPILE_IFELSE([ + AC_LANG_PROGRAM([[ +#undef inline +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include + ]],[[ + PIP_ADAPTER_ADDRESSES pAddresses = NULL; + IP_ADAPTER_PREFIX *pPrefix = NULL; + PIP_ADAPTER_INFO pAdapter = NULL; + ]]) + ],[ + nut_cv_header_iphlpapi_h="yes" + ],[ + nut_cv_header_iphlpapi_h="no" + ]) + AC_LANG_POP([C]) + ]) + AS_CASE([$nut_cv_header_iphlpapi_h], + [yes], [ + AC_DEFINE_UNQUOTED(HAVE_IPHLPAPI_H, 1, + [Define to 1 if you have the iphlpapi.h header file.]) + ] + ) + AM_CONDITIONAL(HAVE_IPHLPAPI_H, test "x$nut_cv_header_iphlpapi_h" = xyes) +]) diff --git a/m4/nut_check_libavahi.m4 b/m4/nut_check_libavahi.m4 index d0c4989d61..feddcce281 100644 --- a/m4/nut_check_libavahi.m4 +++ b/m4/nut_check_libavahi.m4 @@ -83,6 +83,22 @@ if test -z "${nut_have_avahi_seen}"; then LIBAVAHI_CFLAGS="${CFLAGS}" LIBAVAHI_LIBS="${LIBS}" fi + + dnl Help ltdl if we can (nut-scanner etc.) + for TOKEN in $LIBS ; do + AS_CASE(["${TOKEN}"], + [-l*avahi*client*], [ + AX_REALPATH_LIB([${TOKEN}], [SOPATH_LIBAVAHI], []) + AS_IF([test -n "${SOPATH_LIBAVAHI}" && test -s "${SOPATH_LIBAVAHI}"], [ + AC_DEFINE_UNQUOTED([SOPATH_LIBAVAHI],["${SOPATH_LIBAVAHI}"],[Path to dynamic library on build system]) + SOFILE_LIBAVAHI="`basename "$SOPATH_LIBAVAHI"`" + AC_DEFINE_UNQUOTED([SOFILE_LIBAVAHI],["${SOFILE_LIBAVAHI}"],[Base file name of dynamic library on build system]) + break + ]) + ] + ) + done + unset TOKEN fi dnl restore original CFLAGS and LIBS diff --git a/m4/nut_check_libfreeipmi.m4 b/m4/nut_check_libfreeipmi.m4 index 5fad0c6438..45190ea45f 100644 --- a/m4/nut_check_libfreeipmi.m4 +++ b/m4/nut_check_libfreeipmi.m4 @@ -92,6 +92,22 @@ if test -z "${nut_have_libfreeipmi_seen}"; then AC_DEFINE(HAVE_FREEIPMI, 1, [Define if FreeIPMI support is available]) LIBIPMI_CFLAGS="${CFLAGS}" LIBIPMI_LIBS="${LIBS}" + + dnl Help ltdl if we can (nut-scanner etc.) + for TOKEN in $LIBS ; do + AS_CASE(["${TOKEN}"], + [-l*ipmi*], [ + AX_REALPATH_LIB([${TOKEN}], [SOPATH_LIBFREEIPMI], []) + AS_IF([test -n "${SOPATH_LIBFREEIPMI}" && test -s "${SOPATH_LIBFREEIPMI}"], [ + AC_DEFINE_UNQUOTED([SOPATH_LIBFREEIPMI],["${SOPATH_LIBFREEIPMI}"],[Path to dynamic library on build system]) + SOFILE_LIBFREEIPMI="`basename "$SOPATH_LIBFREEIPMI"`" + AC_DEFINE_UNQUOTED([SOFILE_LIBFREEIPMI],["${SOFILE_LIBFREEIPMI}"],[Base file name of dynamic library on build system]) + break + ]) + ] + ) + done + unset TOKEN fi if test "${nut_have_freeipmi_11x_12x}" = "yes"; then diff --git a/m4/nut_check_libgd.m4 b/m4/nut_check_libgd.m4 index a8c7de5d17..b2b56666aa 100644 --- a/m4/nut_check_libgd.m4 +++ b/m4/nut_check_libgd.m4 @@ -141,6 +141,28 @@ if test -z "${nut_have_libgd_seen}"; then ]) ]) + if test "${nut_have_libgd}" = "yes"; then + AC_MSG_CHECKING([whether we can build, link and/or run a program with libgd]) + AC_LANG_PUSH([C]) + AX_RUN_OR_LINK_IFELSE([AC_LANG_PROGRAM([ +#include +#include +#include +], +[ +FILE *tmpf = tmpfile(); +gdImagePtr im = gdImageCreate(64, 128); +int back_color = gdImageColorAllocate(im, 255, 128, 32); +gdImageFilledRectangle(im, 0, 0, 64, 128, back_color); +gdImageColorTransparent(im, back_color); +gdImagePng(im, tmpf ? tmpf : stderr); +gdImageDestroy(im); +] + )], [], [nut_have_libgd=no]) + AC_LANG_POP([C]) + AC_MSG_RESULT([${nut_have_libgd}]) + fi + if test "${nut_have_libgd}" = "yes"; then AC_DEFINE(HAVE_LIBGD, 1, [Define if you have Boutell's libgd installed]) LIBGD_CFLAGS="${CFLAGS}" diff --git a/m4/nut_check_libltdl.m4 b/m4/nut_check_libltdl.m4 index 8c6857ecfe..9c67e93eef 100644 --- a/m4/nut_check_libltdl.m4 +++ b/m4/nut_check_libltdl.m4 @@ -50,12 +50,19 @@ if test -z "${nut_have_libltdl_seen}"; then ], [])dnl No fallback here - we probe suitable libs below AC_MSG_RESULT([${LIBS}]) - AC_CHECK_HEADERS(ltdl.h, [nut_have_libltdl=yes], [nut_have_libltdl=no], [AC_INCLUDES_DEFAULT]) + AC_CHECK_HEADERS(ltdl.h, [nut_have_libltdl=yes], [ + dnl Double-check if we stashed include paths to try above + AS_IF([test -n "$myCFLAGS"], [ + CFLAGS="$myCFLAGS" + AS_UNSET([ac_cv_header_ltdl_h]) + AC_CHECK_HEADERS(ltdl.h, [nut_have_libltdl=yes], [nut_have_libltdl=no], [AC_INCLUDES_DEFAULT]) + ],[nut_have_libltdl=no] + )], [AC_INCLUDES_DEFAULT]) AS_IF([test x"$nut_have_libltdl" = xyes], [ dnl ltdl-number may help find it for MingW DLLs naming AC_SEARCH_LIBS(lt_dlinit, ltdl ltdl-7, [], [ nut_have_libltdl=no - AS_IF([test -n "$myCFLAGS"], [ + AS_IF([test -n "$myCFLAGS" -a x"$myCFLAGS" != x"$CFLAGS"], [ CFLAGS="$myCFLAGS" dnl No ltdl-7 here, this codepath is unlikely on Windows where that matters: AC_SEARCH_LIBS(lt_dlinit, ltdl, [nut_have_libltdl=yes], []) diff --git a/m4/nut_check_libmodbus.m4 b/m4/nut_check_libmodbus.m4 index dbaae4968e..c596bb1edc 100644 --- a/m4/nut_check_libmodbus.m4 +++ b/m4/nut_check_libmodbus.m4 @@ -72,7 +72,12 @@ if test -z "${nut_have_libmodbus_seen}"; then AC_CHECK_FUNCS(modbus_set_byte_timeout, [], [nut_have_libmodbus=no]) AC_CHECK_FUNCS(modbus_set_response_timeout, [], [nut_have_libmodbus=no]) - AC_CHECK_FUNCS(modbus_new_rtu_usb, [nut_have_libmodbus_usb=yes], [nut_have_libmodbus_usb=no]) + AC_CHECK_FUNCS(modbus_new_rtu_usb, [nut_have_libmodbus_usb=yes], [ + AS_IF([test x"${nut_with_usb}" = xyes && test x"${nut_with_modbus}" = xyes && test x"${nut_have_libmodbus}" = xyes ], [ + AC_MSG_WARN([Both --with-modbus and --with-usb were requested, and a libmodbus was found, but it seems to not support USB. You may require a custom build per https://github.com/networkupstools/nut/wiki/APC-UPS-with-Modbus-protocol]) + ]) + nut_have_libmodbus_usb=no + ]) dnl modbus_set_byte_timeout() and modbus_set_response_timeout() dnl in 3.0.x and 3.1.x have different args (since ~2013): the diff --git a/m4/nut_check_libneon.m4 b/m4/nut_check_libneon.m4 index 944f7a356e..015ed7dc71 100644 --- a/m4/nut_check_libneon.m4 +++ b/m4/nut_check_libneon.m4 @@ -78,6 +78,22 @@ if test -z "${nut_have_neon_seen}"; then AC_CHECK_FUNCS(ne_set_connect_timeout ne_sock_connect_timeout) LIBNEON_CFLAGS="${CFLAGS}" LIBNEON_LIBS="${LIBS}" + + dnl Help ltdl if we can (nut-scanner etc.) + for TOKEN in $LIBS ; do + AS_CASE(["${TOKEN}"], + [-l*neon*], [ + AX_REALPATH_LIB([${TOKEN}], [SOPATH_LIBNEON], []) + AS_IF([test -n "${SOPATH_LIBNEON}" && test -s "${SOPATH_LIBNEON}"], [ + AC_DEFINE_UNQUOTED([SOPATH_LIBNEON],["${SOPATH_LIBNEON}"],[Path to dynamic library on build system]) + SOFILE_LIBNEON="`basename "$SOPATH_LIBNEON"`" + AC_DEFINE_UNQUOTED([SOFILE_LIBNEON],["${SOFILE_LIBNEON}"],[Base file name of dynamic library on build system]) + break + ]) + ] + ) + done + unset TOKEN fi dnl restore original CFLAGS and LIBS diff --git a/m4/nut_check_libnetsnmp.m4 b/m4/nut_check_libnetsnmp.m4 index 389ac2d2ff..ed341686d9 100644 --- a/m4/nut_check_libnetsnmp.m4 +++ b/m4/nut_check_libnetsnmp.m4 @@ -79,6 +79,7 @@ if test -z "${nut_have_libnetsnmp_seen}"; then AC_MSG_WARN([did not find either net-snmp-config or pkg-config for net-snmp]) fi + myCFLAGS_SOURCE="" AC_MSG_CHECKING(for Net-SNMP cflags) AC_ARG_WITH(snmp-includes, AS_HELP_STRING([@<:@--with-snmp-includes=CFLAGS@:>@], [include flags for the Net-SNMP library]), @@ -88,17 +89,21 @@ if test -z "${nut_have_libnetsnmp_seen}"; then AC_MSG_ERROR(invalid option --with(out)-snmp-includes - see docs/configure.txt) ;; *) + myCFLAGS_SOURCE="confarg" CFLAGS="${withval}" ;; esac ], [AS_IF(["${prefer_NET_SNMP_CONFIG}"], - [CFLAGS="`${NET_SNMP_CONFIG} --base-cflags 2>/dev/null`"], + [CFLAGS="`${NET_SNMP_CONFIG} --base-cflags 2>/dev/null`" + myCFLAGS_SOURCE="netsnmp-config"], [AS_IF([test x"$have_PKG_CONFIG" = xyes], - [CFLAGS="`$PKG_CONFIG --silence-errors --cflags netsnmp 2>/dev/null`"] + [CFLAGS="`$PKG_CONFIG --silence-errors --cflags netsnmp 2>/dev/null`" + myCFLAGS_SOURCE="pkg-config"], + [myCFLAGS_SOURCE="default"] )] )] ) - AC_MSG_RESULT([${CFLAGS}]) + AC_MSG_RESULT([${CFLAGS} (source: ${myCFLAGS_SOURCE})]) myLIBS_SOURCE="" AC_MSG_CHECKING(for Net-SNMP libs) @@ -124,11 +129,19 @@ if test -z "${nut_have_libnetsnmp_seen}"; then myLIBS_SOURCE="default"])] )] ) - AC_MSG_RESULT([${LIBS}]) + AC_MSG_RESULT([${LIBS} (source: ${myLIBS_SOURCE})]) dnl Check if the Net-SNMP library is usable nut_have_libnetsnmp_static=no - AC_CHECK_HEADERS(net-snmp/net-snmp-config.h, [nut_have_libnetsnmp=yes], [nut_have_libnetsnmp=no], [AC_INCLUDES_DEFAULT]) + nut_have_libnetsnmp=no + AC_CHECK_HEADERS([net-snmp/net-snmp-config.h], + dnl The second header requires the first to be included + [AC_CHECK_HEADERS([net-snmp/net-snmp-includes.h], [nut_have_libnetsnmp=yes], [], + [AC_INCLUDES_DEFAULT + #include + ]) + ], [], [AC_INCLUDES_DEFAULT]) + AC_CHECK_FUNCS(init_snmp, [], [ dnl Probably is dysfunctional, except one case... nut_have_libnetsnmp=no @@ -156,6 +169,7 @@ if test -z "${nut_have_libnetsnmp_seen}"; then ]) ]) AS_UNSET([myLIBS_SOURCE]) + AS_UNSET([myCFLAGS_SOURCE]) AS_IF([test "${nut_have_libnetsnmp}" = "yes"], [ LIBNETSNMP_CFLAGS="${CFLAGS}" @@ -347,7 +361,23 @@ int num = NETSNMP_DRAFT_BLUMENTHAL_AES_04 + 1; /* if defined, NETSNMP_DRAFT_BLUM AC_DEFINE_UNQUOTED(NUT_HAVE_LIBNETSNMP_DRAFT_BLUMENTHAL_AES_04, 0, [Variable or macro by this name is not resolvable]) ]) + dnl Help ltdl if we can (nut-scanner etc.) + for TOKEN in $LIBS ; do + AS_CASE(["${TOKEN}"], + [-l*snmp*], [ + AX_REALPATH_LIB([${TOKEN}], [SOPATH_LIBNETSNMP], []) + AS_IF([test -n "${SOPATH_LIBNETSNMP}" && test -s "${SOPATH_LIBNETSNMP}"], [ + AC_DEFINE_UNQUOTED([SOPATH_LIBNETSNMP],["${SOPATH_LIBNETSNMP}"],[Path to dynamic library on build system]) + SOFILE_LIBNETSNMP="`basename "$SOPATH_LIBNETSNMP"`" + AC_DEFINE_UNQUOTED([SOFILE_LIBNETSNMP],["${SOFILE_LIBNETSNMP}"],[Base file name of dynamic library on build system]) + break + ]) + ] + ) + done + unset TOKEN ]) + AC_LANG_POP([C]) dnl restore original CFLAGS and LIBS diff --git a/m4/nut_check_libregex.m4 b/m4/nut_check_libregex.m4 index 86c2bc1552..8980b25d57 100644 --- a/m4/nut_check_libregex.m4 +++ b/m4/nut_check_libregex.m4 @@ -60,23 +60,19 @@ if test -z "${nut_have_libregex_seen}"; then dnl # Maybe already involved in NUT for Windows builds... nut_have_regex=no AC_CHECK_HEADER([regex.h], - [nut_have_regex=yes - AC_DEFINE([HAVE_REGEX_H], [1], + [AC_DEFINE([HAVE_REGEX_H], [1], [Define to 1 if you have .])]) + AC_CHECK_DECLS([regexec, regcomp], [nut_have_regex=yes], [], [#ifdef HAVE_REGEX_H # include #endif ]) - AC_SEARCH_LIBS([regcomp, regexec], [], [nut_have_regex=yes], [ - AS_IF([test x"$LIBS" = x], [ - dnl Avoid using cached reply for the absent library name - unset ac_cv_search_regcomp__regexec || true - AC_SEARCH_LIBS([regcomp, regexec], [regex], [ - LIBS="-lregex" - nut_have_regex=yes - ]) + AS_IF([test x"${nut_have_regex}" = xyes], [ + nut_have_regex=no + AC_SEARCH_LIBS([regcomp], [regex], [ + AC_SEARCH_LIBS([regcomp], [regex], [nut_have_regex=yes]) ]) ]) diff --git a/m4/nut_check_libsystemd.m4 b/m4/nut_check_libsystemd.m4 index 155add4423..cf7b09ccda 100644 --- a/m4/nut_check_libsystemd.m4 +++ b/m4/nut_check_libsystemd.m4 @@ -13,22 +13,42 @@ if test -z "${nut_have_libsystemd_seen}"; then CFLAGS_ORIG="${CFLAGS}" LIBS_ORIG="${LIBS}" + SYSTEMD_VERSION="none" + + AC_CHECK_TOOL(SYSTEMCTL, systemctl, none) + AS_IF([test x"$have_PKG_CONFIG" = xyes], [dnl See which version of the systemd library (if any) is installed dnl FIXME : Support detection of cflags/ldflags below by legacy dnl discovery if pkgconfig is not there AC_MSG_CHECKING(for libsystemd version via pkg-config) SYSTEMD_VERSION="`$PKG_CONFIG --silence-errors --modversion libsystemd 2>/dev/null`" - if test "$?" != "0" -o -z "${SYSTEMD_VERSION}"; then - SYSTEMD_VERSION="none" - fi + AS_IF([test "$?" != "0" -o -z "${SYSTEMD_VERSION}"], [ + SYSTEMD_VERSION="none" + ]) AC_MSG_RESULT(${SYSTEMD_VERSION} found) - ], - [SYSTEMD_VERSION="none" - AC_MSG_NOTICE([can not check libsystemd settings via pkg-config]) ] ) + AS_IF([test x"${SYSTEMD_VERSION}" = xnone], [ + AS_IF([test x"${SYSTEMCTL}" != xnone], [ + AC_MSG_CHECKING(for libsystemd version via systemctl) + dnl NOTE: Unlike the configure.ac file, in a "pure" + dnl m4 script like this one, we have to escape the + dnl dollar-number references (in awk below) lest they + dnl get seen as m4 function positional parameters. + SYSTEMD_VERSION="`LANG=C LC_ALL=C ${SYSTEMCTL} --version | grep -E '^systemd@<:@ \t@:>@*@<:@0-9@:>@@<:@0-9@:>@*' | awk '{print ''$''2}'`" \ + && test -n "${SYSTEMD_VERSION}" \ + || SYSTEMD_VERSION="none" + AC_MSG_RESULT(${SYSTEMD_VERSION} found) + ]) + ] + ) + + AS_IF([test x"${SYSTEMD_VERSION}" = xnone], [ + AC_MSG_NOTICE([can not check libsystemd settings via pkg-config nor systemctl]) + ]) + AC_MSG_CHECKING(for libsystemd cflags) AC_ARG_WITH(libsystemd-includes, AS_HELP_STRING([@<:@--with-libsystemd-includes=CFLAGS@:>@], [include flags for the systemd library]), @@ -76,11 +96,31 @@ if test -z "${nut_have_libsystemd_seen}"; then AC_CHECK_HEADERS(systemd/sd-daemon.h, [nut_have_libsystemd=yes], [nut_have_libsystemd=no], [AC_INCLUDES_DEFAULT]) AC_CHECK_FUNCS(sd_notify, [], [nut_have_libsystemd=no]) + nut_have_libsystemd_inhibitor=no AS_IF([test x"${nut_have_libsystemd}" = x"yes"], [ dnl Check for additional feature support in library (optional) AC_CHECK_FUNCS(sd_booted sd_watchdog_enabled sd_notify_barrier) LIBSYSTEMD_CFLAGS="${CFLAGS}" LIBSYSTEMD_LIBS="${LIBS}" + + dnl Since systemd 183: https://systemd.io/INHIBITOR_LOCKS/ + dnl ...or 221: https://www.freedesktop.org/software/systemd/man/latest/sd_bus_call_method.html + dnl and some bits even later (e.g. message container reading) + AS_IF([test "$SYSTEMD_VERSION" -ge 221], [ + nut_have_libsystemd_inhibitor=yes + AC_CHECK_HEADERS(systemd/sd-bus.h, [], [nut_have_libsystemd_inhibitor=no], [AC_INCLUDES_DEFAULT]) + AC_CHECK_FUNCS([sd_bus_call_method sd_bus_message_read_basic sd_bus_open_system sd_bus_default_system sd_bus_get_property_trivial], [], [nut_have_libsystemd_inhibitor=no]) + dnl NOTE: In practice we use "p"-suffixed sd_bus_flush_close_unrefp + dnl and sd_bus_message_unrefp methods prepared by a macro in sd-bus.h + AC_CHECK_FUNCS([sd_bus_flush_close_unref sd_bus_message_unref sd_bus_error_free], [], [nut_have_libsystemd_inhibitor=no]) + dnl Optional methods: nicer with them, can do without + AC_CHECK_FUNCS([sd_bus_open_system_with_description sd_bus_set_description]) + dnl For inhibitor per se, we do not have to read containers: + dnl AC_CHECK_FUNCS([sd_bus_message_enter_container sd_bus_message_exit_container]) + ]) + + AC_MSG_CHECKING(for libsystemd inhibitor interface support) + AC_MSG_RESULT([${nut_have_libsystemd_inhibitor}]) ]) dnl restore original CFLAGS and LIBS diff --git a/m4/nut_check_libusb.m4 b/m4/nut_check_libusb.m4 index 186e7c6e2f..236e013355 100644 --- a/m4/nut_check_libusb.m4 +++ b/m4/nut_check_libusb.m4 @@ -88,9 +88,13 @@ if test -z "${nut_have_libusb_seen}"; then AS_IF([test x"${LIBUSB_0_1_VERSION}" != xnone], [ AS_CASE(["${target_os}"], [*mingw*], [ - AC_MSG_NOTICE([mingw builds prefer libusb-0.1(-compat) if available]) - LIBUSB_VERSION="${LIBUSB_0_1_VERSION}" - nut_usb_lib="(libusb-0.1)" + AS_IF([test x"$build" = x"$host"], [ + AC_MSG_NOTICE([mingw/MSYS2 native builds prefer libusb-0.1(-compat) over libusb-1.0 if both are available]) + LIBUSB_VERSION="${LIBUSB_0_1_VERSION}" + nut_usb_lib="(libusb-0.1)" + ],[ + AC_MSG_NOTICE([mingw cross-builds prefer libusb-1.0 over libusb-0.1(-compat) if both are available]) + ]) ]) ] ) @@ -371,14 +375,48 @@ if test -z "${nut_have_libusb_seen}"; then dnl with some value. AS_IF([test "${nut_with_usb}" = "yes" && test "${nut_usb_lib}" = "(libusb-1.0)"], [AC_DEFINE([WITH_LIBUSB_1_0], [1], - [Define to 1 for version 1.0 of the libusb (via pkg-config).])], + [Define to 1 for version 1.0 of the libusb (via pkg-config).]) + + dnl Help ltdl if we can (nut-scanner etc.) + for TOKEN in $LIBS ; do + AS_CASE(["${TOKEN}"], + [-l*usb*], [ + AX_REALPATH_LIB([${TOKEN}], [SOPATH_LIBUSB1], []) + AS_IF([test -n "${SOPATH_LIBUSB1}" && test -s "${SOPATH_LIBUSB1}"], [ + AC_DEFINE_UNQUOTED([SOPATH_LIBUSB1],["${SOPATH_LIBUSB1}"],[Path to dynamic library on build system]) + SOFILE_LIBUSB1="`basename "$SOPATH_LIBUSB1"`" + AC_DEFINE_UNQUOTED([SOFILE_LIBUSB1],["${SOFILE_LIBUSB1}"],[Base file name of dynamic library on build system]) + break + ]) + ] + ) + done + unset TOKEN + ], [AC_DEFINE([WITH_LIBUSB_1_0], [0], [Define to 1 for version 1.0 of the libusb (via pkg-config).])] ) AS_IF([test "${nut_with_usb}" = "yes" && test "${nut_usb_lib}" = "(libusb-0.1)" -o "${nut_usb_lib}" = "(libusb-0.1-config)"], [AC_DEFINE([WITH_LIBUSB_0_1], [1], - [Define to 1 for version 0.1 of the libusb (via pkg-config or libusb-config).])], + [Define to 1 for version 0.1 of the libusb (via pkg-config or libusb-config).]) + + dnl Help ltdl if we can (nut-scanner etc.) + for TOKEN in $LIBS ; do + AS_CASE(["${TOKEN}"], + [-l*usb*], [ + AX_REALPATH_LIB([${TOKEN}], [SOPATH_LIBUSB0], []) + AS_IF([test -n "${SOPATH_LIBUSB0}" && test -s "${SOPATH_LIBUSB0}"], [ + AC_DEFINE_UNQUOTED([SOPATH_LIBUSB0],["${SOPATH_LIBUSB0}"],[Path to dynamic library on build system]) + SOFILE_LIBUSB0="`basename "$SOPATH_LIBUSB0"`" + AC_DEFINE_UNQUOTED([SOFILE_LIBUSB0],["${SOFILE_LIBUSB0}"],[Base file name of dynamic library on build system]) + break + ]) + ] + ) + done + unset TOKEN + ], [AC_DEFINE([WITH_LIBUSB_0_1], [0], [Define to 1 for version 0.1 of the libusb (via pkg-config or libusb-config).])] ) diff --git a/m4/nut_check_pkgconfig.m4 b/m4/nut_check_pkgconfig.m4 index fd9a2449ef..4e2b6b77bd 100644 --- a/m4/nut_check_pkgconfig.m4 +++ b/m4/nut_check_pkgconfig.m4 @@ -5,7 +5,7 @@ dnl do the checking only once. AC_DEFUN([NUT_CHECK_PKGCONFIG], [ -AS_IF([test -z "${nut_have_pkg_config_seen}"], [ + AS_IF([test -z "${nut_have_pkg_config_seen}"], [ nut_have_pkg_config_seen=yes dnl Note that PKG_CONFIG may be a filename, path, @@ -83,8 +83,43 @@ AS_IF([test -z "${nut_have_pkg_config_seen}"], [ PKG_CONFIG="false" ], [AS_IF([test x"$have_PKG_CONFIG_MACROS" = xno], - [AC_MSG_WARN([pkg-config macros are needed to look for further dependencies, but in some cases pkg-config program can be used directly])] - )] + [AC_MSG_WARN([pkg-config macros are needed to look for further dependencies, but in some cases pkg-config program can be used directly])]) + + AC_MSG_CHECKING([for pkg-config envvar PKG_CONFIG_PATH (if passed to configure script)]) + AC_MSG_RESULT([${PKG_CONFIG_PATH}]) + + AC_MSG_CHECKING([for pkg-config reported pc_path]) + myPKG_CONFIG_PC_PATH="`pkg-config --variable pc_path pkg-config`" || myPKG_CONFIG_PC_PATH="" + AC_MSG_RESULT([${myPKG_CONFIG_PC_PATH}]) + + dnl # The piece below is inspired by https://github.com/wxWidgets/wxWidgets/commit/7899850496ba2707c41cc534b51d14ac5b9fdbba + dnl When cross-compiling for another system from Linux, don't use .pc files on + dnl the build system, they are at best useless and can be harmful (e.g. they + dnl may define options inappropriate for the cross-build, resulting in the + dnl failure of all the subsequent tests). + dnl + dnl However do use .pc files for the host libraries that can be found by the + dnl host-specific pkg-config if it was found by PKG_PROG_PKG_CONFIG above. + AS_IF([test x"$build" != x"$host"], [ + AS_CASE(["$PKG_CONFIG"], + [*/"$host"-pkg-config], [], + [AC_MSG_WARN([Not using native pkg-config when cross-compiling.]) + + dnl pkg.m4 forbids the use of PKG_XXX, so undo it here + dnl to avoid autoconf errors. + m4_pattern_allow([PKG_CONFIG_LIBDIR]) + + dnl If pkg-config libdir is already defined, we suppose that + dnl callers know what they're doing and leave it alone, but + dnl if not, set it to a path in which no .pc files will be found. + AS_IF([test x"$PKG_CONFIG_LIBDIR" = x], [ + AC_MSG_WARN([Exporting a hack PKG_CONFIG_LIBDIR to avoid search for .pc files in host paths]) + PKG_CONFIG_LIBDIR=/dev/null + export PKG_CONFIG_LIBDIR + ]) + ]) + ]) + ] ) ]) dnl if nut_have_pkg_config_seen diff --git a/m4/nut_check_python.m4 b/m4/nut_check_python.m4 index b44a5cd5bb..b05057d8eb 100644 --- a/m4/nut_check_python.m4 +++ b/m4/nut_check_python.m4 @@ -47,6 +47,8 @@ AC_DEFUN([NUT_CHECK_PYTHON], PYTHON="" PYTHON_SITE_PACKAGES="" PYTHON_VERSION_REPORT="" + PYTHON_VERSION_INFO_REPORT="" + PYTHON_SYSPATH_REPORT="" AS_CASE([${nut_with_python}], [auto|yes|""], [AC_CHECK_PROGS([PYTHON], [python python3 python2], [_python_runtime])], [no], [PYTHON="no"], @@ -98,7 +100,7 @@ AC_DEFUN([NUT_CHECK_PYTHON], AS_IF([test -n "${PYTHON}" && test "${PYTHON}" != "no"], [ AS_IF([test x"`$PYTHON -c 'import sys; print (sys.version_info >= (2, 6))'`" = xTrue], - [PYTHON_VERSION_REPORT=" (`$PYTHON -c 'import sys; print (sys.version_info)'`)"], + [PYTHON_VERSION_INFO_REPORT=" (`$PYTHON -c 'import sys; print (sys.version_info)'`)"], [AC_MSG_WARN([Version reported by ${PYTHON} was not suitable as python]) PYTHON=no]) ]) @@ -111,10 +113,21 @@ AC_DEFUN([NUT_CHECK_PYTHON], ]) AC_MSG_CHECKING([python interpeter to call]) - AC_MSG_RESULT([${PYTHON}${PYTHON_VERSION_REPORT}]) + AC_MSG_RESULT([${PYTHON}${PYTHON_VERSION_INFO_REPORT}]) AC_SUBST([PYTHON], [${PYTHON}]) AM_CONDITIONAL([HAVE_PYTHON], [test -n "${PYTHON}" && test "${PYTHON}" != "no"]) AS_IF([test -n "${PYTHON}" && test "${PYTHON}" != "no"], [ + AC_MSG_CHECKING([python build sys.version]) + dnl Can have extra lines about compiler used, etc. + PYTHON_VERSION_REPORT="`${PYTHON} -c 'import sys; print(sys.version);' | tr '\n' ' '`" \ + || PYTHON_VERSION_REPORT="" + AC_MSG_RESULT([${PYTHON_VERSION_REPORT}]) + + AC_MSG_CHECKING([python sys.path used to search for modules]) + PYTHON_SYSPATH_REPORT="`${PYTHON} -c 'import sys; print(sys.path);'`" \ + || PYTHON_SYSPATH_REPORT="" + AC_MSG_RESULT([${PYTHON_SYSPATH_REPORT}]) + export PYTHON AC_CACHE_CHECK([python site-packages location], [nut_cv_PYTHON_SITE_PACKAGES], [ nut_cv_PYTHON_SITE_PACKAGES="`${PYTHON} -c 'import site; print(site.getsitepackages().pop(0))'`" @@ -139,10 +152,12 @@ AC_DEFUN([NUT_CHECK_PYTHON2], PYTHON2="" PYTHON2_SITE_PACKAGES="" PYTHON2_VERSION_REPORT="" + PYTHON2_VERSION_INFO_REPORT="" + PYTHON2_SYSPATH_REPORT="" AS_CASE([${nut_with_python2}], [auto|yes|""], [ dnl Cross check --with-python results: - AS_CASE(["${PYTHON_VERSION_REPORT}"], + AS_CASE(["${PYTHON_VERSION_INFO_REPORT}"], [*major=2,*], [ PYTHON2="`${PYTHON} -c 'import sys; print(sys.executable);' 2>/dev/null`" && test -n "${PYTHON2}" || PYTHON2="${PYTHON}" PYTHON2="`realpath "${PYTHON2}" 2>/dev/null`" && test -n "${PYTHON2}" || { @@ -210,7 +225,7 @@ AC_DEFUN([NUT_CHECK_PYTHON2], AS_IF([test -n "${PYTHON2}" && test "${PYTHON2}" != "no"], [ AS_IF([test x"`$PYTHON2 -c 'import sys; print (sys.version_info >= (2, 6) and sys.version_info < (3, 0))'`" = xTrue], - [PYTHON2_VERSION_REPORT=" (`$PYTHON2 -c 'import sys; print (sys.version_info)'`)"], + [PYTHON2_VERSION_INFO_REPORT=" (`$PYTHON2 -c 'import sys; print (sys.version_info)'`)"], [AC_MSG_WARN([Version reported by ${PYTHON2} was not suitable as python2]) PYTHON2=no]) ]) @@ -223,10 +238,21 @@ AC_DEFUN([NUT_CHECK_PYTHON2], ]) AC_MSG_CHECKING([python2 interpeter to call]) - AC_MSG_RESULT([${PYTHON2}${PYTHON2_VERSION_REPORT}]) + AC_MSG_RESULT([${PYTHON2}${PYTHON2_VERSION_INFO_REPORT}]) AC_SUBST([PYTHON2], [${PYTHON2}]) AM_CONDITIONAL([HAVE_PYTHON2], [test -n "${PYTHON2}" && test "${PYTHON2}" != "no"]) AS_IF([test -n "${PYTHON2}" && test "${PYTHON2}" != "no"], [ + AC_MSG_CHECKING([python2 build sys.version]) + dnl Can have extra lines about compiler used, etc. + PYTHON2_VERSION_REPORT="`${PYTHON2} -c 'import sys; print(sys.version);' | tr '\n' ' '`" \ + || PYTHON2_VERSION_REPORT="" + AC_MSG_RESULT([${PYTHON2_VERSION_REPORT}]) + + AC_MSG_CHECKING([python2 sys.path used to search for modules]) + PYTHON2_SYSPATH_REPORT="`${PYTHON2} -c 'import sys; print(sys.path);'`" \ + || PYTHON2_SYSPATH_REPORT="" + AC_MSG_RESULT([${PYTHON2_SYSPATH_REPORT}]) + export PYTHON2 AC_CACHE_CHECK([python2 site-packages location], [nut_cv_PYTHON2_SITE_PACKAGES], [ nut_cv_PYTHON2_SITE_PACKAGES="`${PYTHON2} -c 'import site; print(site.getsitepackages().pop(0))'`" @@ -251,10 +277,12 @@ AC_DEFUN([NUT_CHECK_PYTHON3], PYTHON3="" PYTHON3_SITE_PACKAGES="" PYTHON3_VERSION_REPORT="" + PYTHON3_VERSION_INFO_REPORT="" + PYTHON3_SYSPATH_REPORT="" AS_CASE([${nut_with_python3}], [auto|yes|""], [ dnl Cross check --with-python results: - AS_CASE(["${PYTHON_VERSION_REPORT}"], + AS_CASE(["${PYTHON_VERSION_INFO_REPORT}"], [*major=3,*], [ PYTHON3="`${PYTHON} -c 'import sys; print(sys.executable);' 2>/dev/null`" && test -n "${PYTHON3}" || PYTHON3="${PYTHON}" PYTHON3="`realpath "${PYTHON3}" 2>/dev/null`" && test -n "${PYTHON3}" || { @@ -322,7 +350,7 @@ AC_DEFUN([NUT_CHECK_PYTHON3], AS_IF([test -n "${PYTHON3}" && test "${PYTHON3}" != "no"], [ AS_IF([test x"`$PYTHON3 -c 'import sys; print (sys.version_info >= (3, 0))'`" = xTrue], - [PYTHON3_VERSION_REPORT=" (`$PYTHON3 -c 'import sys; print (sys.version_info)'`)"], + [PYTHON3_VERSION_INFO_REPORT=" (`$PYTHON3 -c 'import sys; print (sys.version_info)'`)"], [AC_MSG_WARN([Version reported by ${PYTHON3} was not suitable as python3]) PYTHON3=no]) ]) @@ -335,10 +363,21 @@ AC_DEFUN([NUT_CHECK_PYTHON3], ]) AC_MSG_CHECKING([python3 interpeter to call]) - AC_MSG_RESULT([${PYTHON3}${PYTHON3_VERSION_REPORT}]) + AC_MSG_RESULT([${PYTHON3}${PYTHON3_VERSION_INFO_REPORT}]) AC_SUBST([PYTHON3], [${PYTHON3}]) AM_CONDITIONAL([HAVE_PYTHON3], [test -n "${PYTHON3}" && test "${PYTHON3}" != "no"]) AS_IF([test -n "${PYTHON3}" && test "${PYTHON3}" != "no"], [ + AC_MSG_CHECKING([python3 build sys.version]) + dnl Can have extra lines about compiler used, etc. + PYTHON3_VERSION_REPORT="`${PYTHON3} -c 'import sys; print(sys.version);' | tr '\n' ' '`" \ + || PYTHON3_VERSION_REPORT="" + AC_MSG_RESULT([${PYTHON3_VERSION_REPORT}]) + + AC_MSG_CHECKING([python3 sys.path used to search for modules]) + PYTHON3_SYSPATH_REPORT="`${PYTHON3} -c 'import sys; print(sys.path);'`" \ + || PYTHON3_SYSPATH_REPORT="" + AC_MSG_RESULT([${PYTHON3_SYSPATH_REPORT}]) + export PYTHON3 AC_CACHE_CHECK([python3 site-packages location], [nut_cv_PYTHON3_SITE_PACKAGES], [ nut_cv_PYTHON3_SITE_PACKAGES="`${PYTHON3} -c 'import site; print(site.getsitepackages().pop(0))'`" diff --git a/m4/nut_compiler_family.m4 b/m4/nut_compiler_family.m4 index 361a77a444..b6e5f06fc3 100644 --- a/m4/nut_compiler_family.m4 +++ b/m4/nut_compiler_family.m4 @@ -2,6 +2,9 @@ dnl detect if current compiler is clang or gcc (or...) AC_DEFUN([NUT_COMPILER_FAMILY], [ +if test -z "${nut_compiler_family_seen}"; then + nut_compiler_family_seen=yes + CC_VERSION_FULL="`LANG=C LC_ALL=C $CC --version 2>&1`" || CC_VERSION_FULL="" CXX_VERSION_FULL="`LANG=C LC_ALL=C $CXX --version 2>&1`" || CXX_VERSION_FULL="" CPP_VERSION_FULL="`LANG=C LC_ALL=C $CPP --version 2>&1`" || CPP_VERSION_FULL="" @@ -86,6 +89,7 @@ AC_DEFUN([NUT_COMPILER_FAMILY], AS_IF([test "x$CC_VERSION" = x], [CC_VERSION="`echo "${CC_VERSION_FULL}" | head -1`"]) AS_IF([test "x$CXX_VERSION" = x], [CXX_VERSION="`echo "${CXX_VERSION_FULL}" | head -1`"]) AS_IF([test "x$CPP_VERSION" = x], [CPP_VERSION="`echo "${CPP_VERSION_FULL}" | head -1`"]) +fi ]) AC_DEFUN([NUT_CHECK_COMPILE_FLAG], diff --git a/scripts/HP-UX/makedepot.sh b/scripts/HP-UX/makedepot.sh index 698bccf44b..82a47db2c0 100755 --- a/scripts/HP-UX/makedepot.sh +++ b/scripts/HP-UX/makedepot.sh @@ -1,6 +1,6 @@ -#!/bin/bash +#!/usr/bin/env bash -set -x +set -x CUR_DIR=$(pwd) TOP_DIR=$CUR_DIR/../.. @@ -26,5 +26,4 @@ cd $CUR_DIR swpackage -s nut.psf -d $CUR_DIR/nut.depot; \ #tar cvf nut.depot.tar nut.depot #gzip nut.depot.tar -echo "Execution completed" - +echo "Execution completed" diff --git a/scripts/Makefile.am b/scripts/Makefile.am index f0f59aa877..194e95840a 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -25,32 +25,35 @@ EXTRA_DIST = \ upower/95-upower-hid.rules \ usb_resetter/README.adoc \ usb_resetter/nut-driver.service \ + valgrind/README.adoc \ + valgrind/.valgrind.supp \ + valgrind/valgrind.sh \ Windows/halt.c \ Windows/Makefile SUBDIRS = augeas devd hotplug installer python systemd udev ufw Solaris Windows upsdrvsvcctl -SPELLCHECK_SRC = README.adoc RedHat/README.adoc usb_resetter/README.adoc +SPELLCHECK_SRC = README.adoc RedHat/README.adoc usb_resetter/README.adoc valgrind/README.adoc # NOTE: Due to portability, we do not use a GNU percent-wildcard extension. # We also have to export some variables that may be tainted by relative # paths when parsing the other makefile (e.g. MKDIR_P that may be defined -# via expanded $(top_builddir)/install_sh): +# via expanded $(top_builddir)/install-sh): #%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) -# +$(MAKE) -s -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ +# +$(MAKE) $(AM_MAKEFLAGS) -s -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ # NOTE: Portable suffix rules do not allow prerequisites, so we shim them here # by a wildcard target in case the make implementation can put the two together. *-spellchecked: Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) .sample.sample-spellchecked: - +$(MAKE) -s -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ + +$(MAKE) $(AM_MAKEFLAGS) -s -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ .in.in-spellchecked: - +$(MAKE) -s -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ + +$(MAKE) $(AM_MAKEFLAGS) -s -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ spellcheck spellcheck-interactive spellcheck-sortdict: - +$(MAKE) -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC="$(SPELLCHECK_SRC)" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ + +$(MAKE) $(AM_MAKEFLAGS) -f $(top_builddir)/docs/Makefile MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC="$(SPELLCHECK_SRC)" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ CLEANFILES = *-spellchecked RedHat/*-spellchecked usb_resetter/*-spellchecked diff --git a/scripts/Solaris/Makefile.am b/scripts/Solaris/Makefile.am index 3f01ec6034..3fad462879 100644 --- a/scripts/Solaris/Makefile.am +++ b/scripts/Solaris/Makefile.am @@ -86,7 +86,7 @@ check-local: $(SOLARIS_CHECK_TARGETS) check-local-solaris-smf: $(SOLARIS_SMF_MANIFESTS) @[ -x /usr/sbin/svccfg ] || { echo "WARNING : Target $@ skipped due to absent /usr/sbin/svccfg" >&2; return 0; } ; \ - RES=0 ; for F in $^ ; do \ + RES=0 ; for F in $? ; do \ echo " SVCCFG-VALIDATE $$F"; \ /usr/sbin/svccfg validate "$$F" || RES=$$? ; \ done; exit $$RES @@ -96,7 +96,7 @@ SPELLCHECK_SRC = README.adoc # NOTE: Due to portability, we do not use a GNU percent-wildcard extension. # We also have to export some variables that may be tainted by relative # paths when parsing the other makefile (e.g. MKDIR_P that may be defined -# via expanded $(top_builddir)/install_sh): +# via expanded $(top_builddir)/install-sh): #%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) # +$(MAKE) -s -f $(top_builddir)/docs/Makefile $(AM_MAKEFLAGS) MKDIR_P="$(MKDIR_P)" builddir="$(builddir)" srcdir="$(srcdir)" top_builddir="$(top_builddir)" top_srcdir="$(top_srcdir)" SPELLCHECK_SRC_ONE="$<" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ diff --git a/scripts/Solaris/nut-driver.xml.in b/scripts/Solaris/nut-driver.xml.in index 81e142ddea..fffd368db9 100644 --- a/scripts/Solaris/nut-driver.xml.in +++ b/scripts/Solaris/nut-driver.xml.in @@ -2,14 +2,14 @@ - +