diff --git a/.circleci/config.yml b/.circleci/config.yml index 5c0c0b5202..19774d05aa 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: XCODE_VER: type: string # https://circleci.com/docs/using-macos/#supported-xcode-versions-silicon - default: "13.4.1" + default: "16.4" CC: type: string default: "" # e.g. "clang" @@ -73,7 +73,10 @@ jobs: # Add steps to the job # See: https://circleci.com/docs/2.0/configuration-reference/#steps steps: - - checkout + - checkout: + # https://circleci.com/changelog/default-method-used-to-checkout-code-from-your-repository-is-changing-on-nov/ + # NUT SEMVER and ChangeLog work, maybe more, rely on git history + method: full # - run: # name: "check shell" @@ -160,16 +163,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) + # NOTE: python3.11 was available but broken on some of the xcode-13.4.1 + # workers (no homebrew dirs in search path) - run: name: "ci_build" command: |- HOMEBREW_PREFIX="`brew config | grep HOMEBREW_PREFIX: | awk '{print $2}'`" \ - CANBUILD_NIT_TESTS=yes \ + CANBUILD_NIT_TESTS=yes DEBUG_NIT=true \ CFLAGS="$CC_STDVER" \ CXXFLAGS="$CXX_STDVER" \ - PYTHON=python3.11 \ + PYTHON=python3.13 \ ./ci_build.sh - run: @@ -219,15 +222,15 @@ workflows: ### 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-xcode13_4_1-default-all-errors" -# XCODE_VER: "13.4.1" +# name: "gnu17-clang-xcode16_4-default-all-errors" +# XCODE_VER: "16.4" # CC: "clang" # CXX: "clang++" # CC_STDVER: "-std=gnu17" # CXX_STDVER: "-std=gnu++17" # - osx-xcode: -# name: "gnu11-gcc-xcode13_4_1-out-of-tree" +# name: "gnu11-gcc-xcode16_4-out-of-tree" # CC: "gcc" # CXX: "g++" # CC_STDVER: "-std=gnu11" @@ -236,7 +239,7 @@ workflows: # CI_BUILDDIR: "obj" # - osx-xcode: -# name: "c99-cxx11-gcc-xcode13_4_1-default-distcheck" +# name: "c99-cxx11-gcc-xcode16_4-default-distcheck" # CC: "gcc" # CXX: "g++" # CC_STDVER: "-std=c99" @@ -245,7 +248,7 @@ workflows: # BUILD_TYPE: "default" - osx-xcode: - name: "gnu11-clang-xcode13_4_1-out-of-tree" + name: "gnu11-clang-xcode16_4-out-of-tree" CC: "clang" CXX: "clang++" CPP: "clang -E" @@ -255,7 +258,7 @@ workflows: CI_BUILDDIR: "obj" - osx-xcode: - name: "c99-cxx11-clang-xcode13_4_1-default-distcheck" + name: "c99-cxx11-clang-xcode16_4-default-distcheck" CC: "clang" CXX: "clang++" CPP: "clang -E" @@ -265,7 +268,7 @@ workflows: BUILD_TYPE: "default" - osx-xcode: - name: "stdDefault-xcode13_4_1-fightwarn-all" + name: "stdDefault-xcode16_4-fightwarn-all" # Run "default-all-errors" with both compiler families, # using their default C/C++ standard for current release: BUILD_TYPE: "fightwarn-all" @@ -273,8 +276,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-xcode13_4_1-alldrv" -# XCODE_VER: "13.4.1" +# name: "c17-clang-xcode16_4-alldrv" +# XCODE_VER: "16.4" # CC: "clang" # CXX: "clang++" # CC_STDVER: "-std=c17" diff --git a/.github/workflows/01-make-dist.yml b/.github/workflows/01-make-dist.yml new file mode 100644 index 0000000000..58af3809a8 --- /dev/null +++ b/.github/workflows/01-make-dist.yml @@ -0,0 +1,297 @@ +# Adapted from NUT codeql.yml with inspiration taken from +# https://javahelps.com/manage-github-artifact-storage-quota +# regarding uploads of artifacts and clearing the way for them. +# See also: +# https://github.com/actions/upload-artifact +# https://docs.github.com/en/actions/reference/workflows-and-actions/variables +# https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax +#name: "GHA-01: Make dist and docs tarballs, see workflow page for links" +name: "GHA-01: Tarballs" + +on: + push: + branches: [ "master", "FTY", "fightwarn", "FTY-obs" ] + tags: + - v* + pull_request_target: + # The branches below must be a subset of the branches above + # Note that for PRs this runs the copy of workflow in the + # target branch (and only then gets some of the permissions + # listed below), and identification/naming of PR source vs. + # a pushed branch (e.g. master updated by a PR merge) is + # tricky. + branches: [ "master", "FTY", "fightwarn", "FTY-obs" ] + schedule: + - cron: '15 12 * * 0' + workflow_dispatch: + # Allow manually running the action, e.g. if disabled after some quietness in the source + +permissions: + checks: write + contents: write + issues: write + pull-requests: write + +jobs: + make-dist-tarballs: + name: "Make Dist and Docs Tarballs, see workflow page for links" + # FIXME: Prepare/maintain a container image with pre-installed + # NUT build/tooling prereqs (save about 3 minutes per run!) + # Maybe https://aschmelyun.com/blog/using-docker-run-inside-of-github-actions/ + # => https://github.com/addnab/docker-run-action can help + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 0 + fetch-tags: true + + # https://github.com/marketplace/actions/substitute-string + # See also examples in https://github.com/dhimmel/dump-actions-context/ + # Note it warns about "unexpected input(s)" with replacement tokens below, + # as they are by design not predefined, as far as actions API is concened. + # They still work for substitutions though. + - uses: bluwy/substitute-string-action@v3 + id: subst-github-ref-name + with: + _input-text: "${{ github.event.pull_request.number > 0 && format('PR-{0}', github.event.pull_request.number) || github.head_ref || github.ref_name }}" + " ": _ + "/": _ + + - name: Debug PR/branch identification + run: | + echo "steps.subst-github-ref-name.outputs.result='${{ steps.subst-github-ref-name.outputs.result }}'" || true + echo "github.event.pull_request.number='${{ github.event.pull_request.number }}'" || true + echo "format('PR-{0}', github.event.pull_request.number)='${{ format('PR-{0}', github.event.pull_request.number) }}'" || true + echo "github.head_ref='${{ github.head_ref }}'" || true + echo "github.ref='${{ github.ref }}'" || true + echo "github.ref_name='${{ github.ref_name }}'" || true + + # Make build identification more useful (so we use no fallbacks in script) + - name: Try to get more Git metadata + run: | + git describe || { + git remote -v || true + git branch -a || true + for R in `git remote` ; do git fetch $R master ; done || true + git fetch --tags + pwd ; ls -la + echo "=== Known commits in history:" + git log --oneline | wc -l + echo "=== Recent commits in history:" + git log -2 || true + echo "=== Known tags:" + git tag || true + echo "=== Try to ensure 'git describe' works:" + git describe || { + git fetch --all && for R in `git remote` ; do for T in `git tag` ; do git fetch $R $T ; done ; done + git describe || { + TEST_REF="`git symbolic-ref --short HEAD 2>/dev/null || cat .git/HEAD`" && [ -n "${TEST_REF}" ] && git checkout master && git pull --all && git checkout "${TEST_REF}" + git describe || true + } + } + } + + # Using hints from https://askubuntu.com/questions/272248/processing-triggers-for-man-db + # and our own docs/config-prereqs.txt + # NOTE: Currently installing the MAX prerequisite footprint, + # which for building just the docs may be a bit of an overkill. + - name: NUT CI Prerequisite packages (Ubuntu, GCC) + run: | + echo "set man-db/auto-update false" | sudo debconf-communicate + sudo dpkg-reconfigure man-db + sudo apt update + sudo apt install \ + gcc g++ clang \ + ccache time \ + git perl curl \ + make autoconf automake libltdl-dev libtool binutils \ + valgrind \ + cppcheck \ + pkg-config \ + libtool-bin \ + python3 gettext python3-pyqt6 pyqt6-dev-tools \ + aspell aspell-en \ + asciidoc source-highlight python3-pygments dblatex \ + libgd-dev \ + systemd-dev \ + libsystemd-dev \ + libcppunit-dev \ + libssl-dev libnss3-dev \ + augeas-tools libaugeas-dev augeas-lenses \ + libusb-dev libusb-1.0-0-dev \ + libi2c-dev \ + libmodbus-dev \ + libsnmp-dev \ + libpowerman0-dev \ + libfreeipmi-dev libipmimonitoring-dev \ + libavahi-common-dev libavahi-core-dev libavahi-client-dev \ + libgpiod-dev \ + bash dash ksh busybox \ + libneon27-gnutls-dev \ + build-essential git-core libi2c-dev i2c-tools lm-sensors \ + || exit + date > .timestamp-init + + - name: Prepare ccache + # Based on https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#example-using-the-cache-action example + id: cache-ccache + uses: actions/cache@v4 + env: + compiler: 'CC=gcc CXX=g++' + cache-name: cache-ccache-${{ env.compiler }} + with: + path: | + ~/.ccache + ~/.cache/ccache + ~/.config/ccache/ccache.conf + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/.timestamp-init') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: CCache stats before build + run: | + ccache -sv || ccache -s || echo "FAILED to read ccache info, oh well" + rm -f .timestamp-init + + - name: Debug gitlog2version processing + run: bash -x ./tools/gitlog2version.sh || true + + - name: NUT CI Build Configuration + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + ./autogen.sh && \ + ./configure --enable-warnings --enable-Werror --enable-Wcolor --with-all --with-dev --with-docs --enable-docs-changelog ${{env.compiler}} + + # NOTE: In this scenario we do not build actually NUT in the main + # checkout directory, at least not explicitly (recipe may generate + # some files like man pages to fulfill the "dist" requirements; + # for now this may generate some libs to figure out their IDs). + # We do `make docs` to provide them as a separate tarball just + # in case, later. + # DO NOT `make dist-files` here as it includes `dist-sig` and + # needs a GPG keychain with maintainers' secrets deployed locally. + - name: NUT CI Build to create "dist" tarball and related files + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + make -s -j 8 dist dist-hash + + - name: NUT CI Build to verify "dist" tarball build + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + make -s -j 8 distcheck + + - name: NUT CI Build to verify "dist" tarball build self-reproducibility + env: + compiler: 'CC=gcc CXX=g++' + run: | + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ( ${{env.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + make -s -j 8 distcheck-completeness + + - name: CCache stats after distcheck + run: ccache -sv || ccache -s || echo "FAILED to read ccache info, oh well" + + - name: NUT CI Build to package complex docs (not part of dist tarball) + run: | + make -s -j 8 dist-docs + + # Inspired by https://javahelps.com/manage-github-artifact-storage-quota + # Note that the code below wipes everything matched by the filter! + # We may want another script block (after this cleanup of obsolete data) + # to iterate clearing the way build by build until there's X MB available + # (at least 12MB as of Nov 2025). + - if: env.GITHUB_REF_TYPE != 'tag' && steps.subst-github-ref-name.outputs.result != 'master' + name: Delete Old Artifacts for this feature branch/PR + uses: actions/github-script@v6 + id: delete_old_artifact_for_pr + continue-on-error: true + with: + script: | + const res = await github.rest.actions.listArtifactsForRepo({ + owner: context.repo.owner, + repo: context.repo.repo, + }) + + res.data.artifacts + .filter(({ name }) => name === 'NUT-tarballs-${{ steps.subst-github-ref-name.outputs.result }}') + .forEach(({ id }) => { + github.rest.actions.deleteArtifact({ + owner: context.repo.owner, + repo: context.repo.repo, + artifact_id: id, + }) + }) + + - name: Upload tarball and its checksum artifacts + uses: actions/upload-artifact@v4 + id: upload_artifact + with: + name: NUT-tarballs-${{ steps.subst-github-ref-name.outputs.result }} + path: | + nut-*.tar* + compression-level: 0 + overwrite: true + + # NOTE: Despite the docs examples, due to GH API changes in Mar 2025 + # this action can no longer update an existing check, only create a + # new one. Also calls authenticated with "github-actions" GH App may + # not set "details_url" freely... + # https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28 + - name: "GHA-01: Create new GH Check report - shell/cURL" + if: always() + continue-on-error: true + env: + artifact_name: "NUT-tarballs-${{ steps.subst-github-ref-name.outputs.result }}.zip" + artifact_url: ${{ steps.upload_artifact.outputs.artifact-url }} + ref: ${{ github.event.pull_request.head.sha || github.sha }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + curl -L \ + -X POST \ + -H "Accept: application/vnd.github+json" \ + -H "Authorization: Bearer ${{ env.GITHUB_TOKEN }}" \ + -H "X-GitHub-Api-Version: 2022-11-28" \ + https://api.github.com/repos/${{ github.repository }}/check-runs \ + -d '{"head_sha": "${{ env.ref }}", "name": "URL for ${{ env.artifact_name }}", "details_url": "${{ env.artifact_url }}", "status": "completed", "conclusion": "${{ job.status }}", "output": {"title": "${{ env.artifact_url }}", "summary": "Dist and Docs [${{ env.artifact_name }}](${{ env.artifact_url }}) are available for commit ${{ env.ref }}"} }' + + ### This tends to use same "name" text on both sides of Checks list entry: + #- name: "GHA-01: Add link as GH Check report" + # uses: LouisBrunner/checks-action@v2.0.0 + # if: always() + # continue-on-error: true + # env: + # artifact_name: "NUT-tarballs-${{ steps.subst-github-ref-name.outputs.result }}.zip" + # artifact_url: ${{ steps.upload_artifact.outputs.artifact-url }} + # ref: ${{ github.event.pull_request.head.sha || github.sha }} + # with: + # token: ${{ secrets.GITHUB_TOKEN }} + # name: "[${{ env.artifact_name }}](${{ env.artifact_url }})" + # status: "completed" + # conclusion: ${{ job.status }} + # details_url: ${{ env.artifact_url }} + # output: '{ "text_description": "Dist and Docs [${{ env.artifact_name }}](${{ env.artifact_url }}) are available for commit ${{ env.ref }}", "summary": "${{ job.status }}" }' diff --git a/.github/workflows/05-codeql.yml b/.github/workflows/05-codeql.yml new file mode 100644 index 0000000000..1bcb5d5f78 --- /dev/null +++ b/.github/workflows/05-codeql.yml @@ -0,0 +1,200 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "GHA-05: CodeQL" + +on: + push: + branches: [ "master", "FTY", "fightwarn" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master", "FTY", "fightwarn" ] + schedule: + - cron: '32 12 * * 0' + workflow_dispatch: + # Allow manually running the action, e.g. if disabled after some quietness in the source + +jobs: + analyze: + name: Analyze + runs-on: ${{ matrix.os }} + permissions: + actions: read + contents: read + security-events: write + + 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 + os: [ 'ubuntu-latest' ] + # TOTHINK: windows-latest, macos-latest? + build-mode: [ 'manual' ] + # https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + # Abusing "manual" here to try building with ccache (and + # have codeql not intercept that build but parse C/C++ + # files on its own), and "manual" to custom-build without; + # the "autobuild" mode is handled by codeql itself but + # would probably ignore our CC/CXX setting + # NOTE: We do not add ccache to PATH when actually compiling NUT code + # (we only speed up "configure" stages), so compilation always happens + # and is parsed by current CodeQL detectors of the day as they evolve! + 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 complete new cell(s) to the matrix, separately + # from the combinatorics dynamically made above + - language: 'python' + os: 'ubuntu-latest' + compiler: 'PYTHON=python3' + build-mode: 'none' + NUT_SSL_VARIANTS: 'no' + NUT_USB_VARIANTS: 'no' + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # 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 + + # Using hints from https://askubuntu.com/questions/272248/processing-triggers-for-man-db + - if: matrix.language == 'cpp' && matrix.os == 'ubuntu-latest' + name: NUT CI Prerequisite packages (Ubuntu) + run: | + echo "set man-db/auto-update false" | sudo debconf-communicate + sudo dpkg-reconfigure man-db + 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 ccache + date > .timestamp-init + + - name: Prepare ccache + # Based on https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching#example-using-the-cache-action example + id: cache-ccache + uses: actions/cache@v4 + env: + cache-name: cache-ccache-${{ matrix.compiler }}-${{ matrix.NUT_SSL_VARIANTS }}-${{ matrix.NUT_USB_VARIANTS }} + with: + path: | + ~/.ccache + ~/.cache/ccache + ~/.config/ccache/ccache.conf + key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/.timestamp-init') }} + restore-keys: | + ${{ runner.os }}-build-${{ env.cache-name }}- + ${{ runner.os }}-build- + ${{ runner.os }}- + + - name: CCache stats before build + run: | + ccache -sv || ccache -s || echo "FAILED to read ccache info, oh well" + rm -f .timestamp-init + + # Make build identification more useful (no fallbacks) + #- name: Try to get more Git metadata + # run: | + # git remote -v || true + # git branch -a || true + # for R in `git remote` ; do git fetch $R master ; done || true + # git fetch --tags + + #- name: Debug gitlog2version processing + # run: bash -x ./tools/gitlog2version.sh || true + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the (whole) list here with "+" to use these queries and those in the config file. + # 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 + + # 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) + # https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages + - if: matrix.build-mode == 'autobuild' + name: Autobuild + uses: github/codeql-action/autobuild@v3 + + # â„šī¸ 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 + + #- if: matrix.build-mode != 'autobuild' && 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.build-mode != 'autobuild' && 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.build-mode != 'autobuild' && matrix.language == 'cpp' + name: NUT CI Build Configuration + run: | + case x"${{matrix.build-mode}}" in + xmanual) + PATH="/usr/lib/ccache:$PATH" ; export PATH + CCACHE_COMPRESS=true; export CCACHE_COMPRESS + ccache --version || true + ;; + xnone|*) + echo "NOTE: NOT USING CCACHE for the CI-tested code base configuration" >&2 + ;; + esac + ( ${{matrix.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + ./autogen.sh && \ + ./configure --enable-warnings --enable-Werror --enable-Wcolor --with-all=auto --with-dev --without-docs --disable-docs-changelog ${{matrix.compiler}} --with-ssl=${{matrix.NUT_SSL_VARIANTS}} --with-usb=${{matrix.NUT_USB_VARIANTS}} + + # NOTE: We do not add ccache to PATH here, so compilation always happens + # and is parsed by current CodeQL detectors of the day as they evolve: + - if: matrix.build-mode != 'autobuild' && matrix.language == 'cpp' + name: NUT CI Build Compilation + run: | + echo "NOTE: NOT USING CCACHE for the CI-tested code base compilation" >&2 + ( ${{matrix.compiler}} ; echo "=== CC: $CC => `command -v $CC` =>" ; $CC --version ; echo "=== CXX: $CXX => `command -v $CXX` =>" ; $CXX --version ) || true + make -s -j 8 || exit + + # NOTE: Assuming GNU make here, not limiting "-j NUM" runners + - if: matrix.build-mode != 'autobuild' && matrix.language == 'cpp' + name: NUT CI Build to verify parallel build recipes in subdirs + run: make -s -j check-parallel-builds || exit + + - name: CCache stats after build + run: ccache -sv || ccache -s || echo "FAILED to read ccache info, oh well" + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/PyNUTClient.yml b/.github/workflows/08-PyNUTClient.yml similarity index 84% rename from .github/workflows/PyNUTClient.yml rename to .github/workflows/08-PyNUTClient.yml index 794aa47037..353394e9bd 100644 --- a/.github/workflows/PyNUTClient.yml +++ b/.github/workflows/08-PyNUTClient.yml @@ -1,4 +1,4 @@ -name: Publish PyNUT client bindings for NUT 🐍 distributions đŸ“Ļ to PyPI +name: "GHA-08: Publish PyNUT client bindings for NUT 🐍 distributions đŸ“Ļ to PyPI" # based on https://medium.com/@VersuS_/automate-pypi-releases-with-github-actions-4c5a9cfe947d # and https://packaging.python.org/en/latest/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ @@ -13,6 +13,8 @@ on: - '*' branches: - 'master' + workflow_dispatch: + # Allow manually running the action, e.g. if disabled after some quietness in the source permissions: id-token: write @@ -35,7 +37,7 @@ jobs: id: pythoncmd run: >- set -x ; - echo "PYTHON=$(command -v python)" >> $GITHUB_OUTPUT + echo "PYTHON_DEFAULT=$(command -v python)" >> $GITHUB_OUTPUT - name: Extract tag name id: tag # Note: this is all a single shell line in the end, @@ -53,14 +55,14 @@ jobs: echo "TAG_NAME=$TAG_NAME" >> $GITHUB_OUTPUT - name: Install pypa/setuptools run: >- - ${{ steps.pythoncmd.outputs.PYTHON }} -m - pip install wheel build + ${{ steps.pythoncmd.outputs.PYTHON_DEFAULT }} -m \ + pip install wheel build setuptools - name: Prepare source layout and Build a binary wheel run: >- set -e ; cd scripts/python/module ; cp -f Makefile.am Makefile ; - make -f Makefile.am clean-local dist NUT_SOURCE_GITREV_NUMERIC="${{ steps.tag.outputs.TAG_NAME }}" PYTHON="${{ steps.pythoncmd.outputs.PYTHON }}" top_srcdir="../../.." srcdir="." builddir="." ; + make -f Makefile.am clean-local dist NUT_SOURCE_GITREV_NUMERIC="${{ steps.tag.outputs.TAG_NAME }}" PYTHON_DEFAULT="${{ steps.pythoncmd.outputs.PYTHON_DEFAULT }}" top_srcdir="../../.." srcdir="." builddir="." ; find . -ls - name: Publish master distribution đŸ“Ļ to Test PyPI # https://github.com/pypa/gh-action-pypi-publish diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml deleted file mode 100644 index 4be255931b..0000000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,130 +0,0 @@ -# For most projects, this workflow file will not need changing; you simply need -# to commit it to your repository. -# -# You may wish to alter this file to override the set of languages analyzed, -# or to provide custom queries or build logic. -# -# ******** NOTE ******** -# We have attempted to detect the languages in your repository. Please check -# the `language` matrix defined below to confirm you have the correct set of -# supported CodeQL languages. -# -name: "CodeQL" - -on: - push: - branches: [ "master", "FTY", "fightwarn" ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "master", "FTY", "fightwarn" ] - schedule: - - cron: '32 12 * * 0' - -jobs: - analyze: - name: Analyze - runs-on: ${{ matrix.os }} - permissions: - actions: read - contents: read - security-events: write - - 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 - 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 - uses: actions/checkout@v3 - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v3 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. - - # 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 - - # Make build identification more useful (no fallbacks) - #- name: Try to get more Git metadata - # run: | - # git remote -v || true - # git branch -a || true - # for R in `git remote` ; do git fetch $R master ; done || true - # git fetch --tags - - #- name: Debug gitlog2version processing - # run: bash -x ./tools/gitlog2version.sh || true - - # 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 - - #- 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 --disable-docs-changelog ${{matrix.compiler}} --with-ssl=${{matrix.NUT_SSL_VARIANTS}} --with-usb=${{matrix.NUT_USB_VARIANTS}} - make -s -j 8 || exit - - - if: matrix.language == 'cpp' - name: NUT CI Build to verify parallel build recipes in subdirs - run: make -s -j check-parallel-builds || exit - - # â„šī¸ 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 - - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 - with: - category: "/language:${{matrix.language}}" diff --git a/.gitignore b/.gitignore index 0cc1efc99f..2d32fbb90d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,6 +21,7 @@ Makefile.in /autom4te.cache/ /ChangeLog /ChangeLog.adoc +/compile /config.guess /config.log /config.log.inplace-outer diff --git a/.obs/workflows.yml b/.obs/workflows.yml new file mode 100644 index 0000000000..280b70ad01 --- /dev/null +++ b/.obs/workflows.yml @@ -0,0 +1,32 @@ +# See https://openbuildservice.org/help/manuals/obs-user-guide/cha-obs-scm-ci-workflow-integration + +main_PR_workflow: + steps: + - branch_package: + source_project: home:networkupstools:nut-stable + source_package: "%{SCM_REPOSITORY_NAME}" + target_project: home:networkupstools:nut-pull-requests + filters: + event: pull_request + +rebuild_master: + steps: + - trigger_services: + project: home:networkupstools:nut-stable + package: "%{SCM_REPOSITORY_NAME}" + filters: + event: + - push + branches: + only: + - master + +release_tag: + steps: + - branch_package: + source_project: home:networkupstools:nut-stable + source_package: "%{SCM_REPOSITORY_NAME}" + target_project: home:networkupstools:nut-releases + filters: + event: + - tag_push diff --git a/COPYING b/COPYING index efc32ff953..ba7bff0614 100644 --- a/COPYING +++ b/COPYING @@ -30,6 +30,9 @@ and were originally available under curl license. Which is not unlike the MIT license, see https://daniel.haxx.se/blog/2022/06/17/curl-is-reuse-compliant/ + Rendered HTML documentation (and relevant NUT source configuration files) + include a copy of AnchorJS project artifacts, available under the MIT license. + To the best of our knowledge, conditions of the 2/3-clause BSD, MIT, curl and CC BY-SA licenses allow redistribution and reuse of the codebase in projects made available under GPL license terms, as long as attribution is provided. diff --git a/INSTALL.nut.adoc b/INSTALL.nut.adoc index fc78f7ed5d..8a6cd6531f 100644 --- a/INSTALL.nut.adoc +++ b/INSTALL.nut.adoc @@ -298,6 +298,17 @@ See <> from the User Manual, docs/configure.txt or './configure --help' for all the available options. +NOTE: Due to various historic or architectural reasons, some of the option +usages may be surprising (compared to other autotools-driven projects), but +those choices remain in place to minimize the surprise for people who rebuild +NUT regularly. A few cases worth mentioning include the long-term use of +`--sysconfdir` as `/etc/nut` directly (rather than keeping `/etc` and having +another option to append `/nut` -- which can be done since NUT v2.8.5), and +multiple `--with-python*` settings for its different major version ecosystems +which can be all auto-detected independently (so you may need certain +`--without-python2` etc. options if you want to disable some module +installation paths that your system is eager to support). + If you alter paths with additional switches, be sure to use those new paths while reading the rest of the steps. diff --git a/Jenkinsfile b/Jenkinsfile deleted file mode 100644 index 4516d92dbc..0000000000 --- a/Jenkinsfile +++ /dev/null @@ -1,700 +0,0 @@ -@Library('etn-ipm2-jenkins') _ - -pipeline { - agent { - label infra.getAgentLabels() - } - parameters { - // Use DEFAULT_DEPLOY_BRANCH_PATTERN and DEFAULT_DEPLOY_JOB_NAME if - // defined in this jenkins setup -- in Jenkins Management Web-GUI - // see Configure System / Global properties / Environment variables - // Default (if unset) is empty => no deployment attempt after good test - // See zproject Jenkinsfile-deploy.example for an example deploy job. - // TODO: Try to marry MultiBranchPipeline support with pre-set defaults - // directly in MultiBranchPipeline plugin, or mechanism like Credentials, - // or a config file uploaded to master for all jobs or this job, see - // https://jenkins.io/doc/pipeline/examples/#configfile-provider-plugin - string ( - defaultValue: '${DEFAULT_DEPLOY_BRANCH_PATTERN}', - description: 'Regular expression of branch names for which a deploy action would be attempted after a successful build and test; leave empty to not deploy. Reasonable value is ^(master|release/.*|feature/*)$', - name : 'DEPLOY_BRANCH_PATTERN') - string ( - defaultValue: '${DEFAULT_DEPLOY_JOB_NAME}', - description: 'Name of your job that handles deployments and should accept arguments: DEPLOY_GIT_URL DEPLOY_GIT_BRANCH DEPLOY_GIT_COMMIT -- and it is up to that job what to do with this knowledge (e.g. git archive + push to packaging); leave empty to not deploy', - name : 'DEPLOY_JOB_NAME') - booleanParam ( - defaultValue: true, - description: 'If the deployment is done, should THIS job wait for it to complete and include its success or failure as the build result (true), or should it schedule the job and exit quickly to free up the executor (false)', - name: 'DEPLOY_REPORT_RESULT') - booleanParam ( - defaultValue: true, - description: 'Publish as an archive a "dist" tarball from a build with docs in this run? (Note: corresponding tools are required in the build environment; enabling this enforces DO_BUILD_DOCS too)', - name: 'DO_DIST_DOCS') - booleanParam ( - defaultValue: true, - description: 'Attempt nut-driver-enumerator-test in this run?', - name: 'DO_TEST_NDE') - booleanParam ( - defaultValue: true, - description: 'Attempt "make check" in this run?', - name: 'DO_TEST_CHECK') - booleanParam ( - defaultValue: false, - description: 'Attempt "make memcheck" in this run?', - name: 'DO_TEST_MEMCHECK') - booleanParam ( - defaultValue: false, - description: 'Attempt "make distcheck" in this run?', - name: 'DO_TEST_DISTCHECK') - booleanParam ( - defaultValue: true, - description: 'Attempt "make distcheck-dmf-all-yes" in this run?', - name: 'DO_TEST_DISTCHECK_DMF_ALL_YES') - booleanParam ( - defaultValue: false, - description: 'Attempt a "make install" check in this run?', - name: 'DO_TEST_INSTALL') - string ( - defaultValue: "`pwd`/tmp/_inst", - description: 'If attempting a "make install" check in this run, what DESTDIR to specify? (absolute path, defaults to "BUILD_DIR/tmp/_inst")', - name: 'USE_TEST_INSTALL_DESTDIR') - booleanParam ( - defaultValue: true, - description: 'Attempt "cppcheck" analysis before this run? (Note: corresponding tools are required in the build environment)', - name: 'DO_CPPCHECK') - booleanParam ( - defaultValue: true, - description: 'Require that there are no files not discovered changed/untracked via .gitignore after builds and tests?', - name: 'CI_REQUIRE_GOOD_GITIGNORE') - booleanParam ( - defaultValue: true, - description: 'Run code analysis (applies for certain branches)?', - name: 'DO_COVERITY') - string ( - defaultValue: "30", - description: 'When running tests, use this timeout (in minutes; be sure to leave enough for double-job of a distcheck too)', - name: 'USE_TEST_TIMEOUT') - booleanParam ( - defaultValue: true, - description: 'When using temporary subdirs in build/test workspaces, wipe them after successful builds?', - name: 'DO_CLEANUP_AFTER_BUILD') - booleanParam ( - defaultValue: true, - description: 'When using temporary subdirs in build/test workspaces, wipe them after the whole job is done successfully?', - name: 'DO_CLEANUP_AFTER_JOB') - booleanParam ( - defaultValue: true, - description: 'When using temporary subdirs in build/test workspaces, wipe them after the whole job is done unsuccessfully (failed)? Note this would not allow postmortems on CI server, but would conserve its disk space.', - name: 'DO_CLEANUP_AFTER_FAILED_JOB') - } - triggers { - pollSCM 'H/5 * * * *' - } - - // Jenkins tends to reschedule jobs that have not yet completed if they took - // too long, maybe this happens in combination with polling. Either way, if - // the server gets into this situation, the snowball of same builds grows as - // the build system lags more and more. Let the devs avoid it in a few ways. - options { - disableConcurrentBuilds() - // Jenkins community suggested that instead of a default checkout, we can do - // an explicit step for that. It is expected that either way Jenkins "should" - // record that a particular commit is being processed, but the explicit ways - // might work better. In either case it honors SCM settings like refrepo if - // set up in the Pipeline or MultiBranchPipeline job. - skipDefaultCheckout() - } -// Note: your Jenkins setup may benefit from similar setup on side of agents: -// PATH="/usr/lib64/ccache:/usr/lib/ccache:/usr/bin:/bin:${PATH}" - - stages { - stage ('pre-clean') { - steps { - milestone ordinal: 20, label: "${env.JOB_NAME}@${env.BRANCH_NAME}" - dir("tmp") { - sh 'if [ -s Makefile ]; then make -k distclean || true ; fi' - sh 'chmod -R u+w .' - deleteDir() - } - sh 'rm -f ccache.log cppcheck.xml' - } - } - - stage ('git') { - steps { - retry(3) { - checkout scm - } - milestone ordinal: 30, label: "${env.JOB_NAME}@${env.BRANCH_NAME}" - } - } - - stage ('prepare') { - steps { - sh './autogen.sh' - stash (name: 'prepped', includes: '**/*', excludes: '**/cppcheck.xml') - milestone ordinal: 40, label: "${env.JOB_NAME}@${env.BRANCH_NAME}" - } - } - - stage ('configure') { - steps { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; ./configure --with-neon=yes --with-lua=yes --with-snmp=yes --with-snmp_dmf_lua=yes --with-dev --with-doc=html-single=auto,man=yes --with-dmfnutscan-regenerate=yes --with-dmfsnmp-regenerate=auto --with-dmfsnmp-validate=yes --with-dmfnutscan-validate=yes' - } - } - - stage ('compile') { - steps { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make -k -j4 all || make all' - sh """ set +x -echo "Are GitIgnores good after make? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - script { - if ( (params.DO_TEST_CHECK && params.DO_TEST_MEMCHECK) || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK) || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK) || - (params.DO_TEST_INSTALL && params.DO_TEST_MEMCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_CHECK) - || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - ) { - stash (name: 'built', includes: '**/*') - } - } - } - } - - stage ('dist') { - when { expression { return ( params.DO_DIST_DOCS ) } } - steps { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make dist-gzip || exit ; DISTFILE="`ls -1tc *.tar.gz | head -1`" && [ -n "$DISTFILE" ] && [ -s "$DISTFILE" ] || exit ; mv -f "$DISTFILE" __dist.tar.gz' - archiveArtifacts artifacts: '__dist.tar.gz' - sh "rm -f __dist.tar.gz" - } - } - - stage ('check') { - parallel { - stage ('cppcheck') { - when { expression { return ( params.DO_CPPCHECK ) } } - steps { - sh 'cppcheck --std=c++11 --enable=all --inconclusive --xml --xml-version=2 . 2>cppcheck.xml' - archiveArtifacts artifacts: '**/cppcheck.xml' - sh 'rm -f cppcheck.xml' - } - } - - stage ('nut-driver-enumerator-test') { - when { expression { return ( params.DO_TEST_NDE ) } } - steps { - dir("tmp/test-nde") { - deleteDir() - } - dir("tmp/test-nde") { - unstash 'prepped' - timeout (time: 5, unit: 'MINUTES') { - sh 'SHELL_PROGS="/bin/sh bash ash dash" ./tests/nut-driver-enumerator-test.sh' - } - script { - if ( params.DO_CLEANUP_AFTER_BUILD ) { - deleteDir() - } - } - } - } - } - - stage ('make check') { - when { expression { return ( params.DO_TEST_CHECK ) } } - steps { - script { - if ( (params.DO_TEST_CHECK && params.DO_TEST_MEMCHECK) || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK) || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK) || - (params.DO_TEST_INSTALL && params.DO_TEST_MEMCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_CHECK) - || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - ) { - dir("tmp/test-check") { - deleteDir() - } - dir("tmp/test-check") { - unstash 'built' - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make check' - } - sh """ set +x -echo "Are GitIgnores good after make check? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - script { - if ( params.DO_CLEANUP_AFTER_BUILD ) { - deleteDir() - } - } - } - } else { - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make check' - } - sh """ set +x -echo "Are GitIgnores good after make check? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - } - } - } - } - - stage ('make memcheck') { - when { expression { return ( params.DO_TEST_MEMCHECK ) } } - steps { - script { - if ( (params.DO_TEST_CHECK && params.DO_TEST_MEMCHECK) || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK) || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK) || - (params.DO_TEST_INSTALL && params.DO_TEST_MEMCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_CHECK) - || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - ) { - dir("tmp/test-memcheck") { - deleteDir() - } - dir("tmp/test-memcheck") { - unstash 'built' - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make memcheck && exit 0 ; echo "Re-running failed ($?) memcheck with greater verbosity" >&2 ; make VERBOSE=1 memcheck-verbose' - } - sh """ set +x -echo "Are GitIgnores good after make memcheck? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - script { - if ( params.DO_CLEANUP_AFTER_BUILD ) { - deleteDir() - } - } - } - } else { - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make memcheck && exit 0 ; echo "Re-running failed ($?) memcheck with greater verbosity" >&2 ; make VERBOSE=1 memcheck-verbose' - } - sh """ set +x -echo "Are GitIgnores good after make memcheck? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - } - } - } - } - - stage ('make distcheck') { - when { expression { return ( params.DO_TEST_DISTCHECK ) } } - steps { - script { - if ( (params.DO_TEST_CHECK && params.DO_TEST_MEMCHECK) || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK) || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK) || - (params.DO_TEST_INSTALL && params.DO_TEST_MEMCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_CHECK) - || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - ) { - dir("tmp/test-distcheck") { - deleteDir() - } - dir("tmp/test-distcheck") { - unstash 'built' - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make DISTCHECK_CONFIGURE_FLAGS="--with-neon=yes --with-lua=yes --with-snmp=yes --with-snmp_dmf_lua=yes --with-dev --with-doc=html-single=auto,man=yes --with-dmfnutscan-regenerate=yes --with-dmfsnmp-regenerate=auto --with-dmfsnmp-validate=yes --with-dmfnutscan-validate=yes" distcheck' - } - sh """ set +x -echo "Are GitIgnores good after make distcheck? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - script { - if ( params.DO_CLEANUP_AFTER_BUILD ) { - deleteDir() - } - } - } - } else { - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make DISTCHECK_CONFIGURE_FLAGS="--with-neon=yes --with-lua=yes --with-snmp=yes --with-snmp_dmf_lua=yes --with-dev --with-doc=html-single=auto,man=yes --with-dmfnutscan-regenerate=yes --with-dmfsnmp-regenerate=auto --with-dmfsnmp-validate=yes --with-dmfnutscan-validate=yes" distcheck' - } - sh """ set +x -echo "Are GitIgnores good after make distcheck? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - } - } - } - } - - stage ('make distcheck-dmf-all-yes') { - when { expression { return ( params.DO_TEST_DISTCHECK_DMF_ALL_YES ) } } - steps { - script { - if ( (params.DO_TEST_CHECK && params.DO_TEST_MEMCHECK) || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK) || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK) || - (params.DO_TEST_INSTALL && params.DO_TEST_MEMCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_CHECK) - || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - ) { - dir("tmp/test-distcheck-dmf-all-yes") { - deleteDir() - } - dir("tmp/test-distcheck-dmf-all-yes") { - unstash 'built' - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make distcheck-dmf-all-yes' - } - sh """ set +x -echo "Are GitIgnores good after make distcheck-dmf-all-yes? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - script { - if ( params.DO_CLEANUP_AFTER_BUILD ) { - deleteDir() - } - } - } - } else { - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh 'CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make distcheck-dmf-all-yes' - } - sh """ set +x -echo "Are GitIgnores good after make distcheck-dmf-all-yes? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - } - } - } - } - - stage ('make install check') { - when { expression { return ( params.DO_TEST_INSTALL ) } } - steps { - script { - if ( (params.DO_TEST_CHECK && params.DO_TEST_MEMCHECK) || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK) || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK) || - (params.DO_TEST_INSTALL && params.DO_TEST_MEMCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK) || (params.DO_TEST_INSTALL && params.DO_TEST_CHECK) - || (params.DO_TEST_CHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_MEMCHECK && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - || (params.DO_TEST_INSTALL && params.DO_TEST_DISTCHECK_DMF_ALL_YES) - ) { - dir("tmp/test-install-check") { - deleteDir() - } - dir("tmp/test-install-check") { - unstash 'built' - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh """CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make DESTDIR="${params.USE_TEST_INSTALL_DESTDIR}" install""" - } - sh """ set +x -echo "Are GitIgnores good after make install? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - script { - if ( params.DO_CLEANUP_AFTER_BUILD ) { - deleteDir() - } - } - } - } else { - timeout (time: "${params.USE_TEST_TIMEOUT}".toInteger(), unit: 'MINUTES') { - sh """CCACHE_BASEDIR="`pwd`" ; export CCACHE_BASEDIR; make DESTDIR="${params.USE_TEST_INSTALL_DESTDIR}" install""" - } - sh """ set +x -echo "Are GitIgnores good after make install? (should have no output below)" -OUT="`git status -s`" && [ -z "\$OUT" ] \\ -|| { echo "\$OUT" >&2 - if [ "${params.CI_REQUIRE_GOOD_GITIGNORE}" = false ]; then - echo "WARNING GitIgnore tests found newly changed or untracked files:" >&2 - exit 0 - else - echo "FAILED GitIgnore tests" >&2 - git diff >&2 - exit 1 - fi -}""" - } - } - } - } - - stage('Analyse with Coverity') { - when { - beforeAgent true - allOf { - expression { return ("true" == "${params.DO_COVERITY}") } - anyOf { - branch 'master' - branch "release/*" - branch 'FTY' - branch '*-FTY-master' - branch '*-FTY' - changeRequest() - } - } - } - - stages { - stage('Compile Coverity') { - steps { - dir("tmp/build-coverity") { - deleteDir() - } - dir("tmp/build-coverity") { - unstash 'prepped' - } - script { - // Autogen is currently part of "prepped" archive - //compile.autogen("tmp/build-coverity") - compile.configure("tmp/build-coverity", "--enable-Werror=yes --with-docs=no") - compile.make("tmp/build-coverity", "clean") - coverity.compile("tmp/build-coverity") - } - } - } - - stage('Analyse Coverity') { - steps { - script { - coverity.analyse("tmp/build-coverity") - } - } - } - - /* Committing the result - that happens below */ - } - - // TODO: Use coverity steps from lib below for the post{} - post { - always { - script { - dir("tmp/build-coverity") { - coverity.postAnalysis() - } - } - } - } - } // Analyze with Coverity - - } - } - - stage('Ready to push') { - steps { - script { - manager.addShortText("Build, analysis and tests passed okay") - } - milestone ordinal: 100, label: "${env.JOB_NAME}@${env.BRANCH_NAME}" - } - } - - stage ('Upload results') { - parallel { - - stage('Commit Coverity') { - when { - beforeAgent true - allOf { - expression { return ("true" == "${params.DO_COVERITY}") } - anyOf { - branch 'master' - branch 'release/*' - branch 'FTY' - branch '*-FTY-master' - branch '*-FTY' - } - } - } - steps { - catchError(buildResult: 'UNSTABLE', stageResult: 'FAILURE') { - script { - /* Many components upload in 2-5 minutes; but some - * seem to require complex processing on server side */ - //coverity.commitResults("tmp/build-coverity", parameters.coverityUploadTimeoutLen, parameters.coverityUploadTimeoutUnit) - coverity.commitResults("tmp/build-coverity") - manager.addShortText("Coverity upload completed") - } - } - } - post { - unsuccessful { - script { - manager.addShortText("Coverity upload failed") - } - } - } - } // Commit Coverity - - stage ('deploy if appropriate') { - steps { - script { - def myDEPLOY_JOB_NAME = sh(returnStdout: true, script: """echo "${params["DEPLOY_JOB_NAME"]}" """).trim(); - def myDEPLOY_BRANCH_PATTERN = sh(returnStdout: true, script: """echo "${params["DEPLOY_BRANCH_PATTERN"]}" """).trim(); - def myDEPLOY_REPORT_RESULT = sh(returnStdout: true, script: """echo "${params["DEPLOY_REPORT_RESULT"]}" """).trim().toBoolean(); - echo "Original: DEPLOY_JOB_NAME : ${params["DEPLOY_JOB_NAME"]} DEPLOY_BRANCH_PATTERN : ${params["DEPLOY_BRANCH_PATTERN"]} DEPLOY_REPORT_RESULT : ${params["DEPLOY_REPORT_RESULT"]}" - echo "Used: myDEPLOY_JOB_NAME:${myDEPLOY_JOB_NAME} myDEPLOY_BRANCH_PATTERN:${myDEPLOY_BRANCH_PATTERN} myDEPLOY_REPORT_RESULT:${myDEPLOY_REPORT_RESULT}" - if ( (myDEPLOY_JOB_NAME != "") && (myDEPLOY_BRANCH_PATTERN != "") ) { - if ( env.BRANCH_NAME =~ myDEPLOY_BRANCH_PATTERN ) { - def GIT_URL = sh(returnStdout: true, script: """git remote -v | egrep '^origin' | awk '{print \$2}' | head -1""").trim() - def GIT_COMMIT = sh(returnStdout: true, script: 'git rev-parse --verify HEAD').trim() - def DIST_ARCHIVE = "" - def msg = "Would deploy ${GIT_URL} ${GIT_COMMIT} because tested branch '${env.BRANCH_NAME}' matches filter '${myDEPLOY_BRANCH_PATTERN}'" - if ( params.DO_DIST_DOCS ) { - DIST_ARCHIVE = env.BUILD_URL + "artifact/__dist.tar.gz" - msg += ", using dist archive '${DIST_ARCHIVE}' to speed up deployment" - } - echo msg - //milestone ordinal: 100, label: "${env.JOB_NAME}@${env.BRANCH_NAME}" - build job: "${myDEPLOY_JOB_NAME}", parameters: [ - string(name: 'DEPLOY_GIT_URL', value: "${GIT_URL}"), - string(name: 'DEPLOY_GIT_BRANCH', value: env.BRANCH_NAME), - string(name: 'DEPLOY_GIT_COMMIT', value: "${GIT_COMMIT}"), - string(name: 'DEPLOY_DIST_ARCHIVE', value: "${DIST_ARCHIVE}") - ], quietPeriod: 0, wait: myDEPLOY_REPORT_RESULT, propagate: myDEPLOY_REPORT_RESULT - } else { - echo "Not deploying because branch '${env.BRANCH_NAME}' did not match filter '${myDEPLOY_BRANCH_PATTERN}'" - } - } else { - echo "Not deploying because deploy-job parameters are not set" - } - - manager.addShortText("Processing of push to packaging completed") - } - } - } - } - - } - - stage ('cleanup') { - when { expression { return ( params.DO_CLEANUP_AFTER_BUILD ) } } - steps { - deleteDir() - } - } - } - - post { - success { - script { - if (currentBuild.getPreviousBuild()?.result != 'SUCCESS') { - // Uncomment desired notification - - //slackSend (color: "#008800", message: "Build ${env.JOB_NAME} is back to normal.") - //emailext (to: "qa@example.com", subject: "Build ${env.JOB_NAME} is back to normal.", body: "Build ${env.JOB_NAME} is back to normal.") - } - if ( params.DO_CLEANUP_AFTER_JOB ) { - dir("tmp") { - deleteDir() - } - } - } - } - failure { - // Uncomment desired notification - // Section must not be empty, you can delete the sleep once you set notification - sleep 1 - //slackSend (color: "#AA0000", message: "Build ${env.BUILD_NUMBER} of ${env.JOB_NAME} ${currentBuild.result} (<${env.BUILD_URL}|Open>)") - //emailext (to: "qa@example.com", subject: "Build ${env.JOB_NAME} failed!", body: "Build ${env.BUILD_NUMBER} of ${env.JOB_NAME} ${currentBuild.result}\nSee ${env.BUILD_URL}") - - dir("tmp") { - script { - if ( params.DO_CLEANUP_AFTER_FAILED_JOB ) { - deleteDir() - } else { - sh """ echo "NOTE: BUILD AREA OF WORKSPACE `pwd` REMAINS FOR POST-MORTEMS ON `hostname` AND CONSUMES `du -hs . | awk '{print \$1}'` !" """ - } - } - } - } - } -} diff --git a/Makefile.am b/Makefile.am index 83965a77c1..9fca463796 100644 --- a/Makefile.am +++ b/Makefile.am @@ -163,13 +163,20 @@ EXTRA_DIST += INSTALL.nut.adoc UPGRADING.adoc TODO.adoc NEWS.adoc README.adoc # The document is now part of qa-guide; the script might be a bit git-oriented, # but can be useful in builds from tarball, probably. Anyhow, having the doc # without the tool which it documents is odd. -EXTRA_DIST += ci_build.sh ci_build.adoc +EXTRA_DIST += ci_build.sh ci_build.adoc \ + builds/nut-driver-enumerator-test/ci_build.sh # Tarballs created by `make dist` include the `configure.ac` and `m4/*` sources # but lack NUT magic logic to recreate the `configure` script if someone would # want to adapt it to their autotools or locally fix a tarball-based build. EXTRA_DIST += autogen.sh +# Help also the tarball users contribute properly: +EXTRA_DIST += .editorconfig + +# automake xompilation wrapper +EXTRA_DIST += compile + if KEEP_NUT_REPORT nodist_data_DATA = config.nut_report_feature.log endif KEEP_NUT_REPORT @@ -202,7 +209,7 @@ SUBDIR_TGT_RULE = ( \ all-libs-local/include: +@$(SUBDIR_TGT_RULE) -### Delivers: libcommon.la libcommonclient.la libcommonstr.la +### Delivers: libcommon.la libcommonclient.la libcommonstr.la libcommonstrjson.la ### (consume only one of these at a time!) ### Delivers: libcommonversion.la (only version methods) ### Delivers: libparseconf.la libnutconf.la libnutwincompat.la @@ -323,6 +330,7 @@ all/common: all/include all-libs-local/common ### Requires-ext: common/libcommon.la common/libcommonclient.la ### Requires-ext: common/libparseconf.la ### Requires-ext: common/libcommonversion.la +### Requires-ext: common/libcommonstrjson.la ### Requires-int: libupsclient.la all/clients: all/common all-libs-local/clients +@$(SUBDIR_TGT_RULE) @@ -739,7 +747,7 @@ SPELLCHECK_DIRS_MOST = \ spellcheck/docs/man \ spellcheck/conf \ spellcheck/data \ - spellcheck/data/html \ + spellcheck/data/htmlcgi \ spellcheck/scripts \ spellcheck/scripts/DMF \ spellcheck/scripts/Solaris \ @@ -748,6 +756,7 @@ SPELLCHECK_DIRS_MOST = \ spellcheck/scripts/external_apis \ spellcheck/scripts/hotplug \ spellcheck/scripts/installer \ + spellcheck/scripts/obs \ spellcheck/scripts/python \ spellcheck/scripts/systemd \ spellcheck/scripts/udev \ @@ -782,11 +791,11 @@ spellcheck spellcheck-interactive: (cd $(builddir)/docs && $(MAKE) $(AM_MAKEFLAGS) -k -s $(abs_top_builddir)/docs/.prep-src-docs) || RES=$$? ; \ (cd $(builddir)/docs/man && $(MAKE) $(AM_MAKEFLAGS) -k -s $(abs_top_builddir)/docs/man/.prep-src-docs) || RES=$$? ; \ for D in $(SPELLCHECK_DIRS_MOST) ; do \ - D="`echo "$$D" | sed 's,^spellcheck/,,'`" ; \ + D="`echo \"$$D\" | sed 's,^spellcheck/,,'`" ; \ (cd "$(builddir)/$$D" && $(MAKE) $(AM_MAKEFLAGS) -k -s $@) || RES=$$? ; \ done ; \ for D in $(SPELLCHECK_DIRS_LAST) ; do \ - D="`echo "$$D" | sed 's,^spellcheck/,,'`" ; \ + D="`echo \"$$D\" | sed 's,^spellcheck/,,'`" ; \ (cd "$(builddir)/$$D" && $(MAKE) $(AM_MAKEFLAGS) SPELLCHECK_REPORT_MAYBE_UPDATED_DICT=yes -k -s $@) || RES=$$? ; \ done ; \ exit $$RES ; \ @@ -796,9 +805,13 @@ spellcheck spellcheck-interactive: SUBDIR_MAKE_VERBOSE=0 ; \ fi ; \ export SUBDIR_MAKE_VERBOSE ; \ - $(MAKE) $(AM_MAKEFLAGS) SPELLCHECK_TGT='$@' SUBDIR_MAKE_VERBOSE="$${SUBDIR_MAKE_VERBOSE}" -k -s $(SPELLCHECK_DIRS) && exit ; \ + ( $(MAKE) $(AM_MAKEFLAGS) SPELLCHECK_TGT='$@' SUBDIR_MAKE_VERBOSE="$${SUBDIR_MAKE_VERBOSE}" -k -s $(SPELLCHECK_DIRS) && exit ; \ echo "WARNING: FAILED fanned-out attempt in $@, retrying with NUT_MAKE_SKIP_FANOUT" >&2 ; \ - $(MAKE) $(AM_MAKEFLAGS) NUT_MAKE_SKIP_FANOUT=true SPELLCHECK_TGT='$@' -k -s $(SPELLCHECK_DIRS) + $(MAKE) $(AM_MAKEFLAGS) NUT_MAKE_SKIP_FANOUT=true SPELLCHECK_TGT='$@' -k -s $(SPELLCHECK_DIRS) ) || exit ; \ + if [ x'$@' = xspellcheck-interactive ] ; then \ + echo "SUCCESS: $@: follow up with spellcheck-quick to revise and update timestamps"; \ + $(MAKE) $(AM_MAKEFLAGS) spellcheck-quick ; \ + fi # Auto-parallel recipe (if current 'make' implementation supports the "-j N" # syntax; the optional MAXPARMAKES may be set in NUT CI farm style builds): @@ -807,8 +820,8 @@ SET_PARMAKES_OPT = \ case " $(MAKEFLAGS) $(AM_MAKEFLAGS)" in \ *"j"*) ;; \ *) \ - if ! [ "$${MAXPARMAKES-}" -gt 1 ] 2>/dev/null ; then \ - MAXPARMAKES=8 ; \ + if [ "$${MAXPARMAKES-}" -gt 1 ] 2>/dev/null ; then \ + true ; else MAXPARMAKES=8 ; \ fi ; \ PARMAKES_OPT="-j $${MAXPARMAKES}" ; \ ;; \ @@ -825,7 +838,7 @@ spellcheck-interactive-quick: if [ x"$(SUBDIR_MAKE_VERBOSE)" = xdefault ] ; then \ SUBDIR_MAKE_VERBOSE=1 ; export SUBDIR_MAKE_VERBOSE ; \ fi ; \ - $(MAKE) $(AM_MAKEFLAGS) -k -s spellcheck-interactive + $(MAKE) $(AM_MAKEFLAGS) SPELLCHECK_QUICK=true -k -s spellcheck-interactive # Note: the "all-docs" and "check-docs" targets may require tools not # found by `configure` script (and so avoided by conventional recipes) @@ -868,7 +881,7 @@ maintainer-asciidocs: find . -name '*.adoc' -or -name '*.txt' | ( \ FILES=""; \ while read F ; do \ - grep -E '^//+GH_MARKUP_1095_INCLUDE_(BEGIN|END)' "$$F" >/dev/null \ + $(EGREP) '^//+GH_MARKUP_1095_INCLUDE_(BEGIN|END)' "$$F" >/dev/null \ || { echo "$@: SKIP: no GH_MARKUP_1095_INCLUDE_* tags: $$F"; continue ; } ; \ rm -f "$${F}"*.tmp || exit ; \ EXT="1.tmp"; \ @@ -879,7 +892,7 @@ maintainer-asciidocs: esac ; \ printf '%s\n' "$${LINE}" >> "$${F}.$${EXT}" || exit ; \ done < "$$F" || { echo "$@: FAILED injection for $${F}" >&2; exit 1; } ; \ - if test -s "$${F}.2.tmp" && test -z "`diff "$${F}.2.tmp" docs/asciidoc-vars.conf | tr -d '\n'`" ; then \ + if test -s "$${F}.2.tmp" && test -z "`diff \"$${F}.2.tmp\" docs/asciidoc-vars.conf | tr -d '\n'`" ; then \ rm -f "$${F}"*.tmp ; \ echo "$@: SKIP: no changes: $$F"; continue ; \ fi; \ @@ -916,10 +929,13 @@ 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 +# ONLY populated into dist tarball if present in the build area! +# (Note we do tarball the VERSION_DEFAULT generated just above) 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 \ + echo " DIST $$D/$$F => $(top_distdir)/$$F"; \ cat "$$D/$$F" > "$(top_distdir)/$$F" || true ; \ fi ; \ done ; \ @@ -939,9 +955,9 @@ dist-hook: ### 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 ] && /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 ; \ + @RUNBASH=bash; if [ -x /bin/bash ] && /bin/bash -c 'echo $${BASH_VERSION}' | $(EGREP) '^[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 \ + case "`file \"$$F\"`" in \ *"Bourne-Again shell script"*) ( set -x ; $$RUNBASH -n "$$F" ; ) ;; \ *"POSIX shell script"*|*"shell script"*) ( set -x ; /bin/sh -n "$$F" ; ) ;; \ esac || { RES=$$? ; echo "ERROR: Syntax check failed for script file: $$F" >&2 ; exit $$RES ; } ; \ @@ -958,7 +974,11 @@ shellcheck-disclaimer: # Note: currently not part of shellcheck target, because the script below # can test the logic with numerous SHELL_PROGS in a CI setting, and because # check-scripts-syntax probably has checked the basic syntax above already. -shellcheck-nde: +nut-driver-enumerator.sh/scripts/upsdrvsvcctl: + +@$(SUBDIR_TGT_RULE) + +shellcheck-nde: nut-driver-enumerator.sh/scripts/upsdrvsvcctl + GREP="$(GREP)"; EGREP="$(EGREP)"; export GREP; export EGREP; \ cd $(srcdir)/tests && SERVICE_FRAMEWORK="selftest" ./nut-driver-enumerator-test.sh shellcheck: shellcheck-disclaimer check-scripts-syntax @@ -1048,43 +1068,43 @@ endif !WITH_PDF_NONASCII_TITLES 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 \ - 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 \ + if ( test -e .git ) 2>/dev/null || test -d .git || test -f .git || test -h .git ; then \ + NUT_GITDIR=".git" ; if test -r "$${NUT_GITDIR}" -a ! -d "$${NUT_GITDIR}" ; then GD="`$(EGREP) '^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 \ - if test -s "$@" ; then \ + if test -s '$@' ; then \ echo " DOC-CHANGELOG-GENERATE $@ : RE-GENERATE (older than Git workspace metadata) ..." ; \ else \ echo " DOC-CHANGELOG-GENERATE $@ : GENERATE (currently absent) ..." ; \ fi ; \ - CHANGELOG_FILE="$@" $(WITH_PDF_NONASCII_TITLES_ENVVAR) \ + 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) $(GITLOG_END_POINT) \ && { echo " DOC-CHANGELOG-GENERATE $@ : SUCCESS"; } \ || { \ 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" > "$@" ; \ + 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 x"$(abs_top_srcdir)" != x"$(abs_top_builddir)" -a -s ./ChangeLog ; then \ echo " DOC-CHANGELOG-GENERATE $@ : SKIP (keep existing)" ; \ - if ! diff ./ChangeLog "$@" >/dev/null 2>/dev/null ; then \ - echo "Using distributed ChangeLog file from sources (and builddir is not srcdir)" >&2 ; \ - rm -f "$@" || true ; \ - cp -pf ./ChangeLog "$@" || { cat ./ChangeLog > "$@" ; touch -r ./ChangeLog "$@" || true ; } ; \ - else \ + if diff ./ChangeLog '$@' >/dev/null 2>/dev/null ; then \ echo "Using distributed ChangeLog file from sources (and builddir already has content identical to one in srcdir)" >&2 ; \ + else \ + echo "Using distributed ChangeLog file from sources (and builddir is not srcdir)" >&2 ; \ + rm -f '$@' || true ; \ + cp -pf ./ChangeLog '$@' || { cat ./ChangeLog > '$@' ; touch -r ./ChangeLog '$@' || true ; } ; \ fi ; \ else \ - if test -s "$@" ; then \ + if test -s '$@' ; then \ echo " DOC-CHANGELOG-GENERATE $@ : SKIP (keep existing)" ; \ echo "Using distributed ChangeLog file from sources (and builddir is srcdir)" >&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" > "$@" ; \ + printf "Failed to generate the ChangeLog.\n\nNOTE: See https://github.com/networkupstools/nut/commits/master for change history.\n\n" > '$@' ; \ fi ; \ fi ; \ fi @@ -1102,6 +1122,65 @@ libupsclient-version.h clients/libupsclient-version.h: tools/gitlog2changelog.py: tools/gitlog2changelog.py.in +cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) -s $(@F) +# Partially snatched from automake generated code +distcheck-completeness: dist + @chmod -R +w $(distdir) $(distdir)-orig-* $(distdir)-derived-* || true + @rm -rf $(distdir) $(distdir)-orig-* $(distdir)-derived-* || true + @case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + *.tar.zst*) \ + zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ + esac + +@RES=0 ; \ + { cp -rf $(distdir) $(distdir)-orig-$$$$ \ + && am__cwd="`pwd`" \ + && $(am__cd) $(distdir) \ + && ./configure \ + $(AM_DISTCHECK_CONFIGURE_FLAGS) \ + $(DISTCHECK_CONFIGURE_FLAGS) \ + --prefix="`pwd`/.inst" \ + && $(MAKE) $(AM_MAKEFLAGS) dist \ + && case '$(DIST_ARCHIVES)' in \ + *.tar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).tar.gz | $(am__untar) ;;\ + *.tar.bz2*) \ + bzip2 -dc $(distdir).tar.bz2 | $(am__untar) ;;\ + *.tar.lz*) \ + lzip -dc $(distdir).tar.lz | $(am__untar) ;;\ + *.tar.xz*) \ + xz -dc $(distdir).tar.xz | $(am__untar) ;;\ + *.tar.Z*) \ + uncompress -c $(distdir).tar.Z | $(am__untar) ;;\ + *.shar.gz*) \ + eval GZIP= gzip $(GZIP_ENV) -dc $(distdir).shar.gz | unshar ;;\ + *.zip*) \ + unzip $(distdir).zip ;;\ + *.tar.zst*) \ + zstd -dc $(distdir).tar.zst | $(am__untar) ;;\ + esac \ + && mv $(distdir) ../$(distdir)-derived-$$$$ \ + && $(MAKE) $(AM_MAKEFLAGS) maintainer-clean \ + && cd "$$am__cwd" \ + && echo " $@ diff $(distdir)-orig-$$$$ $(distdir)-derived-$$$$" \ + && diff $(distdir)-orig-$$$$ $(distdir)-derived-$$$$ ; \ + } || RES=$?? ; + rm -rf $(distdir)-orig-$$$$ $(distdir)-derived-$$$$ $(distdir) ; \ + exit $$RES + @echo "SUCCESS: $(distdir) archives are self-reproducing" + # ---------------------------------------------------------------------- # Maintainers targets: distribution signature and hashes # Assume tools are available (and maintainer GPG keys) @@ -1111,6 +1190,9 @@ nut-@PACKAGE_VERSION@.tar.gz: dist nut-@PACKAGE_VERSION@.tar.gz.sig: dist-sig nut-@PACKAGE_VERSION@.tar.gz.md5 nut-@PACKAGE_VERSION@.tar.gz.sha256: dist-hash +# Bonus feature, results depend on configure script options and available tools +nut-@PACKAGE_VERSION@-docs.tar.gz: dist-docs + dist-sig: nut-@PACKAGE_VERSION@.tar.gz rm -f nut-@PACKAGE_VERSION@.tar.gz.sig gpg --detach-sign nut-@PACKAGE_VERSION@.tar.gz @@ -1119,6 +1201,51 @@ dist-hash: nut-@PACKAGE_VERSION@.tar.gz md5sum nut-@PACKAGE_VERSION@.tar.gz > nut-@PACKAGE_VERSION@.tar.gz.md5 sha256sum nut-@PACKAGE_VERSION@.tar.gz > nut-@PACKAGE_VERSION@.tar.gz.sha256 +# Helper to have all built docs (config-dependent) neatly aligned +# NOT part of standard tarball, though +EXTRA_DIST_DOCS = VERSION_* *.adoc-parsed + +if WITH_CHANGELOG_TEXT +EXTRA_DIST_DOCS += ChangeLog +endif WITH_CHANGELOG_TEXT + +if WITH_CHANGELOG_ADOC +EXTRA_DIST_DOCS += ChangeLog.adoc +endif WITH_CHANGELOG_ADOC + +if KEEP_NUT_REPORT +EXTRA_DIST_DOCS += config.nut_report_feature.log +endif KEEP_NUT_REPORT + +if WITH_HTML_SINGLE +EXTRA_DIST_DOCS += docs/man/*.html docs/*.html +endif WITH_HTML_SINGLE + +if WITH_HTML_CHUNKED +EXTRA_DIST_DOCS += docs/*.chunked +endif WITH_HTML_CHUNKED + +if WITH_PDFS +EXTRA_DIST_DOCS += docs/*.pdf +endif WITH_PDFS + +if WITH_MANS +if !KNOWN_UNABLE_MANS +EXTRA_DIST_DOCS += \ + docs/man/*.@MAN_SECTION_API_BASE@ \ + docs/man/*.@MAN_SECTION_CFG_BASE@ \ + docs/man/*.@MAN_SECTION_CMD_SYS_BASE@ \ + docs/man/*.@MAN_SECTION_CMD_USR_BASE@ \ + docs/man/*.@MAN_SECTION_MISC_BASE@ +endif !KNOWN_UNABLE_MANS +endif WITH_MANS + +# Modeled after automake generated mesh of rules for "distdir" handling with "am__tar" +dist-docs: all-docs + $${TAR-tar} cf - $(EXTRA_DIST_DOCS) | eval GZIP= gzip $(GZIP_ENV) -c > $(distdir)-docs.tar.gz + ls -la $(distdir)-docs.tar.gz + $${TAR-tar} tzvf $(distdir)-docs.tar.gz || true + # ---------------------------------------------------------------------- # targets from old build system (pre-automake). # supported for a period of time for backward "compatibility". @@ -1187,7 +1314,7 @@ install-data-hook: @case "@target_os@" in *mingw*) exit 0;; esac ; \ if [ x"@host_os@" != x"@build_os@" ]; then exit 0 ; fi ; \ if [ x"@target_os@" != x"@build_os@" ]; then exit 0 ; fi ; \ - if (command -v id) && [ x"`id -u`" = x0 ] && [ x"$(DESTDIR)" = x -o x"$(DESTDIR)" = x/ ] ; then \ + if (command -v id) && ( [ x"`id -u 2>/dev/null`" = x0 ] || [ x"`id | sed -e 's,(.*$$,,' -e 's,^.*uid=,,'`" = x0 ] ) && [ x"$(DESTDIR)" = x -o x"$(DESTDIR)" = x/ ] ; then \ echo "================================================================================" >&2 ; \ echo "| NUT data files have been installed into the system, now consider running |" >&2 ; \ echo "| '(sudo) make install-as-root' to apply permissions and service state changes |" >&2 ; \ @@ -1236,24 +1363,24 @@ endif !WITH_SOLARIS_INIT # distros that do not follow current naming in NUT code base. install-as-root: @+echo "$@: starting (no-op if not root)" >&2 ; \ - case "@target_os@" in *mingw*) echo "$@: SKIP: not supported for this target_os='@target_os@'" >&2 ; exit 0;; esac ; \ - if [ x"@host_os@" != x"@build_os@" ]; then echo "$@: SKIP: build_os='@build_os@' is not host_os='@host_os@'" >&2 ; exit 0 ; fi ; \ - if [ x"@target_os@" != x"@build_os@" ]; then echo "$@: SKIP: build_os='@build_os@' is not target_os='@target_os@'" >&2 ; exit 0 ; fi ; \ - if (command -v id) && [ x"`id -u`" = x0 ] ; then \ + case "$(target_os)" in *mingw*) echo "$@: SKIP: not supported for this target_os='$(target_os)'" >&2 ; exit 0;; esac ; \ + if [ x"$(host_os)" != x"$(build_os)" ]; then echo "$@: SKIP: build_os='$(build_os)' is not host_os='$(host_os)'" >&2 ; exit 0 ; fi ; \ + if [ x"$(target_os)" != x"$(build_os)" ]; then echo "$@: SKIP: build_os='$(build_os)' is not target_os='$(target_os)'" >&2 ; exit 0 ; fi ; \ + if (command -v id) && ( [ x"`id -u 2>/dev/null`" = x0 ] || [ x"`id | sed -e 's,(.*$$,,' -e 's,^.*uid=,,'`" = x0 ] ) ; then \ echo "$@: we seem to be root, PROCEEDING" >&2 ; \ else \ echo "$@: SKIP: we seem to NOT be root" >&2 ; \ exit 0 ; \ fi ; \ - prefix="@prefix@"; \ + prefix="$(prefix)"; \ if [ x"$(DESTDIR)" = x -o x"$(DESTDIR)" = x/ ] ; then \ if $(HAVE_SYSTEMD) ; then \ echo "$@: Stop NUT services, if any" >&2 ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ stop nut-monitor.service nut-server.service || true ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ stop nut-driver.service || true ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ stop nut-driver.target || true ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ stop nut-udev-settle.service || true ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ stop nut.target || true ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) stop nut-monitor.service nut-server.service || true ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) stop nut-driver.service || true ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) stop nut-driver.target || true ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) stop nut-udev-settle.service || true ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) stop nut.target || true ; \ fi ; \ if $(WITH_SOLARIS_SMF) || $(WITH_SOLARIS_INIT) ; then \ if $(WITH_SOLARIS_SMF) ; then \ @@ -1275,9 +1402,9 @@ install-as-root: exit ; \ fi ; \ fi ; \ - echo " MKDIR $(DESTDIR)/@STATEPATH@ $(DESTDIR)/@STATEPATH@/upssched" >&2 ; \ - $(MKDIR_P) "$(DESTDIR)/@STATEPATH@/upssched" && \ - for D in "@PIDPATH@" "@ALTPIDPATH@" "@ALTSTATEPATH@" "@CONFPATH@" ; do \ + echo " MKDIR $(DESTDIR)/$(STATEPATH) $(DESTDIR)/$(STATEPATH)/upssched" >&2 ; \ + $(MKDIR_P) "$(DESTDIR)/$(STATEPATH)/upssched" && \ + for D in "$(PIDPATH)" "$(ALTPIDPATH)" "$(ALTSTATEPATH)" "$(CONFPATH)" ; do \ case x"$$D" in \ x|x@*) ;; \ *) echo " MKDIR $(DESTDIR)/$$D" >&2 ; \ @@ -1286,10 +1413,10 @@ install-as-root: esac ; \ done ; \ if (command -v chmod) ; then \ - echo " CHMOD(0770) $(DESTDIR)/@STATEPATH@/upssched" >&2 ; \ - chmod 0770 "$(DESTDIR)/@STATEPATH@/upssched" \ + echo " CHMOD(0770) $(DESTDIR)/$(STATEPATH)/upssched" >&2 ; \ + chmod 0770 "$(DESTDIR)/$(STATEPATH)/upssched" \ || exit ; \ - for D in "@STATEPATH@" "@PIDPATH@" "@ALTPIDPATH@" "@ALTSTATEPATH@" ; do \ + for D in "$(STATEPATH)" "$(PIDPATH)" "$(ALTPIDPATH)" "$(ALTSTATEPATH)" ; do \ case x"$$D" in \ x|x@*|x/run|x/var/run|x/tmp|x/var/tmp|x/dev/shm|x/etc|x/var|x/usr|x/usr/local|x/usr/local/etc|x/usr/etc) ;; \ *) echo " CHMOD(0770) $(DESTDIR)/$$D" >&2 ; \ @@ -1297,53 +1424,53 @@ install-as-root: || exit ;; \ esac ; \ done ; \ - case x"@CONFPATH@" in \ + case x"$(CONFPATH)" in \ x|x@*|x/run|x/var/run|x/tmp|x/var/tmp|x/dev/shm|x/etc|x/var|x/usr|x/usr/local|x/usr/local/etc|x/usr/etc) ;; \ - *) echo " CHMOD(0751) $(DESTDIR)/@CONFPATH@" >&2 ; \ - chmod 0751 "$(DESTDIR)/@CONFPATH@" \ + *) echo " CHMOD(0751) $(DESTDIR)/$(CONFPATH)" >&2 ; \ + chmod 0751 "$(DESTDIR)/$(CONFPATH)" \ || exit ;; \ esac ; \ for F in hosts.conf.sample upsstats-single.html.sample upsstats.html.sample upsset.conf.sample ; do \ - echo " CHMOD(0644) CGI: $(DESTDIR)/@CONFPATH@/$$F" >&2 ; \ - chmod 0644 "$(DESTDIR)/@CONFPATH@/$$F" \ + echo " CHMOD(0644) CGI: $(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" >&2 ; \ + chmod 0644 "$(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" \ || { if $(WITH_CGI) ; then exit 1 ; else true ; fi ; } ; \ done ; \ for F in nut.conf.sample ups.conf.sample upsd.conf.sample upsd.users.sample upsmon.conf.sample upssched.conf.sample ; do \ - echo " CHMOD(0640) $(DESTDIR)/@CONFPATH@/$$F" >&2 ; \ - chmod 0640 "$(DESTDIR)/@CONFPATH@/$$F" \ + echo " CHMOD(0640) $(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" >&2 ; \ + chmod 0640 "$(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" \ || exit ; \ done ; \ else \ echo "$@: WARNING: Can not CHMOD created locations!" >&2 ; \ fi ; \ - if (command -v chown) && test 0 -lt "`id -u '@RUN_AS_USER@'`" \ - && ( test 0 -lt "`getent group '@RUN_AS_GROUP@' | awk -F: '{print $$3}'`" || test 0 -lt "`id -g '@RUN_AS_GROUP@'`" ) \ + if (command -v chown) && ( test 0 -lt "`id -u '$(RUN_AS_USER)' 2>/dev/null`" || test 0 -lt "`id '$(RUN_AS_USER)' | sed -e 's,(.*$$,,' -e 's,^.*uid=,,'`" ] ) \ + && ( test 0 -lt "`getent group '$(RUN_AS_GROUP)' | awk -F: '{print $$3}'`" || test 0 -lt "`id -g '$(RUN_AS_GROUP)'`" ) \ ; then \ - echo " CHOWN(@RUN_AS_USER@:@RUN_AS_GROUP@) $(DESTDIR)/@STATEPATH@/upssched" >&2 ; \ - chown "@RUN_AS_USER@:@RUN_AS_GROUP@" "$(DESTDIR)/@STATEPATH@/upssched" \ + echo " CHOWN($(RUN_AS_USER):$(RUN_AS_GROUP)) $(DESTDIR)/$(STATEPATH)/upssched" >&2 ; \ + chown "$(RUN_AS_USER):$(RUN_AS_GROUP)" "$(DESTDIR)/$(STATEPATH)/upssched" \ || exit ; \ - for D in "@STATEPATH@" "@PIDPATH@" "@ALTPIDPATH@" "@ALTSTATEPATH@" ; do \ + for D in "$(STATEPATH)" "$(PIDPATH)" "$(ALTPIDPATH)" "$(ALTSTATEPATH)" ; do \ case x"$$D" in \ x|x@*|x/run|x/var/run|x/tmp|x/var/tmp|x/dev/shm|x/etc|x/var|x/usr|x/usr/local|x/usr/local/etc|x/usr/etc) ;; \ - *) echo " CHOWN(@RUN_AS_USER@:@RUN_AS_GROUP@) $(DESTDIR)/$$D" >&2 ; \ - chown "@RUN_AS_USER@:@RUN_AS_GROUP@" "$(DESTDIR)/$$D" \ + *) echo " CHOWN($(RUN_AS_USER):$(RUN_AS_GROUP)) $(DESTDIR)/$$D" >&2 ; \ + chown "$(RUN_AS_USER):$(RUN_AS_GROUP)" "$(DESTDIR)/$$D" \ || exit ;; \ esac ; \ done ; \ - case x"@CONFPATH@" in \ + case x"$(CONFPATH)" in \ x|x@*|x/run|x/var/run|x/tmp|x/var/tmp|x/dev/shm|x/etc|x/var|x/usr|x/usr/local|x/usr/local/etc|x/usr/etc) ;; \ - *) echo " CHOWN(root:@RUN_AS_GROUP@) $(DESTDIR)/@CONFPATH@" >&2 ; \ - chown "root:@RUN_AS_GROUP@" "$(DESTDIR)/@CONFPATH@" \ + *) echo " CHOWN(root:$(RUN_AS_GROUP)) $(DESTDIR)/$(CONFPATH)" >&2 ; \ + chown "root:$(RUN_AS_GROUP)" "$(DESTDIR)/$(CONFPATH)" \ || exit ;; \ esac ; \ for F in hosts.conf.sample upsstats-single.html.sample upsstats.html.sample upsset.conf.sample ; do \ - echo " CHOWN(root:@RUN_AS_GROUP@) CGI: $(DESTDIR)/@CONFPATH@/$$F" >&2 ; \ - chown "root:@RUN_AS_GROUP@" "$(DESTDIR)/@CONFPATH@/$$F" \ + echo " CHOWN(root:$(RUN_AS_GROUP)) CGI: $(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" >&2 ; \ + chown "root:$(RUN_AS_GROUP)" "$(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" \ || { if $(WITH_CGI) ; then exit 1 ; else true ; fi ; } ; \ done ; \ for F in nut.conf.sample ups.conf.sample upsd.conf.sample upsd.users.sample upsmon.conf.sample upssched.conf.sample ; do \ - echo " CHOWN(root:@RUN_AS_GROUP@) $(DESTDIR)/@CONFPATH@/$$F" >&2 ; \ - chown "root:@RUN_AS_GROUP@" "$(DESTDIR)/@CONFPATH@/$$F" \ + echo " CHOWN(root:$(RUN_AS_GROUP)) $(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" >&2 ; \ + chown "root:$(RUN_AS_GROUP)" "$(DESTDIR)/$(CONFPATH_EXAMPLES)/$$F" \ || exit ; \ done ; \ else \ @@ -1355,31 +1482,33 @@ install-as-root: echo "$@: Activate default systemd layout, restart services:" >&2 ; \ if $(WITH_SYSTEMD_TMPFILES) ; then \ echo "$@: Apply systemd-tmpfiles presets" >&2 ; \ - @SYSTEMD_TMPFILES_PROGRAM@ --create || exit ; \ + $(SYSTEMD_TMPFILES_PROGRAM) --create || exit ; \ fi ; \ echo "$@: Learn systemd definition changes" >&2 ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ daemon-reload || exit ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) daemon-reload || exit ; \ APPLIED_SYSTEMD_PRESET=false ; \ if $(WITH_SYSTEMD_PRESET) ; then \ echo "$@: Apply systemd enabled/disabled service presets" >&2 ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ preset-all && APPLIED_SYSTEMD_PRESET=true || APPLIED_SYSTEMD_PRESET=false ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) preset-all && APPLIED_SYSTEMD_PRESET=true || APPLIED_SYSTEMD_PRESET=false ; \ fi ; \ if [ x"$${APPLIED_SYSTEMD_PRESET}" = x"false" ] ; then \ echo "$@: Apply systemd enabled/disabled service defaults in a legacy manner" >&2 ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ disable nut.target nut-driver.target nut-udev-settle.service nut-monitor nut-server nut-driver-enumerator.path nut-driver-enumerator.service || exit ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ enable nut.target nut-driver.target nut-udev-settle.service nut-monitor nut-server nut-driver-enumerator.path nut-driver-enumerator.service || exit ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) disable nut.target nut-driver.target nut-udev-settle.service nut-monitor nut-server nut-driver-enumerator.path nut-driver-enumerator.service || exit ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) enable nut.target nut-driver.target nut-udev-settle.service nut-monitor nut-server nut-driver-enumerator.path nut-driver-enumerator.service || exit ; \ fi ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ restart udev && applied_udev=true || true ; \ - if [ -s '@sysconfdir@/ups.conf' ] ; then \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) restart udev && applied_udev=true || true ; \ + if [ -s '$(CONFPATH)/ups.conf' ] ; then \ echo "$@: Reconfigure nut-driver-enumerator (service instance wrapping)" >&2 ; \ $(top_builddir)/scripts/upsdrvsvcctl/nut-driver-enumerator.sh --reconfigure || { RES=$$?; if [ $$RES != 42 ] ; then exit $$RES ; fi ; } ; \ fi; \ echo "$@: Restart NUT services" >&2 ; \ - @SYSTEMD_SYSTEMCTL_PROGRAM@ restart nut-driver-enumerator.service nut-monitor.service nut-server.service \ - || if [ -s '@sysconfdir@/ups.conf' -a -s '@sysconfdir@/upsd.conf' -a -s '@sysconfdir@/upsd.users' -a -s '@sysconfdir@/upsmon.conf' ] ; then exit 1 ; \ + $(SYSTEMD_SYSTEMCTL_PROGRAM) restart nut-driver-enumerator.service nut-monitor.service nut-server.service \ + || if [ -s '$(CONFPATH)/ups.conf' -a -s '$(CONFPATH)/upsd.conf' -a -s '$(CONFPATH)/upsd.users' -a -s '$(CONFPATH)/upsmon.conf' ] ; then exit 1 ; \ else echo "$@: some configs are missing, assuming new NUT installation" >&2; fi; \ fi ; \ - if ! $${applied_udev} && (command -v udevadm); then \ + if $${applied_udev} && (command -v udevadm); then \ + true ; \ + else \ udevadm control --reload-rules && udevadm trigger && applied_udev=true || true ; \ fi ; \ fi ; \ @@ -1388,13 +1517,13 @@ install-as-root: # Clean the dist tarball and packages MAINTAINERCLEANFILES_DISTBALL = nut-*.tar.gz # HP-UX: -MAINTAINERCLEANFILES_PACKAGES = NUT_HPUX_package@PACKAGE_VERSION@.depot NUT_HPUX_package-@PACKAGE_VERSION@.depot +MAINTAINERCLEANFILES_PACKAGES = NUT_HPUX_package$(PACKAGE_VERSION).depot NUT_HPUX_package-$(PACKAGE_VERSION).depot # AIX as below, and RedHat-compatible (cover binary and source packages): MAINTAINERCLEANFILES_PACKAGES += nut*rpm # Debian-compatible (cover binary and source packages): MAINTAINERCLEANFILES_PACKAGES += nut*deb # Solaris SVR4 package archives: -MAINTAINERCLEANFILES_PACKAGES += NUT_solaris_*_package@PACKAGE_VERSION@.local.gz NUT_solaris_*_package-@PACKAGE_VERSION@.local.gz +MAINTAINERCLEANFILES_PACKAGES += NUT_solaris_*_package$(PACKAGE_VERSION).local.gz NUT_solaris_*_package-$(PACKAGE_VERSION).local.gz # Newer Solaris IPS (aka "pkg(5)" format archives) MAINTAINERCLEANFILES_PACKAGES += *.p5p @@ -1408,7 +1537,7 @@ package: dist "HP-UX") \ ( cd scripts/HP-UX && \ $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$DESTDIR" package && \ - mv NUT_HPUX_package.depot $(abs_top_builddir)/NUT_HPUX_package-@PACKAGE_VERSION@.depot ) ;; \ + mv NUT_HPUX_package.depot $(abs_top_builddir)/NUT_HPUX_package-$(PACKAGE_VERSION).depot ) ;; \ "SunOS") \ $(MAKE) $(AM_MAKEFLAGS) && \ $(MAKE) $(AM_MAKEFLAGS) DESTDIR="$$DESTDIR" install && \ @@ -1422,7 +1551,7 @@ package: dist if test -d /usr/src/packages/SOURCES -a -w /usr/src/packages/SOURCES ; then : ; else echo "Can not write to /usr/src/packages/SOURCES" >&2 ; exit 1; fi ; \ $(MAKE) $(AM_MAKEFLAGS) dist && \ cp scripts/Aix/nut-aix.spec /usr/src/packages/SPECS && \ - cp scripts/Aix/nut.init nut-@PACKAGE_VERSION@.tar.gz /usr/src/packages/SOURCES && \ + cp scripts/Aix/nut.init nut-$(PACKAGE_VERSION).tar.gz /usr/src/packages/SOURCES && \ rpm -ba /usr/src/packages/SPECS/nut-aix.spec && \ mv /usr/src/packages/RPMS/nut*rpm $(abs_top_builddir)/ ;; \ *) echo "Unsupported OS for 'make $@' (no recipe bound)" >&2; exit 1;; \ @@ -1470,7 +1599,7 @@ install-win-bundle-thirdparty: '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ echo " DLL->sbin $$D" 2>&1 ; \ - ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ + ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit ; \ ( if test x"$(driverexecdir)" = x"$(bindir)" ; then exit 0 ; fi ; \ @@ -1479,7 +1608,7 @@ install-win-bundle-thirdparty: '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ echo " DLL->drv $$D" 2>&1 ; \ - ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ + ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit ; \ ( if test -z "$(cgiexecdir)" -o ! -d "$(DESTDIR)/$(cgiexecdir)" ; then exit 0 ; fi ; \ @@ -1490,7 +1619,7 @@ install-win-bundle-thirdparty: '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ echo " DLL->cgi $$D" 2>&1 ; \ - ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ + ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit ; \ ( if test x"$(libexecdir)" = x"$(bindir)" ; then exit 0 ; fi ; \ @@ -1501,7 +1630,7 @@ install-win-bundle-thirdparty: '$(abs_top_srcdir)/scripts/Windows/dllldd.sh' dllldddir . \ | while read D ; do \ echo " DLL->libexec $$D" 2>&1 ; \ - ln -f '$(DESTDIR)/$(bindir)'/"`basename "$$D"`" ./ ; \ + ln -f '$(DESTDIR)/$(bindir)'/"`basename \"$$D\"`" ./ ; \ done ; \ ) || exit @echo "CHECKING if any executable files were installed to locations other than those covered by this recipe, so might not have needed DLLs bundled near them" >&2 ; \ @@ -1511,8 +1640,8 @@ install-win-bundle-thirdparty: 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}|$${rellibexecdir})" \ + find . -type f | $(EGREP) -i '\.(exe|dll)$$' \ + | $(EGREP) -v "^($${relbindir}|$${relsbindir}|$${reldriverexecdir}|$${relcgiexecdir}|$${rellibexecdir})" \ | ( RES=0 ; while IFS= read LINE ; do echo "$$LINE" ; RES=1; done; exit $$RES ) else !HAVE_WINDOWS @@ -1531,7 +1660,7 @@ print-DISTCLEANFILES: # TODO: Recursive mode to consider patterns defined in sub-dir makefiles git-realclean-check: - @if test -e .git && (command -v git); then \ + @if ( ( test -e .git ) 2>/dev/null || test -d .git || test -f .git || test -h .git ) && (command -v git); then \ git status --ignored || while read F ; do \ for P in $(MAINTAINERCLEANFILES) ; do \ case "$$F" in \ @@ -1580,11 +1709,11 @@ check-parallel-builds: cd '$(abs_top_builddir)' || exit ; \ $(MAKE) $(AM_MAKEFLAGS) -k -s $${PARMAKES_OPT} clean || { RES=$$?; echo "$@: FAILED: make pre-clean before checking subdirs for sources" >&2; exit $$RES; } ; \ for D in `cd '$(top_srcdir)' && find . -name '*.c' -o -name '*.cpp' | sed 's,/[^/]*\.cp*$$,,' | uniq` ; do \ - [ -e "$(top_srcdir)/$$D/Makefile.am" ] && [ -s "$$D/Makefile" ] || continue ; \ + ( ( [ -e "$(top_srcdir)/$$D/Makefile.am" ] ) 2>/dev/null || [ -f "$(top_srcdir)/$$D/Makefile.am" ] || [ -h "$(top_srcdir)/$$D/Makefile.am" ] ) && [ -s "$$D/Makefile" ] || continue ; \ $(MAKE) $(AM_MAKEFLAGS) -k -s $${PARMAKES_OPT} clean || { RES=$$?; echo "$@: FAILED: make clean before going to $$D" >&2; exit $$RES; } ; \ echo " $@ in $${D}" ; \ ( cd "$$D" && $(MAKE) $(AM_MAKEFLAGS) -k -s $${PARMAKES_OPT} ) || { RES=$$?; echo "$@: FAILED: parallel make in $$D" >&2; \ - echo "To investigate, try: (MAKEFLAGS='$(MAKEFLAGS)' ; export MAKEFLAGS; $(MAKE) $(AM_MAKEFLAGS) $${PARMAKES_OPT} clean ; automake -f && ./config.status && clear && (cd $${D}/ && $(MAKE) $(AM_MAKEFLAGS) V=1 $${PARMAKES_OPT} 2>&1 ; echo $$?) | tee /tmp/make.log ; RES=$$? ; grep -E '(\] Error|No rule to)' /tmp/make.log && less /tmp/make.log ; exit $$RES )"; \ + echo "To investigate, try: (MAKEFLAGS='$(MAKEFLAGS)' ; export MAKEFLAGS; $(MAKE) $(AM_MAKEFLAGS) $${PARMAKES_OPT} clean ; automake -f && ./config.status && clear && (cd $${D}/ && $(MAKE) $(AM_MAKEFLAGS) V=1 $${PARMAKES_OPT} 2>&1 ; echo $$?) | tee /tmp/make.log ; RES=$$? ; $(EGREP) '(\] Error|No rule to)' /tmp/make.log && less /tmp/make.log ; exit $$RES )"; \ echo "If builds were interrupted before, you may also have to re-initialize the build area completely, e.g.: git clean -fdX ; ./ci_build.sh && $(MAKE) $(MAKEFLAGS) $(AM_MAKEFLAGS) $@" ; \ exit $$RES; } ; \ DIRS="$${DIRS} $${D}" ; \ diff --git a/NEWS.adoc b/NEWS.adoc index 4e538bbf45..584c0d7e57 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -13,11 +13,376 @@ ChangeLog file (generated for release archives), or to the Git version control history for "live" codebase. +PLANNED: Release notes for NUT 2.8.5 - what's new since 2.8.4 +------------------------------------------------------------- + +https://github.com/networkupstools/nut/milestone/12 + + - (expected) Dynamic Mapping Files (DMF) feature supported, to allow + the driver binaries to be built once and data mappings to be loaded + and modernized on the fly (porting from 42ITy project) + + - (expected) Porting of reference packaging from 42ITy project + + - (expected) Porting of patches suggested by different distribution packages + + - (expected) C code clean-up/consistency (string format security, work with + Boolean values, string to number conversions, etc. in the same manner) + + - (expected) clean-up of libusb API variants support [#300 and follow-ups] + + - (expected) CI automation for coding style + + - (expected) CI automation for driver flags and variables to be certainly + documented, handled in augeas lenses, nutconf classes, etc. + + - (expected) CI automation for use of data points in drivers that conform + to patterns defined in link:docs/nut-names.txt[] + + - common code: + * Generally improved shell script portability (including `Makefile.am` + embedded recipes) when mixing back-quotes and double-quotes. Namely, + the classic Solaris `ksh` implementation (also used in current versions + of Solaris and illumos based distributions) was strict about following + (an older revision of) the POSIX standard here, and some use-cases + misbehaved with our older script spelling. [issue #3196] + * Introduced `setproctag()` and `getproctag()` (see examples in `upsmon`) + to help track the log messages from massively-forking NUT daemons. [#3084] + * Extended with plural `checkprocnames()` and `compareprocnames()`, + as well as `sendsignalpidaliases()` and `sendsignalfnaliases()`, for + binaries that expect to have one of several names at the moment (e.g. + when renaming a program to "*-old" or promoting a "*-new" into the + stable name as a default implementation). Drivers united by `main.c` + framework introduced a `upsdrv_tweak_prognames()` hook method to let + them manipulate the array of `prognames[]` aliases. [PR #3101] + * `libusb0` and `libusb1` integrations now try both "Resource Descriptor + Length" values they could identify on some devices: sometimes the first + (or otherwise preferred) option may be not the correct one, so try the + other too. [issue #3136] + * For state tree methods, introduced `difftime_st_tree_timespec()` to + abstract platform-dependent definitions of `st_tree_timespec_t`. [PR #3156] + * Introduced global variables for last changed timestamp and value of + `battery.charge`, integrated with `status_commit()`, so all drivers + should now report a `CHRG` or `DISCHRG` status, and verify whether + reality agrees with a status reported by device/protocol (if any). + This is also a step in direction of eventually providing a common + `runtimecal` style fallback to all drivers. [issue #3146, PR #3156] + * Introduced `NOTIFY_STATE_EXTEND_TIMEOUT` option and relevant variables + for `upsnotify()` method, so we can tell systemd to not kill the service + unit if its shutdown takes long (see also the `SHUTDOWNEXIT` option in + the `upsmon` client). [issue #3003, PR #3151] + * Daemons running with systemd integration tended to report "logged the + systemd watchdog situation once, will not spam more about it" even if + those "logged" messages were at an invisible verbosity level. [issue #3157, + PR #3151] + + - `asem`, `bestfortress`, `bestuferrups`, `bicker_ser`, `everups`, `metasys`, + `masterguard`, `mge-utalk`, `oneac`, `phoenixcontact_modbus`, `pijuice`, + `powercom`, `powervar_cx_ser`, `usbhid-ups`, `victronups` driver updates: + * Moved code to align with NUT driver architecture, so `upsdrv_initups()` + would focus on preparing communications, and `upsdrv_initinfo()` would + actually talk to the device and set initial critical data values. [#1962] + + - `apcupsd-ups` driver updates: + * Abandoned use of obsolete `gethostbyname()` in favour of `getaddrinfo()`. + Extended to be IPv6-capable along the way. [#1209] + + - `nhs_ser` driver updates: + * Driver source code includes a table to map baud rates (defined by the OS + as C macros) to numbers and strings more useful to the driver program. + It was discovered that not all operating systems define all the baud rates + referenced by this table, so now several higher speeds would be compiled + conditionally. [issue #3163] + + - `nutdrv_qx` driver updates: + * Define an internal `QX_FLAG_MAPPING_HANDLED` to check if the subdriver + code (mapping table) and the data found from device walk sit together + well -- namely, that we successfully use reasonably many of the existing + mappings. Suggest how user can help improve the driver if too few data + points were seen. [PR #3095] + * New `nutdrv_qx_innovatae` adds support for Ippon INNOVA TAE series which + are similar to Q1 except in reporting of nominal `voltage`, `current`, + `frequency` data (that along with `battery_voltage_reports_one_pack` and + `override.battery.packs=12` settings yields reasonable `battery.voltage.*` + data points). [issue #3137, PR #3139] + * `nutdrv_qx_masterguard` sub-driver adds a way to call battery test until + the battery charge is low, using a new `test.battery.start.low` instant + command name (registered in `docs/nut-names.txt`). The difference between + the "battery test until battery low" and "deep battery test" is that the + latter is actually a battery calibration test and needs some preconditions + to be met, i.e. the load must be between 30% and 100% and the starting + battery capacity is greater than 99%. The "Battery test until battery low" + can be started anytime. Tested on Masterguard A1000 and A2000 units. [#3135] + * `nutdrv_qx_masterguard` sub-driver fix `masterguard_add_slaveaddr` to + return the correct command length, resolving a bug for all commands using + the `,XX` slave address placeholder. This also enables support for the + `test.battery.start.deep` (`TUD`) command. [#3141] + * `nutdrv_qx_masterguard` sub-driver adds a `clear.fault.record` instant + command to clear the fault record. Also adds two experimental variables: + `battery.ageing.factor` (Battery ageing factor in promille) and + `battery.calibration.factor` (Battery calibration factor in percent). + Tested on A1000 and A2000 units. [#3181] + * Hides QX_FLAG_NONUT variables from syslog unless the debug level + is raised. [issue #3190, PR #3198] + + - `powerp-bin` and `powerp-txt` driver updates: + * Their `upsdrv_initinfo()` methods did not explicitly reference the + `INSTCMD` and `SETVAR` handler methods (it is unknown whether this + actually broke anything, or the needed values were deduced from + subdriver definition then). For consistency, these pointers are now + assigned. [#1962] + + - `riello_ser` and `riello_usb` driver updates: + * Since the beginning, these drivers fenced availability of *either* + `load.*` *or* `shutdown.return` instant commands based on current + power situation (which was also unknown when the `drivername -k` mode + was used to shut down the system). There seems to be little reason for + such separation, so it is now avoided and the current device status + is queried when starting to handle the UPS poweroff, as a means to + verify communication ability. [#1962] + + - `snmp-ups` driver updates: + * Define an internal `SU_FLAG_MAPPING_HANDLED` to check if the subdriver + code (mapping table) and the data found from device walk sit together + well -- namely, that we successfully use reasonably many of the existing + mappings. Suggest how user can help improve the driver if too few data + points were seen, or if the `mibs=auto` detection only found the fallback + IETF mapping. [PR #3095] + + - `tripplite_usb` driver updates: + * Added support for Tripplite protocol 3017 (mostly ASCII). [issue #2258, + PR #3093] + * Fixed default `unit_id`, apparently broken by changes in NUT v2.8.1. + [issue #3191, PR #3192] + + - `usbhid-ups` driver updates: + * Declared support for APC with USB ID `051d:0005` (details may evolve + as the devices become better known). [issue #3047, PR #3081] + * Improved support for Eaton 5S1500LCD and 5S1600LCD (US version). [#2380] + * The driver used to report `CHRG` status if the device was not "fully + charged", however the latter status was only queried and reported by + some (not all) subdrivers and probably not by all devices out in the + field. Now we fall back to check that if the "fully charged" status + is not defined at all, consider the `battery.charge` value (100 or not). + [issue #3146] + * For `ups.status` processing, do not assume that `!online`==`offline`, + it may well be that the device just did not report anything (e.g. if + it does not really talk USB HID). [#3080] + * Suggest iterative testing with explicit `subdrivers` if collected data + looks wrong. [#2058, #3061 et al] + * Check if the subdriver code (mapping table) and the device report + descriptor sit together well -- namely, that we do not unwittingly + ignore any useful reports, and that we successfully use reasonably many + of the existing mappings. Suggest how user can help improve the driver + if too few data points were seen. [#3082, #3095] + + - `upsd` data server updates: + * Sometimes "Data for UPS [X] is stale" and "UPS [X] data is no longer + stale" messages were logged in the same second, especially no busy + systems. Now we allow one more second on top of `MAXAGE` setting to + declare the device dead, just in case fractional/whole second rounding + comes into play and breaks things. [issue #661] + + - `upsdrvctl` tool updates: + * Make use of `setproctag()` and `getproctag()` to report parent/child + process names. [#3084] + + - `upslog` tool updates: + * Updated `help()` and failure messages to suggest `-m '*,-'` for logging + of all known local devices to stdout. [#3083] + + - `upsmon` client updates: + * The `SHUTDOWNEXIT` option was not handled properly, and the requested + long delays did not happen in practice. [issues #2133 and #3084, PR #3086] + * Make use of `setproctag()` and `getproctag()` to report parent/child + process names. [#3084] + * Introduced a `SHUTDOWN_HOSTSYNC` notification message, to report that + the primary `upsmon` initiated the shutdown and has some secondaries + to wait for first. [#3084] + * Make sure an `FSD` notification is issued for each UPS when this primary + `upsmon` instance sets it (and does not return to usual data processing + loop to see and report it like secondaries do). This allows a `NOTIFYCMD` + such as `upssched` on the primary to handle the pending power outage + (e.g. begin stopping heavy services) even while `upsmon` waits for the + secondaries to complete their shutdowns and log out of the `upsd` data + server. [issue #3003, PR #3110] + * Tickle service watchdog timer (if any) so the `nut-monitor.service` or + equivalent unit is not killed off due to quietness during shutdown. [#3003] ++ +NOTE: If using `upssched` and monitoring multiple UPSes, consider setting up +a `START-TIMER-SHARED` rule with a short (approx. 1 second) timeout to group +several `FSD` notifications into one executed action. [PR #3097] + + - `upsc` has now optional JSON output [issue #3172, PR #3178] + + - `upsset` should now recognize `RANGE NUMBER` and `NUMBER` types. [#3164] + + - `upsstats` tool updates: + * Now has JSON output mode via `?json` (or `&json`) CGI query + parameter. [issue #2524, PR #3171] + * Handle `device.model` in addition to `ups.model` in upsstats HTML + templates. [#3180] + + - `upssched` tool updates: + * Previously in PR #2896 (NUT releases v2.8.3 and v2.8.4) the `UPSNAME` and + `NOTIFYTYPE` environment variables were neutered for the timer daemon, + since their values at the moment it first started were irrelevant when + actual timers fired. Lack of those values was however also against the + documented expectations. Now the values set when the `upssched` client + is called and ends up updating the timer daemon would be passed into + the timer. Also a `START-TIMER-SHARED` operation was added, to track a + single named timer for possibly many devices and/or event types -- in + this case they would be comma-separated in the environment variable + values. [issue #3092, PR #3097] + * The `CANCEL-TIMER` action no longer stops at first hit for the timer name, + but goes on (in case there are duplicates). A new `CANCEL-TIMER-MATCHED` + was added, which also cares to match `UPSNAME` and `NOTIFYTYPE` values + (if passed). [#3097] + * Introduced support for `DEBUG_MIN` setting via `upssched.conf`. [#3097] + * Introduced `upssched -l` mode to list currently tracked timers. [#3097] + * Make use of `setproctag()` and `getproctag()` to report parent/child + process names. [#3084] + * Introduced optional passing of `NOTIFYMSG` text (normally originating + from `upsmon` which calls `upssched`) as an environment variable into + the ultimately executed `CMDSCRIPT` processes. [#3105] + + - The `nut-driver-enumerator.sh` script (NDE) updates: + * Revised info/error/warning/debug message emission so they go to `stderr` + and have a consistent look. Revised some typos along the way. [issue #3194] + + - `configure` script options: + * For ages, most recipes for building NUT had customized the `sysconfdir` to + be `/etc/nut`, which is not exactly the *system* configuration directory. + This is finally deprecated, with new `--with-confdir` configuration option + taking over the role of a full path to configuration files (by default + `${sysconfdir}${confdir_suffix}`), and new `--with-confdir-suffix` allowing + to specify just `/nut` or `/ups` that would be tacked onto the default + `${sysconfdir}` to resolve the `${confdir}`. Default behavior should be + same as with previous builds: if `sysconfdir` is customized (or `prefix` + is kept at built-in default), the `confdir_suffix` will default to empty; + otherwise it assumes the value of `/${PACKAGE_NAME}` to become `/nut` in + most cases. A `--with-confdir-examples` option was also introduced, to + help distributions that place `*.conf.sample` files into docs or other + locations. [#3131] + * Introduced `--with-python{,2,3}-modules-dir` to specify PyNUT(Client) + module installation location (for module-named dir to be created under + it), if not bundling with NUT-Monitor UI app. By default the respective + interpreter's 'site-packages' or 'dist-packages' location will be used, + so you may have to adjust search paths for any other values. [#3062] + * The script would now warn the caller if several different Python program + paths or module locations were discovered (at least one automatically), + in case this was not something the callers wanted -- just so they know. + [#1792] + * New supported values were introduced for `--with-python{,2,3}=auto-prio=NUM`: + this allows to auto-prioritize some *one* available Python interpreter. + Default settings were changed from plain `auto` (allowing for multiple + `python*` interpreters and `site-package` locations to be used), to + prefer a `python3` if available, else `python2`, else `python`. [#1792] + * Streamlined handling of `--with-*` and `--enable-*` options using NUT 'm4' + macros; fixed reporting help for options whose defaults are evaluated. + [issue #3049, PR #3140] + * Automatic `--with-devd` could not detect the need to install a FreeBSD + devd configuration snippet file, because it expected a source that should + not have been there (probably a copy-paste typo in NUT v2.8.0). Also + fixed a missed compiler family dependency when handling `--enable-strip`, + a few logical mismatches of Solaris/illumos packaging options, and default + report vs. setting of `--enable-force-nut-version-header`. Rephrased and + updated many help messages. The `configure` script logs should now clarify + where CFLAGS/LIBS/LDFLAGS values come from ('pkg-config', 'default', user + provided 'confargs' etc.) [#3140] + * Added configure script options for 'libregex' tuning, just so it is on par + with our other optional dependencies. [#3140] + + - Fixed CI recipes for PyPI publication of PyNUT(Client) module to also + include the source distribution (was posted for NUT v2.8.1 and v2.8.2 + tagged releases, but absent for v2.8.3 and v2.8.4). [#3056] + + - The `configure` script would now probe (if it can) the operating systems for + more user and group account names, such as `upsmon`, `nutmon`, `ups`, `nut` + (last hit wins, separately for user and groups accounts) settling on one of + those if detected instead of `nobody` (and optionally `nogroup`). It would + also warn if `nobody` or `nogroup` end up being used for a build. [#3173] + + - Updated `make spellcheck` to help avoid asciidoc admonition blocks with + visually invalid sentences (after rendering as a box in HTML or PDF). [#3077] + + - Updated `docs/*.txt`: add asciidoc comments with links to nut-website + rendered contents of most interesting pages. [#3095] + + - HTML renditions of NUT man pages and books (or chunked chapters thereof) + now include the AnchorJS script to provide permalinks to sections. [#3185] + + - Added a `make dist-docs` goal to generate, collect and tarball all document + types (man, html-single, html-chunked, pdf) that we may have enabled for the + current build configuration and available tooling. [#1400] + + - Further developer workflow speedup with `make spellcheck-interactive-quick` + which should now not re-check source texts that were okay with the previous + dictionary contents, in case some new terms have to be added. [PRs #3186, #2871] + + - Added a GitHub Actions CI job to generate, upload and recycle `make dist` + and `make dist-docs` tarballs so they are easier to obtain for people and + other CI systems (which might not want to follow a Git repository). Links + to these artifacts can be seen in workflow page (or its logs), and added + as a line in GitHub Checks list associated with a commit. Note that the + GitHub Action storage keeps artifacts for at most 90 days. [#1400, #2829] + + - Source directory `data/html` was renamed to `data/htmlcgi` in order to + better reflect its contents and purpose, compared to documentation-oriented + `html*` directories (and recipe variable names). This may impact some + packaging recipes which do not rely on `make install` alone. [#3049] + + - Dropped the `compile` script from Git sources. It originates from automake + and is added to work area (if missing) during `autogen.sh` rituals anyway. + It is still distributed as part of `make dist` tarball. [#1209] + + - Default `PIDPATH` is now more strictly `/var/run`, unless building on a + system conforming to FHS-3.0 standard where that location is absent or + is a symlink, while `/run` exists and is a true directory. [#3099] + + - Revised CI and deliverable scripts, and Makefile recipes, to not use + the verbatim `grep -E` (loudly preferred by newer systems, but may be + absent on older ones) after all, nor use `egrep` (loudly disliked by + newer systems). Instead, use what `configure` script detected for the + generated files (or ones made from templates), and use a similar + detection in standalone scripts. Also revised the use of `grep -q`, + `id -u`, `diff -u`, etc. which are not ubiquitous, and of `test -e` + which is not only absent in some older shells, but can cause them to + abort processing the script immediately. Also the `if ! condition` + syntax is not supported everywhere (or the `!` operator generally). + [#3099, #1660] + + - The NUT Integration Testing suite (NIT) script, if started as `root`, + can now consult its run-time situation vs. `BUILTIN_RUN_AS_USER` and + `BUILTIN_RUN_AS_GROUP` environment variables, and if those accounts + do not exist (e.g. running in a packaging build root), a different + value like "nobody" or "nogroup" would be defaulted for tests. [#1209] + + - Fixed man page naming for `nutdrv_siemens-sitop(.8)` (dash vs. underscore) + to match the driver program name. Follow-up from slightly botched renaming + in original contribution. [PR #545] + + - The `configure` script should now try harder to report specifically + the "purelib" location as `PYTHON*_SITE_PACKAGES`. [#1209] + + - Introduce `make distcheck-completeness` goal to verify that our distribution + archives can exactly reproduce themselves. [#2829] + + - Upstreamed reference packaging recipes (DEB, RPM) from the 42ITy project + which can be used with OBS (Open Build Service by SUSE), both to support + end-user testing of NUT development, and to have a yet another NUT CI farm + player with a distinct opinion on code quality. Many of the changes listed + above happened thanks to this effort, and due to concerns raised by OBS + build agents with various Linux distributions. [#1209, #1316, #3144] + + - Revised use of NUT macros for USB Vendor IDs listed in different drivers, + so the names would be better exposed in generated `udev.rules` and similar + files. [PR #3139] + + Release notes for NUT 2.8.4 - what's new since 2.8.3 ---------------------------------------------------- -https://github.com/networkupstools/nut/milestone/9 - - Bug fixes for fallout possible due to "fightwarn" effort in 2.8.0+: * In `usbhid-ups` sources, introduced optional `HU_FLAG_PARAM_REQUIRED` for `setvar()` or `instcmd()` handling (and a `HU_TYPE_CMD_PARAM_REQUIRED` @@ -232,6 +597,12 @@ Still TODO: * Added APC BVKxxxM2 and BKxxxM2-CH to list of devices where `lbrb_log_delay_sec=N` may be necessary to address spurious LOWBATT and REPLACEBATT events. [PR #2942, PR #3007, issue #2347, issue #3006] + * The `powercom-hid` subdriver did not handle delayed shutdown commands + properly, at least as of RAPTOR RPT-1500AP LCD models. The RPT series + seem to have same USB protocol like Smart KING Pro series, and that + only supports a specific set of possible delays for Shutdown commands + (certain fractions or counts of whole minutes) -- which should now be + handled if `powercom_sdcmd_discrete_delay` flag is set. [issue #3000] - New NUT drivers: * Introduced a `ve-direct` driver for Victron Energy UPS/solar panels @@ -900,7 +1271,7 @@ to avoid magic numbers in code like `set_exit_flag(-2)`, and revised whether it is getting set at all in "killpower" vs. other cases, based on new `handling_upsdrv_shutdown` internal flag. + -NOTE: during this overhaul, many older drivers got their first ever supported +NOTE: During this overhaul, many older drivers got their first ever supported INSTCMD such as `shutdown.return`, `shutdown.stayoff` or `load.off`. Default logic that was previously the content of `upsdrv_shutdown()` methods was often relocated into new `shutdown.default` INSTCMD definitions. [#2670] @@ -1423,7 +1794,7 @@ as part of https://github.com/networkupstools/nut/issues/1410 solution. * extended default ranges for max battery voltage when guessing [#1279] - sms_ser, a driver for SMS Brazil UPS Protocol 1Phase, was introduced. - NOTE: it may later become a subdriver under nutdrv_qx. [#2090] + NOTE: It may later become a subdriver under nutdrv_qx. [#2090] - usbhid-ups updates: * added support for `subdriver` configuration option, to select the diff --git a/UPGRADING.adoc b/UPGRADING.adoc index 1069297631..1059cb1ffe 100644 --- a/UPGRADING.adoc +++ b/UPGRADING.adoc @@ -21,6 +21,84 @@ be beneficial to add `--enable-option-checking=fatal` to the `./configure` command line, in order to quickly pick up any other removed option flags. ====== +Changes from 2.8.4 to 2.8.5 +--------------------------- + +- PLANNED: Keep track of any further API clean-up? + +- For ages, most recipes for building NUT had customized the `sysconfdir` to + be `/etc/nut`, which is not exactly the *system* configuration directory. + This is finally deprecated, with new `--with-confdir` configuration option + taking over the role of a full path to configuration files (by default + `${sysconfdir}${confdir_suffix}`), and new `--with-confdir-suffix` allowing + to specify just `/nut` or `/ups` that would be tacked onto the default + `${sysconfdir}` to resolve the `${confdir}`. Default behavior should be + same as with previous builds: if `sysconfdir` is customized (or `prefix` + is kept at built-in default), the `confdir_suffix` will default to empty; + otherwise it assumes the value of `/${PACKAGE_NAME}` to become `/nut` in + most cases. A `--with-confdir-examples` option was also introduced, to + help distributions that place `*.conf.sample` files into docs or other + locations. [#3131] + +- Source directory `data/html` was renamed to `data/htmlcgi` in order to better + reflect its contents and purpose, compared to documentation-oriented `html*` + directories (and recipe variable names). This may impact some packaging + recipes which do not rely on `make install` alone. [#3049] + +- Some fixes were applied to HTML templates for NUT CGI clients. It can be + useful for NUT deployments being upgraded to compare the files they have + installed (and possibly customized) with the `*.html.sample` files delivered + by the new build. [PR #3180] + +- Dropped the `compile` script from Git sources. It originates from automake + and is added to work area (if missing) during `autogen.sh` rituals anyway + (as `make` says, `'automake --add-missing' can install 'compile'` when you + run without it). It is still distributed as part of `make dist` tarball. + We are open to any feedback whether this removal impacts NUT rebuilds on + any systems. [#1209] + +- Default `PIDPATH` is now more strictly `/var/run`, unless building on a + system conforming to FHS-3.0 standard where that location is absent or + is a symlink, while `/run` exists and is a true directory. Package recipes + usually override `--with-pidpath` anyway to provide a dedicated location + for NUT `root`-owned daemon PID files. [#3099] + +- In drivers using the common `main.c` and `main.h` framework, introduced + a `void upsdrv_tweak_prognames(void)` required method (may be no-op) to + optionally tweak `prognames[]` entries now that there is certain support + to accept program name aliases, not just one hard-coded string value. + Any third-party drivers may require rebuilding to extend with this method. + Any drivers that would undergo promotion with renaming can take advantage + of this new facility to keep working under both old and new file names. + [#3101] + +- Fixed man page naming for `nutdrv_siemens-sitop(.8)` (dash vs. underscore) + to match the driver program name. Packaging recipes may have to be updated. + Follow-up from slightly botched renaming in original contribution. [PR #545] + +- The `configure` script would now probe (if it can) the operating systems for + more user and group account names, such as `upsmon`, `nutmon`, `ups`, `nut` + (last hit wins, separately for user and groups accounts) settling on one of + those if detected instead of `nobody` (and optionally `nogroup`). It would + also warn if `nobody` or `nogroup` end up being used for a build. This is + not likely to affect package builds (which usually define the accounts to + use), but may (hopefully positively) affect rebuilds of NUT on systems where + an older version is already deployed. [#3173] + +- The `configure` script should now try harder to report specifically + the "purelib" location as `PYTHON*_SITE_PACKAGES`. Packaging recipes + may have to be updated. [#1209] + +- New supported values were introduced for `--with-python{,2,3}=auto-prio=NUM`: + this allows to auto-prioritize some *one* available Python interpreter. + Default settings were changed from plain `auto` (allowing for multiple + `python*` interpreters and `site-package` locations to be used), to + prefer a `python3` if available, else `python2`, else `python`, and + forget about others even if discovered. Packaging recipes may have to be + updated, either to explicitly package the binding for several versions, + or to not-deliver files no longer installed into the prototype area. [#1792] + + Changes from 2.8.3 to 2.8.4 --------------------------- diff --git a/autogen.sh b/autogen.sh index 37f72efd73..9a3fcd8aa6 100755 --- a/autogen.sh +++ b/autogen.sh @@ -24,7 +24,10 @@ else DEBUG=false fi -NUT_VERSION_QUERY=UPDATE_FILE "`dirname $0`"/tools/gitlog2version.sh +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; export GREP ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; export EGREP ; } + +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 @@ -84,7 +87,7 @@ then echo "Proceeding without Augeas integration, be sure to not require it in configure script" >&2 touch scripts/augeas/nutupsconf.aug.in scripts/augeas/nutupsconf.aug.in.AUTOGEN_WITHOUT else - echo "Aborting $0! To avoid this, please export WITHOUT_NUT_AUGEAS=true and re-run" >&2 + echo "Aborting $0! To avoid this, please export WITHOUT_NUT_AUGEAS=true and re-run" >&2 echo "or better yet, export PYTHON=python-x.y and re-run" >&2 exit 1 fi @@ -108,8 +111,8 @@ if [ ! -f scripts/udev/nut-usbups.rules.in -o \ ! -f scripts/devd/nut-usb.conf.in -o \ ! -f scripts/devd/nut-usb.quirks -o \ ! -f tools/nut-scanner/nutscan-usb.h ] \ -|| [ -n "`find drivers -newer scripts/hotplug/libhid.usermap | grep -E '(-hid|nutdrv_qx|usb.*)\.c'`" ] \ -|| [ -n "`find drivers -not -newer tools/nut-usbinfo.pl | grep -E '(-hid|nutdrv_qx|usb.*)\.c'`" ] \ +|| [ -n "`find drivers -newer scripts/hotplug/libhid.usermap | ${EGREP} '(-hid|nutdrv_qx|usb.*)\.c'`" ] \ +|| [ -n "`find drivers \! -newer tools/nut-usbinfo.pl | ${EGREP} '(-hid|nutdrv_qx|usb.*)\.c'`" ] \ ; then if perl -e 1; then VERBOSE_FLAG_PERL="" @@ -200,7 +203,7 @@ else fi [ "$AUTOTOOL_RES" = 0 ] && [ -s configure ] && [ -x configure ] \ -|| { cat << EOF +|| { ( cat << EOF ---------------------------------------------------------------------- FAILED: did not generate an executable configure script! @@ -215,8 +218,9 @@ FAILED: did not generate an executable configure script! # "ifdef" block if your autotools still would not grok it. ---------------------------------------------------------------------- EOF + ) >&2 exit 1 -} >&2 +} # Some autoconf versions may leave "/bin/sh" regardless of CONFIG_SHELL # which originally was made for "recheck" operations @@ -242,6 +246,7 @@ else CONFIG_SHELL="`head -1 configure | sed 's,^#!,,'`" fi +echo "autogen.sh: testing generated script syntax with $CONFIG_SHELL" >&2 # NOTE: Unquoted CONFIG_SHELL, may be multi-token $CONFIG_SHELL -n configure 2>/dev/null >/dev/null \ || { diff --git a/ci_build.sh b/ci_build.sh index 7035f05cac..2bde8b09ae 100755 --- a/ci_build.sh +++ b/ci_build.sh @@ -10,12 +10,17 @@ ################################################################################ set -e -SCRIPTDIR="`dirname "$0"`" -SCRIPTDIR="`cd "$SCRIPTDIR" && pwd`" +SCRIPTDIR="`dirname \"$0\"`" +SCRIPTDIR="`cd \"$SCRIPTDIR\" && pwd`" -SCRIPT_PATH="${SCRIPTDIR}/`basename $0`" +SCRIPT_PATH="${SCRIPTDIR}/`basename \"$0\"`" SCRIPT_ARGS=("$@") +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; export GREP ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; export EGREP ; } +# Hoping for a GNU-style grep with -A/-B support here (for aid printouts; failing is not fatal): +[ -n "$GGREP" ] || { GGREP="`command -v ggrep`" && [ x"${GGREP}" != x ] || GGREP="${GREP}" ; } + # Quick hijack for interactive development like this: # BUILD_TYPE=fightwarn-clang ./ci_build.sh # or to quickly hit the first-found errors in a larger matrix @@ -49,7 +54,8 @@ case "$BUILD_TYPE" in else echo "SKIPPING BUILD_TYPE=fightwarn-clang: compiler not found" >&2 fi - if ! $TRIED_BUILD ; then + if $TRIED_BUILD ; then true + else echo "FAILED to run: no default-named compilers were found" >&2 exit 1 fi @@ -176,8 +182,16 @@ case "${CI_BUILDDIR-}" in ;; esac +TOLOWER="cat" +for TR_VARIANT in "tr 'A-Z' 'a-z'" "tr '[:upper:]' '[:lower:]'" "tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'" ; do + if [ x"`echo C | $TR_VARIANT`" = xc ] ; then + TOLOWER="$TR_VARIANT" + break + fi +done + # Just in case we get blanks from CI - consider them as not-set: -if [ -z "`echo "${MAKE-}" | tr -d ' '`" ] ; then +if [ -z "`echo \"${MAKE-}\" | tr -d ' '`" ] ; then if [ "$1" = spellcheck -o "$1" = spellcheck-interactive -o "$1" = spellcheck-quick -o "$1" = spellcheck-interactive-quick ] \ && (command -v gmake) >/dev/null 2>/dev/null \ ; then @@ -190,8 +204,6 @@ if [ -z "`echo "${MAKE-}" | tr -d ' '`" ] ; then export MAKE fi -[ -n "$GGREP" ] || GGREP=grep - [ -n "$MAKE_FLAGS_QUIET" ] || MAKE_FLAGS_QUIET="VERBOSE=0 V=0 -s" [ -n "$MAKE_FLAGS_VERBOSE" ] || MAKE_FLAGS_VERBOSE="VERBOSE=1 V=1 -s" [ -n "$MAKE_FLAGS_CLEAN" ] || MAKE_FLAGS_CLEAN="${MAKE_FLAGS_QUIET}" @@ -206,7 +218,7 @@ normalize_path() { case "${D}" in "") continue ;; /) ;; - */) D="`echo "${D}" | sed 's,/*$,,'`" ;; + */) D="`echo \"${D}\" | sed 's,/*$,,'`" ;; esac case "${P}" in "${D}"|*":${D}"|"${D}:"*|*":${D}:"*) ;; @@ -245,8 +257,8 @@ ensure_CI_CCACHE_SYMLINKDIR_envvar() { 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 '{}' \;`" ) \ + if ( ls -la "$D" | ${GREP} ' -> .*ccache' >/dev/null) \ + || ( cd "$D" && test -n "`find . -type f | sed 's,^\./,,' | ${GREP} -v / | while read F ; do ${GREP} -li ccache $F ; done`" ) \ ; then CI_CCACHE_SYMLINKDIR="$D" && break else @@ -273,7 +285,7 @@ optional_prepare_ccache() { # See also optional_ensure_ccache(), optional_prepare_compiler_family(), # ensure_CI_CCACHE_SYMLINKDIR_envvar() echo "PATH='$PATH' before possibly applying CCACHE into the mix" - ( echo "$PATH" | grep ccache ) >/dev/null && echo "WARNING: ccache is already in PATH" + ( echo "$PATH" | ${GREP} ccache ) >/dev/null && echo "WARNING: ccache is already in PATH" if [ -n "$CC" ]; then echo "CC='$CC' before possibly applying CCACHE into the mix" $CC --version $CFLAGS || \ @@ -293,7 +305,7 @@ optional_prepare_ccache() { else if [ -n "${CI_CCACHE_SYMLINKDIR}" ]; then # Tell ccache the PATH without itself in it, to avoid loops processing - PATH="`echo "$PATH" | sed -e 's,^'"${CI_CCACHE_SYMLINKDIR}"'/?:,,' -e 's,:'"${CI_CCACHE_SYMLINKDIR}"'/?:,,' -e 's,:'"${CI_CCACHE_SYMLINKDIR}"'/?$,,' -e 's,^'"${CI_CCACHE_SYMLINKDIR}"'/?$,,'`" + PATH="`echo \"$PATH\" | sed -e 's,^'\"${CI_CCACHE_SYMLINKDIR}\"'/?:,,' -e 's,:'\"${CI_CCACHE_SYMLINKDIR}\"'/?:,,' -e 's,:'\"${CI_CCACHE_SYMLINKDIR}\"'/?$,,' -e 's,^'\"${CI_CCACHE_SYMLINKDIR}\"'/?$,,'`" fi CCACHE_PATH="$PATH" CCACHE_DIR="${HOME}/.ccache" @@ -309,25 +321,25 @@ optional_prepare_ccache() { } is_gnucc() { - if [ -n "$1" ] && LANG=C "$1" --version 2>&1 | grep 'Free Software Foundation' > /dev/null ; then true ; else false ; fi + if [ -n "$1" ] && LANG=C "$1" --version 2>&1 | ${GREP} 'Free Software Foundation' > /dev/null ; then true ; else false ; fi } is_clang() { - if [ -n "$1" ] && LANG=C "$1" --version 2>&1 | grep 'clang version' > /dev/null ; then true ; else false ; fi + if [ -n "$1" ] && LANG=C "$1" --version 2>&1 | ${GREP} 'clang version' > /dev/null ; then true ; else false ; fi } filter_version() { # Starting with number like "6.0.0" or "7.5.0-il-0" is fair game, # but a "gcc-4.4.4-il-4" (starting with "gcc") is not - sed -e 's,^.* \([0-9][0-9]*\.[0-9][^ ),]*\).*$,\1,' -e 's, .*$,,' | grep -E '^[0-9]' | head -1 + sed -e 's,^.* \([0-9][0-9]*\.[0-9][^ ),]*\).*$,\1,' -e 's, .*$,,' | ${EGREP} '^[0-9]' | head -1 } ver_gnucc() { - [ -n "$1" ] && LANG=C "$1" --version 2>&1 | grep -i gcc | filter_version + [ -n "$1" ] && LANG=C "$1" --version 2>&1 | ${GREP} -i gcc | filter_version } ver_clang() { - [ -n "$1" ] && LANG=C "$1" --version 2>&1 | grep -i 'clang' | filter_version + [ -n "$1" ] && LANG=C "$1" --version 2>&1 | ${GREP} -i 'clang' | filter_version } optional_prepare_compiler_family() { @@ -361,13 +373,13 @@ optional_prepare_compiler_family() { fi if ( [ "$COMPILER_FAMILY" = "GCC" ] && \ - case "`ver_gnucc "$CC"`" in + case "`ver_gnucc \"$CC\"`" in [123].*) true ;; 4.[0123][.,-]*) true ;; 4.[0123]) true ;; *) false ;; esac && \ - case "`ver_gnucc "$CXX"`" in + case "`ver_gnucc \"$CXX\"`" in [123].*) true ;; 4.[0123][.,-]*) true ;; 4.[0123]) true ;; @@ -417,6 +429,13 @@ optional_prepare_compiler_family() { optional_ensure_ccache() { # Prepare PATH, CC, CXX envvars to use ccache (if enabled, applicable and available) # See also optional_prepare_ccache() + if [ x"${CI_CCACHE_USE-}" = xno ]; then + HAVE_CCACHE=no + CI_CCACHE_SYMLINKDIR="" + echo "WARNING: Caller required to not use ccache even if available" >&2 + return + fi + if [ "$HAVE_CCACHE" = yes ] && [ "${COMPILER_FAMILY}" = GCC -o "${COMPILER_FAMILY}" = CLANG ]; then if [ -n "${CI_CCACHE_SYMLINKDIR}" ]; then echo "INFO: Using ccache via PATH preferring tool names in ${CI_CCACHE_SYMLINKDIR}" >&2 @@ -439,9 +458,9 @@ optional_ensure_ccache() { if [ -x "${CI_CCACHE_SYMLINKDIR}/`basename "$CC"`" ]; then case "$CC" in *ccache*) ;; - */*) DIR_CC="`dirname "$CC"`" && [ -n "$DIR_CC" ] && DIR_CC="`cd "$DIR_CC" && pwd `" && [ -n "$DIR_CC" ] && [ -d "$DIR_CC" ] || DIR_CC="" + */*) DIR_CC="`dirname \"$CC\"`" && [ -n "$DIR_CC" ] && DIR_CC="`cd \"$DIR_CC\" && pwd `" && [ -n "$DIR_CC" ] && [ -d "$DIR_CC" ] || DIR_CC="" [ -z "$CCACHE_PATH" ] && CCACHE_PATH="$DIR_CC" || \ - if echo "$CCACHE_PATH" | grep -E '(^'"$DIR_CC"':.*|^'"$DIR_CC"'$|:'"$DIR_CC"':|:'"$DIR_CC"'$)' ; then + if echo "$CCACHE_PATH" | ${EGREP} '(^'"$DIR_CC"':.*|^'"$DIR_CC"'$|:'"$DIR_CC"':|:'"$DIR_CC"'$)' ; then CCACHE_PATH="$DIR_CC:$CCACHE_PATH" fi ;; @@ -455,9 +474,9 @@ optional_ensure_ccache() { if [ -x "${CI_CCACHE_SYMLINKDIR}/`basename "$CXX"`" ]; then case "$CXX" in *ccache*) ;; - */*) DIR_CXX="`dirname "$CXX"`" && [ -n "$DIR_CXX" ] && DIR_CXX="`cd "$DIR_CXX" && pwd `" && [ -n "$DIR_CXX" ] && [ -d "$DIR_CXX" ] || DIR_CXX="" + */*) DIR_CXX="`dirname \"$CXX\"`" && [ -n "$DIR_CXX" ] && DIR_CXX="`cd \"$DIR_CXX\" && pwd `" && [ -n "$DIR_CXX" ] && [ -d "$DIR_CXX" ] || DIR_CXX="" [ -z "$CCACHE_PATH" ] && CCACHE_PATH="$DIR_CXX" || \ - if echo "$CCACHE_PATH" | grep -E '(^'"$DIR_CXX"':.*|^'"$DIR_CXX"'$|:'"$DIR_CXX"':|:'"$DIR_CXX"'$)' ; then + if echo "$CCACHE_PATH" | ${EGREP} '(^'"$DIR_CXX"':.*|^'"$DIR_CXX"'$|:'"$DIR_CXX"':|:'"$DIR_CXX"'$)' ; then CCACHE_PATH="$DIR_CXX:$CCACHE_PATH" fi ;; @@ -471,9 +490,9 @@ optional_ensure_ccache() { && [ -x "${CI_CCACHE_SYMLINKDIR}/`basename "$CPP"`" ]; then case "$CPP" in *ccache*) ;; - */*) DIR_CPP="`dirname "$CPP"`" && [ -n "$DIR_CPP" ] && DIR_CPP="`cd "$DIR_CPP" && pwd `" && [ -n "$DIR_CPP" ] && [ -d "$DIR_CPP" ] || DIR_CPP="" + */*) DIR_CPP="`dirname \"$CPP\"`" && [ -n "$DIR_CPP" ] && DIR_CPP="`cd \"$DIR_CPP\" && pwd `" && [ -n "$DIR_CPP" ] && [ -d "$DIR_CPP" ] || DIR_CPP="" [ -z "$CCACHE_PATH" ] && CCACHE_PATH="$DIR_CPP" || \ - if echo "$CCACHE_PATH" | grep -E '(^'"$DIR_CPP"':.*|^'"$DIR_CPP"'$|:'"$DIR_CPP"':|:'"$DIR_CPP"'$)' ; then + if echo "$CCACHE_PATH" | ${EGREP} '(^'"$DIR_CPP"':.*|^'"$DIR_CPP"'$|:'"$DIR_CPP"':|:'"$DIR_CPP"'$)' ; then CCACHE_PATH="$DIR_CPP:$CCACHE_PATH" fi ;; @@ -530,12 +549,13 @@ esac [ -n "$NCPUS" ] || { \ NCPUS="`/usr/bin/getconf _NPROCESSORS_ONLN`" || \ NCPUS="`/usr/bin/getconf NPROCESSORS_ONLN`" || \ - NCPUS="`cat /proc/cpuinfo | grep -wc processor`" || \ + NCPUS="`cat /proc/cpuinfo | ${GREP} -wc processor`" || \ { [ -x /usr/sbin/psrinfo ] && NCPUS="`/usr/sbin/psrinfo | wc -l`"; } \ || NCPUS=1; } 2>/dev/null +[ x"$NCPUS" = x ] || NCPUS="`echo \"$NCPUS\" | tr -d ' '`" [ x"$NCPUS" != x -a "$NCPUS" -ge 1 ] || NCPUS=1 -[ x"$NPARMAKES" = x ] && { NPARMAKES="`expr "$NCPUS" '*' 2`" || NPARMAKES=2; } +[ x"$NPARMAKES" = x ] && { NPARMAKES="`expr \"$NCPUS\" '*' 2`" || NPARMAKES=2; } [ x"$NPARMAKES" != x -a "$NPARMAKES" -ge 1 ] || NPARMAKES=2 [ x"$MAXPARMAKES" != x ] && [ "$MAXPARMAKES" -ge 1 ] && \ [ "$NPARMAKES" -gt "$MAXPARMAKES" ] && \ @@ -554,7 +574,7 @@ esac # for actual builds with parallel phases. Specify a whitespace to neuter. if [ -z "$PARMAKE_FLAGS" ]; then PARMAKE_FLAGS="-j $NPARMAKES" - if LANG=C LC_ALL=C "$MAKE" --version 2>&1 | grep -E 'GNU Make|Free Software Foundation' > /dev/null ; then + if LANG=C LC_ALL=C "$MAKE" --version 2>&1 | ${EGREP} 'GNU Make|Free Software Foundation' > /dev/null ; then PARMAKE_FLAGS="$PARMAKE_FLAGS -l $PARMAKE_LA_LIMIT" echo "Parallel builds would spawn up to $NPARMAKES jobs (detected $NCPUS CPUs), or peak out at $PARMAKE_LA_LIMIT system load average" >&2 else @@ -678,7 +698,7 @@ if [ -z "$CI_OS_NAME" ]; then # classification styled after (compatible with) that in Travis CI for CI_OS_HINT in \ "$OS_FAMILY-$OS_DISTRO" \ - "`grep = /etc/os-release 2>/dev/null`" \ + "`${GREP} = /etc/os-release 2>/dev/null`" \ "`cat /etc/release 2>/dev/null`" \ "`uname -o 2>/dev/null`" \ "`uname -s -r -v 2>/dev/null`" \ @@ -688,7 +708,7 @@ if [ -z "$CI_OS_NAME" ]; then [ -z "$CI_OS_HINT" -o "$CI_OS_HINT" = "-" ] || break done - case "`echo "$CI_OS_HINT" | tr 'A-Z' 'a-z'`" in + case "`echo \"$CI_OS_HINT\" | $TOLOWER`" in *freebsd*) CI_OS_NAME="freebsd" ;; *openbsd*) @@ -851,13 +871,16 @@ detect_platform_PKG_CONFIG_PATH_and_FLAGS() { # Caller can override by OVERRIDE_PKG_CONFIG_PATH (ignore other values # then, including a PKG_CONFIG_PATH), where a "-" value leaves it empty. SYS_PKG_CONFIG_PATH="" # Let the OS guess... usually - BUILTIN_PKG_CONFIG_PATH="`pkg-config --variable pc_path pkg-config`" || BUILTIN_PKG_CONFIG_PATH="" - case "`echo "$CI_OS_NAME" | tr 'A-Z' 'a-z'`" in + BUILTIN_PKG_CONFIG_PATH="`$PKG_CONFIG --variable pc_path pkg-config`" || BUILTIN_PKG_CONFIG_PATH="" + case "`echo \"$CI_OS_NAME\" | $TOLOWER`" in *openindiana*|*omnios*|*solaris*|*illumos*|*sunos*) - _ARCHES="${ARCH-}" + _ARCHES="${ARCH_TGT-}${ARCH-}${ARCH32-}${ARCH64-}" _BITS="${BITS-}" _ISA1="" + [ -n "${_BITS}" ] || \ + _BITS="${ARCH_BITS-}" + [ -n "${_BITS}" ] || \ case "${CC}${CXX}${CFLAGS}${CXXFLAGS}${LDFLAGS}" in *-m64*) _BITS=64 ;; @@ -988,7 +1011,7 @@ detect_platform_PKG_CONFIG_PATH_and_FLAGS() { # 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 + if [ -d "$checkFSobj" ] && [ ! -f "${HOMEBREW_PREFIX}/lib/pkgconfig/netsnmp.pc" ] && [ ! -h "${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" @@ -1018,7 +1041,7 @@ detect_platform_PKG_CONFIG_PATH_and_FLAGS() { if [ -z "${XML_CATALOG_FILES-}" ] ; then checkFSobj="${HOMEBREW_PREFIX}/etc/xml/catalog" - if [ -e "$checkFSobj" ] ; then + if ( [ -e "$checkFSobj" ] ) 2>/dev/null || [ -d "$checkFSobj" ] || [ -f "$checkFSobj" ] || [ -h "$checkFSobj" ] ; then echo "Homebrew: export XML_CATALOG_FILES='$checkFSobj' for asciidoc et al" XML_CATALOG_FILES="$checkFSobj" export XML_CATALOG_FILES @@ -1068,7 +1091,7 @@ detect_platform_PKG_CONFIG_PATH_and_FLAGS() { fi # Do not check for existence of non-trivial values, we normalize the mess (if any) - PKG_CONFIG_PATH="`echo "${DEFAULT_PKG_CONFIG_PATH-}:${SYS_PKG_CONFIG_PATH-}:${PKG_CONFIG_PATH-}:${BUILTIN_PKG_CONFIG_PATH-}" | normalize_path`" + PKG_CONFIG_PATH="`echo \"${DEFAULT_PKG_CONFIG_PATH-}:${SYS_PKG_CONFIG_PATH-}:${PKG_CONFIG_PATH-}:${BUILTIN_PKG_CONFIG_PATH-}\" | normalize_path`" } # Would hold full path to the CONFIGURE_SCRIPT="${SCRIPTDIR}/${CONFIGURE_SCRIPT_FILENAME}" @@ -1131,7 +1154,7 @@ configure_nut() { fi # Help copy-pasting build setups from CI logs to terminal: - local CONFIG_OPTS_STR="`for F in "${CONFIG_OPTS[@]}" ; do echo "'$F' " ; done`" ### | tr '\n' ' '`" + local CONFIG_OPTS_STR="`END=' \'; NUM=0; for F in \"${CONFIG_OPTS[@]}\" ; do NUM=$(($NUM + 1)); [ x\"$NUM\" = x\"${#CONFIG_OPTS[@]}\" ] && END=''; printf \"'%s'%s\n\" \"$F\" \"$END\" ; done`" while : ; do # Note the CI_SHELL_IS_FLAKY=true support below echo "=== CONFIGURING NUT: $CONFIGURE_SCRIPT ${CONFIG_OPTS_STR}" echo "=== CC='$CC' CXX='$CXX' CPP='$CPP'" @@ -1209,7 +1232,7 @@ build_to_only_catch_errors_check() { # Lots of tedious touch-files to make, better run it in parallel separately. # May report absence of "aspell" but would not fail in that case (just noise). - if grep "WITH_SPELLCHECK_TRUE=''" config.log >/dev/null 2>/dev/null ; then + if ${GREP} "WITH_SPELLCHECK_TRUE=''" config.log >/dev/null 2>/dev/null ; then echo "`date`: Starting a '$MAKE spellcheck-quick' first" $CI_TIME $MAKE $MAKE_FLAGS_QUIET spellcheck-quick \ && echo "`date`: SUCCESS" \ @@ -1275,7 +1298,9 @@ check_gitignore() { [ -n "${BUILT_TARGETS-}" ] || BUILT_TARGETS="all? (usual default)" echo "=== Are GitIgnores good after '$MAKE $BUILT_TARGETS'? (should have no output below)" - if [ ! -e .git ]; then + if ( [ -e .git ] ) 2>/dev/null || [ -d .git ] || [ -f .git ] || [ -h .git ] ; then + true + else echo "WARNING: Skipping the GitIgnores check after '$BUILT_TARGETS' because there is no `pwd`/.git anymore" >&2 return 0 fi @@ -1287,14 +1312,14 @@ check_gitignore() { if [ -n "${GITOUT-}" ] ; then echo "$GITOUT" \ - | grep -E "${FILE_REGEX}" + | ${EGREP} "${FILE_REGEX}" else echo "Got no output and no errors querying git repo while in `pwd`: seems clean" >&2 fi echo "===" # Another invocation checks that there was nothing to complain about: - if [ -n "`git status $GIT_ARGS -s ${FILE_GLOB} ${FILE_GLOB_EXCLUDE} | grep -E "^.. ${FILE_REGEX}"`" ] \ + if [ -n "`git status $GIT_ARGS -s ${FILE_GLOB} ${FILE_GLOB_EXCLUDE} | ${EGREP} \"^.. ${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 @@ -1319,23 +1344,31 @@ consider_cleanup_shortcut() { 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 + if ( [ -s Makefile ] && ( + [ -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`" ] ) ) \ + || ( [ -s configure ] && ( + [ -n "`find \"${SCRIPTDIR}\" -name configure.ac -newer \"${CI_BUILDDIR}\"/configure`" ] \ + || [ -n "`find \"${SCRIPTDIR}\" -name '*.m4' -newer \"${CI_BUILDDIR}\"/configure`" ] ) ) \ + ; 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 # When iterating configure.ac or m4 sources, we can end up with an # existing but useless script file - nuke it and restart from scratch! if [ -s "${CI_BUILDDIR}"/configure ] ; then - if ! sh -n "${CI_BUILDDIR}"/configure 2>/dev/null ; then + # FIXME: Consider CONFIG_SHELL, maybe from script shebang, + # here - like autogen.sh does + USE_CONFIG_SHELL=sh + if [ -n "${CONFIG_SHELL-}" ]; then + USE_CONFIG_SHELL="${CONFIG_SHELL}" + fi + if ${USE_CONFIG_SHELL} -n "${CI_BUILDDIR}"/configure 2>/dev/null ; then + true + else echo "=== Starting initial clean-up (from old build products): TAKING SHORTCUT because current configure script syntax is broken" DO_REGENERATE=true fi @@ -1351,19 +1384,24 @@ can_clean_check() { # NOTE: Not handling here particular DO_MAINTAINER_CLEAN_CHECK or DO_DIST_CLEAN_CHECK return 1 fi - if [ -s Makefile ] && [ -e .git ] ; then + + if [ -s Makefile ] && ( ( [ -e .git ] ) 2>/dev/null || [ -d .git ] || [ -f .git ] || [ -h .git ] ) ; then return 0 fi return 1 } optional_maintainer_clean_check() { - if [ ! -e .git ]; then + if ( [ -e .git ] ) 2>/dev/null || [ -d .git ] || [ -f .git ] || [ -h .git ] ; then + true + else echo "Skipping maintainer-clean check because there is no .git" >&2 return 0 fi - if [ ! -e Makefile ]; then + if ( [ -e Makefile ] ) 2>/dev/null || [ -f Makefile ] || [ -h Makefile ] ; then + true + else echo "WARNING: Skipping maintainer-clean check because there is no Makefile (did we clean in a loop earlier?)" >&2 return 0 fi @@ -1389,12 +1427,16 @@ optional_maintainer_clean_check() { } optional_dist_clean_check() { - if [ ! -e .git ]; then + if ( [ -e .git ] ) 2>/dev/null || [ -d .git ] || [ -f .git ] || [ -h .git ] ; then + true + else echo "Skipping distclean check because there is no .git" >&2 return 0 fi - if [ ! -e Makefile ]; then + if ( [ -e Makefile ] ) 2>/dev/null || [ -f Makefile ] || [ -h Makefile ] ; then + true + else echo "WARNING: Skipping distclean check because there is no Makefile (did we clean in a loop earlier?)" >&2 return 0 fi @@ -1431,7 +1473,7 @@ if [ -z "$BUILD_TYPE" ] ; then --with-docs|--with-docs=*|--with-doc|--with-doc=*) # Note: causes a developer-style build (not CI) # Arg will be passed to configure script as `--with-$1` - BUILD_TYPE="`echo "$1" | sed 's,^--with-,,'`" + BUILD_TYPE="`echo \"$1\" | sed 's,^--with-,,'`" shift ;; @@ -1441,6 +1483,8 @@ if [ -z "$BUILD_TYPE" ] ; then win|windows|cross-windows-mingw) BUILD_TYPE="cross-windows-mingw" ; shift ;; + pkg*) BUILD_TYPE="$1" ; shift ;; + spellcheck|spellcheck-interactive|spellcheck-quick|spellcheck-interactive-quick) # Note: this is a little hack to reduce typing # and scrolling in (docs) developer iterations. @@ -1453,7 +1497,9 @@ if [ -z "$BUILD_TYPE" ] ; then echo "==========================================================================" sleep 5 ;; - *) if ! (command -v aspell) 2>/dev/null >/dev/null ; then + *) if (command -v aspell) 2>/dev/null >/dev/null ; then + true + else echo "==========================================================================" echo "WARNING: Seems you do not have 'aspell' in PATH (but maybe NUT configure" echo "script would find the spellchecking toolkit elsewhere)" @@ -1476,7 +1522,7 @@ if [ -z "$BUILD_TYPE" ] ; then fi ;; - *) echo "WARNING: Command-line argument '$1' wsa not recognized as a BUILD_TYPE alias" >&2 ;; + *) echo "WARNING: Command-line argument '$1' was not recognized as a BUILD_TYPE alias" >&2 ;; esac fi @@ -1487,7 +1533,9 @@ echo "Processing BUILD_TYPE='${BUILD_TYPE}' ..." ensure_CI_CCACHE_SYMLINKDIR_envvar echo "Build host settings:" -set | grep -E '^(PATH|[^ ]*CCACHE[^ ]*|CI_[^ ]*|OS_[^ ]*|CANBUILD_[^ ]*|NODE_LABELS|MAKE|C[^ ]*FLAGS|LDFLAGS|ARCH[^ ]*|BITS[^ ]*|CC|CXX|CPP|DO_[^ ]*|BUILD_[^ ]*|[^ ]*_TGT|INPLACE_RUNTIME)=' || true +set | ${EGREP} '^(PATH|[^ ]*CCACHE[^ ]*|CI_[^ ]*|OS_[^ ]*|CANBUILD_[^ ]*|NODE_LABELS|MAKE|C[^ ]*FLAGS|LDFLAGS|ARCH[^ ]*|BITS[^ ]*|CC|CXX|CPP|DO_[^ ]*|BUILD_[^ ]*|[^ ]*_TGT|INPLACE_RUNTIME)=' | sed 's,\(.\)$,\1 \\,' || true +# Padding for trailing backslashed line +echo "PARMAKE_FLAGS='${PARMAKE_FLAGS}'" uname -a echo "LONG_BIT:`getconf LONG_BIT` WORD_BIT:`getconf WORD_BIT`" || true if command -v xxd >/dev/null ; then xxd -c 1 -l 6 | tail -1; else if command -v od >/dev/null; then od -N 1 -j 5 -b | head -1 ; else hexdump -s 5 -n 1 -C | head -1; fi; fi < /bin/ls 2>/dev/null | awk '($2 == 1){print "Endianness: LE"}; ($2 == 2){print "Endianness: BE"}' || true @@ -1533,8 +1581,8 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al CONFIG_OPTS+=("PKG_CONFIG_PATH=${PKG_CONFIG_PATH}") fi - PATH="`echo "${PATH}" | normalize_path`" - CCACHE_PATH="`echo "${CCACHE_PATH}" | normalize_path`" + PATH="`echo \"${PATH}\" | normalize_path`" + CCACHE_PATH="`echo \"${CCACHE_PATH}\" | normalize_path`" # Note: Potentially there can be spaces in entries for multiple # *FLAGS here; this should be okay as long as entry expands to @@ -1546,7 +1594,8 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al CONFIG_OPTS+=("--enable-keep_nut_report_feature") CONFIG_OPTS+=("--prefix=${BUILD_PREFIX}") - CONFIG_OPTS+=("--sysconfdir=${BUILD_PREFIX}/etc/nut") + #CONFIG_OPTS+=("--sysconfdir=${BUILD_PREFIX}/etc/nut") + CONFIG_OPTS+=("--with-confdir=${BUILD_PREFIX}/etc/nut") CONFIG_OPTS+=("--with-udev-dir=${BUILD_PREFIX}/etc/udev") CONFIG_OPTS+=("--with-devd-dir=${BUILD_PREFIX}/etc/devd") CONFIG_OPTS+=("--with-hotplug-dir=${BUILD_PREFIX}/etc/hotplug") @@ -1910,7 +1959,7 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al "default-tgt:"*) # Hook for matrix of custom distchecks primarily # e.g. distcheck-ci, distcheck-light, distcheck-valgrind, cppcheck, # maybe others later, as defined in top-level Makefile.am: - BUILD_TGT="`echo "$BUILD_TYPE" | sed 's,^default-tgt:,,'`" + BUILD_TGT="`echo \"$BUILD_TYPE\" | sed 's,^default-tgt:,,'`" if [ -n "${PARMAKE_FLAGS}" ]; then echo "`date`: Starting the parallel build attempt for singular target $BUILD_TGT..." else @@ -1919,7 +1968,7 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al # Note: Makefile.am already sets some default DISTCHECK_CONFIGURE_FLAGS # that include DISTCHECK_FLAGS if provided - DISTCHECK_FLAGS="`for F in "${CONFIG_OPTS[@]}" ; do echo "'$F' " ; done | tr '\n' ' '`" + DISTCHECK_FLAGS="`for F in \"${CONFIG_OPTS[@]}\" ; do echo \"'$F' \" ; done | tr '\n' ' '`" export DISTCHECK_FLAGS # Tell the sub-makes (likely distcheck*) to hush down @@ -1947,7 +1996,7 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al # Note: no PARMAKE_FLAGS here - better have this output readably # ordered in case of issues (in sequential replay below). ( echo "`date`: Starting the quiet build attempt for target $BUILD_TYPE..." >&2 - $CI_TIME $MAKE $MAKE_FLAGS_QUIET SPELLCHECK_ERROR_FATAL=yes -k $PARMAKE_FLAGS "`echo "$BUILD_TYPE" | sed 's,^default-,,'`" >/dev/null 2>&1 \ + $CI_TIME $MAKE $MAKE_FLAGS_QUIET SPELLCHECK_ERROR_FATAL=yes -k $PARMAKE_FLAGS "`echo \"$BUILD_TYPE\" | sed 's,^default-,,'`" >/dev/null 2>&1 \ && echo "`date`: SUCCEEDED the spellcheck" >&2 ) || \ ( echo "`date`: FAILED something in spellcheck above; re-starting a verbose build attempt to give more context first:" >&2 @@ -2526,9 +2575,9 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al ( set -o pipefail; check_gitignore "all" | egrep -v '^.. .*\.dmf$' ) || exit if test -s "${SCRIPTDIR}/install-sh" \ - && grep -w MKDIRPROG "${SCRIPTDIR}/install-sh" >/dev/null \ + && ${GREP} -w MKDIRPROG "${SCRIPTDIR}/install-sh" >/dev/null \ ; then - if grep -v '#' "${SCRIPTDIR}/install-sh" | grep -E '\$mkdirprog.*-p' >/dev/null \ + if ${GREP} -v '#' "${SCRIPTDIR}/install-sh" | ${EGREP} '\$mkdirprog.*-p' >/dev/null \ ; then true else @@ -2552,7 +2601,7 @@ default|default-alldrv|default-alldrv:no-distcheck|default-all-errors|default-al ( # Note: Makefile.am already sets some default DISTCHECK_CONFIGURE_FLAGS # that include DISTCHECK_FLAGS if provided - DISTCHECK_FLAGS="`for F in "${CONFIG_OPTS[@]}" ; do echo "'$F' " ; done | tr '\n' ' '`" + DISTCHECK_FLAGS="`for F in \"${CONFIG_OPTS[@]}\" ; do echo \"'$F' \" ; done | tr '\n' ' '`" export DISTCHECK_FLAGS # Tell the sub-makes (distcheck) to hush down @@ -2586,8 +2635,8 @@ bindings) # NOTE: Alternative to optional_prepare_ccache() # FIXME: Can these be united and de-duplicated? - if [ -n "${CI_CCACHE_SYMLINKDIR}" ] && [ -d "${CI_CCACHE_SYMLINKDIR}" ] ; then - PATH="`echo "$PATH" | sed -e 's,^'"${CI_CCACHE_SYMLINKDIR}"'/?:,,' -e 's,:'"${CI_CCACHE_SYMLINKDIR}"'/?:,,' -e 's,:'"${CI_CCACHE_SYMLINKDIR}"'/?$,,' -e 's,^'"${CI_CCACHE_SYMLINKDIR}"'/?$,,'`" + if [ x"${CI_CCACHE_USE-}" != xno ] && [ -n "${CI_CCACHE_SYMLINKDIR}" ] && [ -d "${CI_CCACHE_SYMLINKDIR}" ] ; then + PATH="`echo \"$PATH\" | sed -e 's,^'\"${CI_CCACHE_SYMLINKDIR}\"'/?:,,' -e 's,:'\"${CI_CCACHE_SYMLINKDIR}\"'/?:,,' -e 's,:'\"${CI_CCACHE_SYMLINKDIR}\"'/?$,,' -e 's,^'\"${CI_CCACHE_SYMLINKDIR}\"'/?$,,'`" CCACHE_PATH="$PATH" CCACHE_DIR="${HOME}/.ccache" if (command -v ccache || which ccache) && ls -la "${CI_CCACHE_SYMLINKDIR}" && mkdir -p "${CCACHE_DIR}"/ ; then @@ -2727,20 +2776,20 @@ bindings) esac if [ "${_EXPORT_FLAGS}" = true ] ; then - [ -z "${CFLAGS}" ] || export CFLAGS - [ -z "${CXXFLAGS}" ] || export CXXFLAGS - [ -z "${CPPFLAGS}" ] || export CPPFLAGS - [ -z "${LDFLAGS}" ] || export LDFLAGS + [ -z "${CFLAGS}" ] || export CFLAGS + [ -z "${CXXFLAGS}" ] || export CXXFLAGS + [ -z "${CPPFLAGS}" ] || export CPPFLAGS + [ -z "${LDFLAGS}" ] || export LDFLAGS else - # NOTE: Passing via CONFIG_OPTS also fails - [ -z "${CFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export CFLAGS='${CFLAGS}'" >&2 - [ -z "${CXXFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export CXXFLAGS='${CXXFLAGS}'" >&2 - [ -z "${CPPFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export CPPFLAGS='${CPPFLAGS}'" >&2 - [ -z "${LDFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export LDFLAGS='${LDFLAGS}'" >&2 + # NOTE: Passing via CONFIG_OPTS also fails + [ -z "${CFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export CFLAGS='${CFLAGS}'" >&2 + [ -z "${CXXFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export CXXFLAGS='${CXXFLAGS}'" >&2 + [ -z "${CPPFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export CPPFLAGS='${CPPFLAGS}'" >&2 + [ -z "${LDFLAGS}" ] || echo "WARNING: SKIP: On '${CI_OS_NAME}' with ccache used, can not export LDFLAGS='${LDFLAGS}'" >&2 fi - PATH="`echo "${PATH}" | normalize_path`" - CCACHE_PATH="`echo "${CCACHE_PATH}" | normalize_path`" + PATH="`echo \"${PATH}\" | normalize_path`" + CCACHE_PATH="`echo \"${CCACHE_PATH}\" | normalize_path`" RES_CFG=0 ${CONFIGURE_SCRIPT} "${CONFIG_OPTS[@]}" \ @@ -2789,10 +2838,11 @@ cross-windows-mingw*) echo "INFO: When using build-mingw-nut.sh consider 'export INSTALL_WIN_BUNDLE=true' to use mainstream DLL co-bundling recipe" >&2 if [ "$HAVE_CCACHE" = yes ] \ + && [ x"${CI_CCACHE_USE-}" != xno ] \ && [ -n "${CI_CCACHE_SYMLINKDIR}" ] \ && [ -d "${CI_CCACHE_SYMLINKDIR}" ] \ ; then - PATH="`echo "$PATH" | sed -e 's,^'"${CI_CCACHE_SYMLINKDIR}"'/?:,,' -e 's,:'"${CI_CCACHE_SYMLINKDIR}"'/?:,,' -e 's,:'"${CI_CCACHE_SYMLINKDIR}"'/?$,,' -e 's,^'"${CI_CCACHE_SYMLINKDIR}"'/?$,,'`" + PATH="`echo \"$PATH\" | sed -e 's,^'\"${CI_CCACHE_SYMLINKDIR}\"'/?:,,' -e 's,:'\"${CI_CCACHE_SYMLINKDIR}\"'/?:,,' -e 's,:'\"${CI_CCACHE_SYMLINKDIR}\"'/?$,,' -e 's,^'\"${CI_CCACHE_SYMLINKDIR}\"'/?$,,'`" CCACHE_PATH="$PATH" CCACHE_DIR="${HOME}/.ccache" if (command -v ccache || which ccache) && ls -la "${CI_CCACHE_SYMLINKDIR}" && mkdir -p "${CCACHE_DIR}"/ ; then @@ -2833,6 +2883,42 @@ cross-windows-mingw*) ./build-mingw-nut.sh $cmd ;; +pkg-rpm|pkg-spec) + echo "WARNING: package build recipes manipulate directly your current workspace" >&2 + echo " and would remove any git-ignored files, might leave trash afterwards!" >&2 + echo " Press Ctrl+C to abort if this is a problem" >&2 + sleep 5 + + rm -f nut.spec nut.changes || true + git clean -fdX || true + cp -f scripts/obs/nut.spec scripts/obs/nut.changes . + sed -e 's,^(Version:).*$,\1 '"`NUT_VERSION_QUERY=VER50 ./tools/gitlog2version.sh`," \ + -i nut.spec + rpmbuild -ba nut.spec \ + && find . -name '*rpm' + ;; + +pkg-deb|pkg-dsc) + echo "WARNING: package build recipes manipulate directly your current workspace" >&2 + echo " and would remove any git-ignored files, might leave trash afterwards!" >&2 + echo " Press Ctrl+C to abort if this is a problem" >&2 + sleep 5 + + rm -rf debian config.*cdbs* nut.dsc || true + git clean -fdX || true + mkdir -p debian + (cd scripts/obs || exit + for F in debian.* ; do + ln -s "../scripts/obs/$F" "../../debian/`echo "$F" | sed 's/debian.//'`" || exit + done) || exit + cp -f scripts/obs/nut.dsc . + sed -e 's,^\(Version:\).*$,\1 '"`NUT_VERSION_QUERY=VER50 ./tools/gitlog2version.sh`," \ + -i nut.dsc + dpkg-checkbuilddeps || sudo mk-build-debs -i + yes | debuild \ + && find . -name '*deb' + ;; + *) pushd "./builds/${BUILD_TYPE}" && REPO_DIR="$(dirs -l +1)" ./ci_build.sh ;; diff --git a/clients/Makefile.am b/clients/Makefile.am index 1b63213803..36fc2be1bf 100644 --- a/clients/Makefile.am +++ b/clients/Makefile.am @@ -21,6 +21,7 @@ AM_CXXFLAGS = -DHAVE_NUTCOMMON=1 -I$(top_srcdir)/include $(top_builddir)/common/libcommon.la \ $(top_builddir)/common/libcommonclient.la \ $(top_builddir)/common/libcommonversion.la \ +$(top_builddir)/common/libcommonstrjson.la \ $(top_builddir)/common/libparseconf.la: dummy +@cd $(@D) && $(MAKE) $(AM_MAKEFLAGS) $(@F) @@ -91,6 +92,7 @@ if WITH_CGI endif WITH_CGI upsc_SOURCES = upsc.c upsclient.h +upsc_LDADD = $(LDADD_CLIENT) $(top_builddir)/common/libcommonstrjson.la upscmd_SOURCES = upscmd.c upsclient.h upsrw_SOURCES = upsrw.c upsclient.h upslog_SOURCES = upslog.c upsclient.h upslog.h @@ -114,6 +116,7 @@ upsimage_cgi_LDADD = $(LDADD) $(LIBGD_LDFLAGS) upsset_cgi_SOURCES = upsset.c upsclient.h cgilib.c cgilib.h upsstats_cgi_SOURCES = upsstats.c upsclient.h status.h upsstats.h \ upsimagearg.h cgilib.c cgilib.h +upsstats_cgi_LDADD = $(LDADD_CLIENT) $(top_builddir)/common/libcommonstrjson.la # not LDADD... why? libupsclient_la_SOURCES = upsclient.c upsclient.h @@ -158,7 +161,7 @@ libupsclient-version.h: libupsclient.la @echo " GENERATE-HEADER $@" ; \ RES=0; \ dlname_filter() { sed -e 's/^[^=]*=//' -e 's/^"\(.*\)"$$/\1/' -e 's/^'"'"'\(.*\)'"'"'$$/\1/' ; }; \ - SOFILE_LIBUPSCLIENT="`grep -E '^dlname' '$?' | dlname_filter`" \ + SOFILE_LIBUPSCLIENT="`$(EGREP) '^dlname' '$?' | dlname_filter`" \ || SOFILE_LIBUPSCLIENT="" ; \ if [ x"$${SOFILE_LIBUPSCLIENT-}" = x ] ; then \ printf "#ifdef SOFILE_LIBUPSCLIENT\n# undef SOFILE_LIBUPSCLIENT\n#endif\n\n" ; \ diff --git a/clients/upsc.c b/clients/upsc.c index b9f86871c6..764811fa87 100644 --- a/clients/upsc.c +++ b/clients/upsc.c @@ -30,21 +30,45 @@ #include "nut_stdint.h" #include "upsclient.h" +#include "strjson.h" /* network timeout for initial connection, in seconds */ #define UPSCLI_DEFAULT_CONNECT_TIMEOUT "10" static char *upsname = NULL, *hostname = NULL; static UPSCONN_t *ups = NULL; +static int output_json = 0; + +static void fatalx_error_json_simple(int msg_is_simple, const char *msg) + __attribute__((noreturn)); + +static void fatalx_error_json_simple(int msg_is_simple, const char *msg) { + /* To be used in simpler cases, where the possible JSON + * message is not embedded into other lists/objects */ + if (output_json) { + if (msg_is_simple) { + /* Caller knows there is nothing to escape here, pass through */ + printf("{\"error\": \"%s\"}\n", msg); + } else { + printf("{\"error\": \""); + json_print_esc(msg); + printf("\"}\n"); + } + } + fatalx(EXIT_FAILURE, "Error: %s", msg); +} static void usage(const char *prog) { print_banner_once(prog, 2); printf("NUT read-only client program to display UPS variables.\n"); - printf("\nusage: %s -l | -L [[:port]]\n", prog); - printf(" %s []\n", prog); - printf(" %s -c \n", prog); + printf("\nusage: %s [-j] -l | -L [[:port]]\n", prog); + printf(" %s [-j] []\n", prog); + printf(" %s [-j] -c \n", prog); + + printf("\nOption:\n"); + printf(" -j - display output in JSON format\n"); printf("\nFirst form (lists UPSes):\n"); printf(" -l - lists each UPS on , one per line.\n"); @@ -80,7 +104,7 @@ static void printvar(const char *var) /* old-style variable name? */ if (!strchr(var, '.')) { - fatalx(EXIT_FAILURE, "Error: old-style variable names are not supported"); + fatalx_error_json_simple(1, "old-style variable names are not supported"); } query[0] = "VAR"; @@ -92,25 +116,39 @@ static void printvar(const char *var) ret = upscli_get(ups, numq, query, &numa, &answer); if (ret < 0) { + const char *msg = NULL; + int msg_is_simple = 1; /* new var and old upsd? try to explain the situation */ if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) { - fatalx(EXIT_FAILURE, "Error: variable unknown (old upsd detected)"); + msg = "variable unknown (old upsd detected)"; + } else { + msg = upscli_strerror(ups); + msg_is_simple = 0; } - fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups)); + fatalx_error_json_simple(msg_is_simple, msg); } if (numa < numq) { - fatalx(EXIT_FAILURE, "Error: insufficient data (got %" PRIuSIZE " args, need at least %" PRIuSIZE ")", numa, numq); + char msg[LARGEBUF]; + snprintf(msg, sizeof(msg), "insufficient data (got %" PRIuSIZE " args, need at least %" PRIuSIZE ")", numa, numq); + fatalx_error_json_simple(1, msg); } - printf("%s\n", answer[3]); + if (output_json) { + printf("\""); + json_print_esc(answer[3]); + printf("\"\n"); + } else { + printf("%s\n", answer[3]); + } } static void list_vars(void) { int ret; + int first = 1; size_t numq, numa; const char *query[4]; char **answer; @@ -119,32 +157,72 @@ static void list_vars(void) query[1] = upsname; numq = 2; + if (output_json) { + printf("{\n"); + } + ret = upscli_list_start(ups, numq, query); if (ret < 0) { + const char *msg = NULL; + int msg_is_simple = 1; /* check for an old upsd */ if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) { - fatalx(EXIT_FAILURE, "Error: upsd is too old to support this query"); + msg = "upsd is too old to support this query"; + } else { + msg = upscli_strerror(ups); + msg_is_simple = 0; } - fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups)); + if (output_json) { + if (msg_is_simple) { + printf(" \"error\": \"%s\"\n}\n", msg); + } else { + printf(" \"error\": \""); + json_print_esc(msg); + printf("\"\n}\n"); + } + } + fatalx(EXIT_FAILURE, "Error: %s", msg); } while (upscli_list_next(ups, numq, query, &numa, &answer) == 1) { /* VAR */ if (numa < 4) { - fatalx(EXIT_FAILURE, "Error: insufficient data (got %" PRIuSIZE " args, need at least 4)", numa); + char msg[LARGEBUF]; + snprintf(msg, sizeof(msg), "insufficient data (got %" PRIuSIZE " args, need at least 4)", numa); + if (output_json) { + printf(" \"error\": \"%s\"\n}\n", msg); + } + fatalx(EXIT_FAILURE, "Error: %s", msg); } - printf("%s: %s\n", answer[2], answer[3]); + if (output_json) { + if (!first) { + printf(",\n"); + } + printf(" \""); + json_print_esc(answer[2]); + printf("\": \""); + json_print_esc(answer[3]); + printf("\""); + first = 0; + } else { + printf("%s: %s\n", answer[2], answer[3]); + } + } + + if (output_json) { + printf("\n}\n"); } } static void list_upses(int verbose) { int ret; + int first = 1; size_t numq, numa; const char *query[4]; char **answer; @@ -155,32 +233,80 @@ static void list_upses(int verbose) ret = upscli_list_start(ups, numq, query); if (ret < 0) { + const char *msg = NULL; + int msg_is_simple = 1; + /* check for an old upsd */ if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) { - fatalx(EXIT_FAILURE, "Error: upsd is too old to support this query"); + msg = "upsd is too old to support this query"; + } else { + msg = upscli_strerror(ups); + msg_is_simple = 0; } - fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups)); + fatalx_error_json_simple(msg_is_simple, msg); + } + + if (output_json) { + if (verbose) { + printf("{\n"); + } else { + printf("[\n"); + } } while (upscli_list_next(ups, numq, query, &numa, &answer) == 1) { /* UPS */ if (numa < 3) { - fatalx(EXIT_FAILURE, "Error: insufficient data (got %" PRIuSIZE " args, need at least 3)", numa); + char msg[LARGEBUF]; + snprintf(msg, sizeof(msg), "insufficient data (got %" PRIuSIZE " args, need at least 3)", numa); + if (output_json) { + printf(" %s", verbose ? "" : "{"); + printf("\"error\": \"%s\"\n}\n", msg); + if (!verbose) + printf("]\n"); + } + fatalx(EXIT_FAILURE, "Error: %s", msg); } - if(verbose) { + if (output_json) { + if (!first) { + printf(",\n"); + } + printf(" "); + if (verbose) { + printf("\""); + json_print_esc(answer[1]); + printf("\": \""); + json_print_esc(answer[2]); + printf("\""); + } else { + printf("\""); + json_print_esc(answer[1]); + printf("\""); + } + first = 0; + } else if(verbose) { printf("%s: %s\n", answer[1], answer[2]); } else { printf("%s\n", answer[1]); } } + + if (output_json) { + if (verbose) { + printf("\n}\n"); + } else { + printf("\n]\n"); + } + } } static void list_clients(const char *devname) { int ret; + int first = 1; size_t numq, numa; const char *query[4]; char **answer; @@ -192,22 +318,51 @@ static void list_clients(const char *devname) ret = upscli_list_start(ups, numq, query); if (ret < 0) { + const char *msg = NULL; + int msg_is_simple = 1; + /* check for an old upsd */ if (upscli_upserror(ups) == UPSCLI_ERR_UNKCOMMAND) { - fatalx(EXIT_FAILURE, "Error: upsd is too old to support this query"); + msg = "upsd is too old to support this query"; + } else { + msg = upscli_strerror(ups); + msg_is_simple = 0; } - fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups)); + fatalx_error_json_simple(msg_is_simple, msg); + } + + if (output_json) { + printf("[\n"); } while ((ret=upscli_list_next(ups, numq, query, &numa, &answer)) == 1) { /* CLIENT
*/ if (numa < 3) { - fatalx(EXIT_FAILURE, "Error: insufficient data (got %" PRIuSIZE " args, need at least 3)", numa); + char msg[LARGEBUF]; + snprintf(msg, sizeof(msg), "insufficient data (got %" PRIuSIZE " args, need at least 3)", numa); + if (output_json) { + printf(" {\"error\": \"%s\"\n}\n]\n", msg); + } + fatalx(EXIT_FAILURE, "Error: %s", msg); } - printf("%s\n", answer[2]); + if (output_json) { + if (!first) { + printf(",\n"); + } + printf(" \""); + json_print_esc(answer[2]); + printf("\""); + first = 0; + } else { + printf("%s\n", answer[2]); + } + } + + if (output_json) { + printf("\n]\n"); } } @@ -242,7 +397,7 @@ int main(int argc, char **argv) } upsdebugx(1, "Starting NUT client: %s", prog); - while ((i = getopt(argc, argv, "+hlLcVW:")) != -1) { + while ((i = getopt(argc, argv, "+hlLcVW:j")) != -1) { switch (i) { @@ -258,6 +413,10 @@ int main(int argc, char **argv) clientlist = 1; break; + case 'j': + output_json = 1; + break; + case 'V': /* just show the version and optional * CONFIG_FLAGS banner if available */ @@ -277,8 +436,9 @@ int main(int argc, char **argv) } if (upscli_init_default_connect_timeout(net_connect_timeout, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT) < 0) { - fatalx(EXIT_FAILURE, "Error: invalid network timeout: %s", - net_connect_timeout); + char msg[LARGEBUF]; + snprintf(msg, sizeof(msg), "invalid network timeout: %s", net_connect_timeout); + fatalx_error_json_simple(0, msg); } argc -= optind; @@ -289,11 +449,11 @@ int main(int argc, char **argv) if (varlist) { if (upscli_splitaddr(argv[0] ? argv[0] : "localhost", &hostname, &port) != 0) { - fatalx(EXIT_FAILURE, "Error: invalid hostname.\nRequired format: [hostname[:port]]"); + fatalx_error_json_simple(0, "invalid hostname.\nRequired format: [hostname[:port]]"); } } else { if (upscli_splitname(argv[0], &upsname, &hostname, &port) != 0) { - fatalx(EXIT_FAILURE, "Error: invalid UPS definition.\nRequired format: upsname[@hostname[:port]]"); + fatalx_error_json_simple(0, "invalid UPS definition.\nRequired format: upsname[@hostname[:port]]"); } } upsdebugx(1, "upsname='%s' hostname='%s' port='%" PRIu16 "'", @@ -302,7 +462,7 @@ int main(int argc, char **argv) ups = xmalloc(sizeof(*ups)); if (upscli_connect(ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0) { - fatalx(EXIT_FAILURE, "Error: %s", upscli_strerror(ups)); + fatalx_error_json_simple(0, upscli_strerror(ups)); } if (varlist) { diff --git a/clients/upsclient.c b/clients/upsclient.c index 30062c81ed..36263e8b06 100644 --- a/clients/upsclient.c +++ b/clients/upsclient.c @@ -1769,9 +1769,14 @@ int upscli_splitaddr(const char *buf, char **hostname, uint16_t *port) } /* Check that "long" port fits in an "uint16_t" so is in IP range - * (under 65536) */ + * (under 65536). + * FIXME: If it is a non-numeric string, try to resolve via + * "services" naming database, with a C equivalent of: + * :; getent services ssh + * ssh 22/tcp + */ if ((*(++s) == '\0') || ((l = strtol(s, NULL, 10)) < 1 ) || (l > 65535)) { - fprintf(stderr, "upscli_splitaddr: no port specified after ':' separator\n"); + fprintf(stderr, "upscli_splitaddr: no port number specified after ':' separator\n"); return -1; } *port = (uint16_t)l; diff --git a/clients/upslog.c b/clients/upslog.c index c81d7cb7e9..66e99b8b0c 100644 --- a/clients/upslog.c +++ b/clients/upslog.c @@ -199,23 +199,25 @@ static void help(const char *prog) printf(" -f - Log format. See below for details.\n"); printf(" - Use -f \"\" so your shell doesn't break it up.\n"); - printf(" -N - Prefix \"%%UPSHOST%%%%t\" before the format (default/custom)"); - printf(" - Useful when logging many systems into same target.\n"); + printf(" -N - Prefix \"%%UPSHOST%%%%t\" before the format (default/custom):\n"); + printf(" useful when logging many systems into same target.\n"); printf(" -i - Time between updates, in seconds\n"); printf(" -d - Exit after specified amount of updates\n"); printf(" -l - Log file name, or - for stdout (foreground by default)\n"); printf(" -D - raise debugging level (and stay foreground by default)\n"); printf(" -F - stay foregrounded even if logging into a file\n"); printf(" -B - stay backgrounded even if logging to stdout or debugging\n"); - printf(" -p - Base name for PID file (defaults to \"%s\")\n", prog); - printf(" - NOTE: PID file is written regardless of fore/back-grounding\n"); + printf(" -p - Base name for PID file (defaults to \"%s\")\n", prog); + printf(" - NOTE: PID file is written regardless of fore/back-grounding\n"); printf(" -s - Monitor UPS - @[:]\n"); printf(" - Example: -s myups@server\n"); + printf(" - Specify '*' as upsname to query all devices on the host\n"); printf(" -m - Monitor UPS \n"); printf(" - Example: -m myups@server,/var/log/myups.log\n"); printf(" - NOTE: You can use '-' as logfile for stdout\n"); printf(" and it would not imply foregrounding\n"); printf(" - Unlike one '-s ups -l file' spec, you can specify many tuples\n"); + printf(" - Example: -m '*,-' to view updates of all known local devices\n"); printf(" -u - Switch to if started as root\n"); printf("\nCommon arguments:\n"); printf(" -V - display the version of this software\n"); @@ -688,9 +690,9 @@ int main(int argc, char **argv) if (monhost || logfn) { /* Both data points must be defined, no defaults */ if (!monhost) - fatalx(EXIT_FAILURE, "No UPS defined for monitoring - use -s when using -l , or use -m "); + fatalx(EXIT_FAILURE, "No UPS defined for monitoring - use -s when using -l , or use -m ; consider -m '*,-' to view updates of all known local devices"); if (!logfn) - fatalx(EXIT_FAILURE, "No filename defined for logging - use -l when using -s , or use -m "); + fatalx(EXIT_FAILURE, "No filename defined for logging - use -l when using -s , or use -m ; consider -m '*,-' to view updates of all known local devices"); /* May be or not be NULL here: */ monhost_ups_prev = monhost_ups_current; @@ -737,7 +739,7 @@ int main(int argc, char **argv) /* shouldn't happen */ if (!monhost_len) - fatalx(EXIT_FAILURE, "No UPS defined for monitoring - use -s -l , or use -m "); + fatalx(EXIT_FAILURE, "No UPS defined for monitoring - use -s -l , or use -m ; consider -m '*,-' to view updates of all known local devices"); /* Split the system specs in a common fashion for tuples and legacy args */ for (monhost_ups_current = monhost_ups_anchor, monhost_ups_prev = NULL; @@ -869,7 +871,7 @@ int main(int argc, char **argv) /* might happen if we only queried remote hosts and found nothing, * just in case we missed something above */ if (!monhost_len || !monhost_ups_anchor) - fatalx(EXIT_FAILURE, "No UPS defined for monitoring - use -s -l , or use -m "); + fatalx(EXIT_FAILURE, "No UPS defined for monitoring - use -s -l , or use -m ; consider -m '*,-' to view updates of all known local devices"); /* Report the logged systems, open the log files as needed */ for (monhost_ups_current = monhost_ups_anchor; diff --git a/clients/upsmon.c b/clients/upsmon.c index 585fcc6e9b..983c6e57e2 100644 --- a/clients/upsmon.c +++ b/clients/upsmon.c @@ -126,7 +126,7 @@ static int alarmcritical = 1; considered critical (e.g. when not communicating). Negative values will prevent a UPS from ever becoming critical from overload. A value of zero will have the UPS instantly be considered critical in such situations. */ -static int overdurationtime = -1; +static int overdurationtime = -1; /* userid for unprivileged process when using fork mode */ static char *run_as_user = NULL; @@ -144,15 +144,16 @@ static int userfsd = 0, pipefd[2]; * into two upsmon processes for some more security? */ #ifndef WIN32 static int use_pipe = 1; +static pid_t pid_pipechild = -1; #else /* WIN32 */ /* Do not fork in WIN32 */ static int use_pipe = 0; -static HANDLE mutex = INVALID_HANDLE_VALUE; +static HANDLE mutex = INVALID_HANDLE_VALUE; #endif /* WIN32 */ static utype_t *firstups = NULL; -static int opt_af = AF_UNSPEC; +static int opt_af = AF_UNSPEC; #ifndef WIN32 /* signal handling things */ @@ -167,8 +168,8 @@ static sigset_t nut_upsmon_sigmask; /* 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; +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 @@ -176,14 +177,16 @@ static int sleep_inhibitor_status = -2; * to set the minimal debug level (CLI provided value less * than that would not have effect, can only have more). */ -static int nut_debug_level_global = -1; +static int nut_debug_level_global = -1; /* Debug level specified via command line - we revert to * it when reloading if there was no DEBUG_MIN in ups.conf */ -static int nut_debug_level_args = 0; +static int nut_debug_level_args = 0; /* pre-declare internal methods */ static int get_var(utype_t *ups, const char *var, char *buf, size_t bufsize); +static void set_alarm(void); +static void clear_alarm(void); static void setflag(int *val, int flag) { @@ -277,6 +280,7 @@ static unsigned __stdcall async_notify(LPVOID param) if (notifycmd != NULL) { snprintf(exec, sizeof(exec), "%s \"%s\"", notifycmd, data->notice); + upsdebugx(6, "%s: Calling NOTIFYCMD: %s", __func__, exec); if (data->upsname) setenv("UPSNAME", data->upsname, 1); else @@ -329,22 +333,24 @@ static void notify(const char *notice, unsigned int flags, const char *ntype, } if (ret != 0) { /* parent */ - upsdebugx(6, "%s (parent): forked a child to notify via subprocesses", __func__); + upsdebugx(2, "%s (parent): forked a child to notify via subprocesses", __func__); return; } /* child continues and does all the work */ - upsdebugx(6, "%s (child): forked to notify via subprocesses", __func__); + setproctag("notify"); + upsdebugx(2, "%s (%schild): forked to notify via subprocesses", + __func__, use_pipe ? "grand" : ""); if (flag_isset(flags, NOTIFY_WALL)) { - upsdebugx(6, "%s (child): NOTIFY_WALL", __func__); + upsdebugx(6, "%s (%schild): NOTIFY_WALL", __func__, use_pipe ? "grand" : ""); wall(notice); } if (flag_isset(flags, NOTIFY_EXEC)) { if (notifycmd != NULL) { - upsdebugx(6, "%s (child): NOTIFY_EXEC: calling NOTIFYCMD as '%s \"%s\"'", - __func__, notifycmd, notice); + upsdebugx(6, "%s (%schild): NOTIFY_EXEC: calling NOTIFYCMD as '%s \"%s\"'", + __func__, use_pipe ? "grand" : "", notifycmd, notice); snprintf(exec, sizeof(exec), "%s \"%s\"", notifycmd, notice); @@ -358,11 +364,11 @@ static void notify(const char *notice, unsigned int flags, const char *ntype, upslog_with_errno(LOG_ERR, "%s", __func__); } } else { - upsdebugx(6, "%s (child): NOTIFY_EXEC: no NOTIFYCMD was configured", __func__); + upsdebugx(6, "%s (%schild): NOTIFY_EXEC: no NOTIFYCMD was configured", __func__, use_pipe ? "grand" : ""); } } - upsdebugx(6, "%s (child): exiting after notifications", __func__); + upsdebugx(6, "%s (%schild): exiting after notifications", __func__, use_pipe ? "grand" : ""); exit(EXIT_SUCCESS); #else /* WIN32 */ @@ -888,7 +894,7 @@ static void ups_on_line(utype_t *ups) { try_restore_pollfreq(ups); - if (flag_isset(ups->status, ST_ONLINE)) { /* no change */ + if (flag_isset(ups->status, ST_ONLINE)) { /* no change */ upsdebugx(4, "%s: %s (no change)", __func__, ups->sys); return; } @@ -905,14 +911,22 @@ static void ups_on_line(utype_t *ups) clearflag(&ups->status, ST_ONBATT); } -/* create the flag file if necessary */ +/* create the flag file if necessary (and if POWERDOWNFLAG is configured in + * upsmon.conf); this can happen on both NUT servers and pure client machines + */ static void set_pdflag(void) { FILE *pdf; - if (!powerdownflag) + /* NOTE: This method typically runs as root in the privileged + * sub-process, or if we do not use_pipe and run as a monolith. + */ + if (!powerdownflag) { + upsdebugx(1, "%s: SKIP creation of a POWERDOWNFLAG file: not configured", __func__); return; + } + upsdebugx(1, "%s: populate POWERDOWNFLAG file: %s", __func__, powerdownflag); pdf = fopen(powerdownflag, "w"); if (!pdf) { upslogx(LOG_ERR, "Failed to create power down flag!"); @@ -929,7 +943,18 @@ static void doshutdown(void) static void doshutdown(void) { + time_t start; + + /* NOTE: This method typically runs in the unprivileged child + * sub-process, unless we do not use_pipe and run as a monolith. + * It can block for a while or forever (depending on SHUTOWNEXIT + * setting), otherwise it exits the (child part of) upsmon daemon. + */ + upsdebugx(1, "%s: starting...", __func__); + upsnotify(NOTIFY_STATE_STOPPING, "Executing automatic power-fail shutdown"); + upsnotify_extend_timeout_usec = UPSNOTIFY_EXTEND_TIMEOUT_USEC_INFINITY; + upsnotify(NOTIFY_STATE_EXTEND_TIMEOUT, NULL); /* this should probably go away at some point */ upslogx(LOG_CRIT, "Executing automatic power-fail shutdown"); @@ -937,7 +962,32 @@ static void doshutdown(void) do_notify(NULL, NOTIFY_SHUTDOWN, NULL); - sleep(finaldelay); + if (finaldelay > 0) { + time_t now; + + upsdebugx(1, "%s: waiting for FINALDELAY=%u (to let notification handling complete)...", + __func__, finaldelay); + + /* FIXME: Track and check if the current system (or NUT build) + * supports notifications and service watchdog in particular; + * if not - use a chaper simple sleep(finaldelay) right away. + */ + time(&start); + time(&now); + + while (difftime(now, start) < finaldelay) { + sleep(1); + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + time(&now); + } + } else { + upsdebugx(1, "%s: FINALDELAY=%u (not waiting to let notification handling complete)...", + __func__, finaldelay); + } + + /* If we would handle SHUTDOWNEXIT as a finite delay below, + * that time should include the duration of SHUTDOWNCMD too */ + time(&start); /* in the pipe model, we let the parent do this for us */ if (use_pipe) { @@ -945,6 +995,7 @@ static void doshutdown(void) ssize_t wret; ch = 1; + upslogx(2, "%s: call parent pipe for shutdown (async)", __func__); wret = write(pipefd[1], &ch, 1); if (wret < 1) @@ -960,6 +1011,9 @@ static void doshutdown(void) set_pdflag(); + upsdebugx(2, "%s: directly call shutdown command (sync)", __func__); + upsnotify(NOTIFY_STATE_EXTEND_TIMEOUT, "Mono-process: calling shutdown command (directly)"); + #ifdef WIN32 SC_HANDLE SCManager; SC_HANDLE Service; @@ -986,6 +1040,8 @@ static void doshutdown(void) } #endif /* WIN32 */ + upsdebugx(1, "%s: upsmon mono-process: Calling shutdown command: %s", + __func__, shutdowncmd); sret = system(shutdowncmd); if (sret != 0) @@ -993,28 +1049,85 @@ static void doshutdown(void) shutdowncmd); } + /* code below runs in the child (or only) process */ + upsdebugx(1, "%s: current exit_flag=%i", __func__, exit_flag); if (shutdownexitdelay == 0) { upsdebugx(1, "Exiting upsmon immediately " "after initiating shutdown, by default"); - } else - if (shutdownexitdelay < 0) { - upslogx(LOG_WARNING, - "Configured to not exit upsmon " - "after initiating shutdown"); - /* Technically, here we sleep until SIGTERM or poweroff */ - do { - sleep(1); - } while (!exit_flag); } else { - upslogx(LOG_WARNING, - "Configured to only exit upsmon %d sec " - "after initiating shutdown", shutdownexitdelay); + time_t now; + + if (shutdownexitdelay < 0) { + upslogx(LOG_WARNING, + "Configured to not exit upsmon " + "after initiating shutdown"); + upsnotify(NOTIFY_STATE_EXTEND_TIMEOUT, "Child/Mono-process: " + "Configured to not exit upsmon after initiating shutdown"); + } else { + upslogx(LOG_WARNING, + "Configured to only exit upsmon SHUTDOWNEXIT=%d sec " + "after initiating shutdown", shutdownexitdelay); + upsnotify(NOTIFY_STATE_EXTEND_TIMEOUT, "Child/Mono-process: " + "Configured to only exit upsmon some time after initiating shutdown"); + } + if (exit_flag) { + /* TOTHINK: Are there cases when we want to + * ignore it? Or is a SIGTERM, SIGBRK etc. + * a good enough reason to do exit quickly? */ + upslogx(LOG_WARNING, + "Note that 'exit_flag' was raised by a " + "signal, so this process will not in fact " + "wait that long"); + } + + /* Technically, here we sleep until SIGTERM or poweroff, + * or in case of initially positive shutdownexitdelay -- + * when it counts down to zero. + */ do { + utype_t *ups; + char temp[SMALLBUF]; + long maxlogins = 0, logins = 0; + + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + + upsdebugx(3, "%s: ping data server(s) for this client to remain not-timed-out", __func__); + +#ifndef WIN32 + /* reap children (e.g. notify) that have exited */ + waitpid(-1, NULL, WNOHANG); +#endif + + /* Contact the data server(s) regularly so this + * client is not assumed dead while looping */ + for (ups = firstups; ups != NULL && !exit_flag; ups = ups->next) { + set_alarm(); + + if (get_var(ups, "numlogins", temp, sizeof(temp)) >= 0) { + logins = strtol(temp, (char **)NULL, 10); + + if (logins > maxlogins) + maxlogins = logins; + } + + clear_alarm(); + } + + /* Countdown is not exactly a timer (pinging can take time) */ + if (shutdownexitdelay > 0) { + time(&now); + if (difftime(now, start) > shutdownexitdelay) { + upsdebugx(3, "%s: SHUTDOWNEXIT timeout expired", __func__); + break; + } + } sleep(1); - shutdownexitdelay--; - } while (!exit_flag && shutdownexitdelay); + } while (!exit_flag && shutdownexitdelay != 0); } + + upsdebugx(1, "%s: current exit_flag=%i", __func__, exit_flag); + upslogx(LOG_WARNING, "Exiting upsmon program after initiating shutdown"); exit(EXIT_SUCCESS); } @@ -1051,8 +1164,20 @@ static void setfsd(utype_t *ups) return; } - if (!strncmp(buf, "OK", 2)) + if (!strncmp(buf, "OK", 2)) { + upsdebugx(1, "%s: data server confirmed setting FSD for UPS [%s]", __func__, ups->sys); + + /* Let NOTIFYCMD (if any) know, and have a chance to react */ + if (ups->lastfsdnotify) { + /* e.g. upsd was still alive with a latched FSD + * status when this upsmon instance started */ + upsdebugx(2, "%s: not notifying about FSD for UPS [%s] because it was recently reported already", __func__, ups->sys); + } else { + time(&(ups->lastfsdnotify)); + do_notify(ups, NOTIFY_FSD, NULL); + } return; + } /* protocol error: upsd said something other than "OK" */ upslogx(LOG_ERR, "FSD set on UPS %s failed: %s", ups->sys, buf); @@ -1196,11 +1321,18 @@ static void sync_secondaries(void) char temp[SMALLBUF]; time_t start, now; long maxlogins, logins; + size_t count = 0; + /* NOTE: This method typically runs in the unprivileged child + * sub-process, unless we do not use_pipe and run as a monolith. + */ time(&start); for (;;) { maxlogins = 0; + count++; + + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); for (ups = firstups; ups != NULL; ups = ups->next) { @@ -1220,22 +1352,29 @@ static void sync_secondaries(void) clear_alarm(); } + upsnotify(NOTIFY_STATE_WATCHDOG, NULL); + /* if no UPS has more than 1 login (that would be us), * then secondaries are all gone */ /* TO THINK: how about redundant setups with several primary-mode - * clients managing an UPS, or possibly differend UPSes, with the + * clients managing an UPS, or possibly different UPSes, with the * same upsd? */ + upsdebugx(3, "%s: Max data server logins per UPS still active: %ld", __func__, maxlogins); if (maxlogins <= 1) return; /* after HOSTSYNC seconds, assume secondaries are stuck - and bail */ time(&now); - if ((now - start) > hostsync) { + if (difftime(now, start) > hostsync) { upslogx(LOG_INFO, "Host sync timer expired, forcing shutdown"); return; } + if (maxlogins > 1 && count == 1) { + do_notify(NULL, NOTIFY_SHUTDOWN_HOSTSYNC, NULL); + } + usleep(250000); } } @@ -1248,26 +1387,40 @@ static void forceshutdown(void) utype_t *ups; int isaprimary = 0; + /* NOTE: This method typically runs in the unprivileged child + * sub-process, unless we do not use_pipe and run as a monolith. + * It can block for a while or forever (depending on SHUTOWNEXIT + * and HOSTSYNC settings), otherwise it exits the (child part of) + * upsmon daemon by calling doshutdown(). + */ upsdebugx(1, "Shutting down any UPSes in PRIMARY mode..."); + upsdebugx(2, "%s: For this system, timing options: " + "SHUTDOWNEXIT=%d HOSTSYNC=%d FINALDELAY=%u DEADTIME=%d; " + "flags: USERFSD=%d EXIT_FLAG=%d USE_PIPE=%d", + __func__, shutdownexitdelay, hostsync, finaldelay, deadtime, + userfsd, exit_flag, use_pipe); /* set FSD on any "primary" UPS entries (forced shutdown in progress) */ for (ups = firstups; ups != NULL; ups = ups->next) if (flag_isset(ups->status, ST_PRIMARY)) { isaprimary = 1; + upsdebugx(2, "%s: tell data server to setfsd(%s@%s)", + __func__, NUT_STRARG(ups->upsname), + NUT_STRARG(ups->hostname)); setfsd(ups); } /* if we're not a primary on anything, we should shut down now */ - if (!isaprimary) + if (!isaprimary) { + upsdebugx(1, "This system is not a primary for any device, shutting down now..."); doshutdown(); + } /* we must be the primary now */ - upsdebugx(1, "This system is a primary... waiting for secondaries to logout..."); - - /* wait up to HOSTSYNC seconds for secondaries to logout */ + upsdebugx(1, "This system is a primary for some device(s); waiting (up to HOSTSYNC=%d seconds) for secondaries to logout...", hostsync); sync_secondaries(); - /* time expired or all the secondaries are gone, so shutdown */ + upsdebugx(1, "HOSTSYNC timeout expired or all the secondaries are gone, so shutting down this primary system now..."); doshutdown(); } @@ -1573,8 +1726,14 @@ static void recalc(void) upsdebugx(3, "Current power value: %u", val_ol); upsdebugx(3, "Minimum power value: %u", minsupplies); - if (val_ol < minsupplies) + /* Note that a monitoring-only upsmon instance would have MINSUPPLIES 0 + * and so would never see a smaller amount of healthily online UPSes */ + if (val_ol < minsupplies) { + upslogx(LOG_WARNING, "Too few UPS(es) are healthy (%u<%u), " + "initiating forced shutdown", + val_ol, minsupplies); forceshutdown(); + } } static void ups_low_batt(utype_t *ups) @@ -1731,9 +1890,12 @@ static void ups_fsd(utype_t *ups) upsdebugx(3, "%s: %s (first time)", __func__, ups->sys); - /* must have changed from !FSD to FSD, so notify */ + /* must have changed from !FSD to FSD, so notify; avoid duplicates though */ - do_notify(ups, NOTIFY_FSD, NULL); + if (!(ups->lastfsdnotify)) { + time(&(ups->lastfsdnotify)); + do_notify(ups, NOTIFY_FSD, NULL); + } setflag(&ups->status, ST_FSD); } @@ -2001,6 +2163,8 @@ static void addups(int reloading, const char *sys, const char *pvs, tmp->lastrbwarn = 0; tmp->lastncwarn = 0; + tmp->lastfsdnotify = 0; + tmp->offsince = 0; tmp->oblbsince = 0; tmp->oversince = 0; @@ -2542,6 +2706,7 @@ static void sigpipe(int sig) /* SIGQUIT, SIGTERM handler */ static void set_exit_flag(int sig) { + upslogx(LOG_INFO, "Signal %d: User requested EXIT", sig); exit_flag = sig; } @@ -2604,12 +2769,19 @@ static void user_fsd(int sig) { upslogx(LOG_INFO, "Signal %d: User requested FSD", sig); userfsd = 1; + + /* Tell the sleep() in main-loop to end early; somehow + * this does get honoured as an immediate interruption + * (at least on Linux). Gets reset to 0 in the end of + * main loop. */ + exit_flag = -1; } static void set_reload_flag(int sig) { NUT_UNUSED_VARIABLE(sig); + upslogx(LOG_INFO, "Signal %d: User requested RELOAD", sig); reload_flag = 1; } @@ -2748,8 +2920,10 @@ static void parse_status(utype_t *ups, char *status, char *buzzword, char *buzzw /* clear these out early if they disappear */ if (!strstr(status, "LB")) clearflag(&ups->status, ST_LOWBATT); - if (!strstr(status, "FSD")) + if (!strstr(status, "FSD")) { clearflag(&ups->status, ST_FSD); + ups->lastfsdnotify = 0; + } /* similar to above - clear these flags and send notifications */ if (!strstr(status, "CAL")) @@ -3181,7 +3355,8 @@ static void clear_pdflag(void) unlink(powerdownflag); } -/* exit with success only if it exists and is proper */ +/* exit with success only if the POWERDOWNFLAG file is configured, + * exists and has proper contents */ static int check_pdflag(void) { int ret; @@ -3283,6 +3458,11 @@ static void runparent(int fd) ssize_t ret; int sret; char ch; + time_t start; + + /* NOTE: This method runs as root in the privileged sub-process; + * it is not executed if we do not use_pipe and run as a monolith. + */ /* handling signals is the child's job */ #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_STRICT_PROTOTYPES) @@ -3296,6 +3476,7 @@ static void runparent(int fd) # pragma GCC diagnostic pop #endif + /* block indefinitely, until child decides to exit or shut down */ ret = read(fd, &ch, 1); if (ret < 1) { @@ -3308,16 +3489,76 @@ static void runparent(int fd) if (ch != 1) fatalx(EXIT_FAILURE, "upsmon parent: got bogus pipe command %c", ch); + /* If we would handle SHUTDOWNEXIT as a finite delay below, + * that time should include the duration of SHUTDOWNCMD too */ + time(&start); + + upsnotify(NOTIFY_STATE_STOPPING, "Parent: calling shutdown command"); + upsnotify_extend_timeout_usec = UPSNOTIFY_EXTEND_TIMEOUT_USEC_INFINITY; + upsnotify(NOTIFY_STATE_EXTEND_TIMEOUT, NULL); + /* have to do this here - child is unprivileged */ set_pdflag(); + upsdebugx(1, "%s: upsmon parent: Calling shutdown command: %s", + __func__, shutdowncmd); sret = system(shutdowncmd); if (sret != 0) - upslogx(LOG_ERR, "parent: Unable to call shutdown command: %s", + upslogx(LOG_ERR, "upsmon parent: Unable to call shutdown command: %s", shutdowncmd); + if (shutdownexitdelay) { + /* make sure the child is still alive - inverse of check_parent() */ + int waitstatus = 0; + pid_t waitret; + time_t now; + + upsdebugx(1, "upsmon parent (exit_flag=%d): " + "waiting for child %" PRIiMAX " to exit, " + "after %s (%i) shutdown command: %s", + exit_flag, (intmax_t)pid_pipechild, + (sret == 0 ? "calling" : "trying to call"), + sret, shutdowncmd); + upsnotify(NOTIFY_STATE_EXTEND_TIMEOUT, "Parent: waiting for child to exit"); + + do { + waitret = waitpid(pid_pipechild, &waitstatus, WNOHANG); + + upsdebugx(3, "upsmon parent: wait for child returned status=%d, pid=%" PRIiMAX, waitstatus, (intmax_t)waitret); + if (waitret != 0 && (WIFEXITED(waitstatus) || WIFSIGNALED(waitstatus))) { + if ( (pid_pipechild > 0 && pid_pipechild == waitret) + || (pid_pipechild <= 0) + ) { + /* If we know specific child PID + * to wait for, do so; otherwise + * any child suffices (but may be + * a "notify" sub-process etc.) */ + /* TOTHINK: Try to write into the + * "pipefd" and check that this + * attempt errors out? */ + upsdebugx(1, "upsmon parent: child has exited"); + break; + } + } + + if (shutdownexitdelay > 0) { + time(&now); + if (difftime(now, start) > shutdownexitdelay) { + upsdebugx(1, "upsmon parent: SHUTDOWNEXIT timeout expired"); + break; + } + } + + sleep(1); + } while(!exit_flag); + upsdebugx(1, "upsmon parent: exit_flag=%d", exit_flag); + } + close(fd); + upslogx(LOG_WARNING, "upsmon parent: Exiting after %s (%i) shutdown command: %s", + (sret == 0 ? "calling" : "trying to call"), + sret, shutdowncmd); exit(EXIT_SUCCESS); } #endif /* !WIN32 */ @@ -3327,20 +3568,24 @@ static void start_pipe(void) { #ifndef WIN32 int ret; + pid_t pid; ret = pipe(pipefd); if (ret) fatal_with_errno(EXIT_FAILURE, "pipe creation failed"); - ret = fork(); + pid = fork(); - if (ret < 0) + if (pid < 0) fatal_with_errno(EXIT_FAILURE, "fork failed"); /* start the privileged parent */ - if (ret != 0) { + if (pid != 0) { close(pipefd[1]); + setproctag("priv-parent"); + pid_pipechild = pid; + upsdebugx(1, "%s (parent): forked a child (%" PRIiMAX ") to run the main loop", __func__, (intmax_t)pid); runparent(pipefd[0]); #ifndef HAVE___ATTRIBUTE__NORETURN @@ -3349,6 +3594,8 @@ static void start_pipe(void) } close(pipefd[0]); + setproctag("main-loop"); + upsdebugx(1, "%s (child): forked a child to run the main loop", __func__); /* prevent pipe leaking to NOTIFYCMD */ set_close_on_exec(pipefd[1]); @@ -3668,6 +3915,12 @@ int main(int argc, char *argv[]) } /* else nothing to bother about */ } + if (cmd) { + setproctag("signaler"); + } else { + setproctag("init"); + } + if (upscli_init_default_connect_timeout(net_connect_timeout, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT) < 0) { fatalx(EXIT_FAILURE, "Error: invalid network timeout: %s", net_connect_timeout); @@ -3792,6 +4045,9 @@ int main(int argc, char *argv[]) #endif /* not WIN32 */ } + upslogx(LOG_WARNING, "Exiting upsmon instance after sending " + "a signal to the running daemon %ssuccessfully", + (cmdret == 0 ? "" : "un")); exit((cmdret == 0) ? EXIT_SUCCESS : EXIT_FAILURE); } @@ -3867,11 +4123,13 @@ int main(int argc, char *argv[]) /* === root parent and unprivileged child split here === */ start_pipe(); - /* write the pid file now, as we will soon lose root */ + /* we are upsmon-child from this point on + * write the pid file now, as we will soon lose root */ writepid(prog); become_user(new_uid); } else { + setproctag("mono"); #ifndef WIN32 /* Note: upsmon does not fork in WIN32 */ upslogx(LOG_INFO, "Warning: running as one big root process by request (upsmon -p)"); @@ -3910,8 +4168,10 @@ int main(int argc, char *argv[]) upsnotify(NOTIFY_STATE_WATCHDOG, NULL); /* check flags from signal handlers */ - if (userfsd) + if (userfsd) { + upslogx(LOG_WARNING, "Got an FSD signal, initiating forced shutdown"); forceshutdown(); + } if (reload_flag) { upsnotify(NOTIFY_STATE_RELOADING, NULL); @@ -3967,8 +4227,10 @@ int main(int argc, char *argv[]) upsnotify(NOTIFY_STATE_READY, NULL); upsnotify(NOTIFY_STATE_WATCHDOG, NULL); - if (exit_flag) + if (exit_flag) { + upslogx(LOG_WARNING, "%s: OS was last known to be preparing for sleep, and we have the exit_flag raised now", prog); break; + } if (reload_flag && sleep_inhibitor_status != 0) { upslogx(LOG_WARNING, "%s: OS was last known to be preparing for sleep, and we were " @@ -4023,6 +4285,7 @@ int main(int argc, char *argv[]) pollups(ups); } + /* the bulk of work: recalculate the online power value and see if things are still OK */ recalc(); /* make sure the parent hasn't died */ @@ -4032,7 +4295,14 @@ int main(int argc, char *argv[]) #ifndef WIN32 /* reap children that have exited */ waitpid(-1, NULL, WNOHANG); +#endif + + if (userfsd) { + upsdebugx(1, "main loop: learned of FSD recently, can not sleep now"); + goto end_loop_cycle; + } +#ifndef WIN32 gettimeofday(&start, NULL); upsdebugx(4, "Beginning %u-sec delay between main loop cycles", sleepval); if (isPreparingForSleepSupported()) { @@ -4084,7 +4354,8 @@ int main(int argc, char *argv[]) * so we aborted it, we end soon after ifdef/endif, * and so not handling here specially */ } else { - /* sleep tight */ + /* sleep tight (unless interrupted by a signal + * and a non-zero exit_flag value) */ sleep(sleepval); } gettimeofday(&end, NULL); @@ -4115,7 +4386,7 @@ int main(int argc, char *argv[]) sleepval, difftimeval(end, start)); if (ret == WAIT_FAILED) { - upslogx(LOG_ERR, "Wait failed"); + upslogx(LOG_ERR, "%s: Wait between main loop cycles failed", prog); exit(EXIT_FAILURE); } @@ -4173,6 +4444,9 @@ int main(int argc, char *argv[]) } end_loop_cycle: + if (exit_flag < 0 && userfsd) + exit_flag = 0; + /* If anyone printed anything, be sure it is output * in a timely manner, not buffered indefinitely: */ fflush(stdout); diff --git a/clients/upsmon.h b/clients/upsmon.h index 26eed7624c..4007cb1b40 100644 --- a/clients/upsmon.h +++ b/clients/upsmon.h @@ -92,10 +92,12 @@ typedef struct { int pollfail_log_throttle_count; /* How many pollfreq loops this UPS was in this state since last logged report? */ time_t lastpoll; /* time of last successful poll */ - time_t lastnoncrit; /* time of last non-crit poll */ + time_t lastnoncrit; /* time of last non-crit poll */ time_t lastrbwarn; /* time of last REPLBATT warning*/ time_t lastncwarn; /* time of last NOCOMM warning */ + time_t lastfsdnotify; /* time of last FSD notification (when first discovering the state, or setting it - avoid duplicate notification); 0 initially or if that state clears */ + 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) */ time_t oversince; /* time of recent entry into OVER state */ @@ -132,6 +134,8 @@ typedef struct { #define NOTIFY_BOOST 24 /* UPS is boosting incoming voltage */ #define NOTIFY_NOTBOOST 25 /* UPS is not anymore boosting incoming voltage */ +#define NOTIFY_SHUTDOWN_HOSTSYNC 26 /* Shutdown initiated; primary system is waiting for secondaries to log out or time out */ + /* Special handling below */ #define NOTIFY_OTHER 28 /* UPS has at least one unclassified status token */ #define NOTIFY_NOTOTHER 29 /* UPS has no unclassified status tokens anymore */ @@ -174,6 +178,7 @@ static struct { { 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 }, + { NOTIFY_SHUTDOWN_HOSTSYNC, "SHUTDOWN_HOSTSYNC", NULL, "Shutdown initiated; primary system is waiting for secondaries to log out or time out", NOTIFY_DEFAULT }, { NOTIFY_REPLBATT, "REPLBATT", NULL, "UPS %s battery needs to be replaced", NOTIFY_DEFAULT }, { NOTIFY_NOCOMM, "NOCOMM", NULL, "UPS %s is unavailable", NOTIFY_DEFAULT }, { NOTIFY_NOPARENT, "NOPARENT", NULL, "upsmon parent process died - shutdown impossible", NOTIFY_DEFAULT }, diff --git a/clients/upssched-cmd b/clients/upssched-cmd index 160f50584c..59ad5236d3 100755 --- a/clients/upssched-cmd +++ b/clients/upssched-cmd @@ -17,8 +17,8 @@ echo "`date -u`: $0: THIS IS A SAMPLE SCRIPT, PLEASE TAILOR IT FOR YOUR DEPLOYMENT OF NUT!" >&2 logger -t upssched-cmd "THIS IS A SAMPLE SCRIPT, PLEASE TAILOR IT FOR YOUR DEPLOYMENT OF NUT!" -printf "`date -u`: UPSNAME='%s'\tNOTIFYTYPE='%s'\targs=%s\n" "$UPSNAME" "$NOTIFYTYPE" "$@" >&2 -printf "UPSNAME='%s' NOTIFYTYPE='%s' args=%s\n" "$UPSNAME" "$NOTIFYTYPE" "$@" | logger -t upssched-cmd-received-NOTIFYTYPE +printf "`date -u`: UPSNAME='%s'\tNOTIFYTYPE='%s'\tNOTIFYMSG='%s'\targs=%s\n" "$UPSNAME" "$NOTIFYTYPE" "$NOTIFYMSG" "$*" >&2 +printf "UPSNAME='%s' NOTIFYTYPE='%s' NOTIFYMSG='%s' args=%s\n" "$UPSNAME" "$NOTIFYTYPE" "$NOTIFYMSG" "$*" | logger -t upssched-cmd-received-NOTIFYTYPE #set diff --git a/clients/upssched.c b/clients/upssched.c index d24fed4c73..fe8f914a12 100644 --- a/clients/upssched.c +++ b/clients/upssched.c @@ -1,6 +1,14 @@ /* upssched.c - upsmon's scheduling helper for offset timers - Copyright (C) 2000 Russell Kroll + Copyright (C) + 2000 Russell Kroll + 2005-2012 Arnaud Quette + 2006 Charles Lepple + 2006-2019 Arjen de Korte + 2006-2007 Peter Selinger + 2010-2012 Frederic BOHE + 2020-2025 Jim Klimov + 2022 Dimitris Economou 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 @@ -19,7 +27,9 @@ /* design notes for the curious: * - * 1. we get called with a upsname and notifytype from upsmon + * 1. we get called with a ups_name and notify_type from upsmon + * (and notify_msg via first non-option argv[] element if + * present and not trivial) * 2. the config file is searched for an AT condition that matches * 3. the conditions on any matching lines are parsed * @@ -63,15 +73,20 @@ typedef struct ttype_s { char *name; time_t etime; + char **upsnames; /* List of unique UPSNAME values that commanded to start this timer name */ + char **notifytypes; /* List of unique NOTIFYTYPE values that commanded to start this timer name */ + char **notifymsgs; /* List of unique NOTIFYMSG values that commanded to start this timer name */ struct ttype_s *next; } ttype_t; static ttype_t *thead = NULL; static conn_t *connhead = NULL; static char *cmdscript = NULL, *pipefn = NULL, *lockfn = NULL; +static int nut_debug_level_args = 0, nut_debug_level_env = 0, nut_debug_level_conf = 0; +static int list_timers = 0; /* ups name and notify type (string) as received from upsmon */ -static const char *upsname, *notify_type, *prog = NULL; +static const char *ups_name = NULL, *notify_type = NULL, *notify_msg = NULL, *prog = NULL; #ifdef WIN32 static OVERLAPPED connect_overlapped; @@ -95,7 +110,9 @@ static void exec_cmd(const char *cmd) snprintf(buf, sizeof(buf), "%s %s", cmdscript, cmd); + upsdebugx(4, "%s: calling: %s", __func__, buf); err = system(buf); + upsdebugx(3, "%s(%s): returned %d", __func__, buf, err); #ifndef WIN32 if (WIFEXITED(err)) { if (WEXITSTATUS(err)) { @@ -120,6 +137,148 @@ static void exec_cmd(const char *cmd) return; } +/* Collect the list of strings into a "sep" (e.g. comma) separated string. + * If the return value is not NULL, caller should free() this. + * TODO: Generalize and move to common as some snprintfcatext()? + */ +static char* collect_string(char **string_arr, char *logtag, char *sep, size_t *pBufsize, size_t *pCount) +{ + size_t bufsize = SMALLBUF, prevlen = 0, count = 0; + char *buf = NULL, **ptr, *s; + int ret_printf; + + /* Do we have at least one non-trivial string there? */ + if (!string_arr || !(*string_arr) || !(**string_arr)) + return NULL; + + buf = xcalloc(bufsize, sizeof(char)); + if (!buf) { + upsdebugx(1, "%s: failed to allocate buffer, will not report any %s values", __func__, logtag); + return NULL; + } + + for (ptr = string_arr; ptr != NULL; ptr++) { + int retry; + + s = *ptr; + upsdebugx(5, "%s: popped '%s'", __func__, NUT_STRARG(s)); + + if (s == NULL || *s == '\0') + break; /*continue?*/ + + upsdebugx(4, "%s: appending '%s' to buffer %" PRIuSIZE "/%" PRIuSIZE " full", + __func__, s, prevlen, bufsize); + do { + retry = 0; + ret_printf = snprintf( + buf + prevlen, + bufsize - prevlen - 1, + "%s%s", + count ? (sep ? sep : ",") : "", + s); + upsdebugx(4, "%s: got %d after adding into buffer %" PRIuSIZE "/%" PRIuSIZE " full", + __func__, ret_printf, prevlen, bufsize); + buf[bufsize - 1] = '\0'; + + if (ret_printf < 0) { + upsdebugx(1, "%s: error collecting names, might not report all %s values", __func__, logtag); + buf[prevlen] = '\0'; + } else if ((size_t)ret_printf + prevlen >= bufsize) { + if (bufsize < SIZE_MAX - LARGEBUF) { + bufsize += LARGEBUF; + upsdebugx(1, "%s: buffer overflowed, trying to re-allocate as %" PRIuSIZE, __func__, bufsize); + buf = realloc(buf, bufsize); + + if (!buf) { + upsdebugx(1, "%s: buffer overflowed and failed to re-allocate, will not report any %s values", __func__, logtag); + return NULL; + } else { + upsdebugx(5, "%s: buffer overflowed, but re-allocated successfully - retrying", __func__); + /* Retry this loop */ + retry = 1; + } + } else { + upsdebugx(1, "%s: buffer overflowed, might not report all %s values", __func__, logtag); + buf[prevlen] = '\0'; + } + } else { + prevlen += (size_t)ret_printf; + } + } while (retry); + + count++; + } + + upsdebugx(3, "%s: collected %" PRIuSIZE " items into %" PRIuSIZE " bytes: %s", + __func__, count, bufsize, buf); + + if (pBufsize) + *pBufsize = bufsize; + + if (pCount) + *pCount = count; + + upsdebugx(5, "%s: returning", __func__); + return buf; +} + +static void exec_cmd_timer(ttype_t *item) +{ + char *upsnames = NULL, *notifytypes = NULL, *notifymsgs = NULL; + size_t upsnames_count = 0, notifytypes_count = 0, notifymsgs_count = 0; + + if (!item || !item->name || !(*(item->name))) { + upsdebugx(1, "%s: SKIP bad call with null arg or its command name", __func__); + return; + } + + /* Do we have at least one non-trivial string there? */ + if (item->upsnames && *(item->upsnames) && **(item->upsnames)) { + upsnames = collect_string(item->upsnames, "UPSNAME", ",", NULL, &upsnames_count); + } + + if (item->notifytypes && *(item->notifytypes) && **(item->notifytypes)) { + notifytypes = collect_string(item->notifytypes, "NOTIFYTYPE", ",", NULL, ¬ifytypes_count); + } + + if (item->notifymsgs && *(item->notifymsgs) && **(item->notifymsgs)) { + notifymsgs = collect_string(item->notifymsgs, "NOTIFYMSG", ".\t", NULL, ¬ifymsgs_count); + } + + if (upsnames) + setenv("UPSNAME", upsnames, 1); + + if (notifytypes) + setenv("NOTIFYTYPE", notifytypes, 1); + + if (notifymsgs) + setenv("NOTIFYMSG", notifymsgs, 1); + + if (nut_debug_level) + upslogx(LOG_INFO, "Executing command by timer: %s\t[%s]\t[%s]\t[%s]", + item->name, NUT_STRARG(notifytypes), NUT_STRARG(upsnames), NUT_STRARG(notifymsgs)); + exec_cmd(item->name); + upsdebugx(3, "%s: returned from exec_cmd()", __func__); + + /* Timer process should not retain random envvars */ + if (upsnames) { + unsetenv("UPSNAME"); + free(upsnames); + } + + if (notifytypes) { + unsetenv("NOTIFYTYPE"); + free(notifytypes); + } + + if (notifymsgs) { + unsetenv("NOTIFYMSG"); + free(notifymsgs); + } + + upsdebugx(3, "%s: done", __func__); +} + static void removetimer(ttype_t *tfind) { ttype_t *tmp, *last; @@ -129,11 +288,37 @@ static void removetimer(ttype_t *tfind) while (tmp) { if (tmp == tfind) { /* found it */ + upsdebugx(5, "%s: found %s", __func__, NUT_STRARG(tmp->name)); if (last == NULL) /* deleting first */ thead = tmp->next; else last->next = tmp->next; + if (tmp->upsnames) { + char **ps; + for (ps = tmp->upsnames; *ps != NULL; ps++) { + free(*ps); + } + free(tmp->upsnames); + } + + if (tmp->notifytypes) { + char **ps; + for (ps = tmp->notifytypes; *ps != NULL; ps++) { + free(*ps); + } + free(tmp->notifytypes); + } + + if (tmp->notifymsgs) { + char **ps; + for (ps = tmp->notifymsgs; *ps != NULL; ps++) { + free(*ps); + } + free(tmp->notifymsgs); + } + + upsdebugx(3, "%s: forgetting %s", __func__, tmp->name); free(tmp->name); free(tmp); return; @@ -154,6 +339,8 @@ static void checktimers(void) time_t now; static int emptyctr = 0; + upsdebugx(3, "%s: starting", __func__); + /* if the queue is empty we might be ready to exit */ if (!thead) { @@ -189,21 +376,25 @@ static void checktimers(void) if (nut_debug_level) upslogx(LOG_INFO, "Event: %s ", tmp->name); - exec_cmd(tmp->name); + exec_cmd_timer(tmp); /* delete from queue */ + upsdebugx(5, "%s: removing timer for the event just handled", __func__); removetimer(tmp); + upsdebugx(5, "%s: removed timer for the event just handled", __func__); } tmp = tmpnext; } + + upsdebugx(3, "%s: done", __func__); } -static void start_timer(const char *name, const char *ofsstr) +static void start_timer(const char *name, const char *ofsstr, const char *notifytype, const char *upsname, const char *notifymsg, int shared_timer) { time_t now; long ofs; - ttype_t *tmp, *last; + ttype_t *tmp, *last = NULL; /* get the time */ time(&now); @@ -216,42 +407,226 @@ static void start_timer(const char *name, const char *ofsstr) return; } + if (shared_timer) { + /* See if there is an older entry to attach to, + * otherwise fall through to creating a new one */ + tmp = last = thead; + upsdebugx(3, "%s: searching for existing timer named '%s' to share", __func__, name); + + while (tmp) { + if (tmp->name && !strcmp(tmp->name, name)) { + if (nut_debug_level) + upslogx(LOG_INFO, "Append data to shared timer: %s\t[%s]\t[%s]\t[%s]\t(will elapse in %g seconds)", + name, NUT_STRARG(notifytype), NUT_STRARG(upsname), NUT_STRARG(notifymsg), + difftime(tmp->etime, now)); + + /* FIXME? Consider only the first hit as the shared timer? + * Or check if there is already a copy with same name elsewhere? + */ + if (notifytype && *notifytype) { + if (tmp->notifytypes) { + char **ps = NULL; + size_t count = 0; /* amount of non-NULL entries, if we get to the end */ + + for (ps = tmp->notifytypes; *ps != NULL ; ps++) { + count++; + if (!strcmp(*ps, notifytype)) + break; + } + + if (*ps == NULL) { + tmp->notifytypes = xrealloc(tmp->notifytypes, count + 2); + tmp->notifytypes[count] = xstrdup(notifytype); + tmp->notifytypes[count + 1] = NULL; + } + } else { + tmp->notifytypes = xcalloc(2, sizeof(char*)); + tmp->notifytypes[0] = xstrdup(notifytype); + tmp->notifytypes[1] = NULL; + } + } + + if (notifymsg && *notifymsg) { + if (tmp->notifymsgs) { + char **ps = NULL; + size_t count = 0; /* amount of non-NULL entries, if we get to the end */ + + for (ps = tmp->notifymsgs; *ps != NULL ; ps++) { + count++; + if (!strcmp(*ps, notifymsg)) + break; + } + + if (*ps == NULL) { + tmp->notifymsgs = xrealloc(tmp->notifymsgs, count + 2); + tmp->notifymsgs[count] = xstrdup(notifymsg); + tmp->notifymsgs[count + 1] = NULL; + } + } else { + tmp->notifymsgs = xcalloc(2, sizeof(char*)); + tmp->notifymsgs[0] = xstrdup(notifymsg); + tmp->notifymsgs[1] = NULL; + } + } + + if (upsname && *upsname) { + if (tmp->upsnames) { + char **ps = NULL; + size_t count = 0; /* amount of non-NULL entries, if we get to the end */ + + for (ps = tmp->upsnames; *ps != NULL ; ps++) { + count++; + if (!strcmp(*ps, upsname)) + break; + } + + if (*ps == NULL) { + tmp->upsnames = xrealloc(tmp->upsnames, count + 2); + tmp->upsnames[count] = xstrdup(upsname); + tmp->upsnames[count + 1] = NULL; + } + } else { + tmp->upsnames = xcalloc(2, sizeof(char*)); + tmp->upsnames[0] = xstrdup(upsname); + tmp->upsnames[1] = NULL; + } + } + + return; + } + + /* Looping anyway, prepare for fall-through if we get to it */ + last = tmp; + tmp = tmp->next; + } + } + if (nut_debug_level) - upslogx(LOG_INFO, "New timer: %s (%ld seconds)", name, ofs); + upslogx(LOG_INFO, "New timer: %s\t[%s]\t[%s]\t[%s]\t(will elapse in %ld seconds)", + name, NUT_STRARG(notifytype), NUT_STRARG(upsname), NUT_STRARG(notifymsg), ofs); /* now add to the queue */ - tmp = last = thead; + if (!shared_timer) { + tmp = last = thead; - while (tmp) { - last = tmp; - tmp = tmp->next; - } + while (tmp) { + last = tmp; + tmp = tmp->next; + } + } /* else we already know */ tmp = xmalloc(sizeof(ttype_t)); tmp->name = xstrdup(name); tmp->etime = now + ofs; + tmp->notifytypes = NULL; + tmp->notifymsgs = NULL; + tmp->upsnames = NULL; tmp->next = NULL; + if (notifytype && *notifytype) { + tmp->notifytypes = xcalloc(2, sizeof(char*)); + tmp->notifytypes[0] = xstrdup(notifytype); + tmp->notifytypes[1] = NULL; + } + + if (notifymsg && *notifymsg) { + tmp->notifymsgs = xcalloc(2, sizeof(char*)); + tmp->notifymsgs[0] = xstrdup(notifymsg); + tmp->notifymsgs[1] = NULL; + } + + if (upsname && *upsname) { + tmp->upsnames = xcalloc(2, sizeof(char*)); + tmp->upsnames[0] = xstrdup(upsname); + tmp->upsnames[1] = NULL; + } + if (last) last->next = tmp; else thead = tmp; } -static void cancel_timer(const char *name, const char *cname) +static void cancel_timer(const char *name, const char *cname, const char *notifytype, const char *upsname, const char *notifymsg, int do_cancel_matched) { ttype_t *tmp; + size_t removed = 0; + + /* TOTHINK: Only cancel events associated with a particular UPS and/or type? */ + NUT_UNUSED_VARIABLE(notifytype); + NUT_UNUSED_VARIABLE(upsname); for (tmp = thead; tmp != NULL; tmp = tmp->next) { if (!strcmp(tmp->name, name)) { /* match */ - if (nut_debug_level) - upslogx(LOG_INFO, "Cancelling timer: %s", name); + /* Note we do not match "notifymsg" as it likely differs */ + if (!do_cancel_matched + || ( (!notifytype || !(*notifytype)) + && (!upsname || !(*upsname)) ) + ) { + upsdebugx(2, "%s: cancelling of timer %s not constrained by caller's NOTIFYTYPE nor UPSNAME", __func__, name); + } else { + char **ps = NULL; + int matched = 0; + + /* FIXME: Do not remove whole timer, just the respective strings? + * (Do drop the timer only if none remain) */ + if (notifytype) { + matched = 0; + if (!(tmp->notifytypes)) { + upsdebugx(2, "%s: do not cancel timer %s due to lack of NOTIFYTYPE in it", __func__, name); + continue; + } + for (ps = tmp->notifytypes; *ps != NULL ; ps++) { + if (!strcmp(*ps, notifytype)) { + matched = 1; + break; + } + } + if (!matched) { + upsdebugx(2, "%s: do not cancel timer %s due to mismatch vs. caller's NOTIFYTYPE", __func__, name); + continue; + } + } + + if (upsname) { + matched = 0; + if (!(tmp->upsnames)) { + upsdebugx(2, "%s: do not cancel timer %s due to lack of UPSNAME in it", __func__, name); + continue; + } + for (ps = tmp->upsnames; *ps != NULL ; ps++) { + if (!strcmp(*ps, upsname)) { + matched = 1; + break; + } + } + if (!matched) { + upsdebugx(2, "%s: do not cancel timer %s due to mismatch vs. caller's UPSNAME", __func__, name); + continue; + } + } + } + + if (nut_debug_level) { + if (notifymsg && *notifymsg) { + upslogx(LOG_INFO, "Cancelling timer: %s: %s", name, notifymsg); + } else { + upslogx(LOG_INFO, "Cancelling timer: %s", name); + } + } removetimer(tmp); - return; + removed++; + + /* Go on, we want to continue and cancel possibly many + * timers with this name (modulo constraints by envvars) */ } } + if (removed > 0) + return; - /* this is not necessarily an error */ + /* this is not necessarily an error: per docs, + * if the timer has passed then pass the optional argument cmd to CMDSCRIPT. + */ if (cname && cname[0]) { if (nut_debug_level) upslogx(LOG_INFO, "Cancel %s, event: %s", name, cname); @@ -373,6 +748,12 @@ static void conn_del(conn_t *target) { conn_t *tmp, *last = NULL; + if (!target) + return; + +#ifndef WIN32 + upsdebugx(3, "%s: closing connection %d", __func__, target->fd); +#endif tmp = connhead; while (tmp) { @@ -424,6 +805,8 @@ static int send_to_one(conn_t *conn, const char *fmt, ...) va_end(ap); buflen = strlen(buf); + upsdebugx(5, "%s: sending %" PRIuSIZE " bytes: [%s]", __func__, buflen, buf); + if (buflen >= SSIZE_MAX) { /* Can't compare buflen to ret */ upsdebugx(2, "send_to_one(): buffered message too large"); @@ -639,27 +1022,118 @@ static TYPE_FD conn_add(TYPE_FD sockfd) static int sock_arg(conn_t *conn) { + /* "Server-side" listener for the timer daemon */ if (conn->ctx.numargs < 1) return 0; - /* CANCEL [] */ - if (!strcmp(conn->ctx.arglist[0], "CANCEL")) { + /* LIST-TIMERS (no args expected now) + * returns a list with tab-separated values for: + * NAME TO_ABS TO_REL NOTIFYTYPES UPSNAMES NOTIFYMSGS_TABSEP + */ + if (!strcmp(conn->ctx.arglist[0], "LIST-TIMERS")) { + ttype_t *item = thead; + char *s = NULL; + time_t now; + + send_to_one(conn, "BEGIN LIST TIMERS\n"); + time(&now); + + while (item) { + if (item->name) { + send_to_one(conn, "%s\t%ld\t%g\t", + item->name, (long)item->etime, difftime(item->etime, now)); + + s = NULL; + if (item->notifytypes && *(item->notifytypes) && **(item->notifytypes)) { + s = collect_string(item->notifytypes, "NOTIFYTYPE", ",", NULL, NULL); + } - if (conn->ctx.numargs < 3) - cancel_timer(conn->ctx.arglist[1], NULL); - else - cancel_timer(conn->ctx.arglist[1], conn->ctx.arglist[2]); + if (s && *s) { + send_to_one(conn, "%s\t", s); + } else { + send_to_one(conn, "\"\"\t"); + } + if (s) { + free(s); + } - send_to_one(conn, "OK\n"); + s = NULL; + if (item->upsnames && *(item->upsnames) && **(item->upsnames)) { + s = collect_string(item->upsnames, "UPSNAME", ",", NULL, NULL); + } + + if (s && *s) { + send_to_one(conn, "%s\t", s); + } else { + send_to_one(conn, "\"\"\t"); + } + if (s) { + free(s); + } + + s = NULL; + if (item->notifymsgs && *(item->notifymsgs) && **(item->notifymsgs)) { + s = collect_string(item->notifymsgs, "NOTIFYMSG", ".\t", NULL, NULL); + } + + if (s && *s) { + send_to_one(conn, "%s\n", s); + } else { + send_to_one(conn, "\"\"\n"); + } + if (s) { + free(s); + } + } + item = item->next; + } + + send_to_one(conn, "END LIST TIMERS\n"); + send_to_one(conn, "OK\n\0"); return 1; } - if (conn->ctx.numargs < 3) + /* CANCEL [] [ ] */ + { /* scoping */ + int do_cancel = !strcmp(conn->ctx.arglist[0], "CANCEL"), + do_cancel_matched = !strcmp(conn->ctx.arglist[0], "CANCEL-MATCHED"); + + if (do_cancel || do_cancel_matched) { + /* "cmd" may be present and empty, this is handled in the method */ + if (conn->ctx.numargs < 3) + cancel_timer(conn->ctx.arglist[1], NULL, + NULL, NULL, NULL, do_cancel_matched); + else + if (conn->ctx.numargs < 6) + cancel_timer(conn->ctx.arglist[1], conn->ctx.arglist[2], + NULL, NULL, NULL, do_cancel_matched); + else + cancel_timer(conn->ctx.arglist[1], conn->ctx.arglist[2], + conn->ctx.arglist[3], conn->ctx.arglist[4], + conn->ctx.arglist[5], do_cancel_matched); + + send_to_one(conn, "OK\n"); + return 1; + } + } + + if (conn->ctx.numargs < 6) return 0; - /* START */ + /* START */ if (!strcmp(conn->ctx.arglist[0], "START")) { - start_timer(conn->ctx.arglist[1], conn->ctx.arglist[2]); + start_timer(conn->ctx.arglist[1], conn->ctx.arglist[2], + conn->ctx.arglist[3], conn->ctx.arglist[4], + conn->ctx.arglist[5], 0); + send_to_one(conn, "OK\n"); + return 1; + } + + /* START-SHARED */ + if (!strcmp(conn->ctx.arglist[0], "START-SHARED")) { + start_timer(conn->ctx.arglist[1], conn->ctx.arglist[2], + conn->ctx.arglist[3], conn->ctx.arglist[4], + conn->ctx.arglist[5], 1); send_to_one(conn, "OK\n"); return 1; } @@ -817,6 +1291,7 @@ static void start_daemon(TYPE_FD lockfd) } /* child */ + setproctag("timer"); /* make fds 0-2 (typically) point somewhere defined */ # ifdef HAVE_DUP2 @@ -896,6 +1371,7 @@ static void start_daemon(TYPE_FD lockfd) * CMDSCRIPT to run */ unsetenv("NOTIFYTYPE"); unsetenv("UPSNAME"); + unsetenv("NOTIFYMSG"); /* now watch for activity */ upsdebugx(2, "Timer daemon waiting for connections on pipefd %d", @@ -1007,6 +1483,7 @@ static void start_daemon(TYPE_FD lockfd) * CMDSCRIPT to run */ unsetenv("NOTIFYTYPE"); unsetenv("UPSNAME"); + unsetenv("NOTIFYMSG"); /* now watch for activity */ @@ -1068,6 +1545,12 @@ static void start_daemon(TYPE_FD lockfd) checktimers(); } #endif /* WIN32 */ + + /* Should not get here */ +/* + if (nut_debug_level) + upslogx(LOG_INFO, "Timer daemon ending"); +*/ } /* --- 'client' functions --- */ @@ -1184,7 +1667,7 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) int i; ssize_t ret; size_t enclen, buflen; - char buf[SMALLBUF], enc[SMALLBUF + 8]; + char buf[LARGEBUF], enc[LARGEBUF + 8]; #ifndef WIN32 int ret_s; struct timeval tv; @@ -1194,17 +1677,32 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) #endif /* WIN32 */ TYPE_FD pipefd; - /* insanity */ - if (!arg1) + /* sanity-check */ + if (!arg1 && !list_timers) return; - /* build the request */ + /* build the request + * note that in list-timers mode we may have no args to send, but + * otherwise we must have at least the timer name to start or cancel + */ snprintf(buf, sizeof(buf), "%s \"%s\"", - cmd, pconf_encode(arg1, enc, sizeof(enc))); + cmd, arg1 ? pconf_encode(arg1, enc, sizeof(enc)) : ""); + + snprintfcat(buf, sizeof(buf), " \"%s\"", + arg2 ? pconf_encode(arg2, enc, sizeof(enc)) : ""); + + /* use envvars set by caller (upsmon) when launching this client process + * (C variables are known not-null courtesy of main() method... except + * when we are in the list-timers mode). + */ + snprintfcat(buf, sizeof(buf), " \"%s\"", + notify_type ? pconf_encode(notify_type, enc, sizeof(enc)) : ""); - if (arg2) - snprintfcat(buf, sizeof(buf), " \"%s\"", - pconf_encode(arg2, enc, sizeof(enc))); + snprintfcat(buf, sizeof(buf), " \"%s\"", + ups_name? pconf_encode(ups_name, enc, sizeof(enc)) : ""); + + snprintfcat(buf, sizeof(buf), " \"%s\"", + notify_msg ? pconf_encode(notify_msg, enc, sizeof(enc)) : ""); snprintf(enc, sizeof(enc), "%s\n", buf); @@ -1216,21 +1714,29 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) fatalx(EXIT_FAILURE, "Unable to connect to daemon: buffered message too large"); } - /* see if the parent needs to be started (and maybe start it) */ - for (i = 0; i < MAX_TRIES; i++) { - pipefd = check_parent(cmd, arg2); + if (list_timers) { + pipefd = try_connect(); - if (pipefd == (TYPE_FD)PARENT_STARTED) { - /* loop back and try to connect now */ - usleep(250000); - continue; - } + if (INVALID_FD(pipefd)) { + upsdebugx(1, "%s: failed to use PIPEFN='%s'", __func__, NUT_STRARG(pipefn)); + fatalx(EXIT_FAILURE, "upssched timer is not running"); + } + } else { + /* see if the parent needs to be started (and maybe start it) */ + pipefd = check_parent(cmd, arg2); - /* special case for CANCEL when no parent is running */ - if (pipefd == (TYPE_FD)PARENT_UNNECESSARY) - return; + if (pipefd == (TYPE_FD)PARENT_STARTED) { + /* loop back and try to connect now */ + usleep(250000); + continue; + } + + /* special case for CANCEL when no parent is running */ + if (pipefd == (TYPE_FD)PARENT_UNNECESSARY) + return; + } /* we're connected now */ #ifndef WIN32 @@ -1238,9 +1744,15 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) /* if we can't send the whole thing, loop back and try again */ if ((ret < 1) || (ret != (ssize_t)enclen)) { - upslogx(LOG_ERR, "write failed, trying again"); - close(pipefd); - continue; + if (list_timers) { + upslogx(LOG_ERR, "write failed, daemon must have ended"); + close(pipefd); + break; + } else { + upslogx(LOG_ERR, "write failed, trying again"); + close(pipefd); + continue; + } } /* select on child's pipe fd */ @@ -1253,6 +1765,7 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) FD_SET(pipefd, &fdread); ret_s = select(pipefd + 1, &fdread, NULL, NULL, &tv); + upsdebugx(2, "%s: ret_s=%d", __func__, ret_s); switch(ret_s) { /* select error */ case -1: @@ -1265,7 +1778,45 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) /* available data to read */ default: - ret = read(pipefd, buf, sizeof(buf)); + memset (buf, 0, sizeof (buf)); + ret = read(pipefd, buf, sizeof(buf) - 1); + upsdebugx(2, "%s: ret=%" PRIiSIZE ": [%s]", __func__, ret, buf); + + if (list_timers && ret > 0) { + /* ASSUME we see whole starting and/or ending lines + * of the response within one read() operation + */ + char *end = strstr(buf, "END LIST TIMERS\n"), + *ok = strstr(buf, "OK\n"); + + /* Require to continue the reading loop */ + ret_s = -2; + + if (end) + *end = '\0'; + else { + end = strstr(buf, "\nEND"); + if (end) + *(end+1) = '\0'; + } + + if (ok) + *ok = '\0'; + + if (!strncmp(buf, "BEGIN LIST TIMERS\n", 18)) { + printf("%s", buf + 18); + } else { + printf("%s", buf); + } + + if (ok) { + ret = snprintf(buf, sizeof(buf), "%s", "OK\n\0"); + ret_s = 1; + } else { + /* Let more of the response accumulate in the buffer */ + usleep(250000); + } + } break; } } while (ret_s <= 0); @@ -1274,16 +1825,27 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) /* same idea: no OK = go try it all again */ if (ret < 2) { - upslogx(LOG_ERR, "read confirmation failed, trying again"); - continue; + if (list_timers) { + upslogx(LOG_ERR, "read confirmation failed, daemon must have ended"); + break; + } else { + upslogx(LOG_ERR, "read confirmation failed, trying again"); + continue; + } } #else /* WIN32 */ ret = WriteFile(pipefd, enc, enclen, &bytesWritten, NULL); if (ret == 0 || bytesWritten != enclen) { - upslogx(LOG_ERR, "write failed, trying again"); - CloseHandle(pipefd); - continue; + if (list_timers) { + upslogx(LOG_ERR, "write failed, daemon must have ended"); + CloseHandle(pipefd); + break; + } else { + upslogx(LOG_ERR, "write failed, trying again"); + CloseHandle(pipefd); + continue; + } } OVERLAPPED read_overlapped; @@ -1295,18 +1857,56 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) FALSE, /* auto-reset*/ FALSE, /* inital state = non signaled*/ NULL /* no name*/); - if(read_overlapped.hEvent == NULL ) { + if (read_overlapped.hEvent == NULL) { fatal_with_errno(EXIT_FAILURE, "Can't create event"); } - ReadFile(pipefd,buf,sizeof(buf)-1,NULL,&(read_overlapped)); + /* Accommodate reading of longer replies like the loop above; + * async (overlapped) read so not tracking bytesRead here, per + * https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile + */ + while (ReadFile(pipefd, buf, sizeof(buf)-1, NULL, &(read_overlapped))) { + ret = WaitForSingleObject(read_overlapped.hEvent, 2000); - ret = WaitForSingleObject(read_overlapped.hEvent,2000); + if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) { + if (list_timers) { + upslogx(LOG_ERR, "read confirmation failed, daemon must have ended"); + CloseHandle(pipefd); + break; + } else { + upslogx(LOG_ERR, "read confirmation failed, trying again"); + CloseHandle(pipefd); + continue; + } + } - if (ret == WAIT_TIMEOUT || ret == WAIT_FAILED) { - upslogx(LOG_ERR, "read confirmation failed, trying again"); - CloseHandle(pipefd); - continue; + if (buf[0] == '\0') + break; + + if (list_timers) { + /* ASSUME we see whole starting and/or ending lines + * of the response within one read() operation + */ + char *end = strstr(buf, "END LIST TIMERS\n"), + *ok = strstr(buf, "OK\n"); + + if (end) + *end = '\0'; + + if (ok) + *ok = '\0'; + + if (!strncmp(buf, "BEGIN LIST TIMERS\n", 18)) { + printf("%s", buf + 18); + } else { + printf("%s", buf); + } + + if (ok) { + snprintf(buf, sizeof(buf), "OK\n"); + break; + } + } } #endif /* WIN32 */ @@ -1315,9 +1915,15 @@ static void sendcmd(const char *cmd, const char *arg1, const char *arg2) upslogx(LOG_ERR, "read confirmation got [%s]", buf); + if (list_timers) + break; + /* try again ... */ } /* loop until MAX_TRIES if no success above */ + if (list_timers) + fatalx(EXIT_FAILURE, "Unable to connect to daemon or connection was broken during listing"); + fatalx(EXIT_FAILURE, "Unable to connect to daemon and unable to start daemon"); } @@ -1341,28 +1947,28 @@ static void parse_at(const char *ntype, const char *un, const char *cmd, fatalx(EXIT_FAILURE, "LOCKFN must be set before any ATs in the config file!"); } - /* check upsname: does this apply to us? */ - upsdebugx(2, "%s: is '%s' in AT command the '%s' we were launched to process?", - __func__, un, upsname); - if (strcmp(upsname, un) != 0) { + /* check ups_name: does this apply to us? */ + upsdebugx(3, "%s: is '%s' in AT command the '%s' we were launched to process?", + __func__, un, ups_name); + if (strcmp(ups_name, un) != 0) { if (strcmp(un, "*") != 0) { - upsdebugx(1, "%s: SKIP: '%s' in AT command " + upsdebugx(4, "%s: SKIP: '%s' in AT command " "did not match the '%s' UPSNAME " "we were launched to process", - __func__, un, upsname); + __func__, un, ups_name); return; /* not for us, and not the wildcard */ } else { - upsdebugx(1, "%s: this AT command is for a wildcard: matched", __func__); + upsdebugx(2, "%s: this AT command is for a wildcard: matched", __func__); } } else { - upsdebugx(1, "%s: '%s' in AT command matched the '%s' " + upsdebugx(2, "%s: '%s' in AT command matched the '%s' " "UPSNAME we were launched to process", - __func__, un, upsname); + __func__, un, ups_name); } /* see if the current notify type matches the one from the .conf */ if (strcasecmp(notify_type, ntype) != 0) { - upsdebugx(1, "%s: SKIP: '%s' in AT command " + upsdebugx(4, "%s: SKIP: '%s' in AT command " "did not match the '%s' NOTIFYTYPE " "we were launched to process", __func__, ntype, notify_type); @@ -1372,19 +1978,37 @@ static void parse_at(const char *ntype, const char *un, const char *cmd, /* if command is valid, send it to the daemon (which may start it) */ if (!strcmp(cmd, "START-TIMER")) { - upsdebugx(1, "%s: processing %s", __func__, cmd); + upsdebugx(1, "%s: processing %s\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]", __func__, cmd, + NUT_STRARG(ca1), NUT_STRARG(ca2), + NUT_STRARG(notify_type), NUT_STRARG(ups_name), + NUT_STRARG(notify_msg)); sendcmd("START", ca1, ca2); return; } + if (!strcmp(cmd, "START-TIMER-SHARED")) { + upsdebugx(1, "%s: processing %s\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]", __func__, cmd, + NUT_STRARG(ca1), NUT_STRARG(ca2), + NUT_STRARG(notify_type), NUT_STRARG(ups_name), + NUT_STRARG(notify_msg)); + sendcmd("START-SHARED", ca1, ca2); + return; + } + if (!strcmp(cmd, "CANCEL-TIMER")) { - upsdebugx(1, "%s: processing %s", __func__, cmd); + upsdebugx(1, "%s: processing %s\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]", __func__, cmd, + NUT_STRARG(ca1), NUT_STRARG(ca2), + NUT_STRARG(notify_type), NUT_STRARG(ups_name), + NUT_STRARG(notify_msg)); sendcmd("CANCEL", ca1, ca2); return; } if (!strcmp(cmd, "EXECUTE")) { - upsdebugx(1, "%s: processing %s", __func__, cmd); + upsdebugx(1, "%s: processing %s\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]", __func__, cmd, + NUT_STRARG(ca1), NUT_STRARG(ca2), + NUT_STRARG(notify_type), NUT_STRARG(ups_name), + NUT_STRARG(notify_msg)); if (ca1[0] == '\0') { upslogx(LOG_ERR, "Empty EXECUTE command argument"); @@ -1398,7 +2022,10 @@ static void parse_at(const char *ntype, const char *un, const char *cmd, return; } - upslogx(LOG_ERR, "Invalid command: %s", cmd); + upslogx(LOG_ERR, "Invalid command: %s\t[%s]\t[%s]\t[%s]\t[%s]\t[%s]", cmd, + NUT_STRARG(ca1), NUT_STRARG(ca2), + NUT_STRARG(notify_type), NUT_STRARG(ups_name), + NUT_STRARG(notify_msg)); } static int conf_arg(size_t numargs, char **arg) @@ -1412,6 +2039,17 @@ static int conf_arg(size_t numargs, char **arg) return 1; } + /* DEBUG_MIN */ + if (!strcmp(arg[0], "DEBUG_MIN")) { + if (str_to_int(arg[1], &nut_debug_level_conf, 10)) { + if (nut_debug_level_conf > nut_debug_level) { + nut_debug_level = nut_debug_level_conf; + upsdebugx(1, "Applying debug_min=%d from upssched.conf", nut_debug_level); + } + } + return 1; + } + /* PIPEFN */ if (!strcmp(arg[0], "PIPEFN")) { #ifndef WIN32 @@ -1432,6 +2070,9 @@ static int conf_arg(size_t numargs, char **arg) return 1; } + if (list_timers) + return 2; + if (numargs < 5) return 0; @@ -1482,6 +2123,11 @@ static void checkconf(void) if (ctx.numargs < 1) continue; + /* Note: for list_timers mode this only parses some config values + * that are of interest for communications with the timer daemon + * (e.g. PIPEFN), but for normal mode this also calls parse_at() + * and does the actual work, as/when relevant, line by line. + */ if (!conf_arg(ctx.numargs, ctx.arglist)) { unsigned int i; char errmsg[SMALLBUF]; @@ -1498,9 +2144,19 @@ static void checkconf(void) } } + if (list_timers) { + if (!pipefn || !(*pipefn)) { + fatalx(EXIT_FAILURE, "upssched.conf: invalid configuration for timer listing: lacks PIPEFN"); + } + + upsdebugx(1, "%s: processing LIST-TIMERS", __func__); + + /* Here the send() also handles the response for this use-case */ + sendcmd("LIST-TIMERS", NULL, NULL); + } /* FIXME: Per legacy behavior, we silently went on. - * Maybe should abort on unusable configs? + * Maybe should abort on unusable configs? */ if (numerrors) { upslogx(LOG_ERR, "Encountered %d config errors, those entries were ignored", numerrors); @@ -1517,10 +2173,11 @@ static void help(const char *arg_progname) printf("upssched: upsmon's scheduling helper for offset timers\n"); printf("Practical behavior is managed by UPSNAME and NOTIFYTYPE envvars\n"); - printf("\nUsage: %s [OPTIONS]\n\n", arg_progname); + printf("\nUsage: %s [OPTIONS] [NOTIFYMSG]\n\n", arg_progname); printf(" -D raise debugging level (NOTE: keeps reporting when daemonized)\n"); printf(" -V display the version of this software\n"); printf(" -h display this help\n"); + printf(" -l display currently pending timers (if any)\n"); nut_report_config_flags(); @@ -1529,20 +2186,20 @@ static void help(const char *arg_progname) exit(EXIT_SUCCESS); } - int main(int argc, char **argv) { - int i; + int i, argn = 0; if (argc > 0) prog = xbasename(argv[0]); if (!prog) prog = "upssched"; - while ((i = getopt(argc, argv, "+DVh")) != -1) { + while ((i = getopt(argc, argv, "+DVhl")) != -1) { + argn++; switch (i) { case 'D': - nut_debug_level++; + nut_debug_level_args++; break; case 'h': @@ -1551,6 +2208,10 @@ int main(int argc, char **argv) break; #endif + case 'l': + list_timers = 1; + break; + case 'V': /* just show the optional CONFIG_FLAGS banner */ nut_report_config_flags(); @@ -1563,15 +2224,15 @@ int main(int argc, char **argv) } } + nut_debug_level = nut_debug_level_args; { /* scoping */ char *s = getenv("NUT_DEBUG_LEVEL"); - int l; - if (s && str_to_int(s, &l, 10)) { - if (l > 0 && nut_debug_level < 1) { + if (s && str_to_int(s, &nut_debug_level_env, 10)) { + if (nut_debug_level_env > 0 && nut_debug_level_args < 1) { upslogx(LOG_INFO, "Defaulting debug verbosity to NUT_DEBUG_LEVEL=%d " - "since none was requested by command-line options", l); - nut_debug_level = l; - } /* else follow -D settings */ + "since none was requested by command-line options", nut_debug_level_env); + nut_debug_level = nut_debug_level_env; + } /* else follow -D and/or upssched.conf DEBUG_MIN settings */ } /* else nothing to bother about */ } @@ -1579,20 +2240,30 @@ int main(int argc, char **argv) open_syslog(prog); syslogbit_set(); - upsname = getenv("UPSNAME"); + ups_name = getenv("UPSNAME"); notify_type = getenv("NOTIFYTYPE"); + upsdebugx(2, "Remaining argn=%d of argc=%d", argn, argc); + if (argc > argn + 1 && *argv[argn + 1]) + notify_msg = argv[argn + 1]; - if ((!upsname) || (!notify_type)) { + if ((!list_timers) && ((!ups_name) || (!notify_type))) { printf("Error: environment variables UPSNAME and NOTIFYTYPE must be set.\n"); - printf("This program should only be run from upsmon.\n"); + printf("This program should only be run from upsmon(%s).\n", MAN_SECTION_CMD_SYS); exit(EXIT_FAILURE); } + upsdebugx(1, "Handling NOTIFYTYPE='%s' for UPSNAME='%s'", notify_type, ups_name); + if (notify_msg) + upsdebugx(1, "Got a NOTIFYMSG from command line: %s", notify_msg); + else + upsdebugx(1, "Did not get any NOTIFYMSG from command line"); + /* see if this matches anything in the config file */ /* This is actually the processing loop: * checkconf -> conf_arg -> parse_at -> sendcmd -> daemon if needed * -> start_daemon -> conn_add(pipefd) or sock_read(conn) */ + setproctag("cli"); checkconf(); upsdebugx(1, "Exiting upssched (CLI process)"); diff --git a/clients/upsset.c b/clients/upsset.c index 741d9cd57e..ebfb2a5e91 100644 --- a/clients/upsset.c +++ b/clients/upsset.c @@ -770,6 +770,19 @@ static void do_type(const char *varname) return; } + if (!strcasecmp(answer[i], "NUMBER")) { + /* 20 is a reasonable default size for the text box */ + do_string(varname, 20); + return; + } + + /* RANGE is usually paired with NUMBER. + We can ignore 'RANGE' and let the 'NUMBER' + case (which should come next) handle it. */ + if (!strcasecmp(answer[i], "RANGE")) { + continue; + } + /* ignore this one */ if (!strcasecmp(answer[i], "RW")) continue; diff --git a/clients/upsstats.c b/clients/upsstats.c index 32c93055fb..06a341a0f8 100644 --- a/clients/upsstats.c +++ b/clients/upsstats.c @@ -23,6 +23,7 @@ #include "timehead.h" #include "upsclient.h" #include "status.h" +#include "strjson.h" #include "cgilib.h" #include "parseconf.h" #include "upsstats.h" @@ -36,6 +37,7 @@ static char *monhost = NULL; static int use_celsius = 1, refreshdelay = -1, treemode = 0; +static int output_json = 0; /* from cgilib's checkhost() */ static char *monhostdesc = NULL; @@ -71,6 +73,10 @@ void parsearg(char *var, char *value) /* FIXME: Validate that treemode is allowed */ treemode = 1; } + + if (!strcmp(var, "json")) { + output_json = 1; + } } static void report_error(void) @@ -111,7 +117,8 @@ static int get_var(const char *var, char *buf, size_t buflen, int verbose) const char *query[4]; char **answer; - if (!check_ups_fd(1)) + /* pass verbose to check_ups_fd */ + if (!check_ups_fd(verbose)) return 0; if (!upsname) { @@ -354,13 +361,13 @@ static void ups_connect(void) { static ulist_t *lastups = NULL; char *newups, *newhost; - uint16_t newport; + uint16_t newport = 0; /* try to minimize reconnects */ if (lastups) { /* don't reconnect if these are both the same UPS */ - if (!strcmp(lastups->sys, currups->sys)) { + if (currups && !strcmp(lastups->sys, currups->sys)) { lastups = currups; return; } @@ -368,7 +375,7 @@ static void ups_connect(void) /* see if it's just on the same host */ newups = newhost = NULL; - if (upscli_splitname(currups->sys, &newups, &newhost, + if (currups && upscli_splitname(currups->sys, &newups, &newhost, &newport) != 0) { printf("Unusable UPS definition [%s]\n", currups->sys); fprintf(stderr, "Unusable UPS definition [%s]\n", @@ -376,7 +383,7 @@ static void ups_connect(void) exit(EXIT_FAILURE); } - if ((!strcmp(newhost, hostname)) && (port == newport)) { + if (currups && hostname && (!strcmp(newhost, hostname)) && (port == newport)) { free(upsname); upsname = newups; @@ -394,14 +401,16 @@ static void ups_connect(void) free(upsname); free(hostname); + upsname = NULL; + hostname = NULL; - if (upscli_splitname(currups->sys, &upsname, &hostname, &port) != 0) { + if (currups && upscli_splitname(currups->sys, &upsname, &hostname, &port) != 0) { printf("Unusable UPS definition [%s]\n", currups->sys); fprintf(stderr, "Unusable UPS definition [%s]\n", currups->sys); exit(EXIT_FAILURE); } - if (upscli_connect(&ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0) + if (currups && upscli_connect(&ups, hostname, port, UPSCLI_CONN_TRYSSL) < 0) fprintf(stderr, "UPS [%s]: can't connect to server: %s\n", currups->sys, upscli_strerror(&ups)); lastups = currups; @@ -597,7 +606,7 @@ static void do_degrees(void) static void do_statuscolor(void) { int severity, i; - char stat[SMALLBUF], *sp, *ptr; + char stat[SMALLBUF], *ptr, *last = NULL; if (!check_ups_fd(0)) { @@ -613,20 +622,15 @@ static void do_statuscolor(void) } severity = 0; - sp = stat; - while (sp) { - ptr = strchr(sp, ' '); - if (ptr) - *ptr++ = '\0'; + /* Use strtok_r for safety */ + for (ptr = strtok_r(stat, " \n", &last); ptr != NULL; ptr = strtok_r(NULL, " \n", &last)) { /* expand from table in status.h */ for (i = 0; stattab[i].name != NULL; i++) - if (!strcmp(stattab[i].name, sp)) + if (stattab[i].name && ptr && !strcmp(stattab[i].name, ptr)) if (stattab[i].severity > severity) severity = stattab[i].severity; - - sp = ptr; } switch(severity) { @@ -978,13 +982,18 @@ static void load_hosts_conf(void) if (!pconf_file_begin(&ctx, fn)) { pconf_finish(&ctx); - printf("\n"); - printf("\n"); - printf("Error: can't open hosts.conf\n"); - printf("\n"); - printf("Error: can't open hosts.conf\n"); - printf("\n"); + /* Don't print HTML here if we are in JSON mode. + * The JSON function will handle the error. + */ + if (!output_json) { + printf("\n"); + printf("\n"); + printf("Error: can't open hosts.conf\n"); + printf("\n"); + printf("Error: can't open hosts.conf\n"); + printf("\n"); + } /* leave something for the admin */ fprintf(stderr, "upsstats: %s\n", ctx.errmsg); @@ -1010,13 +1019,18 @@ static void load_hosts_conf(void) pconf_finish(&ctx); if (!ulhead) { - printf("\n"); - printf("\n"); - printf("Error: no hosts to monitor\n"); - printf("\n"); - printf("Error: no hosts to monitor (check hosts.conf)\n"); - printf("\n"); + /* Don't print HTML here if we are in JSON mode. + * The JSON function will handle the error. + */ + if (!output_json) { + printf("\n"); + printf("\n"); + printf("Error: no hosts to monitor\n"); + printf("\n"); + printf("Error: no hosts to monitor (check hosts.conf)\n"); + printf("\n"); + } /* leave something for the admin */ fprintf(stderr, "upsstats: no hosts to monitor\n"); @@ -1046,6 +1060,151 @@ static void display_single(void) upscli_disconnect(&ups); } +/* ------------------------------------------------------------- */ +/* ---NEW FUNCTION FOR JSON API -------------------------------- */ +/* ------------------------------------------------------------- */ + +/** + * @brief Main JSON output function. + * This function replaces all template logic and outputs a JSON object + * containing data for one or all devices. + */ +static void display_json(void) +{ + size_t numq, numa; + const char *query[4]; + char **answer; + char status_buf[SMALLBUF], status_copy[SMALLBUF]; + int i; + int is_first_status; + int is_first_ups = 1; + int is_first_var = 1; + char *ptr, *last = NULL; + + /* If monhost is set, we're in single-host mode. + * If not, we're in multi-host mode. + * We need to load hosts.conf ONLY in multi-host mode. + */ + if (monhost) { + if (!checkhost(monhost, &monhostdesc)) { + printf("{\"error\": \"Access to host %s is not authorized.\"}", monhost); + return; + } + add_ups(monhost, monhostdesc); + currups = ulhead; + } else { + load_hosts_conf(); /* This populates ulhead */ + currups = ulhead; + } + + if (!currups) { + /* load_hosts_conf() would have exited, but check anyway */ + printf("{\"error\": \"No hosts to monitor.\"}"); + return; + } + + /* In multi-host mode, wrap in a root object. + * In single-host mode, just output the single device object. + */ + if (!monhost) { + printf("{\"devices\": [\n"); + } + + /* Loop through all devices (in single-host mode, this is just one) */ + for (currups = ulhead; currups != NULL; currups = currups->next) { + ups_connect(); + + if (!is_first_ups) printf(",\n"); + + if (upscli_fd(&ups) == -1) { + printf(" {\"host\": \""); + json_print_esc(currups->sys); + printf("\", \"desc\": \""); + json_print_esc(currups->desc); + printf("\", \"error\": \"Connection failed: %s\"}", upscli_strerror(&ups)); + is_first_ups = 0; + continue; + } + + printf(" {\n"); /* Start UPS object */ + printf(" \"host\": \""); + json_print_esc(currups->sys); + printf("\",\n"); + printf(" \"desc\": \""); + json_print_esc(currups->desc); + printf("\",\n"); + + /* Add pre-processed status, as the old template did */ + if (get_var("ups.status", status_buf, sizeof(status_buf), 0)) { + printf(" \"status_raw\": \""); + json_print_esc(status_buf); + printf("\",\n"); + printf(" \"status_parsed\": ["); + + is_first_status = 1; + /* Copy status_buf as strtok_r is destructive */ + strncpy(status_copy, status_buf, sizeof(status_copy)); + status_copy[sizeof(status_copy) - 1] = '\0'; + + for (ptr = strtok_r(status_copy, " \n", &last); ptr != NULL; ptr = strtok_r(NULL, " \n", &last)) { + for (i = 0; stattab[i].name != NULL; i++) { + if (stattab[i].name && ptr && !strcasecmp(stattab[i].name, ptr)) { + if (!is_first_status) printf(", "); + printf("\""); + json_print_esc(stattab[i].desc); + printf("\""); + is_first_status = 0; + } + } + } + printf("],\n"); + } + + printf(" \"vars\": {\n"); /* Start vars object */ + is_first_var = 1; + + /* Full tree mode: list all variables */ + query[0] = "VAR"; + query[1] = upsname; + numq = 2; + + if (upscli_list_start(&ups, numq, query) < 0) { + printf(" \"error\": \"Failed to list variables: %s\"", upscli_strerror(&ups)); + } else { + while (upscli_list_next(&ups, numq, query, &numa, &answer) == 1) { + if (numa < 4) continue; /* Invalid response */ + + if (!is_first_var) printf(",\n"); + + printf(" \""); + json_print_esc(answer[2]); /* var name */ + printf("\": \""); + json_print_esc(answer[3]); /* var value */ + printf("\""); + + is_first_var = 0; + } + } + + printf("\n }\n"); /* End vars object */ + printf(" }"); /* End UPS object */ + + is_first_ups = 0; + upscli_disconnect(&ups); /* Disconnect after each UPS */ + } + + /* Close the root object in multi-host mode */ + if (!monhost) { + printf("\n]}\n"); + } +} + + +/* ------------------------------------------------------------- */ +/* --- END: NEW JSON FUNCTION ---------------------------------- */ +/* ------------------------------------------------------------- */ + + int main(int argc, char **argv) { NUT_UNUSED_VARIABLE(argc); @@ -1055,6 +1214,33 @@ int main(int argc, char **argv) upscli_init_default_connect_timeout(NULL, NULL, UPSCLI_DEFAULT_CONNECT_TIMEOUT); + /* + * If json is in the query, bypass all HTML and call display_json() + */ + if (output_json) { + printf("Content-type: application/json; charset=utf-8\n"); + printf("Pragma: no-cache\n"); + printf("\n"); + + display_json(); + + /* Clean up memory */ + free(monhost); + while (ulhead) { + currups = ulhead->next; + free(ulhead->sys); + free(ulhead->desc); + free(ulhead); + ulhead = currups; + } + free(upsname); + free(hostname); + + exit(EXIT_SUCCESS); + } + + /* --- Original HTML logic continues below --- */ + printf("Content-type: text/html\n"); printf("Pragma: no-cache\n"); printf("\n"); @@ -1062,18 +1248,25 @@ int main(int argc, char **argv) /* if a host is specified, use upsstats-single.html instead */ if (monhost) { display_single(); - exit(EXIT_SUCCESS); + } else { + /* default: multimon replacement mode */ + load_hosts_conf(); + currups = ulhead; + display_template("upsstats.html"); } - /* default: multimon replacement mode */ - - load_hosts_conf(); - - currups = ulhead; - - display_template("upsstats.html"); - + /* Clean up memory */ + free(monhost); upscli_disconnect(&ups); + free(upsname); + free(hostname); + while (ulhead) { + currups = ulhead->next; + free(ulhead->sys); + free(ulhead->desc); + free(ulhead); + ulhead = currups; + } return 0; } diff --git a/common/Makefile.am b/common/Makefile.am index d657b3c832..93de4dd670 100644 --- a/common/Makefile.am +++ b/common/Makefile.am @@ -76,6 +76,10 @@ libcommonstr_la_SOURCES = str.c libcommonstr_la_CFLAGS = $(AM_CFLAGS) -DWITHOUT_LIBSYSTEMD=1 libcommonstr_la_LIBADD = @LTLIBOBJS@ @BSDKVMPROCLIBS@ +# Common methods for JSON outputs from NUT programs +noinst_LTLIBRARIES += libcommonstrjson.la +libcommonstrjson_la_SOURCES = strjson.c + # This one includes only version-related data and methods and should be # linked along with one of the other libcommon*.la for logging methods: noinst_LTLIBRARIES += libcommonversion.la diff --git a/common/common.c b/common/common.c index 281646ff17..83ea99dff5 100644 --- a/common/common.c +++ b/common/common.c @@ -451,6 +451,15 @@ static int upsnotify_reported_disabled_systemd = 0; static int upsnotify_reported_disabled_notech = 0; static int upsnotify_report_verbosity = -1; +/* Exposed to code consumers (NUT daemons) for NOTIFY_STATE_EXTEND_TIMEOUT */ +uint64_t upsnotify_extend_timeout_usec_default = 600 * 1000000, + /* By default upsnotify_extend_timeout_usec == 0 so the + * upsnotify() method would fall back to current WATCHDOG_USEC + * if available, or to upsnotify_extend_timeout_usec_default. + * Whatever value gets applied, it should exceed the relevant + * loop cycle duration at that point in daemon life time. */ + upsnotify_extend_timeout_usec = 0; + #include /* Know which bitness we were built for, @@ -876,6 +885,40 @@ void chroot_start(const char *path) #endif /* !WIN32 */ } +/* In forking, assume process name does not change (PID might); cache it */ +static char *myProcName = NULL; +static const char *myProcBaseName = NULL; +static void procname_cleanup(void) { + if (myProcBaseName) { + /* points to inside of myProcName */ + myProcBaseName = NULL; + } + if (myProcName) { + free(myProcName); + myProcName = NULL; + } +} + +static const char * getmyprocname(void) +{ + if (myProcName) + return (const char *)myProcName; + + myProcName = getprocname(getpid()); /* no xstrdup, we own and free this later */ + if (myProcName) { + myProcBaseName = xbasename(myProcName); /* substring inside myProcName */ + atexit(procname_cleanup); + } + + return (const char *)myProcName; +} + +static const char * getmyprocbasename(void) +{ + getmyprocname(); + return myProcBaseName; +} + char * getprocname(pid_t pid) { /* Try to identify process (program) name for the given PID, @@ -1326,9 +1369,22 @@ int checkprocname_ignored(const char *caller) } int compareprocname(pid_t pid, const char *procname, const char *progname) +{ + /* See comments for compareprocnames(), below */ + const char *arr[2]; + + arr[0] = progname; + arr[1] = NULL; + + return compareprocnames(pid, procname, arr); +} + +int compareprocnames(pid_t pid, const char *procname, const char **prognames) { /* Given the binary path name of (presumably) a running process, - * check if it matches the assumed name of the current program. + * check if it matches the assumed name of the current program, + * possibly one of several expected aliases (for "old"/"new" name + * migrations, etc.) * The "pid" value is used in log reporting. * Returns: * -3 Skipped because NUT_IGNORE_CHECKPROCNAME is set @@ -1343,67 +1399,144 @@ int compareprocname(pid_t pid, const char *procname, const char *progname) */ int ret = -127; - size_t procbasenamelen = 0, progbasenamelen = 0; + size_t procbasenamelen = 0, *progbasenamelen = NULL; /* Track where the last dot is in the basename; 0 means none */ - size_t procbasenamedot = 0, progbasenamedot = 0; - char procbasename[NUT_PATH_MAX + 1], progbasename[NUT_PATH_MAX + 1]; + size_t procbasenamedot = 0, *progbasenamedot = NULL; + char procbasename[NUT_PATH_MAX + 1], **progbasenames = NULL; + /* Best-effort (size-wise) for logging in the end: */ + char all_prognames[LARGEBUF], all_progbasenames[LARGEBUF]; #ifdef NUT_PLATFORM_LINUX char *s = NULL; #endif + const char **pprogname = NULL; + size_t total_prognames = 0, i = 0; if (checkprocname_ignored(__func__)) { ret = -3; goto finish; } - if (!procname || !progname) { + if (!procname || !prognames || !(*prognames)) { ret = -1; goto finish; } + /* Prepare the (potentially) long string listing the names for logging. + * Also count the prognames[] array length to allocate progbasenames[]. + */ + memset(all_prognames, 0, sizeof(all_prognames)); + for (pprogname = prognames; *pprogname != NULL; pprogname++) { + upsdebugx(5, "%s: appending progname [%s] to [%s]", + __func__, NUT_STRARG(*pprogname), all_prognames); + snprintfcat(all_prognames, sizeof(all_prognames), "%s'%s'", + all_prognames[0] ? "/" : "", + *pprogname); + total_prognames++; + } + /* include the NULL sentinel */ + total_prognames++; + upsdebugx(4, "%s: total_prognames=%" PRIuSIZE " (with NULL sentinel); got [%s]", + __func__, total_prognames, all_prognames); + /* First quickly try for an exact hit (possible dir names included) */ - if (!strcmp(procname, progname)) { - ret = 1; + for (i = 0; i < total_prognames - 1; i++) { + if (!strcmp(procname, prognames[i])) { + ret = 1; + goto finish; + } + } + + /* Parse the basenames apart, or fail trying */ + if (!parseprogbasename(procbasename, sizeof(procbasename), procname, &procbasenamelen, &procbasenamedot)) { + ret = -2; goto finish; } - /* Parse the basenames apart */ - if (!parseprogbasename(progbasename, sizeof(progbasename), progname, &progbasenamelen, &progbasenamedot) - || !parseprogbasename(procbasename, sizeof(procbasename), procname, &procbasenamelen, &procbasenamedot) - ) { + progbasenames = xcalloc(total_prognames, sizeof(char*)); + if (!progbasenames) { + ret = -2; + goto finish; + } + progbasenamelen = xcalloc(total_prognames, sizeof(size_t)); + if (!progbasenamelen) { + ret = -2; + goto finish; + } + progbasenamedot = xcalloc(total_prognames, sizeof(size_t)); + if (!progbasenamedot) { ret = -2; goto finish; } + memset(all_progbasenames, 0, sizeof(all_progbasenames)); + + for (i = 0; i < total_prognames - 1; i++) { + progbasenames[i] = xcalloc(NUT_PATH_MAX + 1, sizeof(char)); + + if (!progbasenames[i]) { + ret = -2; + goto finish; + } + + if (!parseprogbasename( + progbasenames[i], NUT_PATH_MAX + 1, + prognames[i], + &(progbasenamelen[i]), &(progbasenamedot[i])) + ) { + ret = -2; + goto finish; + } + + upsdebugx(5, "%s: appending progbasename [%s] to [%s]", + __func__, NUT_STRARG(progbasenames[i]), all_progbasenames); + snprintfcat(all_progbasenames, sizeof(all_progbasenames), "%s'%s'", + all_progbasenames[0] ? "/" : "", + progbasenames[i]); + } /* First quickly try for an exact hit of base names */ - if (progbasenamelen == procbasenamelen && progbasenamedot == procbasenamedot && !strcmp(procbasename, progbasename)) { - ret = 2; - goto finish; + for (i = 0; i < total_prognames - 1; i++) { + if (progbasenamelen[i] == procbasenamelen + && progbasenamedot[i] == procbasenamedot + && !strcmp(procbasename, progbasenames[i]) + ) { + 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; + for (i = 0; i < total_prognames - 1; i++) { + if (!strcasecmp(procname, prognames[i])) { + ret = 3; + goto finish; + } } - if (!strcasecmp(procbasename, progbasename)) { - ret = 4; - goto finish; + for (i = 0; i < total_prognames - 1; i++) { + if (!strcasecmp(procbasename, progbasenames[i])) { + 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")) ) + for (i = 0; i < total_prognames - 1; i++) { + if (progbasenamedot[i] == procbasenamedot + || !progbasenamedot[i] + || !procbasenamedot ) { - ret = 5; - goto finish; + /* Same base name before ext, maybe different casing or absence of ext in one of them */ + size_t dot = progbasenamedot[i] ? progbasenamedot[i] : procbasenamedot; + + /* Optimize away procbasename comparisons beyond first */ + if (!strncasecmp(progbasenames[i], procbasename, dot - 1) && + ( (progbasenamedot[i] && !strcasecmp(progbasenames[i] + progbasenamedot[i], ".exe")) + || (i == 0 && procbasenamedot && !strcasecmp(procbasename + procbasenamedot, ".exe")) ) + ) { + ret = 5; + goto finish; + } } } #endif /* WIN32 */ @@ -1423,9 +1556,12 @@ int compareprocname(pid_t pid, const char *procname, const char *progname) upsdebugx(2, "%s: re-evaluate the substring without a special tail: '%s'=>'%s'", __func__, procname, pntmp); - restmp = compareprocname(pid, pntmp, progname); - if (restmp <= 0) - return restmp; + + for (i = 0; i < total_prognames - 1; i++) { + restmp = compareprocname(pid, pntmp, prognames[i]); + if (restmp <= 0) + return restmp; + } ret = 6; goto finish; @@ -1447,9 +1583,9 @@ int compareprocname(pid_t pid, const char *procname, const char *progname) upsdebugx(1, "%s: original program file of running " "PID %" PRIuMAX " named '%s' was removed " - "or replaced, but matches our '%s'", + "or replaced, but matches our %s", __func__, (uintmax_t)pid, - procname, progname); + procname, all_prognames); break; case 5: @@ -1457,52 +1593,52 @@ int compareprocname(pid_t pid, const char *procname, const char *progname) "%s: case-insensitive base name hit with " "an executable program extension involved for " "PID %" PRIuMAX " of '%s'=>'%s' and checked " - "'%s'=>'%s'", + "%s=>%s", __func__, (uintmax_t)pid, procname, procbasename, - progname, progbasename); + all_prognames, all_progbasenames); break; case 4: upsdebugx(1, "%s: case-insensitive base name hit for PID %" - PRIuMAX " of '%s'=>'%s' and checked '%s'=>'%s'", + PRIuMAX " of '%s'=>'%s' and checked %s=>%s", __func__, (uintmax_t)pid, procname, procbasename, - progname, progbasename); + all_prognames, all_progbasenames); break; case 3: upsdebugx(1, "%s: case-insensitive full name hit for PID %" - PRIuMAX " of '%s' and checked '%s'", - __func__, (uintmax_t)pid, procname, progname); + PRIuMAX " of '%s' and checked %s", + __func__, (uintmax_t)pid, procname, all_prognames); break; case 2: upsdebugx(1, "%s: case-sensitive base name hit for PID %" - PRIuMAX " of '%s'=>'%s' and checked '%s'=>'%s'", + PRIuMAX " of '%s'=>'%s' and checked %s=>%s", __func__, (uintmax_t)pid, procname, procbasename, - progname, progbasename); + all_prognames, all_progbasenames); 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); + PRIuMAX " of '%s' and checked %s", + __func__, (uintmax_t)pid, procname, all_prognames); break; case 0: upsdebugx(1, "%s: did not find any match of program names " "for PID %" PRIuMAX " of '%s'=>'%s' and checked " - "'%s'=>'%s'", + "%s=>%s", __func__, (uintmax_t)pid, procname, procbasename, - progname, progbasename); + all_prognames, all_progbasenames); break; case -1: @@ -1528,13 +1664,44 @@ int compareprocname(pid_t pid, const char *procname, const char *progname) break; } + if (progbasenames) { + /* In case of error, we might not have the complete + * array's worth allocated here, so iterate until NULL + * and not by counter. + */ + char **pprogbasename = NULL; + for (pprogbasename = progbasenames; *pprogbasename != NULL; pprogbasename++) { + free(*pprogbasename); + } + free(progbasenames); + } + + if (progbasenamelen) + free(progbasenamelen); + + if (progbasenamedot) + free(progbasenamedot); + return ret; } int checkprocname(pid_t pid, const char *progname) +{ + /* See comments for checkprocnames(), below */ + const char *arr[2]; + + arr[0] = progname; + arr[1] = NULL; + + return checkprocnames(pid, arr); +} + +int checkprocnames(pid_t pid, const char **prognames) { /* If we can determine the binary path name of the specified "pid", - * check if it matches the assumed name of the current program. + * check if it matches the assumed name of the current program, + * possibly one of several expected aliases (for "old"/"new" name + * migrations, etc.) * Returns: same as compareprocname() * Generally speaking, if (checkprocname(...)) then ok to proceed */ @@ -1547,18 +1714,24 @@ int checkprocname(pid_t pid, const char *progname) goto finish; } - if (!progname) { + if (!prognames || !(*prognames)) { ret = -1; goto finish; } - procname = getprocname(pid); + if (pid == getpid()) { + /* use (or seed) the cached value if we can */ + procname = xstrdup(getmyprocname()); + } + if (!procname) { + procname = getprocname(pid); + } if (!procname) { ret = -1; goto finish; } - ret = compareprocname(pid, procname, progname); + ret = compareprocnames(pid, procname, prognames); finish: if (procname) @@ -1633,10 +1806,23 @@ void writepid(const char *name) * zero for a successfully sent signal */ int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_progname) +{ + const char *arr[2]; + + arr[0] = progname; + arr[1] = NULL; + + return sendsignalpidaliases(pid, sig, arr, check_current_progname); +} + +int sendsignalpidaliases(pid_t pid, int sig, const char **prognames, int check_current_progname) { #ifndef WIN32 int ret, cpn1 = -10, cpn2 = -10; - char *current_progname = NULL, *procname = NULL; + char *procname = NULL; /* free this one after use */ + const char *current_progname = NULL; /* do not free this one! */ + char all_prognames[LARGEBUF]; /* for nicer logging */ + size_t total_prognames = 0; /* TOTHINK: What about containers where a NUT daemon *is* the only process * and is the PID=1 of the container (recycle if dead)? */ @@ -1649,30 +1835,48 @@ int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_pr } ret = 0; - if (!checkprocname_ignored(__func__)) - procname = getprocname(pid); + if (!checkprocname_ignored(__func__)) { + if (pid == getpid()) { + /* use (or seed) the cached value if we can */ + procname = xstrdup(getmyprocname()); + } + if (!procname) { + 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; + memset(all_prognames, 0, sizeof(all_prognames)); + if (procname && prognames && *(prognames)) { + /* Check against some expected (often built-in) name + * Also build a list of those names (from parameter) + */ + const char **pprogname; + for (pprogname = prognames; *pprogname != NULL; pprogname++) { + snprintfcat(all_prognames, sizeof(all_prognames), "%s'%s'", + all_prognames[0] ? "/" : "", *pprogname); + if (!(cpn1 = compareprocname(pid, procname, *pprogname))) { + /* 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 */ } - /* ...else could not determine name of PID; think later */ + total_prognames++; } + /* NULL sentinel of the non-NULL prognames[] array */ + total_prognames++; + + upsdebugx(4, "%s: collected %" PRIuSIZE " aliases (with NULL sentinel): %s", + __func__, total_prognames, all_prognames); } + /* 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()); + current_progname = getmyprocname(); if (current_progname && (cpn2 = compareprocname(pid, procname, current_progname))) { if (cpn2 > 0) { /* Matched current process as asked, ok to proceed */ @@ -1701,10 +1905,10 @@ int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_pr "failed to match: " "found procname='%s', " "expected progname='%s' (res=%d%s), " - "current progname='%s' (res=%d%s)", + "current progname=%s (res=%d%s)", __func__, (uintmax_t)pid, NUT_STRARG(procname), - NUT_STRARG(progname), cpn1, + all_prognames[0] ? all_prognames : "(null)", cpn1, (cpn1 == -10 ? ": did not check" : ""), NUT_STRARG(current_progname), cpn2, (cpn2 == -10 ? ": did not check" : "")); @@ -1714,32 +1918,32 @@ int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_pr case -1: upslogx(LOG_ERR, "Tried to signal PID %" PRIuMAX " which exists but is not of" - " expected program '%s'; not asked" + " expected program %s; not asked" " to cross-check current PID's name", - (uintmax_t)pid, progname); + (uintmax_t)pid, all_prognames); break; /* Maybe we tried both data sources, maybe just current_progname */ case -2: /*case -3:*/ - if (progname && current_progname) { + if (all_prognames[0] && 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); + " program %s nor current '%s'", + (uintmax_t)pid, all_prognames, current_progname); } else if (current_progname) { - /* Not asked for progname==NULL */ + /* Not asked for prognames==NULL nor prognames[0]==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) { + } else if (all_prognames[0]) { upslogx(LOG_ERR, "Tried to signal PID %" PRIuMAX " which exists but is not of" - " expected program '%s'; could not" + " expected program %s; could not" " cross-check current PID's name", - (uintmax_t)pid, progname); + (uintmax_t)pid, all_prognames); } else { /* Both NULL; one not asked, another not detected; * should not actually get here (wannabe `ret==-3`) @@ -1756,11 +1960,6 @@ int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_pr } } - if (current_progname) { - free(current_progname); - current_progname = NULL; - } - if (procname) { free(procname); procname = NULL; @@ -1770,11 +1969,6 @@ int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_pr return -1; } - if (current_progname) { - free(current_progname); - current_progname = NULL; - } - if (procname) { free(procname); procname = NULL; @@ -1805,7 +1999,7 @@ int sendsignalpid(pid_t pid, int sig, const char *progname, int check_current_pr #else /* WIN32 */ NUT_UNUSED_VARIABLE(pid); NUT_UNUSED_VARIABLE(sig); - NUT_UNUSED_VARIABLE(progname); + NUT_UNUSED_VARIABLE(prognames); NUT_UNUSED_VARIABLE(check_current_progname); /* Windows builds use named pipes, not signals per se */ NUT_WIN32_INCOMPLETE_MAYBE_NOT_APPLICABLE(); @@ -1898,13 +2092,23 @@ pid_t parsepidfile(const char *pidfn) */ #ifndef WIN32 int sendsignalfn(const char *pidfn, int sig, const char *progname, int check_current_progname) +{ + const char *arr[2]; + + arr[0] = progname; + arr[1] = NULL; + + return sendsignalfnaliases(pidfn, sig, arr, check_current_progname); +} + +int sendsignalfnaliases(const char *pidfn, int sig, const char **prognames, 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, progname, check_current_progname); + ret = sendsignalpidaliases(pid, sig, prognames, check_current_progname); } return ret; @@ -1913,9 +2117,19 @@ int sendsignalfn(const char *pidfn, int sig, const char *progname, int check_cur #else /* => WIN32 */ int sendsignalfn(const char *pidfn, const char * sig, const char *progname_ignored, int check_current_progname_ignored) +{ + const char *arr[2]; + + arr[0] = progname_ignored; + arr[1] = NULL; + + return sendsignalfnaliases(pidfn, sig, arr, check_current_progname_ignored); +} + +int sendsignalfnaliases(const char *pidfn, const char * sig, const char **prognames_ignored, int check_current_progname_ignored) { BOOL ret; - NUT_UNUSED_VARIABLE(progname_ignored); + NUT_UNUSED_VARIABLE(prognames_ignored); NUT_UNUSED_VARIABLE(check_current_progname_ignored); ret = send_to_named_pipe(pidfn, sig); @@ -2328,6 +2542,11 @@ const char *str_upsnotify_state(upsnotify_state_t state) { case NOTIFY_STATE_WATCHDOG: /* Ping the framework that we are still alive */ return "NOTIFY_STATE_WATCHDOG"; + case NOTIFY_STATE_EXTEND_TIMEOUT: + /* Ping the framework that we are still alive + * when starting/stopping + */ + return "NOTIFY_STATE_EXTEND_TIMEOUT"; #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 @@ -2539,6 +2758,7 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) switch (state) { case NOTIFY_STATE_READY: + /* In systemd: since forever? (unspec) */ ret = snprintf(buf + msglen, sizeof(buf) - msglen, "%sREADY=1%s", msglen ? "\n" : "", @@ -2546,6 +2766,7 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) break; case NOTIFY_STATE_READY_WITH_PID: + /* In systemd: since forever? (unspec) */ if (1) { /* scoping */ char pidbuf[SMALLBUF]; if (snprintf(pidbuf, sizeof(pidbuf), "%lu", (unsigned long) getpid())) { @@ -2580,6 +2801,13 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) break; case NOTIFY_STATE_RELOADING: + /* Tells the service manager that the service + * is beginning to reload its configuration... + * Note that a service that sends this notification + * must also send a "READY=1" notification when + * it completed reloading its configuration. + * In systemd: since v217 + */ ret = snprintf(buf + msglen, sizeof(buf) - msglen, "%s%s%s", msglen ? "\n" : "", "RELOADING=1", @@ -2587,13 +2815,22 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) break; case NOTIFY_STATE_STOPPING: + /* Tells the service manager that the service + * is beginning its shutdown. This is useful + * to allow the service manager to track the + * service's internal state, and present it + * to the user. + * In systemd: since v217 + */ ret = snprintf(buf + msglen, sizeof(buf) - msglen, "%s%s", msglen ? "\n" : "", "STOPPING=1"); break; case NOTIFY_STATE_STATUS: - /* Only send a text message per "fmt" */ + /* Only send a text message per "fmt" + * In systemd: since v233 + */ if (!msglen) { upsdebugx(6, "%s: failed to notify about status: none provided", __func__); ret = -1; @@ -2603,7 +2840,13 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) break; case NOTIFY_STATE_WATCHDOG: - /* Ping the framework that we are still alive */ + /* Ping the framework that we are still alive + * In systemd: since v209 (and if enabled by unit file) + * per https://www.freedesktop.org/software/systemd/man/latest/sd_watchdog_enabled.html + * NOTE: per https://www.freedesktop.org/software/systemd/man/latest/sd_notify.html + * since v233 a service can also request a different + * value of WATCHDOG_USEC=... during run-time. + */ if (1) { /* scoping */ int postit = 0; @@ -2634,7 +2877,7 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) # if ! DEBUG_SYSTEMD_WATCHDOG if (!upsnotify_reported_watchdog_systemd) # endif - upsdebugx(6, "%s: WATCHDOG_USEC=%s", __func__, s); + upsdebugx(6, "%s: WATCHDOG_USEC=%s", __func__, NUT_STRARG(s)); if (s && *s) { long l = strtol(s, (char **)NULL, 10); if (l > 0) { @@ -2685,6 +2928,61 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) } break; + case NOTIFY_STATE_EXTEND_TIMEOUT: + /* Ping the framework that we are still alive + * after we have reported that we are stopping + * (or starting but not yet ready) so its timeout + * for stuck services does not kill us. Value + * can be pre-set by the caller, or inherited + * from WATCHDOG_USEC if available. + * + * Tells the service manager to extend the startup, + * runtime or shutdown service timeout corresponding + * the current state. The value specified is a time + * in microseconds during which the service must + * send a new message. A service timeout will occur + * if the message isn't received, but only if the + * runtime of the current state is beyond the + * original maximum times of TimeoutStartSec=, + * RuntimeMaxSec=, and TimeoutStopSec=. + * See systemd.service(5) for effects on the + * service timeouts. + * + * In systemd: since v236 + */ + if (1) { /* scoping */ + uint64_t to_usec = upsnotify_extend_timeout_usec; + + if (to_usec < 1) { + char *s = getenv("WATCHDOG_USEC"); + +# if ! DEBUG_SYSTEMD_WATCHDOG + if (!upsnotify_reported_watchdog_systemd) +# endif + upsdebugx(6, "%s: WATCHDOG_USEC=%s (for EXTEND_TIMEOUT_USEC)", __func__, NUT_STRARG(s)); + if (s && *s) { + long l = strtol(s, (char **)NULL, 10); + if (l > 0) { + to_usec = l; + } + } + } + + if (to_usec < 1) { + to_usec = upsnotify_extend_timeout_usec_default; + } + + if (to_usec > 0) { + ret = snprintf(buf + msglen, sizeof(buf) - msglen, "%sEXTEND_TIMEOUT_USEC=%" PRIu64, + msglen ? "\n" : "", + to_usec); + } else { + upsdebugx(6, "%s: requested to NOTIFY_STATE_EXTEND_TIMEOUT but did not provide any value", __func__); + ret = -126; + } + } + break; + #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && ( (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) || (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_UNREACHABLE_CODE) ) # pragma GCC diagnostic push #endif @@ -2722,7 +3020,7 @@ 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)) ) { + if ( !(ret == -126 && (state == NOTIFY_STATE_WATCHDOG || state == NOTIFY_STATE_EXTEND_TIMEOUT)) ) { if (syslog_is_disabled()) { fprintf(stderr, "%s (%s:%d): snprintf needed more than %" PRIuSIZE " bytes: %d", @@ -2813,9 +3111,10 @@ int upsnotify(upsnotify_state_t state, const char *fmt, ...) #if defined(WITH_LIBSYSTEMD) && (WITH_LIBSYSTEMD) # if ! DEBUG_SYSTEMD_WATCHDOG if (state == NOTIFY_STATE_WATCHDOG && !upsnotify_reported_watchdog_systemd) { - upsdebugx(upsnotify_report_verbosity, - "%s: logged the systemd watchdog situation once, " - "will not spam more about it", __func__); + if (nut_debug_level >= 6) /* level of upsdebugx() above telling watchdog details */ + upsdebugx(upsnotify_report_verbosity, + "%s: logged the systemd watchdog situation once, " + "will not spam more about it", __func__); upsnotify_reported_watchdog_systemd = 1; upsnotify_suggest_NUT_QUIET_INIT_UPSNOTIFY_once(); } @@ -3654,6 +3953,61 @@ void upslogx(int priority, const char *fmt, ...) va_end(va); } +/* also keep a buffer with prefixed colon for debug printouts */ +static char *proctag = NULL, *proctag_for_upsdebug = NULL, + proctag_cleanup_registered = 0; + +static void proctag_cleanup(void) +{ + if (proctag) { + upsdebugx(2, "a %s sub-process (%s) is exiting now", + NUT_STRARG(getmyprocbasename()), getproctag()); + } + setproctag(NULL); +} + +const char *getproctag(void) +{ + return (const char *)proctag; +} + +void setproctag(const char *tag) +{ + size_t proctag_for_upsdebug_buflen = 0; + if (proctag) { + free(proctag); + proctag = NULL; + } + + if (proctag_for_upsdebug) { + free(proctag_for_upsdebug); + proctag_for_upsdebug = NULL; + } + + if (!tag) { + /* wipe */ + return; + } + + if (!proctag_cleanup_registered) { + /* We would use this anyway in exit handler (probably many times + * for forked children), so better get it over with quickly */ + getmyprocname(); + + atexit(proctag_cleanup); + proctag_cleanup_registered = 1; + } + + /* let the caller's copy be freed */ + proctag = xstrdup(tag); + + proctag_for_upsdebug_buflen = strlen(tag) + 2; + proctag_for_upsdebug = xcalloc(proctag_for_upsdebug_buflen, sizeof(char)); + if (proctag_for_upsdebug) { + snprintf(proctag_for_upsdebug, proctag_for_upsdebug_buflen, ":%s", tag); + } +} + void s_upsdebug_with_errno(int level, const char *fmt, ...) { va_list va; @@ -3682,9 +4036,15 @@ void s_upsdebug_with_errno(int level, const char *fmt, ...) if (NUT_DEBUG_PID) { /* Note that we re-request PID every time as it can * change during the run-time (forking etc.) */ - ret = snprintf(fmt2, sizeof(fmt2), "[D%d:%" PRIiMAX "] %s", level, (intmax_t)getpid(), fmt); + ret = snprintf(fmt2, sizeof(fmt2), "[D%d:%" PRIiMAX "%s] %s", + level, (intmax_t)getpid(), + proctag_for_upsdebug ? proctag_for_upsdebug : "", + fmt); } else { - ret = snprintf(fmt2, sizeof(fmt2), "[D%d] %s", level, fmt); + ret = snprintf(fmt2, sizeof(fmt2), "[D%d%s] %s", + level, + proctag_for_upsdebug ? proctag_for_upsdebug : "", + fmt); } if ((ret < 0) || (ret >= (int) sizeof(fmt2))) { if (syslog_is_disabled()) { @@ -3740,9 +4100,15 @@ void s_upsdebugx(int level, const char *fmt, ...) if (NUT_DEBUG_PID) { /* Note that we re-request PID every time as it can * change during the run-time (forking etc.) */ - ret = snprintf(fmt2, sizeof(fmt2), "[D%d:%" PRIiMAX "] %s", level, (intmax_t)getpid(), fmt); + ret = snprintf(fmt2, sizeof(fmt2), "[D%d:%" PRIiMAX "%s] %s", + level, (intmax_t)getpid(), + proctag_for_upsdebug ? proctag_for_upsdebug : "", + fmt); } else { - ret = snprintf(fmt2, sizeof(fmt2), "[D%d] %s", level, fmt); + ret = snprintf(fmt2, sizeof(fmt2), "[D%d%s] %s", + level, + proctag_for_upsdebug ? proctag_for_upsdebug : "", + fmt); } if ((ret < 0) || (ret >= (int) sizeof(fmt2))) { diff --git a/common/nutconf.cpp b/common/nutconf.cpp index 049fb4d9db..ea2dd1659f 100644 --- a/common/nutconf.cpp +++ b/common/nutconf.cpp @@ -1315,6 +1315,8 @@ UpsmonConfiguration::NotifyType UpsmonConfiguration::NotifyTypeFromString(const return NOTIFY_COMMBAD; else if(str=="SHUTDOWN") return NOTIFY_SHUTDOWN; + else if(str=="SHUTDOWN_HOSTSYNC") + return NOTIFY_SHUTDOWN_HOSTSYNC; else if(str=="REPLBATT") return NOTIFY_REPLBATT; else if(str=="NOCOMM") diff --git a/common/nutwriter.cpp b/common/nutwriter.cpp index eb61d9ba14..19af80c8b3 100644 --- a/common/nutwriter.cpp +++ b/common/nutwriter.cpp @@ -399,6 +399,7 @@ const NotifyFlagsStrings::TypeStrings NotifyFlagsStrings::type_str = { "COMMOK", // NOTIFY_COMMOK "COMMBAD", // NOTIFY_COMMBAD "SHUTDOWN", // NOTIFY_SHUTDOWN + "SHUTDOWN_HOSTSYNC", // NOTIFY_SHUTDOWN_HOSTSYNC "REPLBATT", // NOTIFY_REPLBATT "NOCOMM", // NOTIFY_NOCOMM "NOPARENT", // NOTIFY_NOPARENT diff --git a/common/state.c b/common/state.c index 4697784198..877e9a9bf1 100644 --- a/common/state.c +++ b/common/state.c @@ -156,6 +156,17 @@ int state_get_timestamp(st_tree_timespec_t *now) #endif } +double difftime_st_tree_timespec( + const st_tree_timespec_t finish, + const st_tree_timespec_t start +) { +#if defined(HAVE_CLOCK_GETTIME) && defined(HAVE_CLOCK_MONOTONIC) && HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC + return difftimespec(finish, start); +#else + return difftimeval(finish, start); +#endif +} + /* Returns -1 if the node->lastset is "older" than cutoff, * 0 if it is equal, or +1 if it is newer. * Returns -2 or -3 if node or cutoff are null. @@ -177,11 +188,7 @@ int st_tree_node_compare_timestamp( * so if the diff is negative, then "lastset" happened * before "cutoff": */ -#if defined(HAVE_CLOCK_GETTIME) && defined(HAVE_CLOCK_MONOTONIC) && HAVE_CLOCK_GETTIME && HAVE_CLOCK_MONOTONIC - d = difftimespec(node->lastset, *cutoff); -#else - d = difftimeval(node->lastset, *cutoff); -#endif + d = difftime_st_tree_timespec(node->lastset, *cutoff); if (d < 0) return -1; diff --git a/common/str.c b/common/str.c index ac61ca25a6..ef13dcba3d 100644 --- a/common/str.c +++ b/common/str.c @@ -617,6 +617,8 @@ int str_to_double_strict(const char *string, double *number, const int base) return 1; } +/* Probably derived from https://stackoverflow.com/a/68816055/4715872 + * or a similar suggestion */ int str_ends_with(const char *s, const char *suff) { size_t slen; size_t sufflen; diff --git a/common/strjson.c b/common/strjson.c new file mode 100644 index 0000000000..f6b859bd52 --- /dev/null +++ b/common/strjson.c @@ -0,0 +1,44 @@ +/* strjson.c - common JSON string formatting helper + + 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. +*/ + +#include "common.h" +#include "strjson.h" + +/** + * @brief Helper function to print a string to stdout as a JSON-safe string. + * This function prints the escaped string *without* surrounding quotes. + * @param in The raw C-string to escape and print. + */ +void json_print_esc(const char *in) { + if (!in) { + printf("null"); + return; + } + + while (*in) { + switch (*in) { + case '\"': printf("\\\""); break; + case '\\': printf("\\\\"); break; + case '\b': printf("\\b"); break; + case '\f': printf("\\f"); break; + case '\n': printf("\\n"); break; + case '\r': printf("\\r"); break; + case '\t': printf("\\t"); break; + default: + if ((unsigned char)*in < 32) { + /* Print control characters as unicode */ + printf("\\u%04x", (unsigned int)*in); + } else { + putchar(*in); + } + break; + } + in++; + } +} + diff --git a/compile b/compile deleted file mode 100755 index c0096a7b56..0000000000 --- a/compile +++ /dev/null @@ -1,143 +0,0 @@ -#! /bin/sh -# Wrapper for compilers which do not understand `-c -o'. - -scriptversion=2009-10-06.20; # UTC - -# Copyright (C) 1999, 2000, 2003, 2004, 2005, 2009 Free Software -# Foundation, Inc. -# Written by Tom Tromey . -# -# 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, 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, see . - -# As a special exception to the GNU General Public License, if you -# distribute this file as part of a program that contains a -# configuration script generated by Autoconf, you may include it under -# the same distribution terms that you use for the rest of that program. - -# This file is maintained in Automake, please report -# bugs to or send patches to -# . - -case $1 in - '') - echo "$0: No command. Try \`$0 --help' for more information." 1>&2 - exit 1; - ;; - -h | --h*) - cat <<\EOF -Usage: compile [--help] [--version] PROGRAM [ARGS] - -Wrapper for compilers which do not understand `-c -o'. -Remove `-o dest.o' from ARGS, run PROGRAM with the remaining -arguments, and rename the output as expected. - -If you are trying to build a whole package this is not the -right script to run: please start by reading the file `INSTALL'. - -Report bugs to . -EOF - exit $? - ;; - -v | --v*) - echo "compile $scriptversion" - exit $? - ;; -esac - -ofile= -cfile= -eat= - -for arg -do - if test -n "$eat"; then - eat= - else - case $1 in - -o) - # configure might choose to run compile as `compile cc -o foo foo.c'. - # So we strip `-o arg' only if arg is an object. - eat=1 - case $2 in - *.o | *.obj) - ofile=$2 - ;; - *) - set x "$@" -o "$2" - shift - ;; - esac - ;; - *.c) - cfile=$1 - set x "$@" "$1" - shift - ;; - *) - set x "$@" "$1" - shift - ;; - esac - fi - shift -done - -if test -z "$ofile" || test -z "$cfile"; then - # If no `-o' option was seen then we might have been invoked from a - # pattern rule where we don't need one. That is ok -- this is a - # normal compilation that the losing compiler can handle. If no - # `.c' file was seen then we are probably linking. That is also - # ok. - exec "$@" -fi - -# Name of file we expect compiler to create. -cofile=`echo "$cfile" | sed 's|^.*[\\/]||; s|^[a-zA-Z]:||; s/\.c$/.o/'` - -# Create the lock directory. -# Note: use `[/\\:.-]' here to ensure that we don't use the same name -# that we are using for the .o file. Also, base the name on the expected -# object file name, since that is what matters with a parallel build. -lockdir=`echo "$cofile" | sed -e 's|[/\\:.-]|_|g'`.d -while true; do - if mkdir "$lockdir" >/dev/null 2>&1; then - break - fi - sleep 1 -done -# FIXME: race condition here if user kills between mkdir and trap. -trap "rmdir '$lockdir'; exit 1" 1 2 15 - -# Run the compile. -"$@" -ret=$? - -if test -f "$cofile"; then - test "$cofile" = "$ofile" || mv "$cofile" "$ofile" -elif test -f "${cofile}bj"; then - test "${cofile}bj" = "$ofile" || mv "${cofile}bj" "$ofile" -fi - -rmdir "$lockdir" -exit $ret - -# Local Variables: -# mode: shell-script -# sh-indentation: 2 -# eval: (add-hook 'write-file-hooks 'time-stamp) -# time-stamp-start: "scriptversion=" -# time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC" -# time-stamp-end: "; # UTC" -# End: diff --git a/conf/Makefile.am b/conf/Makefile.am index fd384eebbc..f0f200304c 100644 --- a/conf/Makefile.am +++ b/conf/Makefile.am @@ -13,8 +13,10 @@ else CGI_INSTALL = endif -dist_sysconf_DATA = $(SECFILES) $(PUBFILES) $(CGI_INSTALL) -nodist_sysconf_DATA = upssched.conf.sample upsmon.conf.sample +conf_examplesdir = @CONFPATH_EXAMPLES@ + +dist_conf_examples_DATA = $(SECFILES) $(PUBFILES) $(CGI_INSTALL) +nodist_conf_examples_DATA = upssched.conf.sample upsmon.conf.sample SPELLCHECK_SRC = $(dist_sysconf_DATA) \ upssched.conf.sample.in upsmon.conf.sample.in diff --git a/conf/upsmon.conf.sample.in b/conf/upsmon.conf.sample.in index 8a8383e876..1a0be42969 100644 --- a/conf/upsmon.conf.sample.in +++ b/conf/upsmon.conf.sample.in @@ -332,6 +332,7 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # NOTIFYMSG COMMOK "Communications with UPS %s established" # NOTIFYMSG COMMBAD "Communications with UPS %s lost" # NOTIFYMSG SHUTDOWN "Auto logout and shutdown proceeding" +# NOTIFYMSG SHUTDOWN_HOSTSYNC "Shutdown initiated; primary system is waiting for secondaries to log out or time out" # NOTIFYMSG REPLBATT "UPS %s battery needs to be replaced" # NOTIFYMSG NOCOMM "UPS %s is unavailable" # NOTIFYMSG NOPARENT "upsmon parent process died - shutdown impossible" @@ -401,6 +402,7 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # NOTIFYFLAG COMMOK SYSLOG+WALL # NOTIFYFLAG COMMBAD SYSLOG+WALL # NOTIFYFLAG SHUTDOWN SYSLOG+WALL +# NOTIFYFLAG SHUTDOWN_HOSTSYNC SYSLOG+WALL # NOTIFYFLAG REPLBATT SYSLOG+WALL # NOTIFYFLAG NOCOMM SYSLOG+WALL # NOTIFYFLAG NOPARENT SYSLOG+WALL @@ -412,9 +414,17 @@ POWERDOWNFLAG "@POWERDOWNFLAG@" # NOTIFYFLAG NOTBYPASS SYSLOG+WALL # NOTIFYFLAG ECO SYSLOG+WALL # NOTIFYFLAG NOTECO SYSLOG+WALL +# # NOTIFYFLAG ALARM SYSLOG+WALL # NOTIFYFLAG NOTALARM SYSLOG+WALL # +# NOTIFYFLAG OVER SYSLOG+WALL +# NOTIFYFLAG NOTOVER SYSLOG+WALL +# NOTIFYFLAG TRIM SYSLOG+WALL +# NOTIFYFLAG NOTTRIM SYSLOG+WALL +# NOTIFYFLAG BOOST SYSLOG+WALL +# NOTIFYFLAG NOTBOOST SYSLOG+WALL +# # NOTIFYFLAG OTHER SYSLOG+WALL # NOTIFYFLAG NOTOTHER SYSLOG+WALL # @@ -533,10 +543,10 @@ NOCOMMWARNTIME 300 # -------------------------------------------------------------------------- # FINALDELAY - last sleep interval before shutting down the system # -# On a primary, upsmon will wait this long after sending the NOTIFY_SHUTDOWN -# before executing your SHUTDOWNCMD. If you need to do something in between -# those events, increase this number. Remember, at this point your UPS is -# almost depleted, so don't make this too high. If needed, on high-end UPS +# In FSD handling, upsmon will wait this long after sending the NOTIFY_SHUTDOWN +# message before executing your SHUTDOWNCMD. If you need to do something in +# between those events, increase this number. Remember, at this point your UPS +# is almost depleted, so don't make this too high. If needed, on high-end UPS # devices you can usually configure when the low-battery state is announced # based on estimated remaining run-time or on charge level of the batteries. # diff --git a/conf/upssched.conf.sample.in b/conf/upssched.conf.sample.in index 79543cd714..bfb9b99f68 100644 --- a/conf/upssched.conf.sample.in +++ b/conf/upssched.conf.sample.in @@ -3,6 +3,19 @@ # NOTE: Contents of this file should be pure ASCII (character codes # not in range would be ignored with a warning message). # + +# ============================================================================ +# DEBUG_MIN +# +# Optionally specify a minimum debug level for `upssched` daemon or client, +# e.g. for troubleshooting a deployment without a need to edit consumer +# configuration. +# Note that command-line option `-D` can only increase this verbosity level. +# Also note that the configuration file is processed line by line, so you +# may want to set this option early on. +# +# DEBUG_MIN 6 + # ============================================================================ # # CMDSCRIPT @@ -83,7 +96,9 @@ CMDSCRIPT @BINDIR@/upssched-cmd # # Start a timer called that will trigger after # seconds, calling your CMDSCRIPT with as the first -# argument. +# argument. Each invocation starts an independent timer, even if the +# was already registered and started (note this can mess +# up subsequent CANCEL-TIMER operations). # # Example: # 1) Start a timer that will execute when communication with any UPS (*) has @@ -96,6 +111,28 @@ CMDSCRIPT @BINDIR@/upssched-cmd # # AT ONBATT * START-TIMER onbattwarn 30 +# ----------------------------------------------------------------------- +# +# - START-TIMER-SHARED +# +# Start a timer called that will trigger after +# seconds, calling your CMDSCRIPT with as the first +# argument. Each invocation checks if the was already +# started, and if so -- appends the current event's `UPSNAME`, +# `NOTIFYTYPE` and `NOTIFYMSG` to the list of unique values it would +# report via environment variables (as a comma-separated string) when +# the timer does execute. +# +# NOTE: Currently this updates the first seen instance with the +# (in case you managed to start many). +# +# Example: +# 1) Start or update a timer that will execute when communication +# with any UPS (*) has been gone for 10 seconds (since the first +# started and not yet elapsed timer named ): +# +# AT COMMBAD * START-TIMER upsgone 10 + # ----------------------------------------------------------------------- # # - CANCEL-TIMER [cmd] @@ -114,6 +151,19 @@ CMDSCRIPT @BINDIR@/upssched-cmd # # AT ONLINE * CANCEL-TIMER onbattwarn +# ----------------------------------------------------------------------- +# +# - CANCEL-TIMER-MATCHED [cmd] +# +# Similar to the above, but tries to only cancel the if it +# refers to the `UPSNAME` and `NOTIFYTYPE` values passed by caller (the +# `NOTIFYMSG` is ignored in this context). +# +# 1) If any UPS (*) reverts to utility power, then stop the timer before it +# triggers ONLY if that UPS is associated with the already scheduled timer: +# +# AT ONLINE * CANCEL-TIMER-MATCHED onbattwarn + # ----------------------------------------------------------------------- # # - EXECUTE diff --git a/conf/upsstats-single.html.sample b/conf/upsstats-single.html.sample index 4c2a6a8915..3a94cecce1 100644 --- a/conf/upsstats-single.html.sample +++ b/conf/upsstats-single.html.sample @@ -24,7 +24,15 @@ @REFRESH@ -@HOSTDESC@ : @VAR ups.model@ on @HOST@ + +@HOSTDESC@ : +@IFSUPP device.model@ +@VAR device.model@ +@ELSE@ +@VAR ups.model@ +@ENDIF@ +on @HOST@ + + @@ -119,6 +156,10 @@ book nop
Last updated -- Network UPS Tools + + + diff --git a/docs/config-notes.txt b/docs/config-notes.txt index 3fff30e6eb..12e139b3a8 100644 --- a/docs/config-notes.txt +++ b/docs/config-notes.txt @@ -1,6 +1,11 @@ Configuration notes =================== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/user-manual.chunked/Configuration_notes.html +////////////////////////////////////////////////////////////////////////////// + This chapter describes most of the configuration and use aspects of NUT, including establishing communication with the device and configuring safe shutdowns when the UPS battery runs out of power. @@ -566,20 +571,24 @@ become sufficiently charged. - generate a `NOTIFY_SHUTDOWN` event - wait `FINALDELAY` seconds -- typically `5` - call their `SHUTDOWNCMD` - - disconnect from `upsd` + - disconnect from `upsd` and exit (subject to `SHUTDOWNEXIT` setting) -5. The `upsmon` primary system waits up to `HOSTSYNC` seconds (typically `15`) - for the secondary systems to disconnect from `upsd`. If any are still - connected after this time, `upsmon` primary stops waiting and proceeds - with the shutdown process. +5. The `upsmon` primary system generates a `NOTIFY_SHUTDOWN_HOSTSYNC` event + and waits up to `HOSTSYNC` seconds (typically `15`) for the secondary + systems to disconnect from `upsd`. If any are still connected after + this time, `upsmon` primary stops waiting and proceeds with the shutdown + process anyway. 6. The `upsmon` primary: - generates a `NOTIFY_SHUTDOWN` event - waits `FINALDELAY` seconds -- typically `5` - creates the `POWERDOWNFLAG` file in its local filesystem -- usually - `/etc/killpower`, or `/run/nut/killpower` in a temporary file system + configured (explicitly!) to be `/etc/killpower`, or preferably be + `/var/run/nut/killpower` or `/run/nut/killpower` in a temporary file + system that disappears after reboot - calls the `SHUTDOWNCMD` + - disconnect from `upsd` and exit (subject to `SHUTDOWNEXIT` setting) 7. On most systems, `init` takes over, kills your processes, syncs and unmounts some filesystems, and remounts some read-only. diff --git a/docs/config-prereqs.txt b/docs/config-prereqs.txt index c777741df1..358cc74d57 100644 --- a/docs/config-prereqs.txt +++ b/docs/config-prereqs.txt @@ -3,6 +3,11 @@ Prerequisites for building NUT on different OSes ================================================ endif::website[] +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/qa-guide.chunked/_prerequisites_for_building_nut_on_different_oses.html +////////////////////////////////////////////////////////////////////////////// + This chapter aims to list packages with the tools needed on a freshly minimally deployed worker to build as many targets of NUT recipes as possible, mainly the diverse driver and documentation types. @@ -16,6 +21,12 @@ of new releases. * For Linux systems, we have notes on linkdoc:qa-guide[Custom NUT CI farm build agents: LXC multi-arch containers,CI_LXC,docs/ci-farm-lxc-setup.txt] +* After installing the build prerequisites needed on your platform as listed + below, you might want to follow up with + linkdoc:user-manual[Building NUT for in-place upgrades or non-disruptive tests,Installing_inplace,INSTALL.nut.adoc,_installation_instructions] +////////////////////////////////////////////////////////////////////////////// +// https://networkupstools.org/docs/user-manual.chunked/_installation_instructions.html#5-2-building-nut-for-in-place-upgrades-or-non-disruptive-tests +////////////////////////////////////////////////////////////////////////////// Some of the below are alternatives, e.g. compiler toolkits (gcc vs. clang) or SSL implementations (OpenSSL vs. Mozilla NSS) -- no problem installing @@ -233,7 +244,8 @@ metadata about recently published package revisions: # service unit files in a separate development package; earlier distro # releases did not seem to require it explicitly: :; apt-get install \ - systemd-dev + systemd-dev \ + || true # Optionally for sd_notify integration: :; apt-get install \ @@ -295,7 +307,8 @@ complete environments): qemu-user-static ------ -NOTE: For Jenkins agents, also need to `apt-get install openjdk-21-jdk-headless`. +NOTE: For Jenkins agents, also need to `apt-get install openjdk-21-jdk-headless` +(or `apt-get install openjdk-17-jdk-headless` on Debian 12 and older). You may have to ensure that `/proc` is mounted in the target chroot (or do this from the running container). @@ -1779,7 +1792,7 @@ Currently known dependencies for basic build include: :; brew install curl wget midnight-commander ---- -NOTE: for `asciidoc`/`a2x` to work, you should `export XML_CATALOG_FILES` with +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'` diff --git a/docs/configure.txt b/docs/configure.txt index 304ee67ce3..a2bbdab398 100644 --- a/docs/configure.txt +++ b/docs/configure.txt @@ -3,6 +3,12 @@ Configure options ================= endif::website[] +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/user-manual.chunked/Configure_options.html +// and subsequent pages (next-next-next...) +////////////////////////////////////////////////////////////////////////////// + As many other projects relying on GNU autotools for build recipe automation, NUT sources deliver a `configure` script which is used to set up numerous nuances relevant for your build of the project. Most of the configuration @@ -89,7 +95,8 @@ Tries to detect and pre-set `configure` defaults for run-time settings (which you can still override if needed, but no longer *must* specify explicitly to be on same page as the existing setup), most notably: -* `--sysconfdir` +* `--sysconfdir` and perhaps `--with-confdir-suffix`, + or `--with-confdir` altogether * `--with-user` * `--with-group` @@ -281,8 +288,8 @@ CGI client interface Build and install the optional CGI programs, HTML files, and sample CGI configuration files. This is not enabled by default, as they -are only useful on web servers. See link:data/html/README[] for additional -information on how to set up CGI programs. +are only useful on web servers. See link:data/htmlcgi/README[] for +additional information on how to set up CGI programs. NUT Scanner tool ~~~~~~~~~~~~~~~~ @@ -494,20 +501,42 @@ etc. One exception is generation of NUT-Monitor GUI application, being separated for `NUT-Monitor-py2gtk2`, `NUT-Monitor-py3qt5`, `NUT-Monitor-py3qt6` due -to further backend platform technical differences -- these build products +to further backend platform technical differences -- these build products specifically use `PYTHON2` and `PYTHON3` substitutions. They may also be -co-installed on the same system. A dispatcher shell script `NUT-Monitor` -is used to launch the preferred (newest) or only existing implementation. +co-installed on the same system. A dispatcher shell script `NUT-Monitor` is +used to launch the preferred (newest) or the only existing implementation. Please note that by default NUT tries to make use of everything in your -build environment, so if both Python generation are detected -- the binding +build environment, so if both Python generations are detected -- the binding module will be delivered into both, and two versions of NUT-Monitor GUI application will be installed. If you want to avoid that behaviour on a build system with both interpreters present, you can explicitly specify to build e.g. `--without-python2 --with-python=/usr/bin/python-3.9`. +Default values are currently prioritized for auto-detection to result in +some one Python interpreter version to be chosen even if several were +discovered (in order: '3', '2', or not numbered), using `auto-prio=NUM` +syntax. If some of these options specify a particular interpreter, none +of the `auto-prio` hits are considered. + +NOTE: Previous default (from NUT v2.8.0 to v2.8.4) was `auto` to allow all up +to three hits to be considered as script shebang and as `make install` targets. +When using `auto` values, specifying an explicit `--with-python` value does +not implicitly mean `--without-python2` nor `--without-python3`, and those +would be discovered and configured for, if possible. Conversely, an explicit +e.g. `--with-python2` setting does not automatically mean `--without-python` +nor `--without-python3`. +Since this may come across surprising to some people, the `configure` script +would warn in the end if several Python versions were auto-detected one way +or another. + +A value of `yes` (default if e.g `--with-python` is specified without an +argument) means to search for an implementation, and fail if one was not +located. + The settings below may be of particular interest to non-distribution -packaging efforts with their own dedicated directory trees: +packaging efforts with their own dedicated directory trees (like pkgsrc), +or where all packages have dedicated sub-trees (like NixOS): --with-python=SHEBANG_PATH @@ -516,8 +545,10 @@ code (except version-dependent scripts, see above). The `SHEBANG_PATH` should be a full program pathname, optionally with one argument, e.g. `/usr/bin/python-3.9` or `/usr/bin/env python2`. +Note that packaging distributions generally aim for predictable setups, +and disapprove of `/usr/bin/env` in shebangs for packaged scripts. -Defaults (in order): +Defaults for `--with-python` if it was not specified (in order): * `python`, `python3` or `python2` program if present in `PATH` by such name, * or the newest of `PYTHON3` or `PYTHON2` values (specified or detected below). @@ -542,9 +573,29 @@ availability), and optional `desktop-file-install` integration). --with-pynut Install the PyNUT module files for general consumption into "site-packages" -location of the currently chosen Python interpreter(s): yes, no, auto. +location of the currently chosen Python interpreter(s): `yes`, `no`, `auto`. or dedicated as the required dependency of NUT-Monitor application (app). + --with-python-modules-dir=(auto|PATH) + --with-python2-modules-dir=(auto|PATH) + --with-python3-modules-dir=(auto|PATH) + +Optional, to specify PyNUT(Client) module installation location (for the +module-named dir to be created under it), if not bundling with NUT-Monitor +UI app. By default the respective interpreter's 'site-packages' or +'dist-packages' location will be used, so you may have to adjust module +search paths for any other values (see `NUT-Monitor` GUI sources for an +example). + +NOTE: Specifically in `NUT-Monitor` scripts, the version-less option gets +least priority in search path, but corresponding versioned ones (if not +there yet) would be searched first. Any copy of the PyNUT module located +near the script (as in NUT source tree) has the highest priority of all. +This option may help to make use of that module installed by other means +(e.g. as a released PyPI repository package), if you for some reason (like +OS packaging policy) choose to build and install NUT itself `--without-pynut`. + + [WARNING] ========= The module files are installed into a particular Python version's location @@ -779,11 +830,6 @@ initiate a shutdown. There is always at least a stub process remaining with `root` powers. The network code runs in another (separate) process as the new user. -The `` is used for the permissions of some files, -particularly the hotplugging rules for USB. The idea is that the -device files for any UPS devices should be readable and writable by -members of that group. - The default value for both the username and groupname is `nobody` (or `nogroup` on systems that have it when `configure` script runs). This was done since it's slightly better than staying around as @@ -791,6 +837,27 @@ This was done since it's slightly better than staying around as hack for NFS access. You should create at least one separate user for this software. +Starting from NUT v2.8.5, further default user and group account names +are probed in the operating system (which might exist if a different +version of NUT was already installed before the build), currently trying +`upsmon`, `nutmon`, `ups` or `nut` (last hit wins, separately for groups +and users). + +When such NUT daemons `setuid()` to some user account (whether built-in, +or specified in configuration files, or passed on command line), they +also typically assume that account's primary group via `setgid()`. +With default installation or packaging approaches, the dedicated user +account "coincidentally" has the group specified here as its primary. + +The specified `` is used for the permissions of some files, +particularly the hotplugging rules for USB. The idea is that the +device files for any UPS devices should be readable and writable by +members of that group. It also allows to run the drivers and the data +server as different user accounts, which have some group in common +(it should be the primary group for the user linkman:upsd[8] runs as, +and such deployment would be using explicit user/group account settings +beyond the one built-in value configurable here). + If you use one of the `--with-user` and `--with-group` options, then you have to use the other one too. @@ -851,12 +918,46 @@ default, it is the same as ``. --sysconfdir=PATH +Base system location for configuration files. By default this path is +`/etc`. + +From time immemorial until NUT v2.8.5, this was the option to change +in order to define a dedicated directory for NUT configuration files. +Setting this to `/etc/nut` or `/etc/ups` might be useful in older builds. +Now the `--with-confdir*` options are preferable. + + --with-confdir-suffix=DIRNAME + +Provides a partial location where NUT's configuration files are stored, +relative to ``. By default this path is empty if either +`` was modified (assuming legacy build configuration was +re-used for current NUT), or if `` was NOT modified (then the +value of `/usr/local/ups/etc` makes sense to directly hold NUT configuration +files). Otherwise it defaults to `` which is `nut` (unless +some distribution hacks the `configure` script to their liking). + +Surrounding slashes would be removed and a trailing one added, to normalize +the paths and avoid double-slashes etc. + +See also `--enable-inplace-runtime`. + + --with-confdir=PATH + Changes the location where NUT's configuration files are stored. -By default this path is `/etc`. Setting this to `/etc/nut` or -`/etc/ups` might be useful. See also `--enable-inplace-runtime`. +By default this path is `/` which on +most systems resolves to `/etc/nut`. + +See also `--enable-inplace-runtime`. The `NUT_CONFPATH` environment variable overrides this at run time. + --with-confdir-examples=PATH + +Changes the location where NUT's configuration file examples are stored. +By default this path is ``, but some distributions prefer to +store examples under `` or somewhere else, requiring `` +to be used only by actual live system configuration. + --sbindir=PATH --bindir=PATH @@ -1069,8 +1170,14 @@ Directories used by NUT at run-time --with-pidpath=PATH Changes the directory where NUT pid files are stored for processes running -as `root`. By default this is `/var/run`. Certain programs like `upsmon` -will leave files here. +as `root`. By default this is `/var/run` (or `/run` on systems that follow +FHS-3.0 standard strictly and do not offer a legacy symlink). A build of +NUT is encouraged to configure the use of a dedicated sub-directory, like +`/var/run/ups` or `/var/run/nut`, as long as its existence and proper +ownership and permissions can be ensured by the time NUT daemons start. +Certain programs like `upsmon` will leave files here. + +The `NUT_PIDPATH` environment variable overrides this at run time. --with-altpidpath=PATH @@ -1089,6 +1196,12 @@ commands. Default is `/var/state/ups`. This is also the default location for non-`root` daemons to write a PID file, if a separate location is not specified by `--with-altpidpath` option. +Sample configuration examples for `upssched.conf` (and default systemd-tmpfiles +configuration generated with a NUT build) suggest using a sub-directory like +`${STATEPATH}/upssched` for the tool's lock file and client-daemon communication +pipe. This allows to potentially run that tool and daemon under a dedicated +user account, without overlapping with permissions needed by other NUT programs. + The `NUT_STATEPATH` environment variable overrides this at run time. NOTE: Fun fact: in early iterations of the NUT project, the drivers and the @@ -1130,7 +1243,7 @@ find the header files, use this switch to add additional `-I` flags. If your copy of `libgd` isn't linking properly, use this to give the proper `-L` and `-l` flags to make it work. See `LIBS=` in gd's `Makefile`. -NOTE: the `--with-gd` switches are not necessary if you have gd 2.0.8 +NOTE: The `--with-gd` switches are not necessary if you have gd 2.0.8 or higher installed properly. The `gdlib-config` script or pkg-config manifest will be detected and used by default in that situation. diff --git a/docs/daisychain.txt b/docs/daisychain.txt index bd140199a6..a6f0ad93ca 100644 --- a/docs/daisychain.txt +++ b/docs/daisychain.txt @@ -4,6 +4,12 @@ NUT daisychain support notes ============================ endif::external_title[] +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/daisychain.html +// and subsequent pages (next-next-next...) +////////////////////////////////////////////////////////////////////////////// + NUT supports daisychained devices for any kind of device that proposes it. This chapter introduces: diff --git a/docs/design.txt b/docs/design.txt index a218d84248..fdd789d1a5 100644 --- a/docs/design.txt +++ b/docs/design.txt @@ -1,6 +1,11 @@ NUT design document =================== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/design.html +////////////////////////////////////////////////////////////////////////////// + This software is designed around a layered scheme with drivers, a data server, and clients. These layers communicate with text-based protocols for easier maintenance and diagnostics. diff --git a/docs/developers.txt b/docs/developers.txt index e7ae6154f8..ed43d00a4a 100644 --- a/docs/developers.txt +++ b/docs/developers.txt @@ -86,7 +86,7 @@ Escaping special characters and quoting multiple-word elements is all handled by the state machine. Using the same code for all config files avoids code duplication. -NOTE: this does not apply to drivers. Driver authors should use the +NOTE: This does not apply to drivers. Driver authors should use the `upsdrv_makevartable()` scheme to pick up values from 'ups.conf' file. Drivers should not have their own config files. @@ -131,6 +131,21 @@ There are still older systems out there that don't do C++ style comments. // Not like this. -------------------------------------- +Complete method signatures +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If a method has intentionally no arguments, this should be explicit by +specifying a `void` argument list in both declarations (header or top +of a C source file for `static` methods) and implementations: + +------------------------------------------------------------------------------- +/* Like this: */ +void good_stuff(void); + +/* NOT like this: */ +void bad_stuff(); +------------------------------------------------------------------------------- + Variable declarations go on top ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1204,7 +1219,7 @@ you can limit the depth of the clone: OPTIONALLY you can then fetch known git tags, so semantic versions look better (based off a recent release, see -linkdoc:developer-guide[NUT Versioning,versioning,docs/nut-versioning.adoc] +linkdoc:developer-guide[NUT Semantic Versioning,versioning,docs/nut-versioning.adoc] for more details): :; cd nut diff --git a/docs/docinfo.xml.in b/docs/docinfo.xml.in index 2b3b7bef29..5cc0596519 100644 --- a/docs/docinfo.xml.in +++ b/docs/docinfo.xml.in @@ -4,7 +4,6 @@ --> - /a'"$NEWTAGS_SED" \ @@ -96,7 +98,12 @@ sed -e '//a'"$NEWTAGS_SED" \ | tr '|' '\n' \ > "${DOCINFO_XML}.tmp" -diff -bu "${DOCINFO_XML}" "${DOCINFO_XML}.tmp" +OUTD="`diff -bu \"${DOCINFO_XML}\" \"${DOCINFO_XML}.tmp\" 2>/dev/null`" +if echo "$OUTD" | head -1 | ${EGREP} '^[-+]' >/dev/null ; then + echo "$OUTD" +else + diff "${DOCINFO_XML}" "${DOCINFO_XML}.tmp" +fi echo "Was the change acceptable? Press Y to modify the original '${DOCINFO_XML}' file [Y/N]" >&2 read LINE diff --git a/docs/documentation.txt b/docs/documentation.txt index a17bb8ddb3..7d5a2e9d13 100644 --- a/docs/documentation.txt +++ b/docs/documentation.txt @@ -3,6 +3,10 @@ Documentation ============= endif::website[] +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/documentation.html +////////////////////////////////////////////////////////////////////////////// User Documentation ------------------ diff --git a/docs/download.txt b/docs/download.txt index c8b72baec1..a1357f85de 100644 --- a/docs/download.txt +++ b/docs/download.txt @@ -3,6 +3,33 @@ Download information This section presents the different methods to download NUT. +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/download.html +////////////////////////////////////////////////////////////////////////////// + +Building from source code +------------------------- + +To build from Git sources, you will need a number of tools such as `autoconf`, +`automake` and `libtool` to use these checkouts or snapshots to generate the +`configure` script and some other files. The distribution archives already +include the `configure` script (as well as other files) and do not require +auto* tools to prepare the sources for a build. + +A more complete list of build prerequisites for different platforms can be seen in +link:https://networkupstools.org/docs/qa-guide.chunked/_prerequisites_for_building_nut_on_different_oses.html[Prerequisites +for building NUT on different OSes] chapter in the +link:https://networkupstools.org/docs/qa-guide.chunked/[NUT QA Guide], +and reading about +link:https://networkupstools.org/docs/user-manual.chunked/Configure_options.html[`configure` +script options] can also help. + +After you `configure` the source workspace, a `make dist-hash` recipe would +create the snapshot tarballs which do not require the `auto*` tools, and their +checksum files, such as those available on the NUT website and attached to +link:https://github.com/networkupstools/nut/releases[GitHub Releases page]. + Source code ----------- @@ -39,6 +66,23 @@ You can also browse the link:https://www.networkupstools.org/source/{tree_versio Development tree: ~~~~~~~~~~~~~~~~~ +To get the newest fixes or features, you may want to build code that is +even newer than the most-recent NUT release (and likely much newer than +whatever your OS distribution has packaged). This bleeding-edge approach +usually involves building NUT either from the `master` branch, or even +from feature branches which are sources of a pull request being reviewed +and discussed before it gets merged into the main development trunk. + +As such, it is highly recommended to build from Git sources rather than +"distribution tarball" archives (so you can more easily update your build +workspace to try subsequent iterations), although the latter are now also +available for development iterations. + +See the live Wiki article on +https://github.com/networkupstools/nut/wiki/Building-NUT-for-in%E2%80%90place-upgrades-or-non%E2%80%90disruptive-tests +for latest suggestions for building, testing and installing the latest +NUT code base. + Code repository ^^^^^^^^^^^^^^^ @@ -61,7 +105,7 @@ following script in the directory you just checked out: :; ./autogen.sh -NOTE: it is optionally recommended to have Python 2.x or 3.x, and Perl, to +NOTE: It is optionally recommended to have Python 2.x or 3.x, and Perl, to generate some files included into the `configure` script, presence is checked by autotools when it is generated. Neutered files can be just "touched" to pass the `autogen.sh` if these interpreters are not available, and effectively @@ -94,25 +138,20 @@ sources of operating system distributions, as listed below. Snapshots ^^^^^^^^^ -GitHub has several download links for repository snapshots (for particular tags -or branches), but you will need a number of tools such as autoconf, automake -and libtool to use these snapshots to generate the `configure` script and some -other files. +For pull requests and eventual merges into the master branch, a NUT CI job +prepares correct "dist tarballs" with a snapshot of source code, as well as +an archive with rendered documentation files (PDF, HTML, man pages). -After you `configure` the source workspace, a `make dist-hash` recipe would -create the snapshot tarballs which do not require the auto* tools, and their -checksum files, such as those available on the NUT website and attached to -link:https://github.com/networkupstools/nut/releases[GitHub Releases page]. - -///////// -TODO: #1400 to replace this with a NUT CI farm service to publish the tarballs +Links to these "Dist and Docs" archives can be seen on GitHub in the list of +GitHub Checks associated with merge commits and pull requests. Keep in mind +that artifacts like these are stored for up to 90 days, and can be rotated +away earlier. -If our Buildbot instance is behaving, you can download a snapshot which does -not require auto* tools from this -link:http://buildbot.networkupstools.org/snapshots[builder]. Look for the -latest *[tarball]* link towards the top of the page, and be sure to check the -'Build ##' link to verify the branch name. -///////// +NOTE: GitHub has several download links for repository snapshots (made for +particular tags or branches), but the ability to build NUT seamlessly from +ZIP or `tar.gz` snapshots prepared by GitHub automatically (as a simple +`git archive` of a checked-out workspace) is not regularly checked nor +"supported" by the Network UPS Tools project. YMMV. Older versions ~~~~~~~~~~~~~~ @@ -123,13 +162,29 @@ link:https://www.networkupstools.org/source/[Browse source directory] Binary packages --------------- -NOTE: The only official releases from this project are source code. +[NOTE] +====== +The only official releases from this project are source code "tarballs", +prepared by `make dist-files` from the tagged release commits on main trunk of +the development code base (for more details see NUT Maintainer Guide in source). + +Best-effort packages or archives of installation prototype workspaces, prepared +by regular CI builds, are planned to ease evaluation of the latest development +for some platforms (currently serving only NUT for Windows). +====== NUT is already available in the following operating systems (and link:https://github.com/networkupstools/nut/wiki/Links-to-distribution-packaging-recipes-and-repository-sections[likely more]): -- link:https://repology.org/project/nut/versions[Repology report on NUT] - lists 745 entries about NUT, as of this writing +- link:https://repology.org/project/network-ups-tools/versions[Repology report + on "network-ups-tools"] lists 239 entries about NUT, as of 2025-09-06 + + * Older listing at link:https://repology.org/project/nut/versions[Repology + report on "nut"] listed 745 entries about NUT, as of 2022-04-26 -- that + name was not tracked since 2024 probably due to ambiguity with some other + projects that used "nut" in their name; + see link:https://repology.org/project/nut/history[Repology history of + "nut"] for more details - Linux: @@ -162,8 +217,9 @@ link:https://github.com/networkupstools/nut/wiki/Links-to-distribution-packaging link:http://doc.freenas.org/9.3/freenas_services.html#ups[FreeNAS 9.3 docs on UPS integration] and link:https://www.ixsystems.com/documentation/freenas/11.3-U5/services.html#ups[FreeNAS 11.3-U5 docs on UPS integration] -- Mac OS X: +- macOS 11+ and Mac OS X: + * link:https://formulae.brew.sh/formula/nut[Homebrew formula] * link:https://github.com/fink/fink-distributions/blob/master/10.9-libcxx/stable/main/finkinfo/net/nut.info[Fink recipe] and link:http://pdb.finkproject.org/pdb/package.php/nut[Fink package overview] * link:http://trac.macports.org/browser/trunk/dports/sysutils/nut/Portfile[MacPorts recipe] @@ -183,6 +239,7 @@ link:https://github.com/networkupstools/nut/wiki/Links-to-distribution-packaging + The latest release's automated build archive is available here: link:https://www.networkupstools.org/package/windows/NUT-for-Windows-x86_64-RELEASE-{revision}.7z[NUT-for-Windows-x86_64-RELEASE-{revision}.7z] + * link:https://www.networkupstools.org/package/windows/NUT-Installer-2.6.5-6.msi[(OBSOLETE) Windows MSI installer 2.6.5-6] diff --git a/docs/features.txt b/docs/features.txt index a258da704d..a892920dbf 100644 --- a/docs/features.txt +++ b/docs/features.txt @@ -1,6 +1,11 @@ Features ======== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/features.html +////////////////////////////////////////////////////////////////////////////// + NUT provides many features, and is always improving. Thus this list may lag behind the current code. diff --git a/docs/hid-subdrivers.txt b/docs/hid-subdrivers.txt index cc3fb8260a..aec5fe3048 100644 --- a/docs/hid-subdrivers.txt +++ b/docs/hid-subdrivers.txt @@ -1,6 +1,11 @@ How to make a new subdriver to support another USB/HID UPS ---------------------------------------------------------- +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/new-drivers.html#hid-subdrivers +////////////////////////////////////////////////////////////////////////////// + Overall concept ~~~~~~~~~~~~~~~ @@ -111,8 +116,8 @@ Thus, the above usage tree is internally represented as: -------------------------------------------------------------------------------- To make matters worse, most manufacturers define their own additional -usages, even in cases where standard usages could have been used. for -example Belkin defines `00860040` = ConfigVoltage (which is incidentally +usages, even in cases where standard usages could have been used. For +example Belkin defines `00860040` = `ConfigVoltage` (which is incidentally a violation of the USB PDC specification, as `00860040` is reserved for future use). @@ -122,8 +127,8 @@ Thus, subdrivers generally need to provide: - a mapping of HID variables to NUT variables. Moreover, subdrivers might have to provide additional functionality, -such as custom implementations of specific instant commands (load.off, -shutdown.restart), and conversions of manufacturer specific data +such as custom implementations of specific instant commands (`load.off`, +`shutdown.restart`), and conversions of manufacturer specific data formats. @@ -230,12 +235,27 @@ precision first). Using a GUI tool with partial-line difference matching and highlighting, such as Meld or WinMerge, is recommended for this endeavour. +Alternatively, since NUT v2.8.5, the `usbhid-ups` driver initialization +debug log should clarify which values were parsed from the device report +descriptor, but were not looked at when walking the subdriver mapping table. +Some of these items may be due to the device reporting same HID Path with +different Type values (e.g. as an 'Input' and as a 'Feature') with the +driver only using one of those; in other cases truly new mappings can be +discovered. + For new data points in `hid2nut` tables be sure to not invent new names, but use standard ones from `docs/nut-names.txt` file. Temporarily, the -`experimental.*` namespace may be used. +`experimental.*` or `unmapped.*` namespaces may be used. If you need to standardize a name for some concept not addressed yet, please do so via nut-upsdev mailing list discussion. +NOTE: For new devices (or firmwares) please also anticipate that a wrong +subdriver can get used by default, and a better one may already exist +(perhaps because a vendor matched in the older NUT code by name introduced +new device models with different dialects) -- in this case, please focus +on `claim` methods to ensure that the new devices get handled by the better +subdriver. + Customization ~~~~~~~~~~~~~ diff --git a/docs/maintainer-guide.txt b/docs/maintainer-guide.txt index 8d5aca725c..e059fbfe95 100644 --- a/docs/maintainer-guide.txt +++ b/docs/maintainer-guide.txt @@ -248,15 +248,19 @@ VERSION_FORCED_SEMVER:NUT_VERSION_FORCED_SEMVER='2.8.3' make -j 8 spellcheck && \ make -j 8 distcheck ---- -* create an annotated GPG-signed tag v (ex: `v2.8.0`): +* create an annotated GPG-signed tag v (ex: `v2.8.0-rc1`): + ---- -:; git tag -asm 'Release NUT v2.8.0' v2.8.0 +:; git tag -asm 'Release NUT v2.8.0' v2.8.0-rc1 ---- -** in case of second thoughts, `git tag -d v2.8.0` and retry later +* push the tag to a developer's private repository first; wait for CI + builds to complete without hiccup (test-PyPI push, GHA scanning, etc.) +** in case of second thoughts, `git tag -d v2.8.0-rc1` 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 +* only when the tagged builds seem viable, push the RC tag to upstream + NUT repository; eventually create the final release tag like `v2.8.0` * don't forget to push not only the code, but also the tag: + ---- @@ -268,6 +272,8 @@ VERSION_FORCED_SEMVER:NUT_VERSION_FORCED_SEMVER='2.8.3' * `make dist-files` (especially if you did not `make distcheck` above, or had some changes since then) to store the source tarball, checksum and signature files +** Bonus feature: `make dist-docs` (if on a system configured to build all + documentation types) * post-release update of the "in-development" codebase: ** start a feature branch to return the master branch into development state @@ -299,7 +305,7 @@ VERSION_FORCED_SEMVER:NUT_VERSION_FORCED_SEMVER='2.8.3' ** add an entry to `news.txt` ** update `nut` and `ddl` submodules in nut-website/ to refer to latest info as of current release (this should update the website's version as well). - NOTE: for `nut` submodule be sure to refer to the tagged commit, not to + 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: `make dist{,-hash,-sig}` (or since NUT v2.8.3, `make dist-files`) diff --git a/docs/man/Makefile.am b/docs/man/Makefile.am index 86e9109552..5e5b850a05 100644 --- a/docs/man/Makefile.am +++ b/docs/man/Makefile.am @@ -28,10 +28,6 @@ # * DIST_ALL_MAN_PAGES: all known MAN page file names, to help ensure dist archive # * DIST_ALL_HTML_PAGES: all known HTML page file names, to help ensure nut-website -# Is "egrep == grep -E" always valid? (maybe all a job for configure.ac) -#EGREP = egrep -EGREP = grep -E - SRC_ALL_PAGES = index.txt # Populate these lists depending on DOC_INSTALL_SELECTED_MANS_PROGS_BUILT toggle @@ -134,7 +130,7 @@ LIST_FILES = ( \ TOTAL=0; \ for P in $${FILENAMES} ; do \ TOTAL="`expr $$TOTAL + 1`" ; \ - F="`basename "$$P"`" ; \ + F="`basename \"$$P\"`" ; \ if [ -s "$(abs_builddir)/$$F" ] ; then \ echo "BUILDDIR: $(builddir)/$$F" ; \ else \ @@ -165,7 +161,7 @@ FAKE_FILES = ( \ TOTAL=0; \ for P in $${FILENAMES} ; do \ TOTAL="`expr $$TOTAL + 1`" ; \ - F="`basename "$$P"`" ; \ + F="`basename \"$$P\"`" ; \ if [ ! -s "$(abs_builddir)/$$F" ] && [ ! -s "$(abs_srcdir)/$$F" ] ; then \ echo "PLACEHOLDER" > "$(abs_builddir)/$$F" ; \ MISSING="`expr $$MISSING + 1`" ; \ @@ -181,9 +177,9 @@ FAKE_FILES_RM = ( \ TOTAL=0; \ for P in $${FILENAMES} ; do \ TOTAL="`expr $$TOTAL + 1`" ; \ - F="`basename "$$P"`" ; \ + F="`basename \"$$P\"`" ; \ if [ -s "$(abs_builddir)/$$F" ] \ - && [ x"PLACEHOLDER" = x"`cat "$(abs_builddir)/$$F"`" ] ; then \ + && [ x"PLACEHOLDER" = x"`cat \"$(abs_builddir)/$$F\"`" ] ; then \ rm -f "$(abs_builddir)/$$F" ; \ MISSING="`expr $$MISSING + 1`" ; \ fi ; \ @@ -445,13 +441,13 @@ INST_HTML_CLIENT_MANS_FICTION = \ NUT-Monitor-py3qt6.html NUT-Monitor-py2gtk2.html: NUT-Monitor.html - test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + test -n '$?' -a -s '$@' && rm -f $@ && ln -s $? $@ NUT-Monitor-py3qt5.html: NUT-Monitor.html - test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + test -n '$?' -a -s '$@' && rm -f $@ && ln -s $? $@ NUT-Monitor-py3qt6.html: NUT-Monitor.html - test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + test -n '$?' -a -s '$@' && rm -f $@ && ln -s $? $@ else !WITH_NUT_MONITOR @@ -847,28 +843,28 @@ HTML_DEV_MANS_FICTION = \ nutscan_add_commented_option_to_device.html upscli_readline_timeout.html: upscli_readline.html - test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + test -n '$?' -a -s '$@' && rm -f $@ && ln -s $? $@ upscli_sendline_timeout.html: upscli_sendline.html - test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + test -n '$?' -a -s '$@' && rm -f $@ && ln -s $? $@ upscli_tryconnect.html: upscli_connect.html - test -n "$?" -a -s "$@" && rm -f $@ && ln -s $? $@ + 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 $? $@ + 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 $? $@ + 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 $? $@ + 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 $? $@ + 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 $? $@ + test -n '$?' -a -s '$@' && rm -f $@ && ln -s $? $@ # Drivers related manpages @@ -915,7 +911,7 @@ SRC_SERIAL_PAGES = \ microdowell.txt \ microsol-apc.txt \ nutdrv_hashx.txt \ - nutdrv_siemens_sitop.txt \ + nutdrv_siemens-sitop.txt \ optiups.txt \ powercom.txt \ powerpanel.txt \ @@ -966,7 +962,7 @@ INST_MAN_SERIAL_PAGES = \ microdowell.$(MAN_SECTION_CMD_SYS) \ microsol-apc.$(MAN_SECTION_CMD_SYS) \ nutdrv_hashx.$(MAN_SECTION_CMD_SYS) \ - nutdrv_siemens_sitop.$(MAN_SECTION_CMD_SYS) \ + nutdrv_siemens-sitop.$(MAN_SECTION_CMD_SYS) \ optiups.$(MAN_SECTION_CMD_SYS) \ powercom.$(MAN_SECTION_CMD_SYS) \ powerpanel.$(MAN_SECTION_CMD_SYS) \ @@ -1035,7 +1031,7 @@ INST_HTML_SERIAL_MANS = \ microdowell.html \ microsol-apc.html \ nutdrv_hashx.html \ - nutdrv_siemens_sitop.html \ + nutdrv_siemens-sitop.html \ optiups.html \ powercom.html \ powerpanel.html \ @@ -1621,13 +1617,13 @@ CLEANFILES = linkman-*.txt.tmp* # Note we can have a pre-built file from the tarball, sort of useful when # e.g. no man page building tools are locally available (we might not be -# able to render a new instance though). At least do not let "$@" confuse +# able to render a new instance though). At least do not let '$@' confuse # us into overwriting its instance in srcdir (if differs from builddir). linkman-driver-names.txt: Makefile @echo " GENERATE-LINKMAN $@" @(LC_ALL=C; LANG=C; export LC_ALL LANG; \ for F in $(LINKMAN_PAGES_DRIVERS) ; do echo "$$F" ; done \ - | grep -vE '^nutupsdrv\.txt$$' \ + | $(EGREP) -v '^nutupsdrv\.txt$$' \ | sort -n | uniq \ | sed 's,^\(.*\)\.txt$$,- linkman:\1[$(MAN_SECTION_CMD_SYS)],' ; \ ) > "$(builddir)/$(@F).tmp.$$$$" || exit ; \ @@ -1638,11 +1634,11 @@ linkman-driver-names.txt: Makefile cat "$(srcdir)/$(@F)" "$(builddir)/$(@F).tmp.$$$$" || exit ; \ fi ; \ if test ! -s "$(builddir)/$(@F).tmp.$$$$" ; then echo "- No NUT drivers were built" > "$(builddir)/$(@F).tmp.$$$$" ; fi ; \ - if test ! -s "$(builddir)/$(@F)" || ! diff "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; then \ - mv -f "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; \ - else \ + if test -s "$(builddir)/$(@F)" && diff "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; then \ echo " GENERATE-LINKMAN $@ : SKIP (keep existing file)" ; \ rm -f "$(builddir)/$(@F).tmp.$$$$" ; \ + else \ + mv -f "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; \ fi linkman-drivertool-names.txt: Makefile @@ -1659,11 +1655,11 @@ linkman-drivertool-names.txt: Makefile cat "$(srcdir)/$(@F)" "$(builddir)/$(@F).tmp.$$$$" || exit ; \ fi ; \ if test ! -s "$(builddir)/$(@F).tmp.$$$$" ; then echo "- No NUT driver tools were built" > "$(builddir)/$(@F).tmp.$$$$" ; fi ; \ - if test ! -s "$(builddir)/$(@F)" || ! diff "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; then \ - mv -f "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; \ - else \ + if test -s "$(builddir)/$(@F)" && diff "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; then \ echo " GENERATE-LINKMAN $@ : SKIP (keep existing file)" ; \ rm -f "$(builddir)/$(@F).tmp.$$$$" ; \ + else \ + mv -f "$(builddir)/$(@F).tmp.$$$$" "$(builddir)/$(@F)" ; \ fi # Doing this dynamically as here, so we may later deliver some more dynamic @@ -1836,7 +1832,7 @@ CLEANFILES += *.xml *.html *.pdf DOCBUILD_BEGIN = { \ if test -n "$${A2X_OUTDIR}" && test "$${A2X_OUTDIR}" != '.' ; then \ rm -rf "./$${A2X_OUTDIR}" || true ; \ - test -d "$@" && rm -rf "$@" || true ; \ + test -d '$@' && rm -rf '$@' || true ; \ _CWD="`pwd`" && (cd '$(abs_builddir)' && $(MKDIR_P) "$${_CWD}/$${A2X_OUTDIR}") || exit ; \ for F in $(LINKMAN_INCLUDE_GENERATED) ; do \ if [ -s "./$$F" ] ; then ln -f -s "../../$$F" "./$${A2X_OUTDIR}/" ; else \ @@ -1845,7 +1841,7 @@ DOCBUILD_BEGIN = { \ else A2X_OUTDIR='.' ; fi; \ if test -s "${builddir}/docbook-xsl.css" \ && test -r "${builddir}/docbook-xsl.css" \ - && ! test -w "${builddir}/docbook-xsl.css" \ + && test ! -w "${builddir}/docbook-xsl.css" \ ; then chmod u+w "${builddir}/docbook-xsl.css" ; fi ; \ chmod -R u+w "./$${A2X_OUTDIR}" || true ; \ A2X_VERBOSE="$(ASCIIDOC_VERBOSE)"; if [ "$(V)" = 1 ]; then A2X_VERBOSE="--verbose"; fi; \ @@ -1857,7 +1853,7 @@ DOCBUILD_BEGIN = { \ DOCBUILD_END = { \ if test -n "$${A2X_OUTDIR}" && test "$${A2X_OUTDIR}" != '.' ; then \ chmod -R u+w "./$${A2X_OUTDIR}" || true ; \ - test -d "$@" && rm -rf "$@" || true ; \ + test -d '$@' && rm -rf '$@' || true ; \ for F in $(LINKMAN_INCLUDE_GENERATED) ; do \ rm -f "./$${A2X_OUTDIR}/$$F" || true ; \ done ; \ @@ -1924,7 +1920,7 @@ if KNOWN_UNABLE_MANS .txt.$(MAN_SECTION_CMD_SYS) .txt-prepped.$(MAN_SECTION_CMD_SYS) \ .txt.$(MAN_SECTION_CMD_USR) .txt-prepped.$(MAN_SECTION_CMD_USR) \ .txt.$(MAN_SECTION_MISC) .txt-prepped.$(MAN_SECTION_MISC) : - @if [ -r "$@" -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ + @if [ -r '$@' -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ echo "Not (re)building $@ manual page, since 'asciidoc', 'xmllint' or 'xsltproc' were not functional (even though found)." ; \ else \ echo "Could not find prebuilt $@ manual page." ; \ @@ -1974,7 +1970,7 @@ endif !KNOWN_UNABLE_MANS else !HAVE_ASCIIDOC .txt.html: - @if [ -r "$@" -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ + @if [ -r '$@' -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ echo "Not (re)building $@ manual page (as HTML), since 'asciidoc', 'xmllint' or 'xsltproc' were not found." ; \ else \ echo "Could not find prebuilt $@ manual page (as HTML)." ; \ @@ -1983,7 +1979,7 @@ else !HAVE_ASCIIDOC fi .txt.$(MAN_SECTION_CMD_USR): - @if [ -r "$@" -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ + @if [ -r '$@' -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ echo "Not (re)building $@ manual page, since 'asciidoc', 'xmllint' or 'xsltproc' were not found." ; \ else \ echo "Could not find prebuilt $@ manual page." ; \ @@ -1992,7 +1988,7 @@ else !HAVE_ASCIIDOC fi .txt.$(MAN_SECTION_API): - @if [ -r "$@" -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ + @if [ -r '$@' -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ echo "Not (re)building $@ manual page, since 'asciidoc', 'xmllint' or 'xsltproc' were not found." ; \ else \ echo "Could not find prebuilt $@ manual page." ; \ @@ -2001,7 +1997,7 @@ else !HAVE_ASCIIDOC fi .txt.$(MAN_SECTION_CFG): - @if [ -r "$@" -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ + @if [ -r '$@' -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ echo "Not (re)building $@ manual page, since 'asciidoc', 'xmllint' or 'xsltproc' were not found." ; \ else \ echo "Could not find prebuilt $@ manual page." ; \ @@ -2010,7 +2006,7 @@ else !HAVE_ASCIIDOC fi .txt.$(MAN_SECTION_CMD_SYS): - @if [ -r "$@" -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ + @if [ -r '$@' -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ echo "Not (re)building $@ manual page, since 'asciidoc', 'xmllint' or 'xsltproc' were not found." ; \ else \ echo "Could not find prebuilt $@ manual page." ; \ @@ -2019,7 +2015,7 @@ else !HAVE_ASCIIDOC fi .txt.$(MAN_SECTION_MISC): - @if [ -r "$@" -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ + @if [ -r '$@' -o -r "$(srcdir)/$(@F)" ] $(SRC_PREBUILT_CLAUSE); then \ echo "Not (re)building $@ manual page, since 'asciidoc', 'xmllint' or 'xsltproc' were not found." ; \ else \ echo "Could not find prebuilt $@ manual page." ; \ @@ -2076,7 +2072,7 @@ $(abs_top_builddir)/docs/man/.prep-src-docs: $(PREP_SRC) Makefile if [ x"$(DOCS_NO_MAN)" = xtrue ] ; then \ if [ "$(MAINTAINER_DOCS_PREP_MAN_DELAY)" -gt 0 ] 2>/dev/null ; then \ echo " DOCS_NO_MAN BLOCK: $@ called in docs/man/Makefile : waiting for other thread to complete this target" ; \ - W=0 ; while [ "$${W}" -lt "$(MAINTAINER_DOCS_PREP_MAN_DELAY)" ] && ( [ \! -e '$@' ] || [ x"`find $(PREP_SRC) \! -newer '$@' 2>/dev/null`" = x ] ) ; do sleep 1 ; W="`expr $$W + 1`"; done ; \ + W=0 ; while [ "$${W}" -lt "$(MAINTAINER_DOCS_PREP_MAN_DELAY)" ] && ( [ \! -f '$@' ] || [ x"`find $(PREP_SRC) \! -newer '$@' 2>/dev/null`" = x ] ) ; do sleep 1 ; W="`expr $$W + 1`"; done ; \ echo " DOCS_NO_MAN SKIP: $@ called in docs/man/Makefile : waited $$W seconds" ; \ fi ; \ exit 0 ; \ @@ -2090,7 +2086,7 @@ $(abs_top_builddir)/docs/man/.prep-src-docs: $(PREP_SRC) Makefile 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)" ; \ @@ -2130,9 +2126,9 @@ $(abs_top_builddir)/docs/man/.prep-src-docs: $(PREP_SRC) Makefile else \ COUNT=30 ; \ touch "$@.$$$$" ; \ - while test -e "$@.working" -a "$$COUNT" -gt 0 ; do sleep 1; COUNT="`expr $$COUNT - 1`"; done ; \ + while test -f "$@.working" -a "$$COUNT" -gt 0 ; do sleep 1; COUNT="`expr $$COUNT - 1`"; done ; \ touch "$@.working" ; \ - if test -n "`find "$@" -newer "$@.$$$$" 2>/dev/null`" ; then \ + if test -n "`find '$@' -newer \"$@.$$$$\" 2>/dev/null`" ; then \ rm -f "$@.$$$$" "$@.working" ; \ exit 0; \ fi ; \ @@ -2141,22 +2137,22 @@ $(abs_top_builddir)/docs/man/.prep-src-docs: $(PREP_SRC) Makefile 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)" ; \ cd "$(abs_top_builddir)" ; \ fi ;; \ - "$(srcdir)"/*) F="`echo "$$F" | sed 's#^$(srcdir)/*#./#'`" ;; \ + "$(srcdir)"/*) F="`echo \"$$F\" | sed 's#^$(srcdir)/*#./#'`" ;; \ */*) ;; \ *) \ linkroot="$(abs_builddir)" ; \ linksrcroot="$(abs_srcdir)" ; \ cd "$(abs_top_builddir)" ;; \ esac ; \ - D="`dirname "$$F"`" ; \ + D="`dirname \"$$F\"`" ; \ (cd '$(abs_builddir)' && $(MKDIR_P) "$${linkroot}/$$D") || { rm -f "$@.working" ; exit 1 ; } ; \ - if ! test -e "$${linkroot}/$$F" && test -s "$${linksrcroot}/$$F" ; then \ + if test ! -f "$${linkroot}/$$F" && test ! -h "$${linkroot}/$$F" && test -s "$${linksrcroot}/$$F" ; then \ echo " LN '$${linksrcroot}/$$F' => '$${linkroot}/$$F' (PWD = '`pwd`')" >&2 ; \ ln -fs "$${linksrcroot}/$$F" "$${linkroot}/$$F" || { rm -f "$@.working" ; exit 1 ; } ; \ COUNT="`expr $$COUNT + 1`" ; \ @@ -2167,12 +2163,12 @@ $(abs_top_builddir)/docs/man/.prep-src-docs: $(PREP_SRC) Makefile *) IS_TEXT=true ;; \ esac; \ if $$IS_TEXT ; then \ - grep -w linkman "$${linkroot}/$${F}" > /dev/null || IS_TEXT=false ; \ + $(GREP) -w linkman "$${linkroot}/$${F}" > /dev/null || IS_TEXT=false ; \ fi ; \ case "$$F" in \ *.xml|*.xsl|*.css|*.jpg|*.png|*.pdn|*.svg) ;; \ *.txt|*.adoc|*.in|*.sample|*.conf|*) \ - if $$MAN_SECTIONS_DEFAULT || ! $$IS_TEXT ; then \ + if $$MAN_SECTIONS_DEFAULT || test false = $$IS_TEXT ; then \ sed \ -e 's,\(home page:\) https://www.networkupstools.org/*$$,\1 $(NUT_WEBSITE_BASE)/,' ; \ else \ @@ -2202,7 +2198,7 @@ $(abs_top_builddir)/docs/man/.prep-src-docs: $(PREP_SRC) Makefile COUNT="`expr $$COUNT + 1`" ; \ done ; \ fi ; \ - if test "$$COUNT" -gt 0 -o ! -e "$@" ; then touch "$@" ; fi + if test "$$COUNT" -gt 0 -o ! -f '$@' ; then touch '$@' ; fi @rm -f "$@.working" MAINTAINERCLEANFILES = Makefile.in .dirstamp @@ -2211,7 +2207,7 @@ clean-local: clean-man-pages $(AM_V_at)rm -rf 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/man/apc_modbus.txt b/docs/man/apc_modbus.txt index 55a3d0fdfa..bc756633e8 100644 --- a/docs/man/apc_modbus.txt +++ b/docs/man/apc_modbus.txt @@ -139,7 +139,7 @@ make [NOTE] ====== * Other NUT `configure` options may be needed for proper behavior, such as -`--prefix`, `--with-sysconfdir`, `--with-user` and `--with-group` to match +`--prefix`, `--with-confdir`, `--with-user` and `--with-group` to match your packaged or otherwise preceding NUT installation. ====== diff --git a/docs/man/apcsmart.txt b/docs/man/apcsmart.txt index 5d02c5df10..d68e4aa576 100644 --- a/docs/man/apcsmart.txt +++ b/docs/man/apcsmart.txt @@ -52,12 +52,12 @@ those are pretty fuzzy): and should work just fine with the apcsmart driver. "microlink" models:: - WARNING: these are not _natively_ supported by *apcsmart* (or as of + WARNING: These are not _natively_ supported by *apcsmart* (or as of this writing by *apcupsd*, for that matter, if you're wondering). Around 2007, APC (now APC Schneider) decided to go back to its proprietary roots, and all the new models (SMT, SMX, SURTD) use completely different protocol and cables. If you purchased - a new APC UPS -- that uses cable with RJ45 on the one end, and DB-9 + a new APC UPS -- that uses cable with RJ45 on the one end, and DB-9 on the other -- then you have such model. Your only option to support it through *NUT* is to purchase a "legacy communications card" -- part #AP9620 (google 'AP9620' for more details). Or if that's not diff --git a/docs/man/apcupsd-ups.txt b/docs/man/apcupsd-ups.txt index ebdad2d467..f2e6da78cc 100644 --- a/docs/man/apcupsd-ups.txt +++ b/docs/man/apcupsd-ups.txt @@ -41,6 +41,14 @@ For instance: port = localhost desc = "apcupsd client" +For IPv6 addresses, use square brackets for the hostname part (so colons in +it are not mistaken to be the port, especially if using the default one): + + [apcupsd] + driver = apcupsd-ups + port = "[::1]:3551" + desc = "apcupsd client" + BACKGROUND ---------- diff --git a/docs/man/asciidoc.conf.in b/docs/man/asciidoc.conf.in index 70a5460e8c..2365e84c65 100644 --- a/docs/man/asciidoc.conf.in +++ b/docs/man/asciidoc.conf.in @@ -62,8 +62,43 @@ ifdef::backend-xhtml11[] # Override HTML footer, to include NUT version [footer-text] Last updated {docdate} {doctime} -- Network UPS Tools {nutversion} - -# Format-detection to prevent smartphones from being too smart +# Assume this footer is at the end of the page (after main contents)? +# Have a willing browser run the javascript to add permalinks: + [+docinfo] +# Format-detection to prevent smartphones from being too smart +# JavaScript to add anchor icons to section headers +# Sourced from https://cdn.jsdelivr.net/npm/anchor-js/anchor.min.js on 2025.11.19 +# Originated from https://github.com/bryanbraun/anchorjs (MIT Licensed) +# To add into asciidoc.conf, opening curly braces followed by an alpha +# character had to be escaped (e.g. keep those followed by a quote as is). + +# Match color of headers, that is included into final pages +# from the asciidoc distro, seek there for a file with e.g. +# "Shared CSS for AsciiDoc xhtml11 and html5 backends": + endif::backend-xhtml11[] diff --git a/docs/man/belkin.txt b/docs/man/belkin.txt index d12c90ab68..38bf116710 100644 --- a/docs/man/belkin.txt +++ b/docs/man/belkin.txt @@ -42,7 +42,7 @@ There are dragons lurking within the protocol to this UPS. I have one that essentially behaves like a glorified power strip due to some invasive probing on my part. Don't mess with it directly. -NOTE: the driver doesn't go anywhere near these character sequences, +NOTE: The driver doesn't go anywhere near these character sequences, so it won't zap your UPS. I only mention this here as yet another reminder of the perils of closed hardware. diff --git a/docs/man/genericups.txt b/docs/man/genericups.txt index f3d0b1c5df..2667789806 100644 --- a/docs/man/genericups.txt +++ b/docs/man/genericups.txt @@ -73,7 +73,7 @@ The default behavior of the driver is to exit immediately. If this doesn't reliably trigger a shutdown in your UPS hardware, use this setting to give it more time to react. -NOTE: very large values for +sdtime+ may create warnings from upsdrvctl if +NOTE: Very large values for +sdtime+ may create warnings from upsdrvctl if it gets tired of waiting for the driver to return. CUSTOM CONFIGURATIONS diff --git a/docs/man/mge-shut.txt b/docs/man/mge-shut.txt index 6d5deaabb4..121107a69f 100644 --- a/docs/man/mge-shut.txt +++ b/docs/man/mge-shut.txt @@ -57,8 +57,9 @@ isn't. Some UPSes will restart no matter what, even if the power is (still) out at the moment this timer elapses. In that case, you could try if setting 'ondelay = -1' in *ups.conf* helps. + -WARNING: ondelay parameter was set in ten seconds unit in the legacy mge-shut -driver ( 3 for 30 seconds) . It is now set in seconds ( 30 for 30 seconds). +WARNING: The `ondelay` parameter was set in ten-seconds unit in +the legacy linkman:mge-shut[8] driver (e.g. '3' for 30 seconds). +It is now set in seconds ('30' for 30 seconds). Make sure you use the correct unit in your configuration. *notification*='num':: diff --git a/docs/man/nut.txt b/docs/man/nut.txt index 0df9098597..e420389382 100644 --- a/docs/man/nut.txt +++ b/docs/man/nut.txt @@ -32,13 +32,14 @@ release v2.8.3, it remains not 100% complete) so it is possible to run the whole server and client stack as native Windows programs. Most operating systems deliver NUT as packages built from a certain release, -which for the project is just a better-tested snapshot of on-going development. +which for the project is just a better-tested snapshot of on-going development +with an opinionated choice of build and run-time system configuration options. Some OS distribution maintainers pick fixes from later releases to update their software packages while nominally remaining on the same base line version that the distribution's major release originally went with. This allows to balance delivering stable programs with well-known qualities and minimal downsides for a long stretch of time, but misses out on new -capabilities, features (and yes, new bugs) which the on-going development +capabilities, features (and yes, new bugs), which the on-going development regularly adds to the current code base du-jour. As such, NUT community support for releases (or any other non-current code) @@ -127,7 +128,7 @@ maintained by the community at large. Some standard clients of note include: + NOTE: The `upsmon` client typically splits into two processes: an unprivileged one running with credentials of its `RUN_AS_USER` configured in the - linkman:upsmon.conf[5] file (or a built-in/packaged default like `nut`, + linkman:upsmon.conf[5] file (or a built-in/packaged default like `nut`, `ups`, `monuser` or `nobody`) which does most of the work (including notification scripts called via `NOTIFYCMD` setting), and the part which remains owned by `root` to initiate the system shutdown by calling `SHUTDOWNCMD` when/if @@ -137,9 +138,11 @@ NUT deployments which desire higher-fidelity control (e.g. different systems shutting down after different time spent on battery) often couple the linkman:upsmon[8] with linkman:upssched[8] as their `NOTIFYCMD` handler. + -The "primary" system is usually also responsible for commanding the managed - UPS to shut itself down and power up when the "wall power" returns (or to - reset the load if it is already back), subject to UPS hardware capabilities. +The 'upsmon primary' system waits for 'secondary' systems to disconnect as + part of their shut down, and is usually also responsible for commanding the + managed UPS to shut itself down and power up when the "wall power" returns + (or to reset the load if it is already back), subject to UPS hardware + capabilities. Most client integrations also stall the ultimate reboot/power-off of their machine, if its shut down was triggered by `upsmon`, and reboot after a long delay (if the machine is still alive). @@ -161,6 +164,37 @@ assortment of power devices), using either simple clients like `upsc` or C, C++, Python, Java in a nearby repository) or by third-party efforts (e.g. Perl, Go, C#, REST API). +NUT Drivers during shutdown +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Arguably another layer in this three-layer stack is a separate role for the +NUT Driver programs on the 'linkman:upsmon[8] primary' system as mentioned +above: actually telling the UPS to turn off or power-cycle as part of your +computer's emergency shut down. + +While the long-running services for the NUT drivers, data server and clients +are stopped as part of common OS shutdown, there may be an "endgame" +integration such as high-numbered SysV init scripts (e.g. `/etc/rc0.d/K99nut`) +or the systemd 'shutdown hook' facility, or even something built into the +`initrd` or similar RAM disk image that originally launched the end-user +visible OS and regains control when the OS says it is done. +Please note that not all operating systems offer ways of such integration, +and may resort to other solutions (perhaps calling such logic from the 'stop' +method of `upsmon` init script or service definition). + +Either way, this integration has the effect of detecting that the currently +processed shutdown is due to an `upsmon` 'FSD' activity (e.g. that the flag +file specified by `POWERDOWNFLAG` configuration option in linkman:upsmon.conf[5] +exists and is valid), and launches the NUT driver program(s) again to connect +to the UPS(es) that this system is a 'primary' manager of (and has possibly +out-of-band connections to), just to send them the requested command and exit. + +For an example of such end-game integration please see the `nutshutdown` +script if available in your packaged distribution, or its sources at +https://github.com/networkupstools/nut/blob/master/scripts/systemd/nutshutdown.in +which also implements a solution to avoid the "power race condition" +(for operating systems that do not limit the duration of shutdown activity). + NUT Run-time Nuances ~~~~~~~~~~~~~~~~~~~~ @@ -206,20 +240,28 @@ The message can also be seen from linkman:upsmon[8] being unable to populate the `POWERDOWNFLAG` if the location it points to (`/etc/killpower` by default) does not exist or is read-only, or from a late shutdown integration script like the `nutshutdown` hook if that location was un-mounted by the time it runs. -It is recommended to store that file on a volatile file system (under `/run` -on most modern distributions; typically the `pidpath` is located there too), -which remains until reboot and disappears during reboot. +It is recommended to store that file on a volatile file system (under `/var/run` +or `/run` on most modern distributions; typically the `pidpath` is located there +too), which remains until reboot and disappears during reboot. User/Group Accounts ^^^^^^^^^^^^^^^^^^^ Generally NUT daemon programs avoid running as a highly-privileged `root` -account (on POSIX platforms), and drop privileges to run as the configured +account (on POSIX platforms), although they anticipate being started as such +(most system services are) and drop privileges to run as the configured user and group accounts such as `nut:nut`. * One notable exception is the linkman:upsmon[8] daemon which splits into a `root`-owned process which may trigger the OS shutdown routine, and an unprivileged process which does everything else. +* Default user and group accounts are built-in, selected as options for the + `configure` script when it prepares the build of NUT. Those settings can + be overridden by configuration file or command-line options of respective + NUT daemons, whether to run as a different unprivileged user account (it + may be possible to constrain each NUT service to run in their dedicated + accounts), or to remain capable of everything as `root` (rarely, drivers + are configured this way temporarily to troubleshoot local device access). File system permissions ^^^^^^^^^^^^^^^^^^^^^^^ @@ -235,7 +277,7 @@ for volatile implementations). Note that there are several run-time locations for data and socket files of NUT's privileged and unprivileged programs, and for configuration files. Access to these directories and individual files should be secured according -to NUT documentation; NUT daemons will warn about lax permissions in their +to NUT documentation. NUT daemons will warn about lax permissions in their syslog or console messages. Generally, `root:nut` and `0640` permissions are correct for most of the files (so the run-time NUT programs may only read them but can not rewrite them, not even if there happens to be an @@ -310,8 +352,8 @@ linkman:hosts.conf[5] and linkman:upsset.conf[5] for configuration, and `upsstats-single.html` and `upsstats.html` for HTML UI templates. Other clients, whether delivered by NUT project (linkman:NUT-Monitor[8] -GUI) or co-located (link:https://github.com/networkupstools/wmnut[WMNut]) -or third-party (see https://networkupstools.org/projects.html) would +GUI), or co-located (link:https://github.com/networkupstools/wmnut[WMNut]), +or third-party (see https://networkupstools.org/projects.html), would probably support saving their settings or "favorites". Do not forget to secure access to those files and their copies as well. diff --git a/docs/man/nutdrv_qx.txt b/docs/man/nutdrv_qx.txt index 67c1b92755..d565280649 100644 --- a/docs/man/nutdrv_qx.txt +++ b/docs/man/nutdrv_qx.txt @@ -88,7 +88,7 @@ If you set stayoff in linkman:ups.conf[5] when FSD arises the UPS will call a *s *protocol =* 'string':: Skip autodetection of the protocol to use and only use the one specified. -Supported values: 'bestups', 'gtec', 'hunnox', 'innovart31', 'innovart33', 'masterguard', 'mecer', 'megatec', 'megatec/old', 'mustek', 'q1', 'q2', 'q6', 'voltronic', 'voltronic-axpert', 'voltronic-qs', 'voltronic-qs-hex' and 'zinto'. +Supported values: 'bestups', 'gtec', 'hunnox', 'innovart31', 'innovart33', 'innovatae', 'masterguard', 'mecer', 'megatec', 'megatec/old', 'mustek', 'q1', 'q2', 'q6', 'voltronic', 'voltronic-axpert', 'voltronic-qs', 'voltronic-qs-hex' and 'zinto'. + Run the driver program with the `--help` option to see the exact list of `protocol` values it would currently recognize. @@ -168,8 +168,8 @@ If not specified, the driver defaults to 10%. Only used if *runtimecal* is also specified. -BESTUPS, INNOVART31, INNOVART33, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, Q2, Q6, VOLTRONIC-QS, VOLTRONIC-QS-HEX, ZINTO PROTOCOLS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +BESTUPS, INNOVART31, INNOVART33, INNOVATAE, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, Q2, Q6, VOLTRONIC-QS, VOLTRONIC-QS-HEX, ZINTO PROTOCOLS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *ignoresab*:: Some UPSes incorrectly report the `Shutdown Active' bit as always on, consequently making the driver believe the UPS is nearing a shutdown (and, as a result, ups.status always contains +FSD+... and you know what this means). @@ -217,8 +217,8 @@ Safeguard against talking to the wrong one of several identical UPSes on the sam Note that when changing *ups.id* (through linkman:upsrw[8]) the driver will continue to talk to the UPS with the new 'slave address', but won't claim it again on restart until the *slave_addr* parameter is adjusted. -INNOVART31, INNOVART33, Q1, Q2, Q6 PROTOCOLS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +INNOVART31, INNOVART33, INNOVATAE, Q1, Q2, Q6 PROTOCOLS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *ondelay*:: The acceptable range is +0..599940+ seconds. @@ -515,8 +515,8 @@ Stop a running battery test. (Not available on some hardware) -BESTUPS, INNOVART31, INNOVART33, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, Q2, Q6, ZINTO PROTOCOLS -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +BESTUPS, INNOVART31, INNOVART33, INNOVATAE, MECER, MEGATEC, MEGATEC/OLD, MUSTEK, Q1, Q2, Q6, ZINTO PROTOCOLS +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ *test.battery.start* 'value':: Perform a battery test for the duration of 'value' seconds (truncated to 60 seconds) [+60..5940+]. @@ -525,12 +525,27 @@ Perform a battery test for the duration of 'value' seconds (truncated to 60 seco MASTERGUARD PROTOCOL ~~~~~~~~~~~~~~~~~~~~ +*clear.fault.record*:: +Clear fault/error record + *beeper.enable*:: Enable the UPS beeper. *beeper.disable*:: Disable the UPS beeper. +*test.battery.start.low*:: +Perform a battery test until the battery charge is low. ++ +The difference between the "battery test until battery low" and "deep battery +test" is that the latter is actually a battery calibration test and needs some +preconditions to be met, i.e. the load must be between 30% and 100% and the +starting battery capacity is greater than 99%. ++ +The "Battery test until battery low" can be started anytime. ++ +Feature was tested on Masterguard A1000 and A2000 units. + *test.battery.start* 'value':: Perform a battery test for the duration of 'value' seconds (truncated to 60 seconds) [+0..5940+]. This value is truncated to units of 6 seconds (less than 60 seconds) or 60 seconds (more than 60 seconds). @@ -788,7 +803,7 @@ The driver is supposed to support both "new" A series (A700/1000/2000/3000 and t VOLTRONIC-AXPERT UNITS -~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~ This protocol supports Voltronic Power Axpert inverters, based on P30 protocol (used e.g. in photovoltaic equipment, often paired with an UPS charged by the diff --git a/docs/man/nutdrv_siemens_sitop.txt b/docs/man/nutdrv_siemens-sitop.txt similarity index 95% rename from docs/man/nutdrv_siemens_sitop.txt rename to docs/man/nutdrv_siemens-sitop.txt index 11a2fe4883..dc809ddaee 100644 --- a/docs/man/nutdrv_siemens_sitop.txt +++ b/docs/man/nutdrv_siemens-sitop.txt @@ -1,26 +1,26 @@ -NUTDRV_SIEMENS_SITOP(8) +NUTDRV_SIEMENS-SITOP(8) ======================= NAME ---- -nutdrv_siemens_sitop - Driver for the Siemens SITOP UPS500 series UPS +nutdrv_siemens-sitop - Driver for the Siemens SITOP UPS500 series UPS SYNOPSIS -------- -*nutdrv_siemens_sitop* -h +*nutdrv_siemens-sitop* -h -*nutdrv_siemens_sitop* -a 'UPS_NAME' ['OPTIONS'] +*nutdrv_siemens-sitop* -a 'UPS_NAME' ['OPTIONS'] NOTE: This man page only documents the hardware-specific features of the -*nutdrv_siemens_sitop* driver. For information about the core driver, see +*nutdrv_siemens-sitop* driver. For information about the core driver, see linkman:nutupsdrv[8]. SUPPORTED HARDWARE ------------------ -*nutdrv_siemens_sitop* supports Siemens UPS models from the SITOP UPS500 +*nutdrv_siemens-sitop* supports Siemens UPS models from the SITOP UPS500 series. Some models have a serial port, others have a USB port. The models with USB port actually contain a serial-over-USB chip, diff --git a/docs/man/snmp-ups-dmf.txt b/docs/man/snmp-ups-dmf.txt index c2ba3fa313..145e911193 100644 --- a/docs/man/snmp-ups-dmf.txt +++ b/docs/man/snmp-ups-dmf.txt @@ -153,7 +153,7 @@ is present, the driver will load in alphabetic order all DMF files located in the specified path instead of the default path (e.g. from `/usr/local/share/nut/dmfsnmp.custom/*.dmf`). -NOTE: the `mibs` option, if specified and pointing to a specific MIB mapping, +NOTE: The `mibs` option, if specified and pointing to a specific MIB mapping, should refer to a mapping named in one of the ultimately loaded DMF files. REQUIREMENTS diff --git a/docs/man/upsc.txt b/docs/man/upsc.txt index f63b101252..217009ad43 100644 --- a/docs/man/upsc.txt +++ b/docs/man/upsc.txt @@ -9,11 +9,11 @@ upsc - Lightweight read-only NUT client SYNOPSIS -------- -*upsc* -l | -L ['host'] +*upsc* [-j] -l | -L ['host'] -*upsc* 'ups' ['variable'] +*upsc* [-j] 'ups' ['variable'] -*upsc* -c 'ups' +*upsc* [-j] -c 'ups' DESCRIPTION ----------- @@ -25,6 +25,19 @@ want to include the full interface. OPTIONS ------- +*-j*:: + Output the results in JSON format. This will bypass the standard + text output and print a JSON object to `stdout`. The structure + of the JSON depends on the mode: + +-- + `upsc -j -l`: Returns a JSON array of UPS names. + `upsc -j -L`: Returns a JSON object mapping UPS names to their descriptions. + `upsc -j `: Returns a JSON object of all variables and their values. + `upsc -j `: Returns a single JSON string (the value). + `upsc -j -c `: Returns a JSON array of client addresses. +-- + *-l* 'host':: List all UPS names configured at 'host', one name per line. The hostname @@ -83,6 +96,16 @@ called "mybox", with linkman:upsd[8] running on port '1234': battery.voltage.nominal: 13.6 . . . +To retrieve all variables for "myups" as a JSON object: + + :; upsc -j myups@mybox:1234 + { + "battery.charge": "100.0", + "battery.voltage": "13.9", + "battery.voltage.nominal": "13.6", + ... + } + To list the UPSes configured on this system, along with their descriptions: :; upsc -L diff --git a/docs/man/upsdrvsvcctl.txt b/docs/man/upsdrvsvcctl.txt index 84f85cd6b7..52708b3cd0 100644 --- a/docs/man/upsdrvsvcctl.txt +++ b/docs/man/upsdrvsvcctl.txt @@ -163,11 +163,11 @@ COMMANDS OF UPSDRVCTL NOT (CURRENTLY) APPLICABLE TO UPSDRVSVCCTL Command the UPS driver(s) to run their shutdown sequence. Drivers are stopped according to their `sdorder` value -- see linkman:ups.conf[5]. -WARNING: this will probably power off your computers, so don't +WARNING: This will probably power off your computers, so don't play around with this option. Only use it when your systems are prepared to lose power. -NOTE: refer to linkman:ups.conf[5] for using the *nowait* parameter. +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). diff --git a/docs/man/upsmon.conf.txt b/docs/man/upsmon.conf.txt index d97fefe1a5..0772be515e 100644 --- a/docs/man/upsmon.conf.txt +++ b/docs/man/upsmon.conf.txt @@ -68,13 +68,14 @@ values, and multiply by 3. *FINALDELAY* 'seconds':: -When running in primary mode, upsmon waits this long after sending the -NOTIFY_SHUTDOWN to warn the users. After the timer elapses, it then -runs your SHUTDOWNCMD. By default this is set to 5 seconds. +When handling a forced shutdown (FSD) due to a critical UPS state or +explicit user signal, `upsmon` waits this long after sending the +NOTIFY_SHUTDOWN message to warn the users. After the timer elapses, +it then runs your SHUTDOWNCMD. By default this is set to 5 seconds. + -If you need to let your users do something in between those events, -increase this number. Remember, at this point your UPS battery is -almost depleted, so don't make this too big. +If you need to let your users (or the NOTIFYCMD script) do something +in between those events, increase this number. Remember, at this point +your UPS battery is almost depleted, so don't make this too big. + Alternatively, you can set this very low so you don't wait around when it's time to shut down. Some UPSes don't give much warning for low @@ -271,6 +272,8 @@ COMMBAD;; Communications lost to the UPS SHUTDOWN;; The system is being shutdown +SHUTDOWN_HOSTSYNC;; This primary system began the shutdown, and is waiting for secondaries + REPLBATT;; The UPS battery is bad and needs to be replaced NOCOMM;; A UPS is unavailable (can't be contacted for monitoring) @@ -388,7 +391,8 @@ 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). +in a temporary filesystem (e.g. under `(/var)/run` or even a dedicated +`(/var)/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 @@ -425,7 +429,7 @@ supplies in such cases, and a zero makes the effect of detected "OFF" state immediate. Built-in default value is 30 (seconds), to put an "OFF" state into effect (decrease known-fed supplies count) if it persists for this many seconds. + -NOTE: so far we support the device reporting an "OFF" state which usually +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. @@ -645,7 +649,7 @@ troubleshooting a deployment, without impacting foreground or background running mode directly. Command-line option `-D` can only increase this verbosity level. + -NOTE: if the running daemon receives a `reload` command, presence of the +NOTE: If the running daemon receives a `reload` command, presence of the `DEBUG_MIN NUMBER` value in the configuration file can be used to tune debugging verbosity in the running service daemon (it is recommended to comment it away or set the minimum to explicit zero when done, to avoid diff --git a/docs/man/upsmon.txt b/docs/man/upsmon.txt index 521946b0dd..05c20e29c1 100644 --- a/docs/man/upsmon.txt +++ b/docs/man/upsmon.txt @@ -394,16 +394,16 @@ This design allows you to lose some of your power supplies in a redundant power environment without bringing down the entire system, while still working properly for smaller systems. -UPS TYPES ---------- +UPS CONNECTION TYPES AND UPSMON ROLES +------------------------------------- *upsmon* and linkman:upsd[8] don't always run on the same system. When they -do, any UPSes that are directly attached to the upsmon host should be -monitored in "primary" mode. This makes upsmon take charge of that equipment, -and it will wait for the "secondary" systems to disconnect before shutting -down the local system. This allows the distant systems (monitoring over -the network) to shut down cleanly before `upsdrvctl shutdown` runs locally -and turns them all off. +do, any UPSes that are directly attached to that upsmon host should be +monitored in "primary" mode, which makes that upsmon instance take charge +of that equipment, and it will wait for the "secondary" systems to disconnect +before shutting down the local system. This allows the distant systems (just +monitoring over the network) to shut down cleanly before `upsdrvctl shutdown` +runs locally on the primary system and turns them all off. When upsmon runs as a secondary, it is relying on the distant system to tell it about the state of the UPS. When that UPS goes critical (on battery @@ -423,7 +423,11 @@ should break somehow. This defaults to 15 seconds. If your primary system is shutting down too quickly, set the FINALDELAY interval to something greater than the default 15 seconds. Don't set this too high, or your UPS battery may run out of power before the -primary upsmon process shuts down that system. +primary upsmon process shuts down that system. If you do need more time, +consider starting the shutdown after a short time on battery, for details +see the Timed Shutdowns section. + +For a more technical take, please see the Shutdown Activity Workflow section. TIMED SHUTDOWNS --------------- @@ -500,6 +504,8 @@ by starting another copy of the program with `-c fsd` command line argument. This is useful when you want to initiate a shutdown before the critical stage through some external means, such as linkman:upssched[8]. +For a more technical take, please see the Shutdown Activity Workflow section. + WARNING: Please note that by design, since we require power-cycling the load and don't want some systems to be powered off while others remain running if the "wall power" returns at the wrong moment as usual, the "FSD" @@ -542,11 +548,66 @@ crawling under a desk to find the plug. Note you can also use a dummy SHUTDOWNCMD setting to just report that the systems would shut down at this point, without actually disrupting their work. +For inspiration, you can see the setup done by the NUT Integration Tests suite +under the `tests/NIT` directory in NUT sources, including references to the +shutdown and notification scripts which only log the activity (you may have +to configure at least a trivial NUT build and run `make check-NIT-sandbox` to +generate some of the configuration files -- or inspect the `nit.sh` script +which pieces them together). Notably, see `scripts/misc/notifyme-debug` as +not only a logger, but optionally a wrapper for `upssched` (in test runs), +and `clients/upssched-cmd` as a sample implementation of an linkman:upssched[8] +`CMDSCRIPT` which also focuses on logging. + WARNING: After such "dummy" experiments you may have to restart the NUT data server `upsd` to clear its "FSD" flag for the devices and clients involved, and make sure no files named by `POWERDOWNFLAG` option (e.g. `/etc/killpower`) remain on the `upsmon primary` systems under test. +SHUTDOWN ACTIVITY WORKFLOW +-------------------------- + +Looking into `clients/upsmon.c` sources as the ultimate authority, you +can find that the chain of events during a forced shutdown is. This can +help make sense of the timing variables involved, and notifications sent +(which you may want to handle, perhaps with linkman:upssched[8]): + +* The path to shutdown activity of a host starts when its locally running + `upsmon` client (in any role) decides the power situation is critical, + e.g. by having too few "healthy" power supplies in a real outage, and/or + by seeing `FSD` among `ups.status` tokens -- possibly still "latched" in + the `upsd` data server while you start a new `upsmon` instance, or when + you call `upsmon -c fsd` on that system to simulate the outage; +* Such `upsmon` instance ends up in `forceshutdown()` method; +* There it loops over all UPSes it `MONITOR`s as a `primary`, and calls the + `setfsd()` method for each (causing a local `FSD` notification, if one was + not sent earlier); +* If there were no such UPSes -- we are a secondary, and go into `doshutdown()` + method immediately +* Otherwise we are a primary, and only go into `doshutdown()` method after + first completing the `sync_secondaries()` method, which: + * runs an infinite loop until either there are no other persistent clients + logged on to the data server (`upsd`) for each UPS we are a primary for, + or until `HOSTSYNC` timeout elapses; + * it should issue a `SHUTDOWN_HOSTSYNC` notification if it is going to wait + at all (if there were other clients seen on first loop cycle). +* Finally, in the `doshutdown()` method, it: + * issues a `SHUTDOWN` notification; + * waits for `FINALDELAY`; + * starts the timer for `SHUTDOWNEXIT` (which may be used to force `upsmon` + process to linger after calling `SHUTDOWNCMD` -- e.g. can be used by a + secondary to block the primary from cutting power too early for some use + cases, like safely parking some external machinery); + * calls the `SHUTDOWNCMD` (either directly in mono-process mode, or by + telling the `root`-privileged part to do so in the common case); + * optionally linger, if `SHUTDOWNEXIT` is so configured; + * ultimately exit the daemon (also causes the `root`-privileged part to exit, + by breaking the communications pipe between them). + +Note that if your `upsmon` does split into privileged and unprivileged parts, +all notifications run in the unprivileged context (your handling scripts may +have to `sudo` explicitly if/when/where you want to do something to the system). +Only `SHUTDOWNCMD` is called in privileged context. + DEAD UPSES ---------- diff --git a/docs/man/upssched.conf.txt b/docs/man/upssched.conf.txt index 4dcbdc8b75..b0d38f04c8 100644 --- a/docs/man/upssched.conf.txt +++ b/docs/man/upssched.conf.txt @@ -18,9 +18,30 @@ IMPORTANT NOTES * Contents of this file should be pure ASCII (character codes not in range would be ignored with a warning message). +* Command execution is synchronous (with the called tool process in case + of `EXECUTE` directive, or with the timer process). Consider using your + system shell abilities like `&` to send long-duration handling to the + background and let `upssched` timer daemon continue. This should not + impact `upsmon` daemon, which handles each notification in a separate + sub-process (and so not a problem for immediate `EXECUTE` events). + CONFIGURATION DIRECTIVES ------------------------ +*DEBUG_MIN* 'number':: +Optional. Specify minimal debugging level for `upssched` daemon or client, +e.g. for troubleshooting a deployment without a need to edit consumer +configuration. ++ +Note that command-line option `-D` can only increase this verbosity level. ++ +Also note that the configuration file is processed line by line, so you +may want to set this option early on. ++ +Example: + + DEBUG_MIN 6 + *CMDSCRIPT* 'scriptname':: Required. This must be above any AT lines. This script is used to invoke commands when your timers are triggered. It receives a single @@ -31,20 +52,20 @@ Required. This sets the file name of the socket which will be used for interprocess communications. This should be in a directory where normal users can't create the file, due to the possibility of symlinking and other evil. - -CAUTION: if you are running Solaris or similar, the permissions that ++ +CAUTION: If you are running Solaris or similar, the permissions that upssched sets on this file *are not enough* to keep you safe. If your OS ignores the permissions on a FIFO, then you MUST put this in a protected directory! - -NOTE: by default, linkman:upsmon[8] will run upssched as whatever user -you have defined with RUN_AS_USER in linkman:upsmon.conf[5]. Make sure ++ +NOTE: By default, linkman:upsmon[8] will run `upssched` as whatever user +you have defined with 'RUN_AS_USER'; in linkman:upsmon.conf[5]. Make sure that user can create files and write to files in the path you use for -PIPEFN and LOCKFN. - +'PIPEFN' and 'LOCKFN'. ++ My recommendation: create a special directory for upssched, make it owned by your upsmon user, then use it for both. - ++ The stock version of the upssched.conf ships with PIPEFN disabled to make you visit this portion of the documentation and think about how your system works before potentially opening a security hole. @@ -69,11 +90,33 @@ and 'upsname' match the current activity. Possible values for *START-TIMER* 'timername' 'interval';; Start a timer of 'interval' seconds. When it triggers, it will pass the argument 'timername' as an argument to your -CMDSCRIPT. +CMDSCRIPT. Each invocation starts an independent timer, even +if the 'timername' was already registered and started (note +this can mess up subsequent CANCEL-TIMER operations). + Example: + -Start a timer that will execute when any UPS (*) has been +Start a timer that will execute when any UPS (+*+) has been +gone for 10 seconds + + AT COMMBAD * START-TIMER upsgone 10 + +*START-TIMER-SHARED* 'timername' 'interval';; +Start a timer of 'interval' seconds. When it triggers, it +will pass the argument 'timername' as an argument to your +CMDSCRIPT. Each invocation checks if the 'timername' was already +started, and if so -- appends the current event's `UPSNAME`, +`NOTIFYTYPE` and `NOTIFYMSG` to the list of unique values it +would report via environment variables (as a comma-separated +string for `UPSNAME` and `NOTIFYTYPE`, and tab-separated +sentences for `NOTIFYMSG`) when the timer does execute. ++ +NOTE: Currently this updates the first seen instance with the +'timername' (in case you managed to start many). ++ +Example: ++ +Start a timer that will execute when any UPS (+*+) has been gone for 10 seconds AT COMMBAD * START-TIMER upsgone 10 @@ -83,6 +126,9 @@ Cancel a running timer called 'timername', if possible. If the timer has passed then pass the optional argument 'cmd' to CMDSCRIPT. + +NOTE: This removes the all seen instances with the +'timername' (in case you managed to start many). ++ Example: + If a specific UPS (+myups@localhost+) comes back online, then @@ -90,12 +136,24 @@ stop the timer before it triggers AT COMMOK myups@localhost CANCEL-TIMER upsgone +*CANCEL-TIMER-MATCHED* 'timername' ['cmd'];; +Similar to the above, but tries to only cancel the 'timername' if it +refers to the `UPSNAME` and `NOTIFYTYPE` values passed by caller. +The `NOTIFYMSG` is ignored in this context. ++ +Example: ++ +If any UPS (+*+) reverts to utility power, then stop the timer before it +triggers ONLY if that UPS is associated with the already scheduled timer: + + AT ONLINE * CANCEL-TIMER-MATCHED onbattwarn + *EXECUTE* 'command';; Immediately pass 'command' as an argument to CMDSCRIPT. + Example: + -If any UPS (*) reverts to utility power, then execute +If any UPS (+*+) reverts to utility power, then execute `ups-back-on-line` via CMDSCRIPT. AT ONLINE * EXECUTE ups-back-on-line diff --git a/docs/man/upssched.txt b/docs/man/upssched.txt index 44cdb3d9ae..3b790fbb0f 100644 --- a/docs/man/upssched.txt +++ b/docs/man/upssched.txt @@ -9,11 +9,17 @@ upssched - Timer helper for scheduling events from upsmon SYNOPSIS -------- -*upssched* +*upssched* [OPTIONS] [NOTIFYMSG] NOTE: *upssched* should be run from linkman:upsmon[8] via the NOTIFYCMD. You should never run it directly during normal operations. +*upssched* -l + +List currently tracked timer events, if any. Report as a TAB-separated +table of: 'NAME', 'TIMEOUT_ABS', 'TIMEOUT_REL', 'NOTIFYTYPE', 'UPSNAME', +`NOTIFYMSG`. + DESCRIPTION ----------- @@ -22,6 +28,36 @@ relative to events being monitored by linkman:upsmon[8]. The original purpose was to allow for a shutdown to occur after some fixed period on battery, but there are other uses that are possible. +COMMON OPTIONS +-------------- + +*-h*:: +Show the command-line help message. + +*-V*:: +Show NUT version banner. More details may be available if you also +`export NUT_DEBUG_LEVEL=1` or greater verbosity level. + +*-D*:: +Raise the debugging level. Use this option multiple times for more details. + +OPTIONS +------- + +By default `upssched` processes its configuration file and executes or queues +calls to its `CMDSCRIPT`, or cancels some previously queued item(s), based on +configuration and the `NOTIFYTYPE` it receives. One exception to this is the +queue listing mode `-l`. + +*-l*:: +List pending timers (if any) and exit. + +*NOTIFYMSG*:: +Optionally pass a text message (typically originates from linkman:upsmon[8] +call to `upssched` as its `NOTIFYCMD`) as an environment variable named +`NOTIFYMSG` to the `CMDSCRIPT` launched by `upssched` immediately or after +a timer expires. + INTEGRATION ----------- @@ -45,6 +81,14 @@ If you also want to continue writing to the syslog, just add it in: For a full list of notify flags, see the linkman:upsmon[8] documentation. +Please note that command execution is synchronous (with the called `upssched` +tool process in case of `EXECUTE` directive, or with the timer process). +Consider using your system shell abilities like `&` to send long-duration +handling to the background and let `upssched` timer daemon continue. +This should not impact `upsmon` daemon, which handles each notification +in a separate sub-process (and so not a problem for immediate `EXECUTE` +events). + CONFIGURATION ------------- @@ -60,6 +104,24 @@ to shut down the slaves in a controlled manner. Be sure you cancel the timer if power returns (ONLINE). +EARLY PREPARATION FOR A SHUTDOWN ON UPSMON PRIMARY INSTANCE +----------------------------------------------------------- + +The linkman:upsmon[8] primary instance is responsible for telling the UPS(es) +to power off at the end of emergency shutdown. As such, if there are several +clients, the primary instance raises an "FSD" (Forced Shut Down) flag on the +data server for each UPS it manages, and waits for secondary instances to log +off (or for a timeout to expire). If there are activities that should happen +on the primary upsmon's computer during shutdown which take a long time, you +can use the `FSD` notification to begin those operations while the primary +`upsmon` instance waits for the secondaries to complete their shutdowns. + +If you have several UPSes, you may want to combine several notifications with +the `START-TIMER-SHARED` directive (with a short timeout), so you only react +once. Alternately, if the needed activity varies by the UPS (e.g. custom +remote-device shutdown scripts), you may actually want to use `EXECUTE` rules +right away (and dispatch further work in your `CMDSCRIPT`). + DEBOUNCING EVENTS ----------------- diff --git a/docs/man/upsset.cgi.txt b/docs/man/upsset.cgi.txt index 8c62d2dba2..692e293f97 100644 --- a/docs/man/upsset.cgi.txt +++ b/docs/man/upsset.cgi.txt @@ -52,12 +52,13 @@ If your UPS supports any instant commands, they will be listed in a chooser widget. Pick the one you like and "Issue command" to make it happen. -NOTE: some dangerous commands like "Turn off load" may not happen right +NOTE: Some dangerous commands like "Turn off load" may not happen right away. This is a feature, not a bug. -The apcsmart driver and some others require that you send this command twice -within a short window in order to make it happen. This is to keep you from -accidentally killing your systems by picking the wrong one. +The linkman:apcsmart[8] driver and some others require that you send this +command twice within a short window in order to make it happen. +This is to keep you from accidentally killing your systems by picking the +wrong one. To actually turn off the load, you have to send the command once, then send it again after 3 seconds elapse but before 15 seconds pass. If diff --git a/docs/man/upsstats.cgi.txt b/docs/man/upsstats.cgi.txt index 4215008d15..0d814b049a 100644 --- a/docs/man/upsstats.cgi.txt +++ b/docs/man/upsstats.cgi.txt @@ -21,7 +21,7 @@ DESCRIPTION *upsstats.cgi* uses template files to build web pages containing status information from UPS hardware. It can repeat sections of those template files to -monitor several UPSes simultaneously, or focus on a single UPS. +monitor several devices simultaneously, or focus on a single UPS. These templates can also include references to linkman:upsimage.cgi[8] for graphical displays of battery charge levels, voltage readings, and @@ -48,6 +48,42 @@ When monitoring a single UPS, the file displayed is The format of these files, including the possible commands, is documented in linkman:upsstats.html[5]. +JSON OUTPUT +----------- + +In addition to processing HTML templates, *upsstats.cgi* can be +invoked as a JSON API by adding the *json* parameter to the +query string (e.g., *?json* or *&json*). + +When this parameter is present, the CGI script bypasses all HTML +template parsing and instead returns a JSON object. + +The structure of the JSON output depends on whether the *host* +parameter is also provided: + +* *Multi-host (Default):* + If no *host* parameter is specified, the script returns a + single JSON object containing an *"devices"* key. This key + holds an array, with each element in the array being a JSON + object representing a monitored UPS. + +* *Single-host Mode:* + If a *host* parameter is provided (e.g., + *?host=myups@localhost&json*), the script returns a single + JSON object for that UPS (it will not be wrapped in an + "devices" array). + +In both modes, each UPS object includes: + +* *host*: The UPS identifier (e.g., "myups@localhost") +* *desc*: The host description from ``hosts.conf`` +* *status_raw*: The raw status string (e.g., "OL") +* *status_parsed*: An array of human-readable status strings + (e.g., ["Online"]) +* *vars*: An object containing the full tree of all available + variables for that UPS (e.g., "battery.charge": "100", + "ups.model": "...") + FILES ----- diff --git a/docs/man/usbhid-ups.txt b/docs/man/usbhid-ups.txt index 0f8582e09d..43614f2c7d 100644 --- a/docs/man/usbhid-ups.txt +++ b/docs/man/usbhid-ups.txt @@ -223,6 +223,15 @@ 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... +*powercom_sdcmd_discrete_delay*:: +Some devices (Raptor and Smart KING Pro series) follow the protocol +where we can not set arbitrary value of shutdown delay in seconds +(like in other powercom UPSes), but must use values only from the +"Table of possible delays for Shutdown commands" specified in the +protocol document. This option causes the driver to convert delays +between seconds (in standard NUT variables) and nearest indexed +entry from that table. + *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 @@ -248,7 +257,7 @@ The driver automatically tries to reconnect to the UPS on unexpected error. This parameter (in seconds) allows it to wait before attempting the reconnection. The default value is 0. + -NOTE: for instance, it was found that Eaton MGE Ellipse Max 1500 FR UPS firmware +NOTE: For instance, it was found that Eaton MGE Ellipse Max 1500 FR UPS firmware stops responding every few hours, which causes usbhid-ups driver to detect an libusb insufficient memory error; in this case, when the usbhid-ups driver tries to reconnect too early, the activity sometimes led the UPS firmware to crash and diff --git a/docs/net-protocol.txt b/docs/net-protocol.txt index 65d432401b..46b3eefc8e 100644 --- a/docs/net-protocol.txt +++ b/docs/net-protocol.txt @@ -1,6 +1,11 @@ Network protocol information ============================ +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/net-protocol.html +////////////////////////////////////////////////////////////////////////////// + Since May 2002, this protocol has an official port number from IANA, which is *3493*. The old number (3305) was a relic of the original code's ancestry, and conflicted with other services. Version 0.50.0 and up @@ -10,8 +15,17 @@ This protocol runs over TCP. UDP support was dropped in July 2003. It had been deprecated for some time and was only capable of the simplest query commands as authentication is impossible over a UDP socket. -A library, named libupsclient, that implement this protocol is provided -in both static and shared version to help the client application development. +A C library, named `libupsclient`, that implements this protocol, is provided +in both static and shared version to help the client application development, +and is extensively used by client programs delivered by the Network UPS Tools +project. + +Other bindings maintained in the NUT code base or in its orbit, such as the +C++ library `libnutclient`, a Python `PyNUTClient` module, a PERL `UPS::Nut` +module, and a Java `jNut` client, are also available (but may lag behind in +completeness of the protocol support). Numerous third-party implementations +for other languages also exist, with some related projects listed on the +link:https://networkupstools.org/projects.html[NUT web site pages]. Old command removal notice @@ -86,6 +100,8 @@ still connected when starting the shutdown process. This replaces the old "REQ NUMLOGINS" command. +See also `LIST CLIENT ` to get the actual list of connected clients. + UPSDESC ~~~~~~~ @@ -406,6 +422,8 @@ Response: END LIST CLIENT ups1 +See also `GET NUMLOGINS ` to get just the count of connected clients. + SET --- @@ -792,6 +810,25 @@ For example, `LIST VARS +DESC` would return the current value like now, but it would also append the description of that variable. +Logout pending +~~~~~~~~~~~~~~ + +Add a way for logged-in clients such as linkman:upsmon[8] to tell the +data server that they began their shutdown routine, so eventual loss +of connection would be quickly interpreted as a `LOGOUT` (without any +wait for further timeouts, as a usual disconnection may be treated). + +This is useful in context of +link:https://github.com/networkupstools/nut/pull/3086[PR #3086] +where clients can configure `SHUTDOWNEXIT` behavior to NOT end the +program (and log out) as soon as they tell the OS to shut down, but +rather stay in a heart-beat loop to tell the data server that they +are still alive (for specified time or indefinitely, until the OS +cuts power or networking maybe before it ends the client process), +as a way to delay the primary client proceeding with its shutdown +and cutting the UPS power until some important secondary clients are +actually safely "parked". + Get collection ~~~~~~~~~~~~~~ diff --git a/docs/new-clients.txt b/docs/new-clients.txt index 0ed805622f..21cec6e73c 100644 --- a/docs/new-clients.txt +++ b/docs/new-clients.txt @@ -1,6 +1,11 @@ Creating new client =================== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/new-clients.html +////////////////////////////////////////////////////////////////////////////// + NUT provides bindings for several common languages that are presented below. All these are released under the same license as NUT (the GNU General Public License). diff --git a/docs/new-drivers.txt b/docs/new-drivers.txt index d67bd18127..a390611b9d 100644 --- a/docs/new-drivers.txt +++ b/docs/new-drivers.txt @@ -1,6 +1,11 @@ Creating a new driver to support another device =============================================== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/new-drivers.html +////////////////////////////////////////////////////////////////////////////// + This chapter will present the process of creating a new driver to support another device. @@ -105,6 +110,10 @@ This information is currently used for the startup banner printing and tests. Essential functions ------------------- +For a full current list of functions expected in this context, please see the +`drivers/main.h` file in NUT sources. Some more methods may be required by +driver structure, even if in the common case they have no-op implementations. + upsdrv_initups ~~~~~~~~~~~~~~ @@ -172,6 +181,87 @@ for eventual `exit(EXIT_FAILURE)` and `EF_EXIT_SUCCESS` (`-2`) for `exit(EXIT_SUCCESS)`, which would be handled in the standard driver loop or in `forceshutdown()` method of `main.c`. +addvar, getval and testvar +~~~~~~~~~~~~~~~~~~~~~~~~~~ + +NUT drivers can be configured at run-time with optional settings, which +can be passed on command line or via the linkman:ups.conf[5] file. +Such settings broadly fall into two categories: "values" which convey +some specific value (e.g. `port` or `desc` strings), and "flags" which +can be seen as a sort of booleans which are `true` if mentioned and +`false` if not (any assigned value would be ignored, if not rejected +by configuration parser). + +Some options are common to all drivers and are handled in `drivers/main.c` +(which also defines and implements most of the methods in this area), while +others may be specific to individual drivers or even their sub-drivers. + +The `addvar()` method allows to specify a certain option name, with its +type and a help message string: + + addvar(VAR_VALUE, "sdtype", "Specify simple shutdown method (0-6)"); + + addvar(VAR_FLAG, "wait", "Wait for AC power"); + +In driver code, these calls should comprise the `upsdrv_makevartable()` +method. + +The `getval()` method allows to get the user-provided string value (or +`NULL` -- but it is hard to tell apart if the option was not specified +or no value was assigned). The `testvar()` method allows to check if +the option name was specified (even if without a value), so is better +suited for flags. Internally there is no difference between "values" +and "flags", so historically some flags were "promoted" to values with +boolean-ish strings handled explicitly, in addition to some new features. + +NOTE: Internally, the values can also be marked as reloadable (so that a +running driver program may change them when re-reading the configuration +file due to a signal). The implementation may be currently limited to +common options handled by `main.c` itself. In code base this is exposed +with the `addvar_reloadable()` and several `test*_reloadable()` methods. + +Since these methods involve a data structure walk to locate (or not) the +entry for each option, they should not be used in data processing loops. +Instead, the driver initialization methods such as `upsdrv_initups()` +should consider all values and flags that can impact the set-up of the +driver, and save them into C variables (typically strings, booleans, or +processed and range/value-checked number types) which are then directly +used in the driver program code. + +nut_usb_addvars +~~~~~~~~~~~~~~~ + +For consistency between USB-capable NUT drivers, a set of `addvar()` +options related to USB media setup and expected by `nut_libusb_open()` +can be defined by calling the `nut_usb_addvars()` method, which can be +seen in `drivers/libusb{0,1}.c`. + +Like `addvar()`, a call to this method belongs in `upsdrv_makevartable()` +(see existing drivers for examples). + + +nut_ser_addvars +~~~~~~~~~~~~~~~ + +WARNING: NOT CURRENTLY IMPLEMENTED. + +Issue https://github.com/networkupstools/nut/issues/2748 tracks the idea +to standardize the set and names of options involved in the serial driver +connection set-up (speed, parity, stop-bits, etc.) similarly to the +precedent of `nut_usb_addvars()`. One problem is that many drivers +released during the past decades reinvented this wheel with different +option names. + +nut_modbus_addvars +~~~~~~~~~~~~~~~~~~ + +WARNING: NOT CURRENTLY IMPLEMENTED. + +Issue https://github.com/networkupstools/nut/issues/2419 tracks a similar +idea for Modbus-capable drivers (which may involve or not Serial and/or +USB link and option support, and uniquely TCP/IP RTU support, for example). + + Data types ---------- @@ -348,17 +438,17 @@ must use to manage them. alarm_commit() -- push the value into ups.alarm -NOTE: the ALARM flag in ups.status is automatically set whenever you use -alarm_set. To remove that flag from ups.status, call alarm_init and -alarm_commit without calling alarm_set in the middle. +NOTE: The ALARM flag in `ups.status` is automatically set whenever you use +`alarm_set()`. To remove that flag from `ups.status`, call `alarm_init()` +and `alarm_commit()` without calling `alarm_set()` in the middle. You should never try to set or unset the ALARM flag manually. -If you use UPS alarms, the call to status_commit() should be after -alarm_commit(), otherwise there will be a delay in setting the ALARM -flag in ups.status. +If you use UPS alarms, the call to `status_commit()` should be *after* +`alarm_commit()`, otherwise there will be a delay in setting the ALARM +flag in `ups.status`. -There is no official list of alarm words as of this writing, so don't +There is no official list of alarm keywords as of this writing, so don't use these functions until you check with the upsdev list. Also refer to the <> chapter diff --git a/docs/nut-names.txt b/docs/nut-names.txt index 350df7f9f1..9734799630 100644 --- a/docs/nut-names.txt +++ b/docs/nut-names.txt @@ -3,6 +3,12 @@ NUT variable names and instant commands ======================================= endif::external_title[] +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/nut-names.html +// and subsequent pages (next-next-next...) +////////////////////////////////////////////////////////////////////////////// + [NOTE] .RFC 9271 Recording Document ==== @@ -1024,6 +1030,7 @@ Instant commands | test.failure.stop | Stop simulating a power failure | test.battery.start | Start a battery test | test.battery.start.quick | Start a "quick" battery test +| test.battery.start.low | Start a battery test until battery low | test.battery.start.deep | Start a "deep" battery test | test.battery.stop | Stop the battery test | test.system.start | Start a system test @@ -1073,6 +1080,8 @@ to last as part of NUT standard protocol in the long run. [options="header"] |======================================================================== | Name | Driver/Devices | Description +| clear.fault.record | nutdrv_qx_masterguard => Masterguard + | Clear fault/error record | experimental.test.beeper.start | nutdrv_hashx => Atlantis Land | Start testing the UPS beeper | experimental.test.beeper.stop | nutdrv_hashx => Atlantis Land diff --git a/docs/nut-versioning.adoc b/docs/nut-versioning.adoc index 99e3803062..e3059112f4 100644 --- a/docs/nut-versioning.adoc +++ b/docs/nut-versioning.adoc @@ -1,5 +1,10 @@ -NUT versioning -============== +NUT Semantic Versioning +======================= + +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/versioning.html +////////////////////////////////////////////////////////////////////////////// Historic note ------------- diff --git a/docs/nut.dict b/docs/nut.dict index c8c1b58afc..c808c60917 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 3591 utf-8 +personal_ws-1.1 en 3632 utf-8 AAC AAS ABI @@ -55,6 +55,7 @@ Alexey AllowOverride Alm Amplon +AnchorJS Andreas Andreassen Andrzej @@ -105,6 +106,7 @@ BTEST BTS BTV BUFRD +BUILTIN BUZ BVKnnnnM BVKxxxM @@ -147,6 +149,7 @@ Breiland Brownell Bs BuildBot +BuildRequires Buildbot Bxx ByPass @@ -262,6 +265,7 @@ DEXP DF DHEA DIGYS +DIRNAME DISCHRG DLDIR DLLs @@ -840,6 +844,7 @@ Neus Niels Niklas Niro +NixOS Nobreak Nobreaks Nom @@ -1299,6 +1304,7 @@ SystemIO Systeme Syu Szady +TAE TBD TBR TCIFLUSH @@ -1325,6 +1331,7 @@ TSR TST TT TTT +TUD TXF TXG TXV @@ -1415,6 +1422,7 @@ Uncomment Unices Unitek Upsonic +Upstreamed Ut V'ger VALIGN @@ -1549,6 +1557,7 @@ additionalSOLibSearchPath addons addr addrange +addvar addvars adelsystem adkorte @@ -1562,6 +1571,7 @@ ae aec af afeb +ageing aggregator ai aix @@ -1712,6 +1722,7 @@ bmake bn bool boolean +booleans bootable bootenv bootfs @@ -1779,6 +1790,7 @@ cgroupsv changelog chargetime charset +checkprocnames checkruntime checksum checksums @@ -1822,9 +1834,12 @@ command's commandlen committer comms +compareprocnames compat compilerPath conf +confargs +confdir config configparser configs @@ -1949,6 +1964,7 @@ devscan dfl dhcp dialout +difftime diffutils dir dirpath @@ -1983,6 +1999,7 @@ docinfo docs dod domxml +doshutdown dotnet downloadable dpkg @@ -2155,12 +2172,16 @@ getDevicesVariableValues getTrackingResult getValue getVariable +getaddrinfo getconf getent getenv +gethostbyname getopt +getproctag gettext gettextize +getval getvar gh gif @@ -2228,6 +2249,7 @@ href htaccess htm html +htmlcgi htmlpath http httpd @@ -2287,6 +2309,7 @@ initctl initializer initializers initinfo +initrd initscripts inittime initups @@ -2294,6 +2317,7 @@ inline inlined innotech innovart +innovatae inode inplace installable @@ -2519,6 +2543,7 @@ lz mA mDNS mS +macOS macaddr macosx mailo @@ -2637,6 +2662,7 @@ nSHUTDOWNCMD nabcd nameserver namespace +namespaces nano nanosleep nashkaminski @@ -2779,6 +2805,7 @@ optimizations optiups oq os +osc ostream other's otheruser @@ -2815,6 +2842,7 @@ peername pem perc perl +permalinks pfSense pfexec pfy @@ -2864,6 +2892,7 @@ powermand powermust powernet poweroff +powerp powerpal powerpanel powershell @@ -2889,16 +2918,20 @@ prereqs pretentiousVariableNamingSchemes prgshut printf +prio priv privPassword privProtocol +prjconf problemMatcher probu proc productid prog progname +prognames progs +promille prototyped proxied prtconf @@ -2910,6 +2943,7 @@ pts ptv pty pulizzi +purelib pv pw pwd @@ -2964,6 +2998,7 @@ reindex relatime releasekeyring relicensing +reloadable relogtime remoteip renderer @@ -3062,7 +3097,9 @@ sendline sendmail sendsignal sendsignalfn +sendsignalfnaliases sendsignalpid +sendsignalpidaliases senoidal sequentialized ser @@ -3073,9 +3110,11 @@ servicebypass setFeature setaux setflags +setfsd setgid setinfo setpci +setproctag setq setuid setupCommands @@ -3107,6 +3146,7 @@ ske skel sl slackpkg +slaveaddr slaveid slavesync slibtool @@ -3270,6 +3310,7 @@ termios testime testtime testuser +testvar textproc tgcware tgt diff --git a/docs/nutdrv_qx-subdrivers.txt b/docs/nutdrv_qx-subdrivers.txt index d12b6feb53..642980556a 100644 --- a/docs/nutdrv_qx-subdrivers.txt +++ b/docs/nutdrv_qx-subdrivers.txt @@ -1,6 +1,10 @@ How to make a new subdriver to support another Q* UPS ----------------------------------------------------- +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/new-drivers.html#nutdrv_qx-subdrivers +////////////////////////////////////////////////////////////////////////////// Overall concept ~~~~~~~~~~~~~~~ @@ -1180,6 +1184,7 @@ For more details, have a look at the currently available subdrivers: - +nutdrv_qx_bestups.+{+c+,+h+} - +nutdrv_qx_innovart31.+{+c+,+h+} - +nutdrv_qx_innovart33.+{+c+,+h+} +- +nutdrv_qx_innovatae.+{+c+,+h+} - +nutdrv_qx_masterguard.+{+c+,+h+} - +nutdrv_qx_mecer.+{+c+,+h+} - +nutdrv_qx_megatec.+{+c+,+h+} diff --git a/docs/outlets.txt b/docs/outlets.txt index ee59df1ac9..bea9f19102 100644 --- a/docs/outlets.txt +++ b/docs/outlets.txt @@ -2,6 +2,11 @@ NUT outlets management and PDU notes ==================================== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/user-manual.chunked/outlet_management.html +////////////////////////////////////////////////////////////////////////////// + NUT supports advanced outlets management for any kind of device that proposes it. This chapter introduces how to manage outlets in general, and how to take advantage of the provided features. @@ -84,7 +89,7 @@ NOTE: If you need the scheduling function and your device doesn't support it, you can still use <>. -WARNING: don't plug the UPS's communication cable (USB or network) on a +WARNING: Do not plug the UPS's communication cable (USB or network) on a managed outlet. Otherwise, all computers will be stopped as soon as the communication is lost. diff --git a/docs/packager-guide.txt b/docs/packager-guide.txt index 3950fbd12c..cf44a1a46d 100644 --- a/docs/packager-guide.txt +++ b/docs/packager-guide.txt @@ -2,7 +2,7 @@ NUT Packager and Integrators Guide ================================== Arnaud Quette -WARNING: this is a Work In Progress document. +WARNING: This is a Work In Progress document. Abstract -------- @@ -111,7 +111,7 @@ The following packagers are working on this subject: Jim Klimov - MacOS: Charles Lepple -NOTE: the people below should be contacted to (re)launch discussions! +NOTE: The people below should be contacted to (re)launch discussions! The following packagers should be interested in working on this subject: @@ -247,7 +247,7 @@ libupsclient1-dev - Size: - Deps: -NOTE: the "-dev" suffix is to be replaced by "-devel" on RPM based platforms. +NOTE: The "-dev" suffix is to be replaced by "-devel" on RPM based platforms. [[pkg-nut-cgi]] nut-cgi @@ -265,7 +265,7 @@ nut-scanner - Size: - Deps: -NOTE: hard third-party dependency on `libltdl`; recommends `libsnmp`, `libneon`, +NOTE: Hard third-party dependency on `libltdl`; recommends `libsnmp`, `libneon`, and the `libusb` variant (0.1 or 1.0) it was built against. [[pkg-nut-powerman-pdu]] @@ -284,6 +284,14 @@ nut-snmp - Size: - Deps: +NOTE: With DMF capability merged, two variants of the `snmp-ups` driver can +be built: a legacy-style "monolithic" program with a fixed set of built-in +mapping tables, and `snmp-ups-dmf` with a parsing engine that can load only +the chosen relevant mappings at run-time (and dynamic mapping data files can +be delivered or modified in the field). Distributions may deliver the legacy +driver program file named as `snmp-ups-old`, and symlink the well-known +`snmp-ups` program name pointing to a preferred implementation (legacy or DMF). + [[pkg-nut-xml]] nut-xml ^^^^^^^ @@ -427,7 +435,7 @@ Example: name= "ups" or "nut" ./configure \ --prefix=/ \ - --sysconfdir=/etc/$name \ + --with-confdir=/etc/$name \ --mandir=/usr/share/man \ --libdir=/usr/lib \ --includedir=/usr/include \ @@ -450,4 +458,9 @@ out on new NUT features as they come with new releases. Some may require that you update your build environment with new third-party dependencies, so a broken build of a new NUT release would let you know how to act. +NOTE: For eons, the way to specify the directory for NUT configuration +files was to use `--sysconfdir=/etc/$name`. Since NUT v2.8.5 new options +are available to customize this location and keep the default `sysconfdir` +value intact (maybe unused). + ------------------------------------------------------------------------ diff --git a/docs/qa-guide.adoc b/docs/qa-guide.adoc index 9ec49aaf66..0a7e767a22 100644 --- a/docs/qa-guide.adoc +++ b/docs/qa-guide.adoc @@ -487,6 +487,14 @@ and so ensure non-regression across several Xcode releases. This relies on a few prerequisite packages and a common NUT configuration, as coded in the `.circleci/config.yml` file in the NUT codebase. +CodeQL +~~~~~~ +[[NUT_CI_CODEQL]] + +(Earlier this role was performed by LGTM.com) Run GitHub Actions for static +analysis of C, C++ and Python code and recipes, to produce suggestions based +on common coding flaws and best-practice security patterns. + Travis CI ~~~~~~~~~ [[NUT_CI_TRAVIS]] @@ -512,13 +520,41 @@ gcc and clang, C standards, and requiring to pass builds at least in a mode without warnings (and checking the other cases where any warnings are made fatal). -CodeQL -~~~~~~ -[[NUT_CI_CODEQL]] - -(Earlier this role was performed by LGTM.com) Run GitHub Actions for static -analysis of C, C++ and Python code and recipes, to produce suggestions based -on common coding flaws and best-practice security patterns. +Open Build Service (OBS) +~~~~~~~~~~~~~~~~~~~~~~~~ +[[NUT_CI_OBS]] + +The SuSE-backed software suite and on-line instance of the Open Build +Service (OBS) allows building packages for a wide range of Linux (and +potentially non-Linux) operating system distributions. + +This can be seen as an additional CI test for multi-platform portability, +but mainly allows NUT users to get recent development builds as "proper" +packages. + +* https://build.opensuse.org/ +* https://openbuildservice.org/ + +It is anticipated that corresponding RPM and DEB recipes will evolve to +match code base evolution as well as distribution-provided packages, so +that these builds can be installed as temporary replacements of what the +distro offers (if new features/fixes are required by the user), and that +more and more platforms will get supported (not every one of them provides +all prerequisites at all, or under the same spelling of package names). + +It is also anticipated that beside NUT itself, further related components +would be built in those OBS projects, e.g. the NUT-hosted fork of `libmodbus` +with support of USB RTU until that gets merged into upstream, or clients +like WMNut. + +Resulting packages for several Linux platforms can be seen at: + +* https://build.opensuse.org/project/show/home:networkupstools:nut-releases/nut + for tagged releases +* https://build.opensuse.org/package/show/home:networkupstools:nut-stable/nut + for iterations of the `master` branch +* Sibling OBS projects should automatically spawn and disappear nearby + for pull requests Continuous Integration (NUT CI farm) build agent preparation ------------------------------------------------------------ diff --git a/docs/scheduling.txt b/docs/scheduling.txt index a2db43e8b3..d797a09415 100644 --- a/docs/scheduling.txt +++ b/docs/scheduling.txt @@ -1,6 +1,11 @@ Advanced usage and scheduling notes =================================== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/user-manual.chunked/Advanced_usage_scheduling_notes.html +////////////////////////////////////////////////////////////////////////////// + upsmon can call out to a helper script or program when the device changes state. The example upsmon.conf has a full list of which state changes are available -- ONLINE, ONBATT, LOWBATT, and more. @@ -255,7 +260,7 @@ in your CMDSCRIPT: /sbin/upsmon -c fsd -NOTE: the path to `upsmon` must be provided. The default for an installation +NOTE: The path to `upsmon` must be provided. The default for an installation built from sources is `/usr/local/ups` (so `/usr/local/ups/sbin/upsmon`), while packaged installations will generally comply to link:http://refspecs.linuxfoundation.org/fhs.shtml[FHS -- Filesystem Hierarchy Standard] diff --git a/docs/security.txt b/docs/security.txt index 2f87aa21e5..3c55e94d35 100644 --- a/docs/security.txt +++ b/docs/security.txt @@ -1,6 +1,11 @@ Notes on securing NUT ===================== +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/user-manual.chunked/NUT_Security.html +////////////////////////////////////////////////////////////////////////////// + The NUT Team is very interested in providing the highest security level to its users. diff --git a/docs/snmp-subdrivers.txt b/docs/snmp-subdrivers.txt index 5f3826152d..e1e194dd7b 100644 --- a/docs/snmp-subdrivers.txt +++ b/docs/snmp-subdrivers.txt @@ -1,6 +1,11 @@ How to make a new subdriver to support another SNMP device ---------------------------------------------------------- +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/new-drivers.html#snmp-subdrivers +////////////////////////////////////////////////////////////////////////////// + Overall concept ~~~~~~~~~~~~~~~ diff --git a/docs/sock-protocol.txt b/docs/sock-protocol.txt index 4442dc2a28..f615e92a65 100644 --- a/docs/sock-protocol.txt +++ b/docs/sock-protocol.txt @@ -1,6 +1,11 @@ Driver/server socket protocol ============================= +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/docs/developer-guide.chunked/sock-protocol.html +////////////////////////////////////////////////////////////////////////////// + Here's a brief explanation of the text-based protocol which is used between the drivers and server. diff --git a/docs/support.txt b/docs/support.txt index a90e7e5385..03bcd35861 100644 --- a/docs/support.txt +++ b/docs/support.txt @@ -3,6 +3,10 @@ Support instructions ==================== endif::website[] +////////////////////////////////////////////////////////////////////////////// +// You can find a rendered variant of this document on the web at +// https://networkupstools.org/support.html +////////////////////////////////////////////////////////////////////////////// There are various ways to obtain support for NUT. diff --git a/drivers/Makefile.am b/drivers/Makefile.am index ae36185b0e..1ec3db984c 100644 --- a/drivers/Makefile.am +++ b/drivers/Makefile.am @@ -82,10 +82,14 @@ if HAVE_LINUX_SERIAL_H # Temporary, until ported to more OSes SERIAL_DRIVERLIST += nhs_ser endif HAVE_LINUX_SERIAL_H + +# Note: distributions may deliver this "monolithic" driver program file named +# "snmp-ups-old", and symlink "snmp-ups" pointing to a preferred implementation: SNMP_DRIVERLIST = snmp-ups if WITH_DMFMIB SNMP_DRIVERLIST += snmp-ups-dmf endif + USB_LIBUSB_DRIVERLIST = usbhid-ups bcmxcp_usb tripplite_usb \ blazer_usb richcomm_usb riello_usb powervar_cx_usb \ nutdrv_atcl_usb @@ -156,7 +160,7 @@ sbin_PROGRAMS = upsdrvctl # upsdrvctl: the all-singing all-dancing driver control program upsdrvctl_SOURCES = upsdrvctl.c -upsdrvctl_LDADD = $(LDADD_COMMON) libdummy_upsdrvquery.la +upsdrvctl_LDADD = libdummy_upsdrvquery.la $(LDADD_COMMON) # serial drivers: all of them use standard LDADD and CFLAGS al175_SOURCES = al175.c @@ -440,6 +444,7 @@ NUTDRV_QX_SUBDRIVERS = \ nutdrv_qx_bestups.c nutdrv_qx_blazer-common.c \ nutdrv_qx_innovart31.c \ nutdrv_qx_innovart33.c \ + nutdrv_qx_innovatae.c \ nutdrv_qx_masterguard.c \ nutdrv_qx_mecer.c nutdrv_qx_megatec.c nutdrv_qx_megatec-old.c \ nutdrv_qx_mustek.c nutdrv_qx_q1.c nutdrv_qx_q2.c nutdrv_qx_q6.c \ @@ -467,7 +472,8 @@ dist_noinst_HEADERS = \ upshandler.h usb-common.h usbhid-ups.h powercom-hid.h compaq-mib.h idowell-hid.h \ apcsmart.h apcsmart_tabs.h apcsmart-old.h apcupsd-ups.h cyberpower-mib.h riello.h openups-hid.h \ delta_ups-mib.h nutdrv_qx.h nutdrv_qx_bestups.h nutdrv_qx_blazer-common.h \ - nutdrv_qx_gtec.h nutdrv_qx_innovart31.h nutdrv_qx_innovart33.h nutdrv_qx_masterguard.h nutdrv_qx_mecer.h nutdrv_qx_ablerex.h \ + nutdrv_qx_gtec.h nutdrv_qx_innovart31.h nutdrv_qx_innovart33.h nutdrv_qx_innovatae.h \ + nutdrv_qx_masterguard.h nutdrv_qx_mecer.h nutdrv_qx_ablerex.h \ nutdrv_qx_megatec.h nutdrv_qx_megatec-old.h nutdrv_qx_mustek.h nutdrv_qx_q1.h nutdrv_qx_q2.h nutdrv_qx_q6.h nutdrv_qx_hunnox.h \ nutdrv_qx.h common_voltronic-crc.h nutdrv_qx_voltronic.h \ nutdrv_qx_voltronic-axpert.h nutdrv_qx_voltronic-qs.h nutdrv_qx_voltronic-qs-hex.h \ diff --git a/drivers/adelsystem_cbi.c b/drivers/adelsystem_cbi.c index 11f7a78c7b..9539db01ae 100644 --- a/drivers/adelsystem_cbi.c +++ b/drivers/adelsystem_cbi.c @@ -34,7 +34,7 @@ #endif #define DRIVER_NAME "NUT ADELSYSTEM DC-UPS CB/CBI driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.05" +#define DRIVER_VERSION "0.06" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ @@ -497,6 +497,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/al175.c b/drivers/al175.c index ba462d12ff..5ea80e5689 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.17" +#define DRIVER_VERSION "0.18" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1327,6 +1327,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* no -x flags */ void upsdrv_makevartable(void) { diff --git a/drivers/apc-hid.c b/drivers/apc-hid.c index 0abd0aaa79..6f1826d725 100644 --- a/drivers/apc-hid.c +++ b/drivers/apc-hid.c @@ -32,10 +32,10 @@ #include "apc-hid.h" #include "usb-common.h" -#define APC_HID_VERSION "APC HID 0.100" +#define APC_HID_VERSION "APC HID 0.101" /* APC */ -#define APC_VENDORID 0x051d +#define APC_VENDORID 0x051d /* Tweaks */ static char * tweak_max_report[] = { @@ -99,6 +99,12 @@ static usb_device_id_t apc_usb_device_table[] = { * See https://github.com/networkupstools/nut/issues/1429 */ { USB_DEVICE(APC_VENDORID, 0x0004), disable_interrupt_pipe }, + /* To be introduced in/after 2025, details fuzzy so far + * (e.g. if any tweaks would be needed, or which). + * Placeholder for at least some out-of-the-box support per + * https://github.com/networkupstools/nut/issues/3047 */ + { USB_DEVICE(APC_VENDORID, 0x0005), NULL }, + /* Terminating entry */ { 0, 0, NULL } }; diff --git a/drivers/apc_modbus.c b/drivers/apc_modbus.c index 19d8788725..5f3d77ac1a 100644 --- a/drivers/apc_modbus.c +++ b/drivers/apc_modbus.c @@ -44,12 +44,12 @@ #endif #define DRIVER_NAME "NUT APC Modbus driver " DRIVER_NAME_NUT_MODBUS_HAS_USB_WITH_STR " USB support (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.16" +#define DRIVER_VERSION "0.17" #if defined NUT_MODBUS_HAS_USB /* APC */ -#define APC_VENDORID 0x051D +#define APC_VENDORID 0x051d /* USB IDs device table */ static usb_device_id_t apc_modbus_usb_device_table[] = { @@ -79,7 +79,7 @@ static const HIDNode_t modbus_rtu_usb_usage_tx = 0xff8600fd; /* Variables */ static modbus_t *modbus_ctx = NULL; #if defined NUT_MODBUS_HAS_USB -static USBDevice_t usbdevice; +static USBDevice_t usbdevice = {0}; static USBDeviceMatcher_t *reopen_matcher = NULL; static USBDeviceMatcher_t *regex_matcher = NULL; static USBDeviceMatcher_t *best_matcher = NULL; @@ -1594,6 +1594,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { #if defined NUT_MODBUS_HAS_USB @@ -1659,9 +1664,9 @@ static void _apc_modbus_usb_lib_to_nut(const modbus_usb_device_t *device, USBDev out->VendorID = device->vid; out->ProductID = device->pid; - out->Vendor = device->vendor_str; - out->Product = device->product_str; - out->Serial = device->serial_str; + out->Vendor = device->vendor_str ? strdup(device->vendor_str) : NULL; + out->Product = device->product_str ? strdup(device->product_str) : NULL; + out->Serial = device->serial_str ? strdup(device->serial_str) : NULL; out->bcdDevice = device->bcd_device; res = snprintf(bus_buf, sizeof(bus_buf), "%03u", device->bus); @@ -1983,5 +1988,14 @@ void upsdrv_cleanup(void) #if defined NUT_MODBUS_HAS_USB USBFreeExactMatcher(reopen_matcher); USBFreeExactMatcher(regex_matcher); + + if (usbdevice.Vendor) + free(usbdevice.Vendor); + if (usbdevice.Product) + free(usbdevice.Product); + if (usbdevice.Serial) + free(usbdevice.Serial); + + memset(&usbdevice, 0, sizeof(usbdevice)); #endif /* defined NUT_MODBUS_HAS_USB */ } diff --git a/drivers/apcsmart-old.c b/drivers/apcsmart-old.c index d5ea265fa9..740a41e90c 100644 --- a/drivers/apcsmart-old.c +++ b/drivers/apcsmart-old.c @@ -25,7 +25,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "APC Smart protocol driver (old)" -#define DRIVER_VERSION "2.35" +#define DRIVER_VERSION "2.36" static upsdrv_info_t table_info = { "APC command table", @@ -1490,6 +1490,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_initinfo(void) { const char *pmod, *pser; diff --git a/drivers/apcsmart.c b/drivers/apcsmart.c index 9af6db00e2..e0471f3f8a 100644 --- a/drivers/apcsmart.c +++ b/drivers/apcsmart.c @@ -37,7 +37,7 @@ #include "apcsmart_tabs.h" #define DRIVER_NAME "APC Smart protocol driver" -#define DRIVER_VERSION "3.36" +#define DRIVER_VERSION "3.37" #ifdef WIN32 # ifndef ECANCELED @@ -2154,6 +2154,11 @@ void upsdrv_makevartable(void) addvar(VAR_VALUE, "cshdelay", "CS hack delay"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_help(void) { printf( diff --git a/drivers/apcupsd-ups.c b/drivers/apcupsd-ups.c index 34a2a915b0..0a0bd4cae4 100644 --- a/drivers/apcupsd-ups.c +++ b/drivers/apcupsd-ups.c @@ -18,13 +18,24 @@ #include "config.h" +#define NUT_WANT_INET_NTOP_XX 1 +#include "common.h" + #ifndef WIN32 #include #include #include #include #else /* WIN32 */ +# define SOCK_OPT_CAST (char *) +/* Those 2 files for support of getaddrinfo, getnameinfo and freeaddrinfo + on Windows 2000 and older versions */ +# include +# include +/* This override network system calls to adapt to Windows specificity */ +# define W32_NETWORK_CALL_OVERRIDE #include "wincompat.h" +# undef W32_NETWORK_CALL_OVERRIDE #endif /* WIN32 */ #ifdef HAVE_POLL_H @@ -57,7 +68,7 @@ typedef struct pollfd { #include "nut_stdint.h" #define DRIVER_NAME "apcupsd network client UPS driver" -#define DRIVER_VERSION "0.73" +#define DRIVER_VERSION "0.75" #define POLL_INTERVAL_MIN 10 @@ -70,8 +81,8 @@ upsdrv_info_t upsdrv_info = { { NULL } }; -static uint16_t port=3551; -static struct sockaddr_in host; +static uint16_t port = 3551; /* apcupsd default port */ +static struct addrinfo *host = NULL; static void process(char *item,char *data) { @@ -195,7 +206,7 @@ static int getdata(void) state_get_timestamp((st_tree_timespec_t *)&start); - if (INVALID_FD_SOCK( (p.fd = socket(AF_INET, SOCK_STREAM, 0)) )) + if (INVALID_FD_SOCK( (p.fd = socket(host->ai_family, SOCK_STREAM, 0)) )) { upsdebugx(1,"socket error"); /* return -1; */ @@ -203,7 +214,7 @@ static int getdata(void) goto getdata_return; } - if(connect(p.fd,(struct sockaddr *)&host,sizeof(host))) + if (connect(p.fd, host->ai_addr, host->ai_addrlen)) { upsdebugx(1,"can't connect to apcupsd"); /* close(p.fd); @@ -245,7 +256,7 @@ static int getdata(void) p.events=POLLIN; n=htons(6); - x=write(p.fd,&n,2); + x=write(p.fd,(void*)&n,2); x=write(p.fd,"status",6); /* TODO: double-check for poll() in configure script */ @@ -255,7 +266,7 @@ static int getdata(void) while (WaitForMultipleObjects(1, &event, FALSE, 15000) == WAIT_TIMEOUT) #endif /* WIN32 */ { - if(read(p.fd,&n,2)!=2) + if(read(p.fd,(void*)&n,2)!=2) { upsdebugx(1,"apcupsd communication error"); ret = -1; @@ -334,8 +345,11 @@ static int getdata(void) void upsdrv_initinfo(void) { - if(!port)fatalx(EXIT_FAILURE,"invalid host or port specified!"); - if(getdata())fatalx(EXIT_FAILURE,"can't communicate with apcupsd!"); + if (!port || !host) + fatalx(EXIT_FAILURE,"invalid host or port specified!"); + + if (getdata()) + fatalx(EXIT_FAILURE,"can't communicate with apcupsd!"); else dstate_dataok(); poll_interval = (poll_interval < POLL_INTERVAL_MIN) ? POLL_INTERVAL_MIN : poll_interval; @@ -343,8 +357,10 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { - if(getdata())upslogx(LOG_ERR,"can't communicate with apcupsd!"); - else dstate_dataok(); + if (getdata()) { + upslogx(LOG_ERR,"can't communicate with apcupsd!"); + dstate_datastale(); + } else dstate_dataok(); poll_interval = (poll_interval < POLL_INTERVAL_MIN) ? POLL_INTERVAL_MIN : poll_interval; } @@ -364,14 +380,20 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { } void upsdrv_initups(void) { - char *p; - struct hostent *h; + struct addrinfo hints, *res; + char *p, *namestart = device_path, *nameend = NULL, *portstart = device_path, sport[NI_MAXSERV]; + int v; #ifdef WIN32 WSADATA WSAdata; @@ -379,28 +401,97 @@ void upsdrv_initups(void) atexit((void(*)(void))WSACleanup); #endif /* WIN32 */ - if(device_path&&*device_path) + /* NOTE: in case of errors below we set "port" to 0, + * and bail out with fatalx() in upsdrv_initinfo() */ + if (device_path && *device_path) { - /* TODO: fix parsing since bare IPv6 addresses contain colons */ - if((p=strchr(device_path,':'))) + /* IPv6 */ + if (*device_path == '[') { + namestart++; + nameend = strchr(namestart, ']'); + if (nameend) { + *nameend = '\0'; + portstart = (nameend + 1); + } else { + upslogx(LOG_WARNING, "%s: Seems we were asked for IPv6 address (had an opening bracket, but never a closing one): '%s'", __func__, device_path); + } + } + + /* Look for last colon, since bare IPv6 addresses contain colons too */ + if((p=strrchr(portstart, ':'))) { - int i; - *p++=0; - i=atoi(p); - if(i<1||i>65535)i=0; + int i; + *p++ = '\0'; /* cut off just the host name in device_path */ + i = atoi(p); + if (i<1 || i>65535) + i = 0; port = (uint16_t)i; } } - else device_path="localhost"; - if(!(h=gethostbyname(device_path)))port=0; - else memcpy(&host.sin_addr,h->h_addr,4); + if (!namestart || !(*namestart)) /* did we chop it all off above? */ + namestart = "localhost"; /* keep default port e.g. 3551 was set at start */ + + snprintf(sport, sizeof(sport), "%" PRIuMAX, (uintmax_t)port); + + memset(&hints, 0, sizeof(hints)); + + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = IPPROTO_TCP; + + while ((v = getaddrinfo(namestart, sport, &hints, &res)) != 0) { + upsdebugx(1, "%s: getaddrinfo: %s", __func__, NUT_STRARG(gai_strerror(v))); + + switch (v) + { + case EAI_AGAIN: + continue; + case EAI_NONAME: + upslogx(LOG_WARNING, "%s: Host not found: '%s'", __func__, NUT_STRARG(namestart)); + break; + case EAI_MEMORY: + upslogx(LOG_WARNING, "%s: Insufficient memory", __func__); + break; + case EAI_SYSTEM: + upslog_with_errno(LOG_WARNING, "%s: System error happened during getaddrinfo()", __func__); + break; + default: + upslog_with_errno(LOG_WARNING, "%s: Unknown error happened during getaddrinfo()", __func__); + break; + } + + port = 0; /* abort later in upsdrv_initinfo() */ + res = NULL; /* whatever got populated into the pointer is anyway undefined */ + break; + } + + if (res) + { + /* TODO: loop, connect, retry? + * See the more elaborate examples in NUT code (upsclient) or + * https://man7.org/linux/man-pages/man3/getaddrinfo.3.html + */ + if (res->ai_next) { + upslogx(LOG_WARNING, "%s: Host %s does not map to a unique address; " + "we only tried the first hit", __func__, NUT_STRARG(namestart)); + } + } + + /* Maybe loop above; end up with what we have (or don't) */ + if (port) { + host = res; /* Keep until upsdrv_cleanup()... may be done better */ - /* TODO: add IPv6 support */ - host.sin_family=AF_INET; - host.sin_port=htons(port); + upslogx(LOG_INFO, "Will poll apcupsd at IPv%s address %s port %" PRIu16, + (host->ai_family == AF_INET ? "4" : (host->ai_family == AF_INET6 ? "6" : "?")), + NUT_STRARG(inet_ntopAI(host)), port); + } else if(res) { + freeaddrinfo(res); + } } void upsdrv_cleanup(void) { + if (host) + freeaddrinfo(host); } diff --git a/drivers/arduino-hid.c b/drivers/arduino-hid.c index c63fff7573..4aa47c685d 100644 --- a/drivers/arduino-hid.c +++ b/drivers/arduino-hid.c @@ -41,7 +41,7 @@ /* Arduino */ #define ARDUINO_VENDORID 0x2341 -#define ARDUINO_VENDORID2 0x2A03 +#define ARDUINO_VENDORID2 0x2a03 /* USB IDs device table */ static usb_device_id_t arduino_usb_device_table[] = { diff --git a/drivers/asem.c b/drivers/asem.c index 6ec1990eb2..6b530db70f 100644 --- a/drivers/asem.c +++ b/drivers/asem.c @@ -67,7 +67,7 @@ #endif #define DRIVER_NAME "ASEM" -#define DRIVER_VERSION "0.15" +#define DRIVER_VERSION "0.17" /* Valid on ASEM PB1300 UPS */ #define BQ2060_ADDRESS 0x0B @@ -101,9 +101,12 @@ upsdrv_info_t upsdrv_info = { void upsdrv_initinfo(void) { - __s32 i2c_status; - __u8 buffer[10]; - unsigned short year, month, day; + __s32 i2c_status; + __u8 buffer[10], DeviceName_buffer[10]; + char *DeviceName, *option; + unsigned short year, month, day; + unsigned int i; + unsigned long x; #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXTRA_SEMI_STMT) # pragma GCC diagnostic push @@ -118,6 +121,52 @@ void upsdrv_initinfo(void) # pragma GCC diagnostic pop #endif + /* Get ManufacturerName */ + memset(DeviceName_buffer, 0, 10); + i2c_status = i2c_smbus_read_block_data(upsfd, 0x20, DeviceName_buffer); + if (i2c_status == -1) { + fatal_with_errno(EXIT_FAILURE, "Could not read DeviceName block data"); + } + i = 0; + while ( (DeviceName = valid_devicename_data[i++]) ) { + if (0 == memcmp(DeviceName, DeviceName_buffer, i2c_status)) + break; + } + if (!DeviceName) { + fatal_with_errno(EXIT_FAILURE, "Device '%s' unknown", (char *) DeviceName_buffer); + } + upsdebugx(1, "Found device '%s' on port '%s'", (char *) DeviceName, device_path); + dstate_setinfo("ups.mfr", "%s", (char *) DeviceName); + + option = getval("lb"); + if (option) { + x = strtoul(option, NULL, 0); + if ((x == 0) && (errno != 0)) { + upslogx(LOG_WARNING, "Invalid value specified for low battery threshold: '%s'", option); + } else { + lb_threshold = x; + } + } + option = getval("hb"); + if (option) { + x = strtoul(option, NULL, 0); + if ((x == 0) && (errno != 0)) { + upslogx(LOG_WARNING, "Invalid value specified for high battery threshold: '%s'", option); + } else if ((x < 1) || (x > 100)) { + upslogx(LOG_WARNING, "Invalid value specified for high battery threshold: '%s' (must be 1 < hb <= 100)", option); + } else { + hb_threshold = x; + } + } + /* Invalid values specified */ + if (lb_threshold > hb_threshold) { + upslogx(LOG_WARNING, "lb > hb specified in options. Returning to defaults."); + lb_threshold = LOW_BATTERY_THRESHOLD; + hb_threshold = HIGH_BATTERY_THRESHOLD; + } + + upslogx(LOG_NOTICE, "High battery threshold is %lu, low battery threshold is %lu", lb_threshold, hb_threshold); + /* Set capacity mode in mA(h) */ i2c_status = i2c_smbus_read_word_data(upsfd, 0x03); if (i2c_status == -1) { @@ -356,6 +405,11 @@ void upsdrv_help(void) printf(" hb = " __XSTR__(HIGH_BATTERY_THRESHOLD) " (battery is high above this level)\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { @@ -365,76 +419,10 @@ void upsdrv_makevartable(void) void upsdrv_initups(void) { - __s32 i2c_status; - __u8 DeviceName_buffer[10]; - unsigned int i; - unsigned long x; - char *DeviceName; - char *option; - upsfd = open(device_path, O_RDWR); if (upsfd < 0) { fatal_with_errno(EXIT_FAILURE, "Could not open device port '%s'", device_path); } - -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXTRA_SEMI_STMT) -# pragma GCC diagnostic push -#endif -#ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXTRA_SEMI_STMT -# pragma GCC diagnostic ignored "-Wextra-semi-stmt" -#endif - /* Current definition of this macro ends with a brace; - * we keep the useless trailing ";" for readability */ - ACCESS_DEVICE(upsfd, BQ2060_ADDRESS); -#if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_EXTRA_SEMI_STMT) -# pragma GCC diagnostic pop -#endif - - /* Get ManufacturerName */ - memset(DeviceName_buffer, 0, 10); - i2c_status = i2c_smbus_read_block_data(upsfd, 0x20, DeviceName_buffer); - if (i2c_status == -1) { - fatal_with_errno(EXIT_FAILURE, "Could not read DeviceName block data"); - } - i = 0; - while ( (DeviceName = valid_devicename_data[i++]) ) { - if (0 == memcmp(DeviceName, DeviceName_buffer, i2c_status)) - break; - } - if (!DeviceName) { - fatal_with_errno(EXIT_FAILURE, "Device '%s' unknown", (char *) DeviceName_buffer); - } - upsdebugx(1, "Found device '%s' on port '%s'", (char *) DeviceName, device_path); - dstate_setinfo("ups.mfr", "%s", (char *) DeviceName); - - option = getval("lb"); - if (option) { - x = strtoul(option, NULL, 0); - if ((x == 0) && (errno != 0)) { - upslogx(LOG_WARNING, "Invalid value specified for low battery threshold: '%s'", option); - } else { - lb_threshold = x; - } - } - option = getval("hb"); - if (option) { - x = strtoul(option, NULL, 0); - if ((x == 0) && (errno != 0)) { - upslogx(LOG_WARNING, "Invalid value specified for high battery threshold: '%s'", option); - } else if ((x < 1) || (x > 100)) { - upslogx(LOG_WARNING, "Invalid value specified for high battery threshold: '%s' (must be 1 < hb <= 100)", option); - } else { - hb_threshold = x; - } - } - /* Invalid values specified */ - if (lb_threshold > hb_threshold) { - upslogx(LOG_WARNING, "lb > hb specified in options. Returning to defaults."); - lb_threshold = LOW_BATTERY_THRESHOLD; - hb_threshold = HIGH_BATTERY_THRESHOLD; - } - - upslogx(LOG_NOTICE, "High battery threshold is %lu, low battery threshold is %lu", lb_threshold, hb_threshold); } void upsdrv_cleanup(void) diff --git a/drivers/bcmxcp.c b/drivers/bcmxcp.c index 80ce0b94a8..efb108f3dd 100644 --- a/drivers/bcmxcp.c +++ b/drivers/bcmxcp.c @@ -116,7 +116,7 @@ TODO List: #include "bcmxcp.h" #define DRIVER_NAME "BCMXCP UPS driver" -#define DRIVER_VERSION "0.38" +#define DRIVER_VERSION "0.39" #define MAX_NUT_NAME_LENGTH 128 #define NUT_OUTLET_POSITION 7 @@ -2221,6 +2221,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/bcmxcp_usb.c b/drivers/bcmxcp_usb.c index 24bc4781a0..7a2c3730d2 100644 --- a/drivers/bcmxcp_usb.c +++ b/drivers/bcmxcp_usb.c @@ -26,13 +26,13 @@ upsdrv_info_t comm_upsdrv_info = { #define MAX_TRY_OPENUSB 32 /* Powerware */ -#define POWERWARE 0x0592 +#define POWERWARE_VENDORID 0x0592 /* Phoenixtec Power Co., Ltd */ -#define PHOENIXTEC 0x06da +#define PHOENIXTEC_VENDORID 0x06da /* Hewlett Packard */ -#define HP_VENDORID 0x03f0 +#define HP_VENDORID 0x03f0 static USBDevice_t curDevice; @@ -81,10 +81,10 @@ static void *phoenixtec_ups(USBDevice_t *device) { /* USB IDs device table */ static usb_device_id_t pw_usb_device_table[] = { /* various models */ - { USB_DEVICE(POWERWARE, 0x0002), &powerware_ups }, + { USB_DEVICE(POWERWARE_VENDORID, 0x0002), &powerware_ups }, /* various models */ - { USB_DEVICE(PHOENIXTEC, 0x0002), &phoenixtec_ups }, + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0002), &phoenixtec_ups }, /* T500 */ { USB_DEVICE(HP_VENDORID, 0x1f01), &phoenixtec_ups }, diff --git a/drivers/belkin.c b/drivers/belkin.c index e783fa25ad..24211a93b9 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.29" +#define DRIVER_VERSION "0.30" static ssize_t init_communication(void); static ssize_t get_belkin_reply(char *buf); @@ -486,6 +486,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { } diff --git a/drivers/belkinunv.c b/drivers/belkinunv.c index 5f56264954..a906e435cc 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.12" +#define DRIVER_VERSION "0.13" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1357,6 +1357,11 @@ void upsdrv_help(void) printf(" input.transfer.high: (in V)\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/bestfcom.c b/drivers/bestfcom.c index df64bc7c04..5608cca0b1 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.17" +#define DRIVER_VERSION "0.18" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -512,6 +512,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + static void sync_serial(void) { char buffer[10]; diff --git a/drivers/bestfortress.c b/drivers/bestfortress.c index 6573acf4a6..174009a9f6 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.13" +#define DRIVER_VERSION "0.15" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -120,6 +120,9 @@ void upsdrv_initinfo(void) dstate_setflags("battery.runtime.low", ST_FLAG_STRING | ST_FLAG_RW); dstate_setaux("battery.runtime.low", 3); + /* Set early so that it is in place for shutdown. */ + dstate_setinfo("ups.delay.shutdown", "%s", shutdown_delay); + upsh.instcmd = instcmd; upsh.setvar = upsdrv_setvar; @@ -573,6 +576,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { @@ -627,9 +635,6 @@ void upsdrv_initups(void) upsdebugx(1, "%s: opened %s speed %s upsfd %d", __func__, device_path, speed_val ? speed_val : "DEFAULT", upsfd); - /* Set early so that it is in place for shutdown. */ - dstate_setinfo("ups.delay.shutdown", "%s", shutdown_delay); - upsdebugx(1, "%s: end", __func__); } diff --git a/drivers/bestuferrups.c b/drivers/bestuferrups.c index ed5e4ba9bb..6ae94ac6cc 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.08" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -77,12 +77,103 @@ static struct { /* Forward decls */ +static ssize_t execute(const char *cmd, char *result, size_t resultsize); static int instcmd(const char *cmdname, const char *extra); +static void sync_serial(void); +static void ups_sync(void); /* Set up all the funky shared memory stuff used to communicate with upsd */ void upsdrv_initinfo (void) { + char temp[256], fcstring[512]; + /* now set up room for all future variables that are supported */ + sync_serial(); + ups_sync(); + + fc.model = UNKNOWN; + /* Obtain Model */ + if (execute("id\r", fcstring, sizeof(fcstring)) < 1) { + fatalx(EXIT_FAILURE, "Failed execute in ups_ident()"); + } + + /* response is a one-line packed string starting with $ */ + if (memcmp(fcstring, "Unit", 4)) { + fatalx(EXIT_FAILURE, + "Bad response from formatconfig command in ups_ident()\n" + "id: %s\n", fcstring + ); + } + + /* FIXME: upsdebugx() */ + if (debugging) + fprintf(stderr, "id: %s\n", fcstring); + + /* chars 4:2 are a two-digit ascii hex enumerated model code */ + memcpy(temp, fcstring+9, 2); + temp[2] = '\0'; + + if (memcmp(temp, "ME", 2) == 0) { + fc.model = ME3100; + } else if ((memcmp(temp, "RE", 2) == 0)) { + fc.model = RE1800; + } else if (memcmp(temp, "C1", 2) == 0) { + /* Better way to identify unit is using "d 15\r", which results in + "15 M# MD1KVA", "id\r" yields "Unit ID "C1K03588"" */ + fc.model = MD1KVA; + } + + switch(fc.model) { + case ME3100: + fc.va = 3100; + fc.watts = 2200; + /* determine shutdown battery voltage */ + if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) { + sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts); + } + /* determine fully charged battery voltage */ + if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) { + sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts); + } + fc.fullvolts = 54.20; + /* determine "ideal" voltage by a guess */ + fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts; + break; + case RE1800: + fc.va = 1800; + fc.watts = 1200; + /* determine shutdown battery voltage */ + if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) { + sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts); + } + /* determine fully charged battery voltage */ + if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) { + sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts); + } + fc.fullvolts = 54.20; + /* determine "ideal" voltage by a guess */ + fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts; + break; + case MD1KVA: + fc.va = 1100; + fc.watts = 770; /* Approximate, based on 0.7 power factor */ + /* determine shutdown battery voltage */ + if (execute("d 27\r", fcstring, sizeof(fcstring)) > 0) { + sscanf(fcstring, "27 LowBatt %f", &fc.emptyvolts); + } + /* determine fully charged battery voltage */ + if (execute("d 28\r", fcstring, sizeof(fcstring)) > 0) { + sscanf(fcstring, "28 Hi Batt %f", &fc.fullvolts); + } + fc.fullvolts = 13.70; + /* determine "ideal" voltage by a guess */ + fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts; + break; + default: + fatalx(EXIT_FAILURE, "Unknown model %s in ups_ident()", temp); + } + + fc.valid = 1; dstate_setinfo("ups.mfr", "%s", "Best Power"); switch(fc.model) @@ -411,6 +502,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + static void sync_serial(void) { char buffer[10]; @@ -448,104 +544,14 @@ static void setup_serial(void) if (tcsetattr(upsfd, TCSANOW, &tio) == -1) fatal_with_errno(EXIT_FAILURE, "tcsetattr"); /* end code stolen from bestups.c */ - - sync_serial(); } void upsdrv_initups (void) { - char temp[256], fcstring[512]; - upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, B1200); setup_serial(); - ups_sync(); - - fc.model = UNKNOWN; - /* Obtain Model */ - if (execute("id\r", fcstring, sizeof(fcstring)) < 1) { - fatalx(EXIT_FAILURE, "Failed execute in ups_ident()"); - } - - /* response is a one-line packed string starting with $ */ - if (memcmp(fcstring, "Unit", 4)) { - fatalx(EXIT_FAILURE, - "Bad response from formatconfig command in ups_ident()\n" - "id: %s\n", fcstring - ); - } - - /* FIXME: upsdebugx() */ - if (debugging) - fprintf(stderr, "id: %s\n", fcstring); - - /* chars 4:2 are a two-digit ascii hex enumerated model code */ - memcpy(temp, fcstring+9, 2); - temp[2] = '\0'; - - if (memcmp(temp, "ME", 2) == 0) { - fc.model = ME3100; - } else if ((memcmp(temp, "RE", 2) == 0)) { - fc.model = RE1800; - } else if (memcmp(temp, "C1", 2) == 0) { - /* Better way to identify unit is using "d 15\r", which results in - "15 M# MD1KVA", "id\r" yields "Unit ID "C1K03588"" */ - fc.model = MD1KVA; - } - - switch(fc.model) { - case ME3100: - fc.va = 3100; - fc.watts = 2200; - /* determine shutdown battery voltage */ - if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) { - sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts); - } - /* determine fully charged battery voltage */ - if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) { - sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts); - } - fc.fullvolts = 54.20; - /* determine "ideal" voltage by a guess */ - fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts; - break; - case RE1800: - fc.va = 1800; - fc.watts = 1200; - /* determine shutdown battery voltage */ - if (execute("d 29\r", fcstring, sizeof(fcstring)) > 0) { - sscanf(fcstring, "29 LowBat %f", &fc.emptyvolts); - } - /* determine fully charged battery voltage */ - if (execute("d 31\r", fcstring, sizeof(fcstring)) > 0) { - sscanf(fcstring, "31 HiBatt %f", &fc.fullvolts); - } - fc.fullvolts = 54.20; - /* determine "ideal" voltage by a guess */ - fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts; - break; - case MD1KVA: - fc.va = 1100; - fc.watts = 770; /* Approximate, based on 0.7 power factor */ - /* determine shutdown battery voltage */ - if (execute("d 27\r", fcstring, sizeof(fcstring)) > 0) { - sscanf(fcstring, "27 LowBatt %f", &fc.emptyvolts); - } - /* determine fully charged battery voltage */ - if (execute("d 28\r", fcstring, sizeof(fcstring)) > 0) { - sscanf(fcstring, "28 Hi Batt %f", &fc.fullvolts); - } - fc.fullvolts = 13.70; - /* determine "ideal" voltage by a guess */ - fc.idealbvolts = ((fc.fullvolts - fc.emptyvolts) * 0.7) + fc.emptyvolts; - break; - default: - fatalx(EXIT_FAILURE, "Unknown model %s in ups_ident()", temp); - } - - fc.valid = 1; - return; } void upsdrv_cleanup(void) diff --git a/drivers/bestups.c b/drivers/bestups.c index 9c34a94b87..4421661326 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.11" +#define DRIVER_VERSION "1.12" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -440,6 +440,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "nombattvolt", "Override nominal battery voltage"); diff --git a/drivers/bicker_ser.c b/drivers/bicker_ser.c index 334a3a83e1..d9a14cf760 100644 --- a/drivers/bicker_ser.c +++ b/drivers/bicker_ser.c @@ -108,7 +108,7 @@ #include "serial.h" #define DRIVER_NAME "Bicker serial protocol" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.06" #define BICKER_SOH 0x01 #define BICKER_EOT 0x04 @@ -731,6 +731,35 @@ static int bicker_setvar(const char *varname, const char *val) void upsdrv_initinfo(void) { char string[BICKER_MAXDATA + 1]; + BickerParameter parameter; + const BickerMapping *mapping; + unsigned i; + + 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); + } dstate_setinfo("device.type", "ups"); @@ -888,46 +917,20 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(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) diff --git a/drivers/blazer_ser.c b/drivers/blazer_ser.c index e7095e7604..b96a45e2d7 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.64" +#define DRIVER_VERSION "1.65" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -109,6 +109,12 @@ void upsdrv_help(void) } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "cablepower", "Set cable power for serial interface"); diff --git a/drivers/blazer_usb.c b/drivers/blazer_usb.c index 2e4656589a..08ce026931 100644 --- a/drivers/blazer_usb.c +++ b/drivers/blazer_usb.c @@ -37,7 +37,7 @@ #endif /* WIN32 */ #define DRIVER_NAME "Megatec/Q1 protocol USB driver" -#define DRIVER_VERSION "0.23" +#define DRIVER_VERSION "0.24" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -49,6 +49,39 @@ upsdrv_info_t upsdrv_info = { { NULL } }; +/* Unregistered vendor 0x0001 (commonly identified as Fry's Electronics) */ +#define NONAME0001_VENDORID 0x0001 + +/* Unregistered vendor 0xFFFF */ +#define NONAMEFFFF_VENDORID 0xffff + +/* ST Microelectronics */ +#define STMICRO_VENDORID 0x0483 + +/* Sysgration Ltd. */ +#define SYSGRATION_VENDORID 0x05b8 + +/* Cypress Semiconductor */ +#define CYPRESS_VENDORID 0x0665 + +/* Phoenixtec Power Co., Ltd */ +#define PHOENIXTEC_VENDORID 0x06da + +/* Lakeview Research */ +#define LAKEVIEW_VENDORID 0x0925 + +/* Unitek UPS Systems */ +#define UNITEK_VENDORID 0x0f03 + +/* GE */ +#define GE_VENDORID 0x14f0 + +/* QinHeng Electronics */ +#define QINHENG_VENDORID 0x1a86 + +/* Legrand */ +#define LEGRAND_VENDORID 0x1cb0 + #ifndef TESTING static usb_communication_subdriver_t *usb = &usb_subdriver; @@ -390,18 +423,18 @@ static void *phoenix_subdriver(USBDevice_t *device) static usb_device_id_t blazer_usb_id[] = { - { USB_DEVICE(0x05b8, 0x0000), &cypress_subdriver }, /* Agiler UPS */ - { USB_DEVICE(0x0001, 0x0000), &krauler_subdriver }, /* Krauler UP-M500VA */ - { USB_DEVICE(0xffff, 0x0000), &krauler_subdriver }, /* Ablerex 625L USB */ - { USB_DEVICE(0x0665, 0x5161), &cypress_subdriver }, /* Belkin F6C1200-UNV */ - { USB_DEVICE(0x06da, 0x0002), &cypress_subdriver }, /* Online Yunto YQ450 */ - { USB_DEVICE(0x06da, 0x0003), &ippon_subdriver }, /* Mustek Powermust */ - { USB_DEVICE(0x06da, 0x0004), &cypress_subdriver }, /* Phoenixtec Innova 3/1 T */ - { USB_DEVICE(0x06da, 0x0005), &cypress_subdriver }, /* Phoenixtec Innova RT */ - { USB_DEVICE(0x06da, 0x0201), &cypress_subdriver }, /* Phoenixtec Innova T */ - { USB_DEVICE(0x06da, 0x0601), &phoenix_subdriver }, /* Online Zinto A */ - { USB_DEVICE(0x0f03, 0x0001), &cypress_subdriver }, /* Unitek Alpha 1200Sx */ - { USB_DEVICE(0x14f0, 0x00c9), &phoenix_subdriver }, /* GE EP series */ + { USB_DEVICE(SYSGRATION_VENDORID, 0x0000), &cypress_subdriver }, /* Agiler UPS */ + { USB_DEVICE(NONAME0001_VENDORID, 0x0000), &krauler_subdriver }, /* Krauler UP-M500VA */ + { USB_DEVICE(NONAMEFFFF_VENDORID, 0x0000), &krauler_subdriver }, /* Ablerex 625L USB */ + { USB_DEVICE(CYPRESS_VENDORID, 0x5161), &cypress_subdriver }, /* Belkin F6C1200-UNV */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0002), &cypress_subdriver }, /* Online Yunto YQ450 */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0003), &ippon_subdriver }, /* Mustek Powermust */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0004), &cypress_subdriver }, /* Phoenixtec Innova 3/1 T */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0005), &cypress_subdriver }, /* Phoenixtec Innova RT */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0201), &cypress_subdriver }, /* Phoenixtec Innova T */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0601), &phoenix_subdriver }, /* Online Zinto A */ + { USB_DEVICE(UNITEK_VENDORID, 0x0001), &cypress_subdriver }, /* Unitek Alpha 1200Sx */ + { USB_DEVICE(GE_VENDORID, 0x00c9), &phoenix_subdriver }, /* GE EP series */ /* Terminating entry */ { 0, 0, NULL } @@ -582,6 +615,12 @@ void upsdrv_help(void) } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "subdriver", "Serial-over-USB subdriver selection"); diff --git a/drivers/clone-outlet.c b/drivers/clone-outlet.c index 27afd5e359..98178ca7cc 100644 --- a/drivers/clone-outlet.c +++ b/drivers/clone-outlet.c @@ -31,7 +31,7 @@ #endif /* !WIN32 */ #define DRIVER_NAME "Clone outlet UPS driver" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -529,6 +529,12 @@ void upsdrv_help(void) } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "prefix", "Outlet prefix (mandatory)"); diff --git a/drivers/clone.c b/drivers/clone.c index 7d4a35fe5f..66b8843453 100644 --- a/drivers/clone.c +++ b/drivers/clone.c @@ -34,7 +34,7 @@ #endif /* !WIN32 */ #define DRIVER_NAME "Clone UPS driver" -#define DRIVER_VERSION "0.08" +#define DRIVER_VERSION "0.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -754,6 +754,12 @@ void upsdrv_help(void) } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "offdelay", "Delay before outlet shutdown (seconds)"); diff --git a/drivers/cps-hid.c b/drivers/cps-hid.c index 254a9ef383..6d4c52e859 100644 --- a/drivers/cps-hid.c +++ b/drivers/cps-hid.c @@ -35,7 +35,7 @@ #define CPS_HID_VERSION "CyberPower HID 0.84" /* Cyber Power Systems */ -#define CPS_VENDORID 0x0764 +#define CPS_VENDORID 0x0764 /* ST Microelectronics */ #define STMICRO_VENDORID 0x0483 diff --git a/drivers/cyberpower-mib.c b/drivers/cyberpower-mib.c index d7faf39b9f..e47523dfd7 100644 --- a/drivers/cyberpower-mib.c +++ b/drivers/cyberpower-mib.c @@ -24,7 +24,7 @@ #include "cyberpower-mib.h" -#define CYBERPOWER_MIB_VERSION "0.55" +#define CYBERPOWER_MIB_VERSION "0.56" #define CYBERPOWER_OID_MODEL_NAME ".1.3.6.1.4.1.3808.1.1.1.1.1.1.0" /* CPS-MIB::ups */ @@ -134,6 +134,8 @@ static snmp_info_t cyberpower_mib[] = { SU_FLAG_OK | SU_STATUS_RB, &cyberpower_battrepl_status[0]), snmp_info_default("ups.load", 0, 1.0, ".1.3.6.1.4.1.3808.1.1.1.4.2.3.0", "", 0, NULL), + snmp_info_default("ups.realpower", 0, 1.0, ".1.3.6.1.4.1.3808.1.1.1.4.2.5.0", "", + 0, NULL), snmp_info_default("ups.temperature", 0, 1, ".1.3.6.1.4.1.3808.1.1.1.10.2.0", "", SU_FLAG_OK, NULL), diff --git a/drivers/delta_ups-hid.c b/drivers/delta_ups-hid.c index e544a23b93..d35ddd8511 100644 --- a/drivers/delta_ups-hid.c +++ b/drivers/delta_ups-hid.c @@ -33,13 +33,13 @@ #define DELTA_UPS_HID_VERSION "Delta UPS HID 0.6" /* Delta UPS */ -#define DELTA_UPS_VENDORID 0x05dd +#define DELTA_VENDORID 0x05dd /* USB IDs device table */ static usb_device_id_t delta_ups_usb_device_table[] = { /* Delta RT Series, Single Phase, 1/2/3 kVA */ /* Delta UPS Amplon R Series, Single Phase UPS, 1/2/3 kVA */ - { USB_DEVICE(DELTA_UPS_VENDORID, 0x041b), NULL }, + { USB_DEVICE(DELTA_VENDORID, 0x041b), NULL }, /* Terminating entry */ { 0, 0, NULL } diff --git a/drivers/dstate.c b/drivers/dstate.c index 7458a3554e..5b0a13e2d5 100644 --- a/drivers/dstate.c +++ b/drivers/dstate.c @@ -43,6 +43,7 @@ #include "parseconf.h" #include "attribute.h" #include "nut_stdint.h" +#include "nut_float.h" static TYPE_FD sockfd = ERROR_FD; #ifndef WIN32 @@ -61,6 +62,10 @@ struct ups_handler upsh; + /* Globally track if we are charging or losing power, and how fast */ + double previous_battery_charge_value = -1.0; + st_tree_timespec_t previous_battery_charge_timestamp; + #ifndef WIN32 /* this may be a frequent stumbling point for new users, so be verbose here */ static void sock_fail(const char *fn) @@ -1653,6 +1658,14 @@ const char *dstate_getinfo(const char *var) return state_getinfo(dtree_root, var); } +const st_tree_t *dstate_tree_find(const char *var) +{ + if (!var) + return NULL; + + return state_tree_find(dtree_root, var); +} + void dstate_addcmd(const char *cmdname) { int ret; @@ -1845,10 +1858,13 @@ void status_set(const char *buf) /* write the status_buf into the externally visible dstate storage */ void status_commit(void) { + /* FIXME: Further unify two accesses to, and parses of, "battery.charge" */ + const st_tree_t *dstate_battery_charge_entry = dstate_tree_find("battery.charge"); + while (ignorelb) { const char *val, *low; - val = dstate_getinfo("battery.charge"); + val = dstate_battery_charge_entry ? dstate_battery_charge_entry->val : NULL; low = dstate_getinfo("battery.charge.low"); if (val && low && (strtol(val, NULL, 10) < strtol(low, NULL, 10))) { @@ -1870,6 +1886,52 @@ void status_commit(void) break; } + if (dstate_battery_charge_entry && dstate_battery_charge_entry->val) { + double current_battery_charge_value = -1.0; + + if (previous_battery_charge_value >= 0.0 + && str_to_double(dstate_battery_charge_entry->val, ¤t_battery_charge_value, 10) + && current_battery_charge_value >= 0 + && current_battery_charge_value < 100.0 + && (!(d_equal(previous_battery_charge_value, current_battery_charge_value))) + ) { + /* We are not at 100%... is the battery filling up, + * or is the load greater than wall power/genset? */ + int reports_DISCHRG = status_get("DISCHRG"), reports_CHRG = status_get("CHRG"); + + if (!reports_DISCHRG && !reports_CHRG) { + upsdebugx(5, "%s: driver did not report a (DIS)CHRG state, but we know " + "that battery.charge changed to %g from %g reported %g seconds ago", + __func__, current_battery_charge_value, + previous_battery_charge_value, + difftime_st_tree_timespec(dstate_battery_charge_entry->lastset, previous_battery_charge_timestamp) + ); + + if (current_battery_charge_value > previous_battery_charge_value) { + status_set("CHRG"); + } else { + status_set("DISCHRG"); + } + } else { + int misleading_DISCHRG = (current_battery_charge_value > previous_battery_charge_value && reports_DISCHRG); + int misleading_CHRG = (current_battery_charge_value < previous_battery_charge_value && reports_CHRG); + + if (misleading_DISCHRG || misleading_CHRG) { + upslogx(LOG_WARNING, "Device reports that it is %scharging, " + "but the current battery charge %g is %s than %g " + "reported %g seconds ago", + misleading_DISCHRG ? "dis" : "", + current_battery_charge_value, + misleading_DISCHRG ? "greater" : "less", + previous_battery_charge_value, + difftime_st_tree_timespec(dstate_battery_charge_entry->lastset, previous_battery_charge_timestamp) + ); + status_set(misleading_DISCHRG ? "CHRG" : "DISCHRG"); + } + } + } + } + if (alarm_active || alarm_legacy_status) { if (*status_buf != '\0') { dstate_setinfo("ups.status", "ALARM %s", status_buf); diff --git a/drivers/dstate.h b/drivers/dstate.h index d5fccfd101..b59b128f0b 100644 --- a/drivers/dstate.h +++ b/drivers/dstate.h @@ -71,6 +71,10 @@ typedef struct conn_s { * Defaults to nonblocking, for backward compatibility */ extern int do_synchronous; + /* Globally track if we are charging or losing power, and how fast */ + extern double previous_battery_charge_value; + extern st_tree_timespec_t previous_battery_charge_timestamp; + char * dstate_init(const char *prog, const char *devname); int dstate_poll_fds(struct timeval timeout, TYPE_FD extrafd); int vdstate_setinfo(const char *var, const char *fmt, va_list ap); @@ -89,6 +93,7 @@ void dstate_addflags(const char *var, const int addflags); void dstate_delflags(const char *var, const int delflags); void dstate_setaux(const char *var, long aux); const char *dstate_getinfo(const char *var); +const st_tree_t *dstate_tree_find(const char *var); /* Return the whole entry, or NULL */ void dstate_addcmd(const char *cmdname); int dstate_delinfo_olderthan(const char *var, const st_tree_timespec_t *cutoff); int dstate_delinfo(const char *var); diff --git a/drivers/dummy-ups.c b/drivers/dummy-ups.c index 55e4210ed4..4b3f410d07 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.22" +#define DRIVER_VERSION "0.23" /* driver description structure */ upsdrv_info_t upsdrv_info = @@ -417,6 +417,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "mode", "Specify mode instead of guessing it from port value (dummy = dummy-loop, dummy-once, repeater)"); /* meta */ diff --git a/drivers/etapro.c b/drivers/etapro.c index eed0be3a9d..d2e72010de 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.09" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -363,6 +363,12 @@ upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void +upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { diff --git a/drivers/everups.c b/drivers/everups.c index 3308d894a0..1401e82067 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.08" +#define DRIVER_VERSION "0.10" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -87,6 +87,8 @@ static const char *GetTypeUpsName(void) void upsdrv_initinfo(void) { + InitUpsType(); + dstate_setinfo("ups.mfr", "Ever"); dstate_setinfo("ups.model", "%s", GetTypeUpsName()); @@ -222,6 +224,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { @@ -233,7 +240,6 @@ void upsdrv_initups(void) ser_set_speed(upsfd, device_path, B300); init_serial(); - InitUpsType(); } void upsdrv_cleanup(void) diff --git a/drivers/failover.c b/drivers/failover.c index f3acfa50e6..a8335cf993 100644 --- a/drivers/failover.c +++ b/drivers/failover.c @@ -27,7 +27,7 @@ #include "upsdrvquery.h" #define DRIVER_NAME "UPS Failover Driver" -#define DRIVER_VERSION "0.01" +#define DRIVER_VERSION "0.02" upsdrv_info_t upsdrv_info = { DRIVER_NAME, @@ -233,7 +233,11 @@ void upsdrv_shutdown(void) void upsdrv_help(void) { +} +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ } void upsdrv_makevartable(void) diff --git a/drivers/gamatronic.c b/drivers/gamatronic.c index 587c74e27a..5d836c6764 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.08" +#define DRIVER_VERSION "0.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -386,6 +386,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { @@ -429,7 +434,7 @@ void upsdrv_initups(void) /* upsfd = ser_open(device_path); */ /* ser_set_speed(upsfd, device_path, B1200); */ - /* probe ups type */ + /* probe ups type later, in upsdrv_initinfo() */ /* to get variables and flags from the command line, use this: * @@ -454,7 +459,8 @@ void upsdrv_initups(void) */ /* the upsh handlers can't be done here, as they get initialized - * shortly after upsdrv_initups returns to main. + * shortly after upsdrv_initups() returns to main, and goes back + * to upsdrv_initinfo(). */ } diff --git a/drivers/generic_gpio_common.c b/drivers/generic_gpio_common.c index 1d99db421a..a163f7784e 100644 --- a/drivers/generic_gpio_common.c +++ b/drivers/generic_gpio_common.c @@ -491,6 +491,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/generic_gpio_libgpiod.c b/drivers/generic_gpio_libgpiod.c index 9c56814cd5..9f8c853710 100644 --- a/drivers/generic_gpio_libgpiod.c +++ b/drivers/generic_gpio_libgpiod.c @@ -31,7 +31,7 @@ #endif #define DRIVER_NAME "GPIO UPS driver (API " WITH_LIBGPIO_VERSION_STR ")" -#define DRIVER_VERSION "1.04" +#define DRIVER_VERSION "1.05" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/generic_modbus.c b/drivers/generic_modbus.c index 8b52391794..c1d257d43e 100644 --- a/drivers/generic_modbus.c +++ b/drivers/generic_modbus.c @@ -31,7 +31,7 @@ #endif #define DRIVER_NAME "NUT Generic Modbus driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.07" +#define DRIVER_VERSION "0.08" /* variables */ static modbus_t *mbctx = NULL; /* modbus memory context */ @@ -350,6 +350,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/genericups.c b/drivers/genericups.c index e7e0ae3885..fec4c119ad 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.41" +#define DRIVER_VERSION "1.42" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -408,6 +408,11 @@ void upsdrv_help(void) listtypes(); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "upstype", "Set UPS type (required)"); diff --git a/drivers/hidtypes.h b/drivers/hidtypes.h index 6bce592f0b..9d05851f6f 100644 --- a/drivers/hidtypes.h +++ b/drivers/hidtypes.h @@ -6,6 +6,7 @@ * Copyright (C) * 1998-2003 MGE UPS SYSTEMS, Luc Descotils * 2015 Eaton, Arnaud Quette (Update MAX_REPORT) + * 2020-2025 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 @@ -315,6 +316,8 @@ typedef struct { long PhyMax; /* Physical Max */ int8_t have_PhyMin; /* Physical Min defined? */ int8_t have_PhyMax; /* Physical Max defined? */ + + bool mapping_handled; /* Did any (sub)driver handling loop care about this report? If not, may be a point for improvement... */ } HIDData_t; /* diff --git a/drivers/huawei-ups2000.c b/drivers/huawei-ups2000.c index 86a1802180..80b1efc85d 100644 --- a/drivers/huawei-ups2000.c +++ b/drivers/huawei-ups2000.c @@ -55,7 +55,7 @@ #endif #define DRIVER_NAME "NUT Huawei UPS2000 (1kVA-3kVA) RS-232 Modbus driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.10" +#define DRIVER_VERSION "0.11" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 1 @@ -1830,6 +1830,12 @@ void upsdrv_help(void) } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/hwmon_ina219.c b/drivers/hwmon_ina219.c index 1bc35d510c..8b064c6086 100644 --- a/drivers/hwmon_ina219.c +++ b/drivers/hwmon_ina219.c @@ -37,7 +37,7 @@ #define BATTERY_CHARGE_LOW 15 #define DRIVER_NAME "hwmon-INA219 UPS driver" -#define DRIVER_VERSION "0.03" +#define DRIVER_VERSION "0.04" upsdrv_info_t upsdrv_info = { DRIVER_NAME, @@ -477,6 +477,11 @@ void upsdrv_help(void) /* No special options in this driver (vars/flags are auto-documented) */ } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_cleanup(void) { } diff --git a/drivers/isbmex.c b/drivers/isbmex.c index 734521158e..37f7985084 100644 --- a/drivers/isbmex.c +++ b/drivers/isbmex.c @@ -27,7 +27,7 @@ #include #define DRIVER_NAME "ISBMEX UPS driver" -#define DRIVER_VERSION "0.12" +#define DRIVER_VERSION "0.13" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -421,6 +421,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/ivtscd.c b/drivers/ivtscd.c index a2c094eaba..8b75956f4b 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.07" +#define DRIVER_VERSION "0.08" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -217,6 +217,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { } diff --git a/drivers/legrand-hid.c b/drivers/legrand-hid.c index 371555328b..7a35513d4e 100644 --- a/drivers/legrand-hid.c +++ b/drivers/legrand-hid.c @@ -30,8 +30,10 @@ #define LEGRAND_HID_VERSION "Legrand HID 0.2" -/* Legrand VendorID and ProductID */ -#define LEGRAND_VID 0x1cb0 /* Legrand */ +/* Legrand */ +#define LEGRAND_VENDORID 0x1cb0 + +/* Legrand ProductIDs */ #define LEGRAND_PID_PDU 0x0038 /* Keor PDU model (800VA) */ #define LEGRAND_PID_SP 0x0032 /* Keor SP model (600, 800, 1000, 1500, 2000VA version) */ @@ -48,8 +50,8 @@ static void *disable_interrupt_pipe(USBDevice_t *device) /* USB IDs device table */ static usb_device_id_t legrand_usb_device_table[] = { - { USB_DEVICE(LEGRAND_VID, LEGRAND_PID_PDU), disable_interrupt_pipe }, /* Legrand Keor PDU */ - { USB_DEVICE(LEGRAND_VID, LEGRAND_PID_SP), disable_interrupt_pipe }, /* Legrand Keor SP */ + { USB_DEVICE(LEGRAND_VENDORID, LEGRAND_PID_PDU), disable_interrupt_pipe }, /* Legrand Keor PDU */ + { USB_DEVICE(LEGRAND_VENDORID, LEGRAND_PID_SP) , disable_interrupt_pipe }, /* Legrand Keor SP */ /* Terminating entry */ { 0, 0, NULL } diff --git a/drivers/libusb0.c b/drivers/libusb0.c index 22bf95da79..be1ae8bc0a 100644 --- a/drivers/libusb0.c +++ b/drivers/libusb0.c @@ -38,7 +38,7 @@ #endif /* WIN32 */ #define USB_DRIVER_NAME "USB communication driver (libusb 0.1)" -#define USB_DRIVER_VERSION "0.50" +#define USB_DRIVER_VERSION "0.51" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -219,7 +219,7 @@ static int nut_libusb_open(usb_dev_handle **udevp, #ifdef HAVE_USB_DETACH_KERNEL_DRIVER_NP int retries; #endif - usb_ctrl_charbufsize rdlen1, rdlen2; /* report descriptor length, method 1+2 */ + usb_ctrl_charbufsize rdlen1, rdlen2, rdlens[2]; /* report descriptor length, method 1+2, then an array to iterate them (if both) in chosen order */ USBDeviceMatcher_t *m; struct usb_device *dev; struct usb_bus *bus; @@ -231,13 +231,14 @@ static int nut_libusb_open(usb_dev_handle **udevp, usb_ctrl_char *p; char string[256]; int i; + size_t j; int count_open_EACCESS = 0; int count_open_errors = 0; int count_open_attempts = 0; /* report descriptor */ usb_ctrl_char rdbuf[MAX_REPORT_SIZE]; - usb_ctrl_charbufsize rdlen; + usb_ctrl_charbufsize rdlen = -1; struct usb_bus *busses; @@ -625,13 +626,15 @@ static int nut_libusb_open(usb_dev_handle **udevp, the maximum of the two values instead. */ if ((curDevice->VendorID == 0x463) && (curDevice->bcdDevice == 0x0202)) { upsdebugx(1, "Eaton device v2.02. Using full report descriptor"); - rdlen = rdlen1; + rdlens[0] = rdlen1; + rdlens[1] = rdlen2; } else { - rdlen = rdlen2 >= 0 ? rdlen2 : rdlen1; + rdlens[0] = rdlen2 >= 0 ? rdlen2 : rdlen1; + rdlens[1] = rdlen2 >= 0 ? rdlen1 : rdlen2; } - if (rdlen < 0) { + if (rdlen1 < 0 && rdlen2 < 0) { upsdebugx(2, "Unable to retrieve any HID descriptor"); goto next_device; } @@ -640,61 +643,79 @@ static int nut_libusb_open(usb_dev_handle **udevp, "(Reportlen = %" PRI_NUT_USB_CTRL_CHARBUFSIZE " vs. %" PRI_NUT_USB_CTRL_CHARBUFSIZE ")", rdlen1, rdlen2); + } else { + if (rdlen1 == rdlen2) { + rdlens[1] = -1; + } } - upsdebugx(2, - "HID descriptor length %" PRI_NUT_USB_CTRL_CHARBUFSIZE, - rdlen); + for (j = 0; j < sizeof(rdlens); j++) { + rdlen = rdlens[j]; + if (rdlen < 0) + continue; - if ((uintmax_t)rdlen > sizeof(rdbuf)) { upsdebugx(2, - "HID descriptor too long %" PRI_NUT_USB_CTRL_CHARBUFSIZE - " (max %" PRIuSIZE ")", - rdlen, sizeof(rdbuf)); - goto next_device; - } + "Trying HID descriptor length %" PRI_NUT_USB_CTRL_CHARBUFSIZE, + rdlen); + + if ((uintmax_t)rdlen > sizeof(rdbuf)) { + upsdebugx(2, + "HID descriptor too long %" PRI_NUT_USB_CTRL_CHARBUFSIZE + " (max %" PRIuSIZE ")", + rdlen, sizeof(rdbuf)); + continue; + } - /* Note: rdlen is safe to cast to unsigned below, - * since the <0 case was ruled out above */ - /* res = usb_get_descriptor(udev, USB_DT_REPORT, hid_desc_index, bigbuf, rdlen); */ - res = usb_control_msg(udev, - USB_ENDPOINT_IN + 1, - USB_REQ_GET_DESCRIPTOR, - (USB_DT_REPORT << 8) + usb_subdriver.hid_desc_index, - usb_subdriver.hid_rep_index, - rdbuf, rdlen, USB_TIMEOUT); + /* Note: rdlen is safe to cast to unsigned below, + * since the <0 case was ruled out above */ + /* res = usb_get_descriptor(udev, USB_DT_REPORT, hid_desc_index, bigbuf, rdlen); */ + res = usb_control_msg(udev, + USB_ENDPOINT_IN + 1, + USB_REQ_GET_DESCRIPTOR, + (USB_DT_REPORT << 8) + usb_subdriver.hid_desc_index, + usb_subdriver.hid_rep_index, + rdbuf, rdlen, USB_TIMEOUT); - if (res < 0) - { - upsdebug_with_errno(2, "Unable to get Report descriptor"); - goto next_device; - } + if (res < 0) + { + upsdebug_with_errno(2, "Unable to get Report descriptor"); + continue; + } - if (res < rdlen) - { + if (res < rdlen) + { #ifndef WIN32 - upsdebugx(2, "Warning: report descriptor too short " - "(expected %" PRI_NUT_USB_CTRL_CHARBUFSIZE - ", got %d)", rdlen, res); + upsdebugx(2, "Warning: report descriptor too short " + "(expected %" PRI_NUT_USB_CTRL_CHARBUFSIZE + ", got %d)", rdlen, res); #else /* WIN32 */ - /* https://github.com/networkupstools/nut/issues/1690#issuecomment-1455206002 */ - upsdebugx(0, "Warning: report descriptor too short " - "(expected %" PRI_NUT_USB_CTRL_CHARBUFSIZE - ", got %d)", rdlen, res); - upsdebugx(0, "Please check your Windows Device Manager: " - "perhaps the UPS was recognized by default OS\n" - "driver such as HID UPS Battery (hidbatt.sys, " - "hidusb.sys or similar). It could have been\n" - "\"restored\" by Windows Update. You can try " - "https://zadig.akeo.ie/ to handle it with\n" - "either WinUSB, libusb0.sys or libusbK.sys."); + /* https://github.com/networkupstools/nut/issues/1690#issuecomment-1455206002 */ + upsdebugx(0, "Warning: report descriptor too short " + "(expected %" PRI_NUT_USB_CTRL_CHARBUFSIZE + ", got %d)", rdlen, res); + upsdebugx(0, "Please check your Windows Device Manager: " + "perhaps the UPS was recognized by default OS\n" + "driver such as HID UPS Battery (hidbatt.sys, " + "hidusb.sys or similar). It could have been\n" + "\"restored\" by Windows Update. You can try " + "https://zadig.akeo.ie/ to handle it with\n" + "either WinUSB, libusb0.sys or libusbK.sys."); #endif /* WIN32 */ - rdlen = res; /* correct rdlen if necessary */ + rdlen = res; /* correct rdlen if necessary */ + } + + res = callback(udev, curDevice, rdbuf, rdlen); + if (res < 1) { + upsdebugx(2, "Caller doesn't like this device (or rdlen is wrong)"); + continue; + } + + /* We found it... or at least something that did not complain */ + break; } - res = callback(udev, curDevice, rdbuf, rdlen); - if (res < 1) { - upsdebugx(2, "Caller doesn't like this device"); + if (j >= sizeof(rdlens)) { + /* Ended the loop without success */ goto next_device; } diff --git a/drivers/libusb1.c b/drivers/libusb1.c index 2ce00dbf3c..7cd170d3ca 100644 --- a/drivers/libusb1.c +++ b/drivers/libusb1.c @@ -33,7 +33,7 @@ #include "nut_stdint.h" #define USB_DRIVER_NAME "USB communication driver (libusb 1.0)" -#define USB_DRIVER_VERSION "0.50" +#define USB_DRIVER_VERSION "0.51" /* driver description structure */ upsdrv_info_t comm_upsdrv_info = { @@ -185,7 +185,7 @@ static int nut_libusb_open(libusb_device_handle **udevp, #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 */ + int32_t rdlen1, rdlen2, rdlens[2]; /* report descriptor length, method 1+2, then an array to iterate them (if both) in chosen order */ USBDeviceMatcher_t *m; libusb_device **devlist; ssize_t devcount = 0; @@ -203,13 +203,14 @@ static int nut_libusb_open(libusb_device_handle **udevp, const unsigned char *p; char string[256]; int i; + size_t j; int count_open_EACCESS = 0; int count_open_errors = 0; int count_open_attempts = 0; /* report descriptor */ unsigned char rdbuf[MAX_REPORT_SIZE]; - int32_t rdlen; + int32_t rdlen = -1; static int usb_hid_number_opts_parsed = 0; if (!usb_hid_number_opts_parsed) { @@ -653,7 +654,7 @@ static int nut_libusb_open(libusb_device_handle **udevp, if (rdlen1 < -1) { upsdebugx(2, "Warning: HID descriptor, method 1 failed"); } - upsdebugx(3, "HID descriptor length (method 1) %d", rdlen1); + upsdebugx(3, "HID descriptor length (method 1) %" PRIi32, rdlen1); /* SECOND METHOD: find HID descriptor among "extra" bytes of interface descriptor, i.e., bytes tucked onto the end of @@ -683,7 +684,7 @@ static int nut_libusb_open(libusb_device_handle **udevp, if (rdlen2 < -1) { upsdebugx(2, "Warning: HID descriptor, method 2 failed"); } - upsdebugx(3, "HID descriptor length (method 2) %d", rdlen2); + upsdebugx(3, "HID descriptor length (method 2) %" PRIi32, rdlen2); /* when available, always choose the second value, as it seems to be more reliable (it is the one reported e.g. by @@ -691,28 +692,40 @@ static int nut_libusb_open(libusb_device_handle **udevp, the maximum of the two values instead. */ if ((curDevice->VendorID == 0x463) && (curDevice->bcdDevice == 0x0202)) { upsdebugx(1, "Eaton device v2.02. Using full report descriptor"); - rdlen = rdlen1; + rdlens[0] = rdlen1; + rdlens[1] = rdlen2; } else { - rdlen = rdlen2 >= 0 ? rdlen2 : rdlen1; + rdlens[0] = rdlen2 >= 0 ? rdlen2 : rdlen1; + rdlens[1] = rdlen2 >= 0 ? rdlen1 : rdlen2; } - if (rdlen < 0) { + if (rdlen1 < 0 && rdlen2 < 0) { upsdebugx(2, "Unable to retrieve any HID descriptor"); goto next_device; } if (rdlen1 >= 0 && rdlen2 >= 0 && rdlen1 != rdlen2) { upsdebugx(2, "Warning: two different HID descriptors retrieved " - "(Reportlen = %d vs. %d)", rdlen1, rdlen2); + "(Reportlen = %" PRIi32 " vs. %" PRIi32 ")", + rdlen1, rdlen2); + } else { + if (rdlen1 == rdlen2) { + rdlens[1] = -1; + } } - upsdebugx(2, "HID descriptor length %d", rdlen); + for (j = 0; j < sizeof(rdlens); j++) { + rdlen = rdlens[j]; + if (rdlen < 0) + continue; - if (rdlen > (int)sizeof(rdbuf)) { - upsdebugx(2, "HID descriptor too long %d (max %d)", - rdlen, (int)sizeof(rdbuf)); - goto next_device; - } + upsdebugx(2, "Trying HID descriptor length %" PRIi32, rdlen); + + if (rdlen > (int)sizeof(rdbuf)) { + upsdebugx(2, "HID descriptor too long %" PRIi32 " (max %d)", + rdlen, (int)sizeof(rdbuf)); + continue; + } #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 @@ -726,67 +739,78 @@ static int nut_libusb_open(libusb_device_handle **udevp, #ifdef HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_TAUTOLOGICAL_UNSIGNED_ZERO_COMPARE # pragma GCC diagnostic ignored "-Wtautological-unsigned-zero-compare" #endif - if ((uintmax_t)rdlen > UINT16_MAX) { + if ((uintmax_t)rdlen > UINT16_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 - upsdebugx(2, "HID descriptor too long %d (max %" PRIuMAX ")", - rdlen, (uintmax_t)UINT16_MAX); - goto next_device; - } + upsdebugx(2, "HID descriptor too long %" PRIi32 + " (max %" PRIuMAX ")", + rdlen, (uintmax_t)UINT16_MAX); + continue; + } - /* libusb0: USB_ENDPOINT_IN + 1 */ - res = libusb_control_transfer(udev, - LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_STANDARD|LIBUSB_RECIPIENT_INTERFACE, - LIBUSB_REQUEST_GET_DESCRIPTOR, - (LIBUSB_DT_REPORT << 8) + usb_subdriver.hid_desc_index, - usb_subdriver.hid_rep_index, - rdbuf, (uint16_t)rdlen, USB_TIMEOUT); + /* libusb0: USB_ENDPOINT_IN + 1 */ + res = libusb_control_transfer(udev, + LIBUSB_ENDPOINT_IN|LIBUSB_REQUEST_TYPE_STANDARD|LIBUSB_RECIPIENT_INTERFACE, + LIBUSB_REQUEST_GET_DESCRIPTOR, + (LIBUSB_DT_REPORT << 8) + usb_subdriver.hid_desc_index, + usb_subdriver.hid_rep_index, + rdbuf, (uint16_t)rdlen, USB_TIMEOUT); - if (res < 0) - { - upsdebug_with_errno(2, "Unable to get Report descriptor"); - goto next_device; - } + if (res < 0) + { + upsdebug_with_errno(2, "Unable to get Report descriptor"); + continue; + } - if (res < rdlen) - { + if (res < rdlen) + { #ifndef WIN32 - upsdebugx(2, "Warning: report descriptor too short " - "(expected %d, got %d)", rdlen, res); + upsdebugx(2, "Warning: report descriptor too short " + "(expected %" PRIi32 ", got %d)", rdlen, res); #else /* WIN32 */ - /* https://github.com/networkupstools/nut/issues/1690#issuecomment-1455206002 */ - upsdebugx(0, "Warning: report descriptor too short " - "(expected %d, got %d)", rdlen, res); - upsdebugx(0, "Please check your Windows Device Manager: " - "perhaps the UPS was recognized by default OS\n" - "driver such as HID UPS Battery (hidbatt.sys, " - "hidusb.sys or similar). It could have been\n" - "\"restored\" by Windows Update. You can try " - "https://zadig.akeo.ie/ to handle it with\n" - "either WinUSB, libusb0.sys or libusbK.sys."); + /* https://github.com/networkupstools/nut/issues/1690#issuecomment-1455206002 */ + upsdebugx(0, "Warning: report descriptor too short " + "(expected %" PRIi32 ", got %d)", rdlen, res); + upsdebugx(0, "Please check your Windows Device Manager: " + "perhaps the UPS was recognized by default OS\n" + "driver such as HID UPS Battery (hidbatt.sys, " + "hidusb.sys or similar). It could have been\n" + "\"restored\" by Windows Update. You can try " + "https://zadig.akeo.ie/ to handle it with\n" + "either WinUSB, libusb0.sys or libusbK.sys."); #endif /* WIN32 */ - rdlen = res; /* correct rdlen if necessary */ - } + rdlen = res; /* correct rdlen if necessary */ + } - if (rdlen < USB_CTRL_CHARBUFSIZE_MIN - || (uintmax_t)rdlen > (uintmax_t)USB_CTRL_CHARBUFSIZE_MAX - ) { - upsdebugx(2, - "Report descriptor length is out of range on this device: " - "should be %" PRIdMAX " < %d < %" PRIuMAX, - (intmax_t)USB_CTRL_CHARBUFSIZE_MIN, rdlen, - (uintmax_t)USB_CTRL_CHARBUFSIZE_MAX); - goto next_device; + if (rdlen < USB_CTRL_CHARBUFSIZE_MIN + || (uintmax_t)rdlen > (uintmax_t)USB_CTRL_CHARBUFSIZE_MAX + ) { + upsdebugx(2, + "Report descriptor length is out of range on this device: " + "should be %" PRIdMAX " < %" PRIi32 " < %" PRIuMAX, + (intmax_t)USB_CTRL_CHARBUFSIZE_MIN, rdlen, + (uintmax_t)USB_CTRL_CHARBUFSIZE_MAX); + goto next_device; + } + + res = callback(udev, curDevice, rdbuf, (usb_ctrl_charbufsize)rdlen); + if (res < 1) { + upsdebugx(2, "Caller doesn't like this device"); + continue; + } + + /* We found it... or at least something that did not complain */ + break; } - res = callback(udev, curDevice, rdbuf, (usb_ctrl_charbufsize)rdlen); - if (res < 1) { - upsdebugx(2, "Caller doesn't like this device"); + if (j >= sizeof(rdlens)) { + /* Ended the loop without success */ goto next_device; } - upsdebugx(2, "Report descriptor retrieved (Reportlen = %d)", rdlen); + upsdebugx(2, "Report descriptor retrieved (Reportlen = %" PRIi32 ")", + rdlen); upsdebugx(2, "Found HID device"); upsdebugx(3, "Using default, detected or customized USB HID numbers: " diff --git a/drivers/liebert-esp2.c b/drivers/liebert-esp2.c index 97f93910fb..4672ed5fd6 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.09" +#define DRIVER_VERSION "0.10" #define UPS_SHUTDOWN_DELAY 12 /* it means UPS will be shutdown 120 sec */ #define SHUTDOWN_CMD_LEN 8 @@ -574,6 +574,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/liebert-gxe.c b/drivers/liebert-gxe.c index 6ff20c6527..2fbb762a3c 100644 --- a/drivers/liebert-gxe.c +++ b/drivers/liebert-gxe.c @@ -24,7 +24,7 @@ #include "ydn23.h" #define DRIVER_NAME "Liebert GXE Series UPS driver" -#define DRIVER_VERSION "0.04" +#define DRIVER_VERSION "0.05" #define PROBE_RETRIES 3 #define DEFAULT_STALE_RETRIES 3 @@ -497,6 +497,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "addr", "Override default UPS address"); diff --git a/drivers/liebert-hid.c b/drivers/liebert-hid.c index 7b87706fc5..39094b0fad 100644 --- a/drivers/liebert-hid.c +++ b/drivers/liebert-hid.c @@ -32,7 +32,7 @@ /* FIXME: experimental flag to be put in upsdrv_info */ /* Phoenixtec Power Co., Ltd */ -#define LIEBERT_VENDORID 0x06da +#define PHOENIXTEC_VENDORID 0x06da /*! USB IDs device table. * @@ -42,7 +42,7 @@ * Belkin HID firmware. */ static usb_device_id_t liebert_usb_device_table[] = { /* various models */ - { USB_DEVICE(LIEBERT_VENDORID, 0xffff), NULL }, + { USB_DEVICE(PHOENIXTEC_VENDORID, 0xffff), NULL }, /* Terminating entry */ { 0, 0, NULL } diff --git a/drivers/liebert.c b/drivers/liebert.c index fc6e50d525..1c3fe32a3f 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.06" +#define DRIVER_VERSION "1.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -179,6 +179,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_initups(void) { upsfd = ser_open(device_path); diff --git a/drivers/macosx-ups.c b/drivers/macosx-ups.c index 88c27ea596..92c31bfef2 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.43" +#define DRIVER_VERSION "1.44" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -341,6 +341,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/main.c b/drivers/main.c index 2f18d058ca..a6162ac366 100644 --- a/drivers/main.c +++ b/drivers/main.c @@ -24,6 +24,7 @@ #include "common.h" #include "main.h" #include "nut_stdint.h" +#include "nut_float.h" #include "dstate.h" #include "attribute.h" #include "upsdrvquery.h" @@ -43,7 +44,15 @@ TYPE_FD upsfd = ERROR_FD; * during their assignment when reading program parameters * from CLI or config file. */ char *device_path = NULL, *device_sdcommands = NULL; -const char *progname = NULL, *upsname = NULL, *device_name = NULL; +const char *upsname = NULL, *device_name = NULL; + +/* We allow for aliases to certain program names (e.g. when renaming a driver + * between "old" and "new" and default implementations, it should accept both + * or more names it can be called by). + * The [0] entry is used to set up stuff like pipe names, man page links, etc. + */ +const char *prognames[MAX_PROGNAMES]; +char prognames_should_free[MAX_PROGNAMES]; /* may be set by the driver to wake up while in dstate_poll_fds */ TYPE_FD extrafd = ERROR_FD; @@ -331,6 +340,7 @@ static void storeval(const char *var, char *val) { vartab_t *tmp, *last; + const char **pprogname; /* NOTE: (FIXME?) The override and default mechanisms here * effectively bypass both VAR_SENSITIVE protections and @@ -402,33 +412,37 @@ void storeval(const char *var, char *val) printf("Look in the man page or call this driver with -h for a list of\n"); printf("valid variable names and flags.\n"); - if (!strcmp(progname, "nutdrv_qx")) { - /* First many entries are from nut_usb_addvars() implementations; - * the latter two (about langid) are from nutdrv_qx.c - */ - if (!strcmp(var, "vendor") - || !strcmp(var, "product") - || !strcmp(var, "serial") - || !strcmp(var, "vendorid") - || !strcmp(var, "productid") - || !strcmp(var, "bus") - || !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") - ) { - printf("\nNOTE: for driver '%s', options like '%s' are only available\n" - "if it was built with USB support. If you are running a custom build of NUT,\n" - "please check results of the `configure` checks, and consider an explicit\n" - "`--with-usb` option. Also make sure that both libusb library and headers\n" - "are installed in your build environment.\n\n", progname, var); + /* FIXME: find a way to separate this logic from main.c */ + for (pprogname = prognames; *pprogname != NULL; pprogname++) { + if (!strcmp(*pprogname, "nutdrv_qx")) { + /* First many entries are from nut_usb_addvars() implementations; + * the latter two (about langid) are from nutdrv_qx.c + */ + if (!strcmp(var, "vendor") + || !strcmp(var, "product") + || !strcmp(var, "serial") + || !strcmp(var, "vendorid") + || !strcmp(var, "productid") + || !strcmp(var, "bus") + || !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") + ) { + printf("\nNOTE: for driver '%s', options like '%s' are only available\n" + "if it was built with USB support. If you are running a custom build of NUT,\n" + "please check results of the `configure` checks, and consider an explicit\n" + "`--with-usb` option. Also make sure that both libusb library and headers\n" + "are installed in your build environment.\n\n", progname, var); + break; + } } } @@ -1596,41 +1610,69 @@ void do_upsconf_args(char *confupsname, char *var, char *val) * reload should not allow changes here, but would report */ if (!strcmp(var, "driver")) { - int do_handle; + int do_handle = -2, good_hits = 0, bad_hits = 0; + const char **pprogname; + char all_prognames[LARGEBUF]; + size_t i; upsdebugx(5, "%s: this is a 'driver' setting, may we proceed?", __func__); - do_handle = testval_reloadable(var, progname, val, 0); + for (pprogname = prognames; *pprogname != NULL; pprogname++) { + do_handle = testval_reloadable(var, prognames[0], val, 0); - if (do_handle == -1) { - upsdebugx(5, "%s: 'driver' setting already applied with this value", __func__); - return; + if (do_handle == -1) { + upsdebugx(5, "%s: 'driver' setting already applied with this value", __func__); + return; + } } + memset(all_prognames, 0, sizeof(all_prognames)); /* Acceptable progname is only set once during start-up * val is from ups.conf */ - if (!reload_flag || do_handle > 0) { - /* Accomodate for libtool wrapped developer iterations - * running e.g. `drivers/.libs/lt-dummy-ups` filenames - */ - size_t tmplen = strlen("lt-"); - if (strncmp("lt-", progname, tmplen) == 0 - && strcmp(val, progname + tmplen) == 0) { - /* debug level may be not initialized yet, and situation - * should not happen in end-user builds, so ok to yell: */ - upsdebugx(0, "Seems this driver binary %s is a libtool " - "wrapped build for driver %s", progname, val); - /* progname points to xbasename(argv[0]) in-place; - * roll the pointer forward a bit, we know we can: + i = 0; + for (pprogname = prognames; *pprogname != NULL; pprogname++) { + if (!reload_flag || do_handle > 0) { + /* Accomodate for libtool wrapped developer iterations + * running e.g. `drivers/.libs/lt-dummy-ups` filenames */ - progname = progname + tmplen; + size_t tmplen = strlen("lt-"); + if (strncmp("lt-", *pprogname, tmplen) == 0 + && strcmp(val, *pprogname + tmplen) == 0) { + /* debug level may be not initialized yet, and situation + * should not happen in end-user builds, so ok to yell: */ + upsdebugx(0, "Seems this driver binary %s is a libtool " + "wrapped build for driver %s", *pprogname, val); + if (prognames_should_free[i]) { + char *newpn = xstrdup(*pprogname + tmplen); + free((char*)*pprogname); + *pprogname = newpn; + } else { + /* *pprogname points to xbasename(argv[0]) in-place; + * roll the pointer forward a bit, we know we can */ + *pprogname = *pprogname + tmplen; + } + } } + + snprintfcat(all_prognames, sizeof(all_prognames), "%s'%s'", + all_prognames[0] ? "/" : "", *pprogname); + + if (strcmp(val, *pprogname) != 0) { + bad_hits++; + } else { + good_hits++; + } + + i++; } - if (strcmp(val, progname) != 0) { - fatalx(EXIT_FAILURE, "Error: UPS [%s] is for driver %s, but I'm %s!\n", - confupsname, val, progname); + upsdebugx(3, "%s: collected %d bad hits and %d good hits for '%s' in %s", + __func__, bad_hits, good_hits, val, all_prognames); + if (!good_hits) { + fatalx(EXIT_FAILURE, "Error: UPS [%s] is for driver '%s', but I'm %s!\n", + confupsname, val, all_prognames); } + return; } @@ -1859,6 +1901,8 @@ static void exit_upsdrv_cleanup(void) static void exit_cleanup(void) { + size_t i; + dstate_setinfo("driver.state", "cleanup.exit"); if (!dump_data && !help_only) { @@ -1893,6 +1937,16 @@ static void exit_cleanup(void) CloseHandle(mutex); } #endif /* WIN32 */ + + for (i = 0; i < MAX_PROGNAMES; i++) { + /* Some prognames[] may be allocated statically, + * e.g. can be a pointer to part of argv[0]; + * others come from strdup() and friends. + */ + if (prognames_should_free[i] && *(prognames[i])) { + free((char*)(prognames[i])); + } + } } #endif /* DRIVERS_MAIN_WITHOUT_MAIN */ @@ -2125,24 +2179,31 @@ int main(int argc, char **argv) /* pick up a default from configure --with-group */ group = xstrdup(RUN_AS_GROUP); /* xstrdup: this gets freed at exit */ - progname = xbasename(argv[0]); + memset(prognames, 0, sizeof(prognames)); + memset(prognames_should_free, 0, sizeof(prognames_should_free)); + prognames[0] = xbasename(argv[0]); #ifdef WIN32 - drv_name = xbasename(argv[0]); + drv_name = prognames[0]; /* remove trailing .exe */ dot = strrchr(drv_name,'.'); if (dot != NULL) { if (strcasecmp(dot, ".exe") == 0) { - progname = strdup(drv_name); - char * t = strrchr(progname,'.'); + char *fixed_progname = strdup(drv_name); + char *t = strrchr(fixed_progname,'.'); *t = 0; + prognames[0] = fixed_progname; + prognames_should_free[0] = 1; } } else { - progname = strdup(drv_name); + prognames[0] = strdup(drv_name); + prognames_should_free[0] = 1; } #endif /* WIN32 */ + upsdrv_tweak_prognames(); + open_syslog(progname); if (!banner_is_disabled()) { @@ -2419,10 +2480,13 @@ int main(int argc, char **argv) ssize_t cmdret = -1; struct timeval tv; + upslogx(LOG_INFO, "Checking if an already running driver instance can handle the shutdown command for us"); + /* Post the query and wait for reply */ /* FIXME: coordinate with pollfreq? */ tv.tv_sec = 15; tv.tv_usec = 0; + upsdebugx(1, "Make sure the other driver instance is allowed to kill power"); cmdret = upsdrvquery_oneshot(progname, upsname, "SET driver.flag.allow_killpower 1\n", NULL, 0, &tv); @@ -2431,6 +2495,7 @@ int main(int argc, char **argv) /* FIXME: somehow mark drivers expected to loop infinitely? */ tv.tv_sec = -1; tv.tv_usec = -1; + upsdebugx(1, "Send the actual command to the other driver instance"); cmdret = upsdrvquery_oneshot(progname, upsname, "INSTCMD driver.killpower\n", NULL, 0, &tv); @@ -2438,7 +2503,7 @@ int main(int argc, char **argv) if (cmdret < 0) { upsdebug_with_errno(1, "Socket dialog with the other driver instance"); } else { - upslogx(LOG_INFO, "Request to killpower via running driver returned code %" PRIiSIZE, cmdret); + upslogx(LOG_INFO, "Request to killpower via running driver instance returned code %" PRIiSIZE, cmdret); if (cmdret == 0) /* Note: many drivers would abort with * "shutdown not supported" at this @@ -2642,9 +2707,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, progname, 1); + cmdret = sendsignalfnaliases(pidfnbuf, cmd, prognames, 1); } else { - cmdret = sendsignalpid(oldpid, cmd, progname, 1); + cmdret = sendsignalpidaliases(oldpid, cmd, prognames, 1); } switch (cmdret) { @@ -2739,9 +2804,9 @@ int main(int argc, char **argv) upslogx(LOG_WARNING, "Duplicate driver instance detected (PID file %s exists)! Terminating other driver!", pidfnbuf); - if ((sigret = sendsignalfn(pidfnbuf, SIGTERM, progname, 1) != 0)) { + if ((sigret = sendsignalfnaliases(pidfnbuf, SIGTERM, prognames, 1) != 0)) { upsdebug_with_errno(1, "Can't send signal to PID, assume invalid PID file %s; " - "sendsignalfn() returned %d", pidfnbuf, sigret); + "sendsignalfnaliases() returned %d", pidfnbuf, sigret); break; } @@ -2756,9 +2821,9 @@ int main(int argc, char **argv) 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, progname, 1) == 0) { + if (sendsignalfnaliases(pidfnbuf, SIGKILL, prognames, 1) == 0) { sleep(5); - if (sendsignalfn(pidfnbuf, 0, progname, 1) == 0) { + if (sendsignalfnaliases(pidfnbuf, 0, prognames, 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 @@ -3086,8 +3151,10 @@ int main(int argc, char **argv) upsnotify(NOTIFY_STATE_READY_WITH_PID, NULL); } + memset(&previous_battery_charge_timestamp, 0, sizeof(previous_battery_charge_timestamp)); while (!exit_flag) { struct timeval timeout; + const st_tree_t *dstate_entry = NULL; if (!dump_data) { upsnotify(NOTIFY_STATE_WATCHDOG, NULL); @@ -3096,6 +3163,21 @@ int main(int argc, char **argv) gettimeofday(&timeout, NULL); timeout.tv_sec += poll_interval; + /* Drivers can now choose to track changes of current battery + * charge vs. its previous value to e.g. report "CHRG" status. + * TODO: Eventually provide a common `runtimecal` fallback to all? + */ + if ((dstate_entry = dstate_tree_find("battery.charge")) && dstate_entry->val) { + double d = -1.0; + + if (str_to_double(dstate_entry->val, &d, 10) && d >= 0.0) { + if (!d_equal(previous_battery_charge_value, d)) { + previous_battery_charge_value = d; + previous_battery_charge_timestamp = dstate_entry->lastset; + } + } + } + dstate_setinfo("driver.state", "updateinfo"); upsdrv_updateinfo(); dstate_setinfo("driver.state", "quiet"); diff --git a/drivers/main.h b/drivers/main.h index 3767900f40..1424be82f5 100644 --- a/drivers/main.h +++ b/drivers/main.h @@ -11,14 +11,25 @@ #endif /* WIN32 */ /* public functions & variables from main.c, documented in detail there */ -extern const char *progname, *upsname, *device_name; +extern const char *upsname, *device_name; extern char *device_path, *device_sdcommands; extern int broken_driver, experimental_driver, do_lock_port, exit_flag, handling_upsdrv_shutdown; extern TYPE_FD upsfd, extrafd; extern time_t poll_interval; +/* We allow for aliases to certain program names (e.g. when renaming a driver + * between "old" and "new" and default implementations, it should accept both + * or more names it can be called by). + * The [0] entry is used to set up stuff like pipe names, man page links, etc. + */ +#define MAX_PROGNAMES 4 +extern const char *prognames[MAX_PROGNAMES]; +extern char prognames_should_free[MAX_PROGNAMES]; +#define progname (prognames[0]) + /* functions & variables required in each driver */ +void upsdrv_tweak_prognames(void); /* optionally add aliases and/or set preferred name into [0] (for pipe name etc.); called just after populating prognames[0] and prognames_should_free[] entries */ void upsdrv_initups(void); /* open connection to UPS, fail if not found */ void upsdrv_initinfo(void); /* prep data, settings for UPS monitoring */ void upsdrv_updateinfo(void); /* update state data if possible */ diff --git a/drivers/masterguard.c b/drivers/masterguard.c index 06e748eb2c..ae067443f6 100644 --- a/drivers/masterguard.c +++ b/drivers/masterguard.c @@ -31,7 +31,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "MASTERGUARD UPS driver" -#define DRIVER_VERSION "0.29" +#define DRIVER_VERSION "0.31" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -450,6 +450,17 @@ void upsdrv_help( void ) } +/******************************************************************** + * + * + * optionally tweak prognames[] entries + * + ********************************************************************/ +void upsdrv_tweak_prognames(void) +{ + +} + /******************************************************************** * * Function to initialize the fields of the ups driver. @@ -457,6 +468,42 @@ void upsdrv_help( void ) ********************************************************************/ void upsdrv_initinfo(void) { + int count = 0; + int fail = 0; + int good = 0; + + name[0] = '\0'; + firmware[0] = '\0'; + + /* probe ups type */ + do + { + count++; + + if( ups_ident( ) != 1 ) + fail++; + /* at least two good identifications */ + if( (count - fail) == 2 ) + { + good = 1; + break; + } + } while( (countVendorID) { - case PHOENIXTEC: /* see comments above */ + case PHOENIXTEC_VENDORID: /* see comments above */ if (hd->Vendor && strstr(hd->Vendor, "AEG")) { return 1; } diff --git a/drivers/mge-utalk.c b/drivers/mge-utalk.c index 74c1c871b7..82b3a351ae 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.99" +#define DRIVER_VERSION "0.101" /* driver description structure */ @@ -166,7 +166,6 @@ void upsdrv_makevartable(void) void upsdrv_initups(void) { - char buf[BUFFLEN]; #ifndef WIN32 int RTS = TIOCM_RTS; #endif /* !WIN32 */ @@ -179,6 +178,11 @@ void upsdrv_initups(void) if (testvar ("oldmac")) RTS = ~TIOCM_RTS; + /* TOTHINK: This looks like comms with the device and may belong in + * upsdrv_initinfo(). But in upsdrv_cleanup() we have disable_ups_comm() + * at least, which gets registered and might get called before initinfo. + */ + /* Init serial line */ ioctl(upsfd, TIOCMBIC, &RTS); #else /* WIN32 */ @@ -190,6 +194,28 @@ void upsdrv_initups(void) } #endif /* WIN32 */ enable_ups_comm(); +} + +/* --------------------------------------------------------------- */ + +void upsdrv_initinfo(void) +{ + char buf[BUFFLEN]; + const char *model = NULL; + char *firmware = NULL; + char *p; + char *v = NULL; /* for parsing Si output, get Version ID */ + int table; + int tries; + int status_ok = 0; + ssize_t bytes_rcvd; + int si_data1 = 0; + int si_data2 = 0; + mge_info_item_t *item; + models_name_t *model_info; + mge_model_info_t *legacy_model; + char infostr[32]; + ssize_t chars_rcvd; /* Try to set "Low Battery Level" (if supported and given) */ if (getval ("lowbatt")) @@ -226,28 +252,6 @@ void upsdrv_initups(void) else upsdebugx(1, "initups: OffDelay unavailable"); } -} - -/* --------------------------------------------------------------- */ - -void upsdrv_initinfo(void) -{ - char buf[BUFFLEN]; - const char *model = NULL; - char *firmware = NULL; - char *p; - char *v = NULL; /* for parsing Si output, get Version ID */ - int table; - int tries; - int status_ok = 0; - ssize_t bytes_rcvd; - int si_data1 = 0; - int si_data2 = 0; - mge_info_item_t *item; - models_name_t *model_info; - mge_model_info_t *legacy_model; - char infostr[32]; - ssize_t chars_rcvd; /* manufacturer -------------------------------------------- */ dstate_setinfo("ups.mfr", "MGE UPS SYSTEMS"); @@ -534,6 +538,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* --------------------------------------------------------------- */ /* Internal Functions */ /* --------------------------------------------------------------- */ diff --git a/drivers/microdowell.c b/drivers/microdowell.c index 58081a3ac1..f4f588cae7 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.06" +#define DRIVER_VERSION "0.07" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1033,6 +1033,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/microsol-apc.c b/drivers/microsol-apc.c index 71c16d8775..e6afa63c9f 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.72" +#define DRIVER_VERSION "0.73" /* driver description structure */ upsdrv_info_t upsdrv_info = { diff --git a/drivers/microsol-common.c b/drivers/microsol-common.c index 7e7b8ac6e4..18394b98ac 100644 --- a/drivers/microsol-common.c +++ b/drivers/microsol-common.c @@ -794,6 +794,11 @@ void upsdrv_help(void) printf(" These are valid only if prgshut = 2 or 3\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "battext", "Battery extension (0-80AH)"); diff --git a/drivers/netxml-ups.c b/drivers/netxml-ups.c index fefde6add5..20faa09229 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.48" +#define DRIVER_VERSION "0.49" /** *_OBJECT query multi-part body boundary */ #define FORM_POST_BOUNDARY "NUT-NETXML-UPS-OBJECTS" @@ -557,6 +557,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/nhs_ser.c b/drivers/nhs_ser.c index 4e2be2741f..a7dbfe0954 100644 --- a/drivers/nhs_ser.c +++ b/drivers/nhs_ser.c @@ -43,7 +43,7 @@ #include #define DRIVER_NAME "NHS Nobreak Drivers" -#define DRIVER_VERSION "0.02" +#define DRIVER_VERSION "0.04" #define MANUFACTURER "NHS Sistemas Eletronicos LTDA" #define DEFAULTBAUD 2400 @@ -96,10 +96,22 @@ static baud_rate_t baud_rates[] = { { B921600, 921600, "921600 bps" }, { B1500000, 1500000, "1.5 Mbps" }, { B2000000, 2000000, "2 Mbps" }, +/* NOTE: Per https://github.com/networkupstools/nut/issues/3163 + * not all platforms offer all baud rates, so we wrap some into + * conditional uses. + */ +#ifdef B2500000 { B2500000, 2500000, "2.5 Mbps" }, +#endif +#ifdef B3000000 { B3000000, 3000000, "3 Mbps" }, +#endif +#ifdef B3500000 { B3500000, 3500000, "3.5 Mbps" }, +#endif +#ifdef B4000000 { B4000000, 4000000, "4 Mbps" }, +#endif }; #define NUM_BAUD_RATES (sizeof(baud_rates) / sizeof(baud_rates[0])) @@ -2471,3 +2483,8 @@ void upsdrv_makevartable(void) { void upsdrv_help(void) { } + +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} diff --git a/drivers/nut-ipmipsu.c b/drivers/nut-ipmipsu.c index aa56464a1b..c9229f8e7a 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.35" +#define DRIVER_VERSION "0.36" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -204,6 +204,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/nutdrv_atcl_usb.c b/drivers/nutdrv_atcl_usb.c index 951514784e..c306645a01 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.20" +#define DRIVER_VERSION "1.22" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -53,9 +53,12 @@ upsdrv_info_t upsdrv_info = { #define USB_VENDOR_STRING "ATCL FOR UPS" +/* Unregistered vendor 0x0001 (commonly identified as Fry's Electronics) */ +#define NONAME0001_VENDORID 0x0001 + static usb_device_id_t atcl_usb_id[] = { /* ATCL FOR UPS */ - { USB_DEVICE(0x0001, 0x0000), NULL }, + { USB_DEVICE(NONAME0001_VENDORID, 0x0000), NULL }, /* Terminating entry */ { 0, 0, NULL } @@ -559,11 +562,14 @@ void upsdrv_initups(void) "Unable to find ATCL FOR UPS\n\n" "Things to try:\n" - " - Connect UPS device to USB bus\n" + " - Connect UPS device to USB bus.\n" " - Run this driver as another user (upsdrvctl -u or 'user=...' in ups.conf).\n" - " See upsdrvctl(8) and ups.conf(5).\n\n" + " See upsdrvctl(%s) and ups.conf(%s).\n" + " - Check with nutdrv_qx(%s) or blazer_usb(%s) driver instead.\n" - "Fatal error: unusable configuration"); + "\nFatal error: unusable configuration", + MAN_SECTION_CMD_SYS, MAN_SECTION_CFG, + MAN_SECTION_CMD_SYS, MAN_SECTION_CMD_SYS); } } @@ -735,6 +741,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { /* NOTE: This driver uses a very custom device matching method, diff --git a/drivers/nutdrv_hashx.c b/drivers/nutdrv_hashx.c index 33f187baa9..08c316af5d 100644 --- a/drivers/nutdrv_hashx.c +++ b/drivers/nutdrv_hashx.c @@ -28,7 +28,6 @@ #include "serial.h" #include "str.h" #include "upshandler.h" -#include #include #include @@ -36,7 +35,7 @@ #define IGNCHARS "" #define DRIVER_NAME "Generic #* Serial driver" -#define DRIVER_VERSION "0.02" +#define DRIVER_VERSION "0.03" #define SESSION_ID "OoNUTisAMAZINGoO" #define SESSION_HASH "74279F35A48F5F13" @@ -644,6 +643,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { } diff --git a/drivers/nutdrv_qx.c b/drivers/nutdrv_qx.c index b5824a4d64..a7bd3905b0 100644 --- a/drivers/nutdrv_qx.c +++ b/drivers/nutdrv_qx.c @@ -58,7 +58,7 @@ # define DRIVER_NAME "Generic Q* Serial driver" #endif /* QX_USB */ -#define DRIVER_VERSION "0.45" +#define DRIVER_VERSION "0.48" #ifdef QX_SERIAL # include "serial.h" @@ -73,6 +73,7 @@ #include "nutdrv_qx_hunnox.h" #include "nutdrv_qx_innovart31.h" #include "nutdrv_qx_innovart33.h" +#include "nutdrv_qx_innovatae.h" #include "nutdrv_qx_mecer.h" #include "nutdrv_qx_megatec.h" #include "nutdrv_qx_megatec-old.h" @@ -106,6 +107,7 @@ static subdriver_t *subdriver_list[] = { &ablerex_subdriver, &innovart31_subdriver, &innovart33_subdriver, + &innovatae_subdriver, &q2_subdriver, &q6_subdriver, >ec_subdriver, @@ -294,6 +296,148 @@ int qx_multiply_m2s(item_t *item, char *value, const size_t valuelen) { return 0; } +static void analyze_mapping_usage(void) { + /* Check if the subdriver code (mappings) and the device report + * sit together well. Note that for yet-unknown concepts, the + * NUT driver developers can either raise a discussion on how + * to best formalize that concept via docs/nut-names.txt, or + * temporarily place them into "experimental.*" or "unmapped.*" + * namespaces. + * + * Later also check that all defined mappings were used? + * TBH, this is unlikely in practice, so of little value + * (unless we are troubleshooting and under 5 or 10 data + * points are served from actually the device, and not + * from user configs or driver fallbacks). + * + * See also: similar methods in usbhid-ups and snmp-ups. + */ + size_t unused_count = 0, known_mappings = 0; + size_t unused_bufsize = LARGEBUF, unused_prevlen = 0, used_mappings = 0; + int ret_printf; + char *unused_names = NULL; + item_t *item; + + /* FIXME? this activity is limited to when debugging is enabled, even + * if some of the messages below can be posted visibly at level 0. + */ + if (nut_debug_level < 1) + return; + + upsdebugx(1, "%s: checking if the subdriver code (mappings) " + "consults all data points from the device report", + __func__); + + if (!subdriver->qx2nut) { + upsdebugx(1, "%s: SKIP: subdriver->qx2nut==null", __func__); + return; + } + + unused_names = xcalloc(unused_bufsize, sizeof(char)); + + for (item = subdriver->qx2nut; item->info_type != NULL; item++) { + if (!item) + continue; + + known_mappings++; + + if (item->qxflags & QX_FLAG_MAPPING_HANDLED) { + used_mappings++; + } else { + const char *pName = item->info_type; + const char *pType = (item->qxflags & QX_FLAG_CMD ? "cmd" : "data"); + int retry = 0; + + /* Keep aliases for code similarity with usbhid-ups and nutdrv_qx */ + char **pNames = &unused_names; + size_t *pCount = &unused_count, *pPrevLen = &unused_prevlen, *pBufSize = &unused_bufsize; + + if (!pName) { + upsdebugx(2, "%s: error getting a data point name, skipped", __func__); + continue; + } + + /* We may overflow the pre-allocated buffer, + * so we loop here until snprintf() succeeds + * or we are known to have failed completely. + */ + do { + retry = 0; + if (!*pNames) { + break; + } + + upsdebugx(5, "%s: adding '%s (%s)' (%" PRIuSIZE " bytes) " + "to buffer of %" PRIuSIZE "/%" PRIuSIZE " bytes", + __func__, NUT_STRARG(pName), NUT_STRARG(pType), + pName ? strlen(pName) : 0, + *pPrevLen, *pBufSize); + + ret_printf = snprintf(*pNames + *pPrevLen, *pBufSize - *pPrevLen - 1, "%s%s (%s)", + *pCount ? ", " : "", NUT_STRARG(pName), NUT_STRARG(pType)); + + upsdebugx(6, "%s: snprintf() returned %d", __func__, ret_printf); + (*pNames)[*pBufSize - 1] = '\0'; + + if (ret_printf < 0) { + upsdebugx(1, "%s: error collecting names, might not report unused descriptor names", __func__); + } else if ((size_t)ret_printf + *pPrevLen >= *pBufSize) { + if (*pBufSize < SIZE_MAX - LARGEBUF) { + *pBufSize = *pBufSize + LARGEBUF; + upsdebugx(1, "%s: buffer overflowed, trying to re-allocate as %" PRIuSIZE, __func__, *pBufSize); + *pNames = realloc(*pNames, *pBufSize); + + if (!*pNames) { + upsdebugx(1, "%s: buffer overflowed, will not report unused descriptor names", __func__); + } else { + upsdebugx(5, "%s: buffer overflowed, but reallocated successfully - retrying", __func__); + /* Retry this loop */ + retry = 1; + } + } else { + upsdebugx(1, "%s: buffer overflowed, might not report unused descriptor names", __func__); + } + } else { + *pPrevLen += (size_t)ret_printf; + } + } while (retry); + + *pCount = *pCount + 1; + } + } + + if (unused_count) { + upsdebugx(1, "%s: %" PRIuSIZE " items are present in the " + "mapping table for the SNMP UPS, but %" PRIuSIZE " " + "of them were completely not used by name via the " + "mapping defined in the selected NUT subdriver %s: %s", + __func__, known_mappings, unused_count, + NUT_STRARG(subdriver->name), NUT_STRARG(unused_names)); + } + + if (unused_names) + free(unused_names); + + /* We arbitrarily declare that having under 10 known or used + * mappings is few enough to be loud about this */ + if (known_mappings < 10 || used_mappings < 10) { + upsdebugx(0, + "%s: %" PRIuSIZE " mapping entries are defined, and " + "%" PRIuSIZE " were actually used from SNMP walk, " + "in the selected NUT subdriver %s", + __func__, known_mappings, used_mappings, + NUT_STRARG(subdriver->name)); + + upsdebugx(0, "Please check if there is a newer version of NUT available " + "(may be not packaged for your distribution yet), try a custom " + "build of development branch to test latest driver code per " + "%s/docs/user-manual.chunked/_installation_instructions.html#Installing_inplace, " + "and see %s/docs/developer-guide.chunked/new-drivers.html#nutdrv_qx-subdrivers " + "for suggestions how you can help improve this driver.", + NUT_WEBSITE_BASE, NUT_WEBSITE_BASE); + } +} + /* Fill batt.volt.act and guesstimate the battery charge * if it isn't already available. */ static int qx_battery(void) @@ -2016,10 +2160,10 @@ static void load_armac_endpoint_cache(void) libusb_free_config_descriptor(config_descriptor); return; } - + for (i = 0; i < interface_descriptor->bNumEndpoints; i++) { const struct libusb_endpoint_descriptor *endpoint = &interface_descriptor->endpoint[i]; - + if (endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) { found_in = TRUE; armac_endpoint_cache.in_endpoint_address = endpoint->bEndpointAddress; @@ -2374,28 +2518,62 @@ typedef struct { void *(*fun)(USBDevice_t *); /* Handler for specific processing */ } qx_usb_device_id_t; +/* Unregistered vendor 0x0001 (commonly identified as Fry's Electronics) */ +#define NONAME0001_VENDORID 0x0001 + +/* Unregistered vendor 0xFFFF */ +#define NONAMEFFFF_VENDORID 0xffff + +/* ST Microelectronics */ +#define STMICRO_VENDORID 0x0483 + +/* Sysgration Ltd. */ +#define SYSGRATION_VENDORID 0x05b8 + +/* Cypress Semiconductor */ +#define CYPRESS_VENDORID 0x0665 + +/* Phoenixtec Power Co., Ltd */ +#define PHOENIXTEC_VENDORID 0x06da + +/* Lakeview Research */ +#define LAKEVIEW_VENDORID 0x0925 + +/* Unitek UPS Systems */ +#define UNITEK_VENDORID 0x0f03 + +/* GE */ +#define GE_VENDORID 0x14f0 + +/* QinHeng Electronics */ +#define QINHENG_VENDORID 0x1a86 + +/* Legrand */ +#define LEGRAND_VENDORID 0x1cb0 + /* USB VendorID/ProductID/iManufacturer/iProduct match - note: rightmost comment is used for naming rules by tools/nut-usbinfo.pl */ static qx_usb_device_id_t qx_usb_id[] = { - { USB_DEVICE(0x05b8, 0x0000), NULL, NULL, &cypress_subdriver }, /* Agiler UPS */ - { USB_DEVICE(0xffff, 0x0000), NULL, NULL, &ablerex_subdriver_fun }, /* Ablerex 625L USB (Note: earlier best-fit was "krauler_subdriver" before PR #1135) */ - { USB_DEVICE(0x1cb0, 0x0035), NULL, NULL, &krauler_subdriver }, /* Legrand Daker DK / DK Plus */ - { USB_DEVICE(0x0665, 0x5161), NULL, NULL, &cypress_subdriver }, /* Belkin F6C1200-UNV/Voltronic Power UPSes */ - { USB_DEVICE(0x06da, 0x0002), "Phoenixtec Power","USB Cable (V2.00)", &phoenixtec_subdriver },/* Masterguard A Series */ - { USB_DEVICE(0x06da, 0x0002), NULL, NULL, &cypress_subdriver }, /* Online Yunto YQ450 */ - { USB_DEVICE(0x06da, 0x0003), NULL, NULL, &ippon_subdriver }, /* Mustek Powermust */ - { USB_DEVICE(0x06da, 0x0004), NULL, NULL, &cypress_subdriver }, /* Phoenixtec Innova 3/1 T */ - { USB_DEVICE(0x06da, 0x0005), NULL, NULL, &cypress_subdriver }, /* Phoenixtec Innova RT */ - { USB_DEVICE(0x06da, 0x0201), NULL, NULL, &cypress_subdriver }, /* Phoenixtec Innova T */ - { USB_DEVICE(0x06da, 0x0601), NULL, NULL, &phoenix_subdriver }, /* Online Zinto A */ - { USB_DEVICE(0x0f03, 0x0001), NULL, NULL, &cypress_subdriver }, /* Unitek Alpha 1200Sx */ - { USB_DEVICE(0x14f0, 0x00c9), NULL, NULL, &phoenix_subdriver }, /* GE EP series */ - { USB_DEVICE(0x0483, 0x0035), NULL, NULL, &sgs_subdriver }, /* TS Shara UPSes; vendor ID 0x0483 is from ST Microelectronics - with product IDs delegated to different OEMs */ - { USB_DEVICE(0x0001, 0x0000), "MEC", "MEC0003", &fabula_subdriver }, /* Fideltronik/MEC LUPUS 500 USB */ - { USB_DEVICE(0x0001, 0x0000), NULL, "MEC0003", &fabula_hunnox_subdriver }, /* Hunnox HNX 850, reported to also help support Powercool and some other devices; closely related to fabula with tweaks */ - { 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(SYSGRATION_VENDORID, 0x0000), NULL, NULL, &cypress_subdriver }, /* Agiler UPS */ + { USB_DEVICE(NONAMEFFFF_VENDORID, 0x0000), NULL, NULL, &ablerex_subdriver_fun }, /* Ablerex 625L USB (Note: earlier best-fit was "krauler_subdriver" before PR #1135) */ + { USB_DEVICE(LEGRAND_VENDORID, 0x0035), NULL, NULL, &krauler_subdriver }, /* Legrand Daker DK / DK Plus */ + { USB_DEVICE(CYPRESS_VENDORID, 0x5161), NULL, NULL, &cypress_subdriver }, /* Belkin F6C1200-UNV/Voltronic Power UPSes */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0002), "Phoenixtec Power","USB Cable (V2.00)", &phoenixtec_subdriver },/* Masterguard A Series */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0002), NULL, NULL, &cypress_subdriver }, /* Online Yunto YQ450 */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0003), NULL, NULL, &ippon_subdriver }, /* Mustek Powermust */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0004), NULL, NULL, &cypress_subdriver }, /* Phoenixtec Innova 3/1 T */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0005), NULL, NULL, &cypress_subdriver }, /* Phoenixtec Innova RT */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0201), NULL, NULL, &cypress_subdriver }, /* Phoenixtec Innova T */ + { USB_DEVICE(PHOENIXTEC_VENDORID, 0x0601), NULL, NULL, &phoenix_subdriver }, /* Online Zinto A */ + { USB_DEVICE(UNITEK_VENDORID, 0x0001), NULL, NULL, &cypress_subdriver }, /* Unitek Alpha 1200Sx */ + { USB_DEVICE(GE_VENDORID, 0x00c9), NULL, NULL, &phoenix_subdriver }, /* GE EP series */ + { USB_DEVICE(QINHENG_VENDORID, 0x7523), NULL, NULL, NULL }, /* Ippon Innova TAE series, using QinHeng Electronics CH340 serial converter; no specific "USB subdriver" handler defined at the moment */ + { USB_DEVICE(STMICRO_VENDORID, 0x0035), NULL, NULL, &sgs_subdriver }, /* TS Shara UPSes; vendor ID 0x0483 is from ST Microelectronics - with product IDs delegated to different OEMs */ + { USB_DEVICE(NONAME0001_VENDORID, 0x0000), "MEC", "MEC0003", &fabula_subdriver }, /* Fideltronik/MEC LUPUS 500 USB */ + { USB_DEVICE(NONAME0001_VENDORID, 0x0000), NULL, "MEC0003", &fabula_hunnox_subdriver }, /* Hunnox HNX 850, reported to also help support Powercool and some other devices; closely related to fabula with tweaks */ + { USB_DEVICE(NONAME0001_VENDORID, 0x0000), "ATCL FOR UPS", "ATCL FOR UPS", &fuji_subdriver }, /* Fuji UPSes */ + { USB_DEVICE(NONAME0001_VENDORID, 0x0000), NULL, NULL, &krauler_subdriver }, /* Krauler UP-M500VA */ + { USB_DEVICE(NONAME0001_VENDORID, 0x0000), NULL, "MEC0003", &snr_subdriver }, /* SNR-UPS-LID-XXXX UPSes */ + { USB_DEVICE(LAKEVIEW_VENDORID, 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 } }; @@ -3107,6 +3285,11 @@ void upsdrv_help(void) #endif /* TESTING */ } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* Adding flags/vars */ void upsdrv_makevartable(void) { @@ -3263,6 +3446,8 @@ void upsdrv_initinfo(void) fatalx(EXIT_FAILURE, "Can't initialise data from the UPS"); } + analyze_mapping_usage(); + /* Init battery guesstimation */ qx_initbattery(); @@ -4599,7 +4784,8 @@ int ups_infoval_set(item_t *item) } if (item->qxflags & QX_FLAG_NONUT) { - upslogx(LOG_INFO, "%s: %s", item->info_type, value); + /* Hides QX_FLAG_NONUT variables from syslog unless the debug level is raised */ + upsdebugx(2, "%s: %s", item->info_type, value); return 1; } @@ -4609,6 +4795,7 @@ int ups_infoval_set(item_t *item) return -1; } + item->qxflags |= QX_FLAG_MAPPING_HANDLED; dstate_setinfo(item->info_type, "%s", value); /* Fill batt.{chrg,runt}.act for guesstimation */ diff --git a/drivers/nutdrv_qx.h b/drivers/nutdrv_qx.h index ba15e61c70..e860efd0e0 100644 --- a/drivers/nutdrv_qx.h +++ b/drivers/nutdrv_qx.h @@ -128,6 +128,7 @@ typedef struct item_t { #define QX_FLAG_RANGE 512UL /* Ranges for this var available and are stored in info_rw. */ #define QX_FLAG_NONUT 1024UL /* This var doesn't have a corresponding var in NUT. */ #define QX_FLAG_SKIP 2048UL /* Skip this var: this item won't be processed. */ +#define QX_FLAG_MAPPING_HANDLED 4096UL /* raised internally if any (sub)driver handling loop care about this report; if not, may be a point for improvement... */ #define MAXTRIES 3 /* Max number of retries */ diff --git a/drivers/nutdrv_qx_blazer-common.c b/drivers/nutdrv_qx_blazer-common.c index 1fe00226dc..fd7582ec97 100644 --- a/drivers/nutdrv_qx_blazer-common.c +++ b/drivers/nutdrv_qx_blazer-common.c @@ -1,4 +1,4 @@ -/* nutdrv_qx_blazer-common.c - Common functions/settings for nutdrv_qx_{innovart31,innovart33,mecer,megatec,megatec-old,mustek,q1,q2,q6,voltronic-qs,zinto}.{c,h} +/* nutdrv_qx_blazer-common.c - Common functions/settings for nutdrv_qx_{innovart31,innovart33,innovatae,mecer,megatec,megatec-old,mustek,q1,q2,q6,voltronic-qs,zinto}.{c,h} * * Copyright (C) * 2013 Daniele Pezzini diff --git a/drivers/nutdrv_qx_blazer-common.h b/drivers/nutdrv_qx_blazer-common.h index 997eaa21c5..09bb1ff0f2 100644 --- a/drivers/nutdrv_qx_blazer-common.h +++ b/drivers/nutdrv_qx_blazer-common.h @@ -1,4 +1,4 @@ -/* nutdrv_qx_blazer-common.h - Common functions/settings for nutdrv_qx_{innovart31,innovart33,mecer,megatec,megatec-old,mustek,q1,q2,q6,voltronic-qs,zinto}.{c,h} +/* nutdrv_qx_blazer-common.h - Common functions/settings for nutdrv_qx_{innovart31,innovart33,innovatae,mecer,megatec,megatec-old,mustek,q1,q2,q6,voltronic-qs,zinto}.{c,h} * * Copyright (C) * 2013 Daniele Pezzini diff --git a/drivers/nutdrv_qx_innovatae.c b/drivers/nutdrv_qx_innovatae.c new file mode 100644 index 0000000000..1a5a693705 --- /dev/null +++ b/drivers/nutdrv_qx_innovatae.c @@ -0,0 +1,148 @@ +/* nutdrv_qx_innovatae.c - Subdriver for INNOVA TAE series + * + * Copyright (C) + * 2025 Viktor Drobot + * + * 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 "nutdrv_qx.h" +#include "nutdrv_qx_blazer-common.h" + +#include "nutdrv_qx_innovatae.h" + +#define INNOVATAE_VERSION "INNOVATAE 0.01" + +static void innovatae_initups(void); + +/* qx2nut lookup table */ +static item_t innovatae_qx2nut[] = { + /* Static values */ + { "device.mfr", 0, NULL, "", "", 0, '\0', "", 0, 0, "Ippon", QX_FLAG_STATIC | QX_FLAG_ABSENT,NULL, NULL, NULL }, + + /* + * > [Q1\r] + * < [(226.0 195.0 226.0 014 49.0 27.5 30.0 00001000\r] + * 01234567890123456789012345678901234567890123456 + * 0 1 2 3 4 + */ + + /* Common parameters */ + { "input.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 1, 5, "%.1f", 0, NULL, NULL, NULL }, + { "input.voltage.fault", 0, NULL, "Q1\r", "", 47, '(', "", 7, 11, "%.1f", 0, NULL, NULL, NULL }, + { "output.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 13, 17, "%.1f", 0, NULL, NULL, NULL }, + { "ups.load", 0, NULL, "Q1\r", "", 47, '(', "", 19, 21, "%.0f", 0, NULL, NULL, NULL }, + { "input.frequency", 0, NULL, "Q1\r", "", 47, '(', "", 23, 26, "%.1f", 0, NULL, NULL, NULL }, + { "battery.voltage", 0, NULL, "Q1\r", "", 47, '(', "", 28, 31, "%.2f", 0, NULL, NULL, qx_multiply_battvolt }, + { "ups.temperature", 0, NULL, "Q1\r", "", 47, '(', "", 33, 36, "%.1f", 0, NULL, NULL, NULL }, + + /* Status bits */ + { "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 38, 38, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Utility Fail (Immediate) */ + { "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 39, 39, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Battery Low */ + { "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 40, 40, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Bypass/Boost or Buck Active */ + { "ups.alarm", 0, NULL, "Q1\r", "", 47, '(', "", 41, 41, NULL, 0, NULL, NULL, blazer_process_status_bits }, /* UPS Failed */ + { "ups.type", 0, NULL, "Q1\r", "", 47, '(', "", 42, 42, "%s", QX_FLAG_STATIC, NULL, NULL, blazer_process_status_bits }, /* UPS Type */ + { "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 43, 43, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Test in Progress */ + { "ups.status", 0, NULL, "Q1\r", "", 47, '(', "", 44, 44, NULL, QX_FLAG_QUICK_POLL, NULL, NULL, blazer_process_status_bits }, /* Shutdown Active */ + { "ups.beeper.status", 0, NULL, "Q1\r", "", 47, '(', "", 45, 45, "%s", 0, NULL, NULL, blazer_process_status_bits }, /* Beeper status */ + + /* + * > [F\r] + * < [#230.0 004 024.0 50.0\r] + * 0123456789012345678901 + * 0 1 2 + */ + + /* Nominal parameters (ratings) */ + { "input.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 1, 5, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL }, + { "input.current.nominal", 0, NULL, "F\r", "", 22, '#', "", 7, 9, "%.1f", QX_FLAG_STATIC, NULL, NULL, NULL }, + { "battery.voltage.nominal", 0, NULL, "F\r", "", 22, '#', "", 11, 15, "%.1f", QX_FLAG_STATIC, NULL, NULL, NULL }, + { "input.frequency.nominal", 0, NULL, "F\r", "", 22, '#', "", 17, 20, "%.0f", QX_FLAG_STATIC, NULL, NULL, NULL }, + + /* + * > [I\r] + * < [#222222222222222222222222222R1.00.48 \r] + * 012345678901234567890123456789012345678 + * 0 1 2 3 + */ + + /* Firmware version */ + /* According to the Megatec Qx protocol implementation (nutdrv_qx_megatec.c) firmware version can be reported + * via I command (positions 28-35) and answer length may vary between 38 and 39. + * Referenced source suggests to use lesser number + */ + { "ups.firmware", 0, NULL, "I\r", "", 38, '#', "", 28, 35, "%s", QX_FLAG_STATIC, NULL, NULL, NULL }, + + /* Instant commands */ + { "beeper.toggle", 0, NULL, "Q\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "load.off", 0, NULL, "S00R0000\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "load.on", 0, NULL, "C\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "shutdown.return", 0, NULL, "S%s\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command }, + { "shutdown.stayoff", 0, NULL, "S%sR0000\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command }, + { "shutdown.stop", 0, NULL, "C\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "test.battery.start", 0, NULL, "T%02d\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, blazer_process_command }, + { "test.battery.start.deep", 0, NULL, "TL\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "test.battery.start.quick", 0, NULL, "T\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "test.battery.stop", 0, NULL, "CT\r", "", 0, 0, "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + + /* Server-side settable vars */ + { "ups.delay.start", ST_FLAG_RW, blazer_r_ondelay, NULL, "", 0, 0, "", 0, 0, DEFAULT_ONDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, blazer_process_setvar }, + { "ups.delay.shutdown", ST_FLAG_RW, blazer_r_offdelay, NULL, "", 0, 0, "", 0, 0, DEFAULT_OFFDELAY, QX_FLAG_ABSENT | QX_FLAG_SETVAR | QX_FLAG_RANGE, NULL, NULL, blazer_process_setvar }, + + /* End of structure. */ + { NULL, 0, NULL, NULL, "", 0, 0, "", 0, 0, NULL, 0, NULL, NULL, NULL } +}; + +/* Testing table */ +#ifdef TESTING +static testing_t innovatae_testing[] = { + { "Q1\r", "(215.0 195.0 230.0 014 49.0 22.7 30.0 00000000\r", -1 }, + { "Q\r", "", -1 }, + { "F\r", "#230.0 004 024.0 50.0\r", -1 }, + { "I\r", "#222222222222222222222222222R1.00.48\r", -1 }, + { "S03\r", "", -1 }, + { "C\r", "", -1 }, + { "S02R0005\r", "", -1 }, + { "S.5R0000\r", "", -1 }, + { "T04\r", "ACK", -1 }, + { "TL\r", "ACK", -1 }, + { "T\r", "ACK", -1 }, + { "CT\r", "ACK", -1 }, + { NULL } +}; +#endif /* TESTING */ + +/* Subdriver-specific initups */ +static void innovatae_initups(void) +{ + blazer_initups_light(innovatae_qx2nut); +} + +/* Subdriver interface */ +subdriver_t innovatae_subdriver = { + INNOVATAE_VERSION, + blazer_claim_light, + innovatae_qx2nut, + innovatae_initups, + NULL, + blazer_makevartable_light, + "ACK", + "NAK\r", +#ifdef TESTING + innovatae_testing, +#endif /* TESTING */ +}; diff --git a/drivers/nutdrv_qx_innovatae.h b/drivers/nutdrv_qx_innovatae.h new file mode 100644 index 0000000000..9422a44fa9 --- /dev/null +++ b/drivers/nutdrv_qx_innovatae.h @@ -0,0 +1,29 @@ +/* nutdrv_qx_innovatae.h - Subdriver for INNOVA TAE series + * + * Copyright (C) + * 2025 Viktor Drobot + * + * 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 NUTDRV_QX_INNOVATAE_H +#define NUTDRV_QX_INNOVATAE_H + +#include "nutdrv_qx.h" + +extern subdriver_t innovatae_subdriver; + +#endif /* NUTDRV_QX_INNOVATAE_H */ diff --git a/drivers/nutdrv_qx_masterguard.c b/drivers/nutdrv_qx_masterguard.c index e5549d3357..2acaee90a5 100644 --- a/drivers/nutdrv_qx_masterguard.c +++ b/drivers/nutdrv_qx_masterguard.c @@ -26,7 +26,7 @@ #include #include "nut_stdint.h" -#define MASTERGUARD_VERSION "Masterguard 0.03" +#define MASTERGUARD_VERSION "Masterguard 0.05" /* series (for un-SKIP) */ static char masterguard_my_series = '?'; @@ -456,7 +456,7 @@ static int masterguard_add_slaveaddr(item_t *item, char *command, const size_t c } upsdebugx(4, "add slaveaddr %s to command %s", masterguard_my_slaveaddr, command); memcpy(command + l - 3, masterguard_my_slaveaddr, 2); - return 0; + return l; } @@ -767,6 +767,8 @@ static item_t masterguard_qx2nut[] = { */ /* type flags rw command answer len leading value from to dfl qxflags precmd preans preproc */ { "battery.charge", 0, NULL, "GBS,XX\r", "", 43, '(', "", 4, 6, "%.0f", 0, masterguard_add_slaveaddr, NULL, NULL }, + { "experimental.battery.ageing.factor", 0, NULL, "GBS,XX\r", "", 43, '(', "", 18, 21, "%.0f", QX_FLAG_NONUT, masterguard_add_slaveaddr, NULL, NULL }, + { "experimental.battery.calibration.factor", 0, NULL, "GBS,XX\r", "", 43, '(', "", 33, 35, "%.0f", QX_FLAG_NONUT, masterguard_add_slaveaddr, NULL, NULL }, /* * hhhh: hold time (minutes) * HHHH: recharge time to 90% (minutes) @@ -858,11 +860,13 @@ static item_t masterguard_qx2nut[] = { /* test.failure.stop */ { "test.battery.start", 0, NULL, NULL, "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, masterguard_test_battery }, { "test.battery.start.quick", 0, NULL, "T\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, - { "test.battery.start.deep", 0, NULL, "TUD\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "test.battery.start.low", 0, NULL, "TL\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, + { "test.battery.start.deep", 0, NULL, "TUD,XX\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, masterguard_add_slaveaddr, NULL, NULL }, { "test.battery.stop", 0, NULL, "CT\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, /* test.system.start */ /* calibrate.start */ /* calibrate.stop */ + { "clear.fault.record", 0, NULL, "FCLR,XX\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, masterguard_add_slaveaddr, NULL, NULL }, { "bypass.start", 0, NULL, "FOFF\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, { "bypass.stop", 0, NULL, "FON\r", "", 0, '\0', "", 0, 0, NULL, QX_FLAG_CMD, NULL, NULL, NULL }, /* reset.input.minmax */ @@ -1060,6 +1064,8 @@ static void masterguard_makevartable(void) { addvar(VAR_VALUE, "fault_3", "Fault record 3"); addvar(VAR_VALUE, "fault_4", "Fault record 4"); addvar(VAR_VALUE, "fault_5", "Fault record 5 (oldest)"); + addvar(VAR_VALUE, "experimental.battery.ageing.factor", "Battery ageing factor (promille, 1000=new)"); + addvar(VAR_VALUE, "experimental.battery.calibration.factor", "Battery calibration factor (percent)"); } diff --git a/drivers/nutdrv_qx_megatec.c b/drivers/nutdrv_qx_megatec.c index 1f3c9a6200..fa941ee213 100644 --- a/drivers/nutdrv_qx_megatec.c +++ b/drivers/nutdrv_qx_megatec.c @@ -25,7 +25,7 @@ #include "nutdrv_qx_megatec.h" -#define MEGATEC_VERSION "Megatec 0.08" +#define MEGATEC_VERSION "Megatec 0.09" /* qx2nut lookup table */ static item_t megatec_qx2nut[] = { @@ -99,7 +99,7 @@ static item_t megatec_qx2nut[] = { { "device.mfr", 0, NULL, "I\r", "", 38, '#', "", 1, 7, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL }, /* Shorter field than in other dialects */ { "ups.serial", 0, NULL, "I\r", "", 38, '#', "", 8, 15, "%s", 0, NULL, NULL, NULL }, /* Megatec IC adds "ups.serial" support function */ { "device.model", 0, NULL, "I\r", "", 38, '#', "", 17, 21, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL }, /* Shorter field than in other dialects */ - { "battery.runtime", 0, NULL, "I\r", "", 38, '#', "", 22, 26, "%s", 0, NULL, NULL, NULL }, /* Megatec IC adds "ups.runtime" support function */ + { "battery.runtime", 0, NULL, "I\r", "", 38, '#', "", 22, 26, "%s", QX_FLAG_TRIM, NULL, NULL, NULL }, /* Megatec IC adds "ups.runtime" support function */ { "ups.firmware", 0, NULL, "I\r", "", 38, '#', "", 28, 0, "%s", QX_FLAG_STATIC | QX_FLAG_TRIM, NULL, NULL, NULL }, /* Note: Per example above, this should end at 36 not 37 as in other dialects; we say 0 to go to end of line, wherever that is in fact */ /* Instant commands */ diff --git a/drivers/nutdrv_siemens_sitop.c b/drivers/nutdrv_siemens_sitop.c index 811365b578..92c6a0bb44 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.07" +#define DRIVER_VERSION "0.08" #define RX_BUFFER_SIZE 100 @@ -268,6 +268,11 @@ void upsdrv_shutdown(void) { void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { /* allow '-x max_polls_without_data=' */ diff --git a/drivers/oneac.c b/drivers/oneac.c index 732e75ef19..285609b46e 100644 --- a/drivers/oneac.c +++ b/drivers/oneac.c @@ -47,11 +47,11 @@ #include "nut_stdint.h" /* Prototypes to allow setting pointer before function is defined */ -int setcmd(const char* varname, const char* setvalue); +int setvar(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.85" +#define DRIVER_VERSION "0.87" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -200,11 +200,6 @@ void upsdrv_initups(void) { upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, B9600); - - /*get the UPS in the right frame of mind*/ - ser_send_pace(upsfd, 100, "%s", COMMAND_END); - ser_send_pace(upsfd, 100, "%s", COMMAND_END); - sleep (1); } void upsdrv_initinfo(void) @@ -215,6 +210,11 @@ void upsdrv_initinfo(void) ssize_t RetValue; char buffer[256], buffer2[32]; + /* Get the UPS in the right frame of mind */ + ser_send_pace(upsfd, 100, "%s", COMMAND_END); + ser_send_pace(upsfd, 100, "%s", COMMAND_END); + sleep (1); + /* All families should reply to this request so we can confirm that it is * an ONEAC UPS */ @@ -252,7 +252,7 @@ void upsdrv_initinfo(void) dstate_addcmd("shutdown.stop"); dstate_addcmd("shutdown.reboot"); - upsh.setvar = setcmd; + upsh.setvar = setvar; upsh.instcmd = instcmd; /* set some stuff that shouldn't change after initialization */ @@ -837,6 +837,11 @@ void upsdrv_help(void) printf("You must set the UPS interface card DIP switch to 9600 BPS\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_cleanup(void) { ser_close(upsfd, device_path); @@ -962,7 +967,7 @@ int instcmd(const char *cmdname, const char *extra) } -int setcmd(const char* varname, const char* setvalue) +int setvar(const char* varname, const char* setvalue) { upsdebug_SET_STARTING(varname, setvalue); diff --git a/drivers/optiups.c b/drivers/optiups.c index 12765aac62..4a81ad1fb5 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.08" +#define DRIVER_VERSION "1.09" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -424,8 +424,9 @@ void upsdrv_initinfo(void) dstate_addcmd("test.failure.start"); dstate_addcmd("load.off"); dstate_addcmd("load.on"); - if( optimodel != OPTIMODEL_ZINTO ) - dstate_addcmd("shutdown.stop"); + if (optimodel != OPTIMODEL_ZINTO) { + dstate_addcmd("shutdown.stop"); + } dstate_addcmd("shutdown.return"); dstate_addcmd("shutdown.stayoff"); upsh.instcmd = instcmd; @@ -604,6 +605,11 @@ void upsdrv_help(void) printf(HELP); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/phoenixcontact_modbus.c b/drivers/phoenixcontact_modbus.c index 455d098a49..20edb9569c 100644 --- a/drivers/phoenixcontact_modbus.c +++ b/drivers/phoenixcontact_modbus.c @@ -32,7 +32,7 @@ #endif #define DRIVER_NAME "NUT PhoenixContact Modbus driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.11" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 192 @@ -225,13 +225,15 @@ void upsdrv_initinfo(void) uint64_t PartNumber = 0; size_t i; + upsdebugx(2, "upsdrv_initinfo"); + + phoenixcontact_apply_advanced_config(modbus_ctx); + for (i = 0; i < (sizeof(tab_reg) / sizeof(tab_reg[0])); i++) { tab_reg[i] = 0; } - upsdebugx(2, "upsdrv_initinfo"); - dstate_setinfo("device.mfr", "Phoenix Contact"); /* upsh.instcmd = instcmd; */ @@ -804,6 +806,11 @@ void upsdrv_help(void) "\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { @@ -836,6 +843,7 @@ void upsdrv_initups(void) result = mrir(modbus_ctx, 0x0004, 1, &FWVersion); if (result == -1) { + /* Try to go slower... */ modbus_close(modbus_ctx); modbus_free(modbus_ctx); @@ -860,12 +868,10 @@ void upsdrv_initups(void) if (r < 0) { - fatalx(EXIT_FAILURE, "UPS does not repond to read requests."); + fatalx(EXIT_FAILURE, "UPS does not respond to read requests."); } - } - phoenixcontact_apply_advanced_config(modbus_ctx); dstate_setinfo("ups.firmware", "%" PRIu16, FWVersion); } diff --git a/drivers/pijuice.c b/drivers/pijuice.c index 88e9f68d85..bc815ec5d7 100644 --- a/drivers/pijuice.c +++ b/drivers/pijuice.c @@ -23,7 +23,7 @@ #include "nut_stdint.h" #define DRIVER_NAME "PiJuice UPS driver" -#define DRIVER_VERSION "0.15" +#define DRIVER_VERSION "0.17" /* * Linux I2C userland is a bit of a mess until distros refresh to @@ -775,6 +775,13 @@ static void get_i2c_address(void) void upsdrv_initinfo(void) { + /* probe ups type */ + get_firmware_version(); + + /* get variables and flags from the command line */ + + if (getval("i2c_address")) + i2c_address = atoi(getval("i2c_address")); dstate_setinfo( "ups.mfr", "%s", "PiJuice" ); dstate_setinfo( "ups.type", "%s", "HAT" ); @@ -864,6 +871,11 @@ void upsdrv_help(void) printf("\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "i2c_address", "Override i2c address setting"); @@ -872,14 +884,6 @@ void upsdrv_makevartable(void) void upsdrv_initups(void) { upsfd = open_i2c_bus( device_path, i2c_address ); - - /* probe ups type */ - get_firmware_version(); - - /* get variables and flags from the command line */ - - if (getval("i2c_address")) - i2c_address = atoi(getval("i2c_address")); } void upsdrv_cleanup(void) diff --git a/drivers/powercom-hid.c b/drivers/powercom-hid.c index ebadc68841..96912111ff 100644 --- a/drivers/powercom-hid.c +++ b/drivers/powercom-hid.c @@ -26,7 +26,7 @@ #include "powercom-hid.h" #include "usb-common.h" -#define POWERCOM_HID_VERSION "PowerCOM HID 0.71" +#define POWERCOM_HID_VERSION "PowerCOM HID 0.72" /* FIXME: experimental flag to be put in upsdrv_info */ /* PowerCOM */ @@ -62,10 +62,30 @@ static char powercom_scratch_buf[32]; */ static char powercom_sdcmd_byte_order_fallback = 0; +/* Some devices (Raptor and Smart KING Pro series) follow the protocol + * where we can not set arbitrary value of shutdown delay in seconds + * (like in other powercom UPSes), but the shutdown delay can be set + * only from table "Table of possible delays for Shutdown commands" + * specified at page 17 of the protocol document: + * https://networkupstools.org/protocols/powercom/Software_USB_communication_controller_SKP_series.doc + * + * "Table of possible delays for Shutdown commands" (mentioned for + * `DelayBeforeShutdown` and `DelayBeforeStartup` HID Usages): + * Index 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 + * Value 12s 18s 24s 30s 36s 42s 48s 54s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m + * Sent to UPS .2 .3 .4 .5 .6 .7 .8 .9 01 02 03 04 05 06 07 08 09 10 + * + */ +static char powercom_sdcmd_discrete_delay = 0; + static const char *powercom_startup_fun(double value) { uint16_t i = value; + /* For powercom_sdcmd_discrete_delay we also read minutes + * from DelayBeforeStartup (same as for older dialects). + * FIXME: ...but theoretically they can be 32-bit (1..99999) + */ snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (((i & 0x00FF) << 8) + (i >> 8))); upsdebugx(3, "%s: value = %.0f, buf = %s", __func__, value, powercom_scratch_buf); @@ -75,20 +95,40 @@ static const char *powercom_startup_fun(double value) static double powercom_startup_nuf(const char *value) { const char *s = dstate_getinfo("ups.delay.start"); - uint16_t val, command; + uint32_t val, command; int iv; - iv = atoi(value ? value : s) / 60; - if (iv < 0 || (intmax_t)iv > (intmax_t)UINT16_MAX) { - upsdebugx(0, "%s: value = %d is not in uint16_t range", __func__, iv); + /* Start with seconds "as is" - convert into whole minutes */ + iv = atoi(value ? value : s) / 60; /* minutes */ +#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 (iv < 0 || (intmax_t)iv > (intmax_t)UINT32_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) ) +# pragma GCC diagnostic pop +#endif + upsdebugx(0, "%s: value = %d is not in uint32_t range", __func__, iv); return 0; } - /* COMMENTME: What are we doing here, a byte-swap in the word? */ - val = (uint16_t)iv; - command = (uint16_t)(val << 8); - command += (uint16_t)(val >> 8); - upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command); + if (powercom_sdcmd_discrete_delay) { + /* Per spec, DelayBeforeStartup reads and writes + * 4-byte "mmmm" values in minutes (1..99999) */ + command = (uint32_t)iv; + } else { + /* COMMENTME: What are we doing here, a byte-swap in the word? */ + val = (uint16_t)iv; + command = (uint16_t)(val << 8); + command += (uint16_t)(val >> 8); + } + + upsdebugx(3, "%s: value = %s, command = 0x%08X", __func__, value, command); return command; } @@ -101,6 +141,12 @@ static const char *powercom_shutdown_fun(double value) { uint16_t i = value; + /* NOTE: for powercom_sdcmd_discrete_delay mode it seems we + * do not read DelayBeforeShutdown at all (not for time), + * the value is stored and retrieved as DelayBeforeStartup. + * FIXME: Should anything be changed here? + */ + if (powercom_sdcmd_byte_order_fallback) { /* Legacy behavior */ snprintf(powercom_scratch_buf, sizeof(powercom_scratch_buf), "%d", 60 * (i & 0x00FF) + (i >> 8)); @@ -120,25 +166,46 @@ static double powercom_shutdown_nuf(const char *value) uint16_t val, command; int iv; - iv = atoi(value ? value : s); + iv = atoi(value ? value : s); /* seconds */ if (iv < 0 || (intmax_t)iv > (intmax_t)UINT16_MAX) { upsdebugx(0, "%s: value = %d is not in uint16_t range", __func__, iv); return 0; } - val = (uint16_t)iv; - val = val ? val : 1; /* 0 sets the maximum delay */ - if (powercom_sdcmd_byte_order_fallback) { - /* Legacy behavior */ - command = ((uint16_t)((val % 60) << 8)) + (uint16_t)(val / 60); - command |= 0x4000; /* AC RESTART NORMAL ENABLE */ + if (powercom_sdcmd_discrete_delay) { + if (iv <= 12) command = 1; + else if (iv <= 18) command = 2; + else if (iv <= 24) command = 3; + else if (iv <= 30) command = 4; + else if (iv <= 36) command = 5; + else if (iv <= 42) command = 6; + else if (iv <= 48) command = 7; + else if (iv <= 54) command = 8; + else if (iv <= 60) command = 9; + else if (iv <= 120) command = 10; + else if (iv <= 180) command = 11; + else if (iv <= 240) command = 12; + else if (iv <= 300) command = 13; + else if (iv <= 360) command = 14; + else if (iv <= 420) command = 15; + else if (iv <= 480) command = 16; + else if (iv <= 540) command = 17; + else command = 18; } else { - /* New default */ - command = ((uint16_t)((val / 60) << 8)) + (uint16_t)(val % 60); - command |= 0x0040; /* AC RESTART NORMAL ENABLE */ + val = (uint16_t)iv; + val = val ? val : 1; /* 0 sets the maximum delay */ + 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); + upsdebugx(3, "%s: value = %s, command = 0x%04X", __func__, value, command); return command; } @@ -153,6 +220,7 @@ static double powercom_stayoff_nuf(const char *value) uint16_t val, command; int iv; + /* FIXME: Anything for powercom_sdcmd_discrete_delay? */ iv = atoi(value ? value : s); if (iv < 0 || (intmax_t)iv > (intmax_t)UINT16_MAX) { upsdebugx(0, "%s: value = %d is not in uint16_t range", __func__, iv); @@ -171,7 +239,7 @@ static double powercom_stayoff_nuf(const char *value) command |= 0x0080; /* AC RESTART NORMAL DISABLE */ } - upsdebugx(3, "%s: value = %s, command = %04X", __func__, value, command); + upsdebugx(3, "%s: value = %s, command = 0x%04X", __func__, value, command); return command; } @@ -592,6 +660,7 @@ static int powercom_claim(HIDDevice_t *hd) accept: powercom_sdcmd_byte_order_fallback = testvar("powercom_sdcmd_byte_order_fallback"); + powercom_sdcmd_discrete_delay = testvar("powercom_sdcmd_discrete_delay"); return 1; } diff --git a/drivers/powercom.c b/drivers/powercom.c index df6920eed3..9426f2c788 100644 --- a/drivers/powercom.c +++ b/drivers/powercom.c @@ -86,7 +86,7 @@ #include "nut_float.h" #define DRIVER_NAME "PowerCom protocol UPS driver" -#define DRIVER_VERSION "0.25" +#define DRIVER_VERSION "0.27" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -866,9 +866,7 @@ void upsdrv_shutdown(void) void upsdrv_initups(void) { int tmp; - unsigned int model = 0; unsigned int i; - static char buf[20]; /* check manufacturer name from arguments */ if (testvar("manufacturer")) @@ -1007,6 +1005,85 @@ void upsdrv_initups(void) /* setup flow control */ types[type].flowControl.setup_flow_control(); +} + +/* display help */ +void upsdrv_help(void) +{ + /* 1 2 3 4 5 6 7 8 */ + /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 MAX */ + printf("\n"); + printf("Specify UPS information in the ups.conf file.\n"); + printf(" type: Type of UPS: 'Trust','Egys','KP625AP','IMP','KIN','BNT',\n"); + printf(" 'BNT-other', 'OPTI' (default: 'Trust')\n"); + printf(" 'BNT-other' is a special type intended for BNT 100-120V models,\n"); + printf(" but can be used to override ALL models.\n"); + printf("You can additional specify these variables:\n"); + printf(" manufacturer: Manufacturer name (default: 'PowerCom')\n"); + printf(" modelname: Model name (default: 'Unknown' or autodetected)\n"); + printf(" serialnumber: Serial number (default: Unknown)\n"); + printf(" shutdownArguments: 3 delay arguments for the shutdown operation:\n"); + printf(" {{Minutes,Seconds},UseMinutes?}\n"); + printf(" where Minutes and Seconds are integer, UseMinutes? is either\n"); + printf(" 'y' or 'n'.\n"); + printf("You can specify these variables if not automagically detected for types\n"); + printf(" 'IMP','KIN','BNT'\n"); + printf(" linevoltage: Line voltage: 110-120 or 220-240 (default: 230)\n"); + printf(" numOfBytesFromUPS: Number of bytes in a UPS frame: 16 is common, 11 for 'Trust'\n"); + printf(" methodOfFlowControl: Flow control method for UPS:\n"); + printf(" 'dtr0rts1', 'dtr1' or 'no_flow_control'\n"); + printf(" validationSequence: 3 pairs of validation values: {{I,V},{I,V},{I,V}}\n"); + printf(" where I is the index into BytesFromUPS (see numOfBytesFromUPS)\n"); + printf(" and V is the value for the ByteIndex to match.\n"); + printf(" frequency: Input & Output Frequency conversion values: {A, B}\n"); + printf(" used in function: 1/(A*x+B)\n"); + printf(" If the raw value x IS the frequency, then A=1/(x^2), B=0\n"); + printf(" loadPercentage: Load conversion values for Battery and Line load: {BA,BB,LA,LB}\n"); + printf(" used in function: A*x+B\n"); + printf(" If the raw value x IS the Load Percent, then A=1, B=0\n"); + printf(" batteryPercentage: Battery conversion values for Battery and Line power:\n"); + printf(" {A,B,C,D,E}\n"); + printf(" used in functions: (Battery) A*x+B*y+C, (Line) D*x+E\n"); + printf(" If the raw value x IS the Battery Percent, then\n"); + printf(" A=1, B=0, C=0, D=1, E=0\n"); + printf(" voltage: Voltage conversion values for 240 and 120 voltage:\n"); + printf(" {240A,240B,120A,120B}\n"); + printf(" used in function: A*x+B\n"); + printf(" If the raw value x IS HALF the Voltage, then A=2, B=0\n"); + printf(" nobt: Flag to skip battery check on init/startup.\n\n"); + + printf("Example for BNT1500AP in ups.conf:\n"); + printf("[BNT1500AP]\n"); + printf(" driver = powercom\n"); + printf(" port = /dev/ttyS0\n"); + printf(" desc = \"PowerCom BNT 1500 AP\"\n"); + printf(" manufacturer = PowerCom\n"); + printf(" modelname = BNT1500AP\n"); + printf(" serialnumber = 13245678900\n"); + printf(" type = BNT-other\n"); + printf("# linevoltage = 120\n"); + printf("# numOfBytesFromUPS = 16\n"); + printf("# methodOfFlowControl = no_flow_control\n"); + printf("# validationSequence = {{8,0},{8,0},{8,0}}\n"); + printf("# shutdownArguments = {{1,30},y}\n"); + printf("# frequency = {0.00027778,0.0000}\n"); + printf("# loadPercentage = {1.0000,0.0,1.0000,0.0}\n"); + printf("# batteryPercentage = {1.0000,0.0000,0.0000,1.0000,0.0000}\n"); + printf("# voltage = {2.0000,0.0000,2.0000,0.0000}\n"); + printf(" nobt\n"); + return; +} + +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + +/* initialize information */ +void upsdrv_initinfo(void) +{ + unsigned int model = 0; + static char buf[20]; /* Setup Model and LineVoltage */ if (!strncmp(types[type].name, "BNT",3) || !strcmp(types[type].name, "KIN") || !strcmp(types[type].name, "IMP") || !strcmp(types[type].name, "OPTI")) { @@ -1096,78 +1173,6 @@ void upsdrv_initups(void) types[type].voltage[2], types[type].voltage[3]); } -} - -/* display help */ -void upsdrv_help(void) -{ - /* 1 2 3 4 5 6 7 8 */ - /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 MAX */ - printf("\n"); - printf("Specify UPS information in the ups.conf file.\n"); - printf(" type: Type of UPS: 'Trust','Egys','KP625AP','IMP','KIN','BNT',\n"); - printf(" 'BNT-other', 'OPTI' (default: 'Trust')\n"); - printf(" 'BNT-other' is a special type intended for BNT 100-120V models,\n"); - printf(" but can be used to override ALL models.\n"); - printf("You can additional specify these variables:\n"); - printf(" manufacturer: Manufacturer name (default: 'PowerCom')\n"); - printf(" modelname: Model name (default: 'Unknown' or autodetected)\n"); - printf(" serialnumber: Serial number (default: Unknown)\n"); - printf(" shutdownArguments: 3 delay arguments for the shutdown operation:\n"); - printf(" {{Minutes,Seconds},UseMinutes?}\n"); - printf(" where Minutes and Seconds are integer, UseMinutes? is either\n"); - printf(" 'y' or 'n'.\n"); - printf("You can specify these variables if not automagically detected for types\n"); - printf(" 'IMP','KIN','BNT'\n"); - printf(" linevoltage: Line voltage: 110-120 or 220-240 (default: 230)\n"); - printf(" numOfBytesFromUPS: Number of bytes in a UPS frame: 16 is common, 11 for 'Trust'\n"); - printf(" methodOfFlowControl: Flow control method for UPS:\n"); - printf(" 'dtr0rts1', 'dtr1' or 'no_flow_control'\n"); - printf(" validationSequence: 3 pairs of validation values: {{I,V},{I,V},{I,V}}\n"); - printf(" where I is the index into BytesFromUPS (see numOfBytesFromUPS)\n"); - printf(" and V is the value for the ByteIndex to match.\n"); - printf(" frequency: Input & Output Frequency conversion values: {A, B}\n"); - printf(" used in function: 1/(A*x+B)\n"); - printf(" If the raw value x IS the frequency, then A=1/(x^2), B=0\n"); - printf(" loadPercentage: Load conversion values for Battery and Line load: {BA,BB,LA,LB}\n"); - printf(" used in function: A*x+B\n"); - printf(" If the raw value x IS the Load Percent, then A=1, B=0\n"); - printf(" batteryPercentage: Battery conversion values for Battery and Line power:\n"); - printf(" {A,B,C,D,E}\n"); - printf(" used in functions: (Battery) A*x+B*y+C, (Line) D*x+E\n"); - printf(" If the raw value x IS the Battery Percent, then\n"); - printf(" A=1, B=0, C=0, D=1, E=0\n"); - printf(" voltage: Voltage conversion values for 240 and 120 voltage:\n"); - printf(" {240A,240B,120A,120B}\n"); - printf(" used in function: A*x+B\n"); - printf(" If the raw value x IS HALF the Voltage, then A=2, B=0\n"); - printf(" nobt: Flag to skip battery check on init/startup.\n\n"); - - printf("Example for BNT1500AP in ups.conf:\n"); - printf("[BNT1500AP]\n"); - printf(" driver = powercom\n"); - printf(" port = /dev/ttyS0\n"); - printf(" desc = \"PowerCom BNT 1500 AP\"\n"); - printf(" manufacturer = PowerCom\n"); - printf(" modelname = BNT1500AP\n"); - printf(" serialnumber = 13245678900\n"); - printf(" type = BNT-other\n"); - printf("# linevoltage = 120\n"); - printf("# numOfBytesFromUPS = 16\n"); - printf("# methodOfFlowControl = no_flow_control\n"); - printf("# validationSequence = {{8,0},{8,0},{8,0}}\n"); - printf("# shutdownArguments = {{1,30},y}\n"); - printf("# frequency = {0.00027778,0.0000}\n"); - printf("# loadPercentage = {1.0000,0.0,1.0000,0.0}\n"); - printf("# batteryPercentage = {1.0000,0.0000,0.0000,1.0000,0.0000}\n"); - printf("# voltage = {2.0000,0.0000,2.0000,0.0000}\n"); - printf(" nobt\n"); - return; -} - -/* initialize information */ -void upsdrv_initinfo(void) -{ /* write constant data for this model */ dstate_setinfo ("ups.mfr", "%s", manufacturer); dstate_setinfo ("ups.model", "%s", modelname); diff --git a/drivers/powerman-pdu.c b/drivers/powerman-pdu.c index b760a6369e..8f257fbd6b 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.16" +#define DRIVER_VERSION "0.17" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -184,6 +184,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/powerp-bin.c b/drivers/powerp-bin.c index f7e01238da..67d2d94be9 100644 --- a/drivers/powerp-bin.c +++ b/drivers/powerp-bin.c @@ -36,7 +36,7 @@ #include -#define POWERPANEL_BIN_VERSION "Powerpanel-Binary 0.64" +#define POWERPANEL_BIN_VERSION "Powerpanel-Binary 0.65" typedef struct { unsigned char start; @@ -415,6 +415,10 @@ static void powpan_initinfo(void) dstate_addcmd("shutdown.stayoff"); dstate_addcmd("shutdown.reboot"); + + /* install handlers */ + upsh.instcmd = powpan_instcmd; + upsh.setvar = powpan_setvar; } static ssize_t powpan_status(status_t *status) diff --git a/drivers/powerp-txt.c b/drivers/powerp-txt.c index 7f32d89cf7..9721622932 100644 --- a/drivers/powerp-txt.c +++ b/drivers/powerp-txt.c @@ -36,7 +36,7 @@ #include -#define POWERPANEL_TEXT_VERSION "Powerpanel-Text 0.64" +#define POWERPANEL_TEXT_VERSION "Powerpanel-Text 0.65" typedef struct { float i_volt; @@ -378,6 +378,10 @@ static void powpan_initinfo(void) dstate_addcmd("shutdown.return"); dstate_addcmd("shutdown.stayoff"); dstate_addcmd("shutdown.reboot"); + + /* install handlers */ + upsh.instcmd = powpan_instcmd; + upsh.setvar = powpan_setvar; } static ssize_t powpan_status(status_t *status) diff --git a/drivers/powerpanel.c b/drivers/powerpanel.c index 2d8c212781..f90cb6f9b5 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.30" +#define DRIVER_VERSION "0.31" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -186,6 +186,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "ondelay", "Delay before UPS startup"); diff --git a/drivers/powervar_cx.c b/drivers/powervar_cx.c index 529550b6ce..53e2082065 100644 --- a/drivers/powervar_cx.c +++ b/drivers/powervar_cx.c @@ -853,7 +853,7 @@ void PvarCommon_Initinfo (void) dstate_setflags("ups.delay.start", ST_FLAG_STRING | ST_FLAG_RW); dstate_setaux("ups.delay.start", GET_STARTDELAY_RESP_SIZE); - upsh.setvar = setcmd; + upsh.setvar = setvar; upsh.instcmd = instcmd; } @@ -1315,9 +1315,9 @@ int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_UNKNOWN; } -int setcmd(const char* varname, const char* setvalue) +int setvar(const char* varname, const char* setvalue) { - upsdebugx(2, "In setcmd for %s with %s...", varname, setvalue); + upsdebugx(2, "In setvar for %s with %s...", varname, setvalue); if (!strcasecmp(varname, "ups.delay.shutdown")) { @@ -1380,7 +1380,7 @@ int setcmd(const char* varname, const char* setvalue) return STAT_SET_UNKNOWN; } - upslogx(LOG_NOTICE, "setcmd: unknown command [%s]", varname); + upslogx(LOG_NOTICE, "setvar: unknown command [%s]", varname); return STAT_SET_UNKNOWN; } diff --git a/drivers/powervar_cx.h b/drivers/powervar_cx.h index e332f38e06..aabd682700 100644 --- a/drivers/powervar_cx.h +++ b/drivers/powervar_cx.h @@ -40,7 +40,7 @@ void GetInitFormatAndOrData (const char* sReq, char* sF, const size_t sFSize, char* sD, const size_t sDSize); uint8_t GetUPSData (char* sReq, char* sD, const size_t sDSize); int instcmd(const char *cmdname, const char *extra); -int setcmd(const char* varname, const char* setvalue); +int setvar(const char* varname, const char* setvalue); size_t GetSubstringPosition (const char* chResponse, const char* chSub); diff --git a/drivers/powervar_cx_ser.c b/drivers/powervar_cx_ser.c index d9cc86ddf8..d2be363c21 100644 --- a/drivers/powervar_cx_ser.c +++ b/drivers/powervar_cx_ser.c @@ -39,7 +39,7 @@ #include "powervar_cx.h" /* Common driver defines, variables, and functions */ #define DRIVER_NAME "Powervar-CUSSP UPS driver (Serial)" -#define DRIVER_VERSION "1.00" +#define DRIVER_VERSION "1.02" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -157,16 +157,16 @@ void upsdrv_initups(void) upsdebugx (4, "Serial baud not set!! (%" PRIu32 ").", ulBaud); } } - - /*get the UPS in the right frame of mind */ - ser_send_pace(upsfd, 100, "%s", COMMAND_END); - ser_send_pace(upsfd, 100, "%s", COMMAND_END); - sleep (1); } /* This function is called on driver startup to initialize variables/commands */ void upsdrv_initinfo(void) { + /* Get the UPS in the right frame of mind */ + ser_send_pace(upsfd, 100, "%s", COMMAND_END); + ser_send_pace(upsfd, 100, "%s", COMMAND_END); + sleep (1); + /* Get serial port ready */ ser_flush_in(upsfd, "", 0); @@ -197,6 +197,12 @@ void upsdrv_help(void) } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + + void upsdrv_cleanup(void) { ser_close(upsfd, device_path); diff --git a/drivers/powervar_cx_usb.c b/drivers/powervar_cx_usb.c index bcce84d9c0..982fc9566b 100644 --- a/drivers/powervar_cx_usb.c +++ b/drivers/powervar_cx_usb.c @@ -45,7 +45,7 @@ #include "powervar_cx.h" /* Common driver variables and functions */ #define DRIVER_NAME "Powervar-CUSSP UPS driver (USB)" -#define DRIVER_VERSION "1.00" +#define DRIVER_VERSION "1.01" /* USB comm stuff here */ #define USB_RESPONSE_SIZE 8 @@ -453,6 +453,11 @@ void upsdrv_help(void) printf("This driver is for connecting to a Powervar UPS USB port.\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_cleanup(void) { upsdebugx(2, "In upsdrv_cleanup"); diff --git a/drivers/rhino.c b/drivers/rhino.c index b7af3f74f5..1a8bc55360 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.56" +#define DRIVER_VERSION "0.57" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -785,6 +785,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "battext", "Battery Extension (0-80)min"); diff --git a/drivers/richcomm_usb.c b/drivers/richcomm_usb.c index 3b97e06f11..fbdcaf5cfc 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.15" +#define DRIVER_VERSION "0.16" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -54,9 +54,12 @@ upsdrv_info_t upsdrv_info = { #define USB_ERR_LIMIT 10 /* start limiting after 10 in a row */ #define USB_ERR_RATE 10 /* then only print every 10th error */ +/* Lakeview Research */ +#define LAKEVIEW_VENDORID 0x0925 + static usb_device_id_t richcomm_usb_id[] = { /* Sweex 1000VA */ - { USB_DEVICE(0x0925, 0x1234), NULL }, + { USB_DEVICE(LAKEVIEW_VENDORID, 0x1234), NULL }, /* Terminating entry */ { 0, 0, NULL } @@ -797,6 +800,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { /* allow -x vendor=X, vendorid=X, product=X, productid=X, serial=X */ diff --git a/drivers/riello_ser.c b/drivers/riello_ser.c index a5fa3028d4..f553362071 100644 --- a/drivers/riello_ser.c +++ b/drivers/riello_ser.c @@ -6,6 +6,7 @@ * and "https://www.networkupstools.org/protocols/riello/PSSENTR-0100.pdf". * * Copyright (C) 2012 - Elio Parisi + * Copyright (C) 2022-2025 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 @@ -48,7 +49,7 @@ #include "riello.h" #define DRIVER_NAME "Riello serial driver" -#define DRIVER_VERSION "0.15" +#define DRIVER_VERSION "0.17" #define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */ #define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */ @@ -291,6 +292,63 @@ static int get_ups_status(void) return 0; } +static int parse_ups_status(int doQuery) { + if (doQuery) { + int stat = get_ups_status(); + + upsdebugx(1, "get_ups_status() %d",stat ); + + if (stat < 0) { + return -1; + } + } + + status_init(); + + /* AC Fail */ + if (riello_test_bit(&DevData.StatusCode[0], 1)) + status_set("OB"); + else + status_set("OL"); + + /* LowBatt */ + if ((riello_test_bit(&DevData.StatusCode[0], 1)) && + (riello_test_bit(&DevData.StatusCode[0], 0))) + status_set("LB"); + + /* Standby */ + if (!riello_test_bit(&DevData.StatusCode[0], 3)) + status_set("OFF"); + + /* On Bypass */ + if (riello_test_bit(&DevData.StatusCode[1], 3)) + status_set("BYPASS"); + + /* Overload */ + if (riello_test_bit(&DevData.StatusCode[4], 2)) + status_set("OVER"); + + /* Buck */ + if (riello_test_bit(&DevData.StatusCode[1], 0)) + status_set("TRIM"); + + /* Boost */ + if (riello_test_bit(&DevData.StatusCode[1], 1)) + status_set("BOOST"); + + /* Replace battery */ + if (riello_test_bit(&DevData.StatusCode[2], 0)) + status_set("RB"); + + /* Charging battery */ + if (riello_test_bit(&DevData.StatusCode[2], 2)) + status_set("CHRG"); + + status_commit(); + + return 0; +} + static int get_ups_extended(void) { uint8_t length; @@ -411,7 +469,25 @@ static int riello_instcmd(const char *cmdname, const char *extra) NUT_UNUSED_VARIABLE(extra); upsdebug_INSTCMD_STARTING(cmdname, extra); + if (!strncasecmp(cmdname, "load.", 5) || !strncasecmp(cmdname, "shutdown.", 9)) { + if (DevData.StatusCode[0] == 0 && handling_upsdrv_shutdown > 0) { + /* With a quick init, we may have skipped the + * long device data walk. At least query current + * state to check that we can contact it. */ + if (parse_ups_status(1) < 0) + upsdebugx(1, "%s: failed to query and parse ups.status", __func__); + } + + upslogx(LOG_INFO, "Processing command '%s' while ups.status is '%s'", + cmdname, NUT_STRARG(dstate_getinfo("ups.status"))); + } + +#ifdef RIELLO_SHUTDOWN_DEPENDS_ON_POWERSTATE + /* NOTE: Historically, this code allowed either "load.*" commands + * when we are "OL" or "shutdown.return" when we are "OB", but + * we found no requirement for that in the protocol docs. */ if (!riello_test_bit(&DevData.StatusCode[0], 1)) { +#endif if (!strcasecmp(cmdname, "load.off")) { upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra); @@ -584,8 +660,10 @@ static int riello_instcmd(const char *cmdname, const char *extra) upsdebugx (3, "Command load.on delay Ok"); return STAT_INSTCMD_HANDLED; } +#ifdef RIELLO_SHUTDOWN_DEPENDS_ON_POWERSTATE } else { +#endif if (!strcasecmp(cmdname, "shutdown.return")) { int ipv; @@ -622,7 +700,9 @@ static int riello_instcmd(const char *cmdname, const char *extra) upsdebugx (3, "Command shutdown.return Ok"); return STAT_INSTCMD_HANDLED; } +#ifdef RIELLO_SHUTDOWN_DEPENDS_ON_POWERSTATE } +#endif if (!strcasecmp(cmdname, "shutdown.stop")) { upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); @@ -752,8 +832,70 @@ static int start_ups_comm(void) } upsdebugx (3, "Get identif Ok: received byte %u", buf_ptr_length); + return 0; +} + +void upsdrv_help(void) +{ +} + +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + +/* list flags and values that you want to receive via -x */ +void upsdrv_makevartable(void) +{ + /* allow '-x xyzzy' */ + /* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */ + + /* allow '-x foo=' */ + /* addvar(VAR_VALUE, "foo", "Override foo setting"); */ + addvar(VAR_FLAG, "localcalculation", "Calculate battery charge and runtime locally"); +} + +void upsdrv_initups(void) +{ + upsdebugx(2, "entering upsdrv_initups()"); + + upsfd = ser_open(device_path); + + riello_comm_setup(device_path); + + /* probe ups type */ + + /* to get variables and flags from the command line, use this: + * + * first populate with upsdrv_buildvartable above, then... + * + * set flag foo : /bin/driver -x foo + * set variable 'cable' to '1234' : /bin/driver -x cable=1234 + * + * to test flag foo in your code: + * + * if (testvar("foo")) + * do_something(); + * + * to show the value of cable: + * + * if ((cable = getval("cable"))) + * printf("cable is set to %s\n", cable); + * else + * printf("cable is not set!\n"); + * + * don't use NULL pointers - test the return result first! + */ + + /* the upsh handlers can't be done here, as they get initialized + * shortly after upsdrv_initups returns to main. + */ + + /* don't try to detect the UPS here */ + + /* initialise communication */ } void upsdrv_initinfo(void) @@ -960,6 +1102,50 @@ void upsdrv_initinfo(void) upsh.instcmd = riello_instcmd; } +void upsdrv_shutdown(void) +{ + /* Only implement "shutdown.default"; do not invoke + * general handling of other `sdcommands` here */ + + /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ + int retry; + + /* maybe try to detect the UPS here, but try a shutdown even if + it doesn't respond at first if possible */ + + /* replace with a proper shutdown function */ + + + /* you may have to check the line status since the commands + for toggling power are frequently different for OL vs. OB */ + + /* OL: this must power cycle the load if possible */ + + /* OB: the load must remain off until the power returns */ + upsdebugx(2, "upsdrv Shutdown execute"); + + for (retry = 1; retry <= MAXTRIES; retry++) { + /* By default, abort a previously requested shutdown + * (if any) and schedule a new one from this moment. */ + if (riello_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) { + continue; + } + + if (riello_instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) { + continue; + } + + upslogx(LOG_ERR, "Shutting down"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_SUCCESS); + return; + } + + upslogx(LOG_ERR, "Shutdown failed!"); + if (handling_upsdrv_shutdown > 0) + set_exit_flag(EF_EXIT_FAILURE); +} + void upsdrv_updateinfo(void) { uint8_t getextendedOK; @@ -1112,48 +1298,7 @@ void upsdrv_updateinfo(void) dstate_setinfo("ups.load", "%u", (unsigned int)(DevData.Pout1+DevData.Pout2+DevData.Pout3)/3); } - status_init(); - - /* AC Fail */ - if (riello_test_bit(&DevData.StatusCode[0], 1)) - status_set("OB"); - else - status_set("OL"); - - /* LowBatt */ - if ((riello_test_bit(&DevData.StatusCode[0], 1)) && - (riello_test_bit(&DevData.StatusCode[0], 0))) - status_set("LB"); - - /* Standby */ - if (!riello_test_bit(&DevData.StatusCode[0], 3)) - status_set("OFF"); - - /* On Bypass */ - if (riello_test_bit(&DevData.StatusCode[1], 3)) - status_set("BYPASS"); - - /* Overload */ - if (riello_test_bit(&DevData.StatusCode[4], 2)) - status_set("OVER"); - - /* Buck */ - if (riello_test_bit(&DevData.StatusCode[1], 0)) - status_set("TRIM"); - - /* Boost */ - if (riello_test_bit(&DevData.StatusCode[1], 1)) - status_set("BOOST"); - - /* Replace battery */ - if (riello_test_bit(&DevData.StatusCode[2], 0)) - status_set("RB"); - - /* Charging battery */ - if (riello_test_bit(&DevData.StatusCode[2], 2)) - status_set("CHRG"); - - status_commit(); + parse_ups_status(0); dstate_dataok(); @@ -1172,6 +1317,7 @@ void upsdrv_updateinfo(void) poll_interval = 2; countlost = 0; + /* if (get_ups_statuscode() != 0) upsdebugx(2, "Communication is lost"); else { @@ -1182,51 +1328,6 @@ void upsdrv_updateinfo(void) */ } -void upsdrv_shutdown(void) -{ - /* Only implement "shutdown.default"; do not invoke - * general handling of other `sdcommands` here */ - - /* tell the UPS to shut down, then return - DO NOT SLEEP HERE */ - int retry; - - /* maybe try to detect the UPS here, but try a shutdown even if - it doesn't respond at first if possible */ - - /* replace with a proper shutdown function */ - - - /* you may have to check the line status since the commands - for toggling power are frequently different for OL vs. OB */ - - /* OL: this must power cycle the load if possible */ - - /* OB: the load must remain off until the power returns */ - upsdebugx(2, "upsdrv Shutdown execute"); - - for (retry = 1; retry <= MAXTRIES; retry++) { - /* By default, abort a previously requested shutdown - * (if any) and schedule a new one from this moment. */ - if (riello_instcmd("shutdown.stop", NULL) != STAT_INSTCMD_HANDLED) { - continue; - } - - if (riello_instcmd("shutdown.return", NULL) != STAT_INSTCMD_HANDLED) { - continue; - } - - upslogx(LOG_ERR, "Shutting down"); - if (handling_upsdrv_shutdown > 0) - set_exit_flag(EF_EXIT_SUCCESS); - return; - } - - upslogx(LOG_ERR, "Shutdown failed!"); - if (handling_upsdrv_shutdown > 0) - set_exit_flag(EF_EXIT_FAILURE); -} - - /* static int setvar(const char *varname, const char *val) { @@ -1242,63 +1343,6 @@ static int setvar(const char *varname, const char *val) } */ -void upsdrv_help(void) -{ -} - -/* list flags and values that you want to receive via -x */ -void upsdrv_makevartable(void) -{ - /* allow '-x xyzzy' */ - /* addvar(VAR_FLAG, "xyzzy", "Enable xyzzy mode"); */ - - /* allow '-x foo=' */ - /* addvar(VAR_VALUE, "foo", "Override foo setting"); */ - - addvar(VAR_FLAG, "localcalculation", "Calculate battery charge and runtime locally"); -} - -void upsdrv_initups(void) -{ - upsdebugx(2, "entering upsdrv_initups()"); - - upsfd = ser_open(device_path); - - riello_comm_setup(device_path); - - /* probe ups type */ - - /* to get variables and flags from the command line, use this: - * - * first populate with upsdrv_buildvartable above, then... - * - * set flag foo : /bin/driver -x foo - * set variable 'cable' to '1234' : /bin/driver -x cable=1234 - * - * to test flag foo in your code: - * - * if (testvar("foo")) - * do_something(); - * - * to show the value of cable: - * - * if ((cable = getval("cable"))) - * printf("cable is set to %s\n", cable); - * else - * printf("cable is not set!\n"); - * - * don't use NULL pointers - test the return result first! - */ - - /* the upsh handlers can't be done here, as they get initialized - * shortly after upsdrv_initups returns to main. - */ - - /* don't try to detect the UPS here */ - - /* initialise communication */ -} - void upsdrv_cleanup(void) { /* free(dynamic_mem); */ diff --git a/drivers/riello_usb.c b/drivers/riello_usb.c index c7bfc9a5c2..4e47b6ae4d 100644 --- a/drivers/riello_usb.c +++ b/drivers/riello_usb.c @@ -9,7 +9,7 @@ * Copyright (C) 2012 - Elio Parisi * Copyright (C) 2016 Eaton * Copyright (C) 2022-2024 "amikot" - * Copyright (C) 2022-2024 Jim Klimov + * Copyright (C) 2022-2025 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 +36,7 @@ #include "riello.h" #define DRIVER_NAME "Riello USB driver" -#define DRIVER_VERSION "0.15" +#define DRIVER_VERSION "0.17" #define DEFAULT_OFFDELAY 5 /*!< seconds (max 0xFF) */ #define DEFAULT_BOOTDELAY 5 /*!< seconds (max 0xFF) */ @@ -517,6 +517,63 @@ static int get_ups_status(void) return 0; } +static int parse_ups_status(int doQuery) { + if (doQuery) { + int stat = get_ups_status(); + + upsdebugx(1, "get_ups_status() %d",stat ); + + if (stat < 0) { + return -1; + } + } + + status_init(); + + /* AC Fail */ + if (riello_test_bit(&DevData.StatusCode[0], 1)) + status_set("OB"); + else + status_set("OL"); + + /* LowBatt */ + if ((riello_test_bit(&DevData.StatusCode[0], 1)) && + (riello_test_bit(&DevData.StatusCode[0], 0))) + status_set("LB"); + + /* Standby */ + if (!riello_test_bit(&DevData.StatusCode[0], 3)) + status_set("OFF"); + + /* On Bypass */ + if (riello_test_bit(&DevData.StatusCode[1], 3)) + status_set("BYPASS"); + + /* Overload */ + if (riello_test_bit(&DevData.StatusCode[4], 2)) + status_set("OVER"); + + /* Buck */ + if (riello_test_bit(&DevData.StatusCode[1], 0)) + status_set("TRIM"); + + /* Boost */ + if (riello_test_bit(&DevData.StatusCode[1], 1)) + status_set("BOOST"); + + /* Replace battery */ + if (riello_test_bit(&DevData.StatusCode[2], 0)) + status_set("RB"); + + /* Charging battery */ + if (riello_test_bit(&DevData.StatusCode[2], 2)) + status_set("CHRG"); + + status_commit(); + + return 0; +} + static int get_ups_extended(void) { uint8_t length; @@ -593,8 +650,25 @@ static int riello_instcmd(const char *cmdname, const char *extra) NUT_UNUSED_VARIABLE(extra); upsdebug_INSTCMD_STARTING(cmdname, extra); - if (!riello_test_bit(&DevData.StatusCode[0], 1)) { + if (!strncasecmp(cmdname, "load.", 5) || !strncasecmp(cmdname, "shutdown.", 9)) { + if (DevData.StatusCode[0] == 0 && handling_upsdrv_shutdown > 0) { + /* With a quick init, we may have skipped the + * long device data walk. At least query current + * state to check that we can contact it. */ + if (parse_ups_status(1) < 0) + upsdebugx(1, "%s: failed to query and parse ups.status", __func__); + } + + upslogx(LOG_INFO, "Processing command '%s' while ups.status is '%s'", + cmdname, NUT_STRARG(dstate_getinfo("ups.status"))); + } +#ifdef RIELLO_SHUTDOWN_DEPENDS_ON_POWERSTATE + /* NOTE: Historically, this code allowed either "load.*" commands + * when we are "OL" or "shutdown.return" when we are "OB", but + * we found no requirement for that in the protocol docs. */ + if (!riello_test_bit(&DevData.StatusCode[0], 1)) { +#endif if (!strcasecmp(cmdname, "load.off")) { upslog_INSTCMD_POWERSTATE_CHANGE(cmdname, extra); @@ -714,8 +788,10 @@ static int riello_instcmd(const char *cmdname, const char *extra) upsdebugx (3, "Command load.on.delay Ok: read byte: %d", recv); return STAT_INSTCMD_HANDLED; } +#ifdef RIELLO_SHUTDOWN_DEPENDS_ON_POWERSTATE } else { +#endif if (!strcasecmp(cmdname, "shutdown.return")) { int ipv; @@ -748,9 +824,12 @@ static int riello_instcmd(const char *cmdname, const char *extra) upsdebugx (3, "Command shutdown.return Ok: read byte: %d", recv); return STAT_INSTCMD_HANDLED; } +#ifdef RIELLO_SHUTDOWN_DEPENDS_ON_POWERSTATE } +#endif if (!strcasecmp(cmdname, "shutdown.stop")) { + /* Abort a pending shutdown request */ upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); length = riello_prepare_cd(bufOut, gpser_error_control); @@ -862,9 +941,12 @@ static int start_ups_comm(void) void upsdrv_help(void) { - } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} void upsdrv_makevartable(void) { @@ -1340,48 +1422,7 @@ void upsdrv_updateinfo(void) dstate_setinfo("ups.load", "%u", (unsigned int)(DevData.Pout1+DevData.Pout2+DevData.Pout3)/3); } - status_init(); - - /* AC Fail */ - if (riello_test_bit(&DevData.StatusCode[0], 1)) - status_set("OB"); - else - status_set("OL"); - - /* LowBatt */ - if ((riello_test_bit(&DevData.StatusCode[0], 1)) && - (riello_test_bit(&DevData.StatusCode[0], 0))) - status_set("LB"); - - /* Standby */ - if (!riello_test_bit(&DevData.StatusCode[0], 3)) - status_set("OFF"); - - /* On Bypass */ - if (riello_test_bit(&DevData.StatusCode[1], 3)) - status_set("BYPASS"); - - /* Overload */ - if (riello_test_bit(&DevData.StatusCode[4], 2)) - status_set("OVER"); - - /* Buck */ - if (riello_test_bit(&DevData.StatusCode[1], 0)) - status_set("TRIM"); - - /* Boost */ - if (riello_test_bit(&DevData.StatusCode[1], 1)) - status_set("BOOST"); - - /* Replace battery */ - if (riello_test_bit(&DevData.StatusCode[2], 0)) - status_set("RB"); - - /* Charging battery */ - if (riello_test_bit(&DevData.StatusCode[2], 2)) - status_set("CHRG"); - - status_commit(); + parse_ups_status(0); dstate_dataok(); diff --git a/drivers/safenet.c b/drivers/safenet.c index 59e68317e5..2eee58b533 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.83" +#define DRIVER_VERSION "1.84" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -473,6 +473,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "manufacturer", "manufacturer [unknown]"); diff --git a/drivers/skel.c b/drivers/skel.c index f33d703667..4a5cbc6104 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.07" +#define DRIVER_VERSION "0.08" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -163,10 +163,16 @@ static int setvar(const char *varname, const char *val) } */ +/* print driver-specific usage info */ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/sms_ser.c b/drivers/sms_ser.c index cd2d30b134..f69fa4858f 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.03" +#define DRIVER_VERSION "1.04" #define QUERY_SIZE 7 #define BUFFER_SIZE 18 @@ -572,6 +572,11 @@ void upsdrv_shutdown(void) { void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { char msg[256]; diff --git a/drivers/snmp-ups.c b/drivers/snmp-ups.c index 8a8dfeb346..590c0238b3 100644 --- a/drivers/snmp-ups.c +++ b/drivers/snmp-ups.c @@ -213,7 +213,7 @@ static const char *mibvers; #else # define DRIVER_NAME "Generic SNMP UPS driver" #endif /* WITH_DMFMIB */ -#define DRIVER_VERSION "1.37" +#define DRIVER_VERSION "1.38" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -259,6 +259,149 @@ bool_t get_and_process_data(int mode, snmp_info_t *su_info_p); int extract_template_number(snmp_info_flags_t template_type, const char* varname); snmp_info_flags_t get_template_type(const char* varname); +static void analyze_mapping_usage(void) { + /* Check if the subdriver code (mappings) and the device report + * sit together well. Note that for yet-unknown concepts, the + * NUT driver developers can either raise a discussion on how + * to best formalize that concept via docs/nut-names.txt, or + * temporarily place them into "experimental.*" or "unmapped.*" + * namespaces. + * + * Later also check that all defined mappings were used? + * TBH, this is unlikely in practice, so of little value + * (unless we are troubleshooting and under 5 or 10 data + * points are served from actually the device, and not + * from user configs or driver fallbacks). + * + * See also: similar methods in usbhid-ups and nutdrv_qx. + */ + size_t unused_count = 0, known_mappings = 0; + size_t unused_bufsize = LARGEBUF, unused_prevlen = 0, used_mappings = 0; + int ret_printf; + char *unused_names = NULL; + snmp_info_t *su_info_p; + + /* FIXME? this activity is limited to when debugging is enabled, even + * if some of the messages below can be posted visibly at level 0. + */ + if (nut_debug_level < 1) + return; + + upsdebugx(1, "%s: checking if the subdriver code (mappings) " + "consults all data points from the device report", + __func__); + + if (!snmp_info) { + upsdebugx(1, "%s: SKIP: snmp_info==null", __func__); + return; + } + + unused_names = xcalloc(unused_bufsize, sizeof(char)); + + for (su_info_p = &snmp_info[0]; (su_info_p != NULL && su_info_p->info_type != NULL) ; su_info_p++) + { + if (!su_info_p) + continue; + + known_mappings++; + + if (su_info_p->flags & SU_FLAG_MAPPING_HANDLED) { + used_mappings++; + } else { + char *pName = su_info_p->info_type; + const char *pType = (SU_TYPE(su_info_p) == SU_TYPE_CMD ? "cmd" : "data"); + int retry = 0; + + /* Keep aliases for code similarity with usbhid-ups and nutdrv_qx */ + char **pNames = &unused_names; + size_t *pCount = &unused_count, *pPrevLen = &unused_prevlen, *pBufSize = &unused_bufsize; + + if (!pName) { + upsdebugx(2, "%s: error getting a data point name, skipped", __func__); + continue; + } + + /* We may overflow the pre-allocated buffer, + * so we loop here until snprintf() succeeds + * or we are known to have failed completely. + */ + do { + retry = 0; + if (!*pNames) { + break; + } + + upsdebugx(5, "%s: adding '%s (%s)' (%" PRIuSIZE " bytes) " + "to buffer of %" PRIuSIZE "/%" PRIuSIZE " bytes", + __func__, NUT_STRARG(pName), NUT_STRARG(pType), + pName ? strlen(pName) : 0, + *pPrevLen, *pBufSize); + + ret_printf = snprintf(*pNames + *pPrevLen, *pBufSize - *pPrevLen - 1, "%s%s (%s)", + *pCount ? ", " : "", NUT_STRARG(pName), NUT_STRARG(pType)); + + upsdebugx(6, "%s: snprintf() returned %d", __func__, ret_printf); + (*pNames)[*pBufSize - 1] = '\0'; + + if (ret_printf < 0) { + upsdebugx(1, "%s: error collecting names, might not report unused descriptor names", __func__); + } else if ((size_t)ret_printf + *pPrevLen >= *pBufSize) { + if (*pBufSize < SIZE_MAX - LARGEBUF) { + *pBufSize = *pBufSize + LARGEBUF; + upsdebugx(1, "%s: buffer overflowed, trying to re-allocate as %" PRIuSIZE, __func__, *pBufSize); + *pNames = realloc(*pNames, *pBufSize); + + if (!*pNames) { + upsdebugx(1, "%s: buffer overflowed, will not report unused descriptor names", __func__); + } else { + upsdebugx(5, "%s: buffer overflowed, but reallocated successfully - retrying", __func__); + /* Retry this loop */ + retry = 1; + } + } else { + upsdebugx(1, "%s: buffer overflowed, might not report unused descriptor names", __func__); + } + } else { + *pPrevLen += (size_t)ret_printf; + } + } while (retry); + + *pCount = *pCount + 1; + } + } + + if (unused_count) { + upsdebugx(1, "%s: %" PRIuSIZE " items are present in the " + "mapping table for the SNMP UPS, but %" PRIuSIZE " " + "of them were completely not used by name via the " + "mapping defined in the selected NUT subdriver %s: %s", + __func__, known_mappings, unused_count, + NUT_STRARG(mibname), NUT_STRARG(unused_names)); + } + + if (unused_names) + free(unused_names); + + /* We arbitrarily declare that having under 10 known or used + * mappings is few enough to be loud about this */ + if (known_mappings < 10 || used_mappings < 10) { + upsdebugx(0, + "%s: %" PRIuSIZE " mapping entries are defined, and " + "%" PRIuSIZE " were actually used from SNMP walk, " + "in the selected NUT subdriver %s", + __func__, known_mappings, used_mappings, + NUT_STRARG(mibname)); + + upsdebugx(0, "Please check if there is a newer version of NUT available " + "(may be not packaged for your distribution yet), try a custom " + "build of development branch to test latest driver code per " + "%s/docs/user-manual.chunked/_installation_instructions.html#Installing_inplace, " + "and see %s/docs/developer-guide.chunked/new-drivers.html#snmp-subdrivers " + "for suggestions how you can help improve this driver.", + NUT_WEBSITE_BASE, NUT_WEBSITE_BASE); + } +} + /* --------------------------------------------- * driver functions implementations * --------------------------------------------- */ @@ -324,6 +467,7 @@ void upsdrv_initinfo(void) if (snmp_ups_walk(SU_WALKMODE_INIT) == TRUE) { dstate_dataok(); comm_status = COMM_OK; + analyze_mapping_usage(); } else { dstate_datastale(); @@ -350,7 +494,17 @@ void upsdrv_updateinfo(void) if (snmp_ups_walk(SU_WALKMODE_UPDATE)) { upsdebugx(1, "%s: pollfreq: Data OK", __func__); dstate_dataok(); - comm_status = COMM_OK; + if (comm_status != COMM_OK) { + /* We may have missed the initial connection, + * or lost one subsequently during normal work. + * Help at least with the former case with info + * about efficiency of the current mapping table. + * And who knows how the reconnection went, maybe + * a new firmware got installed or modules added? + */ + analyze_mapping_usage(); + comm_status = COMM_OK; + } } else { upsdebugx(1, "%s: pollfreq: Data STALE", __func__); @@ -426,6 +580,44 @@ void upsdrv_help(void) upsdebugx(1, "entering %s", __func__); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +#if WITH_DMFMIB + char *alias = "snmp-ups-dmf"; +#else + char *alias = "snmp-ups-old"; +#endif /* WITH_DMFMIB */ + + upsdebugx(1, "entering %s", __func__); + + /* If executed via an accepted alias, move it down in prognames[] stack */ + if (!strcmp(prognames[0], alias)) { + upsdebugx(3, "%s: marking program name '%s' as an alias for '%s'", + __func__, prognames[0], "snmp-ups"); + + if (prognames_should_free[1] && prognames[1]) + free((char*)prognames[1]); + prognames[1] = prognames[0]; + prognames_should_free[1] = prognames_should_free[0]; + + if (prognames_should_free[0]) + free((char*)prognames[0]); + prognames[0] = xstrdup("snmp-ups"); + prognames_should_free[0] = 1; + } else { + if (!strcmp(prognames[0], "snmp-ups")) { + upsdebugx(3, "%s: marking program name '%s' as an alias for '%s'", + __func__, alias, prognames[0]); + + if (prognames_should_free[1]) + free((char*)prognames[1]); + prognames[1] = xstrdup(alias); + prognames_should_free[1] = 1; + } + } +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { @@ -931,6 +1123,7 @@ void nut_snmp_init(const char *type, const char *hostname) /* Initialize session */ snmp_sess_init(&g_snmp_sess); + /* peername may include the port, transport, etc. and will be parsed by the SNMP library */ g_snmp_sess.peername = xstrdup(hostname); /* Net-SNMP timeout and retries */ @@ -1236,6 +1429,11 @@ static struct snmp_pdu **nut_snmp_walk(const char *OID, int max_iteration) while( nb_iteration < max_iteration ) { struct snmp_pdu **new_ret_array; + /* Check if we are asked to stop (reactivity++) */ + if (exit_flag != 0) { + fatalx(EXIT_FAILURE, "Aborting because exit_flag was set"); + } + /* Going to a shorter OID means we are outside our sub-tree */ if( current_name_len < name_len ) { break; @@ -1811,6 +2009,7 @@ void su_setinfo(snmp_info_t *su_info_p, const char *value) snprintf(info_type, 128, "%s", su_info_p->info_type); } + su_info_p->flags |= SU_FLAG_MAPPING_HANDLED; upsdebugx(1, "%s: using info_type '%s'", __func__, info_type); if (SU_TYPE(su_info_p) == SU_TYPE_CMD) @@ -1819,8 +2018,8 @@ void su_setinfo(snmp_info_t *su_info_p, const char *value) /* ups.status and {ups, Lx, outlet, outlet.group}.alarm have special * handling, not here! */ if ((strcasecmp(su_info_p->info_type, "ups.status")) - && (strcasecmp(strrchr(su_info_p->info_type, '.'), ".alarm"))) - { + && (strcasecmp(strrchr(su_info_p->info_type, '.'), ".alarm")) + ) { if (value != NULL) dstate_setinfo(info_type, "%s", value); else if (su_info_p->dfl != NULL) @@ -1841,8 +2040,10 @@ void su_setinfo(snmp_info_t *su_info_p, const char *value) upsdebugx(3, "%s: adding enumerated values", __func__); /* Loop on all existing values */ - for (info_lkp = su_info_p->oid2info; info_lkp != NULL - && info_lkp->info_value != NULL; info_lkp++) { + for (info_lkp = su_info_p->oid2info; + info_lkp != NULL && info_lkp->info_value != NULL; + info_lkp++ + ) { dstate_addenum(info_type, "%s", info_lkp->info_value); } } @@ -1897,7 +2098,8 @@ void su_alarm_set(snmp_info_t *su_info_p, long value) /* Special handling for outlet & outlet groups alarms */ if ((su_info_p->flags & SU_OUTLET) - || (su_info_p->flags & SU_OUTLET_GROUP)) { + || (su_info_p->flags & SU_OUTLET_GROUP) + ) { /* Extract template number */ item_number = extract_template_number(su_info_p->flags, info_type); @@ -2136,6 +2338,11 @@ bool_t load_mib2nut(const char *mib) __func__, mib); /* Retry at most 3 times, to maximise chances */ for (i = 0; i < 3 ; i++) { + /* Check if we are asked to stop (reactivity++) */ + if (exit_flag != 0) { + fatalx(EXIT_FAILURE, "Aborting because exit_flag was set"); + } + upsdebugx(3, "%s: trying the new match_sysoid() method: attempt #%d", __func__, (i+1)); if ((m2n = match_sysoid()) != NULL) @@ -2228,6 +2435,15 @@ bool_t load_mib2nut(const char *mib) upsdebugx(1, "%s: using %s MIB for device [%s] (host %s)", __func__, mibname, upsname ? upsname : device_name, device_path); + + /* FIXME: also "tripplite" on devices that do not identify as such */ + if (mibIsAuto && strcasecmp(mibname, "ietf")) + upsdebugx(0, "Only the IETF standard mapping was found as fallback. " + "Please check %s/docs/developer-guide.chunked/new-drivers.html#snmp-subdrivers " + "for suggestions how you can help improve the driver to " + "support vendor-specific mappings for your device better.", + NUT_WEBSITE_BASE); + return TRUE; } @@ -3273,6 +3489,8 @@ bool_t snmp_ups_walk(int mode) bool_t status = FALSE; if (mode == SU_WALKMODE_UPDATE) { + /* Below we skip semi-static elements in update mode: + * only parse when countdown reaches exactly 0 */ semistatic_countdown--; if (semistatic_countdown < 0) semistatic_countdown = semistaticfreq; @@ -3450,8 +3668,8 @@ bool_t snmp_ups_walk(int mode) continue; } - /* Set default value if we cannot fetch it */ - /* and set static flag on this element. + /* Set default value if we cannot fetch it + * and set static flag on this element. * Not applicable to outlets (need SU_FLAG_STATIC tagging) */ if ( (su_info_p->flags & SU_FLAG_ABSENT) diff --git a/drivers/snmp-ups.h b/drivers/snmp-ups.h index 04290b344f..2aa6c479df 100644 --- a/drivers/snmp-ups.h +++ b/drivers/snmp-ups.h @@ -333,7 +333,7 @@ typedef struct { #define SU_FLAG_OK (1UL << 0) /* show element to upsd - * internal to snmp driver */ #define SU_FLAG_STATIC (1UL << 1) /* retrieve info only once. */ -#define SU_FLAG_ABSENT (1UL << 2) /* data is absent in the device, +#define SU_FLAG_ABSENT (1UL << 2) /* data is known to be absent in the device, * use default value. */ #define SU_FLAG_STALE (1UL << 3) /* data stale, don't try too often - * internal to snmp driver */ @@ -341,7 +341,7 @@ typedef struct { #define SU_FLAG_UNIQUE (1UL << 5) /* There can be only be one * provider of this info, * disable the other providers */ -/* Note: older releases defined the following flag, but removed it by 2.7.5: +/* Note: older releases defined the following flag, but removed it by 2.8.0: * #define SU_FLAG_SETINT (1UL << 6)*/ /* save value */ #define SU_FLAG_ZEROINVALID (1UL << 6) /* Invalid if "0" value */ #define SU_FLAG_NAINVALID (1UL << 7) /* Invalid if "N/A" value */ @@ -358,7 +358,7 @@ typedef struct { * a valid OID) in order to detect the base SNMP index (0 or 1) */ -/* "flags" bit 10 */ +/* "flags" bits 10..11 */ #define SU_OUTLET_GROUP (1UL << 10) /* outlet group template definition */ #define SU_OUTLET (1UL << 11) /* outlet template definition */ @@ -399,7 +399,8 @@ typedef struct { /* NOTE: Previously SU_DAISY had same bit-flag value as SU_TYPE_DAISY_2 */ #define SU_TYPE_DAISY_MASTER_ONLY (1UL << 24) /* Only valid for daisychain master (device.1) */ -/* Free slot: (1UL << 25) */ +/* "flags" bit 25 */ +#define SU_FLAG_MAPPING_HANDLED (1UL << 25) /* raised internally if any (sub)driver handling loop care about this report; if not, may be a point for improvement... */ #define SU_AMBIENT_TEMPLATE (1UL << 26) /* ambient template definition */ diff --git a/drivers/socomec_jbus.c b/drivers/socomec_jbus.c index 2800a56813..d4b14ec2c8 100644 --- a/drivers/socomec_jbus.c +++ b/drivers/socomec_jbus.c @@ -35,7 +35,7 @@ #endif #define DRIVER_NAME "Socomec jbus driver (libmodbus link type: " NUT_MODBUS_LINKTYPE_STR ")" -#define DRIVER_VERSION "0.09" +#define DRIVER_VERSION "0.10" #define CHECK_BIT(var,pos) ((var) & (1<<(pos))) #define MODBUS_SLAVE_ID 1 @@ -445,6 +445,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/solis.c b/drivers/solis.c index 2e23734fb9..d6d019c363 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.72" +#define DRIVER_VERSION "0.73" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -1005,6 +1005,11 @@ void upsdrv_help(void) { printf(" These are valid only if prgshut = 2 or 3\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { addvar(VAR_VALUE, "battext", "Battery Extension (0-80)min"); addvar(VAR_VALUE, "prgshut", "Programable power off (0-3)"); diff --git a/drivers/tripplite.c b/drivers/tripplite.c index 0017f4c118..d8404a3f15 100644 --- a/drivers/tripplite.c +++ b/drivers/tripplite.c @@ -117,7 +117,7 @@ #include #define DRIVER_NAME "Tripp-Lite SmartUPS driver" -#define DRIVER_VERSION "0.98" +#define DRIVER_VERSION "0.99" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -619,6 +619,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { char msg[256]; diff --git a/drivers/tripplite_usb.c b/drivers/tripplite_usb.c index f012e86f36..f710f060fb 100644 --- a/drivers/tripplite_usb.c +++ b/drivers/tripplite_usb.c @@ -137,7 +137,7 @@ #include "usb-common.h" #define DRIVER_NAME "Tripp Lite OMNIVS / SMARTPRO driver" -#define DRIVER_VERSION "0.40" +#define DRIVER_VERSION "0.43" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -203,7 +203,8 @@ static enum tl_model_t { TRIPP_LITE_OMNIVS_2001, TRIPP_LITE_SMARTPRO, TRIPP_LITE_SMART_0004, - TRIPP_LITE_SMART_3005 + TRIPP_LITE_SMART_3005, + TRIPP_LITE_SMART_3017 } tl_model = TRIPP_LITE_UNKNOWN; /*! Are the values encoded in ASCII or binary? @@ -218,6 +219,7 @@ static int is_binary_protocol(void) case TRIPP_LITE_SMART_0004: case TRIPP_LITE_OMNIVS: case TRIPP_LITE_OMNIVS_2001: + case TRIPP_LITE_SMART_3017: case TRIPP_LITE_UNKNOWN: #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) # pragma GCC diagnostic push @@ -244,6 +246,7 @@ static int is_smart_protocol(void) case TRIPP_LITE_SMARTPRO: case TRIPP_LITE_SMART_0004: case TRIPP_LITE_SMART_3005: + case TRIPP_LITE_SMART_3017: return 1; case TRIPP_LITE_OMNIVS: case TRIPP_LITE_OMNIVS_2001: @@ -312,7 +315,7 @@ static long battery_voltage_nominal = 12, /* input_voltage_maximum = -1, input_voltage_minimum = -1, */ switchable_load_banks = 0, - unit_id = -1; /*!< range: 1-65535, most likely */ + unit_id = DEFAULT_UPSID; /*!< range: 1-65535, most likely */ /*! Time in seconds to delay before shutting down. */ static unsigned int offdelay = DEFAULT_OFFDELAY; @@ -529,6 +532,9 @@ static enum tl_model_t decode_protocol(unsigned int proto) case 0x3005: upslogx(LOG_INFO, "Using binary SMART protocol (%x)", proto); return TRIPP_LITE_SMART_3005; + case 0x3017: + upslogx(LOG_INFO, "Using (mostly) ASCII SMART protocol (%x)", proto); + return TRIPP_LITE_SMART_3017; default: upslogx(LOG_INFO, "Unknown protocol (%04x)", proto); break; @@ -553,31 +559,83 @@ static void decode_v(const unsigned char *value) ivn = value[1]; lb = value[4]; - switch(ivn) { - case '0': input_voltage_nominal = - input_voltage_scaled = 100; - break; + if( is_smart_protocol() && (tl_model != TRIPP_LITE_SMART_3017) ) { + switch(ivn) { + case 0: + case '0': input_voltage_nominal = + input_voltage_scaled = 100; + break; + + case 1: + case '1': input_voltage_nominal = + input_voltage_scaled = 110; + break; + + case 2: /* protocol 3005 */ + case '2': input_voltage_nominal = + input_voltage_scaled = 120; + break; + + case 3: + case '3': input_voltage_nominal = + input_voltage_scaled = 127; + break; + + case 4: + case '4': input_voltage_nominal = + input_voltage_scaled = 208; + break; + + case 5: + case '5': input_voltage_nominal = + input_voltage_scaled = 220; + break; + + case 6: + case '6': input_voltage_nominal = + input_voltage_scaled = 230; + break; + + case 7: + case '7': input_voltage_nominal = + input_voltage_scaled = 240; + break; - case 2: /* protocol 3005 */ - case '1': input_voltage_nominal = - input_voltage_scaled = 120; - break; - - case '2': input_voltage_nominal = - input_voltage_scaled = 230; - break; - - case '3': input_voltage_nominal = 208; - input_voltage_scaled = 230; - break; + default: + upslogx(LOG_WARNING, "Unknown input voltage range: 0x%02x", (unsigned int)ivn); + break; + } + } else { + /* Lots of odd cases here; maybe some of the SMART protocols got mixed in, too: */ + switch(ivn) { + case '0': input_voltage_nominal = + input_voltage_scaled = 100; + break; + + case '1': input_voltage_nominal = + input_voltage_scaled = 120; + break; + + /* UK SMX1200XLHG protocol 3017 confirmed: */ + case '2': input_voltage_nominal = + input_voltage_scaled = 230; + break; + + case '3': input_voltage_nominal = 208; + input_voltage_scaled = 230; + break; + + case 6: input_voltage_nominal = + input_voltage_scaled = 230; + break; - case 6: input_voltage_nominal = - input_voltage_scaled = 230; - break; + default: + upslogx(LOG_WARNING, "Unknown input voltage range: 0x%02x", (unsigned int)ivn); + break; + } - default: - upslogx(LOG_WARNING, "Unknown input voltage range: 0x%02x", (unsigned int)ivn); - break; + upslogx(LOG_WARNING, "Regard the input voltage range with skepticism (nominal = %ld, scaled = %ld; V[0] = 0x%02x)", + input_voltage_nominal, input_voltage_scaled, (unsigned int)ivn); } if( (lb >= '0') && (lb <= '9') ) { @@ -780,8 +838,12 @@ static int soft_shutdown(void) int ret; unsigned char buf[256], cmd_N[]="N\0x", cmd_G[] = "G"; + /* TODO: find size/format of ASCII delay command */ + if( !is_binary_protocol() ) { + upslogx(LOG_WARNING, "Other commands for this UPS are binary, but the format of the shutdown delay command has not been confirmed."); + } + /* Already binary: */ - /* FIXME: Assumes memory layout / endianness? */ cmd_N[2] = (unsigned char)(offdelay & 0x00FF); cmd_N[1] = (unsigned char)(offdelay >> 8); upsdebugx(3, "soft_shutdown(offdelay=%u): N", offdelay); @@ -896,6 +958,7 @@ static int control_outlet(int outlet_id, int state) case TRIPP_LITE_OMNIVS: case TRIPP_LITE_OMNIVS_2001: + case TRIPP_LITE_SMART_3017: case TRIPP_LITE_UNKNOWN: #if (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_PUSH_POP) && (defined HAVE_PRAGMA_GCC_DIAGNOSTIC_IGNORED_COVERED_SWITCH_DEFAULT) # pragma GCC diagnostic push @@ -1504,7 +1567,7 @@ void upsdrv_updateinfo(void) return; } - if( tl_model == TRIPP_LITE_SMARTPRO ) { + if( (tl_model == TRIPP_LITE_SMARTPRO) || (tl_model == TRIPP_LITE_SMART_3017) ) { freq = hex2d(t_value + 3, 3); dstate_setinfo("input.frequency", "%.1f", freq / 10.0); @@ -1538,7 +1601,7 @@ void upsdrv_updateinfo(void) /* - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ if( tl_model == TRIPP_LITE_OMNIVS || tl_model == TRIPP_LITE_OMNIVS_2001 || - tl_model == TRIPP_LITE_SMARTPRO || tl_model == TRIPP_LITE_SMART_0004 || tl_model == TRIPP_LITE_SMART_3005) { + is_smart_protocol() ) { /* dq ~= sqrt(dV) is a reasonable approximation * Results fit well against the discrete function used in the Tripp Lite * source, but give a continuous result. */ @@ -1569,6 +1632,7 @@ void upsdrv_updateinfo(void) hex2d(l_value+1, 4)/240.0*input_voltage_scaled); break; case TRIPP_LITE_SMARTPRO: + case TRIPP_LITE_SMART_3017: dstate_setinfo("ups.load", "%ld", hex2d(l_value+1, 2)); break; case TRIPP_LITE_SMART_3005: @@ -1617,6 +1681,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { char msg[256]; diff --git a/drivers/tripplitesu.c b/drivers/tripplitesu.c index 05c079de67..f00fae5153 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.10" +#define DRIVER_VERSION "0.11" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -881,6 +881,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x or ups.conf */ void upsdrv_makevartable(void) { diff --git a/drivers/upscode2.c b/drivers/upscode2.c index da205d4b88..df3a49a26b 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.94" +#define DRIVER_VERSION "0.95" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -460,6 +460,10 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} void upsdrv_initups(void) { @@ -1120,7 +1124,7 @@ static int upsc_commandlist(void) upsdebugx(1, "instcmd: %s %s", cp->cmd, cp->upsc); dstate_addcmd(cp->cmd); cp->enabled = 1; - break; + break; } } diff --git a/drivers/upsdrvctl.c b/drivers/upsdrvctl.c index 645a8cc0ef..32732d621d 100644 --- a/drivers/upsdrvctl.c +++ b/drivers/upsdrvctl.c @@ -687,6 +687,7 @@ static void forkexec(char *const argv[], const ups_t *ups) if (pid != 0) { /* parent */ int wstat; struct sigaction sa; + const char *oldTag = getproctag(); /* work around const for this one... */ int *pupid = (int *)&(ups->pid); @@ -694,6 +695,16 @@ static void forkexec(char *const argv[], const ups_t *ups) *pupid = pid; *puexectimeout = 0; + if (oldTag) { + if (!strstr(oldTag, "parent")) { + char tag[SMALLBUF]; + snprintf(tag, sizeof(tag), "%s-parent", getproctag()); + setproctag(tag); + } + } else { + setproctag("parent"); + } + /* Handle "parallel" drivers startup */ if (waitfordrivers == 0) { upsdebugx(2, "'nowait' set, continuing..."); @@ -770,10 +781,21 @@ static void forkexec(char *const argv[], const ups_t *ups) return; } + + if (getproctag()) { + char tag[SMALLBUF]; + snprintf(tag, sizeof(tag), "%s-child", getproctag()); + setproctag(tag); + } else { + setproctag("child"); + } } - /* child or foreground mode (no fork) */ + /* child or foreground mode (no fork, e.g. single driver operation) + * execute the specified binary and args into current process + */ + upsdebugx(1, "%s: calling execv(%s, ...)", __func__, argv[0]); ret = execv(argv[0], argv); /* shouldn't get here normally */ @@ -1210,7 +1232,7 @@ static void start_driver(const ups_t *ups) } } -static void help(const char *progname) +static void help(const char *arg_progname) __attribute__((noreturn)); static void help(const char *arg_progname) @@ -1593,6 +1615,8 @@ int main(int argc, char **argv) } /* else nothing to bother about */ } + setproctag("init"); + argc -= optind; argv += optind; @@ -1661,6 +1685,8 @@ int main(int argc, char **argv) if (argc == lastarg) { ups_t *tmp = upstable; + char tag[SMALLBUF]; + upscount = 0; while (tmp) { @@ -1670,12 +1696,27 @@ int main(int argc, char **argv) upsdebugx(1, "upsdrvctl commanding all drivers (%d found): %s", upscount, (pt_cmd ? pt_cmd : NUT_STRARG(command_name))); + + snprintf(tag, sizeof(tag), "%s-all", + pt_cmd ? pt_cmd : (command_name ? command_name : "unknown-command")); + if (command_name || pt_cmd) + setproctag(tag); + send_all_drivers(command); } else if (argc == (lastarg + 1)) { + char tag[SMALLBUF]; + upscount = 1; upsdebugx(1, "upsdrvctl commanding one driver (%s): %s", argv[lastarg], (pt_cmd ? pt_cmd : NUT_STRARG(command_name))); + + snprintf(tag, sizeof(tag), "%s-%s", + pt_cmd ? pt_cmd : (command_name ? command_name : "unknown-command"), + argv[lastarg]); + if (command_name || pt_cmd) + setproctag(tag); + send_one_driver(command, argv[lastarg]); } else { fatalx(EXIT_FAILURE, "Error: extra arguments left on command line\n" diff --git a/drivers/usb-common.h b/drivers/usb-common.h index b71bed8a06..7355b20965 100644 --- a/drivers/usb-common.h +++ b/drivers/usb-common.h @@ -534,7 +534,9 @@ void USBFreeExactMatcher(USBDeviceMatcher_t *matcher); void USBFreeRegexMatcher(USBDeviceMatcher_t *matcher); /* dummy USB function and macro, inspired from the Linux kernel - * this allows USB information extraction */ + * this allows USB information extraction; used by tools/nut-usbinfo.pl + * to generate some sources (nut-scanner) and deliverables (udev.rules) + * among other things */ #define USB_DEVICE(vendorID, productID) vendorID, productID typedef struct { diff --git a/drivers/usbhid-ups.c b/drivers/usbhid-ups.c index c6ccf057ff..7e7f5e9413 100644 --- a/drivers/usbhid-ups.c +++ b/drivers/usbhid-ups.c @@ -29,7 +29,7 @@ */ #define DRIVER_NAME "Generic HID driver" -#define DRIVER_VERSION "0.67" +#define DRIVER_VERSION "0.71" #define HU_VAR_WAITBEFORERECONNECT "waitbeforereconnect" @@ -285,6 +285,7 @@ typedef struct { static status_lkp_t status_info[] = { /* map internal status strings to bit masks */ { "online", STATUS(ONLINE) }, + { "offline", STATUS(OFFLINE) }, { "dischrg", STATUS(DISCHRG) }, { "chrg", STATUS(CHRG) }, { "lowbatt", STATUS(LOWBATT) }, @@ -304,6 +305,7 @@ static status_lkp_t status_info[] = { { "depleted", STATUS(DEPLETED) }, { "timelimitexp", STATUS(TIMELIMITEXP) }, { "fullycharged", STATUS(FULLYCHARGED) }, + { "notfullycharged", STATUS(NOTFULLYCHARGED) }, { "awaitingpower", STATUS(AWAITINGPOWER) }, { "fanfail", STATUS(FANFAIL) }, { "nobattery", STATUS(NOBATTERY) }, @@ -340,7 +342,7 @@ static status_lkp_t status_info[] = { info_lkp_t online_info[] = { { 1, "online", NULL, NULL }, - { 0, "!online", NULL, NULL }, + { 0, "offline", NULL, NULL }, /* previously was "!online" but that proved ambiguous */ { 0, NULL, NULL, NULL } }; info_lkp_t discharging_info[] = { @@ -463,9 +465,9 @@ info_lkp_t chargerfail_info[] = { { 0, "!chargerfail", NULL, NULL }, { 0, NULL, NULL, NULL } }; -info_lkp_t fullycharged_info[] = { /* used by CyberPower and TrippLite */ +info_lkp_t fullycharged_info[] = { /* used by CyberPower and TrippLite, and some other HIDs */ { 1, "fullycharged", NULL, NULL }, - { 0, "!fullycharged", NULL, NULL }, + { 0, "notfullycharged", NULL, NULL }, { 0, NULL, NULL, NULL } }; info_lkp_t depleted_info[] = { @@ -621,16 +623,16 @@ info_lkp_t divide_by_10_conversion[] = { done with result! */ static const char *divide_by_100_conversion_fun(double value) { - static char buf[20]; + static char buf[20]; - snprintf(buf, sizeof(buf), "%0.1f", value * 0.01); + snprintf(buf, sizeof(buf), "%0.1f", value * 0.01); - return buf; + 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 } + { 0, NULL, divide_by_100_conversion_fun, NULL } }; /* returns statically allocated string - must not use it again before @@ -659,6 +661,190 @@ info_lkp_t kelvin_celsius_conversion[] = { { 0, NULL, kelvin_celsius_conversion_fun, NULL } }; +static void analyze_mapping_usage(void) { + /* Check if the subdriver code (mappings) and the device report + * sit together well. Note that for yet-unknown concepts, the + * NUT driver developers can either raise a discussion on how + * to best formalize that concept via docs/nut-names.txt, or + * temporarily place them into "experimental.*" or "unmapped.*" + * namespaces. + * + * Later also check that all defined mappings were used? + * TBH, this is unlikely in practice, so of little value + * (unless we are troubleshooting and under 5 or 10 data + * points are served from actually the device, and not + * from user configs or driver fallbacks). + * + * See also: similar methods in snmp-ups and nutdrv_qx. + */ + size_t d, unused_count = 0, halfused_count = 0; + size_t unused_bufsize = LARGEBUF, halfused_bufsize = LARGEBUF, unused_prevlen = 0, halfused_prevlen = 0, used_mappings = 0, known_mappings = 0; + int ret_printf; + char *unused_names = NULL, *halfused_names = NULL; + hid_info_t *hidups_item; + + /* FIXME? this activity is limited to when debugging is enabled, even + * if some of the messages below can be posted visibly at level 0. + */ + if (nut_debug_level < 1) + return; + + upsdebugx(1, "%s: checking if the subdriver code (mappings) " + "consults all data points from the device report", + __func__); + + if (!pDesc) { + upsdebugx(1, "%s: SKIP: pDesc==null", __func__); + return; + } + + unused_names = xcalloc(unused_bufsize, sizeof(char)); + halfused_names = xcalloc(halfused_bufsize, sizeof(char)); + + for (d = 0; d < pDesc->nitems; d++) { + HIDData_t *pData = &pDesc->item[d]; + + if (pData && !(pData->mapping_handled)) { + char *pName = HIDGetDataItem(pData, subdriver->utab); + const char *pType = HIDDataType(pData); + int retry = 0; + char **pNames = &unused_names; + size_t *pCount = &unused_count, *pPrevLen = &unused_prevlen, *pBufSize = &unused_bufsize; + + if (!pName) { + upsdebugx(2, "%s: error getting a Report Path name, skipped", __func__); + continue; + } else { + /* check if this is a half-used name */ + for (hidups_item = subdriver->hid2nut; hidups_item->info_type != NULL ; hidups_item++) { + /* Note: using a shortcut with mapping table and + * its "hidpath" strings here, to avoid stringifying + * all paths known from report descriptor - more so + * in a nested loop. */ + if (hidups_item->hidpath && hidups_item->hiddata + && !strcasecmp(hidups_item->hidpath, pName) + && hidups_item->hiddata->mapping_handled + ) { + upsdebugx(5, "%s: Path '%s' is half-used: " + "Type '%s' was not touched via mapping, " + "but '%s' was used", + __func__, NUT_STRARG(pName), + NUT_STRARG(pType), + NUT_STRARG(HIDDataType(hidups_item->hiddata)) + ); + pNames = &halfused_names; + pCount = &halfused_count; + pPrevLen = &halfused_prevlen; + pBufSize = &halfused_bufsize; + break; + } + } + } + + /* We may overflow the pre-allocated buffer, + * so we loop here until snprintf() succeeds + * or we are known to have failed completely. + */ + do { + retry = 0; + if (!*pNames) { + break; + } + + upsdebugx(5, "%s: adding '%s (%s)' (%" PRIuSIZE " bytes) " + "to buffer of %" PRIuSIZE "/%" PRIuSIZE " bytes", + __func__, NUT_STRARG(pName), NUT_STRARG(pType), + pName ? strlen(pName) : 0, + *pPrevLen, *pBufSize); + + ret_printf = snprintf(*pNames + *pPrevLen, *pBufSize - *pPrevLen - 1, "%s%s (%s)", + *pCount ? ", " : "", NUT_STRARG(pName), NUT_STRARG(pType)); + + upsdebugx(6, "%s: snprintf() returned %d", __func__, ret_printf); + (*pNames)[*pBufSize - 1] = '\0'; + + if (ret_printf < 0) { + upsdebugx(1, "%s: error collecting names, might not report unused descriptor names", __func__); + } else if ((size_t)ret_printf + *pPrevLen >= *pBufSize) { + if (*pBufSize < SIZE_MAX - LARGEBUF) { + *pBufSize = *pBufSize + LARGEBUF; + upsdebugx(1, "%s: buffer overflowed, trying to re-allocate as %" PRIuSIZE, __func__, *pBufSize); + *pNames = realloc(*pNames, *pBufSize); + + if (!*pNames) { + upsdebugx(1, "%s: buffer overflowed, will not report unused descriptor names", __func__); + } else { + upsdebugx(5, "%s: buffer overflowed, but reallocated successfully - retrying", __func__); + /* Retry this loop */ + retry = 1; + } + } else { + upsdebugx(1, "%s: buffer overflowed, might not report unused descriptor names", __func__); + } + } else { + *pPrevLen += (size_t)ret_printf; + } + } while (retry); + + *pCount = *pCount + 1; + } + } + + if (unused_count) { + upsdebugx(1, "%s: %" PRIuSIZE " items are present in the " + "report descriptor from HID UPS, but %" PRIuSIZE " " + "of them were completely not used by name via the " + "mapping defined in the selected NUT subdriver %s: %s", + __func__, pDesc->nitems, unused_count, + subdriver->name, NUT_STRARG(unused_names)); + } + + if (halfused_count) { + upsdebugx(1, "%s: %" PRIuSIZE " items are present in the " + "report descriptor from HID UPS, but %" PRIuSIZE " " + "of them have several Types named by same Path value, " + "where at least one of the names was used and other(s) " + "were not used by the mapping defined in " + "the selected NUT subdriver %s: %s", + __func__, pDesc->nitems, halfused_count, + subdriver->name, NUT_STRARG(halfused_names)); + } + + if (unused_names) + free(unused_names); + if (halfused_names) + free(halfused_names); + + /* Check that all defined mappings were used? */ + for (hidups_item = subdriver->hid2nut; hidups_item->info_type != NULL ; hidups_item++) { + known_mappings++; + + if (hidups_item && hidups_item->hiddata + && hidups_item->hiddata->mapping_handled + ) { + used_mappings++; + } + } + + /* We arbitrarily declare that having under 10 known or used + * mappings is few enough to be loud about this */ + upsdebugx( (known_mappings < 10 || used_mappings < 10) ? 0 : 5, + "%s: %" PRIuSIZE " mapping entries are defined, and " + "%" PRIuSIZE " were actually used from USB HID report, " + "in the selected NUT subdriver %s", + __func__, known_mappings, used_mappings, + subdriver->name); + + if (known_mappings < 10 || used_mappings < 10) + upsdebugx(0, "Please check if there is a newer version of NUT available " + "(may be not packaged for your distribution yet), try a custom " + "build of development branch to test latest driver code per " + "%s/docs/user-manual.chunked/_installation_instructions.html#Installing_inplace, " + "and see %s/docs/developer-guide.chunked/new-drivers.html#hid-subdrivers " + "for suggestions how you can help improve this driver.", + NUT_WEBSITE_BASE, NUT_WEBSITE_BASE); +} + static subdriver_t *match_function_subdriver_name(int fatal_mismatch) { char *subdrv = getval("subdriver"); subdriver_t *info = NULL; @@ -1097,6 +1283,11 @@ void upsdrv_help(void) printf("\n"); } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + void upsdrv_makevartable(void) { char temp [MAX_STRING_SIZE]; @@ -1155,6 +1346,9 @@ void upsdrv_makevartable(void) 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"); + addvar(VAR_FLAG, "powercom_sdcmd_discrete_delay", + "Set to use discrete timing table for Powercom HID shutdown commands (Raptor and Smart KING Pro series)"); + #if !((defined SHUT_MODE) && SHUT_MODE) addvar(VAR_VALUE, "subdriver", "Explicit USB HID subdriver selection"); @@ -1362,6 +1556,85 @@ void upsdrv_initinfo(void) dstate_setinfo("driver.version.data", "%s", subdriver->name); + upsdebugx(1, "%s: Performing an initial UPS data walk with subdriver %s...", + __func__, subdriver->name); + if (hid_ups_walk(HU_WALKMODE_INIT) == FALSE) { + fatalx(EXIT_FAILURE, "Can't initialize data from HID UPS"); + } else { + analyze_mapping_usage(); + } + + if (!ups_status) + upslogx(LOG_WARNING, "%s: No flag bits for 'ups.status' were explicitly reported; " + "it is possible a wrong 'subdriver' option was requested or detected " + "(in case of problems with device data, consider testing with other " + "explicit driver option 'subdriver' values)", + __func__); + + upsdebugx(1, "%s: Optionally adjust some threshold values, if applicable and requested to...", __func__); + + /* Set values below from user settings only if supported by UPS */ + if (dstate_getinfo("battery.charge.low")) { + /* Retrieve user defined battery settings */ + val = getval(HU_VAR_LOWBATT); + if (val) { + dstate_setinfo("battery.charge.low", "%ld", strtol(val, NULL, 10)); + } + } + + if (dstate_getinfo("ups.delay.start")) { + /* Retrieve user defined delay settings */ + val = getval(HU_VAR_ONDELAY); + if (val) { + long l = strtol(val, NULL, 10); +#if !((defined SHUT_MODE) && SHUT_MODE) + if (subdriver == &cps_subdriver + && (l < 60 || l % 60) + ) { + upslogx(LOG_WARNING, "CPS devices tend to round delays by 60 sec down (ondelay=120 is the suggested minimum; see more in the man page)"); + } +#endif + dstate_setinfo("ups.delay.start", "%ld", l); + } + } + + if (dstate_getinfo("ups.delay.shutdown")) { + /* Retrieve user defined delay settings */ + val = getval(HU_VAR_OFFDELAY); + if (val) { + long l = strtol(val, NULL, 10); +#if !((defined SHUT_MODE) && SHUT_MODE) + if (subdriver == &cps_subdriver + && (l > 0 && (l < 60 || l % 60)) + ) { + /* Note: zero and negative values may + * have special meanings for the firmware */ + upslogx(LOG_WARNING, "CPS devices tend to round delays by 60 sec down (offdelay=60 is the suggested minimum; see more in the man page)"); + } +#endif + dstate_setinfo("ups.delay.shutdown", "%ld", l); + } + } + + upsdebugx(1, "%s: Optionally enable instant commands related to shutdown, if applicable...", __func__); + + /* Enable instant commands below only if supported by UPS */ + if (find_nut_info("load.off.delay")) { + /* Adds default with a delay value of '0' (= immediate) */ + dstate_addcmd("load.off"); + } + + if (find_nut_info("load.on.delay")) { + /* Adds default with a delay value of '0' (= immediate) */ + dstate_addcmd("load.on"); + } + + if (find_nut_info("load.off.delay") && find_nut_info("load.on.delay")) { + /* Add composite instcmds (require setting multiple HID values) */ + dstate_addcmd("shutdown.return"); + dstate_addcmd("shutdown.stayoff"); + } + /* init polling frequency for full updates */ val = getval(HU_VAR_POLLFREQ); if (val) { @@ -1664,69 +1937,7 @@ void upsdrv_initups(void) lbrb_log_delay_without_calibrating = 1; } - if (hid_ups_walk(HU_WALKMODE_INIT) == FALSE) { - fatalx(EXIT_FAILURE, "Can't initialize data from HID UPS"); - } - - /* Set values below from user settings only if supported by UPS */ - if (dstate_getinfo("battery.charge.low")) { - /* Retrieve user defined battery settings */ - val = getval(HU_VAR_LOWBATT); - if (val) { - dstate_setinfo("battery.charge.low", "%ld", strtol(val, NULL, 10)); - } - } - - if (dstate_getinfo("ups.delay.start")) { - /* Retrieve user defined delay settings */ - val = getval(HU_VAR_ONDELAY); - if (val) { - long l = strtol(val, NULL, 10); -#if !((defined SHUT_MODE) && SHUT_MODE) - if (subdriver == &cps_subdriver - && (l < 60 || l % 60) - ) { - upslogx(LOG_WARNING, "CPS devices tend to round delays by 60 sec down (ondelay=120 is the suggested minimum; see more in the man page)"); - } -#endif - dstate_setinfo("ups.delay.start", "%ld", l); - } - } - - if (dstate_getinfo("ups.delay.shutdown")) { - /* Retrieve user defined delay settings */ - val = getval(HU_VAR_OFFDELAY); - if (val) { - long l = strtol(val, NULL, 10); -#if !((defined SHUT_MODE) && SHUT_MODE) - if (subdriver == &cps_subdriver - && (l > 0 && (l < 60 || l % 60)) - ) { - /* Note: zero and negative values may - * have special meanings for the firmware */ - upslogx(LOG_WARNING, "CPS devices tend to round delays by 60 sec down (offdelay=60 is the suggested minimum; see more in the man page)"); - } -#endif - dstate_setinfo("ups.delay.shutdown", "%ld", l); - } - } - - /* Enable instant commands below only if supported by UPS */ - if (find_nut_info("load.off.delay")) { - /* Adds default with a delay value of '0' (= immediate) */ - dstate_addcmd("load.off"); - } - - if (find_nut_info("load.on.delay")) { - /* Adds default with a delay value of '0' (= immediate) */ - dstate_addcmd("load.on"); - } - - if (find_nut_info("load.off.delay") && find_nut_info("load.on.delay")) { - /* Add composite instcmds (require setting multiple HID values) */ - dstate_addcmd("shutdown.return"); - dstate_addcmd("shutdown.stayoff"); - } + upsdebugx(1, "%s: finished", __func__); } void upsdrv_cleanup(void) @@ -1759,10 +1970,11 @@ void possibly_supported(const char *mfr, HIDDevice_t *arghd) { upsdebugx(0, "This %s device (%04x:%04x) is not (or perhaps not yet) supported\n" -"by usbhid-ups. Please make sure you have an up-to-date version of NUT. If\n" -"this does not fix the problem, try running the driver with the\n" -"'-x productid=%04x' option. Please report your results to the NUT user's\n" -"mailing list .\n", +"by usbhid-ups. Please make sure you have an up-to-date version of NUT.\n" +"If this does not fix the problem, try running the driver with the\n" +"'-x productid=%04x' option, or iterate with explicit '-x subdriver=...'\n" +"option. Please report your results to the NUT user's mailing list\n" +"at .\n", mfr, arghd->VendorID, arghd->ProductID, arghd->ProductID); if (arghd->VendorID == 0x06da) { @@ -1780,6 +1992,18 @@ static void process_boolean_info(const char *nutvalue) upsdebugx(5, "process_boolean_info: %s", nutvalue); + /* Only neuter the other if we know the opposite to be true */ + if (!strcmp(nutvalue, "online")) + process_boolean_info("!offline"); + else if (!strcmp(nutvalue, "offline")) + process_boolean_info("!online"); + + /* Similarly for (NOT)FULLYCHARGED status that not all devices report */ + if (!strcmp(nutvalue, "fullycharged")) + process_boolean_info("!notfullycharged"); + else if (!strcmp(nutvalue, "notfullycharged")) + process_boolean_info("!fullycharged"); + if (*nutvalue == '!') { nutvalue++; clear = 1; @@ -1868,7 +2092,7 @@ static int callback( } if (!subdriver) { - upsdebugx(1, "Manufacturer not supported!"); + upsdebugx(1, "Manufacturer or model not supported!"); return 0; } @@ -1877,6 +2101,10 @@ static int callback( if (subdriver->fix_report_desc(arghd, pDesc)) { upsdebugx(2, "Report Descriptor Fixed"); } + upsdebugx(1, "%s: calling HIDDumpTree(); in case of problems with device data " + "please note that a wrong subdriver could have been chosen above; " + "consider testing others with an explicit driver option", + __func__); HIDDumpTree(udev, arghd, subdriver->utab); #if !((defined SHUT_MODE) && SHUT_MODE) @@ -2102,7 +2330,7 @@ static bool_t hid_ups_walk(walkmode_t mode) */ default: fatalx(EXIT_FAILURE, "hid_ups_walk: unknown update mode!"); - } + } /* end of: switch(mode) */ #ifdef __clang__ # pragma clang diagnostic pop #endif @@ -2168,7 +2396,7 @@ static bool_t hid_ups_walk(walkmode_t mode) case LIBUSB_ERROR_PIPE: /* Broken pipe */ default: /* Don't know what happened, try again later... */ - upsdebugx(1, "HIDGetDataValue unknown retcode '%i'", retcode); + upsdebugx(1, "HIDGetDataValue unknown retcode '%i'", retcode); continue; } @@ -2388,9 +2616,35 @@ static void ups_status_set(void) onlinedischarge_log_throttle_charge = -1; } - if (!(ups_status & STATUS(ONLINE))) { + if ((ups_status & STATUS(OFFLINE))) { status_set("OB"); /* on battery */ + if ((ups_status & STATUS(ONLINE))) { + upsdebugx(1, + "%s: seems that UPS [%s] reports both online and on-battery " + "power states at the same time", + __func__, upsname); + } + } + + if (!(ups_status & STATUS(ONLINE))) { + if ((ups_status & STATUS(OFFLINE))) { + status_set("OB"); /* on battery */ + } else { + if ((ups_status & STATUS(DISCHRG))) { + upsdebugx(1, + "%s: seems that UPS [%s] does not report a power state, " + "but it is discharging - assuming it is on battery now", + __func__, upsname); + + status_set("OB"); + } else { + upsdebugx(1, + "%s: seems that UPS [%s] does not report a power state", + __func__, upsname); + } + } } else if ((ups_status & STATUS(DISCHRG))) { + /* We are known to be online AND discharging... */ int do_logmsg = 0, current_charge = 0; /* if online but discharging */ @@ -2530,26 +2784,40 @@ static void ups_status_set(void) } upslogx(LOG_WARNING, "%s: seems that UPS [%s] is in OL+DISCHRG state now. %s" - "Is it calibrating (perhaps you want to set 'onlinedischarge_calibration' option)? " - "Note that some UPS models (e.g. CyberPower UT series) emit OL+DISCHRG when " - "in fact offline/on-battery (perhaps you want to set 'onlinedischarge_onbattery' option).", - __func__, upsname, msg_charge); + "Is it calibrating (perhaps you want to set 'onlinedischarge_calibration' option)? " + "Note that some UPS models (e.g. CyberPower UT series) emit OL+DISCHRG when " + "in fact offline/on-battery (perhaps you want to set 'onlinedischarge_onbattery' option).", + __func__, upsname, msg_charge); } } else if ((ups_status & STATUS(ONLINE))) { - /* if simply online */ + /* we get here if simply online, not discharging */ status_set("OL"); } isCalibrating = status_get("CAL"); - if ((ups_status & STATUS(DISCHRG)) && - !(ups_status & STATUS(DEPLETED))) { + if ((ups_status & STATUS(DISCHRG)) + && !(ups_status & STATUS(DEPLETED)) + ) { status_set("DISCHRG"); /* discharging */ } - if ((ups_status & STATUS(CHRG)) && - !(ups_status & STATUS(FULLYCHARGED))) { - status_set("CHRG"); /* charging */ + + if (ups_status & STATUS(CHRG)) { + if (ups_status & STATUS(NOTFULLYCHARGED)) { + /* Device reports this status, else below... */ + status_set("CHRG"); /* charging */ + } else if (!(ups_status & STATUS(FULLYCHARGED)) && !(ups_status & STATUS(NOTFULLYCHARGED))) { + /* Device does not report this status at all */ + const char *s; + if ((s = dstate_getinfo("battery.charge"))) { + /* NOTE: exact "0" may mean a conversion error: */ + int current_charge = atoi(s); + if (current_charge > 0 && current_charge < 100) + status_set("CHRG"); /* charging */ + } + } } + if (ups_status & (STATUS(LOWBATT) | STATUS(TIMELIMITEXP) | STATUS(SHUTDOWNIMM))) { if (lbrb_log_delay_sec < 1 || (!isCalibrating && !lbrb_log_delay_without_calibrating) @@ -2575,9 +2843,11 @@ static void ups_status_set(void) } else { last_lb_start = 0; } + if (ups_status & STATUS(OVERLOAD)) { status_set("OVER"); /* overload */ } + if ((ups_status & STATUS(REPLACEBATT)) || (ups_status & STATUS(NOBATTERY))) { if (lbrb_log_delay_sec < 1 || (!isCalibrating && !lbrb_log_delay_without_calibrating) @@ -2604,25 +2874,31 @@ static void ups_status_set(void) } else { last_rb_start = 0; } + if (ups_status & STATUS(TRIM)) { status_set("TRIM"); /* SmartTrim */ } + if (ups_status & STATUS(BOOST)) { status_set("BOOST"); /* SmartBoost */ } + if (ups_status & (STATUS(BYPASSAUTO) | STATUS(BYPASSMAN))) { status_set("BYPASS"); /* on bypass */ } + if (ups_status & STATUS(ECOMODE)) { buzzmode_set("vendor:default:ECO"); /* on ECO(HE) Mode, * should not happen * via ups.status anymore */ } + if (ups_status & STATUS(ESSMODE)) { buzzmode_set("vendor:default:ESS"); /* on ESS Mode, * should not happen * via ups.status anymore */ } + if (ups_status & STATUS(OFF)) { status_set("OFF"); /* ups is off */ } @@ -2775,16 +3051,33 @@ static const char *hu_find_infoval(info_lkp_t *hid2info, const double value) /* return -1 on failure, 0 for a status update and 1 in all other cases */ static int ups_infoval_set(hid_info_t *item, double value) { - const char *nutvalue; + const char *nutvalue = NULL; + const char *pType = (nut_debug_level > 0 && item->hiddata) ? HIDDataType(item->hiddata) : NULL; /* need lookup'ed translation? */ - if (item->hid2info != NULL){ - + if (item->hid2info != NULL) { if ((nutvalue = hu_find_infoval(item->hid2info, value)) == NULL) { - upsdebugx(5, "Lookup [%g] failed for [%s]", value, item->info_type); + upsdebugx(5, "%s: Lookup [%g] failed for [%s]", __func__, value, item->info_type); return -1; } + /* clause continued below after this message... */ + } + + if (item->hiddata != NULL) { + if (!item->hiddata->mapping_handled) { + upsdebugx(5, "%s: setting report descriptor mapping for '%s' (%s) as handled", + __func__, NUT_STRARG(item->hidpath), NUT_STRARG(pType)); + item->hiddata->mapping_handled = TRUE; + } else { + upsdebugx(5, "%s: report descriptor mapping for '%s' (%s) was already set as handled", + __func__, NUT_STRARG(item->hidpath), NUT_STRARG(pType)); + } + } else { + upsdebugx(5, "%s: got no report descriptor mapping for '%s'", + __func__, NUT_STRARG(item->hidpath)); + } + if (nutvalue != NULL) { /* deal with boolean items */ if (!strncmp(item->info_type, "BOOL", 4)) { process_boolean_info(nutvalue); diff --git a/drivers/usbhid-ups.h b/drivers/usbhid-ups.h index 8fe3e815d1..03f76b8d34 100644 --- a/drivers/usbhid-ups.h +++ b/drivers/usbhid-ups.h @@ -139,6 +139,7 @@ extern info_lkp_t kelvin_celsius_conversion[]; typedef enum { ONLINE = 0, /* on line */ + OFFLINE, /* explicitly known as offline */ DISCHRG, /* discharging */ CHRG, /* charging */ LOWBATT, /* low battery */ @@ -157,7 +158,8 @@ typedef enum { COMMFAULT, /* UPS fault; Belkin, TrippLite */ DEPLETED, /* battery depleted; Belkin */ TIMELIMITEXP, /* time limit expired; APC */ - FULLYCHARGED, /* battery full; CyberPower */ + FULLYCHARGED, /* battery full; CyberPower and others */ + NOTFULLYCHARGED, /* battery reported as not full; CyberPower and others */ AWAITINGPOWER, /* awaiting power; Belkin, TrippLite */ FANFAIL, /* fan failure; MGE */ NOBATTERY, /* battery missing; MGE */ diff --git a/drivers/ve-direct.c b/drivers/ve-direct.c index 564c057594..3bcd59ad11 100644 --- a/drivers/ve-direct.c +++ b/drivers/ve-direct.c @@ -22,7 +22,7 @@ #include "serial.h" #define DRIVER_NAME "Victron Energy Direct UPS and solar controller driver" -#define DRIVER_VERSION "0.22" +#define DRIVER_VERSION "0.23" #define VE_GET 7 #define VE_SET 8 @@ -288,6 +288,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { diff --git a/drivers/victronups.c b/drivers/victronups.c index 6113e2f1fc..9a9b141bae 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.25" +#define DRIVER_VERSION "0.27" /* driver description structure */ upsdrv_info_t upsdrv_info = { @@ -84,7 +84,8 @@ static int test_in_progress = VICTRON_NO_TEST; static int get_data (const char *out_string, char *in_string) { - ssize_t ret_code; + ssize_t ret_code; + ser_send(upsfd, "%s%c", out_string, ENDCHAR); usleep (UPS_DELAY); ret_code = ser_get_line(upsfd, in_string, LENGTH_TEMP, ENDCHAR, @@ -98,16 +99,16 @@ static int get_data (const char *out_string, char *in_string) static int instcmd(const char *cmdname, const char *extra) { - char temp[ LENGTH_TEMP ]; + char temp[LENGTH_TEMP]; /* May be used in logging below, but not as a command argument */ NUT_UNUSED_VARIABLE(extra); upsdebug_INSTCMD_STARTING(cmdname, extra); - if(!strcasecmp(cmdname, "calibrate.start")) + if (!strcasecmp(cmdname, "calibrate.start")) { upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); - if(get_data("vTi5!",temp)) + if (get_data("vTi5!", temp)) { upsdebugx(1, "instcmd: ser_send calibrate.start failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -119,10 +120,10 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } } - else if(!strcasecmp(cmdname, "calibrate.stop")) + else if (!strcasecmp(cmdname, "calibrate.stop")) { upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); - if(get_data("vTi2!",temp)) + if (get_data("vTi2!", temp)) { upsdebugx(1, "instcmd: ser_send calibrate.stop failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -133,10 +134,10 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } } - else if(!strcasecmp(cmdname, "test.battery.stop")) + else if (!strcasecmp(cmdname, "test.battery.stop")) { upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); - if(get_data("vTi2!",temp)) + if (get_data("vTi2!", temp)) { upsdebugx(1, "instcmd: ser_send test.battery.stop failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -147,10 +148,10 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } } - else if(!strcasecmp(cmdname, "test.battery.start")) + else if (!strcasecmp(cmdname, "test.battery.start")) { upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); - if(get_data("vTi4!",temp)) + if (get_data("vTi4!", temp)) { upsdebugx(1, "instcmd: ser_send test.battery.start failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -162,9 +163,9 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } } - else if(!strcasecmp(cmdname, "test.panel.stop")) + else if (!strcasecmp(cmdname, "test.panel.stop")) { - if(get_data("vTi2!",temp)) + if (get_data("vTi2!", temp)) { upsdebugx(1, "instcmd: ser_send test.panel.stop failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -175,9 +176,9 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } } - else if(!strcasecmp(cmdname, "test.panel.start")) + else if (!strcasecmp(cmdname, "test.panel.start")) { - if(get_data("vTi3!",temp)) + if (get_data("vTi3!", temp)) { upsdebugx(1, "instcmd: ser_send test.panel.start failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -189,10 +190,10 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } } - else if(!strcasecmp(cmdname, "bypass.stop")) + else if (!strcasecmp(cmdname, "bypass.stop")) { upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); - if(get_data("vTi2!",temp)) + if (get_data("vTi2!", temp)) { upsdebugx(1, "instcmd: ser_send bypass.stop failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -203,10 +204,10 @@ static int instcmd(const char *cmdname, const char *extra) return STAT_INSTCMD_HANDLED; } } - else if(!strcasecmp(cmdname, "bypass.start")) + else if (!strcasecmp(cmdname, "bypass.start")) { upslog_INSTCMD_POWERSTATE_MAYBE(cmdname, extra); - if(get_data("vTi101!",temp)) + if (get_data("vTi101!", temp)) { upsdebugx(1, "instcmd: ser_send bypass.start failed"); return STAT_INSTCMD_UNKNOWN; /* Or Failed when it get defined */ @@ -227,6 +228,20 @@ static int instcmd(const char *cmdname, const char *extra) void upsdrv_initinfo(void) { + char temp[LENGTH_TEMP]; + + /* initialization and synchronization of the UPS */ + ser_send_char(upsfd, ENDCHAR); + usleep (UPS_LONG_DELAY); + ser_send(upsfd, "?%c", ENDCHAR); + usleep (UPS_LONG_DELAY); + ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS, 3, 0); + ser_send(upsfd, "?%c", ENDCHAR); + usleep (UPS_LONG_DELAY); + ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS, 3, 0); + ser_send(upsfd, "?%c", ENDCHAR); + usleep (UPS_DELAY); + ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS, 3, 0); if (model_name) dstate_setinfo("ups.model", "%s", model_name); @@ -246,50 +261,47 @@ void upsdrv_initinfo(void) void upsdrv_updateinfo(void) { - int flags; - char temp[ LENGTH_TEMP ]; - char test_result[ LENGTH_TEMP ]; - long runtime_sec = -1; + int flags; + char temp[LENGTH_TEMP]; + char test_result[LENGTH_TEMP]; + long runtime_sec = -1; if (start_is_datastale) { - if (get_data("vDS?",temp)) return; - if (strcmp(temp+3,"NA")) + if (get_data("vDS?", temp)) return; + if (strcmp(temp + 3,"NA")) exist_ups_serial=1; - if (get_data("vBT?",temp)) return; - if (strcmp(temp+3,"NA")) + if (get_data("vBT?", temp)) return; + if (strcmp(temp + 3,"NA")) exist_ups_temperature =1; - if (get_data("vO0I?",temp)) return; - if (strcmp(temp+4,"NA")) + if (get_data("vO0I?", temp)) return; + if (strcmp(temp + 4,"NA")) exist_output_current =1; - if (get_data("vBC?",temp)) return; - if (strcmp(temp+3,"NA")) + if (get_data("vBC?", temp)) return; + if (strcmp(temp + 3,"NA")) exist_battery_charge = 1; - if (get_data("vBI?",temp)) return; - if (strcmp(temp+3,"NA")) + if (get_data("vBI?", temp)) return; + if (strcmp(temp + 3,"NA")) exist_battery_charge = 1; - if (get_data("vBT?",temp)) return; - if (strcmp(temp+3,"NA")) + if (get_data("vBT?", temp)) return; + if (strcmp(temp + 3,"NA")) exist_battery_temperature = 1; - if (get_data("vBt?",temp)) return; - if (strcmp(temp+3,"NA")) + if (get_data("vBt?", temp)) return; + if (strcmp(temp + 3,"NA")) exist_battery_runtime = 1; start_is_datastale = 0; } - - - /* ups.status */ - if (get_data("vAa?",temp)) return; - flags = atoi (temp+3); + if (get_data("vAa?", temp)) return; + flags = atoi (temp + 3); status_init(); @@ -308,45 +320,50 @@ void upsdrv_updateinfo(void) status_set("OL"); /* Get UPS test results */ - if (get_data("vTr?",temp)) return; - if (get_data("vTd?",test_result)) return; + if (get_data("vTr?", temp)) return; + if (get_data("vTd?", test_result)) return; - switch(atoi(temp+3)) + switch(atoi(temp + 3)) { case 1: - upsdebugx(1, "upsdrv_updateinfo: test %i result = Done, Passed: %s",test_in_progress,test_result+3); + upsdebugx(1, "upsdrv_updateinfo: test %i result = Done, Passed: %s", + test_in_progress, test_result + 3); test_in_progress = VICTRON_NO_TEST; break; case 2: - upsdebugx(1, "upsdrv_updateinfo: test %i result = Done, Warning: %s",test_in_progress,test_result+3); + upsdebugx(1, "upsdrv_updateinfo: test %i result = Done, Warning: %s", + test_in_progress, test_result + 3); test_in_progress = VICTRON_NO_TEST; break; case 3: - upsdebugx(1, "upsdrv_updateinfo: test %i result = Done, Error: %s",test_in_progress,test_result+3); + upsdebugx(1, "upsdrv_updateinfo: test %i result = Done, Error: %s", + test_in_progress, test_result + 3); test_in_progress = VICTRON_NO_TEST; break; case 4: - upsdebugx(1, "upsdrv_updateinfo: test %i result = Aborted: %s",test_in_progress,test_result+3); + upsdebugx(1, "upsdrv_updateinfo: test %i result = Aborted: %s", + test_in_progress, test_result + 3); test_in_progress = VICTRON_NO_TEST; break; case 5: - if(test_in_progress==VICTRON_CALIBRATION) + if (test_in_progress==VICTRON_CALIBRATION) status_set("CAL"); /* calibration in progress */ upsdebugx(1, "upsdrv_updateinfo: test %i result = In Progress: %s", - test_in_progress,test_result+3); + test_in_progress, test_result + 3); break; case 6: upsdebugx(1, "upsdrv_updateinfo: test result = No test initiated: %s", - test_result+3); + test_result + 3); break; default: - upsdebugx(1, "upsdrv_updateinfo: unknown test result: %s / %s",temp+3,test_result+3); + upsdebugx(1, "upsdrv_updateinfo: unknown test result: %s / %s", + temp + 3, test_result + 3); break; } @@ -361,103 +378,101 @@ void upsdrv_updateinfo(void) /* ups model */ if (!model_name) { - if (get_data("vDM?",temp)) return; - dstate_setinfo("ups.model", "%s", temp+3); - upsdebugx(1, "ups.model >%s<>%s<\n",temp,temp+3); + if (get_data("vDM?", temp)) return; + dstate_setinfo("ups.model", "%s", temp + 3); + upsdebugx(1, "ups.model >%s<>%s<\n", temp, temp + 3); } - - /* ups.mfr */ - if (get_data("vDm?",temp)) return; - dstate_setinfo("ups.mfr", "%s", temp+3); - upsdebugx(1, "ups.mfr >%s<>%s<\n",temp,temp+3); + if (get_data("vDm?", temp)) return; + dstate_setinfo("ups.mfr", "%s", temp + 3); + upsdebugx(1, "ups.mfr >%s<>%s<\n", temp, temp + 3); /* ups.serial */ if (exist_ups_serial) { - if (get_data("vDS?",temp)) return; - dstate_setinfo("ups.serial", "%s", temp+3); + if (get_data("vDS?", temp)) return; + dstate_setinfo("ups.serial", "%s", temp + 3); } - upsdebugx(1, "ups.serial >%s<>%s<\n",temp,temp+3); + upsdebugx(1, "ups.serial >%s<>%s<\n", temp, temp + 3); /* ups.firmware */ - if (get_data("vDV?",temp)) return; - dstate_setinfo("ups.firmware", "%s", temp+3); - upsdebugx(1, "ups.firmware >%s<>%s<\n",temp,temp+3); + if (get_data("vDV?", temp)) return; + dstate_setinfo("ups.firmware", "%s", temp + 3); + upsdebugx(1, "ups.firmware >%s<>%s<\n", temp, temp + 3); /* ups.temperature */ if (exist_ups_temperature) { - if (get_data("vBT?",temp)) return; - dstate_setinfo("ups.temperature", "%s", temp+3); + if (get_data("vBT?", temp)) return; + dstate_setinfo("ups.temperature", "%s", temp + 3); } - upsdebugx(1, "ups.temperature >%s<>%s<\n",temp,temp+3); + upsdebugx(1, "ups.temperature >%s<>%s<\n", temp, temp + 3); /* ups.load */ - if (get_data("vO0L?",temp)) return; - dstate_setinfo("ups.load", "%s", temp+4); - upsdebugx(1, "ups.load >%s<>%s<\n",temp,temp+4); + if (get_data("vO0L?", temp)) return; + dstate_setinfo("ups.load", "%s", temp + 4); + upsdebugx(1, "ups.load >%s<>%s<\n", temp, temp + 4); /* ups protocol */ /* - if (get_data("vDC?",temp)) return; - dstate_setinfo("ups.protocol", "%s", temp+3; - upsdebugx(1, "ups.protocol >%s<>%s<\n",temp,temp+3; + if (get_data("vDC?", temp)) return; + dstate_setinfo("ups.protocol", "%s", temp + 3; + upsdebugx(1, "ups.protocol >%s<>%s<\n", temp, temp + 3; */ /************** input.x *****************/ /* input.voltage */ - if (get_data("vI0U?",temp)) return; - dstate_setinfo("input.voltage", "%s", temp+4); - upsdebugx(1, "input.voltage >%s<>%s<\n",temp,temp+4); + if (get_data("vI0U?", temp)) return; + dstate_setinfo("input.voltage", "%s", temp + 4); + upsdebugx(1, "input.voltage >%s<>%s<\n", temp, temp + 4); /* input.transfer.low */ - if (get_data("vFi?",temp)) return; - dstate_setinfo("input.transfer.low", "%s", temp+3); - upsdebugx(1, "input.transfer.low >%s<>%s<\n",temp,temp+3); + if (get_data("vFi?", temp)) return; + dstate_setinfo("input.transfer.low", "%s", temp + 3); + upsdebugx(1, "input.transfer.low >%s<>%s<\n", temp, temp + 3); /* input.transfer.high */ - if (get_data("vFj?",temp)) return; - dstate_setinfo("input.transfer.high", "%s", temp+3); - upsdebugx(1, "input.transfer.high >%s<>%s<\n",temp,temp+3); + if (get_data("vFj?", temp)) return; + dstate_setinfo("input.transfer.high", "%s", temp + 3); + upsdebugx(1, "input.transfer.high >%s<>%s<\n", temp, temp + 3); /* input.frequency */ - if (get_data("vI0f?",temp)) return; - dstate_setinfo("input.frequency", "%2.1f", atof(temp+4) / 10.0); - upsdebugx(1, "input.frequency >%s<>%s<\n",temp,temp+4); + if (get_data("vI0f?", temp)) return; + dstate_setinfo("input.frequency", "%2.1f", atof(temp + 4) / 10.0); + upsdebugx(1, "input.frequency >%s<>%s<\n", temp, temp + 4); /*************** output.x ********************************/ /* output.voltage */ - if (get_data("vO0U?",temp)) return; - dstate_setinfo("output.voltage", "%s", temp+4); - upsdebugx(1, "output.voltage >%s<>%s<\n",temp,temp+4); + if (get_data("vO0U?", temp)) return; + dstate_setinfo("output.voltage", "%s", temp + 4); + upsdebugx(1, "output.voltage >%s<>%s<\n", temp, temp + 4); /* output.frequency */ - if (get_data("vOf?",temp)) return; - dstate_setinfo("output.frequency", "%2.1f", atof(temp+3) / 10.0); - upsdebugx(1, "output.frequency >%s<>%s<\n",temp,temp+3); + if (get_data("vOf?", temp)) return; + dstate_setinfo("output.frequency", "%2.1f", atof(temp + 3) / 10.0); + upsdebugx(1, "output.frequency >%s<>%s<\n", temp, temp + 3); /* output.current */ if (exist_output_current) { - if (get_data("vO0I?",temp)) return; - dstate_setinfo("output.current", "%2.1f", atof(temp+4) / 10.0); + if (get_data("vO0I?", temp)) return; + dstate_setinfo("output.current", "%2.1f", atof(temp + 4) / 10.0); } - upsdebugx(1, "output.current >%s<>%s<\n",temp,temp+4); + upsdebugx(1, "output.current >%s<>%s<\n", temp, temp + 4); /*************** battery.x *******************************/ @@ -465,43 +480,43 @@ void upsdrv_updateinfo(void) /* battery charge */ if (exist_battery_charge) { - if (get_data("vBC?",temp)) return; - dstate_setinfo("battery.charge", "%s", temp+3); + if (get_data("vBC?", temp)) return; + dstate_setinfo("battery.charge", "%s", temp + 3); } - upsdebugx(1, "battery.charge >%s<>%s<\n",temp,temp+3); + upsdebugx(1, "battery.charge >%s<>%s<\n", temp, temp + 3); /* battery.voltage */ - if (get_data("vBU?",temp)) return; - dstate_setinfo("battery.voltage", "%2.1f", atof(temp+3) / 10.0); - upsdebugx(1, "battery.voltage >%s<>%s<\n",temp,temp+3); + if (get_data("vBU?", temp)) return; + dstate_setinfo("battery.voltage", "%2.1f", atof(temp + 3) / 10.0); + upsdebugx(1, "battery.voltage >%s<>%s<\n", temp, temp + 3); /* battery.current */ if (exist_battery_current) { - if (get_data("vBI?",temp)) return; - dstate_setinfo("battery.current", "%2.1f", atof(temp+3) / 10.0); + if (get_data("vBI?", temp)) return; + dstate_setinfo("battery.current", "%2.1f", atof(temp + 3) / 10.0); } - upsdebugx(1, "battery.current >%s<>%s<\n",temp,temp+3); + upsdebugx(1, "battery.current >%s<>%s<\n", temp, temp + 3); /* battery.temperature */ if (exist_battery_temperature) { - if (get_data("vBT?",temp)) return; - dstate_setinfo("battery.temperature", "%s", temp+3); + if (get_data("vBT?", temp)) return; + dstate_setinfo("battery.temperature", "%s", temp + 3); } - upsdebugx(1, "battery.temperature >%s<>%s<\n",temp,temp+3); + upsdebugx(1, "battery.temperature >%s<>%s<\n", temp, temp + 3); /* battery.runtime */ if (exist_battery_runtime) { - if (get_data("vBt?",temp)) return; - runtime_sec = strtol(temp+3, NULL, 10)*60; + if (get_data("vBt?", temp)) return; + runtime_sec = strtol(temp + 3, NULL, 10)*60; snprintf(temp, sizeof(temp), "%ld", runtime_sec); dstate_setinfo("battery.runtime", "%s", temp); } - upsdebugx(1, "battery.runtime >%s<>%ld<\n",temp,runtime_sec); + upsdebugx(1, "battery.runtime >%s<>%ld<\n", temp, runtime_sec); dstate_dataok(); } @@ -521,6 +536,11 @@ void upsdrv_help(void) { } +/* optionally tweak prognames[] entries */ +void upsdrv_tweak_prognames(void) +{ +} + /* list flags and values that you want to receive via -x */ void upsdrv_makevartable(void) { @@ -530,40 +550,23 @@ void upsdrv_makevartable(void) void upsdrv_initups(void) { - char temp[ LENGTH_TEMP ], *usd = NULL; /* = NULL je dulezite jen pro prekladac */ - + char *usd = NULL; /* = NULL is important for compiler */ upsfd = ser_open(device_path); ser_set_speed(upsfd, device_path, B1200); - if ((usd = getval("usd"))) { sdwdelay=atoi(usd); - upsdebugx(1, "(-x) Delay before shutdown %i",sdwdelay); + upsdebugx(1, "(-x) Delay before shutdown %i", sdwdelay); } if ((model_name = getval("modelname"))) { - /* kdyz modelname nebylo zadano je vraceno NULL*/ - upsdebugx(1, "(-x) UPS Name %s",model_name); + /* if modelname were not specified, NULL is returned */ + upsdebugx(1, "(-x) UPS Name %s", model_name); } - /* inicializace a synchronizace UPS */ - - ser_send_char(upsfd, ENDCHAR); - usleep (UPS_LONG_DELAY); - ser_send(upsfd, "?%c", ENDCHAR); - usleep (UPS_LONG_DELAY); - ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS, 3, 0); - ser_send(upsfd, "?%c", ENDCHAR); - usleep (UPS_LONG_DELAY); - ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS, 3, 0); - ser_send(upsfd, "?%c", ENDCHAR); - usleep (UPS_DELAY); - ser_get_line(upsfd, temp, sizeof(temp), ENDCHAR, IGNCHARS, 3, 0); - - /* the upsh handlers can't be done here, as they get initialized * shortly after upsdrv_initups returns to main. */ diff --git a/include/Makefile.am b/include/Makefile.am index 0dbc365801..f2224e8c2e 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -12,7 +12,7 @@ include_HEADERS = dist_noinst_HEADERS = \ attribute.h common.h extstate.h proto.h \ - state.h str.h timehead.h upsconf.h \ + state.h str.h strjson.h timehead.h upsconf.h \ nut_bool.h nut_float.h nut_stdint.h nut_platform.h \ dmf.h alist.h dmfsnmp.h dmfcore.h \ wincompat.h @@ -43,6 +43,8 @@ MAINTAINERCLEANFILES = Makefile.in .dirstamp nut_version.h: @FORCE_NUT_VERSION@ @echo " GENERATE-HEADER $@" ; \ RES=0; \ + GREP="$(GREP)"; EGREP="$(EGREP)"; \ + export GREP; export EGREP; \ 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_IS_PRERELEASE="`NUT_VERSION_QUERY=IS_PRERELEASE $(top_srcdir)/tools/gitlog2version.sh 2>/dev/null`" || GITREV_IS_PRERELEASE="false"; \ diff --git a/include/common.h b/include/common.h index 47182854f8..c3ebfcdf81 100644 --- a/include/common.h +++ b/include/common.h @@ -105,6 +105,7 @@ #include "attribute.h" #include "proto.h" #include "str.h" +#include "nut_stdint.h" #if (defined HAVE_LIBREGEX && HAVE_LIBREGEX) # include @@ -273,6 +274,18 @@ void open_syslog(const char *progname); /* close ttys and become a daemon */ void background(void); +/* allow tagging the (forked) process in logs to ease debugging */ +const char *getproctag(void); +/* save a copy of tag, or call with NULL to clean and free the internal buffer; + * if using this feature in a particular NUT program at all - it automatically + * registers with atexit() to do such clean-up in exit handling. + * + * WARNING: first call to this method also caches the getprocname(getpid()) + * so if you want to see debug logs from that - only call this after setting + * the nut_debug_level (by parsing CLI arguments and/or NUT_DEBUG_LEVEL envvar). + */ +void setproctag(const char *tag); + /* do this here to keep pwd/grp stuff out of the main files */ struct passwd *get_user_pwent(const char *name); @@ -309,14 +322,20 @@ size_t parseprogbasename(char *buf, size_t buflen, const char *progname, size_t * 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 + * Generally speaking, if (checkprocname(...)) then ok to proceed. + * Singular for programs with a single expected executable name, + * plural for programs with expected aliases (e.g. "old"/new" migrations). */ 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 checkprocnames(pid_t pid, const char **prognames); +/* compareprocname*() methods do the bulk of work for checkprocname*() + * and return same values. The "pid" argument is used for logging. + * Generally speaking, if (compareprocname(...)) then ok to proceed. + * Singular for programs with a single expected executable name, + * plural for programs with expected aliases (e.g. "old"/new" migrations). */ int compareprocname(pid_t pid, const char *procname, const char *progname); +int compareprocnames(pid_t pid, const char *procname, const char **prognames); /* Helper for the above methods and some others. If it returns true (1), * work about PID-name comparison should be quickly skipped. */ @@ -376,6 +395,7 @@ 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, const char *progname, int check_current_progname); +int sendsignalpidaliases(pid_t pid, int sig, const char **prognames, int check_current_progname); /* open and get the pid * returns zero or more for successfully retrieved value, @@ -400,11 +420,15 @@ pid_t parsepidfile(const char *pidfn); * named driver programs does not request it) */ int sendsignalfn(const char *pidfn, int sig, const char *progname, int check_current_progname); +int sendsignalfnaliases(const char *pidfn, int sig, const char **prognames, int check_current_progname); #else /* WIN32 */ /* No progname here - communications via named pipe */ int sendsignalfn(const char *pidfn, const char * sig, const char *progname_ignored, int check_current_progname_ignored); +int sendsignalfnaliases(const char *pidfn, const char * sig, const char **prognames_ignored, int check_current_progname_ignored); #endif /* WIN32 */ +/* return a pointer to character inside the file that starts a basename + * caller should strdup() a copy to retain beyond the lifetime of "file" */ const char *xbasename(const char *file); /* enable writing upslog_with_errno() and upslogx() type messages to @@ -470,13 +494,31 @@ typedef enum eupsnotify_state { NOTIFY_STATE_RELOADING, NOTIFY_STATE_STOPPING, NOTIFY_STATE_STATUS, /* Send a text message per "fmt" below */ - NOTIFY_STATE_WATCHDOG /* Ping the framework that we are still alive */ + NOTIFY_STATE_WATCHDOG, /* Ping the framework that we are still alive */ + NOTIFY_STATE_EXTEND_TIMEOUT /* Ping the framework that we are still alive when starting/stopping */ } upsnotify_state_t; const char *str_upsnotify_state(upsnotify_state_t state); /* Note: here fmt may be null, then the STATUS message would not be sent/added */ int upsnotify(upsnotify_state_t state, const char *fmt, ...) __attribute__ ((__format__ (__printf__, 2, 3))); +/* Exposed to code consumers (NUT daemons) for NOTIFY_STATE_EXTEND_TIMEOUT + * By default upsnotify_extend_timeout_usec == 0 so the + * upsnotify() method would fall back to current WATCHDOG_USEC + * if available, or to upsnotify_extend_timeout_usec_default. + * NOTE: It seems that internally in systemd, UINT64_MAX or + * ((uint64_t)-1) means "infinity". Internally systemd uses + * uint64_t as their usec_t (at least currently) but this + * does not seem to be a public API/contract. De-facto the + * value did not have any effect; however INT64_MAX did work + * (presumably as almost 300K years, did not check that long). + * More at https://github.com/systemd/systemd/issues/39535 + * Whatever value gets applied, it should exceed the relevant + * loop cycle duration at that point in daemon life time. + */ +#define UPSNOTIFY_EXTEND_TIMEOUT_USEC_INFINITY ((uint64_t)INT64_MAX) +extern uint64_t upsnotify_extend_timeout_usec_default, upsnotify_extend_timeout_usec; + /* upslog*() messages are sent to syslog always; * their life after that is out of NUT's control */ void upslog_with_errno(int priority, const char *fmt, ...) diff --git a/include/nutconf.hpp b/include/nutconf.hpp index c41c06dadd..710bd56d02 100644 --- a/include/nutconf.hpp +++ b/include/nutconf.hpp @@ -1552,6 +1552,8 @@ class UpsmonConfiguration : public Serialisable NOTIFY_BOOST, NOTIFY_NOTBOOST, + NOTIFY_SHUTDOWN_HOSTSYNC, + NOTIFY_OTHER = 28, NOTIFY_NOTOTHER, diff --git a/include/state.h b/include/state.h index 9857e4d584..f9bad7e5b3 100644 --- a/include/state.h +++ b/include/state.h @@ -39,6 +39,8 @@ typedef struct timespec st_tree_timespec_t; #else typedef struct timeval st_tree_timespec_t; #endif +/* Absorb the build-time variation */ +double difftime_st_tree_timespec(st_tree_timespec_t finish, st_tree_timespec_t start); typedef struct st_tree_s { char *var; diff --git a/include/strjson.h b/include/strjson.h new file mode 100644 index 0000000000..8d7fc58871 --- /dev/null +++ b/include/strjson.h @@ -0,0 +1,32 @@ +/* strjson.h - common JSON string formatting helper + + 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. +*/ + +#ifndef NUT_STRJSON_H_SEEN +#define NUT_STRJSON_H_SEEN 1 + +#ifdef __cplusplus +/* *INDENT-OFF* */ +extern "C" { +/* *INDENT-ON* */ +#endif + +/** + * @brief Helper function to print a string to stdout as a JSON-safe string. + * This function prints the escaped string *without* surrounding quotes. + * @param in The raw C-string to escape and print. + */ +void json_print_esc(const char *in); + +#ifdef __cplusplus +/* *INDENT-OFF* */ +} +/* *INDENT-ON* */ +#endif + +#endif /* NUT_STRJSON_H_SEEN */ + diff --git a/lib/.gitignore b/lib/.gitignore index b99ecfc5f0..5fcea82ecc 100644 --- a/lib/.gitignore +++ b/lib/.gitignore @@ -4,4 +4,3 @@ /libnutscan.pc /libupsclient-config /libupsclient.pc -/README diff --git a/lib/libnutclient.pc.in b/lib/libnutclient.pc.in index 964c24f063..d4629efbe3 100644 --- a/lib/libnutclient.pc.in +++ b/lib/libnutclient.pc.in @@ -2,7 +2,7 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ -sysconfdir=@CONFPATH@ +confdir=@CONFPATH@ statepath=@STATEPATH@ nutuser=@RUN_AS_USER@ diff --git a/lib/libnutclientstub.pc.in b/lib/libnutclientstub.pc.in index e17fca3600..06b57a98f0 100644 --- a/lib/libnutclientstub.pc.in +++ b/lib/libnutclientstub.pc.in @@ -2,7 +2,7 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ -sysconfdir=@CONFPATH@ +confdir=@CONFPATH@ statepath=@STATEPATH@ nutuser=@RUN_AS_USER@ diff --git a/lib/libnutconf.pc.in b/lib/libnutconf.pc.in index fbda516275..5f7d4b0d1a 100644 --- a/lib/libnutconf.pc.in +++ b/lib/libnutconf.pc.in @@ -2,7 +2,7 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ -sysconfdir=@CONFPATH@ +confdir=@CONFPATH@ statepath=@STATEPATH@ nutuser=@RUN_AS_USER@ diff --git a/lib/libnutscan.pc.in b/lib/libnutscan.pc.in index a13a99be76..2c50634dfe 100644 --- a/lib/libnutscan.pc.in +++ b/lib/libnutscan.pc.in @@ -2,7 +2,7 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ -sysconfdir=@CONFPATH@ +confdir=@CONFPATH@ statepath=@STATEPATH@ nutuser=@RUN_AS_USER@ diff --git a/lib/libupsclient-config.in b/lib/libupsclient-config.in index 408574562a..089c8bf8dd 100644 --- a/lib/libupsclient-config.in +++ b/lib/libupsclient-config.in @@ -4,7 +4,7 @@ # **********************************************************# # Copyright 2003 - Arnaud Quette # # Distributed under the GNU GPL v2+ # -# See the distribution lib/README for usage information # +# See the distribution lib/README.adoc for usage information# # **********************************************************# # Note: PACKAGE_VERSION is baked into configure script used @@ -20,7 +20,7 @@ datadir="@datadir@" libexecdir="@libexecdir@" libdir="@libdir@" includedir="@includedir@" -sysconfdir="@sysconfdir@" +confdir="@CONFPATH@" Libs="-L@libdir@ @LDFLAGS_NUT_RPATH@ -lupsclient @LIBSSL_LIBS@" Cflags="-I@includedir@ @LIBSSL_CFLAGS@" ConfigFlags='@CONFIG_FLAGS@' diff --git a/lib/libupsclient.pc.in b/lib/libupsclient.pc.in index d522cdf306..7891f4c3af 100644 --- a/lib/libupsclient.pc.in +++ b/lib/libupsclient.pc.in @@ -2,7 +2,7 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ -sysconfdir=@CONFPATH@ +confdir=@CONFPATH@ statepath=@STATEPATH@ nutuser=@RUN_AS_USER@ diff --git a/m4/ax_check_compile_flag.m4 b/m4/ax_check_compile_flag.m4 index 9316a72448..a58dd3836e 100644 --- a/m4/ax_check_compile_flag.m4 +++ b/m4/ax_check_compile_flag.m4 @@ -52,7 +52,7 @@ AC_CACHE_CHECK([whether _AC_LANG compiler accepts $1], CACHEVAR, [ [dnl Toolkit per se did not return an error code; but did it complain? dnl Below are a few strings typical for some versions of GCC and CLANG dnl This relies on AC_COMPILE_IFELSE implementation retaining conftest.err - AS_IF([grep -E '(unrecognized.* option|did you mean|unknown argument:)' < conftest.err >/dev/null 2>/dev/null], + AS_IF([${EGREP} '(unrecognized.* option|did you mean|unknown argument:)' < conftest.err >/dev/null 2>/dev/null], [AS_VAR_SET(CACHEVAR,[no])],dnl Hit a complaint, flag is not supported after all [AS_VAR_SET(CACHEVAR,[yes])])], [AS_VAR_SET(CACHEVAR,[no])]) diff --git a/m4/ax_lua.m4 b/m4/ax_lua.m4 index 9920c83451..2ca6c32ce9 100644 --- a/m4/ax_lua.m4 +++ b/m4/ax_lua.m4 @@ -202,7 +202,7 @@ AC_DEFUN([AX_PROG_LUA], m4_define_default([_AX_LUA_INTERPRETER_LIST], [lua lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua50])], [ dnl Order preferred version first (e.g. avoid header version mismatch later) - _LUA_NODOT="`echo "$1" | sed 's,\.,,'`" + _LUA_NODOT="`echo \"$1\" | sed 's,\.,,'`" AS_IF([test x"$1" = x"${_LUA_NODOT}"], [_LUA_NODOT=''], dnl no-op [_LUA_NODOT="lua${_LUA_NODOT}"]) dnl Retry without the dot in version diff --git a/m4/ax_realpath.m4 b/m4/ax_realpath.m4 index 6b8cdbc642..e190a08924 100644 --- a/m4/ax_realpath.m4 +++ b/m4/ax_realpath.m4 @@ -26,29 +26,29 @@ AC_DEFUN([AX_REALPATH_SHELL_ONELEVEL], TGT="$1" while test -h "$TGT" ; do - LS_OUT="`ls -ld "$TGT"`" || { RESOLVE_ERROR=$? ; break ; } - LINK="`expr "$LS_OUT" : '.*-> \(.*\)$'`" || { RESOLVE_ERROR=$? ; break ; } + LS_OUT="`ls -ld \"$TGT\"`" || { RESOLVE_ERROR=$? ; break ; } + LINK="`expr \"$LS_OUT\" : '.*-> \(.*\)$'`" || { RESOLVE_ERROR=$? ; break ; } if expr "$LINK" : '/.*' > /dev/null; then TGT="$LINK" else - TGT="`dirname "$TGT"`/$LINK" + TGT="`dirname \"$TGT\"`/$LINK" fi done if test "$RESOLVE_ERROR" = 0 ; then - TGTDIR="`dirname "$TGT"`" && \ - TGTDIR="`cd "$TGTDIR" && pwd`" || { - TGTDIR="`dirname "$TGT"`" || \ + TGTDIR="`dirname \"$TGT\"`" && \ + TGTDIR="`cd \"$TGTDIR\" && pwd`" || { + TGTDIR="`dirname \"$TGT\"`" || \ RESOLVE_ERROR=$? ; } if test "$RESOLVE_ERROR" = 0 ; then while test -h "$TGTDIR" ; do - LS_OUT="`ls -ld "$TGTDIR"`" || { RESOLVE_ERROR=$? ; break ; } - LINK="`expr "$LS_OUT" : '.*-> \(.*\)$'`" || { RESOLVE_ERROR=$? ; break ; } + LS_OUT="`ls -ld \"$TGTDIR\"`" || { RESOLVE_ERROR=$? ; break ; } + LINK="`expr \"$LS_OUT\" : '.*-> \(.*\)$'`" || { RESOLVE_ERROR=$? ; break ; } if expr "$LINK" : '/.*' > /dev/null; then TGTDIR="$LINK" else - PARENTDIR="`dirname "$TGTDIR"`" + PARENTDIR="`dirname \"$TGTDIR\"`" case "$PARENTDIR" in /) TGTDIR="/$LINK" ; break ;; *) TGTDIR="$PARENTDIR/$LINK" ;; @@ -93,11 +93,11 @@ AC_DEFUN([AX_REALPATH_SHELL_RECURSIVE], if test x"$RESOLVE_ERROR" = x0 ; then dnl Recurse to check the (grand)parent dir (if any) if test -n "$RESOLVE_SUFFIX" ; then - RESOLVE_SUFFIX="`basename "$RESOLVE_PREFIX"`/$RESOLVE_SUFFIX" + RESOLVE_SUFFIX="`basename \"$RESOLVE_PREFIX\"`/$RESOLVE_SUFFIX" else - RESOLVE_SUFFIX="`basename "$RESOLVE_PREFIX"`" + RESOLVE_SUFFIX="`basename \"$RESOLVE_PREFIX\"`" fi - RESOLVE_PREFIX="`dirname "$RESOLVE_PREFIX"`" + RESOLVE_PREFIX="`dirname \"$RESOLVE_PREFIX\"`" else dnl Bail out, keep latest answer break @@ -132,14 +132,14 @@ AC_DEFUN([AX_REALPATH], REALPRG="" AS_IF([test -n "$REALPATH"], [ - REALPRG="`${REALPATH} "$1"`" + REALPRG="`${REALPATH} \"$1\"`" ]) AS_IF([test -z "$REALPRG"], [ RESOLVE_ERROR=0 dnl Note: not all "test" implementations have "-e", so got fallbacks: - AS_IF([test -e "$1" || test -f "$1" || test -s "$1" || test -d "$1" || test -L "$1" || test -h "$1" || test -c "$1" || test -b "$1" || test -p "$1"], + AS_IF([(test -e "$1") >/dev/null || test -f "$1" || test -s "$1" || test -d "$1" || test -L "$1" || test -h "$1" || test -c "$1" || test -b "$1" || test -p "$1"], [], [ AC_MSG_WARN([Path name '$1' not found (absent or access to ancestor directories denied)]) dnl We can still try to resolve, e.g. to find @@ -174,7 +174,7 @@ AC_DEFUN([UNITTEST_AX_REALPATH], [ AC_MSG_NOTICE([======= starting UNITTEST for REALPATH macro]) AC_MSG_NOTICE([=== Testing macro for realpath; .../q/x are directories, qwe is a file inside, and .../Q is a symlink to .../q]) - TESTDIR="`mktemp -d`" && test -d "$TESTDIR" && test -w "$TESTDIR" || TESTDIR="/tmp" + TESTDIR="`${MKTEMP} -d`" && test -d "$TESTDIR" && test -w "$TESTDIR" || TESTDIR="/tmp" rm -rf "$TESTDIR"/q ; mkdir -p "$TESTDIR"/q/x ; echo qwe > "$TESTDIR"/q/x/qwe ; ln -fs q "$TESTDIR"/Q dnl Do not quote TESTDIR in macro calls below, shell quotes are added in implem AC_MSG_NOTICE([=======]) diff --git a/m4/ax_realpath_lib.m4 b/m4/ax_realpath_lib.m4 index 7c72dae28f..8ed8775a48 100644 --- a/m4/ax_realpath_lib.m4 +++ b/m4/ax_realpath_lib.m4 @@ -48,7 +48,7 @@ AC_DEFUN([AX_REALPATH_LIB], AS_IF([test "x$GCC" = xyes -o "x$CLANGCC" = xyes], [ myLIBNAME="$1" AS_CASE(["${myLIBNAME}"], - [-l*], [myLIBNAME="`echo "$myLIBNAME" | sed 's/^-l/lib/'`"] + [-l*], [myLIBNAME="`echo \"$myLIBNAME\" | sed 's/^-l/lib/'`"] ) dnl # Primarily we care to know dynamically linked (shared object) @@ -71,7 +71,7 @@ AC_DEFUN([AX_REALPATH_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="`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... ) @@ -88,16 +88,16 @@ AC_DEFUN([AX_REALPATH_LIB], 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`" \ + { 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`" \ + || { 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="" + myLIBPATH="`$DLLTOOL -I \"${myLIBPATH}\"`" || myLIBPATH="" ] ) @@ -112,7 +112,7 @@ AC_DEFUN([AX_REALPATH_LIB], "${MSYSTEM_PREFIX}/lib" \ "${MINGW_PREFIX}/bin" \ "${MINGW_PREFIX}/lib" \ - `${CC} --print-search-dirs 2>/dev/null | grep libraries: | sed 's,^@<:@^=@:>@*=,:,' | sed 's,\(@<:@:;@:>@\)\(@<:@A-Z@:>@\):/,:/\2/,g' | tr ':' '\n'` \ + `${CC} --print-search-dirs 2>/dev/null | ${GREP} libraries: | sed 's,^@<:@^=@:>@*=,:,' | sed 's,\(@<:@:;@:>@\)\(@<:@A-Z@:>@\):/,:/\2/,g' | tr ':' '\n'` \ ; do dnl NOTE: Here we check myLIBPATH detected above, dnl in fallback below we would retry with a myLIBNAME @@ -126,9 +126,9 @@ AC_DEFUN([AX_REALPATH_LIB], ) ]) ], [ 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="`${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="" ] ) @@ -137,11 +137,11 @@ AC_DEFUN([AX_REALPATH_LIB], for TOKEN in $CFLAGS $LDFLAGS $LIBS ; do D="" case "$TOKEN" in - -R*|-L*) D="`echo "$TOKEN" | sed 's,^-[RL],,'`" ;; - -Wl,-R*) D="`echo "$TOKEN" | sed 's,^-Wl\,-R,,'`" ;; - -Wl,-rpath,*) D="`echo "$TOKEN" | sed 's,^-Wl\,-rpath\,,,'`" ;; + -R*|-L*) D="`echo \"$TOKEN\" | sed 's,^-[RL],,'`" ;; + -Wl,-R*) D="`echo \"$TOKEN\" | sed 's,^-Wl\,-R,,'`" ;; + -Wl,-rpath,*) D="`echo \"$TOKEN\" | sed 's,^-Wl\,-rpath\,,,'`" ;; esac - if test -z "$D" || ! test -d "$D" ; then continue ; fi + if test -z "$D" || test ! -d "$D" ; then continue ; fi if test -s "$D/${myLIBNAME}" 2>/dev/null ; then myLIBPATH="$D/${myLIBNAME}" break @@ -154,7 +154,7 @@ AC_DEFUN([AX_REALPATH_LIB], for D in \ "/usr/${target}/bin" \ "/usr/${target}/lib" \ - `${CC} --print-search-dirs 2>/dev/null | grep libraries: | sed 's,^@<:@^=@:>@*=,:,' | sed 's,\(@<:@:;@:>@\)\(@<:@A-Z@:>@\):/,:/\2/,g' | tr ':' '\n'` \ + `${CC} --print-search-dirs 2>/dev/null | ${GREP} libraries: | sed 's,^@<:@^=@:>@*=,:,' | sed 's,\(@<:@:;@:>@\)\(@<:@A-Z@:>@\):/,:/\2/,g' | tr ':' '\n'` \ ; do if test -s "$D/${myLIBNAME}" 2>/dev/null ; then myLIBPATH="$D/${myLIBNAME}" @@ -170,13 +170,13 @@ AC_DEFUN([AX_REALPATH_LIB], 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" ; } \ + { 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="`${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="" @@ -191,7 +191,7 @@ AC_DEFUN([AX_REALPATH_LIB], 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], [ + AS_IF([LANG=C LC_ALL=C file "${myLIBPATH}" | ${EGREP} -i '(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 ) @@ -199,7 +199,7 @@ AC_DEFUN([AX_REALPATH_LIB], # 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,'`" + 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}]) diff --git a/m4/nut_arg_with.m4 b/m4/nut_arg_with.m4 index e85f7ee8ed..10f2516c8f 100644 --- a/m4/nut_arg_with.m4 +++ b/m4/nut_arg_with.m4 @@ -1,21 +1,220 @@ -dnl simplified declaration of some feature options +dnl Simplified declaration of some feature options +dnl Copyright (C) +dnl 2006 Peter Selinger +dnl 2020-2025 Jim Klimov +dnl Common arguments: +dnl $1 option name (part after `--with-`) +dnl $2 help bit in option name (except legacy short funcs) +dnl $2 or $3 help text (descriptive part) +dnl $3 or $4 default value +dnl $4 or $5 how to represent default in help text (for *_CUSTOM_DEFAULT_HELP) +dnl Note that for some reason use of dollar-number gets expanded below, +dnl but any other variable (e.g. $conftemp straight in help string) is not. +dnl In fact, the generated configure script includes the help wall of text +dnl much earlier than it includes lines that manipulate conftemp (unless it +dnl gets somehow escaped to happen earlier), due to m4 diverts in autoconf code. +dnl So we can use the "EXPAND" methods to convert static text, but not to display +dnl any values determined and changed during `configure` shell script run-time. +dnl See research in https://github.com/networkupstools/nut/issues/3049 for more. + +dnl Default expanded once to substitute current variables, +dnl or remove backslashing of verbatim dollars, etc. +dnl NOTE: Not sure if the fuss with uniquely named conftemp is worth it, +dnl or is even remembered between calls. There seems to be 3 calls per name. +AC_DEFUN([NUT_ARG_EXPAND], +[[]m4_esyscmd_s( + [nut_conftemp_]m4_translit([$1], [-], [_])="$2" ; + eval [nut_conftemp_]m4_translit([$1], [-], [_])=\"\${[nut_conftemp_]m4_translit([$1], [-], [_])}\" ; + echo "${[nut_conftemp_]m4_translit([$1], [-], [_])}" ; + dnl # RUNS 3 TIMES # echo "$1 | $2 | [nut_conftemp_]m4_translit([$1], [-], [_])=${[nut_conftemp_]m4_translit([$1], [-], [_])}" >> arg.log ; +)[]]) dnl Working With External Software (might name a variant or other contextual arg) dnl https://www.gnu.org/software/autoconf/manual/autoconf-2.66/html_node/External-Software.html#External-Software +dnl including concepts of the OS as external software (account names, paths...) + +dnl $1 option name (part after `--with-`) +dnl $2 optional "=VALUE" for help tag (after $1) +dnl $3 help text (descriptive part) +dnl $4 default value +dnl $5 how to represent default in help text (for *_CUSTOM_DEFAULT_HELP) +AC_DEFUN([NUT_ARG_WITH_CUSTOM_DEFAULT_HELP], +[ AC_ARG_WITH([$1], + m4_ifval([$2], + [m4_ifval([$5], + [AS_HELP_STRING([--with-$1=$2], [$3 (default: $5)])], + [AS_HELP_STRING([--with-$1=$2], [$3 (default: $4)])])], + [m4_ifval([$5], + [AS_HELP_STRING([--with-$1], [$3 (default: $5)])], + [AS_HELP_STRING([--with-$1], [$3 (default: $4)])])]), + [[nut_with_]m4_translit([$1], [-], [_])="${withval}"], + [[nut_with_]m4_translit([$1], [-], [_])="$4"] + ) +]) + +AC_DEFUN([NUT_ARG_WITH_EXPAND_DEFAULT_HELP], +[ + dnl Note: only 4 args expected + dnl NUT_ARG_EXPAND([$1], $3) + NUT_ARG_WITH_CUSTOM_DEFAULT_HELP([$1], [$2], [$3], [$4], NUT_ARG_EXPAND([with_$1], [$4])) +]) + +AC_DEFUN([NUT_ARG_WITH_EXPAND_DEFAULT_HELP_SINGLEQUOTE], +[ + dnl Variant for paths (likely using backslash-dollar in $4) + dnl Note: only 4 args expected + dnl Note: "resolved from" must be not bracketed + NUT_ARG_WITH_CUSTOM_DEFAULT_HELP([$1], [$2], [$3], [$4], resolved from NUT_ARG_EXPAND([with_$1], '[$4]')) +]) + AC_DEFUN([NUT_ARG_WITH], -[ AC_ARG_WITH($1, - AS_HELP_STRING([--with-$1], [$2 ($3)]), - [[nut_with_]m4_translit($1, [-], [_])="${withval}"], - [[nut_with_]m4_translit($1, [-], [_])="$3"] - ) +[ + dnl Note: historicaly only 3 args were expected + dnl Now a second arg may be injected for optional "=VALUE" for help tag (after $1) + dnl Legacy behavior (for static values): default value + dnl and its help representation are the same (verbatim!) + m4_ifval([$4], + [NUT_ARG_WITH_CUSTOM_DEFAULT_HELP([$1], [$2], [$3], [$4], [$4])], + [NUT_ARG_WITH_CUSTOM_DEFAULT_HELP([$1], [], [$2], [$3], [$3])]) +]) + +dnl Special common case for *-includes and *-libs optional parameters +dnl (bracketed in help text to confer optionality) +dnl Options same as NUT_ARG_WITH (4-arg version) +AC_DEFUN([NUT_ARG_WITH_LIBOPTS], +[ + AC_ARG_WITH([$1], + [AS_HELP_STRING([@<:@--with-$1=$2@:>@], [$3 (default: $4)])], + [[nut_with_]m4_translit([$1], [-], [_])="${withval}"], + [[nut_with_]m4_translit([$1], [-], [_])="$4"] + ) +]) + +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_INVALID_YESNO], +[ + AC_ARG_WITH([$1], + [AS_HELP_STRING([@<:@--with-$1=$2@:>@], [$3 (default: $4)])], + [AS_CASE([${withval}], + [yes|no], [AC_MSG_ERROR([invalid option --with(out)-$1 - see docs/configure.txt])], + [[nut_with_]m4_translit([$1], [-], [_])="${withval}"] + )], + [[nut_with_]m4_translit([$1], [-], [_])="$4"] + ) +]) + +dnl NOTE: The defaulting substituter may be not defined/renamed in some autoconf versions +dnl m4_ifndef([m4_default], [m4_define([m4_default], [m4_ifval([[$1]], [[$1]], [[$2]])])]) + +dnl Just the (normal-cased) third-party project name is required +dnl $1 project name +dnl $2 default value (optional) +dnl $3 project name spelling for help message (very optional) + +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_INCLUDES], +[ + m4_ifval([$2], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-includes], [CFLAGS|auto], [include flags for the ]m4_default([$3], [$1])[ library], [$2])], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-includes], [CFLAGS|auto], [include flags for the ]m4_default([$3], [$1])[ library], [auto])]) +]) + +dnl Technically LIBS and LDFLAGS are about the same for +dnl most of our m4 code and legacy help messaging... so far. +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_LIBS], +[ + m4_ifval([$2], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-libs], [LIBS|auto], [linker flags for the ]m4_default([$3], [$1])[ library], [$2])], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-libs], [LIBS|auto], [linker flags for the ]m4_default([$3], [$1])[ library], [auto])]) ]) -dnl Enable a feature (might name a variant), or yes/no +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_LIBS_AS_LDFLAGS], +[ + m4_ifval([$2], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-libs], [LDFLAGS|auto], [linker flags for the ]m4_default([$3], [$1])[ library], [$2])], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-libs], [LDFLAGS|auto], [linker flags for the ]m4_default([$3], [$1])[ library], [auto])]) +]) + +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_LDFLAGS], +[ + m4_ifval([$2], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-ldflags], [LDFLAGS|auto], [linker flags for the ]m4_default([$3], [$1])[ library], [$2])], + [NUT_ARG_WITH_LIBOPTS_INVALID_YESNO([]m4_translit([$1], 'A-Z', 'a-z')[-ldflags], [LDFLAGS|auto], [linker flags for the ]m4_default([$3], [$1])[ library], [auto])]) +]) + +dnl Help detect legacy -config scripts, assigns "none" if disabled or not found +dnl $1 project name +dnl $2 m4 var to assign +dnl $3 program name(s) to try +dnl $4 default value +dnl $5 project name spelling for help message (very optional) +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM], +[ + dnl By default seek in PATH + NUT_ARG_WITH_LIBOPTS([]m4_translit([$1], 'A-Z', 'a-z')[-config], [/path/to/m4_translit([$1], 'A-Z', 'a-z')-config|auto|none], [path to program that reports ]m4_default([$5], [$1])[ configuration], [$4]) + AS_CASE([[${nut_with_]m4_translit([$1], [-], [_])[_config}]], + [yes|auto|""], [AC_PATH_PROGS([$2], [$3], [none])], + [no|none], [$2="none"], + [$2=["${nut_with_]m4_translit([$1], [-], [_])[_config}"]] + ) +]) + +dnl Part of stack, use or guess optional parameter $2 (m4 var name); pass others as is +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM_2], +[ + m4_ifval([$2], + [NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM([$1], [$2], [$3], [$4], [$5])], + [NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM([$1], m4_translit(m4_translit([$1], 'a-z', 'A-Z'), [-], [_])[_CONFIG], [$3], [$4], [$5])]) +]) + +dnl Part of stack, use or guess optional parameter $3 (prog names); pass others as is +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM_3], +[ + m4_ifval([$3], + [NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM_2([$1], [$2], [$3], [$4], [$5])], + [NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM_2([$1], [$2], []m4_translit([$1], 'A-Z', 'a-z')[-config], [$4], [$5])]) +]) + +dnl Just the (normal-cased) third-party project name is required +dnl $1 project name +dnl $2 m4 var to assign (optional) +dnl $3 program name(s) to try (optional) +dnl $4 default value (optional) +dnl $5 project name spelling for help message (very optional) +AC_DEFUN([NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT], +[ + m4_ifval([$4], + [NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM_3([$1], [$2], [$3], [$4], [$5])], + [NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT_IMPLEM_3([$1], [$2], [$3], [auto], [$5])]) +]) + +dnl Enable a package feature/ability (might name a variant, or yes/no) dnl https://www.gnu.org/software/autoconf/manual/autoconf-2.66/html_node/Package-Options.html +AC_DEFUN([NUT_ARG_ENABLE_CUSTOM_DEFAULT_HELP], +[ AC_ARG_ENABLE([$1], + m4_ifval([$2], + [m4_ifval([$5], + [AS_HELP_STRING([--enable-$1=$2], [$3 (default: $5)])], + [AS_HELP_STRING([--enable-$1=$2], [$3 (default: $4)])])], + [m4_ifval([$5], + [AS_HELP_STRING([--enable-$1], [$3 (default: $5)])], + [AS_HELP_STRING([--enable-$1], [$3 (default: $4)])])]), + [[nut_enable_]m4_translit([$1], [-], [_])="${enableval}"], + [[nut_enable_]m4_translit([$1], [-], [_])="$4"] + ) +]) + +AC_DEFUN([NUT_ARG_ENABLE_EXPAND_DEFAULT_HELP], +[ + NUT_ARG_ENABLE_CUSTOM_DEFAULT_HELP([$1], [$2], [$3], [$4], NUT_ARG_EXPAND([[enable_]$1], [$4])) +]) + +AC_DEFUN([NUT_ARG_ENABLE_EXPAND_DEFAULT_HELP_SINGLEQUOTE], +[ + NUT_ARG_ENABLE_CUSTOM_DEFAULT_HELP([$1], [$2], [$3], [$4], resolved from NUT_ARG_EXPAND([[enable_]$1], '[$4]')) +]) + AC_DEFUN([NUT_ARG_ENABLE], -[ AC_ARG_ENABLE($1, - AS_HELP_STRING([--enable-$1], [$2 ($3)]), - [[nut_enable_]m4_translit($1, [-], [_])="${enableval}"], - [[nut_enable_]m4_translit($1, [-], [_])="$3"] - ) +[ + m4_ifval([$4], + [NUT_ARG_ENABLE_CUSTOM_DEFAULT_HELP([$1], [$2], [$3], [$4], [$4])], + [NUT_ARG_ENABLE_CUSTOM_DEFAULT_HELP([$1], [], [$2], [$3], [$3])]) ]) diff --git a/m4/nut_check_asciidoc.m4 b/m4/nut_check_asciidoc.m4 index 915c7fa2bb..2ed7c3b863 100644 --- a/m4/nut_check_asciidoc.m4 +++ b/m4/nut_check_asciidoc.m4 @@ -71,7 +71,7 @@ if test -z "${nut_have_asciidoc_seen}"; then dnl Some releases also report what flags they were compiled with as dnl part of the version info, so the last-line match finds nothing. dnl Also some builds return version data to stderr. - XMLLINT_VERSION="`${XMLLINT} --version 2>&1 | grep version`" + XMLLINT_VERSION="`${XMLLINT} --version 2>&1 | ${GREP} version`" XMLLINT_VERSION="${XMLLINT_VERSION##* }" fi AC_MSG_RESULT(${XMLLINT_VERSION} found) diff --git a/m4/nut_check_aspell.m4 b/m4/nut_check_aspell.m4 index 4515821add..73b31cf00b 100644 --- a/m4/nut_check_aspell.m4 +++ b/m4/nut_check_aspell.m4 @@ -33,7 +33,7 @@ if test -z "${nut_have_aspell_seen}"; then ASPELL_VERSION="`LANG=C LC_ALL=C ${ASPELL} --version 2>/dev/null | sed -e 's,^.*@<:@Aa@:>@spell \(@<:@0-9.@:>@*\),\1,' -e 's,@<:@^0-9.@:>@.*,,'`" || ASPELL_VERSION="none" AC_MSG_RESULT([${ASPELL_VERSION}]) - ASPELL_VERSION_MINMAJ="`echo "${ASPELL_VERSION}" | sed 's,\.@<:@0-9@:>@@<:@0-9@:>@*$,,'`" + ASPELL_VERSION_MINMAJ="`echo \"${ASPELL_VERSION}\" | sed 's,\.@<:@0-9@:>@@<:@0-9@:>@*$,,'`" dnl FIXME: Some systems have more complicated layouts, e.g. dnl /usr/lib/amd64/aspell-0.60/tex-filter.so @@ -43,18 +43,18 @@ if test -z "${nut_have_aspell_seen}"; then dnl fallback to built-in paths below if this initial guesswork dnl failed. This may need some more-direct addressing later. AC_MSG_CHECKING([for aspell "lib" filtering resources directory]) - ASPELL_BINDIR="`dirname "$ASPELL"`" + ASPELL_BINDIR="`dirname \"$ASPELL\"`" if test -d "${ASPELL_BINDIR}/../lib" ; then if test x"${ASPELL_VERSION}" != x"none" && test -d "${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION}" ; then - ASPELL_FILTER_LIB_PATH="`cd "${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION}" && pwd`" \ + ASPELL_FILTER_LIB_PATH="`cd \"${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION}\" && pwd`" \ || ASPELL_FILTER_LIB_PATH="${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION}" else if test x"${ASPELL_VERSION_MINMAJ}" != x"none" && test -d "${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION_MINMAJ}" ; then - ASPELL_FILTER_LIB_PATH="`cd "${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION_MINMAJ}" && pwd`" \ + ASPELL_FILTER_LIB_PATH="`cd \"${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION_MINMAJ}\" && pwd`" \ || ASPELL_FILTER_LIB_PATH="${ASPELL_BINDIR}/../lib/aspell-${ASPELL_VERSION_MINMAJ}" else if test -d "${ASPELL_BINDIR}/../lib/aspell" ; then - ASPELL_FILTER_LIB_PATH="`cd "${ASPELL_BINDIR}/../lib/aspell" && pwd`" \ + ASPELL_FILTER_LIB_PATH="`cd \"${ASPELL_BINDIR}/../lib/aspell\" && pwd`" \ || ASPELL_FILTER_LIB_PATH="${ASPELL_BINDIR}/../lib/aspell" fi fi @@ -63,18 +63,18 @@ if test -z "${nut_have_aspell_seen}"; then AC_MSG_RESULT([${ASPELL_FILTER_LIB_PATH}]) AC_MSG_CHECKING([for aspell "share" filtering resources directory]) - ASPELL_BINDIR="`dirname "$ASPELL"`" + ASPELL_BINDIR="`dirname \"$ASPELL\"`" if test -d "${ASPELL_BINDIR}/../share" ; then if test x"${ASPELL_VERSION}" != x"none" && test -d "${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION}" ; then - ASPELL_FILTER_SHARE_PATH="`cd "${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION}" && pwd`" \ + ASPELL_FILTER_SHARE_PATH="`cd \"${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION}\" && pwd`" \ || ASPELL_FILTER_SHARE_PATH="${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION}" else if test x"${ASPELL_VERSION_MINMAJ}" != x"none" && test -d "${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION_MINMAJ}" ; then - ASPELL_FILTER_SHARE_PATH="`cd "${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION_MINMAJ}" && pwd`" \ + ASPELL_FILTER_SHARE_PATH="`cd \"${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION_MINMAJ}\" && pwd`" \ || ASPELL_FILTER_SHARE_PATH="${ASPELL_BINDIR}/../share/aspell-${ASPELL_VERSION_MINMAJ}" else if test -d "${ASPELL_BINDIR}/../share/aspell" ; then - ASPELL_FILTER_SHARE_PATH="`cd "${ASPELL_BINDIR}/../share/aspell" && pwd`" \ + ASPELL_FILTER_SHARE_PATH="`cd \"${ASPELL_BINDIR}/../share/aspell\" && pwd`" \ || ASPELL_FILTER_SHARE_PATH="${ASPELL_BINDIR}/../share/aspell" fi fi @@ -86,18 +86,18 @@ if test -z "${nut_have_aspell_seen}"; then dnl # May be in a platform-dependent subdir (e.g. Debian Linux) dnl # or not (e.g. MinGW/MSYS2, OpenIndiana): if test -d "${ASPELL_FILTER_LIB_PATH}" ; then - ASPELL_FILTER_TEX_PATH="`find "${ASPELL_FILTER_LIB_PATH}" -name "tex.amf"`" \ + ASPELL_FILTER_TEX_PATH="`find \"${ASPELL_FILTER_LIB_PATH}\" -name \"tex.amf\"`" \ && test x"${ASPELL_FILTER_TEX_PATH}" != x \ - && ASPELL_FILTER_TEX_PATH="`dirname "${ASPELL_FILTER_TEX_PATH}"`" \ + && ASPELL_FILTER_TEX_PATH="`dirname \"${ASPELL_FILTER_TEX_PATH}\"`" \ && test -d "${ASPELL_FILTER_TEX_PATH}" \ || ASPELL_FILTER_TEX_PATH="none" fi dnl # Fallback (e.g. on FreeBSD): if test x"${ASPELL_FILTER_TEX_PATH}" = xnone \ && test -d "${ASPELL_FILTER_SHARE_PATH}" ; then - ASPELL_FILTER_TEX_PATH="`find "${ASPELL_FILTER_SHARE_PATH}" -name "tex.amf"`" \ + ASPELL_FILTER_TEX_PATH="`find \"${ASPELL_FILTER_SHARE_PATH}\" -name \"tex.amf\"`" \ && test x"${ASPELL_FILTER_TEX_PATH}" != x \ - && ASPELL_FILTER_TEX_PATH="`dirname "${ASPELL_FILTER_TEX_PATH}"`" \ + && ASPELL_FILTER_TEX_PATH="`dirname \"${ASPELL_FILTER_TEX_PATH}\"`" \ && test -d "${ASPELL_FILTER_TEX_PATH}" \ || ASPELL_FILTER_TEX_PATH="none" fi @@ -133,8 +133,8 @@ if test -z "${nut_have_aspell_seen}"; then ]) ] ) - out1="`echo test | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS} | grep test`"; res1=$? - out2="`echo qwer | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS} | grep qwer`"; res2=$? + out1="`echo test | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS} | ${GREP} test`"; res1=$? + out2="`echo qwer | eval ${ASPELL} ${ASPELL_NUT_TEXMODE_ARGS} ${ASPELL_NUT_COMMON_ARGS} | ${GREP} qwer`"; res2=$? AS_IF([test x"$out1" = x -a x"$out2" != x], [ AC_MSG_RESULT(yes) nut_have_aspell="yes" diff --git a/m4/nut_check_libavahi.m4 b/m4/nut_check_libavahi.m4 index 0e8782a366..d7ff54b978 100644 --- a/m4/nut_check_libavahi.m4 +++ b/m4/nut_check_libavahi.m4 @@ -15,7 +15,9 @@ if test -z "${nut_have_avahi_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" AS_IF([test x"$have_PKG_CONFIG" = xyes], [dnl See which version of the avahi library (if any) is installed @@ -36,46 +38,36 @@ if test -z "${nut_have_avahi_seen}"; then ) AC_MSG_CHECKING(for avahi cflags) - AC_ARG_WITH(avahi-includes, - AS_HELP_STRING([@<:@--with-avahi-includes=CFLAGS@:>@], [include flags for the avahi library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-avahi-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], [ - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags avahi-core avahi-client 2>/dev/null`" \ - || depCFLAGS="-I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib"], - [depCFLAGS="-I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib"] - )] + NUT_ARG_WITH_LIBOPTS_INCLUDES([avahi], [auto]) + AS_CASE([${nut_with_avahi_includes}], + [auto], [AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depCFLAGS="`$PKG_CONFIG --silence-errors --cflags avahi-core avahi-client 2>/dev/null`" \ + && depCFLAGS_SOURCE="pkg-config" ; } \ + || { depCFLAGS="-I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib" \ + && depCFLAGS_SOURCE="default" ; }], + [depCFLAGS="-I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib" + depCFLAGS_SOURCE="default"] + )], + [depCFLAGS="${nut_with_avahi_includes}" + depCFLAGS_SOURCE="confarg"] ) - AC_MSG_RESULT([${depCFLAGS}]) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for avahi ldflags) - AC_ARG_WITH(avahi-libs, - AS_HELP_STRING([@<:@--with-avahi-libs=LIBS@:>@], [linker flags for the avahi library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-avahi-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], [ - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depLIBS="`$PKG_CONFIG --silence-errors --libs avahi-core avahi-client 2>/dev/null`" \ - || depLIBS="-lavahi-core -lavahi-client"], - [depLIBS="-lavahi-core -lavahi-client"] - )] + NUT_ARG_WITH_LIBOPTS_LIBS([avahi], [auto]) + AS_CASE([${nut_with_avahi_libs}], + [auto], [AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depLIBS="`$PKG_CONFIG --silence-errors --libs avahi-core avahi-client 2>/dev/null`" \ + && depLIBS_SOURCE="pkg-config" ; } \ + || { depLIBS="-lavahi-core -lavahi-client" \ + && depLIBS_SOURCE="default" ; }], + [depLIBS="-lavahi-core -lavahi-client" + depLIBS_SOURCE="default"] + )], + [depLIBS="${nut_with_avahi_libs}" + depLIBS_SOURCE="confarg"] ) - AC_MSG_RESULT([${depLIBS}]) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if avahi-core is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -99,7 +91,7 @@ if test -z "${nut_have_avahi_seen}"; then 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"`" + SOFILE_LIBAVAHI="`basename \"$SOPATH_LIBAVAHI\"`" AC_DEFINE_UNQUOTED([SOFILE_LIBAVAHI],["${SOFILE_LIBAVAHI}"],[Base file name of dynamic library on build system]) break ]) @@ -111,6 +103,8 @@ if test -z "${nut_have_avahi_seen}"; then unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libfreeipmi.m4 b/m4/nut_check_libfreeipmi.m4 index 1d563013e1..0b4551ccac 100644 --- a/m4/nut_check_libfreeipmi.m4 +++ b/m4/nut_check_libfreeipmi.m4 @@ -16,7 +16,9 @@ if test -z "${nut_have_libfreeipmi_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" AS_IF([test x"$have_PKG_CONFIG" = xyes], [dnl pkg-config support requires Freeipmi 1.0.5, released on Thu Jun 30 2011 @@ -37,42 +39,34 @@ if test -z "${nut_have_libfreeipmi_seen}"; then AS_IF([test x"$FREEIPMI_VERSION" != xnone], [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libfreeipmi libipmimonitoring 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs libfreeipmi libipmimonitoring 2>/dev/null`" + depCFLAGS_SOURCE="pkg-config" + depLIBS_SOURCE="pkg-config" ], [depCFLAGS="" depLIBS="-lfreeipmi -lipmimonitoring" + depCFLAGS_SOURCE="default" + depLIBS_SOURCE="default" ] ) dnl allow overriding FreeIPMI settings if the user knows best AC_MSG_CHECKING(for FreeIPMI cflags) - AC_ARG_WITH(freeipmi-includes, - AS_HELP_STRING([@<:@--with-freeipmi-includes=CFLAGS@:>@], [include flags for the FreeIPMI library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-freeipmi-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([FreeIPMI], [auto]) + AS_CASE([${nut_with_freeipmi_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_freeipmi_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for FreeIPMI ldflags) - AC_ARG_WITH(freeipmi-libs, - AS_HELP_STRING([@<:@--with-freeipmi-libs=LIBS@:>@], [linker flags for the FreeIPMI library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-freeipmi-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS([FreeIPMI], [auto]) + AS_CASE([${nut_with_freeipmi_libs}], + [auto], [], dnl Keep what we had found above + [depLIBS="${nut_with_freeipmi_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if freeipmi is usable with our current flags CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -94,7 +88,7 @@ if test -z "${nut_have_libfreeipmi_seen}"; then dnl Collect possibly updated dependencies after AC SEARCH LIBS: AS_IF([test x"${LIBS}" != x"${LIBS_ORIG} ${depLIBS}"], [ AS_IF([test x = x"${LIBS_ORIG}"], [depLIBS="$LIBS"], [ - depLIBS="`echo "$LIBS" | sed -e 's|'"${LIBS_ORIG}"'| |' -e 's|^ *||' -e 's| *$||'`" + depLIBS="`echo \"$LIBS\" | sed -e 's|'\"${LIBS_ORIG}\"'| |' -e 's|^ *||' -e 's| *$||'`" ]) ]) @@ -107,13 +101,16 @@ if test -z "${nut_have_libfreeipmi_seen}"; then LIBIPMI_LIBS="${depLIBS}" dnl Help ltdl if we can (nut-scanner etc.) + dnl Note we can have e.g. `-lfreeipmi -lipmimonitoring` with + dnl one including the other, so should try to prefer the + dnl "outer" linked library (libipmimonitoring here). (FIXME!) for TOKEN in $depLIBS ; 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"`" + SOFILE_LIBFREEIPMI="`basename \"$SOPATH_LIBFREEIPMI\"`" AC_DEFINE_UNQUOTED([SOFILE_LIBFREEIPMI],["${SOFILE_LIBFREEIPMI}"],[Base file name of dynamic library on build system]) break ]) @@ -133,6 +130,8 @@ if test -z "${nut_have_libfreeipmi_seen}"; then unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libgd.m4 b/m4/nut_check_libgd.m4 index 74c77f3f66..ebc781ba5c 100644 --- a/m4/nut_check_libgd.m4 +++ b/m4/nut_check_libgd.m4 @@ -16,8 +16,11 @@ if test -z "${nut_have_libgd_seen}"; then LDFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLDFLAGS="" + depLDFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" AS_IF([test x"${nut_enable_configure_debug}" = xyes], [ AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) LIBGD (before): CFLAGS_ORIG="${CFLAGS_ORIG}" CXXFLAGS_ORIG="${CXXFLAGS_ORIG}" CPPFLAGS_ORIG="${CPPFLAGS_ORIG}" LDFLAGS_ORIG="${LDFLAGS_ORIG}" LIBS_ORIG="${LIBS_ORIG}"]) @@ -39,6 +42,9 @@ if test -z "${nut_have_libgd_seen}"; then AS_IF([test x"$GD_VERSION" != xnone], [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags gdlib 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs gdlib 2>/dev/null`" + depCFLAGS_SOURCE="pkg-config" + depLDFLAGS_SOURCE="pkg-config(N/A)" + depLIBS_SOURCE="pkg-config" ], [dnl Initial defaults. These are only used if gdlib-config is dnl unusable and the user fails to pass better values in --with @@ -46,23 +52,12 @@ if test -z "${nut_have_libgd_seen}"; then depCFLAGS="" depLDFLAGS="-L/usr/X11R6/lib" depLIBS="-lgd -lpng -lz -ljpeg -lfreetype -lm -lXpm -lX11" + depCFLAGS_SOURCE="default" + depLDFLAGS_SOURCE="default" + depLIBS_SOURCE="default" - dnl By default seek in PATH - AC_PATH_PROGS([GDLIB_CONFIG], [gdlib-config], [none]) - AC_ARG_WITH(gdlib-config, - AS_HELP_STRING([@<:@--with-gdlib-config=/path/to/gdlib-config@:>@], - [path to program that reports GDLIB configuration]), - [ - case "${withval}" in - "") ;; - yes|no) - AC_MSG_ERROR(invalid option --with(out)-gdlib-config - see docs/configure.txt) - ;; - *) - GDLIB_CONFIG="${withval}" - ;; - esac - ]) + dnl Define --with-gdlib-config option + NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT([GDLIB]) AS_IF([test x"$GDLIB_CONFIG" != xnone], [AC_MSG_CHECKING(for gd version via ${GDLIB_CONFIG}) @@ -85,42 +80,36 @@ if test -z "${nut_have_libgd_seen}"; then depCFLAGS="`${GDLIB_CONFIG} --includes 2>/dev/null`" depLDFLAGS="`${GDLIB_CONFIG} --ldflags 2>/dev/null`" depLIBS="`${GDLIB_CONFIG} --libs 2>/dev/null`" + depCFLAGS_SOURCE="${GDLIB_CONFIG} program" + depLDFLAGS_SOURCE="${GDLIB_CONFIG} program" + depLIBS_SOURCE="${GDLIB_CONFIG} program" ;; esac ] ) dnl Now allow overriding gd settings if the user knows best + dnl (note not "GDLIB" as referenced above for config script) AC_MSG_CHECKING(for gd include flags) - AC_ARG_WITH(gd-includes, - AS_HELP_STRING([@<:@--with-gd-includes=CFLAGS@:>@], [include flags for the gd library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-gd-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([gd], [auto]) + AS_CASE([${nut_with_gd_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_gd_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) + dnl Technically, here we seek LDFLAGS not LIBS... AC_MSG_CHECKING(for gd library flags) - AC_ARG_WITH(gd-libs, - AS_HELP_STRING([@<:@--with-gd-libs=LDFLAGS@:>@], [linker flags for the gd library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-gd-libs - see docs/configure.txt) - ;; - *) - depLDFLAGS="${withval}" - depLIBS="" - ;; - esac - ], []) - AC_MSG_RESULT([${depLDFLAGS} ${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS_AS_LDFLAGS([gd], [auto]) + AS_CASE([${nut_with_gd_libs}], + [auto], [], dnl Keep what we had found above + [depLDFLAGS="${nut_with_gd_libs}" + depLIBS="" + depLDFLAGS_SOURCE="confarg" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLDFLAGS} ${depLIBS} (source: ${depLDFLAGS_SOURCE}/${depLIBS_SOURCE})]) dnl check if gd is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -156,7 +145,7 @@ if test -z "${nut_have_libgd_seen}"; then dnl Collect possibly updated dependencies after AC SEARCH LIBS: AS_IF([test x"${LIBS}" != x"${LIBS_ORIG} ${depLIBS}"], [ AS_IF([test x = x"${LIBS_ORIG}"], [depLIBS="$LIBS"], [ - depLIBS="`echo "$LIBS" | sed -e 's|'"${LIBS_ORIG}"'| |' -e 's|^ *||' -e 's| *$||'`" + depLIBS="`echo \"$LIBS\" | sed -e 's|'\"${LIBS_ORIG}\"'| |' -e 's|^ *||' -e 's| *$||'`" ]) ]) @@ -194,6 +183,9 @@ gdImageDestroy(im); unset depCFLAGS unset depLDFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLDFLAGS_SOURCE + unset depLIBS_SOURCE dnl put back the original versions CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libgpiod.m4 b/m4/nut_check_libgpiod.m4 index daf4100325..0be164c010 100644 --- a/m4/nut_check_libgpiod.m4 +++ b/m4/nut_check_libgpiod.m4 @@ -15,7 +15,9 @@ if test -z "${nut_have_gpio_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" # Store implementation (if any) to be reported by configure.ac: nut_gpio_lib="" @@ -39,46 +41,38 @@ if test -z "${nut_have_gpio_seen}"; then ) AC_MSG_CHECKING(for libgpiod cflags) - AC_ARG_WITH(gpio-includes, - AS_HELP_STRING([@<:@--with-gpio-includes=CFLAGS@:>@], [include flags for the gpiod library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-gpio-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], [ - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libgpiod 2>/dev/null`" \ - || depCFLAGS="-I/usr/include -I/usr/local/include"], - [depCFLAGS="-I/usr/include -I/usr/local/include"] - )] + NUT_ARG_WITH_LIBOPTS_INCLUDES([gpio], [auto], [gpiod]) + AS_CASE([${nut_with_gpio_includes}], + [auto], [ + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libgpiod 2>/dev/null`" \ + && depCFLAGS_SOURCE="pkg-config" ; } \ + || { depCFLAGS="-I/usr/include -I/usr/local/include" \ + && depCFLAGS_SOURCE="default" ; }], + [depCFLAGS="-I/usr/include -I/usr/local/include" + depCFLAGS_SOURCE="default"] + )], + [depCFLAGS="${nut_with_gpio_includes}" + depCFLAGS_SOURCE="confarg"] ) - AC_MSG_RESULT([${depCFLAGS}]) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for libgpiod ldflags) - AC_ARG_WITH(gpio-libs, - AS_HELP_STRING([@<:@--with-gpio-libs=LIBS@:>@], [linker flags for the gpiod library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-gpio-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], [ - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depLIBS="`$PKG_CONFIG --silence-errors --libs libgpiod 2>/dev/null`" \ - || depLIBS="-lgpiod"], - [depLIBS="-lgpiod"] - )] + NUT_ARG_WITH_LIBOPTS_LIBS([gpio], [auto], [gpiod]) + AS_CASE([${nut_with_gpio_libs}], + [auto], [ + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depLIBS="`$PKG_CONFIG --silence-errors --libs libgpiod 2>/dev/null`" \ + && depLIBS_SOURCE="pkg-config" ; } \ + || { depLIBS="-lgpiod" \ + && depLIBS_SOURCE="default" ; }], + [depLIBS="-lgpiod" + depLIBS_SOURCE="default"] + )], + [depLIBS="${nut_with_gpio_libs}" + depLIBS_SOURCE="confarg"] ) - AC_MSG_RESULT([${depLIBS}]) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if gpiod is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -124,8 +118,10 @@ if test -z "${nut_have_gpio_seen}"; then AC_DEFINE_UNQUOTED(WITH_LIBGPIO_VERSION_STR, ["0x00000000"], [Define libgpio C API version generation as string]) fi - unset CFLAGS - unset LIBS + unset depCFLAGS + unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libltdl.m4 b/m4/nut_check_libltdl.m4 index 6f3afe261c..cd57323495 100644 --- a/m4/nut_check_libltdl.m4 +++ b/m4/nut_check_libltdl.m4 @@ -12,45 +12,37 @@ if test -z "${nut_have_libltdl_seen}"; then dnl save CFLAGS and LIBS CFLAGS_ORIG="${CFLAGS}" LIBS_ORIG="${LIBS}" - LIBS="" CFLAGS="" - depLIBS="" + LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" + depLIBS="" + depLIBS_SOURCE="" dnl For fallback below: myCFLAGS="" AC_MSG_CHECKING(for libltdl cflags) - AC_ARG_WITH(libltdl-includes, - AS_HELP_STRING([@<:@--with-libltdl-includes=CFLAGS@:>@], [include flags for the libltdl library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-libltdl-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], [dnl Best-Effort Fallback (LDFLAGS might make more sense for -L..., - dnl but other m4's have it so) to use if probe below fails: - myCFLAGS="-I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib" - ]) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([libltdl], [auto]) + AS_CASE([${nut_with_libltdl_includes}], + [auto], [ + dnl Best-Effort Fallback (LDFLAGS might make more sense for -L..., + dnl but other m4 files have it so) to use if probe below fails: + myCFLAGS="-I/usr/local/include -I/usr/include -L/usr/local/lib -L/usr/lib" + depCFLAGS_SOURCE="default" + ], + [depCFLAGS="${nut_with_libltdl_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for libltdl ldflags) - AC_ARG_WITH(libltdl-libs, - AS_HELP_STRING([@<:@--with-libltdl-libs=LIBS@:>@], [linker flags for the libltdl library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-libltdl-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], [])dnl No fallback here - we probe suitable libs below - AC_MSG_RESULT([${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS([libltdl], [auto]) + AS_CASE([${nut_with_libltdl_libs}], + [auto], [depLIBS_SOURCE="default (probe later)"], dnl No fallback here - we probe suitable libs below + [depLIBS="${nut_with_libltdl_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" LIBS="${LIBS_ORIG} ${depLIBS}" @@ -80,7 +72,7 @@ if test -z "${nut_have_libltdl_seen}"; then dnl Collect possibly updated dependencies after AC SEARCH LIBS: AS_IF([test x"${LIBS}" != x"${LIBS_ORIG} ${depLIBS}"], [ AS_IF([test x = x"${LIBS_ORIG}"], [depLIBS="$LIBS"], [ - depLIBS="`echo "$LIBS" | sed -e 's|'"${LIBS_ORIG}"'| |' -e 's|^ *||' -e 's| *$||'`" + depLIBS="`echo \"$LIBS\" | sed -e 's|'\"${LIBS_ORIG}\"'| |' -e 's|^ *||' -e 's| *$||'`" ]) ]) @@ -89,9 +81,12 @@ if test -z "${nut_have_libltdl_seen}"; then LIBLTDL_CFLAGS="${depCFLAGS}" LIBLTDL_LIBS="${depLIBS}" ]) + unset myCFLAGS unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_liblua.m4 b/m4/nut_check_liblua.m4 index 9595b66911..cbb309d2a4 100644 --- a/m4/nut_check_liblua.m4 +++ b/m4/nut_check_liblua.m4 @@ -38,7 +38,7 @@ if test -z "${nut_have_lua_seen}"; then lua54 lua5.4 lua-54 lua-5.4 \ lua55 lua5.5 lua-55 lua-5.5 \ ; do - LUA_VERSION="`$PKG_CONFIG --silence-errors --modversion "$V" 2>/dev/null`" + LUA_VERSION="`$PKG_CONFIG --silence-errors --modversion \"$V\" 2>/dev/null`" if test "$?" != "0" -o -z "${LUA_VERSION}"; then LUA_VERSION="none" else @@ -122,7 +122,7 @@ if test -z "${nut_have_lua_seen}"; then AX_REALPATH_LIB([${TOKEN}], [SOPATH_LIBLUA], []) AS_IF([test -n "${SOPATH_LIBLUA}" && test -s "${SOPATH_LIBLUA}"], [ AC_DEFINE_UNQUOTED([SOPATH_LIBLUA],["${SOPATH_LIBLUA}"],[Path to dynamic library on build system]) - SOFILE_LIBLUA="`basename "$SOPATH_LIBLUA"`" + SOFILE_LIBLUA="`basename \"$SOPATH_LIBLUA\"`" AC_DEFINE_UNQUOTED([SOFILE_LIBLUA],["${SOFILE_LIBLUA}"],[Base file name of dynamic library on build system]) break ]) diff --git a/m4/nut_check_libmodbus.m4 b/m4/nut_check_libmodbus.m4 index 0571fd1450..7cb8c2146b 100644 --- a/m4/nut_check_libmodbus.m4 +++ b/m4/nut_check_libmodbus.m4 @@ -15,7 +15,9 @@ if test -z "${nut_have_libmodbus_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" AS_IF([test x"$have_PKG_CONFIG" = xyes], [AC_MSG_CHECKING(for libmodbus version via pkg-config) @@ -33,41 +35,33 @@ if test -z "${nut_have_libmodbus_seen}"; then AS_IF([test x"$LIBMODBUS_VERSION" != xnone], [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libmodbus 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs libmodbus 2>/dev/null`" + depCFLAGS_SOURCE="pkg-config" + depLIBS_SOURCE="pkg-config" ], [depCFLAGS="-I/usr/include/modbus" depLIBS="-lmodbus" + depCFLAGS_SOURCE="default" + depLIBS_SOURCE="default" ] ) AC_MSG_CHECKING(for libmodbus cflags) - AC_ARG_WITH(modbus-includes, - AS_HELP_STRING([@<:@--with-modbus-includes=CFLAGS@:>@], [include flags for the libmodbus library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-modbus-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([modbus], [auto], [libmodbus]) + AS_CASE([${nut_with_modbus_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_modbus_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for libmodbus ldflags) - AC_ARG_WITH(modbus-libs, - AS_HELP_STRING([@<:@--with-modbus-libs=LIBS@:>@], [linker flags for the libmodbus library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-modbus-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS([modbus], [auto], [libmodbus]) + AS_CASE([${nut_with_modbus_libs}], + [auto], [], dnl Keep what we had found above + [depLIBS="${nut_with_modbus_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if libmodbus is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -256,7 +250,7 @@ modbus_set_byte_timeout(ctx, to_sec, to_usec);]) #include ], [modbus_t *ctx = modbus_new_rtu(NULL, 0, 0, 0, 0); if (ctx) modbus_free(ctx);])], [AS_IF([test -x "conftest$ac_exeext"], [ - AS_IF([test -n "`${LDD} "conftest$ac_exeext" | grep "libmodbus"`" 2>/dev/null], + AS_IF([test -n "`${LDD} \"conftest$ac_exeext\" | ${GREP} \"libmodbus\"`" 2>/dev/null], [LIBMODBUS_LINKTYPE="dynamic"], [LIBMODBUS_LINKTYPE="static"]) ])]) dnl If not GNU LDD, try other tools? @@ -268,6 +262,8 @@ if (ctx) modbus_free(ctx);])], [AS_IF([test -x "conftest$ac_exeext"], [ unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libneon.m4 b/m4/nut_check_libneon.m4 index a20d8328fb..80a326fa78 100644 --- a/m4/nut_check_libneon.m4 +++ b/m4/nut_check_libneon.m4 @@ -15,7 +15,9 @@ if test -z "${nut_have_neon_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" AS_IF([test x"$have_PKG_CONFIG" = xyes], [dnl See which version of the neon library (if any) is installed @@ -34,46 +36,36 @@ if test -z "${nut_have_neon_seen}"; then ) AC_MSG_CHECKING(for libneon cflags) - AC_ARG_WITH(neon-includes, - AS_HELP_STRING([@<:@--with-neon-includes=CFLAGS@:>@], [include flags for the neon library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-neon-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], [ - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags neon 2>/dev/null`" \ - || depCFLAGS="-I/usr/include/neon -I/usr/local/include/neon"], - [depCFLAGS="-I/usr/include/neon -I/usr/local/include/neon"] - )] + NUT_ARG_WITH_LIBOPTS_INCLUDES([neon], [auto]) + AS_CASE([${nut_with_neon_includes}], + [auto], [AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depCFLAGS="`$PKG_CONFIG --silence-errors --cflags neon 2>/dev/null`" \ + && depCFLAGS_SOURCE="pkg-config" ; } \ + || { depCFLAGS="-I/usr/include/neon -I/usr/local/include/neon" \ + && depCFLAGS_SOURCE="default" ; }], + [depCFLAGS="-I/usr/include/neon -I/usr/local/include/neon" + depCFLAGS_SOURCE="default"] + )], + [depCFLAGS="${nut_with_neon_includes}" + depCFLAGS_SOURCE="confarg"] ) - AC_MSG_RESULT([${CFLAGS}]) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for libneon ldflags) - AC_ARG_WITH(neon-libs, - AS_HELP_STRING([@<:@--with-neon-libs=LIBS@:>@], [linker flags for the neon library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-neon-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], [ - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depLIBS="`$PKG_CONFIG --silence-errors --libs neon 2>/dev/null`" \ - || depLIBS="-lneon"], - [depLIBS="-lneon"] - )] + NUT_ARG_WITH_LIBOPTS_LIBS([neon], [auto]) + AS_CASE([${nut_with_neon_libs}], + [auto], [AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depLIBS="`$PKG_CONFIG --silence-errors --libs neon 2>/dev/null`" \ + && depLIBS_SOURCE="pkg-config" ; } \ + || { depLIBS="-lneon" \ + && depLIBS_SOURCE="default" ; }], + [depLIBS="-lneon" + depLIBS_SOURCE="default"] + )], + [depLIBS="${nut_with_neon_libs}" + depLIBS_SOURCE="confarg"] ) - AC_MSG_RESULT([${depLIBS}]) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if neon is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -94,7 +86,7 @@ if test -z "${nut_have_neon_seen}"; then 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"`" + SOFILE_LIBNEON="`basename \"$SOPATH_LIBNEON\"`" AC_DEFINE_UNQUOTED([SOFILE_LIBNEON],["${SOFILE_LIBNEON}"],[Base file name of dynamic library on build system]) break ]) @@ -106,6 +98,8 @@ if test -z "${nut_have_neon_seen}"; then unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libnetsnmp.m4 b/m4/nut_check_libnetsnmp.m4 index 423942a08b..3a848fcc85 100644 --- a/m4/nut_check_libnetsnmp.m4 +++ b/m4/nut_check_libnetsnmp.m4 @@ -18,6 +18,8 @@ if test -z "${nut_have_libnetsnmp_seen}"; then LIBS="" depCFLAGS="" depLIBS="" + depCFLAGS_SOURCE="" + depLIBS_SOURCE="" dnl We prefer to get info from pkg-config (for suitable arch/bitness as dnl specified in args for that mechanism), unless (legacy) a particular @@ -34,30 +36,31 @@ if test -z "${nut_have_libnetsnmp_seen}"; then ) prefer_NET_SNMP_CONFIG=false - AC_ARG_WITH(net-snmp-config, - AS_HELP_STRING([@<:@--with-net-snmp-config=/path/to/net-snmp-config@:>@], - [path to program that reports Net-SNMP configuration]), - [ - case "${withval}" in - ""|yes) prefer_NET_SNMP_CONFIG=true ;; - no) + NUT_ARG_WITH_LIBOPTS([net-snmp-config], [/path/to/net-snmp-config], + [Path to program that reports Net-SNMP configuration], [auto]) + + AS_CASE([${nut_with_net_snmp_config}], + [""|yes], [prefer_NET_SNMP_CONFIG=true], + [auto], [prefer_NET_SNMP_CONFIG=auto], + [no], [ dnl AC_MSG_ERROR(invalid option --with(out)-net-snmp-config - see docs/configure.txt) prefer_NET_SNMP_CONFIG=false - ;; - *) - NET_SNMP_CONFIG="${withval}" - prefer_NET_SNMP_CONFIG=true - ;; - esac - ]) + ], + [NET_SNMP_CONFIG="${nut_with_net_snmp_config}" + prefer_NET_SNMP_CONFIG=true + ] + ) - if test x"$have_PKG_CONFIG" = xyes && ! "${prefer_NET_SNMP_CONFIG}" ; then + if test x"$have_PKG_CONFIG" = xyes -a x"${prefer_NET_SNMP_CONFIG}" != xtrue ; then AC_MSG_CHECKING(for Net-SNMP version via pkg-config) dnl TODO? Loop over possible/historic pkg names, like dnl netsnmp, net-snmp, ucd-snmp, libsnmp, snmp... SNMP_VERSION="`$PKG_CONFIG --silence-errors --modversion netsnmp 2>/dev/null`" if test "$?" = "0" -a -n "${SNMP_VERSION}" ; then AC_MSG_RESULT(${SNMP_VERSION} found) + if test x"${prefer_NET_SNMP_CONFIG}" = xauto; then + prefer_NET_SNMP_CONFIG=false + fi else AC_MSG_RESULT(none found) prefer_NET_SNMP_CONFIG=true @@ -79,59 +82,43 @@ if test -z "${nut_have_libnetsnmp_seen}"; then AC_MSG_RESULT(${SNMP_VERSION} found) fi - if test x"$have_PKG_CONFIG" != xyes && ! "${prefer_NET_SNMP_CONFIG}" ; then + if test x"$have_PKG_CONFIG" != xyes -a x"${prefer_NET_SNMP_CONFIG}" = xfalse ; then AC_MSG_WARN([did not find either net-snmp-config or pkg-config for net-snmp]) fi - depCFLAGS_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]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-snmp-includes - see docs/configure.txt) - ;; - *) - depCFLAGS_SOURCE="confarg" - depCFLAGS="${withval}" - ;; - esac - ], [AS_IF(["${prefer_NET_SNMP_CONFIG}"], - [depCFLAGS="`${NET_SNMP_CONFIG} --base-cflags 2>/dev/null`" - depCFLAGS_SOURCE="netsnmp-config"], - [AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags netsnmp 2>/dev/null`" - depCFLAGS_SOURCE="pkg-config"], - [depCFLAGS_SOURCE="default"] - )] - )] + NUT_ARG_WITH_LIBOPTS_INCLUDES([snmp], [auto], [Net-SNMP]) + AS_CASE([${nut_with_snmp_includes}], + [auto], [AS_IF(["${prefer_NET_SNMP_CONFIG}"], + [depCFLAGS="`${NET_SNMP_CONFIG} --base-cflags 2>/dev/null`" + depCFLAGS_SOURCE="${NET_SNMP_CONFIG} program"], + [AS_IF([test x"$have_PKG_CONFIG" = xyes], + [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags netsnmp 2>/dev/null`" + depCFLAGS_SOURCE="pkg-config"], + [depCFLAGS_SOURCE="default"] + )] + )], + [depCFLAGS_SOURCE="confarg" + depCFLAGS="${nut_with_snmp_includes}"] ) AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) - depLIBS_SOURCE="" + dnl Note: below we check for specifically `if depLIBS_SOURCE == "pkg-config"` AC_MSG_CHECKING(for Net-SNMP libs) - AC_ARG_WITH(snmp-libs, - AS_HELP_STRING([@<:@--with-snmp-libs=LIBS@:>@], [linker flags for the Net-SNMP library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-snmp-libs - see docs/configure.txt) - ;; - *) - depLIBS_SOURCE="confarg" - depLIBS="${withval}" - ;; - esac - ], [AS_IF(["${prefer_NET_SNMP_CONFIG}"], - [depLIBS="`${NET_SNMP_CONFIG} --libs 2>/dev/null`" - depLIBS_SOURCE="netsnmp-config"], - [AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depLIBS="`$PKG_CONFIG --silence-errors --libs netsnmp 2>/dev/null`" - depLIBS_SOURCE="pkg-config"], - [depLIBS="-lnetsnmp" - depLIBS_SOURCE="default"])] - )] + NUT_ARG_WITH_LIBOPTS_LIBS([snmp], [auto], [Net-SNMP]) + AS_CASE([${nut_with_snmp_libs}], + [auto], [AS_IF(["${prefer_NET_SNMP_CONFIG}"], + [depLIBS="`${NET_SNMP_CONFIG} --libs 2>/dev/null`" + depLIBS_SOURCE="${NET_SNMP_CONFIG} program"], + [AS_IF([test x"$have_PKG_CONFIG" = xyes], + [depLIBS="`$PKG_CONFIG --silence-errors --libs netsnmp 2>/dev/null`" + depLIBS_SOURCE="pkg-config"], + [depLIBS="-lnetsnmp" + depLIBS_SOURCE="default"] + )] + )], + [depLIBS_SOURCE="confarg" + depLIBS="${nut_with_snmp_libs}"] ) AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) @@ -164,7 +151,7 @@ if test -z "${nut_have_libnetsnmp_seen}"; then dnl # In Makefiles be sure to use _LDFLAGS (not _LIBADD) to smuggle linker dnl # arguments when building "if WITH_SNMP_STATIC" recipe blocks! dnl # For a practical example, see tools/nut-scanner/Makefile.am. - depLIBS="`echo " $depLIBS" | sed 's/ -l/ -Wl,-l/g'`" + depLIBS="`echo \" $depLIBS\" | sed 's/ -l/ -Wl,-l/g'`" LIBS="${LIBS_ORIG} ${depLIBS}" AS_UNSET([ac_cv_func_init_snmp]) AC_CHECK_FUNCS(init_snmp, [ @@ -375,7 +362,7 @@ int num = NETSNMP_DRAFT_BLUMENTHAL_AES_04 + 1; /* if defined, NETSNMP_DRAFT_BLUM 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"`" + SOFILE_LIBNETSNMP="`basename \"$SOPATH_LIBNETSNMP\"`" AC_DEFINE_UNQUOTED([SOFILE_LIBNETSNMP],["${SOFILE_LIBNETSNMP}"],[Base file name of dynamic library on build system]) break ]) @@ -389,6 +376,8 @@ int num = NETSNMP_DRAFT_BLUMENTHAL_AES_04 + 1; /* if defined, NETSNMP_DRAFT_BLUM unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libnss.m4 b/m4/nut_check_libnss.m4 index ef81ad5491..7c5babd4d0 100644 --- a/m4/nut_check_libnss.m4 +++ b/m4/nut_check_libnss.m4 @@ -17,8 +17,11 @@ if test -z "${nut_have_libnss_seen}"; then LIBS="" REQUIRES="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" depREQUIRES="" + depREQUIRES_SOURCE="" AS_IF([test x"$have_PKG_CONFIG" = xyes], [AC_MSG_CHECKING(for Mozilla NSS version via pkg-config) @@ -37,43 +40,37 @@ if test -z "${nut_have_libnss_seen}"; then [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags nss 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs nss 2>/dev/null`" depREQUIRES="nss" + depCFLAGS_SOURCE="pkg-config" + depLIBS_SOURCE="pkg-config" + depREQUIRES_SOURCE="default(pkg-config)" ], [depCFLAGS="" depLIBS="-lnss3 -lnssutil3 -lsmime3 -lssl3 -lplds4 -lplc4 -lnspr4" depREQUIRES="nss" + depCFLAGS_SOURCE="default" + depLIBS_SOURCE="default" + depREQUIRES_SOURCE="default" ] ) dnl allow overriding NSS settings if the user knows best AC_MSG_CHECKING(for Mozilla NSS cflags) - AC_ARG_WITH(nss-includes, - AS_HELP_STRING([@<:@--with-nss-includes=CFLAGS@:>@], [include flags for the Mozilla NSS library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-nss-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([nss], [auto], [Mozilla NSS]) + AS_CASE([${nut_with_nss_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_nss_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for Mozilla NSS ldflags) - AC_ARG_WITH(nss-libs, - AS_HELP_STRING([@<:@--with-nss-libs=LIBS@:>@], [linker flags for the Mozilla NSS library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-nss-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS([nss], [auto], [Mozilla NSS]) + AS_CASE([${nut_with_nss_libs}], + [auto], [], dnl Keep what we had found above + [depLIBS="${nut_with_nss_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if NSS is usable: we need both the runtime and headers dnl NOTE that caller may have to specify PKG_CONFIG_PATH including @@ -103,7 +100,7 @@ if test -z "${nut_have_libnss_seen}"; then addCFLAGS="" for TOKEN in ${depCFLAGS} ; do case "${TOKEN}" in - -I*) TOKENDIR="`echo "$TOKEN" | sed 's,^-I,,'`" + -I*) TOKENDIR="`echo \"$TOKEN\" | sed 's,^-I,,'`" case " ${CFLAGS} ${addCFLAGS} " in *" -isystem $TOKENDIR "*) ;; *) addCFLAGS="${addCFLAGS} -isystem $TOKENDIR" ;; @@ -140,6 +137,9 @@ dnl fi unset depCFLAGS unset depLIBS unset depREQUIRES + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE + unset depREQUIRES_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libopenssl.m4 b/m4/nut_check_libopenssl.m4 index a39a3c6246..bcf6e0f7cf 100644 --- a/m4/nut_check_libopenssl.m4 +++ b/m4/nut_check_libopenssl.m4 @@ -18,8 +18,11 @@ if test -z "${nut_have_libopenssl_seen}"; then LIBS="" REQUIRES="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" depREQUIRES="" + depREQUIRES_SOURCE="" AS_IF([test x"$have_PKG_CONFIG" = xyes], [AC_MSG_CHECKING(for OpenSSL version via pkg-config) @@ -38,43 +41,37 @@ if test -z "${nut_have_libopenssl_seen}"; then [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags openssl 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs openssl 2>/dev/null`" depREQUIRES="openssl" + depCFLAGS_SOURCE="pkg-config" + depLIBS_SOURCE="pkg-config" + depREQUIRES_SOURCE="default(pkg-config)" ], [depCFLAGS="" depLIBS="-lssl -lcrypto" depREQUIRES="openssl" + depCFLAGS_SOURCE="default" + depLIBS_SOURCE="default" + depREQUIRES_SOURCE="default" ] ) dnl allow overriding OpenSSL settings if the user knows best AC_MSG_CHECKING(for OpenSSL cflags) - AC_ARG_WITH(openssl-includes, - AS_HELP_STRING([@<:@--with-openssl-includes=CFLAGS@:>@], [include flags for the OpenSSL library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-openssl-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([OpenSSL], [auto]) + AS_CASE([${nut_with_openssl_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_openssl_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for OpenSSL ldflags) - AC_ARG_WITH(openssl-libs, - AS_HELP_STRING([@<:@--with-openssl-libs=LIBS@:>@], [linker flags for the OpenSSL library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-openssl-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS([OpenSSL], [auto]) + AS_CASE([${nut_with_openssl_libs}], + [auto], [], dnl Keep what we had found above + [depLIBS="${nut_with_openssl_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if openssl is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -104,7 +101,7 @@ if test -z "${nut_have_libopenssl_seen}"; then addCFLAGS="" for TOKEN in ${depCFLAGS} ; do case "${TOKEN}" in - -I*) TOKENDIR="`echo "$TOKEN" | sed 's,^-I,,'`" + -I*) TOKENDIR="`echo \"$TOKEN\" | sed 's,^-I,,'`" case " ${CFLAGS} ${addCFLAGS} " in *" -isystem $TOKENDIR "*) ;; *) addCFLAGS="${addCFLAGS} -isystem $TOKENDIR" ;; @@ -128,6 +125,9 @@ if test -z "${nut_have_libopenssl_seen}"; then unset depCFLAGS unset depLIBS unset depREQUIRES + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE + unset depREQUIRES_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libpowerman.m4 b/m4/nut_check_libpowerman.m4 index f263a61a31..c0f8f437fe 100644 --- a/m4/nut_check_libpowerman.m4 +++ b/m4/nut_check_libpowerman.m4 @@ -15,7 +15,9 @@ if test -z "${nut_have_libpowerman_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" AS_IF([test x"$have_PKG_CONFIG" = xyes], [AC_MSG_CHECKING([for LLNC libpowerman version via pkg-config]) @@ -39,41 +41,33 @@ if test -z "${nut_have_libpowerman_seen}"; then AS_IF([test x"$POWERMAN_VERSION" != xnone], [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libpowerman 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs libpowerman 2>/dev/null`" + depCFLAGS_SOURCE="pkg-config" + depLIBS_SOURCE="pkg-config" ], [depCFLAGS="" depLIBS="" + depCFLAGS_SOURCE="default" + depLIBS_SOURCE="default" ] ) AC_MSG_CHECKING([for libpowerman cflags]) - AC_ARG_WITH(powerman-includes, - AS_HELP_STRING([@<:@--with-powerman-includes=CFLAGS@:>@], [include flags for the libpowerman library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR([invalid option --with(out)-powerman-includes - see docs/configure.txt]) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([powerman], [auto], [libpowerman]) + AS_CASE([${nut_with_powerman_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_powerman_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for libpowerman libs) - AC_ARG_WITH(powerman-libs, - AS_HELP_STRING([@<:@--with-powerman-libs=LIBS@:>@], [linker flags for the libpowerman library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-powerman-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], []) - AC_MSG_RESULT([${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS([powerman], [auto], [libpowerman]) + AS_CASE([${nut_with_powerman_libs}], + [auto], [], dnl Keep what we had found above + [depLIBS="${nut_with_powerman_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if libpowerman is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -100,6 +94,8 @@ if test -z "${nut_have_libpowerman_seen}"; then unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libregex.m4 b/m4/nut_check_libregex.m4 index dd4c49b595..1697c3715a 100644 --- a/m4/nut_check_libregex.m4 +++ b/m4/nut_check_libregex.m4 @@ -18,8 +18,11 @@ if test -z "${nut_have_libregex_seen}"; then LIBS="" REQUIRES="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" depREQUIRES="" + depREQUIRES_SOURCE="" dnl Actually did not see it in any systems' pkg-config info... dnl Part of standard footprint? @@ -45,16 +48,41 @@ if test -z "${nut_have_libregex_seen}"; then ) AS_IF([test x"$LIBREGEX_VERSION" != xnone && test x"$LIBREGEX_MODULE" != x], - [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags "${LIBREGEX_MODULE}" 2>/dev/null`" - depLIBS="`$PKG_CONFIG --silence-errors --libs "${LIBREGEX_MODULE}" 2>/dev/null`" + [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags \"${LIBREGEX_MODULE}\" 2>/dev/null`" + depLIBS="`$PKG_CONFIG --silence-errors --libs \"${LIBREGEX_MODULE}\" 2>/dev/null`" depREQUIRES="${LIBREGEX_MODULE}" + depCFLAGS_SOURCE="pkg-config" + depLIBS_SOURCE="pkg-config" + depREQUIRES_SOURCE="pkg-config" ], [depCFLAGS="" depLIBS="" depREQUIRES="" + depCFLAGS_SOURCE="default" + depLIBS_SOURCE="default" + depREQUIRES_SOURCE="default" ] ) + dnl allow overriding libregex settings if the user knows best + AC_MSG_CHECKING(for libregex cflags) + NUT_ARG_WITH_LIBOPTS_INCLUDES([regex], [auto]) + AS_CASE([${nut_with_regex_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_regex_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) + + AC_MSG_CHECKING(for libregex ldflags) + NUT_ARG_WITH_LIBOPTS_LIBS([regex], [auto]) + AS_CASE([${nut_with_regex_libs}], + [auto], [], dnl Keep what we had found above + [depLIBS="${nut_with_regex_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) + dnl Check if libregex is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" LIBS="${LIBS_ORIG} ${depLIBS}" @@ -88,7 +116,7 @@ if test -z "${nut_have_libregex_seen}"; then dnl Collect possibly updated dependencies after AC SEARCH LIBS: AS_IF([test x"${LIBS}" != x"${LIBS_ORIG} ${depLIBS}"], [ AS_IF([test x = x"${LIBS_ORIG}"], [depLIBS="$LIBS"], [ - depLIBS="`echo "$LIBS" | sed -e 's|'"${LIBS_ORIG}"'| |' -e 's|^ *||' -e 's| *$||'`" + depLIBS="`echo \"$LIBS\" | sed -e 's|'\"${LIBS_ORIG}\"'| |' -e 's|^ *||' -e 's| *$||'`" ]) ]) @@ -110,9 +138,15 @@ if test -z "${nut_have_libregex_seen}"; then unset depCFLAGS unset depLIBS unset depREQUIRES + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE + unset depREQUIRES_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" LIBS="${LIBS_ORIG}" + + dnl FIXME? We did not restore this value, is this needed by libs checked later? + dnl REQUIRES="${REQUIRES_ORIG}" fi ]) diff --git a/m4/nut_check_libsystemd.m4 b/m4/nut_check_libsystemd.m4 index 671c2bb7ce..946946be67 100644 --- a/m4/nut_check_libsystemd.m4 +++ b/m4/nut_check_libsystemd.m4 @@ -15,7 +15,9 @@ if test -z "${nut_have_libsystemd_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" SYSTEMD_VERSION="none" @@ -41,7 +43,7 @@ if test -z "${nut_have_libsystemd_seen}"; then 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}'`" \ + SYSTEMD_VERSION="`LANG=C LC_ALL=C ${SYSTEMCTL} --version | ${EGREP} '^systemd@<:@ \t@:>@*@<:@0-9@:>@@<:@0-9@:>@*' | awk '{print ''$''2}'`" \ && test -n "${SYSTEMD_VERSION}" \ || SYSTEMD_VERSION="none" AC_MSG_RESULT(${SYSTEMD_VERSION} found) @@ -54,49 +56,41 @@ if test -z "${nut_have_libsystemd_seen}"; then ]) AC_MSG_CHECKING(for libsystemd cflags) - AC_ARG_WITH(libsystemd-includes, - AS_HELP_STRING([@<:@--with-libsystemd-includes=CFLAGS@:>@], [include flags for the systemd library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-libsystemd-includes - see docs/configure.txt) - ;; - *) - depCFLAGS="${withval}" - ;; - esac - ], [ - dnl Not specifying a default include path here, - dnl headers are referenced by relative directory - dnl and these should be in OS location usually. - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libsystemd 2>/dev/null`" \ - || depCFLAGS=""], - [depCFLAGS=""] - )] + NUT_ARG_WITH_LIBOPTS_INCLUDES([libsystemd], [auto], [systemd]) + AS_CASE([${nut_with_libsystemd_includes}], + [auto], [ + dnl Not specifying a default include path here, + dnl headers are referenced by relative directory + dnl and these should be in OS location usually. + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libsystemd 2>/dev/null`" \ + && depCFLAGS_SOURCE="pkg-config" ; } \ + || { depCFLAGS="" \ + && depCFLAGS_SOURCE="default" ; }], + [depCFLAGS="" + depCFLAGS_SOURCE="default"] + )], + [depCFLAGS="${nut_with_libsystemd_includes}" + depCFLAGS_SOURCE="confarg"] ) - AC_MSG_RESULT([${depCFLAGS}]) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for libsystemd ldflags) - AC_ARG_WITH(libsystemd-libs, - AS_HELP_STRING([@<:@--with-libsystemd-libs=LIBS@:>@], [linker flags for the systemd library]), - [ - case "${withval}" in - yes|no) - AC_MSG_ERROR(invalid option --with(out)-libsystemd-libs - see docs/configure.txt) - ;; - *) - depLIBS="${withval}" - ;; - esac - ], [ - AS_IF([test x"$have_PKG_CONFIG" = xyes], - [depLIBS="`$PKG_CONFIG --silence-errors --libs libsystemd 2>/dev/null`" \ - || depLIBS="-lsystemd"], - [depLIBS="-lsystemd"] - )] + NUT_ARG_WITH_LIBOPTS_LIBS([libsystemd], [auto], [systemd]) + AS_CASE([${nut_with_libsystemd_libs}], + [auto], [ + AS_IF([test x"$have_PKG_CONFIG" = xyes], + [ { depLIBS="`$PKG_CONFIG --silence-errors --libs libsystemd 2>/dev/null`" \ + && depLIBS_SOURCE="pkg-config" ; } \ + || { depLIBS="-lsystemd" \ + && depLIBS_SOURCE="default" ; }], + [depLIBS="-lsystemd" + depLIBS_SOURCE="default"] + )], + [depLIBS="${nut_with_libsystemd_libs}" + depLIBS_SOURCE="confarg"] ) - AC_MSG_RESULT([${depLIBS}]) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl check if libsystemd is usable CFLAGS="${CFLAGS_ORIG} ${depCFLAGS}" @@ -133,6 +127,8 @@ if test -z "${nut_have_libsystemd_seen}"; then unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libusb.m4 b/m4/nut_check_libusb.m4 index f17ddd04d2..56b59245ea 100644 --- a/m4/nut_check_libusb.m4 +++ b/m4/nut_check_libusb.m4 @@ -23,7 +23,9 @@ if test -z "${nut_have_libusb_seen}"; then CFLAGS="" LIBS="" depCFLAGS="" + depCFLAGS_SOURCE="" depLIBS="" + depLIBS_SOURCE="" dnl Magic-format string to hold chosen libusb version and its config-source nut_usb_lib="" @@ -51,24 +53,8 @@ if test -z "${nut_have_libusb_seen}"; then dnl Note: it seems the script was only shipped for libusb-0.1 dnl So we don't separate into LIBUSB_0_1_CONFIG and LIBUSB_1_0_CONFIG - AC_PATH_PROGS([LIBUSB_CONFIG], [libusb-config], [none]) - - AC_ARG_WITH(libusb-config, - AS_HELP_STRING([@<:@--with-libusb-config=/path/to/libusb-config@:>@], - [path to program that reports LibUSB configuration]), dnl ...for LibUSB-0.1 - [ - AS_CASE(["${withval}"], - [""], [], dnl empty arg - [yes|no], [ - dnl MAYBE bump preference of script over pkg-config? - AC_MSG_ERROR([invalid option --with(out)-libusb-config - see docs/configure.txt]) - ], - [dnl default - LIBUSB_CONFIG="${withval}" - ] - ) - ] - ) + dnl MAYBE bump preference of a found script over pkg-config? + NUT_ARG_WITH_LIBOPTS_CONFIGSCRIPT([libusb], [], [], [], [LibUSB(-0.1)]) AS_IF([test x"${LIBUSB_CONFIG}" != xnone], [AC_MSG_CHECKING([via ${LIBUSB_CONFIG}]) @@ -121,7 +107,7 @@ if test -z "${nut_have_libusb_seen}"; then ) dnl Pick up the default or caller-provided choice here from - dnl NUT_ARG_WITH(usb, ...) in the main configure.ac script + dnl NUT-ARG-WITH(usb, ...) in the main configure.ac script AC_MSG_CHECKING([for libusb preferred version]) AS_CASE(["${nut_with_usb}"], [auto], [], dnl Use preference picked above @@ -164,54 +150,50 @@ if test -z "${nut_have_libusb_seen}"; then ["(libusb-1.0)"], [ depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libusb-1.0 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs libusb-1.0 2>/dev/null`" + depCFLAGS_SOURCE="pkg-config(libusb-1.0)" + depLIBS_SOURCE="pkg-config(libusb-1.0)" ], ["(libusb-0.1)"], [ depCFLAGS="`$PKG_CONFIG --silence-errors --cflags libusb 2>/dev/null`" depLIBS="`$PKG_CONFIG --silence-errors --libs libusb 2>/dev/null`" + depCFLAGS_SOURCE="pkg-config(libusb-0.1)" + depLIBS_SOURCE="pkg-config(libusb-0.1)" ], ["(libusb-0.1-config)"], [ depCFLAGS="`$LIBUSB_CONFIG --cflags 2>/dev/null`" depLIBS="`$LIBUSB_CONFIG --libs 2>/dev/null`" + depCFLAGS_SOURCE="${LIBUSB_CONFIG} program (libusb-0.1)" + depLIBS_SOURCE="${LIBUSB_CONFIG} program (libusb-0.1)" ], [dnl default, for other versions or "none" AC_MSG_WARN([Defaulting libusb configuration]) LIBUSB_VERSION="none" depCFLAGS="" depLIBS="-lusb" + depCFLAGS_SOURCE="default" + depLIBS_SOURCE="default" ] ) dnl check optional user-provided values for cflags/ldflags dnl and publish what we end up using AC_MSG_CHECKING(for libusb cflags) - AC_ARG_WITH(usb-includes, - AS_HELP_STRING([@<:@--with-usb-includes=CFLAGS@:>@], [include flags for the libusb library]), - [ - AS_CASE(["${withval}"], - [yes|no], [ - AC_MSG_ERROR(invalid option --with(out)-usb-includes - see docs/configure.txt) - ], - [dnl default - depCFLAGS="${withval}" - ] - ) - ], []) - AC_MSG_RESULT([${depCFLAGS}]) + NUT_ARG_WITH_LIBOPTS_INCLUDES([usb], [auto], [libusb]) + AS_CASE([${nut_with_usb_includes}], + [auto], [], dnl Keep what we had found above + [depCFLAGS="${nut_with_usb_includes}" + depCFLAGS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depCFLAGS} (source: ${depCFLAGS_SOURCE})]) AC_MSG_CHECKING(for libusb ldflags) - AC_ARG_WITH(usb-libs, - AS_HELP_STRING([@<:@--with-usb-libs=LIBS@:>@], [linker flags for the libusb library]), - [ - AS_CASE(["${withval}"], - [yes|no], [ - AC_MSG_ERROR(invalid option --with(out)-usb-libs - see docs/configure.txt) - ], - [dnl default - depLIBS="${withval}" - ] - ) - ], []) - AC_MSG_RESULT([${depLIBS}]) + NUT_ARG_WITH_LIBOPTS_LIBS([usb], [auto], [libusb]) + AS_CASE([${nut_with_usb_libs}], + [auto], [], dnl Keep what we had found above + [depLIBS="${nut_with_usb_libs}" + depLIBS_SOURCE="confarg"] + ) + AC_MSG_RESULT([${depLIBS} (source: ${depLIBS_SOURCE})]) dnl TODO: Consult chosen nut_usb_lib value and/or nut_with_usb argument dnl (with "auto" we may use a 0.1 if present and working while a 1.0 is @@ -328,7 +310,7 @@ if test -z "${nut_have_libusb_seen}"; then dnl Collect possibly updated dependencies after AC SEARCH LIBS: AS_IF([test x"${LIBS}" != x"${LIBS_ORIG} ${depLIBS}"], [ AS_IF([test x = x"${LIBS_ORIG}"], [depLIBS="$LIBS"], [ - depLIBS="`echo "$LIBS" | sed -e 's|'"${LIBS_ORIG}"'| |' -e 's|^ *||' -e 's| *$||'`" + depLIBS="`echo \"$LIBS\" | sed -e 's|'\"${LIBS_ORIG}\"'| |' -e 's|^ *||' -e 's| *$||'`" ]) ]) depLIBS="-R/usr/sfw/lib ${depLIBS}" @@ -407,7 +389,7 @@ if test -z "${nut_have_libusb_seen}"; then 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"`" + SOFILE_LIBUSB1="`basename \"$SOPATH_LIBUSB1\"`" AC_DEFINE_UNQUOTED([SOFILE_LIBUSB1],["${SOFILE_LIBUSB1}"],[Base file name of dynamic library on build system]) break ]) @@ -431,7 +413,7 @@ if test -z "${nut_have_libusb_seen}"; then 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"`" + SOFILE_LIBUSB0="`basename \"$SOPATH_LIBUSB0\"`" AC_DEFINE_UNQUOTED([SOFILE_LIBUSB0],["${SOFILE_LIBUSB0}"],[Base file name of dynamic library on build system]) break ]) @@ -446,6 +428,8 @@ if test -z "${nut_have_libusb_seen}"; then unset depCFLAGS unset depLIBS + unset depCFLAGS_SOURCE + unset depLIBS_SOURCE dnl restore original CFLAGS and LIBS CFLAGS="${CFLAGS_ORIG}" diff --git a/m4/nut_check_libwrap.m4 b/m4/nut_check_libwrap.m4 index fd55fd3beb..484603b459 100644 --- a/m4/nut_check_libwrap.m4 +++ b/m4/nut_check_libwrap.m4 @@ -22,7 +22,7 @@ if test -z "${nut_have_libwrap_seen}"; then dnl Collect possibly updated dependencies after AC SEARCH LIBS: AS_IF([test x"${LIBS}" != x"${LIBS_ORIG}"], [ AS_IF([test x = x"${LIBS_ORIG}"], [depLIBS="$LIBS"], [ - depLIBS="`echo "$LIBS" | sed -e 's|'"${LIBS_ORIG}"'| |' -e 's|^ *||' -e 's| *$||'`" + depLIBS="`echo \"$LIBS\" | sed -e 's|'\"${LIBS_ORIG}\"'| |' -e 's|^ *||' -e 's| *$||'`" ]) ]) diff --git a/m4/nut_check_os.m4 b/m4/nut_check_os.m4 index 5f7dbd921c..afbf62e26a 100644 --- a/m4/nut_check_os.m4 +++ b/m4/nut_check_os.m4 @@ -118,7 +118,7 @@ AC_DEFUN([NUT_CHECK_OS], fi fi if test -z "$dist_cv_build_flavor" -a ":${dist_cv_build_issue_file:-no}" != :no ; then - dist_cv_build_flavor=$(os_get_name "$(cat $dist_cv_build_issue_file | grep 'Linux\|Fedora\|Ubuntu' | head -1)") + dist_cv_build_flavor=$(os_get_name "$(cat $dist_cv_build_issue_file | ${GREP} 'Linux\|Fedora\|Ubuntu' | head -1)") fi # do debian after lsb and issue for Ubuntu if test -z "$dist_cv_build_flavor" -a ":${dist_cv_build_rel_file:-no}" != :no ; then @@ -128,7 +128,7 @@ AC_DEFUN([NUT_CHECK_OS], fi # FIXME if test -z "$dist_cv_build_flavor" ; then - dist_cv_build_flavor=$(os_get_name "$(${CC-cc} $CFLAGS -v 2>&1 | grep 'gcc version')") + dist_cv_build_flavor=$(os_get_name "$(${CC-cc} $CFLAGS -v 2>&1 | ${GREP} 'gcc version')") fi # save the result diff --git a/m4/nut_check_pkgconfig.m4 b/m4/nut_check_pkgconfig.m4 index 5366d08ea7..499738ef34 100644 --- a/m4/nut_check_pkgconfig.m4 +++ b/m4/nut_check_pkgconfig.m4 @@ -11,7 +11,7 @@ AC_DEFUN([NUT_CHECK_PKGCONFIG], dnl Note that PKG_CONFIG may be a filename, path, dnl or either with args - so no quoting here AC_MSG_CHECKING([whether usable PKG_CONFIG was already detected by autoconf]) - AS_IF([test -n "${PKG_CONFIG-}" && test x"${PKG_CONFIG-}" != x"false" && $PKG_CONFIG --help 2>&1 | grep -E '(--cflags|--libs)' >/dev/null], + AS_IF([test -n "${PKG_CONFIG-}" && test x"${PKG_CONFIG-}" != x"false" && $PKG_CONFIG --help 2>&1 | ${EGREP} '(--cflags|--libs)' >/dev/null], [AC_MSG_RESULT([yes: ${PKG_CONFIG}]) have_PKG_CONFIG=yes ], @@ -26,20 +26,12 @@ AC_DEFUN([NUT_CHECK_PKGCONFIG], have_PKG_CONFIG=yes AC_PATH_PROG(dummy_PKG_CONFIG, pkg-config) - AC_ARG_WITH(pkg-config, - AS_HELP_STRING([--with-pkg-config=/path/to/pkg-config], - [path to program that reports development package configuration]), - [ - case "${withval}" in - "") ;; - yes|no) - AC_MSG_ERROR(invalid option --with(out)-pkg-config - see docs/configure.txt) - ;; - *) - dummy_PKG_CONFIG="${withval}" - ;; - esac - ]) + NUT_ARG_WITH([pkg-config], [auto|/path/to/pkg-config], [Path to program that reports development package configuration], [auto]) + AS_CASE([${nut_with_pkg_config}], + [""|auto], [], dnl Keep what we had found above + [yes|no], [AC_MSG_ERROR(invalid option --with(out)-pkg-config - see docs/configure.txt)], + [dummy_PKG_CONFIG="${nut_with_pkg_config}"] + ) AC_MSG_CHECKING([whether usable PKG_CONFIG is present in PATH or was set by caller]) AS_IF([test x"$dummy_PKG_CONFIG" = xno || test -z "$dummy_PKG_CONFIG"], @@ -47,7 +39,7 @@ AC_DEFUN([NUT_CHECK_PKGCONFIG], PKG_CONFIG=false have_PKG_CONFIG=no ], - [AS_IF([$dummy_PKG_CONFIG --help 2>&1 | grep -E '(--cflags|--libs)' >/dev/null], + [AS_IF([$dummy_PKG_CONFIG --help 2>&1 | ${EGREP} '(--cflags|--libs)' >/dev/null], [AC_MSG_RESULT([yes: ${dummy_PKG_CONFIG}]) have_PKG_CONFIG=yes PKG_CONFIG="$dummy_PKG_CONFIG" diff --git a/m4/nut_check_python.m4 b/m4/nut_check_python.m4 index 8f73edb991..a43a08bf32 100644 --- a/m4/nut_check_python.m4 +++ b/m4/nut_check_python.m4 @@ -4,10 +4,26 @@ dnl to embed into scripts and Make rules AC_DEFUN([NUT_CHECK_PYTHON_DEFAULT], [ dnl Check for all present variants and pick the default PYTHON + dnl Note that the --with... values may involve "auto-prio=NUM" + dnl which then sets only one of the values (lowest prio number + dnl wins) and discards the others even if detected. AC_REQUIRE([NUT_CHECK_PYTHON]) AC_REQUIRE([NUT_CHECK_PYTHON2]) AC_REQUIRE([NUT_CHECK_PYTHON3]) + dnl It seems AC REQUIRE calls have priority over other code lines in a method, + dnl and get executed first - so using extra methods (also for clearer code base). + + AC_REQUIRE([NUT_CHECK_PYTHON_INTERIM_RESULTS]) + AC_REQUIRE([NUT_CHECK_PYTHON_DEFAULT_BEST]) + + AC_REQUIRE([NUT_CHECK_PYTHON_SITE_PACKAGES]) + AC_REQUIRE([NUT_CHECK_PYTHON2_SITE_PACKAGES]) + AC_REQUIRE([NUT_CHECK_PYTHON3_SITE_PACKAGES]) +]) + +AC_DEFUN([NUT_CHECK_PYTHON_INTERIM_RESULTS], +[ AS_IF([test x"$PYTHON2" = xno], [PYTHON2=""]) AS_IF([test x"$PYTHON3" = xno], [PYTHON3=""]) AS_IF([test x"$PYTHON" = xno], [PYTHON=""]) @@ -36,13 +52,82 @@ AC_DEFUN([NUT_CHECK_PYTHON_DEFAULT], ]) ]) +AC_DEFUN([NUT_CHECK_PYTHON_DEFAULT_BEST], +[ + dnl Pick the top-most hit (smallest prio number, to be considered if no explicit variants were requested) + FOUND_PYTHONS="`( echo \"${nut_with_python}|${PYTHON}\"; echo \"${nut_with_python2}|${PYTHON2}\"; echo \"${nut_with_python3}|${PYTHON3}\" ) | ${EGREP} -v '\|\(no\)*$' | ${EGREP} -v '^no\|'`" + AS_IF([test x"${FOUND_PYTHONS}" = x], [ + AC_MSG_NOTICE([No Python interpreter versions were found or requested]) + ], [ + NON_AUTO="`echo \"${FOUND_PYTHONS}\" | ${EGREP} -v \"^auto-prio=\"`" + BEST_AUTO="`echo \"${FOUND_PYTHONS}\" | ${EGREP} \"^auto-prio=\" | sort -n | head -1`" + AS_IF([test x"${NON_AUTO}" = x], [ + dnl All findings were auto-prio, else silently keep what we have + dnl from "auto", "yes" or explicit settings + BEST_AUTO_PRIO="`echo \"${BEST_AUTO}\" | sed 's,|.*$,,'`" + BEST_AUTO_PYTHON="`echo \"${BEST_AUTO}\" | sed 's,^.*|,,'`" + AS_IF([test x"${BEST_AUTO_PYTHON}" != x], [ + AC_MSG_NOTICE([Got an auto-priority preferred hit: ${BEST_AUTO_PRIO} => ${BEST_AUTO_PYTHON}"]) + AS_CASE([x"${nut_with_python3}"], + [x"${BEST_AUTO_PRIO}"], [AC_MSG_NOTICE([Forgetting PYTHON2='${PYTHON2}' and PYTHON='${PYTHON}' (if any were detected as auto-prio too)]) + AS_CASE([x"${nut_with_python2}"], [xauto-prio=*], [PYTHON2=""]) + AS_CASE([x"${nut_with_python}"], [xauto-prio=*], [PYTHON=""]) + ],[AS_CASE([x"${nut_with_python2}"], + [x"${BEST_AUTO_PRIO}"], [AC_MSG_NOTICE([Forgetting PYTHON3='${PYTHON3}' and PYTHON='${PYTHON}' (if any were detected as auto-prio too)]) + AS_CASE([x"${nut_with_python3}"], [xauto-prio=*], [PYTHON3=""]) + AS_CASE([x"${nut_with_python}"], [xauto-prio=*], [PYTHON=""]) + ],[AS_CASE([x"${nut_with_python}"], + [x"${BEST_AUTO_PRIO}"], [AC_MSG_NOTICE([Forgetting PYTHON3='${PYTHON3}' and PYTHON2='${PYTHON2}' (if any were detected as auto-prio too)]) + AS_CASE([x"${nut_with_python2}"], [xauto-prio=*], [PYTHON2=""]) + AS_CASE([x"${nut_with_python3}"], [xauto-prio=*], [PYTHON3=""]) + ]) + ]) + ]) + ]) + ],[ + AS_IF([test x"${BEST_AUTO}" != x], [ + AC_MSG_NOTICE([Got some auto-priority preferred hits and explicitly preferred ones too; forgetting auto-prio ones"]) + AS_CASE([x"${nut_with_python3}"], [xauto-prio=*], [PYTHON3=""]) + AS_CASE([x"${nut_with_python2}"], [xauto-prio=*], [PYTHON2=""]) + AS_CASE([x"${nut_with_python}"], [xauto-prio=*], [PYTHON=""]) + ]) + ]) + ]) + + unset BEST_AUTO_PYTHON + unset BEST_AUTO_PRIO + unset BEST_AUTO + unset NON_AUTO + unset FOUND_PYTHONS + dnl Only now propagate what we found + + AC_SUBST([PYTHON], [${PYTHON}]) + AM_CONDITIONAL([HAVE_PYTHON], [test -n "${PYTHON}" && test "${PYTHON}" != "no"]) + + AC_SUBST([PYTHON2], [${PYTHON2}]) + AM_CONDITIONAL([HAVE_PYTHON2], [test -n "${PYTHON2}" && test "${PYTHON2}" != "no"]) + + AC_SUBST([PYTHON3], [${PYTHON3}]) + AM_CONDITIONAL([HAVE_PYTHON3], [test -n "${PYTHON3}" && test "${PYTHON3}" != "no"]) + + AC_MSG_CHECKING([which python can be called for internal use, e.g. shebang substitutions and tool calls]) + PYTHON_DEFAULT="" + AS_IF([test x"$PYTHON2" != x], [PYTHON_DEFAULT="${PYTHON2}"]) + AS_IF([test x"$PYTHON3" != x], [PYTHON_DEFAULT="${PYTHON3}"]) + AS_IF([test x"$PYTHON" != x], [PYTHON_DEFAULT="${PYTHON}"]) + AC_MSG_RESULT([${PYTHON_DEFAULT}]) + + AC_SUBST([PYTHON_DEFAULT], [${PYTHON_DEFAULT}]) + AM_CONDITIONAL([HAVE_PYTHON_DEFAULT], [test -n "${PYTHON_DEFAULT}" && test "${PYTHON_DEFAULT}" != "no"]) +]) + dnl Note: this checks for default/un-versioned python version dnl as the --with-python=SHEBANG_PATH setting into the PYTHON dnl variable; it may be further tweaked by NUT_CHECK_PYTHON_DEFAULT AC_DEFUN([NUT_CHECK_PYTHON], [ AS_IF([test -z "${nut_with_python}"], [ - NUT_ARG_WITH([python], [Use a particular program name of the python interpeter], [auto]) + NUT_ARG_WITH([python], [Use a particular program name of the python interpeter], [auto-prio=3]) PYTHON="" PYTHON_SITE_PACKAGES="" @@ -50,7 +135,7 @@ AC_DEFUN([NUT_CHECK_PYTHON], PYTHON_VERSION_INFO_REPORT="" PYTHON_SYSPATH_REPORT="" AS_CASE([${nut_with_python}], - [auto|yes|""], [AC_CHECK_PROGS([PYTHON], [python python3 python2], [_python_runtime])], + [auto|auto-prio=*|yes|""], [AC_CHECK_PROGS([PYTHON], [python python3 python2], [_python_runtime])], [no], [PYTHON="no"], [PYTHON="${nut_with_python}"] ) @@ -76,7 +161,7 @@ AC_DEFUN([NUT_CHECK_PYTHON], ], [*], [ dnl Note: no "realpath" here, see comment below - myPYTHON="`command -v "${PYTHON}" 2>/dev/null`" && test -n "${myPYTHON}" && test -x "${myPYTHON}" \ + myPYTHON="`command -v \"${PYTHON}\" 2>/dev/null`" && test -n "${myPYTHON}" && test -x "${myPYTHON}" \ && PYTHON="${myPYTHON}" \ || PYTHON="/usr/bin/env ${PYTHON}" unset myPYTHON @@ -108,15 +193,18 @@ AC_DEFUN([NUT_CHECK_PYTHON], dnl Unfulfilled "yes" is re-tested in NUT_CHECK_PYTHON_DEFAULT AS_IF([test -z "${PYTHON}" || test "${PYTHON}" = "no"], [ AS_CASE([${nut_with_python}], - [auto|yes|no|""], [], + [auto|auto-prio=*|yes|no|""], [], [AC_MSG_ERROR([A python interpreter was required but not found or validated: ${nut_with_python}])]) ]) AC_MSG_CHECKING([python interpeter to call]) 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_DEFUN([NUT_CHECK_PYTHON_SITE_PACKAGES], +[ + 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' ' '`" \ @@ -129,28 +217,41 @@ AC_DEFUN([NUT_CHECK_PYTHON], AC_MSG_RESULT([${PYTHON_SYSPATH_REPORT}]) export PYTHON + AS_IF([test x"${nut_enable_configure_debug}" = xyes], [AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) nut_with_python_modules_dir='${nut_with_python_modules_dir}'])]) AC_CACHE_CHECK([python site-packages location], [nut_cv_PYTHON_SITE_PACKAGES], [ - dnl sysconfig introduced in python3.2 - AS_IF([test x"`${PYTHON} -c 'import sys; print (sys.version_info >= (3, 2))'`" = xTrue], - [nut_cv_PYTHON_SITE_PACKAGES="`${PYTHON} -c 'import sysconfig; print(sysconfig.get_path("purelib"))'`"], - [nut_cv_PYTHON_SITE_PACKAGES="`${PYTHON} -c 'import site; print(site.getsitepackages().pop(0))'`"]) + AS_CASE(["${nut_with_python_modules_dir}"], + [*:*|*/*], [ dnl # else not a path => auto below + printf "Using caller-provided location... " + nut_cv_PYTHON_SITE_PACKAGES="${nut_with_python_modules_dir}"], + [printf "Using interpreter-provided location... " + dnl sysconfig introduced in python3.2 and 2.7 according to + dnl https://docs.python.org/3/library/sysconfig.html + dnl https://docs.python.org/2.7/library/sysconfig.html + dnl Note that the list at getsitepackages() MAY start + dnl with a "platlib" location (including compiled code): + AS_IF([test x"`${PYTHON} -c 'import sys; print (sys.version_info >= (3, 2))'`" = xTrue || test x"`${PYTHON} -c 'import sys; print (sys.version_info >= (2, 7) and sys.version_info < (3, 0))'`"], + [nut_cv_PYTHON_SITE_PACKAGES="`${PYTHON} -c 'import sysconfig; print(sysconfig.get_path(\"purelib\"))'`"], + [nut_cv_PYTHON_SITE_PACKAGES="`${PYTHON} -c 'import site; print(site.getsitepackages().pop(0))'`"]) + ] + ) AS_CASE(["$nut_cv_PYTHON_SITE_PACKAGES"], [*:*], [ dnl Note: on Windows MSYS2 this embeds "C:/msys64/mingw..." into the string [nut#1584] - nut_cv_PYTHON_SITE_PACKAGES="`cd "$nut_cv_PYTHON_SITE_PACKAGES" && pwd`" + nut_cv_PYTHON_SITE_PACKAGES="`cd \"$nut_cv_PYTHON_SITE_PACKAGES\" && pwd`" ] - ) + ) ]) - ]) - AC_SUBST([PYTHON_SITE_PACKAGES], [${nut_cv_PYTHON_SITE_PACKAGES}]) - AM_CONDITIONAL([HAVE_PYTHON_SITE_PACKAGES], [test x"${PYTHON_SITE_PACKAGES}" != "x"]) + ],[ + nut_cv_PYTHON_SITE_PACKAGES="" ]) + AC_SUBST([PYTHON_SITE_PACKAGES], [${nut_cv_PYTHON_SITE_PACKAGES}]) + AM_CONDITIONAL([HAVE_PYTHON_SITE_PACKAGES], [test x"${PYTHON_SITE_PACKAGES}" != "x"]) ]) AC_DEFUN([NUT_CHECK_PYTHON2], [ AS_IF([test -z "${nut_with_python2}"], [ - NUT_ARG_WITH([python2], [Use a particular program name of the python2 interpeter for code that needs that version and is not compatible with python3], [auto]) + NUT_ARG_WITH([python2], [Use a particular program name of the python2 interpeter for code that needs that version and is not compatible with python3], [auto-prio=2]) PYTHON2="" PYTHON2_SITE_PACKAGES="" @@ -158,21 +259,21 @@ AC_DEFUN([NUT_CHECK_PYTHON2], PYTHON2_VERSION_INFO_REPORT="" PYTHON2_SYSPATH_REPORT="" AS_CASE([${nut_with_python2}], - [auto|yes|""], [ + [auto|auto-prio=*|yes|""], [ dnl Cross check --with-python results: 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}" || { + PYTHON2="`realpath \"${PYTHON2}\" 2>/dev/null`" && test -n "${PYTHON2}" || { PYTHON2="${PYTHON}" - PYTHON_CONFIG="`command -v "${PYTHON}-config" 2>/dev/null`" || PYTHON_CONFIG="" + PYTHON_CONFIG="`command -v \"${PYTHON}-config\" 2>/dev/null`" || PYTHON_CONFIG="" if test -n "${PYTHON_CONFIG}" ; then mySHEBANG_SCRIPT="`${PYTHON_CONFIG} --config-dir 2>/dev/null`/python-config.py" \ || mySHEBANG_SCRIPT="${PYTHON_CONFIG}" if test -f "${mySHEBANG_SCRIPT}" ; then - mySHEBANG="`head -1 "${mySHEBANG_SCRIPT}" | grep -E '^#!'`" || mySHEBANG="" + mySHEBANG="`head -1 \"${mySHEBANG_SCRIPT}\" | ${EGREP} '^#!'`" || mySHEBANG="" if test -n "${mySHEBANG}" ; then - PYTHON2="`echo "${mySHEBANG}" | sed 's,^#! *,,'`" \ + PYTHON2="`echo \"${mySHEBANG}\" | sed 's,^#! *,,'`" \ && test -n "${PYTHON2}" || PYTHON2="${PYTHON}" fi fi @@ -219,7 +320,7 @@ AC_DEFUN([NUT_CHECK_PYTHON2], PYTHON2="/usr/bin/env ${PYTHON2}" ], [*], [ - myPYTHON="`command -v "${PYTHON2}" 2>/dev/null`" && test -n "${myPYTHON}" && test -x "${myPYTHON}" \ + myPYTHON="`command -v \"${PYTHON2}\" 2>/dev/null`" && test -n "${myPYTHON}" && test -x "${myPYTHON}" \ && PYTHON2="${myPYTHON}" \ || PYTHON2="/usr/bin/env ${PYTHON2}" unset myPYTHON @@ -236,15 +337,18 @@ AC_DEFUN([NUT_CHECK_PYTHON2], dnl Unfulfilled "yes" is re-tested in NUT_CHECK_PYTHON_DEFAULT AS_IF([test -z "${PYTHON2}" || test "${PYTHON2}" = "no"], [ AS_CASE([${nut_with_python2}], - [auto|yes|no|""], [], + [auto|auto-prio=*|yes|no|""], [], [AC_MSG_ERROR([A python2 interpreter was required but not found or validated: ${nut_with_python2}])]) ]) AC_MSG_CHECKING([python2 interpeter to call]) 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_DEFUN([NUT_CHECK_PYTHON2_SITE_PACKAGES], +[ + 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' ' '`" \ @@ -257,25 +361,39 @@ AC_DEFUN([NUT_CHECK_PYTHON2], AC_MSG_RESULT([${PYTHON2_SYSPATH_REPORT}]) export PYTHON2 + AS_IF([test x"${nut_enable_configure_debug}" = xyes], [AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) nut_with_python2_modules_dir='${nut_with_python2_modules_dir}'])]) 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))'`" + AS_CASE(["${nut_with_python2_modules_dir}"], + [*:*|*/*], [ dnl # else not a path => auto below + printf "Using caller-provided location... " + nut_cv_PYTHON2_SITE_PACKAGES="${nut_with_python2_modules_dir}"], + [printf "Using interpreter-provided location... " + dnl sysconfig introduced in python2.7 according to + dnl Note that the list at getsitepackages() MAY start + dnl with a "platlib" location (including compiled code): + AS_IF([test x"`${PYTHON2} -c 'import sys; print (sys.version_info >= (2, 7) and sys.version_info < (3, 0))'`"], + [nut_cv_PYTHON2_SITE_PACKAGES="`${PYTHON2} -c 'import sysconfig; print(sysconfig.get_path(\"purelib\"))'`"], + [nut_cv_PYTHON2_SITE_PACKAGES="`${PYTHON2} -c 'import site; print(site.getsitepackages().pop(0))'`"]) + ] + ) AS_CASE(["$nut_cv_PYTHON2_SITE_PACKAGES"], [*:*], [ dnl Note: on Windows MSYS2 this embeds "C:/msys64/mingw..." into the string [nut#1584] - nut_cv_PYTHON2_SITE_PACKAGES="`cd "$nut_cv_PYTHON2_SITE_PACKAGES" && pwd`" + nut_cv_PYTHON2_SITE_PACKAGES="`cd \"$nut_cv_PYTHON2_SITE_PACKAGES\" && pwd`" ] ) ]) - ]) - AC_SUBST([PYTHON2_SITE_PACKAGES], [${nut_cv_PYTHON2_SITE_PACKAGES}]) - AM_CONDITIONAL([HAVE_PYTHON2_SITE_PACKAGES], [test x"${PYTHON2_SITE_PACKAGES}" != "x"]) + ],[ + nut_cv_PYTHON2_SITE_PACKAGES="" ]) + AC_SUBST([PYTHON2_SITE_PACKAGES], [${nut_cv_PYTHON2_SITE_PACKAGES}]) + AM_CONDITIONAL([HAVE_PYTHON2_SITE_PACKAGES], [test x"${PYTHON2_SITE_PACKAGES}" != "x"]) ]) AC_DEFUN([NUT_CHECK_PYTHON3], [ AS_IF([test -z "${nut_with_python3}"], [ - NUT_ARG_WITH([python3], [Use a particular program name of the python3 interpeter for code that needs that version and is not compatible with python2], [auto]) + NUT_ARG_WITH([python3], [Use a particular program name of the python3 interpeter for code that needs that version and is not compatible with python2], [auto-prio=1]) PYTHON3="" PYTHON3_SITE_PACKAGES="" @@ -283,21 +401,21 @@ AC_DEFUN([NUT_CHECK_PYTHON3], PYTHON3_VERSION_INFO_REPORT="" PYTHON3_SYSPATH_REPORT="" AS_CASE([${nut_with_python3}], - [auto|yes|""], [ + [auto|auto-prio=*|yes|""], [ dnl Cross check --with-python results: 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}" || { + PYTHON3="`realpath \"${PYTHON3}\" 2>/dev/null`" && test -n "${PYTHON3}" || { PYTHON3="${PYTHON}" - PYTHON_CONFIG="`command -v "${PYTHON}-config" 2>/dev/null`" || PYTHON_CONFIG="" + PYTHON_CONFIG="`command -v \"${PYTHON}-config\" 2>/dev/null`" || PYTHON_CONFIG="" if test -n "${PYTHON_CONFIG}" ; then mySHEBANG_SCRIPT="`${PYTHON_CONFIG} --config-dir 2>/dev/null`/python-config.py" \ || mySHEBANG_SCRIPT="${PYTHON_CONFIG}" if test -f "${mySHEBANG_SCRIPT}" ; then - mySHEBANG="`head -1 "${mySHEBANG_SCRIPT}" | grep -E '^#!'`" || mySHEBANG="" + mySHEBANG="`head -1 \"${mySHEBANG_SCRIPT}\" | ${EGREP} '^#!'`" || mySHEBANG="" if test -n "${mySHEBANG}" ; then - PYTHON3="`echo "${mySHEBANG}" | sed 's,^#! *,,'`" \ + PYTHON3="`echo \"${mySHEBANG}\" | sed 's,^#! *,,'`" \ && test -n "${PYTHON3}" || PYTHON3="${PYTHON}" fi fi @@ -344,7 +462,7 @@ AC_DEFUN([NUT_CHECK_PYTHON3], PYTHON3="/usr/bin/env ${PYTHON3}" ], [*], [ - myPYTHON="`command -v "${PYTHON3}" 2>/dev/null`" && test -n "${myPYTHON}" && test -x "${myPYTHON}" \ + myPYTHON="`command -v \"${PYTHON3}\" 2>/dev/null`" && test -n "${myPYTHON}" && test -x "${myPYTHON}" \ && PYTHON3="${myPYTHON}" \ || PYTHON3="/usr/bin/env ${PYTHON3}" unset myPYTHON @@ -361,15 +479,18 @@ AC_DEFUN([NUT_CHECK_PYTHON3], dnl Unfulfilled "yes" is re-tested in NUT_CHECK_PYTHON_DEFAULT AS_IF([test -z "${PYTHON3}" || test "${PYTHON3}" = "no"], [ AS_CASE([${nut_with_python3}], - [auto|yes|no|""], [], + [auto|auto-prio=*|yes|no|""], [], [AC_MSG_ERROR([A python3 interpreter was required but not found or validated: ${nut_with_python3}])]) ]) AC_MSG_CHECKING([python3 interpeter to call]) 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_DEFUN([NUT_CHECK_PYTHON3_SITE_PACKAGES], +[ + 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' ' '`" \ @@ -382,20 +503,31 @@ AC_DEFUN([NUT_CHECK_PYTHON3], AC_MSG_RESULT([${PYTHON3_SYSPATH_REPORT}]) export PYTHON3 + AS_IF([test x"${nut_enable_configure_debug}" = xyes], [AC_MSG_NOTICE([(CONFIGURE-DEVEL-DEBUG) nut_with_python3_modules_dir='${nut_with_python3_modules_dir}'])]) AC_CACHE_CHECK([python3 site-packages location], [nut_cv_PYTHON3_SITE_PACKAGES], [ - dnl sysconfig introduced in python3.2 - AS_IF([test x"`${PYTHON3} -c 'import sys; print (sys.version_info >= (3, 2))'`" = xTrue], - [nut_cv_PYTHON3_SITE_PACKAGES="`${PYTHON3} -c 'import sysconfig; print(sysconfig.get_path("purelib"))'`"], - [nut_cv_PYTHON3_SITE_PACKAGES="`${PYTHON3} -c 'import site; print(site.getsitepackages().pop(0))'`"]) + AS_CASE(["${nut_with_python3_modules_dir}"], + [*:*|*/*], [ dnl # else not a path => auto below + printf "Using caller-provided location... " + nut_cv_PYTHON3_SITE_PACKAGES="${nut_with_python3_modules_dir}"], + [printf "Using interpreter-provided location... " + dnl sysconfig introduced in python3.2 + dnl Note that the list at getsitepackages() MAY start + dnl with a "platlib" location (including compiled code): + AS_IF([test x"`${PYTHON3} -c 'import sys; print (sys.version_info >= (3, 2))'`" = xTrue], + [nut_cv_PYTHON3_SITE_PACKAGES="`${PYTHON3} -c 'import sysconfig; print(sysconfig.get_path(\"purelib\"))'`"], + [nut_cv_PYTHON3_SITE_PACKAGES="`${PYTHON3} -c 'import site; print(site.getsitepackages().pop(0))'`"]) + ] + ) AS_CASE(["$nut_cv_PYTHON3_SITE_PACKAGES"], [*:*], [ dnl Note: on Windows MSYS2 this embeds "C:/msys64/mingw..." into the string [nut#1584] - nut_cv_PYTHON3_SITE_PACKAGES="`cd "$nut_cv_PYTHON3_SITE_PACKAGES" && pwd`" + nut_cv_PYTHON3_SITE_PACKAGES="`cd \"$nut_cv_PYTHON3_SITE_PACKAGES\" && pwd`" ] ) ]) - ]) - AC_SUBST([PYTHON3_SITE_PACKAGES], [${nut_cv_PYTHON3_SITE_PACKAGES}]) - AM_CONDITIONAL([HAVE_PYTHON3_SITE_PACKAGES], [test x"${PYTHON3_SITE_PACKAGES}" != "x"]) + ],[ + nut_cv_PYTHON3_SITE_PACKAGES="" ]) + AC_SUBST([PYTHON3_SITE_PACKAGES], [${nut_cv_PYTHON3_SITE_PACKAGES}]) + AM_CONDITIONAL([HAVE_PYTHON3_SITE_PACKAGES], [test x"${PYTHON3_SITE_PACKAGES}" != "x"]) ]) diff --git a/m4/nut_compiler_family.m4 b/m4/nut_compiler_family.m4 index cfa3523e1a..f1719c8ae3 100644 --- a/m4/nut_compiler_family.m4 +++ b/m4/nut_compiler_family.m4 @@ -15,7 +15,7 @@ if test -z "${nut_compiler_family_seen}"; then AC_CACHE_CHECK([if CC compiler family is GCC], [nut_cv_GCC], [AS_IF([test -n "$CC" && test -n "$CC_VERSION_FULL"], - [AS_IF([echo "${CC_VERSION_FULL}" | grep 'Free Software Foundation' > /dev/null], + [AS_IF([echo "${CC_VERSION_FULL}" | ${GREP} 'Free Software Foundation' > /dev/null], [nut_cv_GCC=yes],[nut_cv_GCC=no])], [AC_MSG_ERROR([CC is not set])] )]) @@ -23,7 +23,7 @@ if test -z "${nut_compiler_family_seen}"; then AC_CACHE_CHECK([if CXX compiler family is GCC], [nut_cv_GXX], [AS_IF([test -n "$CXX" && test -n "$CXX_VERSION_FULL"], - [AS_IF([echo "${CXX_VERSION_FULL}" | grep 'Free Software Foundation' > /dev/null], + [AS_IF([echo "${CXX_VERSION_FULL}" | ${GREP} 'Free Software Foundation' > /dev/null], [nut_cv_GXX=yes],[nut_cv_GXX=no])], [AC_MSG_ERROR([CXX is not set])] )]) @@ -31,28 +31,28 @@ if test -z "${nut_compiler_family_seen}"; then AC_CACHE_CHECK([if CPP preprocessor family is GCC], [nut_cv_GPP], [AS_IF([test -n "$CPP" && test -n "$CPP_VERSION_FULL"], - [AS_IF([echo "${CPP_VERSION_FULL}" | grep 'Free Software Foundation' > /dev/null], + [AS_IF([echo "${CPP_VERSION_FULL}" | ${GREP} 'Free Software Foundation' > /dev/null], [nut_cv_GPP=yes],[nut_cv_GPP=no])], [AC_MSG_ERROR([CPP is not set])] )]) AS_IF([test "x$GCC" = "x" && test "$nut_cv_GCC" = yes], [GCC=yes - CC_VERSION="`echo "${CC_VERSION_FULL}" | grep -i gcc | head -1`" \ + CC_VERSION="`echo \"${CC_VERSION_FULL}\" | ${GREP} -i gcc | head -1`" \ && test -n "${CC_VERSION}" || CC_VERSION="" ]) AS_IF([test "x$GXX" = "x" && test "$nut_cv_GXX" = yes], [GXX=yes - CXX_VERSION="`echo "${CXX_VERSION_FULL}" | grep -i -E 'g++|gcc' | head -1`" \ + CXX_VERSION="`echo \"${CXX_VERSION_FULL}\" | ${EGREP} -i 'g++|gcc' | head -1`" \ && test -n "${CXX_VERSION}" || CXX_VERSION="" ]) AS_IF([test "x$GPP" = "x" && test "$nut_cv_GPP" = yes], [GPP=yes - CPP_VERSION="`echo "${CPP_VERSION_FULL}" | grep -i -E 'cpp|gcc' | head -1`" \ + CPP_VERSION="`echo \"${CPP_VERSION_FULL}\" | ${EGREP} -i 'cpp|gcc' | head -1`" \ && test -n "${CPP_VERSION}" || CPP_VERSION="" ]) AC_CACHE_CHECK([if CC compiler family is clang], [nut_cv_CLANGCC], [AS_IF([test -n "$CC" && test -n "$CC_VERSION_FULL"], - [AS_IF([echo "${CC_VERSION_FULL}" | grep -E '(clang version|Apple LLVM version .*clang-)' > /dev/null], + [AS_IF([echo "${CC_VERSION_FULL}" | ${EGREP} '(clang version|Apple LLVM version .*clang-)' > /dev/null], [nut_cv_CLANGCC=yes],[nut_cv_CLANGCC=no])], [AC_MSG_ERROR([CC is not set])] )]) @@ -60,7 +60,7 @@ if test -z "${nut_compiler_family_seen}"; then AC_CACHE_CHECK([if CXX compiler family is clang], [nut_cv_CLANGXX], [AS_IF([test -n "$CXX" && test -n "$CXX_VERSION_FULL"], - [AS_IF([echo "${CXX_VERSION_FULL}" | grep -E '(clang version|Apple LLVM version .*clang-)' > /dev/null], + [AS_IF([echo "${CXX_VERSION_FULL}" | ${EGREP} '(clang version|Apple LLVM version .*clang-)' > /dev/null], [nut_cv_CLANGXX=yes],[nut_cv_CLANGXX=no])], [AC_MSG_ERROR([CXX is not set])] )]) @@ -68,27 +68,27 @@ if test -z "${nut_compiler_family_seen}"; then AC_CACHE_CHECK([if CPP preprocessor family is clang], [nut_cv_CLANGPP], [AS_IF([test -n "$CPP" && test -n "$CPP_VERSION_FULL"], - [AS_IF([echo "${CPP_VERSION_FULL}" | grep -E '(clang version|Apple LLVM version .*clang-)' > /dev/null], + [AS_IF([echo "${CPP_VERSION_FULL}" | ${EGREP} '(clang version|Apple LLVM version .*clang-)' > /dev/null], [nut_cv_CLANGPP=yes],[nut_cv_CLANGPP=no])], [AC_MSG_ERROR([CPP is not set])] )]) AS_IF([test "x$CLANGCC" = "x" && test "$nut_cv_CLANGCC" = yes], [CLANGCC=yes - CC_VERSION="`echo "${CC_VERSION_FULL}" | grep -v "Dir:" | tr '\n' ';' | sed -e 's, *;,;,g' -e 's,;$,,' -e 's,;,; ,g'`" \ + CC_VERSION="`echo \"${CC_VERSION_FULL}\" | ${GREP} -v \"Dir:\" | tr '\n' ';' | sed -e 's, *;,;,g' -e 's,;$,,' -e 's,;,; ,g'`" \ && test -n "${CC_VERSION}" || CC_VERSION="" ]) AS_IF([test "x$CLANGXX" = "x" && test "$nut_cv_CLANGXX" = yes], [CLANGXX=yes - CXX_VERSION="`echo "${CXX_VERSION_FULL}" | grep -v "Dir:" | tr '\n' ';' | sed -e 's, *;,;,g' -e 's,;$,,' -e 's,;,; ,g'`" \ + CXX_VERSION="`echo \"${CXX_VERSION_FULL}\" | ${GREP} -v \"Dir:\" | tr '\n' ';' | sed -e 's, *;,;,g' -e 's,;$,,' -e 's,;,; ,g'`" \ && test -n "${CXX_VERSION}" || CXX_VERSION="" ]) AS_IF([test "x$CLANGPP" = "x" && test "$nut_cv_CLANGPP" = yes], [CLANGPP=yes - CPP_VERSION="`echo "${CPP_VERSION_FULL}" | grep -v "Dir:" | tr '\n' ';' | sed -e 's, *;,;,g' -e 's,;$,,' -e 's,;,; ,g'`" \ + CPP_VERSION="`echo \"${CPP_VERSION_FULL}\" | ${GREP} -v \"Dir:\" | tr '\n' ';' | sed -e 's, *;,;,g' -e 's,;$,,' -e 's,;,; ,g'`" \ && test -n "${CPP_VERSION}" || CPP_VERSION="" ]) - 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`"]) + 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 ]) diff --git a/obs/control b/obs/control deleted file mode 120000 index 467d6decea..0000000000 --- a/obs/control +++ /dev/null @@ -1 +0,0 @@ -debian.control \ No newline at end of file diff --git a/obs/debian.nut-cgi.docs b/obs/debian.nut-cgi.docs deleted file mode 100644 index 80796c9b00..0000000000 --- a/obs/debian.nut-cgi.docs +++ /dev/null @@ -1 +0,0 @@ -data/html/README diff --git a/obs/debian.nut.docs b/obs/debian.nut.docs deleted file mode 100644 index d0026f76e3..0000000000 --- a/obs/debian.nut.docs +++ /dev/null @@ -1,18 +0,0 @@ -AUTHORS -MAINTAINERS -README -UPGRADING -docs/acknowledgements.txt -docs/config-notes.txt -docs/documentation.txt -docs/download.txt -docs/FAQ.txt -docs/features.txt -docs/history.txt -docs/nut-names.txt -docs/outlets.txt -docs/packager-guide.txt -docs/scheduling.txt -docs/security.txt -docs/support.txt -docs/user-manual.txt diff --git a/obs/nut.spec b/obs/nut.spec deleted file mode 100644 index ab28be0f30..0000000000 --- a/obs/nut.spec +++ /dev/null @@ -1,382 +0,0 @@ -# -# spec file for package nut.spec -# -# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. -# Copyright (c) 2016-2018 Eaton EEIC. -# -# All modifications and additions to the file contributed by third parties -# remain the property of their copyright owners, unless otherwise agreed -# upon. The license for this file, and modifications and additions to the -# file, is the same license as for the pristine package itself (unless the -# license for the pristine package is not an Open Source License, in which -# case the license is the MIT License). An "Open Source License" is a -# license that conforms to the Open Source Definition (Version 1.9) -# published by the Open Source Initiative. - -# Please submit bugfixes or comments via http://bugs.opensuse.org/ -# - - -%define apache_serverroot %(%{_sbindir}/apxs2 -q datadir 2>/dev/null || %{_sbindir}/apxs -q PREFIX) -%define CGIPATH %{apache_serverroot}/cgi-bin -%define HTMLPATH %{apache_serverroot}/htdocs -%define MODELPATH %{_libexecdir}/ups/driver -%define STATEPATH %{_localstatedir}/lib/ups -%define CONFPATH %{_sysconfdir}/ups -### Note: this is /etc/nut in Debian version -%define USER upsd -%define GROUP daemon -%define LBRACE ( -%define RBRACE ) -%define QUOTE " -%define BACKSLASH \\ -# Collect all devices listed in ups-nut-device.fdi: -%define USBHIDDRIVERS %(zcat %{SOURCE0} | tr a-z A-Z | fgrep -a -A1 USBHID-UPS | sed -n 's/.*ATTR{IDVENDOR}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, ATTR{IDPRODUCT}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, MODE=.*/modalias%{LBRACE}usb:v%{BACKSLASH}1p%{BACKSLASH}2d*dc*dsc*dp*ic*isc*ip*%{RBRACE}/p' | tr '%{BACKSLASH}n' ' ') -%define USBNONHIDDRIVERS %(zcat %{SOURCE0} | tr a-z A-Z | fgrep -a -A1 _USB | sed -n 's/.*ATTR{IDVENDOR}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, ATTR{IDPRODUCT}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, MODE=.*/modalias%{LBRACE}usb:v%{BACKSLASH}1p%{BACKSLASH}2d*dc*dsc*dp*ic*isc*ip*%{RBRACE}/p' | tr '%{BACKSLASH}n' ' ') -%define systemdsystemunitdir %(pkg-config --variable=systemdsystemunitdir systemd) -%define systemdsystemdutildir %(pkg-config --variable=systemdutildir systemd) -%define systemdshutdowndir %(pkg-config --variable=systemdshutdowndir systemd) - -Name: nut -Version: 2.7.4 -Release: 12 -Summary: Network UPS Tools Core (Uninterruptible Power Supply Monitoring) -License: GPL-2.0+ -Group: Hardware/UPS -Url: http://www.networkupstools.org/ -Source0: %{name}-%{version}.tar.gz - -Requires: %{_bindir}/fgrep -Requires: %{_bindir}/grep -Requires: %{_bindir}/pgrep -Requires: %{_bindir}/pkill -Requires: %{_bindir}/readlink -Requires: usbutils -%if 0%{?suse_version} -Requires(post): udev -%endif -BuildRoot: %{_tmppath}/%{name}-%{version}-build - -BuildRequires: avahi-devel -# To fix end-of-line encoding: -BuildRequires: dos2unix -BuildRequires: freeipmi-devel -BuildRequires: gcc-c++ -BuildRequires: gd-devel -BuildRequires: libtool -BuildRequires: libtool-ltdl-devel -BuildRequires: libusb-devel -BuildRequires: net-snmp-devel -BuildRequires: pkg-config -BuildRequires: python -# LUA 5.1 or 5.2 is known ok for us, both are modern in current distros (201609xx) -BuildRequires: lua-devel -# TODO: Make sure how this is named to use in CentOS/RHEL (may be not in core but EPEL repos) -# The pycparser is required to rebuild DMF files, but those pre-built -# copies in the git repo/tarball "should" be in sync with original -# C files, so we don't require regeneration for packaging. Also the -# Jenkins NUT-master job should have verified this. -#BuildRequires: python-pycparser - -%if 0%{?suse_version} -BuildRequires: apache2-devel -BuildRequires: dbus-1-glib-devel -BuildRequires: libcppunit-devel -BuildRequires: libneon-devel -BuildRequires: libopenssl-devel -BuildRequires: systemd-rpm-macros -BuildRequires: powerman-devel -BuildRequires: tcpd-devel -# TODO: For doc build: move out of opensuse -###BuildRequires: asciidoc -BuildRequires: dblatex -BuildRequires: libxslt-tools -%endif -BuildRequires: asciidoc - -%if 0%{?centos_version} -BuildRequires: cppunit-devel -BuildRequires: dbus-glib-devel -BuildRequires: httpd-devel -BuildRequires: neon-devel -BuildRequires: openssl-devel -BuildRequires: tcp_wrappers-devel -BuildRequires: libxslt -%endif - -%if 0%{?rhel_version}>=7 -BuildRequires: dbus-glib-devel -BuildRequires: httpd-devel -BuildRequires: libusb -BuildRequires: neon -BuildRequires: openssl-devel -BuildRequires: tcp_wrappers-devel -BuildRequires: libxslt -%endif - -%if %{defined opensuse_version} -# Package provides driver for USB HID UPSes, but people can live with hal addon: -Enhances: %{USBHIDDRIVERS} -# Package provides the only avalailable driver for other USB UPSes: -Supplements: %{USBNONHIDDRIVERS} -%systemd_requires -%endif - -%description -Core package of Network UPS Tools. - -Network UPS Tools is a collection of programs which provide a common -interface for monitoring and administering UPS hardware. - -Detailed information about supported hardware can be found in -%{_docdir}/nut. - -%package drivers-net -Summary: Network UPS Tools - Extra Networking Drivers (for Network Monitoring) -Group: Hardware/UPS -Requires: %{name} = %{version} - -%description drivers-net -Networking drivers for the Network UPS Tools. You will need them -together with nut to provide UPS networking support. - -Network UPS Tools is a collection of programs which provide a common -interface for monitoring and administering UPS hardware. - -Detailed information about supported hardware can be found in -%{_docdir}/nut. - -%package -n libupsclient1 -Summary: Network UPS Tools Library (Uninterruptible Power Supply Monitoring) -Group: System/Libraries - -%description -n libupsclient1 -Shared library for the Network UPS Tools. - -Network UPS Tools is a collection of programs which provide a common -interface for monitoring and administering UPS hardware. - -Detailed information about supported hardware can be found in -%{_docdir}/nut. - -%package cgi -Summary: Network UPS Tools Web Server Support (UPS Status Pages) -Group: Hardware/UPS -Requires: %{name} = %{version} - -%description cgi -Web server support package for the Network UPS Tools. - -Predefined URL is http://localhost/nut/index.html - -Network UPS Tools is a collection of programs which provide a common -interface for monitoring and administering UPS hardware. - -Detailed information about supported hardware can be found in -%{_docdir}/nut. - -%package devel -Summary: Network UPS Tools (Uninterruptible Power Supply Monitoring) -Group: Development/Libraries/C and C++ -Requires: %{name} = %{version} -Requires: openssl-devel - -%description devel -Network UPS Tools is a collection of programs which provide a common -interface for monitoring and administering UPS hardware. - -Detailed information about supported hardware can be found in -%{_docdir}/nut. - -%prep -%setup -q - -# Note: NOT configure macro, due to override of --sysconfdir and --datadir -# values just for the recipe part but not for whole specfile -%build -sh autogen.sh -./configure --disable-static --with-pic \ - --prefix=%{_prefix}\ - --bindir=%{_bindir}\ - --sbindir=%{_sbindir}\ - --libdir=%{_libdir}\ - --libexecdir=%{_libexecdir}\ - --sysconfdir=%{CONFPATH}\ - --datadir=%{_datadir}/nut\ - --with-ssl --with-openssl\ - --with-libltdl=yes\ - --with-cgi=auto\ - --with-serial\ - --with-usb\ - --with-snmp\ - --with-neon\ - --with-snmp_dmf_lua\ - --with-dev\ - --with-ipmi \ - --with-powerman=auto\ - --with-doc=man=dist-auto\ - --with-htmlpath=%{HTMLPATH}\ - --with-cgipath=%{CGIPATH}\ - --with-statepath=%{STATEPATH}\ - --with-drvpath=%{MODELPATH}\ - --with-user=%{USER}\ - --with-group=%{GROUP} \ - --with-udev-dir=%{_sysconfdir}/udev \ - --enable-option-checking=fatal\ - --with-systemdsystemunitdir --with-systemdshutdowndir \ - --with-augeas-lenses-dir=/usr/share/augeas/lenses/dist \ - --with-dmfsnmp-regenerate=no --with-dmfnutscan-regenerate=no --with-dmfsnmp-validate=no --with-dmfnutscan-validate=no - -(cd tools; python nut-snmpinfo.py) - -make %{?_smp_mflags} -PORT=$(sed -n 's/#define PORT //p' config.log) -if test "$PORT" = 3493 ; then - PORT=nut -fi - -%install -make DESTDIR=%{buildroot} install %{?_smp_mflags} -mkdir -p %{buildroot}%{STATEPATH} -# SuSE rc -mkdir -p %{buildroot}%{_sbindir} -mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d -install -m 644 scripts/logrotate/nutlogd %{buildroot}%{_sysconfdir}/logrotate.d/ -mkdir -p %{buildroot}%{STATEPATH} -rename .sample "" %{buildroot}%{_sysconfdir}/ups/*.sample -mkdir -p %{buildroot}/bin -mv %{buildroot}%{_bindir}/upssched-cmd %{buildroot}/bin/upssched-cmd -# Rename web pages to not conflict with apache2-example-pages or user home page: -mkdir -p %{buildroot}%{HTMLPATH}/nut %{buildroot}%{CGIPATH}/nut -mv %{buildroot}%{HTMLPATH}/*.{html,png} %{buildroot}%{HTMLPATH}/nut/ -mv %{buildroot}%{CGIPATH}/*.cgi %{buildroot}%{CGIPATH}/nut -find %{buildroot} -type f -name "*.la" -delete -print -mkdir -p %{buildroot}%{_sysconfdir}/bash_completion.d -install -m0644 scripts/misc/nut.bash_completion %{buildroot}%{_sysconfdir}/bash_completion.d/ -install -m0755 scripts/subdriver/gen-snmp-subdriver.sh %{buildroot}%{_sbindir}/ - -%pre -usr/sbin/useradd -r -g %{GROUP} -s /bin/false \ - -c "UPS daemon" -d /sbin %{USER} 2>/dev/null || : -%if %{defined opensuse_version} -%service_add_pre nut-driver@.service nut-server.service nut-monitor.service nut-driver.target nut.target -%endif - -%post -# Be sure that all files are owned by a dedicated user. -bin/chown -R %{USER}:%{GROUP} %{STATEPATH} -# Be sure that all files are owned by a dedicated user. -bin/chown %{USER}:root %{CONFPATH}/upsd.conf %{CONFPATH}/upsmon.conf %{CONFPATH}/upsd.users -bin/chmod 600 %{CONFPATH}/upsd.conf %{CONFPATH}/upsmon.conf %{CONFPATH}/upsd.users -# And finally trigger udev to set permissions according to newly installed rules files. -/sbin/udevadm trigger --subsystem-match=usb --property-match=DEVTYPE=usb_device -%if %{defined opensuse_version} -%service_add_post nut-driver@.service nut-server.service nut-monitor.service nut-driver-enumerator.service nut-driver.target nut.target -%endif - -%preun -%if %{defined opensuse_version} -%service_del_preun nut-driver@.service nut-server.service nut-monitor.service nut-driver-enumerator.service nut-driver.target nut.target -%endif - -%postun -%if %{defined opensuse_version} -%service_del_postun nut-driver@.service nut-server.service nut-monitor.service nut-driver-enumerator.service nut-driver.target nut.target -%endif - -%post -n libupsclient1 -p /sbin/ldconfig - -%postun -n libupsclient1 -p /sbin/ldconfig - -%files -%defattr(-,root,root) -%doc AUTHORS COPYING ChangeLog MAINTAINERS NEWS README UPGRADING docs/*.txt docs/cables -/bin/* -%{_sysconfdir}/bash_completion.d/* -%{_sysconfdir}/logrotate.d/* -%{_bindir}/* -%exclude %{_bindir}/nut-scanner-reindex-dmfsnmp -%{_datadir}/nut -%exclude %{_datadir}/nut/dmfnutscan -%exclude %{_datadir}/nut/dmfsnmp -%exclude %{_datadir}/nut/dmfnutscan.d -%exclude %{_datadir}/nut/dmfsnmp.d -%{_mandir}/man5/*.* -%{_mandir}/man8/*.* -%exclude %{_mandir}/man8/netxml-ups*.* -%exclude %{_mandir}/man8/snmp-ups*.* -%dir %{_libexecdir}/ups -%{_sbindir}/* -%dir %{_sysconfdir}/udev -%dir %{_sysconfdir}/udev/rules.d -%config(noreplace) %{_sysconfdir}/udev/rules.d/*.rules -%config(noreplace) %{CONFPATH}/hosts.conf -%config(noreplace) %attr(600,%{USER},root) %{CONFPATH}/upsd.conf -%config(noreplace) %attr(600,%{USER},root) %{CONFPATH}/upsd.users -%config(noreplace) %attr(600,%{USER},root) %{CONFPATH}/upsmon.conf -%dir %{CONFPATH} -%config(noreplace) %{CONFPATH}/nut.conf -%config(noreplace) %{CONFPATH}/ups.conf -%config(noreplace) %{CONFPATH}/upsset.conf -%config(noreplace) %{CONFPATH}/upssched.conf -%dir %{MODELPATH} -%{MODELPATH}/* -%exclude %{MODELPATH}/snmp-ups -%exclude %{MODELPATH}/netxml-ups -%exclude %{_sbindir}/gen-snmp-subdriver.sh -%attr(700,%{USER},%{GROUP}) %{STATEPATH} -%{systemdsystemunitdir}/* -%{systemdshutdowndir}/nutshutdown -%{_datadir}/augeas/lenses/dist/nuthostsconf.aug -%{_datadir}/augeas/lenses/dist/nutnutconf.aug -%{_datadir}/augeas/lenses/dist/nutupsconf.aug -%{_datadir}/augeas/lenses/dist/nutupsdconf.aug -%{_datadir}/augeas/lenses/dist/nutupsdusers.aug -%{_datadir}/augeas/lenses/dist/nutupsmonconf.aug -%{_datadir}/augeas/lenses/dist/nutupsschedconf.aug -%{_datadir}/augeas/lenses/dist/nutupssetconf.aug -%{_datadir}/augeas/lenses/dist/tests/test_nut.aug -%dir %{_datadir}/augeas -%dir %{_datadir}/augeas/lenses -%dir %{_datadir}/augeas/lenses/dist -%dir %{_datadir}/augeas/lenses/dist/tests -%{_libexecdir}/nut-driver-enumerator.sh - -%files drivers-net -%defattr(-,root,root) -%{MODELPATH}/snmp-ups -%{MODELPATH}/netxml-ups -%{_bindir}/nut-scanner-reindex-dmfsnmp -%{_mandir}/man8/netxml-ups*.* -%{_mandir}/man8/snmp-ups*.* -%dir %{_datadir}/nut/dmfnutscan -%dir %{_datadir}/nut/dmfsnmp -%{_datadir}/nut/dmfnutscan/*.dmf -%{_datadir}/nut/dmfsnmp/*.dmf -%{_datadir}/nut/dmfnutscan/*.xsd -%{_datadir}/nut/dmfsnmp/*.xsd -%dir %{_datadir}/nut/dmfnutscan.d -%dir %{_datadir}/nut/dmfsnmp.d -%{_datadir}/nut/dmfnutscan.d/*.dmf -%{_datadir}/nut/dmfsnmp.d/*.dmf -%{_sbindir}/gen-snmp-subdriver.sh - -%files -n libupsclient1 -%defattr(-,root,root) -%{_libdir}/*.so.* - -%files cgi -%defattr(-,root,root) -%{CGIPATH}/nut -%{HTMLPATH}/nut -%config(noreplace) %{CONFPATH}/upsstats-single.html -%config(noreplace) %{CONFPATH}/upsstats.html - -%files devel -%defattr(-,root,root) -%{_includedir}/*.h -%{_libdir}/*.so -%{_libdir}/pkgconfig/*.pc -%{_mandir}/man3/*.* - -%changelog diff --git a/scripts/Aix/nut-aix.spec.in b/scripts/Aix/nut-aix.spec.in index 1302160dcf..ed68abe56a 100644 --- a/scripts/Aix/nut-aix.spec.in +++ b/scripts/Aix/nut-aix.spec.in @@ -89,7 +89,7 @@ necessary to develop NUT client applications. --with-statepath=%{piddir} \ --with-pidpath=%{piddir} \ --with-altpidpath=%{piddir} \ - --sysconfdir=%{confdir} \ + --with-confdir=%{confdir} \ --with-cgipath=%{cgidir} \ --with-drvpath=%{_sbindir} \ --with-pkgconfig-dir=%{_libdir}/pkgconfig \ diff --git a/scripts/Aix/nut.init.in b/scripts/Aix/nut.init.in index f8bd09444b..b94cc1c777 100755 --- a/scripts/Aix/nut.init.in +++ b/scripts/Aix/nut.init.in @@ -86,7 +86,7 @@ do_stop() { RETVAL=0 if test -e "${NUT_RUN_DIR}"/upsmon.pid; then echo "Stopping UPS monitor: \c" - PID="`cat "${NUT_RUN_DIR}"/upsmon.pid`" + PID="`cat \"${NUT_RUN_DIR}\"/upsmon.pid`" kill -15 $PID && success || { RETVAL=$?; failure; } rm "${NUT_RUN_DIR}"/upsmon.pid fi @@ -94,7 +94,7 @@ do_stop() { if [ "$SERVER" = "yes" ]; then if test -e "${NUT_RUN_DIR}"/upsd.pid; then echo "Stopping upsd: \c" - PID="`cat "${NUT_RUN_DIR}"/upsd.pid`" + PID="`cat \"${NUT_RUN_DIR}\"/upsd.pid`" kill -15 $PID && success || { RETVAL=$?; failure; } rm "${NUT_RUN_DIR}"/upsd.pid fi diff --git a/scripts/DMF/Makefile.am b/scripts/DMF/Makefile.am index c91065c4ce..de81fd1f7e 100644 --- a/scripts/DMF/Makefile.am +++ b/scripts/DMF/Makefile.am @@ -182,15 +182,15 @@ EXTRA_DIST += $(DMFTEST_SRC) # of dynamic vars, see e.g. https://man.omnios.org/man1/make#BUGS LINKED_SOURCE_FILES = dmfsnmp.c dmfsnmp.c : $(top_srcdir)/common/dmfsnmp.c - @test -s "$@" || ln -s -f "$(top_srcdir)/common/dmfsnmp.c" "$@" + @test -s '$@' || ln -s -f "$(top_srcdir)/common/dmfsnmp.c" '$@' LINKED_SOURCE_FILES += dmfcore.c dmfcore.c : $(top_srcdir)/common/dmfcore.c - @test -s "$@" || ln -s -f "$(top_srcdir)/common/dmfcore.c" "$@" + @test -s '$@' || ln -s -f "$(top_srcdir)/common/dmfcore.c" '$@' LINKED_SOURCE_FILES += alist.c alist.c : $(top_srcdir)/common/alist.c - @test -s "$@" || ln -s -f "$(top_srcdir)/common/alist.c" "$@" + @test -s '$@' || ln -s -f "$(top_srcdir)/common/alist.c" '$@' CLEANFILES += $(noinst_PROGRAMS) \ @@ -294,12 +294,12 @@ if WITH_REGENERATE_DMF_SNMP CPPFLAGS="$(DMFTOOLS_CPPFLAGS) $(CPPFLAGS_DMF) $(AM_CPPFLAGS)" \ ABS_BUILDDIR="$(abs_builddir)" \ DEBUG="$(V)" \ - $(abs_srcdir)/dmfify-mib.sh --skip-sanity-check "$$CMIBFILE" "$@" \ + $(abs_srcdir)/dmfify-mib.sh --skip-sanity-check "$$CMIBFILE" '$@' \ ) || exit; \ fi ; \ - if test ! -s "$@" -a ! -s "$(abs_top_builddir)/$@" -a ! -s "$(abs_top_srcdir)/$@"; then \ + if test ! -s '$@' -a ! -s "$(abs_top_builddir)/$@" -a ! -s "$(abs_top_srcdir)/$@"; then \ echo "ERROR: Generated file $@ is missing" >&2; \ - ls -la "$@" "$(abs_top_builddir)/$@" "$(abs_top_srcdir)/$@"; \ + ls -la '$@' "$(abs_top_builddir)/$@" "$(abs_top_srcdir)/$@"; \ exit 2; \ fi; \ ) @@ -324,7 +324,7 @@ else !WITH_REGENERATE_DMF_SNMP DMFGEN_CMD = ( \ echo " SKIP Generation of $@ from $$CMIBFILE not enabled by the configure script (--with-dmfsnmp-regenerate option)"; \ rm -f $(DMFGEN_SANITY_VALIDATED); \ - if test ! -s "$@" -a ! -s "$(abs_srcdir)/$@" -a ! -s "$(abs_top_builddir)/$@" -a ! -s "$(abs_top_srcdir)/$@"; then \ + if test ! -s '$@' -a ! -s "$(abs_srcdir)/$@" -a ! -s "$(abs_top_builddir)/$@" -a ! -s "$(abs_top_srcdir)/$@"; then \ echo "ERROR: Distributed file $@ is missing" >&2; exit 2; \ fi; \ ) @@ -354,7 +354,7 @@ DMFLNK_CMD = ( \ test -f "$$DMFFILE_DIR$$DMFFILE" || { echo "`date`: 2. File '$$DMFFILE_DIR$$DMFFILE' not found yet, sleeping a bit" >&2; sleep 10 ; sync; } ; \ test -f "$$DMFFILE_DIR$$DMFFILE" || { echo "`date`: 3. File '$$DMFFILE_DIR$$DMFFILE' not found yet, sleeping a bit" >&2; sleep 15 ; sync; } ; \ if [ x"$(V)" = x1 ] ; then echo "BEFORE:" ; ls -la "$$DMFFILE_DIR$$DMFFILE" "$$DMFLINK" || true; fi ; \ - DMFLINK_DIR="`dirname "$$DMFLINK"`"; \ + DMFLINK_DIR="`dirname \"$$DMFLINK\"`"; \ test -d "$$DMFLINK_DIR" -a -w "$$DMFLINK_DIR" || $(MKDIR_P) "$$DMFLINK_DIR" || exit ; \ test -f "$$DMFFILE_DIR$$DMFFILE" || { echo "File '$$DMFFILE_DIR$$DMFFILE' not found" >&2; exit 22; } ; \ if [ x"$(V)" = x1 ] ; then echo "$(LN_S_R) '$$DMFFILE_DIR$$DMFFILE' '$$DMFLINK' || exit"; fi ; \ @@ -384,7 +384,7 @@ $(abs_builddir)/$(DMFSNMP_RES_SUBDIR)/.uptodate: $(dmfsnmpres_DMFS) else \ echo "INFO: Build was done out-of-tree, so will copy files from source dir if needed (if nothing got generated)"; \ for F in "$(abs_srcdir)/$(DMFSNMP_RES_SUBDIR)/"*.dmf ; do \ - B="`basename "$$F"`"; \ + B="`basename \"$$F\"`"; \ case "$$B" in \ '*.dmf') exit 0 ;; \ S*|K*) ;; \ @@ -397,13 +397,13 @@ $(abs_builddir)/$(DMFSNMP_RES_SUBDIR)/.uptodate: $(dmfsnmpres_DMFS) esac ; \ done; \ fi - touch "$@" + touch '$@' $(DMFSNMP_SUBDIR)/.uptodate: +$(MAKE) $(AM_MAKEFLAGS) $(abs_builddir)/$(DMFSNMP_SUBDIR)/.uptodate $(abs_builddir)/$(DMFSNMP_SUBDIR)/.uptodate: $(LEGACY_NUT_DMF_SYMLINKS) - touch "$@" + touch '$@' # The recipe assumes we have separate opening and closing lines in the DMF file # just generated by reindexer (the "\n" and "" lines) and a @@ -428,21 +428,21 @@ $(abs_top_builddir)/scripts/DMF/$(DMFNUTSCAN_RES_SUBDIR)/dmfnutscan-snmp.dmf: $( grep ' "$@" + ) > '$@' @LANG=C; LC_ALL=C; TZ=UTC; export LANG LC_ALL TZ; \ sort "$@.tmp" | egrep -v '^$$' | uniq > "$@.tmp1" ; \ - sort "$@" | egrep -v '^$$' | uniq > "$@.tmp2" ; \ + sort '$@' | egrep -v '^$$' | uniq > "$@.tmp2" ; \ diff "$@.tmp1" "$@.tmp2" >/dev/null \ || { echo "FAILED to sort generated $@ without losses!" >&2; \ rm -f "$@.tmp"* ; exit 1; } ; exit 0 rm -f "$@.tmp"* - test -s "$@" + test -s '$@' else !WITH_REGENERATE_DMF_NUTSCAN $(abs_top_builddir)/scripts/DMF/$(DMFNUTSCAN_RES_SUBDIR)/dmfnutscan-snmp.dmf: $(DMFNUTSCAN_DEPS) @echo " SKIP Generation of $@ not enabled by the configure script (--with-dmfnutscan-regenerate option)" - @if test ! -s "$@" -a ! -s "$(abs_srcdir)/$(DMFNUTSCAN_RES_SUBDIR)/$(@F)"; then echo "ERROR: Distributed file $@ is missing" >&2; exit 2; fi; true + @if test ! -s '$@' -a ! -s "$(abs_srcdir)/$(DMFNUTSCAN_RES_SUBDIR)/$(@F)"; then echo "ERROR: Distributed file $@ is missing" >&2; exit 2; fi; true @test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" @if [ "`cd $(abs_builddir) && pwd`" != "`cd $(abs_srcdir) && pwd`" ]; then \ F="$(abs_srcdir)/$(DMFNUTSCAN_RES_SUBDIR)/$(@F)"; \ @@ -453,15 +453,15 @@ $(abs_top_builddir)/scripts/DMF/$(DMFNUTSCAN_RES_SUBDIR)/dmfnutscan-snmp.dmf: $( cp -f "$$F" "$$N" || { ls -la "$$F" "$$N" ; exit 1; } ; \ fi; \ fi - test -s "$@" + test -s '$@' endif !WITH_REGENERATE_DMF_NUTSCAN $(DMFNUTSCAN_SYMLINK): $(abs_top_builddir)/scripts/DMF/$(DMFNUTSCAN_RES_SUBDIR)/dmfnutscan-snmp.dmf - @DMFFILE="$(DMFNUTSCAN_RES_SUBDIR)/dmfnutscan-snmp.dmf"; DMFLINK="$@"; $(DMFLNK_CMD) + @DMFFILE="$(DMFNUTSCAN_RES_SUBDIR)/dmfnutscan-snmp.dmf"; DMFLINK='$@'; $(DMFLNK_CMD) $(INSTALL_NUT_DMFNUTSCAN_SYMLINK): $(INSTALL_NUT_DMFNUTSCAN_FILE) - @DMFFILE="$(INSTALL_NUT_DMFNUTSCAN_FILE)"; DMFLINK="$@"; $(DMFLNK_CMD) + @DMFFILE="$(INSTALL_NUT_DMFNUTSCAN_FILE)"; DMFLINK='$@'; $(DMFLNK_CMD) $(DMFNUTSCAN_RES_SUBDIR)/.uptodate: +$(MAKE) $(AM_MAKEFLAGS) $(abs_builddir)/$(DMFNUTSCAN_RES_SUBDIR)/.uptodate @@ -469,7 +469,7 @@ $(DMFNUTSCAN_RES_SUBDIR)/.uptodate: $(abs_builddir)/$(DMFNUTSCAN_RES_SUBDIR)/.uptodate: $(abs_top_builddir)/scripts/DMF/$(DMFNUTSCAN_RES_SUBDIR)/dmfnutscan-snmp.dmf @echo "DMFNUTSCAN_RES_SUBDIR is now up to date" >&2 @test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" - touch "$@" + touch '$@' $(DMFNUTSCAN_SUBDIR)/.uptodate: +$(MAKE) $(AM_MAKEFLAGS) $(abs_builddir)/$(DMFNUTSCAN_SUBDIR)/.uptodate @@ -477,7 +477,7 @@ $(DMFNUTSCAN_SUBDIR)/.uptodate: $(abs_builddir)/$(DMFNUTSCAN_SUBDIR)/.uptodate: $(DMFNUTSCAN_SYMLINK) @echo "DMFNUTSCAN_SUBDIR is now up to date" >&2 @test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" - touch "$@" + touch '$@' # Validation requires xmllint, which we have if we succeed with asciidoc usage # TODO: Maybe per-DMF *.dmf.validated files would be better? @@ -510,7 +510,7 @@ else !WITH_VALIDATE_DMF_SNMP DMFLINT_CMD = ( \ test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" || exit ; \ echo " SKIP Validation of $@ not enabled by the configure script ($$DMFLINT_OPTION option)" ; \ - echo "VALIDATION-SKIPPED" > "$@" ; \ + echo "VALIDATION-SKIPPED" > '$@' ; \ ) endif !WITH_VALIDATE_DMF_SNMP @@ -727,7 +727,7 @@ testdata-lua/.uptodate: $(abs_builddir)/testdata-lua/.uptodate: $(LUA_EXAMPLE_DMFS) @echo "testdata-lua is now up to date" >&2 @test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" - touch "$@" + touch '$@' testdata-fun/.uptodate: +$(MAKE) $(AM_MAKEFLAGS) $(abs_builddir)/testdata-fun/.uptodate @@ -735,7 +735,7 @@ testdata-fun/.uptodate: $(abs_builddir)/testdata-fun/.uptodate: $(FUN_EXAMPLE_DMFS) @echo "testdata-fun is now up to date" >&2 @test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" - touch "$@" + touch '$@' if WITH_VALIDATE_DMF_SNMP @@ -745,7 +745,7 @@ $(abs_builddir)/testdata-lua/.validated: $(abs_builddir)/testdata-lua/.uptodate echo " XMLLINT-LUA $$F"; \ $(XMLLINT) --noout --schema $(abs_srcdir)/dmfsnmp.xsd "$$F" || exit; \ done - touch "$@" + touch '$@' $(abs_builddir)/testdata-fun/.validated: $(abs_builddir)/testdata-fun/.uptodate test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" @@ -753,14 +753,14 @@ $(abs_builddir)/testdata-fun/.validated: $(abs_builddir)/testdata-fun/.uptodate echo " XMLLINT-LUA $$F"; \ $(XMLLINT) --noout --schema $(abs_srcdir)/dmfsnmp.xsd "$$F" || exit; \ done - touch "$@" + touch '$@' else !WITH_VALIDATE_DMF_SNMP $(abs_builddir)/testdata-lua/.validated $(abs_builddir)/testdata-fun/.validated: test -d "$(@D)" -a -w "$(@D)" || $(MKDIR_P) "$(@D)" @echo " SKIP Validation of $@ not enabled by the configure script (--with-dmfsnmp-validate option)" - @echo "VALIDATION-SKIPPED" > "$@" + @echo "VALIDATION-SKIPPED" > '$@' endif !WITH_VALIDATE_DMF_SNMP @@ -813,17 +813,17 @@ EXTRA_DIST += $(SPELLCHECK_SRC) # paths when parsing the other makefile (e.g. MKDIR_P that may be defined # via expanded $(top_builddir)/install-sh): #%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) -# +$(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)" $@ +# +$(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) $(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)" $@ + +$(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) $(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)" $@ + +$(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) $(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)" $@ diff --git a/scripts/DMF/dmfify-mib.sh b/scripts/DMF/dmfify-mib.sh index 526b69038a..6f3234773a 100755 --- a/scripts/DMF/dmfify-mib.sh +++ b/scripts/DMF/dmfify-mib.sh @@ -6,7 +6,7 @@ # # Copyright (C) 2016 Michal Vyskocil # Copyright (C) 2016 - 2021 Jim Klimov -# Copyright (C) 2022 - 2024 Jim Klimov +# Copyright (C) 2022 - 2025 Jim Klimov # # A bashism, important for us here @@ -17,7 +17,7 @@ XSD_DMFSNMP_VERSION='1.0.0' XSD_DMFSNMP_XMLNS='http://www.networkupstools.org/dmf/snmp/snmp-ups' # Where to look for python scripts - same dir as this shell script -_SCRIPT_DIR="`cd $(dirname "$0") && pwd`" || \ +_SCRIPT_DIR="`cd $(dirname \"$0\") && pwd`" || \ _SCRIPT_DIR="./" # Fallback can fail [ -n "${ABS_OUTDIR-}" ] || ABS_OUTDIR="`pwd`" || exit [ -n "${ABS_BUILDDIR-}" ] || ABS_BUILDDIR="${ABS_OUTDIR}" @@ -111,7 +111,7 @@ if [ -n "${CC-}" ] ; then esac case "$CC" in /*) ;; - *) CC="`command -v "$CC"`" ;; + *) CC="`command -v \"$CC\"`" ;; esac case "$CC" in *" "*|*" "*) $CC --help >/dev/null 2>/dev/null && CC_WITH_ARGS=true;; @@ -150,7 +150,7 @@ if [ -n "${CPP-}" ] ; then esac case "$CPP" in /*) ;; - *) CPP="`command -v "$CPP"`" ;; + *) CPP="`command -v \"$CPP\"`" ;; esac case "$CPP" in # FIXME: Make CPP single-token and prepend args to CPPFLAGS (if any) diff --git a/scripts/DMF/gen-legacy-mibfiles-list.sh b/scripts/DMF/gen-legacy-mibfiles-list.sh index 8ce00141ea..50a33f719f 100755 --- a/scripts/DMF/gen-legacy-mibfiles-list.sh +++ b/scripts/DMF/gen-legacy-mibfiles-list.sh @@ -9,11 +9,11 @@ # Be portable - no bash etc... plain minimal shell. Tested with bash, dash, # busybox sh and ksh for good measure. # -# Copyright (C) 2016-2024 Jim Klimov +# Copyright (C) 2016-2025 Jim Klimov # # Relative to here we look for old sources -_SCRIPT_DIR="`cd $(dirname "$0") && pwd`" || \ +_SCRIPT_DIR="`cd $(dirname \"$0\") && pwd`" || \ _SCRIPT_DIR="./" # Fallback can fail # Note that technically it is not required that this is an ".in" template @@ -42,7 +42,7 @@ LEGACY_NUT_C_MIBS_LIST="" sort_LEGACY_NUT_C_MIBS() { [ -n "${LEGACY_NUT_C_MIBS_LIST}" ] || \ { LEGACY_NUT_C_MIBS_LIST="`list_LEGACY_NUT_C_MIBS`" && \ - LEGACY_NUT_C_MIBS_LIST="`echo "$LEGACY_NUT_C_MIBS_LIST" | sort | uniq`"; } \ + LEGACY_NUT_C_MIBS_LIST="`echo \"$LEGACY_NUT_C_MIBS_LIST\" | sort | uniq`"; } \ || return $? [ -n "${LEGACY_NUT_C_MIBS_LIST}" ] || return $? echo "$LEGACY_NUT_C_MIBS_LIST" @@ -58,10 +58,10 @@ print_makefile_LEGACY_NUT_DMF_RULES() { for CMIBBASE in `sort_LEGACY_NUT_C_MIBS` ; do case "$CMIBBASE" in - */*) CMIBFILE="$CMIBBASE"; CMIBBASE="`basename "$CMIBBASE"`" ;; + */*) CMIBFILE="$CMIBBASE"; CMIBBASE="`basename \"$CMIBBASE\"`" ;; *) CMIBFILE='$(abs_top_srcdir)/drivers/'"$CMIBBASE" ;; esac - DMFBASE="`basename "$CMIBBASE" .c`".dmf + DMFBASE="`basename \"$CMIBBASE\" .c`".dmf DMFSUBDIR='$(DMFSNMP_RES_SUBDIR)/' DMFFILE="${DMFSUBDIR}${DMFBASE}" case "$DMFBASE" in diff --git a/scripts/Makefile.am b/scripts/Makefile.am index 0a72f46465..b4d4e488e7 100644 --- a/scripts/Makefile.am +++ b/scripts/Makefile.am @@ -10,7 +10,7 @@ EXTRA_DIST = \ HP-UX/nut-upsmon \ HP-UX/nut-upsmon.sh \ logrotate/nutlogd \ - misc/nut.bash_completion \ + misc/nut.bash_completion.in \ misc/osd-notify \ perl/Nut.pm \ RedHat/halt.patch \ @@ -28,10 +28,9 @@ EXTRA_DIST = \ valgrind/README.adoc \ valgrind/.valgrind.supp.in \ valgrind/valgrind.sh.in \ - Windows/halt.c \ - Windows/Makefile + Windows/halt.c -SUBDIRS = augeas devd hotplug installer python systemd udev ufw Solaris Windows upsdrvsvcctl external_apis +SUBDIRS = augeas devd hotplug installer obs python systemd udev ufw Solaris Windows upsdrvsvcctl external_apis if WITH_SNMP if WITH_NEON @@ -39,7 +38,7 @@ if WITH_NEON endif endif -SPELLCHECK_SRC = README.adoc RedHat/README.adoc usb_resetter/README.adoc valgrind/README.adoc +SPELLCHECK_SRC = README.adoc RedHat/README.adoc usb_resetter/README.adoc valgrind/README.adoc obs/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 @@ -63,4 +62,6 @@ spellcheck spellcheck-interactive spellcheck-sortdict: CLEANFILES = *-spellchecked RedHat/*-spellchecked usb_resetter/*-spellchecked +DISTCLEANFILES = misc/nut.bash_completion + MAINTAINERCLEANFILES = Makefile.in .dirstamp diff --git a/scripts/Solaris/Makefile.am b/scripts/Solaris/Makefile.am index e9e7406336..e789c4b4ec 100644 --- a/scripts/Solaris/Makefile.am +++ b/scripts/Solaris/Makefile.am @@ -3,7 +3,7 @@ EXTRA_DIST = makelocal.sh precheck.py.in preproto.pl.in README.adoc PROTOTYPE_DIR = $(DESTDIR)@prefix@ SOLARIS_CHECK_TARGETS = -PYTHON = @PYTHON@ +PYTHON_DEFAULT = @PYTHON_DEFAULT@ SOLARIS_SMF_MANIFESTS = \ nut.xml \ @@ -62,15 +62,18 @@ SOLARIS_PACKAGE_SVR4_INSTALLSCRIPTS = preinstall postinstall preremove postremov SOLARIS_PACKAGE_SVR4_INSTALLDATA = pkginfo package-solaris-svr4: $(SOLARIS_PACKAGE_SVR4_HELPERSCRIPTS) $(SOLARIS_PACKAGE_SVR4_INSTALLSCRIPTS) $(SOLARIS_PACKAGE_SVR4_INSTALLDATA) ISANAME=''; case x"$(target_cpu)" in \ - xi386|xsparc|xsparcv9|xamd64) ISANAME='$(target_cpu)' ;; \ + xi386|xsparc|xsparcv7|xsparcv9|xamd64) ISANAME='$(target_cpu)' ;; \ xx86_64) ISANAME='amd64' ;; \ xi686) ISANAME='i386' ;; \ - xsparcv7)ISANAME='sparc' ;; \ x) UNAME_P="`uname -p`" && ISANAME="$${UNAME_P}" ;; \ esac ; \ ISABITS=32; case x"$${ISANAME}" in \ - xi386|xsparc) ISABITS=32 ;; \ + xi386|xsparcv7) ISABITS=32 ;; \ xsparcv9|xamd64) ISABITS=64 ;; \ + xsparc) case "`(file server/upsd || file server/.libs/upsd) 2>/dev/null`" in \ + *32-bit*) ISABITS=32 ;; \ + *64-bit*) ISABITS=64 ;; \ + esac ;; \ x*) echo "WARNING: Unexpected ISANAME='$${ISANAME}'" >&2 ;; \ esac; \ case x"$${ISABITS}" in \ @@ -87,8 +90,8 @@ package-solaris-svr4: $(SOLARIS_PACKAGE_SVR4_HELPERSCRIPTS) $(SOLARIS_PACKAGE_SV cp $(SOLARIS_PACKAGE_SVR4_HELPERSCRIPTS) $(SOLARIS_PACKAGE_SVR4_INSTALLSCRIPTS) $(SOLARIS_PACKAGE_SVR4_INSTALLDATA) $(PROTOTYPE_DIR) || exit ; \ ( cd $(PROTOTYPE_DIR) && chmod +x $(SOLARIS_PACKAGE_SVR4_HELPERSCRIPTS) $(SOLARIS_PACKAGE_SVR4_INSTALLSCRIPTS) ) || exit ; \ ( cd $(PROTOTYPE_DIR) && perl preproto.pl ) || exit ; \ - if test -n '$(PYTHON)' ; then \ - ( cd $(PROTOTYPE_DIR) && $(PYTHON) precheck.py ) || exit ; \ + if test -n '$(PYTHON_DEFAULT)' ; then \ + ( cd $(PROTOTYPE_DIR) && $(PYTHON_DEFAULT) precheck.py ) || exit ; \ fi ; \ ( cd $(PROTOTYPE_DIR) && rm -f prototype1 ) || exit ; \ ( cd $(PROTOTYPE_DIR) && ./makelocal.sh ) || exit ; \ diff --git a/scripts/Solaris/postinstall.in b/scripts/Solaris/postinstall.in index 96dc63951c..30407c2ed9 100755 --- a/scripts/Solaris/postinstall.in +++ b/scripts/Solaris/postinstall.in @@ -5,6 +5,17 @@ NUT_DIR="@prefix@" prefix="@prefix@" # expanded as part of some autoconf macros below +# Operations with upsdrvctl below should not warn about running +# directly on an SMF-capable system (note it currently acts as +# set/unset, e.g. there is no meaningful "false" value handling): +NUT_QUIET_INIT_NDE_WARNING=true +export NUT_QUIET_INIT_NDE_WARNING + +# Possibly tweak the defaults here (or in a calling session) to +# troubleshoot package installation: +[ -n "${NUT_DEBUG_LEVEL-}" ] || NUT_DEBUG_LEVEL=0 +export NUT_DEBUG_LEVEL + # TODO/FIXME : Should "/var/run" be a configure variable? # Note that "/var/run" is transient tmpfs, so upgrade has to be done during same uptime. ACTIVE_ENUMERATOR_FMRI_FILE="/var/run/nut-driver-enumerator-fmri.prev" @@ -19,7 +30,7 @@ if [ -z "$res" ]; then /usr/sbin/useradd -c "Network UPS Tools" -g "@RUN_AS_GROUP@" -G root -d "@STATEPATH@" -s /bin/false @RUN_AS_USER@ fi -res="`groups "@RUN_AS_GROUP@" | grep -w "@RUN_AS_USER@"`" || res="" +res="`groups \"@RUN_AS_GROUP@\" | grep -w \"@RUN_AS_USER@\"`" || res="" if [ -z "$res" ]; then /usr/sbin/usermod -g "@RUN_AS_GROUP@" -G root "@RUN_AS_USER@" fi @@ -72,7 +83,7 @@ if test -x /usr/sbin/svcadm && test -x /usr/sbin/svccfg && test -x /usr/bin/svcs if test -s "@CONFPATH@/ups.conf" ; then echo "Stopping NUT drivers, if any (in case of upgrade)..." @SBINDIR@/upsdrvsvcctl stop - @SBINDIR@/upsdrvctl -DDDDD stop + @SBINDIR@/upsdrvctl stop sleep 5 echo "(Re-)register NUT drivers (if any)..." REPORT_RESTART_42=no AUTO_START=no "@NUT_LIBEXECDIR@/nut-driver-enumerator.sh" --reconfigure @@ -84,7 +95,7 @@ if test -x /usr/sbin/svcadm && test -x /usr/sbin/svccfg && test -x /usr/bin/svcs # This may still fail if the daemon instance is somehow enabled (admin) PREV_ACTIVE_ENUMERATOR="" if test -s "${ACTIVE_ENUMERATOR_FMRI_FILE}" ; then - PREV_ACTIVE_ENUMERATOR="`head -1 "${ACTIVE_ENUMERATOR_FMRI_FILE}"`" + PREV_ACTIVE_ENUMERATOR="`head -1 \"${ACTIVE_ENUMERATOR_FMRI_FILE}\"`" fi [ x"nut-driver-enumerator:default" = x"${PREV_ACTIVE_ENUMERATOR}" ] && PREV_ACTIVE_ENUMERATOR="" for ACTIVE_ENUMERATOR in ${PREV_ACTIVE_ENUMERATOR} nut-driver-enumerator:default ; do @@ -96,8 +107,8 @@ if test -x /usr/sbin/svcadm && test -x /usr/sbin/svccfg && test -x /usr/bin/svcs else echo "NOT ENABLING nut-driver-enumerator at this time : missing or empty @CONFPATH@/ups.conf" >&2 fi - if test -s "@CONFPATH@/ups.conf" && test -e "@CONFPATH@/upsd.conf" && test -e "@CONFPATH@/upsd.users" ; then - # Note on the mix of "-s" and "-e" in tests above: + if test -s "@CONFPATH@/ups.conf" && test -f "@CONFPATH@/upsd.conf" && test -f "@CONFPATH@/upsd.users" ; then + # Note on the mix of "-s" and "-f" in tests above: # it is a valid use-case for an admin to have just touched an # empty upsd.conf and so use default settings for the daemon echo "Enable NUT upsd data server..." @@ -114,9 +125,10 @@ if test -x /usr/sbin/svcadm && test -x /usr/sbin/svccfg && test -x /usr/bin/svcs echo "Enable NUT umbrella service..." /usr/sbin/svcadm enable -s nut else - echo "Put init script in /etc/init.d..." + echo "Put legacy init script in /etc/init.d..." cp -pf "@NUT_DATADIR@/solaris-init/nut" /etc/init.d chown root:bin /etc/init.d/nut + # Not 755, so only root may run it: chmod 744 /etc/init.d/nut ln -s ../init.d/nut /etc/rc3.d/S90nut > /dev/null 2>&1 @@ -124,7 +136,7 @@ else # Start nut services - #echo "Starting nut services" + #echo "Starting legacy NUT services" #$NUT_DIR/sbin/upsdrvctl start #> /dev/null 2>&1 #$NUT_DIR/sbin/upsd #> /dev/null 2>&1 #$NUT_DIR/sbin/upsmon #> /dev/null 2>&1 diff --git a/scripts/Solaris/precheck.py.in b/scripts/Solaris/precheck.py.in index 313b9aae54..0c60767dbe 100755 --- a/scripts/Solaris/precheck.py.in +++ b/scripts/Solaris/precheck.py.in @@ -1,4 +1,4 @@ -#!@PYTHON@ +#!@PYTHON_DEFAULT@ import sys diff --git a/scripts/Solaris/preinstall.in b/scripts/Solaris/preinstall.in index 42da1ceaf6..eb71714ef7 100755 --- a/scripts/Solaris/preinstall.in +++ b/scripts/Solaris/preinstall.in @@ -17,7 +17,7 @@ if [ "$?" != 0 ]; then /usr/sbin/useradd -c "Network UPS Tools" -g "@RUN_AS_GROUP@" -G root -d "@STATEPATH@" -s /bin/false "@RUN_AS_USER@" fi -res="`groups "@RUN_AS_GROUP@" | grep -w "@RUN_AS_USER@"`" || res="" +res="`groups \"@RUN_AS_GROUP@\" | grep -w \"@RUN_AS_USER@\"`" || res="" if [ -z "$res" ]; then /usr/sbin/usermod -g "@RUN_AS_GROUP@" -G root "@RUN_AS_USER@" fi diff --git a/scripts/Solaris/preremove.in b/scripts/Solaris/preremove.in index abce051343..83d3a074d0 100755 --- a/scripts/Solaris/preremove.in +++ b/scripts/Solaris/preremove.in @@ -7,6 +7,17 @@ NUT_DIR="@prefix@" prefix="@prefix@" # expanded as part of some autoconf macros below +# Operations with upsdrvctl below should not warn about running +# directly on an SMF-capable system (note it currently acts as +# set/unset, e.g. there is no meaningful "false" value handling): +NUT_QUIET_INIT_NDE_WARNING=true +export NUT_QUIET_INIT_NDE_WARNING + +# Possibly tweak the defaults here (or in a calling session) to +# troubleshoot package installation: +[ -n "${NUT_DEBUG_LEVEL-}" ] || NUT_DEBUG_LEVEL=0 +export NUT_DEBUG_LEVEL + # TODO/FIXME : Should "/var/run" be a configure variable? # Note that "/var/run" is transient tmpfs, so upgrade has to be done during same uptime. ACTIVE_ENUMERATOR_FMRI_FILE="/var/run/nut-driver-enumerator-fmri.prev" @@ -17,7 +28,7 @@ if test -x /usr/sbin/svcadm && test -x /usr/sbin/svccfg && test -x /usr/bin/svcs # instance of nut-driver-enumerator so we can pass it to the # next lifetime in case of re-install of NUT and keep the # user's previously declared preference. - ACTIVE_ENUMERATOR="`/usr/bin/svcs -H -o state,fmri '*/nut-driver-enumerator:*' | while read S F ; do [ "$S" != "disabled" ] && [ "$S" != "offline" ] && echo "$F" && break ; done`" + ACTIVE_ENUMERATOR="`/usr/bin/svcs -H -o state,fmri '*/nut-driver-enumerator:*' | while read S F ; do [ \"$S\" != \"disabled\" ] && [ \"$S\" != \"offline\" ] && echo \"$F\" && break ; done`" if [ -n "$ACTIVE_ENUMERATOR" ]; then rm -f "${ACTIVE_ENUMERATOR_FMRI_FILE}" touch "${ACTIVE_ENUMERATOR_FMRI_FILE}" @@ -39,7 +50,7 @@ if test -x /usr/sbin/svcadm && test -x /usr/sbin/svccfg && test -x /usr/bin/svcs done echo "Stopping NUT drivers, if any..." @SBINDIR@/upsdrvsvcctl stop - @SBINDIR@/upsdrvctl -DDDDD stop + @SBINDIR@/upsdrvctl stop sleep 5 for S in `/usr/bin/svcs -H -o fmri '*/nut-driver:*'` `/usr/bin/svcs -H -o fmri '*/nut-driver-enumerator:*'` ; do echo "Stopping NUT service: $S..." @@ -50,8 +61,8 @@ if test -x /usr/sbin/svcadm && test -x /usr/sbin/svccfg && test -x /usr/bin/svcs for S in `/usr/bin/svcs -H -o fmri '*/nut-driver:*' | grep -wv default` `/usr/bin/svcs -H -o fmri '*/nut-driver-enumerator:*' | grep -wv default` ; do echo "Removing NUT service: $S..." # Note: S here is a full FMRI URL - SB="`echo "$S" | sed 's,:[^:]*$,,'`" - SI="`echo "$S" | sed 's,^.*:\([^:]*\)$,\1,'`" + SB="`echo \"$S\" | sed 's,:[^:]*$,,'`" + SI="`echo \"$S\" | sed 's,^.*:\([^:]*\)$,\1,'`" /usr/sbin/svcadm disable -s "$S" /usr/sbin/svccfg -s "$SB" delete -f "$SI" || \ /usr/sbin/svccfg delete "$S" diff --git a/scripts/Solaris/reset-ups-usb-solaris.sh.sample b/scripts/Solaris/reset-ups-usb-solaris.sh.sample index f776023f30..4715df5da5 100755 --- a/scripts/Solaris/reset-ups-usb-solaris.sh.sample +++ b/scripts/Solaris/reset-ups-usb-solaris.sh.sample @@ -34,7 +34,7 @@ date svcs -p "$DEVICE" ; upsc "$DEVICE" DO_SVC=false -if [ "`svcs -Hostate "$DEVICE"`" = "online" ]; then +if [ "`svcs -Hostate \"$DEVICE\"`" = "online" ]; then DO_SVC=true svcadm disable -ts "$DEVICE" fi @@ -58,13 +58,13 @@ cfgadm -lv "${CFGADM_APID}" if $DO_SVC ; then svcadm enable "$DEVICE" ; fi svcadm clear "$DEVICE" 2>/dev/null -dmesg | tail -n 20 +dmesg | tail -20 date svcs -p "$DEVICE" ; upsc "$DEVICE" || { \ COUNT=60 while [ "$COUNT" -gt 0 ] ; do COUNT="`expr $COUNT - 1`" - if upsc "$DEVICE" 2>&1 | grep -Ei '^ups\.status:' >/dev/null ; then break ; fi + if upsc "$DEVICE" 2>&1 | egrep -i '^ups\.status:' >/dev/null ; then break ; fi sleep 1 done svcs -p "$DEVICE" ; upsc "$DEVICE" diff --git a/scripts/Windows/README.adoc b/scripts/Windows/README.adoc index 31e1f2ca33..ed9dd9bbb6 100644 --- a/scripts/Windows/README.adoc +++ b/scripts/Windows/README.adoc @@ -590,8 +590,9 @@ net-snmp :; find . -type f -name '*.dll' -o -name '*.dll.a' :; sudo make install -NOTE: net-snmp tends to only `make` a static-linking library for Windows -by default (the shared library only appears with `LDFLAGS` proposed above). +NOTE: The `net-snmp` code base tends to only `make` a static-linking +library for Windows by default (the shared library only appears with +`LDFLAGS` proposed above). In this case consumers must link not only with `-lnetsnmp` but also its dependencies explicitly -- see `Libs.private` line in `netsnmp.pc` of your build (or installation in `${PREFIX}/lib/pkgconfig/netsnmp.pc`). diff --git a/scripts/Windows/build-mingw-nut.sh b/scripts/Windows/build-mingw-nut.sh index 040c352a15..210e4a786a 100755 --- a/scripts/Windows/build-mingw-nut.sh +++ b/scripts/Windows/build-mingw-nut.sh @@ -7,8 +7,8 @@ #set -x -SCRIPTDIR="`dirname "$0"`" -SCRIPTDIR="`cd "$SCRIPTDIR" && pwd`" +SCRIPTDIR="`dirname \"$0\"`" +SCRIPTDIR="`cd \"$SCRIPTDIR\" && pwd`" DLLLDD_SOURCED=true . "${SCRIPTDIR}/dllldd.sh" @@ -69,7 +69,7 @@ case "$SOURCEMODE" in stable) # FIXME # Stable version (download the latest stable archive) - VER_OPT_SHORT="`echo "$VER_OPT" | awk -F. '{print $1"."$2}'`" + VER_OPT_SHORT="`echo \"$VER_OPT\" | awk -F. '{print $1\".\"$2}'`" if [ ! -s "nut-$VER_OPT.tar.gz" ] ; then wget "https://www.networkupstools.org/source/$VER_OPT_SHORT/nut-$VER_OPT.tar.gz" fi @@ -209,10 +209,10 @@ do_build_mingw_nut() { (cd "$INSTALL_DIR" && { dllldddir . | while read D ; do cp -pf "$D" ./bin/ ; done ; } ) || true # Hardlink libraries for sbin (alternative: all bins in one dir): - (cd "$INSTALL_DIR/sbin" && { DESTDIR="$INSTALL_DIR" dllldddir . | while read D ; do ln -f ../bin/"`basename "$D"`" ./ ; done ; } ) || true + (cd "$INSTALL_DIR/sbin" && { DESTDIR="$INSTALL_DIR" dllldddir . | while read D ; do ln -f ../bin/"`basename \"$D\"`" ./ ; done ; } ) || true # Hardlink libraries for cgi-bin if present: - (cd "$INSTALL_DIR/cgi-bin" 2>/dev/null && { DESTDIR="$INSTALL_DIR" dllldddir . | while read D ; do ln -f ../bin/"`basename "$D"`" ./ ; done ; } ) \ + (cd "$INSTALL_DIR/cgi-bin" 2>/dev/null && { DESTDIR="$INSTALL_DIR" dllldddir . | while read D ; do ln -f ../bin/"`basename \"$D\"`" ./ ; done ; } ) \ || echo "NOTE: FAILED to process OPTIONAL cgi-bin directory; was NUT CGI enabled?" >&2 fi diff --git a/scripts/Windows/build-mingw-prereqs.sh b/scripts/Windows/build-mingw-prereqs.sh index 8a4e194648..84192bdedb 100755 --- a/scripts/Windows/build-mingw-prereqs.sh +++ b/scripts/Windows/build-mingw-prereqs.sh @@ -52,7 +52,7 @@ prepareEnv() { [ -n "${PREFIX_ROOT-}" ] || PREFIX_ROOT="/" [ -n "${PREFIX-}" ] || PREFIX="${PREFIX_ROOT}/$MINGW_PREFIX" # Normalize away extra slashes, they confuse at least MSYS2 tools - PREFIX="`echo "${PREFIX}" | sed 's,//*,/,g'`" + PREFIX="`echo \"${PREFIX}\" | sed 's,//*,/,g'`" case "${PATH}" in "${PREFIX}/bin"|"${PREFIX}/bin:"*|*":${PREFIX}/bin:"*|*":${PREFIX}/bin") ;; *) PATH="${PREFIX}/bin:${PATH}" ;; @@ -72,7 +72,7 @@ prepareEnv() { esac export ARCH PATH PREFIX - if ! (command -v sudo) ; then sudo() ( "$@" ) ; fi + if (command -v sudo) ; then true ; else sudo() ( "$@" ) ; fi else if [ -z "${ARCH-}" ] ; then # TODO: Select by args, envvars, directory presence... @@ -84,7 +84,7 @@ prepareEnv() { HOST_FLAG="--host=$ARCH" [ -n "${PREFIX_ROOT-}" ] || PREFIX_ROOT="/usr" [ -n "${PREFIX-}" ] || PREFIX="${PREFIX_ROOT}/${ARCH}" - PREFIX="`echo "${PREFIX}" | sed 's,//*,/,g'`" + PREFIX="`echo \"${PREFIX}\" | sed 's,//*,/,g'`" export ARCH PREFIX @@ -233,7 +233,7 @@ provide_libmodbus_git() ( # there's nothing to change (avoid re-packaging of CI artifact cache) cd "${DEP_DIRNAME}" && \ LOCAL_HASH="`git log -1 --format='%H'`" && \ - OTHER_HASH="`git ls-remote "${DEP_GITREPO}" | grep -E '(refs/(heads|tags)/'"${DEP_VERSION}"'$|^'"${DEP_VERSION}"')' | awk '{print $1}'`" && \ + OTHER_HASH="`git ls-remote \"${DEP_GITREPO}\" | grep -E '(refs/(heads|tags)/'\"${DEP_VERSION}\"'$|^'\"${DEP_VERSION}\"')' | awk '{print $1}'`" && \ if [ x"${LOCAL_HASH}" = x"${OTHER_HASH}" ] ; then echo "FETCH: Current git commit in '`pwd`' matches current '${DEP_VERSION}' in '${DEP_GITREPO}'" >&2 else @@ -241,7 +241,7 @@ provide_libmodbus_git() ( git fetch --tags && \ git fetch --all && \ git checkout "${DEP_VERSION}" && \ - _GITDIFF="`git diff "origin/${DEP_VERSION}"`" && \ + _GITDIFF="`git diff \"origin/${DEP_VERSION}\"`" && \ if [ -n "${_GITDIFF}" ] ; then # Ensure rebase etc. or fail git pull && \ diff --git a/scripts/Windows/dllldd.sh b/scripts/Windows/dllldd.sh index 1827f572e3..c7a57a0319 100755 --- a/scripts/Windows/dllldd.sh +++ b/scripts/Windows/dllldd.sh @@ -6,7 +6,7 @@ # for Windows, bundled with open-source dependencies. # # Copyright (C) -# 2022 Jim Klimov +# 2022-2025 Jim Klimov REGEX_WS="`printf '[\t ]'`" REGEX_NOT_WS="`printf '[^\t ]'`" @@ -26,24 +26,24 @@ dllldd() ( for OD in objdump "$ARCH-objdump" ; do (command -v "$OD" >/dev/null 2>/dev/null) || continue - ODOUT="`$OD -x "$@" 2>/dev/null | grep -Ei "DLL Name:" | awk '{print $NF}' | sort | uniq | grep -vEi '^(/.*/)?(msvcrt|userenv|bcrypt|rpcrt4|usp10|(advapi|kernel|user|wsock|ws2_|gdi|ole||shell)(32|64))\.dll$'`" \ + ODOUT="`$OD -x \"$@\" 2>/dev/null | grep -Ei \"DLL Name:\" | awk '{print $NF}' | sort | uniq | grep -vEi '^(/.*/)?(msvcrt|userenv|bcrypt|rpcrt4|usp10|(advapi|kernel|user|wsock|ws2_|gdi|ole||shell)(32|64))\.dll$'`" \ && [ -n "$ODOUT" ] || continue for F in $ODOUT ; do if [ -n "$DESTDIR" -a -d "${DESTDIR}" ] ; then - OUT="`find "$DESTDIR" -type f -name "$F" \! -size 0 2>/dev/null | head -1`" \ + OUT="`find \"$DESTDIR\" -type f -name \"$F\" \! -size 0 2>/dev/null | head -1`" \ && [ -n "$OUT" ] && { echo "$OUT" ; SEEN="`expr $SEEN + 1`" ; continue ; } fi if [ -n "$ARCH" -a -d "/usr/${ARCH}" ] ; then - OUT="`ls -1 "/usr/${ARCH}/bin/$F" "/usr/${ARCH}/lib/$F" 2>/dev/null || true`" \ + OUT="`ls -1 \"/usr/${ARCH}/bin/$F\" \"/usr/${ARCH}/lib/$F\" 2>/dev/null || true`" \ && [ -n "$OUT" ] && { echo "$OUT" ; SEEN="`expr $SEEN + 1`" ; continue ; } fi if [ -n "$MSYSTEM_PREFIX" -a -d "$MSYSTEM_PREFIX" ] ; then - OUT="`ls -1 "${MSYSTEM_PREFIX}/bin/$F" "${MSYSTEM_PREFIX}/lib/$F" 2>/dev/null || true`" \ + OUT="`ls -1 \"${MSYSTEM_PREFIX}/bin/$F\" \"${MSYSTEM_PREFIX}/lib/$F\" 2>/dev/null || true`" \ && [ -n "$OUT" ] && { echo "$OUT" ; SEEN="`expr $SEEN + 1`" ; continue ; } fi if [ -n "$MINGW_PREFIX" -a "$MINGW_PREFIX" != "$MSYSTEM_PREFIX" -a -d "$MINGW_PREFIX" ] ; then - OUT="`ls -1 "${MINGW_PREFIX}/bin/$F" "${MINGW_PREFIX}/lib/$F" 2>/dev/null || true`" \ + OUT="`ls -1 \"${MINGW_PREFIX}/bin/$F\" \"${MINGW_PREFIX}/lib/$F\" 2>/dev/null || true`" \ && [ -n "$OUT" ] && { echo "$OUT" ; SEEN="`expr $SEEN + 1`" ; continue ; } fi @@ -66,7 +66,7 @@ dllldd() ( else # FIXME: Try to look up in config.log first? if [ -n "$ARCH" ] && (command -v "${ARCH}-gcc") 2>/dev/null >/dev/null ; then - COMPILER_PATHS="`"${ARCH}-gcc" --print-search-dirs | grep libraries: | sed 's,^libraries: *=/,/,'`" + COMPILER_PATHS="`\"${ARCH}-gcc\" --print-search-dirs | grep libraries: | sed 's,^libraries: *=/,/,'`" fi fi if [ -n "$CXX" ] ; then @@ -75,13 +75,13 @@ dllldd() ( else # FIXME: Try to look up in config.log first? if [ -n "$ARCH" ] && (command -v "${ARCH}-g++") 2>/dev/null >/dev/null ; then - COMPILER_PATHS="`"${ARCH}-g++" --print-search-dirs | grep libraries: | sed 's,^libraries: *=/,/,'`" + COMPILER_PATHS="`\"${ARCH}-g++\" --print-search-dirs | grep libraries: | sed 's,^libraries: *=/,/,'`" fi fi if [ -n "$COMPILER_PATHS" ] ; then - COMPILER_PATHS="`echo "$COMPILER_PATHS" | tr ':' '\n'`" + COMPILER_PATHS="`echo \"$COMPILER_PATHS\" | tr ':' '\n'`" for P in $COMPILER_PATHS ; do - OUT="`ls -1 "${P}/$F" 2>/dev/null || true`" \ + OUT="`ls -1 \"${P}/$F\" 2>/dev/null || true`" \ && [ -n "$OUT" ] && { echo "$OUT" ; SEEN="`expr $SEEN + 1`" ; continue 2 ; } done fi @@ -98,7 +98,7 @@ dllldd() ( # libiconv-2.dll => /mingw64/bin/libiconv-2.dll (0x7ffd26c90000) # but it tends to say "not a dynamic executable" # or that file type is not supported - OUT="`ldd "$@" 2>/dev/null | grep -Ei '\.dll' | grep -E '/(bin|lib)/' | sed "s,^${REGEX_WS}*\(${REGEX_NOT_WS}${REGEX_NOT_WS}*\)${REGEX_WS}${REGEX_WS}*=>${REGEX_WS}${REGEX_WS}*\(${REGEX_NOT_WS}${REGEX_NOT_WS}*\)${REGEX_WS}.*\$,\2," | sort | uniq | grep -Ei '\.dll$'`" \ + OUT="`ldd \"$@\" 2>/dev/null | grep -Ei '\.dll' | grep -E '/(bin|lib)/' | sed \"s,^${REGEX_WS}*\(${REGEX_NOT_WS}${REGEX_NOT_WS}*\)${REGEX_WS}${REGEX_WS}*=>${REGEX_WS}${REGEX_WS}*\(${REGEX_NOT_WS}${REGEX_NOT_WS}*\)${REGEX_WS}.*\$,\2,\" | sort | uniq | grep -Ei '\.dll$'`" \ && [ -n "$OUT" ] && { echo "$OUT" ; return 0 ; } return 1 @@ -128,7 +128,7 @@ dllldddir() ( fi # Assume no whitespace in built/MSYS/MinGW paths... - ORIGFILES="`find "$@" -type f | grep -Ei '\.(exe|dll)$'`" || return + ORIGFILES="`find \"$@\" -type f | grep -Ei '\.(exe|dll)$'`" || return # Quick OK, nothing here? [ -n "$ORIGFILES" ] || return 0 @@ -149,15 +149,15 @@ dllldddir() ( # Next iteration we drill into those we have not seen yet #if [ -n "$BASH_VERSION" ] ; then - # NEXTDLLS="`diffvars_bash "$SEENDLLS" "$MOREDLLS"`" + # NEXTDLLS="`diffvars_bash \"$SEENDLLS\" \"$MOREDLLS\"`" #else echo "$SEENDLLS" > "$TMP1" echo "$MOREDLLS" > "$TMP2" - NEXTDLLS="`diff -bu "$TMP1" "$TMP2" | grep -E '^\+[^+]' | sed 's,^\+,,'`" + NEXTDLLS="`diff -bu \"$TMP1\" \"$TMP2\" | grep -E '^\+[^+]' | sed 's,^\+,,'`" #fi if [ -n "$NEXTDLLS" ] ; then - SEENDLLS="`( echo "$SEENDLLS" ; echo "$NEXTDLLS" ) | sort | uniq`" + SEENDLLS="`( echo \"$SEENDLLS\" ; echo \"$NEXTDLLS\" ) | sort | uniq`" fi done diff --git a/scripts/augeas/Makefile.am b/scripts/augeas/Makefile.am index 106a3a3d67..a2781313fa 100644 --- a/scripts/augeas/Makefile.am +++ b/scripts/augeas/Makefile.am @@ -7,16 +7,16 @@ EXTRA_DIST = gen-nutupsconf-aug.py.in nutupsconf.aug.tpl \ # due to inclusion into docs/developer-guide.txt, and the heading # level may be also dictated by that. -PYTHON = @PYTHON@ +PYTHON_DEFAULT = @PYTHON_DEFAULT@ # only call the script to generate Augeas ups.conf lens upon "make dist", # and if Python is present; the distributed gen-nutupsconf-aug.py.in template # is assumed to only differ from a generated gen-nutupsconf-aug.py by the -# @PYTHON@ shebang. +# @PYTHON_DEFAULT@ shebang. dist-hook: - @if [ -n "$(PYTHON)" ] && [ x"no" != x"$(PYTHON)" ] && $(PYTHON) -c "import re,glob,codecs"; then \ - echo "Regenerating Augeas ups.conf lens with '$(PYTHON)'."; \ - $(PYTHON) $(distdir)/gen-nutupsconf-aug.py.in $(distdir)/; \ + @if [ -n "$(PYTHON_DEFAULT)" ] && [ x"no" != x"$(PYTHON_DEFAULT)" ] && $(PYTHON_DEFAULT) -c "import re,glob,codecs"; then \ + echo "Regenerating Augeas ups.conf lens with '$(PYTHON_DEFAULT)'."; \ + $(PYTHON_DEFAULT) $(distdir)/gen-nutupsconf-aug.py.in $(distdir)/; \ else \ echo "----------------------------------------------------------------------"; \ echo "Warning: Python is not available."; \ diff --git a/scripts/augeas/gen-nutupsconf-aug.py.in b/scripts/augeas/gen-nutupsconf-aug.py.in index 705d074db2..07374ac940 100755 --- a/scripts/augeas/gen-nutupsconf-aug.py.in +++ b/scripts/augeas/gen-nutupsconf-aug.py.in @@ -1,4 +1,4 @@ -#!@PYTHON@ +#!@PYTHON_DEFAULT@ # Copyright (C) # 2010 - Arnaud Quette # 2020 - 2024 Jim Klimov diff --git a/scripts/devd/README.adoc b/scripts/devd/README.adoc index baefb3a9fa..ff960c3cb8 100644 --- a/scripts/devd/README.adoc +++ b/scripts/devd/README.adoc @@ -5,7 +5,9 @@ On FreeBSD, the `devd` subsystem has a similar role to `udev` on Linux. NOTE: Some FreeBSD based systems rely on "quirks" instead. -The `devd.conf` file defines actions to perform when devices are plugged in. +The `(/etc/)devd.conf` file defines actions to perform when devices are plugged +in, and usually refers to directories like `/etc/devd` and `/usr/local/etc/devd` +where additional configuration files can be placed. The `tools/nut-usbinfo.pl` script (under NUT source tree root) generates the `nut-usb.conf.in` here by processing USB macros in all of the drivers. diff --git a/scripts/external_apis/enphase/README.adoc b/scripts/external_apis/enphase/README.adoc index 6aa913ae8b..551a63fc18 100644 --- a/scripts/external_apis/enphase/README.adoc +++ b/scripts/external_apis/enphase/README.adoc @@ -104,8 +104,8 @@ enphase-monitor needs to have write access to , so usually upsd hosting should be on local host, but shared filesystems may allow upsd to be remote. -NOTE: if connections to ENVOY_HOST fail, is renamed --nocomms to trigger dummy-ups to show stale data. +NOTE: If connections to 'ENVOY_HOST' fail, `` is renamed into +`-nocomms` to trigger `dummy-ups` to show stale data. Either filename may exist on startup. Environment (optional): diff --git a/scripts/fuse/execfuse-nut/getattr b/scripts/fuse/execfuse-nut/getattr index 6a13a013a5..9929acdad7 100755 --- a/scripts/fuse/execfuse-nut/getattr +++ b/scripts/fuse/execfuse-nut/getattr @@ -2,11 +2,11 @@ # Simple PoC of NUT client as a FUSE mountable filesystem # Requires https://github.com/vi/execfuse -# Copyright (C) 2024 by Jim Klimov +# Copyright (C) 2024-2025 by Jim Klimov # Licensed GPLv2+ as NUT codebase if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then - exec 2>"/tmp/nut-debug-fuse-`basename $0`.log" + exec 2>"/tmp/nut-debug-fuse-`basename \"$0\"`.log" echo "ARGS($#, $0): 1='$1' 2='$2' @='$@'" >&2 fi @@ -20,17 +20,17 @@ case "$1" in ;; /by-server/*/*/*) # VARNAME - F="`basename "$1"`" + F="`basename \"$1\"`" # UPSSRV/UPSNAME - D="`echo "$1" | sed 's,^/by-server/\(.*\)/'"$F"'$,\1,'`" + D="`echo \"$1\" | sed 's,^/by-server/\(.*\)/'\"$F\"'$,\1,'`" ;; /by-server/*/*) # UPSSRV/UPSNAME - D="`echo "$1" | sed 's,^/by-server/\(.*\)$,\1,'`" + D="`echo \"$1\" | sed 's,^/by-server/\(.*\)$,\1,'`" ;; /by-server/*) # No further slash # UPSSRV - D="`basename "$1"`" + D="`basename \"$1\"`" ;; /client-location|/client-version) F="$1" diff --git a/scripts/fuse/execfuse-nut/read_file b/scripts/fuse/execfuse-nut/read_file index 22300d92f5..bcdba8bd9e 100755 --- a/scripts/fuse/execfuse-nut/read_file +++ b/scripts/fuse/execfuse-nut/read_file @@ -2,11 +2,11 @@ # Simple PoC of NUT client as a FUSE mountable filesystem # Requires https://github.com/vi/execfuse -# Copyright (C) 2024 by Jim Klimov +# Copyright (C) 2024-2025 by Jim Klimov # Licensed GPLv2+ as NUT codebase if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then - exec 2>"/tmp/nut-debug-fuse-`basename $0`.log" + exec 2>"/tmp/nut-debug-fuse-`basename \"$0\"`.log" echo "ARGS($#, $0): 1='$1' 2='$2' @='$@'" >&2 fi @@ -17,8 +17,8 @@ case "$1" in /by-server/*/*/*/*) # Don't want subdirs here ;; /by-server/*/*/*) - VARNAME="`basename "$1"`" - UPS="`echo "$1" | sed 's,^/by-server/\([^/]*\)/\([^/]*\)/'"$VARNAME"'$,\2@\1,'`" + VARNAME="`basename \"$1\"`" + UPS="`echo \"$1\" | sed 's,^/by-server/\([^/]*\)/\([^/]*\)/'\"$VARNAME\"'$,\2@\1,'`" if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then #echo "+upsc '$UPS' '$VARNAME'" >&2 set -x diff --git a/scripts/fuse/execfuse-nut/readdir b/scripts/fuse/execfuse-nut/readdir index 27f13696b0..a5f92acabf 100755 --- a/scripts/fuse/execfuse-nut/readdir +++ b/scripts/fuse/execfuse-nut/readdir @@ -2,11 +2,11 @@ # Simple PoC of NUT client as a FUSE mountable filesystem # Requires https://github.com/vi/execfuse -# Copyright (C) 2024 by Jim Klimov +# Copyright (C) 2024-2025 by Jim Klimov # Licensed GPLv2+ as NUT codebase if [ x"$NUT_DEBUG_FUSE" = xtrue ] ; then - exec 2>"/tmp/nut-debug-fuse-`basename $0`.log" + exec 2>"/tmp/nut-debug-fuse-`basename \"$0\"`.log" echo "ARGS($#, $0): 1='$1' 2='$2' @='$@'" >&2 #echo "ARGS: $#: $@" >&2 #set | grep -E '^[^ =]*=' >&2 @@ -48,15 +48,15 @@ case "$1" in exit 2 # ENOENT ;; /by-server/*/*) # list device variables - UPSNAME="`basename "$1"`" - UPSSRV="`echo "$1" | sed 's,^/by-server/\(.*\)/'"$UPSNAME"'$,\1,'`" + UPSNAME="`basename \"$1\"`" + UPSSRV="`echo \"$1\" | sed 's,^/by-server/\(.*\)/'\"$UPSNAME\"'$,\1,'`" print_dots for VARNAME in `upsc "$UPSNAME@$UPSSRV" | awk -F: '{print $1}'` ; do printf 'ino=1 mode=-r--r--r-- nlink=1 uid=0 gid=0 rdev=0 size=16 blksize=512 blocks=1 atime=0 mtime=0 ctime=0 %s\0' "$VARNAME" done ;; /by-server/*) # devices on hostname - UPSSRV="`basename "$1"`" + UPSSRV="`basename \"$1\"`" print_dots for UPSNAME in `upsc -l "$UPSSRV"` ; do printf 'ino=1 mode=drwxr-xr-x nlink=16 uid=0 gid=0 rdev=0 size=16 blksize=512 blocks=1 atime=0 mtime=0 ctime=0 %s\0' "$UPSNAME" diff --git a/scripts/installer/aix/aix_init b/scripts/installer/aix/aix_init index 1ff2104499..91b7f6bbf7 100755 --- a/scripts/installer/aix/aix_init +++ b/scripts/installer/aix/aix_init @@ -112,7 +112,7 @@ do_start() { do_stop() { if test -e "${NUT_RUN_DIR}/upsmon.pid" ; then echo "Stopping UPS monitor: \c" - PID="`cat "${NUT_RUN_DIR}/upsmon.pid"`" + PID="`cat \"${NUT_RUN_DIR}/upsmon.pid\"`" ( kill $PID && success || failure ) || \ RETVAL=$? rm "${NUT_RUN_DIR}/upsmon.pid" @@ -121,7 +121,7 @@ do_stop() { if [ "$SERVER" = "yes" ]; then if test -e "${NUT_RUN_DIR}/upsd.pid" ; then echo "Stopping upsd: \c" - PID="`cat "${NUT_RUN_DIR}/upsd.pid"`" + PID="`cat \"${NUT_RUN_DIR}/upsd.pid\"`" ( kill -9 $PID && success || failure ) || \ RETVAL=$? rm "${NUT_RUN_DIR}/upsd.pid" @@ -186,7 +186,7 @@ case "$1" in if [ "$SERVER" = "yes" ]; then RETVAL_UPSD=1 if test -f "${NUT_LOCK_FILE}" -o -s "${NUT_RUN_DIR}/upsd.pid" ; then - PID_UPSD="`cat "${NUT_RUN_DIR}/upsd.pid"`" && \ + PID_UPSD="`cat \"${NUT_RUN_DIR}/upsd.pid\"`" && \ test -n "$PID_UPSD" && \ test -d "/proc/${PID_UPSD}" && \ echo "upsd is running with PID $PID_UPSD" && \ @@ -200,7 +200,7 @@ case "$1" in RETVAL_UPSMON=1 if test -s "${NUT_RUN_DIR}/upsmon.pid" ; then - PID_UPSMON="`cat "${NUT_RUN_DIR}/upsmon.pid"`" && \ + PID_UPSMON="`cat \"${NUT_RUN_DIR}/upsmon.pid\"`" && \ test -n "$PID_UPSMON" && \ test -d "/proc/${PID_UPSMON}" && \ echo "upsmon is running with PID $PID_UPSMON" && \ diff --git a/scripts/installer/common/aix_init b/scripts/installer/common/aix_init index e4ae5af425..d89239754b 100755 --- a/scripts/installer/common/aix_init +++ b/scripts/installer/common/aix_init @@ -120,7 +120,7 @@ do_restart() { while [ -n "$(ls /var/run/nut/)" -a $waitmore -ge 1 ] do sleep 1 - waitmore=$((waitmore-1)) + waitmore="`expr $waitmore - 1`" done do_start } diff --git a/scripts/installer/common/init b/scripts/installer/common/init index 04e1f08cce..e5ab70c6ff 100755 --- a/scripts/installer/common/init +++ b/scripts/installer/common/init @@ -69,7 +69,7 @@ fi # TODO: Here we want to refine the list to only MONITORed UPSes that power us? # Convert to parsing of "ipp-status -p" which reports all needed details -upslist="`"$NUT_UPSC" -l`" +upslist="`\"$NUT_UPSC\" -l`" echo "$upslist" for u in $upslist; do echo "Disabling poweroff for UPS outlet-groups on '$u' ..." diff --git a/scripts/installer/common/ipp-event.sh b/scripts/installer/common/ipp-event.sh index 3a04844616..e1dd73f95e 100644 --- a/scripts/installer/common/ipp-event.sh +++ b/scripts/installer/common/ipp-event.sh @@ -40,7 +40,7 @@ fi #call notifier script $CMD_NOTIFIER "$*" & -PROC="`ps -ef | grep "$DAEMON" | awk -F" " '{print $2}'`" +PROC="`ps -ef | grep \"$DAEMON\" | awk -F\" \" '{print $2}'`" case "$1" in ONBATT) if [ $PROC = "" ];then diff --git a/scripts/installer/common/ipp-host-shutdown.sample b/scripts/installer/common/ipp-host-shutdown.sample index 497cbadc92..0bbe060176 100644 --- a/scripts/installer/common/ipp-host-shutdown.sample +++ b/scripts/installer/common/ipp-host-shutdown.sample @@ -50,7 +50,7 @@ logmsg() { # The custom shutdown routine if clusterware is present if [ -x /etc/init.d/NONEXISTENT/clusterware ]; then get_ldate - logmsg $(printf "$MessageCustomShutdownStarting_s" "clusterware") + logmsg "`printf \"$MessageCustomShutdownStarting_s\" \"clusterware\"`" trap 'echo "$MessageIrreversibleTrap">&2' 1 2 3 15 @@ -72,7 +72,7 @@ if [ -x /etc/init.d/NONEXISTENT/clusterware ]; then echo 'SDFLAG_COMMONOPTIONS="$SDFLAG_UNGRACEFUL"' >&3 get_ldate - logmsg $(printf "$MessageCustomShutdownCompleted_s" "clusterware") + logmsg "`printf \"$MessageCustomShutdownCompleted_s\" \"clusterware\"`" trap '-' 1 2 3 15 fi diff --git a/scripts/installer/common/ipp-os-shutdown b/scripts/installer/common/ipp-os-shutdown index db596fe343..570659e334 100755 --- a/scripts/installer/common/ipp-os-shutdown +++ b/scripts/installer/common/ipp-os-shutdown @@ -186,7 +186,7 @@ check_pid() { # Returns: PID in $1 is running = 0; not running = 1; syntax errors = 2 [ -n "$1" ] && [ "$1" -gt 0 ] || return 2 [ -d "/proc/$1" ] && return 0 - ( (ps -ef || ps -xawwu ) | grep -v grep | egrep "`basename $0`|shutdown" | \ + ( (ps -ef || ps -xawwu ) | grep -v grep | egrep "`basename \"$0\"`|shutdown" | \ awk '{print $2}' | grep -w "$1" ) 2>/dev/null && return 0 return 1 } @@ -205,7 +205,7 @@ check_pidfile() { # No PID is running - return 1 # No file / bad filename / other errors - then return 2 if [ -n "$1" ] && [ -s "$1" ] && [ -r "$1" ]; then - PIDS="`head -1 "$1"`" && [ -n "$PIDS" ] || return 2 + PIDS="`head -1 \"$1\"`" && [ -n "$PIDS" ] || return 2 check_pids_atleastone $PIDS || return 1 return 0 fi @@ -234,9 +234,9 @@ cancel_shutdown() { case "$?" in 0) echo "$MessageCannotCancelNone"; return 0 ;; 1) - PIDS="`head -1 "$SHUTDOWN_PIDFILE"`" && [ -n "$PIDS" ] || return 1 + PIDS="`head -1 \"$SHUTDOWN_PIDFILE\"`" && [ -n "$PIDS" ] || return 1 get_ldate - logmsg $(printf "${MessageCancelingPIDS_s}\n" "$PIDS") + logmsg "`printf \"${MessageCancelingPIDS_s}\n\" \"$PIDS\"`" kill -9 $PIDS rm -f "$SHUTDOWN_PIDFILE" "$SHUTDOWN_PIDFILE_IRREVERSIBLE" return 0 ;; @@ -257,7 +257,7 @@ while [ "$#" -gt 0 ]; do esac shift ;; +0) SHUTDOWN_TIMER="0" ;; - +[1-9]*) N="`echo "$1" | sed 's,^\+,,'`" && N="`expr 0 + "$N"`" && \ + +[1-9]*) N="`echo \"$1\" | sed 's,^\+,,'`" && N="`expr 0 + \"$N\"`" && \ [ "$N" -gt -1 ] && SHUTDOWN_TIMER="$N" || \ echo "Bad time parameter '$1', ignoring" >&2 ;; --help|help) usage; exit 0 ;; @@ -293,7 +293,7 @@ case "$RES" in esac if [ -f "$CONFIG_UPSMON" ]; then - _VAR="`egrep '^[ \t]*POWERDOWNFLAG ' "$CONFIG_UPSMON" | sed 's,^[ \t]*POWERDOWNFLAG ,,'`" + _VAR="`egrep '^[ \t]*POWERDOWNFLAG ' \"$CONFIG_UPSMON\" | sed 's,^[ \t]*POWERDOWNFLAG ,,'`" _VAR="$(echo "$_VAR" | sed -e 's,^"\(.*\)"$,\1,g' -e "s,^\'\(.*\)\'$,\1,g")" if [ $? = 0 ] && [ -n "$_VAR" ]; then POWERDOWNFLAG="$_VAR" @@ -319,7 +319,7 @@ if [ "$SHUTDOWN_TIMER" -gt -1 ] 2>/dev/null; then else SHUTDOWN_TIMER_SEC="`expr $SHUTDOWN_TIMER \* 60`" \ || SHUTDOWN_TIMER_SEC="120" - logmsg $(printf "${MessageShutdownIsDelayed_s}\n" "${SHUTDOWN_TIMER_SEC}") + logmsg "`printf \"${MessageShutdownIsDelayed_s}\n\" \"${SHUTDOWN_TIMER_SEC}\"`" echo "+ ${SHUTDOWN_TIMER_SEC}" >> "$SHUTDOWN_PIDFILE" trap 'get_ldate; logmsg "$MessageTimerAbortedTrap">&2 ; exit 0' 1 2 3 15 /usr/bin/sleep ${SHUTDOWN_TIMER_SEC} @@ -351,13 +351,13 @@ if [ -n "${SHUTDOWNSCRIPT_CUSTOM-}" ] && \ export CONSOLE_NOTIF CMD_WALL SYSLOG_NOTIF CMD_SYSLOG export SDFLAG_COMMONOPTIONS SDFLAG_UNGRACEFUL get_ldate - logmsg $(printf "$MessageCustomShutdownStarting_s" "$SHUTDOWNSCRIPT_CUSTOM") + logmsg "`printf \"$MessageCustomShutdownStarting_s\" \"$SHUTDOWNSCRIPT_CUSTOM\"`" rm -f "$SHUTDOWN_PIDFILE.custom" touch "$SHUTDOWN_PIDFILE.custom" chmod 600 "$SHUTDOWN_PIDFILE.custom" "$SHUTDOWNSCRIPT_CUSTOM" 3>"$SHUTDOWN_PIDFILE.custom" get_ldate - logmsg $(printf "$MessageCustomShutdownCompleted_s" "$SHUTDOWNSCRIPT_CUSTOM") + logmsg "`printf \"$MessageCustomShutdownCompleted_s\" \"$SHUTDOWNSCRIPT_CUSTOM\"`" # Some variables for this script could be modified in the custom one [ -s "$SHUTDOWN_PIDFILE.custom" ] && . "$SHUTDOWN_PIDFILE.custom" rm -f "$SHUTDOWN_PIDFILE.custom" @@ -374,12 +374,12 @@ if [ -s "$POWERDOWNFLAG" ] || "$NUT_UPSMON" -K ; then fi get_ldate if [ x"$POWERDOWNFLAG_UPSMON" = xyes ] ; then - logmsg $(printf "$MessageKillpowerFileExists_s" "$POWERDOWNFLAG") + logmsg "`printf \"$MessageKillpowerFileExists_s\" \"$POWERDOWNFLAG\"`" else - logmsg $(printf "$MessageKillpowerFileAbsent_s" "$POWERDOWNFLAG") + logmsg "`printf \"$MessageKillpowerFileAbsent_s\" \"$POWERDOWNFLAG\"`" fi if [ -n "$POWERDOWNFLAG_USER" ] ; then - logmsg $(printf "$MessageKillpowerArgumentExists_s" "$POWERDOWNFLAG_USER") + logmsg "`printf \"$MessageKillpowerArgumentExists_s\" \"$POWERDOWNFLAG_USER\"`" else logmsg "$MessageKillpowerArgumentAbsent" fi @@ -389,10 +389,10 @@ if [ x"$POWERDOWNFLAG_UPSMON" = xyes -o "$POWERDOWNFLAG_USER" = "enforce" ] && \ ; then # We will cut our (dev-assumed) power, so should poweroff to be safe... if [ -z "${SDFLAG_POWERSTATE-}" ] ; then - CONSOLE_NOTIF=1 SYSLOG_NOTIF=1 logmsg $(printf "$MessageUPSpowercycleCommandingPoweroff_s" "$DELAY") + CONSOLE_NOTIF=1 SYSLOG_NOTIF=1 logmsg "`printf \"$MessageUPSpowercycleCommandingPoweroff_s\" \"$DELAY\"`" SDFLAG_POWERSTATE="$SDFLAG_POWEROFF" else - CONSOLE_NOTIF=1 SYSLOG_NOTIF=1 logmsg $(printf "$MessageUPSpowercycleCommanding_s" "$DELAY") + CONSOLE_NOTIF=1 SYSLOG_NOTIF=1 logmsg "`printf \"$MessageUPSpowercycleCommanding_s\" \"$DELAY\"`" fi POWERDOWNFLAG_USER=enforce "$NUT_UPS_SHUTDOWN" else @@ -418,6 +418,6 @@ fi SDFLAG_POWERSTATE="$SDFLAG_POWERSTATE_DEFAULT" get_ldate -CONSOLE_NOTIF=1 SYSLOG_NOTIF=1 logmsg $(printf "$MessageRunningCmd_s" "$CMD_SHUTDOWN $SDFLAG_POWERSTATE $SDFLAG_COMMONOPTIONS") +CONSOLE_NOTIF=1 SYSLOG_NOTIF=1 logmsg "`printf \"$MessageRunningCmd_s\" \"$CMD_SHUTDOWN $SDFLAG_POWERSTATE $SDFLAG_COMMONOPTIONS\"`" # Launch the operating system shutdown command $CMD_SHUTDOWN $SDFLAG_POWERSTATE $SDFLAG_COMMONOPTIONS diff --git a/scripts/installer/common/ipp-shutdown-daemon.sh b/scripts/installer/common/ipp-shutdown-daemon.sh index 3f9c027457..ace015da29 100644 --- a/scripts/installer/common/ipp-shutdown-daemon.sh +++ b/scripts/installer/common/ipp-shutdown-daemon.sh @@ -36,6 +36,6 @@ fi # Convert to parsing of "ipp-status -p" which reports all needed details for dev in `$NUT_UPSC -l 2>/dev/null`; do - shutdown="`$NUT_UPSC "$dev"@localhost ups.timer.shutdown 2>/dev/null`" - reboot="`$NUT_UPSC "$dev"@localhost ups.timer.reboot 2>/dev/null`" + shutdown="`\"$NUT_UPSC\" \"$dev\"@localhost ups.timer.shutdown 2>/dev/null`" + reboot="`\"$NUT_UPSC\" \"$dev\"@localhost ups.timer.reboot 2>/dev/null`" done diff --git a/scripts/installer/common/ipp-status b/scripts/installer/common/ipp-status index b7f15a5811..6f6e2b6ca3 100755 --- a/scripts/installer/common/ipp-status +++ b/scripts/installer/common/ipp-status @@ -72,11 +72,11 @@ devices_formatted="yes" # get_devices_info() { for dev in `$awk '/^MONITOR / { print $2 }' "$CONFIG_UPSMON"`; do - status="`$NUT_UPSC "$dev" ups.status 2>/dev/null`" - runtime="`$NUT_UPSC "$dev" battery.runtime 2>/dev/null`" - batterypct="`$NUT_UPSC "$dev" battery.charge 2>/dev/null`" - supplies="`get_pwr_value "$dev" | egrep '^[0-9]*$'`" - upsmon_ms="`get_ms_value "$dev" | egrep 'master|slave'`" + status="`$NUT_UPSC \"$dev\" ups.status 2>/dev/null`" + runtime="`$NUT_UPSC \"$dev\" battery.runtime 2>/dev/null`" + batterypct="`$NUT_UPSC \"$dev\" battery.charge 2>/dev/null`" + supplies="`get_pwr_value \"$dev\" | egrep '^[0-9]*$'`" + upsmon_ms="`get_ms_value \"$dev\" | egrep 'master|slave'`" echo "$dev:$status:$runtime:$batterypct:$supplies:$upsmon_ms" done @@ -178,8 +178,8 @@ overall_status() { # when UPS are on battery # Get non-low battery UPSes (still protecting the system) - protectors="`echo "$devices_info" | parse_protectors_knowngood "${onbattery_sdtimer}"`" - protectors_unknown="`echo "$devices_info" | parse_protectors_unknown`" + protectors="`echo \"$devices_info\" | parse_protectors_knowngood \"${onbattery_sdtimer}\"`" + protectors_unknown="`echo \"$devices_info\" | parse_protectors_unknown`" # Get total powervalue of protecting UPSes protectors_pwr_value=0 @@ -187,7 +187,7 @@ overall_status() { not_protectors=0 for ups in $protectors; do - pwr_value="`get_pwr_value "$ups"`" + pwr_value="`get_pwr_value \"$ups\"`" test -n "$pwr_value" || pwr_value=1 @@ -199,7 +199,7 @@ overall_status() { done for ups in $protectors_unknown; do - pwr_value="`get_pwr_value "$ups"`" + pwr_value="`get_pwr_value \"$ups\"`" test -n "$pwr_value" || pwr_value=1 @@ -398,7 +398,7 @@ HERE # Usage # usage() { - this="`basename $0`" + this="`basename \"$0\"`" cat <&2 diff --git a/scripts/installer/common/shutdown b/scripts/installer/common/shutdown index bbb223d644..9b1d0a906d 100755 --- a/scripts/installer/common/shutdown +++ b/scripts/installer/common/shutdown @@ -80,7 +80,7 @@ DELAYON="`expr $DELAY + 10`" # and/or take into account the killpower flag-file (upsmon master vs. slave # and/or `upsmon -k` status) # Convert to parsing of "ipp-status -p" which reports all needed details -upslist="`"$NUT_UPSC" -l`" +upslist="`\"$NUT_UPSC\" -l`" echo "$upslist" # NOTE: not all UPSes and not all drivers support all possible instcmd's # so we try as many as possible diff --git a/scripts/installer/install.sh b/scripts/installer/install.sh index f1ed69c4e5..ecf6dac76f 100755 --- a/scripts/installer/install.sh +++ b/scripts/installer/install.sh @@ -45,7 +45,7 @@ NUT_PORT="3493" ADMIN_FILE="/tmp/ipp_admin_file" -cd "`dirname $0`" +cd "`dirname \"$0\"`" . "$COMMON_DIR/string.sh" @@ -539,14 +539,14 @@ initial_configure () { chmod 640 "$instpath/etc/upsd.users" >> "$LOG_FILE" 2>&1 # Report the SHUTDOWNSCRIPT_CUSTOM value or that it is missing - _VAR="`egrep '^[ \t]*SHUTDOWNSCRIPT_CUSTOM=' "$instpath/etc/ipp.conf" 2>/dev/null`" || CC_SHUTDOWNSCRIPT_CUSTOM="" + _VAR="`egrep '^[ \t]*SHUTDOWNSCRIPT_CUSTOM=' \"$instpath/etc/ipp.conf\" 2>/dev/null`" || CC_SHUTDOWNSCRIPT_CUSTOM="" if [ -n "${_VAR}" ] ; then - _VAR="`echo "${_VAR}" | sed 's,^[ \t]*SHUTDOWNSCRIPT_CUSTOM=,,'`" - _VAR="`echo "${_VAR}" | sed -e 's,^"\(.*\)"$,\1,g' -e "s,^\'\(.*\)\'$,\1,g"`" + _VAR="`echo \"${_VAR}\" | sed 's,^[ \t]*SHUTDOWNSCRIPT_CUSTOM=,,'`" + _VAR="`echo \"${_VAR}\" | sed -e 's,^\"\(.*\)\"$,\1,g' -e \"s,^\'\(.*\)\'$,\1,g\"`" case "${_VAR}" in - *NUT_DIR*) _VAR1="`egrep '^[^\#]*NUT_DIR=' "$instpath/etc/ipp.conf" 2>/dev/null`" \ - && [ -n "${_VAR1}" ] && _VAR1="`eval $_VAR1 && echo "$NUT_DIR"`" && [ -n "${_VAR1}" ] \ - && _VAR2="`NUT_DIR="${_VAR1}" ; eval echo "${_VAR}"`" \ + *NUT_DIR*) _VAR1="`egrep '^[^\#]*NUT_DIR=' \"$instpath/etc/ipp.conf\" 2>/dev/null`" \ + && [ -n "${_VAR1}" ] && _VAR1="`eval $_VAR1 && echo \"$NUT_DIR\"`" && [ -n "${_VAR1}" ] \ + && _VAR2="`NUT_DIR=\"${_VAR1}\" ; eval echo \"${_VAR}\"`" \ && [ -n "${_VAR2}" ] && _VAR="${_VAR2}" ;; esac if [ -n "${_VAR}" ] && [ -s "${_VAR}" ] && [ -x "${_VAR}" ]; then @@ -585,8 +585,8 @@ display_device () { while [ "$DISP_COUNTER" -le "$C_NUM_DEV" ]; do eval TMP=\$C_DEVICE"$DISP_COUNTER" -# DEV_TYPE="`printf "${DISP_COUNTER}- $TMP" | awk -F' ' '{print \$2}'`" - DEV_CONF="`echo "$TMP" | awk -F' ' '{print \$3}'`" +# DEV_TYPE="`printf \"${DISP_COUNTER}- $TMP\" | awk -F' ' '{print \$2}'`" + DEV_CONF="`echo \"$TMP\" | awk -F' ' '{print \$3}'`" echo " " "$DISP_COUNTER". "$DEV_TYPE" "$DEV_CONF" DISP_COUNTER="`expr $DISP_COUNTER + 1`" done @@ -857,7 +857,7 @@ choose_ask_scan_serial() { } choose_serial() { - serial_list="`$NUTCONF --scan-serial auto 2>> "$LOG_FILE"`" + serial_list="`$NUTCONF --scan-serial auto 2>> \"$LOG_FILE\"`" if [ x"$serial_list" = x"" ]; then echo read_def $CS_ERR_NO_SERIAL "" @@ -876,7 +876,7 @@ choose_serial() { i="1" for s in $serial_list; do - DEV_NAME="`echo "$s" | awk -F' ' '{print \$3}'`" + DEV_NAME="`echo \"$s\" | awk -F' ' '{print \$3}'`" echo " $i. $DEV_NAME" i="`expr $i + 1`" done @@ -919,7 +919,7 @@ choose_manual_serial () { necho $CS_MANUAL_SERIAL_2 read_def $CS_MANUAL_SERIAL_3 "" - DEV="`$NUTCONF --scan-serial $answer 2>> "$LOG_FILE"`" + DEV="`$NUTCONF --scan-serial $answer 2>> \"$LOG_FILE\"`" if [ "$DEV" = "" ]; then read_def $CS_MANUAL_SERIAL_ERR "" @@ -986,14 +986,14 @@ choose_xml() { necho $CS_SNMP_6 echo - LIST_XML="`$NUTCONF --scan-xml-http 2>> "$LOG_FILE"`" + LIST_XML="`$NUTCONF --scan-xml-http 2>> \"$LOG_FILE\"`" if [ "$LIST_XML" = "" ]; then read_def $CS_SNMP_7 "" choose_snmp return $? fi - LIST_XML="`echo "$LIST_XML" | sort`" + LIST_XML="`echo \"$LIST_XML\" | sort`" echo @@ -1005,8 +1005,8 @@ choose_xml() { for s in $LIST_XML; do #FIXME: If there is a comma in the description, the description #will be shown up to this comma only. - IP_ADDR="`echo "$s" | awk -F' ' '{print \$3}' | sed 's/http:\/\///g'`" - #DESC="`echo "$s" | awk -F\" '{print $6}' | sed -e "s/^.*\"\(.*\)\".*$/\1/"`" + IP_ADDR="`echo \"$s\" | awk -F' ' '{print \$3}' | sed 's/http:\/\///g'`" + #DESC="`echo \"$s\" | awk -F\" '{print $6}' | sed -e \"s/^.*\"\(.*\)\".*$/\1/\"`" echo " $i. $IP_ADDR" i="`expr $i + 1`" done @@ -1076,9 +1076,9 @@ choose_snmp() { necho $CS_SNMP_6 echo - list="`$NUTCONF --scan-snmp "$FIRST_IP" "$LAST_IP" community="$C_COMMUNITY" 2>> "$LOG_FILE"`" + list="`$NUTCONF --scan-snmp \"$FIRST_IP\" \"$LAST_IP\" community=\"$C_COMMUNITY\" 2>> \"$LOG_FILE\"`" - list="`echo "$list" | sort`" + list="`echo \"$list\" | sort`" echo filter_snmp_list @@ -1096,8 +1096,8 @@ choose_snmp() { for s in $LIST_SNMP; do #FIXME: If there is a comma in the description, the description #will be shown up to this comma only. - IP_ADDR="`echo "$s" | awk -F' ' '{print \$3}'`" - #DESC="`echo "$s" | awk -F\" '{print $6}' | sed -e "s/^.*\"\(.*\)\".*$/\1/"`" + IP_ADDR="`echo \"$s\" | awk -F' ' '{print \$3}'`" + #DESC="`echo \"$s\" | awk -F\" '{print $6}' | sed -e \"s/^.*\"\(.*\)\".*$/\1/\"`" echo " $i. $IP_ADDR" i="`expr $i + 1`" done @@ -1141,17 +1141,17 @@ filter_snmp_list() { IFS=" " for s in $list; do - IP_SNMP="`echo "$s" | awk -F' ' '{print \$3}'`" + IP_SNMP="`echo \"$s\" | awk -F' ' '{print \$3}'`" TO_ADD="$s" for x in $LIST_XML; do - IP_XML="`echo "$x" | awk -F' ' '{print \$3}' | sed 's/http:\/\///g'`" + IP_XML="`echo \"$x\" | awk -F' ' '{print \$3}' | sed 's/http:\/\///g'`" if [ "$IP_SNMP" = "$IP_XML" ]; then TO_ADD="" break fi done if [ ! "$TO_ADD" = "" ]; then - LIST_SNMP="`echo "$LIST_SNMP";echo "$TO_ADD"`" + LIST_SNMP="`echo \"$LIST_SNMP\";echo \"$TO_ADD\"`" fi done IFS="$OLD1_IFS" @@ -1181,7 +1181,7 @@ choose_server() { necho $CS_SERVER_5 echo - list="`$NUTCONF --scan-nut "$FIRST_IP" "$LAST_IP" "$NUT_PORT" 2>> "$LOG_FILE"`" + list="`$NUTCONF --scan-nut \"$FIRST_IP\" \"$LAST_IP\" \"$NUT_PORT\" 2>> \"$LOG_FILE\"`" #TODO parse scan results if [ "$list" = "" ]; then read_def $CS_SERVER_6 "" @@ -1196,7 +1196,7 @@ choose_server() { i="1" for s in $list; do - UPS="`echo "$s" | awk -F' ' '{print \$3}'`" + UPS="`echo \"$s\" | awk -F' ' '{print \$3}'`" echo " $i. $UPS" i="`expr $i + 1`" done @@ -1261,7 +1261,7 @@ get_networked_device() { while [ "$NDEV" -lt "$C_NUM_DEV" ]; do NDEV="`expr $NDEV + 1`" eval TMP=\$C_DEVICE"$NDEV" - DRIVER="`echo "$TMP" | awk -F' ' '{print \$2}'`" + DRIVER="`echo \"$TMP\" | awk -F' ' '{print \$2}'`" if [ "$DRIVER" = "netxml-ups" ]; then C_NUM_NETWORK_DEVICE="`expr $C_NUM_NETWORK_DEVICE + 1`" fi @@ -1514,9 +1514,9 @@ apply_conf_client() { $NUTCONF --set-user upsmon=slave password=upsmon 2>> "$LOG_FILE" eval TMP=\$C_DEVICE$NDEV - DEV="`echo "$TMP" | awk -F' ' '{print \$3}'`" - UPS="`echo "$DEV" | awk -F'@' '{print \$1}'`" - HOST="`echo "$DEV"| awk -F'@' '{print \$2}'`" + DEV="`echo \"$TMP\" | awk -F' ' '{print \$3}'`" + UPS="`echo \"$DEV\" | awk -F'@' '{print \$1}'`" + HOST="`echo \"$DEV\"| awk -F'@' '{print \$2}'`" # TODO: Here we assume that one UPS powers one input of the server # Logically this can mismatch our setting of MINSUPPLIES if the user # (later) specifies real powersource counts, and topology is not 1:1 @@ -1525,18 +1525,18 @@ apply_conf_client() { while [ "$NDEV" -lt "$C_NUM_DEV" ]; do NDEV="`expr $NDEV + 1`" eval TMP=\$C_DEVICE$NDEV - DEV="`echo "$TMP" | awk -F' ' '{print \$3}'`" - UPS="`echo "$DEV" | awk -F'@' '{print \$1}'`" - HOST="`echo "$DEV"| awk -F'@' '{print \$2}'`" + DEV="`echo \"$TMP\" | awk -F' ' '{print \$3}'`" + UPS="`echo \"$DEV\" | awk -F'@' '{print \$1}'`" + HOST="`echo \"$DEV\"| awk -F'@' '{print \$2}'`" $NUTCONF --add-monitor "${UPS}" "$HOST" 1 upsmon upsmon slave 2>> "$LOG_FILE" done } split_device () { eval TMP=\$C_DEVICE$NDEV - ID="`echo "$TMP" | awk -F' ' '{print \$1}'`" - DRIVER="`echo "$TMP" | awk -F' ' '{print \$2}'`" - PORT="`echo "$TMP" | awk -F' ' '{print \$3}'`" + ID="`echo \"$TMP\" | awk -F' ' '{print \$1}'`" + DRIVER="`echo \"$TMP\" | awk -F' ' '{print \$2}'`" + PORT="`echo \"$TMP\" | awk -F' ' '{print \$3}'`" } setup_tty () { @@ -1788,7 +1788,7 @@ ret=TRUE necho $WELCOME_STR1 # echo " `pwd`/$0 Version 5.0.0" - lineStr="`head "-$STR_VERSION" $installres |tail -1`" + lineStr="`head \"-$STR_VERSION\" $installres |tail -1`" #echo " `pwd`/$0 $lineStr $lineStr1" echo " $lineStr $IPP_VERSION" diff --git a/scripts/installer/uninstall-ipp b/scripts/installer/uninstall-ipp index 3e6f1d4fc9..8a652f6746 100755 --- a/scripts/installer/uninstall-ipp +++ b/scripts/installer/uninstall-ipp @@ -11,7 +11,7 @@ export PATH NUT_PACKAGE_SOLARI="NUT_solaris_sparc_package2.6.5.local" NUT_PACKAGE_SOLINT="NUT_solaris_i386_package2.6.5.local" -cd "`dirname "$0"`" +cd "`dirname \"$0\"`" #configuration data C_MODE="standalone" diff --git a/scripts/misc/.gitignore b/scripts/misc/.gitignore new file mode 100644 index 0000000000..6147e35ee8 --- /dev/null +++ b/scripts/misc/.gitignore @@ -0,0 +1 @@ +/nut.bash_completion diff --git a/scripts/misc/notifyme-debug b/scripts/misc/notifyme-debug index b93e9eaa35..550e62e4f2 100755 --- a/scripts/misc/notifyme-debug +++ b/scripts/misc/notifyme-debug @@ -12,7 +12,7 @@ # Licensed under the terms of the Network UPS Tools source license (GPLv2+) [ -n "${TEMPDIR-}" ] || TEMPDIR="${TMPDIR-}" -[ -n "${TEMPDIR-}" ] || { [ -d "/dev/shm" && TEMPDIR="/dev/shm" || TEMPDIR="/tmp" ; } +[ -n "${TEMPDIR-}" ] || { [ -d "/dev/shm" ] && TEMPDIR="/dev/shm" || TEMPDIR="/tmp" ; } printf '%s\t[%s]\t%s\t[%s]:\t%s\t(%s tokens)\n' "`date -u`" "`id`" "${NOTIFYTYPE-}" "${UPSNAME-}" "$*" "$#" >> "${TEMPDIR}/notifyme-`id -u`.log" @@ -24,5 +24,5 @@ if [ -n "${TOP_BUILDDIR}" -a -x "${TOP_BUILDDIR}/clients/upssched" ] ; then if [ "${NUT_DEBUG_LEVEL-}" -gt 0 ] 2>/dev/null ; then printf '%s: %s\t%s\t[%s]:\targs: %s\t(%s arg tokens)\n' "`date -u`" "$0" "${NOTIFYTYPE-}" "${UPSNAME-}" "$*" "$#" >&2 fi - "${TOP_BUILDDIR}/clients/upssched" + "${TOP_BUILDDIR}/clients/upssched" "$@" fi diff --git a/scripts/misc/nut.bash_completion b/scripts/misc/nut.bash_completion.in similarity index 90% rename from scripts/misc/nut.bash_completion rename to scripts/misc/nut.bash_completion.in index 1844134277..d87be20a50 100644 --- a/scripts/misc/nut.bash_completion +++ b/scripts/misc/nut.bash_completion.in @@ -62,12 +62,12 @@ _nut_upscmd_completion() case "$prev" in -u|-p) # TODO: match against upsd.users, if readable. COMPREPLY=( ) ; return 0 ;; - -l) + -l) upses="$(_nut_upses)" COMPREPLY=( $(compgen -W "$upses" -- ${cur}) ) ; return 0 ;; - upscmd) - upses="$(_nut_upses)" - COMPREPLY=( $(compgen -W "$options $upses" -- ${cur}) ) ; return 0 ;; + upscmd) + upses="$(_nut_upses)" + COMPREPLY=( $(compgen -W "$options $upses" -- ${cur}) ) ; return 0 ;; esac # If the user starts to type an option, then only offer options for that word: @@ -80,7 +80,7 @@ _nut_upscmd_completion() # Get the list of commands from the UPS named in the previous word: local cmds - cmds=$(upscmd -l $prev 2>/dev/null | tail -n +3 | sed 's/ - .*//' ) + cmds=$(upscmd -l $prev 2>/dev/null | @TAIL@ @TAIL_ARGS_FROM_NTH_LINE@ +3 | sed 's/ - .*//' ) COMPREPLY=( $(compgen -W "$cmds" -- ${cur}) ) return 0 @@ -100,9 +100,9 @@ _nut_upsd_completion() options="-c -D -f -h -r -u -V -4 -6" case "$prev" in - -c) # commands: + -c) # commands: COMPREPLY=( $(compgen -W "reload stop" -- ${cur}) ) ; return 0 ;; - -r) # chroot: + -r) # chroot: COMPREPLY=( $(compgen -A directory -- ${cur}) ) ; return 0 ;; -u) # system user, not in upsd.users COMPREPLY=( $(compgen -u -- ${cur}) ) ; return 0 ;; @@ -128,13 +128,13 @@ _nut_upsdrvctl_completion() options="-h -r -t -u -D" case "$prev" in - -r) # chroot: + -r) # chroot: COMPREPLY=( $(compgen -A directory -- ${cur}) ) ; return 0 ;; -u) # system user, not in upsd.users COMPREPLY=( $(compgen -u -- ${cur}) ) ; return 0 ;; - start|stop|shutdown) - upses="$(_nut_local_upses)" - COMPREPLY=( $(compgen -W "$upses" -- ${cur}) ) ; return 0 ;; + start|stop|shutdown) + upses="$(_nut_local_upses)" + COMPREPLY=( $(compgen -W "$upses" -- ${cur}) ) ; return 0 ;; esac # If the user starts to type an option, then only offer options for that word: @@ -162,7 +162,7 @@ _nut_upsmon_completion() options="-c -D -h -K -u -4 -6" case "$prev" in - -c) # commands: + -c) # commands: COMPREPLY=( $(compgen -W "fsd reload stop" -- ${cur}) ) ; return 0 ;; -u) # system user, not in upsd.users COMPREPLY=( $(compgen -u -- ${cur}) ) ; return 0 ;; @@ -192,7 +192,7 @@ _nut_upsrw_completion() case "$prev" in -u|-p) # TODO: match against upsd.users, if readable. COMPREPLY=( ) ; return 0 ;; - -l) + -l) COMPREPLY=( $(compgen -W "$upses" -- ${cur}) ) ; return 0 ;; esac diff --git a/scripts/obs/Makefile.am b/scripts/obs/Makefile.am new file mode 100644 index 0000000000..012c32697d --- /dev/null +++ b/scripts/obs/Makefile.am @@ -0,0 +1,121 @@ +# Network UPS Tools: obs (root) + +EXTRA_DIST = README.adoc _service _config + +# Also EXTRA_DIST the recipe-related files (so OBS can use tarballs if they like) + +# Debian/Ubuntu recipes: +EXTRA_DIST += \ + debian.Makefile.am \ + debian.NEWS \ + debian.changelog \ + debian.compat \ + debian.control \ + debian.copyright \ + debian.description.subst \ + debian.gbp.conf \ + debian.hotplug \ + debian.libnutclient-dev.install \ + debian.libnutclient-dev.manpages \ + debian.libnutclient1.install \ + debian.libnutclient1.lintian-overrides \ + debian.libnutclientstub-dev.install \ + debian.libnutclientstub1.install \ + debian.libnutclientstub1.lintian-overrides \ + debian.libnutscan-dev.install \ + debian.libnutscan1.install \ + debian.libups-nut-perl.install \ + debian.libupsclient-dev.install \ + debian.libupsclient-dev.manpages \ + debian.libupsclient4.install \ + nut.changes \ + debian.nut-cgi.README.Debian \ + debian.nut-cgi.docs \ + debian.nut-cgi.install \ + debian.nut-cgi.manpages \ + debian.nut-cgi.postinst \ + debian.nut-cgi.postrm \ + debian.nut-client.init.in \ + debian.nut-client.install \ + debian.nut-client.links \ + debian.nut-client.lintian-overrides \ + debian.nut-client.maintscript \ + debian.nut-client.manpages \ + debian.nut-client.postinst \ + debian.nut-client.preinst \ + debian.nut-client.prerm \ + debian.nut-common.install \ + debian.nut-common.postinst.in \ + debian.nut-common.prerm.in \ + debian.nut-common.tmpfiles.in \ + debian.nut-doc.doc-base.nut-developer-guide \ + debian.nut-doc.doc-base.nut-faq \ + debian.nut-doc.doc-base.nut-packager-guide \ + debian.nut-doc.doc-base.nut-user-manual \ + debian.nut-doc.install \ + debian.nut-ipmi.install \ + debian.nut-ipmi.manpages \ + debian.nut-linux-i2c.install \ + debian.nut-linux-i2c.manpages \ + debian.nut-modbus.install \ + debian.nut-modbus.manpages \ + debian.nut-monitor.install \ + debian.nut-monitor.menu \ + debian.nut-monitor.xpm \ + debian.nut-powerman-pdu.install \ + debian.nut-powerman-pdu.manpages \ + debian.nut-server.dirs \ + debian.nut-server.init.in \ + debian.nut-server.install \ + debian.nut-server.manpages \ + debian.nut-server.postinst \ + debian.nut-server.postrm \ + debian.nut-server.preinst \ + debian.nut-server.prerm.in \ + debian.nut-snmp.docs \ + debian.nut-snmp.install \ + debian.nut-snmp.manpages \ + debian.nut-xml.install \ + debian.nut-xml.manpages \ + debian.nut.README.Debian \ + debian.nut.TODO.Debian \ + debian.nut.docs \ + debian.python-nut.install \ + debian.rules \ + debian.series \ + debian.watch \ + nut.dsc + +# RedHAT/Fedora/CentOS/... recipes: +EXTRA_DIST += \ + nut.spec + +# PKGSRC recipes: +EXTRA_DIST += \ + pkgsrc.obs + +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): +#%-spellchecked: % Makefile.am $(top_srcdir)/docs/Makefile.am $(abs_srcdir)/$(NUT_SPELL_DICT) +# +$(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) $(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) $(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) $(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 + +MAINTAINERCLEANFILES = Makefile.in .dirstamp diff --git a/scripts/obs/README.adoc b/scripts/obs/README.adoc new file mode 100644 index 0000000000..c43a66ab61 --- /dev/null +++ b/scripts/obs/README.adoc @@ -0,0 +1,66 @@ += Packaging recipes for OBS + +== Overview + +This directory holds reference NUT packaging recipes for RPM, DEB and +other formats, and is primarily aimed at automation of builds with the +link:https://openbuildservice.org/[Open Build Service (OBS)]. + +Note that beside the on-line service, the software is open-source and +any project may install and run their own server instance. The `osc build` +command can also be used on a workstation to iterate recipe nuances for +different distributions. In theory, back-end scripts may be added to +support more operating systems over time (even not necessarily Linux). + +NOTE: The layout of files and directories here is dictated by current +expectations of the OBS software (if at some point it has to be a flat +mess with no structure -- so be it). + +== Practical setup + +The copy of `_service` file represents the directly maintained OBS recipe +in an OBS packaging project, and is the only file needed there (triggering +service runs updates the temporary tarball from git). + +Beside that, a `_config` file in the OBS project repository or live settings +in `osc meta prjconf --edit` can be used to specify unambiguous preferences +for certain packages, when a distro actually offers a choice via conditional +`BuildRequires` statements (and other options), e.g.: + +---- +Prefer: libusbx-devel +Prefer: libusb-1.0-dev +Prefer: neon-devel +---- + +* For more details on this, see + https://openbuildservice.org/help/manuals/obs-user-guide/cha-obs-prjconfig + +A parent project must be used to define OBS Repositories we build for, which +should usually include a layer with `openSUSE:Tools` to have the OBS helper +scripts needed for package and tarball transformations in the build area, +for example: https://build.opensuse.org/projects/home:jimklimov/meta + +The list of known distros and repositories massaged with these tools can be +seen at https://build.opensuse.org/repositories/openSUSE:Tools + +For some historical and/or practical details see: + +* https://github.com/networkupstools/nut/issues/1209 + +== Other notes + +The recipe files themselves would likely cross-pollinate with popular +distributions, to allow easier replacement of standard, supported and +stale package builds with those of experimental development iterations. + +This should be in part facilitated by the NUT Semantic Versioning scheme (see +linkdoc:developer-guide[NUT Semantic Versioning,versioning,docs/nut-versioning.adoc]) +which adds version number components to expose the "age" of each such +iteration as a number of commits merged into the common development trunk +since a preceding release, and a number of commits unique to a feature +branch. + +Maybe some way of pre-making a dist tarball (with `VERSION_DEFAULT`, man +pages pre-built, etc.) and using that would be better, to be explored +(ideally if we can do this within OBS). diff --git a/scripts/obs/_config b/scripts/obs/_config new file mode 100644 index 0000000000..139af5ab1d --- /dev/null +++ b/scripts/obs/_config @@ -0,0 +1,17 @@ +# OBS prjconf for NUT builds +# See https://openbuildservice.org/help/manuals/obs-user-guide/cha-obs-prjconfig + +# Unambiguous preference if "Requires: (A or B)" offered both hits: +Prefer: libusbx-devel +Prefer: libusb-1.0-dev +Prefer: neon-devel +Prefer: util-linux + +%if "x%{_repository}" == "xRaspbian_11" || "x%{_repository}" == "xRHEL_7" +%else +# Use ccache or sccache (let OBS pick its preference) for faster (re-)builds: +BuildFlags: useccache:nut +%endif + +# Embed GIT URL into package metadata: +BuildFlags: setvcs diff --git a/scripts/obs/_service b/scripts/obs/_service new file mode 100644 index 0000000000..81a37d0ecf --- /dev/null +++ b/scripts/obs/_service @@ -0,0 +1,59 @@ + + + https://github.com/jimklimov/nut.git + git + FTY-obs + + + + @PARENT_TAG@.@TAG_OFFSET@ + v(.*) + .git + nut + + scripts/obs/nut.dsc + scripts/obs/nut.spec + scripts/obs/pkgsrc.* + + enable + + + + + + + + + + *.tar + */scripts/obs/debian.* + + + *.tar + */scripts/obs/nut*.changes + + + + + nut + + + + *.tar + gz + + \ No newline at end of file diff --git a/obs/debian.Makefile.am b/scripts/obs/debian.Makefile.am similarity index 100% rename from obs/debian.Makefile.am rename to scripts/obs/debian.Makefile.am diff --git a/obs/debian.NEWS b/scripts/obs/debian.NEWS similarity index 100% rename from obs/debian.NEWS rename to scripts/obs/debian.NEWS diff --git a/obs/debian.changelog b/scripts/obs/debian.changelog similarity index 99% rename from obs/debian.changelog rename to scripts/obs/debian.changelog index af5c586cb2..092c093c21 100644 --- a/obs/debian.changelog +++ b/scripts/obs/debian.changelog @@ -1,3 +1,9 @@ +nut (2.8.4-1) UNRELEASED; urgency=high + + * Revising recipes for OBS builds + + -- Jim Klimov Sun, 5 Oct 2025 14:00:00 +0200 + nut (2.7.4-14) UNRELEASED; urgency=high * Ported packaging recipes over upstream NUT, commented away DMF diff --git a/obs/debian.compat b/scripts/obs/debian.compat similarity index 100% rename from obs/debian.compat rename to scripts/obs/debian.compat diff --git a/obs/debian.control b/scripts/obs/debian.control similarity index 98% rename from obs/debian.control rename to scripts/obs/debian.control index 739dc9ac01..e14a9b93a3 100644 --- a/obs/debian.control +++ b/scripts/obs/debian.control @@ -12,11 +12,11 @@ Build-Depends: debhelper (>= 8.1.3), libjpeg-dev, libsnmp-dev | libsnmp9-dev, libssl1.0-dev | libssl-dev, - libusb-dev (>= 0.1.8), + libusb-1.0-dev | libusb-0.1-dev | libusb-dev (>= 0.1.8), libneon27-gnutls-dev | libneon27-dev, libpowerman0-dev (>= 2.3.3), libwrap0-dev (>= 7.6), - python (>= 2.6.6-3~) | python-is-python2 | python-is-python3, python2 | python3, dh-python | dh-python2 | dh-python3 | dh-pypy, + python2 | python3, dh-python | dh-python2 | dh-python3 | dh-pypy, libfreeipmi-dev (>= 0.8.5) [!hurd-i386], libipmimonitoring-dev (>= 1.1.5-2) [!hurd-i386], libmodbus-dev (>= 3.1.6), @@ -25,6 +25,7 @@ Build-Depends: debhelper (>= 8.1.3), liblua5.1-0-dev, lua5.1, pkg-config +#python (>= 2.6.6-3~) | python-is-python2 | python-is-python3, Build-Depends-Indep: asciidoc (>= 8.6.3), docbook-xsl, dblatex (>= 0.2.5), diff --git a/obs/debian.copyright b/scripts/obs/debian.copyright similarity index 100% rename from obs/debian.copyright rename to scripts/obs/debian.copyright diff --git a/obs/debian.description.subst b/scripts/obs/debian.description.subst similarity index 100% rename from obs/debian.description.subst rename to scripts/obs/debian.description.subst diff --git a/obs/debian.gbp.conf b/scripts/obs/debian.gbp.conf similarity index 100% rename from obs/debian.gbp.conf rename to scripts/obs/debian.gbp.conf diff --git a/obs/debian.hotplug b/scripts/obs/debian.hotplug similarity index 100% rename from obs/debian.hotplug rename to scripts/obs/debian.hotplug diff --git a/obs/debian.libnutclient-dev.install b/scripts/obs/debian.libnutclient-dev.install similarity index 100% rename from obs/debian.libnutclient-dev.install rename to scripts/obs/debian.libnutclient-dev.install diff --git a/obs/debian.libnutclient-dev.manpages b/scripts/obs/debian.libnutclient-dev.manpages similarity index 100% rename from obs/debian.libnutclient-dev.manpages rename to scripts/obs/debian.libnutclient-dev.manpages diff --git a/obs/debian.libnutclient1.install b/scripts/obs/debian.libnutclient1.install similarity index 100% rename from obs/debian.libnutclient1.install rename to scripts/obs/debian.libnutclient1.install diff --git a/obs/debian.libnutclient1.lintian-overrides b/scripts/obs/debian.libnutclient1.lintian-overrides similarity index 100% rename from obs/debian.libnutclient1.lintian-overrides rename to scripts/obs/debian.libnutclient1.lintian-overrides diff --git a/obs/debian.libnutclientstub-dev.install b/scripts/obs/debian.libnutclientstub-dev.install similarity index 100% rename from obs/debian.libnutclientstub-dev.install rename to scripts/obs/debian.libnutclientstub-dev.install diff --git a/obs/debian.libnutclientstub1.install b/scripts/obs/debian.libnutclientstub1.install similarity index 100% rename from obs/debian.libnutclientstub1.install rename to scripts/obs/debian.libnutclientstub1.install diff --git a/obs/debian.libnutclientstub1.lintian-overrides b/scripts/obs/debian.libnutclientstub1.lintian-overrides similarity index 100% rename from obs/debian.libnutclientstub1.lintian-overrides rename to scripts/obs/debian.libnutclientstub1.lintian-overrides diff --git a/obs/debian.libnutscan-dev.install b/scripts/obs/debian.libnutscan-dev.install similarity index 100% rename from obs/debian.libnutscan-dev.install rename to scripts/obs/debian.libnutscan-dev.install diff --git a/obs/debian.libnutscan1.install b/scripts/obs/debian.libnutscan1.install similarity index 100% rename from obs/debian.libnutscan1.install rename to scripts/obs/debian.libnutscan1.install diff --git a/obs/debian.libups-nut-perl.install b/scripts/obs/debian.libups-nut-perl.install similarity index 100% rename from obs/debian.libups-nut-perl.install rename to scripts/obs/debian.libups-nut-perl.install diff --git a/obs/debian.libupsclient-dev.install b/scripts/obs/debian.libupsclient-dev.install similarity index 100% rename from obs/debian.libupsclient-dev.install rename to scripts/obs/debian.libupsclient-dev.install diff --git a/obs/debian.libupsclient-dev.manpages b/scripts/obs/debian.libupsclient-dev.manpages similarity index 100% rename from obs/debian.libupsclient-dev.manpages rename to scripts/obs/debian.libupsclient-dev.manpages diff --git a/obs/debian.libupsclient4.install b/scripts/obs/debian.libupsclient4.install similarity index 100% rename from obs/debian.libupsclient4.install rename to scripts/obs/debian.libupsclient4.install diff --git a/obs/debian.nut-cgi.README.Debian b/scripts/obs/debian.nut-cgi.README.Debian similarity index 100% rename from obs/debian.nut-cgi.README.Debian rename to scripts/obs/debian.nut-cgi.README.Debian diff --git a/scripts/obs/debian.nut-cgi.docs b/scripts/obs/debian.nut-cgi.docs new file mode 100644 index 0000000000..d9036f689e --- /dev/null +++ b/scripts/obs/debian.nut-cgi.docs @@ -0,0 +1 @@ +data/htmlcgi/README.adoc diff --git a/obs/debian.nut-cgi.install b/scripts/obs/debian.nut-cgi.install similarity index 100% rename from obs/debian.nut-cgi.install rename to scripts/obs/debian.nut-cgi.install diff --git a/obs/debian.nut-cgi.manpages b/scripts/obs/debian.nut-cgi.manpages similarity index 100% rename from obs/debian.nut-cgi.manpages rename to scripts/obs/debian.nut-cgi.manpages diff --git a/obs/debian.nut-cgi.postinst b/scripts/obs/debian.nut-cgi.postinst similarity index 100% rename from obs/debian.nut-cgi.postinst rename to scripts/obs/debian.nut-cgi.postinst diff --git a/obs/debian.nut-cgi.postrm b/scripts/obs/debian.nut-cgi.postrm similarity index 100% rename from obs/debian.nut-cgi.postrm rename to scripts/obs/debian.nut-cgi.postrm diff --git a/obs/debian.nut-client.init.in b/scripts/obs/debian.nut-client.init.in similarity index 100% rename from obs/debian.nut-client.init.in rename to scripts/obs/debian.nut-client.init.in diff --git a/obs/debian.nut-client.install b/scripts/obs/debian.nut-client.install similarity index 100% rename from obs/debian.nut-client.install rename to scripts/obs/debian.nut-client.install diff --git a/obs/debian.nut-client.links b/scripts/obs/debian.nut-client.links similarity index 100% rename from obs/debian.nut-client.links rename to scripts/obs/debian.nut-client.links diff --git a/obs/debian.nut-client.lintian-overrides b/scripts/obs/debian.nut-client.lintian-overrides similarity index 100% rename from obs/debian.nut-client.lintian-overrides rename to scripts/obs/debian.nut-client.lintian-overrides diff --git a/obs/debian.nut-client.maintscript b/scripts/obs/debian.nut-client.maintscript similarity index 100% rename from obs/debian.nut-client.maintscript rename to scripts/obs/debian.nut-client.maintscript diff --git a/obs/debian.nut-client.manpages b/scripts/obs/debian.nut-client.manpages similarity index 100% rename from obs/debian.nut-client.manpages rename to scripts/obs/debian.nut-client.manpages diff --git a/obs/debian.nut-client.postinst b/scripts/obs/debian.nut-client.postinst similarity index 100% rename from obs/debian.nut-client.postinst rename to scripts/obs/debian.nut-client.postinst diff --git a/obs/debian.nut-client.preinst b/scripts/obs/debian.nut-client.preinst similarity index 100% rename from obs/debian.nut-client.preinst rename to scripts/obs/debian.nut-client.preinst diff --git a/obs/debian.nut-client.prerm b/scripts/obs/debian.nut-client.prerm similarity index 100% rename from obs/debian.nut-client.prerm rename to scripts/obs/debian.nut-client.prerm diff --git a/obs/debian.nut-common.install b/scripts/obs/debian.nut-common.install similarity index 100% rename from obs/debian.nut-common.install rename to scripts/obs/debian.nut-common.install diff --git a/obs/debian.nut-common.postinst.in b/scripts/obs/debian.nut-common.postinst.in similarity index 100% rename from obs/debian.nut-common.postinst.in rename to scripts/obs/debian.nut-common.postinst.in diff --git a/obs/debian.nut-common.prerm.in b/scripts/obs/debian.nut-common.prerm.in similarity index 100% rename from obs/debian.nut-common.prerm.in rename to scripts/obs/debian.nut-common.prerm.in diff --git a/obs/debian.nut-common.tmpfiles.in b/scripts/obs/debian.nut-common.tmpfiles.in similarity index 100% rename from obs/debian.nut-common.tmpfiles.in rename to scripts/obs/debian.nut-common.tmpfiles.in diff --git a/obs/debian.nut-doc.doc-base.nut-developer-guide b/scripts/obs/debian.nut-doc.doc-base.nut-developer-guide similarity index 100% rename from obs/debian.nut-doc.doc-base.nut-developer-guide rename to scripts/obs/debian.nut-doc.doc-base.nut-developer-guide diff --git a/obs/debian.nut-doc.doc-base.nut-faq b/scripts/obs/debian.nut-doc.doc-base.nut-faq similarity index 100% rename from obs/debian.nut-doc.doc-base.nut-faq rename to scripts/obs/debian.nut-doc.doc-base.nut-faq diff --git a/obs/debian.nut-doc.doc-base.nut-packager-guide b/scripts/obs/debian.nut-doc.doc-base.nut-packager-guide similarity index 100% rename from obs/debian.nut-doc.doc-base.nut-packager-guide rename to scripts/obs/debian.nut-doc.doc-base.nut-packager-guide diff --git a/obs/debian.nut-doc.doc-base.nut-user-manual b/scripts/obs/debian.nut-doc.doc-base.nut-user-manual similarity index 100% rename from obs/debian.nut-doc.doc-base.nut-user-manual rename to scripts/obs/debian.nut-doc.doc-base.nut-user-manual diff --git a/obs/debian.nut-doc.install b/scripts/obs/debian.nut-doc.install similarity index 100% rename from obs/debian.nut-doc.install rename to scripts/obs/debian.nut-doc.install diff --git a/obs/debian.nut-ipmi.install b/scripts/obs/debian.nut-ipmi.install similarity index 100% rename from obs/debian.nut-ipmi.install rename to scripts/obs/debian.nut-ipmi.install diff --git a/obs/debian.nut-ipmi.manpages b/scripts/obs/debian.nut-ipmi.manpages similarity index 100% rename from obs/debian.nut-ipmi.manpages rename to scripts/obs/debian.nut-ipmi.manpages diff --git a/obs/nut-linux-i2c.install b/scripts/obs/debian.nut-linux-i2c.install similarity index 100% rename from obs/nut-linux-i2c.install rename to scripts/obs/debian.nut-linux-i2c.install diff --git a/obs/nut-linux-i2c.manpages b/scripts/obs/debian.nut-linux-i2c.manpages similarity index 100% rename from obs/nut-linux-i2c.manpages rename to scripts/obs/debian.nut-linux-i2c.manpages diff --git a/obs/nut-modbus.install b/scripts/obs/debian.nut-modbus.install similarity index 100% rename from obs/nut-modbus.install rename to scripts/obs/debian.nut-modbus.install diff --git a/obs/nut-modbus.manpages b/scripts/obs/debian.nut-modbus.manpages similarity index 100% rename from obs/nut-modbus.manpages rename to scripts/obs/debian.nut-modbus.manpages diff --git a/obs/debian.nut-monitor.install b/scripts/obs/debian.nut-monitor.install similarity index 83% rename from obs/debian.nut-monitor.install rename to scripts/obs/debian.nut-monitor.install index f92d0850ee..8c2975fd06 100644 --- a/obs/debian.nut-monitor.install +++ b/scripts/obs/debian.nut-monitor.install @@ -1,6 +1,6 @@ scripts/python/app/NUT-Monitor usr/bin/ scripts/python/app/nut-monitor.desktop usr/share/applications/ -scripts/python/app/gui-*.glade usr/share/nut-monitor/ +scripts/python/app/ui/gui-*.glade usr/share/nut-monitor/ scripts/python/app/locale/ usr/share/ scripts/python/app/icons/48x48/nut-monitor.png usr/share/pixmaps/ scripts/python/app/pixmaps usr/share/nut-monitor/ diff --git a/obs/debian.nut-monitor.menu b/scripts/obs/debian.nut-monitor.menu similarity index 100% rename from obs/debian.nut-monitor.menu rename to scripts/obs/debian.nut-monitor.menu diff --git a/obs/debian.nut-monitor.xpm b/scripts/obs/debian.nut-monitor.xpm similarity index 100% rename from obs/debian.nut-monitor.xpm rename to scripts/obs/debian.nut-monitor.xpm diff --git a/obs/debian.nut-powerman-pdu.install b/scripts/obs/debian.nut-powerman-pdu.install similarity index 100% rename from obs/debian.nut-powerman-pdu.install rename to scripts/obs/debian.nut-powerman-pdu.install diff --git a/obs/debian.nut-powerman-pdu.manpages b/scripts/obs/debian.nut-powerman-pdu.manpages similarity index 100% rename from obs/debian.nut-powerman-pdu.manpages rename to scripts/obs/debian.nut-powerman-pdu.manpages diff --git a/obs/debian.nut-server.dirs b/scripts/obs/debian.nut-server.dirs similarity index 100% rename from obs/debian.nut-server.dirs rename to scripts/obs/debian.nut-server.dirs diff --git a/obs/debian.nut-server.init.in b/scripts/obs/debian.nut-server.init.in similarity index 100% rename from obs/debian.nut-server.init.in rename to scripts/obs/debian.nut-server.init.in diff --git a/obs/debian.nut-server.install b/scripts/obs/debian.nut-server.install similarity index 100% rename from obs/debian.nut-server.install rename to scripts/obs/debian.nut-server.install diff --git a/obs/debian.nut-server.manpages b/scripts/obs/debian.nut-server.manpages similarity index 100% rename from obs/debian.nut-server.manpages rename to scripts/obs/debian.nut-server.manpages diff --git a/obs/debian.nut-server.postinst b/scripts/obs/debian.nut-server.postinst similarity index 100% rename from obs/debian.nut-server.postinst rename to scripts/obs/debian.nut-server.postinst diff --git a/obs/debian.nut-server.postrm b/scripts/obs/debian.nut-server.postrm similarity index 100% rename from obs/debian.nut-server.postrm rename to scripts/obs/debian.nut-server.postrm diff --git a/obs/debian.nut-server.preinst b/scripts/obs/debian.nut-server.preinst similarity index 100% rename from obs/debian.nut-server.preinst rename to scripts/obs/debian.nut-server.preinst diff --git a/obs/debian.nut-server.prerm.in b/scripts/obs/debian.nut-server.prerm.in similarity index 100% rename from obs/debian.nut-server.prerm.in rename to scripts/obs/debian.nut-server.prerm.in diff --git a/obs/debian.nut-snmp.docs b/scripts/obs/debian.nut-snmp.docs similarity index 100% rename from obs/debian.nut-snmp.docs rename to scripts/obs/debian.nut-snmp.docs diff --git a/obs/debian.nut-snmp.install b/scripts/obs/debian.nut-snmp.install similarity index 100% rename from obs/debian.nut-snmp.install rename to scripts/obs/debian.nut-snmp.install diff --git a/obs/debian.nut-snmp.manpages b/scripts/obs/debian.nut-snmp.manpages similarity index 100% rename from obs/debian.nut-snmp.manpages rename to scripts/obs/debian.nut-snmp.manpages diff --git a/obs/debian.nut-xml.install b/scripts/obs/debian.nut-xml.install similarity index 100% rename from obs/debian.nut-xml.install rename to scripts/obs/debian.nut-xml.install diff --git a/obs/debian.nut-xml.manpages b/scripts/obs/debian.nut-xml.manpages similarity index 100% rename from obs/debian.nut-xml.manpages rename to scripts/obs/debian.nut-xml.manpages diff --git a/obs/debian.nut.README.Debian b/scripts/obs/debian.nut.README.Debian similarity index 100% rename from obs/debian.nut.README.Debian rename to scripts/obs/debian.nut.README.Debian diff --git a/obs/debian.nut.TODO.Debian b/scripts/obs/debian.nut.TODO.Debian similarity index 100% rename from obs/debian.nut.TODO.Debian rename to scripts/obs/debian.nut.TODO.Debian diff --git a/scripts/obs/debian.nut.docs b/scripts/obs/debian.nut.docs new file mode 100644 index 0000000000..e16c35721b --- /dev/null +++ b/scripts/obs/debian.nut.docs @@ -0,0 +1,46 @@ +AUTHORS +MAINTAINERS +README.adoc +NEWS.adoc +UPGRADING.adoc +docs/FAQ.txt +docs/acknowledgements.txt +docs/asciidoc.txt +docs/cables.txt +docs/ci-farm-do-setup.adoc +docs/ci-farm-lxc-setup.txt +docs/config-notes.txt +docs/config-prereqs.txt +docs/configure.txt +docs/contact-closure.txt +docs/daisychain.txt +docs/design.txt +docs/developer-guide.txt +docs/developers.txt +docs/documentation.txt +docs/download.txt +docs/features.txt +docs/hid-subdrivers.txt +docs/history.txt +docs/macros.txt +docs/maintainer-guide.txt +docs/net-protocol.txt +docs/new-clients.txt +docs/new-drivers.txt +docs/nut-names.txt +docs/nut-qa.txt +docs/nut-versioning.adoc +docs/nutdrv_qx-subdrivers.txt +docs/outlets.txt +docs/packager-guide.txt +docs/qa-guide.adoc +docs/release-notes.txt +docs/scheduling.txt +docs/security.txt +docs/sms-brazil-protocol.txt +docs/snmp-subdrivers.txt +docs/snmp.txt +docs/sock-protocol.txt +docs/solaris-usb.txt +docs/support.txt +docs/user-manual.txt diff --git a/obs/debian.python-nut.install b/scripts/obs/debian.python-nut.install similarity index 100% rename from obs/debian.python-nut.install rename to scripts/obs/debian.python-nut.install diff --git a/obs/debian.rules b/scripts/obs/debian.rules similarity index 69% rename from obs/debian.rules rename to scripts/obs/debian.rules index d72fd7b1e2..21b43e3613 100755 --- a/obs/debian.rules +++ b/scripts/obs/debian.rules @@ -2,9 +2,22 @@ include /usr/share/cdbs/1/rules/debhelper.mk include /usr/share/cdbs/1/class/autotools.mk -include /usr/share/cdbs/1/class/python-module.mk - -include /usr/share/cdbs/1/rules/autoreconf.mk +-include /usr/share/cdbs/1/class/python-module.mk + +-include /usr/share/cdbs/1/rules/autoreconf.mk + +# CDBS has some issues detecting something other than dh_python2 +# which is absent on Debian 12+ and Ubuntu 25.04 for example. +# This hack is broken for Debian Next in Oct 2025, as CDBS sheds +# a lot of older scripts and the helpers seem to get implemented +# somehow differently. +ifeq (,$(shell command -v dh_python2 || which dh_python2 || dh_python2 --help 2>/dev/null)) +ifneq (,$(shell command -v dh_python3 || which dh_python3 || dh_python3 --help 2>/dev/null)) +ifneq (,$(shell command -v python3 || which python3 || python3 --help 2>/dev/null)) +cdbs_curpythonsystems := python3 +endif +endif +endif export DH_OPTIONS @@ -19,24 +32,30 @@ DEB_DH_AUTORECONF_ARGS = --as-needed DEB_HOST_ARCH_OS := $(shell dpkg-architecture -qDEB_HOST_ARCH_OS 2>/dev/null) # echoed fallbacks are for Ubuntu primarily -systemdsystemunitdir := $(shell pkg-config --variable=systemdsystemunitdir systemd || echo /lib/systemd/system) -systemdsystemdutildir := $(shell pkg-config --variable=systemdutildir systemd || echo /lib/systemd) -systemdshutdowndir := $(shell pkg-config --variable=systemdshutdowndir systemd || echo /lib/systemd/system-shutdown) -systemdtmpfilesdir := $(shell pkg-config --variable=systemdtmpfilesdir systemd || echo /usr/lib/tmpfiles.d) +systemdsystemunitdir := $(shell OUT="`pkg-config --variable=systemdsystemunitdir libsystemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || OUT="`pkg-config --variable=systemdsystemunitdir systemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || echo /lib/systemd/system) +systemdsystemdutildir := $(shell OUT="`pkg-config --variable=systemdutildir libsystemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || OUT="`pkg-config --variable=systemdutildir systemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || echo /lib/systemd) +systemdshutdowndir := $(shell OUT="`pkg-config --variable=systemdshutdowndir libsystemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || OUT="`pkg-config --variable=systemdshutdowndir systemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || echo /lib/systemd/system-shutdown) +systemdtmpfilesdir := $(shell OUT="`pkg-config --variable=systemdtmpfilesdir libsystemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || OUT="`pkg-config --variable=systemdtmpfilesdir systemd`" && [ -n "$$OUT" ] && { echo "$$OUT" ; exit; } || echo /usr/lib/tmpfiles.d) + +# Does this NUT branch have DMF feature code? +NUTPKG_WITH_DMF := $(shell test -d scripts/DMF && echo 1 || echo 0) # Newer systems have just /run (and yet newer systemd noisily suggests it) runbasedir := $(shell test -d /run && echo /run || echo /var/run) # FIXME: Find a smarter way to set those from main codebase recipes... -# Something like `grep 'version-info' **/Makefile.am` ? -SO_MAJOR_LIBUPSCLIENT=5 -SO_MAJOR_LIBNUTCLIENT=1 +# Something like `git grep 'version-info' '*.am'` ? +SO_MAJOR_LIBUPSCLIENT=7 +SO_MAJOR_LIBNUTCLIENT=2 SO_MAJOR_LIBNUTCLIENTSTUB=1 -SO_MAJOR_LIBNUTSCAN=1 +SO_MAJOR_LIBNUTSCAN=4 +SO_MAJOR_LIBNUTCONF=0 +ifneq (,$(shell ls -1 /usr/share/cdbs/1/rules/utils.mk 2>/dev/null)) # List any files which are not installed include /usr/share/cdbs/1/rules/utils.mk common-binary-post-install-arch:: list-missing +endif DEB_LDFLAGS_MAINT_APPEND=-Wl,-z,defs -Wl,-O1 -Wl,--as-needed include /usr/share/dpkg/buildflags.mk @@ -49,9 +68,7 @@ DEB_CONFIGURE_EXTRA_FLAGS := --libdir=\$${prefix}/lib/$(DEB_HOST_MULTIARCH) \ --with-ssl --with-openssl --with-libltdl=yes \ --with-cgi=auto --with-powerman=auto \ --with-serial --with-usb --with-snmp --with-neon --with-ipmi \ - --with-snmp_dmf_lua=yes \ - --with-dmfsnmp-regenerate=no --with-dmfnutscan-regenerate=no --with-dmfsnmp-validate=no --with-dmfnutscan-validate=no \ - --with-dev \ + --with-dev --with-dev-libnutconf \ --disable-static \ --with-statepath=$(runbasedir)/nut \ --with-altpidpath=$(runbasedir)/nut \ @@ -61,7 +78,15 @@ DEB_CONFIGURE_EXTRA_FLAGS := --libdir=\$${prefix}/lib/$(DEB_HOST_MULTIARCH) \ --with-pidpath=$(runbasedir)/nut \ --datadir=/usr/share/nut \ --with-pkgconfig-dir=/usr/lib/$(DEB_HOST_MULTIARCH)/pkgconfig \ - --with-user=nut --with-group=nut + --with-user=nut --with-group=nut\ + --enable-strip\ + --enable-keep_nut_report_feature --enable-check-NIT + +ifeq (1,$(NUTPKG_WITH_DMF)) +DEB_CONFIGURE_EXTRA_FLAGS += \ + --with-snmp_dmf_lua=yes \ + --with-dmfsnmp-regenerate=no --with-dmfnutscan-regenerate=no --with-dmfsnmp-validate=no --with-dmfnutscan-validate=no +endif ifeq (linux,$(DEB_HOST_ARCH_OS)) DEB_CONFIGURE_EXTRA_FLAGS+=--with-udev-dir=/lib/udev @@ -83,11 +108,21 @@ endif DEB_CONFIGURE_EXTRA_FLAGS+=--with-doc=man=dist-auto configure: configure.ac - (cd tools; python nut-snmpinfo.py) + ###Part of normal build now### (cd tools; python nut-snmpinfo.py) sh autogen.sh echo "PKG_CONFIG_PATH default:" ; pkg-config --variable pc_path pkg-config || true -pre-build:: debian/compat configure +ifeq (0,$(NUTPKG_WITH_DMF)) +# hide +patch-recipe-dmf: + for F in debian.*.install debian/*.install ; do [ -s "$$F" ] || continue ; sed 's,^\([^#].*dmf.*\)$$,#\1,' -i "$$F" || exit ; done +else +# expose +patch-recipe-dmf: + for F in debian.*.install debian/*.install ; do [ -s "$$F" ] || continue ; sed 's,^#\(.*dmf.*\)$$,\1,' -i "$$F" || exit ; done +endif + +pre-build:: patch-recipe-dmf debian/compat configure # FIXME : The recipe below is a crime scene regarding hardcoded paths that # are more or less relevant for a particular OS distribution and version, @@ -98,8 +133,8 @@ common-install-arch:: # prepare debian packaging files mkdir -p $(CURDIR)/debian for F in nut-client.init nut-common.postinst nut-common.prerm nut-server.init nut-server.prerm nut-common.tmpfiles ; do \ - echo " SED $F.in => $F" ; \ - sed 's,@runbasedir[@],$(runbasedir),g' < "$(CURDIR)/obs/debian.$$F.in" > $(CURDIR)/debian/"$$F" || exit ; \ + echo " SED $$F.in => $$F" ; \ + sed 's,@runbasedir[@],$(runbasedir),g' < "$(CURDIR)/scripts/obs/debian.$$F.in" > $(CURDIR)/debian/"$$F" || exit ; \ done # install the bash completion script @@ -142,6 +177,10 @@ common-install-arch:: test -s $(CURDIR)/debian/tmp/lib/$(DEB_HOST_MULTIARCH)/libnutscan.so.$(SO_MAJOR_LIBNUTSCAN) && \ ln -s /lib/$(DEB_HOST_MULTIARCH)/libnutscan.so.$(SO_MAJOR_LIBNUTSCAN) \ $(CURDIR)/debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/libnutscan.so + rm -f $(CURDIR)/debian/tmp/lib/$(DEB_HOST_MULTIARCH)/libnutconf.so + test -s $(CURDIR)/debian/tmp/lib/$(DEB_HOST_MULTIARCH)/libnutconf.so.$(SO_MAJOR_LIBNUTCONF) && \ + ln -s /lib/$(DEB_HOST_MULTIARCH)/libnutconf.so.$(SO_MAJOR_LIBNUTCONF) \ + $(CURDIR)/debian/tmp/usr/lib/$(DEB_HOST_MULTIARCH)/libnutconf.so # Install systemd files only on systems where it's supported ### Package files for nut-client: @@ -187,7 +226,14 @@ common-install-indep:: mkdir -p $(CURDIR)/debian/tmp/usr/share/doc/nut-doc binary-install/nut-monitor:: - dh_python2 -pnut-monitor || dh_python3 -pnut-monitor || dh_python -pnut-monitor + if (command -v dh_python2) ; then \ + dh_python2 -pnut-monitor ; \ + else if (command -v dh_python3) ; then \ + dh_python3 -pnut-monitor ; \ + else if (command -v dh_python) ; then \ + dh_python -pnut-monitor ; \ + else echo "$@: no implementation of dh_python was found!" >&2 ; exit 1 ; \ + fi; fi; fi DEB_DH_INSTALLINIT_ARGS_nut-server := --init-script=nut-server --restart-after-upgrade DEB_DH_INSTALLINIT_ARGS_nut-client := --init-script=nut-client --restart-after-upgrade diff --git a/obs/debian.series b/scripts/obs/debian.series similarity index 100% rename from obs/debian.series rename to scripts/obs/debian.series diff --git a/obs/debian.watch b/scripts/obs/debian.watch similarity index 100% rename from obs/debian.watch rename to scripts/obs/debian.watch diff --git a/obs/nut.changes b/scripts/obs/nut.changes similarity index 100% rename from obs/nut.changes rename to scripts/obs/nut.changes diff --git a/obs/nut.dsc b/scripts/obs/nut.dsc similarity index 86% rename from obs/nut.dsc rename to scripts/obs/nut.dsc index 29b6c3ba3f..8c910131a3 100644 --- a/obs/nut.dsc +++ b/scripts/obs/nut.dsc @@ -2,7 +2,7 @@ Format: 1.0 Source: nut Binary: nut, nut-server, nut-client, nut-cgi, nut-snmp, nut-ipmi, nut-modbus, nut-linux-i2c, nut-xml, nut-powerman-pdu, nut-doc, libupsclient4, libupsclient-dev, libnutclient1, libnutclient-dev, python-nut, nut-monitor, libups-nut-perl Architecture: any all -Version: 2.7.4-14 +Version: 2.8.4-1 Maintainer: Arnaud Quette Uploaders: Laurent Bigonville Homepage: http://www.networkupstools.org/ @@ -10,7 +10,8 @@ Standards-Version: 3.9.6 Vcs-Browser: http://anonscm.debian.org/gitweb/?p=collab-maint/nut.git;a=summary Vcs-Git: git://anonscm.debian.org/collab-maint/nut.git Testsuite: autopkgtest -Build-Depends: debhelper (>= 8.1.3), cdbs (>= 0.4.122~), autotools-dev, dh-autoreconf, libsystemd-dev, libjpeg-dev, libgd-dev | libgd2-xpm-dev | libgd2-noxpm-dev, libssl1.0-dev | libssl-dev, libsnmp-dev | libsnmp9-dev, libusb-dev (>= 0.1.8), libneon27-gnutls-dev | libneon27-dev, libpowerman0-dev (>= 2.3.3), libwrap0-dev (>= 7.6), python (>= 2.6.6-3~) | python-is-python2 | python-is-python3, python2 | python3, libfreeipmi-dev (>= 0.8.5) [!hurd-i386], libipmimonitoring-dev (>= 1.1.5-2) [!hurd-i386], libnss3-dev, libtool, libltdl-dev, liblua5.1-0-dev, lua5.1, pkg-config, dh-python | dh-python2 | dh-python3 | dh-pypy, libmodbus-dev, libi2c-dev +Build-Depends: debhelper (>= 8.1.3), cdbs (>= 0.4.122~), autotools-dev, dh-autoreconf, libsystemd-dev, libjpeg-dev, libgd-dev | libgd2-xpm-dev | libgd2-noxpm-dev, libssl1.0-dev | libssl-dev, libsnmp-dev | libsnmp9-dev, libusb-dev (>= 0.1.8), libneon27-gnutls-dev | libneon27-dev, libpowerman0-dev (>= 2.3.3), libwrap0-dev (>= 7.6), python2 | python3, libfreeipmi-dev (>= 0.8.5) [!hurd-i386], libipmimonitoring-dev (>= 1.1.5-2) [!hurd-i386], libnss3-dev, libtool, libltdl-dev, liblua5.1-0-dev, lua5.1, pkg-config, dh-python | dh-python2 | dh-python3 | dh-pypy, libmodbus-dev, libi2c-dev +# , python (>= 2.6.6-3~) | python-is-python2 | python-is-python3 #+++ python-pycparser # The pycparser is required to rebuild DMF files, but those pre-built # copies in the git repo/tarball "should" be in sync with original diff --git a/scripts/obs/nut.spec b/scripts/obs/nut.spec new file mode 100644 index 0000000000..a2b98f77d3 --- /dev/null +++ b/scripts/obs/nut.spec @@ -0,0 +1,827 @@ +# +# spec file for package nut.spec +# +# Copyright (c) 2015 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2016-2018 Eaton EEIC. +# Copyright (c) 2025 by Jim Klimov +# +# All modifications and additions to the file contributed by third parties +# remain the property of their copyright owners, unless otherwise agreed +# upon. The license for this file, and modifications and additions to the +# file, is the same license as for the pristine package itself (unless the +# license for the pristine package is not an Open Source License, in which +# case the license is the MIT License). An "Open Source License" is a +# license that conforms to the Open Source Definition (Version 1.9) +# published by the Open Source Initiative. + +# Please submit bugfixes or comments via http://bugs.opensuse.org/ +# + +# NOTE: Evaluations in percent-round-parentheses below happen in the +# build area already populated with packages according to "Required:" +# lines. We can test for OS feature availability here if needed (e.g. +# to decide if we deliver a sub-package with certain dependencies), +# but can not decide that we can/must install something and then we +# would have the needed OS capability. + +%define LIBEXECPATH %{_libexecdir}/ups + +# Requires httpd(-devel?) or apache2(-devel?) to be present in this distro: +%define apache_serverroot_data %(%{_sbindir}/apxs2 -q datadir || %{_sbindir}/apxs -q PREFIX || true) +# FIXME: is naming correct for both versions? +%define apache_serverroot_cgi %(%{_sbindir}/apxs2 -q cgidir || %{_sbindir}/apxs -q cgidir || true) + +%if "0%{?apache_serverroot_cgi}" == "0" || 0%(echo '%{apache_serverroot_cgi}' | grep -E '^%{_datadir}' >/dev/null && echo 1 || echo 0) > 0 +# Spec-var is undefined or empty, or matches the pattern triggering +# E: arch-dependent-file-in-usr-share (Badness: 590) +# Dump nut-cgi artifacts under our own locations, so end-users can +# integrate them later. +%define CGIPATH %{LIBEXECPATH}/cgi-bin +%else +%define CGIPATH %{apache_serverroot_cgi}/nut +%endif + +%if "0%{?apache_serverroot_data}" == "0" || 0%(test x'%{apache_serverroot_data}' = x'^%{_datadir}' && echo 1 || echo 0) > 0 +%define HTMLPATH %{_datadir}/nut/htdocs +%else +# Rename web pages location to not conflict with apache2-example-pages +# or user home page: +%define HTMLPATH %{apache_serverroot_data}/nut +%endif + +%define MODELPATH %{LIBEXECPATH}/driver +%define STATEPATH %{_localstatedir}/lib/ups +### Note: this is /etc/nut in Debian version +%define CONFPATH %{_sysconfdir}/ups +# RPM on OpenSUSE goes: +# DOCDIR=/home/abuild/rpmbuild/BUILD/nut-2.8.4.428-build/BUILDROOT/usr/share/doc/packages/nut +%define DOCPATH %{_docdir}/nut + +### FIXME: Detect properly? +# W: suse-filelist-forbidden-udev-userdirs /etc/udev/rules.d/62-nut-usbups.rules is not allowed in SUSE +# This directory is for user files, use /usr/lib/udev/rules.d +%define UDEVRULEPATH %(test -d /usr/lib/udev && echo /usr/lib/udev || echo "%{_sysconfdir}/udev") + +### FIXME: Detect properly? +# W: suse-filelist-forbidden-bashcomp-userdirs /etc/bash_completion.d/nut.bash_completion is not allowed in SUSE +# This directory is for user files, use /usr/share/bash-completion/completions/ +%define BASHCOMPLETIONPATH %(test -d /usr/share/bash-completion/completions && echo /usr/share/bash-completion/completions || echo "%{_sysconfdir}/bash_completion.d") + +%define NUT_USER upsd +%define NUT_GROUP daemon +%define LBRACE ( +%define RBRACE ) +%define QUOTE " +%define BACKSLASH \\ +# Collect all devices listed in ups-nut-device.fdi: +%define USBHIDDRIVERS %(zcat %{SOURCE0} | tr a-z A-Z | fgrep -a -A1 USBHID-UPS | sed -n 's/.*ATTR{IDVENDOR}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, ATTR{IDPRODUCT}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, MODE=.*/modalias%{LBRACE}usb:v%{BACKSLASH}1p%{BACKSLASH}2d*dc*dsc*dp*ic*isc*ip*%{RBRACE}/p' | tr '%{BACKSLASH}n' ' ') +%define USBNONHIDDRIVERS %(zcat %{SOURCE0} | tr a-z A-Z | fgrep -a -A1 _USB | sed -n 's/.*ATTR{IDVENDOR}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, ATTR{IDPRODUCT}==%{QUOTE}%{BACKSLASH}%{LBRACE}[^%{QUOTE}]*%{BACKSLASH}%{RBRACE}%{QUOTE}, MODE=.*/modalias%{LBRACE}usb:v%{BACKSLASH}1p%{BACKSLASH}2d*dc*dsc*dp*ic*isc*ip*%{RBRACE}/p' | tr '%{BACKSLASH}n' ' ') + +# Collect systemd related paths so we can package files there: +%define systemdsystemunitdir %(pkg-config --variable=systemdsystemunitdir systemd) +%define systemdsystempresetdir %(pkg-config --variable=systemdsystempresetdir systemd || pkg-config --variable=systemdsystempresetdir libsystemd) +%define systemdtmpfilesdir %(pkg-config --variable=tmpfilesdir systemd || pkg-config --variable=tmpfilesdir libsystemd) +%define systemdsystemdutildir %(pkg-config --variable=systemdutildir systemd) +%define systemdshutdowndir %(pkg-config --variable=systemdshutdowndir systemd) + +# % define NUT_SYSTEMD_UNITS_SERVICE_TARGET % (cd scripts/systemd && ls -1 *.{service,target,path,timer}{,.in} | sed 's,.in$,,' | sort | uniq) +%define NUT_SYSTEMD_UNITS_SERVICE_TARGET nut-driver-enumerator.service nut-driver.target nut-driver@.service nut-logger.service nut-monitor.service nut-server.service nut-sleep.service nut-udev-settle.service nut.target nut-driver-enumerator.path +# Most deployments do not want these by default: +%define NUT_SYSTEMD_UNITS_UNCOMMON_NDE nut-driver-enumerator-daemon-activator.path nut-driver-enumerator-daemon-activator.service nut-driver-enumerator-daemon.service + +%define NUT_SYSTEMD_UNITS_PRESET %(cd scripts/systemd && ls -1 *.preset{,.in} | sed 's,.in$,,' | sort | uniq) + +# Not all distros have certain packages (or their equivalents/aliases), +# NOTE: No use searching remote repos for what might be or not be available +# there; we have to use rpm queries based on whatever did get installed +# according to Requires lines below (in turn according to our declaration +# of what is shipped by this or that distro/release), to decide whether we +# deliver certain sub-packages - and set NUTPKG_WITH_ at that time. +# For version-specific checks note that some are directly digited, others +# are off by two or four digits (e.g. 0810 = a "8.10" release), see +# https://en.opensuse.org/openSUSE:Build_Service_cross_distribution_howto + +# Does this NUT branch have DMF feature code? +%define NUTPKG_WITH_DMF %( test -d scripts/DMF && echo 1 || echo 0 ) + +# FIXME: Find a smarter way to set those from main codebase recipes... +# Something like `git grep 'version-info' '*.am'` ? +%define SO_MAJOR_LIBUPSCLIENT 7 +%define SO_MAJOR_LIBNUTCLIENT 2 +%define SO_MAJOR_LIBNUTCLIENTSTUB 1 +%define SO_MAJOR_LIBNUTSCAN 4 +%define SO_MAJOR_LIBNUTCONF 0 + +# If not published, nutconf is built with a statically linked library variant +%define NUTPKG_WITH_LIBNUTCONF 0 + +Name: nut +# NOTE: OBS should rewrite this: +Version: 2.8.4 +Release: 1 +Summary: Network UPS Tools Core (Uninterruptible Power Supply Monitoring) +License: GPL-2.0+ +Group: Hardware/UPS +Url: https://www.networkupstools.org/ +Source0: %{name}-%{version}.tar.gz + +Requires: %{_bindir}/bash +Requires: %{_bindir}/sh +Requires: %{_sbindir}/sh +Requires: %{_bindir}/chown +Requires: %{_bindir}/chgrp +Requires: %{_bindir}/chmod +###BuildRequires: % {_sbindir}/chroot +Requires: %{_bindir}/rm +Requires: %{_bindir}/fgrep +Requires: %{_bindir}/grep +Requires: %{_bindir}/pgrep +Requires: %{_bindir}/pkill +Requires: %{_bindir}/readlink +Requires: usbutils +#%if 0 % {?suse_version} +Requires(post): udev +#%endif +#Requires(post): group(% {NUT_GROUP}) +#Requires(post): user(% {NUT_USER}) +Requires(postun): %{_bindir}/sh + +BuildRoot: %{_tmppath}/%{name}-%{version}-build + +Recommends: logrotate + +# To fix end-of-line encoding: +BuildRequires: dos2unix + +%if ! 0%{?rhel_version} +# For man page aliases +# https://en.opensuse.org/openSUSE:Packaging_Conventions_RPM_Macros#fdupes +BuildRequires: fdupes +%endif + +%if ( ! 0%{?rhel_version} ) && ( ! 0%{?rhel} ) +# Not sure why claimed absent in RHEL7 (even with Fedora/EPEL repo layer added) +%define NUTPKG_WITH_AVAHI 1 +BuildRequires: avahi-devel +%else +%define NUTPKG_WITH_AVAHI 0 +%endif + +%if ( ! 0%{?rhel_version} ) && ( ! 0%{?rhel} ) && ( 0%{?sle_version}>=150000 || ! 0%{?sle_version} ) && ( 0%{?suse_version}>=1300 || ! 0%{?suse_version} ) +# Not sure why claimed absent in RHEL (even with Fedora/EPEL repo layer added) +%define NUTPKG_WITH_FREEIPMI 1 +BuildRequires: (libfreeipmi-devel or freeipmi-devel) +%else +%define NUTPKG_WITH_FREEIPMI 0 +%endif + +%if ( 0%{?rhel_version}>=800 || ! 0%{?rhel_version} ) && ( 0%{?rhel}>=8 || ! 0%{?rhel} ) +# Not in RHEL7 +%define NUTPKG_WITH_LIBGD 1 +BuildRequires: gd-devel +%else +%define NUTPKG_WITH_LIBGD 0 +%endif + +BuildRequires: gcc-c++ +BuildRequires: libtool + +%if ( 0%{?rhel_version}>=800 || ! 0%{?rhel_version} ) && ( 0%{?rhel}>=8 || ! 0%{?rhel} ) +# Not in RHEL7 +%define NUTPKG_WITH_LTDL 1 +BuildRequires: libtool-ltdl-devel +%else +%define NUTPKG_WITH_LTDL 0 +%endif + +# libusb-0.1 or libusb-1.0: +BuildRequires: (libusb-devel or libusbx-devel) +#!Prefer: libusbx-devel +BuildRequires: net-snmp-devel +BuildRequires: libxml2-devel +BuildRequires: pkg-config +# Maybe older Pythons are also okay, but were not tested for ages +BuildRequires: (python >= 2.6 or python3 or python2) + +%if 0%{?NUTPKG_WITH_DMF} +# Toggle decided above based on variant of NUT source codebase +# LUA 5.1 or 5.2 is known ok for us, both are modern in current distros (201609xx) +BuildRequires: lua-devel + +# TODO: Make sure how this is named to use in CentOS/RHEL (may be not in core but EPEL repos) +# The pycparser is required to rebuild DMF files, but those pre-built +# copies in the git repo/tarball "should" be in sync with original +# C files, so we don't require regeneration for packaging. Also the +# Jenkins NUT-master job should have verified this. +#BuildRequires: python-pycparser +%endif + +# Variant names in different distros +# For some platforms we may have to fiddle with distro-named macros like +# % if 0 % {?centos_version} +# % if 0 % {?suse_version} +# % if 0 % {?rhel_version}>=700 +# and whatnot + +%if ( (0%{?rhel_version}>0 && 0%{?rhel_version}<800) || ! 0%{?rhel_version} ) && ( (0%{?centos_version}>0 && 0%{?centos_version}!=800) || ! 0%{?centos_version} ) +# Strange that this is not present in RHEL (even with Fedora EPEL repos attached) +# and that it is resolvable in CentOS 7, 9, 10 but not 8... +# We only need this to learn paths from apxs tool, so no NUTPKG_WITH_* toggle +BuildRequires: (httpd-devel or apache2-devel) +%endif + +BuildRequires: (dbus-1-glib-devel or dbus-glib-devel) + +%if ( ! 0%{?rhel_version} ) && ( ! 0%{?rhel} ) +# Strange that this is not present in RHEL (even with Fedora EPEL repos attached) +BuildRequires: (libcppunit-devel or cppunit-devel) +%endif + +# Obsoleted/away in newer distros +%if ( (0%{?rhel_version}>0 && 0%{?rhel_version}<800) || ! 0%{?rhel_version} ) && ( (0%{?centos_version}>0 && 0%{?centos_version}<800) || ! 0%{?centos_version} ) && ( (0%{?fedora_version}>0 && 0%{?fedora_version}<=27) || ! 0%{?fedora_version} ) +# Note that per https://en.opensuse.org/openSUSE:Build_Service_cross_distribution_howto +# there was "fedora_version" until some time before 36, when the macro became "fedora"; +# similarly for "rhel_version <= 700" vs. "rhel == 8"... +%define NUTPKG_WITH_TCPWRAP 1 +BuildRequires: (tcpd-devel or tcp_wrappers-devel) +%else +%define NUTPKG_WITH_TCPWRAP 0 +%endif + +# May be plain "neon" and "libusb" in RHEL7 or older? +# OBS: This may need `osc meta prjconf` to `Prefer:` one +# certain variant in case several hits happen on a builder: +BuildRequires: (libneon-devel or neon-devel or neon) +#!Prefer: (libneon-devel or neon-devel) +BuildRequires: (libopenssl-devel or openssl-devel or openssl) +#!Prefer: (libopenssl-devel or openssl-devel) + +%if ( ! 0%{?rhel_version} ) && ( ! 0%{?rhel} ) && ( (0%{?centos_version}>0 && 0%{?centos_version}<1000) || ! 0%{?centos_version} ) && ( (0%{?fedora_version}>0 && 0%{?fedora_version}<4200) || ! 0%{?fedora_version} ) && ( (0%{?fedora}>0 && 0%{?fedora}<42) || ! 0%{?fedora} ) +# Strange that this is not present in RHEL (even with Fedora EPEL repos attached) +# NOTE: fedora_version=99 seems to be rawhide; currently it says this package +# is not known (so likely a post-42 release would be more specific later), +# CentOS10 also does not like the name. +%define NUTPKG_WITH_POWERMAN 1 +BuildRequires: powerman-devel +%else +%define NUTPKG_WITH_POWERMAN 0 +%endif + +%if ( 0%{?suse_version}>0 || ! 0%{?suse_version} ) && (0%{?centos_version}>=800 || ! 0%{?centos_version} ) && ( ! 0%{?rhel_version} ) && ( ! 0%{?rhel} ) +# Strange that this is not present in RHEL (even with Fedora EPEL repos attached) +# But it also complains about epel-rpm-macros when this is added though. +BuildRequires: systemd-rpm-macros +%endif + +%if ( 0%{?rhel_version}>=800 || ! 0%{?rhel_version} ) && ( 0%{?rhel}>=8 || ! 0%{?rhel} ) +# Only needed for PDF generation, we do not package that now +#BuildRequires: dblatex + +BuildRequires: (libxslt-tools or libxslt) +BuildRequires: asciidoc +%endif + +%if 0%{?opensuse_version} +# Package provides driver for USB HID UPSes, but people can live with hal addon: +Enhances: %{USBHIDDRIVERS} +# Package provides the only avalailable driver for other USB UPSes: +Supplements: %{USBNONHIDDRIVERS} +%systemd_requires +%endif + +%description +Core package of Network UPS Tools. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%package drivers-net +Summary: Network UPS Tools - Extra Networking Drivers (for Network Monitoring) +Group: Hardware/UPS +Requires: %{name} = %{version} + +%description drivers-net +Networking drivers for the Network UPS Tools. You will need them +together with nut to provide UPS networking support. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%package -n libupsclient%{SO_MAJOR_LIBUPSCLIENT} +Summary: Network UPS Tools Library (Uninterruptible Power Supply Monitoring) +Group: System/Libraries + +%description -n libupsclient%{SO_MAJOR_LIBUPSCLIENT} +Shared library for the Network UPS Tools, used by its and third-party C clients. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%package -n libnutclient%{SO_MAJOR_LIBNUTCLIENT} +Summary: Network UPS Tools Library (Uninterruptible Power Supply Monitoring) +Group: System/Libraries + +%description -n libnutclient%{SO_MAJOR_LIBNUTCLIENT} +Shared library for the Network UPS Tools, used by its and third-party C++ clients. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%package -n libnutclientstub%{SO_MAJOR_LIBNUTCLIENTSTUB} +Summary: Network UPS Tools Library (Uninterruptible Power Supply Monitoring) +Group: System/Libraries + +%description -n libnutclientstub%{SO_MAJOR_LIBNUTCLIENTSTUB} +Shared stub library for the Network UPS Tools with memory-backed configurations, +primarily used by tests and mocks with its and third-party C++ clients. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%if 0%{?NUTPKG_WITH_LTDL} +%package -n libnutscan%{SO_MAJOR_LIBNUTSCAN} +Summary: Network UPS Tools Library (Uninterruptible Power Supply Monitoring) +Group: System/Libraries + +%description -n libnutscan%{SO_MAJOR_LIBNUTSCAN} +Shared library for the Network UPS Tools, used by its nut-scanner and nutconf tools, +and possibly third-party C clients, integrations or tools. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%if 0%{?NUTPKG_WITH_LIBNUTCONF} > 0 +# If not published, nutconf is built with a statically linked library variant +%package -n libnutconf%{SO_MAJOR_LIBNUTCONF} +Summary: Network UPS Tools Library (Uninterruptible Power Supply Monitoring) +Group: System/Libraries + +%description -n libnutconf%{SO_MAJOR_LIBNUTCONF} +Shared library for the Network UPS Tools, used by its nutconf tool, +and possibly third-party C++ clients, integrations or tools. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. +%endif +%endif + +%if 0%{?NUTPKG_WITH_LIBGD} +%package cgi +Summary: Network UPS Tools Web Server Support (UPS Status Pages) +Group: Hardware/UPS +Requires: %{name} = %{version} + +%description cgi +Web server support package for the Network UPS Tools. + +Predefined URL is http://localhost/nut/index.html + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. +%endif + +%package monitor +Summary: Network UPS Tools Web Server Support (GUI client) +Group: Hardware/UPS +Requires: %{name} = %{version} +Requires: python-base +BuildRequires: (python >= 2.6 or python3 or python2) +BuildArch: noarch + +%description monitor +Graphical user interface client for the Network UPS Tools, +written in Python. + +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%package devel +Summary: Network UPS Tools (Uninterruptible Power Supply Monitoring) +Group: Development/Libraries/C and C++ +Requires: %{name} = %{version} +Requires: openssl-devel + +%description devel +Network UPS Tools is a collection of programs which provide a common +interface for monitoring and administering UPS hardware. + +Detailed information about supported hardware can be found in +%{_docdir}/nut. + +%prep +%setup -q + +# Note: NOT configure macro, due to override of --sysconfdir and --datadir +# values just for the recipe part but not for whole specfile +%build +# May be pre-populated if building from tarball, or derived from git +# Otherwise let the SPEC version reflect in NUT self-identification +if [ ! -s VERSION_DEFAULT ] && [ ! -e .git ] && [ -n '%{version}' ] ; then + echo NUT_VERSION_DEFAULT='%{version}' > VERSION_DEFAULT +fi + +sh autogen.sh +./configure --disable-static --with-pic \ + --prefix=%{_prefix}\ + --bindir=%{_bindir}\ + --sbindir=%{_sbindir}\ + --libdir=%{_libdir}\ + --libexecdir=%{LIBEXECPATH}\ + --sysconfdir=%{CONFPATH}\ + --datadir=%{_datadir}/nut\ + --docdir=%{DOCPATH}\ + --with-ssl --with-openssl\ + --with-libltdl=yes\ + --with-cgi=auto\ + --with-serial\ + --with-usb\ + --with-snmp\ + --with-neon\ + --with-dev\ + --with-ipmi=auto\ + --with-powerman=auto\ + --with-doc=man=dist-auto\ + --with-htmlpath=%{HTMLPATH}\ + --with-cgipath=%{CGIPATH}\ + --with-statepath=%{STATEPATH}\ + --with-drvpath=%{MODELPATH}\ + --with-user=%{NUT_USER}\ + --with-group=%{NUT_GROUP} \ + --with-udev-dir=%{UDEVRULEPATH} \ + --enable-option-checking=fatal\ + --with-systemdsystemunitdir --with-systemdshutdowndir \ + --with-augeas-lenses-dir=/usr/share/augeas/lenses/dist \ +%if 0%{?NUTPKG_WITH_LIBNUTCONF} > 0 + --with-dev-libnutconf\ +%endif +%if 0%{?NUTPKG_WITH_DMF} + --with-snmp_dmf_lua\ + --with-dmfsnmp-regenerate=no --with-dmfnutscan-regenerate=no --with-dmfsnmp-validate=no --with-dmfnutscan-validate=no\ +%endif + --enable-keep_nut_report_feature\ + --enable-strip\ + --enable-check-NIT + +### via Make now ### (cd tools; python nut-snmpinfo.py) + +make %{?_smp_mflags} +PORT=$(sed -n 's/#define PORT //p' config.log) +if test "$PORT" = 3493 ; then + PORT=nut +fi + +%check +make %{?_smp_mflags} check + +%install +make DESTDIR=%{buildroot} install %{?_smp_mflags} +mkdir -p %{buildroot}%{STATEPATH}/upssched +# SuSE rc +mkdir -p %{buildroot}%{_sbindir} +mkdir -p %{buildroot}%{_sysconfdir}/logrotate.d +# Avoid W: incoherent-logrotate-file /etc/logrotate.d/nutlogd +install -m 644 scripts/logrotate/nutlogd %{buildroot}%{_sysconfdir}/logrotate.d/nut +# As (currently) hard-coded in that file above +mkdir -p %{buildroot}/var/log +rename .sample "" %{buildroot}%{_sysconfdir}/ups/*.sample +find %{buildroot} -type f -name "*.la" -delete -print +mkdir -p %{buildroot}%{BASHCOMPLETIONPATH} +install -m0644 scripts/misc/nut.bash_completion %{buildroot}%{BASHCOMPLETIONPATH}/ +install -m0755 scripts/subdriver/gen-snmp-subdriver.sh %{buildroot}%{_sbindir}/ +# TODO: Detect path from chosen interpreter or NUT build config files? +# Avoid W: non-executable-script /usr/lib/python3.6/site-packages/PyNUT.py 644 /usr/bin/python... +# Not really relevant for the module (not directly runnable, but has the shebang just in case) +chmod +x %{buildroot}/usr/lib*/python*/*-packages/*.py +if [ x"%{systemdtmpfilesdir}" != x ]; then + # Deliver these dirs by packaging: + sed 's,^\(. %{STATEPATH}\(/upssched\)*\( .*\)*\)$,#PACKAGED#\1,' -i %{buildroot}%{systemdtmpfilesdir}/nut-common-tmpfiles.conf +fi +# Use deterministic script interpreters: +find %{buildroot} -type f -name '*.sh' -o -name '*.py' -o -name '*.pl' | \ +while read F ; do + if head -1 "$F" | grep bin/env >/dev/null ; then + F_SHEBANG="`head -1 \"$F\"`" + + F_SHELL_SHORT="`echo \"$F_SHEBANG\" | sed -e 's,^.*bin/env *,,' -e 's, .*$,,'`" \ + && [ -n "$F_SHELL_SHORT" ] \ + || { echo "WARNING: Failed to extract an interpreter from shebang '${F_SHEBANG}'" >&2 ; continue ; } + + F_SHELL_PATH="`command -v \"$F_SHELL_SHORT\"`" \ + && [ -n "$F_SHELL_PATH" ] && [ -x "$F_SHELL_PATH" ] \ + || { echo "WARNING: Failed to find executable path to interpreter '${F_SHELL_SHORT}' from shebang '${F_SHEBANG}'" >&2 ; continue; } + + echo "REWRITING shebang from '$F_SHEBANG' to '#!${F_SHELL_PATH}' in '$F'" >&2 + sed '1 s,^.*$,#!'"${F_SHELL_PATH}," -i "$F" + fi +done +# create symlinks for man pages; skip man1 (not used with pkgconfig +# capable builds), and man7 (one page there): +%fdupes -s %{buildroot}/%{_mandir}/man3 +%fdupes -s %{buildroot}/%{_mandir}/man5 +%fdupes -s %{buildroot}/%{_mandir}/man8 + +%pre +usr/sbin/groupadd -r -g %{NUT_GROUP} 2>/dev/null || : +usr/sbin/useradd -r -g %{NUT_GROUP} -s /bin/false \ + -c "UPS daemon" -d /sbin %{NUT_USER} 2>/dev/null || : +%if "x%{?systemdsystemunitdir}" == "x" +%else +%service_add_pre %{NUT_SYSTEMD_UNITS_SERVICE_TARGET} %{NUT_SYSTEMD_UNITS_UNCOMMON_NDE} +%endif + +%post +# Be sure that all files are owned by a dedicated user. +# Some systems do not have the users or groups during rpmbuild +# pre/port tests, so we neuter faults with a warning echo. +# Some systems struggle with "chown USER:GROUP" so we separate +# them into two commands here: +bin/chown -R %{NUT_USER} %{STATEPATH} || echo "WARNING: Could not secure state path '%{STATEPATH}'" >&2 +bin/chgrp -R %{NUT_GROUP} %{STATEPATH} || echo "WARNING: Could not secure state path '%{STATEPATH}'" >&2 +# Be sure that all files are owned by a dedicated user. +bin/chown %{NUT_USER} %{CONFPATH}/upsd.conf %{CONFPATH}/upsmon.conf %{CONFPATH}/upsd.users || echo "WARNING: Could not secure config files in path '%{CONFPATH}'" >&2 +bin/chgrp root %{CONFPATH}/upsd.conf %{CONFPATH}/upsmon.conf %{CONFPATH}/upsd.users || echo "WARNING: Could not secure config files in path '%{CONFPATH}'" >&2 +bin/chmod 600 %{CONFPATH}/upsd.conf %{CONFPATH}/upsmon.conf %{CONFPATH}/upsd.users || echo "WARNING: Could not secure config files in path '%{CONFPATH}'" >&2 +# And finally trigger udev to set permissions according to newly installed rules files. +if [ -x /sbin/udevadm ] ; then /sbin/udevadm trigger --subsystem-match=usb --property-match=DEVTYPE=usb_device ; fi +%if "x%{?systemdtmpfilesdir}" == "x" +%else +%tmpfiles_create nut-common-tmpfiles.conf +%endif +%if "x%{?systemdsystemunitdir}" == "x" +%else +%service_add_post %{NUT_SYSTEMD_UNITS_SERVICE_TARGET} %{NUT_SYSTEMD_UNITS_UNCOMMON_NDE} +%endif + +%preun +%if "x%{?systemdsystemunitdir}" == "x" +: +%else +%service_del_preun %{NUT_SYSTEMD_UNITS_SERVICE_TARGET} %{NUT_SYSTEMD_UNITS_UNCOMMON_NDE} +%endif + +%postun +%if "x%{?systemdsystemunitdir}" == "x" +: +%else +%service_del_postun %{NUT_SYSTEMD_UNITS_SERVICE_TARGET} %{NUT_SYSTEMD_UNITS_UNCOMMON_NDE} +%endif + +%post -n libupsclient%{SO_MAJOR_LIBUPSCLIENT} -p /sbin/ldconfig + +%postun -n libupsclient%{SO_MAJOR_LIBUPSCLIENT} -p /sbin/ldconfig + +%post -n libnutclient%{SO_MAJOR_LIBNUTCLIENT} -p /sbin/ldconfig + +%postun -n libnutclient%{SO_MAJOR_LIBNUTCLIENT} -p /sbin/ldconfig + +%post -n libnutclientstub%{SO_MAJOR_LIBNUTCLIENTSTUB} -p /sbin/ldconfig + +%postun -n libnutclientstub%{SO_MAJOR_LIBNUTCLIENTSTUB} -p /sbin/ldconfig + +%if 0%{?NUTPKG_WITH_LTDL} > 0 +%post -n libnutscan%{SO_MAJOR_LIBNUTSCAN} -p /sbin/ldconfig + +%postun -n libnutscan%{SO_MAJOR_LIBNUTSCAN} -p /sbin/ldconfig + +%if 0%{?NUTPKG_WITH_LIBNUTCONF} > 0 +%post -n libnutconf%{SO_MAJOR_LIBNUTCONF} -p /sbin/ldconfig + +%postun -n libnutconf%{SO_MAJOR_LIBNUTCONF} -p /sbin/ldconfig +%endif +%endif + +%files +%defattr(-,root,root) +%doc AUTHORS COPYING LICENSE-DCO LICENSE-GPL2 LICENSE-GPL3 ChangeLog MAINTAINERS NEWS.adoc README.adoc UPGRADING.adoc docs/*.adoc docs/*.txt docs/cables/*.txt +# List the (system) dirs we impact but do not own for the package +#% dir % {BASHCOMPLETIONPATH} +#% dir % {_sysconfdir}/logrotate.d +#% dir % {_bindir} +#% dir % {_sbindir} +#% dir % {_datadir} +#% dir % {_docdir} +#% dir % {DOCPATH} +# NOTE: Currently this only delivers libupsclient-config.1 +# and only if not building with pkg-config available: +#% dir % {_mandir}/man1 +#% dir % {_mandir}/man3 +#% dir % {_mandir}/man5 +#% dir % {_mandir}/man7 +#% dir % {_mandir}/man8 +#% dir % {_libexecdir} +# FIXME: Detect from logrotate properties (or our scriptlet file)? +#% dir /var/log +%if "x%{?systemdsystemunitdir}" == "x" +%else +%dir %{systemdsystemunitdir} +%endif +%if "x%{?systemdsystempresetdir}" == "x" +%else +%dir %{systemdsystempresetdir} +%endif +%if "x%{?systemdtmpfilesdir}" == "x" +%else +%dir %{systemdtmpfilesdir} +%endif +%if "x%{?systemdshutdowndir}" == "x" +%else +%dir %{systemdshutdowndir} +%endif +# List the file patterns to install from proto area +%{BASHCOMPLETIONPATH}/* +%config(noreplace) %{_sysconfdir}/logrotate.d/* +%{_bindir}/* +%if 0%{?NUTPKG_WITH_DMF} +%exclude %{_bindir}/nut-scanner-reindex-dmfsnmp +%endif +%{_datadir}/nut +%if 0%{?NUTPKG_WITH_DMF} +%exclude %{_datadir}/nut/dmfnutscan +%exclude %{_datadir}/nut/dmfsnmp +%exclude %{_datadir}/nut/dmfnutscan.d +%exclude %{_datadir}/nut/dmfsnmp.d +%endif +%{_mandir}/man5/*.* +%{_mandir}/man7/*.* +%{_mandir}/man8/*.* +%exclude %{_mandir}/man8/netxml-ups*.* +%exclude %{_mandir}/man8/snmp-ups*.* +%dir %{LIBEXECPATH} +%{_sbindir}/* +%dir %{UDEVRULEPATH} +%dir %{UDEVRULEPATH}/rules.d +### FIXME: if under /etc ### % config(noreplace) % {UDEVRULEPATH}/rules.d/*.rules +%{UDEVRULEPATH}/rules.d/*.rules +%config(noreplace) %{CONFPATH}/hosts.conf +%config(noreplace) %attr(600,%{NUT_USER},root) %{CONFPATH}/upsd.conf +%config(noreplace) %attr(600,%{NUT_USER},root) %{CONFPATH}/upsd.users +%config(noreplace) %attr(600,%{NUT_USER},root) %{CONFPATH}/upsmon.conf +%dir %{CONFPATH} +%config(noreplace) %{CONFPATH}/nut.conf +%config(noreplace) %{CONFPATH}/ups.conf +%config(noreplace) %{CONFPATH}/upsset.conf +%config(noreplace) %{CONFPATH}/upssched.conf +%dir %{MODELPATH} +%{MODELPATH}/* +%exclude %{MODELPATH}/snmp-ups +%exclude %{MODELPATH}/netxml-ups +%exclude %{_sbindir}/gen-snmp-subdriver.sh +%attr(770,%{NUT_USER},%{NUT_GROUP}) %{STATEPATH} +%attr(770,%{NUT_USER},%{NUT_GROUP}) %{STATEPATH}/upssched +%if "x%{?systemdsystemunitdir}" == "x" +%else +%{systemdsystemunitdir}/* +%endif +%if "x%{?systemdsystempresetdir}" == "x" +%else +%{systemdsystempresetdir}/* +%endif +%if "x%{?systemdtmpfilesdir}" == "x" +%else +%{systemdtmpfilesdir}/* +%endif +%if "x%{?systemdshutdowndir}" == "x" +%else +%{systemdshutdowndir}/nutshutdown +%endif +%{_datadir}/augeas/lenses/dist/nuthostsconf.aug +%{_datadir}/augeas/lenses/dist/nutnutconf.aug +%{_datadir}/augeas/lenses/dist/nutupsconf.aug +%{_datadir}/augeas/lenses/dist/nutupsdconf.aug +%{_datadir}/augeas/lenses/dist/nutupsdusers.aug +%{_datadir}/augeas/lenses/dist/nutupsmonconf.aug +%{_datadir}/augeas/lenses/dist/nutupsschedconf.aug +%{_datadir}/augeas/lenses/dist/nutupssetconf.aug +%{_datadir}/augeas/lenses/dist/tests/test_nut.aug +%dir %{_datadir}/augeas +%dir %{_datadir}/augeas/lenses +%dir %{_datadir}/augeas/lenses/dist +%dir %{_datadir}/augeas/lenses/dist/tests +%{LIBEXECPATH}/nut-driver-enumerator.sh +# Exclude whatever other packages bring, some rpmbuild versions seem to dump +# everything into the base package and then complain about duplicates/conflicts: +### libupsclient7 etc +%exclude %{_libdir}/*.so.* +### nut-cgi +%exclude %{CGIPATH} +%exclude %{HTMLPATH} +%exclude %{CONFPATH}/*.html +### nut-monitor +# TODO: Actually package NUT-Monitor app and scripts where available +# TODO: Detect path from chosen interpreter or NUT build config files? +%exclude /usr/lib*/python*/*-packages/* +### nut-devel +%exclude %{_includedir}/*.h +%exclude %{_libdir}/*.so +%exclude %{_libdir}/pkgconfig/*.pc +%exclude %{_mandir}/man3/*.* +%exclude %{LIBEXECPATH}/sockdebug + + +%files drivers-net +%defattr(-,root,root) +%{MODELPATH}/snmp-ups +%{MODELPATH}/netxml-ups +%{_mandir}/man8/netxml-ups*.* +%{_mandir}/man8/snmp-ups*.* +%{_sbindir}/gen-snmp-subdriver.sh +%if 0%{?NUTPKG_WITH_DMF} +%{_bindir}/nut-scanner-reindex-dmfsnmp +%dir %{_datadir}/nut/dmfnutscan +%dir %{_datadir}/nut/dmfsnmp +%{_datadir}/nut/dmfnutscan/*.dmf +%{_datadir}/nut/dmfsnmp/*.dmf +%{_datadir}/nut/dmfnutscan/*.xsd +%{_datadir}/nut/dmfsnmp/*.xsd +%dir %{_datadir}/nut/dmfnutscan.d +%dir %{_datadir}/nut/dmfsnmp.d +%{_datadir}/nut/dmfnutscan.d/*.dmf +%{_datadir}/nut/dmfsnmp.d/*.dmf +%endif + +%files -n libupsclient%{SO_MAJOR_LIBUPSCLIENT} +%defattr(-,root,root) +%{_libdir}/libupsclient.so.* + +%files -n libnutclient%{SO_MAJOR_LIBNUTCLIENT} +%defattr(-,root,root) +%{_libdir}/libnutclient.so.* + +%files -n libnutclientstub%{SO_MAJOR_LIBNUTCLIENTSTUB} +%defattr(-,root,root) +%{_libdir}/libnutclientstub.so.* + +%if 0%{?NUTPKG_WITH_LTDL} +%files -n libnutscan%{SO_MAJOR_LIBNUTSCAN} +%defattr(-,root,root) +%{_libdir}/libnutscan.so.* + +%if 0%{?NUTPKG_WITH_LIBNUTCONF} > 0 +%files -n libnutconf%{SO_MAJOR_LIBNUTCONF} +%defattr(-,root,root) +%{_libdir}/libnutconf.so.* +%endif +%endif + +%if 0%{?NUTPKG_WITH_LIBGD} +%files cgi +%defattr(-,root,root) +%dir %{CGIPATH} +%dir %{HTMLPATH} +%{CGIPATH}/* +%{HTMLPATH}/* +%config(noreplace) %{CONFPATH}/upsstats-single.html +%config(noreplace) %{CONFPATH}/upsstats.html +%endif + +%files monitor +%defattr(-,root,root) +# TODO: Actually package NUT-Monitor app and scripts where available +# TODO: Detect path from chosen interpreter or NUT build config files? +# Maybe specify python version and location using RPM macros, see e.g. +# https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/installing_and_using_dynamic_programming_languages/assembly_packaging-python-3-rpms_installing-and-using-dynamic-programming-languages +# Note we MAY be dual-packaging for several python versions and locations. +/usr/lib*/python*/*-packages/* + +%files devel +%defattr(-,root,root) +%{_includedir}/*.h +%{_libdir}/*.so +%{_libdir}/pkgconfig/*.pc +# FIXME: Alias man page files... use symlinks or what? +# [ 139s] nut-devel.x86_64: W: files-duplicate /usr/share/man/man3/nutclient_execute_device_command.3.gz /usr/share/man/man3/nutclient_get_device_command_description.3.gz:/usr/share/man/man3/nutclient_get_device_commands.3.gz:/usr/share/man/man3/nutclient_has_device_command.3.gz +# [ 139s] nut-devel.x86_64: W: files-duplicate /usr/share/man/man3/nutclient_get_device_num_logins.3.gz /usr/share/man/man3/nutclient_authenticate.3.gz:/usr/share/man/man3/nutclient_device_forced_shutdown.3.gz:/usr/share/man/man3/nutclient_device_login.3.gz:/usr/share/man/man3/nutclient_device_master.3.gz:/usr/share/man/man3/nutclient_logout.3.gz +# [ 139s] nut-devel.x86_64: W: files-duplicate /usr/share/man/man3/nutclient_get_devices.3.gz /usr/share/man/man3/nutclient_get_device_description.3.gz:/usr/share/man/man3/nutclient_has_device.3.gz +# [ 139s] nut-devel.x86_64: W: files-duplicate /usr/share/man/man3/nutclient_set_device_variable_value.3.gz /usr/share/man/man3/nutclient_get_device_rw_variables.3.gz:/usr/share/man/man3/nutclient_get_device_variable_description.3.gz:/usr/share/man/man3/nutclient_get_device_variable_values.3.gz:/usr/share/man/man3/nutclient_get_device_variables.3.gz:/usr/share/man/man3/nutclient_has_device_variable.3.gz:/usr/share/man/man3/nutclient_set_device_variable_values.3.gz +# [ 139s] nut-devel.x86_64: W: files-duplicate /usr/share/man/man3/nutclient_tcp_get_timeout.3.gz /usr/share/man/man3/nutclient_tcp_create_client.3.gz:/usr/share/man/man3/nutclient_tcp_disconnect.3.gz:/usr/share/man/man3/nutclient_tcp_is_connected.3.gz:/usr/share/man/man3/nutclient_tcp_reconnect.3.gz:/usr/share/man/man3/nutclient_tcp_set_timeout.3.gz +%{_mandir}/man3/*.* +%{LIBEXECPATH}/sockdebug + +%changelog diff --git a/obs/pkgsrc.obs b/scripts/obs/pkgsrc.obs similarity index 100% rename from obs/pkgsrc.obs rename to scripts/obs/pkgsrc.obs diff --git a/scripts/python/Makefile.am b/scripts/python/Makefile.am index 9036904ec3..e70abffd34 100644 --- a/scripts/python/Makefile.am +++ b/scripts/python/Makefile.am @@ -97,14 +97,24 @@ NUT_MONITOR_COMMON_TEMPLATE = \ app/locale/it/it.po \ app/locale/ru/ru.po +# Platforms vary with tooling abilitites... +DEF_TOUPPER = { TOUPPER="cat" ; \ +for TR_VARIANT in "tr 'a-z' 'A-Z'" "tr '[:lower:]' '[:upper:]'" "tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" ; do \ + if [ x"`echo c | $${TR_VARIANT}`" = xC ] ; then \ + TOUPPER="$${TR_VARIANT}" ; \ + break ; \ + fi ; \ +done ; \ +} # Craft locale subdirs with "xx_YY.UTF-8" patterned names, # and similarly alias the "*.po" files inside, as needed # by some platforms but not others to find these files. install-data-hook-app-locale-symlinks: - @cd "$(DESTDIR)$(nutmonitordir)/app/locale" && \ + @cd "$(DESTDIR)$(nutmonitordir)/app/locale" || exit ; \ + $(DEF_TOUPPER) ; \ for L in fr it ru ; do \ - UTF8_NAME="$$L"_"`echo "$$L" | tr '[a-z]' '[A-Z]'`".UTF-8 || exit ; \ + UTF8_NAME="$$L"_"`echo \"$$L\" | $${TOUPPER}`".UTF-8 || exit ; \ UTF8_POFILE="$${L}/$${UTF8_NAME}.po" || exit ; \ UTF8_SUBDIR="$${UTF8_NAME}" || exit ; \ rm -f "$${UTF8_POFILE}" || true ; rm -f "$${UTF8_SUBDIR}" || true ; \ @@ -114,9 +124,10 @@ install-data-hook-app-locale-symlinks: uninstall-hook-app-locale-symlinks: @RES=0 ; \ - cd "$(DESTDIR)$(nutmonitordir)/app/locale" && \ + cd "$(DESTDIR)$(nutmonitordir)/app/locale" || exit ; \ + $(DEF_TOUPPER) ; \ for L in fr it ru ; do \ - UTF8_NAME="$$L"_"`echo "$$L" | tr '[a-z]' '[A-Z]'`".UTF-8 || exit ; \ + UTF8_NAME="$$L"_"`echo \"$$L\" | $${TOUPPER}`".UTF-8 || exit ; \ UTF8_POFILE="$${L}/$${UTF8_NAME}.po" || exit ; \ UTF8_SUBDIR="$${UTF8_NAME}" || exit ; \ rm -f "$${UTF8_POFILE}" || RES=$$? ; \ @@ -178,7 +189,7 @@ if HAVE_MSGFMT # https://github.com/sphinx-doc/sphinx/issues/3443 # Note that OUTFILE may be in builddir (not necessarily same as srcdir) ACT_MSGFMT = { \ - $(GREP) -v -E '^.?POT-Creation-Date: ' < "$${SRCFILE}" > "$${OUTFILE}.tmpsrc" && \ + $(EGREP) -v '^.?POT-Creation-Date: ' < "$${SRCFILE}" > "$${OUTFILE}.tmpsrc" && \ $(MSGFMT) -o "$${OUTFILE}" "$${OUTFILE}.tmpsrc" && \ rm -f "$${OUTFILE}.tmpsrc" ; \ } @@ -186,17 +197,17 @@ ACT_MSGFMT = { \ app/locale/fr/LC_MESSAGES/NUT-Monitor.mo: $(abs_builddir)/app/locale/fr/LC_MESSAGES/NUT-Monitor.mo $(abs_builddir)/app/locale/fr/LC_MESSAGES/NUT-Monitor.mo: app/locale/fr/fr.po @$(MKDIR_P) "$(builddir)/app/locale/fr/LC_MESSAGES" - SRCFILE="$?"; OUTFILE="$@"; $(ACT_MSGFMT) + SRCFILE='$?'; OUTFILE='$@'; $(ACT_MSGFMT) app/locale/it/LC_MESSAGES/NUT-Monitor.mo: $(abs_builddir)/app/locale/it/LC_MESSAGES/NUT-Monitor.mo $(abs_builddir)/app/locale/it/LC_MESSAGES/NUT-Monitor.mo: app/locale/it/it.po @$(MKDIR_P) "$(builddir)/app/locale/it/LC_MESSAGES" - SRCFILE="$?"; OUTFILE="$@"; $(ACT_MSGFMT) + SRCFILE='$?'; OUTFILE='$@'; $(ACT_MSGFMT) app/locale/ru/LC_MESSAGES/NUT-Monitor.mo: $(abs_builddir)/app/locale/ru/LC_MESSAGES/NUT-Monitor.mo $(abs_builddir)/app/locale/ru/LC_MESSAGES/NUT-Monitor.mo: app/locale/ru/ru.po @$(MKDIR_P) "$(builddir)/app/locale/ru/LC_MESSAGES" - SRCFILE="$?"; OUTFILE="$@"; $(ACT_MSGFMT) + SRCFILE='$?'; OUTFILE='$@'; $(ACT_MSGFMT) endif HAVE_MSGFMT if WITH_NUT_MONITOR_PY2GTK2 @@ -228,8 +239,8 @@ sysbin_SCRIPTS = NUT-Monitor # Dummy redirector for /usr/bin/... presence NUT-Monitor: Makefile - @echo '#!/bin/sh' > "$@" - @echo 'exec "$(nutmonitordir)/app/NUT-Monitor" "$$@"' >> "$@" + @echo '#!/bin/sh' > '$@' + @echo 'exec "$(nutmonitordir)/app/NUT-Monitor" "$$@"' >> '$@' endif WITH_NUT_MONITOR @@ -243,17 +254,21 @@ endif if WITH_PYNUT_PY2 +if !PYTHON_SITE_PACKAGES_IS_PY2 pynut_py2_sitedir = $(PYTHON2_SITE_PACKAGES) pynut_py2_site_DATA = $(PYNUT_GENERATED_NOEXEC) pynut_py2_site_SCRIPTS = $(PYNUT_GENERATED_SCRIPT) $(PYNUT_COMMON_CODE) -endif +endif !PYTHON_SITE_PACKAGES_IS_PY2 +endif WITH_PYNUT_PY2 if WITH_PYNUT_PY3 +if !PYTHON_SITE_PACKAGES_IS_PY3 pynut_py3_sitedir = $(PYTHON3_SITE_PACKAGES) pynut_py3_site_DATA = $(PYNUT_GENERATED_NOEXEC) pynut_py3_site_SCRIPTS = $(PYNUT_GENERATED_SCRIPT) $(PYNUT_COMMON_CODE) -endif +endif !PYTHON_SITE_PACKAGES_IS_PY3 +endif WITH_PYNUT_PY3 ################################################################# @@ -267,17 +282,17 @@ SPELLCHECK_SRC = \ # paths when parsing the other makefile (e.g. MKDIR_P that may be defined # 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)" $@ +# +$(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)" $@ # 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 $(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)" $@ + +$(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)" $@ .in.in-spellchecked: - +$(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)" $@ + +$(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)" $@ spellcheck spellcheck-interactive spellcheck-sortdict: +$(MAKE) -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="$(SPELLCHECK_SRC)" SPELLCHECK_SRCDIR="$(srcdir)" SPELLCHECK_BUILDDIR="$(builddir)" $@ diff --git a/scripts/python/app/NUT-Monitor b/scripts/python/app/NUT-Monitor index f230a75a3a..380f9694cf 100755 --- a/scripts/python/app/NUT-Monitor +++ b/scripts/python/app/NUT-Monitor @@ -20,10 +20,10 @@ case "${PREFER_PY2-}" in esac # Detect which variant of NUT-Monitor we can run on the local system: -[ -s "$0"-py2gtk2 -a -x "$0"-py2gtk2 ] && PYTHON_PY2GTK2_SHEBANG="`head -1 "$0"-py2gtk2 | sed 's,^#!,,'`" || PYTHON_PY2GTK2_SHEBANG="" -[ -s "$0"-py3qt5 -a -x "$0"-py3qt5 ] && PYTHON_PY3QT5_SHEBANG="`head -1 "$0"-py3qt5 | sed 's,^#!,,'`" || PYTHON_PY3QT5_SHEBANG="" -[ -s "$0"-py3qt6 -a -x "$0"-py3qt6 ] && PYTHON_PY3QT6_SHEBANG="`head -1 "$0"-py3qt6 | sed 's,^#!,,'`" || PYTHON_PY3QT6_SHEBANG="" -SCRIPTDIR="`dirname "$0"`" && SCRIPTDIR="`cd "$SCRIPTDIR" && pwd`" || SCRIPTDIR="./" +[ -s "$0"-py2gtk2 -a -x "$0"-py2gtk2 ] && PYTHON_PY2GTK2_SHEBANG="`head -1 \"$0\"-py2gtk2 | sed 's,^#!,,'`" || PYTHON_PY2GTK2_SHEBANG="" +[ -s "$0"-py3qt5 -a -x "$0"-py3qt5 ] && PYTHON_PY3QT5_SHEBANG="`head -1 \"$0\"-py3qt5 | sed 's,^#!,,'`" || PYTHON_PY3QT5_SHEBANG="" +[ -s "$0"-py3qt6 -a -x "$0"-py3qt6 ] && PYTHON_PY3QT6_SHEBANG="`head -1 \"$0\"-py3qt6 | sed 's,^#!,,'`" || PYTHON_PY3QT6_SHEBANG="" +SCRIPTDIR="`dirname \"$0\"`" && SCRIPTDIR="`cd \"$SCRIPTDIR\" && pwd`" || SCRIPTDIR="./" PYTHON_PY2GTK2="" for P in "$PYTHON2" "$PYTHON_PY2GTK2_SHEBANG" "python2" "python" ; do @@ -65,11 +65,13 @@ for P in "$PYTHON_PY2GTK2" "$PYTHON_PY3QT5" "$PYTHON_PY3QT6" ; do [ -n "$P" ] || continue # If running from source tree... - if ! $P -c "import PyNUT" >/dev/null 2>/dev/null \ - && PYTHONPATH="${SCRIPTDIR}/../module" $P -c "import PyNUT" >/dev/null 2>/dev/null \ - ; then - PYTHONPATH="${SCRIPTDIR}/../module" - export PYTHONPATH + if $P -c "import PyNUT" >/dev/null 2>/dev/null ; then + true + else + if PYTHONPATH="${SCRIPTDIR}/../module" $P -c "import PyNUT" >/dev/null 2>/dev/null ; then + PYTHONPATH="${SCRIPTDIR}/../module" + export PYTHONPATH + fi fi done diff --git a/scripts/python/app/NUT-Monitor-py2gtk2.in b/scripts/python/app/NUT-Monitor-py2gtk2.in index 45359b8dc0..3e0c94f314 100755 --- a/scripts/python/app/NUT-Monitor-py2gtk2.in +++ b/scripts/python/app/NUT-Monitor-py2gtk2.in @@ -69,9 +69,26 @@ except Exception as ignored: # ModuleNotFoundError, path/parsing, et al # Try our chances with the default path: sys.path = sys_path_orig - ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) - import PyNUT + try: + # This path is for "default python interpreter", possibly customized + # and MIGHT be dist-packages or site-packages for a different python + # version. Keep it at lowest priority! + mod_path = "%s" % "@PYTHON_SITE_PACKAGES@" + if mod_path != "" and os.path.isdir(mod_path) and mod_path not in sys.path: + sys.path.append(mod_path) + + mod_path = "%s" % "@PYTHON2_SITE_PACKAGES@" + if mod_path != "" and os.path.isdir(mod_path) and mod_path not in sys.path: + sys.path.insert(1, mod_path) + + ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) + import PyNUT + + except Exception as ignored: + sys.path = sys_path_orig + ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) + import PyNUT # We would seek locale files relative to script dir os.chdir(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))) @@ -844,6 +861,7 @@ class gui_updater( threading.Thread ) : __parent_class = None __stop_thread = False + was_online = True def __init__( self, parent_class ) : threading.Thread.__init__( self ) @@ -852,7 +870,6 @@ class gui_updater( threading.Thread ) : def run( self ) : ups = self.__parent_class._interface__current_ups - was_online = True # Define a dict containing different UPS status status_mapper = { "LB" : "%s" % _("Low batteries"), @@ -881,16 +898,16 @@ class gui_updater( threading.Thread ) : if ( vars.get("ups.status").find("OL") != -1 ) : text_right += "%s" % _("Online") - if not was_online : + if not self.was_online : self.__parent_class.change_status_icon( "on_line", blink=False ) - was_online = True + self.was_online = True if ( vars.get("ups.status").find("OB") != -1 ) : text_right += "%s" % _("On batteries") - if was_online : + if self.was_online : self.__parent_class.change_status_icon( "on_battery", blink=True ) self.__parent_class.gui_status_notification( _("Device is running on batteries"), "on_battery.png" ) - was_online = False + self.was_online = False # Check for additionnal information for k,v in status_mapper.iteritems() : diff --git a/scripts/python/app/NUT-Monitor-py3qt5.in b/scripts/python/app/NUT-Monitor-py3qt5.in index dc92ad6aaa..7206d15b19 100755 --- a/scripts/python/app/NUT-Monitor-py3qt5.in +++ b/scripts/python/app/NUT-Monitor-py3qt5.in @@ -75,8 +75,26 @@ except Exception as ignored: # ModuleNotFoundError, path/parsing, et al # Try our chances with the default path: sys.path = sys_path_orig - ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) - import PyNUT + + try: + # This path is for "default python interpreter", possibly customized + # and MIGHT be dist-packages or site-packages for a different python + # version. Keep it at lowest priority! + mod_path = "%s" % "@PYTHON_SITE_PACKAGES@" + if mod_path != "" and os.path.isdir(mod_path) and mod_path not in sys.path: + sys.path.append(mod_path) + + mod_path = "%s" % "@PYTHON2_SITE_PACKAGES@" + if mod_path != "" and os.path.isdir(mod_path) and mod_path not in sys.path: + sys.path.insert(1, mod_path) + + ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) + import PyNUT + + except Exception as ignored: + sys.path = sys_path_orig + ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) + import PyNUT # We would seek locale files relative to script dir os.chdir(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))) @@ -866,6 +884,7 @@ class gui_updater : __parent_class = None __stop_thread = False + was_online = True def __init__( self, parent_class ) : threading.Thread.__init__( self ) @@ -879,7 +898,6 @@ class gui_updater : def __update( self ) : ups = self.__parent_class._interface__current_ups - was_online = True # Define a dict containing different UPS status status_mapper = { b"LB" : "%s" % _("Low batteries"), @@ -908,16 +926,16 @@ class gui_updater : if ( vars.get(b"ups.status").find(b"OL") != -1 ) : text_right += "%s" % _("Online") - if not was_online : + if not self.was_online : self.__parent_class.change_status_icon( "on_line", blink=False ) - was_online = True + self.was_online = True if ( vars.get(b"ups.status").find(b"OB") != -1 ) : text_right += "%s" % _("On batteries") - if was_online : + if self.was_online : self.__parent_class.change_status_icon( "on_battery", blink=True ) self.__parent_class.gui_status_notification( _("Device is running on batteries"), "on_battery.png" ) - was_online = False + self.was_online = False # Check for additionnal information for k,v in status_mapper.items() : diff --git a/scripts/python/app/NUT-Monitor-py3qt6.in b/scripts/python/app/NUT-Monitor-py3qt6.in index 3c81258020..40198ce79e 100755 --- a/scripts/python/app/NUT-Monitor-py3qt6.in +++ b/scripts/python/app/NUT-Monitor-py3qt6.in @@ -78,8 +78,26 @@ except Exception as ignored: # ModuleNotFoundError, path/parsing, et al # Try our chances with the default path: sys.path = sys_path_orig - ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) - import PyNUT + + try: + # This path is for "default python interpreter", possibly customized + # and MIGHT be dist-packages or site-packages for a different python + # version. Keep it at lowest priority! + mod_path = "%s" % "@PYTHON_SITE_PACKAGES@" + if mod_path != "" and os.path.isdir(mod_path) and mod_path not in sys.path: + sys.path.append(mod_path) + + mod_path = "%s" % "@PYTHON3_SITE_PACKAGES@" + if mod_path != "" and os.path.isdir(mod_path) and mod_path not in sys.path: + sys.path.insert(1, mod_path) + + ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) + import PyNUT + + except Exception as ignored: + sys.path = sys_path_orig + ### sys.stderr.write("[D2] sys.path: %s\n" % sys.path) + import PyNUT # We would seek locale files relative to script dir os.chdir(os.path.dirname(os.path.abspath(os.path.realpath(__file__)))) diff --git a/scripts/python/module/Makefile.am b/scripts/python/module/Makefile.am index 721f919d95..b8f1553dc9 100644 --- a/scripts/python/module/Makefile.am +++ b/scripts/python/module/Makefile.am @@ -23,7 +23,7 @@ tox: dist .pypi-tools-tox EXTRA_DIST = tox.ini MANIFEST.in NUT_SOURCE_GITREV_NUMERIC = @NUT_SOURCE_GITREV_NUMERIC@ -PYTHON = @PYTHON@ +PYTHON_DEFAULT = @PYTHON_DEFAULT@ GENERATED_DIST = dist build *.egg-info GENERATED_SRC = PyNUTClient README.txt LICENSE-GPL3 @@ -62,7 +62,7 @@ PyNUTClient: .pypi-src # Tagged releases should only have three blocks of digits separated by dots upload publish: +@echo " PYPI Checking upload type for module version '$(NUT_SOURCE_GITREV_NUMERIC)'" ; \ - case x"`echo "$(NUT_SOURCE_GITREV_NUMERIC)" | tr -d '[0-9]'`" in \ + case x"`echo '$(NUT_SOURCE_GITREV_NUMERIC)' | tr -d '[0-9]'`" in \ x..) echo " PYPI ...release"; $(MAKE) $(AM_MAKEFLAGS) upload-pypi ;; \ x*) echo " PYPI ...testing"; $(MAKE) $(AM_MAKEFLAGS) upload-testpypi ;; \ esac @@ -70,12 +70,12 @@ upload publish: # README.txt is also a part of module standard expectations .pypi-src: test_nutclient.py.in PyNUT.py.in setup.py.in README.adoc Makefile $(top_srcdir)/LICENSE-GPL3 @echo " PYPI Generate PyPI module source" - @rm -rf $(GENERATED_SRC) "$@" + @rm -rf $(GENERATED_SRC) '$@' @mkdir -p PyNUTClient - @for S in "$(srcdir)"/*.py.in ; do \ - B="`basename "$${S}" .in`" ; \ + @for S in '$(srcdir)'/*.py.in ; do \ + B="`basename \"$${S}\" .in`" ; \ if test x"$${B}" = xsetup.py ; then \ - if ! test -s "$${B}" ; then \ + if test ! -s "$${B}" ; then \ sed -e "s/[@]NUT_SOURCE_GITREV_NUMERIC[@]/$(NUT_SOURCE_GITREV_NUMERIC)/" < "$(srcdir)/$${B}.in" > "$${B}" || exit ; \ fi ; \ continue; \ @@ -84,72 +84,72 @@ upload publish: cp -pf "$${B}" PyNUTClient/ || exit ; \ continue; \ fi ; \ - sed -e "s,[@]PYTHON[@],@PYTHON@," < "$(srcdir)/$${B}.in" > "PyNUTClient/$${B}" || exit ; \ + sed -e "s,[@]PYTHON_DEFAULT[@],@PYTHON_DEFAULT@," < "$(srcdir)/$${B}.in" > "PyNUTClient/$${B}" || exit ; \ if test -x "$(srcdir)/$${B}.in" ; then chmod +x "PyNUTClient/$${B}"; fi ; \ done ; \ cp -pf "$(srcdir)/README.adoc" README.txt || exit ; \ cp -pf "$(top_srcdir)/LICENSE-GPL3" . || exit ; \ echo "from . import PyNUT" > PyNUTClient/__init__.py || exit - @touch "$@" + @touch '$@' .pypi-tools-python: - @echo " PYPI Checking that PYTHON variable is defined and usable: $(PYTHON)" - @test -n "$(PYTHON)" && command -v $(PYTHON) - @touch "$@" + @echo " PYPI Checking that PYTHON_DEFAULT variable is defined and usable: $(PYTHON_DEFAULT)" + @test -n "$(PYTHON_DEFAULT)" && command -v $(PYTHON_DEFAULT) + @touch '$@' .pypi-tools-dist-wheel: .pypi-tools-python @echo " PYPI Prepare PyPI tools to generate a distribution (wheel)" - @$(PYTHON) -m pip install wheel - @touch "$@" + @$(PYTHON_DEFAULT) -m pip install wheel + @touch '$@' .pypi-tools-dist-build: .pypi-tools-python @echo " PYPI Prepare PyPI tools to generate a distribution (build)" - @$(PYTHON) -m pip install build - @touch "$@" + @$(PYTHON_DEFAULT) -m pip install build + @touch '$@' .pypi-tools-tox: .pypi-tools-python @echo " PYPI Prepare Python multi-environment testing tools (tox)" - @$(PYTHON) -m pip install tox - @touch "$@" + @$(PYTHON_DEFAULT) -m pip install tox + @touch '$@' # Install via OS packaging or pip? # https://twine.readthedocs.io/en/stable/ .pypi-tools-upload: .pypi-tools-python @echo " PYPI Prepare PyPI tools and resources to upload a distribution (twine)" - @$(PYTHON) -m pip install twine + @$(PYTHON_DEFAULT) -m pip install twine @command -v twine @test -s $(HOME)/.pypirc - @touch "$@" + @touch '$@' # Using pypa/setuptools .pypi-dist: .pypi-src +@$(MAKE) $(AM_MAKEFLAGS) .pypi-dist-pip-build || \ $(MAKE) $(AM_MAKEFLAGS) .pypi-dist-obsolete || \ $(MAKE) $(AM_MAKEFLAGS) .pypi-dist-pip-wheel - @touch "$@" + @touch '$@' # The most modern approach as of 2023 .pypi-dist-pip-build: .pypi-tools-dist-build .pypi-src @echo " PYPI Generate PyPI module distribution (using 'build' module)" - @rm -rf $(GENERATED_DIST) "$@" - @$(PYTHON) -m build --skip-dependency-check --no-isolation - @touch "$@" + @rm -rf $(GENERATED_DIST) '$@' + @$(PYTHON_DEFAULT) -m build --skip-dependency-check --no-isolation + @touch '$@' # Using "setup.py" directly causes warnings about its deprecation # While "pip" distribution generator also uses it internally, # it "knows what it's doing" and goes quietly about its work :) -# Does not support "sdis" though. Oh well. +# Does not support "sdist" though. Oh well. .pypi-dist-pip-wheel: .pypi-tools-dist-wheel .pypi-src - @echo " PYPI Generate PyPI module distribution (using 'pip wheel')" - @rm -rf $(GENERATED_DIST) "$@" - @$(PYTHON) -m pip wheel --no-deps -w dist . - @touch "$@" + @echo " PYPI Generate PyPI module distribution (using 'pip wheel'; note no 'sdist' here)" + @rm -rf $(GENERATED_DIST) '$@' + @$(PYTHON_DEFAULT) -m pip wheel --no-deps -w dist . + @touch '$@' .pypi-dist-obsolete: .pypi-tools-dist-wheel .pypi-src @echo " PYPI Generate PyPI module distribution (using setup.py directly)" - @rm -rf $(GENERATED_DIST) "$@" - @$(PYTHON) setup.py sdist bdist_wheel - @touch "$@" + @rm -rf $(GENERATED_DIST) '$@' + @$(PYTHON_DEFAULT) setup.py sdist bdist_wheel + @touch '$@' # These need $HOME/.pypirc prepared with credentials (API tokens) from # https://pypi.org/manage/account/ and https://test.pypi.org/manage/account/ diff --git a/scripts/python/module/PyNUT.py.in b/scripts/python/module/PyNUT.py.in index 0e136d3669..20ad890613 100644 --- a/scripts/python/module/PyNUT.py.in +++ b/scripts/python/module/PyNUT.py.in @@ -1,4 +1,4 @@ -#!@PYTHON@ +#!@PYTHON_DEFAULT@ # -*- coding: utf-8 -*- # Copyright (C) 2008 David Goncalves diff --git a/scripts/python/module/test_nutclient.py.in b/scripts/python/module/test_nutclient.py.in index 8fc2796c67..ea7957ca9f 100755 --- a/scripts/python/module/test_nutclient.py.in +++ b/scripts/python/module/test_nutclient.py.in @@ -1,4 +1,4 @@ -#!@PYTHON@ +#!@PYTHON_DEFAULT@ # -*- coding: utf-8 -*- # This source code is provided for testing/debuging purpose ;) diff --git a/scripts/subdriver/gen-snmp-subdriver.sh b/scripts/subdriver/gen-snmp-subdriver.sh index 52b100ca84..b2fb87294e 100755 --- a/scripts/subdriver/gen-snmp-subdriver.sh +++ b/scripts/subdriver/gen-snmp-subdriver.sh @@ -3,14 +3,14 @@ # an auxiliary script to produce a "stub" snmp-ups subdriver from # SNMP data from a real agent or from dump files # -# Version: 0.16-dmf +# Version: 0.18-dmf # # See also: docs/snmp-subdrivers.txt # # Copyright (C) # 2011 - 2012 Arnaud Quette # 2015 - 2022 Eaton (author: Arnaud Quette ) -# 2011 - 2024 Jim Klimov +# 2011 - 2025 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 @@ -77,6 +77,10 @@ usage() { echo " - 'snmp-mibs-downloader' package (on Debian) to get all standard MIBs" } +# tools +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; } + # variables DRIVER="" KEEP="" @@ -88,21 +92,51 @@ SYSOID="" MODE=0 DMF=0 +if (command -v mktemp) >/dev/null ; then true ; else +# Have a simple (unsafe, unfeatured) fallback implementation: +mktemp() { + if [ x"$1" = x"-d" ] ; then + shift + mkdir -p "$1.$$" || return + else + cat /dev/null > "$1.$$" || return + fi + echo "$1.$$" +} +fi + # constants NAME=gen-snmp-subdriver TMPDIR="${TEMPDIR:-/tmp}" SYSOID_NUMBER=".1.3.6.1.2.1.1.2.0" -DEBUG="`mktemp "$TMPDIR/$NAME-DEBUG.XXXXXX"`" -DFL_NUMWALKFILE="`mktemp "$TMPDIR/$NAME-NUMWALK.XXXXXX"`" -DFL_STRWALKFILE="`mktemp "$TMPDIR/$NAME-STRWALK.XXXXXX"`" -TMP_NUMWALKFILE="`mktemp "$TMPDIR/$NAME-TMP-NUMWALK.XXXXXX"`" -TMP_STRWALKFILE="`mktemp "$TMPDIR/$NAME-TMP-STRWALK.XXXXXX"`" +DEBUG="`mktemp \"$TMPDIR/$NAME-DEBUG.XXXXXX\"`" +DFL_NUMWALKFILE="`mktemp \"$TMPDIR/$NAME-NUMWALK.XXXXXX\"`" +DFL_STRWALKFILE="`mktemp \"$TMPDIR/$NAME-STRWALK.XXXXXX\"`" +TMP_NUMWALKFILE="`mktemp \"$TMPDIR/$NAME-TMP-NUMWALK.XXXXXX\"`" +TMP_STRWALKFILE="`mktemp \"$TMPDIR/$NAME-TMP-STRWALK.XXXXXX\"`" + +# Platforms vary with tooling abilitites... +TOLOWER="cat" +for TR_VARIANT in "tr 'A-Z' 'a-z'" "tr '[:upper:]' '[:lower:]'" "tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'" ; do + if [ x"`echo C | $TR_VARIANT`" = xc ] ; then + TOLOWER="$TR_VARIANT" + break + fi +done + +TOUPPER="cat" +for TR_VARIANT in "tr 'a-z' 'A-Z'" "tr '[:lower:]' '[:upper:]'" "tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" ; do + if [ x"`echo c | $TR_VARIANT`" = xC ] ; then + TOUPPER="$TR_VARIANT" + break + fi +done get_snmp_data() { # 1) get the sysOID (points the mfr specif MIB), apart if there's an override if [ -z "$SYSOID" ] then - SYSOID="`snmpget -On -v1 -c "$COMMUNITY" -Ov "$HOSTNAME" "$SYSOID_NUMBER" | cut -d' ' -f2`" + SYSOID="`snmpget -On -v1 -c \"$COMMUNITY\" -Ov \"$HOSTNAME\" \"$SYSOID_NUMBER\" | cut -d' ' -f2`" echo "sysOID retrieved: ${SYSOID}" else echo "Using the provided sysOID override ($SYSOID)" @@ -134,14 +168,14 @@ get_snmp_data() { generate_C() { # create file names, lowercase - LDRIVER="`echo "$DRIVER" | tr A-Z a-z`" - UDRIVER="`echo "$DRIVER" | tr a-z A-Z`" + LDRIVER="`echo \"$DRIVER\" | $TOLOWER`" + UDRIVER="`echo \"$DRIVER\" | $TOUPPER`" # keep dashes in name for files CFILE="$LDRIVER-mib.c" HFILE="$LDRIVER-mib.h" # but replace with underscores for the structures and defines - LDRIVER="`echo "$LDRIVER" | tr - _`" - UDRIVER="`echo "$UDRIVER" | tr - _`" + LDRIVER="`echo \"$LDRIVER\" | tr - _`" + UDRIVER="`echo \"$UDRIVER\" | tr - _`" # generate header file # NOTE: with <<-EOF leading TABs are all stripped @@ -270,8 +304,8 @@ EOF while IFS= read -r line; do LINENB="`expr $LINENB + 1`" FULL_STR_OID="$line" - STR_OID="`echo "$line" | cut -d'.' -f1`" - echo "$line" | grep STRING > /dev/null + STR_OID="`echo \"$line\" | cut -d'.' -f1`" + echo "$line" | ${GREP} STRING > /dev/null if [ $? -eq 0 ]; then ST_FLAG_TYPE="ST_FLAG_STRING" SU_INFOSIZE="SU_INFOSIZE" @@ -280,7 +314,7 @@ EOF SU_INFOSIZE="1" fi # get the matching numeric OID - NUM_OID="`sed -n "${LINENB}p" "${NUMWALKFILE}" | cut -d' ' -f1`" + NUM_OID="`sed -n \"${LINENB}p\" \"${NUMWALKFILE}\" | cut -d' ' -f1`" printf "\t/* ${FULL_STR_OID} */\n\tsnmp_info_default(\"unmapped.${STR_OID}\", ${ST_FLAG_TYPE}, ${SU_INFOSIZE}, \"${NUM_OID}\", NULL, SU_FLAG_OK, NULL),\n" done < "${STRWALKFILE}" >> "${CFILE}" @@ -301,13 +335,13 @@ EOF generate_DMF() { # create file names - LDRIVER="`echo "$DRIVER" | tr A-Z a-z`" - UDRIVER="`echo "$DRIVER" | tr a-z A-Z`" + LDRIVER="`echo \"$DRIVER\" | $TOLOWER`" + UDRIVER="`echo \"$DRIVER\" | $TOUPPER`" DMFFILE="$LDRIVER-mib.dmf" # but replace with underscores for the structures and defines - LDRIVER="`echo "$LDRIVER" | tr - _`" - UDRIVER="`echo "$UDRIVER" | tr - _`" + LDRIVER="`echo \"$LDRIVER\" | tr - _`" + UDRIVER="`echo \"$UDRIVER\" | tr - _`" # generate DMF file echo "Creating $DMFFILE" @@ -364,13 +398,13 @@ generate_DMF() { printf "\t\t\t\n" >> "${DMFFILE}" printf "\t\t\t\n" >> "${DMFFILE}" printf "\t\t\n\t\t-->\n" >> "${DMFFILE}" - + # extract OID string paths, one by one LINENB="0" while IFS= read -r line; do LINENB="`expr $LINENB + 1`" FULL_STR_OID="$line" - STR_OID="`echo "$line" | cut -d'.' -f1`" + STR_OID="`echo \"$line\" | cut -d'.' -f1`" echo $line | grep STRING > /dev/null if [ $? -eq 0 ]; then ST_FLAG_TYPE="ST_FLAG_STRING" @@ -380,7 +414,7 @@ generate_DMF() { SU_INFOSIZE="1" fi # get the matching numeric OID - NUM_OID="`sed -n "${LINENB}p" "${NUMWALKFILE}" | cut -d' ' -f1`" + NUM_OID="`sed -n \"${LINENB}p\" \"${NUMWALKFILE}\" | cut -d' ' -f1`" printf "\t\t\n\t\t\n" done < "${STRWALKFILE}" >> "${DMFFILE}" @@ -429,7 +463,7 @@ while [ $# -gt 0 ]; do elif [ $# -gt 1 -a "$1" = "-s" ]; then SYSOID="$2" shift 2 - elif echo "$1" | grep -qv '^-'; then + elif echo "$1" | ${GREP} -v '^-' >/dev/null ; then if [ $# -gt 1 ]; then NUMWALKFILE="$1" shift @@ -468,7 +502,7 @@ if [ -z "$NUMWALKFILE" ]; then while [ -z "$HOSTNAME" ]; do printf "\n\tPlease enter the SNMP host IP address or name.\n" read -p "SNMP host IP name or address: " HOSTNAME < /dev/tty - if echo "$HOSTNAME" | grep -E -q '[^a-zA-Z0-9.-]'; then + if echo "$HOSTNAME" | ${EGREP} '[^a-zA-Z0-9.-]' >/dev/null ; then echo "Please use only letters, digits, dash and period character" HOSTNAME="" fi @@ -495,7 +529,7 @@ else fi # Extract the sysOID # Format is "1.3.6.1.2.1.1.2.0 = OID: 1.3.6.1.4.1.4555.1.1.1" - DEVICE_SYSOID="`grep 1.3.6.1.2.1.1.2.0 "$RAWWALKFILE" | cut -d' ' -f4`" + DEVICE_SYSOID="`${GREP} 1.3.6.1.2.1.1.2.0 \"$RAWWALKFILE\" | cut -d' ' -f4`" if [ -n "$DEVICE_SYSOID" ]; then echo "Found sysOID $DEVICE_SYSOID" else @@ -506,14 +540,14 @@ else # Switch to the entry point, and extract the subtree # Extract the numeric walk echo -n "Extracting numeric SNMP walk..." - grep "$DEVICE_SYSOID" "$RAWWALKFILE" | grep -E -v "1.3.6.1.2.1.1.2.0" 2>/dev/null 1> "$NUMWALKFILE" + ${GREP} "$DEVICE_SYSOID" "$RAWWALKFILE" | ${EGREP} -v "1.3.6.1.2.1.1.2.0" 2>/dev/null 1> "$NUMWALKFILE" echo " done" # Create the string walk from a translation of the numeric one echo -n "Converting string SNMP walk..." while IFS=' = ' read NUM_OID OID_VALUE do - STR_OID="`snmptranslate -Os -m ALL -M+. "$NUM_OID" 2>/dev/null`" + STR_OID="`snmptranslate -Os -m ALL -M+. \"$NUM_OID\" 2>/dev/null`" # Uncomment the below line to get debug logs #echo "Got: $STR_OID = $OID_VALUE" printf "." @@ -531,7 +565,7 @@ else Please enter the value of sysOID, as displayed by snmp-ups. For example '.1.3.6.1.4.1.2254.2.4'. You can get it using: snmpget -v1 -c XXX $SYSOID_NUMBER" read -p "Value of sysOID: " SYSOID < /dev/tty - if echo "$SYSOID" | grep -E -q '[^0-9.]'; then + if echo "$SYSOID" | ${EGREP} '[^0-9.]' >/dev/null ; then echo "Please use only the numeric form, with dots and digits" SYSOID="" fi @@ -558,21 +592,22 @@ while [ -z "$DRIVER" ]; do Please enter a name for this driver. Use only letters and numbers. Use natural (upper- and lowercase) capitalization, e.g., 'Belkin', 'APC'." read -p "Name of subdriver: " DRIVER < /dev/tty - if echo "$DRIVER" | grep -E -q '[^a-zA-Z0-9]'; then + if echo "$DRIVER" | ${EGREP} '[^a-zA-Z0-9]' >/dev/null ; then echo "Please use only letters and digits" DRIVER="" fi done # remove blank and "End of MIB" lines -grep -E -e "^[[:space:]]?$" -e "End of MIB" -v "${NUMWALKFILE}" > "${TMP_NUMWALKFILE}" -grep -E -e "^[[:space:]]?$" -e "End of MIB" -v "${STRWALKFILE}" > "${TMP_STRWALKFILE}" +TABCHAR="`printf '\t'`" +${EGREP} "^[ ${TABCHAR}]?\$" | ${GREP} "End of MIB" | ${GREP} -v "${NUMWALKFILE}" > "${TMP_NUMWALKFILE}" +${EGREP} "^[ ${TABCHAR}]?\$" | ${GREP} "End of MIB" | ${GREP} -v "${STRWALKFILE}" > "${TMP_STRWALKFILE}" NUMWALKFILE="${TMP_NUMWALKFILE}" STRWALKFILE="${TMP_STRWALKFILE}" # FIXME: sanity checks (! -z contents -a same `wc -l`) -NUM_OID_COUNT="`cat "$NUMWALKFILE" | wc -l`" -STR_OID_COUNT="`cat "$STRWALKFILE" | wc -l`" +NUM_OID_COUNT="`cat \"$NUMWALKFILE\" | wc -l`" +STR_OID_COUNT="`cat \"$STRWALKFILE\" | wc -l`" echo "SNMP OIDs extracted = $NUM_OID_COUNT / $NUM_OID_COUNT" diff --git a/scripts/subdriver/gen-usbhid-subdriver.sh b/scripts/subdriver/gen-usbhid-subdriver.sh index 75f3ec269b..e6b90b5bdf 100755 --- a/scripts/subdriver/gen-usbhid-subdriver.sh +++ b/scripts/subdriver/gen-usbhid-subdriver.sh @@ -23,6 +23,10 @@ usage() { echo " file -- read from file instead of stdin" } +# tools +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; } + DRIVER="" VENDORID="" PRODUCTID="" @@ -41,7 +45,7 @@ while [ $# -gt 0 ]; do elif [ "$1" = "-k" ]; then KEEP=yes shift - elif echo "$1" | grep -qv '^-'; then + elif echo "$1" | ${GREP} -v '^-' >/dev/null ; then FILE="$1" shift elif [ "$1" = "--help" -o "$1" = "-h" ]; then @@ -61,14 +65,27 @@ if [ -z "$KEEP" ]; then trap cleanup EXIT fi +if (command -v mktemp) >/dev/null ; then true ; else +# Have a simple (unsafe, unfeatured) fallback implementation: +mktemp() { + if [ x"$1" = x"-d" ] ; then + shift + mkdir -p "$1.$$" || return + else + cat /dev/null > "$1.$$" || return + fi + echo "$1.$$" +} +fi + NAME=gen-usbhid-subdriver TMPDIR="${TEMPDIR:-/tmp}" -DEBUG=`mktemp "$TMPDIR/$NAME-DEBUG.XXXXXX"` -UTABLE=`mktemp "$TMPDIR/$NAME-UTABLE.XXXXXX"` -USAGES=`mktemp "$TMPDIR/$NAME-USAGES.XXXXXX"` -SUBST=`mktemp "$TMPDIR/$NAME-SUBST.XXXXXX"` -SEDFILE=`mktemp "$TMPDIR/$NAME-SEDFILE.XXXXXX"` -NEWUTABLE=`mktemp "$TMPDIR/$NAME-NEWUTABLE.XXXXXX"` +DEBUG="`mktemp \"$TMPDIR/$NAME-DEBUG.XXXXXX\"`" +UTABLE="`mktemp \"$TMPDIR/$NAME-UTABLE.XXXXXX\"`" +USAGES="`mktemp \"$TMPDIR/$NAME-USAGES.XXXXXX\"`" +SUBST="`mktemp \"$TMPDIR/$NAME-SUBST.XXXXXX\"`" +SEDFILE="`mktemp \"$TMPDIR/$NAME-SEDFILE.XXXXXX\"`" +NEWUTABLE="`mktemp \"$TMPDIR/$NAME-NEWUTABLE.XXXXXX\"`" # save standard input to a file if [ -z "$FILE" ]; then @@ -82,7 +99,7 @@ while [ -z "$DRIVER" ]; do Please enter a name for this driver. Use only letters and numbers. Use natural (upper- and lowercase) capitalization, e.g., 'Belkin', 'APC'." read -p "Name of subdriver: " DRIVER < /dev/tty - if echo $DRIVER | grep -E -q '[^a-zA-Z0-9]'; then + if echo $DRIVER | ${EGREP} '[^a-zA-Z0-9]' >/dev/null ; then echo "Please use only letters and digits" DRIVER="" fi @@ -100,8 +117,25 @@ if [ -z "$PRODUCTID" ]; then read -p "Product ID: " PRODUCTID < /dev/tty fi -LDRIVER=`echo $DRIVER | tr A-Z a-z` -UDRIVER=`echo $DRIVER | tr a-z A-Z` +# Platforms vary with tooling abilitites... +TOLOWER="cat" +for TR_VARIANT in "tr 'A-Z' 'a-z'" "tr '[:upper:]' '[:lower:]'" "tr 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz'" ; do + if [ x"`echo C | $TR_VARIANT`" = xc ] ; then + TOLOWER="$TR_VARIANT" + break + fi +done + +TOUPPER="cat" +for TR_VARIANT in "tr 'a-z' 'A-Z'" "tr '[:lower:]' '[:upper:]'" "tr 'abcdefghijklmnopqrstuvwxyz' 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'" ; do + if [ x"`echo c | $TR_VARIANT`" = xC ] ; then + TOUPPER="$TR_VARIANT" + break + fi +done + +LDRIVER="`echo $DRIVER | $TOLOWER`" +UDRIVER="`echo $DRIVER | $TOUPPER`" CFILE="$LDRIVER-hid.c" HFILE="$LDRIVER-hid.h" @@ -113,7 +147,7 @@ cat "$UTABLE" | tr '.' $'\n' | sort -u > "$USAGES" # make up dummy names for unknown usages count=0 -cat "$USAGES" | grep -E '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]' |\ +cat "$USAGES" | ${EGREP} '[0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F][0-9a-fA-F]' |\ while read U; do count=`expr $count + 1` echo "$U $UDRIVER$count" @@ -246,7 +280,7 @@ static hid_info_t ${LDRIVER}_hid2nut[] = { EOF cat "$NEWUTABLE" | sort -u | while read U; do - UL=`echo $U | tr A-Z a-z` + UL="`echo $U | $TOLOWER`" cat >> "$CFILE" <&2 } +# This depends on POWERDOWNFLAG being configured in upsmon.conf +# and can happen on both NUT servers and pure client machines: if @SBINDIR@/upsmon -K >/dev/null 2>&1; then if [ -x "@SBINDIR@/upsdrvctl" ] ; then $POWEROFF_QUIET || { diff --git a/scripts/upower/95-upower-hid.hwdb b/scripts/upower/95-upower-hid.hwdb index a45706a78e..f40953ac15 100644 --- a/scripts/upower/95-upower-hid.hwdb +++ b/scripts/upower/95-upower-hid.hwdb @@ -72,6 +72,7 @@ usb:v051Dp0000* usb:v051Dp0002* usb:v051Dp0003* usb:v051Dp0004* +usb:v051Dp0005* UPOWER_BATTERY_TYPE=ups UPOWER_VENDOR=APC diff --git a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in index 17bdf14db1..b7516d1dbc 100644 --- a/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in +++ b/scripts/upsdrvsvcctl/nut-driver-enumerator.sh.in @@ -79,27 +79,67 @@ # start-up lacking a driver, there should be some timer to re-enable # failed not-disabled drivers (would be useful in any case though). +# The GREP and EGREP commands found during configuration for the platform +GREP="@GREP@" +EGREP="@EGREP@" + # Directory where NUT configs are located, e.g. /etc/nut or /etc/ups # Set at package configuration, compiled into daemons and drivers prefix="@prefix@" -[ -n "${NUT_CONFPATH-}" ] || NUT_CONFPATH="@sysconfdir@" +[ -n "${NUT_CONFPATH-}" ] || NUT_CONFPATH="@CONFPATH@" # Technically this should be a distribution-dependent value configured # during package build. But everyone has it the same from systemd defaults: [ -n "${SYSTEMD_CONFPATH-}" ] || SYSTEMD_CONFPATH="/etc/systemd/system" +# NOTE: We log whatever info traces to stderr, because many modes expect +# user/program consumable outputs on stdout. +shouldDebug() { + [ -n "$DEBUG" ] || [ "${NUT_DEBUG_LEVEL}" -gt 0 ] 2>/dev/null +} + +log_debug() { + # TODO: Provide an equivalent of upsdebugx and verbosity levels?.. + if shouldDebug ; then + echo "`TZ=UTC LANG=C date` [DEBUG] $@" >&2 + fi + return 0 +} + +log_info() { + echo "`TZ=UTC LANG=C date` [INFO] $@" >&2 +} + +log_warn() { + echo "`TZ=UTC LANG=C date` [WARNING] $@" >&2 +} + +log_error() { + echo "`TZ=UTC LANG=C date` [ERROR] $@" >&2 +} + +log_fatal() { + EXITCODE="$1" + if [ "${EXITCODE}" -ge 0 ] 2>/dev/null ; then + shift 1 + else + EXITCODE=1 + fi + echo "`TZ=UTC LANG=C date` [FATAL] $@" >&2 + exit $EXITCODE +} + if [ -n "$ZSH_VERSION" ]; then ### Problem: loops like `for UPS in $UPSLIST` do not separate ### the UPSLIST into many tokens but use it as one string. - echo "FATAL: zsh is not supported in this script" >&2 - exit 1 + log_fatal 1 "zsh is not supported in this script" +# FIXME: Try some more checks/solutions like these? # setopt noglob # setopt +F # IFS="`printf ' \t\r\n'`" ; export IFS fi -if set | grep -E '^(shell|version|t?csh)' | grep -E 't?csh' >/dev/null ; then - echo "FATAL: csh or tcsh is not supported in this script" >&2 - exit 1 +if set | $EGREP '^(shell|version|t?csh)' | $EGREP 't?csh' >/dev/null ; then + log_fatal 1 "csh or tcsh is not supported in this script" fi # Third-party services to depend on (can be overridden by config file) @@ -147,8 +187,8 @@ SVCNAME_SMF="svc:/system/power/nut-driver" NUT_DRIVER_ENUMERATOR_CONF="${NUT_CONFPATH}/nut-driver-enumerator.conf" [ -s "${NUT_DRIVER_ENUMERATOR_CONF}" ] && \ - echo "Sourcing config file: ${NUT_DRIVER_ENUMERATOR_CONF}" && \ - . "${NUT_DRIVER_ENUMERATOR_CONF}" + log_info "Sourcing config file: ${NUT_DRIVER_ENUMERATOR_CONF}" && \ + . "${NUT_DRIVER_ENUMERATOR_CONF}" >&2 [ -z "${UPSCONF-}" ] && \ UPSCONF="${NUT_CONFPATH}/ups.conf" @@ -271,17 +311,15 @@ case "${SERVICE_FRAMEWORK-}" in hook_restart_drv="selftest_NOOP" ;; "") - echo "Error detecting the service-management framework on this OS" >&2 - exit 1 + log_fatal 1 "Error detecting the service-management framework on this OS" ;; *) - echo "Error: User provided an unknown service-management framework '$SERVICE_FRAMEWORK'" >&2 - exit 1 + log_fatal 1 "Error: User provided an unknown service-management framework '$SERVICE_FRAMEWORK'" ;; esac selftest_NOOP() { - echo "NO-OP: Self-testing context does not do systems configuration" >&2 + log_warn "NO-OP: Self-testing context does not do systems configuration" return 0 } @@ -289,7 +327,7 @@ common_isFiled() { [ "${#UPSLIST_FILE}" != 0 ] && \ for UPSF in $UPSLIST_FILE ; do [ "$1" = "$UPSF" ] && return 0 - [ "`$hook_validInstanceName "$UPSF"`" = "$1" ] && return 0 + [ "`$hook_validInstanceName \"$UPSF\"`" = "$1" ] && return 0 done return 1 } @@ -298,7 +336,7 @@ common_isRegistered() { [ "${#UPSLIST_SVCS}" != 0 ] && \ for UPSS in $UPSLIST_SVCS ; do [ "$1" = "$UPSS" ] && return 0 - [ "`$hook_validInstanceName "$1"`" = "$UPSS" ] && return 0 + [ "`$hook_validInstanceName \"$1\"`" = "$UPSS" ] && return 0 done return 1 } @@ -320,11 +358,11 @@ upslist_equals() { # Trivial case 1: equal strings [ "$1" = "$2" ] && return 0 # Trivial case 2: different amount of entries - [ "`echo "$1" | wc -l`" = "`echo "$2" | wc -l`" ] || return $? + [ "`echo \"$1\" | wc -l`" = "`echo \"$2\" | wc -l`" ] || return $? _TMP_DEV_SVC="" for _DEV in $1 ; do - DEVINST="`$hook_validInstanceName "${_DEV}"`" + DEVINST="`$hook_validInstanceName \"${_DEV}\"`" for _SVC in $2 ; do [ "${_DEV}" = "${_SVC}" ] \ || [ "$DEVINST" = "${_SVC}" ] \ @@ -339,7 +377,7 @@ ${_DEV} = ${_SVC}" ; } [ "${#_TMP_DEV_SVC}" = 0 ] && return 1 # Exit code : is the built mapping as long as the source list(s)? - [ "`echo "$1" | wc -l`" = "`echo "${_TMP_DEV_SVC}" | wc -l`" ] + [ "`echo \"$1\" | wc -l`" = "`echo \"${_TMP_DEV_SVC}\" | wc -l`" ] } upssvcconf_checksum_unchanged() { @@ -347,7 +385,7 @@ upssvcconf_checksum_unchanged() { # compare checksums of the configuration section from the file and the # stashed configuration in a service instance (if any). # FIXME : optimize by caching, we likely have quite a few requests - [ "`upsconf_getSection_MD5 "$1"`" = "`$hook_getSavedMD5 "$2"`" ] + [ "`upsconf_getSection_MD5 \"$1\"`" = "`$hook_getSavedMD5 \"$2\"`" ] } upslist_checksums_unchanged() { @@ -359,7 +397,7 @@ upslist_checksums_unchanged() { _TMP_SVC="" for _DEV in $1 ; do - DEVINST="`$hook_validInstanceName "${_DEV}"`" + DEVINST="`$hook_validInstanceName \"${_DEV}\"`" for _SVC in $2 ; do if [ "${_DEV}" = "${_SVC}" ] \ || [ "$DEVINST" = "${_SVC}" ] \ @@ -397,8 +435,8 @@ upslist_savednames_find_missing() { # props (do not report those that have a non-trivial prop value): # whether an empty value or completely absent from the list for SVCINST in $SVCINSTS ; do - echo "$SVCINST_PROPS" | grep -E "^${SVCINST}${TABCHAR}"'$' >/dev/null && echo "$SVCINST" - echo "$SVCINST_PROPS" | grep -E "^${SVCINST}${TABCHAR}" >/dev/null || echo "$SVCINST" + echo "$SVCINST_PROPS" | $EGREP "^${SVCINST}${TABCHAR}"'$' >/dev/null && echo "$SVCINST" + echo "$SVCINST_PROPS" | $EGREP "^${SVCINST}${TABCHAR}" >/dev/null || echo "$SVCINST" done return 0 } @@ -430,15 +468,15 @@ upslist_savednames_find_mismatch() { # Find services whose props exist but services themselves do not # (e.g. upgrading from some version with different naming patterns) echo "$SVCINST_PROPS" | while read SVCINST_PROP DEVNAME_PROP ; do - echo "$SVCINSTS" | grep -E "^${SVCINST_PROP}"'$' >/dev/null || echo "$SVCINST_PROP" + echo "$SVCINSTS" | $EGREP "^${SVCINST_PROP}"'$' >/dev/null || echo "$SVCINST_PROP" done # Find and report services which do *not* have saved device names in # props (do not report those that have a non-trivial prop value): # whether an empty value or completely absent from the list for SVCINST in $SVCINSTS ; do - echo "$SVCINST_PROPS" | grep -E "^${SVCINST}${TABCHAR}"'$' >/dev/null && echo "$SVCINST" - echo "$SVCINST_PROPS" | grep -E "^${SVCINST}${TABCHAR}" >/dev/null || echo "$SVCINST" + echo "$SVCINST_PROPS" | $EGREP "^${SVCINST}${TABCHAR}"'$' >/dev/null && echo "$SVCINST" + echo "$SVCINST_PROPS" | $EGREP "^${SVCINST}${TABCHAR}" >/dev/null || echo "$SVCINST" done return 0 } @@ -500,7 +538,7 @@ $LINE" echo "$SECTION_CONTENT" fi - [ "$RES" = 0 ] || echo "ERROR: Section [$1] was not found in the '$UPSCONF' file" >&2 + [ "$RES" = 0 ] || log_error "Section [$1] was not found in the '$UPSCONF' file" return $RES } @@ -515,7 +553,7 @@ EOF } upsconf_getSection_MD5() { - calc_md5 "`upsconf_getSection "$@"`" + calc_md5 "`upsconf_getSection \"$@\"`" } upsconf_getSection_SDP() { @@ -539,7 +577,7 @@ upsconf_getValue() { # Note: Primary aim of this egrep is to pick either assignments or flags # As a by-product it can be used to test if a particular value is set ;) - SECTION_CONTENT="`$GETSECTION "$1"`" || return + SECTION_CONTENT="`$GETSECTION \"$1\"`" || return shift KEYS="$*" @@ -547,17 +585,17 @@ upsconf_getValue() { RES_L=0 VALUE="" - LINE="`echo "$SECTION_CONTENT" | grep -E '(^'"$1"'=|^'"$1"'$)'`" \ + LINE="`echo \"$SECTION_CONTENT\" | $EGREP '(^'\"$1\"'=|^'\"$1\"'$)'`" \ && VALUE="$(echo "$LINE" | sed -e "s,^$1=,," -e 's,^\"\(.*\)\"$,\1,' -e "s,^'\(.*\)'\$,\1,")" \ || RES_L=$? - [ "$RES_L" = 0 ] || { RES="$RES_L" ; echo "ERROR: Section [$CURR_SECTION] or key '$1' in it was not found in the '$UPSCONF' file" >&2 ; } + [ "$RES_L" = 0 ] || { RES="$RES_L" ; log_error "Section [$CURR_SECTION] or key '$1' in it was not found in the '$UPSCONF' file" ; } echo "$VALUE" shift done - [ "$RES" = 0 ] || echo "ERROR: Section [$CURR_SECTION] or key(s) '$KEYS' in it was not found in the '$UPSCONF' file" >&2 + [ "$RES" = 0 ] || log_error "ERROR: Section [$CURR_SECTION] or key(s) '$KEYS' in it was not found in the '$UPSCONF' file" return $RES } @@ -581,13 +619,13 @@ upsconf_getDriverMedia() { # unit), newline-separated (drvnametype). Empty type for unclassified # results, assuming no known special dependencies (note that depending on # particular system's physics, both serial and network media may need USB). - CURR_DRV="`upsconf_getDriver "$1"`" || return $? + CURR_DRV="`upsconf_getDriver \"$1\"`" || return $? case "$CURR_DRV" in *netxml*|*snmp*|*ipmi*|*powerman*|*-mib*|*avahi*) printf '%s\n%s\n' "$CURR_DRV" "network" ; return ;; *apcupsd-ups*) # Relay from a nearby apcupsd network server into NUT ecosystem: - CURR_PORT="`upsconf_getPort "$1"`" || CURR_PORT="" + CURR_PORT="`upsconf_getPort \"$1\"`" || CURR_PORT="" case "$CURR_PORT" in *localhost*|*127.0.0.1*|*::1*) printf '%s\n%s\n' "$CURR_DRV" "network-localhost" ; return ;; @@ -596,8 +634,8 @@ upsconf_getDriverMedia() { esac ;; *apc_modbus*) - CURR_PORT="`upsconf_getPort "$1"`" || CURR_PORT="" - CURR_PORTTYPE="`upsconf_getValue "$1" 'porttype'`" || CURR_PORTTYPE="" + CURR_PORT="`upsconf_getPort \"$1\"`" || CURR_PORT="" + CURR_PORTTYPE="`upsconf_getValue \"$1\" 'porttype'`" || CURR_PORTTYPE="" case "$CURR_PORTTYPE" in *usb*) printf '%s\n%s\n' "$CURR_DRV" "usb" ; return ;; @@ -625,13 +663,13 @@ upsconf_getDriverMedia() { *usb*) printf '%s\n%s\n' "$CURR_DRV" "usb" ; return ;; nutdrv_qx) # May be direct serial or USB - CURR_PORT="`upsconf_getPort "$1"`" || CURR_PORT="" + CURR_PORT="`upsconf_getPort \"$1\"`" || CURR_PORT="" case "$CURR_PORT" in auto|/dev/*usb*|/dev/*hid*) printf '%s\n%s\n' "$CURR_DRV" "usb" ; return ;; /dev/*) # See drivers/nutdrv_qx.c :: upsdrv_initups() for a list - if [ -n "`upsconf_getValue "$1" 'subdriver' 'vendorid' 'productid' 'vendor' 'product' 'serial' 'bus' 'busport' 'langid_fix'`" ] \ + if [ -n "`upsconf_getValue \"$1\" 'subdriver' 'vendorid' 'productid' 'vendor' 'product' 'serial' 'bus' 'busport' 'langid_fix'`" ] \ ; then printf '%s\n%s\n' "$CURR_DRV" "usb" ; return else @@ -643,7 +681,7 @@ upsconf_getDriverMedia() { esac ;; *dummy*) # May be networked (proxy to remote NUT) - CURR_PORT="`upsconf_getPort "$1"`" || CURR_PORT="" + CURR_PORT="`upsconf_getPort \"$1\"`" || CURR_PORT="" case "$CURR_PORT" in *@localhost|*@|*@127.0.0.1|*@::1) printf '%s\n%s\n' "$CURR_DRV" "network-localhost,drivers=`echo "${CURR_PORT}" | sed 's,@.*$,,'`" ; return ;; @@ -663,8 +701,8 @@ upsconf_getDriverMedia() { } upsconf_getMedia() { - _DRVMED="`upsconf_getDriverMedia "$1"`" || return - echo "${_DRVMED}" | tail -n +2 + _DRVMED="`upsconf_getDriverMedia \"$1\"`" || return + echo "${_DRVMED}" | @TAIL@ @TAIL_ARGS_FROM_NTH_LINE@ +2 return 0 } @@ -672,31 +710,33 @@ upsconf_list_dev_drv_socket_checksum() { # NOTE: Output column order matters, it is parsed e.g. to check # for driver-on-driver dependencies when adding services upslist_readFile_once && [ "${#UPSLIST_FILE}" != 0 ] \ - || { echo "No devices detected in '$UPSCONF'" >&2 ; return 1 ; } + || { log_warn "No devices detected in '$UPSCONF'" ; return 1 ; } # Use the section-driver-port subset if [ x"${AVOID_REPARSE}" != xyes ] ; then upslist_normalizeFile_once || return # Propagate errors upwards fi for _DEV in $UPSLIST_FILE ; do - _DRV="`upsconf_getDriver "${_DEV}"`" - _MD5="`upsconf_getSection_MD5 "${_DEV}"`" + _DRV="`upsconf_getDriver \"${_DEV}\"`" + _MD5="`upsconf_getSection_MD5 \"${_DEV}\"`" printf '%s\t%s\t%s\t%s\n' "${_DEV}" "${_DRV}" "${_DRV}-${_DEV}" "${_MD5}" done } upsconf_debug() { - _DRV="`upsconf_getDriver "$1"`" - _PRT="`upsconf_getPort "$1"`" - _MED="`upsconf_getMedia "$1"`" - _MD5="`upsconf_getSection_MD5 "$1"`" - NAME_MD5="`calc_md5 "$1"`" + _DRV="`upsconf_getDriver \"$1\"`" + _PRT="`upsconf_getPort \"$1\"`" + _MED="`upsconf_getMedia \"$1\"`" + _MD5="`upsconf_getSection_MD5 \"$1\"`" + NAME_MD5="`calc_md5 \"$1\"`" + # Not public, not in usage() + # Used by tests, echo as formatted here to stdout echo "INST: ${NAME_MD5}~[$1]: DRV='${_DRV}' PORT='${_PRT}' MEDIA='${_MED}' SECTIONMD5='${_MD5}'" } calc_md5() { # Tries several ways to produce an MD5 of the "$1" argument - _MD5="`echo "$1" | md5sum 2>/dev/null | awk '{print $1}'`" && [ -n "${_MD5}" ] || \ - { _MD5="`echo "$1" | openssl dgst -md5 2>/dev/null | awk '{print $NF}'`" && [ -n "${_MD5}" ]; } || \ + _MD5="`echo \"$1\" | md5sum 2>/dev/null | awk '{print $1}'`" && [ -n "${_MD5}" ] || \ + { _MD5="`echo \"$1\" | openssl dgst -md5 2>/dev/null | awk '{print $NF}'`" && [ -n "${_MD5}" ]; } || \ return 1 echo "${_MD5}" @@ -706,8 +746,8 @@ calc_md5_file() { # Tries several ways to produce an MD5 of the file named by "$1" argument [ -s "$1" ] || return 2 - _MD5="`md5sum 2>/dev/null < "$1" | awk '{print $1}'`" && [ -n "${_MD5}" ] || \ - { _MD5="`openssl dgst -md5 2>/dev/null < "$1" | awk '{print $NF}'`" && [ -n "${_MD5}" ]; } || \ + _MD5="`md5sum 2>/dev/null < \"$1\" | awk '{print $1}'`" && [ -n "${_MD5}" ] || \ + { _MD5="`openssl dgst -md5 2>/dev/null < \"$1\" | awk '{print $NF}'`" && [ -n "${_MD5}" ]; } || \ return 1 echo "${_MD5}" @@ -735,16 +775,16 @@ smf_registerInstance() { smf_unregisterInstance "$SVCINST" fi /usr/sbin/svccfg -s nut-driver add "$DEVICE" || \ - { SVCINST="`smf_validInstanceName "$1"`" || return + { SVCINST="`smf_validInstanceName \"$1\"`" || return if /usr/bin/svcs "nut-driver:$SVCINST" >/dev/null 2>&1 ; then smf_unregisterInstance "$SVCINST" fi /usr/sbin/svccfg -s nut-driver add "$SVCINST" || return ; } - echo "Added instance: 'nut-driver:$SVCINST' for NUT configuration section '$DEVICE'" >&2 + log_info "Added instance: 'nut-driver:$SVCINST' for NUT configuration section '$DEVICE'" DEPSVC="" DEPREQ="" - _MED="`upsconf_getMedia "$DEVICE"`" + _MED="`upsconf_getMedia \"$DEVICE\"`" case "${_MED}" in usb|usb,*) DEPSVC="$DEPSVC_USB_SMF" @@ -759,18 +799,19 @@ smf_registerInstance() { drivers=*) ;; # Handled just below '') ;; # FIXME: modbus? sysfs like INA219? GPIO? Other local devices? - *) echo "WARNING: Unexpected NUT media type ignored: '${_MED}'" >&2 ;; + *) log_warn "WARNING: Unexpected NUT media type ignored: '${_MED}'" ;; esac case "${_MED}" in drivers=*|*,drivers=*) OTHERLIST="`upsconf_list_dev_drv_socket_checksum`" || OTHERLIST="" + # Not double-quoting here to iterate the string tokens: for DEPDRV in `echo "${_MED}" | sed -e 's/^\(.*,d\|d\)rivers=//' -e 's/,/ /g'` ; do case "${DEPDRV}" in *-*) # May be "drivername-upsname", where either sub-string # may have dashes inside too; try to find the right one: - OTHERDEV="`echo "${OTHERLIST}" | awk '($3 == "'"${DEPDRV}"'") { print $1 }'`" && \ - OTHERDRV="`echo "${OTHERLIST}" | awk '($3 == "'"${DEPDRV}"'") { print $2 }'`" && \ + OTHERDEV="`echo \"${OTHERLIST}\" | awk '($3 == \"'\"${DEPDRV}\"'\") { print $1 }'`" && \ + OTHERDRV="`echo \"${OTHERLIST}\" | awk '($3 == \"'\"${DEPDRV}\"'\") { print $2 }'`" && \ [ x"${DEPDRV}" = x"${OTHERDRV}-${OTHERDEV}" ] && \ [ x"${OTHERDEV}" != x"${DEVICE}" ] \ || { @@ -778,7 +819,7 @@ smf_registerInstance() { case "${DEPDRV}" in *-"${OTHERDEV}") OTHERDRV="'`echo "${OTHERLIST}" | awk '($1 == "'"${OTHERDEV}"'") { print $2 }'`'" || OTHERDRV="" - echo "WARNING: Device ${DEVICE} depends on another as '${DEPDRV}', but the other possible device '${OTHERDEV}' uses an unexpected driver: ${OTHERDRV}" >&2 + log_warn "Device ${DEVICE} depends on another as '${DEPDRV}', but the other possible device '${OTHERDEV}' uses an unexpected driver: ${OTHERDRV}" ;; esac done @@ -788,32 +829,35 @@ smf_registerInstance() { ;; *) OTHERDEV="${DEPDRV}" ;; esac - if [ x"${OTHERDEV}" = x ] || ! (echo "$OTHERLIST" | grep -E "^${OTHERDEV}\t") >/dev/null ; then - echo "WARNING: Device ${DEVICE} depends on another ${DEPDRV} but we did not find a config section for it" >&2 - else + if [ x"${OTHERDEV}" != x ] && (echo "$OTHERLIST" | $EGREP "^${OTHERDEV}\t") >/dev/null ; then DEPSVC="$DEPSVC ${SVCNAME_SMF}${SVCNAMESEP_SMF}`smf_validInstanceName ${OTHERDEV}`" + else + log_warn "Device ${DEVICE} depends on another ${DEPDRV} but we did not find a config section for it" fi done - if [ x"`echo "$DEPSVC" | tr -d ' '`" != x ] ; then DEPREQ="$DEPREQ_DRV_FULL_SMF" ; fi + if [ x"`echo \"$DEPSVC\" | tr -d ' '`" != x ] ; then DEPREQ="$DEPREQ_DRV_FULL_SMF" ; fi ;; esac TARGET_FMRI="nut-driver:$SVCINST" if [ -n "$DEPSVC" ]; then [ -n "$DEPREQ" ] || DEPREQ="optional_all" - echo "Adding '$DEPREQ' dependency for '$SVCINST' on '$DEPSVC'..." + log_info "Adding '$DEPREQ' dependency for '$SVCINST' on '$DEPSVC'..." + # NOTE: it seems (at least as of Solaris 11) that the fmri: definition + # below (especially when there are many dependency FMRI's) must be a + # multi-token list with parentheses, not a single quoted string. DEPPG="nut-driver-enumerator-generated" RESTARTON="refresh" /usr/sbin/svccfg -s "$TARGET_FMRI" addpg "$DEPPG" dependency && \ /usr/sbin/svccfg -s "$TARGET_FMRI" setprop "$DEPPG"/grouping = astring: "$DEPREQ" && \ /usr/sbin/svccfg -s "$TARGET_FMRI" setprop "$DEPPG"/restart_on = astring: "$RESTARTON" && \ /usr/sbin/svccfg -s "$TARGET_FMRI" setprop "$DEPPG"/type = astring: service && \ - /usr/sbin/svccfg -s "$TARGET_FMRI" setprop "$DEPPG"/entities = fmri: "($DEPSVC)" && \ - echo "OK" || echo "FAILED to define the dependency" >&2 + /usr/sbin/svccfg -s "$TARGET_FMRI" setprop "$DEPPG"/entities = fmri: "(" $DEPSVC ")" && \ + log_info "OK: Defined the '$DEPREQ' dependency for '$SVCINST' on '$DEPSVC'" || log_error "FAILED to define the '$DEPREQ' dependency for '$SVCINST' on '$DEPSVC'" fi - smf_setSavedMD5 "$SVCINST" "`upsconf_getSection_MD5 "$DEVICE"`" + smf_setSavedMD5 "$SVCINST" "`upsconf_getSection_MD5 \"$DEVICE\"`" # Save original device (config section) name to speed up some searches smf_setSavedDeviceName "$SVCINST" "$DEVICE" smf_setDocLink "$SVCINST" "$DEVICE" @@ -822,11 +866,11 @@ smf_registerInstance() { if [ "$AUTO_START" = yes ] ; then /usr/sbin/svcadm clear "${TARGET_FMRI}" 2>/dev/null || true /usr/sbin/svcadm enable "${TARGET_FMRI}" || return - echo "Started instance: 'nut-driver:$SVCINST' for NUT configuration section '$DEVICE'" >&2 + log_info "Started instance: 'nut-driver:$SVCINST' for NUT configuration section '$DEVICE'" fi } smf_unregisterInstance() { - echo "Removing instance: 'nut-driver:$1' ..." >&2 + log_info "Removing instance: 'nut-driver:$1' ..." /usr/sbin/svcadm disable -ts 'nut-driver:'"$1" || false /usr/sbin/svccfg -s nut-driver delete "$1" } @@ -835,7 +879,7 @@ smf_refreshSupervizor() { } smf_listInstances_raw() { # Newer versions have pattern matching; older SMF might not have this luxury - /usr/bin/svcs -a -H -o fmri | grep -E '/nut-driver:' + /usr/bin/svcs -a -H -o fmri | $EGREP '/nut-driver:' } smf_listInstances() { # Chop twice, in case there is a leading "svc:/...." @@ -899,10 +943,11 @@ smf_setSavedUniq() { *) __TYPE="${__TYPE}:" ;; esac __VAL="$5" + log_info "Stashing service property '${__PG}/${__PROP}' of '${__TARGET_FMRI}'..." /usr/sbin/svccfg -s "${__TARGET_FMRI}" delprop "${__PG}" 2>/dev/null || true /usr/sbin/svccfg -s "${__TARGET_FMRI}" addpg "${__PG}" application && \ /usr/sbin/svccfg -s "${__TARGET_FMRI}" setprop "${__PG}/${__PROP}" = "${__TYPE}" "${__VAL}" - [ $? = 0 ] && echo "OK" || { echo "FAILED to stash the service property ${__PG}/${__PROP}">&2 ; return 1 ; } + [ $? = 0 ] && log_info "OK: Stashed the service property ${__PG}/${__PROP}" || { log_error "FAILED to stash the service property ${__PG}/${__PROP}" ; return 1 ; } case "${__TARGET_FMRI}" in svc:/*:*) ;; # A service instance by full FMRI, refresh @@ -936,7 +981,7 @@ smf_setDocLink() { # into service instance $1 [ -n "$1" ] || return # No-op for global section __TARGET_FMRI="nut-driver:$1" - __DRV="`upsconf_getDriver "$2"`" + __DRV="`upsconf_getDriver \"$2\"`" ### Sample: #tm_common_name template @@ -950,32 +995,36 @@ smf_setDocLink() { #tm_man_nwamd8/title astring nwamd __PG="tm_doc_${__DRV}_Page" + log_info "Stashing service property group '${__PG}' for '${__TARGET_FMRI}' for online docs..." /usr/sbin/svccfg -s "${__TARGET_FMRI}" delprop "${__PG}" 2>/dev/null || true /usr/sbin/svccfg -s "${__TARGET_FMRI}" addpg "${__PG}" template && \ /usr/sbin/svccfg -s "${__TARGET_FMRI}" setprop "${__PG}/name" = "astring:" "\"${__DRV} online\"" && \ /usr/sbin/svccfg -s "${__TARGET_FMRI}" setprop "${__PG}/uri" = "astring:" "${NUT_WEBSITE_BASE}/docs/man/${__DRV}.html" - [ $? = 0 ] && echo "OK" || { echo "FAILED to stash the service property group '${__PG}' for online docs">&2 ; return 1 ; } + [ $? = 0 ] && log_info "OK: Stashed the service property group '${__PG}' for '${__TARGET_FMRI}' for online docs" \ + || { log_error "FAILED to stash the service property group '${__PG}' for '${__TARGET_FMRI}' for online docs" ; return 1 ; } __PG="tm_man_${__DRV}@MAN_SECTION_CMD_SYS@" + log_info "Stashing service property group '${__PG}' for '${__TARGET_FMRI}' for local docs..." /usr/sbin/svccfg -s "${__TARGET_FMRI}" delprop "${__PG}" 2>/dev/null || true /usr/sbin/svccfg -s "${__TARGET_FMRI}" addpg "${__PG}" template && \ /usr/sbin/svccfg -s "${__TARGET_FMRI}" setprop "${__PG}/manpath" = "astring:" "@NUT_MANDIR@" && \ /usr/sbin/svccfg -s "${__TARGET_FMRI}" setprop "${__PG}/section" = "astring:" "@MAN_SECTION_CMD_SYS@" && \ /usr/sbin/svccfg -s "${__TARGET_FMRI}" setprop "${__PG}/title" = "astring:" "${__DRV}" - [ $? = 0 ] && echo "OK" || { echo "FAILED to stash the service property group '${__PG}' for local docs">&2 ; return 1 ; } + [ $? = 0 ] && log_info "OK: Stashed the service property group '${__PG}' for '${__TARGET_FMRI}' for local docs" \ + || { log_error "FAILED to stash the service property group '${__PG}' for '${__TARGET_FMRI}' for local docs" ; return 1 ; } unset __DRV __PG __TARGET_FMRI - [ $? = 0 ] && echo "OK" || { echo "FAILED to stash the device doc links">&2 ; return 1 ; } + [ $? = 0 ] || { log_error "FAILED to stash the device doc links" ; return 1 ; } } smf_restart_upsd() { - echo "Reloading or restarting NUT data server to make sure it knows new configuration..." + log_info "Reloading or restarting NUT data server to make sure it knows new configuration..." /usr/sbin/svcadm enable "nut-server" 2>/dev/null /usr/sbin/svcadm clear "nut-server" 2>/dev/null /usr/sbin/svcadm refresh "nut-server" || \ /usr/sbin/svcadm restart "nut-server" } smf_restart_drv() { - echo "Reloading or restarting NUT driver instance '$1' to make sure it knows new configuration..." + log_info "Reloading or restarting NUT driver instance '$1' to make sure it knows new configuration..." /usr/sbin/svcadm enable "nut-driver:$1" 2>/dev/null /usr/sbin/svcadm clear "nut-driver:$1" 2>/dev/null /usr/sbin/svcadm refresh "nut-driver:$1" || \ @@ -1000,26 +1049,26 @@ systemd_registerInstance() { DEVICE="$1" SVCINST="$1" /bin/systemctl enable 'nut-driver@'"$DEVICE".service || \ - { if /bin/systemctl list-unit-files | awk '$2 == "masked" {print $1}' | grep -E "^nut-driver@${DEVICE}.service$"; then - echo "SKIP: Unit 'nut-driver@${DEVICE}.service' seems deliberately masked" >&2 + { if /bin/systemctl list-unit-files | awk '$2 == "masked" {print $1}' | $EGREP "^nut-driver@${DEVICE}.service$"; then + log_warn "SKIP: Unit 'nut-driver@${DEVICE}.service' seems deliberately masked" return 0 fi - echo "RETRY with validInstanceName of '$1'" >&2 - SVCINST="`systemd_validInstanceName "$1"`" && \ + log_warn "RETRY with validInstanceName of '$1'" + SVCINST="`systemd_validInstanceName \"$1\"`" && \ /bin/systemctl enable 'nut-driver@'"$SVCINST".service || { RES=$? - if /bin/systemctl list-unit-files | awk '$2 == "masked" {print $1}' | grep -E "^nut-driver@${SVCINST}.service$"; then - echo "SKIP: Unit 'nut-driver@${SVCINST}.service' seems deliberately masked" >&2 + if /bin/systemctl list-unit-files | awk '$2 == "masked" {print $1}' | $EGREP "^nut-driver@${SVCINST}.service$"; then + log_warn "SKIP: Unit 'nut-driver@${SVCINST}.service' seems deliberately masked" return 0 fi return $RES } } - echo "Enabled instance: 'nut-driver@$SVCINST' for NUT configuration section '$DEVICE'" >&2 + log_info "Enabled instance: 'nut-driver@$SVCINST' for NUT configuration section '$DEVICE'" DEPSVC="" DEPREQ="" - _MED="`upsconf_getMedia "$DEVICE"`" + _MED="`upsconf_getMedia \"$DEVICE\"`" case "${_MED}" in usb|usb,*) DEPSVC="$DEPSVC_USB_SYSTEMD" @@ -1036,18 +1085,19 @@ systemd_registerInstance() { drivers=*) ;; # Handled just below '') ;; # FIXME: modbus? sysfs like INA219? GPIO? Other local devices? - *) echo "WARNING: Unexpected NUT media type ignored: '${_MED}'" >&2 ;; + *) log_warn "Unexpected NUT media type ignored: '${_MED}'" ;; esac case "${_MED}" in drivers=*|*,drivers=*) OTHERLIST="`upsconf_list_dev_drv_socket_checksum`" || OTHERLIST="" + # Not double-quoting here to iterate the string tokens: for DEPDRV in `echo "${_MED}" | sed -e 's/^\(.*,d\|d\)rivers=//' -e 's/,/ /g'` ; do case "${DEPDRV}" in *-*) # May be "drivername-upsname", where either sub-string # may have dashes inside too; try to find the right one: - OTHERDEV="`echo "${OTHERLIST}" | awk '($3 == "'"${DEPDRV}"'") { print $1 }'`" && \ - OTHERDRV="`echo "${OTHERLIST}" | awk '($3 == "'"${DEPDRV}"'") { print $2 }'`" && \ + OTHERDEV="`echo \"${OTHERLIST}\" | awk '($3 == \"'\"${DEPDRV}\"'\") { print $1 }'`" && \ + OTHERDRV="`echo \"${OTHERLIST}\" | awk '($3 == \"'\"${DEPDRV}\"'\") { print $2 }'`" && \ [ x"${DEPDRV}" = x"${OTHERDRV}-${OTHERDEV}" ] && \ [ x"${OTHERDEV}" != x"${DEVICE}" ] \ || { @@ -1055,7 +1105,7 @@ systemd_registerInstance() { case "${DEPDRV}" in *-"${OTHERDEV}") OTHERDRV="'`echo "${OTHERLIST}" | awk '($1 == "'"${OTHERDEV}"'") { print $2 }'`'" || OTHERDRV="" - echo "WARNING: Device ${DEVICE} depends on another as '${DEPDRV}', but the other possible device '${OTHERDEV}' uses an unexpected driver: ${OTHERDRV}" >&2 + log_warn "Device ${DEVICE} depends on another as '${DEPDRV}', but the other possible device '${OTHERDEV}' uses an unexpected driver: ${OTHERDRV}" ;; esac done @@ -1065,19 +1115,19 @@ systemd_registerInstance() { ;; *) OTHERDEV="${DEPDRV}" ;; esac - if [ x"${OTHERDEV}" = x ] || ! (echo "$OTHERLIST" | grep -E "^${OTHERDEV}\t") >/dev/null ; then - echo "WARNING: Device ${DEVICE} depends on another ${DEPDRV} but we did not find a config section for it" >&2 - else + if [ x"${OTHERDEV}" != x ] && (echo "$OTHERLIST" | $EGREP "^${OTHERDEV}\t") >/dev/null ; then DEPSVC="$DEPSVC ${SVCNAME_SYSTEMD}${SVCNAMESEP_SYSTEMD}`systemd_validInstanceName ${OTHERDEV}`" + else + log_warn "Device ${DEVICE} depends on another ${DEPDRV} but we did not find a config section for it" fi done - if [ x"`echo "$DEPSVC" | tr -d ' '`" != x ] ; then DEPREQ="$DEPREQ_DRV_FULL_SYSTEMD" ; fi + if [ x"`echo \"$DEPSVC\" | tr -d ' '`" != x ] ; then DEPREQ="$DEPREQ_DRV_FULL_SYSTEMD" ; fi ;; esac if [ -n "$DEPSVC" ]; then [ -n "$DEPREQ" ] || DEPREQ="#Wants" - echo "Adding '$DEPREQ'+After dependency for '$SVCINST' on '$DEPSVC'..." + log_info "Adding '$DEPREQ'+After dependency for '$SVCINST' on '$DEPSVC'..." mkdir -p "${SYSTEMD_CONFPATH}/nut-driver@$SVCINST.service.d" && \ cat > "${SYSTEMD_CONFPATH}/nut-driver@$SVCINST.service.d/nut-driver-enumerator-generated.conf" <&2 + [ $? = 0 ] && log_info "OK: Defined the '$DEPREQ'+After dependency for '$SVCINST' on '$DEPSVC'" || log_error "FAILED to define the '$DEPREQ'+After dependency for '$SVCINST' on '$DEPSVC'" fi - systemd_setSavedMD5 "$SVCINST" "`upsconf_getSection_MD5 "$DEVICE"`" + systemd_setSavedMD5 "$SVCINST" "`upsconf_getSection_MD5 \"$DEVICE\"`" systemd_setSavedDeviceName "$SVCINST" "$DEVICE" systemd_setDocLink "$SVCINST" "$DEVICE" if [ "$AUTO_START" = yes ] ; then - systemd_refreshSupervizor || echo "WARNING: Somehow managed to fail systemd_refreshSupervizor()" >&2 + systemd_refreshSupervizor || log_warn "Somehow managed to fail systemd_refreshSupervizor()" $TIMEOUT_CMD $TIMEOUT_ARGS /bin/systemctl start --no-block 'nut-driver@'"$SVCINST".service || return - echo "Started instance: 'nut-driver@$SVCINST' for NUT configuration section '$DEVICE'" >&2 + log_info "Started instance: 'nut-driver@$SVCINST' for NUT configuration section '$DEVICE'" fi } systemd_unregisterInstance() { - echo "Removing instance: 'nut-driver@$1' ..." >&2 + log_info "Removing instance: 'nut-driver@$1' ..." $TIMEOUT_CMD $TIMEOUT_ARGS /bin/systemctl stop 'nut-driver@'"$1".service || \ $TIMEOUT_CMD $TIMEOUT_ARGS /bin/systemctl stop 'nut-driver@'"$1".service || \ $TIMEOUT_CMD $TIMEOUT_ARGS /bin/systemctl stop 'nut-driver@'"$1".service || false @@ -1114,7 +1164,7 @@ systemd_refreshSupervizor() { /bin/systemctl daemon-reload } systemd_listInstances_raw() { - /bin/systemctl show --all 'nut-driver@*' -p Id | grep -E '=nut-driver' | sed 's,^Id=,,' + /bin/systemctl show --all 'nut-driver@*' -p Id | $EGREP '=nut-driver' | sed 's,^Id=,,' } systemd_listInstances() { systemd_listInstances_raw | sed -e 's/^.*@//' -e 's/\.service$//' | sort -k1n -k1 @@ -1125,18 +1175,18 @@ systemd_getSavedMD5() { [ -n "$1" ] || PROP="SECTION_CHECKSUM_GLOBAL" PROPFILE="${SYSTEMD_CONFPATH}/nut-driver@$1.service.d/nut-driver-enumerator-generated-checksum.conf" [ -s "${PROPFILE}" ] \ - && grep "Environment='$PROP=" "${PROPFILE}" | sed -e "s,^Environment='$PROP=,," -e "s,'\$,," \ - || { echo "Did not find '${PROPFILE}' with a $PROP" ; return 1; } + && $GREP "Environment='$PROP=" "${PROPFILE}" | sed -e "s,^Environment='$PROP=,," -e "s,'\$,," \ + || { log_warn "Did not find '${PROPFILE}' with a $PROP" ; return 1; } } systemd_findSavedDeviceName() { # Returns long service instance name which has DEVICE=="$1" # For empty "$1" returns a list of all recorded "SVCDEVICE" if [ -z "$1" ]; then - grep -H "Environment='DEVICE=" \ + $GREP -H "Environment='DEVICE=" \ "${SYSTEMD_CONFPATH}"/nut-driver@*.service.d/nut-driver-enumerator-generated-devicename.conf \ | sed 's|^'"${SYSTEMD_CONFPATH}"'/\(nut-driver@[^/]*\.service\)\.d/.*DEVICE='"[\"']*\([^\"']*\)[\"']*"'$|\1\t\2|' else - grep -E -H "Environment='DEVICE=($1|\"$1\")'" \ + $EGREP -H "Environment='DEVICE=($1|\"$1\")'" \ "${SYSTEMD_CONFPATH}"/nut-driver@*.service.d/nut-driver-enumerator-generated-devicename.conf \ | sed 's|^'"${SYSTEMD_CONFPATH}"'/\(nut-driver@[^/]*\.service\)\.d/.*$|\1|' fi @@ -1149,56 +1199,62 @@ systemd_getSavedDeviceName() { PROP="DEVICE" PROPFILE="${SYSTEMD_CONFPATH}/nut-driver@$1.service.d/nut-driver-enumerator-generated-devicename.conf" [ -s "${PROPFILE}" ] \ - && grep "Environment='$PROP=" "${PROPFILE}" | sed -e "s,^Environment='$PROP=,," -e "s,'\$,," -e 's,^"\(.*\)"$,\1,' \ - || { echo "Did not find '${PROPFILE}' with a $PROP" ; return 1; } + && $GREP "Environment='$PROP=" "${PROPFILE}" | sed -e "s,^Environment='$PROP=,," -e "s,'\$,," -e 's,^"\(.*\)"$,\1,' \ + || { log_warn "Did not find '${PROPFILE}' with a $PROP" ; return 1; } } systemd_setSavedDeviceName() { # Save device (config section) name $2 into service instance $1 [ -n "$1" ] || return # No-op for global section + log_info "Saving device (config section) name $2 into service instance $1..." PROPFILE="${SYSTEMD_CONFPATH}/nut-driver@$1.service.d/nut-driver-enumerator-generated-devicename.conf" mkdir -p "${SYSTEMD_CONFPATH}/nut-driver@$1.service.d" && \ cat > "${PROPFILE}" << EOF [Service] Environment='DEVICE="$2"' EOF - [ $? = 0 ] && echo "OK" || { echo "FAILED to stash the device name">&2 ; return 1 ; } + [ $? = 0 ] && log_info "OK: Stashed the device name" \ + || { log_error "FAILED to stash the device name" ; return 1 ; } } systemd_setDocLink() { # Save documentation links for driver of device (config section) named $2 # into service instance $1 [ -n "$1" ] || return # No-op for global section + log_info "Saving documentation links for driver of device (config section) named $2 into service instance $1..." PROPFILE="${SYSTEMD_CONFPATH}/nut-driver@$1.service.d/nut-driver-enumerator-generated-doclink.conf" mkdir -p "${SYSTEMD_CONFPATH}/nut-driver@$1.service.d" && \ - __DRV="`upsconf_getDriver "$2"`" + __DRV="`upsconf_getDriver \"$2\"`" cat > "${PROPFILE}" << EOF [Unit] Documentation=man:${__DRV}(@MAN_SECTION_CMD_SYS@) Documentation=${NUT_WEBSITE_BASE}/docs/man/${__DRV}.html EOF unset __DRV - [ $? = 0 ] && echo "OK" || { echo "FAILED to stash the device name">&2 ; return 1 ; } + [ $? = 0 ] && log_info "OK: Saved documentation links for driver of device (config section) named $2 into service instance $1" \ + || { log_error "FAILED to save the documentation links" ; return 1 ; } } systemd_setSavedMD5() { # Save checksum value $2 into service instance $1 PROP="SECTION_CHECKSUM" [ -n "$1" ] || PROP="SECTION_CHECKSUM_GLOBAL" + log_info "Saving checksum value $2 into service instance $1..." PROPFILE="${SYSTEMD_CONFPATH}/nut-driver@$1.service.d/nut-driver-enumerator-generated-checksum.conf" mkdir -p "${SYSTEMD_CONFPATH}/nut-driver@$1.service.d" && \ cat > "${PROPFILE}" << EOF [Service] Environment='$PROP=$2' EOF - [ $? = 0 ] && echo "OK" || { echo "FAILED to stash the checksum">&2 ; return 1 ; } + [ $? = 0 ] && log_info "OK: Stashed the checksum" \ + || { log_error "FAILED to stash the checksum" ; return 1 ; } } systemd_restart_upsd() { # Do not restart/reload if not already running - case "`/bin/systemctl is-active "nut-server"`" in + case "`/bin/systemctl is-active \"nut-server\"`" in active|unknown) ;; # unknown meant "starting" in our testing... - failed) echo "Note: nut-server unit was 'failed' - not disabled by user, so (re)starting it (probably had no config initially)" >&2 ;; + failed) log_warn "Note: nut-server unit was 'failed' - not disabled by user, so (re)starting it (probably had no config initially)" ;; *) return 0 ;; esac - echo "Reloading or restarting NUT data server to make sure it knows new configuration..." + log_info "Reloading or restarting NUT data server to make sure it knows new configuration..." # Note: reload is a better way to go about this, so the # data service is not interrupted by re-initialization # of the daemon. But systemd/systemctl sometimes stalls... @@ -1209,12 +1265,12 @@ systemd_restart_upsd() { systemd_restart_drv() { # Do not restart/reload if not already running - case "`/bin/systemctl is-active "nut-driver@$1"`" in + case "`/bin/systemctl is-active \"nut-driver@$1\"`" in active|unknown) ;; *) return 0 ;; esac - echo "Reloading or restarting NUT driver instance '$1' to make sure it knows new configuration..." + log_info "Reloading or restarting NUT driver instance '$1' to make sure it knows new configuration..." # Full restart, e.g. in case we changed the user= in configs # however let "reload" try, in case we changed something that @@ -1237,15 +1293,15 @@ upslist_normalizeFile_filter() { # are dropped. Note that brackets with spaces inside, and brackets # that do not start the non-whitespace payload of the line, are not # sections. - grep -E -v '(^$|^#)' | \ + $EGREP -v '(^$|^#)' | \ sed -e 's,^['"$TABCHAR"'\ ]*,,' \ -e 's,^\#.*$,,' \ -e 's,['"$TABCHAR"'\ ]*$,,' \ -e 's,^\([^=\ '"$TABCHAR"']*\)['"$TABCHAR"'\ ]*=['"$TABCHAR"'\ ]*,\1=,g' \ -e 's,=\"\([^\ '"$TABCHAR"']*\)\"$,=\1,' \ -e 's,^\(\[[^]'"$TABCHAR"'\ ]*\]\)['"$TABCHAR"'\ ]*\(#.*\)*$,\1,' \ - | grep -E -v '^$' \ - | grep -E '([\[\=]|^[^ '"$TABCHAR"']*$|^[^ '"$TABCHAR"']*[ '"$TABCHAR"']*#.*$)' + | $EGREP -v '^$' \ + | $EGREP '([\[\=]|^[^ '"$TABCHAR"']*$|^[^ '"$TABCHAR"']*[ '"$TABCHAR"']*#.*$)' } upslist_normalizeFile() { @@ -1259,11 +1315,12 @@ upslist_normalizeFile() { UPSCONF_DATA_SDP="" if [ -n "$UPSCONF" ] && [ -f "$UPSCONF" ] && [ -r "$UPSCONF" ]; then [ ! -s "$UPSCONF" ] \ - && echo "WARNING: The '$UPSCONF' file exists but is empty" >&2 \ + && log_warn "The '$UPSCONF' file exists but is empty" \ && return 0 # Ok to continue - we may end up removing all instances else - echo "FATAL: The '$UPSCONF' file does not exist or is not readable" >&2 + # TOTHINK: log_fatal() right away? + log_error "The '$UPSCONF' file does not exist or is not readable" return 2 fi @@ -1272,10 +1329,10 @@ upslist_normalizeFile() { # for faster parsing when determining driver-required media etc. UPSCONF_DATA="$(upslist_normalizeFile_filter < "$UPSCONF")" \ && [ "${#UPSCONF_DATA}" != 0 ] \ - && UPSCONF_DATA_SDP="`grep -E '^(\[.*\]|driver=|port=)' << EOF + && UPSCONF_DATA_SDP="`$EGREP '^(\[.*\]|driver=|port=)' << EOF $UPSCONF_DATA EOF`" \ - || { echo "Error reading the '$UPSCONF' file or it does not declare any device configurations: nothing left after normalization" >&2 + || { log_error "Could not read the '$UPSCONF' file or it does not declare any device configurations: nothing left after normalization" UPSCONF_DATA="" UPSCONF_DATA_SDP="" } @@ -1303,10 +1360,10 @@ upslist_readFile() { if [ "${#UPSCONF_DATA}" != 0 ] ; then # Note that section-name brackets should contain a single token - UPSLIST_FILE="$(echo "$UPSCONF_DATA_SDP" | grep -E '^\[[^'"$TABCHAR"'\ ]*\]$' | sed 's,^\[\(.*\)\]$,\1,' | sort -k1n -k1)" \ + UPSLIST_FILE="$(echo "$UPSCONF_DATA_SDP" | $EGREP '^\[[^'"$TABCHAR"'\ ]*\]$' | sed 's,^\[\(.*\)\]$,\1,' | sort -k1n -k1)" \ || UPSLIST_FILE="" if [ "${#UPSLIST_FILE}" = 0 ] ; then - echo "Error reading the '$UPSCONF' file or it does not declare any device configurations: no section declarations in parsed normalized contents" >&2 + log_error "Could not read the '$UPSCONF' file or it does not declare any device configurations: no section declarations in parsed normalized contents" fi fi # Ok to continue with empty results - we may end up removing all instances @@ -1325,7 +1382,7 @@ upslist_readSvcs() { if [ "${#UPSLIST_SVCS}" = 0 ] && [ "$1" != "-" ] ; then EXPLAIN="" [ -z "$1" ] || EXPLAIN=" - $1" - echo "Error reading the list of ${SERVICE_FRAMEWORK-} service instances for UPS drivers, or none are defined${EXPLAIN}" >&2 + log_error "Could not read the list of ${SERVICE_FRAMEWORK-} service instances for UPS drivers, or none are defined${EXPLAIN}" # Ok to continue - we may end up defining new instances fi } @@ -1343,8 +1400,10 @@ upslist_addSvcs() { # recreating it here, since any data could change including the dependency # list, etc. for UPSF in $UPSLIST_FILE ; do - if ! common_isRegistered "$UPSF" ; then - echo "Adding new ${SERVICE_FRAMEWORK} service instance for power device [${UPSF}]..." >&2 + if common_isRegistered "$UPSF" ; then + true + else + log_info "Adding new ${SERVICE_FRAMEWORK} service instance for power device [${UPSF}]..." $hook_registerInstance "$UPSF" fi done @@ -1352,8 +1411,10 @@ upslist_addSvcs() { upslist_delSvcs() { for UPSS in $UPSLIST_SVCS ; do - if ! common_isFiled "$UPSS" ; then - echo "Dropping old ${SERVICE_FRAMEWORK} service instance for power device [${UPSS}] which is no longer in config file..." >&2 + if common_isFiled "$UPSS" ; then + true + else + log_info "Dropping old ${SERVICE_FRAMEWORK} service instance for power device [${UPSS}] which is no longer in config file..." $hook_unregisterInstance "$UPSS" fi done @@ -1376,14 +1437,14 @@ RECONFIGURATION_SIGNALS="1" TERMINATE_ASAP=false trap_handle_interruption_exit() { # Handle SIGINT, SIGQUIT, SIGTERM with a message - echo "`date -u` : Received an interruption signal, terminating the script now" >&2 - exit 0 + log_fatal 0 "Received an interruption signal, terminating the script now" } trap_handle_interruption() { # Handle SIGINT, SIGQUIT, SIGTERM gracefully - tell the main # iteration to complete and just then abort - echo "`date -u` : Received an interruption signal, will terminate the script after ending the main routine. Repeat the signal if urgent!" >&2 + # Sub-shell "log_fatal" to get a standard message but not exit right now: + log_fatal 0 "Received an interruption signal, will terminate the script after ending the main routine. Repeat the signal if urgent!" || true TERMINATE_ASAP=true trap 'trap_handle_interruption_exit' $TERMINATION_SIGNALS } @@ -1405,7 +1466,7 @@ nut_driver_enumerator_main() { # device sections yet) we do not want to spend time and log storage to # parse again and complain again. RESTART_ALL=no - AVOID_REPARSE=yes upssvcconf_checksum_unchanged "" || { echo "`date -u` : Detected changes in global section of '$UPSCONF', will restart all drivers"; RESTART_ALL=yes; } + AVOID_REPARSE=yes upssvcconf_checksum_unchanged "" || { log_info "Detected changes in global section of '$UPSCONF', will restart all drivers"; RESTART_ALL=yes; } # Quickly exit if there's nothing to do (both lists empty or equal); note # the lists are pre-sorted. Otherwise a non-zero exit will be done below. @@ -1417,12 +1478,12 @@ nut_driver_enumerator_main() { # We can only exit quickly if both there are no changed sections and no # new or removed sections since last run. { [ -z "$UPSLIST_FILE" -a -z "$UPSLIST_SVCS" ] || { \ - NEW_CHECKSUM="`upslist_checksums_unchanged "$UPSLIST_FILE" "$UPSLIST_SVCS"`" \ + NEW_CHECKSUM="`upslist_checksums_unchanged \"$UPSLIST_FILE\" \"$UPSLIST_SVCS\"`" \ && [ "${#NEW_CHECKSUM}" = 0 ] \ && upslist_equals "$UPSLIST_FILE" "$UPSLIST_SVCS" ; \ } ; } \ && if [ -z "$DAEMON_SLEEP" -o "${VERBOSE_LOOP}" = yes ] ; then \ - echo "`date -u` : OK: No changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF'" ; \ + log_info "OK: No changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF'" ; \ fi \ && [ "$RESTART_ALL" = no ] && return 0 @@ -1430,38 +1491,38 @@ nut_driver_enumerator_main() { # compared to older runs (stashed in service instance configurations). if [ "${#NEW_CHECKSUM}" = 0 ]; then if [ "${VERBOSE_LOOP}" = yes ] ; then - echo "`date -u` : No changes to reconcile between *contents* of ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF', but the *list* of instances vs. devices seems to have changed" + log_info "No changes to reconcile between *contents* of ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF', but the *list* of instances vs. devices seems to have changed" fi else if [ "${VERBOSE_LOOP}" = yes ] ; then - echo "`date -u` : Got some changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF', content checksums changed for: `echo ${NEW_CHECKSUM} | tr '\n' ' '`" + log_info "Got some changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF', content checksums changed for: `echo ${NEW_CHECKSUM} | tr '\n' ' '`" fi for UPSS in $NEW_CHECKSUM ; do # NOTE: Pedantically, UPSS is a service instance name, # and may be the MD5-normalized version in certain cases. # For some operations below we need the original ups.conf # section name for the device - the CURR_DEV value. - CURR_DEV="`USE_SAVEDINST=true get_device_for_service "${UPSS}"`" && [ "${#CURR_DEV}" -gt 0 ] || CURR_DEV="${UPSS}" - CURR_DRV="`upsconf_getDriver "${CURR_DEV}"`" || CURR_DRV="" + CURR_DEV="`USE_SAVEDINST=true get_device_for_service \"${UPSS}\"`" && [ "${#CURR_DEV}" -gt 0 ] || CURR_DEV="${UPSS}" + CURR_DRV="`upsconf_getDriver \"${CURR_DEV}\"`" || CURR_DRV="" DO_UNREGISTER=yes if [ -n "$CURR_DRV" ] ; then # If reload is handled and does not complain, # we are OK to proceed without re-defining # and restarting the driver service instance. if [ "${VERBOSE_LOOP}" = yes ] ; then - echo "`date -u` : Reloading ${SERVICE_FRAMEWORK} service instance '${UPSS}' whose section '${CURR_DEV}' in config file has changed: calling driver program '${CURR_DRV}' for the low-level work..." >&2 + log_info "Reloading ${SERVICE_FRAMEWORK} service instance '${UPSS}' whose section '${CURR_DEV}' in config file has changed: calling driver program '${CURR_DRV}' for the low-level work..." fi "@DRVPATH@/$CURR_DRV" -a "${CURR_DEV}" -c reload-or-error >/dev/null 2>/dev/null \ || { if [ "${VERBOSE_LOOP}" = yes ] ; then sleep 1; "@DRVPATH@/$CURR_DRV" -a "${CURR_DEV}" -c reload-or-error >&2 ; fi; } \ && DO_UNREGISTER=no fi if [ "$DO_UNREGISTER" = yes ] ; then - echo "Dropping old ${SERVICE_FRAMEWORK} service instance '${UPSS}' whose section '${CURR_DEV}' in config file has changed..." >&2 + log_info "Dropping old ${SERVICE_FRAMEWORK} service instance '${UPSS}' whose section '${CURR_DEV}' in config file has changed..." $hook_unregisterInstance "$UPSS" # Re-registration to reconcile below should set the "saved" values else - echo "Keeping ${SERVICE_FRAMEWORK} service instance '${UPSS}' whose section '${CURR_DEV}' in config file has changed: live reload sufficed. Saving updated info into service properties." >&2 - $hook_setSavedMD5 "$UPSS" "`upsconf_getSection_MD5 "${CURR_DEV}"`" + log_info "Keeping ${SERVICE_FRAMEWORK} service instance '${UPSS}' whose section '${CURR_DEV}' in config file has changed: live reload sufficed. Saving updated info into service properties." + $hook_setSavedMD5 "$UPSS" "`upsconf_getSection_MD5 \"${CURR_DEV}\"`" # TOTHINK: This is already there, else we redefine units for bigger discrepancies? # $hook_setSavedDeviceName "$UPSS" "${CURR_DEV}" fi @@ -1494,14 +1555,14 @@ nut_driver_enumerator_main() { upslist_readSvcs if [ "${#UPSLIST_SVCS}" != 0 ] ; then - echo "=== The currently defined service instances are:" + log_info "The currently defined service instances are:" echo "$UPSLIST_SVCS" - fi + fi >&2 if [ "${#UPSLIST_FILE}" != 0 ] ; then - echo "=== The currently defined configurations in '$UPSCONF' are:" + log_info "The currently defined configurations in '$UPSCONF' are:" echo "$UPSLIST_FILE" - fi + fi >&2 # We had some changes to the config file; upsd must be made aware if [ "$AUTO_START" = yes ] ; then @@ -1518,12 +1579,12 @@ nut_driver_enumerator_main() { # NOTE: Check this at the last moment to minimize # the chance of still not noticing the change made # at just the wrong moment. - UPSCONF_CHECKSUM_END="`calc_md5_file "$UPSCONF"`" || true + UPSCONF_CHECKSUM_END="`calc_md5_file \"$UPSCONF\"`" || true if [ "$UPSCONF_CHECKSUM_END" != "$UPSCONF_CHECKSUM_START" ] ; then # NOTE: even if daemonized, the sleep between iterations # can be configured into an uncomfortably long lag, so # we should re-sync the system config in any case. - echo "`date -u` : '$UPSCONF' changed while $0 $* was processing its older contents; re-running the script to pick up the late-coming changes" + log_info "'$UPSCONF' changed while $0 $* was processing its older contents; re-running the script to pick up the late-coming changes" # Make sure the cycle does not repeat itself due to diffs # from an ages-old state of the file from when we started. UPSCONF_CHECKSUM_START="$UPSCONF_CHECKSUM_END" @@ -1533,7 +1594,7 @@ nut_driver_enumerator_main() { fi if [ "$UPSLIST_EQ_RES" = 0 ] ; then - echo "`date -u` : OK: No more changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF'" + log_info "OK: No more changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF'" [ "${REPORT_RESTART_42-}" = no ] && return 0 || return 42 fi return 13 @@ -1548,7 +1609,7 @@ nut_driver_enumerator_full_reconfigure() { if [ "${#UPSLIST_SVCS}" != 0 ]; then for UPSS in $UPSLIST_SVCS ; do - echo "Dropping old ${SERVICE_FRAMEWORK} service instance for power device [${UPSS}] to reconfigure the service unit..." >&2 + log_info "Dropping old ${SERVICE_FRAMEWORK} service instance for power device [${UPSS}] to reconfigure the service unit..." $hook_unregisterInstance "$UPSS" done upslist_readSvcs "after dropping" @@ -1566,14 +1627,14 @@ nut_driver_enumerator_full_reconfigure() { $hook_refreshSupervizor if [ "${#UPSLIST_SVCS}" != 0 ] ; then - echo "=== The currently defined service instances are:" + log_info "The currently defined service instances are:" echo "$UPSLIST_SVCS" - fi + fi >&2 if [ "${#UPSLIST_FILE}" != 0 ] ; then - echo "=== The currently defined configurations in '$UPSCONF' are:" + log_info "The currently defined configurations in '$UPSCONF' are:" echo "$UPSLIST_FILE" - fi + fi >&2 # We had some changes to the config file; upsd must be made aware if [ "$AUTO_START" = yes ] ; then @@ -1590,15 +1651,15 @@ nut_driver_enumerator_full_reconfigure() { # NOTE: Check this at the last moment to minimize # the chance of still not noticing the change made # at just the wrong moment. - UPSCONF_CHECKSUM_END="`calc_md5_file "$UPSCONF"`" || true + UPSCONF_CHECKSUM_END="`calc_md5_file \"$UPSCONF\"`" || true if [ "$UPSCONF_CHECKSUM_END" != "$UPSCONF_CHECKSUM_START" ] ; then - echo "`date -u` : '$UPSCONF' changed while $0 $* was processing its older contents; re-running the script to pick up and reconcile the late-coming changes" + log_info "'$UPSCONF' changed while $0 $* was processing its older contents; re-running the script to pick up and reconcile the late-coming changes" nut_driver_enumerator_main ; return $? # The "main" routine will do REPORT_RESTART_42 logic too fi if [ "$UPSLIST_EQ_RES" = 0 ] ; then - echo "`date -u` : OK: No more changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF'" + log_info "OK: No more changes to reconcile between ${SERVICE_FRAMEWORK} service instances and device configurations in '$UPSCONF'" [ "${REPORT_RESTART_42-}" = no ] && return 0 || return 42 fi @@ -1608,15 +1669,15 @@ nut_driver_enumerator_full_reconfigure() { list_services_for_devices() { FINAL_RES=0 upslist_readFile_once && [ "${#UPSLIST_FILE}" != 0 ] \ - || { echo "No devices detected in '$UPSCONF'" >&2 ; return 1 ; } + || { log_warn "No devices detected in '$UPSCONF'" ; return 1 ; } upslist_readSvcs "by user request" && [ "${#UPSLIST_SVCS}" != 0 ] \ - || { echo "No service instances detected" >&2 ; return 1; } + || { log_warn "No service instances detected" ; return 1; } UPSLIST_SVCS_RAW="`$hook_listInstances_raw`" && [ "${#UPSLIST_SVCS_RAW}" != 0 ] \ - || { echo "No service units detected" >&2 ; return 1; } + || { log_warn "No service units detected" ; return 1; } for DEV in $UPSLIST_FILE ; do - vINST="`$hook_validInstanceName "$DEV"`" - vUNITD="`$hook_validFullUnitName "$DEV"`" - vUNITI="`$hook_validFullUnitName "$vINST"`" + vINST="`$hook_validInstanceName \"$DEV\"`" + vUNITD="`$hook_validFullUnitName \"$DEV\"`" + vUNITI="`$hook_validFullUnitName \"$vINST\"`" # First pass over simple verbatim names for INST in $UPSLIST_SVCS ; do if [ "$INST" = "$DEV" ] ; then @@ -1638,7 +1699,7 @@ list_services_for_devices() { done fi done - echo "WARNING: no service instances detected that match device '$DEV'" >&2 + log_warn "No service instances detected that match device '$DEV'" FINAL_RES=1 done return $FINAL_RES @@ -1655,29 +1716,29 @@ list_services_for_devices_once() { # Pre-cache config file data, if nobody read it yet, # and keep in main script context for reuse (no subshell) upslist_readFile_once && \ - SVCS_DEVS_LIST="`list_services_for_devices "$@"`" || return $? + SVCS_DEVS_LIST="`list_services_for_devices \"$@\"`" || return $? } get_device_for_service() { - [ -z "$1" ] && echo "Service (instance) name argument required" >&2 && return 1 + [ -z "$1" ] && { log_error "Service (instance) name argument required"; return 1; } # Instance name can be a hash or "native" NUT section name - SVC="`$hook_validInstanceSuffixName "$1"`" && [ -n "$SVC" ] \ - || { echo "Error getting SVC name from the inputs" >&2 ; return 1; } + SVC="`$hook_validInstanceSuffixName \"$1\"`" && [ -n "$SVC" ] \ + || { log_error "Error getting SVC name from the inputs"; return 1; } # Reading the config is too expensive to do for every # driver management attempt when there are many devices if [ "${USE_SAVEDINST-}" != false ]; then # Try to use last-stashed values from service properties first # (NOTE: saved value is assumed to be valid if present) - SAVEDINST="`$hook_getSavedDeviceName "$SVC"`" || SAVEDINST="" + SAVEDINST="`$hook_getSavedDeviceName \"$SVC\"`" || SAVEDINST="" [ "${#SAVEDINST}" = 0 ] || { echo "$SAVEDINST" ; return 0 ; } fi case "$SVC" in MD5_*) ;; # fall through to the bulk of code *) upslist_readFile_once || return $? - echo "$UPSLIST_FILE" | grep -E "^$SVC\$" + echo "$UPSLIST_FILE" | $EGREP "^$SVC\$" return $? ;; esac @@ -1686,36 +1747,36 @@ get_device_for_service() { FINAL_RES=0 list_services_for_devices_once && [ "${#SVCS_DEVS_LIST}" != 0 ] || FINAL_RES=$? if [ "$FINAL_RES" = 0 ]; then - echo "$SVCS_DEVS_LIST" | grep "$SVC" | ( \ + echo "$SVCS_DEVS_LIST" | $GREP "$SVC" | ( \ while read _SVC _DEV ; do - _SVC="`$hook_validInstanceSuffixName "${_SVC}"`" || exit + _SVC="`$hook_validInstanceSuffixName \"${_SVC}\"`" || exit [ "${_SVC}" = "${SVC}" ] && echo "${_DEV}" && exit 0 done ; exit 1 ) && return 0 fi - echo "No service instance '$1' was detected that matches a NUT device" >&2 + log_warn "No service instance '$1' was detected that matches a NUT device" return 1 } get_service_for_device() { DEV="$1" - [ -z "$DEV" ] && echo "Device name argument required" >&2 && return 1 + [ -z "$DEV" ] && { log_error "Device name argument required"; return 1; } # Cheap check in service instance metadata, if saved # (NOTE: saved value is assumed to be valid if present) if [ "${USE_SAVEDSVC-}" != false ]; then - SAVEDSVC="`$hook_findSavedDeviceName "$DEV"`" || SAVEDSVC="" + SAVEDSVC="`$hook_findSavedDeviceName \"$DEV\"`" || SAVEDSVC="" [ "${#SAVEDSVC}" = 0 ] || { echo "$SAVEDSVC" ; return 0 ; } fi # Trawl all the data we have... # TODO: Reorder to avoid extra parsing if we have an early hit? upslist_readSvcs "by user request" && [ "${#UPSLIST_SVCS}" != 0 ] \ - || { echo "No service instances detected" >&2 ; return 1; } + || { log_warn "No service instances detected" ; return 1; } UPSLIST_SVCS_RAW="`$hook_listInstances_raw`" && [ "${#UPSLIST_SVCS_RAW}" != 0 ] \ - || { echo "No service units detected" >&2 ; return 1; } - vINST="`$hook_validInstanceName "$DEV"`" - vUNITD="`$hook_validFullUnitName "$DEV"`" - vUNITI="`$hook_validFullUnitName "$vINST"`" + || { log_warn "No service units detected" ; return 1; } + vINST="`$hook_validInstanceName \"$DEV\"`" + vUNITD="`$hook_validFullUnitName \"$DEV\"`" + vUNITI="`$hook_validFullUnitName \"$vINST\"`" # First pass over simple verbatim names for INST in $UPSLIST_SVCS ; do @@ -1739,7 +1800,7 @@ get_service_for_device() { done fi done - echo "No service instances detected that match device '$DEV'" >&2 + log_warn "No service instances detected that match device '$DEV'" return 1 } @@ -1765,17 +1826,17 @@ update_upslist_savednames_find_missing() { # and write the values learned from mapping above _MISSING_RES=0 for SVCINST in $SVCINSTS_NO_DEVICE ; do - _DEV="`printf '%s\n' "$SVCS_DEVS_LIST" | awk '( \$1 == "'"${SVCINST}"'" ) {print \$NF}'`" - echo "Service instance '$SVCINST' did not have a device recorded into properties, setting to '${_DEV}'" - [ -n "${_DEV}" ] || { echo "The device name value for '$SVCINST' is empty, skipping" >&2 ; _MISSING_RES=1 ; continue ; } - $hook_setSavedDeviceName "`$hook_validInstanceSuffixName "$SVCINST"`" "${_DEV}" || _MISSING_RES=$? + _DEV="`printf '%s\n' \"$SVCS_DEVS_LIST\" | awk '( \$1 == \"'\"${SVCINST}\"'\" ) {print \$NF}'`" + log_warn "Service instance '$SVCINST' did not have a device recorded into properties, setting to '${_DEV}'" + [ -n "${_DEV}" ] || { log_warn "The device name value for '$SVCINST' is empty, skipping" ; _MISSING_RES=1 ; continue ; } + $hook_setSavedDeviceName "`$hook_validInstanceSuffixName \"$SVCINST\"`" "${_DEV}" || _MISSING_RES=$? done return ${_MISSING_RES} } RECONFIGURE_ASAP=false trap_handle_hup_main() { - echo "`date -u` : Received a HUP during processing, scheduling reconfiguration to repeat ASAP (after the current iteration is done)" >&2 + log_info "Received a HUP during processing, scheduling reconfiguration to repeat ASAP (after the current iteration is done)" # Note: in the worst case the reconfig would run twice; # we do not update UPSCONF_CHECKSUM_START in this case # to avoid corrupting decisions of the currently running @@ -1784,10 +1845,10 @@ trap_handle_hup_main() { } trap_handle_hup_sleep() { - echo "`date -u` : Received a HUP in my sleep, reprocessing configs right now!" >&2 + log_info "Received a HUP in my sleep, reprocessing configs right now!" # Avoid re-parsing main twice, though don't recalculate # checksums needlessly on every loop cycle, either... - UPSCONF_CHECKSUM_START="`calc_md5_file "$UPSCONF"`" || true + UPSCONF_CHECKSUM_START="`calc_md5_file \"$UPSCONF\"`" || true RECONFIGURE_ASAP=true if [ -n "$1" ] ; then # Kill the sleeper PID @@ -1798,7 +1859,7 @@ trap_handle_hup_sleep() { daemonize() { # Note: do not further sub-shell this routine, so systemd # is not confused whom to signal. - echo "`date -u` : Daemonizing $0 with config re-evaluation frequency $DAEMON_SLEEP" >&2 + log_info "Daemonizing $0 with config re-evaluation frequency $DAEMON_SLEEP" # Support (SIG)HUP == signal code 1 to quickly reconfigure, # e.g. to request it while the sleep is happening or while @@ -1821,12 +1882,12 @@ daemonize() { while nut_driver_enumerator_main ; do trap 'trap_handle_interruption_exit' $TERMINATION_SIGNALS if $TERMINATE_ASAP ; then - echo "`date -u` : Trapped a SIGTERM/SIGINT/SIGQUIT during last run of nut_driver_enumerator_main, terminating gracefully now" >&2 + log_info "Trapped a SIGTERM/SIGINT/SIGQUIT during last run of nut_driver_enumerator_main, terminating gracefully now" exit 0 fi if $RECONFIGURE_ASAP ; then - echo "`date -u` : Trapped a SIGHUP during last run of nut_driver_enumerator_main, repeating reconfiguration quickly" >&2 + log_info "Trapped a SIGHUP during last run of nut_driver_enumerator_main, repeating reconfiguration quickly" else sleep $DAEMON_SLEEP & trap "trap_handle_hup_sleep $!" $RECONFIGURATION_SIGNALS @@ -1840,7 +1901,7 @@ daemonize() { # Save the checksum of ups.conf as early as possible, # to test in the end that it is still the same file. -UPSCONF_CHECKSUM_START="`calc_md5_file "$UPSCONF"`" || true +UPSCONF_CHECKSUM_START="`calc_md5_file \"$UPSCONF\"`" || true # By default, update wrapping of devices into services if [ $# = 0 ]; then @@ -1852,8 +1913,8 @@ if [ $# = 1 ] ; then [ -n "$DAEMON_SLEEP" ] || DAEMON_SLEEP=60 # Note: Not all shells have 'case ... ;&' support case "$1" in - --daemon=*) DAEMON_SLEEP="`echo "$1" | sed 's,^--daemon=,,'`" ;; - --daemon-after=*) DAEMON_SLEEP="`echo "$1" | sed 's,^--daemon-after=,,'`" ;; + --daemon=*) DAEMON_SLEEP="`echo \"$1\" | sed 's,^--daemon=,,'`" ;; + --daemon-after=*) DAEMON_SLEEP="`echo \"$1\" | sed 's,^--daemon-after=,,'`" ;; esac case "$1" in --daemon-after|--daemon-after=*) @@ -1935,31 +1996,34 @@ while [ $# -gt 0 ]; do --list-devices) upslist_readFile_once && \ if [ "${#UPSLIST_FILE}" != 0 ] ; then - echo "=== The currently defined configurations in '$UPSCONF' are:" >&2 + log_info "The currently defined configurations in '$UPSCONF' are:" + # echo this to stdout: echo "$UPSLIST_FILE" exit 0 fi - echo "No devices detected in '$UPSCONF'" >&2 + log_warn "No devices detected in '$UPSCONF'" exit 1 ;; --list-services) UPSLIST_SVCS_RAW="`$hook_listInstances_raw`" && \ if [ "${#UPSLIST_SVCS_RAW}" != 0 ] ; then - echo "=== The currently defined service units are:" >&2 + log_info "The currently defined service units are:" + # echo this to stdout: echo "$UPSLIST_SVCS_RAW" exit 0 fi - echo "No service units detected" >&2 + log_warn "No service units detected" exit 1 ;; --list-instances) upslist_readSvcs "by user request" && \ if [ "${#UPSLIST_SVCS}" != 0 ] ; then - echo "=== The currently defined service instances are:" >&2 + log_info "The currently defined service instances are:" + # echo this to stdout: echo "$UPSLIST_SVCS" exit 0 fi - echo "No service instances detected" >&2 + log_warn "No service instances detected" exit 1 ;; --get-service-for-device) @@ -1980,28 +2044,29 @@ while [ $# -gt 0 ]; do --show-configs|--show-device-configs|--show-all-configs|--show-all-device-configs) RES=0 upslist_readFile_once || RES=$? - [ "$RES" != 0 ] && { echo "ERROR: upslist_readFile_once () failed with code $RES" >&2; exit $RES; } + [ "$RES" != 0 ] && log_fatal $RES "upslist_readFile_once() failed with code $RES" [ "${#UPSLIST_FILE}" != 0 ] \ - || { echo "WARNING: No devices detected in '$UPSCONF'" >&2 ; RES=1 ; } + || { log_warn "No devices detected in '$UPSCONF'" ; RES=1 ; } + # echo this to stdout: echo "$UPSCONF_DATA" exit $RES ;; --show-config|--show-device-config) - [ -z "$2" ] && echo "WARNING: Device name argument empty, will show global config" >&2 + [ -z "$2" ] && log_warn "Device name argument empty, will show global config" DEV="$2" upsconf_getSection "$DEV" exit $? ;; --show-config-value|--show-device-config-value) - [ -z "$3" ] && echo "At least one configuration key name argument is required" >&2 && exit 1 - [ -z "$2" ] && echo "WARNING: Device name argument empty, will show global config" >&2 + [ -z "$3" ] && log_fatal 1 "At least one configuration key name argument is required" + [ -z "$2" ] && log_warn "Device name argument empty, will show global config" DEV="$2" shift 2 upsconf_getValue "$DEV" "$@" exit $? ;; upsconf_debug) # Not public, not in usage() - [ -z "$2" ] && echo "Device name argument required" >&2 && exit 1 + [ -z "$2" ] && log_fatal 1 "Device name argument required" upsconf_debug "$2" exit $? ;; @@ -2024,7 +2089,7 @@ while [ $# -gt 0 ]; do ;; hook_findSavedDeviceName) shift ; $hook_findSavedDeviceName "$@" ; exit $? ;; hook_getSavedDeviceName) shift ; $hook_getSavedDeviceName "$@" ; exit $? ;; - *) echo "Unrecognized argument: $1" >&2 ; exit 1 ;; + *) log_fatal 1 "Unrecognized argument: $1" ;; esac shift done diff --git a/scripts/upsdrvsvcctl/upsdrvsvcctl.in b/scripts/upsdrvsvcctl/upsdrvsvcctl.in index 6ca0a9bc1a..0eb1eb6a5c 100755 --- a/scripts/upsdrvsvcctl/upsdrvsvcctl.in +++ b/scripts/upsdrvsvcctl/upsdrvsvcctl.in @@ -1,7 +1,7 @@ #!/bin/sh # # Copyright (C) 2016-2018 Eaton -# Copyright (C) 2020-2024 Jim Klimov +# Copyright (C) 2020-2025 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 @@ -161,7 +161,7 @@ while [ $# -gt 0 ]; do ;; status) RES=0 - STATUSES="`NUT_QUIET_INIT_BANNER=true "${UPSDRVCTL}" status $2`" || RES=$? + STATUSES="`NUT_QUIET_INIT_BANNER=true \"${UPSDRVCTL}\" status $2`" || RES=$? if [ x"${STATUSES}" = x ]; then exit $RES fi @@ -184,8 +184,8 @@ while [ $# -gt 0 ]; do printf '%-'"${SVC_NAME_WIDTH}"'s\t%'"${SVC_STATE_WIDTH}"'s\t%s\n' "SVC_NAME" "SVC_STATE" "$LINE" continue fi - DEV_NAME="`echo "$LINE" | awk '{print $1}'`" - SVC_NAME="`echo "$SVCINSTS" | awk '($NF == "'"${DEV_NAME}"'") {print $1}'`" && [ -n "$SVC_NAME" ] || SVC_NAME="N/A" + DEV_NAME="`echo \"$LINE\" | awk '{print $1}'`" + SVC_NAME="`echo \"$SVCINSTS\" | awk '($NF == \"'\"${DEV_NAME}\"'\") {print $1}'`" && [ -n "$SVC_NAME" ] || SVC_NAME="N/A" if [ x"$SVC_NAME" = "N/A" ]; then SVC_STATE="N/A" else @@ -196,7 +196,7 @@ while [ $# -gt 0 ]; do ;; systemd) SVC_STATE="" CMDRES=0 - OUT="`$CMDREPORT is-enabled "${SVC_NAME}" 2>/dev/null`" || CMDRES=$? + OUT="`$CMDREPORT is-enabled \"${SVC_NAME}\" 2>/dev/null`" || CMDRES=$? if [ x"$OUT" != x ] ; then SVC_STATE="${OUT}" ; else [ 0 = "$CMDRES" ] && SVC_STATE="enabled" || SVC_STATE="disabled" fi @@ -206,7 +206,7 @@ while [ $# -gt 0 ]; do # The masked unit state has no separate query in some (all?) # systemd versions and can be reported as part of the above CMDRES=0 - OUT="`$CMDREPORT is-masked "${SVC_NAME}" 2>/dev/null`" || CMDRES=$? + OUT="`$CMDREPORT is-masked \"${SVC_NAME}\" 2>/dev/null`" || CMDRES=$? if [ x"$OUT" != x ] ; then if [ x"${SVC_STATE}" != xmasked ] && [ x"${SVC_STATE}" != x"${OUT}" ] ; then SVC_STATE="${SVC_STATE},${OUT}" @@ -216,7 +216,7 @@ while [ $# -gt 0 ]; do fi CMDRES=0 - OUT="`$CMDREPORT is-active "${SVC_NAME}" 2>/dev/null`" || CMDRES=$? + OUT="`$CMDREPORT is-active \"${SVC_NAME}\" 2>/dev/null`" || CMDRES=$? if [ x"$OUT" != x ] ; then SVC_STATE="${SVC_STATE},${OUT}" else @@ -224,7 +224,7 @@ while [ $# -gt 0 ]; do fi CMDRES=0 - OUT="`$CMDREPORT is-failed "${SVC_NAME}" 2>/dev/null`" || CMDRES=$? + OUT="`$CMDREPORT is-failed \"${SVC_NAME}\" 2>/dev/null`" || CMDRES=$? if [ x"$OUT" != x ] ; then if [ x"${OUT}" != xactive ] && [ x"${OUT}" != xinactive ] ; then SVC_STATE="${SVC_STATE},${OUT}" @@ -242,7 +242,7 @@ while [ $# -gt 0 ]; do start|stop) ACTION="$1" if [ -n "$2" ] ; then - SVCINST="`$ENUMERATOR --get-service-for-device "$2"`" || exit + SVCINST="`$ENUMERATOR --get-service-for-device \"$2\"`" || exit shift fi ;; diff --git a/scripts/valgrind/valgrind.sh.in b/scripts/valgrind/valgrind.sh.in index 966c29f751..7de2662cee 100755 --- a/scripts/valgrind/valgrind.sh.in +++ b/scripts/valgrind/valgrind.sh.in @@ -4,8 +4,8 @@ # NOTE: If there are system problems to suppress, re-run the test with # --gen-suppressions=all option, and update the .valgrind.supp file. -SCRIPTDIR="`dirname "$0"`" -SCRIPTDIR="`cd "$SCRIPTDIR" && pwd`" +SCRIPTDIR="`dirname \"$0\"`" +SCRIPTDIR="`cd \"$SCRIPTDIR\" && pwd`" LTEXEC="" [ -n "${LIBTOOL-}" ] || LIBTOOL="`command -v glibtool`" 2>/dev/null diff --git a/server/sstate.c b/server/sstate.c index e49eaac2cf..9bae0cd397 100644 --- a/server/sstate.c +++ b/server/sstate.c @@ -478,12 +478,24 @@ int sstate_dead(upstype_t *ups, int arg_maxage) if ((elapsed > (arg_maxage / 3)) && (difftime(now, ups->last_ping) > (arg_maxage / 3))) sendping(ups); - if (elapsed > arg_maxage) { + if (elapsed > arg_maxage + 1) { upsdebugx(3, "%s: didn't hear from driver for UPS [%s] for %g seconds (max %d)", __func__, ups->name, elapsed, arg_maxage); return 1; /* dead */ } + if (elapsed > arg_maxage) { + /* Per https://github.com/networkupstools/nut/issues/661 we do + * often see "is stale" and "is alive" messages in the same + * second, especially on busy systems that can not dedicate + * every scheduled time slot to NUT daemons and data pipes. + * So data exchange frequency settings may almost race... + */ + upsdebugx(3, "%s: didn't hear from driver for UPS [%s] for %g seconds (max %d); will declare it dead next second (delaying just in case of whole-second rounding issues)", + __func__, ups->name, elapsed, arg_maxage); + return 0; /* probably dead */ + } + /* ignore DATAOK/DATASTALE unless the dump is done */ if ((ups->dumpdone) && (!ups->data_ok)) { upsdebugx(3, "%s: driver for UPS [%s] says data is stale", __func__, ups->name); diff --git a/tests/Makefile.am b/tests/Makefile.am index 012eb4cf61..af017973e5 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -64,13 +64,13 @@ check_SCRIPTS += nutlogtest-nofail.sh CLEANFILES += nutlogtest-nofail.sh nutlogtest$(EXEEXT) nutlogtest nutlogtest-nofail.sh: nutlogtest$(EXEEXT) - @echo '#!/bin/sh' > $@ - @echo 'echo "WARNING: Your C library requires workarounds to print NULL values!" >&2' >> $@ - @echo 'echo "If nutlogtest below, or generally some NUT program, crashes with" >&2' >> $@ - @echo 'echo "a segmentation fault (especially during verbose debug) - that may be why" >&2' >> $@ - @echo 'SCRIPT_DIR="`dirname "$$0"`"' >> $@ - @echo '"$${SCRIPT_DIR}/nutlogtest" "$$@" || echo "nutlogtest FAILED but it was expected"' >> $@ - @chmod +x $@ + @echo '#!/bin/sh' > '$@' + @echo 'echo "WARNING: Your C library requires workarounds to print NULL values!" >&2' >> '$@' + @echo 'echo "If nutlogtest below, or generally some NUT program, crashes with" >&2' >> '$@' + @echo 'echo "a segmentation fault (especially during verbose debug) - that may be why" >&2' >> '$@' + @echo 'SCRIPT_DIR="`dirname \"$$0\"`"' >> '$@' + @echo '"$${SCRIPT_DIR}/nutlogtest" "$$@" || echo "nutlogtest FAILED but it was expected"' >> '$@' + @chmod +x '$@' # NOTE: Keep the line above empty! else !REQUIRE_NUT_STRARG @@ -91,7 +91,7 @@ LINKED_SOURCE_FILES = hidparser.c # NOTE: Not using "$<" due to a legacy Sun/illumos dmake bug with resolver # of dynamic vars, see e.g. https://man.omnios.org/man1/make#BUGS hidparser.c: $(top_srcdir)/drivers/hidparser.c - test -s "$@" || ln -s -f "$(top_srcdir)/drivers/hidparser.c" "$@" + test -s '$@' || ln -s -f "$(top_srcdir)/drivers/hidparser.c" '$@' if WITH_USB TESTS += getvaluetest getexponenttest-belkin-hid @@ -124,10 +124,10 @@ TESTS += gpiotest # NOTE: Not using "$<" due to a legacy Sun/illumos dmake bug with resolver # of dynamic vars, see e.g. https://man.omnios.org/man1/make#BUGS generic_gpio_libgpiod.c: $(top_srcdir)/drivers/generic_gpio_libgpiod.c - test -s "$@" || ln -s -f "$(top_srcdir)/drivers/generic_gpio_libgpiod.c" "$@" + test -s '$@' || ln -s -f "$(top_srcdir)/drivers/generic_gpio_libgpiod.c" '$@' generic_gpio_common.c: $(top_srcdir)/drivers/generic_gpio_common.c - test -s "$@" || ln -s -f "$(top_srcdir)/drivers/generic_gpio_common.c" "$@" + test -s '$@' || ln -s -f "$(top_srcdir)/drivers/generic_gpio_common.c" '$@' gpiotest_SOURCES = generic_gpio_utest.c generic_gpio_liblocal.c nodist_gpiotest_SOURCES = generic_gpio_libgpiod.c generic_gpio_common.c diff --git a/tests/NIT/Makefile.am b/tests/NIT/Makefile.am index 825df6bd92..40af57864f 100644 --- a/tests/NIT/Makefile.am +++ b/tests/NIT/Makefile.am @@ -21,7 +21,9 @@ endif # Run in builddir, use script from srcdir # Avoid running "$<" - not all make implementations handle that check-NIT: $(abs_srcdir)/nit.sh - "$(abs_srcdir)/nit.sh" + GREP="$(GREP)"; EGREP="$(EGREP)"; export GREP; export EGREP; \ + BUILTIN_RUN_AS_USER='$(RUN_AS_USER)' BUILTIN_RUN_AS_GROUP='$(RUN_AS_GROUP)' \ + "$(abs_srcdir)/nit.sh" # Make sure pre-requisites for NIT are fresh as we iterate check-NIT-devel: $(abs_srcdir)/nit.sh @@ -36,15 +38,19 @@ check-NIT-devel: $(abs_srcdir)/nit.sh NIT_CASE = testgroup_sandbox_upsmon_master NUT_PORT = 12345 check-NIT-sandbox: $(abs_srcdir)/nit.sh + GREP="$(GREP)"; EGREP="$(EGREP)"; export GREP; export EGREP; \ [ -n "$${DEBUG_SLEEP-}" ] && [ "$${DEBUG_SLEEP-}" -gt 0 ] || DEBUG_SLEEP=600 ; export DEBUG_SLEEP ; \ LANG=C LC_ALL=C TZ=UTC \ NUT_PORT=$(NUT_PORT) NIT_CASE="$(NIT_CASE)" NUT_FOREGROUND_WITH_PID=true \ + BUILTIN_RUN_AS_USER='$(RUN_AS_USER)' BUILTIN_RUN_AS_GROUP='$(RUN_AS_GROUP)' \ "$(abs_srcdir)/nit.sh" check-NIT-sandbox-devel: $(abs_srcdir)/nit.sh +[ -n "$${DEBUG_SLEEP-}" ] && [ "$${DEBUG_SLEEP-}" -gt 0 ] || DEBUG_SLEEP=600 ; export DEBUG_SLEEP ; \ + GREP="$(GREP)"; EGREP="$(EGREP)"; export GREP; export EGREP; \ LANG=C LC_ALL=C TZ=UTC \ NUT_PORT=$(NUT_PORT) NIT_CASE="$(NIT_CASE)" NUT_FOREGROUND_WITH_PID=true \ + BUILTIN_RUN_AS_USER='$(RUN_AS_USER)' BUILTIN_RUN_AS_GROUP='$(RUN_AS_GROUP)' \ $(MAKE) $(AM_MAKEFLAGS) check-NIT-devel SPELLCHECK_SRC = README.adoc diff --git a/tests/NIT/README.adoc b/tests/NIT/README.adoc index a83151a4d3..d9210c7048 100644 --- a/tests/NIT/README.adoc +++ b/tests/NIT/README.adoc @@ -24,7 +24,7 @@ A sandbox prepared by this script can be used for `upsmon` testing: # Wait for sandbox, e.g. test that "${NUT_CONFPATH}/NIT.env-sandbox-ready" # file appeared; then source the envvars, e.g.: -:; sleep 5 ; while ! [ -e ./tests/NIT/tmp/etc/NIT.env-sandbox-ready ] ; do sleep 1 ; done +:; sleep 5 ; while ! [ -f ./tests/NIT/tmp/etc/NIT.env-sandbox-ready ] ; do sleep 1 ; done :; . ./tests/NIT/tmp/etc/NIT.env # Prepare upsmon.conf there, e.g.: diff --git a/tests/NIT/nit.sh b/tests/NIT/nit.sh index 41b20a6010..d92435c70a 100755 --- a/tests/NIT/nit.sh +++ b/tests/NIT/nit.sh @@ -14,7 +14,7 @@ # WARNING: Current working directory when starting the script should be # the location where it may create temporary data (e.g. the BUILDDIR). # Caller can export envvars to impact the script behavior, e.g.: -# DEBUG=true to print debug messages, running processes, etc. +# DEBUG_NIT=true to print debug messages, running processes, etc. # DEBUG_SLEEP=60 to sleep after tests, with driver+server running # NUT_DEBUG_MIN=3 to set (minimum) debug level for drivers, upsd... # NUT_DEBUG_LEVEL_UPSSCHED=3 to set debug level for particular @@ -35,6 +35,16 @@ # properly work in whatever different OSes have (bash, dash, # ksh, busybox sh...) # +# Special considerations for starting the tests as "root": +# * See I_AM_ROOT evaluation and conditional uses in code below for the +# most authoritative reference. +# * More liberal permissions to TESTDIR, STATEPATH and configuration files +# than when a non-root user started the test suite right away and could +# only constrain access to allow itself. +# * BUILTIN_RUN_AS_USER and BUILTIN_RUN_AS_GROUP envvars would be consulted +# and if those accounts do not exist (e.g. in a packaging build root), a +# different value like "nobody" or "nogroup" would be defaulted for test. +# # Copyright # 2022-2025 Jim Klimov # @@ -87,6 +97,10 @@ export NUT_QUIET_INIT_NDE_WARNING ARG_FG="-F" if [ x"${NUT_FOREGROUND_WITH_PID-}" = xtrue ] ; then ARG_FG="-FF" ; fi +# tools +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; } + TABCHAR="`printf '\t'`" log_separator() { @@ -95,7 +109,7 @@ log_separator() { } shouldDebug() { - [ -n "$DEBUG" ] || [ -n "$DEBUG_SLEEP" ] + [ -n "$DEBUG" ] || [ -n "$DEBUG_NIT" ] || [ -n "$DEBUG_SLEEP" ] } log_debug() { @@ -123,7 +137,7 @@ report_NUT_PORT() { log_info "Trying to report users of NUT_PORT=${NUT_PORT}" # Note: on Solarish systems, `netstat -anp` does not report PID info - (netstat -an ; netstat -anp || sockstat -l) 2>/dev/null | grep -w "${NUT_PORT}" \ + (netstat -an ; netstat -anp || sockstat -l) 2>/dev/null | ${GREP} -w "${NUT_PORT}" \ || (lsof -i :"${NUT_PORT}") 2>/dev/null \ || true @@ -145,8 +159,8 @@ isBusy_NUT_PORT() { # IPv6: # sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode # 0: 00000000000000000000000000000000:1F46 00000000000000000000000000000000:0000 0A 00000000:00000000 00:00000000 00000000 33 0 37451 1 00000000fa3c0c15 100 0 0 10 0 - NUT_PORT_HEX="`printf '%04X' "${NUT_PORT}"`" - NUT_PORT_HITS="`cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | awk '{print $2}' | grep -E ":${NUT_PORT_HEX}\$"`" \ + NUT_PORT_HEX="`printf '%04X' \"${NUT_PORT}\"`" + NUT_PORT_HITS="`cat /proc/net/tcp /proc/net/tcp6 2>/dev/null | awk '{print $2}' | ${EGREP} \":${NUT_PORT_HEX}\$\"`" \ && [ -n "$NUT_PORT_HITS" ] \ && log_debug "isBusy_NUT_PORT() found that NUT_PORT=${NUT_PORT} is busy per /proc/net/tcp*" \ && return 0 @@ -156,7 +170,7 @@ isBusy_NUT_PORT() { return 1 fi - (netstat -an || sockstat -l || ss -tn || ss -n) 2>/dev/null | grep -E "[:.]${NUT_PORT}(${TABCHAR}| |\$)" > /dev/null \ + (netstat -an || sockstat -l || ss -tn || ss -n) 2>/dev/null | ${EGREP} "[:.]${NUT_PORT}(${TABCHAR}| |\$)" > /dev/null \ && log_debug "isBusy_NUT_PORT() found that NUT_PORT=${NUT_PORT} is busy per netstat, sockstat or ss" \ && return @@ -234,8 +248,8 @@ runcmd() { "$@" > "${NUT_STATEPATH}/runcmd.out" 2>"${NUT_STATEPATH}/runcmd.err" || CMDRES=$? NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" - CMDOUT="`cat "${NUT_STATEPATH}/runcmd.out"`" - CMDERR="`cat "${NUT_STATEPATH}/runcmd.err"`" + CMDOUT="`cat \"${NUT_STATEPATH}/runcmd.out\"`" + CMDERR="`cat \"${NUT_STATEPATH}/runcmd.err\"`" [ "$RUNCMD_QUIET_OUT" = true ] || { [ -z "$CMDOUT" ] || echo "$CMDOUT" ; } [ "$RUNCMD_QUIET_ERR" = true ] || { [ -z "$CMDERR" ] || echo "$CMDERR" >&2 ; } @@ -252,19 +266,19 @@ BUILDDIR="`pwd`" TOP_BUILDDIR="" case "${BUILDDIR}" in */tests/NIT) - TOP_BUILDDIR="`cd "${BUILDDIR}"/../.. && pwd`" ;; + TOP_BUILDDIR="`cd \"${BUILDDIR}\"/../.. && pwd`" ;; *) log_info "Current directory '${BUILDDIR}' is not a .../tests/NIT" ;; esac if test ! -w "${BUILDDIR}" ; then log_error "BUILDDIR='${BUILDDIR}' is not writeable, tests may fail below" fi -SRCDIR="`dirname "$0"`" -SRCDIR="`cd "$SRCDIR" && pwd`" +SRCDIR="`dirname \"$0\"`" +SRCDIR="`cd \"$SRCDIR\" && pwd`" TOP_SRCDIR="" case "${SRCDIR}" in */tests/NIT) - TOP_SRCDIR="`cd "${SRCDIR}"/../.. && pwd`" ;; + TOP_SRCDIR="`cd \"${SRCDIR}\"/../.. && pwd`" ;; *) log_info "Script source directory '${SRCDIR}' is not a .../tests/NIT" ;; esac @@ -319,6 +333,156 @@ PID_DUMMYUPS="" PID_DUMMYUPS1="" PID_DUMMYUPS2="" +# Platforms vary in abilities to report this... +I_AM_NAME="" +get_my_user_name() { + if [ x"${I_AM_NAME}" != x ]; then + # Use cache + echo "${I_AM_NAME}" + return + fi + + _ME="`whoami 2>/dev/null`" && [ x"${_ME}" != x ] \ + || { _ME="`ps -ef 2>/dev/null | grep -v grep | grep $$`" && [ x"${_ME}" != x ] ; } \ + || { _ME="`ps -xawwu 2>/dev/null | grep -v grep | grep $$`" && [ x"${_ME}" != x ] ; } \ + || _ME="" + + if [ x"${_ME}" != x ] ; then + echo "${_ME}" + return + fi + + # TOTHINK: Fallback to get "my current user": touch a file and see who owns it? + + # Non-numeric (empty) stdout; non-successful exit code + return 1 +} + +get_user_id() { + if _ID="`id -u ${1-} 2>/dev/null`" \ + && [ "${_ID}" -ge 0 ] 2>/dev/null ; then echo "${_ID}"; return ; fi + if _ID="`id ${1-} 2>/dev/null | sed -e 's,^.*uid=,,' -e 's,(.*$,,'`" \ + && [ "${_ID}" -ge 0 ] 2>/dev/null ; then echo "${_ID}"; return ; fi + if [ x"${1-}" != x ] 2>/dev/null && _ID="`getent passwd \"$1\" 2>/dev/null | awk -F: '{print $3}'`" \ + && [ "${_ID}" -ge 0 ] 2>/dev/null ; then echo "${_ID}"; return ; fi + + # Fallback + if [ x"${1-}" = x ] 2>/dev/null ; then + if _ME="`get_my_user_name`" 2>/dev/null && [ x"${_ME}" != x ] ; then + _RES=0 + get_user_id "${_ME}" || _RES=$? + return ${_RES} + fi + fi + + # Non-numeric (empty) stdout; non-successful exit code + return 1 +} + +get_group_id() { + if _ID="`id -g ${1-} 2>/dev/null`" \ + && [ "${_ID}" -ge 0 ] 2>/dev/null ; then echo "${_ID}"; return ; fi + if _ID="`id ${1-} 2>/dev/null | sed -e 's,^.*gid=,,' -e 's,(.*$,,'`" \ + && [ "${_ID}" -ge 0 ] 2>/dev/null ; then echo "${_ID}"; return ; fi + if [ x"${1-}" != x ] 2>/dev/null && _ID="`getent group \"$1\" 2>/dev/null | awk -F: '{print $3}'`" \ + && [ "${_ID}" -ge 0 ] 2>/dev/null ; then echo "${_ID}"; return ; fi + + # TOTHINK: Fallback to get "my current group": touch a file and see who owns it? + + # Non-numeric (empty) stdout; non-successful exit code + return 1 +} + +I_AM_NAME="`get_my_user_name`" +I_AM_NAME_REPORT="'${I_AM_NAME}'" +I_AM_ROOT=false +if [ "`get_user_id`" = 0 ] ; then + if [ x"${I_AM_NAME}" = x ]; then + log_warn "Seems we have started as UID 0, but could not detect user name: assuming 'root'" + else if [ x"${I_AM_NAME}" != xroot ]; then + log_warn "Seems we have started as UID 0, but detected user name was not 'root': '${I_AM_NAME}'" + I_AM_NAME_REPORT="'${I_AM_NAME}' (root?)" + fi; fi + I_AM_ROOT=true +fi + +# We have a certain problem when the requested user account does not exist: +# our `common::get_user_pwent()` aborts with `fatalx()` during such a check for +# validity of built-in or CLI-requested account (sometimes before even looking +# at any config files), and fails like "OS user upsd not found". Note we do not +# currently have similar troubles for groups (and interactions with customizing +# them seem limited to `chgrp()` of driver-upsd local socket). +# While this should not happen in real life, it can on test or packaging +# systems that did not provision the accounts (claimed by a NUT build) for +# the build/test run-time environment. In this case, tweak it into something +# we know exists (current non-root user explicitly, or a commonly available +# name if we are running as root). +TWEAK_RUN_AS_USER="" +TWEAK_RUN_AS_GROUP="" +ARG_USER="" +if [ x"${BUILTIN_RUN_AS_USER}" != x ] ; then + if [ "`get_user_id \"${BUILTIN_RUN_AS_USER}\"`" -ge 0 ] 2>/dev/null; then + # Do not bother to re-evaluate more IDs to rule out aliases - many names on same ID? + if $I_AM_ROOT || [ x"${I_AM_NAME}" = x"${BUILTIN_RUN_AS_USER}" ] ; then + log_info "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_USER='${BUILTIN_RUN_AS_USER}' seems present on this system to run test daemons as" + else + log_info "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_USER='${BUILTIN_RUN_AS_USER}' seems present on this system (but we would run test daemons as current unprivileged user)" + fi + else + # The string allegedly built into NUT binaries is unknown to the + # account identification databases of this runtime environment... + if $I_AM_ROOT ; then + for U in nobody daemon bin ; do + if [ "`get_user_id \"${U}\"`" -ge 0 ] ; then + TWEAK_RUN_AS_USER="${U}" + break + fi + done + else + TWEAK_RUN_AS_USER="${I_AM_NAME}" + fi + + if [ x"${TWEAK_RUN_AS_USER}" = x ] ; then + log_warn "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_USER='${BUILTIN_RUN_AS_USER}' seems absent on this system, and did not find a common alternative to run test daemons as; NIT suite can fail below!" + else + log_warn "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_USER='${BUILTIN_RUN_AS_USER}' seems absent on this system, will run test daemons as '${TWEAK_RUN_AS_USER}'" + # Needed e.g. for upsd, its config has no RUN_AS_USER setting + ARG_USER="-u ${TWEAK_RUN_AS_USER}" + fi + fi +fi + +if [ x"${BUILTIN_RUN_AS_GROUP}" != x ] ; then + # Note: GID setting is not much used in NUT code at the moment: + # * In `drivers/main.c` we can set FS access for the pipe to data server + # * Otherwise in `common.c::become_user()` we try to assume the default + # GID of that user account we were asked to switch into. + if [ "`get_group_id \"${BUILTIN_RUN_AS_GROUP}\"`" -ge 0 ] 2>/dev/null ; then + # Do not bother to re-evaluate more IDs to rule out aliases - many names on same ID? + # TOTHINK: Would need I_AM_GROUP first?.. + if $I_AM_ROOT ; then + log_info "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_GROUP='${BUILTIN_RUN_AS_GROUP}' seems present on this system to run test daemons as" + else + log_info "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_GROUP='${BUILTIN_RUN_AS_GROUP}' seems present on this system (but we would run test daemons as current unprivileged user and untweaked group)" + fi + else + # The string allegedly built into NUT binaries is unknown to the + # account identification databases of this runtime environment... + for G in nobody nogroup daemon bin ; do + if [ "`get_group_id \"${G}\"`" -ge 0 ] ; then + TWEAK_RUN_AS_GROUP="${G}" + break + fi + done + + if [ x"${TWEAK_RUN_AS_GROUP}" = x ] ; then + log_warn "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_GROUP='${BUILTIN_RUN_AS_GROUP}' seems absent on this system, and did not find a common alternative to run test daemons as; NIT suite can fail below!" + else + log_warn "Started as ${I_AM_NAME_REPORT}, and built-in RUN_AS_GROUP='${BUILTIN_RUN_AS_GROUP}' seems absent on this system, will run test daemons as '${TWEAK_RUN_AS_GROUP}'" + fi + fi +fi + # Stash it for some later decisions TESTDIR_CALLER="${TESTDIR-}" [ -n "${TESTDIR-}" ] || TESTDIR="$BUILDDIR/tmp" @@ -329,15 +493,17 @@ if [ `echo "${TESTDIR}" | wc -c` -gt 80 ]; then log_info "'${TESTDIR}' is too long to store AF_UNIX socket files, will mktemp" TESTDIR="" else - if [ "`id -u`" = 0 ]; then + if $I_AM_ROOT ; then case "${TESTDIR}" in "${HOME}"/*) - log_info "Test script was started by 'root' and '${TESTDIR}' seems to be under its home, will mktemp so unprivileged daemons may access their configs, pipes and PID files" + log_info "Test script was started by ${I_AM_NAME_REPORT} and '${TESTDIR}' seems to be under its home, will mktemp so unprivileged daemons may access their configs, pipes and PID files" TESTDIR="" ;; esac else - if ! mkdir -p "${TESTDIR}" || ! [ -w "${TESTDIR}" ] ; then + if mkdir -p "${TESTDIR}" && [ -w "${TESTDIR}" ] ; then + true + else log_info "'${TESTDIR}' could not be created/used, will mktemp" TESTDIR="" fi @@ -352,14 +518,14 @@ if [ x"${TESTDIR}" = x ] ; then fi log_warn "Will now mktemp a TESTDIR under '${TMPDIR}'. It will be wiped when the NIT script exits." log_warn "If you want a pre-determined location, pre-export a usable TESTDIR value." - TESTDIR="`mktemp -d "${TMPDIR}/nit-tmp.$$.XXXXXX"`" || die "Failed to mktemp" - if [ "`id -u`" = 0 ]; then + TESTDIR="`mktemp -d \"${TMPDIR}/nit-tmp.$$.XXXXXX\"`" || die "Failed to mktemp" + if $I_AM_ROOT ; then # Cah be protected as 0700 by default chmod ugo+rx "${TESTDIR}" fi else NUT_CONFPATH="${TESTDIR}/etc" - if [ -e "${NUT_CONFPATH}/NIT.env-sandbox-ready" ] ; then + if [ -f "${NUT_CONFPATH}/NIT.env-sandbox-ready" ] ; then log_warn "'${NUT_CONFPATH}/NIT.env-sandbox-ready' exists, do you have another instance of the script still running with same configs?" sleep 3 fi @@ -380,8 +546,8 @@ fi mkdir -p "${TESTDIR}/etc" "${TESTDIR}/run" && chmod 750 "${TESTDIR}/run" \ || die "Failed to create temporary FS structure for the NIT" -if [ "`id -u`" = 0 ]; then - log_info "Test script was started by 'root' - expanding permissions for '${TESTDIR}/run' so unprivileged daemons may create pipes and PID files there" +if $I_AM_ROOT ; then + log_info "Test script was started by ${I_AM_NAME_REPORT} - expanding permissions for '${TESTDIR}/run' so unprivileged daemons may create pipes and PID files there" chmod 777 "${TESTDIR}/run" fi @@ -392,11 +558,11 @@ stop_daemons() { fi if [ -z "$PID_UPSSCHED" ] && [ -s "$NUT_PIDPATH/upssched.pid" ] ; then - PID_UPSSCHED="`head -1 "$NUT_PIDPATH/upssched.pid"`" + PID_UPSSCHED="`head -1 \"$NUT_PIDPATH/upssched.pid\"`" fi if [ -s "$NUT_PIDPATH/upssched.pid" ] ; then - PID_UPSSCHED_NOW="`head -1 "$NUT_PIDPATH/upssched.pid"`" + PID_UPSSCHED_NOW="`head -1 \"$NUT_PIDPATH/upssched.pid\"`" fi if [ -n "$PID_UPSD$PID_UPSMON$PID_DUMMYUPS$PID_DUMMYUPS1$PID_DUMMYUPS2$PID_UPSSCHED$PID_UPSSCHED_NOW" ] ; then @@ -423,7 +589,7 @@ NUT_ALTPIDPATH="${TESTDIR}/run" NUT_CONFPATH="${TESTDIR}/etc" export NUT_STATEPATH NUT_PIDPATH NUT_ALTPIDPATH NUT_CONFPATH -if [ -e "${NUT_CONFPATH}/NIT.env-sandbox-ready" ] ; then +if [ -f "${NUT_CONFPATH}/NIT.env-sandbox-ready" ] ; then log_warn "'${NUT_CONFPATH}/NIT.env-sandbox-ready' exists, do you have another instance of the script still running?" sleep 3 fi @@ -449,7 +615,7 @@ else fi log_warn "Selected NUT_PORT=$NUT_PORT seems occupied; will try another in a few seconds" - COUNTDOWN="`expr "$COUNTDOWN" - 1`" + COUNTDOWN="`expr \"$COUNTDOWN\" - 1`" [ "$COUNTDOWN" = 0 ] || sleep 2 done @@ -467,7 +633,7 @@ else fi # Loop quickly, no sleep here - COUNTDOWN="`expr "$COUNTDOWN" - 1`" + COUNTDOWN="`expr \"$COUNTDOWN\" - 1`" done if [ "$COUNTDOWN" = 0 ] ; then @@ -494,7 +660,7 @@ log_info "Using NUT_PORT=${NUT_PORT} for this test run" # the values when fallback is used. If this is a # problem on any platform (Win/Mac and spaces in # paths?) please investigate and fix accordingly. -set | grep -E '^(NUT_|TESTDIR|LD_LIBRARY_PATH|DEBUG|PATH).*=' \ +set | ${EGREP} '^(NUT_|TESTDIR|LD_LIBRARY_PATH|DEBUG|PATH).*=' \ | while IFS='=' read K V ; do case "$K" in LD_LIBRARY_PATH_CLIENT|LD_LIBRARY_PATH_ORIG|PATH_*|NUT_PORT_*|TESTDIR_*) @@ -522,8 +688,9 @@ LISTEN localhost $NUT_PORT EOF [ $? = 0 ] || die "Failed to populate temporary FS structure for the NIT: upsd.conf" - if [ "`id -u`" = 0 ]; then - log_info "Test script was started by 'root' - expanding permissions for '$NUT_CONFPATH/upsd.conf' so unprivileged daemons (after de-elevation) may read it" + if $I_AM_ROOT ; then + log_info "Test script was started by ${I_AM_NAME_REPORT} - expanding permissions for '$NUT_CONFPATH/upsd.conf' so unprivileged daemons (after de-elevation) may read it" + # NOTE: No RUN_AS_USER in upsd.conf currently; tweaking via CLI args if needed chmod 644 "$NUT_CONFPATH/upsd.conf" else chmod 640 "$NUT_CONFPATH/upsd.conf" @@ -535,7 +702,7 @@ EOF # both addresses in one command. for LH in 127.0.0.1 '::1' ; do if ( - ( cat /etc/hosts || getent hosts ) | grep "$LH" \ + ( cat /etc/hosts || getent hosts ) | ${GREP} "$LH" \ || ping -c 1 "$LH" ) 2>/dev/null >/dev/null ; then echo "LISTEN $LH $NUT_PORT" >> "$NUT_CONFPATH/upsd.conf" @@ -590,8 +757,8 @@ generatecfg_upsdusers_trivial() { EOF [ $? = 0 ] || die "Failed to populate temporary FS structure for the NIT: upsd.users" - if [ "`id -u`" = 0 ]; then - log_info "Test script was started by 'root' - expanding permissions for '$NUT_CONFPATH/upsd.users' so unprivileged daemons (after de-elevation) may read it" + if $I_AM_ROOT ; then + log_info "Test script was started by ${I_AM_NAME_REPORT} - expanding permissions for '$NUT_CONFPATH/upsd.users' so unprivileged daemons (after de-elevation) may read it" chmod 644 "$NUT_CONFPATH/upsd.users" else chmod 640 "$NUT_CONFPATH/upsd.users" @@ -636,7 +803,7 @@ generatecfg_upsmon_trivial() { if [ -n "${NOTIFYTGT}" ]; then if [ -s "${TOP_SRCDIR-}/conf/upsmon.conf.sample.in" ] ; then - grep -E '# NOTIFYFLAG .*SYSLOG\+WALL$' \ + ${EGREP} '# NOTIFYFLAG .*SYSLOG\+WALL$' \ < "${TOP_SRCDIR-}/conf/upsmon.conf.sample.in" \ | sed 's,^# \(NOTIFYFLAG[^A-Z_]*[A-Z_]*\)[^A-Z_]*SYSLOG.*$,\1\t'"${NOTIFYTGT}"',' \ >> "$NUT_CONFPATH/upsmon.conf" || exit @@ -656,13 +823,17 @@ generatecfg_upsmon_trivial() { < "${TOP_SRCDIR-}/tests/NIT/upssched.conf.in" > "$NUT_CONFPATH/upssched.conf" \ || die "Failed to populate temporary FS structure for the NIT: upssched.conf" - if [ "`id -u`" = 0 ]; then - log_info "Test script was started by 'root' - expanding permissions for '$NUT_CONFPATH/upsmon.conf' and '$NUT_CONFPATH/upssched.conf' so unprivileged daemons (after de-elevation) may read them" + if $I_AM_ROOT ; then + log_info "Test script was started by ${I_AM_NAME_REPORT} - expanding permissions for '$NUT_CONFPATH/upsmon.conf' and '$NUT_CONFPATH/upssched.conf' so unprivileged daemons (after de-elevation) may read them" chmod 644 "$NUT_CONFPATH/upsmon.conf" "$NUT_CONFPATH/upssched.conf" else chmod 640 "$NUT_CONFPATH/upsmon.conf" "$NUT_CONFPATH/upssched.conf" fi + if [ x"${TWEAK_RUN_AS_USER}" != x ] ; then + echo "RUN_AS_USER ${TWEAK_RUN_AS_USER}" >> "$NUT_CONFPATH/upsmon.conf" + fi + if [ $# -gt 0 ] ; then updatecfg_upsmon_supplies "$1" fi @@ -721,12 +892,19 @@ generatecfg_ups_trivial() { fi ) || die "Failed to populate temporary FS structure for the NIT: ups.conf" - if [ "`id -u`" = 0 ]; then - log_info "Test script was started by 'root' - expanding permissions for '$NUT_CONFPATH/ups.conf' so unprivileged daemons (after de-elevation) may read it" + if $I_AM_ROOT ; then + log_info "Test script was started by ${I_AM_NAME_REPORT} - expanding permissions for '$NUT_CONFPATH/ups.conf' so unprivileged daemons (after de-elevation) may read it" chmod 644 "$NUT_CONFPATH/ups.conf" else chmod 640 "$NUT_CONFPATH/ups.conf" fi + + if [ x"${TWEAK_RUN_AS_USER}" != x ] ; then + echo "user ${TWEAK_RUN_AS_USER}" >> "$NUT_CONFPATH/ups.conf" + fi + if [ x"${TWEAK_RUN_AS_GROUP}" != x ] ; then + echo "group ${TWEAK_RUN_AS_GROUP}" >> "$NUT_CONFPATH/ups.conf" + fi } generatecfg_ups_dummy() { @@ -774,7 +952,7 @@ EOF for F in "$NUT_CONFPATH/"*.dev "$NUT_CONFPATH/"*.seq ; do sed -e 's,^ups.status: *$,ups.status: OL BOOST,' "$F" > "$F.bak" mv -f "$F.bak" "$F" - grep -E '^ups.status:' "$F" >/dev/null || { echo "ups.status: OL BOOST" >> "$F"; } + ${EGREP} '^ups.status:' "$F" >/dev/null || { echo "ups.status: OL BOOST" >> "$F"; } done fi @@ -797,7 +975,7 @@ testcase_upsd_no_configs_at_all() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} + upsd ${ARG_FG} ${ARG_USER} if [ "$?" = 0 ]; then log_error "[testcase_upsd_no_configs_at_all] upsd should fail without configs" FAILED="`expr $FAILED + 1`" @@ -816,7 +994,7 @@ testcase_upsd_no_configs_driver_file() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} + upsd ${ARG_FG} ${ARG_USER} if [ "$?" = 0 ]; then log_error "[testcase_upsd_no_configs_driver_file] upsd should fail without driver config file" FAILED="`expr $FAILED + 1`" @@ -836,7 +1014,7 @@ testcase_upsd_no_configs_in_driver_file() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} + upsd ${ARG_FG} ${ARG_USER} if [ "$?" = 0 ]; then log_error "[testcase_upsd_no_configs_in_driver_file] upsd should fail without drivers defined in config file" FAILED="`expr $FAILED + 1`" @@ -858,7 +1036,7 @@ upsd_start_loop() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} & + upsd ${ARG_FG} ${ARG_USER} & PID_UPSD="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_debug "[${TESTCASE}] Tried to start UPSD as PID $PID_UPSD" @@ -892,7 +1070,7 @@ upsd_start_loop() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} & + upsd ${ARG_FG} ${ARG_USER} & PID_UPSD="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_warn "[${TESTCASE}] Tried to start UPSD again, now as PID $PID_UPSD" @@ -939,12 +1117,12 @@ testcase_upsd_allow_no_device() { : else # Note: avoid exact matching for stderr, because it can have Init SSL messages etc. - if echo "$CMDERR" | grep "Error: Server disconnected" >/dev/null ; then + if echo "$CMDERR" | ${GREP} "Error: Server disconnected" >/dev/null ; then log_warn "[testcase_upsd_allow_no_device] Retry once to rule out laggy systems" sleep 3 runcmd upsc -l localhost:$NUT_PORT fi - if echo "$CMDERR" | grep "Error: Server disconnected" >/dev/null ; then + if echo "$CMDERR" | ${GREP} "Error: Server disconnected" >/dev/null ; then log_warn "[testcase_upsd_allow_no_device] Retry once more to rule out very laggy systems" sleep 15 runcmd upsc -l localhost:$NUT_PORT @@ -1043,17 +1221,17 @@ sandbox_start_drivers() { if [ -n "${NUT_DEBUG_LEVEL_DRIVERS-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_DRIVERS}" fi - #upsdrvctl ${ARG_FG} start dummy & - dummy-ups -a dummy ${ARG_FG} & + #upsdrvctl ${ARG_FG} ${ARG_USER} start dummy & + dummy-ups -a dummy ${ARG_USER} ${ARG_FG} & PID_DUMMYUPS="$!" log_debug "Tried to start dummy-ups driver for 'dummy' as PID $PID_DUMMYUPS" if [ x"${TOP_SRCDIR}" != x ]; then - dummy-ups -a UPS1 ${ARG_FG} & + dummy-ups -a UPS1 ${ARG_USER} ${ARG_FG} & PID_DUMMYUPS1="$!" log_debug "Tried to start dummy-ups driver for 'UPS1' as PID $PID_DUMMYUPS1" - dummy-ups -a UPS2 ${ARG_FG} & + dummy-ups -a UPS2 ${ARG_USER} ${ARG_FG} & PID_DUMMYUPS2="$!" log_debug "Tried to start dummy-ups driver for 'UPS2' as PID $PID_DUMMYUPS2" fi @@ -1062,7 +1240,7 @@ sandbox_start_drivers() { sleep 5 if shouldDebug ; then - (ps -ef || ps -xawwu) 2>/dev/null | grep -E '(ups|nut|dummy|'"`basename "$0"`"')' | grep -vE '(ssh|startups|grep)' || true + (ps -ef || ps -xawwu) 2>/dev/null | ${EGREP} '(ups|nut|dummy|'"`basename \"$0\"`"')' | ${EGREP} -v '(ssh|startups|grep)' || true fi if isPidAlive "$PID_DUMMYUPS" \ @@ -1089,7 +1267,7 @@ testcase_sandbox_start_upsd_alone() { UPS1 UPS2" # For windows runners (strip CR if any): - EXPECTED_UPSLIST="`echo "$EXPECTED_UPSLIST" | tr -d '\r'`" + EXPECTED_UPSLIST="`echo \"$EXPECTED_UPSLIST\" | tr -d '\r'`" fi log_info "[testcase_sandbox_start_upsd_alone] Query listing from UPSD by UPSC (driver not running yet)" @@ -1097,7 +1275,7 @@ UPS2" runcmd upsc -l localhost:$NUT_PORT || die "[testcase_sandbox_start_upsd_alone] upsd does not respond on port ${NUT_PORT} ($?): $CMDOUT" # For windows runners (printf can do wonders, so strip CR if any): if [ x"${TOP_SRCDIR}" != x ]; then - CMDOUT="`echo "$CMDOUT" | tr -d '\r'`" + CMDOUT="`echo \"$CMDOUT\" | tr -d '\r'`" fi if [ x"$CMDOUT" != x"$EXPECTED_UPSLIST" ] ; then log_error "[testcase_sandbox_start_upsd_alone] got this reply for upsc listing when '$EXPECTED_UPSLIST' was expected: '$CMDOUT'" @@ -1116,7 +1294,7 @@ UPS2" res_testcase_sandbox_start_upsd_alone=1 } # Note: avoid exact matching for stderr, because it can have Init SSL messages etc. - if echo "$CMDERR" | grep 'Error: Driver not connected' >/dev/null ; then + if echo "$CMDERR" | ${GREP} 'Error: Driver not connected' >/dev/null ; then PASSED="`expr $PASSED + 1`" else log_error "[testcase_sandbox_start_upsd_alone] got some other reply for upsc query when 'Error: Driver not connected' was expected on stderr: '$CMDOUT'" @@ -1145,7 +1323,7 @@ testcase_sandbox_start_upsd_after_drivers() { if [ -n "${NUT_DEBUG_LEVEL_UPSD-}" ]; then NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSD}" fi - upsd ${ARG_FG} & + upsd ${ARG_FG} ${ARG_USER} & PID_UPSD="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_debug "[testcase_sandbox_start_upsd_after_drivers] Tried to start UPSD as PID $PID_UPSD" @@ -1258,7 +1436,7 @@ testcase_sandbox_upsc_query_bogus() { FAILED_FUNCS="$FAILED_FUNCS testcase_sandbox_upsc_query_bogus" } # Note: avoid exact matching for stderr, because it can have Init SSL messages etc. - if echo "$CMDERR" | grep 'Error: Variable not supported by UPS' >/dev/null ; then + if echo "$CMDERR" | ${GREP} 'Error: Variable not supported by UPS' >/dev/null ; then PASSED="`expr $PASSED + 1`" log_info "[testcase_sandbox_upsc_query_bogus] PASSED: got expected reply to bogus query" else @@ -1312,9 +1490,9 @@ testcase_sandbox_upsc_query_timer() { kill -15 $PID_UPSLOG 2>/dev/null || true wait $PID_UPSLOG || true - if (grep " [OB] " "${NUT_STATEPATH}/upslog-dummy.log" && grep " [OL] " "${NUT_STATEPATH}/upslog-dummy.log") \ - || (grep " \[OB\] " "${NUT_STATEPATH}/upslog-dummy.log" && grep " \[OL\] " "${NUT_STATEPATH}/upslog-dummy.log") \ - || (echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OB" && echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | grep "OL") \ + if (${GREP} " [OB] " "${NUT_STATEPATH}/upslog-dummy.log" && ${GREP} " [OL] " "${NUT_STATEPATH}/upslog-dummy.log") \ + || (${GREP} " \[OB\] " "${NUT_STATEPATH}/upslog-dummy.log" && ${GREP} " \[OL\] " "${NUT_STATEPATH}/upslog-dummy.log") \ + || (echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | ${GREP} "OB" && echo "$OUT1$OUT2$OUT3$OUT4$OUT5" | ${GREP} "OL") \ ; then log_info "[testcase_sandbox_upsc_query_timer] PASSED: ups.status flips over time" PASSED="`expr $PASSED + 1`" @@ -1327,28 +1505,59 @@ testcase_sandbox_upsc_query_timer() { #rm -f "${NUT_STATEPATH}/upslog-dummy.log" || true } +PY_SHEBANG="" +PY_RES=127 isTestablePython() { # We optionally make python module (if interpreter is found): + case x"${PY_SHEBANG}" in + x"") ;; # Fall through to detection + *) return $PY_RES ;; # Probably resolved (if not a comment)? + esac + if [ x"${TOP_BUILDDIR}" = x ] \ || [ ! -x "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py" ] \ ; then return 1 fi - PY_SHEBANG="`head -1 "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py"`" - if [ x"${PY_SHEBANG}" = x"#!no" ] ; then - return 1 + + if [ x"${PYTHON}" != x ] ; then + PY_SHEBANG="#!${PYTHON}" + PY_RES=0 + return 0 fi - log_debug "=======\nDetected python shebang: '${PY_SHEBANG}'" - return 0 + + if [ x"${PYTHON_DEFAULT}" != x ] ; then + PYTHON="${PYTHON_DEFAULT}" + PY_SHEBANG="#!${PYTHON_DEFAULT}" + PY_RES=0 + return 0 + fi + + PY_SHEBANG="`head -1 \"${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py\"`" + PY_RES=3 + case x"${PY_SHEBANG}" in + x"#!"/*|x"#!"?":\\"*|x"#!"?":/"*) PY_RES=0 ;; # Seems like a full path + x"#!no") PY_RES=1 ;; # Explicitly skipped + x"#!@") PY_RES=2 ;; # Unresolved + *) PY_RES=3 ;; # Unexpected twist + esac + if [ x"${PY_RES}" = x0 ] ; then + log_debug "=======\nDetected python shebang: '${PY_SHEBANG}' (result=${PY_RES})" + PYTHON="`echo \"${PY_SHEBANG}\" | sed 's,^#!,,'`" + else + log_error "[isTestablePython] Detected python shebang: '${PY_SHEBANG}' (result=${PY_RES})" + fi + return $PY_RES } testcase_sandbox_python_without_credentials() { - isTestablePython || return 0 + isTestablePython && [ -n "${PYTHON}" ] || return 0 + log_separator log_info "[testcase_sandbox_python_without_credentials] Call Python module test suite: PyNUT (NUT Python bindings) without login credentials" if ( unset NUT_USER || true unset NUT_PASS || true - "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py" + $PYTHON "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py" ) ; then log_info "[testcase_sandbox_python_without_credentials] PASSED: PyNUT did not complain" PASSED="`expr $PASSED + 1`" @@ -1360,7 +1569,7 @@ testcase_sandbox_python_without_credentials() { } testcase_sandbox_python_with_credentials() { - isTestablePython || return 0 + isTestablePython && [ -n "${PYTHON}" ] || return 0 # That script says it expects data/evolution500.seq (as the UPS1 dummy) # but the dummy data does not currently let issue the commands and @@ -1371,7 +1580,7 @@ testcase_sandbox_python_with_credentials() { NUT_USER='admin' NUT_PASS="${TESTPASS_ADMIN}" export NUT_USER NUT_PASS - "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py" + $PYTHON "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py" ) ; then log_info "[testcase_sandbox_python_with_credentials] PASSED: PyNUT did not complain" PASSED="`expr $PASSED + 1`" @@ -1383,7 +1592,7 @@ testcase_sandbox_python_with_credentials() { } testcase_sandbox_python_with_upsmon_credentials() { - isTestablePython || return 0 + isTestablePython && [ -n "${PYTHON}" ] || return 0 log_separator log_info "[testcase_sandbox_python_with_upsmon_credentials] Call Python module test suite: PyNUT (NUT Python bindings) with upsmon role login credentials" @@ -1391,7 +1600,7 @@ testcase_sandbox_python_with_upsmon_credentials() { NUT_USER='dummy-admin' NUT_PASS="${TESTPASS_UPSMON_PRIMARY}" export NUT_USER NUT_PASS - "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py" + $PYTHON "${TOP_BUILDDIR}/scripts/python/module/test_nutclient.py" ) ; then log_info "[testcase_sandbox_python_with_upsmon_credentials] PASSED: PyNUT did not complain" PASSED="`expr $PASSED + 1`" @@ -1403,7 +1612,8 @@ testcase_sandbox_python_with_upsmon_credentials() { } testcases_sandbox_python() { - isTestablePython || return 0 + isTestablePython && [ -n "${PYTHON}" ] || return 0 + testcase_sandbox_python_without_credentials testcase_sandbox_python_with_credentials testcase_sandbox_python_with_upsmon_credentials @@ -1565,14 +1775,14 @@ testcase_sandbox_nutscanner_list() { # the scanned buses (serial, snmp, usb, etc.) if ( test -n "$CMDOUT" \ - && echo "$CMDOUT" | grep -E '^\[nutdev-nut1\]$' \ - && echo "$CMDOUT" | grep 'port = "dummy@' \ + && echo "$CMDOUT" | ${EGREP} '^\[nutdev-nut1\]$' \ + && echo "$CMDOUT" | ${GREP} 'port = "dummy@' \ || return if [ "${NUT_PORT}" = 3493 ] || [ x"$NUT_PORT" = x ]; then log_info "[testcase_sandbox_nutscanner_list] Note: not testing for suffixed port number" >&2 else - echo "$CMDOUT" | grep -E 'dummy@.*'":${NUT_PORT}" \ + echo "$CMDOUT" | ${EGREP} 'dummy@.*'":${NUT_PORT}" \ || { log_error "[testcase_sandbox_nutscanner_list] dummy@... not found" >&2 return 1 @@ -1582,10 +1792,10 @@ testcase_sandbox_nutscanner_list() { if [ x"${TOP_SRCDIR}" = x ]; then log_info "[testcase_sandbox_nutscanner_list] Note: only testing one dummy device" >&2 else - echo "$CMDOUT" | grep -E '^\[nutdev-nut2\]$' \ - && echo "$CMDOUT" | grep 'port = "UPS1@' \ - && echo "$CMDOUT" | grep -E '^\[nutdev-nut3\]$' \ - && echo "$CMDOUT" | grep 'port = "UPS2@' \ + echo "$CMDOUT" | ${EGREP} '^\[nutdev-nut2\]$' \ + && echo "$CMDOUT" | ${GREP} 'port = "UPS1@' \ + && echo "$CMDOUT" | ${EGREP} '^\[nutdev-nut3\]$' \ + && echo "$CMDOUT" | ${GREP} 'port = "UPS2@' \ || { log_error "[testcase_sandbox_nutscanner_list] something about UPS1/UPS2 not found" >&2 return 1 @@ -1597,7 +1807,7 @@ testcase_sandbox_nutscanner_list() { else PORTS_WANT=3 fi - PORTS_SEEN="`echo "$CMDOUT" | grep -Ec 'port *='`" + PORTS_SEEN="`echo \"$CMDOUT\" | ${EGREP} -c 'port *='`" if [ "$PORTS_WANT" != "$PORTS_SEEN" ]; then log_error "[testcase_sandbox_nutscanner_list] Too many 'port=' lines: want $PORTS_WANT != seen $PORTS_SEEN" >&2 @@ -1607,7 +1817,7 @@ testcase_sandbox_nutscanner_list() { log_info "[testcase_sandbox_nutscanner_list] PASSED: nut-scanner found all expected devices" PASSED="`expr $PASSED + 1`" else - if ( echo "$CMDERR" | grep -E "Cannot load NUT library.*libupsclient.*found.*NUT search disabled" ) ; then + if ( echo "$CMDERR" | ${EGREP} "Cannot load NUT library.*libupsclient.*found.*NUT search disabled" ) ; then log_warn "[testcase_sandbox_nutscanner_list] SKIP: ${TOP_BUILDDIR}/tools/nut-scanner/nut-scanner: $CMDERR" else log_error "[testcase_sandbox_nutscanner_list] nut-scanner complained or did not return all expected data, check above" @@ -1638,7 +1848,7 @@ upsmon_start_loop() { # but the sample script honours NUT_DEBUG_LEVEL_UPSSCHED if set NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_UPSMON}" fi - upsmon ${ARG_FG} & + upsmon ${ARG_FG} ${ARG_USER} & PID_UPSMON="$!" NUT_DEBUG_LEVEL="${NUT_DEBUG_LEVEL_ORIG}" log_debug "[${TESTCASE}] Tried to start UPSMON as PID $PID_UPSMON" @@ -1789,15 +1999,15 @@ if [ -n "${DEBUG_SLEEP-}" ] ; then fi if [ -z "$PID_UPSSCHED" ] && [ -s "$NUT_PIDPATH/upssched.pid" ] ; then - PID_UPSSCHED="`head -1 "$NUT_PIDPATH/upssched.pid"`" + PID_UPSSCHED="`head -1 \"$NUT_PIDPATH/upssched.pid\"`" fi log_separator log_info "Sleeping now as asked (for ${DEBUG_SLEEP} seconds starting `date -u`), so you can play with the driver and server running" log_info "Populated environment variables for this run into a file so you can source them: . '$NUT_CONFPATH/NIT.env'" printf "PID_NIT_SCRIPT='%s'\nexport PID_NIT_SCRIPT\n" "$$" >> "$NUT_CONFPATH/NIT.env" - set | grep -E '^TESTPASS_|PID_[^ =]*='"'?[0-9][0-9]*'?$" | while IFS='=' read K V ; do - V="`echo "$V" | tr -d "'"`" + set | ${EGREP} '^TESTPASS_|PID_[^ =]*='"'?[0-9][0-9]*'?$" | while IFS='=' read K V ; do + V="`echo \"$V\" | tr -d \"'\"`" # Dummy comment to reset syntax highlighting due to ' quote above if [ -n "$V" ] ; then printf "%s='%s'\nexport %s\n" "$K" "$V" "$K" diff --git a/tests/NIT/upssched.conf.in b/tests/NIT/upssched.conf.in index 04932ed48a..2a6d630c09 100644 --- a/tests/NIT/upssched.conf.in +++ b/tests/NIT/upssched.conf.in @@ -6,6 +6,8 @@ # Network UPS Tools - upssched.conf sample file for NIT suite # Note: template values are substituted by nit.sh, not during configure/build +#DEBUG_MIN 4 + CMDSCRIPT @TOP_BUILDDIR@/clients/upssched-cmd PIPEFN @NUT_STATEPATH@/upssched.pipe @@ -13,7 +15,7 @@ LOCKFN @NUT_STATEPATH@/upssched.lock # ============================================================================ # info2client -AT ONLINE * EXECUTE ONLINE +AT ONLINE * EXECUTE ONLINE-HANDLER # info2admin only AT ONLINE * CANCEL-TIMER BATT-STATUS-1s AT ONLINE * CANCEL-TIMER BATT-STATUS-2s @@ -39,7 +41,7 @@ AT ONLINE * START-TIMER LINE-STATUS-30 1800 AT ONLINE * START-TIMER LINE-STATUS-60 3600 # # info2client -AT ONBATT * EXECUTE ONBATT +AT ONBATT * EXECUTE ONBATT-HANDLER # info2admin only AT ONBATT * CANCEL-TIMER LINE-STATUS-1s AT ONBATT * CANCEL-TIMER LINE-STATUS-2s @@ -68,10 +70,11 @@ AT ONBATT * START-TIMER BATT-STATUS-60 3600 ######################### # info2client -AT REPLBATT * ONBATT * EXECUTE REPLBATT -AT NOCOMM * EXECUTE NOCOMM -AT FSD * EXECUTE FSD -AT SHUTDOWN * EXECUTE SHUTDOWN +AT REPLBATT * ONBATT * EXECUTE REPLBATT-HANDLER +AT NOCOMM * EXECUTE NOCOMM-HANDLER +AT FSD * EXECUTE FSD-HANDLER +AT SHUTDOWN * EXECUTE SHUTDOWN-HANDLER +AT SHUTDOWN_HOSTSYNC * EXECUTE SHUTDOWN_HOSTSYNC-HANDLER # info2admin only AT LOWBATT * EXECUTE LOWBATT-INFO diff --git a/tests/nut-driver-enumerator-test.sh b/tests/nut-driver-enumerator-test.sh index 13b401c0af..ff063d7723 100755 --- a/tests/nut-driver-enumerator-test.sh +++ b/tests/nut-driver-enumerator-test.sh @@ -1,6 +1,8 @@ #!/bin/sh -# Copyright (C) 2018 Eaton +# Copyright (C) +# 2018 Eaton +# 2020-2025 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 @@ -30,10 +32,14 @@ LC_ALL=C TZ=UTC export LANG LC_ALL TZ +# tools +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; export GREP ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; export EGREP ; } + ### Note: These are relative to where the selftest script lives, ### not the NUT top_srcdir etc. They can be exported by a Makefile. -[ -n "${BUILDDIR-}" ] || BUILDDIR="`dirname $0`" -[ -n "${SRCDIR-}" ] || SRCDIR="`dirname $0`" +[ -n "${BUILDDIR-}" ] || BUILDDIR="`dirname \"$0\"`" +[ -n "${SRCDIR-}" ] || SRCDIR="`dirname \"$0\"`" [ -n "${SHELL_PROGS-}" ] || SHELL_PROGS="/bin/sh" case "${DEBUG-}" in [Yy]|[Yy][Ee][Ss]) DEBUG=yes ;; @@ -64,28 +70,34 @@ fi FAIL_COUNT=0 GOOD_COUNT=0 -callNDE() { +callSHELL() { + # Calls shell as-is, pass the script name (or stdin) to execute some logic case "$DEBUG" in - yes) time $USE_SHELL $NDE "$@" ;; - trace) time $USE_SHELL -x $NDE "$@" ;; - *) $USE_SHELL $NDE "$@" 2>/dev/null ;; + yes) time $USE_SHELL "$@" ;; + trace) time $USE_SHELL -x "$@" ;; + *) $USE_SHELL "$@" 2>/dev/null ;; esac } -run_testcase() { - # First 3 args are required as defined below; the rest are +callNDE() { + callSHELL $NDE "$@" +} + +run_testcase_generic() { + # First 4 args are required as defined below; the rest are # CLI arg(s) to nut-driver-enumerator.sh - CASE_DESCR="$1" - EXPECT_CODE="$2" - EXPECT_TEXT="$3" - shift 3 + CASE_CMD="$1" + CASE_DESCR="$2" + EXPECT_CODE="$3" # Can be '*' to ignore errors + EXPECT_TEXT="$4" + shift 4 printf "Testing : SHELL='%s'\tCASE='%s'\t" "$USE_SHELL" "$CASE_DESCR" - OUT="`callNDE "$@"`" ; RESCODE=$? + OUT="`$CASE_CMD \"$@\"`" ; RESCODE=$? printf "Got : RESCODE='%s'\t" "$RESCODE" RES=0 - if [ "$RESCODE" = "$EXPECT_CODE" ]; then + if [ "$RESCODE" = "$EXPECT_CODE" ] || [ x'*' = x"$EXPECT_CODE" ]; then printf "STATUS_CODE='MATCHED'\t" GOOD_COUNT="`expr $GOOD_COUNT + 1`" else @@ -104,15 +116,30 @@ run_testcase() { ( rm -f "/tmp/.nde.text.expected.$$" "/tmp/.nde.text.actual.$$" \ && echo "$EXPECT_TEXT" > "/tmp/.nde.text.expected.$$" \ && echo "$OUT" > "/tmp/.nde.text.actual.$$" \ - && diff -u "/tmp/.nde.text.expected.$$" "/tmp/.nde.text.actual.$$" ) 2>/dev/null || true + && { OUTD="`diff -u \"/tmp/.nde.text.expected.$$\" \"/tmp/.nde.text.actual.$$\" 2>/dev/null`" + if echo "$OUTD" | head -1 | ${EGREP} '^[-+]' >/dev/null ; then + echo "$OUTD" + else + diff "/tmp/.nde.text.expected.$$" "/tmp/.nde.text.actual.$$" + fi + } ; ) 2>/dev/null || true rm -f "/tmp/.nde.text.expected.$$" "/tmp/.nde.text.actual.$$" - FAIL_COUNT="`expr $FAIL_COUNT + 1`" - RES="`expr $RES + 2`" + if [ x'*' = x"$EXPECT_CODE" ] ; then + echo 'MISMATCH IGNORED because EXPECT_CODE=*' >&2 + else + FAIL_COUNT="`expr $FAIL_COUNT + 1`" + RES="`expr $RES + 2`" + fi fi if [ "$RES" != 0 ] || [ -n "$DEBUG" ] ; then echo "" ; fi return $RES } +run_testcase() { + # Main call for NDE test suites: + run_testcase_generic callNDE "$@" +} + ################################################################## # Note: expectations in test cases below are tightly connected # # to both the current code in the script and content of the test # @@ -288,6 +315,48 @@ globalflag" \ --show-config-value '' nosuchflag } +# This one is not about NDE as such, but piggy-backs on our ability to test +# multiple shells at once, with a test relevant for how we write scripts +# (with backticks to remain compatible with older Bourne-like shells). +# In particular, we have a problem with KSH on Solaris, treating double +# quotes inside backticked text which is wrapped in more double quotes +# (so some command execution would be a single token) as an end of a +# double-quoted token and so abortion of a command mid-way; other shells +# were not seen to work this way: +testcase_backticks_cmd_natural() { + #DEBUG=yes \ + callSHELL << 'EOF' + nut_with_python=yes; nut_with_python2=no; nut_with_python3=auto-prio=3; PYTHON=python; PYTHON2=auto-py; PYTHON3=python3 + RES=0 + FOUND_PYTHONS="`( echo "${nut_with_python}|${PYTHON}"; echo "${nut_with_python2}|${PYTHON2}"; echo "${nut_with_python3}|${PYTHON3}" ) | ${EGREP} -v '\|\(no\)*$' | ${EGREP} -v '^no\|'`" || RES=$? + echo "${FOUND_PYTHONS}" + exit $RES +EOF +} + +testcase_backticks_cmd_escaped() { + callSHELL << 'EOF' + nut_with_python=yes; nut_with_python2=no; nut_with_python3=auto-prio=3; PYTHON=python; PYTHON2=auto-py; PYTHON3=python3 + RES=0 + # Escape the quotes, following examples at + # https://stackoverflow.com/a/33301370/4715872 + FOUND_PYTHONS="`( echo \"${nut_with_python}|${PYTHON}\"; echo \"${nut_with_python2}|${PYTHON2}\"; echo \"${nut_with_python3}|${PYTHON3}\" ) | ${EGREP} -v '\|\(no\)*$' | ${EGREP} -v '^no\|'`" || RES=$? + echo "${FOUND_PYTHONS}" + exit $RES +EOF +} + +testcase_backticks() { + run_testcase_generic testcase_backticks_cmd_natural \ + "Backticks wrapped in doublequotes, with doublequoted text inside (can fail on some interpreters)" '*' \ +"yes|python +auto-prio=3|python3" + + run_testcase_generic testcase_backticks_cmd_escaped \ + "Backticks wrapped in doublequotes, with escaped-doublequoted text inside" 0 \ +"yes|python +auto-prio=3|python3" +} # Combine the cases above into a stack testsuite() { @@ -298,6 +367,8 @@ testsuite() { testcase_globalSection # This one can take a while, put it last testcase_upslist_debug + # Something very different + testcase_backticks } # If no args... @@ -311,4 +382,26 @@ done echo "Test suite for nut-driver-enumerator has completed with $FAIL_COUNT failed cases and $GOOD_COUNT good cases" >&2 -[ "$FAIL_COUNT" = 0 ] || { echo "As a developer, you may want to export DEBUG=trace or export DEBUG=yes and re-run the test; also make sure you meant the nut-driver-enumerator.sh implementation as NDE='$NDE'" >&2 ; exit 1; } +[ "$FAIL_COUNT" = 0 ] || { + echo "As a developer, you may want to export DEBUG=trace or export DEBUG=yes and re-run the test; also make sure you meant the nut-driver-enumerator.sh implementation as NDE='$NDE'" + for USE_SHELL in $SHELL_PROGS ; do + case "$USE_SHELL" in + */*) test -x "`echo $USE_SHELL | awk '{print $1}'`" ;; + busybox|busybox_sh|"busybox sh") command -v busybox ;; + *) command -v $USE_SHELL ;; + esac || echo "WARNING: Could not resolve shell interpreter '$USE_SHELL' in PATH='$PATH'" + + case "$USE_SHELL" in + */sh|sh) + echo "WARNING: This test was executed with a system default shell interpreter '$USE_SHELL'; depends on implementation whether it supports the (legacy) POSIX syntax our scripts are written for or not" ;; + */ksh*|ksh*|*/ast-ksh*|ast-ksh*|*/oksh*|oksh*) + echo "INFO: We have reports that shell interpreter '$USE_SHELL' should support the (legacy) POSIX syntax our scripts are written for, but beware double-quotes inside and outside backticks" ;; + busybox|busybox_sh|"busybox sh"|*/busybox|*/bash|bash|*/dash|dash) + echo "INFO: We have reports that shell interpreter '$USE_SHELL' should support the (legacy) POSIX syntax our scripts are written for" ;; + */zsh|zsh|*/csh|*/tcsh) + echo "WARNING: Sadly, we have reports that shell interpreter '$USE_SHELL' does not support the (legacy) POSIX syntax our scripts are written for" ;; + *) echo "WARNING: We do not have confirmation whether shell interpreter '$USE_SHELL' supports the (legacy) POSIX syntax our scripts are written for or not" ;; + esac + done + exit 1 +} >&2 diff --git a/tools/Makefile.am b/tools/Makefile.am index de8754d4ba..f20e7412d3 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -22,7 +22,7 @@ # make, to handle developer workflow (editing the *.c sources this uses). SUBDIRS = . nut-scanner nutconf -PYTHON = @PYTHON@ +PYTHON_DEFAULT = @PYTHON_DEFAULT@ EXTRA_DIST = nut-usbinfo.pl nut-recorder.sh nut-ddl-dump.sh nut-dumpdiff.sh \ gitlog2changelog.py.in gitlog2version.sh nut-snmpinfo.py.in driver-list-format.sh @@ -75,13 +75,13 @@ nut-scanner-deps-snmpinfo: $(GENERATED_SNMP_FILES) nut-scanner-deps-usb: $(GENERATED_USB_FILES) # The distributed nut-snmpinfo.py.in template is assumed to only differ from -# a generated nut-snmpinfo.py by the @PYTHON@ shebang. +# a generated nut-snmpinfo.py by the @PYTHON_DEFAULT@ shebang. $(GENERATED_SNMP_FILES): $(top_srcdir)/drivers/*-mib.c $(top_srcdir)/tools/nut-snmpinfo.py.in - @if [ -n "$(PYTHON)" ] && [ x"no" != x"$(PYTHON)" ] && $(PYTHON) -c 1; then \ - echo "Regenerating the SNMP helper files in SRC dir with '$(PYTHON)'."; \ + @if [ -n "$(PYTHON_DEFAULT)" ] && [ x"no" != x"$(PYTHON_DEFAULT)" ] && $(PYTHON_DEFAULT) -c 1; then \ + echo "Regenerating the SNMP helper files in SRC dir with '$(PYTHON_DEFAULT)'."; \ TOP_SRCDIR="$(top_srcdir)" ; export TOP_SRCDIR; \ TOP_BUILDDIR="$(top_builddir)" ; export TOP_BUILDDIR; \ - cd $(builddir) && $(PYTHON) $(top_srcdir)/tools/nut-snmpinfo.py.in; \ + cd $(builddir) && $(PYTHON_DEFAULT) $(top_srcdir)/tools/nut-snmpinfo.py.in; \ else \ echo "----------------------------------------------------------------------"; \ echo "Warning: Python is not available."; \ @@ -103,19 +103,19 @@ $(GENERATED_SNMP_FILES): $(top_srcdir)/drivers/*-mib.c $(top_srcdir)/tools/nut-s # use files pre-generated in SRCDIR if those in BUILDDIR dir are empty # or did not appear. $(GENERATED_USB_OS_FILES): $(GENERATED_USB_FILES) - @test -s "$@" || echo "WARNING: GENERATED_USB_OS_FILES: '$@' is empty or missing; do not use this build to generate 'dist' tarballs" >&2 + @test -s '$@' || echo "WARNING: GENERATED_USB_OS_FILES: '$@' is empty or missing; do not use this build to generate 'dist' tarballs" >&2 @$(MKDIR_P) "$(@D)" - @if ! test -s "$@" ; then \ + @if test ! -s '$@' ; then \ if test x"$(top_srcdir)" != x"$(top_builddir)" ; then \ - RELNAME="`echo "$@" | sed 's,^$(top_builddir)/*,,'`" ; \ + RELNAME="`echo '$@' | sed 's,^$(top_builddir)/*,,'`" ; \ SRCNAME="$(top_srcdir)/$${RELNAME}" ; \ if test -s "$${SRCNAME}" ; then \ echo "WARNING: GENERATED_USB_OS_FILES: using '$${SRCNAME}' for '$@'" >&2 ; \ - cp "$${SRCNAME}" "$@" ; \ + cp "$${SRCNAME}" '$@' ; \ fi ; \ fi ; \ fi - @touch "$@" + @touch '$@' $(GENERATED_USB_FILES): $(top_srcdir)/drivers/*-hid.c $(top_srcdir)/drivers/*usb*.c $(top_srcdir)/drivers/nutdrv_qx.c $(top_srcdir)/tools/nut-usbinfo.pl @if perl -e 1; then \ @@ -138,13 +138,13 @@ $(GENERATED_USB_FILES): $(top_srcdir)/drivers/*-hid.c $(top_srcdir)/drivers/*usb # so it may write the files in "dist" case (read-only sources), but the script # is called from the distdir where its copy is present. # The distributed nut-snmpinfo.py.in template is assumed to only differ from -# a generated nut-snmpinfo.py by the @PYTHON@ shebang. +# a generated nut-snmpinfo.py by the @PYTHON_DEFAULT@ shebang. dist-hook: - @if [ -n "$(PYTHON)" ] && [ x"no" != x"$(PYTHON)" ] && $(PYTHON) -c 1; then \ - echo "Regenerating the SNMP helper files in DIST dir with '$(PYTHON)'."; \ + @if [ -n "$(PYTHON_DEFAULT)" ] && [ x"no" != x"$(PYTHON_DEFAULT)" ] && $(PYTHON_DEFAULT) -c 1; then \ + echo "Regenerating the SNMP helper files in DIST dir with '$(PYTHON_DEFAULT)'."; \ TOP_SRCDIR="$(top_srcdir)" ; export TOP_SRCDIR; \ TOP_BUILDDIR="$(top_builddir)" ; export TOP_BUILDDIR; \ - $(PYTHON) $(distdir)/nut-snmpinfo.py.in; \ + $(PYTHON_DEFAULT) $(distdir)/nut-snmpinfo.py.in; \ else \ echo "----------------------------------------------------------------------"; \ echo "Warning: Python is not available."; \ diff --git a/tools/driver-list-format.sh b/tools/driver-list-format.sh index 23dafc58d6..4e36f55c36 100755 --- a/tools/driver-list-format.sh +++ b/tools/driver-list-format.sh @@ -12,7 +12,7 @@ ################################################################################ # Adapt path for either dist target or manual call -CURRENT_PATH="`dirname $0`" +CURRENT_PATH="`dirname \"$0\"`" DRVLIST_PATH="" # Integrate with Makefile recipes @@ -54,6 +54,10 @@ fi RES=0 +# tools +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; } + # Some sed/grep implementations tend to have a problem with "\t" # (treat it as escaped "t" character); substitutions are okay: TABCHAR="`printf '\t'`" @@ -70,8 +74,8 @@ do # * if there is a trailing comment, make sure it is # also TAB-separated (from the presumed sixth field) sed \ - -e '/^#/!s/\"[[:blank:]]\+\"/\"\t\"/g' \ - -e '/^#/!s/[[:blank:]]*$//' \ + -e '/^#/!s/\"[ '"${TABCHAR}"']\+\"/\"\t\"/g' \ + -e '/^#/!s/[ '"${TABCHAR}"']*$//' \ -e '/^#/!s/\" \+\#/\"\t\#/' \ < "${DRVLIST_PATH}/${drvfile}" \ > "${TMPBUILD_PATH}/${drvfile}.tabbed" \ @@ -80,7 +84,7 @@ do # verify that lines are either empty, all-comments, # or have six quoted fields (and optional comment); # the fields may be empty (just two double-quotes). - BADLINES="`grep -vE "${VALID_LINE}" < "${TMPBUILD_PATH}/${drvfile}.tabbed"`" + BADLINES="`$EGREP -v \"${VALID_LINE}\" < \"${TMPBUILD_PATH}/${drvfile}.tabbed\"`" if [ x"${BADLINES}" != x ] ; then echo "$0: ERROR: markup of '${DRVLIST_PATH}/${drvfile}' needs to be fixed: some lines are not exactly 6 fields (and optional comment)" >&2 echo "$BADLINES" | head -5 @@ -96,13 +100,29 @@ do # Ensure new content is applied mv -f "${TMPBUILD_PATH}/${drvfile}.tabbed" "${DRVLIST_PATH}/${drvfile}" fi - else # Checking - diff -u "${TMPBUILD_PATH}/${drvfile}.tabbed" "${DRVLIST_PATH}/${drvfile}" \ - || { GITACT="" - case "${drvfile}" in *.in) GITACT=" and commit the git change" ;; esac - echo "$0: ERROR: markup of '${DRVLIST_PATH}/${drvfile}' needs to be fixed: re-run this script without args${GITACT}, please" >&2 - RES=1 - } + else # Checking; also report the diff markup + if OUTD="`diff -u \"${TMPBUILD_PATH}/${drvfile}.tabbed\" \"${DRVLIST_PATH}/${drvfile}\" 2>/dev/null`" ; then + # Ok, no differences encountered + OUTD="" + else + # Either different contents, or "diff -u" does not work + if echo "$OUTD" | head -1 | ${EGREP} '^[-+]' >/dev/null ; then + true + else + if OUTD="`diff \"${TMPBUILD_PATH}/${drvfile}.tabbed\" \"${DRVLIST_PATH}/${drvfile}\"`" ; then + # Ok, no differences encountered + OUTD="" + fi + fi + fi + + if test -n "${OUTD}" ; then + echo "$OUTD" + GITACT="" + case "${drvfile}" in *.in) GITACT=" and commit the git change" ;; esac + echo "$0: ERROR: markup of '${DRVLIST_PATH}/${drvfile}' needs to be fixed: re-run this script without args${GITACT}, please" >&2 + RES=1 + fi fi \ || RES=$? diff --git a/tools/gitlog2changelog.py.in b/tools/gitlog2changelog.py.in index 67c9c0a169..812468df00 100755 --- a/tools/gitlog2changelog.py.in +++ b/tools/gitlog2changelog.py.in @@ -1,4 +1,4 @@ -#!@PYTHON@ +#!@PYTHON_DEFAULT@ # Copyright 2008 Marcus D. Hanwell # Minor changes for NUT by Charles Lepple # Subsequent maintenance for NUT by Jim Klimov (since 2021) diff --git a/tools/gitlog2version.sh b/tools/gitlog2version.sh index 92938424d2..a33ad54505 100755 --- a/tools/gitlog2version.sh +++ b/tools/gitlog2version.sh @@ -70,14 +70,22 @@ TZ=UTC export LANG LC_ALL TZ if [ x"${abs_top_srcdir}" = x ]; then - SCRIPT_DIR="`dirname "$0"`" - SCRIPT_DIR="`cd "${SCRIPT_DIR}" && pwd`" + SCRIPT_DIR="`dirname \"$0\"`" + SCRIPT_DIR="`cd \"${SCRIPT_DIR}\" && pwd`" abs_top_srcdir="${SCRIPT_DIR}/.." fi if [ x"${abs_top_builddir}" = x ]; then abs_top_builddir="${abs_top_srcdir}" fi +SRC_IS_GIT=false +if ( [ -e "${abs_top_srcdir}/.git" ] ) 2>/dev/null || [ -d "${abs_top_srcdir}/.git" ] || [ -f "${abs_top_srcdir}/.git" ] || [ -h "${abs_top_srcdir}/.git" ] ; then + SRC_IS_GIT=true +fi + +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; } + ############################################################################ # Numeric-only default version, for AC_INIT and similar consumers # in case we build without a Git workspace (from tarball, etc.) @@ -122,25 +130,25 @@ fi if [ -z "${NUT_VERSION_DEFAULT-}" -a -s "${abs_top_builddir}/VERSION_DEFAULT" ] ; then . "${abs_top_builddir}/VERSION_DEFAULT" || exit - [ x"${NUT_VERSION_PREFER_GIT-}" = xtrue ] || { [ -e "${abs_top_srcdir}/.git" ] || NUT_VERSION_PREFER_GIT=false ; } + [ x"${NUT_VERSION_PREFER_GIT-}" = xtrue ] || { [ x"${SRC_IS_GIT}" = xtrue ] || NUT_VERSION_PREFER_GIT=false ; } fi if [ -z "${NUT_VERSION_DEFAULT-}" -a -s "${abs_top_srcdir}/VERSION_DEFAULT" ] ; then . "${abs_top_srcdir}/VERSION_DEFAULT" || exit - [ x"${NUT_VERSION_PREFER_GIT-}" = xtrue ] || { [ -e "${abs_top_srcdir}/.git" ] || NUT_VERSION_PREFER_GIT=false ; } + [ x"${NUT_VERSION_PREFER_GIT-}" = xtrue ] || { [ x"${SRC_IS_GIT}" = xtrue ] || NUT_VERSION_PREFER_GIT=false ; } fi # Fallback default, to be updated only during release cycle -[ -n "${NUT_VERSION_DEFAULT-}" ] || NUT_VERSION_DEFAULT='2.8.4' +[ -n "${NUT_VERSION_DEFAULT-}" ] || NUT_VERSION_DEFAULT='2.8.4.1' # Default website paths, extended for historic sub-sites for a release [ -n "${NUT_WEBSITE-}" ] || NUT_WEBSITE="https://www.networkupstools.org/" # Must be "true" or "false" exactly, interpreted as such below: -[ x"${NUT_VERSION_PREFER_GIT-}" = xfalse ] || { [ -e "${abs_top_srcdir}/.git" ] && NUT_VERSION_PREFER_GIT=true || NUT_VERSION_PREFER_GIT=false ; } +[ x"${NUT_VERSION_PREFER_GIT-}" = xfalse ] || { [ x"${SRC_IS_GIT}" = xtrue ] && NUT_VERSION_PREFER_GIT=true || NUT_VERSION_PREFER_GIT=false ; } check_shallow_git() { - if git log --oneline --decorate=short | tail -1 | grep -w grafted >&2 || [ 10 -gt `git log --oneline | wc -l` ] ; then + if git log --oneline --decorate=short | tail -1 | $GREP -w grafted >&2 || [ 10 -gt `git log --oneline | wc -l` ] ; then echo "$0: $1" >&2 fi } @@ -154,7 +162,7 @@ getver_git() { # Currently we repeat the likely branch names in the # end, so that if they exist and are still newest - # those are the names to report. - for T in master `git branch -a 2>/dev/null | grep -E '^ *remotes/[^ ]*/master$'` origin/master upstream/master master ; do + for T in master `git branch -a 2>/dev/null | ${EGREP} '^ *remotes/[^ ]*/master$'` origin/master upstream/master master ; do git log -1 "$T" 2>/dev/null >/dev/null || continue if [ x"${NUT_VERSION_GIT_TRUNK-}" = x ] ; then NUT_VERSION_GIT_TRUNK="$T" @@ -189,12 +197,12 @@ getver_git() { # NOTE: match/exclude by shell glob expressions, not regex! DESC="`git describe $ALL_TAGS_ARG $ALWAYS_DESC_ARG --match 'v[0-9]*.[0-9]*.[0-9]' --exclude '*-signed' --exclude '*rc*' --exclude '*alpha*' --exclude '*beta*' --exclude '*Windows*' --exclude '*IPM*' 2>/dev/null`" \ && [ -n "${DESC}" ] \ - || DESC="`git describe $ALL_TAGS_ARG $ALWAYS_DESC_ARG | grep -Ev '(rc|-signed|alpha|beta|Windows|IPM)' | grep -E 'v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*'`" + || DESC="`git describe $ALL_TAGS_ARG $ALWAYS_DESC_ARG | ${EGREP} -v '(rc|-signed|alpha|beta|Windows|IPM)' | ${EGREP} 'v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*'`" # Old stripper (also for possible refspec parts like "tags/"): # echo "${DESC}" | sed -e 's/^v\([0-9]\)/\1/' -e 's,^.*/,,' # Follow https://semver.org/#spec-item-10 about build metadata: # it is (a dot-separated list) separated by a plus sign from preceding - DESC="`echo "${DESC}" | sed 's/\(-[0-9][0-9]*\)-\(g[0-9a-fA-F][0-9a-fA-F]*\)$/\1+\2/'`" + DESC="`echo \"${DESC}\" | sed 's/\(-[0-9][0-9]*\)-\(g[0-9a-fA-F][0-9a-fA-F]*\)$/\1+\2/'`" if [ x"${DESC}" = x ] ; then echo "$0: FAILED to 'git describe' this codebase" >&2 check_shallow_git "NOTE: Current checkout is shallow, may not include enough history to describe it" @@ -208,7 +216,7 @@ getver_git() { # string over longer ones if available, or older RC over newer release # like "v2.8.2-rc8" preferred over "v2.8.3" if they happen to be tagging # the same commit): - DESC_PRERELEASE="`git describe --tags | grep -E '^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*([0-9]*|[-](rc|alpha|beta)[-]*[0-9][0-9]*)$'`" \ + DESC_PRERELEASE="`git describe --tags | ${EGREP} '^v[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*([0-9]*|[-](rc|alpha|beta)[-]*[0-9][0-9]*)$'`" \ || DESC_PRERELEASE="" # How much of the known trunk history is in current HEAD? @@ -216,7 +224,7 @@ getver_git() { # some of it if looking at a historic snapshot, or nothing if looking # at the tagged commit (it is the merge base for itself and any of # its descendants): - BASE="`git merge-base HEAD "${NUT_VERSION_GIT_TRUNK}"`" || BASE="" + BASE="`git merge-base HEAD \"${NUT_VERSION_GIT_TRUNK}\"`" || BASE="" if [ x"${BASE}" = x ] ; then echo "$0: FAILED to get a git merge-base of this codebase vs. '${NUT_VERSION_GIT_TRUNK}'" >&2 check_shallow_git "NOTE: Current checkout is shallow, may not include enough history to describe it or find intersections with other trees" @@ -225,10 +233,10 @@ getver_git() { fi # Nearest (annotated by default) tag preceding the HEAD in history: - TAG="`echo "${DESC}" | sed 's/-[0-9][0-9]*[+-]g[0-9a-fA-F][0-9a-fA-F]*$//'`" + TAG="`echo \"${DESC}\" | sed 's/-[0-9][0-9]*[+-]g[0-9a-fA-F][0-9a-fA-F]*$//'`" TAG_PRERELEASE="" if [ -n "${DESC_PRERELEASE}" ] ; then - TAG_PRERELEASE="`echo "${DESC_PRERELEASE}" | sed 's/-[0-9][0-9]*[+-]g[0-9a-fA-F][0-9a-fA-F]*$//'`" + TAG_PRERELEASE="`echo \"${DESC_PRERELEASE}\" | sed 's/-[0-9][0-9]*[+-]g[0-9a-fA-F][0-9a-fA-F]*$//'`" if [ x"${DESC_PRERELEASE}" != x"${TAG_PRERELEASE}" ] ; then # We did chop off something, so `git describe` above did not hit # exactly the tagged commit, but something later - not interesting @@ -242,13 +250,13 @@ getver_git() { # Commit count since the tag, and hash, of the current HEAD commit; # empty e.g. when HEAD is the release-tagged commit: - SUFFIX="`echo "${DESC}" | sed 's/^.*\(-[0-9][0-9]*[+-]g[0-9a-fA-F][0-9a-fA-F]*\)$/\1/'`" \ + SUFFIX="`echo \"${DESC}\" | sed 's/^.*\(-[0-9][0-9]*[+-]g[0-9a-fA-F][0-9a-fA-F]*\)$/\1/'`" \ && [ x"${SUFFIX}" != x"${TAG}" ] || SUFFIX="" # Tack on "this commit is a pre-release!" info, if known SUFFIX_PRERELEASE="" if [ -n "${TAG_PRERELEASE}" ] ; then - SUFFIX_PRERELEASE="`echo "${TAG_PRERELEASE}" | tr '-' '+'`" + SUFFIX_PRERELEASE="`echo \"${TAG_PRERELEASE}\" | tr '-' '+'`" if [ -n "${SUFFIX}" ] ; then SUFFIX="${SUFFIX}+${SUFFIX_PRERELEASE}" else @@ -259,11 +267,11 @@ getver_git() { # 5-digit version, note we strip leading "v" from the expected TAG value # Note the commit count will be non-trivial even if this is commit tagged # as a final release but it is not (yet?) on the BASE branch! - VER5="${TAG#v}.`git log --oneline "${TAG}..${BASE}" | wc -l | tr -d ' '`.`git log --oneline "${NUT_VERSION_GIT_TRUNK}..HEAD" | wc -l | tr -d ' '`" + VER5="`echo "${TAG}" | sed 's,^v,,'`.`git log --oneline "${TAG}..${BASE}" | wc -l | tr -d ' '`.`git log --oneline "${NUT_VERSION_GIT_TRUNK}..HEAD" | wc -l | tr -d ' '`" DESC5="${VER5}${SUFFIX}" # Strip up to two trailing zeroes for trunk snapshots and releases - VER50="`echo "${VER5}" | sed -e 's/\.0$//' -e 's/\.0$//'`" + VER50="`echo \"${VER5}\" | sed -e 's/\.0$//' -e 's/\.0$//'`" DESC50="${VER50}${SUFFIX}" # Leave exactly 3 components @@ -273,9 +281,9 @@ getver_git() { if [ -n "${TAG_PRERELEASE}" ] ; then # Actually report as SEMVER the version of (next) release # for which this commit is candidate - SEMVER="`echo "${TAG_PRERELEASE}" | sed -e 's/^v*//' -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)[^0-9].*$/\1/'`" + SEMVER="`echo \"${TAG_PRERELEASE}\" | sed -e 's/^v*//' -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)[^0-9].*$/\1/'`" else - SEMVER="`echo "${VER5}" | sed -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)\..*$/\1/'`" + SEMVER="`echo \"${VER5}\" | sed -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)\..*$/\1/'`" fi fi # FIXME? Add ".0" up to 3 components? @@ -299,41 +307,41 @@ getver_default() { # Assume triplet (possibly prefixed with `v`) + suffix # like `v2.8.3-rc6` or `2.8.2-beta-1` # FIXME: Check the assumption better! - SUFFIX="`echo "${NUT_VERSION_DEFAULT}" | grep -E '^v*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*([0-9]*|[-](rc|alpha|beta)[-]*[0-9][0-9]*)$' | sed -e 's/^v*//' -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)\([^0-9].*\)$/\2/'`" \ + SUFFIX="`echo \"${NUT_VERSION_DEFAULT}\" | ${EGREP} '^v*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*([0-9]*|[-](rc|alpha|beta)[-]*[0-9][0-9]*)$' | sed -e 's/^v*//' -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)\([^0-9].*\)$/\2/'`" \ && [ -n "${SUFFIX}" ] \ - && SUFFIX_PRERELEASE="`echo "${SUFFIX}" | sed 's/^-*//'`" \ - && NUT_VERSION_DEFAULT="`echo "${NUT_VERSION_DEFAULT}" | sed -e 's/'"${SUFFIX}"'$//'`" + && SUFFIX_PRERELEASE="`echo \"${SUFFIX}\" | sed 's/^-*//'`" \ + && NUT_VERSION_DEFAULT="`echo \"${NUT_VERSION_DEFAULT}\" | sed -e 's/'\"${SUFFIX}\"'$//'`" ;; *+rc*|*+alpha*|*+beta*) # Consider forced `2.8.2.2878.3-2881+g45029249f+v2.8.3+rc6` values # We remove up to 5 dot-separated leading numbers, so # for the example above, `-2881+g45029249f` remains: - tmpSUFFIX="`echo "${NUT_VERSION_DEFAULT}" | grep -E '^v*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*(.*\+(rc|alpha|beta)[+-]*[0-9][0-9]*)$' | sed -e 's/^v*//' -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)\([^0-9].*\)$/\2/' -e 's/^\(\.[0-9][0-9]*\)//' -e 's/^\(\.[0-9][0-9]*\)//'`" \ + tmpSUFFIX="`echo \"${NUT_VERSION_DEFAULT}\" | ${EGREP} '^v*[0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*(.*\+(rc|alpha|beta)[+-]*[0-9][0-9]*)$' | sed -e 's/^v*//' -e 's/^\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\)\([^0-9].*\)$/\2/' -e 's/^\(\.[0-9][0-9]*\)//' -e 's/^\(\.[0-9][0-9]*\)//'`" \ || tmpSUFFIX="" if [ -n "${tmpSUFFIX}" ] && [ x"${tmpSUFFIX}" != "${NUT_VERSION_DEFAULT}" ] ; then # Extract tagged NUT version from that suffix SUFFIX="${tmpSUFFIX}" # for the example above, `v2.8.3+rc6` remains - tmpTAG_PRERELEASE="`echo "${tmpSUFFIX}" | sed 's/^.*[^0-9]\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[+]\(rc\|alpha\|beta\)[+-]*[0-9][0-9]*\)$/\1/'`" \ + tmpTAG_PRERELEASE="`echo \"${tmpSUFFIX}\" | sed 's/^.*[^0-9]\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*[+]\(rc\|alpha\|beta\)[+-]*[0-9][0-9]*\)$/\1/'`" \ || tmpTAG_PRERELEASE="" if [ -n "${tmpTAG_PRERELEASE}" ] && [ x"${tmpSUFFIX}" != "${tmpTAG_PRERELEASE}" ] ; then # Replace back pluses to dashes for the tag TAG_PRERELEASE="v`echo "${tmpTAG_PRERELEASE}" | sed -e 's/[+]\(rc\|alpha\|beta\)/-\1/' -e 's/\(rc\|alpha\|beta\)[+]/\1-/'`" if [ -z "${SEMVER}" ] ; then # for the example above, `2.8.3` remains: - SEMVER="`echo "${tmpTAG_PRERELEASE}" | sed -e 's/[-+].*$//'`" + SEMVER="`echo \"${tmpTAG_PRERELEASE}\" | sed -e 's/[-+].*$//'`" fi # for the example above, `rc6` remains: - SUFFIX_PRERELEASE="`echo "${tmpTAG_PRERELEASE}" | sed 's/^[^+-]*[+-]//'`" + SUFFIX_PRERELEASE="`echo \"${tmpTAG_PRERELEASE}\" | sed 's/^[^+-]*[+-]//'`" # for the example above, `2.8.2.2878.3-2881+g45029249f` remains: - NUT_VERSION_DEFAULT="`echo "${NUT_VERSION_DEFAULT}" | sed -e 's/'"${SUFFIX}"'$//'`" + NUT_VERSION_DEFAULT="`echo \"${NUT_VERSION_DEFAULT}\" | sed -e 's/'\"${SUFFIX}\"'$//'`" fi fi ;; esac - NUT_VERSION_DEFAULT_DOTS="`echo "${NUT_VERSION_DEFAULT}" | sed 's/[^.]*//g' | tr -d '\n' | wc -c`" + NUT_VERSION_DEFAULT_DOTS="`echo \"${NUT_VERSION_DEFAULT}\" | sed 's/[^.]*//g' | tr -d '\n' | wc -c`" # Ensure at least 4 dots (5 presumed-numeric components) NUT_VERSION_DEFAULT5_DOTS="${NUT_VERSION_DEFAULT_DOTS}" @@ -351,7 +359,7 @@ getver_default() { NUT_VERSION_DEFAULT3_DOTS="`expr $NUT_VERSION_DEFAULT3_DOTS + 1`" done while [ "${NUT_VERSION_DEFAULT3_DOTS}" -gt 2 ] ; do - NUT_VERSION_DEFAULT3="`echo "${NUT_VERSION_DEFAULT3}" | sed 's,\.[0-9][0-9]*[^.]*$,,'`" + NUT_VERSION_DEFAULT3="`echo \"${NUT_VERSION_DEFAULT3}\" | sed 's,\.[0-9][0-9]*[^.]*$,,'`" NUT_VERSION_DEFAULT3_DOTS="`expr $NUT_VERSION_DEFAULT3_DOTS - 1`" done @@ -406,18 +414,22 @@ report_output() { # NOTE: For maintainers, changes SRCDIR not BUILDDIR; requires GIT # Do not "mv" here because maintainer files may be hard-linked from elsewhere echo "NUT_VERSION_FORCED='${DESC50}'" > "${abs_top_srcdir}/VERSION_FORCED.tmp" || exit - if ! cmp "${abs_top_srcdir}/VERSION_FORCED.tmp" "${abs_top_srcdir}/VERSION_FORCED" >/dev/null 2>/dev/null ; then + if cmp "${abs_top_srcdir}/VERSION_FORCED.tmp" "${abs_top_srcdir}/VERSION_FORCED" >/dev/null 2>/dev/null ; then + true + else cat "${abs_top_srcdir}/VERSION_FORCED.tmp" > "${abs_top_srcdir}/VERSION_FORCED" || exit fi rm -f "${abs_top_srcdir}/VERSION_FORCED.tmp" echo "NUT_VERSION_FORCED_SEMVER='${SEMVER}'" > "${abs_top_srcdir}/VERSION_FORCED_SEMVER.tmp" || exit - if ! cmp "${abs_top_srcdir}/VERSION_FORCED_SEMVER.tmp" "${abs_top_srcdir}/VERSION_FORCED_SEMVER" >/dev/null 2>/dev/null ; then + if cmp "${abs_top_srcdir}/VERSION_FORCED_SEMVER.tmp" "${abs_top_srcdir}/VERSION_FORCED_SEMVER" >/dev/null 2>/dev/null ; then + true + else cat "${abs_top_srcdir}/VERSION_FORCED_SEMVER.tmp" > "${abs_top_srcdir}/VERSION_FORCED_SEMVER" || exit fi rm -f "${abs_top_srcdir}/VERSION_FORCED_SEMVER.tmp" - grep . "${abs_top_srcdir}/VERSION_FORCED" "${abs_top_srcdir}/VERSION_FORCED_SEMVER" + $GREP . "${abs_top_srcdir}/VERSION_FORCED" "${abs_top_srcdir}/VERSION_FORCED_SEMVER" ;; "UPDATE_FILE") if [ x"${abs_top_builddir}" != x"${abs_top_srcdir}" ] \ diff --git a/tools/nut-ddl-dump.sh b/tools/nut-ddl-dump.sh index 4a1a9f3958..a651c05dfb 100755 --- a/tools/nut-ddl-dump.sh +++ b/tools/nut-ddl-dump.sh @@ -4,7 +4,7 @@ ################################################################################ # Authors: # Copyright (C) 2015 - 2017 by Arnaud Quette -# Copyright (C) 2020 - 2023 by Jim Klimov +# Copyright (C) 2020 - 2025 by Jim Klimov # License: GPL v2+ ################################################################################ # FIXME: @@ -24,7 +24,7 @@ else fi # Test communication with the device -upscResult="`upsc "${DDL_DEVICE_NAME}" 2> /dev/null`" +upscResult="`upsc \"${DDL_DEVICE_NAME}\" 2> /dev/null`" if [ $? -gt 0 ]; then echo "Can't communicate with ${DDL_DEVICE_NAME}" >&2 exit @@ -32,25 +32,25 @@ fi # Collect more information dumpDate="`date -R`" -upsrwResult="`upsrw "${DDL_DEVICE_NAME}" 2> /dev/null`" -upscmdResult="`upscmd -l "${DDL_DEVICE_NAME}" 2> /dev/null`" +upsrwResult="`upsrw \"${DDL_DEVICE_NAME}\" 2> /dev/null`" +upscmdResult="`upscmd -l \"${DDL_DEVICE_NAME}\" 2> /dev/null`" # Build the filename # ________. # Process the Manufacturer name -RAW_DDL_MFR="`upsc "${DDL_DEVICE_NAME}" device.mfr 2>/dev/null`" +RAW_DDL_MFR="`upsc \"${DDL_DEVICE_NAME}\" device.mfr 2>/dev/null`" if [ "${RAW_DDL_MFR}" = "EATON" ]; then RAW_DDL_MFR="Eaton" fi # Replace spaces with underscores -DDL_MFR="`echo "${RAW_DDL_MFR}" | sed s/\ /_/g`" +DDL_MFR="`echo \"${RAW_DDL_MFR}\" | sed s/\ /_/g`" # Process the Model name # Replace spaces with underscores -RAW_DDL_MODEL="`upsc "${DDL_DEVICE_NAME}" device.model 2>/dev/null`" -DDL_MODEL="`echo "${RAW_DDL_MODEL}" | sed s/\ /_/g`" +RAW_DDL_MODEL="`upsc \"${DDL_DEVICE_NAME}\" device.model 2>/dev/null`" +DDL_MODEL="`echo \"${RAW_DDL_MODEL}\" | sed s/\ /_/g`" # Process the driver name and NUT version -DDL_DRIVER_NAME="`upsc "${DDL_DEVICE_NAME}" driver.name 2>/dev/null`" -DDL_NUT_VERSION="`upsc "${DDL_DEVICE_NAME}" driver.version 2>/dev/null`" +DDL_DRIVER_NAME="`upsc \"${DDL_DEVICE_NAME}\" driver.name 2>/dev/null`" +DDL_NUT_VERSION="`upsc \"${DDL_DEVICE_NAME}\" driver.version 2>/dev/null`" # TODO: check if a similar file exists in nut-ddl repo, to update Report nb DDL_REPORT_NUMBER="01" DDL_FILENAME="${DDL_MFR}__${DDL_MODEL}__${DDL_DRIVER_NAME}__${DDL_NUT_VERSION}__${DDL_REPORT_NUMBER}.dev" diff --git a/tools/nut-dumpdiff.sh b/tools/nut-dumpdiff.sh index 8219b926ac..2fa45f4db2 100755 --- a/tools/nut-dumpdiff.sh +++ b/tools/nut-dumpdiff.sh @@ -9,7 +9,7 @@ # Subject to same license as the NUT Project. # # Copyright (C) -# 2022 Jim Klimov +# 2022-2025 Jim Klimov # if [ $# = 2 ] && [ -s "$1" ] && [ -s "$2" ]; then @@ -19,17 +19,21 @@ else exit 1 fi +# tools +[ -n "${GREP}" ] || { GREP="`command -v grep`" && [ x"${GREP}" != x ] || { echo "$0: FAILED to locate GREP tool" >&2 ; exit 1 ; } ; } +[ -n "${EGREP}" ] || { if ( [ x"`echo a | $GREP -E '(a|b)'`" = xa ] ) 2>/dev/null ; then EGREP="$GREP -E" ; else EGREP="`command -v egrep`" ; fi && [ x"${EGREP}" != x ] || { echo "$0: FAILED to locate EGREP tool" >&2 ; exit 1 ; } ; } + # Pre-sort just in case: -DATA1="`sort -n < "$1"`" -DATA2="`sort -n < "$2"`" +DATA1="`sort -n < \"$1\"`" +DATA2="`sort -n < \"$2\"`" # Strip away same-context lines, # and lines with measurements that are either decimal numbers # or multi-digit numbers without a decimal point (assuming # differences in shorter numbers or counters may be important) diff -bu <(echo "$DATA1") <(echo "$DATA2") \ -| grep -E '^[+-][^+-]' \ -| grep -vE '^[^:]*(power|load|voltage|current|frequency|temperature|humidity): ([0-9][0-9]*|[0-9][0-9]*\.[0-9][0-9]*)$' +| ${EGREP} '^[+-][^+-]' \ +| ${EGREP} -v '^[^:]*(power|load|voltage|current|frequency|temperature|humidity): ([0-9][0-9]*|[0-9][0-9]*\.[0-9][0-9]*)$' # Note: up to user to post-filter, "^driver.version.*:" # may be deemed irrelevant as well diff --git a/tools/nut-snmpinfo.py.in b/tools/nut-snmpinfo.py.in index 84f60dab3b..f89aa7ff2e 100755 --- a/tools/nut-snmpinfo.py.in +++ b/tools/nut-snmpinfo.py.in @@ -1,4 +1,4 @@ -#!@PYTHON@ +#!@PYTHON_DEFAULT@ # Copyright (C) 2011-2021 Eaton # Authors: Frederic Bohe # Arnaud Quette