From: Marco d'Itri Date: Thu, 6 Mar 2025 06:42:03 +0000 (+0100) Subject: Import kmod_34.1.orig.tar.xz X-Git-Tag: archive/raspbian/34.1-2+rpi1^2~3 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=9c75334ba6b19f4e21c154909e9225c9d61bd3d3;p=kmod.git Import kmod_34.1.orig.tar.xz [dgit import orig kmod_34.1.orig.tar.xz] --- 9c75334ba6b19f4e21c154909e9225c9d61bd3d3 diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..7b05896 --- /dev/null +++ b/.clang-format @@ -0,0 +1,146 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 12. +# +# For more information, see: +# +# Documentation/dev-tools/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -4 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeComma +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 90 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: false + +# Taken from: +# git grep -h '^#define [^[:space:]]*foreach[^[:space:]]*(' \ +# | sed "s,^#define \([^[:space:]]*foreach[^[:space:]]*\)(.*$, - '\1'," \ +# | LC_ALL=C sort -u +ForEachMacros: + - 'kmod_list_foreach' + - 'kmod_list_foreach_reverse' + +# Taken from: +# git grep -h '^#define .*__attribute__' \ +# | cut -d' ' -f2 \ +# | sed -e 's/(.*//' -e 's/^/ - /' \ +# | LC_ALL=C sort -u +AttributeMacros: + - KMOD_EXPORT + - TS_EXPORT + - _aligned_ + - _alignedptr_ + - _always_inline_ + - _cleanup_ + - _must_check_ + - _nonnull_ + - _nonnull_all_ + - _printf_format_ + - _section_ + - noreturn + +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentGotoLabels: false +IndentPPDirectives: None +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +PenaltyBreakAssignment: 10 +PenaltyBreakBeforeFirstCallParameter: 100 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 50 +PenaltyReturnTypeOnItsOwnLine: 110 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatementsExceptForEachMacros +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... diff --git a/.codespellignore b/.codespellignore new file mode 100644 index 0000000..414509a --- /dev/null +++ b/.codespellignore @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +caf +parm diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d1e3045 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,35 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +# To use this config on you editor, follow the instructions at: +# http://editorconfig.org + +root = true + +[*] +charset = utf-8 +insert_final_newline = true +tab_width = 8 + +[*.{c,h}] +indent_style = tab +max_line_length = 90 + +[{Makefile*,*.mk}] +indent_style = tab + +[*.sh] +indent_style = space +indent_size = 4 + +[*.yml] +indent_style = space +indent_size = 2 + +[*.patch] +trim_trailing_whitespace = false + +[{meson.build,meson_options.txt}] +indent_style = space +indent_size = 2 diff --git a/.github/actions/setup-alpine/action.yml b/.github/actions/setup-alpine/action.yml new file mode 100644 index 0000000..636795b --- /dev/null +++ b/.github/actions/setup-alpine/action.yml @@ -0,0 +1,32 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: 'setup Alpine' +description: 'Setup an Alpine container for running CI' +runs: + using: 'composite' + steps: + - name: Install dependencies + shell: sh + run: | + apk update + apk add \ + autoconf \ + automake \ + bash \ + build-base \ + coreutils \ + clang \ + git \ + gtk-doc \ + libtool \ + linux-edge-dev \ + meson \ + openssl-dev \ + scdoc \ + tar \ + xz-dev \ + zlib-dev \ + zstd-dev diff --git a/.github/actions/setup-archlinux/action.yml b/.github/actions/setup-archlinux/action.yml new file mode 100644 index 0000000..5197f2a --- /dev/null +++ b/.github/actions/setup-archlinux/action.yml @@ -0,0 +1,28 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: 'setup Archlinux' +description: 'Setup an Archlinux container for running CI' +runs: + using: 'composite' + steps: + - name: Install dependencies + shell: bash + run: | + # Semi-regularly the packager key may have (temporarily) expired. + # Somewhat robust solution is to wipe the local keyring and + # regenerate/reinstall it prior to any other packages on the system. + rm -rf /etc/pacman.d/gnupg + pacman-key --init + pacman-key --populate + pacman --noconfirm -Sy archlinux-keyring + + pacman --noconfirm -Su \ + clang \ + git \ + gtk-doc \ + linux-headers \ + meson \ + scdoc diff --git a/.github/actions/setup-debian/action.yml b/.github/actions/setup-debian/action.yml new file mode 100644 index 0000000..5b58b74 --- /dev/null +++ b/.github/actions/setup-debian/action.yml @@ -0,0 +1,33 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: 'setup Debian' +description: 'Setup a Debian container for running CI' +runs: + using: 'composite' + steps: + - name: Install dependencies + shell: bash + run: | + export DEBIAN_FRONTEND=noninteractive + export TZ=Etc/UTC + apt-get update + apt-get install --yes \ + autoconf \ + automake \ + build-essential \ + clang \ + gcc-multilib \ + git \ + gtk-doc-tools \ + liblzma-dev \ + libssl-dev \ + libtool \ + libzstd-dev \ + linux-headers-generic \ + meson \ + scdoc \ + zlib1g-dev \ + zstd diff --git a/.github/actions/setup-fedora/action.yml b/.github/actions/setup-fedora/action.yml new file mode 100644 index 0000000..1bc6b7d --- /dev/null +++ b/.github/actions/setup-fedora/action.yml @@ -0,0 +1,41 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: 'setup Fedora' +description: 'Setup a Fedora container for running CI' +runs: + using: 'composite' + steps: + - name: Install dependencies + shell: bash + run: | + dnf update -y + dnf install -y \ + autoconf \ + automake \ + clang \ + compiler-rt \ + gcc \ + git \ + gtk-doc \ + kernel-devel \ + libasan \ + libhwasan \ + liblsan \ + libtool \ + libtsan \ + libubsan \ + libzstd-devel \ + make \ + meson \ + openssl-devel \ + scdoc \ + xz-devel \ + zlib-devel + # CI builds with KDIR pointing to /usr/lib/modules/*/build + # so just a foo/build pointing to the right place, assuming + # just one kernel installed + mkdir -p /usr/lib/modules/foo/ + ln -s /usr/src/kernels/* /usr/lib/modules/foo/build diff --git a/.github/actions/setup-ubuntu/action.yml b/.github/actions/setup-ubuntu/action.yml new file mode 100644 index 0000000..810fc32 --- /dev/null +++ b/.github/actions/setup-ubuntu/action.yml @@ -0,0 +1,34 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: 'setup Ubuntu' +description: 'Setup an Ubuntu container for running CI' +runs: + using: 'composite' + steps: + - name: Install dependencies + shell: bash + run: | + export DEBIAN_FRONTEND=noninteractive + export TZ=Etc/UTC + apt-get update + apt-get install --yes \ + autoconf \ + automake \ + build-essential \ + clang \ + gcc-multilib \ + gcovr \ + git \ + gtk-doc-tools \ + liblzma-dev \ + libssl-dev \ + libtool \ + libzstd-dev \ + linux-headers-generic \ + meson \ + scdoc \ + zlib1g-dev \ + zstd diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..923029c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,21 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + commit-message: + prefix: "ci" + groups: + all-actions: + patterns: + - "*" diff --git a/.github/print-kdir.sh b/.github/print-kdir.sh new file mode 100755 index 0000000..b3167e7 --- /dev/null +++ b/.github/print-kdir.sh @@ -0,0 +1,17 @@ +#!/bin/bash + +set -euo pipefail + +for moddir in /usr/lib/modules /lib/modules; do + if [[ -e "$moddir" ]]; then + kernel=$(find "$moddir" -maxdepth 1 -mindepth 1 -type d -exec basename {} \;) + break + fi +done + +# There should be one entry - the kernel we installed +if (( $(echo "$kernel" | wc -l) != 1 )); then + echo >&2 "Error: exactly one kernel must be installed" + exit 1 +fi +echo "KDIR=$moddir/$kernel/build" diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..ae3ea88 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,18 @@ +name: Check code style with clang-format + +on: + pull_request: + branches: [master] + +permissions: + contents: read + +jobs: + stylecheck: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - run: git fetch --depth=1 origin ${{ github.event.pull_request.base.sha }} + - uses: yshui/git-clang-format-lint@27f3890c6655216edadcc2759110b9c127c74786 # v1.17 + with: + base: ${{ github.event.pull_request.base.sha }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..644c494 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,88 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: CodeQL + +on: + push: + branches: [master, ci-test] + pull_request: + branches: [master] + schedule: + - cron: "30 2 * * 0" + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-24.04 + permissions: + actions: read + security-events: write + + strategy: + fail-fast: false + matrix: + container: + - name: 'ubuntu:24.04' + meson_setup: '-D b_sanitize=none -D build-tests=false' + + container: + image: ${{ matrix.container.name }} + + steps: + - name: Sparse checkout the local actions + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: .github + + - uses: ./.github/actions/setup-ubuntu + if: ${{ startsWith(matrix.container.name, 'ubuntu') }} + + - name: Checkout the whole project + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set the environment + run: | + # The second checkout above claims to set safe.directory, yet it + # doesn't quite work. Perhaps our double/sparse checkout is to blame? + git config --global --add safe.directory '*' + + .github/print-kdir.sh >> "$GITHUB_ENV" + + - name: Initialize CodeQL + uses: github/codeql-action/init@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 + with: + languages: cpp + queries: +security-and-quality + + - name: Build + run: | + mkdir build && cd build + meson setup --native-file ../build-dev.ini ${{ matrix.container.meson_setup }} . .. + meson compile + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 + with: + category: "/language:cpp" + upload: false + output: sarif-results + + - name: Filter out meson-internal test files + uses: advanced-security/filter-sarif@f3b8118a9349d88f7b1c0c488476411145b6270d # v1.0.1 + with: + patterns: | + -build/meson-private/**/testfile.c + input: sarif-results/cpp.sarif + output: sarif-results/cpp.sarif + + - name: Upload CodeQL results to code scanning + uses: github/codeql-action/upload-sarif@4f3212b61783c3c68e8309a0f18a699764811cda # v3.27.1 + with: + sarif_file: sarif-results/cpp.sarif + category: "/language:cpp" diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml new file mode 100644 index 0000000..828cb0c --- /dev/null +++ b/.github/workflows/codespell.yml @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: Check spelling with codespell + +on: + push: + branches: [master, ci-test] + pull_request: + branches: [master] + +permissions: + contents: read + +jobs: + spellcheck: + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: codespell-project/actions-codespell@406322ec52dd7b488e48c1c4b82e2a8b3a1bf630 # v2.1 + with: + ignore_words_file: .codespellignore diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 0000000..bc8d593 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: Code Coverage + +on: + push: + branches: [master, ci-test] + pull_request: + branches: [master] + schedule: + - cron: "30 2 * * 0" + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + container: + - name: 'ubuntu:24.04' + meson_setup: '-D b_sanitize=none -D b_coverage=true' + + container: + image: ${{ matrix.container.name }} + + steps: + - name: Sparse checkout the local actions + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: .github + + - uses: ./.github/actions/setup-ubuntu + if: ${{ startsWith(matrix.container.name, 'ubuntu') }} + + - name: Checkout the whole project + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set the environment + run: | + # The second checkout above claims to set safe.directory, yet it + # doesn't quite work. Perhaps our double/sparse checkout is to blame? + git config --global --add safe.directory '*' + + .github/print-kdir.sh >> "$GITHUB_ENV" + + - name: Build + run: | + mkdir build && cd build + meson setup --native-file ../build-dev.ini ${{ matrix.container.meson_setup }} . .. + meson compile + meson test + ninja coverage-xml + + - name: Upload Coverage + uses: codecov/codecov-action@b9fd7d16f6d7d1b5d2bec1a2887e65ceed900238 # v4.6.0 + with: + token: ${{ secrets.CODECOV_TOKEN }} + file: build/meson-logs/coverage.xml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..08e3db0 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,153 @@ +# SPDX-FileCopyrightText: 2024 Emil Velikov +# SPDX-FileCopyrightText: 2024 Lucas De Marchi +# +# SPDX-License-Identifier: LGPL-2.1-or-later + +name: Build and Test + +on: + push: + branches: [master, ci-test] + pull_request: + branches: [master] + schedule: + - cron: "30 2 * * 0" + +permissions: + contents: read + +jobs: + build: + env: + CC: ${{ matrix.compiler }} + runs-on: ubuntu-24.04 + strategy: + fail-fast: false + matrix: + build: ['meson', 'autotools'] + compiler: ['clang','gcc'] + container: + - name: 'alpine:latest' + meson_setup: '-D docs=false -D b_sanitize=none' + - name: 'archlinux:multilib-devel' + multilib: 'true' + - name: 'debian:unstable' + multilib: 'true' + - name: 'fedora:latest' + - name: 'fedora:latest' + meson_setup: '-D xz=disabled -D dlopen=all' + - name: 'ubuntu:22.04' + multilib: 'true' + - name: 'ubuntu:22.04' + multilib: 'true' + meson_setup: '-D dlopen=zstd,zlib' + - name: 'ubuntu:24.04' + multilib: 'true' + + container: + image: ${{ matrix.container.name }} + + steps: + - name: Sparse checkout the local actions + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + sparse-checkout: .github + + - uses: ./.github/actions/setup-alpine + if: ${{ startsWith(matrix.container.name, 'alpine') }} + - uses: ./.github/actions/setup-archlinux + if: ${{ startsWith(matrix.container.name, 'archlinux') }} + - uses: ./.github/actions/setup-debian + if: ${{ startsWith(matrix.container.name, 'debian') }} + - uses: ./.github/actions/setup-fedora + if: ${{ startsWith(matrix.container.name, 'fedora') }} + - uses: ./.github/actions/setup-ubuntu + if: ${{ startsWith(matrix.container.name, 'ubuntu') }} + + - name: Checkout the whole project + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set the environment + run: | + # The second checkout above claims to set safe.directory, yet it + # doesn't quite work. Perhaps our double/sparse checkout is to blame? + git config --global --add safe.directory '*' + + .github/print-kdir.sh >> "$GITHUB_ENV" + + - name: configure checks (meson) + if: ${{ matrix.build == 'meson' }} + run: | + should_fail() { + rm -rf build-setup-test/ + if meson setup "$@" build-setup-test/; then + echo Command was expected to fail, but was successful + return 1 + fi + } + should_pass() { + rm -rf build-setup-test/ + meson setup "$@" build-setup-test/ + } + + should_fail -D distconfdir=relative/ + should_fail -D moduledir=relative/ + should_fail -D dlopen=nonexistent + should_fail -D xz=disabled -D dlopen=xz + + should_pass -D dlopen=xz + should_pass -D dlopen=xz -D xz=enabled + + - name: configure (meson) + if: ${{ matrix.build == 'meson' }} + run: mkdir build && cd build && meson setup --native-file ../build-dev.ini ${{ matrix.container.meson_setup }} . .. + + - name: configure (autotools) + if: ${{ matrix.build == 'autotools' }} + run: mkdir build && cd build && ../autogen.sh c + + - name: build (meson) + if: ${{ matrix.build == 'meson' }} + run: cd build && meson compile + + - name: build (autotools) + if: ${{ matrix.build == 'autotools' }} + run: cd build && make -j$(nproc) + + - name: test (meson) + if: ${{ matrix.build == 'meson' }} + run: cd build && meson test || meson test --verbose + + - name: test (autotools) + if: ${{ matrix.build == 'autotools' }} + run: cd build && make -j$(nproc) check + + - name: install (meson) + if: ${{ matrix.build == 'meson' }} + run: cd build && DESTDIR=$PWD/inst meson install + + - name: install (autotools) + if: ${{ matrix.build == 'autotools' }} + run: cd build && DESTDIR=$PWD/inst make install + + - name: distcheck (meson) + if: ${{ matrix.build == 'meson' }} + run: cd build && meson dist + + - name: distcheck (autotools) + if: ${{ matrix.build == 'autotools' }} + run: cd build && make distcheck + + - name: configure (32bit) (meson) + if: ${{ matrix.build == 'meson' && matrix.container.multilib == 'true' }} + run: | + cross_options="-D zstd=disabled -D xz=disabled -D zlib=disabled -D openssl=disabled" + CC="$CC -m32" meson setup $cross_options build32/ + + - name: build (32bit) (meson) + if: ${{ matrix.build == 'meson' && matrix.container.multilib == 'true' }} + run: cd build32 && meson compile + + - name: test (32bit) (meson) + if: ${{ matrix.build == 'meson' && matrix.container.multilib == 'true' }} + run: cd build32 && meson test || meson test --verbose diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5591513 --- /dev/null +++ b/.gitignore @@ -0,0 +1,36 @@ +*.o +*.gcda +*.gcno +*.pc +/*.tar.xz +/*.md5sum +/*.mbx +/*.cover +.deps/ +.libs/ +/Makefile +/Makefile.in + +/aclocal.m4 +/autom4te.cache/ +/build-aux/ +/config.h +/config.h.in +/config.log +/config.status +/configure +/cov-int +/coverage +/gtk-doc.make +/gtk-doc.m4 +/kmod-*.tar.* +/libtool +/stamp-h1 +/test-suite.log +/build +/build-* + +*~ +.*.swp +cscope.out +tags diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000..7a9ca51 --- /dev/null +++ b/.mailmap @@ -0,0 +1 @@ +Emil Velikov diff --git a/CODING-STYLE b/CODING-STYLE new file mode 100644 index 0000000..2cff255 --- /dev/null +++ b/CODING-STYLE @@ -0,0 +1,67 @@ +Every project has its coding style, and kmod is not an exception. This +document describes the preferred coding style for kmod code, in order to keep +some level of consistency among developers so that code can be easily +understood and maintained, and also to help your code survive under +maintainer's fastidious eyes so that you can get a passport for your patch +ASAP. + +First of all, kmod coding style must follow every rule for Linux kernel +(http://www.kernel.org/doc/Documentation/CodingStyle). There also exists a tool +named checkpatch.pl to help you check the compliance with it. Just type +"checkpatch.pl --no-tree patch_name" to check your patch. In theory, you need +to clean up all the warnings and errors. In certain circumstances one can ignore +the 80 character per line limit. This is generally only allowed if the +alternative would make the code even less readable. + +Besides the kernel coding style above, kmod coding style is heavily based on +oFono's and BlueZ's. Below some basic rules: + +1) Wrap line at 80 char limit. + +There are a few exceptions: + - Headers may or may not wrap + - If it's a string that is hitting the limit, it's preferred not to break + in order to be able to grep for that string. E.g: + + err = my_function(ctx, "this is a long string that will pass the 80chr limit"); + + - If code would become unreadable if line is wrapped + - If there's only one argument to the function, don't put it alone in a + new line. + +Align the wrapped line either with tabs (BlueZ, oFono, etc) or tab + spaces +(kernel), at your discretion. Kernel's is preferred. + +2) It's better to return/exit early in a function than having a really long + "if (...) { }". Example: + + if (x) { // worse | if (!x) // better + ... | return b; + ... | + ... | ... + ... | ... + ... | ... + ... | ... + ... | ... + ... | ... + } else { | ... + return b; | return a; + } | + | + return a; | + +3) Don't initialize variable unnecessarily +When declaring a variable, try not to initialize it unless necessary. + +Example: +int i = 1; // wrong + +for (i = 0; i < 3; i++) { +} + +4) Let the includes in the following order, separated by a new line: + < system headers > + < shared/* > + < libkmod > + < tool > + "local headers" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e35cb84 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,115 @@ +# Contributing to kmod + +Thanks for taking the time to contribute to kmod! + +If you want to submit changes, you can send GitHub [pull requests] as well as +patches sent to the [mailing list]. In addition you can open issues and feature +requests on our [GitHub tracker]. + +In this file you can find: + * [License and SPDX](#license-and-spdx) + * [Commit style and history](#commit-style-and-history) + * [Using commit trailers](#using-commit-trailers) + + [Signed-off-by](#signed-off-by) + + [Issues, feature requests](#issues--feature-requests) + + [Discussions, references](#discussions--references) + + [Link(s)](#link-s-) + + [Bugfixes, regressions](#bugfixes--regressions) + * [Coding style](#coding-style) + * [API documentation](#api-documentation) + * [Tools manual pages](#tools-manual-pages) + * [Tools shell completion](#tools-shell-completion) + +## License and SPDX + +The project uses `GPLv2+` for `tools/` and `LGPLv2.1+` for everything else. +New tools should be licensed under `LGPLv2.1+` to make it easy to migrate code +to other places when/if needed. When adding new files use the respective +`SPDX-License-Identifier:` instead of the complete license text. + +## Commit style and history + +The kmod project uses a [linear, "recipe" style] history. This means that +commits should be small, digestible, stand-alone, and functional. + +Commit messages are in imperative mood and merges are to be avoided. + +When in doubt, or need a refresher, checking through the output of `git log` is +highly recommended. + +## Using commit trailers + +Commit messages, apart from stating why a particular change is made, can include +a range of trailers. + +### Signed-off-by + +By using a `Signed-off-by:` trailer you agree that you comply with the +[Developer Certificate of Origin](DCO.txt). + +### Issues, feature requests + +Whenever a patch resolves a particular issue, be that one on our [GitHub +tracker] or elsewhere, use the `Closes:` trailer followed by the full URL. + + Closes: https://github.com/kmod-project/kmod/issues/65 + +### Discussions, references + +If your commit covers a topic raised in an issue, but does not resolve the issue +itself; or otherwise refers to a more complicated topic, you can use +`Reference:`. + +### Link(s) + +The use of `Link:` trailer is reserved and should be used only to point to the +patch origin. Be that the GitHub pull request, or the mailing list archive. + +You can add it yourself, although usually the maintainer will include it when +merging the patch. + +### Bugfixes, regressions + +Nobody is perfect and regressions happen from time to time. Whenever a commit +addresses a regression caused by another commit, use `Fixes:` as below: + + Fixes: 38943b2 ("shared: use size_t for strbuf") + +## Coding style + +The project uses style practically identical to the kernel style. You can see +the in-tree [CODING-STYLE file](CODING-STYLE) for quick references. + +We also have a [.clang-format file](.clang-format) to ease and enforce the +style. Make sure you run `git-clang-format` against your changes, before +submitting PRs/patches. + +## API documentation + +The official libkmod documentation is generated by [gtk-doc] and a simple test +is wired to `meson test` to catch accidental mistakes or omissions. + +If you're unfamiliar with `gtk-doc` here is a [quick primer]. + +When adding new API, make sure to include the `Since:` token and add a new +api-index section in `libkmod/docs/libkmod-docs.xml`. + +## Tools manual pages + +Our manual pages are written in [scdoc] a simple [markdown-like syntax]. Please +make sure to update them as you add new options to the kmod tools. + +## Tools shell completion + +The project aims to provide `bash`, `zsh` and `fish` shell completions for all +the kmod tools. Currently support is borderline non-existent, so your help is +greatly appreciated. + +[pull requests]: https://github.com/kmod-project/kmod/pull/ +[mailing list]: mailto:linux-modules@vger.kernel.org +[GitHub tracker]: https://github.com/kmod-project/kmod/issues/ +[linear, "recipe" style]: https://www.bitsnbites.eu/git-history-work-log-vs-recipe/ +[gtk-doc]: https://gitlab.gnome.org/GNOME/gtk-doc +[quick primer]: https://gi.readthedocs.io/en/latest/annotations/gtkdoc.html +[scdoc]: https://sr.ht/~sircmpwn/scdoc/ +[markdown-like syntax]: https://man.archlinux.org/man/scdoc.5.en diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..8add30a --- /dev/null +++ b/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/DCO.txt b/DCO.txt new file mode 100644 index 0000000..49b8cb0 --- /dev/null +++ b/DCO.txt @@ -0,0 +1,34 @@ +Developer Certificate of Origin +Version 1.1 + +Copyright (C) 2004, 2006 The Linux Foundation and its contributors. + +Everyone is permitted to copy and distribute verbatim copies of this +license document, but changing it is not allowed. + + +Developer's Certificate of Origin 1.1 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the open source license + indicated in the file; or + +(b) The contribution is based upon previous work that, to the best + of my knowledge, is covered under an appropriate open source + license and I have the right under that license to submit that + work with modifications, whether created in whole or in part + by me, under the same open source license (unless I am + permitted to submit under a different license), as indicated + in the file; or + +(c) The contribution was provided directly to me by some other + person who certified (a), (b) or (c) and I have not modified + it. + +(d) I understand and agree that this project and the contribution + are public and that a record of the contribution (including all + personal information I submit with it, including my sign-off) is + maintained indefinitely and may be redistributed consistent with + this project or the open source license(s) involved. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..3401265 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,448 @@ +SUBDIRS = . libkmod/docs + +if BUILD_MANPAGES +SUBDIRS += man +endif + +DISTCLEAN_LOCAL_HOOKS = +EXTRA_DIST = +CLEANFILES = $(BUILT_FILES) +DISTCLEANFILES = +BUILT_FILES = +ACLOCAL_AMFLAGS = -I m4 ${ACLOCAL_FLAGS} +AM_MAKEFLAGS = --no-print-directory + +GCC_COLORS ?= 'yes' +export GCC_COLORS + +# meson bits +EXTRA_DIST += \ + libkmod/docs/meson.build \ + man/meson.build \ + meson.build \ + meson_options.txt \ + testsuite/meson.build \ + scripts/build-scdoc.sh \ + scripts/sanitizer-env.sh \ + scripts/test-gtkdoc.sh + +AM_CPPFLAGS = \ + -include $(top_builddir)/config.h \ + -I$(top_srcdir) \ + -DSYSCONFDIR=\""$(sysconfdir)"\" \ + -DDISTCONFDIR=\""$(distconfdir)"\" \ + -DMODULE_DIRECTORY=\""$(module_directory)"\" \ + ${zlib_CFLAGS} + +AM_CFLAGS = $(OUR_CFLAGS) +AM_LDFLAGS = $(OUR_LDFLAGS) + +# Rules for libtool versioning (from https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html) +# 1. Start with version information of ‘0:0:0’ for each libtool library. +# 2. Update the version information only immediately before a public release of +# your software. More frequent updates are unnecessary, and only guarantee that +# the current interface number gets larger faster. +# 3. If the library source code has changed at all since the last update, then +# increment revision (‘c:r:a’ becomes ‘c:r+1:a’). +# 4. If any interfaces have been added, removed, or changed since the last +# update, increment current, and set revision to 0. +# 5. If any interfaces have been added since the last public release, then +# increment age. +# 6. If any interfaces have been removed or changed since the last public +# release, then set age to 0. +LIBKMOD_CURRENT=7 +LIBKMOD_REVISION=1 +LIBKMOD_AGE=5 + +noinst_LTLIBRARIES = shared/libshared.la +shared_libshared_la_SOURCES = \ + shared/array.c \ + shared/array.h \ + shared/elf-note.h \ + shared/hash.c \ + shared/hash.h \ + shared/macro.h \ + shared/missing.h \ + shared/strbuf.c \ + shared/strbuf.h \ + shared/util.c \ + shared/util.h + +include_HEADERS = libkmod/libkmod.h +lib_LTLIBRARIES = libkmod/libkmod.la + +libkmod_libkmod_la_SOURCES = \ + libkmod/libkmod-builtin.c \ + libkmod/libkmod.c \ + libkmod/libkmod-config.c \ + libkmod/libkmod-elf.c \ + libkmod/libkmod-file.c \ + libkmod/libkmod.h \ + libkmod/libkmod-index.c \ + libkmod/libkmod-index.h \ + libkmod/libkmod-internal-file.h \ + libkmod/libkmod-internal.h \ + libkmod/libkmod-list.c \ + libkmod/libkmod-module.c \ + libkmod/libkmod-signature.c + +if ENABLE_XZ +libkmod_libkmod_la_SOURCES += libkmod/libkmod-file-xz.c +endif + +if ENABLE_ZLIB +libkmod_libkmod_la_SOURCES += libkmod/libkmod-file-zlib.c +endif + +if ENABLE_ZSTD +libkmod_libkmod_la_SOURCES += libkmod/libkmod-file-zstd.c +endif + +EXTRA_DIST += libkmod/libkmod.sym +EXTRA_DIST += libkmod/README \ + libkmod/COPYING testsuite/COPYING tools/COPYING COPYING + +libkmod_libkmod_la_LDFLAGS = $(AM_LDFLAGS) \ + -version-info $(LIBKMOD_CURRENT):$(LIBKMOD_REVISION):$(LIBKMOD_AGE) \ + -Wl,--version-script=$(top_srcdir)/libkmod/libkmod.sym +libkmod_libkmod_la_DEPENDENCIES = \ + shared/libshared.la \ + ${top_srcdir}/libkmod/libkmod.sym +libkmod_libkmod_la_LIBADD = \ + shared/libshared.la \ + ${libzstd_LIBS} ${liblzma_LIBS} ${zlib_LIBS} ${libcrypto_LIBS} + +noinst_LTLIBRARIES += libkmod/libkmod-internal.la +libkmod_libkmod_internal_la_SOURCES = $(libkmod_libkmod_la_SOURCES) +libkmod_libkmod_internal_la_LDFLAGS = $(AM_LDFLAGS) \ + -Wl,--version-script=$(top_srcdir)/libkmod/libkmod.sym +libkmod_libkmod_internal_la_DEPENDENCIES = $(libkmod_libkmod_la_DEPENDENCIES) +libkmod_libkmod_internal_la_LIBADD = $(libkmod_libkmod_la_LIBADD) + +pkgconfig_DATA = libkmod/libkmod.pc +noarch_pkgconfig_DATA = tools/kmod.pc + +bashcompletiondir=@bashcompletiondir@ +dist_bashcompletion_DATA = \ + shell-completion/bash/insmod \ + shell-completion/bash/kmod \ + shell-completion/bash/lsmod \ + shell-completion/bash/rmmod + +fishcompletiondir=@fishcompletiondir@ +dist_fishcompletion_DATA = \ + shell-completion/fish/insmod.fish \ + shell-completion/fish/lsmod.fish \ + shell-completion/fish/rmmod.fish + +zshcompletiondir=@zshcompletiondir@ +dist_zshcompletion_DATA = \ + shell-completion/zsh/_insmod \ + shell-completion/zsh/_lsmod \ + shell-completion/zsh/_rmmod + +install-exec-hook: +if BUILD_TOOLS + for tool in insmod lsmod rmmod depmod modprobe modinfo; do \ + $(MKDIR_P) $(DESTDIR)$(sbindir); \ + $(LN_S) --force --relative $(DESTDIR)$(bindir)/kmod $(DESTDIR)$(sbindir)/$$tool; \ + done +endif + +uninstall-hook: +if BUILD_TOOLS + for tool in insmod lsmod rmmod depmod modprobe modinfo; do \ + rm -f $(DESTDIR)$(bindir)/$$tool; \ + done +endif + +if BUILD_TOOLS +bin_PROGRAMS = tools/kmod + +noinst_SCRIPTS = \ + tools/insmod tools/rmmod tools/lsmod \ + tools/modprobe tools/modinfo tools/depmod + +tools_kmod_SOURCES = \ + tools/depmod.c \ + tools/insmod.c \ + tools/kmod.c \ + tools/kmod.h \ + tools/log.c \ + tools/log.h \ + tools/lsmod.c \ + tools/modinfo.c \ + tools/modprobe.c \ + tools/opt.c \ + tools/opt.h \ + tools/rmmod.c \ + tools/static-nodes.c + +tools_kmod_LDADD = \ + shared/libshared.la \ + libkmod/libkmod-internal.la + +${noinst_SCRIPTS}: tools/kmod + $(AM_V_GEN) ($(RM) $@; \ + $(LN_S) $(notdir $<) $@) +endif + +# ------------------------------------------------------------------------------ +# TESTSUITE +# ------------------------------------------------------------------------------ + +EXTRA_DIST += scripts/setup-modules.sh +EXTRA_DIST += scripts/setup-rootfs.sh + +MODULE_PLAYGROUND = testsuite/module-playground +BUILD_MODULES = $(AM_V_GEN) $(top_srcdir)/scripts/setup-modules.sh $(top_srcdir) $(top_builddir) $(MODULE_PLAYGROUND) +ROOTFS = testsuite/rootfs +ROOTFS_PRISTINE = $(top_srcdir)/testsuite/rootfs-pristine +CREATE_ROOTFS = $(AM_V_GEN) $(top_srcdir)/scripts/setup-rootfs.sh $(ROOTFS_PRISTINE) $(ROOTFS) $(MODULE_PLAYGROUND) $(top_builddir)/config.h $(sysconfdir) $(module_directory) + +build-module-playground: + $(BUILD_MODULES) + + +rootfs: build-module-playground + $(CREATE_ROOTFS) + +.PHONY: rootfs build-playground + +$(ROOTFS): $(ROOTFS_PRISTINE) + $(CREATE_ROOTFS) + +TESTSUITE_OVERRIDE_LIBS = \ + testsuite/uname.la testsuite/path.la \ + testsuite/init_module.la \ + testsuite/delete_module.la +TESTSUITE_OVERRIDE_LIBS_LDFLAGS = \ + -avoid-version -module -shared -rpath /nowhere -ldl + +check-am: rootfs + + +EXTRA_DIST += \ + testsuite/module-playground/dummy.pkcs7 \ + testsuite/module-playground/dummy.sha1 \ + testsuite/module-playground/dummy.sha256 \ + testsuite/module-playground/Kbuild \ + testsuite/module-playground/Makefile \ + testsuite/module-playground/Makefile.arch \ + testsuite/module-playground/mod-fake-cciss.c \ + testsuite/module-playground/mod-fake-hpsa.c \ + testsuite/module-playground/mod-fake-scsi-mod.c \ + testsuite/module-playground/mod-foo-a.c \ + testsuite/module-playground/mod-foo-b.c \ + testsuite/module-playground/mod-foo.c \ + testsuite/module-playground/mod-foo-c.c \ + testsuite/module-playground/mod-loop-a.c \ + testsuite/module-playground/mod-loop-b.c \ + testsuite/module-playground/mod-loop-c.c \ + testsuite/module-playground/mod-loop-d.c \ + testsuite/module-playground/mod-loop-e.c \ + testsuite/module-playground/mod-loop-f.c \ + testsuite/module-playground/mod-loop-g.c \ + testsuite/module-playground/mod-loop-h.c \ + testsuite/module-playground/mod-loop-i.c \ + testsuite/module-playground/mod-loop-j.c \ + testsuite/module-playground/mod-loop-k.c \ + testsuite/module-playground/mod-loop.h \ + testsuite/module-playground/mod-simple.c \ + testsuite/module-playground/mod-weakdep.c \ + testsuite/module-playground/README + +check_LTLIBRARIES = $(TESTSUITE_OVERRIDE_LIBS) + +testsuite_uname_la_LDFLAGS = $(TESTSUITE_OVERRIDE_LIBS_LDFLAGS) +testsuite_path_la_CPPFLAGS = $(AM_CPPFLAGS) \ + -DABS_TOP_BUILDDIR=\"$(abs_top_builddir)\" +testsuite_path_la_LDFLAGS = $(TESTSUITE_OVERRIDE_LIBS_LDFLAGS) + +testsuite_delete_module_la_LDFLAGS = $(TESTSUITE_OVERRIDE_LIBS_LDFLAGS) +testsuite_init_module_la_LDFLAGS = $(TESTSUITE_OVERRIDE_LIBS_LDFLAGS) +testsuite_init_module_la_SOURCES = testsuite/init_module.c \ + testsuite/stripped-module.h +testsuite_init_module_la_LIBADD = libkmod/libkmod-internal.la + +TESTSUITE_CPPFLAGS = $(AM_CPPFLAGS) \ + -DTESTSUITE_ROOTFS=\"$(abs_top_builddir)/$(ROOTFS)/\" \ + -DTOOLS_DIR=\"$(abs_top_builddir)/tools\" \ + -DOVERRIDE_LIBDIR=\"$(abs_top_builddir)/testsuite/.libs/\" +TESTSUITE_LDADD = \ + testsuite/libtestsuite.la libkmod/libkmod-internal.la \ + shared/libshared.la + +check_LTLIBRARIES += testsuite/libtestsuite.la +testsuite_libtestsuite_la_SOURCES = \ + testsuite/testsuite.c testsuite/testsuite.h +testsuite_libtestsuite_la_DEPENDENCIES = \ + $(ROOTFS) $(TESTSUITE_OVERRIDE_LIBS) +testsuite_libtestsuite_la_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_libtestsuite_la_LIBADD = -lrt + +TESTSUITE = \ + testsuite/test-array \ + testsuite/test-blacklist \ + testsuite/test-dependencies \ + testsuite/test-depmod \ + testsuite/test-hash \ + testsuite/test-init \ + testsuite/test-initstate \ + testsuite/test-list \ + testsuite/test-loaded \ + testsuite/test-modinfo \ + testsuite/test-modprobe \ + testsuite/test-new-module \ + testsuite/test-strbuf \ + testsuite/test-testsuite \ + testsuite/test-util \ + testsuite/test-weakdep + +check_PROGRAMS = $(TESTSUITE) +TESTS = $(TESTSUITE) + +testsuite_test_testsuite_LDADD = \ + testsuite/libtestsuite.la shared/libshared.la +testsuite_test_testsuite_CPPFLAGS = $(TESTSUITE_CPPFLAGS) + +testsuite_test_hash_LDADD = $(TESTSUITE_LDADD) +testsuite_test_hash_CPPFLAGS = $(TESTSUITE_CPPFLAGS) + +testsuite_test_array_LDADD = $(TESTSUITE_LDADD) +testsuite_test_array_CPPFLAGS = $(TESTSUITE_CPPFLAGS) + +testsuite_test_strbuf_LDADD = $(TESTSUITE_LDADD) +testsuite_test_strbuf_CPPFLAGS = $(TESTSUITE_CPPFLAGS) + +testsuite_test_init_LDADD = $(TESTSUITE_LDADD) +testsuite_test_init_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_initstate_LDADD = $(TESTSUITE_LDADD) +testsuite_test_initstate_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_loaded_LDADD = $(TESTSUITE_LDADD) +testsuite_test_loaded_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_modinfo_LDADD = $(TESTSUITE_LDADD) +testsuite_test_modinfo_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_util_LDADD = $(TESTSUITE_LDADD) +testsuite_test_util_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_new_module_LDADD = $(TESTSUITE_LDADD) +testsuite_test_new_module_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_modprobe_LDADD = $(TESTSUITE_LDADD) +testsuite_test_modprobe_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_blacklist_LDADD = $(TESTSUITE_LDADD) +testsuite_test_blacklist_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_dependencies_LDADD = $(TESTSUITE_LDADD) +testsuite_test_dependencies_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_depmod_LDADD = $(TESTSUITE_LDADD) +testsuite_test_depmod_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_list_LDADD = $(TESTSUITE_LDADD) +testsuite_test_list_CPPFLAGS = $(TESTSUITE_CPPFLAGS) +testsuite_test_weakdep_LDADD = $(TESTSUITE_LDADD) +testsuite_test_weakdep_CPPFLAGS = $(TESTSUITE_CPPFLAGS) + +testsuite-distclean: + $(RM) -r $(ROOTFS) + $(RM) testsuite/stamp-rootfs + $(MAKE) -C $(MODULE_PLAYGROUND) clean + if test "$(top_srcdir)" != "$(top_builddir)"; then \ + $(RM) -rf testsuite/module-playground; \ + fi + +DISTCLEAN_LOCAL_HOOKS += testsuite-distclean +EXTRA_DIST += testsuite/rootfs-pristine + +AM_DISTCHECK_CONFIGURE_FLAGS=--enable-gtk-doc --sysconfdir=/etc \ + --with-zlib --with-zstd --with-xz --with-openssl \ + --with-bashcompletiondir=$$dc_install_base/$(bashcompletiondir) \ + --with-fishcompletiondir=$$dc_install_base/$(fishcompletiondir) \ + --with-zshcompletiondir=$$dc_install_base/$(zshcompletiondir) + +distclean-local: $(DISTCLEAN_LOCAL_HOOKS) + +buildtest-TESTS: + $(MAKE) $(AM_MAKEFLAGS) $(check_LTLIBRARIES) $(check_PROGRAMS) + +# ------------------------------------------------------------------------------ +# coverage +# ------------------------------------------------------------------------------ + +# .PHONY so it always rebuilds it +.PHONY: coverage lcov-run lcov-report coverage-sync + +# run lcov from scratch, always +coverage: all + $(MAKE) lcov-run + $(MAKE) lcov-report + +coverage_dir = coverage +coverage_opts = --base-directory $(srcdir) --directory $(builddir) \ + --rc 'geninfo_adjust_src_path=$(abspath $(srcdir))=>$(abspath $(builddir))' + +if ENABLE_COVERAGE +# reset run coverage tests +lcov-run: + @rm -rf $(coverage_dir) + lcov $(coverage_opts) --zerocounters + -$(MAKE) check + +# generate report based on current coverage data +lcov-report: + $(MKDIR_P) $(coverage_dir) + lcov $(coverage_opts) --capture --no-external --ignore-errors graph \ + | sed 's|$(abspath $(builddir))|$(abspath $(srcdir))|' > $(coverage_dir)/.lcov.info + lcov --remove $(coverage_dir)/.lcov.info --output-file $(coverage_dir)/.lcov-clean.info 'test-*' + genhtml -t "kmod test coverage" -o $(coverage_dir) $(coverage_dir)/.lcov-clean.info + @echo "Coverage report generated in $(abs_builddir)/$(coverage_dir)/index.html" + +else + +lcov-run lcov-report: + echo "Need to reconfigure with --enable-coverage" + +endif + +# ------------------------------------------------------------------------------ +# coverity +# ------------------------------------------------------------------------------ + +kmod-coverity-%.tar.xz: + rm -rf $< cov-int + ./autogen.sh c --disable-manpages + make clean + cov-build --dir cov-int make -j 4 + tar caf $@ cov-int + +coverity-tar: kmod-coverity-$(shell git describe 2>/dev/null).tar.xz + +coverity-sync: kmod-coverity-$(shell git describe 2>/dev/null).tar.xz + @echo "uploading coverity tarball" + @curl --form token=$(COVERITY_KMOD_TOKEN) \ + --form email=lucas.de.marchi@gmail.com \ + --form file=@$< \ + --form version="$(shell git describe)" \ + --form description="" \ + https://scan.coverity.com/builds?project=kmod + +coverity-clean: + rm -rf kmod-coverity-*.tar.xz cov-int + +# ------------------------------------------------------------------------------ +# custom release helpers +# ------------------------------------------------------------------------------ + +git-release: + head -1 NEWS | grep -q "kmod $(VERSION)" + git commit -a -s -m "kmod $(VERSION)" + git tag -m "kmod $(VERSION)" -s v$(VERSION) + git gc --prune=0 + +kmod-$(VERSION).tar.xz: + make distcheck + +kmod-$(VERSION).tar.sign: + xz -d -c kmod-$(VERSION).tar.xz | gpg --armor --detach-sign --output kmod-$(VERSION).tar.sign + +tar: kmod-$(VERSION).tar.xz kmod-$(VERSION).tar.sign + +tar-sync: kmod-$(VERSION).tar.xz kmod-$(VERSION).tar.sign + kup put kmod-$(VERSION).tar.xz kmod-$(VERSION).tar.sign /pub/linux/utils/kernel/kmod/ diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..02cd69a --- /dev/null +++ b/NEWS @@ -0,0 +1,1181 @@ +kmod 34 +======= + +- Improvements + + - Drop pre-built .ko modules from git - distros/packages will need the + linux-headers to be able to run the testsuite. There was limited use + of the feature, while linters complained about "source-not-included" + or "source-contains-prebuilt-binary". + + - Switch build system to meson: autotools is still supported but slated + for removal on next release. This is the transition release to help + distros and integrators to move to the new build system. Default options + target distros while developers can use the build-dev.ini configuration + file. + + - Allow to load decompression libraries ondemand: liblzma.so, libz.so + and libzstd.so can now be loaded ondemand, only when there is + such a need. For use during early boot for loading modules, if + configured well it means none of these libraries are loaded: the + module loading logic via finit_module() will just hand over to kernel + the open file descriptor and kernel will use its own decompress routine. + + If kernel doesn't handle decompression or if the module is compressed + with a different algorithm than the one configured in the kernel, + libkmod can still open the module by dynamically loading the + correspondent library. + + Tools inspecting the module contents, like modinfo, will load that + single decompression library instead all of them. + + For distros building with meson it's possible to choose the behavior + per library. Examples: a) -Ddlopen=all uses dlopen behavior for all + the libraries; b) -Ddlopen=xz, will make only xz to be dlopen'ed + while other enabled libraries will be linked in at build time. + + The use of dlopen is annotated in the ELF file by using the ELF + Package Metadata spec as documented in + https://systemd.io/ELF_PACKAGE_METADATA/. Example: + + $ dlopen-notes.py libkmod.so + # build/libkmod.so + [ + { + "feature": "xz", + "description": "Support for uncompressing xz-compressed modules", + "priority": "recommended", + "soname": [ + "liblzma.so.5" + ] + } + ] + + - Add -m / --moduledir to depmod to override in runtime the module + directory that was already possible to set on build time. Document + the interaction between the dir options: base, module and output. + + - Better error propagation in libkmod for its internal APIs and libc + functions up to the callers. + + - Improve libkmod API documentation by adding new sections, documenting + functions previously missing, rewording existing ones, adding version + information, cross-referencing, etc. + + - Remove deprecated arguments for depmod: --unresolved-error, --quiet, + --root and --map. + + - Remove deprecated arguments for rmmod: -w. + + - Remove deprecated arguments for insmod: -p and -s. + + - Add --syslog and --force for insmod to normalize it with other tools. + + - Add bash, fish and zsh shell-completion for insmod, rmmod and lsmod. + + - Remove depmod_module_directory_override from .pc as the kernel side + is not making use of it and will likely not need it. + + - Improve builtin module listing and retrieving information from its + modinfo index which reduces the amount of needed syscalls by 90%. + + - Improve zstd decompression by using streaming bufferless mode which + reduces the amount of syscalls by 65%. + + - Increase use of pread while parsing ELF and indexes in order to reduce + syscalls and improve performance. + + - Improve module sorting in depmod to speedup the use of the + modules.order index and support duplicate lines in it. + + - Avoid misaligned memory access while reading module signature in + libkmod. + + - Add more documentation for contributing to kmod. New developers are + welcome to look at the new README.md and CONTRIBUTING.md files for + information on process, coding style, build/installation, etc. + + - Overhaul man pages with multiple clarifications, section rewrites and + additional documentation. + + - Drop --with-rootlibdir as it's seldom used and was partially broken. + + - Drop strndupa() and alloca() for increased libc compatibility. + + - Better handling of LFS for increased compatibility with libc. + + - Protect kmod_get_dirname() and kmod_new() against NULL argument. + + - Normalize --version / --help output across all tools. + + - Always include log priority in messages, even when building with debug. + + - Optimize index reading by lazily reading nodes on demand, reducing + FILE overhead and reducing code duplication wrt FILE vs mmap + implementations, etc. + + - Switch index to pre-order to improve performance in both read and + write, meaning faster lookup and faster depmod. Some examples: + a) traversing all indexes via configuration dump shows a 9% + improvement on Raspberry Pi 2. b) writing the indexes takes 90% less + lseek() calls, leading to a performance gain of 13%. + + - Make symlink install locations more similar to what distros are + using: by default it installs the kmod binary as bin/kmod and the + symlinks are located in e.g. `sbin/depmod -> ../bin/kmod`. Changing + the sbin location is sufficient to move the symlinks to the + appropriate place, so distros using `--sbin /usr/bin` will have them + installed in that directory. This avoids distros having to remove the + symlink and add the symlinks by themselves. (meson only) + + - Install configuration directories, + /{etc,usr/lib}/{depmod,modprobe}.d/ as part of installation, matching + what several distros do during packaging. (mson only) + +- Bug fixes + + - Fix testsuite using when using configurable module dir. + + - Fix typos on documentation and source code. + + - Fix out of bound access in multiple places when using long paths, + synthetic huge files, or handling memory allocation errors, or + inconsistent variable types, particularly on 32b builds. + + - Fix internal array APIs, with better error checking: improve execution on + very memory-constrained scenarios or very long paths. + + - Fix absolute path handling in depmod. + + - Fix libkmod memory leaks on error handling when getting builtin + module list. + + - Do not crash on invalid modules.builtin.modinfo file. + + - Fix link with lld resulting in empty testsuite. + + - Fix testsuite build/execution with musl. + +- Others + + - Adopt clang-format and editorconfig for coding style and setup CI + action to keep the codebase consistent. + + - Adopt codespell in CI. + + - Adopt CodeQL integration in CI. + + - Adopt Codecov in CI. + + - Adopt SPDX copyright and license identifiers throughout the project. + + - Add more distros to CI, 32b builds, clang as compiler and lld as + linker. + + - Add support for clang sanitizers and squelch warnings. + + - Add tests for builtin modules from modinfo index file. + + - Multiple testsuite refactors and fixes to make it simpler to write tests. + + - Add CI coverage for docs + + - Improve strbuf implementation with more error checks and generalize + it to cover the role of scratchbuf. This allows to remove the + scratchbuf implementation. + + - Use common array and strbuf code in depmod to remove duplication. + + - Add abstraction and use more compiler builtins for addition and + multiplication with overflow checking. + + - Normalize use of C attributes throughout the project. + +kmod 33 +======= + +- Improvements + + - Allow to handle compressed modules even without decompression + libraries linked in. Previously we would detect if the kernel + supported the decompression algorithm and pass the module directly + through finit_module(). However it wouldn't consider the file if + the respective decompression library was compiled out. Now it's + possible to completely disable all libraries and still have module + load working with libkmod. + + Tools that inspect module content themselves like modinfo and depmod + won't work if the decompression library is not enabled. + + - Add weak dependencies - these are similar to pre softdep, but they + don't cause the dependency to be loaded by libkmod: when a module has + a weak dependency, it is expected that module may or may not be used, + with decision happening in runtime by the kernel. It's purpose is to + be informational for other tools like ones used to create initramfs, + so the module is made available before switch_root(), but doesn't + imply it to be loaded when not needed. + + - Improve compatibility with non-gnu libc like musl and uClibc. Now it's + possible to build and use libkmod and tools without any additional compat + patches. + + - Move manpages from xsltproc to scdoc, which is now needed during build. + + - Improve documentation in manpages, fixing typos, rewording sentences, + detailing how configuration files are handled with precedence order + and making all the manpages more consistent on how to reference options, + environment variables, configuration, authors, etc. + + - Speed up zstd decompression, particularly when not using glibc. + + - Stop parsing .alias files from modprobe.d directories. Configuration files + were always documented as needing the .conf extension. For compatibility + reason with module-init-tools, kmod also parsed .alias files. However that + was also done in module-init-tools for compatibility reasons and not + documented anywhere. From inspection on what distros are using, none use + .alias files in practice, so stop parsing those files and follow what's + documented. + + - Adopt SPDX for license and cleanup comments on individual files. + + - Since kmod 29 there's a github mirror for the repository. Now it's + also used for issues and improvement tracking. With that, the old + TODO file has been removed and distros/users are encouraged to file + issues in github. + +- Bug fixes + + - Move kmod.pc to the right dir, ${datadir}/pkgconfig, as it's related + to kmod, not libkmod. + + - Fix error handling while loading a file and mmap fails. + + - Fix error handling while handling errors from the decompression + libraries. + + - Add missing documentation for KMOD_INDEX_MODULES_BUILTIN that was + added in v27 breaking the ABI. A wide search has found one external + tool using it, which hasn't been updated in the past 12 years. It + was deemed safe to simply update the documentation to include the + missing enum. + + - Move kmod_module_new_from_name_lookup() to the correct symbol + version. It was added by mistake to @LIBKMOD_5 when v30 got released. + No external user of this API was found, so it was considered safe + to just move it. + +- Others + + - Overwrite symlinks when installing tools. + + - General cleanup of how (de)compression libraries are integrated. + + - Add CI infrastructure to automatically test in several distros + before applying commit series. Currently the latest versions of + Alpine, Archlinux, Fedora and Ubuntu are covered. More distros are + easy to add as they are all containerized. + +kmod 32 +======= + +- Improvements + + - Use any hash algo known by kernel/openssl instead of keep needing + to update the mapping + + - Teach kmod to load modprobe.d/depmod.d configuration from ${prefix}/lib + and allow it to be overridden during build with --with-distconfdir=DIR + + - Make kernel modules directory configurable. This allows distro to + make kmod use only files from /usr regardless of having a compat + symlink in place. + + - Install kmod.pc containing the features selected at build time. + + - Install all tools and symlinks by default. Previously kmod relied on + distro packaging to set up the symlinks in place like modprobe, + depmod, lsmod, etc. Now those symlinks are created by kmod itself + and they are always placed in $bindir. + +- Bug Fixes + + - Fix warnings due to -Walloc-size + +- Others + + - Drop python bindings. Those were not update in ages and not compatible + with latest python releases. + + - Cleanup test infra, dropping what was not used anymore + + - Drop experimental tools `kmod insert` / `kmod remove`. Building those + was protected by a configure option never set by distros. They also + didn't gain enough traction to replace the older interfaces via + modprobe/insmod/rmmod. + +kmod 31 +======= + +- Improvements + + - Allow passing a path to modprobe so the module is loaded from + anywhere from the filesystem, but still handling the module + dependencies recorded in the indexes. This is mostly intended for kernel + developers to speedup testing their kernel modules without having to load the + dependencies manually or override the module in /usr/lib/modules/. + Now it's possible to do: + + # modprobe ./drivers/gpu/drm/i915/i915.ko + + As long as the dependencies didn't change, this should do the right thing + + - Use in-kernel decompression if available. This will check the runtime support + in the kernel for decompressing modules and use it through finit_module(). + Previously kmod would fallback to the older init_module() when using + compressed modules since there wasn't a way to instruct the kernel to + uncompress it on load or check if the kernel supported it or not. + This requires a recent kernel (>= 6.4) to have that support and + in-kernel decompression properly working in the kernel. + + - Make modprobe fallback to syslog when stderr is not available, as was + documented in the man page, but not implemented + + - Better explaining `modprobe -r` and how it differentiates from rmmod + + - depmod learned a `-o ` option to allow using a separate output + directory. With this, it's possible to split the output files from + the ones used as input from the kernel build system + + - Add compat with glibc >= 2.32.9000 that dropped __xstat + + - Improve testsuite to stop skipping tests when sysconfdir is something + other than /etc + + - Build system improvements and updates + + - Change a few return codes from -ENOENT to -ENODATA to avoid confusing output + in depmod when the module itself lacks a particular ELF section due to e.g. + CONFIG_MODVERSIONS=n in the kernel. + + +- Bug Fixes + + - Fix testsuite using uninitialized memory when testing module removal + with --wait + + - Fix testsuite not correctly overriding the stat syscall on 32-bit + platforms. For most architectures this was harmless, but for MIPS it + was causing some tests to fail. + + - Fix handling unknown signature algorithm + + - Fix linking with a static liblzma, libzstd or zlib + + - Fix memory leak when removing module holders + + - Fix out-of-bounds access when using very long paths as argument to rmmod + + - Fix warnings reported by UBSan + +kmod 30 +======= + +- Improvements + - Stop adding duplicate information on modules.builtin.alias.bin, just use + the modules.builtin.bin index + + - Speedup depmod, particularly under qemu with emulated arch, by + avoiding a lot of open/read/close of modules.alias.bin. On an + emulated ARM rootfs, depmod with only 2 modules was taking ~32s + vs ~0.07s now. + + - Add kmod_module_new_from_name_lookup() which allows doing a lookup by + module name, without considering the aliases. Other than that search + order is similar to kmod_module_new_from_lookup(). + + - modinfo learned the --modname option to explicitly show information + about the module, even if there is an alias with the same name. This + allows showing information about e.g. kernel/lib/crc32.ko, even if + kernel also exports a crc32 alias in modules.alias: + + alias crc32 crc32_pclmul + alias crc32 crc32_generic + + Same behavior will be used to other modules and to aliases provided + by user/distro. + + - depmod.conf learned a new "excludedir" directive so distro/user can + configure more directories to be excluded from its search, besides + the hardcoded values "build" and "source". + + - Better group modprobe options on help output under "Management, Query and General". + + - modprobe learned a --wait option to be used together with -r + when removing a module. This allows modprobe to keep trying the + removal if it fails because the module is still in use. An exponential backoff + time is used for further retries. + + The wait behavior provided by the kernel when not passing O_NONBLOCK + to delete_module() was removed in v3.13 due to not be used and the + consequences of having to support it in the kernel. However there may + be some users, particularly on testsuites for individual subsystems, that + would want that. So provide a userspace implementation inside modprobe for + such users. "rmmod" doesn't have a --wait as it remains a bare minimal over + the API provided by the kernel. In future the --wait behavior can be added + to libkmod for testsuites not exec'ing modprobe for module removal. + + - kmod_module_remove_module() learned a new flag to silence output when + caller wants to handle them - this is particularly important for the + --wait flag to modprobe, as it's not desired to keep seeing error messages + while waiting for the module to be unused. + + - Add SM3 hash algo support to modinfo output, as already available in the kernel. + +- Bug Fixes + - Fix modinfo output when showing information for a .ko module when running + on a kernel that has that module as builtin. + + - Fix kmod_module_new_from_lookup() returning > 0 rather than 0 + when it matches an alias. + + - Fix modinfo segfault when module doesn't exist. + + - Add missing function in the html documentation: kmod_get_dirname(). + + - Fix modprobe incorrectly handling number of arguments when prepending values from + MODPROBE_OPTIONS environment variable. + + - Fix modprobe -r --remove-dependencies and since "dependencies" was a + misnomer, add the preferred argument option: "--remove-holders". This + is the same name used by the kernel. It allows users to also remove + other modules holding the one that is being removed. + + - Fix off-by-one in max module name length in depmod. + +- Infra/internal + - Start some changes in the out-of-tree test modules in kmod so they are useful + for being really inserted in the kernel rather than relying on kmod's mock + interface. This helps manual testing and may be used to exercise to test + changes in the kernel. + +kmod 29 +======= + +- Improvements + - Add support to use /usr/local as a place for configuration files. This makes it easier + to install locally without overriding distro files. + +- Bug fixes + - Fix `modinfo -F` when module is builtin: when we asked by a specific field from modinfo, + it was not working correctly if the module was builtin + + - Documentation fixes on precedence order of /etc and /run: the correct order is + /etc/modprobe.d, /run/modprobe.d, /lib/modprobe.d + + - Fix the priority order that we use for searching configuration files. The + correct one is /etc, /run, /usr/local/lib, /lib, for both modprobe.d + and depmo.d + + - Fix kernel command line parsing when there are quotes present. Grub + mangles the command line and changes it from 'module.option="val with + spaces"' to '"module.option=val with spaces"'. Although this is weird + behavior and grub could have been fixed, the kernel understands it + correctly for builtin modules. So change libkmod to also parse it + correctly. This also brings another hidden behavior from the kernel: + newline in the kernel command line is also allowed and can be used to + separate options. + + - Fix a memory leak, overflow and double free on error path + + - Fix documentation for return value from kmod_module_get_info(): we + return the number of entries we added to the list + + - Fix output of modules.builtin.alias.bin index: we were writing an empty file due to + the misuse of kmod_module_get_info() + +- Infra/internal + - Retire integration with semaphoreci + + - Declare the github mirror also as an official upstream source: now besides accepting + patches via mailing list, PRs on github are also acceptable + + - Misc improvements to testsuite, so we can use it reliably regardless + of the configuration used: now tests will skip if we don't have the + build dependencies) + +kmod 28 +======= + +- Improvements + - Add Zstandard to the supported compression formats using libzstd + (pass --with-zstd to configure) + +- Bug fixes + - Ignore ill-formed kernel command line, e.g. with "ivrs_acpihid[00:14.5]=AMD0020:0" + option in it + - Fix some memory leaks + - Fix 0-length builtin.alias.bin: it needs at least the index header + +kmod 27 +======= + +- Improvements + - Link to libcrypto rather than requiring openssl + + - Print a better error message when kernel doesn't support module unload + + - Use PKCS#7 instead of CMS for parsing module signature to be + compatible with LibreSSL and OpenSSL < 1.1.0 + + - Teach modinfo to parse modules.builtin.modinfo. When using Linux kernel + >= v5.2-rc1 it's possible to get module information from this new file. Now + modinfo is able to show it instead of an error message that the module is + built-in: + + Before: + $ modinfo ext4 + modinfo: ERROR: Module ext4 not found. + + After: + $ modinfo ext4 + name: ext4 + filename: (builtin) + softdep: pre: crc32c + license: GPL + description: Fourth Extended Filesystem + author: Remy Card, Stephen Tweedie, Andrew Morton, Andreas Dilger, Theodore Ts'o and others + alias: fs-ext4 + alias: ext3 + alias: fs-ext3 + alias: ext2 + alias: fs-ext2 + +- Bug fixes + - Do not link python bindings with libpython to be compatible with + python3.8 + + - Fix module removal with `modprobe -r` when a dependency is built-in. + Now it properly ignores them and proceed with removal of other + dependencies + + - Fix propagation of return code from install/remove commands to the + the probe function. The return values of kmod_module_probe_insert_module() + have very specific meanings, do not confuse the caller by return codes + from system() + + - Fix softdep config parsing leading to buffer overflow + +kmod 26 +======= + +- Improvements + - Add more error-checking in library functions and remove warnings on newer + toolchains + + - Depmod now handles parallel invoctions better by protecting the temporary + files being used + + - Improvements to testsuite and added tests to check the our behavior + regardless of the features enabled in the kernel, or libraries we link to + + - Teach the --show-exports option to modprobe. This works similarly to + --show-modversions, but it reports the exported symbols from that module. + Under the hood this reads the .symtab and .strtab section rather than + __versions so it shows useful data even if kernel is configured without + modversions (CONFIG_MODVERSIONS) + + - Teach pkcs7 parsing to modinfo by using openssl. This allows modinfo to + correctly parse the signature appended to a module by the kernel build + system when configured with CONFIG_MODULE_SIG_ALL, or when externally + signed by the distro. Traditionally modules were signed and a struct + was appended together with the signature to the end of the module. + This has changed on the kernel for pkcs#7 and now the structure isn't + filled out with useful information. So we have to parse the signature + block in order to return useful data to the user. + + If kmod is linked with openssl we parse the signature and return the + fields as we do for other signatures. An example of the relevant part + on the output of modinfo is below: + + Before: + sig_id: PKCS#7 + signer: + sig_key: + sig_hashalgo: md4 + After: + sig_id: PKCS#7 + signer: Fedora kernel signing key + sig_key: 51:C4:0C:6D:7E:A5:6C:D8:8F:B4:3A:DF:91:78:4F:18:BC:D5:E4:C5 + sig_hashalgo: sha256 + + If kmod is not linked to openssl we just start printing "unknown" in the + sig_hashalgo field rather than the bogus value. + + +kmod 25 +======= + +- Improvements + - Add module signature to modinfo output + + - Add support for external directories in depmod: now there's a new + "external" keyword parsed by depmod when calculating the dependencies. + It allows to add modules to other directories which are not relative + to where the modules are commonly installed. This results in + modules.dep and friends now understanding absolute paths rather than + relative paths only. For more information see depmod.d(1). + + - Add support for CONFIG_MODULE_REL_CRCS + + - Add missing documentation references in man pages + + - Handle the case in which module has a .TOC symbol already while + calculating dependencies + + - Improve testsuite and allow to use mkosi to run testsuite in different + distros + +kmod 24 +======= + +- Improvements: + - Add more information on dependency loop + + - Sanitize use of strcpy and allow to grow from small strings on stack + (common case) to bigger strings on heap when needed + +- Bug fixes + - Fix wrong dependency loops being reported by depmod + + - Fix crashes when reporting dependency loops + + - Fix parsing kernel command line containing quotes + + - Fix leaks on error paths + +kmod 23 +======= + +- Improvements: + - Don't add comment to modules.devname if it would otherwise be empty + to play nice with tools detecting empty files + + - Allow building with BSD sed, that doesn't have -E flag + + - Ignore .TOC. symbols in depmod parsing as it's for PPC64 the + equivalent of _GLOBAL_OFFSET_TABLE_ + + - Teach modinfo about PKCS#7 module signatures: it doesn't add any + other info besides telling the user the module is signed since + kernel doesn't add other info on the module section + +- Bug fixes + + - Fix -s and -p compat options to insmod triggering force flag + + - Fix long lines from /proc/modules not being handled correctly by + kmod_module_new_from_loaded() and kmod_module_get_size() and several + other library functions that use them + + - Fix crash on modinfo while checking for available signature of + unknown type + + - Fix documentation generation with gtk-doc + +kmod 22 +======= + +- Tools: + - Change default log level for tools to WARNING rather than ERROR and update + some log levels for current messages + + - depmod doesn't fallback to uname if a bad version is passed in the command + line anymore. We just exit with an error. + + - insmod was taught the -f flag, just like in modprobe. It was previously + silently ignoring it. + +- libkmod + - New kmod_get_dirname() API to get the module directory set in the + context + +- Bug fixes: + - Fix return code in error path of kmod_module_insert_module(). We were + previously returning ENOSYS rather than ENOENT. + +kmod 21 +======= + +- New features: + - kmod tool started to learn the "insert" and "remove" commands that + are the simplified versions of the older modprobe tool. These + commands are still work in progress so they are hidden behind a + --enable-experimental flag during build. It should not be enabled + unless you know what you're doing. + - kmod tool now prints the relevant configuration options it was built + with when the "--version" argument is passed. This helps to mitigate + problems for example when the user is trying to load a compressed + module but kmod was built without support for the compression method. + +- Improvements to testsuite: + - Cache built modules so it is easier to run "make check" on build + servers by distro maintainers. If kmod is configured with + --disable-test-modules the modules from cache will be used by + "make check". No changes to the tests are needed and all of them + can run fine. + +kmod 20 +======= +- Bug fixes: + - Handle bogus values from ELF, making sure they don't overflow while + parsing the file + - Fix leak in depmod when -b flag is passed multiple times + - Multiple minor fixes from static analysis by coverity and + clang-analyze + - Fix race between loading modules and checking if it's loaded in the + kernel + +- New features: + - There's a change in behavior regarding builtin modules: we now only + consider as builtin those that are present in modules.builtin index. + Previously we were also checking the presence of + /sys/module/, but this is racy and only modules that + contain parameters are the ones creating a directory in sysfs. + + Now some commands will start to fail, e.g. "modprobe vt". Since vt + can't be compiled as a module it's not present in modules.builtin + index. Previously we would report at as builtin, but now we fail + because we couldn't find the module. + +- Improvements: + - Integration of gcov into the build. Currently libkmod is at ~70% + covered and tools at ~50% by tests in the testsuite. Utility + functions and structures in shared have more than 90% of coverage. + - Upload build to coverity + +- Improvements to testsuite: + - Fix parsing return codes of init_module() calls + - Add tests for utility functions in shared/ + - Add tests for kmod_module_remove_module() + - Add playground, in which our own modules are compiled + - Port all tests to use modules from module-playground instead of + copying prebuilt modules to the repository + - Properly handle binaries that exit with no output + - Besides comparing the output of commands, allow to copy to + stdout/stderr + +kmod 19 +======= + +- Bug fixes: + - Fix missing CLOEXEC in library + - Fix error message while opening kmod's index + +- New features: + - Add kmod(8) man page + - Allow to build with libc's without be32toh() + - Move code around separating common code and data structures into a + shared directory. This allows to share more code between library and + tools, making the binary size of tools smaller. + - Clarify tools vs library licenses + - static-nodes: when writing in tmpfiles format, indicate that + creation of static nodes should only happen at boot. This is used and + required by systemd-217+. + +- Improvements to testsuite: + - Add tests for newly created shared/ code + - Improve how tests are declared so there's less boilerplate code for + each test. + +kmod 18 +======= + +- Bug fixes: + - Fix leaks in error paths + - Fix use-after-free in hash implementation causing a wrong index to be + generated by depmod with out-of-tree modules + +- New features: + - Calling depmod with modules creating a dependency loop will now make + depmod return an error and not update the indexes. This is to protect + the current index not being overridden by another index that may cause + a boot failure, depending on the buggy module. It's a necessary + change in behavior regarding previous kmod releases and + module-init-tools. The error message was also improved to output + the modules that caused the dependency cycle. + +- Improvements to testsuite: + - Fix and improve expected-fail test + - Add tests for hashmap implementation + +kmod 17 +======= + +- Bug fixes: + - Fix matching a "." in kernel cmdline, making garbage in the command + line be parsed as kmod options + - Fix man pages to clarify we don't fallback to parsing modules.dep + but instead we depend on modules.dep.bin (generated by depmod) to + be present + - Fix ELF parsing on 32 bit systems assigning the wrong class. + - Fix partial matches of search directives in depmod. Previously having + a line in depmod.conf such as "search foo foobar built-in" would cause + unpretictable results because foo is a partial match of foobar as well. + - Fix unaligned access in modinfo when getting the signature from a + module + - Make sure softdeps are treated as optional dependencies + +- New features: + - Accept special files given to "-C" switch in modprobe. This way it's + possible to skip system configuration with "modprobe -C /dev/null" + - Do not require xsltproc on released tarballs + - Don't use Werror anymore + - Add experimental python bindings, merged from python-kmod repository + (https://github.com/agrover/python-kmod) + - Parse softdeps exported by the kernel as + /lib/modules/`uname -r`/modules.softdep + +- Improvements to testsuite: + - Check the list of loaded modules after a test + +kmod 16 +======= + +- Bug fixes: + - Fix usage of readdir_r() + - Add some missing checks for memory allocation errors + +- New features: + - Remove option from libkmod to allow waiting on module removal if + the module is being used. It's dangerous since it can block the + caller indefinitely. + - Improve compatibility with musl libc + - Add fallback implementation for compilers without _Static_assert(), + e.g. gcc < 4.6 + - Minor optimizations to the hash table + - Make depmod warn if a module has incorrect devname specification + - Use cleanup attribute + +kmod 15 +======= + +- Bug fixes: + - kmod static-nodes doesn't fail if modules.devname isn't available + - Fix getting boolean parameter from kernel cmdline in case the value + is omitted + - Fix some mkdir_p() corner cases (used in testsuite and static-nodes) + +- New features: + - kmod static-nodes creates parent directories if given a -o option + - kmod binary statically links to libkmod - if distro is only interested + in the kmod tool (for example in an initrd) it can refrain from + installing the library + - Add shell completion for kmod tool + +kmod 14 +======= + +- Bug fixes: + - Fix some format strings + - Protect against NULL being passed around to index + - Avoid calling syscall() with -1 when finit_module() is not available, + since this doesn't always work + - Fix not being able to remove alias due to checking the module's + refcount + - Minor fixes and refactors + +- New features: + - Improve libkmod documentation, particularly on how flags are dealt + with. + - Remove ability to build a static libkmod + - Add static-nodes command to kmod that parses modules.devname + generating output in useful formats + +kmod 13 +======= + +- Bug fixes: + - Add the long option --symbol-prefix option to depmod (it was absent) + and fix its behavior + - Don't abort if there's a bogus line in configuration file like "alias + psmouse off". Some distros are carrying this since the days of + modutils + +- New features: + - Add support for finit_module(2). If the module is load straight from + the disk and without compression we use finit_module() syscall when + available, falling back to init_module() otherwise + - kmod_module_get_info() also returns the signature if the module is + signed and modinfo uses it + - Use secure_getenv if available + - rmmod understands builtin modules, just like modprobe does + - Improve compatibility with musl-libc + - Test cases exit with success when receiving a signal if they are + xfail tests + +kmod 12 +======= + +- Bug fixes: + - Fix removing vermagic from module when told to force load a module + - Fix removing __versions section when told to force load a module: we + need to mangle the section header, not the section. + - modinfo no longer fails while loading a module from file when path + contains ".ko" substring + +kmod 11 +======= + +- Improvements to testsuite: + - Fix testsuite defining symbols twice on 32 bit systems + - Allow to check generated files against correct ones + +- New features: + - libkmod now keeps a file opened after the first call to + kmod_module_get_{info,versions,symbols,dependency_symbols}. This + reduces significantly the amount of time depmod tool takes to + execute. Particularly if compressed modules are used. + - Remove --with-rootprefix from build system. It was not a great idea + after all and should not be use since it causes more harm then + benefits. + - Hide --wait option on rmmod. This feature is being targeted for + removal from kernel. rmmod still accepts this option, but it's hidden + now: man page and usage() say nothing about it and if it's used, + user will get a 10s sleep. This way we can check and help if anyone + is using this feature. + - Refactor message logging on all tools, giving proper prefix, routing + everything to syslog when asked for, etc. + +- Bug fixes: + - Fix parsing of modules.order when using compressed modules + - Usage messages go to stdout instead of stderr + - Fix memory leak in hash implementation + +kmod 10 +======= + +- New features: + - Read coresize from /sys if supported + + - Add flag to kmod_module_probe_insert() to apply blacklisting during + probe only if mod is an alias. Now modprobe uses this flag by default. + This is needed to fix a change in behavior regarding module-init-tools + and ultimately makes us loading a blacklisted module. + +- Better formatting in man pages + +- Add option to disable building man pages at build time + +- Fixes in the testsuite and refactoring of LDPRELOAD'ed libraries + +- Re-licensing testsuite as LGPL + +kmod 9 +====== + +- Improvements to the testsuite: + - Check for correct handling of softdep loops + - Check for correct handling of install command loops + +- Bug fixes: + - Fix build with compilers that don't support --gc-sections + - Handle errors when dealing with gzipped modules + - depmod now handles errors while writing indices, so it doesn't end up + with a corrupted index without telling the user + +kmod 8 +====== + +- No new features, small bug fixes only. + - Fix a bug in "modprobe -c" output: be compatible with + module-init-tools + + - Give a useful error message when init_module fails due to bad + parameter or unknown symbols + + - Fix doc generation + +kmod 7 +====== + +- Re-order dirs for configuration files to match the change in systemd and + udev: now the priority is: + 1. /etc/modprobe.d + 2. /run/modprobe.d + 3. /lib/modprobe.d + +- Fix setting CFLAGS/LDFLAGS in build system. This prevented us from not + allowing the user to set his preferences. + +- Bug fixes: + - Return same error codes of module-init-tools when removing modules + with modprobe + - Fix builtin output in "--show-depends" when target kernel is not the + same of the running kernel + - 'modprobe -r' always look at all command line arguments + - Fix '-q' usage in modprobe + +kmod 6 +====== + +- New API in libkmod: + - kmod_module_apply_filter(): a generic function to apply filters in a + list of modules. This deprecates the use of + kmod_module_get_filtered_blacklist() + +- More tests in testsuite + +- Add compatibility with uClibc again + +- Lookup modules.builtin.bin to decide if a module is built in kernel + +- Downgrade some log messages so we don't annoy people with useless messages + +- Bug fixes: + - Flag --ignore-loaded was not being properly handled + - Infinite loop with softdeps + - Infinite loop with dumb user configuration with install commands + - Fix leak in index when there's a partial match + +- Move repository and tarballs to kernel.org + +kmod 5 +====== + +- Break libkmod's API to insert a module like modprobe does. It now accepts + extra an extra argument to print its action and acceptable flags were + sanitized. + +- Share more code between modprobe and libkmod: using the new version of + kmod_module_probe_insert_module() it's possible to share a great amount of + code between modprobe and libkmod + +- modprobe no longer works with paths: it only accepts module names and/or + aliases now. + +- testsuite was added to repository, allowing automated tests to be run and + easing the way bugs are reproduced. + +- modprobe: when dumping configuration ('-c' option) separate config + and indexes by adding a commented line between them. + +- Fix bugs wrt normalizing aliases and module names + +- Fix bug wrt inserting an alias that resolves to multiple modules: we should + not stop on the first error, but rather continue to try loading other + modules. + +- Fix unaligned memory access in hash function, causing depmod to output wrong + information in ARMv5 + +- Fix man page build and install: now they are only installed if tools are + enabled + +kmod 4 +====== + +- New APIs in libkmod to: + - Get configuration lists: blacklists, install commands, remove + commands, aliases, options and softdeps + - Dump indexes + +- Several bugs fixed in libkmod, modprobe, depmod and modinfo + +- API documentation: if configure with run with --enable-gtk-doc, the API doc + will be generated by make. Gtk-doc is required for that. + +- Man pages are built, which replace man pages from module-init-tools + +- 'include' and 'config' options in *.conf files were deprecated + +- configure is not run by autogen.sh. Instead, a common set of options is + printed. If you are hacking on kmod, consider using bootstrap-configure + script. + +- 'modprobe -c' works as expected now. As opposed to module-init-tools, it + dumps the parsed configuration, not only the file contents. + +kmod 3 +====== + +- New APIs in libkmod to: + - Get symbols from module, parsing the ELF section + - Get dependency symbols + - Check if resources are still valid or if libkmod must be reloaded + - Insert module like modprobe, checking (soft-)dependencies, commands, + blacklist. It can run commands by itself and to call a callback + function. + +- Support to load modules compressed with xz + +- Tools are now bundled together in a single tool called kmod. It can be + called using symlinks with the same names as tools from module-init-tools. + E.g: /usr/bin/lsmod -> /usr/bin/kmod. With this we are aiming to complete a + 1:1 replacement of module-init-tools. + +- The only missing tool, depmod, was added to kmod together with the necessary + APIs in libkmod. + +- If a program using libkmod runs for a long time, as for example udev, it must + check if it doesn't have to re-load libkmod. A new helper function was added + in libkmod to check if context is still valid and udev is already using it. + +- An 'unaligned access' bug was fixed. So those architecture that does not + handle unaligned access can use kmod, too. + +kmod 2 +====== + +Some bugs fixed: the worst of them was with an infinite loop when an alias +matched more than one module. + +- New APIs in libkmod to: + - Get soft dependencies + - Get info from module files parsing ELF + - Get modversions from files parsing ELF + +- Support to load gzipped kernel modules: kmod can be compiled with support to + gzipped modules by giving the --enable-zlib flag + +- Support to forcefully load modules, both vermagic and modversion + +- Support to force and nowait removal flags + +- Configuration files are parsed in the same order as modprobe: files are + sorted alphabetically (independently of their dir) and files with the same + name obey a precedence order + +- New tool: kmod-modinfo + +- kmod-modprobe gained several features to be a 1:1 replacement for modprobe. + The only missing things are the options '--showconfig' and '-t / -l'. These + last ones have been deprecated long ago and they will be removed from + modprobe. A lot of effort has been put on kmod-modprobe to ensure it + maintains compatibility with modprobe. + +- linux-modules@vger.kernel.org became the official mailing list for kmod + +kmod 1 +====== + +First version of kmod and its library, libkmod. + +In the libkmod it's currently possible to: + - List modules currently loaded + - Get information about loaded modules such as initstate, refcount, + holders, sections, address and size + - Lookup modules by alias, module name or path + - Insert modules: options from configuration and extra options can be + passed, but flags are not implemented, yet + - Remove modules + - Filter list of modules using blacklist + - For each module, get the its list of options and install/remove + commands + - Indexes can be loaded on startup to speedup lookups later + +Tools provided with the same set of options as in module-init-tools: + - kmod-lsmod + - kmod-insmod + - kmod-rmmod + - kmod-modprobe, with some functionality still missing (use of softdep, + dump configuration, show modversions) diff --git a/README.md b/README.md new file mode 100644 index 0000000..5169890 --- /dev/null +++ b/README.md @@ -0,0 +1,82 @@ +## kmod - Linux kernel module handling + +OVERVIEW +======== + +kmod is a set of tools to handle common tasks with Linux kernel modules like +insert, remove, list, check properties, resolve dependencies and aliases. + +These tools are designed on top of libkmod, a library that is shipped with +kmod. See libkmod/README for more details on this library and how to use it. +The aim is to be compatible with tools, configurations and indexes from +module-init-tools project. + + +Links +===== +- Mailing list (no subscription needed): linux-modules@vger.kernel.org +- Mailing list archives: https://lore.kernel.org/linux-modules/ + +- Signed packages: http://www.kernel.org/pub/linux/utils/kernel/kmod/ + +- Git: + - Official: https://git.kernel.org/pub/scm/utils/kernel/kmod/kmod.git + - Mirror: https://github.com/kmod-project/kmod + - Mirror: https://kernel.googlesource.com/pub/scm/utils/kernel/kmod/kmod.git + +- License: + - LGPLv2.1+ for libkmod, testsuite and helper libraries + - GPLv2+ for tools/* + +- Irc: `#kmod` on irc.oftc.net + +- Issues: https://github.com/kmod-project/kmod/issues + + +Compilation and installation +============================ + +In order to compile the source code you need the following software packages: +- GCC/CLANG compiler +- GNU C library / musl / uClibc + +Optional dependencies, required with the default build configuration: +- ZLIB library +- LZMA library +- ZSTD library +- OPENSSL library (signature handling in modinfo) + +Typical configuration and installation + + meson setup builddir/ + meson compile -C builddir/ + sudo meson install -C builddir/ + +For end-user and distributions builds, it's recommended to use: + + meson setup --buildtype release builddir/ + +Alternatively you can try autotools build. +NOTE: The autotools build is slated for removal with kmod v35 + + ./configure CFLAGS="-g -O2" --prefix=/usr \ + --sysconfdir=/etc --libdir=/usr/lib + make && make install + +Hacking +======= + +When working on kmod, use the included `build-dev.ini` file, as: + + meson setup --native-file build-dev.ini builddir/ + +Make sure to read [our contributing guide](CONTRIBUTING.md) and the other +READMEs: [libkmod](libkmod/README) and [testsuite](testsuite/README). + +Compatibility with module-init-tools +==================================== + +kmod replaced module-init-tools, which was EOL'ed in 2011. All the tools were +rewritten on top of libkmod and they can be used as drop in replacements. +Along the years there were a few behavior changes and new features implemented, +following feedback from Linux kernel community and distros. diff --git a/autogen.sh b/autogen.sh new file mode 100755 index 0000000..33f363f --- /dev/null +++ b/autogen.sh @@ -0,0 +1,84 @@ +#!/bin/sh + +set -e + +oldpwd=$(pwd) + +if [ -n "$MESON_DIST_ROOT" ]; then + topdir="$MESON_DIST_ROOT" +else + topdir=$(dirname $0) +fi + +cd $topdir + +gtkdocize --docdir libkmod/docs || NO_GTK_DOC="yes" +if [ "x$NO_GTK_DOC" = "xyes" ] +then + for f in libkmod/docs/gtk-doc.make m4/gtk-doc.m4 + do + rm -f $f + touch $f + done +fi + +autoreconf --force --install --symlink + +libdir() { + echo $(cd "$1/$(gcc -print-multi-os-directory)"; pwd) +} + +args="\ +--prefix=/usr \ +--sysconfdir=/etc \ +--libdir=$(libdir /usr/lib) \ +" + +if [ -f "$topdir/.config.args" ]; then + args="$args $(cat $topdir/.config.args)" +fi + +cd $oldpwd + +hackargs="\ +--enable-debug \ +--enable-gtk-doc \ +--with-zstd \ +--with-xz \ +--with-zlib \ +--with-openssl \ +" + +if [ "x$1" = "xc" ]; then + shift + $topdir/configure CFLAGS='-g -O2' $args $hackargs "$@" + make clean +elif [ "x$1" = "xg" ]; then + shift + $topdir/configure CFLAGS='-g -Og' $args "$@" + make clean +elif [ "x$1" = "xl" ]; then + shift + $topdir/configure CC=clang CXX=clang++ $args "$@" + make clean +elif [ "x$1" = "xa" ]; then + shift + $topdir/configure CFLAGS='-g -O2 -Wsuggest-attribute=pure -Wsuggest-attribute=const' $args "$@" + make clean +elif [ "x$1" = "xs" ]; then + shift + scan-build $topdir/configure CFLAGS='-g -O0 -std=gnu11' $args "$@" + scan-build make +else + echo + echo "----------------------------------------------------------------" + echo "Initialized build system. For a common configuration please run:" + echo "----------------------------------------------------------------" + echo + echo "$topdir/configure CFLAGS='-g -O2' $args" + echo + echo If you are debugging or hacking on kmod, consider configuring + echo like below: + echo + echo "$topdir/configure CFLAGS='-g -O2' $args $hackargs" +fi diff --git a/build-dev.ini b/build-dev.ini new file mode 100644 index 0000000..804893f --- /dev/null +++ b/build-dev.ini @@ -0,0 +1,18 @@ +; SPDX-FileCopyrightText: 2024 Emil Velikov +; SPDX-FileCopyrightText: 2024 Lucas De Marchi +; +; SPDX-License-Identifier: LGPL-2.1-or-later + +[project options] +build-tests = true +debug-messages = true +docs = true +zstd = 'enabled' +xz = 'enabled' +zlib = 'enabled' +openssl = 'enabled' +werror = true +b_sanitize = 'address,undefined' + +[built-in options] +buildtype = 'debugoptimized' diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..f49f0e6 --- /dev/null +++ b/configure.ac @@ -0,0 +1,360 @@ +AC_PREREQ(2.64) +AC_INIT([kmod], + [34.1], + [linux-modules@vger.kernel.org], + [kmod], + [http://git.kernel.org/?p=utils/kernel/kmod/kmod.git]) + +AC_CONFIG_SRCDIR([libkmod/libkmod.c]) +AC_CONFIG_MACRO_DIR([m4]) +AC_CONFIG_HEADERS(config.h) +AC_CONFIG_AUX_DIR([build-aux]) + +AC_USE_SYSTEM_EXTENSIONS +AC_SYS_LARGEFILE +AC_PREFIX_DEFAULT([/usr]) +AM_MAINTAINER_MODE([enable]) +AM_INIT_AUTOMAKE([foreign 1.11 silent-rules tar-pax no-dist-gzip dist-xz subdir-objects color-tests parallel-tests]) +AM_SILENT_RULES([yes]) +LT_INIT([disable-static pic-only]) + +AS_IF([test "x$enable_static" = "xyes"], [AC_MSG_ERROR([--enable-static is not supported by kmod])]) +AS_IF([test "x$enable_largefile" = "xno"], [AC_MSG_ERROR([--disable-largefile is not supported by kmod])]) + +module_compressions="" +module_signatures="legacy" + +##################################################################### +# Program checks and configurations +##################################################################### + +AC_PROG_SED +AC_PROG_MKDIR_P +AC_PROG_LN_S +PKG_PROG_PKG_CONFIG + +AC_PROG_CC_C99 + +##################################################################### +# Function and structure checks +##################################################################### + +AC_CHECK_FUNCS_ONCE([open64 stat64 fopen64 __stat64_time64]) +AC_CHECK_FUNCS_ONCE([secure_getenv]) + +CC_CHECK_FUNC_BUILTIN([__builtin_clz]) +CC_CHECK_FUNC_BUILTIN([__builtin_types_compatible_p]) +CC_CHECK_FUNC_BUILTIN([__builtin_uadd_overflow], [ ], [ ]) +CC_CHECK_FUNC_BUILTIN([__builtin_uaddl_overflow], [ ], [ ]) +CC_CHECK_FUNC_BUILTIN([__builtin_uaddll_overflow], [ ], [ ]) +CC_CHECK_FUNC_BUILTIN([__builtin_umul_overflow], [ ], [ ]) +CC_CHECK_FUNC_BUILTIN([__builtin_umull_overflow], [ ], [ ]) +CC_CHECK_FUNC_BUILTIN([__builtin_umulll_overflow], [ ], [ ]) + +# dietlibc doesn't have st.st_mtim struct member +AC_CHECK_MEMBERS([struct stat.st_mtim], [], [], [#include ]) + +# basename may be only available in libgen.h with the POSIX behavior, +# not desired here +AC_CHECK_DECLS_ONCE([[basename]], [], [], [[#include ]]) +AC_CHECK_DECLS_ONCE([[__xstat]], [], [], [[#include + _Noreturn int foo(void) { exit(0); }]])], + [AC_DEFINE([HAVE_NORETURN], [1], [Define if _Noreturn is available]) + AC_MSG_RESULT([yes])], + [AC_MSG_RESULT([no])]) + + +##################################################################### +# --with- +##################################################################### + +AC_ARG_WITH([distconfdir], AS_HELP_STRING([--with-distconfdir=DIR], [directory to search for distribution configuration files]), + [], [with_distconfdir='${prefix}/lib']) +AC_SUBST([distconfdir], [$with_distconfdir]) + +# Ideally this would be $prefix/lib/modules but default to /lib/modules for compatibility with earlier versions +AC_ARG_WITH([module_directory], + AS_HELP_STRING([--with-module-directory=DIR], [directory in which to look for kernel modules @<:@default=/lib/modules@:>@]), + [], [with_module_directory=/lib/modules]) +AC_SUBST([module_directory], [$with_module_directory]) + +# Check all directory arguments for consistency. +for ac_var in distconfdir module_directory +do + eval ac_val=\$$ac_var + # Remove trailing slashes. + case $ac_val in + */ ) + ac_val=`expr "X$ac_val" : 'X\(.*@<:@^/@:>@\)' \| "X$ac_val" : 'X\(.*\)'` + eval $ac_var=\$ac_val;; + esac + # Be sure to have absolute directory names. + case $ac_val in + @<:@\\/$@:>@* | ?:@<:@\\/@:>@* ) continue;; + esac + as_fn_error $? "expected an absolute directory name for --$ac_var: $ac_val" +done + +AC_ARG_WITH([zstd], + AS_HELP_STRING([--with-zstd], [handle Zstandard-compressed modules @<:@default=disabled@:>@]), + [], [with_zstd=no]) +AS_IF([test "x$with_zstd" != "xno"], [ + PKG_CHECK_MODULES([libzstd], [libzstd >= 1.4.4], [LIBS="$LIBS $libzstd_LIBS"]) + AC_DEFINE([ENABLE_ZSTD], [1], [Zstandard for modules.]) + module_compressions="zstd $module_compressions" +], [ + AC_DEFINE([ENABLE_ZSTD], [0], [Zstandard for modules.]) + AC_MSG_NOTICE([Zstandard support not requested]) +]) +CC_FEATURE_APPEND([with_features], [with_zstd], [ZSTD]) +AM_CONDITIONAL([ENABLE_ZSTD], [test "x$with_zstd" != "xno"]) +AC_DEFINE([ENABLE_ZSTD_DLOPEN], [0], [dlopen zstd]) + +AC_ARG_WITH([xz], + AS_HELP_STRING([--with-xz], [handle Xz-compressed modules @<:@default=disabled@:>@]), + [], [with_xz=no]) +AS_IF([test "x$with_xz" != "xno"], [ + PKG_CHECK_MODULES([liblzma], [liblzma >= 4.99], [LIBS="$LIBS $liblzma_LIBS"]) + AC_DEFINE([ENABLE_XZ], [1], [xz for modules.]) + module_compressions="xz $module_compressions" +], [ + AC_DEFINE([ENABLE_XZ], [0], [xz for modules.]) + AC_MSG_NOTICE([Xz support not requested]) +]) +CC_FEATURE_APPEND([with_features], [with_xz], [XZ]) +AM_CONDITIONAL([ENABLE_XZ], [test "x$with_xz" != "xno"]) +AC_DEFINE([ENABLE_XZ_DLOPEN], [0], [dlopen xz]) + +AC_ARG_WITH([zlib], + AS_HELP_STRING([--with-zlib], [handle gzipped modules @<:@default=disabled@:>@]), + [], [with_zlib=no]) +AS_IF([test "x$with_zlib" != "xno"], [ + PKG_CHECK_MODULES([zlib], [zlib], [LIBS="$LIBS $zlib_LIBS"]) + AC_DEFINE([ENABLE_ZLIB], [1], [zlib for modules.]) + module_compressions="gzip $module_compressions" +], [ + AC_DEFINE([ENABLE_ZLIB], [0], [zlib for modules.]) + AC_MSG_NOTICE([zlib support not requested]) +]) +CC_FEATURE_APPEND([with_features], [with_zlib], [ZLIB]) +AM_CONDITIONAL([ENABLE_ZLIB], [test "x$with_zlib" != "xno"]) +AC_DEFINE([ENABLE_ZLIB_DLOPEN], [0], [dlopen zlib]) + +AC_ARG_WITH([openssl], + AS_HELP_STRING([--with-openssl], [handle PKCS7 signatures @<:@default=disabled@:>@]), + [], [with_openssl=no]) +AS_IF([test "x$with_openssl" != "xno"], [ + PKG_CHECK_MODULES([libcrypto], [libcrypto >= 1.1.0], [LIBS="$LIBS $libcrypto_LIBS"]) + AC_DEFINE([ENABLE_OPENSSL], [1], [openssl for modinfo.]) + module_signatures="PKCS7 $module_signatures" +], [ + AC_DEFINE([ENABLE_OPENSSL], [0], [openssl for modinfo.]) + AC_MSG_NOTICE([openssl support not requested]) +]) +CC_FEATURE_APPEND([with_features], [with_openssl], [LIBCRYPTO]) + +AC_ARG_WITH([bashcompletiondir], + AS_HELP_STRING([--with-bashcompletiondir=DIR], [Bash completions directory]), + [], + [AS_IF([$($PKG_CONFIG --exists bash-completion)], [ + with_bashcompletiondir=$($PKG_CONFIG --variable=completionsdir bash-completion) + ] , [ + with_bashcompletiondir=${datadir}/bash-completion/completions + ])]) +AC_SUBST([bashcompletiondir], [$with_bashcompletiondir]) + +AC_ARG_WITH([fishcompletiondir], + AS_HELP_STRING([--with-fishcompletiondir=DIR], [Fish completions directory]), + [], + [AS_IF([$($PKG_CONFIG --exists fish)], [ + with_fishcompletiondir=$($PKG_CONFIG --variable=completionsdir fish) + ] , [ + with_fishcompletiondir=${datadir}/fish/vendor_functions.d + ])]) +AC_SUBST([fishcompletiondir], [$with_fishcompletiondir]) + +AC_ARG_WITH([zshcompletiondir], + AS_HELP_STRING([--with-zshcompletiondir=DIR], [Zsh completions directory]), + [], + [with_zshcompletiondir=${datadir}/zsh/site-functions]) +AC_SUBST([zshcompletiondir], [$with_zshcompletiondir]) + +##################################################################### +# --enable- +##################################################################### + +AC_ARG_ENABLE([tools], + AS_HELP_STRING([--disable-tools], [disable building tools that provide same functionality as module-init-tools @<:@default=enabled@:>@]), + [], enable_tools=yes) +AM_CONDITIONAL([BUILD_TOOLS], [test "x$enable_tools" = "xyes"]) + +AC_ARG_ENABLE([manpages], + AS_HELP_STRING([--disable-manpages], [disable manpages @<:@default=enabled@:>@]), + [], enable_manpages=yes) +AS_IF([test "x$enable_manpages" = "xyes"], [ + AC_PATH_PROG([SCDOC], [scdoc]) + AS_IF([test "x$SCDOC" = "x"],[ + AC_MSG_ERROR([*** scdoc needed for building manpages. Either install it or pass --disable-manpages]) + ])]) +AM_CONDITIONAL([BUILD_MANPAGES], [test "x$enable_manpages" = "xyes"]) + +AC_ARG_ENABLE([logging], + AS_HELP_STRING([--disable-logging], [disable system logging @<:@default=enabled@:>@]), + [], enable_logging=yes) +AS_IF([test "x$enable_logging" = "xyes"], [ + AC_DEFINE(ENABLE_LOGGING, [1], [System logging.]) +], [ + AC_DEFINE(ENABLE_LOGGING, [0], [System logging.]) +]) + +AC_ARG_ENABLE([debug], + AS_HELP_STRING([--enable-debug], [enable debug messages @<:@default=disabled@:>@]), + [], [enable_debug=no]) +AS_IF([test "x$enable_debug" = "xyes"], [ + AC_DEFINE(ENABLE_DEBUG, [1], [Debug messages.]) +], [ + AC_DEFINE(ENABLE_DEBUG, [0], [Debug messages.]) +]) +AC_DEFINE(ENABLE_ELFDBG, [0], [Debug elf parsing messages.]) + +AC_ARG_ENABLE([coverage], + AS_HELP_STRING([--enable-coverage], [enable test coverage @<:@default=disabled@:>@]), + [], [enable_coverage=no]) +AS_IF([test "x$enable_coverage" = "xyes"], [ + AC_CHECK_PROG(have_coverage, [lcov], [yes], [no]) + AS_IF([test "x$have_coverage" = xno],[ + AC_MSG_ERROR([*** lcov support requested but the program was not found]) + ], [ + lcov_version_major="`lcov --version | cut -d ' ' -f 4 | cut -d '.' -f 1`" + lcov_version_minor="`lcov --version | cut -d ' ' -f 4 | cut -d '.' -f 2`" + AS_IF([test "$lcov_version_major" -lt 1 -o "$lcov_version_minor" -lt 10], [ + AC_MSG_ERROR([*** lcov version is too old. 1.10 required]) + ], [ + have_coverage=yes + CC_CHECK_FLAGS_APPEND([with_coverage_cflags], [CFLAGS], [\ + -fprofile-arcs \ + -ftest-coverage]) + ]) + ]) +]) +AM_CONDITIONAL([ENABLE_COVERAGE], [test "x$enable_coverage" = "xyes"]) + +m4_ifdef([GTK_DOC_CHECK], [ +GTK_DOC_CHECK([1.14],[--flavour no-tmpl-flat]) +], [ +AM_CONDITIONAL([ENABLE_GTK_DOC], false)]) + +PKG_INSTALLDIR +PKG_NOARCH_INSTALLDIR + +##################################################################### +# Default CFLAGS and LDFLAGS +##################################################################### + +CC_CHECK_FLAGS_APPEND(with_cflags, [CFLAGS], [\ + -pipe \ + -fdata-sections \ + -fdiagnostics-show-option \ + -ffunction-sections \ + -fno-common \ + -fvisibility=hidden \ + -W \ + -Wall \ + -Wchar-subscripts \ + -Wdeclaration-after-statement \ + -Wendif-labels \ + -Wextra \ + -Wfloat-equal \ + -Wformat=2 \ + -Wformat-nonliteral \ + -Wformat-security \ + -Winit-self \ + -Wlogical-op \ + -Wmissing-declarations \ + -Wmissing-include-dirs \ + -Wmissing-noreturn \ + -Wmissing-prototypes \ + -Wnested-externs \ + -Wno-attributes=clang::suppress \ + -Wno-unused-parameter \ + -Wold-style-definition \ + -Wpointer-arith \ + -Wredundant-decls \ + -Wshadow \ + -Wsign-compare \ + -Wstrict-aliasing=3 \ + -Wstrict-prototypes \ + -Wtype-limits \ + -Wundef \ + -Wuninitialized \ + -Wvla \ + -Wwrite-strings]) +AC_SUBST([OUR_CFLAGS], "$with_cflags $with_coverage_cflags") + + +CC_CHECK_FLAGS_APPEND([with_ldflags], [LDFLAGS], [ \ + -Wl,--as-needed \ + -Wl,--no-undefined \ + -Wl,--gc-sections]) +AC_SUBST([OUR_LDFLAGS], $with_ldflags) + +AC_DEFINE_UNQUOTED(KMOD_FEATURES, ["$with_features"], [Features in this build]) + +##################################################################### +# Generate files from *.in +##################################################################### + +AC_SUBST([module_compressions], $module_compressions) +AC_SUBST([module_signatures], $module_signatures) + +AC_CONFIG_FILES([ + Makefile + man/Makefile + libkmod/docs/Makefile + libkmod/docs/version.xml + libkmod/libkmod.pc + tools/kmod.pc +]) + + +##################################################################### + +AC_OUTPUT +AC_MSG_RESULT([ + $PACKAGE $VERSION + ======= + + module_directory: ${module_directory} + prefix: ${prefix} + sysconfdir: ${sysconfdir} + distconfdir: ${distconfdir} + libdir: ${libdir} + includedir: ${includedir} + bindir: ${bindir} + Bash completions dir: ${with_bashcompletiondir} + + compiler: ${CC} + cflags: ${with_cflags} ${CFLAGS} + ldflags: ${with_ldflags} ${LDFLAGS} + + tools: ${enable_tools} + logging: ${enable_logging} + compression: zstd=${with_zstd} xz=${with_xz} zlib=${with_zlib} + debug: ${enable_debug} + coverage: ${enable_coverage} + doc: ${enable_gtk_doc} + man: ${enable_manpages} + + features: ${with_features} +]) diff --git a/libkmod/.gitignore b/libkmod/.gitignore new file mode 100644 index 0000000..826fd62 --- /dev/null +++ b/libkmod/.gitignore @@ -0,0 +1,6 @@ +.dirstamp +.deps/ +.libs/ +*.la +*.lo +libkmod.pc diff --git a/libkmod/COPYING b/libkmod/COPYING new file mode 100644 index 0000000..8add30a --- /dev/null +++ b/libkmod/COPYING @@ -0,0 +1,504 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/libkmod/Makefile b/libkmod/Makefile new file mode 100644 index 0000000..223bec2 --- /dev/null +++ b/libkmod/Makefile @@ -0,0 +1,13 @@ +# Copyright 2010 Lennart Poettering +# +# This file has been copied from systemd. It is a dirty trick to simplify +# compilation when CWD is not the root of the source tree. This file is not +# intended to be distributed. So, don't touch it, even better ignore it! + +all: + $(MAKE) -C .. + +clean: + $(MAKE) -C .. clean + +.PHONY: all clean diff --git a/libkmod/README b/libkmod/README new file mode 100644 index 0000000..3e1c8dc --- /dev/null +++ b/libkmod/README @@ -0,0 +1,58 @@ +libkmod - linux kernel module handling library + +ABSTRACT +======== + +libkmod was created to allow programs to easily insert, remove and +list modules, also checking its properties, dependencies and aliases. + +there is no shared/global context information and it can be used by +multiple sites on a single program, also being able to be used from +threads, although it's not thread safe (you must lock explicitly). + + +OVERVIEW +======== + +Every user should create and manage it's own library context with: + + struct kmod_ctx *ctx = kmod_new(kernel_dirname); + kmod_unref(ctx); + + +Modules can be created by various means: + + struct kmod_module *mod; + int err; + + err = kmod_module_new_from_path(ctx, path, &mod); + if (err < 0) { + /* code */ + } else { + /* code */ + kmod_module_unref(mod); + } + + err = kmod_module_new_from_name(ctx, name, &mod); + if (err < 0) { + /* code */ + } else { + /* code */ + kmod_module_unref(mod); + } + + +Or could be resolved from a known alias to a list of alternatives: + + struct kmod_list *list, *itr; + int err; + err = kmod_module_new_from_lookup(ctx, alias, &list); + if (err < 0) { + /* code */ + } else { + kmod_list_foreach(itr, list) { + struct kmod_module *mod = kmod_module_get_module(itr); + /* code */ + } + } + diff --git a/libkmod/docs/.gitignore b/libkmod/docs/.gitignore new file mode 100644 index 0000000..7514b08 --- /dev/null +++ b/libkmod/docs/.gitignore @@ -0,0 +1,14 @@ +*.bak +*.stamp +*.sgml +libkmod.* +libkmod-*.xml +!libkmod-docs.xml +libkmod-*.txt +!libkmod-sections.txt +version.xml +xml +html +gtk-doc.make +Makefile +Makefile.in diff --git a/libkmod/docs/Makefile.am b/libkmod/docs/Makefile.am new file mode 100644 index 0000000..41482e9 --- /dev/null +++ b/libkmod/docs/Makefile.am @@ -0,0 +1,34 @@ + +AUTOMAKE_OPTIONS = 1.11 + +DOC_MODULE = libkmod + +DOC_MODULE_VERSION = 3 + +DOC_MAIN_SGML_FILE = $(DOC_MODULE)-docs.xml + +DOC_SOURCE_DIR = $(top_srcdir)/libkmod + +SCAN_OPTIONS = --ignore-decorators="KMOD_EXPORT" + +MKDB_OPTIONS = --xml-mode --output-format=xml --name-space kmod + +MKHTML_OPTIONS = --path=$(abs_srcdir)/doc --path=$(abs_builddir)/doc + +HFILE_GLOB = $(top_srcdir)/libkmod/libkmod.h +CFILE_GLOB = $(top_srcdir)/libkmod/libkmod.c $(top_srcdir)/libkmod/libkmod-module.c $(top_srcdir)/libkmod/libkmod-list.c + +IGNORE_HFILES = \ + libkmod-index.h \ + libkmod-internal-file.h \ + libkmod-internal.h + +content_files = version.xml + +EXTRA_DIST = + +if ENABLE_GTK_DOC +include $(top_srcdir)/libkmod/docs/gtk-doc.make +else +EXTRA_DIST += libkmod-docs.xml libkmod-sections.txt +endif diff --git a/libkmod/docs/libkmod-docs.xml b/libkmod/docs/libkmod-docs.xml new file mode 100644 index 0000000..674fe3a --- /dev/null +++ b/libkmod/docs/libkmod-docs.xml @@ -0,0 +1,63 @@ + + + +]> + + + libkmod Reference Manual + for libkmod version &version; + + + + libkmod + + + + + + + + + API Index + + + + Index of deprecated symbols + + + + Index of new symbols in 1 + + + + Index of new symbols in 2 + + + + Index of new symbols in 3 + + + + Index of new symbols in 4 + + + + Index of new symbols in 6 + + + + Index of new symbols in 22 + + + + Index of new symbols in 30 + + + + Index of new symbols in 33 + + + diff --git a/libkmod/docs/libkmod-sections.txt b/libkmod/docs/libkmod-sections.txt new file mode 100644 index 0000000..dd50264 --- /dev/null +++ b/libkmod/docs/libkmod-sections.txt @@ -0,0 +1,118 @@ +
+libkmod +kmod_ctx +kmod_new +kmod_ref +kmod_unref + +kmod_load_resources +kmod_unload_resources +kmod_resources +kmod_validate_resources +kmod_index +kmod_dump_index + +kmod_set_log_priority +kmod_get_log_priority +kmod_set_log_fn +kmod_get_userdata +kmod_set_userdata +kmod_get_dirname +
+ +
+libkmod-list +kmod_list +kmod_list_foreach +kmod_list_foreach_reverse +kmod_list_last +kmod_list_next +kmod_list_prev +
+ +
+libkmod-config +kmod_config_iter +kmod_config_get_blacklists +kmod_config_get_install_commands +kmod_config_get_remove_commands +kmod_config_get_aliases +kmod_config_get_options +kmod_config_get_softdeps +kmod_config_get_weakdeps +kmod_config_iter_get_key +kmod_config_iter_get_value +kmod_config_iter_next +kmod_config_iter_free_iter +
+ +
+libkmod-module +kmod_module +kmod_module_new_from_lookup +kmod_module_new_from_name_lookup +kmod_module_new_from_name +kmod_module_new_from_path + +kmod_module_ref +kmod_module_unref +kmod_module_unref_list + +kmod_insert +kmod_module_insert_module +kmod_probe +kmod_module_probe_insert_module +kmod_remove +kmod_module_remove_module + +kmod_module_get_module +kmod_module_get_dependencies +kmod_module_get_softdeps +kmod_module_get_weakdeps +kmod_filter +kmod_module_apply_filter +kmod_module_get_filtered_blacklist +kmod_module_get_install_commands +kmod_module_get_remove_commands +kmod_module_get_name +kmod_module_get_options +kmod_module_get_path + +kmod_module_get_dependency_symbols +kmod_symbol_bind +kmod_module_dependency_symbol_get_bind +kmod_module_dependency_symbol_get_crc +kmod_module_dependency_symbol_get_symbol +kmod_module_dependency_symbols_free_list + +kmod_module_get_sections +kmod_module_section_get_address +kmod_module_section_get_name +kmod_module_section_free_list + +kmod_module_get_symbols +kmod_module_symbol_get_crc +kmod_module_symbol_get_symbol +kmod_module_symbols_free_list + +kmod_module_get_versions +kmod_module_version_get_crc +kmod_module_version_get_symbol +kmod_module_versions_free_list + +kmod_module_get_info +kmod_module_info_get_key +kmod_module_info_get_value +kmod_module_info_free_list +
+ +
+libkmod-loaded +kmod_module_new_from_loaded +kmod_module_initstate +kmod_module_get_initstate +kmod_module_initstate_str +kmod_module_get_size +kmod_module_get_refcnt +kmod_module_get_holders +
diff --git a/libkmod/docs/meson.build b/libkmod/docs/meson.build new file mode 100644 index 0000000..b4d41cc --- /dev/null +++ b/libkmod/docs/meson.build @@ -0,0 +1,29 @@ +gnome = import('gnome') + +version_file = configure_file( + input: 'version.xml.in', + output: 'version.xml', + configuration: cdata, +) + +built_docs = gnome.gtkdoc( + 'libkmod', + content_files : version_file, + ignore_headers : [ + '@0@/libkmod/libkmod-index.h'.format(meson.project_source_root()), + '@0@/libkmod/libkmod-internal-file.h'.format(meson.project_source_root()), + '@0@/libkmod/libkmod-internal.h'.format(meson.project_source_root()), + ], + scan_args : '--ignore-decorators="KMOD_EXPORT"', + src_dir : '@0@/libkmod/'.format(meson.project_source_root()), + namespace : 'kmod', + module_version : '3', + main_xml : 'libkmod-docs.xml', +) + +test( + 'test-gtkdoc', + test_gtkdoc, + args : meson.current_build_dir(), + depends : built_docs, +) diff --git a/libkmod/docs/version.xml.in b/libkmod/docs/version.xml.in new file mode 100644 index 0000000..d78bda9 --- /dev/null +++ b/libkmod/docs/version.xml.in @@ -0,0 +1 @@ +@VERSION@ diff --git a/libkmod/libkmod-builtin.c b/libkmod/libkmod-builtin.c new file mode 100644 index 0000000..293ea89 --- /dev/null +++ b/libkmod/libkmod-builtin.c @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2019 Alexey Gladkov + * Copyright (C) 2024 Tobias Stöckmann + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +#define MODULES_BUILTIN_MODINFO "modules.builtin.modinfo" + +struct kmod_builtin_info { + struct kmod_ctx *ctx; + + // The file handle. + FILE *fp; + + // Internal buffer and its size. Used by getdelim. + size_t bufsz; + char *buf; +}; + +static bool kmod_builtin_info_init(struct kmod_builtin_info *info, struct kmod_ctx *ctx) +{ + char path[PATH_MAX]; + FILE *fp = NULL; + const char *dirname = kmod_get_dirname(ctx); + size_t len = strlen(dirname); + + if ((len + 1 + strlen(MODULES_BUILTIN_MODINFO) + 1) >= PATH_MAX) { + errno = ENAMETOOLONG; + return false; + } + snprintf(path, PATH_MAX, "%s/" MODULES_BUILTIN_MODINFO, dirname); + + fp = fopen(path, "r"); + if (fp == NULL) + return false; + + info->ctx = ctx; + info->fp = fp; + info->bufsz = 0; + info->buf = NULL; + + return true; +} + +static void kmod_builtin_info_release(struct kmod_builtin_info *info) +{ + free(info->buf); + fclose(info->fp); +} + +static ssize_t get_string(struct kmod_builtin_info *info) +{ + ssize_t len; + + len = getdelim(&info->buf, &info->bufsz, '\0', info->fp); + if (len > 0 && info->buf[len] != '\0') { + errno = EINVAL; + len = -1; + } + + return len; +} + +static ssize_t get_strings(struct kmod_builtin_info *info, const char *modname, + struct strbuf *buf) +{ + const size_t modlen = strlen(modname); + ssize_t count, n; + + for (count = 0; count < INTPTR_MAX;) { + char *dot, *line; + + n = get_string(info); + if (n == -1) { + if (!feof(info->fp)) { + count = -errno; + ERR(info->ctx, "get_string: %s\n", strerror(errno)); + } + break; + } + + line = info->buf; + dot = strchr(line, '.'); + if (dot == NULL) { + count = -EINVAL; + ERR(info->ctx, "get_strings: " + "unexpected string without modname prefix\n"); + return count; + } + if (strncmp(line, modname, modlen) || line[modlen] != '.') { + /* + * If no string matched modname yet, keep searching. + * Otherwise this indicates that no further string will + * match again. + */ + if (count == 0) + continue; + break; + } + if (!strbuf_pushchars(buf, dot + 1) || !strbuf_pushchar(buf, '\0')) { + count = -errno; + ERR(info->ctx, "get_strings: " + "failed to append modinfo string\n"); + return count; + } + count++; + } + + if (count == INTPTR_MAX) { + count = -ENOMEM; + ERR(info->ctx, "get_strings: " + "too many modinfo strings encountered\n"); + return count; + } + + return count; +} + +static char **strbuf_to_vector(struct strbuf *buf, size_t count) +{ + size_t vec_size, total_size; + char **vector; + char *s; + size_t n; + + /* (string vector + NULL) * sizeof(char *) + strbuf_used() */ + if (uaddsz_overflow(count, 1, &n) || + umulsz_overflow(sizeof(char *), n, &vec_size) || + uaddsz_overflow(strbuf_used(buf), vec_size, &total_size)) { + errno = ENOMEM; + return NULL; + } + + vector = realloc(buf->bytes, total_size); + if (vector == NULL) + return NULL; + buf->bytes = NULL; + memmove(vector + count + 1, vector, strbuf_used(buf)); + s = (char *)(vector + count + 1); + + for (n = 0; n < count; n++) { + vector[n] = s; + s += strlen(s) + 1; + } + vector[n] = NULL; + + return vector; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +ssize_t kmod_builtin_get_modinfo(struct kmod_ctx *ctx, const char *modname, + char ***modinfo) +{ + struct kmod_builtin_info info; + struct strbuf buf; + ssize_t count; + + if (!kmod_builtin_info_init(&info, ctx)) + return -errno; + strbuf_init(&buf); + + count = get_strings(&info, modname, &buf); + if (count == 0) + *modinfo = NULL; + else if (count > 0) { + *modinfo = strbuf_to_vector(&buf, (size_t)count); + if (*modinfo == NULL) { + count = -errno; + ERR(ctx, "strbuf_to_vector: %s\n", strerror(errno)); + strbuf_release(&buf); + } + } + + kmod_builtin_info_release(&info); + return count; +} diff --git a/libkmod/libkmod-config.c b/libkmod/libkmod-config.c new file mode 100644 index 0000000..77b9392 --- /dev/null +++ b/libkmod/libkmod-config.c @@ -0,0 +1,1292 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + * Copyright (C) 2013 Intel Corporation. All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +struct kmod_alias { + char *name; + char modname[]; +}; + +struct kmod_options { + char *options; + char modname[]; +}; + +struct kmod_command { + char *command; + char modname[]; +}; + +struct kmod_softdep { + char *name; + const char **pre; + const char **post; + unsigned int n_pre; + unsigned int n_post; +}; + +struct kmod_weakdep { + char *name; + const char **weak; + unsigned int n_weak; +}; + +const char *kmod_blacklist_get_modname(const struct kmod_list *l) +{ + return l->data; +} + +const char *kmod_alias_get_name(const struct kmod_list *l) +{ + const struct kmod_alias *alias = l->data; + return alias->name; +} + +const char *kmod_alias_get_modname(const struct kmod_list *l) +{ + const struct kmod_alias *alias = l->data; + return alias->modname; +} + +const char *kmod_option_get_options(const struct kmod_list *l) +{ + const struct kmod_options *alias = l->data; + return alias->options; +} + +const char *kmod_option_get_modname(const struct kmod_list *l) +{ + const struct kmod_options *alias = l->data; + return alias->modname; +} + +const char *kmod_command_get_command(const struct kmod_list *l) +{ + const struct kmod_command *alias = l->data; + return alias->command; +} + +const char *kmod_command_get_modname(const struct kmod_list *l) +{ + const struct kmod_command *alias = l->data; + return alias->modname; +} + +const char *kmod_softdep_get_name(const struct kmod_list *l) +{ + const struct kmod_softdep *dep = l->data; + return dep->name; +} + +const char *const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count) +{ + const struct kmod_softdep *dep = l->data; + *count = dep->n_pre; + return dep->pre; +} + +const char *const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count) +{ + const struct kmod_softdep *dep = l->data; + *count = dep->n_post; + return dep->post; +} + +const char *kmod_weakdep_get_name(const struct kmod_list *l) +{ + const struct kmod_weakdep *dep = l->data; + return dep->name; +} + +const char *const *kmod_weakdep_get_weak(const struct kmod_list *l, unsigned int *count) +{ + const struct kmod_weakdep *dep = l->data; + *count = dep->n_weak; + return dep->weak; +} +static int kmod_config_add_command(struct kmod_config *config, const char *modname, + const char *command, const char *command_name, + struct kmod_list **list) +{ + _cleanup_free_ struct kmod_command *cmd; + struct kmod_list *l; + size_t modnamelen = strlen(modname) + 1; + size_t commandlen = strlen(command) + 1; + + DBG(config->ctx, "modname='%s' cmd='%s %s'\n", modname, command_name, command); + + cmd = malloc(sizeof(*cmd) + modnamelen + commandlen); + if (!cmd) + return -ENOMEM; + + cmd->command = sizeof(*cmd) + modnamelen + (char *)cmd; + memcpy(cmd->modname, modname, modnamelen); + memcpy(cmd->command, command, commandlen); + + l = kmod_list_append(*list, cmd); + if (!l) + return -ENOMEM; + + TAKE_PTR(cmd); + *list = l; + + return 0; +} + +static int kmod_config_add_options(struct kmod_config *config, const char *modname, + const char *options) +{ + _cleanup_free_ struct kmod_options *opt; + struct kmod_list *list; + size_t modnamelen = strlen(modname) + 1; + size_t optionslen = strlen(options) + 1; + + DBG(config->ctx, "modname='%s' options='%s'\n", modname, options); + + opt = malloc(sizeof(*opt) + modnamelen + optionslen); + if (!opt) + return -ENOMEM; + + opt->options = sizeof(*opt) + modnamelen + (char *)opt; + + memcpy(opt->modname, modname, modnamelen); + memcpy(opt->options, options, optionslen); + strchr_replace(opt->options, '\t', ' '); + + list = kmod_list_append(config->options, opt); + if (!list) + return -ENOMEM; + + TAKE_PTR(opt); + config->options = list; + + return 0; +} + +static int kmod_config_add_alias(struct kmod_config *config, const char *name, + const char *modname) +{ + _cleanup_free_ struct kmod_alias *alias; + struct kmod_list *list; + size_t namelen = strlen(name) + 1, modnamelen = strlen(modname) + 1; + + DBG(config->ctx, "name=%s modname=%s\n", name, modname); + + alias = malloc(sizeof(*alias) + namelen + modnamelen); + if (!alias) + return -ENOMEM; + + alias->name = sizeof(*alias) + modnamelen + (char *)alias; + + memcpy(alias->modname, modname, modnamelen); + memcpy(alias->name, name, namelen); + + list = kmod_list_append(config->aliases, alias); + if (!list) + return -ENOMEM; + + TAKE_PTR(alias); + config->aliases = list; + + return 0; +} + +static int kmod_config_add_blacklist(struct kmod_config *config, const char *modname) +{ + _cleanup_free_ char *p; + struct kmod_list *list; + + DBG(config->ctx, "modname=%s\n", modname); + + _clang_suppress_alloc_ p = strdup(modname); + if (!p) + return -ENOMEM; + + list = kmod_list_append(config->blacklists, p); + if (!list) + return -ENOMEM; + + TAKE_PTR(p); + config->blacklists = list; + + return 0; +} + +static int kmod_config_add_softdep(struct kmod_config *config, const char *modname, + const char *line) +{ + struct kmod_list *list; + struct kmod_softdep *dep; + const char *s, *p; + char *itr; + unsigned int n_pre = 0, n_post = 0; + size_t modnamelen = strlen(modname) + 1; + size_t buflen = 0, size, size_pre, size_post; + bool was_space = false; + enum { S_NONE, S_PRE, S_POST } mode = S_NONE; + + DBG(config->ctx, "modname=%s\n", modname); + + /* analyze and count */ + for (p = s = line;; s++) { + size_t plen; + + if (*s != '\0') { + if (!isspace(*s)) { + was_space = false; + continue; + } + + if (was_space) { + p = s + 1; + continue; + } + was_space = true; + + if (p >= s) + continue; + } + plen = s - p; + + if (plen == sizeof("pre:") - 1 && + memcmp(p, "pre:", sizeof("pre:") - 1) == 0) + mode = S_PRE; + else if (plen == sizeof("post:") - 1 && + memcmp(p, "post:", sizeof("post:") - 1) == 0) + mode = S_POST; + else if (*s != '\0' || (*s == '\0' && !was_space)) { + if (mode == S_PRE) { + buflen += plen + 1; + if (uadd32_overflow(n_pre, 1, &n_pre)) { + ERR(config->ctx, + "too many pre softdeps for modname=%s\n", + modname); + return -EINVAL; + } + } else if (mode == S_POST) { + buflen += plen + 1; + if (uadd32_overflow(n_post, 1, &n_post)) { + ERR(config->ctx, + "too many post softdeps for modname=%s\n", + modname); + return -EINVAL; + } + } + } + p = s + 1; + if (*s == '\0') + break; + } + + DBG(config->ctx, "%u pre, %u post\n", n_pre, n_post); + + /* + * sizeof(struct kmod_softdep) + modnamelen + + * n_pre * sizeof(const char *) + n_post * sizeof(const char *) + buflen + */ + if (uaddsz_overflow(sizeof(struct kmod_softdep), modnamelen, &size) || + umulsz_overflow(n_pre, sizeof(const char *), &size_pre) || + uaddsz_overflow(size, size_pre, &size) || + umulsz_overflow(n_post, sizeof(const char *), &size_post) || + uaddsz_overflow(size, size_post, &size) || + uaddsz_overflow(size, buflen, &size)) { + ERR(config->ctx, "out-of-memory modname=%s\n", modname); + return -ENOMEM; + } + + dep = malloc(size); + if (dep == NULL) { + ERR(config->ctx, "out-of-memory modname=%s\n", modname); + return -ENOMEM; + } + dep->n_pre = n_pre; + dep->n_post = n_post; + dep->pre = (const char **)((char *)dep + sizeof(struct kmod_softdep)); + dep->post = dep->pre + n_pre; + dep->name = (char *)(dep->post + n_post); + + memcpy(dep->name, modname, modnamelen); + + /* copy strings */ + itr = dep->name + modnamelen; + n_pre = 0; + n_post = 0; + mode = S_NONE; + was_space = false; + for (p = s = line;; s++) { + size_t plen; + + if (*s != '\0') { + if (!isspace(*s)) { + was_space = false; + continue; + } + + if (was_space) { + p = s + 1; + continue; + } + was_space = true; + + if (p >= s) + continue; + } + plen = s - p; + + if (plen == sizeof("pre:") - 1 && + memcmp(p, "pre:", sizeof("pre:") - 1) == 0) + mode = S_PRE; + else if (plen == sizeof("post:") - 1 && + memcmp(p, "post:", sizeof("post:") - 1) == 0) + mode = S_POST; + else if (*s != '\0' || (*s == '\0' && !was_space)) { + if (mode == S_PRE) { + dep->pre[n_pre] = itr; + memcpy(itr, p, plen); + itr[plen] = '\0'; + itr += plen + 1; + n_pre++; + } else if (mode == S_POST) { + dep->post[n_post] = itr; + memcpy(itr, p, plen); + itr[plen] = '\0'; + itr += plen + 1; + n_post++; + } + } + p = s + 1; + if (*s == '\0') + break; + } + + list = kmod_list_append(config->softdeps, dep); + if (list == NULL) { + free(dep); + return -ENOMEM; + } + config->softdeps = list; + + return 0; +} + +static int kmod_config_add_weakdep(struct kmod_config *config, const char *modname, + const char *line) +{ + struct kmod_list *list; + struct kmod_weakdep *dep; + const char *s, *p; + char *itr; + unsigned int n_weak = 0; + size_t modnamelen = strlen(modname) + 1; + size_t buflen = 0, size, size_weak; + bool was_space = false; + + DBG(config->ctx, "modname=%s\n", modname); + + /* analyze and count */ + for (p = s = line;; s++) { + size_t plen; + + if (*s != '\0') { + if (!isspace(*s)) { + was_space = false; + continue; + } + + if (was_space) { + p = s + 1; + continue; + } + was_space = true; + + if (p >= s) + continue; + } + plen = s - p; + + if (*s != '\0' || (*s == '\0' && !was_space)) { + buflen += plen + 1; + if (uadd32_overflow(n_weak, 1, &n_weak)) { + ERR(config->ctx, "too many weakdeps for modname=%s\n", + modname); + return -EINVAL; + } + } + p = s + 1; + if (*s == '\0') + break; + } + + DBG(config->ctx, "%u weak\n", n_weak); + + /* sizeof(struct kmod_weakdep) + modnamelen + n_weak * sizeof(const char *) + buflen */ + if (uaddsz_overflow(sizeof(struct kmod_weakdep), modnamelen, &size) || + umulsz_overflow(n_weak, sizeof(const char *), &size_weak) || + uaddsz_overflow(size, size_weak, &size) || + uaddsz_overflow(size, buflen, &size)) { + ERR(config->ctx, "out-of-memory modname=%s\n", modname); + return -ENOMEM; + } + + dep = malloc(size); + if (dep == NULL) { + ERR(config->ctx, "out-of-memory modname=%s\n", modname); + return -ENOMEM; + } + dep->n_weak = n_weak; + dep->weak = (const char **)((char *)dep + sizeof(struct kmod_weakdep)); + dep->name = (char *)(dep->weak + n_weak); + + memcpy(dep->name, modname, modnamelen); + + /* copy strings */ + itr = dep->name + modnamelen; + n_weak = 0; + was_space = false; + for (p = s = line;; s++) { + size_t plen; + + if (*s != '\0') { + if (!isspace(*s)) { + was_space = false; + continue; + } + + if (was_space) { + p = s + 1; + continue; + } + was_space = true; + + if (p >= s) + continue; + } + plen = s - p; + + if (*s != '\0' || (*s == '\0' && !was_space)) { + dep->weak[n_weak] = itr; + memcpy(itr, p, plen); + itr[plen] = '\0'; + itr += plen + 1; + n_weak++; + } + p = s + 1; + if (*s == '\0') + break; + } + + list = kmod_list_append(config->weakdeps, dep); + if (list == NULL) { + free(dep); + return -ENOMEM; + } + config->weakdeps = list; + + return 0; +} + +static char *softdep_to_char(struct kmod_softdep *dep) +{ + const size_t sz_preprefix = sizeof("pre: ") - 1; + const size_t sz_postprefix = sizeof("post: ") - 1; + size_t sz = 1; /* at least '\0' */ + size_t sz_pre, sz_post; + const char *start, *end; + char *s, *itr; + + /* + * Rely on the fact that dep->pre[] and dep->post[] are strv's that + * point to a contiguous buffer + */ + if (dep->n_pre > 0) { + start = dep->pre[0]; + end = dep->pre[dep->n_pre - 1] + strlen(dep->pre[dep->n_pre - 1]); + sz_pre = end - start; + sz += sz_pre + sz_preprefix; + } else + sz_pre = 0; + + if (dep->n_post > 0) { + start = dep->post[0]; + end = dep->post[dep->n_post - 1] + strlen(dep->post[dep->n_post - 1]); + sz_post = end - start; + sz += sz_post + sz_postprefix; + } else + sz_post = 0; + + itr = s = malloc(sz); + if (s == NULL) + return NULL; + + if (sz_pre) { + char *p; + + memcpy(itr, "pre: ", sz_preprefix); + itr += sz_preprefix; + + /* include last '\0' */ + memcpy(itr, dep->pre[0], sz_pre + 1); + for (p = itr; p < itr + sz_pre; p++) { + if (*p == '\0') + *p = ' '; + } + itr = p; + } + + if (sz_post) { + char *p; + + memcpy(itr, "post: ", sz_postprefix); + itr += sz_postprefix; + + /* include last '\0' */ + memcpy(itr, dep->post[0], sz_post + 1); + for (p = itr; p < itr + sz_post; p++) { + if (*p == '\0') + *p = ' '; + } + itr = p; + } + + *itr = '\0'; + + return s; +} + +static char *weakdep_to_char(struct kmod_weakdep *dep) +{ + size_t sz; + const char *start, *end; + char *s, *itr; + + /* Rely on the fact that dep->weak[] is a strv that points to a contiguous buffer */ + if (dep->n_weak > 0) { + start = dep->weak[0]; + end = dep->weak[dep->n_weak - 1] + strlen(dep->weak[dep->n_weak - 1]); + sz = end - start; + } else + sz = 0; + + itr = s = malloc(sz); + if (s == NULL) + return NULL; + + if (sz) { + char *p; + + /* include last '\0' */ + memcpy(itr, dep->weak[0], sz + 1); + for (p = itr; p < itr + sz; p++) { + if (*p == '\0') + *p = ' '; + } + itr = p; + } + + *itr = '\0'; + + return s; +} + +static void kcmdline_parse_result(struct kmod_config *config, char *modname, char *param, + char *value) +{ + if (modname == NULL || param == NULL) + return; + + DBG(config->ctx, "%s %s\n", modname, param); + + if (streq(modname, "modprobe") && !strncmp(param, "blacklist=", 10)) { + for (;;) { + char *t = strsep(&value, ","); + if (t == NULL) + break; + + kmod_config_add_blacklist(config, t); + } + } else { + if (underscores(modname) < 0) { + ERR(config->ctx, + "Ignoring bad option on kernel command line while parsing module name: '%s'\n", + modname); + } else { + kmod_config_add_options(config, modname, param); + } + } +} + +static int kmod_config_parse_kcmdline(struct kmod_config *config) +{ + char buf[KCMD_LINE_SIZE]; + int fd, err; + char *p, *p_quote_start, *modname, *param = NULL, *value = NULL; + bool is_quoted = false, iter = true; + enum state { + STATE_IGNORE, + STATE_MODNAME, + STATE_PARAM, + STATE_VALUE, + STATE_COMPLETE, + } state; + + fd = open("/proc/cmdline", O_RDONLY | O_CLOEXEC); + if (fd < 0) { + err = -errno; + DBG(config->ctx, "could not open '/proc/cmdline' for reading: %m\n"); + return err; + } + + err = read_str_safe(fd, buf, sizeof(buf)); + close(fd); + if (err < 0) { + ERR(config->ctx, "could not read from '/proc/cmdline': %s\n", + strerror(-err)); + return err; + } + + state = STATE_MODNAME; + p_quote_start = NULL; + for (p = buf, modname = buf; iter; p++) { + switch (*p) { + case '"': + is_quoted = !is_quoted; + + /* + * only allow starting quote as first char when looking + * for a modname: anything else is considered ill-formed + */ + if (is_quoted && state == STATE_MODNAME && p == modname) { + p_quote_start = p; + modname = p + 1; + } else if (state != STATE_VALUE) { + state = STATE_IGNORE; + } + + break; + case '\0': + iter = false; + /* fall-through */ + case ' ': + case '\n': + case '\t': + case '\v': + case '\f': + case '\r': + if (is_quoted && state == STATE_VALUE) { + /* no state change*/; + } else if (is_quoted) { + /* spaces are only allowed in the value part */ + state = STATE_IGNORE; + } else if (state == STATE_VALUE || state == STATE_PARAM) { + *p = '\0'; + state = STATE_COMPLETE; + } else { + /* + * go to next option, ignoring any possible + * partial match we have + */ + modname = p + 1; + state = STATE_MODNAME; + p_quote_start = NULL; + } + break; + case '.': + if (state == STATE_MODNAME) { + *p = '\0'; + param = p + 1; + state = STATE_PARAM; + } else if (state == STATE_PARAM) { + state = STATE_IGNORE; + } + break; + case '=': + if (state == STATE_PARAM) { + /* + * Don't set *p to '\0': the value var shadows + * param + */ + value = p + 1; + state = STATE_VALUE; + } else if (state == STATE_MODNAME) { + state = STATE_IGNORE; + } + break; + } + + if (state == STATE_COMPLETE) { + /* + * We may need to re-quote to unmangle what the + * bootloader passed. Example: grub passes the option as + * "parport.dyndbg=file drivers/parport/ieee1284_ops.c +mpf" + * instead of + * parport.dyndbg="file drivers/parport/ieee1284_ops.c +mpf" + */ + if (p_quote_start && p_quote_start < modname) { + /* + * p_quote_start + * | + * |modname param value + * || | | + * vv v v + * "parport\0dyndbg=file drivers/parport/ieee1284_ops.c +mpf" */ + memmove(p_quote_start, modname, value - modname); + value--; + modname--; + param--; + *value = '"'; + } + kcmdline_parse_result(config, modname, param, value); + /* start over on next iteration */ + modname = p + 1; + state = STATE_MODNAME; + p_quote_start = NULL; + } + } + + return 0; +} + +/* + * Take an fd and own it. It will be closed on return. filename is used only + * for debug messages + */ +static int kmod_config_parse(struct kmod_config *config, int fd, const char *filename) +{ + struct kmod_ctx *ctx = config->ctx; + char *line; + FILE *fp; + unsigned int linenum = 0; + int err; + + fp = fdopen(fd, "r"); + if (fp == NULL) { + err = -errno; + ERR(config->ctx, "fd %d: %m\n", fd); + close(fd); + return err; + } + + while ((line = freadline_wrapped(fp, &linenum)) != NULL) { + char *cmd, *saveptr; + + if (line[0] == '\0' || line[0] == '#') + goto done_next; + + cmd = strtok_r(line, "\t ", &saveptr); + if (cmd == NULL) + goto done_next; + + if (streq(cmd, "alias")) { + char *alias = strtok_r(NULL, "\t ", &saveptr); + char *modname = strtok_r(NULL, "\t ", &saveptr); + + if (underscores(alias) < 0 || underscores(modname) < 0) + goto syntax_error; + + kmod_config_add_alias(config, alias, modname); + } else if (streq(cmd, "blacklist")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + + if (underscores(modname) < 0) + goto syntax_error; + + kmod_config_add_blacklist(config, modname); + } else if (streq(cmd, "options")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *options = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || options == NULL) + goto syntax_error; + + kmod_config_add_options(config, modname, options); + } else if (streq(cmd, "install")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *installcmd = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || installcmd == NULL) + goto syntax_error; + + kmod_config_add_command(config, modname, installcmd, cmd, + &config->install_commands); + } else if (streq(cmd, "remove")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *removecmd = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || removecmd == NULL) + goto syntax_error; + + kmod_config_add_command(config, modname, removecmd, cmd, + &config->remove_commands); + } else if (streq(cmd, "softdep")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *softdeps = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || softdeps == NULL) + goto syntax_error; + + kmod_config_add_softdep(config, modname, softdeps); + } else if (streq(cmd, "weakdep")) { + char *modname = strtok_r(NULL, "\t ", &saveptr); + char *weakdeps = strtok_r(NULL, "\0", &saveptr); + + if (underscores(modname) < 0 || weakdeps == NULL) + goto syntax_error; + + kmod_config_add_weakdep(config, modname, weakdeps); + } else if (streq(cmd, "include") || streq(cmd, "config")) { + ERR(ctx, "%s: command %s is deprecated and not parsed anymore\n", + filename, cmd); + } else { +syntax_error: + ERR(ctx, "%s line %u: ignoring bad line starting with '%s'\n", + filename, linenum, cmd); + } + +done_next: + free(line); + } + + fclose(fp); + + return 0; +} + +void kmod_config_free(struct kmod_config *config) +{ + kmod_list_release(config->aliases, free); + kmod_list_release(config->blacklists, free); + kmod_list_release(config->options, free); + kmod_list_release(config->install_commands, free); + kmod_list_release(config->remove_commands, free); + kmod_list_release(config->softdeps, free); + kmod_list_release(config->weakdeps, free); + kmod_list_release(config->paths, free); + free(config); +} + +static bool conf_files_filter_out(struct kmod_ctx *ctx, DIR *d, const char *path, + const char *fn) +{ + size_t len = strlen(fn); + struct stat st; + + if (fn[0] == '.') + return true; + + if (len < 6 || !streq(&fn[len - 5], ".conf")) + return true; + + if (fstatat(dirfd(d), fn, &st, 0) < 0) { + ERR(ctx, "Cannot stat directory entry: %s/%s\n", path, fn); + return true; + } + + if (S_ISDIR(st.st_mode)) { + ERR(ctx, + "Directories inside directories are not supported: " + "%s/%s\n", + path, fn); + return true; + } + + return false; +} + +struct conf_file { + const char *path; + bool is_single; + char name[]; +}; + +static int conf_files_insert_sorted(struct kmod_ctx *ctx, struct kmod_list **list, + const char *path, const char *name) +{ + struct kmod_list *lpos, *tmp; + struct conf_file *cf; + size_t namelen; + int cmp = -1; + bool is_single = false; + + if (name == NULL) { + name = basename(path); + is_single = true; + } + + kmod_list_foreach(lpos, *list) { + cf = lpos->data; + + if ((cmp = strcmp(name, cf->name)) <= 0) + break; + } + + if (cmp == 0) { + DBG(ctx, "Ignoring duplicate config file: %s/%s\n", path, name); + return -EEXIST; + } + + namelen = strlen(name); + cf = malloc(sizeof(*cf) + namelen + 1); + if (cf == NULL) + return -ENOMEM; + + memcpy(cf->name, name, namelen + 1); + cf->path = path; + cf->is_single = is_single; + + if (lpos == NULL) + tmp = kmod_list_append(*list, cf); + else if (lpos == *list) + tmp = kmod_list_prepend(*list, cf); + else + tmp = kmod_list_insert_before(lpos, cf); + + if (tmp == NULL) { + free(cf); + return -ENOMEM; + } + + if (lpos == NULL || lpos == *list) + *list = tmp; + + return 0; +} + +/* + * Insert configuration files in @list, ignoring duplicates + */ +static int conf_files_list(struct kmod_ctx *ctx, struct kmod_list **list, + const char *path, unsigned long long *path_stamp) +{ + DIR *d; + int err; + struct stat st; + struct dirent *dent; + + if (stat(path, &st) != 0) { + err = -errno; + DBG(ctx, "could not stat '%s': %m\n", path); + return err; + } + + *path_stamp = stat_mstamp(&st); + + if (!S_ISDIR(st.st_mode)) { + conf_files_insert_sorted(ctx, list, path, NULL); + return 0; + } + + d = opendir(path); + if (d == NULL) { + ERR(ctx, "opendir(%s): %m\n", path); + return -EINVAL; + } + + for (dent = readdir(d); dent != NULL; dent = readdir(d)) { + if (conf_files_filter_out(ctx, d, path, dent->d_name)) + continue; + + conf_files_insert_sorted(ctx, list, path, dent->d_name); + } + + closedir(d); + return 0; +} + +int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **p_config, + const char *const *config_paths) +{ + struct kmod_config *config; + struct kmod_list *list = NULL; + struct kmod_list *path_list = NULL; + size_t i; + + conf_files_insert_sorted(ctx, &list, kmod_get_dirname(ctx), "modules.softdep"); + conf_files_insert_sorted(ctx, &list, kmod_get_dirname(ctx), "modules.weakdep"); + + for (i = 0; config_paths[i] != NULL; i++) { + const char *path = config_paths[i]; + unsigned long long path_stamp = 0; + size_t pathlen; + struct kmod_list *tmp; + struct kmod_config_path *cf; + + if (conf_files_list(ctx, &list, path, &path_stamp) < 0) + continue; + + pathlen = strlen(path) + 1; + cf = malloc(sizeof(*cf) + pathlen); + if (cf == NULL) + goto oom; + + cf->stamp = path_stamp; + memcpy(cf->path, path, pathlen); + + tmp = kmod_list_append(path_list, cf); + if (tmp == NULL) { + free(cf); + goto oom; + } + path_list = tmp; + } + + *p_config = config = calloc(1, sizeof(struct kmod_config)); + if (config == NULL) + goto oom; + + config->paths = path_list; + config->ctx = ctx; + + for (; list != NULL; list = kmod_list_remove(list)) { + char buf[PATH_MAX]; + const char *fn = buf; + struct conf_file *cf = list->data; + int fd; + + if (cf->is_single) { + fn = cf->path; + } else if (snprintf(buf, sizeof(buf), "%s/%s", cf->path, cf->name) >= + (int)sizeof(buf)) { + ERR(ctx, "Error parsing %s/%s: path too long\n", cf->path, + cf->name); + free(cf); + continue; + } + + fd = open(fn, O_RDONLY | O_CLOEXEC); + DBG(ctx, "parsing file '%s' fd=%d\n", fn, fd); + + if (fd >= 0) + kmod_config_parse(config, fd, fn); + + free(cf); + } + + kmod_config_parse_kcmdline(config); + + return 0; + +oom: + kmod_list_release(list, free); + kmod_list_release(path_list, free); + + return -ENOMEM; +} + +/********************************************************************** + * struct kmod_config_iter functions + **********************************************************************/ + +enum config_type { + CONFIG_TYPE_BLACKLIST = 0, + CONFIG_TYPE_INSTALL, + CONFIG_TYPE_REMOVE, + CONFIG_TYPE_ALIAS, + CONFIG_TYPE_OPTION, + CONFIG_TYPE_SOFTDEP, + CONFIG_TYPE_WEAKDEP, +}; + +struct kmod_config_iter { + enum config_type type; + bool intermediate; + const struct kmod_list *list; + const struct kmod_list *curr; + void *data; + const char *(*get_key)(const struct kmod_list *l); + const char *(*get_value)(const struct kmod_list *l); +}; + +static const char *softdep_get_plain_softdep(const struct kmod_list *l) +{ + char *s = softdep_to_char(l->data); + return s; +} + +static const char *weakdep_get_plain_weakdep(const struct kmod_list *l) +{ + char *s = weakdep_to_char(l->data); + return s; +} + +static struct kmod_config_iter *kmod_config_iter_new(const struct kmod_ctx *ctx, + enum config_type type) +{ + struct kmod_config_iter *iter = calloc(1, sizeof(*iter)); + const struct kmod_config *config = kmod_get_config(ctx); + + if (iter == NULL) + return NULL; + + iter->type = type; + + switch (type) { + case CONFIG_TYPE_BLACKLIST: + iter->list = config->blacklists; + iter->get_key = kmod_blacklist_get_modname; + break; + case CONFIG_TYPE_INSTALL: + iter->list = config->install_commands; + iter->get_key = kmod_command_get_modname; + iter->get_value = kmod_command_get_command; + break; + case CONFIG_TYPE_REMOVE: + iter->list = config->remove_commands; + iter->get_key = kmod_command_get_modname; + iter->get_value = kmod_command_get_command; + break; + case CONFIG_TYPE_ALIAS: + iter->list = config->aliases; + iter->get_key = kmod_alias_get_name; + iter->get_value = kmod_alias_get_modname; + break; + case CONFIG_TYPE_OPTION: + iter->list = config->options; + iter->get_key = kmod_option_get_modname; + iter->get_value = kmod_option_get_options; + break; + case CONFIG_TYPE_SOFTDEP: + iter->list = config->softdeps; + iter->get_key = kmod_softdep_get_name; + iter->get_value = softdep_get_plain_softdep; + iter->intermediate = true; + break; + case CONFIG_TYPE_WEAKDEP: + iter->list = config->weakdeps; + iter->get_key = kmod_weakdep_get_name; + iter->get_value = weakdep_get_plain_weakdep; + iter->intermediate = true; + break; + } + + return iter; +} + +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_blacklists(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_BLACKLIST); +} + +// clang-format off +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_install_commands(const struct kmod_ctx *ctx) +// clang-format on +{ + if (ctx == NULL) + return NULL; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_INSTALL); +} + +// clang-format off +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_remove_commands(const struct kmod_ctx *ctx) +// clang-format on +{ + if (ctx == NULL) + return NULL; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_REMOVE); +} + +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_aliases(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_ALIAS); +} + +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_options(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_OPTION); +} + +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_softdeps(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_SOFTDEP); +} + +KMOD_EXPORT struct kmod_config_iter *kmod_config_get_weakdeps(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + return kmod_config_iter_new(ctx, CONFIG_TYPE_WEAKDEP); +} + +KMOD_EXPORT const char *kmod_config_iter_get_key(const struct kmod_config_iter *iter) +{ + if (iter == NULL || iter->curr == NULL) + return NULL; + + return iter->get_key(iter->curr); +} + +KMOD_EXPORT const char *kmod_config_iter_get_value(const struct kmod_config_iter *iter) +{ + const char *s; + + if (iter == NULL || iter->curr == NULL) + return NULL; + + if (iter->get_value == NULL) + return NULL; + + if (iter->intermediate) { + struct kmod_config_iter *i = (struct kmod_config_iter *)iter; + + free(i->data); + s = i->data = (void *)iter->get_value(iter->curr); + } else + s = iter->get_value(iter->curr); + + return s; +} + +KMOD_EXPORT bool kmod_config_iter_next(struct kmod_config_iter *iter) +{ + if (iter == NULL) + return false; + + if (iter->curr == NULL) { + iter->curr = iter->list; + return iter->curr != NULL; + } + + iter->curr = kmod_list_next(iter->list, iter->curr); + + return iter->curr != NULL; +} + +KMOD_EXPORT void kmod_config_iter_free_iter(struct kmod_config_iter *iter) +{ + if (iter) + free(iter->data); + free(iter); +} diff --git a/libkmod/libkmod-elf.c b/libkmod/libkmod-elf.c new file mode 100644 index 0000000..f215f1e --- /dev/null +++ b/libkmod/libkmod-elf.c @@ -0,0 +1,1264 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +/* as defined in module-init-tools */ +struct kmod_modversion32 { + uint32_t crc; + char name[64 - sizeof(uint32_t)]; +}; + +struct kmod_modversion64 { + uint64_t crc; + char name[64 - sizeof(uint64_t)]; +}; + +enum kmod_elf_section { + KMOD_ELF_SECTION_KSYMTAB, + KMOD_ELF_SECTION_MODINFO, + KMOD_ELF_SECTION_STRTAB, + KMOD_ELF_SECTION_SYMTAB, + KMOD_ELF_SECTION_VERSIONS, + KMOD_ELF_SECTION_MAX, +}; + +static const char *const section_name_map[] = { + [KMOD_ELF_SECTION_KSYMTAB] = "__ksymtab_strings", + [KMOD_ELF_SECTION_MODINFO] = ".modinfo", + [KMOD_ELF_SECTION_STRTAB] = ".strtab", + [KMOD_ELF_SECTION_SYMTAB] = ".symtab", + [KMOD_ELF_SECTION_VERSIONS] = "__versions", +}; + +struct kmod_elf { + const uint8_t *memory; + uint64_t size; + bool x32; + bool msb; + struct { + struct { + uint64_t offset; + uint16_t count; + uint16_t entry_size; + } section; + struct { + uint16_t section; /* index of the strings section */ + uint64_t size; + uint64_t offset; + } strings; + uint16_t machine; + } header; + struct { + uint64_t offset; + uint64_t size; + } sections[KMOD_ELF_SECTION_MAX]; +}; + +//#undef ENABLE_ELFDBG +//#define ENABLE_ELFDBG 1 + +#define ELFDBG(elf, ...) \ + do { \ + if (ENABLE_LOGGING == 1 && ENABLE_ELFDBG == 1) \ + _elf_dbg(elf, __FILE__, __LINE__, __func__, __VA_ARGS__); \ + } while (0); + +_printf_format_(5, 6) static inline void _elf_dbg(const struct kmod_elf *elf, + const char *fname, unsigned line, + const char *func, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "ELFDBG-%d%c: %s:%u %s() ", elf->x32 ? 32 : 64, + elf->msb ? 'M' : 'L', fname, line, func); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +static int elf_identify(struct kmod_elf *elf, const void *memory, uint64_t size) +{ + const uint8_t *p = memory; + + if (size <= EI_NIDENT || memcmp(p, ELFMAG, SELFMAG) != 0) + return -ENOEXEC; + + switch (p[EI_CLASS]) { + case ELFCLASS32: + if (size <= sizeof(Elf32_Ehdr)) + return -EINVAL; + elf->x32 = true; + break; + case ELFCLASS64: + if (size <= sizeof(Elf64_Ehdr)) + return -EINVAL; + elf->x32 = false; + break; + default: + return -EINVAL; + } + + switch (p[EI_DATA]) { + case ELFDATA2LSB: + elf->msb = false; + break; + case ELFDATA2MSB: + elf->msb = true; + break; + default: + return -EINVAL; + } + + return 0; +} + +static inline bool elf_range_valid(const struct kmod_elf *elf, uint64_t offset, + uint64_t size) +{ + uint64_t min_size; + + if (uadd64_overflow(offset, size, &min_size) || min_size > elf->size) { + ELFDBG(elf, + "out of bounds: %" PRIu64 " + %" PRIu64 " > %" PRIu64 + " (ELF size)\n", + offset, size, elf->size); + return false; + } + return true; +} + +static inline uint64_t elf_get_uint(const struct kmod_elf *elf, uint64_t offset, + uint16_t size) +{ + const uint8_t *p; + uint64_t ret = 0; + + assert(size <= sizeof(uint64_t)); + + p = elf->memory + offset; + + if (elf->msb) { + memcpy((char *)&ret + sizeof(ret) - size, p, size); + ret = be64toh(ret); + } else { + memcpy(&ret, p, size); + ret = le64toh(ret); + } + + ELFDBG(elf, "size=%" PRIu16 " offset=%" PRIu64 " value=%" PRIu64 "\n", size, + offset, ret); + + return ret; +} + +static inline int elf_set_uint(const struct kmod_elf *elf, uint64_t offset, uint64_t size, + uint64_t value, uint8_t *changed) +{ + uint8_t *p; + size_t i; + + ELFDBG(elf, + "size=%" PRIu64 " offset=%" PRIu64 " value=%" PRIu64 " write memory=%p\n", + size, offset, value, changed); + + assert(size <= sizeof(uint64_t)); + + p = changed + offset; + if (elf->msb) { + for (i = 1; i <= size; i++) { + p[size - i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } else { + for (i = 0; i < size; i++) { + p[i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } + + return 0; +} + +static inline const void *elf_get_mem(const struct kmod_elf *elf, uint64_t offset) +{ + return elf->memory + offset; +} + +/* + * Returns offset to section header for section with given index or 0 on error + * (offset 0 cannot be a valid section offset because ELF header is located there). + */ +static inline uint64_t elf_get_section_header_offset(const struct kmod_elf *elf, + uint16_t idx) +{ + assert(idx != SHN_UNDEF); + assert(idx < elf->header.section.count); + if (idx == SHN_UNDEF || idx >= elf->header.section.count) { + ELFDBG(elf, "invalid section number: %" PRIu16 ", last=%" PRIu16 "\n", + idx, elf->header.section.count); + return 0; + } + return elf->header.section.offset + + (uint64_t)(idx * elf->header.section.entry_size); +} + +static inline int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx, + uint64_t *offset, uint64_t *size, + const char **name) +{ + uint64_t nameoff; + uint64_t off = elf_get_section_header_offset(elf, idx); + + if (off == 0) { + ELFDBG(elf, "no section at %" PRIu16 "\n", idx); + goto fail; + } + +#define READV(field) \ + elf_get_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field)) + + if (elf->x32) { + Elf32_Shdr *hdr; + + if (!elf_range_valid(elf, off, sizeof(*hdr))) + goto fail; + *size = READV(sh_size); + *offset = READV(sh_offset); + nameoff = READV(sh_name); + } else { + Elf64_Shdr *hdr; + + if (!elf_range_valid(elf, off, sizeof(*hdr))) + goto fail; + *size = READV(sh_size); + *offset = READV(sh_offset); + nameoff = READV(sh_name); + } +#undef READV + + if (!elf_range_valid(elf, *offset, *size)) + goto fail; + + if (nameoff >= elf->header.strings.size) + goto fail; + *name = elf_get_mem(elf, elf->header.strings.offset + nameoff); + + ELFDBG(elf, + "section=%" PRIu16 " is: offset=%" PRIu64 " size=%" PRIu64 " name=%s\n", + idx, *offset, *size, *name); + + return 0; +fail: + *offset = 0; + *size = 0; + *name = NULL; + return -EINVAL; +} + +static void kmod_elf_save_sections(struct kmod_elf *elf) +{ + const uint16_t all_sec = (1 << KMOD_ELF_SECTION_MAX) - 1; + uint16_t found_sec = 0; + enum kmod_elf_section sec; + + for (uint16_t i = 1; i < elf->header.section.count && found_sec != all_sec; i++) { + uint64_t off, size; + const char *n; + int err = elf_get_section_info(elf, i, &off, &size, &n); + if (err < 0) + continue; + + for (sec = KMOD_ELF_SECTION_KSYMTAB; sec < KMOD_ELF_SECTION_MAX; sec++) { + if (found_sec & (1 << sec)) + continue; + + if (streq(section_name_map[sec], n)) { + elf->sections[sec].offset = off; + elf->sections[sec].size = size; + found_sec |= 1 << sec; + break; + } + } + } + + for (sec = KMOD_ELF_SECTION_KSYMTAB; sec < KMOD_ELF_SECTION_MAX; sec++) { + if (found_sec & (1 << sec)) + continue; + + ELFDBG(elf, "section %s not found\n", section_name_map[sec]); + elf->sections[sec].offset = 0; + elf->sections[sec].size = 0; + } +} + +struct kmod_elf *kmod_elf_new(const void *memory, off_t size) +{ + struct kmod_elf *elf; + size_t shdrs_size, shdr_size; + int err; + const char *name; + + assert_cc(sizeof(uint16_t) == sizeof(Elf32_Half)); + assert_cc(sizeof(uint16_t) == sizeof(Elf64_Half)); + assert_cc(sizeof(uint32_t) == sizeof(Elf32_Word)); + assert_cc(sizeof(uint32_t) == sizeof(Elf64_Word)); + + if (!memory) { + errno = -EINVAL; + return NULL; + } + + elf = malloc(sizeof(struct kmod_elf)); + if (elf == NULL) { + return NULL; + } + + err = elf_identify(elf, memory, size); + if (err < 0) { + free(elf); + errno = -err; + return NULL; + } + + elf->memory = memory; + elf->size = size; + +#define READV(field) elf_get_uint(elf, offsetof(typeof(*hdr), field), sizeof(hdr->field)) + +#define LOAD_HEADER \ + elf->header.section.offset = READV(e_shoff); \ + elf->header.section.count = READV(e_shnum); \ + elf->header.section.entry_size = READV(e_shentsize); \ + elf->header.strings.section = READV(e_shstrndx); \ + elf->header.machine = READV(e_machine) + if (elf->x32) { + Elf32_Ehdr *hdr; + + shdr_size = sizeof(Elf32_Shdr); + if (!elf_range_valid(elf, 0, sizeof(*hdr))) + goto invalid; + LOAD_HEADER; + } else { + Elf64_Ehdr *hdr; + + shdr_size = sizeof(Elf64_Shdr); + if (!elf_range_valid(elf, 0, sizeof(*hdr))) + goto invalid; + LOAD_HEADER; + } +#undef LOAD_HEADER +#undef READV + + ELFDBG(elf, + "section: offset=%" PRIu64 " count=%" PRIu16 " entry_size=%" PRIu16 + " strings index=%" PRIu16 "\n", + elf->header.section.offset, elf->header.section.count, + elf->header.section.entry_size, elf->header.strings.section); + + if (elf->header.section.entry_size != shdr_size) { + ELFDBG(elf, "unexpected section entry size: %" PRIu16 ", expected %zu\n", + elf->header.section.entry_size, shdr_size); + goto invalid; + } + shdrs_size = shdr_size * elf->header.section.count; + if (!elf_range_valid(elf, elf->header.section.offset, shdrs_size)) + goto invalid; + + if (elf_get_section_info(elf, elf->header.strings.section, + &elf->header.strings.offset, &elf->header.strings.size, + &name) < 0) { + ELFDBG(elf, "could not get strings section\n"); + goto invalid; + } else { + uint64_t slen = elf->header.strings.size; + const char *s = elf_get_mem(elf, elf->header.strings.offset); + if (slen == 0 || s[slen - 1] != '\0') { + ELFDBG(elf, "strings section does not end with \\0\n"); + goto invalid; + } + } + + kmod_elf_save_sections(elf); + return elf; + +invalid: + free(elf); + errno = EINVAL; + return NULL; +} + +void kmod_elf_unref(struct kmod_elf *elf) +{ + free(elf); +} + +const void *kmod_elf_get_memory(const struct kmod_elf *elf) +{ + return elf->memory; +} + +/* + * Returns section index on success, negative value otherwise. + * On success, sec_off and sec_size are range checked and valid. + */ +int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, + uint64_t *sec_off, uint64_t *sec_size) +{ + uint16_t i; + + *sec_off = 0; + *sec_size = 0; + + for (i = 1; i < elf->header.section.count; i++) { + uint64_t off, size; + const char *n; + int err = elf_get_section_info(elf, i, &off, &size, &n); + if (err < 0) + continue; + if (!streq(section, n)) + continue; + + *sec_off = off; + *sec_size = size; + return i; + } + + return -ENODATA; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_modinfo_strings(const struct kmod_elf *elf, char ***array) +{ + size_t i, j, count; + size_t tmp_size, vec_size, total_size; + uint64_t off, size; + const char *strings; + char *s, **a; + + *array = NULL; + + off = elf->sections[KMOD_ELF_SECTION_MODINFO].offset; + size = elf->sections[KMOD_ELF_SECTION_MODINFO].size; + if (off == 0) + return -ENODATA; + + strings = elf_get_mem(elf, off); + + /* skip zero padding */ + while (size > 1 && strings[0] == '\0') { + strings++; + size--; + } + + if (size <= 1) + return 0; + + for (i = 0, count = 0; i < size;) { + if (strings[i] != '\0') { + i++; + continue; + } + + while (strings[i] == '\0' && i < size) + i++; + + count++; + } + + if (strings[i - 1] != '\0') + count++; + + /* (string vector + NULL) * sizeof(char *) + size + NUL */ + if (uaddsz_overflow(count, 1, &tmp_size) || + umulsz_overflow(sizeof(char *), tmp_size, &vec_size) || + uaddsz_overflow(size, vec_size, &tmp_size) || + uaddsz_overflow(1, tmp_size, &total_size)) { + return -ENOMEM; + } + + *array = a = malloc(total_size); + if (*array == NULL) + return -errno; + + s = (char *)(a + count + 1); + memcpy(s, strings, size); + + /* make sure the last string is NULL-terminated */ + s[size] = '\0'; + a[count] = NULL; + a[0] = s; + + for (i = 0, j = 1; j < count && i < size;) { + if (s[i] != '\0') { + i++; + continue; + } + + while (i < size && s[i] == '\0') + i++; + + a[j] = &s[i]; + j++; + } + + return count; +} + +static inline void elf_get_modversion_lengths(const struct kmod_elf *elf, size_t *verlen, + size_t *crclen, size_t *namlen) +{ + assert_cc(sizeof(struct kmod_modversion64) == sizeof(struct kmod_modversion32)); + + if (elf->x32) { + struct kmod_modversion32 *mv; + + *verlen = sizeof(*mv); + *crclen = sizeof(mv->crc); + *namlen = sizeof(mv->name); + } else { + struct kmod_modversion64 *mv; + + *verlen = sizeof(*mv); + *crclen = sizeof(mv->crc); + *namlen = sizeof(mv->name); + } +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + size_t i, count, crclen, namlen, verlen; + uint64_t off, sec_off, size; + struct kmod_modversion *a; + + elf_get_modversion_lengths(elf, &verlen, &crclen, &namlen); + + *array = NULL; + + sec_off = elf->sections[KMOD_ELF_SECTION_VERSIONS].offset; + size = elf->sections[KMOD_ELF_SECTION_VERSIONS].size; + if (sec_off == 0) + return -ENODATA; + + if (size == 0) + return 0; + + if (size % verlen != 0) + return -EINVAL; + + count = size / verlen; + if (count > INT_MAX) { + ELFDBG(elf, "too many modversions: %zu\n", count); + return -EINVAL; + } + + *array = a = malloc(sizeof(struct kmod_modversion) * count); + if (*array == NULL) + return -errno; + + for (i = 0, off = sec_off; i < count; i++, off += verlen) { + uint64_t crc = elf_get_uint(elf, off, crclen); + const char *symbol = elf_get_mem(elf, off + crclen); + size_t nlen = strnlen(symbol, namlen); + + if (nlen == namlen) { + ELFDBG(elf, "symbol name at index %zu too long\n", i); + return -EINVAL; + } + + if (symbol[0] == '.') + symbol++; + + a[i].crc = crc; + a[i].bind = KMOD_SYMBOL_UNDEF; + a[i].symbol = symbol; + } + + return count; +} + +static int elf_strip_versions_section(const struct kmod_elf *elf, uint8_t *changed) +{ + uint64_t off, size; + const void *buf; + /* the off and size values are not used, supply them as dummies */ + int idx = kmod_elf_get_section(elf, "__versions", &off, &size); + uint64_t val; + + if (idx < 0) + return idx == -ENODATA ? 0 : idx; + + off = elf_get_section_header_offset(elf, idx); + + if (elf->x32) { + off += offsetof(Elf32_Shdr, sh_flags); + size = sizeof(((Elf32_Shdr *)buf)->sh_flags); + } else { + off += offsetof(Elf64_Shdr, sh_flags); + size = sizeof(((Elf64_Shdr *)buf)->sh_flags); + } + + val = elf_get_uint(elf, off, size); + val &= ~(uint64_t)SHF_ALLOC; + + return elf_set_uint(elf, off, size, val, changed); +} + +static int elf_strip_vermagic(const struct kmod_elf *elf, uint8_t *changed) +{ + uint64_t i, sec_off, size; + const char *strings; + + sec_off = elf->sections[KMOD_ELF_SECTION_MODINFO].offset; + size = elf->sections[KMOD_ELF_SECTION_MODINFO].size; + if (sec_off == 0) + return 0; + strings = elf_get_mem(elf, sec_off); + + /* skip zero padding */ + while (size > 1 && strings[0] == '\0') { + strings++; + size--; + } + if (size <= 1) + return 0; + + for (i = 0; i < size; i++) { + const char *s; + size_t off, len; + + if (strings[i] == '\0') + continue; + if (i + 1 >= size) + continue; + + s = strings + i; + len = sizeof("vermagic=") - 1; + if (i + len >= size) + continue; + if (strncmp(s, "vermagic=", len) != 0) { + i += strlen(s); + continue; + } + off = (const uint8_t *)s - elf->memory; + + len = strlen(s); + ELFDBG(elf, "clear .modinfo vermagic \"%s\" (%zu bytes)\n", s, len); + memset(changed + off, '\0', len); + return 0; + } + + ELFDBG(elf, "no vermagic found in .modinfo\n"); + return -ENODATA; +} + +const void *kmod_elf_strip(const struct kmod_elf *elf, unsigned int flags) +{ + uint8_t *changed; + int err = 0; + + assert(flags & (KMOD_INSERT_FORCE_MODVERSION | KMOD_INSERT_FORCE_VERMAGIC)); + + changed = memdup(elf->memory, elf->size); + if (changed == NULL) + return NULL; + + ELFDBG(elf, "copied memory to allow writing.\n"); + + if (flags & KMOD_INSERT_FORCE_MODVERSION) { + err = elf_strip_versions_section(elf, changed); + if (err < 0) { + errno = -err; + goto fail; + } + } + + if (flags & KMOD_INSERT_FORCE_VERMAGIC) { + err = elf_strip_vermagic(elf, changed); + if (err < 0) { + errno = -err; + goto fail; + } + } + + return changed; +fail: + free(changed); + return NULL; +} + +static int kmod_elf_get_symbols_symtab(const struct kmod_elf *elf, + struct kmod_modversion **array) +{ + uint64_t i, last, off, size; + const char *strings; + struct kmod_modversion *a; + size_t count, total_size; + + *array = NULL; + + off = elf->sections[KMOD_ELF_SECTION_KSYMTAB].offset; + size = elf->sections[KMOD_ELF_SECTION_KSYMTAB].size; + if (off == 0) + return -ENODATA; + strings = elf_get_mem(elf, off); + + /* skip zero padding */ + while (size > 1 && strings[0] == '\0') { + strings++; + size--; + } + if (size <= 1) + return 0; + + if (strings[size - 1] != '\0') { + ELFDBG(elf, "section __ksymtab_strings does not end with \\0 byte"); + return -EINVAL; + } + + last = 0; + for (i = 0, count = 0; i < size; i++) { + if (strings[i] == '\0') { + if (last == i) { + last = i + 1; + continue; + } + count++; + last = i + 1; + } + } + + if (count > INT_MAX) { + ELFDBG(elf, "too many symbols: %zu\n", count); + return -EINVAL; + } + + /* sizeof(struct kmod_modversion) * count */ + if (umulsz_overflow(sizeof(struct kmod_modversion), count, &total_size)) { + return -ENOMEM; + } + + *array = a = malloc(total_size); + if (*array == NULL) + return -errno; + + last = 0; + for (i = 0, count = 0; i < size; i++) { + if (strings[i] == '\0') { + if (last == i) { + last = i + 1; + continue; + } + a[count].crc = 0; + a[count].bind = KMOD_SYMBOL_GLOBAL; + a[count].symbol = strings + last; + count++; + last = i + 1; + } + } + + return count; +} + +static inline uint8_t kmod_symbol_bind_from_elf(uint8_t elf_value) +{ + switch (elf_value) { + case STB_LOCAL: + return KMOD_SYMBOL_LOCAL; + case STB_GLOBAL: + return KMOD_SYMBOL_GLOBAL; + case STB_WEAK: + return KMOD_SYMBOL_WEAK; + default: + return KMOD_SYMBOL_NONE; + } +} + +static uint64_t kmod_elf_resolve_crc(const struct kmod_elf *elf, uint64_t crc, + uint16_t shndx) +{ + int err; + uint64_t off, size; + const char *name; + + if (shndx == SHN_ABS || shndx == SHN_UNDEF) + return crc; + + err = elf_get_section_info(elf, shndx, &off, &size, &name); + if (err < 0) { + ELFDBG(elf, "Could not find section index %" PRIu16 " for crc", shndx); + return (uint64_t)-1; + } + + if (size < sizeof(uint32_t) || crc > (size - sizeof(uint32_t))) { + ELFDBG(elf, + "CRC offset %" PRIu64 " is too big, section %" PRIu16 + " size is %" PRIu64 "\n", + crc, shndx, size); + return (uint64_t)-1; + } + + crc = elf_get_uint(elf, off + crc, sizeof(uint32_t)); + return crc; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + static const char crc_str[] = "__crc_"; + static const size_t crc_strlen = sizeof(crc_str) - 1; + uint64_t strtablen, symtablen, str_sec_off, sym_sec_off, str_off, sym_off; + struct kmod_modversion *a; + size_t i, count, symcount, symlen; + + str_sec_off = elf->sections[KMOD_ELF_SECTION_STRTAB].offset; + strtablen = elf->sections[KMOD_ELF_SECTION_STRTAB].size; + if (str_sec_off == 0) { + ELFDBG(elf, "no .strtab found.\n"); + goto fallback; + } + + sym_sec_off = elf->sections[KMOD_ELF_SECTION_SYMTAB].offset; + symtablen = elf->sections[KMOD_ELF_SECTION_SYMTAB].size; + if (sym_sec_off == 0) { + ELFDBG(elf, "no .symtab found.\n"); + goto fallback; + } + + if (elf->x32) + symlen = sizeof(Elf32_Sym); + else + symlen = sizeof(Elf64_Sym); + + if (symtablen % symlen != 0) { + ELFDBG(elf, + "unexpected .symtab of length %" PRIu64 + ", not multiple of %zu as expected.\n", + symtablen, symlen); + goto fallback; + } + + symcount = symtablen / symlen; + count = 0; + str_off = str_sec_off; + sym_off = sym_sec_off + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint32_t name_off; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field), sizeof(s->field)) + if (elf->x32) { + Elf32_Sym *s; + + name_off = READV(st_name); + } else { + Elf64_Sym *s; + + name_off = READV(st_name); + } +#undef READV + if (name_off >= strtablen) { + ELFDBG(elf, + ".strtab is %" PRIu64 + " bytes, but .symtab entry %zu wants to access offset %" PRIu32 + ".\n", + strtablen, i, name_off); + goto fallback; + } + + name = elf_get_mem(elf, str_off + name_off); + + if (strncmp(name, crc_str, crc_strlen) != 0) + continue; + count++; + } + + if (count == 0) + goto fallback; + + *array = a = malloc(sizeof(struct kmod_modversion) * count); + if (*array == NULL) + return -errno; + + count = 0; + str_off = str_sec_off; + sym_off = sym_sec_off + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint32_t name_off; + uint64_t crc; + uint8_t info, bind; + uint16_t shndx; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field), sizeof(s->field)) + if (elf->x32) { + Elf32_Sym *s; + + name_off = READV(st_name); + crc = READV(st_value); + info = READV(st_info); + shndx = READV(st_shndx); + } else { + Elf64_Sym *s; + + name_off = READV(st_name); + crc = READV(st_value); + info = READV(st_info); + shndx = READV(st_shndx); + } +#undef READV + name = elf_get_mem(elf, str_off + name_off); + if (strncmp(name, crc_str, crc_strlen) != 0) + continue; + name += crc_strlen; + + if (elf->x32) + bind = ELF32_ST_BIND(info); + else + bind = ELF64_ST_BIND(info); + + a[count].crc = kmod_elf_resolve_crc(elf, crc, shndx); + a[count].bind = kmod_symbol_bind_from_elf(bind); + a[count].symbol = name; + count++; + } + return count; + +fallback: + ELFDBG(elf, "Falling back to __ksymtab_strings!\n"); + return kmod_elf_get_symbols_symtab(elf, array); +} + +static int kmod_elf_crc_find(const struct kmod_elf *elf, uint64_t off, + uint64_t versionslen, const char *name, uint64_t *crc) +{ + size_t namlen, verlen, crclen; + uint64_t i; + + elf_get_modversion_lengths(elf, &verlen, &crclen, &namlen); + + for (i = 0; i < versionslen; i += verlen) { + const char *symbol = elf_get_mem(elf, off + i + crclen); + if (strnlen(symbol, namlen) == namlen || !streq(name, symbol)) { + ELFDBG(elf, "symbol name at index %" PRIu64 " too long\n", i); + continue; + } + *crc = elf_get_uint(elf, off + i, crclen); + return i / verlen; + } + + ELFDBG(elf, "could not find crc for symbol '%s'\n", name); + *crc = 0; + return -1; +} + +/* from module-init-tools:elfops_core.c */ +#ifndef STT_REGISTER +#define STT_REGISTER 13 /* Global register reserved to app. */ +#endif + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_dependency_symbols(const struct kmod_elf *elf, + struct kmod_modversion **array) +{ + uint64_t versionslen, strtablen, symtablen, str_off, sym_off, ver_off; + uint64_t str_sec_off, sym_sec_off; + struct kmod_modversion *a; + size_t i, count, namlen, vercount, verlen, symcount, symlen, crclen; + bool handle_register_symbols; + uint8_t *visited_versions; + uint64_t *symcrcs; + + ver_off = elf->sections[KMOD_ELF_SECTION_VERSIONS].offset; + versionslen = elf->sections[KMOD_ELF_SECTION_VERSIONS].size; + if (ver_off == 0) { + versionslen = 0; + verlen = 0; + crclen = 0; + namlen = 0; + } else { + elf_get_modversion_lengths(elf, &verlen, &crclen, &namlen); + if (versionslen % verlen != 0) { + ELFDBG(elf, + "unexpected __versions of length %" PRIu64 + ", not multiple of %zu as expected.\n", + versionslen, verlen); + ver_off = 0; + versionslen = 0; + } + } + + str_sec_off = elf->sections[KMOD_ELF_SECTION_STRTAB].offset; + strtablen = elf->sections[KMOD_ELF_SECTION_STRTAB].size; + if (str_sec_off == 0) { + ELFDBG(elf, "no .strtab found.\n"); + return -EINVAL; + } + + sym_sec_off = elf->sections[KMOD_ELF_SECTION_SYMTAB].offset; + symtablen = elf->sections[KMOD_ELF_SECTION_SYMTAB].size; + if (sym_sec_off == 0) { + ELFDBG(elf, "no .symtab found.\n"); + return -EINVAL; + } + + if (elf->x32) + symlen = sizeof(Elf32_Sym); + else + symlen = sizeof(Elf64_Sym); + + if (symtablen % symlen != 0) { + ELFDBG(elf, + "unexpected .symtab of length %" PRIu64 + ", not multiple of %zu as expected.\n", + symtablen, symlen); + return -EINVAL; + } + + if (versionslen == 0) { + vercount = 0; + visited_versions = NULL; + } else { + vercount = versionslen / verlen; + visited_versions = calloc(vercount, sizeof(uint8_t)); + if (visited_versions == NULL) + return -ENOMEM; + } + + handle_register_symbols = + (elf->header.machine == EM_SPARC || elf->header.machine == EM_SPARCV9); + + symcount = symtablen / symlen; + count = 0; + str_off = str_sec_off; + sym_off = sym_sec_off + symlen; + + symcrcs = calloc(symcount, sizeof(uint64_t)); + if (symcrcs == NULL) { + free(visited_versions); + return -ENOMEM; + } + + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint64_t crc; + uint32_t name_off; + uint16_t secidx; + uint8_t info; + int idx; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field), sizeof(s->field)) + if (elf->x32) { + Elf32_Sym *s; + + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } else { + Elf64_Sym *s; + + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } +#undef READV + if (secidx != SHN_UNDEF) + continue; + + if (handle_register_symbols) { + uint8_t type; + if (elf->x32) + type = ELF32_ST_TYPE(info); + else + type = ELF64_ST_TYPE(info); + + /* Not really undefined: sparc gcc 3.3 creates + * U references when you have global asm + * variables, to avoid anyone else misusing + * them. + */ + if (type == STT_REGISTER) + continue; + } + + if (name_off >= strtablen) { + ELFDBG(elf, + ".strtab is %" PRIu64 + " bytes, but .symtab entry %zu wants to access offset %" PRIu32 + ".\n", + strtablen, i, name_off); + free(visited_versions); + free(symcrcs); + return -EINVAL; + } + + name = elf_get_mem(elf, str_off + name_off); + if (name[0] == '\0') { + ELFDBG(elf, "empty symbol name at index %zu\n", i); + continue; + } + + count++; + + idx = kmod_elf_crc_find(elf, ver_off, versionslen, name, &crc); + if (idx >= 0 && visited_versions != NULL) + visited_versions[idx] = 1; + symcrcs[i] = crc; + } + + if (visited_versions != NULL) { + /* module_layout/struct_module are not visited, but needed */ + for (i = 0; i < vercount; i++) { + if (visited_versions[i] == 0) { + const char *name; + size_t nlen; + + name = elf_get_mem(elf, ver_off + i * verlen + crclen); + nlen = strnlen(name, namlen); + + if (nlen == namlen) { + ELFDBG(elf, "symbol name at index %zu too long\n", + i); + free(visited_versions); + free(symcrcs); + return -EINVAL; + } + + count++; + } + } + } + + if (count > INT_MAX) { + ELFDBG(elf, "too many symbols: %zu\n", count); + free(visited_versions); + free(symcrcs); + *array = NULL; + return -EINVAL; + } + + if (count == 0) { + free(visited_versions); + free(symcrcs); + *array = NULL; + return 0; + } + + *array = a = malloc(sizeof(struct kmod_modversion) * count); + if (*array == NULL) { + free(visited_versions); + free(symcrcs); + return -errno; + } + + count = 0; + str_off = str_sec_off; + sym_off = sym_sec_off + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint64_t crc; + uint32_t name_off; + uint16_t secidx; + uint8_t info, bind; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field), sizeof(s->field)) + if (elf->x32) { + Elf32_Sym *s; + + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } else { + Elf64_Sym *s; + + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } +#undef READV + if (secidx != SHN_UNDEF) + continue; + + if (handle_register_symbols) { + uint8_t type; + if (elf->x32) + type = ELF32_ST_TYPE(info); + else + type = ELF64_ST_TYPE(info); + + /* Not really undefined: sparc gcc 3.3 creates + * U references when you have global asm + * variables, to avoid anyone else misusing + * them. + */ + if (type == STT_REGISTER) + continue; + } + + name = elf_get_mem(elf, str_off + name_off); + if (name[0] == '\0') { + ELFDBG(elf, "empty symbol name at index %zu\n", i); + continue; + } + + if (elf->x32) + bind = ELF32_ST_BIND(info); + else + bind = ELF64_ST_BIND(info); + if (bind == STB_WEAK) + bind = KMOD_SYMBOL_WEAK; + else + bind = KMOD_SYMBOL_UNDEF; + + crc = symcrcs[i]; + + a[count].crc = crc; + a[count].bind = bind; + a[count].symbol = name; + + count++; + } + + free(symcrcs); + + if (visited_versions == NULL) + return count; + + /* add unvisited (module_layout/struct_module) */ + for (i = 0; i < vercount; i++) { + const char *name; + uint64_t crc; + + if (visited_versions[i] != 0) + continue; + + name = elf_get_mem(elf, ver_off + i * verlen + crclen); + crc = elf_get_uint(elf, ver_off + i * verlen, crclen); + + a[count].crc = crc; + a[count].bind = KMOD_SYMBOL_UNDEF; + a[count].symbol = name; + + count++; + } + free(visited_versions); + return count; +} diff --git a/libkmod/libkmod-file-xz.c b/libkmod/libkmod-file-xz.c new file mode 100644 index 0000000..f693877 --- /dev/null +++ b/libkmod/libkmod-file-xz.c @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright © 2024 Intel Corporation + */ + +#define DLSYM_LOCALLY_ENABLED ENABLE_XZ_DLOPEN + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" +#include "libkmod-internal-file.h" + +#define DL_SYMBOL_TABLE(M) \ + M(lzma_stream_decoder) \ + M(lzma_code) \ + M(lzma_end) + +DL_SYMBOL_TABLE(DECLARE_SYM) + +static int dlopen_lzma(void) +{ +#if !DLSYM_LOCALLY_ENABLED + return 0; +#else + static void *dl = NULL; + + ELF_NOTE_DLOPEN("xz", "Support for uncompressing xz-compressed modules", + ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "liblzma.so.5"); + + return dlsym_many(&dl, "liblzma.so.5", DL_SYMBOL_TABLE(DLSYM_ARG) NULL); +#endif +} + +static void xz_uncompress_belch(struct kmod_file *file, lzma_ret ret) +{ + switch (ret) { + case LZMA_MEM_ERROR: + ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); + break; + case LZMA_FORMAT_ERROR: + ERR(file->ctx, "xz: File format not recognized\n"); + break; + case LZMA_OPTIONS_ERROR: + ERR(file->ctx, "xz: Unsupported compression options\n"); + break; + case LZMA_DATA_ERROR: + ERR(file->ctx, "xz: File is corrupt\n"); + break; + case LZMA_BUF_ERROR: + ERR(file->ctx, "xz: Unexpected end of input\n"); + break; + default: + ERR(file->ctx, "xz: Internal error (bug)\n"); + break; + } +} + +static int xz_uncompress(lzma_stream *strm, struct kmod_file *file) +{ + uint8_t in_buf[BUFSIZ], out_buf[BUFSIZ]; + lzma_action action = LZMA_RUN; + lzma_ret ret; + void *p = NULL; + size_t total = 0; + + strm->avail_in = 0; + strm->next_out = out_buf; + strm->avail_out = sizeof(out_buf); + + while (true) { + if (strm->avail_in == 0) { + ssize_t rdret = read(file->fd, in_buf, sizeof(in_buf)); + if (rdret < 0) { + ret = -errno; + goto out; + } + strm->next_in = in_buf; + strm->avail_in = rdret; + if (rdret == 0) + action = LZMA_FINISH; + } + ret = sym_lzma_code(strm, action); + if (strm->avail_out == 0 || ret != LZMA_OK) { + size_t write_size = BUFSIZ - strm->avail_out; + char *tmp = realloc(p, total + write_size); + if (tmp == NULL) { + ret = -errno; + goto out; + } + memcpy(tmp + total, out_buf, write_size); + total += write_size; + p = tmp; + strm->next_out = out_buf; + strm->avail_out = BUFSIZ; + } + if (ret == LZMA_STREAM_END) + break; + if (ret != LZMA_OK) { + xz_uncompress_belch(file, ret); + ret = -EINVAL; + goto out; + } + } + file->memory = p; + file->size = total; + return 0; +out: + free(p); + return ret; +} + +int kmod_file_load_xz(struct kmod_file *file) +{ + lzma_stream strm = LZMA_STREAM_INIT; + lzma_ret lzret; + int ret; + + ret = dlopen_lzma(); + if (ret < 0) { + ERR(file->ctx, "xz: can't load and resolve symbols (%s)", strerror(-ret)); + return -EINVAL; + } + + lzret = sym_lzma_stream_decoder(&strm, UINT64_MAX, LZMA_CONCATENATED); + if (lzret == LZMA_MEM_ERROR) { + ERR(file->ctx, "xz: %s\n", strerror(ENOMEM)); + return -ENOMEM; + } else if (lzret != LZMA_OK) { + ERR(file->ctx, "xz: Internal error (bug)\n"); + return -EINVAL; + } + ret = xz_uncompress(&strm, file); + sym_lzma_end(&strm); + return ret; +} diff --git a/libkmod/libkmod-file-zlib.c b/libkmod/libkmod-file-zlib.c new file mode 100644 index 0000000..b26c7d1 --- /dev/null +++ b/libkmod/libkmod-file-zlib.c @@ -0,0 +1,112 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright © 2024 Intel Corporation + */ + +#define DLSYM_LOCALLY_ENABLED ENABLE_ZLIB_DLOPEN + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" +#include "libkmod-internal-file.h" + +#define READ_STEP (4 * 1024 * 1024) + +#define DL_SYMBOL_TABLE(M) \ + M(gzclose) \ + M(gzdopen) \ + M(gzerror) \ + M(gzread) + +DL_SYMBOL_TABLE(DECLARE_SYM) + +static int dlopen_zlib(void) +{ +#if !DLSYM_LOCALLY_ENABLED + return 0; +#else + static void *dl = NULL; + + ELF_NOTE_DLOPEN("zlib", "Support for uncompressing zlib-compressed modules", + ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libz.so.1"); + + return dlsym_many(&dl, "libz.so.1", DL_SYMBOL_TABLE(DLSYM_ARG) NULL); +#endif +} + +int kmod_file_load_zlib(struct kmod_file *file) +{ + _cleanup_free_ unsigned char *p = NULL; + int ret = 0; + off_t did = 0, total = 0; + gzFile gzf; + int gzfd; + + ret = dlopen_zlib(); + if (ret < 0) { + ERR(file->ctx, "zlib: can't load and resolve symbols (%s)", + strerror(-ret)); + return -EINVAL; + } + + errno = 0; + gzfd = fcntl(file->fd, F_DUPFD_CLOEXEC, 3); + if (gzfd < 0) + return -errno; + + gzf = sym_gzdopen(gzfd, "rb"); /* takes ownership of the fd */ + if (gzf == NULL) { + close(gzfd); + return -errno; + } + + for (;;) { + int r; + + if (did == total) { + void *tmp = realloc(p, total + READ_STEP); + if (tmp == NULL) { + ret = -errno; + goto error; + } + total += READ_STEP; + p = tmp; + } + + r = sym_gzread(gzf, p + did, total - did); + if (r == 0) + break; + else if (r < 0) { + int gzerr; + const char *gz_errmsg = sym_gzerror(gzf, &gzerr); + + ERR(file->ctx, "gzip: %s\n", gz_errmsg); + + /* gzip might not set errno here */ + ret = gzerr == Z_ERRNO ? -errno : -EINVAL; + goto error; + } + did += r; + } + + file->memory = TAKE_PTR(p); + file->size = did; + sym_gzclose(gzf); + + return 0; + +error: + sym_gzclose(gzf); + return ret; +} diff --git a/libkmod/libkmod-file-zstd.c b/libkmod/libkmod-file-zstd.c new file mode 100644 index 0000000..f403a01 --- /dev/null +++ b/libkmod/libkmod-file-zstd.c @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright © 2024 Intel Corporation + */ + +#define DLSYM_LOCALLY_ENABLED ENABLE_ZSTD_DLOPEN + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" +#include "libkmod-internal-file.h" + +#define DL_SYMBOL_TABLE(M) \ + M(ZSTD_decompress) \ + M(ZSTD_getErrorName) \ + M(ZSTD_getFrameContentSize) \ + M(ZSTD_isError) + +DL_SYMBOL_TABLE(DECLARE_SYM) + +static int dlopen_zstd(void) +{ +#if !DLSYM_LOCALLY_ENABLED + return 0; +#else + static void *dl = NULL; + + ELF_NOTE_DLOPEN("zstd", "Support for uncompressing zstd-compressed modules", + ELF_NOTE_DLOPEN_PRIORITY_RECOMMENDED, "libzstd.so.1"); + + return dlsym_many(&dl, "libzstd.so.1", DL_SYMBOL_TABLE(DLSYM_ARG) NULL); +#endif +} + +int kmod_file_load_zstd(struct kmod_file *file) +{ + void *src_buf = MAP_FAILED, *dst_buf = NULL; + size_t src_size, dst_size; + unsigned long long frame_size; + struct stat st; + int ret; + + ret = dlopen_zstd(); + if (ret < 0) { + ERR(file->ctx, "zstd: can't load and resolve symbols (%s)", + strerror(-ret)); + return -EINVAL; + } + + if (fstat(file->fd, &st) < 0) { + ret = -errno; + ERR(file->ctx, "zstd: %m\n"); + goto out; + } + + if ((uintmax_t)st.st_size > SIZE_MAX) { + ret = -ENOMEM; + goto out; + } + + src_size = st.st_size; + src_buf = mmap(NULL, src_size, PROT_READ, MAP_PRIVATE, file->fd, 0); + if (src_buf == MAP_FAILED) { + ret = -errno; + goto out; + } + + frame_size = sym_ZSTD_getFrameContentSize(src_buf, src_size); + if (frame_size == 0 || frame_size == ZSTD_CONTENTSIZE_UNKNOWN || + frame_size == ZSTD_CONTENTSIZE_ERROR) { + ret = -EINVAL; + ERR(file->ctx, "zstd: Failed to determine decompression size\n"); + goto out; + } + + if (frame_size > SIZE_MAX) { + ret = -ENOMEM; + goto out; + } + + dst_size = frame_size; + dst_buf = malloc(dst_size); + if (dst_buf == NULL) { + ret = -errno; + goto out; + } + + dst_size = sym_ZSTD_decompress(dst_buf, dst_size, src_buf, src_size); + if (sym_ZSTD_isError(dst_size)) { + ERR(file->ctx, "zstd: %s\n", sym_ZSTD_getErrorName(dst_size)); + ret = -EINVAL; + goto out; + } + + file->memory = dst_buf; + file->size = dst_size; + + ret = 0; + dst_buf = NULL; + +out: + free(dst_buf); + + if (src_buf != MAP_FAILED) + munmap(src_buf, src_size); + + return ret; +} diff --git a/libkmod/libkmod-file.c b/libkmod/libkmod-file.c new file mode 100644 index 0000000..89f7464 --- /dev/null +++ b/libkmod/libkmod-file.c @@ -0,0 +1,172 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" +#include "libkmod-internal-file.h" + +static const char magic_zstd[] = { 0x28, 0xB5, 0x2F, 0xFD }; +static const char magic_xz[] = { 0xfd, '7', 'z', 'X', 'Z', 0 }; +static const char magic_zlib[] = { 0x1f, 0x8b }; + +static int load_reg(struct kmod_file *file) +{ + struct stat st; + + if (fstat(file->fd, &st) < 0) + return -errno; + + file->size = st.st_size; + if ((uintmax_t)file->size > SIZE_MAX) + return -ENOMEM; + + file->memory = mmap(NULL, file->size, PROT_READ, MAP_PRIVATE, file->fd, 0); + if (file->memory == MAP_FAILED) { + file->memory = NULL; + return -errno; + } + + return 0; +} + +static const struct comp_type { + size_t magic_size; + enum kmod_file_compression_type compression; + const char *magic_bytes; + int (*load)(struct kmod_file *file); +} comp_types[] = { + // clang-format off + { sizeof(magic_zstd), KMOD_FILE_COMPRESSION_ZSTD, magic_zstd, kmod_file_load_zstd }, + { sizeof(magic_xz), KMOD_FILE_COMPRESSION_XZ, magic_xz, kmod_file_load_xz }, + { sizeof(magic_zlib), KMOD_FILE_COMPRESSION_ZLIB, magic_zlib, kmod_file_load_zlib }, + { 0, KMOD_FILE_COMPRESSION_NONE, NULL, load_reg }, + // clang-format on +}; + +struct kmod_elf *kmod_file_get_elf(struct kmod_file *file) +{ + int err; + + if (file->elf) + return file->elf; + + err = kmod_file_load_contents(file); + if (err) { + errno = -err; + return NULL; + } + + file->elf = kmod_elf_new(file->memory, file->size); + return file->elf; +} + +struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx, const char *filename) +{ + struct kmod_file *file; + char buf[7]; + ssize_t sz; + + assert_cc(sizeof(magic_zstd) < sizeof(buf)); + assert_cc(sizeof(magic_xz) < sizeof(buf)); + assert_cc(sizeof(magic_zlib) < sizeof(buf)); + + file = calloc(1, sizeof(struct kmod_file)); + if (file == NULL) + return NULL; + + file->fd = open(filename, O_RDONLY | O_CLOEXEC); + if (file->fd < 0) { + free(file); + return NULL; + } + + sz = pread_str_safe(file->fd, buf, sizeof(buf), 0); + if (sz != (sizeof(buf) - 1)) { + if (sz < 0) + errno = -sz; + else + errno = EINVAL; + + close(file->fd); + free(file); + return NULL; + } + + for (unsigned int i = 0; i < ARRAY_SIZE(comp_types); i++) { + const struct comp_type *itr = &comp_types[i]; + + file->load = itr->load; + file->compression = itr->compression; + if (itr->magic_size && + memcmp(buf, itr->magic_bytes, itr->magic_size) == 0) { + break; + } + } + + file->ctx = ctx; + + return file; +} + +/* + * Callers should just check file->memory got updated + */ +int kmod_file_load_contents(struct kmod_file *file) +{ + if (file->memory) + return 0; + + /* The load functions already log possible errors. */ + return file->load(file); +} + +void *kmod_file_get_contents(const struct kmod_file *file) +{ + return file->memory; +} + +off_t kmod_file_get_size(const struct kmod_file *file) +{ + return file->size; +} + +enum kmod_file_compression_type kmod_file_get_compression(const struct kmod_file *file) +{ + return file->compression; +} + +int kmod_file_get_fd(const struct kmod_file *file) +{ + return file->fd; +} + +void kmod_file_unref(struct kmod_file *file) +{ + if (file->elf) + kmod_elf_unref(file->elf); + + if (file->compression == KMOD_FILE_COMPRESSION_NONE) { + if (file->memory) + munmap(file->memory, file->size); + } else { + free(file->memory); + } + + close(file->fd); + free(file); +} diff --git a/libkmod/libkmod-index.c b/libkmod/libkmod-index.c new file mode 100644 index 0000000..40199a5 --- /dev/null +++ b/libkmod/libkmod-index.c @@ -0,0 +1,1086 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "libkmod-internal.h" +#include "libkmod-index.h" + +/* libkmod-index.c: module index file implementation + * + * Integers are stored as 32 bit unsigned in "network" order, i.e. MSB first. + * All files start with a magic number. + * + * Magic spells "BOOTFAST", where the exact encoding varies across versions. + * + * The original implementation in modutils/module-init-tools used 0xB007FA57, but also + * lacked the version fields. Thus later on, with module-init-tools commit 44d7ac4 + * ("add versioning to the binary module files"), the encoding was changed to 0xB007F457 + * (A -> 4) and the version fields were introduced. Shortly afterwards the format was + * changed in backwards incompatible way and the major version was bumped to 2. + * + * We use a version string to keep track of changes to the binary format. + * This is stored in the form: INDEX_MAJOR (hi) INDEX_MINOR (lo) just in + * case we ever decide to have minor changes that are not incompatible. + */ +#define INDEX_MAGIC 0xB007F457 +#define INDEX_VERSION_MAJOR 0x0002 +#define INDEX_VERSION_MINOR 0x0001 +#define INDEX_VERSION ((INDEX_VERSION_MAJOR << 16) | INDEX_VERSION_MINOR) + +/* The index file maps keys to values. Both keys and values are ASCII strings. + * Each key can have multiple values. Values are sorted by an integer priority. + * + * The reader also implements a wildcard search (including range expressions) + * where the keys in the index are treated as patterns. + * This feature is required for module aliases. + */ +#define INDEX_CHILDMAX 128u + +/* Disk format: + * + * uint32_t magic = INDEX_MAGIC; + * uint32_t version = INDEX_VERSION; + * uint32_t root_offset; + * + * (node_offset & INDEX_NODE_MASK) specifies the file offset of nodes: + * + * char[] prefix; // nul terminated + * + * uint8_t first; + * uint8_t last; + * uint32_t children[last - first + 1]; + * + * uint32_t value_count; + * struct { + * uint32_t priority; + * char[] value; // nul terminated + * } values[value_count]; + * + * (node_offset & INDEX_NODE_FLAGS) indicates which fields are present. + * Empty prefixes are omitted, leaf nodes omit the three child-related fields. + * + * This could be optimised further by adding a sparse child format + * (indicated using a new flag). + * + * + * Implementation is based on a radix tree, or "trie". + * Each arc from parent to child is labelled with a character. + * Each path from the root represents a string. + * + * == Example strings == + * + * ask + * ate + * on + * once + * one + * + * == Key == + * + Normal node + * * Marked node, representing a key and its values. + * + * + + * |-a-+-s-+-k-* + * | | + * | `-t-+-e-* + * | + * `-o-+-n-*-c-+-e-* + * | + * `-e-* + * + * Naive implementations tend to be very space inefficient; child pointers + * are stored in arrays indexed by character, but most child pointers are null. + * + * Our implementation uses a scheme described by Wikipedia as a Patricia trie, + * + * "easiest to understand as a space-optimized trie where + * each node with only one child is merged with its child" + * + * + + * |-a-+-sk-* + * | | + * | `-te-* + * | + * `-on-*-ce-* + * | + * `-e-* + * + * We still use arrays of child pointers indexed by a single character; + * the remaining characters of the label are stored as a "prefix" in the child. + * + * The paper describing the original Patricia trie works on individual bits - + * each node has a maximum of two children, which increases space efficiency. + * However for this application it is simpler to use the ASCII character set. + * Since the index file is read-only, it can be compressed by omitting null + * child pointers at the start and end of arrays. + */ + +/* Format of node offsets within index file */ +enum node_offset { + INDEX_NODE_FLAGS = 0xF0000000, /* Flags in high nibble */ + INDEX_NODE_PREFIX = 0x80000000, + INDEX_NODE_VALUES = 0x40000000, + INDEX_NODE_CHILDS = 0x20000000, + + INDEX_NODE_MASK = 0x0FFFFFFF, /* Offset value */ +}; + +struct wrtbuf { + char bytes[4096]; + size_t len; + int fd; +}; + +static void wrtbuf_init(struct wrtbuf *buf, int fd) +{ + buf->len = 0; + buf->fd = fd; +} + +static void wrtbuf_flush(struct wrtbuf *buf) +{ + if (buf->len > 0) { + write_str_safe(buf->fd, buf->bytes, buf->len); + buf->len = 0; + } +} + +static void wrtbuf_write(struct wrtbuf *buf, const char *p, size_t len) +{ + size_t todo = len; + size_t done = 0; + + do { + size_t n = MIN(sizeof(buf->bytes) - buf->len, todo); + + memcpy(buf->bytes + buf->len, p + done, n); + buf->len += n; + todo -= n; + done += n; + + if (buf->len == sizeof(buf->bytes)) + wrtbuf_flush(buf); + } while (todo > 0); +} + +void index_values_free(struct index_value *values) +{ + while (values) { + struct index_value *value = values; + + values = value->next; + free(value); + } +} + +static int add_value(struct index_value **values, const char *value, size_t len, + uint32_t priority) +{ + struct index_value *v; + + /* find position to insert value */ + while (*values && (*values)->priority < priority) + values = &(*values)->next; + + v = malloc(sizeof(struct index_value) + len + 1); + if (!v) + return -1; + v->next = *values; + v->priority = priority; + v->len = len; + memcpy(v->value, value, len); + v->value[len] = '\0'; + *values = v; + + return 0; +} + +static inline int read_char(FILE *in) +{ + return getc_unlocked(in); +} + +static bool read_u32s(FILE *in, uint32_t *l, size_t n) +{ + size_t i; + + if (fread_unlocked(l, sizeof(uint32_t), n, in) != n) { + return false; + } + for (i = 0; i < n; i++) + l[i] = ntohl(l[i]); + return true; +} + +static inline bool read_u32(FILE *in, uint32_t *l) +{ + return read_u32s(in, l, 1); +} + +/* + * Index file searching + */ +struct index_file { + FILE *file; + uint32_t root_offset; + char *tmp; + size_t tmp_size; +}; + +struct index_node_f { + struct index_file *idx; + char *prefix; /* path compression */ + struct index_value *values; + uint8_t first; /* range of child nodes */ + uint8_t last; + uint32_t children[0]; +}; + +static struct index_node_f *index_read(struct index_file *idx, uint32_t offset) +{ + struct index_node_f *node = NULL; + char *prefix = NULL; + size_t child_count = 0; + FILE *fp = idx->file; + + if ((offset & INDEX_NODE_MASK) == 0) + return NULL; + + if (fseek(fp, offset & INDEX_NODE_MASK, SEEK_SET) < 0) + return NULL; + + if (offset & INDEX_NODE_PREFIX) { + if (getdelim(&idx->tmp, &idx->tmp_size, '\0', fp) < 0) + return NULL; + prefix = strdup(idx->tmp); + } else + prefix = strdup(""); + + if (prefix == NULL) + goto err; + + if (offset & INDEX_NODE_CHILDS) { + int first = read_char(fp); + int last = read_char(fp); + + if (first == EOF || last == EOF || first > last) + goto err; + + child_count = last - first + 1; + + node = malloc(sizeof(struct index_node_f) + + sizeof(uint32_t) * child_count); + if (node == NULL) + goto err; + + node->first = (uint8_t)first; + node->last = (uint8_t)last; + + if (!read_u32s(fp, node->children, child_count)) + goto err; + } else { + node = malloc(sizeof(struct index_node_f)); + if (node == NULL) + goto err; + + node->first = INDEX_CHILDMAX; + node->last = 0; + } + + node->values = NULL; + if (offset & INDEX_NODE_VALUES) { + uint32_t value_count; + uint32_t priority; + + if (!read_u32(fp, &value_count)) + goto err; + + while (value_count--) { + ssize_t n; + + if (!read_u32(fp, &priority)) + goto err; + n = getdelim(&idx->tmp, &idx->tmp_size, '\0', fp); + if (n < 0) + goto err; + add_value(&node->values, idx->tmp, n, priority); + } + } + + node->prefix = prefix; + node->idx = idx; + return node; +err: + free(prefix); + free(node); + return NULL; +} + +static void index_close(struct index_node_f *node) +{ + free(node->prefix); + index_values_free(node->values); + free(node); +} + +struct index_file *index_file_open(const char *filename) +{ + FILE *file; + uint32_t magic, version; + struct index_file *new; + + file = fopen(filename, "re"); + if (!file) + return NULL; + + if (!read_u32(file, &magic) || magic != INDEX_MAGIC) + goto err; + + if (!read_u32(file, &version) || version >> 16 != INDEX_VERSION_MAJOR) + goto err; + + new = malloc(sizeof(struct index_file)); + if (new == NULL) + goto err; + + new->file = file; + if (!read_u32(new->file, &new->root_offset)) { + free(new); + goto err; + } + new->tmp = NULL; + new->tmp_size = 0; + + return new; +err: + fclose(file); + return NULL; +} + +void index_file_close(struct index_file *idx) +{ + fclose(idx->file); + free(idx->tmp); + free(idx); +} + +static struct index_node_f *index_readroot(struct index_file *in) +{ + return index_read(in, in->root_offset); +} + +static struct index_node_f *index_readchild(const struct index_node_f *parent, uint8_t ch) +{ + if (parent->first <= ch && ch <= parent->last) { + return index_read(parent->idx, parent->children[ch - parent->first]); + } + + return NULL; +} + +static void index_dump_node(struct index_node_f *node, struct strbuf *buf, + struct wrtbuf *wbuf) +{ + struct index_value *v; + size_t pushed; + + pushed = strbuf_pushchars(buf, node->prefix); + + for (v = node->values; v != NULL; v = v->next) { + wrtbuf_write(wbuf, buf->bytes, strbuf_used(buf)); + wrtbuf_write(wbuf, " ", 1); + wrtbuf_write(wbuf, v->value, strlen(v->value)); + wrtbuf_write(wbuf, "\n", 1); + } + + for (uint8_t ch = node->first; ch <= node->last; ch++) { + struct index_node_f *child = index_readchild(node, ch); + + if (!child) + continue; + + if (strbuf_pushchar(buf, ch)) { + index_dump_node(child, buf, wbuf); + strbuf_popchar(buf); + } + } + + strbuf_popchars(buf, pushed); + index_close(node); +} + +void index_dump(struct index_file *in, int fd, bool alias_prefix) +{ + struct index_node_f *root; + struct strbuf buf; + struct wrtbuf wbuf; + + root = index_readroot(in); + if (root == NULL) + return; + + strbuf_init(&buf); + wrtbuf_init(&wbuf, fd); + if (!alias_prefix || strbuf_pushchars(&buf, "alias ")) + index_dump_node(root, &buf, &wbuf); + wrtbuf_flush(&wbuf); + strbuf_release(&buf); +} + +static char *index_search__node(struct index_node_f *node, const char *key, int i) +{ + char *value; + struct index_node_f *child; + int j; + + while (node) { + for (j = 0; node->prefix[j]; j++) { + uint8_t ch = node->prefix[j]; + + if (ch != key[i + j]) { + index_close(node); + return NULL; + } + } + + i += j; + + if (key[i] == '\0') { + value = node->values != NULL ? strdup(node->values[0].value) : + NULL; + + index_close(node); + return value; + } + + child = index_readchild(node, key[i]); + index_close(node); + node = child; + i++; + } + + return NULL; +} + +/* + * Search the index for a key + * + * Returns the value of the first match + * + * The recursive functions free their node argument (using index_close). + */ +char *index_search(struct index_file *in, const char *key) +{ + // FIXME: return value by reference instead of strdup + struct index_node_f *root; + char *value; + + root = index_readroot(in); + value = index_search__node(root, key, 0); + + return value; +} + +/* Level 4: add all the values from a matching node */ +static void index_searchwild__allvalues(struct index_node_f *node, + struct index_value **out) +{ + struct index_value *v; + + for (v = node->values; v != NULL; v = v->next) + add_value(out, v->value, v->len, v->priority); + + index_close(node); +} + +/* + * Level 3: traverse a sub-keyspace which starts with a wildcard, + * looking for matches. + */ +static void index_searchwild__all(struct index_node_f *node, int j, struct strbuf *buf, + const char *subkey, struct index_value **out) +{ + size_t pushed; + + pushed = strbuf_pushchars(buf, &node->prefix[j]); + + for (uint8_t ch = node->first; ch <= node->last; ch++) { + struct index_node_f *child = index_readchild(node, ch); + + if (!child) + continue; + + if (strbuf_pushchar(buf, ch)) { + index_searchwild__all(child, 0, buf, subkey, out); + strbuf_popchar(buf); + } + } + + if (node->values) { + const char *s = strbuf_str(buf); + + if (s != NULL && fnmatch(s, subkey, 0) == 0) + index_searchwild__allvalues(node, out); + else + index_close(node); + } else { + index_close(node); + } + + strbuf_popchars(buf, pushed); +} + +/* Level 2: descend the tree (until we hit a wildcard) */ +static void index_searchwild__node(struct index_node_f *node, struct strbuf *buf, + const char *key, struct index_value **out) +{ + struct index_node_f *child; + int j; + + while (node) { + for (j = 0; node->prefix[j]; j++) { + uint8_t ch = node->prefix[j]; + + if (ch == '*' || ch == '?' || ch == '[') { + index_searchwild__all(node, j, buf, &key[j], out); + return; + } + + if (ch != key[j]) { + index_close(node); + return; + } + } + + key += j; + + child = index_readchild(node, '*'); + if (child) { + if (strbuf_pushchar(buf, '*')) { + index_searchwild__all(child, 0, buf, key, out); + strbuf_popchar(buf); + } + } + + child = index_readchild(node, '?'); + if (child) { + if (strbuf_pushchar(buf, '?')) { + index_searchwild__all(child, 0, buf, key, out); + strbuf_popchar(buf); + } + } + + child = index_readchild(node, '['); + if (child) { + if (strbuf_pushchar(buf, '[')) { + index_searchwild__all(child, 0, buf, key, out); + strbuf_popchar(buf); + } + } + + if (*key == '\0') { + index_searchwild__allvalues(node, out); + + return; + } + + child = index_readchild(node, *key); + index_close(node); + node = child; + key++; + } +} + +/* + * Search the index for a key. The index may contain wildcards. + * + * Returns a list of all the values of matching keys. + */ +struct index_value *index_searchwild(struct index_file *in, const char *key) +{ + struct index_node_f *root = index_readroot(in); + struct strbuf buf; + struct index_value *out = NULL; + + strbuf_init(&buf); + index_searchwild__node(root, &buf, key, &out); + strbuf_release(&buf); + return out; +} + +/**************************************************************************/ +/* + * Alternative implementation, using mmap to map all the file to memory when + * starting + */ +#include +#include +#include + +struct index_mm { + const struct kmod_ctx *ctx; + void *mm; + uint32_t root_offset; + size_t size; +}; + +struct index_mm_value { + uint32_t priority; + size_t len; + const char *value; +}; + +struct index_mm_node { + const struct index_mm *idx; + const char *prefix; /* mmap'ed value */ + unsigned char first; + unsigned char last; + const void *children; /* mmap'ed value */ + size_t value_count; + const void *values; /* mmap'ed value */ +}; + +static inline uint32_t read_u32_mm(const void **p) +{ + const uint8_t *addr = *(const uint8_t **)p; + uint32_t v; + + /* addr may be unaligned to uint32_t */ + v = get_unaligned((const uint32_t *)addr); + + *p = addr + sizeof(uint32_t); + return ntohl(v); +} + +static inline uint8_t read_char_mm(const void **p) +{ + const uint8_t *addr = *(const uint8_t **)p; + uint8_t v = *addr; + *p = addr + sizeof(uint8_t); + return v; +} + +static inline const char *read_chars_mm(const void **p, size_t *rlen) +{ + const char *addr = *(const char **)p; + size_t len = *rlen = strlen(addr); + *p = addr + len + 1; + return addr; +} + +static inline void read_value_mm(const void **p, struct index_mm_value *v) +{ + v->priority = read_u32_mm(p); + v->value = read_chars_mm(p, &v->len); +} + +/* reads node into given node struct and returns its address on success or NULL on error. */ +static struct index_mm_node *index_mm_read_node(const struct index_mm *idx, + uint32_t offset, + struct index_mm_node *node) +{ + const void *p; + + if ((offset & INDEX_NODE_MASK) == 0 || (offset & INDEX_NODE_MASK) >= idx->size) + return NULL; + + p = (const char *)idx->mm + (offset & INDEX_NODE_MASK); + + if (offset & INDEX_NODE_PREFIX) { + size_t len; + node->prefix = read_chars_mm(&p, &len); + } else { + node->prefix = ""; + } + + if (offset & INDEX_NODE_CHILDS) { + size_t child_count; + + node->first = read_char_mm(&p); + node->last = read_char_mm(&p); + + if (node->first > node->last || node->first >= INDEX_CHILDMAX || + node->last >= INDEX_CHILDMAX) + return NULL; + + node->children = p; + + child_count = node->last - node->first + 1; + p = (const char *)p + sizeof(uint32_t) * child_count; + } else { + node->first = INDEX_CHILDMAX; + node->last = 0; + node->children = NULL; + } + + if (offset & INDEX_NODE_VALUES) { + node->value_count = read_u32_mm(&p); + node->values = p; + } else { + node->value_count = 0; + node->values = NULL; + } + + node->idx = idx; + + return node; +} + +int index_mm_open(const struct kmod_ctx *ctx, const char *filename, + unsigned long long *stamp, struct index_mm **pidx) +{ + int fd, err; + struct stat st; + struct index_mm *idx; + struct { + uint32_t magic; + uint32_t version; + uint32_t root_offset; + } hdr; + const void *p; + + assert(pidx != NULL); + + DBG(ctx, "file=%s\n", filename); + + idx = malloc(sizeof(*idx)); + if (idx == NULL) { + ERR(ctx, "malloc: %m\n"); + return -ENOMEM; + } + + if ((fd = open(filename, O_RDONLY | O_CLOEXEC)) < 0) { + DBG(ctx, "open(%s, O_RDONLY|O_CLOEXEC): %m\n", filename); + err = -errno; + goto fail_open; + } + + if (fstat(fd, &st) < 0 || st.st_size < (off_t)sizeof(hdr)) { + err = -EINVAL; + goto fail_nommap; + } + + if ((uintmax_t)st.st_size > SIZE_MAX) { + err = -ENOMEM; + goto fail_nommap; + } + + idx->mm = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (idx->mm == MAP_FAILED) { + ERR(ctx, "mmap(NULL, %" PRIu64 ", PROT_READ, MAP_PRIVATE, %d, 0): %m\n", + (uint64_t)st.st_size, fd); + err = -errno; + goto fail_nommap; + } + + p = idx->mm; + hdr.magic = read_u32_mm(&p); + hdr.version = read_u32_mm(&p); + hdr.root_offset = read_u32_mm(&p); + + if (hdr.magic != INDEX_MAGIC) { + ERR(ctx, "magic check fail: %x instead of %x\n", hdr.magic, INDEX_MAGIC); + err = -EINVAL; + goto fail; + } + + if (hdr.version >> 16 != INDEX_VERSION_MAJOR) { + ERR(ctx, "major version check fail: %u instead of %u\n", + hdr.version >> 16, INDEX_VERSION_MAJOR); + err = -EINVAL; + goto fail; + } + + idx->root_offset = hdr.root_offset; + idx->size = st.st_size; + idx->ctx = ctx; + close(fd); + + *stamp = stat_mstamp(&st); + *pidx = idx; + + return 0; + +fail: + munmap(idx->mm, st.st_size); +fail_nommap: + close(fd); +fail_open: + free(idx); + return err; +} + +void index_mm_close(struct index_mm *idx) +{ + munmap(idx->mm, idx->size); + free(idx); +} + +static struct index_mm_node *index_mm_readroot(const struct index_mm *idx, + struct index_mm_node *root) +{ + return index_mm_read_node(idx, idx->root_offset, root); +} + +static struct index_mm_node *index_mm_readchild(const struct index_mm_node *parent, + uint8_t ch, struct index_mm_node *child) +{ + if (parent->first <= ch && ch <= parent->last) { + const void *p; + uint32_t off; + + p = (const char *)parent->children + + sizeof(uint32_t) * (ch - parent->first); + off = read_u32_mm(&p); + + return index_mm_read_node(parent->idx, off, child); + } + + return NULL; +} + +static void index_mm_dump_node(struct index_mm_node *node, struct strbuf *buf, + struct wrtbuf *wbuf) +{ + const void *p; + size_t i, pushed; + + pushed = strbuf_pushchars(buf, node->prefix); + + for (i = 0, p = node->values; i < node->value_count; i++) { + struct index_mm_value v; + + read_value_mm(&p, &v); + wrtbuf_write(wbuf, buf->bytes, strbuf_used(buf)); + wrtbuf_write(wbuf, " ", 1); + wrtbuf_write(wbuf, v.value, v.len); + wrtbuf_write(wbuf, "\n", 1); + } + + for (uint8_t ch = node->first; ch <= node->last; ch++) { + struct index_mm_node *child, nbuf; + + child = index_mm_readchild(node, ch, &nbuf); + if (child == NULL) + continue; + + if (strbuf_pushchar(buf, ch)) { + index_mm_dump_node(child, buf, wbuf); + strbuf_popchar(buf); + } + } + + strbuf_popchars(buf, pushed); +} + +void index_mm_dump(const struct index_mm *idx, int fd, bool alias_prefix) +{ + struct index_mm_node nbuf, *root; + struct strbuf buf; + struct wrtbuf wbuf; + + root = index_mm_readroot(idx, &nbuf); + if (root == NULL) + return; + + strbuf_init(&buf); + wrtbuf_init(&wbuf, fd); + if (!alias_prefix || strbuf_pushchars(&buf, "alias ")) + index_mm_dump_node(root, &buf, &wbuf); + wrtbuf_flush(&wbuf); + strbuf_release(&buf); +} + +static char *index_mm_search_node(struct index_mm_node *node, const char *key) +{ + char *value; + int j; + + while (node) { + for (j = 0; node->prefix[j]; j++) { + uint8_t ch = node->prefix[j]; + + if (ch != key[j]) + return NULL; + } + + key += j; + + if (*key == '\0') { + if (node->value_count > 0) { + const char *p = node->values; + + /* return first value without priority */ + p += sizeof(uint32_t); + value = strdup(p); + } else { + value = NULL; + } + + return value; + } + + node = index_mm_readchild(node, *key, node); + key++; + } + + return NULL; +} + +/* + * Search the index for a key + * + * Returns the value of the first match + */ +char *index_mm_search(const struct index_mm *idx, const char *key) +{ + // FIXME: return value by reference instead of strdup + struct index_mm_node nbuf, *root; + char *value; + + root = index_mm_readroot(idx, &nbuf); + value = index_mm_search_node(root, key); + + return value; +} + +/* Level 4: add all the values from a matching node */ +static void index_mm_searchwild_allvalues(struct index_mm_node *node, + struct index_value **out) +{ + const void *p; + size_t i; + + for (i = 0, p = node->values; i < node->value_count; i++) { + struct index_mm_value v; + + read_value_mm(&p, &v); + add_value(out, v.value, v.len, v.priority); + } +} + +/* + * Level 3: traverse a sub-keyspace which starts with a wildcard, + * looking for matches. + */ +static void index_mm_searchwild_all(struct index_mm_node *node, int j, struct strbuf *buf, + const char *subkey, struct index_value **out) +{ + size_t pushed; + + pushed = strbuf_pushchars(buf, &node->prefix[j]); + + for (uint8_t ch = node->first; ch <= node->last; ch++) { + struct index_mm_node *child, nbuf; + + child = index_mm_readchild(node, ch, &nbuf); + if (!child) + continue; + + if (strbuf_pushchar(buf, ch)) { + index_mm_searchwild_all(child, 0, buf, subkey, out); + strbuf_popchar(buf); + } + } + + if (node->value_count > 0) { + const char *s = strbuf_str(buf); + + if (s != NULL && fnmatch(s, subkey, 0) == 0) + index_mm_searchwild_allvalues(node, out); + } + + strbuf_popchars(buf, pushed); +} + +/* Level 2: descend the tree (until we hit a wildcard) */ +static void index_mm_searchwild_node(struct index_mm_node *node, struct strbuf *buf, + const char *key, struct index_value **out) +{ + while (node) { + struct index_mm_node *child, nbuf; + int j; + + for (j = 0; node->prefix[j]; j++) { + uint8_t ch = node->prefix[j]; + + if (ch == '*' || ch == '?' || ch == '[') { + index_mm_searchwild_all(node, j, buf, key + j, out); + return; + } + + if (ch != key[j]) + return; + } + + key += j; + + child = index_mm_readchild(node, '*', &nbuf); + if (child) { + if (strbuf_pushchar(buf, '*')) { + index_mm_searchwild_all(child, 0, buf, key, out); + strbuf_popchar(buf); + } + } + + child = index_mm_readchild(node, '?', &nbuf); + if (child) { + if (strbuf_pushchar(buf, '?')) { + index_mm_searchwild_all(child, 0, buf, key, out); + strbuf_popchar(buf); + } + } + + child = index_mm_readchild(node, '[', &nbuf); + if (child) { + if (strbuf_pushchar(buf, '[')) { + index_mm_searchwild_all(child, 0, buf, key, out); + strbuf_popchar(buf); + } + } + + if (*key == '\0') { + index_mm_searchwild_allvalues(node, out); + + return; + } + + node = index_mm_readchild(node, *key, node); + key++; + } +} + +/* + * Search the index for a key. The index may contain wildcards. + * + * Returns a list of all the values of matching keys. + */ +struct index_value *index_mm_searchwild(const struct index_mm *idx, const char *key) +{ + struct index_mm_node nbuf, *root; + struct strbuf buf; + struct index_value *out = NULL; + + root = index_mm_readroot(idx, &nbuf); + strbuf_init(&buf); + index_mm_searchwild_node(root, &buf, key, &out); + strbuf_release(&buf); + return out; +} diff --git a/libkmod/libkmod-index.h b/libkmod/libkmod-index.h new file mode 100644 index 0000000..47c50d3 --- /dev/null +++ b/libkmod/libkmod-index.h @@ -0,0 +1,34 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#pragma once + +#include + +struct index_value { + struct index_value *next; + uint32_t priority; + size_t len; + char value[0]; +}; + +/* In-memory index (depmod only) */ +struct index_file; +struct index_file *index_file_open(const char *filename); +void index_file_close(struct index_file *idx); +char *index_search(struct index_file *idx, const char *key); +void index_dump(struct index_file *in, int fd, bool alias_prefix); +struct index_value *index_searchwild(struct index_file *idx, const char *key); + +void index_values_free(struct index_value *values); + +/* Implementation using mmap */ +struct index_mm; +int index_mm_open(const struct kmod_ctx *ctx, const char *filename, + unsigned long long *stamp, struct index_mm **pidx); +void index_mm_close(struct index_mm *index); +char *index_mm_search(const struct index_mm *idx, const char *key); +struct index_value *index_mm_searchwild(const struct index_mm *idx, const char *key); +void index_mm_dump(const struct index_mm *idx, int fd, bool alias_prefix); diff --git a/libkmod/libkmod-internal-file.h b/libkmod/libkmod-internal-file.h new file mode 100644 index 0000000..a50d06e --- /dev/null +++ b/libkmod/libkmod-internal-file.h @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright © 2024 Intel Corporation + */ + +#include + +#include + +struct kmod_ctx; +struct kmod_elf; + +struct kmod_file { + int fd; + enum kmod_file_compression_type compression; + off_t size; + void *memory; + int (*load)(struct kmod_file *file); + const struct kmod_ctx *ctx; + struct kmod_elf *elf; +}; + +#if ENABLE_XZ +int kmod_file_load_xz(struct kmod_file *file); +#else +static inline int kmod_file_load_xz(struct kmod_file *file) +{ + return -ENOSYS; +} +#endif + +#if ENABLE_ZLIB +int kmod_file_load_zlib(struct kmod_file *file); +#else +static inline int kmod_file_load_zlib(struct kmod_file *file) +{ + return -ENOSYS; +} +#endif + +#if ENABLE_ZSTD +int kmod_file_load_zstd(struct kmod_file *file); +#else +static inline int kmod_file_load_zstd(struct kmod_file *file) +{ + return -ENOSYS; +} +#endif diff --git a/libkmod/libkmod-internal.h b/libkmod/libkmod-internal.h new file mode 100644 index 0000000..4706071 --- /dev/null +++ b/libkmod/libkmod-internal.h @@ -0,0 +1,187 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" + +#define kmod_log_cond(ctx, prio, arg...) \ + do { \ + if (ENABLE_LOGGING == 1 && \ + (ENABLE_DEBUG == 1 || (!ENABLE_DEBUG && prio != LOG_DEBUG)) && \ + kmod_get_log_priority(ctx) >= prio) \ + kmod_log(ctx, prio, __FILE__, __LINE__, __func__, ##arg); \ + } while (0) + +#define DBG(ctx, arg...) kmod_log_cond(ctx, LOG_DEBUG, ##arg) +#define NOTICE(ctx, arg...) kmod_log_cond(ctx, LOG_NOTICE, ##arg) +#define INFO(ctx, arg...) kmod_log_cond(ctx, LOG_INFO, ##arg) +#define ERR(ctx, arg...) kmod_log_cond(ctx, LOG_ERR, ##arg) + +#define KMOD_EXPORT __attribute__((visibility("default"))) + +#define KCMD_LINE_SIZE 4096 + +#ifndef HAVE_SECURE_GETENV +#warning secure_getenv is not available +#define secure_getenv getenv +#endif + +_printf_format_(6, 7) _nonnull_(1, 3, 5) void kmod_log(const struct kmod_ctx *ctx, + int priority, const char *file, + int line, const char *fn, + const char *format, ...); + +struct list_node { + struct list_node *next, *prev; +}; + +struct kmod_list { + struct list_node node; + void *data; +}; + +enum kmod_file_compression_type { + KMOD_FILE_COMPRESSION_NONE = 0, + KMOD_FILE_COMPRESSION_ZSTD, + KMOD_FILE_COMPRESSION_XZ, + KMOD_FILE_COMPRESSION_ZLIB, +}; + +// clang-format off +_must_check_ _nonnull_(2) struct kmod_list *kmod_list_append(struct kmod_list *list, const void *data); +_must_check_ _nonnull_(2) struct kmod_list *kmod_list_prepend(struct kmod_list *list, const void *data); +_must_check_ struct kmod_list *kmod_list_remove(struct kmod_list *list); +_must_check_ _nonnull_(2) struct kmod_list *kmod_list_remove_data(struct kmod_list *list, const void *data); +_nonnull_(2) struct kmod_list *kmod_list_insert_after(struct kmod_list *list, const void *data); +_nonnull_(2) struct kmod_list *kmod_list_insert_before(struct kmod_list *list, const void *data); +_must_check_ struct kmod_list *kmod_list_append_list(struct kmod_list *list1, struct kmod_list *list2); +#define kmod_list_release(list, free_data) \ + while (list) { \ + free_data((list)->data); \ + list = kmod_list_remove(list); \ + } + +/* libkmod.c */ +_nonnull_all_ int kmod_lookup_alias_from_config(struct kmod_ctx *ctx, const char *name, struct kmod_list **list); +_nonnull_all_ int kmod_lookup_alias_from_symbols_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list); +_nonnull_all_ int kmod_lookup_alias_from_aliases_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list); +_nonnull_all_ int kmod_lookup_alias_from_moddep_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list); +_nonnull_all_ int kmod_lookup_alias_from_kernel_builtin_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list); +_nonnull_all_ int kmod_lookup_alias_from_builtin_file(struct kmod_ctx *ctx, const char *name, struct kmod_list **list); +_nonnull_all_ bool kmod_lookup_alias_is_builtin(struct kmod_ctx *ctx, const char *name); +_nonnull_all_ int kmod_lookup_alias_from_commands(struct kmod_ctx *ctx, const char *name, struct kmod_list **list); +_nonnull_(1) void kmod_set_modules_visited(struct kmod_ctx *ctx, bool visited); +_nonnull_(1) void kmod_set_modules_required(struct kmod_ctx *ctx, bool required); + +_nonnull_all_ char *kmod_search_moddep(struct kmod_ctx *ctx, const char *name); + +_nonnull_all_ struct kmod_module *kmod_pool_get_module(struct kmod_ctx *ctx, const char *key); +_nonnull_all_ int kmod_pool_add_module(struct kmod_ctx *ctx, struct kmod_module *mod, const char *key); +_nonnull_all_ void kmod_pool_del_module(struct kmod_ctx *ctx, struct kmod_module *mod, const char *key); + +_nonnull_all_ const struct kmod_config *kmod_get_config(const struct kmod_ctx *ctx); +_nonnull_all_ enum kmod_file_compression_type kmod_get_kernel_compression(const struct kmod_ctx *ctx); + +/* libkmod-config.c */ +struct kmod_config_path { + unsigned long long stamp; + char path[]; +}; + +struct kmod_config { + struct kmod_ctx *ctx; + struct kmod_list *aliases; + struct kmod_list *blacklists; + struct kmod_list *options; + struct kmod_list *remove_commands; + struct kmod_list *install_commands; + struct kmod_list *softdeps; + struct kmod_list *weakdeps; + + struct kmod_list *paths; +}; + +_nonnull_all_ int kmod_config_new(struct kmod_ctx *ctx, struct kmod_config **config, const char * const *config_paths); +_nonnull_all_ void kmod_config_free(struct kmod_config *config); +_nonnull_all_ const char *kmod_blacklist_get_modname(const struct kmod_list *l); +_nonnull_all_ const char *kmod_alias_get_name(const struct kmod_list *l); +_nonnull_all_ const char *kmod_alias_get_modname(const struct kmod_list *l); +_nonnull_all_ const char *kmod_option_get_options(const struct kmod_list *l); +_nonnull_all_ const char *kmod_option_get_modname(const struct kmod_list *l); +_nonnull_all_ const char *kmod_command_get_command(const struct kmod_list *l); +_nonnull_all_ const char *kmod_command_get_modname(const struct kmod_list *l); + +_nonnull_all_ const char *kmod_softdep_get_name(const struct kmod_list *l); +_nonnull_all_ const char * const *kmod_softdep_get_pre(const struct kmod_list *l, unsigned int *count); +const char * const *kmod_softdep_get_post(const struct kmod_list *l, unsigned int *count); + +_nonnull_all_ const char * kmod_weakdep_get_name(const struct kmod_list *l); +_nonnull_all_ const char * const *kmod_weakdep_get_weak(const struct kmod_list *l, unsigned int *count); + +/* libkmod-module.c */ +int kmod_module_new_from_alias(struct kmod_ctx *ctx, const char *alias, const char *name, struct kmod_module **mod); +_nonnull_all_ void kmod_module_parse_depline(struct kmod_module *mod, char *line); +_nonnull_(1) void kmod_module_set_install_commands(struct kmod_module *mod, const char *cmd); +_nonnull_(1) void kmod_module_set_remove_commands(struct kmod_module *mod, const char *cmd); +_nonnull_(1)void kmod_module_set_visited(struct kmod_module *mod, bool visited); +_nonnull_(1) void kmod_module_set_builtin(struct kmod_module *mod, bool builtin); +_nonnull_(1) void kmod_module_set_required(struct kmod_module *mod, bool required); +_nonnull_all_ bool kmod_module_is_builtin(struct kmod_module *mod); + +/* libkmod-file.c */ +_must_check_ _nonnull_all_ struct kmod_file *kmod_file_open(const struct kmod_ctx *ctx, const char *filename); +_nonnull_all_ struct kmod_elf *kmod_file_get_elf(struct kmod_file *file); +_nonnull_all_ int kmod_file_load_contents(struct kmod_file *file); +_must_check_ _nonnull_all_ void *kmod_file_get_contents(const struct kmod_file *file); +_must_check_ _nonnull_all_ off_t kmod_file_get_size(const struct kmod_file *file); +_must_check_ _nonnull_all_ enum kmod_file_compression_type kmod_file_get_compression(const struct kmod_file *file); +_must_check_ _nonnull_all_ int kmod_file_get_fd(const struct kmod_file *file); +_nonnull_all_ void kmod_file_unref(struct kmod_file *file); + +/* libkmod-elf.c */ +struct kmod_elf; +struct kmod_modversion { + uint64_t crc; + enum kmod_symbol_bind bind; + const char *symbol; +}; + +struct kmod_elf *kmod_elf_new(const void *memory, off_t size); +_nonnull_all_ void kmod_elf_unref(struct kmod_elf *elf); +_must_check_ _nonnull_all_ const void *kmod_elf_get_memory(const struct kmod_elf *elf); +_must_check_ _nonnull_all_ int kmod_elf_get_modinfo_strings(const struct kmod_elf *elf, char ***array); +_must_check_ _nonnull_all_ int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array); +_must_check_ _nonnull_all_ int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array); +_must_check_ _nonnull_all_ int kmod_elf_get_dependency_symbols(const struct kmod_elf *elf, struct kmod_modversion **array); +_must_check_ _nonnull_all_ const void *kmod_elf_strip(const struct kmod_elf *elf, unsigned int flags); + +/* + * Debug mock lib need to find section ".gnu.linkonce.this_module" in order to + * get modname + */ +_must_check_ _nonnull_all_ int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, uint64_t *sec_off, uint64_t *sec_size); + +/* libkmod-signature.c */ +struct kmod_signature_info { + const char *signer; + size_t signer_len; + const char *key_id; + size_t key_id_len; + const char *algo, *hash_algo, *id_type; + const char *sig; + size_t sig_len; + void (*free)(void *); + void *private; +}; +_must_check_ _nonnull_all_ bool kmod_module_signature_info(const struct kmod_file *file, struct kmod_signature_info *sig_info); +_nonnull_all_ void kmod_module_signature_info_free(struct kmod_signature_info *sig_info); + +/* libkmod-builtin.c */ +_nonnull_all_ ssize_t kmod_builtin_get_modinfo(struct kmod_ctx *ctx, const char *modname, char ***modinfo); +// clang-format on diff --git a/libkmod/libkmod-list.c b/libkmod/libkmod-list.c new file mode 100644 index 0000000..5b5899c --- /dev/null +++ b/libkmod/libkmod-list.c @@ -0,0 +1,230 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +static inline struct list_node *list_node_init(struct list_node *node) +{ + node->next = node; + node->prev = node; + + return node; +} + +static inline void list_node_append(struct list_node *list, struct list_node *node) +{ + if (list == NULL) { + list_node_init(node); + return; + } + + node->prev = list->prev; + list->prev->next = node; + list->prev = node; + node->next = list; +} + +static inline struct list_node *list_node_remove(struct list_node *node) +{ + if (node->prev == node || node->next == node) + return NULL; + + node->prev->next = node->next; + node->next->prev = node->prev; + + return node->next; +} + +static inline void list_node_insert_after(struct list_node *list, struct list_node *node) +{ + if (list == NULL) { + list_node_init(node); + return; + } + + node->prev = list; + node->next = list->next; + list->next->prev = node; + list->next = node; +} + +static inline void list_node_insert_before(struct list_node *list, struct list_node *node) +{ + if (list == NULL) { + list_node_init(node); + return; + } + + node->next = list; + node->prev = list->prev; + list->prev->next = node; + list->prev = node; +} + +static inline void list_node_append_list(struct list_node *list1, struct list_node *list2) +{ + struct list_node *list1_last; + + if (list1 == NULL) { + list_node_init(list2); + return; + } + + list1->prev->next = list2; + list2->prev->next = list1; + + /* cache the last, because we will lose the pointer */ + list1_last = list1->prev; + + list1->prev = list2->prev; + list2->prev = list1_last; +} + +struct kmod_list *kmod_list_append(struct kmod_list *list, const void *data) +{ + struct kmod_list *new; + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_append(list ? &list->node : NULL, &new->node); + + return list ? list : new; +} + +struct kmod_list *kmod_list_insert_after(struct kmod_list *list, const void *data) +{ + struct kmod_list *new; + + if (list == NULL) + return kmod_list_append(list, data); + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_insert_after(&list->node, &new->node); + + return list; +} + +struct kmod_list *kmod_list_insert_before(struct kmod_list *list, const void *data) +{ + struct kmod_list *new; + + if (list == NULL) + return kmod_list_append(list, data); + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_insert_before(&list->node, &new->node); + + return new; +} + +struct kmod_list *kmod_list_append_list(struct kmod_list *list1, struct kmod_list *list2) +{ + if (list1 == NULL) + return list2; + + if (list2 == NULL) + return list1; + + list_node_append_list(&list1->node, &list2->node); + + return list1; +} + +struct kmod_list *kmod_list_prepend(struct kmod_list *list, const void *data) +{ + struct kmod_list *new; + + new = malloc(sizeof(*new)); + if (new == NULL) + return NULL; + + new->data = (void *)data; + list_node_append(list ? &list->node : NULL, &new->node); + + return new; +} + +struct kmod_list *kmod_list_remove(struct kmod_list *list) +{ + struct list_node *node; + + if (list == NULL) + return NULL; + + node = list_node_remove(&list->node); + free(list); + + if (node == NULL) + return NULL; + + return container_of(node, struct kmod_list, node); +} + +struct kmod_list *kmod_list_remove_data(struct kmod_list *list, const void *data) +{ + struct kmod_list *itr; + struct list_node *node; + + for (itr = list; itr != NULL; itr = kmod_list_next(list, itr)) { + if (itr->data == data) + break; + } + + if (itr == NULL) + return list; + + node = list_node_remove(&itr->node); + free(itr); + + if (node == NULL) + return NULL; + + return container_of(node, struct kmod_list, node); +} + +KMOD_EXPORT struct kmod_list *kmod_list_prev(const struct kmod_list *list, + const struct kmod_list *curr) +{ + if (list == NULL || curr == NULL) + return NULL; + + if (list == curr) + return NULL; + + return container_of(curr->node.prev, struct kmod_list, node); +} + +KMOD_EXPORT struct kmod_list *kmod_list_next(const struct kmod_list *list, + const struct kmod_list *curr) +{ + if (list == NULL || curr == NULL) + return NULL; + + if (curr->node.next == &list->node) + return NULL; + + return container_of(curr->node.next, struct kmod_list, node); +} + +KMOD_EXPORT struct kmod_list *kmod_list_last(const struct kmod_list *list) +{ + if (list == NULL) + return NULL; + return container_of(list->node.prev, struct kmod_list, node); +} diff --git a/libkmod/libkmod-module.c b/libkmod/libkmod-module.c new file mode 100644 index 0000000..0d74100 --- /dev/null +++ b/libkmod/libkmod-module.c @@ -0,0 +1,2307 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" + +enum kmod_module_builtin { + KMOD_MODULE_BUILTIN_UNKNOWN, + KMOD_MODULE_BUILTIN_NO, + KMOD_MODULE_BUILTIN_YES, +}; + +struct kmod_module { + struct kmod_ctx *ctx; + char *hashkey; + char *name; + char *path; + struct kmod_list *dep; + char *options; + const char *install_commands; /* owned by kmod_config */ + const char *remove_commands; /* owned by kmod_config */ + char *alias; /* only set if this module was created from an alias */ + struct kmod_file *file; + int refcount; + struct { + bool dep : 1; + bool options : 1; + bool install_commands : 1; + bool remove_commands : 1; + } init; + + /* + * mark if module is builtin, i.e. it's present on modules.builtin + * file. This is set as soon as it is needed or as soon as we know + * about it, i.e. the module was created from builtin lookup. + */ + enum kmod_module_builtin builtin; + + /* + * private field used by kmod_module_get_probe_list() to detect + * dependency loops + */ + bool visited : 1; + + /* + * set by kmod_module_get_probe_list: indicates for probe_insert() + * whether the module's command and softdep should be ignored + */ + bool ignorecmd : 1; + + /* + * set by kmod_module_get_probe_list: indicates whether this is the + * module the user asked for or its dependency, or whether this + * is a softdep only + */ + bool required : 1; +}; + +static inline const char *path_join(const char *path, size_t prefixlen, char buf[PATH_MAX]) +{ + size_t pathlen; + + if (path[0] == '/') + return path; + + pathlen = strlen(path); + if (prefixlen + pathlen + 1 >= PATH_MAX) + return NULL; + + memcpy(buf + prefixlen, path, pathlen + 1); + return buf; +} + +static inline bool module_is_inkernel(struct kmod_module *mod) +{ + int state = kmod_module_get_initstate(mod); + + if (state == KMOD_MODULE_LIVE || state == KMOD_MODULE_BUILTIN) + return true; + + return false; +} + +void kmod_module_parse_depline(struct kmod_module *mod, char *line) +{ + struct kmod_ctx *ctx = mod->ctx; + struct kmod_list *list = NULL; + const char *dirname; + char buf[PATH_MAX]; + char *p, *saveptr; + size_t n = 0; + size_t dirnamelen; + + if (mod->init.dep) + return; + assert(mod->dep == NULL); + mod->init.dep = true; + + p = strchr(line, ':'); + if (p == NULL) + return; + + *p = '\0'; + dirname = kmod_get_dirname(mod->ctx); + dirnamelen = strlen(dirname); + if (dirnamelen + 2 >= PATH_MAX) + return; + + memcpy(buf, dirname, dirnamelen); + buf[dirnamelen] = '/'; + dirnamelen++; + buf[dirnamelen] = '\0'; + + if (mod->path == NULL) { + const char *str = path_join(line, dirnamelen, buf); + if (str == NULL) + return; + mod->path = strdup(str); + if (mod->path == NULL) + return; + } + + p++; + for (p = strtok_r(p, " \t", &saveptr); p != NULL; + p = strtok_r(NULL, " \t", &saveptr)) { + struct kmod_list *l_new; + struct kmod_module *depmod = NULL; + const char *path; + int err; + + path = path_join(p, dirnamelen, buf); + if (path == NULL) { + ERR(ctx, "could not join path '%s' and '%s'.\n", dirname, p); + goto fail; + } + + err = kmod_module_new_from_path(ctx, path, &depmod); + if (err < 0) { + ERR(ctx, "ctx=%p path=%s error=%s\n", ctx, path, strerror(-err)); + goto fail; + } + + DBG(ctx, "add dep: %s\n", path); + + l_new = kmod_list_prepend(list, depmod); + if (l_new == NULL) { + ERR(ctx, "could not add dependency for %s\n", mod->name); + goto fail; + } + list = l_new; + } + + DBG(ctx, "%zu dependencies for %s\n", n, mod->name); + + mod->dep = list; + return; + +fail: + kmod_module_unref_list(list); + mod->init.dep = false; +} + +void kmod_module_set_visited(struct kmod_module *mod, bool visited) +{ + mod->visited = visited; +} + +void kmod_module_set_builtin(struct kmod_module *mod, bool builtin) +{ + mod->builtin = builtin ? KMOD_MODULE_BUILTIN_YES : KMOD_MODULE_BUILTIN_NO; +} + +void kmod_module_set_required(struct kmod_module *mod, bool required) +{ + mod->required = required; +} + +bool kmod_module_is_builtin(struct kmod_module *mod) +{ + if (mod->builtin == KMOD_MODULE_BUILTIN_UNKNOWN) { + kmod_module_set_builtin(mod, kmod_lookup_alias_is_builtin(mod->ctx, + mod->name)); + } + + return mod->builtin == KMOD_MODULE_BUILTIN_YES; +} +/* + * Memory layout with alias: + * + * struct kmod_module { + * hashkey -----. + * alias -----. | + * name ----. | | + * } | | | + * name <----------' | | + * alias <-----------' | + * name\alias <--------' + * + * Memory layout without alias: + * + * struct kmod_module { + * hashkey ---. + * alias -----|----> NULL + * name ----. | + * } | | + * name <----------'-' + * + * @key is "name\alias" or "name" (in which case alias == NULL) + */ +static int kmod_module_new(struct kmod_ctx *ctx, const char *key, const char *name, + size_t namelen, const char *alias, size_t aliaslen, + struct kmod_module **mod) +{ + struct kmod_module *m; + size_t keylen; + int err; + + m = kmod_pool_get_module(ctx, key); + if (m != NULL) { + *mod = kmod_module_ref(m); + return 0; + } + + if (alias == NULL) + keylen = namelen; + else + keylen = namelen + aliaslen + 1; + + m = malloc(sizeof(*m) + (alias == NULL ? 1 : 2) * (keylen + 1)); + if (m == NULL) + return -ENOMEM; + + memset(m, 0, sizeof(*m)); + + m->ctx = kmod_ref(ctx); + m->name = (char *)m + sizeof(*m); + memcpy(m->name, key, keylen + 1); + if (alias == NULL) { + m->hashkey = m->name; + m->alias = NULL; + } else { + m->name[namelen] = '\0'; + m->alias = m->name + namelen + 1; + m->hashkey = m->name + keylen + 1; + memcpy(m->hashkey, key, keylen + 1); + } + + m->refcount = 1; + err = kmod_pool_add_module(ctx, m, m->hashkey); + if (err < 0) { + free(m); + return err; + } + *mod = m; + + return 0; +} + +KMOD_EXPORT int kmod_module_new_from_name(struct kmod_ctx *ctx, const char *name, + struct kmod_module **mod) +{ + size_t namelen; + char name_norm[PATH_MAX]; + + if (ctx == NULL || name == NULL || mod == NULL) + return -ENOENT; + + modname_normalize(name, name_norm, &namelen); + + return kmod_module_new(ctx, name_norm, name_norm, namelen, NULL, 0, mod); +} + +int kmod_module_new_from_alias(struct kmod_ctx *ctx, const char *alias, const char *name, + struct kmod_module **mod) +{ + char key[PATH_MAX]; + size_t namelen = strlen(name); + size_t aliaslen = strlen(alias); + + if (namelen + aliaslen + 2 > PATH_MAX) + return -ENAMETOOLONG; + + memcpy(key, name, namelen); + memcpy(key + namelen + 1, alias, aliaslen + 1); + key[namelen] = '\\'; + + return kmod_module_new(ctx, key, name, namelen, alias, aliaslen, mod); +} + +KMOD_EXPORT int kmod_module_new_from_path(struct kmod_ctx *ctx, const char *path, + struct kmod_module **mod) +{ + struct kmod_module *m; + int err; + struct stat st; + char name[PATH_MAX]; + char *abspath; + size_t namelen; + + if (ctx == NULL || path == NULL || mod == NULL) + return -ENOENT; + + abspath = path_make_absolute_cwd(path); + if (abspath == NULL) { + DBG(ctx, "no absolute path for %s\n", path); + return -ENOMEM; + } + + err = stat(abspath, &st); + if (err < 0) { + err = -errno; + DBG(ctx, "stat %s: %s\n", path, strerror(errno)); + free(abspath); + return err; + } + + if (path_to_modname(path, name, &namelen) == NULL) { + DBG(ctx, "could not get modname from path %s\n", path); + free(abspath); + return -ENOENT; + } + + err = kmod_module_new(ctx, name, name, namelen, NULL, 0, &m); + if (err < 0) { + free(abspath); + return err; + } + if (m->path == NULL) + m->path = abspath; + else if (streq(m->path, abspath)) + free(abspath); + else { + kmod_module_unref(m); + ERR(ctx, + "kmod_module '%s' already exists with different path: new-path='%s' old-path='%s'\n", + name, abspath, m->path); + free(abspath); + return -EEXIST; + } + + m->builtin = KMOD_MODULE_BUILTIN_NO; + *mod = m; + + return 0; +} + +KMOD_EXPORT struct kmod_module *kmod_module_unref(struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (--mod->refcount > 0) + return mod; + + DBG(mod->ctx, "kmod_module %p released\n", mod); + + kmod_pool_del_module(mod->ctx, mod, mod->hashkey); + kmod_module_unref_list(mod->dep); + + if (mod->file) + kmod_file_unref(mod->file); + + kmod_unref(mod->ctx); + free(mod->options); + free(mod->path); + free(mod); + return NULL; +} + +KMOD_EXPORT struct kmod_module *kmod_module_ref(struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + mod->refcount++; + + return mod; +} + +typedef _nonnull_all_ int (*lookup_func)(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list); + +static int __kmod_module_new_from_lookup(struct kmod_ctx *ctx, const lookup_func lookup[], + size_t lookup_count, const char *s, + struct kmod_list **list) +{ + unsigned int i; + + for (i = 0; i < lookup_count; i++) { + int err; + + err = lookup[i](ctx, s, list); + if (err < 0 && err != -ENOSYS) + return err; + else if (*list != NULL) + return 0; + } + + return 0; +} + +KMOD_EXPORT int kmod_module_new_from_lookup(struct kmod_ctx *ctx, const char *given_alias, + struct kmod_list **list) +{ + static const lookup_func lookup[] = { + kmod_lookup_alias_from_config, + kmod_lookup_alias_from_moddep_file, + kmod_lookup_alias_from_symbols_file, + kmod_lookup_alias_from_commands, + kmod_lookup_alias_from_aliases_file, + kmod_lookup_alias_from_builtin_file, + kmod_lookup_alias_from_kernel_builtin_file, + }; + char alias[PATH_MAX]; + int err; + + if (ctx == NULL || given_alias == NULL) + return -ENOENT; + + if (list == NULL || *list != NULL) { + ERR(ctx, "An empty list is needed to create lookup\n"); + return -ENOSYS; + } + + if (alias_normalize(given_alias, alias, NULL) < 0) { + DBG(ctx, "invalid alias: %s\n", given_alias); + return -EINVAL; + } + + DBG(ctx, "input alias=%s, normalized=%s\n", given_alias, alias); + + err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup), alias, list); + + DBG(ctx, "lookup=%s found=%d\n", alias, err >= 0 && *list); + + if (err < 0) { + kmod_module_unref_list(*list); + *list = NULL; + } + + return err; +} + +KMOD_EXPORT int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx, + const char *modname, + struct kmod_module **mod) +{ + static const lookup_func lookup[] = { + kmod_lookup_alias_from_moddep_file, + kmod_lookup_alias_from_builtin_file, + kmod_lookup_alias_from_kernel_builtin_file, + }; + char name_norm[PATH_MAX]; + struct kmod_list *list = NULL; + int err; + + if (ctx == NULL || modname == NULL || mod == NULL) + return -ENOENT; + + modname_normalize(modname, name_norm, NULL); + + DBG(ctx, "input modname=%s, normalized=%s\n", modname, name_norm); + + err = __kmod_module_new_from_lookup(ctx, lookup, ARRAY_SIZE(lookup), name_norm, + &list); + + DBG(ctx, "lookup=%s found=%d\n", name_norm, err >= 0 && list); + + if (err >= 0 && list != NULL) + *mod = kmod_module_get_module(list); + + kmod_module_unref_list(list); + + return err; +} + +KMOD_EXPORT int kmod_module_unref_list(struct kmod_list *list) +{ + kmod_list_release(list, kmod_module_unref); + + return 0; +} + +KMOD_EXPORT int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx, + const struct kmod_list *input, + struct kmod_list **output) +{ + return kmod_module_apply_filter(ctx, KMOD_FILTER_BLACKLIST, input, output); +} + +static void module_get_dependencies_noref(struct kmod_module *mod) +{ + if (!mod->init.dep) { + /* lazy init */ + char *line = kmod_search_moddep(mod->ctx, mod->name); + + if (line != NULL) { + kmod_module_parse_depline(mod, line); + free(line); + } + } +} + +KMOD_EXPORT struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod) +{ + struct kmod_list *l, *l_new, *list_new = NULL; + + if (mod == NULL) + return NULL; + + module_get_dependencies_noref((struct kmod_module *)mod); + + kmod_list_foreach(l, mod->dep) { + l_new = kmod_list_append(list_new, kmod_module_ref(l->data)); + if (l_new == NULL) { + kmod_module_unref(l->data); + goto fail; + } + + list_new = l_new; + } + + return list_new; + +fail: + ERR(mod->ctx, "out of memory\n"); + kmod_module_unref_list(list_new); + return NULL; +} + +KMOD_EXPORT struct kmod_module *kmod_module_get_module(const struct kmod_list *entry) +{ + if (entry == NULL) + return NULL; + + return kmod_module_ref(entry->data); +} + +KMOD_EXPORT const char *kmod_module_get_name(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + return mod->name; +} + +KMOD_EXPORT const char *kmod_module_get_path(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + DBG(mod->ctx, "name='%s' path='%s'\n", mod->name, mod->path); + + if (mod->path != NULL) + return mod->path; + if (mod->init.dep) + return NULL; + + /* lazy init */ + module_get_dependencies_noref((struct kmod_module *)mod); + + return mod->path; +} + +extern long delete_module(const char *name, unsigned int flags); + +KMOD_EXPORT int kmod_module_remove_module(struct kmod_module *mod, unsigned int flags) +{ + unsigned int libkmod_flags = flags & 0xff; + + int err; + + if (mod == NULL) + return -ENOENT; + + /* Filter out other flags and force ONONBLOCK */ + flags &= KMOD_REMOVE_FORCE; + flags |= KMOD_REMOVE_NOWAIT; + + err = delete_module(mod->name, flags); + if (err != 0) { + err = -errno; + if (!(libkmod_flags & KMOD_REMOVE_NOLOG)) + ERR(mod->ctx, "could not remove '%s': %m\n", mod->name); + } + + return err; +} + +extern long init_module(const void *mem, unsigned long len, const char *args); + +static int do_finit_module(struct kmod_module *mod, unsigned int flags, const char *args) +{ + enum kmod_file_compression_type compression, kernel_compression; + unsigned int kernel_flags = 0; + int err; + + /* + * When module is not compressed or its compression type matches the + * one in use by the kernel, there is no need to read the file + * in userspace. Otherwise, reuse ENOSYS to trigger the same fallback + * as when finit_module() is not supported. + */ + compression = kmod_file_get_compression(mod->file); + kernel_compression = kmod_get_kernel_compression(mod->ctx); + if (!(compression == KMOD_FILE_COMPRESSION_NONE || + compression == kernel_compression)) + return -ENOSYS; + + if (compression != KMOD_FILE_COMPRESSION_NONE) + kernel_flags |= MODULE_INIT_COMPRESSED_FILE; + + if (flags & KMOD_INSERT_FORCE_VERMAGIC) + kernel_flags |= MODULE_INIT_IGNORE_VERMAGIC; + if (flags & KMOD_INSERT_FORCE_MODVERSION) + kernel_flags |= MODULE_INIT_IGNORE_MODVERSIONS; + + err = finit_module(kmod_file_get_fd(mod->file), args, kernel_flags); + if (err < 0) + err = -errno; + + return err; +} + +static int do_init_module(struct kmod_module *mod, unsigned int flags, const char *args) +{ + _cleanup_free_ const void *stripped = NULL; + struct kmod_elf *elf; + const void *mem; + off_t size; + int err; + + if (flags & (KMOD_INSERT_FORCE_VERMAGIC | KMOD_INSERT_FORCE_MODVERSION)) { + elf = kmod_file_get_elf(mod->file); + if (elf == NULL) { + err = -errno; + return err; + } + + stripped = kmod_elf_strip(elf, flags); + if (stripped == NULL) { + ERR(mod->ctx, "Failed to strip version information: %s\n", + strerror(errno)); + return -errno; + } + mem = stripped; + } else { + err = kmod_file_load_contents(mod->file); + if (err) + return err; + + mem = kmod_file_get_contents(mod->file); + } + size = kmod_file_get_size(mod->file); + + err = init_module(mem, size, args); + if (err < 0) + err = -errno; + + return err; +} + +KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod, unsigned int flags, + const char *options) +{ + int err; + const char *path; + const char *args = options ? options : ""; + + if (mod == NULL) + return -ENOENT; + + path = kmod_module_get_path(mod); + if (path == NULL) { + ERR(mod->ctx, "could not find module by name='%s'\n", mod->name); + return -ENOENT; + } + + if (!mod->file) { + mod->file = kmod_file_open(mod->ctx, path); + if (mod->file == NULL) { + err = -errno; + return err; + } + } + + err = do_finit_module(mod, flags, args); + if (err == -ENOSYS) + err = do_init_module(mod, flags, args); + + if (err < 0) + INFO(mod->ctx, "Failed to insert module '%s': %s\n", path, strerror(-err)); + + return err; +} + +static bool module_is_blacklisted(const struct kmod_module *mod) +{ + const struct kmod_ctx *ctx = mod->ctx; + const struct kmod_config *config = kmod_get_config(ctx); + const struct kmod_list *bl = config->blacklists; + const struct kmod_list *l; + + kmod_list_foreach(l, bl) { + const char *modname = kmod_blacklist_get_modname(l); + + if (streq(modname, mod->name)) + return true; + } + + return false; +} + +KMOD_EXPORT int kmod_module_apply_filter(const struct kmod_ctx *ctx, + enum kmod_filter filter_type, + const struct kmod_list *input, + struct kmod_list **output) +{ + const struct kmod_list *li; + + if (ctx == NULL || output == NULL) + return -ENOENT; + + *output = NULL; + if (input == NULL) + return 0; + + kmod_list_foreach(li, input) { + struct kmod_module *mod = li->data; + struct kmod_list *node; + + if ((filter_type & KMOD_FILTER_BLACKLIST) && module_is_blacklisted(mod)) + continue; + + if ((filter_type & KMOD_FILTER_BUILTIN) && kmod_module_is_builtin(mod)) + continue; + + node = kmod_list_append(*output, mod); + if (node == NULL) + goto fail; + + *output = node; + kmod_module_ref(mod); + } + + return 0; + +fail: + kmod_module_unref_list(*output); + *output = NULL; + return -ENOMEM; +} + +static int command_do(struct kmod_module *mod, const char *type, const char *cmd) +{ + const char *modname = kmod_module_get_name(mod); + int err; + + DBG(mod->ctx, "%s %s\n", type, cmd); + + setenv("MODPROBE_MODULE", modname, 1); + err = system(cmd); + unsetenv("MODPROBE_MODULE"); + + if (err == -1) { + ERR(mod->ctx, "Could not run %s command '%s' for module %s: %m\n", type, + cmd, modname); + return -EINVAL; + } + + if (WEXITSTATUS(err)) { + ERR(mod->ctx, "Error running %s command '%s' for module %s: retcode %d\n", + type, cmd, modname, WEXITSTATUS(err)); + return -EINVAL; + } + + return 0; +} + +struct probe_insert_cb { + int (*run_install)(struct kmod_module *m, const char *cmd, void *data); + void *data; +}; + +static int module_do_install_commands(struct kmod_module *mod, const char *options, + struct probe_insert_cb *cb) +{ + const char *command = kmod_module_get_install_commands(mod); + char *p; + _cleanup_free_ char *cmd; + int err; + size_t cmdlen, options_len, varlen; + + assert(command); + + if (options == NULL) + options = ""; + + options_len = strlen(options); + cmdlen = strlen(command); + varlen = sizeof("$CMDLINE_OPTS") - 1; + + cmd = memdup(command, cmdlen + 1); + if (cmd == NULL) + return -ENOMEM; + + while ((p = strstr(cmd, "$CMDLINE_OPTS")) != NULL) { + size_t prefixlen = p - cmd; + size_t suffixlen = cmdlen - prefixlen - varlen; + size_t slen = cmdlen - varlen + options_len; + char *suffix = p + varlen; + _clang_suppress_alloc_ char *s = malloc(slen + 1); + if (!s) + return -ENOMEM; + + memcpy(s, cmd, p - cmd); + memcpy(s + prefixlen, options, options_len); + memcpy(s + prefixlen + options_len, suffix, suffixlen); + s[slen] = '\0'; + + free(cmd); + cmd = s; + cmdlen = slen; + } + + if (cb->run_install != NULL) + err = cb->run_install(mod, cmd, cb->data); + else + err = command_do(mod, "install", cmd); + + return err; +} + +static char *module_options_concat(const char *opt, const char *xopt) +{ + // TODO: we might need to check if xopt overrides options on opt + size_t optlen = opt == NULL ? 0 : strlen(opt); + size_t xoptlen = xopt == NULL ? 0 : strlen(xopt); + char *r; + + if (optlen == 0 && xoptlen == 0) + return NULL; + + r = malloc(optlen + xoptlen + 2); + + if (opt != NULL) { + memcpy(r, opt, optlen); + r[optlen] = ' '; + optlen++; + } + + if (xopt != NULL) + memcpy(r + optlen, xopt, xoptlen); + + r[optlen + xoptlen] = '\0'; + + return r; +} + +static int __kmod_module_get_probe_list(struct kmod_module *mod, bool required, + bool ignorecmd, struct kmod_list **list); + +/* re-entrant */ +static int __kmod_module_fill_softdep(struct kmod_module *mod, struct kmod_list **list) +{ + struct kmod_list *pre = NULL, *post = NULL, *l; + int err; + + err = kmod_module_get_softdeps(mod, &pre, &post); + if (err < 0) { + ERR(mod->ctx, "could not get softdep: %s\n", strerror(-err)); + goto fail; + } + + kmod_list_foreach(l, pre) { + struct kmod_module *m = l->data; + err = __kmod_module_get_probe_list(m, false, false, list); + if (err < 0) + goto fail; + } + + l = kmod_list_append(*list, kmod_module_ref(mod)); + if (l == NULL) { + kmod_module_unref(mod); + err = -ENOMEM; + goto fail; + } + *list = l; + mod->ignorecmd = (pre != NULL || post != NULL); + + kmod_list_foreach(l, post) { + struct kmod_module *m = l->data; + err = __kmod_module_get_probe_list(m, false, false, list); + if (err < 0) + goto fail; + } + +fail: + kmod_module_unref_list(pre); + kmod_module_unref_list(post); + + return err; +} + +/* re-entrant */ +static int __kmod_module_get_probe_list(struct kmod_module *mod, bool required, + bool ignorecmd, struct kmod_list **list) +{ + struct kmod_list *dep, *l; + int err = 0; + + if (mod->visited) { + DBG(mod->ctx, "Ignore module '%s': already visited\n", mod->name); + return 0; + } + mod->visited = true; + + dep = kmod_module_get_dependencies(mod); + if (required) { + /* + * Called from kmod_module_probe_insert_module(); set the + * ->required flag on mod and all its dependencies before + * they are possibly visited through some softdeps. + */ + mod->required = true; + kmod_list_foreach(l, dep) { + struct kmod_module *m = l->data; + m->required = true; + } + } + + kmod_list_foreach(l, dep) { + struct kmod_module *m = l->data; + err = __kmod_module_fill_softdep(m, list); + if (err < 0) + goto finish; + } + + if (ignorecmd) { + l = kmod_list_append(*list, kmod_module_ref(mod)); + if (l == NULL) { + kmod_module_unref(mod); + err = -ENOMEM; + goto finish; + } + *list = l; + mod->ignorecmd = true; + } else + err = __kmod_module_fill_softdep(mod, list); + +finish: + kmod_module_unref_list(dep); + return err; +} + +static int kmod_module_get_probe_list(struct kmod_module *mod, bool ignorecmd, + struct kmod_list **list) +{ + int err; + + assert(mod != NULL); + assert(list != NULL && *list == NULL); + + /* + * Make sure we don't get screwed by previous calls to this function + */ + kmod_set_modules_visited(mod->ctx, false); + kmod_set_modules_required(mod->ctx, false); + + err = __kmod_module_get_probe_list(mod, true, ignorecmd, list); + if (err < 0) { + kmod_module_unref_list(*list); + *list = NULL; + } + + return err; +} + +KMOD_EXPORT int kmod_module_probe_insert_module( + struct kmod_module *mod, unsigned int flags, const char *extra_options, + int (*run_install)(struct kmod_module *m, const char *cmd, void *data), + const void *data, + void (*print_action)(struct kmod_module *m, bool install, const char *options)) +{ + struct kmod_list *list = NULL, *l; + struct probe_insert_cb cb; + int err; + + if (mod == NULL) + return -ENOENT; + + if (!(flags & KMOD_PROBE_IGNORE_LOADED) && module_is_inkernel(mod)) { + if (flags & KMOD_PROBE_FAIL_ON_LOADED) + return -EEXIST; + else + return 0; + } + + if (module_is_blacklisted(mod)) { + if (mod->alias != NULL && (flags & KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY)) + return KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY; + + if (flags & KMOD_PROBE_APPLY_BLACKLIST_ALL) + return KMOD_PROBE_APPLY_BLACKLIST_ALL; + + if (flags & KMOD_PROBE_APPLY_BLACKLIST) + return KMOD_PROBE_APPLY_BLACKLIST; + } + + err = kmod_module_get_probe_list(mod, !!(flags & KMOD_PROBE_IGNORE_COMMAND), + &list); + if (err < 0) + return err; + + if (flags & KMOD_PROBE_APPLY_BLACKLIST_ALL) { + struct kmod_list *filtered = NULL; + + err = kmod_module_apply_filter(mod->ctx, KMOD_FILTER_BLACKLIST, list, + &filtered); + if (err < 0) + return err; + + kmod_module_unref_list(list); + if (filtered == NULL) + return KMOD_PROBE_APPLY_BLACKLIST_ALL; + + list = filtered; + } + + cb.run_install = run_install; + cb.data = (void *)data; + + kmod_list_foreach(l, list) { + struct kmod_module *m = l->data; + const char *moptions = kmod_module_get_options(m); + const char *cmd = kmod_module_get_install_commands(m); + char *options; + + if (!(flags & KMOD_PROBE_IGNORE_LOADED) && module_is_inkernel(m)) { + DBG(mod->ctx, "Ignoring module '%s': already loaded\n", m->name); + err = -EEXIST; + goto finish_module; + } + + options = + module_options_concat(moptions, m == mod ? extra_options : NULL); + + if (cmd != NULL && !m->ignorecmd) { + if (print_action != NULL) + print_action(m, true, options ?: ""); + + if (!(flags & KMOD_PROBE_DRY_RUN)) + err = module_do_install_commands(m, options, &cb); + } else { + if (print_action != NULL) + print_action(m, false, options ?: ""); + + if (!(flags & KMOD_PROBE_DRY_RUN)) + err = kmod_module_insert_module(m, flags, options); + } + + free(options); + +finish_module: + /* + * Treat "already loaded" error. If we were told to stop on + * already loaded and the module being loaded is not a softdep + * or dep, bail out. Otherwise, just ignore and continue. + * + * We need to check here because of race conditions. We + * checked first if module was already loaded but it may have + * been loaded between the check and the moment we try to + * insert it. + */ + if (err == -EEXIST && m == mod && (flags & KMOD_PROBE_FAIL_ON_LOADED)) + break; + + /* + * Ignore errors from softdeps + */ + if (err == -EEXIST || !m->required) + err = 0; + + else if (err < 0) + break; + } + + kmod_module_unref_list(list); + return err; +} + +KMOD_EXPORT const char *kmod_module_get_options(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (!mod->init.options) { + /* lazy init */ + struct kmod_module *m = (struct kmod_module *)mod; + const struct kmod_list *l; + const struct kmod_config *config; + char *opts = NULL; + size_t optslen = 0; + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->options) { + const char *modname = kmod_option_get_modname(l); + const char *str; + size_t len; + void *tmp; + + DBG(mod->ctx, "modname=%s mod->name=%s mod->alias=%s\n", modname, + mod->name, mod->alias); + if (!(streq(modname, mod->name) || + (mod->alias != NULL && streq(modname, mod->alias)))) + continue; + + DBG(mod->ctx, "passed = modname=%s mod->name=%s mod->alias=%s\n", + modname, mod->name, mod->alias); + str = kmod_option_get_options(l); + len = strlen(str); + if (len < 1) + continue; + + tmp = realloc(opts, optslen + len + 2); + if (tmp == NULL) { + free(opts); + goto failed; + } + + opts = tmp; + + if (optslen > 0) { + opts[optslen] = ' '; + optslen++; + } + + memcpy(opts + optslen, str, len); + optslen += len; + opts[optslen] = '\0'; + } + + m->init.options = true; + m->options = opts; + } + + return mod->options; + +failed: + ERR(mod->ctx, "out of memory\n"); + return NULL; +} + +KMOD_EXPORT const char *kmod_module_get_install_commands(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (!mod->init.install_commands) { + /* lazy init */ + struct kmod_module *m = (struct kmod_module *)mod; + const struct kmod_list *l; + const struct kmod_config *config; + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->install_commands) { + const char *modname = kmod_command_get_modname(l); + + if (fnmatch(modname, mod->name, 0) != 0) + continue; + + m->install_commands = kmod_command_get_command(l); + + /* + * find only the first command, as modprobe from + * module-init-tools does + */ + break; + } + + m->init.install_commands = true; + } + + return mod->install_commands; +} + +void kmod_module_set_install_commands(struct kmod_module *mod, const char *cmd) +{ + mod->init.install_commands = true; + mod->install_commands = cmd; +} + +static struct kmod_list *lookup_dep(struct kmod_ctx *ctx, const char *const *array, + unsigned int count) +{ + struct kmod_list *ret = NULL; + unsigned i; + + for (i = 0; i < count; i++) { + const char *depname = array[i]; + struct kmod_list *lst = NULL; + int err; + + err = kmod_module_new_from_lookup(ctx, depname, &lst); + if (err < 0) { + ERR(ctx, "failed to lookup dependency '%s', continuing anyway.\n", + depname); + continue; + } else if (lst != NULL) + ret = kmod_list_append_list(ret, lst); + } + return ret; +} + +KMOD_EXPORT int kmod_module_get_softdeps(const struct kmod_module *mod, + struct kmod_list **pre, struct kmod_list **post) +{ + const struct kmod_list *l; + const struct kmod_config *config; + + if (mod == NULL || pre == NULL || post == NULL) + return -ENOENT; + + assert(*pre == NULL); + assert(*post == NULL); + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->softdeps) { + const char *modname = kmod_softdep_get_name(l); + const char *const *array; + unsigned count; + + if (fnmatch(modname, mod->name, 0) != 0) + continue; + + array = kmod_softdep_get_pre(l, &count); + *pre = lookup_dep(mod->ctx, array, count); + array = kmod_softdep_get_post(l, &count); + *post = lookup_dep(mod->ctx, array, count); + + /* + * find only the first command, as modprobe from + * module-init-tools does + */ + break; + } + + return 0; +} + +KMOD_EXPORT int kmod_module_get_weakdeps(const struct kmod_module *mod, + struct kmod_list **weak) +{ + const struct kmod_list *l; + const struct kmod_config *config; + + if (mod == NULL || weak == NULL) + return -ENOENT; + + assert(*weak == NULL); + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->weakdeps) { + const char *modname = kmod_weakdep_get_name(l); + const char *const *array; + unsigned count; + + if (fnmatch(modname, mod->name, 0) != 0) + continue; + + array = kmod_weakdep_get_weak(l, &count); + *weak = lookup_dep(mod->ctx, array, count); + + /* + * find only the first command, as modprobe from + * module-init-tools does + */ + break; + } + + return 0; +} + +KMOD_EXPORT const char *kmod_module_get_remove_commands(const struct kmod_module *mod) +{ + if (mod == NULL) + return NULL; + + if (!mod->init.remove_commands) { + /* lazy init */ + struct kmod_module *m = (struct kmod_module *)mod; + const struct kmod_list *l; + const struct kmod_config *config; + + config = kmod_get_config(mod->ctx); + + kmod_list_foreach(l, config->remove_commands) { + const char *modname = kmod_command_get_modname(l); + + if (fnmatch(modname, mod->name, 0) != 0) + continue; + + m->remove_commands = kmod_command_get_command(l); + + /* + * find only the first command, as modprobe from + * module-init-tools does + */ + break; + } + + m->init.remove_commands = true; + } + + return mod->remove_commands; +} + +void kmod_module_set_remove_commands(struct kmod_module *mod, const char *cmd) +{ + mod->init.remove_commands = true; + mod->remove_commands = cmd; +} + +KMOD_EXPORT int kmod_module_new_from_loaded(struct kmod_ctx *ctx, struct kmod_list **list) +{ + struct kmod_list *l = NULL; + FILE *fp; + char line[4096]; + + if (ctx == NULL || list == NULL) + return -ENOENT; + + fp = fopen("/proc/modules", "re"); + if (fp == NULL) { + int err = -errno; + ERR(ctx, "could not open /proc/modules: %s\n", strerror(errno)); + return err; + } + + while (fgets(line, sizeof(line), fp)) { + struct kmod_module *m; + struct kmod_list *node; + int err; + size_t len = strlen(line); + char *saveptr, *name = strtok_r(line, " \t", &saveptr); + + err = kmod_module_new_from_name(ctx, name, &m); + if (err < 0) { + ERR(ctx, "could not get module from name '%s': %s\n", name, + strerror(-err)); + goto eat_line; + } + + node = kmod_list_append(l, m); + if (node) + l = node; + else { + ERR(ctx, "out of memory\n"); + kmod_module_unref(m); + } +eat_line: + while (len > 0 && line[len - 1] != '\n' && fgets(line, sizeof(line), fp)) + len = strlen(line); + } + + fclose(fp); + *list = l; + + return 0; +} + +KMOD_EXPORT const char *kmod_module_initstate_str(enum kmod_module_initstate state) +{ + switch (state) { + case KMOD_MODULE_BUILTIN: + return "builtin"; + case KMOD_MODULE_LIVE: + return "live"; + case KMOD_MODULE_COMING: + return "coming"; + case KMOD_MODULE_GOING: + return "going"; + default: + return NULL; + } +} + +KMOD_EXPORT int kmod_module_get_initstate(const struct kmod_module *mod) +{ + char path[PATH_MAX], buf[32]; + int fd, err, pathlen; + + if (mod == NULL) + return -ENOENT; + + /* remove const: this can only change internal state */ + if (kmod_module_is_builtin((struct kmod_module *)mod)) + return KMOD_MODULE_BUILTIN; + + pathlen = snprintf(path, sizeof(path), "/sys/module/%s/initstate", mod->name); + if (pathlen >= (int)sizeof(path)) { + /* Too long path was truncated */ + return -ENAMETOOLONG; + } + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + err = -errno; + + DBG(mod->ctx, "could not open '%s': %s\n", path, strerror(-err)); + + if (pathlen > (int)sizeof("/initstate") - 1) { + struct stat st; + path[pathlen - (sizeof("/initstate") - 1)] = '\0'; + if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) + return KMOD_MODULE_COMING; + } + + DBG(mod->ctx, "could not open '%s': %s\n", path, strerror(-err)); + return err; + } + + err = read_str_safe(fd, buf, sizeof(buf)); + close(fd); + if (err < 0) { + ERR(mod->ctx, "could not read from '%s': %s\n", path, strerror(-err)); + return err; + } + + if (streq(buf, "live\n")) + return KMOD_MODULE_LIVE; + else if (streq(buf, "coming\n")) + return KMOD_MODULE_COMING; + else if (streq(buf, "going\n")) + return KMOD_MODULE_GOING; + + ERR(mod->ctx, "unknown %s: '%s'\n", path, buf); + return -EINVAL; +} + +KMOD_EXPORT long kmod_module_get_size(const struct kmod_module *mod) +{ + FILE *fp; + char line[4096]; + int lineno = 0; + long size = -ENOENT; + int dfd, cfd; + + if (mod == NULL) + return -ENOENT; + + /* try to open the module dir in /sys. If this fails, don't + * bother trying to find the size as we know the module isn't + * loaded. + */ + snprintf(line, sizeof(line), "/sys/module/%s", mod->name); + dfd = open(line, O_RDONLY | O_CLOEXEC); + if (dfd < 0) + return -errno; + + /* available as of linux 3.3.x */ + cfd = openat(dfd, "coresize", O_RDONLY | O_CLOEXEC); + if (cfd >= 0) { + if (read_str_long(cfd, &size, 10) < 0) + ERR(mod->ctx, "failed to read coresize from %s\n", line); + close(cfd); + goto done; + } + + /* fall back on parsing /proc/modules */ + fp = fopen("/proc/modules", "re"); + if (fp == NULL) { + int err = -errno; + ERR(mod->ctx, "could not open /proc/modules: %s\n", strerror(errno)); + close(dfd); + return err; + } + + while (fgets(line, sizeof(line), fp)) { + size_t len = strlen(line); + char *saveptr, *endptr, *tok = strtok_r(line, " \t", &saveptr); + long value; + + lineno++; + if (tok == NULL || !streq(tok, mod->name)) + goto eat_line; + + tok = strtok_r(NULL, " \t", &saveptr); + if (tok == NULL) { + ERR(mod->ctx, "invalid line format at /proc/modules:%d\n", lineno); + break; + } + + value = strtol(tok, &endptr, 10); + if (endptr == tok || *endptr != '\0') { + ERR(mod->ctx, "invalid line format at /proc/modules:%d\n", lineno); + break; + } + + size = value; + break; +eat_line: + while (len > 0 && line[len - 1] != '\n' && fgets(line, sizeof(line), fp)) + len = strlen(line); + } + fclose(fp); + +done: + close(dfd); + return size; +} + +KMOD_EXPORT int kmod_module_get_refcnt(const struct kmod_module *mod) +{ + char path[PATH_MAX]; + long refcnt; + int fd, err; + + if (mod == NULL) + return -ENOENT; + + snprintf(path, sizeof(path), "/sys/module/%s/refcnt", mod->name); + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + err = -errno; + DBG(mod->ctx, "could not open '%s': %s\n", path, strerror(errno)); + return err; + } + + err = read_str_long(fd, &refcnt, 10); + close(fd); + if (err < 0) { + ERR(mod->ctx, "could not read integer from '%s': '%s'\n", path, + strerror(-err)); + return err; + } + + return (int)refcnt; +} + +KMOD_EXPORT struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod) +{ + char dname[PATH_MAX]; + struct kmod_list *list = NULL; + struct dirent *dent; + DIR *d; + + if (mod == NULL || mod->ctx == NULL) + return NULL; + + snprintf(dname, sizeof(dname), "/sys/module/%s/holders", mod->name); + + d = opendir(dname); + if (d == NULL) { + ERR(mod->ctx, "could not open '%s': %s\n", dname, strerror(errno)); + return NULL; + } + + for (dent = readdir(d); dent != NULL; dent = readdir(d)) { + struct kmod_module *holder; + struct kmod_list *l; + int err; + + if (dent->d_name[0] == '.') { + if (dent->d_name[1] == '\0' || + (dent->d_name[1] == '.' && dent->d_name[2] == '\0')) + continue; + } + + err = kmod_module_new_from_name(mod->ctx, dent->d_name, &holder); + if (err < 0) { + ERR(mod->ctx, "could not create module for '%s': %s\n", + dent->d_name, strerror(-err)); + goto fail; + } + + l = kmod_list_append(list, holder); + if (l != NULL) { + list = l; + } else { + ERR(mod->ctx, "out of memory\n"); + kmod_module_unref(holder); + goto fail; + } + } + + closedir(d); + return list; + +fail: + closedir(d); + kmod_module_unref_list(list); + return NULL; +} + +struct kmod_module_section { + unsigned long address; + char name[]; +}; + +static void kmod_module_section_free(struct kmod_module_section *section) +{ + free(section); +} + +KMOD_EXPORT struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod) +{ + char dname[PATH_MAX]; + struct kmod_list *list = NULL; + struct dirent *dent; + DIR *d; + int dfd; + + if (mod == NULL) + return NULL; + + snprintf(dname, sizeof(dname), "/sys/module/%s/sections", mod->name); + + d = opendir(dname); + if (d == NULL) { + ERR(mod->ctx, "could not open '%s': %s\n", dname, strerror(errno)); + return NULL; + } + + dfd = dirfd(d); + + for (dent = readdir(d); dent; dent = readdir(d)) { + struct kmod_module_section *section; + struct kmod_list *l; + unsigned long address; + size_t namesz; + int fd, err; + + if (dent->d_name[0] == '.') { + if (dent->d_name[1] == '\0' || + (dent->d_name[1] == '.' && dent->d_name[2] == '\0')) + continue; + } + + fd = openat(dfd, dent->d_name, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + ERR(mod->ctx, "could not open '%s/%s': %m\n", dname, dent->d_name); + goto fail; + } + + err = read_str_ulong(fd, &address, 16); + close(fd); + if (err < 0) { + ERR(mod->ctx, "could not read long from '%s/%s': %m\n", dname, + dent->d_name); + goto fail; + } + + namesz = strlen(dent->d_name) + 1; + section = malloc(sizeof(*section) + namesz); + + if (section == NULL) { + ERR(mod->ctx, "out of memory\n"); + goto fail; + } + + section->address = address; + memcpy(section->name, dent->d_name, namesz); + + l = kmod_list_append(list, section); + if (l != NULL) { + list = l; + } else { + ERR(mod->ctx, "out of memory\n"); + free(section); + goto fail; + } + } + + closedir(d); + return list; + +fail: + closedir(d); + kmod_module_unref_list(list); + return NULL; +} + +KMOD_EXPORT const char *kmod_module_section_get_name(const struct kmod_list *entry) +{ + struct kmod_module_section *section; + + if (entry == NULL) + return NULL; + + section = entry->data; + return section->name; +} + +KMOD_EXPORT unsigned long kmod_module_section_get_address(const struct kmod_list *entry) +{ + struct kmod_module_section *section; + + if (entry == NULL) + return (unsigned long)-1; + + section = entry->data; + return section->address; +} + +KMOD_EXPORT void kmod_module_section_free_list(struct kmod_list *list) +{ + kmod_list_release(list, kmod_module_section_free); +} + +static struct kmod_elf *kmod_module_get_elf(const struct kmod_module *mod) +{ + if (mod->file == NULL) { + const char *path = kmod_module_get_path(mod); + + if (path == NULL) { + errno = ENOENT; + return NULL; + } + + ((struct kmod_module *)mod)->file = kmod_file_open(mod->ctx, path); + if (mod->file == NULL) + return NULL; + } + + return kmod_file_get_elf(mod->file); +} + +struct kmod_module_info { + char *key; + char value[]; +}; + +static struct kmod_module_info *kmod_module_info_new(const char *key, size_t keylen, + const char *value, size_t valuelen) +{ + struct kmod_module_info *info; + + info = malloc(sizeof(struct kmod_module_info) + keylen + valuelen + 2); + if (info == NULL) + return NULL; + + info->key = (char *)info + sizeof(struct kmod_module_info) + valuelen + 1; + memcpy(info->key, key, keylen); + info->key[keylen] = '\0'; + memcpy(info->value, value, valuelen); + info->value[valuelen] = '\0'; + return info; +} + +static void kmod_module_info_free(struct kmod_module_info *info) +{ + free(info); +} + +static struct kmod_list *kmod_module_info_append(struct kmod_list **list, const char *key, + size_t keylen, const char *value, + size_t valuelen) +{ + struct kmod_module_info *info; + struct kmod_list *n; + + info = kmod_module_info_new(key, keylen, value, valuelen); + if (info == NULL) + return NULL; + n = kmod_list_append(*list, info); + if (n != NULL) + *list = n; + else + kmod_module_info_free(info); + return n; +} + +static char *kmod_module_hex_to_str(const char *hex, size_t len) +{ + static const char digits[] = "0123456789ABCDEF"; + _cleanup_strbuf_ struct strbuf sbuf; + const size_t line_limit = 20; + + strbuf_init(&sbuf); + + for (size_t i = 0; i < len; i++) { + if (!strbuf_pushchar(&sbuf, digits[(hex[i] >> 4) & 0x0F]) || + !strbuf_pushchar(&sbuf, digits[hex[i] & 0x0F])) + return NULL; + + if (i < len - 1) { + if (!strbuf_pushchar(&sbuf, ':')) + return NULL; + + if ((i + 1) % line_limit == 0 && + !strbuf_pushchars(&sbuf, "\n\t\t")) + return NULL; + } + } + + return strbuf_steal(&sbuf); +} + +static struct kmod_list *kmod_module_info_append_hex(struct kmod_list **list, + const char *key, size_t keylen, + const char *value, size_t valuelen) +{ + char *hex; + struct kmod_list *n; + + if (valuelen > 0) { + /* Display as 01:12:DE:AD:BE:EF:... */ + hex = kmod_module_hex_to_str(value, valuelen); + if (hex == NULL) + goto list_error; + n = kmod_module_info_append(list, key, keylen, hex, strlen(hex)); + free(hex); + if (n == NULL) + goto list_error; + } else { + n = kmod_module_info_append(list, key, keylen, NULL, 0); + if (n == NULL) + goto list_error; + } + + return n; + +list_error: + return NULL; +} + +KMOD_EXPORT int kmod_module_get_info(const struct kmod_module *mod, + struct kmod_list **list) +{ + struct kmod_elf *elf; + char **strings; + int i, count, ret = -ENOMEM; + struct kmod_signature_info sig_info = {}; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + /* remove const: this can only change internal state */ + if (kmod_module_is_builtin((struct kmod_module *)mod)) { + count = kmod_builtin_get_modinfo(mod->ctx, kmod_module_get_name(mod), + &strings); + if (count < 0) + return count; + } else { + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_modinfo_strings(elf, &strings); + if (count < 0) + return count; + } + + for (i = 0; i < count; i++) { + struct kmod_list *n; + const char *key, *value; + size_t keylen, valuelen; + + key = strings[i]; + value = strchr(key, '='); + if (value == NULL) { + keylen = strlen(key); + valuelen = 0; + value = key; + } else { + keylen = value - key; + value++; + valuelen = strlen(value); + } + + n = kmod_module_info_append(list, key, keylen, value, valuelen); + if (n == NULL) + goto list_error; + } + + if (mod->file && kmod_module_signature_info(mod->file, &sig_info)) { + struct kmod_list *n; + + n = kmod_module_info_append(list, "sig_id", strlen("sig_id"), + sig_info.id_type, strlen(sig_info.id_type)); + if (n == NULL) + goto list_error; + count++; + + n = kmod_module_info_append(list, "signer", strlen("signer"), + sig_info.signer, sig_info.signer_len); + if (n == NULL) + goto list_error; + count++; + + n = kmod_module_info_append_hex(list, "sig_key", strlen("sig_key"), + sig_info.key_id, sig_info.key_id_len); + if (n == NULL) + goto list_error; + count++; + + n = kmod_module_info_append(list, "sig_hashalgo", strlen("sig_hashalgo"), + sig_info.hash_algo, + strlen(sig_info.hash_algo)); + if (n == NULL) + goto list_error; + count++; + + /* + * Omit sig_info.algo for now, as these + * are currently constant. + */ + n = kmod_module_info_append_hex(list, "signature", strlen("signature"), + sig_info.sig, sig_info.sig_len); + + if (n == NULL) + goto list_error; + count++; + } + ret = count; + +list_error: + /* aux structures freed in normal case also */ + kmod_module_signature_info_free(&sig_info); + + if (ret < 0) { + kmod_module_info_free_list(*list); + *list = NULL; + } + free(strings); + return ret; +} + +KMOD_EXPORT const char *kmod_module_info_get_key(const struct kmod_list *entry) +{ + struct kmod_module_info *info; + + if (entry == NULL) + return NULL; + + info = entry->data; + return info->key; +} + +KMOD_EXPORT const char *kmod_module_info_get_value(const struct kmod_list *entry) +{ + struct kmod_module_info *info; + + if (entry == NULL) + return NULL; + + info = entry->data; + return info->value; +} + +KMOD_EXPORT void kmod_module_info_free_list(struct kmod_list *list) +{ + kmod_list_release(list, kmod_module_info_free); +} + +struct kmod_module_version { + uint64_t crc; + char symbol[]; +}; + +static struct kmod_module_version *kmod_module_versions_new(uint64_t crc, + const char *symbol) +{ + struct kmod_module_version *mv; + size_t symbollen = strlen(symbol) + 1; + + mv = malloc(sizeof(struct kmod_module_version) + symbollen); + if (mv == NULL) + return NULL; + + mv->crc = crc; + memcpy(mv->symbol, symbol, symbollen); + return mv; +} + +static void kmod_module_version_free(struct kmod_module_version *version) +{ + free(version); +} + +KMOD_EXPORT int kmod_module_get_versions(const struct kmod_module *mod, + struct kmod_list **list) +{ + struct kmod_elf *elf; + struct kmod_modversion *versions; + int i, count, ret = 0; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_modversions(elf, &versions); + if (count < 0) + return count; + + for (i = 0; i < count; i++) { + struct kmod_module_version *mv; + struct kmod_list *n; + + mv = kmod_module_versions_new(versions[i].crc, versions[i].symbol); + if (mv == NULL) { + ret = -errno; + kmod_module_versions_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, mv); + if (n != NULL) + *list = n; + else { + kmod_module_version_free(mv); + kmod_module_versions_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(versions); + return ret; +} + +KMOD_EXPORT const char *kmod_module_version_get_symbol(const struct kmod_list *entry) +{ + struct kmod_module_version *version; + + if (entry == NULL || entry->data == NULL) + return NULL; + + version = entry->data; + return version->symbol; +} + +KMOD_EXPORT uint64_t kmod_module_version_get_crc(const struct kmod_list *entry) +{ + struct kmod_module_version *version; + + if (entry == NULL || entry->data == NULL) + return 0; + + version = entry->data; + return version->crc; +} + +KMOD_EXPORT void kmod_module_versions_free_list(struct kmod_list *list) +{ + kmod_list_release(list, kmod_module_version_free); +} + +struct kmod_module_symbol { + uint64_t crc; + char symbol[]; +}; + +static struct kmod_module_symbol *kmod_module_symbols_new(uint64_t crc, const char *symbol) +{ + struct kmod_module_symbol *mv; + size_t symbollen = strlen(symbol) + 1; + + mv = malloc(sizeof(struct kmod_module_symbol) + symbollen); + if (mv == NULL) + return NULL; + + mv->crc = crc; + memcpy(mv->symbol, symbol, symbollen); + return mv; +} + +static void kmod_module_symbol_free(struct kmod_module_symbol *symbol) +{ + free(symbol); +} + +KMOD_EXPORT int kmod_module_get_symbols(const struct kmod_module *mod, + struct kmod_list **list) +{ + struct kmod_elf *elf; + struct kmod_modversion *symbols; + int i, count, ret = 0; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_symbols(elf, &symbols); + if (count < 0) + return count; + + for (i = 0; i < count; i++) { + struct kmod_module_symbol *mv; + struct kmod_list *n; + + mv = kmod_module_symbols_new(symbols[i].crc, symbols[i].symbol); + if (mv == NULL) { + ret = -errno; + kmod_module_symbols_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, mv); + if (n != NULL) + *list = n; + else { + kmod_module_symbol_free(mv); + kmod_module_symbols_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(symbols); + return ret; +} + +KMOD_EXPORT const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry) +{ + struct kmod_module_symbol *symbol; + + if (entry == NULL || entry->data == NULL) + return NULL; + + symbol = entry->data; + return symbol->symbol; +} + +KMOD_EXPORT uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry) +{ + struct kmod_module_symbol *symbol; + + if (entry == NULL || entry->data == NULL) + return 0; + + symbol = entry->data; + return symbol->crc; +} + +KMOD_EXPORT void kmod_module_symbols_free_list(struct kmod_list *list) +{ + kmod_list_release(list, kmod_module_symbol_free); +} + +struct kmod_module_dependency_symbol { + uint64_t crc; + uint8_t bind; + char symbol[]; +}; + +// clang-format off +static struct kmod_module_dependency_symbol *kmod_module_dependency_symbols_new(uint64_t crc, uint8_t bind, const char *symbol) +// clang-format on +{ + struct kmod_module_dependency_symbol *mv; + size_t symbollen = strlen(symbol) + 1; + + mv = malloc(sizeof(struct kmod_module_dependency_symbol) + symbollen); + if (mv == NULL) + return NULL; + + mv->crc = crc; + mv->bind = bind; + memcpy(mv->symbol, symbol, symbollen); + return mv; +} + +static void kmod_module_dependency_symbol_free( + struct kmod_module_dependency_symbol *dependency_symbol) +{ + free(dependency_symbol); +} + +KMOD_EXPORT int kmod_module_get_dependency_symbols(const struct kmod_module *mod, + struct kmod_list **list) +{ + struct kmod_elf *elf; + struct kmod_modversion *symbols; + int i, count, ret = 0; + + if (mod == NULL || list == NULL) + return -ENOENT; + + assert(*list == NULL); + + elf = kmod_module_get_elf(mod); + if (elf == NULL) + return -errno; + + count = kmod_elf_get_dependency_symbols(elf, &symbols); + if (count < 0) + return count; + + for (i = 0; i < count; i++) { + struct kmod_module_dependency_symbol *mv; + struct kmod_list *n; + + mv = kmod_module_dependency_symbols_new(symbols[i].crc, symbols[i].bind, + symbols[i].symbol); + if (mv == NULL) { + ret = -errno; + kmod_module_dependency_symbols_free_list(*list); + *list = NULL; + goto list_error; + } + + n = kmod_list_append(*list, mv); + if (n != NULL) + *list = n; + else { + kmod_module_dependency_symbol_free(mv); + kmod_module_dependency_symbols_free_list(*list); + *list = NULL; + ret = -ENOMEM; + goto list_error; + } + } + ret = count; + +list_error: + free(symbols); + return ret; +} + +// clang-format off +KMOD_EXPORT const char *kmod_module_dependency_symbol_get_symbol(const struct kmod_list *entry) +// clang-format on +{ + struct kmod_module_dependency_symbol *dependency_symbol; + + if (entry == NULL || entry->data == NULL) + return NULL; + + dependency_symbol = entry->data; + return dependency_symbol->symbol; +} + +KMOD_EXPORT uint64_t kmod_module_dependency_symbol_get_crc(const struct kmod_list *entry) +{ + struct kmod_module_dependency_symbol *dependency_symbol; + + if (entry == NULL || entry->data == NULL) + return 0; + + dependency_symbol = entry->data; + return dependency_symbol->crc; +} + +KMOD_EXPORT int kmod_module_dependency_symbol_get_bind(const struct kmod_list *entry) +{ + struct kmod_module_dependency_symbol *dependency_symbol; + + if (entry == NULL || entry->data == NULL) + return 0; + + dependency_symbol = entry->data; + return dependency_symbol->bind; +} + +KMOD_EXPORT void kmod_module_dependency_symbols_free_list(struct kmod_list *list) +{ + kmod_list_release(list, kmod_module_dependency_symbol_free); +} diff --git a/libkmod/libkmod-signature.c b/libkmod/libkmod-signature.c new file mode 100644 index 0000000..4f5023e --- /dev/null +++ b/libkmod/libkmod-signature.c @@ -0,0 +1,344 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2013 Michal Marek, SUSE + */ + +#include +#include +#if ENABLE_OPENSSL +#include +#include +#endif +#include +#include +#include + +#include +#include + +#include "libkmod-internal.h" + +/* These types and tables were copied from the 3.7 kernel sources. + * As this is just description of the signature format, it should not be + * considered derived work (so libkmod can use the LGPL license). + */ +enum pkey_algo { + PKEY_ALGO_DSA, + PKEY_ALGO_RSA, + PKEY_ALGO__LAST, +}; + +static const char *const pkey_algo[PKEY_ALGO__LAST] = { + [PKEY_ALGO_DSA] = "DSA", + [PKEY_ALGO_RSA] = "RSA", +}; + +enum pkey_hash_algo { + PKEY_HASH_MD4, + PKEY_HASH_MD5, + PKEY_HASH_SHA1, + PKEY_HASH_RIPE_MD_160, + PKEY_HASH_SHA256, + PKEY_HASH_SHA384, + PKEY_HASH_SHA512, + PKEY_HASH_SHA224, + PKEY_HASH_SM3, + PKEY_HASH__LAST, +}; + +const char *const pkey_hash_algo[PKEY_HASH__LAST] = { + // clang-format off + [PKEY_HASH_MD4] = "md4", + [PKEY_HASH_MD5] = "md5", + [PKEY_HASH_SHA1] = "sha1", + [PKEY_HASH_RIPE_MD_160] = "rmd160", + [PKEY_HASH_SHA256] = "sha256", + [PKEY_HASH_SHA384] = "sha384", + [PKEY_HASH_SHA512] = "sha512", + [PKEY_HASH_SHA224] = "sha224", + [PKEY_HASH_SM3] = "sm3", + // clang-format on +}; + +enum pkey_id_type { + PKEY_ID_PGP, /* OpenPGP generated key ID */ + PKEY_ID_X509, /* X.509 arbitrary subjectKeyIdentifier */ + PKEY_ID_PKCS7, /* Signature in PKCS#7 message */ + PKEY_ID_TYPE__LAST, +}; + +const char *const pkey_id_type[PKEY_ID_TYPE__LAST] = { + [PKEY_ID_PGP] = "PGP", + [PKEY_ID_X509] = "X509", + [PKEY_ID_PKCS7] = "PKCS#7", +}; + +/* + * Module signature information block. + */ +struct module_signature { + uint8_t algo; /* Public-key crypto algorithm [enum pkey_algo] */ + uint8_t hash; /* Digest algorithm [enum pkey_hash_algo] */ + uint8_t id_type; /* Key identifier type [enum pkey_id_type] */ + uint8_t signer_len; /* Length of signer's name */ + uint8_t key_id_len; /* Length of key identifier */ + uint8_t __pad[3]; + uint32_t sig_len; /* Length of signature data (big endian) */ +}; + +static bool fill_default(const char *mem, off_t size, + const struct module_signature *modsig, size_t sig_len, + struct kmod_signature_info *sig_info) +{ + size -= sig_len; + sig_info->sig = mem + size; + sig_info->sig_len = sig_len; + + size -= modsig->key_id_len; + sig_info->key_id = mem + size; + sig_info->key_id_len = modsig->key_id_len; + + size -= modsig->signer_len; + sig_info->signer = mem + size; + sig_info->signer_len = modsig->signer_len; + + sig_info->algo = pkey_algo[modsig->algo]; + sig_info->hash_algo = pkey_hash_algo[modsig->hash]; + sig_info->id_type = pkey_id_type[modsig->id_type]; + + return true; +} + +#if ENABLE_OPENSSL + +struct pkcs7_private { + PKCS7 *pkcs7; + unsigned char *key_id; + BIGNUM *sno; + char *hash_algo; +}; + +static void pkcs7_free(void *s) +{ + struct kmod_signature_info *si = s; + struct pkcs7_private *pvt = si->private; + + PKCS7_free(pvt->pkcs7); + BN_free(pvt->sno); + free(pvt->key_id); + free(pvt->hash_algo); + free(pvt); + si->private = NULL; +} + +static const char *x509_name_to_str(X509_NAME *name) +{ + int i; + X509_NAME_ENTRY *e; + ASN1_STRING *d; + ASN1_OBJECT *o; + int nid = -1; + const char *str; + + for (i = 0; i < X509_NAME_entry_count(name); i++) { + e = X509_NAME_get_entry(name, i); + o = X509_NAME_ENTRY_get_object(e); + nid = OBJ_obj2nid(o); + if (nid == NID_commonName) + break; + } + if (nid == -1) + return NULL; + + d = X509_NAME_ENTRY_get_data(e); + str = (const char *)ASN1_STRING_get0_data(d); + + return str; +} + +static bool fill_pkcs7(const char *mem, off_t size, const struct module_signature *modsig, + size_t sig_len, struct kmod_signature_info *sig_info) +{ + const char *pkcs7_raw; + PKCS7 *pkcs7; + STACK_OF(PKCS7_SIGNER_INFO) * sis; + PKCS7_SIGNER_INFO *si; + PKCS7_ISSUER_AND_SERIAL *is; + X509_NAME *issuer; + ASN1_INTEGER *sno; + ASN1_OCTET_STRING *sig; + BIGNUM *sno_bn; + X509_ALGOR *dig_alg; + X509_ALGOR *sig_alg; + const ASN1_OBJECT *o; + BIO *in; + int len; + unsigned char *key_id_str; + struct pkcs7_private *pvt; + const char *issuer_str; + char *hash_algo; + int hash_algo_len; + + size -= sig_len; + pkcs7_raw = mem + size; + + in = BIO_new_mem_buf(pkcs7_raw, sig_len); + + pkcs7 = d2i_PKCS7_bio(in, NULL); + if (pkcs7 == NULL) { + BIO_free(in); + return false; + } + + BIO_free(in); + + sis = PKCS7_get_signer_info(pkcs7); + if (sis == NULL) + goto err; + + si = sk_PKCS7_SIGNER_INFO_value(sis, 0); + if (si == NULL) + goto err; + + is = si->issuer_and_serial; + if (is == NULL) + goto err; + issuer = is->issuer; + sno = is->serial; + + sig = si->enc_digest; + if (sig == NULL) + goto err; + + PKCS7_SIGNER_INFO_get0_algs(si, NULL, &dig_alg, &sig_alg); + + sig_info->sig = (const char *)ASN1_STRING_get0_data(sig); + sig_info->sig_len = ASN1_STRING_length(sig); + + sno_bn = ASN1_INTEGER_to_BN(sno, NULL); + if (sno_bn == NULL) + goto err; + + len = BN_num_bytes(sno_bn); + key_id_str = malloc(len); + if (key_id_str == NULL) + goto err2; + BN_bn2bin(sno_bn, key_id_str); + + sig_info->key_id = (const char *)key_id_str; + sig_info->key_id_len = len; + + issuer_str = x509_name_to_str(issuer); + if (issuer_str != NULL) { + sig_info->signer = issuer_str; + sig_info->signer_len = strlen(issuer_str); + } + + X509_ALGOR_get0(&o, NULL, NULL, dig_alg); + + // Use OBJ_obj2txt to calculate string length + hash_algo_len = OBJ_obj2txt(NULL, 0, o, 0); + if (hash_algo_len < 0) + goto err3; + hash_algo = malloc(hash_algo_len + 1); + if (hash_algo == NULL) + goto err3; + hash_algo_len = OBJ_obj2txt(hash_algo, hash_algo_len + 1, o, 0); + if (hash_algo_len < 0) + goto err4; + + // Assign libcrypto hash algo string or number + sig_info->hash_algo = hash_algo; + + sig_info->id_type = pkey_id_type[modsig->id_type]; + + pvt = malloc(sizeof(*pvt)); + if (pvt == NULL) + goto err4; + + pvt->pkcs7 = pkcs7; + pvt->key_id = key_id_str; + pvt->sno = sno_bn; + pvt->hash_algo = hash_algo; + sig_info->private = pvt; + + sig_info->free = pkcs7_free; + + return true; +err4: + free(hash_algo); +err3: + free(key_id_str); +err2: + BN_free(sno_bn); +err: + PKCS7_free(pkcs7); + return false; +} + +#else /* ENABLE OPENSSL */ + +static bool fill_pkcs7(const char *mem, off_t size, const struct module_signature *modsig, + size_t sig_len, struct kmod_signature_info *sig_info) +{ + sig_info->hash_algo = "unknown"; + sig_info->id_type = pkey_id_type[modsig->id_type]; + return true; +} + +#endif /* ENABLE OPENSSL */ + +#define SIG_MAGIC "~Module signature appended~\n" + +/* + * A signed module has the following layout: + * + * [ module ] + * [ signer's name ] + * [ key identifier ] + * [ signature data ] + * [ struct module_signature ] + * [ SIG_MAGIC ] + */ + +bool kmod_module_signature_info(const struct kmod_file *file, + struct kmod_signature_info *sig_info) +{ + const char *mem; + off_t size; + struct module_signature modsig; + size_t sig_len; + + size = kmod_file_get_size(file); + mem = kmod_file_get_contents(file); + if (size < (off_t)strlen(SIG_MAGIC)) + return false; + size -= strlen(SIG_MAGIC); + if (memcmp(SIG_MAGIC, mem + size, strlen(SIG_MAGIC)) != 0) + return false; + + if (size < (off_t)sizeof(struct module_signature)) + return false; + size -= sizeof(struct module_signature); + memcpy(&modsig, mem + size, sizeof(struct module_signature)); + if (modsig.algo >= PKEY_ALGO__LAST || modsig.hash >= PKEY_HASH__LAST || + modsig.id_type >= PKEY_ID_TYPE__LAST) + return false; + sig_len = be32toh(modsig.sig_len); + if (sig_len == 0 || + size < (int64_t)sig_len + modsig.signer_len + modsig.key_id_len) + return false; + + switch (modsig.id_type) { + case PKEY_ID_PKCS7: + return fill_pkcs7(mem, size, &modsig, sig_len, sig_info); + default: + return fill_default(mem, size, &modsig, sig_len, sig_info); + } +} + +void kmod_module_signature_info_free(struct kmod_signature_info *sig_info) +{ + if (sig_info->free) + sig_info->free(sig_info); +} diff --git a/libkmod/libkmod.c b/libkmod/libkmod.c new file mode 100644 index 0000000..f4510d0 --- /dev/null +++ b/libkmod/libkmod.c @@ -0,0 +1,856 @@ +// SPDX-License-Identifier: LGPL-2.1-or-later +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "libkmod.h" +#include "libkmod-internal.h" +#include "libkmod-index.h" + +#define KMOD_HASH_SIZE (256) +#define KMOD_LRU_MAX (128) +#define _KMOD_INDEX_MODULES_SIZE KMOD_INDEX_MODULES_BUILTIN + 1 + +static const struct { + const char *fn; + bool alias_prefix; +} index_files[] = { + // clang-format off + [KMOD_INDEX_MODULES_DEP] = { .fn = "modules.dep" }, + [KMOD_INDEX_MODULES_ALIAS] = { .fn = "modules.alias", .alias_prefix = true }, + [KMOD_INDEX_MODULES_SYMBOL] = { .fn = "modules.symbols", .alias_prefix = true }, + [KMOD_INDEX_MODULES_BUILTIN_ALIAS] = { .fn = "modules.builtin.alias" }, + [KMOD_INDEX_MODULES_BUILTIN] = { .fn = "modules.builtin" }, + // clang-format on +}; + +static const char *const default_config_paths[] = { + // clang-format off + SYSCONFDIR "/modprobe.d", + "/run/modprobe.d", + "/usr/local/lib/modprobe.d", + DISTCONFDIR "/modprobe.d", + "/lib/modprobe.d", + NULL, + // clang-format on +}; + +struct kmod_ctx { + int refcount; + int log_priority; + void (*log_fn)(void *data, int priority, const char *file, int line, + const char *fn, const char *format, va_list args); + void *log_data; + const void *userdata; + char *dirname; + enum kmod_file_compression_type kernel_compression; + struct kmod_config *config; + struct hash *modules_by_name; + struct index_mm *indexes[_KMOD_INDEX_MODULES_SIZE]; + unsigned long long indexes_stamp[_KMOD_INDEX_MODULES_SIZE]; +}; + +void kmod_log(const struct kmod_ctx *ctx, int priority, const char *file, int line, + const char *fn, const char *format, ...) +{ + va_list args; + + if (ctx->log_fn == NULL) + return; + + va_start(args, format); + ctx->log_fn(ctx->log_data, priority, file, line, fn, format, args); + va_end(args); +} + +_printf_format_(6, 0) static void log_filep(void *data, int priority, const char *file, + int line, const char *fn, const char *format, + va_list args) +{ + FILE *fp = data; + char buf[16]; + const char *priname; + switch (priority) { + case LOG_EMERG: + priname = "EMERGENCY"; + break; + case LOG_ALERT: + priname = "ALERT"; + break; + case LOG_CRIT: + priname = "CRITICAL"; + break; + case LOG_ERR: + priname = "ERROR"; + break; + case LOG_WARNING: + priname = "WARNING"; + break; + case LOG_NOTICE: + priname = "NOTICE"; + break; + case LOG_INFO: + priname = "INFO"; + break; + case LOG_DEBUG: + priname = "DEBUG"; + break; + default: + snprintf(buf, sizeof(buf), "L:%d", priority); + priname = buf; + } + if (ENABLE_DEBUG == 1) + fprintf(fp, "libkmod: %s %s:%d %s: ", priname, file, line, fn); + else + fprintf(fp, "libkmod: %s: %s: ", priname, fn); + vfprintf(fp, format, args); +} + +KMOD_EXPORT const char *kmod_get_dirname(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + return ctx->dirname; +} + +KMOD_EXPORT void *kmod_get_userdata(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + return (void *)ctx->userdata; +} + +KMOD_EXPORT void kmod_set_userdata(struct kmod_ctx *ctx, const void *userdata) +{ + if (ctx == NULL) + return; + ctx->userdata = userdata; +} + +static int log_priority(const char *priority) +{ + char *endptr; + int prio; + + prio = strtol(priority, &endptr, 10); + if (endptr[0] == '\0' || isspace(endptr[0])) + return prio; + if (strncmp(priority, "err", 3) == 0) + return LOG_ERR; + if (strncmp(priority, "info", 4) == 0) + return LOG_INFO; + if (strncmp(priority, "debug", 5) == 0) + return LOG_DEBUG; + return 0; +} + +static const char *dirname_default_prefix = MODULE_DIRECTORY; + +static char *get_kernel_release(const char *dirname) +{ + struct utsname u; + char *p; + + if (dirname != NULL) + return path_make_absolute_cwd(dirname); + + if (uname(&u) < 0) + return NULL; + + if (asprintf(&p, "%s/%s", dirname_default_prefix, u.release) < 0) + return NULL; + + return p; +} + +static enum kmod_file_compression_type get_kernel_compression(struct kmod_ctx *ctx) +{ + const char *path = "/sys/module/compression"; + char buf[16]; + int fd; + int err; + + fd = open(path, O_RDONLY | O_CLOEXEC); + if (fd < 0) { + /* Not having the file is not an error: kernel may be too old */ + DBG(ctx, "could not open '%s' for reading: %m\n", path); + return KMOD_FILE_COMPRESSION_NONE; + } + + err = read_str_safe(fd, buf, sizeof(buf)); + close(fd); + if (err < 0) { + ERR(ctx, "could not read from '%s': %s\n", path, strerror(-err)); + return KMOD_FILE_COMPRESSION_NONE; + } + + if (streq(buf, "zstd\n")) + return KMOD_FILE_COMPRESSION_ZSTD; + else if (streq(buf, "xz\n")) + return KMOD_FILE_COMPRESSION_XZ; + else if (streq(buf, "gzip\n")) + return KMOD_FILE_COMPRESSION_ZLIB; + + ERR(ctx, "unknown kernel compression %s", buf); + + return KMOD_FILE_COMPRESSION_NONE; +} + +KMOD_EXPORT struct kmod_ctx *kmod_new(const char *dirname, const char *const *config_paths) +{ + const char *env; + struct kmod_ctx *ctx; + int err; + + ctx = calloc(1, sizeof(struct kmod_ctx)); + if (!ctx) + return NULL; + + ctx->refcount = 1; + ctx->log_fn = log_filep; + ctx->log_data = stderr; + ctx->log_priority = LOG_ERR; + + ctx->dirname = get_kernel_release(dirname); + if (ctx->dirname == NULL) { + ERR(ctx, "could not retrieve directory\n"); + goto fail; + } + + /* environment overwrites config */ + env = secure_getenv("KMOD_LOG"); + if (env != NULL) + kmod_set_log_priority(ctx, log_priority(env)); + + ctx->kernel_compression = get_kernel_compression(ctx); + + if (config_paths == NULL) + config_paths = default_config_paths; + err = kmod_config_new(ctx, &ctx->config, config_paths); + if (err < 0) { + ERR(ctx, "could not create config\n"); + goto fail; + } + + ctx->modules_by_name = hash_new(KMOD_HASH_SIZE, NULL); + if (ctx->modules_by_name == NULL) { + ERR(ctx, "could not create by-name hash\n"); + goto fail; + } + + INFO(ctx, "ctx %p created\n", ctx); + DBG(ctx, "log_priority=%d\n", ctx->log_priority); + + return ctx; + +fail: + free(ctx->modules_by_name); + free(ctx->dirname); + free(ctx); + return NULL; +} + +KMOD_EXPORT struct kmod_ctx *kmod_ref(struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + ctx->refcount++; + return ctx; +} + +KMOD_EXPORT struct kmod_ctx *kmod_unref(struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return NULL; + + if (--ctx->refcount > 0) + return ctx; + + INFO(ctx, "context %p released\n", ctx); + + kmod_unload_resources(ctx); + hash_free(ctx->modules_by_name); + free(ctx->dirname); + if (ctx->config) + kmod_config_free(ctx->config); + + free(ctx); + return NULL; +} + +KMOD_EXPORT void kmod_set_log_fn(struct kmod_ctx *ctx, + void (*log_fn)(void *data, int priority, + const char *file, int line, const char *fn, + const char *format, va_list args), + const void *data) +{ + if (ctx == NULL) + return; + ctx->log_fn = log_fn; + ctx->log_data = (void *)data; + INFO(ctx, "custom logging function %p registered\n", log_fn); +} + +KMOD_EXPORT int kmod_get_log_priority(const struct kmod_ctx *ctx) +{ + if (ctx == NULL) + return -1; + return ctx->log_priority; +} + +KMOD_EXPORT void kmod_set_log_priority(struct kmod_ctx *ctx, int priority) +{ + if (ctx == NULL) + return; + ctx->log_priority = priority; +} + +struct kmod_module *kmod_pool_get_module(struct kmod_ctx *ctx, const char *key) +{ + struct kmod_module *mod; + + mod = hash_find(ctx->modules_by_name, key); + + DBG(ctx, "get module name='%s' found=%p\n", key, mod); + + return mod; +} + +int kmod_pool_add_module(struct kmod_ctx *ctx, struct kmod_module *mod, const char *key) +{ + DBG(ctx, "add %p key='%s'\n", mod, key); + + return hash_add(ctx->modules_by_name, key, mod); +} + +void kmod_pool_del_module(struct kmod_ctx *ctx, struct kmod_module *mod, const char *key) +{ + DBG(ctx, "del %p key='%s'\n", mod, key); + + hash_del(ctx->modules_by_name, key); +} + +static int kmod_lookup_alias_from_alias_bin(struct kmod_ctx *ctx, + enum kmod_index index_number, + const char *name, struct kmod_list **list) +{ + int err, nmatch = 0; + struct index_file *idx; + struct index_value *realnames, *realname; + + assert(*list == NULL); + + if (ctx->indexes[index_number] != NULL) { + DBG(ctx, "use mmapped index '%s' for name=%s\n", + index_files[index_number].fn, name); + realnames = index_mm_searchwild(ctx->indexes[index_number], name); + } else { + char fn[PATH_MAX]; + + snprintf(fn, sizeof(fn), "%s/%s.bin", ctx->dirname, + index_files[index_number].fn); + + DBG(ctx, "file=%s name=%s\n", fn, name); + + idx = index_file_open(fn); + if (idx == NULL) + return -ENOSYS; + + realnames = index_searchwild(idx, name); + index_file_close(idx); + } + + for (realname = realnames; realname; realname = realname->next) { + struct kmod_module *mod; + struct kmod_list *node; + + err = kmod_module_new_from_alias(ctx, name, realname->value, &mod); + if (err < 0) { + ERR(ctx, "Could not create module for alias=%s realname=%s: %s\n", + name, realname->value, strerror(-err)); + goto fail; + } + + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + kmod_module_unref(mod); + err = -ENOMEM; + goto fail; + } + *list = node; + nmatch++; + } + + index_values_free(realnames); + return nmatch; + +fail: + kmod_list_release(*list, kmod_module_unref); + index_values_free(realnames); + return err; +} + +int kmod_lookup_alias_from_symbols_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + if (!strstartswith(name, "symbol:")) + return 0; + + return kmod_lookup_alias_from_alias_bin(ctx, KMOD_INDEX_MODULES_SYMBOL, name, + list); +} + +int kmod_lookup_alias_from_aliases_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + return kmod_lookup_alias_from_alias_bin(ctx, KMOD_INDEX_MODULES_ALIAS, name, list); +} + +static char *lookup_file(struct kmod_ctx *ctx, enum kmod_index index_number, + const char *name) +{ + char *line; + + if (ctx->indexes[index_number]) { + DBG(ctx, "use mmapped index '%s' modname=%s\n", + index_files[index_number].fn, name); + line = index_mm_search(ctx->indexes[index_number], name); + } else { + struct index_file *idx; + char fn[PATH_MAX]; + + snprintf(fn, sizeof(fn), "%s/%s.bin", ctx->dirname, + index_files[index_number].fn); + DBG(ctx, "file=%s modname=%s\n", fn, name); + + idx = index_file_open(fn); + if (idx == NULL) { + DBG(ctx, "could not open builtin file '%s'\n", fn); + return NULL; + } + + line = index_search(idx, name); + index_file_close(idx); + } + + return line; +} + +static bool lookup_builtin_file(struct kmod_ctx *ctx, const char *name) +{ + _cleanup_free_ char *line = lookup_file(ctx, KMOD_INDEX_MODULES_BUILTIN, name); + + return line; +} + +int kmod_lookup_alias_from_kernel_builtin_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + struct kmod_list *l; + int ret; + + ret = kmod_lookup_alias_from_alias_bin(ctx, KMOD_INDEX_MODULES_BUILTIN_ALIAS, + name, list); + + kmod_list_foreach(l, *list) { + struct kmod_module *mod = l->data; + kmod_module_set_builtin(mod, true); + } + + return ret; +} + +int kmod_lookup_alias_from_builtin_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + assert(*list == NULL); + + if (lookup_builtin_file(ctx, name)) { + struct kmod_module *mod; + struct kmod_list *node; + int err; + + err = kmod_module_new_from_name(ctx, name, &mod); + if (err < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", name, + strerror(-err)); + return err; + } + + /* already mark it as builtin since it's being created from + * this index */ + kmod_module_set_builtin(mod, true); + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + kmod_module_unref(mod); + return -ENOMEM; + } + *list = node; + } + + return 0; +} + +bool kmod_lookup_alias_is_builtin(struct kmod_ctx *ctx, const char *name) +{ + return lookup_builtin_file(ctx, name); +} + +char *kmod_search_moddep(struct kmod_ctx *ctx, const char *name) +{ + return lookup_file(ctx, KMOD_INDEX_MODULES_DEP, name); +} + +int kmod_lookup_alias_from_moddep_file(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + char *line; + int n = 0; + + assert(*list == NULL); + + /* + * Module names do not contain ':'. Return early if we know it will + * not be found. + */ + if (strchr(name, ':')) + return 0; + + line = kmod_search_moddep(ctx, name); + if (line != NULL) { + struct kmod_module *mod; + struct kmod_list *node; + + n = kmod_module_new_from_name(ctx, name, &mod); + if (n < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", name, + strerror(-n)); + goto finish; + } + + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + kmod_module_unref(mod); + n = -ENOMEM; + goto finish; + } + *list = node; + kmod_module_parse_depline(mod, line); + } + +finish: + free(line); + + return n; +} + +int kmod_lookup_alias_from_config(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + struct kmod_config *config = ctx->config; + struct kmod_list *l; + int err, nmatch = 0; + + assert(*list == NULL); + + kmod_list_foreach(l, config->aliases) { + const char *aliasname = kmod_alias_get_name(l); + const char *modname = kmod_alias_get_modname(l); + + if (fnmatch(aliasname, name, 0) == 0) { + struct kmod_module *mod; + struct kmod_list *node; + + err = kmod_module_new_from_alias(ctx, aliasname, modname, &mod); + if (err < 0) { + ERR(ctx, + "Could not create module for alias=%s modname=%s: %s\n", + name, modname, strerror(-err)); + goto fail; + } + + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + kmod_module_unref(mod); + err = -ENOMEM; + goto fail; + } + *list = node; + nmatch++; + } + } + + return nmatch; + +fail: + kmod_list_release(*list, kmod_module_unref); + return err; +} + +int kmod_lookup_alias_from_commands(struct kmod_ctx *ctx, const char *name, + struct kmod_list **list) +{ + struct kmod_config *config = ctx->config; + struct kmod_list *l, *node; + int err, nmatch = 0; + + assert(*list == NULL); + + kmod_list_foreach(l, config->install_commands) { + const char *modname = kmod_command_get_modname(l); + + if (streq(modname, name)) { + const char *cmd = kmod_command_get_command(l); + struct kmod_module *mod; + + err = kmod_module_new_from_name(ctx, modname, &mod); + if (err < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", + modname, strerror(-err)); + return err; + } + + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + kmod_module_unref(mod); + return -ENOMEM; + } + + *list = node; + nmatch = 1; + + kmod_module_set_install_commands(mod, cmd); + + /* + * match only the first one, like modprobe from + * module-init-tools does + */ + break; + } + } + + if (nmatch) + return nmatch; + + kmod_list_foreach(l, config->remove_commands) { + const char *modname = kmod_command_get_modname(l); + + if (streq(modname, name)) { + const char *cmd = kmod_command_get_command(l); + struct kmod_module *mod; + + err = kmod_module_new_from_name(ctx, modname, &mod); + if (err < 0) { + ERR(ctx, "Could not create module from name %s: %s\n", + modname, strerror(-err)); + return err; + } + + node = kmod_list_append(*list, mod); + if (node == NULL) { + ERR(ctx, "out of memory\n"); + kmod_module_unref(mod); + return -ENOMEM; + } + + *list = node; + nmatch = 1; + + kmod_module_set_remove_commands(mod, cmd); + + /* + * match only the first one, like modprobe from + * module-init-tools does + */ + break; + } + } + + return nmatch; +} + +void kmod_set_modules_visited(struct kmod_ctx *ctx, bool visited) +{ + struct hash_iter iter; + const void *v; + + hash_iter_init(ctx->modules_by_name, &iter); + while (hash_iter_next(&iter, NULL, &v)) + kmod_module_set_visited((struct kmod_module *)v, visited); +} + +void kmod_set_modules_required(struct kmod_ctx *ctx, bool required) +{ + struct hash_iter iter; + const void *v; + + hash_iter_init(ctx->modules_by_name, &iter); + while (hash_iter_next(&iter, NULL, &v)) + kmod_module_set_required((struct kmod_module *)v, required); +} + +static bool is_cache_invalid(const char *path, unsigned long long stamp) +{ + struct stat st; + + if (stat(path, &st) < 0) + return true; + + if (stamp != stat_mstamp(&st)) + return true; + + return false; +} + +KMOD_EXPORT int kmod_validate_resources(struct kmod_ctx *ctx) +{ + struct kmod_list *l; + size_t i; + + if (ctx == NULL || ctx->config == NULL) + return KMOD_RESOURCES_MUST_RECREATE; + + kmod_list_foreach(l, ctx->config->paths) { + struct kmod_config_path *cf = l->data; + + if (is_cache_invalid(cf->path, cf->stamp)) + return KMOD_RESOURCES_MUST_RECREATE; + } + + for (i = 0; i < _KMOD_INDEX_MODULES_SIZE; i++) { + char path[PATH_MAX]; + + if (ctx->indexes[i] == NULL) + continue; + + snprintf(path, sizeof(path), "%s/%s.bin", ctx->dirname, index_files[i].fn); + + if (is_cache_invalid(path, ctx->indexes_stamp[i])) + return KMOD_RESOURCES_MUST_RELOAD; + } + + return KMOD_RESOURCES_OK; +} + +KMOD_EXPORT int kmod_load_resources(struct kmod_ctx *ctx) +{ + int ret = 0; + size_t i; + + if (ctx == NULL) + return -ENOENT; + + for (i = 0; i < _KMOD_INDEX_MODULES_SIZE; i++) { + char path[PATH_MAX]; + + if (ctx->indexes[i] != NULL) { + INFO(ctx, "Index %s already loaded\n", index_files[i].fn); + continue; + } + + snprintf(path, sizeof(path), "%s/%s.bin", ctx->dirname, index_files[i].fn); + ret = index_mm_open(ctx, path, &ctx->indexes_stamp[i], &ctx->indexes[i]); + + /* + * modules.builtin.alias are considered optional since it's + * recently added and older installations may not have it; + * we allow failing for any reason + */ + if (ret) { + if (i != KMOD_INDEX_MODULES_BUILTIN_ALIAS) + break; + ret = 0; + } + } + + if (ret) + kmod_unload_resources(ctx); + + return ret; +} + +KMOD_EXPORT void kmod_unload_resources(struct kmod_ctx *ctx) +{ + size_t i; + + if (ctx == NULL) + return; + + for (i = 0; i < _KMOD_INDEX_MODULES_SIZE; i++) { + if (ctx->indexes[i] != NULL) { + index_mm_close(ctx->indexes[i]); + ctx->indexes[i] = NULL; + ctx->indexes_stamp[i] = 0; + } + } +} + +KMOD_EXPORT int kmod_dump_index(struct kmod_ctx *ctx, enum kmod_index type, int fd) +{ + if (ctx == NULL) + return -ENOSYS; + +#if defined(__clang__) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wtautological-unsigned-enum-zero-compare" +#endif + if (type < 0 || type >= _KMOD_INDEX_MODULES_SIZE) + return -ENOENT; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + if (ctx->indexes[type] != NULL) { + DBG(ctx, "use mmapped index '%s'\n", index_files[type].fn); + index_mm_dump(ctx->indexes[type], fd, index_files[type].alias_prefix); + } else { + char fn[PATH_MAX]; + struct index_file *idx; + + snprintf(fn, sizeof(fn), "%s/%s.bin", ctx->dirname, index_files[type].fn); + + DBG(ctx, "file=%s\n", fn); + + idx = index_file_open(fn); + if (idx == NULL) + return -ENOSYS; + + index_dump(idx, fd, index_files[type].alias_prefix); + index_file_close(idx); + } + + return 0; +} + +const struct kmod_config *kmod_get_config(const struct kmod_ctx *ctx) +{ + return ctx->config; +} + +enum kmod_file_compression_type kmod_get_kernel_compression(const struct kmod_ctx *ctx) +{ + return ctx->kernel_compression; +} diff --git a/libkmod/libkmod.h b/libkmod/libkmod.h new file mode 100644 index 0000000..1c0d118 --- /dev/null +++ b/libkmod/libkmod.h @@ -0,0 +1,1466 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/* + * Copyright (C) 2011-2013 ProFUSION embedded systems + */ + +#pragma once +#ifndef _LIBKMOD_H_ +#define _LIBKMOD_H_ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * SECTION:libkmod + * @short_description: libkmod context + * + * The context contains the default values for the library user, + * and is passed to all library operations. + */ + +/** + * kmod_ctx: + * + * Opaque object representing the library context. + */ +struct kmod_ctx; + +/** + * kmod_new: + * @dirname: what to consider as linux module's directory, if NULL + * defaults to $MODULE_DIRECTORY/`uname -r`. If it's relative, + * it's treated as relative to the current working directory. + * Otherwise, give an absolute dirname. + * @config_paths: ordered array of paths (directories or files) where + * to load from user-defined configuration parameters such as + * alias, blacklists, commands (install, remove). If NULL + * defaults to /etc/modprobe.d, /run/modprobe.d, + * /usr/local/lib/modprobe.d, DISTCONFDIR/modprobe.d, and + * /lib/modprobe.d. Give an empty vector if configuration should + * not be read. This array must be null terminated. + * + * Create kmod library context. This reads the kmod configuration + * and fills in the default values. + * + * The initial refcount is 1, and needs to be decremented to + * release the resources of the kmod library context. + * + * Returns: a new kmod library context + * + * Since: 1 + */ +struct kmod_ctx *kmod_new(const char *dirname, const char *const *config_paths); + +/** + * kmod_ref: + * @ctx: kmod library context + * + * Take a reference of the kmod library context. + * + * Returns: the passed kmod library context + * + * Since: 1 + */ +struct kmod_ctx *kmod_ref(struct kmod_ctx *ctx); + +/** + * kmod_unref: + * @ctx: kmod library context + * + * Drop a reference of the kmod library context. If the refcount + * reaches zero, the resources of the context will be released. + * + * Returns: the passed kmod library context or NULL if it's freed + * + * Since: 1 + */ +struct kmod_ctx *kmod_unref(struct kmod_ctx *ctx); + +/** + * kmod_load_resources: + * @ctx: kmod library context + * + * Load indexes and keep them open in @ctx. This way it's faster to lookup + * information within the indexes. If this function is not called before a + * search, the necessary index is always opened and closed. + * + * If user will do more than one or two lookups, insertions, deletions, most + * likely it's good to call this function first. Particularly in a daemon like + * udev that on boot issues hundreds of calls to lookup the index, calling + * this function will speedup the searches. + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 1 + */ +int kmod_load_resources(struct kmod_ctx *ctx); + +/** + * kmod_unload_resources: + * @ctx: kmod library context + * + * Unload all the indexes. This will free the resources to maintain the index + * open and all subsequent searches will need to open and close the index. + * + * User is free to call kmod_load_resources() and kmod_unload_resources() as + * many times as wanted during the lifecycle of @ctx. For example, if a daemon + * knows that when starting up it will lookup a lot of modules, it could call + * kmod_load_resources() and after the first burst of searches is gone, it + * could free the resources by calling kmod_unload_resources(). + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 1 + */ +void kmod_unload_resources(struct kmod_ctx *ctx); + +/** + * kmod_resources: + * @KMOD_RESOURCES_OK: resources are valid + * @KMOD_RESOURCES_MUST_RELOAD: resources are not valid; to resolve call + * kmod_unload_resources() and kmod_load_resources() + * @KMOD_RESOURCES_MUST_RECREATE: resources are not valid; to resolve @ctx must + * be re-created. + * + * The validity state of the current libkmod resources, returned by + * kmod_validate_resources(). + */ +enum kmod_resources { + KMOD_RESOURCES_OK = 0, + KMOD_RESOURCES_MUST_RELOAD = 1, + KMOD_RESOURCES_MUST_RECREATE = 2, +}; + +/** + * kmod_validate_resources: + * @ctx: kmod library context + * + * Check if indexes and configuration files changed on disk and the current + * context is not valid anymore. + * + * Returns: the resources state, valid states are #kmod_resources. + * + * Since: 3 + */ +int kmod_validate_resources(struct kmod_ctx *ctx); + +/** + * kmod_index: + * @KMOD_INDEX_MODULES_DEP: index of module dependencies + * @KMOD_INDEX_MODULES_ALIAS: index of module aliases + * @KMOD_INDEX_MODULES_SYMBOL: index of symbol aliases + * @KMOD_INDEX_MODULES_BUILTIN_ALIAS: index of builtin module aliases + * @KMOD_INDEX_MODULES_BUILTIN: index of builtin module + * @_KMOD_INDEX_PAD: DO NOT USE; padding to make sure enum is not mapped to char + * + * The (module) index type, used by kmod_dump_index(). + */ +enum kmod_index { + KMOD_INDEX_MODULES_DEP = 0, + KMOD_INDEX_MODULES_ALIAS, + KMOD_INDEX_MODULES_SYMBOL, + KMOD_INDEX_MODULES_BUILTIN_ALIAS, + KMOD_INDEX_MODULES_BUILTIN, + _KMOD_INDEX_PAD = 1U << 31, +}; + +/** + * kmod_dump_index: + * @ctx: kmod library context + * @type: index to dump, valid indexes are #kmod_index + * @fd: file descriptor to dump index to + * + * Dump index to file descriptor. Note that this function doesn't use stdio.h + * so call fflush() before calling this function to be sure data is written in + * order. + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 4 + */ +int kmod_dump_index(struct kmod_ctx *ctx, enum kmod_index type, int fd); + +/** + * kmod_set_log_priority: + * @ctx: kmod library context + * @priority: the new logging priority + * + * Set the current logging priority, as defined in syslog.h(0P). The value + * controls which messages are logged. + * + * Since: 1 + */ +void kmod_set_log_priority(struct kmod_ctx *ctx, int priority); + +/** + * kmod_get_log_priority: + * @ctx: kmod library context + * + * Get the current logging priority, as defined in syslog.h(0P). + * + * Returns: the current logging priority + * + * Since: 1 + */ +int kmod_get_log_priority(const struct kmod_ctx *ctx); + +/** + * kmod_set_log_fn: + * @ctx: kmod library context + * @log_fn: function to be called for logging messages + * @data: data to pass to log function + * + * The built-in logging writes to stderr. It can be + * overridden by a custom function, to plug log messages + * into the user's logging functionality. + * + * Since: 1 + */ +void kmod_set_log_fn(struct kmod_ctx *ctx, + void (*log_fn)(void *log_data, int priority, const char *file, + int line, const char *fn, const char *format, + va_list args), + const void *data); + +/** + * kmod_set_userdata: + * @ctx: kmod library context + * @userdata: data pointer + * + * Store custom @userdata in the library context. + * + * Since: 1 + */ +void kmod_set_userdata(struct kmod_ctx *ctx, const void *userdata); + +/** + * kmod_get_userdata: + * @ctx: kmod library context + * + * Retrieve stored data pointer from library context. This might be useful + * to access from callbacks. + * + * Returns: stored userdata + * + * Since: 1 + */ +void *kmod_get_userdata(const struct kmod_ctx *ctx); + +/** + * kmod_get_dirname: + * @ctx: kmod library context + * + * Retrieve the absolute path used for linux modules in this context. The path + * is computed from the arguments to kmod_new(). + * + * Since: 22 + */ +const char *kmod_get_dirname(const struct kmod_ctx *ctx); + +/** + * SECTION:libkmod-list + * @short_description: general purpose list + * + * Access to kmod generated lists. + */ + +/** + * kmod_list: + * + * Opaque object for a circular (doubly linked) list. + */ +struct kmod_list; + +/** + * kmod_list_foreach: + * @curr: the current node in the list + * @list: the head of the list + * + * Iterate over the list @list. + */ +#define kmod_list_foreach(curr, list) \ + for (curr = list; curr != NULL; curr = kmod_list_next(list, curr)) + +/** + * kmod_list_foreach_reverse: + * @curr: the current node in the list + * @list: the head of the list + * + * Iterate in reverse over the list @list. + */ +#define kmod_list_foreach_reverse(curr, list) \ + for (curr = kmod_list_last(list); curr != NULL; curr = kmod_list_prev(list, curr)) + +/** + * kmod_list_last: + * @list: the head of the list + * + * Get the last element of the @list. As @list is a circular list, + * this is a cheap operation O(1) with the last element being the + * previous element. + * + * If the list has a single element it will return the list itself (as + * expected, and this is what differentiates from kmod_list_prev()). + * + * Returns: last node at @list or NULL if the list is empty. + * + * Since: 2 + */ +struct kmod_list *kmod_list_last(const struct kmod_list *list); + +/** + * kmod_list_next: + * @list: the head of the list + * @curr: the current node in the list + * + * Get the next node in @list relative to @curr as if @list was not a circular + * list. I.e. calling this function in the last node of the list returns + * NULL.. It can be used to iterate a list by checking for NULL return to know + * when all elements were iterated. + * + * Returns: node next to @curr or NULL if either this node is the last of or + * list is empty. + * + * Since: 1 + */ +struct kmod_list *kmod_list_next(const struct kmod_list *list, + const struct kmod_list *curr); + +/** + * kmod_list_prev: + * @list: the head of the list + * @curr: the current node in the list + * + * Get the previous node in @list relative to @curr as if @list was not a + * circular list. I.e.: the previous of the head is NULL. It can be used to + * iterate a list by checking for NULL return to know when all elements were + * iterated. + * + * Returns: node previous to @curr or NULL if either this node is the head of + * the list or the list is empty. + * + * Since: 1 + */ +struct kmod_list *kmod_list_prev(const struct kmod_list *list, + const struct kmod_list *curr); + +/** + * SECTION:libkmod-config + * @short_description: retrieve current libkmod configuration + * + * Access to configuration lists - it allows to get each configuration's + * key/value stored by kmod. + */ + +/** + * kmod_config_iter: + * + * Opaque object for iterating and retrieving configuration information. + */ +struct kmod_config_iter; + +/** + * kmod_config_get_blacklists: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the blacklist maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the blacklists or NULL on failure. Free it + * with kmod_config_iter_free_iter(). + * + * Since: 4 + */ +struct kmod_config_iter *kmod_config_get_blacklists(const struct kmod_ctx *ctx); + +/** + * kmod_config_get_install_commands: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the install commands maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the install commands or NULL on failure. Free + * it with kmod_config_iter_free_iter(). + * + * Since: 4 + */ +struct kmod_config_iter *kmod_config_get_install_commands(const struct kmod_ctx *ctx); + +/** + * kmod_config_get_remove_commands: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the remove commands maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the remove commands or NULL on failure. Free + * it with kmod_config_iter_free_iter(). + * + * Since: 4 + */ +struct kmod_config_iter *kmod_config_get_remove_commands(const struct kmod_ctx *ctx); + +/** + * kmod_config_get_aliases: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the aliases maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the aliases or NULL on failure. Free it with + * kmod_config_iter_free_iter(). + * + * Since: 4 + */ +struct kmod_config_iter *kmod_config_get_aliases(const struct kmod_ctx *ctx); + +/** + * kmod_config_get_options: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the options maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the options or NULL on failure. Free it with + * kmod_config_iter_free_iter(). + * + * Since: 4 + */ +struct kmod_config_iter *kmod_config_get_options(const struct kmod_ctx *ctx); + +/** + * kmod_config_get_softdeps: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the softdeps maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the softdeps or NULL on failure. Free it with + * kmod_config_iter_free_iter(). + * + * Since: 4 + */ +struct kmod_config_iter *kmod_config_get_softdeps(const struct kmod_ctx *ctx); + +/** + * kmod_config_get_weakdeps: + * @ctx: kmod library context + * + * Retrieve an iterator to deal with the weakdeps maintained inside the + * library. See kmod_config_iter_get_key(), kmod_config_iter_get_value() and + * kmod_config_iter_next(). At least one call to kmod_config_iter_next() must + * be made to initialize the iterator and check if it's valid. + * + * Returns: a new iterator over the weakdeps or NULL on failure. Free it with + * kmod_config_iter_free_iter(). + * + * Since: 33 + */ +struct kmod_config_iter *kmod_config_get_weakdeps(const struct kmod_ctx *ctx); + +/** + * kmod_config_iter_get_key: + * @iter: iterator over a certain configuration + * + * When using a new allocated iterator, user must perform a call to + * kmod_config_iter_next() to initialize iterator's position and check if it's + * valid. + * + * Returns: the key of the current configuration pointed by @iter. + * + * Since: 4 + */ +const char *kmod_config_iter_get_key(const struct kmod_config_iter *iter); + +/** + * kmod_config_iter_get_value: + * @iter: iterator over a certain configuration + * + * When using a new allocated iterator, user must perform a call to + * kmod_config_iter_next() to initialize iterator's position and check if it's + * valid. + * + * Returns: the value of the current configuration pointed by @iter. + * + * Since: 4 + */ +const char *kmod_config_iter_get_value(const struct kmod_config_iter *iter); + +/** + * kmod_config_iter_next: + * @iter: iterator over a certain configuration + * + * Make @iter point to the next item of a certain configuration. It's an + * automatically recycling iterator. When it reaches the end, false is + * returned; then if user wants to iterate again, it's sufficient to call this + * function once more. + * + * Returns: true if next position of @iter is valid or false if its end is + * reached. + * + * Since: 4 + */ +bool kmod_config_iter_next(struct kmod_config_iter *iter); + +/** + * kmod_config_iter_free_iter: + * @iter: iterator over a certain configuration + * + * Free resources used by the iterator. + * + * Since: 4 + */ +void kmod_config_iter_free_iter(struct kmod_config_iter *iter); + +/** + * SECTION:libkmod-module + * @short_description: operate on kernel modules + * + * Wide range of function to operate on kernel modules - loading, unloading, + * reference counting, retrieving a list of module dependencies and more. + */ + +/** + * kmod_module: + * + * Opaque object representing a module. + */ +struct kmod_module; + +/** + * kmod_module_new_from_lookup: + * @ctx: kmod library context + * @given_alias: alias to look for + * @list: an empty list where to save the list of modules matching + * @given_alias + * + * Create a new list of kmod modules using an alias or module name and lookup + * libkmod's configuration files and indexes in order to find the module. + * Once it's found in one of the places, it stops searching and create the + * list of modules that is saved in @list. + * + * The search order is: 1. aliases in configuration file; 2. module names in + * modules.dep index; 3. symbol aliases in modules.symbols index; 4. aliases + * from install commands; 5. builtin indexes from kernel. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. The returned @list must be released by + * calling kmod_module_unref_list(). Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned list are + * completed. + * + * Returns: 0 on success or < 0 otherwise. It fails if any of the lookup + * methods failed, which is basically due to memory allocation fail. If module + * is not found, it still returns 0, but @list is an empty list. + * + * Since: 1 + */ +int kmod_module_new_from_lookup(struct kmod_ctx *ctx, const char *given_alias, + struct kmod_list **list); + +/** + * kmod_module_new_from_name_lookup: + * @ctx: kmod library context + * @modname: module name to look for + * @mod: returned module on success + * + * Lookup by module name, without considering possible aliases. This is similar + * to kmod_module_new_from_lookup(), but don't consider as source indexes and + * configurations that work with aliases. When successful, this always resolves + * to one and only one module. + * + * The search order is: 1. module names in modules.dep index; + * 2. builtin indexes from kernel. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned list are + * completed. + * + * Returns: 0 on success or < 0 otherwise. It fails if any of the lookup + * methods failed, which is basically due to memory allocation failure. If + * module is not found, it still returns 0, but @mod is left untouched. + * + * Since: 30 + */ +int kmod_module_new_from_name_lookup(struct kmod_ctx *ctx, const char *modname, + struct kmod_module **mod); + +/** + * kmod_module_new_from_name: + * @ctx: kmod library context + * @name: name of the module + * @mod: where to save the created struct kmod_module + * + * Create a new struct kmod_module using the module name. @name can not be an + * alias, file name or anything else; it must be a module name. There's no + * check if the module exists in the system. + * + * This function is also used internally by many others that return a new + * struct kmod_module or a new list of modules. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned + * kmod_module are done. + * + * Returns: 0 on success or < 0 otherwise. It fails if name is not a valid + * module name or if memory allocation failed. + * + * Since: 1 + */ +int kmod_module_new_from_name(struct kmod_ctx *ctx, const char *name, + struct kmod_module **mod); + +/** + * kmod_module_new_from_path: + * @ctx: kmod library context + * @path: path where to find the given module + * @mod: where to save the created struct kmod_module + * + * Create a new struct kmod_module using the module path. @path must be an + * existent file within the filesystem and must be accessible to libkmod. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned + * kmod_module are done. + * + * If @path is relative, it's treated as relative to the current working + * directory. Otherwise, give an absolute path. + * + * Returns: 0 on success or < 0 otherwise. It fails if file does not exist, if + * it's not a valid file for a kmod_module or if memory allocation failed. + * + * Since: 1 + */ +int kmod_module_new_from_path(struct kmod_ctx *ctx, const char *path, + struct kmod_module **mod); + +/** + * kmod_module_ref: + * @mod: kmod module + * + * Take a reference of the kmod module, incrementing its refcount. + * + * Returns: the passed @module with its refcount incremented. + * + * Since: 1 + */ +struct kmod_module *kmod_module_ref(struct kmod_module *mod); + +/** + * kmod_module_unref: + * @mod: kmod module + * + * Drop a reference of the kmod module. If the refcount reaches zero, its + * resources are released. + * + * Returns: NULL if @mod is NULL or if the module was released. Otherwise it + * returns the passed @mod with its refcount decremented. + * + * Since: 1 + */ +struct kmod_module *kmod_module_unref(struct kmod_module *mod); + +/** + * kmod_module_unref_list: + * @list: list of kmod modules + * + * Drop a reference of each kmod module in @list and releases the resources + * taken by the list itself. + * + * Returns: 0 + * + * Since: 1 + */ +int kmod_module_unref_list(struct kmod_list *list); + +/** + * kmod_insert: + * @KMOD_INSERT_FORCE_VERMAGIC: ignore kernel version magic + * @KMOD_INSERT_FORCE_MODVERSION: ignore symbol version hashes + * + * Insertion flags, used by kmod_module_insert_module(). + */ +enum kmod_insert { + KMOD_INSERT_FORCE_VERMAGIC = 0x1, + KMOD_INSERT_FORCE_MODVERSION = 0x2, +}; + +/** + * kmod_module_insert_module: + * @mod: kmod module + * @flags: flags are not passed to the kernel, but instead they dictate the + * behavior of this function, valid flags #kmod_insert + * @options: module's options to pass to the kernel. + * + * Insert a module in the kernel. It opens the file pointed by @mod, + * mmap'ing it and passing to kernel. + * + * Returns: 0 on success or < 0 on failure. If module is already loaded it + * returns -EEXIST. + * + * Since: 1 + */ +int kmod_module_insert_module(struct kmod_module *mod, unsigned int flags, + const char *options); + +/** + * kmod_probe: + * @KMOD_PROBE_FORCE_VERMAGIC: ignore kernel version magic + * @KMOD_PROBE_FORCE_MODVERSION: ignore symbol version hashes + * @KMOD_PROBE_IGNORE_COMMAND: ignore install commands and softdeps configured + * in the system + * @KMOD_PROBE_IGNORE_LOADED: do not check whether the module is already + * live in the kernel or not + * @KMOD_PROBE_DRY_RUN: dry run, do not insert module, just call the + * associated callback function + * @KMOD_PROBE_FAIL_ON_LOADED: probe will fail if KMOD_PROBE_IGNORE_LOADED is + * not specified and the module is already live in the kernel + * @KMOD_PROBE_APPLY_BLACKLIST_ALL: prior to probe, apply KMOD_FILTER_BLACKLIST + * filter to this module and its dependencies. If any of them are blacklisted + * and the blacklisted module is not live in the kernel, the function returns + * early with thus enum + * @KMOD_PROBE_APPLY_BLACKLIST: probe will return early with this enum, if the + * module is blacklisted + * @KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY: probe will return early with this + * enum, if the module is an alias and is blacklisted + * + * Bitmask which defines the behaviour of kmod_module_probe_insert_module(). + */ +enum kmod_probe { + KMOD_PROBE_FORCE_VERMAGIC = 0x00001, + KMOD_PROBE_FORCE_MODVERSION = 0x00002, + KMOD_PROBE_IGNORE_COMMAND = 0x00004, + KMOD_PROBE_IGNORE_LOADED = 0x00008, + KMOD_PROBE_DRY_RUN = 0x00010, + KMOD_PROBE_FAIL_ON_LOADED = 0x00020, + + /* codes below can be used in return value, too */ + KMOD_PROBE_APPLY_BLACKLIST_ALL = 0x10000, + KMOD_PROBE_APPLY_BLACKLIST = 0x20000, + KMOD_PROBE_APPLY_BLACKLIST_ALIAS_ONLY = 0x40000, +}; + +/** + * kmod_module_probe_insert_module: + * @mod: kmod module + * @flags: flags are not passed to the kernel, but instead they dictate the + * behavior of this function, valid flags are #kmod_probe + * @extra_options: module's options to pass to the kernel. It applies only + * to @mod, not to its dependencies. + * @run_install: function to run when @mod is backed by an install command. + * @data: data to give back to @run_install callback + * @print_action: function to call with the action being taken (install or + * insmod). It's useful for tools like modprobe when running with verbose + * output or in dry-run mode. + * + * Insert a module in the kernel resolving dependencies, soft dependencies, + * install commands and applying blacklist. + * + * If @run_install is NULL, this function will fork and exec by calling + * system(3). Don't pass a NULL argument in @run_install if your binary is + * setuid/setgid (see warning in system(3)). If you need control over the + * execution of an install command, give a callback function instead. + * + * Returns: 0 on success, > 0 if stopped by a reason given in @flags or < 0 on + * failure. + * + * Since: 3 + */ +int kmod_module_probe_insert_module( + struct kmod_module *mod, unsigned int flags, const char *extra_options, + int (*run_install)(struct kmod_module *m, const char *cmdline, void *data), + const void *data, + void (*print_action)(struct kmod_module *m, bool install, const char *options)); + +/** + * kmod_remove: + * @KMOD_REMOVE_FORCE: force remove module regardless if it's still in + * use by a kernel subsystem or other process; passed directly to the kernel + * @KMOD_REMOVE_NOWAIT: always set, pass O_NONBLOCK to delete_module(2); + * passed directly to the kernel + * @KMOD_REMOVE_NOLOG: when module removal fails, do not log anything; not + * passed to the kernel + * + * Removal flags, used by kmod_module_remove_module(). + */ +enum kmod_remove { + KMOD_REMOVE_FORCE = O_TRUNC, + KMOD_REMOVE_NOWAIT = O_NONBLOCK, + KMOD_REMOVE_NOLOG = 1, +}; + +/** + * kmod_module_remove_module: + * @mod: kmod module + * @flags: flags used when removing the module, valid flags are #kmod_remove + * + * Remove a module from the kernel. + * + * Returns: 0 on success or < 0 on failure. + * + * Since: 1 + */ +int kmod_module_remove_module(struct kmod_module *mod, unsigned int flags); + +/** + * kmod_module_get_module: + * @entry: an entry in a list of kmod modules. + * + * Get the kmod module of this @entry in the list, increasing its refcount. + * After it's used, unref it. Since the refcount is incremented upon return, + * you still have to call kmod_module_unref_list() to release the list of kmod + * modules. + * + * Returns: NULL on failure or the kmod_module contained in this list entry + * with its refcount incremented. + * + * Since: 1 + */ +struct kmod_module *kmod_module_get_module(const struct kmod_list *entry); + +/** + * kmod_module_get_dependencies: + * @mod: kmod module + * + * Search the modules.dep index to find the dependencies of the given @mod. + * The result is cached in @mod, so subsequent calls to this function will + * return the already searched list of modules. + * + * Returns: NULL on failure. Otherwise it returns a list of kmod modules + * that can be released by calling kmod_module_unref_list(). + * + * Since: 1 + */ +struct kmod_list *kmod_module_get_dependencies(const struct kmod_module *mod); + +/** + * kmod_module_get_softdeps: + * @mod: kmod module + * @pre: where to save the list of preceding soft dependencies. + * @post: where to save the list of post soft dependencies. + * + * Get soft dependencies for this kmod module. Soft dependencies come + * from configuration file and are not cached in @mod because it may include + * dependency cycles that would make we leak kmod_module. Any call + * to this function will search for this module in configuration, allocate a + * list and return the result. + * + * Both @pre and @post are newly created list of kmod_module and + * should be unreferenced with kmod_module_unref_list(). + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 2 + */ +int kmod_module_get_softdeps(const struct kmod_module *mod, struct kmod_list **pre, + struct kmod_list **post); + +/** + * kmod_module_get_weakdeps: + * @mod: kmod module + * @weak: where to save the list of weak dependencies. + * + * Get weak dependencies for this kmod module. Weak dependencies come + * from configuration file and are not cached in @mod because it may include + * dependency cycles that would make we leak kmod_module. Any call + * to this function will search for this module in configuration, allocate a + * list and return the result. + * + * @weak is newly created list of kmod_module and + * should be unreferenced with kmod_module_unref_list(). + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 33 + */ +int kmod_module_get_weakdeps(const struct kmod_module *mod, struct kmod_list **weak); + +/** + * kmod_filter: + * @KMOD_FILTER_BLACKLIST: filter modules in blacklist out + * @KMOD_FILTER_BUILTIN: filter builtin modules out + * + * Bitmask defining what gets filtered out, used by kmod_module_apply_filter(). + */ +enum kmod_filter { + KMOD_FILTER_BLACKLIST = 0x00001, + KMOD_FILTER_BUILTIN = 0x00002, +}; + +/** + * kmod_module_apply_filter: + * @ctx: kmod library context + * @filter_type: bitmask to filter modules out, valid types are #kmod_filter + * @input: list of kmod_module to be filtered + * @output: where to save the new list + * + * Given a list @input, this function filter it out by the filter mask + * and save it in @output. + * + * Returns: 0 on success or < 0 otherwise. @output is saved with the updated + * list. + * + * Since: 6 + */ +int kmod_module_apply_filter(const struct kmod_ctx *ctx, enum kmod_filter filter_type, + const struct kmod_list *input, struct kmod_list **output); + +/** + * kmod_module_get_filtered_blacklist: + * @ctx: kmod library context + * @input: list of kmod_module to be filtered with blacklist + * @output: where to save the new list + * + * This function should not be used. Use kmod_module_apply_filter instead. + * + * Given a list @input, this function filter it out with config's blacklist + * and save it in @output. + * + * Returns: 0 on success or < 0 otherwise. @output is saved with the updated + * list. + * + * Since: 1 + * Deprecated: 6: Use #kmod_module_apply_filter instead. + */ +int kmod_module_get_filtered_blacklist(const struct kmod_ctx *ctx, + const struct kmod_list *input, + struct kmod_list **output) + __attribute__((deprecated)); + +/** + * kmod_module_get_install_commands: + * @mod: kmod module + * + * Get install commands for this kmod module. Install commands come from the + * configuration file and are cached in @mod. The first call to this function + * will search for this module in configuration and subsequent calls return + * the cached string. The install commands are returned as they were in the + * configuration, concatenated by ';'. No other processing is made in this + * string. + * + * Returns: a string with all install commands separated by semicolons. This + * string is owned by @mod, do not free it. + * + * Since: 1 + */ +const char *kmod_module_get_install_commands(const struct kmod_module *mod); + +/** + * kmod_module_get_remove_commands: + * @mod: kmod module + * + * Get remove commands for this kmod module. Remove commands come from the + * configuration file and are cached in @mod. The first call to this function + * will search for this module in configuration and subsequent calls return + * the cached string. The remove commands are returned as they were in the + * configuration, concatenated by ';'. No other processing is made in this + * string. + * + * Returns: a string with all remove commands separated by semicolons. This + * string is owned by @mod, do not free it. + * + * Since: 1 + */ +const char *kmod_module_get_remove_commands(const struct kmod_module *mod); + +/** + * kmod_module_get_name: + * @mod: kmod module + * + * Get the name of this kmod module. Name is always available, independently + * if it was created by kmod_module_new_from_name() or another function and + * it's always normalized (dashes are replaced with underscores). + * + * Returns: the name of this kmod module. + * + * Since: 1 + */ +const char *kmod_module_get_name(const struct kmod_module *mod); + +/** + * kmod_module_get_options: + * @mod: kmod module + * + * Get options of this kmod module. Options come from the configuration file + * and are cached in @mod. The first call to this function will search for + * this module in configuration and subsequent calls return the cached string. + * + * Returns: a string with all the options separated by spaces. This string is + * owned by @mod, do not free it. + * + * Since: 1 + */ +const char *kmod_module_get_options(const struct kmod_module *mod); + +/** + * kmod_module_get_path: + * @mod: kmod module + * + * Get the path of this kmod module. If this kmod module was not created by + * path, it can search the modules.dep index in order to find out the module + * under context's dirname. + * + * Returns: the path of this kmod module or NULL if such information is not + * available. + * + * Since: 1 + */ +const char *kmod_module_get_path(const struct kmod_module *mod); + +/** + * kmod_module_get_dependency_symbols: + * @mod: kmod module + * @list: where to return list of module dependency_symbols + * + * Get a list of entries in ELF section ".symtab" or "__ksymtab_strings". + * + * The structure contained in this list is internal to libkmod and its fields + * can be obtainsed by calling kmod_module_dependency_symbol_get_crc() and + * kmod_module_dependency_symbol_get_symbol(). + * + * After use, free the @list by calling + * kmod_module_dependency_symbols_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 3 + */ +int kmod_module_get_dependency_symbols(const struct kmod_module *mod, + struct kmod_list **list); + +/** + * kmod_symbol_bind: + * @KMOD_SYMBOL_NONE: no or unknown symbol type + * @KMOD_SYMBOL_LOCAL: local symbol, accessible only within the module + * @KMOD_SYMBOL_GLOBAL: global symbol, accessible by all modules + * @KMOD_SYMBOL_WEAK: weak symbol, a lower precedence global symbols + * @KMOD_SYMBOL_UNDEF: undefined or not yet resolved symbol + * + * The symbol bind type, see kmod_module_dependency_symbol_get_bind(). + */ +enum kmod_symbol_bind { + KMOD_SYMBOL_NONE = '\0', + KMOD_SYMBOL_LOCAL = 'L', + KMOD_SYMBOL_GLOBAL = 'G', + KMOD_SYMBOL_WEAK = 'W', + KMOD_SYMBOL_UNDEF = 'U', +}; + +/** + * kmod_module_dependency_symbol_get_bind: + * @entry: a list entry representing a kmod module dependency_symbol + * + * Get the bind type of a kmod module dependency_symbol. + * + * Returns: the bind of this kmod module dependency_symbol on success, + * or < 0 on failure. Valid bind types are #kmod_symbol_bind. + * + * Since: 3 + */ +int kmod_module_dependency_symbol_get_bind(const struct kmod_list *entry); + +/** + * kmod_module_dependency_symbol_get_crc: + * @entry: a list entry representing a kmod module dependency_symbol + * + * Get the crc of a kmod module dependency_symbol. + * + * Returns: the crc of this kmod module dependency_symbol if available, otherwise default to 0. + * + * Since: 3 + */ +uint64_t kmod_module_dependency_symbol_get_crc(const struct kmod_list *entry); + +/** + * kmod_module_dependency_symbol_get_symbol: + * @entry: a list entry representing a kmod module dependency_symbols + * + * Get the dependency symbol of a kmod module + * + * Returns: the symbol of this kmod module dependency_symbols on success or NULL + * on failure. The string is owned by the dependency_symbols, do not free it. + * + * Since: 3 + */ +const char *kmod_module_dependency_symbol_get_symbol(const struct kmod_list *entry); + +/** + * kmod_module_dependency_symbols_free_list: + * @list: kmod module dependency_symbols list + * + * Release the resources taken by @list + * + * Since: 3 + */ +void kmod_module_dependency_symbols_free_list(struct kmod_list *list); + +/** + * kmod_module_get_sections: + * @mod: kmod module + * + * Get a list of kmod sections of this @mod, as returned by the kernel. + * + * The structure contained in this list is internal to libkmod and its fields + * can be obtained by calling kmod_module_section_get_name() and + * kmod_module_section_get_address(). + * + * After use, free the @list by calling kmod_module_section_free_list(). + * + * Returns: a new list of kmod module sections on success or NULL on failure. + * + * Since: 1 + */ +struct kmod_list *kmod_module_get_sections(const struct kmod_module *mod); + +/** + * kmod_module_section_get_address: + * @entry: a list entry representing a kmod module section + * + * Get the address of a kmod module section. + * + * Returns: the address of this kmod module section on success or ULONG_MAX + * on failure. + * + * Since: 1 + */ +unsigned long kmod_module_section_get_address(const struct kmod_list *entry); + +/** + * kmod_module_section_get_name: + * @entry: a list entry representing a kmod module section + * + * Get the name of a kmod module section. + * + * Returns: the name of this kmod module section on success or NULL on + * failure. The string is owned by the section, do not free it. + * + * Since: 1 + */ +const char *kmod_module_section_get_name(const struct kmod_list *entry); + +/** + * kmod_module_section_free_list: + * @list: kmod module section list + * + * Release the resources taken by @list + * + * Since: 1 + */ +void kmod_module_section_free_list(struct kmod_list *list); + +/** + * kmod_module_get_symbols: + * @mod: kmod module + * @list: where to return list of module symbols + * + * Get a list of entries in ELF section ".symtab" or "__ksymtab_strings". + * + * The structure contained in this list is internal to libkmod and its fields + * can be obtainsed by calling kmod_module_symbol_get_crc() and + * kmod_module_symbol_get_symbol(). + * + * After use, free the @list by calling kmod_module_symbols_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 3 + */ +int kmod_module_get_symbols(const struct kmod_module *mod, struct kmod_list **list); + +/** + * kmod_module_symbol_get_crc: + * @entry: a list entry representing a kmod module symbol + * + * Get the crc of a kmod module symbol. + * + * Returns: the crc of this kmod module symbol if available, otherwise default to 0. + * + * Since: 3 + */ +uint64_t kmod_module_symbol_get_crc(const struct kmod_list *entry); + +/** + * kmod_module_symbol_get_symbol: + * @entry: a list entry representing a kmod module symbols + * + * Get the symbol of a kmod module symbols. + * + * Returns: the symbol of this kmod module symbols on success or NULL + * on failure. The string is owned by the symbols, do not free it. + * + * Since: 3 + */ +const char *kmod_module_symbol_get_symbol(const struct kmod_list *entry); + +/** + * kmod_module_symbols_free_list: + * @list: kmod module symbols list + * + * Release the resources taken by @list + * + * Since: 3 + */ +void kmod_module_symbols_free_list(struct kmod_list *list); + +/** + * kmod_module_get_versions: + * @mod: kmod module + * @list: where to return list of module versions + * + * Get a list of entries in ELF section "__versions". + * + * The structure contained in this list is internal to libkmod and its fields + * can be obtainsed by calling kmod_module_version_get_crc() and + * kmod_module_version_get_symbol(). + * + * After use, free the @list by calling kmod_module_versions_free_list(). + * + * Returns: 0 on success or < 0 otherwise. + * + * Since: 2 + */ +int kmod_module_get_versions(const struct kmod_module *mod, struct kmod_list **list); + +/** + * kmod_module_version_get_crc: + * @entry: a list entry representing a kmod module version + * + * Get the crc of a kmod module version. + * + * Returns: the crc of this kmod module version if available, otherwise default to 0. + * + * Since: 2 + */ +uint64_t kmod_module_version_get_crc(const struct kmod_list *entry); + +/** + * kmod_module_version_get_symbol: + * @entry: a list entry representing a kmod module versions + * + * Get the symbol of a kmod module versions. + * + * Returns: the symbol of this kmod module versions on success or NULL + * on failure. The string is owned by the versions, do not free it. + * + * Since: 2 + */ +const char *kmod_module_version_get_symbol(const struct kmod_list *entry); + +/** + * kmod_module_versions_free_list: + * @list: kmod module versions list + * + * Release the resources taken by @list + * + * Since: 2 + */ +void kmod_module_versions_free_list(struct kmod_list *list); + +/** + * kmod_module_get_info: + * @mod: kmod module + * @list: where to return list of module information + * + * Get a list of entries in ELF section ".modinfo", these contain + * alias, license, depends, vermagic and other keys with respective + * values. If the module is signed (CONFIG_MODULE_SIG), information + * about the module signature is included as well: signer, + * sig_key and sig_hashalgo. + * + * The structure contained in this list is internal to libkmod and its fields + * can be obtainsed by calling kmod_module_info_get_key() and + * kmod_module_info_get_value(). + * + * After use, free the @list by calling kmod_module_info_free_list(). + * + * Returns: number of entries in @list on success or < 0 otherwise. + * + * Since: 2 + */ +int kmod_module_get_info(const struct kmod_module *mod, struct kmod_list **list); + +/** + * kmod_module_info_get_key: + * @entry: a list entry representing a kmod module info + * + * Get the key of a kmod module info. + * + * Returns: the key of this kmod module info on success or NULL on + * failure. The string is owned by the info, do not free it. + * + * Since: 2 + */ +const char *kmod_module_info_get_key(const struct kmod_list *entry); + +/** + * kmod_module_info_get_value: + * @entry: a list entry representing a kmod module info + * + * Get the value of a kmod module info. + * + * Returns: the value of this kmod module info on success or NULL on + * failure. The string is owned by the info, do not free it. + * + * Since: 2 + */ +const char *kmod_module_info_get_value(const struct kmod_list *entry); + +/** + * kmod_module_info_free_list: + * @list: kmod module info list + * + * Release the resources taken by @list + * + * Since: 2 + */ +void kmod_module_info_free_list(struct kmod_list *list); + +/** + * SECTION:libkmod-loaded + * @short_description: currently loaded modules + * + * Information about currently loaded modules, as reported by the kernel. + * These information are not cached by libkmod and are always read from /sys + * and /proc/modules. + */ + +/** + * kmod_module_new_from_loaded: + * @ctx: kmod library context + * @list: where to save the list of loaded modules + * + * Create a new list of kmod modules with all modules currently loaded in + * kernel. It uses /proc/modules to get the names of loaded modules and to + * create kmod modules by calling kmod_module_new_from_name() in each of them. + * They are put in @list in no particular order. + * + * The initial refcount is 1, and needs to be decremented to release the + * resources of the kmod_module. The returned @list must be released by + * calling kmod_module_unref_list(). Since libkmod keeps track of all + * kmod_modules created, they are all released upon @ctx destruction too. Do + * not unref @ctx before all the desired operations with the returned list are + * completed. + * + * Returns: 0 on success or < 0 on error. + * + * Since: 1 + */ +int kmod_module_new_from_loaded(struct kmod_ctx *ctx, struct kmod_list **list); + +/** + * kmod_module_initstate: + * @KMOD_MODULE_BUILTIN: module is builtin + * @KMOD_MODULE_LIVE: module is live in the kernel + * @KMOD_MODULE_COMING: module is being loaded + * @KMOD_MODULE_GOING: module is being unloaded + * @_KMOD_MODULE_PAD: DO NOT USE; padding to make sure enum is not mapped to char + * + * The module "live information" as reported by the kernel, see + * kmod_module_get_initstate(). + */ +enum kmod_module_initstate { + KMOD_MODULE_BUILTIN = 0, + KMOD_MODULE_LIVE, + KMOD_MODULE_COMING, + KMOD_MODULE_GOING, + _KMOD_MODULE_PAD = 1U << 31, +}; + +/** + * kmod_module_get_initstate: + * @mod: kmod module + * + * Get the initstate of this @mod, as returned by the kernel, by reading + * /sys filesystem. + * + * Returns: < 0 on error or module state if module is found in the kernel, valid + * states are #kmod_module_initstate. + * + * Since: 1 + */ +int kmod_module_get_initstate(const struct kmod_module *mod); + +/** + * kmod_module_initstate_str: + * @state: the state as returned by kmod_module_get_initstate() + * + * Translate a initstate to a string. + * + * Returns: the string associated to the @state. This string is statically + * allocated, do not free it. + * + * Since: 1 + */ +const char *kmod_module_initstate_str(enum kmod_module_initstate state); + +/** + * kmod_module_get_size: + * @mod: kmod module + * + * Get the size of this kmod module as returned by the kernel. If supported, + * the size is read from the coresize attribute in /sys/module. For older + * kernels, this falls back on /proc/modules and searches for the specified + * module to get its size. + * + * Returns: the size of this kmod module. + * + * Since: 1 + */ +long kmod_module_get_size(const struct kmod_module *mod); + +/** + * kmod_module_get_refcnt: + * @mod: kmod module + * + * Get the ref count of this @mod, as returned by the kernel, by reading + * /sys filesystem. + * + * Returns: the reference count on success or < 0 on failure. + * + * Since: 1 + */ +int kmod_module_get_refcnt(const struct kmod_module *mod); + +/** + * kmod_module_get_holders: + * @mod: kmod module + * + * Get a list of kmod modules that are holding this @mod, as returned by Linux + * Kernel. After use, free the @list by calling kmod_module_unref_list(). + * + * Returns: a new list of kmod modules on success or NULL on failure. + * + * Since: 1 + */ +struct kmod_list *kmod_module_get_holders(const struct kmod_module *mod); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif diff --git a/libkmod/libkmod.pc.in b/libkmod/libkmod.pc.in new file mode 100644 index 0000000..3acca6a --- /dev/null +++ b/libkmod/libkmod.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: libkmod +Description: Library to deal with kernel modules +Version: @VERSION@ +Libs: -L${libdir} -lkmod +Libs.private: @libzstd_LIBS@ @liblzma_LIBS@ @zlib_LIBS@ +Cflags: -I${includedir} diff --git a/libkmod/libkmod.sym b/libkmod/libkmod.sym new file mode 100644 index 0000000..ea1cb44 --- /dev/null +++ b/libkmod/libkmod.sym @@ -0,0 +1,107 @@ +# With kmod v5 we bumped the DSO major and re-tagged all symbols. +# +# NOTE: Do not change the existing sections. Add new symbols to new sections. +LIBKMOD_5 { +global: + kmod_get_log_priority; + kmod_get_userdata; + kmod_new; + kmod_ref; + kmod_set_log_fn; + kmod_set_log_priority; + kmod_set_userdata; + kmod_unref; + kmod_list_next; + kmod_list_prev; + kmod_list_last; + + kmod_load_resources; + kmod_unload_resources; + kmod_validate_resources; + kmod_config_get_blacklists; + kmod_config_get_install_commands; + kmod_config_get_remove_commands; + kmod_config_get_aliases; + kmod_config_get_options; + kmod_config_get_softdeps; + kmod_config_iter_get_key; + kmod_config_iter_get_value; + kmod_config_iter_next; + kmod_config_iter_free_iter; + kmod_dump_index; + + kmod_module_new_from_name; + kmod_module_new_from_path; + kmod_module_new_from_lookup; + kmod_module_new_from_loaded; + kmod_module_ref; + kmod_module_unref; + kmod_module_unref_list; + kmod_module_get_module; + kmod_module_remove_module; + kmod_module_insert_module; + kmod_module_probe_insert_module; + + kmod_module_get_dependencies; + kmod_module_get_softdeps; + kmod_module_get_filtered_blacklist; + + kmod_module_get_name; + kmod_module_get_path; + + kmod_module_initstate_str; + kmod_module_get_initstate; + kmod_module_get_refcnt; + kmod_module_get_sections; + kmod_module_section_free_list; + kmod_module_section_get_name; + kmod_module_section_get_address; + kmod_module_get_holders; + kmod_module_get_size; + + kmod_module_get_options; + kmod_module_get_install_commands; + kmod_module_get_remove_commands; + + kmod_module_get_info; + kmod_module_info_get_key; + kmod_module_info_get_value; + kmod_module_info_free_list; + kmod_module_get_versions; + kmod_module_version_get_symbol; + kmod_module_version_get_crc; + kmod_module_versions_free_list; + kmod_module_get_symbols; + kmod_module_symbol_get_symbol; + kmod_module_symbol_get_crc; + kmod_module_symbols_free_list; + + kmod_module_get_dependency_symbols; + kmod_module_dependency_symbol_get_symbol; + kmod_module_dependency_symbol_get_crc; + kmod_module_dependency_symbol_get_bind; + kmod_module_dependency_symbols_free_list; +local: + *; +}; + +LIBKMOD_6 { +global: + kmod_module_apply_filter; +} LIBKMOD_5; + +LIBKMOD_22 { +global: + kmod_get_dirname; +} LIBKMOD_6; + +LIBKMOD_30 { +global: + kmod_module_new_from_name_lookup; +} LIBKMOD_22; + +LIBKMOD_33 { +global: + kmod_config_get_weakdeps; + kmod_module_get_weakdeps; +} LIBKMOD_30; diff --git a/m4/.gitignore b/m4/.gitignore new file mode 100644 index 0000000..d59889a --- /dev/null +++ b/m4/.gitignore @@ -0,0 +1,6 @@ +libtool.m4 +ltoptions.m4 +ltsugar.m4 +ltversion.m4 +lt~obsolete.m4 +gtk-doc.m4 diff --git a/m4/attributes.m4 b/m4/attributes.m4 new file mode 100644 index 0000000..a22bb58 --- /dev/null +++ b/m4/attributes.m4 @@ -0,0 +1,277 @@ +# SPDX-License-Identifier: GPL-2.0-or-later WITH Autoconf-exception-macro +# +# Copyright (c) 2006-2008 Diego Pettenò +# Copyright (c) 2006-2008 xine project +# Copyright (c) 2012 Lucas De Marchi + +# Check if FLAG in ENV-VAR is supported by compiler and append it +# to WHERE-TO-APPEND variable. Note that we invert -Wno-* checks to +# -W* as gcc cannot test for negated warnings. +# CC_CHECK_FLAG_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG]) + +AC_DEFUN([CC_CHECK_FLAG_APPEND], [ + AC_CACHE_CHECK([if $CC supports flag $3 in envvar $2], + AS_TR_SH([cc_cv_$2_$3]), + [eval "AS_TR_SH([cc_save_$2])='${$2}'" + eval "AS_TR_SH([$2])='-Werror `echo "$3" | sed 's/^-Wno-/-W/'`'" + AC_LINK_IFELSE([AC_LANG_SOURCE([int main(void) { return 0; } ])], + [eval "AS_TR_SH([cc_cv_$2_$3])='yes'"], + [eval "AS_TR_SH([cc_cv_$2_$3])='no'"]) + eval "AS_TR_SH([$2])='$cc_save_$2'"]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_$2_$3])[ = xyes], + [eval "$1='${$1} $3'"]) +]) + +dnl CC_CHECK_FLAGS_APPEND([WHERE-TO-APPEND], [ENV-VAR], [FLAG1 FLAG2]) +AC_DEFUN([CC_CHECK_FLAGS_APPEND], [ + for flag in $3; do + CC_CHECK_FLAG_APPEND($1, $2, $flag) + done +]) + +dnl Check if the flag is supported by linker (cacheable) +dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) + + +dnl Check if the flag is supported by linker +dnl CC_CHECK_LDFLAGS_SILENT([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) +AC_DEFUN([CC_CHECK_LDFLAGS_SILENT], [ + AC_CACHE_VAL(AS_TR_SH([cc_cv_ldflags_$1]), + [ac_save_LDFLAGS="$LDFLAGS" + LDFLAGS="$LDFLAGS $1" + AC_LINK_IFELSE([AC_LANG_SOURCE([int main() { return 1; }])], + [eval "AS_TR_SH([cc_cv_ldflags_$1])='yes'"], + [eval "AS_TR_SH([cc_cv_ldflags_$1])="]) + LDFLAGS="$ac_save_LDFLAGS" + ]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes], + [$2], [$3]) +]) + +dnl Check if the flag is supported by linker (cacheable) +dnl CC_CHECK_LDFLAGS([FLAG], [ACTION-IF-FOUND],[ACTION-IF-NOT-FOUND]) + +AC_DEFUN([CC_CHECK_LDFLAGS], [ + AC_CACHE_CHECK([if $CC supports $1 flag], + AS_TR_SH([cc_cv_ldflags_$1]), + CC_CHECK_LDFLAGS_SILENT([$1]) dnl Don't execute actions here! + ) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_ldflags_$1])[ = xyes], + [$2], [$3]) +]) + +dnl define the LDFLAGS_NOUNDEFINED variable with the correct value for +dnl the current linker to avoid undefined references in a shared object. +AC_DEFUN([CC_NOUNDEFINED], [ + dnl We check $host for which systems to enable this for. + AC_REQUIRE([AC_CANONICAL_HOST]) + + case $host in + dnl FreeBSD (et al.) does not complete linking for shared objects when pthreads + dnl are requested, as different implementations are present; to avoid problems + dnl use -Wl,-z,defs only for those platform not behaving this way. + *-freebsd* | *-openbsd*) ;; + *) + dnl First of all check for the --no-undefined variant of GNU ld. This allows + dnl for a much more readable command line, so that people can understand what + dnl it does without going to look for what the heck -z defs does. + for possible_flags in "-Wl,--no-undefined" "-Wl,-z,defs"; do + CC_CHECK_LDFLAGS([$possible_flags], [LDFLAGS_NOUNDEFINED="$possible_flags"]) + break + done + ;; + esac + + AC_SUBST([LDFLAGS_NOUNDEFINED]) +]) + +dnl Check for a -Werror flag or equivalent. -Werror is the GCC +dnl and ICC flag that tells the compiler to treat all the warnings +dnl as fatal. We usually need this option to make sure that some +dnl constructs (like attributes) are not simply ignored. +dnl +dnl Other compilers don't support -Werror per se, but they support +dnl an equivalent flag: +dnl - Sun Studio compiler supports -errwarn=%all +AC_DEFUN([CC_CHECK_WERROR], [ + AC_CACHE_CHECK( + [for $CC way to treat warnings as errors], + [cc_cv_werror], + [CC_CHECK_CFLAGS_SILENT([-Werror], [cc_cv_werror=-Werror], + [CC_CHECK_CFLAGS_SILENT([-errwarn=%all], [cc_cv_werror=-errwarn=%all])]) + ]) +]) + +AC_DEFUN([CC_CHECK_ATTRIBUTE], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if $CC supports __attribute__(( ifelse([$2], , [$1], [$2]) ))], + AS_TR_SH([cc_cv_attribute_$1]), + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + AC_COMPILE_IFELSE([AC_LANG_SOURCE([$3])], + [eval "AS_TR_SH([cc_cv_attribute_$1])='yes'"], + [eval "AS_TR_SH([cc_cv_attribute_$1])='no'"]) + CFLAGS="$ac_save_CFLAGS" + ]) + + AS_IF([eval test x$]AS_TR_SH([cc_cv_attribute_$1])[ = xyes], + [AC_DEFINE( + AS_TR_CPP([SUPPORT_ATTRIBUTE_$1]), 1, + [Define this if the compiler supports __attribute__(( ifelse([$2], , [$1], [$2]) ))] + ) + $4], + [$5]) +]) + +AC_DEFUN([CC_ATTRIBUTE_CONSTRUCTOR], [ + CC_CHECK_ATTRIBUTE( + [constructor],, + [void __attribute__((constructor)) ctor() { int a; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_FORMAT], [ + CC_CHECK_ATTRIBUTE( + [format], [format(printf, n, n)], + [void __attribute__((format(printf, 1, 2))) printflike(const char *fmt, ...) { fmt = (void *)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_FORMAT_ARG], [ + CC_CHECK_ATTRIBUTE( + [format_arg], [format_arg(printf)], + [char *__attribute__((format_arg(1))) gettextlike(const char *fmt) { fmt = (void *)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_VISIBILITY], [ + CC_CHECK_ATTRIBUTE( + [visibility_$1], [visibility("$1")], + [void __attribute__((visibility("$1"))) $1_function() { }], + [$2], [$3]) +]) + +AC_DEFUN([CC_ATTRIBUTE_NONNULL], [ + CC_CHECK_ATTRIBUTE( + [nonnull], [nonnull()], + [void __attribute__((nonnull())) some_function(void *foo, void *bar) { foo = (void*)0; bar = (void*)0; }], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_UNUSED], [ + CC_CHECK_ATTRIBUTE( + [unused], , + [void some_function(void *foo, __attribute__((unused)) void *bar);], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_SENTINEL], [ + CC_CHECK_ATTRIBUTE( + [sentinel], , + [void some_function(void *foo, ...) __attribute__((sentinel));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_DEPRECATED], [ + CC_CHECK_ATTRIBUTE( + [deprecated], , + [void some_function(void *foo, ...) __attribute__((deprecated));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_ALIAS], [ + CC_CHECK_ATTRIBUTE( + [alias], [weak, alias], + [void other_function(void *foo) { } + void some_function(void *foo) __attribute__((weak, alias("other_function")));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_MALLOC], [ + CC_CHECK_ATTRIBUTE( + [malloc], , + [void * __attribute__((malloc)) my_alloc(int n);], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_PACKED], [ + CC_CHECK_ATTRIBUTE( + [packed], , + [struct astructure { char a; int b; long c; void *d; } __attribute__((packed));], + [$1], [$2]) +]) + +AC_DEFUN([CC_ATTRIBUTE_CONST], [ + CC_CHECK_ATTRIBUTE( + [const], , + [int __attribute__((const)) twopow(int n) { return 1 << n; } ], + [$1], [$2]) +]) + +AC_DEFUN([CC_FLAG_VISIBILITY], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([if $CC supports -fvisibility=hidden], + [cc_cv_flag_visibility], + [cc_flag_visibility_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + CC_CHECK_CFLAGS_SILENT([-fvisibility=hidden], + cc_cv_flag_visibility='yes', + cc_cv_flag_visibility='no') + CFLAGS="$cc_flag_visibility_save_CFLAGS"]) + + AS_IF([test "x$cc_cv_flag_visibility" = "xyes"], + [AC_DEFINE([SUPPORT_FLAG_VISIBILITY], 1, + [Define this if the compiler supports the -fvisibility flag]) + $1], + [$2]) +]) + +AC_DEFUN([CC_CHECK_FUNC_BUILTIN], [ + m4_pushdef([UPNAME], m4_translit([$1], [-a-z], [_A-Z])) + AC_CACHE_CHECK([if compiler has $1 builtin function], + [cc_cv_have_$1], + [AC_LINK_IFELSE([AC_LANG_PROGRAM([], [ + m4_case([$1], + [__builtin_clz], [$1(0)], + [__builtin_types_compatible_p], [$1(int, int)], + [__builtin_uaddl_overflow], [$1(0UL, 0UL, (void*)0)], + [__builtin_uaddll_overflow], [$1(0ULL, 0ULL, (void*)0)], + [__builtin_expect], [$1(0, 0)] + )])], + [cc_cv_have_$1=yes], + [cc_cv_have_$1=no])]) + + AS_IF([test "x$cc_cv_have_$1" = "xyes"], + [AC_DEFINE([HAVE_]m4_defn([UPNAME]), 1, [Define to 1 if compiler has $1() builtin function]) + $2], + [AS_IF([test "x$3" = "x"], [AC_MSG_ERROR([*** builtin function not found: $1()])], + [AC_DEFINE([HAVE_]m4_defn([UPNAME]), 0) + $3])] + ) + m4_popdef([UPNAME]) +]) + +AC_DEFUN([CC_ATTRIBUTE_ALIGNED], [ + AC_REQUIRE([CC_CHECK_WERROR]) + AC_CACHE_CHECK([highest __attribute__ ((aligned ())) supported], + [cc_cv_attribute_aligned], + [ac_save_CFLAGS="$CFLAGS" + CFLAGS="$CFLAGS $cc_cv_werror" + for cc_attribute_align_try in 64 32 16 8 4 2; do + AC_COMPILE_IFELSE([AC_LANG_SOURCE([ + int main() { + static char c __attribute__ ((aligned($cc_attribute_align_try))) = 0; + return c; + }])], [cc_cv_attribute_aligned=$cc_attribute_align_try; break]) + done + CFLAGS="$ac_save_CFLAGS" + ]) + + if test "x$cc_cv_attribute_aligned" != "x"; then + AC_DEFINE_UNQUOTED([ATTRIBUTE_ALIGNED_MAX], [$cc_cv_attribute_aligned], + [Define the highest alignment supported]) + fi +]) diff --git a/m4/features.m4 b/m4/features.m4 new file mode 100644 index 0000000..6384a05 --- /dev/null +++ b/m4/features.m4 @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: GPL-2.0-or-later WITH Autoconf-exception-macro +# +# Copyright (c) 2015 Lucas De Marchi + +# CC_FEATURE_APPEND([FLAGS], [ENV-TO-CHECK], [FLAG-NAME]) +AC_DEFUN([CC_FEATURE_APPEND], [ + AS_VAR_PUSHDEF([FLAGS], [$1])dnl + AS_VAR_PUSHDEF([ENV_CHECK], [$2])dnl + AS_VAR_PUSHDEF([FLAG_NAME], [$3])dnl + + AS_CASE([" AS_VAR_GET(FLAGS) " ], + [*" FLAG_NAME "*], [AC_RUN_LOG([: FLAGS already contains FLAG_NAME])], + [ + AS_IF([test "x$FLAGS" != "x"], [AS_VAR_APPEND(FLAGS, " ")]) + AS_IF([test "x$ENV_CHECK" = "xyes"], + [AS_VAR_APPEND(FLAGS, "+FLAG_NAME")], + [AS_VAR_APPEND(FLAGS, "-FLAG_NAME")]) + ] + ) + + AS_VAR_POPDEF([FLAG_NAME])dnl + AS_VAR_POPDEF([ENV_CHECK])dnl + AS_VAR_POPDEF([FLAGS])dnl +]) diff --git a/man/.gitignore b/man/.gitignore new file mode 100644 index 0000000..a229b2f --- /dev/null +++ b/man/.gitignore @@ -0,0 +1,4 @@ +*.5 +*.8 +Makefile +Makefile.in diff --git a/man/Makefile.am b/man/Makefile.am new file mode 100644 index 0000000..6356d87 --- /dev/null +++ b/man/Makefile.am @@ -0,0 +1,28 @@ +MAN5 = depmod.d.5 modprobe.d.5 modules.dep.5 +MAN8 = kmod.8 depmod.8 insmod.8 lsmod.8 rmmod.8 modprobe.8 modinfo.8 +MAN_STUB = modules.dep.bin.5 + +AM_V_SCDOC = $(AM_V_SCDOC_$(V)) +AM_V_SCDOC_ = $(AM_V_SCDOC_$(AM_DEFAULT_VERBOSITY)) +AM_V_SCDOC_0 = @echo " SCDOC " $@; + +if BUILD_TOOLS +dist_man_MANS = $(MAN5) $(MAN8) $(MAN_STUB) +endif + +EXTRA_DIST = $(MAN5:%.5=%.5.scd) $(MAN8:%.8=%.8.scd) +CLEANFILES = $(filter-out $(MAN_STUB), $(dist_man_MANS)) + +define generate_manpage + $(AM_V_SCDOC)cat $< | \ + sed -e 's|@SYSCONFDIR@|$(sysconfdir)|g' | \ + sed -e 's|@DISTCONFDIR@|$(distconfdir)|g' | \ + sed -e 's|@MODULE_DIRECTORY@|$(module_directory)|g' | \ + $(SCDOC) > $@ +endef + +%.5: %.5.scd + $(generate_manpage) + +%.8: %.8.scd + $(generate_manpage) diff --git a/man/depmod.8.scd b/man/depmod.8.scd new file mode 100644 index 0000000..4a4b51a --- /dev/null +++ b/man/depmod.8.scd @@ -0,0 +1,181 @@ +DEPMOD(8) "kmod" "depmod" + +# NAME + +depmod - Generate modules.dep and map files. + +# SYNOPSIS + +*depmod* [*-b* _basedir_] [*-m* _moduledir_] [*-o* _outdir_] [*-e*] [*-E* _Module.symvers_] +\ \ \ \ \ \ \ \[*-F* _System.map_] [*-n*] [*-v*] [*-A*] [*-P* _prefix_] [*-w*] [_version_] + +*depmod* [*-e*] [*-E* _Module.symvers_] [*-F* _System.map_] [*-n*] [*-v*] [*-P* _prefix_] +\ \ \ \ \ \ \ \[*-w*] [_version_] [_filename_...] + +# DESCRIPTION + +Linux kernel modules can provide services (called "symbols") for other modules +to use (using one of the EXPORT_SYMBOL variants in the code). If a second module +uses this symbol, that second module clearly depends on the first module. These +dependencies can get quite complex. + +*depmod* creates a list of module dependencies by reading each module under +//_version_. By default is @MODULE_DIRECTORY@ +and is empty. See options below to override when needed. It determines +what symbols each module exports and needs. This list is written to +*modules.dep*, and a binary hashed version named modules.dep.bin, in the same +directory. If filenames are given on the command line, only those modules are +examined (which is rarely useful unless all modules are listed). *depmod* also +creates a list of symbols provided by modules in the file named modules.symbols +and its binary hashed version, modules.symbols.bin. Finally, *depmod* will +output a file named modules.devname if modules supply special device names +(devname) that should be populated in /dev on boot (by a utility such as +systemd-tmpfiles). + +If a _version_ is provided, then that kernel version's module directory is used +rather than the current kernel version (as returned by *uname -r*). + +# OPTIONS + +*-a*, *--all* + Probe all modules. This option is enabled by default if no file names + are given in the command-line. + +*-A*, *--quick* + This option scans to see if any modules are newer than the + *modules.dep* file before any work is done: if not, it silently exits + rather than regenerating the files. + +*-b* _basedir_, *--basedir*=_basedir_ + Override the base directory where modules are located. + If your modules are not currently in the (normal) directory + @MODULE_DIRECTORY@/_version_, but in a staging area, you can specify a + _basedir_ which is prepended to the directory name. This _basedir_ is + stripped from the resulting *modules.dep* file, so it is ready to be + moved into the normal location. Use this option if you are a + distribution vendor who needs to pre-generate the meta-data files rather + than running *depmod* again later. + + If a relative path is given, it's relative to the current working + directory. + + Example: + depmod -b /my/build/staging/dir/ + + This expects all input files under + _/my/build/staging/dir@MODULE_DIRECTORY@/$(uname -r)_ and generates + index files under that same directory. + +*-m* _moduledir_, *--moduledir*=_moduledir_ + Override the module directory , which defaults to + @MODULE_DIRECTORY@ prefix set at build time. This is useful when + building *modules.dep* file in _basedir_ for a system that uses a + different prefix, e.g. _/usr/lib/modules_ vs _/lib/modules_. + + Relative and absolute paths are accepted, but they are always relative + to the _basedir_. + + Examples: + depmod -b /tmp/build -m /kernel-modules++ +depmod -b /tmp/build -m kernel-modules + + This expects all input files under + _/tmp/build/kernel-modules/$(uname -r)_ and generates index files under + that same directory. + + Without an accompanying *-b* argument, the moduledir is relative to _/_. + Example: + + depmod -m foo/bar + + This expects all input files under _/foo/bar/$(uname -r)_ and generates + index files under the same directory. Unless libkmod is prepared to + handle that arbitrary location, it won't work in runtime. + + +*-o* _outdir_, *--outdir*=_outdir_ + Set the output directory where *depmod* will store any generated file. + _outdir_ serves as a root to that location, similar to how _basedir_ is + used. Also this setting takes precedence and if used together with + _basedir_ it will result in the input being that directory, but the output + being the one set by _outdir_. + + If a relative path is given, it's relative to the current working + directory. + + Example: + depmod -o /my/build/staging/dir/ + + This expects all input files under + _@MODULE_DIRECTORY@/$(uname -r)_ and generates index files under + _/my/build/staging/dir@MODULE_DIRECTORY@/$(uname -r)_. + +*-C* _file_ _or_ _directory_, *--config*=_file_ _or_ _directory_ + This option overrides the default configuration files. See + *depmod.d*(5). + +*-e*, *--errsyms* + When combined with the *-F* option, this reports any symbols which a + module needs which are not supplied by other modules or the kernel. + Normally, any symbols not provided by modules are assumed to be provided + by the kernel (which should be true in a perfect world), but this + assumption can break especially when additionally updated third party + drivers are not correctly installed or were built incorrectly. + +*-E* _Module.symvers_, *--symvers*=_Module.symvers_ + When combined with the *-e* option, this reports any symbol versions + supplied by modules that do not match with the symbol versions provided + by the kernel in its _Module.symvers_. This option is mutually + incompatible with *-F*. + +*-F* _System.map_, *--filesyms*=_System.map_ + Supplied with the _System.map_ produced when the kernel was built, this + allows the *-e* option to report unresolved symbols. This option is + mutually incompatible with *-E*. + +*-h*, *--help* + Print the help message and exit. + +*-n*, *--show*, *--dry-run* + This sends the resulting *modules.dep* and the various map files to + standard output rather than writing them into the module directory. + +*-P* + Some architectures prefix symbols with an extraneous character. This + specifies a prefix character (for example '\_') to ignore. + +*-v*, *--verbose* + In verbose mode, *depmod* will print (to stdout) all the symbols each + module depends on and the module's file name which provides that symbol. + +*-V*, *--version* + Show version of program and exit. See below for caveats when run on + older kernels. + +*-w* + Warn on duplicate dependencies, aliases, symbol versions, etc. + +# COPYRIGHT + +This manual page originally Copyright 2002, Rusty Russell, IBM Corporation. +Portions Copyright Jon Masters, and others. + +# SEE ALSO + +*depmod.d*(5), *modprobe*(8), *modules.dep*(5) + +# BUGS + +Please direct any bug reports to kmod's issue tracker at +https://github.com/kmod-project/kmod/issues/ alongside with version used, steps +to reproduce the problem and the expected outcome. + +# AUTHORS + +Numerous contributions have come from the linux-modules mailing list + and Github. If you have a clone of kmod.git +itself, the output of *git-shortlog*(1) and *git-blame*(1) can show you the +authors for specific parts of the project. + +*Lucas De Marchi* is the current maintainer of the +project. diff --git a/man/depmod.d.5.scd b/man/depmod.d.5.scd new file mode 100644 index 0000000..cb8de4d --- /dev/null +++ b/man/depmod.d.5.scd @@ -0,0 +1,118 @@ +DEPMOD.D(5) "kmod" "depmod.d" + +# NAME + +depmod.d - Configuration directory for depmod + +# SYNOPSIS + +@SYSCONFDIR@/depmod.d/\*.conf + +/run/depmod.d/\*.conf + +/usr/local/lib/depmod.d/\*.conf + +@DISTCONFDIR@/depmod.d/\*.conf + +/lib/depmod.d/\*.conf + +# DESCRIPTION + +On execution *depmod* reads the configuration files from the above location and +based on that it processes the available modules and their dependencies. For +example: one can change the search order, exclude folders, override specific +module's location and more. + +This is typically useful in cases where built-in kernel modules are complemented +by custom built versions of the same and the user wishes to affect the priority +of processing in order to override the module version supplied by the kernel. + +# CONFIGURATION FORMAT + +The configuration files contain one command per line, with blank lines and lines +starting with '#' ignored (useful for adding comments). A '\\' at the end of a +line causes it to continue on the next line, which makes the files a bit neater. + +See the COMMANDS section below for more. + +# CONFIGURATION DIRECTORIES AND PRECEDENCE + +Configuration files are read from directories in listed in SYNOPSIS in that +order of precedence. Once a file of a given filename is loaded, any file of the +same name in subsequent directories is ignored. + +All configuration files are sorted in lexicographic order, regardless of the +directory they reside in. Configuration files can either be completely replaced +(by having a new configuration file with the same name in a directory of higher +priority) or partially replaced (by having a configuration file that is ordered +later). + +# COMMANDS + +search _subdirectory..._ + This allows you to specify the order in which @MODULE_DIRECTORY@ (or + other configured module location) subdirectories will be processed by + *depmod*. Directories are listed in order, with the highest priority + given to the first listed directory and the lowest priority given to the + last directory listed. The special keyword *built-in* refers to the + standard module directories installed by the kernel. Another special + keyword *external* refers to the list of external directories, defined + by the *external* command. + + By default, depmod will give a higher priority to a directory with the + name *updates* using this built-in search string: "updates built-in" but + more complex arrangements are possible and are used in several popular + distributions. + +override _modulename_ _kernelversion_ _modulesubdirectory_ + This command allows you to override which version of a specific module + will be used when more than one module sharing the same name is + processed by the *depmod* command. It is possible to specify one kernel + or all kernels using the \* wildcard. _modulesubdirectory_ is the name + of the subdirectory under @MODULE_DIRECTORY@ (or other module location) + where the target module is installed. + + For example, it is possible to override the priority of an updated test + module called *kmod* by specifying the following command: "override + kmod \* extra". This will ensure that any matching module name installed + under the *extra* subdirectory within @MODULE_DIRECTORY@ (or other + module location) will take priority over any likenamed module already + provided by the kernel. + +external _kernelversion_ _absolutemodulesdirectory..._ + This specifies a list of directories, which will be checked according to + the priorities in the *search* command. The order matters also, the + first directory has the higher priority. + + The _kernelversion_ is a POSIX regular expression or \* wildcard, like + in the *override*. + +exclude _excludedir_ + This specifies the trailing directories that will be excluded during the + search for kernel modules. + + The _excludedir_ is the trailing directory to exclude. + +# COPYRIGHT + +This manual page Copyright 2006-2010, Jon Masters, Red Hat, Inc. + +# SEE ALSO + +*depmod*(8) + +# BUGS + +Please direct any bug reports to kmod's issue tracker at +https://github.com/kmod-project/kmod/issues/ alongside with version used, steps +to reproduce the problem and the expected outcome. + +# AUTHORS + +Numerous contributions have come from the linux-modules mailing list + and Github. If you have a clone of kmod.git +itself, the output of *git-shortlog*(1) and *git-blame*(1) can show you the +authors for specific parts of the project. + +*Lucas De Marchi* is the current maintainer of the +project. diff --git a/man/insmod.8.scd b/man/insmod.8.scd new file mode 100644 index 0000000..a5c4264 --- /dev/null +++ b/man/insmod.8.scd @@ -0,0 +1,64 @@ +INSMOD(8) "kmod" "insmod" + +# NAME + +insmod - Simple program to insert a module into the Linux Kernel + +# SYNOPSIS + +*insmod* [_OPTIONS_] [_filename_] [_module options_] + +# DESCRIPTION + +*insmod* is a trivial program to insert a module into the kernel. Most users +will want to use *modprobe*(8) instead, which is more clever and can handle +module dependencies. + +Only the most general of error messages are reported: as the work of trying to +link the module is now done inside the kernel, the *dmesg*(1) usually gives more +information about errors. + +# OPTIONS + +*-f*, *--force* + This option can be extremely dangerous: it tells the kernel to ignore + the module version and vermagic fields when loading. With this option, + you can load modules build locally or by third parties, although this + can lead to memory corruption, system crashes and data loss. + +*-s*, *--syslog* + Send errors to syslog instead of standard error. + +*-v*, *--verbose* + Print messages about what the program is doing. Usually *insmod* prints + messages only if something goes wrong. + +*-V*, *--version* + Show version of program and exit. + +*-h*, *--help* + Print the help message and exit. + +# COPYRIGHT + +This manual page originally Copyright 2002, Rusty Russell, IBM Corporation. + +# SEE ALSO + +*modprobe*(8), *rmmod*(8), *lsmod*(8), *modinfo*(8), *depmod*(8) + +# BUGS + +Please direct any bug reports to kmod's issue tracker at +https://github.com/kmod-project/kmod/issues/ alongside with version used, steps +to reproduce the problem and the expected outcome. + +# AUTHORS + +Numerous contributions have come from the linux-modules mailing list + and Github. If you have a clone of kmod.git +itself, the output of *git-shortlog*(1) and *git-blame*(1) can show you the +authors for specific parts of the project. + +*Lucas De Marchi* is the current maintainer of the +project. diff --git a/man/kmod.8.scd b/man/kmod.8.scd new file mode 100644 index 0000000..b196bc1 --- /dev/null +++ b/man/kmod.8.scd @@ -0,0 +1,62 @@ +KMOD(8) "kmod" "kmod" + +# NAME + +kmod - Program to manage Linux Kernel modules + +# SYNOPSIS + +*kmod* [*OPTIONS...*] [_COMMAND_] [*COMMAND_OPTIONS...*] + +# DESCRIPTION + +*kmod* is a multi-call binary which implements the programs used to control +Linux Kernel modules. Most users will only run it using its other names. + +# OPTIONS + +*-V*, *--version* + Show the program version and exit. + +*-h*, *--help* + Show the help message. + +# COMMANDS + +*help* + Show the help message. + +*list* + List the currently loaded modules. + +*static-nodes* + Output the static device nodes information provided by the modules of + the currently running kernel version. + +# COPYRIGHT + +This manual page originally Copyright 2014, Marco d'Itri. + +# SEE ALSO + +*lsmod*(8), *rmmod*(8), *insmod*(8), *modinfo*(8), *modprobe*(8), *depmod*(8) + +# BUGS + +Please direct any bug reports to kmod's issue tracker at +https://github.com/kmod-project/kmod/issues/ alongside with version used, steps +to reproduce the problem and the expected outcome. + +# AUTHOR + +*kmod* project was started by Lucas De Marchi as a drop-in replacement to +module-init-tools that was maintained by Jon Masters, adding a library (libkmod) +and additional features. + +Numerous contributions have come from the linux-modules mailing list + and Github. If you have a clone of kmod.git +itself, the output of *git-shortlog*(1) and *git-blame*(1) can show you the +authors for specific parts of the project. + +*Lucas De Marchi* is the current maintainer of the +project. diff --git a/man/lsmod.8.scd b/man/lsmod.8.scd new file mode 100644 index 0000000..339526d --- /dev/null +++ b/man/lsmod.8.scd @@ -0,0 +1,53 @@ +LSMOD(8) "kmod" "lsmod" + +# NAME + +lsmod - Show the status of modules in the Linux Kernel + +# SYNOPSIS + +*lsmod* [_OPTIONS_] + +# DESCRIPTION + +*lsmod* is a trivial program which nicely formats the contents of the +_/proc/modules_, showing what kernel modules are currently loaded. + +# OPTIONS + +*-s*, *--syslog* + Send errors to syslog instead of standard error. + +*-v*, *--verbose* + Print messages about what the program is doing. Usually *lsmod* prints + messages only if something goes wrong. + +*-V*, *--version* + Show version of program and exit. + +*-h*, *--help* + Print the help message and exit. + +# COPYRIGHT + +This manual page originally Copyright 2002, Rusty Russell, IBM Corporation. + +# SEE ALSO + +*insmod*(8), *modprobe*(8), *modinfo*(8), *depmod*(8) + +# BUGS + +Please direct any bug reports to kmod's issue tracker at +https://github.com/kmod-project/kmod/issues/ alongside with version used, steps +to reproduce the problem and the expected outcome. + +# AUTHORS + +Numerous contributions have come from the linux-modules mailing list + and Github. If you have a clone of kmod.git +itself, the output of *git-shortlog*(1) and *git-blame*(1) can show you the +authors for specific parts of the project. + +*Lucas De Marchi* is the current maintainer of the +project. diff --git a/man/meson.build b/man/meson.build new file mode 100644 index 0000000..a9eb3cf --- /dev/null +++ b/man/meson.build @@ -0,0 +1,57 @@ +scdoc = find_program('scdoc') + +_man_pages = [ + ['5', 'depmod.d'], + ['5', 'modprobe.d'], + ['5', 'modules.dep'], + ['8', 'depmod'], + ['8', 'insmod'], + ['8', 'kmod'], + ['8', 'lsmod'], + ['8', 'modinfo'], + ['8', 'modprobe'], + ['8', 'rmmod'], +] +foreach tuple : _man_pages + section = tuple[0] + man = tuple[1] + + custom_target( + 'man_@0@_@1@'.format(section, man), + command : [ + build_scdoc, + scdoc, + '@INPUT@', + 's|@SYSCONFDIR@|@0@|g;'.format(sysconfdir) + + 's|@DISTCONFDIR@|@0@|g;'.format(distconfdir) + + 's|@MODULE_DIRECTORY@|@0@|g;'.format(moduledir), + ], + input : '@0@.@1@.scd'.format(man, section), + output : '@0@.@1@'.format(man, section), + capture : true, + build_by_default : get_option('manpages'), + install : true, + install_dir : get_option('mandir') / 'man@0@'.format(section) + ) +endforeach + +_man_aliases = [ + ['5', 'modprobe.conf', 'modprobe.d.5'], + ['5', 'modules.dep.bin', 'modules.dep.5'], +] + +foreach tuple : _man_aliases + section = tuple[0] + man = tuple[1] + dest = tuple[2] + + custom_target( + 'man_@0@_@1@'.format(section, man), + command : ['echo', '.so @0@'.format(dest)], + output : '@0@.@1@'.format(man, section), + capture : true, + build_by_default : get_option('manpages'), + install : true, + install_dir : get_option('mandir') / 'man@0@'.format(section) + ) +endforeach diff --git a/man/modinfo.8.scd b/man/modinfo.8.scd new file mode 100644 index 0000000..9601ad5 --- /dev/null +++ b/man/modinfo.8.scd @@ -0,0 +1,87 @@ +MODINFO(8) "kmod" "modinfo" + +# NAME + +modinfo - Show information about a Linux Kernel module + +# SYNOPSIS + +*modinfo* [*-0*] [*-F* _field_] [*-k* _kernel_] [modulename|filename...] + +*modinfo* *-V* + +*modinfo* *-h* + +# DESCRIPTION + +*modinfo* extracts information from the Linux Kernel modules given on the +command line. If the module name is not a filename, then the @MODULE_DIRECTORY@/ +_version_ directory is searched, as is also done by *modprobe*(8) when loading +kernel modules. + +*modinfo* by default lists each attribute of the module in form _fieldname_ : +_value_, for easy reading. The filename is listed the same way (although it's +not really an attribute). + +This version of *modinfo* can understand modules of any Linux Kernel +architecture. + +# OPTIONS + +*-V*, *--version* + Print the *modinfo* version. + +*-F* _field_, *--field*=_field_ + Only print this _field_ value, one per line. This is most useful for + scripts. Field names are case-insensitive. Common fields (which may not + be in every module) include author, description, license, parm, depends, + and alias. There are often multiple parm, alias and depends fields. The + special _field_ filename lists the filename of the module. + +*-b* _basedir_, *--basedir*=_basedir_ + Root directory for modules, / by default. + +*-k* _kernel_ + Provide information about a kernel other than the running one. This is + particularly useful for distributions needing to extract information + from a newly installed (but not yet running) set of kernel modules. For + example, you wish to find which firmware files are needed by various + modules in a new kernel for which you must make an initrd/initramfs + image prior to booting. + +*-0*, *--null* + Use the ASCII zero character to separate _field_ values, instead of a new + line. This is useful for scripts, since a new line can theoretically + appear inside a _field_. + +*-a* *--author*, *-d* *--description*, *-l* *--license*, *-p* *--parameters*, *-n* *--filename* + These are shortcuts for the *--field* flag's author, description, + license, parm and filename arguments, to ease the transition from the + old modutils *modinfo*. + +*-h*, *--help* + Print the help message and exit. + +# COPYRIGHT + +This manual page originally Copyright 2003, Rusty Russell, IBM Corporation. + +# SEE ALSO + +*modprobe*(8) + +# BUGS + +Please direct any bug reports to kmod's issue tracker at +https://github.com/kmod-project/kmod/issues/ alongside with version used, steps +to reproduce the problem and the expected outcome. + +# AUTHORS + +Numerous contributions have come from the linux-modules mailing list + and Github. If you have a clone of kmod.git +itself, the output of *git-shortlog*(1) and *git-blame*(1) can show you the +authors for specific parts of the project. + +*Lucas De Marchi* is the current maintainer of the +project. diff --git a/man/modprobe.8.scd b/man/modprobe.8.scd new file mode 100644 index 0000000..13738c1 --- /dev/null +++ b/man/modprobe.8.scd @@ -0,0 +1,223 @@ +MODPROBE(8) "kmod" "modprobe" + +# NAME + +modprobe - Add and remove modules from the Linux Kernel + +# SYNOPSIS + +*modprobe* [*-v*] [*-V*] [*-C* _config-file_] [*-n*] [*-i*] [*-q*] [*-b*] [_modulename_] +\ \ \ \ \ \ \ \ \ \[_module parameters_...] + +*modprobe* [*-r*] [*-v*] [*-n*] [*-i*] [_modulename_...] + +*modprobe* [*-c*] + +*modprobe* [*--dump-modversions*] [_filename_] + +# DESCRIPTION + +*modprobe* intelligently adds or removes a module from the Linux kernel: note +that for convenience, there is no difference between \_ and - in module names +(automatic underscore conversion is performed). *modprobe* looks in the module +directory @DISTCONFDIR@/`uname -r` for all the modules and other files, except +for the optional configuration files (see *modprobe.d*(5)). *modprobe* will also +use module options specified on the kernel command line in the form of +.